summaryrefslogtreecommitdiffstats
path: root/vcl
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
commit940b4d1848e8c70ab7642901a68594e8016caffc (patch)
treeeb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /vcl
parentInitial commit. (diff)
downloadlibreoffice-940b4d1848e8c70ab7642901a68594e8016caffc.tar.xz
libreoffice-940b4d1848e8c70ab7642901a68594e8016caffc.zip
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
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_apitests.mk64
-rw-r--r--vcl/CppunitTest_vcl_app_test.mk32
-rw-r--r--vcl/CppunitTest_vcl_backend_test.mk50
-rw-r--r--vcl/CppunitTest_vcl_bitmap_render_test.mk48
-rw-r--r--vcl/CppunitTest_vcl_bitmap_test.mk64
-rw-r--r--vcl/CppunitTest_vcl_bitmapprocessor_test.mk55
-rw-r--r--vcl/CppunitTest_vcl_blocklistparser_test.mk45
-rw-r--r--vcl/CppunitTest_vcl_complextext.mk55
-rw-r--r--vcl/CppunitTest_vcl_dialogs_test.mk68
-rw-r--r--vcl/CppunitTest_vcl_errorhandler.mk49
-rw-r--r--vcl/CppunitTest_vcl_filter_ipdf.mk49
-rw-r--r--vcl/CppunitTest_vcl_filters_test.mk53
-rw-r--r--vcl/CppunitTest_vcl_font.mk49
-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.mk43
-rw-r--r--vcl/CppunitTest_vcl_graphic_test.mk57
-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.mk48
-rw-r--r--vcl/CppunitTest_vcl_png_test.mk54
-rw-r--r--vcl/CppunitTest_vcl_svm_test.mk59
-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_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.mk33
-rw-r--r--vcl/Executable_602fuzzer.mk48
-rw-r--r--vcl/Executable_bmpfuzzer.mk47
-rw-r--r--vcl/Executable_cgmfuzzer.mk48
-rw-r--r--vcl/Executable_diffuzzer.mk48
-rw-r--r--vcl/Executable_docxfuzzer.mk50
-rw-r--r--vcl/Executable_dxffuzzer.mk47
-rw-r--r--vcl/Executable_epsfuzzer.mk47
-rw-r--r--vcl/Executable_fftester.mk39
-rw-r--r--vcl/Executable_fodpfuzzer.mk50
-rw-r--r--vcl/Executable_fodsfuzzer.mk50
-rw-r--r--vcl/Executable_fodtfuzzer.mk50
-rw-r--r--vcl/Executable_giffuzzer.mk47
-rw-r--r--vcl/Executable_htmlfuzzer.mk50
-rw-r--r--vcl/Executable_hwpfuzzer.mk48
-rw-r--r--vcl/Executable_icontest.mk67
-rw-r--r--vcl/Executable_jpgfuzzer.mk47
-rw-r--r--vcl/Executable_lo_kde5filepicker.mk94
-rw-r--r--vcl/Executable_lwpfuzzer.mk48
-rw-r--r--vcl/Executable_metfuzzer.mk47
-rw-r--r--vcl/Executable_mmlfuzzer.mk50
-rw-r--r--vcl/Executable_mtfdemo.mk54
-rw-r--r--vcl/Executable_mtpfuzzer.mk48
-rw-r--r--vcl/Executable_olefuzzer.mk47
-rw-r--r--vcl/Executable_pcdfuzzer.mk47
-rw-r--r--vcl/Executable_pctfuzzer.mk47
-rw-r--r--vcl/Executable_pcxfuzzer.mk47
-rw-r--r--vcl/Executable_pngfuzzer.mk47
-rw-r--r--vcl/Executable_ppmfuzzer.mk47
-rw-r--r--vcl/Executable_pptfuzzer.mk48
-rw-r--r--vcl/Executable_pptxfuzzer.mk50
-rw-r--r--vcl/Executable_psdfuzzer.mk47
-rw-r--r--vcl/Executable_qpwfuzzer.mk48
-rw-r--r--vcl/Executable_rasfuzzer.mk47
-rw-r--r--vcl/Executable_rtffuzzer.mk48
-rw-r--r--vcl/Executable_scrtffuzzer.mk48
-rw-r--r--vcl/Executable_sftfuzzer.mk47
-rw-r--r--vcl/Executable_slkfuzzer.mk48
-rw-r--r--vcl/Executable_svdemo.mk38
-rw-r--r--vcl/Executable_svmfuzzer.mk47
-rw-r--r--vcl/Executable_svpclient.mk42
-rw-r--r--vcl/Executable_svptest.mk38
-rw-r--r--vcl/Executable_tgafuzzer.mk47
-rw-r--r--vcl/Executable_tiffuzzer.mk47
-rw-r--r--vcl/Executable_ui-previewer.mk54
-rw-r--r--vcl/Executable_vcldemo.mk68
-rw-r--r--vcl/Executable_visualbackendtest.mk56
-rw-r--r--vcl/Executable_wksfuzzer.mk50
-rw-r--r--vcl/Executable_wmffuzzer.mk47
-rw-r--r--vcl/Executable_ww2fuzzer.mk48
-rw-r--r--vcl/Executable_ww6fuzzer.mk48
-rw-r--r--vcl/Executable_ww8fuzzer.mk48
-rw-r--r--vcl/Executable_xbmfuzzer.mk47
-rw-r--r--vcl/Executable_xlsfuzzer.mk50
-rw-r--r--vcl/Executable_xlsxfuzzer.mk50
-rw-r--r--vcl/Executable_xpmfuzzer.mk47
-rw-r--r--vcl/IwyuFilter_vcl.yaml128
-rw-r--r--vcl/Library_desktop_detector.mk72
-rw-r--r--vcl/Library_vcl.mk736
-rw-r--r--vcl/Library_vclplug_gen.mk161
-rw-r--r--vcl/Library_vclplug_gtk3.mk124
-rw-r--r--vcl/Library_vclplug_gtk3_kde5.mk130
-rw-r--r--vcl/Library_vclplug_kf5.mk92
-rw-r--r--vcl/Library_vclplug_osx.mk153
-rw-r--r--vcl/Library_vclplug_qt5.mk131
-rw-r--r--vcl/Library_vclplug_win.mk109
-rw-r--r--vcl/Makefile14
-rw-r--r--vcl/Module_vcl.mk255
-rw-r--r--vcl/Package_fontunxppds.mk25
-rw-r--r--vcl/Package_fontunxpsprint.mk25
-rw-r--r--vcl/Package_opengl_blacklist.mk16
-rw-r--r--vcl/Package_opengl_shader.mk41
-rw-r--r--vcl/Package_osxres.mk18
-rw-r--r--vcl/Package_skia_blacklist.mk16
-rw-r--r--vcl/Package_theme_definitions.mk57
-rw-r--r--vcl/Package_tipoftheday.mk20
-rw-r--r--vcl/README242
-rw-r--r--vcl/README.GDIMetaFile190
-rw-r--r--vcl/README.lifecycle325
-rw-r--r--vcl/README.scheduler394
-rw-r--r--vcl/README.vars52
-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_fuzzerstubs.mk47
-rw-r--r--vcl/StaticLibrary_glxtest.mk41
-rw-r--r--vcl/StaticLibrary_vclmain.mk42
-rw-r--r--vcl/UIConfig_vcl.mk34
-rw-r--r--vcl/WinResTarget_vcl.mk97
-rw-r--r--vcl/android/androidinst.cxx250
-rw-r--r--vcl/backendtest/VisualBackendTest.cxx690
-rw-r--r--vcl/backendtest/outputdevice/bitmap.cxx193
-rw-r--r--vcl/backendtest/outputdevice/clip.cxx83
-rw-r--r--vcl/backendtest/outputdevice/common.cxx478
-rw-r--r--vcl/backendtest/outputdevice/gradient.cxx43
-rw-r--r--vcl/backendtest/outputdevice/line.cxx200
-rw-r--r--vcl/backendtest/outputdevice/outputdevice.cxx95
-rw-r--r--vcl/backendtest/outputdevice/pixel.cxx56
-rw-r--r--vcl/backendtest/outputdevice/polygon.cxx157
-rw-r--r--vcl/backendtest/outputdevice/polyline.cxx139
-rw-r--r--vcl/backendtest/outputdevice/polyline_b2d.cxx127
-rw-r--r--vcl/backendtest/outputdevice/polypolygon.cxx72
-rw-r--r--vcl/backendtest/outputdevice/polypolygon_b2d.cxx70
-rw-r--r--vcl/backendtest/outputdevice/rectangle.cxx114
-rw-r--r--vcl/commonfuzzer.mk172
-rw-r--r--vcl/headless/CustomWidgetDraw.cxx421
-rw-r--r--vcl/headless/headlessinst.cxx101
-rw-r--r--vcl/headless/svpbmp.cxx283
-rw-r--r--vcl/headless/svpcairotextrender.cxx40
-rw-r--r--vcl/headless/svpdata.cxx33
-rw-r--r--vcl/headless/svpdummies.cxx58
-rw-r--r--vcl/headless/svpframe.cxx502
-rw-r--r--vcl/headless/svpgdi.cxx2622
-rw-r--r--vcl/headless/svpinst.cxx627
-rw-r--r--vcl/headless/svpprn.cxx269
-rw-r--r--vcl/headless/svptext.cxx121
-rw-r--r--vcl/headless/svpvd.cxx148
-rw-r--r--vcl/inc/BitmapColorizeFilter.hxx34
-rw-r--r--vcl/inc/BitmapDisabledImageFilter.hxx26
-rw-r--r--vcl/inc/BitmapFastScaleFilter.hxx36
-rw-r--r--vcl/inc/BitmapInterpolateScaleFilter.hxx35
-rw-r--r--vcl/inc/BitmapLightenFilter.hxx24
-rw-r--r--vcl/inc/BitmapScaleConvolutionFilter.hxx78
-rw-r--r--vcl/inc/BitmapScaleSuperFilter.hxx41
-rw-r--r--vcl/inc/BitmapSymmetryCheck.hxx31
-rw-r--r--vcl/inc/ControlCacheKey.hxx94
-rw-r--r--vcl/inc/FileDefinitionWidgetDraw.hxx61
-rw-r--r--vcl/inc/IconThemeScanner.hxx92
-rw-r--r--vcl/inc/IconThemeSelector.hxx97
-rw-r--r--vcl/inc/OptionalBox.hxx42
-rw-r--r--vcl/inc/PhysicalFontCollection.hxx96
-rw-r--r--vcl/inc/PhysicalFontFace.hxx78
-rw-r--r--vcl/inc/PhysicalFontFamily.hxx101
-rw-r--r--vcl/inc/ResampleKernel.hxx116
-rw-r--r--vcl/inc/SalGradient.hxx36
-rw-r--r--vcl/inc/TypeSerializer.hxx52
-rw-r--r--vcl/inc/WidgetDrawInterface.hxx126
-rw-r--r--vcl/inc/WidgetThemeLibrary.hxx159
-rw-r--r--vcl/inc/WidgetThemeLibraryTypes.hxx235
-rw-r--r--vcl/inc/accel.h41
-rw-r--r--vcl/inc/accmgr.hxx53
-rw-r--r--vcl/inc/android/androidinst.hxx48
-rw-r--r--vcl/inc/android/svsys.h17
-rw-r--r--vcl/inc/backend/BackendCapabilities.hxx28
-rw-r--r--vcl/inc/bitmap/Octree.hxx82
-rw-r--r--vcl/inc/bitmap/ScanlineTools.hxx236
-rw-r--r--vcl/inc/bitmap/impoctree.hxx109
-rw-r--r--vcl/inc/bitmaps.hlst228
-rw-r--r--vcl/inc/bitmapwriteaccess.hxx94
-rw-r--r--vcl/inc/bmpfast.hxx45
-rw-r--r--vcl/inc/brdwin.hxx294
-rw-r--r--vcl/inc/canvasbitmap.hxx121
-rw-r--r--vcl/inc/configsettings.hxx66
-rw-r--r--vcl/inc/controldata.hxx38
-rw-r--r--vcl/inc/cursor_hotspots.hxx169
-rw-r--r--vcl/inc/dbggui.hxx29
-rw-r--r--vcl/inc/debugevent.hxx36
-rw-r--r--vcl/inc/displayconnectiondispatch.hxx63
-rw-r--r--vcl/inc/dndeventdispatcher.hxx111
-rw-r--r--vcl/inc/dndlistenercontainer.hxx112
-rw-r--r--vcl/inc/driverblocklist.hxx176
-rw-r--r--vcl/inc/factory.hxx79
-rw-r--r--vcl/inc/fltcall.hxx37
-rw-r--r--vcl/inc/font/FeatureCollector.hxx55
-rw-r--r--vcl/inc/font/OpenTypeFeatureDefinitionList.hxx48
-rw-r--r--vcl/inc/font/OpenTypeFeatureStrings.hrc106
-rw-r--r--vcl/inc/fontattributes.hxx126
-rw-r--r--vcl/inc/fontinstance.hxx126
-rw-r--r--vcl/inc/fontselect.hxx83
-rw-r--r--vcl/inc/fontsubset.hxx94
-rw-r--r--vcl/inc/graphic/DetectorTools.hxx61
-rw-r--r--vcl/inc/graphic/GraphicFormatDetector.hxx76
-rw-r--r--vcl/inc/graphic/GraphicID.hxx47
-rw-r--r--vcl/inc/graphic/GraphicReader.hxx40
-rw-r--r--vcl/inc/graphic/Manager.hxx76
-rw-r--r--vcl/inc/graphic/UnoGraphic.hxx82
-rw-r--r--vcl/inc/graphic/UnoGraphicDescriptor.hxx121
-rw-r--r--vcl/inc/graphic/UnoGraphicTransformer.hxx58
-rw-r--r--vcl/inc/headless/CustomWidgetDraw.hxx56
-rw-r--r--vcl/inc/headless/svpbmp.hxx89
-rw-r--r--vcl/inc/headless/svpcairotextrender.hxx32
-rw-r--r--vcl/inc/headless/svpdummies.hxx64
-rw-r--r--vcl/inc/headless/svpframe.hxx124
-rw-r--r--vcl/inc/headless/svpgdi.hxx287
-rw-r--r--vcl/inc/headless/svpinst.hxx195
-rw-r--r--vcl/inc/headless/svpprn.hxx39
-rw-r--r--vcl/inc/headless/svpvd.hxx68
-rw-r--r--vcl/inc/helpwin.hxx84
-rw-r--r--vcl/inc/hyperlabel.hxx71
-rw-r--r--vcl/inc/iconview.hxx39
-rw-r--r--vcl/inc/image.h72
-rw-r--r--vcl/inc/imagerepository.hxx59
-rw-r--r--vcl/inc/impanmvw.hxx98
-rw-r--r--vcl/inc/impdel.hxx80
-rw-r--r--vcl/inc/impfont.hxx137
-rw-r--r--vcl/inc/impfontcache.hxx99
-rw-r--r--vcl/inc/impfontcharmap.hxx58
-rw-r--r--vcl/inc/impfontmetricdata.hxx149
-rw-r--r--vcl/inc/impglyphitem.hxx140
-rw-r--r--vcl/inc/impgraph.hxx214
-rw-r--r--vcl/inc/implimagetree.hxx158
-rw-r--r--vcl/inc/ios/iosinst.hxx51
-rw-r--r--vcl/inc/ios/svsys.h17
-rw-r--r--vcl/inc/jobdata.hxx87
-rw-r--r--vcl/inc/jobset.h112
-rw-r--r--vcl/inc/jsdialog/jsdialogbuilder.hxx133
-rw-r--r--vcl/inc/langboost.hxx18
-rw-r--r--vcl/inc/listbox.hxx608
-rw-r--r--vcl/inc/messagedialog.hxx58
-rw-r--r--vcl/inc/opengl/BufferObject.hxx84
-rw-r--r--vcl/inc/opengl/DeviceInfo.hxx23
-rw-r--r--vcl/inc/opengl/FixedTextureAtlas.hxx47
-rw-r--r--vcl/inc/opengl/LineRenderUtils.hxx55
-rw-r--r--vcl/inc/opengl/PackedTextureAtlas.hxx57
-rw-r--r--vcl/inc/opengl/RenderList.hxx173
-rw-r--r--vcl/inc/opengl/RenderState.hxx188
-rw-r--r--vcl/inc/opengl/TextureState.hxx76
-rw-r--r--vcl/inc/opengl/VertexUtils.hxx120
-rw-r--r--vcl/inc/opengl/framebuffer.hxx48
-rw-r--r--vcl/inc/opengl/gdiimpl.hxx395
-rw-r--r--vcl/inc/opengl/program.hxx122
-rw-r--r--vcl/inc/opengl/salbmp.hxx113
-rw-r--r--vcl/inc/opengl/texture.hxx139
-rw-r--r--vcl/inc/opengl/win/WinDeviceInfo.hxx103
-rw-r--r--vcl/inc/opengl/win/gdiimpl.hxx97
-rw-r--r--vcl/inc/opengl/win/winlayout.hxx51
-rw-r--r--vcl/inc/opengl/x11/X11DeviceInfo.hxx75
-rw-r--r--vcl/inc/opengl/x11/cairotextrender.hxx27
-rw-r--r--vcl/inc/opengl/x11/gdiimpl.hxx42
-rw-r--r--vcl/inc/opengl/x11/glxtest.hxx21
-rw-r--r--vcl/inc/opengl/x11/salvd.hxx57
-rw-r--r--vcl/inc/opengl/zone.hxx46
-rw-r--r--vcl/inc/osx/a11yfactory.h42
-rw-r--r--vcl/inc/osx/a11yfocustracker.hxx103
-rw-r--r--vcl/inc/osx/a11ylistener.hxx53
-rw-r--r--vcl/inc/osx/a11ywrapper.h110
-rw-r--r--vcl/inc/osx/keyboardfocuslistener.hxx37
-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.hxx111
-rw-r--r--vcl/inc/osx/salframe.h224
-rw-r--r--vcl/inc/osx/salframeview.h213
-rw-r--r--vcl/inc/osx/salinst.h162
-rw-r--r--vcl/inc/osx/salmenu.h112
-rw-r--r--vcl/inc/osx/salnativewidgets.h70
-rw-r--r--vcl/inc/osx/salnsmenu.h53
-rw-r--r--vcl/inc/osx/salnstimer.h35
-rw-r--r--vcl/inc/osx/salobj.h68
-rw-r--r--vcl/inc/osx/salprn.h152
-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/outdata.hxx32
-rw-r--r--vcl/inc/outdev.h144
-rw-r--r--vcl/inc/pch/precompiled_vcl.cxx12
-rw-r--r--vcl/inc/pch/precompiled_vcl.hxx340
-rw-r--r--vcl/inc/pdf/BitmapID.hxx44
-rw-r--r--vcl/inc/pdf/Matrix3.hxx54
-rw-r--r--vcl/inc/pdf/ResourceDict.hxx42
-rw-r--r--vcl/inc/pdf/XmpMetadata.hxx51
-rw-r--r--vcl/inc/ppdparser.hxx274
-rw-r--r--vcl/inc/print.h73
-rw-r--r--vcl/inc/print.hrc115
-rw-r--r--vcl/inc/printaccessoryview.hrc36
-rw-r--r--vcl/inc/printdlg.hxx292
-rw-r--r--vcl/inc/printerinfomanager.hxx175
-rw-r--r--vcl/inc/qt5/Qt5AccessibleEventListener.hxx38
-rw-r--r--vcl/inc/qt5/Qt5AccessibleWidget.hxx144
-rw-r--r--vcl/inc/qt5/Qt5Bitmap.hxx66
-rw-r--r--vcl/inc/qt5/Qt5Clipboard.hxx97
-rw-r--r--vcl/inc/qt5/Qt5Data.hxx47
-rw-r--r--vcl/inc/qt5/Qt5DragAndDrop.hxx115
-rw-r--r--vcl/inc/qt5/Qt5FilePicker.hxx175
-rw-r--r--vcl/inc/qt5/Qt5Font.hxx41
-rw-r--r--vcl/inc/qt5/Qt5FontFace.hxx67
-rw-r--r--vcl/inc/qt5/Qt5Frame.hxx225
-rw-r--r--vcl/inc/qt5/Qt5Graphics.hxx202
-rw-r--r--vcl/inc/qt5/Qt5GraphicsBase.hxx30
-rw-r--r--vcl/inc/qt5/Qt5Graphics_Controls.hxx97
-rw-r--r--vcl/inc/qt5/Qt5Instance.hxx161
-rw-r--r--vcl/inc/qt5/Qt5MainWindow.hxx40
-rw-r--r--vcl/inc/qt5/Qt5Menu.hxx122
-rw-r--r--vcl/inc/qt5/Qt5Object.hxx79
-rw-r--r--vcl/inc/qt5/Qt5OpenGLContext.hxx50
-rw-r--r--vcl/inc/qt5/Qt5Painter.hxx67
-rw-r--r--vcl/inc/qt5/Qt5Printer.hxx32
-rw-r--r--vcl/inc/qt5/Qt5SvpGraphics.hxx55
-rw-r--r--vcl/inc/qt5/Qt5SvpSurface.hxx44
-rw-r--r--vcl/inc/qt5/Qt5System.hxx23
-rw-r--r--vcl/inc/qt5/Qt5Timer.hxx47
-rw-r--r--vcl/inc/qt5/Qt5Tools.hxx154
-rw-r--r--vcl/inc/qt5/Qt5Transferable.hxx124
-rw-r--r--vcl/inc/qt5/Qt5VirtualDevice.hxx56
-rw-r--r--vcl/inc/qt5/Qt5Widget.hxx103
-rw-r--r--vcl/inc/qt5/Qt5XAccessible.hxx34
-rw-r--r--vcl/inc/quartz/CGHelpers.hxx105
-rw-r--r--vcl/inc/quartz/common.h51
-rw-r--r--vcl/inc/quartz/ctfonts.hxx31
-rw-r--r--vcl/inc/quartz/salbmp.h105
-rw-r--r--vcl/inc/quartz/salgdi.h425
-rw-r--r--vcl/inc/quartz/salgdicommon.hxx111
-rw-r--r--vcl/inc/quartz/salvd.h73
-rw-r--r--vcl/inc/quartz/utils.h50
-rw-r--r--vcl/inc/regband.hxx129
-rw-r--r--vcl/inc/regionband.hxx82
-rw-r--r--vcl/inc/salbmp.hxx125
-rw-r--r--vcl/inc/saldatabasic.hxx67
-rw-r--r--vcl/inc/salframe.hxx311
-rw-r--r--vcl/inc/salgdi.hxx638
-rw-r--r--vcl/inc/salgdiimpl.hxx211
-rw-r--r--vcl/inc/salgeom.hxx72
-rw-r--r--vcl/inc/salinst.hxx223
-rw-r--r--vcl/inc/sallayout.hxx226
-rw-r--r--vcl/inc/salmenu.hxx102
-rw-r--r--vcl/inc/salobj.hxx79
-rw-r--r--vcl/inc/salprn.hxx122
-rw-r--r--vcl/inc/salptype.hxx52
-rw-r--r--vcl/inc/salsession.hxx113
-rw-r--r--vcl/inc/salsys.hxx90
-rw-r--r--vcl/inc/saltimer.hxx103
-rw-r--r--vcl/inc/salusereventlist.hxx122
-rw-r--r--vcl/inc/salvd.hxx58
-rw-r--r--vcl/inc/salvtables.hxx1105
-rw-r--r--vcl/inc/salwtype.hxx274
-rw-r--r--vcl/inc/scanlinewriter.hxx90
-rw-r--r--vcl/inc/schedulerimpl.hxx70
-rw-r--r--vcl/inc/scrptrun.h158
-rw-r--r--vcl/inc/scrwnd.hxx82
-rw-r--r--vcl/inc/sft.hxx752
-rw-r--r--vcl/inc/skia/gdiimpl.hxx332
-rw-r--r--vcl/inc/skia/salbmp.hxx134
-rw-r--r--vcl/inc/skia/utils.hxx135
-rw-r--r--vcl/inc/skia/win/gdiimpl.hxx102
-rw-r--r--vcl/inc/skia/x11/gdiimpl.hxx48
-rw-r--r--vcl/inc/skia/x11/salvd.hxx46
-rw-r--r--vcl/inc/skia/x11/textrender.hxx40
-rw-r--r--vcl/inc/skia/zone.hxx30
-rw-r--r--vcl/inc/slider.hxx103
-rw-r--r--vcl/inc/spin.hxx43
-rw-r--r--vcl/inc/strhelper.hxx72
-rw-r--r--vcl/inc/strings.hrc158
-rw-r--r--vcl/inc/strings.hxx17
-rw-r--r--vcl/inc/svdata.hxx462
-rw-r--r--vcl/inc/svimpbox.hxx398
-rw-r--r--vcl/inc/svmconverter.hxx90
-rw-r--r--vcl/inc/svsys.h41
-rw-r--r--vcl/inc/test/outputdevice.hxx223
-rw-r--r--vcl/inc/textlayout.hxx112
-rw-r--r--vcl/inc/textlineinfo.hxx71
-rw-r--r--vcl/inc/textrender.hxx74
-rw-r--r--vcl/inc/toolbox.h158
-rw-r--r--vcl/inc/treeglue.hxx174
-rw-r--r--vcl/inc/uiobject-internal.hxx34
-rw-r--r--vcl/inc/units.hrc62
-rw-r--r--vcl/inc/unx/XIM.h111
-rw-r--r--vcl/inc/unx/cairotextrender.hxx42
-rw-r--r--vcl/inc/unx/cpdmgr.hxx126
-rw-r--r--vcl/inc/unx/cupsmgr.hxx90
-rw-r--r--vcl/inc/unx/desktops.hxx40
-rw-r--r--vcl/inc/unx/fc_fontoptions.hxx43
-rw-r--r--vcl/inc/unx/fontmanager.hxx322
-rw-r--r--vcl/inc/unx/freetype_glyphcache.hxx127
-rw-r--r--vcl/inc/unx/freetypetextrender.hxx75
-rw-r--r--vcl/inc/unx/gendata.hxx102
-rw-r--r--vcl/inc/unx/gendisp.hxx55
-rw-r--r--vcl/inc/unx/geninst.h84
-rw-r--r--vcl/inc/unx/genprn.h94
-rw-r--r--vcl/inc/unx/genpspgraphics.h213
-rw-r--r--vcl/inc/unx/gensys.h50
-rw-r--r--vcl/inc/unx/glyphcache.hxx174
-rw-r--r--vcl/inc/unx/gstsink.hxx27
-rw-r--r--vcl/inc/unx/gtk/atkbridge.hxx30
-rw-r--r--vcl/inc/unx/gtk/gloactiongroup.h78
-rw-r--r--vcl/inc/unx/gtk/glomenu.h137
-rw-r--r--vcl/inc/unx/gtk/gtkbackend.hxx25
-rw-r--r--vcl/inc/unx/gtk/gtkdata.hxx152
-rw-r--r--vcl/inc/unx/gtk/gtkframe.hxx567
-rw-r--r--vcl/inc/unx/gtk/gtkgdi.hxx399
-rw-r--r--vcl/inc/unx/gtk/gtkinst.hxx298
-rw-r--r--vcl/inc/unx/gtk/gtkobject.hxx107
-rw-r--r--vcl/inc/unx/gtk/gtkprintwrapper.hxx69
-rw-r--r--vcl/inc/unx/gtk/gtkprn.hxx51
-rw-r--r--vcl/inc/unx/gtk/gtksalmenu.hxx151
-rw-r--r--vcl/inc/unx/gtk/gtksys.hxx48
-rw-r--r--vcl/inc/unx/gtk/hudawareness.h32
-rw-r--r--vcl/inc/unx/helper.hxx54
-rw-r--r--vcl/inc/unx/i18n_cb.hxx91
-rw-r--r--vcl/inc/unx/i18n_ic.hxx80
-rw-r--r--vcl/inc/unx/i18n_im.hxx54
-rw-r--r--vcl/inc/unx/i18n_keysym.hxx67
-rw-r--r--vcl/inc/unx/i18n_xkb.hxx67
-rw-r--r--vcl/inc/unx/nativewindowhandleprovider.hxx25
-rw-r--r--vcl/inc/unx/printergfx.hxx347
-rw-r--r--vcl/inc/unx/printerjob.hxx128
-rw-r--r--vcl/inc/unx/salbmp.h231
-rw-r--r--vcl/inc/unx/saldata.hxx75
-rw-r--r--vcl/inc/unx/saldisp.hxx428
-rw-r--r--vcl/inc/unx/salframe.h270
-rw-r--r--vcl/inc/unx/salgdi.h340
-rw-r--r--vcl/inc/unx/salinst.h95
-rw-r--r--vcl/inc/unx/salobj.h91
-rw-r--r--vcl/inc/unx/saltimer.h40
-rw-r--r--vcl/inc/unx/saltype.h26
-rw-r--r--vcl/inc/unx/salunx.h28
-rw-r--r--vcl/inc/unx/salunxtime.h80
-rw-r--r--vcl/inc/unx/salvd.h79
-rw-r--r--vcl/inc/unx/screensaverinhibitor.hxx71
-rw-r--r--vcl/inc/unx/sm.hxx81
-rw-r--r--vcl/inc/unx/svsys.h29
-rw-r--r--vcl/inc/unx/wmadaptor.hxx302
-rw-r--r--vcl/inc/unx/x11/x11cairotextrender.hxx46
-rw-r--r--vcl/inc/unx/x11/x11gdiimpl.h25
-rw-r--r--vcl/inc/unx/x11/x11sys.hxx44
-rw-r--r--vcl/inc/unx/x11/xlimits.hxx20
-rw-r--r--vcl/inc/unx/x11/xrender_peer.hxx165
-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/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.h151
-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.hxx37
-rw-r--r--vcl/inc/vclpluginapi.h76
-rw-r--r--vcl/inc/vclstatuslistener.hxx108
-rw-r--r--vcl/inc/wall2.hxx50
-rw-r--r--vcl/inc/watchdog.hxx32
-rw-r--r--vcl/inc/widgetdraw/WidgetDefinition.hxx298
-rw-r--r--vcl/inc/widgetdraw/WidgetDefinitionReader.hxx45
-rw-r--r--vcl/inc/win/DWriteTextRenderer.hxx101
-rw-r--r--vcl/inc/win/salbmp.h115
-rw-r--r--vcl/inc/win/saldata.hxx305
-rw-r--r--vcl/inc/win/salframe.h154
-rw-r--r--vcl/inc/win/salgdi.h429
-rw-r--r--vcl/inc/win/salids.hrc93
-rw-r--r--vcl/inc/win/salinst.h87
-rw-r--r--vcl/inc/win/salmenu.h68
-rw-r--r--vcl/inc/win/salobj.h55
-rw-r--r--vcl/inc/win/salprn.h113
-rw-r--r--vcl/inc/win/salsys.h70
-rw-r--r--vcl/inc/win/saltimer.h86
-rw-r--r--vcl/inc/win/salvd.h70
-rw-r--r--vcl/inc/win/scoped_gdi.hxx75
-rw-r--r--vcl/inc/win/svsys.h33
-rw-r--r--vcl/inc/win/wincomp.hxx234
-rw-r--r--vcl/inc/win/wingdiimpl.hxx61
-rw-r--r--vcl/inc/win/winlayout.hxx187
-rw-r--r--vcl/inc/window.h438
-rw-r--r--vcl/inc/wizdlg.hxx315
-rw-r--r--vcl/ios/DataFlavorMapping.cxx571
-rw-r--r--vcl/ios/DataFlavorMapping.hxx127
-rw-r--r--vcl/ios/HtmlFmtFlt.cxx172
-rw-r--r--vcl/ios/HtmlFmtFlt.hxx41
-rw-r--r--vcl/ios/clipboard.cxx185
-rw-r--r--vcl/ios/clipboard.hxx111
-rw-r--r--vcl/ios/dummies.cxx138
-rw-r--r--vcl/ios/iOSTransferable.cxx186
-rw-r--r--vcl/ios/iOSTransferable.hxx73
-rw-r--r--vcl/ios/iosinst.cxx197
-rw-r--r--vcl/jsdialog/jsdialogbuilder.cxx207
-rw-r--r--vcl/null/printerinfomanager.cxx120
-rw-r--r--vcl/opengl/DeviceInfo.cxx16
-rw-r--r--vcl/opengl/FixedTextureAtlas.cxx137
-rw-r--r--vcl/opengl/LineRenderUtils.cxx189
-rw-r--r--vcl/opengl/PackedTextureAtlas.cxx192
-rw-r--r--vcl/opengl/README.deprecated23
-rw-r--r--vcl/opengl/README.opengl26
-rw-r--r--vcl/opengl/RenderList.cxx403
-rw-r--r--vcl/opengl/framebuffer.cxx108
-rw-r--r--vcl/opengl/gdiimpl.cxx2315
-rw-r--r--vcl/opengl/opengl_blacklist_windows.xml67
-rw-r--r--vcl/opengl/program.cxx390
-rw-r--r--vcl/opengl/salbmp.cxx783
-rw-r--r--vcl/opengl/scale.cxx423
-rw-r--r--vcl/opengl/shaders/areaHashCRC64TFragmentShader.glsl87
-rw-r--r--vcl/opengl/shaders/areaScaleFastFragmentShader.glsl59
-rw-r--r--vcl/opengl/shaders/areaScaleFragmentShader.glsl239
-rw-r--r--vcl/opengl/shaders/blendedTextureFragmentShader.glsl33
-rw-r--r--vcl/opengl/shaders/blendedTextureVertexShader.glsl28
-rw-r--r--vcl/opengl/shaders/combinedFragmentShader.glsl44
-rw-r--r--vcl/opengl/shaders/combinedTextureFragmentShader.glsl73
-rw-r--r--vcl/opengl/shaders/combinedTextureVertexShader.glsl43
-rw-r--r--vcl/opengl/shaders/combinedVertexShader.glsl76
-rw-r--r--vcl/opengl/shaders/convolutionFragmentShader.glsl30
-rw-r--r--vcl/opengl/shaders/diffTextureFragmentShader.glsl30
-rw-r--r--vcl/opengl/shaders/dumbVertexShader.glsl20
-rw-r--r--vcl/opengl/shaders/greyscaleFragmentShader.glsl20
-rw-r--r--vcl/opengl/shaders/invert50FragmentShader.glsl25
-rw-r--r--vcl/opengl/shaders/lineFragmentShader.glsl38
-rw-r--r--vcl/opengl/shaders/lineVertexShader.glsl37
-rw-r--r--vcl/opengl/shaders/linearGradientFragmentShader.glsl23
-rw-r--r--vcl/opengl/shaders/maskFragmentShader.glsl23
-rw-r--r--vcl/opengl/shaders/maskedTextureFragmentShader.glsl27
-rw-r--r--vcl/opengl/shaders/maskedTextureVertexShader.glsl26
-rw-r--r--vcl/opengl/shaders/radialGradientFragmentShader.glsl23
-rw-r--r--vcl/opengl/shaders/replaceColorFragmentShader.glsl25
-rw-r--r--vcl/opengl/shaders/solidFragmentShader.glsl19
-rw-r--r--vcl/opengl/shaders/textureFragmentShader.glsl20
-rw-r--r--vcl/opengl/shaders/textureVertexShader.glsl22
-rw-r--r--vcl/opengl/shaders/transformedTextureVertexShader.glsl28
-rw-r--r--vcl/opengl/texture.cxx606
-rw-r--r--vcl/opengl/win/WinDeviceInfo.cxx494
-rw-r--r--vcl/opengl/win/gdiimpl.cxx898
-rw-r--r--vcl/opengl/win/winlayout.cxx60
-rw-r--r--vcl/opengl/x11/X11DeviceInfo.cxx363
-rw-r--r--vcl/opengl/x11/cairotextrender.cxx84
-rw-r--r--vcl/opengl/x11/gdiimpl.cxx598
-rw-r--r--vcl/opengl/x11/salvd.cxx90
-rw-r--r--vcl/osx/DataFlavorMapping.cxx747
-rw-r--r--vcl/osx/DataFlavorMapping.hxx129
-rw-r--r--vcl/osx/DragActionConversion.cxx84
-rw-r--r--vcl/osx/DragActionConversion.hxx43
-rw-r--r--vcl/osx/DragSource.cxx338
-rw-r--r--vcl/osx/DragSource.hxx128
-rw-r--r--vcl/osx/DragSourceContext.cxx55
-rw-r--r--vcl/osx/DragSourceContext.hxx53
-rw-r--r--vcl/osx/DropTarget.cxx548
-rw-r--r--vcl/osx/DropTarget.hxx157
-rw-r--r--vcl/osx/HtmlFmtFlt.cxx165
-rw-r--r--vcl/osx/HtmlFmtFlt.hxx41
-rw-r--r--vcl/osx/OSXTransferable.cxx192
-rw-r--r--vcl/osx/OSXTransferable.hxx74
-rw-r--r--vcl/osx/PictToBmpFlt.cxx72
-rw-r--r--vcl/osx/PictToBmpFlt.hxx38
-rw-r--r--vcl/osx/README.a11y7
-rw-r--r--vcl/osx/a11yactionwrapper.h35
-rw-r--r--vcl/osx/a11yactionwrapper.mm77
-rw-r--r--vcl/osx/a11ycomponentwrapper.h39
-rw-r--r--vcl/osx/a11ycomponentwrapper.mm102
-rw-r--r--vcl/osx/a11yfactory.mm219
-rw-r--r--vcl/osx/a11yfocuslistener.cxx82
-rw-r--r--vcl/osx/a11yfocuslistener.hxx46
-rw-r--r--vcl/osx/a11yfocustracker.cxx256
-rw-r--r--vcl/osx/a11ylistener.cxx145
-rw-r--r--vcl/osx/a11yrolehelper.h35
-rw-r--r--vcl/osx/a11yrolehelper.mm281
-rw-r--r--vcl/osx/a11yselectionwrapper.h37
-rw-r--r--vcl/osx/a11yselectionwrapper.mm88
-rw-r--r--vcl/osx/a11ytablewrapper.h38
-rw-r--r--vcl/osx/a11ytablewrapper.mm202
-rw-r--r--vcl/osx/a11ytextattributeswrapper.h32
-rw-r--r--vcl/osx/a11ytextattributeswrapper.mm361
-rw-r--r--vcl/osx/a11ytextwrapper.h58
-rw-r--r--vcl/osx/a11ytextwrapper.mm296
-rw-r--r--vcl/osx/a11yutil.h32
-rw-r--r--vcl/osx/a11yutil.mm46
-rw-r--r--vcl/osx/a11yvaluewrapper.h40
-rw-r--r--vcl/osx/a11yvaluewrapper.mm87
-rw-r--r--vcl/osx/a11ywrapper.mm1147
-rw-r--r--vcl/osx/a11ywrapperbutton.h35
-rw-r--r--vcl/osx/a11ywrapperbutton.mm54
-rw-r--r--vcl/osx/a11ywrappercheckbox.h35
-rw-r--r--vcl/osx/a11ywrappercheckbox.mm58
-rw-r--r--vcl/osx/a11ywrappercombobox.h44
-rw-r--r--vcl/osx/a11ywrappercombobox.mm155
-rw-r--r--vcl/osx/a11ywrappergroup.h34
-rw-r--r--vcl/osx/a11ywrappergroup.mm49
-rw-r--r--vcl/osx/a11ywrapperlist.h33
-rw-r--r--vcl/osx/a11ywrapperlist.mm40
-rw-r--r--vcl/osx/a11ywrapperradiobutton.h35
-rw-r--r--vcl/osx/a11ywrapperradiobutton.mm57
-rw-r--r--vcl/osx/a11ywrapperradiogroup.h33
-rw-r--r--vcl/osx/a11ywrapperradiogroup.mm40
-rw-r--r--vcl/osx/a11ywrapperrow.h34
-rw-r--r--vcl/osx/a11ywrapperrow.mm50
-rw-r--r--vcl/osx/a11ywrapperscrollarea.h35
-rw-r--r--vcl/osx/a11ywrapperscrollarea.mm77
-rw-r--r--vcl/osx/a11ywrapperscrollbar.h33
-rw-r--r--vcl/osx/a11ywrapperscrollbar.mm43
-rw-r--r--vcl/osx/a11ywrappersplitter.h33
-rw-r--r--vcl/osx/a11ywrappersplitter.mm40
-rw-r--r--vcl/osx/a11ywrapperstatictext.h34
-rw-r--r--vcl/osx/a11ywrapperstatictext.mm48
-rw-r--r--vcl/osx/a11ywrappertabgroup.h33
-rw-r--r--vcl/osx/a11ywrappertabgroup.mm42
-rw-r--r--vcl/osx/a11ywrappertextarea.h33
-rw-r--r--vcl/osx/a11ywrappertextarea.mm40
-rw-r--r--vcl/osx/a11ywrappertoolbar.h33
-rw-r--r--vcl/osx/a11ywrappertoolbar.mm42
-rw-r--r--vcl/osx/clipboard.cxx345
-rw-r--r--vcl/osx/clipboard.hxx152
-rw-r--r--vcl/osx/cuidraw.hxx47
-rw-r--r--vcl/osx/documentfocuslistener.cxx214
-rw-r--r--vcl/osx/documentfocuslistener.hxx100
-rw-r--r--vcl/osx/printaccessoryview.mm1268
-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.cxx298
-rw-r--r--vcl/osx/salframe.cxx1828
-rw-r--r--vcl/osx/salframeview.mm1788
-rw-r--r--vcl/osx/salinst.cxx987
-rw-r--r--vcl/osx/salmenu.cxx902
-rw-r--r--vcl/osx/salnativewidgets.cxx1141
-rw-r--r--vcl/osx/salnsmenu.mm257
-rw-r--r--vcl/osx/salnstimer.mm40
-rw-r--r--vcl/osx/salobj.cxx442
-rw-r--r--vcl/osx/salprn.cxx683
-rw-r--r--vcl/osx/salsys.cxx117
-rw-r--r--vcl/osx/saltimer.cxx209
-rw-r--r--vcl/osx/service_entry.cxx64
-rw-r--r--vcl/osx/vclnsapp.mm473
-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.cxx241
-rw-r--r--vcl/qa/api/data/TestGraphic.pngbin0 -> 81 bytes
-rw-r--r--vcl/qa/cppunit/BackendTest.cxx599
-rw-r--r--vcl/qa/cppunit/BitmapExTest.cxx113
-rw-r--r--vcl/qa/cppunit/BitmapFilterTest.cxx216
-rw-r--r--vcl/qa/cppunit/BitmapProcessorTest.cxx90
-rw-r--r--vcl/qa/cppunit/BitmapScaleTest.cxx293
-rw-r--r--vcl/qa/cppunit/BitmapTest.cxx660
-rw-r--r--vcl/qa/cppunit/FontFeatureTest.cxx336
-rw-r--r--vcl/qa/cppunit/GraphicDescriptorTest.cxx103
-rw-r--r--vcl/qa/cppunit/GraphicFormatDetectorTest.cxx416
-rw-r--r--vcl/qa/cppunit/GraphicNativeMetadataTest.cxx101
-rw-r--r--vcl/qa/cppunit/GraphicTest.cxx435
-rw-r--r--vcl/qa/cppunit/ScanlineToolsTest.cxx224
-rw-r--r--vcl/qa/cppunit/TypeSerializerTest.cxx502
-rw-r--r--vcl/qa/cppunit/app/test_IconThemeInfo.cxx116
-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.cxx262
-rw-r--r--vcl/qa/cppunit/bitmaprender/BitmapRenderTest.cxx269
-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.cxx153
-rw-r--r--vcl/qa/cppunit/builder/demo.ui1960
-rw-r--r--vcl/qa/cppunit/canvasbitmaptest.cxx773
-rw-r--r--vcl/qa/cppunit/complextext.cxx170
-rw-r--r--vcl/qa/cppunit/data/123_Numbers.gifbin0 -> 1515 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/SimpleExample.svg4
-rw-r--r--vcl/qa/cppunit/data/TypeDetectionExample.bmpbin0 -> 442 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 -> 166 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.wmfbin0 -> 290 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/inch-size.emfbin0 -> 28322 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/dndtest.cxx311
-rw-r--r--vcl/qa/cppunit/errorhandler.cxx73
-rw-r--r--vcl/qa/cppunit/filter/ipdf/data/dict-array-dict.pdf55
-rw-r--r--vcl/qa/cppunit/filter/ipdf/ipdf.cxx73
-rw-r--r--vcl/qa/cppunit/font.cxx161
-rw-r--r--vcl/qa/cppunit/fontcharmap.cxx45
-rw-r--r--vcl/qa/cppunit/fontmetric.cxx138
-rw-r--r--vcl/qa/cppunit/gen/data/tdf121120.pngbin0 -> 3196 bytes
-rw-r--r--vcl/qa/cppunit/gen/gen.cxx116
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/README7
-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/crash-1.bmpbin0 -> 632 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/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/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/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-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/afl-sample-Z_NEED_DICT.pngbin0 -> 260 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/CVE-2016-0952-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/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/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/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/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-test.cxx181
-rw-r--r--vcl/qa/cppunit/jpeg/JpegReaderTest.cxx167
-rw-r--r--vcl/qa/cppunit/jpeg/JpegWriterTest.cxx105
-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/lifecycle.cxx357
-rw-r--r--vcl/qa/cppunit/mnemonic.cxx57
-rw-r--r--vcl/qa/cppunit/outdev.cxx291
-rw-r--r--vcl/qa/cppunit/pdfexport/data/6m-wide.odgbin0 -> 9146 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/SimpleMultiPagePDF.pdfbin0 -> 17693 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/forcepoint71.keybin0 -> 114022 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/link-wrong-page.odpbin0 -> 12293 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/reduce-image.fodt29
-rw-r--r--vcl/qa/cppunit/pdfexport/data/reduce-small-image.fodt21
-rw-r--r--vcl/qa/cppunit/pdfexport/data/softhyphen_pdf.odtbin0 -> 9071 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/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/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 -> 12091 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/tdf128630.odpbin0 -> 17558 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf66597-1.odtbin0 -> 8154 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/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/pdfexport.cxx2295
-rw-r--r--vcl/qa/cppunit/png/PngFilterTest.cxx193
-rw-r--r--vcl/qa/cppunit/png/data/alpha-rect-8bit-RGBA.pngbin0 -> 158 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.pngbin0 -> 90 bytes
-rw-r--r--vcl/qa/cppunit/png/data/rect-1bit-pal.pngbin0 -> 89 bytes
-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 -> 255 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/ellipse.svmbin0 -> 263 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/fillcolor.svmbin0 -> 244 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/gradient.svmbin0 -> 15497 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/hatch.svmbin0 -> 271 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/masks.svmbin0 -> 603 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/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/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.cxx1684
-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/timer.cxx566
-rw-r--r--vcl/qa/cppunit/widgetdraw/WidgetDefinitionReaderTest.cxx132
-rw-r--r--vcl/qa/cppunit/widgetdraw/data/definition1.xml82
-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.cxx63
-rw-r--r--vcl/qt5/Qt5AccessibleEventListener.cxx174
-rw-r--r--vcl/qt5/Qt5AccessibleWidget.cxx1267
-rw-r--r--vcl/qt5/Qt5Bitmap.cxx301
-rw-r--r--vcl/qt5/Qt5Clipboard.cxx243
-rw-r--r--vcl/qt5/Qt5Data.cxx334
-rw-r--r--vcl/qt5/Qt5DragAndDrop.cxx252
-rw-r--r--vcl/qt5/Qt5FilePicker.cxx945
-rw-r--r--vcl/qt5/Qt5Font.cxx161
-rw-r--r--vcl/qt5/Qt5FontFace.cxx206
-rw-r--r--vcl/qt5/Qt5Frame.cxx1449
-rw-r--r--vcl/qt5/Qt5Graphics.cxx128
-rw-r--r--vcl/qt5/Qt5Graphics_Controls.cxx1111
-rw-r--r--vcl/qt5/Qt5Graphics_GDI.cxx712
-rw-r--r--vcl/qt5/Qt5Graphics_Text.cxx233
-rw-r--r--vcl/qt5/Qt5Instance.cxx651
-rw-r--r--vcl/qt5/Qt5Instance_Print.cxx132
-rw-r--r--vcl/qt5/Qt5MainWindow.cxx45
-rw-r--r--vcl/qt5/Qt5Menu.cxx704
-rw-r--r--vcl/qt5/Qt5Object.cxx156
-rw-r--r--vcl/qt5/Qt5OpenGLContext.cxx152
-rw-r--r--vcl/qt5/Qt5Painter.cxx53
-rw-r--r--vcl/qt5/Qt5Printer.cxx27
-rw-r--r--vcl/qt5/Qt5SvpGraphics.cxx114
-rw-r--r--vcl/qt5/Qt5SvpSurface.cxx91
-rw-r--r--vcl/qt5/Qt5SvpVirtualDevice.hxx37
-rw-r--r--vcl/qt5/Qt5System.cxx38
-rw-r--r--vcl/qt5/Qt5Timer.cxx52
-rw-r--r--vcl/qt5/Qt5Tools.cxx122
-rw-r--r--vcl/qt5/Qt5Transferable.cxx345
-rw-r--r--vcl/qt5/Qt5VirtualDevice.cxx93
-rw-r--r--vcl/qt5/Qt5Widget.cxx740
-rw-r--r--vcl/qt5/Qt5XAccessible.cxx29
-rw-r--r--vcl/quartz/ctfonts.cxx560
-rw-r--r--vcl/quartz/salbmp.cxx963
-rw-r--r--vcl/quartz/salgdi.cxx888
-rw-r--r--vcl/quartz/salgdicommon.cxx2047
-rw-r--r--vcl/quartz/salgdiutils.cxx265
-rw-r--r--vcl/quartz/salvd.cxx303
-rw-r--r--vcl/quartz/utils.cxx161
-rw-r--r--vcl/skia/README35
-rw-r--r--vcl/skia/SkiaHelper.cxx611
-rw-r--r--vcl/skia/gdiimpl.cxx1829
-rw-r--r--vcl/skia/salbmp.cxx819
-rw-r--r--vcl/skia/skia_blacklist_vulkan.xml37
-rw-r--r--vcl/skia/win/gdiimpl.cxx393
-rw-r--r--vcl/skia/x11/gdiimpl.cxx161
-rw-r--r--vcl/skia/x11/salvd.cxx86
-rw-r--r--vcl/skia/x11/textrender.cxx80
-rw-r--r--vcl/skia/zone.cxx76
-rw-r--r--vcl/source/animate/Animation.cxx678
-rw-r--r--vcl/source/animate/AnimationBitmap.cxx53
-rw-r--r--vcl/source/app/ITiledRenderable.cxx81
-rw-r--r--vcl/source/app/IconThemeInfo.cxx188
-rw-r--r--vcl/source/app/IconThemeScanner.cxx215
-rw-r--r--vcl/source/app/IconThemeSelector.cxx175
-rw-r--r--vcl/source/app/brand.cxx73
-rw-r--r--vcl/source/app/customweld.cxx105
-rw-r--r--vcl/source/app/dbggui.cxx50
-rw-r--r--vcl/source/app/dndhelp.cxx139
-rw-r--r--vcl/source/app/help.cxx677
-rw-r--r--vcl/source/app/i18nhelp.cxx160
-rw-r--r--vcl/source/app/idle.cxx65
-rw-r--r--vcl/source/app/salplug.cxx355
-rw-r--r--vcl/source/app/salusereventlist.cxx171
-rw-r--r--vcl/source/app/salvtables.cxx6777
-rw-r--r--vcl/source/app/scheduler.cxx659
-rw-r--r--vcl/source/app/session.cxx414
-rw-r--r--vcl/source/app/settings.cxx3233
-rw-r--r--vcl/source/app/sound.cxx38
-rw-r--r--vcl/source/app/stdtext.cxx124
-rw-r--r--vcl/source/app/svapp.cxx1707
-rw-r--r--vcl/source/app/svdata.cxx491
-rw-r--r--vcl/source/app/svmain.cxx684
-rw-r--r--vcl/source/app/timer.cxx102
-rw-r--r--vcl/source/app/unohelp.cxx233
-rw-r--r--vcl/source/app/unohelp2.cxx98
-rw-r--r--vcl/source/app/vclevent.cxx104
-rw-r--r--vcl/source/app/watchdog.cxx165
-rw-r--r--vcl/source/app/weldutils.cxx129
-rw-r--r--vcl/source/app/winscheduler.cxx46
-rw-r--r--vcl/source/bitmap/BitmapAlphaClampFilter.cxx45
-rw-r--r--vcl/source/bitmap/BitmapBasicMorphologyFilter.cxx360
-rw-r--r--vcl/source/bitmap/BitmapColorQuantizationFilter.cxx232
-rw-r--r--vcl/source/bitmap/BitmapColorizeFilter.cxx92
-rw-r--r--vcl/source/bitmap/BitmapConvolutionMatrixFilter.cxx208
-rw-r--r--vcl/source/bitmap/BitmapDisabledImageFilter.cxx61
-rw-r--r--vcl/source/bitmap/BitmapDuoToneFilter.cxx65
-rw-r--r--vcl/source/bitmap/BitmapEmbossGreyFilter.cxx158
-rw-r--r--vcl/source/bitmap/BitmapFastScaleFilter.cxx131
-rw-r--r--vcl/source/bitmap/BitmapFilterStackBlur.cxx629
-rw-r--r--vcl/source/bitmap/BitmapGaussianSeparableBlurFilter.cxx215
-rw-r--r--vcl/source/bitmap/BitmapInterpolateScaleFilter.cxx237
-rw-r--r--vcl/source/bitmap/BitmapLightenFilter.cxx69
-rw-r--r--vcl/source/bitmap/BitmapMedianFilter.cxx218
-rw-r--r--vcl/source/bitmap/BitmapMonochromeFilter.cxx101
-rw-r--r--vcl/source/bitmap/BitmapMosaicFilter.cxx185
-rw-r--r--vcl/source/bitmap/BitmapPopArtFilter.cxx97
-rw-r--r--vcl/source/bitmap/BitmapScaleConvolutionFilter.cxx391
-rw-r--r--vcl/source/bitmap/BitmapScaleSuperFilter.cxx1202
-rw-r--r--vcl/source/bitmap/BitmapSeparableUnsharpenFilter.cxx75
-rw-r--r--vcl/source/bitmap/BitmapSepiaFilter.cxx111
-rw-r--r--vcl/source/bitmap/BitmapSimpleColorQuantizationFilter.cxx113
-rw-r--r--vcl/source/bitmap/BitmapSmoothenFilter.cxx32
-rw-r--r--vcl/source/bitmap/BitmapSobelGreyFilter.cxx170
-rw-r--r--vcl/source/bitmap/BitmapSolarizeFilter.cxx71
-rw-r--r--vcl/source/bitmap/BitmapSymmetryCheck.cxx84
-rw-r--r--vcl/source/bitmap/BitmapTools.cxx1131
-rw-r--r--vcl/source/bitmap/Octree.cxx288
-rw-r--r--vcl/source/bitmap/bitmap.cxx892
-rw-r--r--vcl/source/bitmap/bitmapfilter.cxx58
-rw-r--r--vcl/source/bitmap/bitmappaint.cxx1146
-rw-r--r--vcl/source/bitmap/checksum.cxx154
-rw-r--r--vcl/source/bitmap/salbmp.cxx225
-rw-r--r--vcl/source/components/dtranscomp.cxx491
-rw-r--r--vcl/source/components/factory.cxx85
-rw-r--r--vcl/source/components/fontident.cxx184
-rw-r--r--vcl/source/control/InterimItemWindow.cxx99
-rw-r--r--vcl/source/control/button.cxx3705
-rw-r--r--vcl/source/control/calendar.cxx1554
-rw-r--r--vcl/source/control/combobox.cxx1542
-rw-r--r--vcl/source/control/ctrl.cxx495
-rw-r--r--vcl/source/control/edit.cxx2939
-rw-r--r--vcl/source/control/field.cxx2059
-rw-r--r--vcl/source/control/field2.cxx2708
-rw-r--r--vcl/source/control/fixed.cxx972
-rw-r--r--vcl/source/control/fixedhyper.cxx185
-rw-r--r--vcl/source/control/fmtfield.cxx1291
-rw-r--r--vcl/source/control/hyperlabel.cxx200
-rw-r--r--vcl/source/control/imgctrl.cxx184
-rw-r--r--vcl/source/control/imivctl.hxx513
-rw-r--r--vcl/source/control/imivctl1.cxx2980
-rw-r--r--vcl/source/control/imivctl2.cxx715
-rw-r--r--vcl/source/control/imp_listbox.cxx3116
-rw-r--r--vcl/source/control/ivctrl.cxx633
-rw-r--r--vcl/source/control/listbox.cxx1444
-rw-r--r--vcl/source/control/longcurr.cxx516
-rw-r--r--vcl/source/control/menubtn.cxx250
-rw-r--r--vcl/source/control/notebookbar.cxx297
-rw-r--r--vcl/source/control/prgsbar.cxx229
-rw-r--r--vcl/source/control/quickselectionengine.cxx161
-rw-r--r--vcl/source/control/roadmap.cxx842
-rw-r--r--vcl/source/control/roadmapwizard.cxx858
-rw-r--r--vcl/source/control/scrbar.cxx1469
-rw-r--r--vcl/source/control/slider.cxx906
-rw-r--r--vcl/source/control/spinbtn.cxx468
-rw-r--r--vcl/source/control/spinfld.cxx1007
-rw-r--r--vcl/source/control/tabctrl.cxx2382
-rw-r--r--vcl/source/control/throbber.cxx232
-rw-r--r--vcl/source/control/thumbpos.hxx23
-rw-r--r--vcl/source/control/wizardmachine.cxx1496
-rw-r--r--vcl/source/control/wizimpldata.hxx64
-rw-r--r--vcl/source/edit/textdat2.hxx283
-rw-r--r--vcl/source/edit/textdata.cxx343
-rw-r--r--vcl/source/edit/textdoc.cxx536
-rw-r--r--vcl/source/edit/textdoc.hxx129
-rw-r--r--vcl/source/edit/texteng.cxx2896
-rw-r--r--vcl/source/edit/textund2.hxx108
-rw-r--r--vcl/source/edit/textundo.cxx330
-rw-r--r--vcl/source/edit/textundo.hxx80
-rw-r--r--vcl/source/edit/textview.cxx2302
-rw-r--r--vcl/source/edit/txtattr.cxx93
-rw-r--r--vcl/source/edit/vclmedit.cxx1539
-rw-r--r--vcl/source/edit/xtextedt.cxx227
-rw-r--r--vcl/source/filter/FilterConfigCache.cxx539
-rw-r--r--vcl/source/filter/FilterConfigCache.hxx108
-rw-r--r--vcl/source/filter/FilterConfigItem.cxx393
-rw-r--r--vcl/source/filter/GraphicFormatDetector.cxx550
-rw-r--r--vcl/source/filter/GraphicNativeMetadata.cxx61
-rw-r--r--vcl/source/filter/GraphicNativeTransform.cxx169
-rw-r--r--vcl/source/filter/graphicfilter.cxx2303
-rw-r--r--vcl/source/filter/graphicfilter2.cxx1165
-rw-r--r--vcl/source/filter/graphicfilter_internal.hxx32
-rw-r--r--vcl/source/filter/igif/decode.cxx215
-rw-r--r--vcl/source/filter/igif/decode.hxx66
-rw-r--r--vcl/source/filter/igif/gifread.cxx994
-rw-r--r--vcl/source/filter/igif/gifread.hxx30
-rw-r--r--vcl/source/filter/ipdf/pdfdocument.cxx3100
-rw-r--r--vcl/source/filter/ipdf/pdfread.cxx311
-rw-r--r--vcl/source/filter/ixbm/xbmread.cxx395
-rw-r--r--vcl/source/filter/ixbm/xbmread.hxx29
-rw-r--r--vcl/source/filter/ixpm/rgbtable.hxx696
-rw-r--r--vcl/source/filter/ixpm/xpmread.cxx695
-rw-r--r--vcl/source/filter/ixpm/xpmread.hxx31
-rw-r--r--vcl/source/filter/jpeg/Exif.cxx295
-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.hxx73
-rw-r--r--vcl/source/filter/jpeg/JpegTransform.cxx45
-rw-r--r--vcl/source/filter/jpeg/JpegTransform.hxx42
-rw-r--r--vcl/source/filter/jpeg/JpegWriter.cxx265
-rw-r--r--vcl/source/filter/jpeg/JpegWriter.hxx56
-rw-r--r--vcl/source/filter/jpeg/jinclude.h81
-rw-r--r--vcl/source/filter/jpeg/jpeg.cxx62
-rw-r--r--vcl/source/filter/jpeg/jpeg.h67
-rw-r--r--vcl/source/filter/jpeg/jpeg.hxx37
-rw-r--r--vcl/source/filter/jpeg/jpegc.cxx513
-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.cxx279
-rw-r--r--vcl/source/filter/png/pngread.cxx1715
-rw-r--r--vcl/source/filter/png/pngwrite.cxx720
-rw-r--r--vcl/source/filter/wmf/emfwr.cxx1483
-rw-r--r--vcl/source/filter/wmf/emfwr.hxx114
-rw-r--r--vcl/source/filter/wmf/wmf.cxx107
-rw-r--r--vcl/source/filter/wmf/wmfexternal.cxx77
-rw-r--r--vcl/source/filter/wmf/wmfwr.cxx1895
-rw-r--r--vcl/source/filter/wmf/wmfwr.hxx205
-rw-r--r--vcl/source/font/Feature.cxx165
-rw-r--r--vcl/source/font/FeatureCollector.cxx158
-rw-r--r--vcl/source/font/FeatureParser.cxx69
-rw-r--r--vcl/source/font/OpenTypeFeatureDefinitionList.cxx197
-rw-r--r--vcl/source/font/PhysicalFontCollection.cxx1267
-rw-r--r--vcl/source/font/PhysicalFontFace.cxx199
-rw-r--r--vcl/source/font/PhysicalFontFamily.cxx273
-rw-r--r--vcl/source/font/font.cxx894
-rw-r--r--vcl/source/font/fontattributes.cxx62
-rw-r--r--vcl/source/font/fontcache.cxx278
-rw-r--r--vcl/source/font/fontcharmap.cxx634
-rw-r--r--vcl/source/font/fontinstance.cxx168
-rw-r--r--vcl/source/font/fontmetric.cxx394
-rw-r--r--vcl/source/font/fontselect.cxx150
-rw-r--r--vcl/source/fontsubset/cff.cxx2061
-rw-r--r--vcl/source/fontsubset/fontsubset.cxx163
-rw-r--r--vcl/source/fontsubset/list.cxx229
-rw-r--r--vcl/source/fontsubset/list.h89
-rw-r--r--vcl/source/fontsubset/sft.cxx2635
-rw-r--r--vcl/source/fontsubset/ttcr.cxx1505
-rw-r--r--vcl/source/fontsubset/ttcr.hxx220
-rw-r--r--vcl/source/fontsubset/xlat.cxx144
-rw-r--r--vcl/source/fontsubset/xlat.hxx40
-rw-r--r--vcl/source/gdi/CommonSalLayout.cxx854
-rw-r--r--vcl/source/gdi/FileDefinitionWidgetDraw.cxx1080
-rw-r--r--vcl/source/gdi/TypeSerializer.cxx419
-rw-r--r--vcl/source/gdi/VerticalOrientationData.cxx78
-rw-r--r--vcl/source/gdi/WidgetDefinition.cxx186
-rw-r--r--vcl/source/gdi/WidgetDefinitionReader.cxx494
-rw-r--r--vcl/source/gdi/alpha.cxx176
-rw-r--r--vcl/source/gdi/bitmap3.cxx1146
-rw-r--r--vcl/source/gdi/bitmapex.cxx1685
-rw-r--r--vcl/source/gdi/bmpacc.cxx464
-rw-r--r--vcl/source/gdi/bmpacc2.cxx362
-rw-r--r--vcl/source/gdi/bmpacc3.cxx278
-rw-r--r--vcl/source/gdi/bmpfast.cxx743
-rw-r--r--vcl/source/gdi/configsettings.cxx137
-rw-r--r--vcl/source/gdi/cvtgrf.cxx73
-rw-r--r--vcl/source/gdi/dibtools.cxx1904
-rw-r--r--vcl/source/gdi/embeddedfontshelper.cxx332
-rw-r--r--vcl/source/gdi/extoutdevdata.cxx31
-rw-r--r--vcl/source/gdi/gdimetafiletools.cxx1086
-rw-r--r--vcl/source/gdi/gdimtf.cxx2866
-rwxr-xr-xvcl/source/gdi/genVerticalOrientationData.pl206
-rw-r--r--vcl/source/gdi/gfxlink.cxx189
-rw-r--r--vcl/source/gdi/gradient.cxx285
-rw-r--r--vcl/source/gdi/graph.cxx601
-rw-r--r--vcl/source/gdi/graphictools.cxx296
-rw-r--r--vcl/source/gdi/hatch.cxx110
-rw-r--r--vcl/source/gdi/impanmvw.cxx321
-rw-r--r--vcl/source/gdi/impglyphitem.cxx78
-rw-r--r--vcl/source/gdi/impgraph.cxx1882
-rw-r--r--vcl/source/gdi/impvect.cxx1000
-rw-r--r--vcl/source/gdi/impvect.hxx35
-rw-r--r--vcl/source/gdi/jobset.cxx394
-rw-r--r--vcl/source/gdi/lineinfo.cxx261
-rw-r--r--vcl/source/gdi/mapmod.cxx178
-rw-r--r--vcl/source/gdi/metaact.cxx3412
-rw-r--r--vcl/source/gdi/mtfxmldump.cxx1274
-rw-r--r--vcl/source/gdi/oldprintadaptor.cxx112
-rw-r--r--vcl/source/gdi/pdfbuildin_fonts.cxx740
-rw-r--r--vcl/source/gdi/pdfbuildin_fonts.hxx81
-rw-r--r--vcl/source/gdi/pdfextoutdevdata.cxx891
-rw-r--r--vcl/source/gdi/pdffontcache.cxx65
-rw-r--r--vcl/source/gdi/pdffontcache.hxx72
-rw-r--r--vcl/source/gdi/pdfwriter.cxx467
-rw-r--r--vcl/source/gdi/pdfwriter_impl.cxx11212
-rw-r--r--vcl/source/gdi/pdfwriter_impl.hxx1265
-rw-r--r--vcl/source/gdi/pdfwriter_impl2.cxx1989
-rw-r--r--vcl/source/gdi/print.cxx1639
-rw-r--r--vcl/source/gdi/print2.cxx1314
-rw-r--r--vcl/source/gdi/print3.cxx2118
-rw-r--r--vcl/source/gdi/regband.cxx885
-rw-r--r--vcl/source/gdi/region.cxx1778
-rw-r--r--vcl/source/gdi/regionband.cxx1358
-rw-r--r--vcl/source/gdi/salgdiimpl.cxx26
-rw-r--r--vcl/source/gdi/salgdilayout.cxx897
-rw-r--r--vcl/source/gdi/sallayout.cxx1585
-rw-r--r--vcl/source/gdi/salmisc.cxx491
-rw-r--r--vcl/source/gdi/scrptrun.cxx247
-rw-r--r--vcl/source/gdi/svmconverter.cxx1245
-rw-r--r--vcl/source/gdi/textlayout.cxx351
-rw-r--r--vcl/source/gdi/vectorgraphicdata.cxx358
-rw-r--r--vcl/source/gdi/virdev.cxx510
-rw-r--r--vcl/source/gdi/wall.cxx366
-rw-r--r--vcl/source/graphic/GraphicID.cxx103
-rw-r--r--vcl/source/graphic/GraphicLoader.cxx44
-rw-r--r--vcl/source/graphic/GraphicObject.cxx942
-rw-r--r--vcl/source/graphic/GraphicObject2.cxx506
-rw-r--r--vcl/source/graphic/GraphicReader.cxx29
-rw-r--r--vcl/source/graphic/Manager.cxx247
-rw-r--r--vcl/source/graphic/UnoGraphic.cxx192
-rw-r--r--vcl/source/graphic/UnoGraphicDescriptor.cxx424
-rw-r--r--vcl/source/graphic/UnoGraphicObject.cxx100
-rw-r--r--vcl/source/graphic/UnoGraphicProvider.cxx837
-rw-r--r--vcl/source/graphic/UnoGraphicTransformer.cxx140
-rw-r--r--vcl/source/graphic/grfattr.cxx62
-rw-r--r--vcl/source/helper/canvasbitmap.cxx1407
-rw-r--r--vcl/source/helper/canvastools.cxx623
-rw-r--r--vcl/source/helper/commandinfoprovider.cxx480
-rw-r--r--vcl/source/helper/displayconnectiondispatch.cxx111
-rw-r--r--vcl/source/helper/driverblocklist.cxx762
-rw-r--r--vcl/source/helper/errcode.cxx147
-rw-r--r--vcl/source/helper/evntpost.cxx57
-rw-r--r--vcl/source/helper/lazydelete.cxx58
-rw-r--r--vcl/source/helper/strhelper.cxx370
-rw-r--r--vcl/source/helper/svtaccessiblefactory.cxx273
-rw-r--r--vcl/source/helper/threadex.cxx72
-rw-r--r--vcl/source/image/Image.cxx163
-rw-r--r--vcl/source/image/ImageRepository.cxx37
-rw-r--r--vcl/source/image/ImageTree.cxx62
-rw-r--r--vcl/source/image/ImplImage.cxx156
-rw-r--r--vcl/source/image/ImplImageTree.cxx698
-rw-r--r--vcl/source/opengl/GLMHelper.hxx24
-rw-r--r--vcl/source/opengl/OpenGLContext.cxx809
-rw-r--r--vcl/source/opengl/OpenGLHelper.cxx1055
-rw-r--r--vcl/source/outdev/bitmap.cxx1788
-rw-r--r--vcl/source/outdev/clipping.cxx226
-rw-r--r--vcl/source/outdev/curvedshapes.cxx210
-rw-r--r--vcl/source/outdev/font.cxx1435
-rw-r--r--vcl/source/outdev/gradient.cxx1045
-rw-r--r--vcl/source/outdev/hatch.cxx409
-rw-r--r--vcl/source/outdev/line.cxx304
-rw-r--r--vcl/source/outdev/map.cxx1936
-rw-r--r--vcl/source/outdev/mask.cxx154
-rw-r--r--vcl/source/outdev/nativecontrols.cxx356
-rw-r--r--vcl/source/outdev/outdev.cxx707
-rw-r--r--vcl/source/outdev/outdevstate.cxx609
-rw-r--r--vcl/source/outdev/pixel.cxx116
-rw-r--r--vcl/source/outdev/polygon.cxx518
-rw-r--r--vcl/source/outdev/polyline.cxx386
-rw-r--r--vcl/source/outdev/rect.cxx415
-rw-r--r--vcl/source/outdev/text.cxx2510
-rw-r--r--vcl/source/outdev/textline.cxx1026
-rw-r--r--vcl/source/outdev/transparent.cxx859
-rw-r--r--vcl/source/outdev/vclreferencebase.cxx44
-rw-r--r--vcl/source/outdev/wallpaper.cxx396
-rw-r--r--vcl/source/pdf/Matrix3.cxx117
-rw-r--r--vcl/source/pdf/PDFiumLibrary.cxx96
-rw-r--r--vcl/source/pdf/ResourceDict.cxx60
-rw-r--r--vcl/source/pdf/XmpMetadata.cxx190
-rw-r--r--vcl/source/salmain/salmain.cxx36
-rw-r--r--vcl/source/toolkit/README2
-rw-r--r--vcl/source/toolkit/group.cxx257
-rw-r--r--vcl/source/toolkit/morebtn.cxx123
-rw-r--r--vcl/source/treelist/headbar.cxx1302
-rw-r--r--vcl/source/treelist/iconview.cxx221
-rw-r--r--vcl/source/treelist/iconviewimpl.cxx656
-rw-r--r--vcl/source/treelist/iconviewimpl.hxx68
-rw-r--r--vcl/source/treelist/imap.cxx994
-rw-r--r--vcl/source/treelist/imap2.cxx531
-rw-r--r--vcl/source/treelist/imap3.cxx87
-rw-r--r--vcl/source/treelist/inetimg.cxx135
-rw-r--r--vcl/source/treelist/svimpbox.cxx3314
-rw-r--r--vcl/source/treelist/svlbitm.cxx529
-rw-r--r--vcl/source/treelist/svtabbx.cxx1033
-rw-r--r--vcl/source/treelist/transfer.cxx2264
-rw-r--r--vcl/source/treelist/transfer2.cxx519
-rw-r--r--vcl/source/treelist/treelist.cxx1565
-rw-r--r--vcl/source/treelist/treelistbox.cxx3603
-rw-r--r--vcl/source/treelist/treelistentry.cxx235
-rw-r--r--vcl/source/treelist/uiobject.cxx179
-rw-r--r--vcl/source/treelist/viewdataentry.cxx87
-rw-r--r--vcl/source/uipreviewer/previewer.cxx116
-rw-r--r--vcl/source/uitest/logger.cxx553
-rw-r--r--vcl/source/uitest/uiobject.cxx1506
-rw-r--r--vcl/source/uitest/uitest.cxx80
-rw-r--r--vcl/source/uitest/uno/uiobject_uno.cxx210
-rw-r--r--vcl/source/uitest/uno/uiobject_uno.hxx73
-rw-r--r--vcl/source/uitest/uno/uitest_uno.cxx120
-rw-r--r--vcl/source/window/EnumContext.cxx213
-rw-r--r--vcl/source/window/NotebookBarAddonsMerger.cxx190
-rw-r--r--vcl/source/window/OptionalBox.cxx63
-rw-r--r--vcl/source/window/abstdlg.cxx85
-rw-r--r--vcl/source/window/accel.cxx296
-rw-r--r--vcl/source/window/accessibility.cxx634
-rw-r--r--vcl/source/window/accmgr.cxx229
-rw-r--r--vcl/source/window/brdwin.cxx2080
-rw-r--r--vcl/source/window/bufferdevice.cxx45
-rw-r--r--vcl/source/window/bufferdevice.hxx38
-rw-r--r--vcl/source/window/builder.cxx4708
-rw-r--r--vcl/source/window/clipping.cxx719
-rw-r--r--vcl/source/window/commandevent.cxx198
-rw-r--r--vcl/source/window/cursor.cxx462
-rw-r--r--vcl/source/window/debug.cxx48
-rw-r--r--vcl/source/window/debugevent.cxx270
-rw-r--r--vcl/source/window/decoview.cxx1090
-rw-r--r--vcl/source/window/dialog.cxx1617
-rw-r--r--vcl/source/window/dlgctrl.cxx1157
-rw-r--r--vcl/source/window/dlgctrl.hxx37
-rw-r--r--vcl/source/window/dndeventdispatcher.cxx412
-rw-r--r--vcl/source/window/dndlistenercontainer.cxx451
-rw-r--r--vcl/source/window/dockingarea.cxx256
-rw-r--r--vcl/source/window/dockmgr.cxx1059
-rw-r--r--vcl/source/window/dockwin.cxx1061
-rw-r--r--vcl/source/window/errinf.cxx319
-rw-r--r--vcl/source/window/event.cxx678
-rw-r--r--vcl/source/window/floatwin.cxx950
-rw-r--r--vcl/source/window/globalization.cxx39
-rw-r--r--vcl/source/window/introwin.cxx52
-rw-r--r--vcl/source/window/keycod.cxx108
-rw-r--r--vcl/source/window/keyevent.cxx67
-rw-r--r--vcl/source/window/layout.cxx2882
-rw-r--r--vcl/source/window/legacyaccessibility.cxx242
-rw-r--r--vcl/source/window/menu.cxx3100
-rw-r--r--vcl/source/window/menubarwindow.cxx1252
-rw-r--r--vcl/source/window/menubarwindow.hxx148
-rw-r--r--vcl/source/window/menufloatingwindow.cxx1334
-rw-r--r--vcl/source/window/menufloatingwindow.hxx130
-rw-r--r--vcl/source/window/menuitemlist.cxx318
-rw-r--r--vcl/source/window/menuitemlist.hxx149
-rw-r--r--vcl/source/window/menuwindow.cxx111
-rw-r--r--vcl/source/window/menuwindow.hxx58
-rw-r--r--vcl/source/window/mnemonic.cxx343
-rw-r--r--vcl/source/window/mnemonicengine.cxx108
-rw-r--r--vcl/source/window/mouse.cxx792
-rw-r--r--vcl/source/window/paint.cxx1844
-rw-r--r--vcl/source/window/popupmenuwindow.cxx74
-rw-r--r--vcl/source/window/printdlg.cxx2239
-rw-r--r--vcl/source/window/scrwnd.cxx390
-rw-r--r--vcl/source/window/seleng.cxx411
-rw-r--r--vcl/source/window/settings.cxx266
-rw-r--r--vcl/source/window/split.cxx701
-rw-r--r--vcl/source/window/splitwin.cxx2757
-rw-r--r--vcl/source/window/stacking.cxx1162
-rw-r--r--vcl/source/window/status.cxx1473
-rw-r--r--vcl/source/window/syschild.cxx187
-rw-r--r--vcl/source/window/syswin.cxx1199
-rw-r--r--vcl/source/window/tabdlg.cxx184
-rw-r--r--vcl/source/window/tabpage.cxx202
-rw-r--r--vcl/source/window/taskpanelist.cxx286
-rw-r--r--vcl/source/window/toolbox.cxx4902
-rw-r--r--vcl/source/window/toolbox2.cxx1778
-rw-r--r--vcl/source/window/window.cxx3926
-rw-r--r--vcl/source/window/window2.cxx1984
-rw-r--r--vcl/source/window/window3.cxx68
-rw-r--r--vcl/source/window/winproc.cxx2634
-rw-r--r--vcl/source/window/wrkwin.cxx280
-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.ui135
-rw-r--r--vcl/uiconfig/ui/combobox.ui124
-rw-r--r--vcl/uiconfig/ui/cupspassworddialog.ui181
-rw-r--r--vcl/uiconfig/ui/editmenu.ui77
-rw-r--r--vcl/uiconfig/ui/errornocontentdialog.ui35
-rw-r--r--vcl/uiconfig/ui/errornoprinterdialog.ui38
-rw-r--r--vcl/uiconfig/ui/moreoptionsdialog.ui94
-rw-r--r--vcl/uiconfig/ui/printdialog.ui1295
-rw-r--r--vcl/uiconfig/ui/printerdevicepage.ui264
-rw-r--r--vcl/uiconfig/ui/printerpaperpage.ui130
-rw-r--r--vcl/uiconfig/ui/printerpropertiesdialog.ui176
-rw-r--r--vcl/uiconfig/ui/printprogressdialog.ui85
-rw-r--r--vcl/uiconfig/ui/querydialog.ui111
-rw-r--r--vcl/uiconfig/ui/screenshotparent.ui54
-rw-r--r--vcl/uiconfig/ui/wizard.ui17
-rw-r--r--vcl/unx/generic/app/gendata.cxx44
-rw-r--r--vcl/unx/generic/app/gendisp.cxx69
-rw-r--r--vcl/unx/generic/app/geninst.cxx77
-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.cxx507
-rw-r--r--vcl/unx/generic/app/randrwrapper.cxx181
-rw-r--r--vcl/unx/generic/app/saldata.cxx782
-rw-r--r--vcl/unx/generic/app/saldisp.cxx2889
-rw-r--r--vcl/unx/generic/app/salinst.cxx249
-rw-r--r--vcl/unx/generic/app/saltimer.cxx68
-rw-r--r--vcl/unx/generic/app/sm.cxx858
-rw-r--r--vcl/unx/generic/app/wmadaptor.cxx2308
-rw-r--r--vcl/unx/generic/desktopdetect/desktopdetector.cxx253
-rw-r--r--vcl/unx/generic/dtrans/X11_clipboard.cxx231
-rw-r--r--vcl/unx/generic/dtrans/X11_clipboard.hxx114
-rw-r--r--vcl/unx/generic/dtrans/X11_dndcontext.cxx118
-rw-r--r--vcl/unx/generic/dtrans/X11_dndcontext.hxx83
-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.hxx501
-rw-r--r--vcl/unx/generic/dtrans/X11_service.cxx84
-rw-r--r--vcl/unx/generic/dtrans/X11_transferable.cxx101
-rw-r--r--vcl/unx/generic/dtrans/X11_transferable.hxx53
-rw-r--r--vcl/unx/generic/dtrans/bmp.cxx786
-rw-r--r--vcl/unx/generic/dtrans/bmp.hxx78
-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.cxx1198
-rw-r--r--vcl/unx/generic/fontmanager/fontmanager.cxx1176
-rw-r--r--vcl/unx/generic/fontmanager/fontsubst.cxx221
-rw-r--r--vcl/unx/generic/fontmanager/helper.cxx262
-rw-r--r--vcl/unx/generic/gdi/cairo_xlib_cairo.cxx314
-rw-r--r--vcl/unx/generic/gdi/cairo_xlib_cairo.hxx98
-rw-r--r--vcl/unx/generic/gdi/cairotextrender.cxx309
-rw-r--r--vcl/unx/generic/gdi/font.cxx124
-rw-r--r--vcl/unx/generic/gdi/freetypetextrender.cxx237
-rw-r--r--vcl/unx/generic/gdi/gdiimpl.cxx1993
-rw-r--r--vcl/unx/generic/gdi/gdiimpl.hxx296
-rw-r--r--vcl/unx/generic/gdi/nativewindowhandleprovider.cxx17
-rw-r--r--vcl/unx/generic/gdi/salbmp.cxx1036
-rw-r--r--vcl/unx/generic/gdi/salgdi.cxx799
-rw-r--r--vcl/unx/generic/gdi/salgdi2.cxx181
-rw-r--r--vcl/unx/generic/gdi/salvd.cxx237
-rw-r--r--vcl/unx/generic/gdi/x11cairotextrender.cxx90
-rw-r--r--vcl/unx/generic/gdi/xrender_peer.cxx48
-rw-r--r--vcl/unx/generic/glyphs/freetype_glyphcache.cxx951
-rw-r--r--vcl/unx/generic/glyphs/glyphcache.cxx119
-rw-r--r--vcl/unx/generic/print/bitmap_gfx.cxx696
-rw-r--r--vcl/unx/generic/print/common_gfx.cxx1148
-rw-r--r--vcl/unx/generic/print/genprnpsp.cxx1300
-rw-r--r--vcl/unx/generic/print/genpspgraphics.cxx1006
-rw-r--r--vcl/unx/generic/print/glyphset.cxx301
-rw-r--r--vcl/unx/generic/print/glyphset.hxx82
-rw-r--r--vcl/unx/generic/print/printerjob.cxx973
-rw-r--r--vcl/unx/generic/print/prtsetup.cxx507
-rw-r--r--vcl/unx/generic/print/prtsetup.hxx137
-rw-r--r--vcl/unx/generic/print/psheader.ps363
-rw-r--r--vcl/unx/generic/print/psputil.cxx184
-rw-r--r--vcl/unx/generic/print/psputil.hxx54
-rw-r--r--vcl/unx/generic/print/text_gfx.cxx158
-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.conf99
-rw-r--r--vcl/unx/generic/printer/cpdmgr.cxx754
-rw-r--r--vcl/unx/generic/printer/cupsmgr.cxx965
-rw-r--r--vcl/unx/generic/printer/jobdata.cxx306
-rw-r--r--vcl/unx/generic/printer/ppdparser.cxx1987
-rw-r--r--vcl/unx/generic/printer/printerinfomanager.cxx900
-rw-r--r--vcl/unx/generic/window/salframe.cxx4107
-rw-r--r--vcl/unx/generic/window/salobj.cxx506
-rw-r--r--vcl/unx/generic/window/screensaverinhibitor.cxx370
-rw-r--r--vcl/unx/glxtest.cxx294
-rw-r--r--vcl/unx/gtk3/a11y/TODO49
-rw-r--r--vcl/unx/gtk3/a11y/atkfactory.hxx33
-rw-r--r--vcl/unx/gtk3/a11y/atklistener.hxx71
-rw-r--r--vcl/unx/gtk3/a11y/atkregistry.hxx35
-rw-r--r--vcl/unx/gtk3/a11y/atktextattributes.hxx52
-rw-r--r--vcl/unx/gtk3/a11y/atkutil.hxx29
-rw-r--r--vcl/unx/gtk3/a11y/atkwrapper.hxx125
-rw-r--r--vcl/unx/gtk3/a11y/gtk3atkaction.cxx275
-rw-r--r--vcl/unx/gtk3/a11y/gtk3atkbridge.cxx34
-rw-r--r--vcl/unx/gtk3/a11y/gtk3atkcomponent.cxx387
-rw-r--r--vcl/unx/gtk3/a11y/gtk3atkeditabletext.cxx193
-rw-r--r--vcl/unx/gtk3/a11y/gtk3atkfactory.cxx186
-rw-r--r--vcl/unx/gtk3/a11y/gtk3atkhypertext.cxx277
-rw-r--r--vcl/unx/gtk3/a11y/gtk3atkimage.cxx131
-rw-r--r--vcl/unx/gtk3/a11y/gtk3atklistener.cxx748
-rw-r--r--vcl/unx/gtk3/a11y/gtk3atkregistry.cxx66
-rw-r--r--vcl/unx/gtk3/a11y/gtk3atkselection.cxx190
-rw-r--r--vcl/unx/gtk3/a11y/gtk3atktable.cxx580
-rw-r--r--vcl/unx/gtk3/a11y/gtk3atktext.cxx897
-rw-r--r--vcl/unx/gtk3/a11y/gtk3atktextattributes.cxx1390
-rw-r--r--vcl/unx/gtk3/a11y/gtk3atkutil.cxx708
-rw-r--r--vcl/unx/gtk3/a11y/gtk3atkvalue.cxx138
-rw-r--r--vcl/unx/gtk3/a11y/gtk3atkwrapper.cxx942
-rw-r--r--vcl/unx/gtk3/cairo_gtk3_cairo.cxx128
-rw-r--r--vcl/unx/gtk3/cairo_gtk3_cairo.hxx49
-rw-r--r--vcl/unx/gtk3/fpicker/SalGtkFilePicker.cxx1991
-rw-r--r--vcl/unx/gtk3/fpicker/SalGtkFilePicker.hxx240
-rw-r--r--vcl/unx/gtk3/fpicker/SalGtkFolderPicker.cxx177
-rw-r--r--vcl/unx/gtk3/fpicker/SalGtkFolderPicker.hxx66
-rw-r--r--vcl/unx/gtk3/fpicker/SalGtkPicker.cxx266
-rw-r--r--vcl/unx/gtk3/fpicker/SalGtkPicker.hxx117
-rw-r--r--vcl/unx/gtk3/fpicker/eventnotification.hxx43
-rw-r--r--vcl/unx/gtk3/fpicker/resourceprovider.cxx80
-rw-r--r--vcl/unx/gtk3/gtk3gloactiongroup.cxx405
-rw-r--r--vcl/unx/gtk3/gtk3glomenu.cxx692
-rw-r--r--vcl/unx/gtk3/gtk3gtkdata.cxx778
-rw-r--r--vcl/unx/gtk3/gtk3gtkframe.cxx4609
-rw-r--r--vcl/unx/gtk3/gtk3gtkinst.cxx16272
-rw-r--r--vcl/unx/gtk3/gtk3gtkobject.cxx463
-rw-r--r--vcl/unx/gtk3/gtk3gtkprintwrapper.cxx153
-rw-r--r--vcl/unx/gtk3/gtk3gtksalmenu.cxx1409
-rw-r--r--vcl/unx/gtk3/gtk3gtksys.cxx275
-rw-r--r--vcl/unx/gtk3/gtk3hudawareness.cxx110
-rw-r--r--vcl/unx/gtk3/gtk3salnativewidgets-gtk.cxx3731
-rw-r--r--vcl/unx/gtk3/gtk3salprn-gtk.cxx954
-rw-r--r--vcl/unx/gtk3/gtkprintwrapper.hxx17
-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_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_a11y.cxx38
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_cairo.cxx22
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_filepicker.cxx456
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_filepicker.hxx131
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_filepicker_ipc.cxx275
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_filepicker_ipc.hxx134
-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_printwrapper.cxx22
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_salnativewidgets-gtk.cxx22
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_salprn-gtk.cxx22
-rw-r--r--vcl/unx/gtk3_kde5/kde5_filepicker.cxx292
-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/kf5/KF5FilePicker.cxx180
-rw-r--r--vcl/unx/kf5/KF5FilePicker.hxx63
-rw-r--r--vcl/unx/kf5/KF5SalFrame.cxx188
-rw-r--r--vcl/unx/kf5/KF5SalFrame.hxx43
-rw-r--r--vcl/unx/kf5/KF5SalInstance.cxx101
-rw-r--r--vcl/unx/kf5/KF5SalInstance.hxx36
-rw-r--r--vcl/unx/x11/x11sys.cxx105
-rw-r--r--vcl/unx/x11/xlimits.cxx29
-rw-r--r--vcl/vcl.android.component31
-rw-r--r--vcl/vcl.common.component30
-rw-r--r--vcl/vcl.headless.component28
-rw-r--r--vcl/vcl.ios.component31
-rw-r--r--vcl/vcl.macosx.component41
-rw-r--r--vcl/vcl.unx.component41
-rw-r--r--vcl/vcl.windows.component32
-rw-r--r--vcl/win/app/saldata.cxx78
-rw-r--r--vcl/win/app/salinfo.cxx176
-rw-r--r--vcl/win/app/salinst.cxx1095
-rw-r--r--vcl/win/app/salshl.cxx112
-rw-r--r--vcl/win/app/saltimer.cxx199
-rw-r--r--vcl/win/gdi/DWriteTextRenderer.cxx413
-rw-r--r--vcl/win/gdi/gdiimpl.cxx2710
-rw-r--r--vcl/win/gdi/gdiimpl.hxx250
-rw-r--r--vcl/win/gdi/salbmp.cxx1067
-rw-r--r--vcl/win/gdi/salfont.cxx1766
-rw-r--r--vcl/win/gdi/salgdi.cxx1058
-rw-r--r--vcl/win/gdi/salgdi2.cxx248
-rw-r--r--vcl/win/gdi/salgdi_gdiplus.cxx98
-rw-r--r--vcl/win/gdi/salnativewidgets-luna.cxx1559
-rw-r--r--vcl/win/gdi/salprn.cxx1629
-rw-r--r--vcl/win/gdi/salvd.cxx226
-rw-r--r--vcl/win/gdi/winlayout.cxx635
-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/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.rc89
-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.cxx224
-rw-r--r--vcl/win/window/salframe.cxx5925
-rw-r--r--vcl/win/window/salmenu.cxx355
-rw-r--r--vcl/win/window/salobj.cxx727
-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.options2
-rw-r--r--vcl/workben/cgmfuzzer.cxx129
-rw-r--r--vcl/workben/cgmfuzzer.options4
-rw-r--r--vcl/workben/commonfuzzer.hxx143
-rw-r--r--vcl/workben/diffuzzer.cxx86
-rw-r--r--vcl/workben/diffuzzer.options4
-rw-r--r--vcl/workben/docxfuzzer.cxx39
-rw-r--r--vcl/workben/docxfuzzer.options4
-rw-r--r--vcl/workben/dxffuzzer.cxx67
-rw-r--r--vcl/workben/dxffuzzer.options2
-rw-r--r--vcl/workben/epsfuzzer.cxx67
-rw-r--r--vcl/workben/epsfuzzer.options2
-rw-r--r--vcl/workben/fftester.cxx692
-rw-r--r--vcl/workben/fodpfuzzer.cxx39
-rw-r--r--vcl/workben/fodpfuzzer.options5
-rw-r--r--vcl/workben/fodsfuzzer.cxx34
-rw-r--r--vcl/workben/fodsfuzzer.options5
-rw-r--r--vcl/workben/fodtfuzzer.cxx39
-rw-r--r--vcl/workben/fodtfuzzer.options5
-rw-r--r--vcl/workben/giffuzzer.cxx56
-rw-r--r--vcl/workben/giffuzzer.options3
-rw-r--r--vcl/workben/htmlfuzzer.cxx33
-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.cxx219
-rw-r--r--vcl/workben/jpgfuzzer.cxx56
-rw-r--r--vcl/workben/jpgfuzzer.options3
-rw-r--r--vcl/workben/localestub/localedata_en_AU.cxx1263
-rw-r--r--vcl/workben/localestub/localedata_en_BW.cxx140
-rw-r--r--vcl/workben/localestub/localedata_en_BZ.cxx225
-rw-r--r--vcl/workben/localestub/localedata_en_CA.cxx929
-rw-r--r--vcl/workben/localestub/localedata_en_GB.cxx912
-rw-r--r--vcl/workben/localestub/localedata_en_GH.cxx1526
-rw-r--r--vcl/workben/localestub/localedata_en_GM.cxx140
-rw-r--r--vcl/workben/localestub/localedata_en_IE.cxx259
-rw-r--r--vcl/workben/localestub/localedata_en_IN.cxx200
-rw-r--r--vcl/workben/localestub/localedata_en_JM.cxx1263
-rw-r--r--vcl/workben/localestub/localedata_en_LK.cxx144
-rw-r--r--vcl/workben/localestub/localedata_en_MW.cxx140
-rw-r--r--vcl/workben/localestub/localedata_en_MY.cxx118
-rw-r--r--vcl/workben/localestub/localedata_en_NA.cxx1280
-rw-r--r--vcl/workben/localestub/localedata_en_NG.cxx142
-rw-r--r--vcl/workben/localestub/localedata_en_NZ.cxx563
-rw-r--r--vcl/workben/localestub/localedata_en_PH.cxx140
-rw-r--r--vcl/workben/localestub/localedata_en_TT.cxx563
-rw-r--r--vcl/workben/localestub/localedata_en_US.cxx2609
-rw-r--r--vcl/workben/localestub/localedata_en_ZA.cxx1263
-rw-r--r--vcl/workben/localestub/localedata_en_ZM.cxx142
-rw-r--r--vcl/workben/localestub/localedata_en_ZW.cxx563
-rw-r--r--vcl/workben/localestub/localestub.cxx39
-rw-r--r--vcl/workben/lwpfuzzer.cxx57
-rw-r--r--vcl/workben/lwpfuzzer.options2
-rw-r--r--vcl/workben/metfuzzer.cxx67
-rw-r--r--vcl/workben/metfuzzer.options4
-rw-r--r--vcl/workben/mmlfuzzer.cxx29
-rw-r--r--vcl/workben/mmlfuzzer.options5
-rw-r--r--vcl/workben/mtfdemo.cxx165
-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/pcdfuzzer.cxx58
-rw-r--r--vcl/workben/pcdfuzzer.options2
-rw-r--r--vcl/workben/pctfuzzer.cxx58
-rw-r--r--vcl/workben/pctfuzzer.options2
-rw-r--r--vcl/workben/pcxfuzzer.cxx58
-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.cxx58
-rw-r--r--vcl/workben/ppmfuzzer.options2
-rw-r--r--vcl/workben/pptfuzzer.cxx135
-rw-r--r--vcl/workben/pptfuzzer.options2
-rw-r--r--vcl/workben/pptxfuzzer.cxx33
-rw-r--r--vcl/workben/pptxfuzzer.options4
-rw-r--r--vcl/workben/psdfuzzer.cxx58
-rw-r--r--vcl/workben/psdfuzzer.options2
-rw-r--r--vcl/workben/qpwfuzzer.cxx89
-rw-r--r--vcl/workben/qpwfuzzer.options4
-rw-r--r--vcl/workben/rasfuzzer.cxx58
-rw-r--r--vcl/workben/rasfuzzer.options2
-rw-r--r--vcl/workben/rtffuzzer.cxx90
-rw-r--r--vcl/workben/rtffuzzer.options4
-rw-r--r--vcl/workben/scrtffuzzer.cxx83
-rw-r--r--vcl/workben/scrtffuzzer.options4
-rw-r--r--vcl/workben/sftfuzzer.cxx44
-rw-r--r--vcl/workben/sftfuzzer.options2
-rw-r--r--vcl/workben/slkfuzzer.cxx89
-rw-r--r--vcl/workben/slkfuzzer.options4
-rw-r--r--vcl/workben/svdem.cxx98
-rw-r--r--vcl/workben/svmfuzzer.cxx56
-rw-r--r--vcl/workben/svmfuzzer.options2
-rw-r--r--vcl/workben/svpclient.cxx283
-rw-r--r--vcl/workben/svptest.cxx328
-rw-r--r--vcl/workben/tgafuzzer.cxx58
-rw-r--r--vcl/workben/tgafuzzer.options2
-rw-r--r--vcl/workben/tiffuzzer.cxx64
-rw-r--r--vcl/workben/tiffuzzer.options3
-rw-r--r--vcl/workben/vcldemo.cxx2450
-rw-r--r--vcl/workben/wksfuzzer.cxx53
-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.cxx104
-rw-r--r--vcl/workben/ww2fuzzer.options4
-rw-r--r--vcl/workben/ww6fuzzer.cxx106
-rw-r--r--vcl/workben/ww6fuzzer.options4
-rw-r--r--vcl/workben/ww8fuzzer.cxx106
-rw-r--r--vcl/workben/ww8fuzzer.options4
-rw-r--r--vcl/workben/xbmfuzzer.cxx56
-rw-r--r--vcl/workben/xbmfuzzer.options2
-rw-r--r--vcl/workben/xlsfuzzer.cxx53
-rw-r--r--vcl/workben/xlsfuzzer.options4
-rw-r--r--vcl/workben/xlsxfuzzer.cxx31
-rw-r--r--vcl/workben/xlsxfuzzer.options4
-rw-r--r--vcl/workben/xpmfuzzer.cxx56
-rw-r--r--vcl/workben/xpmfuzzer.options2
1982 files changed, 537161 insertions, 0 deletions
diff --git a/vcl/AllLangMoTarget_vcl.mk b/vcl/AllLangMoTarget_vcl.mk
new file mode 100644
index 000000000..784cbe5cf
--- /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_apitests.mk b/vcl/CppunitTest_vcl_apitests.mk
new file mode 100644
index 000000000..83c618b06
--- /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 000000000..9ee43d5a3
--- /dev/null
+++ b/vcl/CppunitTest_vcl_app_test.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_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 \
+))
+
+# 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 000000000..5a8862240
--- /dev/null
+++ b/vcl/CppunitTest_vcl_backend_test.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_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\
+))
+
+$(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 000000000..3af9c9faf
--- /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 000000000..dca63852a
--- /dev/null
+++ b/vcl/CppunitTest_vcl_bitmap_test.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_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 \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,vcl_bitmap_test,\
+ boost_headers \
+ glm_headers \
+))
+ifeq ($(DISABLE_GUI),)
+$(eval $(call gb_CppunitTest_use_externals,vcl_bitmap_test,\
+ epoxy \
+ ))
+endif
+
+$(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_components,vcl_bitmap_test,\
+ configmgr/source/configmgr \
+ i18npool/util/i18npool \
+ ucb/source/core/ucb1 \
+ unotools/util/utl \
+))
+
+$(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 000000000..6bdffa9d4
--- /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 000000000..b585382b4
--- /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_complextext.mk b/vcl/CppunitTest_vcl_complextext.mk
new file mode 100644
index 000000000..540110af9
--- /dev/null
+++ b/vcl/CppunitTest_vcl_complextext.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_complextext))
+
+$(eval $(call gb_CppunitTest_set_include,vcl_complextext,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_complextext, \
+ vcl/qa/cppunit/canvasbitmaptest \
+ vcl/qa/cppunit/complextext \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,vcl_complextext,\
+ boost_headers \
+ harfbuzz \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_complextext, \
+ comphelper \
+ cppu \
+ cppuhelper \
+ i18nlangtag \
+ sal \
+ svt \
+ test \
+ tl \
+ unotest \
+ vcl \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_complextext))
+
+$(eval $(call gb_CppunitTest_use_ure,vcl_complextext))
+$(eval $(call gb_CppunitTest_use_vcl,vcl_complextext))
+
+$(eval $(call gb_CppunitTest_use_components,vcl_complextext,\
+ configmgr/source/configmgr \
+ i18npool/util/i18npool \
+ ucb/source/core/ucb1 \
+))
+
+$(eval $(call gb_CppunitTest_use_configuration,vcl_complextext))
+
+$(eval $(call gb_CppunitTest_use_more_fonts,vcl_complextext))
+
+# 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 000000000..a602b4270
--- /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_errorhandler.mk b/vcl/CppunitTest_vcl_errorhandler.mk
new file mode 100644
index 000000000..00857fff0
--- /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_ipdf.mk b/vcl/CppunitTest_vcl_filter_ipdf.mk
new file mode 100644
index 000000000..403836ac7
--- /dev/null
+++ b/vcl/CppunitTest_vcl_filter_ipdf.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_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 \
+ sal \
+ sfx \
+ svx \
+ test \
+ tl \
+ unotest \
+ utl \
+ 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))
+
+# 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 000000000..03a423233
--- /dev/null
+++ b/vcl/CppunitTest_vcl_filters_test.mk
@@ -0,0 +1,53 @@
+# -*- 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-test \
+))
+
+ifeq ($(DISABLE_CVE_TESTS),TRUE)
+$(eval $(call gb_CppunitTest_add_defs,vcl_filters_test,\
+ -DDISABLE_CVE_TESTS \
+))
+endif
+
+$(eval $(call gb_CppunitTest_use_external,vcl_filters_test,boost_headers))
+
+$(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 000000000..8b66a989f
--- /dev/null
+++ b/vcl/CppunitTest_vcl_font.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_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 \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,vcl_font,boost_headers))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_font, \
+ comphelper \
+ cppu \
+ cppuhelper \
+ sal \
+ svt \
+ test \
+ tl \
+ tk \
+ unotest \
+ 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))
+
+# 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 000000000..0c596bf8d
--- /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 000000000..07a1b319e
--- /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 000000000..5496a75c1
--- /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 000000000..d4f701782
--- /dev/null
+++ b/vcl/CppunitTest_vcl_gen.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_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 \
+ 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_graphic_test.mk b/vcl/CppunitTest_vcl_graphic_test.mk
new file mode 100644
index 000000000..353d054e1
--- /dev/null
+++ b/vcl/CppunitTest_vcl_graphic_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_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 \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,vcl_graphic_test,\
+ boost_headers \
+ glm_headers \
+))
+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))
+
+# we need to explicitly depend on Library_gie because it's dynamically loaded for .gif
+$(call gb_CppunitTest_get_target,vcl_graphic_test) : $(call gb_Library_get_target,gie)
+
+# 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 000000000..88385872b
--- /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 000000000..4e8895013
--- /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 000000000..15dc99e8f
--- /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 000000000..65fd6b5fa
--- /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 000000000..2721d15e4
--- /dev/null
+++ b/vcl/CppunitTest_vcl_pdfexport.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_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 \
+ 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))
+
+$(eval $(call gb_CppunitTest_use_more_fonts,vcl_pdfexport))
+
+# 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 000000000..fc513eaec
--- /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_svm_test.mk b/vcl/CppunitTest_vcl_svm_test.mk
new file mode 100644
index 000000000..92b4dbaaf
--- /dev/null
+++ b/vcl/CppunitTest_vcl_svm_test.mk
@@ -0,0 +1,59 @@
+# -*- 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 \
+))
+ifeq ($(DISABLE_GUI),)
+$(eval $(call gb_CppunitTest_use_externals,vcl_svm_test,\
+ epoxy \
+ ))
+endif
+
+
+$(eval $(call gb_CppunitTest_set_include,vcl_svm_test,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_svm_test, \
+ comphelper \
+ cppu \
+ cppuhelper \
+ sal \
+ 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))
+
+# 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 000000000..a89e4e070
--- /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 000000000..ac668da41
--- /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 000000000..a7d77312c
--- /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 000000000..06f28de31
--- /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 000000000..96f84a937
--- /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)/KF5FilePicker.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_nativecalc.mk b/vcl/CustomTarget_nativecalc.mk
new file mode 100644
index 000000000..1c6804082
--- /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 000000000..964e7af1a
--- /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 000000000..513bf564b
--- /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 000000000..7465378c1
--- /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 000000000..c396f0f5d
--- /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 000000000..87caee8d9
--- /dev/null
+++ b/vcl/CustomTarget_qt5_moc.mk
@@ -0,0 +1,33 @@
+# -*- 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)/Qt5AccessibleWidget.moc \
+ $(call gb_CustomTarget_get_workdir,vcl/qt5)/Qt5Clipboard.moc \
+ $(call gb_CustomTarget_get_workdir,vcl/qt5)/Qt5FilePicker.moc \
+ $(call gb_CustomTarget_get_workdir,vcl/qt5)/Qt5Frame.moc \
+ $(call gb_CustomTarget_get_workdir,vcl/qt5)/Qt5Instance.moc \
+ $(call gb_CustomTarget_get_workdir,vcl/qt5)/Qt5MainWindow.moc \
+ $(call gb_CustomTarget_get_workdir,vcl/qt5)/Qt5Menu.moc \
+ $(call gb_CustomTarget_get_workdir,vcl/qt5)/Qt5Object.moc \
+ $(call gb_CustomTarget_get_workdir,vcl/qt5)/Qt5Timer.moc \
+ $(call gb_CustomTarget_get_workdir,vcl/qt5)/Qt5Widget.moc \
+ $(call gb_CustomTarget_get_workdir,vcl/qt5)/Qt5XAccessible.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/Executable_602fuzzer.mk b/vcl/Executable_602fuzzer.mk
new file mode 100644
index 000000000..d18a9a5c9
--- /dev/null
+++ b/vcl/Executable_602fuzzer.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,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,\
+ findsofficepath \
+ ulingu \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,602fuzzer,\
+ vcl/workben/602fuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,602fuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_bmpfuzzer.mk b/vcl/Executable_bmpfuzzer.mk
new file mode 100644
index 000000000..da22f2f3b
--- /dev/null
+++ b/vcl/Executable_bmpfuzzer.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/.
+#
+
+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,\
+ findsofficepath \
+ ulingu \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,bmpfuzzer,\
+ vcl/workben/bmpfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,bmpfuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_cgmfuzzer.mk b/vcl/Executable_cgmfuzzer.mk
new file mode 100644
index 000000000..f7cb91520
--- /dev/null
+++ b/vcl/Executable_cgmfuzzer.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,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,\
+ findsofficepath \
+ ulingu \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,cgmfuzzer,\
+ vcl/workben/cgmfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,cgmfuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_diffuzzer.mk b/vcl/Executable_diffuzzer.mk
new file mode 100644
index 000000000..03a05ee7c
--- /dev/null
+++ b/vcl/Executable_diffuzzer.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,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,\
+ findsofficepath \
+ ulingu \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,diffuzzer,\
+ vcl/workben/diffuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,diffuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_docxfuzzer.mk b/vcl/Executable_docxfuzzer.mk
new file mode 100644
index 000000000..b2634a390
--- /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) \
+))
+
+$(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,\
+ findsofficepath \
+ ulingu \
+ fuzzer_writer \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,docxfuzzer,\
+ vcl/workben/docxfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,docxfuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_dxffuzzer.mk b/vcl/Executable_dxffuzzer.mk
new file mode 100644
index 000000000..2b8665466
--- /dev/null
+++ b/vcl/Executable_dxffuzzer.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/.
+#
+
+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,\
+ findsofficepath \
+ ulingu \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,dxffuzzer,\
+ vcl/workben/dxffuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,dxffuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_epsfuzzer.mk b/vcl/Executable_epsfuzzer.mk
new file mode 100644
index 000000000..2851b43ad
--- /dev/null
+++ b/vcl/Executable_epsfuzzer.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/.
+#
+
+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,\
+ findsofficepath \
+ ulingu \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,epsfuzzer,\
+ vcl/workben/epsfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,epsfuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_fftester.mk b/vcl/Executable_fftester.mk
new file mode 100644
index 000000000..0eaa4e39c
--- /dev/null
+++ b/vcl/Executable_fftester.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_Executable_Executable,fftester))
+
+$(eval $(call gb_Executable_use_api,fftester,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_external,fftester,boost_headers))
+
+$(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 000000000..776231dcd
--- /dev/null
+++ b/vcl/Executable_fodpfuzzer.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,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,\
+ findsofficepath \
+ ulingu \
+ fuzzer_draw \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,fodpfuzzer,\
+ vcl/workben/fodpfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,fodpfuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_fodsfuzzer.mk b/vcl/Executable_fodsfuzzer.mk
new file mode 100644
index 000000000..a60c14eae
--- /dev/null
+++ b/vcl/Executable_fodsfuzzer.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,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,\
+ findsofficepath \
+ ulingu \
+ fuzzer_calc \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,fodsfuzzer,\
+ vcl/workben/fodsfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,fodsfuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_fodtfuzzer.mk b/vcl/Executable_fodtfuzzer.mk
new file mode 100644
index 000000000..c9801df6c
--- /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) \
+))
+
+$(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,\
+ findsofficepath \
+ ulingu \
+ fuzzer_writer \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,fodtfuzzer,\
+ vcl/workben/fodtfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,fodtfuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_giffuzzer.mk b/vcl/Executable_giffuzzer.mk
new file mode 100644
index 000000000..6d0458cba
--- /dev/null
+++ b/vcl/Executable_giffuzzer.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/.
+#
+
+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,\
+ findsofficepath \
+ ulingu \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,giffuzzer,\
+ vcl/workben/giffuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,giffuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_htmlfuzzer.mk b/vcl/Executable_htmlfuzzer.mk
new file mode 100644
index 000000000..9c60941cb
--- /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) \
+))
+
+$(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,\
+ findsofficepath \
+ ulingu \
+ fuzzer_writer \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,htmlfuzzer,\
+ vcl/workben/htmlfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,htmlfuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_hwpfuzzer.mk b/vcl/Executable_hwpfuzzer.mk
new file mode 100644
index 000000000..974cfd624
--- /dev/null
+++ b/vcl/Executable_hwpfuzzer.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,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,\
+ findsofficepath \
+ ulingu \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,hwpfuzzer,\
+ vcl/workben/hwpfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,hwpfuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_icontest.mk b/vcl/Executable_icontest.mk
new file mode 100644
index 000000000..78a10ccc6
--- /dev/null
+++ b/vcl/Executable_icontest.mk
@@ -0,0 +1,67 @@
+# -*- 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_use_externals,icontest,\
+ boost_headers \
+ glm_headers \
+))
+ifeq ($(DISABLE_GUI),)
+$(eval $(call gb_Executable_use_externals,icontest,\
+ epoxy \
+))
+endif
+
+ifeq ($(SYSTEM_GLM),TRUE)
+$(eval $(call gb_Executable_add_defs,icontest,\
+ -DGLM_ENABLE_EXPERIMENTAL \
+))
+endif
+
+$(eval $(call gb_Executable_add_defs,icontest,\
+ -DVCL_INTERNALS \
+))
+
+$(eval $(call gb_Executable_use_api,icontest,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,icontest,\
+ vclmain \
+))
+
+ifeq ($(OS), $(filter LINUX %BSD SOLARIS, $(OS)))
+$(eval $(call gb_Executable_add_libs,icontest,\
+ -lm $(DLOPEN_LIBS) \
+ -lX11 \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,icontest,\
+ glxtest \
+))
+
+endif
+
+$(eval $(call gb_Executable_use_libraries,icontest,\
+ comphelper \
+ cppu \
+ cppuhelper \
+ sal \
+ tl \
+ ucbhelper \
+ vcl \
+))
+
+$(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 000000000..daa801d45
--- /dev/null
+++ b/vcl/Executable_jpgfuzzer.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/.
+#
+
+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,\
+ findsofficepath \
+ ulingu \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,jpgfuzzer,\
+ vcl/workben/jpgfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,jpgfuzzer,\
+ -lFuzzingEngine \
+))
+
+# 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 000000000..08f6b82ed
--- /dev/null
+++ b/vcl/Executable_lo_kde5filepicker.mk
@@ -0,0 +1,94 @@
+# -*- 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) \
+ $(QT5_CFLAGS) \
+ $(KF5_CFLAGS) \
+))
+
+$(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,\
+ boost_headers \
+ epoxy \
+ kf5 \
+ dbus \
+))
+
+$(eval $(call gb_Executable_add_libs,lo_kde5filepicker,\
+ $(QT5_LIBS) \
+ $(KF5_LIBS) \
+ $(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 000000000..f5148e125
--- /dev/null
+++ b/vcl/Executable_lwpfuzzer.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,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,\
+ findsofficepath \
+ ulingu \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,lwpfuzzer,\
+ vcl/workben/lwpfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,lwpfuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_metfuzzer.mk b/vcl/Executable_metfuzzer.mk
new file mode 100644
index 000000000..930277d3b
--- /dev/null
+++ b/vcl/Executable_metfuzzer.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/.
+#
+
+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,\
+ findsofficepath \
+ ulingu \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,metfuzzer,\
+ vcl/workben/metfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,metfuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_mmlfuzzer.mk b/vcl/Executable_mmlfuzzer.mk
new file mode 100644
index 000000000..b093017a7
--- /dev/null
+++ b/vcl/Executable_mmlfuzzer.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,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,\
+ findsofficepath \
+ ulingu \
+ fuzzer_math \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,mmlfuzzer,\
+ vcl/workben/mmlfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,mmlfuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_mtfdemo.mk b/vcl/Executable_mtfdemo.mk
new file mode 100644
index 000000000..5c4c45165
--- /dev/null
+++ b/vcl/Executable_mtfdemo.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_Executable_Executable,mtfdemo))
+
+$(eval $(call gb_Executable_use_api,mtfdemo,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_external,mtfdemo,boost_headers))
+
+$(eval $(call gb_Executable_set_include,mtfdemo,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,mtfdemo,\
+ basegfx \
+ tl \
+ sal \
+ vcl \
+ cppu \
+ cppuhelper \
+ comphelper \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,mtfdemo,\
+ vcl/workben/mtfdemo \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,mtfdemo,\
+ vclmain \
+))
+
+ifeq ($(OS), $(filter LINUX %BSD SOLARIS, $(OS)))
+$(eval $(call gb_Executable_add_libs,mtfdemo,\
+ -lm $(DLOPEN_LIBS) \
+ -lX11 \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,mtfdemo,\
+ glxtest \
+))
+endif
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_mtpfuzzer.mk b/vcl/Executable_mtpfuzzer.mk
new file mode 100644
index 000000000..73e30b257
--- /dev/null
+++ b/vcl/Executable_mtpfuzzer.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,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,\
+ findsofficepath \
+ ulingu \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,mtpfuzzer,\
+ vcl/workben/mtpfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,mtpfuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_olefuzzer.mk b/vcl/Executable_olefuzzer.mk
new file mode 100644
index 000000000..4a73d573b
--- /dev/null
+++ b/vcl/Executable_olefuzzer.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/.
+#
+
+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,\
+ findsofficepath \
+ ulingu \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,olefuzzer,\
+ vcl/workben/olefuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,olefuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_pcdfuzzer.mk b/vcl/Executable_pcdfuzzer.mk
new file mode 100644
index 000000000..65916135e
--- /dev/null
+++ b/vcl/Executable_pcdfuzzer.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/.
+#
+
+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,\
+ findsofficepath \
+ ulingu \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,pcdfuzzer,\
+ vcl/workben/pcdfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,pcdfuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_pctfuzzer.mk b/vcl/Executable_pctfuzzer.mk
new file mode 100644
index 000000000..0265fd59d
--- /dev/null
+++ b/vcl/Executable_pctfuzzer.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/.
+#
+
+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,\
+ findsofficepath \
+ ulingu \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,pctfuzzer,\
+ vcl/workben/pctfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,pctfuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_pcxfuzzer.mk b/vcl/Executable_pcxfuzzer.mk
new file mode 100644
index 000000000..ad24b43c1
--- /dev/null
+++ b/vcl/Executable_pcxfuzzer.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/.
+#
+
+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,\
+ findsofficepath \
+ ulingu \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,pcxfuzzer,\
+ vcl/workben/pcxfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,pcxfuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_pngfuzzer.mk b/vcl/Executable_pngfuzzer.mk
new file mode 100644
index 000000000..24b9f4897
--- /dev/null
+++ b/vcl/Executable_pngfuzzer.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/.
+#
+
+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,\
+ findsofficepath \
+ ulingu \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,pngfuzzer,\
+ vcl/workben/pngfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,pngfuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_ppmfuzzer.mk b/vcl/Executable_ppmfuzzer.mk
new file mode 100644
index 000000000..d0909ebba
--- /dev/null
+++ b/vcl/Executable_ppmfuzzer.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/.
+#
+
+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,\
+ findsofficepath \
+ ulingu \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,ppmfuzzer,\
+ vcl/workben/ppmfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,ppmfuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_pptfuzzer.mk b/vcl/Executable_pptfuzzer.mk
new file mode 100644
index 000000000..85c8ed3c4
--- /dev/null
+++ b/vcl/Executable_pptfuzzer.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,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,\
+ findsofficepath \
+ ulingu \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,pptfuzzer,\
+ vcl/workben/pptfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,pptfuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_pptxfuzzer.mk b/vcl/Executable_pptxfuzzer.mk
new file mode 100644
index 000000000..3687a1808
--- /dev/null
+++ b/vcl/Executable_pptxfuzzer.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,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,\
+ findsofficepath \
+ ulingu \
+ fuzzer_draw \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,pptxfuzzer,\
+ vcl/workben/pptxfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,pptxfuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_psdfuzzer.mk b/vcl/Executable_psdfuzzer.mk
new file mode 100644
index 000000000..e53d3dddb
--- /dev/null
+++ b/vcl/Executable_psdfuzzer.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/.
+#
+
+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,\
+ findsofficepath \
+ ulingu \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,psdfuzzer,\
+ vcl/workben/psdfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,psdfuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_qpwfuzzer.mk b/vcl/Executable_qpwfuzzer.mk
new file mode 100644
index 000000000..4f8103355
--- /dev/null
+++ b/vcl/Executable_qpwfuzzer.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,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,\
+ findsofficepath \
+ ulingu \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,qpwfuzzer,\
+ vcl/workben/qpwfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,qpwfuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_rasfuzzer.mk b/vcl/Executable_rasfuzzer.mk
new file mode 100644
index 000000000..7440544c7
--- /dev/null
+++ b/vcl/Executable_rasfuzzer.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/.
+#
+
+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,\
+ findsofficepath \
+ ulingu \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,rasfuzzer,\
+ vcl/workben/rasfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,rasfuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_rtffuzzer.mk b/vcl/Executable_rtffuzzer.mk
new file mode 100644
index 000000000..28f0a3afe
--- /dev/null
+++ b/vcl/Executable_rtffuzzer.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,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,\
+ findsofficepath \
+ ulingu \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,rtffuzzer,\
+ vcl/workben/rtffuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,rtffuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_scrtffuzzer.mk b/vcl/Executable_scrtffuzzer.mk
new file mode 100644
index 000000000..6721092b2
--- /dev/null
+++ b/vcl/Executable_scrtffuzzer.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,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,\
+ findsofficepath \
+ ulingu \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,scrtffuzzer,\
+ vcl/workben/scrtffuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,scrtffuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_sftfuzzer.mk b/vcl/Executable_sftfuzzer.mk
new file mode 100644
index 000000000..be2ecff64
--- /dev/null
+++ b/vcl/Executable_sftfuzzer.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/.
+#
+
+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,\
+ findsofficepath \
+ ulingu \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,sftfuzzer,\
+ vcl/workben/sftfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,sftfuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_slkfuzzer.mk b/vcl/Executable_slkfuzzer.mk
new file mode 100644
index 000000000..b37cb8f16
--- /dev/null
+++ b/vcl/Executable_slkfuzzer.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,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,\
+ findsofficepath \
+ ulingu \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,slkfuzzer,\
+ vcl/workben/slkfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,slkfuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_svdemo.mk b/vcl/Executable_svdemo.mk
new file mode 100644
index 000000000..0a958a286
--- /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_use_external,svdemo,boost_headers))
+
+$(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 \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,svdemo,\
+ vcl/workben/svdem \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_svmfuzzer.mk b/vcl/Executable_svmfuzzer.mk
new file mode 100644
index 000000000..9d41cc6ed
--- /dev/null
+++ b/vcl/Executable_svmfuzzer.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/.
+#
+
+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,\
+ findsofficepath \
+ ulingu \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,svmfuzzer,\
+ vcl/workben/svmfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,svmfuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_svpclient.mk b/vcl/Executable_svpclient.mk
new file mode 100644
index 000000000..3d92f2ebc
--- /dev/null
+++ b/vcl/Executable_svpclient.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/.
+#
+
+$(eval $(call gb_Executable_Executable,svpclient))
+
+$(eval $(call gb_Executable_use_api,svpclient,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_external,svpclient,boost_headers))
+
+$(eval $(call gb_Executable_set_include,svpclient,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(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 000000000..c373aa66f
--- /dev/null
+++ b/vcl/Executable_svptest.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,svptest))
+
+$(eval $(call gb_Executable_use_api,svptest,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_external,svptest,boost_headers))
+
+$(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 000000000..a53da7c7e
--- /dev/null
+++ b/vcl/Executable_tgafuzzer.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/.
+#
+
+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,\
+ findsofficepath \
+ ulingu \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,tgafuzzer,\
+ vcl/workben/tgafuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,tgafuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_tiffuzzer.mk b/vcl/Executable_tiffuzzer.mk
new file mode 100644
index 000000000..1b8103aa0
--- /dev/null
+++ b/vcl/Executable_tiffuzzer.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/.
+#
+
+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,\
+ findsofficepath \
+ ulingu \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,tiffuzzer,\
+ vcl/workben/tiffuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,tiffuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_ui-previewer.mk b/vcl/Executable_ui-previewer.mk
new file mode 100644
index 000000000..bee97cbac
--- /dev/null
+++ b/vcl/Executable_ui-previewer.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_Executable_Executable,ui-previewer))
+
+$(eval $(call gb_Executable_use_external,ui-previewer,boost_headers))
+
+$(eval $(call gb_Executable_use_api,ui-previewer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,ui-previewer,\
+ vclmain \
+))
+
+$(eval $(call gb_Executable_use_libraries,ui-previewer,\
+ comphelper \
+ cppu \
+ cppuhelper \
+ sal \
+ tl \
+ ucbhelper \
+ vcl \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,ui-previewer,\
+ vcl/source/uipreviewer/previewer \
+))
+
+$(eval $(call gb_Executable_add_defs,ui-previewer,\
+ -DVCL_INTERNALS \
+))
+
+ifeq ($(OS), $(filter LINUX %BSD SOLARIS, $(OS)))
+$(eval $(call gb_Executable_add_libs,ui-previewer,\
+ -lm $(DLOPEN_LIBS) \
+ -lX11 \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,ui-previewer,\
+ glxtest \
+))
+endif
+
+$(eval $(call gb_Executable_add_default_nativeres,ui-previewer))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_vcldemo.mk b/vcl/Executable_vcldemo.mk
new file mode 100644
index 000000000..a9a8d195f
--- /dev/null
+++ b/vcl/Executable_vcldemo.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_Executable_Executable,vcldemo))
+
+$(eval $(call gb_Executable_use_api,vcldemo,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,vcldemo,\
+ boost_headers \
+ glm_headers \
+ harfbuzz \
+))
+ifeq ($(DISABLE_GUI),)
+$(eval $(call gb_Executable_use_externals,vcldemo,\
+ epoxy \
+))
+endif
+
+$(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 \
+ vcl \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,vcldemo,\
+ vcl/workben/vcldemo \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,vcldemo,\
+ vclmain \
+))
+
+ifeq ($(OS), $(filter LINUX %BSD SOLARIS, $(OS)))
+$(eval $(call gb_Executable_add_libs,vcldemo,\
+ -lm $(DLOPEN_LIBS) \
+ -lX11 \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,vcldemo,\
+ glxtest \
+))
+endif
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_visualbackendtest.mk b/vcl/Executable_visualbackendtest.mk
new file mode 100644
index 000000000..41c641a5f
--- /dev/null
+++ b/vcl/Executable_visualbackendtest.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_Executable_Executable,visualbackendtest))
+
+$(eval $(call gb_Executable_use_api,visualbackendtest,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_external,visualbackendtest,boost_headers))
+
+$(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_add_exception_objects,visualbackendtest,\
+ vcl/backendtest/VisualBackendTest \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,visualbackendtest,\
+ vclmain \
+))
+
+ifeq ($(OS),LINUX)
+$(eval $(call gb_Executable_add_libs,visualbackendtest,\
+ -lm \
+ -ldl \
+ -lX11 \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,visualbackendtest,\
+ glxtest \
+))
+endif
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_wksfuzzer.mk b/vcl/Executable_wksfuzzer.mk
new file mode 100644
index 000000000..2c0776888
--- /dev/null
+++ b/vcl/Executable_wksfuzzer.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,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,\
+ findsofficepath \
+ ulingu \
+ fuzzer_calc \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,wksfuzzer,\
+ vcl/workben/wksfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,wksfuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_wmffuzzer.mk b/vcl/Executable_wmffuzzer.mk
new file mode 100644
index 000000000..7e277e0ae
--- /dev/null
+++ b/vcl/Executable_wmffuzzer.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/.
+#
+
+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,\
+ findsofficepath \
+ ulingu \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,wmffuzzer,\
+ vcl/workben/wmffuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,wmffuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_ww2fuzzer.mk b/vcl/Executable_ww2fuzzer.mk
new file mode 100644
index 000000000..bd6f95fa3
--- /dev/null
+++ b/vcl/Executable_ww2fuzzer.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,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,\
+ findsofficepath \
+ ulingu \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,ww2fuzzer,\
+ vcl/workben/ww2fuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,ww2fuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_ww6fuzzer.mk b/vcl/Executable_ww6fuzzer.mk
new file mode 100644
index 000000000..cf540f89e
--- /dev/null
+++ b/vcl/Executable_ww6fuzzer.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,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,\
+ findsofficepath \
+ ulingu \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,ww6fuzzer,\
+ vcl/workben/ww6fuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,ww6fuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_ww8fuzzer.mk b/vcl/Executable_ww8fuzzer.mk
new file mode 100644
index 000000000..62bfd3c53
--- /dev/null
+++ b/vcl/Executable_ww8fuzzer.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,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,\
+ findsofficepath \
+ ulingu \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,ww8fuzzer,\
+ vcl/workben/ww8fuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,ww8fuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_xbmfuzzer.mk b/vcl/Executable_xbmfuzzer.mk
new file mode 100644
index 000000000..fc67b8e8d
--- /dev/null
+++ b/vcl/Executable_xbmfuzzer.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/.
+#
+
+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,\
+ findsofficepath \
+ ulingu \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,xbmfuzzer,\
+ vcl/workben/xbmfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,xbmfuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_xlsfuzzer.mk b/vcl/Executable_xlsfuzzer.mk
new file mode 100644
index 000000000..d3b2948d6
--- /dev/null
+++ b/vcl/Executable_xlsfuzzer.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,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,\
+ findsofficepath \
+ ulingu \
+ fuzzer_calc \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,xlsfuzzer,\
+ vcl/workben/xlsfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,xlsfuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_xlsxfuzzer.mk b/vcl/Executable_xlsxfuzzer.mk
new file mode 100644
index 000000000..adf33c457
--- /dev/null
+++ b/vcl/Executable_xlsxfuzzer.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,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,\
+ findsofficepath \
+ ulingu \
+ fuzzer_calc \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,xlsxfuzzer,\
+ vcl/workben/xlsxfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,xlsxfuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_xpmfuzzer.mk b/vcl/Executable_xpmfuzzer.mk
new file mode 100644
index 000000000..892d5c962
--- /dev/null
+++ b/vcl/Executable_xpmfuzzer.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/.
+#
+
+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,\
+ findsofficepath \
+ ulingu \
+ fuzzerstubs \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,xpmfuzzer,\
+ vcl/workben/xpmfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,xpmfuzzer,\
+ -lFuzzingEngine \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/IwyuFilter_vcl.yaml b/vcl/IwyuFilter_vcl.yaml
new file mode 100644
index 000000000..423c06109
--- /dev/null
+++ b/vcl/IwyuFilter_vcl.yaml
@@ -0,0 +1,128 @@
+---
+assumeFilename: vcl/source/app/svapp.cxx
+blacklist:
+ vcl/inc/salusereventlist.hxx:
+ # Don't propose hxx -> h change in URE libs
+ - osl/thread.hxx
+ vcl/inc/headless/svpgdi.hxx:
+ # OSL_BIGENDIAN is being checked
+ - osl/endian.h
+ vcl/inc/headless/svpinst.hxx:
+ # Don't propose hxx -> h change in URE libs
+ - osl/thread.hxx
+ vcl/inc/opengl/RenderList.hxx:
+ # Don't replace with impl. details
+ - glm/glm.hpp
+ vcl/inc/opengl/VertexUtils.hxx:
+ # Don't replace with impl. details
+ - glm/gtx/norm.hpp
+ 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/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/bitmapaccess.hxx
+ vcl/source/filter/png/pngwrite.cxx:
+ # Actually these are used
+ - com/sun/star/beans/PropertyValue.hpp
+ - com/sun/star/uno/Sequence.hxx
+ vcl/source/filter/wmf/wmfexternal.cxx:
+ # Actually these are used
+ - com/sun/star/beans/PropertyValue.hpp
+ vcl/source/font/fontselect.cxx:
+ # Needed on WIN32
+ - PhysicalFontFace.hxx
+ vcl/source/fontsubset/sft.cxx:
+ # OSL_BIGENDIAN is being checked
+ - osl/endian.h
+ # 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/gdi/salbmp.cxx:
+ # OSL_BIGENDIAN is being checked
+ - osl/endian.h
+ 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
diff --git a/vcl/Library_desktop_detector.mk b/vcl/Library_desktop_detector.mk
new file mode 100644
index 000000000..f28ff9078
--- /dev/null
+++ b/vcl/Library_desktop_detector.mk
@@ -0,0 +1,72 @@
+# -*- 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_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,\
+ vcl \
+ 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 $(DLOPEN_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 000000000..e12ccf1a6
--- /dev/null
+++ b/vcl/Library_vcl.mk
@@ -0,0 +1,736 @@
+# -*- 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))
+
+ifeq ($(OS),MACOSX)
+$(eval $(call gb_Library_set_componentfile,vcl,vcl/vcl.macosx))
+else ifeq ($(OS),WNT)
+$(eval $(call gb_Library_set_componentfile,vcl,vcl/vcl.windows))
+else ifeq ($(OS),ANDROID)
+$(eval $(call gb_Library_set_componentfile,vcl,vcl/vcl.android))
+else ifeq ($(OS),iOS)
+$(eval $(call gb_Library_set_componentfile,vcl,vcl/vcl.ios))
+else ifeq ($(DISABLE_GUI),TRUE)
+$(eval $(call gb_Library_set_componentfile,vcl,vcl/vcl.headless))
+else
+$(eval $(call gb_Library_set_componentfile,vcl,vcl/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))\" \
+))
+
+ifeq ($(SYSTEM_GLM),TRUE)
+$(eval $(call gb_Library_add_defs,vcl,\
+ -DGLM_ENABLE_EXPERIMENTAL \
+))
+endif
+
+$(eval $(call gb_Library_use_sdk_api,vcl))
+
+$(eval $(call gb_Library_use_custom_headers,vcl,\
+ officecfg/registry \
+))
+
+$(eval $(call gb_Library_use_externals,vcl,\
+ libjpeg \
+ libeot \
+ libpng \
+ $(if $(filter PDFIUM,$(BUILD_TYPE)),pdfium) \
+))
+
+$(eval $(call gb_Library_use_libraries,vcl,\
+ $(call gb_Helper_optional,BREAKPAD, \
+ crashreport) \
+ svl \
+ tl \
+ utl \
+ sot \
+ ucbhelper \
+ basegfx \
+ comphelper \
+ cppuhelper \
+ i18nlangtag \
+ i18nutil \
+ $(if $(filter OPENCL,$(BUILD_TYPE)),opencl) \
+ cppu \
+ sal \
+ salhelper \
+ xmlreader \
+))
+
+ifeq ($(ENABLE_JAVA),TRUE)
+$(eval $(call gb_Library_use_libraries,vcl,\
+ jvmaccess \
+))
+endif
+
+$(eval $(call gb_Library_use_externals,vcl,\
+ boost_headers \
+ gio \
+ glm_headers \
+ graphite \
+ harfbuzz \
+ icu_headers \
+ icuuc \
+ lcms2 \
+ mdds_headers \
+ $(if $(filter SKIA,$(BUILD_TYPE)),skia) \
+))
+
+ifeq ($(DISABLE_GUI),)
+$(eval $(call gb_Library_use_externals,vcl,\
+ epoxy \
+ ))
+endif
+
+$(eval $(call gb_Library_add_exception_objects,vcl,\
+ vcl/source/animate/Animation \
+ vcl/source/animate/AnimationBitmap \
+ 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/event \
+ vcl/source/window/floatwin \
+ 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/mnemonicengine \
+ vcl/source/window/mouse \
+ vcl/source/window/NotebookBarAddonsMerger \
+ vcl/source/window/OptionalBox \
+ vcl/source/window/popupmenuwindow \
+ 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/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/menubtn \
+ vcl/source/control/notebookbar \
+ 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/outdev \
+ vcl/source/outdev/outdevstate \
+ vcl/source/outdev/clipping \
+ vcl/source/outdev/polygon \
+ vcl/source/outdev/transparent \
+ vcl/source/outdev/mask \
+ vcl/source/outdev/bitmap \
+ 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/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/gdi/alpha \
+ vcl/source/gdi/bitmap3 \
+ vcl/source/gdi/bitmapex \
+ vcl/source/gdi/bmpacc2 \
+ vcl/source/gdi/bmpacc3 \
+ vcl/source/gdi/bmpacc \
+ vcl/source/gdi/bmpfast \
+ vcl/source/gdi/configsettings \
+ vcl/source/gdi/cvtgrf \
+ vcl/source/gdi/svmconverter \
+ vcl/source/gdi/dibtools \
+ 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/impanmvw \
+ vcl/source/gdi/impglyphitem \
+ vcl/source/gdi/impgraph \
+ vcl/source/gdi/impvect \
+ 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/pdffontcache \
+ vcl/source/gdi/pdfwriter \
+ vcl/source/gdi/pdfwriter_impl2 \
+ vcl/source/gdi/pdfwriter_impl \
+ 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/textlayout \
+ vcl/source/gdi/virdev \
+ vcl/source/gdi/wall \
+ vcl/source/gdi/scrptrun \
+ vcl/source/gdi/CommonSalLayout \
+ vcl/source/gdi/TypeSerializer \
+ vcl/source/pdf/ResourceDict \
+ vcl/source/pdf/Matrix3 \
+ vcl/source/pdf/XmpMetadata \
+ vcl/source/pdf/PDFiumLibrary \
+ vcl/source/graphic/GraphicID \
+ vcl/source/graphic/GraphicLoader \
+ vcl/source/graphic/GraphicObject \
+ vcl/source/graphic/GraphicObject2 \
+ vcl/source/graphic/GraphicReader \
+ vcl/source/graphic/grfattr \
+ vcl/source/graphic/Manager \
+ vcl/source/graphic/UnoGraphic \
+ vcl/source/graphic/UnoGraphicDescriptor \
+ vcl/source/graphic/UnoGraphicObject \
+ vcl/source/graphic/UnoGraphicProvider \
+ vcl/source/graphic/UnoGraphicTransformer \
+ vcl/source/bitmap/bitmap \
+ vcl/source/bitmap/bitmapfilter \
+ vcl/source/bitmap/BitmapAlphaClampFilter \
+ vcl/source/bitmap/BitmapBasicMorphologyFilter \
+ vcl/source/bitmap/BitmapMonochromeFilter \
+ vcl/source/bitmap/BitmapSmoothenFilter \
+ vcl/source/bitmap/BitmapLightenFilter \
+ vcl/source/bitmap/BitmapDisabledImageFilter \
+ vcl/source/bitmap/BitmapColorizeFilter \
+ vcl/source/bitmap/bitmappaint \
+ 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/checksum \
+ 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/errcode \
+ vcl/source/helper/evntpost \
+ 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/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/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/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/igif/decode \
+ vcl/source/filter/igif/gifread \
+ vcl/source/filter/ipdf/pdfread \
+ vcl/source/filter/ipdf/pdfdocument \
+ 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/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/pngread \
+ vcl/source/filter/png/pngwrite \
+ vcl/source/font/Feature \
+ vcl/source/font/FeatureCollector \
+ vcl/source/font/FeatureParser \
+ vcl/source/font/OpenTypeFeatureDefinitionList \
+ vcl/source/font/PhysicalFontCollection \
+ vcl/source/font/PhysicalFontFace \
+ vcl/source/font/PhysicalFontFamily \
+ vcl/source/font/fontattributes \
+ vcl/source/font/fontselect \
+ vcl/source/font/fontinstance \
+ 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/list \
+ vcl/source/fontsubset/sft \
+ vcl/source/fontsubset/ttcr \
+ vcl/source/fontsubset/xlat \
+ 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/rectangle \
+ vcl/jsdialog/jsdialogbuilder \
+))
+
+$(eval $(call gb_Library_add_cobjects,vcl,\
+ vcl/source/filter/jpeg/transupp \
+))
+
+vcl_quartz_code= \
+ vcl/quartz/salbmp \
+ vcl/quartz/utils \
+ vcl/quartz/salgdicommon \
+ vcl/quartz/salvd \
+
+vcl_coretext_code= \
+ vcl/quartz/ctfonts \
+ vcl/quartz/salgdi \
+
+vcl_headless_code= \
+ vcl/headless/svpframe \
+ $(if $(filter-out iOS,$(OS)), \
+ vcl/headless/svpbmp \
+ vcl/headless/svpgdi \
+ vcl/headless/svpdata \
+ vcl/headless/CustomWidgetDraw) \
+ 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/headless/svpcairotextrender \
+ vcl/unx/generic/print/bitmap_gfx \
+ vcl/unx/generic/print/common_gfx \
+ vcl/unx/generic/print/glyphset \
+ vcl/unx/generic/print/printerjob \
+ vcl/unx/generic/print/psputil \
+ vcl/unx/generic/print/genpspgraphics \
+ vcl/unx/generic/print/genprnpsp \
+ vcl/unx/generic/print/prtsetup \
+ vcl/unx/generic/print/text_gfx \
+
+ifeq ($(USING_X11),TRUE)
+$(eval $(call gb_Library_add_exception_objects,vcl,\
+ vcl/source/app/salplug \
+ vcl/unx/generic/printer/jobdata \
+ vcl/unx/generic/printer/ppdparser \
+ vcl/unx/generic/gdi/nativewindowhandleprovider \
+ vcl/unx/generic/window/screensaverinhibitor \
+ vcl/unx/generic/printer/cpdmgr \
+ $(if $(filter TRUE,$(ENABLE_CUPS)),\
+ vcl/unx/generic/printer/cupsmgr \
+ vcl/unx/generic/printer/printerinfomanager \
+ , \
+ vcl/null/printerinfomanager \
+ ) \
+ $(vcl_headless_code) \
+ $(vcl_headless_freetype_code) \
+))
+
+$(eval $(call gb_Library_use_externals,vcl,\
+ cairo \
+ cups \
+ dbus \
+ fontconfig \
+ freetype \
+ valgrind \
+))
+endif
+
+ifeq ($(OS), $(filter LINUX %BSD SOLARIS, $(OS)))
+$(eval $(call gb_Library_add_libs,vcl,\
+ -lm $(DLOPEN_LIBS) \
+))
+endif
+
+ifeq ($(DISABLE_GUI),TRUE)
+$(eval $(call gb_Library_add_exception_objects,vcl,\
+ vcl/unx/generic/printer/jobdata \
+ vcl/unx/generic/printer/ppdparser \
+ vcl/null/printerinfomanager \
+ vcl/headless/headlessinst \
+ vcl/skia/SkiaHelper \
+ $(vcl_headless_code) \
+ $(vcl_headless_freetype_code) \
+))
+
+$(eval $(call gb_Library_use_externals,vcl,\
+ cairo \
+ freetype \
+ fontconfig \
+))
+
+else # ! DISABLE_GUI
+
+$(eval $(call gb_Library_add_exception_objects,vcl,\
+ vcl/opengl/DeviceInfo \
+ vcl/opengl/gdiimpl \
+ vcl/opengl/salbmp \
+ vcl/opengl/scale \
+ vcl/opengl/framebuffer \
+ vcl/opengl/program \
+ vcl/opengl/texture \
+ vcl/opengl/FixedTextureAtlas \
+ vcl/opengl/PackedTextureAtlas \
+ vcl/opengl/RenderList \
+ vcl/opengl/LineRenderUtils \
+ vcl/source/opengl/OpenGLContext \
+ vcl/source/opengl/OpenGLHelper \
+ vcl/skia/SkiaHelper \
+ $(if $(filter SKIA,$(BUILD_TYPE)), \
+ vcl/skia/salbmp \
+ vcl/skia/zone \
+ vcl/skia/gdiimpl \
+ ) \
+ ))
+
+# runtime dependency
+$(eval $(call gb_Library_use_package,vcl,vcl_opengl_shader))
+ifeq ($(OS),WNT)
+$(eval $(call gb_Library_use_package,vcl,vcl_opengl_blacklist))
+endif
+
+ifeq ($(OS), $(filter LINUX %BSD SOLARIS, $(OS)))
+$(eval $(call gb_Library_add_libs,vcl,\
+ -lX11 \
+ -lXext \
+))
+$(eval $(call gb_Library_add_exception_objects,vcl,\
+ vcl/opengl/x11/X11DeviceInfo \
+))
+endif
+endif # ! DISABLE_GUI
+
+
+ifeq ($(OS),HAIKU)
+$(eval $(call gb_Library_add_exception_objects,vcl,\
+ vcl/unx/generic/printer/jobdata \
+ vcl/unx/generic/printer/ppdparser \
+ vcl/null/printerinfomanager \
+ $(vcl_headless_code) \
+ $(vcl_headless_freetype_code) \
+))
+
+$(eval $(call gb_Library_add_libs,vcl,\
+ -lbe \
+))
+
+$(eval $(call gb_Library_add_exception_objects,vcl, \
+ $(if $(or $(ENABLE_QT5),$(ENABLE_KF5)),vcl/source/app/salplug) \
+))
+
+$(eval $(call gb_Library_use_externals,vcl,\
+ cairo \
+ fontconfig \
+ freetype \
+ expat \
+))
+endif
+
+
+ifeq ($(OS),ANDROID)
+$(eval $(call gb_Library_add_libs,vcl,\
+ -llog \
+ -landroid \
+ -llo-bootstrap \
+))
+$(eval $(call gb_Library_add_exception_objects,vcl,\
+ vcl/unx/generic/printer/jobdata \
+ vcl/unx/generic/printer/ppdparser \
+ vcl/null/printerinfomanager \
+ vcl/android/androidinst \
+ $(vcl_headless_code) \
+ $(vcl_headless_freetype_code) \
+))
+
+$(eval $(call gb_Library_use_externals,vcl,\
+ cairo \
+ fontconfig \
+ freetype \
+ expat \
+))
+endif
+
+
+ifeq ($(OS),iOS)
+$(eval $(call gb_Library_add_cxxflags,vcl,\
+ $(gb_OBJCXXFLAGS) \
+))
+$(eval $(call gb_Library_add_exception_objects,vcl,\
+ vcl/ios/iosinst \
+ vcl/ios/dummies \
+ vcl/ios/clipboard \
+ vcl/ios/iOSTransferable \
+ vcl/ios/DataFlavorMapping \
+ vcl/ios/HtmlFmtFlt \
+ $(vcl_coretext_code) \
+ $(vcl_quartz_code) \
+ $(vcl_headless_code) \
+))
+$(eval $(call gb_Library_use_system_darwin_frameworks,vcl,\
+ UIKit \
+ CoreFoundation \
+))
+endif
+
+
+ifeq ($(OS),MACOSX)
+$(eval $(call gb_Library_use_system_darwin_frameworks,vcl,\
+ Cocoa \
+ CoreFoundation \
+))
+
+$(eval $(call gb_Library_add_exception_objects,vcl,\
+ vcl/source/app/salplug \
+))
+endif
+
+
+ifeq ($(OS),WNT)
+$(eval $(call gb_Library_add_exception_objects,vcl,\
+ vcl/opengl/win/WinDeviceInfo \
+ vcl/source/app/salplug \
+))
+
+$(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_package,vcl,postprocess_images))
+endif
+
+ifeq ($(OS), $(filter LINUX %BSD SOLARIS, $(OS)))
+ifeq ($(USING_X11),TRUE)
+$(eval $(call gb_Library_use_static_libraries,vcl,\
+ glxtest \
+))
+endif
+endif
+
+# 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 000000000..36652b701
--- /dev/null
+++ b/vcl/Library_vclplug_gen.mk
@@ -0,0 +1,161 @@
+# -*- 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_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,\
+ vcl \
+ 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 \
+ glm_headers \
+ 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/x11cairotextrender \
+ vcl/unx/generic/gdi/gdiimpl \
+ vcl/unx/generic/gdi/salbmp \
+ vcl/unx/generic/gdi/salgdi2 \
+ vcl/unx/generic/gdi/font \
+ vcl/unx/generic/gdi/salgdi \
+ vcl/unx/generic/gdi/salvd \
+ vcl/unx/generic/gdi/xrender_peer \
+ vcl/unx/generic/window/salframe \
+ vcl/unx/generic/window/salobj \
+ vcl/unx/x11/x11sys \
+ vcl/unx/x11/xlimits \
+ vcl/opengl/x11/cairotextrender \
+ vcl/opengl/x11/gdiimpl \
+ vcl/opengl/x11/salvd \
+ $(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 \
+))
+
+## 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 ($(USE_XINERAMA),)
+$(eval $(call gb_Library_add_defs,vclplug_gen,\
+ -DUSE_XINERAMA_XORG \
+))
+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 # USE_XINERAMA
+
+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 000000000..9e67d5efc
--- /dev/null
+++ b/vcl/Library_vclplug_gtk3.mk
@@ -0,0 +1,124 @@
+# -*- 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))
+
+# 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 \
+))
+
+$(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,\
+ vcl \
+ 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/gtk3atkaction \
+ vcl/unx/gtk3/a11y/gtk3atkbridge \
+ vcl/unx/gtk3/a11y/gtk3atkcomponent \
+ vcl/unx/gtk3/a11y/gtk3atkeditabletext \
+ vcl/unx/gtk3/a11y/gtk3atkfactory \
+ vcl/unx/gtk3/a11y/gtk3atkhypertext \
+ vcl/unx/gtk3/a11y/gtk3atkimage \
+ vcl/unx/gtk3/a11y/gtk3atklistener \
+ vcl/unx/gtk3/a11y/gtk3atkregistry \
+ vcl/unx/gtk3/a11y/gtk3atkselection \
+ vcl/unx/gtk3/a11y/gtk3atktable \
+ vcl/unx/gtk3/a11y/gtk3atktextattributes \
+ vcl/unx/gtk3/a11y/gtk3atktext \
+ vcl/unx/gtk3/a11y/gtk3atkutil \
+ vcl/unx/gtk3/a11y/gtk3atkvalue \
+ vcl/unx/gtk3/a11y/gtk3atkwrapper \
+ vcl/unx/gtk3/fpicker/resourceprovider \
+ vcl/unx/gtk3/fpicker/SalGtkFilePicker \
+ vcl/unx/gtk3/fpicker/SalGtkFolderPicker \
+ vcl/unx/gtk3/fpicker/SalGtkPicker \
+ vcl/unx/gtk3/gtk3gtkdata \
+ vcl/unx/gtk3/gtk3gtkinst \
+ vcl/unx/gtk3/gtk3gtksys \
+ vcl/unx/gtk3/cairo_gtk3_cairo \
+ vcl/unx/gtk3/gtk3gtkprintwrapper \
+ vcl/unx/gtk3/gtk3salnativewidgets-gtk \
+ vcl/unx/gtk3/gtk3salprn-gtk \
+ vcl/unx/gtk3/gtk3gtkframe \
+ vcl/unx/gtk3/gtk3gtkobject \
+ vcl/unx/gtk3/gtk3gtksalmenu \
+ vcl/unx/gtk3/gtk3glomenu \
+ vcl/unx/gtk3/gtk3gloactiongroup \
+ vcl/unx/gtk3/gtk3hudawareness \
+))
+
+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 000000000..51dafda09
--- /dev/null
+++ b/vcl/Library_vclplug_gtk3_kde5.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_gtk3_kde5))
+
+# 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) \
+ $(KF5_CFLAGS) \
+ $$(GSTREAMER_1_0_CFLAGS) \
+))
+
+$(eval $(call gb_Library_add_defs,vclplug_gtk3_kde5,\
+ -DVCLPLUG_GTK_IMPLEMENTATION -DVCLPLUG_GTK3_KDE5_IMPLEMENTATION \
+))
+
+$(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 \
+ vcl \
+ 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 \
+))
+
+$(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_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_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_printwrapper \
+ vcl/unx/gtk3_kde5/gtk3_kde5_salnativewidgets-gtk \
+ vcl/unx/gtk3_kde5/gtk3_kde5_salprn-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 \
+ $(KF5_LIBS) \
+))
+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 000000000..d10cecd16
--- /dev/null
+++ b/vcl/Library_vclplug_kf5.mk
@@ -0,0 +1,92 @@
+# -*- 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_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_KF5_IMPLEMENTATION \
+))
+
+$(eval $(call gb_Library_use_sdk_api,vclplug_kf5))
+
+$(eval $(call gb_Library_use_libraries,vclplug_kf5,\
+ vclplug_qt5 \
+ vcl \
+ 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_cxxflags,vclplug_kf5,\
+ $(KF5_CFLAGS) \
+))
+$(eval $(call gb_Library_add_libs,vclplug_kf5,\
+ $(KF5_LIBS) \
+))
+
+$(eval $(call gb_Library_add_exception_objects,vclplug_kf5,\
+ vcl/unx/kf5/KF5FilePicker \
+ vcl/unx/kf5/KF5SalFrame \
+ vcl/unx/kf5/KF5SalInstance \
+))
+
+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_osx.mk b/vcl/Library_vclplug_osx.mk
new file mode 100644
index 000000000..f0d22298a
--- /dev/null
+++ b/vcl/Library_vclplug_osx.mk
@@ -0,0 +1,153 @@
+# -*- 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_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 \
+))
+
+$(eval $(call gb_Library_add_libs,vclplug_osx,\
+ -framework IOKit \
+ -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 \
+ vcl \
+))
+
+$(eval $(call gb_Library_use_externals,vclplug_osx,\
+ boost_headers \
+ harfbuzz \
+))
+
+ifeq ($(DISABLE_GUI),)
+$(eval $(call gb_Library_use_externals,vclplug_osx,\
+ epoxy \
+))
+endif
+
+$(eval $(call gb_Library_add_defs,vclplug_osx,\
+ -DMACOSX_BUNDLE_IDENTIFIER=\"$(MACOSX_BUNDLE_IDENTIFIER)\" \
+))
+
+$(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/salinst \
+ vcl/osx/salmenu \
+ vcl/osx/salnativewidgets \
+ vcl/osx/salobj \
+ vcl/osx/salprn \
+ vcl/osx/salsys \
+ vcl/osx/saltimer \
+ vcl/osx/service_entry \
+ vcl/quartz/ctfonts \
+ vcl/quartz/salbmp \
+ vcl/quartz/salgdi \
+ vcl/quartz/salgdicommon \
+ vcl/quartz/salgdiutils \
+ vcl/quartz/salvd \
+ vcl/quartz/utils \
+))
+
+$(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 000000000..25cbfe9f6
--- /dev/null
+++ b/vcl/Library_vclplug_qt5.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_qt5))
+
+$(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_QT5_IMPLEMENTATION \
+))
+
+$(eval $(call gb_Library_use_sdk_api,vclplug_qt5))
+
+$(eval $(call gb_Library_use_libraries,vclplug_qt5,\
+ vcl \
+ 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 \
+))
+
+$(eval $(call gb_Library_add_cxxflags,vclplug_qt5,\
+ $(QT5_CFLAGS) \
+))
+$(eval $(call gb_Library_add_libs,vclplug_qt5,\
+ $(QT5_LIBS) \
+))
+
+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/Qt5AccessibleEventListener \
+ vcl/qt5/Qt5AccessibleWidget \
+ vcl/qt5/Qt5Bitmap \
+ vcl/qt5/Qt5Clipboard \
+ vcl/qt5/Qt5Data \
+ vcl/qt5/Qt5DragAndDrop \
+ vcl/qt5/Qt5FilePicker \
+ vcl/qt5/Qt5Font \
+ vcl/qt5/Qt5FontFace \
+ vcl/qt5/Qt5Frame \
+ vcl/qt5/Qt5Graphics \
+ vcl/qt5/Qt5Graphics_Controls \
+ vcl/qt5/Qt5Graphics_GDI \
+ vcl/qt5/Qt5Graphics_Text \
+ vcl/qt5/Qt5Instance \
+ vcl/qt5/Qt5Instance_Print \
+ vcl/qt5/Qt5MainWindow \
+ vcl/qt5/Qt5Menu \
+ vcl/qt5/Qt5Object \
+ vcl/qt5/Qt5OpenGLContext \
+ vcl/qt5/Qt5Painter \
+ vcl/qt5/Qt5Printer \
+ vcl/qt5/Qt5SvpGraphics \
+ vcl/qt5/Qt5SvpSurface \
+ vcl/qt5/Qt5System \
+ vcl/qt5/Qt5Timer \
+ vcl/qt5/Qt5Tools \
+ vcl/qt5/Qt5Transferable \
+ vcl/qt5/Qt5VirtualDevice \
+ vcl/qt5/Qt5Widget \
+ vcl/qt5/Qt5XAccessible \
+))
+
+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_win.mk b/vcl/Library_vclplug_win.mk
new file mode 100644
index 000000000..2e169ded8
--- /dev/null
+++ b/vcl/Library_vclplug_win.mk
@@ -0,0 +1,109 @@
+# -*- 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_include,vclplug_win,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+ -I$(SRCDIR)/vcl/inc/win \
+))
+
+$(eval $(call gb_Library_add_defs,vclplug_win,\
+ -DVCLPLUG_WIN_IMPLEMENTATION \
+))
+
+$(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 \
+ i18nlangtag \
+ i18nutil \
+ sal \
+ salhelper \
+ tl \
+ utl \
+ vcl \
+))
+
+$(eval $(call gb_Library_use_externals,vclplug_win,\
+ boost_headers \
+ epoxy \
+ glm_headers \
+ harfbuzz \
+ $(if $(filter SKIA,$(BUILD_TYPE)),skia) \
+))
+
+$(eval $(call gb_Library_add_exception_objects,vclplug_win,\
+ vcl/opengl/win/gdiimpl \
+ vcl/opengl/win/winlayout \
+ vcl/win/app/saldata \
+ vcl/win/app/salinfo \
+ vcl/win/app/salinst \
+ vcl/win/app/salshl \
+ vcl/win/app/saltimer \
+ vcl/win/gdi/gdiimpl \
+ vcl/win/gdi/salbmp \
+ vcl/win/gdi/salgdi \
+ vcl/win/gdi/salgdi2 \
+ vcl/win/gdi/salfont \
+ 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/salframe \
+ vcl/win/window/keynames \
+ 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,\
+ d2d1 \
+ dwrite \
+ gdi32 \
+ gdiplus \
+ imm32 \
+ ole32 \
+ shell32 \
+ shlwapi \
+ 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
+$(eval $(call gb_Library_use_package,vclplug_win,postprocess_images))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Makefile b/vcl/Makefile
new file mode 100644
index 000000000..0997e6284
--- /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 000000000..938cd3201
--- /dev/null
+++ b/vcl/Module_vcl.mk
@@ -0,0 +1,255 @@
+# -*- 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))
+
+$(eval $(call gb_Module_add_targets,vcl,\
+ Library_vcl \
+ Package_opengl_shader \
+ Package_theme_definitions \
+ Package_tipoftheday \
+ UIConfig_vcl \
+ $(if $(filter WNT,$(OS)), \
+ Package_opengl_blacklist ) \
+ $(if $(filter SKIA,$(BUILD_TYPE)), \
+ Package_skia_blacklist ) \
+ $(if $(filter DESKTOP,$(BUILD_TYPE)), \
+ StaticLibrary_vclmain \
+ $(if $(ENABLE_MACOSX_SANDBOX),, \
+ $(if $(DISABLE_GUI),, \
+ Executable_ui-previewer)) \
+ $(if $(filter LINUX MACOSX SOLARIS WNT %BSD,$(OS)), \
+ $(if $(DISABLE_GUI),, \
+ Executable_vcldemo \
+ 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_svdemo \
+ Executable_fftester \
+ Executable_svptest \
+ Executable_svpclient) \
+))
+
+endif
+
+$(eval $(call gb_Module_add_l10n_targets,vcl,\
+ AllLangMoTarget_vcl \
+))
+
+ifeq ($(USING_X11),TRUE)
+$(eval $(call gb_Module_add_targets,vcl,\
+ Library_vclplug_gen \
+ Library_desktop_detector \
+ StaticLibrary_glxtest \
+ Package_fontunxppds \
+ Package_fontunxpsprint \
+))
+
+ifneq ($(ENABLE_GTK3),)
+$(eval $(call gb_Module_add_targets,vcl,\
+ Library_vclplug_gtk3 \
+))
+endif
+ifneq ($(ENABLE_KF5),)
+$(eval $(call gb_Module_add_targets,vcl,\
+ CustomTarget_kf5_moc \
+ Library_vclplug_kf5 \
+))
+endif
+ifneq ($(ENABLE_QT5),)
+$(eval $(call gb_Module_add_targets,vcl,\
+ CustomTarget_qt5_moc \
+ Library_vclplug_qt5 \
+))
+endif
+ifneq ($(ENABLE_GTK3_KDE5),)
+$(eval $(call gb_Module_add_targets,vcl,\
+ CustomTarget_gtk3_kde5_moc \
+ Library_vclplug_gtk3_kde5 \
+ Executable_lo_kde5filepicker \
+))
+endif
+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
+
+ifeq ($(OS),HAIKU)
+ifneq ($(ENABLE_QT5),)
+$(eval $(call gb_Module_add_targets,vcl,\
+ CustomTarget_qt5_moc \
+ Library_vclplug_qt5 \
+))
+endif
+ifneq ($(ENABLE_KF5),)
+$(eval $(call gb_Module_add_targets,vcl,\
+ CustomTarget_kf5_moc \
+ Library_vclplug_kf5 \
+))
+endif
+endif
+
+ifneq ($(ENABLE_FUZZERS),)
+$(eval $(call gb_Module_add_targets,vcl,\
+ CustomTarget_nativecore \
+ CustomTarget_nativecalc \
+ CustomTarget_nativedraw \
+ CustomTarget_nativewriter \
+ CustomTarget_nativemath \
+ StaticLibrary_fuzzerstubs \
+ 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_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 \
+))
+endif
+
+$(eval $(call gb_Module_add_check_targets,vcl,\
+ CppunitTest_vcl_lifecycle \
+ CppunitTest_vcl_bitmap_test \
+ CppunitTest_vcl_bitmapprocessor_test \
+ CppunitTest_vcl_graphic_test \
+ CppunitTest_vcl_fontcharmap \
+ CppunitTest_vcl_font \
+ CppunitTest_vcl_fontfeature \
+ CppunitTest_vcl_fontmetric \
+ CppunitTest_vcl_complextext \
+ CppunitTest_vcl_filters_test \
+ CppunitTest_vcl_mnemonic \
+ CppunitTest_vcl_outdev \
+ 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 \
+))
+
+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_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 000000000..0902bcb42
--- /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 000000000..1ab2a560b
--- /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_blacklist.mk b/vcl/Package_opengl_blacklist.mk
new file mode 100644
index 000000000..35dbfcb0b
--- /dev/null
+++ b/vcl/Package_opengl_blacklist.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_blacklist,$(SRCDIR)/vcl/opengl))
+
+$(eval $(call gb_Package_add_files,vcl_opengl_blacklist,$(LIBO_SHARE_FOLDER)/opengl,\
+ opengl_blacklist_windows.xml \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Package_opengl_shader.mk b/vcl/Package_opengl_shader.mk
new file mode 100644
index 000000000..15e3bee1d
--- /dev/null
+++ b/vcl/Package_opengl_shader.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_Package_Package,vcl_opengl_shader,$(SRCDIR)/vcl/opengl/shaders))
+
+$(eval $(call gb_Package_add_files,vcl_opengl_shader,$(LIBO_ETC_FOLDER)/opengl,\
+ areaHashCRC64TFragmentShader.glsl \
+ areaScaleFastFragmentShader.glsl \
+ areaScaleFragmentShader.glsl \
+ blendedTextureFragmentShader.glsl \
+ blendedTextureVertexShader.glsl \
+ dumbVertexShader.glsl \
+ diffTextureFragmentShader.glsl \
+ greyscaleFragmentShader.glsl \
+ invert50FragmentShader.glsl \
+ convolutionFragmentShader.glsl \
+ linearGradientFragmentShader.glsl \
+ combinedTextureFragmentShader.glsl \
+ combinedTextureVertexShader.glsl \
+ combinedFragmentShader.glsl \
+ combinedVertexShader.glsl \
+ lineFragmentShader.glsl \
+ lineVertexShader.glsl \
+ maskFragmentShader.glsl \
+ maskedTextureVertexShader.glsl \
+ maskedTextureFragmentShader.glsl \
+ radialGradientFragmentShader.glsl \
+ replaceColorFragmentShader.glsl \
+ solidFragmentShader.glsl \
+ textureFragmentShader.glsl \
+ textureVertexShader.glsl \
+ transformedTextureVertexShader.glsl \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Package_osxres.mk b/vcl/Package_osxres.mk
new file mode 100644
index 000000000..dfbc35a97
--- /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_blacklist.mk b/vcl/Package_skia_blacklist.mk
new file mode 100644
index 000000000..611766eb7
--- /dev/null
+++ b/vcl/Package_skia_blacklist.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_blacklist,$(SRCDIR)/vcl/skia))
+
+$(eval $(call gb_Package_add_files,vcl_skia_blacklist,$(LIBO_SHARE_FOLDER)/skia,\
+ skia_blacklist_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 000000000..395a90b19
--- /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 000000000..18a086758
--- /dev/null
+++ b/vcl/Package_tipoftheday.mk
@@ -0,0 +1,20 @@
+# -*- 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,\
+ tipoftheday.png \
+ tipoftheday_w.png \
+ tipoftheday_c.png \
+ tipoftheday_i.png \
+ tipoftheday_d.png \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/README b/vcl/README
new file mode 100644
index 000000000..e0944688f
--- /dev/null
+++ b/vcl/README
@@ -0,0 +1,242 @@
+Visual Class Library 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 (under construction)
+
+unx/
+ + X11 backend and its sub-platforms
+ gtk3/
+ + GTK3 support
+ kf5/
+ + KF5 support (based on qt5 VCL plugin mentioned above)
+ gtk3_kde5/
+ + GTK3 support with KDE5 file pickers (alternative to native kf5 one)
+ generic/
+ + raw X11 support
+
+ plugadapt/
+ + pluggable framework to select correct unx backend
+
+ 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-in platform backend
+ + it stores various bits of global state in the
+ 'SalData' (inc/saldatabasic.hxx) structure but:
+ + 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".
+
+== 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
+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.
diff --git a/vcl/README.GDIMetaFile b/vcl/README.GDIMetaFile
new file mode 100644
index 000000000..98be38d08
--- /dev/null
+++ b/vcl/README.GDIMetaFile
@@ -0,0 +1,190 @@
+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 MetaActions 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 b/vcl/README.lifecycle
new file mode 100644
index 000000000..a309b65ef
--- /dev/null
+++ b/vcl/README.lifecycle
@@ -0,0 +1,325 @@
+** 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 ? ----------
+
+ * Cleanup DogTags
+
+ * 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.
+
+ * VclBuilder 'makeFoo' methods
+ + these should return VclPtr<> types and have their
+ signatures adjusted en-masse.
+ + currently we use a VclPtr<> constructor with
+ SAL_NO_ACQUIRE inside the builder.
+
+---------- 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 grepping 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.scheduler b/vcl/README.scheduler
new file mode 100644
index 000000000..52c76dac5
--- /dev/null
+++ b/vcl/README.scheduler
@@ -0,0 +1,394 @@
+= 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.
+
+
+= 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 DoYield( bool bWait, bool bAllCurrent )
+{
+ bool bWasEvent = ProcessUserEvents( bAllCurrent );
+ if ( !bAllCurrent && bWasEvent )
+ return true;
+ 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 0ms. 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 0ms 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.
diff --git a/vcl/README.vars b/vcl/README.vars
new file mode 100644
index 000000000..9f5a10a26
--- /dev/null
+++ b/vcl/README.vars
@@ -0,0 +1,52 @@
+Environment variables in VCL:
+
+General
+-------
+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_NO_FONT_LOOKUP - disable font search and fallback and always use a hard-coded font name (for some unit tests)
+
+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
+
+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
+
+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_FORCEGL - force enable OpenGL
+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.
+SAL_DISABLE_WATCHDOG - don't start the thread that watches for broken GL/Vulkan/OpenCL drivers
+
+Skia
+----
+SAL_DISABLESKIA=1 - force disabled Skia
+SAL_ENABLESKIA=1 - enable Skia, unless blacklisted (and if the VCL backend supports Skia)
+SAL_FORCESKIA=1 - force using Skia, even if blacklisted
+SAL_SKIA=raster|vulkan - select Skia's drawing method, by default Vulkan is used
+SAL_DISABLE_SKIA_CACHE=1 - disable caching of complex images
+
+OpenGL,Skia
+-----------
+SAL_WITHOUT_WIDGET_CACHE - disable LRU caching of native widget textures
+SAL_DISABLE_GLYPH_CACHING - don't render glyphs through OpenGL textures or Skia surfaces
diff --git a/vcl/StaticLibrary_fuzzer_calc.mk b/vcl/StaticLibrary_fuzzer_calc.mk
new file mode 100644
index 000000000..5bb543f56
--- /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 000000000..6fa58b10b
--- /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 000000000..69c2cd5ab
--- /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 000000000..ba5d03889
--- /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 000000000..91765353f
--- /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_fuzzerstubs.mk b/vcl/StaticLibrary_fuzzerstubs.mk
new file mode 100644
index 000000000..7d8105906
--- /dev/null
+++ b/vcl/StaticLibrary_fuzzerstubs.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_StaticLibrary_StaticLibrary,fuzzerstubs))
+
+$(eval $(call gb_StaticLibrary_set_include,fuzzerstubs,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_StaticLibrary_use_api,fuzzerstubs,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_StaticLibrary_add_exception_objects,fuzzerstubs,\
+ vcl/workben/localestub/localestub \
+ vcl/workben/localestub/localedata_en_AU \
+ vcl/workben/localestub/localedata_en_BW \
+ vcl/workben/localestub/localedata_en_BZ \
+ vcl/workben/localestub/localedata_en_CA \
+ vcl/workben/localestub/localedata_en_GB \
+ vcl/workben/localestub/localedata_en_GH \
+ vcl/workben/localestub/localedata_en_GM \
+ vcl/workben/localestub/localedata_en_IE \
+ vcl/workben/localestub/localedata_en_IN \
+ vcl/workben/localestub/localedata_en_JM \
+ vcl/workben/localestub/localedata_en_LK \
+ vcl/workben/localestub/localedata_en_MW \
+ vcl/workben/localestub/localedata_en_MY \
+ vcl/workben/localestub/localedata_en_NA \
+ vcl/workben/localestub/localedata_en_NG \
+ vcl/workben/localestub/localedata_en_NZ \
+ vcl/workben/localestub/localedata_en_PH \
+ vcl/workben/localestub/localedata_en_TT \
+ vcl/workben/localestub/localedata_en_US \
+ vcl/workben/localestub/localedata_en_ZA \
+ vcl/workben/localestub/localedata_en_ZM \
+ vcl/workben/localestub/localedata_en_ZW \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/StaticLibrary_glxtest.mk b/vcl/StaticLibrary_glxtest.mk
new file mode 100644
index 000000000..1a285875c
--- /dev/null
+++ b/vcl/StaticLibrary_glxtest.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/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this 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,glxtest))
+
+$(eval $(call gb_StaticLibrary_set_include,glxtest,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_StaticLibrary_use_api,glxtest,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_StaticLibrary_add_libs,glxtest,\
+ -lm $(DLOPEN_LIBS) \
+ -lX11 \
+))
+
+$(eval $(call gb_StaticLibrary_add_exception_objects,glxtest,\
+ vcl/unx/glxtest \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/StaticLibrary_vclmain.mk b/vcl/StaticLibrary_vclmain.mk
new file mode 100644
index 000000000..cc283cc0d
--- /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 000000000..7941303b6
--- /dev/null
+++ b/vcl/UIConfig_vcl.mk
@@ -0,0 +1,34 @@
+# -*- 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/editmenu \
+ vcl/uiconfig/ui/errornocontentdialog \
+ vcl/uiconfig/ui/errornoprinterdialog \
+ vcl/uiconfig/ui/moreoptionsdialog \
+ 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 \
+))
+
+$(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 000000000..f7195b432
--- /dev/null
+++ b/vcl/WinResTarget_vcl.mk
@@ -0,0 +1,97 @@
+# -*- 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 \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/android/androidinst.cxx b/vcl/android/androidinst.cxx
new file mode 100644
index 000000000..94e5f4227
--- /dev/null
+++ b/vcl/android/androidinst.cxx
@@ -0,0 +1,250 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/log.h>
+#include <android/looper.h>
+#include <android/bitmap.h>
+
+#include <android/androidinst.hxx>
+#include <headless/svpdummies.hxx>
+#include <unx/gendata.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>
+
+#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__))
+
+// Horrible hack
+static int viewWidth = 1, viewHeight = 1;
+
+class AndroidSalData : public GenericUnixSalData
+{
+public:
+ explicit AndroidSalData( SalInstance *pInstance ) : GenericUnixSalData( SAL_DATA_ANDROID, pInstance ) {}
+ virtual void ErrorTrapPush() {}
+ virtual bool ErrorTrapPop( bool ) { return false; }
+};
+
+void AndroidSalInstance::GetWorkArea(tools::Rectangle& rRect)
+{
+ rRect = tools::Rectangle( Point( 0, 0 ),
+ Size( viewWidth, viewHeight ) );
+}
+
+AndroidSalInstance *AndroidSalInstance::getInstance()
+{
+ if (!ImplGetSVData())
+ return NULL;
+ AndroidSalData *pData = static_cast<AndroidSalData *>(ImplGetSVData()->mpSalData);
+ if (!pData)
+ return NULL;
+ return static_cast<AndroidSalInstance *>(pData->m_pInstance);
+}
+
+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(tools::Rectangle& 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 );
+}
+
+void SalAbort( const OUString& rErrorText, bool bDumpCore )
+{
+ OUString aError( rErrorText );
+ if( aError.isEmpty() )
+ aError = "Unknown application error";
+ LOGI("%s", OUStringToOString(rErrorText, osl_getThreadTextEncoding()).getStr() );
+
+ LOGI("SalAbort: '%s'",
+ OUStringToOString(aError, RTL_TEXTENCODING_ASCII_US).getStr());
+ if( bDumpCore )
+ abort();
+ else
+ _exit(1);
+}
+
+const OUString& SalGetDesktopEnvironment()
+{
+ static OUString aEnv( "android" );
+ return aEnv;
+}
+
+SalData::SalData() :
+ m_pInstance( 0 ),
+ m_pPIManager(0 )
+{
+}
+
+SalData::~SalData()
+{
+}
+
+// This is our main entry point:
+SalInstance *CreateSalInstance()
+{
+ LOGI("Android: CreateSalInstance!");
+ AndroidSalInstance* pInstance = new AndroidSalInstance( std::make_unique<SvpSalYieldMutex>() );
+ new AndroidSalData( pInstance );
+ pInstance->AcquireYieldMutex();
+ return pInstance;
+}
+
+void DestroySalInstance( SalInstance *pInst )
+{
+ pInst->ReleaseYieldMutexAll();
+ delete pInst;
+}
+
+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/VisualBackendTest.cxx b/vcl/backendtest/VisualBackendTest.cxx
new file mode 100644
index 000000000..08efc1d38
--- /dev/null
+++ b/vcl/backendtest/VisualBackendTest.cxx
@@ -0,0 +1,690 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/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 <tools/diagnose_ex.h>
+
+#include <chrono>
+#include <iostream>
+
+#include <test/outputdevice.hxx>
+
+using namespace css;
+
+static void drawBitmapCentered(tools::Rectangle const& rRect, const Bitmap& aBitmap,
+ vcl::RenderContext& rRenderContext)
+{
+ long nWidth = rRect.GetWidth();
+ 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)
+{
+ long nWidth = rRect.GetWidth();
+ 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(PushFlags::LINECOLOR | 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 = 9;
+ unsigned char mnTest;
+ bool mbAnimate;
+ ScopedVclPtr<VirtualDevice> mpVDev;
+
+public:
+ VisualBackendTestWindow()
+ : WorkWindow(nullptr, WB_APP | WB_STDWORK)
+ , 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)
+ {
+ 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++)
+ {
+ long x1 = x * (nWidth / nPartitionsX);
+ long y1 = y * (nHeight / nPartitionsY);
+ long x2 = (x+1) * (nWidth / nPartitionsX);
+ long y2 = (y+1) * (nHeight / nPartitionsY);
+
+ aRegions.emplace_back(x1 + 1, y1 + 1, x2 - 2, y2 - 2);
+ }
+ }
+ return aRegions;
+ }
+
+ static void testRectangles(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::OutputDeviceTestRect aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(false);
+ assertAndSetBackground(vcl::test::OutputDeviceTestCommon::checkRectangle(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestPixel aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(false);
+ assertAndSetBackground(vcl::test::OutputDeviceTestCommon::checkRectangle(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(false);
+ assertAndSetBackground(vcl::test::OutputDeviceTestCommon::checkRectangle(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(false);
+ assertAndSetBackground(vcl::test::OutputDeviceTestCommon::checkRectangle(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestPolyLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(false);
+ assertAndSetBackground(vcl::test::OutputDeviceTestCommon::checkRectangle(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestPolyPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(false);
+ assertAndSetBackground(vcl::test::OutputDeviceTestCommon::checkRectangle(aBitmap), 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(3, 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::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);
+ }
+ }
+
+ static void testDiamonds(vcl::RenderContext& rRenderContext, int nWidth, int nHeight)
+ {
+ tools::Rectangle aRectangle;
+ size_t index = 0;
+
+ std::vector<tools::Rectangle> aRegions = setupRegions(3, 1, 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);
+ }
+ }
+
+ 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 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();
+ assertAndSetBackground(vcl::test::OutputDeviceTestBitmap::checkTransformedBitmap(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawTransformedBitmap();
+ assertAndSetBackground(vcl::test::OutputDeviceTestBitmap::checkTransformedBitmap(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawBitmapExWithAlpha();
+ assertAndSetBackground(vcl::test::OutputDeviceTestBitmap::checkBitmapExWithAlpha(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawMask();
+ assertAndSetBackground(vcl::test::OutputDeviceTestBitmap::checkMask(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ BitmapEx aBitmap = aOutDevTest.setupDrawBlend();
+ 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);
+ }
+ }
+
+ 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::EnableB2dDraw | 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(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();
+
+ long nWidth = aSize.Width();
+ long nHeight = aSize.Height();
+
+ tools::Rectangle aRectangle;
+ size_t index = 0;
+
+ if (mnTest % gnNumberOfTests == 0)
+ {
+ testRectangles(rRenderContext, nWidth, nHeight);
+ }
+ else if (mnTest % gnNumberOfTests == 1)
+ {
+ testFilledRectangles(rRenderContext, nWidth, nHeight);
+ }
+ else if (mnTest % gnNumberOfTests == 2)
+ {
+ testDiamonds(rRenderContext, nWidth, nHeight);
+ }
+ else if (mnTest % gnNumberOfTests == 3)
+ {
+ testLines(rRenderContext, nWidth, nHeight);
+ }
+ else if (mnTest % gnNumberOfTests == 4)
+ {
+ testBitmaps(rRenderContext, nWidth, nHeight);
+ }
+ else if (mnTest % gnNumberOfTests == 5)
+ {
+ testInvert(rRenderContext, nWidth, nHeight);
+ }
+ else if (mnTest % gnNumberOfTests == 6)
+ {
+ testClip(rRenderContext, nWidth, nHeight);
+ }
+ else if (mnTest % gnNumberOfTests == 7)
+ {
+ std::vector<tools::Rectangle> aRegions = setupRegions(2, 2, nWidth, nHeight);
+
+ 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::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDashedLine();
+ assertAndSetBackground(vcl::test::OutputDeviceTestLine::checkDashedLine(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestGradient aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLinearGradient();
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestGradient aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRadialGradient();
+ 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
+ {
+ uno::Reference<lang::XComponent> xComponent(comphelper::getProcessComponentContext(), uno::UNO_QUERY_THROW);
+ xComponent->dispose();
+ 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 000000000..79cd3d379
--- /dev/null
+++ b/vcl/backendtest/outputdevice/bitmap.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/.
+ *
+ */
+
+#include <test/outputdevice.hxx>
+#include <vcl/bitmapex.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <bitmapwriteaccess.hxx>
+
+namespace vcl::test {
+
+Bitmap OutputDeviceTestBitmap::setupDrawTransformedBitmap()
+{
+ Size aBitmapSize(9, 9);
+ Bitmap aBitmap(aBitmapSize, 24);
+ {
+ 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));
+ }
+
+ 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::setupDrawBitmap()
+{
+ Size aBitmapSize(9, 9);
+ Bitmap aBitmap(aBitmapSize, 24);
+ {
+ 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));
+ }
+
+ 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()
+{
+ Size aBitmapSize(9, 9);
+ Bitmap aBitmap(aBitmapSize, 24);
+ {
+ 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);
+ {
+ AlphaScopedWriteAccess aWriteAccess(aAlpha);
+ aWriteAccess->Erase(COL_WHITE);
+ aWriteAccess->SetLineColor(Color(0x44, 0x44, 0x44));
+ 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()
+{
+ Size aBitmapSize(9, 9);
+ Bitmap aBitmap(aBitmapSize, 24);
+ {
+ 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()
+{
+ Size aBitmapSize(9, 9);
+ Bitmap aBitmap(aBitmapSize, 24);
+ {
+ 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);
+ {
+ AlphaScopedWriteAccess aWriteAccess(aAlpha);
+ aWriteAccess->Erase(COL_WHITE);
+ aWriteAccess->SetLineColor(Color(0x44, 0x44, 0x44));
+ 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());
+}
+
+TestResult OutputDeviceTestBitmap::checkTransformedBitmap(Bitmap& rBitmap)
+{
+ std::vector<Color> aExpected
+ {
+ constBackgroundColor, constBackgroundColor,
+ COL_YELLOW, constFillColor, COL_YELLOW, constFillColor, constFillColor
+ };
+ 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(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/clip.cxx b/vcl/backendtest/outputdevice/clip.cxx
new file mode 100644
index 000000000..86064b583
--- /dev/null
+++ b/vcl/backendtest/outputdevice/clip.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 <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.getX(), rectangle.getY()),
+ basegfx::B2DPoint(rectangle.getX(), rectangle.getY() + rectangle.getHeight()),
+ basegfx::B2DPoint(rectangle.getX() + rectangle.getWidth(),
+ rectangle.getY() + rectangle.getHeight()),
+ basegfx::B2DPoint(rectangle.getX() + rectangle.getWidth(), rectangle.getY()),
+ })));
+ 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 000000000..a5d032315
--- /dev/null
+++ b/vcl/backendtest/outputdevice/common.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/.
+ *
+ */
+
+#include <test/outputdevice.hxx>
+#include <bitmapwriteaccess.hxx>
+#include <salgdi.hxx>
+
+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);
+ }
+}
+
+TestResult checkRect(Bitmap& rBitmap, int aLayerNumber, Color aExpectedColor)
+{
+ BitmapScopedWriteAccess pAccess(rBitmap);
+ long nHeight = pAccess->Height();
+ long nWidth = pAccess->Width();
+
+ long firstX = 0 + aLayerNumber;
+ long firstY = 0 + aLayerNumber;
+
+ long lastX = nWidth - aLayerNumber - 1;
+ 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 (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 (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);
+ long nWidth = pAccess->Width();
+ long nHeight = pAccess->Height();
+
+ TestResult aResult = TestResult::Passed;
+ int nNumberOfQuirks = 0;
+ int nNumberOfErrors = 0;
+
+ // check horizontal line
+ {
+ long startX = 4;
+ long endX = nWidth - 2;
+
+ long y = 1;
+
+ checkValue(pAccess, startX, y, aExpectedColor, nNumberOfQuirks, nNumberOfErrors, true, nColorThresh);
+ checkValue(pAccess, endX, y, aExpectedColor, nNumberOfQuirks, nNumberOfErrors, true, nColorThresh);
+
+ for (long x = startX + 1; x <= endX - 1; x++)
+ {
+ checkValue(pAccess, x, y, aExpectedColor, nNumberOfQuirks, nNumberOfErrors, false, nColorThresh);
+ }
+ }
+
+ // check vertical line
+ {
+ long startY = 4;
+ long endY = nHeight - 2;
+
+ long x = 1;
+
+ checkValue(pAccess, x, startY, aExpectedColor, nNumberOfQuirks, nNumberOfErrors, true, nColorThresh);
+ checkValue(pAccess, x, endY, aExpectedColor, nNumberOfQuirks, nNumberOfErrors, true, nColorThresh);
+
+ for (long y = startY + 1; y <= endY - 1; y++)
+ {
+ checkValue(pAccess, x, y, aExpectedColor, nNumberOfQuirks, nNumberOfErrors, false, nColorThresh);
+ }
+ }
+
+ // check diagonal line
+ {
+ long startX = 1;
+ long endX = nWidth - 2;
+
+ long startY = 1;
+ long endY = nHeight - 2;
+
+ long x = startX;
+ 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);
+ long nHeight = pAccess->Height();
+ long nWidth = pAccess->Width();
+
+ long midX = nWidth / 2;
+ long midY = nHeight / 2;
+
+ long firstX = aLayerNumber;
+ long lastX = nWidth - aLayerNumber - 1;
+
+ long firstY = aLayerNumber;
+ long lastY = nHeight - aLayerNumber - 1;
+
+ 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 (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 (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(long nWidth, long nHeight, Color aColor, bool bEnableAA, bool bAlphaVirtualDevice)
+{
+ if (bAlphaVirtualDevice)
+ mpVirtualDevice = VclPtr<VirtualDevice>::Create(DeviceFormat::DEFAULT, DeviceFormat::DEFAULT);
+ else
+ mpVirtualDevice = VclPtr<VirtualDevice>::Create(DeviceFormat::DEFAULT);
+
+ maVDRectangle = tools::Rectangle(Point(), Size (nWidth, nHeight));
+ mpVirtualDevice->SetOutputSizePixel(maVDRectangle.GetSize());
+ if (bEnableAA)
+ mpVirtualDevice->SetAntialiasing(AntialiasingFlags::EnableB2dDraw | 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::checkRectangleAA(Bitmap& aBitmap)
+{
+ std::vector<Color> aExpected
+ {
+ constBackgroundColor, constBackgroundColor, constLineColor,
+ constBackgroundColor, constBackgroundColor, constLineColor, constBackgroundColor
+ };
+ return checkRectangles(aBitmap, aExpected);
+}
+
+TestResult OutputDeviceTestCommon::checkFilledRectangle(Bitmap& aBitmap, bool useLineColor)
+{
+ std::vector<Color> aExpected
+ {
+ constBackgroundColor, constBackgroundColor,
+ useLineColor ? constLineColor : constFillColor,
+ constFillColor, constFillColor, constFillColor, constFillColor
+ };
+ return checkRectangles(aBitmap, aExpected);
+}
+
+TestResult OutputDeviceTestCommon::checkFilled(Bitmap& rBitmap, tools::Rectangle aRectangle, Color aExpectedColor)
+{
+ BitmapScopedWriteAccess pAccess(rBitmap);
+
+ TestResult aResult = TestResult::Passed;
+ int nNumberOfQuirks = 0;
+ int nNumberOfErrors = 0;
+
+ for (long y = aRectangle.Top(); y < aRectangle.Top() + aRectangle.GetHeight(); y++)
+ {
+ for (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)
+{
+ long midPointX = rRect.Left() + (rRect.Right() - rRect.Left()) / 2.0;
+ 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 );
+}
+
+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);
+}
+
+} // 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 000000000..9880fedd1
--- /dev/null
+++ b/vcl/backendtest/outputdevice/gradient.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 <test/outputdevice.hxx>
+#include <vcl/gradient.hxx>
+
+namespace vcl::test {
+
+Bitmap OutputDeviceTestGradient::setupLinearGradient()
+{
+ initialSetup(12, 12, constBackgroundColor);
+
+ Gradient aGradient(GradientStyle::Linear, Color(0xFF, 0xFF, 0xFF), Color(0x00, 0x00, 0x00));
+ aGradient.SetAngle(900);
+ 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(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());
+}
+
+} // 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 000000000..5b5d73261
--- /dev/null
+++ b/vcl/backendtest/outputdevice/line.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 <test/outputdevice.hxx>
+
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <vcl/bitmapaccess.hxx>
+
+#include <list>
+
+namespace vcl::test {
+
+namespace
+{
+
+void drawLineOffset(OutputDevice& rDevice, tools::Rectangle const & rRect, int nOffset)
+{
+ Point aLeftTop (rRect.Left() + nOffset, rRect.Top() + nOffset);
+ Point aRightTop (rRect.Right() - nOffset, rRect.Top() + nOffset);
+ Point aLeftBottom (rRect.Left() + nOffset, rRect.Bottom() - nOffset);
+ Point aRightBottom (rRect.Right() - nOffset, rRect.Bottom() - nOffset);
+
+ 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::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::EnableB2dDraw);
+ 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 stroke({ 2.0, 1.0 });
+ mpVirtualDevice->DrawPolyLineDirect( basegfx::B2DHomMatrix(),
+ basegfx::B2DPolygon{
+ basegfx::B2DPoint(rectangle.getX(), rectangle.getY()),
+ basegfx::B2DPoint(rectangle.getX(), rectangle.getY() + rectangle.getHeight()),
+ basegfx::B2DPoint(rectangle.getX() + rectangle.getWidth(),
+ rectangle.getY() + rectangle.getHeight()),
+ basegfx::B2DPoint(rectangle.getX() + rectangle.getWidth(), rectangle.getY()),
+ basegfx::B2DPoint(rectangle.getX(), rectangle.getY())},
+ 1, 0, &stroke, basegfx::B2DLineJoin::NONE, css::drawing::LineCap_BUTT, basegfx::deg2rad(15.0), true );
+
+ 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;
+}
+
+} // 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 000000000..07d66ffb4
--- /dev/null
+++ b/vcl/backendtest/outputdevice/outputdevice.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/.
+ *
+ */
+
+#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::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());
+}
+
+TestResult OutputDeviceTestAnotherOutDev::checkDrawOutDev(Bitmap& rBitmap)
+{
+ std::vector<Color> aExpected
+ {
+ constBackgroundColor, constBackgroundColor,
+ constFillColor, constFillColor, constFillColor, constFillColor, constFillColor
+ };
+ return checkRectangles(rBitmap, aExpected);
+}
+
+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);
+}
+
+} // 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 000000000..71997868f
--- /dev/null
+++ b/vcl/backendtest/outputdevice/pixel.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 <test/outputdevice.hxx>
+
+namespace vcl::test {
+
+namespace
+{
+
+void drawPixelOffset(OutputDevice& rDevice, tools::Rectangle const & rRect, int nOffset)
+{
+ for (long x = 0 + nOffset; x < (rRect.GetWidth() - nOffset); ++x)
+ {
+ long y1 = nOffset;
+ long y2 = rRect.GetHeight() - nOffset - 1;
+
+ rDevice.DrawPixel(Point(x, y1));
+ rDevice.DrawPixel(Point(x, y2));
+ }
+
+ for (long y = 0 + nOffset; y < (rRect.GetHeight() - nOffset); ++y)
+ {
+ long x1 = nOffset;
+ long x2 = rRect.GetWidth() - nOffset - 1;
+
+ 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());
+}
+
+} // 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 000000000..8d207ade9
--- /dev/null
+++ b/vcl/backendtest/outputdevice/polygon.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/.
+ *
+ */
+
+#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);
+ aPolygon.SetPoint(Point(rRect.Left() + nOffset, rRect.Top() + nOffset), 0);
+ aPolygon.SetPoint(Point(rRect.Right() - nOffset + nFix, rRect.Top() + nOffset), 1);
+ aPolygon.SetPoint(Point(rRect.Right() - nOffset + nFix, rRect.Bottom() - nOffset + nFix), 2);
+ aPolygon.SetPoint(Point(rRect.Left() + nOffset, rRect.Bottom() - nOffset + nFix), 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::EnableB2dDraw);
+ 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());
+}
+
+} // 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 000000000..e1d1fd9e0
--- /dev/null
+++ b/vcl/backendtest/outputdevice/polyline.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/.
+ *
+ */
+
+#include <test/outputdevice.hxx>
+
+namespace vcl::test {
+
+namespace
+{
+
+void drawPolyLineOffset(OutputDevice& rDevice, tools::Rectangle const & rRect, int nOffset)
+{
+ tools::Polygon aPolygon(4);
+ aPolygon.SetPoint(Point(rRect.Left() + nOffset, rRect.Top() + nOffset), 0);
+ aPolygon.SetPoint(Point(rRect.Right() - nOffset, rRect.Top() + nOffset), 1);
+ aPolygon.SetPoint(Point(rRect.Right() - nOffset, rRect.Bottom() - nOffset), 2);
+ aPolygon.SetPoint(Point(rRect.Left() + nOffset, rRect.Bottom() - nOffset), 3);
+ aPolygon.Optimize(PolyOptimizeFlags::CLOSE);
+
+ rDevice.DrawPolyLine(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::EnableB2dDraw);
+ 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());
+}
+
+} // 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 000000000..65658d7ae
--- /dev/null
+++ b/vcl/backendtest/outputdevice/polyline_b2d.cxx
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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)
+{
+ basegfx::B2DPolygon aPolygon{
+ basegfx::B2DPoint(rRect.Left() + nOffset, rRect.Top() + nOffset),
+ basegfx::B2DPoint(rRect.Right() - nOffset, rRect.Top() + nOffset),
+ basegfx::B2DPoint(rRect.Right() - nOffset, rRect.Bottom() - nOffset),
+ basegfx::B2DPoint(rRect.Left() + nOffset, rRect.Bottom() - nOffset),
+ };
+ 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());
+}
+} // 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 000000000..a53acbaf7
--- /dev/null
+++ b/vcl/backendtest/outputdevice/polypolygon.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/.
+ *
+ */
+
+#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);
+ aPolygon.SetPoint(Point(rRect.Left() + nOffset, rRect.Top() + nOffset), 0);
+ aPolygon.SetPoint(Point(rRect.Right() - nOffset + nFix, rRect.Top() + nOffset), 1);
+ aPolygon.SetPoint(Point(rRect.Right() - nOffset + nFix, rRect.Bottom() - nOffset + nFix), 2);
+ aPolygon.SetPoint(Point(rRect.Left() + nOffset, rRect.Bottom() - nOffset + nFix), 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());
+}
+
+} // 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 000000000..737cfae19
--- /dev/null
+++ b/vcl/backendtest/outputdevice/polypolygon_b2d.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 <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.
+ basegfx::B2DPolygon aPolygon{
+ basegfx::B2DPoint(rRect.Left() + nOffset, rRect.Top() + nOffset),
+ basegfx::B2DPoint(rRect.Right() - nOffset + nFix, rRect.Top() + nOffset),
+ basegfx::B2DPoint(rRect.Right() - nOffset + nFix, rRect.Bottom() - nOffset + nFix),
+ basegfx::B2DPoint(rRect.Left() + nOffset, rRect.Bottom() - nOffset + 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());
+}
+} // 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 000000000..8e7c0ba86
--- /dev/null
+++ b/vcl/backendtest/outputdevice/rectangle.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 <test/outputdevice.hxx>
+
+namespace vcl::test {
+
+namespace
+{
+ void drawRectOffset(OutputDevice& rDevice, tools::Rectangle const & rRect, int nOffset)
+ {
+ rDevice.DrawRect(tools::Rectangle(rRect.Left() + nOffset, rRect.Top() + nOffset,
+ rRect.Right() - nOffset, rRect.Bottom() - nOffset));
+
+ }
+
+ 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());
+}
+
+} // end namespace vcl::test
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/commonfuzzer.mk b/vcl/commonfuzzer.mk
new file mode 100644
index 000000000..59ec0ff8a
--- /dev/null
+++ b/vcl/commonfuzzer.mk
@@ -0,0 +1,172 @@
+# -*- 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 \
+ openssl \
+ expat \
+ mythes \
+ hyphen \
+ hunspell \
+ zlib \
+
+fuzzer_core_libraries = \
+ basctl \
+ avmedia \
+ basegfx \
+ bib \
+ canvastools \
+ configmgr \
+ cppcanvas \
+ ctl \
+ dba \
+ dbtools \
+ deployment \
+ deploymentmisc \
+ drawinglayer \
+ editeng \
+ emfio \
+ filterconfig \
+ fsstorage \
+ fwe \
+ fwi \
+ fwk \
+ i18npool \
+ i18nutil \
+ lng \
+ localebe1 \
+ mcnttype \
+ msfilter \
+ package2 \
+ sax \
+ sb \
+ spell \
+ sfx \
+ sofficeapp \
+ sot \
+ svl \
+ svt \
+ svx \
+ svxcore \
+ emboleobj \
+ svgfilter \
+ svgio \
+ animcore \
+ tk \
+ tl \
+ ucb1 \
+ ucbhelper \
+ ucpexpand1 \
+ ucpfile1 \
+ unoxml \
+ utl \
+ uui \
+ vcl \
+ xmlscript \
+ xo \
+ xstor \
+ cui \
+ chartcontroller \
+ chartcore \
+ sm \
+ gie \
+ 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 \
+ expwrap \
+ gcc3_uno \
+ salhelper \
+ sal \
+
+fuzzer_calc_libraries = \
+ analysis \
+ date \
+ pricing \
+ scfilt \
+ scd \
+ vbaevents \
+ sc \
+ for \
+ forui \
+ guesslang \
+
+fuzzer_writer_libraries = \
+ msword \
+ sw \
+ swd \
+ writerfilter \
+ wpftwriter \
+ textfd \
+ guesslang \
+
+fuzzer_draw_libraries = \
+ sdfilt \
+ sd \
+ sdd \
+ icg \
+ guesslang \
+
+fuzzer_math_libraries = \
+ sm \
+ guesslang \
diff --git a/vcl/headless/CustomWidgetDraw.cxx b/vcl/headless/CustomWidgetDraw.cxx
new file mode 100644
index 000000000..7c167ff05
--- /dev/null
+++ b/vcl/headless/CustomWidgetDraw.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/.
+ *
+ */
+
+#include <cairo.h>
+#include <headless/CustomWidgetDraw.hxx>
+#include <sal/config.h>
+#include <rtl/bootstrap.hxx>
+#include <tools/svlibrary.h>
+#include <osl/module.hxx>
+#include <svdata.hxx>
+
+namespace vcl
+{
+WidgetThemeLibrary* CustomWidgetDraw::s_pWidgetImplementation = nullptr;
+
+CustomWidgetDraw::CustomWidgetDraw(SvpSalGraphics& rGraphics)
+ : m_rGraphics(rGraphics)
+{
+#ifndef DISABLE_DYNLOADING
+ static bool s_bMissingLibrary = false;
+ if (!s_pWidgetImplementation && !s_bMissingLibrary)
+ {
+ OUString aUrl("${LO_LIB_DIR}/" SVLIBRARY("vcl_widget_theme"));
+ rtl::Bootstrap::expandMacros(aUrl);
+ osl::Module aLibrary;
+ aLibrary.load(aUrl, SAL_LOADMODULE_GLOBAL);
+ auto fCreateWidgetThemeLibraryFunction
+ = reinterpret_cast<vcl::WidgetThemeLibrary*(SAL_CALL*)()>(
+ aLibrary.getFunctionSymbol("CreateWidgetThemeLibrary"));
+ aLibrary.release();
+
+ if (fCreateWidgetThemeLibraryFunction)
+ s_pWidgetImplementation = (*fCreateWidgetThemeLibraryFunction)();
+
+ // Init
+ if (s_pWidgetImplementation)
+ {
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maNWFData.mbNoFocusRects = true;
+ pSVData->maNWFData.mbNoFocusRectsForFlatButtons = true;
+ }
+ else
+ s_bMissingLibrary = true;
+ }
+#endif
+}
+
+CustomWidgetDraw::~CustomWidgetDraw() {}
+
+bool CustomWidgetDraw::isNativeControlSupported(ControlType eType, ControlPart ePart)
+{
+ return s_pWidgetImplementation
+ && s_pWidgetImplementation->isNativeControlSupported(eType, ePart);
+}
+
+bool CustomWidgetDraw::hitTestNativeControl(ControlType /*eType*/, ControlPart /*ePart*/,
+ const tools::Rectangle& /*rBoundingControlRegion*/,
+ const Point& /*aPos*/, bool& /*rIsInside*/)
+{
+ return false;
+}
+
+bool CustomWidgetDraw::drawNativeControl(ControlType eType, ControlPart ePart,
+ const tools::Rectangle& rControlRegion,
+ ControlState eState, const ImplControlValue& rValue,
+ const OUString& /*aCaptions*/,
+ const Color& /*rBackgroundColor*/)
+{
+ if (!s_pWidgetImplementation)
+ return false;
+
+ bool bOldAA = m_rGraphics.getAntiAliasB2DDraw();
+ m_rGraphics.setAntiAliasB2DDraw(true);
+
+ cairo_t* pCairoContext = m_rGraphics.getCairoContext(false);
+ m_rGraphics.clipRegion(pCairoContext);
+
+ cairo_translate(pCairoContext, rControlRegion.Left(), rControlRegion.Top());
+
+ long nWidth = rControlRegion.GetWidth();
+ long nHeight = rControlRegion.GetHeight();
+
+ bool bOK = false;
+
+ ControlDrawParameters aParameters{ pCairoContext, ePart, eState };
+
+ switch (eType)
+ {
+ case ControlType::Generic:
+ {
+ }
+ break;
+ case ControlType::Pushbutton:
+ {
+ const PushButtonValue* pPushButtonValue = static_cast<const PushButtonValue*>(&rValue);
+ if (pPushButtonValue)
+ aParameters.bIsAction = pPushButtonValue->mbIsAction;
+ bOK = s_pWidgetImplementation->drawPushButton(aParameters, nWidth, nHeight);
+ }
+ break;
+ case ControlType::Radiobutton:
+ {
+ aParameters.eButtonValue = rValue.getTristateVal();
+ bOK = s_pWidgetImplementation->drawRadiobutton(aParameters, nWidth, nHeight);
+ }
+ break;
+ case ControlType::Checkbox:
+ {
+ aParameters.eButtonValue = rValue.getTristateVal();
+ bOK = s_pWidgetImplementation->drawCheckbox(aParameters, nWidth, nHeight);
+ }
+ break;
+ case ControlType::Combobox:
+ {
+ bOK = s_pWidgetImplementation->drawCombobox(aParameters, nWidth, nHeight);
+ }
+ break;
+ case ControlType::Editbox:
+ {
+ bOK = s_pWidgetImplementation->drawEditbox(aParameters, nWidth, nHeight);
+ }
+ break;
+ case ControlType::EditboxNoBorder:
+ {
+ bOK = s_pWidgetImplementation->drawEditbox(aParameters, nWidth, nHeight);
+ }
+ break;
+ case ControlType::MultilineEditbox:
+ {
+ bOK = s_pWidgetImplementation->drawEditbox(aParameters, nWidth, nHeight);
+ }
+ break;
+ case ControlType::Listbox:
+ {
+ bOK = s_pWidgetImplementation->drawListbox(aParameters, nWidth, nHeight);
+ }
+ break;
+ case ControlType::Spinbox:
+ {
+ if (rValue.getType() == ControlType::SpinButtons)
+ {
+ const SpinbuttonValue* pSpinVal = static_cast<const SpinbuttonValue*>(&rValue);
+
+ ControlPart upBtnPart = pSpinVal->mnUpperPart;
+ ControlState upBtnState = pSpinVal->mnUpperState;
+
+ ControlPart downBtnPart = pSpinVal->mnLowerPart;
+ ControlState downBtnState = pSpinVal->mnLowerState;
+ {
+ ControlDrawParameters aParametersUp{ pCairoContext, upBtnPart, upBtnState };
+ cairo_save(pCairoContext);
+ cairo_translate(pCairoContext,
+ pSpinVal->maUpperRect.Left() - rControlRegion.Left(),
+ pSpinVal->maUpperRect.Top() - rControlRegion.Top());
+ bOK = s_pWidgetImplementation->drawSpinbox(aParametersUp,
+ pSpinVal->maUpperRect.GetWidth(),
+ pSpinVal->maUpperRect.GetHeight());
+ cairo_restore(pCairoContext);
+ }
+
+ if (bOK)
+ {
+ ControlDrawParameters aParametersDown{ pCairoContext, downBtnPart,
+ downBtnState };
+ cairo_save(pCairoContext);
+ cairo_translate(pCairoContext,
+ pSpinVal->maLowerRect.Left() - rControlRegion.Left(),
+ pSpinVal->maLowerRect.Top() - rControlRegion.Top());
+ bOK = s_pWidgetImplementation->drawSpinbox(aParametersDown,
+ pSpinVal->maLowerRect.GetWidth(),
+ pSpinVal->maLowerRect.GetHeight());
+ cairo_restore(pCairoContext);
+ }
+ }
+ else
+ {
+ bOK = s_pWidgetImplementation->drawSpinbox(aParameters, nWidth, nHeight);
+ }
+ }
+ break;
+ case ControlType::SpinButtons:
+ {
+ bOK = s_pWidgetImplementation->drawSpinButtons(aParameters, nWidth, nHeight);
+ }
+ break;
+ case ControlType::TabItem:
+ {
+ bOK = s_pWidgetImplementation->drawTabItem(aParameters, nWidth, nHeight);
+ }
+ break;
+ case ControlType::TabPane:
+ {
+ bOK = s_pWidgetImplementation->drawTabPane(aParameters, nWidth, nHeight);
+ }
+ break;
+ case ControlType::TabHeader:
+ {
+ bOK = s_pWidgetImplementation->drawTabHeader(aParameters, nWidth, nHeight);
+ }
+ break;
+ case ControlType::TabBody:
+ {
+ bOK = s_pWidgetImplementation->drawTabBody(aParameters, nWidth, nHeight);
+ }
+ break;
+ case ControlType::Scrollbar:
+ {
+ bOK = s_pWidgetImplementation->drawScrollbar(aParameters, nWidth, nHeight);
+ }
+ break;
+ case ControlType::Slider:
+ {
+ cairo_save(pCairoContext);
+ bOK = s_pWidgetImplementation->drawSlider(aParameters, nWidth, nHeight);
+ cairo_restore(pCairoContext);
+
+ if (bOK)
+ {
+ const SliderValue* pSliderValue = static_cast<const SliderValue*>(&rValue);
+
+ ControlDrawParameters aParametersButton{ pCairoContext, ControlPart::Button,
+ eState | pSliderValue->mnThumbState };
+ cairo_save(pCairoContext);
+ cairo_translate(pCairoContext,
+ pSliderValue->maThumbRect.Left() - rControlRegion.Left(),
+ pSliderValue->maThumbRect.Top() - rControlRegion.Top());
+ bOK = s_pWidgetImplementation->drawSlider(aParametersButton,
+ pSliderValue->maThumbRect.GetWidth(),
+ pSliderValue->maThumbRect.GetHeight());
+ cairo_restore(pCairoContext);
+ }
+ }
+ break;
+ case ControlType::Fixedline:
+ {
+ bOK = s_pWidgetImplementation->drawFixedline(aParameters, nWidth, nHeight);
+ }
+ break;
+ case ControlType::Toolbar:
+ {
+ bOK = s_pWidgetImplementation->drawToolbar(aParameters, nWidth, nHeight);
+ }
+ break;
+ case ControlType::Menubar:
+ break;
+ case ControlType::MenuPopup:
+ break;
+ case ControlType::Progress:
+ {
+ aParameters.nValue = rValue.getNumericVal();
+ bOK = s_pWidgetImplementation->drawProgress(aParameters, nWidth, nHeight);
+ }
+ break;
+ case ControlType::IntroProgress:
+ break;
+ case ControlType::Tooltip:
+ break;
+ case ControlType::WindowBackground:
+ {
+ bOK = s_pWidgetImplementation->drawWindowsBackground(aParameters, nWidth, nHeight);
+ }
+ break;
+ case ControlType::Frame:
+ {
+ bOK = s_pWidgetImplementation->drawFrame(aParameters, nWidth, nHeight);
+ }
+ break;
+ case ControlType::ListNode:
+ {
+ aParameters.eButtonValue = rValue.getTristateVal();
+ bOK = s_pWidgetImplementation->drawListNode(aParameters, nWidth, nHeight);
+ }
+ break;
+ case ControlType::ListNet:
+ {
+ bOK = s_pWidgetImplementation->drawListNet(aParameters, nWidth, nHeight);
+ }
+ break;
+ case ControlType::ListHeader:
+ {
+ bOK = s_pWidgetImplementation->drawListHeader(aParameters, nWidth, nHeight);
+ }
+ break;
+ }
+
+ basegfx::B2DRange aExtents(rControlRegion.Left(), rControlRegion.Top(), rControlRegion.Right(),
+ rControlRegion.Bottom());
+
+ m_rGraphics.releaseCairoContext(pCairoContext, true, aExtents);
+
+ m_rGraphics.setAntiAliasB2DDraw(bOldAA);
+
+ return bOK;
+}
+
+bool CustomWidgetDraw::getNativeControlRegion(
+ ControlType eType, ControlPart ePart, const tools::Rectangle& rBoundingControlRegion,
+ ControlState eState, const ImplControlValue& /*aValue*/, const OUString& /*aCaption*/,
+ tools::Rectangle& rNativeBoundingRegion, tools::Rectangle& rNativeContentRegion)
+{
+ // Translate to POD rectangle and back.
+ const rectangle_t aRegion
+ = { rBoundingControlRegion.getX(), rBoundingControlRegion.getY(),
+ rBoundingControlRegion.GetWidth(), rBoundingControlRegion.GetHeight() };
+ if (s_pWidgetImplementation)
+ {
+ rectangle_t aNativeBoundingRegion;
+ rectangle_t aNativeContentRegion;
+ s_pWidgetImplementation->getRegion(eType, ePart, eState, aRegion, aNativeBoundingRegion,
+ aNativeContentRegion);
+
+ rNativeBoundingRegion
+ = tools::Rectangle(aNativeBoundingRegion.x, aNativeBoundingRegion.y,
+ aNativeBoundingRegion.width, aNativeBoundingRegion.height);
+ rNativeContentRegion
+ = tools::Rectangle(aNativeBoundingRegion.x, aNativeBoundingRegion.y,
+ aNativeBoundingRegion.width, aNativeBoundingRegion.height);
+ }
+
+ return false;
+}
+
+bool CustomWidgetDraw::updateSettings(AllSettings& rSettings)
+{
+ if (!s_pWidgetImplementation)
+ return false;
+
+ WidgetDrawStyle aStyle;
+ aStyle.nSize = sizeof(WidgetDrawStyle);
+
+ if (s_pWidgetImplementation->updateSettings(aStyle))
+ {
+ StyleSettings aStyleSet = rSettings.GetStyleSettings();
+
+ aStyleSet.SetFaceColor(aStyle.maFaceColor);
+ aStyleSet.SetCheckedColor(aStyle.maCheckedColor);
+ aStyleSet.SetLightColor(aStyle.maLightColor);
+ aStyleSet.SetLightBorderColor(aStyle.maLightBorderColor);
+ aStyleSet.SetShadowColor(aStyle.maShadowColor);
+ aStyleSet.SetDarkShadowColor(aStyle.maDarkShadowColor);
+ aStyleSet.SetDefaultButtonTextColor(aStyle.maDefaultButtonTextColor);
+ aStyleSet.SetButtonTextColor(aStyle.maButtonTextColor);
+ aStyleSet.SetDefaultActionButtonTextColor(aStyle.maDefaultActionButtonTextColor);
+ aStyleSet.SetActionButtonTextColor(aStyle.maActionButtonTextColor);
+ aStyleSet.SetFlatButtonTextColor(aStyle.maFlatButtonTextColor);
+ aStyleSet.SetDefaultButtonRolloverTextColor(aStyle.maDefaultButtonRolloverTextColor);
+ aStyleSet.SetButtonRolloverTextColor(aStyle.maButtonRolloverTextColor);
+ aStyleSet.SetDefaultActionButtonRolloverTextColor(
+ aStyle.maDefaultActionButtonRolloverTextColor);
+ aStyleSet.SetActionButtonRolloverTextColor(aStyle.maActionButtonRolloverTextColor);
+ aStyleSet.SetFlatButtonRolloverTextColor(aStyle.maFlatButtonRolloverTextColor);
+ aStyleSet.SetDefaultButtonPressedRolloverTextColor(
+ aStyle.maDefaultButtonPressedRolloverTextColor);
+ aStyleSet.SetButtonPressedRolloverTextColor(aStyle.maButtonPressedRolloverTextColor);
+ aStyleSet.SetDefaultActionButtonPressedRolloverTextColor(
+ aStyle.maDefaultActionButtonPressedRolloverTextColor);
+ aStyleSet.SetActionButtonPressedRolloverTextColor(
+ aStyle.maActionButtonPressedRolloverTextColor);
+ aStyleSet.SetFlatButtonPressedRolloverTextColor(
+ aStyle.maFlatButtonPressedRolloverTextColor);
+ aStyleSet.SetRadioCheckTextColor(aStyle.maRadioCheckTextColor);
+ aStyleSet.SetGroupTextColor(aStyle.maGroupTextColor);
+ aStyleSet.SetLabelTextColor(aStyle.maLabelTextColor);
+ aStyleSet.SetWindowColor(aStyle.maWindowColor);
+ aStyleSet.SetWindowTextColor(aStyle.maWindowTextColor);
+ aStyleSet.SetDialogColor(aStyle.maDialogColor);
+ aStyleSet.SetDialogTextColor(aStyle.maDialogTextColor);
+ aStyleSet.SetWorkspaceColor(aStyle.maWorkspaceColor);
+ aStyleSet.SetMonoColor(aStyle.maMonoColor);
+ aStyleSet.SetFieldColor(Color(aStyle.maFieldColor));
+ aStyleSet.SetFieldTextColor(aStyle.maFieldTextColor);
+ aStyleSet.SetFieldRolloverTextColor(aStyle.maFieldRolloverTextColor);
+ aStyleSet.SetActiveColor(aStyle.maActiveColor);
+ aStyleSet.SetActiveTextColor(aStyle.maActiveTextColor);
+ aStyleSet.SetActiveBorderColor(aStyle.maActiveBorderColor);
+ aStyleSet.SetDeactiveColor(aStyle.maDeactiveColor);
+ aStyleSet.SetDeactiveTextColor(aStyle.maDeactiveTextColor);
+ aStyleSet.SetDeactiveBorderColor(aStyle.maDeactiveBorderColor);
+ aStyleSet.SetMenuColor(aStyle.maMenuColor);
+ aStyleSet.SetMenuBarColor(aStyle.maMenuBarColor);
+ aStyleSet.SetMenuBarRolloverColor(aStyle.maMenuBarRolloverColor);
+ aStyleSet.SetMenuBorderColor(aStyle.maMenuBorderColor);
+ aStyleSet.SetMenuTextColor(aStyle.maMenuTextColor);
+ aStyleSet.SetMenuBarTextColor(aStyle.maMenuBarTextColor);
+ aStyleSet.SetMenuBarRolloverTextColor(aStyle.maMenuBarRolloverTextColor);
+ aStyleSet.SetMenuBarHighlightTextColor(aStyle.maMenuBarHighlightTextColor);
+ aStyleSet.SetMenuHighlightColor(aStyle.maMenuHighlightColor);
+ aStyleSet.SetMenuHighlightTextColor(aStyle.maMenuHighlightTextColor);
+ aStyleSet.SetHighlightColor(aStyle.maHighlightColor);
+ aStyleSet.SetHighlightTextColor(aStyle.maHighlightTextColor);
+ aStyleSet.SetActiveTabColor(aStyle.maActiveTabColor);
+ aStyleSet.SetInactiveTabColor(aStyle.maInactiveTabColor);
+ aStyleSet.SetTabTextColor(aStyle.maTabTextColor);
+ aStyleSet.SetTabRolloverTextColor(aStyle.maTabRolloverTextColor);
+ aStyleSet.SetTabHighlightTextColor(aStyle.maTabHighlightTextColor);
+ aStyleSet.SetDisableColor(aStyle.maDisableColor);
+ aStyleSet.SetHelpColor(aStyle.maHelpColor);
+ aStyleSet.SetHelpTextColor(aStyle.maHelpTextColor);
+ aStyleSet.SetLinkColor(aStyle.maLinkColor);
+ aStyleSet.SetVisitedLinkColor(aStyle.maVisitedLinkColor);
+ aStyleSet.SetToolTextColor(aStyle.maToolTextColor);
+ aStyleSet.SetFontColor(aStyle.maFontColor);
+
+ rSettings.SetStyleSettings(aStyleSet);
+
+ return true;
+ }
+
+ return false;
+}
+
+} // end vcl namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/headless/headlessinst.cxx b/vcl/headless/headlessinst.cxx
new file mode 100644
index 000000000..e1694c429
--- /dev/null
+++ b/vcl/headless/headlessinst.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/.
+ */
+#include <headless/svpinst.hxx>
+#include <headless/svpdummies.hxx>
+#include <unx/gendata.hxx>
+
+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();
+}
+
+class HeadlessSalData : public GenericUnixSalData
+{
+public:
+ explicit HeadlessSalData( SalInstance *pInstance ) : GenericUnixSalData( SAL_DATA_HEADLESS, pInstance ) {}
+ virtual void ErrorTrapPush() override {}
+ virtual bool ErrorTrapPop( bool ) override { return false; }
+};
+
+void SalAbort( const OUString& rErrorText, bool bDumpCore )
+{
+ OUString aError( rErrorText );
+ if( aError.isEmpty() )
+ aError = "Unknown application error";
+
+ SAL_WARN("vcl.headless", rErrorText);
+ SAL_INFO("vcl.headless", "SalAbort: '" << aError << "'.");
+
+ if( bDumpCore )
+ abort();
+ else
+ _exit(1);
+}
+
+const OUString& SalGetDesktopEnvironment()
+{
+ static OUString aEnv( "headless" );
+ return aEnv;
+}
+
+SalData::SalData() :
+ m_pInstance( nullptr ),
+ m_pPIManager( nullptr )
+{
+}
+
+SalData::~SalData()
+{
+}
+
+// This is our main entry point:
+SalInstance *CreateSalInstance()
+{
+ HeadlessSalInstance* pInstance = new HeadlessSalInstance(std::make_unique<SvpSalYieldMutex>());
+ new HeadlessSalData( pInstance );
+ pInstance->AcquireYieldMutex();
+ return pInstance;
+}
+
+void DestroySalInstance( SalInstance *pInst )
+{
+ pInst->ReleaseYieldMutexAll();
+ delete pInst;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/headless/svpbmp.cxx b/vcl/headless/svpbmp.cxx
new file mode 100644
index 000000000..4d881f025
--- /dev/null
+++ b/vcl/headless/svpbmp.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 <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()
+: SalBitmap(),
+ basegfx::SystemDependentDataHolder(), // MM02
+ mpDIB()
+{
+}
+
+SvpSalBitmap::~SvpSalBitmap()
+{
+ Destroy();
+}
+
+static std::unique_ptr<BitmapBuffer> ImplCreateDIB(
+ const Size& rSize,
+ sal_uInt16 nBitCount,
+ const BitmapPalette& rPal)
+{
+ assert(
+ (nBitCount == 0
+ || nBitCount == 1
+ || nBitCount == 4
+ || nBitCount == 8
+ || nBitCount == 24
+ || nBitCount == 32)
+ && "Unsupported BitCount!");
+
+ if (!rSize.Width() || !rSize.Height())
+ return nullptr;
+
+ std::unique_ptr<BitmapBuffer> pDIB;
+
+ try
+ {
+ pDIB.reset(new BitmapBuffer);
+ }
+ catch (const std::bad_alloc&)
+ {
+ return nullptr;
+ }
+
+ const sal_uInt16 nColors = ( nBitCount <= 8 ) ? ( 1 << nBitCount ) : 0;
+
+ switch (nBitCount)
+ {
+ case 1:
+ pDIB->mnFormat = ScanlineFormat::N1BitLsbPal;
+ break;
+ case 4:
+ pDIB->mnFormat = ScanlineFormat::N4BitMsnPal;
+ break;
+ case 8:
+ pDIB->mnFormat = ScanlineFormat::N8BitPal;
+ break;
+ case 24:
+ pDIB->mnFormat = SVP_24BIT_FORMAT;
+ break;
+ default:
+ nBitCount = 32;
+ [[fallthrough]];
+ case 32:
+ pDIB->mnFormat = SVP_CAIRO_FORMAT;
+ break;
+ }
+
+ pDIB->mnFormat |= ScanlineFormat::TopDown;
+ pDIB->mnWidth = rSize.Width();
+ pDIB->mnHeight = rSize.Height();
+ long nScanlineBase;
+ bool bFail = o3tl::checked_multiply<long>(pDIB->mnWidth, nBitCount, nScanlineBase);
+ if (bFail)
+ {
+ SAL_WARN("vcl.gdi", "checked multiply failed");
+ return nullptr;
+ }
+ pDIB->mnScanlineSize = AlignedWidth4Bytes(nScanlineBase);
+ if (pDIB->mnScanlineSize < nScanlineBase/8)
+ {
+ SAL_WARN("vcl.gdi", "scanline calculation wraparound");
+ return nullptr;
+ }
+ pDIB->mnBitCount = nBitCount;
+
+ 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 nullptr;
+ }
+
+ 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
+ {
+ std::memset(pDIB->mpBits, 0, size);
+ }
+ }
+ catch (const std::bad_alloc&)
+ {
+ pDIB.reset();
+ }
+
+ return pDIB;
+}
+
+void SvpSalBitmap::Create(std::unique_ptr<BitmapBuffer> pBuf)
+{
+ Destroy();
+ mpDIB = std::move(pBuf);
+}
+
+bool SvpSalBitmap::Create(const Size& rSize, sal_uInt16 nBitCount, const BitmapPalette& rPal)
+{
+ Destroy();
+ mpDIB = ImplCreateDIB( rSize, nBitCount, rPal );
+ return mpDIB != nullptr;
+}
+
+bool SvpSalBitmap::Create(const SalBitmap& rBmp)
+{
+ Destroy();
+
+ const SvpSalBitmap& rSalBmp = static_cast<const SvpSalBitmap&>(rBmp);
+
+ if (rSalBmp.mpDIB)
+ {
+ // TODO: reference counting...
+ mpDIB.reset(new BitmapBuffer( *rSalBmp.mpDIB ));
+
+ const size_t size = mpDIB->mnScanlineSize * mpDIB->mnHeight;
+ if (size > SAL_MAX_INT32/2)
+ {
+ mpDIB.reset();
+ return false;
+ }
+
+ // TODO: get rid of this when BitmapBuffer gets copy constructor
+ try
+ {
+ mpDIB->mpBits = new sal_uInt8[size];
+ std::memcpy(mpDIB->mpBits, rSalBmp.mpDIB->mpBits, size);
+ }
+ catch (const std::bad_alloc&)
+ {
+ mpDIB.reset();
+ }
+ }
+
+ return !rSalBmp.mpDIB || (mpDIB != nullptr);
+}
+
+bool SvpSalBitmap::Create( const SalBitmap& /*rSalBmp*/,
+ SalGraphics* /*pGraphics*/ )
+{
+ return false;
+}
+
+bool SvpSalBitmap::Create( const SalBitmap& /*rSalBmp*/,
+ sal_uInt16 /*nNewBitCount*/ )
+{
+ return false;
+}
+
+bool SvpSalBitmap::Create( const css::uno::Reference< css::rendering::XBitmapCanvas >& /*xBitmapCanvas*/, Size& /*rSize*/, bool /*bMask*/ )
+{
+ return false;
+}
+
+void SvpSalBitmap::Destroy()
+{
+ if (mpDIB)
+ {
+ delete[] mpDIB->mpBits;
+ mpDIB.reset();
+ }
+}
+
+Size SvpSalBitmap::GetSize() const
+{
+ Size aSize;
+
+ if (mpDIB)
+ {
+ aSize.setWidth( mpDIB->mnWidth );
+ aSize.setHeight( mpDIB->mnHeight );
+ }
+
+ return aSize;
+}
+
+sal_uInt16 SvpSalBitmap::GetBitCount() const
+{
+ sal_uInt16 nBitCount;
+
+ if (mpDIB)
+ nBitCount = mpDIB->mnBitCount;
+ else
+ nBitCount = 0;
+
+ return nBitCount;
+}
+
+BitmapBuffer* SvpSalBitmap::AcquireBuffer(BitmapAccessMode)
+{
+ return mpDIB.get();
+}
+
+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;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/headless/svpcairotextrender.cxx b/vcl/headless/svpcairotextrender.cxx
new file mode 100644
index 000000000..de62b9e50
--- /dev/null
+++ b/vcl/headless/svpcairotextrender.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/.
+ */
+
+#include <headless/svpcairotextrender.hxx>
+#include <headless/svpgdi.hxx>
+#include <cairo.h>
+
+SvpCairoTextRender::SvpCairoTextRender(SvpSalGraphics& rParent)
+ : mrParent(rParent)
+{
+}
+
+cairo_t* SvpCairoTextRender::getCairoContext()
+{
+ return mrParent.getCairoContext(false);
+}
+
+void SvpCairoTextRender::getSurfaceOffset(double& nDX, double& nDY)
+{
+ nDX = 0;
+ nDY = 0;
+}
+
+void SvpCairoTextRender::clipRegion(cairo_t* cr)
+{
+ mrParent.clipRegion(cr);
+}
+
+void SvpCairoTextRender::releaseCairoContext(cairo_t* cr)
+{
+ mrParent.releaseCairoContext(cr, false, basegfx::B2DRange());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/headless/svpdata.cxx b/vcl/headless/svpdata.cxx
new file mode 100644
index 000000000..4ac909c67
--- /dev/null
+++ b/vcl/headless/svpdata.cxx
@@ -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/.
+ */
+
+#include <unx/gendata.hxx>
+#include <headless/svpinst.hxx>
+
+namespace {
+
+class SvpSalData : public GenericUnixSalData
+{
+public:
+ explicit SvpSalData( SalInstance *pInstance ) : GenericUnixSalData( SAL_DATA_SVP, pInstance ) {}
+ virtual void ErrorTrapPush() override {}
+ virtual bool ErrorTrapPop( bool /*bIgnoreError*/ = true ) override { return false; }
+};
+
+}
+
+// plugin factory function
+SalInstance* svp_create_SalInstance()
+{
+ SvpSalInstance* pInstance = new SvpSalInstance( std::make_unique<SvpSalYieldMutex>() );
+ new SvpSalData( pInstance );
+ return pInstance;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/headless/svpdummies.cxx b/vcl/headless/svpdummies.cxx
new file mode 100644
index 000000000..548868c05
--- /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( long, long, long, long ) {}
+void SvpSalObject::EndSetClipRegion() {}
+void SvpSalObject::SetPosSize( long, long, long, long ) {}
+void SvpSalObject::Show( bool ) {}
+const SystemEnvData* SvpSalObject::GetSystemData() const { return &m_aSystemChildData; }
+
+// SalSystem
+SvpSalSystem::~SvpSalSystem() {}
+
+unsigned int SvpSalSystem::GetDisplayScreenCount()
+{
+ return 1;
+}
+
+tools::Rectangle SvpSalSystem::GetDisplayScreenPosSizePixel( unsigned int nScreen )
+{
+ tools::Rectangle aRect;
+ if( nScreen == 0 )
+ aRect = tools::Rectangle( Point(0,0), Size(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 000000000..0f6da8d28
--- /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 <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 )
+{
+ // SAL_DEBUG("SvpSalFrame::SvpSalFrame: " << this);
+#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::list<SvpSalFrame*> Children = m_aChildren;
+ for( auto& rChild : Children )
+ rChild->SetParent( m_pParent );
+ if( m_pParent )
+ m_pParent->m_aChildren.remove( this );
+
+ if( s_pFocusFrame == this )
+ {
+ // SAL_DEBUG("SvpSalFrame::~SvpSalFrame: losing focus: " << 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( s_pFocusFrame == this )
+ return;
+
+ if( (m_nStyle & (SalFrameStyleFlags::OWNERDRAWDECORATION | SalFrameStyleFlags::FLOAT)) == SalFrameStyleFlags::NONE )
+ {
+ if( s_pFocusFrame )
+ s_pFocusFrame->LoseFocus();
+ // SAL_DEBUG("SvpSalFrame::GetFocus(): " << this);
+ s_pFocusFrame = this;
+ m_pInstance->PostEvent( this, nullptr, SalEvent::GetFocus );
+ }
+}
+
+void SvpSalFrame::LoseFocus()
+{
+ if( s_pFocusFrame == this )
+ {
+ // SAL_DEBUG("SvpSalFrame::LoseFocus: " << this);
+ m_pInstance->PostEvent( this, nullptr, SalEvent::LoseFocus );
+ s_pFocusFrame = nullptr;
+ }
+}
+
+SalGraphics* SvpSalFrame::AcquireGraphics()
+{
+ SvpSalGraphics* pGraphics = new SvpSalGraphics();
+#ifndef IOS
+ pGraphics->setSurface(m_pSurface, basegfx::B2IVector(maGeometry.nWidth, maGeometry.nHeight));
+#endif
+ m_aGraphics.push_back( pGraphics );
+ return pGraphics;
+}
+
+void SvpSalFrame::ReleaseGraphics( SalGraphics* pGraphics )
+{
+ SvpSalGraphics* pSvpGraphics = dynamic_cast<SvpSalGraphics*>(pGraphics);
+ m_aGraphics.erase(std::remove(m_aGraphics.begin(), m_aGraphics.end(), pSvpGraphics), m_aGraphics.end());
+ 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.nWidth, maGeometry.nHeight);
+ aPEvt.mbImmediateUpdate = false;
+ CallCallback( SalEvent::Paint, &aPEvt );
+ }
+}
+
+void SvpSalFrame::SetTitle( const OUString& )
+{
+}
+
+void SvpSalFrame::SetIcon( sal_uInt16 )
+{
+}
+
+void SvpSalFrame::SetMenu( SalMenu* )
+{
+}
+
+void SvpSalFrame::DrawMenuBar()
+{
+}
+
+void SvpSalFrame::SetExtendedFrameStyle( SalExtStyle )
+{
+}
+
+void SvpSalFrame::Show( bool bVisible, bool bNoActivate )
+{
+ if( bVisible && ! m_bVisible )
+ {
+ // SAL_DEBUG("SvpSalFrame::Show: showing: " << this);
+ m_bVisible = true;
+ m_pInstance->PostEvent( this, nullptr, SalEvent::Resize );
+ if( ! bNoActivate )
+ GetFocus();
+ }
+ else if( ! bVisible && m_bVisible )
+ {
+ // SAL_DEBUG("SvpSalFrame::Show: hiding: " << this);
+ m_bVisible = false;
+ m_pInstance->PostEvent( this, nullptr, SalEvent::Resize );
+ LoseFocus();
+ }
+ else
+ {
+ // SAL_DEBUG("SvpSalFrame::Show: nothing: " << this);
+ }
+}
+
+void SvpSalFrame::SetMinClientSize( long nWidth, long nHeight )
+{
+ m_nMinWidth = nWidth;
+ m_nMinHeight = nHeight;
+}
+
+void SvpSalFrame::SetMaxClientSize( long nWidth, long nHeight )
+{
+ m_nMaxWidth = nWidth;
+ m_nMaxHeight = nHeight;
+}
+
+void SvpSalFrame::SetPosSize( long nX, long nY, long nWidth, long nHeight, sal_uInt16 nFlags )
+{
+ if( (nFlags & SAL_FRAME_POSSIZE_X) != 0 )
+ maGeometry.nX = nX;
+ if( (nFlags & SAL_FRAME_POSSIZE_Y) != 0 )
+ maGeometry.nY = nY;
+ if( (nFlags & SAL_FRAME_POSSIZE_WIDTH) != 0 )
+ {
+ maGeometry.nWidth = nWidth;
+ if( m_nMaxWidth > 0 && maGeometry.nWidth > o3tl::make_unsigned(m_nMaxWidth) )
+ maGeometry.nWidth = m_nMaxWidth;
+ if( m_nMinWidth > 0 && maGeometry.nWidth < o3tl::make_unsigned(m_nMinWidth) )
+ maGeometry.nWidth = m_nMinWidth;
+ }
+ if( (nFlags & SAL_FRAME_POSSIZE_HEIGHT) != 0 )
+ {
+ maGeometry.nHeight = nHeight;
+ if( m_nMaxHeight > 0 && maGeometry.nHeight > o3tl::make_unsigned(m_nMaxHeight) )
+ maGeometry.nHeight = m_nMaxHeight;
+ if( m_nMinHeight > 0 && maGeometry.nHeight < o3tl::make_unsigned(m_nMinHeight) )
+ maGeometry.nHeight = m_nMinHeight;
+ }
+#ifndef IOS
+ basegfx::B2IVector aFrameSize( maGeometry.nWidth, maGeometry.nHeight );
+ if (!m_pSurface || cairo_image_surface_get_width(m_pSurface) != aFrameSize.getX() ||
+ cairo_image_surface_get_height(m_pSurface) != aFrameSize.getY() )
+ {
+ if( aFrameSize.getX() == 0 )
+ aFrameSize.setX( 1 );
+ if( aFrameSize.getY() == 0 )
+ aFrameSize.setY( 1 );
+
+ if (m_pSurface)
+ cairo_surface_destroy(m_pSurface);
+
+ // Creating backing surfaces for invisible windows costs a big chunk of RAM.
+ if (Application::IsHeadlessModeEnabled())
+ aFrameSize = basegfx::B2IVector( 1, 1 );
+
+ 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( long& rWidth, long& rHeight )
+{
+ rWidth = maGeometry.nWidth;
+ rHeight = maGeometry.nHeight;
+}
+
+void SvpSalFrame::GetWorkArea( tools::Rectangle& rRect )
+{
+ rRect = tools::Rectangle( Point( 0, 0 ),
+ Size( VIRTUAL_DESKTOP_WIDTH, VIRTUAL_DESKTOP_HEIGHT ) );
+}
+
+SalFrame* SvpSalFrame::GetParent() const
+{
+ return m_pParent;
+}
+
+static constexpr auto FRAMESTATE_MASK_GEOMETRY =
+ WindowStateMask::X | WindowStateMask::Y |
+ WindowStateMask::Width | WindowStateMask::Height;
+
+void SvpSalFrame::SetWindowState( const SalFrameState *pState )
+{
+ if (pState == nullptr)
+ return;
+
+ // Request for position or size change
+ if (pState->mnMask & FRAMESTATE_MASK_GEOMETRY)
+ {
+ long nX = maGeometry.nX;
+ long nY = maGeometry.nY;
+ long nWidth = maGeometry.nWidth;
+ long nHeight = maGeometry.nHeight;
+
+ // change requested properties
+ if (pState->mnMask & WindowStateMask::X)
+ nX = pState->mnX;
+ if (pState->mnMask & WindowStateMask::Y)
+ nY = pState->mnY;
+ if (pState->mnMask & WindowStateMask::Width)
+ nWidth = pState->mnWidth;
+ if (pState->mnMask & WindowStateMask::Height)
+ nHeight = pState->mnHeight;
+
+ SetPosSize( nX, nY, nWidth, nHeight,
+ SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y |
+ SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT );
+ }
+}
+
+bool SvpSalFrame::GetWindowState( SalFrameState* pState )
+{
+ pState->mnState = WindowStateState::Normal;
+ pState->mnX = maGeometry.nX;
+ pState->mnY = maGeometry.nY;
+ pState->mnWidth = maGeometry.nWidth;
+ pState->mnHeight = maGeometry.nHeight;
+ pState->mnMask = FRAMESTATE_MASK_GEOMETRY | WindowStateMask::State;
+
+ 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 )
+{
+ GetFocus();
+}
+
+void SvpSalFrame::SetPointer( PointerStyle )
+{
+}
+
+void SvpSalFrame::CaptureMouse( bool )
+{
+}
+
+void SvpSalFrame::SetPointerPos( long, 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.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 )
+ m_pParent->m_aChildren.remove( this );
+ m_pParent = static_cast<SvpSalFrame*>(pNewParent);
+}
+
+bool SvpSalFrame::SetPluginParent( SystemParentData* )
+{
+ return true;
+}
+
+void SvpSalFrame::ResetClipRegion()
+{
+}
+
+void SvpSalFrame::BeginSetClipRegion( sal_uInt32 )
+{
+}
+
+void SvpSalFrame::UnionClipRegion( long, long, long, 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 000000000..fadf641fd
--- /dev/null
+++ b/vcl/headless/svpgdi.cxx
@@ -0,0 +1,2622 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <headless/svpbmp.hxx>
+#include <headless/svpframe.hxx>
+#include <headless/svpcairotextrender.hxx>
+#include <headless/CustomWidgetDraw.hxx>
+#include <saldatabasic.hxx>
+
+#include <sal/log.hxx>
+#include <tools/helpers.hxx>
+#include <o3tl/safeint.hxx>
+#include <vcl/BitmapTools.hxx>
+#include <vcl/sysdata.hxx>
+#include <vcl/gradient.hxx>
+#include <config_cairo_canvas.h>
+#include <basegfx/numeric/ftools.hxx>
+#include <basegfx/range/b2drange.hxx>
+#include <basegfx/range/b2ibox.hxx>
+#include <basegfx/range/b2irange.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/utils/canvastools.hxx>
+#include <basegfx/utils/systemdependentdata.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <comphelper/lok.hxx>
+#include <unx/gendata.hxx>
+#include <dlfcn.h>
+
+#if ENABLE_CAIRO_CANVAS
+# if defined CAIRO_VERSION && CAIRO_VERSION < CAIRO_VERSION_ENCODE(1, 10, 0)
+# define CAIRO_OPERATOR_DIFFERENCE (static_cast<cairo_operator_t>(23))
+# endif
+#endif
+
+namespace
+{
+ 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 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 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;
+ }
+}
+
+bool SvpSalGraphics::blendBitmap( const SalTwoRect&, const SalBitmap& /*rBitmap*/ )
+{
+ SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::blendBitmap case");
+ return false;
+}
+
+bool SvpSalGraphics::blendAlphaBitmap( const SalTwoRect&, const SalBitmap&, const SalBitmap&, const SalBitmap& )
+{
+ SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::blendAlphaBitmap case");
+ return false;
+}
+
+namespace
+{
+ 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;
+ }
+
+ void Toggle1BitTransparency(const BitmapBuffer& rBuf)
+ {
+ assert(rBuf.maPalette.GetBestIndex(BitmapColor(COL_BLACK)) == 0);
+ // TODO: make upper layers use standard alpha
+ if (getCairoFormat(rBuf) == CAIRO_FORMAT_A1)
+ {
+ const int nImageSize = rBuf.mnHeight * rBuf.mnScanlineSize;
+ unsigned char* pDst = rBuf.mpBits;
+ for (int i = nImageSize; --i >= 0; ++pDst)
+ *pDst = ~*pDst;
+ }
+ }
+
+ std::unique_ptr<BitmapBuffer> FastConvert24BitRgbTo32BitCairo(const BitmapBuffer* pSrc)
+ {
+ if (pSrc == nullptr)
+ return nullptr;
+
+ assert(pSrc->mnFormat == SVP_24BIT_FORMAT);
+ const long nWidth = pSrc->mnWidth;
+ const long nHeight = pSrc->mnHeight;
+ std::unique_ptr<BitmapBuffer> pDst(new BitmapBuffer);
+ pDst->mnFormat = (ScanlineFormat::N32BitTcArgb | ScanlineFormat::TopDown);
+ pDst->mnWidth = nWidth;
+ pDst->mnHeight = nHeight;
+ pDst->mnBitCount = 32;
+ pDst->maColorMask = pSrc->maColorMask;
+ pDst->maPalette = pSrc->maPalette;
+
+ long nScanlineBase;
+ const bool bFail = o3tl::checked_multiply<long>(pDst->mnBitCount, nWidth, nScanlineBase);
+ if (bFail)
+ {
+ SAL_WARN("vcl.gdi", "checked multiply failed");
+ pDst->mpBits = nullptr;
+ return nullptr;
+ }
+
+ pDst->mnScanlineSize = AlignedWidth4Bytes(nScanlineBase);
+ if (pDst->mnScanlineSize < nScanlineBase/8)
+ {
+ SAL_WARN("vcl.gdi", "scanline calculation wraparound");
+ pDst->mpBits = nullptr;
+ return nullptr;
+ }
+
+ try
+ {
+ pDst->mpBits = new sal_uInt8[ pDst->mnScanlineSize * nHeight ];
+ }
+ catch (const std::bad_alloc&)
+ {
+ // memory exception, clean up
+ pDst->mpBits = nullptr;
+ return nullptr;
+ }
+
+ for (long y = 0; y < nHeight; ++y)
+ {
+ sal_uInt8* pS = pSrc->mpBits + y * pSrc->mnScanlineSize;
+ sal_uInt8* pD = pDst->mpBits + y * pDst->mnScanlineSize;
+ for (long x = 0; x < nWidth; ++x)
+ {
+#if defined(ANDROID) && !HAVE_FEATURE_ANDROID_LOK
+ 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;
+ }
+
+ // check for env var that decides for using downscale pattern
+ static const char* pDisableDownScale(getenv("SAL_DISABLE_CAIRO_DOWNSCALE"));
+ static bool bDisableDownScale(nullptr != pDisableDownScale);
+
+ class SurfaceHelper
+ {
+ private:
+ cairo_surface_t* pSurface;
+ std::unordered_map<unsigned long long, cairo_surface_t*> maDownscaled;
+
+ SurfaceHelper(const SurfaceHelper&) = delete;
+ SurfaceHelper& operator=(const SurfaceHelper&) = delete;
+
+ cairo_surface_t* 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
+ const unsigned long long key((nW * LONG_MAX) + 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);
+
+ // did a version to scale self first that worked well, but wouuld'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;
+ }
+
+ protected:
+ cairo_surface_t* implGetSurface() const { return pSurface; }
+ void implSetSurface(cairo_surface_t* pNew) { pSurface = pNew; }
+
+ bool 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;
+ }
+
+ public:
+ explicit SurfaceHelper()
+ : pSurface(nullptr),
+ maDownscaled()
+ {
+ }
+ ~SurfaceHelper()
+ {
+ cairo_surface_destroy(pSurface);
+ for(auto& candidate : maDownscaled)
+ {
+ cairo_surface_destroy(candidate.second);
+ }
+ }
+ cairo_surface_t* getSurface(
+ unsigned long nTargetWidth = 0,
+ unsigned long nTargetHeight = 0) 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);
+ }
+ };
+
+ class 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)
+ : SurfaceHelper(),
+#ifdef HAVE_CAIRO_FORMAT_RGB24_888
+ m_bForceARGB32(bForceARGB32),
+#endif
+ aTmpBmp()
+ {
+ 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::unique_ptr<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(SvpSalGraphics::createCairoSurface(aTmpBmp.GetBuffer()));
+ }
+ else
+ {
+ implSetSurface(SvpSalGraphics::createCairoSurface(rSrcBmp.GetBuffer()));
+ }
+ }
+ void mark_dirty()
+ {
+ cairo_surface_mark_dirty(implGetSurface());
+ }
+ unsigned char* 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;
+ }
+ };
+
+ sal_Int64 estimateUsageInBytesForSurfaceHelper(const SurfaceHelper* pHelper)
+ {
+ sal_Int64 nRetval(0);
+
+ if(nullptr != pHelper)
+ {
+ cairo_surface_t* pSurface(pHelper->getSurface());
+
+ if(pSurface)
+ {
+ const long nStride(cairo_image_surface_get_stride(pSurface));
+ const 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;
+ }
+
+ class SystemDependentData_BitmapHelper : public basegfx::SystemDependentData
+ {
+ private:
+ std::shared_ptr<BitmapHelper> maBitmapHelper;
+
+ public:
+ SystemDependentData_BitmapHelper(
+ basegfx::SystemDependentDataManager& rSystemDependentDataManager,
+ const std::shared_ptr<BitmapHelper>& rBitmapHelper)
+ : basegfx::SystemDependentData(rSystemDependentDataManager),
+ maBitmapHelper(rBitmapHelper)
+ {
+ }
+
+ const std::shared_ptr<BitmapHelper>& getBitmapHelper() const { return maBitmapHelper; };
+ virtual sal_Int64 estimateUsageInBytes() const override;
+ };
+
+ sal_Int64 SystemDependentData_BitmapHelper::estimateUsageInBytes() const
+ {
+ return estimateUsageInBytesForSurfaceHelper(maBitmapHelper.get());
+ }
+
+ class MaskHelper : public SurfaceHelper
+ {
+ private:
+ std::unique_ptr<unsigned char[]> pAlphaBits;
+
+ public:
+ explicit MaskHelper(const SalBitmap& rAlphaBitmap)
+ : SurfaceHelper(),
+ pAlphaBits()
+ {
+ const SvpSalBitmap& rMask = static_cast<const SvpSalBitmap&>(rAlphaBitmap);
+ const BitmapBuffer* pMaskBuf = rMask.GetBuffer();
+
+ if (rAlphaBitmap.GetBitCount() == 8)
+ {
+ // the alpha values need to be inverted for Cairo
+ // so big stupid copy and invert here
+ const int nImageSize = pMaskBuf->mnHeight * pMaskBuf->mnScanlineSize;
+ pAlphaBits.reset( new unsigned char[nImageSize] );
+ memcpy(pAlphaBits.get(), pMaskBuf->mpBits, nImageSize);
+
+ // TODO: make upper layers use standard alpha
+ sal_uInt32* pLDst = reinterpret_cast<sal_uInt32*>(pAlphaBits.get());
+ for( int i = nImageSize/sizeof(sal_uInt32); --i >= 0; ++pLDst )
+ *pLDst = ~*pLDst;
+ assert(reinterpret_cast<unsigned char*>(pLDst) == pAlphaBits.get()+nImageSize);
+
+ implSetSurface(
+ cairo_image_surface_create_for_data(
+ pAlphaBits.get(),
+ CAIRO_FORMAT_A8,
+ pMaskBuf->mnWidth,
+ pMaskBuf->mnHeight,
+ pMaskBuf->mnScanlineSize));
+ }
+ else
+ {
+ // the alpha values need to be inverted for Cairo
+ // so big stupid copy and invert here
+ const int nImageSize = pMaskBuf->mnHeight * pMaskBuf->mnScanlineSize;
+ pAlphaBits.reset( new unsigned char[nImageSize] );
+ memcpy(pAlphaBits.get(), pMaskBuf->mpBits, nImageSize);
+
+ const sal_Int32 nBlackIndex = pMaskBuf->maPalette.GetBestIndex(BitmapColor(COL_BLACK));
+ if (nBlackIndex == 0)
+ {
+ // TODO: make upper layers use standard alpha
+ unsigned char* pDst = pAlphaBits.get();
+ for (int i = nImageSize; --i >= 0; ++pDst)
+ *pDst = ~*pDst;
+ }
+
+ implSetSurface(
+ cairo_image_surface_create_for_data(
+ pAlphaBits.get(),
+ CAIRO_FORMAT_A1,
+ pMaskBuf->mnWidth,
+ pMaskBuf->mnHeight,
+ pMaskBuf->mnScanlineSize));
+ }
+ }
+ };
+
+ class SystemDependentData_MaskHelper : public basegfx::SystemDependentData
+ {
+ private:
+ std::shared_ptr<MaskHelper> maMaskHelper;
+
+ public:
+ SystemDependentData_MaskHelper(
+ basegfx::SystemDependentDataManager& rSystemDependentDataManager,
+ const std::shared_ptr<MaskHelper>& rMaskHelper)
+ : basegfx::SystemDependentData(rSystemDependentDataManager),
+ maMaskHelper(rMaskHelper)
+ {
+ }
+
+ const std::shared_ptr<MaskHelper>& getMaskHelper() const { return maMaskHelper; };
+ virtual sal_Int64 estimateUsageInBytes() const override;
+ };
+
+ sal_Int64 SystemDependentData_MaskHelper::estimateUsageInBytes() const
+ {
+ return estimateUsageInBytesForSurfaceHelper(maMaskHelper.get());
+ }
+
+ // MM02 decide to use buffers or not
+ static const char* pDisableMM02Goodies(getenv("SAL_DISABLE_MM02_GOODIES"));
+ static bool bUseBuffer(nullptr == pDisableMM02Goodies);
+ static 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)
+ {
+ const SvpSalBitmap& rSrcBmp(static_cast<const SvpSalBitmap&>(rSourceBitmap));
+ pSystemDependentData_BitmapHelper = rSrcBmp.getSystemDependentData<SystemDependentData_BitmapHelper>();
+
+ if(pSystemDependentData_BitmapHelper)
+ {
+ // reuse buffered data
+ rSurface = pSystemDependentData_BitmapHelper->getBitmapHelper();
+ }
+ }
+
+ if(!rSurface)
+ {
+ // create data on-demand
+ rSurface = std::make_shared<BitmapHelper>(rSourceBitmap);
+
+ if(bBufferSource)
+ {
+ // add to buffering mechanism to potentially reuse next time
+ const SvpSalBitmap& rSrcBmp(static_cast<const SvpSalBitmap&>(rSourceBitmap));
+ rSrcBmp.addOrReplaceSystemDependentData<SystemDependentData_BitmapHelper>(
+ ImplGetSystemDependentDataManager(),
+ 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)
+ {
+ const SvpSalBitmap& rSrcBmp(static_cast<const SvpSalBitmap&>(rMaskBitmap));
+ pSystemDependentData_MaskHelper = rSrcBmp.getSystemDependentData<SystemDependentData_MaskHelper>();
+
+ if(pSystemDependentData_MaskHelper)
+ {
+ // reuse buffered data
+ rMask = pSystemDependentData_MaskHelper->getMaskHelper();
+ }
+ }
+
+ if(!rMask)
+ {
+ // create data on-demand
+ rMask = std::make_shared<MaskHelper>(rMaskBitmap);
+
+ if(bBufferMask)
+ {
+ // add to buffering mechanism to potentially reuse next time
+ const SvpSalBitmap& rSrcBmp(static_cast<const SvpSalBitmap&>(rMaskBitmap));
+ rSrcBmp.addOrReplaceSystemDependentData<SystemDependentData_MaskHelper>(
+ ImplGetSystemDependentDataManager(),
+ rMask);
+ }
+ }
+ }
+}
+
+bool SvpSalGraphics::drawAlphaBitmap( const SalTwoRect& rTR, const SalBitmap& rSourceBitmap, const SalBitmap& rAlphaBitmap )
+{
+ if (rAlphaBitmap.GetBitCount() != 8 && rAlphaBitmap.GetBitCount() != 1)
+ {
+ SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawAlphaBitmap alpha depth case: " << rAlphaBitmap.GetBitCount());
+ return false;
+ }
+
+ // 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);
+ 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);
+
+ //tdf#114117 when stretching a single pixel width/height source to fit an area
+ //set extend and filter to stretch it with simplest expected interpolation
+ if ((fXScale != 1.0 && rTR.mnSrcWidth == 1) || (fYScale != 1.0 && rTR.mnSrcHeight == 1))
+ {
+ cairo_pattern_t* sourcepattern = cairo_get_source(cr);
+ cairo_pattern_set_extend(sourcepattern, CAIRO_EXTEND_REPEAT);
+ cairo_pattern_set_filter(sourcepattern, CAIRO_FILTER_NEAREST);
+ cairo_pattern_set_extend(maskpattern, CAIRO_EXTEND_REPEAT);
+ cairo_pattern_set_filter(maskpattern, CAIRO_FILTER_NEAREST);
+ }
+
+ //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 SvpSalGraphics::drawTransformedBitmap(
+ const basegfx::B2DPoint& rNull,
+ const basegfx::B2DPoint& rX,
+ const basegfx::B2DPoint& rY,
+ const SalBitmap& rSourceBitmap,
+ const SalBitmap* pAlphaBitmap)
+{
+ if (pAlphaBitmap && pAlphaBitmap->GetBitCount() != 8 && pAlphaBitmap->GetBitCount() != 1)
+ {
+ SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawTransformedBitmap alpha depth case: " << pAlphaBitmap->GetBitCount());
+ return false;
+ }
+
+ // MM02 try to access buffered BitmapHelper
+ std::shared_ptr<BitmapHelper> aSurface;
+ tryToUseSourceBuffer(rSourceBitmap, aSurface);
+ const long nDestWidth(basegfx::fround(basegfx::B2DVector(rX - rNull).getLength()));
+ const 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);
+ 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 SvpSalGraphics::clipRegion(cairo_t* cr, const vcl::Region& rClipRegion)
+{
+ RectangleVector aRectangles;
+ if (!rClipRegion.IsEmpty())
+ {
+ rClipRegion.GetRegionRectangles(aRectangles);
+ }
+ if (!aRectangles.empty())
+ {
+ for (auto const& rectangle : aRectangles)
+ {
+ cairo_rectangle(cr, rectangle.Left(), rectangle.Top(), rectangle.GetWidth(), rectangle.GetHeight());
+ }
+ cairo_clip(cr);
+ }
+}
+
+void SvpSalGraphics::clipRegion(cairo_t* cr)
+{
+ SvpSalGraphics::clipRegion(cr, m_aClipRegion);
+}
+
+bool SvpSalGraphics::drawAlphaRect(long nX, long nY, long nWidth, long nHeight, sal_uInt8 nTransparency)
+{
+ const bool bHasFill(m_aFillColor != SALCOLOR_NONE);
+ const bool bHasLine(m_aLineColor != SALCOLOR_NONE);
+
+ if(!(bHasFill || bHasLine))
+ {
+ return true;
+ }
+
+ cairo_t* cr = getCairoContext(false);
+ 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_aFillColor, 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_aLineColor, 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;
+}
+
+SvpSalGraphics::SvpSalGraphics()
+ : m_pSurface(nullptr)
+ , m_fScale(1.0)
+ , m_aLineColor(Color(0x00, 0x00, 0x00))
+ , m_aFillColor(Color(0xFF, 0xFF, 0XFF))
+ , m_ePaintMode(PaintMode::Over)
+ , m_aTextRenderImpl(*this)
+{
+ bool bLOKActive = comphelper::LibreOfficeKit::isActive();
+ if (!initWidgetDrawBackends(bLOKActive))
+ {
+ if (bLOKActive)
+ m_pWidgetDraw.reset(new vcl::CustomWidgetDraw(*this));
+ }
+}
+
+SvpSalGraphics::~SvpSalGraphics()
+{
+ ReleaseFonts();
+}
+
+void SvpSalGraphics::setSurface(cairo_surface_t* pSurface, const basegfx::B2IVector& rSize)
+{
+ m_pSurface = pSurface;
+ m_aFrameSize = rSize;
+ dl_cairo_surface_get_device_scale(pSurface, &m_fScale, nullptr);
+ ResetClipRegion();
+}
+
+void SvpSalGraphics::GetResolution( sal_Int32& rDPIX, sal_Int32& rDPIY )
+{
+ rDPIX = rDPIY = 96;
+}
+
+sal_uInt16 SvpSalGraphics::GetBitCount() const
+{
+ if (cairo_surface_get_content(m_pSurface) != CAIRO_CONTENT_COLOR_ALPHA)
+ return 1;
+ return 32;
+}
+
+long SvpSalGraphics::GetGraphicsWidth() const
+{
+ return m_pSurface ? m_aFrameSize.getX() : 0;
+}
+
+void SvpSalGraphics::ResetClipRegion()
+{
+ m_aClipRegion.SetNull();
+}
+
+bool SvpSalGraphics::setClipRegion( const vcl::Region& i_rClip )
+{
+ m_aClipRegion = i_rClip;
+ return true;
+}
+
+void SvpSalGraphics::SetLineColor()
+{
+ m_aLineColor = SALCOLOR_NONE;
+}
+
+void SvpSalGraphics::SetLineColor( Color nColor )
+{
+ m_aLineColor = nColor;
+}
+
+void SvpSalGraphics::SetFillColor()
+{
+ m_aFillColor = SALCOLOR_NONE;
+}
+
+void SvpSalGraphics::SetFillColor( Color nColor )
+{
+ m_aFillColor = nColor;
+}
+
+void SvpSalGraphics::SetXORMode(bool bSet, bool )
+{
+ m_ePaintMode = bSet ? PaintMode::Xor : PaintMode::Over;
+}
+
+void SvpSalGraphics::SetROPLineColor( SalROPColor nROPColor )
+{
+ switch( nROPColor )
+ {
+ case SalROPColor::N0:
+ m_aLineColor = Color(0, 0, 0);
+ break;
+ case SalROPColor::N1:
+ m_aLineColor = Color(0xff, 0xff, 0xff);
+ break;
+ case SalROPColor::Invert:
+ m_aLineColor = Color(0xff, 0xff, 0xff);
+ break;
+ }
+}
+
+void SvpSalGraphics::SetROPFillColor( SalROPColor nROPColor )
+{
+ switch( nROPColor )
+ {
+ case SalROPColor::N0:
+ m_aFillColor = Color(0, 0, 0);
+ break;
+ case SalROPColor::N1:
+ m_aFillColor = Color(0xff, 0xff, 0xff);
+ break;
+ case SalROPColor::Invert:
+ m_aFillColor = Color(0xff, 0xff, 0xff);
+ break;
+ }
+}
+
+void SvpSalGraphics::drawPixel( long nX, long nY )
+{
+ if (m_aLineColor != SALCOLOR_NONE)
+ {
+ drawPixel(nX, nY, m_aLineColor);
+ }
+}
+
+void SvpSalGraphics::drawPixel( long nX, long nY, Color aColor )
+{
+ cairo_t* cr = getCairoContext(true);
+ clipRegion(cr);
+
+ cairo_rectangle(cr, nX, nY, 1, 1);
+ applyColor(cr, aColor, 0.0);
+ cairo_fill(cr);
+
+ basegfx::B2DRange extents = getClippedFillDamage(cr);
+ releaseCairoContext(cr, true, extents);
+}
+
+void SvpSalGraphics::drawRect( long nX, long nY, long nWidth, long nHeight )
+{
+ // because of the -1 hack we have to do fill and draw separately
+ Color aOrigFillColor = m_aFillColor;
+ Color aOrigLineColor = m_aLineColor;
+ m_aFillColor = SALCOLOR_NONE;
+ m_aLineColor = SALCOLOR_NONE;
+
+ if (aOrigFillColor != SALCOLOR_NONE)
+ {
+ basegfx::B2DPolygon aRect = basegfx::utils::createPolygonFromRect(basegfx::B2DRectangle(nX, nY, nX+nWidth, nY+nHeight));
+ m_aFillColor = aOrigFillColor;
+
+ drawPolyPolygon(
+ basegfx::B2DHomMatrix(),
+ basegfx::B2DPolyPolygon(aRect),
+ 0.0);
+
+ m_aFillColor = SALCOLOR_NONE;
+ }
+
+ if (aOrigLineColor != SALCOLOR_NONE)
+ {
+ // need same -1 hack as X11SalGraphicsImpl::drawRect
+ basegfx::B2DPolygon aRect = basegfx::utils::createPolygonFromRect(basegfx::B2DRectangle( nX, nY, nX+nWidth-1, nY+nHeight-1));
+ m_aLineColor = aOrigLineColor;
+
+ drawPolyPolygon(
+ basegfx::B2DHomMatrix(),
+ basegfx::B2DPolyPolygon(aRect),
+ 0.0);
+
+ m_aLineColor = SALCOLOR_NONE;
+ }
+
+ m_aFillColor = aOrigFillColor;
+ m_aLineColor = aOrigLineColor;
+}
+
+void SvpSalGraphics::drawPolyLine(sal_uInt32 nPoints, const SalPoint* pPtAry)
+{
+ basegfx::B2DPolygon aPoly;
+ aPoly.append(basegfx::B2DPoint(pPtAry->mnX, pPtAry->mnY), nPoints);
+ for (sal_uInt32 i = 1; i < nPoints; ++i)
+ aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].mnX, pPtAry[i].mnY));
+ aPoly.setClosed(false);
+
+ drawPolyLine(
+ basegfx::B2DHomMatrix(),
+ aPoly,
+ 0.0,
+ 1.0,
+ nullptr, // MM01
+ basegfx::B2DLineJoin::Miter,
+ css::drawing::LineCap_BUTT,
+ basegfx::deg2rad(15.0) /*default*/,
+ false);
+}
+
+void SvpSalGraphics::drawPolygon(sal_uInt32 nPoints, const SalPoint* pPtAry)
+{
+ basegfx::B2DPolygon aPoly;
+ aPoly.append(basegfx::B2DPoint(pPtAry->mnX, pPtAry->mnY), nPoints);
+ for (sal_uInt32 i = 1; i < nPoints; ++i)
+ aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].mnX, pPtAry[i].mnY));
+
+ drawPolyPolygon(
+ basegfx::B2DHomMatrix(),
+ basegfx::B2DPolyPolygon(aPoly),
+ 0.0);
+}
+
+void SvpSalGraphics::drawPolyPolygon(sal_uInt32 nPoly,
+ const sal_uInt32* pPointCounts,
+ PCONSTSALPOINT* pPtAry)
+{
+ basegfx::B2DPolyPolygon aPolyPoly;
+ for(sal_uInt32 nPolygon = 0; nPolygon < nPoly; ++nPolygon)
+ {
+ sal_uInt32 nPoints = pPointCounts[nPolygon];
+ if (nPoints)
+ {
+ PCONSTSALPOINT pPoints = pPtAry[nPolygon];
+ basegfx::B2DPolygon aPoly;
+ aPoly.append( basegfx::B2DPoint(pPoints->mnX, pPoints->mnY), nPoints);
+ for (sal_uInt32 i = 1; i < nPoints; ++i)
+ aPoly.setB2DPoint(i, basegfx::B2DPoint( pPoints[i].mnX, pPoints[i].mnY));
+
+ aPolyPoly.append(aPoly);
+ }
+ }
+
+ drawPolyPolygon(
+ basegfx::B2DHomMatrix(),
+ aPolyPoly,
+ 0.0);
+}
+
+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);
+}
+
+// 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
+static 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;
+
+ 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 = impPixelSnap(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;
+}
+
+void SvpSalGraphics::drawLine( long nX1, long nY1, long nX2, long nY2 )
+{
+ 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 !getAntiAliasB2DDraw(), losing the (0.5, 0.5) offset
+ aPoly.append(basegfx::B2DPoint(nX1, nY1));
+ aPoly.append(basegfx::B2DPoint(nX2, nY2));
+
+ cairo_t* cr = getCairoContext(false);
+ clipRegion(cr);
+
+ // 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(),
+ !getAntiAliasB2DDraw(),
+ false);
+
+ applyColor(cr, m_aLineColor);
+
+ basegfx::B2DRange extents = getClippedStrokeDamage(cr);
+ extents.transform(basegfx::utils::createTranslateB2DHomMatrix(0.5, 0.5));
+
+ cairo_stroke(cr);
+
+ releaseCairoContext(cr, false, extents);
+}
+
+namespace {
+
+class SystemDependentData_CairoPath : 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 mbAntiAliasB2DDraw;
+ std::vector< double > maStroke;
+
+public:
+ SystemDependentData_CairoPath(
+ basegfx::SystemDependentDataManager& rSystemDependentDataManager,
+ size_t nSizeMeasure,
+ cairo_t* cr,
+ bool bNoJoin,
+ bool bAntiAliasB2DDraw,
+ const std::vector< double >* pStroke); // MM01
+ virtual ~SystemDependentData_CairoPath() override;
+
+ // read access
+ cairo_path_t* getCairoPath() { return mpCairoPath; }
+ bool getNoJoin() const { return mbNoJoin; }
+ bool getAntiAliasB2DDraw() const { return mbAntiAliasB2DDraw; }
+ const std::vector< double >& getStroke() const { return maStroke; }
+
+ virtual sal_Int64 estimateUsageInBytes() const override;
+};
+
+}
+
+SystemDependentData_CairoPath::SystemDependentData_CairoPath(
+ basegfx::SystemDependentDataManager& rSystemDependentDataManager,
+ size_t nSizeMeasure,
+ cairo_t* cr,
+ bool bNoJoin,
+ bool bAntiAliasB2DDraw,
+ const std::vector< double >* pStroke)
+: basegfx::SystemDependentData(rSystemDependentDataManager),
+ mpCairoPath(nullptr),
+ mbNoJoin(bNoJoin),
+ mbAntiAliasB2DDraw(bAntiAliasB2DDraw),
+ maStroke()
+{
+ // tdf#129845 only create a copy of the path when nSizeMeasure is
+ // bigger than some decent threshold
+ if(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;
+}
+
+bool SvpSalGraphics::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)
+{
+ // short circuit if there is nothing to do
+ if(0 == rPolyLine.count() || fTransparency < 0.0 || fTransparency >= 1.0)
+ {
+ return true;
+ }
+
+ // Wrap call to static version of ::drawPolyLine by
+ // preparing/getting some local data and parameters
+ // due to usage in vcl/unx/generic/gdi/salgdi.cxx.
+ // This is mainly about extended handling of extents
+ // and the way destruction of CairoContext is handled
+ // due to current XOR stuff
+ cairo_t* cr = getCairoContext(false);
+ basegfx::B2DRange aExtents;
+ clipRegion(cr);
+
+ bool bRetval(
+ drawPolyLine(
+ cr,
+ &aExtents,
+ m_aLineColor,
+ getAntiAliasB2DDraw(),
+ rObjectToDevice,
+ rPolyLine,
+ fTransparency,
+ fLineWidth,
+ pStroke, // MM01
+ eLineJoin,
+ eLineCap,
+ fMiterMinimumAngle,
+ bPixelSnapHairline));
+
+ releaseCairoContext(cr, false, aExtents);
+
+ return bRetval;
+}
+
+bool SvpSalGraphics::drawPolyLine(
+ cairo_t* cr,
+ basegfx::B2DRange* pExtents,
+ const Color& rLineColor,
+ bool bAntiAliasB2DDraw,
+ 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)
+{
+ // short circuit if there is nothing to do
+ if(0 == rPolyLine.count() || fTransparency < 0.0 || fTransparency >= 1.0)
+ {
+ return true;
+ }
+
+ // 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( fMiterMinimumAngle / 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,
+ rLineColor.GetRed()/255.0,
+ rLineColor.GetGreen()/255.0,
+ rLineColor.GetBlue()/255.0,
+ 1.0-fTransparency);
+
+ cairo_set_line_join(cr, eCairoLineJoin);
+ cairo_set_line_cap(cr, eCairoLineCap);
+ 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 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->getAntiAliasB2DDraw() != bAntiAliasB2DDraw
+ || 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
+ !bAntiAliasB2DDraw,
+ 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
+ !bAntiAliasB2DDraw,
+ 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>(
+ ImplGetSystemDependentDataManager(),
+ nSizeMeasure,
+ cr,
+ bNoJoin,
+ bAntiAliasB2DDraw,
+ pStroke);
+ }
+ }
+
+ // extract extents
+ if (pExtents)
+ {
+ *pExtents = getClippedStrokeDamage(cr);
+ // transform also extents (ranges) of damage so they can be correctly redrawn
+ pExtents->transform(aDamageMatrix);
+ }
+
+ // draw and consume
+ cairo_stroke(cr);
+
+ return true;
+}
+
+bool SvpSalGraphics::drawPolyLineBezier( sal_uInt32,
+ const SalPoint*,
+ const PolyFlags* )
+{
+ SAL_INFO("vcl.gdi", "unsupported SvpSalGraphics::drawPolyLineBezier case");
+ return false;
+}
+
+bool SvpSalGraphics::drawPolygonBezier( sal_uInt32,
+ const SalPoint*,
+ const PolyFlags* )
+{
+ SAL_INFO("vcl.gdi", "unsupported SvpSalGraphics::drawPolygonBezier case");
+ return false;
+}
+
+bool SvpSalGraphics::drawPolyPolygonBezier( sal_uInt32,
+ const sal_uInt32*,
+ const SalPoint* const*,
+ const PolyFlags* const* )
+{
+ SAL_INFO("vcl.gdi", "unsupported SvpSalGraphics::drawPolyPolygonBezier case");
+ return false;
+}
+
+namespace
+{
+ 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>(
+ ImplGetSystemDependentDataManager(),
+ nSizeMeasure,
+ cr,
+ false,
+ false,
+ nullptr);
+ }
+ }
+}
+
+bool SvpSalGraphics::drawPolyPolygon(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon& rPolyPolygon,
+ double fTransparency)
+{
+ const bool bHasFill(m_aFillColor != SALCOLOR_NONE);
+ const bool bHasLine(m_aLineColor != SALCOLOR_NONE);
+
+ if(0 == rPolyPolygon.count() || !(bHasFill || bHasLine) || fTransparency < 0.0 || fTransparency >= 1.0)
+ {
+ return true;
+ }
+
+ cairo_t* cr = getCairoContext(true);
+ 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, !getAntiAliasB2DDraw());
+
+ applyColor(cr, m_aFillColor, 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, !getAntiAliasB2DDraw());
+
+ applyColor(cr, m_aLineColor, 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);
+
+ return true;
+}
+
+bool SvpSalGraphics::implDrawGradient(basegfx::B2DPolyPolygon const & rPolyPolygon, SalGradient const & rGradient)
+{
+ cairo_t* cr = getCairoContext(true);
+ clipRegion(cr);
+
+ basegfx::B2DHomMatrix rObjectToDevice;
+
+ for (auto const & rPolygon : rPolyPolygon)
+ AddPolygonToPath(cr, rPolygon, rObjectToDevice, !getAntiAliasB2DDraw(), false);
+
+ cairo_pattern_t* pattern;
+ 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 = (0xFF - rStop.maColor.GetTransparency()) / 255.0;
+ double offset = rStop.mfOffset;
+
+ cairo_pattern_add_color_stop_rgba(pattern, offset, r, g, b, a);
+ }
+ cairo_set_source(cr, pattern);
+
+ basegfx::B2DRange extents = getClippedFillDamage(cr);
+ cairo_fill_preserve(cr);
+
+ releaseCairoContext(cr, true, extents);
+
+ return true;
+}
+
+void SvpSalGraphics::applyColor(cairo_t *cr, Color aColor, double fTransparency)
+{
+ if (cairo_surface_get_content(m_pSurface) == CAIRO_CONTENT_COLOR_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 SvpSalGraphics::copyArea( long nDestX,
+ long nDestY,
+ long nSrcX,
+ long nSrcY,
+ long nSrcWidth,
+ long nSrcHeight,
+ bool /*bWindowInvalidate*/ )
+{
+ SalTwoRect aTR(nSrcX, nSrcY, nSrcWidth, nSrcHeight, nDestX, nDestY, nSrcWidth, nSrcHeight);
+ copyBits(aTR, this);
+}
+
+static 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);
+ double fXScale = 1.0f;
+ double fYScale = 1.0f;
+ if (rTR.mnSrcWidth != 0 && rTR.mnSrcHeight != 0) {
+ fXScale = static_cast<double>(rTR.mnDestWidth)/rTR.mnSrcWidth;
+ 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 ((fXScale != 1.0 && rTR.mnSrcWidth == 1) || (fYScale != 1.0 && rTR.mnSrcHeight == 1))
+ {
+ cairo_pattern_t* sourcepattern = cairo_get_source(cr);
+ cairo_pattern_set_extend(sourcepattern, CAIRO_EXTEND_REPEAT);
+ cairo_pattern_set_filter(sourcepattern, CAIRO_FILTER_NEAREST);
+ }
+ cairo_set_operator(cr, eOperator);
+ cairo_paint(cr);
+ cairo_restore(cr);
+
+ return extents;
+}
+
+static basegfx::B2DRange renderSource(cairo_t* cr, const SalTwoRect& rTR,
+ cairo_surface_t* source)
+{
+ return renderWithOperator(cr, rTR, source, CAIRO_OPERATOR_SOURCE);
+}
+
+void SvpSalGraphics::copyWithOperator( const SalTwoRect& rTR, cairo_surface_t* source,
+ cairo_operator_t eOp )
+{
+ cairo_t* cr = getCairoContext(false);
+ clipRegion(cr);
+
+ basegfx::B2DRange extents = renderWithOperator(cr, rTR, source, eOp);
+
+ releaseCairoContext(cr, false, extents);
+}
+
+void SvpSalGraphics::copySource( const SalTwoRect& rTR, cairo_surface_t* source )
+{
+ copyWithOperator(rTR, source, CAIRO_OPERATOR_SOURCE);
+}
+
+void SvpSalGraphics::copyBits( const SalTwoRect& rTR,
+ SalGraphics* pSrcGraphics )
+{
+ SalTwoRect aTR(rTR);
+
+ SvpSalGraphics* pSrc = pSrcGraphics ?
+ static_cast<SvpSalGraphics*>(pSrcGraphics) : this;
+
+ cairo_surface_t* source = pSrc->m_pSurface;
+
+ cairo_surface_t *pCopy = nullptr;
+ if (pSrc == this)
+ {
+ //self copy is a problem, so dup source in that case
+ pCopy = cairo_surface_create_similar(source,
+ cairo_surface_get_content(m_pSurface),
+ 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, source, -aTR.mnSrcX, -aTR.mnSrcY);
+ cairo_rectangle(cr, 0, 0, aTR.mnSrcWidth, aTR.mnSrcHeight);
+ cairo_fill(cr);
+ cairo_destroy(cr);
+
+ source = pCopy;
+
+ aTR.mnSrcX = 0;
+ aTR.mnSrcY = 0;
+ }
+
+ copySource(aTR, source);
+
+ if (pCopy)
+ cairo_surface_destroy(pCopy);
+}
+
+void SvpSalGraphics::drawBitmap(const SalTwoRect& rTR, const SalBitmap& rSourceBitmap)
+{
+ // 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;
+ }
+
+ copyWithOperator(rTR, source, CAIRO_OPERATOR_OVER);
+}
+
+void SvpSalGraphics::drawBitmap(const SalTwoRect& rTR, const BitmapBuffer* pBuffer, cairo_operator_t eOp)
+{
+ cairo_surface_t* source = createCairoSurface( pBuffer );
+ copyWithOperator(rTR, source, eOp);
+ cairo_surface_destroy(source);
+}
+
+void SvpSalGraphics::drawBitmap( const SalTwoRect& rTR,
+ const SalBitmap& rSourceBitmap,
+ const SalBitmap& rTransparentBitmap )
+{
+ drawAlphaBitmap(rTR, rSourceBitmap, rTransparentBitmap);
+}
+
+void SvpSalGraphics::drawMask( const SalTwoRect& rTR,
+ const SalBitmap& rSalBitmap,
+ Color nMaskColor )
+{
+ /** 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);
+ vcl::bitmap::lookup_table unpremultiply_table = vcl::bitmap::get_unpremultiply_table();
+ for (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 (long x = rTR.mnSrcX; x < rTR.mnSrcX + rTR.mnSrcWidth; ++x)
+ {
+ 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]];
+ 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);
+ 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 ((fXScale != 1.0 && rTR.mnSrcWidth == 1) || (fYScale != 1.0 && rTR.mnSrcHeight == 1))
+ {
+ cairo_pattern_t* sourcepattern = cairo_get_source(cr);
+ cairo_pattern_set_extend(sourcepattern, CAIRO_EXTEND_REPEAT);
+ cairo_pattern_set_filter(sourcepattern, CAIRO_FILTER_NEAREST);
+ }
+ cairo_paint(cr);
+
+ releaseCairoContext(cr, false, extents);
+}
+
+std::shared_ptr<SalBitmap> SvpSalGraphics::getBitmap( long nX, long nY, long nWidth, long nHeight )
+{
+ std::shared_ptr<SvpSalBitmap> pBitmap = std::make_shared<SvpSalBitmap>();
+ BitmapPalette aPal;
+ if (GetBitCount() == 1)
+ {
+ aPal.SetEntryCount(2);
+ aPal[0] = COL_BLACK;
+ aPal[1] = COL_WHITE;
+ }
+
+ if (!pBitmap->Create(Size(nWidth, nHeight), GetBitCount(), aPal))
+ {
+ SAL_WARN("vcl.gdi", "SvpSalGraphics::getBitmap, cannot create bitmap");
+ return nullptr;
+ }
+
+ cairo_surface_t* target = SvpSalGraphics::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);
+ renderSource(cr, aTR, m_pSurface);
+
+ cairo_destroy(cr);
+ cairo_surface_destroy(target);
+
+ Toggle1BitTransparency(*pBitmap->GetBuffer());
+
+ return pBitmap;
+}
+
+Color SvpSalGraphics::getPixel( long nX, long nY )
+{
+#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 12, 0)
+ cairo_surface_t *target = cairo_surface_create_similar_image(m_pSurface, CAIRO_FORMAT_ARGB32, 1, 1);
+#else
+ cairo_surface_t *target = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1);
+#endif
+
+ cairo_t* cr = cairo_create(target);
+
+ cairo_rectangle(cr, 0, 0, 1, 1);
+ cairo_set_source_surface(cr, m_pSurface, -nX, -nY);
+ cairo_paint(cr);
+ cairo_destroy(cr);
+
+ cairo_surface_flush(target);
+ vcl::bitmap::lookup_table 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(0xFF - a, r, g, b);
+ cairo_surface_destroy(target);
+
+ return aColor;
+}
+
+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;
+ }
+}
+
+void SvpSalGraphics::invert(const basegfx::B2DPolygon &rPoly, SalInvert nFlags)
+{
+ cairo_t* cr = getCairoContext(false);
+ clipRegion(cr);
+
+ // To make releaseCairoContext work, use empty extents
+ basegfx::B2DRange extents;
+
+ AddPolygonToPath(
+ cr,
+ rPoly,
+ basegfx::B2DHomMatrix(),
+ !getAntiAliasB2DDraw(),
+ false);
+
+ cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
+
+ if (cairo_version() >= CAIRO_VERSION_ENCODE(1, 10, 0))
+ {
+ cairo_set_operator(cr, CAIRO_OPERATOR_DIFFERENCE);
+ }
+ else
+ {
+ SAL_WARN("vcl.gdi", "SvpSalGraphics::invert, archaic cairo");
+ }
+
+ 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 SvpSalGraphics::invert( long nX, long nY, long nWidth, long nHeight, SalInvert nFlags )
+{
+ basegfx::B2DPolygon aRect = basegfx::utils::createPolygonFromRect(basegfx::B2DRectangle(nX, nY, nX+nWidth, nY+nHeight));
+
+ invert(aRect, nFlags);
+}
+
+void SvpSalGraphics::invert(sal_uInt32 nPoints, const SalPoint* pPtAry, SalInvert nFlags)
+{
+ basegfx::B2DPolygon aPoly;
+ aPoly.append(basegfx::B2DPoint(pPtAry->mnX, pPtAry->mnY), nPoints);
+ for (sal_uInt32 i = 1; i < nPoints; ++i)
+ aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].mnX, pPtAry[i].mnY));
+ aPoly.setClosed(true);
+
+ invert(aPoly, nFlags);
+}
+
+bool SvpSalGraphics::drawEPS( long, long, long, long, void*, sal_uInt32 )
+{
+ return false;
+}
+
+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* SvpSalGraphics::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;
+}
+
+cairo_t* SvpSalGraphics::createTmpCompatibleCairoContext() const
+{
+#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 12, 0)
+ cairo_surface_t *target = cairo_surface_create_similar_image(m_pSurface,
+#else
+ cairo_surface_t *target = cairo_image_surface_create(
+#endif
+ 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);
+}
+
+cairo_t* SvpSalGraphics::getCairoContext(bool bXorModeAllowed) 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, getAntiAliasB2DDraw() ? 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;
+}
+
+cairo_user_data_key_t* SvpSalGraphics::getDamageKey()
+{
+ static cairo_user_data_key_t aDamageKey;
+ return &aDamageKey;
+}
+
+void SvpSalGraphics::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)
+ {
+ 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
+ cairo_t* copycr = createTmpCompatibleCairoContext();
+ cairo_rectangle(copycr, nExtentsLeft, nExtentsTop,
+ nExtentsRight - nExtentsLeft,
+ nExtentsBottom - nExtentsTop);
+ cairo_set_source_surface(copycr, m_pSurface, 0, 0);
+ cairo_paint(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;
+ vcl::bitmap::lookup_table unpremultiply_table = vcl::bitmap::get_unpremultiply_table();
+ vcl::bitmap::lookup_table premultiply_table = vcl::bitmap::get_premultiply_table();
+ 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];
+ 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];
+ true_data+=4;
+ xor_data+=4;
+ }
+ }
+ cairo_surface_mark_dirty(target_surface);
+
+ if (target_surface != m_pSurface)
+ {
+ cairo_t* copycr = cairo_create(m_pSurface);
+ //unlikely case we couldn't use m_pSurface directly, 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_paint(copycr);
+ cairo_destroy(copycr);
+ cairo_surface_destroy(target_surface);
+ }
+
+ cairo_surface_destroy(surface);
+ }
+
+ 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);
+ }
+}
+
+#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
+{
+ return SystemGraphicsData();
+}
+
+bool SvpSalGraphics::supportsOperation(OutDevSupportType eType) const
+{
+ switch (eType)
+ {
+ case OutDevSupportType::TransparentRect:
+ case OutDevSupportType::B2DDraw:
+ return true;
+ }
+ return false;
+}
+
+void dl_cairo_surface_set_device_scale(cairo_surface_t *surface, double x_scale, double y_scale)
+{
+#ifdef ANDROID
+ cairo_surface_set_device_scale(surface, x_scale, y_scale);
+#else
+ static auto func = reinterpret_cast<void(*)(cairo_surface_t*, double, double)>(
+ dlsym(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)
+{
+#ifdef ANDROID
+ cairo_surface_get_device_scale(surface, x_scale, y_scale);
+#else
+ static auto func = reinterpret_cast<void(*)(cairo_surface_t*, double*, double*)>(
+ dlsym(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
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/headless/svpinst.cxx b/vcl/headless/svpinst.cxx
new file mode 100644
index 000000000..daaa4d170
--- /dev/null
+++ b/vcl/headless/svpinst.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 <config_features.h>
+#include <sal/config.h>
+
+#include <mutex>
+
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <sys/time.h>
+#include <sys/poll.h>
+
+#include <sal/types.h>
+#include <sal/log.hxx>
+
+#include <vcl/virdev.hxx>
+#include <vcl/inputtypes.hxx>
+#include <vcl/lok.hxx>
+#if HAVE_FEATURE_UI
+# include <vcl/opengl/OpenGLContext.hxx>
+#endif
+
+#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 <headless/svpgdi.hxx>
+#endif
+#include <headless/svpbmp.hxx>
+
+#include <salframe.hxx>
+#include <svdata.hxx>
+#include <unx/gendata.hxx>
+// FIXME: remove when we re-work the svp mainloop
+#include <unx/salunxtime.h>
+#include <comphelper/lok.hxx>
+
+SvpSalInstance* SvpSalInstance::s_pDefaultInstance = nullptr;
+
+#if !defined(ANDROID) && !defined(IOS)
+
+static void atfork_child()
+{
+ if (SvpSalInstance::s_pDefaultInstance != nullptr)
+ {
+ SvpSalInstance::s_pDefaultInstance->CloseWakeupPipe(false);
+ SvpSalInstance::s_pDefaultInstance->CreateWakeupPipe(false);
+ }
+}
+
+#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();
+ CreateWakeupPipe(true);
+ if( s_pDefaultInstance == nullptr )
+ s_pDefaultInstance = this;
+#if !defined(ANDROID) && !defined(IOS)
+ pthread_atfork(nullptr, nullptr, atfork_child);
+#endif
+}
+
+SvpSalInstance::~SvpSalInstance()
+{
+ if( s_pDefaultInstance == this )
+ s_pDefaultInstance = nullptr;
+ CloseWakeupPipe(true);
+}
+
+void SvpSalInstance::CloseWakeupPipe(bool log)
+{
+ SvpSalYieldMutex *const pMutex(dynamic_cast<SvpSalYieldMutex*>(GetYieldMutex()));
+ if (!pMutex)
+ return;
+ if (pMutex->m_FeedbackFDs[0] != -1)
+ {
+ if (log)
+ {
+ SAL_INFO("vcl.headless", "CloseWakeupPipe: Closing inherited feedback pipe: [" << pMutex->m_FeedbackFDs[0] << "," << pMutex->m_FeedbackFDs[1] << "]");
+ }
+ close (pMutex->m_FeedbackFDs[0]);
+ close (pMutex->m_FeedbackFDs[1]);
+ pMutex->m_FeedbackFDs[0] = pMutex->m_FeedbackFDs[1] = -1;
+ }
+}
+
+void SvpSalInstance::CreateWakeupPipe(bool log)
+{
+ SvpSalYieldMutex *const pMutex(dynamic_cast<SvpSalYieldMutex*>(GetYieldMutex()));
+ if (!pMutex)
+ return;
+ if (pipe (pMutex->m_FeedbackFDs) == -1)
+ {
+ if (log)
+ {
+ SAL_WARN("vcl.headless", "Could not create feedback pipe: " << strerror(errno));
+ std::abort();
+ }
+ }
+ else
+ {
+ if (log)
+ {
+ SAL_INFO("vcl.headless", "CreateWakeupPipe: Created feedback pipe: [" << pMutex->m_FeedbackFDs[0] << "," << pMutex->m_FeedbackFDs[1] << "]");
+ }
+
+ int flags;
+
+ // set close-on-exec descriptor flag.
+ if ((flags = fcntl (pMutex->m_FeedbackFDs[0], F_GETFD)) != -1)
+ {
+ flags |= FD_CLOEXEC;
+ (void) fcntl(pMutex->m_FeedbackFDs[0], F_SETFD, flags);
+ }
+ if ((flags = fcntl (pMutex->m_FeedbackFDs[1], F_GETFD)) != -1)
+ {
+ flags |= FD_CLOEXEC;
+ (void) fcntl(pMutex->m_FeedbackFDs[1], F_SETFD, flags);
+ }
+
+ // retain the default blocking I/O for feedback pipe
+ }
+}
+
+void SvpSalInstance::TriggerUserEventProcessing()
+{
+ Wakeup();
+}
+
+#ifndef NDEBUG
+static bool g_CheckedMutex = false;
+#endif
+
+void SvpSalInstance::Wakeup(SvpRequest const request)
+{
+#ifndef NDEBUG
+ if (!g_CheckedMutex)
+ {
+ assert(dynamic_cast<SvpSalYieldMutex*>(GetYieldMutex()) != nullptr
+ && "This SvpSalInstance function requires use of SvpSalYieldMutex");
+ g_CheckedMutex = true;
+ }
+#endif
+
+ 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* pGraphics,
+ long &nDX, long &nDY,
+ DeviceFormat eFormat,
+ const SystemGraphicsData* pGd)
+{
+ SvpSalGraphics *pSvpSalGraphics = dynamic_cast<SvpSalGraphics*>(pGraphics);
+ 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> pNew(new SvpSalVirtualDevice(eFormat, pSvpSalGraphics->getSurface(), pPreExistingTarget));
+ pNew->SetSize( nDX, nDY );
+ return pNew;
+}
+
+cairo_surface_t* get_underlying_cairo_surface(const VirtualDevice& rDevice)
+{
+ return static_cast<SvpSalVirtualDevice*>(rDevice.mpVirDev.get())->GetSurface();
+}
+
+#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 )
+{
+ 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();
+ }
+#ifndef NDEBUG
+ if (!g_CheckedMutex)
+ {
+ assert(dynamic_cast<SvpSalYieldMutex*>(GetYieldMutex()) != nullptr
+ && "This SvpSalInstance function requires use of SvpSalYieldMutex");
+ g_CheckedMutex = true;
+ }
+#endif
+ SvpSalYieldMutex *const pMutex(static_cast<SvpSalYieldMutex*>(GetYieldMutex()));
+ pMutex->m_NonMainWaitingYieldCond.set();
+}
+
+SvpSalYieldMutex::SvpSalYieldMutex()
+{
+#ifndef IOS
+ m_FeedbackFDs[0] = m_FeedbackFDs[1] = -1;
+#endif
+}
+
+SvpSalYieldMutex::~SvpSalYieldMutex()
+{
+}
+
+void SvpSalYieldMutex::doAcquire(sal_uInt32 const nLockCount)
+{
+ SvpSalInstance *const pInst = static_cast<SvpSalInstance *>(GetSalData()->m_pInstance);
+ 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;
+ write(m_FeedbackFDs[1], &bEvents, sizeof(bool));
+ }
+ }
+ while (true);
+ }
+ else
+ {
+ m_aMutex.acquire();
+ }
+ ++m_nCount;
+ SalYieldMutex::doAcquire(nLockCount - 1);
+}
+
+sal_uInt32 SvpSalYieldMutex::doRelease(bool const bUnlockAll)
+{
+ SvpSalInstance *const pInst = static_cast<SvpSalInstance *>(GetSalData()->m_pInstance);
+ 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(SvpRequest::NONE);
+ }
+ else
+ {
+ std::scoped_lock<std::mutex> g(m_WakeUpMainMutex);
+ m_wakeUpMain = true;
+ m_WakeUpMainCond.notify_one();
+ }
+ }
+ }
+ return nCount;
+}
+
+bool SvpSalYieldMutex::IsCurrentThread() const
+{
+ if (GetSalData()->m_pInstance->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::DoYield(bool bWait, bool bHandleAllCurrentEvents)
+{
+#ifndef NDEBUG
+ if (!g_CheckedMutex)
+ {
+ assert(dynamic_cast<SvpSalYieldMutex*>(GetYieldMutex()) != nullptr
+ && "This SvpSalInstance function requires use of SvpSalYieldMutex");
+ g_CheckedMutex = true;
+ }
+#endif
+
+ // first, process current user events
+ bool bEvent = DispatchUserEvents(bHandleAllCurrentEvents);
+ if (!bHandleAllCurrentEvents && bEvent)
+ return true;
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ bool bTimeout = CheckTimeout();
+ bool bSkipPoll = bEvent;
+ if (pSVData->mpPollCallback == nullptr)
+ bSkipPoll = bEvent || bTimeout;
+ // else - give the poll-callback visibility into waiting timeouts too.
+
+ SvpSalYieldMutex *const pMutex(static_cast<SvpSalYieldMutex*>(GetYieldMutex()));
+
+ if (IsMainThread())
+ {
+ // in kit case
+ if (bWait && !bSkipPoll)
+ {
+ sal_Int64 nTimeoutMicroS = 0;
+ 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
+
+ sal_uInt32 nAcquireCount = ReleaseYieldMutexAll();
+
+ if (pSVData->mpPollCallback)
+ {
+ // Poll for events from the LOK client.
+ if (nTimeoutMicroS < 0)
+ nTimeoutMicroS = 5000 * 1000;
+
+ // External poll.
+ if (pSVData->mpPollClosure != nullptr &&
+ pSVData->mpPollCallback(pSVData->mpPollClosure, nTimeoutMicroS) < 0)
+ pSVData->maAppData.mbAppQuit = true;
+ }
+ else
+ {
+ 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
+ }
+ AcquireYieldMutex( nAcquireCount );
+ }
+ else if (bSkipPoll)
+ {
+ pMutex->m_NonMainWaitingYieldCond.set(); // wake up other threads
+ }
+ }
+ else // !IsMainThread()
+ {
+ Wakeup(bHandleAllCurrentEvents
+ ? SvpRequest::MainThreadDispatchAllEvents
+ : SvpRequest::MainThreadDispatchOneEvent);
+
+ bool bDidWork(false);
+ // blocking read (for synchronisation)
+ auto const nRet = read(pMutex->m_FeedbackFDs[0], &bDidWork, sizeof(bool));
+ assert(nRet == 1); (void) nRet;
+ if (!bDidWork && bWait)
+ {
+ // block & release YieldMutex until the main thread does something
+ pMutex->m_NonMainWaitingYieldCond.reset();
+ sal_uInt32 nAcquireCount = ReleaseYieldMutexAll();
+ pMutex->m_NonMainWaitingYieldCond.wait();
+ AcquireYieldMutex( nAcquireCount );
+ }
+ }
+
+ return bSkipPoll;
+}
+
+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&)
+{
+}
+
+std::shared_ptr<vcl::BackendCapabilities> SvpSalInstance::GetBackendCapabilities()
+{
+ auto pBackendCapabilities = SalInstance::GetBackendCapabilities();
+ pBackendCapabilities->mbSupportsBitmap32 = true;
+ return pBackendCapabilities;
+}
+
+//obviously doesn't actually do anything, it's just a nonfunctional stub
+
+#if HAVE_FEATURE_UI
+
+namespace {
+
+class SvpOpenGLContext : public OpenGLContext
+{
+ GLWindow m_aGLWin;
+private:
+ virtual const GLWindow& getOpenGLWindow() const override { return m_aGLWin; }
+ virtual GLWindow& getModifiableOpenGLWindow() override { return m_aGLWin; }
+};
+
+}
+
+OpenGLContext* SvpSalInstance::CreateOpenGLContext()
+{
+ return new SvpOpenGLContext;
+}
+
+#else
+
+class SvpOpenGLContext
+{
+};
+
+OpenGLContext* SvpSalInstance::CreateOpenGLContext()
+{
+ return nullptr;
+}
+
+
+#endif
+
+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 000000000..aa8cd59de
--- /dev/null
+++ b/vcl/headless/svpprn.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/timer.hxx>
+#include <printerinfomanager.hxx>
+
+#include <jobset.h>
+#include <print.h>
+#include <salptype.hxx>
+#include <saldatabasic.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() )
+ aDir = OStringToOUString( OString( getenv( "HOME" ) ), 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
+ if( pJobSetup->GetDriverData() )
+ std::free( const_cast<sal_uInt8*>(pJobSetup->GetDriverData()) );
+
+ sal_uInt32 nBytes;
+ void* pBuffer = nullptr;
+ if( rData.getStreamBuffer( pBuffer, nBytes ) )
+ {
+ pJobSetup->SetDriverDataLen( nBytes );
+ pJobSetup->SetDriverData( static_cast<sal_uInt8*>(pBuffer) );
+ }
+ else
+ {
+ pJobSetup->SetDriverDataLen( 0 );
+ pJobSetup->SetDriverData( nullptr );
+ }
+}
+
+// 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;
+ pPrinter->m_aPrinterGfx.Init( pPrinter->m_aJobData );
+
+ 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 000000000..e4b625b36
--- /dev/null
+++ b/vcl/headless/svptext.cxx
@@ -0,0 +1,121 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <unotools/configmgr.hxx>
+#include <vcl/fontcharmap.hxx>
+#include <basegfx/range/b2ibox.hxx>
+#include <headless/svpgdi.hxx>
+#include <config_cairo_canvas.h>
+#include <impfontmetricdata.hxx>
+#include <sallayout.hxx>
+
+void SvpSalGraphics::SetFont(LogicalFontInstance* pIFSD, int nFallbackLevel)
+{
+ m_aTextRenderImpl.SetFont(pIFSD, nFallbackLevel);
+}
+
+void SvpSalGraphics::GetFontMetric( ImplFontMetricDataRef& 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( PhysicalFontCollection* pFontCollection )
+{
+ m_aTextRenderImpl.GetDevFontList(pFontCollection);
+}
+
+void SvpSalGraphics::ClearDevFontCache()
+{
+ m_aTextRenderImpl.ClearDevFontCache();
+}
+
+bool SvpSalGraphics::AddTempDevFont( PhysicalFontCollection* pFontCollection,
+ const OUString& rFileURL, const OUString& rFontName)
+{
+ return m_aTextRenderImpl.AddTempDevFont(pFontCollection, rFileURL, rFontName);
+}
+
+bool SvpSalGraphics::CreateFontSubset(
+ const OUString& rToFile,
+ const PhysicalFontFace* pFont,
+ const sal_GlyphId* pGlyphIds,
+ const sal_uInt8* pEncoding,
+ sal_Int32* pWidths,
+ int nGlyphCount,
+ FontSubsetInfo& rInfo)
+{
+ return m_aTextRenderImpl.CreateFontSubset(rToFile, pFont, pGlyphIds, pEncoding, pWidths, nGlyphCount, rInfo);
+}
+
+const void* SvpSalGraphics::GetEmbedFontData(const PhysicalFontFace* pFont, long* pDataLen)
+{
+ return m_aTextRenderImpl.GetEmbedFontData(pFont, pDataLen);
+}
+
+void SvpSalGraphics::FreeEmbedFontData( const void* pData, long nLen )
+{
+ m_aTextRenderImpl.FreeEmbedFontData(pData, nLen);
+}
+
+void SvpSalGraphics::GetGlyphWidths( const PhysicalFontFace* pFont,
+ bool bVertical,
+ std::vector< sal_Int32 >& rWidths,
+ Ucs2UIntMap& rUnicodeEnc )
+{
+ m_aTextRenderImpl.GetGlyphWidths(pFont, bVertical, rWidths, rUnicodeEnc);
+}
+
+std::unique_ptr<GenericSalLayout> SvpSalGraphics::GetTextLayout(int nFallbackLevel)
+{
+ if (utl::ConfigManager::IsFuzzing())
+ return nullptr;
+ 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);
+}
+
+#if ENABLE_CAIRO_CANVAS
+
+SystemFontData SvpSalGraphics::GetSysFontData( int nFallbacklevel ) const
+{
+ return m_aTextRenderImpl.GetSysFontData(nFallbacklevel);
+}
+
+#endif // ENABLE_CAIRO_CANVAS
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/headless/svpvd.cxx b/vcl/headless/svpvd.cxx
new file mode 100644
index 000000000..70ac5785e
--- /dev/null
+++ b/vcl/headless/svpvd.cxx
@@ -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 .
+ */
+
+#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(DeviceFormat eFormat, cairo_surface_t* pRefSurface, cairo_surface_t* pPreExistingTarget)
+ : m_eFormat(eFormat)
+ , 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 )
+{
+ m_aGraphics.erase(std::remove(m_aGraphics.begin(), m_aGraphics.end(), dynamic_cast<SvpSalGraphics*>(pGraphics)), m_aGraphics.end());
+ delete pGraphics;
+}
+
+bool SvpSalVirtualDevice::SetSize( long nNewDX, long nNewDY )
+{
+ return SetSizeUsingBuffer(nNewDX, nNewDY, nullptr);
+}
+
+void SvpSalVirtualDevice::CreateSurface(long nNewDX, long nNewDY, sal_uInt8 *const pBuffer)
+{
+ if (m_pSurface)
+ {
+ cairo_surface_destroy(m_pSurface);
+ }
+
+ if (m_eFormat == DeviceFormat::BITMASK)
+ {
+ m_pSurface = cairo_surface_create_similar(m_pRefSurface, CAIRO_CONTENT_ALPHA,
+ nNewDX, nNewDY);
+ }
+ else if (pBuffer)
+ {
+ double fXScale, fYScale;
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ // Force scaling of the painting
+ fXScale = fYScale = comphelper::LibreOfficeKit::getDPIScale();
+ }
+ else
+ {
+ dl_cairo_surface_get_device_scale(m_pRefSurface, &fXScale, &fYScale);
+ nNewDX *= fXScale;
+ nNewDY *= fYScale;
+ }
+
+ 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, fXScale, fYScale);
+ }
+ else
+ {
+ m_pSurface = cairo_surface_create_similar(m_pRefSurface, CAIRO_CONTENT_COLOR_ALPHA, nNewDX, nNewDY);
+ }
+}
+
+bool SvpSalVirtualDevice::SetSizeUsingBuffer( long nNewDX, long nNewDY,
+ sal_uInt8 *const pBuffer)
+{
+ 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)
+ 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 true;
+}
+
+long SvpSalVirtualDevice::GetWidth() const
+{
+ return m_pSurface ? m_aFrameSize.getX() : 0;
+}
+
+long SvpSalVirtualDevice::GetHeight() const
+{
+ return m_pSurface ? m_aFrameSize.getY() : 0;
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/BitmapColorizeFilter.hxx b/vcl/inc/BitmapColorizeFilter.hxx
new file mode 100644
index 000000000..93e4d92b2
--- /dev/null
+++ b/vcl/inc/BitmapColorizeFilter.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/.
+ *
+ */
+
+#ifndef INCLUDED_VCL_INC_BITMAPCOLORIZEFILTER_HXX
+#define INCLUDED_VCL_INC_BITMAPCOLORIZEFILTER_HXX
+
+#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;
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/BitmapDisabledImageFilter.hxx b/vcl/inc/BitmapDisabledImageFilter.hxx
new file mode 100644
index 000000000..0967cad6b
--- /dev/null
+++ b/vcl/inc/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_BITMAPDISABLEDIMAGEFILTER_HXX
+#define INCLUDED_VCL_INC_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/BitmapFastScaleFilter.hxx b/vcl/inc/BitmapFastScaleFilter.hxx
new file mode 100644
index 000000000..09fbdcaeb
--- /dev/null
+++ b/vcl/inc/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_BITMAPFASTSCALEFILTER_HXX
+#define VCL_INC_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/BitmapInterpolateScaleFilter.hxx b/vcl/inc/BitmapInterpolateScaleFilter.hxx
new file mode 100644
index 000000000..8cbae0601
--- /dev/null
+++ b/vcl/inc/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_BITMAPINTERPOLATESCALEFILTER_HXX
+#define VCL_INC_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/BitmapLightenFilter.hxx b/vcl/inc/BitmapLightenFilter.hxx
new file mode 100644
index 000000000..f30e0d5bb
--- /dev/null
+++ b/vcl/inc/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_BITMAPLIGHTENFILTER_HXX
+#define INCLUDED_VCL_INC_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/BitmapScaleConvolutionFilter.hxx b/vcl/inc/BitmapScaleConvolutionFilter.hxx
new file mode 100644
index 000000000..9bc1bd45c
--- /dev/null
+++ b/vcl/inc/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_BITMAPSCALECONVOLUTIONFILTER_HXX
+#define VCL_INC_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/BitmapScaleSuperFilter.hxx b/vcl/inc/BitmapScaleSuperFilter.hxx
new file mode 100644
index 000000000..47e48eb1a
--- /dev/null
+++ b/vcl/inc/BitmapScaleSuperFilter.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_BITMAPSCALESUPER_HXX
+#define INCLUDED_VCL_INC_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/BitmapSymmetryCheck.hxx b/vcl/inc/BitmapSymmetryCheck.hxx
new file mode 100644
index 000000000..faf058923
--- /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>
+#include <vcl/bitmapaccess.hxx>
+
+class VCL_DLLPUBLIC BitmapSymmetryCheck final
+{
+public:
+ BitmapSymmetryCheck();
+ ~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/ControlCacheKey.hxx b/vcl/inc/ControlCacheKey.hxx
new file mode 100644
index 000000000..9c8f467ee
--- /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 <boost/functional/hash.hpp>
+
+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::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;
+ boost::hash_combine(seed, aCache.mnType);
+ boost::hash_combine(seed, aCache.mnPart);
+ boost::hash_combine(seed, aCache.mnState);
+ boost::hash_combine(seed, aCache.maSize.Width());
+ boost::hash_combine(seed, aCache.maSize.Height());
+ return seed;
+ }
+};
+
+#endif // INCLUDED_VCL_INC_CONTROLCACHEKEY_HXX
diff --git a/vcl/inc/FileDefinitionWidgetDraw.hxx b/vcl/inc/FileDefinitionWidgetDraw.hxx
new file mode 100644
index 000000000..68f5d3489
--- /dev/null
+++ b/vcl/inc/FileDefinitionWidgetDraw.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/.
+ *
+ */
+
+#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, long nX, long nY, long nWidth,
+ 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;
+};
+
+} // end vcl namespace
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/IconThemeScanner.hxx b/vcl/inc/IconThemeScanner.hxx
new file mode 100644
index 000000000..3cbca74a4
--- /dev/null
+++ b/vcl/inc/IconThemeScanner.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/.
+ */
+
+#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(const OUString &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(const OUString &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 000000000..0fcd66d1f
--- /dev/null
+++ b/vcl/inc/IconThemeSelector.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/.
+ */
+
+#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);
+
+ void
+ 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 theme which is used as fallback */
+ static const OUStringLiteral FALLBACK_ICON_THEME_ID;
+
+
+ static OUString
+ GetIconThemeForDesktopEnvironment(const OUString& desktopEnvironment);
+
+ 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/OptionalBox.hxx b/vcl/inc/OptionalBox.hxx
new file mode 100644
index 000000000..326fc7536
--- /dev/null
+++ b/vcl/inc/OptionalBox.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_OPTIONALBOX_HXX
+#define INCLUDED_VCL_OPTIONALBOX_HXX
+
+#include <vcl/IPrioritable.hxx>
+#include <vcl/layout.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;
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/PhysicalFontCollection.hxx b/vcl/inc/PhysicalFontCollection.hxx
new file mode 100644
index 000000000..87b94fe2c
--- /dev/null
+++ b/vcl/inc/PhysicalFontCollection.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_PHYSICALFONTCOLLECTION_HXX
+#define INCLUDED_VCL_INC_PHYSICALFONTCOLLECTION_HXX
+
+#include <vcl/dllapi.h>
+
+#include "fontinstance.hxx"
+#include "PhysicalFontFamily.hxx"
+#include <array>
+
+#define MAX_GLYPHFALLBACK 16
+
+class ImplDeviceFontSizeList;
+class ImplGlyphFallbackFontSubstitution;
+class ImplPreMatchFontSubstitution;
+
+
+// TODO: merge with ImplFontCache
+// TODO: rename to LogicalFontManager
+
+class VCL_PLUGIN_PUBLIC PhysicalFontCollection final
+{
+public:
+ explicit PhysicalFontCollection();
+ ~PhysicalFontCollection();
+
+ // fill the list with device font faces
+ void Add( PhysicalFontFace* );
+ void Clear();
+ int Count() const { return maPhysicalFontFamilies.size(); }
+
+ // find the device font family
+ PhysicalFontFamily* FindFontFamily( const OUString& rFontName ) const;
+ PhysicalFontFamily* FindOrCreateFontFamily( const OUString &rFamilyName );
+ PhysicalFontFamily* FindFontFamily( FontSelectPattern& ) const;
+ PhysicalFontFamily* FindFontFamilyByTokenNames(const OUString& rTokenStr) const;
+ PhysicalFontFamily* FindFontFamilyByAttributes(ImplFontAttrs nSearchType, FontWeight, FontWidth,
+ FontItalic, const OUString& rSearchFamily) const;
+
+ // suggest fonts for glyph fallback
+ PhysicalFontFamily* GetGlyphFallbackFont( FontSelectPattern&,
+ LogicalFontInstance* pLogicalFont,
+ OUString& rMissingCodes, int nFallbackLevel ) const;
+
+ // prepare platform specific font substitutions
+ void SetPreMatchHook( ImplPreMatchFontSubstitution* );
+ void SetFallbackHook( ImplGlyphFallbackFontSubstitution* );
+
+ // misc utilities
+ std::shared_ptr<PhysicalFontCollection> Clone() const;
+ std::unique_ptr<ImplDeviceFontList> GetDeviceFontList() const;
+ std::unique_ptr<ImplDeviceFontSizeList> GetDeviceFontSizeList( const OUString& rFontName ) const;
+
+private:
+ mutable bool mbMatchData; // true if matching attributes are initialized
+
+ typedef std::unordered_map<OUString, std::unique_ptr<PhysicalFontFamily>> PhysicalFontFamilies;
+ PhysicalFontFamilies maPhysicalFontFamilies;
+
+ ImplPreMatchFontSubstitution* mpPreMatchHook; // device specific prematch substitution
+ ImplGlyphFallbackFontSubstitution* mpFallbackHook; // device specific glyph fallback substitution
+
+ mutable std::unique_ptr<std::array<PhysicalFontFamily*,MAX_GLYPHFALLBACK>> mpFallbackList;
+ mutable int mnFallbackCount;
+
+ void ImplInitMatchData() const;
+ void ImplInitGenericGlyphFallback() const;
+
+ PhysicalFontFamily* ImplFindFontFamilyBySearchName( const OUString& ) const;
+ PhysicalFontFamily* ImplFindFontFamilyBySubstFontAttr( const utl::FontNameAttr& ) const;
+
+ PhysicalFontFamily* ImplFindFontFamilyOfDefaultFont() const;
+
+};
+
+#endif // INCLUDED_VCL_INC_PHYSICALFONTCOLLECTION_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/PhysicalFontFace.hxx b/vcl/inc/PhysicalFontFace.hxx
new file mode 100644
index 000000000..23af5be91
--- /dev/null
+++ b/vcl/inc/PhysicalFontFace.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 INCLUDED_VCL_INC_PHYSICALFONTFACE_HXX
+#define INCLUDED_VCL_INC_PHYSICALFONTFACE_HXX
+
+#include <salhelper/simplereferenceobject.hxx>
+#include <rtl/ref.hxx>
+#include <vcl/dllapi.h>
+
+#include "fontattributes.hxx"
+
+class LogicalFontInstance;
+struct FontMatchStatus;
+class FontSelectPattern;
+class PhysicalFontFamily;
+
+struct FontMatchStatus
+{
+public:
+ int mnFaceMatch;
+ int mnHeightMatch;
+ int mnWidthMatch;
+ const OUString* mpTargetStyleName;
+};
+
+
+// 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:
+ virtual rtl::Reference<LogicalFontInstance> CreateFontInstance(const FontSelectPattern&) const = 0;
+
+ int GetHeight() const { return mnHeight; }
+ int GetWidth() const { return mnWidth; }
+ virtual sal_IntPtr GetFontId() const = 0;
+
+ bool IsBetterMatch( const FontSelectPattern&, FontMatchStatus& ) const;
+ sal_Int32 CompareWithSize( const PhysicalFontFace& ) const;
+ sal_Int32 CompareIgnoreSize( const PhysicalFontFace& ) const;
+
+protected:
+ explicit PhysicalFontFace(const FontAttributes&);
+ void SetBitmapSize( int nW, int nH ) { mnWidth=nW; mnHeight=nH; }
+
+ long mnWidth; // Width (in pixels)
+ long mnHeight; // Height (in pixels)
+};
+
+#endif // INCLUDED_VCL_INC_PHYSICALFONTFACE_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
+
diff --git a/vcl/inc/PhysicalFontFamily.hxx b/vcl/inc/PhysicalFontFamily.hxx
new file mode 100644
index 000000000..b8468a6e4
--- /dev/null
+++ b/vcl/inc/PhysicalFontFamily.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_PHYSICALFONTFAMILY_HXX
+#define INCLUDED_VCL_INC_PHYSICALFONTFAMILY_HXX
+
+#include <vcl/dllapi.h>
+#include <vcl/outdev.hxx>
+
+#include <set>
+
+#include <unotools/fontcfg.hxx>
+
+#include "PhysicalFontFace.hxx"
+
+class ImplDeviceFontList;
+class PhysicalFontFace;
+class PhysicalFontCollection;
+
+// 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<FontTypeFaces> : is_typed_flags<FontTypeFaces, 0xff> {};
+};
+
+class VCL_PLUGIN_PUBLIC PhysicalFontFamily
+{
+public:
+ PhysicalFontFamily( const OUString& rSearchName );
+ ~PhysicalFontFamily();
+
+ const OUString& GetFamilyName() const { return maFamilyName; }
+ const OUString& GetSearchName() const { return maSearchName; }
+ const OUString& GetAliasNames() const { return maMapNames; }
+ int GetMinQuality() const { return mnMinQuality; }
+ FontTypeFaces GetTypeFaces() const { return mnTypeFaces; }
+ void GetFontHeights( std::set<int>& rHeights ) const;
+
+ 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 FontSelectPattern& rFSD ) const;
+
+ void UpdateDevFontList( ImplDeviceFontList& ) 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
+ OUString maMapNames; // fontname aliases
+ 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
+};
+
+#endif // INCLUDED_VCL_INC_PHYSICALFONTFAMILY_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/ResampleKernel.hxx b/vcl/inc/ResampleKernel.hxx
new file mode 100644
index 000000000..ca54213f5
--- /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 000000000..1b4a47f9a
--- /dev/null
+++ b/vcl/inc/SalGradient.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 INCLUDED_VCL_INC_SALGRADIENT_HXX
+#define INCLUDED_VCL_INC_SALGRADIENT_HXX
+
+#include <basegfx/point/b2dpoint.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/TypeSerializer.hxx b/vcl/inc/TypeSerializer.hxx
new file mode 100644
index 000000000..060876593
--- /dev/null
+++ b/vcl/inc/TypeSerializer.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_TYPESERIALIZER_HXX
+#define INCLUDED_VCL_INC_TYPESERIALIZER_HXX
+
+#include <vcl/dllapi.h>
+#include <tools/GenericTypeSerializer.hxx>
+#include <vcl/gradient.hxx>
+#include <vcl/gfxlink.hxx>
+#include <vcl/graph.hxx>
+
+constexpr sal_uInt32 createMagic(char char1, char char2, char char3, char char4)
+{
+ return (static_cast<sal_uInt32>(char1) << 24) | (static_cast<sal_uInt32>(char2) << 16)
+ | (static_cast<sal_uInt32>(char3) << 8) | (static_cast<sal_uInt32>(char4) << 0);
+}
+
+class VCL_DLLPUBLIC TypeSerializer : public tools::GenericTypeSerializer
+{
+public:
+ TypeSerializer(SvStream& rStream);
+
+ void readGradient(Gradient& rGradient);
+ void writeGradient(const Gradient& rGradient);
+
+ void readGfxLink(GfxLink& rGfxLink);
+ void writeGfxLink(const GfxLink& rGfxLink);
+
+ void readGraphic(Graphic& rGraphic);
+ void writeGraphic(const Graphic& rGraphic);
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/WidgetDrawInterface.hxx b/vcl/inc/WidgetDrawInterface.hxx
new file mode 100644
index 000000000..78d5d7625
--- /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/WidgetThemeLibrary.hxx b/vcl/inc/WidgetThemeLibrary.hxx
new file mode 100644
index 000000000..ace74a49a
--- /dev/null
+++ b/vcl/inc/WidgetThemeLibrary.hxx
@@ -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/.
+ *
+ */
+
+#ifndef INCLUDED_VCL_INC_WIDGETTHEME_HXX
+#define INCLUDED_VCL_INC_WIDGETTHEME_HXX
+
+#include <cstdint>
+
+namespace vcl
+{
+struct WidgetDrawStyle
+{
+ uint32_t nSize;
+ uint32_t maFaceColor;
+ uint32_t maCheckedColor;
+ uint32_t maLightColor;
+ uint32_t maLightBorderColor;
+ uint32_t maShadowColor;
+ uint32_t maDarkShadowColor;
+ uint32_t maDefaultButtonTextColor;
+ uint32_t maButtonTextColor;
+ uint32_t maDefaultActionButtonTextColor;
+ uint32_t maActionButtonTextColor;
+ uint32_t maFlatButtonTextColor;
+ uint32_t maDefaultButtonRolloverTextColor;
+ uint32_t maButtonRolloverTextColor;
+ uint32_t maDefaultActionButtonRolloverTextColor;
+ uint32_t maActionButtonRolloverTextColor;
+ uint32_t maFlatButtonRolloverTextColor;
+ uint32_t maDefaultButtonPressedRolloverTextColor;
+ uint32_t maButtonPressedRolloverTextColor;
+ uint32_t maDefaultActionButtonPressedRolloverTextColor;
+ uint32_t maActionButtonPressedRolloverTextColor;
+ uint32_t maFlatButtonPressedRolloverTextColor;
+ uint32_t maRadioCheckTextColor;
+ uint32_t maGroupTextColor;
+ uint32_t maLabelTextColor;
+ uint32_t maWindowColor;
+ uint32_t maWindowTextColor;
+ uint32_t maDialogColor;
+ uint32_t maDialogTextColor;
+ uint32_t maWorkspaceColor;
+ uint32_t maMonoColor;
+ uint32_t maFieldColor;
+ uint32_t maFieldTextColor;
+ uint32_t maFieldRolloverTextColor;
+ uint32_t maActiveColor;
+ uint32_t maActiveTextColor;
+ uint32_t maActiveBorderColor;
+ uint32_t maDeactiveColor;
+ uint32_t maDeactiveTextColor;
+ uint32_t maDeactiveBorderColor;
+ uint32_t maMenuColor;
+ uint32_t maMenuBarColor;
+ uint32_t maMenuBarRolloverColor;
+ uint32_t maMenuBorderColor;
+ uint32_t maMenuTextColor;
+ uint32_t maMenuBarTextColor;
+ uint32_t maMenuBarRolloverTextColor;
+ uint32_t maMenuBarHighlightTextColor;
+ uint32_t maMenuHighlightColor;
+ uint32_t maMenuHighlightTextColor;
+ uint32_t maHighlightColor;
+ uint32_t maHighlightTextColor;
+ uint32_t maActiveTabColor;
+ uint32_t maInactiveTabColor;
+ uint32_t maTabTextColor;
+ uint32_t maTabRolloverTextColor;
+ uint32_t maTabHighlightTextColor;
+ uint32_t maDisableColor;
+ uint32_t maHelpColor;
+ uint32_t maHelpTextColor;
+ uint32_t maLinkColor;
+ uint32_t maVisitedLinkColor;
+ uint32_t maToolTextColor;
+ uint32_t maFontColor;
+};
+
+struct ControlDrawParameters
+{
+ typedef struct _cairo cairo_t;
+ ControlDrawParameters(cairo_t* i_pCairo, ControlPart i_ePart, ControlState i_eState)
+ : nSize(sizeof(ControlDrawParameters))
+ , pCairo(i_pCairo)
+ , ePart(i_ePart)
+ , eState(i_eState)
+ , eButtonValue(ButtonValue::DontKnow)
+ , bIsAction(false)
+ , nValue(0)
+ {
+ }
+
+ uint32_t nSize;
+ cairo_t* pCairo;
+ ControlPart ePart;
+ ControlState eState;
+ ButtonValue eButtonValue;
+ bool bIsAction;
+ int64_t nValue;
+};
+
+typedef struct WidgetThemeLibrary_t WidgetThemeLibrary;
+
+typedef struct _rectangle
+{
+ long x, y;
+ long width, height;
+} rectangle_t;
+
+struct WidgetThemeLibrary_t
+{
+ uint32_t nSize;
+
+ bool (*isNativeControlSupported)(ControlType eType, ControlPart ePart);
+ bool (*getRegion)(ControlType eType, ControlPart ePart, ControlState eState,
+ const rectangle_t& rBoundingControlRegion, rectangle_t& rNativeBoundingRegion,
+ rectangle_t& rNativeContentRegion);
+
+ bool (*drawPushButton)(ControlDrawParameters const& rParameters, long nWidth, long nHeight);
+ bool (*drawRadiobutton)(ControlDrawParameters const& rParameters, long nWidth, long nHeight);
+ bool (*drawCheckbox)(ControlDrawParameters const& rParameters, long nWidth, long nHeight);
+ bool (*drawCombobox)(ControlDrawParameters const& rParameters, long nWidth, long nHeight);
+ bool (*drawEditbox)(ControlDrawParameters const& rParameters, long nWidth, long nHeight);
+ bool (*drawScrollbar)(ControlDrawParameters const& rParameters, long nWidth, long nHeight);
+ bool (*drawSpinButtons)(ControlDrawParameters const& rParameters, long nWidth, long nHeight);
+ bool (*drawSpinbox)(ControlDrawParameters const& rParameters, long nWidth, long nHeight);
+ bool (*drawTabItem)(ControlDrawParameters const& rParameters, long nWidth, long nHeight);
+ bool (*drawTabPane)(ControlDrawParameters const& rParameters, long nWidth, long nHeight);
+ bool (*drawTabHeader)(ControlDrawParameters const& rParameters, long nWidth, long nHeight);
+ bool (*drawTabBody)(ControlDrawParameters const& rParameters, long nWidth, long nHeight);
+ bool (*drawSlider)(ControlDrawParameters const& rParameters, long nWidth, long nHeight);
+ bool (*drawFixedline)(ControlDrawParameters const& rParameters, long nWidth, long nHeight);
+ bool (*drawToolbar)(ControlDrawParameters const& rParameters, long nWidth, long nHeight);
+ bool (*drawProgress)(ControlDrawParameters const& rParameters, long nWidth, long nHeight);
+ bool (*drawWindowsBackground)(ControlDrawParameters const& rParameters, long nWidth,
+ long nHeight);
+ bool (*drawListbox)(ControlDrawParameters const& rParameters, long nWidth, long nHeight);
+ bool (*drawFrame)(ControlDrawParameters const& rParameters, long nWidth, long nHeight);
+ bool (*drawListNode)(ControlDrawParameters const& rParameters, long nWidth, long nHeight);
+ bool (*drawListNet)(ControlDrawParameters const& rParameters, long nWidth, long nHeight);
+ bool (*drawListHeader)(ControlDrawParameters const& rParameters, long nWidth, long nHeight);
+
+ bool (*updateSettings)(WidgetDrawStyle& rStyle);
+};
+
+extern "C" vcl::WidgetThemeLibrary* CreateWidgetThemeLibrary();
+
+} // end vcl namespace
+
+#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 000000000..b3270bf23
--- /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.h b/vcl/inc/accel.h
new file mode 100644
index 000000000..0c27a5411
--- /dev/null
+++ b/vcl/inc/accel.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_ACCEL_H
+#define INCLUDED_VCL_INC_ACCEL_H
+
+#include <vcl/keycod.hxx>
+
+class Accelerator;
+
+class ImplAccelEntry
+{
+public:
+ sal_uInt16 mnId;
+ vcl::KeyCode maKeyCode;
+ Accelerator* mpAccel;
+ Accelerator* mpAutoAccel;
+ bool mbEnabled;
+};
+
+bool ImplGetKeyCode( KeyFuncType eFunc, sal_uInt16& rCode1, sal_uInt16& rCode2, sal_uInt16& rCode3, sal_uInt16& rCode4 );
+
+#endif // INCLUDED_VCL_INC_ACCEL_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/accmgr.hxx b/vcl/inc/accmgr.hxx
new file mode 100644
index 000000000..1e57ed44d
--- /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 <memory>
+
+#include <vcl/keycod.hxx>
+
+class Accelerator;
+
+class ImplAccelManager
+{
+private:
+ std::unique_ptr<std::vector< Accelerator* >> mpAccelList;
+ std::unique_ptr<std::vector< Accelerator* >> mpSequenceList;
+
+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 000000000..526174b14
--- /dev/null
+++ b/vcl/inc/android/androidinst.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_ANDROID_ANDROIDINST_HXX
+#define INCLUDED_VCL_INC_ANDROID_ANDROIDINST_HXX
+
+#include <jni.h>
+#include <android/input.h>
+#include <android/native_window.h>
+#include <headless/svpinst.hxx>
+#include <headless/svpframe.hxx>
+
+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( tools::Rectangle& 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();
+};
+
+#endif // INCLUDED_VCL_INC_ANDROID_ANDROIDINST_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/android/svsys.h b/vcl/inc/android/svsys.h
new file mode 100644
index 000000000..6939e5066
--- /dev/null
+++ b/vcl/inc/android/svsys.h
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_ANDROID_SVSYS_H
+#define INCLUDED_VCL_INC_ANDROID_SVSYS_H
+
+// ?
+
+#endif // INCLUDED_VCL_INC_ANDROID_SVSYS_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/backend/BackendCapabilities.hxx b/vcl/inc/backend/BackendCapabilities.hxx
new file mode 100644
index 000000000..89879ee49
--- /dev/null
+++ b/vcl/inc/backend/BackendCapabilities.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/.
+ *
+ */
+
+#ifndef INCLUDED_VCL_INC_BACKENDCAPABILITIES_HXX
+#define INCLUDED_VCL_INC_BACKENDCAPABILITIES_HXX
+
+namespace vcl
+{
+struct BackendCapabilities
+{
+ bool mbSupportsBitmap32;
+ BackendCapabilities()
+ : mbSupportsBitmap32(false)
+ {
+ }
+};
+}
+
+#endif // INCLUDED_VCL_INC_BACKENDCAPABILITIES_HXX
+
+/* 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 000000000..f1d6e2a58
--- /dev/null
+++ b/vcl/inc/bitmap/Octree.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_OCTREE_HXX
+#define INCLUDED_VCL_INC_OCTREE_HXX
+
+#include <vcl/dllapi.h>
+#include <vcl/BitmapColor.hxx>
+
+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);
+
+ SAL_DLLPRIVATE void add(std::unique_ptr<OctreeNode>& rpNode);
+ SAL_DLLPRIVATE void reduce();
+
+ BitmapPalette maPalette;
+ sal_uLong mnLeafCount;
+ sal_uLong mnLevel;
+ std::unique_ptr<OctreeNode> pTree;
+ std::vector<OctreeNode*> mpReduce;
+ BitmapColor const* mpColor;
+ 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 000000000..9528f4809
--- /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(pData[4], pData[1], pData[2], pData[3]);
+ pData += 4;
+ return aColor;
+ }
+
+ virtual void writePixel(Color nColor) override
+ {
+ *pData++ = nColor.GetTransparency();
+ *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/impoctree.hxx b/vcl/inc/bitmap/impoctree.hxx
new file mode 100644
index 000000000..06fbd6924
--- /dev/null
+++ b/vcl/inc/bitmap/impoctree.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_IMPOCTREE_HXX
+#define INCLUDED_VCL_INC_IMPOCTREE_HXX
+
+#include "Octree.hxx"
+
+class ImpErrorQuad
+{
+ long nRed;
+ long nGreen;
+ long nBlue;
+
+public:
+ ImpErrorQuad()
+ : nRed(0)
+ , nGreen(0)
+ , nBlue(0)
+ {
+ }
+
+ ImpErrorQuad(const BitmapColor& rColor)
+ : nRed(long(rColor.GetRed()) << 5)
+ , nGreen(long(rColor.GetGreen()) << 5)
+ , nBlue(long(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();
+};
+
+inline void ImpErrorQuad::operator=(const BitmapColor& rColor)
+{
+ nRed = long(rColor.GetRed()) << 5;
+ nGreen = long(rColor.GetGreen()) << 5;
+ nBlue = long(rColor.GetBlue()) << 5;
+}
+
+inline ImpErrorQuad& ImpErrorQuad::operator-=(const BitmapColor& rColor)
+{
+ nRed -= long(rColor.GetRed()) << 5;
+ nGreen -= long(rColor.GetGreen()) << 5;
+ nBlue -= long(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()
+{
+ return BitmapColor(std::clamp(nRed, 0L, 8160L) >> 5, std::clamp(nGreen, 0L, 8160L) >> 5,
+ std::clamp(nBlue, 0L, 8160L) >> 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 000000000..68f23533e
--- /dev/null
+++ b/vcl/inc/bitmaps.hlst
@@ -0,0 +1,228 @@
+/* -*- 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_BITMAPS_HRC
+#define INCLUDED_VCL_INC_BITMAPS_HRC
+
+#define SV_RESID_BITMAP_CHECK1 "vcl/res/check1.png"
+#define SV_RESID_BITMAP_CHECK2 "vcl/res/check2.png"
+#define SV_RESID_BITMAP_CHECK3 "vcl/res/check3.png"
+#define SV_RESID_BITMAP_CHECK4 "vcl/res/check4.png"
+#define SV_RESID_BITMAP_CHECK5 "vcl/res/check5.png"
+#define SV_RESID_BITMAP_CHECK6 "vcl/res/check6.png"
+#define SV_RESID_BITMAP_CHECK7 "vcl/res/check7.png"
+#define SV_RESID_BITMAP_CHECK8 "vcl/res/check8.png"
+#define SV_RESID_BITMAP_CHECK9 "vcl/res/check9.png"
+#define SV_RESID_BITMAP_CHECKMONO1 "vcl/res/checkmono1.png"
+#define SV_RESID_BITMAP_CHECKMONO2 "vcl/res/checkmono2.png"
+#define SV_RESID_BITMAP_CHECKMONO3 "vcl/res/checkmono3.png"
+#define SV_RESID_BITMAP_CHECKMONO4 "vcl/res/checkmono4.png"
+#define SV_RESID_BITMAP_CHECKMONO5 "vcl/res/checkmono5.png"
+#define SV_RESID_BITMAP_CHECKMONO6 "vcl/res/checkmono6.png"
+#define SV_RESID_BITMAP_CHECKMONO7 "vcl/res/checkmono7.png"
+#define SV_RESID_BITMAP_CHECKMONO8 "vcl/res/checkmono8.png"
+#define SV_RESID_BITMAP_CHECKMONO9 "vcl/res/checkmono9.png"
+#define SV_RESID_BITMAP_RADIO1 "vcl/res/radio1.png"
+#define SV_RESID_BITMAP_RADIO2 "vcl/res/radio2.png"
+#define SV_RESID_BITMAP_RADIO3 "vcl/res/radio3.png"
+#define SV_RESID_BITMAP_RADIO4 "vcl/res/radio4.png"
+#define SV_RESID_BITMAP_RADIO5 "vcl/res/radio5.png"
+#define SV_RESID_BITMAP_RADIO6 "vcl/res/radio6.png"
+#define SV_RESID_BITMAP_RADIOMONO1 "vcl/res/radiomono1.png"
+#define SV_RESID_BITMAP_RADIOMONO2 "vcl/res/radiomono2.png"
+#define SV_RESID_BITMAP_RADIOMONO3 "vcl/res/radiomono3.png"
+#define SV_RESID_BITMAP_RADIOMONO4 "vcl/res/radiomono4.png"
+#define SV_RESID_BITMAP_RADIOMONO5 "vcl/res/radiomono5.png"
+#define SV_RESID_BITMAP_RADIOMONO6 "vcl/res/radiomono6.png"
+
+#define SV_RESID_BITMAP_ERRORBOX "vcl/res/errorbox.png"
+#define SV_RESID_BITMAP_QUERYBOX "vcl/res/querybox.png"
+#define SV_RESID_BITMAP_WARNINGBOX "vcl/res/warningbox.png"
+#define SV_RESID_BITMAP_INFOBOX "vcl/res/infobox.png"
+
+#define SV_RESID_BITMAP_SCROLLMSK "vcl/res/scrmsk.png"
+#define SV_RESID_BITMAP_WHEELVH "vcl/res/wheelvh.png"
+#define SV_RESID_BITMAP_WHEELV "vcl/res/wheelv.png"
+#define SV_RESID_BITMAP_WHEELH "vcl/res/wheelh.png"
+#define SV_RESID_BITMAP_SCROLLVH "vcl/res/scrollvh.png"
+#define SV_RESID_BITMAP_SCROLLV "vcl/res/scrollv.png"
+#define SV_RESID_BITMAP_SCROLLH "vcl/res/scrollh.png"
+#define SV_RESID_BITMAP_CLOSEDOC "vcl/res/closedoc.png"
+#define SV_RESID_BITMAP_INDEX "vcl/res/index.png"
+#define SV_RESID_BITMAP_REFRESH "res/reload.png"
+#define SV_RESID_BITMAP_NOTEBOOKBAR "res/notebookbar.png"
+
+#define SV_DISCLOSURE_PLUS "res/plus.png"
+#define SV_DISCLOSURE_MINUS "res/minus.png"
+
+#define SV_PRINT_COLLATE_BMP "vcl/res/collate.png"
+#define SV_PRINT_NOCOLLATE_BMP "vcl/res/ncollate.png"
+
+#define MAINAPP_48_8 "res/mainapp_48_8.png"
+#define ODT_48_8 "res/odt_48_8.png"
+#define OTT_48_8 "res/ott_48_8.png"
+#define ODS_48_8 "res/ods_48_8.png"
+#define OTS_48_8 "res/ots_48_8.png"
+#define ODG_48_8 "res/odg_48_8.png"
+#define ODP_48_8 "res/odp_48_8.png"
+#define ODM_48_8 "res/odm_48_8.png"
+#define ODB_48_8 "res/odb_48_8.png"
+#define ODF_48_8 "res/odf_48_8.png"
+
+#define MAINAPP_32_8 "res/mainapp_32_8.png"
+#define ODT_32_8 "res/odt_32_8.png"
+#define OTT_32_8 "res/ott_32_8.png"
+#define ODS_32_8 "res/ods_32_8.png"
+#define OTS_32_8 "res/ots_32_8.png"
+#define ODG_32_8 "res/odg_32_8.png"
+#define ODP_32_8 "res/odp_32_8.png"
+#define ODM_32_8 "res/odm_32_8.png"
+#define ODB_32_8 "res/odb_32_8.png"
+#define ODF_32_8 "res/odf_32_8.png"
+
+#define MAINAPP_16_8 "res/mainapp_16_8.png"
+#define ODT_16_8 "res/odt_16_8.png"
+#define OTT_16_8 "res/ott_16_8.png"
+#define ODS_16_8 "res/ods_16_8.png"
+#define OTS_16_8 "res/ots_16_8.png"
+#define ODG_16_8 "res/odg_16_8.png"
+#define ODP_16_8 "res/odp_16_8.png"
+#define ODM_16_8 "res/odm_16_8.png"
+#define ODB_16_8 "res/odb_16_8.png"
+#define ODF_16_8 "res/odf_16_8.png"
+
+//start, Throbber::getDefaultImageURLs
+#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
+
+#define IMG_APPLY "sw/res/sc20558.png"
+#define IMG_WARN "dbaccess/res/exwarning.png"
+#define IMG_ERROR "dbaccess/res/exerror.png"
+#define IMG_INFO "dbaccess/res/exinfo.png"
+#define IMG_ADD "extensions/res/scanner/plus.png"
+#define IMG_REMOVE "extensions/res/scanner/minus.png"
+#define IMG_COPY "cmd/sc_copy.png"
+#define IMG_PASTE "cmd/sc_paste.png"
+
+#define RID_BMP_TREENODE_COLLAPSED "res/plus.png"
+#define RID_BMP_TREENODE_EXPANDED "res/minus.png"
+
+#define RID_CURSOR_AUTOSCROLL_E "vcl/res/autoscroll_e.png"
+#define RID_CURSOR_AUTOSCROLL_N "vcl/res/autoscroll_n.png"
+#define RID_CURSOR_AUTOSCROLL_NE "vcl/res/autoscroll_ne.png"
+#define RID_CURSOR_AUTOSCROLL_NS "vcl/res/autoscroll_ns.png"
+#define RID_CURSOR_AUTOSCROLL_NSWE "vcl/res/autoscroll_nswe.png"
+#define RID_CURSOR_AUTOSCROLL_NW "vcl/res/autoscroll_nw.png"
+#define RID_CURSOR_AUTOSCROLL_S "vcl/res/autoscroll_s.png"
+#define RID_CURSOR_AUTOSCROLL_SE "vcl/res/autoscroll_se.png"
+#define RID_CURSOR_AUTOSCROLL_SW "vcl/res/autoscroll_sw.png"
+#define RID_CURSOR_AUTOSCROLL_W "vcl/res/autoscroll_w.png"
+#define RID_CURSOR_AUTOSCROLL_WE "vcl/res/autoscroll_we.png"
+#define RID_CURSOR_CHAIN "vcl/res/chain.png"
+#define RID_CURSOR_CHAIN_NOT_ALLOWED "vcl/res/chain_not_allowed.png"
+#define RID_CURSOR_CHART "vcl/res/chart.png"
+#define RID_CURSOR_COPY_DATA "vcl/res/copy_data.png"
+#define RID_CURSOR_COPY_DATA_LINK "vcl/res/copy_data_link.png"
+#define RID_CURSOR_COPY_FILE "vcl/res/copy_file.png"
+#define RID_CURSOR_COPY_FILES "vcl/res/copy_files.png"
+#define RID_CURSOR_COPY_FILE_LINK "vcl/res/copy_file_link.png"
+#define RID_CURSOR_CROOK "vcl/res/crook.png"
+#define RID_CURSOR_CROP "vcl/res/crop.png"
+#define RID_CURSOR_DRAW_ARC "vcl/res/draw_arc.png"
+#define RID_CURSOR_DRAW_BEZIER "vcl/res/draw_bezier.png"
+#define RID_CURSOR_DRAW_CAPTION "vcl/res/draw_caption.png"
+#define RID_CURSOR_DRAW_CIRCLE_CUT "vcl/res/draw_circle_cut.png"
+#define RID_CURSOR_DRAW_CONNECT "vcl/res/draw_connect.png"
+#define RID_CURSOR_DRAW_ELLIPSE "vcl/res/draw_ellipse.png"
+#define RID_CURSOR_DETECTIVE "vcl/res/detective.png"
+#define RID_CURSOR_DRAW_FREEHAND "vcl/res/draw_freehand.png"
+#define RID_CURSOR_DRAW_LINE "vcl/res/draw_line.png"
+#define RID_CURSOR_DRAW_PIE "vcl/res/draw_pie.png"
+#define RID_CURSOR_DRAW_POLYGON "vcl/res/draw_polygon.png"
+#define RID_CURSOR_DRAW_RECT "vcl/res/draw_rect.png"
+#define RID_CURSOR_DRAW_TEXT "vcl/res/draw_text.png"
+#define RID_CURSOR_FILL "vcl/res/fill.png"
+#define RID_CURSOR_HELP "vcl/res/help.png"
+#define RID_CURSOR_H_SHEAR "vcl/res/h_shear.png"
+#define RID_CURSOR_LINK_DATA "vcl/res/link_data.png"
+#define RID_CURSOR_LINK_FILE "vcl/res/link_file.png"
+#define RID_CURSOR_MAGNIFY "vcl/res/magnify.png"
+#define RID_CURSOR_MIRROR "vcl/res/mirror.png"
+#define RID_CURSOR_MOVE_BEZIER_WEIGHT "vcl/res/move_bezier_weight.png"
+#define RID_CURSOR_MOVE_DATA "vcl/res/move_data.png"
+#define RID_CURSOR_MOVE_DATA_LINK "vcl/res/move_data_link.png"
+#define RID_CURSOR_MOVE_FILE "vcl/res/move_file.png"
+#define RID_CURSOR_MOVE_FILES "vcl/res/move_files.png"
+#define RID_CURSOR_MOVE_FILE_LINK "vcl/res/move_file_link.png"
+#define RID_CURSOR_MOVE_POINT "vcl/res/move_point.png"
+#define RID_CURSOR_NESWSIZE "vcl/res/neswsize.png"
+#define RID_CURSOR_NOT_ALLOWED "vcl/res/not_allowed.png"
+#define RID_CURSOR_NULL "vcl/res/null.png"
+#define RID_CURSOR_NWSESIZE "vcl/res/nwsesize.png"
+#define RID_CURSOR_PEN "vcl/res/pen.png"
+#define RID_CURSOR_PIVOT_COLUMN "vcl/res/pivot_column.png"
+#define RID_CURSOR_PIVOT_DELETE "vcl/res/pivot_delete.png"
+#define RID_CURSOR_PIVOT_FIELD "vcl/res/pivot_field.png"
+#define RID_CURSOR_PIVOT_ROW "vcl/res/pivot_row.png"
+#define RID_CURSOR_ROTATE "vcl/res/rotate.png"
+#define RID_CURSOR_TAB_SELECT_E "vcl/res/tab_select_e.png"
+#define RID_CURSOR_TAB_SELECT_S "vcl/res/tab_select_s.png"
+#define RID_CURSOR_TAB_SELECT_SE "vcl/res/tab_select_se.png"
+#define RID_CURSOR_TAB_SELECT_SW "vcl/res/tab_select_sw.png"
+#define RID_CURSOR_TAB_SELECT_W "vcl/res/tab_select_w.png"
+#define RID_CURSOR_V_SHEAR "vcl/res/v_shear.png"
+#define RID_CURSOR_TEXT_VERTICAL "vcl/res/text_vertical.png"
+#define RID_CURSOR_HIDE_WHITESPACE "vcl/res/hide_whitespace.png"
+#define RID_CURSOR_SHOW_WHITESPACE "vcl/res/show_whitespace.png"
+#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"
+
+#define CHEVRON "sfx2/res/chevron.png"
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/inc/bitmapwriteaccess.hxx b/vcl/inc/bitmapwriteaccess.hxx
new file mode 100644
index 000000000..f747223ec
--- /dev/null
+++ b/vcl/inc/bitmapwriteaccess.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/.
+ *
+ */
+
+#ifndef INCLUDED_VCL_INC_BITMAPWRITEACCESS_HXX
+#define INCLUDED_VCL_INC_BITMAPWRITEACCESS_HXX
+
+#include <vcl/alpha.hxx>
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapaccess.hxx>
+#include <optional>
+
+typedef vcl::ScopedBitmapAccess<BitmapWriteAccess, Bitmap, &Bitmap::AcquireWriteAccess>
+ BitmapScopedWriteAccess;
+
+typedef vcl::ScopedBitmapAccess<BitmapWriteAccess, AlphaMask, &AlphaMask::AcquireAlphaWriteAccess>
+ AlphaScopedWriteAccess;
+
+class VCL_DLLPUBLIC BitmapWriteAccess : public BitmapReadAccess
+{
+public:
+ BitmapWriteAccess(Bitmap& rBitmap);
+ virtual ~BitmapWriteAccess() override;
+
+ void CopyScanline(long nY, const BitmapReadAccess& rReadAcc);
+ void CopyScanline(long nY, ConstScanline aSrcScanline, ScanlineFormat nSrcScanlineFormat,
+ sal_uInt32 nSrcScanlineSize);
+
+ void SetPalette(const BitmapPalette& rPalette)
+ {
+ assert(mpBuffer && "Access is not valid!");
+
+ mpBuffer->maPalette = rPalette;
+ }
+
+ void SetPaletteEntryCount(sal_uInt16 nCount)
+ {
+ assert(mpBuffer && "Access is not valid!");
+
+ mpBuffer->maPalette.SetEntryCount(nCount);
+ }
+
+ void SetPaletteColor(sal_uInt16 nColor, const BitmapColor& rBitmapColor)
+ {
+ assert(mpBuffer && "Access is not valid!");
+ assert(HasPalette() && "Bitmap has no palette!");
+
+ mpBuffer->maPalette[nColor] = rBitmapColor;
+ }
+
+ void SetPixel(long nY, long nX, const BitmapColor& rBitmapColor)
+ {
+ assert(mpBuffer && "Access is not valid!");
+ assert(nX < mpBuffer->mnWidth && "x-coordinate out of range!");
+ assert(nY < mpBuffer->mnHeight && "y-coordinate out of range!");
+
+ mFncSetPixel(GetScanline(nY), nX, rBitmapColor, maColorMask);
+ }
+
+ void SetPixelIndex(long nY, long nX, sal_uInt8 cIndex)
+ {
+ SetPixel(nY, nX, BitmapColor(cIndex));
+ }
+
+ void SetLineColor(const Color& rColor);
+
+ void SetFillColor();
+ void SetFillColor(const Color& rColor);
+
+ void Erase(const Color& rColor);
+
+ void DrawLine(const Point& rStart, const Point& rEnd);
+
+ void FillRect(const tools::Rectangle& rRect);
+ void DrawRect(const tools::Rectangle& rRect);
+
+private:
+ std::optional<BitmapColor> mpLineColor;
+ std::optional<BitmapColor> mpFillColor;
+
+ BitmapWriteAccess() = delete;
+ BitmapWriteAccess(const BitmapWriteAccess&) = delete;
+ BitmapWriteAccess& operator=(const BitmapWriteAccess&) = delete;
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/bmpfast.hxx b/vcl/inc/bmpfast.hxx
new file mode 100644
index 000000000..f9a1f891b
--- /dev/null
+++ b/vcl/inc/bmpfast.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_BMPFAST_HXX
+#define INCLUDED_VCL_INC_BMPFAST_HXX
+
+#include <vcl/dllapi.h>
+
+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 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/brdwin.hxx b/vcl/inc/brdwin.hxx
new file mode 100644
index 000000000..1dedce0e2
--- /dev/null
+++ b/vcl/inc/brdwin.hxx
@@ -0,0 +1,294 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_VCL_INC_BRDWIN_HXX
+#define INCLUDED_VCL_INC_BRDWIN_HXX
+
+#include <vcl/notebookbar.hxx>
+#include <vcl/window.hxx>
+#include <o3tl/typed_flags_set.hxx>
+#include <vcl/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,
+ Roll = 0x0800,
+ Dock = 0x1000,
+ Hide = 0x2000,
+ Help = 0x4000,
+};
+namespace o3tl {
+ template<> struct typed_flags<BorderWindowHitTest> : is_typed_flags<BorderWindowHitTest, 0x7fff> {};
+};
+
+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 : public vcl::Window
+{
+ friend class vcl::Window;
+ friend class ImplBorderWindowView;
+ friend class ImplSmallBorderWindowView;
+ friend class ImplStdBorderWindowView;
+
+private:
+ std::unique_ptr<ImplBorderWindowView> mpBorderView;
+ VclPtr<vcl::Window> mpMenuBarWindow;
+ VclPtr<NotebookBar> mpNotebookBar;
+ long mnMinWidth;
+ long mnMinHeight;
+ long mnMaxWidth;
+ long mnMaxHeight;
+ long mnOrgMenuHeight;
+ BorderWindowTitleType mnTitleType;
+ WindowBorderStyle mnBorderStyle;
+ bool mbFloatWindow;
+ bool mbSmallOutBorder;
+ bool mbFrameBorder;
+ bool mbRollUp;
+ 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 SetRollUp( bool bRollUp, const Size& rSize );
+ 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( long nWidth, long nHeight )
+ { mnMinWidth = nWidth; mnMinHeight = nHeight; }
+ void SetMaxOutputSize( long nWidth, long nHeight )
+ { mnMaxWidth = nWidth; mnMaxHeight = nHeight; }
+
+ void GetBorder( sal_Int32& rLeftBorder, sal_Int32& rTopBorder,
+ sal_Int32& rRightBorder, sal_Int32& rBottomBorder ) const;
+ 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 maRollRect;
+ tools::Rectangle maDockRect;
+ tools::Rectangle maMenuRect;
+ tools::Rectangle maHideRect;
+ tools::Rectangle maHelpRect;
+ Point maMouseOff;
+ long mnWidth;
+ long mnHeight;
+ long mnTrackX;
+ long mnTrackY;
+ long mnTrackWidth;
+ long mnTrackHeight;
+ sal_Int32 mnLeftBorder;
+ sal_Int32 mnTopBorder;
+ sal_Int32 mnRightBorder;
+ sal_Int32 mnBottomBorder;
+ long mnNoTitleTop;
+ long mnBorderSize;
+ long mnTitleHeight;
+ BorderWindowHitTest mnHitTest;
+ DrawButtonFlags mnCloseState;
+ DrawButtonFlags mnRollState;
+ 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, long nWidth, long nHeight ) = 0;
+ virtual void GetBorder( sal_Int32& rLeftBorder, sal_Int32& rTopBorder,
+ sal_Int32& rRightBorder, sal_Int32& rBottomBorder ) const = 0;
+ virtual 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 long ImplCalcTitleWidth( const ImplBorderFrameData* pData );
+};
+
+class ImplNoBorderWindowView final : public ImplBorderWindowView
+{
+public:
+ ImplNoBorderWindowView();
+
+ virtual void Init( OutputDevice* pDev, long nWidth, long nHeight ) override;
+ virtual void GetBorder( sal_Int32& rLeftBorder, sal_Int32& rTopBorder,
+ sal_Int32& rRightBorder, sal_Int32& rBottomBorder ) const override;
+ virtual long CalcTitleWidth() const override;
+ virtual void DrawWindow(vcl::RenderContext& rRenderContext, const Point* pOffset = nullptr) override;
+};
+
+class ImplSmallBorderWindowView : public ImplBorderWindowView
+{
+ VclPtr<ImplBorderWindow> mpBorderWindow;
+ VclPtr<OutputDevice> mpOutDev;
+ long mnWidth;
+ 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, long nWidth, long nHeight ) override;
+ virtual void GetBorder( sal_Int32& rLeftBorder, sal_Int32& rTopBorder,
+ sal_Int32& rRightBorder, sal_Int32& rBottomBorder ) const override;
+ virtual long CalcTitleWidth() const override;
+ virtual void DrawWindow(vcl::RenderContext& rRenderContext, const Point* pOffset = nullptr) override;
+};
+
+class ImplStdBorderWindowView : 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, long nWidth, long nHeight ) override;
+ virtual void GetBorder( sal_Int32& rLeftBorder, sal_Int32& rTopBorder,
+ sal_Int32& rRightBorder, sal_Int32& rBottomBorder ) const override;
+ virtual 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/canvasbitmap.hxx b/vcl/inc/canvasbitmap.hxx
new file mode 100644
index 000000000..e49f3f43a
--- /dev/null
+++ b/vcl/inc/canvasbitmap.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 .
+ */
+
+#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>
+
+namespace vcl
+{
+namespace 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;
+ Bitmap::ScopedReadAccess m_pBmpAcc;
+ Bitmap::ScopedReadAccess m_pAlphaAcc;
+ 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;
+ sal_Int8 m_nEndianness;
+ bool m_bPalette;
+
+ SAL_DLLPRIVATE void setComponentInfo( sal_uInt32 redShift, sal_uInt32 greenShift, sal_uInt32 blueShift );
+
+ 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 000000000..ea6cf6574
--- /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/controldata.hxx b/vcl/inc/controldata.hxx
new file mode 100644
index 000000000..ed6e3a368
--- /dev/null
+++ b/vcl/inc/controldata.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_CONTROLDATA_HXX
+#define INCLUDED_VCL_INC_CONTROLDATA_HXX
+
+#include <memory>
+#include <vcl/toolkit/controllayout.hxx>
+
+namespace vcl
+{
+ struct ImplControlData
+ {
+ mutable std::unique_ptr<ControlLayoutData> mpLayoutData;
+ VclPtr<OutputDevice> mpReferenceDevice;
+ };
+
+} // namespace vcl
+
+#endif // INCLUDED_VCL_INC_CONTROLDATA_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 000000000..74a041ddd
--- /dev/null
+++ b/vcl/inc/cursor_hotspots.hxx
@@ -0,0 +1,169 @@
+/* -*- 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
+
+#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 000000000..b3d93c5e1
--- /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 000000000..a6f458265
--- /dev/null
+++ b/vcl/inc/debugevent.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 INCLUDED_VCL_DEBUGEVENT_HXX
+#define INCLUDED_VCL_DEBUGEVENT_HXX
+
+#include <vcl/dllapi.h>
+#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 000000000..e988ee62f
--- /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 <osl/mutex.hxx>
+#include <com/sun/star/uno/Reference.hxx>
+#include <vector>
+
+namespace vcl {
+
+class DisplayConnectionDispatch final :
+ public cppu::WeakImplHelper< css::awt::XDisplayConnection >
+{
+ ::osl::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 000000000..bc2f5d999
--- /dev/null
+++ b/vcl/inc/dndeventdispatcher.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_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>
+
+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);
+
+ ::osl::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/dndlistenercontainer.hxx b/vcl/inc/dndlistenercontainer.hxx
new file mode 100644
index 000000000..28339e7ab
--- /dev/null
+++ b/vcl/inc/dndlistenercontainer.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_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 <cppuhelper/compbase.hxx>
+
+#include <vcl/unohelp2.hxx>
+
+class DNDListenerContainer final : public vcl::unohelper::MutexHelper,
+ public ::cppu::WeakComponentImplHelper<
+ css::datatransfer::dnd::XDragGestureRecognizer,
+ css::datatransfer::dnd::XDropTargetDragContext,
+ css::datatransfer::dnd::XDropTargetDropContext,
+ css::datatransfer::dnd::XDropTarget >
+{
+ bool m_bActive;
+ sal_Int8 m_nDefaultActions;
+
+ 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/driverblocklist.hxx b/vcl/inc/driverblocklist.hxx
new file mode 100644
index 000000000..c3feea278
--- /dev/null
+++ b/vcl/inc/driverblocklist.hxx
@@ -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/.
+ */
+
+#ifndef INCLUDED_VCL_DRIVERBLOCKLIST_HXX
+#define INCLUDED_VCL_DRIVERBLOCKLIST_HXX
+
+#include <vcl/dllapi.h>
+#include <xmlreader/xmlreader.hxx>
+#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,
+ const OUString& driverVersion, const OUString& 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 OUStringLiteral 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, const OUString& vendor, VersionComparisonOp op,
+ uint64_t driverVersion, bool bWhiteListed = false,
+ const char* suggestedVersion = nullptr);
+
+ DriverInfo();
+
+ OperatingSystem meOperatingSystem;
+ OUString maAdapterVendor;
+ std::vector<OUString> maDevices;
+
+ bool mbWhitelisted;
+
+ VersionComparisonOp meComparisonOp;
+
+ /* versions are assumed to be A.B.C.D packed as 0xAAAABBBBCCCCDDDD */
+ uint64_t mnDriverVersion;
+ uint64_t mnDriverVersionMax;
+ static uint64_t allDriverVersions;
+
+ OUString maSuggestedVersion;
+ OUString maMsg;
+};
+
+class VCL_DLLPUBLIC Parser
+{
+public:
+ Parser(const OUString& rURL, 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(const OString& rString);
+
+ enum class BlockType
+ {
+ WHITELIST,
+ BLACKLIST,
+ 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, OUString const& sDriverVersion,
+ OUString const& 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 000000000..54a382aa4
--- /dev/null
+++ b/vcl/inc/factory.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_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; }
+}
+
+css::uno::Sequence<OUString> vcl_session_getSupportedServiceNames();
+
+OUString vcl_session_getImplementationName();
+
+css::uno::Reference<css::uno::XInterface> vcl_session_createInstance(
+ css::uno::Reference<css::lang::XMultiServiceFactory > const &);
+
+namespace vcl {
+
+css::uno::Sequence<OUString>
+FontIdentificator_getSupportedServiceNames();
+
+OUString FontIdentificator_getImplementationName();
+
+css::uno::Reference<css::uno::XInterface>
+FontIdentificator_createInstance(
+ css::uno::Reference<css::lang::XMultiServiceFactory > const &);
+
+OUString Clipboard_getImplementationName();
+
+css::uno::Reference<css::lang::XSingleServiceFactory>
+Clipboard_createFactory();
+
+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/fltcall.hxx b/vcl/inc/fltcall.hxx
new file mode 100644
index 000000000..f10e72b07
--- /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/FeatureCollector.hxx b/vcl/inc/font/FeatureCollector.hxx
new file mode 100644
index 000000000..bae9e3c56
--- /dev/null
+++ b/vcl/inc/font/FeatureCollector.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/.
+ *
+ */
+
+#ifndef INCLUDED_VCL_INC_FONT_FEATURECOLLECTOR_HXX
+#define INCLUDED_VCL_INC_FONT_FEATURECOLLECTOR_HXX
+
+#include <vcl/font/Feature.hxx>
+#include <hb.h>
+#include <i18nlangtag/lang.h>
+
+namespace vcl
+{
+namespace font
+{
+class FeatureCollector
+{
+private:
+ hb_face_t* m_pHbFace;
+ std::vector<vcl::font::Feature>& m_rFontFeatures;
+ LanguageType m_eLanguageType;
+
+public:
+ FeatureCollector(hb_face_t* pHbFace, std::vector<vcl::font::Feature>& rFontFeatures,
+ LanguageType eLanguageType)
+ : m_pHbFace(pHbFace)
+ , m_rFontFeatures(rFontFeatures)
+ , m_eLanguageType(eLanguageType)
+ {
+ }
+
+private:
+ void collectForLanguage(hb_tag_t aTableTag, sal_uInt32 nScript, hb_tag_t aScriptTag,
+ sal_uInt32 nLanguage, hb_tag_t aLanguageTag);
+
+ void collectForScript(hb_tag_t aTableTag, sal_uInt32 nScript, hb_tag_t aScriptTag);
+ void collectForTable(hb_tag_t aTableTag);
+ bool collectGraphite();
+
+public:
+ bool collect();
+};
+
+} // end namespace font
+} // end namespace vcl
+
+#endif // INCLUDED_VCL_INC_FONT_FEATURECOLLECTOR_HXX
+
+/* 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 000000000..3a01d9135
--- /dev/null
+++ b/vcl/inc/font/OpenTypeFeatureDefinitionList.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 <vcl/dllapi.h>
+#include <vcl/font/Feature.hxx>
+#include <rtl/instance.hxx>
+#include <vector>
+#include <unordered_map>
+
+namespace vcl
+{
+namespace 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();
+
+ static bool isSpecialFeatureCode(sal_uInt32 nFeatureCode);
+ static FeatureDefinition handleSpecialFeatureCode(sal_uInt32 nFeatureCode);
+
+public:
+ OpenTypeFeatureDefinitionListPrivate();
+ FeatureDefinition getDefinition(sal_uInt32 nFeatureCode);
+ bool isRequired(sal_uInt32 nFeatureCode);
+};
+
+class VCL_DLLPUBLIC OpenTypeFeatureDefinitionList
+ : public rtl::Static<OpenTypeFeatureDefinitionListPrivate, OpenTypeFeatureDefinitionList>
+{
+};
+
+} // end font namespace
+} // end vcl namespace
+
+/* 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 000000000..9fa782b16
--- /dev/null
+++ b/vcl/inc/font/OpenTypeFeatureStrings.hrc
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_FONT_OPENTYPEFEATRESTRINGS_HRC
+#define INCLUDED_VCL_INC_FONT_OPENTYPEFEATRESTRINGS_HRC
+
+#define NC_(Context, String) reinterpret_cast<char const *>(Context "\004" 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_FRAC_PARAM_0 NC_("STR_FONT_FEATURE_ID_FRAC_PARAM_0", "None")
+#define STR_FONT_FEATURE_ID_FRAC_PARAM_1 NC_("STR_FONT_FEATURE_ID_FRAC_PARAM_1", "Diagonal Fractions")
+#define STR_FONT_FEATURE_ID_FRAC_PARAM_2 NC_("STR_FONT_FEATURE_ID_FRAC_PARAM_2", "Nut Fractions")
+#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")
+
+#endif // INCLUDED_VCL_INC_STRINGS_HRC
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/fontattributes.hxx b/vcl/inc/fontattributes.hxx
new file mode 100644
index 000000000..352ea10e3
--- /dev/null
+++ b/vcl/inc/fontattributes.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_FONTATTRIBUTES_HXX
+#define INCLUDED_VCL_INC_FONTATTRIBUTES_HXX
+
+#include <vcl/dllapi.h>
+#include <rtl/ustring.hxx>
+#include <sal/log.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; }
+ rtl_TextEncoding GetCharSet() const { return meCharSet; }
+
+ bool IsSymbolFont() const { return mbSymbolFlag; }
+
+ 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 SetSymbolFlag(const bool );
+
+ bool CompareDeviceIndependentFontAttributes(const FontAttributes& rOther) const;
+
+ // Device dependent functions
+ int GetQuality() const { return mnQuality; }
+ const OUString& GetMapNames() const { return maMapNames; }
+
+
+ void SetQuality( int nQuality ) { mnQuality = nQuality; }
+ void IncreaseQualityBy( int nQualityAmount ) { mnQuality += nQualityAmount; }
+ void AddMapName( OUString const& );
+
+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
+ rtl_TextEncoding meCharSet; // RTL_TEXTENCODING_SYMBOL or RTL_TEXTENCODING_UNICODE
+ bool mbSymbolFlag; // Is font a symbol?
+
+ // device dependent variables
+ OUString maMapNames; // List of family name aliases separated with ';'
+ int mnQuality; // Quality (used when similar fonts compete)
+
+};
+
+inline void FontAttributes::SetSymbolFlag( const bool bSymbolFlag )
+{
+ mbSymbolFlag = bSymbolFlag;
+ if ( bSymbolFlag )
+ {
+ meCharSet = RTL_TEXTENCODING_SYMBOL;
+ }
+ else
+ {
+ // if the symbol flag is unset, but it was a symbol font before then
+ // until the character set encoding is set via SetCharSet then we
+ // can't know what the characterset is!
+ if ( meCharSet == RTL_TEXTENCODING_SYMBOL )
+ {
+ meCharSet = RTL_TEXTENCODING_DONTKNOW;
+ }
+ }
+}
+
+inline void FontAttributes::AddMapName( OUString const & aMapName )
+{
+ if( maMapNames.getLength() > 0 )
+ {
+ maMapNames += ";";
+ }
+
+ if (aMapName.getLength() == 0)
+ {
+ SAL_WARN("vcl.fonts", "New map name is empty");
+ return;
+ }
+
+ maMapNames += aMapName;
+}
+
+#endif // INCLUDED_VCL_INC_FONTATTRIBUTES_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/fontinstance.hxx b/vcl/inc/fontinstance.hxx
new file mode 100644
index 000000000..4aff50c95
--- /dev/null
+++ b/vcl/inc/fontinstance.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_FONTINSTANCE_HXX
+#define INCLUDED_VCL_INC_FONTINSTANCE_HXX
+
+#include "fontselect.hxx"
+#include "impfontmetricdata.hxx"
+
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <rtl/ref.hxx>
+#include <salhelper/simplereferenceobject.hxx>
+#include <tools/gen.hxx>
+#include <tools/fontenum.hxx>
+#include <vcl/glyphitem.hxx>
+
+#include <optional>
+#include <unordered_map>
+#include <memory>
+
+#include <hb.h>
+
+class ConvertChar;
+class ImplFontCache;
+class PhysicalFontFace;
+
+// 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 PhysicalFontFace;
+ friend class ImplFontCache;
+
+public: // TODO: make data members private
+ virtual ~LogicalFontInstance() override;
+
+ ImplFontMetricDataRef mxFontMetric; // Font attributes
+ const ConvertChar* mpConversion; // used e.g. for StarBats->StarSymbol
+
+ long mnLineHeight;
+ short mnOwnOrientation; // text angle if lower layers don't rotate text themselves
+ short mnOrientation; // text angle in 3600 system
+ bool mbInit; // true if maFontMetric member is valid
+
+ void AddFallbackForUnicode( sal_UCS4, FontWeight eWeight, const OUString& rFontName );
+ bool GetFallbackForUnicode( sal_UCS4, FontWeight eWeight, OUString* pFontName ) const;
+ void IgnoreFallbackForUnicode( sal_UCS4, FontWeight eWeight, const OUString& rFontName );
+
+ inline hb_font_t* GetHbFont();
+ bool IsGraphiteFont();
+ void SetAverageWidthFactor(double nFactor) { m_nAveWidthFactor = std::abs(nFactor); }
+ double GetAverageWidthFactor() const { return m_nAveWidthFactor; }
+ const FontSelectPattern& GetFontSelectPattern() const { return m_aFontSelData; }
+
+ const PhysicalFontFace* GetFontFace() const { return m_pFontFace.get(); }
+ 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;
+
+ int GetKashidaWidth();
+
+ void GetScale(double* nXScale, double* nYScale);
+ static inline void DecodeOpenTypeTag(const uint32_t nTableTag, char* pTagName);
+
+protected:
+ explicit LogicalFontInstance(const PhysicalFontFace&, const FontSelectPattern&);
+
+ virtual bool ImplGetGlyphBoundRect(sal_GlyphId, tools::Rectangle&, bool) const = 0;
+
+ // Takes ownership of pHbFace.
+ static hb_font_t* InitHbFont(hb_face_t* pHbFace);
+ virtual hb_font_t* ImplInitHbFont() { assert(false); return hb_font_get_empty(); }
+
+private:
+ // cache of Unicode characters and replacement font names
+ // 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>, OUString > UnicodeFallbackList;
+ std::unique_ptr<UnicodeFallbackList> mpUnicodeFallbackList;
+ mutable ImplFontCache * mpFontCache;
+ const FontSelectPattern m_aFontSelData;
+ hb_font_t* m_pHbFont;
+ double m_nAveWidthFactor;
+ rtl::Reference<PhysicalFontFace> m_pFontFace;
+ std::optional<bool> m_xbIsGraphiteFont;
+};
+
+inline hb_font_t* LogicalFontInstance::GetHbFont()
+{
+ if (!m_pHbFont)
+ m_pHbFont = ImplInitHbFont();
+ return m_pHbFont;
+}
+
+inline void LogicalFontInstance::DecodeOpenTypeTag(const uint32_t nTableTag, char* pTagName)
+{
+ pTagName[0] = static_cast<char>(nTableTag >> 24);
+ pTagName[1] = static_cast<char>(nTableTag >> 16);
+ pTagName[2] = static_cast<char>(nTableTag >> 8);
+ pTagName[3] = static_cast<char>(nTableTag);
+ pTagName[4] = 0;
+}
+
+#endif // INCLUDED_VCL_INC_FONTINSTANCE_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/fontselect.hxx b/vcl/inc/fontselect.hxx
new file mode 100644
index 000000000..7df7dd930
--- /dev/null
+++ b/vcl/inc/fontselect.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_FONTSELECT_HXX
+#define INCLUDED_VCL_INC_FONTSELECT_HXX
+
+#include <i18nlangtag/lang.h>
+#include <vcl/vclenum.hxx>
+
+#include "fontattributes.hxx"
+
+#include <ostream>
+
+namespace vcl { class Font; }
+
+class LogicalFontInstance;
+class PhysicalFontFace;
+class Size;
+
+class VCL_DLLPUBLIC FontSelectPattern : public FontAttributes
+{
+public:
+ FontSelectPattern(const vcl::Font&, const OUString& rSearchName,
+ 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)
+ int 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 FontSelectPattern & rFSP)
+{
+ stream << (rFSP.maTargetName.isEmpty() ? "<default>" : rFSP.maTargetName)
+ << " (" << rFSP.maSearchName << ") w: " << rFSP.mnWidth << " h: "
+ << rFSP.mnHeight << " alias: " << rFSP.mbNonAntialiased;
+ return stream;
+}
+
+#endif // INCLUDED_VCL_INC_FONTSELECT_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 000000000..54fa20946
--- /dev/null
+++ b/vcl/inc/fontsubset.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_FONTSUBSET_HXX
+#define INCLUDED_VCL_INC_FONTSUBSET_HXX
+
+#include <rtl/ustring.hxx>
+#include <tools/gen.hxx>
+#include <o3tl/typed_flags_set.hxx>
+
+#include <vcl/dllapi.h>
+#include <vcl/glyphitem.hxx>
+
+namespace vcl { struct TrueTypeFont; } ///< SFT's idea of a TTF font
+
+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
+ TYPE3_FONT = 1<<6, ///< PSType3 Postscript font
+ TYPE42_FONT = 1<<7, ///< PSType42 wrapper for an SFNT_TTF
+ 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 );
+ void LoadFont( vcl::TrueTypeFont* pSftTrueTypeFont );
+
+ bool CreateFontSubset( FontType nOutFontTypeMask,
+ FILE* pOutFile, const char* pOutFontName,
+ const sal_GlyphId* pGlyphIds, const sal_uInt8* pEncodedIds,
+ int nReqGlyphCount, sal_Int32* pOutGlyphWidths = nullptr );
+
+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
+
+private:
+ // input-font-specific details
+ unsigned const char* mpInFontBytes;
+ int mnInByteLength;
+ FontType meInFontType; ///< allowed mask of input font-types
+ vcl::TrueTypeFont* mpSftTTFont;
+
+ // subset-request details
+ FontType mnReqFontTypeMask; ///< allowed subset-target font types
+ FILE* mpOutFile;
+ const char* mpReqFontName;
+ const sal_GlyphId* mpReqGlyphIds;
+ const sal_uInt8* mpReqEncodedIds;
+ int mnReqGlyphCount;
+
+ bool CreateFontSubsetFromCff( sal_Int32* pOutGlyphWidths );
+ bool CreateFontSubsetFromSfnt( sal_Int32* pOutGlyphWidths );
+ static bool CreateFontSubsetFromType1( const sal_Int32* pOutGlyphWidths );
+};
+
+#endif // INCLUDED_VCL_INC_FONTSUBSET_HXX
+
+/* 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 000000000..3847457fc
--- /dev/null
+++ b/vcl/inc/graphic/DetectorTools.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/.
+ *
+ */
+
+#pragma once
+
+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 000000000..e914ee64a
--- /dev/null
+++ b/vcl/inc/graphic/GraphicFormatDetector.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_GRAPHICFORMATDETECTOR_HXX
+#define INCLUDED_VCL_INC_GRAPHICFORMATDETECTOR_HXX
+
+#include <tools/stream.hxx>
+#include <vector>
+
+VCL_DLLPUBLIC bool ImpPeekGraphicFormat(SvStream& rStream, OUString& rFormatExtension, bool bTest);
+
+namespace vcl
+{
+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;
+
+ OUString msDetectedFormat;
+
+ GraphicFormatDetector(SvStream& rStream, OUString const& rFormatExtension);
+
+ bool detect();
+
+ bool checkMET();
+ bool checkBMP();
+ bool checkWMForEMF();
+ bool checkPCX();
+ bool checkTIF();
+ bool checkGIF();
+ bool checkPNG();
+ bool checkJPG();
+ bool checkSVM();
+ bool checkPCD();
+ bool checkPSD();
+ bool checkEPS();
+ bool checkDXF();
+ bool checkPCT();
+ bool checkPBMorPGMorPPM();
+ bool checkRAS();
+ bool checkXPM();
+ bool checkXBM();
+ bool checkSVG();
+ bool checkTGA();
+ bool checkMOV();
+ bool checkPDF();
+};
+}
+
+#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 000000000..87ac6a50f
--- /dev/null
+++ b/vcl/inc/graphic/GraphicID.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in 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 <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 000000000..9576d4253
--- /dev/null
+++ b/vcl/inc/graphic/GraphicReader.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 <vcl/dllapi.h>
+#include <rtl/ustring.hxx>
+#include <tools/gen.hxx>
+#include <memory>
+
+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 000000000..bb52a0cfc
--- /dev/null
+++ b/vcl/inc/graphic/Manager.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/.
+ */
+
+#ifndef INCLUDED_VCL_INC_GRAPHIC_MANAGER_HXX
+#define INCLUDED_VCL_INC_GRAPHIC_MANAGER_HXX
+
+#include <sal/types.h>
+#include <rtl/ustring.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/animate/Animation.hxx>
+#include <vcl/vectorgraphicdata.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/GraphicExternalLink.hxx>
+
+#include <memory>
+#include <mutex>
+#include <chrono>
+#include <o3tl/sorted_vector.hxx>
+
+class ImpGraphic;
+
+namespace vcl
+{
+namespace graphic
+{
+class Manager final
+{
+private:
+ std::recursive_mutex maMutex; // instead of SolarMutex because graphics can live past vcl main
+ o3tl::sorted_vector<ImpGraphic*> m_pImpGraphicList;
+ std::chrono::seconds mnAllowedIdleTime;
+ bool mbSwapEnabled;
+ sal_Int64 mnMemoryLimit;
+ sal_Int64 mnUsedSize;
+ Timer maSwapOutTimer;
+
+ Manager();
+
+ void registerGraphic(const std::shared_ptr<ImpGraphic>& rImpGraphic, OUString const& rsContext);
+
+ DECL_LINK(SwapOutTimerHandler, Timer*, void);
+
+ static sal_Int64 getGraphicSizeBytes(const ImpGraphic* pImpGraphic);
+
+public:
+ static Manager& get();
+
+ void swappedIn(const ImpGraphic* pImpGraphic);
+ void swappedOut(const ImpGraphic* pImpGraphic);
+
+ void reduceGraphicMemory();
+ 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 Bitmap& rBitmap);
+ std::shared_ptr<ImpGraphic> newInstance(const BitmapEx& rBitmapEx);
+ 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/UnoGraphic.hxx b/vcl/inc/graphic/UnoGraphic.hxx
new file mode 100644
index 000000000..f6eed8fab
--- /dev/null
+++ b/vcl/inc/graphic/UnoGraphic.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_SOURCE_GRAPHIC_GRAPHIC_HXX
+#define INCLUDED_VCL_SOURCE_GRAPHIC_GRAPHIC_HXX
+
+#include <com/sun/star/graphic/XGraphic.hpp>
+#include <com/sun/star/lang/XUnoTunnel.hpp>
+#include <com/sun/star/awt/XBitmap.hpp>
+
+#include <graphic/UnoGraphicDescriptor.hxx>
+#include <graphic/UnoGraphicTransformer.hxx>
+
+#include <vcl/graph.hxx>
+
+namespace unographic {
+
+class Graphic final : public css::graphic::XGraphic,
+ public css::awt::XBitmap,
+ public css::lang::XUnoTunnel,
+ public ::unographic::GraphicDescriptor,
+ public ::unographic::GraphicTransformer
+{
+public:
+ Graphic();
+ virtual ~Graphic() throw() override;
+
+ using ::unographic::GraphicDescriptor::init;
+ void init( const ::Graphic& rGraphic ) throw();
+
+private:
+
+ // XInterface
+ virtual css::uno::Any SAL_CALL queryAggregation( const css::uno::Type & rType ) override;
+ virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type & rType ) override;
+ virtual void SAL_CALL acquire() throw() override;
+ virtual void SAL_CALL release() throw() 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;
+
+ // 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;
+
+ // XUnoTunnel
+ virtual sal_Int64 SAL_CALL getSomething( const css::uno::Sequence< sal_Int8 >& rId ) 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 000000000..ad46952ce
--- /dev/null
+++ b/vcl/inc/graphic/UnoGraphicDescriptor.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 .
+ */
+
+#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 <cppuhelper/weakagg.hxx>
+#include <comphelper/propertysetinfo.hxx>
+#include <vcl/graph.hxx>
+
+#define MIMETYPE_BMP "image/x-MS-bmp"
+#define MIMETYPE_GIF "image/gif"
+#define MIMETYPE_JPG "image/jpeg"
+#define MIMETYPE_PCD "image/x-photo-cd"
+#define MIMETYPE_PCX "image/x-pcx"
+#define MIMETYPE_PNG "image/png"
+#define MIMETYPE_TIF "image/tiff"
+#define MIMETYPE_XBM "image/x-xbitmap"
+#define MIMETYPE_XPM "image/x-xpixmap"
+#define MIMETYPE_PBM "image/x-portable-bitmap"
+#define MIMETYPE_PGM "image/x-portable-graymap"
+#define MIMETYPE_PPM "image/x-portable-pixmap"
+#define MIMETYPE_RAS "image/x-cmu-raster"
+#define MIMETYPE_TGA "image/x-targa"
+#define MIMETYPE_PSD "image/vnd.adobe.photoshop"
+#define MIMETYPE_EPS "image/x-eps"
+#define MIMETYPE_DXF "image/vnd.dxf"
+#define MIMETYPE_MET "image/x-met"
+#define MIMETYPE_PCT "image/x-pict"
+#define MIMETYPE_SVM "image/x-svm"
+#define MIMETYPE_WMF "image/x-wmf"
+#define MIMETYPE_EMF "image/x-emf"
+#define MIMETYPE_SVG "image/svg+xml"
+#define MIMETYPE_PDF "application/pdf"
+#define MIMETYPE_VCLGRAPHIC "image/x-vclgraphic"
+
+namespace comphelper { class PropertySetInfo; }
+namespace com::sun::star::io { class XInputStream; }
+
+class Graphic;
+
+namespace unographic {
+
+class GraphicDescriptor : public ::cppu::OWeakAggObject,
+ public css::lang::XServiceInfo,
+ public css::lang::XTypeProvider,
+ public ::comphelper::PropertySetHelper
+{
+public:
+
+ GraphicDescriptor();
+ virtual ~GraphicDescriptor() throw() override;
+
+ void init( const ::Graphic& rGraphic );
+ void init( const OUString& rURL );
+ void init( const css::uno::Reference< css::io::XInputStream >& rxIStm, const OUString& rURL );
+
+protected:
+
+ static rtl::Reference<::comphelper::PropertySetInfo> createPropertySetInfo();
+
+ // XInterface
+ virtual css::uno::Any SAL_CALL queryAggregation( const css::uno::Type & rType ) override;
+ virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type & rType ) override;
+ virtual void SAL_CALL acquire() throw() override;
+ virtual void SAL_CALL release() throw() 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;
+
+ // 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/UnoGraphicTransformer.hxx b/vcl/inc/graphic/UnoGraphicTransformer.hxx
new file mode 100644
index 000000000..785efdcd1
--- /dev/null
+++ b/vcl/inc/graphic/UnoGraphicTransformer.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_SOURCE_GRAPHIC_TRANSFORMER_HXX
+#define INCLUDED_VCL_SOURCE_GRAPHIC_TRANSFORMER_HXX
+
+#include <cppuhelper/implbase1.hxx>
+#include <com/sun/star/graphic/XGraphicTransformer.hpp>
+
+namespace unographic {
+
+
+typedef ::cppu::WeakAggImplHelper1<
+ css::graphic::XGraphicTransformer
+ > GraphicTransformer_UnoImplHelper1;
+class GraphicTransformer : public GraphicTransformer_UnoImplHelper1
+{
+ public:
+
+ GraphicTransformer();
+ virtual ~GraphicTransformer() 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;
+
+};
+
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/headless/CustomWidgetDraw.hxx b/vcl/inc/headless/CustomWidgetDraw.hxx
new file mode 100644
index 000000000..f30488ca8
--- /dev/null
+++ b/vcl/inc/headless/CustomWidgetDraw.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/.
+ *
+ */
+
+#ifndef INCLUDED_VCL_INC_CUSTOMWIDGETDRAW_HXX
+#define INCLUDED_VCL_INC_CUSTOMWIDGETDRAW_HXX
+
+#include <vcl/dllapi.h>
+#include <WidgetDrawInterface.hxx>
+#include <WidgetThemeLibrary.hxx>
+#include <headless/svpgdi.hxx>
+#include <vcl/settings.hxx>
+
+namespace vcl
+{
+class CustomWidgetDraw final : public vcl::WidgetDrawInterface
+{
+private:
+ static WidgetThemeLibrary* s_pWidgetImplementation;
+ SvpSalGraphics& m_rGraphics;
+
+public:
+ CustomWidgetDraw(SvpSalGraphics& rGraphics);
+ ~CustomWidgetDraw() override;
+
+ 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;
+};
+
+} // end vcl namespace
+
+#endif
+
+/* 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 000000000..1551fc844
--- /dev/null
+++ b/vcl/inc/headless/svpbmp.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_INC_HEADLESS_SVPBMP_HXX
+#define INCLUDED_VCL_INC_HEADLESS_SVPBMP_HXX
+
+#include <sal/config.h>
+
+#include <salbmp.hxx>
+#include <basegfx/utils/systemdependentdata.hxx>
+
+class VCL_DLLPUBLIC SvpSalBitmap final : public SalBitmap, public basegfx::SystemDependentDataHolder // MM02
+{
+ std::unique_ptr<BitmapBuffer> mpDIB;
+public:
+ SvpSalBitmap();
+ virtual ~SvpSalBitmap() override;
+
+ // SalBitmap
+ virtual bool Create( const Size& rSize,
+ sal_uInt16 nBitCount,
+ 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,
+ sal_uInt16 nNewBitCount ) override;
+ virtual bool Create( const css::uno::Reference< css::rendering::XBitmapCanvas >& rBitmapCanvas,
+ Size& rSize,
+ bool bMask = false ) override;
+ void Create(std::unique_ptr<BitmapBuffer> pBuf);
+ const BitmapBuffer* GetBuffer() const
+ {
+ return mpDIB.get();
+ }
+ 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;
+
+ // MM02 exclusive management op's for SystemDependentData at WinSalBitmap
+ template<class T>
+ std::shared_ptr<T> getSystemDependentData() const
+ {
+ return std::static_pointer_cast<T>(basegfx::SystemDependentDataHolder::getSystemDependentData(typeid(T).hash_code()));
+ }
+
+ template<class T, class... Args>
+ std::shared_ptr<T> addOrReplaceSystemDependentData(basegfx::SystemDependentDataManager& manager, Args&&... args) const
+ {
+ std::shared_ptr<T> r = std::make_shared<T>(manager, 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< SvpSalBitmap* >(this)->basegfx::SystemDependentDataHolder::addOrReplaceSystemDependentData(r2);
+ }
+
+ return r;
+ }
+};
+
+#endif // INCLUDED_VCL_INC_HEADLESS_SVPBMP_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/headless/svpcairotextrender.hxx b/vcl/inc/headless/svpcairotextrender.hxx
new file mode 100644
index 000000000..35744d5a2
--- /dev/null
+++ b/vcl/inc/headless/svpcairotextrender.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/.
+ */
+
+#ifndef INCLUDED_VCL_UNX_GTK3_GDI_GTK3CAIROTEXTRENDER_HXX
+#define INCLUDED_VCL_UNX_GTK3_GDI_GTK3CAIROTEXTRENDER_HXX
+
+#include <unx/cairotextrender.hxx>
+
+class SvpSalGraphics;
+
+class SvpCairoTextRender final : public CairoTextRender
+{
+ SvpSalGraphics& mrParent;
+
+public:
+ explicit SvpCairoTextRender(SvpSalGraphics& rParent);
+
+ virtual cairo_t* getCairoContext() override;
+ virtual void getSurfaceOffset(double& nDX, double& nDY) override;
+ virtual void clipRegion(cairo_t* cr) override;
+ virtual void releaseCairoContext(cairo_t* cr) override;
+};
+
+#endif
+
+/* 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 000000000..51007916b
--- /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
+{
+public:
+ SystemEnvData m_aSystemChildData;
+
+ virtual ~SvpSalObject() override;
+
+ // override all pure virtual methods
+ virtual void ResetClipRegion() override;
+ virtual void BeginSetClipRegion( sal_uInt32 nRects ) override;
+ virtual void UnionClipRegion( long nX, long nY, long nWidth, long nHeight ) override;
+ virtual void EndSetClipRegion() override;
+
+ virtual void SetPosSize( long nX, long nY, long nWidth, 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 tools::Rectangle 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 000000000..7245a31d5
--- /dev/null
+++ b/vcl/inc/headless/svpframe.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_HEADLESS_SVPFRAME_HXX
+#define INCLUDED_VCL_INC_HEADLESS_SVPFRAME_HXX
+
+#include <vcl/sysdata.hxx>
+
+#include <salframe.hxx>
+
+#include <list>
+#include <vector>
+
+#ifdef IOS
+#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::list< SvpSalFrame* > m_aChildren; // List of child frames
+ SalFrameStyleFlags m_nStyle;
+ bool m_bVisible;
+#ifndef IOS
+ cairo_surface_t* m_pSurface;
+#endif
+ long m_nMinWidth;
+ long m_nMinHeight;
+ long m_nMaxWidth;
+ long m_nMaxHeight;
+
+ SystemEnvData m_aSystemChildData;
+
+ std::vector< SvpSalGraphics* > m_aGraphics;
+
+ static SvpSalFrame* s_pFocusFrame;
+public:
+ SvpSalFrame( SvpSalInstance* pInstance,
+ SalFrame* pParent,
+ SalFrameStyleFlags nSalFrameStyle );
+ virtual ~SvpSalFrame() override;
+
+ void GetFocus();
+ void LoseFocus();
+ void PostPaint() const;
+
+ // 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 DrawMenuBar() override;
+
+ virtual void SetExtendedFrameStyle( SalExtStyle nExtStyle ) override;
+ virtual void Show( bool bVisible, bool bNoActivate = false ) override;
+ virtual void SetMinClientSize( long nWidth, long nHeight ) override;
+ virtual void SetMaxClientSize( long nWidth, long nHeight ) override;
+ virtual void SetPosSize( long nX, long nY, long nWidth, long nHeight, sal_uInt16 nFlags ) override;
+ virtual void GetClientSize( long& rWidth, long& rHeight ) override;
+ virtual void GetWorkArea( tools::Rectangle& rRect ) override;
+ virtual SalFrame* GetParent() const override;
+ virtual void SetWindowState( const SalFrameState* pState ) override;
+ virtual bool GetWindowState( SalFrameState* pState ) 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( long nX, 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 bool SetPluginParent( SystemParentData* pNewParent ) override;
+ virtual void ResetClipRegion() override;
+ virtual void BeginSetClipRegion( sal_uInt32 nRects ) override;
+ virtual void UnionClipRegion( long nX, long nY, long nWidth, long nHeight ) override;
+ virtual void EndSetClipRegion() override;
+
+ /*TODO: functional implementation */
+ virtual void SetScreenNumber( unsigned int ) override {}
+ virtual void SetApplicationID(const OUString &) override {}
+
+};
+
+#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 000000000..3d1617d07
--- /dev/null
+++ b/vcl/inc/headless/svpgdi.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_HEADLESS_SVPGDI_HXX
+#define INCLUDED_VCL_INC_HEADLESS_SVPGDI_HXX
+
+#ifdef IOS
+#error This file is not for iOS
+#endif
+
+#include <config_features.h>
+
+#include <osl/endian.h>
+#include <vcl/sysdata.hxx>
+#include <config_cairo_canvas.h>
+
+#include <salgdi.hxx>
+#include <sallayout.hxx>
+#include "svpcairotextrender.hxx"
+#include <impfontmetricdata.hxx>
+
+#include <cairo.h>
+
+//Using formats that match cairo's formats. For android we patch cairo,
+//which is internal in that case, to swap the rgb components so that
+//cairo then matches the OpenGL GL_RGBA format so we can use it there
+//where we don't have GL_BGRA support.
+// SVP_24BIT_FORMAT is used to store 24-bit images in 3-byte pixels to conserve memory.
+#if defined(ANDROID) && !HAVE_FEATURE_ANDROID_LOK
+# define SVP_24BIT_FORMAT (ScanlineFormat::N24BitTcRgb | ScanlineFormat::TopDown)
+# define SVP_CAIRO_FORMAT (ScanlineFormat::N32BitTcRgba | ScanlineFormat::TopDown)
+# define SVP_CAIRO_BLUE 1
+# define SVP_CAIRO_GREEN 2
+# define SVP_CAIRO_RED 0
+# define SVP_CAIRO_ALPHA 3
+#elif defined OSL_BIGENDIAN
+# define SVP_24BIT_FORMAT (ScanlineFormat::N24BitTcRgb | ScanlineFormat::TopDown)
+# define SVP_CAIRO_FORMAT (ScanlineFormat::N32BitTcArgb | ScanlineFormat::TopDown)
+# define SVP_CAIRO_BLUE 3
+# define SVP_CAIRO_GREEN 2
+# define SVP_CAIRO_RED 1
+# define SVP_CAIRO_ALPHA 0
+#else
+# define SVP_24BIT_FORMAT (ScanlineFormat::N24BitTcBgr | ScanlineFormat::TopDown)
+# define SVP_CAIRO_FORMAT (ScanlineFormat::N32BitTcBgra | ScanlineFormat::TopDown)
+# define SVP_CAIRO_BLUE 0
+# define SVP_CAIRO_GREEN 1
+# define SVP_CAIRO_RED 2
+# define SVP_CAIRO_ALPHA 3
+#endif
+
+struct BitmapBuffer;
+class FreetypeFont;
+typedef struct _cairo cairo_t;
+typedef struct _cairo_surface cairo_surface_t;
+typedef struct _cairo_user_data_key cairo_user_data_key_t;
+
+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);
+
+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;
+};
+
+class VCL_DLLPUBLIC SvpSalGraphics : public SalGraphics
+{
+ cairo_surface_t* m_pSurface;
+ basegfx::B2IVector m_aFrameSize;
+ double m_fScale;
+ Color m_aLineColor;
+ Color m_aFillColor;
+ PaintMode m_ePaintMode;
+
+public:
+ void setSurface(cairo_surface_t* pSurface, const basegfx::B2IVector& rSize);
+ cairo_surface_t* getSurface() const { return m_pSurface; }
+ static cairo_user_data_key_t* getDamageKey();
+
+ static void clipRegion(cairo_t* cr, const vcl::Region& rClipRegion);
+
+ // need this static version of ::drawPolyLine for usage from
+ // vcl/unx/generic/gdi/salgdi.cxx. It gets wrapped by
+ // ::drawPolyLine with some added parameters (see there)
+ static bool drawPolyLine(
+ cairo_t* cr,
+ basegfx::B2DRange* pExtents,
+ const Color& rLineColor,
+ bool bAntiAliasB2DDraw,
+ 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);
+
+ void copySource(const SalTwoRect& rTR, cairo_surface_t* source);
+ void copyWithOperator(const SalTwoRect& rTR, cairo_surface_t* source,
+ cairo_operator_t eOp = CAIRO_OPERATOR_SOURCE);
+
+private:
+ void invert(const basegfx::B2DPolygon &rPoly, SalInvert nFlags);
+ void applyColor(cairo_t *cr, Color rColor, double fTransparency = 0.0);
+
+protected:
+ vcl::Region m_aClipRegion;
+ SvpCairoTextRender m_aTextRenderImpl;
+
+protected:
+ 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) override;
+ virtual bool drawAlphaRect( long nX, long nY, long nWidth, long nHeight, sal_uInt8 nTransparency ) override;
+
+ cairo_t* createTmpCompatibleCairoContext() const;
+
+public:
+ SvpSalGraphics();
+ virtual ~SvpSalGraphics() override;
+
+ virtual SalGraphicsImpl* GetImpl() const override { return nullptr; };
+ virtual void GetResolution( sal_Int32& rDPIX, sal_Int32& rDPIY ) override;
+ virtual sal_uInt16 GetBitCount() const override;
+ virtual long GetGraphicsWidth() const override;
+
+ virtual void ResetClipRegion() override;
+ virtual bool setClipRegion( const vcl::Region& ) override;
+
+ virtual void SetLineColor() override;
+ virtual void SetLineColor( Color nColor ) override;
+ virtual void SetFillColor() override;
+ virtual void SetFillColor( Color nColor ) override;
+
+ virtual void SetXORMode( bool bSet, bool ) override;
+
+ virtual void SetROPLineColor( SalROPColor nROPColor ) override;
+ virtual void SetROPFillColor( SalROPColor nROPColor ) override;
+
+ virtual void SetTextColor( Color nColor ) override;
+ virtual void SetFont(LogicalFontInstance*, int nFallbackLevel) override;
+ virtual void GetFontMetric( ImplFontMetricDataRef&, int nFallbackLevel ) override;
+ virtual FontCharMapRef GetFontCharMap() const override;
+ virtual bool GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const override;
+ virtual void GetDevFontList( PhysicalFontCollection* ) override;
+ virtual void ClearDevFontCache() override;
+ virtual bool AddTempDevFont( PhysicalFontCollection*, const OUString& rFileURL, const OUString& rFontName ) override;
+ virtual bool CreateFontSubset( const OUString& rToFile,
+ const PhysicalFontFace*,
+ const sal_GlyphId* pGlyphIds,
+ const sal_uInt8* pEncoding,
+ sal_Int32* pWidths,
+ int nGlyphs,
+ FontSubsetInfo& rInfo
+ ) override;
+ virtual const void* GetEmbedFontData(const PhysicalFontFace*, long* pDataLen) override;
+ virtual void FreeEmbedFontData( const void* pData, long nDataLen ) override;
+ virtual void GetGlyphWidths( const PhysicalFontFace*,
+ bool bVertical,
+ std::vector< sal_Int32 >& rWidths,
+ Ucs2UIntMap& rUnicodeEnc ) override;
+ virtual std::unique_ptr<GenericSalLayout>
+ GetTextLayout(int nFallbackLevel) override;
+ virtual void DrawTextLayout( const GenericSalLayout& ) override;
+ virtual bool supportsOperation( OutDevSupportType ) const override;
+ virtual void drawPixel( long nX, long nY ) override;
+ virtual void drawPixel( long nX, long nY, Color nColor ) override;
+ virtual void drawLine( long nX1, long nY1, long nX2, long nY2 ) override;
+ virtual void drawRect( long nX, long nY, long nWidth, long nHeight ) override;
+
+ virtual bool 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 void drawPolyLine( sal_uInt32 nPoints, const SalPoint* pPtAry ) override;
+ virtual void drawPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry ) override;
+ virtual void drawPolyPolygon( sal_uInt32 nPoly,
+ const sal_uInt32* pPoints,
+ PCONSTSALPOINT* pPtAry ) override;
+ virtual bool drawPolyLineBezier( sal_uInt32 nPoints,
+ const SalPoint* pPtAry,
+ const PolyFlags* pFlgAry ) override;
+ virtual bool drawPolygonBezier( sal_uInt32 nPoints,
+ const SalPoint* pPtAry,
+ const PolyFlags* pFlgAry ) override;
+ virtual bool drawPolyPolygonBezier( sal_uInt32 nPoly,
+ const sal_uInt32* pPoints,
+ const SalPoint* const* pPtAry,
+ const PolyFlags* const* pFlgAry ) override;
+ virtual bool drawGradient( const tools::PolyPolygon&, const Gradient& ) override { return false; };
+
+ virtual bool implDrawGradient(basegfx::B2DPolyPolygon const & rPolyPolygon, SalGradient const & rGradient) override;
+
+ virtual void copyArea( long nDestX,
+ long nDestY,
+ long nSrcX,
+ long nSrcY,
+ long nSrcWidth,
+ long nSrcHeight,
+ bool bWindowInvalidate) override;
+ virtual void copyBits( const SalTwoRect& rPosAry,
+ SalGraphics* pSrcGraphics ) override;
+ virtual void drawBitmap( const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap ) override;
+ void drawBitmap( const SalTwoRect& rPosAry,
+ const BitmapBuffer* pBuffer,
+ cairo_operator_t eOp );
+ 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( long nX, long nY, long nWidth, long nHeight ) override;
+ virtual Color getPixel( long nX, long nY ) override;
+ virtual void invert( long nX, long nY, long nWidth, long nHeight, SalInvert nFlags ) override;
+ virtual void invert( sal_uInt32 nPoints, const SalPoint* pPtAry, SalInvert nFlags ) override;
+
+ virtual bool drawEPS( long nX, long nY, long nWidth, long nHeight, void* pPtr, sal_uInt32 nSize ) override;
+
+ virtual SystemGraphicsData GetGraphicsData() const override;
+
+ virtual OUString getRenderBackendName() const override { return "svp"; }
+
+#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;
+ virtual SystemFontData GetSysFontData( int nFallbacklevel ) const override;
+#endif // ENABLE_CAIRO_CANVAS
+
+ cairo_t* getCairoContext(bool bXorModeAllowed) const;
+ void releaseCairoContext(cairo_t* cr, bool bXorModeAllowed, const basegfx::B2DRange& rExtents) const;
+ static cairo_surface_t* createCairoSurface(const BitmapBuffer *pBuffer);
+ void clipRegion(cairo_t* cr);
+};
+
+#endif // INCLUDED_VCL_INC_HEADLESS_SVPGDI_HXX
+
+/* 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 000000000..ef20f0cab
--- /dev/null
+++ b/vcl/inc/headless/svpinst.hxx
@@ -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 .
+ */
+
+#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 <sys/time.h>
+
+#define VIRTUAL_DESKTOP_WIDTH 1024
+#define VIRTUAL_DESKTOP_HEIGHT 768
+
+#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
+ int m_FeedbackFDs[2];
+ 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;
+};
+
+SalInstance* svp_create_SalInstance();
+
+// 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;
+
+public:
+ static SvpSalInstance* s_pDefaultInstance;
+
+ SvpSalInstance( std::unique_ptr<SalYieldMutex> pMutex );
+ virtual ~SvpSalInstance() override;
+
+ void CloseWakeupPipe(bool log);
+ void CreateWakeupPipe(bool log);
+ 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* pGraphics,
+ long &nDX, 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;
+
+ std::shared_ptr<vcl::BackendCapabilities> GetBackendCapabilities() 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 OpenGLContext* CreateOpenGLContext() 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;
+};
+
+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 000000000..3d241a96e
--- /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 000000000..7ec666709
--- /dev/null
+++ b/vcl/inc/headless/svpvd.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_HEADLESS_SVPVD_HXX
+#define INCLUDED_VCL_INC_HEADLESS_SVPVD_HXX
+
+#include <salvd.hxx>
+#include <vcl/salgtype.hxx>
+#include <basegfx/vector/b2ivector.hxx>
+
+#include <vector>
+
+class SvpSalGraphics;
+typedef struct _cairo_surface cairo_surface_t;
+
+class VCL_DLLPUBLIC SvpSalVirtualDevice : public SalVirtualDevice
+{
+ DeviceFormat m_eFormat;
+ 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;
+
+ void CreateSurface(long nNewDX, long nNewDY, sal_uInt8 *const pBuffer);
+
+protected:
+ SvpSalGraphics* AddGraphics(SvpSalGraphics* aGraphics);
+
+public:
+ SvpSalVirtualDevice(DeviceFormat eFormat, 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( long nNewDX, long nNewDY ) override;
+ virtual bool SetSizeUsingBuffer( long nNewDX, long nNewDY,
+ sal_uInt8 * pBuffer
+ ) override;
+
+ cairo_surface_t* GetSurface() const { return m_pSurface; }
+
+ // SalGeometryProvider
+ virtual long GetWidth() const override;
+ virtual 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 000000000..089f7417c
--- /dev/null
+++ b/vcl/inc/helpwin.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_HELPWIN_HXX
+#define INCLUDED_VCL_INC_HELPWIN_HXX
+
+#include <vcl/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 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 000000000..c43dbb469
--- /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 <memory>
+
+#include <vcl/fixed.hxx>
+
+namespace vcl
+{
+ class HyperLabelImpl;
+
+ class HyperLabel final : public FixedText
+ {
+ std::unique_ptr<HyperLabelImpl> m_pImpl;
+ 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 dispose() 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( long nMaxWidth ) const;
+ };
+}
+
+#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 000000000..09d1a0b02
--- /dev/null
+++ b/vcl/inc/iconview.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_SVTOOLS_ICONVIEW_HXX
+#define INCLUDED_SVTOOLS_ICONVIEW_HXX
+
+#include <vcl/treelistbox.hxx>
+
+class IconView final : public SvTreeListBox
+{
+public:
+ IconView( vcl::Window* pParent, WinBits nBits );
+
+ virtual void Resize() override;
+
+ virtual tools::Rectangle GetFocusRect(const SvTreeListEntry*, long nEntryPos) override;
+
+ void PaintEntry( SvTreeListEntry&, long nX, long nY, vcl::RenderContext& rRenderContext);
+};
+
+#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 000000000..c25015624
--- /dev/null
+++ b/vcl/inc/image.h
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_IMAGE_H
+#define INCLUDED_VCL_INC_IMAGE_H
+
+#include <vcl/bitmapex.hxx>
+
+class ImplImage
+{
+private:
+ BitmapChecksum maBitmapChecksum;
+ /// if non-empty: cached original size of maStockName else Size of maBitmap
+ Size maSizePixel;
+ Size maPreferedSizePixel;
+ /// If set - defines the bitmap via images.zip*
+ OUString maStockName;
+
+
+ /// Original bitmap - or cache of a potentially scaled bitmap
+ BitmapEx maBitmapEx;
+ BitmapEx maDisabledBitmapEx;
+
+ bool loadStockAtScale(double fScale, BitmapEx &rBitmapEx);
+
+public:
+ ImplImage(const BitmapEx& rBitmapEx);
+ ImplImage(const OUString &aStockName, Size const & rPreferedSize);
+
+ bool isStock() const
+ {
+ return maStockName.getLength() > 0;
+ }
+
+ 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 = false);
+
+ 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 000000000..a1a28d3fa
--- /dev/null
+++ b/vcl/inc/imagerepository.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 .
+ */
+
+#ifndef INCLUDED_VCL_IMAGEREPOSITORY_HXX
+#define INCLUDED_VCL_IMAGEREPOSITORY_HXX
+
+#include <vcl/dllapi.h>
+#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/impanmvw.hxx b/vcl/inc/impanmvw.hxx
new file mode 100644
index 000000000..b4e634071
--- /dev/null
+++ b/vcl/inc/impanmvw.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 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_GDI_IMPANMVW_HXX
+#define INCLUDED_VCL_SOURCE_GDI_IMPANMVW_HXX
+
+#include <vcl/animate/Animation.hxx>
+#include <vcl/vclptr.hxx>
+
+class Animation;
+class OutputDevice;
+class VirtualDevice;
+struct AnimationBitmap;
+
+struct AInfo
+{
+ Point aStartOrg;
+ Size aStartSize;
+ VclPtr<OutputDevice> pOutDev;
+ void* pViewData;
+ long nExtraData;
+ bool bPause;
+
+ AInfo();
+};
+
+
+class ImplAnimView
+{
+private:
+
+ friend class Animation;
+
+ Animation* mpParent;
+ VclPtr<OutputDevice> mpRenderContext;
+ long mnExtraData;
+ Point maPt;
+ Point maDispPt;
+ Point maRestPt;
+ Size maSz;
+ Size maSzPix;
+ Size maDispSz;
+ Size maRestSz;
+ vcl::Region maClip;
+ VclPtr<VirtualDevice> mpBackground;
+ VclPtr<VirtualDevice> mpRestore;
+ sal_uLong mnActPos;
+ Disposal meLastDisposal;
+ bool mbIsPaused;
+ bool mbIsMarked;
+ bool mbIsMirroredHorizontally;
+ bool mbIsMirroredVertically;
+
+public:
+ ~ImplAnimView();
+private:
+ ImplAnimView( Animation* pParent, OutputDevice* pOut,
+ const Point& rPt, const Size& rSz, sal_uLong nExtraData,
+ OutputDevice* pFirstFrameOutDev = nullptr );
+
+ bool matches(const OutputDevice* pOut, long nExtraData) const;
+ void drawToPos( sal_uLong nPos );
+ void draw( sal_uLong nPos, VirtualDevice* pVDev=nullptr );
+ void repaint();
+ AInfo* createAInfo() const;
+
+ void getPosSize( const AnimationBitmap& rAnm, Point& rPosPix, Size& rSizePix );
+
+ const Point& getOutPos() const { return maPt; }
+
+ const Size& getOutSizePix() const { return maSzPix; }
+
+ void pause( bool bIsPaused ) { mbIsPaused = bIsPaused; }
+ bool isPause() const { return mbIsPaused; }
+
+ void setMarked( bool bIsMarked ) { mbIsMarked = bIsMarked; }
+ bool isMarked() const { return mbIsMarked; }
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/impdel.hxx b/vcl/inc/impdel.hxx
new file mode 100644
index 000000000..308872b9c
--- /dev/null
+++ b/vcl/inc/impdel.hxx
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_VCL_IMPDEL_HXX
+#define INCLUDED_VCL_IMPDEL_HXX
+
+#include <list>
+
+namespace vcl
+{
+
+class DeletionListener;
+
+class DeletionNotifier
+{
+ std::list< DeletionListener* > m_aListeners;
+ protected:
+ DeletionNotifier() {}
+
+ ~DeletionNotifier()
+ { notifyDelete(); }
+
+ inline void notifyDelete();
+
+ public:
+ void addDel( DeletionListener* pListener )
+ { m_aListeners.push_back( pListener ); }
+
+ void removeDel( DeletionListener* pListener )
+ { m_aListeners.remove( 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 000000000..7f4301d97
--- /dev/null
+++ b/vcl/inc/impfont.hxx
@@ -0,0 +1,137 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_IMPFONT_HXX
+#define INCLUDED_VCL_INC_IMPFONT_HXX
+
+#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 "fontselect.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; }
+
+ bool IsSymbolFont() const { return mbSymbolFlag; }
+
+ 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 ) { maAverageFontSize = rSize; }
+
+ void SetSymbolFlag( const bool bSymbolFlag ) { mbSymbolFlag = bSymbolFlag; }
+
+ // 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; }
+
+ bool operator==( const ImplFont& ) const;
+
+private:
+ friend class vcl::Font;
+ friend SvStream& ReadImplFont( SvStream& rIStm, ImplFont& );
+ friend SvStream& WriteImplFont( SvStream& rOStm, const ImplFont& );
+
+ 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;
+ Size maAverageFontSize;
+ rtl_TextEncoding meCharSet;
+
+ LanguageTag maLanguageTag;
+ LanguageTag maCJKLanguageTag;
+
+ // Flags - device independent
+ bool mbSymbolFlag:1,
+ 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
+ short mnOrientation;
+
+ int mnQuality;
+
+};
+
+#endif // INCLUDED_VCL_INC_IMPFONT_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/impfontcache.hxx b/vcl/inc/impfontcache.hxx
new file mode 100644
index 000000000..78dfb4a71
--- /dev/null
+++ b/vcl/inc/impfontcache.hxx
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_IMPFONTCACHE_HXX
+#define INCLUDED_VCL_INC_IMPFONTCACHE_HXX
+
+#include <boost/functional/hash.hpp>
+
+#include <rtl/ref.hxx>
+#include <o3tl/lru_map.hxx>
+#include <tools/gen.hxx>
+#include <vcl/glyphitem.hxx>
+
+#include "fontselect.hxx"
+
+class Size;
+namespace vcl { class 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;
+ boost::hash_combine(seed, aCache.m_pFont);
+ boost::hash_combine(seed, aCache.m_nId);
+ return seed;
+ }
+};
+
+typedef o3tl::lru_map<GlyphBoundRectCacheKey, tools::Rectangle,
+ GlyphBoundRectCacheHash> GlyphBoundRectCache;
+typedef GlyphBoundRectCache::key_value_pair_t GlyphBoundRectCachePair;
+
+class ImplFontCache
+{
+private:
+ // cache of recently used font instances
+ struct IFSD_Equal { bool operator()( const FontSelectPattern&, const FontSelectPattern& ) const; };
+ struct IFSD_Hash { size_t operator()( const FontSelectPattern& ) const; };
+ typedef o3tl::lru_map<FontSelectPattern, rtl::Reference<LogicalFontInstance>, IFSD_Hash, IFSD_Equal> FontInstanceList;
+ typedef FontInstanceList::key_value_pair_t FontInstanceListPair;
+
+ LogicalFontInstance* mpLastHitCacheEntry; ///< keeps the last hit cache entry
+ FontInstanceList maFontInstanceList;
+ GlyphBoundRectCache m_aBoundRectCache;
+
+ rtl::Reference<LogicalFontInstance> GetFontInstance(PhysicalFontCollection const*, FontSelectPattern&);
+
+public:
+ ImplFontCache();
+ ~ImplFontCache();
+
+ rtl::Reference<LogicalFontInstance> GetFontInstance(PhysicalFontCollection const *,
+ const vcl::Font&, const Size& rPixelSize, float fExactHeight, bool bNonAntialias = false);
+ rtl::Reference<LogicalFontInstance> GetGlyphFallbackFont( PhysicalFontCollection const *, 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();
+};
+
+#endif // INCLUDED_VCL_INC_IMPFONTCACHE_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/impfontcharmap.hxx b/vcl/inc/impfontcharmap.hxx
new file mode 100644
index 000000000..eaa99dbe2
--- /dev/null
+++ b/vcl/inc/impfontcharmap.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_IMPFONTCHARMAP_HXX
+#define INCLUDED_VCL_INC_IMPFONTCHARMAP_HXX
+
+#include <tools/ref.hxx>
+#include <vcl/dllapi.h>
+
+class ImplFontCharMap;
+typedef tools::SvRef<ImplFontCharMap> ImplFontCharMapRef;
+
+class CmapResult;
+
+class ImplFontCharMap : public SvRefBase
+{
+public:
+ explicit ImplFontCharMap( const CmapResult& );
+ virtual ~ImplFontCharMap() override;
+
+private:
+ friend class FontCharMap;
+
+ ImplFontCharMap( const ImplFontCharMap& ) = delete;
+ void operator=( const ImplFontCharMap& ) = delete;
+
+ static ImplFontCharMapRef const & getDefaultMap( bool bSymbols=false);
+ bool isDefaultMap() const;
+
+private:
+ const sal_uInt32* mpRangeCodes; // pairs of StartCode/(EndCode+1)
+ const int* mpStartGlyphs; // range-specific mapper to glyphs
+ const sal_uInt16* mpGlyphIds; // individual glyphid mappings
+ int mnRangeCount;
+ int mnCharCount; // covered codepoints
+};
+
+bool VCL_DLLPUBLIC ParseCMAP( const unsigned char* pRawData, int nRawLength, CmapResult& );
+
+#endif // INCLUDED_VCL_INC_IMPFONTCHARMAP_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/impfontmetricdata.hxx b/vcl/inc/impfontmetricdata.hxx
new file mode 100644
index 000000000..d801b9e41
--- /dev/null
+++ b/vcl/inc/impfontmetricdata.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_IMPFONTMETRICDATA_HXX
+#define INCLUDED_VCL_INC_IMPFONTMETRICDATA_HXX
+
+#include <vcl/dllapi.h>
+#include <tools/ref.hxx>
+#include "fontattributes.hxx"
+#include "sft.hxx"
+
+#include <vector>
+
+class ImplFontMetricData;
+typedef tools::SvRef<ImplFontMetricData> ImplFontMetricDataRef;
+
+class OutputDevice;
+class FontSelectPattern;
+class LogicalFontInstance;
+
+class VCL_DLLPUBLIC ImplFontMetricData : public FontAttributes, public SvRefBase
+{
+public:
+ explicit ImplFontMetricData( const FontSelectPattern& );
+
+ // font instance attributes from the font request
+ long GetWidth() const { return mnWidth; }
+ short GetOrientation() const { return mnOrientation; }
+
+ void SetWidth(long nWidth) { mnWidth=nWidth; }
+ void SetOrientation(short nOrientation) { mnOrientation=nOrientation; }
+
+ // font metrics measured for the font instance
+ long GetAscent() const { return mnAscent; }
+ long GetDescent() const { return mnDescent; }
+ long GetInternalLeading() const { return mnIntLeading; }
+ long GetExternalLeading() const { return mnExtLeading; }
+ int GetSlant() const { return mnSlant; }
+ long GetMinKashida() const { return mnMinKashida; }
+
+ void SetSlant(int nSlant) { mnSlant=nSlant; }
+ void SetMinKashida( long nMinKashida ) { mnMinKashida=nMinKashida; }
+
+ // font attributes queried from the font instance
+ bool IsFullstopCentered() const { return mbFullstopCentered; }
+ long GetBulletOffset() const { return mnBulletOffset; }
+
+ void SetFullstopCenteredFlag(bool bFullstopCentered) { mbFullstopCentered = bFullstopCentered; }
+
+ // font metrics that are usually derived from the measurements
+ long GetUnderlineSize() const { return mnUnderlineSize; }
+ long GetUnderlineOffset() const { return mnUnderlineOffset; }
+ long GetBoldUnderlineSize() const { return mnBUnderlineSize; }
+ long GetBoldUnderlineOffset() const { return mnBUnderlineOffset; }
+ long GetDoubleUnderlineSize() const { return mnDUnderlineSize; }
+ long GetDoubleUnderlineOffset1() const { return mnDUnderlineOffset1; }
+ long GetDoubleUnderlineOffset2() const { return mnDUnderlineOffset2; }
+ long GetWavelineUnderlineSize() const { return mnWUnderlineSize; }
+ long GetWavelineUnderlineOffset() const { return mnWUnderlineOffset; }
+ long GetAboveUnderlineSize() const { return mnAboveUnderlineSize; }
+ long GetAboveUnderlineOffset() const { return mnAboveUnderlineOffset; }
+ long GetAboveBoldUnderlineSize() const { return mnAboveBUnderlineSize; }
+ long GetAboveBoldUnderlineOffset() const { return mnAboveBUnderlineOffset; }
+ long GetAboveDoubleUnderlineSize() const { return mnAboveDUnderlineSize; }
+ long GetAboveDoubleUnderlineOffset1() const { return mnAboveDUnderlineOffset1; }
+ long GetAboveDoubleUnderlineOffset2() const { return mnAboveDUnderlineOffset2; }
+ long GetAboveWavelineUnderlineSize() const { return mnAboveWUnderlineSize; }
+ long GetAboveWavelineUnderlineOffset() const { return mnAboveWUnderlineOffset; }
+ long GetStrikeoutSize() const { return mnStrikeoutSize; }
+ long GetStrikeoutOffset() const { return mnStrikeoutOffset; }
+ long GetBoldStrikeoutSize() const { return mnBStrikeoutSize; }
+ long GetBoldStrikeoutOffset() const { return mnBStrikeoutOffset; }
+ long GetDoubleStrikeoutSize() const { return mnDStrikeoutSize; }
+ long GetDoubleStrikeoutOffset1() const { return mnDStrikeoutOffset1; }
+ long GetDoubleStrikeoutOffset2() const { return mnDStrikeoutOffset2; }
+
+ void ImplInitTextLineSize( const OutputDevice* pDev );
+ void ImplInitAboveTextLineSize();
+ void ImplInitFlags( const OutputDevice* pDev );
+ void ImplCalcLineSpacing(LogicalFontInstance *pFontInstance);
+
+private:
+ bool ShouldUseWinMetrics(const vcl::TTGlobalFontInfo& rInfo);
+
+ // font instance attributes from the font request
+ long mnHeight; // Font size
+ long mnWidth; // Reference Width
+ short mnOrientation; // Rotation in 1/10 degrees
+
+ // font metrics measured for the font instance
+ long mnAscent; // Ascent
+ long mnDescent; // Descent
+ long mnIntLeading; // Internal Leading
+ long mnExtLeading; // External Leading
+ int mnSlant; // Slant (Italic/Oblique)
+ long mnMinKashida; // Minimal width of kashida (Arabic)
+
+ // font attributes queried from the font instance
+ bool mbFullstopCentered;
+ long mnBulletOffset; // Offset to position non-print character
+
+ // font metrics that are usually derived from the measurements
+ long mnUnderlineSize; // Lineheight of Underline
+ long mnUnderlineOffset; // Offset from Underline to Baseline
+ long mnBUnderlineSize; // Height of bold underline
+ long mnBUnderlineOffset; // Offset from bold underline to baseline
+ long mnDUnderlineSize; // Height of double underline
+ long mnDUnderlineOffset1; // Offset from double underline to baseline
+ long mnDUnderlineOffset2; // Offset from double underline to baseline
+ long mnWUnderlineSize; // Height of WaveLine underline
+ long mnWUnderlineOffset; // Offset from WaveLine underline to baseline, but centrered to WaveLine
+ long mnAboveUnderlineSize; // Height of single underline (for Vertical Right)
+ long mnAboveUnderlineOffset; // Offset from single underline to baseline (for Vertical Right)
+ long mnAboveBUnderlineSize; // Height of bold underline (for Vertical Right)
+ long mnAboveBUnderlineOffset; // Offset from bold underline to baseline (for Vertical Right)
+ long mnAboveDUnderlineSize; // Height of double underline (for Vertical Right)
+ long mnAboveDUnderlineOffset1; // Offset from double underline to baseline (for Vertical Right)
+ long mnAboveDUnderlineOffset2; // Offset from double underline to baseline (for Vertical Right)
+ long mnAboveWUnderlineSize; // Height of WaveLine-strike-out (for Vertical Right)
+ long mnAboveWUnderlineOffset; // Offset from WaveLine-strike-out to baseline, but centrered to the WaveLine (for Vertical Right)
+ long mnStrikeoutSize; // Height of single strike-out
+ long mnStrikeoutOffset; // Offset from single strike-out to baseline
+ long mnBStrikeoutSize; // Height of bold strike-out
+ long mnBStrikeoutOffset; // Offset of bold strike-out to baseline
+ long mnDStrikeoutSize; // Height of double strike-out
+ long mnDStrikeoutOffset1; // Offset of double strike-out to baseline
+ long mnDStrikeoutOffset2; // Offset of double strike-out to baseline
+
+};
+
+#endif // INCLUDED_VCL_INC_IMPFONTMETRICDATA_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 000000000..da56b660d
--- /dev/null
+++ b/vcl/inc/impglyphitem.hxx
@@ -0,0 +1,140 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/glyphitem.hxx>
+#include <vcl/outdev.hxx>
+#include <vector>
+
+#include "fontinstance.hxx"
+
+enum class GlyphItemFlags
+{
+ NONE = 0,
+ IS_IN_CLUSTER = 0x001,
+ IS_RTL_GLYPH = 0x002,
+ IS_DIACRITIC = 0x004,
+ IS_VERTICAL = 0x008,
+ IS_SPACING = 0x010,
+ ALLOW_KASHIDA = 0x020,
+ IS_DROPPED = 0x040,
+ IS_CLUSTER_START = 0x080
+};
+namespace o3tl
+{
+template <> struct typed_flags<GlyphItemFlags> : is_typed_flags<GlyphItemFlags, 0xff>
+{
+};
+};
+
+class VCL_DLLPUBLIC GlyphItem
+{
+ sal_GlyphId m_aGlyphId;
+ int m_nCharCount; // number of characters making up this glyph
+ int m_nOrigWidth; // original glyph width
+ LogicalFontInstance* m_pFontInstance;
+ int m_nCharPos; // index in string
+ GlyphItemFlags m_nFlags;
+ int m_nXOffset;
+
+public:
+ int m_nNewWidth; // width after adjustments
+ Point m_aLinearPos; // absolute position of non rotated string
+
+ GlyphItem(int nCharPos, int nCharCount, sal_GlyphId aGlyphId, const Point& rLinearPos,
+ GlyphItemFlags nFlags, int nOrigWidth, int nXOffset,
+ LogicalFontInstance* pFontInstance)
+ : m_aGlyphId(aGlyphId)
+ , m_nCharCount(nCharCount)
+ , m_nOrigWidth(nOrigWidth)
+ , m_pFontInstance(pFontInstance)
+ , m_nCharPos(nCharPos)
+ , m_nFlags(nFlags)
+ , m_nXOffset(nXOffset)
+ , m_nNewWidth(nOrigWidth)
+ , m_aLinearPos(rLinearPos)
+ {
+ assert(m_pFontInstance);
+ }
+
+ bool IsInCluster() const { return bool(m_nFlags & GlyphItemFlags::IS_IN_CLUSTER); }
+ bool IsRTLGlyph() const { return bool(m_nFlags & GlyphItemFlags::IS_RTL_GLYPH); }
+ bool IsDiacritic() const { return bool(m_nFlags & GlyphItemFlags::IS_DIACRITIC); }
+ bool IsVertical() const { return bool(m_nFlags & GlyphItemFlags::IS_VERTICAL); }
+ bool IsSpacing() const { return bool(m_nFlags & GlyphItemFlags::IS_SPACING); }
+ bool AllowKashida() const { return bool(m_nFlags & GlyphItemFlags::ALLOW_KASHIDA); }
+ bool IsDropped() const { return bool(m_nFlags & GlyphItemFlags::IS_DROPPED); }
+ bool IsClusterStart() const { return bool(m_nFlags & GlyphItemFlags::IS_CLUSTER_START); }
+
+ inline bool GetGlyphBoundRect(tools::Rectangle&) const;
+ inline bool GetGlyphOutline(basegfx::B2DPolyPolygon&) const;
+ inline void dropGlyph();
+
+ sal_GlyphId glyphId() const { return m_aGlyphId; }
+ int charCount() const { return m_nCharCount; }
+ int origWidth() const { return m_nOrigWidth; }
+ int charPos() const { return m_nCharPos; }
+ int xOffset() const { return m_nXOffset; }
+};
+
+VCL_DLLPUBLIC bool GlyphItem::GetGlyphBoundRect(tools::Rectangle& rRect) const
+{
+ return m_pFontInstance->GetGlyphBoundRect(m_aGlyphId, rRect, IsVertical());
+}
+
+VCL_DLLPUBLIC bool GlyphItem::GetGlyphOutline(basegfx::B2DPolyPolygon& rPoly) const
+{
+ return m_pFontInstance->GetGlyphOutline(m_aGlyphId, rPoly, IsVertical());
+}
+
+void GlyphItem::dropGlyph()
+{
+ m_nCharPos = -1;
+ m_nFlags |= GlyphItemFlags::IS_DROPPED;
+}
+
+class SalLayoutGlyphsImpl : public std::vector<GlyphItem>
+{
+ friend class GenericSalLayout;
+
+public:
+ SalLayoutGlyphsImpl* clone(SalLayoutGlyphs& rGlyphs) const;
+ LogicalFontInstance& GetFont() const { return *m_rFontInstance; }
+ bool IsValid() const;
+ void Invalidate();
+
+private:
+ mutable rtl::Reference<LogicalFontInstance> m_rFontInstance;
+ SalLayoutFlags mnFlags = SalLayoutFlags::NONE;
+
+ SalLayoutGlyphsImpl(SalLayoutGlyphs& rGlyphs, LogicalFontInstance& rFontInstance)
+ : m_rFontInstance(&rFontInstance)
+ {
+ rGlyphs.m_pImpl = this;
+ }
+};
+
+#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 000000000..749a5b82a
--- /dev/null
+++ b/vcl/inc/impgraph.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_IMPGRAPH_HXX
+#define INCLUDED_VCL_INC_IMPGRAPH_HXX
+
+#include <vcl/dllapi.h>
+#include <vcl/GraphicExternalLink.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/graph.hxx>
+#include "graphic/Manager.hxx"
+#include "graphic/GraphicID.hxx"
+
+struct ImpSwapInfo
+{
+ MapMode maPrefMapMode;
+ Size maPrefSize;
+ Size maSizePixel;
+
+ bool mbIsAnimated;
+ bool mbIsEPS;
+ bool mbIsTransparent;
+ bool mbIsAlpha;
+
+ sal_uInt32 mnAnimationLoopCount;
+};
+
+class OutputDevice;
+class GfxLink;
+class ImpSwapFile;
+class GraphicConversionParameters;
+class ImpGraphic;
+
+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::unique_ptr<GraphicID> mpGraphicID;
+ GraphicExternalLink maGraphicExternalLink;
+
+ std::chrono::high_resolution_clock::time_point maLastUsed;
+ bool mbPrepared;
+
+public:
+ ImpGraphic();
+ ImpGraphic( const ImpGraphic& rImpGraphic );
+ ImpGraphic( ImpGraphic&& rImpGraphic ) noexcept;
+ ImpGraphic( const GraphicExternalLink& rExternalLink);
+ ImpGraphic( const Bitmap& rBmp );
+ ImpGraphic( const BitmapEx& rBmpEx );
+ ImpGraphic(const std::shared_ptr<VectorGraphicData>& rVectorGraphicDataPtr);
+ ImpGraphic( const Animation& rAnimation );
+ ImpGraphic( const GDIMetaFile& rMtf );
+ ~ImpGraphic();
+
+ void ImplSetPrepared(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 (!mpGraphicID)
+ mpGraphicID.reset(new GraphicID(*this));
+ return mpGraphicID->getIDString();
+ }
+
+ void createSwapInfo();
+ void ImplClearGraphics();
+ void ImplClear();
+
+ GraphicType ImplGetType() const { return meType;}
+ void ImplSetDefaultType();
+ bool ImplIsSupportedGraphic() const;
+
+ bool ImplIsTransparent() const;
+ bool ImplIsAlpha() const;
+ bool ImplIsAnimated() const;
+ bool ImplIsEPS() const;
+
+ bool isAvailable() const;
+ bool makeAvailable();
+
+ Bitmap ImplGetBitmap(const GraphicConversionParameters& rParameters) const;
+ BitmapEx ImplGetBitmapEx(const GraphicConversionParameters& rParameters) const;
+ /// Gives direct access to the contained BitmapEx.
+ const BitmapEx& ImplGetBitmapExRef() const;
+ Animation ImplGetAnimation() const;
+ const GDIMetaFile& ImplGetGDIMetaFile() const;
+
+ Size ImplGetSizePixel() const;
+
+ Size ImplGetPrefSize() const;
+ void ImplSetPrefSize( const Size& rPrefSize );
+
+ MapMode ImplGetPrefMapMode() const;
+ void ImplSetPrefMapMode( const MapMode& rPrefMapMode );
+
+ sal_uLong ImplGetSizeBytes() const;
+
+ void ImplDraw( OutputDevice* pOutDev,
+ const Point& rDestPt ) const;
+ void ImplDraw( OutputDevice* pOutDev,
+ const Point& rDestPt,
+ const Size& rDestSize ) const;
+
+ void ImplStartAnimation( OutputDevice* pOutDev,
+ const Point& rDestPt,
+ const Size& rDestSize,
+ long nExtraData,
+ OutputDevice* pFirstFrameOutDev );
+ void ImplStopAnimation( OutputDevice* pOutputDevice,
+ long nExtraData );
+
+ void ImplSetAnimationNotifyHdl( const Link<Animation*,void>& rLink );
+ Link<Animation*,void> ImplGetAnimationNotifyHdl() const;
+
+ sal_uInt32 ImplGetAnimationLoopCount() const;
+
+private:
+
+ std::shared_ptr<GraphicReader>& ImplGetContext() { return mpContext;}
+ void ImplSetContext( const std::shared_ptr<GraphicReader>& pReader );
+ void ImplSetDummyContext( bool value ) { mbDummyContext = value; }
+ bool ImplReadEmbedded( SvStream& rIStream );
+ bool ImplWriteEmbedded( SvStream& rOStream );
+
+ bool swapInFromStream(SvStream* pIStm);
+
+ bool ImplIsDummyContext() const { return mbDummyContext; }
+ void ImplSetLink( const std::shared_ptr<GfxLink>& );
+ std::shared_ptr<GfxLink> ImplGetSharedGfxLink() const;
+ GfxLink ImplGetLink();
+ bool ImplIsLink() const;
+
+ BitmapChecksum ImplGetChecksum() const;
+
+ bool ImplExportNative( SvStream& rOStm ) const;
+
+ friend void WriteImpGraphic(SvStream& rOStm, const ImpGraphic& rImpGraphic);
+ friend void ReadImpGraphic(SvStream& rIStm, ImpGraphic& rImpGraphic);
+
+ const std::shared_ptr<VectorGraphicData>& getVectorGraphicData() const;
+
+ /// Gets the bitmap replacement for a vector graphic.
+ BitmapEx getVectorGraphicReplacement() const;
+
+ bool ensureAvailable () const;
+
+ bool loadPrepared();
+
+ sal_Int32 getPageNumber() const;
+
+public:
+ bool swapIn();
+ bool swapOut();
+ bool isSwappedOut() const { return mbSwapOut; }
+ OUString getSwapFileURL();
+};
+
+#endif // INCLUDED_VCL_INC_IMPGRAPH_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/implimagetree.hxx b/vcl/inc/implimagetree.hxx
new file mode 100644
index 000000000..3656b66de
--- /dev/null
+++ b/vcl/inc/implimagetree.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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;
+}
+
+struct ImageRequestParameters
+{
+ OUString msName;
+ OUString msStyle;
+ BitmapEx& mrBitmap;
+ bool mbLocalized;
+ ImageLoadFlags meFlags;
+ bool mbWriteImageToCache;
+ sal_Int32 mnScalePercentage;
+
+ ImageRequestParameters(const OUString & rName, const OUString & rStyle, BitmapEx& rBitmap, bool bLocalized,
+ ImageLoadFlags eFlags, sal_Int32 nScalePercentage)
+ : msName(rName)
+ , msStyle(rStyle)
+ , 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);
+
+ 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, std::unique_ptr<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()
+ {}
+
+ IconSet(const OUString & rURL)
+ : maURL(rURL)
+ {}
+ };
+
+ /// 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(const OUString &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 000000000..a937fb25f
--- /dev/null
+++ b/vcl/inc/ios/iosinst.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_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 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( tools::Rectangle& rRect );
+ SalFrame* CreateFrame( SalFrame* pParent, SalFrameStyleFlags nStyle ) override;
+ SalFrame* CreateChildFrame( SystemParentData* pParent, SalFrameStyleFlags nStyle ) override;
+};
+
+#endif // INCLUDED_VCL_INC_IOS_IOSINST_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/ios/svsys.h b/vcl/inc/ios/svsys.h
new file mode 100644
index 000000000..dcd14e79f
--- /dev/null
+++ b/vcl/inc/ios/svsys.h
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_IOS_SVSYS_H
+#define INCLUDED_VCL_INC_IOS_SVSYS_H
+
+// ?
+
+#endif // INCLUDED_VCL_INC_IOS_SVSYS_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/jobdata.hxx b/vcl/inc/jobdata.hxx
new file mode 100644
index 000000000..714bcb381
--- /dev/null
+++ b/vcl/inc/jobdata.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 .
+ */
+
+#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_nPSLevel; // 0: no override, else languagelevel to use
+ int m_nColorDevice; // 0: no override, -1 grey scale, +1 color
+ int m_nPDFDevice; // 0: no override, -1 PostScript, +1: Automatically PDF, +2: Explicitly PDF
+ 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_nPSLevel( 0 ),
+ m_nColorDevice( 0 ),
+ m_nPDFDevice( 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 );
+ void resolveDefaultBackend();
+ void setDefaultBackend(bool bUsePDF);
+
+ // creates a new buffer using new
+ // it is up to the user to delete it again
+ bool getStreamBuffer( void*& 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 000000000..ce53c0541
--- /dev/null
+++ b/vcl/inc/jobset.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_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>
+
+// 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
+ long mnPaperWidth; //< paper width (100th mm)
+ long mnPaperHeight; //< paper height (100th mm)
+ sal_uInt32 mnDriverDataLen; //< length of system specific data
+ 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);
+
+ long GetPaperWidth() const { return mnPaperWidth; }
+ void SetPaperWidth(long nWidth);
+
+ long GetPaperHeight() const { return mnPaperHeight; }
+ void SetPaperHeight(long nHeight);
+
+ sal_uInt32 GetDriverDataLen() const { return mnDriverDataLen; }
+ void SetDriverDataLen(sal_uInt32 nDriverDataLen);
+
+ const sal_uInt8* GetDriverData() const { return mpDriverData; }
+ void SetDriverData(sal_uInt8* pDriverData);
+
+ 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/jsdialogbuilder.hxx b/vcl/inc/jsdialog/jsdialogbuilder.hxx
new file mode 100644
index 000000000..d01521924
--- /dev/null
+++ b/vcl/inc/jsdialog/jsdialogbuilder.hxx
@@ -0,0 +1,133 @@
+#ifndef INCLUDED_VCL_INC_JSDIALOG_JSDIALOG_HXX
+#define INCLUDED_VCL_INC_JSDIALOG_JSDIALOG_HXX
+
+#include <vcl/weld.hxx>
+#include <comphelper/string.hxx>
+#include <vcl/sysdata.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/builder.hxx>
+#include <salvtables.hxx>
+#include <vcl/toolkit/combobox.hxx>
+#include <vcl/button.hxx>
+
+class JSDialogSender
+{
+ VclPtr<vcl::Window> m_aOwnedToplevel;
+
+public:
+ JSDialogSender(VclPtr<vcl::Window> aOwnedToplevel)
+ : m_aOwnedToplevel(aOwnedToplevel)
+ {
+ }
+
+ void notifyDialogState();
+};
+
+class VCL_DLLPUBLIC JSInstanceBuilder : public SalInstanceBuilder
+{
+public:
+ JSInstanceBuilder(weld::Widget* pParent, const OUString& rUIRoot, const OUString& rUIFile);
+ virtual std::unique_ptr<weld::Dialog> weld_dialog(const OString& id,
+ bool bTakeOwnership = true) override;
+ virtual std::unique_ptr<weld::Label> weld_label(const OString& id,
+ bool bTakeOwnership = false) override;
+ virtual std::unique_ptr<weld::Button> weld_button(const OString& id,
+ bool bTakeOwnership = false) override;
+ virtual std::unique_ptr<weld::Entry> weld_entry(const OString& id,
+ bool bTakeOwnership = false) override;
+ virtual std::unique_ptr<weld::ComboBox> weld_combo_box(const OString& id,
+ bool bTakeOwnership = false) override;
+ virtual std::unique_ptr<weld::Notebook> weld_notebook(const OString& id,
+ bool bTakeOwnership = false) override;
+};
+
+template <class BaseInstanceClass, class VclClass>
+class JSWidget : public BaseInstanceClass, public JSDialogSender
+{
+public:
+ JSWidget(VclPtr<vcl::Window> aOwnedToplevel, VclClass* pObject, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : BaseInstanceClass(pObject, pBuilder, bTakeOwnership)
+ , JSDialogSender(aOwnedToplevel)
+ {
+ }
+
+ virtual void show() override
+ {
+ BaseInstanceClass::show();
+ notifyDialogState();
+ }
+
+ virtual void hide() override
+ {
+ BaseInstanceClass::hide();
+ notifyDialogState();
+ }
+
+ virtual void set_sensitive(bool sensitive) override
+ {
+ BaseInstanceClass::set_sensitive(sensitive);
+ notifyDialogState();
+ }
+};
+
+class VCL_DLLPUBLIC JSLabel : public JSWidget<SalInstanceLabel, FixedText>
+{
+public:
+ JSLabel(VclPtr<vcl::Window> aOwnedToplevel, FixedText* pLabel, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership);
+ virtual void set_label(const OUString& rText) override;
+};
+
+class VCL_DLLPUBLIC JSButton : public JSWidget<SalInstanceButton, ::Button>
+{
+public:
+ JSButton(VclPtr<vcl::Window> aOwnedToplevel, ::Button* pButton, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership);
+};
+
+class VCL_DLLPUBLIC JSEntry : public JSWidget<SalInstanceEntry, ::Edit>
+{
+public:
+ JSEntry(VclPtr<vcl::Window> aOwnedToplevel, ::Edit* pEntry, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership);
+ virtual void set_text(const OUString& rText) override;
+};
+
+class VCL_DLLPUBLIC JSListBox : public JSWidget<SalInstanceComboBoxWithoutEdit, ::ListBox>
+{
+public:
+ JSListBox(VclPtr<vcl::Window> aOwnedToplevel, ::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;
+};
+
+class VCL_DLLPUBLIC JSComboBox : public JSWidget<SalInstanceComboBoxWithEdit, ::ComboBox>
+{
+public:
+ JSComboBox(VclPtr<vcl::Window> aOwnedToplevel, ::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(const OUString& rText) override;
+};
+
+class VCL_DLLPUBLIC JSNotebook : public JSWidget<SalInstanceNotebook, ::TabControl>
+{
+public:
+ JSNotebook(VclPtr<vcl::Window> aOwnedToplevel, ::TabControl* pControl,
+ SalInstanceBuilder* pBuilder, bool bTakeOwnership);
+
+ virtual void set_current_page(int nPage) override;
+
+ virtual void set_current_page(const OString& rIdent) override;
+
+ virtual void remove_page(const OString& rIdent) override;
+
+ virtual void insert_page(const OString& rIdent, const OUString& rLabel, int nPos) override;
+};
+
+#endif
diff --git a/vcl/inc/langboost.hxx b/vcl/inc/langboost.hxx
new file mode 100644
index 000000000..84bdb2d84
--- /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 000000000..1e4512eef
--- /dev/null
+++ b/vcl/inc/listbox.hxx
@@ -0,0 +1,608 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <vcl/toolkit/button.hxx>
+#include <vcl/floatwin.hxx>
+#include <vcl/quickselectionengine.hxx>
+#include <vcl/glyphitem.hxx>
+#include <vcl/vcllayout.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;
+ long mnHeight;
+
+ long getHeightWithMargin() const;
+
+ ImplEntryType( const OUString& rStr, const Image& rImage ) :
+ maStr( rStr ),
+ maImage( rImage ),
+ mnFlags( ListBoxEntryFlags::NONE ),
+ mnHeight( 0 )
+ {
+ mbIsSelected = false;
+ mpUserData = nullptr;
+ }
+
+ ImplEntryType( const OUString& rStr ) :
+ maStr( rStr ),
+ 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();
+
+ sal_Int32 FindMatchingEntry( const OUString& rStr, sal_Int32 nStart, bool bLazy ) const;
+ sal_Int32 FindEntry( const OUString& 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 )
+ long GetAddedHeight( sal_Int32 nEndIndex, sal_Int32 nBeginIndex ) const;
+ 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 );
+};
+
+class ImplListBoxWindow : public Control, public vcl::ISearchableStringList
+{
+private:
+ std::unique_ptr<ImplEntryList> mpEntryList; ///< EntryList
+ tools::Rectangle maFocusRect;
+
+ Size maUserItemSize;
+
+ long mnMaxTxtHeight; ///< Maximum height of a text item
+ long mnMaxTxtWidth; ///< Maximum width of a text item
+ ///< Entry without Image
+ long mnMaxImgTxtWidth;///< Maximum width of a text item
+ ///< Entry AND Image
+ long mnMaxImgWidth; ///< Maximum width of an image item
+ long mnMaxImgHeight; ///< Maximum height of an image item
+ long mnMaxWidth; ///< Maximum width of an entry
+ 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
+ long mnLeft; ///< output from column on
+ 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 mbStackMode : 1; ///< StackSelection
+ 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 mbMirroring : 1; ///< pb: #106948# explicit mirroring for calc
+ 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;
+
+protected:
+ 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;
+
+ ImplEntryList* GetEntryList() const { return mpEntryList.get(); }
+
+ 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;
+
+ long GetLeftIndent() const { return mnLeft; }
+ void SetLeftIndent( long n );
+ void ScrollHorz( 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, bool bStackMode ) { mbMulti = bMulti; mbStackMode = bStackMode; }
+ bool IsMultiSelectionEnabled() const { return mbMulti; }
+
+ void SetMultiSelectionSimpleMode( bool bSimple ) { mbSimpleMode = bSimple; }
+
+ void EnableMouseMoveSelect( bool bMouseMoveSelect ) { mbMouseMoveSelect = bMouseMoveSelect; }
+ bool IsMouseMoveSelect() const { return mbMouseMoveSelect||mbStackMode; }
+
+ Size CalcSize(sal_Int32 nMaxLines) const;
+ tools::Rectangle GetBoundingRectangle( sal_Int32 nItem ) const;
+
+ long GetEntryHeight() const { return mnMaxHeight; }
+ long GetEntryHeightWithMargin() const;
+ 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;
+
+ /// pb: #106948# explicit mirroring for calc
+ void EnableMirroring() { mbMirroring = true; }
+ bool IsMirroring() const { return mbMirroring; }
+
+ bool GetEdgeBlending() const { return mbEdgeBlending; }
+ void SetEdgeBlending(bool bNew) { mbEdgeBlending = bNew; }
+
+ using Control::ImplInitSettings;
+ virtual void ApplySettings(vcl::RenderContext& rRenderContext) override;
+
+protected:
+ // 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 : 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
+
+protected:
+ 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 );
+
+ /**
+ * 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 ); }
+
+ 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, bool bStackMode ) { maLBWindow->EnableMultiSelection( bMulti, bStackMode ); }
+ 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 ); }
+ long GetEntryHeight() const { return maLBWindow->GetEntryHeight(); }
+ long GetEntryHeightWithMargin() const{ return maLBWindow->GetEntryHeightWithMargin(); }
+ 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 SetMRUEntries( const OUString& 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);
+
+ /// pb: #106948# explicit mirroring for calc
+ void EnableMirroring() { maLBWindow->EnableMirroring(); }
+};
+
+class ImplListBoxFloatingWindow : public FloatingWindow
+{
+private:
+ VclPtr<ImplListBox> mpImplLB;
+ Size maPrefSz;
+ sal_uInt16 mnDDLineCount;
+ sal_Int32 mnPopupModeStartSaveSelection;
+ bool mbAutoWidth;
+
+protected:
+ 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();
+ void StartFloat( bool bStartTracking );
+
+ virtual void setPosSizePixel( long nX, long nY,
+ long nWidth, 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 : 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);
+protected:
+ 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;
+ virtual bool PreNotify( NotifyEvent& rNEvt ) 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 : 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/messagedialog.hxx b/vcl/inc/messagedialog.hxx
new file mode 100644
index 000000000..679767be7
--- /dev/null
+++ b/vcl/inc/messagedialog.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/.
+ */
+
+#ifndef INCLUDED_VCL_INC_MESSAGEDIALOG_HXX
+#define INCLUDED_VCL_INC_MESSAGEDIALOG_HXX
+
+#include <vcl/toolkit/dialog.hxx>
+#include <vcl/layout.hxx>
+#include <vcl/fixed.hxx>
+
+class MessageDialog : 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, const OUString& rMessage, VclMessageType eMessageType,
+ VclButtonsType eButtonsType);
+ virtual bool set_property(const OString& 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/BufferObject.hxx b/vcl/inc/opengl/BufferObject.hxx
new file mode 100644
index 000000000..396de89de
--- /dev/null
+++ b/vcl/inc/opengl/BufferObject.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/.
+ *
+ */
+
+#ifndef INCLUDED_VCL_INC_OPENGL_BUFFEROBJECT_H
+#define INCLUDED_VCL_INC_OPENGL_BUFFEROBJECT_H
+
+namespace vcl
+{
+
+template<typename TYPE, GLenum BUFFER_TYPE>
+class BufferObject
+{
+private:
+ GLuint mId;
+
+public:
+ BufferObject()
+ : mId(0)
+ {
+ glGenBuffers(1, &mId);
+ CHECK_GL_ERROR();
+ }
+
+ virtual ~BufferObject()
+ {
+ if (mId)
+ {
+ glDeleteBuffers(1, &mId);
+ CHECK_GL_ERROR();
+ mId = 0;
+ }
+ }
+
+ void bind()
+ {
+ if (mId)
+ {
+ glBindBuffer(BUFFER_TYPE, mId);
+ CHECK_GL_ERROR();
+ }
+ }
+
+ void unbind()
+ {
+ if (mId)
+ {
+ glBindBuffer(BUFFER_TYPE, 0);
+ CHECK_GL_ERROR();
+ }
+ }
+
+ void upload(const std::vector<TYPE>& rData)
+ {
+ if (mId)
+ {
+ bind();
+ glBufferData(BUFFER_TYPE, sizeof(TYPE) * rData.size(), rData.data(), GL_STATIC_DRAW);
+ CHECK_GL_ERROR();
+ }
+ }
+
+};
+
+template<typename TYPE>
+class VertexBufferObject final : public BufferObject<TYPE, GL_ARRAY_BUFFER>
+{
+};
+
+class IndexBufferObject final : public BufferObject<GLuint, GL_ELEMENT_ARRAY_BUFFER>
+{
+};
+
+} // end vcl
+
+#endif // INCLUDED_VCL_INC_OPENGL_BUFFEROBJECT_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/opengl/DeviceInfo.hxx b/vcl/inc/opengl/DeviceInfo.hxx
new file mode 100644
index 000000000..b0ad3a6cf
--- /dev/null
+++ b/vcl/inc/opengl/DeviceInfo.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/.
+ */
+
+#ifndef INCLUDED_VCL_INC_OPENGL_DEVICEINFO_HXX
+#define INCLUDED_VCL_INC_OPENGL_DEVICEINFO_HXX
+
+class OpenGLDeviceInfo
+{
+public:
+ virtual ~OpenGLDeviceInfo() = 0;
+
+ virtual bool isDeviceBlocked() = 0;
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/opengl/FixedTextureAtlas.hxx b/vcl/inc/opengl/FixedTextureAtlas.hxx
new file mode 100644
index 000000000..e576fcb54
--- /dev/null
+++ b/vcl/inc/opengl/FixedTextureAtlas.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/.
+ *
+ */
+
+#ifndef INCLUDED_VCL_INC_OPENGL_FIXEDTEXTUREATLAS_HXX
+#define INCLUDED_VCL_INC_OPENGL_FIXEDTEXTUREATLAS_HXX
+
+#include <memory>
+#include <opengl/texture.hxx>
+
+struct FixedTexture;
+
+class FixedTextureAtlasManager final
+{
+ std::vector<std::unique_ptr<FixedTexture>> maFixedTextures;
+
+ int mWidthFactor;
+ int mHeightFactor;
+ int mSubTextureSize;
+
+ void CreateNewTexture();
+
+ FixedTextureAtlasManager( const FixedTextureAtlasManager& ) = delete;
+ FixedTextureAtlasManager& operator=( const FixedTextureAtlasManager& ) = delete;
+
+public:
+ FixedTextureAtlasManager(int nWidthFactor, int nHeightFactor, int nTextureSize);
+ ~FixedTextureAtlasManager();
+
+ OpenGLTexture InsertBuffer(int nWidth, int nHeight, int nFormat, int nType, sal_uInt8 const * pData);
+ OpenGLTexture Reserve(int nWidth, int nHeight);
+
+ int GetSubtextureSize() const
+ {
+ return mSubTextureSize;
+ }
+};
+
+#endif // INCLUDED_VCL_INC_OPENGL_FIXEDTEXTUREATLAS_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/opengl/LineRenderUtils.hxx b/vcl/inc/opengl/LineRenderUtils.hxx
new file mode 100644
index 000000000..8d97fa3ee
--- /dev/null
+++ b/vcl/inc/opengl/LineRenderUtils.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/.
+ *
+ */
+
+#ifndef INCLUDED_VCL_INC_OPENGL_LINERENDERUTILS_H
+#define INCLUDED_VCL_INC_OPENGL_LINERENDERUTILS_H
+
+#include <opengl/RenderList.hxx>
+
+namespace vcl
+{
+class LineBuilder
+{
+private:
+ std::vector<Vertex>& mrVertices;
+ std::vector<GLuint>& mrIndices;
+ GLubyte mR, mG, mB, mA;
+ GLfloat mfLineWidth;
+ GLfloat mfLineWidthAndAA;
+ size_t mnInitialIndexSize;
+ bool mbIncomplete;
+
+public:
+ LineBuilder(std::vector<Vertex>& rVertices, std::vector<GLuint>& rIndices,
+ Color nColor, GLfloat fTransparency,
+ GLfloat fLineWidth, bool bUseAA);
+
+ void appendLineSegment(const glm::vec2& rPoint1, const glm::vec2& rNormal1, GLfloat aExtrusion1,
+ const glm::vec2& rPoint2, const glm::vec2& rNormal2, GLfloat aExtrusion2);
+
+ void appendLine(const glm::vec2& rPoint1, const glm::vec2& rPoint2);
+
+ void appendAndConnectLinePoint(const glm::vec2& rPoint, const glm::vec2& aNormal, GLfloat aExtrusion);
+
+ void appendMiterJoint(glm::vec2 const& point, const glm::vec2& prevLineVector,
+ glm::vec2 const& nextLineVector);
+ void appendBevelJoint(glm::vec2 const& point, const glm::vec2& prevLineVector,
+ const glm::vec2& nextLineVector);
+ void appendRoundJoint(glm::vec2 const& point, const glm::vec2& prevLineVector,
+ const glm::vec2& nextLineVector);
+ void appendRoundLineCapVertices(const glm::vec2& rPoint1, const glm::vec2& rPoint2);
+ void appendSquareLineCapVertices(const glm::vec2& rPoint1, const glm::vec2& rPoint2);
+};
+
+} // end vcl
+
+#endif // INCLUDED_VCL_INC_OPENGL_LINERENDERUTILS_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/opengl/PackedTextureAtlas.hxx b/vcl/inc/opengl/PackedTextureAtlas.hxx
new file mode 100644
index 000000000..7a283aa45
--- /dev/null
+++ b/vcl/inc/opengl/PackedTextureAtlas.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_OPENGL_PACKEDTEXTUREATLAS_HXX
+#define INCLUDED_VCL_INC_OPENGL_PACKEDTEXTUREATLAS_HXX
+
+#include <memory>
+#include <opengl/texture.hxx>
+
+struct PackedTexture;
+
+/**
+ * Pack textures into one texture atlas.
+ *
+ * This is based on algorithm described in [1] and is an
+ * adaptation of "texture atlas generator" from [2].
+ *
+ * [1]: http://www.blackpawn.com/texts/lightmaps/
+ * [2]: https://github.com/lukaszdk/texture-atlas-generator
+ *
+ */
+class VCL_DLLPUBLIC PackedTextureAtlasManager final
+{
+ std::vector<std::unique_ptr<PackedTexture>> maPackedTextures;
+
+ int mnTextureWidth;
+ int mnTextureHeight;
+
+ void CreateNewTexture();
+
+ PackedTextureAtlasManager( const PackedTextureAtlasManager& ) = delete;
+ PackedTextureAtlasManager& operator=( const PackedTextureAtlasManager& ) = delete;
+
+public:
+
+ /**
+ * nTextureWidth and nTextureHeight are the dimensions of the common texture(s)
+ * nTextureLimit is the maximum limit of that a texture atlas can have (0 or less - unlimited)
+ */
+ PackedTextureAtlasManager(int nTextureWidth, int nTextureHeight);
+ ~PackedTextureAtlasManager();
+
+ OpenGLTexture InsertBuffer(int nWidth, int nHeight, int nFormat, int nType, sal_uInt8 const * pData);
+ OpenGLTexture Reserve(int nWidth, int nHeight);
+ std::vector<GLuint> ReduceTextureNumber(int nMaxNumberOfTextures);
+};
+
+#endif // INCLUDED_VCL_INC_OPENGL_PACKEDTEXTUREATLAS_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/opengl/RenderList.hxx b/vcl/inc/opengl/RenderList.hxx
new file mode 100644
index 000000000..213a2f43b
--- /dev/null
+++ b/vcl/inc/opengl/RenderList.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/.
+ *
+ */
+
+#ifndef INCLUDED_VCL_INC_OPENGL_RENDERLIST_H
+#define INCLUDED_VCL_INC_OPENGL_RENDERLIST_H
+
+#include <unordered_map>
+
+#include <glm/glm.hpp>
+
+#include <vcl/salgtype.hxx>
+#include <basegfx/range/b2drange.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+
+#include <opengl/texture.hxx>
+
+#include <com/sun/star/drawing/LineCap.hpp>
+
+struct Vertex
+{
+ glm::vec2 position;
+ glm::vec4 color;
+ glm::vec4 lineData;
+};
+
+static_assert(sizeof(Vertex) == (2*4 + 4*4 + 4*4), "Vertex struct has wrong size/alignment");
+
+
+struct RenderParameters
+{
+ std::vector<Vertex> maVertices;
+ std::vector<GLuint> maIndices;
+};
+
+struct RenderTextureParameters
+{
+ std::vector<GLfloat> maVertices;
+ std::vector<GLfloat> maTextureCoords;
+ std::vector<GLubyte> maColors;
+ OpenGLTexture maTexture;
+};
+
+struct RenderEntry
+{
+ basegfx::B2DRange maOverlapTrackingRectangle;
+
+ RenderParameters maTriangleParameters;
+ RenderParameters maLineParameters;
+
+ std::unordered_map<GLuint, RenderTextureParameters> maTextureParametersMap;
+
+ bool hasTriangles() const
+ {
+ return !maTriangleParameters.maVertices.empty();
+ }
+
+ bool hasLines() const
+ {
+ return !maLineParameters.maVertices.empty();
+ }
+
+ bool hasTextures() const
+ {
+ return !maTextureParametersMap.empty();
+ }
+};
+
+class RenderList
+{
+private:
+ std::vector<RenderEntry> maRenderEntries;
+ std::vector<basegfx::B2DRange> maRectangles;
+
+ bool doesOverlap(const basegfx::B2DRange& rDrawRectangle)
+ {
+ if (!maRenderEntries.back().maOverlapTrackingRectangle.overlaps(rDrawRectangle))
+ return false;
+
+ for (const basegfx::B2DRange& rRectangle : maRectangles)
+ {
+ if (rRectangle.overlaps(rDrawRectangle))
+ return true;
+ }
+ return false;
+ }
+
+ void checkOverlapping(const basegfx::B2DRange& rDrawRectangle)
+ {
+ if (maRenderEntries.empty() || doesOverlap(rDrawRectangle))
+ {
+ maRenderEntries.emplace_back();
+ maRenderEntries.back().maOverlapTrackingRectangle = rDrawRectangle;
+
+ maRectangles.clear();
+ maRectangles.reserve(30);
+ maRectangles.push_back(rDrawRectangle);
+ }
+ else
+ {
+ maRenderEntries.back().maOverlapTrackingRectangle.expand(rDrawRectangle);
+
+ if (maRectangles.size() < 30)
+ {
+ maRectangles.push_back(rDrawRectangle);
+ }
+ else
+ {
+ basegfx::B2DRange aTempRectangle(maRectangles[0]);
+ aTempRectangle.expand(rDrawRectangle);
+ double minArea = aTempRectangle.getWidth() * aTempRectangle.getHeight();
+ size_t index = 0;
+
+ double area;
+ for (size_t i = 1; i < maRectangles.size(); ++i)
+ {
+ aTempRectangle = maRectangles[i];
+ aTempRectangle.expand(rDrawRectangle);
+ area = aTempRectangle.getWidth() * aTempRectangle.getHeight();
+ if (area < minArea)
+ index = i;
+ }
+ maRectangles[index].expand(rDrawRectangle);
+ }
+ }
+ }
+
+public:
+
+ RenderList() = default;
+
+ bool empty()
+ {
+ return maRenderEntries.empty();
+ }
+
+ void clear()
+ {
+ maRenderEntries.clear();
+ }
+
+ std::vector<RenderEntry>& getEntries()
+ {
+ return maRenderEntries;
+ }
+
+ VCL_DLLPUBLIC void addDrawTextureWithMaskColor(OpenGLTexture const & rTexture, Color nColor, const SalTwoRect& r2Rect);
+
+ void addDrawPixel(long nX, long nY, Color nColor);
+
+ void addDrawRectangle(long nX, long nY, long nWidth, long nHeight, double fTransparency,
+ Color nLineColor, Color nFillColor);
+
+ void addDrawLine(long nX1, long nY1, long nX2, long nY2, Color nLineColor, bool bUseAA);
+
+ void addDrawPolyPolygon(const basegfx::B2DPolyPolygon& rPolyPolygon, double fTransparency,
+ Color nLineColor, Color nFillColor, bool bUseAA);
+
+ void addDrawPolyLine(const basegfx::B2DPolygon& rPolygon, double fTransparency,
+ double fLineWidth, basegfx::B2DLineJoin eLineJoin,
+ css::drawing::LineCap eLineCap, double fMiterMinimumAngle,
+ Color nLineColor, bool bUseAA);
+};
+
+#endif // INCLUDED_VCL_INC_OPENGL_RENDERLIST_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/opengl/RenderState.hxx b/vcl/inc/opengl/RenderState.hxx
new file mode 100644
index 000000000..3a89344e2
--- /dev/null
+++ b/vcl/inc/opengl/RenderState.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/.
+ *
+ */
+
+#ifndef INCLUDED_VCL_INC_OPENGL_RENDER_STATE_H
+#define INCLUDED_VCL_INC_OPENGL_RENDER_STATE_H
+
+#include <opengl/TextureState.hxx>
+
+template<GLenum ENUM_TYPE, typename TYPE>
+class GenericCapabilityState
+{
+protected:
+ bool mbTest;
+
+ GenericCapabilityState()
+ : mbTest(false)
+ {
+ }
+
+ static bool readState()
+ {
+ return glIsEnabled(ENUM_TYPE);
+ }
+
+public:
+ void sync()
+ {
+ mbTest = readState();
+ }
+
+ void enable()
+ {
+ if (!mbTest)
+ {
+ glEnable(ENUM_TYPE);
+ CHECK_GL_ERROR();
+ mbTest = true;
+ }
+ else
+ {
+ VCL_GL_INFO(TYPE::className() << ": enable called but already enabled");
+ }
+#ifdef DBG_UTIL
+ checkState();
+#endif
+ }
+
+ void disable()
+ {
+ if (mbTest)
+ {
+ glDisable(ENUM_TYPE);
+ CHECK_GL_ERROR();
+ mbTest = false;
+ }
+ else
+ {
+ VCL_GL_INFO(TYPE::className() << ": disable called but already disabled");
+ }
+#ifdef DBG_UTIL
+ checkState();
+#endif
+ }
+
+#ifdef DBG_UTIL
+ void checkState()
+ {
+ bool bRealState = readState();
+ if (mbTest != bRealState)
+ {
+ VCL_GL_INFO(TYPE::className() << " mismatch! "
+ << "Expected: " << (mbTest ? "enabled" : "disabled")
+ << " but is: " << (bRealState ? "enabled" : "disabled"));
+ }
+ }
+#endif
+};
+
+class ScissorState : public GenericCapabilityState<GL_SCISSOR_TEST, ScissorState>
+{
+private:
+ int mX;
+ int mY;
+ int mWidth;
+ int mHeight;
+
+public:
+ static std::string className() { return std::string("ScissorState"); }
+
+ ScissorState()
+ : mX(0)
+ , mY(0)
+ , mWidth(0)
+ , mHeight(0)
+ {}
+
+ void set(int x, int y, int width, int height)
+ {
+ if (x != mX || y != mY || width != mWidth || height != mHeight)
+ {
+ glScissor(x, y, width, height);
+ CHECK_GL_ERROR();
+
+ mX = x;
+ mY = y;
+ mWidth = width;
+ mHeight = height;
+ }
+ }
+};
+
+class StencilState : public GenericCapabilityState<GL_STENCIL_TEST, StencilState>
+{
+public:
+ static std::string className() { return std::string("StencilState"); }
+};
+
+class BlendState : public GenericCapabilityState<GL_BLEND, BlendState>
+{
+ GLenum mnSourceMode;
+ GLenum mnDestinationMode;
+public:
+ BlendState()
+ : mnSourceMode(GL_ZERO)
+ , mnDestinationMode(GL_ZERO)
+ {}
+
+ static std::string className() { return std::string("BlendState"); }
+
+ void func(GLenum nSource, GLenum nDestination)
+ {
+ if (mnSourceMode != nSource || mnDestinationMode != nDestination)
+ {
+ glBlendFunc(nSource, nDestination);
+ CHECK_GL_ERROR();
+ mnSourceMode = nSource;
+ mnDestinationMode = nDestination;
+ }
+ }
+};
+
+class RenderState
+{
+ TextureState maTexture;
+ ScissorState maScissor;
+ StencilState maStencil;
+ BlendState maBlend;
+
+ tools::Rectangle maCurrentViewport;
+
+public:
+ RenderState()
+ {}
+
+ void viewport(tools::Rectangle aViewPort)
+ {
+ if (aViewPort != maCurrentViewport)
+ {
+ glViewport(aViewPort.Left(), aViewPort.Top(), aViewPort.GetWidth(), aViewPort.GetHeight());
+ CHECK_GL_ERROR();
+ maCurrentViewport = aViewPort;
+ }
+ }
+
+ TextureState& texture() { return maTexture; }
+ ScissorState& scissor() { return maScissor; }
+ StencilState& stencil() { return maStencil; }
+ BlendState& blend() { return maBlend; }
+
+ void sync()
+ {
+ VCL_GL_INFO("RenderState::sync");
+ maScissor.sync();
+ maStencil.sync();
+ maBlend.sync();
+ }
+};
+
+#endif // INCLUDED_VCL_INC_OPENGL_RENDER_STATE_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/opengl/TextureState.hxx b/vcl/inc/opengl/TextureState.hxx
new file mode 100644
index 000000000..fbf2b9cee
--- /dev/null
+++ b/vcl/inc/opengl/TextureState.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/.
+ *
+ */
+
+#ifndef INCLUDED_VCL_INC_OPENGL_TEXTURE_STATE_H
+#define INCLUDED_VCL_INC_OPENGL_TEXTURE_STATE_H
+
+#include <vcl/opengl/OpenGLHelper.hxx>
+
+class TextureState
+{
+private:
+ GLuint mnCurrentTextureUnit;
+ std::vector<GLuint> maBoundTextures;
+
+public:
+ TextureState()
+ : mnCurrentTextureUnit(0)
+ , maBoundTextures(4, 0)
+ {}
+
+ static void generate(GLuint& nTexture)
+ {
+ glGenTextures(1, &nTexture);
+ CHECK_GL_ERROR();
+ }
+
+ void active(GLuint nTextureUnit)
+ {
+ if (mnCurrentTextureUnit != nTextureUnit)
+ {
+ glActiveTexture(GL_TEXTURE0 + nTextureUnit);
+ CHECK_GL_ERROR();
+ mnCurrentTextureUnit = nTextureUnit;
+ }
+
+ }
+
+ void bind(GLuint nTexture)
+ {
+ if (maBoundTextures[mnCurrentTextureUnit] != nTexture)
+ {
+ glBindTexture(GL_TEXTURE_2D, nTexture);
+ CHECK_GL_ERROR();
+ maBoundTextures[mnCurrentTextureUnit] = nTexture;
+ }
+ }
+
+ void unbindAndDelete(GLuint nTexture)
+ {
+ unbind(nTexture);
+ glDeleteTextures(1, &nTexture);
+ }
+
+ void unbind(GLuint nTexture)
+ {
+ for (size_t i = 0; i < maBoundTextures.size(); i++)
+ {
+ if (nTexture == maBoundTextures[i])
+ maBoundTextures[i] = 0;
+ }
+ }
+
+};
+
+
+
+#endif // INCLUDED_VCL_INC_OPENGL_TEXTURE_STATE_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/opengl/VertexUtils.hxx b/vcl/inc/opengl/VertexUtils.hxx
new file mode 100644
index 000000000..910b8725a
--- /dev/null
+++ b/vcl/inc/opengl/VertexUtils.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/.
+ *
+ */
+
+#ifndef INCLUDED_VCL_INC_OPENGL_VERTEXUTILS_H
+#define INCLUDED_VCL_INC_OPENGL_VERTEXUTILS_H
+
+#include <basegfx/numeric/ftools.hxx>
+#include <epoxy/gl.h>
+#include <glm/gtx/norm.hpp>
+#include <tools/color.hxx>
+#include <vector>
+
+namespace vcl
+{
+namespace vertex
+{
+
+template<GLenum TYPE>
+inline void addRectangle(std::vector<GLfloat>& rVertices, GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2);
+
+template<>
+inline void addRectangle<GL_TRIANGLES>(std::vector<GLfloat>& rVertices, GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2)
+{
+ rVertices.insert(rVertices.end(), {
+ x1, y1, x2, y1, x1, y2,
+ x1, y2, x2, y1, x2, y2
+ });
+}
+
+template<>
+inline void addRectangle<GL_TRIANGLE_FAN>(std::vector<GLfloat>& rVertices, GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2)
+{
+ rVertices.insert(rVertices.end(), {
+ x1, y2, x1, y1,
+ x2, y1, x2, y2
+ });
+}
+
+inline void createColor(Color nColor, GLfloat fTransparency, GLubyte& nR, GLubyte& nG, GLubyte& nB, GLubyte& nA)
+{
+ nR = nColor.GetRed();
+ nG = nColor.GetGreen();
+ nB = nColor.GetBlue();
+ nA = (1.0f - fTransparency) * 255.0f;
+}
+
+template<GLenum TYPE>
+inline void addQuadColors(std::vector<GLubyte>& rColors, Color nColor, GLfloat fTransparency);
+
+template<>
+inline void addQuadColors<GL_TRIANGLES>(std::vector<GLubyte>& rColors, Color nColor, GLfloat fTransparency)
+{
+ GLubyte nR, nG, nB, nA;
+ createColor(nColor, fTransparency, nR, nG, nB, nA);
+
+ rColors.insert(rColors.end(), {
+ nR, nG, nB, nA,
+ nR, nG, nB, nA,
+ nR, nG, nB, nA,
+ nR, nG, nB, nA,
+ nR, nG, nB, nA,
+ nR, nG, nB, nA,
+ });
+}
+
+inline void addLineSegmentVertices(std::vector<GLfloat>& rVertices, std::vector<GLfloat>& rExtrusionVectors,
+ glm::vec2 prevPoint, glm::vec2 prevExtrusionVector, GLfloat prevLength,
+ glm::vec2 currPoint, glm::vec2 currExtrusionVector, GLfloat currLength)
+{
+ rVertices.insert(rVertices.end(), {
+ prevPoint.x, prevPoint.y,
+ prevPoint.x, prevPoint.y,
+ currPoint.x, currPoint.y,
+ currPoint.x, currPoint.y,
+ prevPoint.x, prevPoint.y,
+ currPoint.x, currPoint.y,
+ });
+
+ rExtrusionVectors.insert(rExtrusionVectors.end(), {
+ -prevExtrusionVector.x, -prevExtrusionVector.y, -prevLength,
+ prevExtrusionVector.x, prevExtrusionVector.y, prevLength,
+ -currExtrusionVector.x, -currExtrusionVector.y, -currLength,
+ -currExtrusionVector.x, -currExtrusionVector.y, -currLength,
+ prevExtrusionVector.x, prevExtrusionVector.y, prevLength,
+ currExtrusionVector.x, currExtrusionVector.y, currLength,
+ });
+}
+
+inline glm::vec2 normalize(const glm::vec2& vector)
+{
+ if (glm::length(vector) > 0.0)
+ return glm::normalize(vector);
+ return vector;
+}
+
+inline glm::vec2 perpendicular(const glm::vec2& vector)
+{
+ return glm::vec2(-vector.y, vector.x);
+}
+
+inline float lineVectorAngle(const glm::vec2& previous, const glm::vec2& next)
+{
+ float angle = std::atan2(previous.x * next.y - previous.y * next.x,
+ previous.x * next.x + previous.y * next.y);
+
+ return F_PI - std::fabs(angle);
+}
+
+}} // end vcl::vertex
+
+#endif // INCLUDED_VCL_INC_OPENGL_VERTEXUTILS_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/opengl/framebuffer.hxx b/vcl/inc/opengl/framebuffer.hxx
new file mode 100644
index 000000000..4445e6198
--- /dev/null
+++ b/vcl/inc/opengl/framebuffer.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_OPENGL_FRAMEBUFFER_H
+#define INCLUDED_VCL_INC_OPENGL_FRAMEBUFFER_H
+
+#include <vcl/dllapi.h>
+
+#include <opengl/texture.hxx>
+
+class OpenGLFramebuffer final
+{
+private:
+ GLuint mnId;
+ int mnWidth;
+ int mnHeight;
+ GLuint mnAttachedTexture;
+
+public:
+ OpenGLFramebuffer();
+ ~OpenGLFramebuffer();
+
+ int GetWidth() const { return mnWidth; };
+ int GetHeight() const { return mnHeight; };
+
+ void Bind(GLenum eTarget = GL_FRAMEBUFFER);
+
+ static void Unbind(GLenum eTarget = GL_FRAMEBUFFER);
+
+ VCL_DLLPUBLIC bool IsFree() const;
+ bool IsAttached( GLuint nTexture ) const;
+ bool IsAttached( const OpenGLTexture& rTexture ) const;
+ void AttachTexture( const OpenGLTexture& rTexture );
+ void DetachTexture();
+
+public:
+ OpenGLFramebuffer* mpPrevFramebuffer;
+};
+
+#endif // INCLUDED_VCL_INC_OPENGL_FRAMEBUFFER_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/opengl/gdiimpl.hxx b/vcl/inc/opengl/gdiimpl.hxx
new file mode 100644
index 000000000..a6de106a6
--- /dev/null
+++ b/vcl/inc/opengl/gdiimpl.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_OPENGLGDIIMPL_HXX
+#define INCLUDED_VCL_OPENGLGDIIMPL_HXX
+
+#include <vcl/dllapi.h>
+#include <vcl/opengl/OpenGLContext.hxx>
+
+#include <regionband.hxx>
+#include <salgeom.hxx>
+#include <salgdiimpl.hxx>
+#include <opengl/program.hxx>
+#include <opengl/texture.hxx>
+#include <opengl/RenderList.hxx>
+
+#include <memory>
+
+class SalFrame;
+class SalVirtualDevice;
+class OpenGLTests;
+
+namespace basegfx
+{
+class B2DTrapezoid;
+};
+
+namespace tools
+{
+ class Polygon;
+ class PolyPolygon;
+}
+
+struct TextureCombo
+{
+ std::unique_ptr<OpenGLTexture> mpTexture;
+ std::unique_ptr<OpenGLTexture> mpMask;
+};
+
+class OpenGLFlushIdle;
+
+class VCL_DLLPUBLIC OpenGLSalGraphicsImpl : public SalGraphicsImpl
+{
+ friend class OpenGLTests;
+protected:
+
+ /// This context is solely for blitting maOffscreenTex
+ rtl::Reference<OpenGLContext> mpWindowContext;
+
+ /// This context is whatever is most convenient to render
+ /// to maOffscreenTex with.
+ rtl::Reference<OpenGLContext> mpContext;
+
+ SalGraphics& mrParent;
+ /// Pointer to the SalFrame or SalVirtualDevice
+ SalGeometryProvider* mpProvider;
+ OpenGLProgram* mpProgram;
+
+ /// This idle handler is used to swap buffers after rendering.
+ std::unique_ptr<OpenGLFlushIdle> mpFlush;
+
+ // clipping
+ vcl::Region maClipRegion;
+ bool mbUseScissor;
+ bool mbUseStencil;
+
+ bool mbXORMode;
+
+ bool mbAcquiringOpenGLContext;
+
+ /**
+ * All rendering happens to this off-screen texture. For
+ * non-virtual devices, ie. windows - we will blit it and
+ * swapBuffers later.
+ */
+ OpenGLTexture maOffscreenTex;
+
+ Color mnLineColor;
+ Color mnFillColor;
+#ifdef DBG_UTIL
+ bool mProgramIsSolidColor;
+#endif
+ sal_uInt32 mnDrawCount;
+ sal_uInt32 mnDrawCountAtFlush;
+ Color mProgramSolidColor;
+ double mProgramSolidTransparency;
+
+ std::unique_ptr<RenderList> mpRenderList;
+
+ void ImplInitClipRegion();
+ void ImplSetClipBit( const vcl::Region& rClip, GLuint nMask );
+ void ImplDrawLineAA( double nX1, double nY1, double nX2, double nY2, bool edge );
+ void CheckOffscreenTexture();
+
+ void ApplyProgramMatrices(float fPixelOffset = 0.0);
+
+public:
+ bool UseProgram( const OUString& rVertexShader, const OUString& rFragmentShader, const OString& preamble = "" );
+ bool UseSolid( Color nColor, sal_uInt8 nTransparency );
+ bool UseSolid( Color nColor, double fTransparency );
+ bool UseSolid( Color nColor );
+ void UseSolid();
+ bool UseLine(Color nColor, double fTransparency, GLfloat fLineWidth, bool bUseAA);
+ void UseLine(GLfloat fLineWidth, bool bUseAA);
+ bool UseInvert50();
+ bool UseInvert(SalInvert nFlags);
+
+ void DrawConvexPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry, bool blockAA = false );
+ void DrawConvexPolygon( const tools::Polygon& rPolygon, bool blockAA );
+ void DrawTrapezoid( const basegfx::B2DTrapezoid& trapezoid, bool blockAA );
+ void DrawRect( long nX, long nY, long nWidth, long nHeight );
+ void DrawRect( const tools::Rectangle& rRect );
+ void DrawPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry );
+ void DrawLineSegment(float x1, float y1, float x2, float y2);
+ void DrawPolyPolygon( const basegfx::B2DPolyPolygon& rPolyPolygon, bool blockAA = false );
+ void DrawRegionBand( const RegionBand& rRegion );
+ void DrawTextureRect( const SalTwoRect& rPosAry );
+ void DrawTexture( OpenGLTexture& rTexture, const SalTwoRect& rPosAry, bool bInverted = false );
+ void DrawTransformedTexture( OpenGLTexture& rTexture, OpenGLTexture& rMask, const basegfx::B2DPoint& rNull, const basegfx::B2DPoint& rX, const basegfx::B2DPoint& rY );
+ void DrawAlphaTexture( OpenGLTexture& rTexture, const SalTwoRect& rPosAry, bool bInverted, bool pPremultiplied );
+ void DrawTextureDiff( OpenGLTexture& rTexture, OpenGLTexture& rMask, const SalTwoRect& rPosAry, bool bInverted );
+ void DrawTextureWithMask( OpenGLTexture& rTexture, OpenGLTexture& rMask, const SalTwoRect& rPosAry );
+ void DrawBlendedTexture( OpenGLTexture& rTexture, OpenGLTexture& rMask, OpenGLTexture& rAlpha, const SalTwoRect& rPosAry );
+ void DrawMask( OpenGLTexture& rTexture, Color nMaskColor, const SalTwoRect& rPosAry );
+ void DrawLinearGradient( const Gradient& rGradient, const tools::Rectangle& rRect );
+ void DrawAxialGradient( const Gradient& rGradient, const tools::Rectangle& rRect );
+ void DrawRadialGradient( const Gradient& rGradient, const tools::Rectangle& rRect );
+
+ void FlushDeferredDrawing();
+ void FlushLinesOrTriangles(DrawShaderType eType, RenderParameters const & rParameters);
+
+public:
+ // get the width of the device
+ GLfloat GetWidth() const { return mpProvider ? mpProvider->GetWidth() : 1; }
+
+ // get the height of the device
+ GLfloat GetHeight() const { return mpProvider ? mpProvider->GetHeight() : 1; }
+
+ /**
+ * check whether this instance is used for offscreen (Virtual Device)
+ * rendering ie. does it need its own context.
+ */
+ bool IsOffscreen() const { return mpProvider == nullptr || mpProvider->IsOffScreen(); }
+
+ /// Oddly not all operations obey the XOR option.
+ enum XOROption { IGNORE_XOR, IMPLEMENT_XOR };
+
+ // initialize pre-draw state
+ void InitializePreDrawState(XOROption eOpt);
+
+ // operations to do before painting
+ void PreDraw(XOROption eOpt = IGNORE_XOR);
+
+ // operations to do after painting
+ void PostDraw();
+
+ void PostBatchDraw();
+
+protected:
+ bool AcquireContext(bool bForceCreate = false);
+ void ReleaseContext();
+
+ /// create a new context for rendering to the underlying window
+ virtual rtl::Reference<OpenGLContext> CreateWinContext() = 0;
+
+ /// check whether the given context can be used for off-screen rendering
+ static bool UseContext( const rtl::Reference<OpenGLContext> &pContext )
+ {
+ return pContext->isInitialized() && // not released by the OS etc.
+ pContext->isVCLOnly();
+ }
+
+public:
+ OpenGLSalGraphicsImpl(SalGraphics& pParent, SalGeometryProvider *pProvider);
+ virtual ~OpenGLSalGraphicsImpl () override;
+
+ rtl::Reference<OpenGLContext> GetOpenGLContext();
+
+ virtual void Init() override;
+
+ virtual void DeInit() override;
+
+ virtual void freeResources() override;
+
+ virtual OUString getRenderBackendName() const override { return "opengl"; }
+
+ const vcl::Region& getClipRegion() const;
+ virtual bool setClipRegion( const vcl::Region& ) override;
+
+ //
+ // get the depth of the device
+ virtual sal_uInt16 GetBitCount() const override;
+
+ // get the width of the device
+ virtual 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( long nX, long nY ) override;
+ virtual void drawPixel( long nX, long nY, Color nColor ) override;
+
+ virtual void drawLine( long nX1, long nY1, long nX2, long nY2 ) override;
+
+ virtual void drawRect( long nX, long nY, long nWidth, long nHeight ) override;
+
+ virtual void drawPolyLine( sal_uInt32 nPoints, const SalPoint* pPtAry ) override;
+
+ virtual void drawPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry ) override;
+
+ virtual void drawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints, PCONSTSALPOINT* pPtAry ) override;
+
+ virtual bool 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 SalPoint* pPtAry,
+ const PolyFlags* pFlgAry ) override;
+
+ virtual bool drawPolygonBezier(
+ sal_uInt32 nPoints,
+ const SalPoint* pPtAry,
+ const PolyFlags* pFlgAry ) override;
+
+ virtual bool drawPolyPolygonBezier(
+ sal_uInt32 nPoly,
+ const sal_uInt32* pPoints,
+ const SalPoint* const* pPtAry,
+ const PolyFlags* const* pFlgAry ) override;
+
+ // CopyArea --> No RasterOp, but ClipRegion
+ virtual void copyArea(
+ long nDestX, long nDestY,
+ long nSrcX, long nSrcY,
+ long nSrcWidth, long nSrcHeight,
+ bool bWindowInvalidate ) override;
+
+ // CopyBits and DrawBitmap --> RasterOp and ClipRegion
+ // CopyBits() --> pSrcGraphics == NULL, then CopyBits on same Graphics
+ void DoCopyBits(const SalTwoRect& rPosAry, OpenGLSalGraphicsImpl &rSrcImpl);
+
+ 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( long nX, long nY, long nWidth, long nHeight ) override;
+
+ virtual Color getPixel( long nX, long nY ) override;
+
+ // invert --> ClipRegion (only Windows or VirDevs)
+ virtual void invert(
+ long nX, long nY,
+ long nWidth, long nHeight,
+ SalInvert nFlags) override;
+
+ virtual void invert( sal_uInt32 nPoints, const SalPoint* pPtAry, SalInvert nFlags ) override;
+
+ virtual bool drawEPS(
+ long nX, long nY,
+ long nWidth, 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) 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(
+ long nX, long nY,
+ long nWidth, long nHeight,
+ sal_uInt8 nTransparency ) override;
+
+ virtual bool drawGradient(const tools::PolyPolygon& rPolygon, const Gradient& rGradient) override;
+
+ virtual bool supportsOperation(OutDevSupportType eType) const override;
+
+ /// queue an idle flush of contents of the back-buffer to the screen
+ void flush();
+
+public:
+ /// do flush of contents of the back-buffer to the screen & swap.
+ void doFlush();
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/opengl/program.hxx b/vcl/inc/opengl/program.hxx
new file mode 100644
index 000000000..bff248d9b
--- /dev/null
+++ b/vcl/inc/opengl/program.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/.
+ */
+
+#ifndef INCLUDED_VCL_INC_OPENGL_PROGRAM_H
+#define INCLUDED_VCL_INC_OPENGL_PROGRAM_H
+
+#include <sal/config.h>
+
+#include <vector>
+
+#include <vcl/dllapi.h>
+
+#include <basegfx/point/b2dpoint.hxx>
+#include <rtl/ustring.hxx>
+#include <opengl/texture.hxx>
+
+#include <unordered_map>
+
+class Color;
+
+enum class TextureShaderType
+{
+ Normal = 0,
+ Blend,
+ Masked,
+ Diff,
+ MaskedColor
+};
+
+enum class DrawShaderType
+{
+ Normal = 0,
+ Line
+};
+
+class OpenGLProgram
+{
+private:
+ GLuint mnId;
+ std::unordered_map< OString, GLuint >
+ maUniformLocations;
+ sal_uInt32 mnEnabledAttribs;
+ GLuint mnPositionAttrib;
+ GLuint mnTexCoordAttrib;
+ GLuint mnAlphaCoordAttrib;
+ GLuint mnMaskCoordAttrib;
+ GLuint mnExtrusionVectorsAttrib;
+ GLuint mnVertexColorsAttrib;
+
+ std::vector< OpenGLTexture > maTextures;
+ bool mbBlending;
+
+ float mfLastWidth;
+ float mfLastHeight;
+ float mfLastPixelOffset;
+
+
+ OpenGLProgram(const OpenGLProgram &) = delete;
+public:
+ OpenGLProgram();
+ ~OpenGLProgram();
+
+ GLuint Id() { return mnId; }
+
+ bool Load( const OUString& rVertexShader, const OUString& rFragmentShader,
+ const OString& preamble, const OString& rDigest );
+ void Use();
+ void Reuse();
+ void Clean();
+
+ void SetVertices( const GLvoid* pData );
+ void SetTextureCoord( const GLvoid* pData );
+ void SetAlphaCoord( const GLvoid* pData );
+ void SetMaskCoord(const GLvoid* pData);
+ void SetExtrusionVectors(const GLvoid* pData);
+ void SetVertexColors(std::vector<GLubyte>& rColorVector);
+
+ void SetUniform1f( const OString& rName, GLfloat v1 );
+ void SetUniform2f( const OString& rName, GLfloat v1, GLfloat v2 );
+ void SetUniform1fv( const OString& rName, GLsizei nCount, GLfloat const * aValues );
+ void SetUniform2fv( const OString& rName, GLsizei nCount, GLfloat const * aValues );
+ void SetUniform1i( const OString& rName, GLint v1 );
+ void SetColor( const OString& rName, const Color& rColor );
+ void SetColor( const OString& rName, Color nColor, sal_uInt8 nTransparency );
+ void SetColorf( const OString& rName, Color nColor, double fTransparency );
+ void SetColorWithIntensity( const OString& rName, const Color& rColor, long nFactor );
+ void SetTexture( const OString& rName, OpenGLTexture& rTexture );
+ void SetTransform( const OString& rName, const OpenGLTexture& rTexture,
+ const basegfx::B2DPoint& rNull, const basegfx::B2DPoint& rX,
+ const basegfx::B2DPoint& rY );
+ void SetIdentityTransform(const OString& rName);
+ void SetShaderType(TextureShaderType eTextureShaderType);
+ void SetShaderType(DrawShaderType eDrawShaderType);
+
+ void SetBlendMode( GLenum nSFactor, GLenum nDFactor );
+
+ void ApplyMatrix(float fWidth, float fHeight, float fPixelOffset = 0.0f);
+
+ void DrawTexture( const OpenGLTexture& rTexture );
+
+ void DrawArrays(GLenum aMode, std::vector<GLfloat>& aVertices);
+ void DrawElements(GLenum aMode, GLuint nNumberOfVertices);
+
+ bool EnableVertexAttrib(GLuint& rAttrib, const OString& rName);
+
+ void SetVertexAttrib(GLuint& rAttrib, const OString& rName, GLint nSize,
+ GLenum eType, GLboolean bNormalized, GLsizei aStride,
+ const GLvoid* pPointer);
+
+private:
+ GLuint GetUniformLocation( const OString& rName );
+};
+
+#endif // INCLUDED_VCL_INC_OPENGL_PROGRAM_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/opengl/salbmp.hxx b/vcl/inc/opengl/salbmp.hxx
new file mode 100644
index 000000000..a01eca89f
--- /dev/null
+++ b/vcl/inc/opengl/salbmp.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_OPENGL_SALBMP_H
+#define INCLUDED_VCL_INC_OPENGL_SALBMP_H
+
+#include <vcl/opengl/OpenGLContext.hxx>
+
+#include <opengl/texture.hxx>
+
+#include <salbmp.hxx>
+
+#include <memory>
+
+struct BitmapBuffer;
+class BitmapPalette;
+namespace vcl
+{
+ class Kernel;
+}
+
+class VCL_PLUGIN_PUBLIC OpenGLSalBitmap final : public SalBitmap
+{
+private:
+ OpenGLTexture maTexture;
+ bool mbDirtyTexture;
+ BitmapPalette maPalette;
+ std::shared_ptr<sal_uInt8> mpUserBuffer;
+ sal_uInt16 mnBits;
+ sal_uInt32 mnBytesPerRow;
+ int mnWidth;
+ int mnHeight;
+
+ virtual void updateChecksum() const override;
+
+ bool calcChecksumGL(OpenGLTexture& rInputTexture, BitmapChecksum& rChecksum) const;
+
+public:
+ OpenGLSalBitmap();
+ virtual ~OpenGLSalBitmap() override;
+
+public:
+
+ // SalBitmap methods
+ bool Create( const Size& rSize, sal_uInt16 nBitCount, const BitmapPalette& rPal ) override;
+ bool Create( const SalBitmap& rSalBmp ) override;
+ bool Create( const SalBitmap& rSalBmp, SalGraphics* pGraphics ) override;
+ bool Create( const SalBitmap& rSalBmp, sal_uInt16 nNewBitCount ) override;
+ virtual bool Create( const css::uno::Reference< css::rendering::XBitmapCanvas >& rBitmapCanvas,
+ Size& rSize,
+ bool bMask = false ) override;
+
+ void Destroy() final 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;
+ bool ConvertToGreyscale() override;
+ bool InterpretAs8Bit() override;
+
+public:
+
+ void Create( const OpenGLTexture& rTex, long nX, long nY, long nWidth, long nHeight );
+ OpenGLTexture& GetTexture() const;
+ const BitmapPalette& GetBitmapPalette() const { return maPalette; }
+
+private:
+
+ GLuint CreateTexture();
+ bool AllocateUserData();
+ void DeallocateUserData();
+ bool ReadTexture();
+
+private:
+
+ bool ImplScaleFilter( const rtl::Reference< OpenGLContext > &xContext, const double& rScaleX, const double& rScaleY, GLenum nFilter );
+ static void ImplCreateKernel( const double& fScale, const vcl::Kernel& rKernel, GLfloat*& pWeights, sal_uInt32& aKernelSize );
+ bool ImplScaleConvolution(const rtl::Reference< OpenGLContext > &xContext, const double& rScaleX, const double& rScaleY, const vcl::Kernel& rKernel);
+ bool ImplScaleArea( const rtl::Reference< OpenGLContext > &xContext,
+ double rScaleX, double rScaleY );
+
+public:
+
+ void ImplScale( const double& rScaleX, const double& rScaleY, BmpScaleFlag nScaleFlag );
+};
+
+#endif // INCLUDED_VCL_INC_OPENGL_SALBMP_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/opengl/texture.hxx b/vcl/inc/opengl/texture.hxx
new file mode 100644
index 000000000..0438b9af1
--- /dev/null
+++ b/vcl/inc/opengl/texture.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_OPENGL_TEXTURE_H
+#define INCLUDED_VCL_INC_OPENGL_TEXTURE_H
+
+#include <epoxy/gl.h>
+#include <vcl/dllapi.h>
+#include <vcl/salgtype.hxx>
+#include <rtl/ustring.hxx>
+#include <tools/gen.hxx>
+
+#include <functional>
+#include <memory>
+#include <vector>
+
+class ImplOpenGLTexture
+{
+public:
+ GLuint mnTexture;
+ int mnWidth;
+ int mnHeight;
+ GLenum mnFilter;
+ GLuint mnOptStencil;
+
+ std::unique_ptr<std::vector<int>> mpSlotReferences;
+ std::function<void(int)> mFunctSlotDeallocateCallback;
+
+ ImplOpenGLTexture( int nWidth, int nHeight, bool bAllocate );
+ ImplOpenGLTexture( int nWidth, int nHeight, int nFormat, int nType, void const * pData );
+ ImplOpenGLTexture( int nX, int nY, int nWidth, int nHeight );
+ ~ImplOpenGLTexture();
+
+ bool InsertBuffer(int nX, int nY, int nWidth, int nHeight, int nFormat, int nType, sal_uInt8 const * pData);
+
+ void IncreaseRefCount(int nSlotNumber);
+ void DecreaseRefCount(int nSlotNumber);
+
+ void InitializeSlotMechanism(int nInitialSlotSize);
+
+ void SetSlotDeallocateCallback(std::function<void(int)> aCallback)
+ {
+ mFunctSlotDeallocateCallback = aCallback;
+ }
+
+ void ResetSlotDeallocateCallback()
+ {
+ mFunctSlotDeallocateCallback = std::function<void(int)>();
+ }
+
+ GLuint AddStencil();
+};
+
+class VCL_DLLPUBLIC OpenGLTexture final
+{
+private:
+ // if the rect size doesn't match the mpImpl one, this instance
+ // is a sub-area from the real OpenGL texture
+ tools::Rectangle maRect;
+ std::shared_ptr<ImplOpenGLTexture> mpImpl;
+ int mnSlotNumber;
+
+ inline void GetTextureRect(const SalTwoRect& rPosAry, GLfloat& x1, GLfloat& x2, GLfloat& y1, GLfloat& y2) const;
+
+ bool IsValid() const
+ {
+ return (mpImpl && mpImpl->mnTexture != 0);
+ }
+
+public:
+ OpenGLTexture();
+ OpenGLTexture(const std::shared_ptr<ImplOpenGLTexture>& pImpl, tools::Rectangle aRectangle, int nSlotNumber);
+
+ OpenGLTexture( int nWidth, int nHeight, bool bAllocate = true );
+ OpenGLTexture( int nWidth, int nHeight, int nFormat, int nType, void const * pData );
+ OpenGLTexture( int nX, int nY, int nWidth, int nHeight );
+ OpenGLTexture( const OpenGLTexture& rTexture );
+ OpenGLTexture( OpenGLTexture&& rTexture ) noexcept;
+ OpenGLTexture( const OpenGLTexture& rTexture, int nX, int nY, int nWidth, int nHeight );
+ ~OpenGLTexture();
+
+ bool IsUnique() const;
+
+ GLuint Id() const;
+ int GetWidth() const;
+ int GetHeight() const;
+
+ void GetCoord( GLfloat* pCoord, const SalTwoRect& rPosAry, bool bInverted=false ) const;
+ void GetWholeCoord( GLfloat* pCoord ) const;
+ void Bind();
+ void Unbind();
+ void Read( GLenum nFormat, GLenum nType, sal_uInt8* pData );
+ GLuint AddStencil();
+ GLuint StencilId() const;
+
+ bool CopyData(int nWidth, int nHeight, int nFormat, int nType, sal_uInt8 const * pData);
+
+ void SaveToFile(const OUString& rFileName);
+
+ GLenum GetFilter() const;
+ void SetFilter( GLenum nFilter );
+
+ operator bool() const;
+ OpenGLTexture& operator=( const OpenGLTexture& rTexture );
+ OpenGLTexture& operator=( OpenGLTexture&& rTexture );
+ bool operator==( const OpenGLTexture& rTexture ) const;
+ bool operator!=( const OpenGLTexture& rTexture ) const;
+
+ template<GLenum type>
+ void FillCoords(std::vector<GLfloat>& aCoordVector, const SalTwoRect& rPosAry) const;
+};
+
+template<> void OpenGLTexture::FillCoords<GL_TRIANGLES>(
+ std::vector<GLfloat>& aCoord, const SalTwoRect& rPosAry)
+ const;
+
+template<> void OpenGLTexture::FillCoords<GL_TRIANGLE_FAN>(
+ std::vector<GLfloat>& aCoord, const SalTwoRect& rPosAry)
+ const;
+
+#endif // INCLUDED_VCL_INC_OPENGL_TEXTURE_H
+
+/* 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 000000000..04d97406b
--- /dev/null
+++ b/vcl/inc/opengl/win/WinDeviceInfo.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/.
+ */
+
+#ifndef INCLUDED_VCL_OPENGL_WIN_WINDEVICEINFO_HXX
+#define INCLUDED_VCL_OPENGL_WIN_WINDEVICEINFO_HXX
+
+#include <vcl/dllapi.h>
+
+#include <opengl/DeviceInfo.hxx>
+#include <driverblocklist.hxx>
+
+#include <rtl/ustring.hxx>
+#include <vector>
+#include <cstdint>
+
+class VCL_DLLPUBLIC WinOpenGLDeviceInfo : public OpenGLDeviceInfo
+{
+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();
+
+ virtual ~WinOpenGLDeviceInfo() override;
+
+ virtual bool isDeviceBlocked() override;
+
+ 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/win/gdiimpl.hxx b/vcl/inc/opengl/win/gdiimpl.hxx
new file mode 100644
index 000000000..dff47ef7e
--- /dev/null
+++ b/vcl/inc/opengl/win/gdiimpl.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/.
+ */
+
+#ifndef INCLUDED_VCL_INC_OPENGL_WIN_GDIIMPL_HXX
+#define INCLUDED_VCL_INC_OPENGL_WIN_GDIIMPL_HXX
+
+#include <memory>
+#include <vcl/dllapi.h>
+
+#include <opengl/gdiimpl.hxx>
+#include <svdata.hxx>
+#include <win/salgdi.h>
+#include <win/wingdiimpl.hxx>
+#include <o3tl/lru_map.hxx>
+#include <vcl/opengl/OpenGLContext.hxx>
+#include <ControlCacheKey.hxx>
+
+class OpenGLCompatibleDC : public CompatibleDC
+{
+public:
+ OpenGLCompatibleDC(SalGraphics &rGraphics, int x, int y, int width, int height);
+
+ virtual std::unique_ptr<Texture> getAsMaskTexture() const override;
+ // caller must delete
+ OpenGLTexture* getOpenGLTexture() const;
+
+ /// Copy bitmap data to the texture. Texture must be initialized and the correct size to hold the bitmap.
+ bool copyToTexture(Texture& aTexture) const;
+
+ struct Texture;
+};
+
+struct OpenGLCompatibleDC::Texture : public CompatibleDC::Texture
+{
+ OpenGLTexture texture;
+ virtual bool isValid() const { return !!texture; }
+ virtual int GetWidth() const { return texture.GetWidth(); }
+ virtual int GetHeight() const { return texture.GetHeight(); }
+};
+
+class WinOpenGLSalGraphicsImpl : public OpenGLSalGraphicsImpl, public WinSalGraphicsImplBase
+{
+ friend class WinLayout;
+private:
+ WinSalGraphics& mrWinParent;
+
+ bool RenderCompatibleDC(OpenGLCompatibleDC& rWhite, OpenGLCompatibleDC& rBlack,
+ int nX, int nY, TextureCombo& rCombo);
+
+public:
+ WinOpenGLSalGraphicsImpl(WinSalGraphics& rGraphics,
+ SalGeometryProvider *mpProvider);
+
+protected:
+ virtual rtl::Reference<OpenGLContext> CreateWinContext() override;
+
+ bool RenderTextureCombo(TextureCombo const & rCombo, int nX, int nY);
+
+public:
+ virtual void Init() override;
+ virtual void copyBits( const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics ) override;
+
+ virtual bool UseTextDraw() const override { return true; }
+ virtual void PreDrawText() override;
+ virtual void PostDrawText() override;
+ virtual void DrawTextMask( CompatibleDC::Texture* rTexture, Color nMaskColor, const SalTwoRect& rPosAry ) override;
+ using OpenGLSalGraphicsImpl::DrawMask;
+ virtual void DeferredTextDraw(const CompatibleDC::Texture* pTexture, Color nMaskColor, const SalTwoRect& rPosAry) override;
+
+ 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;
+
+};
+
+typedef std::pair<ControlCacheKey, std::unique_ptr<TextureCombo>> OpenGLControlCachePair;
+typedef o3tl::lru_map<ControlCacheKey, std::unique_ptr<TextureCombo>, ControlCacheHashFunction> OpenGLControlCacheType;
+
+class OpenGLControlsCache {
+ OpenGLControlCacheType cache;
+
+ OpenGLControlsCache();
+
+public:
+ static OpenGLControlCacheType & get();
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/opengl/win/winlayout.hxx b/vcl/inc/opengl/win/winlayout.hxx
new file mode 100644
index 000000000..d017bc250
--- /dev/null
+++ b/vcl/inc/opengl/win/winlayout.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_OPENGL_WIN_WINLAYOUT_HXX
+#define INCLUDED_VCL_INC_OPENGL_WIN_WINLAYOUT_HXX
+
+#include <win/winlayout.hxx>
+#include <opengl/PackedTextureAtlas.hxx>
+
+struct OpenGLGlobalWinGlyphCache : public GlobalWinGlyphCache
+{
+ OpenGLGlobalWinGlyphCache()
+ : maPackedTextureAtlas(2048, 2048)
+ {
+ }
+
+ PackedTextureAtlasManager maPackedTextureAtlas;
+
+ virtual bool AllocateTexture(WinGlyphDrawElement& rElement, CompatibleDC* dc) override;
+ virtual void Prune() override;
+};
+
+class OpenGLWinGlyphCache : public WinGlyphCache
+{
+public:
+ void RemoveTextures(std::vector<GLuint>& rTextureIDs);
+
+private:
+ // This class just "adds" RemoveTextures() to the base class, it's never instantiated.
+ OpenGLWinGlyphCache() = delete;
+};
+
+#endif // INCLUDED_VCL_INC_OPENGL_WIN_WINLAYOUT_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/opengl/x11/X11DeviceInfo.hxx b/vcl/inc/opengl/x11/X11DeviceInfo.hxx
new file mode 100644
index 000000000..d51de3a69
--- /dev/null
+++ b/vcl/inc/opengl/x11/X11DeviceInfo.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/.
+ */
+
+#ifndef INCLUDED_VCL_INC_OPENGL_X11_X11DEVICEINFO_HXX
+#define INCLUDED_VCL_INC_OPENGL_X11_X11DEVICEINFO_HXX
+
+#include <opengl/DeviceInfo.hxx>
+
+#include <rtl/string.hxx>
+
+class X11OpenGLDeviceInfo final : public OpenGLDeviceInfo
+{
+private:
+ bool mbIsMesa;
+ bool mbIsNVIDIA;
+ bool mbIsFGLRX;
+ bool mbIsNouveau;
+ bool mbIsIntel;
+ bool mbIsOldSwrast;
+ bool mbIsLlvmpipe;
+
+ OString maVendor;
+ OString maRenderer;
+ OString maVersion;
+ OString maOS;
+ OString maOSRelease;
+
+ size_t mnGLMajorVersion;
+ size_t mnMajorVersion;
+ size_t mnMinorVersion;
+ size_t mnRevisionVersion;
+
+ void GetData();
+
+public:
+ X11OpenGLDeviceInfo();
+
+ virtual bool isDeviceBlocked() override;
+
+ const OString& GetVendor() const
+ {
+ return maVendor;
+ }
+
+ const OString& GetRenderer() const
+ {
+ return maRenderer;
+ }
+
+ const OString& GetVersion() const
+ {
+ return maVersion;
+ }
+
+ const OString& GetOS() const
+ {
+ return maOS;
+ }
+
+ const OString& GetOSRelease() const
+ {
+ return maOSRelease;
+ }
+
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/opengl/x11/cairotextrender.hxx b/vcl/inc/opengl/x11/cairotextrender.hxx
new file mode 100644
index 000000000..137022fa8
--- /dev/null
+++ b/vcl/inc/opengl/x11/cairotextrender.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/.
+ */
+
+#ifndef INCLUDED_VCL_UNX_GENERIC_GDI_OPENGLX11CAIROTEXTRENDER_HXX
+#define INCLUDED_VCL_UNX_GENERIC_GDI_OPENGLX11CAIROTEXTRENDER_HXX
+
+#include <unx/x11/x11cairotextrender.hxx>
+
+class OpenGLX11CairoTextRender final : public X11CairoTextRender
+{
+public:
+ explicit OpenGLX11CairoTextRender(X11SalGraphics& rParent);
+
+ virtual cairo_t* getCairoContext() override;
+ virtual void getSurfaceOffset(double& nDX, double& nDY) override;
+ virtual void releaseCairoContext(cairo_t* cr) override;
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/opengl/x11/gdiimpl.hxx b/vcl/inc/opengl/x11/gdiimpl.hxx
new file mode 100644
index 000000000..d86af9223
--- /dev/null
+++ b/vcl/inc/opengl/x11/gdiimpl.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/.
+ */
+
+#ifndef INCLUDED_VCL_INC_OPENGL_X11_GDIIMPL_HXX
+#define INCLUDED_VCL_INC_OPENGL_X11_GDIIMPL_HXX
+
+#include <vcl/dllapi.h>
+
+#include <unx/salgdi.h>
+#include <unx/x11/x11gdiimpl.h>
+#include <opengl/gdiimpl.hxx>
+#include <ControlCacheKey.hxx>
+
+struct TextureCombo;
+
+class X11OpenGLSalGraphicsImpl : public OpenGLSalGraphicsImpl, public X11GraphicsImpl
+{
+private:
+ X11SalGraphics& mrX11Parent;
+
+public:
+ X11OpenGLSalGraphicsImpl( X11SalGraphics& rParent );
+ virtual ~X11OpenGLSalGraphicsImpl() override;
+
+protected:
+ virtual rtl::Reference<OpenGLContext> CreateWinContext() override;
+
+public:
+ virtual void copyBits( const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics ) override;
+
+ virtual void Init() override;
+};
+
+#endif // INCLUDED_VCL_INC_OPENGL_X11_GDIIMPL_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/opengl/x11/glxtest.hxx b/vcl/inc/opengl/x11/glxtest.hxx
new file mode 100644
index 000000000..5715a6d9f
--- /dev/null
+++ b/vcl/inc/opengl/x11/glxtest.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/.
+ */
+
+#ifndef INCLUDED_VCL_INC_OPENGL_X11_GLXTEST_HXX
+#define INCLUDED_VCL_INC_OPENGL_X11_GLXTEST_HXX
+
+#include <vcl/dllapi.h>
+
+VCL_DLLPUBLIC int* getGlxPipe();
+
+VCL_DLLPUBLIC pid_t* getGlxPid();
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/opengl/x11/salvd.hxx b/vcl/inc/opengl/x11/salvd.hxx
new file mode 100644
index 000000000..a77b9874e
--- /dev/null
+++ b/vcl/inc/opengl/x11/salvd.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_OPENGL_X11_SALVD_H
+#define INCLUDED_VCL_INC_OPENGL_X11_SALVD_H
+
+#include <memory>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#include <unx/saltype.h>
+#include <salvd.hxx>
+
+class SalDisplay;
+class X11OpenGLSalGraphics;
+class X11SalGraphics;
+
+class X11OpenGLSalVirtualDevice : public SalVirtualDevice
+{
+ SalDisplay *mpDisplay;
+ std::unique_ptr<X11SalGraphics>
+ mpGraphics;
+ bool mbGraphics; // is Graphics used
+ SalX11Screen mnXScreen;
+ int mnWidth;
+ int mnHeight;
+
+public:
+ X11OpenGLSalVirtualDevice( SalGraphics const *pGraphics,
+ long nDX, long nDY,
+ const SystemGraphicsData *pData,
+ std::unique_ptr<X11SalGraphics> pNewGraphics);
+ virtual ~X11OpenGLSalVirtualDevice() override;
+
+ // SalGeometryProvider
+ virtual long GetWidth() const override { return mnWidth; }
+ virtual 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( long nNewDX, long nNewDY ) override;
+};
+
+#endif // INCLUDED_VCL_INC_OPENGL_X11_SALVD_H
+
+/* 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 000000000..4bcdf15dc
--- /dev/null
+++ b/vcl/inc/opengl/zone.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/.
+ */
+
+#ifndef INCLUDED_VCL_INC_OPENGL_ZONE_H
+#define INCLUDED_VCL_INC_OPENGL_ZONE_H
+
+#include <sal/config.h>
+#include <sal/types.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"; }
+};
+
+/// Create this to not only enter the zone, but set VCL context.
+class OpenGLVCLContextZone {
+ OpenGLZone aZone;
+public:
+ OpenGLVCLContextZone();
+};
+
+class VCL_DLLPUBLIC PreDefaultWinNoOpenGLZone {
+public:
+ PreDefaultWinNoOpenGLZone();
+ ~PreDefaultWinNoOpenGLZone();
+};
+
+#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 000000000..b20755a03
--- /dev/null
+++ b/vcl/inc/osx/a11yfactory.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_OSX_A11YFACTORY_H
+#define INCLUDED_VCL_INC_OSX_A11YFACTORY_H
+
+#include "osxvcltypes.h"
+#include "a11ywrapper.h"
+#include <com/sun/star/accessibility/XAccessibleContext.hpp>
+
+@interface AquaA11yFactory : NSObject
+{
+}
++(void)insertIntoWrapperRepository: (NSView *) viewElement 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)registerView: (NSView *) theView;
++(void)revokeView: (NSView *) theViewt;
+@end
+
+#endif // INCLUDED_VCL_INC_OSX_A11YFACTORY_H
+
+/* 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 000000000..59f2e579c
--- /dev/null
+++ b/vcl/inc/osx/a11yfocustracker.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_OSX_A11YFOCUSTRACKER_HXX
+#define INCLUDED_VCL_INC_OSX_A11YFOCUSTRACKER_HXX
+
+#include <com/sun/star/accessibility/XAccessible.hpp>
+
+#include "keyboardfocuslistener.hxx"
+
+#include <rtl/instance.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;
+};
+
+struct TheAquaA11yFocusTracker:
+ rtl::Static<AquaA11yFocusTracker, TheAquaA11yFocusTracker>
+{};
+
+#endif // INCLUDED_VCL_INC_OSX_A11YFOCUSTRACKER_HXX
+
+/* 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 000000000..a53559467
--- /dev/null
+++ b/vcl/inc/osx/a11ylistener.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_OSX_A11YLISTENER_HXX
+#define INCLUDED_VCL_INC_OSX_A11YLISTENER_HXX
+
+#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;
+};
+
+#endif // INCLUDED_VCL_INC_OSX_A11YLISTENER_HXX
+/* 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 000000000..e33d247a1
--- /dev/null
+++ b/vcl/inc/osx/a11ywrapper.h
@@ -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_OSX_A11YWRAPPER_H
+#define INCLUDED_VCL_INC_OSX_A11YWRAPPER_H
+
+#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 : NSView
+{
+ ReferenceWrapper * mpReferenceWrapper;
+ 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;
+-(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)initWithAccessibleContext: (css::uno::Reference < css::accessibility::XAccessibleContext >) anAccessibleContext;
+-(void) setDefaults: (css::uno::Reference < css::accessibility::XAccessibleContext >) rxAccessibleContext;
+-(void) dealloc;
++(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
+
+#endif // INCLUDED_VCL_INC_OSX_A11YWRAPPER_H
+
+/* 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 000000000..1da1c8452
--- /dev/null
+++ b/vcl/inc/osx/keyboardfocuslistener.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_INC_OSX_KEYBOARDFOCUSLISTENER_HXX
+#define INCLUDED_VCL_INC_OSX_KEYBOARDFOCUSLISTENER_HXX
+
+#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;
+};
+
+#endif // INCLUDED_VCL_INC_OSX_KEYBOARDFOCUSLISTENER_HXX
+
+/* 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 000000000..bf3f4ec46
--- /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 000000000..a2a6e5454
--- /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 000000000..e68bc4d35
--- /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 000000000..483902ff2
--- /dev/null
+++ b/vcl/inc/osx/saldata.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_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 <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
+ 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;
+ long mnDPIX; // #i100617# read DPI only once per office life
+ long 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();
+};
+
+inline void SetSalData( SalData* pData ) { ImplGetSVData()->mpSalData = pData; }
+inline SalData *GetSalData() { return ImplGetSVData()->mpSalData; }
+
+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 000000000..3d0f19f41
--- /dev/null
+++ b/vcl/inc/osx/salframe.h
@@ -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 .
+ */
+
+#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 <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
+
+ NSTrackingRectTag mnTrackingRectTag; // used to get enter/leave messages
+ 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;
+
+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 DrawMenuBar() override;
+ virtual void Show( bool bVisible, bool bNoActivate = false ) override;
+ virtual void SetMinClientSize( long nWidth, long nHeight ) override;
+ virtual void SetMaxClientSize( long nWidth, long nHeight ) override;
+ virtual void SetPosSize( long nX, long nY, long nWidth, long nHeight, sal_uInt16 nFlags ) override;
+ virtual void GetClientSize( long& rWidth, long& rHeight ) override;
+ virtual void GetWorkArea( tools::Rectangle& rRect ) override;
+ virtual SalFrame* GetParent() const override;
+ virtual void SetWindowState( const SalFrameState* pState ) override;
+ virtual bool GetWindowState( SalFrameState* pState ) 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( long nX, 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 bool 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( long nX, long nY, long nWidth, long nHeight ) override;
+ // done setting up the clipregion
+ virtual void EndSetClipRegion() 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(long nX, long nY, long nWidth, 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 000000000..36537f3db
--- /dev/null
+++ b/vcl/inc/osx/salframeview.h
@@ -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 .
+ */
+
+#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;
+}
+-(id)initWithSalFrame: (AquaSalFrame*)pFrame;
+-(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)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;
+@end
+
+@interface SalFrameView : AquaA11yWrapper <NSTextInputClient>
+{
+ AquaSalFrame* mpFrame;
+
+ // 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;
+}
++(void)unsetMouseFrame: (AquaSalFrame*)pFrame;
+-(id)initWithSalFrame: (AquaSalFrame*)pFrame;
+-(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;
+/*
+ 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;
+
+@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 000000000..edece53b6
--- /dev/null
+++ b/vcl/inc/osx/salinst.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_OSX_SALINST_H
+#define INCLUDED_VCL_INC_OSX_SALINST_H
+
+#include <sal/config.h>
+
+#include <condition_variable>
+#include <list>
+#include <mutex>
+
+#include <comphelper/solarmutex.hxx>
+#include <osl/conditn.hxx>
+#include <osl/thread.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;
+
+ OUString maDefaultPrinter;
+ oslThreadIdentifier maMainThread;
+ int mnActivePrintJobs;
+ osl::Mutex maUserEventListMutex;
+ osl::Condition maWaitingYieldCond;
+ bool mbIsLiveResize;
+ 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* pGraphics,
+ long &nDX, 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 > CreateDragSource() override;
+ virtual css::uno::Reference< css::uno::XInterface > CreateDropTarget() 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 NSApplicationDefined 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& );
+
+#endif // INCLUDED_VCL_INC_OSX_SALINST_H
+
+/* 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 000000000..597180cc1
--- /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 000000000..3fdcdc40c
--- /dev/null
+++ b/vcl/inc/osx/salnativewidgets.h
@@ -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 .
+ */
+
+#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 VCL_TAB_TEXT_SEPARATOR 2 // Space between two tabs required by VCL
+
+#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 000000000..f815c14c0
--- /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 000000000..d1928fe9d
--- /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 000000000..17f5374d1
--- /dev/null
+++ b/vcl/inc/osx/salobj.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_OSX_SALOBJ_H
+#define INCLUDED_VCL_INC_OSX_SALOBJ_H
+
+#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;
+
+ long mnClipX;
+ long mnClipY;
+ long mnClipWidth;
+ long mnClipHeight;
+ bool mbClip;
+
+ long mnX;
+ long mnY;
+ long mnWidth;
+ long 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( long nX, long nY, long nWidth, long nHeight ) override;
+ virtual void EndSetClipRegion() override;
+ virtual void SetPosSize( long nX, long nY, long nWidth, 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 000000000..0c7ec1e9e
--- /dev/null
+++ b/vcl/inc/osx/salprn.h
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_OSX_SALPRN_H
+#define INCLUDED_VCL_INC_OSX_SALPRN_H
+
+#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,
+ long& o_rOutWidth, 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,
+ const OUString& i_rAppName,
+ 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( long i_nWidth, long i_nHeight, Orientation& o_rOrientation ) const;
+ void setPaperSize( long i_nWidth, 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 000000000..b0bc98578
--- /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 tools::Rectangle 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 000000000..cdde3ec84
--- /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 000000000..9fcabe074
--- /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 000000000..82f0229a9
--- /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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_OSX_VCLNSAPP_H
+#define INCLUDED_VCL_INC_OSX_VCLNSAPP_H
+
+#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)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
+
+#endif // INCLUDED_VCL_INC_OSX_VCLNSAPP_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/outdata.hxx b/vcl/inc/outdata.hxx
new file mode 100644
index 000000000..9764a6cda
--- /dev/null
+++ b/vcl/inc/outdata.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_INC_OUTDATA_HXX
+#define INCLUDED_VCL_INC_OUTDATA_HXX
+
+#include <tools/color.hxx>
+
+inline bool ImplIsColorTransparent( Color aColor )
+{
+ return aColor.GetTransparency() != 0;
+}
+
+#endif // INCLUDED_VCL_INC_OUTDATA_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/outdev.h b/vcl/inc/outdev.h
new file mode 100644
index 000000000..27f8dc548
--- /dev/null
+++ b/vcl/inc/outdev.h
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_OUTDEV_H
+#define INCLUDED_VCL_INC_OUTDEV_H
+
+#include <set>
+#include <vector>
+
+#include <tools/gen.hxx>
+#include <vcl/vclptr.hxx>
+
+#include "fontinstance.hxx"
+#include "PhysicalFontFace.hxx"
+#include "impfontcache.hxx"
+
+class Size;
+namespace vcl { class Font; }
+class VirtualDevice;
+class PhysicalFontCollection;
+enum class AddFontSubstituteFlags;
+
+// an ImplDeviceFontList is created by a PhysicalFontCollection
+// it becomes invalid when original PhysicalFontCollection is modified
+class ImplDeviceFontList
+{
+private:
+ std::vector<rtl::Reference<PhysicalFontFace>> maDevFontVector;
+
+public:
+ ImplDeviceFontList() { 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(); }
+};
+
+class ImplDeviceFontSizeList
+{
+private:
+ std::vector<int> maSizeList;
+
+public:
+ ImplDeviceFontSizeList()
+ { maSizeList.reserve( 32 ); }
+ void Add( int nHeight ) { maSizeList.push_back( nHeight ); }
+ int Count() const { return maSizeList.size(); }
+ int Get( int nIndex ) const { return maSizeList[ nIndex ]; }
+};
+
+// 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
+
+class ImplFontSubstitution
+{
+ // TODO: there is more commonality between the different substitutions
+protected:
+ virtual ~ImplFontSubstitution() {}
+};
+
+// ImplDirectFontSubstitution 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
+
+struct ImplFontSubstEntry
+{
+ OUString maSearchName;
+ OUString maSearchReplaceName;
+ AddFontSubstituteFlags mnFlags;
+
+ ImplFontSubstEntry( const OUString& rFontName, const OUString& rSubstFontName, AddFontSubstituteFlags nSubstFlags );
+};
+
+class ImplDirectFontSubstitution final
+: public ImplFontSubstitution
+{
+private:
+ std::vector<ImplFontSubstEntry> maFontSubstList;
+public:
+ void AddFontSubstitute( const OUString& rFontName, const OUString& rSubstName, AddFontSubstituteFlags nFlags );
+ void RemoveFontsSubstitute();
+
+ bool FindFontSubstitute( OUString& rSubstName, const OUString& rFontName ) const;
+};
+
+// PreMatchFontSubstitution
+// abstracts the concept of a configured font substitution
+// before the availability of the originally selected font has been checked
+class ImplPreMatchFontSubstitution
+: public ImplFontSubstitution
+{
+public:
+ virtual bool FindFontSubstitute(FontSelectPattern&) const = 0;
+};
+
+// ImplGlyphFallbackFontSubstitution
+// abstracts the concept of finding the best font to support an incomplete font
+class ImplGlyphFallbackFontSubstitution
+: public ImplFontSubstitution
+{
+public:
+ virtual bool FindFontSubstitute(FontSelectPattern&, LogicalFontInstance* pLogicalFont, OUString& rMissingCodes) const = 0;
+};
+
+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;
+};
+
+void ImplFontSubstitute( OUString& rFontName );
+
+#endif // INCLUDED_VCL_INC_OUTDEV_H
+
+/* 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 000000000..79285a8f9
--- /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 000000000..2e94519a4
--- /dev/null
+++ b/vcl/inc/pch/precompiled_vcl.hxx
@@ -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 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 2020-05-07 20:21:38 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
+*/
+
+#if PCH_LEVEL >= 1
+#include <algorithm>
+#include <atomic>
+#include <cassert>
+#include <cmath>
+#include <cstddef>
+#include <cstdlib>
+#include <cstring>
+#include <deque>
+#include <float.h>
+#include <functional>
+#include <initializer_list>
+#include <iomanip>
+#include <limits.h>
+#include <limits>
+#include <map>
+#include <math.h>
+#include <memory>
+#include <new>
+#include <optional>
+#include <ostream>
+#include <set>
+#include <stddef.h>
+#include <string.h>
+#include <string>
+#include <string_view>
+#include <type_traits>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+#include <boost/functional/hash.hpp>
+#include <boost/math/special_functions/sinc.hpp>
+#include <boost/multi_array.hpp>
+#include <boost/property_tree/ptree.hpp>
+#include <boost/property_tree/ptree_fwd.hpp>
+#endif // PCH_LEVEL >= 1
+#if PCH_LEVEL >= 2
+#include <osl/conditn.hxx>
+#include <osl/diagnose.h>
+#include <osl/endian.h>
+#include <osl/file.hxx>
+#include <osl/interlck.h>
+#include <osl/module.hxx>
+#include <osl/mutex.h>
+#include <osl/mutex.hxx>
+#include <osl/process.h>
+#include <osl/signal.h>
+#include <osl/thread.h>
+#include <osl/time.h>
+#include <rtl/alloc.h>
+#include <rtl/bootstrap.hxx>
+#include <rtl/character.hxx>
+#include <rtl/crc.h>
+#include <rtl/digest.h>
+#include <rtl/instance.hxx>
+#include <rtl/math.h>
+#include <rtl/math.hxx>
+#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/config.h>
+#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/b2drange.hxx>
+#include <basegfx/range/basicrange.hxx>
+#include <basegfx/tuple/b2dtuple.hxx>
+#include <basegfx/tuple/b2ituple.hxx>
+#include <basegfx/tuple/b3dtuple.hxx>
+#include <basegfx/utils/common.hxx>
+#include <basegfx/vector/b2dvector.hxx>
+#include <basegfx/vector/b2enums.hxx>
+#include <basegfx/vector/b2ivector.hxx>
+#include <com/sun/star/awt/Key.hpp>
+#include <com/sun/star/awt/KeyGroup.hpp>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/datatransfer/DataFlavor.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/i18n/TransliterationModules.hpp>
+#include <com/sun/star/i18n/TransliterationModulesExtra.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/lang/XUnoTunnel.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/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/fileformat.h>
+#include <comphelper/lok.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/sequence.hxx>
+#include <comphelper/string.hxx>
+#include <cppu/cppudllapi.h>
+#include <cppu/unotype.hxx>
+#include <cppuhelper/cppuhelperdllapi.h>
+#include <cppuhelper/implbase.hxx>
+#include <cppuhelper/implbase_ex.hxx>
+#include <cppuhelper/implbase_ex_post.hxx>
+#include <cppuhelper/implbase_ex_pre.hxx>
+#include <cppuhelper/interfacecontainer.h>
+#include <cppuhelper/supportsservice.hxx>
+#include <cppuhelper/weak.hxx>
+#include <cppuhelper/weakagg.hxx>
+#include <cppuhelper/weakref.hxx>
+#include <i18nlangtag/lang.h>
+#include <i18nlangtag/languagetag.hxx>
+#include <i18nlangtag/mslangid.hxx>
+#include <i18nutil/i18nutildllapi.h>
+#include <o3tl/cow_wrapper.hxx>
+#include <o3tl/safeint.hxx>
+#include <o3tl/strong_int.hxx>
+#include <o3tl/typed_flags_set.hxx>
+#include <o3tl/underlyingenumvalue.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 <sot/sotdllapi.h>
+#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/UnitConversion.hxx>
+#include <tools/color.hxx>
+#include <tools/contnr.hxx>
+#include <tools/date.hxx>
+#include <tools/datetime.hxx>
+#include <tools/debug.hxx>
+#include <tools/diagnose_ex.h>
+#include <tools/fontenum.hxx>
+#include <tools/fract.hxx>
+#include <tools/gen.hxx>
+#include <tools/globname.hxx>
+#include <tools/helpers.hxx>
+#include <tools/link.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/configmgr.hxx>
+#include <unotools/localedatawrapper.hxx>
+#include <unotools/ucbstreamhelper.hxx>
+#include <unotools/unotoolsdllapi.h>
+#endif // PCH_LEVEL >= 3
+#if PCH_LEVEL >= 4
+#include <PhysicalFontCollection.hxx>
+#include <PhysicalFontFace.hxx>
+#include <TypeSerializer.hxx>
+#include <bitmapwriteaccess.hxx>
+#include <brdwin.hxx>
+#include <configsettings.hxx>
+#include <controldata.hxx>
+#include <fontattributes.hxx>
+#include <impglyphitem.hxx>
+#include <outdev.h>
+#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 <svdata.hxx>
+#include <vcl/BitmapFilter.hxx>
+#include <vcl/BitmapTools.hxx>
+#include <vcl/accel.hxx>
+#include <vcl/alpha.hxx>
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapaccess.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/builder.hxx>
+#include <vcl/canvastools.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/edit.hxx>
+#include <vcl/event.hxx>
+#include <vcl/fixed.hxx>
+#include <vcl/floatwin.hxx>
+#include <vcl/fntstyle.hxx>
+#include <vcl/font.hxx>
+#include <vcl/gdimtf.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/lineinfo.hxx>
+#include <vcl/lstbox.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/mnemonic.hxx>
+#include <vcl/mnemonicengine.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/ptrstyle.hxx>
+#include <vcl/quickselectionengine.hxx>
+#include <vcl/scrbar.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/spinfld.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/svlbitm.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/dialog.hxx>
+#include <vcl/toolkit/unowrap.hxx>
+#include <vcl/transfer.hxx>
+#include <vcl/treelist.hxx>
+#include <vcl/treelistentries.hxx>
+#include <vcl/treelistentry.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/vclptr.hxx>
+#include <vcl/viewdataentry.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/weld.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 000000000..cedc57903
--- /dev/null
+++ b/vcl/inc/pdf/BitmapID.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/.
+ *
+ */
+
+#ifndef INCLUDED_VCL_INC_PDF_BITMAPID_HXX
+#define INCLUDED_VCL_INC_PDF_BITMAPID_HXX
+
+#include <vcl/dllapi.h>
+#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);
+ }
+};
+}
+
+#endif
+
+/* 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 000000000..c36332015
--- /dev/null
+++ b/vcl/inc/pdf/Matrix3.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_INC_PDF_MATRIX3_HXX
+#define INCLUDED_VCL_INC_PDF_MATRIX3_HXX
+
+#include <vcl/dllapi.h>
+#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;
+};
+}
+
+#endif
+
+/* 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 000000000..488a821db
--- /dev/null
+++ b/vcl/inc/pdf/ResourceDict.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/.
+ *
+ */
+
+#ifndef INCLUDED_VCL_INC_PDF_RESOURCEDICT_HXX
+#define INCLUDED_VCL_INC_PDF_RESOURCEDICT_HXX
+
+#include <vcl/dllapi.h>
+#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);
+};
+}
+
+#endif
+
+/* 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 000000000..61438e0e5
--- /dev/null
+++ b/vcl/inc/pdf/XmpMetadata.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/.
+ *
+ */
+
+#ifndef INCLUDED_VCL_INC_PDF_XMPMETADATA_HXX
+#define INCLUDED_VCL_INC_PDF_XMPMETADATA_HXX
+
+#include <vcl/dllapi.h>
+#include <rtl/string.hxx>
+#include <tools/stream.hxx>
+#include <memory>
+
+namespace vcl::pdf
+{
+class XmpMetadata
+{
+private:
+ bool mbWritten;
+ std::unique_ptr<SvMemoryStream> mpMemoryStream;
+
+public:
+ OString msTitle;
+ OString msAuthor;
+ OString msSubject;
+ OString msProducer;
+ OString msKeywords;
+ OString m_sCreatorTool;
+ OString m_sCreateDate;
+
+ sal_Int32 mnPDF_A;
+ bool mbPDF_UA;
+
+public:
+ XmpMetadata();
+ sal_uInt64 getSize();
+ const void* getData();
+
+private:
+ void write();
+};
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/ppdparser.hxx b/vcl/inc/ppdparser.hxx
new file mode 100644
index 000000000..b5bf309ed
--- /dev/null
+++ b/vcl/inc/ppdparser.hxx
@@ -0,0 +1,274 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <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 {
+
+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 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;
+
+public:
+ enum class SetupType { ExitServer, Prolog, DocumentSetup, PageSetup, JCLSetup, AnySetup };
+private:
+
+ bool m_bUIOption;
+ int m_nOrderDependency;
+ SetupType m_eSetupType;
+
+ void eraseValue( const OUString& rOption );
+public:
+ PPDKey( const OUString& rKey );
+ ~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; }
+ SetupType getSetupType() const { return m_eSetupType; }
+ 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
+ bool m_bColorDevice;
+ bool m_bType42Capable;
+ sal_uLong m_nLanguageLevel;
+ 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( const OUString& rFile );
+ PPDParser(const OUString& rFile, const std::vector<PPDKey*>& keys);
+
+ void parseOrderDependency(const OString& rLine);
+ void parseOpenUI(const OString& rLine, const OString& 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 );
+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; }
+
+ bool isColorDevice() const { return m_bColorDevice; }
+ bool isType42Capable() const { return m_bType42Capable; }
+ sal_uLong getLanguageLevel() const { return m_nLanguageLevel; }
+
+ OUString getDefaultPaperDimension() const;
+ void getDefaultPaperDimension( int& rWidth, int& rHeight ) const
+ { getPaperDimension( getDefaultPaperDimension(), rWidth, rHeight ); }
+ bool getPaperDimension( const OUString& 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 ) const;
+
+ bool getMargins( const OUString& 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( const OUString&, int&, int& );
+ // helper function
+
+ OUString translateKey( const OUString& i_rKey ) const;
+ OUString translateOption( const OUString& 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 000000000..13650ceef
--- /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/print.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 000000000..91c11309a
--- /dev/null
+++ b/vcl/inc/print.hrc
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_VCL_INC_PRINT_HRC
+#define INCLUDED_VCL_INC_PRINT_HRC
+
+#define NC_(Context, String) reinterpret_cast<char const *>(Context "\004" u8##String)
+
+const char* RID_STR_PAPERNAMES[] =
+{
+ // To translators: This is the first entry of a sequence of paper size names
+ 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)"),
+ // To translators: This is the last entry of the sequence of paper size names
+ NC_("RID_STR_PAPERNAMES", "16k (197 x 273)")
+};
+
+#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 000000000..936c5b2bc
--- /dev/null
+++ b/vcl/inc/printaccessoryview.hrc
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_PRINTACCESSORYVIEW_HRC
+#define INCLUDED_VCL_INC_PRINTACCESSORYVIEW_HRC
+
+#define NC_(Context, String) reinterpret_cast<char const *>(Context "\004" u8##String)
+
+const char* 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 000000000..c313e8fc4
--- /dev/null
+++ b/vcl/inc/printdlg.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 .
+ */
+
+#ifndef VCL_INC_NEWPRINTDLG_HXX
+#define VCL_INC_NEWPRINTDLG_HXX
+
+#include <vcl/bitmapex.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/print.hxx>
+#include <vcl/customweld.hxx>
+#include <vcl/weld.hxx>
+#include <map>
+
+namespace vcl {
+ class PrintDialog;
+}
+
+namespace vcl
+{
+ class MoreOptionsDialog final : public weld::GenericDialogController
+ {
+ PrintDialog* mpParent;
+ std::unique_ptr<weld::Button> mxOKButton;
+ std::unique_ptr<weld::Button> mxCancelButton;
+ std::unique_ptr<weld::CheckButton> mxSingleJobsBox;
+
+ DECL_LINK( ClickHdl, weld::Button&, void );
+
+ public:
+
+ MoreOptionsDialog(PrintDialog* i_pParent);
+ virtual ~MoreOptionsDialog() override;
+ };
+
+ class PrintDialog : 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 SetDrawingArea(weld::DrawingArea* pDrawingArea) 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,
+ const OUString& 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*, const std::shared_ptr<PrinterController>&);
+ virtual ~PrintDialog() override;
+
+ bool isPrintToFile() const;
+ bool isCollate() const;
+ bool isSingleJobs() const { return mbSingleJobs; };
+ 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<MoreOptionsDialog> mxMoreOptionsDlg;
+
+ 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> mxReverseOrderBox;
+
+ std::unique_ptr<weld::Button> mxOKButton;
+ std::unique_ptr<weld::Button> mxCancelButton;
+ std::unique_ptr<weld::Button> mxHelpButton;
+ std::unique_ptr<weld::Button> mxMoreOptionsBtn;
+
+ 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;
+ bool mbSingleJobs;
+
+ Paper mePaper;
+
+ 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::ToggleButton&, void );
+
+ DECL_LINK( UIOption_CheckHdl, weld::ToggleButton&, void );
+ DECL_LINK( UIOption_RadioHdl, weld::ToggleButton&, void );
+ DECL_LINK( UIOption_SelectHdl, weld::ComboBox&, void );
+ DECL_LINK( UIOption_SpinModifyHdl, weld::SpinButton&, void );
+ DECL_LINK( UIOption_EntryModifyHdl, weld::Entry&, void );
+
+ DECL_LINK( ExpandHdl, weld::Expander&, void );
+
+ css::beans::PropertyValue* getValueForWindow(weld::Widget*) const;
+
+ void preparePreview( bool i_bMayUseCache );
+ void setupPaperSidesBox();
+ void storeToSettings();
+ void readFromSettings();
+ void setPaperOrientation( Orientation eOrientation );
+ void updateOrientationBox( bool bAutomatic = true );
+ bool hasOrientationChanged() const;
+ void checkPaperSize( Size& rPaperSize );
+ 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 000000000..2286158d6
--- /dev/null
+++ b/vcl/inc/printerinfomanager.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_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;
+ bool m_bUseIncludeFeature;
+ bool m_bUseJobPatch;
+ 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();
+ // only called by SalData destructor, frees the global instance
+ static void release();
+
+ // 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 );
+
+ bool getUseIncludeFeature() const { return m_bUseIncludeFeature; }
+ bool getUseJobPatch() const { return m_bUseJobPatch; }
+
+ // check whether a printer's feature string contains a subfeature
+ bool checkFeatureToken( const OUString& rPrinterName, const char* pToken ) const;
+
+ virtual ~PrinterInfoManager();
+};
+
+} // namespace
+
+#endif // INCLUDED_VCL_PRINTERINFOMANAGER_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/Qt5AccessibleEventListener.hxx b/vcl/inc/qt5/Qt5AccessibleEventListener.hxx
new file mode 100644
index 000000000..0fd3783ee
--- /dev/null
+++ b/vcl/inc/qt5/Qt5AccessibleEventListener.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 "Qt5AccessibleWidget.hxx"
+
+#include <cppuhelper/implbase.hxx>
+
+class Qt5AccessibleEventListener final
+ : public cppu::WeakImplHelper<css::accessibility::XAccessibleEventListener>
+{
+public:
+ Qt5AccessibleEventListener(
+ const css::uno::Reference<css::accessibility::XAccessible> xAccessible,
+ Qt5AccessibleWidget* pAccessibleWidget);
+
+ virtual void SAL_CALL
+ notifyEvent(const css::accessibility::AccessibleEventObject& aEvent) override;
+
+ virtual void SAL_CALL disposing(const css::lang::EventObject& Source) override;
+
+private:
+ css::uno::Reference<css::accessibility::XAccessible> m_xAccessible;
+ Qt5AccessibleWidget* m_pAccessibleWidget;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/Qt5AccessibleWidget.hxx b/vcl/inc/qt5/Qt5AccessibleWidget.hxx
new file mode 100644
index 000000000..aacaba9a8
--- /dev/null
+++ b/vcl/inc/qt5/Qt5AccessibleWidget.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/.
+ */
+
+#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/QAccessibleTableInterface>
+#include <QtGui/QAccessibleTextInterface>
+#include <QtGui/QAccessibleValueInterface>
+#include <QtGui/QColor>
+#include <QtGui/QWindow>
+
+#include <com/sun/star/accessibility/XAccessible.hpp>
+
+class Qt5Frame;
+class Qt5Widget;
+
+class Qt5AccessibleWidget final : public QObject,
+ public QAccessibleInterface,
+ public QAccessibleActionInterface,
+ public QAccessibleTextInterface,
+ public QAccessibleEditableTextInterface,
+ public QAccessibleTableInterface,
+ public QAccessibleValueInterface
+{
+ Q_OBJECT
+
+public:
+ Qt5AccessibleWidget(const css::uno::Reference<css::accessibility::XAccessible> xAccessible,
+ QObject* pObject);
+ 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;
+
+ static QAccessibleValueInterface* valueInterface();
+ static QAccessibleTextInterface* textInterface();
+
+ // 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;
+
+ // 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;
+ QObject* m_pObject;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/Qt5Bitmap.hxx b/vcl/inc/qt5/Qt5Bitmap.hxx
new file mode 100644
index 000000000..8ff4297e4
--- /dev/null
+++ b/vcl/inc/qt5/Qt5Bitmap.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 <salbmp.hxx>
+
+#include <memory>
+
+class QImage;
+
+class Qt5Bitmap final : public SalBitmap
+{
+ std::unique_ptr<QImage> m_pImage;
+ BitmapPalette m_aPalette;
+
+ // for 4bit support
+ std::unique_ptr<sal_uInt8[]> m_pBuffer;
+ Size m_aSize;
+ sal_uInt32 m_nScanline;
+
+public:
+ Qt5Bitmap();
+ Qt5Bitmap(const QImage& rQImage);
+
+ const QImage* GetQImage() const { return m_pImage.get(); }
+
+ virtual bool Create(const Size& rSize, sal_uInt16 nBitCount,
+ 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, sal_uInt16 nNewBitCount) 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/Qt5Clipboard.hxx b/vcl/inc/qt5/Qt5Clipboard.hxx
new file mode 100644
index 000000000..b99534f59
--- /dev/null
+++ b/vcl/inc/qt5/Qt5Clipboard.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 Qt5Clipboard 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 Qt5ClipboardTransferable
+ 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 Qt5Clipboard(const 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 Qt5Clipboard 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/Qt5Data.hxx b/vcl/inc/qt5/Qt5Data.hxx
new file mode 100644
index 000000000..1834835d3
--- /dev/null
+++ b/vcl/inc/qt5/Qt5Data.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in 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_QT5_PUBLIC Qt5Data final : public GenericUnixSalData
+{
+ o3tl::enumarray<PointerStyle, std::unique_ptr<QCursor>> m_aCursors;
+
+public:
+ explicit Qt5Data(SalInstance* pInstance);
+ virtual ~Qt5Data() override;
+
+ virtual void ErrorTrapPush() override;
+ virtual bool ErrorTrapPop(bool bIgnoreError = true) override;
+
+ QCursor& getCursor(PointerStyle ePointerStyle);
+
+ static bool noNativeControls();
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/Qt5DragAndDrop.hxx b/vcl/inc/qt5/Qt5DragAndDrop.hxx
new file mode 100644
index 000000000..c88465ab3
--- /dev/null
+++ b/vcl/inc/qt5/Qt5DragAndDrop.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 Qt5Frame;
+
+class Qt5DragSource final
+ : public cppu::WeakComponentImplHelper<css::datatransfer::dnd::XDragSource,
+ css::lang::XInitialization, css::lang::XServiceInfo>
+{
+ osl::Mutex m_aMutex;
+ Qt5Frame* m_pFrame;
+ css::uno::Reference<css::datatransfer::dnd::XDragSourceListener> m_xListener;
+
+public:
+ Qt5DragSource()
+ : WeakComponentImplHelper(m_aMutex)
+ , m_pFrame(nullptr)
+ {
+ }
+
+ virtual ~Qt5DragSource() 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 Qt5DropTarget 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;
+ Qt5Frame* 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:
+ Qt5DropTarget();
+ virtual ~Qt5DropTarget() 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/Qt5FilePicker.hxx b/vcl/inc/qt5/Qt5FilePicker.hxx
new file mode 100644
index 000000000..5fef2aaea
--- /dev/null
+++ b/vcl/inc/qt5/Qt5FilePicker.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 .
+ */
+
+#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/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 <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::XFolderPicker2>
+ Qt5FilePicker_Base;
+
+class VCLPLUG_QT5_PUBLIC Qt5FilePicker : public QObject, public Qt5FilePicker_Base
+{
+ Q_OBJECT
+
+private:
+ css::uno::Reference<css::uno::XComponentContext> m_context;
+
+ css::uno::Reference<css::ui::dialogs::XFilePickerListener> m_xListener;
+
+ 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 Qt5FilePicker(css::uno::Reference<css::uno::XComponentContext> const& context,
+ QFileDialog::FileMode, bool bUseNative = false);
+ virtual ~Qt5FilePicker() 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;
+
+ // 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:
+ Qt5FilePicker(const Qt5FilePicker&) = delete;
+ Qt5FilePicker& operator=(const Qt5FilePicker&) = delete;
+
+ static QString getResString(const char* 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);
+
+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();
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/Qt5Font.hxx b/vcl/inc/qt5/Qt5Font.hxx
new file mode 100644
index 000000000..0720be931
--- /dev/null
+++ b/vcl/inc/qt5/Qt5Font.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 <fontinstance.hxx>
+
+#include <QtGui/QFont>
+
+#include "Qt5FontFace.hxx"
+
+class Qt5Font final : public QFont, public LogicalFontInstance
+{
+ friend rtl::Reference<LogicalFontInstance>
+ Qt5FontFace::CreateFontInstance(const FontSelectPattern&) const;
+
+ bool GetGlyphOutline(sal_GlyphId, basegfx::B2DPolyPolygon&, bool) const override;
+ bool ImplGetGlyphBoundRect(sal_GlyphId, tools::Rectangle&, bool) const override;
+
+ virtual hb_font_t* ImplInitHbFont() override;
+
+ explicit Qt5Font(const PhysicalFontFace&, const FontSelectPattern&);
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/Qt5FontFace.hxx b/vcl/inc/qt5/Qt5FontFace.hxx
new file mode 100644
index 000000000..585f4aaa8
--- /dev/null
+++ b/vcl/inc/qt5/Qt5FontFace.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 .
+ */
+
+#pragma once
+
+#include <vclpluginapi.h>
+#include <PhysicalFontFace.hxx>
+
+#include <tools/ref.hxx>
+#include <vcl/fontcapabilities.hxx>
+#include <vcl/fontcharmap.hxx>
+
+#include <QtCore/QString>
+#include <QtGui/QFont>
+
+class FontAttributes;
+class FontSelectPattern;
+
+class Qt5FontFace final : public PhysicalFontFace
+{
+public:
+ static Qt5FontFace* fromQFont(const QFont& rFont);
+ static Qt5FontFace* fromQFontDatabase(const QString& aFamily, const QString& aStyle);
+ static void fillAttributesFromQFont(const QFont& rFont, FontAttributes& rFA);
+
+ VCLPLUG_QT5_PUBLIC static FontWeight toFontWeight(const int nWeight);
+ VCLPLUG_QT5_PUBLIC static FontWidth toFontWidth(const int nStretch);
+ VCLPLUG_QT5_PUBLIC static FontItalic toFontItalic(const QFont::Style eStyle);
+
+ sal_IntPtr GetFontId() const override;
+
+ int GetFontTable(const char pTagName[5], unsigned char*) const;
+
+ const FontCharMapRef& GetFontCharMap() const;
+ bool GetFontCapabilities(vcl::FontCapabilities& rFontCapabilities) const;
+ bool HasChar(sal_uInt32 cChar) const;
+
+ rtl::Reference<LogicalFontInstance>
+ CreateFontInstance(const FontSelectPattern& rFSD) const override;
+
+private:
+ Qt5FontFace(const Qt5FontFace&);
+ Qt5FontFace(const FontAttributes& rFA, const QString& rFontID);
+
+ const QString m_aFontId;
+ mutable FontCharMapRef m_xCharMap;
+ mutable vcl::FontCapabilities m_aFontCapabilities;
+ mutable bool m_bFontCapabilitiesRead;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/Qt5Frame.hxx b/vcl/inc/qt5/Qt5Frame.hxx
new file mode 100644
index 000000000..0caf1bc3e
--- /dev/null
+++ b/vcl/inc/qt5/Qt5Frame.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 .
+ */
+
+#pragma once
+
+#include <config_vclplug.h>
+
+#include <salframe.hxx>
+#include <vclpluginapi.h>
+
+#include "Qt5Tools.hxx"
+
+#include <headless/svpgdi.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/sysdata.hxx>
+
+#include <QtCore/QObject>
+
+#if QT5_USING_X11
+#include <unx/screensaverinhibitor.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 Qt5DragSource;
+class Qt5DropTarget;
+class Qt5Graphics;
+class Qt5Instance;
+class Qt5MainWindow;
+class Qt5Menu;
+class Qt5SvpGraphics;
+
+class QDragMoveEvent;
+class QDropEvent;
+class QImage;
+class QMimeData;
+class QPaintDevice;
+class QScreen;
+class QWidget;
+
+class VCLPLUG_QT5_PUBLIC Qt5Frame : public QObject, public SalFrame
+{
+ Q_OBJECT
+
+ friend class Qt5Widget;
+
+ QWidget* m_pQWidget;
+ Qt5MainWindow* m_pTopLevel;
+
+ const bool m_bUseCairo;
+ std::unique_ptr<QImage> m_pQImage;
+ std::unique_ptr<Qt5Graphics> m_pQt5Graphics;
+ UniqueCairoSurface m_pSurface;
+ std::unique_ptr<Qt5SvpGraphics> m_pOurSvpGraphics;
+ // in base class, this ptr is the same as m_pOurSvpGraphic
+ // in derived class, it can point to a derivative
+ // of Qt5SvpGraphics (which the derived class then owns)
+ Qt5SvpGraphics* m_pSvpGraphics;
+ DamageHandler m_aDamageHandler;
+ QRegion m_aRegion;
+ bool m_bNullRegion;
+
+ bool m_bGraphicsInUse;
+ bool m_bGraphicsInvalid;
+ SalFrameStyleFlags m_nStyle;
+ Qt5Frame* m_pParent;
+ PointerStyle m_ePointerStyle;
+
+ SystemEnvData m_aSystemData;
+
+ Qt5Menu* m_pSalMenu;
+
+ Qt5DragSource* m_pDragSource;
+ Qt5DropTarget* 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 QT5_USING_X11
+ ScreenSaverInhibitor m_ScreenSaverInhibitor;
+#endif
+
+ 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);
+
+ void fixICCCMwindowGroup();
+
+public:
+ Qt5Frame(Qt5Frame* pParent, SalFrameStyleFlags nSalFrameStyle, bool bUseCairo);
+ virtual ~Qt5Frame() override;
+
+ QWidget* GetQWidget() const { return m_pQWidget; }
+ Qt5MainWindow* GetTopLevelWindow() const { return m_pTopLevel; }
+ QWidget* asChild() const;
+ qreal devicePixelRatioF() const;
+
+ void Damage(sal_Int32 nExtentsX, sal_Int32 nExtentsY, sal_Int32 nExtentsWidth,
+ sal_Int32 nExtentsHeight) const;
+
+ void InitQt5SvpGraphics(Qt5SvpGraphics* pQt5SvpGraphics);
+ 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 DrawMenuBar() override;
+
+ virtual void registerDragSource(Qt5DragSource* pDragSource);
+ virtual void deregisterDragSource(Qt5DragSource const* pDragSource);
+ virtual void registerDropTarget(Qt5DropTarget* pDropTarget);
+ virtual void deregisterDropTarget(Qt5DropTarget 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(long nWidth, long nHeight) override;
+ virtual void SetMaxClientSize(long nWidth, long nHeight) override;
+ virtual void SetPosSize(long nX, long nY, long nWidth, long nHeight,
+ sal_uInt16 nFlags) override;
+ virtual void GetClientSize(long& rWidth, long& rHeight) override;
+ virtual void GetWorkArea(tools::Rectangle& rRect) override;
+ virtual SalFrame* GetParent() const override;
+ virtual void SetModal(bool bModal) override;
+ virtual bool GetModal() const override;
+ virtual void SetWindowState(const SalFrameState* pState) override;
+ virtual bool GetWindowState(SalFrameState* pState) 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(long nX, 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 bool SetPluginParent(SystemParentData* pNewParent) override;
+ virtual void ResetClipRegion() override;
+ virtual void BeginSetClipRegion(sal_uInt32 nRects) override;
+ virtual void UnionClipRegion(long nX, long nY, long nWidth, long nHeight) override;
+ virtual void EndSetClipRegion() override;
+
+ virtual void SetScreenNumber(unsigned int) override;
+ virtual void SetApplicationID(const OUString&) override;
+
+ inline bool CallCallback(SalEvent nEvent, const void* pEvent) const;
+
+ cairo_t* getCairoContext() const;
+};
+
+inline bool Qt5Frame::CallCallback(SalEvent nEvent, const void* pEvent) const
+{
+ SolarMutexGuard aGuard;
+ return SalFrame::CallCallback(nEvent, pEvent);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/Qt5Graphics.hxx b/vcl/inc/qt5/Qt5Graphics.hxx
new file mode 100644
index 000000000..bc4870ee7
--- /dev/null
+++ b/vcl/inc/qt5/Qt5Graphics.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 <salgdi.hxx>
+
+#include <memory>
+
+#include <QtGui/QPainter>
+#include <QtGui/QPainterPath>
+#include <QtGui/QRegion>
+
+#include "Qt5GraphicsBase.hxx"
+
+class PhysicalFontCollection;
+class QImage;
+class QPushButton;
+class Qt5Font;
+class Qt5FontFace;
+class Qt5Frame;
+class Qt5Painter;
+
+class Qt5Graphics final : public SalGraphics, public Qt5GraphicsBase
+{
+ friend class Qt5Bitmap;
+ friend class Qt5Painter;
+
+ Qt5Frame* m_pFrame;
+ QImage* m_pQImage;
+ QRegion m_aClipRegion;
+ QPainterPath m_aClipPath;
+ Color m_aLineColor;
+ Color m_aFillColor;
+ QPainter::CompositionMode m_eCompositionMode;
+
+ PhysicalFontCollection* m_pFontCollection;
+ rtl::Reference<Qt5Font> m_pTextStyle[MAX_FALLBACK];
+ Color m_aTextColor;
+ std::unique_ptr<QPushButton> m_focusedButton;
+ std::unique_ptr<QImage> m_image;
+ QRect m_lastPopupRect;
+
+ Qt5Graphics(Qt5Frame* pFrame, QImage* pQImage);
+
+ void drawScaledImage(const SalTwoRect& rPosAry, const QImage& rImage);
+
+ void handleDamage(const tools::Rectangle&) override;
+
+public:
+ Qt5Graphics(Qt5Frame* pFrame)
+ : Qt5Graphics(pFrame, nullptr)
+ {
+ }
+ Qt5Graphics(QImage* pQImage)
+ : Qt5Graphics(nullptr, pQImage)
+ {
+ }
+ virtual ~Qt5Graphics() override;
+
+ void ChangeQImage(QImage* pImage);
+
+ virtual SalGraphicsImpl* GetImpl() const override;
+ virtual SystemGraphicsData GetGraphicsData() const override;
+ virtual bool supportsOperation(OutDevSupportType) const override;
+ virtual OUString getRenderBackendName() const override { return "qt5"; }
+
+#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;
+ virtual SystemFontData GetSysFontData(int nFallbacklevel) const override;
+#endif // ENABLE_CAIRO_CANVAS
+
+ // GDI
+
+ virtual bool setClipRegion(const vcl::Region&) override;
+ virtual void ResetClipRegion() override;
+
+ virtual void drawPixel(long nX, long nY) override;
+ virtual void drawPixel(long nX, long nY, Color nColor) override;
+ virtual void drawLine(long nX1, long nY1, long nX2, long nY2) override;
+ virtual void drawRect(long nX, long nY, long nWidth, long nHeight) override;
+ virtual void drawPolyLine(sal_uInt32 nPoints, const SalPoint* pPtAry) override;
+ virtual void drawPolygon(sal_uInt32 nPoints, const SalPoint* pPtAry) override;
+ virtual void drawPolyPolygon(sal_uInt32 nPoly, const sal_uInt32* pPoints,
+ PCONSTSALPOINT* pPtAry) override;
+ virtual bool drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon&, double fTransparency) override;
+ virtual bool drawPolyLineBezier(sal_uInt32 nPoints, const SalPoint* pPtAry,
+ const PolyFlags* pFlgAry) override;
+ virtual bool drawPolygonBezier(sal_uInt32 nPoints, const SalPoint* pPtAry,
+ const PolyFlags* pFlgAry) override;
+ virtual bool drawPolyPolygonBezier(sal_uInt32 nPoly, const sal_uInt32* pPoints,
+ const SalPoint* const* pPtAry,
+ const PolyFlags* const* pFlgAry) override;
+ virtual bool drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolygon&, double fTransparency, double fLineWidths,
+ const std::vector<double>* pStroke, // MM01
+ basegfx::B2DLineJoin, css::drawing::LineCap eLineCap,
+ double fMiterMinimumAngle, bool bPixelSnapHairline) override;
+ virtual bool drawGradient(const tools::PolyPolygon&, const Gradient&) override;
+
+ virtual void copyArea(long nDestX, long nDestY, long nSrcX, long nSrcY, long nSrcWidth,
+ long nSrcHeight, bool bWindowInvalidate) override;
+
+ 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(long nX, long nY, long nWidth,
+ long nHeight) override;
+ virtual Color getPixel(long nX, long nY) override;
+
+ virtual void invert(long nX, long nY, long nWidth, long nHeight, SalInvert nFlags) override;
+ virtual void invert(sal_uInt32 nPoints, const SalPoint* pPtAry, SalInvert nFlags) override;
+
+ virtual bool drawEPS(long nX, long nY, long nWidth, 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;
+
+ virtual 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) override;
+
+ virtual bool drawAlphaRect(long nX, long nY, long nWidth, long nHeight,
+ sal_uInt8 nTransparency) override;
+
+ virtual void GetResolution(sal_Int32& rDPIX, sal_Int32& rDPIY) override;
+ virtual sal_uInt16 GetBitCount() const override;
+ virtual long GetGraphicsWidth() const override;
+
+ virtual void SetLineColor() override;
+ virtual void SetLineColor(Color nColor) override;
+ virtual void SetFillColor() override;
+ virtual void SetFillColor(Color nColor) override;
+ virtual void SetXORMode(bool bSet, bool bInvertOnly) override;
+ virtual void SetROPLineColor(SalROPColor nROPColor) override;
+ virtual void SetROPFillColor(SalROPColor nROPColor) override;
+
+ // Text rendering + font support
+
+ virtual void SetTextColor(Color nColor) override;
+ virtual void SetFont(LogicalFontInstance*, int nFallbackLevel) override;
+ virtual void GetFontMetric(ImplFontMetricDataRef&, int nFallbackLevel) override;
+ virtual FontCharMapRef GetFontCharMap() const override;
+ virtual bool GetFontCapabilities(vcl::FontCapabilities& rFontCapabilities) const override;
+ virtual void GetDevFontList(PhysicalFontCollection*) override;
+ virtual void ClearDevFontCache() override;
+ virtual bool AddTempDevFont(PhysicalFontCollection*, const OUString& rFileURL,
+ const OUString& rFontName) override;
+ virtual bool CreateFontSubset(const OUString& rToFile, const PhysicalFontFace* pFont,
+ const sal_GlyphId* pGlyphIds, const sal_uInt8* pEncoding,
+ sal_Int32* pWidths, int nGlyphs,
+ FontSubsetInfo& rInfo // out parameter
+ ) override;
+
+ virtual const void* GetEmbedFontData(const PhysicalFontFace*, long* pDataLen) override;
+ virtual void FreeEmbedFontData(const void* pData, long nDataLen) override;
+
+ virtual void GetGlyphWidths(const PhysicalFontFace*, bool bVertical,
+ std::vector<sal_Int32>& rWidths, Ucs2UIntMap& rUnicodeEnc) 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/Qt5GraphicsBase.hxx b/vcl/inc/qt5/Qt5GraphicsBase.hxx
new file mode 100644
index 000000000..ef7955186
--- /dev/null
+++ b/vcl/inc/qt5/Qt5GraphicsBase.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 <QtWidgets/QApplication>
+
+class Qt5GraphicsBase
+{
+ qreal m_fDPR;
+
+protected:
+ Qt5GraphicsBase()
+ : m_fDPR(qApp ? qApp->devicePixelRatio() : 1.0)
+ {
+ }
+
+ void setDevicePixelRatioF(qreal fDPR) { m_fDPR = fDPR; }
+
+public:
+ qreal devicePixelRatioF() const { return m_fDPR; }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/Qt5Graphics_Controls.hxx b/vcl/inc/qt5/Qt5Graphics_Controls.hxx
new file mode 100644
index 000000000..325e5c351
--- /dev/null
+++ b/vcl/inc/qt5/Qt5Graphics_Controls.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 <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 Qt5GraphicsBase;
+
+class Qt5Graphics_Controls final : public vcl::WidgetDrawInterface
+{
+ std::unique_ptr<QImage> m_image;
+ QRect m_lastPopupRect;
+ Qt5GraphicsBase const& m_rGraphics;
+
+public:
+ Qt5Graphics_Controls(const Qt5GraphicsBase& 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);
+ 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* option, QImage* image,
+ QStyle::State const state = QStyle::State_None, QRect rect = QRect());
+ void draw(QStyle::PrimitiveElement element, QStyleOption* option, QImage* image,
+ QStyle::State const state = QStyle::State_None, QRect rect = QRect());
+ void draw(QStyle::ComplexControl element, QStyleOptionComplex* option, QImage* image,
+ QStyle::State const state = QStyle::State_None);
+ void drawFrame(QStyle::PrimitiveElement element, QImage* image, 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/Qt5Instance.hxx b/vcl/inc/qt5/Qt5Instance.hxx
new file mode 100644
index 000000000..361eca0fc
--- /dev/null
+++ b/vcl/inc/qt5/Qt5Instance.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in 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 "Qt5FilePicker.hxx"
+
+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_QT5_PUBLIC Qt5Instance : public QObject,
+ public SalGenericInstance,
+ public SalUserEventList
+{
+ Q_OBJECT
+
+ osl::Condition m_aWaitingYieldCond;
+ int m_postUserEventId;
+ const bool m_bUseCairo;
+ 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;
+
+ DECL_LINK(updateStyleHdl, Timer*, void);
+ void AfterAppInit() override;
+
+private Q_SLOTS:
+ bool ImplYield(bool bWait, bool bHandleAllCurrentEvents);
+ void ImplRunInMain();
+ static void deleteObjectLater(QObject* pObject);
+
+Q_SIGNALS:
+ bool ImplYieldSignal(bool bWait, bool bHandleAllCurrentEvents);
+ void ImplRunInMainSignal();
+ void deleteObjectLaterSignal(QObject* pObject);
+
+protected:
+ virtual Qt5FilePicker*
+ createPicker(css::uno::Reference<css::uno::XComponentContext> const& context,
+ QFileDialog::FileMode);
+
+public:
+ explicit Qt5Instance(std::unique_ptr<QApplication>& pQApp, bool bUseCairo = false);
+ virtual ~Qt5Instance() 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* pGraphics, long& nDX, 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;
+
+ virtual OpenGLContext* CreateOpenGLContext() 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 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> CreateDragSource() override;
+ virtual css::uno::Reference<css::uno::XInterface> CreateDropTarget() override;
+
+ void UpdateStyle(bool bFontsChanged);
+
+ void* CreateGStreamerSink(const SystemChildWindow*) override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/Qt5MainWindow.hxx b/vcl/inc/qt5/Qt5MainWindow.hxx
new file mode 100644
index 000000000..f1e91b489
--- /dev/null
+++ b/vcl/inc/qt5/Qt5MainWindow.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 "Qt5Frame.hxx"
+
+class Qt5MainWindow : public QMainWindow
+{
+ Q_OBJECT
+
+ Qt5Frame& m_rFrame;
+
+ virtual void closeEvent(QCloseEvent* pEvent) override;
+ void moveEvent(QMoveEvent*) override;
+
+public:
+ Qt5MainWindow(Qt5Frame& rFrame, Qt::WindowFlags f = Qt::WindowFlags());
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/Qt5Menu.hxx b/vcl/inc/qt5/Qt5Menu.hxx
new file mode 100644
index 000000000..2e5434f4d
--- /dev/null
+++ b/vcl/inc/qt5/Qt5Menu.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/.
+ */
+
+#pragma once
+
+#include <salmenu.hxx>
+
+#include <QtCore/QObject>
+
+#include <memory>
+
+class MenuItemList;
+class QAction;
+class QActionGroup;
+class QPushButton;
+class QMenu;
+class QMenuBar;
+class Qt5MenuItem;
+class Qt5Frame;
+
+/*
+ * Qt5Menu 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 Qt5Menu (held in 'mpOwnedQMenu').
+ * (3) a "submenu" in an existing menu (like (1)), in which case the corresponding
+ * QMenu object is owned by the corresponding Qt5MenuItem.
+ *
+ * For (2) and (3), member 'mpQMenu' points to the corresponding QMenu object.
+ */
+class Qt5Menu : public QObject, public SalMenu
+{
+ Q_OBJECT
+private:
+ std::vector<Qt5MenuItem*> maItems;
+ VclPtr<Menu> mpVCLMenu;
+ Qt5Menu* mpParentSalMenu;
+ Qt5Frame* mpFrame;
+ bool mbMenuBar;
+ QMenuBar* mpQMenuBar;
+ // self-created QMenu that this Qt5Menu represents, if applicable (s. comment for class)
+ std::unique_ptr<QMenu> mpOwnedQMenu;
+ // pointer to QMenu owned by the corresponding Qt5MenuItem or self (-> mpOwnedQMenu)
+ QMenu* mpQMenu;
+ QPushButton* mpCloseButton;
+ QMetaObject::Connection maCloseButtonConnection;
+
+ void DoFullMenuUpdate(Menu* pMenuBar);
+ static void NativeItemText(OUString& rItemText);
+
+ void InsertMenuItem(Qt5MenuItem* pSalMenuItem, unsigned nPos);
+
+ void ReinitializeActionGroup(unsigned nPos);
+ void ResetAllActionGroups();
+ void UpdateActionGroupItem(const Qt5MenuItem* pSalMenuItem);
+
+public:
+ Qt5Menu(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;
+ const Qt5Frame* GetFrame() const;
+ virtual void ShowMenuBar(bool bVisible) override;
+ virtual bool ShowNativePopupMenu(FloatingWindow* pWin, const tools::Rectangle& rRect,
+ FloatWinPopupFlags nFlags) override;
+ Qt5Menu* 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;
+
+ void SetMenu(Menu* pMenu) { mpVCLMenu = pMenu; }
+ Menu* GetMenu() { return mpVCLMenu; }
+ unsigned GetItemCount() const { return maItems.size(); }
+ Qt5MenuItem* GetItemAtPos(unsigned nPos) { return maItems[nPos]; }
+
+private slots:
+ static void slotMenuTriggered(Qt5MenuItem* pQItem);
+ static void slotMenuAboutToShow(Qt5MenuItem* pQItem);
+ static void slotMenuAboutToHide(Qt5MenuItem* pQItem);
+ void slotCloseDocument();
+};
+
+class Qt5MenuItem : public SalMenuItem
+{
+public:
+ Qt5MenuItem(const SalItemParams*);
+
+ QAction* getAction() const;
+
+ Qt5Menu* mpParentMenu; // The menu into which this menu item is inserted
+ Qt5Menu* 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/Qt5Object.hxx b/vcl/inc/qt5/Qt5Object.hxx
new file mode 100644
index 000000000..0479db2d6
--- /dev/null
+++ b/vcl/inc/qt5/Qt5Object.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 <salobj.hxx>
+#include <vcl/sysdata.hxx>
+
+#include <QtCore/QObject>
+#include <QtGui/QRegion>
+#include <QtGui/QWindow>
+
+class Qt5Frame;
+class QWidget;
+
+class Qt5Object final : public QObject, public SalObject
+{
+ Q_OBJECT
+
+ SystemEnvData m_aSystemData;
+ Qt5Frame* m_pParent;
+ QWidget* m_pQWidget; // main widget, container
+ QWindow* m_pQWindow; // contained window, used for opengl rendering
+ QRegion m_pRegion;
+
+public:
+ Qt5Object(Qt5Frame* pParent, bool bShow);
+ ~Qt5Object() override;
+
+ Qt5Frame* frame() const { return m_pParent; }
+ QWidget* widget() const { return m_pQWidget; }
+ QWindow* windowHandle() const { return m_pQWindow; }
+
+ virtual void ResetClipRegion() override;
+ virtual void BeginSetClipRegion(sal_uInt32 nRects) override;
+ virtual void UnionClipRegion(long nX, long nY, long nWidth, long nHeight) override;
+ virtual void EndSetClipRegion() override;
+
+ virtual void SetPosSize(long nX, long nY, long nWidth, long nHeight) override;
+ virtual void Show(bool bVisible) override;
+
+ virtual void SetForwardKey(bool bEnable) override;
+
+ virtual const SystemEnvData* GetSystemData() const override { return &m_aSystemData; }
+};
+
+class Qt5ObjectWindow final : public QWindow
+{
+ Qt5Object& m_rParent;
+
+ bool event(QEvent*) override;
+ void focusInEvent(QFocusEvent*) override;
+ void focusOutEvent(QFocusEvent*) override;
+ void mousePressEvent(QMouseEvent*) override;
+ void mouseReleaseEvent(QMouseEvent*) override;
+ // keyPressEvent(QKeyEvent*) is handled via event(QEvent*); see comment in Qt5Widget::event
+ void keyReleaseEvent(QKeyEvent*) override;
+
+public:
+ explicit Qt5ObjectWindow(Qt5Object& rParent);
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/Qt5OpenGLContext.hxx b/vcl/inc/qt5/Qt5OpenGLContext.hxx
new file mode 100644
index 000000000..df5424620
--- /dev/null
+++ b/vcl/inc/qt5/Qt5OpenGLContext.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 Qt5OpenGLContext 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/Qt5Painter.hxx b/vcl/inc/qt5/Qt5Painter.hxx
new file mode 100644
index 000000000..51ddc3d69
--- /dev/null
+++ b/vcl/inc/qt5/Qt5Painter.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 .
+ */
+
+#pragma once
+
+#include <QtCore/QRectF>
+#include <QtGui/QPainter>
+#include <QtWidgets/QWidget>
+
+#include "Qt5Frame.hxx"
+#include "Qt5Graphics.hxx"
+
+class Qt5Painter final : public QPainter
+{
+ Qt5Graphics& m_rGraphics;
+ QRegion m_aRegion;
+
+public:
+ Qt5Painter(Qt5Graphics& rGraphics, bool bPrepareBrush = false, sal_uInt8 nTransparency = 255);
+ ~Qt5Painter()
+ {
+ 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/Qt5Printer.hxx b/vcl/inc/qt5/Qt5Printer.hxx
new file mode 100644
index 000000000..23f5428ca
--- /dev/null
+++ b/vcl/inc/qt5/Qt5Printer.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 Qt5Printer final : public PspSalPrinter
+{
+public:
+ Qt5Printer(SalInfoPrinter* pInfoPrinter);
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/Qt5SvpGraphics.hxx b/vcl/inc/qt5/Qt5SvpGraphics.hxx
new file mode 100644
index 000000000..2ea5e6ad9
--- /dev/null
+++ b/vcl/inc/qt5/Qt5SvpGraphics.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 <vclpluginapi.h>
+#include <headless/svpgdi.hxx>
+
+#include "Qt5GraphicsBase.hxx"
+
+class Qt5Frame;
+
+class VCLPLUG_QT5_PUBLIC Qt5SvpGraphics : public SvpSalGraphics, public Qt5GraphicsBase
+{
+ Qt5Frame* const m_pFrame;
+
+protected:
+ void handleDamage(const tools::Rectangle&) override;
+
+public:
+ Qt5SvpGraphics(Qt5Frame* pFrame);
+ ~Qt5SvpGraphics() 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/Qt5SvpSurface.hxx b/vcl/inc/qt5/Qt5SvpSurface.hxx
new file mode 100644
index 000000000..0e4da4877
--- /dev/null
+++ b/vcl/inc/qt5/Qt5SvpSurface.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 Qt5SvpGraphics;
+class OutputDevice;
+
+namespace cairo
+{
+class Qt5SvpSurface final : public Surface
+{
+ const Qt5SvpGraphics* m_pGraphics;
+ cairo_t* const m_pCairoContext;
+ CairoSurfaceSharedPtr m_pSurface;
+
+public:
+ /// takes over ownership of passed cairo_surface
+ explicit Qt5SvpSurface(const CairoSurfaceSharedPtr& pSurface);
+ /// create surface on subarea of given drawable
+ explicit Qt5SvpSurface(const Qt5SvpGraphics* pGraphics, int x, int y, int width, int height);
+ ~Qt5SvpSurface() 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/Qt5System.hxx b/vcl/inc/qt5/Qt5System.hxx
new file mode 100644
index 000000000..63a7e9bde
--- /dev/null
+++ b/vcl/inc/qt5/Qt5System.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 <unx/gensys.h>
+
+class Qt5System final : public SalGenericSystem
+{
+public:
+ virtual unsigned int GetDisplayScreenCount() override;
+ virtual tools::Rectangle 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/Qt5Timer.hxx b/vcl/inc/qt5/Qt5Timer.hxx
new file mode 100644
index 000000000..20b2a4c70
--- /dev/null
+++ b/vcl/inc/qt5/Qt5Timer.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in 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 Qt5Timer 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:
+ Qt5Timer();
+
+ virtual void Start(sal_uInt64 nMS) override;
+ virtual void Stop() override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/Qt5Tools.hxx b/vcl/inc/qt5/Qt5Tools.hxx
new file mode 100644
index 000000000..1b58750ec
--- /dev/null
+++ b/vcl/inc/qt5/Qt5Tools.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 <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 <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(reinterpret_cast<ushort const*>(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 tools::Rectangle& 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(),
+ 255 - rColor.GetTransparency());
+}
+
+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;
+}
+
+static constexpr QImage::Format Qt5_DefaultFormat32 = QImage::Format_ARGB32;
+
+inline QImage::Format getBitFormat(sal_uInt16 nBitCount)
+{
+ switch (nBitCount)
+ {
+ case 1:
+ return QImage::Format_Mono;
+ case 8:
+ return QImage::Format_Indexed8;
+ case 24:
+ return QImage::Format_RGB888;
+ case 32:
+ return Qt5_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 Qt5_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);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/Qt5Transferable.hxx b/vcl/inc/qt5/Qt5Transferable.hxx
new file mode 100644
index 000000000..0d1cc7050
--- /dev/null
+++ b/vcl/inc/qt5/Qt5Transferable.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/.
+ *
+ */
+
+#pragma once
+
+#include <cppuhelper/compbase.hxx>
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+
+#include <QtCore/QMimeData>
+#include <QtCore/QStringList>
+#include <QtGui/QClipboard>
+
+/**
+ * Qt5Transferable classes are used to read QMimeData via the XTransferable
+ * interface. All the functionality is already implemented in the Qt5Transferable.
+ *
+ * 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 Qt5MimeData, which is defined below.
+ **/
+class Qt5Transferable : public cppu::WeakImplHelper<css::datatransfer::XTransferable>
+{
+ Qt5Transferable(const Qt5Transferable&) = delete;
+
+ const QMimeData* m_pMimeData;
+ osl::Mutex m_aMutex;
+ bool m_bConvertFromLocale;
+ css::uno::Sequence<css::datatransfer::DataFlavor> m_aMimeTypeSeq;
+
+public:
+ Qt5Transferable(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 Qt5ClipboardTransferable final : public Qt5Transferable
+{
+ // to detect in-flight QMimeData changes
+ const QClipboard::Mode m_aMode;
+
+ bool hasInFlightChanged() const;
+
+public:
+ explicit Qt5ClipboardTransferable(const QClipboard::Mode aMode, const QMimeData* pMimeData);
+
+ // these are the same then Qt5Transferable, 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 Qt5Transferable Qt5DnDTransferable;
+
+/**
+ * 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 Qt5Transferable.
+ **/
+class Qt5MimeData final : public QMimeData
+{
+ friend class Qt5ClipboardTransferable;
+
+ 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;
+
+ QVariant retrieveData(const QString& mimeType, QVariant::Type type) const override;
+
+public:
+ explicit Qt5MimeData(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/Qt5VirtualDevice.hxx b/vcl/inc/qt5/Qt5VirtualDevice.hxx
new file mode 100644
index 000000000..89251c96d
--- /dev/null
+++ b/vcl/inc/qt5/Qt5VirtualDevice.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 <list>
+
+#include <QtCore/QSize>
+
+class Qt5Graphics;
+class QImage;
+enum class DeviceFormat;
+
+class Qt5VirtualDevice final : public SalVirtualDevice
+{
+ std::list<Qt5Graphics*> m_aGraphics;
+ std::unique_ptr<QImage> m_pImage;
+ DeviceFormat m_eFormat;
+ QSize m_aFrameSize;
+ double m_fScale;
+
+public:
+ Qt5VirtualDevice(DeviceFormat eFormat, double fScale);
+
+ // SalVirtualDevice
+ virtual SalGraphics* AcquireGraphics() override;
+ virtual void ReleaseGraphics(SalGraphics* pGraphics) override;
+
+ virtual bool SetSize(long nNewDX, long nNewDY) override;
+ virtual bool SetSizeUsingBuffer(long nNewDX, long nNewDY, sal_uInt8* pBuffer) override;
+
+ // SalGeometryProvider
+ virtual long GetWidth() const override;
+ virtual long GetHeight() const override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/Qt5Widget.hxx b/vcl/inc/qt5/Qt5Widget.hxx
new file mode 100644
index 000000000..159794b2d
--- /dev/null
+++ b/vcl/inc/qt5/Qt5Widget.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 <QtWidgets/QWidget>
+#include <rtl/ustring.hxx>
+
+#include <com/sun/star/uno/Reference.hxx>
+#include <com/sun/star/accessibility/XAccessibleEditableText.hpp>
+
+class Qt5Frame;
+class Qt5Object;
+
+class Qt5Widget : public QWidget
+{
+ Q_OBJECT
+
+ Qt5Frame& m_rFrame;
+ bool m_bNonEmptyIMPreeditSeen;
+ int m_nDeltaX;
+ int m_nDeltaY;
+
+ enum class ButtonKeyState
+ {
+ Pressed,
+ Released
+ };
+
+ static void commitText(Qt5Frame&, const QString& aText);
+ static bool handleKeyEvent(Qt5Frame&, const QWidget&, QKeyEvent*, const ButtonKeyState);
+ static void handleMouseButtonEvent(const Qt5Frame&, const QMouseEvent*, const ButtonKeyState);
+
+ 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 wheelEvent(QWheelEvent*) override;
+ virtual void closeEvent(QCloseEvent*) override;
+ virtual void changeEvent(QEvent*) override;
+
+ void inputMethodEvent(QInputMethodEvent*) override;
+ QVariant inputMethodQuery(Qt::InputMethodQuery) const override;
+
+public:
+ Qt5Widget(Qt5Frame& rFrame, Qt::WindowFlags f = Qt::WindowFlags());
+
+ Qt5Frame& frame() const { return m_rFrame; }
+ void endExtTextInput();
+
+ static bool handleEvent(Qt5Frame&, const QWidget&, QEvent*);
+ // key events might be propagated further down => call base on false
+ static inline bool handleKeyReleaseEvent(Qt5Frame&, const QWidget&, QKeyEvent*);
+ // mouse events are always accepted
+ static inline void handleMousePressEvent(const Qt5Frame&, const QMouseEvent*);
+ static inline void handleMouseReleaseEvent(const Qt5Frame&, const QMouseEvent*);
+};
+
+bool Qt5Widget::handleKeyReleaseEvent(Qt5Frame& rFrame, const QWidget& rWidget, QKeyEvent* pEvent)
+{
+ return handleKeyEvent(rFrame, rWidget, pEvent, ButtonKeyState::Released);
+}
+
+void Qt5Widget::handleMousePressEvent(const Qt5Frame& rFrame, const QMouseEvent* pEvent)
+{
+ handleMouseButtonEvent(rFrame, pEvent, ButtonKeyState::Pressed);
+}
+
+void Qt5Widget::handleMouseReleaseEvent(const Qt5Frame& rFrame, const QMouseEvent* pEvent)
+{
+ handleMouseButtonEvent(rFrame, pEvent, ButtonKeyState::Released);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/Qt5XAccessible.hxx b/vcl/inc/qt5/Qt5XAccessible.hxx
new file mode 100644
index 000000000..fe8e424e4
--- /dev/null
+++ b/vcl/inc/qt5/Qt5XAccessible.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/.
+ */
+
+#pragma once
+
+#include <vclpluginapi.h>
+
+#include <QtCore/QObject>
+
+#include <com/sun/star/accessibility/XAccessible.hpp>
+
+#include <vcl/window.hxx>
+
+class Qt5Frame;
+class Qt5Widget;
+
+// Wrapper class to hold a css::accessibility::XAccessible object
+// while being able to pass it as a QObject
+class Qt5XAccessible : public QObject
+{
+ Q_OBJECT
+
+public:
+ Qt5XAccessible(css::uno::Reference<css::accessibility::XAccessible> xAccessible);
+ css::uno::Reference<css::accessibility::XAccessible> m_xAccessible;
+};
+
+/* 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 000000000..cbd9a5e78
--- /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 multiplied 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() { 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/common.h b/vcl/inc/quartz/common.h
new file mode 100644
index 000000000..003711198
--- /dev/null
+++ b/vcl/inc/quartz/common.h
@@ -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_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>
+
+// CoreFoundation designers, in their wisdom, decided that CFRelease of NULL
+// cause a Crash, yet few API can return NULL when asking for the creation
+// of an object, which force us to paper the code with ugly if construct everywhere
+// and open the door to very nasty crash on rare occasion
+// this macro hide the mess
+#define SafeCFRelease(a) do { if(a) { CFRelease(a); (a)=NULL; } } while(false)
+
+#define round_to_long(a) ((a) >= 0 ? ((long)((a) + 0.5)) : ((long)((a) - 0.5)))
+
+#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/ctfonts.hxx b/vcl/inc/quartz/ctfonts.hxx
new file mode 100644
index 000000000..89cc55443
--- /dev/null
+++ b/vcl/inc/quartz/ctfonts.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_QUARTZ_CTFONTS_HXX
+#define INCLUDED_VCL_INC_QUARTZ_CTFONTS_HXX
+
+#include <quartz/salgdi.h>
+#include <sallayout.hxx>
+
+SystemFontList* GetCoretextFontList();
+FontAttributes DevFontFromCTFontDescriptor( CTFontDescriptorRef, bool* );
+
+#endif // INCLUDED_VCL_INC_QUARTZ_CTFONTS_HXX
+
+/* 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 000000000..87929249f
--- /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, sal_uInt16 nBitCount, const BitmapPalette& rPal ) override;
+ bool Create( const SalBitmap& rSalBmp ) override;
+ bool Create( const SalBitmap& rSalBmp, SalGraphics* pGraphics ) override;
+ bool Create( const SalBitmap& rSalBmp, sal_uInt16 nNewBitCount ) 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:
+ CGImageRef CreateWithMask( const QuartzSalBitmap& rMask, int nX, int nY, int nWidth, int nHeight ) const;
+ CGImageRef CreateColorMask( int nX, int nY, int nWidth, int nHeight, Color nMaskColor ) const;
+ CGImageRef CreateCroppedImage( int nX, int nY, int nWidth, int nHeight ) const;
+
+ 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 000000000..2ac33fdbd
--- /dev/null
+++ b/vcl/inc/quartz/salgdi.h
@@ -0,0 +1,425 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_SALGDI_H
+#define INCLUDED_VCL_INC_QUARTZ_SALGDI_H
+
+#include <vector>
+
+#include <basegfx/polygon/b2dpolypolygon.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>
+
+#include <vcl/fontcapabilities.hxx>
+#include <vcl/metric.hxx>
+
+
+#include <fontinstance.hxx>
+#include <impfontmetricdata.hxx>
+#include <PhysicalFontFace.hxx>
+#include <salgdi.hxx>
+
+#include <quartz/salgdicommon.hxx>
+#include <unordered_map>
+#include <hb-ot.h>
+
+#include <quartz/CGHelpers.hxx>
+
+class AquaSalFrame;
+class FontAttributes;
+class CoreTextStyle;
+class XorEmulation;
+
+// CoreText-specific physically available font face
+class CoreTextFontFace : public PhysicalFontFace
+{
+public:
+ CoreTextFontFace( const FontAttributes&, sal_IntPtr nFontID );
+ virtual ~CoreTextFontFace() override;
+
+ sal_IntPtr GetFontId() const override;
+
+ int GetFontTable( uint32_t nTagCode, unsigned char* ) const;
+ int GetFontTable( const char pTagName[5], unsigned char* ) const;
+
+ FontCharMapRef GetFontCharMap() const;
+ bool GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const;
+ bool HasChar( sal_uInt32 cChar ) const;
+
+ rtl::Reference<LogicalFontInstance> CreateFontInstance(const FontSelectPattern&) const override;
+
+private:
+ const sal_IntPtr mnFontId;
+ mutable FontCharMapRef mxCharMap;
+ mutable vcl::FontCapabilities maFontCapabilities;
+ mutable bool mbFontCapabilitiesRead;
+};
+
+class CoreTextStyle final : public LogicalFontInstance
+{
+ friend rtl::Reference<LogicalFontInstance> CoreTextFontFace::CreateFontInstance(const FontSelectPattern&) const;
+
+public:
+ ~CoreTextStyle() override;
+
+ void GetFontMetric( ImplFontMetricDataRef const & );
+ bool GetGlyphOutline(sal_GlyphId, basegfx::B2DPolyPolygon&, bool) const override;
+
+ CFMutableDictionaryRef GetStyleDict( void ) const { return mpStyleDict; }
+
+ /// <1.0: font is squeezed, >1.0 font is stretched, else 1.0
+ float mfFontStretch;
+ /// text rotation in radian
+ float mfFontRotation;
+ /// faux bold - true, if font doesn't have proper bold variants
+ bool mbFauxBold;
+
+private:
+ explicit CoreTextStyle(const PhysicalFontFace&, const FontSelectPattern&);
+
+ hb_font_t* ImplInitHbFont() override;
+ bool ImplGetGlyphBoundRect(sal_GlyphId, tools::Rectangle&, bool) const override;
+
+ /// CoreText text style object
+ CFMutableDictionaryRef mpStyleDict;
+};
+
+// TODO: move into cross-platform headers
+
+class SystemFontList
+{
+public:
+ SystemFontList( void );
+ ~SystemFontList( void );
+
+ bool Init( void );
+ void AddFont( CoreTextFontFace* );
+
+ void AnnounceFonts( PhysicalFontCollection& ) const;
+ CoreTextFontFace* GetFontDataFromId( sal_IntPtr nFontId ) const;
+
+private:
+ CTFontCollectionRef mpCTFontCollection;
+ CFArrayRef mpCTFontArray;
+
+ std::unordered_map<sal_IntPtr, rtl::Reference<CoreTextFontFace>> maFontContainer;
+};
+
+class AquaSalGraphics : public SalGraphics
+{
+ 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
+
+ XorEmulation* mpXorEmulation;
+ int mnXorMode; // 0: off 1: on 2: invert only
+ int mnWidth;
+ int mnHeight;
+ int mnBitmapDepth; // zero unless bitmap
+ /// device resolution of this graphics
+ long mnRealDPIX;
+ long mnRealDPIY;
+
+ /// path representing current clip region
+ CGMutablePathRef mxClipPath;
+
+ /// Drawing colors
+ /// pen color RGBA
+ RGBAColor maLineColor;
+ /// brush color RGBA
+ RGBAColor maFillColor;
+
+ // Device Font settings
+ rtl::Reference<CoreTextStyle> mpTextStyle[MAX_FALLBACK];
+ RGBAColor maTextColor;
+ /// allows text to be rendered without antialiasing
+ bool mbNonAntialiasedText;
+
+#ifdef MACOSX
+ AquaSalFrame* mpFrame;
+#endif
+
+ // Graphics types
+
+ /// is this a printer graphics
+ bool mbPrinter;
+ /// is this a virtual device graphics
+ bool mbVirDev;
+#ifdef MACOSX
+ /// is this a window graphics
+ bool mbWindow;
+
+#else // IOS
+
+ // mirror AquaSalVirtualDevice::mbForeignContext for SvpSalGraphics objects related to such
+ bool mbForeignContext;
+
+#endif
+
+public:
+ AquaSalGraphics();
+ virtual ~AquaSalGraphics() override;
+
+ bool IsPenVisible() const { return maLineColor.IsVisible(); }
+ bool IsBrushVisible() const { return maFillColor.IsVisible(); }
+
+ void SetWindowGraphics( AquaSalFrame* pFrame );
+ void SetPrinterGraphics( CGContextRef, long nRealDPIX, long nRealDPIY );
+ void SetVirDevGraphics(CGLayerHolder const & rLayer, CGContextRef, int nBitDepth = 0);
+#ifdef MACOSX
+ void initResolution( NSWindow* );
+ void copyResolution( AquaSalGraphics& );
+ void updateResolution();
+
+ bool IsWindowGraphics() const { return mbWindow; }
+ AquaSalFrame* getGraphicsFrame() const { return mpFrame; }
+ void setGraphicsFrame( AquaSalFrame* pFrame ) { mpFrame = pFrame; }
+#endif
+
+ void ImplDrawPixel( long nX, long nY, const RGBAColor& ); // helper to draw single pixels
+
+ bool CheckContext();
+ CGContextRef GetContext();
+#ifdef MACOSX
+ void UpdateWindow( NSRect& ); // delivered in NSView coordinates
+ void RefreshRect( const NSRect& );
+#else
+ void RefreshRect( const CGRect& ) {}
+#endif
+ void RefreshRect(float lX, float lY, float lWidth, float lHeight);
+
+ void SetState();
+ void UnsetState();
+ // InvalidateContext does an UnsetState and sets mrContext to 0
+ void InvalidateContext();
+
+ virtual SalGraphicsImpl* GetImpl() const override;
+
+ virtual bool setClipRegion( const vcl::Region& ) override;
+
+ // draw --> LineColor and FillColor and RasterOp and ClipRegion
+ virtual void drawPixel( long nX, long nY ) override;
+ virtual void drawPixel( long nX, long nY, Color nColor ) override;
+ virtual void drawLine( long nX1, long nY1, long nX2, long nY2 ) override;
+ virtual void drawRect( long nX, long nY, long nWidth, long nHeight ) override;
+ virtual void drawPolyLine( sal_uInt32 nPoints, const SalPoint* pPtAry ) override;
+ virtual void drawPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry ) override;
+ virtual void drawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints, PCONSTSALPOINT* pPtAry ) override;
+ virtual bool drawPolyPolygon(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon&,
+ double fTransparency) override;
+ virtual bool drawPolyLineBezier( sal_uInt32 nPoints, const SalPoint* pPtAry, const PolyFlags* pFlgAry ) override;
+ virtual bool drawPolygonBezier( sal_uInt32 nPoints, const SalPoint* pPtAry, const PolyFlags* pFlgAry ) override;
+ virtual bool drawPolyPolygonBezier( sal_uInt32 nPoly, const sal_uInt32* pPoints, const SalPoint* const* pPtAry, const PolyFlags* const* pFlgAry ) override;
+ virtual bool drawPolyLine(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolygon&,
+ double fTransparency,
+ double rLineWidth,
+ const std::vector< double >* pStroke, // MM01
+ basegfx::B2DLineJoin,
+ css::drawing::LineCap eLineCap,
+ double fMiterMinimumAngle,
+ bool bPixelSnapHairline) override;
+ virtual bool drawGradient( const tools::PolyPolygon&, const Gradient& ) override { return false; };
+
+ // CopyArea --> No RasterOp, but ClipRegion
+ virtual void copyArea( long nDestX, long nDestY, long nSrcX, long nSrcY, long nSrcWidth,
+ 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( long nX, long nY, long nWidth, long nHeight ) override;
+ virtual Color getPixel( long nX, long nY ) override;
+
+ // invert --> ClipRegion (only Windows or VirDevs)
+ virtual void invert( long nX, long nY, long nWidth, long nHeight, SalInvert nFlags) override;
+ virtual void invert( sal_uInt32 nPoints, const SalPoint* pPtAry, SalInvert nFlags ) override;
+
+ virtual bool drawEPS( long nX, long nY, long nWidth, 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;
+
+ virtual 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) override;
+
+ virtual bool drawAlphaRect( long nX, long nY, long nWidth,
+ long nHeight, sal_uInt8 nTransparency ) override;
+
+ // native widget rendering methods that require mirroring
+#ifdef MACOSX
+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:
+#endif
+
+ // 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 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;
+ // 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( ImplFontMetricDataRef&, 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( PhysicalFontCollection* ) override;
+ // graphics must drop any cached font info
+ virtual void ClearDevFontCache() override;
+ virtual bool AddTempDevFont( PhysicalFontCollection*, const OUString& rFileURL, const OUString& rFontName ) override;
+ // CreateFontSubset: a method to get a subset of glyhps of a font
+ // inside a new valid font file
+ // returns TRUE if creation of subset was successful
+ // parameters: rToFile: contains an osl file URL to write the subset to
+ // pFont: describes from which font to create a subset
+ // pGlyphIDs: the glyph ids to be extracted
+ // pEncoding: the character code corresponding to each glyph
+ // pWidths: the advance widths of the corresponding glyphs (in PS font units)
+ // nGlyphs: the number of glyphs
+ // rInfo: additional outgoing information
+ // implementation note: encoding 0 with glyph id 0 should be added implicitly
+ // as "undefined character"
+ virtual bool CreateFontSubset( const OUString& rToFile,
+ const PhysicalFontFace* pFont,
+ const sal_GlyphId* pGlyphIds,
+ const sal_uInt8* pEncoding,
+ sal_Int32* pWidths,
+ int nGlyphs,
+ FontSubsetInfo& rInfo // out parameter
+ ) override;
+
+ // GetEmbedFontData: gets the font data for a font marked
+ // embeddable by GetDevFontList or NULL in case of error
+ // parameters: pFont: describes the font in question
+ // pDataLen: out parameter, contains the byte length of the returned buffer
+ virtual const void* GetEmbedFontData(const PhysicalFontFace*, long* pDataLen) override;
+ // frees the font data again
+ virtual void FreeEmbedFontData( const void* pData, long nDataLen ) override;
+
+ virtual void GetGlyphWidths( const PhysicalFontFace*,
+ bool bVertical,
+ std::vector< sal_Int32 >& rWidths,
+ Ucs2UIntMap& rUnicodeEnc ) override;
+
+ virtual std::unique_ptr<GenericSalLayout>
+ GetTextLayout(int nFallbackLevel) override;
+ virtual void DrawTextLayout( const GenericSalLayout& ) override;
+ virtual bool supportsOperation( OutDevSupportType ) const override;
+ virtual OUString getRenderBackendName() const override { return "aqua"; }
+
+ virtual SystemGraphicsData
+ GetGraphicsData() const override;
+
+private:
+ // differences between VCL, Quartz and kHiThemeOrientation coordinate systems
+ // make some graphics seem to be vertically-mirrored from a VCL perspective
+ bool IsFlipped() const;
+
+ void ApplyXorContext();
+ void Pattern50Fill();
+ UInt32 getState( ControlState nState );
+ UInt32 getTrackState( ControlState nState );
+ static bool GetRawFontData( const PhysicalFontFace* pFontData,
+ std::vector<unsigned char>& rBuffer,
+ bool* pJustCFF );
+};
+
+// --- some trivial inlines
+
+#ifdef MACOSX
+
+inline void AquaSalGraphics::RefreshRect( const NSRect& rRect )
+{
+ RefreshRect( rRect.origin.x, rRect.origin.y, rRect.size.width, rRect.size.height );
+}
+
+#endif
+
+#endif // INCLUDED_VCL_INC_QUARTZ_SALGDI_H
+
+/* 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 000000000..71c40acdf
--- /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 000000000..9c2d70798
--- /dev/null
+++ b/vcl/inc/quartz/salvd.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_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 <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; // Quartz layer
+ AquaSalGraphics* mpGraphics; // current VirDev graphics
+
+ long mnWidth;
+ long mnHeight;
+
+ void Destroy();
+
+public:
+ AquaSalVirtualDevice( AquaSalGraphics* pGraphic, long &nDX, long &nDY, DeviceFormat eFormat, const SystemGraphicsData *pData );
+ virtual ~AquaSalVirtualDevice() override;
+
+ virtual SalGraphics* AcquireGraphics() override;
+ virtual void ReleaseGraphics( SalGraphics* pGraphics ) override;
+ virtual bool SetSize( long nNewDX, long nNewDY ) override;
+
+ long GetWidth() const override
+ {
+ return mnWidth;
+ }
+
+ 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 000000000..2452c151d
--- /dev/null
+++ b/vcl/inc/quartz/utils.h
@@ -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 .
+ */
+
+#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& );
+
+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);
+
+#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 000000000..c7b9e6119
--- /dev/null
+++ b/vcl/inc/regband.hxx
@@ -0,0 +1,129 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_VCL_INC_REGBAND_HXX
+#define INCLUDED_VCL_INC_REGBAND_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;
+ long mnXLeft;
+ long mnXRight;
+ bool mbRemoved;
+};
+
+enum class LineType { Ascending, Descending };
+
+// element for the list with x-separations
+struct ImplRegionBandPoint
+{
+ ImplRegionBandPoint* mpNextBandPoint;
+ long mnX;
+ 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
+ long mnYTop; // actual boundary of the band
+ long mnYBottom;
+
+ bool mbTouched : 1;
+
+ // create y-band with boundaries
+ ImplRegionBand( long nYTop, 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();
+
+ long GetXLeftBoundary() const;
+ 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( long nX, long nLineID,
+ bool bEndPoint, LineType eLineType );
+
+ void Union( long nXLeft, long nXRight );
+ void Intersect( long nXLeft, long nXRight );
+ void Exclude( long nXLeft, long nXRight );
+ void XOr( long nXLeft, long nXRight );
+
+ void MoveX( long nHorzMove );
+ void ScaleX( double fHorzScale );
+
+ bool IsInside( 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 000000000..a00726148
--- /dev/null
+++ b/vcl/inc/regionband.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_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 long nTop, const long nBottom);
+ void InsertBand(ImplRegionBand* pPreviousBand, ImplRegionBand* pBandToInsert);
+ void processPoints();
+ void CreateBandRange(long nYTop, long nYBottom);
+ void InsertLine(const Point& rStartPt, const Point& rEndPt, long nLineId);
+ void InsertPoint(const Point &rPoint, long nLineID, bool bEndPoint, LineType eLineType);
+ bool OptimizeBandList();
+ void Move(long nHorzMove, long nVertMove);
+ void Scale(double fScaleX, double fScaleY);
+ void InsertBands(long nTop, long nBottom);
+ static bool InsertSingleBand(ImplRegionBand* pBand, long nYBandPosition);
+ void Union(long nLeft, long nTop, long nRight, long nBottom);
+ void Intersect(long nLeft, long nTop, long nRight, long nBottom);
+ void Union(const RegionBand& rSource);
+ void Exclude(long nLeft, long nTop, long nRight, long nBottom);
+ void XOr(long nLeft, long nTop, long nRight, long nBottom);
+ void Intersect(const RegionBand& rSource);
+ bool Exclude(const RegionBand& rSource);
+ void XOr(const RegionBand& rSource);
+ tools::Rectangle GetBoundRect() const;
+ bool IsInside(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 000000000..4244ae1a3
--- /dev/null
+++ b/vcl/inc/salbmp.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_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 <com/sun/star/rendering/XBitmapCanvas.hpp>
+
+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,
+ sal_uInt16 nBitCount,
+ 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,
+ sal_uInt16 nNewBitCount ) = 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;
+ }
+
+ void GetChecksum(BitmapChecksum& rChecksum) const
+ {
+ updateChecksum();
+ if (!mbChecksumValid)
+ rChecksum = 0; // back-compat
+ else
+ rChecksum = mnChecksum;
+ }
+
+ void InvalidateChecksum()
+ {
+ mbChecksumValid = false;
+ }
+
+protected:
+ BitmapChecksum mnChecksum;
+ bool mbChecksumValid;
+
+protected:
+ virtual void updateChecksum() const;
+ // helper function to convert data in 1,2,4 bpp formats to a 8/24/32bpp format
+ enum class BitConvert
+ {
+ A8,
+ RGB,
+ BGR,
+ RGBA,
+ 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 );
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/saldatabasic.hxx b/vcl/inc/saldatabasic.hxx
new file mode 100644
index 000000000..859117188
--- /dev/null
+++ b/vcl/inc/saldatabasic.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_SALDATABASIC_HXX
+#define INCLUDED_VCL_INC_SALDATABASIC_HXX
+
+#include <vcl/dllapi.h>
+
+#include "svdata.hxx"
+#include "salinst.hxx"
+
+#ifdef IOS
+#include "quartz/salgdi.h"
+#endif
+
+namespace psp
+{
+ class PrinterInfoManager;
+}
+
+class VCL_PLUGIN_PUBLIC SalData
+{
+public:
+ SalInstance* m_pInstance; // pointer to instance
+#ifndef IOS
+ psp::PrinterInfoManager* m_pPIManager;
+#endif
+
+ SalData();
+ virtual ~SalData() COVERITY_NOEXCEPT_FALSE;
+#ifdef IOS
+ SystemFontList* mpFontList;
+ CGColorSpaceRef mxRGBSpace;
+ CGColorSpaceRef mxGraySpace;
+ static void ensureThreadAutoreleasePool() {};
+#endif
+};
+
+inline void SetSalData( SalData* pData )
+{
+ ImplGetSVData()->mpSalData = pData;
+}
+
+inline SalData* GetSalData()
+{
+ return ImplGetSVData()->mpSalData;
+}
+
+#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 000000000..b83138e29
--- /dev/null
+++ b/vcl/inc/salframe.hxx
@@ -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 .
+ */
+
+#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;
+
+struct SalFrameState;
+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,
+ // partial fullscreen: fullscreen on one monitor of a multimonitor display
+ PARTIAL_FULLSCREEN = 0x00800000,
+ // 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, 0x788001ff> {};
+};
+
+// 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 long GetWidth() const override { return maGeometry.nWidth; }
+ virtual long GetHeight() const override { return maGeometry.nHeight; }
+ 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 DrawMenuBar() = 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( long nWidth, long nHeight ) = 0;
+ virtual void SetMaxClientSize( long nWidth, long nHeight ) = 0;
+ virtual void SetPosSize( long nX, long nY, long nWidth, long nHeight, sal_uInt16 nFlags ) = 0;
+ virtual void GetClientSize( long& rWidth, long& rHeight ) = 0;
+ virtual void GetWorkArea( tools::Rectangle& 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 SalFrameState* pState ) = 0;
+ // return the absolute, unmirrored system frame state
+ // if this returns false the structure is uninitialised
+ [[nodiscard]]
+ virtual bool GetWindowState( SalFrameState* pState ) = 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( long nX, 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;
+
+ // get current modifier, button mask and mouse position
+ struct SalPointerState
+ {
+ sal_uLong 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 bool 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( long nX, long nY, long nWidth, 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); }
+
+ // 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 ); }
+};
+
+#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 000000000..8d8e4c96d
--- /dev/null
+++ b/vcl/inc/salgdi.hxx
@@ -0,0 +1,638 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_SALGDI_HXX
+#define INCLUDED_VCL_INC_SALGDI_HXX
+
+#include <vcl/outdev.hxx>
+
+#include "impfontmetricdata.hxx"
+#include "salgdiimpl.hxx"
+#include "sallayout.hxx"
+#include "SalGradient.hxx"
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include "WidgetDrawInterface.hxx"
+
+#include <config_cairo_canvas.h>
+
+#include <map>
+#include <vector>
+
+class PhysicalFontCollection;
+class SalBitmap;
+class FontSelectPattern;
+class FontAttributes;
+class PhysicalFontFace;
+class SalLayout;
+class ImplLayoutArgs;
+namespace tools { class Rectangle; }
+class FontSubsetInfo;
+class OpenGLContext;
+class OutputDevice;
+class FreetypeFont;
+struct SystemGraphicsData;
+
+#if ENABLE_CAIRO_CANVAS
+struct SystemFontData;
+#endif // ENABLE_CAIRO_CANVAS
+
+namespace basegfx {
+ class B2DVector;
+ class B2DPolygon;
+ class B2DPolyPolygon;
+}
+
+typedef sal_Unicode sal_Ucs; // TODO: use sal_UCS4 instead of sal_Unicode
+typedef std::map< sal_Ucs, sal_uInt32 > Ucs2UIntMap;
+
+// 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;
+
+ /// Check that our mpImpl is OpenGL and return the context, otherwise NULL.
+ rtl::Reference<OpenGLContext> GetOpenGLContext() const;
+
+ void setAntiAliasB2DDraw(bool bNew) { m_bAntiAliasB2DDraw = bNew; }
+ bool getAntiAliasB2DDraw() const { return m_bAntiAliasB2DDraw; }
+
+ // 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 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( ImplFontMetricDataRef&, 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( PhysicalFontCollection* ) = 0;
+
+ // graphics must drop any cached font info
+ virtual void ClearDevFontCache() = 0;
+
+ virtual bool AddTempDevFont(
+ PhysicalFontCollection*,
+ const OUString& rFileURL,
+ const OUString& rFontName ) = 0;
+
+ // CreateFontSubset: a method to get a subset of glyhps of a font
+ // inside a new valid font file
+ // returns true if creation of subset was successful
+ // parameters: rToFile: contains an osl file URL to write the subset to
+ // pFont: describes from which font to create a subset
+ // pGlyphIDs: the glyph ids to be extracted
+ // pEncoding: the character code corresponding to each glyph
+ // pWidths: the advance widths of the corresponding glyphs (in PS font units)
+ // nGlyphs: the number of glyphs
+ // rInfo: additional outgoing information
+ // implementation note: encoding 0 with glyph id 0 should be added implicitly
+ // as "undefined character"
+ virtual bool CreateFontSubset(
+ const OUString& rToFile,
+ const PhysicalFontFace* pFont,
+ const sal_GlyphId* pGlyphIDs,
+ const sal_uInt8* pEncoding,
+ sal_Int32* pWidths,
+ int nGlyphs,
+ FontSubsetInfo& rInfo ) = 0;
+
+ // GetEmbedFontData: gets the font data for a font marked
+ // embeddable by GetDevFontList or NULL in case of error
+ // parameters: pFont: describes the font in question
+ // pDataLen: out parameter, contains the byte length of the returned buffer
+ virtual const void* GetEmbedFontData(const PhysicalFontFace* pFont, long* pDataLen) = 0;
+
+ // free the font data again
+ virtual void FreeEmbedFontData( const void* pData, long nDataLen ) = 0;
+
+ // get the same widths as in CreateFontSubset
+ // in case of an embeddable font also fill the mapping
+ // between unicode and glyph id
+ // leave widths vector and mapping untouched in case of failure
+ virtual void GetGlyphWidths(
+ const PhysicalFontFace* pFont,
+ bool bVertical,
+ std::vector< sal_Int32 >& rWidths,
+ Ucs2UIntMap& rUnicodeEnc ) = 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( long& nX, const OutputDevice *pOutDev ) const;
+ // only called mirror2 to avoid ambiguity
+ [[nodiscard]]
+ long mirror2( long nX, const OutputDevice *pOutDev ) const;
+ void mirror( long& nX, long nWidth, const OutputDevice *pOutDev, bool bBack = false ) const;
+ bool mirror( sal_uInt32 nPoints, const SalPoint *pPtAry, SalPoint *pPtAry2, const OutputDevice *pOutDev ) const;
+ void mirror( tools::Rectangle& rRect, const OutputDevice*, bool bBack = false ) const;
+ void mirror( vcl::Region& rRgn, const OutputDevice *pOutDev ) const;
+ void mirror( ImplControlValue&, const OutputDevice* ) const;
+ basegfx::B2DPolyPolygon mirror( const basegfx::B2DPolyPolygon& i_rPoly, const OutputDevice *pOutDev ) const;
+ const basegfx::B2DHomMatrix& getMirror( const OutputDevice *pOutDev ) const;
+
+ // non virtual methods; these do possible coordinate mirroring and
+ // then delegate to protected virtual methods
+ bool SetClipRegion( const vcl::Region&, const OutputDevice *pOutDev );
+
+ // draw --> LineColor and FillColor and RasterOp and ClipRegion
+ void DrawPixel( long nX, long nY, const OutputDevice *pOutDev );
+ void DrawPixel( long nX, long nY, Color nColor, const OutputDevice *pOutDev );
+
+ void DrawLine( long nX1, long nY1, long nX2, long nY2, const OutputDevice *pOutDev );
+
+ void DrawRect( long nX, long nY, long nWidth, long nHeight, const OutputDevice *pOutDev );
+
+ void DrawPolyLine( sal_uInt32 nPoints, SalPoint const * pPtAry, const OutputDevice *pOutDev );
+
+ void DrawPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry, const OutputDevice *pOutDev );
+
+ void DrawPolyPolygon(
+ sal_uInt32 nPoly,
+ const sal_uInt32* pPoints,
+ PCONSTSALPOINT* pPtAry,
+ const OutputDevice *pOutDev );
+
+ bool DrawPolyPolygon(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon &i_rPolyPolygon,
+ double i_fTransparency,
+ const OutputDevice *i_pOutDev);
+
+ 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_pOutDev);
+
+ bool DrawPolyLineBezier(
+ sal_uInt32 nPoints,
+ const SalPoint* pPtAry,
+ const PolyFlags* pFlgAry,
+ const OutputDevice *pOutDev );
+
+ bool DrawPolygonBezier(
+ sal_uInt32 nPoints,
+ const SalPoint* pPtAry,
+ const PolyFlags* pFlgAry,
+ const OutputDevice *pOutDev );
+
+ bool DrawPolyPolygonBezier(
+ sal_uInt32 nPoly,
+ const sal_uInt32* pPoints,
+ const SalPoint* const* pPtAry,
+ const PolyFlags* const* pFlgAry,
+ const OutputDevice *pOutDev );
+
+ bool DrawGradient(
+ const tools::PolyPolygon& rPolyPoly,
+ const Gradient& rGradient );
+
+ bool DrawGradient(basegfx::B2DPolyPolygon const & rPolyPolygon,
+ SalGradient const & rGradient);
+
+ // CopyArea --> No RasterOp, but ClipRegion
+ void CopyArea(
+ long nDestX, long nDestY,
+ long nSrcX, long nSrcY,
+ long nSrcWidth, long nSrcHeight,
+ const OutputDevice *pOutDev );
+
+ // CopyBits and DrawBitmap --> RasterOp and ClipRegion
+ // CopyBits() --> pSrcGraphics == NULL, then CopyBits on same Graphics
+ void CopyBits(
+ const SalTwoRect& rPosAry,
+ SalGraphics* pSrcGraphics,
+ const OutputDevice *pOutDev,
+ const OutputDevice *pSrcOutDev );
+
+ void DrawBitmap(
+ const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap,
+ const OutputDevice *pOutDev );
+
+ void DrawBitmap(
+ const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap,
+ const SalBitmap& rTransparentBitmap,
+ const OutputDevice *pOutDev );
+
+ void DrawMask(
+ const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap,
+ Color nMaskColor,
+ const OutputDevice *pOutDev );
+
+ std::shared_ptr<SalBitmap> GetBitmap(
+ long nX, long nY,
+ long nWidth, long nHeight,
+ const OutputDevice *pOutDev );
+
+ Color GetPixel(
+ long nX, long nY,
+ const OutputDevice *pOutDev );
+
+ // invert --> ClipRegion (only Windows)
+ void Invert(
+ long nX, long nY,
+ long nWidth, long nHeight,
+ SalInvert nFlags,
+ const OutputDevice *pOutDev );
+
+ void Invert(
+ sal_uInt32 nPoints,
+ const SalPoint* pPtAry,
+ SalInvert nFlags,
+ const OutputDevice *pOutDev );
+
+ bool DrawEPS(
+ long nX, long nY,
+ long nWidth, long nHeight,
+ void* pPtr,
+ sal_uInt32 nSize,
+ const OutputDevice *pOutDev );
+
+ // 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 *pOutDev );
+
+ /**
+ * @see WidgetDrawInterface::drawNativeControl
+ */
+ bool DrawNativeControl(
+ ControlType nType,
+ ControlPart nPart,
+ const tools::Rectangle& rControlRegion,
+ ControlState nState,
+ const ImplControlValue& aValue,
+ const OUString& aCaption,
+ const OutputDevice *pOutDev,
+ 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 *pOutDev );
+
+ /**
+ * @see WidgetDrawInterface::updateSettings
+ */
+ inline bool UpdateSettings(AllSettings&);
+
+ bool BlendBitmap(
+ const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap,
+ const OutputDevice *pOutDev );
+
+ bool BlendAlphaBitmap(
+ const SalTwoRect& rPosAry,
+ const SalBitmap& rSalSrcBitmap,
+ const SalBitmap& rSalMaskBitmap,
+ const SalBitmap& rSalAlphaBitmap,
+ const OutputDevice *pOutDev );
+
+ bool DrawAlphaBitmap(
+ const SalTwoRect&,
+ const SalBitmap& rSourceBitmap,
+ const SalBitmap& rAlphaBitmap,
+ const OutputDevice *pOutDev );
+
+ bool DrawTransformedBitmap(
+ const basegfx::B2DPoint& rNull,
+ const basegfx::B2DPoint& rX,
+ const basegfx::B2DPoint& rY,
+ const SalBitmap& rSourceBitmap,
+ const SalBitmap* pAlphaBitmap,
+ const OutputDevice* pOutDev );
+
+ bool DrawAlphaRect(
+ long nX, long nY,
+ long nWidth, long nHeight,
+ sal_uInt8 nTransparency,
+ const OutputDevice *pOutDev );
+
+ virtual OUString getRenderBackendName() const;
+
+ virtual SystemGraphicsData GetGraphicsData() const = 0;
+
+#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;
+
+ virtual SystemFontData GetSysFontData( int nFallbacklevel ) const = 0;
+
+#endif // ENABLE_CAIRO_CANVAS
+
+protected:
+ virtual bool setClipRegion( const vcl::Region& ) = 0;
+
+ // draw --> LineColor and FillColor and RasterOp and ClipRegion
+ virtual void drawPixel( long nX, long nY ) = 0;
+ virtual void drawPixel( long nX, long nY, Color nColor ) = 0;
+
+ virtual void drawLine( long nX1, long nY1, long nX2, long nY2 ) = 0;
+
+ virtual void drawRect( long nX, long nY, long nWidth, long nHeight ) = 0;
+
+ virtual void drawPolyLine( sal_uInt32 nPoints, const SalPoint* pPtAry ) = 0;
+
+ virtual void drawPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry ) = 0;
+
+ virtual void drawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints, PCONSTSALPOINT* pPtAry ) = 0;
+
+ virtual bool 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 SalPoint* pPtAry,
+ const PolyFlags* pFlgAry ) = 0;
+
+ virtual bool drawPolygonBezier(
+ sal_uInt32 nPoints,
+ const SalPoint* pPtAry,
+ const PolyFlags* pFlgAry ) = 0;
+
+ virtual bool drawPolyPolygonBezier(
+ sal_uInt32 nPoly,
+ const sal_uInt32* pPoints,
+ const SalPoint* 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(
+ long nDestX, long nDestY,
+ long nSrcX, long nSrcY,
+ long nSrcWidth, 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( long nX, long nY, long nWidth, long nHeight ) = 0;
+
+ virtual Color getPixel( long nX, long nY ) = 0;
+
+ // invert --> ClipRegion (only Windows or VirDevs)
+ virtual void invert(
+ long nX, long nY,
+ long nWidth, long nHeight,
+ SalInvert nFlags) = 0;
+
+ virtual void invert( sal_uInt32 nPoints, const SalPoint* pPtAry, SalInvert nFlags ) = 0;
+
+ virtual bool drawEPS(
+ long nX, long nY,
+ long nWidth, 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 */
+ virtual bool drawTransformedBitmap(
+ const basegfx::B2DPoint& rNull,
+ const basegfx::B2DPoint& rX,
+ const basegfx::B2DPoint& rY,
+ const SalBitmap& rSourceBitmap,
+ const SalBitmap* pAlphaBitmap) = 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(
+ long nX, long nY,
+ long nWidth, long nHeight,
+ sal_uInt8 nTransparency ) = 0;
+
+private:
+ SalLayoutFlags m_nLayout; //< 0: mirroring off, 1: mirror x-axis
+
+ // for buffering the Mirror-Matrix, see ::getMirror
+ basegfx::B2DHomMatrix m_aLastMirror;
+ long m_aLastMirrorW;
+
+protected:
+ /// flags which hold the SetAntialiasing() value from OutputDevice
+ bool m_bAntiAliasB2DDraw : 1;
+
+ inline long GetDeviceWidth(const OutputDevice* pOutDev) 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&) {}
+
+#endif // INCLUDED_VCL_INC_SALGDI_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/salgdiimpl.hxx b/vcl/inc/salgdiimpl.hxx
new file mode 100644
index 000000000..0e62669d6
--- /dev/null
+++ b/vcl/inc/salgdiimpl.hxx
@@ -0,0 +1,211 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 OpenGLContext;
+class SalVirtualDevice;
+
+class VCL_PLUGIN_PUBLIC SalGraphicsImpl
+{
+public:
+
+ virtual ~SalGraphicsImpl();
+
+ virtual void Init() = 0;
+
+ virtual void DeInit() {}
+
+ virtual void freeResources() = 0;
+
+ virtual OUString getRenderBackendName() const = 0;
+
+ virtual bool setClipRegion( const vcl::Region& ) = 0;
+ //
+ // get the depth of the device
+ virtual sal_uInt16 GetBitCount() const = 0;
+
+ // get the width of the device
+ virtual 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;
+
+ // draw --> LineColor and FillColor and RasterOp and ClipRegion
+ virtual void drawPixel( long nX, long nY ) = 0;
+ virtual void drawPixel( long nX, long nY, Color nColor ) = 0;
+
+ virtual void drawLine( long nX1, long nY1, long nX2, long nY2 ) = 0;
+
+ virtual void drawRect( long nX, long nY, long nWidth, long nHeight ) = 0;
+
+ virtual void drawPolyLine( sal_uInt32 nPoints, const SalPoint* pPtAry ) = 0;
+
+ virtual void drawPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry ) = 0;
+
+ virtual void drawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints, PCONSTSALPOINT* pPtAry ) = 0;
+
+ virtual bool 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 SalPoint* pPtAry,
+ const PolyFlags* pFlgAry ) = 0;
+
+ virtual bool drawPolygonBezier(
+ sal_uInt32 nPoints,
+ const SalPoint* pPtAry,
+ const PolyFlags* pFlgAry ) = 0;
+
+ virtual bool drawPolyPolygonBezier(
+ sal_uInt32 nPoly,
+ const sal_uInt32* pPoints,
+ const SalPoint* const* pPtAry,
+ const PolyFlags* const* pFlgAry ) = 0;
+
+ // CopyArea --> No RasterOp, but ClipRegion
+ virtual void copyArea(
+ long nDestX, long nDestY,
+ long nSrcX, long nSrcY,
+ long nSrcWidth, 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( long nX, long nY, long nWidth, long nHeight ) = 0;
+
+ virtual Color getPixel( long nX, long nY ) = 0;
+
+ // invert --> ClipRegion (only Windows or VirDevs)
+ virtual void invert(
+ long nX, long nY,
+ long nWidth, long nHeight,
+ SalInvert nFlags) = 0;
+
+ virtual void invert( sal_uInt32 nPoints, const SalPoint* pPtAry, SalInvert nFlags ) = 0;
+
+ virtual bool drawEPS(
+ long nX, long nY,
+ long nWidth, 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;
+
+ /** 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) = 0;
+
+ virtual bool drawAlphaRect(
+ long nX, long nY,
+ long nWidth, long nHeight,
+ sal_uInt8 nTransparency ) = 0;
+
+ virtual bool drawGradient(const tools::PolyPolygon& rPolygon, const Gradient& 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 000000000..6adeb7a69
--- /dev/null
+++ b/vcl/inc/salgeom.hxx
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_SALGEOM_HXX
+#define INCLUDED_VCL_INC_SALGEOM_HXX
+
+#include <iostream>
+
+#include <vcl/dllapi.h>
+
+struct SalFrameGeometry {
+ // screen position of upper left corner of drawable area in pixel
+ long nX, nY;
+ // dimensions of the drawable area in pixel
+ unsigned long nWidth, nHeight;
+ // thickness of the decoration in pixel
+ unsigned long nLeftDecoration,
+ nTopDecoration,
+ nRightDecoration,
+ nBottomDecoration;
+ unsigned int nDisplayScreenNumber;
+
+ SalFrameGeometry() :
+ nX( 0 ),
+ nY( 0 ),
+ nWidth( 1 ),
+ nHeight( 1 ),
+ nLeftDecoration( 0 ),
+ nTopDecoration( 0 ),
+ nRightDecoration( 0 ),
+ nBottomDecoration( 0 ),
+ nDisplayScreenNumber( 0 )
+ {}
+};
+
+inline std::ostream &operator <<(std::ostream& s, const SalFrameGeometry& rGeom)
+{
+ s << rGeom.nWidth << "x" << rGeom.nHeight << "@(" << rGeom.nX << "," << rGeom.nY << "):{"
+ << rGeom.nLeftDecoration << "," << rGeom.nTopDecoration << "," << rGeom.nRightDecoration << "," << rGeom.nBottomDecoration << "}";
+
+ return s;
+}
+
+/// Interface used to share logic on sizing between
+/// SalVirtualDevices and SalFrames
+class VCL_PLUGIN_PUBLIC SalGeometryProvider {
+public:
+ virtual ~SalGeometryProvider() {}
+ virtual long GetWidth() const = 0;
+ virtual 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 000000000..36c51d990
--- /dev/null
+++ b/vcl/inc/salinst.hxx
@@ -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 .
+ */
+
+#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 "backend/BackendCapabilities.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 {
+namespace sun {
+namespace star {
+namespace 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 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;
+
+public:
+ SalInstance(std::unique_ptr<comphelper::SolarMutex> pMutex);
+ virtual ~SalInstance();
+
+ //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* pGraphics,
+ long &rDX, 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;
+ // BackendCapabilities
+ virtual std::shared_ptr<vcl::BackendCapabilities> GetBackendCapabilities()
+ {
+ return std::make_shared<vcl::BackendCapabilities>();
+ }
+
+ // 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();
+
+ virtual OpenGLContext* CreateOpenGLContext() = 0;
+
+ virtual weld::Builder* CreateBuilder(weld::Widget* pParent, const OUString& rUIRoot, const OUString& rUIFile);
+ virtual weld::Builder* CreateInterimBuilder(vcl::Window* pParent, const OUString& rUIRoot, const OUString& rUIFile);
+ 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 > CreateDragSource();
+ virtual css::uno::Reference< css::uno::XInterface > CreateDropTarget();
+ 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 jobStartedPrinterUpdate() {}
+ 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; }
+};
+
+// 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 000000000..e94b4c205
--- /dev/null
+++ b/vcl/inc/sallayout.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_SALLAYOUT_HXX
+#define INCLUDED_VCL_INC_SALLAYOUT_HXX
+
+#include <iostream>
+#include <memory>
+#include <vector>
+
+#include <hb.h>
+
+#include <com/sun/star/i18n/XBreakIterator.hpp>
+
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <tools/gen.hxx>
+#include <vcl/dllapi.h>
+#include <vcl/vclenum.hxx> // for typedef sal_UCS4
+#include <vcl/devicecoordinate.hxx>
+#include <vcl/vcllayout.hxx>
+
+#include "impglyphitem.hxx"
+
+#define MAX_FALLBACK 16
+
+
+class SalGraphics;
+class PhysicalFontFace;
+class GenericSalLayout;
+enum class SalLayoutFlags;
+namespace vcl {
+ class TextLayoutCache;
+}
+
+// used for managing runs e.g. for BiDi, glyph and script fallback
+class ImplLayoutRuns
+{
+private:
+ int mnRunIndex;
+ std::vector<int> maRuns;
+
+public:
+ ImplLayoutRuns() { mnRunIndex = 0; maRuns.reserve(8); }
+
+ 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;
+};
+
+class VCL_DLLPUBLIC ImplLayoutArgs
+{
+public:
+ // string related inputs
+ LanguageTag maLanguageTag;
+ SalLayoutFlags mnFlags;
+ const OUString& mrStr;
+ int mnMinCharPos;
+ int mnEndCharPos;
+
+ // performance hack
+ vcl::TextLayoutCache const* m_pTextLayoutCache;
+
+ // positioning related inputs
+ const DeviceCoordinate* mpDXArray; // in pixel units
+ DeviceCoordinate mnLayoutWidth; // in pixel units
+ int mnOrientation; // in 0-3600 system
+
+ // data for bidi and glyph+script fallback
+ ImplLayoutRuns maRuns;
+ ImplLayoutRuns maFallbackRuns;
+
+ ImplLayoutArgs( const OUString& rStr,
+ int nMinCharPos, int nEndCharPos, SalLayoutFlags nFlags,
+ const LanguageTag& rLanguageTag,
+ vcl::TextLayoutCache const* pLayoutCache);
+
+ void SetLayoutWidth( DeviceCoordinate nWidth ) { mnLayoutWidth = nWidth; }
+ void SetDXArray( const DeviceCoordinate* pDXArray ) { mpDXArray = pDXArray; }
+ void SetOrientation( int nOrientation ) { mnOrientation = nOrientation; }
+
+ void ResetPos()
+ { maRuns.ResetPos(); }
+ bool GetNextPos( int* nCharPos, bool* bRTL )
+ { return maRuns.GetNextPos( nCharPos, bRTL ); }
+ bool GetNextRun( int* nMinRunPos, int* nEndRunPos, bool* bRTL );
+ void NeedFallback( int nMinRunPos, int nEndRunPos, bool bRTL )
+ { maFallbackRuns.AddRun( nMinRunPos, nEndRunPos, bRTL ); }
+ // methods used by BiDi and glyph fallback
+ bool NeedFallback() const
+ { return !maFallbackRuns.IsEmpty(); }
+ bool PrepareFallback();
+
+private:
+ void AddRun( int nMinCharPos, int nEndCharPos, bool bRTL );
+};
+
+// For nice SAL_INFO logging of ImplLayoutArgs values
+std::ostream &operator <<(std::ostream& s, ImplLayoutArgs const &rArgs);
+
+class MultiSalLayout final : public SalLayout
+{
+public:
+ void DrawText(SalGraphics&) const override;
+ sal_Int32 GetTextBreak(DeviceCoordinate nMaxWidth, DeviceCoordinate nCharExtra, int nFactor) const override;
+ DeviceCoordinate FillDXArray(DeviceCoordinate* pDXArray) const override;
+ void GetCaretPositions(int nArraySize, long* pCaretXArray) const override;
+ bool GetNextGlyph(const GlyphItem** pGlyph, Point& rPos, int& nStart,
+ const PhysicalFontFace** pFallbackFont = nullptr,
+ int* const pFallbackLevel = nullptr) const override;
+ bool GetOutline(basegfx::B2DPolyPolygonVector&) const override;
+ bool IsKashidaPosValid(int nCharPos) const 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 &);
+ bool LayoutText(ImplLayoutArgs&, const SalLayoutGlyphs*) override;
+ void AdjustLayout(ImplLayoutArgs&) override;
+ void InitFont() const override;
+
+ void SetIncomplete(bool bIncomplete);
+
+public:
+ 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::AdjustLayout(ImplLayoutArgs&);
+
+public:
+ GenericSalLayout(LogicalFontInstance&);
+ ~GenericSalLayout() override;
+
+ void AdjustLayout(ImplLayoutArgs&) final override;
+ bool LayoutText(ImplLayoutArgs&, const SalLayoutGlyphs*) final override;
+ void DrawText(SalGraphics&) const final override;
+ static std::shared_ptr<vcl::TextLayoutCache> CreateTextLayoutCache(OUString const&);
+ const SalLayoutGlyphs* GetGlyphs() const final override;
+
+ bool IsKashidaPosValid(int nCharPos) const final override;
+
+ // used by upper layers
+ DeviceCoordinate GetTextWidth() const final override;
+ DeviceCoordinate FillDXArray(DeviceCoordinate* pDXArray) const final override;
+ sal_Int32 GetTextBreak(DeviceCoordinate nMaxWidth, DeviceCoordinate nCharExtra, int nFactor) const final override;
+ void GetCaretPositions(int nArraySize, long* pCaretXArray) const final override;
+
+ // used by display layers
+ LogicalFontInstance& GetFont() const
+ { return m_GlyphItems.Impl()->GetFont(); }
+
+ bool GetNextGlyph(const GlyphItem** pGlyph, Point& rPos, int& nStart,
+ const PhysicalFontFace** pFallbackFont = nullptr,
+ int* const pFallbackLevel = nullptr) const override;
+
+private:
+ // for glyph+font+script fallback
+ void MoveGlyph(int nStart, long nNewXPos);
+ void DropGlyph(int nStart);
+ void Simplify(bool bIsBase);
+
+ GenericSalLayout( const GenericSalLayout& ) = delete;
+ GenericSalLayout& operator=( const GenericSalLayout& ) = delete;
+
+ void ApplyDXArray(const ImplLayoutArgs&);
+ void Justify(DeviceCoordinate nNewWidth);
+ void ApplyAsianKerning(const OUString& rStr);
+
+ void GetCharWidths(DeviceCoordinate* pCharWidths) const;
+
+ void SetNeedFallback(ImplLayoutArgs&, sal_Int32, bool);
+
+ bool HasVerticalAlternate(sal_UCS4 aChar, sal_UCS4 aNextChar);
+
+ void ParseFeatures(const OUString& name);
+
+ css::uno::Reference<css::i18n::XBreakIterator> mxBreak;
+
+ SalLayoutGlyphs m_GlyphItems;
+
+ OString msLanguage;
+ std::vector<hb_feature_t> maFeatures;
+
+ hb_set_t* mpVertGlyphs;
+ const bool mbFuzzing;
+};
+
+#undef SalGraphics
+
+#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 000000000..c69650354
--- /dev/null
+++ b/vcl/inc/salmenu.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_SALMENU_HXX
+#define INCLUDED_VCL_INC_SALMENU_HXX
+
+#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, const Image& rImg, const OUString& i_rTTText )
+ : mnId( i_nId ), maImage( rImg ), maToolTipText( i_rTTText ) {}
+};
+
+class VCL_PLUGIN_PUBLIC SalMenuItem
+{
+public:
+ SalMenuItem() {}
+ virtual ~SalMenuItem();
+};
+
+class VCL_PLUGIN_PUBLIC SalMenu
+{
+public:
+ SalMenu() {}
+ 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 000000000..6e79e4920
--- /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( long nX, long nY, long nWidth, long nHeight ) = 0;
+ virtual void EndSetClipRegion() = 0;
+
+ virtual void SetPosSize( long nX, long nY, long nWidth, 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 000000000..9c631dc02
--- /dev/null
+++ b/vcl/inc/salprn.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_SALPRN_HXX
+#define INCLUDED_VCL_INC_SALPRN_HXX
+
+#include <i18nutil/paper.hxx>
+#include <vcl/prntypes.hxx>
+#include <vcl/dllapi.h>
+
+#include "salptype.hxx"
+
+#include <vector>
+
+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::unique_ptr<OUString> mpPortName; // 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,
+ long& rOutWidth, 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 000000000..9d09c869a
--- /dev/null
+++ b/vcl/inc/salptype.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_SALPTYPE_HXX
+#define INCLUDED_VCL_INC_SALPTYPE_HXX
+
+#include <sal/config.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 000000000..fb9763bb6
--- /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 000000000..fc80e90d0
--- /dev/null
+++ b/vcl/inc/salsys.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_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:
+ SalSystem() {}
+ 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 whether multiple screens are part of one bigger display
+
+ @returns true if screens form one big display
+ false if screens are distinct and windows cannot
+ be moved between, or span multiple screens
+ */
+ virtual bool IsUnifiedDisplay() { return true; }
+ /* 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 tools::Rectangle 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();
+
+#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 000000000..69545ac96
--- /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 000000000..cc5aa88c9
--- /dev/null
+++ b/vcl/inc/salusereventlist.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_SALUSEREVENTLIST_HXX
+#define INCLUDED_VCL_INC_SALUSEREVENTLIST_HXX
+
+#include <sal/config.h>
+#include <vcl/dllapi.h>
+#include <osl/mutex.hxx>
+#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 osl::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() {}
+
+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
+{
+ osl::MutexGuard aGuard( m_aUserEventsMutex );
+ return !(m_aUserEvents.empty() && m_aProcessingUserEvents.empty());
+}
+
+inline void SalUserEventList::PostEvent( SalFrame* pFrame, void* pData, SalEvent nEvent )
+{
+ osl::MutexGuard 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 000000000..8a6fe144c
--- /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( long nNewDX, long nNewDY ) = 0;
+
+ // Set new size using a buffer at the given address
+ virtual bool SetSizeUsingBuffer( long nNewDX, 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 000000000..d0ae4ad38
--- /dev/null
+++ b/vcl/inc/salvtables.hxx
@@ -0,0 +1,1105 @@
+/* -*- 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/weld.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/syswin.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/ctrl.hxx>
+#include <vcl/edit.hxx>
+#include <vcl/spinfld.hxx>
+#include <vcl/fixed.hxx>
+#include <vcl/lstbox.hxx>
+#include <vcl/menubtn.hxx>
+#include <vcl/toolkit/combobox.hxx>
+#include <vcl/tabctrl.hxx>
+#include <vcl/layout.hxx>
+#include "messagedialog.hxx"
+
+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);
+
+ virtual std::unique_ptr<weld::MessageDialog>
+ weld_message_dialog(const OString& id, bool bTakeOwnership = true) override;
+
+ virtual std::unique_ptr<weld::Dialog> weld_dialog(const OString& id,
+ bool bTakeOwnership = true) override;
+
+ virtual std::unique_ptr<weld::Assistant> weld_assistant(const OString& id,
+ bool bTakeOwnership = true) override;
+
+ virtual std::unique_ptr<weld::Window> create_screenshot_window() override;
+
+ virtual std::unique_ptr<weld::Window> weld_window(const OString& id,
+ bool bTakeOwnership = true) override;
+
+ virtual std::unique_ptr<weld::Widget> weld_widget(const OString& id,
+ bool bTakeOwnership = false) override;
+
+ virtual std::unique_ptr<weld::Container> weld_container(const OString& id,
+ bool bTakeOwnership = false) override;
+
+ virtual std::unique_ptr<weld::Box> weld_box(const OString& id,
+ bool bTakeOwnership = false) override;
+
+ virtual std::unique_ptr<weld::Paned> weld_paned(const OString& id,
+ bool bTakeOwnership = false) override;
+
+ virtual std::unique_ptr<weld::Frame> weld_frame(const OString& id,
+ bool bTakeOwnership = false) override;
+
+ virtual std::unique_ptr<weld::ScrolledWindow>
+ weld_scrolled_window(const OString& id, bool bTakeOwnership = false) override;
+
+ virtual std::unique_ptr<weld::Notebook> weld_notebook(const OString& id,
+ bool bTakeOwnership = false) override;
+
+ virtual std::unique_ptr<weld::Button> weld_button(const OString& id,
+ bool bTakeOwnership = false) override;
+
+ virtual std::unique_ptr<weld::MenuButton>
+ weld_menu_button(const OString& id, bool bTakeOwnership = false) override;
+
+ virtual std::unique_ptr<weld::LinkButton>
+ weld_link_button(const OString& id, bool bTakeOwnership = false) override;
+
+ virtual std::unique_ptr<weld::ToggleButton>
+ weld_toggle_button(const OString& id, bool bTakeOwnership = false) override;
+
+ virtual std::unique_ptr<weld::RadioButton>
+ weld_radio_button(const OString& id, bool bTakeOwnership = false) override;
+
+ virtual std::unique_ptr<weld::CheckButton>
+ weld_check_button(const OString& id, bool bTakeOwnership = false) override;
+
+ virtual std::unique_ptr<weld::Scale> weld_scale(const OString& id,
+ bool bTakeOwnership = false) override;
+
+ virtual std::unique_ptr<weld::ProgressBar>
+ weld_progress_bar(const OString& id, bool bTakeOwnership = false) override;
+
+ virtual std::unique_ptr<weld::Spinner> weld_spinner(const OString& id,
+ bool bTakeOwnership = false) override;
+
+ virtual std::unique_ptr<weld::Image> weld_image(const OString& id,
+ bool bTakeOwnership = false) override;
+
+ virtual std::unique_ptr<weld::Calendar> weld_calendar(const OString& id,
+ bool bTakeOwnership = false) override;
+
+ virtual std::unique_ptr<weld::Entry> weld_entry(const OString& id,
+ bool bTakeOwnership = false) override;
+
+ virtual std::unique_ptr<weld::SpinButton>
+ weld_spin_button(const OString& id, bool bTakeOwnership = false) override;
+
+ virtual std::unique_ptr<weld::MetricSpinButton>
+ weld_metric_spin_button(const OString& id, FieldUnit eUnit,
+ bool bTakeOwnership = false) override;
+
+ virtual std::unique_ptr<weld::FormattedSpinButton>
+ weld_formatted_spin_button(const OString& id, bool bTakeOwnership = false) override;
+
+ virtual std::unique_ptr<weld::TimeSpinButton>
+ weld_time_spin_button(const OString& id, TimeFieldFormat eFormat,
+ bool bTakeOwnership = false) override;
+
+ virtual std::unique_ptr<weld::ComboBox> weld_combo_box(const OString& id,
+ bool bTakeOwnership = false) override;
+
+ virtual std::unique_ptr<weld::EntryTreeView>
+ weld_entry_tree_view(const OString& containerid, const OString& entryid,
+ const OString& treeviewid, bool bTakeOwnership = false) override;
+
+ virtual std::unique_ptr<weld::TreeView> weld_tree_view(const OString& id,
+ bool bTakeOwnership = false) override;
+
+ virtual std::unique_ptr<weld::IconView> weld_icon_view(const OString& id,
+ bool bTakeOwnership = false) override;
+
+ virtual std::unique_ptr<weld::Label> weld_label(const OString& id,
+ bool bTakeOwnership = false) override;
+
+ virtual std::unique_ptr<weld::TextView> weld_text_view(const OString& id,
+ bool bTakeOwnership = false) override;
+
+ virtual std::unique_ptr<weld::Expander> weld_expander(const OString& id,
+ bool bTakeOwnership = false) override;
+
+ virtual std::unique_ptr<weld::DrawingArea>
+ weld_drawing_area(const OString& id, const a11yref& rA11yImpl = nullptr,
+ FactoryFunction pUITestFactoryFunction = nullptr, void* pUserData = nullptr,
+ bool bTakeOwnership = false) override;
+
+ virtual std::unique_ptr<weld::Menu> weld_menu(const OString& id,
+ bool bTakeOwnership = true) override;
+
+ virtual std::unique_ptr<weld::Toolbar> weld_toolbar(const OString& id,
+ bool bTakeOwnership = false) override;
+
+ virtual std::unique_ptr<weld::SizeGroup> create_size_group() override;
+
+ OString get_current_page_help_id() const;
+
+ virtual ~SalInstanceBuilder() override;
+};
+
+class SalInstanceMenu : public weld::Menu
+{
+private:
+ VclPtr<PopupMenu> m_xMenu;
+
+ bool m_bTakeOwnership;
+ sal_uInt16 m_nLastId;
+
+ DECL_LINK(SelectMenuHdl, ::Menu*, bool);
+
+public:
+ SalInstanceMenu(PopupMenu* pMenu, bool bTakeOwnership);
+ virtual OString popup_at_rect(weld::Widget* pParent, const tools::Rectangle& rRect) override;
+ virtual void set_sensitive(const OString& rIdent, bool bSensitive) override;
+ virtual void set_active(const OString& rIdent, bool bActive) override;
+ virtual bool get_active(const OString& rIdent) const override;
+ virtual void set_label(const OString& rIdent, const OUString& rLabel) override;
+ virtual OUString get_label(const OString& rIdent) const override;
+ virtual void set_visible(const OString& rIdent, bool bShow) override;
+ virtual void clear() override;
+ virtual void insert(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 remove(const OString& rId) override;
+ virtual int n_children() const override;
+ PopupMenu* getMenu() const;
+ virtual ~SalInstanceMenu() override;
+};
+
+class SalInstanceWidget : public virtual weld::Widget
+{
+protected:
+ VclPtr<vcl::Window> m_xWidget;
+ SalInstanceBuilder* m_pBuilder;
+
+private:
+ DECL_LINK(EventListener, VclWindowEvent&, void);
+ DECL_LINK(KeyEventListener, VclWindowEvent&, bool);
+ DECL_LINK(MouseEventListener, VclSimpleEvent&, void);
+ DECL_LINK(MnemonicActivateHdl, vcl::Window&, bool);
+
+ const bool m_bTakeOwnership;
+ bool m_bEventListener;
+ bool m_bKeyEventListener;
+ bool m_bMouseEventListener;
+ int m_nBlockNotify;
+
+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();
+
+ virtual void HandleEventListener(VclWindowEvent& rEvent);
+ virtual bool HandleKeyEventListener(VclWindowEvent& rEvent);
+ virtual void HandleMouseEventListener(VclSimpleEvent& rEvent);
+
+ void set_background(const Color& rColor);
+
+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 void set_has_default(bool has_default) override;
+
+ virtual bool get_has_default() 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 OString get_buildable_name() const override;
+
+ virtual void set_help_id(const OString& rId) override;
+
+ virtual OString 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_secondary(bool bSecondary) override;
+
+ virtual void set_margin_top(int nMargin) override;
+
+ virtual void set_margin_bottom(int nMargin) override;
+
+ virtual void set_margin_left(int nMargin) override;
+
+ virtual void set_margin_right(int nMargin) override;
+
+ virtual int get_margin_top() const override;
+
+ virtual int get_margin_bottom() const override;
+
+ virtual int get_margin_left() const override;
+
+ virtual int get_margin_right() const override;
+
+ virtual void set_accessible_name(const OUString& rName) 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_accessible_relation_label_for(weld::Widget* pLabeled) override;
+
+ virtual void set_tooltip_text(const OUString& rTip) override;
+
+ virtual OUString get_tooltip_text() const 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 bool get_extents_relative_to(Widget& rRelative, int& x, int& y, int& width,
+ int& height) 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 std::unique_ptr<weld::Container> weld_parent() const override;
+
+ virtual ~SalInstanceWidget() override;
+
+ vcl::Window* getWidget();
+
+ void disable_notify_events();
+
+ bool notify_events_disabled();
+
+ void enable_notify_events();
+
+ virtual void help_hierarchy_foreach(const std::function<bool(const OString&)>& 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 void
+ connect_get_property_tree(const Link<boost::property_tree::ptree&, void>& rLink) override;
+
+ virtual void set_stack_background() override;
+
+ virtual void set_toolbar_background() override;
+
+ virtual void set_highlight_background() override;
+
+ virtual void draw(VirtualDevice& rOutput) 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_message_type(weld::EntryMessageType eType) override;
+
+ virtual void set_font(const vcl::Font& rFont) override;
+};
+
+class SalInstanceContainer : public SalInstanceWidget, public virtual weld::Container
+{
+protected:
+ VclPtr<vcl::Window> m_xContainer;
+
+private:
+ void implResetDefault(const vcl::Window* _pWindow);
+
+public:
+ SalInstanceContainer(vcl::Window* pContainer, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership);
+ virtual void move(weld::Widget* pWidget, weld::Container* pNewParent) override;
+ virtual void recursively_unset_default_buttons() 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);
+
+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 void set_busy_cursor(bool bBusy) override;
+
+ 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 tools::Rectangle 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 set_window_state(const OString& rStr) override;
+
+ virtual OString get_window_state(WindowStateMask nMask) const override;
+
+ virtual SystemEnvData get_system_data() const override;
+
+ virtual void connect_toplevel_focus_changed(const Link<weld::Widget&, void>& rLink) override;
+
+ virtual void HandleEventListener(VclWindowEvent& rEvent) override;
+
+ virtual void draw(VirtualDevice& rOutput) override;
+
+ virtual weld::ScreenShotCollection collect_screenshot_data() override;
+
+ virtual ~SalInstanceWindow() override;
+};
+
+class SalInstanceDialog : public SalInstanceWindow, public virtual weld::Dialog
+{
+private:
+ VclPtr<::Dialog> m_xDialog;
+
+ // 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
+ 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 OString& rHelpId = OString()) 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 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_message_type(weld::EntryMessageType eType) override;
+
+ virtual void set_font(const vcl::Font& rFont) 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 ~SalInstanceEntry() override;
+};
+
+class SalInstanceSpinButton final : public SalInstanceEntry, public virtual weld::SpinButton
+{
+private:
+ VclPtr<FormattedField> m_xButton;
+
+ DECL_LINK(UpDownHdl, SpinField&, void);
+ DECL_LINK(LoseFocusHdl, Control&, void);
+ DECL_LINK(OutputHdl, Edit&, bool);
+ DECL_LINK(InputHdl, sal_Int64*, TriState);
+ DECL_LINK(ActivateHdl, Edit&, bool);
+
+ double toField(int nValue) const;
+
+ int fromField(double fValue) const;
+
+public:
+ SalInstanceSpinButton(FormattedField* pButton, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership);
+
+ virtual int get_value() const override;
+
+ virtual void set_value(int value) override;
+
+ virtual void set_range(int min, int max) override;
+
+ virtual void get_range(int& min, int& 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;
+
+ //so with hh::mm::ss, incrementing mm will not reset ss
+ void DisableRemainderFactor();
+
+ //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 SalInstanceContainer, 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)
+ : SalInstanceContainer(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
+ {
+ 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)
+ {
+ if (m_xMenuButton->GetParent() != pEvent->GetWindow())
+ m_xMenuButton->SetParent(pEvent->GetWindow());
+ int nButtonWidth = get_menu_button_width();
+ m_xMenuButton->SetSizePixel(Size(nButtonWidth, rRect.GetHeight()));
+ m_xMenuButton->SetPosPixel(Point(rRect.GetWidth() - nButtonWidth, rRect.getY()));
+ }
+ }
+
+ 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 OString& 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 = OUString::fromUtf8(rIdent);
+ }
+
+ int get_menu_button_width() const override
+ {
+ const int nButtonWidth = 20;
+ return nButtonWidth;
+ }
+
+ void CallHandleEventListener(VclWindowEvent& rEvent)
+ {
+ if (rEvent.GetId() == VclEventId::DropdownPreOpen
+ || rEvent.GetId() == VclEventId::DropdownClose)
+ {
+ signal_popup_toggled();
+ return;
+ }
+ SalInstanceContainer::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_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_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 ~SalInstanceComboBoxWithEdit() override;
+};
+
+class SalInstanceButton : public SalInstanceContainer, 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 void set_label_line_wrap(bool wrap) override;
+
+ virtual OUString get_label() const override;
+
+ virtual ~SalInstanceButton() override;
+};
+
+class SalInstanceNotebook : public SalInstanceContainer, public virtual weld::Notebook
+{
+private:
+ VclPtr<TabControl> m_xNotebook;
+ mutable std::vector<std::shared_ptr<SalInstanceContainer>> m_aPages;
+ std::map<OString, 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 OString get_page_ident(int nPage) const override;
+
+ virtual OString get_current_page_ident() const override;
+
+ virtual weld::Container* get_page(const OString& rIdent) const override;
+
+ virtual void set_current_page(int nPage) override;
+
+ virtual void set_current_page(const OString& rIdent) override;
+
+ virtual void remove_page(const OString& rIdent) override;
+
+ virtual void insert_page(const OString& rIdent, const OUString& rLabel, int nPos) override;
+
+ virtual int get_n_pages() const override;
+
+ virtual OUString get_tab_label_text(const OString& rIdent) const override;
+
+ virtual void set_tab_label_text(const OString& rIdent, const OUString& rText) override;
+
+ virtual ~SalInstanceNotebook() override;
+};
+
+class SalInstanceMessageDialog : public SalInstanceDialog, public virtual weld::MessageDialog
+{
+private:
+ 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 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 ~SalInstanceCheckButton() 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 000000000..fc8aeb497
--- /dev/null
+++ b/vcl/inc/salwtype.hxx
@@ -0,0 +1,274 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <i18nlangtag/lang.h>
+#include <rtl/ref.hxx>
+#include <rtl/ustring.hxx>
+#include <tools/solar.h>
+#include <vcl/GestureEvent.hxx>
+
+class LogicalFontInstance;
+class SalGraphics;
+class SalFrame;
+class SalObject;
+namespace vcl { class Window; }
+enum class InputContextFlags;
+enum class WindowStateMask;
+enum class WindowStateState;
+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,
+ SurroundingTextSelectionChange,
+ StartReconversion,
+ QueryCharPosition,
+ Swipe,
+ LongPress,
+ ExternalGesture,
+ Gesture,
+};
+
+// 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
+{
+ sal_uInt64 mnTime; // Time in ms, when event is created
+ long mnX; // X-Position (Pixel, TopLeft-Output)
+ long mnY; // Y-Position (Pixel, TopLeft-Output)
+ sal_uInt16 mnButton; // 0-MouseMove/MouseLeave, MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE
+ sal_uInt16 mnCode; // SV-Modifiercode (KEY_SHIFT|KEY_MOD1|KEY_MOD2|MOUSE_LEFT|MOUSE_MIDDLE|MOUSE_RIGHT)
+};
+
+// 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
+{
+ long mnBoundX; // BoundRect - X
+ long mnBoundY; // BoundRect - Y
+ long mnBoundWidth; // BoundRect - Width
+ long mnBoundHeight; // BoundRect - Height
+ bool mbImmediateUpdate; // set to true to force an immediate update
+
+ SalPaintEvent( long x, long y, long w, 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
+{
+ sal_uInt64 mnTime; // Time in ms, when event is created
+ long mnX; // X-Position (Pixel, TopLeft-Output)
+ long mnY; // Y-Position (Pixel, TopLeft-Output)
+ long mnDelta; // Number of rotations
+ long mnNotchDelta; // Number of fixed rotations
+ double mnScrollLines; // Actual number of lines to scroll
+ sal_uInt16 mnCode; // SV-Modifiercode (KEY_SHIFT|KEY_MOD1|KEY_MOD2|MOUSE_LEFT|MOUSE_MIDDLE|MOUSE_RIGHT)
+ bool mbHorz; // Horizontal
+ bool mbDeltaIsPixel; // delta value is a pixel value (on touch devices)
+
+ SalWheelMouseEvent()
+ : mnTime( 0 ), mnX( 0 ), mnY( 0 ), mnDelta( 0 ), mnNotchDelta( 0 ), mnScrollLines( 0 ), mnCode( 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
+{
+ long mnX; // Cursor-X-Position to upper left corner of frame
+ long mnY; // Cursor-Y-Position to upper left corner of frame
+ long mnWidth; // Cursor-Width in Pixel
+ long mnHeight; // Cursor-Height in Pixel
+ 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
+{
+ bool mbValid; // The data is valid or not.
+ sal_uLong mnCharPos; // The index of character in a composition.
+ bool mbVertical; // The text is vertical or not.
+ long mnCursorBoundX; // The cursor bounds corresponding to the character specified by mnCharPos - X
+ long mnCursorBoundY; // The cursor bounds corresponding to the character specified by mnCharPos - Y
+ long mnCursorBoundWidth; // The cursor bounds corresponding to the character specified by mnCharPos - Width
+ long mnCursorBoundHeight; // The cursor bounds corresponding to the character specified by mnCharPos - Height
+};
+
+typedef bool (*SALFRAMEPROC)( vcl::Window* pInst, SalEvent nEvent, const void* pEvent );
+
+enum class SalObjEvent {
+ GetFocus = 1,
+ LoseFocus = 2,
+ ToTop = 3
+};
+
+struct SalFrameState
+{
+ WindowStateMask mnMask;
+ long mnX;
+ long mnY;
+ long mnWidth;
+ long mnHeight;
+ long mnMaximizedX;
+ long mnMaximizedY;
+ long mnMaximizedWidth;
+ long mnMaximizedHeight;
+ WindowStateState mnState;
+};
+
+struct SalInputContext
+{
+ rtl::Reference<LogicalFontInstance> mpFont;
+ InputContextFlags mnOptions;
+};
+
+struct SalSwipeEvent
+{
+ double mnVelocityX;
+ double mnVelocityY;
+ long mnX;
+ long mnY;
+};
+
+struct SalLongPressEvent
+{
+ long mnX;
+ long mnY;
+};
+
+struct SalGestureEvent
+{
+ GestureEventType meEventType;
+ PanningOrientation meOrientation;
+ double mfOffset;
+ long mnX;
+ long mnY;
+};
+
+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 000000000..a5b892b02
--- /dev/null
+++ b/vcl/inc/scanlinewriter.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_SCANLINEWRITER_HXX
+#define INCLUDED_VCL_INC_SCANLINEWRITER_HXX
+
+#include <vcl/bitmap.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;
+ 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
+ long nScanlineIndex = mnX / mnColorsPerByte;
+
+ // calculate the number of shifts to get the color information to the right place
+ 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 000000000..38f27a665
--- /dev/null
+++ b/vcl/inc/schedulerimpl.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_SCHEDULERIMPL_HXX
+#define INCLUDED_VCL_INC_SCHEDULERIMPL_HXX
+
+#include "salwtype.hxx"
+#include <osl/mutex.hxx>
+#include <vcl/scheduler.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
+ bool mbInScheduler; ///< Task currently processed?
+ sal_uInt64 mnUpdateTime; ///< Last Update Time
+ TaskPriority mePriority; ///< Task priority
+
+ const char *GetDebugName() const;
+};
+
+class SchedulerMutex final
+{
+ sal_uInt32 mnLockDepth;
+ osl::Mutex maMutex;
+
+public:
+ SchedulerMutex() : mnLockDepth( 0 ) {}
+
+ void acquire( sal_uInt32 nLockCount = 1 );
+ sal_uInt32 release( bool bUnlockAll = false );
+ sal_uInt32 lockDepth() const { return mnLockDepth; }
+};
+
+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 000000000..b663deeed
--- /dev/null
+++ b/vcl/inc/scrptrun.h
@@ -0,0 +1,158 @@
+/*
+ *******************************************************************************
+ *
+ * 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 <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 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)
+{
+ 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.resize(128);
+}
+
+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 000000000..c55fbd985
--- /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/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;
+ long mnActDeltaX;
+ long mnActDeltaY;
+ void ImplCreateImageList();
+ void ImplSetRegion(const Bitmap& rRegionBmp);
+ using Window::ImplGetMousePointer;
+ PointerStyle ImplGetMousePointer( long nDistX, long nDistY );
+ 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 000000000..aca60a456
--- /dev/null
+++ b/vcl/inc/sft.hxx
@@ -0,0 +1,752 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <vcl/dllapi.h>
+#include <vcl/fontcapabilities.hxx>
+#include <i18nlangtag/lang.h>
+
+#include <array>
+#include <memory>
+#include <vector>
+#include <cstdint>
+
+namespace vcl
+{
+
+/*@{*/
+ typedef sal_Int32 F16Dot16; /**< fixed: 16.16 */
+/*@}*/
+
+/** Return value of OpenTTFont() and CreateT3FromTTGlyphs() */
+ 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 */
+ 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() */
+ typedef struct {
+ sal_uInt16 platformID; /**< Platform ID */
+ sal_uInt16 encodingID; /**< Platform-specific encoding ID */
+ LanguageType languageID; /**< Language ID */
+ sal_uInt16 nameID; /**< Name ID */
+ sal_uInt16 slen; /**< String length in bytes */
+ sal_uInt8 *sptr; /**< Pointer to string data (not zero-terminated!) */
+ } NameRecord;
+
+/** Return value of GetTTGlobalFontInfo() */
+
+ typedef struct {
+ char *family; /**< family name */
+ sal_Unicode *ufamily; /**< family name UCS2 */
+ char *subfamily; /**< subfamily name */
+ sal_Unicode *usubfamily; /**< subfamily name UCS2 */
+ char *psname; /**< PostScript name */
+ sal_uInt16 macStyle; /**< macstyle bits from 'HEAD' table */
+ int weight; /**< value of WeightClass or 0 if can't be determined */
+ int width; /**< value of WidthClass or 0 if can't be determined */
+ int pitch; /**< 0: proportional font, otherwise: monospaced */
+ int italicAngle; /**< in counter-clockwise degrees * 65536 */
+ int xMin; /**< global bounding box: xMin */
+ int yMin; /**< global bounding box: yMin */
+ int xMax; /**< global bounding box: xMax */
+ int yMax; /**< global bounding box: yMax */
+ int ascender; /**< typographic ascent. */
+ int descender; /**< typographic descent. */
+ int linegap; /**< typographic line gap.\ Negative values are treated as
+ zero in Win 3.1, System 6 and System 7. */
+ int typoAscender; /**< OS/2 portable typographic ascender */
+ int typoDescender; /**< OS/2 portable typographic descender */
+ int typoLineGap; /**< OS/2 portable typographic line gap */
+ int winAscent; /**< ascender metric for Windows */
+ int winDescent; /**< descender metric for Windows */
+ bool symbolEncoded; /**< true: MS symbol encoded */
+ sal_uInt8 panose[10]; /**< PANOSE classification number */
+ sal_uInt32 typeFlags; /**< type flags (copyright bits) */
+ sal_uInt16 fsSelection; /**< 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;
+
+ struct TrueTypeFont;
+
+/*
+ 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_gsub = 0x47535542;
+constexpr sal_uInt32 T_CFF = 0x43464620;
+
+
+/**
+ * @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 - array of TrueTypeFonts
+ * @return value of SFErrCodes enum
+ * @ingroup sft
+ */
+ SFErrCodes VCL_DLLPUBLIC OpenTTFontBuffer(const void* pBuffer, sal_uInt32 nLen, sal_uInt32 facenum, TrueTypeFont** ttf);
+#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 - array of TrueTypeFonts
+ * @return value of SFErrCodes enum
+ * @ingroup sft
+ */
+ SFErrCodes VCL_DLLPUBLIC OpenTTFontFile(const char *fname, sal_uInt32 facenum, TrueTypeFont** ttf);
+#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(TrueTypeFont *ttf, sal_uInt32 glyphID, 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
+ *
+ */
+ GlyphData *GetTTRawGlyphData(TrueTypeFont *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(TrueTypeFont *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 pointer to the array of NameRecord structs
+ *
+ * @return number of NameRecord structs
+ * @ingroup sft
+ */
+
+ int GetTTNameRecords(TrueTypeFont const *ttf, NameRecord **nr);
+
+/**
+ * Deallocates previously allocated array of NameRecords.
+ *
+ * @param nr array of NameRecord structs
+ * @param n number of elements in the array
+ *
+ * @ingroup sft
+ */
+ void DisposeNameRecords(NameRecord* nr, int n);
+
+/**
+ * Generates a new PostScript Type 3 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 outf the resulting font is written to this stream
+ * @param fname font name for the new font. If it is NULL the PostScript name of the
+ * original font will be used
+ * @param glyphArray pointer to an array of glyphs that are to be extracted from ttf
+ * @param encoding array of encoding values. encoding[i] specifies the position of the glyph
+ * glyphArray[i] in the encoding vector of the resulting Type3 font
+ * @param nGlyphs number of glyph IDs in glyphArray and encoding values in encoding
+ * @param wmode writing mode for the output file: 0 - horizontal, 1 - vertical
+ * @return return the value of SFErrCodes enum
+ * @see SFErrCodes
+ * @ingroup sft
+ *
+ */
+ SFErrCodes CreateT3FromTTGlyphs(TrueTypeFont *ttf, FILE *outf, const char *fname, sal_uInt16 const *glyphArray, sal_uInt8 *encoding, int nGlyphs, int wmode);
+
+/**
+ * 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(TrueTypeFont *ttf,
+ const char *fname,
+ sal_uInt16 const *glyphArray,
+ sal_uInt8 const *encoding,
+ int nGlyphs);
+
+/**
+ * Generates a new PostScript Type42 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 outf output stream for a resulting font
+ * @param psname PostScript name of the resulting font
+ * @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
+ * @return SFErrCodes::Ok - no errors
+ * SFErrCodes::GlyphNum - too many glyphs (> 255)
+ * SFErrCodes::TtFormat - corrupted TrueType fonts
+ *
+ * @see SFErrCodes
+ * @ingroup sft
+ *
+ */
+ SFErrCodes CreateT42FromTTGlyphs(TrueTypeFont *ttf,
+ FILE *outf,
+ const char *psname,
+ sal_uInt16 const *glyphArray,
+ sal_uInt8 *encoding,
+ int nGlyphs);
+
+/**
+ * Queries glyph metrics. Allocates an array of advance width/height values and returns it.
+ *
+ * @param ttf pointer to the TrueTypeFont structure
+ * @param glyphArray pointer to an array of glyphs that are to be extracted from ttf
+ * @param nGlyphs number of glyph IDs in glyphArray and encoding values in encoding
+ * @param vertical writing mode: false - horizontal, true - vertical
+ * @ingroup sft
+ *
+ */
+ VCL_DLLPUBLIC std::unique_ptr<sal_uInt16[]> GetTTSimpleGlyphMetrics(TrueTypeFont const *ttf, const sal_uInt16 *glyphArray, int nGlyphs, bool vertical);
+
+#if defined(_WIN32) || defined(MACOSX) || defined(IOS)
+/**
+ * Maps a Unicode (UCS-2) character to a glyph ID and returns it. Missing glyph has
+ * a glyphID of 0 so this function can be used to test if a character is encoded in the font.
+ *
+ * @param ttf pointer to the TrueTypeFont structure
+ * @param ch Unicode (UCS-2) character
+ * @return glyph ID, if the character is missing in the font, the return value is 0.
+ * @ingroup sft
+ */
+ VCL_DLLPUBLIC sal_uInt16 MapChar(TrueTypeFont const *ttf, sal_uInt16 ch);
+#endif
+
+/**
+ * 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(TrueTypeFont *ttf, TTGlobalFontInfo *info);
+
+/**
+ * Returns fonts metrics.
+ * @see TTGlobalFontInfo
+ *
+ * @param hhea hhea table data
+ * @param os2 OS/2 table data
+ * @param info pointer to a TTGlobalFontInfo structure
+ * @ingroup sft
+ *
+ */
+ void GetTTFontMetrics(const uint8_t *pHhea, size_t nHhea,
+ const uint8_t *pOs2, size_t nOs2,
+ TTGlobalFontInfo *info);
+
+/**
+ * returns the number of glyphs in a font
+ */
+ VCL_DLLPUBLIC int GetTTGlyphCount( TrueTypeFont const * ttf );
+
+/**
+ * provide access to the raw data of a SFNT-container's subtable
+ */
+ bool GetSfntTable( TrueTypeFont const * ttf, int nSubtableIndex,
+ const sal_uInt8** ppRawBytes, int* pRawLength );
+
+/*- 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_gsub = 15; /* 'GSUB' */
+constexpr int O_CFF = 16; /* 'CFF' */
+constexpr int NUM_TAGS = 17;
+
+ struct TrueTypeFont {
+ char *fname;
+ sal_Int32 fsize;
+ sal_uInt8 *ptr;
+
+ char *psname;
+ char *family;
+ sal_Unicode *ufamily;
+ char *subfamily;
+ sal_Unicode *usubfamily;
+
+ sal_uInt32 ntables;
+ sal_uInt32 *goffsets;
+ sal_uInt32 nglyphs;
+ sal_uInt32 unitsPerEm;
+ sal_uInt32 numberOfHMetrics;
+ sal_uInt32 numOfLongVerMetrics; /* if this number is not 0, font has vertical metrics information */
+ const sal_uInt8* cmap;
+ int cmapType;
+ sal_uInt32 (*mapper)(const sal_uInt8 *, sal_uInt32, sal_uInt32); /* character to glyphID translation function */
+ std::array<const sal_uInt8 *, NUM_TAGS> tables; /* array of pointers to raw subtables in SFNT file */
+ std::array<sal_uInt32, NUM_TAGS> tlens; /* array of table lengths */
+ };
+
+
+} // 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 000000000..ca227e4e8
--- /dev/null
+++ b/vcl/inc/skia/gdiimpl.hxx
@@ -0,0 +1,332 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <SkSurface.h>
+#include <SkRegion.h>
+
+#include <prewin.h>
+#include <tools/sk_app/WindowContext.h>
+#include <postwin.h>
+
+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 bool setClipRegion(const vcl::Region&) override;
+
+ //
+ // get the depth of the device
+ virtual sal_uInt16 GetBitCount() const override;
+
+ // get the width of the device
+ virtual 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(long nX, long nY) override;
+ virtual void drawPixel(long nX, long nY, Color nColor) override;
+
+ virtual void drawLine(long nX1, long nY1, long nX2, long nY2) override;
+
+ virtual void drawRect(long nX, long nY, long nWidth, long nHeight) override;
+
+ virtual void drawPolyLine(sal_uInt32 nPoints, const SalPoint* pPtAry) override;
+
+ virtual void drawPolygon(sal_uInt32 nPoints, const SalPoint* pPtAry) override;
+
+ virtual void drawPolyPolygon(sal_uInt32 nPoly, const sal_uInt32* pPoints,
+ PCONSTSALPOINT* pPtAry) override;
+
+ virtual bool 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 SalPoint* pPtAry,
+ const PolyFlags* pFlgAry) override;
+
+ virtual bool drawPolygonBezier(sal_uInt32 nPoints, const SalPoint* pPtAry,
+ const PolyFlags* pFlgAry) override;
+
+ virtual bool drawPolyPolygonBezier(sal_uInt32 nPoly, const sal_uInt32* pPoints,
+ const SalPoint* const* pPtAry,
+ const PolyFlags* const* pFlgAry) override;
+
+ // CopyArea --> No RasterOp, but ClipRegion
+ virtual void copyArea(long nDestX, long nDestY, long nSrcX, long nSrcY, long nSrcWidth,
+ 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(long nX, long nY, long nWidth,
+ long nHeight) override;
+
+ virtual Color getPixel(long nX, long nY) override;
+
+ // invert --> ClipRegion (only Windows or VirDevs)
+ virtual void invert(long nX, long nY, long nWidth, long nHeight, SalInvert nFlags) override;
+
+ virtual void invert(sal_uInt32 nPoints, const SalPoint* pPtAry, SalInvert nFlags) override;
+
+ virtual bool drawEPS(long nX, long nY, long nWidth, 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) 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(long nX, long nY, long nWidth, long nHeight,
+ sal_uInt8 nTransparency) override;
+
+ virtual bool drawGradient(const tools::PolyPolygon& rPolygon,
+ const Gradient& rGradient) override;
+
+ virtual bool supportsOperation(OutDevSupportType eType) const override;
+
+#ifdef DBG_UTIL
+ void dump(const char* file) const;
+#endif
+
+ // Default blend mode for SkPaint is SkBlendMode::kSrcOver
+ void drawImage(const SalTwoRect& rPosAry, const sk_sp<SkImage>& aImage,
+ SkBlendMode eBlendMode = SkBlendMode::kSrcOver);
+
+ void drawShader(const SalTwoRect& rPosAry, const sk_sp<SkShader>& shader);
+
+ enum class GlyphOrientation
+ {
+ Apply,
+ Ignore
+ };
+ void drawGenericLayout(const GenericSalLayout& layout, Color textColor, const SkFont& font,
+ GlyphOrientation glyphOrientation);
+
+protected:
+ // To be called before any drawing.
+ void preDraw();
+ // To be called after any drawing.
+ void postDraw();
+ // The canvas to draw to. Will be diverted to a temporary for Xor mode.
+ SkCanvas* getDrawCanvas() { return mXorMode ? getXorCanvas() : 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 { return false; }
+ void createWindowSurface(bool forceRaster = false);
+ virtual void createWindowContext(bool forceRaster = false) = 0;
+ void createOffscreenSurface();
+
+ void privateDrawAlphaRect(long nX, long nY, long nWidth, long nHeight, double nTransparency,
+ bool blockAA = false);
+
+ void setProvider(SalGeometryProvider* provider) { mProvider = provider; }
+
+ bool isOffscreen() const { return mProvider == nullptr || mProvider->IsOffScreen(); }
+ bool isGPU() const { return mIsGPU; }
+
+ void invert(basegfx::B2DPolygon const& rPoly, SalInvert eFlags);
+
+ // Called by SkiaFlushIdle.
+ virtual void performFlush() = 0;
+ 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; }
+
+ SkCanvas* getXorCanvas();
+ void applyXor();
+ // NOTE: This must be called before the operation does any drawing.
+ void addXorRegion(const SkRect& rect)
+ {
+ if (mXorMode)
+ {
+ // Make slightly larger, just in case (rounding, antialiasing,...).
+ SkIRect addedRect = rect.makeOutset(2, 2).round();
+ // Two xor operations should cancel each other out. We batch xor operations,
+ // but if they can overlap, apply xor now, since applyXor() does the operation
+ // just once.
+ if (mXorRegion.intersects(addedRect))
+ applyXor();
+ mXorRegion.op(addedRect, SkRegion::kUnion_Op);
+ }
+ }
+ static void setCanvasClipRegion(SkCanvas* canvas, const vcl::Region& region);
+ sk_sp<SkImage> mergeCacheBitmaps(const SkiaSalBitmap& bitmap, const SkiaSalBitmap* alphaBitmap,
+ const Size targetSize);
+
+ // 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(long x) { return x + 0.495; }
+ static constexpr SkScalar toSkY(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);
+
+ template <typename charT, typename traits>
+ friend inline std::basic_ostream<charT, traits>&
+ operator<<(std::basic_ostream<charT, traits>& stream, const SkiaSalGraphicsImpl* graphics)
+ { // O - offscreen, G - GPU-based, R - raster
+ return stream << static_cast<const void*>(graphics) << " "
+ << Size(graphics->GetWidth(), graphics->GetHeight())
+ << (graphics->isGPU() ? "G" : "R") << (graphics->isOffscreen() ? "O" : "");
+ }
+
+ SalGraphics& mParent;
+ /// Pointer to the SalFrame or SalVirtualDevice
+ SalGeometryProvider* mProvider;
+ std::unique_ptr<sk_app::WindowContext> mWindowContext;
+ // The Skia surface that is target of all the rendering.
+ sk_sp<SkSurface> mSurface;
+ bool mIsGPU; // whether the surface is GPU-backed
+ // Keep reference to shared GrContext.
+ vcl::Region mClipRegion;
+ Color mLineColor;
+ Color mFillColor;
+ bool mXorMode;
+ SkBitmap mXorBitmap;
+ std::unique_ptr<SkCanvas> mXorCanvas;
+ SkRegion mXorRegion; // the area that needs updating for the xor operation
+ 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;
+};
+
+#endif
+
+/* 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 000000000..5079be088
--- /dev/null
+++ b/vcl/inc/skia/salbmp.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_SKIA_SALBMP_H
+#define INCLUDED_VCL_INC_SKIA_SALBMP_H
+
+#include <salbmp.hxx>
+
+#include <SkImage.h>
+
+#include <boost/shared_ptr.hpp>
+
+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, sal_uInt16 nBitCount,
+ 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, sal_uInt16 nNewBitCount) 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;
+
+ const BitmapPalette& Palette() const { return mPalette; }
+ // Returns the contents as SkImage (possibly GPU-backed).
+ const sk_sp<SkImage>& GetSkImage() const;
+
+ // Returns the contents as alpha SkImage (possibly GPU-backed)
+ const sk_sp<SkImage>& GetAlphaSkImage() const;
+
+#ifdef DBG_UTIL
+ void dump(const char* file) const;
+#endif
+
+private:
+ // Reset the cached images allocated in GetSkImage()/GetAlphaSkImage().
+ void ResetCachedData();
+ // Sets the data only as SkImage (will be converted as needed).
+ void ResetToSkImage(sk_sp<SkImage> image);
+ // Resets all data that does not match mSize.
+ void ResetCachedDataBySize();
+ // 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).
+ bool CreateBitmapData();
+ SkBitmap GetAsSkBitmap() const;
+#ifdef DBG_UTIL
+ void verify() const;
+#else
+ void verify() const {};
+#endif
+
+ template <typename charT, typename traits>
+ friend inline std::basic_ostream<charT, traits>&
+ operator<<(std::basic_ostream<charT, traits>& stream, const SkiaSalBitmap* bitmap)
+ {
+ // I/i - has SkImage (on GPU/CPU),
+ // A/a - has alpha SkImage (on GPU/CPU)
+ return stream << static_cast<const void*>(bitmap) << " " << bitmap->GetSize() << "/"
+ << (bitmap->mImage ? (bitmap->mImage->isTextureBacked() ? "I" : "i") : "")
+ << (bitmap->mAlphaImage ? (bitmap->mAlphaImage->isTextureBacked() ? "A" : "a")
+ : "");
+ }
+
+ 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
+ sk_sp<SkImage> mImage; // possibly GPU-backed
+ 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;
+ SkFilterQuality mScaleQuality = kHigh_SkFilterQuality; // quality for on-demand scaling
+#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 000000000..a43ff8f58
--- /dev/null
+++ b/vcl/inc/skia/utils.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_SKIA_UTILS_H
+#define INCLUDED_VCL_INC_SKIA_UTILS_H
+
+#include <vcl/skia/SkiaHelper.hxx>
+
+#include <tools/gen.hxx>
+#include <driverblocklist.hxx>
+
+#include <SkRegion.h>
+#include <tools/sk_app/VulkanWindowContext.h>
+
+namespace SkiaHelper
+{
+// Get the one shared GrContext instance.
+GrContext* getSharedGrContext();
+
+void disableRenderMethod(RenderMethod method);
+
+// Create SkSurface, GPU-backed if possible.
+VCL_DLLPUBLIC sk_sp<SkSurface> createSkSurface(int width, int height,
+ SkColorType type = kN32_SkColorType);
+
+inline sk_sp<SkSurface> createSkSurface(const Size& size, SkColorType type = kN32_SkColorType)
+{
+ return createSkSurface(size.Width(), size.Height(), type);
+}
+
+// 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);
+
+// 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> (*createVulkanWindowContext)(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);
+
+#ifdef DBG_UTIL
+void prefillSurface(sk_sp<SkSurface>& surface);
+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);
+#endif
+
+extern uint32_t vendorId;
+
+inline DriverBlocklist::DeviceVendor getVendor()
+{
+ return DriverBlocklist::GetVendorFromId(vendorId);
+}
+
+} // namespace
+
+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 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)
+{
+ return stream << *image;
+}
+
+#endif // INCLUDED_VCL_INC_SKIA_UTILS_H
+
+/* 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 000000000..564fcd7e9
--- /dev/null
+++ b/vcl/inc/skia/win/gdiimpl.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/.
+ */
+
+#ifndef INCLUDED_VCL_INC_SKIA_WIN_GDIIMPL_HXX
+#define INCLUDED_VCL_INC_SKIA_WIN_GDIIMPL_HXX
+
+#include <memory>
+#include <vcl/dllapi.h>
+
+#include <skia/gdiimpl.hxx>
+#include <win/salgdi.h>
+#include <win/wingdiimpl.hxx>
+#include <o3tl/lru_map.hxx>
+#include <ControlCacheKey.hxx>
+#include <svdata.hxx>
+
+#include <SkFont.h>
+
+class SkTypeface;
+class SkFontMgr;
+class ControlCacheKey;
+
+class SkiaCompatibleDC : public CompatibleDC
+{
+public:
+ SkiaCompatibleDC(SalGraphics& rGraphics, int x, int y, int width, int height);
+
+ virtual std::unique_ptr<Texture> getAsMaskTexture() const override;
+
+ sk_sp<SkImage> getAsImage() const;
+ sk_sp<SkImage> getAsMaskImage() const;
+ sk_sp<SkImage> getAsImageDiff(const SkiaCompatibleDC& white) const;
+
+ struct Texture;
+};
+
+struct SkiaCompatibleDC::Texture : public CompatibleDC::Texture
+{
+ sk_sp<SkImage> image;
+ virtual bool isValid() const { return image.get(); }
+ virtual int GetWidth() const { return image->width(); }
+ virtual int GetHeight() const { return image->height(); }
+};
+
+class WinSkiaSalGraphicsImpl : public SkiaSalGraphicsImpl, public WinSalGraphicsImplBase
+{
+private:
+ WinSalGraphics& mWinParent;
+
+public:
+ WinSkiaSalGraphicsImpl(WinSalGraphics& rGraphics, SalGeometryProvider* mpProvider);
+
+ virtual void DeInit() override;
+ virtual void freeResources() override;
+
+ 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;
+
+ static void prepareSkia();
+
+protected:
+ virtual void createWindowContext(bool forceRaster = false) override;
+ virtual void performFlush() override;
+ sk_sp<SkTypeface> createDirectWriteTypeface(const LOGFONTW& logFont);
+ SkFont::Edging getFontEdging();
+ IDWriteFactory* dwriteFactory;
+ IDWriteGdiInterop* dwriteGdiInterop;
+ sk_sp<SkFontMgr> dwriteFontMgr;
+ bool dwriteDone = false;
+ SkFont::Edging fontEdging;
+ bool fontEdgingDone = false;
+};
+
+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 000000000..d131d54bf
--- /dev/null
+++ b/vcl/inc/skia/x11/gdiimpl.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_GDIIMPL_HXX
+#define INCLUDED_VCL_INC_SKIA_X11_GDIIMPL_HXX
+
+#include <vcl/dllapi.h>
+
+#include <unx/salgdi.h>
+#include <unx/x11/x11gdiimpl.h>
+#include <skia/gdiimpl.hxx>
+#include <skia/utils.hxx>
+
+class VCL_PLUGIN_PUBLIC X11SkiaSalGraphicsImpl final : public SkiaSalGraphicsImpl,
+ public X11GraphicsImpl
+{
+private:
+ X11SalGraphics& mX11Parent;
+
+public:
+ X11SkiaSalGraphicsImpl(X11SalGraphics& rParent);
+ virtual ~X11SkiaSalGraphicsImpl() override;
+
+ virtual void Init() override;
+ virtual void DeInit() override;
+ virtual void freeResources() override;
+
+ static void prepareSkia();
+
+private:
+ virtual void createWindowContext(bool forceRaster = false) override;
+ virtual void performFlush() 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 000000000..8ff75175d
--- /dev/null
+++ b/vcl/inc/skia/x11/salvd.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/.
+ */
+
+#ifndef INCLUDED_VCL_INC_SKIA_X11_SALVD_H
+#define INCLUDED_VCL_INC_SKIA_X11_SALVD_H
+
+#include <salvd.hxx>
+
+class X11SkiaSalVirtualDevice : public SalVirtualDevice
+{
+ SalDisplay* mpDisplay;
+ std::unique_ptr<X11SalGraphics> mpGraphics;
+ bool mbGraphics; // is Graphics used
+ SalX11Screen mnXScreen;
+ int mnWidth;
+ int mnHeight;
+
+public:
+ X11SkiaSalVirtualDevice(SalGraphics const* pGraphics, long nDX, long nDY,
+ const SystemGraphicsData* pData,
+ std::unique_ptr<X11SalGraphics> pNewGraphics);
+ virtual ~X11SkiaSalVirtualDevice() override;
+
+ // SalGeometryProvider
+ virtual long GetWidth() const override { return mnWidth; }
+ virtual 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(long nNewDX, 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 000000000..d6eda9a04
--- /dev/null
+++ b/vcl/inc/skia/x11/textrender.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_SKIA_TEXTRENDER_HXX
+#define INCLUDED_VCL_INC_SKIA_TEXTRENDER_HXX
+
+#include <unx/freetypetextrender.hxx>
+
+#include <SkFontMgr.h>
+#include <SkFontMgr_fontconfig.h>
+
+class VCL_DLLPUBLIC SkiaTextRender final : public FreeTypeTextRenderImpl
+{
+public:
+ virtual void DrawTextLayout(const GenericSalLayout&, const SalGraphics&) override;
+ virtual void ClearDevFontCache() override;
+
+private:
+ 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 000000000..1f6bbb0dd
--- /dev/null
+++ b/vcl/inc/skia/zone.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/.
+ */
+
+#ifndef INCLUDED_VCL_INC_SKIA_ZONE_H
+#define INCLUDED_VCL_INC_SKIA_ZONE_H
+
+#include <comphelper/crashzone.hxx>
+
+#include <vcl/dllapi.h>
+
+// Used around calls to Skia code to detect crashes in drivers.
+class VCL_DLLPUBLIC SkiaZone : public CrashZone<SkiaZone>
+{
+public:
+ 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 000000000..6f57d269c
--- /dev/null
+++ b/vcl/inc/slider.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_SLIDER_HXX
+#define INCLUDED_VCL_SLIDER_HXX
+
+#include <vcl/dllapi.h>
+#include <vcl/ctrl.hxx>
+#include <vcl/scrbar.hxx>
+
+class Slider final : public Control
+{
+private:
+ tools::Rectangle maChannel1Rect;
+ tools::Rectangle maChannel2Rect;
+ tools::Rectangle maThumbRect;
+ long mnStartPos;
+ long mnMouseOff;
+ long mnThumbPixOffset;
+ long mnThumbPixRange;
+ long mnThumbPixPos;
+ long mnThumbSize;
+ long mnChannelPixRange;
+ long mnChannelPixTop;
+ long mnChannelPixBottom;
+ long mnMinRange;
+ long mnMaxRange;
+ long mnThumbPos;
+ long mnLineSize;
+ 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 );
+ long ImplCalcThumbPos( long nPixPos );
+ long ImplCalcThumbPosPix( long nPos );
+ void ImplCalc( bool bUpdate = true );
+ void ImplDraw(vcl::RenderContext& rRenderContext);
+ bool ImplIsPageUp( const Point& rPos );
+ bool ImplIsPageDown( const Point& rPos );
+ long ImplSlide( long nNewPos );
+ long ImplDoAction( );
+ void ImplDoMouseAction( const Point& rPos, bool bCallAction );
+ void ImplDoSlide( 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(long nNewRange);
+ long GetRangeMin() const { return mnMinRange; }
+ void SetRangeMax(long nNewRange);
+ long GetRangeMax() const { return mnMaxRange; }
+ void SetRange( const Range& rRange );
+ void SetThumbPos( long nThumbPos );
+ long GetThumbPos() const { return mnThumbPos; }
+ void SetLineSize( long nNewSize ) { mnLineSize = nNewSize; }
+ long GetLineSize() const { return mnLineSize; }
+ void SetPageSize( long nNewSize ) { mnPageSize = nNewSize; }
+ long GetPageSize() const { return mnPageSize; }
+
+ Size CalcWindowSizePixel();
+
+ 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 000000000..7b7fbe11b
--- /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 000000000..be2f54678
--- /dev/null
+++ b/vcl/inc/strhelper.hxx
@@ -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 .
+ */
+#ifndef INCLUDED_VCL_STRHELPER_HXX
+#define INCLUDED_VCL_STRHELPER_HXX
+
+#include <cstring>
+#include <rtl/math.hxx>
+#include <rtl/ustring.hxx>
+#include <vcl/dllapi.h>
+
+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( const OUString&, bool bProtect = true );
+ OString WhitespaceToSpace(const OString&);
+ // 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( const OUString& rStr )
+ {
+ return rtl::math::stringToDouble(rStr, u'.', u'\0');
+ }
+
+ inline double StringToDouble(const OString& rStr)
+ {
+ return rtl::math::stringToDouble(rStr, '.', static_cast<char>(0));
+ }
+
+ // fills a character buffer with the string representation of a double
+ // the buffer has to be long enough (e.g. 128 bytes)
+ // returns the string len
+ inline int getValueOfDouble( char* pBuffer, double f, int nPrecision = 0)
+ {
+ OString aStr( rtl::math::doubleToString( f, rtl_math_StringFormat_G, nPrecision, '.', true ) );
+ int nLen = aStr.getLength();
+ std::strncpy( pBuffer, aStr.getStr(), nLen+1 ); // copy string including terminating zero
+ return nLen;
+ }
+
+} // 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 000000000..e378b5481
--- /dev/null
+++ b/vcl/inc/strings.hrc
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_STRINGS_HRC
+#define INCLUDED_VCL_INC_STRINGS_HRC
+
+#define NC_(Context, String) reinterpret_cast<char const *>(Context "\004" 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_ROLLDOWN NC_("SV_HELPTEXT_ROLLDOWN", "Drop down")
+#define SV_HELPTEXT_ROLLUP NC_("SV_HELPTEXT_ROLLUP", "Roll up")
+#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_RESET NC_("SV_BUTTONTEXT_RESET", "R~eset")
+#define SV_BUTTONTEXT_ADD NC_("SV_BUTTONTEXT_ADD", "~Add")
+#define SV_BUTTONTEXT_DELETE NC_("SV_BUTTONTEXT_DELETE", "~Delete")
+#define SV_BUTTONTEXT_REMOVE NC_("SV_BUTTONTEXT_REMOVE", "~Remove")
+#define SV_BUTTONTEXT_NEW NC_("SV_BUTTONTEXT_NEW", "~New")
+#define SV_BUTTONTEXT_EDIT NC_("SV_BUTTONTEXT_EDIT", "~Edit")
+#define SV_BUTTONTEXT_APPLY NC_("SV_BUTTONTEXT_APPLY", "~Apply")
+#define SV_BUTTONTEXT_SAVE NC_("SV_BUTTONTEXT_SAVE", "~Save")
+#define SV_BUTTONTEXT_UNDO NC_("SV_BUTTONTEXT_UNDO", "~Undo")
+#define SV_BUTTONTEXT_PASTE NC_("SV_BUTTONTEXT_PASTE", "~Paste")
+#define SV_BUTTONTEXT_NEXT NC_("SV_BUTTONTEXT_NEXT", "~Next")
+#define SV_BUTTONTEXT_PREV NC_("SV_BUTTONTEXT_PREV", "~Previous")
+#define SV_BUTTONTEXT_GO_UP NC_("SV_BUTTONTEXT_GO_UP", "~Up")
+#define SV_BUTTONTEXT_GO_DOWN NC_("SV_BUTTONTEXT_GO_DOWN", "Do~wn")
+#define SV_BUTTONTEXT_CLEAR NC_("SV_BUTTONTEXT_CLEAR", "~Clear")
+#define SV_BUTTONTEXT_OPEN NC_("SV_BUTTONTEXT_OPEN", "~Open")
+#define SV_BUTTONTEXT_PLAY NC_("SV_BUTTONTEXT_PLAY", "~Play")
+#define SV_BUTTONTEXT_FIND NC_("SV_BUTTONTEXT_FIND", "~Find")
+#define SV_BUTTONTEXT_STOP NC_("SV_BUTTONTEXT_STOP", "~Stop")
+#define SV_BUTTONTEXT_CONNECT NC_("SV_BUTTONTEXT_CONNECT", "C~onnect")
+#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 STR_FPICKER_AUTO_EXTENSION NC_("STR_FPICKER_AUTO_EXTENSION", "~Automatic file name extension")
+#define STR_FPICKER_PASSWORD NC_("STR_FPICKER_PASSWORD", "Save with pass~word")
+// dear loplugins, please don't remove this constant, it will be used in follow-up commits
+#define STR_FPICKER_GPGENCRYPT NC_("STR_FPICKER_GPGENCRYPT", "Encrypt with ~GPG key")
+#define STR_FPICKER_FILTER_OPTIONS NC_("STR_FPICKER_FILTER_OPTIONS", "~Edit filter settings")
+#define STR_FPICKER_READONLY NC_("STR_FPICKER_READONLY", "~Read-only")
+#define STR_FPICKER_INSERT_AS_LINK NC_("STR_FPICKER_INSERT_AS_LINK", "Insert as ~Link")
+#define STR_FPICKER_SHOW_PREVIEW NC_("STR_FPICKER_SHOW_PREVIEW", "Pr~eview")
+#define STR_FPICKER_PLAY NC_("STR_FPICKER_PLAY", "~Play")
+#define STR_FPICKER_VERSION NC_("STR_FPICKER_VERSION", "~Version:")
+#define STR_FPICKER_TEMPLATES NC_("STR_FPICKER_TEMPLATES", "S~tyles:")
+#define STR_FPICKER_IMAGE_TEMPLATE NC_("STR_FPICKER_IMAGE_TEMPLATE", "Frame Style: ")
+#define STR_FPICKER_IMAGE_ANCHOR NC_("STR_FPICKER_IMAGE_ANCHOR", "A~nchor: ")
+#define STR_FPICKER_SELECTION NC_("STR_FPICKER_SELECTION", "~Selection")
+#define STR_FPICKER_FOLDER_DEFAULT_TITLE NC_("STR_FPICKER_FOLDER_DEFAULT_TITLE", "Select Path")
+#define STR_FPICKER_FOLDER_DEFAULT_DESCRIPTION NC_("STR_FPICKER_FOLDER_DEFAULT_DESCRIPTION", "Please select a folder.")
+#define STR_FPICKER_ALREADYEXISTOVERWRITE_PRIMARY NC_("STR_FPICKER_ALREADYEXISTOVERWRITE_PRIMARY", "A file named \"$filename$\" already exists. Do you want to replace it?")
+#define STR_FPICKER_ALREADYEXISTOVERWRITE_SECONDARY NC_("STR_FPICKER_ALREADYEXISTOVERWRITE_SECONDARY", "The file already exists in \"$dirname$\". Replacing it will overwrite its contents.")
+#define STR_FPICKER_ALLFORMATS NC_("STR_FPICKER_ALLFORMATS", "All Formats")
+#define STR_FPICKER_OPEN NC_("STR_FPICKER_OPEN", "Open")
+#define STR_FPICKER_SAVE NC_("STR_FPICKER_SAVE", "Save")
+#define STR_FPICKER_TYPE NC_("STR_FPICKER_TYPE", "File ~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_GL NC_("SV_APP_GL", "GL")
+#define SV_APP_SKIA_VULKAN NC_("SV_APP_SKIA_VULKAN", "Skia/Vulkan")
+#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")
+
+#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 000000000..45e9b2af4
--- /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 000000000..46a4a20cf
--- /dev/null
+++ b/vcl/inc/svdata.hxx
@@ -0,0 +1,462 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_SVDATA_HXX
+#define INCLUDED_VCL_INC_SVDATA_HXX
+
+#include <config_version.h>
+
+#include <o3tl/lru_map.hxx>
+#include <tools/fldunit.hxx>
+#include <unotools/options.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/window.hxx>
+#include <vcl/task.hxx>
+#include <LibreOfficeKit/LibreOfficeKitTypes.h>
+
+#include <com/sun/star/lang/XComponent.hpp>
+#include <com/sun/star/i18n/XCharacterClassification.hpp>
+
+#include "vcleventlisteners.hxx"
+#include "salwtype.hxx"
+#include "displayconnectiondispatch.hxx"
+
+#include <vector>
+#include <unordered_map>
+#include <boost/functional/hash.hpp>
+#include "ControlCacheKey.hxx"
+#include "schedulerimpl.hxx"
+#include <basegfx/DrawCommands.hxx>
+
+struct ImplPostEventData;
+struct ImplTimerData;
+struct ImplIdleData;
+struct ImplConfigData;
+class ImplDirectFontSubstitution;
+struct ImplHotKey;
+struct ImplEventHook;
+class Point;
+class ImplAccelManager;
+class PhysicalFontCollection;
+class ImplFontCache;
+class HelpTextWindow;
+class ImplTBDragMgr;
+class ImplIdleMgr;
+class FloatingWindow;
+class AllSettings;
+class NotifyEvent;
+class Timer;
+class AutoTimer;
+class Idle;
+class Help;
+class Image;
+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
+
+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::vector<Link<VclWindowEvent&,bool> > SVAppKeyListeners;
+
+typedef std::pair<VclPtr<vcl::Window>, ImplPostEventData *> ImplPostEventPair;
+
+struct ImplSVAppData
+{
+ ~ImplSVAppData();
+
+ std::unique_ptr<AllSettings> mpSettings; // Application settings
+ LocaleConfigurationListener* mpCfgListener = nullptr;
+ VclEventListeners maEventListeners; // listeners for vcl events (eg, extended toolkit)
+ SVAppKeyListeners maKeyListeners; // listeners for key events only (eg, extended toolkit)
+ std::vector<ImplPostEventPair> maPostedEventList;
+ ImplAccelManager* mpAccelMgr; // 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
+ bool mbAppQuit = false; // is Application::Quit() called
+ 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
+
+ SvFileStream* mpEventTestInput = nullptr;
+ Idle* mpEventTestingIdle = nullptr;
+ int mnEventTestLimit = 0;
+
+ DECL_STATIC_LINK(ImplSVAppData, ImplQuitMsg, void*, void);
+ DECL_STATIC_LINK(ImplSVAppData, ImplPrepareExitMsg, void*, void);
+ DECL_STATIC_LINK(ImplSVAppData, ImplEndAllDialogsMsg, void*, void);
+ DECL_STATIC_LINK(ImplSVAppData, ImplEndAllPopupsMsg, void*, void);
+ DECL_STATIC_LINK(ImplSVAppData, ImplVclEventTestingHdl, void*, void);
+ DECL_LINK(VclEventTestingHdl, Timer*, 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;
+ boost::hash_combine(seed, k.mpBitmap);
+ boost::hash_combine(seed, k.maDestSize.getWidth());
+ boost::hash_combine(seed, k.maDestSize.getHeight());
+ return seed;
+ }
+};
+
+} // end std namespace
+
+typedef o3tl::lru_map<ScaleCacheKey, BitmapEx> lru_scale_cache;
+
+struct ImplSVGDIData
+{
+ ~ImplSVGDIData();
+
+ VclPtr<OutputDevice> mpFirstWinGraphics; // First OutputDevice with a Frame Graphics
+ VclPtr<OutputDevice> 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<OutputDevice> mpFirstPrnGraphics; // First OutputDevice with an InfoPrinter Graphics
+ VclPtr<OutputDevice> 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<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
+ ImplDirectFontSubstitution* mpDirectFontSubst = nullptr; // Font-Substitutions defined in Tools->Options->Fonts
+ GraphicConverter* mpGrfConverter = nullptr; // Converter for graphics
+ long mnAppFontX = 0; // AppFont X-Numenator for 40/tel Width
+ 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
+ AutoTimer* mpTrackTimer = nullptr; // 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
+};
+
+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::unique_ptr<Image> mpDisclosurePlus;
+ std::unique_ptr<Image> mpDisclosureMinus;
+ 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 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
+ SchedulerMutex maMutex; ///< lock counting mutex for scheduler locking
+ 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;
+};
+
+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(const char* pId);
+DockingManager* ImplGetDockingManager();
+BlendFrameCache* ImplGetBlendFrameCache();
+
+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;
+
+#endif // INCLUDED_VCL_INC_SVDATA_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/svimpbox.hxx b/vcl/inc/svimpbox.hxx
new file mode 100644
index 000000000..c5a51143f
--- /dev/null
+++ b/vcl/inc/svimpbox.hxx
@@ -0,0 +1,398 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/dllapi.h>
+#include <vcl/seleng.hxx>
+#include <vcl/scrbar.hxx>
+#include <vcl/idle.hxx>
+#include <vcl/image.hxx>
+#include <vcl/svtaccessiblefactory.hxx>
+#include <vcl/vclevent.hxx>
+#include <vcl/treelistbox.hxx>
+#include <o3tl/enumarray.hxx>
+#include <memory>
+#include <vector>
+
+class SvLBoxButton;
+class SvTreeList;
+class SvImpLBox;
+class SvTreeListEntry;
+namespace comphelper
+{
+ namespace 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,
+ InScrolling = 0x0001,
+ 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, 0x07ff> {};
+}
+
+#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<ScrollBar> m_aHorSBar;
+ 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 )
+ NodeDontKnow, // don't know the node state
+ EntryDefExpanded, // default for expanded entries
+ EntryDefCollapsed, // default for collapsed entries
+ LAST = EntryDefCollapsed
+ };
+
+ // all our images
+ o3tl::enumarray<ImageType, Image>
+ m_aNodeAndEntryImages;
+
+ ImpLBSelEng m_aFctSet;
+
+ long m_nNodeBmpWidth;
+ 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 m_bContextMenuHandling : 1;
+ 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( long nY ) const;
+ bool IsLineVisible( long nY ) const;
+ void KeyLeftRight( 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, long nLine);
+ void InitScrollBarBox();
+ SvLBoxTab* NextTab( SvLBoxTab const * );
+
+ bool 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_aVerSBar;
+ SvTreeListEntry* m_pCursor;
+ 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
+ long m_nNextVerVisSize;
+ long m_nNodeBmpTabDistance; // typical smaller than 0
+
+ virtual 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( long nY ) const;
+
+ tools::Rectangle GetVisibleArea() const;
+ void SetCursor( SvTreeListEntry* pEntry, bool bForceNoSelect = false );
+ void BeginScroll();
+ void EndScroll();
+ 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( bool bInvalidateCompleteView );
+ 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( 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 const Image& GetDontKnowNodeBmp( );
+
+ 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 );
+ void EndSelection();
+ bool IsNodeButton( const Point& rPosPixel, 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 ShowFocusRect( const SvTreeListEntry* pEntry );
+ void CallEventListeners( VclEventId nEvent, void* pData = nullptr );
+
+ bool IsSelectable( const SvTreeListEntry* pEntry );
+ void SetForceMakeVisible(bool bEnable) { mbForceMakeVisible = bEnable; }
+};
+
+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::GetDontKnowNodeBmp( )
+{
+ return implGetImageLocation( ImageType::NodeDontKnow );
+}
+
+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( 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/svmconverter.hxx b/vcl/inc/svmconverter.hxx
new file mode 100644
index 000000000..459f327d4
--- /dev/null
+++ b/vcl/inc/svmconverter.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_SVMCONVERTER_HXX
+#define INCLUDED_VCL_INC_SVMCONVERTER_HXX
+
+#include <vcl/dllapi.h>
+#include <vcl/gdimtf.hxx>
+
+#define GDI_PIXEL_ACTION 1
+#define GDI_POINT_ACTION 2
+#define GDI_LINE_ACTION 3
+#define GDI_RECT_ACTION 4
+#define GDI_ELLIPSE_ACTION 5
+#define GDI_ARC_ACTION 6
+#define GDI_PIE_ACTION 7
+#define GDI_INVERTRECT_ACTION 8
+#define GDI_HIGHLIGHTRECT_ACTION 9
+#define GDI_POLYLINE_ACTION 10
+#define GDI_POLYGON_ACTION 11
+#define GDI_POLYPOLYGON_ACTION 12
+#define GDI_TEXT_ACTION 13
+#define GDI_TEXTARRAY_ACTION 14
+#define GDI_STRETCHTEXT_ACTION 15
+#define GDI_BITMAP_ACTION 17
+#define GDI_BITMAPSCALE_ACTION 18
+#define GDI_PEN_ACTION 19
+#define GDI_FONT_ACTION 20
+#define GDI_FILLBRUSH_ACTION 22
+#define GDI_MAPMODE_ACTION 23
+#define GDI_CLIPREGION_ACTION 24
+#define GDI_RASTEROP_ACTION 25
+#define GDI_PUSH_ACTION 26
+#define GDI_POP_ACTION 27
+#define GDI_MOVECLIPREGION_ACTION 28
+#define GDI_ISECTCLIPREGION_ACTION 29
+#define GDI_BITMAPSCALEPART_ACTION 32
+#define GDI_GRADIENT_ACTION 33
+
+#define GDI_TRANSPARENT_COMMENT 1024
+#define GDI_HATCH_COMMENT 1025
+#define GDI_REFPOINT_COMMENT 1026
+#define GDI_TEXTLINECOLOR_COMMENT 1027
+#define GDI_TEXTLINE_COMMENT 1028
+#define GDI_FLOATTRANSPARENT_COMMENT 1029
+#define GDI_GRADIENTEX_COMMENT 1030
+#define GDI_COMMENT_COMMENT 1031
+#define GDI_UNICODE_COMMENT 1032
+
+#define GDI_LINEJOIN_ACTION 1033
+#define GDI_EXTENDEDPOLYGON_ACTION 1034
+#define GDI_LINEDASHDOT_ACTION 1035
+
+#define GDI_LINECAP_ACTION 1036
+
+/**
+ * Converts old SVGDI aka SVM1 format data to current VCLMTF aka SVM2 format metafile data.
+ */
+class SVMConverter
+{
+private:
+ static void ImplConvertFromSVM1( SvStream& rIStm, GDIMetaFile& rMtf );
+
+public:
+ SVMConverter( SvStream& rIStm, GDIMetaFile& rMtf );
+
+private:
+ SVMConverter( const SVMConverter& ) = delete;
+ SVMConverter& operator=( const SVMConverter& ) = delete;
+};
+
+#endif // INCLUDED_VCL_INC_SVMCONVERTER_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 000000000..d86985669
--- /dev/null
+++ b/vcl/inc/svsys.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_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
+#include "ios/svsys.h"
+#elif defined ANDROID
+#include "android/svsys.h"
+#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/outputdevice.hxx b/vcl/inc/test/outputdevice.hxx
new file mode 100644
index 000000000..b8ad0b67f
--- /dev/null
+++ b/vcl/inc/test/outputdevice.hxx
@@ -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/.
+ *
+ */
+
+#ifndef INCLUDED_VCL_OUTDEVTESTS_HXX
+#define INCLUDED_VCL_OUTDEVTESTS_HXX
+
+#include <vcl/virdev.hxx>
+
+namespace vcl {
+namespace test {
+
+/** Rendering test result.
+ *
+ * Test either "Passed", "Failed" or "PassedWithQuirks" which means
+ * the test passed but at least one rendering quirk was detected.
+ */
+enum class TestResult
+{
+ Failed,
+ PassedWithQuirks,
+ Passed
+};
+
+/** 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(long nWidth, 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 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 void createDiamondPoints(tools::Rectangle rRect, int nOffset,
+ Point& rPoint1, Point& rPoint2,
+ Point& rPoint3, Point& rPoint4);
+
+ 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);
+};
+
+class VCL_DLLPUBLIC OutputDeviceTestBitmap : public OutputDeviceTestCommon
+{
+public:
+ OutputDeviceTestBitmap() = default;
+
+ Bitmap setupDrawTransformedBitmap();
+ Bitmap setupDrawBitmap();
+ Bitmap setupDrawBitmapExWithAlpha();
+ Bitmap setupDrawMask();
+ BitmapEx setupDrawBlend();
+
+ static TestResult checkTransformedBitmap(Bitmap& rBitmap);
+ static TestResult checkBitmapExWithAlpha(Bitmap& rBitmap);
+ static TestResult checkMask(Bitmap& rBitmap);
+ static TestResult checkBlend(BitmapEx& rBitmap);
+};
+
+class VCL_DLLPUBLIC OutputDeviceTestAnotherOutDev : public OutputDeviceTestCommon
+{
+public:
+ OutputDeviceTestAnotherOutDev() = default;
+
+ Bitmap setupDrawOutDev();
+ Bitmap setupXOR();
+
+ static TestResult checkDrawOutDev(Bitmap& rBitmap);
+ static TestResult checkXOR(Bitmap& rBitmap);
+};
+
+class VCL_DLLPUBLIC OutputDeviceTestPixel : public OutputDeviceTestCommon
+{
+public:
+ OutputDeviceTestPixel() = default;
+
+ Bitmap setupRectangle(bool bEnableAA);
+};
+
+class VCL_DLLPUBLIC OutputDeviceTestLine : public OutputDeviceTestCommon
+{
+public:
+ OutputDeviceTestLine() = default;
+
+ Bitmap setupRectangle(bool bEnableAA);
+ Bitmap setupDiamond();
+ Bitmap setupLines();
+ Bitmap setupAALines();
+
+ Bitmap setupDashedLine();
+ static TestResult checkDashedLine(Bitmap& rBitmap);
+};
+
+class VCL_DLLPUBLIC OutputDeviceTestPolyLine : public OutputDeviceTestCommon
+{
+public:
+ OutputDeviceTestPolyLine() = default;
+
+ Bitmap setupRectangle(bool bEnableAA);
+ Bitmap setupDiamond();
+ Bitmap setupLines();
+ Bitmap setupAALines();
+};
+
+class VCL_DLLPUBLIC OutputDeviceTestPolyLineB2D : public OutputDeviceTestCommon
+{
+public:
+ OutputDeviceTestPolyLineB2D() = default;
+
+ Bitmap setupRectangle(bool bEnableAA);
+ Bitmap setupDiamond();
+ Bitmap setupBezier();
+ Bitmap setupAABezier();
+};
+
+class VCL_DLLPUBLIC OutputDeviceTestRect : public OutputDeviceTestCommon
+{
+public:
+ OutputDeviceTestRect() = default;
+
+ Bitmap setupRectangle(bool bEnableAA);
+ Bitmap setupFilledRectangle(bool useLineColor);
+ 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();
+};
+
+class VCL_DLLPUBLIC OutputDeviceTestPolyPolygon : public OutputDeviceTestCommon
+{
+public:
+ OutputDeviceTestPolyPolygon() = default;
+
+ Bitmap setupRectangle(bool bEnableAA);
+ Bitmap setupFilledRectangle(bool useLineColor);
+};
+
+class VCL_DLLPUBLIC OutputDeviceTestPolyPolygonB2D : public OutputDeviceTestCommon
+{
+public:
+ OutputDeviceTestPolyPolygonB2D() = default;
+
+ Bitmap setupRectangle(bool bEnableAA);
+ Bitmap setupFilledRectangle(bool useLineColor);
+};
+
+class VCL_DLLPUBLIC OutputDeviceTestGradient : public OutputDeviceTestCommon
+{
+public:
+ OutputDeviceTestGradient() = default;
+
+ Bitmap setupLinearGradient();
+ Bitmap setupRadialGradient();
+};
+
+class VCL_DLLPUBLIC OutputDeviceTestClip : public OutputDeviceTestCommon
+{
+public:
+ Bitmap setupClipRectangle();
+ Bitmap setupClipPolygon();
+ Bitmap setupClipPolyPolygon();
+ Bitmap setupClipB2DPolyPolygon();
+
+ static TestResult checkClip(Bitmap& rBitmap);
+};
+
+
+}} // 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 000000000..e26cc6219
--- /dev/null
+++ b/vcl/inc/textlayout.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_TEXTLAYOUT_HXX
+#define INCLUDED_VCL_INC_TEXTLAYOUT_HXX
+
+#include <memory>
+
+class Control;
+
+namespace vcl
+{
+ class SAL_NO_VTABLE ITextLayout
+ {
+ public:
+ virtual 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,
+ MetricVector* _pVector, OUString* _pDisplayText ) = 0;
+ virtual void GetCaretPositions( const OUString& _rText, long* _pCaretXArray, sal_Int32 _nStartIndex, sal_Int32 _nLength ) const = 0;
+ virtual sal_Int32 GetTextBreak( const OUString& _rText, long _nMaxTextWidth, sal_Int32 _nStartIndex, sal_Int32 _nLength ) const = 0;
+ virtual bool DecomposeTextRectAction() const = 0;
+
+ protected:
+ ~ITextLayout() COVERITY_NOEXCEPT_FALSE {}
+ };
+
+ /** 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 ITextLayout
+ {
+ public:
+ DefaultTextLayout( OutputDevice& _rTargetDevice )
+ : m_rTargetDevice( _rTargetDevice )
+ {
+ }
+ virtual ~DefaultTextLayout();
+
+ // ITextLayout overridables
+ virtual 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,
+ MetricVector* _pVector,
+ OUString* _pDisplayText ) override;
+
+ virtual void GetCaretPositions( const OUString& _rText,
+ long* _pCaretXArray,
+ sal_Int32 _nStartIndex,
+ sal_Int32 _nLength ) const override;
+
+ virtual sal_Int32 GetTextBreak( const OUString& _rText,
+ 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,
+ MetricVector* _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
+
+#endif // INCLUDED_VCL_INC_TEXTLAYOUT_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/textlineinfo.hxx b/vcl/inc/textlineinfo.hxx
new file mode 100644
index 000000000..03b19ede9
--- /dev/null
+++ b/vcl/inc/textlineinfo.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_INC_TEXTLINEINFO_HXX
+#define INCLUDED_VCL_INC_TEXTLINEINFO_HXX
+
+#include <memory>
+#include <vector>
+
+class ImplTextLineInfo
+{
+private:
+ long mnWidth;
+ sal_Int32 mnIndex;
+ sal_Int32 mnLen;
+
+public:
+ ImplTextLineInfo( long nWidth, sal_Int32 nIndex, sal_Int32 nLen )
+ {
+ mnWidth = nWidth;
+ mnIndex = nIndex;
+ mnLen = nLen;
+ }
+
+ 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( ImplTextLineInfo* pLine );
+ void Clear();
+
+ ImplTextLineInfo* GetLine( sal_Int32 nLine ) const
+ { return mvLines[nLine].get(); }
+ sal_Int32 Count() const { return mvLines.size(); }
+
+private:
+ ImplMultiTextLineInfo( const ImplMultiTextLineInfo& ) = delete;
+ ImplMultiTextLineInfo& operator=( const ImplMultiTextLineInfo& ) = delete;
+
+ std::vector<std::unique_ptr<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 000000000..1aec8fba2
--- /dev/null
+++ b/vcl/inc/textrender.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_UNX_CAIROFONTIMPL_HXX
+#define INCLUDED_VCL_INC_UNX_CAIROFONTIMPL_HXX
+
+#include "salgdi.hxx"
+
+#include <config_cairo_canvas.h>
+
+class ImplLayoutArgs;
+class ImplFontMetricData;
+class PhysicalFontCollection;
+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( ImplFontMetricDataRef&, int nFallbackLevel ) = 0;
+ virtual FontCharMapRef GetFontCharMap() const = 0;
+ virtual bool GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const = 0;
+ virtual void GetDevFontList( PhysicalFontCollection* ) = 0;
+ virtual void ClearDevFontCache() = 0;
+ virtual bool AddTempDevFont( PhysicalFontCollection*, const OUString& rFileURL, const OUString& rFontName ) = 0;
+ virtual bool CreateFontSubset(
+ const OUString& rToFile,
+ const PhysicalFontFace*,
+ const sal_GlyphId* pGlyphIDs,
+ const sal_uInt8* pEncoding,
+ sal_Int32* pWidths,
+ int nGlyphs,
+ FontSubsetInfo& rInfo) = 0;
+
+ virtual const void* GetEmbedFontData(const PhysicalFontFace*, long* pDataLen) = 0;
+ virtual void FreeEmbedFontData( const void* pData, long nDataLen ) = 0;
+ virtual void GetGlyphWidths(
+ const PhysicalFontFace*,
+ bool bVertical,
+ std::vector< sal_Int32 >& rWidths,
+ Ucs2UIntMap& rUnicodeEnc ) = 0;
+
+ virtual std::unique_ptr<GenericSalLayout>
+ GetTextLayout(int nFallbackLevel) = 0;
+ virtual void DrawTextLayout(const GenericSalLayout&, const SalGraphics&) = 0;
+#if ENABLE_CAIRO_CANVAS
+ virtual SystemFontData GetSysFontData( int nFallbackLevel ) const = 0;
+#endif // ENABLE_CAIRO_CANVAS
+};
+
+#endif
+
+/* vim:set tabstop=4 shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/toolbox.h b/vcl/inc/toolbox.h
new file mode 100644
index 000000000..bf4c51c5d
--- /dev/null
+++ b/vcl/inc/toolbox.h
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_TOOLBOX_H
+#define INCLUDED_VCL_INC_TOOLBOX_H
+
+#include <vcl/toolbox.hxx>
+#include <vcl/toolkit/controllayout.hxx>
+
+#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;
+ long mnImageAngle;
+ bool mbMirrorMode;
+ OUString maText;
+ OUString maQuickHelpText;
+ OUString maHelpText;
+ OUString maCommandStr;
+ OString 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;
+ long mnSepSize;
+ 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;
+ sal_uInt16 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( sal_uInt16 nItemId, const Image& rImage,
+ ToolBoxItemBits nItemBits );
+ ImplToolItem( sal_uInt16 nItemId, const OUString& rTxt,
+ ToolBoxItemBits nItemBits );
+ ImplToolItem( sal_uInt16 nItemId, const Image& rImage,
+ const OUString& rTxt,
+ 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, 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(sal_uInt16 nItemId, ToolBoxItemBits nItemBits, bool bEmptyBtn);
+};
+
+namespace vcl
+{
+
+struct ToolBoxLayoutData : public ControlLayoutData
+{
+ std::vector< sal_uInt16 > m_aLineItemIds;
+};
+
+} /* namespace vcl */
+
+struct ImplToolBoxPrivateData
+{
+ std::unique_ptr<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; // 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;
+ 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 000000000..11c61944f
--- /dev/null
+++ b/vcl/inc/treeglue.hxx
@@ -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 <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(TransferDataContainer&, SvTreeListEntry*) override
+ {
+ return GetDragDropMode();
+ }
+
+ virtual bool EditingEntry(SvTreeListEntry* pEntry, Selection&) 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(TransferDataContainer&, SvTreeListEntry*) 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)
+ {
+ SvTreeListEntry* pOldTargetEntry = pTargetEntry;
+ pTargetEntry = pImpl->GetEntry(rPos);
+ if (pOldTargetEntry != pTargetEntry)
+ ImplShowTargetEmphasis(pOldTargetEntry, false);
+
+ // 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, Selection&) 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 000000000..accecb2e8
--- /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 000000000..74c449675
--- /dev/null
+++ b/vcl/inc/units.hrc
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_UNITS_HRC
+#define INCLUDED_VCL_INC_UNITS_HRC
+
+#define NC_(Context, String) reinterpret_cast<char const *>(Context "\004" u8##String)
+
+std::pair<const char*, 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/XIM.h b/vcl/inc/unx/XIM.h
new file mode 100644
index 000000000..d2fcd9c0e
--- /dev/null
+++ b/vcl/inc/unx/XIM.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_UNX_XIM_H
+#define INCLUDED_VCL_INC_UNX_XIM_H
+
+#include <X11/Xlib.h>
+
+#ifndef XIMCallback1
+typedef int (*XIMProc1)(XIC, XPointer, XPointer);
+typedef struct {
+ XPointer client_data;
+ XIMProc1 callback;
+} XIMCallback1;
+#endif
+
+typedef struct {
+ int start_position;
+ int end_position;
+ XPointer data;
+} XIMAnnotation;
+
+/*
+ XIMUText: XIMText extension for UTF16
+ */
+typedef struct {
+ unsigned short length;
+ XIMFeedback *feedback;
+ Bool encoding_is_wchar;
+ union {
+ char *multi_byte;
+ wchar_t *wide_char;
+ unsigned short *utf16_char;
+ } string;
+ unsigned int count_annotations;
+ XIMAnnotation *annotations;
+} XIMUnicodeText;
+
+/* lookup choice */
+typedef enum {
+ XIMDrawUpHorizontally = 0 ,
+ XIMDrawUpVertically = 1
+} XIMDrawUpDirection ;
+
+typedef struct {
+ int choice_per_window; /* Number of choices can be display
+ * in the region
+ */
+ int nrows;
+ int ncolumns;
+ XIMDrawUpDirection draw_up_direction;
+} XIMLookupStartCallbackStruct;
+
+typedef struct {
+ XIMUnicodeText *label;
+ XIMUnicodeText *value;
+} XIMUnicodeChoiceObject;
+
+typedef struct {
+ XIMUnicodeChoiceObject *choices; /* the lookup choices */
+ int n_choices; /* Total number of lookup choices */
+ int first_index;
+ int last_index;
+ int current_index;
+ XIMUnicodeText *title;
+} XIMLookupDrawCallbackStruct;
+
+/* Unicode Subset */
+typedef enum {
+ XIMKatakana, XIMHanzi
+} XIMUnicodeCharacterSubsetID;
+
+typedef struct {
+ XIMUnicodeCharacterSubsetID index;
+ XIMUnicodeCharacterSubsetID subset_id;
+ char *name;
+ Bool is_active;
+} XIMUnicodeCharacterSubset;
+
+typedef struct {
+ unsigned short count_subsets;
+ XIMUnicodeCharacterSubset *supported_subsets;
+} XIMUnicodeCharacterSubsets;
+
+typedef struct {
+ XIMUnicodeCharacterSubset *from;
+ XIMUnicodeCharacterSubset *to;
+} XIMSwitchIMNotifyCallbackStruct;
+
+/* XIC attributes for multilingual IM extension */
+
+#define XNUnicodeCharacterSubset "UnicodeChararcterSubset"
+
+#endif
+
+/* 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 000000000..3879bf5d9
--- /dev/null
+++ b/vcl/inc/unx/cairotextrender.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_CAIROTEXTRENDER_HXX
+#define INCLUDED_VCL_INC_UNX_CAIROTEXTRENDER_HXX
+
+#include <unx/freetypetextrender.hxx>
+
+typedef struct _cairo cairo_t;
+
+class VCL_DLLPUBLIC CairoTextRender : public FreeTypeTextRenderImpl
+{
+protected:
+ virtual cairo_t* getCairoContext() = 0;
+ virtual void getSurfaceOffset(double& nDX, double& nDY) = 0;
+ virtual void releaseCairoContext(cairo_t* cr) = 0;
+
+ virtual void clipRegion(cairo_t* cr) = 0;
+
+public:
+ virtual void DrawTextLayout(const GenericSalLayout&, const SalGraphics&) override;
+};
+
+#endif
+
+/* 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 000000000..6449355a1
--- /dev/null
+++ b/vcl/inc/unx/cpdmgr.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_UNX_CPDMGR_HXX
+#define INCLUDED_VCL_INC_UNX_CPDMGR_HXX
+
+#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 : 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
+
+#endif
+
+/* 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 000000000..9fd153938
--- /dev/null
+++ b/vcl/inc/unx/cupsmgr.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_UNX_CUPSMGR_HXX
+#define INCLUDED_VCL_INC_UNX_CUPSMGR_HXX
+
+#include <printerinfomanager.hxx>
+#include <osl/thread.h>
+#include <osl/mutex.hxx>
+
+namespace psp
+{
+
+class PPDParser;
+
+struct FPtrHash
+{
+ size_t operator()(const FILE* pPtr) const
+ { return reinterpret_cast<size_t>(pPtr); }
+};
+
+class CUPSManager : public PrinterInfoManager
+{
+ std::unordered_map< FILE*, OString, FPtrHash > m_aSpoolFiles;
+ int m_nDests;
+ void* 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
+
+#endif
+
+/* 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 000000000..531e4c642
--- /dev/null
+++ b/vcl/inc/unx/desktops.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_UNX_DESKTOPS_HXX
+#define INCLUDED_VCL_INC_UNX_DESKTOPS_HXX
+
+#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_LXQT
+}; // keep in sync with desktop_strings[] in salplug.cxx
+
+#endif // INCLUDED_VCL_INC_UNX_DESKTOPS_HXX
+
+/* 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 000000000..de6ed2aba
--- /dev/null
+++ b/vcl/inc/unx/fc_fontoptions.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_UNX_FC_FONTOPTIONS_HXX
+#define INCLUDED_VCL_INC_UNX_FC_FONTOPTIONS_HXX
+
+#include <rtl/string.hxx>
+
+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;
+};
+
+
+#endif // INCLUDED_VCL_INC_UNX_FC_FONTOPTIONS_HXX
+
+/* 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 000000000..7d625555d
--- /dev/null
+++ b/vcl/inc/unx/fontmanager.hxx
@@ -0,0 +1,322 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_FONTMANAGER_HXX
+#define INCLUDED_VCL_INC_FONTMANAGER_HXX
+
+#include <config_options.h>
+#include <tools/fontenum.hxx>
+#include <vcl/dllapi.h>
+#include <vcl/glyphitem.hxx>
+#include <vcl/timer.hxx>
+#include <com/sun/star/lang/Locale.hpp>
+#include <unx/fc_fontoptions.hxx>
+
+#include <map>
+#include <set>
+#include <memory>
+#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 FontSubsetInfo;
+class FontConfigFontOptions;
+class FontSelectPattern;
+class GenericUnixSalData;
+
+namespace psp {
+class PPDParser;
+
+typedef int fontID;
+
+/*
+ * the difference between FastPrintFontInfo and PrintFontInfo
+ * is that the information in FastPrintFontInfo can usually
+ * be gathered without opening either the font file, they are
+ * gathered from fonts.dir alone.
+ * if only FastPrintFontInfo is gathered and PrintFontInfo
+ * on demand and for less fonts, then performance in startup
+ * increases considerably
+ */
+
+struct FastPrintFontInfo
+{
+ fontID m_nID; // FontID
+
+ // font attributes
+ OUString m_aFamilyName;
+ OUString m_aStyleName;
+ std::vector< OUString > m_aAliases;
+ FontFamily m_eFamilyStyle;
+ FontItalic m_eItalic;
+ FontWidth m_eWidth;
+ FontWeight m_eWeight;
+ FontPitch m_ePitch;
+ rtl_TextEncoding m_aEncoding;
+
+ FastPrintFontInfo()
+ : m_nID(0)
+ , m_eFamilyStyle(FAMILY_DONTKNOW)
+ , m_eItalic(ITALIC_DONTKNOW)
+ , m_eWidth(WIDTH_DONTKNOW)
+ , m_eWeight(WEIGHT_DONTKNOW)
+ , m_ePitch(PITCH_DONTKNOW)
+ , m_aEncoding(RTL_TEXTENCODING_DONTKNOW)
+ {}
+};
+
+struct PrintFontInfo : public FastPrintFontInfo
+{
+ int m_nAscend;
+ int m_nDescend;
+
+ PrintFontInfo() :
+ FastPrintFontInfo(),
+ m_nAscend( 0 ),
+ m_nDescend( 0 )
+ {}
+};
+
+// a class to manage printable fonts
+
+class VCL_PLUGIN_PUBLIC PrintFontManager
+{
+ struct PrintFont;
+ friend struct PrintFont;
+
+ struct VCL_DLLPRIVATE PrintFont
+ {
+ // font attributes
+ OUString m_aFamilyName;
+ std::vector<OUString> m_aAliases;
+ OUString m_aPSName;
+ OUString m_aStyleName;
+ FontFamily m_eFamilyStyle;
+ FontItalic m_eItalic;
+ FontWidth m_eWidth;
+ FontWeight m_eWeight;
+ FontPitch m_ePitch;
+ rtl_TextEncoding m_aEncoding;
+ int m_nAscend;
+ int m_nDescend;
+ int m_nLeading;
+ int m_nXMin; // font bounding box
+ int m_nYMin;
+ int m_nXMax;
+ int m_nYMax;
+
+ 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, std::unique_ptr<PrintFont> > m_aFonts;
+ // for speeding up findFontFileID
+ std::unordered_map< OString, std::set< fontID > >
+ m_aFontFileToFontID;
+
+ std::unordered_map< OString, int >
+ m_aDirToAtom;
+ std::unordered_map< int, OString > m_aAtomToDir;
+ int m_nNextDirAtom;
+
+ OString getFontFile(const PrintFont* pFont) const;
+
+ std::vector<std::unique_ptr<PrintFont>> analyzeFontFile(int nDirID, const OString& rFileName, const char *pFormat=nullptr) const;
+ static OUString convertSfntName( void* pNameRecord ); // actually a NameRecord* format font subsetting code
+ static void analyzeSfntFamilyName( void const * pTTFont, std::vector< OUString >& rnames ); // actually a TrueTypeFont* from font subsetting code
+ bool analyzeSfntFile(PrintFont* pFont) 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( const OUString& rFamily );
+
+ PrintFont* getFont( fontID nID ) const
+ {
+ auto it = m_aFonts.find( nID );
+ return it == m_aFonts.end() ? nullptr : it->second.get();
+ }
+ static void fillPrintFontInfo(PrintFont* pFont, FastPrintFontInfo& rInfo);
+ void fillPrintFontInfo( PrintFont* pFont, PrintFontInfo& rInfo ) const;
+
+ 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( std::unordered_map<OString, int>& o_rVisitedPaths );
+ /* 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);
+
+ std::set<OString> m_aPreviousLangSupportRequests;
+ std::vector<OUString> m_aCurrentRequests;
+ Timer m_aFontInstallerTimer;
+
+ DECL_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( const OUString& rFileUrl );
+
+ void initialize();
+
+ // returns the ids of all managed fonts.
+ void getFontList( std::vector< fontID >& rFontIDs );
+
+ // get font info for a specific font
+ bool getFontInfo( fontID nFontID, PrintFontInfo& rInfo ) const;
+ // get fast font info for a specific font
+ bool getFontFastInfo( fontID nFontID, FastPrintFontInfo& rInfo ) const;
+
+ // routines to get font info in small pieces
+
+ // get a specific fonts PSName name
+ OUString getPSName( fontID nFontID ) const;
+
+ // get a specific fonts italic type
+ FontItalic getFontItalic( fontID nFontID ) const
+ {
+ PrintFont* pFont = getFont( nFontID );
+ return pFont ? pFont->m_eItalic : ITALIC_DONTKNOW;
+ }
+
+ // get a specific fonts weight type
+ FontWeight getFontWeight( fontID nFontID ) const
+ {
+ PrintFont* pFont = getFont( nFontID );
+ return pFont ? pFont->m_eWeight : WEIGHT_DONTKNOW;
+ }
+
+ // 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;
+
+ // get a specific fonts ascend
+ int getFontAscend( fontID nFontID ) const;
+
+ // get a specific fonts descent
+ int getFontDescend( fontID nFontID ) const;
+
+ // get a fonts glyph bounding box
+ void getFontBoundingBox( fontID nFont, int& xMin, int& yMin, int& xMax, int& yMax );
+
+ // creates a new font subset of an existing SFNT font
+ // returns true in case of success, else false
+ // nFont: the font to be subsetted
+ // rOutFile: the file to put the new subset into;
+ // must be a valid osl file URL
+ // pGlyphIDs: input array of glyph ids for new font
+ // pNewEncoding: the corresponding encoding in the new font
+ // pWidths: output array of widths of requested glyphs
+ // nGlyphs: number of glyphs in arrays
+ // pCapHeight:: capital height of the produced font
+ // pXMin, pYMin, pXMax, pYMax: outgoing font bounding box
+ // TODO: callers of this method should use its FontSubsetInfo counterpart directly
+ bool createFontSubset( FontSubsetInfo&,
+ fontID nFont,
+ const OUString& rOutFile,
+ const sal_GlyphId* pGlyphIDs,
+ const sal_uInt8* pNewEncoding,
+ sal_Int32* pWidths,
+ int nGlyphs
+ );
+ void getGlyphWidths( fontID nFont,
+ bool bVertical,
+ std::vector< sal_Int32 >& rWidths,
+ std::map< sal_Unicode, sal_uInt32 >& rUnicodeEnc );
+
+ // 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
+ the <code>FastPrintFontInfo</code> passed in as parameter
+ will be update to the found matching font.
+ </p>
+ <p>
+ implementation note: currently the function is only implemented
+ for fontconfig.
+ </p>
+
+ @param rInfo
+ out of the FastPrintFontInfo 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
+ */
+ void matchFont( FastPrintFontInfo& rInfo, const css::lang::Locale& rLocale );
+ static std::unique_ptr<FontConfigFontOptions> getFontOptions( const FastPrintFontInfo&, int nSize);
+
+ void Substitute(FontSelectPattern &rPattern, OUString& rMissingCodes);
+
+};
+
+} // namespace
+
+#endif // INCLUDED_VCL_INC_FONTMANAGER_HXX
+
+/* 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 000000000..c375ba2ff
--- /dev/null
+++ b/vcl/inc/unx/freetype_glyphcache.hxx
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_GENERIC_GLYPHS_GCACH_FTYP_HXX
+#define INCLUDED_VCL_GENERIC_GLYPHS_GCACH_FTYP_HXX
+
+#include <unx/glyphcache.hxx>
+#include <PhysicalFontFace.hxx>
+#include <fontinstance.hxx>
+#include <vcl/glyphitem.hxx>
+
+class CmapResult;
+
+// 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( const OString& rNativeFileName );
+
+ const OString maNativeFileName;
+ unsigned char* mpFileMap;
+ int mnFileSize;
+ int mnRefCount;
+ int mnLangBoost;
+};
+
+// FreetypeFontInfo corresponds to an unscaled font face
+class FreetypeFontInfo final
+{
+public:
+ ~FreetypeFontInfo();
+
+ const unsigned char* GetTable( const char*, sal_uLong* pLength) const;
+
+ FT_FaceRec_* GetFaceFT();
+ void ReleaseFaceFT();
+
+ const OString& GetFontFileName() const { return mpFontFile->GetFileName(); }
+ int GetFontFaceIndex() const { return mnFaceNum; }
+ int GetFontFaceVariation() const { return mnFaceVariation; }
+ sal_IntPtr GetFontId() const { return mnFontId; }
+ bool IsSymbolFont() const { return maDevFontAttributes.IsSymbolFont(); }
+ const FontAttributes& GetFontAttributes() const { return maDevFontAttributes; }
+
+ void AnnounceFont( PhysicalFontCollection* );
+
+ const FontCharMapRef& GetFontCharMap();
+
+private:
+ friend class FreetypeManager;
+ explicit FreetypeFontInfo(const 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;
+
+ FontCharMapRef mxFontCharMap;
+};
+
+class FreetypeFontFace : public PhysicalFontFace
+{
+private:
+ FreetypeFontInfo* mpFreetypeFontInfo;
+
+public:
+ FreetypeFontFace( FreetypeFontInfo*, const FontAttributes& );
+
+ virtual rtl::Reference<LogicalFontInstance> CreateFontInstance( const FontSelectPattern& ) const override;
+ virtual sal_IntPtr GetFontId() const override { return mpFreetypeFontInfo->GetFontId(); }
+};
+
+class SAL_DLLPUBLIC_RTTI FreetypeFontInstance : public LogicalFontInstance
+{
+ friend rtl::Reference<LogicalFontInstance> FreetypeFontFace::CreateFontInstance(const FontSelectPattern&) const;
+
+ std::unique_ptr<FreetypeFont> mxFreetypeFont;
+
+ virtual hb_font_t* ImplInitHbFont() override;
+ virtual bool ImplGetGlyphBoundRect(sal_GlyphId, tools::Rectangle&, bool) const override;
+
+protected:
+ explicit FreetypeFontInstance(const PhysicalFontFace& rPFF, const FontSelectPattern& rFSP);
+
+public:
+ virtual ~FreetypeFontInstance() override;
+
+ FreetypeFont& GetFreetypeFont() const { return *mxFreetypeFont; }
+
+ virtual bool GetGlyphOutline(sal_GlyphId, basegfx::B2DPolyPolygon&, bool) const override;
+};
+
+#endif // INCLUDED_VCL_GENERIC_GLYPHS_GCACH_FTYP_HXX
+
+/* 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 000000000..ccc1db015
--- /dev/null
+++ b/vcl/inc/unx/freetypetextrender.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_UNX_FREETYPETEXTRENDER_HXX
+#define INCLUDED_VCL_INC_UNX_FREETYPETEXTRENDER_HXX
+
+#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( ImplFontMetricDataRef&, int nFallbackLevel ) override;
+ virtual FontCharMapRef GetFontCharMap() const override;
+ virtual bool GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const override;
+ virtual void GetDevFontList( PhysicalFontCollection* ) override;
+ virtual void ClearDevFontCache() override;
+ virtual bool AddTempDevFont( PhysicalFontCollection*, const OUString& rFileURL, const OUString& rFontName ) override;
+ virtual bool CreateFontSubset(
+ const OUString& rToFile,
+ const PhysicalFontFace*,
+ const sal_GlyphId* pGlyphIDs,
+ const sal_uInt8* pEncoding,
+ sal_Int32* pWidths,
+ int nGlyphs,
+ FontSubsetInfo& rInfo) override;
+
+ virtual const void* GetEmbedFontData(const PhysicalFontFace*, long* pDataLen) override;
+ virtual void FreeEmbedFontData( const void* pData, long nDataLen ) override;
+ virtual void GetGlyphWidths(
+ const PhysicalFontFace*,
+ bool bVertical,
+ std::vector< sal_Int32 >& rWidths,
+ Ucs2UIntMap& rUnicodeEnc ) override;
+
+ virtual std::unique_ptr<GenericSalLayout>
+ GetTextLayout(int nFallbackLevel) override;
+#if ENABLE_CAIRO_CANVAS
+ virtual SystemFontData GetSysFontData( int nFallbackLevel ) const override;
+#endif
+};
+
+#endif
+
+/* 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 000000000..f06dda35c
--- /dev/null
+++ b/vcl/inc/unx/gendata.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/.
+ */
+
+#ifndef INCLUDED_VCL_INC_GENERIC_GENDATA_HXX
+#define INCLUDED_VCL_INC_GENERIC_GENDATA_HXX
+
+#include <osl/socket.hxx>
+
+#include <saldatabasic.hxx>
+
+#include <memory>
+
+class FreetypeManager;
+class SalGenericDisplay;
+namespace psp
+{
+class PrintFontManager;
+}
+
+enum GenericUnixSalDataType
+{
+ SAL_DATA_GTK,
+ SAL_DATA_GTK3,
+ SAL_DATA_KF5,
+ SAL_DATA_UNX,
+ SAL_DATA_SVP,
+ SAL_DATA_ANDROID,
+ SAL_DATA_IOS,
+ SAL_DATA_HEADLESS,
+ SAL_DATA_QT5
+};
+
+class VCL_DLLPUBLIC GenericUnixSalData : public SalData
+{
+private:
+ GenericUnixSalDataType m_eType;
+ 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;
+
+ std::unique_ptr<FreetypeManager> m_pFreetypeManager;
+ std::unique_ptr<psp::PrintFontManager> m_pPrintFontManager;
+
+ void InitFreetypeManager();
+ void InitPrintFontManager();
+
+public:
+ GenericUnixSalData(GenericUnixSalDataType const t, SalInstance* const pInstance);
+ 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; }
+
+ GenericUnixSalDataType GetType() const { return m_eType; }
+
+ 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();
+ }
+
+ // 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);
+}
+
+#endif
+
+/* 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 000000000..a6188e27a
--- /dev/null
+++ b/vcl/inc/unx/gendisp.hxx
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_VCL_INC_GENERIC_GENDISP_HXX
+#define INCLUDED_VCL_INC_GENERIC_GENDISP_HXX
+
+#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; }
+};
+
+#endif // INCLUDED_VCL_INC_GENERIC_GENDISP_HXX
+
+/* 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 000000000..dcd8bdd6f
--- /dev/null
+++ b/vcl/inc/unx/geninst.h
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_GENERIC_GENINST_H
+#define INCLUDED_VCL_INC_GENERIC_GENINST_H
+
+#include <memory>
+#include <comphelper/solarmutex.hxx>
+#include <salinst.hxx>
+#include <saldatabasic.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;
+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 jobStartedPrinterUpdate() 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( PhysicalFontCollection* pFontCollection );
+
+protected:
+ static void configurePspInfoPrinter( PspSalInfoPrinter* pInfoPrinter,
+ SalPrinterQueueInfo const * pQueueInfo,
+ ImplJobSetup* pSetupData );
+};
+
+inline SalGenericInstance *GetGenericInstance()
+{
+ return static_cast<SalGenericInstance *>(GetSalData()->m_pInstance);
+}
+
+#endif // INCLUDED_VCL_INC_GENERIC_GENINST_H
+
+/* 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 000000000..14917cf72
--- /dev/null
+++ b/vcl/inc/unx/genprn.h
@@ -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_GENERIC_GENPRN_H
+#define INCLUDED_VCL_INC_GENERIC_GENPRN_H
+
+#include <jobdata.hxx>
+#include <unx/printergfx.hxx>
+#include <unx/printerjob.hxx>
+#include <salprn.hxx>
+
+class GenPspGraphics;
+class VCL_DLLPUBLIC PspSalInfoPrinter : public SalInfoPrinter
+{
+public:
+ std::unique_ptr<GenPspGraphics> m_pGraphics;
+ psp::JobData m_aJobData;
+ psp::PrinterGfx m_aPrinterGfx;
+
+ 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,
+ long& rOutWidth, 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:
+ OUString m_aFileName;
+ OUString m_aTmpFile;
+ SalInfoPrinter* m_pInfoPrinter;
+ std::unique_ptr<GenPspGraphics> m_xGraphics;
+ psp::PrinterJob m_aPrintJob;
+ psp::JobData m_aJobData;
+ psp::PrinterGfx m_aPrinterGfx;
+ sal_uInt32 m_nCopies;
+ bool m_bCollate;
+ bool m_bPdf;
+ bool m_bIsPDFWriterJob;
+
+ 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;
+};
+
+#endif // INCLUDED_VCL_INC_GENERIC_GENPRN_H
+
+/* 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 000000000..b696618c7
--- /dev/null
+++ b/vcl/inc/unx/genpspgraphics.h
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_GENERIC_GENPSPGRAPHICS_H
+#define INCLUDED_VCL_INC_GENERIC_GENPSPGRAPHICS_H
+
+#include <vcl/vclenum.hxx>
+#include <config_cairo_canvas.h>
+
+#include <unx/fontmanager.hxx>
+#include <salgdi.hxx>
+#include <sallayout.hxx>
+
+class PhysicalFontFace;
+class PhysicalFontCollection;
+
+namespace psp { struct JobData; class PrinterGfx; }
+
+class FreetypeFontInstance;
+class FontAttributes;
+class SalInfoPrinter;
+class ImplFontMetricData;
+
+class VCL_DLLPUBLIC GenPspGraphics final : public SalGraphics
+{
+ psp::JobData* m_pJobData;
+ psp::PrinterGfx* m_pPrinterGfx;
+
+ rtl::Reference<FreetypeFontInstance>
+ m_pFreetypeFont[ MAX_FALLBACK ];
+public:
+ GenPspGraphics();
+ virtual ~GenPspGraphics() override;
+
+ void Init( psp::JobData* pJob, psp::PrinterGfx* pGfx );
+
+ // helper methods
+ static const void * DoGetEmbedFontData(psp::fontID aFont, long* pDataLen);
+ static void DoFreeEmbedFontData( const void* pData, long nLen );
+
+ // helper methods for sharing with X11SalGraphics
+ static void DoGetGlyphWidths( psp::fontID aFont,
+ bool bVertical,
+ std::vector< sal_Int32 >& rWidths,
+ Ucs2UIntMap& rUnicodeEnc );
+
+ static FontAttributes Info2FontAttributes( const psp::FastPrintFontInfo& );
+ static void AnnounceFonts( PhysicalFontCollection*,
+ const psp::FastPrintFontInfo& );
+
+ // override all pure virtual methods
+ virtual SalGraphicsImpl*GetImpl() const override { return nullptr; };
+ virtual void GetResolution( sal_Int32& rDPIX, sal_Int32& rDPIY ) override;
+ virtual sal_uInt16 GetBitCount() const override;
+ virtual long GetGraphicsWidth() const override;
+
+ virtual void ResetClipRegion() override;
+ virtual bool setClipRegion( const vcl::Region& ) override;
+
+ virtual void SetLineColor() override;
+ virtual void SetLineColor( Color nColor ) override;
+ virtual void SetFillColor() override;
+ virtual void SetFillColor( Color nColor ) override;
+ virtual void SetXORMode( bool bSet, bool ) override;
+ virtual void SetROPLineColor( SalROPColor nROPColor ) override;
+ virtual void SetROPFillColor( SalROPColor nROPColor ) override;
+
+ virtual void SetTextColor( Color nColor ) override;
+ virtual void SetFont(LogicalFontInstance*, int nFallbackLevel) override;
+ virtual void GetFontMetric( ImplFontMetricDataRef&, int nFallbackLevel ) override;
+ virtual FontCharMapRef GetFontCharMap() const override;
+ virtual bool GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const override;
+ virtual void GetDevFontList( PhysicalFontCollection* ) override;
+ // graphics must drop any cached font info
+ virtual void ClearDevFontCache() override;
+ virtual bool AddTempDevFont( PhysicalFontCollection*,
+ const OUString& rFileURL,
+ const OUString& rFontName ) override;
+ static bool AddTempDevFontHelper( PhysicalFontCollection* pFontCollection,
+ const OUString& rFileURL,
+ const OUString& rFontName);
+
+ virtual bool CreateFontSubset( const OUString& rToFile,
+ const PhysicalFontFace*,
+ const sal_GlyphId* pGlyphIDs,
+ const sal_uInt8* pEncoding,
+ sal_Int32* pWidths,
+ int nGlyphs,
+ FontSubsetInfo& rInfo ) override;
+ virtual const void* GetEmbedFontData(const PhysicalFontFace*, long* pDataLen) override;
+ virtual void FreeEmbedFontData( const void* pData, long nDataLen ) override;
+ virtual void GetGlyphWidths( const PhysicalFontFace*,
+ bool bVertical,
+ std::vector< sal_Int32 >& rWidths,
+ Ucs2UIntMap& rUnicodeEnc ) override;
+ virtual std::unique_ptr<GenericSalLayout>
+ GetTextLayout(int nFallbackLevel) override;
+ virtual void DrawTextLayout( const GenericSalLayout& ) override;
+ virtual bool supportsOperation( OutDevSupportType ) const override;
+ virtual void drawPixel( long nX, long nY ) override;
+ virtual void drawPixel( long nX, long nY, Color nColor ) override;
+ virtual void drawLine( long nX1, long nY1, long nX2, long nY2 ) override;
+ virtual void drawRect( long nX, long nY, long nWidth, long nHeight ) override;
+ virtual void drawPolyLine( sal_uInt32 nPoints, const SalPoint* pPtAry ) override;
+ virtual void drawPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry ) override;
+ virtual void drawPolyPolygon( sal_uInt32 nPoly,
+ const sal_uInt32* pPoints,
+ PCONSTSALPOINT* pPtAry ) override;
+
+ virtual bool 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 SalPoint* pPtAry,
+ const PolyFlags* pFlgAry ) override;
+ virtual bool drawPolygonBezier( sal_uInt32 nPoints,
+ const SalPoint* pPtAry,
+ const PolyFlags* pFlgAry ) override;
+ virtual bool drawPolyPolygonBezier( sal_uInt32 nPoly,
+ const sal_uInt32* pPoints,
+ const SalPoint* const* pPtAry,
+ const PolyFlags* const* pFlgAry ) override;
+ virtual bool drawGradient( const tools::PolyPolygon&, const Gradient& ) override { return false; };
+
+ virtual void copyArea( long nDestX,
+ long nDestY,
+ long nSrcX,
+ long nSrcY,
+ long nSrcWidth,
+ long nSrcHeight,
+ bool bWindowInvalidate) override;
+ 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( long nX, long nY, long nWidth, long nHeight ) override;
+ virtual Color getPixel( long nX, long nY ) override;
+ virtual void invert( long nX, long nY, long nWidth, long nHeight,
+ SalInvert nFlags ) override;
+ virtual void invert( sal_uInt32 nPoints, const SalPoint* pPtAry,
+ SalInvert nFlags ) override;
+
+ virtual bool drawEPS( long nX, long nY, long nWidth, 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;
+ 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) override;
+ virtual bool drawAlphaRect( long nX, long nY, long nWidth, long nHeight,
+ sal_uInt8 nTransparency ) override;
+
+ virtual SystemGraphicsData GetGraphicsData() const override;
+
+ virtual OUString getRenderBackendName() const override { return "genpsp"; }
+
+#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;
+
+ virtual SystemFontData GetSysFontData( int nFallbacklevel ) const override;
+#endif // ENABLE_CAIRO_CANVAS
+};
+
+#endif // INCLUDED_VCL_INC_GENERIC_GENPSPGRAPHICS_H
+
+/* 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 000000000..6da0b17b8
--- /dev/null
+++ b/vcl/inc/unx/gensys.h
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_GENERIC_GENSYS_H
+#define INCLUDED_VCL_INC_GENERIC_GENSYS_H
+
+#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
+};
+
+#endif // INCLUDED_VCL_INC_GENERIC_GENSYS_H
+
+/* 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 000000000..983a6ec03
--- /dev/null
+++ b/vcl/inc/unx/glyphcache.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_GENERIC_GLYPHCACHE_HXX
+#define INCLUDED_VCL_INC_GENERIC_GLYPHCACHE_HXX
+
+#include <memory>
+#include <freetype/config/ftheader.h>
+#include FT_FREETYPE_H
+#include FT_GLYPH_H
+
+#include <tools/gen.hxx>
+#include <tools/solar.h>
+#include <unx/gendata.hxx>
+#include <vcl/dllapi.h>
+#include <vcl/outdev.hxx>
+
+#include <fontattributes.hxx>
+#include <fontinstance.hxx>
+#include <impfontmetricdata.hxx>
+
+#include <unordered_map>
+
+class FreetypeFont;
+class FreetypeFontFile;
+class FreetypeFontInstance;
+class FreetypeFontInfo;
+class FontConfigFontOptions;
+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( 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;
+ sal_IntPtr m_nMaxFontId;
+
+ 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;
+ int GetLoadFlags() const { return (mnLoadFlags & ~FT_LOAD_IGNORE_TRANSFORM); }
+ const FontConfigFontOptions* GetFontOptions() const;
+ bool NeedsArtificialBold() const { return mbArtBold; }
+ bool NeedsArtificialItalic() const { return mbArtItalic; }
+
+ void GetFontMetric(ImplFontMetricDataRef const &) const;
+ const unsigned char* GetTable( const char* pName, sal_uLong* pLength ) const;
+ FontCharMapRef GetFontCharMap() const;
+ bool GetFontCapabilities(vcl::FontCapabilities &) const;
+
+ bool GetGlyphBoundRect(sal_GlyphId, tools::Rectangle&, bool) const;
+ bool GetGlyphOutline(sal_GlyphId, basegfx::B2DPolyPolygon&, bool) const;
+ bool GetAntialiasAdvice() const;
+
+ FreetypeFontInstance& GetFontInstance() const { return mrFontInstance; }
+
+ void SetFontVariationsOnHBFont(hb_font_t* pHbFace) const;
+
+ // tdf#127189 FreeType <= 2.8 will fail to render stretched horizontal brace glyphs
+ // in starmath at a fairly low stretch ratio. This appears fixed in 2.9 with
+ // https://git.savannah.gnu.org/cgit/freetype/freetype2.git/commit/?id=91015cb41d8f56777f93394f5a60914bc0c0f330
+ // "Improve complex rendering at high ppem"
+ static bool AlmostHorizontalDrainsRenderingPool(int nRatio, const FontSelectPattern& rFSD);
+
+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
+ long mnCos;
+ long mnSin;
+
+ int mnWidth;
+ int mnPrioAntiAlias;
+ std::shared_ptr<FreetypeFontInfo> mxFontInfo;
+ FT_Int mnLoadFlags;
+ double mfStretch;
+ FT_FaceRec_* maFaceFT;
+ FT_SizeRec_* maSizeFT;
+
+ mutable std::unique_ptr<FontConfigFontOptions> mxFontOptions;
+
+ bool mbFaceOk;
+ bool mbArtItalic;
+ bool mbArtBold;
+};
+
+#endif // INCLUDED_VCL_INC_GENERIC_GLYPHCACHE_HXX
+
+/* 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 000000000..2dff94b02
--- /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 000000000..49c422dac
--- /dev/null
+++ b/vcl/inc/unx/gtk/atkbridge.hxx
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_VCL_INC_UNX_GTK_ATKBRIDGE_HXX
+#define INCLUDED_VCL_INC_UNX_GTK_ATKBRIDGE_HXX
+
+#include <vclpluginapi.h>
+
+bool InitAtkBridge();
+void DeInitAtkBridge();
+
+#endif
+
+/* 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 000000000..4028f76dc
--- /dev/null
+++ b/vcl/inc/unx/gtk/gloactiongroup.h
@@ -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/.
+ */
+
+#ifndef INCLUDED_VCL_INC_UNX_GTK_GLOACTIONGROUP_H
+#define INCLUDED_VCL_INC_UNX_GTK_GLOACTIONGROUP_H
+
+#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
+
+#endif // INCLUDED_VCL_INC_UNX_GTK_GLOACTIONGROUP_H
+
+/* 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 000000000..a0150a845
--- /dev/null
+++ b/vcl/inc/unx/gtk/glomenu.h
@@ -0,0 +1,137 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_UNX_GTK_GLOMENU_H
+#define INCLUDED_VCL_INC_UNX_GTK_GLOMENU_H
+
+#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
+
+#endif // INCLUDED_VCL_INC_UNX_GTK_GLOMENU_H
+
+/* 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 000000000..288311b41
--- /dev/null
+++ b/vcl/inc/unx/gtk/gtkbackend.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/.
+ */
+
+#ifndef INCLUDED_VCL_INC_UNX_GTK_GTKBACKEND_HXX
+#define INCLUDED_VCL_INC_UNX_GTK_GTKBACKEND_HXX
+
+#include <gtk/gtk.h>
+#if defined(GDK_WINDOWING_X11)
+#include <gdk/gdkx.h>
+bool DLSYM_GDK_IS_X11_DISPLAY(GdkDisplay* pDisplay);
+#endif
+#if defined(GDK_WINDOWING_WAYLAND)
+#include <gdk/gdkwayland.h>
+bool DLSYM_GDK_IS_WAYLAND_DISPLAY(GdkDisplay* pDisplay);
+#endif
+
+#endif // INCLUDED_VCL_INC_UNX_GTK_GTKBACKEND_HXX
+
+/* 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 000000000..2f88ffe8b
--- /dev/null
+++ b/vcl/inc/unx/gtk/gtkdata.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_UNX_GTK_GTKDATA_HXX
+#define INCLUDED_VCL_INC_UNX_GTK_GTKDATA_HXX
+
+#define GLIB_DISABLE_DEPRECATION_WARNINGS
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+
+#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 <vector>
+
+namespace com::sun::star::accessibility { class XAccessibleEventListener; }
+
+class GtkSalDisplay;
+class DocumentFocusListener;
+
+inline ::Window widget_get_xid(GtkWidget *widget)
+{
+ return GDK_WINDOW_XID(gtk_widget_get_window(widget));
+}
+
+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 GtkSalData final : public GenericUnixSalData
+{
+ GSource* m_pUserEvent;
+ osl::Mutex m_aDispatchMutex;
+ osl::Condition m_aDispatchCondition;
+ std::exception_ptr m_aException;
+
+ css::uno::Reference<css::accessibility::XAccessibleEventListener> m_xDocumentFocusListener;
+ DocumentFocusListener * m_pDocumentFocusListener;
+
+public:
+ GtkSalData( SalInstance *pInstance );
+ virtual ~GtkSalData() override;
+
+ 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 : public SalGenericDisplay
+{
+ GtkSalSystem* m_pSys;
+ GdkDisplay* m_pGdkDisplay;
+ o3tl::enumarray<PointerStyle, GdkCursor*> m_aCursors;
+ bool m_bStartupCompleted;
+ bool m_bX11Display;
+
+ GdkCursor* getFromSvg( OUString const & name, int nXHot, int nYHot );
+
+public:
+ GtkSalDisplay( GdkDisplay* pDisplay );
+ virtual ~GtkSalDisplay() override;
+
+ GdkDisplay* GetGdkDisplay() const { return m_pGdkDisplay; }
+ bool IsX11Display() const { return m_bX11Display; }
+
+ 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(); }
+ Size GetScreenSize( int nDisplayScreen );
+
+ GdkFilterReturn filterGdkEvent( GdkXEvent* sys_event );
+ void startupNotificationCompleted() { m_bStartupCompleted = true; }
+
+ void screenSizeChanged( GdkScreen const * );
+ void monitorsChanged( GdkScreen const * );
+
+ 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());
+}
+
+#endif // INCLUDED_VCL_INC_UNX_GTK_GTKDATA_HXX
+
+/* 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 000000000..e42e1649a
--- /dev/null
+++ b/vcl/inc/unx/gtk/gtkframe.hxx
@@ -0,0 +1,567 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_GTK_GTKFRAME_HXX
+#define INCLUDED_VCL_INC_UNX_GTK_GTKFRAME_HXX
+
+#include <cairo.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+#include <gtk/gtkx.h>
+#include <gdk/gdkkeysyms.h>
+
+#include <salframe.hxx>
+#include <vcl/idle.hxx>
+#include <vcl/sysdata.hxx>
+#include <unx/nativewindowhandleprovider.hxx>
+#include <unx/saltype.h>
+#include <unx/screensaverinhibitor.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>
+
+class GtkSalGraphics;
+class GtkSalDisplay;
+
+typedef sal_uIntPtr GdkNativeWindow;
+#define GDK_WINDOW_XWINDOW(o) GDK_WINDOW_XID(o)
+#define gdk_set_sm_client_id(i) gdk_x11_set_sm_client_id(i)
+#define gdk_window_foreign_new_for_display(a,b) gdk_x11_window_foreign_new_for_display(a,b)
+class GtkDropTarget;
+class GtkDragSource;
+class GtkDnDTransferable;
+
+class GtkSalMenu;
+
+class GtkSalFrame final : public SalFrame
+ , public NativeWindowHandleProvider
+{
+ struct IMHandler
+ {
+
+ // 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)
+ ;
+ }
+ };
+
+ GtkSalFrame* m_pFrame;
+ std::list< PreviousKeyPress > m_aPrevKeyPresses;
+ 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 );
+ bool handleKeyEvent( GdkEventKey* pEvent );
+ 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;
+ GtkEventBox* m_pEventBox;
+ GtkFixed* m_pFixedContainer;
+ GdkWindow* m_pForeignParent;
+ GdkNativeWindow m_aForeignParentWindow;
+ GdkWindow* m_pForeignTopLevel;
+ GdkNativeWindow m_aForeignTopLevelWindow;
+ SalFrameStyleFlags m_nStyle;
+ GtkSalFrame* m_pParent;
+ std::list< GtkSalFrame* > m_aChildren;
+ GdkWindowState m_nState;
+ SystemEnvData m_aSystemData;
+ std::unique_ptr<GtkSalGraphics> m_pGraphics;
+ bool m_bGraphics;
+ ModKeyFlags m_nKeyModifiers;
+ GdkCursor *m_pCurrentCursor;
+ PointerStyle m_ePointerStyle;
+ ScreenSaverInhibitor m_ScreenSaverInhibitor;
+ bool m_bFullscreen;
+ bool m_bSpanMonitorsWhenFullscreen;
+ 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;
+ long m_nWidthRequest;
+ long m_nHeightRequest;
+ cairo_region_t* m_pRegion;
+ GtkDropTarget* m_pDropTarget;
+ GtkDragSource* m_pDragSource;
+ bool m_bGeometryIsProvisional;
+ bool m_bIconSetWhileUnmapped;
+
+ GtkSalMenu* m_pSalMenu;
+
+#if ENABLE_DBUS && ENABLE_GIO
+ private:
+ friend void ensure_dbus_setup(GdkWindow* gdkWindow, GtkSalFrame* pSalFrame);
+ 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
+ static gboolean signalButton( GtkWidget*, GdkEventButton*, gpointer );
+ static void signalStyleUpdated(GtkWidget*, gpointer);
+ static gboolean signalDraw( GtkWidget*, cairo_t *cr, gpointer );
+ static void signalRealize(GtkWidget*, gpointer frame);
+ static void sizeAllocated(GtkWidget*, GdkRectangle *pAllocation, gpointer frame);
+ static gboolean signalTooltipQuery(GtkWidget*, gint x, gint y,
+ gboolean keyboard_mode, GtkTooltip *tooltip,
+ gpointer frame);
+ 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);
+
+ static void gestureSwipe(GtkGestureSwipe* gesture, gdouble velocity_x, gdouble velocity_y, gpointer frame);
+ static void gestureLongPress(GtkGestureLongPress* gesture, gdouble x, gdouble y, gpointer frame);
+ static gboolean signalFocus( GtkWidget*, GdkEventFocus*, gpointer );
+ static void signalSetFocus( GtkWindow* pWindow, GtkWidget* pWidget, gpointer frame );
+ static gboolean signalMap( GtkWidget*, GdkEvent*, gpointer );
+ static gboolean signalUnmap( GtkWidget*, GdkEvent*, gpointer );
+ static gboolean signalConfigure( GtkWidget*, GdkEventConfigure*, gpointer );
+ static gboolean signalMotion( GtkWidget*, GdkEventMotion*, gpointer );
+ static gboolean signalKey( GtkWidget*, GdkEventKey*, gpointer );
+ static gboolean signalDelete( GtkWidget*, GdkEvent*, gpointer );
+ static gboolean signalWindowState( GtkWidget*, GdkEvent*, gpointer );
+ static gboolean signalScroll( GtkWidget*, GdkEvent*, gpointer );
+ static gboolean signalCrossing( GtkWidget*, GdkEventCrossing*, gpointer );
+ static gboolean signalVisibility( GtkWidget*, GdkEventVisibility*, gpointer );
+ 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
+ );
+
+ static GdkNativeWindow findTopLevelSystemWindow( GdkNativeWindow aWindow );
+
+ 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(long nWidth, long nHeight);
+ //call gtk_widget_set_size_request
+ void widget_set_size_request(long nWidth, long nHeight);
+
+ void resizeWindow( long nWidth, long nHeight );
+ void moveWindow( long nX, 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);
+
+public:
+ cairo_surface_t* m_pSurface;
+ basegfx::B2IVector m_aFrameSize;
+ DamageHandler m_aDamageHandler;
+ std::vector<GdkEvent*> m_aPendingScrollEvents;
+ Idle m_aSmoothScrollIdle;
+ 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 m_pFixedContainer; }
+ GtkEventBox* getEventBox() const { return m_pEventBox; }
+ GtkWidget* getMouseEventWidget() const;
+ GtkGrid* getTopLevelGridWidget() const { return m_pTopLevelGrid; }
+ const SalX11Screen& getXScreenNumber() const { return m_nXScreen; }
+ int GetDisplayScreen() const { return maGeometry.nDisplayScreenNumber; }
+ void updateScreenNumber();
+
+ cairo_t* getCairoContext() const;
+ void damaged(sal_Int32 nExtentsLeft, sal_Int32 nExtentsTop,
+ sal_Int32 nExtentsRight, sal_Int32 nExtentsBottom) const;
+
+ void registerDropTarget(GtkDropTarget* pDropTarget)
+ {
+ assert(!m_pDropTarget);
+ m_pDropTarget = pDropTarget;
+ }
+
+ void deregisterDropTarget(GtkDropTarget const * pDropTarget)
+ {
+ assert(m_pDropTarget == pDropTarget); (void)pDropTarget;
+ m_pDropTarget = nullptr;
+ }
+
+ void registerDragSource(GtkDragSource* pDragSource)
+ {
+ assert(!m_pDragSource);
+ m_pDragSource = pDragSource;
+ }
+
+ void deregisterDragSource(GtkDragSource const * pDragSource)
+ {
+ assert(m_pDragSource == pDragSource); (void)pDragSource;
+ m_pDragSource = nullptr;
+ }
+
+ void startDrag(gint nButton, gint nDragOriginX, gint nDragOriginY,
+ GdkDragAction sourceActions, GtkTargetList* pTargetList);
+
+ void closePopup();
+
+ void addGrabLevel();
+ void removeGrabLevel();
+
+ void nopaint_container_resize_children(GtkContainer*);
+
+ void LaunchAsyncScroll(GdkEvent const * pEvent);
+ DECL_LINK(AsyncScroll, Timer *, void);
+
+ 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();
+ virtual void DrawMenuBar() override;
+ 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( long nWidth, long nHeight ) override;
+ virtual void SetMaxClientSize( long nWidth, long nHeight ) override;
+ virtual void SetPosSize( long nX, long nY, long nWidth, long nHeight, sal_uInt16 nFlags ) override;
+ virtual void GetClientSize( long& rWidth, long& rHeight ) override;
+ virtual void GetWorkArea( tools::Rectangle& rRect ) override;
+ virtual SalFrame* GetParent() const override;
+ virtual void SetWindowState( const SalFrameState* pState ) override;
+ virtual bool GetWindowState( SalFrameState* pState ) 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( long nX, 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;
+
+ // 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 bool 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( long nX, long nY, long nWidth, 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;
+
+ static GtkSalFrame *getFromWindow( GtkWidget *pWindow );
+
+ sal_uIntPtr GetNativeWindowHandle(GtkWidget *pWidget);
+ virtual sal_uIntPtr GetNativeWindowHandle() override;
+
+ //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;
+
+
+ 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);
+ static guint GetKeyValFor(GdkKeymap* pKeyMap, guint16 hardware_keycode, guint8 group);
+ static sal_uInt16 GetKeyModCode(guint nState);
+ static GdkEvent* makeFakeKeyPress(GtkWidget* pWidget);
+ static SalWheelMouseEvent GetWheelEvent(GdkEventScroll& rEvent);
+ static gboolean NativeWidgetHelpPressed(GtkAccelGroup*, GObject*, guint,
+ GdkModifierType, gpointer pFrame);
+ static OUString GetPreeditDetails(GtkIMContext* pIMContext, std::vector<ExtTextInputAttr>& rInputFlags, sal_Int32& rCursorPos, sal_uInt8& rCursorFlags);
+};
+
+#define OOO_TYPE_FIXED ooo_fixed_get_type()
+
+extern "C" {
+
+GType ooo_fixed_get_type();
+AtkObject* ooo_fixed_get_accessible(GtkWidget *obj);
+
+} // extern "C"
+
+#if !GTK_CHECK_VERSION(3, 20, 0)
+enum GdkDragCancelReason
+{
+ GDK_DRAG_CANCEL_NO_TARGET,
+ GDK_DRAG_CANCEL_USER_CANCELLED,
+ GDK_DRAG_CANCEL_ERROR
+};
+#endif
+
+#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
+
+#endif // INCLUDED_VCL_INC_UNX_GTK_GTKFRAME_HXX
+
+/* 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 000000000..430bab56d
--- /dev/null
+++ b/vcl/inc/unx/gtk/gtkgdi.hxx
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_UNX_GTK_GTKGDI_HXX
+#define INCLUDED_VCL_INC_UNX_GTK_GTKGDI_HXX
+
+#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,
+ SpinButtonEntry,
+ 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,
+};
+
+typedef void (*gtk_widget_path_iter_set_object_nameFunc)(GtkWidgetPath *, guint, const char*);
+
+class GtkSalGraphics : public SvpSalGraphics
+{
+ GtkSalFrame * const mpFrame;
+
+protected:
+ 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;
+ 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(gtk_widget_path_iter_set_object_nameFunc set_object_name, GtkControlPart ePart);
+ GtkStyleContext* createNewContext(GtkControlPart ePart, gtk_widget_path_iter_set_object_nameFunc set_object_name);
+ GtkStyleContext* createOldContext(GtkControlPart ePart);
+ GtkStyleContext* makeContext(GtkWidgetPath *pPath, GtkStyleContext *pParent);
+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 *mpToolbarSeperatorStyle;
+ static GtkStyleContext *mpCheckButtonStyle;
+ static GtkStyleContext *mpCheckButtonCheckStyle;
+ static GtkStyleContext *mpRadioButtonStyle;
+ static GtkStyleContext *mpRadioButtonRadioStyle;
+ static GtkStyleContext *mpSpinStyle;
+ static GtkStyleContext *mpSpinEntryStyle;
+ 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 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);
+
+
+ static bool style_loaded;
+};
+
+#else
+#include <unx/salgdi.h>
+
+class GdkX11Pixmap;
+class GtkSalGraphics : public X11SalGraphics
+{
+ GtkWidget *m_pWindow;
+ vcl::Region m_aClipRegion;
+
+public:
+ GtkSalGraphics(GtkSalFrame *, GtkWidget *window, SalX11Screen nXScreen);
+ virtual ~GtkSalGraphics() override;
+
+ GtkWidget* GetGtkWidget() const { return m_pWindow; }
+ GdkWindow* GetGdkWindow() const { return m_pWindow->window; }
+ GtkSalFrame* GetGtkFrame() const { return static_cast<GtkSalFrame*>(m_pFrame); }
+ void SetWindow( GtkWidget* window ) { m_pWindow = window; }
+
+ // will be set when UI theme was changed
+ static bool bThemeChanged;
+ static bool bNeedPixmapPaint;
+ static bool bNeedTwoPasses;
+
+protected:
+ // native widget methods
+ bool isNativeControlSupported(ControlType, ControlPart) 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& 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;
+ bool updateSettings(AllSettings&) override;
+
+public:
+ //helper methods for frame's UpdateSettings
+ static void refreshFontconfig( GtkSettings *pSettings );
+ static void signalSettingsNotify( GObject*, GParamSpec *pSpec, gpointer );
+
+ virtual bool setClipRegion( const vcl::Region& ) override;
+ virtual void ResetClipRegion() override;
+
+ // some themes set the background pixmap of our window EVERY time
+ // a control is painted; but presentation effects need
+ // the background set to None; workaround: set the background
+ // before copyBits
+ virtual void copyBits( const SalTwoRect& rPosAry,
+ SalGraphics* pSrcGraphics ) override;
+
+ virtual OUString getRenderBackendName() const override { return "gtk3"; }
+
+protected:
+
+ std::unique_ptr<GdkX11Pixmap> NWGetPixmapFromScreen( tools::Rectangle srcRect, int nBgColor = 0 );
+ bool NWRenderPixmapToScreen( GdkX11Pixmap* pPixmap, GdkX11Pixmap* pMask, tools::Rectangle dstRect );
+
+ bool DoDrawNativeControl( GdkDrawable* pDrawable,
+ ControlType nType,
+ ControlPart nPart,
+ const tools::Rectangle& aCtrlRect,
+ const std::vector< tools::Rectangle >& aClip,
+ ControlState nState,
+ const ImplControlValue& aValue,
+ ControlCacheKey& rControlCacheKey);
+
+ bool NWPaintGTKArrow( GdkDrawable* gdkDrawable,
+ const tools::Rectangle& rControlRectangle,
+ const std::vector< tools::Rectangle >& rClipList,
+ ControlState nState, const ImplControlValue& aValue );
+ bool NWPaintGTKListHeader( GdkDrawable* gdkDrawable,
+ const tools::Rectangle& rControlRectangle,
+ const std::vector< tools::Rectangle >& rClipList,
+ ControlState nState );
+ bool NWPaintGTKFixedLine( GdkDrawable* gdkDrawable, ControlPart nPart,
+ const tools::Rectangle& rControlRectangle );
+ bool NWPaintGTKFrame( GdkDrawable* gdkDrawable,
+ const tools::Rectangle& rControlRectangle,
+ const std::vector< tools::Rectangle >& rClipList,
+ const ImplControlValue& aValue );
+ bool NWPaintGTKWindowBackground( GdkDrawable* gdkDrawable,
+ const tools::Rectangle& rControlRectangle,
+ const std::vector< tools::Rectangle >& rClipList );
+ bool NWPaintGTKButtonReal( GtkWidget* button, GdkDrawable* gdkDrawable,
+ const tools::Rectangle& rControlRectangle,
+ const std::vector< tools::Rectangle >& rClipList,
+ ControlState nState );
+ bool NWPaintGTKButton( GdkDrawable* gdkDrawable,
+ const tools::Rectangle& rControlRectangle,
+ const std::vector< tools::Rectangle >& rClipList,
+ ControlState nState );
+ bool NWPaintGTKRadio( GdkDrawable* gdkDrawable,
+ const tools::Rectangle& rControlRectangle,
+ const std::vector< tools::Rectangle >& rClipList,
+ ControlState nState, const ImplControlValue& aValue );
+ bool NWPaintGTKCheck( GdkDrawable* gdkDrawable,
+ const tools::Rectangle& rControlRectangle,
+ const std::vector< tools::Rectangle >& rClipList,
+ ControlState nState, const ImplControlValue& aValue );
+ bool NWPaintGTKScrollbar( ControlPart nPart,
+ const tools::Rectangle& rControlRectangle,
+ ControlState nState, const ImplControlValue& aValue );
+ bool NWPaintGTKEditBox( GdkDrawable* gdkDrawable, ControlType nType,
+ const tools::Rectangle& rControlRectangle,
+ const std::vector< tools::Rectangle >& rClipList,
+ ControlState nState );
+ bool NWPaintGTKSpinBox(ControlType nType, ControlPart nPart,
+ const tools::Rectangle& rControlRectangle,
+ ControlState nState, const ImplControlValue& aValue,
+ ControlCacheKey& rControlCacheKey);
+ bool NWPaintGTKComboBox( GdkDrawable* gdkDrawable, ControlType nType, ControlPart nPart,
+ const tools::Rectangle& rControlRectangle,
+ const std::vector< tools::Rectangle >& rClipList,
+ ControlState nState );
+ bool NWPaintGTKTabItem( ControlType nType,
+ const tools::Rectangle& rControlRectangle,
+ ControlState nState, const ImplControlValue& aValue );
+ bool NWPaintGTKListBox( GdkDrawable* gdkDrawable, ControlPart nPart,
+ const tools::Rectangle& rControlRectangle,
+ const std::vector< tools::Rectangle >& rClipList,
+ ControlState nState );
+
+ bool NWPaintGTKToolbar( GdkDrawable* gdkDrawable, ControlPart nPart,
+ const tools::Rectangle& rControlRectangle,
+ const std::vector< tools::Rectangle >& rClipList,
+ ControlState nState, const ImplControlValue& aValue );
+ bool NWPaintGTKMenubar( GdkDrawable* gdkDrawable, ControlPart nPart,
+ const tools::Rectangle& rControlRectangle,
+ const std::vector< tools::Rectangle >& rClipList,
+ ControlState nState );
+ bool NWPaintGTKPopupMenu( GdkDrawable* gdkDrawable, ControlPart nPart,
+ const tools::Rectangle& rControlRectangle,
+ const std::vector< tools::Rectangle >& rClipList,
+ ControlState nState );
+ bool NWPaintGTKTooltip( GdkDrawable* gdkDrawable,
+ const tools::Rectangle& rControlRectangle,
+ const std::vector< tools::Rectangle >& rClipList );
+ bool NWPaintGTKProgress(
+ const tools::Rectangle& rControlRectangle,
+ const ImplControlValue& aValue );
+ bool NWPaintGTKSlider( GdkDrawable* gdkDrawable, ControlPart nPart,
+ const tools::Rectangle& rControlRectangle,
+ ControlState nState, const ImplControlValue& aValue );
+ bool NWPaintGTKListNode( GdkDrawable* gdkDrawable,
+ const tools::Rectangle& rControlRectangle,
+ ControlState nState, const ImplControlValue& aValue );
+};
+
+#endif // INCLUDED_VCL_INC_UNX_GTK_GTKGDI_HXX
+
+/* 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 000000000..3402aa6e3
--- /dev/null
+++ b/vcl/inc/unx/gtk/gtkinst.hxx
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_UNX_GTK_GTKINST_HXX
+#define INCLUDED_VCL_INC_UNX_GTK_GTKINST_HXX
+
+#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>
+
+namespace vcl
+{
+namespace unx
+{
+class GtkPrintWrapper;
+}
+}
+
+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;
+
+struct VclToGtkHelper
+{
+ std::vector<css::datatransfer::DataFlavor> aInfoToFlavor;
+ std::vector<GtkTargetEntry> FormatsToGtk(const css::uno::Sequence<css::datatransfer::DataFlavor> &rFormats);
+ void setSelectionData(const css::uno::Reference<css::datatransfer::XTransferable> &rTrans,
+ GtkSelectionData *selection_data, guint info);
+private:
+ GtkTargetEntry makeGtkTargetEntry(const css::datatransfer::DataFlavor& rFlavor);
+};
+
+class GtkTransferable : public cppu::WeakImplHelper<css::datatransfer::XTransferable>
+{
+protected:
+ std::map<OUString, GdkAtom> m_aMimeTypeToAtom;
+
+ std::vector<css::datatransfer::DataFlavor> getTransferDataFlavorsAsVector(GdkAtom *targets, gint n_targets);
+
+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 GtkDropTarget 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:
+ GtkDropTarget();
+ virtual ~GtkDropTarget() 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;
+ }
+
+ gboolean signalDragDrop(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, guint time);
+ gboolean signalDragMotion(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, guint time);
+ void signalDragDropReceived(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, GtkSelectionData* data, guint ttype, guint time);
+ void signalDragLeave(GtkWidget* pWidget, GdkDragContext* context, guint time);
+};
+
+class GtkDragSource 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:
+ GtkDragSource()
+ : 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);
+
+ std::vector<GtkTargetEntry> FormatsToGtk(const css::uno::Sequence<css::datatransfer::DataFlavor> &rFormats);
+
+ void setActiveDragSource();
+
+ virtual ~GtkDragSource() 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();
+ void dragEnd(GdkDragContext* context);
+ void dragDataGet(GtkSelectionData *data, guint info);
+
+ // 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 GtkDragSource* g_ActiveDragSource;
+ css::uno::Reference<css::datatransfer::XTransferable> const & GetTransferrable() const { return m_xTrans; }
+};
+
+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*,
+ long &nDX, 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 > CreateDragSource() override;
+ virtual css::uno::Reference< css::uno::XInterface > CreateDropTarget() override;
+ virtual OpenGLContext* CreateOpenGLContext() override;
+ virtual weld::Builder* CreateBuilder(weld::Widget* pParent, const OUString& rUIRoot, const OUString& rUIFile) override;
+ virtual weld::Builder* CreateInterimBuilder(vcl::Window* pParent, const OUString& rUIRoot, const OUString& rUIFile) 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 ();
+
+ std::shared_ptr<vcl::unx::GtkPrintWrapper> const & getPrintWrapper() const;
+
+ void* CreateGStreamerSink(const SystemChildWindow*) override;
+
+private:
+ GtkSalTimer *m_pTimer;
+ std::unordered_map< GdkAtom, css::uno::Reference<css::uno::XInterface> > m_aClipboards;
+ bool IsTimerExpired();
+ bool bNeedsInit;
+ cairo_font_options_t* m_pLastCairoFontOptions;
+
+ mutable std::shared_ptr<vcl::unx::GtkPrintWrapper> m_xPrintWrapper;
+};
+
+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);
+
+#endif // INCLUDED_VCL_INC_UNX_GTK_GTKINST_HXX
+
+/* 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 000000000..d1a5226cd
--- /dev/null
+++ b/vcl/inc/unx/gtk/gtkobject.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_UNX_GTK_GTKOBJECT_HXX
+#define INCLUDED_VCL_INC_UNX_GTK_GTKOBJECT_HXX
+
+#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( long nX, long nY, long nWidth, long nHeight ) override;
+
+ virtual void SetForwardKey( bool bEnable ) override;
+
+ virtual const SystemEnvData* GetSystemData() const override;
+
+ virtual Size GetOptimalSize() const override;
+
+private:
+ // signals
+ static gboolean signalButton( GtkWidget*, GdkEventButton*, gpointer );
+ static gboolean signalFocus( GtkWidget*, GdkEventFocus*, gpointer );
+};
+
+// 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( long nX, long nY, long nWidth, 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;
+
+ // signals
+ static gboolean signalScroll( GtkWidget*, GdkEvent*, gpointer );
+ static void signalDestroy( GtkWidget*, gpointer );
+
+ bool signal_scroll(GtkWidget* pScrolledWindow, GdkEvent* pEvent);
+
+ void ApplyClipRegion();
+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( long nX, long nY, long nWidth, long nHeight ) override;
+ virtual void Show( bool bVisible ) override;
+ virtual void Reparent(SalFrame* pFrame) override;
+};
+
+
+#endif // INCLUDED_VCL_INC_UNX_GTK_GTKOBJECT_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/gtk/gtkprintwrapper.hxx b/vcl/inc/unx/gtk/gtkprintwrapper.hxx
new file mode 100644
index 000000000..589c800d7
--- /dev/null
+++ b/vcl/inc/unx/gtk/gtkprintwrapper.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/.
+ */
+
+#ifndef INCLUDED_VCL_UNX_GTK_INC_GTKPRINTWRAPPER_HXX
+#define INCLUDED_VCL_UNX_GTK_INC_GTKPRINTWRAPPER_HXX
+
+#include <gtk/gtk.h>
+
+#include <gtk/gtkunixprint.h>
+
+#include <osl/module.hxx>
+#include <sal/types.h>
+
+namespace vcl
+{
+namespace unx
+{
+
+class GtkPrintWrapper
+{
+private:
+ GtkPrintWrapper(const GtkPrintWrapper&) = delete;
+ GtkPrintWrapper& operator=(const GtkPrintWrapper&) = delete;
+public:
+ GtkPrintWrapper();
+ ~GtkPrintWrapper();
+
+ bool supportsPrinting() const;
+ bool supportsPrintSelection() const;
+
+ // general printing support, since 2.10.0
+ GtkPageSetup* page_setup_new() const;
+
+ GtkPrintJob* print_job_new(const gchar* title, GtkPrinter* printer, GtkPrintSettings* settings, GtkPageSetup* page_setup) const;
+ void print_job_send(GtkPrintJob* job, GtkPrintJobCompleteFunc callback, gpointer user_data, GDestroyNotify dnotify) const;
+ gboolean print_job_set_source_file(GtkPrintJob* job, const gchar* filename, GError** error) const;
+
+ const gchar* print_settings_get(GtkPrintSettings* settings, const gchar* key) const;
+ gboolean print_settings_get_collate(GtkPrintSettings* settings) const;
+ void print_settings_set_collate(GtkPrintSettings* settings, gboolean collate) const;
+ gint print_settings_get_n_copies(GtkPrintSettings* settings) const;
+ void print_settings_set_n_copies(GtkPrintSettings* settings, gint num_copies) const;
+ GtkPageRange* print_settings_get_page_ranges(GtkPrintSettings* settings, gint* num_ranges) const;
+ void print_settings_set_print_pages(GtkPrintSettings* settings, GtkPrintPages pages) const;
+
+ GtkWidget* print_unix_dialog_new() const;
+ void print_unix_dialog_add_custom_tab(GtkPrintUnixDialog* dialog, GtkWidget* child, GtkWidget* tab_label) const;
+ GtkPrinter* print_unix_dialog_get_selected_printer(GtkPrintUnixDialog* dialog) const;
+ void print_unix_dialog_set_manual_capabilities(GtkPrintUnixDialog* dialog, GtkPrintCapabilities capabilities) const;
+ GtkPrintSettings* print_unix_dialog_get_settings(GtkPrintUnixDialog* dialog) const;
+ void print_unix_dialog_set_settings(GtkPrintUnixDialog* dialog, GtkPrintSettings* settings) const;
+
+ // print selection support, since 2.17.4
+ void print_unix_dialog_set_support_selection(GtkPrintUnixDialog* dialog, gboolean support_selection) const;
+ void print_unix_dialog_set_has_selection(GtkPrintUnixDialog* dialog, gboolean has_selection) const;
+};
+
+}
+}
+
+#endif // INCLUDED_VCL_UNX_GTK_INC_GTKPRINTWRAPPER_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/gtk/gtkprn.hxx b/vcl/inc/unx/gtk/gtkprn.hxx
new file mode 100644
index 000000000..aa24f0e4d
--- /dev/null
+++ b/vcl/inc/unx/gtk/gtkprn.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/.
+ */
+
+#ifndef INCLUDED_VCL_INC_UNX_GTK_GTKPRN_HXX
+#define INCLUDED_VCL_INC_UNX_GTK_GTKPRN_HXX
+
+#include <unx/genprn.h>
+
+#include <memory>
+
+struct GtkSalPrinter_Impl;
+
+class GtkSalPrinter final : public PspSalPrinter
+{
+public:
+ GtkSalPrinter(SalInfoPrinter* i_pInfoPrinter);
+
+ ~GtkSalPrinter() override;
+
+ using PspSalPrinter::StartJob;
+ virtual bool StartJob(
+ const OUString* i_pFileName, const OUString& i_rJobName,
+ const OUString& i_rAppName, ImplJobSetup* io_pSetupData,
+ vcl::PrinterController& io_rController) override;
+ virtual bool EndJob() override;
+
+private:
+ bool impl_doJob(
+ const OUString* i_pFileName, const OUString& i_rJobName,
+ const OUString& i_rAppName, ImplJobSetup* io_pSetupData,
+ bool i_bCollate, vcl::PrinterController& io_rController);
+
+private:
+ std::unique_ptr<GtkSalPrinter_Impl> m_xImpl;
+};
+
+class GtkSalInfoPrinter final : public PspSalInfoPrinter
+{
+public:
+ sal_uInt32 GetCapabilities(const ImplJobSetup* i_pSetupData, PrinterCapType i_nType) override;
+};
+
+#endif // INCLUDED_VCL_INC_UNX_GTK_GTKPRN_HXX
+
+/* 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 000000000..4bac1fe53
--- /dev/null
+++ b/vcl/inc/unx/gtk/gtksalmenu.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/.
+ */
+
+#ifndef INCLUDED_VCL_INC_UNX_GTK_GTKSALMENU_HXX
+#define INCLUDED_VCL_INC_UNX_GTK_GTKSALMENU_HXX
+
+#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 : public SalMenu
+{
+private:
+ std::vector< GtkSalMenuItem* > maItems;
+ 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::TempFile> mxPersonaImage;
+ BitmapEx maPersonaBitmap;
+ GtkWidget* mpMenuAllowShrinkWidget;
+ GtkWidget* mpMenuBarWidget;
+ 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);
+
+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, const OUString& 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();
+ virtual void Update() override; // Update this menu only.
+ // Update full menu hierarchy from this menu.
+ void UpdateFull () { ActivateAllSubmenus(mpVCLMenu); Update(); }
+ // Clear ActionGroup and MenuModel from full menu hierarchy
+ void ClearActionGroupAndMenuModel();
+ GtkSalMenu* GetTopLevel();
+ void SetNeedsUpdate();
+
+ GtkWidget* GetMenuBarContainerWidget() const { return mpMenuBarContainerWidget; }
+
+ void CreateMenuBarWidget();
+ void DestroyMenuBarWidget();
+ gboolean SignalKey(GdkEventKey const * pEvent);
+ void ReturnFocus();
+
+ virtual bool ShowNativePopupMenu(FloatingWindow * pWin, const tools::Rectangle& rRect, FloatWinPopupFlags nFlags) override;
+ virtual void ShowCloseButton(bool bShow) override;
+ virtual bool CanGetFocus() const override;
+ virtual bool TakeFocus() override;
+ virtual int GetMenuBarHeight() const override;
+ virtual void ApplyPersona() override;
+};
+
+class GtkSalMenuItem : 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.
+};
+
+#endif // INCLUDED_VCL_INC_UNX_GTK_GTKSALMENU_HXX
+
+/* 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 000000000..8775f5af0
--- /dev/null
+++ b/vcl/inc/unx/gtk/gtksys.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_UNX_GTK_GTKSYS_HXX
+#define INCLUDED_VCL_INC_UNX_GTK_GTKSYS_HXX
+
+#include <unx/gensys.h>
+#include <gtk/gtk.h>
+#include <unx/saltype.h>
+#include <deque>
+
+class GtkSalSystem final : public SalGenericSystem
+{
+ typedef std::deque<std::pair<GdkScreen*, int> > ScreenMonitors_t;
+
+ GdkDisplay *mpDisplay;
+ // Number of monitors for every active screen.
+ ScreenMonitors_t maScreenMonitors;
+public:
+ GtkSalSystem();
+ virtual ~GtkSalSystem() override;
+ static GtkSalSystem *GetSingleton();
+
+ virtual bool IsUnifiedDisplay() override;
+ virtual unsigned int GetDisplayScreenCount() override;
+ virtual unsigned int GetDisplayBuiltInScreen() override;
+ virtual tools::Rectangle 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);
+ 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 // INCLUDED_VCL_INC_UNX_GTK_GTKSYS_HXX
+
+/* 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 000000000..eb80e12fe
--- /dev/null
+++ b/vcl/inc/unx/gtk/hudawareness.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/.
+ */
+
+#ifndef INCLUDED_VCL_INC_UNX_GTK_HUDAWARENESS_H
+#define INCLUDED_VCL_INC_UNX_GTK_HUDAWARENESS_H
+
+#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
+
+#endif // INCLUDED_VCL_INC_UNX_GTK_HUDAWARENESS_H
diff --git a/vcl/inc/unx/helper.hxx b/vcl/inc/unx/helper.hxx
new file mode 100644
index 000000000..ac018b374
--- /dev/null
+++ b/vcl/inc/unx/helper.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_INC_UNX_HELPER_HXX
+#define INCLUDED_VCL_INC_UNX_HELPER_HXX
+
+#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
+
+
+#endif // INCLUDED_VCL_INC_UNX_HELPER_HXX
+
+/* 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 000000000..d6a505e0d
--- /dev/null
+++ b/vcl/inc/unx/i18n_cb.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_UNX_I18N_CB_HXX
+#define INCLUDED_VCL_INC_UNX_I18N_CB_HXX
+
+#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)
+ {
+ }
+};
+
+#endif // INCLUDED_VCL_INC_UNX_I18N_CB_HXX
+
+/* 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 000000000..c9302c5eb
--- /dev/null
+++ b/vcl/inc/unx/i18n_ic.hxx
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_VCL_INC_UNX_I18N_IC_HXX
+#define INCLUDED_VCL_INC_UNX_I18N_IC_HXX
+
+#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() { return mbUseable; }
+ bool IsPreeditMode() const { return maClientData.eState == PreeditStatus::Active; }
+ XIC GetContext() { 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();
+};
+
+#endif // INCLUDED_VCL_INC_UNX_I18N_IC_HXX
+
+/* 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 000000000..2539ab2da
--- /dev/null
+++ b/vcl/inc/unx/i18n_im.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_INC_UNX_I18N_IM_HXX
+#define INCLUDED_VCL_INC_UNX_I18N_IM_HXX
+
+#include <X11/Xlib.h>
+
+#include <vclpluginapi.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() { return mbUseable; }
+ XIM GetMethod() { return maMethod; }
+ void HandleDestroyIM();
+ void CreateMethod( Display *pDisplay );
+ XIMStyles *GetSupportedStyles() { return mpStyles; }
+ void SetLocale();
+ bool FilterEvent( XEvent *pEvent, ::Window window );
+
+ SalI18N_InputMethod();
+ ~SalI18N_InputMethod();
+};
+
+#endif // INCLUDED_VCL_INC_UNX_I18N_IM_HXX
+
+/* 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 000000000..318908739
--- /dev/null
+++ b/vcl/inc/unx/i18n_keysym.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_UNX_I18N_KEYSYM_HXX
+#define INCLUDED_VCL_INC_UNX_I18N_KEYSYM_HXX
+
+#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);
+
+#endif // INCLUDED_VCL_INC_UNX_I18N_KEYSYM_HXX
+
+/* 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 000000000..473251b48
--- /dev/null
+++ b/vcl/inc/unx/i18n_xkb.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_UNX_I18N_XKB_HXX
+#define INCLUDED_VCL_INC_UNX_I18N_XKB_HXX
+
+#include <X11/Xlib.h>
+
+#include <vclpluginapi.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;
+}
+
+#endif // INCLUDED_VCL_INC_UNX_I18N_XKB_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/nativewindowhandleprovider.hxx b/vcl/inc/unx/nativewindowhandleprovider.hxx
new file mode 100644
index 000000000..1d85cb4cf
--- /dev/null
+++ b/vcl/inc/unx/nativewindowhandleprovider.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/.
+ */
+
+#ifndef INCLUDED_VCL_UNX_NATIVEWINDOWHANDLEPROVIDER
+#define INCLUDED_VCL_UNX_NATIVEWINDOWHANDLEPROVIDER
+
+#include <vcl/dllapi.h>
+
+class VCL_PLUGIN_PUBLIC NativeWindowHandleProvider
+{
+public:
+ virtual ~NativeWindowHandleProvider();
+
+ virtual sal_uIntPtr GetNativeWindowHandle() = 0;
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/printergfx.hxx b/vcl/inc/unx/printergfx.hxx
new file mode 100644
index 000000000..fcb8a9241
--- /dev/null
+++ b/vcl/inc/unx/printergfx.hxx
@@ -0,0 +1,347 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_GENERIC_PRINTERGFX_HXX
+#define INCLUDED_VCL_INC_GENERIC_PRINTERGFX_HXX
+
+#include <osl/file.hxx>
+#include <tools/gen.hxx>
+#include <vcl/dllapi.h>
+#include <vcl/glyphitem.hxx>
+
+#include <impglyphitem.hxx>
+
+#include <list>
+#include <vector>
+
+enum class PolyFlags : sal_uInt8;
+
+namespace psp {
+
+struct JobData;
+
+/*
+ * lightweight container to handle RGB values
+ */
+
+class PrinterColor
+{
+public:
+
+ enum ColorSpace { eInvalid, eRGB };
+
+private:
+
+ sal_uInt8 mnRed;
+ sal_uInt8 mnGreen;
+ sal_uInt8 mnBlue;
+ ColorSpace meColorspace;
+
+public:
+
+ PrinterColor()
+ : mnRed(0)
+ , mnGreen(0)
+ , mnBlue(0)
+ , meColorspace(eInvalid)
+ {}
+ PrinterColor (sal_uInt16 nRed, sal_uInt16 nGreen,
+ sal_uInt16 nBlue) :
+ mnRed (nRed),
+ mnGreen (nGreen),
+ mnBlue (nBlue),
+ meColorspace (eRGB)
+ {}
+ PrinterColor (sal_uInt32 nRGB) :
+ mnRed ((nRGB & 0x00ff0000) >> 16),
+ mnGreen ((nRGB & 0x0000ff00) >> 8),
+ mnBlue ((nRGB & 0x000000ff) ),
+ meColorspace (eRGB)
+ {}
+
+ bool Is () const
+ { return meColorspace != eInvalid; }
+
+ sal_uInt16 GetRed () const
+ { return mnRed; }
+ sal_uInt16 GetGreen () const
+ { return mnGreen; }
+ sal_uInt16 GetBlue () const
+ { return mnBlue; }
+ bool operator== (const PrinterColor& aColor) const
+ {
+ return aColor.Is() && Is()
+ && mnRed == aColor.mnRed
+ && mnGreen == aColor.mnGreen
+ && mnBlue == aColor.mnBlue;
+ }
+ bool operator!= (const PrinterColor& aColor) const
+ { return ! (aColor==*this); }
+
+ PrinterColor& operator= (sal_uInt32 nRGB)
+ {
+ meColorspace = eRGB;
+ mnBlue = (nRGB & 0x000000ff);
+ mnGreen = (nRGB & 0x0000ff00) >> 8;
+ mnRed = (nRGB & 0x00ff0000) >> 16;
+
+ return *this;
+ }
+};
+
+class GlyphSet;
+class PrinterJob;
+class PrintFontManager;
+struct CharacterMetric;
+
+/*
+ * Bitmap Interface, this has to be filled with your actual bitmap implementation
+ * sample implementations can be found in:
+ * psprint/workben/cui/pspdem.cxx
+ * vcl/unx/source/gdi/salgdi2.cxx
+ */
+
+class PrinterBmp
+{
+public:
+
+ virtual ~PrinterBmp () = 0;
+ virtual sal_uInt32 GetPaletteColor (sal_uInt32 nIdx) const = 0;
+ virtual sal_uInt32 GetPaletteEntryCount () const = 0;
+ virtual sal_uInt32 GetPixelRGB (sal_uInt32 nRow, sal_uInt32 nColumn) const = 0;
+ virtual sal_uInt8 GetPixelGray (sal_uInt32 nRow, sal_uInt32 nColumn) const = 0;
+ virtual sal_uInt8 GetPixelIdx (sal_uInt32 nRow, sal_uInt32 nColumn) const = 0;
+ virtual sal_uInt32 GetDepth () const = 0;
+};
+
+enum class ImageType {
+ TrueColorImage,
+ MonochromeImage,
+ PaletteImage,
+ GrayScaleImage
+};
+
+/*
+ * printer raster operations
+ */
+
+struct GraphicsStatus
+{
+ OString maFont;
+ rtl_TextEncoding maEncoding;
+ bool mbArtItalic;
+ bool mbArtBold;
+ sal_Int32 mnTextHeight;
+ sal_Int32 mnTextWidth;
+ PrinterColor maColor;
+ double mfLineWidth;
+
+ GraphicsStatus();
+};
+
+class PrinterGfx
+{
+private:
+
+ /* common settings */
+
+ double mfScaleX;
+ double mfScaleY;
+
+ sal_uInt32 mnDpi;
+ sal_uInt16 mnDepth;
+
+ sal_uInt16 mnPSLevel;
+ bool mbColor;
+ bool mbUploadPS42Fonts;
+
+ osl::File* mpPageBody;
+
+ /* text/font related data, for a type1 font it has to be checked
+ whether this font has already been downloaded. A TrueType font
+ will be converted into one or more Type3 fonts, containing glyphs
+ in no particular order. In addition to the existence of the
+ glyph in one of the subfonts, the mapping from unicode to the
+ glyph has to be remembered */
+
+ std::vector< GlyphSet > maPS3Font;
+
+ sal_Int32 mnFontID;
+ sal_Int32 mnTextAngle;
+ bool mbTextVertical;
+ PrintFontManager& mrFontMgr;
+
+ /* bitmap drawing implementation */
+
+ void DrawPS1GrayImage (const PrinterBmp& rBitmap, const tools::Rectangle& rArea);
+ void writePS2ImageHeader (const tools::Rectangle& rArea, psp::ImageType nType);
+ void writePS2Colorspace (const PrinterBmp& rBitmap, psp::ImageType nType);
+ void DrawPS2GrayImage (const PrinterBmp& rBitmap, const tools::Rectangle& rArea);
+ void DrawPS2PaletteImage (const PrinterBmp& rBitmap, const tools::Rectangle& rArea);
+ void DrawPS2TrueColorImage (const PrinterBmp& rBitmap, const tools::Rectangle& rArea);
+ void DrawPS2MonoImage (const PrinterBmp& rBitmap, const tools::Rectangle& rArea);
+
+ /* clip region */
+
+ std::list< tools::Rectangle > maClipRegion;
+ bool JoinVerticalClipRectangles( std::list< tools::Rectangle >::iterator& it,
+ Point& aOldPoint, sal_Int32& nColumn );
+
+ /* color settings */
+ PrinterColor maFillColor;
+ PrinterColor maTextColor;
+ PrinterColor maLineColor;
+
+ /* graphics state */
+ GraphicsStatus maVirtualStatus;
+ std::list< GraphicsStatus > maGraphicsStack;
+ GraphicsStatus& currentState() { return maGraphicsStack.front(); }
+
+public:
+ /* graphics status update */
+ void PSSetColor ();
+ void PSSetLineWidth ();
+ void PSSetFont ();
+
+ /* graphics status functions */
+ void PSSetColor (const PrinterColor& rColor)
+ { maVirtualStatus.maColor = rColor; }
+
+ void PSSetFont (const OString& rName,
+ rtl_TextEncoding nEncoding)
+ { maVirtualStatus.maFont = rName; maVirtualStatus.maEncoding = nEncoding; }
+
+ /* graphics status stack */
+ void PSGSave ();
+ void PSGRestore ();
+
+ /* PS helpers */
+ enum pspath_t { moveto = 0, lineto = 1 };
+ void PSBinLineTo (const Point& rCurrent, Point& rOld,
+ sal_Int32& nColumn);
+ void PSBinMoveTo (const Point& rCurrent, Point& rOld,
+ sal_Int32& nColumn);
+ void PSBinStartPath ();
+ void PSBinEndPath ();
+ void PSBinCurrentPath (sal_uInt32 nPoints, const Point* pPath);
+ void PSBinPath (const Point& rCurrent, Point& rOld,
+ pspath_t eType, sal_Int32& nColumn);
+
+ void PSRotate (sal_Int32 nAngle);
+ void PSTranslate (const Point& rPoint);
+ void PSMoveTo (const Point& rPoint);
+ void PSScale (double fScaleX, double fScaleY);
+ void PSLineTo(const Point& rPoint );
+ void PSPointOp (const Point& rPoint, const char* pOperator);
+ void PSHexString (const unsigned char* pString, sal_Int16 nLen);
+ void PSShowGlyph (const unsigned char nGlyphId);
+
+ void OnEndJob ();
+ void writeResources( osl::File* pFile, std::vector< OString >& rSuppliedFonts );
+ PrintFontManager& GetFontMgr () { return mrFontMgr; }
+
+ void drawGlyph(const Point& rPoint,
+ sal_GlyphId aGlyphId);
+public:
+ PrinterGfx();
+ ~PrinterGfx();
+ void Init (PrinterJob &rPrinterSpec);
+ void Init (const JobData& rData);
+ void Clear();
+
+ // query depth
+ sal_uInt16 GetBitCount () const { return mnDepth;}
+
+ // clip region
+ void ResetClipRegion ();
+ void BeginSetClipRegion();
+ void UnionClipRegion (sal_Int32 nX, sal_Int32 nY,
+ sal_Int32 nDX, sal_Int32 nDY);
+ void EndSetClipRegion ();
+
+ // set xy color
+ void SetLineColor (const PrinterColor& rLineColor = PrinterColor())
+ { maLineColor = rLineColor; }
+ void SetFillColor (const PrinterColor& rFillColor = PrinterColor())
+ { maFillColor = rFillColor; }
+
+ // drawing primitives
+ void DrawPixel (const Point& rPoint, const PrinterColor& rPixelColor);
+ void DrawPixel (const Point& rPoint)
+ { DrawPixel (rPoint, maLineColor); }
+ void DrawLine (const Point& rFrom, const Point& rTo);
+ void DrawRect (const tools::Rectangle& rRectangle);
+ void DrawPolyLine (sal_uInt32 nPoints, const Point* pPath );
+ void DrawPolygon (sal_uInt32 nPoints, const Point* pPath);
+ void DrawPolyPolygon (sal_uInt32 nPoly,
+ const sal_uInt32 *pPolygonSize,
+ const Point** pPolygonList);
+ void DrawPolyLineBezier (sal_uInt32 nPoints,
+ const Point* pPath,
+ const PolyFlags* pFlgAry );
+ void DrawPolygonBezier (sal_uInt32 nPoints,
+ const Point* pPath,
+ const PolyFlags* pFlgAry);
+ void DrawPolyPolygonBezier (sal_uInt32 nPoly,
+ const sal_uInt32* pPoints,
+ const Point* const* pPtAry,
+ const PolyFlags* const* pFlgAry);
+
+ // eps
+ bool DrawEPS ( const tools::Rectangle& rBoundingBox, void* pPtr, sal_uInt32 nSize);
+
+ // image drawing
+ void DrawBitmap (const tools::Rectangle& rDest, const tools::Rectangle& rSrc,
+ const PrinterBmp& rBitmap);
+
+ // font and text handling
+ void SetFont (
+ sal_Int32 nFontID,
+ sal_Int32 nPointHeight,
+ sal_Int32 nPointWidth,
+ sal_Int32 nAngle,
+ bool bVertical,
+ bool bArtItalic,
+ bool bArtBold
+ );
+ sal_Int32 GetFontID () const
+ { return mnFontID; }
+ bool GetFontVertical() const
+ { return mbTextVertical; }
+ sal_Int32 GetFontHeight () const
+ { return maVirtualStatus.mnTextHeight; }
+ sal_Int32 GetFontWidth () const
+ { return maVirtualStatus.mnTextWidth; }
+ bool GetArtificialItalic() const
+ { return maVirtualStatus.mbArtItalic; }
+ bool GetArtificialBold() const
+ { return maVirtualStatus.mbArtBold; }
+ void SetTextColor (PrinterColor const & rTextColor)
+ { maTextColor = rTextColor; }
+
+ void DrawGlyph(const Point& rPoint,
+ const GlyphItem& rGlyph);
+
+};
+
+} /* namespace psp */
+
+#endif // INCLUDED_VCL_INC_GENERIC_PRINTERGFX_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/printerjob.hxx b/vcl/inc/unx/printerjob.hxx
new file mode 100644
index 000000000..33f92abc7
--- /dev/null
+++ b/vcl/inc/unx/printerjob.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_GENERIC_PRINTERJOB_HXX
+#define INCLUDED_VCL_INC_GENERIC_PRINTERJOB_HXX
+
+#include <jobdata.hxx>
+#include <osl/file.hxx>
+
+#include <vector>
+
+namespace psp {
+
+class PrinterGfx;
+
+class PrinterJob
+{
+private:
+ OUString maSpoolDirName;
+ OUString maFileName; // empty: spool to command, else spool to named file
+ OUString maJobTitle;
+ int mnFileMode;
+
+ std::unique_ptr<osl::File> mpJobHeader;
+ std::unique_ptr<osl::File> mpJobTrailer;
+
+ std::vector< std::unique_ptr<osl::File> > maPageVector;
+ std::vector< std::unique_ptr<osl::File> > maHeaderVector;
+
+ JobData m_aDocumentJobData;
+ JobData m_aLastJobData;
+ PrinterGfx* m_pGraphics;
+
+ sal_uInt32 mnResolution;
+
+ sal_uInt32 mnWidthPt;
+ sal_uInt32 mnHeightPt;
+ sal_uInt32 mnMaxWidthPt;
+ sal_uInt32 mnMaxHeightPt;
+
+ int mnLandscapes;
+ int mnPortraits;
+
+ sal_uInt32 mnLMarginPt;
+ sal_uInt32 mnRMarginPt;
+ sal_uInt32 mnTMarginPt;
+ sal_uInt32 mnBMarginPt;
+
+ double mfXScale;
+ double mfYScale;
+
+ bool m_bQuickJob;
+
+private:
+ std::unique_ptr<osl::File> CreateSpoolFile (const OUString& rName,
+ const OUString& rExtension);
+ void InitPaperSize (const JobData& rJobSetup);
+
+ bool writeFeatureList( osl::File* pFile, const JobData&, bool bDocumentSetup );
+ bool writeSetup( osl::File* pFile, const JobData& );
+ bool writePageSetup( osl::File* pFile, const JobData&, bool bWriteFeatures );
+ static void writeJobPatch( osl::File* File, const JobData& );
+ static void writeProlog (osl::File* pFile, const JobData& );
+
+public: // for usage in PrinterGfx
+ sal_uInt32 GetResolution () const { return mnResolution; }
+ void GetScale (double &rXScale, double &rYScale) const;
+ sal_uInt16 GetDepth () const;
+ sal_uInt16 GetPostscriptLevel (const JobData *pJobData = nullptr) const;
+ bool IsColorPrinter () const;
+
+ osl::File* GetCurrentPageBody ();
+
+ const OUString& GetPrinterName() const { return m_aLastJobData.m_aPrinterName; }
+
+public:
+ PrinterJob ();
+ ~PrinterJob ();
+
+ /* rFileName: if length is greater than 0 save resulting PostScript
+ * to named file.
+ * nMode: only meaningful when saving to file: if nonzero, try
+ * to impose the mode on the resulting file's inode; for nonexistent
+ * files use open, for existent files try a chmod
+ * rJobName: text to appear in the %%Title comment
+ * rAppName: text to appear in the %%Creator comment
+ * rSetupData: JobData that apply to this job
+ * pGraphics: the graphics used to print this job;
+ * this graphics must live until EndJob() has returned
+ * bIsQuickJob: the job was started as "direct print" meaning
+ * the quick command for spooling should be used instead
+ * of the normal command
+ */
+ bool StartJob (const OUString& rFileName,
+ int nMode,
+ const OUString& rJobName,
+ const OUString& rAppName,
+ const JobData& rSetupData,
+ PrinterGfx* pGraphics,
+ bool bIsQuickJob
+ );
+ bool EndJob ();
+
+ void StartPage (const JobData& rJobSetup);
+ void EndPage ();
+};
+
+} // namespace psp
+
+#endif // INCLUDED_VCL_INC_GENERIC_PRINTERJOB_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/salbmp.h b/vcl/inc/unx/salbmp.h
new file mode 100644
index 000000000..fc8450129
--- /dev/null
+++ b/vcl/inc/unx/salbmp.h
@@ -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_UNX_SALBMP_H
+#define INCLUDED_VCL_INC_UNX_SALBMP_H
+
+#include <X11/Xlib.h>
+
+#include <vcl/salgtype.hxx>
+#include <unx/saldisp.hxx>
+#include <salbmp.hxx>
+#include <vclpluginapi.h>
+
+struct BitmapBuffer;
+class BitmapPalette;
+class SalGraphics;
+class ImplSalDDB;
+class ImplSalBitmapCache;
+
+
+class X11SalBitmap final : public SalBitmap
+{
+private:
+
+ static std::unique_ptr<BitmapBuffer>
+ ImplCreateDIB(
+ const Size& rSize,
+ sal_uInt16 nBitCount,
+ const BitmapPalette& rPal
+ );
+
+ static std::unique_ptr<BitmapBuffer>
+ ImplCreateDIB(
+ Drawable aDrawable,
+ SalX11Screen nXScreen,
+ long nDrawableDepth,
+ long nX,
+ long nY,
+ long nWidth,
+ long nHeight,
+ bool bGrey
+ );
+
+public:
+
+ static ImplSalBitmapCache* mpCache;
+ static unsigned int mnCacheInstCount;
+
+ static void ImplCreateCache();
+ static void ImplDestroyCache();
+ void ImplRemovedFromCache();
+
+private:
+
+ std::unique_ptr<BitmapBuffer> mpDIB;
+ mutable std::unique_ptr<ImplSalDDB> mpDDB;
+ bool mbGrey;
+
+public:
+
+ bool ImplCreateFromDrawable(
+ Drawable aDrawable,
+ SalX11Screen nXScreen,
+ long nDrawableDepth,
+ long nX,
+ long nY,
+ long nWidth,
+ long nHeight
+ );
+
+ XImage* ImplCreateXImage(
+ SalDisplay const * pSalDisp,
+ SalX11Screen nXScreen,
+ long nDepth,
+ const SalTwoRect& rTwoRect
+ ) const;
+
+ ImplSalDDB* ImplGetDDB(
+ Drawable,
+ SalX11Screen nXScreen,
+ long nDrawableDepth,
+ const SalTwoRect&
+ ) const;
+
+ void ImplDraw(
+ Drawable aDrawable,
+ SalX11Screen nXScreen,
+ long nDrawableDepth,
+ const SalTwoRect& rTwoRect,
+ const GC& rGC
+ ) const;
+
+public:
+
+ X11SalBitmap();
+ virtual ~X11SalBitmap() override;
+
+ // override pure virtual methods
+ virtual bool Create(
+ const Size& rSize,
+ sal_uInt16 nBitCount,
+ 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,
+ sal_uInt16 nNewBitCount
+ ) 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;
+ 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;
+};
+
+
+class ImplSalDDB
+{
+private:
+
+ Pixmap maPixmap;
+ SalTwoRect maTwoRect;
+ long mnDepth;
+ SalX11Screen mnXScreen;
+
+ static void ImplDraw(
+ Drawable aSrcDrawable,
+ long nSrcDrawableDepth,
+ Drawable aDstDrawable,
+ long nSrcX,
+ long nSrcY,
+ long nDestWidth,
+ long nDestHeight,
+ long nDestX,
+ long nDestY,
+ const GC& rGC
+ );
+
+public:
+
+ ImplSalDDB(
+ XImage* pImage,
+ Drawable aDrawable,
+ SalX11Screen nXScreen,
+ const SalTwoRect& rTwoRect
+ );
+
+ ImplSalDDB(
+ Drawable aDrawable,
+ SalX11Screen nXScreen,
+ long nDrawableDepth,
+ long nX,
+ long nY,
+ long nWidth,
+ long nHeight
+ );
+
+ ~ImplSalDDB();
+
+ Pixmap ImplGetPixmap() const { return maPixmap; }
+ long ImplGetWidth() const { return maTwoRect.mnDestWidth; }
+ long ImplGetHeight() const { return maTwoRect.mnDestHeight; }
+ long ImplGetDepth() const { return mnDepth; }
+ const SalX11Screen& ImplGetScreen() const { return mnXScreen; }
+
+ bool ImplMatches( SalX11Screen nXScreen, long nDepth, const SalTwoRect& rTwoRect ) const;
+
+ void ImplDraw(
+ Drawable aDrawable,
+ const SalTwoRect& rTwoRect,
+ const GC& rGC
+ ) const;
+};
+
+
+class X11SalBitmap;
+
+class ImplSalBitmapCache
+{
+private:
+ std::vector<X11SalBitmap*> maBmpList;
+
+public:
+
+ ImplSalBitmapCache();
+ ~ImplSalBitmapCache();
+
+ void ImplAdd( X11SalBitmap* pBmp );
+ void ImplRemove( X11SalBitmap const * pBmp );
+ void ImplClear();
+};
+
+#endif // INCLUDED_VCL_INC_UNX_SALBMP_H
+
+/* 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 000000000..de66ace37
--- /dev/null
+++ b/vcl/inc/unx/saldata.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_UNX_SALDATA_HXX
+#define INCLUDED_VCL_INC_UNX_SALDATA_HXX
+
+#include <X11/Xlib.h>
+
+#include <unx/saldisp.hxx>
+#include <unx/gendata.hxx>
+#include <vclpluginapi.h>
+
+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( GenericUnixSalDataType t, SalInstance *pInstance );
+ 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();
+
+#endif // INCLUDED_VCL_INC_UNX_SALDATA_HXX
+
+/* 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 000000000..b5bed8e60
--- /dev/null
+++ b/vcl/inc/unx/saldisp.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_SALDISP_HXX
+#define INCLUDED_VCL_INC_UNX_SALDISP_HXX
+
+class SalDisplay;
+class SalColormap;
+class SalVisual;
+class SalXLib;
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/extensions/render.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 <unordered_map>
+#include <vector>
+#include <tools/gen.hxx>
+#include <salwtype.hxx>
+#include <unx/gendata.hxx>
+#include <unx/gendisp.hxx>
+#include <o3tl/enumarray.hxx>
+
+#include <vclpluginapi.h>
+
+/* 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)
+
+enum class SalRGB { RGB, RBG,
+ GBR, GRB,
+ BGR, BRG,
+ otherSalRGB };
+
+class SalVisual : public XVisualInfo
+{
+ SalRGB eRGBMode_;
+ int nRedShift_;
+ int nGreenShift_;
+ int nBlueShift_;
+ int nRedBits_;
+ int nGreenBits_;
+ int nBlueBits_;
+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; }
+
+ Pixel GetTCPixel( Color nColor ) const;
+ Color GetTCColor( Pixel nPixel ) const;
+};
+
+// 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;
+ std::vector<sal_uInt16> m_aLookupTable; // Pseudocolor: 12bit reduction
+ Pixel m_nWhitePixel;
+ Pixel m_nBlackPixel;
+ Pixel m_nUsed; // Pseudocolor
+
+ void GetPalette();
+ void GetLookupTable();
+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; }
+ Visual* GetXVisual() const { return m_aVisual.GetVisual(); }
+ Pixel GetWhitePixel() const { return m_nWhitePixel; }
+ Pixel GetBlackPixel() const { return m_nBlackPixel; }
+ Pixel GetUsed() const { return m_nUsed; }
+
+ bool GetXPixels( XColor &rColor,
+ int r,
+ int g,
+ int b ) const;
+ inline bool GetXPixel( XColor &rColor,
+ int r,
+ int g,
+ int b ) const;
+ Pixel GetPixel( Color nColor ) const;
+ Color GetColor( Pixel nPixel ) const;
+};
+
+class SalI18N_InputMethod;
+
+typedef int(*YieldFunc)(int fd, void* data);
+
+class SalXLib
+{
+protected:
+ 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();
+ virtual ~SalXLib();
+ virtual void Init();
+
+ virtual bool Yield( bool bWait, bool bHandleAllCurrentEvents );
+ virtual void Wakeup();
+ void TriggerUserEventProcessing();
+
+ virtual void Insert( int fd, void* data,
+ YieldFunc pending,
+ YieldFunc queued,
+ YieldFunc handle );
+ virtual void Remove( int fd );
+
+ virtual void StartTimer( sal_uInt64 nMS );
+ virtual void StopTimer();
+
+ virtual 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 RenderEntry
+ {
+ Pixmap m_aPixmap;
+ Picture m_aPicture;
+
+ RenderEntry() : m_aPixmap( 0 ), m_aPicture( 0 ) {}
+ };
+
+ typedef std::unordered_map<int,RenderEntry> RenderEntryMap;
+
+ struct ScreenData
+ {
+ bool m_bInit;
+
+ ::Window m_aRoot;
+ ::Window m_aRefWindow;
+ Size 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;
+ mutable RenderEntryMap m_aRenderData;
+
+ 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 ),
+ m_aRenderData( 1 )
+ {}
+ };
+
+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< tools::Rectangle > m_aXineramaScreens;
+ std::vector< int > m_aXineramaScreenIndexMap;
+ std::list<SalObject*> m_aSalObjects;
+
+ mutable Time m_nLastUserEventTime; // mutable because changed on first access
+
+ virtual bool Dispatch( XEvent *pEvent ) = 0;
+ void InitXinerama();
+ void InitRandR( ::Window aRoot ) const;
+ static void DeInitRandR();
+ void processRandREvent( XEvent* );
+
+ void doDestruct();
+ void addXineramaScreenUnique( int i, long i_nX, long i_nY, long i_nWidth, long i_nHeight );
+ Time GetEventTimeImpl( bool bAlwaysReget = false ) const;
+public:
+ static bool BestOpenGLVisual(Display* pDisplay, int nScreen, XVisualInfo& rVI);
+ 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, 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 Size& GetScreenSize( SalX11Screen nXScreen ) const { return getDataForScreen( nXScreen ).m_aSize; }
+ srv_vendor_t GetServerVendor() const { return meServerVendor; }
+ bool IsDisplay() const { return !!pXLib_; }
+ GC GetCopyGC( SalX11Screen nXScreen ) const { return getDataForScreen(nXScreen).m_aCopyGC; }
+ Pixmap GetInvert50( SalX11Screen nXScreen ) const { return getDataForScreen(nXScreen).m_hInvert50; }
+ const SalColormap& GetColormap( SalX11Screen nXScreen ) const { return getDataForScreen(nXScreen).m_aColormap; }
+ const SalVisual& GetVisual( SalX11Screen nXScreen ) const { return getDataForScreen(nXScreen).m_aVisual; }
+ RenderEntryMap& GetRenderEntries( SalX11Screen nXScreen ) const { return getDataForScreen(nXScreen).m_aRenderData; }
+ const Pair &GetResolution() const { return aResolution_; }
+ sal_uLong GetMaxRequestSize() const { return nMaxRequestSize_; }
+ Time GetLastUserEventTime() const { return GetEventTimeImpl(); }
+ // this is an equivalent of gdk_x11_get_server_time()
+ Time GetX11ServerTime() const { return GetEventTimeImpl( true ); }
+
+ bool XIfEventWithTimeout( XEvent*, XPointer, X_if_predicate ) const;
+ SalXLib* GetXLib() const { return pXLib_; }
+
+ 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< tools::Rectangle >& 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 bool Dispatch( XEvent *pEvent ) override;
+ virtual void Yield();
+ virtual void TriggerUserEventProcessing() override;
+
+ bool IsEvent();
+ void SetupInput();
+};
+
+namespace vcl_sal {
+ // get foreign key names
+ OUString getKeysymReplacementName(
+ const OUString& pLang,
+ KeySym nSymbol );
+
+ inline SalDisplay *getSalDisplay(GenericUnixSalData const * data)
+ {
+ assert(data != nullptr);
+ assert(data->GetType() != SAL_DATA_GTK3);
+ return static_cast<SalDisplay *>(data->GetDisplay());
+ }
+}
+
+#endif // INCLUDED_VCL_INC_UNX_SALDISP_HXX
+
+/* 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 000000000..4bc756a01
--- /dev/null
+++ b/vcl/inc/unx/salframe.h
@@ -0,0 +1,270 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_SALFRAME_H
+#define INCLUDED_VCL_INC_UNX_SALFRAME_H
+
+#include <X11/Xlib.h>
+
+#include <unx/saltype.h>
+#include <unx/saldisp.hxx>
+#include <unx/screensaverinhibitor.hxx>
+#include <unx/nativewindowhandleprovider.hxx>
+#include <salframe.hxx>
+#include <salwtype.hxx>
+#include <salinst.hxx>
+
+#include <vcl/ptrstyle.hxx>
+#include <vcl/sysdata.hxx>
+#include <vcl/timer.hxx>
+#include <vclpluginapi.h>
+
+#include <list>
+
+class X11SalGraphics;
+class SalI18N_InputContext;
+
+namespace vcl_sal { class WMAdaptor; class NetWMAdaptor; class GnomeWMAdaptor; }
+
+// X11SalFrame
+#define SHOWSTATE_UNKNOWN -1
+#define SHOWSTATE_MINIMIZED 0
+#define SHOWSTATE_NORMAL 1
+#define SHOWSTATE_HIDDEN 2
+
+enum class WMWindowType
+{
+ Normal,
+ ModelessDialogue,
+ Utility,
+ Splash,
+ Toolbar,
+ Dock
+};
+
+class X11SalFrame final : public SalFrame, public NativeWindowHandleProvider
+{
+ 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;
+ ::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;
+
+ int nShowState_; // show state
+ int nWidth_; // client width
+ int nHeight_; // client height
+ tools::Rectangle 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;
+
+ ScreenSaverInhibitor maScreenSaverInhibitor;
+ tools::Rectangle maPaintRegion;
+
+ Timer maAlwaysOnTopRaiseTimer;
+
+ // data for WMAdaptor
+ WMWindowType meWindowType;
+ bool mbMaximizedVert;
+ bool mbMaximizedHorz;
+ bool mbShaded;
+ bool mbFullScreen;
+
+ // 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( tools::Rectangle &rPosSize );
+ void SetSize ( const Size &rSize );
+ void Center();
+ void SetPosSize( const tools::Rectangle &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; }
+ ::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 DrawMenuBar() override;
+
+ virtual void SetExtendedFrameStyle( SalExtStyle nExtStyle ) override;
+ virtual void Show( bool bVisible, bool bNoActivate = false ) override;
+ virtual void SetMinClientSize( long nWidth, long nHeight ) override;
+ virtual void SetMaxClientSize( long nWidth, long nHeight ) override;
+ virtual void SetPosSize( long nX, long nY, long nWidth, long nHeight, sal_uInt16 nFlags ) override;
+ virtual void GetClientSize( long& rWidth, long& rHeight ) override;
+ virtual void GetWorkArea( tools::Rectangle& rRect ) override;
+ virtual SalFrame* GetParent() const override;
+ virtual void SetWindowState( const SalFrameState* pState ) override;
+ virtual bool GetWindowState( SalFrameState* pState ) 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( long nX, 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 bool 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( long nX, long nY, long nWidth, long nHeight ) override;
+ // done setting up the clipregion
+ virtual void EndSetClipRegion() override;
+
+ virtual sal_uIntPtr GetNativeWindowHandle() override;
+
+ /// @internal
+ void setPendingSizeEvent();
+};
+
+#endif // INCLUDED_VCL_INC_UNX_SALFRAME_H
+
+/* 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 000000000..4216b703c
--- /dev/null
+++ b/vcl/inc/unx/salgdi.h
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_UNX_SALGDI_H
+#define INCLUDED_VCL_INC_UNX_SALGDI_H
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/extensions/Xrender.h>
+
+#include <vcl/salgtype.hxx>
+#include <vcl/vclenum.hxx>
+
+#include <unx/saldisp.hxx>
+#include <salgdi.hxx>
+#include <salgeom.hxx>
+#include <sallayout.hxx>
+#include <vclpluginapi.h>
+#include <ControlCacheKey.hxx>
+
+#include "saltype.h"
+#include "saldisp.hxx"
+
+#include <memory>
+
+/* From <X11/Intrinsic.h> */
+typedef unsigned long Pixel;
+
+class FontAttributes;
+class FontSelectPattern;
+class SalBitmap;
+class SalColormap;
+class SalDisplay;
+class SalFrame;
+class X11Pixmap;
+class X11SalVirtualDevice;
+class X11SalGraphicsImpl;
+class X11OpenGLSalGraphicsImpl;
+class X11OpenGLSalVirtualDevice;
+class X11SkiaSalVirtualDevice;
+class FreetypeFont;
+class ImplLayoutArgs;
+class PhysicalFontCollection;
+class PhysicalFontFace;
+class SalGraphicsImpl;
+class TextRenderImpl;
+
+namespace basegfx {
+ class B2DTrapezoid;
+}
+
+class X11SalGraphics final : public SalGraphics
+{
+ friend class X11SalGraphicsImpl;
+ friend class X11OpenGLSalGraphicsImpl;
+ friend class X11CairoTextRender;
+
+public:
+ X11SalGraphics();
+ virtual ~X11SalGraphics() COVERITY_NOEXCEPT_FALSE override;
+
+ void Init( SalFrame *pFrame, Drawable aDrawable, SalX11Screen nXScreen );
+ void Init( X11SalVirtualDevice *pVirtualDevice, SalColormap* pColormap = nullptr, bool bDeleteColormap = false );
+ void Init( X11OpenGLSalVirtualDevice *pVirtualDevice );
+ void Init( X11SkiaSalVirtualDevice *pVirtualDevice );
+ void DeInit();
+
+ virtual SalGraphicsImpl* GetImpl() const override;
+ inline const SalDisplay* GetDisplay() const;
+ inline Display* GetXDisplay() const;
+ inline const SalVisual& GetVisual() const;
+ SalGeometryProvider* GetGeometryProvider() const;
+ Drawable GetDrawable() const { return hDrawable_; }
+ void SetDrawable( Drawable d, SalX11Screen nXScreen );
+ XRenderPictFormat* GetXRenderFormat() const;
+ void SetXRenderFormat( XRenderPictFormat* pXRenderFormat ) { m_pXRenderFormat = pXRenderFormat; }
+ const SalColormap& GetColormap() const { return *m_pColormap; }
+
+ using SalGraphics::GetPixel;
+ inline Pixel GetPixel( Color nColor ) const;
+
+ const SalX11Screen& GetScreenNumber() const { return m_nXScreen; }
+
+ // override all pure virtual methods
+ virtual void GetResolution( sal_Int32& rDPIX, sal_Int32& rDPIY ) override;
+ virtual sal_uInt16 GetBitCount() const override;
+ virtual long GetGraphicsWidth() const override;
+
+ virtual void ResetClipRegion() override;
+ virtual bool setClipRegion( const vcl::Region& ) override;
+
+ virtual void SetLineColor() override;
+ virtual void SetLineColor( Color nColor ) override;
+ virtual void SetFillColor() override;
+
+ virtual void SetFillColor( Color nColor ) override;
+
+ virtual void SetXORMode( bool bSet, bool ) override;
+
+ virtual void SetROPLineColor( SalROPColor nROPColor ) override;
+ virtual void SetROPFillColor( SalROPColor nROPColor ) override;
+
+ virtual void SetTextColor( Color nColor ) override;
+ virtual void SetFont(LogicalFontInstance*, int nFallbackLevel) override;
+ virtual void GetFontMetric( ImplFontMetricDataRef&, int nFallbackLevel ) override;
+ virtual FontCharMapRef GetFontCharMap() const override;
+ virtual bool GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const override;
+ virtual void GetDevFontList( PhysicalFontCollection* ) override;
+ virtual void ClearDevFontCache() override;
+ virtual bool AddTempDevFont( PhysicalFontCollection*, const OUString& rFileURL, const OUString& rFontName ) override;
+
+ virtual bool CreateFontSubset(
+ const OUString& rToFile,
+ const PhysicalFontFace*,
+ const sal_GlyphId* pGlyphIDs,
+ const sal_uInt8* pEncoding,
+ sal_Int32* pWidths,
+ int nGlyphs,
+ FontSubsetInfo& rInfo ) override;
+
+ virtual const void* GetEmbedFontData(const PhysicalFontFace*, long* pDataLen) override;
+ virtual void FreeEmbedFontData( const void* pData, long nDataLen ) override;
+
+ virtual void GetGlyphWidths(
+ const PhysicalFontFace*,
+ bool bVertical,
+ std::vector< sal_Int32 >& rWidths,
+ Ucs2UIntMap& rUnicodeEnc ) override;
+
+ virtual std::unique_ptr<GenericSalLayout>
+ GetTextLayout(int nFallbackLevel) override;
+ virtual void DrawTextLayout( const GenericSalLayout& ) override;
+
+ virtual bool supportsOperation( OutDevSupportType ) const override;
+ virtual void drawPixel( long nX, long nY ) override;
+ virtual void drawPixel( long nX, long nY, Color nColor ) override;
+ virtual void drawLine( long nX1, long nY1, long nX2, long nY2 ) override;
+ virtual void drawRect( long nX, long nY, long nWidth, long nHeight ) override;
+ virtual void drawPolyLine( sal_uInt32 nPoints, const SalPoint* pPtAry ) override;
+ virtual void drawPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry ) override;
+
+ virtual void drawPolyPolygon(
+ sal_uInt32 nPoly,
+ const sal_uInt32* pPoints,
+ PCONSTSALPOINT* pPtAry ) override;
+
+ virtual bool 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 drawGradient( const tools::PolyPolygon&, const Gradient& ) override;
+
+#if 1 // TODO: remove these obsolete methods
+ virtual bool drawPolyLineBezier(
+ sal_uInt32 nPoints,
+ const SalPoint* pPtAry,
+ const PolyFlags* pFlgAry ) override;
+
+ virtual bool drawPolygonBezier(
+ sal_uInt32 nPoints,
+ const SalPoint* pPtAry,
+ const PolyFlags* pFlgAry ) override;
+
+ virtual bool drawPolyPolygonBezier(
+ sal_uInt32 nPoly,
+ const sal_uInt32* pPoints,
+ const SalPoint* const* pPtAry,
+ const PolyFlags* const* pFlgAry ) override;
+#endif
+
+ virtual void copyArea(
+ long nDestX,
+ long nDestY,
+ long nSrcX,
+ long nSrcY,
+ long nSrcWidth,
+ long nSrcHeight,
+ bool bWindowInvalidate ) override;
+
+ 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( long nX, long nY, long nWidth, long nHeight ) override;
+ virtual Color getPixel( long nX, long nY ) override;
+ virtual void invert( long nX, long nY, long nWidth, long nHeight, SalInvert nFlags ) override;
+ virtual void invert( sal_uInt32 nPoints, const SalPoint* pPtAry, SalInvert nFlags ) override;
+
+ virtual bool drawEPS( long nX, long nY, long nWidth, 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;
+
+ 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) override;
+
+ virtual bool drawAlphaRect(
+ long nX, long nY, long nWidth,
+ long nHeight, sal_uInt8 nTransparency ) 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;
+ virtual SystemFontData GetSysFontData( int nFallbackLevel ) const override;
+
+ void clipRegion(cairo_t* cr);
+#endif // ENABLE_CAIRO_CANVAS
+
+ /* use to handle GraphicsExpose/NoExpose after XCopyArea & friends
+ * if pFrame is not NULL, corresponding Paint events are generated
+ * and dispatched to pFrame
+ *
+ * it is imperative to eat up graphics exposes even in case you don't need
+ * them because the next one using XCopyArea can depend on them
+ */
+ void YieldGraphicsExpose();
+
+ cairo_t* getCairoContext();
+ static void releaseCairoContext(cairo_t* cr);
+
+
+private:
+ using SalGraphics::SetClipRegion;
+ void SetClipRegion( GC pGC, Region pXReg = nullptr ) const;
+ bool GetDitherPixmap ( Color nColor );
+
+ using SalGraphics::DrawBitmap;
+
+ void freeResources();
+
+ SalFrame* m_pFrame; // the SalFrame which created this Graphics or NULL
+ SalVirtualDevice* m_pVDev; // the SalVirtualDevice which created this Graphics or NULL
+
+ const SalColormap* m_pColormap;
+ std::unique_ptr<SalColormap> m_pDeleteColormap;
+ Drawable hDrawable_; // use
+ SalX11Screen m_nXScreen;
+ mutable XRenderPictFormat* m_pXRenderFormat;
+ XID m_aXRenderPicture;
+
+ Region mpClipRegion;
+#if ENABLE_CAIRO_CANVAS
+ vcl::Region maClipRegion;
+ Color mnPenColor;
+ Color mnFillColor;
+#endif // ENABLE_CAIRO_CANVAS
+
+ Pixel nTextPixel_;
+
+ Pixmap hBrush_; // Dither
+
+ bool bWindow_ : 1; // is Window
+ bool bVirDev_ : 1; // is VirDev
+ bool m_bOpenGL : 1;
+ bool m_bSkia : 1;
+
+private:
+ std::unique_ptr<SalGraphicsImpl> mxImpl;
+ std::unique_ptr<TextRenderImpl> mxTextRenderImpl;
+
+};
+
+inline const SalDisplay *X11SalGraphics::GetDisplay() const
+{ return GetColormap().GetDisplay(); }
+
+inline const SalVisual& X11SalGraphics::GetVisual() const
+{ return GetColormap().GetVisual(); }
+
+inline Display *X11SalGraphics::GetXDisplay() const
+{ return GetColormap().GetXDisplay(); }
+
+inline Pixel X11SalGraphics::GetPixel( Color nColor ) const
+{ return GetColormap().GetPixel( nColor ); }
+
+#endif // INCLUDED_VCL_INC_UNX_SALGDI_H
+
+/* 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 000000000..89c9a8ae0
--- /dev/null
+++ b/vcl/inc/unx/salinst.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_UNX_SALINST_H
+#define INCLUDED_VCL_INC_UNX_SALINST_H
+
+#include <vclpluginapi.h>
+#include <salinst.hxx>
+#include <unx/geninst.h>
+
+#include <X11/X.h>
+
+namespace com::sun::star::datatransfer {
+ namespace 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(SalGraphics const * pGraphics, long &nDX, long &nDY,
+ DeviceFormat eFormat, const SystemGraphicsData* pData, std::unique_ptr<X11SalGraphics> pNewGraphics);
+
+ virtual std::unique_ptr<SalVirtualDevice>
+ CreateVirtualDevice( SalGraphics* pGraphics,
+ long &nDX, 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;
+
+ std::shared_ptr<vcl::BackendCapabilities> GetBackendCapabilities() 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 > CreateDragSource() override;
+ virtual css::uno::Reference< css::uno::XInterface > CreateDropTarget() override;
+ virtual void AddToRecentDocumentList(const OUString& rFileUrl, const OUString& rMimeType, const OUString& rDocumentService) override;
+};
+
+#endif // INCLUDED_VCL_INC_UNX_SALINST_H
+
+/* 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 000000000..52e4b8f2b
--- /dev/null
+++ b/vcl/inc/unx/salobj.h
@@ -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_UNX_SALOBJ_H
+#define INCLUDED_VCL_INC_UNX_SALOBJ_H
+
+#include <X11/Xlib.h>
+
+#include <salobj.hxx>
+#include <vcl/sysdata.hxx>
+#include <vclpluginapi.h>
+#include <memory>
+
+class SalClipRegion
+{
+
+public:
+
+ SalClipRegion();
+ ~SalClipRegion();
+
+ void BeginSetClipRegion( sal_uInt32 nRects );
+ void UnionClipRegion( long nX, long nY, long nWidth, 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
+{
+public:
+ SystemEnvData maSystemChildData;
+ SalFrame* mpParent;
+ ::Window maParentWin;
+ ::Window maPrimary;
+ ::Window maSecondary;
+ Colormap maColormap;
+ SalClipRegion maClipRegion;
+ bool mbVisible;
+
+ 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( long nX, long nY, long nWidth, long nHeight ) override;
+ virtual void EndSetClipRegion() override;
+
+ virtual void SetPosSize( long nX, long nY, long nWidth, 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;
+};
+
+#endif // INCLUDED_VCL_INC_UNX_SALOBJ_H
+
+/* 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 000000000..c8da2fcac
--- /dev/null
+++ b/vcl/inc/unx/saltimer.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_UNX_SALTIMER_H
+#define INCLUDED_VCL_INC_UNX_SALTIMER_H
+
+#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;
+};
+
+#endif
+
+/* 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 000000000..e62bdd25e
--- /dev/null
+++ b/vcl/inc/unx/saltype.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/.
+ */
+#ifndef INCLUDED_VCL_INC_UNX_SALTYPE_H
+#define INCLUDED_VCL_INC_UNX_SALTYPE_H
+
+// 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; }
+};
+
+#endif // INCLUDED_VCL_INC_UNX_SALTYPE_H
+/* 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 000000000..b70f45769
--- /dev/null
+++ b/vcl/inc/unx/salunx.h
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_UNX_SALUNX_H
+#define INCLUDED_VCL_INC_UNX_SALUNX_H
+
+inline long Divide( long nDividend, long nDivisor )
+{ return (nDividend + nDivisor/2) / nDivisor; }
+
+#endif
+
+/* 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 000000000..13f8ecc35
--- /dev/null
+++ b/vcl/inc/unx/salunxtime.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_VCL_INC_UNX_SALUNXTIME_H
+#define INCLUDED_VCL_INC_UNX_SALUNXTIME_H
+
+#if defined LINUX || defined FREEBSD || \
+ defined NETBSD || defined OPENBSD || defined DRAGONFLY
+#include <sys/time.h>
+#elif defined AIX
+#include <time.h>
+#include <sys/time.h>
+#include <strings.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;
+}
+
+#endif // INCLUDED_VCL_INC_UNX_SALUNXTIME_H
+
+/* 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 000000000..b9874a3eb
--- /dev/null
+++ b/vcl/inc/unx/salvd.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_UNX_SALVD_H
+#define INCLUDED_VCL_INC_UNX_SALVD_H
+
+#include <X11/Xlib.h>
+
+#include <unx/saldisp.hxx>
+#include <unx/saltype.h>
+#include <salvd.hxx>
+
+#include <memory>
+
+class SalDisplay;
+class X11SalGraphics;
+
+class X11SalVirtualDevice : 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_;
+
+public:
+ X11SalVirtualDevice(SalGraphics const *pGraphics, long &nDX, 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_; }
+ 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( long nNewDX, long nNewDY ) override;
+
+ // SalGeometryProvider
+ virtual long GetWidth() const override { return nDX_; }
+ virtual long GetHeight() const override { return nDY_; }
+};
+
+#endif // INCLUDED_VCL_INC_UNX_SALVD_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/screensaverinhibitor.hxx b/vcl/inc/unx/screensaverinhibitor.hxx
new file mode 100644
index 000000000..58c00e7fa
--- /dev/null
+++ b/vcl/inc/unx/screensaverinhibitor.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/.
+ */
+
+#ifndef INCLUDED_VCL_INC_UNX_SCREENSAVERINHIBITOR_HXX
+#define INCLUDED_VCL_INC_UNX_SCREENSAVERINHIBITOR_HXX
+
+#include <X11/Xlib.h>
+#include <X11/Xmd.h>
+
+#include <rtl/ustring.hxx>
+#include <vcl/dllapi.h>
+
+#include <optional>
+
+class VCL_PLUGIN_PUBLIC ScreenSaverInhibitor
+{
+public:
+ void inhibit( bool bInhibit, const OUString& sReason,
+ bool bIsX11, const std::optional<unsigned int>& xid, std::optional<Display*> pDisplay );
+
+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> mnFDOCookie; // 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) && !defined(AIX)
+ 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)
+ // FDO: 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 FDO (UnInhibit) vs GSM (Uninhibit)
+ void inhibitFDO( 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, const unsigned int xid );
+ void inhibitMSM( bool bInhibit, const char* appname, const char* reason, const unsigned int xid );
+
+ void inhibitXScreenSaver( bool bInhibit, Display* pDisplay );
+ static void inhibitXAutoLock( bool bInhibit, Display* pDisplay );
+ void inhibitDPMS( bool bInhibit, Display* pDisplay );
+};
+
+#endif // INCLUDED_VCL_INC_UNX_SCREENSAVERINHIBITOR_HXX
+
+/* 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 000000000..164fd4f6d
--- /dev/null
+++ b/vcl/inc/unx/sm.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_INC_UNX_SM_HXX
+#define INCLUDED_VCL_INC_UNX_SM_HXX
+
+#include <sal/config.h>
+
+#include <X11/SM/SMlib.h>
+
+#include <tools/link.hxx>
+#include <rtl/ustring.hxx>
+#include <memory>
+
+#include <vclpluginapi.h>
+
+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();
+};
+
+#endif
+
+/* 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 000000000..174a1ed74
--- /dev/null
+++ b/vcl/inc/unx/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_UNX_SVSYS_H
+#define INCLUDED_VCL_INC_UNX_SVSYS_H
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/extensions/Xrender.h>
+#include <X11/XKBlib.h>
+
+#endif // INCLUDED_VCL_INC_UNX_SVSYS_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 000000000..1739053a0
--- /dev/null
+++ b/vcl/inc/unx/wmadaptor.hxx
@@ -0,0 +1,302 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_WMADAPTOR_HXX
+#define INCLUDED_VCL_INC_UNX_WMADAPTOR_HXX
+
+#include <rtl/ustring.hxx>
+#include <tools/gen.hxx>
+
+#include <X11/Xlib.h>
+
+#include <vclpluginapi.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_SHADED,
+ 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< tools::Rectangle >
+ 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 tools::Rectangle& 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, long i_nUserTime ) const;
+
+ /*
+ * tells whether fullscreen mode is supported by WM
+ */
+ bool supportsFullScreen() const { return m_aWMAtoms[ NET_WM_STATE_FULLSCREEN ] != 0; }
+
+ /*
+ * shade/unshade frame
+ */
+ virtual void shade( X11SalFrame* pFrame, bool bToShaded ) const;
+
+ /*
+ * 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
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11/x11cairotextrender.hxx b/vcl/inc/unx/x11/x11cairotextrender.hxx
new file mode 100644
index 000000000..666f09f8c
--- /dev/null
+++ b/vcl/inc/unx/x11/x11cairotextrender.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 .
+ */
+
+#ifndef INCLUDED_VCL_UNX_GENERIC_GDI_X11CAIROTEXTRENDER_HXX
+#define INCLUDED_VCL_UNX_GENERIC_GDI_X11CAIROTEXTRENDER_HXX
+
+#include <unx/cairotextrender.hxx>
+#include <unx/salgdi.h>
+
+class X11CairoTextRender : public CairoTextRender
+{
+protected:
+ X11SalGraphics& mrParent;
+
+protected:
+ size_t GetWidth() const;
+ size_t GetHeight() const;
+
+public:
+ explicit X11CairoTextRender(X11SalGraphics& rParent);
+
+ virtual cairo_t* getCairoContext() override;
+ virtual void getSurfaceOffset(double& nDX, double& nDY) override;
+ virtual void clipRegion(cairo_t* cr) override;
+ virtual void releaseCairoContext(cairo_t* cr) override;
+};
+
+#endif
+
+/* 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 000000000..2e41e87c8
--- /dev/null
+++ b/vcl/inc/unx/x11/x11gdiimpl.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/.
+ */
+
+#ifndef INCLUDED_VCL_INC_UNX_X11_X11GDIIMPL_HXX
+#define INCLUDED_VCL_INC_UNX_X11_X11GDIIMPL_HXX
+
+#include <ControlCacheKey.hxx>
+
+class ControlCacheKey;
+
+class X11GraphicsImpl
+{
+public:
+ virtual ~X11GraphicsImpl() {};
+};
+
+#endif // INCLUDED_VCL_INC_UNX_X11_X11GDIIMPL_HXX
+
+/* 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 000000000..dcbe45928
--- /dev/null
+++ b/vcl/inc/unx/x11/x11sys.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_UNX_X11_X11SYS_HXX
+#define INCLUDED_VCL_INC_UNX_X11_X11SYS_HXX
+
+#include <unx/gensys.h>
+#include <vclpluginapi.h>
+
+class X11SalSystem final : public SalGenericSystem
+{
+public:
+ X11SalSystem() {}
+ virtual ~X11SalSystem() override;
+
+ // override pure virtual methods
+ virtual unsigned int GetDisplayScreenCount() override;
+ virtual bool IsUnifiedDisplay() override;
+ virtual unsigned int GetDisplayBuiltInScreen() override;
+ virtual tools::Rectangle 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 000000000..b46ff9bb1
--- /dev/null
+++ b/vcl/inc/unx/x11/xlimits.hxx
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_UNX_X11_XLIMITS_HXX
+#define INCLUDED_VCL_INC_UNX_X11_XLIMITS_HXX
+
+#include <X11/Xlib.h>
+#include <vclpluginapi.h>
+
+Pixmap limitXCreatePixmap(Display *display, Drawable d, unsigned int width, unsigned int height, unsigned int depth);
+
+#endif // INCLUDED_VCL_INC_UNX_X11_XLIMITS_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11/xrender_peer.hxx b/vcl/inc/unx/x11/xrender_peer.hxx
new file mode 100644
index 000000000..e1006f88e
--- /dev/null
+++ b/vcl/inc/unx/x11/xrender_peer.hxx
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_UNX_X11_XRENDER_PEER_HXX
+#define INCLUDED_VCL_UNX_X11_XRENDER_PEER_HXX
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+struct _XTrap; // on some older systems this is not declared within Xrender.h
+#include <X11/extensions/Xrender.h>
+
+#include <tools/color.hxx>
+
+
+class XRenderPeer
+{
+public:
+ static XRenderPeer& GetInstance();
+
+private:
+ XRenderPeer();
+ void InitRenderLib();
+
+ Display* mpDisplay;
+ XRenderPictFormat* mpStandardFormatA8;
+
+public:
+ XRenderPictFormat* GetStandardFormatA8() const;
+ XRenderPictFormat* FindStandardFormat(int nFormat) const;
+
+ // the methods below are thin wrappers for the XRENDER API
+ XRenderPictFormat* FindVisualFormat( Visual const * ) const;
+ Picture CreatePicture( Drawable, const XRenderPictFormat*,
+ unsigned long nDrawable, const XRenderPictureAttributes* ) const;
+ void ChangePicture( Picture, unsigned long nValueMask,
+ const XRenderPictureAttributes* ) const;
+ void SetPictureClipRegion( Picture, Region ) const;
+ void CompositePicture( int nOp, Picture aSrc, Picture aMask, Picture aDst,
+ int nXSrc, int nYSrc,
+ int nXDst, int nYDst, unsigned nWidth, unsigned nHeight ) const;
+ void FreePicture( Picture ) const;
+
+ void FillRectangle( int nOp, Picture aDst, const XRenderColor*,
+ int nX, int nY, unsigned nW, unsigned nH ) const;
+ void CompositeTrapezoids( int nOp, Picture aSrc, Picture aDst,
+ const XRenderPictFormat*, int nXSrc, int nYSrc,
+ const XTrapezoid*, int nCount ) const;
+ void CompositeTriangles( int nOp, Picture aSrc, Picture aDst,
+ const XRenderPictFormat*, int nXSrc, int nYSrc,
+ const XTriangle*, int nCount ) const;
+};
+
+inline XRenderPictFormat* XRenderPeer::GetStandardFormatA8() const
+{
+ return mpStandardFormatA8;
+}
+
+inline XRenderPictFormat* XRenderPeer::FindStandardFormat(int nFormat) const
+{
+ return XRenderFindStandardFormat(mpDisplay, nFormat);
+}
+
+inline XRenderPictFormat* XRenderPeer::FindVisualFormat( Visual const * pVisual ) const
+{
+ return XRenderFindVisualFormat ( mpDisplay, pVisual );
+}
+
+inline Picture XRenderPeer::CreatePicture( Drawable aDrawable,
+ const XRenderPictFormat* pVisFormat, unsigned long nValueMask,
+ const XRenderPictureAttributes* pRenderAttr ) const
+{
+ return XRenderCreatePicture( mpDisplay, aDrawable, pVisFormat,
+ nValueMask, pRenderAttr );
+}
+
+inline void XRenderPeer::ChangePicture( Picture aPicture,
+ unsigned long nValueMask, const XRenderPictureAttributes* pRenderAttr ) const
+{
+ XRenderChangePicture( mpDisplay, aPicture, nValueMask, pRenderAttr );
+}
+
+inline void XRenderPeer::SetPictureClipRegion( Picture aPicture,
+ Region aXlibRegion ) const
+{
+ XRenderSetPictureClipRegion( mpDisplay, aPicture, aXlibRegion );
+}
+
+inline void XRenderPeer::CompositePicture( int nXRenderOp,
+ Picture aSrcPic, Picture aMaskPic, Picture aDstPic,
+ int nSrcX, int nSrcY, int nDstX, int nDstY,
+ unsigned nWidth, unsigned nHeight ) const
+{
+ XRenderComposite( mpDisplay, nXRenderOp, aSrcPic, aMaskPic, aDstPic,
+ nSrcX, nSrcY, 0/*nMaskX*/, 0/*nMaskY*/, nDstX, nDstY, nWidth, nHeight );
+}
+
+inline void XRenderPeer::FreePicture( Picture aPicture ) const
+{
+ XRenderFreePicture( mpDisplay, aPicture );
+}
+
+inline void XRenderPeer::FillRectangle( int a, Picture b, const XRenderColor* c,
+ int d, int e, unsigned int f, unsigned int g) const
+{
+ XRenderFillRectangle( mpDisplay, a, b, c, d, e, f, g );
+}
+
+inline void XRenderPeer::CompositeTrapezoids( int nOp,
+ Picture aSrc, Picture aDst, const XRenderPictFormat* pXRPF,
+ int nXSrc, int nYSrc, const XTrapezoid* pXT, int nCount ) const
+{
+ XRenderCompositeTrapezoids( mpDisplay, nOp, aSrc, aDst, pXRPF,
+ nXSrc, nYSrc, pXT, nCount );
+}
+
+inline void XRenderPeer::CompositeTriangles( int nOp,
+ Picture aSrc, Picture aDst, const XRenderPictFormat* pXRPF,
+ int nXSrc, int nYSrc, const XTriangle* pXT, int nCount ) const
+{
+ XRenderCompositeTriangles( mpDisplay, nOp, aSrc, aDst, pXRPF,
+ nXSrc, nYSrc, pXT, nCount );
+}
+
+inline XRenderColor GetXRenderColor( Color rColor, double fTransparency )
+{
+ XRenderColor aRetVal;
+ // convert the Color
+ aRetVal.red = rColor.GetRed(); aRetVal.red |= (aRetVal.red << 8);
+ aRetVal.green = rColor.GetGreen(); aRetVal.green |= (aRetVal.green << 8);
+ aRetVal.blue = rColor.GetBlue(); aRetVal.blue |= (aRetVal.blue << 8);
+
+ // handle transparency
+ aRetVal.alpha = 0xFFFF; // default to opaque
+ if( fTransparency != 0 )
+ {
+ const double fAlpha = 1.0 - fTransparency;
+ aRetVal.alpha = static_cast<sal_uInt16>(fAlpha * 0xFFFF + 0.5);
+ // xrender wants pre-multiplied colors
+ aRetVal.red = static_cast<sal_uInt16>(fAlpha * aRetVal.red + 0.5);
+ aRetVal.green = static_cast<sal_uInt16>(fAlpha * aRetVal.green + 0.5);
+ aRetVal.blue = static_cast<sal_uInt16>(fAlpha * aRetVal.blue + 0.5);
+ }
+
+ return aRetVal;
+}
+
+#endif // INCLUDED_VCL_UNX_X11_XRENDER_PEER_HXX
+
+/* 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 000000000..878d1ff57
--- /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 000000000..258b2bc93
--- /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 000000000..1e6170af4
--- /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 000000000..c44b5eeb9
--- /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 000000000..cc757474e
--- /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 000000000..ebb80c0ba
--- /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 000000000..27c0ce7e7
--- /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 000000000..d7d8a1253
--- /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 000000000..e746fc59b
--- /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 000000000..69bb087d3
--- /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 000000000..67df6fb7b
--- /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 000000000..15bc43bcf
--- /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 000000000..4335c1821
--- /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 000000000..1ba699bc4
--- /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 000000000..ea3607bea
--- /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 000000000..3d366d8e1
--- /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 000000000..fe5645c28
--- /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 000000000..959a0600c
--- /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 000000000..b3b4a56c4
--- /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 000000000..ad85d47f7
--- /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 000000000..0f7ed0fc1
--- /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 000000000..24e105058
--- /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 000000000..26c055225
--- /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 000000000..4b25c9c4b
--- /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 000000000..9af6f79d3
--- /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 000000000..e5e24e0c7
--- /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 000000000..367f6b05c
--- /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 000000000..6f6977062
--- /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 000000000..4cc36ebde
--- /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 000000000..a3538c952
--- /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 000000000..df05429e9
--- /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 000000000..b2ffcca62
--- /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 000000000..22d9fc6ff
--- /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 000000000..171d8b7bc
--- /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 000000000..2a8041795
--- /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 000000000..02f2d4c12
--- /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 000000000..43629e23a
--- /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 000000000..cd17b334c
--- /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 000000000..989d43b54
--- /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 000000000..6e30897e5
--- /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 000000000..f40842257
--- /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 000000000..10d3598af
--- /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 000000000..265be0fa2
--- /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 000000000..411e8a39d
--- /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 000000000..17edc92db
--- /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 000000000..6c0c01754
--- /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 000000000..5470e8d6d
--- /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 000000000..b3b128261
--- /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 000000000..d16c2103b
--- /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 000000000..24a6643a0
--- /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 000000000..35939eb26
--- /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 000000000..eeead07a4
--- /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 000000000..adad711d1
--- /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 000000000..566a134c7
--- /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 000000000..36e886263
--- /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 000000000..304db762b
--- /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 000000000..b00d9be98
--- /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 000000000..0e5d38ebd
--- /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 000000000..5376a6600
--- /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 000000000..f283ac7fa
--- /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 000000000..777634e1c
--- /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 000000000..93ac75c2a
--- /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 000000000..7eebead2a
--- /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 000000000..0865ce1f5
--- /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 000000000..4f98f355f
--- /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 000000000..b00b06c91
--- /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 000000000..f530146d7
--- /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 000000000..75c335bea
--- /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/fill_curs.h b/vcl/inc/unx/x11_cursors/fill_curs.h
new file mode 100644
index 000000000..1cdb63410
--- /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 000000000..df5d4cdeb
--- /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 000000000..5497f0515
--- /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 000000000..c94277c6a
--- /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 000000000..cae29c67e
--- /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 000000000..8a4e6db38
--- /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 000000000..a1875a8e0
--- /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 000000000..571d928d4
--- /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 000000000..cbef41368
--- /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 000000000..4ce3d6e5a
--- /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 000000000..fcc34f886
--- /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 000000000..420ff3147
--- /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 000000000..157accb3c
--- /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 000000000..fdae75127
--- /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 000000000..08203fe76
--- /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 000000000..b253ce70c
--- /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 000000000..d317b1556
--- /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 000000000..1e82e277a
--- /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 000000000..e56f9714c
--- /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 000000000..3ffea197f
--- /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 000000000..ab74f25d8
--- /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 000000000..d5c726a2e
--- /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 000000000..c1683b333
--- /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 000000000..02d0c8145
--- /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 000000000..189c11443
--- /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 000000000..7f85113ce
--- /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 000000000..aa16b5a56
--- /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 000000000..958257518
--- /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 000000000..662a30064
--- /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 000000000..ebeee4e6f
--- /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 000000000..71f08a94a
--- /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 000000000..a34520ab0
--- /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 000000000..9571a031c
--- /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 000000000..d4547e25f
--- /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 000000000..68868aeec
--- /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 000000000..287bc9709
--- /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 000000000..0d5244722
--- /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 000000000..4aec27919
--- /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 000000000..ec9f7f2ba
--- /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 000000000..936f9f12b
--- /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 000000000..44804f00f
--- /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 000000000..1b8aa6c08
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/salcursors.h
@@ -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 <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>
+
+/* 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 000000000..e8ca82dd5
--- /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 000000000..6bb306e73
--- /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 000000000..54d37ddb0
--- /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 000000000..3b6ae7118
--- /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 000000000..9bedaabcc
--- /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 000000000..26cbc3282
--- /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 000000000..c6f6dedf1
--- /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 000000000..eb9bd3c2d
--- /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 000000000..97de23456
--- /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 000000000..601fe5396
--- /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 000000000..88cc660ba
--- /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 000000000..0fbb5d99e
--- /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 000000000..3e3859cf6
--- /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 000000000..df7c51a9f
--- /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 000000000..e8fd27230
--- /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 000000000..8547baa25
--- /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 000000000..56b705e69
--- /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 000000000..2da7aea99
--- /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 000000000..a73349906
--- /dev/null
+++ b/vcl/inc/vcleventlisteners.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_INC_VCLEVENTLISTENERS_HXX
+#define INCLUDED_VCL_INC_VCLEVENTLISTENERS_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;
+};
+
+#endif // INCLUDED_VCL_INC_VCLEVENTLISTENERS_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/vclpluginapi.h b/vcl/inc/vclpluginapi.h
new file mode 100644
index 000000000..589d7244d
--- /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_KF5_IMPLEMENTATION
+#define VCLPLUG_KF5_PUBLIC SAL_DLLPUBLIC_EXPORT
+#else
+#define VCLPLUG_KF5_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_QT5_IMPLEMENTATION
+#define VCLPLUG_QT5_PUBLIC SAL_DLLPUBLIC_EXPORT
+#else
+#define VCLPLUG_QT5_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 000000000..2652befcd
--- /dev/null
+++ b/vcl/inc/vclstatuslistener.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/.
+ */
+
+#ifndef INCLUDED_VCL_VCLSTATUSLISTENER_HXX
+#define INCLUDED_VCL_VCLSTATUSLISTENER_HXX
+
+#include <cppuhelper/implbase.hxx>
+#include <comphelper/processfactory.hxx>
+#include <vcl/vclptr.hxx>
+
+#include <com/sun/star/frame/Desktop.hpp>
+#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/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>(T* widget, 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;
+
+ const css::uno::Reference<css::frame::XFrame>& getFrame() { return mxFrame; }
+
+ void startListening();
+
+ void dispose();
+};
+
+template<class T>
+VclStatusListener<T>::VclStatusListener(T* 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);
+}
+
+template<class T>
+void VclStatusListener<T>::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);
+}
+
+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();
+}
+
+
+#endif // INCLUDED_VCL_VCLSTATUSLISTENER_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/wall2.hxx b/vcl/inc/wall2.hxx
new file mode 100644
index 000000000..401593b3f
--- /dev/null
+++ b/vcl/inc/wall2.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_WALL2_HXX
+#define INCLUDED_VCL_INC_WALL2_HXX
+
+#include <optional>
+
+class ImplWallpaper
+{
+ friend class Wallpaper;
+
+private:
+ std::optional<tools::Rectangle> mpRect;
+ std::unique_ptr<BitmapEx> mpBitmap;
+ std::unique_ptr<Gradient> mpGradient;
+ std::unique_ptr<BitmapEx> mpCache;
+ Color maColor;
+ WallpaperStyle meStyle;
+
+public:
+ ImplWallpaper();
+ ImplWallpaper( const ImplWallpaper& rImplWallpaper );
+ ~ImplWallpaper();
+
+ bool operator==( const ImplWallpaper& rImplWallpaper ) const = delete;
+
+ friend SvStream& ReadImplWallpaper( SvStream& rIStm, ImplWallpaper& rImplWallpaper );
+ friend SvStream& WriteImplWallpaper( SvStream& rOStm, const ImplWallpaper& rImplWallpaper );
+};
+
+#endif // INCLUDED_VCL_INC_WALL2_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/watchdog.hxx b/vcl/inc/watchdog.hxx
new file mode 100644
index 000000000..9202e432f
--- /dev/null
+++ b/vcl/inc/watchdog.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/.
+ */
+
+#ifndef INCLUDED_VCL_INC_WATCHDOG_H
+#define INCLUDED_VCL_INC_WATCHDOG_H
+
+#include <sal/config.h>
+#include <salhelper/thread.hxx>
+#include <atomic>
+#include <vector>
+
+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();
+};
+
+#endif // INCLUDED_VCL_INC_WATCHDOG_H
+
+/* 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 000000000..cc3eb6606
--- /dev/null
+++ b/vcl/inc/widgetdraw/WidgetDefinition.hxx
@@ -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/.
+ *
+ */
+
+#ifndef INCLUDED_VCL_INC_WIDGETDEFINITION_HXX
+#define INCLUDED_VCL_INC_WIDGETDEFINITION_HXX
+
+#include <vcl/dllapi.h>
+#include <memory>
+#include <rtl/ustring.hxx>
+#include <tools/color.hxx>
+#include <unordered_map>
+#include <vector>
+#include <cstddef>
+#include <boost/functional/hash.hpp>
+#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;
+ boost::hash_combine(seed, rControlTypeAndPart.meType);
+ boost::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 const& sEnabled, OString const& sFocused, OString const& sPressed,
+ OString const& sRollover, OString const& sDefault,
+ OString const& sSelected, OString const& sButtonValue,
+ OString const& 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;
+ Color maFontColor;
+};
+
+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
+
+#endif // INCLUDED_VCL_INC_WIDGETDEFINITION_HXX
+
+/* 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 000000000..49ecfac7a
--- /dev/null
+++ b/vcl/inc/widgetdraw/WidgetDefinitionReader.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_WIDGETDEFINITIONREADER_HXX
+#define INCLUDED_VCL_INC_WIDGETDEFINITIONREADER_HXX
+
+#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 const& rDefinitionFile, OUString const& rResourcePath);
+ bool read(WidgetDefinition& rWidgetDefinition);
+};
+
+} // end vcl namespace
+
+#endif // INCLUDED_VCL_INC_WIDGETDEFINITIONREADER_HXX
+
+/* 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 000000000..9011a951d
--- /dev/null
+++ b/vcl/inc/win/DWriteTextRenderer.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_WIN_DWRITERENDERER_HXX
+#define INCLUDED_VCL_INC_WIN_DWRITERENDERER_HXX
+
+#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();
+ virtual ~D2DWriteTextOutRenderer() override;
+
+ bool operator ()(GenericSalLayout const &rLayout,
+ SalGraphics &rGraphics,
+ HDC hDC) override;
+
+ HRESULT BindDC(HDC hDC, tools::Rectangle const & rRect = tools::Rectangle(0, 0, 1, 1));
+
+ bool BindFont(HDC hDC) /*override*/;
+ bool ReleaseFont() /*override*/;
+
+ std::vector<tools::Rectangle> GetGlyphInkBoxes(uint16_t const * pGid, uint16_t const * pGidEnd) const /*override*/;
+ ID2D1RenderTarget * GetRenderTarget() const { return mpRT; }
+ IDWriteFontFace * GetFontFace() const { return mpFontFace; }
+ float GetEmHeight() const { return mlfEmHeight; }
+
+ HRESULT CreateRenderTarget();
+
+ bool Ready() const;
+
+ void applyTextAntiAliasMode();
+ void changeTextAntiAliasMode(D2DTextAntiAliasMode eMode);
+
+private:
+ // This is a singleton object disable copy ctor and assignment operator
+ D2DWriteTextOutRenderer(const D2DWriteTextOutRenderer &) = delete;
+ D2DWriteTextOutRenderer & operator = (const D2DWriteTextOutRenderer &) = delete;
+
+ bool GetDWriteFaceFromHDC(HDC hDC, IDWriteFontFace ** ppFontFace, float * lfSize) const;
+ bool performRender(GenericSalLayout const &rLayout, SalGraphics &rGraphics, HDC hDC, bool& bRetry);
+
+ ID2D1Factory * mpD2DFactory;
+ IDWriteFactory * mpDWriteFactory;
+ IDWriteGdiInterop * mpGdiInterop;
+ ID2D1DCRenderTarget * mpRT;
+ const D2D1_RENDER_TARGET_PROPERTIES mRTProps;
+
+ IDWriteFontFace * mpFontFace;
+ float mlfEmHeight;
+ HDC mhDC;
+ 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);
+ ~WinFontTransformGuard();
+
+private:
+ ID2D1RenderTarget* mpRenderTarget;
+ D2D1::Matrix3x2F maTransform;
+};
+
+#endif // INCLUDED_VCL_INC_WIN_DWRITERENDERER_HXX
+
+/* 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 000000000..d6acd14e8
--- /dev/null
+++ b/vcl/inc/win/salbmp.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_WIN_SALBMP_H
+#define INCLUDED_VCL_INC_WIN_SALBMP_H
+
+#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;
+
+ Gdiplus::Bitmap* ImplCreateGdiPlusBitmap(const WinSalBitmap& rAlphaSource);
+ 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, sal_uInt16 nBitCount, const BitmapPalette& rPal );
+ static HANDLE ImplCopyDIBOrDDB( HANDLE hHdl, bool bDIB );
+ static sal_uInt16 ImplGetDIBColorCount( HGLOBAL hDIB );
+ static void ImplDecodeRLEBuffer( const BYTE* pSrcBuf, BYTE* pDstBuf,
+ const Size& rSizePixel, bool bRLE4 );
+
+public:
+
+ WinSalBitmap();
+ virtual ~WinSalBitmap() override;
+
+public:
+
+ bool Create( HANDLE hBitmap, bool bDIB, bool bCopyHandle );
+ virtual bool Create( const Size& rSize, sal_uInt16 nBitCount, 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, sal_uInt16 nNewBitCount ) 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;
+
+ // exclusive management op's for SystemDependentData at WinSalBitmap
+ template<class T>
+ std::shared_ptr<T> getSystemDependentData() const
+ {
+ return std::static_pointer_cast<T>(basegfx::SystemDependentDataHolder::getSystemDependentData(typeid(T).hash_code()));
+ }
+
+ template<class T, class... Args>
+ std::shared_ptr<T> addOrReplaceSystemDependentData(basegfx::SystemDependentDataManager& manager, Args&&... args) const
+ {
+ std::shared_ptr<T> r = std::make_shared<T>(manager, 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< WinSalBitmap* >(this)->basegfx::SystemDependentDataHolder::addOrReplaceSystemDependentData(r2);
+ }
+
+ return r;
+ }
+};
+
+#endif // INCLUDED_VCL_INC_WIN_SALBMP_H
+
+/* 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 000000000..eadc7ade8
--- /dev/null
+++ b/vcl/inc/win/saldata.hxx
@@ -0,0 +1,305 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_SALDATA_HXX
+#define INCLUDED_VCL_INC_WIN_SALDATA_HXX
+
+#include <config_features.h>
+
+#include <memory>
+#include <osl/module.h>
+
+#include <svdata.hxx>
+#include <salwtype.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 GlobalWinGlyphCache;
+struct HDCCache;
+struct TempFontItem;
+class TextOutRenderer;
+class OpenGLControlsCache;
+#if HAVE_FEATURE_SKIA
+class SkiaControlsCache;
+#endif
+
+#define MAX_STOCKPEN 4
+#define MAX_STOCKBRUSH 4
+#define SAL_CLIPRECT_COUNT 16
+
+struct SalIcon
+{
+ int nId;
+ HICON hIcon;
+ HICON hSmallIcon;
+ SalIcon *pNext;
+};
+
+class SalData
+{
+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
+ long* mpDitherDiff; // Dither mapping table
+ BYTE* mpDitherLow; // Dither mapping table
+ BYTE* mpDitherHigh; // Dither mapping table
+ HHOOK mhSalObjMsgHook; // hook to get interesting msg for SalObject
+ HWND mhWantLeaveMsg; // window handle, that want a MOUSELEAVE message
+ AutoTimer* mpMouseLeaveTimer; // Timer for MouseLeave Test
+ WinSalInstance* mpInstance;
+ WinSalFrame* mpFirstFrame; // pointer of first frame
+ WinSalObject* mpFirstObject; // pointer of first object window
+ WinSalVirtualDevice* mpFirstVD; // first VirDev
+ WinSalPrinter* mpFirstPrinter; // first printing printer
+ HDCCache* mpHDCCache; // 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
+ BOOL mbScrSvrEnabled; // ScreenSaver enabled
+ 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;
+ std::unique_ptr<GlobalWinGlyphCache> m_pGlobalWinGlyphCache;
+ std::unique_ptr<OpenGLControlsCache> m_pOpenGLControlsCache;
+#if HAVE_FEATURE_SKIA
+ std::unique_ptr<SkiaControlsCache> m_pSkiaControlsCache;
+#endif
+};
+
+inline void SetSalData( SalData* pData ) { ImplGetSVData()->mpSalData = pData; }
+inline SalData* GetSalData() { return ImplGetSVData()->mpSalData; }
+
+struct SalShlData
+{
+ HINSTANCE mhInst; // Instance of SAL-DLL
+ UINT mnWheelScrollLines; // WheelScrollLines
+ UINT mnWheelScrollChars; // WheelScrollChars
+};
+
+extern SalShlData aSalShlData;
+
+#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;
+ HPALETTE mhDefPal;
+ HBITMAP mhDefBmp;
+ HBITMAP mhSelBmp;
+ HBITMAP mhActBmp;
+};
+
+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"
+
+#define SAL_MOUSELEAVE_TIMEOUT 300
+
+// 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 ));
+}
+
+#endif // INCLUDED_VCL_INC_WIN_SALDATA_HXX
+
+/* 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 000000000..f0c758806
--- /dev/null
+++ b/vcl/inc/win/salframe.h
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_WIN_SALFRAME_H
+#define INCLUDED_VCL_INC_WIN_SALFRAME_H
+
+#include <vcl/sysdata.hxx>
+#include <salframe.hxx>
+#include <svsys.h>
+
+class WinSalGraphics;
+
+
+class WinSalFrame final: public SalFrame
+{
+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
+ SalFrameState maState = {}; // frame state
+ int mnShowState; // show state
+ long mnWidth; // client width in pixeln
+ long mnHeight; // client height in pixeln
+ 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 mbFullScreen; // TRUE: in full screen mode
+ 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 DrawMenuBar() override;
+ virtual void SetExtendedFrameStyle( SalExtStyle nExtStyle ) override;
+ virtual void Show( bool bVisible, bool bNoActivate = false ) override;
+ virtual void SetMinClientSize( long nWidth, long nHeight ) override;
+ virtual void SetMaxClientSize( long nWidth, long nHeight ) override;
+ virtual void SetPosSize( long nX, long nY, long nWidth, long nHeight, sal_uInt16 nFlags ) override;
+ virtual void GetClientSize( long& rWidth, long& rHeight ) override;
+ virtual void GetWorkArea( tools::Rectangle& rRect ) override;
+ virtual SalFrame* GetParent() const override;
+ virtual void SetWindowState( const SalFrameState* pState ) override;
+ virtual bool GetWindowState( SalFrameState* pState ) 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( long nX, 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 bool 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( long nX, long nY, long nWidth, long nHeight ) override;
+ virtual void EndSetClipRegion() override;
+};
+
+void ImplSalGetWorkArea( HWND hWnd, RECT *pRect, const RECT *pParentRect );
+
+// get foreign key names
+namespace vcl_sal {
+ OUString getKeysReplacementName(
+ OUString const & pLang,
+ LONG nSymbol );
+}
+
+#endif // INCLUDED_VCL_INC_WIN_SALFRAME_H
+
+/* 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 000000000..83c6b6f2d
--- /dev/null
+++ b/vcl/inc/win/salgdi.h
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_WIN_SALGDI_H
+#define INCLUDED_VCL_INC_WIN_SALGDI_H
+
+#include <sallayout.hxx>
+#include <salgeom.hxx>
+#include <salgdi.hxx>
+#include <fontinstance.hxx>
+#include <fontattributes.hxx>
+#include <PhysicalFontFace.hxx>
+#include <impfont.hxx>
+#include <vcl/fontcapabilities.hxx>
+#include <vcl/fontcharmap.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>
+
+class FontSelectPattern;
+class WinFontInstance;
+class ImplFontAttrCache;
+class PhysicalFontCollection;
+class SalGraphicsImpl;
+class WinSalGraphicsImplBase;
+class ImplFontMetricData;
+
+#define RGB_TO_PALRGB(nRGB) ((nRGB)|0x02000000)
+#define PALRGB_TO_RGB(nPalRGB) ((nPalRGB)&0x00ffffff)
+
+// win32 specific physically available font face
+class WinFontFace : public PhysicalFontFace
+{
+public:
+ explicit WinFontFace( const FontAttributes&,
+ BYTE eWinCharSet,
+ BYTE nPitchAndFamily );
+ virtual ~WinFontFace() override;
+
+ virtual rtl::Reference<LogicalFontInstance> CreateFontInstance( const FontSelectPattern& ) const override;
+ virtual sal_IntPtr GetFontId() const override;
+ void SetFontId( sal_IntPtr nId ) { mnId = nId; }
+ void UpdateFromHDC( HDC ) const;
+
+ bool HasChar( sal_uInt32 cChar ) const;
+
+ BYTE GetCharSet() const { return meWinCharSet; }
+ BYTE GetPitchAndFamily() const { return mnPitchAndFamily; }
+
+ FontCharMapRef GetFontCharMap() const;
+ bool GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const;
+
+private:
+ sal_IntPtr mnId;
+
+ // some members that are initialized lazily when the font gets selected into a HDC
+ mutable bool mbFontCapabilitiesRead;
+ mutable FontCharMapRef mxUnicodeMap;
+ mutable vcl::FontCapabilities maFontCapabilities;
+
+ BYTE meWinCharSet;
+ BYTE mnPitchAndFamily;
+ bool mbAliasSymbolsHigh;
+ bool mbAliasSymbolsLow;
+
+ void ReadCmapTable( HDC ) const;
+ void GetFontCapabilities( HDC hDC ) const;
+};
+
+/** 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; }
+
+ long getBitmapWidth() const { return maRects.mnSrcWidth; }
+ long getBitmapHeight() const { return maRects.mnSrcHeight; }
+
+ /// Reset the DC with the defined color.
+ void fill(sal_uInt32 color);
+
+ /// Base texture class (OpenGL and Skia will provide their implementations).
+ struct Texture;
+
+ /// Obtain the texture in format for WinSalGraphicsImplBase::DrawTextMask().
+ virtual std::unique_ptr<Texture> getAsMaskTexture() const { abort(); };
+};
+
+struct CompatibleDC::Texture
+{
+ virtual ~Texture() {};
+ virtual bool isValid() const = 0;
+ virtual int GetWidth() const = 0;
+ virtual int GetHeight() const = 0;
+};
+
+class WinSalGraphics : public SalGraphics
+{
+ friend class WinSalGraphicsImpl;
+ friend class WinOpenGLSalGraphicsImpl;
+ friend class ScopedFont;
+
+protected:
+ std::unique_ptr<SalGraphicsImpl> mpImpl;
+
+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
+
+ bool CacheGlyphs(const GenericSalLayout& rLayout);
+ bool DrawCachedGlyphs(const GenericSalLayout& rLayout);
+
+public:
+ HFONT ImplDoSetFont(FontSelectPattern const & i_rFont, const PhysicalFontFace * i_pFontFace, HFONT& o_rOldFont);
+
+ HDC getHDC() const { return mhLocalDC; }
+ void setHDC(HDC aNew) { mhLocalDC = aNew; }
+
+ HPALETTE getDefPal() const;
+ void setDefPal(HPALETTE hDefPal);
+
+ HRGN getRegion() const;
+
+ void InitGraphics();
+ void DeInitGraphics();
+
+ enum Type
+ {
+ PRINTER,
+ VIRTUAL_DEVICE,
+ WINDOW,
+ SCREEN
+ };
+
+public:
+
+ HWND gethWnd();
+
+
+public:
+ explicit WinSalGraphics(WinSalGraphics::Type eType, bool bScreen, HWND hWnd,
+ SalGeometryProvider *pProvider);
+ virtual ~WinSalGraphics() override;
+
+ SalGraphicsImpl* GetImpl() const override;
+ bool isPrinter() const;
+ bool isVirtualDevice() const;
+ bool isWindow() const;
+ bool isScreen() const;
+
+ void setHWND(HWND hWnd);
+
+protected:
+ virtual bool setClipRegion( const vcl::Region& ) override;
+ // draw --> LineColor and FillColor and RasterOp and ClipRegion
+ virtual void drawPixel( long nX, long nY ) override;
+ virtual void drawPixel( long nX, long nY, Color nColor ) override;
+ virtual void drawLine( long nX1, long nY1, long nX2, long nY2 ) override;
+ virtual void drawRect( long nX, long nY, long nWidth, long nHeight ) override;
+ virtual void drawPolyLine( sal_uInt32 nPoints, const SalPoint* pPtAry ) override;
+ virtual void drawPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry ) override;
+ virtual void drawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints, PCONSTSALPOINT* pPtAry ) override;
+ virtual bool 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 SalPoint* pPtAry, const PolyFlags* pFlgAry ) override;
+ virtual bool drawPolygonBezier( sal_uInt32 nPoints, const SalPoint* pPtAry, const PolyFlags* pFlgAry ) override;
+ virtual bool drawPolyPolygonBezier( sal_uInt32 nPoly, const sal_uInt32* pPoints, const SalPoint* const* pPtAry, const PolyFlags* const* pFlgAry ) override;
+ virtual bool drawGradient( const tools::PolyPolygon&, const Gradient& ) override { return false; };
+
+ // CopyArea --> No RasterOp, but ClipRegion
+ virtual void copyArea( long nDestX, long nDestY, long nSrcX, long nSrcY, long nSrcWidth,
+ 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( long nX, long nY, long nWidth, long nHeight ) override;
+ virtual Color getPixel( long nX, long nY ) override;
+
+ // invert --> ClipRegion (only Windows or VirDevs)
+ virtual void invert( long nX, long nY, long nWidth, long nHeight, SalInvert nFlags) override;
+ virtual void invert( sal_uInt32 nPoints, const SalPoint* pPtAry, SalInvert nFlags ) override;
+
+ virtual bool drawEPS( long nX, long nY, long nWidth, 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) override;
+ virtual bool drawAlphaRect( long nX, long nY, long nWidth, long nHeight, sal_uInt8 nTransparency ) override;
+
+private:
+ // local helpers
+
+ void DrawTextLayout(const GenericSalLayout&, HDC, bool bUseDWrite);
+
+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 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( ImplFontMetricDataRef&, 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( PhysicalFontCollection* ) override;
+ // graphics must drop any cached font info
+ virtual void ClearDevFontCache() override;
+ virtual bool AddTempDevFont( PhysicalFontCollection*, const OUString& rFileURL, const OUString& rFontName ) override;
+ // CreateFontSubset: a method to get a subset of glyhps of a font
+ // inside a new valid font file
+ // returns TRUE if creation of subset was successful
+ // parameters: rToFile: contains an osl file URL to write the subset to
+ // pFont: describes from which font to create a subset
+ // pGlyphIDs: the glyph ids to be extracted
+ // pEncoding: the character code corresponding to each glyph
+ // pWidths: the advance widths of the corresponding glyphs (in PS font units)
+ // nGlyphs: the number of glyphs
+ // rInfo: additional outgoing information
+ // implementation note: encoding 0 with glyph id 0 should be added implicitly
+ // as "undefined character"
+ virtual bool CreateFontSubset( const OUString& rToFile,
+ const PhysicalFontFace*,
+ const sal_GlyphId* pGlyphIDs,
+ const sal_uInt8* pEncoding,
+ sal_Int32* pWidths,
+ int nGlyphs,
+ FontSubsetInfo& rInfo // out parameter
+ ) override;
+
+ // GetEmbedFontData: gets the font data for a font marked
+ // embeddable by GetDevFontList or NULL in case of error
+ // parameters: pFont: describes the font in question
+ // pDataLen: out parameter, contains the byte length of the returned buffer
+ virtual const void* GetEmbedFontData(const PhysicalFontFace*, long* pDataLen) override;
+ // frees the font data again
+ virtual void FreeEmbedFontData( const void* pData, long nDataLen ) override;
+ virtual void GetGlyphWidths( const PhysicalFontFace*,
+ bool bVertical,
+ std::vector< sal_Int32 >& rWidths,
+ Ucs2UIntMap& rUnicodeEnc ) 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( HDC, const FontSelectPattern&,
+ const PhysicalFontFace*, LOGFONTW& );
+
+#define MAX_64KSALPOINTS ((((sal_uInt16)0xFFFF)-8)/sizeof(POINTS))
+
+// called extremely often from just one spot => inline
+inline bool WinFontFace::HasChar( sal_uInt32 cChar ) const
+{
+ if( mxUnicodeMap->HasChar( cChar ) )
+ return true;
+ // second chance to allow symbol aliasing
+ if( mbAliasSymbolsLow && ((cChar-0xF000) <= 0xFF) )
+ cChar -= 0xF000;
+ else if( mbAliasSymbolsHigh && (cChar <= 0xFF) )
+ cChar += 0xF000;
+ else
+ return false;
+ return mxUnicodeMap->HasChar( cChar );
+}
+
+#endif // INCLUDED_VCL_INC_WIN_SALGDI_H
+
+/* 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 000000000..092a24b52
--- /dev/null
+++ b/vcl/inc/win/salids.hrc
@@ -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 .
+ */
+
+#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_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 000000000..c06e51c84
--- /dev/null
+++ b/vcl/inc/win/salinst.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_WIN_SALINST_H
+#define INCLUDED_VCL_INC_WIN_SALINST_H
+
+#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 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* pGraphics,
+ long &nDX, 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 std::shared_ptr<vcl::BackendCapabilities> GetBackendCapabilities() override;
+
+ static int WorkaroundExceptionHandlingInUSER32Lib(int nExcept, LPEXCEPTION_POINTERS pExceptionInfo);
+};
+
+SalFrame* ImplSalCreateFrame( WinSalInstance* pInst, HWND hWndParent, SalFrameStyleFlags nSalFrameStyle );
+SalObject* ImplSalCreateObject( WinSalInstance* pInst, WinSalFrame* pParent );
+HWND ImplSalReCreateHWND( HWND hWndParent, HWND oldhWnd, bool bAsChild );
+
+#endif // INCLUDED_VCL_INC_WIN_SALINST_H
+
+/* 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 000000000..7058d9c82
--- /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 000000000..465c9ec77
--- /dev/null
+++ b/vcl/inc/win/salobj.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_WIN_SALOBJ_H
+#define INCLUDED_VCL_INC_WIN_SALOBJ_H
+
+#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( long nX, long nY, long nWidth, long nHeight) override;
+ virtual void EndSetClipRegion() override;
+ virtual void SetPosSize( long nX, long nY, long nWidth, 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;
+};
+
+#endif // INCLUDED_VCL_INC_WIN_SALOBJ_H
+
+/* 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 000000000..c0c6e7fb7
--- /dev/null
+++ b/vcl/inc/win/salprn.h
@@ -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_WIN_SALPRN_H
+#define INCLUDED_VCL_INC_WIN_SALPRN_H
+
+#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 : public SalInfoPrinter
+{
+public:
+ WinSalGraphics* mpGraphics; // current Printer graphics
+ OUString maDriverName; // printer driver name
+ OUString maDeviceName; // printer device name
+ OUString maPortName; // printer port name
+ HDC mhDC; // printer hdc
+ bool mbGraphics; // is Graphics used
+public:
+ WinSalInfoPrinter();
+ virtual ~WinSalInfoPrinter() override;
+
+ 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,
+ long& rOutWidth, 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:
+ WinSalGraphics* mpGraphics; // current Printer graphics
+ WinSalInfoPrinter* mpInfoPrinter; // pointer to the compatible InfoPrinter
+ WinSalPrinter* mpNextPrinter; // next printing printer
+ HDC mhDC; // printer hdc
+ SalPrinterError mnError; // error code
+ sal_uIntPtr 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; }
+};
+
+#endif // INCLUDED_VCL_INC_WIN_SALPRN_H
+
+/* 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 000000000..03f627b5a
--- /dev/null
+++ b/vcl/inc/win/salsys.h
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_WIN_SALSYS_H
+#define INCLUDED_VCL_INC_WIN_SALSYS_H
+
+#include <salsys.hxx>
+
+#include <vector>
+#include <map>
+
+class WinSalSystem : public SalSystem
+{
+public:
+ struct DisplayMonitor
+ {
+ OUString m_aName;
+ tools::Rectangle m_aArea;
+
+ DisplayMonitor() {}
+ DisplayMonitor( const OUString& rName,
+ const tools::Rectangle& 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 tools::Rectangle 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*/ );
+};
+
+#endif // INCLUDED_VCL_INC_WIN_SALSYS_H
+
+/* 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 000000000..a467de815
--- /dev/null
+++ b/vcl/inc/win/saltimer.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_WIN_SALTIMER_H
+#define INCLUDED_VCL_INC_WIN_SALTIMER_H
+
+#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;
+}
+
+#endif
+
+/* 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 000000000..4121d2c89
--- /dev/null
+++ b/vcl/inc/win/salvd.h
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_WIN_SALVD_H
+#define INCLUDED_VCL_INC_WIN_SALVD_H
+
+#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
+ long mnWidth;
+ 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, long nWidth = 0, long nHeight = 0);
+ virtual ~WinSalVirtualDevice() override;
+
+ virtual SalGraphics* AcquireGraphics() override;
+ virtual void ReleaseGraphics( SalGraphics* pGraphics ) override;
+ virtual bool SetSize( long nNewDX, long nNewDY ) override;
+
+ static HBITMAP ImplCreateVirDevBitmap(HDC hDC, long nDX, long nDY, sal_uInt16 nBitCount, void **ppDummy);
+
+ // SalGeometryProvider
+ virtual long GetWidth() const override { return mnWidth; }
+ virtual long GetHeight() const override { return mnHeight; }
+};
+
+
+#endif // INCLUDED_VCL_INC_WIN_SALVD_H
+
+/* 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 000000000..d02ad9545
--- /dev/null
+++ b/vcl/inc/win/scoped_gdi.hxx
@@ -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/.
+ */
+
+#ifndef INCLUDED_VCL_INC_WIN_SCOPED_GDI_HXX
+#define INCLUDED_VCL_INC_WIN_SCOPED_GDI_HXX
+
+#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;
+};
+
+#endif // INCLUDED_VCL_INC_WIN_SCOPED_GDI_HXX
+
+/* 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 000000000..44cae93cf
--- /dev/null
+++ b/vcl/inc/win/svsys.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_WIN_SVSYS_H
+#define INCLUDED_VCL_INC_WIN_SVSYS_H
+
+#ifdef _WIN32
+#ifndef INCLUDED_PRE_POST_WIN_H
+#define INCLUDED_PRE_POST_WIN_H
+#include <prewin.h>
+#include <postwin.h>
+#endif
+#endif
+
+#endif // INCLUDED_VCL_INC_WIN_SVSYS_H
+
+/* 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 000000000..05b4800e1
--- /dev/null
+++ b/vcl/inc/win/wincomp.hxx
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_WIN_WINCOMP_HXX
+#define INCLUDED_VCL_INC_WIN_WINCOMP_HXX
+
+#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"
+
+#ifndef WHEEL_DELTA
+#define WHEEL_DELTA 120
+#endif
+#ifndef WM_MOUSEWHEEL
+#define WM_MOUSEWHEEL 0x020A
+#endif
+#ifndef SPI_GETWHEELSCROLLLINES
+#define SPI_GETWHEELSCROLLLINES 104
+#endif
+#ifndef SPI_SETWHEELSCROLLLINES
+#define SPI_SETWHEELSCROLLLINES 105
+#endif
+#ifndef WHEEL_PAGESCROLL
+#define WHEEL_PAGESCROLL (UINT_MAX)
+#endif
+
+
+// - 5.0 extensions -
+
+#ifndef COLOR_GRADIENTACTIVECAPTION
+#define COLOR_GRADIENTACTIVECAPTION 27
+#endif
+#ifndef COLOR_GRADIENTINACTIVECAPTION
+#define COLOR_GRADIENTINACTIVECAPTION 28
+#endif
+
+#ifndef SPI_GETFLATMENU
+#define SPI_GETFLATMENU 0x1022
+#endif
+#ifndef COLOR_MENUBAR
+#define COLOR_MENUBAR 30
+#endif
+#ifndef COLOR_MENUHILIGHT
+#define COLOR_MENUHILIGHT 29
+#endif
+
+#ifndef CS_DROPSHADOW
+#define CS_DROPSHADOW 0x00020000
+#endif
+
+// MT 12/03: From winuser.h, only needed in salframe.cxx
+// Better change salframe.cxx to include winuser.h
+
+#define WS_EX_LAYERED 0x00080000
+
+#ifndef WM_UNICHAR
+#define WM_UNICHAR 0x0109
+#define UNICODE_NOCHAR 0xFFFF
+#endif
+
+#endif // INCLUDED_VCL_INC_WIN_WINCOMP_HXX
+
+/* 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 000000000..679be2c36
--- /dev/null
+++ b/vcl/inc/win/wingdiimpl.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/.
+ */
+
+#ifndef INCLUDED_VCL_INC_WIN_WINGDIIMPL_HXX
+#define INCLUDED_VCL_INC_WIN_WINGDIIMPL_HXX
+
+#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(){};
+
+ // Implementation for WinSalGraphics::DrawTextLayout().
+ // Returns true if handled, if false, then WinSalGraphics will handle it itself.
+ virtual bool DrawTextLayout(const GenericSalLayout&) { return false; }
+ // If true is returned, the following functions are used for text rendering.
+ virtual bool UseTextDraw() const { return false; }
+ virtual void PreDrawText() {}
+ virtual void PostDrawText() {}
+ virtual void DrawTextMask(CompatibleDC::Texture* /*rTexture*/, Color /*nMaskColor*/,
+ const SalTwoRect& /*rPosAry*/)
+ {
+ abort();
+ };
+ virtual void DeferredTextDraw(const CompatibleDC::Texture* /*pTexture*/, Color /*nMaskColor*/,
+ const SalTwoRect& /*rPosAry*/)
+ {
+ abort();
+ };
+};
+
+#endif // INCLUDED_VCL_INC_WIN_WINGDIIMPL_HXX
+
+/* 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 000000000..35a855a37
--- /dev/null
+++ b/vcl/inc/win/winlayout.hxx
@@ -0,0 +1,187 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_WINLAYOUT_HXX
+#define INCLUDED_VCL_INC_WIN_WINLAYOUT_HXX
+
+#include <rtl/ustring.hxx>
+
+#include <sallayout.hxx>
+#include <svsys.h>
+#include <win/salgdi.h>
+#include <o3tl/sorted_vector.hxx>
+
+class WinFontInstance;
+
+namespace
+{
+// Extra space at the top and bottom of the glyph in total = tmHeight / GLYPH_SPACE_RATIO;
+const int GLYPH_SPACE_RATIO = 8;
+// Border size at the top of the glyph = tmHeight / GLYPH_OFFSET_RATIO;
+const int GLYPH_OFFSET_RATIO = GLYPH_SPACE_RATIO * 2;
+}
+
+struct WinGlyphDrawElement
+{
+ tools::Rectangle maLocation;
+ int maLeftOverhangs;
+ std::unique_ptr<CompatibleDC::Texture> maTexture;
+ int mnBaselineOffset;
+ int mnHeight;
+ bool mbVertical;
+
+ int getExtraSpace() const
+ {
+ return std::max(mnHeight / GLYPH_SPACE_RATIO, 4);
+ }
+
+ int getExtraOffset() const
+ {
+ return std::max(mnHeight / GLYPH_OFFSET_RATIO, 2);
+ }
+};
+
+class WinGlyphCache;
+
+struct GlobalWinGlyphCache
+{
+ o3tl::sorted_vector<WinGlyphCache*> maWinGlyphCaches;
+
+ static GlobalWinGlyphCache * get();
+
+ virtual ~GlobalWinGlyphCache() {}
+ virtual bool AllocateTexture(WinGlyphDrawElement& rElement, CompatibleDC* dc) = 0;
+ virtual void NotifyElementUsed(WinGlyphDrawElement& /*rElement*/) {}
+ virtual void Prune() {}
+};
+
+class WinGlyphCache
+{
+protected:
+ std::unordered_map<int, WinGlyphDrawElement> maWinTextureCache;
+
+public:
+ WinGlyphCache()
+ {
+ if(GlobalWinGlyphCache* c = GlobalWinGlyphCache::get())
+ c->maWinGlyphCaches.insert(this);
+ }
+
+ virtual ~WinGlyphCache()
+ {
+ if(GlobalWinGlyphCache* c = GlobalWinGlyphCache::get())
+ c->maWinGlyphCaches.erase(this);
+ }
+
+ void PutDrawElementInCache(WinGlyphDrawElement&& rElement, int nGlyphIndex)
+ {
+ assert(GlobalWinGlyphCache::get());
+ assert(!IsGlyphCached(nGlyphIndex));
+ maWinTextureCache[nGlyphIndex] = std::move( rElement );
+ }
+
+ WinGlyphDrawElement& GetDrawElement(int nGlyphIndex)
+ {
+ assert(GlobalWinGlyphCache::get());
+ assert(IsGlyphCached(nGlyphIndex));
+ WinGlyphDrawElement& element = maWinTextureCache[nGlyphIndex];
+ GlobalWinGlyphCache::get()->NotifyElementUsed(element);
+ return element;
+ }
+
+ bool IsGlyphCached(int nGlyphIndex) const
+ {
+ return maWinTextureCache.find(nGlyphIndex) != maWinTextureCache.end();
+ }
+};
+
+// win32 specific logical font instance
+class WinFontInstance : public LogicalFontInstance
+{
+ friend rtl::Reference<LogicalFontInstance> WinFontFace::CreateFontInstance(const 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; }
+ float GetScale() const { return m_fScale; }
+
+ // Prevent deletion of the HFONT in the WinFontInstance destructor
+ // Used for the ScopedFont handling
+ void SetHFONT(HFONT hFont) { m_hFont = hFont; }
+
+ const WinFontFace * GetFontFace() const { return static_cast<const WinFontFace *>(LogicalFontInstance::GetFontFace()); }
+ WinFontFace * GetFontFace() { return static_cast<WinFontFace *>(LogicalFontInstance::GetFontFace()); }
+
+ bool CacheGlyphToAtlas(HDC hDC, HFONT hFont, int nGlyphIndex, SalGraphics& rGraphics, const GenericSalLayout& rLayout);
+ WinGlyphCache& GetWinGlyphCache() { return maWinGlyphCache; }
+
+ bool GetGlyphOutline(sal_GlyphId, basegfx::B2DPolyPolygon&, bool) const override;
+
+private:
+ explicit WinFontInstance(const WinFontFace&, const FontSelectPattern&);
+
+ hb_font_t* ImplInitHbFont() override;
+ bool ImplGetGlyphBoundRect(sal_GlyphId, tools::Rectangle&, bool) const override;
+
+ WinSalGraphics *m_pGraphics;
+ HFONT m_hFont;
+ float m_fScale;
+ WinGlyphCache maWinGlyphCache;
+};
+
+class TextOutRenderer
+{
+protected:
+ explicit TextOutRenderer() = default;
+ TextOutRenderer(const TextOutRenderer &) = delete;
+ TextOutRenderer & operator = (const TextOutRenderer &) = delete;
+
+public:
+ static TextOutRenderer & get(bool bUseDWrite);
+
+ virtual ~TextOutRenderer() = default;
+
+ virtual bool operator ()(GenericSalLayout const &rLayout,
+ SalGraphics &rGraphics,
+ HDC hDC) = 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) override;
+};
+
+#endif // INCLUDED_VCL_INC_WIN_WINLAYOUT_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/window.h b/vcl/inc/window.h
new file mode 100644
index 000000000..6b362e7d3
--- /dev/null
+++ b/vcl/inc/window.h
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_WINDOW_H
+#define INCLUDED_VCL_INC_WINDOW_H
+
+#include <sal/config.h>
+
+#include <tools/fract.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/idle.hxx>
+#include <vcl/inputctx.hxx>
+#include <vcl/window.hxx>
+#include <vcl/settings.hxx>
+#include <o3tl/typed_flags_set.hxx>
+#include <cppuhelper/weakref.hxx>
+
+#include <optional>
+#include <list>
+#include <memory>
+#include <vector>
+#include <set>
+
+class FixedText;
+class VclSizeGroup;
+class VirtualDevice;
+class PhysicalFontCollection;
+class ImplFontCache;
+class VCLXWindow;
+class WindowStateData;
+class SalFrame;
+class SalObject;
+enum class MouseEventModifiers;
+enum class MouseNotifyEvent;
+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 rendering {
+ class XCanvas;
+ }
+
+ namespace awt {
+ class XWindowPeer;
+ 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 );
+
+struct ImplWinData
+{
+ std::optional<OUString>
+ mpExtOldText;
+ std::unique_ptr<ExtTextInputAttr[]>
+ mpExtOldAttrAry;
+ std::optional<tools::Rectangle>
+ mpCursorRect;
+ long mnCursorExtWidth;
+ bool mbVertical;
+ std::unique_ptr<tools::Rectangle[]>
+ mpCompositionCharRects;
+ 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
+ std::vector<VclPtr<vcl::Window> > maOwnerDrawList; //< List of system windows with owner draw decoration
+ std::shared_ptr<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
+ long mnLastMouseX; //< last x mouse position
+ long mnLastMouseY; //< last y mouse position
+ long mnBeforeLastMouseX; //< last but one x mouse position
+ long mnBeforeLastMouseY; //< last but one y mouse position
+ long mnFirstMouseX; //< first x mouse position by mousebuttondown
+ long mnFirstMouseY; //< first y mouse position by mousebuttondown
+ long mnLastMouseWinX; //< last x mouse position, rel. to pMouseMoveWin
+ 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;
+ css::uno::Reference< css::datatransfer::clipboard::XClipboard > mxSelection;
+
+ bool mbInternalDragGestureRecognizer;
+ 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;
+ VclPtr<vcl::Window> pMemberOfWindow;
+
+ ImplAccessibleInfos();
+ ~ImplAccessibleInfos();
+};
+
+enum AlwaysInputMode { AlwaysInputNone = 0, AlwaysInputEnabled = 1, AlwaysInputDisabled =2 };
+
+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( WindowType );
+ ~WindowImpl();
+
+ 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<vcl::Window> 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<boost::property_tree::ptree&, void> maDumpAsPropertyTreeHdl;
+
+ // The canvas interface for this VCL window. Is persistent after the first GetCanvas() call
+ css::uno::WeakReference< css::rendering::XCanvas > mxCanvas;
+
+ vcl::Cursor* mpCursor;
+ PointerStyle maPointer;
+ Fraction maZoom;
+ OUString maText;
+ std::unique_ptr<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;
+ long mnX;
+ long mnY;
+ long mnAbsScreenX;
+ Point maPos;
+ OString maHelpId;
+ OUString maHelpText;
+ OUString maQuickHelpText;
+ OUString maID;
+ InputContext maInputContext;
+ css::uno::Reference< css::awt::XWindowPeer > 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,
+ mbCallHandlersDuringInputDisabled: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 mbLOKParentNotifier;
+};
+
+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;
+ long mnOutOffX;
+ long mnOutOffY;
+ tools::Rectangle m_aPaintRect;
+public:
+ PaintBufferGuard(ImplFrameData* pFrameData, vcl::Window* pWindow);
+ ~PaintBufferGuard();
+ /// 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();
+};
+}
+
+// helper methods
+
+bool ImplHandleMouseEvent( const VclPtr<vcl::Window>& xWindow, MouseNotifyEvent nSVEvent, bool bMouseLeave,
+ long nX, long nY, sal_uInt64 nMsgTime,
+ sal_uInt16 nCode, MouseEventModifiers nMode );
+void ImplHandleResize( vcl::Window* pWindow, long nNewWidth, long nNewHeight );
+
+VCL_DLLPUBLIC void ImplWindowStateFromStr(WindowStateData& rData, const OString& rStr);
+
+VCL_DLLPUBLIC css::uno::Reference<css::accessibility::XAccessibleEditableText>
+FindFocusedEditableText(css::uno::Reference<css::accessibility::XAccessibleContext> const&);
+
+#endif // INCLUDED_VCL_INC_WINDOW_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/wizdlg.hxx b/vcl/inc/wizdlg.hxx
new file mode 100644
index 000000000..2c82b3ab8
--- /dev/null
+++ b/vcl/inc/wizdlg.hxx
@@ -0,0 +1,315 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_VCL_WIZDLG_HXX
+#define INCLUDED_VCL_WIZDLG_HXX
+
+#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 : 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
+
+ protected:
+ long LogicalCoordinateToPixel(int iCoordinate);
+ /**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 );
+ bool Finish( 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, long nOffset = 0 );
+ void RemoveButton( Button* pButton );
+
+ void SetPageSizePixel( const Size& rSize ) { maPageSize = rSize; }
+ const Size& GetPageSizePixel() const { return maPageSize; }
+
+ /// enable (or disable) buttons
+ void enableButtons(WizardButtonFlags _nWizardButtonFlags, bool _bEnable);
+
+ /// determines whether there is a next state to which we can advance
+ bool canAdvance() const;
+
+ void SetRoadmapHelpId( const OString& _rId );
+
+ void InsertRoadmapItem(int nIndex, const OUString& rLabel, int nId, bool bEnabled);
+ void DeleteRoadmapItems();
+ int GetCurrentRoadmapItemID() const;
+ void SelectRoadmapItemByID(int nId);
+ void SetItemSelectHdl( const Link<LinkParamNone*,void>& _rHdl );
+ void ShowRoadmap(bool bShow);
+
+ FactoryFunction GetUITestFactory() const override;
+
+ protected:
+
+ /// 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);
+
+ /** will be called when the current state is about to be left for the given reason
+
+ The base implementation in this class will simply call <member>OWizardPage::commitPage</member>
+ for the current page, and return whatever this call returns.
+
+ @param _eReason
+ The reason why the state is to be left.
+ @return
+ <TRUE/> if and only if the page is allowed to be left
+ */
+ bool prepareLeaveCurrentState( WizardTypes::CommitPageReason eReason );
+
+ /** 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
+ bool travelNext();
+
+ /// travel to the previous state
+ bool travelPrevious();
+
+ /** enables the automatic enabled/disabled state of the "Next" button
+
+ If this is <TRUE/>, then upon entering a new state, the "Next" button will automatically be
+ enabled if and only if determineNextState does not return WZS_INVALID_STATE.
+ */
+ bool isAutomaticNextButtonStateEnabled() const;
+
+ /** 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(); }
+
+ static IWizardPageController* getPageController( TabPage* _pCurrentPage );
+
+ /** 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;
+
+ private:
+ 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;
+
+ protected:
+ TabPage* GetOrCreatePage(const WizardTypes::WizardState i_nState);
+
+ private:
+ 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 implUpdateTitle();
+ void implConstruct( const WizardButtonFlags _nButtonFlags );
+ };
+
+ /// 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
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/ios/DataFlavorMapping.cxx b/vcl/ios/DataFlavorMapping.cxx
new file mode 100644
index 000000000..88b0e6199
--- /dev/null
+++ b/vcl/ios/DataFlavorMapping.cxx
@@ -0,0 +1,571 @@
+/* -*- 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_PLAINTEXT = (__bridge NSString*)kUTTypePlainText;
+// Nope. See commented-out use below.
+// 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;
+NSString* PBTYPE_SESX
+ = @"application/x-openoffice-embed-source-xml;windows_formatname=\"Star Embed Source (XML)\"";
+NSString* PBTYPE_SLSDX = @"application/"
+ @"x-openoffice-linksrcdescriptor-xml;windows_formatname=\"Star Link "
+ @"Source Descriptor (XML)\"";
+NSString* PBTYPE_LSX
+ = @"application/x-openoffice-link-source-xml;windows_formatname=\"Star Link Source (XML)\"";
+NSString* PBTYPE_EOX = @"application/x-openoffice-embedded-obj-xml;windows_formatname=\"Star "
+ @"Embedded Object (XML)\"";
+NSString* PBTYPE_SVXB
+ = @"application/x-openoffice-svbx;windows_formatname=\"SVXB (StarView Bitmap/Animation)\"";
+NSString* PBTYPE_GDIMF = @"application/x-openoffice-gdimetafile;windows_formatname=\"GDIMetaFile\"";
+NSString* PBTYPE_SODX = @"application/x-openoffice-objectdescriptor-xml;windows_formatname=\"Star "
+ @"Object Descriptor (XML)\"";
+NSString* PBTYPE_DUMMY_INTERNAL = @"application/x-openoffice-internal";
+
+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)\"";
+const char* FLAVOR_DUMMY_INTERNAL = "application/x-openoffice-internal";
+
+struct FlavorMap
+{
+ NSString* SystemFlavor;
+ const char* OOoFlavor;
+ const char* HumanPresentableName;
+ bool DataTypeOUString; // sequence<byte> otherwise
+};
+
+static const FlavorMap flavorMap[]
+ = { { PBTYPE_PLAINTEXT, "text/plain;charset=utf-16", "Unicode Text (UTF-16)", true },
+ // Nope. The LO code does not understand text/plain in UTF-8. Which is a shame.
+ // PBTYPE_UTF8PLAINTEXT, "text/plain;charset=utf-8", "Unicode Text (UTF-8)", false },
+ { 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 },
+ { PBTYPE_SESX, FLAVOR_SESX, "Star Embed Source (XML)", false },
+ { PBTYPE_SLSDX, FLAVOR_SLSDX, "Star Link Source Descriptor (XML)", false },
+ { PBTYPE_LSX, FLAVOR_LSX, "Star Link Source (XML)", false },
+ { PBTYPE_EOX, FLAVOR_EOX, "Star Embedded Object (XML)", false },
+ { PBTYPE_SVXB, FLAVOR_SVXB, "SVXB (StarView Bitmap/Animation", false },
+ { PBTYPE_GDIMF, FLAVOR_GDIMF, "GDIMetaFile", false },
+ { PBTYPE_SODX, FLAVOR_SODX, "Star Object Descriptor (XML)", false },
+ { PBTYPE_DUMMY_INTERNAL, FLAVOR_DUMMY_INTERNAL, "internal data", 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 UniDataProvider : public DataProviderBaseImpl
+{
+public:
+ UniDataProvider(const Any& data);
+ UniDataProvider(NSData* data);
+
+ NSData* getSystemData() override;
+ 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;
+}
+
+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 ([systemDataFlavor
+ caseInsensitiveCompare:const_cast<NSString*>(flavorMap[i].SystemFlavor)]
+ == NSOrderedSame)
+ {
+ 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)))
+ {
+ sysFlavor = flavorMap[i].SystemFlavor;
+ }
+ }
+
+ 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 ~SwIndexReg, 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(UIPasteboard* pPasteboard)
+{
+ if ([pPasteboard containsPasteboardTypes:@[ PBTYPE_PNG ]])
+ return PBTYPE_PNG;
+ else if ([pPasteboard containsPasteboardTypes:@[ PBTYPE_JPEG ]])
+ return PBTYPE_JPEG;
+ else if ([pPasteboard 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 UniDataProvider(data));
+ }
+ }
+ catch (UnsupportedFlavorException&)
+ {
+ // 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 ([systemFlavor caseInsensitiveCompare:PBTYPE_PLAINTEXT] == NSOrderedSame)
+ {
+ dp = DataProviderPtr_t(new UniDataProvider(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 (IllegalArgumentException&)
+ {
+ 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];
+ }
+ }
+ }
+
+ // #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:PBTYPE_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[flavors.getLength() - 1] = oOOFlavor;
+ SAL_INFO("vcl.ios.clipboard",
+ "Mapped " << [sysFormat UTF8String] << " to " << oOOFlavor.MimeType);
+ }
+ }
+
+ 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 000000000..7e527dc09
--- /dev/null
+++ b/vcl/ios/DataFlavorMapping.hxx
@@ -0,0 +1,127 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_VCL_IOS_DATAFLAVORMAPPING_HXX
+#define INCLUDED_VCL_IOS_DATAFLAVORMAPPING_HXX
+
+#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(UIPasteboard* 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 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;
+};
+
+#endif // INCLUDED_VCL_IOS_DATAFLAVORMAPPING_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/ios/HtmlFmtFlt.cxx b/vcl/ios/HtmlFmtFlt.cxx
new file mode 100644
index 000000000..4f90ced3b
--- /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 000000000..b11b19857
--- /dev/null
+++ b/vcl/ios/HtmlFmtFlt.hxx
@@ -0,0 +1,41 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_VCL_OSX_HTMLFMTFLT_HXX
+#define INCLUDED_VCL_OSX_HTMLFMTFLT_HXX
+
+#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);
+
+#endif // INCLUDED_VCL_OSX_HTMLFMTFLT_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/ios/clipboard.cxx b/vcl/ios/clipboard.cxx
new file mode 100644
index 000000000..b60cda1be
--- /dev/null
+++ b/vcl/ios/clipboard.cxx
@@ -0,0 +1,185 @@
+/* -*- 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 "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());
+
+ mPasteboard = [UIPasteboard generalPasteboard];
+ assert(mPasteboard != nil);
+}
+
+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, mPasteboard));
+}
+
+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;
+ }
+ }
+ [mPasteboard 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(static_cast<OWeakObject*>(this),
+ 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 css::uno::Reference<css::uno::XInterface>(
+ static_cast<cppu::OWeakObject*>(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 000000000..144e9c3ac
--- /dev/null
+++ b/vcl/ios/clipboard.hxx
@@ -0,0 +1,111 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_VCL_IOS_CLIPBOARD_HXX
+#define INCLUDED_VCL_IOS_CLIPBOARD_HXX
+
+#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;
+ UIPasteboard* mPasteboard;
+};
+
+#endif // INCLUDED_VCL_IOS_CLIPBOARD_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/ios/dummies.cxx b/vcl/ios/dummies.cxx
new file mode 100644
index 000000000..d62609dc9
--- /dev/null
+++ b/vcl/ios/dummies.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 "salprn.hxx"
+#include "quartz/salgdi.h"
+#include "headless/svpinst.hxx"
+#include "unx/fontmanager.hxx"
+#include "unx/gendata.hxx"
+
+class FreetypeManager
+{
+};
+
+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::jobStartedPrinterUpdate()
+{
+}
+
+void SalGenericInstance::jobEndedPrinterUpdate()
+{
+}
+
+bool AquaSalGraphics::drawEPS( long, long, long, long, void*, sal_uInt32 )
+{
+ return false;
+}
+
+using namespace psp;
+
+GenericUnixSalData::GenericUnixSalData(GenericUnixSalDataType const t, SalInstance *const pInstance)
+ : m_eType(t)
+ , m_pDisplay(nullptr)
+ , m_pFreetypeManager(new FreetypeManager)
+ , m_pPrintFontManager(nullptr)
+{
+ m_pInstance = pInstance;
+ SetSalData(this);
+}
+
+GenericUnixSalData::~GenericUnixSalData()
+{
+}
+
+PrintFontManager::~PrintFontManager()
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/ios/iOSTransferable.cxx b/vcl/ios/iOSTransferable.cxx
new file mode 100644
index 000000000..9ec27867f
--- /dev/null
+++ b/vcl/ios/iOSTransferable.cxx
@@ -0,0 +1,186 @@
+/* -*- 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 "iOSTransferable.hxx"
+
+#include "DataFlavorMapping.hxx"
+
+using namespace std;
+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,
+ UIPasteboard* pasteboard)
+ : mrXMimeCntFactory(rXMimeCntFactory)
+ , mDataFlavorMapper(pDataFlavorMapper)
+ , mPasteboard(pasteboard)
+{
+ [mPasteboard retain];
+
+ initClipboardItemList();
+}
+
+iOSTransferable::~iOSTransferable() { [mPasteboard release]; }
+
+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(mPasteboard)
+ : mDataFlavorMapper->openOfficeToSystemFlavor(aFlavor, bInternal);
+ DataProviderPtr_t dp;
+
+ NSData* sysData = [mPasteboard dataForPasteboardType:sysFormat];
+ 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 = [mPasteboard pasteboardTypes];
+
+ if (pboardFormats == nullptr)
+ {
+ throw RuntimeException("Cannot get clipboard data", static_cast<XTransferable*>(this));
+ }
+
+#ifdef SAL_LOG_INFO
+ NSString* types = @"";
+ for (unsigned i = 0; i < [pboardFormats count]; i++)
+ {
+ if ([types length] > 0)
+ types = [types stringByAppendingString:@", "];
+ types = [types stringByAppendingString:[pboardFormats objectAtIndex:i]];
+ }
+ SAL_INFO("vcl.ios.clipboard", "Types on clipboard: " << [types UTF8String]);
+#endif
+
+ 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 000000000..5c685dba8
--- /dev/null
+++ b/vcl/ios/iOSTransferable.hxx
@@ -0,0 +1,73 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_VCL_IOS_IOSTRANSFERABLE_HXX
+#define INCLUDED_VCL_IOS_IOSTRANSFERABLE_HXX
+
+#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, UIPasteboard* pasteboard);
+
+ 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;
+ UIPasteboard* mPasteboard;
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/ios/iosinst.cxx b/vcl/ios/iosinst.cxx
new file mode 100644
index 000000000..65963ef9c
--- /dev/null
+++ b/vcl/ios/iosinst.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 <premac.h>
+#include <UIKit/UIKit.h>
+#include <postmac.h>
+
+#include "ios/iosinst.hxx"
+#include "headless/svpdummies.hxx"
+#include "unx/gendata.hxx"
+#include "quartz/utils.h"
+#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;
+
+class IosSalData : public GenericUnixSalData
+{
+public:
+ explicit IosSalData(SalInstance *pInstance)
+ : GenericUnixSalData(SAL_DATA_IOS, pInstance)
+ {
+ }
+ virtual void ErrorTrapPush() {}
+ virtual bool ErrorTrapPop( bool ) { return false; }
+};
+
+void IosSalInstance::GetWorkArea( tools::Rectangle& rRect )
+{
+ rRect = tools::Rectangle( Point( 0, 0 ),
+ Size( viewWidth, viewHeight ) );
+}
+
+IosSalInstance *IosSalInstance::getInstance()
+{
+ if (!ImplGetSVData())
+ return NULL;
+ IosSalData *pData = static_cast<IosSalData *>(ImplGetSVData()->mpSalData);
+ if (!pData)
+ return NULL;
+ return static_cast<IosSalInstance *>(pData->m_pInstance);
+}
+
+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( tools::Rectangle& 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 )
+{
+ pParent = NULL;
+ return new IosSalFrame( this, NULL, nStyle );
+}
+
+SalFrame *IosSalInstance::CreateFrame( SalFrame* pParent, SalFrameStyleFlags nStyle )
+{
+ return new IosSalFrame( this, pParent, nStyle );
+}
+
+void SalAbort( const OUString& rErrorText, bool bDumpCore )
+{
+ (void) bDumpCore;
+
+ NSLog(@"SalAbort: %s", OUStringToOString(rErrorText, osl_getThreadTextEncoding()).getStr() );
+}
+
+const OUString& SalGetDesktopEnvironment()
+{
+ static OUString aEnv( "iOS" );
+ return aEnv;
+}
+
+SalData::SalData() :
+ m_pInstance( 0 ),
+ mpFontList( 0 ),
+ mxRGBSpace( CGColorSpaceCreateDeviceRGB() ),
+ mxGraySpace( CGColorSpaceCreateDeviceGray() )
+{
+}
+
+SalData::~SalData()
+{
+}
+
+// This is our main entry point:
+SalInstance *CreateSalInstance()
+{
+ IosSalInstance* pInstance = new IosSalInstance( std::make_unique<SvpSalYieldMutex>() );
+ new IosSalData( pInstance );
+ pInstance->AcquireYieldMutex();
+ return pInstance;
+}
+
+void DestroySalInstance( SalInstance *pInst )
+{
+ pInst->ReleaseYieldMutexAll();
+ delete pInst;
+}
+
+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/jsdialog/jsdialogbuilder.cxx b/vcl/jsdialog/jsdialogbuilder.cxx
new file mode 100644
index 000000000..759007ce4
--- /dev/null
+++ b/vcl/jsdialog/jsdialogbuilder.cxx
@@ -0,0 +1,207 @@
+#include <jsdialog/jsdialogbuilder.hxx>
+#include <sal/log.hxx>
+#include <boost/property_tree/json_parser.hpp>
+#include <comphelper/lok.hxx>
+#include <vcl/tabpage.hxx>
+#include <vcl/toolkit/dialog.hxx>
+#include <LibreOfficeKit/LibreOfficeKitEnums.h>
+
+void JSDialogSender::notifyDialogState()
+{
+ if (!m_aOwnedToplevel)
+ return;
+
+ const vcl::ILibreOfficeKitNotifier* pNotifier = m_aOwnedToplevel->GetLOKNotifier();
+ if (pNotifier)
+ {
+ std::stringstream aStream;
+ boost::property_tree::ptree aTree = m_aOwnedToplevel->DumpAsPropertyTree();
+ aTree.put("id", m_aOwnedToplevel->GetLOKWindowId());
+ boost::property_tree::write_json(aStream, aTree);
+ const std::string message = aStream.str();
+ pNotifier->libreOfficeKitViewCallback(LOK_CALLBACK_JSDIALOG, message.c_str());
+ }
+}
+
+JSInstanceBuilder::JSInstanceBuilder(weld::Widget* pParent, const OUString& rUIRoot,
+ const OUString& rUIFile)
+ : SalInstanceBuilder(dynamic_cast<SalInstanceWidget*>(pParent)
+ ? dynamic_cast<SalInstanceWidget*>(pParent)->getWidget()
+ : nullptr,
+ rUIRoot, rUIFile)
+{
+}
+
+std::unique_ptr<weld::Dialog> JSInstanceBuilder::weld_dialog(const OString& id, bool bTakeOwnership)
+{
+ ::Dialog* pDialog = m_xBuilder->get<::Dialog>(id);
+ std::unique_ptr<weld::Dialog> pRet(pDialog ? new SalInstanceDialog(pDialog, this, false)
+ : nullptr);
+ if (bTakeOwnership && pDialog)
+ {
+ assert(!m_aOwnedToplevel && "only one toplevel per .ui allowed");
+ m_aOwnedToplevel.set(pDialog);
+ m_xBuilder->drop_ownership(pDialog);
+ }
+
+ const vcl::ILibreOfficeKitNotifier* pNotifier = pDialog->GetLOKNotifier();
+ if (pNotifier)
+ {
+ std::stringstream aStream;
+ boost::property_tree::ptree aTree = m_aOwnedToplevel->DumpAsPropertyTree();
+ aTree.put("id", m_aOwnedToplevel->GetLOKWindowId());
+ boost::property_tree::write_json(aStream, aTree);
+ const std::string message = aStream.str();
+ pNotifier->libreOfficeKitViewCallback(LOK_CALLBACK_JSDIALOG, message.c_str());
+ }
+
+ return pRet;
+}
+
+std::unique_ptr<weld::Label> JSInstanceBuilder::weld_label(const OString& id, bool bTakeOwnership)
+{
+ ::FixedText* pLabel = m_xBuilder->get<FixedText>(id);
+ return std::make_unique<JSLabel>(m_aOwnedToplevel, pLabel, this, bTakeOwnership);
+}
+
+std::unique_ptr<weld::Button> JSInstanceBuilder::weld_button(const OString& id, bool bTakeOwnership)
+{
+ ::Button* pButton = m_xBuilder->get<::Button>(id);
+ return pButton ? std::make_unique<JSButton>(m_aOwnedToplevel, pButton, this, bTakeOwnership)
+ : nullptr;
+}
+
+std::unique_ptr<weld::Entry> JSInstanceBuilder::weld_entry(const OString& id, bool bTakeOwnership)
+{
+ Edit* pEntry = m_xBuilder->get<Edit>(id);
+ return pEntry ? std::make_unique<JSEntry>(m_aOwnedToplevel, pEntry, this, bTakeOwnership)
+ : nullptr;
+}
+
+std::unique_ptr<weld::ComboBox> JSInstanceBuilder::weld_combo_box(const OString& id,
+ bool bTakeOwnership)
+{
+ vcl::Window* pWidget = m_xBuilder->get<vcl::Window>(id);
+ ::ComboBox* pComboBox = dynamic_cast<::ComboBox*>(pWidget);
+ if (pComboBox)
+ return std::make_unique<JSComboBox>(m_aOwnedToplevel, pComboBox, this, bTakeOwnership);
+ ListBox* pListBox = dynamic_cast<ListBox*>(pWidget);
+ return pListBox ? std::make_unique<JSListBox>(m_aOwnedToplevel, pListBox, this, bTakeOwnership)
+ : nullptr;
+}
+
+std::unique_ptr<weld::Notebook> JSInstanceBuilder::weld_notebook(const OString& id,
+ bool bTakeOwnership)
+{
+ TabControl* pNotebook = m_xBuilder->get<TabControl>(id);
+ return pNotebook
+ ? std::make_unique<JSNotebook>(m_aOwnedToplevel, pNotebook, this, bTakeOwnership)
+ : nullptr;
+}
+
+JSLabel::JSLabel(VclPtr<vcl::Window> aOwnedToplevel, FixedText* pLabel,
+ SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : JSWidget<SalInstanceLabel, FixedText>(aOwnedToplevel, pLabel, pBuilder, bTakeOwnership)
+{
+}
+
+void JSLabel::set_label(const OUString& rText)
+{
+ SalInstanceLabel::set_label(rText);
+ notifyDialogState();
+};
+
+JSButton::JSButton(VclPtr<vcl::Window> aOwnedToplevel, ::Button* pButton,
+ SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : JSWidget<SalInstanceButton, ::Button>(aOwnedToplevel, pButton, pBuilder, bTakeOwnership)
+{
+}
+
+JSEntry::JSEntry(VclPtr<vcl::Window> aOwnedToplevel, ::Edit* pEntry, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : JSWidget<SalInstanceEntry, ::Edit>(aOwnedToplevel, pEntry, pBuilder, bTakeOwnership)
+{
+}
+
+void JSEntry::set_text(const OUString& rText)
+{
+ SalInstanceEntry::set_text(rText);
+ notifyDialogState();
+}
+
+JSListBox::JSListBox(VclPtr<vcl::Window> aOwnedToplevel, ::ListBox* pListBox,
+ SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : JSWidget<SalInstanceComboBoxWithoutEdit, ::ListBox>(aOwnedToplevel, 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);
+ notifyDialogState();
+}
+
+void JSListBox::remove(int pos)
+{
+ SalInstanceComboBoxWithoutEdit::remove(pos);
+ notifyDialogState();
+}
+
+JSComboBox::JSComboBox(VclPtr<vcl::Window> aOwnedToplevel, ::ComboBox* pComboBox,
+ SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : JSWidget<SalInstanceComboBoxWithEdit, ::ComboBox>(aOwnedToplevel, 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);
+ notifyDialogState();
+}
+
+void JSComboBox::remove(int pos)
+{
+ SalInstanceComboBoxWithEdit::remove(pos);
+ notifyDialogState();
+}
+
+void JSComboBox::set_entry_text(const OUString& rText)
+{
+ SalInstanceComboBoxWithEdit::set_entry_text(rText);
+ notifyDialogState();
+}
+
+JSNotebook::JSNotebook(VclPtr<vcl::Window> aOwnedToplevel, ::TabControl* pControl,
+ SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : JSWidget<SalInstanceNotebook, ::TabControl>(aOwnedToplevel, pControl, pBuilder,
+ bTakeOwnership)
+{
+}
+
+void JSNotebook::set_current_page(int nPage)
+{
+ SalInstanceNotebook::set_current_page(nPage);
+ notifyDialogState();
+}
+
+void JSNotebook::set_current_page(const OString& rIdent)
+{
+ SalInstanceNotebook::set_current_page(rIdent);
+ notifyDialogState();
+}
+
+void JSNotebook::remove_page(const OString& rIdent)
+{
+ SalInstanceNotebook::remove_page(rIdent);
+ notifyDialogState();
+}
+
+void JSNotebook::insert_page(const OString& rIdent, const OUString& rLabel, int nPos)
+{
+ SalInstanceNotebook::insert_page(rIdent, rLabel, nPos);
+ notifyDialogState();
+}
diff --git a/vcl/null/printerinfomanager.cxx b/vcl/null/printerinfomanager.cxx
new file mode 100644
index 000000000..cfdb6eb67
--- /dev/null
+++ b/vcl/null/printerinfomanager.cxx
@@ -0,0 +1,120 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <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()
+{
+ SalData* pSalData = GetSalData();
+ if( ! pSalData->m_pPIManager )
+ pSalData->m_pPIManager = new PrinterInfoManager();
+ return *pSalData->m_pPIManager;
+}
+
+void PrinterInfoManager::release()
+{
+ SalData* pSalData = GetSalData();
+ delete pSalData->m_pPIManager;
+ pSalData->m_pPIManager = nullptr;
+}
+
+PrinterInfoManager::PrinterInfoManager( Type eType ) :
+ m_pQueueInfo( nullptr ),
+ m_eType( eType ),
+ m_bUseIncludeFeature( false ),
+ m_bUseJobPatch( true ),
+ 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 */, const char* /* 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/opengl/DeviceInfo.cxx b/vcl/opengl/DeviceInfo.cxx
new file mode 100644
index 000000000..24f42e34e
--- /dev/null
+++ b/vcl/opengl/DeviceInfo.cxx
@@ -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 <opengl/DeviceInfo.hxx>
+
+OpenGLDeviceInfo::~OpenGLDeviceInfo()
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/FixedTextureAtlas.cxx b/vcl/opengl/FixedTextureAtlas.cxx
new file mode 100644
index 000000000..7425942d1
--- /dev/null
+++ b/vcl/opengl/FixedTextureAtlas.cxx
@@ -0,0 +1,137 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 <sal/config.h>
+#include <vcl/opengl/OpenGLContext.hxx>
+#include <vcl/opengl/OpenGLHelper.hxx>
+
+#include <opengl/framebuffer.hxx>
+#include <opengl/texture.hxx>
+
+#include <opengl/FixedTextureAtlas.hxx>
+
+struct FixedTexture
+{
+ std::shared_ptr<ImplOpenGLTexture> mpTexture;
+ int mnFreeSlots;
+ std::vector<bool> maAllocatedSlots;
+
+ FixedTexture(int nTextureWidth, int nTextureHeight, int nNumberOfSlots)
+ : mpTexture(std::make_shared<ImplOpenGLTexture>(nTextureWidth, nTextureHeight, true))
+ , mnFreeSlots(nNumberOfSlots)
+ , maAllocatedSlots(nNumberOfSlots, false)
+ {
+ auto aDeallocateFunction = [this] (int nSlotNumber)
+ {
+ deallocateSlot(nSlotNumber);
+ };
+
+ mpTexture->SetSlotDeallocateCallback(aDeallocateFunction);
+ mpTexture->InitializeSlotMechanism(nNumberOfSlots);
+ }
+
+ ~FixedTexture()
+ {
+ mpTexture->ResetSlotDeallocateCallback();
+ }
+
+ void allocateSlot(int nSlot)
+ {
+ maAllocatedSlots[nSlot] = true;
+ mnFreeSlots--;
+ }
+
+ void deallocateSlot(int nSlot)
+ {
+ maAllocatedSlots[nSlot] = false;
+ mnFreeSlots++;
+ }
+
+ int findAndAllocateFreeSlot()
+ {
+ for (size_t i = 0; i < maAllocatedSlots.size(); ++i)
+ {
+ if (!maAllocatedSlots[i])
+ {
+ allocateSlot(i);
+ return i;
+ }
+ }
+ return -1;
+ }
+
+private:
+ FixedTexture(const FixedTexture&) = delete;
+ FixedTexture& operator=(const FixedTexture&) = delete;
+};
+
+FixedTextureAtlasManager::FixedTextureAtlasManager(int nWidthFactor, int nHeightFactor, int nSubTextureSize)
+ : mWidthFactor(nWidthFactor)
+ , mHeightFactor(nHeightFactor)
+ , mSubTextureSize(nSubTextureSize)
+{
+}
+
+FixedTextureAtlasManager::~FixedTextureAtlasManager()
+{
+}
+
+void FixedTextureAtlasManager::CreateNewTexture()
+{
+ int nTextureWidth = mWidthFactor * mSubTextureSize;
+ int nTextureHeight = mHeightFactor * mSubTextureSize;
+ maFixedTextures.push_back(std::make_unique<FixedTexture>(nTextureWidth, nTextureHeight, mWidthFactor * mHeightFactor));
+}
+
+OpenGLTexture FixedTextureAtlasManager::Reserve(int nWidth, int nHeight)
+{
+ FixedTexture* pFixedTexture = nullptr;
+
+ auto funFreeSlot = [] (std::unique_ptr<FixedTexture>& inFixedTexture)
+ {
+ return inFixedTexture->mnFreeSlots > 0;
+ };
+
+ auto it = std::find_if(maFixedTextures.begin(), maFixedTextures.end(), funFreeSlot);
+
+ if (it != maFixedTextures.end())
+ {
+ pFixedTexture = (*it).get();
+ }
+ else
+ {
+ CreateNewTexture();
+ pFixedTexture = maFixedTextures.back().get();
+ }
+
+ int nSlot = pFixedTexture->findAndAllocateFreeSlot();
+
+ // Calculate coordinates in texture
+ int nX = (nSlot % mWidthFactor) * mSubTextureSize;
+ int nY = (nSlot / mWidthFactor) * mSubTextureSize;
+
+ tools::Rectangle aRectangle(Point(nX, nY), Size(nWidth, nHeight));
+
+ return OpenGLTexture(pFixedTexture->mpTexture, aRectangle, nSlot);
+}
+
+OpenGLTexture FixedTextureAtlasManager::InsertBuffer(int nWidth, int nHeight, int nFormat, int nType, sal_uInt8 const * pData)
+{
+ OpenGLTexture aTexture = Reserve(nWidth, nHeight);
+ if (pData == nullptr)
+ return aTexture;
+
+ aTexture.CopyData(nWidth, nHeight, nFormat, nType, pData);
+
+ return aTexture;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/LineRenderUtils.cxx b/vcl/opengl/LineRenderUtils.cxx
new file mode 100644
index 000000000..e130fb93b
--- /dev/null
+++ b/vcl/opengl/LineRenderUtils.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 <opengl/LineRenderUtils.hxx>
+#include <opengl/VertexUtils.hxx>
+
+namespace vcl
+{
+
+LineBuilder::LineBuilder(std::vector<Vertex>& rVertices, std::vector<GLuint>& rIndices,
+ Color nColor, GLfloat fTransparency,
+ GLfloat fLineWidth, bool bUseAA)
+ : mrVertices(rVertices)
+ , mrIndices(rIndices)
+ , mR(nColor.GetRed())
+ , mG(nColor.GetGreen())
+ , mB(nColor.GetBlue())
+ , mA((1.0f - fTransparency) * 255.0f)
+ , mfLineWidth(fLineWidth)
+ , mfLineWidthAndAA(bUseAA ? fLineWidth : -fLineWidth)
+ , mnInitialIndexSize(rIndices.size())
+ , mbIncomplete(false)
+{
+}
+
+void LineBuilder::appendLineSegment(const glm::vec2& rPoint1, const glm::vec2& rNormal1, GLfloat aExtrusion1,
+ const glm::vec2& rPoint2, const glm::vec2& rNormal2, GLfloat aExtrusion2)
+{
+ GLuint zero = mrVertices.size();
+
+ mrVertices.insert(mrVertices.end(), {
+ {rPoint1, glm::vec4{mR, mG, mB, mA}, glm::vec4{-rNormal1.x, -rNormal1.y, -aExtrusion1, mfLineWidthAndAA}},
+ {rPoint1, glm::vec4{mR, mG, mB, mA}, glm::vec4{ rNormal1.x, rNormal1.y, aExtrusion1, mfLineWidthAndAA}},
+ {rPoint2, glm::vec4{mR, mG, mB, mA}, glm::vec4{-rNormal2.x, -rNormal2.y, -aExtrusion2, mfLineWidthAndAA}},
+ {rPoint2, glm::vec4{mR, mG, mB, mA}, glm::vec4{ rNormal2.x, rNormal2.y, aExtrusion2, mfLineWidthAndAA}},
+ });
+
+ mrIndices.insert(mrIndices.end(), {
+ zero + 0, zero + 1, zero + 2,
+ zero + 2, zero + 1, zero + 3
+ });
+
+}
+
+void LineBuilder::appendLine(const glm::vec2& rPoint1, const glm::vec2& rPoint2)
+{
+ glm::vec2 aLineVector = vcl::vertex::normalize(rPoint2 - rPoint1);
+ glm::vec2 aNormal = vcl::vertex::perpendicular(aLineVector);
+
+ appendLineSegment(rPoint1, aNormal, 1.0f,
+ rPoint2, aNormal, 1.0f);
+}
+
+void LineBuilder::appendAndConnectLinePoint(const glm::vec2& rPoint, const glm::vec2& aNormal, GLfloat aExtrusion)
+{
+ GLuint zero = mrVertices.size();
+
+ mrVertices.insert(mrVertices.end(), {
+ {rPoint, glm::vec4{mR, mG, mB, mA}, glm::vec4{-aNormal.x, -aNormal.y, -aExtrusion, mfLineWidthAndAA}},
+ {rPoint, glm::vec4{mR, mG, mB, mA}, glm::vec4{ aNormal.x, aNormal.y, aExtrusion, mfLineWidthAndAA}},
+ });
+
+ if (mnInitialIndexSize == mrIndices.size())
+ {
+ mrIndices.insert(mrIndices.end(), {
+ zero + 0, zero + 1
+ });
+ mbIncomplete = true;
+ }
+ else
+ {
+ if (mbIncomplete)
+ {
+ mrIndices.insert(mrIndices.end(), {
+ zero + 0,
+ zero + 0, zero - 1, zero + 1
+ });
+ mbIncomplete = false;
+ }
+ else
+ {
+ mrIndices.insert(mrIndices.end(), {
+ zero - 2, zero - 1, zero + 0,
+ zero + 0, zero - 1, zero + 1
+ });
+ }
+ }
+}
+
+void LineBuilder::appendMiterJoint(glm::vec2 const& point, const glm::vec2& prevLineVector,
+ glm::vec2 const& nextLineVector)
+{
+ // With miter join we calculate the extrusion vector by adding normals of
+ // previous and next line segment. The vector shows the way but we also
+ // need the length (otherwise the line will be deformed). Length factor is
+ // calculated as dot product of extrusion vector and one of the normals.
+ // The value we get is the inverse length (used in the shader):
+ // length = line_width / dot(extrusionVector, normal)
+
+ glm::vec2 normal(-prevLineVector.y, prevLineVector.x);
+
+ glm::vec2 tangent = vcl::vertex::normalize(nextLineVector + prevLineVector);
+ glm::vec2 extrusionVector(-tangent.y, tangent.x);
+ GLfloat length = glm::dot(extrusionVector, normal);
+
+ appendAndConnectLinePoint(point, extrusionVector, length);
+}
+
+void LineBuilder::appendBevelJoint(glm::vec2 const& point, const glm::vec2& prevLineVector,
+ const glm::vec2& nextLineVector)
+{
+ // For bevel join we just add 2 additional vertices and use previous
+ // line segment normal and next line segment normal as extrusion vector.
+ // All the magic is done by the fact that we draw triangle strips, so we
+ // cover the joins correctly.
+
+ glm::vec2 prevNormal(-prevLineVector.y, prevLineVector.x);
+ glm::vec2 nextNormal(-nextLineVector.y, nextLineVector.x);
+
+ appendAndConnectLinePoint(point, prevNormal, 1.0f);
+ appendAndConnectLinePoint(point, nextNormal, 1.0f);
+}
+
+void LineBuilder::appendRoundJoint(glm::vec2 const& point, const glm::vec2& prevLineVector,
+ const glm::vec2& nextLineVector)
+{
+ // For round join we do a similar thing as in bevel, we add more intermediate
+ // vertices and add normals to get extrusion vectors in the between the
+ // both normals.
+
+ // 3 additional extrusion vectors + normals are enough to make most
+ // line joins look round. Ideally the number of vectors could be
+ // calculated.
+
+ glm::vec2 prevNormal(-prevLineVector.y, prevLineVector.x);
+ glm::vec2 nextNormal(-nextLineVector.y, nextLineVector.x);
+
+ glm::vec2 middle = vcl::vertex::normalize(prevNormal + nextNormal);
+ glm::vec2 middleLeft = vcl::vertex::normalize(prevNormal + middle);
+ glm::vec2 middleRight = vcl::vertex::normalize(middle + nextNormal);
+
+ appendAndConnectLinePoint(point, prevNormal, 1.0f);
+ appendAndConnectLinePoint(point, middleLeft, 1.0f);
+ appendAndConnectLinePoint(point, middle, 1.0f);
+ appendAndConnectLinePoint(point, middleRight, 1.0f);
+ appendAndConnectLinePoint(point, nextNormal, 1.0f);
+}
+
+void LineBuilder::appendRoundLineCapVertices(const glm::vec2& rPoint1, const glm::vec2& rPoint2)
+{
+ constexpr int nRoundCapIteration = 12;
+
+ glm::vec2 lineVector = vcl::vertex::normalize(rPoint2 - rPoint1);
+ glm::vec2 normal(-lineVector.y, lineVector.x);
+ glm::vec2 previousRoundNormal = normal;
+
+ for (int nFactor = 1; nFactor <= nRoundCapIteration; nFactor++)
+ {
+ float angle = float(nFactor) * (M_PI / float(nRoundCapIteration));
+ glm::vec2 roundNormal(normal.x * glm::cos(angle) - normal.y * glm::sin(angle),
+ normal.x * glm::sin(angle) + normal.y * glm::cos(angle));
+
+ appendLineSegment(rPoint1, previousRoundNormal, 1.0f,
+ rPoint1, roundNormal, 1.0f);
+ previousRoundNormal = roundNormal;
+ }
+}
+
+void LineBuilder::appendSquareLineCapVertices(const glm::vec2& rPoint1, const glm::vec2& rPoint2)
+{
+ glm::vec2 lineVector = vcl::vertex::normalize(rPoint2 - rPoint1);
+ glm::vec2 normal(-lineVector.y, lineVector.x);
+
+ glm::vec2 extrudedPoint = rPoint1 + -lineVector * (mfLineWidth / 2.0f);
+
+ appendLineSegment(extrudedPoint, normal, 1.0f,
+ rPoint1, normal, 1.0f);
+}
+
+} // end vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/PackedTextureAtlas.cxx b/vcl/opengl/PackedTextureAtlas.cxx
new file mode 100644
index 000000000..8508bbe3c
--- /dev/null
+++ b/vcl/opengl/PackedTextureAtlas.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 <memory>
+#include <sal/config.h>
+#include <vcl/opengl/OpenGLContext.hxx>
+#include <vcl/opengl/OpenGLHelper.hxx>
+
+#include <opengl/framebuffer.hxx>
+#include <opengl/texture.hxx>
+
+#include <opengl/PackedTextureAtlas.hxx>
+
+namespace {
+
+struct Node
+{
+ tools::Rectangle mRectangle;
+ std::unique_ptr<Node> mLeftNode;
+ std::unique_ptr<Node> mRightNode;
+ bool mOccupied;
+
+ explicit Node(int nWidth, int nHeight);
+ explicit Node(tools::Rectangle const & aRectangle);
+
+ bool isLeaf() const;
+ Node* insert(int nWidth, int nHeight, int nPadding);
+};
+
+}
+
+Node::Node(int nWidth, int nHeight)
+ : mRectangle(tools::Rectangle(Point(), Size(nWidth, nHeight)))
+ , mLeftNode()
+ , mRightNode()
+ , mOccupied(false)
+{}
+
+Node::Node(tools::Rectangle const & aRectangle)
+ : mRectangle(aRectangle)
+ , mLeftNode()
+ , mRightNode()
+ , mOccupied(false)
+{}
+
+bool Node::isLeaf() const { return mLeftNode == nullptr && mRightNode == nullptr; }
+
+Node* Node::insert(int nWidth, int nHeight, int nPadding)
+{
+ if (!isLeaf())
+ {
+ Node* pNewNode = mLeftNode->insert(nWidth, nHeight, nPadding);
+
+ if (pNewNode != nullptr)
+ return pNewNode;
+
+ return mRightNode->insert(nWidth, nHeight, nPadding);
+ }
+ else
+ {
+ if (mOccupied)
+ {
+ return nullptr;
+ }
+
+ if (nWidth > mRectangle.GetWidth() || nHeight > mRectangle.GetHeight())
+ { // does not fit
+ return nullptr;
+ }
+
+ if (nWidth == mRectangle.GetWidth() && nHeight == mRectangle.GetHeight())
+ { // perfect fit
+ mOccupied = true;
+ return this;
+ }
+
+ int dw = mRectangle.GetWidth() - nWidth;
+ int dh = mRectangle.GetHeight() - nHeight;
+
+ tools::Rectangle aLeftRect;
+ tools::Rectangle aRightRect;
+ if (dw > dh)
+ {
+ aLeftRect = tools::Rectangle(Point(mRectangle.Left(), mRectangle.Top()),
+ Size(nWidth, mRectangle.GetHeight()));
+ aRightRect = tools::Rectangle(Point(nPadding + mRectangle.Left() + nWidth, mRectangle.Top()),
+ Size(mRectangle.GetWidth() - nWidth - nPadding, mRectangle.GetHeight()));
+ }
+ else
+ {
+ aLeftRect = tools::Rectangle(Point(mRectangle.Left(), mRectangle.Top()),
+ Size(mRectangle.GetWidth(), nHeight));
+ aRightRect = tools::Rectangle(Point(mRectangle.Left(), nPadding + mRectangle.Top() + nHeight),
+ Size(mRectangle.GetWidth(), mRectangle.GetHeight() - nHeight - nPadding));
+ }
+
+ mLeftNode.reset(new Node(aLeftRect));
+ mRightNode.reset(new Node(aRightRect));
+
+ return mLeftNode->insert(nWidth, nHeight, nPadding);
+ }
+}
+
+struct PackedTexture
+{
+ std::shared_ptr<ImplOpenGLTexture> mpTexture;
+ std::unique_ptr<Node> mpRootNode;
+
+ PackedTexture(int nWidth, int nHeight)
+ : mpTexture(std::make_shared<ImplOpenGLTexture>(nWidth, nHeight, true))
+ , mpRootNode(new Node(nWidth, nHeight))
+ {}
+};
+
+PackedTextureAtlasManager::PackedTextureAtlasManager(int nTextureWidth, int nTextureHeight)
+ : mnTextureWidth(nTextureWidth)
+ , mnTextureHeight(nTextureHeight)
+{
+}
+
+PackedTextureAtlasManager::~PackedTextureAtlasManager()
+{
+ for (std::unique_ptr<PackedTexture>& pPackedTexture : maPackedTextures)
+ {
+ // Free texture early in VCL shutdown while we have a context.
+ pPackedTexture->mpTexture.reset();
+ }
+}
+
+void PackedTextureAtlasManager::CreateNewTexture()
+{
+ std::unique_ptr<PackedTexture> pPackedTexture(new PackedTexture(mnTextureWidth, mnTextureHeight));
+ GLuint nTextureID = pPackedTexture->mpTexture->mnTexture;
+ maPackedTextures.push_back(std::move(pPackedTexture));
+ VCL_GL_INFO("PackedTextureAtlas::CreateNewTexture adding texture: " << nTextureID <<
+ " atlases: " << maPackedTextures.size());
+}
+
+OpenGLTexture PackedTextureAtlasManager::Reserve(int nWidth, int nHeight)
+{
+ for (std::unique_ptr<PackedTexture>& pPackedTexture : maPackedTextures)
+ {
+ Node* pNode = pPackedTexture->mpRootNode->insert(nWidth, nHeight, 1);
+ if (pNode != nullptr)
+ {
+ return OpenGLTexture(pPackedTexture->mpTexture, pNode->mRectangle, -1);
+ }
+ }
+ CreateNewTexture();
+ std::unique_ptr<PackedTexture>& pPackedTexture = maPackedTextures.back();
+ Node* pNode = pPackedTexture->mpRootNode->insert(nWidth, nHeight, 1);
+ if (pNode != nullptr)
+ {
+ return OpenGLTexture(pPackedTexture->mpTexture, pNode->mRectangle, -1);
+ }
+ return OpenGLTexture();
+}
+
+OpenGLTexture PackedTextureAtlasManager::InsertBuffer(int nWidth, int nHeight, int nFormat, int nType, sal_uInt8 const * pData)
+{
+ OpenGLTexture aTexture = Reserve(nWidth, nHeight);
+ if (aTexture && pData == nullptr)
+ return aTexture;
+
+ aTexture.CopyData(nWidth, nHeight, nFormat, nType, pData);
+
+ return aTexture;
+}
+
+std::vector<GLuint> PackedTextureAtlasManager::ReduceTextureNumber(int nMaxNumberOfTextures)
+{
+ std::vector<GLuint> aTextureIDs;
+ while (int(maPackedTextures.size()) > nMaxNumberOfTextures)
+ {
+ // Remove oldest created texture
+ GLuint nTextureID = maPackedTextures[0]->mpTexture->mnTexture;
+ aTextureIDs.push_back(nTextureID);
+ maPackedTextures.erase(maPackedTextures.begin());
+ VCL_GL_INFO("PackedTextureAtlas::ReduceTextureNumber removing texture: " << nTextureID <<
+ " atlases: " << maPackedTextures.size());
+ }
+ return aTextureIDs;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/README.deprecated b/vcl/opengl/README.deprecated
new file mode 100644
index 000000000..eb033a0fd
--- /dev/null
+++ b/vcl/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/opengl/README.opengl b/vcl/opengl/README.opengl
new file mode 100644
index 000000000..231abbf36
--- /dev/null
+++ b/vcl/opengl/README.opengl
@@ -0,0 +1,26 @@
+Run LO with OpenGL enabled
+--------------------------
+SAL_USE_VCLPLUGIN=gen SAL_FORCEGL=1 ./soffice
+
+Environment variables used:
+
+SAL_USE_VCLPLUGIN - use the specified VCL plugin (GTK2 in this case - currently
+needed on Linux because the default GTK3 doesn't support OpenGL yet)
+
+SAL_FORCEGL - enable OpenGL even if the card is blacklisted.
+
+Other variables:
+
+LIBGL_ALWAYS_SOFTWARE=1 - on Linux+Mesa forces the software renderer to be used
+(this is useful as an alternative to spot driver specific bugs)
+
+SAL_LOG=+INFO.vcl.opengl - if "--enable-dbgutil" is used, this can show OpenGL
+various rendering messages.
+
+LD_PRELOAD=/usr/lib64/apitrace/wrappers/glxtrace.so - preload the wrapper for
+APItrace. The path is the default used in Fedora 21+.
+
+Run VCLDemo
+-----------
+
+SAL_USE_VCLPLUGIN=gen SAL_FORCEGL=1 ./bin/run vcldemo
diff --git a/vcl/opengl/RenderList.cxx b/vcl/opengl/RenderList.cxx
new file mode 100644
index 000000000..4830f1040
--- /dev/null
+++ b/vcl/opengl/RenderList.cxx
@@ -0,0 +1,403 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/RenderList.hxx>
+#include <opengl/VertexUtils.hxx>
+#include <opengl/LineRenderUtils.hxx>
+
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolygontriangulator.hxx>
+#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
+#include <basegfx/polygon/b2dtrapezoid.hxx>
+
+namespace
+{
+
+/** Append vertices for the polyline
+ *
+ * OpenGL polyline drawing algorithm inspired by:
+ * - http://mattdesl.svbtle.com/drawing-lines-is-hard
+ * - https://www.mapbox.com/blog/drawing-antialiased-lines/
+ * - https://cesiumjs.org/2013/04/22/Robust-Polyline-Rendering-with-WebGL/
+ * - http://artgrammer.blogspot.si/2011/05/drawing-nearly-perfect-2d-line-segments.html
+ * - http://artgrammer.blogspot.si/2011/07/drawing-polylines-by-tessellation.html
+ *
+ */
+void appendPolyLine(vcl::LineBuilder& rBuilder, const basegfx::B2DPolygon& rPolygon,
+ basegfx::B2DLineJoin eLineJoin, css::drawing::LineCap eLineCap,
+ double fMiterMinimumAngle)
+{
+ sal_uInt32 nPoints = rPolygon.count();
+ bool bClosed = rPolygon.isClosed();
+
+ if (nPoints == 2 || eLineJoin == basegfx::B2DLineJoin::NONE)
+ {
+ // If line joint is NONE or a simple line with 2 points, draw the polyline
+ // each line segment separately.
+
+ for (sal_uInt32 i = 0; i < (bClosed ? nPoints : nPoints - 1); ++i)
+ {
+ sal_uInt32 index1 = (i + 0) % nPoints; // loop indices - important when polyline is closed
+ sal_uInt32 index2 = (i + 1) % nPoints;
+
+ glm::vec2 aPoint1(rPolygon.getB2DPoint(index1).getX(), rPolygon.getB2DPoint(index1).getY());
+ glm::vec2 aPoint2(rPolygon.getB2DPoint(index2).getX(), rPolygon.getB2DPoint(index2).getY());
+
+ rBuilder.appendLine(aPoint1, aPoint2);
+ }
+ }
+ else if (nPoints > 2)
+ {
+ int i = 0;
+ int lastPoint = int(nPoints);
+
+ glm::vec2 p0(rPolygon.getB2DPoint(nPoints - 1).getX(), rPolygon.getB2DPoint(nPoints - 1).getY());
+ glm::vec2 p1(rPolygon.getB2DPoint(0).getX(), rPolygon.getB2DPoint(0).getY());
+ glm::vec2 p2(rPolygon.getB2DPoint(1).getX(), rPolygon.getB2DPoint(1).getY());
+
+ glm::vec2 nextLineVector;
+ glm::vec2 previousLineVector;
+ glm::vec2 normal; // perpendicular to the line vector
+
+ nextLineVector = vcl::vertex::normalize(p2 - p1);
+
+ if (!bClosed)
+ {
+ normal = glm::vec2(-nextLineVector.y, nextLineVector.x); // make perpendicular
+ rBuilder.appendAndConnectLinePoint(p1, normal, 1.0f);
+
+ i++; // first point done already
+ lastPoint--; // last point will be calculated separately from the loop
+
+ p0 = p1;
+ previousLineVector = nextLineVector;
+ }
+ else
+ {
+ lastPoint++; // we need to connect last point to first point so one more line segment to calculate
+ previousLineVector = vcl::vertex::normalize(p1 - p0);
+ }
+
+ for (; i < lastPoint; ++i)
+ {
+ int index1 = (i + 0) % nPoints; // loop indices - important when polyline is closed
+ int index2 = (i + 1) % nPoints;
+
+ p1 = glm::vec2(rPolygon.getB2DPoint(index1).getX(), rPolygon.getB2DPoint(index1).getY());
+ p2 = glm::vec2(rPolygon.getB2DPoint(index2).getX(), rPolygon.getB2DPoint(index2).getY());
+
+ if (p1 == p2) // skip equal points, normals could div-by-0
+ continue;
+
+ nextLineVector = vcl::vertex::normalize(p2 - p1);
+
+ if (eLineJoin == basegfx::B2DLineJoin::Miter)
+ {
+ if (vcl::vertex::lineVectorAngle(previousLineVector, nextLineVector) < fMiterMinimumAngle)
+ rBuilder.appendBevelJoint(p1, previousLineVector, nextLineVector);
+ else
+ rBuilder.appendMiterJoint(p1, previousLineVector, nextLineVector);
+ }
+ else if (eLineJoin == basegfx::B2DLineJoin::Bevel)
+ {
+ rBuilder.appendBevelJoint(p1, previousLineVector, nextLineVector);
+ }
+ else if (eLineJoin == basegfx::B2DLineJoin::Round)
+ {
+ rBuilder.appendRoundJoint(p1, previousLineVector, nextLineVector);
+ }
+ p0 = p1;
+ previousLineVector = nextLineVector;
+ }
+
+ if (!bClosed)
+ {
+ // Create vertices for the last point. There is no line join so just
+ // use the last line segment normal as the extrusion vector.
+ p1 = glm::vec2(rPolygon.getB2DPoint(nPoints - 1).getX(), rPolygon.getB2DPoint(nPoints - 1).getY());
+ normal = glm::vec2(-previousLineVector.y, previousLineVector.x);
+ rBuilder.appendAndConnectLinePoint(p1, normal, 1.0f);
+ }
+ }
+
+ if (!bClosed && nPoints >= 2 && (eLineCap == css::drawing::LineCap_ROUND || eLineCap == css::drawing::LineCap_SQUARE))
+ {
+ glm::vec2 aBeginCapPoint1(rPolygon.getB2DPoint(0).getX(), rPolygon.getB2DPoint(0).getY());
+ glm::vec2 aBeginCapPoint2(rPolygon.getB2DPoint(1).getX(), rPolygon.getB2DPoint(1).getY());
+
+ glm::vec2 aEndCapPoint1(rPolygon.getB2DPoint(nPoints - 1).getX(), rPolygon.getB2DPoint(nPoints - 1).getY());
+ glm::vec2 aEndCapPoint2(rPolygon.getB2DPoint(nPoints - 2).getX(), rPolygon.getB2DPoint(nPoints - 2).getY());
+
+ if (eLineCap == css::drawing::LineCap_ROUND)
+ {
+ rBuilder.appendRoundLineCapVertices(aBeginCapPoint1, aBeginCapPoint2);
+ rBuilder.appendRoundLineCapVertices(aEndCapPoint1, aEndCapPoint2);
+ }
+ else if (eLineCap == css::drawing::LineCap_SQUARE)
+ {
+ rBuilder.appendSquareLineCapVertices(aBeginCapPoint1, aBeginCapPoint2);
+ rBuilder.appendSquareLineCapVertices(aEndCapPoint1, aEndCapPoint2);
+ }
+ }
+}
+
+void appendTrapezoid(std::vector<Vertex>& rVertices, std::vector<GLuint>& rIndices,
+ GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2,
+ GLfloat x3, GLfloat y3, GLfloat x4, GLfloat y4,
+ Color nColor, GLfloat fTransparency)
+{
+ GLubyte nR, nG, nB, nA;
+ vcl::vertex::createColor(nColor, fTransparency, nR, nG, nB, nA);
+
+ GLuint zero = rVertices.size();
+
+ rVertices.insert(rVertices.end(), {
+ {glm::vec2{x1, y1}, glm::vec4{nR, nG, nB, nA}, glm::vec4{0.0f, 0.0f, 0.0f, 0.0f}},
+ {glm::vec2{x2, y2}, glm::vec4{nR, nG, nB, nA}, glm::vec4{0.0f, 0.0f, 0.0f, 0.0f}},
+ {glm::vec2{x3, y3}, glm::vec4{nR, nG, nB, nA}, glm::vec4{0.0f, 0.0f, 0.0f, 0.0f}},
+ {glm::vec2{x4, y4}, glm::vec4{nR, nG, nB, nA}, glm::vec4{0.0f, 0.0f, 0.0f, 0.0f}},
+ });
+
+ rIndices.insert(rIndices.end(), {
+ zero + 0, zero + 1, zero + 2,
+ zero + 2, zero + 1, zero + 3
+ });
+}
+
+void appendRectangle(std::vector<Vertex>& rVertices, std::vector<GLuint>& rIndices,
+ GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2,
+ Color nColor, GLfloat fTransparency)
+{
+ GLubyte nR, nG, nB, nA;
+ vcl::vertex::createColor(nColor, fTransparency, nR, nG, nB, nA);
+
+ GLuint zero = rVertices.size();
+
+ rVertices.insert(rVertices.end(), {
+ {glm::vec2{x1, y1}, glm::vec4{nR, nG, nB, nA}, glm::vec4{0.0f, 0.0f, 0.0f, 0.0f}},
+ {glm::vec2{x2, y1}, glm::vec4{nR, nG, nB, nA}, glm::vec4{0.0f, 0.0f, 0.0f, 0.0f}},
+ {glm::vec2{x1, y2}, glm::vec4{nR, nG, nB, nA}, glm::vec4{0.0f, 0.0f, 0.0f, 0.0f}},
+ {glm::vec2{x2, y2}, glm::vec4{nR, nG, nB, nA}, glm::vec4{0.0f, 0.0f, 0.0f, 0.0f}},
+ });
+
+ rIndices.insert(rIndices.end(), {
+ zero + 0, zero + 1, zero + 2,
+ zero + 2, zero + 1, zero + 3
+ });
+}
+
+} // end anonymous namespace
+
+void RenderList::addDrawPixel(long nX, long nY, Color nColor)
+{
+ if (nColor == SALCOLOR_NONE)
+ return;
+
+ checkOverlapping(basegfx::B2DRange(nX, nY, nX, nY));
+
+ RenderParameters& rRenderParameter = maRenderEntries.back().maTriangleParameters;
+ appendRectangle(rRenderParameter.maVertices, rRenderParameter.maIndices,
+ nX - 0.5f, nY - 0.5f, nX + 0.5f, nY + 0.5f, nColor, 0.0f);
+}
+
+void RenderList::addDrawRectangle(long nX, long nY, long nWidth, long nHeight, double fTransparency,
+ Color nLineColor, Color nFillColor)
+{
+ if (nLineColor == SALCOLOR_NONE && nFillColor == SALCOLOR_NONE)
+ return;
+ if (fTransparency == 1.0f)
+ return;
+
+ GLfloat fX1(nX);
+ GLfloat fY1(nY);
+ GLfloat fX2(nX + nWidth - 1);
+ GLfloat fY2(nY + nHeight - 1);
+
+ checkOverlapping(basegfx::B2DRange(fX1, fY1, fX2, fY2));
+
+ RenderParameters& rRenderParameter = maRenderEntries.back().maTriangleParameters;
+
+ // Draw rectangle stroke with line color
+ if (nLineColor != SALCOLOR_NONE)
+ {
+ appendRectangle(rRenderParameter.maVertices, rRenderParameter.maIndices,
+ fX1 - 0.5f, fY1 - 0.5f, fX1 + 0.5f, fY2 + 0.5f, nLineColor, fTransparency);
+ appendRectangle(rRenderParameter.maVertices, rRenderParameter.maIndices,
+ fX1 - 0.5f, fY1 - 0.5f, fX2 + 0.5f, fY1 + 0.5f, nLineColor, fTransparency);
+ appendRectangle(rRenderParameter.maVertices, rRenderParameter.maIndices,
+ fX2 - 0.5f, fY1 - 0.5f, fX2 + 0.5f, fY2 + 0.5f, nLineColor, fTransparency);
+ appendRectangle(rRenderParameter.maVertices, rRenderParameter.maIndices,
+ fX1 - 0.5f, fY2 - 0.5f, fX2 + 0.5f, fY2 + 0.5f, nLineColor, fTransparency);
+ }
+
+ if (nFillColor != SALCOLOR_NONE)
+ {
+ if (nLineColor == SALCOLOR_NONE)
+ {
+ appendRectangle(rRenderParameter.maVertices, rRenderParameter.maIndices,
+ fX1 - 0.5f, fY1 - 0.5f, fX1 + 0.5f, fY2 + 0.5f, nFillColor, fTransparency);
+ appendRectangle(rRenderParameter.maVertices, rRenderParameter.maIndices,
+ fX1 - 0.5f, fY1 - 0.5f, fX2 + 0.5f, fY1 + 0.5f, nFillColor, fTransparency);
+ appendRectangle(rRenderParameter.maVertices, rRenderParameter.maIndices,
+ fX2 - 0.5f, fY1 - 0.5f, fX2 + 0.5f, fY2 + 0.5f, nFillColor, fTransparency);
+ appendRectangle(rRenderParameter.maVertices, rRenderParameter.maIndices,
+ fX1 - 0.5f, fY2 - 0.5f, fX2 + 0.5f, fY2 + 0.5f, nFillColor, fTransparency);
+ }
+ // Draw rectangle fill with fill color
+ appendRectangle(rRenderParameter.maVertices, rRenderParameter.maIndices,
+ fX1 + 0.5f, fY1 + 0.5f, fX2 - 0.5f, fY2 - 0.5f, nFillColor, fTransparency);
+ }
+}
+
+void RenderList::addDrawLine(long nX1, long nY1, long nX2, long nY2, Color nLineColor, bool bUseAA)
+{
+ if (nLineColor == SALCOLOR_NONE)
+ return;
+
+ checkOverlapping(basegfx::B2DRange(nX1, nY1, nX2, nY2));
+
+ RenderParameters& rRenderParameter = maRenderEntries.back().maLineParameters;
+
+ glm::vec2 aPoint1(nX1, nY1);
+ glm::vec2 aPoint2(nX2, nY2);
+
+ vcl::LineBuilder aBuilder(rRenderParameter.maVertices, rRenderParameter.maIndices, nLineColor, 0.0f, 1.0f, bUseAA);
+ aBuilder.appendLine(aPoint1, aPoint2);
+}
+
+void RenderList::addDrawPolyPolygon(const basegfx::B2DPolyPolygon& rPolyPolygon, double fTransparency,
+ Color nLineColor, Color nFillColor, bool bUseAA)
+{
+ if (rPolyPolygon.count() <= 0)
+ return;
+ if (nLineColor == SALCOLOR_NONE && nFillColor == SALCOLOR_NONE)
+ return;
+ if (fTransparency == 1.0)
+ return;
+
+ checkOverlapping(rPolyPolygon.getB2DRange());
+
+ if (nFillColor != SALCOLOR_NONE)
+ {
+ basegfx::B2DTrapezoidVector aTrapezoidVector;
+ basegfx::utils::trapezoidSubdivide(aTrapezoidVector, rPolyPolygon);
+
+ if (!aTrapezoidVector.empty())
+ {
+ RenderParameters& rTriangleRenderParameter = maRenderEntries.back().maTriangleParameters;
+
+ for (const basegfx::B2DTrapezoid & rTrapezoid : aTrapezoidVector)
+ {
+ GLfloat topX1 = rTrapezoid.getTopXLeft();
+ GLfloat topX2 = rTrapezoid.getTopXRight();
+ GLfloat topY = rTrapezoid.getTopY();
+
+ GLfloat bottomX1 = rTrapezoid.getBottomXLeft();
+ GLfloat bottomX2 = rTrapezoid.getBottomXRight();
+ GLfloat bottomY = rTrapezoid.getBottomY();
+
+ appendTrapezoid(rTriangleRenderParameter.maVertices, rTriangleRenderParameter.maIndices,
+ topX1, topY, topX2, topY,
+ bottomX1, bottomY, bottomX2, bottomY,
+ nFillColor, fTransparency);
+ }
+ }
+ }
+
+ if (nLineColor != SALCOLOR_NONE || bUseAA)
+ {
+ RenderParameters& rLineRenderParameter = maRenderEntries.back().maLineParameters;
+ Color nColor = (nLineColor == SALCOLOR_NONE) ? nFillColor : nLineColor;
+
+ vcl::LineBuilder aBuilder(rLineRenderParameter.maVertices, rLineRenderParameter.maIndices,
+ nColor, fTransparency, 1.0f, bUseAA);
+
+ for (const basegfx::B2DPolygon& rPolygon : rPolyPolygon)
+ {
+ basegfx::B2DPolygon aPolygon(rPolygon);
+ if (rPolygon.areControlPointsUsed())
+ aPolygon = rPolygon.getDefaultAdaptiveSubdivision();
+
+ sal_uInt32 nPoints = aPolygon.count();
+ if (nPoints <= 1)
+ continue;
+
+ GLfloat x1, y1, x2, y2;
+ sal_uInt32 index1, index2;
+
+ for (sal_uInt32 i = 0; i <= nPoints; ++i)
+ {
+ index1 = i % nPoints;
+ index2 = (i + 1) % nPoints;
+
+ x1 = aPolygon.getB2DPoint(index1).getX();
+ y1 = aPolygon.getB2DPoint(index1).getY();
+ x2 = aPolygon.getB2DPoint(index2).getX();
+ y2 = aPolygon.getB2DPoint(index2).getY();
+
+ aBuilder.appendLine(glm::vec2(x1, y1), glm::vec2(x2, y2));
+ }
+ }
+ }
+}
+
+void RenderList::addDrawTextureWithMaskColor(OpenGLTexture const & rTexture, Color nColor, const SalTwoRect& r2Rect)
+{
+ if (!rTexture)
+ return;
+
+ GLfloat fX1 = r2Rect.mnDestX;
+ GLfloat fY1 = r2Rect.mnDestY;
+ GLfloat fX2 = fX1 + r2Rect.mnDestWidth;
+ GLfloat fY2 = fY1 + r2Rect.mnDestHeight;
+
+ checkOverlapping(basegfx::B2DRange(fX1, fY1, fX2, fY2));
+
+ GLuint nTextureId = rTexture.Id();
+
+ RenderTextureParameters& rTextureParameter = maRenderEntries.back().maTextureParametersMap[nTextureId];
+ rTextureParameter.maTexture = rTexture;
+
+ rTexture.FillCoords<GL_TRIANGLES>(rTextureParameter.maTextureCoords, r2Rect);
+
+ vcl::vertex::addRectangle<GL_TRIANGLES>(rTextureParameter.maVertices, fX1, fY1, fX2, fY2);
+ vcl::vertex::addQuadColors<GL_TRIANGLES>(rTextureParameter.maColors, nColor, 0.0f);
+}
+
+void RenderList::addDrawPolyLine(const basegfx::B2DPolygon& rPolygon, double fTransparency,
+ double fLineWidth, basegfx::B2DLineJoin eLineJoin,
+ css::drawing::LineCap eLineCap, double fMiterMinimumAngle,
+ Color nLineColor, bool bUseAA)
+{
+ if (rPolygon.count() <= 1)
+ return;
+ if (nLineColor == SALCOLOR_NONE)
+ return;
+ if (fTransparency == 1.0)
+ return;
+
+ const bool bIsHairline = fLineWidth <= 1.2;
+ fLineWidth = bIsHairline ? 1.0f : fLineWidth;
+
+ basegfx::B2DPolygon aPolygon(rPolygon);
+ if (rPolygon.areControlPointsUsed())
+ aPolygon = rPolygon.getDefaultAdaptiveSubdivision();
+
+ checkOverlapping(aPolygon.getB2DRange());
+
+ RenderParameters& rParameter = maRenderEntries.back().maLineParameters;
+
+ vcl::LineBuilder aBuilder(rParameter.maVertices, rParameter.maIndices,
+ nLineColor, fTransparency, fLineWidth, bUseAA);
+
+ appendPolyLine(aBuilder, aPolygon, eLineJoin, eLineCap, fMiterMinimumAngle);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/framebuffer.cxx b/vcl/opengl/framebuffer.cxx
new file mode 100644
index 000000000..db957d1a6
--- /dev/null
+++ b/vcl/opengl/framebuffer.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/.
+ */
+
+#include <sal/log.hxx>
+
+#include <opengl/framebuffer.hxx>
+
+#include <vcl/opengl/OpenGLHelper.hxx>
+
+OpenGLFramebuffer::OpenGLFramebuffer() :
+ mnId( 0 ),
+ mnWidth( 0 ),
+ mnHeight( 0 ),
+ mnAttachedTexture( 0 ),
+ mpPrevFramebuffer( nullptr )
+{
+ glGenFramebuffers( 1, &mnId );
+ CHECK_GL_ERROR();
+ VCL_GL_INFO( "Created framebuffer " << static_cast<int>(mnId) );
+}
+
+OpenGLFramebuffer::~OpenGLFramebuffer()
+{
+ glDeleteFramebuffers( 1, &mnId );
+ VCL_GL_INFO( "Deleted framebuffer " << static_cast<int>(mnId) );
+ CHECK_GL_ERROR();
+}
+
+void OpenGLFramebuffer::Bind(GLenum eTarget)
+{
+ VCL_GL_INFO( "Binding framebuffer " << static_cast<int>(mnId) );
+ glBindFramebuffer(eTarget, mnId);
+ CHECK_GL_ERROR();
+}
+
+void OpenGLFramebuffer::Unbind(GLenum eTarget)
+{
+ glBindFramebuffer(eTarget, 0);
+ CHECK_GL_ERROR();
+ VCL_GL_INFO( "Binding default framebuffer" );
+}
+
+bool OpenGLFramebuffer::IsFree() const
+{
+ return !mnAttachedTexture;
+}
+
+bool OpenGLFramebuffer::IsAttached( GLuint nTexture ) const
+{
+ return mnAttachedTexture == nTexture;
+}
+
+bool OpenGLFramebuffer::IsAttached( const OpenGLTexture& rTexture ) const
+{
+ return mnAttachedTexture == rTexture.Id();
+}
+
+void OpenGLFramebuffer::AttachTexture( const OpenGLTexture& rTexture )
+{
+ if( rTexture.Id() == mnAttachedTexture )
+ return;
+
+ VCL_GL_INFO( "Attaching texture " << rTexture.Id() << " to framebuffer " << static_cast<int>(mnId) );
+ mnAttachedTexture = rTexture.Id();
+ mnWidth = rTexture.GetWidth();
+ mnHeight = rTexture.GetHeight();
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mnAttachedTexture, 0);
+ CHECK_GL_ERROR();
+
+ GLuint nStencil = rTexture.StencilId();
+ if( nStencil )
+ {
+ VCL_GL_INFO( "Attaching stencil " << nStencil << " to framebuffer " << static_cast<int>(mnId) );
+ glFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
+ GL_RENDERBUFFER, nStencil );
+ CHECK_GL_ERROR();
+ }
+
+ GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+ CHECK_GL_ERROR();
+ if (status != GL_FRAMEBUFFER_COMPLETE)
+ {
+ SAL_WARN("vcl.opengl", "Framebuffer incomplete");
+ }
+}
+
+void OpenGLFramebuffer::DetachTexture()
+{
+ if( mnAttachedTexture != 0 )
+ {
+ mnAttachedTexture = 0;
+ glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0 );
+ CHECK_GL_ERROR();
+
+ // FIXME: we could make this conditional on having a stencil ?
+ glFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
+ GL_RENDERBUFFER, 0 );
+ CHECK_GL_ERROR();
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/gdiimpl.cxx b/vcl/opengl/gdiimpl.cxx
new file mode 100644
index 000000000..6c76154ea
--- /dev/null
+++ b/vcl/opengl/gdiimpl.cxx
@@ -0,0 +1,2315 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <opengl/gdiimpl.hxx>
+#include <opengl/framebuffer.hxx>
+
+#include <vcl/gradient.hxx>
+#include <vcl/idle.hxx>
+#include <salframe.hxx>
+#include <salvd.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/polygon/b2dlinegeometry.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/polygon/b2dpolygontriangulator.hxx>
+#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
+#include <basegfx/polygon/b2dtrapezoid.hxx>
+#include <sal/log.hxx>
+
+#include <vcl/opengl/OpenGLHelper.hxx>
+#include <salgdi.hxx>
+#include <svdata.hxx>
+#include <opengl/zone.hxx>
+#include <opengl/salbmp.hxx>
+#include <opengl/RenderState.hxx>
+#include <opengl/VertexUtils.hxx>
+#include <opengl/BufferObject.hxx>
+
+#include <cmath>
+#include <vector>
+#include <numeric>
+
+#include <glm/gtc/type_ptr.hpp>
+#include <glm/gtx/norm.hpp>
+
+#include <stdlib.h>
+
+class OpenGLFlushIdle : public Idle
+{
+ OpenGLSalGraphicsImpl *m_pImpl;
+public:
+ explicit OpenGLFlushIdle( OpenGLSalGraphicsImpl *pImpl )
+ : Idle( "gl idle swap" )
+ , m_pImpl( pImpl )
+ {
+ // We don't want to be swapping before we've painted.
+ SetPriority( TaskPriority::POST_PAINT );
+ }
+
+ virtual void Invoke() override
+ {
+ m_pImpl->doFlush();
+ Stop();
+ SetPriority(TaskPriority::HIGHEST);
+ }
+};
+
+OpenGLSalGraphicsImpl::OpenGLSalGraphicsImpl(SalGraphics& rParent, SalGeometryProvider *pProvider)
+ : mrParent(rParent)
+ , mpProvider(pProvider)
+ , mpProgram(nullptr)
+ , mpFlush(new OpenGLFlushIdle(this))
+ , mbUseScissor(false)
+ , mbUseStencil(false)
+ , mbXORMode(false)
+ , mbAcquiringOpenGLContext(false)
+ , mnLineColor(SALCOLOR_NONE)
+ , mnFillColor(SALCOLOR_NONE)
+#ifdef DBG_UTIL
+ , mProgramIsSolidColor(false)
+#endif
+ , mnDrawCount(0)
+ , mnDrawCountAtFlush(0)
+ , mProgramSolidColor(SALCOLOR_NONE)
+ , mProgramSolidTransparency(0.0)
+ , mpRenderList(new RenderList)
+{
+}
+
+OpenGLSalGraphicsImpl::~OpenGLSalGraphicsImpl()
+{
+ if( !IsOffscreen() && mnDrawCountAtFlush != mnDrawCount )
+ VCL_GL_INFO( "Destroying un-flushed on-screen graphics" );
+
+ mpFlush.reset();
+
+ ReleaseContext();
+}
+
+rtl::Reference<OpenGLContext> OpenGLSalGraphicsImpl::GetOpenGLContext()
+{
+ if (mbAcquiringOpenGLContext)
+ return nullptr;
+ mbAcquiringOpenGLContext = true;
+ bool bSuccess = AcquireContext(true);
+ mbAcquiringOpenGLContext = false;
+ if (!bSuccess)
+ return nullptr;
+ return mpContext;
+}
+
+bool OpenGLSalGraphicsImpl::AcquireContext( bool bForceCreate )
+{
+ mpContext = OpenGLContext::getVCLContext( false );
+
+ if( !mpContext.is() && mpWindowContext.is() )
+ {
+ mpContext = mpWindowContext;
+ }
+ else if( bForceCreate && !IsOffscreen() )
+ {
+ mpWindowContext = CreateWinContext();
+ mpContext = mpWindowContext;
+ }
+
+ if( !mpContext.is() )
+ mpContext = OpenGLContext::getVCLContext();
+
+ return mpContext.is();
+}
+
+void OpenGLSalGraphicsImpl::ReleaseContext()
+{
+ mpContext.clear();
+}
+
+void OpenGLSalGraphicsImpl::Init()
+{
+ // Our init phase is strange ::Init is called twice for vdevs.
+ // the first time around with a NULL geometry provider.
+ if( !mpProvider )
+ return;
+
+ // check if we can simply re-use the same context
+ if( mpContext.is() )
+ {
+ if( !UseContext( mpContext ) )
+ ReleaseContext();
+ }
+
+ // Always create the offscreen texture
+ if( maOffscreenTex.GetWidth() != GetWidth() ||
+ maOffscreenTex.GetHeight() != GetHeight() )
+ {
+ // We don't want to be swapping before we've painted.
+ mpFlush->SetPriority( TaskPriority::POST_PAINT );
+
+ if( maOffscreenTex && // don't work to release empty textures
+ mpContext.is() ) // valid context
+ {
+ mpContext->makeCurrent();
+ mpContext->ReleaseFramebuffer( maOffscreenTex );
+ }
+ maOffscreenTex = OpenGLTexture();
+ VCL_GL_INFO("::Init - re-size offscreen texture");
+ }
+
+ if( mpWindowContext.is() )
+ {
+ mpWindowContext->reset();
+ mpWindowContext.clear();
+ }
+}
+
+// Currently only used to get windows ordering right.
+void OpenGLSalGraphicsImpl::DeInit()
+{
+ VCL_GL_INFO("::DeInit");
+
+ FlushDeferredDrawing();
+
+ // tdf#93839:
+ // Our window handles and resources are being free underneath us.
+ // These can be bound into a context, which relies on them. So
+ // let it know. Other eg. VirtualDevice contexts which have
+ // references on and rely on this context continuing to work will
+ // get a shiny new context in AcquireContext:: next PreDraw.
+ if( mpWindowContext.is() )
+ {
+ mpWindowContext->reset();
+ mpWindowContext.clear();
+ }
+ mpContext.clear();
+}
+
+void OpenGLSalGraphicsImpl::PreDraw(XOROption eOpt)
+{
+ FlushDeferredDrawing();
+
+ InitializePreDrawState(eOpt);
+}
+
+void OpenGLSalGraphicsImpl::InitializePreDrawState(XOROption eOpt)
+{
+ OpenGLZone::enter();
+
+ mnDrawCount++;
+
+ if( !AcquireContext() )
+ {
+ SAL_WARN( "vcl.opengl", "Couldn't acquire context" );
+ return;
+ }
+
+ mpContext->makeCurrent();
+ CHECK_GL_ERROR();
+
+ CheckOffscreenTexture();
+ CHECK_GL_ERROR();
+
+ mpContext->state().viewport(tools::Rectangle(Point(0, 0), Size(GetWidth(), GetHeight())));
+
+ ImplInitClipRegion();
+ CHECK_GL_ERROR();
+
+ if (eOpt == IMPLEMENT_XOR && mbXORMode)
+ {
+ glEnable(GL_COLOR_LOGIC_OP);
+ CHECK_GL_ERROR();
+
+ glLogicOp(GL_XOR);
+ CHECK_GL_ERROR();
+
+ glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
+ CHECK_GL_ERROR();
+ }
+}
+
+void OpenGLSalGraphicsImpl::PostDraw()
+{
+ if (mbXORMode)
+ {
+ glDisable(GL_COLOR_LOGIC_OP);
+ CHECK_GL_ERROR();
+ glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+ CHECK_GL_ERROR();
+ }
+
+ if( mpProgram )
+ {
+ mpProgram->Clean();
+ mpProgram = nullptr;
+#ifdef DBG_UTIL
+ mProgramIsSolidColor = false;
+#endif
+ }
+
+ assert (maOffscreenTex);
+
+ // Always queue the flush.
+ if( !IsOffscreen() )
+ flush();
+
+ OpenGLZone::leave();
+}
+
+void OpenGLSalGraphicsImpl::PostBatchDraw()
+{
+ if (IsOffscreen())
+ return;
+
+ if (!mpFlush->IsActive())
+ mpFlush->Start();
+}
+
+void OpenGLSalGraphicsImpl::ApplyProgramMatrices(float fPixelOffset)
+{
+ mpProgram->ApplyMatrix(GetWidth(), GetHeight(), fPixelOffset);
+}
+
+void OpenGLSalGraphicsImpl::freeResources()
+{
+ // TODO Delete shaders, programs and textures if not shared
+ if( mpContext.is() && mpContext->isInitialized() )
+ {
+ VCL_GL_INFO( "freeResources" );
+ mpContext->makeCurrent();
+ FlushDeferredDrawing();
+ mpContext->ReleaseFramebuffer( maOffscreenTex );
+ }
+ ReleaseContext();
+}
+
+void OpenGLSalGraphicsImpl::ImplSetClipBit( const vcl::Region& rClip, GLuint nMask )
+{
+ mpContext->state().scissor().disable();
+ mpContext->state().stencil().enable();
+
+ VCL_GL_INFO( "Adding complex clip / stencil" );
+ GLuint nStencil = maOffscreenTex.StencilId();
+ if( nStencil == 0 )
+ {
+ nStencil = maOffscreenTex.AddStencil();
+ glFramebufferRenderbuffer(
+ GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
+ GL_RENDERBUFFER, nStencil );
+ CHECK_GL_ERROR();
+ }
+ // else - we associated the stencil in
+ // AcquireFrameBuffer / AttachTexture
+
+ CHECK_GL_ERROR();
+ glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );
+ CHECK_GL_ERROR();
+ glStencilMask( nMask );
+ CHECK_GL_ERROR();
+ glStencilFunc( GL_NEVER, nMask, 0xFF );
+ CHECK_GL_ERROR();
+ glStencilOp( GL_REPLACE, GL_KEEP, GL_KEEP );
+ CHECK_GL_ERROR();
+
+ glClear( GL_STENCIL_BUFFER_BIT );
+ CHECK_GL_ERROR();
+ if( UseSolid( Color( 0xFF, 0xFF, 0xFF ) ) )
+ {
+ if( rClip.getRegionBand() )
+ DrawRegionBand( *rClip.getRegionBand() );
+ else
+ DrawPolyPolygon( rClip.GetAsB2DPolyPolygon(), true );
+ }
+
+ glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );
+ CHECK_GL_ERROR();
+ glStencilMask( 0x00 );
+ CHECK_GL_ERROR();
+
+ mpContext->state().stencil().disable();
+}
+
+void OpenGLSalGraphicsImpl::ImplInitClipRegion()
+{
+ // make sure the context has the right clipping set
+ if (maClipRegion != mpContext->maClipRegion)
+ {
+ mpContext->maClipRegion = maClipRegion;
+ if (mbUseStencil)
+ {
+ ImplSetClipBit(maClipRegion, 0x01);
+ }
+ }
+
+ if (mbUseScissor)
+ {
+ tools::Rectangle aRect(maClipRegion.GetBoundRect());
+ mpContext->state().scissor().set(aRect.Left(), GetHeight() - aRect.Bottom() - 1, aRect.GetWidth(), aRect.GetHeight());
+ mpContext->state().scissor().enable();
+ }
+ else
+ {
+ mpContext->state().scissor().disable();
+ }
+
+ if (mbUseStencil)
+ {
+ glStencilFunc( GL_EQUAL, 1, 0x1 );
+ CHECK_GL_ERROR();
+ mpContext->state().stencil().enable();
+ }
+ else
+ {
+ mpContext->state().stencil().disable();
+ }
+}
+
+const vcl::Region& OpenGLSalGraphicsImpl::getClipRegion() const
+{
+ return maClipRegion;
+}
+
+bool OpenGLSalGraphicsImpl::setClipRegion( const vcl::Region& rClip )
+{
+ if (maClipRegion == rClip)
+ {
+ VCL_GL_INFO("::setClipRegion (no change) " << rClip);
+ return true;
+ }
+
+ FlushDeferredDrawing();
+
+ VCL_GL_INFO("::setClipRegion " << rClip);
+
+ maClipRegion = rClip;
+
+ mbUseStencil = false;
+ mbUseScissor = false;
+ if (maClipRegion.IsRectangle())
+ mbUseScissor = true;
+ else if (!maClipRegion.IsEmpty())
+ mbUseStencil = true;
+
+ return true;
+}
+
+// set the clip region to empty
+void OpenGLSalGraphicsImpl::ResetClipRegion()
+{
+ if (maClipRegion.IsEmpty())
+ {
+ VCL_GL_INFO("::ResetClipRegion (no change) ");
+ return;
+ }
+
+ FlushDeferredDrawing();
+
+ VCL_GL_INFO("::ResetClipRegion");
+
+ maClipRegion.SetEmpty();
+ mbUseScissor = false;
+ mbUseStencil = false;
+}
+
+// get the depth of the device
+sal_uInt16 OpenGLSalGraphicsImpl::GetBitCount() const
+{
+ return 32;
+}
+
+// get the width of the device
+long OpenGLSalGraphicsImpl::GetGraphicsWidth() const
+{
+ return GetWidth();
+}
+
+// set the line color to transparent (= don't draw lines)
+void OpenGLSalGraphicsImpl::SetLineColor()
+{
+ if( mnLineColor != SALCOLOR_NONE )
+ {
+ mnLineColor = SALCOLOR_NONE;
+ }
+}
+
+// set the line color to a specific color
+void OpenGLSalGraphicsImpl::SetLineColor( Color nColor )
+{
+ if( mnLineColor != nColor )
+ {
+ mnLineColor = nColor;
+ }
+}
+
+// set the fill color to transparent (= don't fill)
+void OpenGLSalGraphicsImpl::SetFillColor()
+{
+ if( mnFillColor != SALCOLOR_NONE )
+ {
+ mnFillColor = SALCOLOR_NONE;
+ }
+}
+
+// set the fill color to a specific color, shapes will be
+// filled accordingly
+void OpenGLSalGraphicsImpl::SetFillColor( Color nColor )
+{
+ if( mnFillColor != nColor )
+ {
+ mnFillColor = nColor;
+ }
+}
+
+// enable/disable XOR drawing
+void OpenGLSalGraphicsImpl::SetXORMode( bool bSet, bool )
+{
+ if (mbXORMode != bSet)
+ {
+ FlushDeferredDrawing();
+ mbXORMode = bSet;
+ }
+}
+
+void OpenGLSalGraphicsImpl::SetROPLineColor(SalROPColor nROPColor)
+{
+ switch (nROPColor)
+ {
+ case SalROPColor::N0:
+ mnLineColor = Color(0, 0, 0);
+ break;
+ case SalROPColor::N1:
+ mnLineColor = Color(0xff, 0xff, 0xff);
+ break;
+ case SalROPColor::Invert:
+ mnLineColor = Color(0xff, 0xff, 0xff);
+ break;
+ }
+}
+
+void OpenGLSalGraphicsImpl::SetROPFillColor(SalROPColor nROPColor)
+{
+ switch (nROPColor)
+ {
+ case SalROPColor::N0:
+ mnFillColor = Color(0, 0, 0);
+ break;
+ case SalROPColor::N1:
+ mnFillColor = Color(0xff, 0xff, 0xff);
+ break;
+ case SalROPColor::Invert:
+ mnFillColor = Color(0xff, 0xff, 0xff);
+ break;
+ }
+}
+
+void OpenGLSalGraphicsImpl::CheckOffscreenTexture()
+{
+ bool bClearTexture = false;
+
+ VCL_GL_INFO( "Check Offscreen texture" );
+
+ // Always create the offscreen texture
+ if( maOffscreenTex )
+ {
+ if( maOffscreenTex.GetWidth() != GetWidth() ||
+ maOffscreenTex.GetHeight() != GetHeight() )
+ {
+ VCL_GL_INFO( "re-size offscreen texture " << maOffscreenTex.Id() );
+ mpFlush->SetPriority( TaskPriority::POST_PAINT );
+ mpContext->ReleaseFramebuffer( maOffscreenTex );
+ maOffscreenTex = OpenGLTexture();
+ }
+ }
+
+ if( !maOffscreenTex )
+ {
+ VCL_GL_INFO( "create texture of size "
+ << GetWidth() << " x " << GetHeight() );
+ maOffscreenTex = OpenGLTexture( GetWidth(), GetHeight() );
+ bClearTexture = true;
+ }
+
+ if( !maOffscreenTex.IsUnique() )
+ {
+ GLfloat fWidth = GetWidth();
+ GLfloat fHeight = GetHeight();
+ SalTwoRect aPosAry(0, 0, fWidth, fHeight, 0,0, fWidth, fHeight);
+
+ // TODO: lfrb: User GL_ARB_copy_image?
+ OpenGLTexture aNewTex( GetWidth(), GetHeight() );
+
+ mpContext->state().scissor().disable();
+ mpContext->state().stencil().disable();
+
+ mpContext->AcquireFramebuffer( aNewTex );
+ DrawTexture( maOffscreenTex, aPosAry );
+ maOffscreenTex = aNewTex;
+ }
+ else
+ {
+ mpContext->AcquireFramebuffer( maOffscreenTex );
+ CHECK_GL_ERROR();
+
+ if( bClearTexture )
+ {
+ glDrawBuffer( GL_COLOR_ATTACHMENT0 );
+#if OSL_DEBUG_LEVEL > 0 // lets have some red debugging background.
+ GLfloat const clearColor[4] = { 1.0, 0, 0, 0 };
+#else
+ GLfloat const clearColor[4] = { 1.0, 1.0, 1.0, 0 };
+#endif
+ glClearBufferfv( GL_COLOR, 0, clearColor );
+ // FIXME: use glClearTexImage if we have it ?
+ }
+ }
+
+ assert( maOffscreenTex );
+
+ CHECK_GL_ERROR();
+}
+
+bool OpenGLSalGraphicsImpl::UseProgram( const OUString& rVertexShader, const OUString& rFragmentShader, const OString& preamble )
+{
+ if( mpProgram != nullptr )
+ mpProgram->Clean();
+ mpProgram = mpContext->UseProgram( rVertexShader, rFragmentShader, preamble );
+#ifdef DBG_UTIL
+ mProgramIsSolidColor = false; // UseSolid() will set to true if needed
+#endif
+ return ( mpProgram != nullptr );
+}
+
+bool OpenGLSalGraphicsImpl::UseSolid( Color nColor, sal_uInt8 nTransparency )
+{
+ if( nColor == SALCOLOR_NONE )
+ return false;
+ UseSolid();
+ mpProgram->SetColor( "color", nColor, nTransparency );
+#ifdef DBG_UTIL
+ mProgramIsSolidColor = true;
+#endif
+ mProgramSolidColor = nColor;
+ mProgramSolidTransparency = nTransparency / 100.0;
+
+ return true;
+}
+
+bool OpenGLSalGraphicsImpl::UseSolid( Color nColor, double fTransparency )
+{
+ if( nColor == SALCOLOR_NONE )
+ return false;
+ UseSolid();
+ mpProgram->SetColorf( "color", nColor, fTransparency );
+#ifdef DBG_UTIL
+ mProgramIsSolidColor = true;
+#endif
+ mProgramSolidColor = nColor;
+ mProgramSolidTransparency = fTransparency;
+ return true;
+}
+
+void OpenGLSalGraphicsImpl::UseSolid()
+{
+ if (!UseProgram("combinedVertexShader", "combinedFragmentShader"))
+ return;
+ mpProgram->SetShaderType(DrawShaderType::Normal);
+}
+
+bool OpenGLSalGraphicsImpl::UseInvert50()
+{
+ return UseProgram( "dumbVertexShader", "invert50FragmentShader" );
+}
+
+bool OpenGLSalGraphicsImpl::UseSolid( Color nColor )
+{
+ return UseSolid( nColor, 0.0f );
+}
+
+bool OpenGLSalGraphicsImpl::UseInvert( SalInvert nFlags )
+{
+ OpenGLZone aZone;
+
+ if( ( nFlags & SalInvert::N50 ) ||
+ ( nFlags & SalInvert::TrackFrame ) )
+ {
+ // FIXME: Trackframe really should be 2 pix. on/off stipple.
+ if( !UseInvert50() )
+ return false;
+ mpProgram->SetBlendMode( GL_ONE_MINUS_DST_COLOR,
+ GL_ONE_MINUS_SRC_COLOR );
+ }
+ else
+ {
+ if( !UseSolid( Color( 255, 255, 255 ) ) )
+ return false;
+ mpProgram->SetBlendMode( GL_ONE_MINUS_DST_COLOR, GL_ZERO );
+ }
+ return true;
+}
+
+void OpenGLSalGraphicsImpl::DrawLineSegment(float x1, float y1, float x2, float y2)
+{
+ std::vector<GLfloat> aVertices;
+ std::vector<GLfloat> aExtrusionVectors;
+
+ OpenGLZone aZone;
+
+ glm::vec2 aPoint1(x1, y1);
+ glm::vec2 aPoint2(x2, y2);
+
+ glm::vec2 aLineVector = vcl::vertex::normalize(aPoint2 - aPoint1);
+ glm::vec2 aNormal(-aLineVector.y, aLineVector.x);
+
+ vcl::vertex::addLineSegmentVertices(aVertices, aExtrusionVectors,
+ aPoint1, aNormal, 1.0f,
+ aPoint2, aNormal, 1.0f);
+
+ ApplyProgramMatrices(0.5f);
+ mpProgram->SetExtrusionVectors(aExtrusionVectors.data());
+ mpProgram->DrawArrays(GL_TRIANGLES, aVertices);
+
+ CHECK_GL_ERROR();
+}
+
+bool OpenGLSalGraphicsImpl::UseLine(Color nColor, double fTransparency, GLfloat fLineWidth, bool bUseAA)
+{
+ if( nColor == SALCOLOR_NONE )
+ return false;
+ UseLine(fLineWidth, bUseAA);
+ mpProgram->SetColorf("color", nColor, fTransparency);
+#ifdef DBG_UTIL
+ mProgramIsSolidColor = true;
+#endif
+ mProgramSolidColor = nColor;
+ mProgramSolidTransparency = fTransparency;
+ return true;
+}
+
+void OpenGLSalGraphicsImpl::UseLine(GLfloat fLineWidth, bool bUseAA)
+{
+ if (!UseProgram("combinedVertexShader", "combinedFragmentShader"))
+ return;
+ mpProgram->SetShaderType(DrawShaderType::Line);
+ mpProgram->SetUniform1f("line_width", fLineWidth);
+ // The width of the feather - area we make linearly transparent in VS.
+ // Good AA value is 0.5f, no AA if feather 0.0f
+ mpProgram->SetUniform1f("feather", bUseAA ? 0.5f : 0.0f);
+ // We need blending or AA won't work correctly
+ mpProgram->SetBlendMode(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+}
+
+void OpenGLSalGraphicsImpl::DrawConvexPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry, bool blockAA )
+{
+ OpenGLZone aZone;
+
+ std::vector<GLfloat> aVertices(nPoints * 2);
+ sal_uInt32 i, j;
+
+ for( i = 0, j = 0; i < nPoints; i++, j += 2 )
+ {
+ aVertices[j] = GLfloat(pPtAry[i].mnX);
+ aVertices[j+1] = GLfloat(pPtAry[i].mnY);
+ }
+
+ ApplyProgramMatrices();
+ std::vector<GLfloat> aExtrusion(nPoints * 3, 0);
+ mpProgram->SetExtrusionVectors(aExtrusion.data());
+ mpProgram->DrawArrays(GL_TRIANGLE_FAN, aVertices);
+ CHECK_GL_ERROR();
+
+ if( !blockAA && mrParent.getAntiAliasB2DDraw())
+ {
+ // Make the edges antialiased by drawing the edge lines again with AA.
+ // TODO: If transparent drawing is set up, drawing the lines themselves twice
+ // may be a problem, if that is a real problem, the polygon areas itself needs to be
+ // masked out for this or something.
+#ifdef DBG_UTIL
+ assert( mProgramIsSolidColor );
+#endif
+ Color lastSolidColor = mProgramSolidColor;
+ double lastSolidTransparency = mProgramSolidTransparency;
+ if (UseLine(lastSolidColor, lastSolidTransparency, 1.0f, true))
+ {
+ for( i = 0; i < nPoints; ++i )
+ {
+ const SalPoint& rPt1 = pPtAry[ i ];
+ const SalPoint& rPt2 = pPtAry[ ( i + 1 ) % nPoints ];
+ DrawLineSegment(rPt1.mnX, rPt1.mnY, rPt2.mnX, rPt2.mnY);
+ }
+ UseSolid( lastSolidColor, lastSolidTransparency );
+ }
+ }
+}
+
+void OpenGLSalGraphicsImpl::DrawConvexPolygon( const tools::Polygon& rPolygon, bool blockAA )
+{
+ OpenGLZone aZone;
+
+ sal_uInt16 nPoints = rPolygon.GetSize() - 1;
+ std::vector<GLfloat> aVertices(nPoints * 2);
+ sal_uInt32 i, j;
+
+ for( i = 0, j = 0; i < nPoints; i++, j += 2 )
+ {
+ const Point& rPt = rPolygon.GetPoint( i );
+ aVertices[j] = GLfloat(rPt.X());
+ aVertices[j+1] = GLfloat(rPt.Y());
+ }
+
+ ApplyProgramMatrices();
+ std::vector<GLfloat> aExtrusion(nPoints * 3, 0);
+ mpProgram->SetExtrusionVectors(aExtrusion.data());
+ mpProgram->DrawArrays(GL_TRIANGLE_FAN, aVertices);
+ CHECK_GL_ERROR();
+
+ if( !blockAA && mrParent.getAntiAliasB2DDraw())
+ {
+ // Make the edges antialiased by drawing the edge lines again with AA.
+ // TODO: If transparent drawing is set up, drawing the lines themselves twice
+ // may be a problem, if that is a real problem, the polygon areas itself needs to be
+ // masked out for this or something.
+#ifdef DBG_UTIL
+ assert( mProgramIsSolidColor );
+#endif
+ Color lastSolidColor = mProgramSolidColor;
+ double lastSolidTransparency = mProgramSolidTransparency;
+ if (UseLine(lastSolidColor, lastSolidTransparency, 1.0f, true))
+ {
+ for( i = 0; i < nPoints; ++i )
+ {
+ const Point& rPt1 = rPolygon.GetPoint( i );
+ const Point& rPt2 = rPolygon.GetPoint(( i + 1 ) % nPoints );
+ DrawLineSegment(rPt1.getX(), rPt1.getY(), rPt2.getX(), rPt2.getY());
+ }
+ UseSolid( lastSolidColor, lastSolidTransparency );
+ }
+ }
+}
+
+void OpenGLSalGraphicsImpl::DrawTrapezoid( const basegfx::B2DTrapezoid& trapezoid, bool blockAA )
+{
+ OpenGLZone aZone;
+
+ const basegfx::B2DPolygon& rPolygon = trapezoid.getB2DPolygon();
+ sal_uInt16 nPoints = rPolygon.count();
+ std::vector<GLfloat> aVertices(nPoints * 2);
+ sal_uInt32 i, j;
+
+ for( i = 0, j = 0; i < nPoints; i++, j += 2 )
+ {
+ const basegfx::B2DPoint& rPt = rPolygon.getB2DPoint( i );
+ aVertices[j] = GLfloat(rPt.getX());
+ aVertices[j+1] = GLfloat(rPt.getY());
+ }
+
+ if (!mpProgram)
+ {
+ SAL_WARN("vcl.opengl", "OpenGLSalGraphicsImpl::DrawTrapezoid: mpProgram is 0");
+ return;
+ }
+
+ ApplyProgramMatrices();
+ std::vector<GLfloat> aExtrusion(nPoints * 3, 0);
+ mpProgram->SetExtrusionVectors(aExtrusion.data());
+ mpProgram->DrawArrays(GL_TRIANGLE_FAN, aVertices);
+ CHECK_GL_ERROR();
+
+ if( !blockAA && mrParent.getAntiAliasB2DDraw())
+ {
+ // Make the edges antialiased by drawing the edge lines again with AA.
+ // TODO: If transparent drawing is set up, drawing the lines themselves twice
+ // may be a problem, if that is a real problem, the polygon areas itself needs to be
+ // masked out for this or something.
+#ifdef DBG_UTIL
+ assert( mProgramIsSolidColor );
+#endif
+ Color lastSolidColor = mProgramSolidColor;
+ double lastSolidTransparency = mProgramSolidTransparency;
+ if (UseLine(lastSolidColor, lastSolidTransparency, 1.0f, true))
+ {
+ for( i = 0; i < nPoints; ++i )
+ {
+ const basegfx::B2DPoint& rPt1 = rPolygon.getB2DPoint( i );
+ const basegfx::B2DPoint& rPt2 = rPolygon.getB2DPoint(( i + 1 ) % nPoints );
+ DrawLineSegment(rPt1.getX(), rPt1.getY(), rPt2.getX(), rPt2.getY());
+ }
+ UseSolid( lastSolidColor, lastSolidTransparency );
+ }
+ }
+}
+
+void OpenGLSalGraphicsImpl::DrawRect( long nX, long nY, long nWidth, long nHeight )
+{
+ long nX1( nX );
+ long nY1( nY );
+ long nX2( nX + nWidth );
+ long nY2( nY + nHeight );
+ const SalPoint aPoints[] = { { nX1, nY2 }, { nX1, nY1 },
+ { nX2, nY1 }, { nX2, nY2 }};
+
+ DrawConvexPolygon( 4, aPoints, true );
+}
+
+void OpenGLSalGraphicsImpl::DrawRect( const tools::Rectangle& rRect )
+{
+ long nX1( rRect.Left() );
+ long nY1( rRect.Top() );
+ long nX2( rRect.Right() );
+ long nY2( rRect.Bottom() );
+ const SalPoint aPoints[] = { { nX1, nY2 }, { nX1, nY1 },
+ { nX2, nY1 }, { nX2, nY2 }};
+
+ DrawConvexPolygon( 4, aPoints, true );
+}
+
+void OpenGLSalGraphicsImpl::DrawPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry )
+{
+ basegfx::B2DPolygon aPolygon;
+
+ for( sal_uInt32 i = 0; i < nPoints; i++ )
+ aPolygon.append( basegfx::B2DPoint( pPtAry[i].mnX, pPtAry[i].mnY ) );
+ aPolygon.setClosed( true );
+
+ if( basegfx::utils::isConvex( aPolygon ) )
+ {
+ if( nPoints > 2 )
+ DrawConvexPolygon( nPoints, pPtAry );
+ }
+ else
+ {
+ const basegfx::B2DPolyPolygon aPolyPolygon( aPolygon );
+ DrawPolyPolygon( aPolyPolygon );
+ }
+}
+
+void OpenGLSalGraphicsImpl::DrawPolyPolygon( const basegfx::B2DPolyPolygon& rPolyPolygon, bool blockAA )
+{
+ const basegfx::B2DPolyPolygon& aSimplePolyPolygon = ::basegfx::utils::solveCrossovers( rPolyPolygon );
+ basegfx::B2DTrapezoidVector aB2DTrapVector;
+ basegfx::utils::trapezoidSubdivide( aB2DTrapVector, aSimplePolyPolygon );
+ // draw tessellation result
+ for(const basegfx::B2DTrapezoid & i : aB2DTrapVector)
+ DrawTrapezoid( i, blockAA );
+}
+
+void OpenGLSalGraphicsImpl::DrawRegionBand( const RegionBand& rRegion )
+{
+ OpenGLZone aZone;
+
+ RectangleVector aRects;
+ std::vector<GLfloat> aVertices;
+ rRegion.GetRegionRectangles( aRects );
+
+ if( aRects.empty() )
+ return;
+
+#define ADD_VERTICE(pt) \
+ aVertices.push_back(GLfloat(pt.X())); \
+ aVertices.push_back(GLfloat(pt.Y()));
+
+ for(tools::Rectangle & rRect : aRects)
+ {
+ rRect.AdjustBottom(1 );
+ rRect.AdjustRight(1 );
+ ADD_VERTICE( rRect.TopLeft() );
+ ADD_VERTICE( rRect.TopRight() );
+ ADD_VERTICE( rRect.BottomLeft() );
+ ADD_VERTICE( rRect.BottomLeft() );
+ ADD_VERTICE( rRect.TopRight() );
+ ADD_VERTICE( rRect.BottomRight() );
+ }
+#undef ADD_VERTICE
+ std::vector<GLfloat> aExtrusion(aRects.size() * 6 * 3, 0);
+ mpProgram->SetExtrusionVectors(aExtrusion.data());
+ ApplyProgramMatrices();
+ mpProgram->DrawArrays(GL_TRIANGLES, aVertices);
+ CHECK_GL_ERROR();
+}
+
+void OpenGLSalGraphicsImpl::DrawTextureRect( const SalTwoRect& rPosAry )
+{
+ OpenGLZone aZone;
+
+ SAL_INFO("vcl.opengl", "draw texture rect");
+
+ long nX = rPosAry.mnDestX;
+ long nY = rPosAry.mnDestY;
+ long nWidth = rPosAry.mnDestWidth;
+ long nHeight = rPosAry.mnDestHeight;
+
+ std::vector<GLfloat> aVertices;
+ aVertices.reserve(8);
+ vcl::vertex::addRectangle<GL_TRIANGLE_FAN>(aVertices, nX, nY, nX + nWidth, nY + nHeight);
+
+ ApplyProgramMatrices();
+ mpProgram->DrawArrays(GL_TRIANGLE_FAN, aVertices);
+ CHECK_GL_ERROR();
+}
+
+void OpenGLSalGraphicsImpl::DrawTexture( OpenGLTexture& rTexture, const SalTwoRect& rPosAry, bool bInverted )
+{
+ OpenGLZone aZone;
+
+ SAL_INFO("vcl.opengl", "draw texture");
+
+ if (!UseProgram("combinedTextureVertexShader", "combinedTextureFragmentShader"))
+ return;
+ mpProgram->SetShaderType(TextureShaderType::Normal);
+ mpProgram->SetIdentityTransform("transform");
+ mpProgram->SetTexture("texture", rTexture);
+
+ GLfloat aTexCoord[8];
+ rTexture.GetCoord(aTexCoord, rPosAry, bInverted);
+ mpProgram->SetTextureCoord(aTexCoord);
+ mpProgram->SetMaskCoord(aTexCoord);
+ mpProgram->SetAlphaCoord(aTexCoord);
+
+ DrawTextureRect( rPosAry );
+ mpProgram->Clean();
+}
+
+namespace {
+
+bool scaleTexture(const rtl::Reference< OpenGLContext > &xContext,
+ OpenGLTexture& rOutTexture, const double& ixscale, const double& iyscale, OpenGLTexture& rTexture)
+{
+ int nWidth = rTexture.GetWidth();
+ int nHeight = rTexture.GetHeight();
+ if (nWidth == 0 || nHeight == 0)
+ return false;
+
+ int nNewWidth = nWidth / ixscale;
+ int nNewHeight = nHeight / iyscale;
+
+ OString sUseReducedRegisterVariantDefine;
+ if (xContext->getOpenGLCapabilitySwitch().mbLimitedShaderRegisters)
+ sUseReducedRegisterVariantDefine = OString("#define USE_REDUCED_REGISTER_VARIANT\n");
+
+ OpenGLProgram* pProgram = xContext->UseProgram("textureVertexShader", "areaScaleFragmentShader", sUseReducedRegisterVariantDefine);
+ if (pProgram == nullptr)
+ return false;
+
+ OpenGLTexture aScratchTex(nNewWidth, nNewHeight);
+ OpenGLFramebuffer* pFramebuffer = xContext->AcquireFramebuffer(aScratchTex);
+
+ // From OpenGLSalBitmap::ImplScaleArea().
+ pProgram->SetUniform1f("xscale", ixscale);
+ pProgram->SetUniform1f("yscale", iyscale);
+ pProgram->SetUniform1i("swidth", nWidth);
+ pProgram->SetUniform1i("sheight", nHeight);
+ // For converting between <0,nWidth> and <0.0,1.0> coordinate systems.
+ GLfloat srcCoords[ 8 ];
+ rTexture.GetWholeCoord( srcCoords );
+ pProgram->SetUniform1f( "xoffset", srcCoords[ 0 ] );
+ pProgram->SetUniform1f( "yoffset", srcCoords[ 1 ] );
+ pProgram->SetUniform1f( "xtopixelratio", nNewWidth / ( srcCoords[ 4 ] - srcCoords[ 0 ] ));
+ pProgram->SetUniform1f( "ytopixelratio", nNewHeight / ( srcCoords[ 5 ] - srcCoords[ 1 ] ));
+ pProgram->SetUniform1f( "xfrompixelratio", ( srcCoords[ 4 ] - srcCoords[ 0 ] ) / nWidth );
+ pProgram->SetUniform1f( "yfrompixelratio", ( srcCoords[ 5 ] - srcCoords[ 1 ] ) / nHeight );
+
+ pProgram->SetTexture("sampler", rTexture);
+ pProgram->DrawTexture(rTexture);
+ pProgram->Clean();
+
+ OpenGLContext::ReleaseFramebuffer(pFramebuffer);
+
+ CHECK_GL_ERROR();
+
+ rOutTexture = aScratchTex;
+ return true;
+}
+
+}
+
+void OpenGLSalGraphicsImpl::DrawTransformedTexture(
+ OpenGLTexture& rTexture,
+ OpenGLTexture& rMask,
+ const basegfx::B2DPoint& rNull,
+ const basegfx::B2DPoint& rX,
+ const basegfx::B2DPoint& rY )
+{
+ OpenGLZone aZone;
+
+ std::vector<GLfloat> aVertices = {
+ 0, GLfloat(rTexture.GetHeight()),
+ 0, 0,
+ GLfloat(rTexture.GetWidth()), 0,
+ GLfloat(rTexture.GetWidth()), GLfloat(rTexture.GetHeight())
+ };
+
+ GLfloat aTexCoord[8];
+
+ const long nDestWidth = basegfx::fround(basegfx::B2DVector(rX - rNull).getLength());
+ const long nDestHeight = basegfx::fround(basegfx::B2DVector(rY - rNull).getLength());
+
+ // Invisibly small images shouldn't divide by zero.
+ if( nDestHeight == 0 || nDestWidth == 0 )
+ return;
+
+ // inverted scale ratios
+ double ixscale = rTexture.GetWidth() / double(nDestWidth);
+ double iyscale = rTexture.GetHeight() / double(nDestHeight);
+
+ // If downscaling at a higher scale ratio, use the area scaling algorithm rather
+ // than plain OpenGL's scaling (texture mapping), for better results.
+ // See OpenGLSalBitmap::ImplScaleArea().
+ bool areaScaling = false;
+ bool fastAreaScaling = false;
+
+ OString sUseReducedRegisterVariantDefine;
+ if (mpContext->getOpenGLCapabilitySwitch().mbLimitedShaderRegisters)
+ sUseReducedRegisterVariantDefine = OString("#define USE_REDUCED_REGISTER_VARIANT\n");
+
+ OUString textureFragmentShader;
+ if( ixscale >= 2 && iyscale >= 2 ) // scale ratio less than 50%
+ {
+ areaScaling = true;
+ fastAreaScaling = ( ixscale == std::trunc( ixscale ) && iyscale == std::trunc( iyscale ));
+ // The generic case has arrays only up to 16 ratio downscaling and is performed in 2 passes,
+ // when the ratio is in the 16-100 range, which is hopefully enough in practice, but protect
+ // against buffer overflows in case such an extreme case happens (and in such case the precision
+ // of the generic algorithm probably doesn't matter anyway).
+ if( ixscale > 100 || iyscale > 100 )
+ fastAreaScaling = true;
+ if( fastAreaScaling )
+ textureFragmentShader = "areaScaleFastFragmentShader";
+ else
+ textureFragmentShader = "areaScaleFragmentShader";
+ }
+
+ OpenGLTexture aInTexture = rTexture;
+ OpenGLTexture aInMask = rMask;
+
+ // When using the area scaling algorithm we need to reduce the texture size in 2 passes
+ // in order to not use a big array inside the fragment shader.
+ if (areaScaling && !fastAreaScaling)
+ {
+ // Perform a first texture downscaling by an inverted scale ratio equal to
+ // the square root of the whole inverted scale ratio.
+ if (ixscale > 16 || iyscale > 16)
+ {
+ // The scissor area is set to the current window size in PreDraw,
+ // so if we do not disable the scissor test, the texture produced
+ // by the first downscaling is clipped to the current window size.
+ mpContext->state().scissor().disable();
+ mpContext->state().stencil().disable();
+
+ // the square root of the whole inverted scale ratio
+ double ixscalesqrt = std::floor(std::sqrt(ixscale));
+ double iyscalesqrt = std::floor(std::sqrt(iyscale));
+ ixscale /= ixscalesqrt; // second pass inverted x-scale factor
+ iyscale /= iyscalesqrt; // second pass inverted y-scale factor
+
+ scaleTexture(mpContext, aInTexture, ixscalesqrt, iyscalesqrt, rTexture);
+
+ if (rMask) // we need to downscale the mask too
+ {
+ scaleTexture(mpContext, aInMask, ixscalesqrt, iyscalesqrt, rMask);
+ }
+
+ // We need to re-acquire the off-screen texture.
+ CheckOffscreenTexture();
+ CHECK_GL_ERROR();
+
+ // Re-enable scissor and stencil tests if needed.
+ if (mbUseScissor)
+ mpContext->state().scissor().enable();
+
+ if (mbUseStencil)
+ mpContext->state().stencil().enable();
+ }
+ }
+
+ if( aInMask )
+ {
+ if( !UseProgram( "transformedTextureVertexShader",
+ textureFragmentShader.isEmpty() ? "maskedTextureFragmentShader" : textureFragmentShader,
+ "#define MASKED\n" + sUseReducedRegisterVariantDefine))
+ return;
+ mpProgram->SetTexture( "mask", aInMask );
+ GLfloat aMaskCoord[8];
+ aInMask.GetWholeCoord(aMaskCoord);
+ mpProgram->SetMaskCoord(aMaskCoord);
+ aInMask.SetFilter( GL_LINEAR );
+ mpProgram->SetBlendMode( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
+ }
+ else
+ {
+ if( !UseProgram( "transformedTextureVertexShader",
+ textureFragmentShader.isEmpty() ? "textureFragmentShader" : textureFragmentShader,
+ sUseReducedRegisterVariantDefine))
+ return;
+ }
+
+ if(areaScaling)
+ {
+ int nWidth = aInTexture.GetWidth();
+ int nHeight = aInTexture.GetHeight();
+
+ // From OpenGLSalBitmap::ImplScaleArea().
+ if (fastAreaScaling && nWidth && nHeight)
+ {
+ mpProgram->SetUniform1i( "xscale", ixscale );
+ mpProgram->SetUniform1i( "yscale", iyscale );
+ GLfloat srcCoords[ 8 ];
+ aInTexture.GetWholeCoord( srcCoords );
+ mpProgram->SetUniform1f( "xstep", ( srcCoords[ 4 ] - srcCoords[ 0 ] ) / nWidth );
+ mpProgram->SetUniform1f( "ystep", ( srcCoords[ 5 ] - srcCoords[ 1 ] ) / nHeight );
+ mpProgram->SetUniform1f( "ratio", 1.0 / ( ixscale * iyscale ));
+ }
+ else if (nHeight > 1 && nWidth > 1)
+ {
+ mpProgram->SetUniform1f( "xscale", ixscale );
+ mpProgram->SetUniform1f( "yscale", iyscale );
+ mpProgram->SetUniform1i( "swidth", nWidth );
+ mpProgram->SetUniform1i( "sheight", nHeight );
+ // For converting between <0,nWidth-1> and <0.0,1.0> coordinate systems.
+ GLfloat srcCoords[ 8 ];
+ aInTexture.GetWholeCoord( srcCoords );
+ mpProgram->SetUniform1f( "xoffset", srcCoords[ 0 ] );
+ mpProgram->SetUniform1f( "yoffset", srcCoords[ 1 ] );
+ mpProgram->SetUniform1f( "xtopixelratio", ( nWidth / ixscale ) / ( srcCoords[ 4 ] - srcCoords[ 0 ] ));
+ mpProgram->SetUniform1f( "ytopixelratio", ( nHeight / iyscale ) / ( srcCoords[ 5 ] - srcCoords[ 1 ] ));
+ mpProgram->SetUniform1f( "xfrompixelratio", ( srcCoords[ 4 ] - srcCoords[ 0 ] ) / nWidth );
+ mpProgram->SetUniform1f( "yfrompixelratio", ( srcCoords[ 5 ] - srcCoords[ 1 ] ) / nHeight );
+ }
+ }
+
+ ApplyProgramMatrices();
+ mpProgram->SetUniform2f( "viewport", GetWidth(), GetHeight() );
+ // Here, in order to get the correct transformation we need to pass the original texture,
+ // since it has been used for initializing the rectangle vertices.
+ mpProgram->SetTransform( "transform", rTexture, rNull, rX, rY );
+ aInTexture.GetWholeCoord(aTexCoord);
+ mpProgram->SetTexture("sampler", aInTexture);
+ aInTexture.SetFilter(GL_LINEAR);
+ mpProgram->SetTextureCoord( aTexCoord );
+ mpProgram->DrawArrays(GL_TRIANGLE_FAN, aVertices);
+
+ CHECK_GL_ERROR();
+ mpProgram->Clean();
+}
+
+void OpenGLSalGraphicsImpl::DrawAlphaTexture( OpenGLTexture& rTexture, const SalTwoRect& rPosAry, bool bInverted, bool bPremultiplied )
+{
+ OpenGLZone aZone;
+
+ if (!UseProgram("combinedTextureVertexShader", "combinedTextureFragmentShader"))
+ return;
+ mpProgram->SetShaderType(TextureShaderType::Normal);
+ mpProgram->SetIdentityTransform("transform");
+ mpProgram->SetTexture("texture", rTexture);
+ mpProgram->SetBlendMode( bPremultiplied ? GL_ONE : GL_SRC_ALPHA,
+ GL_ONE_MINUS_SRC_ALPHA );
+
+ GLfloat aTexCoord[8];
+ rTexture.GetCoord(aTexCoord, rPosAry, bInverted);
+ mpProgram->SetTextureCoord(aTexCoord);
+ mpProgram->SetMaskCoord(aTexCoord);
+ mpProgram->SetAlphaCoord(aTexCoord);
+
+ DrawTextureRect( rPosAry );
+ mpProgram->Clean();
+}
+
+void OpenGLSalGraphicsImpl::DrawTextureDiff( OpenGLTexture& rTexture, OpenGLTexture& rMask, const SalTwoRect& rPosAry, bool bInverted )
+{
+ OpenGLZone aZone;
+
+ if (!UseProgram("combinedTextureVertexShader", "combinedTextureFragmentShader"))
+ return;
+ mpProgram->SetShaderType(TextureShaderType::Diff);
+ mpProgram->SetIdentityTransform("transform");
+ mpProgram->SetTexture( "texture", rTexture );
+ mpProgram->SetTexture( "mask", rMask );
+ mpProgram->SetBlendMode( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
+
+ GLfloat aTexCoord[8];
+ rTexture.GetCoord(aTexCoord, rPosAry, bInverted);
+ mpProgram->SetTextureCoord(aTexCoord);
+ mpProgram->SetAlphaCoord(aTexCoord);
+
+ GLfloat aMaskCoord[8];
+ rMask.GetCoord(aMaskCoord, rPosAry, bInverted);
+ mpProgram->SetMaskCoord(aMaskCoord);
+
+ DrawTextureRect( rPosAry );
+ mpProgram->Clean();
+}
+
+void OpenGLSalGraphicsImpl::DrawTextureWithMask( OpenGLTexture& rTexture, OpenGLTexture& rMask, const SalTwoRect& rPosAry )
+{
+ OpenGLZone aZone;
+
+ if (!UseProgram("combinedTextureVertexShader", "combinedTextureFragmentShader"))
+ return;
+ mpProgram->SetShaderType(TextureShaderType::Masked);
+ mpProgram->SetIdentityTransform("transform");
+ mpProgram->SetTexture( "texture", rTexture );
+ mpProgram->SetTexture( "mask", rMask );
+ mpProgram->SetBlendMode( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
+
+ GLfloat aTexCoord[8];
+ rTexture.GetCoord(aTexCoord, rPosAry);
+ mpProgram->SetTextureCoord(aTexCoord);
+ mpProgram->SetAlphaCoord(aTexCoord);
+
+ GLfloat aMaskCoord[8];
+ rMask.GetCoord(aMaskCoord, rPosAry);
+ mpProgram->SetMaskCoord(aMaskCoord);
+
+ DrawTextureRect(rPosAry);
+ mpProgram->Clean();
+}
+
+void OpenGLSalGraphicsImpl::DrawBlendedTexture( OpenGLTexture& rTexture, OpenGLTexture& rMask, OpenGLTexture& rAlpha, const SalTwoRect& rPosAry )
+{
+ OpenGLZone aZone;
+
+ if (!UseProgram("combinedTextureVertexShader", "combinedTextureFragmentShader"))
+ return;
+ mpProgram->SetShaderType(TextureShaderType::Blend);
+ mpProgram->SetTexture( "texture", rTexture );
+ mpProgram->SetTexture( "mask", rMask );
+ mpProgram->SetTexture( "alpha", rAlpha );
+
+ GLfloat aTexCoord[8];
+ rTexture.GetCoord(aTexCoord, rPosAry);
+ mpProgram->SetTextureCoord(aTexCoord);
+
+ GLfloat aAlphaCoord[8];
+ rAlpha.GetCoord(aAlphaCoord, rPosAry);
+ mpProgram->SetAlphaCoord(aAlphaCoord);
+
+ GLfloat aMaskCoord[8];
+ rMask.GetCoord(aMaskCoord, rPosAry);
+ mpProgram->SetMaskCoord(aMaskCoord);
+
+ mpProgram->SetBlendMode( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
+ DrawTextureRect( rPosAry );
+ mpProgram->Clean();
+}
+
+void OpenGLSalGraphicsImpl::DrawMask( OpenGLTexture& rMask, Color nMaskColor, const SalTwoRect& rPosAry )
+{
+ OpenGLZone aZone;
+
+ if (!UseProgram("combinedTextureVertexShader", "combinedTextureFragmentShader"))
+ return;
+ mpProgram->SetShaderType(TextureShaderType::MaskedColor);
+ mpProgram->SetIdentityTransform("transform");
+ mpProgram->SetColor( "color", nMaskColor, 0 );
+ mpProgram->SetTexture("texture", rMask);
+
+ GLfloat aTexCoord[8];
+ rMask.GetCoord(aTexCoord, rPosAry);
+ mpProgram->SetTextureCoord(aTexCoord);
+ mpProgram->SetMaskCoord(aTexCoord);
+ mpProgram->SetAlphaCoord(aTexCoord);
+
+ mpProgram->SetBlendMode( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
+ DrawTextureRect(rPosAry);
+ mpProgram->Clean();
+}
+
+void OpenGLSalGraphicsImpl::FlushLinesOrTriangles(DrawShaderType eType, RenderParameters const & rParameters)
+{
+ if (!UseProgram("combinedVertexShader", "combinedFragmentShader", "#define USE_VERTEX_COLORS"))
+ return;
+
+ mpProgram->SetShaderType(eType);
+ mpProgram->SetBlendMode(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ ApplyProgramMatrices(0.5f);
+
+ vcl::VertexBufferObject<Vertex> vbo;
+ vbo.upload(rParameters.maVertices);
+
+ GLuint positionAttrib = SAL_MAX_UINT32;
+ GLuint colorAttrib = SAL_MAX_UINT32;
+ GLuint lineDataAttrib = SAL_MAX_UINT32;
+
+ mpProgram->SetVertexAttrib(positionAttrib, "position", 2, GL_FLOAT, GL_FALSE,
+ sizeof(Vertex), reinterpret_cast<void*>(offsetof(Vertex, position)));
+
+ mpProgram->SetVertexAttrib(colorAttrib, "vertex_color_in", 4, GL_FLOAT, GL_FALSE,
+ sizeof(Vertex), reinterpret_cast<void*>(offsetof(Vertex, color)));
+
+ mpProgram->SetVertexAttrib(lineDataAttrib, "extrusion_vectors", 4, GL_FLOAT, GL_FALSE,
+ sizeof(Vertex), reinterpret_cast<void*>(offsetof(Vertex, lineData)));
+
+ vcl::IndexBufferObject ibo;
+ ibo.upload(rParameters.maIndices);
+ ibo.bind();
+
+ mpProgram->DrawElements(GL_TRIANGLES, rParameters.maIndices.size());
+ CHECK_GL_ERROR();
+
+ mpProgram->Clean();
+}
+
+void OpenGLSalGraphicsImpl::FlushDeferredDrawing()
+{
+ if (mpRenderList->empty())
+ return;
+
+ VCL_GL_INFO("FlushDeferredDrawing: " << mpRenderList->getEntries().size());
+
+ InitializePreDrawState(XOROption::IMPLEMENT_XOR);
+
+ OpenGLZone aZone;
+ for (RenderEntry& rRenderEntry : mpRenderList->getEntries())
+ {
+ if (rRenderEntry.hasTriangles())
+ {
+ RenderParameters& rParameters = rRenderEntry.maTriangleParameters;
+ VCL_GL_INFO("Flush Triangles: " << rParameters.maVertices.size());
+ FlushLinesOrTriangles(DrawShaderType::Normal, rParameters);
+ }
+ if (rRenderEntry.hasLines())
+ {
+ RenderParameters& rParameters = rRenderEntry.maLineParameters;
+ VCL_GL_INFO("Flush Lines: " << rParameters.maVertices.size());
+ FlushLinesOrTriangles(DrawShaderType::Line, rParameters);
+ }
+ if (rRenderEntry.hasTextures() && UseProgram("combinedTextureVertexShader", "combinedTextureFragmentShader", "#define USE_VERTEX_COLORS"))
+ {
+ mpProgram->SetShaderType(TextureShaderType::MaskedColor);
+ mpProgram->SetIdentityTransform("transform");
+ mpProgram->SetBlendMode(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+ for (auto& rPair : rRenderEntry.maTextureParametersMap)
+ {
+ RenderTextureParameters& rParameters = rPair.second;
+ mpProgram->SetTexture("texture", rParameters.maTexture);
+ ApplyProgramMatrices();
+ mpProgram->SetTextureCoord(rParameters.maTextureCoords.data());
+ mpProgram->SetMaskCoord(rParameters.maTextureCoords.data());
+ mpProgram->SetAlphaCoord(rParameters.maTextureCoords.data());
+ mpProgram->SetVertexColors(rParameters.maColors);
+ mpProgram->DrawArrays(GL_TRIANGLES, rParameters.maVertices);
+ CHECK_GL_ERROR();
+ }
+ mpProgram->Clean();
+ }
+ }
+
+ mpRenderList->clear();
+ PostDraw();
+
+ VCL_GL_INFO("End FlushDeferredDrawing");
+}
+
+void OpenGLSalGraphicsImpl::DrawLinearGradient( const Gradient& rGradient, const tools::Rectangle& rRect )
+{
+ OpenGLZone aZone;
+
+ if( !UseProgram( "textureVertexShader", "linearGradientFragmentShader" ) )
+ return;
+ Color aStartCol = rGradient.GetStartColor();
+ Color aEndCol = rGradient.GetEndColor();
+ long nFactor = rGradient.GetStartIntensity();
+ mpProgram->SetColorWithIntensity( "start_color", aStartCol, nFactor );
+ nFactor = rGradient.GetEndIntensity();
+ mpProgram->SetColorWithIntensity( "end_color", aEndCol, nFactor );
+
+ tools::Rectangle aBoundRect;
+ Point aCenter;
+ rGradient.GetBoundRect( rRect, aBoundRect, aCenter );
+ tools::Polygon aPoly( aBoundRect );
+ aPoly.Rotate( aCenter, rGradient.GetAngle() % 3600 );
+
+ GLfloat aTexCoord[8] = { 0, 1, 1, 1, 1, 0, 0, 0 };
+ GLfloat fMin = 1.0 - 100.0 / (100.0 - rGradient.GetBorder());
+ aTexCoord[5] = aTexCoord[7] = fMin;
+ mpProgram->SetTextureCoord( aTexCoord );
+ DrawConvexPolygon( aPoly, true );
+}
+
+void OpenGLSalGraphicsImpl::DrawAxialGradient( const Gradient& rGradient, const tools::Rectangle& rRect )
+{
+ OpenGLZone aZone;
+
+ if( !UseProgram( "textureVertexShader", "linearGradientFragmentShader" ) )
+ return;
+ Color aStartCol = rGradient.GetStartColor();
+ Color aEndCol = rGradient.GetEndColor();
+ long nFactor = rGradient.GetStartIntensity();
+ mpProgram->SetColorWithIntensity( "start_color", aStartCol, nFactor );
+ nFactor = rGradient.GetEndIntensity();
+ mpProgram->SetColorWithIntensity( "end_color", aEndCol, nFactor );
+
+ /**
+ * Draw two rectangles with linear gradient.
+ *
+ * 1 *---* 2
+ * | /|
+ * | / | Points 0 and 3 have start color
+ * 0 |/__| 3 Points 1, 2, 4 and 5 have end color
+ * |\ |
+ * | \ |
+ * | \|
+ * 5 *---* 4
+ *
+ */
+
+ tools::Rectangle aRect;
+ Point aCenter;
+ rGradient.GetBoundRect( rRect, aRect, aCenter );
+
+ // determine points 0 and 3
+ Point aPt0( aRect.Left(), (aRect.Top() + aRect.Bottom() + 1) / 2 );
+ Point aPt3( aRect.Right(), (aRect.Top() + aRect.Bottom() + 1) / 2 );
+
+ tools::Polygon aPoly( 7 );
+ aPoly.SetPoint( aPt0, 0 );
+ aPoly.SetPoint( aRect.TopLeft(), 1 );
+ aPoly.SetPoint( aRect.TopRight(), 2 );
+ aPoly.SetPoint( aPt3, 3 );
+ aPoly.SetPoint( aRect.BottomRight(), 4 );
+ aPoly.SetPoint( aRect.BottomLeft(), 5 );
+ aPoly.SetPoint( aPt0, 6 );
+ aPoly.Rotate( aCenter, rGradient.GetAngle() % 3600 );
+
+ GLfloat aTexCoord[12] = { 0, 1, 1, 0, 2, 0, 3, 1, 4, 0, 5, 0 };
+ GLfloat fMin = 1.0 - 100.0 / (100.0 - rGradient.GetBorder());
+ aTexCoord[3] = aTexCoord[5] = aTexCoord[9] = aTexCoord[11] = fMin;
+ mpProgram->SetTextureCoord( aTexCoord );
+ DrawConvexPolygon( aPoly, true );
+}
+
+void OpenGLSalGraphicsImpl::DrawRadialGradient( const Gradient& rGradient, const tools::Rectangle& rRect )
+{
+ OpenGLZone aZone;
+
+ if( !UseProgram( "textureVertexShader", "radialGradientFragmentShader" ) )
+ return;
+ Color aStartCol = rGradient.GetStartColor();
+ Color aEndCol = rGradient.GetEndColor();
+ long nFactor = rGradient.GetStartIntensity();
+ mpProgram->SetColorWithIntensity( "start_color", aStartCol, nFactor );
+ nFactor = rGradient.GetEndIntensity();
+ mpProgram->SetColorWithIntensity( "end_color", aEndCol, nFactor );
+
+ tools::Rectangle aRect;
+ Point aCenter;
+ rGradient.GetBoundRect( rRect, aRect, aCenter );
+
+ // adjust coordinates so that radius has distance equals to 1.0
+ double fRadius = aRect.GetWidth() / 2.0f;
+ GLfloat fWidth = rRect.GetWidth() / fRadius;
+ GLfloat fHeight = rRect.GetHeight() / fRadius;
+ GLfloat aTexCoord[8] = { 0, 0, 0, fHeight, fWidth, fHeight, fWidth, 0 };
+ mpProgram->SetTextureCoord( aTexCoord );
+ mpProgram->SetUniform2f( "center", (aCenter.X() - rRect.Left()) / fRadius,
+ (aCenter.Y() - rRect.Top()) / fRadius );
+ DrawRect( rRect );
+}
+
+void OpenGLSalGraphicsImpl::drawPixel(long nX, long nY)
+{
+ VCL_GL_INFO("::drawPixel: (" << nX << ", " << nY << ")");
+ mpRenderList->addDrawPixel(nX, nY, mnLineColor);
+ PostBatchDraw();
+}
+
+void OpenGLSalGraphicsImpl::drawPixel(long nX, long nY, Color nColor)
+{
+ VCL_GL_INFO("::drawPixel: (" << nX << ", " << nY << ")");
+ mpRenderList->addDrawPixel(nX, nY, nColor);
+ PostBatchDraw();
+}
+
+void OpenGLSalGraphicsImpl::drawLine(long nX1, long nY1, long nX2, long nY2)
+{
+ VCL_GL_INFO("::drawLine (" << nX1 << ", " << nY1 << ") (" << nX2 << ", " << nY2 << ")");
+ mpRenderList->addDrawLine(nX1, nY1, nX2, nY2, mnLineColor, mrParent.getAntiAliasB2DDraw());
+ PostBatchDraw();
+}
+
+void OpenGLSalGraphicsImpl::drawRect( long nX, long nY, long nWidth, long nHeight )
+{
+ VCL_GL_INFO("::drawRect (" << nX << ", " << nY << ") [" << nWidth << ", " << nHeight << "]");
+ mpRenderList->addDrawRectangle(nX, nY, nWidth, nHeight, 0.0, mnLineColor, mnFillColor);
+ PostBatchDraw();
+}
+
+void OpenGLSalGraphicsImpl::drawPolyLine( sal_uInt32 nPoints, const SalPoint* pPtAry )
+{
+ VCL_GL_INFO("::drawPolyLine legacy -> redirecting to drawPolyLine");
+ basegfx::B2DPolygon aPoly;
+ aPoly.append(basegfx::B2DPoint(pPtAry->mnX, pPtAry->mnY), nPoints);
+ for (sal_uInt32 i = 1; i < nPoints; ++i)
+ aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].mnX, pPtAry[i].mnY));
+ aPoly.setClosed(false);
+
+ drawPolyLine(
+ basegfx::B2DHomMatrix(),
+ aPoly,
+ 0.0,
+ 1.0,
+ nullptr, // MM01
+ basegfx::B2DLineJoin::Miter,
+ css::drawing::LineCap_BUTT,
+ basegfx::deg2rad(15.0) /*default*/,
+ false);
+}
+
+void OpenGLSalGraphicsImpl::drawPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry )
+{
+ VCL_GL_INFO("::drawPolygon legacy -> redirecting to drawPolyPolygon with transparency");
+ basegfx::B2DPolygon aPoly;
+ aPoly.append(basegfx::B2DPoint(pPtAry->mnX, pPtAry->mnY), nPoints);
+ for (sal_uInt32 i = 1; i < nPoints; ++i)
+ aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].mnX, pPtAry[i].mnY));
+
+ drawPolyPolygon(
+ basegfx::B2DHomMatrix(),
+ basegfx::B2DPolyPolygon(aPoly),
+ 0.0);
+}
+
+void OpenGLSalGraphicsImpl::drawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPointCounts, PCONSTSALPOINT* pPtAry )
+{
+ VCL_GL_INFO("::drawPolyPolygon legacy -> redirecting to drawPolyPolygon with transparency");
+ basegfx::B2DPolyPolygon aPolyPoly;
+ for(sal_uInt32 nPolygon = 0; nPolygon < nPoly; ++nPolygon)
+ {
+ sal_uInt32 nPoints = pPointCounts[nPolygon];
+ if (nPoints)
+ {
+ PCONSTSALPOINT pPoints = pPtAry[nPolygon];
+ basegfx::B2DPolygon aPoly;
+ aPoly.append( basegfx::B2DPoint(pPoints->mnX, pPoints->mnY), nPoints);
+ for (sal_uInt32 i = 1; i < nPoints; ++i)
+ aPoly.setB2DPoint(i, basegfx::B2DPoint( pPoints[i].mnX, pPoints[i].mnY));
+
+ aPolyPoly.append(aPoly);
+ }
+ }
+
+ drawPolyPolygon(
+ basegfx::B2DHomMatrix(),
+ aPolyPoly,
+ 0.0);
+}
+
+bool OpenGLSalGraphicsImpl::drawPolyPolygon(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon& rPolyPolygon,
+ double fTransparency)
+{
+ VCL_GL_INFO("::drawPolyPolygon " << rPolyPolygon.getB2DRange());
+
+ // Fallback: Transform to DeviceCoordinates
+ basegfx::B2DPolyPolygon aPolyPolygon(rPolyPolygon);
+ aPolyPolygon.transform(rObjectToDevice);
+
+ // FlushLinesOrTriangles() works with a 0.5 pixel offset, compensate for that here.
+ basegfx::B2DHomMatrix aMatrix;
+ aMatrix.translate(-0.5f, -0.5f);
+ aPolyPolygon.transform(aMatrix);
+
+ mpRenderList->addDrawPolyPolygon(
+ aPolyPolygon,
+ fTransparency,
+ mnLineColor,
+ mnFillColor,
+ mrParent.getAntiAliasB2DDraw());
+
+ PostBatchDraw();
+ return true;
+}
+
+bool OpenGLSalGraphicsImpl::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)
+{
+ VCL_GL_INFO("::drawPolyLine " << rPolygon.getB2DRange());
+
+ // MM01 check done for simple reasons
+ if(!rPolygon.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(
+ rPolygon, // source
+ *pStroke, // pattern
+ &aPolyPolygonLine, // target for lines
+ nullptr, // target for gaps
+ fDotDashLength); // full length if available
+ }
+ else
+ {
+ // no line dashing, just copy
+ aPolyPolygonLine.append(rPolygon);
+ }
+
+ // 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();
+
+ for(sal_uInt32 a(0); a < aPolyPolygonLine.count(); a++)
+ {
+ // addDrawPolyLine() assumes that there are no duplicate points in the polygon
+ basegfx::B2DPolygon aPolyLine(aPolyPolygonLine.getB2DPolygon(a));
+ basegfx::utils::simplifyCurveSegments(aPolyLine);
+ aPolyLine.removeDoublePoints();
+
+ mpRenderList->addDrawPolyLine(
+ aPolyLine,
+ fTransparency,
+ fLineWidth,
+ eLineJoin,
+ eLineCap,
+ fMiterMinimumAngle,
+ mnLineColor,
+ mrParent.getAntiAliasB2DDraw());
+
+ // MM01: not sure - maybe this can be moved out of this loop, but to
+ // keep on the safe side for now, do not really change something for now
+ PostBatchDraw();
+ }
+
+ return true;
+}
+
+bool OpenGLSalGraphicsImpl::drawPolyLineBezier(
+ sal_uInt32 /*nPoints*/,
+ const SalPoint* /*pPtAry*/,
+ const PolyFlags* /*pFlgAry*/ )
+{
+ return false;
+}
+
+bool OpenGLSalGraphicsImpl::drawPolygonBezier(
+ sal_uInt32 /*nPoints*/,
+ const SalPoint* /*pPtAry*/,
+ const PolyFlags* /*pFlgAry*/ )
+{
+ return false;
+}
+
+bool OpenGLSalGraphicsImpl::drawPolyPolygonBezier(
+ sal_uInt32 /*nPoly*/,
+ const sal_uInt32* /*pPoints*/,
+ const SalPoint* const* /*pPtAry*/,
+ const PolyFlags* const* /*pFlgAry*/ )
+{
+ return false;
+}
+
+// CopyArea --> No RasterOp, but ClipRegion
+void OpenGLSalGraphicsImpl::copyArea(
+ long nDestX, long nDestY,
+ long nSrcX, long nSrcY,
+ long nSrcWidth, long nSrcHeight, bool /*bWindowInvalidate*/ )
+{
+ VCL_GL_INFO( "::copyArea " << nSrcX << "," << nSrcY << " >> " << nDestX << "," << nDestY << " (" << nSrcWidth << "," << nSrcHeight << ")" );
+ OpenGLTexture aTexture;
+ SalTwoRect aPosAry(0, 0, nSrcWidth, nSrcHeight, nDestX, nDestY, nSrcWidth, nSrcHeight);
+
+ PreDraw();
+ // TODO offscreen case
+ aTexture = OpenGLTexture( nSrcX, GetHeight() - nSrcY - nSrcHeight,
+ nSrcWidth, nSrcHeight );
+ DrawTexture( aTexture, aPosAry );
+ PostDraw();
+}
+
+// CopyBits and DrawBitmap --> RasterOp and ClipRegion
+// CopyBits() --> pSrcGraphics == NULL, then CopyBits on same Graphics
+void OpenGLSalGraphicsImpl::DoCopyBits( const SalTwoRect& rPosAry, OpenGLSalGraphicsImpl& rImpl )
+{
+ VCL_GL_INFO( "::copyBits" );
+
+ rImpl.FlushDeferredDrawing();
+
+ if( !rImpl.maOffscreenTex )
+ {
+ VCL_GL_INFO( "::copyBits - skipping copy of un-initialized framebuffer contents of size "
+ << rImpl.GetWidth() << "x" << rImpl.GetHeight() );
+ return;
+ }
+
+ if( &rImpl == this &&
+ (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;
+ }
+
+ PreDraw();
+ DrawTexture( rImpl.maOffscreenTex, rPosAry );
+ PostDraw();
+}
+
+void OpenGLSalGraphicsImpl::drawBitmap( const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap )
+{
+ // check that carefully only in the debug mode
+ assert(dynamic_cast<const OpenGLSalBitmap*>(&rSalBitmap));
+
+ OpenGLZone aZone;
+
+ const OpenGLSalBitmap& rBitmap = static_cast<const OpenGLSalBitmap&>(rSalBitmap);
+ OpenGLTexture& rTexture = rBitmap.GetTexture();
+
+ VCL_GL_INFO( "::drawBitmap" );
+ PreDraw();
+ if (rPosAry.mnSrcWidth != rPosAry.mnDestWidth ||
+ rPosAry.mnSrcHeight != rPosAry.mnDestHeight)
+ {
+ basegfx::B2DPoint aNull(rPosAry.mnDestX,rPosAry.mnDestY);
+ basegfx::B2DPoint aX(rPosAry.mnDestX + rPosAry.mnDestWidth, rPosAry.mnDestY);
+ basegfx::B2DPoint aY(rPosAry.mnDestX, rPosAry.mnDestY + rPosAry.mnDestHeight);
+ OpenGLTexture mask; // no mask set
+ DrawTransformedTexture(rTexture, mask, aNull, aX, aY);
+ }
+ else
+ {
+ DrawTexture( rTexture, rPosAry );
+ }
+ PostDraw();
+}
+
+void OpenGLSalGraphicsImpl::drawBitmap(
+ const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap,
+ const SalBitmap& rMaskBitmap )
+{
+ VCL_GL_INFO("::drawBitmap with MASK -> redirect to ::drawAlphaBitmap");
+ drawAlphaBitmap(rPosAry, rSalBitmap, rMaskBitmap);
+}
+
+void OpenGLSalGraphicsImpl::drawMask(
+ const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap,
+ Color nMaskColor )
+{
+ VCL_GL_INFO("::drawMask");
+
+ assert(dynamic_cast<const OpenGLSalBitmap*>(&rSalBitmap));
+ const OpenGLSalBitmap& rBitmap = static_cast<const OpenGLSalBitmap&>(rSalBitmap);
+ mpRenderList->addDrawTextureWithMaskColor(rBitmap.GetTexture(), nMaskColor, rPosAry);
+ PostBatchDraw();
+}
+
+std::shared_ptr<SalBitmap> OpenGLSalGraphicsImpl::getBitmap( long nX, long nY, long nWidth, long nHeight )
+{
+ FlushDeferredDrawing();
+
+ OpenGLZone aZone;
+
+ std::shared_ptr<OpenGLSalBitmap> pBitmap(std::make_shared<OpenGLSalBitmap>());
+ VCL_GL_INFO( "::getBitmap " << nX << "," << nY <<
+ " " << nWidth << "x" << nHeight );
+ //TODO really needed?
+ PreDraw();
+ pBitmap->Create( maOffscreenTex, nX, nY, nWidth, nHeight );
+ PostDraw();
+ return pBitmap;
+}
+
+Color OpenGLSalGraphicsImpl::getPixel( long nX, long nY )
+{
+ FlushDeferredDrawing();
+
+ char pixel[3] = { 0, 0, 0 };
+
+ PreDraw( XOROption::IMPLEMENT_XOR );
+ nY = GetHeight() - nY - 1;
+ glReadPixels( nX, nY, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, pixel);
+ CHECK_GL_ERROR();
+ PostDraw();
+
+ return Color( pixel[0], pixel[1], pixel[2] );
+}
+
+// invert --> ClipRegion (only Windows or VirDevs)
+void OpenGLSalGraphicsImpl::invert(
+ long nX, long nY,
+ long nWidth, long nHeight,
+ SalInvert nFlags)
+{
+ PreDraw();
+
+ if( UseInvert( nFlags ) )
+ {
+ if( nFlags & SalInvert::TrackFrame )
+ { // FIXME: could be more efficient.
+ DrawRect( nX, nY, nWidth, 1 );
+ DrawRect( nX, nY + nHeight, nWidth, 1 );
+ DrawRect( nX, nY, 1, nHeight );
+ DrawRect( nX + nWidth, nY, 1, nHeight );
+ }
+ else
+ DrawRect( nX, nY, nWidth, nHeight );
+ }
+
+ PostDraw();
+}
+
+void OpenGLSalGraphicsImpl::invert( sal_uInt32 nPoints, const SalPoint* pPtAry, SalInvert nFlags )
+{
+ PreDraw();
+
+ if( UseInvert( nFlags ) )
+ {
+ if (nFlags & SalInvert::TrackFrame)
+ {
+ // Track frame means the invert50FragmentShader must remain active
+ // (to draw what looks like a dashed line), so DrawLineSegment()
+ // can't be used. Draw the edge of the polygon as polygons instead.
+ for (size_t nPoint = 0; nPoint < nPoints; ++nPoint)
+ {
+ const SalPoint& rFrom = pPtAry[nPoint];
+ const SalPoint& rTo = pPtAry[(nPoint + 1) % nPoints];
+ if (rFrom.mnX == rTo.mnX)
+ {
+ // Extend to the right, comments assuming "to" is above
+ // "from":
+ const SalPoint aPoints[] = { { rFrom.mnX + 1, rFrom.mnY }, // bottom right
+ { rFrom.mnX, rFrom.mnY }, // bottom left
+ { rTo.mnX, rTo.mnY }, // top left
+ { rTo.mnX + 1, rTo.mnY } }; // top right
+ DrawConvexPolygon(4, aPoints, true);
+ }
+ else
+ {
+ // Otherwise can extend downwards, comments assuming "to"
+ // is above and on the right of "from":
+ const SalPoint aPoints[] = { { rFrom.mnX, rFrom.mnY + 1 }, // bottom left
+ { rFrom.mnX, rFrom.mnY }, // top left
+ { rTo.mnX, rTo.mnY }, // top right
+ { rTo.mnX, rTo.mnY + 1 } }; // bottom right
+ DrawConvexPolygon(4, aPoints, true);
+ }
+ }
+ }
+ else
+ DrawPolygon(nPoints, pPtAry);
+ }
+
+ PostDraw();
+}
+
+bool OpenGLSalGraphicsImpl::drawEPS(
+ long /*nX*/, long /*nY*/,
+ long /*nWidth*/, long /*nHeight*/,
+ void* /*pPtr*/,
+ sal_uInt32 /*nSize*/ )
+{
+ return false;
+}
+
+bool OpenGLSalGraphicsImpl::blendBitmap(
+ const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap )
+{
+ assert(dynamic_cast<const OpenGLSalBitmap*>(&rSalBitmap));
+
+ OpenGLZone aZone;
+
+ const OpenGLSalBitmap& rBitmap = static_cast<const OpenGLSalBitmap&>(rSalBitmap);
+ OpenGLTexture& rTexture( rBitmap.GetTexture() );
+
+ VCL_GL_INFO( "::blendBitmap" );
+ PreDraw();
+
+ if (!UseProgram("combinedTextureVertexShader", "combinedTextureFragmentShader"))
+ return true;
+
+ mpProgram->SetShaderType(TextureShaderType::Normal);
+ mpProgram->SetIdentityTransform("transform");
+ mpProgram->SetTexture("texture", rTexture);
+
+ GLfloat aTexCoord[8];
+ rTexture.GetCoord(aTexCoord, rPosAry);
+ mpProgram->SetTextureCoord(aTexCoord);
+ mpProgram->SetMaskCoord(aTexCoord);
+ mpProgram->SetAlphaCoord(aTexCoord);
+
+ mpProgram->SetBlendMode(GL_ZERO, GL_SRC_COLOR);
+ DrawTextureRect(rPosAry);
+ mpProgram->Clean();
+
+ PostDraw();
+ return true;
+}
+
+bool OpenGLSalGraphicsImpl::blendAlphaBitmap(
+ const SalTwoRect& rPosAry,
+ const SalBitmap& rSalSrcBitmap,
+ const SalBitmap& rSalMaskBitmap,
+ const SalBitmap& rSalAlphaBitmap )
+{
+ assert(dynamic_cast<const OpenGLSalBitmap*>(&rSalSrcBitmap));
+ assert(dynamic_cast<const OpenGLSalBitmap*>(&rSalMaskBitmap));
+ assert(dynamic_cast<const OpenGLSalBitmap*>(&rSalAlphaBitmap));
+
+ OpenGLZone aZone;
+
+ const OpenGLSalBitmap& rSrcBitmap = static_cast<const OpenGLSalBitmap&>(rSalSrcBitmap);
+ const OpenGLSalBitmap& rMaskBitmap = static_cast<const OpenGLSalBitmap&>(rSalMaskBitmap);
+ const OpenGLSalBitmap& rAlphaBitmap = static_cast<const OpenGLSalBitmap&>(rSalAlphaBitmap);
+ OpenGLTexture& rTexture( rSrcBitmap.GetTexture() );
+ OpenGLTexture& rMask( rMaskBitmap.GetTexture() );
+ OpenGLTexture& rAlpha( rAlphaBitmap.GetTexture() );
+
+ VCL_GL_INFO( "::blendAlphaBitmap" );
+ PreDraw();
+ DrawBlendedTexture( rTexture, rMask, rAlpha, rPosAry );
+ PostDraw();
+ return true;
+}
+
+/** 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 OpenGLSalGraphicsImpl::drawAlphaBitmap(
+ const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap,
+ const SalBitmap& rAlphaBitmap )
+{
+ assert(dynamic_cast<const OpenGLSalBitmap*>(&rSalBitmap));
+ assert(dynamic_cast<const OpenGLSalBitmap*>(&rAlphaBitmap));
+
+ OpenGLZone aZone;
+
+ const OpenGLSalBitmap& rBitmap = static_cast<const OpenGLSalBitmap&>(rSalBitmap);
+ const OpenGLSalBitmap& rAlpha = static_cast<const OpenGLSalBitmap&>(rAlphaBitmap);
+ OpenGLTexture& rTexture(rBitmap.GetTexture());
+ OpenGLTexture& rAlphaTexture(rAlpha.GetTexture());
+
+ VCL_GL_INFO( "::drawAlphaBitmap" );
+ PreDraw();
+
+ if (rPosAry.mnSrcWidth != rPosAry.mnDestWidth ||
+ rPosAry.mnSrcHeight != rPosAry.mnDestHeight)
+ {
+ basegfx::B2DPoint aNull(rPosAry.mnDestX,rPosAry.mnDestY);
+ basegfx::B2DPoint aX(rPosAry.mnDestX + rPosAry.mnDestWidth, rPosAry.mnDestY);
+ basegfx::B2DPoint aY(rPosAry.mnDestX, rPosAry.mnDestY + rPosAry.mnDestHeight);
+ DrawTransformedTexture(rTexture, rAlphaTexture, aNull, aX, aY);
+ }
+ else
+ {
+ DrawTextureWithMask( rTexture, rAlphaTexture, rPosAry );
+ }
+
+ PostDraw();
+ return true;
+}
+
+/** draw transformed bitmap (maybe with alpha) where Null, X, Y define the coordinate system */
+bool OpenGLSalGraphicsImpl::drawTransformedBitmap(
+ const basegfx::B2DPoint& rNull,
+ const basegfx::B2DPoint& rX,
+ const basegfx::B2DPoint& rY,
+ const SalBitmap& rSrcBitmap,
+ const SalBitmap* pAlphaBitmap)
+{
+ assert(dynamic_cast<const OpenGLSalBitmap*>(&rSrcBitmap));
+ assert(!pAlphaBitmap || dynamic_cast<const OpenGLSalBitmap*>(pAlphaBitmap));
+
+ OpenGLZone aZone;
+
+ const OpenGLSalBitmap& rBitmap = static_cast<const OpenGLSalBitmap&>(rSrcBitmap);
+ const OpenGLSalBitmap* pMaskBitmap = static_cast<const OpenGLSalBitmap*>(pAlphaBitmap);
+ OpenGLTexture& rTexture( rBitmap.GetTexture() );
+ OpenGLTexture aMask; // no texture
+
+ if( pMaskBitmap != nullptr )
+ aMask = pMaskBitmap->GetTexture();
+
+ VCL_GL_INFO( "::drawTransformedBitmap" );
+ PreDraw();
+ DrawTransformedTexture( rTexture, aMask, rNull, rX, rY );
+ PostDraw();
+
+ return true;
+}
+
+/** Render solid rectangle with given transparency
+
+ @param nTransparency
+ Transparency value (0-255) to use. 0 blits and opaque, 255 a
+ fully transparent rectangle
+ */
+bool OpenGLSalGraphicsImpl::drawAlphaRect(
+ long nX, long nY,
+ long nWidth, long nHeight,
+ sal_uInt8 nTransparency )
+{
+ VCL_GL_INFO("::drawAlphaRect (" << nX << ", " << nY << ") [" << nWidth << ", " << nHeight << "]");
+ mpRenderList->addDrawRectangle(nX, nY, nWidth, nHeight, nTransparency / 100.0, mnLineColor, mnFillColor);
+ PostBatchDraw();
+ return true;
+}
+
+bool OpenGLSalGraphicsImpl::drawGradient(const tools::PolyPolygon& rPolyPoly,
+ const Gradient& rGradient)
+{
+ tools::Rectangle aBoundRect( rPolyPoly.GetBoundRect() );
+
+ VCL_GL_INFO("::drawGradient " << rPolyPoly.GetBoundRect());
+
+ if (aBoundRect.IsEmpty())
+ {
+ VCL_GL_INFO("::drawGradient nothing to draw");
+ return true;
+ }
+
+ if (rGradient.GetStyle() != GradientStyle::Linear &&
+ rGradient.GetStyle() != GradientStyle::Axial &&
+ rGradient.GetStyle() != GradientStyle::Radial )
+ {
+ VCL_GL_INFO("::drawGradient unsupported gradient type");
+ return false;
+ }
+
+ aBoundRect.AdjustLeft( -1 );
+ aBoundRect.AdjustTop( -1 );
+ aBoundRect.AdjustRight( 1 );
+ aBoundRect.AdjustBottom( 1 );
+
+ PreDraw( XOROption::IMPLEMENT_XOR );
+
+#define FIXME_BROKEN_STENCIL_FOR_GRADIENTS 0
+#if FIXME_BROKEN_STENCIL_FOR_GRADIENTS
+ ImplSetClipBit( vcl::Region( rPolyPoly ), 0x02 );
+ if( mbUseStencil )
+ {
+ mpContext->state().stencil().enable();
+ CHECK_GL_ERROR();
+ glStencilFunc( GL_EQUAL, 3, 0xFF );
+ CHECK_GL_ERROR();
+ }
+ else
+ {
+ mpContext->state().stencil().enable();
+ CHECK_GL_ERROR();
+ glStencilFunc( GL_EQUAL, 2, 0xFF );
+ CHECK_GL_ERROR();
+ }
+#endif
+
+ // if border >= 100%, draw solid rectangle with start color
+ if (rGradient.GetBorder() >= 100.0)
+ {
+ VCL_GL_INFO("::drawGradient -> DrawRect (no gradient)");
+
+ Color aColor = rGradient.GetStartColor();
+ long nIntensity = rGradient.GetStartIntensity();
+ if (UseSolid(Color(aColor.GetRed() * nIntensity / 100.0,
+ aColor.GetGreen()* nIntensity / 100.0,
+ aColor.GetBlue() * nIntensity / 100.0)))
+ {
+ DrawRect(aBoundRect);
+ }
+ }
+ else if (rGradient.GetStyle() == GradientStyle::Linear)
+ {
+ VCL_GL_INFO("::drawGradient -> DrawLinearGradient");
+ DrawLinearGradient(rGradient, aBoundRect);
+ }
+ else if (rGradient.GetStyle() == GradientStyle::Axial)
+ {
+ VCL_GL_INFO("::drawGradient -> DrawAxialGradient");
+ DrawAxialGradient(rGradient, aBoundRect);
+ }
+ else if (rGradient.GetStyle() == GradientStyle::Radial)
+ {
+ VCL_GL_INFO("::drawGradient -> DrawRadialGradient");
+ DrawRadialGradient(rGradient, aBoundRect);
+ }
+
+#if FIXME_BROKEN_STENCIL_FOR_GRADIENTS
+ if( !mbUseStencil )
+ {
+ mpContext->state().stencil().disable();
+ CHECK_GL_ERROR();
+ }
+#endif
+ PostDraw();
+
+ return true;
+}
+
+void OpenGLSalGraphicsImpl::flush()
+{
+ FlushDeferredDrawing();
+
+ if( IsOffscreen() )
+ return;
+
+ if( !Application::IsInExecute() )
+ {
+ // otherwise nothing would trigger idle rendering
+ doFlush();
+ }
+ else if( !mpFlush->IsActive() )
+ mpFlush->Start();
+}
+
+void OpenGLSalGraphicsImpl::doFlush()
+{
+ FlushDeferredDrawing();
+
+ if (OpenGLContext::hasCurrent())
+ {
+ mpContext->state().scissor().disable();
+ mpContext->state().stencil().disable();
+ }
+
+ if( IsOffscreen() )
+ return;
+
+ if( !maOffscreenTex )
+ {
+ VCL_GL_INFO( "doFlush - odd no texture !" );
+ return;
+ }
+
+ if( mnDrawCountAtFlush == mnDrawCount )
+ {
+ VCL_GL_INFO( "eliding redundant doFlush, no drawing since last!" );
+ return;
+ }
+
+ mnDrawCountAtFlush = mnDrawCount;
+
+ OpenGLZone aZone;
+
+ VCL_GL_INFO( "doFlush" );
+
+ if( !mpWindowContext.is() )
+ {
+ // ensure everything is released from the old context.
+ OpenGLContext::clearCurrent();
+ mpWindowContext = CreateWinContext();
+ VCL_GL_INFO( "late creation of window context" );
+ }
+
+ assert( mpWindowContext.is() );
+
+ if( !mpWindowContext.is() )
+ {
+ // failed to create a GL context for this window:
+ // eg. mis-matching pixel formats, underlying window
+ // resource lifecycle, etc.
+ VCL_GL_INFO( "Failed to create window context" );
+ return;
+ }
+
+ // Interesting ! -> this destroys a context [ somehow ] ...
+ mpWindowContext->makeCurrent();
+ CHECK_GL_ERROR();
+
+ VCL_GL_INFO( "doFlush - acquire default framebuffer" );
+
+ mpWindowContext->AcquireDefaultFramebuffer();
+
+ CHECK_GL_ERROR();
+
+ mpWindowContext->state().sync();
+ mpWindowContext->state().viewport(tools::Rectangle(Point(0, 0), Size(GetWidth(), GetHeight())));
+ mpWindowContext->state().scissor().disable();
+ mpWindowContext->state().stencil().disable();
+
+#if OSL_DEBUG_LEVEL > 0 // random background glClear
+ glClearColor(static_cast<float>(double(rand())/RAND_MAX),
+ static_cast<float>(double(rand())/RAND_MAX),
+ static_cast<float>(double(rand())/RAND_MAX), 1.0);
+ glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT );
+ CHECK_GL_ERROR();
+#endif
+
+ VCL_GL_INFO( "Texture height " << maOffscreenTex.GetHeight() << " vs. window height " << GetHeight() );
+
+ OpenGLFramebuffer* pFrameBuffer = mpWindowContext->AcquireFramebuffer(maOffscreenTex);
+ CHECK_GL_ERROR();
+ if (pFrameBuffer)
+ {
+ OpenGLFramebuffer::Unbind(GL_DRAW_FRAMEBUFFER);
+ pFrameBuffer->Bind(GL_READ_FRAMEBUFFER);
+
+ glBlitFramebuffer(0, 0, GetWidth(), GetHeight(),
+ 0, 0, GetWidth(), GetHeight(), GL_COLOR_BUFFER_BIT, GL_NEAREST);
+ CHECK_GL_ERROR();
+
+ pFrameBuffer->Bind();
+ }
+
+ static bool bNoSwap = getenv("SAL_GL_NO_SWAP");
+ if (!bNoSwap)
+ mpWindowContext->swapBuffers();
+
+ VCL_GL_INFO( "doFlush - end." );
+}
+
+bool OpenGLSalGraphicsImpl::supportsOperation(OutDevSupportType eType) const
+{
+ switch (eType)
+ {
+ case OutDevSupportType::B2DDraw:
+ case OutDevSupportType::TransparentRect:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/opengl_blacklist_windows.xml b/vcl/opengl/opengl_blacklist_windows.xml
new file mode 100644
index 000000000..71e562fa9
--- /dev/null
+++ b/vcl/opengl/opengl_blacklist_windows.xml
@@ -0,0 +1,67 @@
+<?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>
+ <whitelist>
+ </whitelist>
+ <blacklist>
+ <entry os="all" vendor="intel" compare="less" version="10.18.14.4264">
+ <device id="all"/>
+ </entry>
+ <!-- tdf#99919 -->
+ <entry os="all" vendor="intel" compare="equal" version="20.19.15.4352">
+ <device id="0x1927"/>
+ </entry>
+ <!-- tdf#100243 -->
+ <!-- tdf#117477 -->
+ <entry os="all" vendor="intel" compare="equal" version="21.20.16.4664">
+ <device id="0x591b"/>
+ <device id="0x5916"/>
+ </entry>
+ <!-- tdf#115092 -->
+ <entry os="all" vendor="intel" compare="equal" version="22.20.16.4735">
+ <device id="0x1912"/>
+ </entry>
+ <entry os="7" vendor="intel">
+ <device id="all"/>
+ </entry>
+ <entry os="8" vendor="intel" compare="equal" version="10.18.10.3308"><!-- Intel(R) HD Graphics 4000 -->
+ <device id="0x0166"/>
+ </entry>
+ <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>
+ <!-- tdf#131221 -->
+ <entry os="10" vendor="intel">
+ <device id="0x5917"/>
+ </entry>
+ <entry os="all" vendor="amd" compare="less" version="15.200.1062.1004"> <!-- 150.200 -->
+ <device id="all"/>
+ </entry>
+ <entry os="all" vendor="nvidia" compare="less" version="10.18.13.5362"> <!-- 353.62 -->
+ <device id="all"/>
+ </entry>
+ <entry os="10" vendor="nvidia"> <!-- tdf#128441 -->
+ <device id="0x2182"/>
+ </entry>
+ <entry os="all" vendor="microsoft" compare="less" version="6.2.0.0"> <!-- 6.2.0.0 -->
+ <device id="all"/>
+ </entry>
+ </blacklist>
+</root>
diff --git a/vcl/opengl/program.cxx b/vcl/opengl/program.cxx
new file mode 100644
index 000000000..6557eccf8
--- /dev/null
+++ b/vcl/opengl/program.cxx
@@ -0,0 +1,390 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/program.hxx>
+#include <opengl/RenderState.hxx>
+
+#include <vcl/opengl/OpenGLHelper.hxx>
+#include <vcl/opengl/OpenGLContext.hxx>
+
+#include <glm/glm.hpp>
+#include <glm/gtc/type_ptr.hpp>
+#include <glm/gtc/matrix_transform.hpp>
+
+OpenGLProgram::OpenGLProgram() :
+ mnId( 0 ),
+ mnEnabledAttribs( 0 ),
+ mnPositionAttrib( SAL_MAX_UINT32 ),
+ mnTexCoordAttrib( SAL_MAX_UINT32 ),
+ mnAlphaCoordAttrib( SAL_MAX_UINT32 ),
+ mnMaskCoordAttrib( SAL_MAX_UINT32 ),
+ mnExtrusionVectorsAttrib( SAL_MAX_UINT32 ),
+ mnVertexColorsAttrib( SAL_MAX_UINT32 ),
+ mbBlending(false),
+ mfLastWidth(0.0),
+ mfLastHeight(0.0),
+ mfLastPixelOffset(0.0)
+{
+}
+
+OpenGLProgram::~OpenGLProgram()
+{
+ maUniformLocations.clear();
+ if( mnId != 0 )
+ {
+ glDeleteProgram( mnId );
+ CHECK_GL_ERROR();
+ }
+}
+
+bool OpenGLProgram::Load( const OUString& rVertexShader,
+ const OUString& rFragmentShader,
+ const OString& preamble,
+ const OString& rDigest )
+{
+ mnId = OpenGLHelper::LoadShaders( rVertexShader, rFragmentShader, preamble, rDigest );
+ return ( mnId != 0 );
+}
+
+void OpenGLProgram::Reuse()
+{
+ mbBlending = false;
+}
+
+void OpenGLProgram::Use()
+{
+ if (!mnId)
+ return;
+
+ glUseProgram(mnId);
+ CHECK_GL_ERROR();
+ Reuse();
+}
+
+void OpenGLProgram::Clean()
+{
+ // unbind all textures
+ for (OpenGLTexture& rTexture : maTextures)
+ {
+ rTexture.Unbind();
+ }
+ maTextures.clear();
+
+ // disable any enabled vertex attrib array
+ if( mnEnabledAttribs )
+ {
+ for( int i = 0; i < 32; i++ )
+ {
+ if( mnEnabledAttribs & ( 1 << i ) )
+ {
+ glDisableVertexAttribArray( i );
+ CHECK_GL_ERROR();
+ }
+ }
+ mnEnabledAttribs = 0;
+ }
+}
+
+bool OpenGLProgram::EnableVertexAttrib(GLuint& rAttrib, const OString& rName)
+{
+ if( rAttrib == SAL_MAX_UINT32 )
+ {
+ GLint aLocation = glGetAttribLocation(mnId, rName.getStr());
+ CHECK_GL_ERROR();
+ if (aLocation < 0)
+ return false;
+ rAttrib = GLuint(aLocation);
+ }
+ if( (mnEnabledAttribs & ( 1 << rAttrib )) == 0 )
+ {
+ glEnableVertexAttribArray( rAttrib );
+ CHECK_GL_ERROR();
+ mnEnabledAttribs |= ( 1 << rAttrib );
+ }
+ return true;
+}
+
+void OpenGLProgram::SetVertexAttrib(GLuint& rAttrib, const OString& rName, GLint nSize,
+ GLenum eType, GLboolean bNormalized, GLsizei aStride,
+ const GLvoid* pPointer)
+{
+ if (EnableVertexAttrib(rAttrib, rName))
+ {
+ glVertexAttribPointer(rAttrib, nSize, eType, bNormalized, aStride, pPointer);
+ CHECK_GL_ERROR();
+ }
+ else
+ {
+ VCL_GL_INFO("Vertex attribute '" << rName << "' doesn't exist in this program (" << mnId << ")");
+ }
+}
+
+void OpenGLProgram::SetVertices( const GLvoid* pData )
+{
+ SetVertexAttrib(mnPositionAttrib, "position", 2, GL_FLOAT, GL_FALSE, 0, pData);
+}
+
+void OpenGLProgram::SetTextureCoord( const GLvoid* pData )
+{
+ SetVertexAttrib(mnTexCoordAttrib, "tex_coord_in", 2, GL_FLOAT, GL_FALSE, 0, pData);
+}
+
+void OpenGLProgram::SetAlphaCoord( const GLvoid* pData )
+{
+ SetVertexAttrib(mnAlphaCoordAttrib, "alpha_coord_in", 2, GL_FLOAT, GL_FALSE, 0, pData);
+}
+
+void OpenGLProgram::SetMaskCoord(const GLvoid* pData)
+{
+ SetVertexAttrib(mnMaskCoordAttrib, "mask_coord_in", 2, GL_FLOAT, GL_FALSE, 0, pData);
+}
+
+void OpenGLProgram::SetExtrusionVectors(const GLvoid* pData)
+{
+ SetVertexAttrib(mnExtrusionVectorsAttrib, "extrusion_vectors", 3, GL_FLOAT, GL_FALSE, 0, pData);
+}
+
+void OpenGLProgram::SetVertexColors(std::vector<GLubyte>& rColorVector)
+{
+ SetVertexAttrib(mnVertexColorsAttrib, "vertex_color_in", 4, GL_UNSIGNED_BYTE, GL_FALSE, 0, rColorVector.data());
+}
+
+void OpenGLProgram::SetShaderType(TextureShaderType eTextureShaderType)
+{
+ SetUniform1i("type", GLint(eTextureShaderType));
+}
+
+void OpenGLProgram::SetShaderType(DrawShaderType eDrawShaderType)
+{
+ SetUniform1i("type", GLint(eDrawShaderType));
+}
+
+GLuint OpenGLProgram::GetUniformLocation( const OString& rName )
+{
+ auto it = maUniformLocations.find( rName );
+ if( it == maUniformLocations.end() )
+ {
+ GLuint nLocation = glGetUniformLocation( mnId, rName.getStr() );
+ CHECK_GL_ERROR();
+ maUniformLocations[rName] = nLocation;
+ return nLocation;
+ }
+
+ return it->second;
+}
+
+void OpenGLProgram::DrawArrays(GLenum aMode, std::vector<GLfloat>& aVertices)
+{
+ if (!mbBlending)
+ OpenGLContext::getVCLContext()->state().blend().disable();
+
+ SetVertices(aVertices.data());
+ glDrawArrays(aMode, 0, aVertices.size() / 2);
+}
+
+void OpenGLProgram::DrawElements(GLenum aMode, GLuint nNumberOfVertices)
+{
+ if (!mbBlending)
+ OpenGLContext::getVCLContext()->state().blend().disable();
+
+ glDrawElements(aMode, nNumberOfVertices, GL_UNSIGNED_INT, nullptr);
+}
+
+void OpenGLProgram::SetUniform1f( const OString& rName, GLfloat v1 )
+{
+ GLuint nUniform = GetUniformLocation( rName );
+ glUniform1f( nUniform, v1 );
+ CHECK_GL_ERROR();
+}
+
+void OpenGLProgram::SetUniform2f( const OString& rName, GLfloat v1, GLfloat v2 )
+{
+ GLuint nUniform = GetUniformLocation( rName );
+ glUniform2f( nUniform, v1, v2 );
+ CHECK_GL_ERROR();
+}
+
+void OpenGLProgram::SetUniform1fv( const OString& rName, GLsizei nCount, GLfloat const * aValues )
+{
+ GLuint nUniform = GetUniformLocation( rName );
+ glUniform1fv( nUniform, nCount, aValues );
+ CHECK_GL_ERROR();
+}
+
+void OpenGLProgram::SetUniform2fv( const OString& rName, GLsizei nCount, GLfloat const * aValues )
+{
+ GLuint nUniform = GetUniformLocation( rName );
+ glUniform2fv( nUniform, nCount, aValues );
+ CHECK_GL_ERROR();
+}
+
+void OpenGLProgram::SetUniform1i( const OString& rName, GLint v1 )
+{
+ GLuint nUniform = GetUniformLocation( rName );
+ glUniform1i( nUniform, v1 );
+ CHECK_GL_ERROR();
+}
+
+void OpenGLProgram::SetColor( const OString& rName, Color nColor, sal_uInt8 nTransparency )
+{
+ GLuint nUniform = GetUniformLocation( rName );
+ glUniform4f( nUniform,
+ nColor.GetRed() / 255.0f,
+ nColor.GetGreen() / 255.0f,
+ nColor.GetBlue() / 255.0f,
+ (100 - nTransparency) * (1.0 / 100) );
+ CHECK_GL_ERROR();
+
+ if( nTransparency > 0 )
+ SetBlendMode( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
+}
+
+void OpenGLProgram::SetColorf( const OString& rName, Color nColor, double fTransparency )
+{
+ GLuint nUniform = GetUniformLocation( rName );
+ glUniform4f( nUniform,
+ nColor.GetRed() / 255.0f,
+ nColor.GetGreen() / 255.0f,
+ nColor.GetBlue() / 255.0f,
+ (1.0f - fTransparency) );
+ CHECK_GL_ERROR();
+
+ if( fTransparency > 0.0 )
+ SetBlendMode( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
+}
+
+void OpenGLProgram::SetColor( const OString& rName, const Color& rColor )
+{
+ GLuint nUniform = GetUniformLocation( rName );
+ glUniform4f( nUniform,
+ static_cast<float>(rColor.GetRed()) / 255,
+ static_cast<float>(rColor.GetGreen()) / 255,
+ static_cast<float>(rColor.GetBlue()) / 255,
+ 1.0f - static_cast<float>(rColor.GetTransparency()) / 255 );
+ CHECK_GL_ERROR();
+
+ if( rColor.GetTransparency() > 0 )
+ SetBlendMode( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
+}
+
+void OpenGLProgram::SetColorWithIntensity( const OString& rName, const Color& rColor, long nFactor )
+{
+ GLuint nUniform = GetUniformLocation( rName );
+ glUniform4f( nUniform,
+ static_cast<float>(rColor.GetRed()) * nFactor / 25500.0,
+ static_cast<float>(rColor.GetGreen()) * nFactor / 25500.0,
+ static_cast<float>(rColor.GetBlue()) * nFactor / 25500.0,
+ 1.0f );
+ CHECK_GL_ERROR();
+}
+
+void OpenGLProgram::SetTexture( const OString& rName, OpenGLTexture& rTexture )
+{
+ GLuint nUniform = GetUniformLocation( rName );
+ int nIndex = maTextures.size();
+
+ glUniform1i( nUniform, nIndex );
+ CHECK_GL_ERROR();
+
+ OpenGLContext::getVCLContext()->state().texture().active(nIndex);
+
+ rTexture.Bind();
+ maTextures.push_back(rTexture);
+}
+
+void OpenGLProgram::SetTransform(
+ const OString& rName,
+ const OpenGLTexture& rTexture,
+ const basegfx::B2DPoint& rNull,
+ const basegfx::B2DPoint& rX,
+ const basegfx::B2DPoint& rY )
+{
+ auto nTexWidth = rTexture.GetWidth();
+ auto nTexHeight = rTexture.GetHeight();
+ if (nTexWidth == 0 || nTexHeight == 0)
+ return;
+
+ GLuint nUniform = GetUniformLocation( rName );
+ const basegfx::B2DVector aXRel = rX - rNull;
+ const basegfx::B2DVector aYRel = rY - rNull;
+ const float aValues[] = {
+ static_cast<float>(aXRel.getX())/nTexWidth, static_cast<float>(aXRel.getY())/nTexWidth, 0, 0,
+ static_cast<float>(aYRel.getX())/nTexHeight, static_cast<float>(aYRel.getY())/nTexHeight, 0, 0,
+ 0, 0, 1, 0,
+ static_cast<float>(rNull.getX()), static_cast<float>(rNull.getY()), 0, 1 };
+ glm::mat4 aMatrix = glm::make_mat4( aValues );
+ glUniformMatrix4fv( nUniform, 1, GL_FALSE, glm::value_ptr( aMatrix ) );
+ CHECK_GL_ERROR();
+}
+
+void OpenGLProgram::SetIdentityTransform(const OString& rName)
+{
+ GLuint nUniform = GetUniformLocation(rName);
+ glm::mat4 aMatrix {};
+ glUniformMatrix4fv(nUniform, 1, GL_FALSE, glm::value_ptr( aMatrix ) );
+ CHECK_GL_ERROR();
+}
+
+void OpenGLProgram::ApplyMatrix(float fWidth, float fHeight, float fPixelOffset)
+{
+
+ if (mfLastWidth == fWidth && mfLastHeight == fHeight && mfLastPixelOffset == fPixelOffset)
+ return;
+
+ mfLastWidth = fWidth;
+ mfLastHeight = fHeight;
+ mfLastPixelOffset = fPixelOffset;
+
+ GLuint nUniform = GetUniformLocation("mvp");
+
+ glm::mat4 aMVP = glm::ortho(0.0f, fWidth, fHeight, 0.0f, 0.0f, 1.0f);
+
+ if (fPixelOffset != 0.0f)
+ aMVP = glm::translate(aMVP, glm::vec3(fPixelOffset, fPixelOffset, 0.0f));
+
+ glUniformMatrix4fv(nUniform, 1, GL_FALSE, glm::value_ptr(aMVP));
+ CHECK_GL_ERROR();
+}
+
+void OpenGLProgram::SetBlendMode(GLenum nSFactor, GLenum nDFactor)
+{
+ OpenGLContext::getVCLContext()->state().blend().enable();
+ OpenGLContext::getVCLContext()->state().blend().func(nSFactor, nDFactor);
+ mbBlending = true;
+}
+
+void OpenGLProgram::DrawTexture( const OpenGLTexture& rTexture )
+{
+ if (!rTexture)
+ return;
+
+ float fWidth = rTexture.GetWidth();
+ float fHeight = rTexture.GetHeight();
+
+ float fMinX = 0.0f;
+ float fMaxX = fWidth;
+ float fMinY = 0.0f;
+ float fMaxY = fHeight;
+
+ std::vector<GLfloat> aPosition {
+ fMinX, fMaxY,
+ fMinX, fMinY,
+ fMaxX, fMinY,
+ fMaxX, fMaxY
+ };
+ GLfloat aTexCoord[8];
+
+ rTexture.GetWholeCoord( aTexCoord );
+ SetTextureCoord( aTexCoord );
+ ApplyMatrix(fWidth, fHeight);
+ DrawArrays(GL_TRIANGLE_FAN, aPosition);
+ CHECK_GL_ERROR();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/salbmp.cxx b/vcl/opengl/salbmp.cxx
new file mode 100644
index 000000000..e9b1bca73
--- /dev/null
+++ b/vcl/opengl/salbmp.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 <memory>
+#include <sal/config.h>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+#include <tools/debug.hxx>
+
+#include <vcl/opengl/OpenGLHelper.hxx>
+
+#include <vcl/bitmap.hxx>
+#include <vcl/checksum.hxx>
+#include <vcl/outdev.hxx>
+#include <svdata.hxx>
+#include <salgdi.hxx>
+#include <vcleventlisteners.hxx>
+#include <vcl/lazydelete.hxx>
+#include <scanlinewriter.hxx>
+
+#include <o3tl/make_shared.hxx>
+
+#include <opengl/zone.hxx>
+#include <opengl/program.hxx>
+#include <opengl/salbmp.hxx>
+#include <opengl/RenderState.hxx>
+#include <opengl/FixedTextureAtlas.hxx>
+
+#if OSL_DEBUG_LEVEL > 0
+# define CANARY "tex-canary"
+#endif
+
+namespace
+{
+
+bool determineTextureFormat(sal_uInt16 nBits, GLenum& nFormat, GLenum& nType)
+{
+ switch(nBits)
+ {
+ case 8:
+ nFormat = GL_LUMINANCE;
+ nType = GL_UNSIGNED_BYTE;
+ return true;
+ case 24:
+ nFormat = GL_RGB;
+ nType = GL_UNSIGNED_BYTE;
+ return true;
+ case 32:
+ nFormat = GL_RGBA;
+ nType = GL_UNSIGNED_BYTE;
+ return true;
+ default:
+ break;
+ }
+ SAL_WARN("vcl.opengl", "Could not determine the appropriate texture format for input bits '" << nBits << "'");
+ return false;
+}
+
+bool isValidBitCount( sal_uInt16 nBitCount )
+{
+ return (nBitCount == 1) || (nBitCount == 4) || (nBitCount == 8) || (nBitCount == 24) || (nBitCount == 32);
+}
+
+sal_uInt32 lclBytesPerRow(sal_uInt16 nBits, int nWidth)
+{
+ switch(nBits)
+ {
+ case 1: return (nWidth + 7) >> 3;
+ case 4: return (nWidth + 1) >> 1;
+ case 8: return nWidth;
+ case 24: return nWidth * 3;
+ case 32: return nWidth * 4;
+ default:
+ OSL_FAIL("vcl::OpenGLSalBitmap::AllocateUserData(), illegal bitcount!");
+ }
+ return 0;
+}
+}
+
+OpenGLSalBitmap::OpenGLSalBitmap()
+: mbDirtyTexture(true)
+, mnBits(0)
+, mnBytesPerRow(0)
+, mnWidth(0)
+, mnHeight(0)
+{
+}
+
+OpenGLSalBitmap::~OpenGLSalBitmap()
+{
+ Destroy();
+ VCL_GL_INFO( "~OpenGLSalBitmap" );
+}
+
+void OpenGLSalBitmap::Create( const OpenGLTexture& rTex, long nX, long nY, long nWidth, long nHeight )
+{
+ DBG_TESTSOLARMUTEX();
+ static const BitmapPalette aEmptyPalette;
+ OpenGLVCLContextZone aContextZone;
+
+ Destroy();
+ VCL_GL_INFO( "OpenGLSalBitmap::Create from FBO: ["
+ << nX << ", " << nY << "] " << nWidth << "x" << nHeight );
+
+ GLint nMaxTextureSize;
+ glGetIntegerv( GL_MAX_TEXTURE_SIZE, &nMaxTextureSize );
+ if ( nWidth > nMaxTextureSize )
+ {
+ nWidth = nMaxTextureSize;
+ VCL_GL_INFO( "Width limited to " << nMaxTextureSize );
+ }
+
+ if ( nHeight > nMaxTextureSize )
+ {
+ nHeight = nMaxTextureSize;
+ VCL_GL_INFO( "Height limited to " << nMaxTextureSize );
+ }
+
+ mnWidth = nWidth;
+ mnHeight = nHeight;
+
+ // TODO Check the framebuffer configuration
+ mnBits = 32;
+ maPalette = aEmptyPalette;
+
+ if( rTex )
+ maTexture = OpenGLTexture( rTex, nX, nY, nWidth, nHeight );
+ else
+ maTexture = OpenGLTexture( nX, nY, nWidth, nHeight );
+ mbDirtyTexture = false;
+ VCL_GL_INFO( "Created texture " << maTexture.Id() );
+
+ assert(mnWidth == maTexture.GetWidth() &&
+ mnHeight == maTexture.GetHeight());
+}
+
+bool OpenGLSalBitmap::Create( const Size& rSize, sal_uInt16 nBits, const BitmapPalette& rBitmapPalette )
+{
+ DBG_TESTSOLARMUTEX();
+ OpenGLVCLContextZone aContextZone;
+
+ Destroy();
+ VCL_GL_INFO( "OpenGLSalBitmap::Create with size: " << rSize );
+
+ if( !isValidBitCount( nBits ) )
+ return false;
+ maPalette = rBitmapPalette;
+ mnBits = nBits;
+ mnWidth = rSize.Width();
+ mnHeight = rSize.Height();
+
+ // Limit size to what GL allows, so later glTexImage2D() won't fail.
+ GLint nMaxTextureSize;
+ glGetIntegerv(GL_MAX_TEXTURE_SIZE, &nMaxTextureSize);
+ if (mnWidth > nMaxTextureSize)
+ mnWidth = nMaxTextureSize;
+ if (mnHeight > nMaxTextureSize)
+ mnHeight = nMaxTextureSize;
+
+ return false;
+}
+
+bool OpenGLSalBitmap::Create( const SalBitmap& rSalBmp )
+{
+ DBG_TESTSOLARMUTEX();
+ return Create( rSalBmp, rSalBmp.GetBitCount() );
+}
+
+bool OpenGLSalBitmap::Create( const SalBitmap& rSalBmp, SalGraphics* pGraphics )
+{
+ DBG_TESTSOLARMUTEX();
+ return Create( rSalBmp, pGraphics ? pGraphics->GetBitCount() : rSalBmp.GetBitCount() );
+}
+
+bool OpenGLSalBitmap::Create( const SalBitmap& rSalBmp, sal_uInt16 nNewBitCount )
+{
+ DBG_TESTSOLARMUTEX();
+ OpenGLZone aZone;
+
+ // check that carefully only in the debug mode
+ assert(dynamic_cast<const OpenGLSalBitmap*>(&rSalBmp));
+
+ const OpenGLSalBitmap& rSourceBitmap = static_cast<const OpenGLSalBitmap&>(rSalBmp);
+
+ VCL_GL_INFO("OpenGLSalBitmap::Create from BMP: "
+ << rSourceBitmap.mnWidth << "x" << rSourceBitmap.mnHeight
+ << " Bits old: " << mnBits << " new:" << nNewBitCount );
+
+ if( isValidBitCount( nNewBitCount ) )
+ {
+ // TODO: lfrb: What about the pending operations?!
+ mnBits = nNewBitCount;
+ mnBytesPerRow = rSourceBitmap.mnBytesPerRow;
+ mnWidth = rSourceBitmap.mnWidth;
+ mnHeight = rSourceBitmap.mnHeight;
+ maPalette = rSourceBitmap.maPalette;
+ // execute any pending operations on the source bitmap
+ maTexture = rSourceBitmap.GetTexture();
+ mbDirtyTexture = false;
+
+ // be careful here, we are share & reference-count the
+ // mpUserBuffer, BUT this Create() is called from
+ // Bitmap::ImplMakeUnique().
+ // Consequently, there might be cases when this needs to be made
+ // unique later (when we don't do that right away here), like when
+ // using the BitmapWriteAccess.
+ mpUserBuffer = rSourceBitmap.mpUserBuffer;
+
+ return true;
+ }
+ return false;
+}
+
+bool OpenGLSalBitmap::Create( const css::uno::Reference< css::rendering::XBitmapCanvas >& /*xBitmapCanvas*/, Size& /*rSize*/, bool /*bMask*/ )
+{
+ DBG_TESTSOLARMUTEX();
+ // TODO Is this method needed?
+ return false;
+}
+
+OpenGLTexture& OpenGLSalBitmap::GetTexture() const
+{
+ OpenGLSalBitmap* pThis = const_cast<OpenGLSalBitmap*>(this);
+ if( !maTexture || mbDirtyTexture )
+ pThis->CreateTexture();
+ VCL_GL_INFO( "Got texture " << maTexture.Id() );
+ return pThis->maTexture;
+}
+
+void OpenGLSalBitmap::Destroy()
+{
+ OpenGLZone aZone;
+
+ VCL_GL_INFO("Destroy OpenGLSalBitmap texture:" << maTexture.Id());
+ maTexture = OpenGLTexture();
+ DeallocateUserData();
+}
+
+bool OpenGLSalBitmap::AllocateUserData()
+{
+ VCL_GL_INFO( "OpenGLSalBitmap::AllocateUserData" );
+
+ if( mnWidth && mnHeight )
+ {
+ mnBytesPerRow = lclBytesPerRow(mnBits, mnWidth);
+ }
+
+ bool alloc = false;
+ if (mnBytesPerRow != 0 && mnHeight &&
+ mnBytesPerRow <= std::numeric_limits<sal_uInt32>::max() / mnHeight)
+ {
+ try
+ {
+ size_t nToAllocate = mnBytesPerRow * mnHeight;
+#if OSL_DEBUG_LEVEL > 0
+ nToAllocate += sizeof(CANARY);
+#endif
+ mpUserBuffer = o3tl::make_shared_array<sal_uInt8>(nToAllocate);
+#if OSL_DEBUG_LEVEL > 0
+ memcpy(mpUserBuffer.get() + nToAllocate - sizeof(CANARY),
+ CANARY, sizeof(CANARY));
+#endif
+ alloc = true;
+ }
+ catch (const std::bad_alloc &) {}
+ }
+ if (!alloc)
+ {
+ SAL_WARN("vcl.opengl", "bad alloc " << mnBytesPerRow << "x" << mnHeight);
+ DeallocateUserData();
+ }
+#ifdef DBG_UTIL
+ else
+ {
+ for (size_t i = 0; i < size_t(mnBytesPerRow * mnHeight); i++)
+ mpUserBuffer.get()[i] = (i & 0xFF);
+ }
+#endif
+
+ return mpUserBuffer != nullptr;
+}
+
+void OpenGLSalBitmap::DeallocateUserData()
+{
+ mpUserBuffer.reset();
+ mnBytesPerRow = 0;
+}
+
+namespace {
+
+void lclInstantiateTexture(OpenGLTexture& rTexture, const int nWidth, const int nHeight,
+ const GLenum nFormat, const GLenum nType, sal_uInt8 const * pData)
+{
+ if (nWidth == nHeight)
+ {
+ typedef std::vector<std::unique_ptr<FixedTextureAtlasManager>> TextureAtlasVector;
+ static vcl::DeleteOnDeinit<TextureAtlasVector> aTextureAtlases([]() {
+ TextureAtlasVector* p = new TextureAtlasVector;
+ p->reserve(5);
+ p->push_back(std::make_unique<FixedTextureAtlasManager>(8, 8, 16));
+ p->push_back(std::make_unique<FixedTextureAtlasManager>(8, 8, 24));
+ p->push_back(std::make_unique<FixedTextureAtlasManager>(8, 8, 32));
+ p->push_back(std::make_unique<FixedTextureAtlasManager>(8, 8, 48));
+ p->push_back(std::make_unique<FixedTextureAtlasManager>(8, 8, 64));
+ return p;
+ }());
+ for (std::unique_ptr<FixedTextureAtlasManager>& pTextureAtlas : *aTextureAtlases.get())
+ {
+ if (nWidth == pTextureAtlas->GetSubtextureSize())
+ {
+ rTexture = pTextureAtlas->InsertBuffer(nWidth, nHeight, nFormat, nType, pData);
+ return;
+ }
+ }
+ }
+ rTexture = OpenGLTexture (nWidth, nHeight, nFormat, nType, pData);
+}
+
+} // end anonymous namespace
+
+Size OpenGLSalBitmap::GetSize() const
+{
+ return Size(mnWidth, mnHeight);
+}
+
+GLuint OpenGLSalBitmap::CreateTexture()
+{
+ VCL_GL_INFO( "::CreateTexture bits: " << mnBits);
+ GLenum nFormat = GL_RGBA;
+ GLenum nType = GL_UNSIGNED_BYTE;
+ sal_uInt8* pData( nullptr );
+ bool bAllocated( false );
+
+ if (mpUserBuffer != nullptr)
+ {
+ if( mnBits == 24 || mnBits == 32 )
+ {
+ // no conversion needed for truecolor
+ pData = mpUserBuffer.get();
+
+ determineTextureFormat(mnBits, nFormat, nType);
+ }
+ else if( mnBits == 8 && maPalette.IsGreyPalette8Bit() )
+ {
+ // no conversion needed for 8bit grayscale
+ pData = mpUserBuffer.get();
+ nFormat = GL_LUMINANCE;
+ nType = GL_UNSIGNED_BYTE;
+ }
+ else
+ {
+ VCL_GL_INFO( "::CreateTexture - convert from " << mnBits << " to 24 bits" );
+ // convert to 24 bits RGB using palette
+ determineTextureFormat(24, nFormat, nType);
+ pData = convertDataBitCount( mpUserBuffer.get(), mnWidth, mnHeight,
+ mnBits, mnBytesPerRow, maPalette,
+ nFormat == GL_BGR ? BitConvert::BGR : BitConvert::RGB ).release();
+ bAllocated = true;
+ }
+ }
+
+ OpenGLVCLContextZone aContextZone;
+
+ lclInstantiateTexture(maTexture, mnWidth, mnHeight, nFormat, nType, pData);
+
+ VCL_GL_INFO("Created texture " << maTexture.Id() << " bits: " << mnBits);
+
+ if( bAllocated )
+ delete[] pData;
+
+ mbDirtyTexture = false;
+
+ CHECK_GL_ERROR();
+ return maTexture.Id();
+}
+
+bool OpenGLSalBitmap::ReadTexture()
+{
+ sal_uInt8* pData = mpUserBuffer.get();
+
+ GLenum nFormat = GL_RGBA;
+ GLenum nType = GL_UNSIGNED_BYTE;
+
+ VCL_GL_INFO( "::ReadTexture " << mnWidth << "x" << mnHeight << " bits: " << mnBits);
+
+ if( pData == nullptr )
+ return false;
+
+ OpenGLVCLContextZone aContextZone;
+
+ rtl::Reference<OpenGLContext> xContext = OpenGLContext::getVCLContext();
+ xContext->state().scissor().disable();
+ xContext->state().stencil().disable();
+
+ if ((mnBits == 8 && maPalette.IsGreyPalette8Bit()) || mnBits == 24 || mnBits == 32)
+ {
+ determineTextureFormat(mnBits, nFormat, nType);
+
+#if OSL_DEBUG_LEVEL > 0
+ // help valgrind & drmemory rescue us - touch last and first bits.
+ pData[0] = 0;
+ pData[mnBits/8*mnWidth*mnHeight-1] = 0;
+ // if this fails we can read too much into pData
+ assert(mnWidth == maTexture.GetWidth() &&
+ mnHeight == maTexture.GetHeight());
+#endif
+
+ maTexture.Read(nFormat, nType, pData);
+
+#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG
+ // If we read over the end of pData we have a real hidden memory
+ // corruption problem !
+ size_t nCanary = mnBytesPerRow * mnHeight;
+ assert(!memcmp(pData + nCanary, CANARY, sizeof (CANARY)));
+#endif
+ return true;
+ }
+ else if (mnBits == 1 || mnBits == 4 || mnBits == 8)
+ { // convert buffers from 24-bit RGB to 1,4 or 8-bit buffer
+ std::vector<sal_uInt8> aBuffer(mnWidth * mnHeight * 3);
+
+ sal_uInt8* pBuffer = aBuffer.data();
+ determineTextureFormat(24, nFormat, nType);
+ maTexture.Read(nFormat, nType, pBuffer);
+ sal_uInt32 nSourceBytesPerRow = lclBytesPerRow(24, mnWidth);
+
+ std::unique_ptr<vcl::ScanlineWriter> pWriter = vcl::ScanlineWriter::Create(mnBits, maPalette);
+ for (int y = 0; y < mnHeight; ++y)
+ {
+ sal_uInt8* pSource = &pBuffer[y * nSourceBytesPerRow];
+ sal_uInt8* pDestination = &pData[y * mnBytesPerRow];
+
+ pWriter->nextLine(pDestination);
+
+ for (int x = 0; x < mnWidth; ++x)
+ {
+ // read source
+ sal_uInt8 nR = *pSource++;
+ sal_uInt8 nG = *pSource++;
+ sal_uInt8 nB = *pSource++;
+
+ pWriter->writeRGB(nR, nG, nB);
+ }
+ }
+ return true;
+ }
+
+ SAL_WARN("vcl.opengl", "::ReadTexture - tx:" << maTexture.Id() << " @ "
+ << mnWidth << "x" << mnHeight << "- unimplemented bit depth: "
+ << mnBits);
+ return false;
+}
+
+sal_uInt16 OpenGLSalBitmap::GetBitCount() const
+{
+ return mnBits;
+}
+
+bool OpenGLSalBitmap::calcChecksumGL(OpenGLTexture& rInputTexture, BitmapChecksum& rChecksum) const
+{
+ OUString FragShader("areaHashCRC64TFragmentShader");
+
+ rtl::Reference< OpenGLContext > xContext = OpenGLContext::getVCLContext();
+ xContext->state().scissor().disable();
+ xContext->state().stencil().disable();
+
+ static vcl::DeleteOnDeinit<OpenGLTexture> gCRCTableTexture(
+ new OpenGLTexture(512, 1, GL_RGBA, GL_UNSIGNED_BYTE,
+ vcl_get_crc64_table()));
+ OpenGLTexture &rCRCTableTexture = *gCRCTableTexture.get();
+
+ // First Pass
+
+ int nWidth = rInputTexture.GetWidth();
+ int nHeight = rInputTexture.GetHeight();
+
+ OpenGLProgram* pProgram = xContext->UseProgram("textureVertexShader", FragShader);
+ if (pProgram == nullptr)
+ return false;
+
+ int nNewWidth = ceil( nWidth / 4.0 );
+ int nNewHeight = ceil( nHeight / 4.0 );
+
+ OpenGLTexture aFirstPassTexture(nNewWidth, nNewHeight);
+ OpenGLFramebuffer* pFramebuffer = xContext->AcquireFramebuffer(aFirstPassTexture);
+
+ pProgram->SetUniform1f( "xstep", 1.0 / mnWidth );
+ pProgram->SetUniform1f( "ystep", 1.0 / mnHeight );
+
+ pProgram->SetTexture("crc_table", rCRCTableTexture);
+ pProgram->SetTexture("sampler", rInputTexture);
+ pProgram->DrawTexture(rInputTexture);
+ pProgram->Clean();
+
+ OpenGLContext::ReleaseFramebuffer(pFramebuffer);
+
+ CHECK_GL_ERROR();
+
+ // Second Pass
+
+ nWidth = aFirstPassTexture.GetWidth();
+ nHeight = aFirstPassTexture.GetHeight();
+
+ pProgram = xContext->UseProgram("textureVertexShader", FragShader);
+ if (pProgram == nullptr)
+ return false;
+
+ nNewWidth = ceil( nWidth / 4.0 );
+ nNewHeight = ceil( nHeight / 4.0 );
+
+ OpenGLTexture aSecondPassTexture(nNewWidth, nNewHeight);
+ pFramebuffer = xContext->AcquireFramebuffer(aSecondPassTexture);
+
+ pProgram->SetUniform1f( "xstep", 1.0 / mnWidth );
+ pProgram->SetUniform1f( "ystep", 1.0 / mnHeight );
+
+ pProgram->SetTexture("crc_table", rCRCTableTexture);
+ pProgram->SetTexture("sampler", aFirstPassTexture);
+ pProgram->DrawTexture(aFirstPassTexture);
+ pProgram->Clean();
+
+ OpenGLContext::ReleaseFramebuffer(pFramebuffer);
+
+ CHECK_GL_ERROR();
+
+ // Final CRC on CPU
+ OpenGLTexture& aFinalTexture = aSecondPassTexture;
+ std::vector<sal_uInt8> aBuf( aFinalTexture.GetWidth() * aFinalTexture.GetHeight() * 4 );
+ aFinalTexture.Read(GL_RGBA, GL_UNSIGNED_BYTE, aBuf.data());
+
+ BitmapChecksum nCrc = vcl_get_checksum(0, aBuf.data(), aBuf.size());
+
+ rChecksum = nCrc;
+ return true;
+}
+
+void OpenGLSalBitmap::updateChecksum() const
+{
+ if (mbChecksumValid)
+ return;
+
+ if( (mnWidth * mnHeight) < (1024*768) || mnWidth < 128 || mnHeight < 128 )
+ {
+ SalBitmap::updateChecksum();
+ return;
+ }
+
+ OpenGLSalBitmap* pThis = const_cast<OpenGLSalBitmap*>(this);
+
+ OpenGLVCLContextZone aContextZone;
+ OpenGLTexture& rInputTexture = GetTexture();
+ pThis->mbChecksumValid = calcChecksumGL(rInputTexture, pThis->mnChecksum);
+ if (!pThis->mbChecksumValid)
+ SalBitmap::updateChecksum();
+}
+
+BitmapBuffer* OpenGLSalBitmap::AcquireBuffer( BitmapAccessMode nMode )
+{
+ OpenGLVCLContextZone aContextZone;
+
+ if( nMode != BitmapAccessMode::Info )
+ {
+ if (!mpUserBuffer)
+ {
+ if( !AllocateUserData() )
+ return nullptr;
+ if( maTexture && !ReadTexture() )
+ {
+ DeallocateUserData();
+ return nullptr;
+ }
+ }
+ }
+
+ // mpUserBuffer must be unique when we are doing the write access
+ if (nMode == BitmapAccessMode::Write && mpUserBuffer && mpUserBuffer.use_count() > 1)
+ {
+ std::shared_ptr<sal_uInt8> aBuffer(mpUserBuffer);
+
+ mpUserBuffer.reset();
+ AllocateUserData();
+ memcpy(mpUserBuffer.get(), aBuffer.get(), mnBytesPerRow * mnHeight);
+ }
+
+ BitmapBuffer* pBuffer = new BitmapBuffer;
+ pBuffer->mnWidth = mnWidth;
+ pBuffer->mnHeight = mnHeight;
+ pBuffer->maPalette = maPalette;
+ pBuffer->mnScanlineSize = mnBytesPerRow;
+ pBuffer->mpBits = mpUserBuffer.get();
+ pBuffer->mnBitCount = mnBits;
+
+ switch (mnBits)
+ {
+ case 1:
+ pBuffer->mnFormat = ScanlineFormat::N1BitMsbPal;
+ break;
+ case 4:
+ pBuffer->mnFormat = ScanlineFormat::N4BitMsnPal;
+ break;
+ case 8:
+ pBuffer->mnFormat = ScanlineFormat::N8BitPal;
+ break;
+ case 24:
+ {
+ pBuffer->mnFormat = ScanlineFormat::N24BitTcRgb;
+ break;
+ }
+ case 32:
+ {
+ pBuffer->mnFormat = ScanlineFormat::N32BitTcRgba;
+ ColorMaskElement aRedMask(0xff000000);
+ aRedMask.CalcMaskShift();
+ ColorMaskElement aGreenMask(0x00ff0000);
+ aGreenMask.CalcMaskShift();
+ ColorMaskElement aBlueMask(0x0000ff00);
+ aBlueMask.CalcMaskShift();
+ pBuffer->maColorMask = ColorMask(aRedMask, aGreenMask, aBlueMask);
+ break;
+ }
+ default: assert(false);
+ }
+
+ return pBuffer;
+}
+
+void OpenGLSalBitmap::ReleaseBuffer( BitmapBuffer* pBuffer, BitmapAccessMode nMode )
+{
+ OpenGLVCLContextZone aContextZone;
+
+ if( nMode == BitmapAccessMode::Write )
+ {
+ maTexture = OpenGLTexture();
+ mbDirtyTexture = true;
+ mbChecksumValid = false;
+ }
+ // The palette is modified on read during the BitmapWriteAccess,
+ // but of course, often it is not modified; interesting.
+ maPalette = pBuffer->maPalette;
+
+ // Are there any more ground movements underneath us ?
+ assert( pBuffer->mnWidth == mnWidth );
+ assert( pBuffer->mnHeight == mnHeight );
+ assert( pBuffer->mnBitCount == mnBits );
+
+ delete pBuffer;
+}
+
+bool OpenGLSalBitmap::GetSystemData( BitmapSystemData& /*rData*/ )
+{
+ SAL_WARN( "vcl.opengl", "*** NOT IMPLEMENTED *** GetSystemData" );
+#if 0
+ // TODO Implement for ANDROID/OSX/IOS/WIN32
+ X11SalBitmap rBitmap;
+ BitmapBuffer* pBuffer;
+
+ rBitmap.Create( GetSize(), mnBits, maPalette );
+ pBuffer = rBitmap.AcquireBuffer( false );
+ if( pBuffer == NULL )
+ return false;
+
+ if (!mpUserBuffer.get())
+ {
+ if( !AllocateUserData() || !ReadTexture() )
+ {
+ rBitmap.ReleaseBuffer( pBuffer, false );
+ DeallocateUserData();
+ return false;
+ }
+ }
+
+ // TODO Might be more efficient to add a static method to SalBitmap
+ // to get system data from a buffer
+ memcpy( pBuffer->mpBits, mpUserBuffer.get(), mnBytesPerRow * mnHeight );
+
+ rBitmap.ReleaseBuffer( pBuffer, false );
+ return rBitmap.GetSystemData( rData );
+#else
+ return false;
+#endif
+}
+
+bool OpenGLSalBitmap::Replace( const Color& rSearchColor, const Color& rReplaceColor, sal_uInt8 nTol )
+{
+ VCL_GL_INFO("::Replace");
+
+ OpenGLZone aZone;
+ rtl::Reference<OpenGLContext> xContext = OpenGLContext::getVCLContext();
+ xContext->state().scissor().disable();
+ xContext->state().stencil().disable();
+
+ OpenGLFramebuffer* pFramebuffer;
+ OpenGLProgram* pProgram;
+
+ GetTexture();
+ pProgram = xContext->UseProgram( "textureVertexShader",
+ "replaceColorFragmentShader" );
+ if( !pProgram )
+ return false;
+
+ OpenGLTexture aNewTex( mnWidth, mnHeight );
+ pFramebuffer = xContext->AcquireFramebuffer( aNewTex );
+
+ pProgram->SetTexture( "sampler", maTexture );
+ pProgram->SetColor( "search_color", rSearchColor );
+ pProgram->SetColor( "replace_color", rReplaceColor );
+ pProgram->SetUniform1f( "epsilon", nTol / 255.0f );
+ pProgram->DrawTexture( maTexture );
+ pProgram->Clean();
+
+ OpenGLContext::ReleaseFramebuffer( pFramebuffer );
+ maTexture = aNewTex;
+
+ CHECK_GL_ERROR();
+ return true;
+}
+
+// Convert texture to greyscale and adjust bitmap metadata
+bool OpenGLSalBitmap::ConvertToGreyscale()
+{
+ VCL_GL_INFO("::ConvertToGreyscale");
+
+ // avoid re-converting to 8bits.
+ if ( mnBits == 8 && maPalette.IsGreyPalette8Bit())
+ return true;
+
+ OpenGLZone aZone;
+ rtl::Reference<OpenGLContext> xContext = OpenGLContext::getVCLContext();
+ xContext->state().scissor().disable();
+ xContext->state().stencil().disable();
+
+ OpenGLFramebuffer* pFramebuffer;
+ OpenGLProgram* pProgram;
+
+ GetTexture();
+ pProgram = xContext->UseProgram("textureVertexShader", "greyscaleFragmentShader");
+
+ if (!pProgram)
+ return false;
+
+ OpenGLTexture aNewTex(mnWidth, mnHeight);
+ pFramebuffer = xContext->AcquireFramebuffer(aNewTex);
+ pProgram->SetTexture("sampler", maTexture);
+ pProgram->DrawTexture(maTexture);
+ pProgram->Clean();
+
+ OpenGLContext::ReleaseFramebuffer( pFramebuffer );
+ maTexture = aNewTex;
+ mnBits = 8;
+ maPalette = Bitmap::GetGreyPalette(256);
+
+ // AllocateUserData will handle the rest.
+ DeallocateUserData();
+ mbDirtyTexture = false;
+
+ CHECK_GL_ERROR();
+ return true;
+}
+
+// This is needed to just make the bitmap usable as an alpha channel.
+// Converting to 8bit grey will do.
+bool OpenGLSalBitmap::InterpretAs8Bit()
+{
+ return ConvertToGreyscale();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/scale.cxx b/vcl/opengl/scale.cxx
new file mode 100644
index 000000000..98f0f5ea7
--- /dev/null
+++ b/vcl/opengl/scale.cxx
@@ -0,0 +1,423 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <cmath>
+
+#include <vcl/opengl/OpenGLHelper.hxx>
+
+#include <vcl/bitmap.hxx>
+
+#include <opengl/zone.hxx>
+#include <opengl/salbmp.hxx>
+#include <opengl/program.hxx>
+#include <opengl/texture.hxx>
+#include <opengl/RenderState.hxx>
+
+#include <ResampleKernel.hxx>
+
+using vcl::Kernel;
+using vcl::Lanczos3Kernel;
+
+bool OpenGLSalBitmap::ImplScaleFilter(
+ const rtl::Reference< OpenGLContext > &xContext,
+ const double& rScaleX,
+ const double& rScaleY,
+ GLenum nFilter )
+{
+ OpenGLFramebuffer* pFramebuffer;
+ OpenGLProgram* pProgram;
+ GLenum nOldFilter;
+ int nNewWidth( mnWidth * rScaleX );
+ int nNewHeight( mnHeight * rScaleY );
+
+ pProgram = xContext->UseProgram( "textureVertexShader",
+ "textureFragmentShader" );
+ if( !pProgram )
+ return false;
+
+ OpenGLTexture aNewTex(nNewWidth, nNewHeight);
+ pFramebuffer = xContext->AcquireFramebuffer( aNewTex );
+
+ pProgram->SetTexture( "sampler", maTexture );
+ nOldFilter = maTexture.GetFilter();
+ maTexture.SetFilter( nFilter );
+ pProgram->ApplyMatrix(mnWidth, mnHeight);
+ pProgram->DrawTexture( maTexture );
+ maTexture.SetFilter( nOldFilter );
+ pProgram->Clean();
+
+ OpenGLContext::ReleaseFramebuffer( pFramebuffer );
+
+ mnWidth = nNewWidth;
+ mnHeight = nNewHeight;
+ maTexture = aNewTex;
+
+ CHECK_GL_ERROR();
+ return true;
+}
+
+void OpenGLSalBitmap::ImplCreateKernel(
+ const double& fScale,
+ const Kernel& rKernel,
+ GLfloat*& pWeights,
+ sal_uInt32& aKernelSize )
+{
+ const double fSamplingRadius(rKernel.GetWidth());
+ const double fScaledRadius((fScale < 1.0) ? fSamplingRadius / fScale : fSamplingRadius);
+ const double fFilterFactor(std::min(fScale, 1.0));
+ int aNumberOfContributions;
+ double aSum( 0 );
+
+ aNumberOfContributions = (static_cast< sal_uInt32 >(fabs(ceil(fScaledRadius))) * 2) + 1 - 6;
+ aKernelSize = aNumberOfContributions / 2 + 1;
+
+ // avoid a crash for now; re-think me.
+ if (aKernelSize > 16)
+ aKernelSize = 16;
+
+ pWeights = new GLfloat[16];
+ memset( pWeights, 0, 16 * sizeof( GLfloat ) );
+
+ for( sal_uInt32 i(0); i < aKernelSize; i++ )
+ {
+ const GLfloat aWeight( rKernel.Calculate( fFilterFactor * i ) );
+ if( fabs( aWeight ) >= 0.0001 )
+ {
+ pWeights[i] = aWeight;
+ aSum += i > 0 ? aWeight * 2 : aWeight;
+ }
+ }
+
+ for( sal_uInt32 i(0); i < aKernelSize; i++ )
+ {
+ pWeights[i] /= aSum;
+ }
+}
+
+bool OpenGLSalBitmap::ImplScaleConvolution(
+ const rtl::Reference< OpenGLContext > &xContext,
+ const double& rScaleX,
+ const double& rScaleY,
+ const Kernel& aKernel )
+{
+ OpenGLFramebuffer* pFramebuffer;
+ OpenGLProgram* pProgram;
+ GLfloat* pWeights( nullptr );
+ sal_uInt32 nKernelSize;
+ GLfloat aOffsets[32];
+ int nNewWidth( mnWidth * rScaleX );
+ int nNewHeight( mnHeight * rScaleY );
+
+ // TODO Make sure the framebuffer is alright
+
+ pProgram = xContext->UseProgram( "textureVertexShader",
+ "convolutionFragmentShader" );
+ if( pProgram == nullptr )
+ return false;
+
+ // horizontal scaling in scratch texture
+ if( mnWidth != nNewWidth )
+ {
+ OpenGLTexture aScratchTex(nNewWidth, nNewHeight);
+
+ pFramebuffer = xContext->AcquireFramebuffer( aScratchTex );
+
+ for( sal_uInt32 i = 0; i < 16; i++ )
+ {
+ aOffsets[i * 2] = i / static_cast<double>(mnWidth);
+ aOffsets[i * 2 + 1] = 0;
+ }
+ ImplCreateKernel( rScaleX, aKernel, pWeights, nKernelSize );
+ pProgram->SetUniform1fv( "kernel", 16, pWeights );
+ pProgram->SetUniform2fv( "offsets", 16, aOffsets );
+ pProgram->SetTexture( "sampler", maTexture );
+ pProgram->DrawTexture( maTexture );
+ pProgram->Clean();
+
+ maTexture = aScratchTex;
+ OpenGLContext::ReleaseFramebuffer( pFramebuffer );
+ }
+
+ // vertical scaling in final texture
+ if( mnHeight != nNewHeight )
+ {
+ OpenGLTexture aScratchTex(nNewWidth, nNewHeight);
+
+ pFramebuffer = xContext->AcquireFramebuffer( aScratchTex );
+
+ for( sal_uInt32 i = 0; i < 16; i++ )
+ {
+ aOffsets[i * 2] = 0;
+ aOffsets[i * 2 + 1] = i / static_cast<double>(mnHeight);
+ }
+ ImplCreateKernel( rScaleY, aKernel, pWeights, nKernelSize );
+ pProgram->SetUniform1fv( "kernel", 16, pWeights );
+ pProgram->SetUniform2fv( "offsets", 16, aOffsets );
+ pProgram->SetTexture( "sampler", maTexture );
+ pProgram->DrawTexture( maTexture );
+ pProgram->Clean();
+
+ maTexture = aScratchTex;
+ OpenGLContext::ReleaseFramebuffer( pFramebuffer );
+ }
+
+ mnWidth = nNewWidth;
+ mnHeight = nNewHeight;
+
+ CHECK_GL_ERROR();
+ return true;
+}
+
+/*
+ "Area" scaling algorithm, which seems to give better results for downscaling
+ than other algorithms. The principle (taken from opencv, see resize.cl)
+ is that each resulting pixel is the average of all the source pixel values
+ it represents. Which is trivial in the case of exact multiples for downscaling,
+ the generic case needs to also consider that some source pixels contribute
+ only partially to their resulting pixels (because of non-integer multiples).
+*/
+bool OpenGLSalBitmap::ImplScaleArea( const rtl::Reference< OpenGLContext > &xContext,
+ double rScaleX, double rScaleY )
+{
+ int nNewWidth( mnWidth * rScaleX );
+ int nNewHeight( mnHeight * rScaleY );
+
+ if( nNewWidth == mnWidth && nNewHeight == mnHeight )
+ return true;
+
+ double ixscale = 1 / rScaleX;
+ double iyscale = 1 / rScaleY;
+ bool fast = ( ixscale == std::trunc( ixscale ) && iyscale == std::trunc( iyscale )
+ && int( nNewWidth * ixscale ) == mnWidth && int( nNewHeight * iyscale ) == mnHeight );
+
+ bool bTwoPasses = false;
+
+ // The generic case has arrays only up to 100 ratio downscaling, which is hopefully enough
+ // in practice, but protect against buffer overflows in case such an extreme case happens
+ // (and in such case the precision of the generic algorithm probably doesn't matter anyway).
+ if( ixscale > 100 || iyscale > 100 )
+ {
+ fast = true;
+ }
+ else
+ {
+ if (ixscale > 16 || iyscale > 16)
+ {
+ ixscale = std::floor(std::sqrt(ixscale));
+ iyscale = std::floor(std::sqrt(iyscale));
+ nNewWidth = int(mnWidth / ixscale);
+ rScaleX *= ixscale; // second pass x-scale factor
+ nNewHeight = int(mnHeight / iyscale);
+ rScaleY *= iyscale; // second pass y-scale factor
+ bTwoPasses = true;
+ }
+ }
+
+ // TODO Make sure the framebuffer is alright
+
+ OString sUseReducedRegisterVariantDefine;
+ if (xContext->getOpenGLCapabilitySwitch().mbLimitedShaderRegisters)
+ sUseReducedRegisterVariantDefine = OString("#define USE_REDUCED_REGISTER_VARIANT\n");
+
+ OpenGLProgram* pProgram = xContext->UseProgram( "textureVertexShader",
+ fast ? OUString( "areaScaleFastFragmentShader" ) : OUString( "areaScaleFragmentShader" ),
+ sUseReducedRegisterVariantDefine);
+
+ if( pProgram == nullptr )
+ return false;
+
+ OpenGLTexture aScratchTex(nNewWidth, nNewHeight);
+
+ OpenGLFramebuffer* pFramebuffer = xContext->AcquireFramebuffer( aScratchTex );
+
+ // NOTE: This setup is also done in OpenGLSalGraphicsImpl::DrawTransformedTexture().
+ if( fast )
+ {
+ pProgram->SetUniform1i( "xscale", ixscale );
+ pProgram->SetUniform1i( "yscale", iyscale );
+ // The shader operates on pixels in the surrounding area, so it's necessary
+ // to know the step in texture coordinates to get to the next pixel.
+ // With a texture atlas the "texture" is just a subtexture of a larger texture,
+ // so while with a normal texture we'd map between <0.0,1.0> and <0,mnWidth>,
+ // with a subtexture the texture coordinates range is smaller.
+ GLfloat srcCoords[ 8 ];
+ maTexture.GetWholeCoord( srcCoords );
+ pProgram->SetUniform1f( "xstep", ( srcCoords[ 4 ] - srcCoords[ 0 ] ) / mnWidth );
+ pProgram->SetUniform1f( "ystep", ( srcCoords[ 5 ] - srcCoords[ 1 ] ) / mnHeight );
+ pProgram->SetUniform1f( "ratio", 1.0 / ( ixscale * iyscale ));
+ }
+ else
+ {
+ pProgram->SetUniform1f( "xscale", ixscale );
+ pProgram->SetUniform1f( "yscale", iyscale );
+ pProgram->SetUniform1i( "swidth", mnWidth );
+ pProgram->SetUniform1i( "sheight", mnHeight );
+ // The shader internally actually operates on pixel coordinates,
+ // so it needs to know how to convert to those from the texture coordinates.
+ // With a simple texture that would mean converting e.g. between
+ // <0,mnWidth-1> and <0.0,1.0> coordinates.
+ // However with a texture atlas the "texture" is just a subtexture
+ // of a larger texture, so the texture coordinates need offset and ratio
+ // conversion too.
+ GLfloat srcCoords[ 8 ];
+ maTexture.GetWholeCoord( srcCoords );
+ pProgram->SetUniform1f( "xoffset", srcCoords[ 0 ] );
+ pProgram->SetUniform1f( "yoffset", srcCoords[ 1 ] );
+ pProgram->SetUniform1f( "xtopixelratio", nNewWidth / ( srcCoords[ 4 ] - srcCoords[ 0 ] ));
+ pProgram->SetUniform1f( "ytopixelratio", nNewHeight / ( srcCoords[ 5 ] - srcCoords[ 1 ] ));
+ pProgram->SetUniform1f( "xfrompixelratio", ( srcCoords[ 4 ] - srcCoords[ 0 ] ) / mnWidth );
+ pProgram->SetUniform1f( "yfrompixelratio", ( srcCoords[ 5 ] - srcCoords[ 1 ] ) / mnHeight );
+ }
+
+ pProgram->SetTexture( "sampler", maTexture );
+ pProgram->DrawTexture( maTexture );
+ pProgram->Clean();
+
+ OpenGLContext::ReleaseFramebuffer(pFramebuffer);
+
+ CHECK_GL_ERROR();
+
+ if (bTwoPasses)
+ {
+ mnWidth = nNewWidth;
+ mnHeight = nNewHeight;
+
+ nNewWidth = round(mnWidth * rScaleX);
+ nNewHeight = round(mnHeight * rScaleY);
+
+ ixscale = 1 / rScaleX;
+ iyscale = 1 / rScaleY;
+
+ pProgram = xContext->UseProgram("textureVertexShader", "areaScaleFragmentShader", sUseReducedRegisterVariantDefine);
+ if (pProgram == nullptr)
+ return false;
+
+ OpenGLTexture aScratchTex2(nNewWidth, nNewHeight);
+
+ pFramebuffer = xContext->AcquireFramebuffer(aScratchTex2);
+
+ pProgram->SetUniform1f("xscale", ixscale);
+ pProgram->SetUniform1f("yscale", iyscale);
+ pProgram->SetUniform1i("swidth", mnWidth);
+ pProgram->SetUniform1i("sheight", mnHeight);
+
+ GLfloat srcCoords[ 8 ];
+ aScratchTex.GetWholeCoord( srcCoords );
+ pProgram->SetUniform1f( "xoffset", srcCoords[ 0 ] );
+ pProgram->SetUniform1f( "yoffset", srcCoords[ 1 ] );
+ pProgram->SetUniform1f( "xtopixelratio", nNewWidth / ( srcCoords[ 4 ] - srcCoords[ 0 ] ));
+ pProgram->SetUniform1f( "ytopixelratio", nNewHeight / ( srcCoords[ 5 ] - srcCoords[ 1 ] ));
+ pProgram->SetUniform1f( "xfrompixelratio", ( srcCoords[ 4 ] - srcCoords[ 0 ] ) / mnWidth );
+ pProgram->SetUniform1f( "yfrompixelratio", ( srcCoords[ 5 ] - srcCoords[ 1 ] ) / mnHeight );
+
+ pProgram->SetTexture("sampler", aScratchTex);
+ pProgram->DrawTexture(aScratchTex);
+ pProgram->Clean();
+
+ OpenGLContext::ReleaseFramebuffer(pFramebuffer);
+
+ CHECK_GL_ERROR();
+
+ maTexture = aScratchTex2;
+ mnWidth = nNewWidth;
+ mnHeight = nNewHeight;
+ }
+ else
+ {
+ maTexture = aScratchTex;
+ mnWidth = nNewWidth;
+ mnHeight = nNewHeight;
+ }
+
+ return true;
+}
+
+void OpenGLSalBitmap::ImplScale( const double& rScaleX, const double& rScaleY, BmpScaleFlag nScaleFlag )
+{
+ VCL_GL_INFO( "::ImplScale" );
+
+ mpUserBuffer.reset();
+ OpenGLVCLContextZone aContextZone;
+ rtl::Reference<OpenGLContext> xContext = OpenGLContext::getVCLContext();
+ xContext->state().scissor().disable();
+ xContext->state().stencil().disable();
+
+ if (rScaleX <= 1 && rScaleY <= 1)
+ {
+ nScaleFlag = BmpScaleFlag::BestQuality;
+ }
+
+ if( nScaleFlag == BmpScaleFlag::Fast )
+ {
+ ImplScaleFilter( xContext, rScaleX, rScaleY, GL_NEAREST );
+ }
+ else if( nScaleFlag == BmpScaleFlag::BiLinear )
+ {
+ ImplScaleFilter( xContext, rScaleX, rScaleY, GL_LINEAR );
+ }
+ else if( nScaleFlag == BmpScaleFlag::Default )
+ {
+ const Lanczos3Kernel aKernel;
+
+ ImplScaleConvolution( xContext, rScaleX, rScaleY, aKernel );
+ }
+ else if( nScaleFlag == BmpScaleFlag::BestQuality && rScaleX <= 1 && rScaleY <= 1 )
+ { // Use area scaling for best quality, but only if downscaling.
+ ImplScaleArea( xContext, rScaleX, rScaleY );
+ }
+ else if( nScaleFlag == BmpScaleFlag::Lanczos || nScaleFlag == BmpScaleFlag::BestQuality )
+ {
+ const Lanczos3Kernel aKernel;
+
+ ImplScaleConvolution( xContext, rScaleX, rScaleY, aKernel );
+ }
+ else
+ SAL_WARN( "vcl.opengl", "Invalid flag for scaling operation" );
+}
+
+bool OpenGLSalBitmap::ScalingSupported() const
+{
+ return true;
+}
+
+bool OpenGLSalBitmap::Scale( const double& rScaleX, const double& rScaleY, BmpScaleFlag nScaleFlag )
+{
+ OpenGLVCLContextZone aContextZone;
+
+ VCL_GL_INFO("::Scale " << int(nScaleFlag)
+ << " from " << mnWidth << "x" << mnHeight
+ << " to " << (mnWidth * rScaleX) << "x" << (mnHeight * rScaleY) );
+
+ if( nScaleFlag == BmpScaleFlag::Fast ||
+ nScaleFlag == BmpScaleFlag::BiLinear ||
+ nScaleFlag == BmpScaleFlag::Lanczos ||
+ nScaleFlag == BmpScaleFlag::Default ||
+ nScaleFlag == BmpScaleFlag::BestQuality )
+ {
+ ImplScale( rScaleX, rScaleY, nScaleFlag );
+ return true;
+ }
+
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/shaders/areaHashCRC64TFragmentShader.glsl b/vcl/opengl/shaders/areaHashCRC64TFragmentShader.glsl
new file mode 100644
index 000000000..901b481d8
--- /dev/null
+++ b/vcl/opengl/shaders/areaHashCRC64TFragmentShader.glsl
@@ -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/.
+ */
+
+/* TODO Use textureOffset for newest version of GLSL */
+
+
+#version 130
+
+uniform sampler2D crc_table;
+uniform sampler2D sampler;
+uniform float xstep;
+uniform float ystep;
+
+varying vec2 tex_coord;
+
+const int scale = 4;
+const float ratio = 16.0;
+
+
+ivec2 crc64( ivec2 hval, int color )
+{
+ int dx = 2 * ((hval[0] ^ color) & 0xff);
+ float s = dx / 255.0;
+ vec4 table_value_lo = round(texture2D( crc_table, vec2( s, 0.0 ) ) * 255.0);
+ s = (dx+1) / 255.0;
+ vec4 table_value_hi = round(texture2D( crc_table, vec2( s, 0.0 ) ) * 255.0);
+
+ int tvalue_lo = int(table_value_lo[0]) | (int(table_value_lo[1]) << 8) | (int(table_value_lo[2]) << 16) | (int(table_value_lo[3]) << 24);
+ int tvalue_hi = int(table_value_hi[0]) | (int(table_value_hi[1]) << 8) | (int(table_value_hi[2]) << 16) | (int(table_value_hi[3]) << 24);
+
+ hval[1] = tvalue_hi ^ (hval[1] >> 8);
+ hval[0] = tvalue_lo ^ ( (hval[1] << 24) | (hval[0] >> 8) );
+
+ return hval;
+}
+
+
+void main(void)
+{
+ ivec2 Crc = ivec2( 0xffffffff, 0xffffffff );
+ vec2 offset = vec2( 0.0, 0.0 );
+ vec2 next_coord = tex_coord.st;
+ for( int y = 0; y < scale && next_coord.y <= 1.0; ++y )
+ {
+ for( int x = 0; x < scale && next_coord.x <= 1.0; ++x )
+ {
+ vec4 pixel = round(texture2D( sampler, next_coord ) * 255.0);
+
+ int r = int(pixel.r); // 0..255
+ int g = int(pixel.g); // 0..255
+ int b = int(pixel.b); // 0..255
+ int a = int(pixel.a); // 0..255
+
+ Crc = crc64( Crc, r );
+ Crc = crc64( Crc, g );
+ Crc = crc64( Crc, b );
+ Crc = crc64( Crc, a );
+
+ offset.x += xstep;
+ next_coord = tex_coord.st + offset;
+ }
+ offset.y += ystep;
+ offset.x = 0.0;
+ next_coord = tex_coord.st + offset;
+ }
+
+ Crc[0] = ~Crc[0];
+ Crc[1] = ~Crc[1];
+
+ int Hash = Crc[0] ^ Crc[1];
+
+ float fr = ( Hash & 0xff) / 255.0;
+ float fg = ((Hash >> 8) & 0xff) / 255.0;
+ float fb = ((Hash >> 16) & 0xff) / 255.0;
+ float fa = ((Hash >> 24) & 0xff) / 255.0;
+
+
+ gl_FragColor = vec4(fr, fg, fb, fa);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/shaders/areaScaleFastFragmentShader.glsl b/vcl/opengl/shaders/areaScaleFastFragmentShader.glsl
new file mode 100644
index 000000000..57ad8fa97
--- /dev/null
+++ b/vcl/opengl/shaders/areaScaleFastFragmentShader.glsl
@@ -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/.
+ */
+
+/* TODO Use textureOffset for newest version of GLSL */
+
+#version 130
+
+uniform sampler2D sampler;
+uniform int xscale;
+uniform int yscale;
+uniform float xstep;
+uniform float ystep;
+uniform float ratio; // = 1.0/(xscale*yscale)
+
+varying vec2 tex_coord;
+
+// This mode makes the scaling work like maskedTextureFragmentShader.glsl
+// (instead of like plain textureVertexShader.glsl).
+#ifdef MASKED
+varying vec2 mask_coord;
+uniform sampler2D mask;
+#endif
+
+/*
+ Just make the resulting color the average of all the source pixels
+ (which is an area (xscale)x(yscale) ).
+*/
+void main(void)
+{
+ vec4 sum = vec4( 0.0, 0.0, 0.0, 0.0 );
+ vec2 offset = vec2( 0.0, 0.0 );
+ for( int y = 0; y < yscale; ++y )
+ {
+ for( int x = 0; x < xscale; ++x )
+ {
+#ifndef MASKED
+ sum += texture2D( sampler, tex_coord.st + offset );
+#else
+ vec4 texel;
+ texel = texture2D( sampler, tex_coord.st + offset );
+ texel.a = 1.0 - texture2D( mask, mask_coord.st + offset ).r;
+ sum += texel;
+#endif
+ offset.x += xstep;
+ }
+ offset.y += ystep;
+ offset.x = 0.0;
+ }
+ sum *= ratio;
+ gl_FragColor = sum;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/shaders/areaScaleFragmentShader.glsl b/vcl/opengl/shaders/areaScaleFragmentShader.glsl
new file mode 100644
index 000000000..5dab5ba01
--- /dev/null
+++ b/vcl/opengl/shaders/areaScaleFragmentShader.glsl
@@ -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/.
+ */
+
+#version 130
+
+uniform sampler2D sampler;
+uniform int swidth;
+uniform int sheight;
+uniform float xscale;
+uniform float yscale;
+uniform float xoffset;
+uniform float yoffset;
+uniform float xfrompixelratio;
+uniform float yfrompixelratio;
+uniform float xtopixelratio;
+uniform float ytopixelratio;
+
+varying vec2 tex_coord;
+
+// This mode makes the scaling work like maskedTextureFragmentShader.glsl
+// (instead of like plain textureVertexShader.glsl).
+#ifdef MASKED
+varying vec2 mask_coord;
+uniform sampler2D mask;
+#endif
+
+#ifdef USE_REDUCED_REGISTER_VARIANT
+
+vec4 getTexel(int x, int y)
+{
+ vec2 pos = vec2( x * xfrompixelratio + xoffset, y * yfrompixelratio + yoffset );
+ vec4 texel = texture2D(sampler, pos);
+#ifdef MASKED
+ texel.a = 1.0 - texture2D(mask, pos - tex_coord.st + mask_coord.st).r;
+#endif
+ return texel;
+}
+
+void main(void)
+{
+ // Convert to pixel coordinates again.
+ int dx = int(( tex_coord.s - xoffset ) * xtopixelratio );
+ int dy = int(( tex_coord.t - yoffset ) * ytopixelratio );
+
+ // Compute the range of source pixels which will make up this destination pixel.
+ float fsx1 = min(dx * xscale, float(swidth - 1));
+ float fsx2 = min(fsx1 + xscale, float(swidth - 1));
+
+ float fsy1 = min(dy * yscale, float(sheight - 1));
+ float fsy2 = min(fsy1 + yscale, float(sheight - 1));
+
+ // To whole pixel coordinates.
+ int xstart = int(floor(fsx1));
+ int xend = int(floor(fsx2));
+
+ int ystart = int(floor(fsy1));
+ int yend = int(floor(fsy2));
+
+ float xlength = fsx2 - fsx1;
+ float ylength = fsy2 - fsy1;
+
+ float xContribution[3];
+ xContribution[0] = (1.0 - max(0.0, fsx1 - xstart)) / xlength;
+ xContribution[1] = 1.0 / xlength;
+ xContribution[2] = (1.0 - max(0.0, (xend + 1) - fsx2)) / xlength;
+
+ float yContribution[3];
+ yContribution[0] = (1.0 - max(0.0, fsy1 - ystart)) / ylength;
+ yContribution[1] = 1.0 / ylength;
+ yContribution[2] = (1.0 - max(0.0, (yend + 1) - fsy2)) / ylength;
+
+ vec4 sumAll = vec4(0.0, 0.0, 0.0, 0.0);
+ vec4 texel;
+ // First Y pass
+ {
+ vec4 sumX = vec4(0.0, 0.0, 0.0, 0.0);
+
+ sumX += getTexel(xstart, ystart) * xContribution[0];
+ for (int x = xstart + 1; x < xend; ++x)
+ {
+ sumX += getTexel(x, ystart) * xContribution[1];
+ }
+ sumX += getTexel(xend, ystart) * xContribution[2];
+
+ sumAll += sumX * yContribution[0];
+ }
+
+ // Middle Y Passes
+ for (int y = ystart + 1; y < yend; ++y)
+ {
+ vec4 sumX = vec4(0.0, 0.0, 0.0, 0.0);
+
+ sumX += getTexel(xstart, y) * xContribution[0];
+ for (int x = xstart + 1; x < xend; ++x)
+ {
+ sumX += getTexel(x, y) * xContribution[1];
+ }
+ sumX += getTexel(xend, y) * xContribution[2];
+
+ sumAll += sumX * yContribution[1];
+ }
+
+ // Last Y pass
+ {
+ vec4 sumX = vec4(0.0, 0.0, 0.0, 0.0);
+
+ sumX += getTexel(xstart, yend) * xContribution[0];
+ for (int x = xstart + 1; x < xend; ++x)
+ {
+ sumX += getTexel(x, yend) * xContribution[1];
+ }
+ sumX += getTexel(xend, yend) * xContribution[2];
+
+ sumAll += sumX * yContribution[2];
+ }
+
+ gl_FragColor = sumAll;
+}
+#else
+void main(void)
+{
+ // Convert to pixel coordinates again.
+ int dx = int(( tex_coord.s - xoffset ) * xtopixelratio );
+ int dy = int(( tex_coord.t - yoffset ) * ytopixelratio );
+
+ // How much each column/row will contribute to the resulting pixel.
+ // Note: These values are always the same for the same X (or Y),
+ // so they could be precalculated in C++ and passed to the shader,
+ // but GLSL has limits on the size of uniforms passed to it,
+ // so it'd need something like texture buffer objects from newer
+ // GLSL versions, and it seems the hassle is not really worth it.
+ float xratio[ 16 + 2 ];
+ float yratio[ 16 + 2 ];
+
+ // For finding the first and last source pixel.
+ int xpixel[ 16 + 2 ];
+ int ypixel[ 16 + 2 ];
+
+ int xpos = 0;
+ int ypos = 0;
+
+ // Compute the range of source pixels which will make up this destination pixel.
+ float fsx1 = dx * xscale;
+ float fsx2 = fsx1 + xscale;
+ // To whole pixel coordinates.
+ int sx1 = int( ceil( fsx1 ) );
+ int sx2 = int( floor( fsx2 ) );
+ // Range checking.
+ sx2 = min( sx2, swidth - 1 );
+ sx1 = min( sx1, sx2 );
+
+ // How much one full column contributes to the resulting pixel.
+ float width = min( xscale, swidth - fsx1 );
+
+ if( sx1 - fsx1 > 0.001 )
+ { // The first column contributes only partially.
+ xpixel[ xpos ] = sx1 - 1;
+ xratio[ xpos ] = ( sx1 - fsx1 ) / width;
+ ++xpos;
+ }
+ for( int sx = sx1; sx < sx2; ++sx )
+ { // Columns that fully contribute to the resulting pixel.
+ xpixel[ xpos ] = sx;
+ xratio[ xpos ] = 1.0 / width;
+ ++xpos;
+ }
+ if( fsx2 - sx2 > 0.001 )
+ { // The last column contributes only partially.
+ xpixel[ xpos ] = sx2;
+ xratio[ xpos ] = min( min( fsx2 - sx2, 1.0 ) / width, 1.0 );
+ ++xpos;
+ }
+
+ // The same for Y.
+ float fsy1 = dy * yscale;
+ float fsy2 = fsy1 + yscale;
+ int sy1 = int( ceil( fsy1 ) );
+ int sy2 = int( floor( fsy2 ) );
+ sy2 = min( sy2, sheight - 1 );
+ sy1 = min( sy1, sy2 );
+
+ float height = min( yscale, sheight - fsy1 );
+
+ if( sy1 - fsy1 > 0.001 )
+ {
+ ypixel[ ypos ] = sy1 - 1;
+ yratio[ ypos ] = ( sy1 - fsy1 ) / height;
+ ++ypos;
+ }
+ for( int sy = sy1; sy < sy2; ++sy )
+ {
+ ypixel[ ypos ] = sy;
+ yratio[ ypos ] = 1.0 / height;
+ ++ypos;
+ }
+ if( fsy2 - sy2 > 0.001 )
+ {
+ ypixel[ ypos ] = sy2;
+ yratio[ ypos ] = min( min( fsy2 - sy2, 1.0 ) / height, 1.0 );
+ ++ypos;
+ }
+
+ int xstart = xpixel[ 0 ];
+ int xend = xpixel[ xpos - 1 ];
+ int ystart = ypixel[ 0 ];
+ int yend = ypixel[ ypos - 1 ];
+
+ vec4 sum = vec4( 0.0, 0.0, 0.0, 0.0 );
+
+ ypos = 0;
+ for( int y = ystart; y <= yend; ++y, ++ypos )
+ {
+ vec4 tmp = vec4( 0.0, 0.0, 0.0, 0.0 );
+ xpos = 0;
+ for( int x = xstart; x <= xend; ++x, ++xpos )
+ {
+ vec2 pos = vec2( x * xfrompixelratio + xoffset, y * yfrompixelratio + yoffset );
+#ifndef MASKED
+ tmp += texture2D( sampler, pos ) * xratio[ xpos ];
+#else
+ vec4 texel;
+ texel = texture2D( sampler, pos );
+ texel.a = 1.0 - texture2D( mask, pos - tex_coord.st + mask_coord.st ).r;
+ tmp += texel * xratio[ xpos ];
+#endif
+ }
+ sum += tmp * yratio[ ypos ];
+ }
+
+ gl_FragColor = sum;
+}
+#endif
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/shaders/blendedTextureFragmentShader.glsl b/vcl/opengl/shaders/blendedTextureFragmentShader.glsl
new file mode 100644
index 000000000..15dfcf7e7
--- /dev/null
+++ b/vcl/opengl/shaders/blendedTextureFragmentShader.glsl
@@ -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/.
+ */
+
+#version 130
+
+varying vec2 tex_coord;
+varying vec2 alpha_coord;
+varying vec2 mask_coord;
+
+uniform sampler2D sampler;
+uniform sampler2D mask;
+uniform sampler2D alpha;
+
+void main()
+{
+ vec4 texel0, texel1, texel2;
+
+ texel0 = texture2D(sampler, tex_coord);
+ texel1 = texture2D(mask, mask_coord);
+ texel2 = texture2D(alpha, alpha_coord);
+ gl_FragColor = texel0;
+
+ /* Only blend if the alpha texture wasn't fully transparent */
+ gl_FragColor.a = 1.0 - (1.0 - floor(texel2.r)) * texel1.r;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/shaders/blendedTextureVertexShader.glsl b/vcl/opengl/shaders/blendedTextureVertexShader.glsl
new file mode 100644
index 000000000..3e60d0e22
--- /dev/null
+++ b/vcl/opengl/shaders/blendedTextureVertexShader.glsl
@@ -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/.
+ */
+
+#version 130
+
+attribute vec4 position;
+attribute vec2 tex_coord_in;
+attribute vec2 alpha_coord_in;
+attribute vec2 mask_coord_in;
+varying vec2 tex_coord;
+varying vec2 alpha_coord;
+varying vec2 mask_coord;
+uniform mat4 mvp;
+
+void main() {
+ gl_Position = mvp * position;
+ tex_coord = tex_coord_in;
+ alpha_coord = alpha_coord_in;
+ mask_coord = mask_coord_in;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/shaders/combinedFragmentShader.glsl b/vcl/opengl/shaders/combinedFragmentShader.glsl
new file mode 100644
index 000000000..2515b174f
--- /dev/null
+++ b/vcl/opengl/shaders/combinedFragmentShader.glsl
@@ -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/.
+ */
+
+#version 130
+
+varying float fade_factor; // 0->1 fade factor used for AA
+varying float multiply;
+
+#ifdef USE_VERTEX_COLORS
+varying vec4 vertex_color;
+#endif
+
+uniform vec4 color;
+
+#define TYPE_NORMAL 0
+#define TYPE_LINE 1
+
+uniform int type;
+
+void main()
+{
+#ifdef USE_VERTEX_COLORS
+ vec4 result = vertex_color;
+#else
+ vec4 result = color;
+#endif
+
+ if (type == TYPE_LINE)
+ {
+ float dist = (1.0 - abs(fade_factor)) * multiply;
+ float alpha = clamp(dist, 0.0, 1.0);
+ result.a = result.a * alpha;
+ }
+
+ gl_FragColor = result;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/shaders/combinedTextureFragmentShader.glsl b/vcl/opengl/shaders/combinedTextureFragmentShader.glsl
new file mode 100644
index 000000000..2990de8c4
--- /dev/null
+++ b/vcl/opengl/shaders/combinedTextureFragmentShader.glsl
@@ -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/.
+ */
+
+#version 130
+
+varying vec2 tex_coord;
+varying vec2 alpha_coord;
+varying vec2 mask_coord;
+#ifdef USE_VERTEX_COLORS
+varying vec4 vertex_color;
+#endif
+
+uniform sampler2D texture;
+uniform sampler2D mask;
+uniform sampler2D alpha;
+
+uniform vec4 color;
+
+uniform int type;
+
+#define TYPE_NORMAL 0
+#define TYPE_BLEND 1
+#define TYPE_MASKED 2
+#define TYPE_DIFF 3
+#define TYPE_MASKED_COLOR 4
+
+void main()
+{
+ vec4 texelTexture = texture2D(texture, tex_coord);
+
+ if (type == TYPE_NORMAL)
+ {
+ gl_FragColor = texelTexture;
+ }
+ else if (type == TYPE_BLEND)
+ {
+ vec4 texelMask = texture2D(mask, mask_coord);
+ vec4 texelAlpha = texture2D(alpha, alpha_coord);
+ gl_FragColor = texelTexture;
+ gl_FragColor.a = 1.0 - (1.0 - floor(texelAlpha.r)) * texelMask.r;
+ }
+ else if (type == TYPE_MASKED)
+ {
+ vec4 texelMask = texture2D(mask, mask_coord);
+ gl_FragColor = texelTexture;
+ gl_FragColor.a = 1.0 - texelMask.r;
+ }
+ else if (type == TYPE_DIFF)
+ {
+ vec4 texelMask = texture2D(mask, mask_coord);
+ float alpha = 1.0 - abs(texelTexture.r - texelMask.r);
+ if (alpha > 0.0)
+ gl_FragColor = texelMask / alpha;
+ gl_FragColor.a = alpha;
+ }
+ else if (type == TYPE_MASKED_COLOR)
+ {
+#ifdef USE_VERTEX_COLORS
+ gl_FragColor = vertex_color;
+#else
+ gl_FragColor = color;
+#endif
+ gl_FragColor.a = 1.0 - texelTexture.r;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/shaders/combinedTextureVertexShader.glsl b/vcl/opengl/shaders/combinedTextureVertexShader.glsl
new file mode 100644
index 000000000..52d44d553
--- /dev/null
+++ b/vcl/opengl/shaders/combinedTextureVertexShader.glsl
@@ -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/.
+ */
+
+#version 130
+
+attribute vec4 position;
+attribute vec2 tex_coord_in;
+attribute vec2 mask_coord_in;
+attribute vec2 alpha_coord_in;
+#ifdef USE_VERTEX_COLORS
+attribute vec4 vertex_color_in;
+#endif
+
+varying vec2 tex_coord;
+varying vec2 mask_coord;
+varying vec2 alpha_coord;
+#ifdef USE_VERTEX_COLORS
+varying vec4 vertex_color;
+#endif
+
+uniform mat4 mvp;
+uniform mat4 transform;
+
+uniform int type;
+
+void main()
+{
+ gl_Position = mvp * transform * position;
+ tex_coord = tex_coord_in;
+ mask_coord = mask_coord_in;
+ alpha_coord = alpha_coord_in;
+#ifdef USE_VERTEX_COLORS
+ vertex_color = vertex_color_in / 255.0;
+#endif
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/shaders/combinedVertexShader.glsl b/vcl/opengl/shaders/combinedVertexShader.glsl
new file mode 100644
index 000000000..16fc4a942
--- /dev/null
+++ b/vcl/opengl/shaders/combinedVertexShader.glsl
@@ -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/.
+ */
+
+#version 130
+
+attribute vec2 position;
+attribute vec4 extrusion_vectors;
+#ifdef USE_VERTEX_COLORS
+attribute vec4 vertex_color_in;
+#endif
+
+varying float fade_factor; // fade factor for anti-aliasing
+varying float multiply;
+
+#ifdef USE_VERTEX_COLORS
+varying vec4 vertex_color;
+#endif
+
+uniform float line_width;
+uniform float feather; // width where we fade the line
+
+uniform mat4 mvp;
+
+#define TYPE_NORMAL 0
+#define TYPE_LINE 1
+
+uniform int type;
+
+void main()
+{
+ vec2 extrusion_vector = extrusion_vectors.xy;
+
+ float render_thickness = 0.0;
+
+ if (type == TYPE_LINE)
+ {
+ // miter factor to additionally lengthen the distance of vertex (needed for miter)
+ // if 1.0 - miter_factor has no effect
+ float miter_factor = 1.0 / abs(extrusion_vectors.z);
+ // fade factor is always -1.0 or 1.0 -> we transport that info together with length
+ fade_factor = sign(extrusion_vectors.z);
+#ifdef USE_VERTEX_COLORS
+ float the_feather = (1.0 + sign(extrusion_vectors.w)) / 4.0;
+ float the_line_width = abs(extrusion_vectors.w);
+#else
+ float the_feather = feather;
+ float the_line_width = line_width;
+#endif
+ render_thickness = (the_line_width * miter_factor + the_feather * 2.0 * miter_factor);
+
+ // Calculate the multiplier so we can transform the 0->1 fade factor
+ // to take feather and line width into account.
+
+ float start = mix(0.0, (the_line_width / 2.0) - the_feather, the_feather * 2.0);
+ float end = mix(1.0, (the_line_width / 2.0) + the_feather, the_feather * 2.0);
+
+ multiply = 1.0 / (1.0 - (start / end));
+ }
+
+ // lengthen the vertex in direction of the extrusion vector by line width.
+ vec4 final_position = vec4(position + (extrusion_vector * (render_thickness / 2.0) ), 0.0, 1.0);
+
+ gl_Position = mvp * final_position;
+
+#ifdef USE_VERTEX_COLORS
+ vertex_color = vertex_color_in / 255.0;
+#endif
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/shaders/convolutionFragmentShader.glsl b/vcl/opengl/shaders/convolutionFragmentShader.glsl
new file mode 100644
index 000000000..4b2f316e0
--- /dev/null
+++ b/vcl/opengl/shaders/convolutionFragmentShader.glsl
@@ -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/.
+ */
+
+/* TODO Use textureOffset for newest version of GLSL */
+
+#version 130
+
+uniform sampler2D sampler;
+uniform vec2 offsets[16];
+uniform float kernel[16];
+
+varying vec2 tex_coord;
+
+void main(void)
+{
+ vec4 sum = texture2D(sampler, tex_coord.st) * kernel[0];
+ for (int i = 1; i < 16; i++) {
+ sum += texture2D(sampler, tex_coord.st - offsets[i]) * kernel[i];
+ sum += texture2D(sampler, tex_coord.st + offsets[i]) * kernel[i];
+ }
+ gl_FragColor = sum;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/shaders/diffTextureFragmentShader.glsl b/vcl/opengl/shaders/diffTextureFragmentShader.glsl
new file mode 100644
index 000000000..8c50ddf98
--- /dev/null
+++ b/vcl/opengl/shaders/diffTextureFragmentShader.glsl
@@ -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/.
+ */
+
+#version 130
+
+/*precision mediump float;*/
+varying vec2 tex_coord;
+varying vec2 mask_coord;
+uniform sampler2D texture; /* white background */
+uniform sampler2D mask; /* black background */
+
+void main()
+{
+ float alpha;
+ vec4 texel0, texel1;
+ texel0 = texture2D(texture, tex_coord);
+ texel1 = texture2D(mask, mask_coord);
+ alpha = 1.0 - abs(texel0.r - texel1.r);
+ if(alpha > 0.0)
+ gl_FragColor = texel1 / alpha;
+ gl_FragColor.a = alpha;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/shaders/dumbVertexShader.glsl b/vcl/opengl/shaders/dumbVertexShader.glsl
new file mode 100644
index 000000000..80341b614
--- /dev/null
+++ b/vcl/opengl/shaders/dumbVertexShader.glsl
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#version 130
+
+attribute vec4 position;
+uniform mat4 mvp;
+
+void main() {
+ gl_Position = mvp * position;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/shaders/greyscaleFragmentShader.glsl b/vcl/opengl/shaders/greyscaleFragmentShader.glsl
new file mode 100644
index 000000000..c37f0d5df
--- /dev/null
+++ b/vcl/opengl/shaders/greyscaleFragmentShader.glsl
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#version 130
+
+varying vec2 tex_coord;
+uniform sampler2D sampler;
+
+void main() {
+ vec4 texel = texture2D(sampler, tex_coord);
+ gl_FragColor = vec4(vec3(dot(texel.rgb, vec3(0.301, 0.591, 0.108))), 1.0);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/shaders/invert50FragmentShader.glsl b/vcl/opengl/shaders/invert50FragmentShader.glsl
new file mode 100644
index 000000000..9222888f0
--- /dev/null
+++ b/vcl/opengl/shaders/invert50FragmentShader.glsl
@@ -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/.
+ */
+
+#version 130
+
+/*precision mediump float;*/
+
+void main() {
+ vec2 tex_mod = mod(gl_FragCoord, 2).xy;
+ bool bLeft = (tex_mod.x > 0.0) && (tex_mod.x < 1.0);
+ bool bTop = (tex_mod.y > 0.0) && (tex_mod.y < 1.0);
+ // horrors - where is the XOR operator ?
+ if ((bTop && bLeft) || (!bTop && !bLeft))
+ gl_FragColor = vec4(255,255,255,0);
+ else
+ gl_FragColor = vec4(0,0,0,0);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/shaders/lineFragmentShader.glsl b/vcl/opengl/shaders/lineFragmentShader.glsl
new file mode 100644
index 000000000..c49570be3
--- /dev/null
+++ b/vcl/opengl/shaders/lineFragmentShader.glsl
@@ -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/.
+ */
+
+#version 130
+
+varying float fade_factor; // 0->1 fade factor used for AA
+uniform vec4 color;
+
+uniform float line_width;
+uniform float feather;
+
+void main()
+{
+ float start = (line_width / 2.0) - feather; // where we start to apply alpha
+ float end = (line_width / 2.0) + feather; // where we end to apply alpha
+
+ // Calculate the multiplier so we can transform the 0->1 fade factor
+ // to take feather and line width into account.
+ float multiplied = 1.0 / (1.0 - (start / end));
+
+ float dist = (1.0 - abs(fade_factor)) * multiplied;
+
+ float alpha = clamp(dist, 0.0, 1.0);
+
+ // modify the alpha channel only
+ vec4 result_color = color;
+ result_color.a = result_color.a * alpha;
+
+ gl_FragColor = result_color;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/shaders/lineVertexShader.glsl b/vcl/opengl/shaders/lineVertexShader.glsl
new file mode 100644
index 000000000..e26be78d0
--- /dev/null
+++ b/vcl/opengl/shaders/lineVertexShader.glsl
@@ -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/.
+ */
+
+attribute vec2 position;
+attribute vec4 extrusion_vectors;
+
+varying float fade_factor; // fade factor for anti-aliasing
+
+uniform float line_width;
+uniform float feather; // width where we fade the line
+
+uniform mat4 mvp;
+
+void main()
+{
+ vec2 extrusion_vector = extrusion_vectors.xy;
+ // miter factor to additionally lengthen the distance of vertex (needed for miter)
+ // if 1.0 - miter_factor has no effect
+ float miter_factor = 1.0f / abs(extrusion_vectors.z);
+ // fade factor is always -1.0 or 1.0 -> we transport that info together with length
+ fade_factor = sign(extrusion_vectors.z);
+
+ float rendered_thickness = (line_width + feather * 2.0) * miter_factor;
+
+ // lengthen the vertex in direction of the extrusion vector by line width.
+ vec4 position = vec4(position + (extrusion_vector * (rendered_thickness / 2.0) ), 0.0, 1.0);
+
+ gl_Position = mvp * position;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/shaders/linearGradientFragmentShader.glsl b/vcl/opengl/shaders/linearGradientFragmentShader.glsl
new file mode 100644
index 000000000..bd1137c16
--- /dev/null
+++ b/vcl/opengl/shaders/linearGradientFragmentShader.glsl
@@ -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/.
+ */
+
+#version 120
+
+uniform vec4 start_color;
+uniform vec4 end_color;
+uniform mat3x3 transform;
+varying vec2 tex_coord;
+
+void main(void)
+{
+ gl_FragColor = mix(end_color, start_color,
+ clamp(tex_coord.t, 0.0, 1.0));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/shaders/maskFragmentShader.glsl b/vcl/opengl/shaders/maskFragmentShader.glsl
new file mode 100644
index 000000000..864869c89
--- /dev/null
+++ b/vcl/opengl/shaders/maskFragmentShader.glsl
@@ -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/.
+ */
+
+#version 130
+
+varying vec2 tex_coord;
+uniform sampler2D sampler;
+uniform vec4 color;
+
+void main() {
+ vec4 texel0;
+ texel0 = texture2D(sampler, tex_coord);
+ gl_FragColor = color;
+ gl_FragColor.a = 1.0 - texel0.r;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/shaders/maskedTextureFragmentShader.glsl b/vcl/opengl/shaders/maskedTextureFragmentShader.glsl
new file mode 100644
index 000000000..31c793897
--- /dev/null
+++ b/vcl/opengl/shaders/maskedTextureFragmentShader.glsl
@@ -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/.
+ */
+
+#version 130
+
+/*precision mediump float;*/
+varying vec2 tex_coord;
+varying vec2 mask_coord;
+uniform sampler2D sampler;
+uniform sampler2D mask;
+
+void main()
+{
+ vec4 texel0, texel1;
+ texel0 = texture2D(sampler, tex_coord);
+ texel1 = texture2D(mask, mask_coord);
+ gl_FragColor = texel0;
+ gl_FragColor.a = 1.0 - texel1.r;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/shaders/maskedTextureVertexShader.glsl b/vcl/opengl/shaders/maskedTextureVertexShader.glsl
new file mode 100644
index 000000000..6b5f327da
--- /dev/null
+++ b/vcl/opengl/shaders/maskedTextureVertexShader.glsl
@@ -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/.
+ */
+
+#version 130
+
+attribute vec4 position;
+attribute vec2 tex_coord_in;
+attribute vec2 mask_coord_in;
+varying vec2 tex_coord;
+varying vec2 mask_coord;
+uniform mat4 mvp;
+
+void main()
+{
+ gl_Position = mvp * position;
+ tex_coord = tex_coord_in;
+ mask_coord = mask_coord_in;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/shaders/radialGradientFragmentShader.glsl b/vcl/opengl/shaders/radialGradientFragmentShader.glsl
new file mode 100644
index 000000000..94a86eb95
--- /dev/null
+++ b/vcl/opengl/shaders/radialGradientFragmentShader.glsl
@@ -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/.
+ */
+
+#version 120
+
+uniform vec4 start_color;
+uniform vec4 end_color;
+uniform vec2 center;
+varying vec2 tex_coord;
+
+void main(void)
+{
+ gl_FragColor = mix(end_color, start_color,
+ clamp(distance(tex_coord, center), 0.0, 1.0));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/shaders/replaceColorFragmentShader.glsl b/vcl/opengl/shaders/replaceColorFragmentShader.glsl
new file mode 100644
index 000000000..24f6008e2
--- /dev/null
+++ b/vcl/opengl/shaders/replaceColorFragmentShader.glsl
@@ -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/.
+ */
+
+#version 130
+
+varying vec2 tex_coord;
+uniform sampler2D sampler;
+uniform vec4 search_color;
+uniform vec4 replace_color;
+uniform float epsilon;
+
+void main() {
+ vec4 texel = texture2D(sampler, tex_coord);
+ vec4 diff = clamp(abs(texel - search_color) - epsilon, 0.0, 1.0);
+ float bump = max(0.0, 1.0 - ceil(diff.x + diff.y + diff.z));
+ gl_FragColor = texel + bump * (replace_color - search_color);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/shaders/solidFragmentShader.glsl b/vcl/opengl/shaders/solidFragmentShader.glsl
new file mode 100644
index 000000000..b77e2578d
--- /dev/null
+++ b/vcl/opengl/shaders/solidFragmentShader.glsl
@@ -0,0 +1,19 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#version 130
+
+/*precision mediump float;*/
+
+uniform vec4 color;
+void main() {
+ gl_FragColor = color;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/shaders/textureFragmentShader.glsl b/vcl/opengl/shaders/textureFragmentShader.glsl
new file mode 100644
index 000000000..b1fedcba5
--- /dev/null
+++ b/vcl/opengl/shaders/textureFragmentShader.glsl
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#version 130
+
+/* precision mediump float; */
+varying vec2 tex_coord;
+uniform sampler2D sampler;
+
+void main() {
+ gl_FragColor = texture2D(sampler, tex_coord);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/shaders/textureVertexShader.glsl b/vcl/opengl/shaders/textureVertexShader.glsl
new file mode 100644
index 000000000..7fbdcf1eb
--- /dev/null
+++ b/vcl/opengl/shaders/textureVertexShader.glsl
@@ -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/.
+ */
+
+#version 130
+
+attribute vec4 position;
+attribute vec2 tex_coord_in;
+varying vec2 tex_coord;
+uniform mat4 mvp;
+
+void main() {
+ gl_Position = mvp * position;
+ tex_coord = tex_coord_in;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/shaders/transformedTextureVertexShader.glsl b/vcl/opengl/shaders/transformedTextureVertexShader.glsl
new file mode 100644
index 000000000..3d67f78e0
--- /dev/null
+++ b/vcl/opengl/shaders/transformedTextureVertexShader.glsl
@@ -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/.
+ */
+
+#version 130
+
+attribute vec4 position;
+attribute vec2 tex_coord_in;
+attribute vec2 mask_coord_in;
+uniform vec2 viewport;
+uniform mat4 transform;
+uniform mat4 mvp;
+varying vec2 tex_coord;
+varying vec2 mask_coord;
+
+void main() {
+ vec4 pos = mvp * transform * position;
+ gl_Position = pos;
+ tex_coord = tex_coord_in;
+ mask_coord = mask_coord_in;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/texture.cxx b/vcl/opengl/texture.cxx
new file mode 100644
index 000000000..9f4acc0fc
--- /dev/null
+++ b/vcl/opengl/texture.cxx
@@ -0,0 +1,606 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/stream.hxx>
+#include <vcl/opengl/OpenGLContext.hxx>
+#include <vcl/opengl/OpenGLHelper.hxx>
+
+#include <svdata.hxx>
+
+#include <vcl/pngwrite.hxx>
+
+#include <opengl/framebuffer.hxx>
+#include <opengl/texture.hxx>
+#include <opengl/zone.hxx>
+#include <opengl/RenderState.hxx>
+
+namespace
+{
+
+constexpr GLenum constInternalFormat = GL_RGBA8;
+
+} // end anonymous namespace
+
+// texture with allocated size
+ImplOpenGLTexture::ImplOpenGLTexture( int nWidth, int nHeight, bool bAllocate ) :
+ mnTexture( 0 ),
+ mnWidth( nWidth ),
+ mnHeight( nHeight ),
+ mnFilter( GL_NEAREST ),
+ mnOptStencil( 0 )
+{
+ OpenGLVCLContextZone aContextZone;
+
+ auto& rState = OpenGLContext::getVCLContext()->state();
+ TextureState::generate(mnTexture);
+ rState.texture().active(0);
+ rState.texture().bind(mnTexture);
+
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
+ CHECK_GL_ERROR();
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
+ CHECK_GL_ERROR();
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
+ CHECK_GL_ERROR();
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
+ CHECK_GL_ERROR();
+ if( bAllocate )
+ {
+#ifdef DBG_UTIL
+ std::vector< sal_uInt8 > buffer;
+ buffer.resize( nWidth * nHeight * 4 );
+ for( int i = 0; i < nWidth * nHeight; ++i )
+ { // pre-fill the texture with deterministic garbage
+ bool odd = (i & 0x01);
+ buffer[ i * 4 ] = odd ? 0x40 : 0xBF;
+ buffer[ i * 4 + 1 ] = 0x80;
+ buffer[ i * 4 + 2 ] = odd ? 0xBF : 0x40;
+ buffer[ i * 4 + 3 ] = 0xFF;
+ }
+ glTexImage2D( GL_TEXTURE_2D, 0, constInternalFormat, nWidth, nHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer.data());
+#else
+ glTexImage2D( GL_TEXTURE_2D, 0, constInternalFormat, nWidth, nHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr );
+#endif
+ CHECK_GL_ERROR();
+ }
+
+ VCL_GL_INFO( "OpenGLTexture " << mnTexture << " " << nWidth << "x" << nHeight << " allocate" );
+}
+
+// texture with content retrieved from FBO
+ImplOpenGLTexture::ImplOpenGLTexture( int nX, int nY, int nWidth, int nHeight ) :
+ mnTexture( 0 ),
+ mnWidth( nWidth ),
+ mnHeight( nHeight ),
+ mnFilter( GL_NEAREST ),
+ mnOptStencil( 0 )
+{
+ OpenGLVCLContextZone aContextZone;
+
+ // FIXME We need the window height here
+ // nY = GetHeight() - nHeight - nY;
+
+ auto& rState = OpenGLContext::getVCLContext()->state();
+ TextureState::generate(mnTexture);
+ rState.texture().active(0);
+ rState.texture().bind(mnTexture);
+
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
+ CHECK_GL_ERROR();
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
+ CHECK_GL_ERROR();
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
+ CHECK_GL_ERROR();
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
+ CHECK_GL_ERROR();
+ glCopyTexImage2D( GL_TEXTURE_2D, 0, constInternalFormat, nX, nY, nWidth, nHeight, 0 );
+ CHECK_GL_ERROR();
+
+ VCL_GL_INFO( "OpenGLTexture " << mnTexture << " " << nWidth << "x" << nHeight << " from x" << nX << ", y" << nY );
+}
+
+// texture from buffer data
+ImplOpenGLTexture::ImplOpenGLTexture( int nWidth, int nHeight, int nFormat, int nType, void const * pData ) :
+ mnTexture( 0 ),
+ mnWidth( nWidth ),
+ mnHeight( nHeight ),
+ mnFilter( GL_NEAREST ),
+ mnOptStencil( 0 )
+{
+ OpenGLVCLContextZone aContextZone;
+
+ auto& rState = OpenGLContext::getVCLContext()->state();
+ TextureState::generate(mnTexture);
+ rState.texture().active(0);
+ rState.texture().bind(mnTexture);
+
+ glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );
+ CHECK_GL_ERROR();
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
+ CHECK_GL_ERROR();
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
+ CHECK_GL_ERROR();
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
+ CHECK_GL_ERROR();
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
+ CHECK_GL_ERROR();
+ glTexImage2D( GL_TEXTURE_2D, 0, constInternalFormat, mnWidth, mnHeight, 0, nFormat, nType, pData );
+ CHECK_GL_ERROR();
+
+ VCL_GL_INFO( "OpenGLTexture " << mnTexture << " " << nWidth << "x" << nHeight << " from data" );
+}
+
+GLuint ImplOpenGLTexture::AddStencil()
+{
+ assert( mnOptStencil == 0 );
+
+ glGenRenderbuffers( 1, &mnOptStencil );
+ CHECK_GL_ERROR();
+ glBindRenderbuffer( GL_RENDERBUFFER, mnOptStencil );
+ CHECK_GL_ERROR();
+ VCL_GL_INFO( "Allocate stencil " << mnWidth << " x " << mnHeight );
+ glRenderbufferStorage( GL_RENDERBUFFER, GL_STENCIL_INDEX,
+ mnWidth, mnHeight );
+ CHECK_GL_ERROR();
+ glBindRenderbuffer(GL_RENDERBUFFER, 0);
+ CHECK_GL_ERROR();
+
+ return mnOptStencil;
+}
+
+ImplOpenGLTexture::~ImplOpenGLTexture()
+{
+ VCL_GL_INFO( "~OpenGLTexture " << mnTexture );
+ if( mnTexture != 0 )
+ {
+ // During shutdown GL is already de-initialized, so we should not try to create a new context.
+ OpenGLZone aZone;
+ rtl::Reference<OpenGLContext> xContext = OpenGLContext::getVCLContext(false);
+ if( xContext.is() )
+ {
+ // FIXME: this is really not optimal performance-wise.
+
+ // Check we have been correctly un-bound from all framebuffers.
+ ImplSVData* pSVData = ImplGetSVData();
+ rtl::Reference<OpenGLContext> pContext = pSVData->maGDIData.mpLastContext;
+
+ if( pContext.is() )
+ {
+ pContext->makeCurrent();
+ pContext->UnbindTextureFromFramebuffers( mnTexture );
+ }
+
+ if( mnOptStencil != 0 )
+ {
+ glDeleteRenderbuffers( 1, &mnOptStencil );
+ mnOptStencil = 0;
+ }
+ auto& rState = pContext->state();
+ rState.texture().unbindAndDelete(mnTexture);
+ mnTexture = 0;
+ }
+ else
+ {
+ mnOptStencil = 0;
+ mnTexture = 0;
+ }
+ }
+}
+
+bool ImplOpenGLTexture::InsertBuffer(int nX, int nY, int nWidth, int nHeight, int nFormat, int nType, sal_uInt8 const * pData)
+{
+ if (!pData || mnTexture == 0)
+ return false;
+
+ rtl::Reference<OpenGLContext> xContext = OpenGLContext::getVCLContext();
+ xContext->state().texture().active(0);
+ xContext->state().texture().bind(mnTexture);
+
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+ CHECK_GL_ERROR();
+ glTexSubImage2D(GL_TEXTURE_2D, 0, nX, mnHeight - nY - nHeight, nWidth, nHeight, nFormat, nType, pData);
+ CHECK_GL_ERROR();
+
+ VCL_GL_INFO( "OpenGLTexture " << mnTexture << " Insert buff. to " << nX << " " << nY
+ << " size " << nWidth << "x" << nHeight << " from data" );
+
+ return true;
+}
+
+void ImplOpenGLTexture::InitializeSlotMechanism(int nInitialSlotSize)
+{
+ if (mpSlotReferences)
+ return;
+
+ mpSlotReferences.reset(new std::vector<int>(nInitialSlotSize, 0));
+}
+
+void ImplOpenGLTexture::IncreaseRefCount(int nSlotNumber)
+{
+ if (mpSlotReferences && nSlotNumber >= 0)
+ {
+ if (nSlotNumber >= int(mpSlotReferences->size()))
+ mpSlotReferences->resize(nSlotNumber + 1, 0);
+
+ mpSlotReferences->at(nSlotNumber)++;
+ }
+}
+
+void ImplOpenGLTexture::DecreaseRefCount(int nSlotNumber)
+{
+ if (mpSlotReferences && nSlotNumber >= 0)
+ {
+ if (nSlotNumber >= int(mpSlotReferences->size()))
+ mpSlotReferences->resize(nSlotNumber, 0);
+
+ mpSlotReferences->at(nSlotNumber)--;
+
+ if (mpSlotReferences->at(nSlotNumber) == 0 && mFunctSlotDeallocateCallback)
+ {
+ mFunctSlotDeallocateCallback(nSlotNumber);
+ }
+ }
+}
+
+OpenGLTexture::OpenGLTexture() :
+ maRect( 0, 0, 0, 0 ),
+ mpImpl(),
+ mnSlotNumber(-1)
+{
+}
+
+OpenGLTexture::OpenGLTexture(const std::shared_ptr<ImplOpenGLTexture>& rpImpl, tools::Rectangle aRectangle, int nSlotNumber)
+ : maRect(aRectangle)
+ , mpImpl(rpImpl)
+ , mnSlotNumber(nSlotNumber)
+{
+ if (mpImpl)
+ mpImpl->IncreaseRefCount(nSlotNumber);
+}
+
+OpenGLTexture::OpenGLTexture( int nWidth, int nHeight, bool bAllocate )
+ : maRect( Point( 0, 0 ), Size( nWidth, nHeight ) )
+ , mpImpl(std::make_shared<ImplOpenGLTexture>(nWidth, nHeight, bAllocate))
+ , mnSlotNumber(-1)
+{
+}
+
+OpenGLTexture::OpenGLTexture( int nX, int nY, int nWidth, int nHeight )
+ : maRect( Point( 0, 0 ), Size( nWidth, nHeight ) )
+ , mpImpl(std::make_shared<ImplOpenGLTexture>(nX, nY, nWidth, nHeight))
+ , mnSlotNumber(-1)
+{
+}
+
+OpenGLTexture::OpenGLTexture( int nWidth, int nHeight, int nFormat, int nType, void const * pData )
+ : maRect( Point( 0, 0 ), Size( nWidth, nHeight ) )
+ , mpImpl(std::make_shared<ImplOpenGLTexture>(nWidth, nHeight, nFormat, nType, pData))
+ , mnSlotNumber(-1)
+{
+
+}
+
+OpenGLTexture::OpenGLTexture(const OpenGLTexture& rTexture)
+ : maRect(rTexture.maRect)
+ , mpImpl(rTexture.mpImpl)
+ , mnSlotNumber(rTexture.mnSlotNumber)
+{
+ if (mpImpl)
+ mpImpl->IncreaseRefCount(mnSlotNumber);
+}
+
+OpenGLTexture::OpenGLTexture(OpenGLTexture&& rTexture) noexcept
+ : maRect(rTexture.maRect)
+ , mpImpl(std::move(rTexture.mpImpl))
+ , mnSlotNumber(rTexture.mnSlotNumber)
+{
+}
+
+OpenGLTexture::OpenGLTexture( const OpenGLTexture& rTexture,
+ int nX, int nY, int nWidth, int nHeight )
+{
+ maRect = tools::Rectangle( Point( rTexture.maRect.Left() + nX, rTexture.maRect.Top() + nY ),
+ Size( nWidth, nHeight ) );
+ mpImpl = rTexture.mpImpl;
+ mnSlotNumber = rTexture.mnSlotNumber;
+ if (mpImpl)
+ mpImpl->IncreaseRefCount(mnSlotNumber);
+ VCL_GL_INFO( "Copying texture " << Id() << " [" << maRect.Left() << "," << maRect.Top() << "] " << GetWidth() << "x" << GetHeight() );
+}
+
+OpenGLTexture::~OpenGLTexture()
+{
+ if (mpImpl)
+ mpImpl->DecreaseRefCount(mnSlotNumber);
+}
+
+bool OpenGLTexture::IsUnique() const
+{
+ return !mpImpl || (mpImpl.use_count() == 1);
+}
+
+GLuint OpenGLTexture::Id() const
+{
+ if (mpImpl)
+ return mpImpl->mnTexture;
+ return 0;
+}
+
+int OpenGLTexture::GetWidth() const
+{
+ return maRect.GetWidth();
+}
+
+int OpenGLTexture::GetHeight() const
+{
+ return maRect.GetHeight();
+}
+
+GLuint OpenGLTexture::StencilId() const
+{
+ return mpImpl ? mpImpl->mnOptStencil : 0;
+}
+
+GLuint OpenGLTexture::AddStencil()
+{
+ if (mpImpl)
+ return mpImpl->AddStencil();
+ else
+ return 0;
+}
+
+void OpenGLTexture::GetCoord( GLfloat* pCoord, const SalTwoRect& rPosAry, bool bInverted ) const
+{
+ VCL_GL_INFO( "Getting coord " << Id() << " [" << maRect.Left() << "," << maRect.Top() << "] " << GetWidth() << "x" << GetHeight() );
+
+ if (!IsValid())
+ {
+ pCoord[0] = pCoord[1] = pCoord[2] = pCoord[3] = 0.0f;
+ pCoord[4] = pCoord[5] = pCoord[6] = pCoord[7] = 0.0f;
+ return;
+ }
+
+ pCoord[0] = pCoord[2] = (maRect.Left() + rPosAry.mnSrcX) / static_cast<double>(mpImpl->mnWidth);
+ pCoord[4] = pCoord[6] = (maRect.Left() + rPosAry.mnSrcX + rPosAry.mnSrcWidth) / static_cast<double>(mpImpl->mnWidth);
+
+ if( !bInverted )
+ {
+ pCoord[3] = pCoord[5] = 1.0f - (maRect.Top() + rPosAry.mnSrcY) / static_cast<double>(mpImpl->mnHeight);
+ pCoord[1] = pCoord[7] = 1.0f - (maRect.Top() + rPosAry.mnSrcY + rPosAry.mnSrcHeight) / static_cast<double>(mpImpl->mnHeight);
+ }
+ else
+ {
+ pCoord[1] = pCoord[7] = 1.0f - (maRect.Top() + rPosAry.mnSrcY) / static_cast<double>(mpImpl->mnHeight);
+ pCoord[3] = pCoord[5] = 1.0f - (maRect.Top() + rPosAry.mnSrcY + rPosAry.mnSrcHeight) / static_cast<double>(mpImpl->mnHeight);
+ }
+}
+
+void OpenGLTexture::GetTextureRect(const SalTwoRect& rPosAry, GLfloat& x1, GLfloat& x2, GLfloat& y1, GLfloat& y2) const
+{
+ if (IsValid())
+ {
+ double fTextureWidth(mpImpl->mnWidth);
+ double fTextureHeight(mpImpl->mnHeight);
+
+ x1 = (maRect.Left() + rPosAry.mnSrcX) / fTextureWidth;
+ x2 = (maRect.Left() + rPosAry.mnSrcX + rPosAry.mnSrcWidth) / fTextureWidth;
+
+ y1 = 1.0f - (maRect.Top() + rPosAry.mnSrcY) / fTextureHeight;
+ y2 = 1.0f - (maRect.Top() + rPosAry.mnSrcY + rPosAry.mnSrcHeight) / fTextureHeight;
+ }
+}
+
+template <>
+void OpenGLTexture::FillCoords<GL_TRIANGLE_FAN>(std::vector<GLfloat>& rCoords, const SalTwoRect& rPosAry) const
+{
+ GLfloat x1 = 0.0f;
+ GLfloat x2 = 0.0f;
+ GLfloat y1 = 0.0f;
+ GLfloat y2 = 0.0f;
+
+ GetTextureRect(rPosAry, x1, x2, y1, y2);
+
+ rCoords.insert(rCoords.end(), {
+ x1, y2, x1, y1,
+ x2, y1, x2, y2
+ });
+}
+
+template <>
+void OpenGLTexture::FillCoords<GL_TRIANGLES>(std::vector<GLfloat>& rCoords, const SalTwoRect& rPosAry) const
+{
+ GLfloat x1 = 0.0f;
+ GLfloat x2 = 0.0f;
+ GLfloat y1 = 0.0f;
+ GLfloat y2 = 0.0f;
+
+ GetTextureRect(rPosAry, x1, x2, y1, y2);
+
+ rCoords.insert(rCoords.end(), {
+ x1, y1, x2, y1, x1, y2,
+ x1, y2, x2, y1, x2, y2
+ });
+}
+
+void OpenGLTexture::GetWholeCoord( GLfloat* pCoord ) const
+{
+ if( GetWidth() != mpImpl->mnWidth || GetHeight() != mpImpl->mnHeight )
+ {
+ pCoord[0] = pCoord[2] = maRect.Left() / static_cast<double>(mpImpl->mnWidth);
+ pCoord[4] = pCoord[6] = maRect.Right() / static_cast<double>(mpImpl->mnWidth);
+ pCoord[3] = pCoord[5] = 1.0f - maRect.Top() / static_cast<double>(mpImpl->mnHeight);
+ pCoord[1] = pCoord[7] = 1.0f - maRect.Bottom() / static_cast<double>(mpImpl->mnHeight);
+ }
+ else
+ {
+ pCoord[0] = pCoord[2] = 0;
+ pCoord[4] = pCoord[6] = 1;
+ pCoord[1] = pCoord[7] = 0;
+ pCoord[3] = pCoord[5] = 1;
+ }
+}
+
+GLenum OpenGLTexture::GetFilter() const
+{
+ if( mpImpl )
+ return mpImpl->mnFilter;
+ return GL_NEAREST;
+}
+
+bool OpenGLTexture::CopyData(int nWidth, int nHeight, int nFormat, int nType, sal_uInt8 const * pData)
+{
+ if (!pData || !IsValid())
+ return false;
+
+ int nX = maRect.Left();
+ int nY = maRect.Top();
+
+ return mpImpl->InsertBuffer(nX, nY, nWidth, nHeight, nFormat, nType, pData);
+}
+
+void OpenGLTexture::SetFilter( GLenum nFilter )
+{
+ if( mpImpl )
+ {
+ mpImpl->mnFilter = nFilter;
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, nFilter );
+ CHECK_GL_ERROR();
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, nFilter );
+ CHECK_GL_ERROR();
+ }
+}
+
+void OpenGLTexture::Bind()
+{
+ if (IsValid())
+ {
+ OpenGLContext::getVCLContext()->state().texture().bind(mpImpl->mnTexture);
+ }
+ else
+ VCL_GL_INFO( "OpenGLTexture::Binding invalid texture" );
+
+ CHECK_GL_ERROR();
+}
+
+void OpenGLTexture::Unbind()
+{
+ if (IsValid())
+ {
+ OpenGLContext::getVCLContext()->state().texture().unbind(mpImpl->mnTexture);
+ }
+}
+
+void OpenGLTexture::SaveToFile(const OUString& rFileName)
+{
+ std::vector<sal_uInt8> aBuffer(GetWidth() * GetHeight() * 4);
+ Read(OpenGLHelper::OptimalBufferFormat(), GL_UNSIGNED_BYTE, aBuffer.data());
+ BitmapEx aBitmap = OpenGLHelper::ConvertBufferToBitmapEx(aBuffer.data(), GetWidth(), GetHeight());
+ try
+ {
+ vcl::PNGWriter aWriter(aBitmap);
+ SvFileStream sOutput(rFileName, StreamMode::WRITE);
+ aWriter.Write(sOutput);
+ sOutput.Close();
+ }
+ catch (...)
+ {
+ SAL_WARN("vcl.opengl", "Error writing png to " << rFileName);
+ }
+}
+
+void OpenGLTexture::Read( GLenum nFormat, GLenum nType, sal_uInt8* pData )
+{
+ if (!IsValid())
+ {
+ SAL_WARN( "vcl.opengl", "Can't read invalid texture" );
+ return;
+ }
+
+ OpenGLVCLContextZone aContextZone;
+
+ VCL_GL_INFO( "Reading texture " << Id() << " " << GetWidth() << "x" << GetHeight() );
+
+ if( GetWidth() == mpImpl->mnWidth && GetHeight() == mpImpl->mnHeight )
+ {
+ Bind();
+ glPixelStorei( GL_PACK_ALIGNMENT, 1 );
+ CHECK_GL_ERROR();
+ // XXX: Call not available with GLES 2.0
+ glGetTexImage( GL_TEXTURE_2D, 0, nFormat, nType, pData );
+ CHECK_GL_ERROR();
+ Unbind();
+ }
+ else
+ {
+ long nWidth = maRect.GetWidth();
+ long nHeight = maRect.GetHeight();
+ long nX = maRect.Left();
+ long nY = mpImpl->mnHeight - maRect.Top() - nHeight;
+
+ // Retrieve current context
+ ImplSVData* pSVData = ImplGetSVData();
+ rtl::Reference<OpenGLContext> pContext = pSVData->maGDIData.mpLastContext;
+ OpenGLFramebuffer* pFramebuffer = pContext->AcquireFramebuffer(*this);
+ glPixelStorei(GL_PACK_ALIGNMENT, 1);
+ CHECK_GL_ERROR();
+ glReadPixels(nX, nY, nWidth, nHeight, nFormat, nType, pData);
+ CHECK_GL_ERROR();
+ OpenGLContext::ReleaseFramebuffer(pFramebuffer);
+ }
+}
+
+OpenGLTexture::operator bool() const
+{
+ return IsValid();
+}
+
+OpenGLTexture& OpenGLTexture::operator=(const OpenGLTexture& rTexture)
+{
+ OpenGLTexture aTemp(rTexture);
+ *this = std::move(aTemp);
+ return *this;
+}
+
+OpenGLTexture& OpenGLTexture::operator=(OpenGLTexture&& rTexture)
+{
+ if (mpImpl)
+ mpImpl->DecreaseRefCount(mnSlotNumber);
+
+ maRect = rTexture.maRect;
+ mpImpl = std::move(rTexture.mpImpl);
+ mnSlotNumber = rTexture.mnSlotNumber;
+
+ return *this;
+}
+
+bool OpenGLTexture::operator==( const OpenGLTexture& rTexture ) const
+{
+ return (mpImpl == rTexture.mpImpl
+ && maRect == rTexture.maRect
+ && mnSlotNumber == rTexture.mnSlotNumber);
+}
+
+bool OpenGLTexture::operator!=( const OpenGLTexture& rTexture ) const
+{
+ return !( *this == rTexture );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/win/WinDeviceInfo.cxx b/vcl/opengl/win/WinDeviceInfo.cxx
new file mode 100644
index 000000000..301c8e74d
--- /dev/null
+++ b/vcl/opengl/win/WinDeviceInfo.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/.
+ */
+
+#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 <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();
+}
+
+WinOpenGLDeviceInfo::~WinOpenGLDeviceInfo()
+{
+}
+
+static OUString getBlacklistFile()
+{
+ OUString url("$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER);
+ rtl::Bootstrap::expandMacros(url);
+
+ return url + "/opengl/opengl_blacklist_windows.xml";
+}
+
+bool WinOpenGLDeviceInfo::FindBlocklistedDeviceInList()
+{
+ return DriverBlocklist::IsDeviceBlocked( getBlacklistFile(), 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, const OUString & rVal)
+{
+ rStrm.WriteCharPtr(pKey);
+ rStrm.WriteCharPtr(": ");
+ rStrm.WriteOString(OUStringToOString(rVal, RTL_TEXTENCODING_UTF8));
+ rStrm.WriteChar('\n');
+}
+
+}
+
+bool WinOpenGLDeviceInfo::isDeviceBlocked()
+{
+ CrashReporter::addKeyValue("OpenGLVendor", maAdapterVendorID, CrashReporter::AddItem);
+ CrashReporter::addKeyValue("OpenGLDevice", maAdapterDeviceID, CrashReporter::AddItem);
+ CrashReporter::addKeyValue("OpenGLDriver", maDriverVersion, CrashReporter::Write);
+
+ SAL_INFO("vcl.opengl", maDriverVersion);
+ SAL_INFO("vcl.opengl", maDriverDate);
+ SAL_INFO("vcl.opengl", maDeviceID);
+ SAL_INFO("vcl.opengl", maAdapterVendorID);
+ SAL_INFO("vcl.opengl", maAdapterDeviceID);
+ SAL_INFO("vcl.opengl", maAdapterSubsysID);
+ SAL_INFO("vcl.opengl", maDeviceKey);
+ SAL_INFO("vcl.opengl", maDeviceString);
+
+ OUString aCacheFolder = getCacheFolder();
+
+ OUString aCacheFile(aCacheFolder + "/opengl_device.log");
+ SvFileStream aOpenGLLogFile(aCacheFile, StreamMode::WRITE|StreamMode::TRUNC);
+
+ writeToLog(aOpenGLLogFile, "DriverVersion", maDriverVersion);
+ writeToLog(aOpenGLLogFile, "DriverDate", maDriverDate);
+ writeToLog(aOpenGLLogFile, "DeviceID", maDeviceID);
+ writeToLog(aOpenGLLogFile, "AdapterVendorID", maAdapterVendorID);
+ writeToLog(aOpenGLLogFile, "AdapterDeviceID", maAdapterDeviceID);
+ writeToLog(aOpenGLLogFile, "AdapterSubsysID", maAdapterSubsysID);
+ writeToLog(aOpenGLLogFile, "DeviceKey", maDeviceKey);
+ writeToLog(aOpenGLLogFile, "DeviceString", maDeviceString);
+
+ // Check if the device is blocked from the downloaded blocklist. If not, check
+ // the static list after that. This order is used so that we can later escape
+ // out of static blocks (i.e. if we were wrong or something was patched, we
+ // can back out our static block without doing a release).
+ if (mbRDP)
+ {
+ SAL_WARN("vcl.opengl", "all OpenGL blocked for RDP sessions");
+ return true;
+ }
+
+ return FindBlocklistedDeviceInList();
+}
+
+void WinOpenGLDeviceInfo::GetData()
+{
+ DISPLAY_DEVICEW displayDevice;
+ displayDevice.cb = sizeof(displayDevice);
+
+ int deviceIndex = 0;
+
+ while (EnumDisplayDevicesW(nullptr, deviceIndex, &displayDevice, 0))
+ {
+ if (displayDevice.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)
+ {
+ break;
+ }
+ deviceIndex++;
+ }
+
+ // make sure the string is null terminated
+ // (using the term "null" here to mean a zero UTF-16 unit)
+ if (wcsnlen(displayDevice.DeviceKey, SAL_N_ELEMENTS(displayDevice.DeviceKey))
+ == SAL_N_ELEMENTS(displayDevice.DeviceKey))
+ {
+ // we did not find a null
+ SAL_WARN("vcl.opengl", "string not null terminated");
+ return;
+ }
+
+ /* DeviceKey is "reserved" according to MSDN so we'll be careful with it */
+ /* check that DeviceKey begins with DEVICE_KEY_PREFIX */
+ /* some systems have a DeviceKey starting with \REGISTRY\Machine\ so we need to compare case insensitively */
+ if (_wcsnicmp(displayDevice.DeviceKey, DEVICE_KEY_PREFIX, SAL_N_ELEMENTS(DEVICE_KEY_PREFIX)-1) != 0)
+ {
+ SAL_WARN("vcl.opengl", "incorrect DeviceKey");
+ return;
+ }
+
+ // chop off DEVICE_KEY_PREFIX
+ maDeviceKey = o3tl::toU(displayDevice.DeviceKey) + SAL_N_ELEMENTS(DEVICE_KEY_PREFIX)-1;
+
+ maDeviceID = o3tl::toU(displayDevice.DeviceID);
+ maDeviceString = o3tl::toU(displayDevice.DeviceString);
+
+ if (maDeviceID.isEmpty() &&
+ (maDeviceString == "RDPDD Chained DD" ||
+ (maDeviceString == "RDPUDD Chained DD")))
+ {
+ // we need to block RDP as it does not provide OpenGL 2.1+
+ mbRDP = true;
+ SAL_WARN("vcl.opengl", "RDP => blocked");
+ return;
+ }
+
+ /* create a device information set composed of the current display device */
+ HDEVINFO devinfo = SetupDiGetClassDevsW(nullptr, o3tl::toW(maDeviceID.getStr()), nullptr,
+ DIGCF_PRESENT | DIGCF_PROFILE | DIGCF_ALLCLASSES);
+
+ if (devinfo != INVALID_HANDLE_VALUE)
+ {
+ HKEY key;
+ LONG result;
+ WCHAR value[255];
+ DWORD dwcbData;
+ SP_DEVINFO_DATA devinfoData;
+ DWORD memberIndex = 0;
+
+ devinfoData.cbSize = sizeof(devinfoData);
+ /* enumerate device information elements in the device information set */
+ while (SetupDiEnumDeviceInfo(devinfo, memberIndex++, &devinfoData))
+ {
+ /* get a string that identifies the device's driver key */
+ if (SetupDiGetDeviceRegistryPropertyW(devinfo,
+ &devinfoData,
+ SPDRP_DRIVER,
+ nullptr,
+ reinterpret_cast<PBYTE>(value),
+ sizeof(value),
+ nullptr))
+ {
+ OUString driverKey(OUStringLiteral("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(OUStringLiteral("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/opengl/win/gdiimpl.cxx b/vcl/opengl/win/gdiimpl.cxx
new file mode 100644
index 000000000..eabfe8a09
--- /dev/null
+++ b/vcl/opengl/win/gdiimpl.cxx
@@ -0,0 +1,898 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 <thread>
+#include <opengl/win/gdiimpl.hxx>
+#include <vcl/opengl/OpenGLHelper.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:
+ bool init( HDC hDC, HWND hWnd );
+ 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();
+}
+
+bool WinOpenGLContext::init(HDC hDC, HWND hWnd)
+{
+ if (isInitialized())
+ return true;
+
+ m_aGLWin.hDC = hDC;
+ m_aGLWin.hWnd = hWnd;
+ return ImplInit();
+}
+
+void WinOpenGLContext::initWindow()
+{
+ if( !m_pChildWindow )
+ {
+ SystemWindowData winData = generateWinData(mpWindow, false);
+ m_pChildWindow = VclPtr<SystemChildWindow>::Create(mpWindow, 0, &winData, false);
+ }
+
+ if (m_pChildWindow)
+ {
+ InitChildWindow(m_pChildWindow.get());
+ const SystemEnvData* sysData(m_pChildWindow->GetSystemData());
+ m_aGLWin.hWnd = sysData->hWnd;
+ }
+
+ m_aGLWin.hDC = GetDC(m_aGLWin.hWnd);
+}
+
+void WinOpenGLContext::destroyCurrentContext()
+{
+ if (m_aGLWin.hRC)
+ {
+ std::vector<HGLRC>::iterator itr = std::remove(g_vShareList.begin(), g_vShareList.end(), m_aGLWin.hRC);
+ if (itr != g_vShareList.end())
+ g_vShareList.erase(itr);
+
+ if (wglGetCurrentContext() != nullptr)
+ {
+ wglMakeCurrent(nullptr, nullptr);
+ g_bAnyCurrent = false;
+ }
+ wglDeleteContext( m_aGLWin.hRC );
+ ReleaseDC( m_aGLWin.hWnd, m_aGLWin.hDC );
+ m_aGLWin.hRC = nullptr;
+ }
+}
+
+static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
+{
+ switch (message)
+ {
+ case WM_CREATE:
+ return 0;
+ case WM_CLOSE:
+ PostQuitMessage(0);
+ return 0;
+ case WM_DESTROY:
+ return 0;
+ default:
+ return DefWindowProcW(hwnd, message, wParam, lParam);
+ }
+}
+
+static bool InitTempWindow(HWND& hwnd, int width, int height, const PIXELFORMATDESCRIPTOR& inPfd, GLWinWindow& glWin)
+{
+ OpenGLZone aZone;
+
+ PIXELFORMATDESCRIPTOR pfd = inPfd;
+ int ret;
+ WNDCLASSW wc;
+ wc.style = 0;
+ wc.lpfnWndProc = WndProc;
+ wc.cbClsExtra = wc.cbWndExtra = 0;
+ wc.hInstance = nullptr;
+ wc.hIcon = nullptr;
+ wc.hCursor = nullptr;
+ wc.hbrBackground = nullptr;
+ wc.lpszMenuName = nullptr;
+ wc.lpszClassName = L"GLRenderer";
+ RegisterClassW(&wc);
+ hwnd = CreateWindowW(wc.lpszClassName, nullptr, WS_DISABLED, 0, 0, width, height, nullptr, nullptr, wc.hInstance, nullptr);
+ glWin.hDC = GetDC(hwnd);
+
+ int nPixelFormat = ChoosePixelFormat(glWin.hDC, &pfd);
+ if (!nPixelFormat)
+ {
+ ReleaseDC(hwnd, glWin.hDC);
+ DestroyWindow(hwnd);
+ return false;
+ }
+ ret = SetPixelFormat(glWin.hDC, nPixelFormat, &pfd);
+ if(!ret)
+ {
+ ReleaseDC(hwnd, glWin.hDC);
+ DestroyWindow(hwnd);
+ return false;
+ }
+ glWin.hRC = wglCreateContext(glWin.hDC);
+ if(!(glWin.hRC))
+ {
+ ReleaseDC(hwnd, glWin.hDC);
+ DestroyWindow(hwnd);
+ return false;
+ }
+ ret = wglMakeCurrent(glWin.hDC, glWin.hRC);
+ if(!ret)
+ {
+ wglMakeCurrent(nullptr, nullptr);
+ g_bAnyCurrent = false;
+ wglDeleteContext(glWin.hRC);
+ ReleaseDC(hwnd, glWin.hDC);
+ DestroyWindow(hwnd);
+ return false;
+ }
+ g_bAnyCurrent = false;
+
+ return true;
+}
+
+static bool WGLisExtensionSupported(const char *extension)
+{
+ OpenGLZone aZone;
+
+ const size_t extlen = strlen(extension);
+ const char *supported = nullptr;
+
+ // Try to use wglGetExtensionStringARB on current DC, if possible
+ PROC wglGetExtString = wglGetProcAddress("wglGetExtensionsStringARB");
+
+ if (wglGetExtString)
+ supported = reinterpret_cast<char*(__stdcall*)(HDC)>(wglGetExtString)(wglGetCurrentDC());
+ // If that failed, try standard OpenGL extensions string
+ if (supported == nullptr)
+ supported = reinterpret_cast<char const *>(glGetString(GL_EXTENSIONS));
+ // If that failed too, must be no extensions supported
+ if (supported == nullptr)
+ return false;
+
+ // Begin examination at start of string, increment by 1 on false match
+ for (const char* p = supported; ; p++)
+ {
+ // Advance p up to the next possible match
+ p = strstr(p, extension);
+
+ if (p == nullptr)
+ return false; // No Match
+
+ // Make sure that match is at the start of the string or that
+ // the previous char is a space, or else we could accidentally
+ // match "wglFunkywglExtension" with "wglExtension"
+
+ // Also, make sure that the following character is space or null
+ // or else "wglExtensionTwo" might match "wglExtension"
+ if ((p==supported || p[-1]==' ') && (p[extlen]=='\0' || p[extlen]==' '))
+ return true; // Match
+ }
+}
+
+static bool InitMultisample(const PIXELFORMATDESCRIPTOR& pfd, int& rPixelFormat,
+ bool bUseDoubleBufferedRendering, bool bRequestVirtualDevice)
+{
+ OpenGLZone aZone;
+
+ HWND hWnd = nullptr;
+ GLWinWindow glWin;
+ // Create a temp window to check whether support multi-sample, if support, get the format
+ if (!InitTempWindow(hWnd, 32, 32, pfd, glWin))
+ {
+ SAL_WARN("vcl.opengl", "Can't create temp window to test");
+ return false;
+ }
+
+ // See if the string exists in WGL
+ if (!WGLisExtensionSupported("WGL_ARB_multisample"))
+ {
+ SAL_WARN("vcl.opengl", "Device doesn't support multisample");
+ wglMakeCurrent(nullptr, nullptr);
+ g_bAnyCurrent = false;
+ wglDeleteContext(glWin.hRC);
+ ReleaseDC(hWnd, glWin.hDC);
+ DestroyWindow(hWnd);
+ return false;
+ }
+ // Get our pixel format
+ PFNWGLCHOOSEPIXELFORMATARBPROC fn_wglChoosePixelFormatARB = reinterpret_cast<PFNWGLCHOOSEPIXELFORMATARBPROC>(wglGetProcAddress("wglChoosePixelFormatARB"));
+ if (!fn_wglChoosePixelFormatARB)
+ {
+ wglMakeCurrent(nullptr, nullptr);
+ g_bAnyCurrent = false;
+ wglDeleteContext(glWin.hRC);
+ ReleaseDC(hWnd, glWin.hDC);
+ DestroyWindow(hWnd);
+ return false;
+ }
+ // Get our current device context
+ HDC hDC = GetDC(hWnd);
+
+ int pixelFormat;
+ int valid;
+ UINT numFormats;
+ float fAttributes[] = {0,0};
+ // These attributes are the bits we want to test for in our sample.
+ // Everything is pretty standard, the only one we want to
+ // really focus on is the WGL_SAMPLE_BUFFERS_ARB and WGL_SAMPLES_ARB.
+ // These two are going to do the main testing for whether or not
+ // we support multisampling on this hardware.
+ int iAttributes[] =
+ {
+ WGL_DOUBLE_BUFFER_ARB,GL_TRUE,
+ WGL_DRAW_TO_WINDOW_ARB,GL_TRUE,
+ WGL_SUPPORT_OPENGL_ARB,GL_TRUE,
+ WGL_ACCELERATION_ARB,WGL_FULL_ACCELERATION_ARB,
+ WGL_COLOR_BITS_ARB,24,
+ WGL_ALPHA_BITS_ARB,8,
+ WGL_DEPTH_BITS_ARB,24,
+ WGL_STENCIL_BITS_ARB,0,
+ WGL_SAMPLE_BUFFERS_ARB,GL_TRUE,
+ WGL_SAMPLES_ARB,8,
+ 0,0
+ };
+
+ if (!bUseDoubleBufferedRendering)
+ {
+ // Use asserts to make sure the iAttributes array is not changed without changing these ugly
+ // hardcode indexes into it.
+ assert(iAttributes[0] == WGL_DOUBLE_BUFFER_ARB);
+ iAttributes[1] = GL_FALSE;
+ }
+
+ if (bRequestVirtualDevice)
+ {
+ assert(iAttributes[2] == WGL_DRAW_TO_WINDOW_ARB);
+ iAttributes[2] = WGL_DRAW_TO_BITMAP_ARB;
+ }
+
+ bool bArbMultisampleSupported = false;
+
+ // First we check to see if we can get a pixel format for 8 samples
+ valid = fn_wglChoosePixelFormatARB(hDC, iAttributes, fAttributes, 1, &pixelFormat, &numFormats);
+ // If we returned true, and our format count is greater than 1
+ if (valid && numFormats >= 1)
+ {
+ bArbMultisampleSupported = true;
+ rPixelFormat = pixelFormat;
+ wglMakeCurrent(nullptr, nullptr);
+ g_bAnyCurrent = false;
+ wglDeleteContext(glWin.hRC);
+ ReleaseDC(hWnd, glWin.hDC);
+ DestroyWindow(hWnd);
+ return bArbMultisampleSupported;
+ }
+ // Our pixel format with 8 samples failed, test for 2 samples
+ assert(iAttributes[18] == WGL_SAMPLES_ARB);
+ iAttributes[19] = 2;
+ valid = fn_wglChoosePixelFormatARB(hDC, iAttributes, fAttributes, 1, &pixelFormat, &numFormats);
+ if (valid && numFormats >= 1)
+ {
+ bArbMultisampleSupported = true;
+ rPixelFormat = pixelFormat;
+ wglMakeCurrent(nullptr, nullptr);
+ g_bAnyCurrent = false;
+ wglDeleteContext(glWin.hRC);
+ ReleaseDC(hWnd, glWin.hDC);
+ DestroyWindow(hWnd);
+ return bArbMultisampleSupported;
+ }
+ // Return the valid format
+ wglMakeCurrent(nullptr, nullptr);
+ g_bAnyCurrent = false;
+ wglDeleteContext(glWin.hRC);
+ ReleaseDC(hWnd, glWin.hDC);
+ DestroyWindow(hWnd);
+
+ return bArbMultisampleSupported;
+}
+
+namespace
+{
+
+bool tryShaders(const OUString& rVertexShader, const OUString& rFragmentShader, const OUString& rGeometryShader = "", const OString& 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.isEmpty());
+ 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 vcl for now
+ // canvas
+ tryShaders("dummyVertexShader", "linearMultiColorGradientFragmentShader") &&
+ tryShaders("dummyVertexShader", "linearTwoColorGradientFragmentShader") &&
+ tryShaders("dummyVertexShader", "radialMultiColorGradientFragmentShader") &&
+ tryShaders("dummyVertexShader", "radialTwoColorGradientFragmentShader") &&
+ tryShaders("dummyVertexShader", "rectangularMultiColorGradientFragmentShader") &&
+ tryShaders("dummyVertexShader", "rectangularTwoColorGradientFragmentShader") &&
+ // chart2
+ (GLEW_VERSION_3_3 ?
+ (tryShaders("shape3DVertexShader", "shape3DFragmentShader") &&
+ tryShaders("shape3DVertexShaderBatchScroll", "shape3DFragmentShaderBatchScroll") &&
+ tryShaders("shape3DVertexShaderBatch", "shape3DFragmentShaderBatch") &&
+ tryShaders("textVertexShaderBatch", "textFragmentShaderBatch")) :
+ (tryShaders("shape3DVertexShaderV300", "shape3DFragmentShaderV300"))) &&
+ tryShaders("textVertexShader", "textFragmentShader") &&
+ tryShaders("screenTextVertexShader", "screenTextFragmentShader") &&
+ tryShaders("commonVertexShader", "commonFragmentShader") &&
+ tryShaders("pickingVertexShader", "pickingFragmentShader") &&
+ tryShaders("backgroundVertexShader", "backgroundFragmentShader") &&
+ tryShaders("symbolVertexShader", "symbolFragmentShader") &&
+ tryShaders("symbolVertexShader", "symbolFragmentShader") &&
+ // slideshow
+ tryShaders("reflectionVertexShader", "reflectionFragmentShader") &&
+ tryShaders("basicVertexShader", "basicFragmentShader") &&
+ tryShaders("vortexVertexShader", "vortexFragmentShader", "vortexGeometryShader") &&
+ tryShaders("basicVertexShader", "rippleFragmentShader") &&
+ tryShaders("glitterVertexShader", "glitterFragmentShader") &&
+ tryShaders("honeycombVertexShader", "honeycombFragmentShader", "honeycombGeometryShader") &&
+#endif
+ // vcl
+ tryShaders("combinedVertexShader", "combinedFragmentShader") &&
+ tryShaders("dumbVertexShader", "invert50FragmentShader") &&
+ tryShaders("textureVertexShader", "areaScaleFragmentShader") &&
+ tryShaders("transformedTextureVertexShader", "maskedTextureFragmentShader") &&
+ tryShaders("transformedTextureVertexShader", "areaScaleFastFragmentShader") &&
+ tryShaders("transformedTextureVertexShader", "areaScaleFastFragmentShader", "", "#define MASKED") &&
+ tryShaders("transformedTextureVertexShader", "areaScaleFragmentShader") &&
+ tryShaders("transformedTextureVertexShader", "areaScaleFragmentShader", "", "#define MASKED") &&
+ tryShaders("transformedTextureVertexShader", "textureFragmentShader") &&
+ tryShaders("combinedTextureVertexShader", "combinedTextureFragmentShader") &&
+ tryShaders("combinedTextureVertexShader", "combinedTextureFragmentShader", "", "// flush shader\n") &&
+ tryShaders("textureVertexShader", "linearGradientFragmentShader") &&
+ tryShaders("textureVertexShader", "radialGradientFragmentShader") &&
+ tryShaders("textureVertexShader", "areaHashCRC64TFragmentShader") &&
+ tryShaders("textureVertexShader", "replaceColorFragmentShader") &&
+ tryShaders("textureVertexShader", "greyscaleFragmentShader") &&
+ tryShaders("textureVertexShader", "textureFragmentShader") &&
+ tryShaders("textureVertexShader", "convolutionFragmentShader") &&
+ tryShaders("textureVertexShader", "areaScaleFastFragmentShader"));
+
+ 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;
+
+ if (!mbVCLOnly)
+ bMultiSampleSupport = InitMultisample(PixelFormatFront, WindowPix, /*bUseDoubleBufferedRendering*/true, false);
+ else
+ VCL_GL_INFO("Skipping multisample detection for VCL.");
+
+ 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;
+
+ static OString aVendor(reinterpret_cast<const char*>(glGetString(GL_VENDOR)));
+
+ if (aVendor.equalsIgnoreAsciiCase("intel"))
+ maOpenGLCapabilitySwitch.mbLimitedShaderRegisters = true;
+
+ return true;
+}
+
+OpenGLContext* WinSalInstance::CreateOpenGLContext()
+{
+ return new WinOpenGLContext;
+}
+
+WinOpenGLSalGraphicsImpl::WinOpenGLSalGraphicsImpl(WinSalGraphics& rGraphics,
+ SalGeometryProvider *mpProvider):
+ OpenGLSalGraphicsImpl(rGraphics,mpProvider),
+ mrWinParent(rGraphics)
+{
+}
+
+void WinOpenGLSalGraphicsImpl::copyBits( const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics )
+{
+ OpenGLSalGraphicsImpl *pImpl = pSrcGraphics ? static_cast< OpenGLSalGraphicsImpl* >(pSrcGraphics->GetImpl()) : static_cast< OpenGLSalGraphicsImpl *>(mrParent.GetImpl());
+ OpenGLSalGraphicsImpl::DoCopyBits( rPosAry, *pImpl );
+}
+
+rtl::Reference<OpenGLContext> WinOpenGLSalGraphicsImpl::CreateWinContext()
+{
+ rtl::Reference<WinOpenGLContext> xContext(new WinOpenGLContext);
+ xContext->setVCLOnly();
+ if (!xContext->init(mrWinParent.mhLocalDC, mrWinParent.mhWnd))
+ {
+ SAL_WARN("vcl.opengl", "Context could not be created.");
+ return rtl::Reference<OpenGLContext>();
+ }
+ return rtl::Reference<OpenGLContext>(xContext.get());
+}
+
+void WinOpenGLSalGraphicsImpl::Init()
+{
+ if (!IsOffscreen() && mpContext.is() && mpContext->isInitialized())
+ {
+ const GLWinWindow& rGLWindow = static_cast<const GLWinWindow&>(mpContext->getOpenGLWindow());
+ if (rGLWindow.hWnd != mrWinParent.mhWnd || rGLWindow.hDC == mrWinParent.mhLocalDC)
+ {
+ // This can legitimately happen, SalFrame keeps 2x
+ // SalGraphics which share the same hWnd and hDC.
+ // The shape 'Area' dialog does reparenting to trigger this.
+ SAL_WARN("vcl.opengl", "Unusual: Windows handle / DC changed without DeInit");
+ DeInit();
+ }
+ }
+
+ OpenGLSalGraphicsImpl::Init();
+}
+
+OpenGLControlsCache::OpenGLControlsCache(): cache(200) {}
+
+OpenGLControlCacheType & OpenGLControlsCache::get() {
+ SalData * data = GetSalData();
+ if (!data->m_pOpenGLControlsCache) {
+ data->m_pOpenGLControlsCache.reset(new OpenGLControlsCache);
+ }
+ return data->m_pOpenGLControlsCache->cache;
+}
+
+OpenGLCompatibleDC::OpenGLCompatibleDC(SalGraphics &rGraphics, int x, int y, int width, int height)
+: CompatibleDC( rGraphics, x, y, width, height, false )
+{
+}
+
+OpenGLTexture* OpenGLCompatibleDC::getOpenGLTexture() const
+{
+ if (!mpImpl)
+ return nullptr;
+
+ // turn what's in the mpData into a texture
+ return new OpenGLTexture(maRects.mnSrcWidth, maRects.mnSrcHeight, GL_BGRA, GL_UNSIGNED_BYTE, mpData);
+}
+
+std::unique_ptr<CompatibleDC::Texture> OpenGLCompatibleDC::getAsMaskTexture() const
+{
+ auto ret = std::make_unique<OpenGLCompatibleDC::Texture>();
+ ret->texture = OpenGLTexture(maRects.mnSrcWidth, maRects.mnSrcHeight, GL_BGRA, GL_UNSIGNED_BYTE, mpData);
+ return ret;
+}
+
+bool OpenGLCompatibleDC::copyToTexture(CompatibleDC::Texture& aTexture) const
+{
+ if (!mpImpl)
+ return false;
+
+ assert(dynamic_cast<OpenGLCompatibleDC::Texture*>(&aTexture));
+ return static_cast<OpenGLCompatibleDC::Texture&>(aTexture).texture.CopyData(
+ maRects.mnSrcWidth, maRects.mnSrcHeight, GL_BGRA, GL_UNSIGNED_BYTE, reinterpret_cast<sal_uInt8*>(mpData));
+}
+
+bool WinOpenGLSalGraphicsImpl::TryRenderCachedNativeControl(ControlCacheKey const & rControlCacheKey, int nX, int nY)
+{
+ static bool gbCacheEnabled = !getenv("SAL_WITHOUT_WIDGET_CACHE");
+
+ if (!gbCacheEnabled)
+ return false;
+
+ auto & gTextureCache = OpenGLControlsCache::get();
+ OpenGLControlCacheType::const_iterator iterator = gTextureCache.find(rControlCacheKey);
+
+ if (iterator == gTextureCache.end())
+ return false;
+
+ const std::unique_ptr<TextureCombo>& pCombo = iterator->second;
+
+ bool bRet = false;
+
+ PreDraw();
+
+ bRet = RenderTextureCombo(*pCombo, nX, nY);
+
+ PostDraw();
+
+ return bRet;
+}
+
+bool WinOpenGLSalGraphicsImpl::RenderTextureCombo(TextureCombo const & rCombo, int nX, int nY)
+{
+ OpenGLTexture& rTexture = *rCombo.mpTexture;
+
+ SalTwoRect aPosAry(0, 0, rTexture.GetWidth(), rTexture.GetHeight(),
+ nX, nY, rTexture.GetWidth(), rTexture.GetHeight());
+
+ DrawTextureDiff(rTexture, *rCombo.mpMask, aPosAry, false);
+
+ return true;
+}
+
+bool WinOpenGLSalGraphicsImpl::RenderCompatibleDC(OpenGLCompatibleDC& rWhite, OpenGLCompatibleDC& rBlack,
+ int nX, int nY, TextureCombo& rCombo)
+{
+ bool bRet = false;
+
+ PreDraw();
+
+ rCombo.mpTexture.reset(rWhite.getOpenGLTexture());
+ rCombo.mpMask.reset(rBlack.getOpenGLTexture());
+
+ bRet = RenderTextureCombo(rCombo, nX, nY);
+
+ PostDraw();
+ return bRet;
+}
+
+bool WinOpenGLSalGraphicsImpl::RenderAndCacheNativeControl(CompatibleDC& rWhite, CompatibleDC& rBlack,
+ int nX, int nY , ControlCacheKey& aControlCacheKey)
+{
+ assert(dynamic_cast<OpenGLCompatibleDC*>(&rWhite));
+ assert(dynamic_cast<OpenGLCompatibleDC*>(&rBlack));
+
+ std::unique_ptr<TextureCombo> pCombo(new TextureCombo);
+
+ bool bResult = RenderCompatibleDC(static_cast<OpenGLCompatibleDC&>(rWhite),
+ static_cast<OpenGLCompatibleDC&>(rBlack), nX, nY, *pCombo);
+ if (!bResult)
+ return false;
+
+ if (!aControlCacheKey.canCacheControl())
+ return true;
+
+ OpenGLControlCachePair pair(aControlCacheKey, std::move(pCombo));
+ OpenGLControlsCache::get().insert(std::move(pair));
+
+ return bResult;
+}
+
+void WinOpenGLSalGraphicsImpl::PreDrawText()
+{
+ PreDraw();
+}
+
+void WinOpenGLSalGraphicsImpl::PostDrawText()
+{
+ PostDraw();
+}
+
+void WinOpenGLSalGraphicsImpl::DeferredTextDraw(const CompatibleDC::Texture* pTexture, Color aMaskColor, const SalTwoRect& rPosAry)
+{
+ assert(dynamic_cast<const OpenGLCompatibleDC::Texture*>(pTexture));
+ mpRenderList->addDrawTextureWithMaskColor(
+ static_cast<const OpenGLCompatibleDC::Texture*>(pTexture)->texture, aMaskColor, rPosAry);
+ PostBatchDraw();
+}
+
+void WinOpenGLSalGraphicsImpl::DrawTextMask( CompatibleDC::Texture* pTexture, Color nMaskColor, const SalTwoRect& rPosAry )
+{
+ assert(dynamic_cast<OpenGLCompatibleDC::Texture*>(pTexture));
+ DrawMask( static_cast<OpenGLCompatibleDC::Texture*>(pTexture)->texture, nMaskColor, rPosAry );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/win/winlayout.cxx b/vcl/opengl/win/winlayout.cxx
new file mode 100644
index 000000000..59bf12c25
--- /dev/null
+++ b/vcl/opengl/win/winlayout.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 <opengl/win/winlayout.hxx>
+
+#include <opengl/win/gdiimpl.hxx>
+
+bool OpenGLGlobalWinGlyphCache::AllocateTexture(WinGlyphDrawElement& rElement, CompatibleDC* dc)
+{
+ assert(rElement.maTexture.get() == nullptr);
+ assert(dynamic_cast<OpenGLCompatibleDC*>(dc));
+ OpenGLCompatibleDC* odc = static_cast<OpenGLCompatibleDC*>(dc);
+ OpenGLCompatibleDC::Texture* texture = new OpenGLCompatibleDC::Texture;
+ rElement.maTexture.reset(texture);
+ texture->texture = maPackedTextureAtlas.Reserve(dc->getBitmapWidth(), dc->getBitmapHeight());
+ if (!texture->texture)
+ return false;
+ if (!odc->copyToTexture(*rElement.maTexture))
+ return false;
+ return true;
+}
+
+void OpenGLGlobalWinGlyphCache::Prune()
+{
+ std::vector<GLuint> aTextureIDs = maPackedTextureAtlas.ReduceTextureNumber(8);
+ if (!aTextureIDs.empty())
+ {
+ for (auto& pWinGlyphCache : maWinGlyphCaches)
+ static_cast<OpenGLWinGlyphCache*>(pWinGlyphCache)->RemoveTextures(aTextureIDs);
+ }
+}
+
+void OpenGLWinGlyphCache::RemoveTextures(std::vector<GLuint>& rTextureIDs)
+{
+ auto it = maWinTextureCache.begin();
+
+ while (it != maWinTextureCache.end())
+ {
+ assert(dynamic_cast<OpenGLCompatibleDC::Texture*>(it->second.maTexture.get()));
+ GLuint nTextureID
+ = static_cast<OpenGLCompatibleDC::Texture*>(it->second.maTexture.get())->texture.Id();
+
+ if (std::find(rTextureIDs.begin(), rTextureIDs.end(), nTextureID) != rTextureIDs.end())
+ {
+ it = maWinTextureCache.erase(it);
+ }
+ else
+ {
+ ++it;
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/x11/X11DeviceInfo.cxx b/vcl/opengl/x11/X11DeviceInfo.cxx
new file mode 100644
index 000000000..7f671952f
--- /dev/null
+++ b/vcl/opengl/x11/X11DeviceInfo.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 <opengl/x11/X11DeviceInfo.hxx>
+#include <opengl/x11/glxtest.hxx>
+
+#include <config_features.h>
+
+#include <rtl/ustring.hxx>
+#include <sal/log.hxx>
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <sys/utsname.h>
+
+#include <desktop/crashreport.hxx>
+
+namespace glx {
+
+static int glxtest_pipe = 0;
+
+static pid_t glxtest_pid = 0;
+
+}
+
+pid_t* getGlxPid()
+{
+ return &glx::glxtest_pid;
+}
+
+int* getGlxPipe()
+{
+ return &glx::glxtest_pipe;
+}
+
+namespace {
+
+const char*
+strspnp_wrapper(const char* aDelims, const char* aStr)
+{
+ const char* d;
+ do
+ {
+ for (d = aDelims; *d != '\0'; ++d)
+ {
+ if (*aStr == *d)
+ {
+ ++aStr;
+ break;
+ }
+ }
+ } while (*d);
+
+ return aStr;
+}
+
+char* strtok_wrapper(const char* aDelims, char** aStr)
+{
+ if (!*aStr)
+ {
+ return nullptr;
+ }
+
+ char* ret = const_cast<char*>(strspnp_wrapper(aDelims, *aStr));
+
+ if (!*ret)
+ {
+ *aStr = ret;
+ return nullptr;
+ }
+
+ char* i = ret;
+ do
+ {
+ for (const char* d = aDelims; *d != '\0'; ++d)
+ {
+ if (*i == *d) {
+ *i = '\0';
+ *aStr = ++i;
+ return ret;
+ }
+ }
+ ++i;
+ } while (*i);
+
+ *aStr = nullptr;
+ return ret;
+}
+
+uint64_t version(uint32_t major, uint32_t minor, uint32_t revision = 0)
+{
+ return (uint64_t(major) << 32) + (uint64_t(minor) << 16) + uint64_t(revision);
+}
+
+}
+
+X11OpenGLDeviceInfo::X11OpenGLDeviceInfo():
+ mbIsMesa(false),
+ mbIsNVIDIA(false),
+ mbIsFGLRX(false),
+ mbIsNouveau(false),
+ mbIsIntel(false),
+ mbIsOldSwrast(false),
+ mbIsLlvmpipe(false),
+ mnGLMajorVersion(0),
+ mnMajorVersion(0),
+ mnMinorVersion(0),
+ mnRevisionVersion(0)
+{
+ GetData();
+}
+
+void X11OpenGLDeviceInfo::GetData()
+{
+ if (!glx::glxtest_pipe)
+ return;
+
+ // to understand this function, see bug moz#639842. We retrieve the OpenGL driver information in a
+ // separate process to protect against bad drivers.
+ enum { buf_size = 1024 };
+ char buf[buf_size];
+ ssize_t bytesread = read(glx::glxtest_pipe,
+ &buf,
+ buf_size-1); // -1 because we'll append a zero
+ close(glx::glxtest_pipe);
+ glx::glxtest_pipe = 0;
+
+ // bytesread < 0 would mean that the above read() call failed.
+ // This should never happen. If it did, the outcome would be to blacklist anyway.
+ if (bytesread < 0)
+ bytesread = 0;
+
+ // let buf be a zero-terminated string
+ buf[bytesread] = 0;
+
+ // Wait for the glxtest process to finish. This serves 2 purposes:
+ // * avoid having a zombie glxtest process laying around
+ // * get the glxtest process status info.
+ int glxtest_status = 0;
+ bool wait_for_glxtest_process = true;
+ bool waiting_for_glxtest_process_failed = false;
+ int waitpid_errno = 0;
+ while(wait_for_glxtest_process)
+ {
+ wait_for_glxtest_process = false;
+ if (waitpid(glx::glxtest_pid, &glxtest_status, 0) == -1)
+ {
+ waitpid_errno = errno;
+ if (waitpid_errno == EINTR)
+ {
+ wait_for_glxtest_process = true;
+ }
+ else
+ {
+ // Bug moz#718629
+ // ECHILD happens when the glxtest process got reaped got reaped after a PR_CreateProcess
+ // as per bug moz#227246. This shouldn't matter, as we still seem to get the data
+ // from the pipe, and if we didn't, the outcome would be to blacklist anyway.
+ waiting_for_glxtest_process_failed = (waitpid_errno != ECHILD);
+ }
+ }
+ }
+
+ bool exited_with_error_code = !waiting_for_glxtest_process_failed &&
+ WIFEXITED(glxtest_status) &&
+ WEXITSTATUS(glxtest_status) != EXIT_SUCCESS;
+ bool received_signal = !waiting_for_glxtest_process_failed &&
+ WIFSIGNALED(glxtest_status);
+
+ bool error = waiting_for_glxtest_process_failed || exited_with_error_code || received_signal;
+
+ OString textureFromPixmap;
+ OString *stringToFill = nullptr;
+ char *bufptr = buf;
+ if (!error)
+ {
+ while(true)
+ {
+ char *line = strtok_wrapper("\n", &bufptr);
+ if (!line)
+ break;
+ if (stringToFill) {
+ *stringToFill = OString(line);
+ stringToFill = nullptr;
+ }
+ else if(!strcmp(line, "VENDOR"))
+ stringToFill = &maVendor;
+ else if(!strcmp(line, "RENDERER"))
+ stringToFill = &maRenderer;
+ else if(!strcmp(line, "VERSION"))
+ stringToFill = &maVersion;
+ else if(!strcmp(line, "TFP"))
+ stringToFill = &textureFromPixmap;
+ }
+ }
+
+ // only useful for Linux kernel version check for FGLRX driver.
+ // assumes X client == X server, which is sad.
+ struct utsname unameobj;
+ if (!uname(&unameobj))
+ {
+ maOS = OString(unameobj.sysname);
+ maOSRelease = OString(unameobj.release);
+ }
+
+ // determine the major OpenGL version. That's the first integer in the version string.
+ mnGLMajorVersion = strtol(maVersion.getStr(), nullptr, 10);
+
+ // determine driver type (vendor) and where in the version string
+ // the actual driver version numbers should be expected to be found (whereToReadVersionNumbers)
+ const char *whereToReadVersionNumbers = nullptr;
+ const char *Mesa_in_version_string = strstr(maVersion.getStr(), "Mesa");
+ if (Mesa_in_version_string)
+ {
+ mbIsMesa = true;
+ // with Mesa, the version string contains "Mesa major.minor" and that's all the version information we get:
+ // there is no actual driver version info.
+ whereToReadVersionNumbers = Mesa_in_version_string + strlen("Mesa");
+ if (strcasestr(maVendor.getStr(), "nouveau"))
+ mbIsNouveau = true;
+ if (strcasestr(maRenderer.getStr(), "intel")) // yes, intel is in the renderer string
+ mbIsIntel = true;
+ if (strcasestr(maRenderer.getStr(), "llvmpipe"))
+ mbIsLlvmpipe = true;
+ if (strcasestr(maRenderer.getStr(), "software rasterizer"))
+ mbIsOldSwrast = true;
+ }
+ else if (strstr(maVendor.getStr(), "NVIDIA Corporation"))
+ {
+ mbIsNVIDIA = true;
+ // with the NVIDIA driver, the version string contains "NVIDIA major.minor"
+ // note that here the vendor and version strings behave differently, that's why we don't put this above
+ // alongside Mesa_in_version_string.
+ const char *NVIDIA_in_version_string = strstr(maVersion.getStr(), "NVIDIA");
+ if (NVIDIA_in_version_string)
+ whereToReadVersionNumbers = NVIDIA_in_version_string + strlen("NVIDIA");
+ }
+ else if (strstr(maVendor.getStr(), "ATI Technologies Inc"))
+ {
+ mbIsFGLRX = true;
+ // with the FGLRX driver, the version string only gives an OpenGL version: so let's return that.
+ // that can at least give a rough idea of how old the driver is.
+ whereToReadVersionNumbers = maVersion.getStr();
+ }
+
+ // read major.minor version numbers of the driver (not to be confused with the OpenGL version)
+ if (whereToReadVersionNumbers)
+ {
+ // copy into writable buffer, for tokenization
+ strncpy(buf, whereToReadVersionNumbers, buf_size-1);
+ buf[buf_size-1] = 0;
+ bufptr = buf;
+
+ // now try to read major.minor version numbers. In case of failure, gracefully exit: these numbers have
+ // been initialized as 0 anyways
+ char *token = strtok_wrapper(".", &bufptr);
+ if (token)
+ {
+ mnMajorVersion = strtol(token, nullptr, 10);
+ token = strtok_wrapper(".", &bufptr);
+ if (token)
+ {
+ mnMinorVersion = strtol(token, nullptr, 10);
+ token = strtok_wrapper(".", &bufptr);
+ if (token)
+ mnRevisionVersion = strtol(token, nullptr, 10);
+ }
+ }
+ }
+}
+
+bool X11OpenGLDeviceInfo::isDeviceBlocked()
+{
+ // don't even try to use OpenGL 1.x
+ if (mnGLMajorVersion == 1)
+ return true;
+
+ CrashReporter::addKeyValue("AdapterVendorId", OStringToOUString(maVendor, RTL_TEXTENCODING_UTF8), CrashReporter::AddItem);
+ CrashReporter::addKeyValue("AdapterDeviceId", OStringToOUString(maRenderer, RTL_TEXTENCODING_UTF8), CrashReporter::Write);
+
+ SAL_INFO("vcl.opengl", "Vendor: " << maVendor);
+ SAL_INFO("vcl.opengl", "Renderer: " << maRenderer);
+ SAL_INFO("vcl.opengl", "Version: " << maVersion);
+ SAL_INFO("vcl.opengl", "OS: " << maOS);
+ SAL_INFO("vcl.opengl", "OSRelease: " << maOSRelease);
+
+ if (mbIsMesa)
+ {
+ if (mbIsNouveau && version(mnMajorVersion, mnMinorVersion) < version(8,0))
+ {
+ SAL_WARN("vcl.opengl", "blocked driver version: old nouveau driver (requires mesa 8.0+)");
+ return true;
+ }
+ else if (version(mnMajorVersion, mnMinorVersion, mnRevisionVersion) < version(7,10,3))
+ {
+ SAL_WARN("vcl.opengl", "blocked driver version: requires at least mesa 7.10.3");
+ return true;
+ }
+ else if (mbIsIntel && version(mnMajorVersion, mnMinorVersion, mnRevisionVersion) == version(9,0,2))
+ {
+ SAL_WARN("vcl.opengl", "blocked driver version: my broken intel driver Mesa 9.0.2");
+ return true;
+ }
+ else if (mbIsOldSwrast)
+ {
+ SAL_WARN("vcl.opengl", "blocked driver version: software rasterizer");
+ return true;
+ }
+ else if (mbIsLlvmpipe && version(mnMajorVersion, mnMinorVersion) < version(9, 1))
+ {
+ // bug moz#791905, Mesa bug 57733, fixed in Mesa 9.1 according to
+ // https://bugs.freedesktop.org/show_bug.cgi?id=57733#c3
+ SAL_WARN("vcl.opengl", "blocked driver version: fdo#57733");
+ return true;
+ }
+ }
+ else if (mbIsNVIDIA)
+ {
+ if (version(mnMajorVersion, mnMinorVersion, mnRevisionVersion) < version(257,21))
+ {
+ SAL_WARN("vcl.opengl", "blocked driver version: nvidia requires at least 257.21");
+ return true;
+ }
+ }
+ else if (mbIsFGLRX)
+ {
+ // FGLRX does not report a driver version number, so we have the OpenGL version instead.
+ // by requiring OpenGL 3, we effectively require recent drivers.
+ if (version(mnMajorVersion, mnMinorVersion, mnRevisionVersion) < version(3, 0))
+ {
+ SAL_WARN("vcl.opengl", "blocked driver version: require at least OpenGL 3 for fglrx");
+ return true;
+ }
+ // Bug moz#724640: FGLRX + Linux 2.6.32 is a crashy combo
+ bool unknownOS = maOS.isEmpty() || maOSRelease.isEmpty();
+ bool badOS = maOS.indexOf("Linux") != -1 &&
+ maOSRelease.indexOf("2.6.32") != -1;
+ if (unknownOS || badOS)
+ {
+ SAL_WARN("vcl.opengl", "blocked OS version with fglrx");
+ return true;
+ }
+ }
+ else
+ {
+ // like on windows, let's block unknown vendors. Think of virtual machines.
+ // Also, this case is hit whenever the GLXtest probe failed to get driver info or crashed.
+ SAL_WARN("vcl.opengl", "unknown vendor => blocked");
+ return true;
+ }
+
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/x11/cairotextrender.cxx b/vcl/opengl/x11/cairotextrender.cxx
new file mode 100644
index 000000000..39b5f661d
--- /dev/null
+++ b/vcl/opengl/x11/cairotextrender.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 <opengl/x11/cairotextrender.hxx>
+
+#include <opengl/gdiimpl.hxx>
+
+#include <cairo.h>
+
+OpenGLX11CairoTextRender::OpenGLX11CairoTextRender(X11SalGraphics& rParent)
+ : X11CairoTextRender(rParent)
+{
+}
+
+cairo_t* OpenGLX11CairoTextRender::getCairoContext()
+{
+ cairo_surface_t* surface = nullptr;
+ OpenGLSalGraphicsImpl *pImpl = dynamic_cast< OpenGLSalGraphicsImpl* >(mrParent.GetImpl());
+ if( pImpl )
+ {
+ tools::Rectangle aClipRect = pImpl->getClipRegion().GetBoundRect();
+ if( aClipRect.GetWidth() == 0 || aClipRect.GetHeight() == 0 )
+ {
+ aClipRect.setWidth( GetWidth() );
+ aClipRect.setHeight( GetHeight() );
+ }
+ surface = cairo_image_surface_create( CAIRO_FORMAT_ARGB32, aClipRect.GetWidth(), aClipRect.GetHeight() );
+ }
+ if (!surface)
+ return nullptr;
+ cairo_t *cr = cairo_create(surface);
+ cairo_surface_destroy(surface);
+ return cr;
+}
+
+void OpenGLX11CairoTextRender::getSurfaceOffset( double& nDX, double& nDY )
+{
+ OpenGLSalGraphicsImpl *pImpl = dynamic_cast< OpenGLSalGraphicsImpl* >(mrParent.GetImpl());
+ if( pImpl )
+ {
+ tools::Rectangle aClipRect = pImpl->getClipRegion().GetBoundRect();
+ nDX = -aClipRect.Left();
+ nDY = -aClipRect.Top();
+ }
+}
+
+void OpenGLX11CairoTextRender::releaseCairoContext(cairo_t* cr)
+{
+ // XXX: lfrb: GLES 2.0 doesn't support GL_UNSIGNED_INT_8_8_8_8_REV
+ OpenGLSalGraphicsImpl *pImpl = dynamic_cast< OpenGLSalGraphicsImpl* >(mrParent.GetImpl());
+ if(!pImpl)
+ {
+ cairo_destroy(cr);
+ return;
+ }
+
+ cairo_surface_t* pSurface = cairo_get_target(cr);
+ int nWidth = cairo_image_surface_get_width( pSurface );
+ int nHeight = cairo_image_surface_get_height( pSurface );
+ cairo_surface_flush(pSurface);
+ unsigned char *pSrc = cairo_image_surface_get_data( pSurface );
+
+ // XXX: lfrb: GLES 2.0 doesn't support GL_UNSIGNED_INT_8_8_8_8_REV
+ tools::Rectangle aClipRect = pImpl->getClipRegion().GetBoundRect();
+
+ SalTwoRect aRect(0, 0, nWidth, nHeight,
+ aClipRect.Left(), aClipRect.Top(), nWidth, nHeight);
+
+ // Cairo surface data is ARGB with premultiplied alpha and is Y-inverted
+ OpenGLTexture aTexture( nWidth, nHeight, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, pSrc );
+ pImpl->PreDraw();
+ pImpl->DrawAlphaTexture( aTexture, aRect, true, true );
+ pImpl->PostDraw();
+
+ cairo_destroy(cr);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/x11/gdiimpl.cxx b/vcl/opengl/x11/gdiimpl.cxx
new file mode 100644
index 000000000..c00ff76e8
--- /dev/null
+++ b/vcl/opengl/x11/gdiimpl.cxx
@@ -0,0 +1,598 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 <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/texture.hxx>
+#include <opengl/zone.hxx>
+#include <opengl/RenderState.hxx>
+#include <opengl/x11/gdiimpl.hxx>
+#include <opengl/x11/salvd.hxx>
+
+#include <vcl/opengl/OpenGLContext.hxx>
+#include <vcl/opengl/OpenGLHelper.hxx>
+#include <sal/log.hxx>
+
+#include <o3tl/lru_map.hxx>
+#include <ControlCacheKey.hxx>
+
+static std::vector<GLXContext> g_vShareList;
+static bool g_bAnyCurrent;
+
+namespace {
+
+class X11OpenGLContext : public OpenGLContext
+{
+public:
+ void init(Display* dpy, Window win, int screen);
+ 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);
+ }
+ }
+ };
+
+ static 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;
+ }
+
+ Visual* getVisual(Display* dpy, Window win)
+ {
+ OpenGLZone aZone;
+
+ XWindowAttributes xattr;
+ if( !XGetWindowAttributes( dpy, win, &xattr ) )
+ {
+ SAL_WARN("vcl.opengl", "Failed to get window attributes for getVisual " << win);
+ xattr.visual = nullptr;
+ }
+ VCL_GL_INFO("using VisualID " << xattr.visual);
+ return xattr.visual;
+ }
+}
+
+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->aWindow;
+
+ 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)
+ {
+ std::vector<GLXContext>::iterator itr = std::remove( g_vShareList.begin(), g_vShareList.end(), m_aGLWin.ctx );
+ if (itr != g_vShareList.end())
+ g_vShareList.erase(itr);
+
+ glXMakeCurrent(m_aGLWin.dpy, None, nullptr);
+ g_bAnyCurrent = false;
+ if( glGetError() != GL_NO_ERROR )
+ {
+ SAL_WARN("vcl.opengl", "glError: " << glGetError());
+ }
+ glXDestroyContext(m_aGLWin.dpy, m_aGLWin.ctx);
+ m_aGLWin.ctx = nullptr;
+ }
+}
+
+void X11OpenGLContext::init(Display* dpy, Window win, int screen)
+{
+ if (isInitialized())
+ return;
+
+ if (!dpy)
+ return;
+
+ OpenGLZone aZone;
+
+ m_aGLWin.dpy = dpy;
+ m_aGLWin.win = win;
+ m_aGLWin.screen = screen;
+
+ Visual* pVisual = getVisual(dpy, win);
+
+ initGLWindow(pVisual);
+
+ ImplInit();
+}
+
+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->aWindow;
+ 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 (GLXExtensions.getToken(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;
+}
+
+X11OpenGLSalGraphicsImpl::X11OpenGLSalGraphicsImpl( X11SalGraphics& rParent ):
+ OpenGLSalGraphicsImpl(rParent,rParent.GetGeometryProvider()),
+ mrX11Parent(rParent)
+{
+}
+
+X11OpenGLSalGraphicsImpl::~X11OpenGLSalGraphicsImpl()
+{
+}
+
+void X11OpenGLSalGraphicsImpl::Init()
+{
+ // The m_pFrame and m_pVDev pointers are updated late in X11
+ mpProvider = mrX11Parent.GetGeometryProvider();
+ OpenGLSalGraphicsImpl::Init();
+}
+
+rtl::Reference<OpenGLContext> X11OpenGLSalGraphicsImpl::CreateWinContext()
+{
+ NativeWindowHandleProvider *pProvider = dynamic_cast<NativeWindowHandleProvider*>(mrX11Parent.m_pFrame);
+
+ if( !pProvider )
+ return nullptr;
+
+ sal_uIntPtr aWin = pProvider->GetNativeWindowHandle();
+ rtl::Reference<X11OpenGLContext> xContext = new X11OpenGLContext;
+ xContext->setVCLOnly();
+ xContext->init( mrX11Parent.GetXDisplay(), aWin,
+ mrX11Parent.m_nXScreen.getXScreen() );
+ return rtl::Reference<OpenGLContext>(xContext.get());
+}
+
+void X11OpenGLSalGraphicsImpl::copyBits( const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics )
+{
+ OpenGLSalGraphicsImpl *pImpl = pSrcGraphics ? static_cast< OpenGLSalGraphicsImpl* >(pSrcGraphics->GetImpl()) : static_cast< OpenGLSalGraphicsImpl *>(mrX11Parent.GetImpl());
+ OpenGLSalGraphicsImpl::DoCopyBits( rPosAry, *pImpl );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/x11/salvd.cxx b/vcl/opengl/x11/salvd.cxx
new file mode 100644
index 000000000..a6ed5602f
--- /dev/null
+++ b/vcl/opengl/x11/salvd.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 <vcl/sysdata.hxx>
+
+#include <unx/salunx.h>
+#include <unx/saldisp.hxx>
+#include <unx/salgdi.h>
+#include <unx/salvd.h>
+
+#include <opengl/x11/salvd.hxx>
+
+void X11SalGraphics::Init( X11OpenGLSalVirtualDevice *pDevice )
+{
+ SalDisplay *pDisplay = pDevice->GetDisplay();
+
+ m_nXScreen = pDevice->GetXScreenNumber();
+ m_pColormap = &pDisplay->GetColormap( m_nXScreen );
+
+ m_pVDev = pDevice;
+ m_pFrame = nullptr;
+
+ bWindow_ = pDisplay->IsDisplay();
+ bVirDev_ = true;
+
+ mxImpl->Init();
+}
+
+X11OpenGLSalVirtualDevice::X11OpenGLSalVirtualDevice( SalGraphics const * pGraphics,
+ long nDX, 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 = pGraphics ? static_cast<X11SalGraphics const *>(pGraphics)->GetScreenNumber() :
+ vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetDefaultXScreen();
+ mnWidth = nDX;
+ mnHeight = nDY;
+ mpGraphics->Init( this );
+}
+
+X11OpenGLSalVirtualDevice::~X11OpenGLSalVirtualDevice()
+{
+}
+
+SalGraphics* X11OpenGLSalVirtualDevice::AcquireGraphics()
+{
+ if( mbGraphics )
+ return nullptr;
+
+ if( mpGraphics )
+ mbGraphics = true;
+
+ return mpGraphics.get();
+}
+
+void X11OpenGLSalVirtualDevice::ReleaseGraphics( SalGraphics* )
+{
+ mbGraphics = false;
+}
+
+
+bool X11OpenGLSalVirtualDevice::SetSize( long nDX, 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/osx/DataFlavorMapping.cxx b/vcl/osx/DataFlavorMapping.cxx
new file mode 100644
index 000000000..dfba27d20
--- /dev/null
+++ b/vcl/osx/DataFlavorMapping.cxx
@@ -0,0 +1,747 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <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;
+using namespace std;
+
+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_SODX = @"application/x-openoffice-objectdescriptor-xml;windows_formatname=\"Star Object Descriptor (XML)\"";
+ NSString* PBTYPE_SESX = @"application/x-openoffice-embed-source-xml;windows_formatname=\"Star Embed Source (XML)\"";
+ NSString* PBTYPE_SLSDX = @"application/x-openoffice-linksrcdescriptor-xml;windows_formatname=\"Star Link Source Descriptor (XML)\"";
+ NSString* PBTYPE_ESX = @"application/x-openoffice-embed-source-xml;windows_formatname=\"Star Embed Source (XML)\"";
+ NSString* PBTYPE_LSX = @"application/x-openoffice-link-source-xml;windows_formatname=\"Star Link Source (XML)\"";
+ NSString* PBTYPE_EOX = @"application/x-openoffice-embedded-obj-xml;windows_formatname=\"Star Embedded Object (XML)\"";
+ NSString* PBTYPE_SVXB = @"application/x-openoffice-svbx;windows_formatname=\"SVXB (StarView Bitmap/Animation)\"";
+ NSString* PBTYPE_GDIMF = @"application/x-openoffice-gdimetafile;windows_formatname=\"GDIMetaFile\"";
+ NSString* PBTYPE_WMF = @"application/x-openoffice-wmf;windows_formatname=\"Image WMF\"";
+ NSString* PBTYPE_EMF = @"application/x-openoffice-emf;windows_formatname=\"Image EMF\"";
+
+ NSString* PBTYPE_DUMMY_INTERNAL = @"application/x-openoffice-internal";
+
+ const char* FLAVOR_SODX = "application/x-openoffice-objectdescriptor-xml;windows_formatname=\"Star Object Descriptor (XML)\"";
+ 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_DUMMY_INTERNAL = "application/x-openoffice-internal";
+
+ struct FlavorMap
+ {
+ const NSString* SystemFlavor;
+ const char* OOoFlavor;
+ const char* HumanPresentableName;
+ bool DataTypeOUString; // sequence<byte> otherwise
+ };
+
+ /* At the moment it appears as if only MS Office pastes "public.html" to the clipboard.
+ */
+ static 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
+ { PBTYPE_SESX, FLAVOR_SESX, "Star Embed Source (XML)", false },
+ { PBTYPE_SLSDX, FLAVOR_SLSDX, "Star Link Source Descriptor (XML)", false },
+ { PBTYPE_ESX, FLAVOR_ESX, "Star Embed Source (XML)", false },
+ { PBTYPE_LSX, FLAVOR_LSX, "Star Link Source (XML)", false },
+ { PBTYPE_EOX, FLAVOR_EOX, "Star Embedded Object (XML)", false },
+ { PBTYPE_SVXB, FLAVOR_SVXB, "SVXB (StarView Bitmap/Animation", false },
+ { PBTYPE_GDIMF, FLAVOR_GDIMF, "GDIMetaFile", false },
+ { PBTYPE_WMF, FLAVOR_WMF, "Windows MetaFile", false },
+ { PBTYPE_EMF, FLAVOR_EMF, "Windows Enhanced MetaFile", false },
+ { PBTYPE_SODX, FLAVOR_SODX, "Star Object Descriptor (XML)", false },
+ { PBTYPE_DUMMY_INTERNAL, 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 ([systemDataFlavor caseInsensitiveCompare:const_cast<NSString*>(flavorMap[i].SystemFlavor)] == NSOrderedSame)
+ {
+ 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) 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)))
+ {
+ sysFlavor = flavorMap[i].SystemFlavor;
+ }
+ }
+
+ 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(UnsupportedFlavorException&)
+ {
+ // 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( IllegalArgumentException& )
+ {
+ result = false;
+ }
+
+ return result;
+}
+
+NSArray* DataFlavorMapper::flavorSequenceToTypesArray(const css::uno::Sequence<css::datatransfer::DataFlavor>& flavors) 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);
+
+ 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: PBTYPE_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[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++)
+ {
+ [array addObject: flavorMap[i].SystemFlavor];
+ }
+
+ 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 000000000..4720a7f73
--- /dev/null
+++ b/vcl/osx/DataFlavorMapping.hxx
@@ -0,0 +1,129 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_VCL_OSX_DATAFLAVORMAPPING_HXX
+#define INCLUDED_VCL_OSX_DATAFLAVORMAPPING_HXX
+
+#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) 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) 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;
+
+#endif // INCLUDED_VCL_OSX_DATAFLAVORMAPPING_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/DragActionConversion.cxx b/vcl/osx/DragActionConversion.cxx
new file mode 100644
index 000000000..3af6cd010
--- /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 000000000..47e6ebed5
--- /dev/null
+++ b/vcl/osx/DragActionConversion.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_OSX_DRAGACTIONCONVERSION_HXX
+#define INCLUDED_VCL_OSX_DRAGACTIONCONVERSION_HXX
+
+#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);
+
+#endif // INCLUDED_VCL_OSX_DRAGACTIONCONVERSION_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/DragSource.cxx b/vcl/osx/DragSource.cxx
new file mode 100644
index 000000000..bfe990d73
--- /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(static_cast<OWeakObject*>(mDragSource),
+ 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(static_cast<OWeakObject*>(mDragSource),
+ 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(static_cast<OWeakObject*>(mDragSource),
+ 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.",
+ static_cast<OWeakObject*>(this));
+ }
+
+ 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",
+ static_cast<OWeakObject*>(this));
+ }
+ NSWindow* pWin = [mView window];
+ if( ! pWin || ![pWin respondsToSelector: @selector(getSalFrame)] )
+ {
+ throw Exception("DragSource::initialize: Provided view is not attached to a vcl frame",
+ static_cast<OWeakObject*>(this));
+ }
+ mpFrame = reinterpret_cast<AquaSalFrame*>([pWin performSelector: @selector(getSalFrame)]);
+
+ mDragSourceHelper = [[DragSourceHelper alloc] initWithDragSource: this];
+
+ if (mDragSourceHelper == nil)
+ {
+ throw Exception("DragSource::initialize: Cannot initialize DragSource",
+ static_cast<OWeakObject*>(this));
+ }
+
+ [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
+ // NSDragOperationGenric 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 000000000..9027ac455
--- /dev/null
+++ b/vcl/osx/DragSource.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_OSX_DRAGSOURCE_HXX
+#define INCLUDED_VCL_OSX_DRAGSOURCE_HXX
+
+#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;
+
+};
+
+#endif // INCLUDED_VCL_OSX_DRAGSOURCE_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/DragSourceContext.cxx b/vcl/osx/DragSourceContext.cxx
new file mode 100644
index 000000000..253dc867d
--- /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 000000000..3ebeb362b
--- /dev/null
+++ b/vcl/osx/DragSourceContext.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_OSX_DRAGSOURCECONTEXT_HXX
+#define INCLUDED_VCL_OSX_DRAGSOURCECONTEXT_HXX
+
+#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;
+};
+
+#endif // INCLUDED_VCL_OSX_DRAGSOURCECONTEXT_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/DropTarget.cxx b/vcl/osx/DropTarget.cxx
new file mode 100644
index 000000000..26398cb90
--- /dev/null
+++ b/vcl/osx/DropTarget.cxx
@@ -0,0 +1,548 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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(static_cast<OWeakObject*>(this),
+ 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(static_cast<OWeakObject*>(this),
+ 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(static_cast<OWeakObject*>(this), 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(static_cast<OWeakObject*>(this),
+ 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",
+ static_cast<OWeakObject*>(this));
+ }
+
+ 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];
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // 'NSClosableWindowMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSResizableWindowMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSTitleWindowMask' is deprecated: first deprecated in macOS 10.12
+ unsigned int topWndStyle = (NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask);
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ 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 000000000..aafb64495
--- /dev/null
+++ b/vcl/osx/DropTarget.hxx
@@ -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 INCLUDED_VCL_OSX_DROPTARGET_HXX
+#define INCLUDED_VCL_OSX_DROPTARGET_HXX
+
+#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;
+};
+
+#endif // INCLUDED_VCL_OSX_DROPTARGET_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/HtmlFmtFlt.cxx b/vcl/osx/HtmlFmtFlt.cxx
new file mode 100644
index 000000000..940bf90cb
--- /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 000000000..a50b72a6b
--- /dev/null
+++ b/vcl/osx/HtmlFmtFlt.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 .
+ */
+
+#ifndef INCLUDED_VCL_OSX_HTMLFMTFLT_HXX
+#define INCLUDED_VCL_OSX_HTMLFMTFLT_HXX
+
+#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);
+
+#endif // INCLUDED_VCL_OSX_HTMLFMTFLT_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/OSXTransferable.cxx b/vcl/osx/OSXTransferable.cxx
new file mode 100644
index 000000000..77417f3c2
--- /dev/null
+++ b/vcl/osx/OSXTransferable.cxx
@@ -0,0 +1,192 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <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"
+
+using namespace std;
+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
+
+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.get() == nullptr)
+ {
+ 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 : 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));
+ }
+
+ 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 000000000..2e7056145
--- /dev/null
+++ b/vcl/osx/OSXTransferable.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_OSX_OSXTRANSFERABLE_HXX
+#define INCLUDED_VCL_OSX_OSXTRANSFERABLE_HXX
+
+#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;
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/PictToBmpFlt.cxx b/vcl/osx/PictToBmpFlt.cxx
new file mode 100644
index 000000000..a818cb5e7
--- /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 000000000..027bd178b
--- /dev/null
+++ b/vcl/osx/PictToBmpFlt.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 .
+ */
+
+#ifndef INCLUDED_VCL_OSX_PICTTOBMPFLT_HXX
+#define INCLUDED_VCL_OSX_PICTTOBMPFLT_HXX
+
+#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);
+
+#endif // INCLUDED_VCL_OSX_PICTTOBMPFLT_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/README.a11y b/vcl/osx/README.a11y
new file mode 100644
index 000000000..4422713bc
--- /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 000000000..20b2bad1b
--- /dev/null
+++ b/vcl/osx/a11yactionwrapper.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_OSX_A11YACTIONWRAPPER_H
+#define INCLUDED_VCL_OSX_A11YACTIONWRAPPER_H
+
+#include <osx/osxvcltypes.h>
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yActionWrapper : NSObject
+{
+}
++(NSArray *)actionNamesForElement:(AquaA11yWrapper *)wrapper;
++(void)doAction:(NSString *)action ofElement:(AquaA11yWrapper *)wrapper;
+@end
+
+#endif // INCLUDED_VCL_OSX_A11YACTIONWRAPPER_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11yactionwrapper.mm b/vcl/osx/a11yactionwrapper.mm
new file mode 100644
index 000000000..8af087edf
--- /dev/null
+++ b/vcl/osx/a11yactionwrapper.mm
@@ -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 .
+ */
+
+
+#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;
+ }
+ }
+ }
+}
+
+@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 000000000..92c6b1b49
--- /dev/null
+++ b/vcl/osx/a11ycomponentwrapper.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_OSX_A11YCOMPONENTWRAPPER_H
+#define INCLUDED_VCL_OSX_A11YCOMPONENTWRAPPER_H
+
+#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
+
+#endif // INCLUDED_VCL_OSX_A11YCOMPONENTWRAPPER_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ycomponentwrapper.mm b/vcl/osx/a11ycomponentwrapper.mm
new file mode 100644
index 000000000..d9d6db175
--- /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 000000000..0a17f15da
--- /dev/null
+++ b/vcl/osx/a11yfactory.mm
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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::get().setFocusListener(listener.get());
+ 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() -> contains ( AccessibleStateType::TRANSIENT ) )
+ #endif
+ {
+ [ dAllWrapper setObject: aWrapper forKey: nKey ];
+ /* fdo#67410: Accessibility notifications are not delivered on NSView subclasses that do not
+ "reasonably" participate in NSView hierarchy (perhaps the only important point is
+ that the view is a transitive subview of the NSWindow's content view, but I
+ did not try to verify that).
+
+ So let the superview-subviews relationship mirror the AXParent-AXChildren relationship.
+ */
+ id parent = [aWrapper accessibilityAttributeValue:NSAccessibilityParentAttribute];
+ if (parent) {
+ if ([parent isKindOfClass:[NSView class]]) {
+ // SAL_DEBUG("Wrapper INIT: " << [[aWrapper description] UTF8String] << " ==> " << [[parent description] UTF8String]);
+ NSView *parentView = static_cast<NSView *>(parent);
+ [parentView addSubview:aWrapper positioned:NSWindowBelow relativeTo:nil];
+ } else if ([parent isKindOfClass:NSClassFromString(@"SalFrameWindow")]) {
+ NSWindow *window = static_cast<NSWindow *>(parent);
+ NSView *salView = [window contentView];
+ // SAL_DEBUG("Wrapper INIT SAL: " << [[aWrapper description] UTF8String] << " ==> " << [[salView description] UTF8String]);
+ [salView addSubview:aWrapper positioned:NSWindowBelow relativeTo:nil];
+ } else {
+ // SAL_DEBUG("Wrapper INIT: !! " << [[aWrapper description] UTF8String] << " !==>! " << [[parent description] UTF8String] << "!!");
+ }
+ } else {
+ // SAL_DEBUG("Wrapper INIT: " << [[aWrapper description] UTF8String] << " ==> NO PARENT");
+ }
+ }
+ }
+ return aWrapper;
+}
+
++(void)insertIntoWrapperRepository: (NSView *) viewElement forAccessibleContext: (Reference < XAccessibleContext >) rxAccessibleContext {
+ NSMutableDictionary * dAllWrapper = [ AquaA11yFactory allWrapper ];
+ [ dAllWrapper setObject: viewElement 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 ) {
+ if (![theWrapper isKindOfClass:NSClassFromString(@"SalFrameView")]) {
+ [theWrapper removeFromSuperview];
+ }
+ [ [ AquaA11yFactory allWrapper ] removeObjectForKey: [ AquaA11yFactory keyForAccessibleContext: rxAccessibleContext ] ];
+ [ theWrapper release ];
+ }
+}
+
++(void)registerView: (NSView *) theView {
+ if ( enabled && [ theView isKindOfClass: [ AquaA11yWrapper class ] ] ) {
+ // insertIntoWrapperRepository gets called from SalFrameView itself to bootstrap the bridge initially
+ [ static_cast<AquaA11yWrapper *>(theView) accessibleContext ];
+ }
+}
+
++(void)revokeView: (NSView *) theView {
+ if ( enabled && [ theView isKindOfClass: [ AquaA11yWrapper class ] ] ) {
+ [ AquaA11yFactory removeFromWrapperRepositoryFor: [ static_cast<AquaA11yWrapper *>(theView) 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 000000000..68eb45a31
--- /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::get().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 000000000..44a25cce0
--- /dev/null
+++ b/vcl/osx/a11yfocuslistener.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 .
+ */
+
+#ifndef INCLUDED_VCL_OSX_A11YFOCUSLISTENER_HXX
+#define INCLUDED_VCL_OSX_A11YFOCUSLISTENER_HXX
+
+#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;
+};
+
+#endif // INCLUDED_VCL_OSX_A11YFOCUSLISTENER_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11yfocustracker.cxx b/vcl/osx/a11yfocustracker.cxx
new file mode 100644
index 000000000..057169a30
--- /dev/null
+++ b/vcl/osx/a11yfocustracker.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/XAccessibleStateSet.hpp>
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+#include <com/sun/star/accessibility/AccessibleRole.hpp>
+
+using namespace ::com::sun::star::accessibility;
+using namespace ::com::sun::star::uno;
+
+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 );
+ }
+ else if( const VclAccessibleEvent* pAccEvent = dynamic_cast < const VclAccessibleEvent* > (&rEvent) )
+ {
+ Reference< XAccessible > xAccessible = pAccEvent->GetAccessible();
+ if( xAccessible.is() )
+ pFocusTracker->setFocusedObject( xAccessible );
+ }
+ 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() )
+ {
+ 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!
+ }
+ }
+}
+
+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 ) {
+ Reference < XAccessible > rxAccessibleChild = rxContext -> getAccessibleChild( 0 );
+ if ( ! rxAccessibleChild.is() ) {
+ return;
+ }
+ setFocusedObject ( rxAccessibleChild );
+ }
+ }
+}
+
+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;
+
+ Reference< XAccessibleStateSet > xStateSet = xContext->getAccessibleStateSet();
+
+ if( ! xStateSet.is() )
+ 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( xStateSet->contains(AccessibleStateType::FOCUSED) && (pWindow->GetType() != WindowType::TREELISTBOX) )
+ {
+ setFocusedObject( xAccessible );
+ }
+ else
+ {
+ if( m_aDocumentWindowList.insert(pWindow).second )
+ m_xDocumentFocusListener->attachRecursive(xAccessible, xContext, xStateSet);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ylistener.cxx b/vcl/osx/a11ylistener.cxx
new file mode 100644
index 000000000..b8220c07d
--- /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) &&
+ ( AccessibleTableModelChangeType::INSERT == aChange.Type || AccessibleTableModelChangeType::DELETE == aChange.Type ) &&
+ aChange.FirstRow != aChange.LastRow )
+ {
+ 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::get().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 000000000..7a4df6dbe
--- /dev/null
+++ b/vcl/osx/a11yrolehelper.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_OSX_A11YROLEHELPER_H
+#define INCLUDED_VCL_OSX_A11YROLEHELPER_H
+
+#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
+
+#endif // INCLUDED_VCL_OSX_A11YROLEHELPER_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11yrolehelper.mm b/vcl/osx/a11yrolehelper.mm
new file mode 100644
index 000000000..e42b2d53d
--- /dev/null
+++ b/vcl/osx/a11yrolehelper.mm
@@ -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 <osx/a11yfactory.h>
+
+#include "a11yrolehelper.h"
+
+#include <com/sun/star/accessibility/AccessibleRole.hpp>
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+
+using namespace ::com::sun::star::accessibility;
+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::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 );
+
+#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 ) {
+ Reference < XAccessible > rxAccessible = accessibleContext -> getAccessibleChild(0);
+ if ( rxAccessible.is() ) {
+ Reference < XAccessibleContext > rxAccessibleContext = rxAccessible -> getAccessibleContext();
+ if ( rxAccessibleContext.is() && rxAccessibleContext -> getAccessibleRole() == AccessibleRole::TEXT ) {
+ if ( ! rxAccessibleContext -> getAccessibleStateSet() -> contains ( AccessibleStateType::EDITABLE ) ) {
+ [ nativeRole release ];
+ nativeRole = NSAccessibilityPopUpButtonRole;
+ }
+ }
+ }
+ }
+ 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::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, @"" );
+
+#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 000000000..923737b65
--- /dev/null
+++ b/vcl/osx/a11yselectionwrapper.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 .
+ */
+
+#ifndef INCLUDED_VCL_OSX_A11YSELECTIONWRAPPER_H
+#define INCLUDED_VCL_OSX_A11YSELECTIONWRAPPER_H
+
+#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
+
+#endif // INCLUDED_VCL_OSX_A11YSELECTIONWRAPPER_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11yselectionwrapper.mm b/vcl/osx/a11yselectionwrapper.mm
new file mode 100644
index 000000000..163fe27da
--- /dev/null
+++ b/vcl/osx/a11yselectionwrapper.mm
@@ -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 <osx/salinst.h>
+#include <osx/a11yfactory.h>
+
+#include "a11yselectionwrapper.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_Int32 n = xAccessibleSelection -> getSelectedAccessibleChildCount();
+ for ( sal_Int32 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 000000000..450a829c1
--- /dev/null
+++ b/vcl/osx/a11ytablewrapper.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 .
+ */
+
+#ifndef INCLUDED_VCL_OSX_A11YTABLEWRAPPER_H
+#define INCLUDED_VCL_OSX_A11YTABLEWRAPPER_H
+
+#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
+#endif // INCLUDED_VCL_OSX_A11YTABLEWRAPPER_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ytablewrapper.mm b/vcl/osx/a11ytablewrapper.mm
new file mode 100644
index 000000000..e7cd02fce
--- /dev/null
+++ b/vcl/osx/a11ytablewrapper.mm
@@ -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 .
+ */
+
+
+#include <osx/a11yfactory.h>
+
+#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();
+
+ if( nRows * nCols < 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_Int32 idxTopLeft = rAccessibleTopLeft -> getAccessibleContext() -> getAccessibleIndexInParent();
+ sal_Int32 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 );
+ // 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();
+
+
+ if( nRows*nCols < 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();
+ if( nRows * nCols < 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();
+ if( nRows * nCols < 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 000000000..4170f5f8a
--- /dev/null
+++ b/vcl/osx/a11ytextattributeswrapper.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 .
+ */
+
+#ifndef INCLUDED_VCL_OSX_A11YTEXTATTRIBUTESWRAPPER_H
+#define INCLUDED_VCL_OSX_A11YTEXTATTRIBUTESWRAPPER_H
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yTextAttributesWrapper : NSObject
+{
+}
++(NSMutableAttributedString *)createAttributedStringForElement:(AquaA11yWrapper *)wrapper inOrigRange:(id)origRange;
+@end
+#endif // INCLUDED_VCL_OSX_A11YTEXTATTRIBUTESWRAPPER_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ytextattributeswrapper.mm b/vcl/osx/a11ytextattributeswrapper.mm
new file mode 100644
index 000000000..433906d7d
--- /dev/null
+++ b/vcl/osx/a11ytextattributeswrapper.mm
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 >)attributes toString:(NSMutableAttributedString *)string forRange:(NSRange)range fontDescriptor:(AquaA11yFontDescriptor*)fontDescriptor {
+ NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ];
+ // constants
+ static const OUString attrUnderline("CharUnderline");
+ static const OUString attrBold("CharWeight");
+ static const OUString attrFontname("CharFontName");
+ static const OUString attrItalic("CharPosture");
+ static const OUString attrHeight("CharHeight");
+ static const OUString attrStrikethrough("CharStrikeout");
+ static const OUString attrShadow("CharShadowed");
+ static const OUString attrUnderlineColor("CharUnderlineColor");
+ static const OUString attrUnderlineHasColor("CharUnderlineHasColor");
+ static const OUString attrForegroundColor("CharColor");
+ static const OUString attrBackgroundColor("CharBackColor");
+ static const OUString attrSuperscript("CharEscapement");
+ static const OUString attrTextAlignment("ParaAdjust");
+ // vars
+ sal_Int32 underlineColor = 0;
+ bool underlineHasColor = false;
+ // add attributes to string
+ for ( const PropertyValue& property : attributes ) {
+ // TODO: NSAccessibilityMisspelledTextAttribute, NSAccessibilityAttachmentTextAttribute, NSAccessibilityLinkTextAttribute
+ // NSAccessibilityStrikethroughColorTextAttribute is unsupported by UNP-API
+ if ( property.Value.hasValue() ) {
+ if ( property.Name.equals ( attrUnderline ) ) {
+ int style = [ AquaA11yTextAttributesWrapper convertUnderlineStyle: property ];
+ if ( style != NSUnderlineStyleNone ) {
+ [ string addAttribute: NSAccessibilityUnderlineTextAttribute value: [ NSNumber numberWithInt: style ] range: range ];
+ }
+ } else if ( property.Name.equals ( attrFontname ) ) {
+ OUString fontname;
+ property.Value >>= fontname;
+ [fontDescriptor setName:CreateNSString(fontname)];
+ } else if ( property.Name.equals ( attrBold ) ) {
+ [fontDescriptor setBold:[AquaA11yTextAttributesWrapper convertBoldStyle:property]];
+ } else if ( property.Name.equals ( attrItalic ) ) {
+ [fontDescriptor setItalic:[AquaA11yTextAttributesWrapper convertItalicStyle:property]];
+ } else if ( property.Name.equals ( attrHeight ) ) {
+ float size;
+ property.Value >>= size;
+ [fontDescriptor setSize:size];
+ } else if ( property.Name.equals ( attrStrikethrough ) ) {
+ if ( [ AquaA11yTextAttributesWrapper isStrikethrough: property ] ) {
+ [ string addAttribute: NSAccessibilityStrikethroughTextAttribute value: [ NSNumber numberWithBool: YES ] range: range ];
+ }
+ } else if ( property.Name.equals ( attrShadow ) ) {
+ if ( [ AquaA11yTextAttributesWrapper convertBoolean: property ] ) {
+ [ string addAttribute: NSAccessibilityShadowTextAttribute value: [ NSNumber numberWithBool: YES ] range: range ];
+ }
+ } else if ( property.Name.equals ( attrUnderlineColor ) ) {
+ property.Value >>= underlineColor;
+ } else if ( property.Name.equals ( attrUnderlineHasColor ) ) {
+ underlineHasColor = [ AquaA11yTextAttributesWrapper convertBoolean: property ];
+ } else if ( property.Name.equals ( attrForegroundColor ) ) {
+ [ AquaA11yTextAttributesWrapper addColor: property.Value.get<sal_Int32>() forAttribute: NSAccessibilityForegroundColorTextAttribute andRange: range toString: string ];
+ } else if ( property.Name.equals ( attrBackgroundColor ) ) {
+ [ AquaA11yTextAttributesWrapper addColor: property.Value.get<sal_Int32>() forAttribute: NSAccessibilityBackgroundColorTextAttribute andRange: range toString: string ];
+ } else if ( property.Name.equals ( attrSuperscript ) ) {
+ // 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.equals ( attrTextAlignment ) ) {
+ sal_Int32 alignment;
+ property.Value >>= alignment;
+ NSNumber *textAlignment = nil;
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // 'NSCenterTextAlignment' is deprecated: first deprecated in macOS 10.12
+ // 'NSJustifiedTextAlignment' is deprecated: first deprecated in macOS 10.12
+ // 'NSLeftTextAlignment' is deprecated: first deprecated in macOS 10.12
+ // 'NSRightTextAlignment' is deprecated: first deprecated in macOS 10.12
+ switch(static_cast<css::style::ParagraphAdjust>(alignment)) {
+ case css::style::ParagraphAdjust_RIGHT : textAlignment = [NSNumber numberWithInteger:NSRightTextAlignment] ; break;
+ case css::style::ParagraphAdjust_CENTER: textAlignment = [NSNumber numberWithInteger:NSCenterTextAlignment] ; break;
+ case css::style::ParagraphAdjust_BLOCK : textAlignment = [NSNumber numberWithInteger:NSJustifiedTextAlignment]; break;
+ case css::style::ParagraphAdjust_LEFT :
+ default : textAlignment = [NSNumber numberWithInteger:NSLeftTextAlignment] ; break;
+ }
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ NSDictionary *paragraphStyle = [NSDictionary dictionaryWithObjectsAndKeys:textAlignment, @"AXTextAlignment", textAlignment, @"AXVisualTextAlignment", nil];
+ [string addAttribute:@"AXParagraphStyle" value:paragraphStyle range:range];
+ }
+ }
+ }
+ // add underline information
+ if ( underlineHasColor ) {
+ [ AquaA11yTextAttributesWrapper addColor: 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:(long)type toString:(NSMutableAttributedString*)string inRange:(NSRange)range {
+ const long markupCount = markup->getTextMarkupCount(type);
+ for (long 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];
+}
+
++(NSMutableAttributedString *)createAttributedStringForElement:(AquaA11yWrapper *)wrapper inOrigRange:(id)origRange {
+ 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 ) ) ];
+ 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 000000000..5d2154ff6
--- /dev/null
+++ b/vcl/osx/a11ytextwrapper.h
@@ -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_OSX_A11YTEXTWRAPPER_H
+#define INCLUDED_VCL_OSX_A11YTEXTWRAPPER_H
+
+#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
+
+#endif // INCLUDED_VCL_OSX_A11YTEXTWRAPPER_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ytextwrapper.mm b/vcl/osx/a11ytextwrapper.mm
new file mode 100644
index 000000000..a39037f07
--- /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 {
+ long 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 000000000..56778e76d
--- /dev/null
+++ b/vcl/osx/a11yutil.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 .
+ */
+
+#ifndef INCLUDED_VCL_OSX_A11YUTIL_H
+#define INCLUDED_VCL_OSX_A11YUTIL_H
+
+#include <com/sun/star/awt/Point.hpp>
+
+@interface AquaA11yUtil : NSObject {
+}
++(NSValue *)vclPointToNSPoint:(css::awt::Point)vclPoint;
++(css::awt::Point)nsPointToVclPoint:(NSValue *)nsPoint;
+@end
+
+#endif // INCLUDED_VCL_OSX_A11YUTIL_H
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11yutil.mm b/vcl/osx/a11yutil.mm
new file mode 100644
index 000000000..de0389a19
--- /dev/null
+++ b/vcl/osx/a11yutil.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 <osx/osxvcltypes.h>
+
+#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<long>([ nsPoint pointValue ].x), static_cast<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 000000000..b4db1de96
--- /dev/null
+++ b/vcl/osx/a11yvaluewrapper.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 .
+ */
+
+#ifndef INCLUDED_VCL_OSX_A11YVALUEWRAPPER_H
+#define INCLUDED_VCL_OSX_A11YVALUEWRAPPER_H
+
+#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
+
+#endif // INCLUDED_VCL_OSX_A11YVALUEWRAPPER_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11yvaluewrapper.mm b/vcl/osx/a11yvaluewrapper.mm
new file mode 100644
index 000000000..4882cd2ef
--- /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 ] ) {
+ long 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 ] ) {
+ long 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 ] ) {
+ long 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 000000000..d3a42058d
--- /dev/null
+++ b/vcl/osx/a11ywrapper.mm
@@ -0,0 +1,1147 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <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/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 <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 : NSView
+
+#pragma mark -
+#pragma mark Init and dealloc
+
+-(id)initWithAccessibleContext: (Reference < XAccessibleContext >) rxAccessibleContext {
+ self = [ super init ];
+ if ( self ) {
+ [ self setDefaults: rxAccessibleContext ];
+ }
+ return self;
+}
+
+-(void) setDefaults: (Reference < XAccessibleContext >) rxAccessibleContext {
+ mpReferenceWrapper = new ReferenceWrapper;
+ mActsAsRadioGroup = NO;
+ mpReferenceWrapper -> rAccessibleContext = rxAccessibleContext;
+ mIsTableCell = NO;
+ // Querying all supported interfaces
+ try {
+ // XAccessibleComponent
+ mpReferenceWrapper->rAccessibleComponent.set( rxAccessibleContext, UNO_QUERY );
+ // XAccessibleExtendedComponent
+ mpReferenceWrapper->rAccessibleExtendedComponent.set( rxAccessibleContext, UNO_QUERY );
+ // XAccessibleSelection
+ mpReferenceWrapper->rAccessibleSelection.set( rxAccessibleContext, UNO_QUERY );
+ // XAccessibleTable
+ mpReferenceWrapper->rAccessibleTable.set( rxAccessibleContext, UNO_QUERY );
+ // XAccessibleText
+ mpReferenceWrapper->rAccessibleText.set( rxAccessibleContext, UNO_QUERY );
+ // XAccessibleEditableText
+ mpReferenceWrapper->rAccessibleEditableText.set( rxAccessibleContext, UNO_QUERY );
+ // XAccessibleValue
+ mpReferenceWrapper->rAccessibleValue.set( rxAccessibleContext, UNO_QUERY );
+ // XAccessibleAction
+ mpReferenceWrapper->rAccessibleAction.set( rxAccessibleContext, UNO_QUERY );
+ // XAccessibleTextAttributes
+ mpReferenceWrapper->rAccessibleTextAttributes.set( rxAccessibleContext, UNO_QUERY );
+ // XAccessibleMultiLineText
+ mpReferenceWrapper->rAccessibleMultiLineText.set( rxAccessibleContext, UNO_QUERY );
+ // XAccessibleTextMarkup
+ mpReferenceWrapper->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() -> contains ( 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 ) {
+ }
+}
+
+-(void)dealloc {
+ if ( mpReferenceWrapper ) {
+ delete mpReferenceWrapper;
+ }
+ [ super dealloc ];
+}
+
+#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 {
+ if ( [ self accessibleContext ] -> getAccessibleStateSet().is() ) {
+ return [ NSNumber numberWithBool: [ self accessibleContext ] -> getAccessibleStateSet() -> contains ( AccessibleStateType::ENABLED ) ];
+ } 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() && rxContext -> getAccessibleStateSet().is() ) {
+ isFocused = [ NSNumber numberWithBool: rxContext -> getAccessibleStateSet() -> contains ( AccessibleStateType::FOCUSED ) ];
+ }
+ }
+ return isFocused;
+ } else if ( [ self accessibleContext ] -> getAccessibleStateSet().is() ) {
+ return [ NSNumber numberWithBool: [ self accessibleContext ] -> getAccessibleStateSet() -> contains ( AccessibleStateType::FOCUSED ) ];
+ } 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 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 ] );
+
+ sal_Int32 cnt = xContext -> getAccessibleChildCount();
+ for ( sal_Int32 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 ];
+ }
+ }
+ }
+
+ // 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 {
+ return [ NSNumber numberWithBool: [ self accessibleContext ] -> getAccessibleStateSet() -> contains ( AccessibleStateType::EXPANDED ) ];
+}
+
+-(id)selectedAttribute {
+ return [ NSNumber numberWithBool: [ self accessibleContext ] -> getAccessibleStateSet() -> contains ( AccessibleStateType::SELECTED ) ];
+}
+
+-(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;
+ Reference < XAccessibleStateSet > stateSet = [ self accessibleContext ] -> getAccessibleStateSet();
+ if ( stateSet -> contains ( AccessibleStateType::HORIZONTAL ) ) {
+ orientation = NSAccessibilityHorizontalOrientationValue;
+ } else if ( stateSet -> contains ( 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 {
+ 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 {
+ SAL_INFO("vcl.a11y", "[" << self << " accessibilityIsIgnored]");
+ // #i90575# guard NSAccessibility protocol against unwanted access
+ if ( isPopupMenuOpen ) {
+ return NO;
+ }
+ bool ignored = false;
+ 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() -> contains ( AccessibleStateType::VISIBLE ) );
+ break;
+ }
+ return ignored; // TODO: to be completed
+}
+
+-(NSArray *)accessibilityAttributeNames {
+ 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_Int32 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 ];
+ }
+ [ nativeSubrole release ];
+ [ title release ];
+ return attributeNames;
+ } catch ( DisposedException & ) { // Object is no longer available
+ if ( nativeSubrole ) {
+ [ nativeSubrole release ];
+ }
+ if ( title ) {
+ [ title release ];
+ }
+ if ( attributeNames ) {
+ [ attributeNames release ];
+ }
+ [ AquaA11yFactory removeFromWrapperRepositoryFor: [ self accessibleContext ] ];
+ return [ [ NSArray alloc ] init ];
+ }
+}
+
+-(BOOL)accessibilityIsAttributeSettable:(NSString *)attribute {
+ 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 {
+ SAL_INFO("vcl.a11y", "[" << self << " accessibilityParameterizedAttributeNames]");
+ NSMutableArray * attributeNames = [ [ NSMutableArray alloc ] init ];
+ // Special Attributes depending on interface
+ if ( [ self accessibleText ] ) {
+ [ AquaA11yTextWrapper addParameterizedAttributeNamesTo: attributeNames ];
+ }
+ return attributeNames; // TODO: to be completed
+}
+
+-(id)accessibilityAttributeValue:(NSString *)attribute forParameter:(id)parameter {
+ 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 ] ) {
+ return [ self performSelector: methodSelector withObject: parameter ];
+ }
+ 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 {
+ 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 {
+ 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 {
+ SAL_INFO("vcl.a11y", "[" << self << " accessibilityPerformAction:" << action << "]");
+ AquaA11yWrapper * actionResponder = [ self actionResponder ];
+ if ( actionResponder ) {
+ [ AquaA11yActionWrapper doAction: action ofElement: actionResponder ];
+ }
+}
+
+-(NSArray *)accessibilityActionNames {
+ 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_Int32 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.
+ Reference< XAccessibleStateSet > xStateSet;
+ xStateSet = rxAccessibleContext -> getAccessibleStateSet();
+ if (xStateSet.is() && xStateSet -> contains(AccessibleStateType::MANAGES_DESCENDANTS ) )
+ bSafeToIterate = false;
+ }
+
+ if( bSafeToIterate ) {
+ for ( int 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 ( RuntimeException ) {
+ return emptyReference;
+ }
+ return hitChild;
+}
+
+-(id)accessibilityHitTest:(NSPoint)point {
+ 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<long>(point.x) , static_cast<long>(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 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 ( !mpReferenceWrapper ) {
+ [ self setDefaults: [ self accessibleContext ] ];
+ }
+ hitChild = hitTestRunner ( hitPoint, mpReferenceWrapper -> 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 mpReferenceWrapper -> rAccessibleAction.get();
+}
+
+-(XAccessibleContext *)accessibleContext {
+ return mpReferenceWrapper -> rAccessibleContext.get();
+}
+
+-(XAccessibleComponent *)accessibleComponent {
+ return mpReferenceWrapper -> rAccessibleComponent.get();
+}
+
+-(XAccessibleExtendedComponent *)accessibleExtendedComponent {
+ return mpReferenceWrapper -> rAccessibleExtendedComponent.get();
+}
+
+-(XAccessibleSelection *)accessibleSelection {
+ return mpReferenceWrapper -> rAccessibleSelection.get();
+}
+
+-(XAccessibleTable *)accessibleTable {
+ return mpReferenceWrapper -> rAccessibleTable.get();
+}
+
+-(XAccessibleText *)accessibleText {
+ return mpReferenceWrapper -> rAccessibleText.get();
+}
+
+-(XAccessibleEditableText *)accessibleEditableText {
+ return mpReferenceWrapper -> rAccessibleEditableText.get();
+}
+
+-(XAccessibleValue *)accessibleValue {
+ return mpReferenceWrapper -> rAccessibleValue.get();
+}
+
+-(XAccessibleTextAttributes *)accessibleTextAttributes {
+ return mpReferenceWrapper -> rAccessibleTextAttributes.get();
+}
+
+-(XAccessibleMultiLineText *)accessibleMultiLineText {
+ return mpReferenceWrapper -> rAccessibleMultiLineText.get();
+}
+
+-(XAccessibleTextMarkup *)accessibleTextMarkup {
+ return mpReferenceWrapper -> rAccessibleTextMarkup.get();
+}
+
+-(NSWindow*)windowForParent {
+ return [self window];
+}
+
+-(void)setActsAsRadioGroup:(BOOL)actsAsRadioGroup {
+ mActsAsRadioGroup = actsAsRadioGroup;
+}
+
+-(BOOL)actsAsRadioGroup {
+ return mActsAsRadioGroup;
+}
+
++(void)setPopupMenuOpen:(BOOL)popupMenuOpen {
+ isPopupMenuOpen = popupMenuOpen;
+}
+
+@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 000000000..23103cd92
--- /dev/null
+++ b/vcl/osx/a11ywrapperbutton.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_OSX_A11YWRAPPERBUTTON_H
+#define INCLUDED_VCL_OSX_A11YWRAPPERBUTTON_H
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperButton : AquaA11yWrapper
+{
+}
+-(id)valueAttribute;
+-(id)descriptionAttribute;
+-(NSArray *)accessibilityAttributeNames;
+@end
+
+#endif // INCLUDED_VCL_OSX_A11YWRAPPERBUTTON_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperbutton.mm b/vcl/osx/a11ywrapperbutton.mm
new file mode 100644
index 000000000..ca5f16af3
--- /dev/null
+++ b/vcl/osx/a11ywrapperbutton.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 <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 {
+ // 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 000000000..6f20a888b
--- /dev/null
+++ b/vcl/osx/a11ywrappercheckbox.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_OSX_A11YWRAPPERCHECKBOX_H
+#define INCLUDED_VCL_OSX_A11YWRAPPERCHECKBOX_H
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperCheckBox : AquaA11yWrapper
+{
+}
+-(id)valueAttribute;
+-(BOOL)accessibilityIsAttributeSettable:(NSString *)attribute;
+-(NSArray *)accessibilityAttributeNames;
+@end
+
+#endif // INCLUDED_VCL_OSX_A11YWRAPPERCHECKBOX_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrappercheckbox.mm b/vcl/osx/a11ywrappercheckbox.mm
new file mode 100644
index 000000000..d67c5b610
--- /dev/null
+++ b/vcl/osx/a11ywrappercheckbox.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 <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 {
+ if ( [ attribute isEqualToString: NSAccessibilityValueAttribute ] ) {
+ return NO;
+ }
+ return [ super accessibilityIsAttributeSettable: attribute ];
+}
+
+-(NSArray *)accessibilityAttributeNames {
+ // 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 000000000..7995f06b6
--- /dev/null
+++ b/vcl/osx/a11ywrappercombobox.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_OSX_A11YWRAPPERCOMBOBOX_H
+#define INCLUDED_VCL_OSX_A11YWRAPPERCOMBOBOX_H
+
+#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
+
+#endif // INCLUDED_VCL_OSX_A11YWRAPPERCOMBOBOX_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrappercombobox.mm b/vcl/osx/a11ywrappercombobox.mm
new file mode 100644
index 000000000..962a66914
--- /dev/null
+++ b/vcl/osx/a11ywrappercombobox.mm
@@ -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 <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 {
+ 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 {
+ 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 {
+ // 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 000000000..5309c155b
--- /dev/null
+++ b/vcl/osx/a11ywrappergroup.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 .
+ */
+
+#ifndef INCLUDED_VCL_OSX_A11YWRAPPERGROUP_H
+#define INCLUDED_VCL_OSX_A11YWRAPPERGROUP_H
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperGroup : AquaA11yWrapper
+{
+}
+-(id)titleUIElementAttribute;
+-(NSArray *)accessibilityAttributeNames;
+@end
+
+#endif // INCLUDED_VCL_OSX_A11YWRAPPERGROUP_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrappergroup.mm b/vcl/osx/a11ywrappergroup.mm
new file mode 100644
index 000000000..39cbd9adf
--- /dev/null
+++ b/vcl/osx/a11ywrappergroup.mm
@@ -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 <osx/salinst.h>
+#include "a11ywrappergroup.h"
+
+// Wrapper for AXGroup role
+
+@implementation AquaA11yWrapperGroup : AquaA11yWrapper
+
+-(id)titleUIElementAttribute {
+ return self; // TODO
+}
+
+-(NSArray *)accessibilityAttributeNames {
+ // 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 000000000..8864555f2
--- /dev/null
+++ b/vcl/osx/a11ywrapperlist.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 .
+ */
+
+#ifndef INCLUDED_VCL_OSX_A11YWRAPPERLIST_H
+#define INCLUDED_VCL_OSX_A11YWRAPPERLIST_H
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperList : AquaA11yWrapper
+{
+}
+-(NSArray *)accessibilityAttributeNames;
+@end
+
+#endif // INCLUDED_VCL_OSX_A11YWRAPPERLIST_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperlist.mm b/vcl/osx/a11ywrapperlist.mm
new file mode 100644
index 000000000..9b0bac733
--- /dev/null
+++ b/vcl/osx/a11ywrapperlist.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 <osx/salinst.h>
+#include "a11ywrapperlist.h"
+
+using namespace ::com::sun::star::accessibility;
+
+// Wrapper for AXList role
+
+@implementation AquaA11yWrapperList : AquaA11yWrapper
+
+-(NSArray *)accessibilityAttributeNames {
+ // 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 000000000..ec8999f36
--- /dev/null
+++ b/vcl/osx/a11ywrapperradiobutton.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_OSX_A11YWRAPPERRADIOBUTTON_H
+#define INCLUDED_VCL_OSX_A11YWRAPPERRADIOBUTTON_H
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperRadioButton : AquaA11yWrapper
+{
+}
+-(id)valueAttribute;
+-(BOOL)accessibilityIsAttributeSettable:(NSString *)attribute;
+-(NSArray *)accessibilityAttributeNames;
+@end
+
+#endif // INCLUDED_VCL_OSX_A11YWRAPPERRADIOBUTTON_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperradiobutton.mm b/vcl/osx/a11ywrapperradiobutton.mm
new file mode 100644
index 000000000..21fd9529b
--- /dev/null
+++ b/vcl/osx/a11ywrapperradiobutton.mm
@@ -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 <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 {
+ if ( [ attribute isEqualToString: NSAccessibilityValueAttribute ] ) {
+ return NO;
+ }
+ return [ super accessibilityIsAttributeSettable: attribute ];
+}
+
+-(NSArray *)accessibilityAttributeNames {
+ // 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 000000000..98e5a5cf5
--- /dev/null
+++ b/vcl/osx/a11ywrapperradiogroup.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 .
+ */
+
+#ifndef INCLUDED_VCL_OSX_A11YWRAPPERRADIOGROUP_H
+#define INCLUDED_VCL_OSX_A11YWRAPPERRADIOGROUP_H
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperRadioGroup : AquaA11yWrapper
+{
+}
+-(NSArray *)accessibilityAttributeNames;
+@end
+
+#endif // INCLUDED_VCL_OSX_A11YWRAPPERRADIOGROUP_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperradiogroup.mm b/vcl/osx/a11ywrapperradiogroup.mm
new file mode 100644
index 000000000..557e0b1bc
--- /dev/null
+++ b/vcl/osx/a11ywrapperradiogroup.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 <osx/salinst.h>
+#include "a11ywrapperradiogroup.h"
+#include "a11ytextwrapper.h"
+
+// Wrapper for AXRadioGroup role
+
+@implementation AquaA11yWrapperRadioGroup : AquaA11yWrapper
+
+-(NSArray *)accessibilityAttributeNames {
+ // 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 000000000..00e32f4df
--- /dev/null
+++ b/vcl/osx/a11ywrapperrow.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 .
+ */
+
+#ifndef INCLUDED_VCL_OSX_A11YWRAPPERROW_H
+#define INCLUDED_VCL_OSX_A11YWRAPPERROW_H
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperRow : AquaA11yWrapper
+{
+}
+-(id)disclosingAttribute;
+-(NSArray *)accessibilityAttributeNames;
+@end
+
+#endif // INCLUDED_VCL_OSX_A11YWRAPPERROW_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperrow.mm b/vcl/osx/a11ywrapperrow.mm
new file mode 100644
index 000000000..1b7ee56dd
--- /dev/null
+++ b/vcl/osx/a11ywrapperrow.mm
@@ -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 <osx/salinst.h>
+
+#include "a11ywrapperrow.h"
+#include "a11ytextwrapper.h"
+
+// Wrapper for AXRow role
+
+@implementation AquaA11yWrapperRow : AquaA11yWrapper
+
+-(id)disclosingAttribute {
+ // TODO: implement
+ return nil;
+}
+
+-(NSArray *)accessibilityAttributeNames {
+ // 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 000000000..c1556f9a9
--- /dev/null
+++ b/vcl/osx/a11ywrapperscrollarea.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_OSX_A11YWRAPPERSCROLLAREA_H
+#define INCLUDED_VCL_OSX_A11YWRAPPERSCROLLAREA_H
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperScrollArea : AquaA11yWrapper
+{
+}
+-(id)verticalScrollBarAttribute;
+-(id)horizontalScrollBarAttribute;
+-(NSArray *)accessibilityAttributeNames;
+@end
+
+#endif // INCLUDED_VCL_OSX_A11YWRAPPERSCROLLAREA_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperscrollarea.mm b/vcl/osx/a11ywrapperscrollarea.mm
new file mode 100644
index 000000000..b0b963a6b
--- /dev/null
+++ b/vcl/osx/a11ywrapperscrollarea.mm
@@ -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 .
+ */
+
+
+#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 {
+ // 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 000000000..88beceb4f
--- /dev/null
+++ b/vcl/osx/a11ywrapperscrollbar.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 .
+ */
+
+#ifndef INCLUDED_VCL_OSX_A11YWRAPPERSCROLLBAR_H
+#define INCLUDED_VCL_OSX_A11YWRAPPERSCROLLBAR_H
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperScrollBar : AquaA11yWrapper
+{
+}
+-(NSArray *)accessibilityAttributeNames;
+@end
+
+#endif // INCLUDED_VCL_OSX_A11YWRAPPERSCROLLBAR_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperscrollbar.mm b/vcl/osx/a11ywrapperscrollbar.mm
new file mode 100644
index 000000000..a4b7a246b
--- /dev/null
+++ b/vcl/osx/a11ywrapperscrollbar.mm
@@ -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 <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 {
+ // 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 000000000..22397e869
--- /dev/null
+++ b/vcl/osx/a11ywrappersplitter.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 .
+ */
+
+#ifndef INCLUDED_VCL_OSX_A11YWRAPPERSPLITTER_H
+#define INCLUDED_VCL_OSX_A11YWRAPPERSPLITTER_H
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperSplitter : AquaA11yWrapper
+{
+}
+-(NSArray *)accessibilityAttributeNames;
+@end
+
+#endif // INCLUDED_VCL_OSX_A11YWRAPPERSPLITTER_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrappersplitter.mm b/vcl/osx/a11ywrappersplitter.mm
new file mode 100644
index 000000000..b43928bd4
--- /dev/null
+++ b/vcl/osx/a11ywrappersplitter.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 <osx/salinst.h>
+#include "a11ywrappersplitter.h"
+
+using namespace ::com::sun::star::accessibility;
+
+// Wrapper for AXSplitter role
+
+@implementation AquaA11yWrapperSplitter : AquaA11yWrapper
+
+-(NSArray *)accessibilityAttributeNames {
+ // 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 000000000..c02a1f3b8
--- /dev/null
+++ b/vcl/osx/a11ywrapperstatictext.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 .
+ */
+
+#ifndef INCLUDED_VCL_OSX_A11YWRAPPERSTATICTEXT_H
+#define INCLUDED_VCL_OSX_A11YWRAPPERSTATICTEXT_H
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperStaticText : AquaA11yWrapper
+{
+}
+-(id)titleAttribute;
+-(NSArray *)accessibilityAttributeNames;
+@end
+
+#endif // INCLUDED_VCL_OSX_A11YWRAPPERSTATICTEXT_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperstatictext.mm b/vcl/osx/a11ywrapperstatictext.mm
new file mode 100644
index 000000000..cd4728544
--- /dev/null
+++ b/vcl/osx/a11ywrapperstatictext.mm
@@ -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 <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 {
+ // 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 000000000..38c12ed21
--- /dev/null
+++ b/vcl/osx/a11ywrappertabgroup.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 .
+ */
+
+#ifndef INCLUDED_VCL_OSX_A11YWRAPPERTABGROUP_H
+#define INCLUDED_VCL_OSX_A11YWRAPPERTABGROUP_H
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperTabGroup : AquaA11yWrapper
+{
+}
+-(NSArray *)accessibilityAttributeNames;
+@end
+
+#endif // INCLUDED_VCL_OSX_A11YWRAPPERTABGROUP_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrappertabgroup.mm b/vcl/osx/a11ywrappertabgroup.mm
new file mode 100644
index 000000000..ad5971865
--- /dev/null
+++ b/vcl/osx/a11ywrappertabgroup.mm
@@ -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 <osx/salinst.h>
+#include "a11ywrappertabgroup.h"
+
+// Wrapper for AXTabGroup role
+
+@implementation AquaA11yWrapperTabGroup : AquaA11yWrapper
+
+-(NSArray *)accessibilityAttributeNames {
+ // 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 000000000..325284973
--- /dev/null
+++ b/vcl/osx/a11ywrappertextarea.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 .
+ */
+
+#ifndef INCLUDED_VCL_OSX_A11YWRAPPERTEXTAREA_H
+#define INCLUDED_VCL_OSX_A11YWRAPPERTEXTAREA_H
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperTextArea : AquaA11yWrapper
+{
+}
+-(NSArray *)accessibilityAttributeNames;
+@end
+
+#endif // INCLUDED_VCL_OSX_A11YWRAPPERTEXTAREA_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrappertextarea.mm b/vcl/osx/a11ywrappertextarea.mm
new file mode 100644
index 000000000..3f51f3541
--- /dev/null
+++ b/vcl/osx/a11ywrappertextarea.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 <osx/salinst.h>
+#include "a11ywrappertextarea.h"
+
+// Wrapper for AXTextArea role
+
+@implementation AquaA11yWrapperTextArea : AquaA11yWrapper
+
+-(NSArray *)accessibilityAttributeNames {
+ // 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 000000000..d8871a507
--- /dev/null
+++ b/vcl/osx/a11ywrappertoolbar.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 .
+ */
+
+#ifndef INCLUDED_VCL_OSX_A11YWRAPPERTOOLBAR_H
+#define INCLUDED_VCL_OSX_A11YWRAPPERTOOLBAR_H
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperToolbar : AquaA11yWrapper
+{
+}
+-(NSArray *)accessibilityAttributeNames;
+@end
+
+#endif // INCLUDED_VCL_OSX_A11YWRAPPERTOOLBAR_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrappertoolbar.mm b/vcl/osx/a11ywrappertoolbar.mm
new file mode 100644
index 000000000..05e9b30e2
--- /dev/null
+++ b/vcl/osx/a11ywrappertoolbar.mm
@@ -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 <osx/salinst.h>
+#include "a11ywrappertoolbar.h"
+
+// Wrapper for AXToolbar role
+
+@implementation AquaA11yWrapperToolbar : AquaA11yWrapper
+
+-(NSArray *)accessibilityAttributeNames {
+ // 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 000000000..c2adb5bfc
--- /dev/null
+++ b/vcl/osx/clipboard.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 "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);
+
+ // 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()) :
+ [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(static_cast<OWeakObject*>(this), 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.get() != nullptr)
+ {
+ 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 000000000..2bfbd6cf6
--- /dev/null
+++ b/vcl/osx/clipboard.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 .
+ */
+
+#ifndef INCLUDED_VCL_OSX_CLIPBOARD_HXX
+#define INCLUDED_VCL_OSX_CLIPBOARD_HXX
+
+#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;
+};
+
+#endif // INCLUDED_VCL_OSX_CLIPBOARD_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/cuidraw.hxx b/vcl/osx/cuidraw.hxx
new file mode 100644
index 000000000..02cd2cdc3
--- /dev/null
+++ b/vcl/osx/cuidraw.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_OSX_CUIDRAW_HXX
+#define INCLUDED_VCL_OSX_CUIDRAW_HXX
+
+#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
+
+#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 000000000..494a3a945
--- /dev/null
+++ b/vcl/osx/documentfocuslistener.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 "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_Int16 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() )
+ {
+ return xParentContext->getAccessibleChild( xContext->getAccessibleIndexInParent() );
+ }
+ }
+ }
+
+ 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() )
+ {
+ Reference< XAccessibleStateSet > xStateSet = xContext->getAccessibleStateSet();
+
+ if( xStateSet.is() )
+ attachRecursive(xAccessible, xContext, xStateSet);
+ }
+}
+
+void DocumentFocusListener::attachRecursive(
+ const Reference< XAccessible >& xAccessible,
+ const Reference< XAccessibleContext >& xContext,
+ const Reference< XAccessibleStateSet >& xStateSet
+)
+{
+ if( xStateSet->contains(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( ! xStateSet->contains(AccessibleStateType::MANAGES_DESCENDANTS ) )
+ {
+ sal_Int32 n, nmax = xContext->getAccessibleChildCount();
+ for( n = 0; n < nmax; n++ )
+ {
+ Reference< XAccessible > xChild( xContext->getAccessibleChild( n ) );
+
+ if( xChild.is() )
+ attachRecursive(xChild);
+ }
+ }
+ }
+}
+
+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
+)
+{
+ Reference< XAccessibleStateSet > xStateSet = xContext->getAccessibleStateSet();
+
+ if( xStateSet.is() )
+ detachRecursive(xAccessible, xContext, xStateSet);
+}
+
+void DocumentFocusListener::detachRecursive(
+ const Reference< XAccessible >&,
+ const Reference< XAccessibleContext >& xContext,
+ const Reference< XAccessibleStateSet >& xStateSet
+)
+{
+ Reference< XAccessibleEventBroadcaster > xBroadcaster(xContext, UNO_QUERY);
+
+ if( xBroadcaster.is() && 0 < m_aRefList.erase(xBroadcaster) )
+ {
+ xBroadcaster->removeAccessibleEventListener(static_cast< XAccessibleEventListener *>(this));
+
+ if( ! xStateSet->contains(AccessibleStateType::MANAGES_DESCENDANTS ) )
+ {
+ sal_Int32 n, nmax = xContext->getAccessibleChildCount();
+ for( n = 0; n < nmax; n++ )
+ {
+ Reference< XAccessible > xChild( xContext->getAccessibleChild( n ) );
+
+ if( xChild.is() )
+ detachRecursive(xChild);
+ }
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/documentfocuslistener.hxx b/vcl/osx/documentfocuslistener.hxx
new file mode 100644
index 000000000..93bc8136a
--- /dev/null
+++ b/vcl/osx/documentfocuslistener.hxx
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_VCL_OSX_DOCUMENTFOCUSLISTENER_HXX
+#define INCLUDED_VCL_OSX_DOCUMENTFOCUSLISTENER_HXX
+
+#include <com/sun/star/accessibility/XAccessibleEventListener.hpp>
+
+#include <cppuhelper/implbase.hxx>
+
+#include <osx/a11yfocustracker.hxx>
+
+#include <set>
+
+
+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,
+ const css::uno::Reference< css::accessibility::XAccessibleStateSet >& xStateSet
+ );
+
+ /// @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,
+ const css::uno::Reference< css::accessibility::XAccessibleStateSet >& xStateSet
+ );
+
+ /// @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:
+ std::set< css::uno::Reference< css::uno::XInterface > > m_aRefList;
+
+ AquaA11yFocusTracker& m_aFocusTracker;
+};
+
+#endif // INCLUDED_VCL_OSX_DOCUMENTFOCUSLISTENER_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/printaccessoryview.mm b/vcl/osx/printaccessoryview.mm
new file mode 100644
index 000000000..932b65dd3
--- /dev/null
+++ b/vcl/osx/printaccessoryview.mm
@@ -0,0 +1,1268 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <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>
+
+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( rText.getToken( 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;
+ long nOffset;
+ NSControl* pSubControl;
+
+ ColumnItem( NSControl* i_pControl = nil, long i_nOffset = 0, NSControl* i_pSub = nil )
+ : pControl( i_pControl )
+ , nOffset( i_nOffset )
+ , pSubControl( i_pSub )
+ {}
+
+ long getWidth() const
+ {
+ long 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
+ long nLeftWidth = 0;
+ long nRightWidth = 0;
+ for( size_t i = 0; i < rLeftColumn.size(); i++ )
+ {
+ long nW = rLeftColumn[i].getWidth();
+ if( nW > nLeftWidth )
+ nLeftWidth = nW;
+ }
+ for( size_t i = 0; i < rRightColumn.size(); i++ )
+ {
+ long 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];
+ long 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];
+ long 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, long& 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, long rCurX, long& rCurY, long nAttachOffset,
+ const OUString& rText, bool bEnabled,
+ const OUString& rProperty, bool bValue,
+ std::vector<ColumnItem >& rRightColumn,
+ ControllerProperties* pControllerProperties,
+ ControlTarget* pCtrlTarget
+ )
+{
+ NSRect aCheckRect = { { static_cast<CGFloat>(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, long rCurX, long& rCurY, long 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
+ )
+{
+ sal_Int32 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 = { { static_cast<CGFloat>(rCurX + nOff), 0 },
+ { static_cast<CGFloat>(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, long& rCurX, long& rCurY, long /*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, long rCurX, long& rCurY, long nAttachOffset,
+ const OUString& 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
+ )
+{
+ sal_Int32 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 = { { static_cast<CGFloat>(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 == "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;
+ long nCurY = 0;
+ long 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 : 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;
+ bool bEnabled = true;
+ sal_Int64 nMinValue = 0, nMaxValue = 0;
+ long nAttachOffset = 0;
+ bool bIgnore = false;
+
+ for( const beans::PropertyValue& rEntry : 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 == "Enabled" )
+ {
+ bool bValue = true;
+ rEntry.Value >>= bValue;
+ bEnabled = bValue;
+ }
+ 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 000000000..b54e1b056
--- /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 000000000..b9b4b09f6
--- /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 000000000..856429aee
--- /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 000000000..d39d10119
--- /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 000000000..5235f657f
--- /dev/null
+++ b/vcl/osx/saldata.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 <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>
+
+#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 ),
+ mpFontList( 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];
+ NSSize imageSize = NSMakeSize(CGImageGetWidth([imageRep CGImage]), CGImageGetHeight([imageRep CGImage]));
+
+ NSImage * image = [[NSImage alloc] initWithSize:imageSize];
+ [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 );
+
+ 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);
+ assert ([theImage size].width == 128 || [theImage size].width == 32);
+ if ([theImage size].width == 128)
+ {
+ // If we have a 128x128 image, generate scaled versions of it.
+ // This will result in macOS picking a reasonably sized image for different screen dpi.
+ NSSize cursorSize = NSMakeSize(32,32);
+ NSImage *multiResImage = [[NSImage alloc] initWithSize:cursorSize];
+ for (int scale = 1; scale <= 4; scale++) {
+ NSAffineTransform *xform = [[NSAffineTransform alloc] init];
+ [xform scaleBy:scale];
+ id hints = @{ NSImageHintCTM: xform };
+ CGImageRef rasterCGImage = [theImage CGImageForProposedRect:nullptr context:nil hints:hints];
+ NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithCGImage:rasterCGImage];
+ [rep setSize:cursorSize];
+ [multiResImage addRepresentation:rep];
+ }
+ pCurs = [[NSCursor alloc] initWithImage: multiResImage hotSpot: aHotSpot];
+ }
+ else
+ 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 000000000..71cc5a4f1
--- /dev/null
+++ b/vcl/osx/salframe.cxx
@@ -0,0 +1,1828 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <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 <postmac.h>
+
+using namespace std;
+
+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 ),
+ mnTrackingRectTag( 0 ),
+ mrClippingPath( nullptr ),
+ mnICOptions( InputContextFlags::NONE ),
+ mnBlinkCursorDelay ( 500 )
+{
+ mpParent = dynamic_cast<AquaSalFrame*>(pParent);
+
+ initWindowAndView();
+
+ SalData* pSalData = GetSalData();
+ pSalData->mpInstance->insertFrame( this );
+ NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
+ if (userDefaults != nil)
+ {
+ id setting = [userDefaults objectForKey: @"NSTextInsertionPointBlinkPeriodOn"];
+ if (setting)
+ mnBlinkCursorDelay = [setting intValue];
+ else
+ {
+ setting = [userDefaults objectForKey: @"NSTextInsertionPointBlinkPeriodOff"];
+ if (setting)
+ mnBlinkCursorDelay = [setting intValue];
+ }
+ }
+}
+
+AquaSalFrame::~AquaSalFrame()
+{
+ if (mbFullScreen)
+ doShowFullScreen(false, maGeometry.nDisplayScreenNumber);
+
+ 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 ) {
+ [AquaA11yFactory revokeView: mpNSView];
+ [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.nX = static_cast<int>(aVisibleRect.origin.x + aVisibleRect.size.width / 10);
+ maGeometry.nY = static_cast<int>(aVisibleRect.origin.y + aVisibleRect.size.height / 10);
+ maGeometry.nWidth = static_cast<unsigned int>(aVisibleRect.size.width * 0.8);
+ maGeometry.nHeight = static_cast<unsigned int>(aVisibleRect.size.height * 0.8);
+
+ // calculate style mask
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // 'NSBorderlessWindowMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSClosableWindowMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSMiniaturizableWindowMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSResizableWindowMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSTitledWindowMask' is deprecated: first deprecated in macOS 10.12
+ if( (mnStyle & SalFrameStyleFlags::FLOAT) ||
+ (mnStyle & SalFrameStyleFlags::OWNERDRAWDECORATION) )
+ mnStyleMask = NSBorderlessWindowMask;
+ else if( mnStyle & SalFrameStyleFlags::DEFAULT )
+ {
+ mnStyleMask = NSTitledWindowMask |
+ NSMiniaturizableWindowMask |
+ NSResizableWindowMask |
+ NSClosableWindowMask;
+ // make default window "maximized"
+ maGeometry.nX = static_cast<int>(aVisibleRect.origin.x);
+ maGeometry.nY = static_cast<int>(aVisibleRect.origin.y);
+ maGeometry.nWidth = static_cast<int>(aVisibleRect.size.width);
+ maGeometry.nHeight = static_cast<int>(aVisibleRect.size.height);
+ mbPositioned = mbSized = true;
+ }
+ else
+ {
+ if( mnStyle & SalFrameStyleFlags::MOVEABLE )
+ {
+ mnStyleMask |= NSTitledWindowMask;
+ if( mpParent == nullptr )
+ mnStyleMask |= NSMiniaturizableWindowMask;
+ }
+ if( mnStyle & SalFrameStyleFlags::SIZEABLE )
+ mnStyleMask |= NSResizableWindowMask;
+ if( mnStyle & SalFrameStyleFlags::CLOSEABLE )
+ mnStyleMask |= NSClosableWindowMask;
+ // documentation says anything other than NSBorderlessWindowMask (=0)
+ // should also include NSTitledWindowMask;
+ if( mnStyleMask != 0 )
+ mnStyleMask |= NSTitledWindowMask;
+ }
+SAL_WNODEPRECATED_DECLARATIONS_POP
+
+ 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
+ [mpNSWindow setAcceptsMouseMovedEvents: YES];
+ [mpNSWindow setHasShadow: YES];
+
+ [mpNSWindow setDelegate: static_cast<id<NSWindowDelegate> >(mpNSWindow)];
+
+ [mpNSWindow setRestorable:NO];
+ const NSRect aRect = { NSZeroPoint, NSMakeSize( maGeometry.nWidth, maGeometry.nHeight )};
+ mnTrackingRectTag = [mpNSView addTrackingRect: aRect owner: mpNSView userData: nil assumeInside: NO];
+
+ 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.nHeight - (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.nHeight - (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.nHeight - 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.nHeight - io_rPoint.y;
+}
+
+void AquaSalFrame::screenParametersChanged()
+{
+ OSX_SALDATA_RUNINMAIN( screenParametersChanged() )
+
+ 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 )
+ {
+ tools::Rectangle aScreenRect;
+ GetWorkArea( aScreenRect );
+ if( mpParent ) // center relative to parent
+ {
+ // center on parent
+ long nNewX = mpParent->maGeometry.nX + (static_cast<long>(mpParent->maGeometry.nWidth) - static_cast<long>(maGeometry.nWidth))/2;
+ if( nNewX < aScreenRect.Left() )
+ nNewX = aScreenRect.Left();
+ if( long(nNewX + maGeometry.nWidth) > aScreenRect.Right() )
+ nNewX = aScreenRect.Right() - maGeometry.nWidth-1;
+ long nNewY = mpParent->maGeometry.nY + (static_cast<long>(mpParent->maGeometry.nHeight) - static_cast<long>(maGeometry.nHeight))/2;
+ if( nNewY < aScreenRect.Top() )
+ nNewY = aScreenRect.Top();
+ if( nNewY > aScreenRect.Bottom() )
+ nNewY = aScreenRect.Bottom() - maGeometry.nHeight-1;
+ SetPosSize( nNewX - mpParent->maGeometry.nX,
+ nNewY - mpParent->maGeometry.nY,
+ 0, 0, SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y );
+ }
+ else if( ! (mnStyle & SalFrameStyleFlags::SIZEABLE) )
+ {
+ // center on screen
+ long nNewX = (aScreenRect.GetWidth() - maGeometry.nWidth)/2;
+ long nNewY = (aScreenRect.GetHeight() - maGeometry.nHeight)/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
+ [AquaA11yFactory registerView: mpNSView];
+}
+
+void AquaSalFrame::SendPaintEvent( const tools::Rectangle* pRect )
+{
+ OSX_SALDATA_RUNINMAIN( SendPaintEvent( pRect ) )
+
+ SalPaintEvent aPaintEvt( 0, 0, maGeometry.nWidth, maGeometry.nHeight, 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) )
+
+ 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( long nWidth, 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.nLeftDecoration + maGeometry.nRightDecoration;
+ nHeight += maGeometry.nTopDecoration + maGeometry.nBottomDecoration;
+
+ 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( long nWidth, 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.nLeftDecoration + maGeometry.nRightDecoration;
+ nHeight += maGeometry.nTopDecoration + maGeometry.nBottomDecoration;
+
+ // 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( long& rWidth, long& rHeight )
+{
+ if (mbShown || mbInitShow || Application::IsBitmapRendering())
+ {
+ rWidth = maGeometry.nWidth;
+ rHeight = maGeometry.nHeight;
+ }
+ else
+ {
+ rWidth = 0;
+ rHeight = 0;
+ }
+}
+
+SalEvent AquaSalFrame::PreparePosSize(long nX, long nY, long nWidth, 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.nX = nX;
+ if (nFlags & SAL_FRAME_POSSIZE_Y)
+ maGeometry.nY = nY;
+ if (nFlags & SAL_FRAME_POSSIZE_WIDTH)
+ {
+ maGeometry.nWidth = nWidth;
+ if (mnMaxWidth > 0 && maGeometry.nWidth > o3tl::make_unsigned(mnMaxWidth))
+ maGeometry.nWidth = mnMaxWidth;
+ if (mnMinWidth > 0 && maGeometry.nWidth < o3tl::make_unsigned(mnMinWidth))
+ maGeometry.nWidth = mnMinWidth;
+ }
+ if (nFlags & SAL_FRAME_POSSIZE_HEIGHT)
+ {
+ maGeometry.nHeight = nHeight;
+ if (mnMaxHeight > 0 && maGeometry.nHeight > o3tl::make_unsigned(mnMaxHeight))
+ maGeometry.nHeight = mnMaxHeight;
+ if (mnMinHeight > 0 && maGeometry.nHeight < o3tl::make_unsigned(mnMinHeight))
+ maGeometry.nHeight = mnMinHeight;
+ }
+ if (nEvent != SalEvent::NONE)
+ CallCallback(nEvent, nullptr);
+ }
+
+ return nEvent;
+}
+
+void AquaSalFrame::SetWindowState( const SalFrameState* pState )
+{
+ if (!mpNSWindow && !Application::IsBitmapRendering())
+ return;
+
+ OSX_SALDATA_RUNINMAIN( SetWindowState( pState ) )
+
+ sal_uInt16 nFlags = 0;
+ nFlags |= ((pState->mnMask & WindowStateMask::X) ? SAL_FRAME_POSSIZE_X : 0);
+ nFlags |= ((pState->mnMask & WindowStateMask::Y) ? SAL_FRAME_POSSIZE_Y : 0);
+ nFlags |= ((pState->mnMask & WindowStateMask::Width) ? SAL_FRAME_POSSIZE_WIDTH : 0);
+ nFlags |= ((pState->mnMask & WindowStateMask::Height) ? SAL_FRAME_POSSIZE_HEIGHT : 0);
+
+ SalEvent nEvent = PreparePosSize(pState->mnX, pState->mnY, pState->mnWidth, pState->mnHeight, nFlags);
+ if (Application::IsBitmapRendering())
+ return;
+
+ // set normal state
+ NSRect aStateRect = [mpNSWindow frame];
+ aStateRect = [NSWindow contentRectForFrameRect: aStateRect styleMask: mnStyleMask];
+ CocoaToVCL(aStateRect);
+ if (pState->mnMask & WindowStateMask::X)
+ aStateRect.origin.x = float(pState->mnX);
+ if (pState->mnMask & WindowStateMask::Y)
+ aStateRect.origin.y = float(pState->mnY);
+ if (pState->mnMask & WindowStateMask::Width)
+ aStateRect.size.width = float(pState->mnWidth);
+ if (pState->mnMask & WindowStateMask::Height)
+ aStateRect.size.height = float(pState->mnHeight);
+ VCLToCocoa(aStateRect);
+ aStateRect = [NSWindow frameRectForContentRect: aStateRect styleMask: mnStyleMask];
+ [mpNSWindow setFrame: aStateRect display: NO];
+
+ if (pState->mnState == WindowStateState::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->mnState == WindowStateState::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( SalFrameState* pState )
+{
+ if (!mpNSWindow)
+ {
+ if (Application::IsBitmapRendering())
+ {
+ pState->mnMask = WindowStateMask::X | WindowStateMask::Y
+ | WindowStateMask::Width | WindowStateMask::Height
+ | WindowStateMask::State;
+ pState->mnX = maGeometry.nX;
+ pState->mnY = maGeometry.nY;
+ pState->mnWidth = maGeometry.nWidth;
+ pState->mnHeight = maGeometry.nHeight;
+ pState->mnState = WindowStateState::Normal;
+ return true;
+ }
+ return false;
+ }
+
+ OSX_SALDATA_RUNINMAIN_UNION( GetWindowState( pState ), boolean )
+
+ pState->mnMask = WindowStateMask::X |
+ WindowStateMask::Y |
+ WindowStateMask::Width |
+ WindowStateMask::Height |
+ WindowStateMask::State;
+
+ NSRect aStateRect = [mpNSWindow frame];
+ aStateRect = [NSWindow contentRectForFrameRect: aStateRect styleMask: mnStyleMask];
+ CocoaToVCL( aStateRect );
+ pState->mnX = long(aStateRect.origin.x);
+ pState->mnY = long(aStateRect.origin.y);
+ pState->mnWidth = long(aStateRect.size.width);
+ pState->mnHeight = long(aStateRect.size.height);
+
+ if( [mpNSWindow isMiniaturized] )
+ pState->mnState = WindowStateState::Minimized;
+ else if( ! [mpNSWindow isZoomed] )
+ pState->mnState = WindowStateState::Normal;
+ else
+ pState->mnState = WindowStateState::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", OSL_THIS_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( long nX, long nY )
+{
+ OSX_SALDATA_RUNINMAIN( SetPointerPos( nX, nY ) )
+
+ // FIXME: use Cocoa functions
+ // FIXME: multiscreen support
+ CGPoint aPoint = { static_cast<CGFloat>(nX + maGeometry.nX), static_cast<CGFloat>(nY + maGeometry.nY) };
+ 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( 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( 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 )
+{
+}
+
+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 ] = "'";
+
+ /* 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 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) );
+ /*
+ do not release here; leads to duplicate free in yield
+ it seems the converted color comes out autoreleased, although this
+ is not documented
+ [pRBGColor release];
+ */
+ }
+ }
+ return aRet;
+}
+
+static vcl::Font getFont( NSFont* pFont, long nDPIY, const vcl::Font& rDefault )
+{
+ vcl::Font aResult( rDefault );
+ if( pFont )
+ {
+ aResult.SetFamilyName( GetOUString( [pFont familyName] ) );
+ aResult.SetFontHeight( static_cast<int>(([pFont pointSize] * 72.0 / static_cast<float>(nDPIY))+0.5) );
+ 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 );
+}
+
+// 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
+
+ StyleSettings aStyleSettings = rSettings.GetStyleSettings();
+
+ // Background Color
+ Color aBackgroundColor( 0xEC, 0xEC, 0xEC );
+ aStyleSettings.BatchSetBackgrounds( aBackgroundColor, false );
+ aStyleSettings.SetLightBorderColor( aBackgroundColor );
+
+ Color aInactiveTabColor( aBackgroundColor );
+ aInactiveTabColor.DecreaseLuminance( 32 );
+ aStyleSettings.SetInactiveTabColor( aInactiveTabColor );
+
+ Color aShadowColor( aStyleSettings.GetShadowColor() );
+ aShadowColor.IncreaseLuminance( 32 );
+ aStyleSettings.SetShadowColor( aShadowColor );
+
+ // 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 aHighlightColor( getColor( [NSColor selectedTextBackgroundColor],
+ aStyleSettings.GetHighlightColor(), mpNSWindow ) );
+ aStyleSettings.SetHighlightColor( aHighlightColor );
+ Color aHighlightTextColor( getColor( [NSColor selectedTextColor],
+ aStyleSettings.GetHighlightTextColor(), mpNSWindow ) );
+ aStyleSettings.SetHighlightTextColor( aHighlightTextColor );
+
+#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( aBackgroundColor );
+ Color aMenuTextColor( getColor( [NSColor textColor],
+ aStyleSettings.GetMenuTextColor(), mpNSWindow ) );
+ aStyleSettings.SetMenuTextColor( aMenuTextColor );
+ aStyleSettings.SetMenuBarTextColor( aMenuTextColor );
+ aStyleSettings.SetMenuBarRolloverTextColor( aMenuTextColor );
+ aStyleSettings.SetMenuBarHighlightTextColor(aStyleSettings.GetMenuHighlightTextColor());
+
+ // Set text colors for buttons and their different status according to OS settings, typically white for selected buttons,
+ // black otherwise
+
+ Color aControlTextColor(getColor([NSColor controlTextColor], COL_BLACK, mpNSWindow));
+ Color aSelectedControlTextColor(getColor([NSColor alternateSelectedControlTextColor], COL_WHITE, mpNSWindow));
+ aStyleSettings.SetDefaultButtonTextColor(aSelectedControlTextColor);
+ aStyleSettings.SetButtonTextColor(aControlTextColor);
+ aStyleSettings.SetDefaultActionButtonTextColor(aSelectedControlTextColor);
+ aStyleSettings.SetActionButtonTextColor(aControlTextColor);
+ aStyleSettings.SetFlatButtonTextColor(aControlTextColor);
+ aStyleSettings.SetDefaultButtonRolloverTextColor(aSelectedControlTextColor);
+ aStyleSettings.SetButtonRolloverTextColor(aControlTextColor);
+ aStyleSettings.SetDefaultActionButtonRolloverTextColor(aSelectedControlTextColor);
+ aStyleSettings.SetActionButtonRolloverTextColor(aControlTextColor);
+ aStyleSettings.SetFlatButtonRolloverTextColor(aControlTextColor);
+ aStyleSettings.SetDefaultButtonPressedRolloverTextColor(aSelectedControlTextColor);
+ aStyleSettings.SetButtonPressedRolloverTextColor(aSelectedControlTextColor);
+ aStyleSettings.SetDefaultActionButtonPressedRolloverTextColor(aSelectedControlTextColor);
+ aStyleSettings.SetActionButtonPressedRolloverTextColor(aSelectedControlTextColor);
+ aStyleSettings.SetFlatButtonPressedRolloverTextColor(aControlTextColor);
+
+ // Set text colors for tabs according to OS settings, typically white for selected buttons, black otherwise
+
+ aStyleSettings.SetTabTextColor(aControlTextColor);
+ aStyleSettings.SetTabHighlightTextColor(aSelectedControlTextColor);
+
+ aStyleSettings.SetCursorBlinkTime( mnBlinkCursorDelay );
+
+ // no mnemonics on macOS
+ aStyleSettings.SetOptions( aStyleSettings.GetOptions() | StyleSettingsOptions::NoMnemonics );
+
+ getAppleScrollBarVariant(aStyleSettings);
+
+ // set scrollbar size
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // 'NSRegularControlSize' is deprecated: first deprecated in macOS 10.12
+ aStyleSettings.SetScrollBarSize( static_cast<long int>([NSScroller scrollerWidthForControlSize:NSRegularControlSize scrollerStyle:NSScrollerStyleLegacy]) );
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ // 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(long nX, long nY, long nWidth, 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 = mpParent->maGeometry.nWidth - nWidth-1 - nX;
+ else
+ nX = mpParent->maGeometry.nWidth - static_cast<long int>( 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( tools::Rectangle& rRect )
+{
+ if (!mpNSWindow)
+ {
+ if (Application::IsBitmapRendering())
+ rRect = tools::Rectangle(Point(0, 0), Size(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<long>(aRect.origin.x) );
+ rRect.SetTop( static_cast<long>(aRect.origin.y) );
+ rRect.SetRight( static_cast<long>(aRect.origin.x + aRect.size.width - 1) );
+ rRect.SetBottom( static_cast<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<long>(aPt.x), static_cast<long>(aPt.y));
+
+ NSEvent* pCur = [NSApp currentEvent];
+ bool bMouseEvent = false;
+ if( pCur )
+ {
+ bMouseEvent = true;
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // 'NSLeftMouseDown' is deprecated: first deprecated in macOS 10.12
+ // 'NSLeftMouseDragged' is deprecated: first deprecated in macOS 10.12
+ // 'NSLeftMouseUp' is deprecated: first deprecated in macOS 10.12
+ // 'NSMouseMoved' is deprecated: first deprecated in macOS 10.12
+ // 'NSOtherMouseDown' is deprecated: first deprecated in macOS 10.12
+ // 'NSOtherMouseDragged' is deprecated: first deprecated in macOS 10.12
+ // 'NSOtherMouseUp' is deprecated: first deprecated in macOS 10.12
+ // 'NSRightMouseDown' is deprecated: first deprecated in macOS 10.12
+ // 'NSRightMouseDragged' is deprecated: first deprecated in macOS 10.12
+ // 'NSRightMouseUp' is deprecated: first deprecated in macOS 10.12
+ switch( [pCur type] )
+ {
+ case NSLeftMouseDown: state.mnState |= MOUSE_LEFT; break;
+ case NSLeftMouseUp: break;
+ case NSRightMouseDown: state.mnState |= MOUSE_RIGHT; break;
+ case NSRightMouseUp: break;
+ case NSOtherMouseDown: state.mnState |= ([pCur buttonNumber] == 2) ? MOUSE_MIDDLE : 0; break;
+ case NSOtherMouseUp: break;
+ case NSMouseMoved: break;
+ case NSLeftMouseDragged: state.mnState |= MOUSE_LEFT; break;
+ case NSRightMouseDragged: state.mnState |= MOUSE_RIGHT; break;
+ case NSOtherMouseDragged: state.mnState |= ([pCur buttonNumber] == 2) ? MOUSE_MIDDLE : 0; break;
+ break;
+ default:
+ bMouseEvent = false;
+ break;
+ }
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ }
+ if( bMouseEvent )
+ {
+ unsigned int nMask = static_cast<unsigned int>([pCur modifierFlags]);
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // 'NSAlternateKeyMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSCommandKeyMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSControlKeyMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSShiftKeyMask' is deprecated: first deprecated in macOS 10.12
+ if( (nMask & NSShiftKeyMask) != 0 )
+ state.mnState |= KEY_SHIFT;
+ if( (nMask & NSControlKeyMask) != 0 )
+ state.mnState |= KEY_MOD3;
+ if( (nMask & NSAlternateKeyMask) != 0 )
+ state.mnState |= KEY_MOD2;
+ if( (nMask & NSCommandKeyMask) != 0 )
+ state.mnState |= KEY_MOD1;
+SAL_WNODEPRECATED_DECLARATIONS_POP
+
+ }
+ 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*/ )
+{
+}
+
+bool AquaSalFrame::SetPluginParent( SystemParentData* )
+{
+ // plugin parent may be killed unexpectedly by
+ // plugging process;
+
+ //TODO: implement
+ return false;
+}
+
+bool AquaSalFrame::MapUnicodeToKeyCode( sal_Unicode , LanguageType , vcl::KeyCode& )
+{
+ // not supported yet
+ return false;
+}
+
+LanguageType AquaSalFrame::GetInputLanguage()
+{
+ //TODO: implement
+ return LANGUAGE_DONTKNOW;
+}
+
+void AquaSalFrame::DrawMenuBar()
+{
+}
+
+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()
+{
+ bool bFirstTime = (mnTrackingRectTag == 0);
+ 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 (bFirstTime || !NSEqualRects(maScreenRect, aNewScreenRect))
+ {
+ mbGeometryDidChange = true;
+ maScreenRect = aNewScreenRect;
+ }
+ NSArray* pScreens = [NSScreen screens];
+ if( pScreens )
+ {
+ unsigned int nNewDisplayScreenNumber = [pScreens indexOfObject: pScreen];
+ if (bFirstTime || maGeometry.nDisplayScreenNumber != nNewDisplayScreenNumber)
+ {
+ mbGeometryDidChange = true;
+ maGeometry.nDisplayScreenNumber = nNewDisplayScreenNumber;
+ }
+ }
+ }
+
+ NSRect aFrameRect = [mpNSWindow frame];
+ NSRect aContentRect = [NSWindow contentRectForFrameRect: aFrameRect styleMask: mnStyleMask];
+
+ NSRect aTrackRect = { NSZeroPoint, aContentRect.size };
+
+ if (bFirstTime || !NSEqualRects(maTrackingRect, aTrackRect))
+ {
+ mbGeometryDidChange = true;
+ maTrackingRect = aTrackRect;
+
+ // release old track rect
+ [mpNSView removeTrackingRect: mnTrackingRectTag];
+ // install the new track rect
+ mnTrackingRectTag = [mpNSView addTrackingRect: aTrackRect owner: mpNSView userData: nil assumeInside: NO];
+ }
+
+ // convert to vcl convention
+ CocoaToVCL( aFrameRect );
+ CocoaToVCL( aContentRect );
+
+ if (bFirstTime || !NSEqualRects(maContentRect, aContentRect) || !NSEqualRects(maFrameRect, aFrameRect))
+ {
+ mbGeometryDidChange = true;
+
+ maContentRect = aContentRect;
+ maFrameRect = aFrameRect;
+
+ maGeometry.nX = static_cast<int>(aContentRect.origin.x);
+ maGeometry.nY = static_cast<int>(aContentRect.origin.y);
+
+ maGeometry.nLeftDecoration = static_cast<unsigned int>(aContentRect.origin.x - aFrameRect.origin.x);
+ maGeometry.nRightDecoration = static_cast<unsigned int>((aFrameRect.origin.x + aFrameRect.size.width) -
+ (aContentRect.origin.x + aContentRect.size.width));
+
+ maGeometry.nTopDecoration = static_cast<unsigned int>(aContentRect.origin.y - aFrameRect.origin.y);
+ maGeometry.nBottomDecoration = static_cast<unsigned int>((aFrameRect.origin.y + aFrameRect.size.height) -
+ (aContentRect.origin.y + aContentRect.size.height));
+
+ maGeometry.nWidth = static_cast<unsigned int>(aContentRect.size.width);
+ maGeometry.nHeight = static_cast<unsigned int>(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> aEmptyVec;
+ maClippingRects.swap( aEmptyVec );
+ }
+ maClippingRects.clear();
+ maClippingRects.reserve( nRects );
+}
+
+void AquaSalFrame::UnionClipRegion( long nX, long nY, long nWidth, 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 000000000..d26617eb3
--- /dev/null
+++ b/vcl/osx/salframeview.mm
@@ -0,0 +1,1788 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/macros.h>
+#include <tools/helpers.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>
+
+#define WHEEL_EVENT_FACTOR 1.5
+
+static sal_uInt16 ImplGetModifierMask( unsigned int nMask )
+{
+ sal_uInt16 nRet = 0;
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // 'NSAlternateKeyMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSCommandKeyMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSControlKeyMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSShiftKeyMask' is deprecated: first deprecated in macOS 10.12
+ if( (nMask & NSShiftKeyMask) != 0 )
+ nRet |= KEY_SHIFT;
+ if( (nMask & NSControlKeyMask) != 0 )
+ nRet |= KEY_MOD3;
+ if( (nMask & NSAlternateKeyMask) != 0 )
+ nRet |= KEY_MOD2;
+ if( (nMask & NSCommandKeyMask) != 0 )
+ nRet |= KEY_MOD1;
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ 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_0,
+ 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;
+}
+
+@implementation SalFrameWindow
+-(id)initWithSalFrame: (AquaSalFrame*)pFrame
+{
+ mDraggingDestinationHandler = nil;
+ mpFrame = pFrame;
+ NSRect aRect = { { static_cast<CGFloat>(pFrame->maGeometry.nX), static_cast<CGFloat>(pFrame->maGeometry.nY) },
+ { static_cast<CGFloat>(pFrame->maGeometry.nWidth), static_cast<CGFloat>(pFrame->maGeometry.nHeight) } };
+ 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];
+
+ return static_cast<SalFrameWindow *>(pNSWindow);
+}
+
+-(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;
+
+ 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();
+ }
+ #if 0
+ // FIXME: we should disable menus while in modal mode
+ // however from down here there is currently no reliable way to
+ // find out when to do this
+ if( (mpFrame->mpParent && mpFrame->mpParent->GetWindow()->IsInModalMode()) )
+ AquaSalMenu::enableMainMenu( false );
+ #endif
+ mpFrame->CallCallback( SalEvent::GetFocus, nullptr );
+ mpFrame->SendPaintEvent(); // repaint controls as active
+ }
+}
+
+-(void)windowDidResignKey: (NSNotification*)pNotification
+{
+ (void)pNotification;
+ SolarMutexGuard aGuard;
+
+ 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
+{
+ (void)pNotification;
+ SolarMutexGuard aGuard;
+
+ if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
+ {
+ mpFrame->UpdateFrameGeometry();
+ mpFrame->CallCallback( SalEvent::Resize, nullptr );
+ mpFrame->SendPaintEvent();
+ }
+}
+
+-(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
+ mpFrame->CallCallback( SalEvent::EndExtTextInput, nullptr );
+ 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)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();
+}
+
+-(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;
+}
+
+@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;
+ mMarkedRange = NSMakeRange(NSNotFound, 0);
+ mSelectedRange = NSMakeRange(NSNotFound, 0);
+ mpReferenceWrapper = nil;
+ mpMouseEventListener = nil;
+ mpLastSuperEvent = nil;
+ }
+
+ mfLastMagnifyTime = 0.0;
+ return self;
+}
+
+-(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.nWidth, mpFrame->maGeometry.nHeight) };
+ [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
+{
+ AquaSalInstance *pInstance = GetSalData()->mpInstance;
+ assert(pInstance);
+ if (!pInstance)
+ return;
+
+ SolarMutexGuard aGuard;
+ if (!mpFrame || !AquaSalFrame::isAlive(mpFrame))
+ return;
+
+ const bool bIsLiveResize = [self inLiveResize];
+ const bool bWasLiveResize = pInstance->mbIsLiveResize;
+ if (bWasLiveResize != bIsLiveResize)
+ {
+ pInstance->mbIsLiveResize = bIsLiveResize;
+ Scheduler::ProcessTaskScheduling();
+ }
+
+ 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<long>(aPt.x) - pDispatchFrame->maGeometry.nX;
+ aEvent.mnY = static_cast<long>(aPt.y) - pDispatchFrame->maGeometry.nY;
+ aEvent.mnButton = nButton;
+ aEvent.mnCode = aEvent.mnButton | nModMask;
+
+ if( AllSettings::GetLayoutRTL() )
+ aEvent.mnX = pDispatchFrame->maGeometry.nWidth-1-aEvent.mnX;
+
+ pDispatchFrame->CallCallback( nEvent, &aEvent );
+ }
+}
+
+-(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<long>(aPt.x) - mpFrame->maGeometry.nX;
+ aEvent.mnY = static_cast<long>(aPt.y) - mpFrame->maGeometry.nY;
+ aEvent.mnCode = ImplGetModifierMask( mpFrame->mnLastModifierFlags );
+ aEvent.mnCode |= KEY_MOD1; // we want zooming, no scrolling
+ aEvent.mbDeltaIsPixel = true;
+
+ if( AllSettings::GetLayoutRTL() )
+ aEvent.mnX = mpFrame->maGeometry.nWidth-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];
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // 'NSScrollWheelMask' is deprecated: first deprecated in macOS 10.12
+ NSEvent* pNextEvent = [NSApp nextEventMatchingMask: NSScrollWheelMask
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ 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<long>(aPt.x) - mpFrame->maGeometry.nX;
+ aEvent.mnY = static_cast<long>(aPt.y) - mpFrame->maGeometry.nY;
+ aEvent.mnCode = ImplGetModifierMask( mpFrame->mnLastModifierFlags );
+ aEvent.mbDeltaIsPixel = true;
+
+ if( AllSettings::GetLayoutRTL() )
+ aEvent.mnX = mpFrame->maGeometry.nWidth-1-aEvent.mnX;
+
+ if( dX != 0.0 )
+ {
+ aEvent.mnDelta = static_cast<long>(floor(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<long>(floor(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];
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // 'NSScrollWheelMask' is deprecated: first deprecated in macOS 10.12
+ NSEvent* pNextEvent = [NSApp nextEventMatchingMask: NSScrollWheelMask
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ 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<long>(aPt.x) - mpFrame->maGeometry.nX;
+ aEvent.mnY = static_cast<long>(aPt.y) - mpFrame->maGeometry.nY;
+ aEvent.mnCode = ImplGetModifierMask( mpFrame->mnLastModifierFlags );
+ aEvent.mbDeltaIsPixel = false;
+
+ if( AllSettings::GetLayoutRTL() )
+ aEvent.mnX = mpFrame->maGeometry.nWidth-1-aEvent.mnX;
+
+ if( dX != 0.0 )
+ {
+ aEvent.mnDelta = static_cast<long>(floor(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<long>(floor(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 );
+ }
+ }
+}
+
+
+-(void)keyDown: (NSEvent*)pEvent
+{
+ SolarMutexGuard aGuard;
+
+ if( AquaSalFrame::isAlive( mpFrame ) )
+ {
+ mpLastEvent = pEvent;
+ mbInKeyInput = true;
+ mbNeedSpecialKeyHandle = false;
+ mbKeyHandled = false;
+
+ mpFrame->mnLastEventTime = static_cast<sal_uInt64>( [pEvent timestamp] * 1000.0 );
+ mpFrame->mnLastModifierFlags = [pEvent modifierFlags];
+
+ if( ! [self handleKeyDownException: pEvent] )
+ {
+ NSArray* pArray = [NSArray arrayWithObject: pEvent];
+ [self interpretKeyEvents: pArray];
+ }
+
+ 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
+ */
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // 'NSAlternateKeyMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSCommandKeyMask' is deprecated: first deprecated in macOS 10.12
+ if( (mpFrame->mnLastModifierFlags & (NSAlternateKeyMask | NSCommandKeyMask))
+ == (NSAlternateKeyMask | NSCommandKeyMask) )
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ {
+ 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;
+
+ if( AquaSalFrame::isAlive( mpFrame ) )
+ {
+ NSString* pInsert = nil;
+ if( [aString isMemberOfClass: [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
+
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // 'NSAlternateKeyMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSCommandKeyMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSControlKeyMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSKeyDown' is deprecated: first deprecated in macOS 10.12
+ // 'NSKeyUp' is deprecated: first deprecated in macOS 10.12
+ // sanity check
+ if( mpLastEvent && ( [mpLastEvent type] == NSKeyDown || [mpLastEvent type] == NSKeyUp ) )
+ {
+ // 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 & (NSControlKeyMask | NSAlternateKeyMask | NSCommandKeyMask))
+ == NSAlternateKeyMask )
+ {
+ nLastModifiers = 0;
+ }
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ [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 );
+
+ }
+ mbKeyHandled = true;
+ [self unmarkText];
+ }
+}
+
+-(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;
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // 'NSShiftKeyMask' is deprecated: first deprecated in macOS 10.12
+ [self sendKeyInputAndReleaseToFrame: KEY_LEFT character: 0 modifiers: NSShiftKeyMask];
+SAL_WNODEPRECATED_DECLARATIONS_POP
+}
+
+-(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;
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // 'NSShiftKeyMask' is deprecated: first deprecated in macOS 10.12
+ [self sendKeyInputAndReleaseToFrame: KEY_RIGHT character: 0 modifiers: NSShiftKeyMask];
+SAL_WNODEPRECATED_DECLARATIONS_POP
+}
+
+-(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.
+ if( ! mbInKeyInput )
+ return NSMakeRange( 0, 0 );
+
+ return [self hasMarkedText] ? mMarkedRange : NSMakeRange( NSNotFound, 0 );
+}
+
+- (NSRange)selectedRange
+{
+ return mSelectedRange;
+}
+
+- (void)setMarkedText:(id)aString selectedRange:(NSRange)selRange replacementRange:(NSRange)replacementRange
+{
+ (void) replacementRange; // FIXME - use it!
+
+ SolarMutexGuard aGuard;
+
+ if( ![aString isKindOfClass:[NSAttributedString class]] )
+ aString = [[[NSAttributedString alloc] initWithString:aString] autorelease];
+ NSRange rangeToReplace = [self hasMarkedText] ? [self markedRange] : [self selectedRange];
+ if( rangeToReplace.location == NSNotFound )
+ {
+ mMarkedRange = NSMakeRange( selRange.location, [aString length] );
+ mSelectedRange = NSMakeRange( selRange.location, selRange.length );
+ }
+ else
+ {
+ mMarkedRange = NSMakeRange( rangeToReplace.location, [aString length] );
+ mSelectedRange = NSMakeRange( rangeToReplace.location + selRange.location, selRange.length );
+ }
+
+ int len = [aString length];
+ SalExtTextInputEvent aInputEvent;
+ if( len > 0 ) {
+ NSString *pString = [aString string];
+ OUString aInsertString( GetOUString( pString ) );
+ std::vector<ExtTextInputAttr> aInputFlags( std::max( 1, len ), ExtTextInputAttr::NONE );
+ for ( int i = 0; i < len; i++ )
+ {
+ 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::Underline | ExtTextInputAttr::Highlight;
+ break;
+ case NSUnderlineStyleDouble:
+ aInputFlags[i] = ExtTextInputAttr::BoldUnderline;
+ break;
+ default:
+ aInputFlags[i] = ExtTextInputAttr::Highlight;
+ break;
+ }
+ }
+
+ aInputEvent.maText = aInsertString;
+ aInputEvent.mnCursorPos = selRange.location;
+ 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
+{
+ 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
+{
+ mpLastEvent = nil;
+}
+
+- (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange
+{
+ // FIXME - These should probably be used?
+ (void) aRange;
+ (void) actualRange;
+
+ SolarMutexGuard aGuard;
+
+ SalExtTextInputPosEvent aPosEvent;
+ mpFrame->CallCallback( SalEvent::ExtTextInputPos, static_cast<void *>(&aPosEvent) );
+
+ NSRect rect;
+
+ rect.origin.x = aPosEvent.mnX + mpFrame->maGeometry.nX;
+ rect.origin.y = aPosEvent.mnY + mpFrame->maGeometry.nY + 4; // add some space for underlines
+ rect.size.width = aPosEvent.mnWidth;
+ rect.size.height = aPosEvent.mnHeight;
+
+ mpFrame->VCLToCocoa( rect );
+ return rect;
+}
+
+-(id)parentAttribute {
+ return reinterpret_cast<NSView*>(mpFrame->getNSWindow());
+ //TODO: odd cast really needed for fdo#74121?
+}
+
+-(css::accessibility::XAccessibleContext *)accessibleContext
+{
+ if ( !mpReferenceWrapper ) {
+ // some frames never become visible ..
+ vcl::Window *pWindow = mpFrame -> GetWindow();
+ if ( ! pWindow )
+ return nil;
+
+ mpReferenceWrapper = new ReferenceWrapper;
+ mpReferenceWrapper -> rAccessibleContext = pWindow -> /*GetAccessibleChildWindow( 0 ) ->*/ GetAccessible() -> getAccessibleContext();
+ [ AquaA11yFactory insertIntoWrapperRepository: self forAccessibleContext: mpReferenceWrapper -> rAccessibleContext ];
+ }
+ return [ super accessibleContext ];
+}
+
+-(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;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/salinst.cxx b/vcl/osx/salinst.cxx
new file mode 100644
index 000000000..c1b11ebfa
--- /dev/null
+++ b/vcl/osx/salinst.cxx
@@ -0,0 +1,987 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/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 <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>
+
+extern "C" {
+#include <crt_externs.h>
+}
+
+using namespace std;
+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 ) :
+ 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;
+ }
+};
+
+}
+
+void AquaSalInstance::delayedSettingsChanged( bool bInvalidate )
+{
+ osl::Guard< comphelper::SolarMutex > aGuard( *GetYieldMutex() );
+ AquaDelayedSettingsChanged* pIdle = new AquaDelayedSettingsChanged( bInvalidate );
+ pIdle->SetDebugName( "AquaSalInstance AquaDelayedSettingsChanged" );
+ 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
+}
+
+void SalAbort( const OUString& rErrorText, bool bDumpCore )
+{
+ if( rErrorText.isEmpty() )
+ fprintf( stderr, "Application Error " );
+ else
+ fprintf( stderr, "%s ",
+ OUStringToOString( rErrorText, osl_getThreadTextEncoding() ).getStr() );
+ if( bDumpCore )
+ abort();
+ else
+ _exit(1);
+}
+
+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 )
+ , mbIsLiveResize( false )
+ , mbNoYieldLock( false )
+ , mbTimerProcessed( false )
+{
+ maMainThread = osl::Thread::getCurrentIdentifier();
+
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maAppData.mxToolkitName = OUString("osx");
+}
+
+AquaSalInstance::~AquaSalInstance()
+{
+ [NSApp stop: NSApp];
+ bLeftMain = true;
+ if( pDockMenu )
+ {
+ [pDockMenu release];
+ pDockMenu = nil;
+ }
+}
+
+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( MouseNotifyEvent::COMMAND, pWindow, &aCEvt );
+
+ if ( !ImplCallPreNotify( aNCmdEvt ) )
+ pWindow->Command( aCEvt );
+ }
+
+ }
+ break;
+#endif
+
+ case YieldWakeupEvent:
+ // do nothing, fall out of Yield
+ break;
+
+ default:
+ OSL_FAIL( "unhandled NSApplicationDefined event" );
+ break;
+ }
+}
+
+bool AquaSalInstance::RunInMainYield( bool bHandleAllCurrentEvents )
+{
+ OSX_SALDATA_RUNINMAIN_UNION( DoYield( false, bHandleAllCurrentEvents), boolean )
+ assert( false && "Don't call this from the main thread!" );
+ return false;
+
+}
+
+static bool isWakeupEvent( NSEvent *pEvent )
+{
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ return NSApplicationDefined == [pEvent type]
+ && AquaSalInstance::YieldWakeupEvent == static_cast<int>([pEvent subtype]);
+SAL_WNODEPRECATED_DECLARATIONS_POP
+}
+
+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
+ bool bHadEvent = 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;
+
+ do
+ {
+ SolarMutexReleaser aReleaser;
+
+ pEvent = [NSApp nextEventMatchingMask: NSEventMaskAny
+ untilDate: nil
+ inMode: NSDefaultRunLoopMode
+ dequeue: YES];
+ if( pEvent )
+ {
+ [NSApp sendEvent: pEvent];
+ if ( isWakeupEvent( pEvent ) )
+ continue;
+ bHadEvent = true;
+ }
+
+ [NSApp updateWindows];
+
+ if ( !bHandleAllCurrentEvents || !pEvent || now < [pEvent timestamp] )
+ 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
+ if( bWait && ! bHadEvent )
+ {
+ SolarMutexReleaser aReleaser;
+
+ pEvent = [NSApp nextEventMatchingMask: NSEventMaskAny
+ untilDate: [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;
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // 'NSFlagsChangedMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSKeyDownMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSKeyUpMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSLeftMouseDownMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSLeftMouseDraggedMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSLeftMouseUpMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSMouseEnteredMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSMouseExitedMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSOtherMouseDownMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSOtherMouseDraggedMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSOtherMouseUpMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSRightMouseDownMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSRightMouseDraggedMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSRightMouseUpMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSScrollWheelMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSTabletPoint' is deprecated: first deprecated in macOS 10.12
+ if( nType & VclInputFlags::MOUSE)
+ nEventMask |=
+ NSLeftMouseDownMask | NSRightMouseDownMask | NSOtherMouseDownMask |
+ NSLeftMouseUpMask | NSRightMouseUpMask | NSOtherMouseUpMask |
+ NSLeftMouseDraggedMask | NSRightMouseDraggedMask | NSOtherMouseDraggedMask |
+ NSScrollWheelMask |
+ // NSMouseMovedMask |
+ NSMouseEnteredMask | NSMouseExitedMask;
+ if( nType & VclInputFlags::KEYBOARD)
+ nEventMask |= NSKeyDownMask | NSKeyUpMask | NSFlagsChangedMask;
+ if( nType & VclInputFlags::OTHER)
+ nEventMask |= NSTabletPoint | NSApplicationDefinedMask;
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ // TODO: VclInputFlags::PAINT / more VclInputFlags::OTHER
+ if( !bool(nType) )
+ return false;
+
+ NSEvent* pEvent = [NSApp nextEventMatchingMask: nEventMask untilDate: nil
+ 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) );
+ }
+ }
+}
+
+void AquaSalInstance::GetPrinterQueueState( SalPrinterQueueInfo* )
+{
+}
+
+OUString AquaSalInstance::GetDefaultPrinter()
+{
+ // #i113170# may not be the main thread if called from UNO API
+ SalData::ensureThreadAutoreleasePool();
+
+ if( maDefaultPrinter.isEmpty() )
+ {
+ 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 )
+ {
+ NSString* pDefName = [pPr name];
+ SAL_WARN_IF( !pDefName, "vcl", "printer has no name" );
+ maDefaultPrinter = GetOUString( pDefName );
+ }
+ }
+ }
+ return maDefaultPrinter;
+}
+
+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()
+{
+ 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 = "Mac OS X ";
+ if ( versionString )
+ aVersion += OUString::fromUtf8( [ versionString UTF8String ] );
+ else
+ aVersion += "(unknown)";
+
+ return aVersion;
+}
+
+CGImageRef CreateCGImage( const Image& rImage )
+{
+ BitmapEx aBmpEx( rImage.GetBitmapEx() );
+ Bitmap aBmp( aBmpEx.GetBitmap() );
+
+ if( ! aBmp || ! 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() || aBmpEx.IsTransparent() ) )
+ xImage = pSalBmp->CreateCroppedImage( 0, 0, pSalBmp->mnWidth, pSalBmp->mnHeight );
+ else if( aBmpEx.IsAlpha() )
+ {
+ AlphaMask aAlphaMask( aBmpEx.GetAlpha() );
+ 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 );
+ }
+ else if( aBmpEx.GetTransparentType() == TransparentType::Bitmap )
+ {
+ Bitmap aMask( aBmpEx.GetMask() );
+ 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 );
+ }
+ else if( aBmpEx.GetTransparentType() == TransparentType::Color )
+ {
+ Color aTransColor( aBmpEx.GetTransparentColor() );
+ Color nTransColor( aTransColor.GetRed(), aTransColor.GetGreen(), aTransColor.GetBlue() );
+ xImage = pSalBmp->CreateColorMask( 0, 0, pSalBmp->mnWidth, pSalBmp->mnHeight, nTransColor );
+ }
+
+ 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/salmenu.cxx b/vcl/osx/salmenu.cxx
new file mode 100644
index 000000000..e52b14563
--- /dev/null
+++ b/vcl/osx/salmenu.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 <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 <vcl/commandevent.hxx>
+#include <vcl/floatwin.hxx>
+#include <vcl/window.hxx>
+#include <vcl/svapp.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>
+
+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;
+ 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];
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // 'NSCommandKeyMask' is deprecated: first deprecated in macOS 10.12
+ [pNewItem setKeyEquivalentModifierMask: NSCommandKeyMask];
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ [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];
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // 'NSCommandKeyMask' is deprecated: first deprecated in macOS 10.12
+ [pNewItem setKeyEquivalentModifierMask: NSCommandKeyMask | NSAlternateKeyMask];
+SAL_WNODEPRECATED_DECLARATIONS_POP
+
+ // 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.nX - pParentAquaSalFrame->maGeometry.nX + offset;
+ displayPopupFrame.origin.y = pWin->ImplGetFrame()->maGeometry.nY - pParentAquaSalFrame->maGeometry.nY + 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 )
+{
+ 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 mnemonics
+ OUString aText = i_rText.replaceAll("~", "");
+
+ /* #i90015# until there is a correct solution
+ strip out any appended (.*) in menubar entries
+ */
+ if( mbMenuBar )
+ {
+ sal_Int32 nPos = aText.lastIndexOf( '(' );
+ if( nPos != -1 )
+ {
+ sal_Int32 nPos2 = aText.indexOf( ')' );
+ if( nPos2 != -1 )
+ aText = aText.replaceAt( nPos, nPos2-nPos+1, "" );
+ }
+ }
+
+ 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_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;
+
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // 'NSAlternateKeyMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSCommandKeyMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSControlKeyMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSShiftKeyMask' is deprecated: first deprecated in macOS 10.12
+ if (nModifier & KEY_SHIFT)
+ {
+ nItemModifier |= NSShiftKeyMask; // actually useful only for function keys
+ if( nKeyCode >= KEY_A && nKeyCode <= KEY_Z )
+ nCommandKey = nKeyCode - KEY_A + 'A';
+ }
+
+ if (nModifier & KEY_MOD1)
+ nItemModifier |= NSCommandKeyMask;
+
+ if(nModifier & KEY_MOD2)
+ nItemModifier |= NSAlternateKeyMask;
+
+ if(nModifier & KEY_MOD3)
+ nItemModifier |= NSControlKeyMask;
+SAL_WNODEPRECATED_DECLARATIONS_POP
+
+ 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.nX;
+ aRect.origin.y -= i_pReferenceFrame->maGeometry.nY + aRect.size.height;
+
+ return tools::Rectangle( Point(static_cast<long int>(aRect.origin.x),
+ static_cast<long int>(aRect.origin.y)
+ ),
+ Size( static_cast<long int>(aRect.size.width),
+ static_cast<long int>(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
+ NSString* pString = CreateNSString( pItemData->aText.replaceAll( "~", "" ) );
+ 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 000000000..6ec959ed2
--- /dev/null
+++ b/vcl/osx/salnativewidgets.cxx
@@ -0,0 +1,1141 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/salnativewidgets.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/svapp.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 ThemeButtonValue ImplGetButtonValue(ButtonValue aButtonValue)
+{
+ switch (aButtonValue)
+ {
+ case ButtonValue::On:
+ return kThemeButtonOn;
+ break;
+ case ButtonValue::Off:
+ case ButtonValue::DontKnow:
+ return kThemeButtonOff;
+ break;
+ case ButtonValue::Mixed:
+ default:
+ return kThemeButtonMixed;
+ break;
+ }
+}
+
+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;
+ break;
+ 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::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.IsInside(rPos);
+ return bValid;
+ }
+ return false;
+}
+
+UInt32 AquaSalGraphics::getState(ControlState nState)
+{
+
+ // there are non key windows which are childs 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 kThemeStateInactive;
+ }
+ if (nState & ControlState::PRESSED)
+ return kThemeStatePressed;
+ return kThemeStateActive;
+}
+
+UInt32 AquaSalGraphics::getTrackState(ControlState nState)
+{
+ const bool bDrawActive = mpFrame == nullptr || [mpFrame->getNSWindow() isKeyWindow];
+ if (!(nState & ControlState::ENABLED) || !bDrawActive)
+ return kThemeTrackInactive;
+ return kThemeTrackActive;
+}
+
+bool AquaSalGraphics::drawNativeControl(ControlType nType,
+ ControlPart nPart,
+ const tools::Rectangle &rControlRegion,
+ ControlState nState,
+ const ImplControlValue &aValue,
+ const OUString &,
+ const Color&)
+{
+ bool bOK = false;
+ if (!CheckContext())
+ return false;
+ maContextHolder.saveState();
+ tools::Rectangle buttonRect = rControlRegion;
+ HIRect rc = ImplGetHIRectFromRectangle(buttonRect);
+ switch (nType)
+ {
+ case ControlType::Toolbar:
+ {
+#if HAVE_FEATURE_MACOSX_SANDBOX
+ HIThemeMenuItemDrawInfo aMenuItemDrawInfo;
+ aMenuItemDrawInfo.version = 0;
+ aMenuItemDrawInfo.state = kThemeMenuActive;
+ aMenuItemDrawInfo.itemType = kThemeMenuItemHierBackground;
+ HIThemeDrawMenuItem(&rc, &rc, &aMenuItemDrawInfo, maContextHolder.get(), kHIThemeOrientationNormal, nullptr);
+#else
+ if (rControlRegion.Top() == 0 && nPart == ControlPart::DrawBackgroundHorz)
+ {
+ const bool bDrawActive = mpFrame == nullptr || [mpFrame->getNSWindow() isKeyWindow];
+ CGFloat unifiedHeight = rControlRegion.GetHeight();
+ CGRect drawRect = CGRectMake(rControlRegion.Left(), rControlRegion.Top(),
+ rControlRegion.GetWidth(), rControlRegion.GetHeight());
+ CUIDraw([NSWindow coreUIRenderer], drawRect, maContextHolder.get(),
+ reinterpret_cast<CFDictionaryRef>([NSDictionary dictionaryWithObjectsAndKeys:
+ @"kCUIWidgetWindowFrame",
+ @"widget",
+ @"regularwin",
+ @"windowtype",
+ (bDrawActive ? @"normal" : @"inactive"),
+ @"state",
+ [NSNumber numberWithDouble:unifiedHeight],
+ @"kCUIWindowFrameUnifiedTitleBarHeightKey",
+ [NSNumber numberWithBool:NO],
+ @"kCUIWindowFrameDrawTitleSeparatorKey",
+ [NSNumber numberWithBool:YES],
+ @"is.flipped",
+ nil]),
+ nil);
+ }
+ else
+ {
+ HIThemeMenuItemDrawInfo aMenuItemDrawInfo;
+ aMenuItemDrawInfo.version = 0;
+ aMenuItemDrawInfo.state = kThemeMenuActive;
+ aMenuItemDrawInfo.itemType = kThemeMenuItemHierBackground;
+ HIThemeDrawMenuItem(&rc, &rc, &aMenuItemDrawInfo, maContextHolder.get(), kHIThemeOrientationNormal, nullptr);
+ }
+#endif
+ bOK = true;
+ }
+ break;
+ case ControlType::WindowBackground:
+ {
+ HIThemeBackgroundDrawInfo aThemeBackgroundInfo;
+ aThemeBackgroundInfo.version = 0;
+ aThemeBackgroundInfo.state = getState(nState);
+ aThemeBackgroundInfo.kind = kThemeBrushDialogBackgroundActive;
+
+ // FIXME: without this magical offset there is a 2 pixel black border on the right and bottom
+
+ rc.size.width += 2;
+ rc.size.height += 2;
+ HIThemeApplyBackground( &rc, &aThemeBackgroundInfo, maContextHolder.get(), kHIThemeOrientationNormal);
+ CGContextFillRect(maContextHolder.get(), rc);
+ bOK = true;
+ }
+ break;
+ case ControlType::Tooltip:
+ {
+ HIThemeBackgroundDrawInfo aThemeBackgroundInfo;
+ aThemeBackgroundInfo.version = 0;
+ aThemeBackgroundInfo.state = getState(nState);
+ aThemeBackgroundInfo.kind = kThemeBrushAlertBackgroundActive;
+ rc.size.width += 2;
+ rc.size.height += 2;
+ HIThemeApplyBackground(&rc, &aThemeBackgroundInfo, maContextHolder.get(), kHIThemeOrientationNormal);
+ CGContextFillRect(maContextHolder.get(), rc);
+ 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,maContextHolder.get(), kHIThemeOrientationNormal);
+
+ // repaints the item either blue (selected) and/or grey (active only)
+
+ HIThemeDrawMenuItem(&rc, &rc, &aMenuItemDrawInfo, maContextHolder.get(), 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, maContextHolder.get(), kHIThemeOrientationNormal);
+ if (cfString)
+ CFRelease(cfString);
+ bOK = true;
+ }
+ }
+ break;
+ case ControlType::Pushbutton:
+ {
+
+ // FIXME: instead of use a value, VCL can retrieve correct values on the fly (to be implemented)
+
+ HIThemeButtonDrawInfo aPushInfo;
+ aPushInfo.version = 0;
+
+ // no animation
+
+ aPushInfo.animation.time.start = 0;
+ aPushInfo.animation.time.current = 0;
+ PushButtonValue const *pPBVal = aValue.getType() == ControlType::Pushbutton ?
+ static_cast<PushButtonValue const *>(&aValue) : nullptr;
+ int nPaintHeight = static_cast<int>(rc.size.height);
+ if (pPBVal && pPBVal->mbBevelButton)
+ {
+ aPushInfo.kind = kThemeRoundedBevelButton;
+ }
+ else if (rc.size.height <= PUSH_BUTTON_NORMAL_HEIGHT)
+ {
+ aPushInfo.kind = kThemePushButtonMini;
+ nPaintHeight = PUSH_BUTTON_SMALL_HEIGHT;
+ }
+ else if ((pPBVal && pPBVal->mbSingleLine) || rc.size.height < PUSH_BUTTON_NORMAL_HEIGHT * 3 / 2)
+ {
+ aPushInfo.kind = kThemePushButtonNormal;
+ nPaintHeight = PUSH_BUTTON_NORMAL_HEIGHT;
+
+ // avoid clipping when focused
+
+ rc.origin.x += FOCUS_RING_WIDTH / 2;
+ rc.size.width -= FOCUS_RING_WIDTH;
+ }
+ else
+ aPushInfo.kind = kThemeBevelButton;
+
+ // translate the origin for controls with fixed paint height so content ends up somewhere sensible
+
+ rc.origin.y += (rc.size.height - nPaintHeight) / 2;
+ aPushInfo.state = getState(nState);
+ aPushInfo.value = ImplGetButtonValue(aValue.getTristateVal());
+ aPushInfo.adornment = (nState & ControlState::DEFAULT) ? kThemeAdornmentDefault : kThemeAdornmentNone;
+ if (nState & ControlState::FOCUSED)
+ aPushInfo.adornment |= kThemeAdornmentFocus;
+ HIThemeDrawButton(&rc, &aPushInfo, maContextHolder.get(), kHIThemeOrientationNormal, nullptr);
+ bOK = true;
+ }
+ break;
+ case ControlType::Radiobutton:
+ case ControlType::Checkbox:
+ {
+ HIThemeButtonDrawInfo aInfo;
+ aInfo.version = 0;
+ switch (nType)
+ {
+ case ControlType::Radiobutton:
+ if (rc.size.width >= RADIO_BUTTON_SMALL_SIZE)
+ aInfo.kind = kThemeRadioButton;
+ else
+ aInfo.kind = kThemeSmallRadioButton;
+ break;
+ case ControlType::Checkbox:
+ if (rc.size.width >= CHECKBOX_SMALL_SIZE)
+ aInfo.kind = kThemeCheckBox;
+ else
+ aInfo.kind = kThemeSmallCheckBox;
+ break;
+ default:
+ break;
+ }
+ aInfo.state = getState(nState);
+ ButtonValue aButtonValue = aValue.getTristateVal();
+ aInfo.value = ImplGetButtonValue(aButtonValue);
+ aInfo.adornment = (nState & ControlState::DEFAULT) ? kThemeAdornmentDefault : kThemeAdornmentNone;
+ if (nState & ControlState::FOCUSED)
+ aInfo.adornment |= kThemeAdornmentFocus;
+ 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;
+ HIThemeDrawButton(&rc, &aInfo, maContextHolder.get(), kHIThemeOrientationNormal, nullptr);
+ bOK = true;
+ }
+ break;
+ case ControlType::ListNode:
+ {
+ ButtonValue aButtonValue = aValue.getTristateVal();
+ HIThemeButtonDrawInfo aInfo;
+ aInfo.version = 0;
+ aInfo.kind = kThemeDisclosureTriangle;
+ aInfo.value = kThemeDisclosureRight;
+ aInfo.state = getState(nState);
+ aInfo.adornment = kThemeAdornmentNone;
+ switch (aButtonValue)
+ {
+ case ButtonValue::On:
+ aInfo.value = kThemeDisclosureDown;
+ break;
+ case ButtonValue::Off:
+ if (AllSettings::GetLayoutRTL())
+ aInfo.value = kThemeDisclosureLeft;
+ break;
+ case ButtonValue::DontKnow:
+ default:
+ break;
+ }
+ HIThemeDrawButton(&rc, &aInfo, maContextHolder.get(), kHIThemeOrientationNormal, nullptr);
+ bOK = true;
+ }
+ break;
+ case ControlType::Progress:
+ case ControlType::IntroProgress:
+ {
+ long nProgressWidth = aValue.getNumericVal();
+ HIThemeTrackDrawInfo aTrackInfo;
+ aTrackInfo.version = 0;
+ aTrackInfo.kind = (rc.size.height > 10) ? kThemeProgressBarLarge : kThemeProgressBarMedium;
+ aTrackInfo.bounds = rc;
+ aTrackInfo.min = 0;
+ aTrackInfo.max = static_cast<SInt32>(rc.size.width);
+ aTrackInfo.value = nProgressWidth;
+ aTrackInfo.reserved = 0;
+ aTrackInfo.attributes = kThemeTrackHorizontal;
+ if (AllSettings::GetLayoutRTL())
+ aTrackInfo.attributes |= kThemeTrackRightToLeft;
+ aTrackInfo.enableState = getTrackState(nState);
+
+ // the intro bitmap never gets key anyway; we want to draw that enabled
+
+ if (nType == ControlType::IntroProgress)
+ aTrackInfo.enableState = kThemeTrackActive;
+ aTrackInfo.filler1 = 0;
+ aTrackInfo.trackInfo.progress.phase = static_cast<long long>(CFAbsoluteTimeGetCurrent() * 10.0);
+ HIThemeDrawTrack(&aTrackInfo, nullptr, maContextHolder.get(), kHIThemeOrientationNormal);
+ bOK = true;
+ }
+ break;
+ case ControlType::Slider:
+ {
+ const SliderValue *pSliderVal = static_cast<SliderValue const *>(&aValue);
+ HIThemeTrackDrawInfo aTrackDraw;
+ aTrackDraw.kind = kThemeSliderMedium;
+ if (nPart == ControlPart::TrackHorzArea || nPart == ControlPart::TrackVertArea)
+ {
+ aTrackDraw.bounds = rc;
+ aTrackDraw.min = pSliderVal->mnMin;
+ aTrackDraw.max = pSliderVal->mnMax;
+ aTrackDraw.value = pSliderVal->mnCur;
+ aTrackDraw.reserved = 0;
+ aTrackDraw.attributes = kThemeTrackShowThumb;
+ if (nPart == ControlPart::TrackHorzArea)
+ aTrackDraw.attributes |= kThemeTrackHorizontal;
+ aTrackDraw.enableState = (nState & ControlState::ENABLED) ? kThemeTrackActive : kThemeTrackInactive;
+ SliderTrackInfo aSlideInfo;
+ aSlideInfo.thumbDir = kThemeThumbUpward;
+ aSlideInfo.pressState = 0;
+ aTrackDraw.trackInfo.slider = aSlideInfo;
+ HIThemeDrawTrack(&aTrackDraw, nullptr, maContextHolder.get(), kHIThemeOrientationNormal);
+ 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)
+ {
+ HIThemeTrackDrawInfo aTrackDraw;
+ aTrackDraw.kind = kThemeMediumScrollBar;
+ aTrackDraw.bounds = rc;
+ aTrackDraw.min = pScrollbarVal->mnMin;
+ aTrackDraw.max = pScrollbarVal->mnMax - pScrollbarVal->mnVisibleSize;
+ aTrackDraw.value = pScrollbarVal->mnCur;
+ aTrackDraw.reserved = 0;
+ aTrackDraw.attributes = kThemeTrackShowThumb;
+ if (nPart == ControlPart::DrawBackgroundHorz)
+ aTrackDraw.attributes |= kThemeTrackHorizontal;
+ aTrackDraw.enableState = getTrackState(nState);
+ ScrollBarTrackInfo aScrollInfo;
+ aScrollInfo.viewsize = pScrollbarVal->mnVisibleSize;
+ aScrollInfo.pressState = 0;
+ if (pScrollbarVal->mnButton1State & ControlState::ENABLED)
+ if (pScrollbarVal->mnButton1State & ControlState::PRESSED)
+ aScrollInfo.pressState = kThemeTopOutsideArrowPressed;
+ if (pScrollbarVal->mnButton2State & ControlState::ENABLED )
+ if (pScrollbarVal->mnButton2State & ControlState::PRESSED )
+ aScrollInfo.pressState = kThemeBottomOutsideArrowPressed;
+ if ( pScrollbarVal->mnThumbState & ControlState::ENABLED)
+ if (pScrollbarVal->mnThumbState & ControlState::PRESSED)
+ aScrollInfo.pressState = kThemeThumbPressed;
+ aTrackDraw.trackInfo.scrollbar = aScrollInfo;
+ HIThemeDrawTrack(&aTrackDraw, nullptr, maContextHolder.get(), kHIThemeOrientationNormal);
+ bOK = true;
+ }
+ }
+ break;
+ case ControlType::TabPane:
+ {
+ HIThemeTabPaneDrawInfo aTabPaneDrawInfo;
+ aTabPaneDrawInfo.version = 1;
+ aTabPaneDrawInfo.state = kThemeStateActive;
+ aTabPaneDrawInfo.direction = kThemeTabNorth;
+ aTabPaneDrawInfo.size = kHIThemeTabSizeNormal;
+ aTabPaneDrawInfo.kind = kHIThemeTabKindNormal;
+
+ // border is outside the rect rc for Carbon but for VCL it should be inside
+
+ rc.origin.x += 1;
+ rc.origin.y -= TAB_HEIGHT / 2;
+ rc.size.height += TAB_HEIGHT / 2;
+ rc.size.width -= 2;
+ HIThemeDrawTabPane(&rc, &aTabPaneDrawInfo, maContextHolder.get(), kHIThemeOrientationNormal);
+ bOK = true;
+ }
+ break;
+ case ControlType::TabItem:
+ {
+ HIThemeTabDrawInfo aTabItemDrawInfo;
+ aTabItemDrawInfo.version = 1;
+ aTabItemDrawInfo.style = kThemeTabNonFront;
+ aTabItemDrawInfo.direction = kThemeTabNorth;
+ aTabItemDrawInfo.size = kHIThemeTabSizeNormal;
+ aTabItemDrawInfo.adornment = kHIThemeTabAdornmentTrailingSeparator;
+ if (nState & ControlState::SELECTED)
+ aTabItemDrawInfo.style = kThemeTabFront;
+ if(nState & ControlState::FOCUSED)
+ aTabItemDrawInfo.adornment |= kHIThemeTabAdornmentFocus;
+
+ // first, last or middle tab
+
+ aTabItemDrawInfo.position = kHIThemeTabPositionMiddle;
+ 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
+
+ if (((nAlignment & TabitemFlags::LeftAligned) && (nAlignment & TabitemFlags::RightAligned))
+ || ((nAlignment & TabitemFlags::FirstInGroup) && (nAlignment & TabitemFlags::LastInGroup)))
+ aTabItemDrawInfo.position = kHIThemeTabPositionOnly;
+ else if ((nAlignment & TabitemFlags::LeftAligned) || (nAlignment & TabitemFlags::FirstInGroup))
+ aTabItemDrawInfo.position = kHIThemeTabPositionFirst;
+ else if ((nAlignment & TabitemFlags::RightAligned) || (nAlignment & TabitemFlags::LastInGroup))
+ aTabItemDrawInfo.position = kHIThemeTabPositionLast;
+
+ // support for RTL (see issue 79748)
+
+ if (AllSettings::GetLayoutRTL()) {
+ if (aTabItemDrawInfo.position == kHIThemeTabPositionFirst)
+ aTabItemDrawInfo.position = kHIThemeTabPositionLast;
+ else if (aTabItemDrawInfo.position == kHIThemeTabPositionLast)
+ aTabItemDrawInfo.position = kHIThemeTabPositionFirst;
+ }
+ rc.size.width += VCL_TAB_TEXT_SEPARATOR;
+ rc.origin.x -= 1;
+ HIThemeDrawTab(&rc, &aTabItemDrawInfo, maContextHolder.get(), kHIThemeOrientationNormal, nullptr);
+ bOK=true;
+ }
+ break;
+ case ControlType::Editbox:
+ case ControlType::MultilineEditbox:
+ {
+ HIThemeFrameDrawInfo aTextDrawInfo;
+ aTextDrawInfo.version = 0;
+ aTextDrawInfo.kind = kHIThemeFrameTextFieldSquare;
+ aTextDrawInfo.state = getState(nState);
+ aTextDrawInfo.isFocused = false;
+ 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;
+
+ // fill a white background, because HIThemeDrawFrame only draws the border
+
+ CGContextFillRect(maContextHolder.get(), CGRectMake(rc.origin.x, rc.origin.y, rc.size.width, rc.size.height));
+ HIThemeDrawFrame(&rc, &aTextDrawInfo, maContextHolder.get(), kHIThemeOrientationNormal);
+ if (nState & ControlState::FOCUSED)
+ HIThemeDrawFocusRect(&rc, true, maContextHolder.get(), kHIThemeOrientationNormal);
+ bOK = true;
+ }
+ break;
+ case ControlType::Combobox:
+ if (nPart == ControlPart::HasBackgroundTexture || nPart == ControlPart::Entire)
+ {
+ HIThemeButtonDrawInfo aComboInfo;
+ aComboInfo.version = 0;
+ aComboInfo.kind = kThemeComboBox;
+ aComboInfo.state = getState(nState);
+ aComboInfo.value = kThemeButtonOn;
+ aComboInfo.adornment = kThemeAdornmentNone;
+ if (nState & ControlState::FOCUSED)
+ aComboInfo.adornment |= kThemeAdornmentFocus;
+ rc.size.width -= 2 * FOCUS_RING_WIDTH;
+ rc.size.height = COMBOBOX_HEIGHT;
+ rc.origin.x += FOCUS_RING_WIDTH;
+ rc.origin.y += FOCUS_RING_WIDTH;
+ HIThemeDrawButton(&rc, &aComboInfo, maContextHolder.get(), kHIThemeOrientationNormal, nullptr);
+ bOK = true;
+ }
+ break;
+ case ControlType::Listbox:
+ switch (nPart)
+ {
+ case ControlPart::Entire:
+ case ControlPart::ButtonDown:
+ HIThemeButtonDrawInfo aListInfo;
+ aListInfo.version = 0;
+ aListInfo.kind = kThemePopupButton;
+ aListInfo.state = getState(nState);
+ aListInfo.value = kThemeButtonOn;
+ aListInfo.adornment = kThemeAdornmentDefault;
+ if (nState & ControlState::FOCUSED)
+ aListInfo.adornment |= kThemeAdornmentFocus;
+ rc.size.width -= 2 * FOCUS_RING_WIDTH;
+ rc.size.height = LISTBOX_HEIGHT;
+ rc.origin.x += FOCUS_RING_WIDTH;
+ rc.origin.y += FOCUS_RING_WIDTH;
+ HIThemeDrawButton(&rc, &aListInfo, maContextHolder.get(), kHIThemeOrientationNormal, nullptr);
+ bOK = true;
+ break;
+ case ControlPart::ListboxWindow:
+ HIThemeFrameDrawInfo aTextDrawInfo;
+ aTextDrawInfo.version = 0;
+ aTextDrawInfo.kind = kHIThemeFrameTextFieldSquare;
+ aTextDrawInfo.state = getState(nState);
+ aTextDrawInfo.isFocused = false;
+ rc.size.width -= 2 * FOCUS_RING_WIDTH;
+ rc.size.height -= 2 * FOCUS_RING_WIDTH;
+ rc.origin.x += FOCUS_RING_WIDTH;
+ rc.origin.y += FOCUS_RING_WIDTH;
+ HIThemeDrawFrame(&rc, &aTextDrawInfo, maContextHolder.get(), kHIThemeOrientationNormal);
+ if (nState & ControlState::FOCUSED)
+ HIThemeDrawFocusRect(&rc, true, maContextHolder.get(), kHIThemeOrientationNormal);
+ bOK = true;
+ break;
+ default:
+ break;
+ }
+ break;
+ case ControlType::Spinbox:
+ if (nPart == ControlPart::Entire)
+ {
+
+ // text field
+
+ HIThemeFrameDrawInfo aTextDrawInfo;
+ aTextDrawInfo.version = 0;
+ aTextDrawInfo.kind = kHIThemeFrameTextFieldSquare;
+ aTextDrawInfo.state = getState(nState);
+ aTextDrawInfo.isFocused = false;
+ 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;
+
+ // fill a white background, because HIThemeDrawFrame only draws the border
+
+ CGContextFillRect(maContextHolder.get(), CGRectMake(rc.origin.x, rc.origin.y, rc.size.width, rc.size.height));
+ HIThemeDrawFrame(&rc, &aTextDrawInfo, maContextHolder.get(), kHIThemeOrientationNormal);
+ if (nState & ControlState::FOCUSED)
+ HIThemeDrawFocusRect(&rc, true, maContextHolder.get(), kHIThemeOrientationNormal);
+
+ // buttons
+
+ const SpinbuttonValue *pSpinButtonVal = (aValue.getType() == ControlType::SpinButtons)
+ ? static_cast <const SpinbuttonValue *>(&aValue) : nullptr;
+ ControlState nUpperState = ControlState::ENABLED;
+ ControlState nLowerState = ControlState::ENABLED;
+ if (pSpinButtonVal)
+ {
+ nUpperState = pSpinButtonVal->mnUpperState;
+ nLowerState = pSpinButtonVal->mnLowerState;
+ HIThemeButtonDrawInfo aSpinInfo;
+ aSpinInfo.kind = kThemeIncDecButton;
+ aSpinInfo.state = kThemeStateActive;
+ if (nUpperState & ControlState::PRESSED)
+ aSpinInfo.state = kThemeStatePressedUp;
+ else if (nLowerState & ControlState::PRESSED)
+ aSpinInfo.state = kThemeStatePressedDown;
+ else if (nUpperState & ~ControlState::ENABLED || nLowerState & ~ControlState::ENABLED)
+ aSpinInfo.state = kThemeStateInactive;
+ else if (nUpperState & ControlState::ROLLOVER || nLowerState & ControlState::ROLLOVER)
+ aSpinInfo.state = kThemeStateRollover;
+ switch (aValue.getTristateVal())
+ {
+ case ButtonValue::On:
+ aSpinInfo.value = kThemeButtonOn;
+ break;
+ case ButtonValue::Off:
+ aSpinInfo.value = kThemeButtonOff;
+ break;
+ case ButtonValue::Mixed:
+ case ButtonValue::DontKnow:
+ default:
+ aSpinInfo.value = kThemeButtonMixed;
+ break;
+ }
+ aSpinInfo.adornment = (nUpperState & ControlState::DEFAULT || nLowerState & ControlState::DEFAULT)
+ ? kThemeAdornmentDefault : kThemeAdornmentNone;
+ if (nUpperState & ControlState::FOCUSED || nLowerState & ControlState::FOCUSED)
+ aSpinInfo.adornment |= kThemeAdornmentFocus;
+ rc.origin.x += rc.size.width + 2 * FOCUS_RING_WIDTH;
+ rc.size.width = SPIN_BUTTON_WIDTH;
+ rc.size.height = SPIN_LOWER_BUTTON_HEIGHT + SPIN_LOWER_BUTTON_HEIGHT;
+ HIThemeDrawButton(&rc, &aSpinInfo, maContextHolder.get(), kHIThemeOrientationNormal, 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.nHeight - 3)
+ {
+ CGMutablePathRef rPath = CGPathCreateMutable();
+ CGPathAddRect(rPath, nullptr,
+ CGRectMake(0, 0, mpFrame->maGeometry.nWidth - 1, mpFrame->maGeometry.nHeight - 1));
+ CGContextBeginPath(maContextHolder.get());
+ CGContextAddPath(maContextHolder.get(), rPath);
+ CGContextClip(maContextHolder.get());
+ CGPathRelease(rPath);
+ }
+ HIThemeFrameDrawInfo aTextDrawInfo;
+ aTextDrawInfo.version = 0;
+ aTextDrawInfo.kind = kHIThemeFrameListBox;
+ aTextDrawInfo.state = kThemeStateActive;
+ aTextDrawInfo.isFocused = false;
+ HIThemeDrawFrame(&rc, &aTextDrawInfo, maContextHolder.get(), kHIThemeOrientationNormal);
+ bOK = true;
+ }
+ }
+ }
+ break;
+ case ControlType::ListNet:
+
+ // do nothing as there isn't net for listviews on macOS
+
+ bOK = true;
+ break;
+ default:
+ break;
+ }
+ maContextHolder.restoreState();
+
+ // 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 (mxClipPath)
+ aRect = CGPathGetBoundingBox(mxClipPath);
+ if (aRect.size.width != 0 && aRect.size.height != 0)
+ buttonRect.Intersection(tools::Rectangle(Point(static_cast<long int>(aRect.origin.x),
+ static_cast<long int>(aRect.origin.y)),
+ Size(static_cast<long int>(aRect.size.width),
+ static_cast<long int>(aRect.size.height))));
+ }
+ RefreshRect(buttonRect.Left(), buttonRect.Top(), buttonRect.GetWidth(), buttonRect.GetHeight());
+ 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::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 - 2 * VCL_TAB_TEXT_SEPARATOR;
+ 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:
+ {
+ w = aCtrlBoundRect.GetWidth();
+ h = EDITBOX_HEIGHT + 2 * FOCUS_RING_WIDTH;
+ rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ w -= 2 * (FOCUS_RING_WIDTH + EDITBOX_BORDER_WIDTH + EDITBOX_INSET_MARGIN);
+ h -= 2 * (FOCUS_RING_WIDTH + EDITBOX_BORDER_WIDTH + EDITBOX_INSET_MARGIN);
+ x += FOCUS_RING_WIDTH + EDITBOX_BORDER_WIDTH + EDITBOX_INSET_MARGIN;
+ y += FOCUS_RING_WIDTH + EDITBOX_BORDER_WIDTH + EDITBOX_INSET_MARGIN;
+ 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;
+ }
+ 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 000000000..1dba47f03
--- /dev/null
+++ b/vcl/osx/salnsmenu.mm
@@ -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 <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/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;
+
+ // 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];
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // 'NSAlternateKeyMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSCommandKeyMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSControlKeyMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSKeyDown' is deprecated: first deprecated in macOS 10.12
+ // 'NSShiftKeyMask' is deprecated: first deprecated in macOS 10.12
+ if( pEvent && [pEvent type] == NSKeyDown )
+ {
+ unsigned int nModMask = ([pEvent modifierFlags] & (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask|NSCommandKeyMask));
+ NSString* charactersIgnoringModifiers = [pEvent charactersIgnoringModifiers];
+ if( nModMask == NSCommandKeyMask &&
+ ( [charactersIgnoringModifiers isEqualToString: @"v"] ||
+ [charactersIgnoringModifiers isEqualToString: @"c"] ||
+ [charactersIgnoringModifiers isEqualToString: @"x"] ||
+ [charactersIgnoringModifiers isEqualToString: @"a"] ||
+ [charactersIgnoringModifiers isEqualToString: @"z"] ) )
+ {
+ [[[NSApp keyWindow] contentView] keyDown: pEvent];
+ return;
+ }
+ }
+SAL_WNODEPRECATED_DECLARATIONS_POP
+
+ 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 )
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // 'NSCompositeSourceOver' is deprecated: first deprecated in macOS 10.12
+ [rButtons[i].mpNSImage drawInRect: aImgRect fromRect: aFromRect operation: NSCompositeSourceOver fraction: 1.0];
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ 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 000000000..95be18164
--- /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 000000000..75969d4e6
--- /dev/null
+++ b/vcl/osx/salobj.cxx
@@ -0,0 +1,442 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <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( long nX, long nY, long nWidth, 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( long nX, long nY, long nWidth, 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 000000000..56510b977
--- /dev/null
+++ b/vcl/osx/salprn.cxx
@@ -0,0 +1,683 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <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();
+
+ 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
+ long 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( static_cast<sal_uInt8*>(std::malloc( 4 )) );
+ io_pSetupData->SetDriverDataLen( 4 );
+ }
+ else
+ bSuccess = false;
+
+ return bSuccess;
+}
+
+void AquaSalInfoPrinter::setPaperSize( long i_nWidth, 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
+ 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*,
+ long& o_rOutWidth, 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<long>( double(aPaperSize.width) * fXScaling ) );
+ rPaperSize.setHeight( static_cast<long>( double(aPaperSize.height) * fYScaling ) );
+
+ NSRect aImageRect = [mpPrintInfo imageablePageBounds];
+ rPageOffset.setX( static_cast<long>( aImageRect.origin.x * fXScaling ) );
+ rPageOffset.setY( static_cast<long>( (aPaperSize.height - aImageRect.size.height - aImageRect.origin.y) * fYScaling ) );
+ o_rOutWidth = static_cast<long>( aImageRect.size.width * fXScaling );
+ o_rOutHeight = static_cast<long>( aImageRect.size.height * fYScaling );
+
+ if( mePageOrientation == Orientation::Landscape )
+ {
+ std::swap( o_rOutWidth, o_rOutHeight );
+ // swap width and height
+ 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 > 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,
+ const OUString& /*i_rAppName*/,
+ 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 = false;
+ beans::PropertyValue* pSingleValue = i_rController.getValue( OUString( "PrintCollateAsSingleJobs" ) );
+ if( pSingleValue )
+ {
+ pSingleValue->Value >>= bSinglePrintJobs;
+ }
+
+ // 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& i_rAppName,
+ ImplJobSetup* i_pSetupData,
+ vcl::PrinterController& i_rController )
+{
+ return mpInfoPrinter->StartJob( i_pFileName, i_rJobName, i_rAppName, 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( long i_nWidth, 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 000000000..36c92b8f2
--- /dev/null
+++ b/vcl/osx/salsys.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 <rtl/ustrbuf.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;
+}
+
+tools::Rectangle AquaSalSystem::GetDisplayScreenPosSizePixel( unsigned int nScreen )
+{
+ if (Application::IsBitmapRendering())
+ {
+ tools::Rectangle aRect;
+ if (nScreen == 0)
+ aRect = tools::Rectangle(Point(0,0), Size(1024, 768));
+ return aRect;
+ }
+
+ NSArray* pScreens = [NSScreen screens];
+ tools::Rectangle aRet;
+ NSScreen* pScreen = nil;
+ if( pScreens && nScreen < [pScreens count] )
+ pScreen = [pScreens objectAtIndex: nScreen];
+ else
+ pScreen = [NSScreen mainScreen];
+
+ if( pScreen )
+ {
+ NSRect aFrame = [pScreen frame];
+ aRet = tools::Rectangle( Point( static_cast<long int>(aFrame.origin.x), static_cast<long int>(aFrame.origin.y) ),
+ Size( static_cast<long int>(aFrame.size.width), static_cast<long int>(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 000000000..55593e64e
--- /dev/null
+++ b/vcl/osx/saltimer.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 <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;
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+// 'NSApplicationDefined' is deprecated: first deprecated in macOS 10.12
+ NSEvent* pEvent = [NSEvent otherEventWithType: NSApplicationDefined
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ 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) && !pSalData->mpInstance->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 || GetSalData()->mpInstance->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 000000000..eebb675ce
--- /dev/null
+++ b/vcl/osx/service_entry.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/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() )
+ 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::CreateDragSource()
+{
+ if ( Application::IsHeadlessModeEnabled() )
+ return SalInstance::CreateDragSource();
+
+ return uno::Reference<XInterface>(static_cast< XInitialization* >(new DragSource()), UNO_QUERY);
+}
+
+uno::Reference<XInterface> AquaSalInstance::CreateDropTarget()
+{
+ if ( Application::IsHeadlessModeEnabled() )
+ return SalInstance::CreateDropTarget();
+
+ return uno::Reference<XInterface>(static_cast< XInitialization* >(new DropTarget()), UNO_QUERY);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/vclnsapp.mm b/vcl/osx/vclnsapp.mm
new file mode 100644
index 000000000..53d002a8a
--- /dev/null
+++ b/vcl/osx/vclnsapp.mm
@@ -0,0 +1,473 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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;
+
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // 'NSApplicationDefined' is deprecated: first deprecated in macOS 10.12
+ NSEvent* pEvent = [NSEvent otherEventWithType: NSApplicationDefined
+ location: NSZeroPoint
+ modifierFlags: 0
+ timestamp: [[NSProcessInfo processInfo] systemUptime]
+ windowNumber: 0
+ context: nil
+ subtype: AquaSalInstance::AppExecuteSVMain
+ data1: 0
+ data2: 0 ];
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ assert( pEvent );
+ [NSApp postEvent: pEvent atStart: NO];
+
+ if( [NSWindow respondsToSelector:@selector(allowsAutomaticWindowTabbing)] )
+ {
+ [NSWindow setAllowsAutomaticWindowTabbing:NO];
+ }
+}
+
+-(void)sendEvent:(NSEvent*)pEvent
+{
+ NSEventType eType = [pEvent type];
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // 'NSAlternateKeyMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSApplicationDefined' is deprecated: first deprecated in macOS 10.12
+ // 'NSClosableWindowMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSCommandKeyMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSControlKeyMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSKeyDown' is deprecated: first deprecated in macOS 10.12
+ // 'NSMiniaturizableWindowMask' is deprecated: first deprecated in macOS 10.12
+ // 'NSShiftKeyMask' is deprecated: first deprecated in macOS 10.12
+ if( eType == NSApplicationDefined )
+ {
+ AquaSalInstance::handleAppDefinedEvent( pEvent );
+ }
+ else if( eType == NSKeyDown && ([pEvent modifierFlags] & NSCommandKeyMask) != 0 )
+ {
+ NSWindow* pKeyWin = [NSApp keyWindow];
+ if( pKeyWin && [pKeyWin isKindOfClass: [SalFrameWindow class]] )
+ {
+ AquaSalFrame* pFrame = [static_cast<SalFrameWindow*>(pKeyWin) getSalFrame];
+ unsigned int nModMask = ([pEvent modifierFlags] & (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask|NSCommandKeyMask));
+ /*
+ * #i98949# - Cmd-M miniaturize window, Cmd-Option-M miniaturize all windows
+ */
+ if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"m"] )
+ {
+ if ( nModMask == NSCommandKeyMask && ([pFrame->getNSWindow() styleMask] & NSMiniaturizableWindowMask) )
+ {
+ [pFrame->getNSWindow() performMiniaturize: nil];
+ return;
+ }
+
+ if ( nModMask == ( NSCommandKeyMask | NSAlternateKeyMask ) )
+ {
+ [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] & (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask|NSCommandKeyMask));
+ if( nModMask == NSCommandKeyMask )
+ {
+
+ 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 == (NSCommandKeyMask|NSShiftKeyMask) )
+ {
+ if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"Z"] )
+ {
+ if( [NSApp sendAction: @selector(redo:) to: nil from: nil] )
+ return;
+ }
+ }
+ }
+ }
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ [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;
+ aFile.push_back( GetOUString( pFile ) );
+ if( ! AquaSalInstance::isOnCommandLine( aFile[0] ) )
+ {
+ const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::Type::Open, 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, 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;
+ aFile.push_back( GetOUString( pFile ) );
+ const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::Type::Print, 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, 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)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 000000000..963bed1cd
--- /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 000000000..ac7991875
--- /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 000000000..f8802b5ad
--- /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 000000000..d312939e4
--- /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 000000000..e6639bab1
--- /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 000000000..3b0f9bc82
--- /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 000000000..022175453
--- /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 000000000..b85a20356
--- /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 000000000..accc384d2
--- /dev/null
+++ b/vcl/qa/api/XGraphicTest.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 <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;
+
+static OUString const gaDataUrl = "/vcl/qa/api/data/";
+
+class XGraphicTest : public test::BootstrapFixture
+{
+public:
+ XGraphicTest()
+ : BootstrapFixture(true, false)
+ {
+ }
+
+ OUString getFullUrl(const OUString& 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), 24);
+ 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("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::makeAny(aGraphicURL) },
+ { "LazyRead", uno::makeAny(true) },
+ { "LoadAsLink", uno::makeAny(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::makeAny(aGraphicURL) },
+ { "LazyRead", uno::makeAny(false) },
+ { "LoadAsLink", uno::makeAny(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::makeAny(aGraphicURL) },
+ { "LazyRead", uno::makeAny(true) },
+ { "LoadAsLink", uno::makeAny(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 000000000..fe0c3c8ae
--- /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 000000000..ff4ed0d87
--- /dev/null
+++ b/vcl/qa/cppunit/BackendTest.cxx
@@ -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/.
+ *
+ */
+
+#include <test/bootstrapfixture.hxx>
+
+#include <vcl/bitmap.hxx>
+#include <tools/stream.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+
+#include <test/outputdevice.hxx>
+
+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(aBitmap, aStream);
+ }
+ }
+
+ void exportDevice(const OUString& filename, const VclPtr<VirtualDevice>& device)
+ {
+ if (mbExportBitmap)
+ {
+ BitmapEx aBitmapEx(device->GetBitmap(Point(0, 0), device->GetOutputSizePixel()));
+ SvFileStream aStream(filename, StreamMode::WRITE | StreamMode::TRUNC);
+ GraphicFilter::GetGraphicFilter().compressAsPNG(aBitmapEx, aStream);
+ }
+ }
+
+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 false;
+ }
+
+// 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")'.
+#define SHOULD_ASSERT \
+ (assertBackendNameNotEmpty(aOutDevTest.getRenderBackendName()) \
+ || aOutDevTest.getRenderBackendName() == "skia")
+
+ void testDrawRectWithRectangle()
+ {
+ vcl::test::OutputDeviceTestRect aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(false);
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkRectangle(aBitmap);
+ exportImage("01-01_rectangle_test-rectangle.png", aBitmap);
+
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawRectWithPixel()
+ {
+ vcl::test::OutputDeviceTestPixel aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(false);
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkRectangle(aBitmap);
+ exportImage("01-02_rectangle_test-pixel.png", aBitmap);
+
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawRectWithLine()
+ {
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(false);
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkRectangle(aBitmap);
+ exportImage("01-03_rectangle_test-line.png", aBitmap);
+
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawRectWithPolygon()
+ {
+ vcl::test::OutputDeviceTestPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(false);
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkRectangle(aBitmap);
+ exportImage("01-04_rectangle_test-polygon.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawRectWithPolyLine()
+ {
+ vcl::test::OutputDeviceTestPolyLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(false);
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkRectangle(aBitmap);
+ exportImage("01-05_rectangle_test-polyline.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawRectWithPolyLineB2D()
+ {
+ vcl::test::OutputDeviceTestPolyLineB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(false);
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkRectangle(aBitmap);
+ exportImage("01-06_rectangle_test-polyline_b2d.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawRectWithPolyPolygon()
+ {
+ vcl::test::OutputDeviceTestPolyPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(false);
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkRectangle(aBitmap);
+ exportImage("01-07_rectangle_test-polypolygon.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawRectWithPolyPolygonB2D()
+ {
+ vcl::test::OutputDeviceTestPolyPolygonB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(false);
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkRectangle(aBitmap);
+ exportImage("01-08_rectangle_test-polypolygon_b2d.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawRectAAWithRectangle()
+ {
+ 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()
+ {
+ 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()
+ {
+ 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()
+ {
+ 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()
+ {
+ 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()
+ {
+ 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()
+ {
+ 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()
+ {
+ 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()
+ {
+ 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()
+ {
+ 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()
+ {
+ 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()
+ {
+ 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)
+ 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)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawBezierWithPolylineB2D()
+ {
+ 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()
+ {
+ 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 testDrawBitmap()
+ {
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawBitmap();
+ exportImage("08-01_bitmap_test.png", aBitmap);
+ auto eResult = vcl::test::OutputDeviceTestBitmap::checkTransformedBitmap(aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawTransformedBitmap()
+ {
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawTransformedBitmap();
+ auto eResult = vcl::test::OutputDeviceTestBitmap::checkTransformedBitmap(aBitmap);
+ exportImage("08-02_transformed_bitmap_test.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawBitmapExWithAlpha()
+ {
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawBitmapExWithAlpha();
+ auto eResult = vcl::test::OutputDeviceTestBitmap::checkBitmapExWithAlpha(aBitmap);
+ exportImage("08-03_bitmapex_with_alpha_test.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawMask()
+ {
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawMask();
+ auto eResult = vcl::test::OutputDeviceTestBitmap::checkMask(aBitmap);
+ exportImage("08-04_mask_test.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawBlend()
+ {
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ BitmapEx aBitmapEx = aOutDevTest.setupDrawBlend();
+ auto eResult = vcl::test::OutputDeviceTestBitmap::checkBlend(aBitmapEx);
+ exportImage("08-05_blend_test.png", aBitmapEx);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawXor()
+ {
+ 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 testClipRectangle()
+ {
+ 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()
+ {
+ 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()
+ {
+ 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()
+ {
+ 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 testDashedLine()
+ {
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDashedLine();
+ auto eResult = vcl::test::OutputDeviceTestLine::checkDashedLine(aBitmap);
+ exportImage("10-01_dashed_line_test.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testTdf124848()
+ {
+ ScopedVclPtr<VirtualDevice> device = VclPtr<VirtualDevice>::Create(DeviceFormat::DEFAULT);
+ device->SetOutputSizePixel(Size(100, 100));
+ device->SetBackground(Wallpaper(COL_WHITE));
+ device->Erase();
+ device->SetAntialiasing(AntialiasingFlags::EnableB2dDraw);
+ 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("/tmp/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("/tmp/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)));
+ }
+
+ 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(testDrawBitmap);
+ CPPUNIT_TEST(testDrawTransformedBitmap);
+ CPPUNIT_TEST(testDrawBitmapExWithAlpha);
+ CPPUNIT_TEST(testDrawMask);
+ CPPUNIT_TEST(testDrawBlend);
+ CPPUNIT_TEST(testDrawXor);
+
+ CPPUNIT_TEST(testClipRectangle);
+ CPPUNIT_TEST(testClipPolygon);
+ CPPUNIT_TEST(testClipPolyPolygon);
+ CPPUNIT_TEST(testClipB2DPolyPolygon);
+
+ CPPUNIT_TEST(testDashedLine);
+
+ CPPUNIT_TEST(testTdf124848);
+
+ 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/BitmapExTest.cxx b/vcl/qa/cppunit/BitmapExTest.cxx
new file mode 100644
index 000000000..23f40f001
--- /dev/null
+++ b/vcl/qa/cppunit/BitmapExTest.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 <cppunit/TestAssert.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <vcl/bitmapex.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <bitmapwriteaccess.hxx>
+#include <svdata.hxx>
+#include <salinst.hxx>
+
+namespace
+{
+class BitmapExTest : public CppUnit::TestFixture
+{
+ void testGetPixelColor24_8();
+ void testGetPixelColor32();
+ void testTransformBitmapEx();
+
+ CPPUNIT_TEST_SUITE(BitmapExTest);
+ CPPUNIT_TEST(testGetPixelColor24_8);
+ CPPUNIT_TEST(testGetPixelColor32);
+ CPPUNIT_TEST(testTransformBitmapEx);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void BitmapExTest::testGetPixelColor24_8()
+{
+ Bitmap aBitmap(Size(3, 3), 24);
+ {
+ BitmapScopedWriteAccess pWriteAccess(aBitmap);
+ pWriteAccess->Erase(Color(0x00, 0x00, 0xFF, 0x00));
+ }
+ AlphaMask aMask(Size(3, 3));
+ {
+ AlphaScopedWriteAccess pWriteAccess(aMask);
+ pWriteAccess->Erase(Color(0x00, 0xAA, 0xAA, 0xAA));
+ }
+
+ BitmapEx aBitmapEx(aBitmap, aMask);
+
+ CPPUNIT_ASSERT_EQUAL(Color(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
+ auto pBackendCapabilities = ImplGetSVData()->mpDefInst->GetBackendCapabilities();
+ if (!pBackendCapabilities->mbSupportsBitmap32)
+ return;
+
+ Bitmap aBitmap(Size(3, 3), 32);
+ {
+ BitmapScopedWriteAccess pWriteAccess(aBitmap);
+ pWriteAccess->Erase(Color(0xAA, 0x00, 0xFF, 0x00));
+ }
+
+ BitmapEx aBitmapEx(aBitmap);
+
+ CPPUNIT_ASSERT_EQUAL(Color(0xAA, 0x00, 0xFF, 0x00), aBitmapEx.GetPixelColor(0, 0));
+}
+
+void BitmapExTest::testTransformBitmapEx()
+{
+ Bitmap aBitmap(Size(16, 16), 24);
+ {
+ 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();
+ Bitmap::ScopedReadAccess 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);
+ }
+ }
+}
+
+} // 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 000000000..dddfaf571
--- /dev/null
+++ b/vcl/qa/cppunit/BitmapFilterTest.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 <test/bootstrapfixture.hxx>
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapaccess.hxx>
+#include <bitmapwriteaccess.hxx>
+
+#include <tools/stream.hxx>
+#include <vcl/graphicfilter.hxx>
+
+#include <vcl/BitmapBasicMorphologyFilter.hxx>
+#include <vcl/BitmapFilterStackBlur.hxx>
+#include <BitmapSymmetryCheck.hxx>
+
+#include <chrono>
+
+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();
+
+ CPPUNIT_TEST_SUITE(BitmapFilterTest);
+ CPPUNIT_TEST(testBlurCorrectness);
+ CPPUNIT_TEST(testBasicMorphology);
+ CPPUNIT_TEST(testPerformance);
+ CPPUNIT_TEST_SUITE_END();
+
+private:
+ OUString getFullUrl(const OUString& sFileName)
+ {
+ return m_directories.getURLFromSrc("vcl/qa/cppunit/data/") + sFileName;
+ }
+
+ BitmapEx loadBitmap(const OUString& 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(rBmp, aStream);
+ }
+};
+
+void BitmapFilterTest::testBlurCorrectness()
+{
+ // Setup test bitmap
+ Size aSize(41, 31);
+ Bitmap aBitmap24Bit(aSize, 24);
+
+ ScanlineFormat scanlineFormat = ScanlineFormat::NONE;
+ sal_uInt16 nBPP = aBitmap24Bit.GetBitCount();
+
+ {
+ long aMargin1 = 1;
+ 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<long>(41), aBitmap24Bit.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(static_cast<long>(31), aBitmap24Bit.GetSizePixel().Height());
+
+ CPPUNIT_ASSERT_EQUAL(nBPP, aBitmap24Bit.GetBitCount());
+
+ // Check that the bitmap is horizontally and vertically symmetrical
+ CPPUNIT_ASSERT(BitmapSymmetryCheck::check(aBitmap24Bit));
+
+ {
+ Bitmap::ScopedReadAccess aReadAccess(aBitmap24Bit);
+ CPPUNIT_ASSERT_EQUAL(scanlineFormat, aReadAccess->GetScanlineFormat());
+ }
+}
+
+void BitmapFilterTest::testBasicMorphology()
+{
+ const BitmapEx aOrigBitmap = loadBitmap("testBasicMorphology.png");
+ const BitmapEx aRefBitmapDilated1 = loadBitmap("testBasicMorphologyDilated1.png");
+ const BitmapEx aRefBitmapDilated1Eroded1 = loadBitmap("testBasicMorphologyDilated1Eroded1.png");
+ const BitmapEx aRefBitmapDilated2 = loadBitmap("testBasicMorphologyDilated2.png");
+ const BitmapEx aRefBitmapDilated2Eroded1 = loadBitmap("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, 24);
+ {
+ 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(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");
+ }
+}
+
+} // 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 000000000..2a628285d
--- /dev/null
+++ b/vcl/qa/cppunit/BitmapProcessorTest.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 <cppunit/TestAssert.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/plugin/TestPlugIn.h>
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapaccess.hxx>
+#include <vcl/alpha.hxx>
+
+#include <BitmapDisabledImageFilter.hxx>
+#include <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), 24);
+ {
+ BitmapScopedWriteAccess pWriteAccess(aBitmap);
+ pWriteAccess->Erase(Color(0x00, 0x00, 0xFF, 0x00));
+ }
+ BitmapEx aBitmapEx(aBitmap);
+ BitmapDisabledImageFilter aDisabledImageFilter;
+ BitmapEx aDisabledBitmapEx(aDisabledImageFilter.execute(aBitmapEx));
+ Bitmap aDisabledBitmap(aDisabledBitmapEx.GetBitmap());
+ {
+ Bitmap::ScopedReadAccess pReadAccess(aDisabledBitmap);
+ Color aColor(pReadAccess->GetPixel(0, 0));
+ CPPUNIT_ASSERT_EQUAL(Color(0x00C5C5C5), aColor);
+ }
+ }
+
+ {
+ Bitmap aBitmap(Size(3, 3), 24);
+ {
+ BitmapScopedWriteAccess pWriteAccess(aBitmap);
+ pWriteAccess->Erase(Color(0x00, 0x00, 0xFF, 0x00));
+ }
+ AlphaMask aMask(Size(3, 3));
+ {
+ AlphaScopedWriteAccess pWriteAccess(aMask);
+ pWriteAccess->Erase(Color(0x00, 0xAA, 0xAA, 0xAA));
+ }
+
+ BitmapEx aBitmapEx(aBitmap, aMask);
+ BitmapDisabledImageFilter aDisabledImageFilter;
+ BitmapEx aDisabledBitmapEx(aDisabledImageFilter.execute(aBitmapEx));
+
+ Bitmap aDisabledBitmap(aDisabledBitmapEx.GetBitmap());
+ {
+ Bitmap::ScopedReadAccess pReadAccess(aDisabledBitmap);
+ Color aColor(pReadAccess->GetPixel(0, 0));
+ CPPUNIT_ASSERT_EQUAL(Color(0x00C5C5C5), aColor);
+ }
+ AlphaMask aDisabledAlphaMask(aDisabledBitmapEx.GetAlpha());
+ {
+ AlphaMask::ScopedReadAccess 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 000000000..b804cd1bf
--- /dev/null
+++ b/vcl/qa/cppunit/BitmapScaleTest.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/.
+ */
+
+#include <cppunit/TestAssert.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapaccess.hxx>
+
+#include <tools/stream.hxx>
+#include <vcl/graphicfilter.hxx>
+
+#include <BitmapSymmetryCheck.hxx>
+#include <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);
+ Bitmap::ScopedReadAccess pReadAccess(aBitmap);
+ long nHeight = pReadAccess->Height();
+ long nWidth = pReadAccess->Width();
+ for (long y = 0; y < nHeight; ++y)
+ {
+ Scanline pScanlineRead = pReadAccess->GetScanline(y);
+ for (long x = 0; x < nWidth; ++x)
+ {
+ Color aColor = pReadAccess->GetPixelFromData(pScanlineRead, x);
+ if (aColor != rExpectedColor)
+ bResult = false;
+ }
+ }
+
+ return bResult;
+}
+
+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 == actual)
+ return;
+ if (abs(expected.GetRed() - actual.GetRed()) <= maxDifference
+ && abs(expected.GetGreen() - actual.GetGreen()) <= maxDifference
+ && abs(expected.GetBlue() - actual.GetBlue()) <= maxDifference
+ && abs(expected.GetAlpha() - actual.GetAlpha()) <= maxDifference)
+ {
+ return;
+ }
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(message, expected, actual);
+}
+
+void assertColorsAreSimilar(int maxDifference, int line, const BitmapColor& expected,
+ const BitmapColor& actual)
+{
+ std::stringstream stream;
+ stream << "Line: " << line;
+ assertColorsAreSimilar(maxDifference, 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) }
+ };
+ 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, 24);
+ {
+ // 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;
+ writeAccess->SetFillColor(COL_GREEN);
+ writeAccess->FillRect(Rectangle(Point(0, 0), Size(halfW, halfH)));
+ writeAccess->SetFillColor(COL_RED);
+ writeAccess->FillRect(Rectangle(Point(0, halfH), Size(halfW, halfH)));
+ writeAccess->SetFillColor(COL_YELLOW);
+ writeAccess->FillRect(Rectangle(Point(halfW, 0), Size(halfW, halfH)));
+ writeAccess->SetFillColor(COL_BLACK);
+ writeAccess->FillRect(Rectangle(Point(halfW, halfH), Size(halfW, halfH)));
+ writeAccess->SetFillColor(COL_BLUE);
+ writeAccess->FillRect(Rectangle(Point(halfW / 2, halfH / 2), Size(halfW, halfH)));
+ }
+ if (bExportBitmap)
+ {
+ SvFileStream aStream("~/scale_before.png", StreamMode::WRITE | StreamMode::TRUNC);
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ rFilter.compressAsPNG(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(bitmap, aStream);
+ }
+ CPPUNIT_ASSERT_EQUAL(scaleSize.destSize, bitmap.GetSizePixel());
+ {
+ // 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);
+ const int lastW = scaleSize.destSize.getWidth() - 1;
+ const int lastH = scaleSize.destSize.getHeight() - 1;
+ 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));
+ }
+ }
+ }
+}
+
+void BitmapScaleTest::testScale2()
+{
+ const bool bExportBitmap(false);
+
+ Bitmap aBitmap24Bit(Size(4096, 4096), 24);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(24), aBitmap24Bit.GetBitCount());
+ 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(aBitmap24Bit, aStream);
+ }
+
+ // Scale - 65x65
+ CPPUNIT_ASSERT_EQUAL(static_cast<long>(4096), aBitmap24Bit.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(static_cast<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(aScaledBitmap, aStream);
+ }
+
+ CPPUNIT_ASSERT_EQUAL(static_cast<long>(65), aScaledBitmap.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(static_cast<long>(65), aScaledBitmap.GetSizePixel().Height());
+ CPPUNIT_ASSERT(checkBitmapColor(aScaledBitmap, aBitmapColor));
+
+ // Scale - 64x64
+ CPPUNIT_ASSERT_EQUAL(static_cast<long>(4096), aBitmap24Bit.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(static_cast<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(aScaledBitmap, aStream);
+ }
+
+ CPPUNIT_ASSERT_EQUAL(static_cast<long>(64), aScaledBitmap.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(static_cast<long>(64), aScaledBitmap.GetSizePixel().Height());
+ CPPUNIT_ASSERT(checkBitmapColor(aScaledBitmap, aBitmapColor));
+
+ // Scale - 63x63
+ CPPUNIT_ASSERT_EQUAL(static_cast<long>(4096), aBitmap24Bit.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(static_cast<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(aScaledBitmap, aStream);
+ }
+
+ CPPUNIT_ASSERT_EQUAL(static_cast<long>(63), aScaledBitmap.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(static_cast<long>(63), aScaledBitmap.GetSizePixel().Height());
+ CPPUNIT_ASSERT(checkBitmapColor(aScaledBitmap, aBitmapColor));
+}
+
+void BitmapScaleTest::testScaleSymmetry()
+{
+ const bool bExportBitmap(false);
+
+ Bitmap aBitmap24Bit(Size(10, 10), 24);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(24), aBitmap24Bit.GetBitCount());
+
+ {
+ 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<long>(10), aBitmap24Bit.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(static_cast<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(aBitmap24Bit, aStream);
+ }
+
+ aBitmap24Bit.Scale(2, 2, BmpScaleFlag::Fast);
+
+ CPPUNIT_ASSERT_EQUAL(static_cast<long>(20), aBitmap24Bit.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(static_cast<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(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 000000000..640c477e3
--- /dev/null
+++ b/vcl/qa/cppunit/BitmapTest.cxx
@@ -0,0 +1,660 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <cppunit/TestAssert.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/plugin/TestPlugIn.h>
+
+#include <unordered_map>
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapaccess.hxx>
+#include <vcl/virdev.hxx>
+
+#include <rtl/strbuf.hxx>
+#include <config_features.h>
+#if HAVE_FEATURE_OPENGL
+#include <vcl/opengl/OpenGLHelper.hxx>
+#endif
+#include <vcl/skia/SkiaHelper.hxx>
+#include <vcl/BitmapMonochromeFilter.hxx>
+
+#include <bitmapwriteaccess.hxx>
+
+#include <svdata.hxx>
+#include <salinst.hxx>
+#include <bitmap/Octree.hxx>
+
+namespace
+{
+class BitmapTest : public CppUnit::TestFixture
+{
+ void testCreation();
+ void testEmpty();
+ void testMonochrome();
+ void testN4Greyscale();
+ void testN8Greyscale();
+ void testConvert();
+ void testCRC();
+ void testGreyPalette();
+ void testCustom8BitPalette();
+ void testErase();
+ void testBitmap32();
+ void testOctree();
+
+ CPPUNIT_TEST_SUITE(BitmapTest);
+ CPPUNIT_TEST(testCreation);
+ CPPUNIT_TEST(testEmpty);
+ CPPUNIT_TEST(testMonochrome);
+ CPPUNIT_TEST(testConvert);
+ CPPUNIT_TEST(testN4Greyscale);
+ CPPUNIT_TEST(testN8Greyscale);
+ CPPUNIT_TEST(testCRC);
+ CPPUNIT_TEST(testGreyPalette);
+ CPPUNIT_TEST(testCustom8BitPalette);
+ CPPUNIT_TEST(testErase);
+ CPPUNIT_TEST(testBitmap32);
+ CPPUNIT_TEST(testOctree);
+ 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 == actual)
+ return;
+ if (abs(expected.GetRed() - actual.GetRed()) <= maxDifference
+ && abs(expected.GetGreen() - actual.GetGreen()) <= maxDifference
+ && abs(expected.GetBlue() - actual.GetBlue()) <= maxDifference
+ && abs(expected.GetAlpha() - actual.GetAlpha()) <= 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<long>(0), aSize.Width());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong height", static_cast<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 bit count", static_cast<sal_uInt16>(0),
+ aBmp.GetBitCount());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong color count", sal_Int64(1), aBmp.GetColorCount());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong byte size", static_cast<sal_uLong>(0),
+ aBmp.GetSizeBytes());
+ }
+
+ {
+ Bitmap aBmp(Size(10, 10), 1);
+ Size aSize = aBmp.GetSizePixel();
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong width", static_cast<long>(10), aSize.Width());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong height", static_cast<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 bit count", static_cast<sal_uInt16>(1),
+ aBmp.GetBitCount());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong color count", sal_Int64(2), aBmp.GetColorCount());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong byte size", static_cast<sal_uLong>(12),
+ aBmp.GetSizeBytes());
+ }
+
+ {
+ Bitmap aBmp(Size(10, 10), 4);
+ Size aSize = aBmp.GetSizePixel();
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong width", static_cast<long>(10), aSize.Width());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong height", static_cast<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 bit count", static_cast<sal_uInt16>(4),
+ aBmp.GetBitCount());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong color count", sal_Int64(16), aBmp.GetColorCount());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong byte size", static_cast<sal_uLong>(50),
+ aBmp.GetSizeBytes());
+ }
+
+ {
+ Bitmap aBmp(Size(10, 10), 8);
+ Size aSize = aBmp.GetSizePixel();
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong width", static_cast<long>(10), aSize.Width());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong height", static_cast<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 bit count", static_cast<sal_uInt16>(8),
+ aBmp.GetBitCount());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong color count", sal_Int64(256), aBmp.GetColorCount());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong byte size", static_cast<sal_uLong>(100),
+ aBmp.GetSizeBytes());
+ }
+
+ {
+ Bitmap aBmp(Size(10, 10), 24);
+ Size aSize = aBmp.GetSizePixel();
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong width", static_cast<long>(10), aSize.Width());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong height", static_cast<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 bit count", static_cast<sal_uInt16>(24),
+ aBmp.GetBitCount());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong color count", sal_Int64(16777216),
+ aBmp.GetColorCount());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong byte size", static_cast<sal_uLong>(300),
+ aBmp.GetSizeBytes());
+ }
+
+ // Check backend capabilities and return from the test successfully
+ // if the backend doesn't support 32-bit bitmap
+ auto pBackendCapabilities = ImplGetSVData()->mpDefInst->GetBackendCapabilities();
+ if (pBackendCapabilities->mbSupportsBitmap32)
+ {
+ Bitmap aBmp(Size(10, 10), 32);
+ Size aSize = aBmp.GetSizePixel();
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong width", static_cast<long>(10), aSize.Width());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong height", static_cast<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 bit count", sal_uInt16(32), aBmp.GetBitCount());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong color count", sal_Int64(4294967296ull),
+ aBmp.GetColorCount());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong byte size", sal_uLong(400), aBmp.GetSizeBytes());
+ }
+}
+
+void BitmapTest::testEmpty()
+{
+ Bitmap aBitmap(Size(10, 10), 8);
+ aBitmap.Erase(COL_LIGHTGRAYBLUE);
+
+ CPPUNIT_ASSERT(!aBitmap.IsEmpty());
+
+ aBitmap.SetEmpty();
+ CPPUNIT_ASSERT(aBitmap.IsEmpty());
+}
+
+Bitmap createTestBitmap()
+{
+ Bitmap aBmp(Size(4, 4), 24);
+ 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::testN4Greyscale()
+{
+ Bitmap aBmp = createTestBitmap();
+ BitmapPalette aGreyscalePalette = Bitmap::GetGreyPalette(16);
+
+ aBmp.Convert(BmpConversion::N4BitGreys);
+ BitmapReadAccess aBmpReadAccess(aBmp);
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Black pixel wrong 8-bit greyscale value", aGreyscalePalette[0],
+ aBmpReadAccess.GetColor(0, 0));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Blue pixel wrong 8-bit greyscale value", aGreyscalePalette[0],
+ aBmpReadAccess.GetColor(0, 1));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Green pixel wrong 8-bit greyscale value", aGreyscalePalette[4],
+ aBmpReadAccess.GetColor(0, 2));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Cyan pixel wrong 8-bit greyscale value", aGreyscalePalette[5],
+ aBmpReadAccess.GetColor(0, 3));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Red pixel wrong 8-bit greyscale value", aGreyscalePalette[2],
+ aBmpReadAccess.GetColor(1, 0));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Magenta pixel wrong 8-bit greyscale value", aGreyscalePalette[3],
+ aBmpReadAccess.GetColor(1, 1));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Brown pixel wrong 8-bit greyscale value", aGreyscalePalette[7],
+ aBmpReadAccess.GetColor(1, 2));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Gray pixel wrong 8-bit greyscale value", aGreyscalePalette[8],
+ aBmpReadAccess.GetColor(1, 3));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Light gray pixel wrong 8-bit greyscale value",
+ aGreyscalePalette[12], aBmpReadAccess.GetColor(2, 0));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Light blue pixel wrong 8-bit greyscale value",
+ aGreyscalePalette[1], aBmpReadAccess.GetColor(2, 1));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Light green pixel wrong 8-bit greyscale value",
+ aGreyscalePalette[9], aBmpReadAccess.GetColor(2, 2));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Light cyan pixel wrong 8-bit greyscale value",
+ aGreyscalePalette[11], aBmpReadAccess.GetColor(2, 3));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Light red pixel wrong 8-bit greyscale value",
+ aGreyscalePalette[4], aBmpReadAccess.GetColor(3, 0));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Light magenta pixel wrong 8-bit greyscale value",
+ aGreyscalePalette[6], aBmpReadAccess.GetColor(3, 1));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Yellow pixel wrong 8-bit greyscale value", aGreyscalePalette[14],
+ aBmpReadAccess.GetColor(3, 2));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("White pixel wrong 8-bit greyscale value", aGreyscalePalette[15],
+ 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), 8);
+
+ aBitmap.Erase(COL_LIGHTGRAYBLUE);
+
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(8), aBitmap.GetBitCount());
+ {
+ Bitmap::ScopedReadAccess pReadAccess(aBitmap);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(8), pReadAccess->GetBitCount());
+#if defined MACOSX || defined IOS
+ //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
+ if (!SkiaHelper::isVCLSkiaEnabled())
+#if HAVE_FEATURE_OPENGL
+ if (!OpenGLHelper::isVCLOpenGLEnabled())
+#endif
+ 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(sal_uInt16(24), aBitmap.GetBitCount());
+ {
+ Bitmap::ScopedReadAccess 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 HAVE_FEATURE_OPENGL
+ if (OpenGLHelper::isVCLOpenGLEnabled())
+ CPPUNIT_ASSERT_EQUAL(sal_uInt32(30), pReadAccess->GetScanlineSize());
+ else
+#endif
+#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())
+ {
+ OStringBuffer aBuf("CRC collision between ");
+ aBuf.append(pLocation);
+ aBuf.append(" and ");
+ aBuf.append(it->second);
+ aBuf.append(" hash is 0x");
+ aBuf.append(static_cast<sal_Int64>(nCRC), 16);
+ CPPUNIT_FAIL(aBuf.toString().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), 24, nullptr);
+ 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), 8, &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);
+ }
+
+ {
+ Bitmap::ScopedReadAccess 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), 24);
+ {
+ BitmapScopedWriteAccess pWriteAccess(aBitmap);
+ pWriteAccess->Erase(Color(0x11, 0x22, 0x33));
+ }
+ {
+ Bitmap::ScopedReadAccess pReadAccess(aBitmap);
+ BitmapColor aColor(pReadAccess->GetPixel(0, 0));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(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
+ auto pBackendCapabilities = ImplGetSVData()->mpDefInst->GetBackendCapabilities();
+ if (!pBackendCapabilities->mbSupportsBitmap32)
+ return;
+
+ Bitmap aBitmap(Size(3, 3), 32);
+ {
+ BitmapScopedWriteAccess pWriteAccess(aBitmap);
+ pWriteAccess->Erase(Color(0xFF, 0x11, 0x22, 0x33));
+ pWriteAccess->SetPixel(1, 1, BitmapColor(0x44, 0xFF, 0xBB, 0x00));
+ pWriteAccess->SetPixel(2, 2, BitmapColor(0x99, 0x77, 0x66, 0x55));
+ }
+ {
+ Bitmap::ScopedReadAccess pReadAccess(aBitmap);
+ BitmapColor aColor = pReadAccess->GetPixel(0, 0);
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0x00, 0x00, 0x00, 0xFF), aColor);
+
+ aColor = pReadAccess->GetPixel(1, 1);
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0x44, 0xFF, 0xBB, 0x00), aColor);
+
+ aColor = pReadAccess->GetPixel(2, 2);
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0x99, 0x77, 0x66, 0x55), aColor);
+ }
+}
+
+void BitmapTest::testOctree()
+{
+ Size aSize(1000, 100);
+ Bitmap aBitmap(aSize, 24);
+ {
+ BitmapScopedWriteAccess pWriteAccess(aBitmap);
+ for (long y = 0; y < aSize.Height(); ++y)
+ {
+ for (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
+ Bitmap::ScopedReadAccess 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
+ Bitmap::ScopedReadAccess 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
+ Bitmap::ScopedReadAccess pAccess(aBitmap);
+ Octree aOctree(*pAccess, 256);
+ auto aBitmapPalette = aOctree.GetPalette();
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(74), aBitmapPalette.GetEntryCount());
+ }
+}
+
+} // namespace
+
+CPPUNIT_TEST_SUITE_REGISTRATION(BitmapTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* 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 000000000..e4040f35b
--- /dev/null
+++ b/vcl/qa/cppunit/FontFeatureTest.cxx
@@ -0,0 +1,336 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 <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 testGetFontFeatures();
+ void testParseFeature();
+
+ CPPUNIT_TEST_SUITE(FontFeatureTest);
+ CPPUNIT_TEST(testGetFontFeatures);
+ CPPUNIT_TEST(testParseFeature);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void FontFeatureTest::testGetFontFeatures()
+{
+#if HAVE_MORE_FONTS
+ ScopedVclPtrInstance<VirtualDevice> aVDev(*Application::GetDefaultDevice(),
+ DeviceFormat::DEFAULT, DeviceFormat::DEFAULT);
+ 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));
+
+ // We're interested only in defaults here
+ std::vector<vcl::font::Feature> rDefaultFontFeatures;
+ OUString aFeaturesString;
+ for (vcl::font::Feature const& rFeature : rFontFeatures)
+ {
+ if (rFeature.m_aID.m_aScriptCode == vcl::font::featureCode("DFLT")
+ && rFeature.m_aID.m_aLanguageCode == vcl::font::featureCode("dflt"))
+ {
+ rDefaultFontFeatures.push_back(rFeature);
+ aFeaturesString += vcl::font::featureCodeAsString(rFeature.m_aID.m_aFeatureCode) + " ";
+ }
+ }
+
+ CPPUNIT_ASSERT_EQUAL(size_t(53), rDefaultFontFeatures.size());
+
+ OUString aExpectedFeaturesString = "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 ";
+
+ CPPUNIT_ASSERT_EQUAL(aExpectedFeaturesString, aFeaturesString);
+
+ // Check C2SC feature
+ {
+ vcl::font::Feature& rFeature = rDefaultFontFeatures[0];
+ CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("c2sc"), rFeature.m_aID.m_aFeatureCode);
+
+ 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 = rDefaultFontFeatures[4];
+ CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("frac"), rFeature.m_aID.m_aFeatureCode);
+
+ 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::testParseFeature()
+{
+ { // No font features specified
+ vcl::font::FeatureParser aParser("Font name with no features");
+ CPPUNIT_ASSERT_EQUAL(size_t(0), aParser.getFeatures().size());
+ }
+ { // One feature specified, no value
+ vcl::font::FeatureParser aParser("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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 000000000..4ee5e96ed
--- /dev/null
+++ b/vcl/qa/cppunit/GraphicDescriptorTest.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/.
+ */
+
+#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 CppUnit::TestFixture
+{
+ void testDetectPNG();
+ void testDetectJPG();
+ void testDetectGIF();
+
+ CPPUNIT_TEST_SUITE(GraphicDescriptorTest);
+ CPPUNIT_TEST(testDetectPNG);
+ CPPUNIT_TEST(testDetectJPG);
+ CPPUNIT_TEST(testDetectGIF);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+BitmapEx createBitmap()
+{
+ Bitmap aBitmap(Size(100, 100), 24);
+ aBitmap.Erase(COL_LIGHTRED);
+
+ return BitmapEx(aBitmap);
+}
+
+void createBitmapAndExportForType(SvStream& rStream, OUString const& sType)
+{
+ BitmapEx aBitmapEx = createBitmap();
+
+ uno::Sequence<beans::PropertyValue> aFilterData;
+ GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter();
+ sal_uInt16 nFilterFormat = rGraphicFilter.GetExportFormatNumberForShortName(sType);
+ rGraphicFilter.ExportGraphic(aBitmapEx, "none", rStream, nFilterFormat, &aFilterData);
+
+ rStream.Seek(STREAM_SEEK_TO_BEGIN);
+}
+
+void GraphicDescriptorTest::testDetectPNG()
+{
+ SvMemoryStream aStream;
+ createBitmapAndExportForType(aStream, "png");
+
+ GraphicDescriptor aDescriptor(aStream, nullptr);
+ aDescriptor.Detect(true);
+
+ CPPUNIT_ASSERT_EQUAL(GraphicFileFormat::PNG, aDescriptor.GetFileFormat());
+
+ CPPUNIT_ASSERT_EQUAL(100L, aDescriptor.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(100L, aDescriptor.GetSizePixel().Height());
+}
+
+void GraphicDescriptorTest::testDetectJPG()
+{
+ SvMemoryStream aStream;
+ createBitmapAndExportForType(aStream, "jpg");
+
+ GraphicDescriptor aDescriptor(aStream, nullptr);
+ aDescriptor.Detect(true);
+
+ CPPUNIT_ASSERT_EQUAL(GraphicFileFormat::JPG, aDescriptor.GetFileFormat());
+
+ CPPUNIT_ASSERT_EQUAL(100L, aDescriptor.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(100L, aDescriptor.GetSizePixel().Height());
+}
+
+void GraphicDescriptorTest::testDetectGIF()
+{
+ SvMemoryStream aStream;
+ createBitmapAndExportForType(aStream, "gif");
+
+ GraphicDescriptor aDescriptor(aStream, nullptr);
+ aDescriptor.Detect(true);
+
+ CPPUNIT_ASSERT_EQUAL(GraphicFileFormat::GIF, aDescriptor.GetFileFormat());
+
+ CPPUNIT_ASSERT_EQUAL(100L, aDescriptor.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(100L, aDescriptor.GetSizePixel().Height());
+}
+
+} // 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 000000000..1ce516bf5
--- /dev/null
+++ b/vcl/qa/cppunit/GraphicFormatDetectorTest.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/.
+ */
+
+#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>
+
+using namespace css;
+
+namespace
+{
+class GraphicFormatDetectorTest : public test::BootstrapFixtureBase
+{
+ OUString getFullUrl(const OUString& sFileName)
+ {
+ return m_directories.getURLFromSrc("/vcl/qa/cppunit/data/") + sFileName;
+ }
+
+ void testDetectMET();
+ void testDetectBMP();
+ void testDetectWMF();
+ void testDetectPCX();
+ void testDetectJPG();
+ void testDetectPNG();
+ void testDetectGIF();
+ void testDetectPSD();
+ void testDetectTGA();
+ void testDetectTIF();
+ void testDetectXBM();
+ void testDetectXPM();
+ void testDetectSVG();
+ void testDetectSVGZ();
+ void testDetectPDF();
+ void testDetectEPS();
+ void testMatchArray();
+ void testCheckArrayForMatchingStrings();
+
+ CPPUNIT_TEST_SUITE(GraphicFormatDetectorTest);
+ CPPUNIT_TEST(testDetectMET);
+ CPPUNIT_TEST(testDetectBMP);
+ CPPUNIT_TEST(testDetectWMF);
+ CPPUNIT_TEST(testDetectPCX);
+ CPPUNIT_TEST(testDetectJPG);
+ CPPUNIT_TEST(testDetectPNG);
+ 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(testMatchArray);
+ CPPUNIT_TEST(testCheckArrayForMatchingStrings);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void GraphicFormatDetectorTest::testDetectMET()
+{
+ SvFileStream aFileStream(getFullUrl("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(ImpPeekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("MET"), rFormatExtension);
+}
+
+void GraphicFormatDetectorTest::testDetectBMP()
+{
+ SvFileStream aFileStream(getFullUrl("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(ImpPeekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("BMP"), rFormatExtension);
+}
+
+void GraphicFormatDetectorTest::testDetectWMF()
+{
+ SvFileStream aFileStream(getFullUrl("TypeDetectionExample.wmf"), StreamMode::READ);
+ vcl::GraphicFormatDetector aDetector(aFileStream, "WMF");
+
+ CPPUNIT_ASSERT(aDetector.detect());
+ CPPUNIT_ASSERT(aDetector.checkWMForEMF());
+
+ aFileStream.Seek(aDetector.mnStreamPosition);
+
+ OUString rFormatExtension;
+ CPPUNIT_ASSERT(ImpPeekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("WMF"), rFormatExtension);
+}
+
+void GraphicFormatDetectorTest::testDetectPCX()
+{
+ SvFileStream aFileStream(getFullUrl("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(ImpPeekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("PCX"), rFormatExtension);
+}
+
+void GraphicFormatDetectorTest::testDetectJPG()
+{
+ SvFileStream aFileStream(getFullUrl("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(ImpPeekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("JPG"), rFormatExtension);
+}
+
+void GraphicFormatDetectorTest::testDetectPNG()
+{
+ SvFileStream aFileStream(getFullUrl("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(ImpPeekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("PNG"), rFormatExtension);
+}
+
+void GraphicFormatDetectorTest::testDetectGIF()
+{
+ SvFileStream aFileStream(getFullUrl("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(ImpPeekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("GIF"), rFormatExtension);
+}
+
+void GraphicFormatDetectorTest::testDetectPSD()
+{
+ SvFileStream aFileStream(getFullUrl("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(ImpPeekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("PSD"), rFormatExtension);
+}
+
+void GraphicFormatDetectorTest::testDetectTGA()
+{
+ SvFileStream aFileStream(getFullUrl("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(ImpPeekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("TGA"), rFormatExtension);
+}
+
+void GraphicFormatDetectorTest::testDetectTIF()
+{
+ SvFileStream aFileStream(getFullUrl("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(ImpPeekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("TIF"), rFormatExtension);
+}
+
+void GraphicFormatDetectorTest::testDetectXBM()
+{
+ SvFileStream aFileStream(getFullUrl("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(ImpPeekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("XBM"), rFormatExtension);
+}
+
+void GraphicFormatDetectorTest::testDetectXPM()
+{
+ SvFileStream aFileStream(getFullUrl("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(ImpPeekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("XPM"), rFormatExtension);
+}
+
+void GraphicFormatDetectorTest::testDetectSVG()
+{
+ SvFileStream aFileStream(getFullUrl("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(ImpPeekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("SVG"), rFormatExtension);
+}
+
+void GraphicFormatDetectorTest::testDetectSVGZ()
+{
+ SvFileStream aFileStream(getFullUrl("TypeDetectionExample.svgz"), StreamMode::READ);
+ vcl::GraphicFormatDetector aDetector(aFileStream, "SVG");
+
+ CPPUNIT_ASSERT(aDetector.detect());
+ CPPUNIT_ASSERT(aDetector.checkSVG());
+
+ aFileStream.Seek(aDetector.mnStreamPosition);
+
+ OUString rFormatExtension;
+ CPPUNIT_ASSERT(ImpPeekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("SVG"), rFormatExtension);
+}
+
+void GraphicFormatDetectorTest::testDetectPDF()
+{
+ SvFileStream aFileStream(getFullUrl("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(ImpPeekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("PDF"), rFormatExtension);
+}
+
+void GraphicFormatDetectorTest::testDetectEPS()
+{
+ SvFileStream aFileStream(getFullUrl("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(ImpPeekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("EPS"), 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");
+ CPPUNIT_ASSERT(pMatchPointer != nullptr);
+ CPPUNIT_ASSERT_EQUAL(0, int(pMatchPointer - pCompleteStringPointer));
+ CPPUNIT_ASSERT_EQUAL(true, OString(pMatchPointer).startsWith("<?xml"));
+
+ // Check middle of the input string
+ pMatchPointer = vcl::matchArrayWithString(aString.c_str(), nCheckSize, "version");
+ CPPUNIT_ASSERT(pMatchPointer != nullptr);
+ CPPUNIT_ASSERT_EQUAL(6, int(pMatchPointer - pCompleteStringPointer));
+ CPPUNIT_ASSERT_EQUAL(true, OString(pMatchPointer).startsWith("version"));
+
+ pMatchPointer = vcl::matchArrayWithString(aString.c_str(), nCheckSize, "<svg");
+ CPPUNIT_ASSERT(pMatchPointer != nullptr);
+ CPPUNIT_ASSERT_EQUAL(38, int(pMatchPointer - pCompleteStringPointer));
+ CPPUNIT_ASSERT_EQUAL(true, OString(pMatchPointer).startsWith("<svg"));
+
+ // Check end of the input string
+ pMatchPointer = vcl::matchArrayWithString(aString.c_str(), nCheckSize, "/svg>");
+ CPPUNIT_ASSERT(pMatchPointer != nullptr);
+ CPPUNIT_ASSERT_EQUAL(119, int(pMatchPointer - pCompleteStringPointer));
+ CPPUNIT_ASSERT_EQUAL(true, OString(pMatchPointer).startsWith("/svg>"));
+
+ // Check that non-existing search string
+ pMatchPointer = vcl::matchArrayWithString(aString.c_str(), nCheckSize, "none");
+ 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" });
+ CPPUNIT_ASSERT_EQUAL(true, bResult);
+
+ // check ending string
+ bResult = vcl::checkArrayForMatchingStrings(pCompleteStringPointer, nCheckSize, { "/svg>" });
+ CPPUNIT_ASSERT_EQUAL(true, bResult);
+
+ // check middle string
+ bResult = vcl::checkArrayForMatchingStrings(pCompleteStringPointer, nCheckSize, { "version" });
+ CPPUNIT_ASSERT_EQUAL(true, bResult);
+
+ // check beginning and then ending string
+ bResult = vcl::checkArrayForMatchingStrings(pCompleteStringPointer, nCheckSize,
+ { "<?xml", "/svg>" });
+ CPPUNIT_ASSERT_EQUAL(true, bResult);
+
+ // check ending and then beginning string
+ bResult = vcl::checkArrayForMatchingStrings(pCompleteStringPointer, nCheckSize,
+ { "/svg>", "<?xml" });
+ CPPUNIT_ASSERT_EQUAL(false, bResult);
+
+ // check middle strings
+ bResult = vcl::checkArrayForMatchingStrings(pCompleteStringPointer, nCheckSize,
+ { "version", "<svg" });
+ CPPUNIT_ASSERT_EQUAL(true, bResult);
+
+ // check beginning, middle and ending strings
+ bResult = vcl::checkArrayForMatchingStrings(pCompleteStringPointer, nCheckSize,
+ { "<?xml", "version", "<svg", "/svg>" });
+ CPPUNIT_ASSERT_EQUAL(true, bResult);
+
+ // check non-existing
+ bResult = vcl::checkArrayForMatchingStrings(pCompleteStringPointer, nCheckSize, { "none" });
+ CPPUNIT_ASSERT_EQUAL(false, bResult);
+
+ // check non-existing on the beginning
+ bResult = vcl::checkArrayForMatchingStrings(pCompleteStringPointer, nCheckSize,
+ { "none", "version", "<svg", "/svg>" });
+ CPPUNIT_ASSERT_EQUAL(false, bResult);
+
+ // check non-existing on the end
+ bResult = vcl::checkArrayForMatchingStrings(pCompleteStringPointer, nCheckSize,
+ { "<?xml", "version", "<svg", "none" });
+ CPPUNIT_ASSERT_EQUAL(false, bResult);
+
+ // check non-existing after the end
+ bResult = vcl::checkArrayForMatchingStrings(pCompleteStringPointer, nCheckSize,
+ { "<?xml", "/svg>", "none" });
+ 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 000000000..7e5d1adcb
--- /dev/null
+++ b/vcl/qa/cppunit/GraphicNativeMetadataTest.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/.
+ */
+
+#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(const OUString& sFileName)
+ {
+ return m_directories.getURLFromSrc("/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("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_uInt16(1800), aMetadata.getRotation());
+ // 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_uInt16(1800), aMetadata.getRotation());
+ }
+}
+
+void GraphicNativeMetadataTest::testExifRotationJpeg()
+{
+ {
+ // No rotation in metadata
+ SvFileStream aFileStream(getFullUrl("Exif1.jpg"), StreamMode::READ);
+ GraphicNativeMetadata aMetadata;
+ aMetadata.read(aFileStream);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(0), aMetadata.getRotation());
+ }
+ {
+ // Rotation 90 degree clock-wise = 270 degree counter-clock-wise
+ SvFileStream aFileStream(getFullUrl("Exif1_090CW.jpg"), StreamMode::READ);
+ GraphicNativeMetadata aMetadata;
+ aMetadata.read(aFileStream);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(2700), aMetadata.getRotation());
+ }
+ {
+ // Rotation 180 degree
+ SvFileStream aFileStream(getFullUrl("Exif1_180.jpg"), StreamMode::READ);
+ GraphicNativeMetadata aMetadata;
+ aMetadata.read(aFileStream);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(1800), aMetadata.getRotation());
+ }
+ {
+ // Rotation 270 degree clock-wise = 90 degree counter-clock-wise
+ SvFileStream aFileStream(getFullUrl("Exif1_270CW.jpg"), StreamMode::READ);
+ GraphicNativeMetadata aMetadata;
+ aMetadata.read(aFileStream);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(900), aMetadata.getRotation());
+ }
+}
+
+} // 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 000000000..6a70ba921
--- /dev/null
+++ b/vcl/qa/cppunit/GraphicTest.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/.
+ */
+
+#include <sal/config.h>
+#include <config_oox.h>
+#include <cppunit/TestAssert.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/plugin/TestPlugIn.h>
+
+#include <com/sun/star/beans/PropertyValue.hpp>
+
+#include <vcl/bitmapaccess.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 <impgraph.hxx>
+
+#if USE_TLS_NSS
+#include <nss.h>
+#endif
+
+using namespace css;
+
+namespace
+{
+class GraphicTest : public CppUnit::TestFixture
+{
+public:
+ ~GraphicTest();
+
+private:
+ void testUnloadedGraphic();
+ void testUnloadedGraphicLoading();
+ void testUnloadedGraphicWmf();
+ void testUnloadedGraphicAlpha();
+ void testUnloadedGraphicSizeUnit();
+ void testSwapping();
+ void testSwappingVectorGraphic();
+
+ CPPUNIT_TEST_SUITE(GraphicTest);
+ CPPUNIT_TEST(testUnloadedGraphic);
+ CPPUNIT_TEST(testUnloadedGraphicLoading);
+ CPPUNIT_TEST(testUnloadedGraphicWmf);
+ CPPUNIT_TEST(testUnloadedGraphicAlpha);
+ CPPUNIT_TEST(testUnloadedGraphicSizeUnit);
+ CPPUNIT_TEST(testSwapping);
+ CPPUNIT_TEST(testSwappingVectorGraphic);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+GraphicTest::~GraphicTest()
+{
+#if USE_TLS_NSS
+ NSS_Shutdown();
+#endif
+}
+
+BitmapEx createBitmap(bool alpha = false)
+{
+ Bitmap aBitmap(Size(120, 100), 24);
+ 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, OUString const& 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, "none", rStream, nFilterFormat, &aFilterData);
+
+ rStream.Seek(STREAM_SEEK_TO_BEGIN);
+}
+
+Graphic makeUnloadedGraphic(OUString const& 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::unique_ptr<SvStream> createStream(OUString const& rSwapFileURL)
+{
+ std::unique_ptr<SvStream> xStream;
+
+ try
+ {
+ xStream = ::utl::UcbStreamHelper::CreateStream(
+ rSwapFileURL, StreamMode::READWRITE | StreamMode::SHARE_DENYWRITE);
+ }
+ catch (const css::uno::Exception&)
+ {
+ }
+
+ return xStream;
+}
+
+std::vector<unsigned char> calculateHash(std::unique_ptr<SvStream>& rStream)
+{
+ 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();
+}
+
+bool checkBitmap(Graphic& rGraphic)
+{
+ bool bResult = true;
+
+ Bitmap aBitmap(rGraphic.GetBitmapEx().GetBitmap());
+ {
+ Bitmap::ScopedReadAccess pReadAccess(aBitmap);
+ for (long y = 0; y < rGraphic.GetSizePixel().Height(); y++)
+ {
+ for (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;
+}
+
+char const DATA_DIRECTORY[] = "/vcl/qa/cppunit/data/";
+
+void GraphicTest::testUnloadedGraphic()
+{
+ // make unloaded test graphic
+ Graphic aGraphic = makeUnloadedGraphic("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("png");
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(120L, aGraphic.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(100L, aGraphic.GetSizePixel().Height());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+
+ // check GetPrefSize doesn't load graphic
+ CPPUNIT_ASSERT_EQUAL(6000L, aGraphic.GetPrefSize().Width());
+ CPPUNIT_ASSERT_EQUAL(5000L, 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("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());
+}
+
+void GraphicTest::testUnloadedGraphicLoading()
+{
+ const OUString aFormats[] = { "png", "gif", "jpg" };
+
+ for (OUString const& sFormat : aFormats)
+ {
+ Graphic aGraphic = makeUnloadedGraphic(sFormat);
+
+ // check available
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(120L, aGraphic.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(100L, aGraphic.GetSizePixel().Height());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+ CPPUNIT_ASSERT(aGraphic.GetSizeBytes() > 0);
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+
+ if (sFormat != "jpg")
+ CPPUNIT_ASSERT_EQUAL(true, checkBitmap(aGraphic));
+ }
+}
+
+void 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("wmf");
+ Graphic aGraphic(aBitmapEx);
+ aGraphic.SetPrefSize(Size(99, 99));
+ aGraphic.SetPrefMapMode(MapMode(MapUnit::Map100thMM));
+ rGraphicFilter.ExportGraphic(aGraphic, "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());
+}
+
+void GraphicTest::testUnloadedGraphicAlpha()
+{
+ // make unloaded test graphic with alpha
+ Graphic aGraphic = makeUnloadedGraphic("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("png", false);
+
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.IsAlpha());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.IsTransparent());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+}
+
+void 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);
+ 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());
+}
+
+void GraphicTest::testSwapping()
+{
+ // Prepare Graphic from a PNG image first
+ Graphic aGraphic = makeUnloadedGraphic("png");
+
+ CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aGraphic.GetType());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.makeAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+
+ CPPUNIT_ASSERT_EQUAL(120L, aGraphic.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(100L, aGraphic.GetSizePixel().Height());
+
+ BitmapChecksum aChecksumBeforeSwapping = aGraphic.GetChecksum();
+
+ 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();
+ OUString rSwapFileURL = aGraphic.ImplGetImpGraphic()->getSwapFileURL();
+ CPPUNIT_ASSERT_EQUAL(true, rSwapFileURL.isEmpty());
+
+ // 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
+ rSwapFileURL = aGraphic.ImplGetImpGraphic()->getSwapFileURL();
+ CPPUNIT_ASSERT_EQUAL(true, comphelper::DirectoryHelper::fileExists(rSwapFileURL));
+
+ { // Check the swap file content
+ std::unique_ptr<SvStream> xStream = createStream(rSwapFileURL);
+ CPPUNIT_ASSERT_EQUAL(true, bool(xStream));
+
+ // Check size of the stream
+ CPPUNIT_ASSERT_EQUAL(sal_uInt64(445), xStream->remainingSize());
+
+ std::vector<unsigned char> aHash = calculateHash(xStream);
+ CPPUNIT_ASSERT_EQUAL(std::string("304f17d9c56e79b95f6c337dab88709d4f9b61f0"),
+ toHexString(aHash));
+ }
+
+ // 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());
+
+ // File shouldn't be available anymore
+ CPPUNIT_ASSERT_EQUAL(false, comphelper::DirectoryHelper::fileExists(rSwapFileURL));
+
+ // Check the bitmap
+ CPPUNIT_ASSERT_EQUAL(120L, aGraphic.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(100L, aGraphic.GetSizePixel().Height());
+ CPPUNIT_ASSERT_EQUAL(true, checkBitmap(aGraphic));
+ CPPUNIT_ASSERT_EQUAL(true, checkBitmap(aGraphic));
+}
+
+void GraphicTest::testSwappingVectorGraphic()
+{
+ 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);
+
+ CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aGraphic.GetType());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+
+ // Load the vector graphic
+ CPPUNIT_ASSERT_EQUAL(true, bool(aGraphic.getVectorGraphicData()));
+ CPPUNIT_ASSERT_EQUAL(sal_uInt32(223),
+ aGraphic.getVectorGraphicData()->getVectorGraphicDataArrayLength());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(sal_uInt32(223), aGraphic.GetGfxLink().GetDataSize());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+
+ 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);
+ OUString rSwapFileURL = aGraphic.ImplGetImpGraphic()->getSwapFileURL();
+ CPPUNIT_ASSERT_EQUAL(true, rSwapFileURL.isEmpty());
+
+ // 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
+ rSwapFileURL = aGraphic.ImplGetImpGraphic()->getSwapFileURL();
+ CPPUNIT_ASSERT_EQUAL(true, comphelper::DirectoryHelper::fileExists(rSwapFileURL));
+
+ { // Check the swap file content
+ std::unique_ptr<SvStream> xStream = createStream(rSwapFileURL);
+ CPPUNIT_ASSERT_EQUAL(true, bool(xStream));
+
+ // Check size of the stream
+ CPPUNIT_ASSERT_EQUAL(sal_uInt64(349), xStream->remainingSize());
+
+ std::vector<unsigned char> aHash = calculateHash(xStream);
+ CPPUNIT_ASSERT_EQUAL(std::string("88b4c1c359e3cf7be005fbb46c93ffa6de9dcf4a"),
+ toHexString(aHash));
+ }
+
+ // 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(aBitmapChecksumBeforeSwapping, aGraphic.GetBitmapEx().GetChecksum());
+
+ // File shouldn't be available anymore
+ CPPUNIT_ASSERT_EQUAL(false, comphelper::DirectoryHelper::fileExists(rSwapFileURL));
+}
+
+} // namespace
+
+CPPUNIT_TEST_SUITE_REGISTRATION(GraphicTest);
+
+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 000000000..bf053d3bc
--- /dev/null
+++ b/vcl/qa/cppunit/ScanlineToolsTest.cxx
@@ -0,0 +1,224 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <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(0, 10, 250, 120), Color(50, 30, 230, 110), Color(100, 50, 210, 100),
+ Color(150, 70, 190, 90), Color(200, 90, 170, 80),
+ };
+
+ for (Color const& aColor : aColors)
+ {
+ pScanlineTransformer->writePixel(aColor);
+ }
+
+ std::vector<sal_uInt8> aExpectedBytes{ 0, 10, 250, 120, 50, 30, 230, 110, 100, 50,
+ 210, 100, 150, 70, 190, 90, 200, 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(0, 10, 250, 120), Color(50, 30, 230, 110), Color(100, 50, 210, 100),
+ Color(150, 70, 190, 90), Color(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(0, 10, 250, 120), Color(50, 30, 230, 110), Color(100, 50, 210, 100),
+ Color(150, 70, 190, 90), Color(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 (size_t i = 0; i < aColors.size(); ++i)
+ {
+ Color aColor = pScanlineTransformer->readPixel();
+ CPPUNIT_ASSERT_EQUAL(aColors[i], 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 (size_t i = 0; i < aColors.size(); ++i)
+ {
+ Color aColor = pScanlineTransformer->readPixel();
+ CPPUNIT_ASSERT_EQUAL(aColors[i], 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 (size_t i = 0; i < aColors.size(); ++i)
+ {
+ Color aColor = pScanlineTransformer->readPixel();
+ CPPUNIT_ASSERT_EQUAL(aColorsExpected[i], 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 000000000..30966700a
--- /dev/null
+++ b/vcl/qa/cppunit/TypeSerializerTest.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/.
+ */
+
+#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 <TypeSerializer.hxx>
+
+#if USE_TLS_NSS
+#include <nss.h>
+#endif
+
+namespace
+{
+constexpr char DATA_DIRECTORY[] = "/vcl/qa/cppunit/data/";
+
+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();
+
+ 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_SUITE_END();
+};
+
+TypeSerializerTest::~TypeSerializerTest()
+{
+#if USE_TLS_NSS
+ NSS_Shutdown();
+#endif
+}
+
+void TypeSerializerTest::testGradient()
+{
+ Gradient aGradient(GradientStyle::Radial, Color(0xFF, 0x00, 0x00), Color(0x00, 0xFF, 0x00));
+ aGradient.SetAngle(900);
+ 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(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_uInt16(900), aReadGradient.GetAngle());
+ 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 WriteGraphic - Native Format 5
+ {
+ SvMemoryStream aMemoryStream;
+ aMemoryStream.SetVersion(SOFFICE_FILEFORMAT_50);
+ aMemoryStream.SetCompressMode(SvStreamCompressFlags::NATIVE);
+ WriteGraphic(aMemoryStream, 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;
+ ReadGraphic(aMemoryStream, aNewGraphic);
+ CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aNewGraphic.GetType());
+ CPPUNIT_ASSERT_EQUAL(aChecksum, aNewGraphic.getVectorGraphicData()->GetChecksum());
+ }
+
+ // Test WriteGraphic - Normal
+ {
+ SvMemoryStream aMemoryStream;
+ WriteGraphic(aMemoryStream, 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;
+ ReadGraphic(aMemoryStream, aNewGraphic);
+ CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aNewGraphic.GetType());
+ CPPUNIT_ASSERT_EQUAL(aChecksum, aNewGraphic.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), 24);
+ aBitmap.Erase(COL_LIGHTGRAYBLUE);
+ BitmapEx aBitmapEx(aBitmap);
+ Graphic aGraphic(aBitmapEx);
+
+ // Test WriteGraphic
+ {
+ SvMemoryStream aMemoryStream;
+ WriteGraphic(aMemoryStream, 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;
+ ReadGraphic(aMemoryStream, aNewGraphic);
+ CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aNewGraphic.GetType());
+ CPPUNIT_ASSERT_EQUAL(aBitmapEx.GetChecksum(), aNewGraphic.GetBitmapExRef().GetChecksum());
+ }
+
+ // 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 WriteGraphic
+ {
+ SvMemoryStream aMemoryStream;
+ WriteGraphic(aMemoryStream, aGraphic);
+ aMemoryStream.Seek(STREAM_SEEK_TO_BEGIN);
+
+ CPPUNIT_ASSERT_EQUAL(sal_uInt64(15167), aMemoryStream.remainingSize());
+ std::vector<unsigned char> aHash = calculateHash(aMemoryStream);
+ CPPUNIT_ASSERT_EQUAL(std::string("69d0f80832a0aebcbda7ad43ecadf85e99fc1057"),
+ 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;
+ ReadGraphic(aMemoryStream, aNewGraphic);
+ CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aNewGraphic.GetType());
+ CPPUNIT_ASSERT_EQUAL(true, aNewGraphic.IsAnimated());
+ }
+
+ // Test WriteGraphic - Native Format 5
+ {
+ SvMemoryStream aMemoryStream;
+ aMemoryStream.SetVersion(SOFFICE_FILEFORMAT_50);
+ aMemoryStream.SetCompressMode(SvStreamCompressFlags::NATIVE);
+ WriteGraphic(aMemoryStream, 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;
+ ReadGraphic(aMemoryStream, aNewGraphic);
+ CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aNewGraphic.GetType());
+ CPPUNIT_ASSERT_EQUAL(true, aNewGraphic.IsAnimated());
+ }
+
+ // Test TypeSerializer
+ {
+ SvMemoryStream aMemoryStream;
+ {
+ TypeSerializer aSerializer(aMemoryStream);
+ aSerializer.writeGraphic(aGraphic);
+ }
+ aMemoryStream.Seek(STREAM_SEEK_TO_BEGIN);
+
+ CPPUNIT_ASSERT_EQUAL(sal_uInt64(15167), aMemoryStream.remainingSize());
+ std::vector<unsigned char> aHash = calculateHash(aMemoryStream);
+ CPPUNIT_ASSERT_EQUAL(std::string("69d0f80832a0aebcbda7ad43ecadf85e99fc1057"),
+ 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 WriteGraphic
+ {
+ SvMemoryStream aMemoryStream;
+ WriteGraphic(aMemoryStream, 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(OString("VCLMTF"), sID);
+
+ // Read it back
+ aMemoryStream.Seek(STREAM_SEEK_TO_BEGIN);
+ Graphic aNewGraphic;
+ ReadGraphic(aMemoryStream, aNewGraphic);
+ CPPUNIT_ASSERT_EQUAL(GraphicType::GdiMetafile, aNewGraphic.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(OString("VCLMTF"), 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());
+ }
+}
+
+} // namespace
+
+CPPUNIT_TEST_SUITE_REGISTRATION(TypeSerializerTest);
+
+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 000000000..d2e466d89
--- /dev/null
+++ b/vcl/qa/cppunit/app/test_IconThemeInfo.cxx
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <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 const id("katze");
+ OUString displayName = vcl::IconThemeInfo::ThemeIdToDisplayName(id);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("theme id is properly uppercased", OUString("Katze"), displayName);
+}
+
+void
+IconThemeInfoTest::ImagesZipIsNotValid()
+{
+ bool valid = vcl::IconThemeInfo::UrlCanBeParsed("file://images.zip");
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("images.zip is not a valid theme name", false, valid);
+}
+
+void
+IconThemeInfoTest::ImagesColibreZipIsValid()
+{
+ OUString const id("file://images_colibre.zip");
+ bool valid = vcl::IconThemeInfo::UrlCanBeParsed(id);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("images_colibre.zip is a valid theme name", true, valid);
+}
+
+void
+IconThemeInfoTest::ThemeIdIsDetectedFromFileNameWithUnderscore()
+{
+ OUString const fname("images_colibre.zip");
+ OUString sname = vcl::IconThemeInfo::FileNameToThemeId(fname);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("'colibre' theme id is returned for 'images_colibre.zip'", OUString("colibre"), sname);
+}
+
+void
+IconThemeInfoTest::ExceptionIsThrownWhenIdCannotBeDetermined1()
+{
+ bool thrown = false;
+ OUString const fname("images_colibre");
+ try {
+ vcl::IconThemeInfo::FileNameToThemeId(fname);
+ }
+ catch (std::runtime_error&) {
+ thrown = true;
+ }
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Exception was thrown",true, thrown);
+}
+
+void
+IconThemeInfoTest::ExceptionIsThrownWhenIdCannotBeDetermined2()
+{
+ bool thrown = false;
+ OUString const fname("image_colibre.zip");
+ try {
+ vcl::IconThemeInfo::FileNameToThemeId(fname);
+ }
+ 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 000000000..f65e70bc6
--- /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("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("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 000000000..69f61c79e
--- /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");
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("'breeze' theme is overridden by high contrast mode",
+ OUString("sifr"), selected);
+ 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",
+ OUString(vcl::IconThemeSelector::FALLBACK_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 000000000..eafa4d138
--- /dev/null
+++ b/vcl/qa/cppunit/bitmapcolor.cxx
@@ -0,0 +1,262 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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("Alpha wrong", static_cast<sal_uInt8>(0), 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("Alpha wrong", static_cast<sal_uInt8>(0),
+ 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("Alpha wrong", static_cast<sal_uInt8>(0),
+ 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("Alpha wrong", static_cast<sal_uInt8>(0),
+ 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("Alpha wrong", static_cast<sal_uInt8>(0),
+ 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("Alpha wrong", static_cast<sal_uInt8>(0),
+ 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("Alpha wrong", static_cast<sal_uInt8>(0),
+ aBmpColor.GetAlpha());
+ }
+
+ // Transparency / Alpha
+ {
+ BitmapColor aBmpColor(Color(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("Alpha wrong", static_cast<sal_uInt8>(255),
+ 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 000000000..73e3baab9
--- /dev/null
+++ b/vcl/qa/cppunit/bitmaprender/BitmapRenderTest.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 <test/bootstrapfixture.hxx>
+
+#include <vcl/virdev.hxx>
+#include <vcl/bitmapaccess.hxx>
+#include <vcl/svapp.hxx>
+
+#include <tools/stream.hxx>
+
+#include <vcl/graphicfilter.hxx>
+#include <vcl/filter/PngImageReader.hxx>
+
+#include <svdata.hxx>
+#include <salinst.hxx>
+
+static OUString const gaDataUrl = "/vcl/qa/cppunit/bitmaprender/data/";
+
+class BitmapRenderTest : public test::BootstrapFixture
+{
+ OUString getFullUrl(const OUString& 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("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()
+ && 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("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(aColor.GetGreen() == aColor.GetRed() && aColor.GetGreen() == aColor.GetBlue());
+ CPPUNIT_ASSERT(aColor.GetGreen() > 100);
+}
+
+void BitmapRenderTest::testDrawAlphaBitmapEx()
+{
+ 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("ImageRGBA.png"), StreamMode::READ);
+
+ vcl::PngImageReader aPngReader(aFileStream);
+ BitmapEx aBitmapEx;
+ aPngReader.read(aBitmapEx);
+
+ // Check backend capabilities, if the backend support 32-bit bitmap
+ auto pBackendCapabilities = ImplGetSVData()->mpDefInst->GetBackendCapabilities();
+ if (pBackendCapabilities->mbSupportsBitmap32)
+ {
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(32), aBitmapEx.GetBitmap().GetBitCount());
+ }
+ else
+ {
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(24), aBitmapEx.GetBitmap().GetBitCount());
+ CPPUNIT_ASSERT_EQUAL(true, aBitmapEx.IsAlpha());
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(8), aBitmapEx.GetAlpha().GetBitCount());
+ }
+
+ // Check the bitmap has pixels we expect
+ CPPUNIT_ASSERT_EQUAL(Color(0xFF, 0x00, 0x00, 0x00), aBitmapEx.GetPixelColor(0, 0));
+ CPPUNIT_ASSERT_EQUAL(Color(0x00, 0xFF, 0xFF, 0x00), aBitmapEx.GetPixelColor(1, 1));
+ CPPUNIT_ASSERT_EQUAL(Color(0x7F, 0x00, 0xFF, 0x00), aBitmapEx.GetPixelColor(2, 2));
+
+ pVDev->DrawBitmapEx(Point(), aBitmapEx);
+
+ CPPUNIT_ASSERT_EQUAL(Color(0x00, 0xFF, 0xFF, 0xFF), pVDev->GetPixel(Point(0, 0)));
+ CPPUNIT_ASSERT_EQUAL(Color(0x00, 0xFF, 0xFF, 0x00), pVDev->GetPixel(Point(1, 1)));
+
+// sometimes on Windows we get rounding error in blending so let's ignore this on Windows for now.
+#if !defined(_WIN32)
+ CPPUNIT_ASSERT_EQUAL(Color(0x00, 0x7F, 0xFF, 0x7F), pVDev->GetPixel(Point(2, 2)));
+#endif
+}
+
+#ifdef _WIN32
+
+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::testAlphaVirtualDevice()
+{
+ // Create an alpha virtual device
+ ScopedVclPtr<VirtualDevice> pAlphaVirtualDevice(VclPtr<VirtualDevice>::Create(
+ *Application::GetDefaultDevice(), DeviceFormat::DEFAULT, DeviceFormat::DEFAULT));
+
+ // 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(long(4), aBitmap.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(long(4), aBitmap.GetSizePixel().Height());
+ Color aColor = aBitmap.GetPixelColor(1, 1);
+ CPPUNIT_ASSERT_EQUAL(Color(0xffffffff), 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(long(4), aBitmap.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(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(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(0x4422FF55), aColor));
+#else
+ CPPUNIT_ASSERT_EQUAL(Color(0x4422FF55), aColor);
+#endif
+
+ // Read back the BitmapEx and check the semi-transparent pixel
+ aBitmap = pAlphaVirtualDevice->GetBitmapEx(Point(), Size(4, 4));
+ CPPUNIT_ASSERT_EQUAL(long(4), aBitmap.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(long(4), aBitmap.GetSizePixel().Height());
+
+ aColor = aBitmap.GetPixelColor(0, 0);
+#if defined _WIN32
+ CPPUNIT_ASSERT_LESS(6, deltaColor(Color(0x4422FF55), aColor));
+#else
+ CPPUNIT_ASSERT_EQUAL(Color(0x4422FF55), aColor);
+#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("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,
+ Bitmap::ScopedReadAccess 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 000000000..7a8399293
--- /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 000000000..d6390fdaa
--- /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 000000000..dd49897d9
--- /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 000000000..295310949
--- /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 000000000..d62568de6
--- /dev/null
+++ b/vcl/qa/cppunit/blocklistparsertest.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 <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();
+
+ CPPUNIT_TEST_SUITE(BlocklistParserTest);
+ CPPUNIT_TEST(testParse);
+ CPPUNIT_TEST(testEvaluate);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void BlocklistParserTest::testParse()
+{
+ std::vector<DriverInfo> aDriveInfos;
+
+ Parser aBlocklistParser(m_directories.getURLFromSrc("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 bIsWhitelisted : {true, false})
+ {
+ DriverInfo& aDriveInfo = aDriveInfos[i++];
+ CPPUNIT_ASSERT_EQUAL(bIsWhitelisted, aDriveInfo.mbWhitelisted);
+ 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(bIsWhitelisted, aDriveInfo.mbWhitelisted);
+ CPPUNIT_ASSERT_EQUAL(GetVendorId(VendorNVIDIA), aDriveInfo.maAdapterVendor);
+ CPPUNIT_ASSERT_EQUAL(VersionComparisonOp::DRIVER_EQUAL, aDriveInfo.meComparisonOp);
+
+ aDriveInfo = aDriveInfos[i++];
+ CPPUNIT_ASSERT_EQUAL(bIsWhitelisted, aDriveInfo.mbWhitelisted);
+ CPPUNIT_ASSERT_EQUAL(GetVendorId(VendorMicrosoft), aDriveInfo.maAdapterVendor);
+ CPPUNIT_ASSERT_EQUAL(VersionComparisonOp::DRIVER_NOT_EQUAL, aDriveInfo.meComparisonOp);
+
+ aDriveInfo = aDriveInfos[i++];
+ CPPUNIT_ASSERT_EQUAL(bIsWhitelisted, aDriveInfo.mbWhitelisted);
+ CPPUNIT_ASSERT_EQUAL(OUString("0xcafe"), aDriveInfo.maAdapterVendor);
+ CPPUNIT_ASSERT_EQUAL(VersionComparisonOp::DRIVER_NOT_EQUAL, aDriveInfo.meComparisonOp);
+
+ aDriveInfo = aDriveInfos[i++];
+ CPPUNIT_ASSERT_EQUAL(bIsWhitelisted, aDriveInfo.mbWhitelisted);
+ CPPUNIT_ASSERT_EQUAL(GetVendorId(VendorAll), aDriveInfo.maAdapterVendor);
+ CPPUNIT_ASSERT_EQUAL(VersionComparisonOp::DRIVER_BETWEEN_EXCLUSIVE, aDriveInfo.meComparisonOp);
+
+ aDriveInfo = aDriveInfos[i++];
+ CPPUNIT_ASSERT_EQUAL(bIsWhitelisted, aDriveInfo.mbWhitelisted);
+ CPPUNIT_ASSERT_EQUAL(GetVendorId(VendorAll), aDriveInfo.maAdapterVendor);
+ CPPUNIT_ASSERT_EQUAL(VersionComparisonOp::DRIVER_BETWEEN_INCLUSIVE, aDriveInfo.meComparisonOp);
+
+ aDriveInfo = aDriveInfos[i++];
+ CPPUNIT_ASSERT_EQUAL(bIsWhitelisted, aDriveInfo.mbWhitelisted);
+ CPPUNIT_ASSERT_EQUAL(GetVendorId(VendorAll), aDriveInfo.maAdapterVendor);
+ CPPUNIT_ASSERT_EQUAL(VersionComparisonOp::DRIVER_BETWEEN_INCLUSIVE_START, aDriveInfo.meComparisonOp);
+
+ aDriveInfo = aDriveInfos[i++];
+ CPPUNIT_ASSERT_EQUAL(bIsWhitelisted, aDriveInfo.mbWhitelisted);
+ 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("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, "10.20.30.40", vendorNVIDIA, "all", DRIVER_OS_WINDOWS_7));
+ CPPUNIT_ASSERT_EQUAL(false, FindBlocklistedDeviceInList(
+ aDriveInfos, VersionType::OpenGL, "10.20.30.40", vendorNVIDIA, "all", DRIVER_OS_WINDOWS_8));
+ CPPUNIT_ASSERT_EQUAL(false, FindBlocklistedDeviceInList(
+ aDriveInfos, VersionType::OpenGL, "10.20.30.40", vendorNVIDIA, "all", DRIVER_OS_WINDOWS_10));
+
+ // Check generic OS
+ CPPUNIT_ASSERT_EQUAL(false, FindBlocklistedDeviceInList(
+ aDriveInfos, VersionType::OpenGL, "10.20.30.50", vendorMicrosoft, "all", DRIVER_OS_WINDOWS_10));
+ CPPUNIT_ASSERT_EQUAL(true, FindBlocklistedDeviceInList(
+ aDriveInfos, VersionType::OpenGL, "10.20.30.50", vendorMicrosoft, "all", DRIVER_OS_LINUX));
+ CPPUNIT_ASSERT_EQUAL(true, FindBlocklistedDeviceInList(
+ aDriveInfos, VersionType::OpenGL, "10.20.30.50", vendorMicrosoft, "all", DRIVER_OS_OSX_10_7));
+ CPPUNIT_ASSERT_EQUAL(true, FindBlocklistedDeviceInList(
+ aDriveInfos, VersionType::OpenGL, "10.20.30.50", vendorMicrosoft, "all", DRIVER_OS_OSX_10_8));
+
+ // Check Vendors
+ CPPUNIT_ASSERT_EQUAL(true, FindBlocklistedDeviceInList(
+ aDriveInfos, VersionType::OpenGL, "10.20.30.40", vendorMicrosoft, "all", DRIVER_OS_WINDOWS_7));
+ CPPUNIT_ASSERT_EQUAL(true, FindBlocklistedDeviceInList(
+ aDriveInfos, VersionType::OpenGL, "10.20.30.40", vendorMicrosoft, "all", DRIVER_OS_WINDOWS_10));
+
+ // Check Versions
+ CPPUNIT_ASSERT_EQUAL(true, FindBlocklistedDeviceInList(
+ aDriveInfos, VersionType::OpenGL, "10.20.30.39", vendorAMD, "all", DRIVER_OS_WINDOWS_7));
+ CPPUNIT_ASSERT_EQUAL(false, FindBlocklistedDeviceInList(
+ aDriveInfos, VersionType::OpenGL, "10.20.30.40", vendorAMD, "all", DRIVER_OS_WINDOWS_7));
+ CPPUNIT_ASSERT_EQUAL(false, FindBlocklistedDeviceInList(
+ aDriveInfos, VersionType::OpenGL, "10.20.30.41", vendorAMD, "all", DRIVER_OS_WINDOWS_7));
+
+ // Check
+ CPPUNIT_ASSERT_EQUAL(true, FindBlocklistedDeviceInList(
+ aDriveInfos, VersionType::OpenGL, "9.17.10.4229", vendorIntel, "all", DRIVER_OS_WINDOWS_7));
+
+
+}
+
+} // 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 000000000..d5784b79f
--- /dev/null
+++ b/vcl/qa/cppunit/builder/demo.ui
@@ -0,0 +1,1960 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface domain="vcl">
+ <requires lib="gtk+" version="3.18"/>
+ <object class="GtkListStore" id="liststore1">
+ <columns>
+ <!-- column-name gchararray1 -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="no">[None]</col>
+ </row>
+ <row>
+ <col id="0" translatable="no">Normal</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="liststore2">
+ <columns>
+ <!-- column-name gchararray1 -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="no">1</col>
+ </row>
+ <row>
+ <col id="0" translatable="no">2</col>
+ </row>
+ <row>
+ <col id="0" translatable="no">3</col>
+ </row>
+ <row>
+ <col id="0" translatable="no">4</col>
+ </row>
+ <row>
+ <col id="0" translatable="no">5</col>
+ </row>
+ <row>
+ <col id="0" translatable="no">6</col>
+ </row>
+ <row>
+ <col id="0" translatable="no">7</col>
+ </row>
+ <row>
+ <col id="0" translatable="no">8</col>
+ </row>
+ <row>
+ <col id="0" translatable="no">9</col>
+ </row>
+ <row>
+ <col id="0" translatable="no">10</col>
+ </row>
+ <row>
+ <col id="0" translatable="no">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>
+ <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">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">gtk-cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">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">gtk-ok</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">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>
+ <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" translatable="no">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" translatable="no">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" translatable="no">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="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>
+ </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" translatable="no">EXPAND</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="no">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" translatable="no">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" translatable="no">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" translatable="no">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="xalign">0</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" translatable="no">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="xalign">0</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="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkBox" id="box2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</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" translatable="no">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" translatable="no">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" translatable="no">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="text" translatable="no">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>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label9">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="no">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" translatable="no">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>
+ <object class="GtkAlignment" id="alignment2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkGrid" id="grid2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</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" translatable="no">_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" translatable="no">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" translatable="no">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="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>
+ <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" translatable="no">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="xalign">0</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" translatable="no">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="xalign">0</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" translatable="no">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="xalign">0</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" translatable="no">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="xalign">0</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>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label11">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="no">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="GtkAlignment" id="alignment3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkBox" id="box7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="homogeneous">True</property>
+ <child>
+ <object class="GtkCheckButton" id="CB_RESTART_NUMBERING">
+ <property name="label" translatable="no">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="xalign">0</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" translatable="no">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>
+ <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" translatable="no">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="xalign">0</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" translatable="no">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>
+ <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>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label12">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="no">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="GtkAlignment" id="alignment4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkBox" id="box10">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkComboBox" id="LB_PAGE_PROPERTIES">
+ <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">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="PB_PAGE_PROPERTIES">
+ <property name="label" translatable="no">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>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label13">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="no">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" translatable="no">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="GtkAlignment" id="alignment5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkBox" id="box11">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkRadioButton" id="2">
+ <property name="label" translatable="no">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="xalign">0</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" translatable="no">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="xalign">0</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" translatable="no">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="xalign">0</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" translatable="no">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>
+ </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" translatable="no">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="xalign">0</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>
+ <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>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="no">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" translatable="no">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="GtkAlignment" id="alignment6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkBox" id="box14">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkRadioButton" id="15">
+ <property name="label" translatable="no">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="xalign">0</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" translatable="no">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="xalign">0</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" translatable="no">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="xalign">0</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" translatable="no">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="xalign">0</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" translatable="no">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="xalign">0</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>
+ <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>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="14">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="no">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="GtkAlignment" id="alignment7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkBox" id="box16">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkRadioButton" id="22">
+ <property name="label" translatable="no">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="xalign">0</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" translatable="no">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="xalign">0</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" translatable="no">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="xalign">0</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>
+ <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="GtkAlignment" id="alignment8">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">10</property>
+ <child>
+ <object class="GtkCheckButton" id="26">
+ <property name="label" translatable="no">Book mode</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="xalign">0</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </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" translatable="no">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" translatable="no">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_left">10</property>
+ <property name="margin_right">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="GtkAlignment" id="alignment9">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkTreeView" id="treeview1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="model">liststore2</property>
+ <property name="headers_visible">False</property>
+ <property name="search_column">0</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="treeview-selection3"/>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label15">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="no">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="GtkAlignment" id="alignment10">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkBox" id="box19">
+ <property name="visible">True</property>
+ <property name="can_focus">False</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>
+ <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" translatable="no">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>
+ </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" translatable="no">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" translatable="no">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" translatable="no">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" translatable="no">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>
+ </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>
+ </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="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="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="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="can_focus">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">8</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment11">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">20</property>
+ <child>
+ <object class="GtkLabel" id="label21">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="no">Before</property>
+ <property name="mnemonic_widget">entry2</property>
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">6</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment12">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">20</property>
+ <child>
+ <object class="GtkLabel" id="label22">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="no">After</property>
+ <property name="mnemonic_widget">entry3</property>
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">7</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label23">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="no">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>
+ </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>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label24">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="no">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" translatable="no">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_left">10</property>
+ <property name="margin_right">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="GtkAlignment" id="alignment13">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkTreeView" id="treeview2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="model">liststore2</property>
+ <property name="headers_visible">False</property>
+ <property name="search_column">0</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="treeview-selection4"/>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label26">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="no">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>
+ <object class="GtkAlignment" id="alignment14">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkGrid" id="grid4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</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" translatable="no">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" translatable="no">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" translatable="no">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" translatable="no">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>
+ </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="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" translatable="no">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="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="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>
+ </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" translatable="no">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>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label32">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="no">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" translatable="no">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>
+ <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" translatable="no">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" translatable="no">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" translatable="no">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" translatable="no">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" translatable="no">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" translatable="no">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" translatable="no">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" translatable="no">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 000000000..511874a0c
--- /dev/null
+++ b/vcl/qa/cppunit/canvasbitmaptest.cxx
@@ -0,0 +1,773 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/bitmapaccess.hxx>
+#include <vcl/bitmapex.hxx>
+
+#include <canvasbitmap.hxx>
+#include <algorithm>
+#include <bitmapwriteaccess.hxx>
+
+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;
+
+ {
+ Bitmap::ScopedReadAccess 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_MESSAGE( "Original bitmap size via API not (200,200)",
+ xBmp->getSize().Width == 200 && xBmp->getSize().Height == 200);
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "alpha state mismatch",
+ aContainedBmpEx.IsTransparent(), 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.IsTransparent() ? 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.IsTransparent() )
+ {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "First pixel is not fully transparent",
+ 0.0, pARGBStart[0].Alpha);
+ }
+
+ CPPUNIT_ASSERT_MESSAGE( "Second pixel is not black",
+ pRGBStart[1].Red == 0.0 && pRGBStart[1].Green == 0.0 && pRGBStart[1].Blue == 0.0);
+
+ 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<rendering::ARGBColor> aARGBColor(1);
+ uno::Sequence<rendering::RGBColor> aRGBColor(1);
+ uno::Sequence<sal_Int8> aPixel3, aPixel4;
+
+ const Color aCol(COL_GREEN);
+ aARGBColor[0].Red = vcl::unotools::toDoubleColor(aCol.GetRed());
+ aARGBColor[0].Green = vcl::unotools::toDoubleColor(aCol.GetGreen());
+ aARGBColor[0].Blue = vcl::unotools::toDoubleColor(aCol.GetBlue());
+ aARGBColor[0].Alpha = 1.0;
+
+ aRGBColor[0].Red = vcl::unotools::toDoubleColor(aCol.GetRed());
+ aRGBColor[0].Green = vcl::unotools::toDoubleColor(aCol.GetGreen());
+ aRGBColor[0].Blue = 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.IsTransparent() )
+ {
+ 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 && 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 && nIndex < 256);
+ return false;
+ }
+
+ struct PaletteColorSpaceHolder: public rtl::StaticWithInit<uno::Reference<rendering::XColorSpace>,
+ PaletteColorSpaceHolder>
+ {
+ uno::Reference<rendering::XColorSpace> operator()()
+ {
+ return vcl::unotools::createStandardColorSpace();
+ }
+ };
+
+ virtual uno::Reference< rendering::XColorSpace > SAL_CALL getColorSpace( ) override
+ {
+ // this is the method from XBitmapPalette. Return palette color
+ // space here
+ return PaletteColorSpaceHolder::get();
+ }
+
+ // 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.begin(),
+ [](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.begin(),
+ [](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.begin(),
+ [](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.realloc(1);
+ maComponentTags[0] = rendering::ColorComponentTag::INDEX;
+
+ maComponentBitCounts.realloc(1);
+ maComponentBitCounts[0] = 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 const sal_Int8 lcl_depths[]={1,4,8,24};
+
+ // Testing VclCanvasBitmap wrapper
+
+ for( size_t i=0; i<SAL_N_ELEMENTS(lcl_depths); ++i )
+ {
+ const sal_Int8 nDepth( lcl_depths[i] );
+ Bitmap aBitmap(Size(200,200),nDepth);
+ 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 );
+
+ Bitmap aMask(Size(200,200),1);
+ aMask.Erase(COL_WHITE);
+ {
+ BitmapScopedWriteAccess pAcc(aMask);
+ if( pAcc.get() )
+ {
+ pAcc->SetFillColor(COL_BLACK);
+ pAcc->FillRect(tools::Rectangle(0,0,100,100));
+ pAcc->SetPixel(0,0,BitmapColor(1));
+ pAcc->SetPixel(0,1,BitmapColor(0));
+ pAcc->SetPixel(0,2,BitmapColor(1));
+ }
+ }
+
+ xBmp.set( new VclCanvasBitmap(BitmapEx(aBitmap,aMask)) );
+
+ checkCanvasBitmap( xBmp, "masked bitmap", nDepth );
+
+ AlphaMask aAlpha(Size(200,200));
+ aAlpha.Erase(255);
+ {
+ BitmapWriteAccess* pAcc = aAlpha.AcquireWriteAccess();
+ if( pAcc )
+ {
+ pAcc->SetFillColor(COL_BLACK);
+ pAcc->FillRect(tools::Rectangle(0,0,100,100));
+ pAcc->SetPixel(0,0,BitmapColor(255));
+ pAcc->SetPixel(0,1,BitmapColor(0));
+ pAcc->SetPixel(0,2,BitmapColor(255));
+ aAlpha.ReleaseAccess(pAcc);
+ }
+ }
+
+ 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 transparent",
+ !aBmp.IsTransparent());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Bitmap does not have size (10,10)",
+ Size(10,10), aBmp.GetSizePixel());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Bitmap does not have bitcount of 8",
+ static_cast<sal_uInt16>(8), aBmp.GetBitCount());
+ {
+ Bitmap aBitmap = aBmp.GetBitmap();
+ BitmapReadAccess* pBmpAcc = aBitmap.AcquireReadAccess();
+
+ 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));
+
+ Bitmap::ReleaseAccess(pBmpAcc);
+ }
+
+ xTestBmp.set( new TestBitmap( geometry::IntegerSize2D(10,10), false ));
+
+ aBmp = vcl::unotools::bitmapExFromXBitmap(xTestBmp);
+ CPPUNIT_ASSERT_MESSAGE( "Palette bitmap is not transparent",
+ aBmp.IsTransparent());
+ 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 has bitcount of 24",
+ static_cast<sal_uInt16>(24), aBmp.GetBitCount());
+ {
+ Bitmap aBitmap = aBmp.GetBitmap();
+ BitmapReadAccess* pBmpAcc = aBitmap.AcquireReadAccess();
+ BitmapReadAccess* pAlphaAcc = aBmp.GetAlpha().AcquireReadAccess();
+
+ 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(255), 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(253), 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(253), pAlphaAcc->GetPixel(2,9));
+
+ aBmp.GetAlpha().ReleaseAccess(pAlphaAcc);
+ Bitmap::ReleaseAccess(pBmpAcc);
+ }
+}
+
+} // namespace
+
+CPPUNIT_TEST_SUITE_REGISTRATION(CanvasBitmapTest);
+
+/* 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 000000000..85a5b3991
--- /dev/null
+++ b/vcl/qa/cppunit/complextext.cxx
@@ -0,0 +1,170 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 <ostream>
+#include <vector>
+
+#if HAVE_MORE_FONTS
+// must be declared before inclusion of test/bootstrapfixture.hxx
+static std::ostream& operator<<(std::ostream& rStream, const std::vector<long>& 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>
+
+#if HAVE_MORE_FONTS
+static std::ostream& operator<<(std::ostream& rStream, const std::vector<long>& 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
+{
+public:
+ VclComplexTextTest() : BootstrapFixture(true, false) {}
+
+ /// Play with font measuring etc.
+ void testArabic();
+ void testKashida();
+ void testTdf95650(); // Windows-only issue
+
+ CPPUNIT_TEST_SUITE(VclComplexTextTest);
+ CPPUNIT_TEST(testArabic);
+ CPPUNIT_TEST(testKashida);
+ CPPUNIT_TEST(testTdf95650);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void VclComplexTextTest::testArabic()
+{
+#if HAVE_MORE_FONTS
+ const unsigned char pOneTwoThreeUTF8[] = {
+ 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 aOneTwoThree( reinterpret_cast<char const *>(pOneTwoThreeUTF8),
+ SAL_N_ELEMENTS( pOneTwoThreeUTF8 ) - 1,
+ RTL_TEXTENCODING_UTF8 );
+ ScopedVclPtrInstance<WorkWindow> pWin(static_cast<vcl::Window *>(nullptr));
+ CPPUNIT_ASSERT( pWin );
+
+ vcl::Font aFont("DejaVu Sans", "Book", Size(0, 12));
+
+ OutputDevice *pOutDev = pWin.get();
+ pOutDev->SetFont( aFont );
+
+ // absolute character widths AKA text array.
+ std::vector<long> aRefCharWidths {6, 9, 16, 16, 22, 22, 26, 29, 32, 32,
+ 36, 40, 49, 53, 56, 63, 63, 66, 72, 72};
+ std::vector<long> aCharWidths(aOneTwoThree.getLength(), 0);
+ long nTextWidth = pOutDev->GetTextArray(aOneTwoThree, aCharWidths.data());
+
+ CPPUNIT_ASSERT_EQUAL(aRefCharWidths, aCharWidths);
+ // this sporadically returns 75 or 74 on some of the windows tinderboxes eg. tb73
+ CPPUNIT_ASSERT_EQUAL(72L, nTextWidth);
+ CPPUNIT_ASSERT_EQUAL(nTextWidth, aCharWidths.back());
+
+ // text advance width and line height
+ CPPUNIT_ASSERT_EQUAL(72L, pOutDev->GetTextWidth(aOneTwoThree));
+ CPPUNIT_ASSERT_EQUAL(14L, pOutDev->GetTextHeight());
+
+ // exact bounding rectangle, not essentially the same as text width/height
+ tools::Rectangle aBoundRect, aTestRect( 0, 1, 71, 15 );
+ pOutDev->GetTextBoundRect(aBoundRect, aOneTwoThree);
+ CPPUNIT_ASSERT_EQUAL(aTestRect, aBoundRect);
+
+#if 0
+ // FIXME: This seems to be wishful thinking, GetTextRect() does not take
+ // rotation into account.
+
+ // normal orientation
+ tools::Rectangle aInput;
+ tools::Rectangle aRect = pOutDev->GetTextRect( aInput, aOneTwoThree );
+
+ // now rotate 270 degrees
+ vcl::Font aRotated( aFont );
+ aRotated.SetOrientation( 2700 );
+ pOutDev->SetFont( aRotated );
+ tools::Rectangle aRectRot = pOutDev->GetTextRect( aInput, aOneTwoThree );
+
+ // Check that we did do the rotation...
+ fprintf( stderr, "%ld %ld %ld %ld\n",
+ aRect.GetWidth(), aRect.GetHeight(),
+ aRectRot.GetWidth(), aRectRot.GetHeight() );
+ CPPUNIT_ASSERT( aRectRot.GetWidth() == aRect.GetHeight() );
+ CPPUNIT_ASSERT( aRectRot.GetHeight() == aRect.GetWidth() );
+#endif
+#endif
+}
+
+void VclComplexTextTest::testKashida()
+{
+#if HAVE_MORE_FONTS
+ // Cache the glyph list of some Arabic text.
+ ScopedVclPtrInstance<VirtualDevice> pOutputDevice;
+ auto aText
+ = OUString::fromUtf8(u8"ﻊﻨﺻﺭ ﺎﻠﻓﻮﺴﻓﻭﺭ ﻊﻨﺻﺭ ﻒﻟﺰﻳ ﺺﻠﺑ. ﺖﺘﻛﻮﻧ ﺎﻟﺩﻭﺭﺓ ﺎﻟﺭﺎﺒﻋﺓ ﻢﻧ ١٥ ﻊﻨﺻﺭïº.");
+ std::unique_ptr<SalLayout> pLayout = pOutputDevice->ImplLayout(
+ aText, 0, aText.getLength(), Point(0, 0), 0, nullptr, SalLayoutFlags::GlyphItemsOnly);
+ const SalLayoutGlyphs* pGlyphs = pLayout->GetGlyphs();
+ if (!pGlyphs)
+ // Failed in some non-interesting ways.
+ return;
+ SalLayoutGlyphs aGlyphs = *pGlyphs;
+
+ // Now lay it out using the cached glyph list.
+ ImplLayoutArgs aLayoutArgs(aText, 0, aText.getLength(), SalLayoutFlags::NONE,
+ pOutputDevice->GetFont().GetLanguageTag(), nullptr);
+ pLayout = pOutputDevice->GetGraphics()->GetTextLayout(0);
+ CPPUNIT_ASSERT(pLayout->LayoutText(aLayoutArgs, &aGlyphs));
+
+ // Without the accompanying fix in place, this test would have failed with 'assertion failed'.
+ // The kashida justification flag was lost when going via the glyph cache.
+ CPPUNIT_ASSERT(aLayoutArgs.mnFlags & SalLayoutFlags::KashidaJustification);
+#endif
+}
+
+void VclComplexTextTest::testTdf95650()
+{
+ const sal_Unicode pTxt[] = {
+ 0x0131, 0x0302, 0x0504, 0x4E44, 0x3031, 0x3030, 0x3531, 0x2D30,
+ 0x3037, 0x0706, 0x0908, 0x0B0A, 0x0D0C, 0x0F0E, 0x072E, 0x100A,
+ 0x0D11, 0x1312, 0x0105, 0x020A, 0x0512, 0x1403, 0x030C, 0x1528,
+ 0x2931, 0x632E, 0x7074, 0x0D20, 0x0E0A, 0x100A, 0xF00D, 0x0D20,
+ 0x030A, 0x0C0B, 0x20E0, 0x0A0D
+ };
+ OUString aTxt(pTxt, SAL_N_ELEMENTS(pTxt) - 1);
+ ScopedVclPtrInstance<WorkWindow> pWin(static_cast<vcl::Window *>(nullptr));
+ CPPUNIT_ASSERT(pWin);
+
+ OutputDevice *pOutDev = pWin.get();
+ // Check that the following executes without failing assertion
+ pOutDev->ImplLayout(aTxt, 9, 1, Point(), 0, nullptr, SalLayoutFlags::BiDiRtl);
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(VclComplexTextTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* 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 000000000..2e47e28cb
--- /dev/null
+++ b/vcl/qa/cppunit/data/123_Numbers.gif
Binary files differ
diff --git a/vcl/qa/cppunit/data/Exif1.jpg b/vcl/qa/cppunit/data/Exif1.jpg
new file mode 100644
index 000000000..a81425e75
--- /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 000000000..bb8a81a16
--- /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 000000000..b18b70f13
--- /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 000000000..f10764114
--- /dev/null
+++ b/vcl/qa/cppunit/data/Exif1_270CW.jpg
Binary files differ
diff --git a/vcl/qa/cppunit/data/SimpleExample.svg b/vcl/qa/cppunit/data/SimpleExample.svg
new file mode 100644
index 000000000..6890b5456
--- /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.bmp b/vcl/qa/cppunit/data/TypeDetectionExample.bmp
new file mode 100644
index 000000000..5197e42a7
--- /dev/null
+++ b/vcl/qa/cppunit/data/TypeDetectionExample.bmp
Binary files differ
diff --git a/vcl/qa/cppunit/data/TypeDetectionExample.eps b/vcl/qa/cppunit/data/TypeDetectionExample.eps
new file mode 100644
index 000000000..7f0db47bc
--- /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 000000000..b33eb4f90
--- /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 000000000..b8436eaa1
--- /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 000000000..7635e841f
--- /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 000000000..639323455
--- /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 000000000..b68bff5e1
--- /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 000000000..f73f5fd74
--- /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 000000000..8282b14fd
--- /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 000000000..e23e44c23
--- /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 000000000..17c1bcc3c
--- /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 000000000..870c88b10
--- /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 000000000..dc74dc958
--- /dev/null
+++ b/vcl/qa/cppunit/data/TypeDetectionExample.tif
Binary files differ
diff --git a/vcl/qa/cppunit/data/TypeDetectionExample.wmf b/vcl/qa/cppunit/data/TypeDetectionExample.wmf
new file mode 100644
index 000000000..7ed706928
--- /dev/null
+++ b/vcl/qa/cppunit/data/TypeDetectionExample.wmf
Binary files differ
diff --git a/vcl/qa/cppunit/data/TypeDetectionExample.xbm b/vcl/qa/cppunit/data/TypeDetectionExample.xbm
new file mode 100644
index 000000000..b40d1a45e
--- /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 000000000..7b9b94359
--- /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/inch-size.emf b/vcl/qa/cppunit/data/inch-size.emf
new file mode 100644
index 000000000..ac5a1b805
--- /dev/null
+++ b/vcl/qa/cppunit/data/inch-size.emf
Binary files differ
diff --git a/vcl/qa/cppunit/data/testBasicMorphology.png b/vcl/qa/cppunit/data/testBasicMorphology.png
new file mode 100644
index 000000000..5db565779
--- /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 000000000..ba335bab3
--- /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 000000000..3b10a949a
--- /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 000000000..30d90757e
--- /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 000000000..a506577da
--- /dev/null
+++ b/vcl/qa/cppunit/data/testBasicMorphologyDilated2Eroded1.png
Binary files differ
diff --git a/vcl/qa/cppunit/dndtest.cxx b/vcl/qa/cppunit/dndtest.cxx
new file mode 100644
index 000000000..37629239b
--- /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/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/errorhandler.cxx b/vcl/qa/cppunit/errorhandler.cxx
new file mode 100644
index 000000000..21c672ac5
--- /dev/null
+++ b/vcl/qa/cppunit/errorhandler.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 <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 ErrorInfo *pErrInfo, OUString &rErrString) const override
+ {
+ if (pErrInfo->GetErrorCode().IsDynamic())
+ rErrString = "Dynamic error";
+ else
+ 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;
+ std::unique_ptr<ErrorInfo> xErrorInfo;
+ 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
+ xErrorInfo = ErrorInfo::GetErrorInfo(ERRCODE_ABORT);
+ aErrHdlr.CreateString(xErrorInfo.get(), 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));
+ xErrorInfo = ErrorInfo::GetErrorInfo(ERRCODE_NONE);
+ aErrHdlr.CreateString(xErrorInfo.get(), 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/ipdf/data/dict-array-dict.pdf b/vcl/qa/cppunit/filter/ipdf/data/dict-array-dict.pdf
new file mode 100644
index 000000000..73de3117b
--- /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/ipdf.cxx b/vcl/qa/cppunit/filter/ipdf/ipdf.cxx
new file mode 100644
index 000000000..0f94f3276
--- /dev/null
+++ b/vcl/qa/cppunit/filter/ipdf/ipdf.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 <test/bootstrapfixture.hxx>
+#include <unotest/macros_test.hxx>
+
+#include <com/sun/star/drawing/XDrawPagesSupplier.hpp>
+#include <com/sun/star/drawing/XDrawView.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/security/XCertificate.hpp>
+#include <com/sun/star/view/XSelectionSupplier.hpp>
+#include <com/sun/star/xml/crypto/SEInitializer.hpp>
+
+#include <comphelper/propertyvalue.hxx>
+#include <osl/file.hxx>
+#include <unotools/tempfile.hxx>
+#include <sfx2/sfxbasemodel.hxx>
+#include <svx/svdview.hxx>
+#include <sfx2/viewsh.hxx>
+#include <sfx2/objsh.hxx>
+#include <vcl/filter/PDFiumLibrary.hxx>
+#include <vcl/filter/pdfdocument.hxx>
+
+using namespace ::com::sun::star;
+
+namespace
+{
+char const DATA_DIRECTORY[] = "/vcl/qa/cppunit/filter/ipdf/data/";
+}
+
+/// Covers vcl/source/filter/ipdf/ fixes.
+class VclFilterIpdfTest : public test::BootstrapFixture, public unotest::MacrosTest
+{
+public:
+ void setUp() override;
+};
+
+void VclFilterIpdfTest::setUp()
+{
+ test::BootstrapFixture::setUp();
+
+ mxDesktop.set(frame::Desktop::create(mxComponentContext));
+}
+
+CPPUNIT_TEST_FIXTURE(VclFilterIpdfTest, testDictArrayDict)
+{
+ // Load a file that has markup like this:
+ // 3 0 obj <<
+ // /Key[<</InnerKey 42>>]
+ // >>
+ OUString aSourceURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "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"));
+
+ // 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_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 000000000..e99bf12a5
--- /dev/null
+++ b/vcl/qa/cppunit/font.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 <test/bootstrapfixture.hxx>
+#include <cppunit/TestAssert.h>
+
+#include <vcl/font.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 testSymbolFlagAndCharSet();
+
+ 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(testSymbolFlagAndCharSet);
+ 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::testSymbolFlagAndCharSet()
+{
+ // default constructor should set scalable flag to false
+ vcl::Font aFont;
+
+ CPPUNIT_ASSERT_MESSAGE( "Should not be seen as a symbol font after default constructor called", !aFont.IsSymbolFont() );
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Character set should be RTL_TEXTENCODING_DONTKNOW after default constructor called",
+ RTL_TEXTENCODING_DONTKNOW, aFont.GetCharSet() );
+
+ aFont.SetSymbolFlag(true);
+
+ CPPUNIT_ASSERT_MESSAGE( "Test 1: Symbol font flag should be on", aFont.IsSymbolFont() );
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Test 1: Character set should be RTL_TEXTENCODING_SYMBOL",
+ RTL_TEXTENCODING_SYMBOL, aFont.GetCharSet() );
+
+ aFont.SetSymbolFlag(false);
+
+ CPPUNIT_ASSERT_MESSAGE( "Test 2: Symbol font flag should be off", !aFont.IsSymbolFont() );
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Test 2: Character set should be RTL_TEXTENCODING_DONTKNOW",
+ RTL_TEXTENCODING_DONTKNOW, aFont.GetCharSet() );
+
+ aFont.SetCharSet( RTL_TEXTENCODING_SYMBOL );
+
+ CPPUNIT_ASSERT_MESSAGE( "Test 3: Symbol font flag should be on", aFont.IsSymbolFont() );
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Test 3: Character set should be RTL_TEXTENCODING_SYMBOL",
+ RTL_TEXTENCODING_SYMBOL, aFont.GetCharSet() );
+
+ aFont.SetCharSet( RTL_TEXTENCODING_UNICODE );
+
+ CPPUNIT_ASSERT_MESSAGE( "Test 4: Symbol font flag should be off", !aFont.IsSymbolFont() );
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Test 4: Character set should be RTL_TEXTENCODING_UNICODE",
+ RTL_TEXTENCODING_UNICODE, aFont.GetCharSet() );
+}
+
+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 000000000..b9d0d8c21
--- /dev/null
+++ b/vcl/qa/cppunit/fontcharmap.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 <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 000000000..4a3f476c2
--- /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( 0L, aFontMetric.GetAscent() );
+ CPPUNIT_ASSERT_EQUAL( 0L, aFontMetric.GetDescent() );
+ CPPUNIT_ASSERT_EQUAL( 0L, aFontMetric.GetExternalLeading() );
+ CPPUNIT_ASSERT_EQUAL( 0L, aFontMetric.GetInternalLeading() );
+ CPPUNIT_ASSERT_EQUAL( 0L, aFontMetric.GetLineHeight() );
+
+
+ aFontMetric.SetAscent( 100 );
+ CPPUNIT_ASSERT_EQUAL( 100L, aFontMetric.GetAscent() );
+
+ aFontMetric.SetDescent( 100 );
+ CPPUNIT_ASSERT_EQUAL( 100L, aFontMetric.GetDescent() );
+
+ aFontMetric.SetExternalLeading( 100 );
+ CPPUNIT_ASSERT_EQUAL( 100L, aFontMetric.GetExternalLeading() );
+
+ aFontMetric.SetInternalLeading( 100 );
+ CPPUNIT_ASSERT_EQUAL( 100L, aFontMetric.GetInternalLeading() );
+
+ aFontMetric.SetLineHeight( 100 );
+ CPPUNIT_ASSERT_EQUAL( 100L, aFontMetric.GetLineHeight() );
+}
+
+void VclFontMetricTest::testSlant()
+{
+ // default constructor should set scalable flag to false
+ FontMetric aFontMetric;
+
+ CPPUNIT_ASSERT_EQUAL( 0L, aFontMetric.GetSlant() );
+
+ aFontMetric.SetSlant( 45 );
+ CPPUNIT_ASSERT_EQUAL( 45L, aFontMetric.GetSlant() );
+}
+
+void VclFontMetricTest::testBulletOffset()
+{
+ // default constructor should set scalable flag to false
+ FontMetric aFontMetric;
+
+ CPPUNIT_ASSERT_EQUAL( 0L, aFontMetric.GetBulletOffset() );
+
+ aFontMetric.SetBulletOffset( 45 );
+ CPPUNIT_ASSERT_EQUAL( 45L, 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/gen/data/tdf121120.png b/vcl/qa/cppunit/gen/data/tdf121120.png
new file mode 100644
index 000000000..8e48fba38
--- /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 000000000..dfcfaa997
--- /dev/null
+++ b/vcl/qa/cppunit/gen/gen.cxx
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <com/sun/star/frame/Desktop.hpp>
+
+#include <comphelper/processfactory.hxx>
+#include <sfx2/app.hxx>
+#include <sfx2/objsh.hxx>
+#include <sfx2/sfxbasemodel.hxx>
+#include <test/unoapi_test.hxx>
+#include <vcl/bitmapaccess.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/")
+ {
+ }
+
+ virtual void setUp() override
+ {
+ UnoApiTest::setUp();
+ mxDesktop.set(
+ frame::Desktop::create(comphelper::getComponentContext(getMultiServiceFactory())));
+ SfxApplication::GetOrCreate();
+ };
+
+ virtual void tearDown() override
+ {
+ if (mxComponent.is())
+ {
+ closeDocument(mxComponent);
+ mxComponent->dispose();
+ }
+ UnoApiTest::tearDown();
+ };
+
+ Bitmap load(const char* pName)
+ {
+ OUString aFileURL;
+ createFileURL(OUString::createFromAscii(pName), aFileURL);
+ mxComponent = loadFromDesktop(aFileURL, "com.sun.star.drawing.DrawingDocument");
+ 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();
+ }
+
+ uno::Reference<lang::XComponent> mxComponent;
+};
+
+CPPUNIT_TEST_FIXTURE(GenTest, testTdf121120)
+{
+ Bitmap aBitmap = load("tdf121120.png");
+ Bitmap::ScopedReadAccess 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::EnableB2dDraw);
+
+ // 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));
+ Bitmap::ScopedReadAccess 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/graphicfilter/data/README b/vcl/qa/cppunit/graphicfilter/data/README
new file mode 100644
index 000000000..2cc9fb3cb
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/README
@@ -0,0 +1,7 @@
+Files with the string 'CVE' in their name are encrypted to avoid
+problems with virus checkers on source code download.; use:
+
+mdecrypt --bare -a arcfour -o hex -k 435645 -s 3 foo.doc # to unencrypt
+mcrypt --bare -a arcfour -o hex -k 435645 -s 3 foo.doc # to create new tests
+
+to get access to the plain files for manual testing.
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 000000000..e69de29bb
--- /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 000000000..d77db5782
--- /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 000000000..4cfbdfff8
--- /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 000000000..289cf8c0e
--- /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 000000000..84ac054db
--- /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 000000000..a6aed5983
--- /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 000000000..76aaecf97
--- /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 000000000..d223dde28
--- /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 000000000..b733b2484
--- /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 000000000..2b58d1035
--- /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 000000000..cfe7e40f6
--- /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 000000000..1ca6e0008
--- /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/crash-1.bmp b/vcl/qa/cppunit/graphicfilter/data/bmp/fail/crash-1.bmp
new file mode 100644
index 000000000..84b6c35c8
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/bmp/fail/crash-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 000000000..a75d6ebae
--- /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 000000000..583b009c7
--- /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 000000000..e69de29bb
--- /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 000000000..3815a1c13
--- /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 000000000..88b11ad57
--- /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/emf/fail/.gitignore b/vcl/qa/cppunit/graphicfilter/data/emf/fail/.gitignore
new file mode 100644
index 000000000..e69de29bb
--- /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 000000000..a511da43a
--- /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 000000000..dd57d9102
--- /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 000000000..8fa6e9377
--- /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 000000000..6adabec8b
--- /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 000000000..92da5f05a
--- /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 000000000..b89db21c2
--- /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 000000000..634fccdc0
--- /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 000000000..e3baf3bfa
--- /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 000000000..ef4c6a009
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..c71739a50
--- /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 000000000..746e85e84
--- /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 000000000..fbd546cb0
--- /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 000000000..40f24b41d
--- /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 000000000..dcc2a66f7
--- /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 000000000..b82444a97
--- /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 000000000..8e67ce5ae
--- /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 000000000..b17ce7061
--- /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 000000000..0991bba4f
--- /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 000000000..78d030daa
--- /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 000000000..fadbb7516
--- /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 000000000..bbc0285db
--- /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 000000000..a52213238
--- /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 000000000..0af6c749b
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/emf/pass/fdo38580-3.emf
Binary files differ
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 000000000..e69de29bb
--- /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 000000000..7e84566e9
--- /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 000000000..cbefd0162
--- /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 000000000..a8f51b6ef
--- /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 000000000..8f0f4fdb7
--- /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 000000000..d81d3b084
--- /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 000000000..26b35e63b
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..63426f9d8
--- /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 000000000..e92a316e4
--- /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 000000000..190c7b079
--- /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 000000000..cf4f30c21
--- /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 000000000..53d2ca01e
--- /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 000000000..7cb2a03d5
--- /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 000000000..cddbdc357
--- /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 000000000..860f9e1d8
--- /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 000000000..b7265f807
--- /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 000000000..47f5d4341
--- /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 000000000..e69de29bb
--- /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 000000000..c03c8529c
--- /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 000000000..1a24da322
--- /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 000000000..794ff52e4
--- /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 000000000..8911646fa
--- /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 000000000..c5373df43
--- /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 000000000..33bbe9b5f
--- /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 000000000..e783bd33e
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..3d9481aca
--- /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 000000000..5eb27ffb5
--- /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 000000000..4917f207f
--- /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 000000000..9d26db005
--- /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 000000000..bc668d3e3
--- /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 000000000..675881f36
--- /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 000000000..01e7fe16f
--- /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 000000000..4753ed8db
--- /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 000000000..c6ee53505
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/jpg/pass/fatalerror-1.jpg
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 000000000..e69de29bb
--- /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 000000000..fa90a296f
--- /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 000000000..d0644d139
--- /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 000000000..9b30cc38c
--- /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 000000000..b9ff67bb8
--- /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 000000000..592fda10a
--- /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 000000000..38899f733
--- /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-2.png b/vcl/qa/cppunit/graphicfilter/data/png/fail/CVE-2016-0952-2.png
new file mode 100644
index 000000000..5494ca00e
--- /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 000000000..3165f2c5e
--- /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/afl-sample-Z_NEED_DICT.png b/vcl/qa/cppunit/graphicfilter/data/png/fail/afl-sample-Z_NEED_DICT.png
new file mode 100644
index 000000000..db8e7a834
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/png/fail/afl-sample-Z_NEED_DICT.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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..b5e6220dc
--- /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/CVE-2016-0952-1.png b/vcl/qa/cppunit/graphicfilter/data/png/pass/CVE-2016-0952-1.png
new file mode 100644
index 000000000..7848d5ad5
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/png/pass/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/pass/afl-sample-IDAT.png b/vcl/qa/cppunit/graphicfilter/data/png/pass/afl-sample-IDAT.png
new file mode 100644
index 000000000..b116a92ec
--- /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/black.png b/vcl/qa/cppunit/graphicfilter/data/png/pass/black.png
new file mode 100644
index 000000000..cbba93bed
--- /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 000000000..1c45c7689
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/png/pass/invalid-chunk.png
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 000000000..e69de29bb
--- /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 000000000..2fce465f7
--- /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 000000000..1b3cd1416
--- /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 000000000..b4afeb09f
--- /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 000000000..c2f14d4a0
--- /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 000000000..835e7f668
--- /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 000000000..8d6d94e12
--- /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 000000000..ad722ea13
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..14dbea080
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/svm/pass/leak-1.svm
Binary files differ
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 000000000..e69de29bb
--- /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 000000000..49d3ddf28
--- /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 000000000..ac546ce5b
--- /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 000000000..aab34004e
--- /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 000000000..b68b7403c
--- /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 000000000..370abe0e2
--- /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 000000000..10da32742
--- /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 000000000..10da32742
--- /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 000000000..87319c2d4
--- /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 000000000..ff20cad78
--- /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 000000000..20a70fa67
--- /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 000000000..045f1f45e
--- /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 000000000..2ec88066f
--- /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 000000000..02c72ad88
--- /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 000000000..29c534fdc
--- /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/ofz5942-1.wmf b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/ofz5942-1.wmf
new file mode 100644
index 000000000..f9a72867c
--- /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 000000000..e2fac1523
--- /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 000000000..583b009c7
--- /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 000000000..e69de29bb
--- /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 000000000..e70664e64
--- /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 000000000..cdb09c6b2
--- /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 000000000..7864da572
--- /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 000000000..1512a2256
--- /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 000000000..365a247a7
--- /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 000000000..bfd7e20de
--- /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 000000000..e69de29bb
--- /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 000000000..9d2a43470
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..aad9f0305
--- /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 000000000..e69de29bb
--- /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 000000000..ca2d777a1
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..b5eab3e33
--- /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-test.cxx b/vcl/qa/cppunit/graphicfilter/filters-test.cxx
new file mode 100644
index 000000000..26f743cfa
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/filters-test.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/.
+ */
+
+#include <unotest/filters-test.hxx>
+#include <test/bootstrapfixture.hxx>
+
+#include <comphelper/fileformat.h>
+
+#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(const OUString& 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 ), 24 );
+ 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 %ld, %ld\n", aAfter.Width(), aAfter.Height() );
+ CPPUNIT_ASSERT( labs (aAfter.Height() - aAfter.Width()) <= 1 );
+ }
+}
+
+void VclFiltersTest::checkExportImport(const OUString& aFilterShortName)
+{
+ Bitmap aBitmap( Size( 100, 100 ), 24 );
+ aBitmap.Erase(COL_WHITE);
+
+ SvMemoryStream aStream;
+ aStream.SetVersion( SOFFICE_FILEFORMAT_CURRENT );
+
+ css::uno::Sequence< css::beans::PropertyValue > aFilterData( 3 );
+ aFilterData[ 0 ].Name = "Interlaced";
+ aFilterData[ 0 ].Value <<= sal_Int32(0);
+ aFilterData[ 1 ].Name = "Compression";
+ aFilterData[ 1 ].Value <<= sal_Int32(1);
+ aFilterData[ 2 ].Name = "Quality";
+ aFilterData[ 2 ].Value <<= sal_Int32(90);
+
+ sal_uInt16 aFilterType = mpGraphicFilter->GetExportFormatNumberForShortName(aFilterShortName);
+ mpGraphicFilter->ExportGraphic( aBitmap, OUString(), aStream, aFilterType, &aFilterData );
+
+ CPPUNIT_ASSERT(aStream.Tell() > 0);
+
+ aStream.Seek( STREAM_SEEK_TO_BEGIN );
+
+ Graphic aLoadedGraphic;
+ mpGraphicFilter->ImportGraphic( aLoadedGraphic, OUString(), aStream );
+
+ BitmapEx aLoadedBitmapEx = aLoadedGraphic.GetBitmapEx();
+ Size aSize = aLoadedBitmapEx.GetSizePixel();
+
+ CPPUNIT_ASSERT_EQUAL(100L, aSize.Width());
+ CPPUNIT_ASSERT_EQUAL(100L, aSize.Height());
+}
+
+void VclFiltersTest::testExportImport()
+{
+ fprintf(stderr, "Check ExportImport JPG\n");
+ checkExportImport("jpg");
+ fprintf(stderr, "Check ExportImport PNG\n");
+ checkExportImport("png");
+ fprintf(stderr, "Check ExportImport BMP\n");
+ checkExportImport("bmp");
+}
+
+void VclFiltersTest::testCVEs()
+{
+#ifndef DISABLE_CVE_TESTS
+ testDir(OUString(),
+ m_directories.getURLFromSrc("/vcl/qa/cppunit/graphicfilter/data/wmf/"));
+
+ testDir(OUString(),
+ m_directories.getURLFromSrc("/vcl/qa/cppunit/graphicfilter/data/emf/"));
+
+ testDir(OUString(),
+ m_directories.getURLFromSrc("/vcl/qa/cppunit/graphicfilter/data/png/"));
+
+ testDir(OUString(),
+ m_directories.getURLFromSrc("/vcl/qa/cppunit/graphicfilter/data/jpg/"));
+
+ testDir(OUString(),
+ m_directories.getURLFromSrc("/vcl/qa/cppunit/graphicfilter/data/gif/"));
+
+ testDir(OUString(),
+ m_directories.getURLFromSrc("/vcl/qa/cppunit/graphicfilter/data/bmp/"));
+
+ testDir(OUString(),
+ m_directories.getURLFromSrc("/vcl/qa/cppunit/graphicfilter/data/xbm/"));
+
+ testDir(OUString(),
+ m_directories.getURLFromSrc("/vcl/qa/cppunit/graphicfilter/data/xpm/"));
+
+ testDir(OUString(),
+ m_directories.getURLFromSrc("/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/jpeg/JpegReaderTest.cxx b/vcl/qa/cppunit/jpeg/JpegReaderTest.cxx
new file mode 100644
index 000000000..a5c0d9e95
--- /dev/null
+++ b/vcl/qa/cppunit/jpeg/JpegReaderTest.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/.
+ *
+ */
+
+#include <unotest/bootstrapfixturebase.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <bitmapwriteaccess.hxx>
+#include <tools/stream.hxx>
+
+static OUString const gaDataUrl("/vcl/qa/cppunit/jpeg/data/");
+
+class JpegReaderTest : public test::BootstrapFixtureBase
+{
+ OUString getFullUrl(const OUString& sFileName)
+ {
+ return m_directories.getURLFromSrc(gaDataUrl) + sFileName;
+ }
+
+ Graphic loadJPG(const OUString& aURL);
+
+public:
+ void testReadRGB();
+ void testReadGray();
+ void testReadCMYK();
+
+ CPPUNIT_TEST_SUITE(JpegReaderTest);
+ CPPUNIT_TEST(testReadRGB);
+ CPPUNIT_TEST(testReadGray);
+ CPPUNIT_TEST(testReadCMYK);
+ 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, long nAreaHeight, long nAreaWidth, Color aExpectedColor, int nMaxDelta)
+{
+ BitmapScopedWriteAccess pAccess(rBitmap);
+
+ long nWidth = std::min(nAreaWidth, pAccess->Width());
+ long nHeight = std::min(nAreaHeight, pAccess->Height());
+
+ long firstX = 0 + aLayerNumber;
+ long firstY = 0 + aLayerNumber;
+
+ long lastX = nWidth - 1 - aLayerNumber;
+ long lastY = nHeight - 1 - aLayerNumber;
+
+ int delta;
+
+ for (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 (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("JPEGTestRGB.jpeg"));
+ Bitmap aBitmap = aGraphic.GetBitmapEx().GetBitmap();
+ Size aSize = aBitmap.GetSizePixel();
+ CPPUNIT_ASSERT_EQUAL(12L, aSize.Width());
+ CPPUNIT_ASSERT_EQUAL(12L, 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("JPEGTestGray.jpeg"));
+ Bitmap aBitmap = aGraphic.GetBitmapEx().GetBitmap();
+ Size aSize = aBitmap.GetSizePixel();
+ CPPUNIT_ASSERT_EQUAL(12L, aSize.Width());
+ CPPUNIT_ASSERT_EQUAL(12L, 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("JPEGTestCMYK.jpeg"));
+ Bitmap aBitmap = aGraphic.GetBitmapEx().GetBitmap();
+ Size aSize = aBitmap.GetSizePixel();
+ CPPUNIT_ASSERT_EQUAL(12L, aSize.Width());
+ CPPUNIT_ASSERT_EQUAL(12L, 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));
+}
+
+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 000000000..b4d9d2460
--- /dev/null
+++ b/vcl/qa/cppunit/jpeg/JpegWriterTest.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 <unotest/bootstrapfixturebase.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <vcl/bitmapaccess.hxx>
+#include <tools/stream.hxx>
+
+static OUString const gaDataUrl("/vcl/qa/cppunit/jpeg/data/");
+
+class JpegWriterTest : public test::BootstrapFixtureBase
+{
+ OUString getFullUrl(const OUString& 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)
+{
+ SvMemoryStream stream;
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ sal_uInt16 exportFormatJPG = rFilter.GetExportFormatNumberForShortName(JPG_SHORTNAME);
+ Graphic aExportGraphic(bitmap);
+ ErrCode bResult = rFilter.ExportGraphic(aExportGraphic, "memory", stream, exportFormatJPG);
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, bResult);
+ stream.Seek(0);
+ Graphic aImportGraphic;
+ sal_uInt16 importFormatJPG = rFilter.GetImportFormatNumberForShortName(JPG_SHORTNAME);
+ bResult = rFilter.ImportGraphic(aImportGraphic, "memory", stream, importFormatJPG);
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, bResult);
+ return aImportGraphic.GetBitmapEx();
+}
+
+void JpegWriterTest::testWrite8BitGrayscale()
+{
+ Bitmap bitmap = roundtripJPG(getFullUrl("8BitGrayscale.jpg")).GetBitmap();
+ Bitmap::ScopedReadAccess 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("8BitNonGrayscale.gif")).GetBitmap();
+ Bitmap::ScopedReadAccess 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 000000000..91541e4a8
--- /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 000000000..295310949
--- /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 000000000..81cca025e
--- /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 000000000..014825f42
--- /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 000000000..3cfe1dda2
--- /dev/null
+++ b/vcl/qa/cppunit/jpeg/data/JPEGTestRGB.jpeg
Binary files differ
diff --git a/vcl/qa/cppunit/lifecycle.cxx b/vcl/qa/cppunit/lifecycle.cxx
new file mode 100644
index 000000000..a4a3f5d9e
--- /dev/null
+++ b/vcl/qa/cppunit/lifecycle.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/.
+ */
+
+#include <test/bootstrapfixture.hxx>
+
+#include <vcl/wrkwin.hxx>
+#include <vcl/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/XWindowPeer.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 testFocus();
+ 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(testFocus);
+ 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::BITMASK);
+ CPPUNIT_ASSERT(!!pVDev && !!pVDev2 && !!pVDev3 && !!pVDev4);
+ pVDev.disposeAndClear();
+ pVDev4.disposeAndClear();
+}
+
+void LifecycleTest::testMultiDispose()
+{
+ VclPtrInstance<WorkWindow> xWin(nullptr, WB_APP|WB_STDWORK);
+ CPPUNIT_ASSERT(xWin.get() != nullptr);
+ 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 );
+ (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.get() != nullptr);
+ 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.get() != nullptr);
+ 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 {
+
+class FocusCrashPostDispose : public TabControl
+{
+public:
+ explicit FocusCrashPostDispose(vcl::Window *pParent) :
+ TabControl(pParent, 0)
+ {
+ }
+ virtual bool PreNotify( NotifyEvent& ) override
+ {
+ return false;
+ }
+ virtual bool EventNotify( NotifyEvent& ) override
+ {
+ return false;
+ }
+ virtual void GetFocus() override
+ {
+ CPPUNIT_FAIL("get focus");
+ }
+ virtual void LoseFocus() override
+ {
+ CPPUNIT_FAIL("this should never be called");
+ }
+};
+
+}
+
+void LifecycleTest::testFocus()
+{
+ ScopedVclPtrInstance<WorkWindow> xWin(nullptr, WB_APP|WB_STDWORK);
+ ScopedVclPtrInstance< FocusCrashPostDispose > xChild(xWin);
+ xWin->Show();
+ xChild->GrabFocus();
+ // process asynchronous ToTop
+ Scheduler::ProcessTaskScheduling();
+ // FIXME: really awful to test focus issues without showing windows.
+ // CPPUNIT_ASSERT(xChild->HasFocus());
+}
+
+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<OKButton>(xParent));
+ aObjects.push_back(LeakTestObject::Create<CancelButton>(xParent));
+ aObjects.push_back(LeakTestObject::Create<HelpButton>(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));
+
+ { // 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, "PrintProgressDialog", "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/mnemonic.cxx b/vcl/qa/cppunit/mnemonic.cxx
new file mode 100644
index 000000000..e870e1d29
--- /dev/null
+++ b/vcl/qa/cppunit/mnemonic.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 <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();
+
+ CPPUNIT_TEST_SUITE(VclMnemonicTest);
+ CPPUNIT_TEST(testMnemonic);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void VclMnemonicTest::testMnemonic()
+{
+ MnemonicGenerator aGenerator;
+
+ {
+ OUString sResult = aGenerator.CreateMnemonic(u"ßa");
+ CPPUNIT_ASSERT_EQUAL(u'~', sResult[1]);
+ }
+
+ {
+ const sal_Unicode TEST[] = { 0x4E00, 'b' };
+ OUString sResult = aGenerator.CreateMnemonic(OUString(TEST, SAL_N_ELEMENTS(TEST)));
+ CPPUNIT_ASSERT_EQUAL(u'~', sResult[1]);
+ }
+
+ {
+ const sal_Unicode TEST[] = { 0x4E00 };
+ OUString sResult = aGenerator.CreateMnemonic(OUString(TEST, SAL_N_ELEMENTS(TEST)));
+ CPPUNIT_ASSERT_EQUAL(OUString("(~C)"), sResult.copy(sResult.getLength() - 4));
+ }
+
+}
+
+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 000000000..e99a35f67
--- /dev/null
+++ b/vcl/qa/cppunit/outdev.cxx
@@ -0,0 +1,291 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/print.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/window.hxx>
+#include <vcl/bitmapaccess.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <bitmapwriteaccess.hxx>
+#include <bufferdevice.hxx>
+#include <window.h>
+
+#include <basegfx/matrix/b2dhommatrix.hxx>
+
+class VclOutdevTest : public test::BootstrapFixture
+{
+public:
+ VclOutdevTest() : BootstrapFixture(true, false) {}
+
+ void testVirtualDevice();
+ void testUseAfterDispose();
+ void testPrinterBackgroundColor();
+ void testWindowBackgroundColor();
+ void testGetReadableFontColorPrinter();
+ void testGetReadableFontColorWindow();
+ void testDrawTransformedBitmapEx();
+ void testDrawTransformedBitmapExFlip();
+ void testRTL();
+ void testRTLGuard();
+
+ CPPUNIT_TEST_SUITE(VclOutdevTest);
+ CPPUNIT_TEST(testVirtualDevice);
+ CPPUNIT_TEST(testUseAfterDispose);
+ CPPUNIT_TEST(testPrinterBackgroundColor);
+ CPPUNIT_TEST(testWindowBackgroundColor);
+ CPPUNIT_TEST(testGetReadableFontColorPrinter);
+ CPPUNIT_TEST(testGetReadableFontColorWindow);
+ CPPUNIT_TEST(testDrawTransformedBitmapEx);
+ CPPUNIT_TEST(testDrawTransformedBitmapExFlip);
+ CPPUNIT_TEST(testRTL);
+ CPPUNIT_TEST(testRTLGuard);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void VclOutdevTest::testGetReadableFontColorPrinter()
+{
+ ScopedVclPtrInstance<Printer> pPrinter;
+ CPPUNIT_ASSERT_EQUAL(pPrinter->GetReadableFontColor(COL_WHITE, COL_WHITE), COL_BLACK);
+}
+
+void VclOutdevTest::testGetReadableFontColorWindow()
+{
+ ScopedVclPtrInstance<vcl::Window> pWindow(nullptr, WB_APP | WB_STDWORK);
+ CPPUNIT_ASSERT_EQUAL(pWindow->GetReadableFontColor(COL_WHITE, COL_BLACK), COL_WHITE);
+ CPPUNIT_ASSERT_EQUAL(pWindow->GetReadableFontColor(COL_WHITE, COL_WHITE), COL_BLACK);
+ CPPUNIT_ASSERT_EQUAL(pWindow->GetReadableFontColor(COL_BLACK, COL_BLACK), COL_WHITE);
+}
+
+void VclOutdevTest::testPrinterBackgroundColor()
+{
+ ScopedVclPtrInstance<Printer> pPrinter;
+ CPPUNIT_ASSERT_EQUAL(pPrinter->GetBackgroundColor(), COL_WHITE);
+}
+
+void VclOutdevTest::testWindowBackgroundColor()
+{
+ ScopedVclPtrInstance<vcl::Window> pWindow(nullptr, WB_APP | WB_STDWORK);
+ pWindow->SetBackground(Wallpaper(COL_WHITE));
+ CPPUNIT_ASSERT_EQUAL(pWindow->GetBackgroundColor(), COL_WHITE);
+}
+
+void VclOutdevTest::testVirtualDevice()
+{
+ ScopedVclPtrInstance< VirtualDevice > pVDev;
+ pVDev->SetOutputSizePixel(Size(32,32));
+ pVDev->SetBackground(Wallpaper(COL_WHITE));
+
+ CPPUNIT_ASSERT_EQUAL(pVDev->GetBackgroundColor(), COL_WHITE);
+
+ 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.
+ Bitmap::ScopedReadAccess 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
+}
+
+void 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();
+}
+
+void 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), 24);
+ {
+ // 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();
+ Bitmap::ScopedReadAccess 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));
+ }
+ }
+}
+
+void 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), 24);
+ {
+ // 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();
+ Bitmap::ScopedReadAccess 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));
+}
+
+void VclOutdevTest::testRTL()
+{
+ ScopedVclPtrInstance<vcl::Window> pWindow(nullptr, WB_APP | WB_STDWORK);
+ pWindow->EnableRTL();
+ vcl::RenderContext& rRenderContext = *pWindow;
+ 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());
+}
+
+void 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_SUITE_REGISTRATION(VclOutdevTest);
+
+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 000000000..49fb9bfff
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/6m-wide.odg
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/SimpleMultiPagePDF.pdf b/vcl/qa/cppunit/pdfexport/data/SimpleMultiPagePDF.pdf
new file mode 100644
index 000000000..af665fcba
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/SimpleMultiPagePDF.pdf
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/forcepoint71.key b/vcl/qa/cppunit/pdfexport/data/forcepoint71.key
new file mode 100644
index 000000000..716fe5848
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/forcepoint71.key
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 000000000..b6787aff6
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/link-wrong-page.odp
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 000000000..739a80c47
--- /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/reduce-image.fodt b/vcl/qa/cppunit/pdfexport/data/reduce-image.fodt
new file mode 100644
index 000000000..b5737ae27
--- /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 000000000..99ff22746
--- /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/softhyphen_pdf.odt b/vcl/qa/cppunit/pdfexport/data/softhyphen_pdf.odt
new file mode 100644
index 000000000..ecd779537
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/softhyphen_pdf.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 000000000..82ce29cda
--- /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 000000000..9c86a3bd7
--- /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 000000000..ba5c96de6
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf105954.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf106059.odt b/vcl/qa/cppunit/pdfexport/data/tdf106059.odt
new file mode 100644
index 000000000..a2c180378
--- /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 000000000..3581157de
--- /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 000000000..a2c180378
--- /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 000000000..da3b7e814
--- /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 000000000..d46c93dff
--- /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 000000000..3fa76c49f
--- /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 000000000..644e65c6d
--- /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 000000000..3bfc7b2d7
--- /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 000000000..5aaaab944
--- /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 000000000..8309f4b87
--- /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 000000000..246c0c72a
--- /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 000000000..7d9afa378
--- /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 000000000..5f8a1b10e
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf113143.odp
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 000000000..63fe82946
--- /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 000000000..c1e1f6d43
--- /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 000000000..b401a74ce
--- /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 000000000..39f4ce178
--- /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 000000000..caabc4987
--- /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 000000000..7d2a87cf0
--- /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 000000000..a831b1136
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf121962.odt
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 000000000..d216504b7
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf128630.odp
Binary files differ
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 000000000..7fecc55c6
--- /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 000000000..3d7b5e59c
--- /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 000000000..6db91fe81
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf66597-3.odt
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 000000000..47c370004
--- /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 000000000..de12f9baa
--- /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 000000000..ab29e88a4
--- /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/pdfexport.cxx b/vcl/qa/cppunit/pdfexport/pdfexport.cxx
new file mode 100644
index 000000000..fe402b4ed
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/pdfexport.cxx
@@ -0,0 +1,2295 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 <type_traits>
+
+#include <config_features.h>
+
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/frame/XStorable.hpp>
+#include <com/sun/star/view/XPrintable.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/processfactory.hxx>
+#include <comphelper/propertysequence.hxx>
+#include <test/bootstrapfixture.hxx>
+#include <unotest/macros_test.hxx>
+#include <unotools/mediadescriptor.hxx>
+#include <unotools/tempfile.hxx>
+#include <vcl/filter/pdfdocument.hxx>
+#include <tools/zcodec.hxx>
+#include <fpdf_edit.h>
+#include <fpdf_text.h>
+#include <fpdf_doc.h>
+#include <fpdfview.h>
+#include <vcl/graphicfilter.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <unotools/streamwrap.hxx>
+
+#include <vcl/filter/PDFiumLibrary.hxx>
+
+using namespace ::com::sun::star;
+
+static std::ostream& operator<<(std::ostream& rStrm, const Color& rColor)
+{
+ rStrm << "Color: R:" << static_cast<int>(rColor.GetRed())
+ << " G:" << static_cast<int>(rColor.GetGreen())
+ << " B:" << static_cast<int>(rColor.GetBlue())
+ << " A:" << static_cast<int>(rColor.GetTransparency());
+ return rStrm;
+}
+
+namespace
+{
+
+struct CloseDocument {
+ void operator ()(FPDF_DOCUMENT doc) {
+ if (doc != nullptr) {
+ FPDF_CloseDocument(doc);
+ }
+ }
+};
+
+using DocumentHolder =
+ std::unique_ptr<typename std::remove_pointer<FPDF_DOCUMENT>::type, CloseDocument>;
+
+struct ClosePage {
+ void operator ()(FPDF_PAGE page) {
+ if (page != nullptr) {
+ FPDF_ClosePage(page);
+ }
+ }
+};
+
+using PageHolder =
+ std::unique_ptr<typename std::remove_pointer<FPDF_PAGE>::type, ClosePage>;
+
+/// Tests the PDF export filter.
+class PdfExportTest : public test::BootstrapFixture, public unotest::MacrosTest
+{
+ uno::Reference<lang::XComponent> mxComponent;
+ utl::TempFile maTempFile;
+ SvMemoryStream maMemory;
+ // Export the document as PDF, then parse it with PDFium.
+ DocumentHolder exportAndParse(const OUString& rURL, const utl::MediaDescriptor& rDescriptor);
+ std::shared_ptr<vcl::pdf::PDFium> mpPDFium;
+
+public:
+ PdfExportTest();
+ virtual void setUp() override;
+ virtual void tearDown() override;
+ void saveAsPDF(const OUString& rFile);
+ void load(const OUString& rFile, vcl::filter::PDFDocument& rDocument);
+ /// Tests that a pdf image is roundtripped back to PDF as a vector format.
+ void testTdf106059();
+ /// Tests that text highlight from Impress is not lost.
+ void testTdf105461();
+ void testTdf107868();
+ /// Tests that embedded video from Impress is not exported as a linked one.
+ void testTdf105093();
+ /// Tests export of non-PDF images.
+ void testTdf106206();
+ /// Tests export of PDF images without reference XObjects.
+ void testTdf106693();
+ void testForcePoint71();
+ void testTdf106972();
+ void testTdf106972Pdf17();
+ void testSofthyphenPos();
+ void testTdf107013();
+ void testTdf107018();
+ void testTdf107089();
+ void testTdf99680();
+ void testTdf99680_2();
+ void testTdf108963();
+ void testTdf118244_radioButtonGroup();
+ /// Test writing ToUnicode CMAP for LTR ligatures.
+ void testTdf115117_1();
+ /// Text extracting LTR text with ligatures.
+ void testTdf115117_1a();
+ /// Test writing ToUnicode CMAP for RTL ligatures.
+ void testTdf115117_2();
+ /// Test extracting RTL text with ligatures.
+ void testTdf115117_2a();
+ /// Test writing ToUnicode CMAP for doubly encoded glyphs.
+ void testTdf66597_1();
+ /// Test writing ActualText for RTL many to one glyph to Unicode mapping.
+ void testTdf66597_2();
+ /// Test writing ActualText for LTR many to one glyph to Unicode mapping.
+ void testTdf66597_3();
+ void testTdf109143();
+ void testTdf105954();
+ void testTdf128630();
+ void testTdf106702();
+ void testTdf113143();
+ void testTdf115262();
+ void testTdf121962();
+ void testTdf115967();
+ void testTdf121615();
+ void testTocLink();
+ void testPdfImageResourceInlineXObjectRef();
+ void testReduceSmallImage();
+ void testReduceImage();
+ void testLinkWrongPage();
+ void testLargePage();
+ void testVersion15();
+ void testDefaultVersion();
+ void testMultiPagePDF();
+
+
+ CPPUNIT_TEST_SUITE(PdfExportTest);
+ CPPUNIT_TEST(testTdf106059);
+ CPPUNIT_TEST(testTdf105461);
+ CPPUNIT_TEST(testTdf107868);
+ CPPUNIT_TEST(testTdf105093);
+ CPPUNIT_TEST(testTdf106206);
+ CPPUNIT_TEST(testTdf106693);
+ CPPUNIT_TEST(testForcePoint71);
+ CPPUNIT_TEST(testTdf106972);
+ CPPUNIT_TEST(testTdf106972Pdf17);
+ CPPUNIT_TEST(testSofthyphenPos);
+ CPPUNIT_TEST(testTdf107013);
+ CPPUNIT_TEST(testTdf107018);
+ CPPUNIT_TEST(testTdf107089);
+ CPPUNIT_TEST(testTdf99680);
+ CPPUNIT_TEST(testTdf99680_2);
+ CPPUNIT_TEST(testTdf108963);
+ CPPUNIT_TEST(testTdf118244_radioButtonGroup);
+ CPPUNIT_TEST(testTdf115117_1);
+ CPPUNIT_TEST(testTdf115117_1a);
+ CPPUNIT_TEST(testTdf115117_2);
+ CPPUNIT_TEST(testTdf115117_2a);
+ CPPUNIT_TEST(testTdf66597_1);
+ CPPUNIT_TEST(testTdf66597_2);
+ CPPUNIT_TEST(testTdf66597_3);
+ CPPUNIT_TEST(testTdf109143);
+ CPPUNIT_TEST(testTdf105954);
+ CPPUNIT_TEST(testTdf128630);
+ CPPUNIT_TEST(testTdf106702);
+ CPPUNIT_TEST(testTdf113143);
+ CPPUNIT_TEST(testTdf115262);
+ CPPUNIT_TEST(testTdf121962);
+ CPPUNIT_TEST(testTdf115967);
+ CPPUNIT_TEST(testTdf121615);
+ CPPUNIT_TEST(testTocLink);
+ CPPUNIT_TEST(testPdfImageResourceInlineXObjectRef);
+ CPPUNIT_TEST(testReduceSmallImage);
+ CPPUNIT_TEST(testReduceImage);
+ CPPUNIT_TEST(testLinkWrongPage);
+ CPPUNIT_TEST(testLargePage);
+ CPPUNIT_TEST(testVersion15);
+ CPPUNIT_TEST(testDefaultVersion);
+ CPPUNIT_TEST(testMultiPagePDF);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+PdfExportTest::PdfExportTest()
+{
+ maTempFile.EnableKillingFile();
+}
+
+DocumentHolder PdfExportTest::exportAndParse(const OUString& rURL, const utl::MediaDescriptor& rDescriptor)
+{
+ // Import the bugdoc and export as PDF.
+ mxComponent = loadFromDesktop(rURL);
+ CPPUNIT_ASSERT(mxComponent.is());
+
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ xStorable->storeToURL(maTempFile.GetURL(), rDescriptor.getAsConstPropertyValueList());
+
+ // Parse the export result with pdfium.
+ SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
+ maMemory.WriteStream(aFile);
+ DocumentHolder pPdfDocument(
+ FPDF_LoadMemDocument(maMemory.GetData(), maMemory.GetSize(), /*password=*/nullptr));
+ CPPUNIT_ASSERT(pPdfDocument.get());
+ return pPdfDocument;
+}
+
+void PdfExportTest::setUp()
+{
+ test::BootstrapFixture::setUp();
+
+ mxDesktop.set(frame::Desktop::create(mxComponentContext));
+
+ mpPDFium = vcl::pdf::PDFiumLibrary::get();
+}
+
+void PdfExportTest::tearDown()
+{
+ if (mxComponent.is())
+ mxComponent->dispose();
+
+ test::BootstrapFixture::tearDown();
+}
+
+char const DATA_DIRECTORY[] = "/vcl/qa/cppunit/pdfexport/data/";
+
+void PdfExportTest::saveAsPDF(const OUString& rFile)
+{
+ // Import the bugdoc and export as PDF.
+ OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + rFile;
+ mxComponent = loadFromDesktop(aURL);
+ CPPUNIT_ASSERT(mxComponent.is());
+
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ utl::MediaDescriptor aMediaDescriptor;
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
+}
+
+void PdfExportTest::load(const OUString& rFile, vcl::filter::PDFDocument& rDocument)
+{
+ saveAsPDF(rFile);
+
+ // Parse the export result.
+ SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
+ CPPUNIT_ASSERT(rDocument.Read(aStream));
+}
+
+void PdfExportTest::testTdf106059()
+{
+ // Import the bugdoc and export as PDF.
+ OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf106059.odt";
+ mxComponent = loadFromDesktop(aURL);
+ CPPUNIT_ASSERT(mxComponent.is());
+
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ utl::MediaDescriptor aMediaDescriptor;
+ 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;
+ 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));
+
+ // 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");
+ CPPUNIT_ASSERT(pResources);
+ auto pXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
+ 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"));
+}
+
+void PdfExportTest::testTdf106693()
+{
+ vcl::filter::PDFDocument aDocument;
+ load("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");
+ CPPUNIT_ASSERT(pResources);
+ auto pXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
+ 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"));
+ CPPUNIT_ASSERT(pSubtype);
+ CPPUNIT_ASSERT_EQUAL(OString("Form"), pSubtype->GetValue());
+ // This failed: UseReferenceXObject was ignored and Ref was always created.
+ CPPUNIT_ASSERT(!pXObject->Lookup("Ref"));
+
+ // Assert that the form object refers to an inner form object, not a
+ // bitmap.
+ auto pInnerResources = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject->Lookup("Resources"));
+ CPPUNIT_ASSERT(pInnerResources);
+ auto pInnerXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pInnerResources->LookupElement("XObject"));
+ 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"));
+ CPPUNIT_ASSERT(pInnerSubtype);
+ // This failed: this was Image (bitmap), not Form (vector).
+ CPPUNIT_ASSERT_EQUAL(OString("Form"), pInnerSubtype->GetValue());
+}
+
+void PdfExportTest::testTdf105461()
+{
+ // Import the bugdoc and export as PDF.
+ OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf105461.odp";
+ mxComponent = loadFromDesktop(aURL);
+ CPPUNIT_ASSERT(mxComponent.is());
+
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ utl::MediaDescriptor aMediaDescriptor;
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
+
+ // Parse the export result with pdfium.
+ SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
+ SvMemoryStream aMemory;
+ aMemory.WriteStream(aFile);
+ DocumentHolder pPdfDocument(FPDF_LoadMemDocument(aMemory.GetData(), aMemory.GetSize(), /*password=*/nullptr));
+ CPPUNIT_ASSERT(pPdfDocument.get());
+
+ // The document has one page.
+ CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
+ PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
+ CPPUNIT_ASSERT(pPdfPage.get());
+
+ // Make sure there is a filled rectangle inside.
+ int nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
+ int nYellowPathCount = 0;
+ for (int i = 0; i < nPageObjectCount; ++i)
+ {
+ FPDF_PAGEOBJECT pPdfPageObject = FPDFPage_GetObject(pPdfPage.get(), i);
+ if (FPDFPageObj_GetType(pPdfPageObject) != FPDF_PAGEOBJ_PATH)
+ continue;
+
+ unsigned int nRed = 0, nGreen = 0, nBlue = 0, nAlpha = 0;
+ FPDFPageObj_GetFillColor(pPdfPageObject, &nRed, &nGreen, &nBlue, &nAlpha);
+ if (Color(nRed, nGreen, nBlue) == COL_YELLOW)
+ ++nYellowPathCount;
+ }
+
+ // This was 0, the page contained no yellow paths.
+ CPPUNIT_ASSERT_EQUAL(1, nYellowPathCount);
+}
+
+void 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.
+ OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf107868.odt";
+ mxComponent = loadFromDesktop(aURL);
+ CPPUNIT_ASSERT(mxComponent.is());
+
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ uno::Reference<view::XPrintable> xPrintable(mxComponent, uno::UNO_QUERY);
+ CPPUNIT_ASSERT(xPrintable.is());
+ uno::Sequence<beans::PropertyValue> aOptions(comphelper::InitPropertySequence(
+ {
+ {"FileName", uno::makeAny(maTempFile.GetURL())},
+ {"Wait", uno::makeAny(true)}
+ }));
+ xPrintable->print(aOptions);
+
+ // Parse the export result with pdfium.
+ SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
+ SvMemoryStream aMemory;
+ aMemory.WriteStream(aFile);
+ DocumentHolder pPdfDocument(FPDF_LoadMemDocument(aMemory.GetData(), aMemory.GetSize(), /*password=*/nullptr));
+ 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, FPDF_GetPageCount(pPdfDocument.get()));
+ PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
+ CPPUNIT_ASSERT(pPdfPage.get());
+
+ // Make sure there is no filled rectangle inside.
+ int nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
+ int nWhitePathCount = 0;
+ for (int i = 0; i < nPageObjectCount; ++i)
+ {
+ FPDF_PAGEOBJECT pPdfPageObject = FPDFPage_GetObject(pPdfPage.get(), i);
+ if (FPDFPageObj_GetType(pPdfPageObject) != FPDF_PAGEOBJ_PATH)
+ continue;
+
+ unsigned int nRed = 0, nGreen = 0, nBlue = 0, nAlpha = 0;
+ FPDFPageObj_GetFillColor(pPdfPageObject, &nRed, &nGreen, &nBlue, &nAlpha);
+ if (Color(nRed, nGreen, nBlue) == COL_WHITE)
+ ++nWhitePathCount;
+ }
+
+ // This was 4, the page contained 4 white paths at problematic positions.
+ CPPUNIT_ASSERT_EQUAL(0, nWhitePathCount);
+#endif
+}
+
+void PdfExportTest::testTdf105093()
+{
+ vcl::filter::PDFDocument aDocument;
+ load("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"));
+ 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(OString("Annot"), static_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"))->GetValue());
+
+ // Get the Action -> Rendition -> MediaClip -> FileSpec.
+ auto pAction = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pAnnot->Lookup("A"));
+ CPPUNIT_ASSERT(pAction);
+ auto pRendition = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pAction->LookupElement("R"));
+ CPPUNIT_ASSERT(pRendition);
+ auto pMediaClip = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pRendition->LookupElement("C"));
+ CPPUNIT_ASSERT(pMediaClip);
+ auto pFileSpec = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pMediaClip->LookupElement("D"));
+ 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"));
+}
+
+void PdfExportTest::testTdf106206()
+{
+ // Import the bugdoc and export as PDF.
+ OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf106206.odt";
+ mxComponent = loadFromDesktop(aURL);
+ CPPUNIT_ASSERT(mxComponent.is());
+
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ utl::MediaDescriptor aMediaDescriptor;
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ 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));
+
+ // 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");
+ 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");
+ 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");
+ it = std::search(pStart, pEnd, aInvalidImage.getStr(), aInvalidImage.getStr() + aInvalidImage.getLength());
+ // This failed, object #0 was referenced.
+ CPPUNIT_ASSERT(bool(it == pEnd));
+}
+
+void PdfExportTest::testTdf109143()
+{
+ // Import the bugdoc and export as PDF.
+ OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf109143.odt";
+ mxComponent = loadFromDesktop(aURL);
+ CPPUNIT_ASSERT(mxComponent.is());
+
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ utl::MediaDescriptor aMediaDescriptor;
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ 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));
+
+ // 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");
+ CPPUNIT_ASSERT(pResources);
+ auto pXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
+ 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"));
+ 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);
+}
+
+void PdfExportTest::testTdf106972()
+{
+ // Import the bugdoc and export as PDF.
+ OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf106972.odt";
+ mxComponent = loadFromDesktop(aURL);
+ CPPUNIT_ASSERT(mxComponent.is());
+
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ utl::MediaDescriptor aMediaDescriptor;
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ 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));
+
+ // 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");
+ CPPUNIT_ASSERT(pResources);
+ auto pXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
+ 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"));
+ CPPUNIT_ASSERT(pFormResources);
+ auto pImages = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pFormResources->LookupElement("XObject"));
+ 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"));
+ CPPUNIT_ASSERT(pImageResources);
+ // This failed: the PDF image had no Font resource.
+ CPPUNIT_ASSERT(pImageResources->LookupElement("Font"));
+}
+
+void PdfExportTest::testTdf106972Pdf17()
+{
+ // Import the bugdoc and export as PDF.
+ OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf106972-pdf17.odt";
+ mxComponent = loadFromDesktop(aURL);
+ CPPUNIT_ASSERT(mxComponent.is());
+
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ utl::MediaDescriptor aMediaDescriptor;
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ 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));
+
+ // 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");
+ CPPUNIT_ASSERT(pResources);
+ auto pXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
+ 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"));
+}
+
+void 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.
+ OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "softhyphen_pdf.odt";
+ mxComponent = loadFromDesktop(aURL);
+ CPPUNIT_ASSERT(mxComponent.is());
+
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ uno::Reference<view::XPrintable> xPrintable(mxComponent, uno::UNO_QUERY);
+ CPPUNIT_ASSERT(xPrintable.is());
+ uno::Sequence<beans::PropertyValue> aOptions(comphelper::InitPropertySequence(
+ {
+ {"FileName", uno::makeAny(maTempFile.GetURL())},
+ {"Wait", uno::makeAny(true)}
+ }));
+ xPrintable->print(aOptions);
+
+ // Parse the export result with pdfium.
+ SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
+ SvMemoryStream aMemory;
+ aMemory.WriteStream(aFile);
+ if (aFile.bad() || !aMemory.GetSize())
+ {
+ // Printing to PDF failed in a non-interesting way, e.g. CUPS is not
+ // running, there is no printer defined, etc.
+ return;
+ }
+ DocumentHolder pPdfDocument(FPDF_LoadMemDocument(aMemory.GetData(), aMemory.GetSize(), /*password=*/nullptr));
+ CPPUNIT_ASSERT(pPdfDocument);
+
+ // The document has one page.
+ CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
+ PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
+ CPPUNIT_ASSERT(pPdfPage.get());
+
+ // 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 = FPDFPage_CountObjects(pPdfPage.get());
+ for (int i = 0; i < nPageObjectCount; ++i)
+ {
+ FPDF_PAGEOBJECT pPdfPageObject = FPDFPage_GetObject(pPdfPage.get(), i);
+ CPPUNIT_ASSERT_EQUAL(FPDF_PAGEOBJ_TEXT, FPDFPageObj_GetType(pPdfPageObject));
+ haveText = true;
+ double const size(FPDFTextObj_GetFontSize(pPdfPageObject));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(11.05, size, 1E-06);
+ }
+
+ CPPUNIT_ASSERT(haveText);
+#endif
+}
+
+void PdfExportTest::testTdf107013()
+{
+ vcl::filter::PDFDocument aDocument;
+ load("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");
+ CPPUNIT_ASSERT(pResources);
+ auto pXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
+ 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);
+}
+
+void PdfExportTest::testTdf107018()
+{
+ vcl::filter::PDFDocument aDocument;
+ load("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");
+ CPPUNIT_ASSERT(pResources);
+ auto pXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
+ 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"));
+ CPPUNIT_ASSERT(pXObjectResources);
+ auto pXObjectForms = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObjectResources->LookupElement("XObject"));
+ 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"));
+ CPPUNIT_ASSERT(pFormResources);
+ auto pFonts = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pFormResources->LookupElement("Font"));
+ CPPUNIT_ASSERT(pFonts);
+ auto pF1Ref = dynamic_cast<vcl::filter::PDFReferenceElement*>(pFonts->LookupElement("F1"));
+ 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"));
+ CPPUNIT_ASSERT(pFontFoo);
+ auto pBar = dynamic_cast<vcl::filter::PDFReferenceElement*>(pFontFoo->LookupElement("Bar"));
+ CPPUNIT_ASSERT(pBar);
+ vcl::filter::PDFObjectElement* pObject = pBar->LookupObject();
+ CPPUNIT_ASSERT(pObject);
+ auto pName = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"));
+ 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(OString("Pages"), pName->GetValue());
+}
+
+void PdfExportTest::testTdf107089()
+{
+ vcl::filter::PDFDocument aDocument;
+ load("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");
+ CPPUNIT_ASSERT(pResources);
+ auto pXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
+ 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"));
+ CPPUNIT_ASSERT(pXObjectResources);
+ auto pXObjectForms = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObjectResources->LookupElement("XObject"));
+ 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");
+ 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);
+}
+
+void PdfExportTest::testTdf99680()
+{
+ vcl::filter::PDFDocument aDocument;
+ load("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");
+ 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);
+}
+
+void PdfExportTest::testTdf99680_2()
+{
+ vcl::filter::PDFDocument aDocument;
+ load("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");
+ 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);
+ }
+}
+
+void PdfExportTest::testTdf108963()
+{
+ // Import the bugdoc and export as PDF.
+ OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf108963.odp";
+ mxComponent = loadFromDesktop(aURL);
+ CPPUNIT_ASSERT(mxComponent.is());
+
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ utl::MediaDescriptor aMediaDescriptor;
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
+
+ // Parse the export result with pdfium.
+ SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
+ SvMemoryStream aMemory;
+ aMemory.WriteStream(aFile);
+ DocumentHolder pPdfDocument(FPDF_LoadMemDocument(aMemory.GetData(), aMemory.GetSize(), /*password=*/nullptr));
+ CPPUNIT_ASSERT(pPdfDocument.get());
+
+ // The document has one page.
+ CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
+ PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
+ CPPUNIT_ASSERT(pPdfPage.get());
+
+ // FIXME: strangely this fails on some Win systems after a pdfium update, expected: 793.7; actual: 793
+#if !defined _WIN32
+ // 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 = FPDF_GetPageWidth(pPdfPage.get());
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(793.7, aWidth, 0.01);
+ const double aHeight = FPDF_GetPageHeight(pPdfPage.get());
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(446.46, aHeight, 0.01);
+
+ // Make sure there is a filled rectangle inside.
+ int nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
+ int nYellowPathCount = 0;
+ for (int i = 0; i < nPageObjectCount; ++i)
+ {
+ FPDF_PAGEOBJECT pPdfPageObject = FPDFPage_GetObject(pPdfPage.get(), i);
+ if (FPDFPageObj_GetType(pPdfPageObject) != FPDF_PAGEOBJ_PATH)
+ continue;
+
+ unsigned int nRed = 0, nGreen = 0, nBlue = 0, nAlpha = 0;
+ FPDFPageObj_GetFillColor(pPdfPageObject, &nRed, &nGreen, &nBlue, &nAlpha);
+ if (Color(nRed, nGreen, nBlue) == COL_YELLOW)
+ {
+ ++nYellowPathCount;
+ // The path described a yellow rectangle, but it was not rotated.
+ int nSegments = FPDFPath_CountSegments(pPdfPageObject);
+ CPPUNIT_ASSERT_EQUAL(5, nSegments);
+ FPDF_PATHSEGMENT pSegment = FPDFPath_GetPathSegment(pPdfPageObject, 0);
+ CPPUNIT_ASSERT_EQUAL(FPDF_SEGMENT_MOVETO, FPDFPathSegment_GetType(pSegment));
+ float fX = 0;
+ float fY = 0;
+ FPDFPathSegment_GetPoint(pSegment, &fX, &fY);
+ CPPUNIT_ASSERT_EQUAL(245395, static_cast<int>(round(fX * 1000)));
+ CPPUNIT_ASSERT_EQUAL(244261, static_cast<int>(round(fY * 1000)));
+ CPPUNIT_ASSERT(!FPDFPathSegment_GetClose(pSegment));
+
+ pSegment = FPDFPath_GetPathSegment(pPdfPageObject, 1);
+ CPPUNIT_ASSERT_EQUAL(FPDF_SEGMENT_LINETO, FPDFPathSegment_GetType(pSegment));
+ FPDFPathSegment_GetPoint(pSegment, &fX, &fY);
+ CPPUNIT_ASSERT_EQUAL(275102, static_cast<int>(round(fX * 1000)));
+ CPPUNIT_ASSERT_EQUAL(267618, static_cast<int>(round(fY * 1000)));
+ CPPUNIT_ASSERT(!FPDFPathSegment_GetClose(pSegment));
+
+ pSegment = FPDFPath_GetPathSegment(pPdfPageObject, 2);
+ CPPUNIT_ASSERT_EQUAL(FPDF_SEGMENT_LINETO, FPDFPathSegment_GetType(pSegment));
+ FPDFPathSegment_GetPoint(pSegment, &fX, &fY);
+ CPPUNIT_ASSERT_EQUAL(287518, static_cast<int>(round(fX * 1000)));
+ CPPUNIT_ASSERT_EQUAL(251829, static_cast<int>(round(fY * 1000)));
+ CPPUNIT_ASSERT(!FPDFPathSegment_GetClose(pSegment));
+
+ pSegment = FPDFPath_GetPathSegment(pPdfPageObject, 3);
+ CPPUNIT_ASSERT_EQUAL(FPDF_SEGMENT_LINETO, FPDFPathSegment_GetType(pSegment));
+ FPDFPathSegment_GetPoint(pSegment, &fX, &fY);
+ CPPUNIT_ASSERT_EQUAL(257839, static_cast<int>(round(fX * 1000)));
+ CPPUNIT_ASSERT_EQUAL(228472, static_cast<int>(round(fY * 1000)));
+ CPPUNIT_ASSERT(!FPDFPathSegment_GetClose(pSegment));
+
+ pSegment = FPDFPath_GetPathSegment(pPdfPageObject, 4);
+ CPPUNIT_ASSERT_EQUAL(FPDF_SEGMENT_LINETO, FPDFPathSegment_GetType(pSegment));
+ FPDFPathSegment_GetPoint(pSegment, &fX, &fY);
+ CPPUNIT_ASSERT_EQUAL(245395, static_cast<int>(round(fX * 1000)));
+ CPPUNIT_ASSERT_EQUAL(244261, static_cast<int>(round(fY * 1000)));
+ CPPUNIT_ASSERT(FPDFPathSegment_GetClose(pSegment));
+ }
+ }
+
+ CPPUNIT_ASSERT_EQUAL(1, nYellowPathCount);
+#endif
+}
+
+void PdfExportTest::testTdf118244_radioButtonGroup()
+{
+ vcl::filter::PDFDocument aDocument;
+ load("tdf118244_radioButtonGroup.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());
+
+ // There are eight radio buttons.
+ auto pAnnots = dynamic_cast<vcl::filter::PDFArrayElement*>(aPages[0]->Lookup("Annots"));
+ 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"));
+ if ( pType && pType->GetValue() == "Btn" )
+ {
+ auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject->Lookup("Kids"));
+ 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);
+}
+
+// This requires Carlito font, if it is missing the test will most likely
+// fail.
+void PdfExportTest::testTdf115117_1()
+{
+#if HAVE_MORE_FONTS
+ vcl::filter::PDFDocument aDocument;
+ load("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"));
+ if (pType && pType->GetValue() == "Font")
+ {
+ auto pToUnicodeRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(pObject->Lookup("ToUnicode"));
+ 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");
+ 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
+}
+
+// This requires DejaVu Sans font, if it is missing the test will most likely
+// fail.
+void PdfExportTest::testTdf115117_2()
+{
+#if HAVE_MORE_FONTS
+ // See the comments in testTdf115117_1() for explanation.
+
+ vcl::filter::PDFDocument aDocument;
+ load("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"));
+ if (pType && pType->GetValue() == "Font")
+ {
+ auto pToUnicodeRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(pObject->Lookup("ToUnicode"));
+ 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");
+ 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
+}
+
+void PdfExportTest::testTdf115117_1a()
+{
+#if HAVE_MORE_FONTS
+ // Import the bugdoc and export as PDF.
+ OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf115117-1.odt";
+ mxComponent = loadFromDesktop(aURL);
+ CPPUNIT_ASSERT(mxComponent.is());
+
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ utl::MediaDescriptor aMediaDescriptor;
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
+
+ // Parse the export result with pdfium.
+ SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
+ SvMemoryStream aMemory;
+ aMemory.WriteStream(aFile);
+ DocumentHolder pPdfDocument(FPDF_LoadMemDocument(aMemory.GetData(), aMemory.GetSize(), /*password=*/nullptr));
+ CPPUNIT_ASSERT(pPdfDocument.get());
+
+ // The document has one page.
+ CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
+ PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
+ CPPUNIT_ASSERT(pPdfPage.get());
+
+ auto pPdfTextPage = FPDFText_LoadPage(pPdfPage.get());
+ 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 = FPDFText_CountChars(pPdfTextPage);
+ CPPUNIT_ASSERT_EQUAL(44, nChars);
+
+ OUString aExpectedText = "ti ti test ti\r\nti test fi fl ffi ffl test fi";
+ std::vector<sal_uInt32> aChars(nChars);
+ for (int i = 0; i < nChars; i++)
+ aChars[i] = FPDFText_GetUnicode(pPdfTextPage, i);
+ OUString aActualText(aChars.data(), aChars.size());
+ CPPUNIT_ASSERT_EQUAL(aExpectedText, aActualText);
+
+ FPDFText_ClosePage(pPdfTextPage);
+#endif
+}
+
+void PdfExportTest::testTdf115117_2a()
+{
+#if HAVE_MORE_FONTS
+ // See the comments in testTdf115117_1a() for explanation.
+
+ // Import the bugdoc and export as PDF.
+ OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf115117-2.odt";
+ mxComponent = loadFromDesktop(aURL);
+ CPPUNIT_ASSERT(mxComponent.is());
+
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ utl::MediaDescriptor aMediaDescriptor;
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
+
+ // Parse the export result with pdfium.
+ SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
+ SvMemoryStream aMemory;
+ aMemory.WriteStream(aFile);
+ DocumentHolder pPdfDocument(FPDF_LoadMemDocument(aMemory.GetData(), aMemory.GetSize(), /*password=*/nullptr));
+ CPPUNIT_ASSERT(pPdfDocument.get());
+
+ // The document has one page.
+ CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
+ PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
+ CPPUNIT_ASSERT(pPdfPage.get());
+
+ auto pPdfTextPage = FPDFText_LoadPage(pPdfPage.get());
+ CPPUNIT_ASSERT(pPdfTextPage);
+
+ int nChars = FPDFText_CountChars(pPdfTextPage);
+ CPPUNIT_ASSERT_EQUAL(13, nChars);
+
+ OUString aExpectedText = u"\u0627\u0644 \u0628\u0627\u0644 \u0648\u0642\u0641 \u0627\u0644";
+ std::vector<sal_uInt32> aChars(nChars);
+ for (int i = 0; i < nChars; i++)
+ aChars[i] = FPDFText_GetUnicode(pPdfTextPage, i);
+ OUString aActualText(aChars.data(), aChars.size());
+ CPPUNIT_ASSERT_EQUAL(aExpectedText, aActualText);
+
+ FPDFText_ClosePage(pPdfTextPage);
+#endif
+}
+
+void PdfExportTest::testTdf66597_1()
+{
+#if HAVE_MORE_FONTS
+ // This requires Amiri font, if it is missing the test will fail.
+ vcl::filter::PDFDocument aDocument;
+ load("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"));
+ if (pType && pType->GetValue() == "Font")
+ {
+ auto pName = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("BaseFont"));
+ auto aName = pName->GetValue().copy(7); // skip the subset id
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected font name", OString("Amiri-Regular"), aName);
+
+ auto pToUnicodeRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(pObject->Lookup("ToUnicode"));
+ 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, <0020> is code point.
+ // The document has three characters <space><nbspace><space>, but the font
+ // reuses the same glyph for space and nbspace so we should have a single
+ // CMAP entry for the space, and nbspace will be handled with ActualText
+ // (tested above).
+ std::string aCmap("1 beginbfchar\n"
+ "<01> <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");
+ 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<FEFF00A0>>>";
+ nPos = aData.find(aActualText);
+ CPPUNIT_ASSERT_MESSAGE("ActualText not found!", nPos != std::string::npos);
+ }
+#endif
+}
+
+// This requires Reem Kufi font, if it is missing the test will fail.
+void PdfExportTest::testTdf66597_2()
+{
+#if HAVE_MORE_FONTS
+ vcl::filter::PDFDocument aDocument;
+ load("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"));
+ if (pType && pType->GetValue() == "Font")
+ {
+ auto pName = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("BaseFont"));
+ auto aName = pName->GetValue().copy(7); // skip the subset id
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected font name", OString("ReemKufi-Regular"), aName);
+
+ auto pToUnicodeRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(pObject->Lookup("ToUnicode"));
+ 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"
+ "<09> <0628>\n"
+ "<0B> <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");
+ 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
+}
+
+// This requires Gentium Basic font, if it is missing the test will fail.
+void PdfExportTest::testTdf66597_3()
+{
+#if HAVE_MORE_FONTS
+ vcl::filter::PDFDocument aDocument;
+ load("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"));
+ if (pType && pType->GetValue() == "Font")
+ {
+ auto pName = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("BaseFont"));
+ auto aName = pName->GetValue().copy(7); // skip the subset id
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected font name", OString("GentiumBasic"), aName);
+
+ auto pToUnicodeRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(pObject->Lookup("ToUnicode"));
+ 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");
+ 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
+}
+
+void PdfExportTest::testTdf105954()
+{
+ // Import the bugdoc and export as PDF.
+ OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf105954.odt";
+ mxComponent = loadFromDesktop(aURL);
+ CPPUNIT_ASSERT(mxComponent.is());
+
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ utl::MediaDescriptor aMediaDescriptor;
+ 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;
+ xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
+
+ // Parse the export result with pdfium.
+ SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
+ SvMemoryStream aMemory;
+ aMemory.WriteStream(aFile);
+ DocumentHolder pPdfDocument(
+ FPDF_LoadMemDocument(aMemory.GetData(), aMemory.GetSize(), /*password=*/nullptr));
+ CPPUNIT_ASSERT(pPdfDocument.get());
+
+ // The document has one page.
+ CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
+ PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
+ CPPUNIT_ASSERT(pPdfPage.get());
+
+ // There is a single image on the page.
+ int nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
+ CPPUNIT_ASSERT_EQUAL(1, nPageObjectCount);
+
+ // Check width of the image.
+ FPDF_PAGEOBJECT pPageObject = FPDFPage_GetObject(pPdfPage.get(), /*index=*/0);
+ FPDF_IMAGEOBJ_METADATA aMeta;
+ CPPUNIT_ASSERT(FPDFImageObj_GetImageMetadata(pPageObject, pPdfPage.get(), &aMeta));
+ // 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<unsigned int>(250), aMeta.width);
+}
+
+void PdfExportTest::testTdf128630()
+{
+ // Import the bugdoc and export as PDF.
+ OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf128630.odp";
+ utl::MediaDescriptor aMediaDescriptor;
+ aMediaDescriptor["FilterName"] <<= OUString("impress_pdf_Export");
+ DocumentHolder pPdfDocument = exportAndParse(aURL, aMediaDescriptor);
+
+ // The document has one page.
+ CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
+
+ // Assert the aspect ratio of the only bitmap on the page.
+ PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
+ CPPUNIT_ASSERT(pPdfPage.get());
+ int nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
+ for (int i = 0; i < nPageObjectCount; ++i)
+ {
+ FPDF_PAGEOBJECT pPageObject = FPDFPage_GetObject(pPdfPage.get(), i);
+ if (FPDFPageObj_GetType(pPageObject) != FPDF_PAGEOBJ_IMAGE)
+ continue;
+
+ FPDF_BITMAP pBitmap = FPDFImageObj_GetBitmap(pPageObject);
+ CPPUNIT_ASSERT(pBitmap);
+ int nWidth = FPDFBitmap_GetWidth(pBitmap);
+ int nHeight = FPDFBitmap_GetHeight(pBitmap);
+ FPDFBitmap_Destroy(pBitmap);
+ // Without the accompanying fix in place, this test would have failed with:
+ // assertion failed
+ // - Expression: nWidth != nHeight
+ // i.e. the bitmap lost its custom aspect ratio during export.
+ CPPUNIT_ASSERT(nWidth != nHeight);
+ }
+}
+
+void PdfExportTest::testTdf106702()
+{
+ // Import the bugdoc and export as PDF.
+ OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf106702.odt";
+ utl::MediaDescriptor aMediaDescriptor;
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ auto pPdfDocument = exportAndParse(aURL, aMediaDescriptor);
+
+ // The document has two pages.
+ CPPUNIT_ASSERT_EQUAL(2, FPDF_GetPageCount(pPdfDocument.get()));
+
+ // First page already has the correct image position.
+ PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
+ CPPUNIT_ASSERT(pPdfPage.get());
+ int nExpected = 0;
+ int nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
+ for (int i = 0; i < nPageObjectCount; ++i)
+ {
+ FPDF_PAGEOBJECT pPageObject = FPDFPage_GetObject(pPdfPage.get(), i);
+ if (FPDFPageObj_GetType(pPageObject) != FPDF_PAGEOBJ_IMAGE)
+ continue;
+
+ float fLeft = 0, fBottom = 0, fRight = 0, fTop = 0;
+ FPDFPageObj_GetBounds(pPageObject, &fLeft, &fBottom, &fRight, &fTop);
+ nExpected = fTop;
+ break;
+ }
+
+ // Second page had an incorrect image position.
+ pPdfPage.reset(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/1));
+ CPPUNIT_ASSERT(pPdfPage.get());
+ int nActual = 0;
+ nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
+ for (int i = 0; i < nPageObjectCount; ++i)
+ {
+ FPDF_PAGEOBJECT pPageObject = FPDFPage_GetObject(pPdfPage.get(), i);
+ if (FPDFPageObj_GetType(pPageObject) != FPDF_PAGEOBJ_IMAGE)
+ continue;
+
+ float fLeft = 0, fBottom = 0, fRight = 0, fTop = 0;
+ FPDFPageObj_GetBounds(pPageObject, &fLeft, &fBottom, &fRight, &fTop);
+ nActual = fTop;
+ break;
+ }
+
+ // This failed, vertical pos is 818 points, was 1674 (outside visible page
+ // bounds).
+ CPPUNIT_ASSERT_EQUAL(nExpected, nActual);
+}
+
+void PdfExportTest::testTdf113143()
+{
+ OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf113143.odp";
+ utl::MediaDescriptor aMediaDescriptor;
+ 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::makeAny(static_cast<sal_Int32>(16)) },
+ }));
+ aMediaDescriptor["FilterData"] <<= aFilterData;
+ auto pPdfDocument = exportAndParse(aURL, aMediaDescriptor);
+
+ // The document has two pages.
+ CPPUNIT_ASSERT_EQUAL(2, FPDF_GetPageCount(pPdfDocument.get()));
+
+ // First has the original (larger) image.
+ PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
+ CPPUNIT_ASSERT(pPdfPage.get());
+ int nLarger = 0;
+ int nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
+ for (int i = 0; i < nPageObjectCount; ++i)
+ {
+ FPDF_PAGEOBJECT pPageObject = FPDFPage_GetObject(pPdfPage.get(), i);
+ if (FPDFPageObj_GetType(pPageObject) != FPDF_PAGEOBJ_IMAGE)
+ continue;
+
+ float fLeft = 0, fBottom = 0, fRight = 0, fTop = 0;
+ FPDFPageObj_GetBounds(pPageObject, &fLeft, &fBottom, &fRight, &fTop);
+ nLarger = fRight - fLeft;
+ break;
+ }
+
+ // Second page has the scaled (smaller) image.
+ pPdfPage.reset(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/1));
+ CPPUNIT_ASSERT(pPdfPage.get());
+ int nSmaller = 0;
+ nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
+ for (int i = 0; i < nPageObjectCount; ++i)
+ {
+ FPDF_PAGEOBJECT pPageObject = FPDFPage_GetObject(pPdfPage.get(), i);
+ if (FPDFPageObj_GetType(pPageObject) != FPDF_PAGEOBJ_IMAGE)
+ continue;
+
+ float fLeft = 0, fBottom = 0, fRight = 0, fTop = 0;
+ FPDFPageObj_GetBounds(pPageObject, &fLeft, &fBottom, &fRight, &fTop);
+ nSmaller = fRight - fLeft;
+ 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");
+ OString aHeader(read_uInt8s_ToOString(maMemory, aExpectedHeader.getLength()));
+ CPPUNIT_ASSERT_EQUAL(aExpectedHeader, aHeader);
+}
+
+void PdfExportTest::testForcePoint71()
+{
+ // I just care it doesn't crash
+ saveAsPDF("forcepoint71.key");
+}
+
+void PdfExportTest::testTdf115262()
+{
+ OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf115262.ods";
+ utl::MediaDescriptor aMediaDescriptor;
+ aMediaDescriptor["FilterName"] <<= OUString("calc_pdf_Export");
+ auto pPdfDocument = exportAndParse(aURL, aMediaDescriptor);
+ CPPUNIT_ASSERT_EQUAL(8, FPDF_GetPageCount(pPdfDocument.get()));
+
+ // Get the 6th page.
+ PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/5));
+ CPPUNIT_ASSERT(pPdfPage.get());
+
+ // Look up the position of the first image and the 400th row.
+ FPDF_TEXTPAGE pTextPage = FPDFText_LoadPage(pPdfPage.get());
+ int nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
+ int nFirstImageTop = 0;
+ int nRowTop = 0;
+ for (int i = 0; i < nPageObjectCount; ++i)
+ {
+ FPDF_PAGEOBJECT pPageObject = FPDFPage_GetObject(pPdfPage.get(), i);
+ float fLeft = 0, fBottom = 0, fRight = 0, fTop = 0;
+ FPDFPageObj_GetBounds(pPageObject, &fLeft, &fBottom, &fRight, &fTop);
+
+ if (FPDFPageObj_GetType(pPageObject) == FPDF_PAGEOBJ_IMAGE)
+ {
+ nFirstImageTop = fTop;
+ }
+ else if (FPDFPageObj_GetType(pPageObject) == FPDF_PAGEOBJ_TEXT)
+ {
+ unsigned long nTextSize = FPDFTextObj_GetText(pPageObject, pTextPage, nullptr, 0);
+ std::vector<sal_Unicode> aText(nTextSize);
+ FPDFTextObj_GetText(pPageObject, pTextPage, aText.data(), nTextSize);
+ OUString sText(aText.data(), nTextSize / 2 - 1);
+ 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);
+ FPDFText_ClosePage(pTextPage);
+}
+
+void PdfExportTest::testTdf121962()
+{
+ OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf121962.odt";
+ utl::MediaDescriptor aMediaDescriptor;
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ auto pPdfDocument = exportAndParse(aURL, aMediaDescriptor);
+ CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
+
+ // Get the first page
+ PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
+ CPPUNIT_ASSERT(pPdfPage.get());
+ FPDF_TEXTPAGE pTextPage = FPDFText_LoadPage(pPdfPage.get());
+
+ // Make sure the table sum is displayed as "0", not faulty expression.
+ int nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
+ for (int i = 0; i < nPageObjectCount; ++i)
+ {
+ FPDF_PAGEOBJECT pPageObject = FPDFPage_GetObject(pPdfPage.get(), i);
+ if (FPDFPageObj_GetType(pPageObject) != FPDF_PAGEOBJ_TEXT)
+ continue;
+ unsigned long nTextSize = FPDFTextObj_GetText(pPageObject, pTextPage, nullptr, 0);
+ std::vector<sal_Unicode> aText(nTextSize);
+ FPDFTextObj_GetText(pPageObject, pTextPage, aText.data(), nTextSize);
+ OUString sText(aText.data(), nTextSize / 2 - 1);
+ CPPUNIT_ASSERT(sText != "** Expression is faulty **");
+ }
+
+ FPDFText_ClosePage(pTextPage);
+}
+
+void PdfExportTest::testTdf115967()
+{
+ OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf115967.odt";
+ utl::MediaDescriptor aMediaDescriptor;
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ auto pPdfDocument = exportAndParse(aURL, aMediaDescriptor);
+ CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
+
+ // Get the first page
+ PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
+ CPPUNIT_ASSERT(pPdfPage.get());
+ FPDF_TEXTPAGE pTextPage = FPDFText_LoadPage(pPdfPage.get());
+
+ // Make sure the elements inside a formula in a RTL document are exported
+ // LTR ( m=750abc ) and not RTL ( m=057cba )
+ int nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
+ OUString sText;
+ for (int i = 0; i < nPageObjectCount; ++i)
+ {
+ FPDF_PAGEOBJECT pPageObject = FPDFPage_GetObject(pPdfPage.get(), i);
+ if (FPDFPageObj_GetType(pPageObject) != FPDF_PAGEOBJ_TEXT)
+ continue;
+ unsigned long nTextSize = FPDFTextObj_GetText(pPageObject, pTextPage, nullptr, 2);
+ std::vector<sal_Unicode> aText(nTextSize);
+ FPDFTextObj_GetText(pPageObject, pTextPage, aText.data(), nTextSize);
+ OUString sChar(aText.data(), nTextSize / 2 - 1);
+ sText += sChar.trim();
+ }
+ CPPUNIT_ASSERT_EQUAL(OUString("m=750abc"), sText);
+
+ FPDFText_ClosePage(pTextPage);
+}
+
+void PdfExportTest::testTdf121615()
+{
+ vcl::filter::PDFDocument aDocument;
+ load("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");
+ CPPUNIT_ASSERT(pResources);
+ auto pXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
+ 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, OUString( "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( 200L, aBitmap.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL( 300L, aBitmap.GetSizePixel().Height());
+ CPPUNIT_ASSERT_EQUAL( 8, int(aBitmap.GetBitCount()));
+ // 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 ));
+}
+
+void PdfExportTest::testTocLink()
+{
+ // Load the Writer document.
+ OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "toc-link.fodt";
+ mxComponent = loadFromDesktop(aURL);
+ CPPUNIT_ASSERT(mxComponent.is());
+
+ // 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.
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ utl::MediaDescriptor aMediaDescriptor;
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
+
+ SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
+ maMemory.WriteStream(aFile);
+ DocumentHolder pPdfDocument(
+ FPDF_LoadMemDocument(maMemory.GetData(), maMemory.GetSize(), /*password=*/nullptr));
+ CPPUNIT_ASSERT(pPdfDocument.get());
+ CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
+
+ PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
+ CPPUNIT_ASSERT(pPdfPage.get());
+
+ // Ensure there is a link on the first page (in the ToC).
+ int nStartPos = 0;
+ FPDF_LINK pLinkAnnot = nullptr;
+ // Without the accompanying fix in place, this test would have failed, as FPDFLink_Enumerate()
+ // returned false, as the page contained no links.
+ CPPUNIT_ASSERT(FPDFLink_Enumerate(pPdfPage.get(), &nStartPos, &pLinkAnnot));
+}
+
+void PdfExportTest::testReduceSmallImage()
+{
+ // Load the Writer document.
+ OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "reduce-small-image.fodt";
+ mxComponent = loadFromDesktop(aURL);
+
+ // Save as PDF.
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ utl::MediaDescriptor aMediaDescriptor;
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
+
+ // Parse the PDF: get the image.
+ SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
+ maMemory.WriteStream(aFile);
+ DocumentHolder pPdfDocument(
+ FPDF_LoadMemDocument(maMemory.GetData(), maMemory.GetSize(), /*password=*/nullptr));
+ CPPUNIT_ASSERT(pPdfDocument.get());
+ CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
+ PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
+ CPPUNIT_ASSERT(pPdfPage.get());
+ CPPUNIT_ASSERT_EQUAL(1, FPDFPage_CountObjects(pPdfPage.get()));
+ FPDF_PAGEOBJECT pPageObject = FPDFPage_GetObject(pPdfPage.get(), 0);
+ CPPUNIT_ASSERT_EQUAL(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(pPageObject));
+
+ // Make sure we don't scale down a tiny bitmap.
+ FPDF_BITMAP pBitmap = FPDFImageObj_GetBitmap(pPageObject);
+ CPPUNIT_ASSERT(pBitmap);
+ int nWidth = FPDFBitmap_GetWidth(pBitmap);
+ int nHeight = FPDFBitmap_GetHeight(pBitmap);
+ FPDFBitmap_Destroy(pBitmap);
+ // 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);
+}
+
+void PdfExportTest::testReduceImage()
+{
+ // Load the Writer document.
+ OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "reduce-image.fodt";
+ mxComponent = loadFromDesktop(aURL);
+
+ // 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.
+ SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
+ maMemory.WriteStream(aFile);
+ DocumentHolder pPdfDocument(
+ FPDF_LoadMemDocument(maMemory.GetData(), maMemory.GetSize(), /*password=*/nullptr));
+ CPPUNIT_ASSERT(pPdfDocument.get());
+ CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
+ PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
+ CPPUNIT_ASSERT(pPdfPage.get());
+ CPPUNIT_ASSERT_EQUAL(1, FPDFPage_CountObjects(pPdfPage.get()));
+ FPDF_PAGEOBJECT pPageObject = FPDFPage_GetObject(pPdfPage.get(), 0);
+ CPPUNIT_ASSERT_EQUAL(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(pPageObject));
+
+ // Make sure we don't scale down a bitmap.
+ FPDF_BITMAP pBitmap = FPDFImageObj_GetBitmap(pPageObject);
+ CPPUNIT_ASSERT(pBitmap);
+ int nWidth = FPDFBitmap_GetWidth(pBitmap);
+ int nHeight = FPDFBitmap_GetHeight(pBitmap);
+ FPDFBitmap_Destroy(pBitmap);
+ // 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);
+}
+
+bool HasLinksOnPage(PageHolder& pPdfPage)
+{
+ int nStartPos = 0;
+ FPDF_LINK pLinkAnnot = nullptr;
+ return FPDFLink_Enumerate(pPdfPage.get(), &nStartPos, &pLinkAnnot);
+}
+
+void PdfExportTest::testLinkWrongPage()
+{
+ // Import the bugdoc and export as PDF.
+ OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "link-wrong-page.odp";
+ utl::MediaDescriptor aMediaDescriptor;
+ aMediaDescriptor["FilterName"] <<= OUString("impress_pdf_Export");
+ DocumentHolder pPdfDocument = exportAndParse(aURL, aMediaDescriptor);
+
+ // The document has 2 pages.
+ CPPUNIT_ASSERT_EQUAL(2, FPDF_GetPageCount(pPdfDocument.get()));
+
+ // First page should have 1 link (2nd slide, 1st was hidden).
+ PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
+ CPPUNIT_ASSERT(pPdfPage.get());
+
+ // 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(HasLinksOnPage(pPdfPage));
+
+ // Second page should have no links (3rd slide).
+ PageHolder pPdfPage2(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/1));
+ CPPUNIT_ASSERT(pPdfPage2.get());
+ CPPUNIT_ASSERT(!HasLinksOnPage(pPdfPage2));
+}
+
+void PdfExportTest::testLargePage()
+{
+ // Import the bugdoc and export as PDF.
+ OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "6m-wide.odg";
+ utl::MediaDescriptor aMediaDescriptor;
+ aMediaDescriptor["FilterName"] <<= OUString("draw_pdf_Export");
+ DocumentHolder pPdfDocument = exportAndParse(aURL, aMediaDescriptor);
+
+ // The document has 1 page.
+ CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
+
+ // Check the value (not the unit) of the page size.
+ FS_SIZEF aSize;
+ FPDF_GetPageSizeByIndexF(pPdfDocument.get(), 0, &aSize);
+ // 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, static_cast<double>(aSize.width), 0.01);
+}
+
+void PdfExportTest::testPdfImageResourceInlineXObjectRef()
+{
+ // Create an empty document.
+ mxComponent = loadFromDesktop("private:factory/swriter");
+ CPPUNIT_ASSERT(mxComponent.is());
+ 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
+ = m_directories.getURLFromSrc(DATA_DIRECTORY) + "pdf-image-resource-inline-xobject-ref.pdf";
+ xGraphicObject->setPropertyValue("GraphicURL", uno::makeAny(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);
+ utl::MediaDescriptor aMediaDescriptor;
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
+
+ // Parse the export result.
+ SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
+ maMemory.WriteStream(aFile);
+ DocumentHolder pPdfDocument(
+ FPDF_LoadMemDocument(maMemory.GetData(), maMemory.GetSize(), /*password=*/nullptr));
+ CPPUNIT_ASSERT(pPdfDocument.get());
+ CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
+
+ // Make sure that the page -> form -> form has a child image.
+ PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
+ CPPUNIT_ASSERT(pPdfPage.get());
+ CPPUNIT_ASSERT_EQUAL(1, FPDFPage_CountObjects(pPdfPage.get()));
+ FPDF_PAGEOBJECT pPageObject = FPDFPage_GetObject(pPdfPage.get(), 0);
+ CPPUNIT_ASSERT_EQUAL(FPDF_PAGEOBJ_FORM, FPDFPageObj_GetType(pPageObject));
+ // 2: white background and the actual object.
+ CPPUNIT_ASSERT_EQUAL(2, FPDFFormObj_CountObjects(pPageObject));
+ FPDF_PAGEOBJECT pFormObject = FPDFFormObj_GetObject(pPageObject, 1);
+ CPPUNIT_ASSERT_EQUAL(FPDF_PAGEOBJ_FORM, FPDFPageObj_GetType(pFormObject));
+ // 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, FPDFFormObj_CountObjects(pFormObject));
+
+ // Check if the inner form object (original page object in the pdf image) has the correct
+ // rotation.
+ FPDF_PAGEOBJECT pInnerFormObject = FPDFFormObj_GetObject(pFormObject, 0);
+ CPPUNIT_ASSERT_EQUAL(FPDF_PAGEOBJ_FORM, FPDFPageObj_GetType(pInnerFormObject));
+ CPPUNIT_ASSERT_EQUAL(1, FPDFFormObj_CountObjects(pInnerFormObject));
+ FPDF_PAGEOBJECT pImage = FPDFFormObj_GetObject(pInnerFormObject, 0);
+ CPPUNIT_ASSERT_EQUAL(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(pImage));
+ FS_MATRIX aMatrix;
+ FPDFFormObj_GetMatrix(pInnerFormObject, &aMatrix);
+ basegfx::B2DHomMatrix aMat{ aMatrix.a, aMatrix.c, aMatrix.e, aMatrix.b, aMatrix.d, aMatrix.f };
+ 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);
+}
+
+void PdfExportTest::testDefaultVersion()
+{
+ // Create an empty document.
+ mxComponent = loadFromDesktop("private:factory/swriter");
+ CPPUNIT_ASSERT(mxComponent.is());
+
+ // Save as PDF.
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ utl::MediaDescriptor aMediaDescriptor;
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
+
+ // Parse the export result.
+ SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
+ maMemory.WriteStream(aFile);
+ DocumentHolder pPdfDocument(
+ FPDF_LoadMemDocument(maMemory.GetData(), maMemory.GetSize(), /*password=*/nullptr));
+ CPPUNIT_ASSERT(pPdfDocument.get());
+ int nFileVersion = 0;
+ FPDF_GetFileVersion(pPdfDocument.get(), &nFileVersion);
+ CPPUNIT_ASSERT_EQUAL(16, nFileVersion);
+}
+
+void PdfExportTest::testVersion15()
+{
+ // Create an empty document.
+ mxComponent = loadFromDesktop("private:factory/swriter");
+ CPPUNIT_ASSERT(mxComponent.is());
+
+ // Save as PDF.
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ uno::Sequence<beans::PropertyValue> aFilterData(comphelper::InitPropertySequence(
+ { { "SelectPdfVersion", uno::makeAny(static_cast<sal_Int32>(15)) } }));
+ utl::MediaDescriptor aMediaDescriptor;
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ aMediaDescriptor["FilterData"] <<= aFilterData;
+ xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
+
+ // Parse the export result.
+ SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
+ maMemory.WriteStream(aFile);
+ DocumentHolder pPdfDocument(
+ FPDF_LoadMemDocument(maMemory.GetData(), maMemory.GetSize(), /*password=*/nullptr));
+ CPPUNIT_ASSERT(pPdfDocument.get());
+ int nFileVersion = 0;
+ FPDF_GetFileVersion(pPdfDocument.get(), &nFileVersion);
+ 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).
+void PdfExportTest::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
+ OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "SimpleMultiPagePDF.pdf";
+ mxComponent = loadFromDesktop(aURL);
+ CPPUNIT_ASSERT(mxComponent.is());
+
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ utl::MediaDescriptor aMediaDescriptor;
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ 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));
+
+ 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");
+ CPPUNIT_ASSERT(pResources);
+
+ auto pXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
+ 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(OString("Im19"), rIDs[0]);
+
+ auto pSubtype1 = dynamic_cast<vcl::filter::PDFNameElement*>(pXObject1->Lookup("Subtype"));
+ CPPUNIT_ASSERT(pSubtype1);
+ CPPUNIT_ASSERT_EQUAL(OString("Form"), pSubtype1->GetValue());
+
+ auto pXObjectResources = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject1->Lookup("Resources"));
+ CPPUNIT_ASSERT(pXObjectResources);
+ auto pXObjectForms = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObjectResources->LookupElement("XObject"));
+ 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(OString("Im34"), rIDs[1]);
+
+ auto pSubtype2 = dynamic_cast<vcl::filter::PDFNameElement*>(pXObject2->Lookup("Subtype"));
+ CPPUNIT_ASSERT(pSubtype2);
+ CPPUNIT_ASSERT_EQUAL(OString("Form"), pSubtype2->GetValue());
+
+ auto pXObjectResources = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject2->Lookup("Resources"));
+ CPPUNIT_ASSERT(pXObjectResources);
+ auto pXObjectForms = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObjectResources->LookupElement("XObject"));
+ 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(OString("Im4"), rIDs[2]);
+
+ auto pSubtype3 = dynamic_cast<vcl::filter::PDFNameElement*>(pXObject3->Lookup("Subtype"));
+ CPPUNIT_ASSERT(pSubtype3);
+ CPPUNIT_ASSERT_EQUAL(OString("Form"), pSubtype3->GetValue());
+
+ auto pXObjectResources = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject3->Lookup("Resources"));
+ CPPUNIT_ASSERT(pXObjectResources);
+ auto pXObjectForms = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObjectResources->LookupElement("XObject"));
+ 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_SUITE_REGISTRATION(PdfExportTest);
+
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/png/PngFilterTest.cxx b/vcl/qa/cppunit/png/PngFilterTest.cxx
new file mode 100644
index 000000000..c304ee9d3
--- /dev/null
+++ b/vcl/qa/cppunit/png/PngFilterTest.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 <test/bootstrapfixture.hxx>
+#include <tools/stream.hxx>
+#include <vcl/filter/PngImageReader.hxx>
+#include <vcl/bitmapaccess.hxx>
+#include <vcl/alpha.hxx>
+
+using namespace css;
+
+class PngFilterTest : public test::BootstrapFixture
+{
+ OUString maDataUrl;
+
+ OUString getFullUrl(const OUString& sFileName)
+ {
+ return m_directories.getURLFromSrc(maDataUrl) + sFileName;
+ }
+
+public:
+ PngFilterTest()
+ : BootstrapFixture(true, false)
+ , maDataUrl("/vcl/qa/cppunit/png/data/")
+ {
+ }
+
+ void testPng();
+
+ CPPUNIT_TEST_SUITE(PngFilterTest);
+ CPPUNIT_TEST(testPng);
+ 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();
+ {
+ Bitmap::ScopedReadAccess pAccess(aBitmap);
+ CPPUNIT_ASSERT_EQUAL(4L, pAccess->Width());
+ CPPUNIT_ASSERT_EQUAL(4L, pAccess->Height());
+
+ if (pAccess->GetBitCount() == 24 || pAccess->GetBitCount() == 32)
+ {
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0xFF, 0xFF, 0xFF, 0x00), pAccess->GetPixel(0, 0));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0xFF, 0xFF, 0xFF, 0x00), pAccess->GetPixel(3, 3));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0xFF, 0xFF, 0xFF, 0x00), pAccess->GetPixel(3, 0));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0xFF, 0xFF, 0xFF, 0x00), pAccess->GetPixel(0, 3));
+
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0x00, 0x00, 0x00, 0x00), pAccess->GetPixel(1, 1));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0x00, 0x00, 0x00, 0x00), pAccess->GetPixel(1, 2));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0x00, 0x00, 0x00, 0x00), pAccess->GetPixel(2, 1));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0x00, 0x00, 0x00, 0x00), pAccess->GetPixel(2, 2));
+ }
+ else
+ {
+ CPPUNIT_ASSERT_MESSAGE("Bitmap is not 24 or 32 bit.", false);
+ }
+ }
+ }
+ for (const OUString& aFileName :
+ { OUString("color-rect-8bit-RGB.png"), OUString("color-rect-4bit-pal.png") })
+ {
+ SvFileStream aFileStream(getFullUrl(aFileName), StreamMode::READ);
+
+ vcl::PngImageReader aPngReader(aFileStream);
+ BitmapEx aBitmapEx;
+ aPngReader.read(aBitmapEx);
+
+ Bitmap aBitmap = aBitmapEx.GetBitmap();
+ {
+ Bitmap::ScopedReadAccess pAccess(aBitmap);
+ CPPUNIT_ASSERT_EQUAL(4L, pAccess->Width());
+ CPPUNIT_ASSERT_EQUAL(4L, pAccess->Height());
+ if (pAccess->GetBitCount() == 24 || pAccess->GetBitCount() == 32)
+ {
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0xFF, 0xFF, 0xFF, 0x00), pAccess->GetPixel(0, 0));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0xFF, 0xFF, 0xFF, 0x00), pAccess->GetPixel(3, 3));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0xFF, 0xFF, 0xFF, 0x00), pAccess->GetPixel(3, 0));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0xFF, 0xFF, 0xFF, 0x00), pAccess->GetPixel(0, 3));
+
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0xFF, 0x00, 0x00, 0x00), pAccess->GetPixel(1, 1));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0x00, 0xFF, 0x00, 0x00), pAccess->GetPixel(1, 2));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0x00, 0x00, 0xFF, 0x00), pAccess->GetPixel(2, 1));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(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();
+ {
+ Bitmap::ScopedReadAccess pAccess(aBitmap);
+ CPPUNIT_ASSERT_EQUAL(4L, pAccess->Width());
+ CPPUNIT_ASSERT_EQUAL(4L, pAccess->Height());
+
+ if (pAccess->GetBitCount() == 24)
+ {
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0xFF, 0xFF, 0xFF, 0x00), pAccess->GetPixel(0, 0));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0xFF, 0xFF, 0xFF, 0x00), pAccess->GetPixel(3, 3));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0xFF, 0xFF, 0xFF, 0x00), pAccess->GetPixel(3, 0));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0xFF, 0xFF, 0xFF, 0x00), pAccess->GetPixel(0, 3));
+
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0xFF, 0x00, 0x00, 0x00), pAccess->GetPixel(1, 1));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0x00, 0xFF, 0x00, 0x00), pAccess->GetPixel(1, 2));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0x00, 0x00, 0xFF, 0x00), pAccess->GetPixel(2, 1));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0xFF, 0xFF, 0x00, 0x00), pAccess->GetPixel(2, 2));
+
+ AlphaMask aAlpha = aBitmapEx.GetAlpha();
+ {
+ AlphaMask::ScopedReadAccess pAlphaAccess(aAlpha);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(8), pAlphaAccess->GetBitCount());
+ CPPUNIT_ASSERT_EQUAL(4L, pAlphaAccess->Width());
+ CPPUNIT_ASSERT_EQUAL(4L, pAlphaAccess->Height());
+
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0x00, 0x00, 0x80, 0x00),
+ pAlphaAccess->GetPixel(0, 0));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0x00, 0x00, 0x80, 0x00),
+ pAlphaAccess->GetPixel(3, 3));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0x00, 0x00, 0x80, 0x00),
+ pAlphaAccess->GetPixel(3, 0));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0x00, 0x00, 0x80, 0x00),
+ pAlphaAccess->GetPixel(0, 3));
+
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0x00, 0x00, 0x40, 0x00),
+ pAlphaAccess->GetPixel(1, 1));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0x00, 0x00, 0xC0, 0x00),
+ pAlphaAccess->GetPixel(1, 2));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0x00, 0x00, 0xC0, 0x00),
+ pAlphaAccess->GetPixel(2, 1));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0x00, 0x00, 0x40, 0x00),
+ pAlphaAccess->GetPixel(2, 2));
+ }
+ }
+ else if (pAccess->GetBitCount() == 32)
+ {
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0xFF, 0xFF, 0xFF, 0x80), pAccess->GetPixel(0, 0));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0xFF, 0xFF, 0xFF, 0x80), pAccess->GetPixel(3, 3));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0xFF, 0xFF, 0xFF, 0x80), pAccess->GetPixel(3, 0));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0xFF, 0xFF, 0xFF, 0x80), pAccess->GetPixel(0, 3));
+
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0xFF, 0x00, 0x00, 0x40), pAccess->GetPixel(1, 1));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0x00, 0xFF, 0x00, 0xC0), pAccess->GetPixel(1, 2));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0x00, 0x00, 0xFF, 0xC0), pAccess->GetPixel(2, 1));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0xFF, 0xFF, 0x00, 0x40), pAccess->GetPixel(2, 2));
+ }
+ else
+ {
+ CPPUNIT_ASSERT_MESSAGE("Bitmap is not 24 or 32 bit.", false);
+ }
+ }
+ }
+}
+
+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 000000000..1e90e1a6c
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/alpha-rect-8bit-RGBA.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 000000000..740eede51
--- /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.png b/vcl/qa/cppunit/png/data/color-rect-8bit-RGB.png
new file mode 100644
index 000000000..727859d8a
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/color-rect-8bit-RGB.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 000000000..cf7ac3e7c
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/rect-1bit-pal.png
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/arc.svm b/vcl/qa/cppunit/svm/data/arc.svm
new file mode 100644
index 000000000..5d443a704
--- /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 000000000..f6860bdff
--- /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 000000000..bf42f1bae
--- /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 000000000..7c7136eed
--- /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 000000000..aa0d277fe
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/clipregion.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 000000000..369e4292c
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/ellipse.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 000000000..8e1a826c4
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/fillcolor.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 000000000..68811e065
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/gradient.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 000000000..4e6bdf638
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/hatch.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 000000000..67441ec92
--- /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 000000000..2cba7f734
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/linecolor.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 000000000..b9e777930
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/masks.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 000000000..10cd773fb
--- /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 000000000..1eaf67447
--- /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 000000000..dc15ab8f0
--- /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 000000000..5b5c3731b
--- /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 000000000..162ccfd9c
--- /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 000000000..9139da371
--- /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 000000000..ea01bd14a
--- /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 000000000..d5ab6e80a
--- /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 000000000..ac9f796c2
--- /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 000000000..8c398b572
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/rect.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 000000000..aa0ca3e29
--- /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 000000000..f6dcb5fba
--- /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 000000000..37da7f598
--- /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 000000000..091c49876
--- /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 000000000..7863b03ce
--- /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 000000000..f55b3ccd7
--- /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 000000000..52a4f29f3
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/textfillecolor.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 000000000..1d8517823
--- /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 000000000..786dfc465
--- /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 000000000..62c424d6a
--- /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 000000000..40864b90a
--- /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 000000000..8fca8c31f
--- /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 000000000..413c3b7f6
--- /dev/null
+++ b/vcl/qa/cppunit/svm/svmtest.cxx
@@ -0,0 +1,1684 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/xmltesttools.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/gradient.hxx>
+#include <vcl/hatch.hxx>
+#include <vcl/lineinfo.hxx>
+#include <vcl/virdev.hxx>
+#include <bitmapwriteaccess.hxx>
+#include <vcl/pngwrite.hxx>
+
+#include <config_features.h>
+#if HAVE_FEATURE_OPENGL
+#include <vcl/opengl/OpenGLHelper.hxx>
+#endif
+#include <vcl/skia/SkiaHelper.hxx>
+
+using namespace css;
+
+class SvmTest : public test::BootstrapFixture, public XmlTestTools
+{
+ OUString maDataUrl;
+
+ OUString getFullUrl(const OUString& sFileName)
+ {
+ return m_directories.getURLFromSrc(maDataUrl) + sFileName;
+ }
+
+ void checkRendering(ScopedVclPtrInstance<VirtualDevice> const & pVirtualDev, const GDIMetaFile& rMetaFile);
+
+ // write GDI Metafile to a file in data directory
+ // only use this for new tests to create the svm file
+ void writeToFile(GDIMetaFile& rMetaFile, OUString const & rName);
+
+ GDIMetaFile writeAndReadStream(GDIMetaFile& rMetaFile, OUString const & rName = OUString());
+
+ GDIMetaFile readFile(const OUString& 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 checkStrechText(const GDIMetaFile& rMetaFile);
+ void testStrechText();
+
+ 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);
+ 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();
+
+ //void checkFont(const GDIMetaFile& rMetaFile);
+ 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(testStrechText);
+ 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)
+{
+ BitmapEx aSourceBitmapEx = pVirtualDev->GetBitmapEx(Point(), Size(10, 10));
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDevResult;
+ const_cast<GDIMetaFile&>(rMetaFile).Play(pVirtualDevResult.get());
+ BitmapEx aResultBitmapEx = pVirtualDev->GetBitmapEx(Point(), Size(10, 10));
+
+ const bool bWriteCompareBitmap = false;
+
+ if (bWriteCompareBitmap)
+ {
+ utl::TempFile aTempFile;
+ aTempFile.EnableKillingFile();
+
+ {
+ SvFileStream aStream(aTempFile.GetURL() + ".source.png", StreamMode::WRITE | StreamMode::TRUNC);
+ vcl::PNGWriter aPNGWriter(aSourceBitmapEx);
+ aPNGWriter.Write(aStream);
+ }
+ {
+ SvFileStream aStream(aTempFile.GetURL() + ".result.png", StreamMode::WRITE | StreamMode::TRUNC);
+ vcl::PNGWriter aPNGWriter(aResultBitmapEx);
+ aPNGWriter.Write(aStream);
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(aSourceBitmapEx.GetChecksum(), aResultBitmapEx.GetChecksum());
+}
+
+static GDIMetaFile readMetafile(const OUString& rUrl)
+{
+ GDIMetaFile aResultMetafile;
+ SvFileStream aFileStream(rUrl, StreamMode::READ);
+ aFileStream.Seek(STREAM_SEEK_TO_BEGIN);
+ aResultMetafile.Read(aFileStream);
+ return aResultMetafile;
+}
+
+static void writeMetaFile(GDIMetaFile& rInputMetafile, const OUString& rUrl)
+{
+ SvFileStream aFileStream(rUrl, StreamMode::WRITE);
+ aFileStream.Seek(STREAM_SEEK_TO_BEGIN);
+ rInputMetafile.Write(aFileStream);
+ aFileStream.Close();
+}
+
+void SvmTest::writeToFile(GDIMetaFile& rMetaFile, OUString const & rName)
+{
+ if (rName.isEmpty())
+ return;
+ OUString sFilePath = getFullUrl(rName);
+ writeMetaFile(rMetaFile, sFilePath);
+}
+
+GDIMetaFile SvmTest::writeAndReadStream(GDIMetaFile& rMetaFile, OUString const & rName)
+{
+ if (!rName.isEmpty())
+ writeToFile(rMetaFile, rName);
+
+ SvMemoryStream aStream;
+ rMetaFile.Write(aStream);
+ aStream.Seek(STREAM_SEEK_TO_BEGIN);
+
+ GDIMetaFile aResultMetafile;
+ aResultMetafile.Read(aStream);
+ return aResultMetafile;
+}
+
+GDIMetaFile SvmTest::readFile(const OUString& 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]", "color", "#000000");
+ assertXPath(pDoc, "/metafile/fillcolor[1]", "color", "#ffffff");
+
+ assertXPathAttrs(pDoc, "/metafile/rect[1]", {
+ {"left", "0"}, {"top", "0"},
+ {"right", "9"}, {"bottom", "9"}
+ });
+
+ assertXPath(pDoc, "/metafile/linecolor[2]", "color", "#000000");
+ assertXPath(pDoc, "/metafile/fillcolor[2]", "color", "#ffffff");
+}
+
+void SvmTest::checkErase(const xmlDocUniquePtr& pDoc)
+{
+ assertXPath(pDoc, "/metafile/linecolor[3]", "color", "#000000");
+ assertXPath(pDoc, "/metafile/fillcolor[3]", "color", "#ff0000");
+
+ assertXPathAttrs(pDoc, "/metafile/rect[2]", {
+ {"left", "0"}, {"top", "0"},
+ {"right", "9"}, {"bottom", "9"}
+ });
+}
+
+void SvmTest::checkPixel(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/pixel[1]", {
+ {"x", "8"}, {"y", "1"}, {"color", "#008000"},
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/pixel[2]", {
+ {"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("pixel.svm"));
+}
+
+void SvmTest::checkPoint(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/point[1]", {
+ {"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("point.svm"));
+}
+
+void SvmTest::checkLine(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/line[1]", {
+ {"startx", "1"}, {"starty", "1"},
+ {"endx", "8"}, {"endy", "8"},
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/line[1]", {
+ {"style", "solid"}, {"width", "0"},
+ {"dashlen", "0"}, {"dashcount", "0"},
+ {"dotlen", "0"}, {"dotcount", "0"},
+ {"distance", "0"},
+ {"join", "round"}, {"cap", "butt"}
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/line[2]", {
+ {"startx", "1"}, {"starty", "8"},
+ {"endx", "8"}, {"endy", "1"},
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/line[2]", {
+ {"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("line.svm"));
+}
+
+void SvmTest::checkRect(const GDIMetaFile& rMetaFile)
+{
+
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPath(pDoc, "/metafile/linecolor[5]", "color", "#123456");
+ assertXPath(pDoc, "/metafile/fillcolor[5]", "color", "#654321");
+
+ assertXPathAttrs(pDoc, "/metafile/rect[3]", {
+ {"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("rect.svm"));
+}
+
+void SvmTest::checkRoundRect(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPath(pDoc, "/metafile/linecolor[5]", "color", "#123456");
+ assertXPath(pDoc, "/metafile/fillcolor[5]", "color", "#654321");
+
+ assertXPathAttrs(pDoc, "/metafile/roundrect[1]", {
+ {"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("roundrect.svm"));
+}
+
+void SvmTest::checkEllipse(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPath(pDoc, "/metafile/linecolor[5]", "color", "#123456");
+ assertXPath(pDoc, "/metafile/fillcolor[5]", "color", "#654321");
+
+ assertXPathAttrs(pDoc, "/metafile/ellipse[1]", {
+ {"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("ellipse.svm"));
+}
+
+void SvmTest::checkArc(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPath(pDoc, "/metafile/linecolor[5]", "color", "#123456");
+ assertXPath(pDoc, "/metafile/fillcolor[5]", "color", "#654321");
+
+ assertXPathAttrs(pDoc, "/metafile/arc[1]", {
+ {"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("arc.svm"));
+}
+
+void SvmTest::checkPie(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPath(pDoc, "/metafile/linecolor[5]", "color", "#123456");
+ assertXPath(pDoc, "/metafile/fillcolor[5]", "color", "#654321");
+
+ assertXPathAttrs(pDoc, "/metafile/pie[1]", {
+ {"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("pie.svm"));
+}
+
+void SvmTest::checkChord(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPath(pDoc, "/metafile/linecolor[5]", "color", "#123456");
+ assertXPath(pDoc, "/metafile/fillcolor[5]", "color", "#654321");
+
+ assertXPathAttrs(pDoc, "/metafile/chord[1]", {
+ {"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("chord.svm"));
+}
+
+void SvmTest::checkPolyLine(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/polyline[1]", {
+ {"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]", {{"x", "1"}, {"y", "8"}});
+ assertXPathAttrs(pDoc, "/metafile/polyline[1]/point[2]", {{"x", "2"}, {"y", "7"}});
+ assertXPathAttrs(pDoc, "/metafile/polyline[1]/point[3]", {{"x", "3"}, {"y", "6"}});
+
+ assertXPathAttrs(pDoc, "/metafile/polyline[2]", {
+ {"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]", {{"x", "8"}, {"y", "1"}, {"flags", "normal"}});
+ assertXPathAttrs(pDoc, "/metafile/polyline[2]/point[2]", {{"x", "7"}, {"y", "2"}, {"flags", "control"}});
+ assertXPathAttrs(pDoc, "/metafile/polyline[2]/point[3]", {{"x", "6"}, {"y", "3"}, {"flags", "smooth"}});
+ assertXPathAttrs(pDoc, "/metafile/polyline[2]/point[4]", {{"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("polyline.svm"));
+}
+
+void SvmTest::checkPolygon(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/polygon[1]/point[1]", {{"x", "1"}, {"y", "8"}});
+ assertXPathAttrs(pDoc, "/metafile/polygon[1]/point[2]", {{"x", "2"}, {"y", "7"}});
+ assertXPathAttrs(pDoc, "/metafile/polygon[1]/point[3]", {{"x", "3"}, {"y", "6"}});
+
+ assertXPathAttrs(pDoc, "/metafile/polygon[2]/point[1]", {{"x", "8"}, {"y", "1"}, {"flags", "normal"}});
+ assertXPathAttrs(pDoc, "/metafile/polygon[2]/point[2]", {{"x", "7"}, {"y", "2"}, {"flags", "control"}});
+ assertXPathAttrs(pDoc, "/metafile/polygon[2]/point[3]", {{"x", "6"}, {"y", "3"}, {"flags", "smooth"}});
+ assertXPathAttrs(pDoc, "/metafile/polygon[2]/point[4]", {{"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("polygon.svm"));
+}
+
+void SvmTest::checkPolyPolygon(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/polypolygon[1]/polygon[1]/point[1]", {{"x", "1"}, {"y", "8"}});
+ assertXPathAttrs(pDoc, "/metafile/polypolygon[1]/polygon[1]/point[2]", {{"x", "2"}, {"y", "7"}});
+ assertXPathAttrs(pDoc, "/metafile/polypolygon[1]/polygon[1]/point[3]", {{"x", "3"}, {"y", "6"}});
+
+ assertXPathAttrs(pDoc, "/metafile/polypolygon[1]/polygon[2]/point[1]", {{"x", "8"}, {"y", "1"}, {"flags", "normal"}});
+ assertXPathAttrs(pDoc, "/metafile/polypolygon[1]/polygon[2]/point[2]", {{"x", "7"}, {"y", "2"}, {"flags", "control"}});
+ assertXPathAttrs(pDoc, "/metafile/polypolygon[1]/polygon[2]/point[3]", {{"x", "6"}, {"y", "3"}, {"flags", "smooth"}});
+ assertXPathAttrs(pDoc, "/metafile/polypolygon[1]/polygon[2]/point[4]", {{"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("polypolygon.svm"));
+}
+
+void SvmTest::checkText(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/text[1]", {
+ {"x", "4"}, {"y", "6"}, {"index", "1"}, {"length", "2"},
+ });
+
+ assertXPathContent(pDoc, "/metafile/text[1]/textcontent", "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("text.svm"));
+}
+
+void SvmTest::checkTextArray(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/textarray[1]", {
+ {"x", "4"}, {"y", "6"}, {"index", "1"}, {"length", "4"},
+ });
+ assertXPathContent(pDoc, "/metafile/textarray[1]/dxarray", "15 20 25 ");
+ assertXPathContent(pDoc, "/metafile/textarray[1]/text", "123456");
+}
+
+void SvmTest::testTextArray()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+ long const aDX[] = { 10, 15, 20, 25, 30, 35 };
+ pVirtualDev->DrawTextArray(Point(4,6), "123456", aDX, 1, 4);
+
+ checkTextArray(writeAndReadStream(aGDIMetaFile));
+ checkTextArray(readFile("textarray.svm"));
+}
+
+void SvmTest::checkStrechText(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/stretchtext[1]", {
+ {"x", "4"}, {"y", "6"}, {"index", "1"}, {"length", "4"}, {"width", "10"}
+ });
+
+ assertXPathContent(pDoc, "/metafile/stretchtext[1]/textcontent", "123456");
+}
+
+void SvmTest::testStrechText()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+ pVirtualDev->DrawStretchText(Point(4,6), 10, "123456", 1, 4);
+
+ checkStrechText(writeAndReadStream(aGDIMetaFile));
+ checkStrechText(readFile("strecthtext.svm"));
+}
+
+void SvmTest::checkTextRect(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/textrect[1]", {
+ {"left", "0"}, {"top", "0"}, {"right", "4"}, {"bottom", "4"}
+ });
+ assertXPathContent(pDoc, "/metafile/textrect[1]/textcontent", "123456");
+ assertXPathContent(pDoc, "/metafile/textrect[1]/style", "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("textrectangle.svm"));
+}
+
+void SvmTest::checkTextLine(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/textline[1]", {
+ {"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("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)
+
+ OUString crc1 = "b8dee5da";
+ OUString crc2 = "281fc589";
+ OUString crc3 = "5e01ddcc";
+#if HAVE_FEATURE_OPENGL
+ if (OpenGLHelper::isVCLOpenGLEnabled())
+ {
+ // OpenGL uses a different scaling algorithm and also a different RGB order.
+ crc1 = "5e01ddcc";
+ crc2 = "281fc589";
+ crc3 = "b8dee5da";
+ }
+#endif
+
+ assertXPathAttrs(pDoc, "/metafile/bmp[1]", {{"x", "1"}, {"y", "2"}, {"crc", crc1}});
+ assertXPathAttrs(pDoc, "/metafile/bmpscale[1]", {
+ {"x", "1"}, {"y", "2"}, {"width", "3"}, {"height", "4"}, {"crc", crc2}
+ });
+ assertXPathAttrs(pDoc, "/metafile/bmpscalepart[1]", {
+ {"destx", "1"}, {"desty", "2"}, {"destwidth", "3"}, {"destheight", "4"},
+ {"srcx", "2"}, {"srcy", "1"}, {"srcwidth", "4"}, {"srcheight", "3"},
+ {"crc", crc3}
+ });
+}
+
+void SvmTest::testBitmaps()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ Bitmap aBitmap1(Size(4,4), 24);
+ {
+ BitmapScopedWriteAccess pAccess(aBitmap1);
+ pAccess->Erase(COL_RED);
+ }
+ Bitmap aBitmap2(Size(4,4), 24);
+ {
+ BitmapScopedWriteAccess pAccess(aBitmap2);
+ pAccess->Erase(COL_GREEN);
+ }
+ Bitmap aBitmap3(Size(4,4), 24);
+ {
+ 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);
+ }
+ {
+ GDIMetaFile aFileGDIMetaFile = readFile("bitmaps.svm");
+ checkBitmaps(aFileGDIMetaFile);
+ checkRendering(pVirtualDev, aFileGDIMetaFile);
+ }
+}
+
+void SvmTest::checkBitmapExs(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ if (SkiaHelper::isVCLSkiaEnabled())
+ return; // TODO SKIA using CRCs is broken (the idea of it)
+
+ std::vector<OUString> aExpectedCRC;
+
+#if HAVE_FEATURE_OPENGL
+ if (OpenGLHelper::isVCLOpenGLEnabled())
+ {
+ aExpectedCRC.insert(aExpectedCRC.end(),
+ {
+ "08feb5d3",
+ "281fc589",
+ "b8dee5da",
+ "4df0e464",
+ "7d3a8da3",
+ "1426653b",
+ "4fd547df",
+ "71efc447",
+ });
+ }
+ else
+#endif
+ {
+ aExpectedCRC.insert(aExpectedCRC.end(),
+ {
+ "d8377d4f",
+ "281fc589",
+ "5e01ddcc",
+ "4df0e464",
+ "34434a50",
+ "d1736327",
+ "b37875c2",
+ "a85d44b8",
+ });
+ }
+
+ assertXPathAttrs(pDoc, "/metafile/bmpex[1]", {
+ {"x", "1"}, {"y", "1"}, {"crc", aExpectedCRC[0]}, {"transparenttype", "bitmap"}
+ });
+ assertXPathAttrs(pDoc, "/metafile/bmpexscale[1]", {
+ {"x", "5"}, {"y", "0"}, {"width", "2"}, {"height", "3"},
+ {"crc", aExpectedCRC[1]}, {"transparenttype", "bitmap"}
+ });
+ assertXPathAttrs(pDoc, "/metafile/bmpexscalepart[1]", {
+ {"destx", "7"}, {"desty", "1"}, {"destwidth", "2"}, {"destheight", "2"},
+ {"srcx", "0"}, {"srcy", "0"}, {"srcwidth", "3"}, {"srcheight", "4"},
+ {"crc", aExpectedCRC[2]}, {"transparenttype", "bitmap"}
+ });
+
+#ifndef MACOSX
+ assertXPathAttrs(pDoc, "/metafile/bmpex[2]", {
+ {"x", "6"}, {"y", "6"}, {"crc", aExpectedCRC[3]}, {"transparenttype", "bitmap"}
+ });
+ assertXPathAttrs(pDoc, "/metafile/bmpex[3]", {
+ {"x", "0"}, {"y", "6"}, {"crc", aExpectedCRC[4]}, {"transparenttype", "bitmap"}
+ });
+ assertXPathAttrs(pDoc, "/metafile/bmpex[4]", {
+ {"x", "2"}, {"y", "6"}, {"crc", aExpectedCRC[5]}, {"transparenttype", "bitmap"}
+ });
+ assertXPathAttrs(pDoc, "/metafile/bmpex[5]", {
+ {"x", "0"}, {"y", "8"}, {"crc", aExpectedCRC[6]}, {"transparenttype", "bitmap"}
+ });
+ assertXPathAttrs(pDoc, "/metafile/bmpex[6]", {
+ {"x", "2"}, {"y", "8"}, {"crc", aExpectedCRC[7]}, {"transparenttype", "bitmap"}
+ });
+#endif
+}
+
+void SvmTest::testBitmapExs()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ // DrawBitmapEx
+ {
+ Bitmap aBitmap(Size(4,4), 24);
+ {
+ BitmapScopedWriteAccess pAccess(aBitmap);
+ pAccess->Erase(COL_YELLOW);
+ }
+
+ pVirtualDev->DrawBitmapEx(Point(1, 1), BitmapEx(aBitmap, COL_WHITE));
+ }
+
+ // DrawBitmapEx - Scale
+ {
+ Bitmap aBitmap(Size(4,4), 24);
+ {
+ 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), 24);
+ {
+ 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), 24);
+ AlphaMask aAlpha(Size(4, 4));
+ {
+ BitmapScopedWriteAccess pAccess(aBitmap);
+ pAccess->Erase(COL_MAGENTA);
+
+ AlphaScopedWriteAccess pAlphaAccess(aAlpha);
+ pAlphaAccess->Erase(Color(128, 128, 128));
+ }
+ pVirtualDev->DrawBitmapEx(Point(6, 6), BitmapEx(aBitmap, aAlpha));
+ }
+
+ // DrawBitmapEx - 1-bit
+ {
+ Bitmap aBitmap(Size(2, 2), 24);
+ {
+ BitmapScopedWriteAccess pAccess(aBitmap);
+ pAccess->Erase(COL_MAGENTA);
+ }
+ aBitmap.Convert(BmpConversion::N1BitThreshold);
+ pVirtualDev->DrawBitmapEx(Point(0, 6), BitmapEx(aBitmap, COL_WHITE));
+ }
+
+ // DrawBitmapEx - 4-bit
+ {
+ Bitmap aBitmap(Size(2, 2), 24);
+ {
+ BitmapScopedWriteAccess pAccess(aBitmap);
+ pAccess->Erase(COL_MAGENTA);
+ }
+ aBitmap.Convert(BmpConversion::N4BitColors);
+ pVirtualDev->DrawBitmapEx(Point(2, 6), BitmapEx(aBitmap, COL_WHITE));
+ }
+
+ // DrawBitmapEx - 8-bit Color
+ {
+ Bitmap aBitmap(Size(2, 2), 24);
+ {
+ 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), 24);
+ {
+ 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);
+ checkRendering(pVirtualDev, aReloadedGDIMetaFile);
+ }
+ {
+ GDIMetaFile aFileGDIMetaFile = readFile("bitmapexs.svm");
+ checkBitmapExs(aFileGDIMetaFile);
+ checkRendering(pVirtualDev, aFileGDIMetaFile);
+ }
+}
+
+void SvmTest::checkMasks(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/mask[1]", {
+ {"x", "1"}, {"y", "2"},
+ {"color", "#000000"}
+ });
+ assertXPathAttrs(pDoc, "/metafile/maskscale[1]", {
+ {"x", "1"}, {"y", "2"}, {"width", "3"}, {"height", "4"},
+ {"color", "#000000"}
+ });
+ assertXPathAttrs(pDoc, "/metafile/maskscalepart[1]", {
+ {"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), 24);
+ {
+ BitmapScopedWriteAccess pAccess(aBitmap1);
+ pAccess->Erase(COL_RED);
+ }
+ Bitmap aBitmap2(Size(4,4), 24);
+ {
+ BitmapScopedWriteAccess pAccess(aBitmap2);
+ pAccess->Erase(COL_GREEN);
+ }
+ Bitmap aBitmap3(Size(4,4), 24);
+ {
+ 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("masks.svm"));
+}
+
+void SvmTest::checkGradient(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/gradient[1]", {
+ {"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", {
+ {"left", "1"},
+ {"top", "2"},
+ {"right", "4"},
+ {"bottom", "6"},
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/gradient[2]", {
+ {"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", {
+ {"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(GradientStyle::Linear, COL_WHITE, COL_BLACK);
+ pVirtualDev->DrawGradient(aRectangle, aGradient);
+
+ tools::Rectangle aRectangle2(Point(3, 4), Size(1,2));
+
+ Gradient aGradient2;
+ aGradient2.SetStyle(GradientStyle::Radial);
+ aGradient2.SetStartColor(COL_LIGHTRED);
+ aGradient2.SetEndColor(COL_LIGHTGREEN);
+ aGradient2.SetAngle(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("gradient.svm"));
+}
+
+void SvmTest::testGradientEx()
+{}
+
+void SvmTest::checkHatch(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/hatch[1]/polygon/point[1]", {
+ {"x", "1"}, {"y", "8"},
+ });
+ assertXPathAttrs(pDoc, "/metafile/hatch[1]/polygon/point[2]", {
+ {"x", "2"}, {"y", "7"},
+ });
+ assertXPathAttrs(pDoc, "/metafile/hatch[1]/polygon/point[3]", {
+ {"x", "3"}, {"y", "6"},
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/hatch[1]/hatch", {
+ {"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);
+
+ pVirtualDev->DrawHatch(aPolyPolygon, aHatch);
+
+ checkHatch(writeAndReadStream(aGDIMetaFile));
+ checkHatch(readFile("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]",
+ {
+ {"left", "0"},
+ {"top", "0"},
+ {"right", "empty"},
+ {"bottom", "empty"},
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/wallpaper[1]/wallpaper",
+ {
+ {"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("wallpaper.svm"));
+}
+
+void SvmTest::checkClipRegion(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/clipregion[1]", {
+ {"left", "2"},
+ {"top", "2"},
+ {"right", "5"},
+ {"bottom", "5"},
+ });
+}
+
+void SvmTest::testClipRegion()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ vcl::Region aRegion(tools::Rectangle(Point(2, 2), Size(4, 4)));
+
+ // TODO
+ // explicit Region(const tools::Polygon& rPolygon);
+ // explicit Region(const tools::PolyPolygon& rPolyPoly);
+ // explicit Region(const basegfx::B2DPolyPolygon&);
+ pVirtualDev->SetClipRegion(aRegion);
+
+ checkClipRegion(writeAndReadStream(aGDIMetaFile));
+ checkClipRegion(readFile("clipregion.svm"));
+}
+
+void SvmTest::testIntersectRectClipRegion()
+{}
+void SvmTest::testIntersectRegionClipRegion()
+{}
+void SvmTest::testMoveClipRegion()
+{}
+
+void SvmTest::checkLineColor(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/push/linecolor[1]", {
+ {"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("linecolor.svm"));
+}
+
+void SvmTest::checkFillColor(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/push/fillcolor[1]", {
+ {"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("fillcolor.svm"));
+}
+
+void SvmTest::checkTextColor(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/textcolor[1]", {
+ {"color", "#123456"},
+ });
+}
+
+void SvmTest::testTextColor()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ pVirtualDev->SetTextColor(Color(0x123456));
+
+ checkTextColor(writeAndReadStream(aGDIMetaFile));
+ checkTextColor(readFile("textcolor.svm"));
+}
+
+void SvmTest::checkTextFillColor(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/textfillcolor[1]", {
+ {"color", "#234567"},
+ });
+}
+
+void SvmTest::testTextFillColor()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ pVirtualDev->SetTextFillColor(Color(0x234567));
+
+ checkTextFillColor(writeAndReadStream(aGDIMetaFile));
+ checkTextFillColor(readFile("textfillecolor.svm"));
+}
+
+void SvmTest::checkTextLineColor(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/textlinecolor[1]", {
+ {"color", "#345678"},
+ });
+}
+
+void SvmTest::testTextLineColor()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ pVirtualDev->SetTextLineColor(Color(0x345678));
+
+ checkTextLineColor(writeAndReadStream(aGDIMetaFile));
+ checkTextLineColor(readFile("textlinecolor.svm"));
+}
+
+void SvmTest::checkOverLineColor(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/push/overlinecolor[1]", {
+ {"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("overlinecolor.svm"));
+}
+
+void SvmTest::checkTextAlign(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/textalign[1]", {
+ {"align", "bottom"},
+ });
+}
+
+void SvmTest::testTextAlign()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ pVirtualDev->SetTextAlign(TextAlign::ALIGN_BOTTOM);
+
+ checkTextAlign(writeAndReadStream(aGDIMetaFile));
+ checkTextAlign(readFile("textalign.svm"));
+}
+
+void SvmTest::testMapMode()
+{}
+void SvmTest::testFont()
+{}
+
+void SvmTest::checkPushPop(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/push[1]", {{"flags", "PushAll"}});
+ assertXPathAttrs(pDoc, "/metafile/push[1]/linecolor[1]", {{"color", "#800000"}});
+ assertXPathAttrs(pDoc, "/metafile/push[1]/line[1]", {
+ {"startx", "4"}, {"starty", "4"},
+ {"endx", "6"}, {"endy", "6"},
+ });
+ assertXPathAttrs(pDoc, "/metafile/push[1]/push[1]", {{"flags", "PushLineColor, PushFillColor"}});
+ assertXPathAttrs(pDoc, "/metafile/push[1]/push[1]/line[1]", {
+ {"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(PushFlags::FILLCOLOR | 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("pushpop.svm"));
+}
+
+void SvmTest::checkRasterOp(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/rasterop[1]", {
+ {"operation", "xor"},
+ });
+}
+
+void SvmTest::testRasterOp()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ pVirtualDev->SetRasterOp(RasterOp::Xor);
+
+ checkRasterOp(writeAndReadStream(aGDIMetaFile));
+ checkRasterOp(readFile("rasterop.svm"));
+}
+
+void SvmTest::checkTransparent(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/transparent[1]", {
+ {"transparence", "50"},
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/transparent[1]/polygon/point[1]", {
+ {"x", "1"}, {"y", "8"},
+ });
+ assertXPathAttrs(pDoc, "/metafile/transparent[1]/polygon/point[2]", {
+ {"x", "2"}, {"y", "7"},
+ });
+ assertXPathAttrs(pDoc, "/metafile/transparent[1]/polygon/point[3]", {
+ {"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(aPolygon, 50);
+
+ CPPUNIT_ASSERT(aGDIMetaFile.HasTransparentActions());
+ checkTransparent(writeAndReadStream(aGDIMetaFile));
+ checkTransparent(readFile("transparent.svm"));
+}
+
+void SvmTest::testFloatTransparent()
+{}
+
+void SvmTest::testEPS()
+{}
+
+void SvmTest::testRefPoint()
+{}
+
+void SvmTest::testComment()
+{}
+
+void SvmTest::testLayoutMode()
+{}
+
+void SvmTest::testTextLanguage()
+{}
+
+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 000000000..d7b72d6d8
--- /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>
+ <whitelist>
+ </whitelist>
+ <blacklist>
+
+ <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>
+
+ </blacklist>
+</root>
diff --git a/vcl/qa/cppunit/test_blocklist_parse.xml b/vcl/qa/cppunit/test_blocklist_parse.xml
new file mode 100644
index 000000000..f9af4cb54
--- /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>
+ <whitelist>
+ <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>
+ </whitelist>
+ <blacklist>
+ <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>
+ </blacklist>
+</root>
diff --git a/vcl/qa/cppunit/timer.cxx b/vcl/qa/cppunit/timer.cxx
new file mode 100644
index 000000000..4071cd0fd
--- /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_uLong nMS, bool &rBool ) :
+ Timer( "TimerBool" ), mrBool( rBool )
+ {
+ SetTimeout( nMS );
+ Start();
+ mrBool = false;
+ }
+ virtual void Invoke() override
+ {
+ mrBool = true;
+ Application::EndYield();
+ }
+};
+
+}
+
+void TimerTest::testDurations()
+{
+ static const sal_uLong aDurations[] = { 0, 1, 500, 1000 };
+ for (size_t i = 0; i < SAL_N_ELEMENTS( aDurations ); i++)
+ {
+ bool bDone = false;
+ TimerBool aTimer( aDurations[i], 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_uLong 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_uLong 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_uLong 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 &mrProcesed;
+public:
+ IdleSerializer(const char *pDebugName, TaskPriority ePrio,
+ sal_uInt32 nPosition, sal_uInt32 &rProcesed)
+ : Idle( pDebugName )
+ , mnPosition( nPosition )
+ , mrProcesed( rProcesed )
+ {
+ SetPriority(ePrio);
+ Start();
+ }
+ virtual void Invoke() override
+ {
+ ++mrProcesed;
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Ignored prio", mnPosition, mrProcesed );
+ }
+};
+
+}
+
+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( 3 == nCount1 && 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 000000000..d5d0167b7
--- /dev/null
+++ b/vcl/qa/cppunit/widgetdraw/WidgetDefinitionReaderTest.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 <cppunit/TestAssert.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/plugin/TestPlugIn.h>
+#include <unotest/bootstrapfixturebase.hxx>
+
+#include <widgetdraw/WidgetDefinitionReader.hxx>
+
+namespace
+{
+static OUString const gaDataUrl("/vcl/qa/cppunit/widgetdraw/data/");
+
+class WidgetDefinitionReaderTest : public test::BootstrapFixtureBase
+{
+private:
+ OUString getFullUrl(const OUString& 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("definitionSettings1.xml"), getFullUrl(""));
+ CPPUNIT_ASSERT(aReader.read(aDefinition));
+ CPPUNIT_ASSERT_EQUAL(OString(""), aDefinition.mpSettings->msCenteredTabs);
+ }
+
+ {
+ vcl::WidgetDefinition aDefinition;
+ vcl::WidgetDefinitionReader aReader(getFullUrl("definitionSettings2.xml"), getFullUrl(""));
+ CPPUNIT_ASSERT(aReader.read(aDefinition));
+ CPPUNIT_ASSERT_EQUAL(OString("true"), aDefinition.mpSettings->msCenteredTabs);
+ }
+
+ {
+ vcl::WidgetDefinition aDefinition;
+ vcl::WidgetDefinitionReader aReader(getFullUrl("definitionSettings3.xml"), getFullUrl(""));
+ CPPUNIT_ASSERT(aReader.read(aDefinition));
+ CPPUNIT_ASSERT_EQUAL(OString("true"), aDefinition.mpSettings->msNoActiveTabTextRaise);
+ CPPUNIT_ASSERT_EQUAL(OString("false"), aDefinition.mpSettings->msCenteredTabs);
+ CPPUNIT_ASSERT_EQUAL(OString("0"), aDefinition.mpSettings->msListBoxEntryMargin);
+ CPPUNIT_ASSERT_EQUAL(OString("10"), aDefinition.mpSettings->msDefaultFontSize);
+ CPPUNIT_ASSERT_EQUAL(OString("16"), aDefinition.mpSettings->msTitleHeight);
+ CPPUNIT_ASSERT_EQUAL(OString("12"), aDefinition.mpSettings->msFloatTitleHeight);
+ CPPUNIT_ASSERT_EQUAL(OString("15"),
+ aDefinition.mpSettings->msListBoxPreviewDefaultLogicWidth);
+ CPPUNIT_ASSERT_EQUAL(OString("7"),
+ aDefinition.mpSettings->msListBoxPreviewDefaultLogicHeight);
+ }
+}
+
+void WidgetDefinitionReaderTest::testRead()
+{
+ vcl::WidgetDefinition aDefinition;
+
+ vcl::WidgetDefinitionReader aReader(getFullUrl("definition1.xml"), getFullUrl(""));
+ 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->maFontColor.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 000000000..041e8fc24
--- /dev/null
+++ b/vcl/qa/cppunit/widgetdraw/data/definition1.xml
@@ -0,0 +1,82 @@
+<?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"/>
+ <fontColor 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 000000000..9ca7f894f
--- /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 000000000..0d6d6e111
--- /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 000000000..9ad88dd54
--- /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 000000000..19fb5402c
--- /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 VclBuilder
+#
+
+# 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 000000000..fa750666e
--- /dev/null
+++ b/vcl/qa/unit/vcl-dialogs-test.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 <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("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/Qt5AccessibleEventListener.cxx b/vcl/qt5/Qt5AccessibleEventListener.cxx
new file mode 100644
index 000000000..621e54172
--- /dev/null
+++ b/vcl/qt5/Qt5AccessibleEventListener.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <Qt5AccessibleEventListener.hxx>
+
+#include <sal/log.hxx>
+
+#include <com/sun/star/accessibility/AccessibleEventId.hpp>
+
+#include <QtGui/QAccessible>
+
+using namespace css;
+using namespace css::accessibility;
+using namespace css::lang;
+using namespace css::uno;
+
+Qt5AccessibleEventListener::Qt5AccessibleEventListener(const Reference<XAccessible> xAccessible,
+ Qt5AccessibleWidget* pAccessibleWidget)
+ : m_xAccessible(xAccessible)
+ , m_pAccessibleWidget(pAccessibleWidget)
+{
+}
+
+void Qt5AccessibleEventListener::notifyEvent(
+ const css::accessibility::AccessibleEventObject& aEvent)
+{
+ QAccessibleInterface* pQAccessibleInterface = m_pAccessibleWidget;
+
+ Reference<XAccessible> xChild;
+ 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:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::ActiveDescendantChanged));
+ return;
+ case AccessibleEventId::CHILD:
+ {
+ QAccessible::Event event = QAccessible::InvalidEvent;
+ if (aEvent.OldValue >>= xChild)
+ event = QAccessible::ObjectDestroyed;
+ if (aEvent.NewValue >>= xChild)
+ event = QAccessible::ObjectCreated;
+ if (event != QAccessible::InvalidEvent)
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, event));
+ 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:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::Selection));
+ return;
+ case AccessibleEventId::TEXT_ATTRIBUTE_CHANGED:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::AttributeChanged));
+ 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_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:
+ case AccessibleEventId::CARET_CHANGED:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::TableSummaryChanged));
+ return;
+ case AccessibleEventId::SELECTION_CHANGED_ADD:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::SelectionAdd));
+ return;
+ case AccessibleEventId::SELECTION_CHANGED_REMOVE:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::SelectionRemove));
+ 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::TEXT_CHANGED:
+ 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:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::ForegroundChanged));
+ return;
+ case AccessibleEventId::ROLE_CHANGED:
+ case AccessibleEventId::INVALIDATE_ALL_CHILDREN:
+ case AccessibleEventId::VALUE_CHANGED:
+ 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::HYPERTEXT_CHANGED:
+ case AccessibleEventId::TABLE_MODEL_CHANGED:
+ case AccessibleEventId::LISTBOX_ENTRY_EXPANDED:
+ case AccessibleEventId::LISTBOX_ENTRY_COLLAPSED:
+ case AccessibleEventId::ACTIVE_DESCENDANT_CHANGED_NOFOCUS:
+ default:
+ SAL_WARN("vcl.qt5", "Unmapped AccessibleEventId: " << aEvent.EventId);
+ return;
+ }
+}
+
+void Qt5AccessibleEventListener::disposing(const EventObject& /* Source */) {}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5AccessibleWidget.cxx b/vcl/qt5/Qt5AccessibleWidget.cxx
new file mode 100644
index 000000000..15ebdf36b
--- /dev/null
+++ b/vcl/qt5/Qt5AccessibleWidget.cxx
@@ -0,0 +1,1267 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <Qt5AccessibleWidget.hxx>
+#include <Qt5AccessibleWidget.moc>
+
+#include <QtGui/QAccessibleInterface>
+
+#include <Qt5AccessibleEventListener.hxx>
+#include <Qt5Frame.hxx>
+#include <Qt5Tools.hxx>
+#include <Qt5Widget.hxx>
+#include <Qt5XAccessible.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/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/XAccessibleStateSet.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/awt/FontWeight.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/popupmenuwindow.hxx>
+
+using namespace css;
+using namespace css::accessibility;
+using namespace css::beans;
+using namespace css::uno;
+
+Qt5AccessibleWidget::Qt5AccessibleWidget(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 Qt5AccessibleEventListener(xAccessible, this));
+ xBroadcaster->addAccessibleEventListener(xListener);
+ }
+}
+
+Reference<XAccessibleContext> Qt5AccessibleWidget::getAccessibleContextImpl() const
+{
+ Reference<XAccessibleContext> xAc;
+
+ if (m_xAccessible.is())
+ {
+ try
+ {
+ xAc = m_xAccessible->getAccessibleContext();
+ }
+ catch (css::lang::DisposedException /*ex*/)
+ {
+ SAL_WARN("vcl.qt5", "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.qt5", "Accessible context no longer alive");
+ }
+ }
+
+ return xAc;
+}
+
+QWindow* Qt5AccessibleWidget::window() const { return nullptr; }
+
+int Qt5AccessibleWidget::childCount() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return 0;
+
+ return xAc->getAccessibleChildCount();
+}
+
+int Qt5AccessibleWidget::indexOfChild(const QAccessibleInterface* /* child */) const { return 0; }
+
+namespace
+{
+QAccessible::Relation lcl_matchUnoRelation(short relationType)
+{
+ switch (relationType)
+ {
+ case AccessibleRelationType::CONTROLLER_FOR:
+ return QAccessible::Controller;
+ case AccessibleRelationType::CONTROLLED_BY:
+ return QAccessible::Controlled;
+ case AccessibleRelationType::LABEL_FOR:
+ return QAccessible::Label;
+ case AccessibleRelationType::LABELED_BY:
+ return QAccessible::Labelled;
+ case AccessibleRelationType::INVALID:
+ case AccessibleRelationType::CONTENT_FLOWS_FROM:
+ case AccessibleRelationType::CONTENT_FLOWS_TO:
+ case AccessibleRelationType::MEMBER_OF:
+ case AccessibleRelationType::SUB_WINDOW_OF:
+ case AccessibleRelationType::NODE_CHILD_OF:
+ case AccessibleRelationType::DESCRIBED_BY:
+ default:
+ SAL_WARN("vcl.qt5", "Unmatched relation: " << relationType);
+ return nullptr;
+ }
+}
+
+short lcl_matchQtRelation(QAccessible::Relation relationType)
+{
+ switch (relationType)
+ {
+ case QAccessible::Controller:
+ return AccessibleRelationType::CONTROLLER_FOR;
+ case QAccessible::Controlled:
+ return AccessibleRelationType::CONTROLLED_BY;
+ case QAccessible::Label:
+ return AccessibleRelationType::LABEL_FOR;
+ case QAccessible::Labelled:
+ return AccessibleRelationType::LABELED_BY;
+ default:
+ SAL_WARN("vcl.qt5", "Unmatched relation: " << relationType);
+ }
+ return 0;
+}
+
+void lcl_appendRelation(QVector<QPair<QAccessibleInterface*, QAccessible::Relation>>* relations,
+ AccessibleRelation aRelation)
+{
+ QAccessible::Relation aQRelation = lcl_matchUnoRelation(aRelation.RelationType);
+ 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(new Qt5XAccessible(xAccessible)), aQRelation });
+ }
+}
+}
+
+QVector<QPair<QAccessibleInterface*, QAccessible::Relation>>
+Qt5AccessibleWidget::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())
+ {
+ if (match == QAccessible::AllRelations)
+ {
+ int count = xRelationSet->getRelationCount();
+ for (int i = 0; i < count; i++)
+ {
+ AccessibleRelation aRelation = xRelationSet->getRelation(i);
+ lcl_appendRelation(&relations, aRelation);
+ }
+ }
+ else
+ {
+ AccessibleRelation aRelation = xRelationSet->getRelation(lcl_matchQtRelation(match));
+ lcl_appendRelation(&relations, aRelation);
+ }
+ }
+
+ return relations;
+}
+
+QAccessibleInterface* Qt5AccessibleWidget::focusChild() const
+{
+ /* if (m_pWindow->HasChildPathFocus())
+ return QAccessible::queryAccessibleInterface(
+ new Qt5XAccessible(m_xAccessible->getAccessibleContext()->getAccessibleChild(index))); */
+ return QAccessible::queryAccessibleInterface(object());
+}
+
+QRect Qt5AccessibleWidget::rect() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return QRect();
+
+ Reference<XAccessibleComponent> xAccessibleComponent(xAc, UNO_QUERY);
+ awt::Point aPoint = xAccessibleComponent->getLocation();
+ awt::Size aSize = xAccessibleComponent->getSize();
+
+ return QRect(aPoint.X, aPoint.Y, aSize.Width, aSize.Height);
+}
+
+QAccessibleInterface* Qt5AccessibleWidget::parent() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return nullptr;
+
+ return QAccessible::queryAccessibleInterface(new Qt5XAccessible(xAc->getAccessibleParent()));
+}
+QAccessibleInterface* Qt5AccessibleWidget::child(int index) const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return nullptr;
+
+ return QAccessible::queryAccessibleInterface(
+ new Qt5XAccessible(xAc->getAccessibleChild(index)));
+}
+
+QString Qt5AccessibleWidget::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 Qt5AccessibleWidget::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::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::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:
+ return QAccessible::Paragraph;
+
+ case AccessibleRole::PASSWORD_TEXT:
+ 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::Button;
+
+ case AccessibleRole::BUTTON_MENU:
+ return QAccessible::Button;
+
+ 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;
+
+ /* Ignore window objects for sub-menus, combo- and list boxes,
+ * which are exposed as children of their parents.
+ */
+ case AccessibleRole::WINDOW: // top-level window without title bar
+ {
+ return QAccessible::Window;
+ }
+ }
+
+ SAL_WARN("vcl.qt5",
+ "Unmapped role: " << m_xAccessible->getAccessibleContext()->getAccessibleRole());
+ return QAccessible::NoRole;
+}
+
+namespace
+{
+void lcl_addState(QAccessible::State* state, sal_Int16 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::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::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:
+ // No match
+ break;
+ case AccessibleStateType::MANAGES_DESCENDANTS:
+ // No match
+ break;
+ case AccessibleStateType::MODAL:
+ state->modal = 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.qt5", "Unmapped state: " << nState);
+ break;
+ }
+}
+}
+
+QAccessible::State Qt5AccessibleWidget::state() const
+{
+ QAccessible::State state;
+
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return state;
+
+ Reference<XAccessibleStateSet> xStateSet(xAc->getAccessibleStateSet());
+
+ if (!xStateSet.is())
+ return state;
+
+ Sequence<sal_Int16> aStates = xStateSet->getStates();
+
+ for (sal_Int32 n = 0; n < aStates.getLength(); n++)
+ {
+ lcl_addState(&state, n);
+ }
+
+ return state;
+}
+
+QColor Qt5AccessibleWidget::foregroundColor() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return QColor();
+
+ Reference<XAccessibleComponent> xAccessibleComponent(xAc, UNO_QUERY);
+ return toQColor(xAccessibleComponent->getForeground());
+}
+
+QColor Qt5AccessibleWidget::backgroundColor() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return QColor();
+
+ Reference<XAccessibleComponent> xAccessibleComponent(xAc, UNO_QUERY);
+ return toQColor(xAccessibleComponent->getBackground());
+}
+
+void* Qt5AccessibleWidget::interface_cast(QAccessible::InterfaceType t)
+{
+ if (t == QAccessible::ActionInterface)
+ return static_cast<QAccessibleActionInterface*>(this);
+ if (t == QAccessible::TextInterface)
+ return static_cast<QAccessibleTextInterface*>(this);
+ if (t == QAccessible::EditableTextInterface)
+ return static_cast<QAccessibleEditableTextInterface*>(this);
+ if (t == QAccessible::ValueInterface)
+ return static_cast<QAccessibleValueInterface*>(this);
+ if (t == QAccessible::TableInterface)
+ return static_cast<QAccessibleTableInterface*>(this);
+ return nullptr;
+}
+
+bool Qt5AccessibleWidget::isValid() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ return xAc.is();
+}
+
+QObject* Qt5AccessibleWidget::object() const { return m_pObject; }
+
+void Qt5AccessibleWidget::setText(QAccessible::Text /* t */, const QString& /* text */) {}
+
+QAccessibleInterface* Qt5AccessibleWidget::childAt(int x, int y) const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return nullptr;
+
+ Reference<XAccessibleComponent> xAccessibleComponent(xAc, UNO_QUERY);
+ return QAccessible::queryAccessibleInterface(
+ new Qt5XAccessible(xAccessibleComponent->getAccessibleAtPoint(awt::Point(x, y))));
+}
+
+QAccessibleInterface* Qt5AccessibleWidget::customFactory(const QString& classname, QObject* object)
+{
+ if (classname == QLatin1String("Qt5Widget") && object && object->isWidgetType())
+ {
+ Qt5Widget* pWidget = static_cast<Qt5Widget*>(object);
+ vcl::Window* pWindow = pWidget->frame().GetWindow();
+
+ if (pWindow)
+ return new Qt5AccessibleWidget(pWindow->GetAccessible(), object);
+ }
+ if (classname == QLatin1String("Qt5XAccessible") && object)
+ {
+ Qt5XAccessible* pXAccessible = dynamic_cast<Qt5XAccessible*>(object);
+ if (pXAccessible && pXAccessible->m_xAccessible.is())
+ return new Qt5AccessibleWidget(pXAccessible->m_xAccessible, object);
+ }
+
+ return nullptr;
+}
+
+// QAccessibleActionInterface
+QStringList Qt5AccessibleWidget::actionNames() const
+{
+ QStringList actionNames;
+ Reference<XAccessibleAction> xAccessibleAction(m_xAccessible, 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 Qt5AccessibleWidget::doAction(const QString& actionName)
+{
+ Reference<XAccessibleAction> xAccessibleAction(m_xAccessible, UNO_QUERY);
+ if (!xAccessibleAction.is())
+ return;
+
+ int index = actionNames().indexOf(actionName);
+ if (index == -1)
+ return;
+ xAccessibleAction->doAccessibleAction(index);
+}
+
+QStringList Qt5AccessibleWidget::keyBindingsForAction(const QString& actionName) const
+{
+ QStringList keyBindings;
+ Reference<XAccessibleAction> xAccessibleAction(m_xAccessible, 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;
+}
+
+QAccessibleValueInterface* Qt5AccessibleWidget::valueInterface() { return nullptr; }
+
+QAccessibleTextInterface* Qt5AccessibleWidget::textInterface() { return nullptr; }
+
+// QAccessibleTextInterface
+void Qt5AccessibleWidget::addSelection(int /* startOffset */, int /* endOffset */)
+{
+ SAL_INFO("vcl.qt5", "Unsupported QAccessibleTextInterface::addSelection");
+}
+
+namespace
+{
+OUString lcl_convertFontWeight(double fontWeight)
+{
+ if (fontWeight == awt::FontWeight::THIN || fontWeight == awt::FontWeight::ULTRALIGHT)
+ return "100";
+ if (fontWeight == awt::FontWeight::LIGHT)
+ return "200";
+ if (fontWeight == awt::FontWeight::SEMILIGHT)
+ return "300";
+ if (fontWeight == awt::FontWeight::NORMAL)
+ return "normal";
+ if (fontWeight == awt::FontWeight::SEMIBOLD)
+ return "500";
+ if (fontWeight == awt::FontWeight::BOLD)
+ return "bold";
+ if (fontWeight == awt::FontWeight::ULTRABOLD)
+ return "800";
+ if (fontWeight == awt::FontWeight::BLACK)
+ return "900";
+
+ // awt::FontWeight::DONTKNOW || fontWeight == awt::FontWeight::NORMAL
+ return "normal";
+}
+}
+
+QString Qt5AccessibleWidget::attributes(int offset, int* startOffset, int* endOffset) const
+{
+ Reference<XAccessibleText> xText(m_xAccessible, 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(); // currently always returns 0
+
+ const int nTextLength = characterCount();
+ if (offset == -1 || offset == nTextLength)
+ offset = nTextLength - 1;
+
+ if (offset < 0 || offset > nTextLength)
+ {
+ *startOffset = -1;
+ *endOffset = -1;
+ return QString();
+ }
+
+ const Sequence<PropertyValue> attribs
+ = xText->getCharacterAttributes(offset, Sequence<OUString>());
+ OUString aRet;
+ for (PropertyValue const& prop : attribs)
+ {
+ if (prop.Name == "CharFontName")
+ {
+ aRet += "font-family:" + *o3tl::doAccess<OUString>(prop.Value) + ";";
+ continue;
+ }
+ if (prop.Name == "CharHeight")
+ {
+ aRet += "font-size:" + OUString::number(*o3tl::doAccess<double>(prop.Value)) + "pt;";
+ continue;
+ }
+ if (prop.Name == "CharWeight")
+ {
+ aRet += "font-weight:" + lcl_convertFontWeight(*o3tl::doAccess<double>(prop.Value))
+ + ";";
+ continue;
+ }
+ }
+ *startOffset = offset;
+ *endOffset = offset + 1;
+ return toQString(aRet);
+}
+int Qt5AccessibleWidget::characterCount() const
+{
+ Reference<XAccessibleText> xText(m_xAccessible, UNO_QUERY);
+ if (xText.is())
+ return xText->getCharacterCount();
+ return 0;
+}
+QRect Qt5AccessibleWidget::characterRect(int /* offset */) const
+{
+ SAL_INFO("vcl.qt5", "Unsupported QAccessibleTextInterface::characterRect");
+ return QRect();
+}
+int Qt5AccessibleWidget::cursorPosition() const
+{
+ SAL_INFO("vcl.qt5", "Unsupported QAccessibleTextInterface::cursorPosition");
+ return 0;
+}
+int Qt5AccessibleWidget::offsetAtPoint(const QPoint& /* point */) const
+{
+ SAL_INFO("vcl.qt5", "Unsupported QAccessibleTextInterface::offsetAtPoint");
+ return 0;
+}
+void Qt5AccessibleWidget::removeSelection(int /* selectionIndex */)
+{
+ SAL_INFO("vcl.qt5", "Unsupported QAccessibleTextInterface::removeSelection");
+}
+void Qt5AccessibleWidget::scrollToSubstring(int startIndex, int endIndex)
+{
+ Reference<XAccessibleText> xText(m_xAccessible, UNO_QUERY);
+ if (xText.is())
+ xText->scrollSubstringTo(startIndex, endIndex, AccessibleScrollType_SCROLL_ANYWHERE);
+}
+
+void Qt5AccessibleWidget::selection(int selectionIndex, int* startOffset, int* endOffset) const
+{
+ if (!startOffset && !endOffset)
+ return;
+
+ Reference<XAccessibleText> xText;
+ if (selectionIndex == 0)
+ xText = Reference<XAccessibleText>(m_xAccessible, UNO_QUERY);
+
+ if (startOffset)
+ *startOffset = xText.is() ? xText->getSelectionStart() : 0;
+ if (endOffset)
+ *endOffset = xText.is() ? xText->getSelectionEnd() : 0;
+}
+
+int Qt5AccessibleWidget::selectionCount() const
+{
+ Reference<XAccessibleText> xText(m_xAccessible, UNO_QUERY);
+ if (xText.is() && !xText->getSelectedText().isEmpty())
+ return 1; // Only 1 selection supported atm
+ return 0;
+}
+void Qt5AccessibleWidget::setCursorPosition(int position)
+{
+ Reference<XAccessibleText> xText(m_xAccessible, UNO_QUERY);
+ if (xText.is())
+ xText->setCaretPosition(position);
+}
+void Qt5AccessibleWidget::setSelection(int /* selectionIndex */, int startOffset, int endOffset)
+{
+ Reference<XAccessibleText> xText(m_xAccessible, UNO_QUERY);
+ if (xText.is())
+ xText->setSelection(startOffset, endOffset);
+}
+QString Qt5AccessibleWidget::text(int startOffset, int endOffset) const
+{
+ Reference<XAccessibleText> xText(m_xAccessible, UNO_QUERY);
+ if (xText.is())
+ return toQString(xText->getTextRange(startOffset, endOffset));
+ return QString();
+}
+QString Qt5AccessibleWidget::textAfterOffset(int /* offset */,
+ QAccessible::TextBoundaryType /* boundaryType */,
+ int* /* startOffset */, int* /* endOffset */) const
+{
+ SAL_INFO("vcl.qt5", "Unsupported QAccessibleTextInterface::textAfterOffset");
+ return QString();
+}
+QString Qt5AccessibleWidget::textAtOffset(int /* offset */,
+ QAccessible::TextBoundaryType /* boundaryType */,
+ int* /* startOffset */, int* /* endOffset */) const
+{
+ SAL_INFO("vcl.qt5", "Unsupported QAccessibleTextInterface::textAtOffset");
+ return QString();
+}
+QString Qt5AccessibleWidget::textBeforeOffset(int /* offset */,
+ QAccessible::TextBoundaryType /* boundaryType */,
+ int* /* startOffset */, int* /* endOffset */) const
+{
+ SAL_INFO("vcl.qt5", "Unsupported QAccessibleTextInterface::textBeforeOffset");
+ return QString();
+}
+
+// QAccessibleEditableTextInterface
+
+void Qt5AccessibleWidget::deleteText(int startOffset, int endOffset)
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return;
+
+ Reference<XAccessibleEditableText> xEditableText(xAc, UNO_QUERY);
+ if (!xEditableText.is())
+ return;
+ xEditableText->deleteText(startOffset, endOffset);
+}
+
+void Qt5AccessibleWidget::insertText(int offset, const QString& text)
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return;
+
+ Reference<XAccessibleEditableText> xEditableText(xAc, UNO_QUERY);
+ if (!xEditableText.is())
+ return;
+ xEditableText->insertText(toOUString(text), offset);
+}
+
+void Qt5AccessibleWidget::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;
+ xEditableText->replaceText(startOffset, endOffset, toOUString(text));
+}
+
+// QAccessibleValueInterface
+QVariant Qt5AccessibleWidget::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 Qt5AccessibleWidget::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 Qt5AccessibleWidget::minimumStepSize() const { return QVariant(); }
+QVariant Qt5AccessibleWidget::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 Qt5AccessibleWidget::setCurrentValue(const QVariant& value)
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return;
+
+ Reference<XAccessibleValue> xValue(xAc, UNO_QUERY);
+ if (!xValue.is())
+ return;
+ xValue->setCurrentValue(Any(value.toDouble()));
+}
+
+// QAccessibleTable
+QAccessibleInterface* Qt5AccessibleWidget::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(
+ new Qt5XAccessible(xTable->getAccessibleCaption()));
+}
+
+QAccessibleInterface* Qt5AccessibleWidget::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;
+ return QAccessible::queryAccessibleInterface(
+ new Qt5XAccessible(xTable->getAccessibleCellAt(row, column)));
+}
+
+int Qt5AccessibleWidget::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 Qt5AccessibleWidget::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 Qt5AccessibleWidget::isColumnSelected(int /* column */) const { return true; }
+
+bool Qt5AccessibleWidget::isRowSelected(int /* row */) const { return true; }
+
+void Qt5AccessibleWidget::modelChange(QAccessibleTableModelChangeEvent*) {}
+
+int Qt5AccessibleWidget::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 Qt5AccessibleWidget::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 Qt5AccessibleWidget::selectColumn(int column)
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return false;
+
+ Reference<XAccessibleTableSelection> xTableSelection(xAc, UNO_QUERY);
+ if (!xTableSelection.is())
+ return false;
+ return xTableSelection->selectColumn(column);
+}
+
+bool Qt5AccessibleWidget::selectRow(int row)
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return false;
+
+ Reference<XAccessibleTableSelection> xTableSelection(xAc, UNO_QUERY);
+ if (!xTableSelection.is())
+ return false;
+ return xTableSelection->selectRow(row);
+}
+
+int Qt5AccessibleWidget::selectedCellCount() const
+{
+ SAL_INFO("vcl.qt5", "Unsupported QAccessibleTableInterface::selectedCellCount");
+ return 0;
+}
+
+QList<QAccessibleInterface*> Qt5AccessibleWidget::selectedCells() const
+{
+ SAL_INFO("vcl.qt5", "Unsupported QAccessibleTableInterface::selectedCells");
+ return QList<QAccessibleInterface*>();
+}
+
+int Qt5AccessibleWidget::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> Qt5AccessibleWidget::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 Qt5AccessibleWidget::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> Qt5AccessibleWidget::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* Qt5AccessibleWidget::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(
+ new Qt5XAccessible(xTable->getAccessibleSummary()));
+}
+
+bool Qt5AccessibleWidget::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 Qt5AccessibleWidget::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);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5Bitmap.cxx b/vcl/qt5/Qt5Bitmap.cxx
new file mode 100644
index 000000000..01c6ebc4d
--- /dev/null
+++ b/vcl/qt5/Qt5Bitmap.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <Qt5Bitmap.hxx>
+#include <Qt5Tools.hxx>
+#include <Qt5Graphics.hxx>
+
+#include <QtGui/QImage>
+#include <QtCore/QVector>
+#include <QtGui/QColor>
+
+#include <o3tl/safeint.hxx>
+#include <sal/log.hxx>
+#include <tools/helpers.hxx>
+
+Qt5Bitmap::Qt5Bitmap() {}
+
+Qt5Bitmap::Qt5Bitmap(const QImage& rImage) { m_pImage.reset(new QImage(rImage)); }
+
+bool Qt5Bitmap::Create(const Size& rSize, sal_uInt16 nBitCount, const BitmapPalette& rPal)
+{
+ assert(
+ (nBitCount == 1 || nBitCount == 4 || nBitCount == 8 || nBitCount == 24 || nBitCount == 32)
+ && "Unsupported BitCount!");
+
+ if (nBitCount == 1)
+ assert(2 >= rPal.GetEntryCount());
+ if (nBitCount == 4)
+ assert(16 >= rPal.GetEntryCount());
+ if (nBitCount == 8)
+ assert(256 >= rPal.GetEntryCount());
+
+ if (nBitCount == 4)
+ {
+ m_pImage.reset();
+ m_aSize = rSize;
+ bool bFail = o3tl::checked_multiply<sal_uInt32>(rSize.Width(), nBitCount, m_nScanline);
+ if (bFail)
+ {
+ SAL_WARN("vcl.gdi", "checked multiply failed");
+ return false;
+ }
+ m_nScanline = AlignedWidth4Bytes(m_nScanline);
+ sal_uInt8* pBuffer = nullptr;
+ if (0 != m_nScanline && 0 != rSize.Height())
+ pBuffer = new sal_uInt8[m_nScanline * rSize.Height()];
+ m_pBuffer.reset(pBuffer);
+ }
+ else
+ {
+ m_pImage.reset(new QImage(toQSize(rSize), getBitFormat(nBitCount)));
+ m_pImage->fill(Qt::transparent);
+ m_pBuffer.reset();
+ }
+ m_aPalette = rPal;
+
+ auto count = rPal.GetEntryCount();
+ if (nBitCount != 4 && 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(aColorTable);
+ }
+ return true;
+}
+
+bool Qt5Bitmap::Create(const SalBitmap& rSalBmp)
+{
+ const Qt5Bitmap* pBitmap = static_cast<const Qt5Bitmap*>(&rSalBmp);
+ if (pBitmap->m_pImage)
+ {
+ m_pImage.reset(new QImage(*pBitmap->m_pImage));
+ m_pBuffer.reset();
+ }
+ else
+ {
+ m_aSize = pBitmap->m_aSize;
+ m_nScanline = pBitmap->m_nScanline;
+ sal_uInt8* pBuffer = nullptr;
+ if (0 != m_nScanline && 0 != m_aSize.Height())
+ {
+ sal_uInt32 nSize = m_nScanline * m_aSize.Height();
+ pBuffer = new sal_uInt8[nSize];
+ memcpy(pBuffer, pBitmap->m_pBuffer.get(), nSize);
+ }
+ m_pBuffer.reset(pBuffer);
+ m_pImage.reset();
+ }
+ m_aPalette = pBitmap->m_aPalette;
+ return true;
+}
+
+bool Qt5Bitmap::Create(const SalBitmap& rSalBmp, SalGraphics* pSalGraphics)
+{
+ const Qt5Bitmap* pBitmap = static_cast<const Qt5Bitmap*>(&rSalBmp);
+ Qt5Graphics* pGraphics = static_cast<Qt5Graphics*>(pSalGraphics);
+ QImage* pImage = pGraphics->m_pQImage;
+ m_pImage.reset(new QImage(pBitmap->m_pImage->convertToFormat(pImage->format())));
+ m_pBuffer.reset();
+ return true;
+}
+
+bool Qt5Bitmap::Create(const SalBitmap& rSalBmp, sal_uInt16 nNewBitCount)
+{
+ assert((nNewBitCount == 1 || nNewBitCount == 4 || nNewBitCount == 8 || nNewBitCount == 24
+ || nNewBitCount == 32)
+ && "Unsupported BitCount!");
+
+ const Qt5Bitmap* pBitmap = static_cast<const Qt5Bitmap*>(&rSalBmp);
+ if (pBitmap->m_pBuffer)
+ {
+ if (nNewBitCount != 32)
+ return false;
+
+ // convert 4bit indexed palette to 32bit ARGB
+ m_pImage.reset(new QImage(pBitmap->m_aSize.Width(), pBitmap->m_aSize.Height(),
+ getBitFormat(nNewBitCount)));
+ m_pImage->fill(Qt::transparent);
+
+ // prepare a whole palette
+ const BitmapPalette& rPal = pBitmap->m_aPalette;
+ QVector<QRgb> colorTable(16);
+ int i = 0, maxEntry = pBitmap->m_aPalette.GetEntryCount();
+ assert(maxEntry <= 16 && maxEntry >= 0);
+ for (; i < maxEntry; ++i)
+ colorTable[i] = qRgb(rPal[i].GetRed(), rPal[i].GetGreen(), rPal[i].GetBlue());
+ for (; i < 16; ++i)
+ colorTable[i] = qRgb(0, 0, 0);
+
+ sal_uInt32* image_data = reinterpret_cast<sal_uInt32*>(m_pImage->bits());
+ sal_uInt8* buffer_data_pos = pBitmap->m_pBuffer.get();
+ sal_uInt32 nWidth = pBitmap->m_aSize.Height() / 2;
+ bool isOdd(0 != pBitmap->m_aSize.Height() % 2);
+
+ for (long h = 0; h < pBitmap->m_aSize.Height(); ++h)
+ {
+ sal_uInt8* buffer_data = buffer_data_pos;
+ buffer_data_pos += pBitmap->m_nScanline;
+ for (sal_uInt32 w = 0; w < nWidth; ++w)
+ {
+ *image_data = static_cast<sal_uInt32>(colorTable.at(*buffer_data >> 4));
+ ++image_data;
+ *image_data = static_cast<sal_uInt32>(colorTable.at(*buffer_data & 0xF));
+ ++image_data;
+ ++buffer_data;
+ }
+ if (isOdd)
+ {
+ *image_data = static_cast<sal_uInt32>(colorTable.at(*buffer_data >> 4));
+ ++image_data;
+ }
+ }
+ }
+ else
+ m_pImage.reset(new QImage(pBitmap->m_pImage->convertToFormat(getBitFormat(nNewBitCount))));
+ m_pBuffer.reset();
+ return true;
+}
+
+bool Qt5Bitmap::Create(const css::uno::Reference<css::rendering::XBitmapCanvas>& /*rBitmapCanvas*/,
+ Size& /*rSize*/, bool /*bMask*/)
+{
+ return false;
+}
+
+void Qt5Bitmap::Destroy()
+{
+ m_pImage.reset();
+ m_pBuffer.reset();
+}
+
+Size Qt5Bitmap::GetSize() const
+{
+ if (m_pBuffer)
+ return m_aSize;
+ else if (m_pImage)
+ return toSize(m_pImage->size());
+ return Size();
+}
+
+sal_uInt16 Qt5Bitmap::GetBitCount() const
+{
+ if (m_pBuffer)
+ return 4;
+ else if (m_pImage)
+ return getFormatBits(m_pImage->format());
+ return 0;
+}
+
+BitmapBuffer* Qt5Bitmap::AcquireBuffer(BitmapAccessMode /*nMode*/)
+{
+ static const BitmapPalette aEmptyPalette;
+
+ if (!(m_pImage || m_pBuffer))
+ return nullptr;
+
+ BitmapBuffer* pBuffer = new BitmapBuffer;
+
+ if (m_pBuffer)
+ {
+ pBuffer->mnWidth = m_aSize.Width();
+ pBuffer->mnHeight = m_aSize.Height();
+ pBuffer->mnBitCount = 4;
+ pBuffer->mpBits = m_pBuffer.get();
+ pBuffer->mnScanlineSize = m_nScanline;
+ }
+ else
+ {
+ 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 4:
+ pBuffer->mnFormat = ScanlineFormat::N4BitMsnPal | 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 Qt5Bitmap::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(aColorTable);
+ }
+ delete pBuffer;
+ if (nMode == BitmapAccessMode::Write)
+ InvalidateChecksum();
+}
+
+bool Qt5Bitmap::GetSystemData(BitmapSystemData& /*rData*/) { return false; }
+
+bool Qt5Bitmap::ScalingSupported() const { return false; }
+
+bool Qt5Bitmap::Scale(const double& /*rScaleX*/, const double& /*rScaleY*/,
+ BmpScaleFlag /*nScaleFlag*/)
+{
+ return false;
+}
+
+bool Qt5Bitmap::Replace(const Color& /*rSearchColor*/, const Color& /*rReplaceColor*/,
+ sal_uInt8 /*nTol*/)
+{
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5Clipboard.cxx b/vcl/qt5/Qt5Clipboard.cxx
new file mode 100644
index 000000000..8720cfe44
--- /dev/null
+++ b/vcl/qt5/Qt5Clipboard.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/.
+ *
+ */
+
+#include <Qt5Clipboard.hxx>
+#include <Qt5Clipboard.moc>
+
+#include <cppuhelper/supportsservice.hxx>
+#include <sal/log.hxx>
+
+#include <QtWidgets/QApplication>
+
+#include <Qt5Instance.hxx>
+#include <Qt5Transferable.hxx>
+#include <Qt5Tools.hxx>
+
+#include <cassert>
+#include <map>
+
+Qt5Clipboard::Qt5Clipboard(const OUString& aModeString, const QClipboard::Mode aMode)
+ : cppu::WeakComponentImplHelper<css::datatransfer::clipboard::XSystemClipboard,
+ css::datatransfer::clipboard::XFlushableClipboard,
+ XServiceInfo>(m_aMutex)
+ , m_aClipboardName(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, &Qt5Clipboard::handleChanged,
+ Qt::DirectConnection);
+
+ // explicitly queue an event, so we can eventually ignore it
+ connect(this, &Qt5Clipboard::clearClipboard, this, &Qt5Clipboard::handleClearClipboard,
+ Qt::QueuedConnection);
+}
+
+css::uno::Reference<css::uno::XInterface> Qt5Clipboard::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 static_cast<cppu::OWeakObject*>(new Qt5Clipboard(aModeString, iter->second));
+ SAL_WARN("vcl.qt5", "Ignoring unrecognized clipboard type: '" << aModeString << "'");
+ return css::uno::Reference<css::uno::XInterface>();
+}
+
+void Qt5Clipboard::flushClipboard()
+{
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ SolarMutexGuard g;
+ pSalInst->RunInMainThread([&, this]() {
+ if (!isOwner(m_aClipboardMode))
+ return;
+
+ QClipboard* pClipboard = QApplication::clipboard();
+ const Qt5MimeData* pQt5MimeData
+ = dynamic_cast<const Qt5MimeData*>(pClipboard->mimeData(m_aClipboardMode));
+ assert(pQt5MimeData);
+
+ QMimeData* pMimeCopy = nullptr;
+ if (pQt5MimeData && pQt5MimeData->deepCopy(&pMimeCopy))
+ {
+ m_bOwnClipboardChange = true;
+ pClipboard->setMimeData(pMimeCopy, m_aClipboardMode);
+ m_bOwnClipboardChange = false;
+ }
+ });
+}
+
+css::uno::Reference<css::datatransfer::XTransferable> Qt5Clipboard::getContents()
+{
+ 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 Qt5ClipboardTransferable
+ const QMimeData* pMimeData = QApplication::clipboard()->mimeData(m_aClipboardMode);
+ if (m_aContents.is())
+ {
+ const auto* pTrans = dynamic_cast<Qt5ClipboardTransferable*>(m_aContents.get());
+ assert(pTrans);
+ if (pTrans && pTrans->mimeData() == pMimeData)
+ return m_aContents;
+ }
+
+ m_aContents = new Qt5ClipboardTransferable(m_aClipboardMode, pMimeData);
+ return m_aContents;
+}
+
+void Qt5Clipboard::handleClearClipboard()
+{
+ if (!m_bDoClear)
+ return;
+ QApplication::clipboard()->clear(m_aClipboardMode);
+}
+
+void Qt5Clipboard::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 Qt5MimeData(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 Qt5Clipboard::handleChanged(QClipboard::Mode aMode)
+{
+ if (aMode != m_aClipboardMode)
+ return;
+
+ osl::ClearableMutexGuard aGuard(m_aMutex);
+
+ 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 Qt5Clipboard::getImplementationName() { return "com.sun.star.datatransfer.Qt5Clipboard"; }
+
+css::uno::Sequence<OUString> Qt5Clipboard::getSupportedServiceNames()
+{
+ return { "com.sun.star.datatransfer.clipboard.SystemClipboard" };
+}
+
+sal_Bool Qt5Clipboard::supportsService(const OUString& ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+OUString Qt5Clipboard::getName() { return m_aClipboardName; }
+
+sal_Int8 Qt5Clipboard::getRenderingCapabilities() { return 0; }
+
+void Qt5Clipboard::addClipboardListener(
+ const css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>& listener)
+{
+ osl::MutexGuard aGuard(m_aMutex);
+ m_aListeners.push_back(listener);
+}
+
+void Qt5Clipboard::removeClipboardListener(
+ const css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>& listener)
+{
+ osl::MutexGuard aGuard(m_aMutex);
+ m_aListeners.erase(std::remove(m_aListeners.begin(), m_aListeners.end(), listener),
+ m_aListeners.end());
+}
+
+bool Qt5Clipboard::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 Qt5Clipboard::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/Qt5Data.cxx b/vcl/qt5/Qt5Data.cxx
new file mode 100644
index 000000000..c50f8c57d
--- /dev/null
+++ b/vcl/qt5/Qt5Data.cxx
@@ -0,0 +1,334 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <Qt5Data.hxx>
+
+#include <QtGui/QBitmap>
+#include <QtGui/QCursor>
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QStyle>
+
+#include <sal/log.hxx>
+
+#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/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/chart_curs.h>
+#include <unx/x11_cursors/chart_mask.h>
+#include <unx/x11_cursors/copydata_curs.h>
+#include <unx/x11_cursors/copydata_mask.h>
+#include <unx/x11_cursors/copydlnk_curs.h>
+#include <unx/x11_cursors/copydlnk_mask.h>
+#include <unx/x11_cursors/copyfile_curs.h>
+#include <unx/x11_cursors/copyfile_mask.h>
+#include <unx/x11_cursors/copyfiles_curs.h>
+#include <unx/x11_cursors/copyfiles_mask.h>
+#include <unx/x11_cursors/copyflnk_curs.h>
+#include <unx/x11_cursors/copyflnk_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/detective_curs.h>
+#include <unx/x11_cursors/detective_mask.h>
+#include <unx/x11_cursors/drawarc_curs.h>
+#include <unx/x11_cursors/drawarc_mask.h>
+#include <unx/x11_cursors/drawbezier_curs.h>
+#include <unx/x11_cursors/drawbezier_mask.h>
+#include <unx/x11_cursors/drawcaption_curs.h>
+#include <unx/x11_cursors/drawcaption_mask.h>
+#include <unx/x11_cursors/drawcirclecut_curs.h>
+#include <unx/x11_cursors/drawcirclecut_mask.h>
+#include <unx/x11_cursors/drawconnect_curs.h>
+#include <unx/x11_cursors/drawconnect_mask.h>
+#include <unx/x11_cursors/drawellipse_curs.h>
+#include <unx/x11_cursors/drawellipse_mask.h>
+#include <unx/x11_cursors/drawfreehand_curs.h>
+#include <unx/x11_cursors/drawfreehand_mask.h>
+#include <unx/x11_cursors/drawline_curs.h>
+#include <unx/x11_cursors/drawline_mask.h>
+#include <unx/x11_cursors/drawpie_curs.h>
+#include <unx/x11_cursors/drawpie_mask.h>
+#include <unx/x11_cursors/drawpolygon_curs.h>
+#include <unx/x11_cursors/drawpolygon_mask.h>
+#include <unx/x11_cursors/drawrect_curs.h>
+#include <unx/x11_cursors/drawrect_mask.h>
+#include <unx/x11_cursors/drawtext_curs.h>
+#include <unx/x11_cursors/drawtext_mask.h>
+#include <unx/x11_cursors/fill_curs.h>
+#include <unx/x11_cursors/fill_mask.h>
+#include <unx/x11_cursors/hshear_curs.h>
+#include <unx/x11_cursors/hshear_mask.h>
+#include <unx/x11_cursors/linkdata_curs.h>
+#include <unx/x11_cursors/linkdata_mask.h>
+#include <unx/x11_cursors/linkfile_curs.h>
+#include <unx/x11_cursors/linkfile_mask.h>
+#include <unx/x11_cursors/magnify_curs.h>
+#include <unx/x11_cursors/magnify_mask.h>
+#include <unx/x11_cursors/mirror_curs.h>
+#include <unx/x11_cursors/mirror_mask.h>
+#include <unx/x11_cursors/movebezierweight_curs.h>
+#include <unx/x11_cursors/movebezierweight_mask.h>
+#include <unx/x11_cursors/movedata_curs.h>
+#include <unx/x11_cursors/movedata_mask.h>
+#include <unx/x11_cursors/movedlnk_curs.h>
+#include <unx/x11_cursors/movedlnk_mask.h>
+#include <unx/x11_cursors/movefile_curs.h>
+#include <unx/x11_cursors/movefile_mask.h>
+#include <unx/x11_cursors/movefiles_curs.h>
+#include <unx/x11_cursors/movefiles_mask.h>
+#include <unx/x11_cursors/moveflnk_curs.h>
+#include <unx/x11_cursors/moveflnk_mask.h>
+#include <unx/x11_cursors/movepoint_curs.h>
+#include <unx/x11_cursors/movepoint_mask.h>
+#include <unx/x11_cursors/nodrop_curs.h>
+#include <unx/x11_cursors/nodrop_mask.h>
+#include <unx/x11_cursors/pivotcol_curs.h>
+#include <unx/x11_cursors/pivotcol_mask.h>
+#include <unx/x11_cursors/pivotdel_curs.h>
+#include <unx/x11_cursors/pivotdel_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/rotate_curs.h>
+#include <unx/x11_cursors/rotate_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/tblselsw_curs.h>
+#include <unx/x11_cursors/tblselsw_mask.h>
+#include <unx/x11_cursors/tblselw_curs.h>
+#include <unx/x11_cursors/tblselw_mask.h>
+#include <unx/x11_cursors/vertcurs_curs.h>
+#include <unx/x11_cursors/vertcurs_mask.h>
+#include <unx/x11_cursors/vshear_curs.h>
+#include <unx/x11_cursors/vshear_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/glyphcache.hxx>
+
+Qt5Data::Qt5Data(SalInstance* pInstance)
+ : GenericUnixSalData(SAL_DATA_QT5, pInstance)
+{
+ 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
+Qt5Data::~Qt5Data() {}
+
+static QCursor* getQCursorFromXBM(const unsigned char* pBitmap, const unsigned char* pMask,
+ int nWidth, int nHeight, int nXHot, int nYHot)
+{
+ QBitmap aPixmap = QBitmap::fromData(QSize(nWidth, nHeight), pBitmap);
+ QBitmap aMask = QBitmap::fromData(QSize(nWidth, nHeight), pMask);
+ return new QCursor(aPixmap, aMask, nXHot, nYHot);
+}
+
+#define MAKE_CURSOR(vcl_name, name) \
+ case vcl_name: \
+ pCursor = getQCursorFromXBM(name##curs##_bits, name##mask##_bits, name##curs_width, \
+ name##curs_height, 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& Qt5Data::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::OpenHandCursor);
+ 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_);
+ MAKE_CURSOR(PointerStyle::Fill, fill_);
+ MAKE_CURSOR(PointerStyle::MoveData, movedata_);
+ MAKE_CURSOR(PointerStyle::CopyData, copydata_);
+ MAKE_CURSOR(PointerStyle::MoveFile, movefile_);
+ MAKE_CURSOR(PointerStyle::CopyFile, copyfile_);
+ MAKE_CURSOR(PointerStyle::MoveFiles, movefiles_);
+ MAKE_CURSOR(PointerStyle::CopyFiles, copyfiles_);
+ MAKE_CURSOR(PointerStyle::NotAllowed, nodrop_);
+ MAKE_CURSOR(PointerStyle::Rotate, rotate_);
+ MAKE_CURSOR(PointerStyle::HShear, hshear_);
+ MAKE_CURSOR(PointerStyle::VShear, vshear_);
+ MAKE_CURSOR(PointerStyle::DrawLine, drawline_);
+ MAKE_CURSOR(PointerStyle::DrawRect, drawrect_);
+ MAKE_CURSOR(PointerStyle::DrawPolygon, drawpolygon_);
+ MAKE_CURSOR(PointerStyle::DrawBezier, drawbezier_);
+ MAKE_CURSOR(PointerStyle::DrawArc, drawarc_);
+ MAKE_CURSOR(PointerStyle::DrawPie, drawpie_);
+ MAKE_CURSOR(PointerStyle::DrawCircleCut, drawcirclecut_);
+ MAKE_CURSOR(PointerStyle::DrawEllipse, drawellipse_);
+ MAKE_CURSOR(PointerStyle::DrawConnect, drawconnect_);
+ MAKE_CURSOR(PointerStyle::DrawText, drawtext_);
+ MAKE_CURSOR(PointerStyle::Mirror, mirror_);
+ MAKE_CURSOR(PointerStyle::Crook, crook_);
+ MAKE_CURSOR(PointerStyle::Crop, crop_);
+ MAKE_CURSOR(PointerStyle::MovePoint, movepoint_);
+ MAKE_CURSOR(PointerStyle::MoveBezierWeight, movebezierweight_);
+ MAKE_CURSOR(PointerStyle::DrawFreehand, drawfreehand_);
+ MAKE_CURSOR(PointerStyle::DrawCaption, drawcaption_);
+ MAKE_CURSOR(PointerStyle::LinkData, linkdata_);
+ MAKE_CURSOR(PointerStyle::MoveDataLink, movedlnk_);
+ MAKE_CURSOR(PointerStyle::CopyDataLink, copydlnk_);
+ MAKE_CURSOR(PointerStyle::LinkFile, linkfile_);
+ MAKE_CURSOR(PointerStyle::MoveFileLink, moveflnk_);
+ MAKE_CURSOR(PointerStyle::CopyFileLink, copyflnk_);
+ MAKE_CURSOR(PointerStyle::Chart, chart_);
+ MAKE_CURSOR(PointerStyle::Detective, detective_);
+ MAKE_CURSOR(PointerStyle::PivotCol, pivotcol_);
+ MAKE_CURSOR(PointerStyle::PivotRow, pivotrow_);
+ MAKE_CURSOR(PointerStyle::PivotField, pivotfld_);
+ MAKE_CURSOR(PointerStyle::PivotDelete, pivotdel_);
+ MAKE_CURSOR(PointerStyle::Chain, chain_);
+ MAKE_CURSOR(PointerStyle::ChainNotAllowed, chainnot_);
+ MAKE_CURSOR(PointerStyle::AutoScrollN, asn_);
+ MAKE_CURSOR(PointerStyle::AutoScrollS, ass_);
+ MAKE_CURSOR(PointerStyle::AutoScrollW, asw_);
+ MAKE_CURSOR(PointerStyle::AutoScrollE, ase_);
+ MAKE_CURSOR(PointerStyle::AutoScrollNW, asnw_);
+ MAKE_CURSOR(PointerStyle::AutoScrollNE, asne_);
+ MAKE_CURSOR(PointerStyle::AutoScrollSW, assw_);
+ MAKE_CURSOR(PointerStyle::AutoScrollSE, asse_);
+ MAKE_CURSOR(PointerStyle::AutoScrollNS, asns_);
+ MAKE_CURSOR(PointerStyle::AutoScrollWE, aswe_);
+ MAKE_CURSOR(PointerStyle::AutoScrollNSWE, asnswe_);
+ MAKE_CURSOR(PointerStyle::TextVertical, vertcurs_);
+
+ MAKE_CURSOR(PointerStyle::TabSelectS, tblsels_);
+ MAKE_CURSOR(PointerStyle::TabSelectE, tblsele_);
+ MAKE_CURSOR(PointerStyle::TabSelectSE, tblselse_);
+ MAKE_CURSOR(PointerStyle::TabSelectW, tblselw_);
+ MAKE_CURSOR(PointerStyle::TabSelectSW, tblselsw_);
+
+ MAKE_CURSOR(PointerStyle::HideWhitespace, hidewhitespace_);
+ MAKE_CURSOR(PointerStyle::ShowWhitespace, showwhitespace_);
+ default:
+ break;
+ }
+ if (!pCursor)
+ {
+ pCursor = new QCursor(Qt::ArrowCursor);
+ SAL_WARN("vcl.qt5",
+ "pointer " << static_cast<int>(ePointerStyle) << " not implemented");
+ }
+
+ m_aCursors[ePointerStyle].reset(pCursor);
+ }
+
+ return *m_aCursors[ePointerStyle];
+}
+
+void Qt5Data::ErrorTrapPush() {}
+
+bool Qt5Data::ErrorTrapPop(bool /*bIgnoreError*/) { return false; }
+
+bool Qt5Data::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/Qt5DragAndDrop.cxx b/vcl/qt5/Qt5DragAndDrop.cxx
new file mode 100644
index 000000000..615b5d1f7
--- /dev/null
+++ b/vcl/qt5/Qt5DragAndDrop.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/.
+ *
+ */
+
+#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 <Qt5DragAndDrop.hxx>
+#include <Qt5Frame.hxx>
+#include <Qt5Transferable.hxx>
+#include <Qt5Widget.hxx>
+
+#include <QtGui/QDrag>
+
+using namespace com::sun::star;
+
+Qt5DragSource::~Qt5DragSource() {}
+
+void Qt5DragSource::deinitialize() { m_pFrame = nullptr; }
+
+sal_Bool Qt5DragSource::isDragImageSupported() { return true; }
+
+sal_Int32 Qt5DragSource::getDefaultCursor(sal_Int8) { return 0; }
+
+void Qt5DragSource::initialize(const css::uno::Sequence<css::uno::Any>& rArguments)
+{
+ if (rArguments.getLength() < 2)
+ {
+ throw uno::RuntimeException("DragSource::initialize: Cannot install window event handler",
+ static_cast<OWeakObject*>(this));
+ }
+
+ sal_IntPtr nFrame = 0;
+ rArguments.getConstArray()[1] >>= nFrame;
+
+ if (!nFrame)
+ {
+ throw uno::RuntimeException("DragSource::initialize: missing SalFrame",
+ static_cast<OWeakObject*>(this));
+ }
+
+ m_pFrame = reinterpret_cast<Qt5Frame*>(nFrame);
+ m_pFrame->registerDragSource(this);
+}
+
+void Qt5DragSource::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 Qt5MimeData(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 Qt5DragSource::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 Qt5DragSource::getImplementationName()
+{
+ return "com.sun.star.datatransfer.dnd.VclQt5DragSource";
+}
+
+sal_Bool SAL_CALL Qt5DragSource::supportsService(OUString const& ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence<OUString> SAL_CALL Qt5DragSource::getSupportedServiceNames()
+{
+ return { "com.sun.star.datatransfer.dnd.Qt5DragSource" };
+}
+
+Qt5DropTarget::Qt5DropTarget()
+ : WeakComponentImplHelper(m_aMutex)
+ , m_pFrame(nullptr)
+ , m_bActive(false)
+ , m_nDefaultActions(0)
+{
+}
+
+OUString SAL_CALL Qt5DropTarget::getImplementationName()
+{
+ return "com.sun.star.datatransfer.dnd.VclQt5DropTarget";
+}
+
+sal_Bool SAL_CALL Qt5DropTarget::supportsService(OUString const& ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence<OUString> SAL_CALL Qt5DropTarget::getSupportedServiceNames()
+{
+ return { "com.sun.star.datatransfer.dnd.Qt5DropTarget" };
+}
+
+Qt5DropTarget::~Qt5DropTarget() {}
+
+void Qt5DropTarget::deinitialize()
+{
+ m_pFrame = nullptr;
+ m_bActive = false;
+}
+
+void Qt5DropTarget::initialize(const uno::Sequence<uno::Any>& rArguments)
+{
+ if (rArguments.getLength() < 2)
+ {
+ throw uno::RuntimeException("DropTarget::initialize: Cannot install window event handler",
+ static_cast<OWeakObject*>(this));
+ }
+
+ sal_IntPtr nFrame = 0;
+ rArguments.getConstArray()[1] >>= nFrame;
+
+ if (!nFrame)
+ {
+ throw uno::RuntimeException("DropTarget::initialize: missing SalFrame",
+ static_cast<OWeakObject*>(this));
+ }
+
+ m_nDropAction = datatransfer::dnd::DNDConstants::ACTION_NONE;
+
+ m_pFrame = reinterpret_cast<Qt5Frame*>(nFrame);
+ m_pFrame->registerDropTarget(this);
+ m_bActive = true;
+}
+
+void Qt5DropTarget::addDropTargetListener(
+ const uno::Reference<css::datatransfer::dnd::XDropTargetListener>& xListener)
+{
+ ::osl::Guard<::osl::Mutex> aGuard(m_aMutex);
+
+ m_aListeners.push_back(xListener);
+}
+
+void Qt5DropTarget::removeDropTargetListener(
+ const uno::Reference<css::datatransfer::dnd::XDropTargetListener>& xListener)
+{
+ ::osl::Guard<::osl::Mutex> aGuard(m_aMutex);
+
+ m_aListeners.erase(std::remove(m_aListeners.begin(), m_aListeners.end(), xListener),
+ m_aListeners.end());
+}
+
+sal_Bool Qt5DropTarget::isActive() { return m_bActive; }
+
+void Qt5DropTarget::setActive(sal_Bool bActive) { m_bActive = bActive; }
+
+sal_Int8 Qt5DropTarget::getDefaultActions() { return m_nDefaultActions; }
+
+void Qt5DropTarget::setDefaultActions(sal_Int8 nDefaultActions)
+{
+ m_nDefaultActions = nDefaultActions;
+}
+
+void Qt5DropTarget::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 Qt5DropTarget::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 Qt5DropTarget::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 Qt5DropTarget::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 Qt5DropTarget::acceptDrag(sal_Int8 dragOperation) { m_nDropAction = dragOperation; }
+
+void Qt5DropTarget::rejectDrag() { m_nDropAction = 0; }
+
+void Qt5DropTarget::acceptDrop(sal_Int8 dropOperation) { m_nDropAction = dropOperation; }
+
+void Qt5DropTarget::rejectDrop() { m_nDropAction = 0; }
+
+void Qt5DropTarget::dropComplete(sal_Bool success)
+{
+ m_bDropSuccessful = (m_bDropSuccessful && success);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5FilePicker.cxx b/vcl/qt5/Qt5FilePicker.cxx
new file mode 100644
index 000000000..d648a5d94
--- /dev/null
+++ b/vcl/qt5/Qt5FilePicker.cxx
@@ -0,0 +1,945 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <Qt5FilePicker.hxx>
+#include <Qt5FilePicker.moc>
+
+#include <Qt5Frame.hxx>
+#include <Qt5Tools.hxx>
+#include <Qt5Widget.hxx>
+#include <Qt5Instance.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 <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;
+
+namespace
+{
+uno::Sequence<OUString> FilePicker_getSupportedServiceNames()
+{
+ return { "com.sun.star.ui.dialogs.FilePicker", "com.sun.star.ui.dialogs.SystemFilePicker",
+ "com.sun.star.ui.dialogs.Qt5FilePicker" };
+}
+}
+
+Qt5FilePicker::Qt5FilePicker(css::uno::Reference<css::uno::XComponentContext> const& context,
+ QFileDialog::FileMode eMode, bool bUseNative)
+ : Qt5FilePicker_Base(m_aHelperMutex)
+ , m_context(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(VclResId(STR_FPICKER_FOLDER_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()));
+}
+
+Qt5FilePicker::~Qt5FilePicker()
+{
+ SolarMutexGuard g;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ assert(pSalInst);
+ pSalInst->RunInMainThread([this]() {
+ // must delete it in main thread, otherwise
+ // QSocketNotifier::setEnabled() will crash us
+ m_pFileDialog.reset();
+ });
+}
+
+void SAL_CALL
+Qt5FilePicker::addFilePickerListener(const uno::Reference<XFilePickerListener>& xListener)
+{
+ SolarMutexGuard aGuard;
+ m_xListener = xListener;
+}
+
+void SAL_CALL Qt5FilePicker::removeFilePickerListener(const uno::Reference<XFilePickerListener>&)
+{
+ SolarMutexGuard aGuard;
+ m_xListener.clear();
+}
+
+void SAL_CALL Qt5FilePicker::setTitle(const OUString& title)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ assert(pSalInst);
+ pSalInst->RunInMainThread(
+ [this, &title]() { m_pFileDialog->setWindowTitle(toQString(title)); });
+}
+
+sal_Int16 SAL_CALL Qt5FilePicker::execute()
+{
+ SolarMutexGuard g;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ assert(pSalInst);
+ if (!pSalInst->IsMainThread())
+ {
+ sal_uInt16 ret;
+ pSalInst->RunInMainThread([&ret, this]() { ret = execute(); });
+ return ret;
+ }
+
+ QWidget* pTransientParent = m_pParentWidget;
+ if (!pTransientParent)
+ {
+ vcl::Window* pWindow = ::Application::GetActiveTopWindow();
+ if (pWindow)
+ {
+ Qt5Frame* pFrame = dynamic_cast<Qt5Frame*>(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);
+ int result = m_pFileDialog->exec();
+ xDesktop->removeTerminateListener(this);
+ m_pFileDialog->setParent(nullptr, m_pFileDialog->windowFlags());
+
+ if (QFileDialog::Rejected == result)
+ return ExecutableDialogResults::CANCEL;
+ return ExecutableDialogResults::OK;
+}
+
+void SAL_CALL Qt5FilePicker::setMultiSelectionMode(sal_Bool multiSelect)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ 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 Qt5FilePicker::setDefaultName(const OUString& name)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ assert(pSalInst);
+ pSalInst->RunInMainThread([this, &name]() { m_pFileDialog->selectFile(toQString(name)); });
+}
+
+void SAL_CALL Qt5FilePicker::setDisplayDirectory(const OUString& dir)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ assert(pSalInst);
+ pSalInst->RunInMainThread([this, &dir]() {
+ QString qDir(toQString(dir));
+ m_pFileDialog->setDirectoryUrl(QUrl(qDir));
+ });
+}
+
+OUString SAL_CALL Qt5FilePicker::getDisplayDirectory()
+{
+ SolarMutexGuard g;
+ OUString ret;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ assert(pSalInst);
+ pSalInst->RunInMainThread(
+ [&ret, this]() { ret = toOUString(m_pFileDialog->directoryUrl().toString()); });
+ return ret;
+}
+
+uno::Sequence<OUString> SAL_CALL Qt5FilePicker::getFiles()
+{
+ uno::Sequence<OUString> seq = getSelectedFiles();
+ if (seq.getLength() > 1)
+ seq.realloc(1);
+ return seq;
+}
+
+uno::Sequence<OUString> SAL_CALL Qt5FilePicker::getSelectedFiles()
+{
+ SolarMutexGuard g;
+ QList<QUrl> urls;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ assert(pSalInst);
+ pSalInst->RunInMainThread([&urls, this]() { urls = m_pFileDialog->selectedUrls(); });
+
+ uno::Sequence<OUString> seq(urls.size());
+
+ 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.qt5", "cannot convert <" << extUrl << "> from locale encoding to UTF-8");
+ intUrl = extUrl;
+ }
+ seq[i++] = intUrl;
+ }
+
+ return seq;
+}
+
+void SAL_CALL Qt5FilePicker::appendFilter(const OUString& title, const OUString& filter)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ 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 Qt5 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 Qt5FilePicker::setCurrentFilter(const OUString& title)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ assert(pSalInst);
+ pSalInst->RunInMainThread([this, &title]() {
+ m_aCurrentFilter = m_aTitleToFilterMap.value(toQString(title).replace("/", "\\/"));
+ });
+}
+
+OUString SAL_CALL Qt5FilePicker::getCurrentFilter()
+{
+ SolarMutexGuard g;
+ QString filter;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ 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 Qt5FilePicker::appendFilterGroup(const OUString& rGroupTitle,
+ const uno::Sequence<beans::StringPair>& filters)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ 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 Qt5FilePicker::handleGetListValue(const QComboBox* pWidget, sal_Int16 nControlAction)
+{
+ uno::Any aAny;
+ switch (nControlAction)
+ {
+ case ControlActions::GET_ITEMS:
+ {
+ Sequence<OUString> aItemList(pWidget->count());
+ for (sal_Int32 i = 0; i < pWidget->count(); ++i)
+ aItemList[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.qt5",
+ "undocumented/unimplemented ControlAction for a list " << nControlAction);
+ break;
+ }
+ return aAny;
+}
+
+void Qt5FilePicker::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.qt5",
+ "undocumented/unimplemented ControlAction for a list " << nControlAction);
+ break;
+ }
+
+ pWidget->setEnabled(pWidget->count() > 0);
+}
+
+void SAL_CALL Qt5FilePicker::setValue(sal_Int16 controlId, sal_Int16 nControlAction,
+ const uno::Any& value)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ 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.qt5", "set value on unknown control " << controlId);
+}
+
+uno::Any SAL_CALL Qt5FilePicker::getValue(sal_Int16 controlId, sal_Int16 nControlAction)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ 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.qt5", "get value on unknown control " << controlId);
+
+ return res;
+}
+
+void SAL_CALL Qt5FilePicker::enableControl(sal_Int16 controlId, sal_Bool enable)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ assert(pSalInst);
+ pSalInst->RunInMainThread([this, controlId, enable]() {
+ if (m_aCustomWidgetsMap.contains(controlId))
+ m_aCustomWidgetsMap.value(controlId)->setEnabled(enable);
+ else
+ SAL_WARN("vcl.qt5", "enable unknown control " << controlId);
+ });
+}
+
+void SAL_CALL Qt5FilePicker::setLabel(sal_Int16 controlId, const OUString& label)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ 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.qt5", "set label on unknown control " << controlId);
+}
+
+OUString SAL_CALL Qt5FilePicker::getLabel(sal_Int16 controlId)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ 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.qt5", "get label on unknown control " << controlId);
+
+ return toOUString(label);
+}
+
+QString Qt5FilePicker::getResString(const char* pResId)
+{
+ QString aResString;
+
+ if (pResId == nullptr)
+ return aResString;
+
+ aResString = toQString(VclResId(pResId));
+
+ return aResString.replace('~', '&');
+}
+
+void Qt5FilePicker::addCustomControl(sal_Int16 controlId)
+{
+ QWidget* widget = nullptr;
+ QLabel* label = nullptr;
+ const char* resId = nullptr;
+ QCheckBox* pCheckbox = nullptr;
+
+ switch (controlId)
+ {
+ case CHECKBOX_AUTOEXTENSION:
+ resId = STR_FPICKER_AUTO_EXTENSION;
+ break;
+ case CHECKBOX_PASSWORD:
+ resId = STR_FPICKER_PASSWORD;
+ break;
+ case CHECKBOX_FILTEROPTIONS:
+ resId = STR_FPICKER_FILTER_OPTIONS;
+ break;
+ case CHECKBOX_READONLY:
+ resId = STR_FPICKER_READONLY;
+ break;
+ case CHECKBOX_LINK:
+ resId = STR_FPICKER_INSERT_AS_LINK;
+ break;
+ case CHECKBOX_PREVIEW:
+ resId = STR_FPICKER_SHOW_PREVIEW;
+ break;
+ case CHECKBOX_SELECTION:
+ resId = STR_FPICKER_SELECTION;
+ break;
+ case CHECKBOX_GPGENCRYPTION:
+ resId = STR_FPICKER_GPGENCRYPT;
+ break;
+ case PUSHBUTTON_PLAY:
+ resId = STR_FPICKER_PLAY;
+ break;
+ case LISTBOX_VERSION:
+ resId = STR_FPICKER_VERSION;
+ break;
+ case LISTBOX_TEMPLATE:
+ resId = STR_FPICKER_TEMPLATES;
+ break;
+ case LISTBOX_IMAGE_TEMPLATE:
+ resId = STR_FPICKER_IMAGE_TEMPLATE;
+ break;
+ case LISTBOX_IMAGE_ANCHOR:
+ resId = STR_FPICKER_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 Qt5FilePicker::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(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ 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);
+ }
+
+ const char* resId = nullptr;
+ switch (acceptMode)
+ {
+ case QFileDialog::AcceptOpen:
+ resId = STR_FPICKER_OPEN;
+ break;
+ case QFileDialog::AcceptSave:
+ resId = STR_FPICKER_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())
+ {
+ css::uno::Reference<css::awt::XSystemDependentWindowPeer> xSysWinPeer(xParentWindow,
+ css::uno::UNO_QUERY);
+ if (xSysWinPeer.is())
+ {
+ // 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 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 && long(pData->aWindow) == aWindowHandle;
+ });
+ if (it != pFrames.end())
+ m_pParentWidget = static_cast<Qt5Frame*>(*it)->asChild();
+ }
+ }
+}
+
+void SAL_CALL Qt5FilePicker::cancel() { m_pFileDialog->reject(); }
+
+void SAL_CALL Qt5FilePicker::disposing(const lang::EventObject& rEvent)
+{
+ uno::Reference<XFilePickerListener> xFilePickerListener(rEvent.Source, uno::UNO_QUERY);
+
+ if (xFilePickerListener.is())
+ {
+ removeFilePickerListener(xFilePickerListener);
+ }
+}
+
+void SAL_CALL Qt5FilePicker::queryTermination(const css::lang::EventObject&)
+{
+ throw css::frame::TerminationVetoException();
+}
+
+void SAL_CALL Qt5FilePicker::notifyTermination(const css::lang::EventObject&)
+{
+ SolarMutexGuard aGuard;
+ m_pFileDialog->reject();
+}
+
+OUString SAL_CALL Qt5FilePicker::getImplementationName()
+{
+ return "com.sun.star.ui.dialogs.Qt5FilePicker";
+}
+
+sal_Bool SAL_CALL Qt5FilePicker::supportsService(const OUString& ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+uno::Sequence<OUString> SAL_CALL Qt5FilePicker::getSupportedServiceNames()
+{
+ return FilePicker_getSupportedServiceNames();
+}
+
+void Qt5FilePicker::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.qt5",
+ "Unable to retrieve unambiguous file extension. Will not add any automatically.");
+ bSetAutoExtension = false;
+ }
+ }
+
+ if (!bSetAutoExtension)
+ m_pFileDialog->setDefaultSuffix("");
+}
+
+void Qt5FilePicker::filterSelected(const QString&)
+{
+ FilePickerEvent aEvent;
+ aEvent.ElementId = LISTBOX_FILTER;
+ SAL_INFO("vcl.qt5", "filter changed");
+ if (m_xListener.is())
+ m_xListener->controlStateChanged(aEvent);
+}
+
+void Qt5FilePicker::currentChanged(const QString&)
+{
+ FilePickerEvent aEvent;
+ SAL_INFO("vcl.qt5", "file selection changed");
+ if (m_xListener.is())
+ m_xListener->fileSelectionChanged(aEvent);
+}
+
+OUString Qt5FilePicker::getDirectory()
+{
+ uno::Sequence<OUString> seq = getSelectedFiles();
+ if (seq.getLength() > 1)
+ seq.realloc(1);
+ return seq[0];
+}
+
+void Qt5FilePicker::setDescription(const OUString&) {}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5Font.cxx b/vcl/qt5/Qt5Font.cxx
new file mode 100644
index 000000000..2ab614043
--- /dev/null
+++ b/vcl/qt5/Qt5Font.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <Qt5Font.hxx>
+#include <Qt5Tools.hxx>
+
+#include <QtGui/QFont>
+#include <QtGui/QRawFont>
+
+static inline void applyWeight(Qt5Font& 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(Qt5Font& 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(Qt5Font& 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;
+ }
+}
+
+Qt5Font::Qt5Font(const PhysicalFontFace& rPFF, const FontSelectPattern& rFSP)
+ : LogicalFontInstance(rPFF, rFSP)
+{
+ setFamily(toQString(rPFF.GetFamilyName()));
+ applyWeight(*this, rPFF.GetWeight());
+ setPixelSize(rFSP.mnHeight);
+ applyStretch(*this, rPFF.GetWidthType());
+ applyStyle(*this, rFSP.GetItalic());
+}
+
+static hb_blob_t* getFontTable(hb_face_t*, hb_tag_t nTableTag, void* pUserData)
+{
+ char pTagName[5];
+ LogicalFontInstance::DecodeOpenTypeTag(nTableTag, pTagName);
+
+ Qt5Font* pFont = static_cast<Qt5Font*>(pUserData);
+ QRawFont aRawFont(QRawFont::fromFont(*pFont));
+ 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;
+}
+
+hb_font_t* Qt5Font::ImplInitHbFont()
+{
+ return InitHbFont(hb_face_create_for_tables(getFontTable, this, nullptr));
+}
+
+bool Qt5Font::GetGlyphOutline(sal_GlyphId, basegfx::B2DPolyPolygon&, bool) const { return false; }
+
+bool Qt5Font::ImplGetGlyphBoundRect(sal_GlyphId nId, tools::Rectangle& rRect, bool) const
+{
+ QRawFont aRawFont(QRawFont::fromFont(*this));
+ rRect = toRectangle(aRawFont.boundingRect(nId).toAlignedRect());
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5FontFace.cxx b/vcl/qt5/Qt5FontFace.cxx
new file mode 100644
index 000000000..8ac2f87d1
--- /dev/null
+++ b/vcl/qt5/Qt5FontFace.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 <unotools/fontdefs.hxx>
+
+#include <Qt5FontFace.hxx>
+#include <Qt5Font.hxx>
+#include <Qt5Tools.hxx>
+
+#include <sft.hxx>
+#include <impfontcharmap.hxx>
+#include <fontinstance.hxx>
+#include <fontselect.hxx>
+#include <PhysicalFontCollection.hxx>
+
+#include <QtGui/QFont>
+#include <QtGui/QFontDatabase>
+#include <QtGui/QFontInfo>
+#include <QtGui/QRawFont>
+
+using namespace vcl;
+
+Qt5FontFace::Qt5FontFace(const Qt5FontFace& rSrc)
+ : PhysicalFontFace(rSrc)
+ , m_aFontId(rSrc.m_aFontId)
+{
+ if (rSrc.m_xCharMap.is())
+ m_xCharMap = rSrc.m_xCharMap;
+}
+
+FontWeight Qt5FontFace::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 Qt5FontFace::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 Qt5FontFace::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 Qt5FontFace::fillAttributesFromQFont(const QFont& rFont, FontAttributes& rFA)
+{
+ QFontInfo aFontInfo(rFont);
+
+ rFA.SetFamilyName(toOUString(aFontInfo.family()));
+ if (IsStarSymbol(toOUString(aFontInfo.family())))
+ rFA.SetSymbolFlag(true);
+ rFA.SetStyleName(toOUString(aFontInfo.styleName()));
+ rFA.SetPitch(aFontInfo.fixedPitch() ? PITCH_FIXED : PITCH_VARIABLE);
+ rFA.SetWeight(Qt5FontFace::toFontWeight(aFontInfo.weight()));
+ rFA.SetItalic(Qt5FontFace::toFontItalic(aFontInfo.style()));
+ rFA.SetWidthType(Qt5FontFace::toFontWidth(rFont.stretch()));
+}
+
+Qt5FontFace* Qt5FontFace::fromQFont(const QFont& rFont)
+{
+ FontAttributes aFA;
+ fillAttributesFromQFont(rFont, aFA);
+ return new Qt5FontFace(aFA, rFont.toString());
+}
+
+Qt5FontFace* Qt5FontFace::fromQFontDatabase(const QString& aFamily, const QString& aStyle)
+{
+ QFontDatabase aFDB;
+ FontAttributes aFA;
+ aFA.SetFamilyName(toOUString(aFamily));
+ if (IsStarSymbol(aFA.GetFamilyName()))
+ aFA.SetSymbolFlag(true);
+ aFA.SetStyleName(toOUString(aStyle));
+ aFA.SetPitch(aFDB.isFixedPitch(aFamily, aStyle) ? PITCH_FIXED : PITCH_VARIABLE);
+ aFA.SetWeight(Qt5FontFace::toFontWeight(aFDB.weight(aFamily, aStyle)));
+ aFA.SetItalic(aFDB.italic(aFamily, aStyle) ? ITALIC_NORMAL : ITALIC_NONE);
+ return new Qt5FontFace(aFA, aFamily + "," + aStyle);
+}
+
+Qt5FontFace::Qt5FontFace(const FontAttributes& rFA, const QString& rFontID)
+ : PhysicalFontFace(rFA)
+ , m_aFontId(rFontID)
+ , m_bFontCapabilitiesRead(false)
+{
+}
+
+sal_IntPtr Qt5FontFace::GetFontId() const { return reinterpret_cast<sal_IntPtr>(&m_aFontId); }
+
+rtl::Reference<LogicalFontInstance>
+Qt5FontFace::CreateFontInstance(const FontSelectPattern& rFSD) const
+{
+ return new Qt5Font(*this, rFSD);
+}
+
+const FontCharMapRef& Qt5FontFace::GetFontCharMap() const
+{
+ if (m_xCharMap.is())
+ return m_xCharMap;
+
+ QFont aFont;
+ aFont.fromString(m_aFontId);
+ QRawFont aRawFont(QRawFont::fromFont(aFont));
+ QByteArray aCMapTable = aRawFont.fontTable("cmap");
+ if (aCMapTable.isEmpty())
+ {
+ m_xCharMap = new FontCharMap();
+ return m_xCharMap;
+ }
+
+ CmapResult aCmapResult;
+ if (ParseCMAP(reinterpret_cast<const unsigned char*>(aCMapTable.data()), aCMapTable.size(),
+ aCmapResult))
+ m_xCharMap = new FontCharMap(aCmapResult);
+
+ return m_xCharMap;
+}
+
+bool Qt5FontFace::GetFontCapabilities(vcl::FontCapabilities& rFontCapabilities) const
+{
+ // read this only once per font
+ if (m_bFontCapabilitiesRead)
+ {
+ rFontCapabilities = m_aFontCapabilities;
+ return rFontCapabilities.oUnicodeRange || rFontCapabilities.oCodePageRange;
+ }
+ m_bFontCapabilitiesRead = true;
+
+ QFont aFont;
+ aFont.fromString(m_aFontId);
+ QRawFont aRawFont(QRawFont::fromFont(aFont));
+ QByteArray aOS2Table = aRawFont.fontTable("OS/2");
+ if (!aOS2Table.isEmpty())
+ {
+ vcl::getTTCoverage(m_aFontCapabilities.oUnicodeRange, m_aFontCapabilities.oCodePageRange,
+ reinterpret_cast<const unsigned char*>(aOS2Table.data()),
+ aOS2Table.size());
+ }
+
+ rFontCapabilities = m_aFontCapabilities;
+ return rFontCapabilities.oUnicodeRange || rFontCapabilities.oCodePageRange;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5Frame.cxx b/vcl/qt5/Qt5Frame.cxx
new file mode 100644
index 000000000..936216b9d
--- /dev/null
+++ b/vcl/qt5/Qt5Frame.cxx
@@ -0,0 +1,1449 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <Qt5Frame.hxx>
+#include <Qt5Frame.moc>
+
+#include <Qt5Data.hxx>
+#include <Qt5DragAndDrop.hxx>
+#include <Qt5Graphics.hxx>
+#include <Qt5Instance.hxx>
+#include <Qt5MainWindow.hxx>
+#include <Qt5Menu.hxx>
+#include <Qt5SvpGraphics.hxx>
+#include <Qt5System.hxx>
+#include <Qt5Tools.hxx>
+#include <Qt5Transferable.hxx>
+#include <Qt5Widget.hxx>
+
+#include <QtCore/QMimeData>
+#include <QtCore/QPoint>
+#include <QtCore/QSize>
+#include <QtCore/QThread>
+#include <QtCore/QVersionNumber>
+#include <QtGui/QDragMoveEvent>
+#include <QtGui/QDropEvent>
+#include <QtGui/QIcon>
+#include <QtGui/QWindow>
+#include <QtGui/QScreen>
+#include <QtWidgets/QStyle>
+#include <QtWidgets/QToolTip>
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QDesktopWidget>
+#include <QtWidgets/QMenuBar>
+#include <QtWidgets/QMainWindow>
+
+#if QT5_USING_X11
+#include <QtX11Extras/QX11Info>
+#include <xcb/xproto.h>
+#if QT5_HAVE_XCB_ICCCM
+#include <xcb/xcb_icccm.h>
+#endif
+#endif
+
+#include <saldatabasic.hxx>
+#include <window.h>
+#include <vcl/layout.hxx>
+#include <vcl/syswin.hxx>
+
+#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
+
+#include <cairo.h>
+#include <headless/svpgdi.hxx>
+
+#if QT5_USING_X11 && QT5_HAVE_XCB_ICCCM
+static bool g_bNeedsWmHintsWindowGroup = true;
+static xcb_atom_t g_aXcbClientLeaderAtom = 0;
+#endif
+
+static void SvpDamageHandler(void* handle, sal_Int32 nExtentsX, sal_Int32 nExtentsY,
+ sal_Int32 nExtentsWidth, sal_Int32 nExtentsHeight)
+{
+ Qt5Frame* pThis = static_cast<Qt5Frame*>(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;
+}
+}
+
+Qt5Frame::Qt5Frame(Qt5Frame* pParent, SalFrameStyleFlags nStyle, bool bUseCairo)
+ : m_pTopLevel(nullptr)
+ , m_bUseCairo(bUseCairo)
+ , m_pSvpGraphics(nullptr)
+ , m_bNullRegion(true)
+ , m_bGraphicsInUse(false)
+ , m_bGraphicsInvalid(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)
+{
+ Qt5Instance* pInst = static_cast<Qt5Instance*>(GetSalData()->m_pInstance);
+ 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;
+ 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::FLOAT | SalFrameStyleFlags::TOOLTIP))
+ aWinFlags |= Qt::ToolTip;
+ else if ((nStyle & SalFrameStyleFlags::FLOAT)
+ && !(nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION))
+ aWinFlags |= Qt::Popup;
+ 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 Qt5MainWindow(*this, aWinFlags);
+ m_pQWidget = new Qt5Widget(*this, aWinFlags);
+ m_pTopLevel->setCentralWidget(m_pQWidget);
+ m_pTopLevel->setFocusProxy(m_pQWidget);
+ }
+ else
+ m_pQWidget = new Qt5Widget(*this, aWinFlags);
+
+ if (pParent && !(pParent->m_nStyle & SalFrameStyleFlags::PLUG))
+ {
+ QWindow* pParentWindow = pParent->GetQWidget()->window()->windowHandle();
+ QWindow* pChildWindow = asChild()->window()->windowHandle();
+ if (pParentWindow && pChildWindow && (pParentWindow != pChildWindow))
+ pChildWindow->setTransientParent(pParentWindow);
+ }
+
+ // Calling 'QWidget::winId()' implicitly enables native windows to be used
+ // rather than "alien widgets" that are unknown to the windowing system,
+ // s. https://doc.qt.io/qt-5/qwidget.html#native-widgets-vs-alien-widgets
+ // Avoid this on Wayland due to problems with missing 'mouseMoveEvent's,
+ // s. tdf#122293/QTBUG-75766
+ const bool bWayland = QGuiApplication::platformName() == "wayland";
+ if (!bWayland)
+ m_aSystemData.aWindow = m_pQWidget->winId();
+ else
+ {
+ // TODO implement as needed for Wayland,
+ // s.a. commit c0d4f3ad3307c which did this for gtk3
+ // QPlatformNativeInterface* native = QGuiApplication::platformNativeInterface();
+ // m_aSystemData.pDisplay = native->nativeResourceForWindow("display", nullptr);
+ // m_aSystemData.aWindow = reinterpret_cast<unsigned long>(
+ // native->nativeResourceForWindow("surface", m_pQWidget->windowHandle()));
+ }
+
+ m_aSystemData.aShellWindow = reinterpret_cast<sal_IntPtr>(this);
+ //m_aSystemData.pSalFrame = this;
+ m_aSystemData.pWidget = m_pQWidget;
+ //m_aSystemData.nScreen = m_nXScreen.getXScreen();
+ m_aSystemData.toolkit = SystemEnvData::Toolkit::Qt5;
+ if (!bWayland)
+ m_aSystemData.platform = SystemEnvData::Platform::Xcb;
+ else
+ m_aSystemData.platform = SystemEnvData::Platform::Wayland;
+
+ SetIcon(SV_ICON_ID_OFFICE);
+
+ fixICCCMwindowGroup();
+}
+
+void Qt5Frame::fixICCCMwindowGroup()
+{
+#if QT5_USING_X11 && QT5_HAVE_XCB_ICCCM
+ // older Qt5 just sets WM_CLIENT_LEADER, but not the XCB_ICCCM_WM_HINT_WINDOW_GROUP
+ // see Qt commit 0de4b326d8 ("xcb: fix issue with dialogs hidden by other windows")
+ // or QTBUG-46626. So LO has to set this itself to help some WMs.
+ if (!g_bNeedsWmHintsWindowGroup)
+ return;
+ g_bNeedsWmHintsWindowGroup = false;
+
+ if (QGuiApplication::platformName() != "xcb")
+ return;
+ if (QVersionNumber::fromString(qVersion()) >= QVersionNumber(5, 12))
+ return;
+
+ xcb_connection_t* conn = QX11Info::connection();
+ xcb_window_t win = asChild()->winId();
+
+ xcb_icccm_wm_hints_t hints;
+
+ xcb_get_property_cookie_t prop_cookie = xcb_icccm_get_wm_hints_unchecked(conn, win);
+ if (!xcb_icccm_get_wm_hints_reply(conn, prop_cookie, &hints, nullptr))
+ return;
+
+ if (hints.flags & XCB_ICCCM_WM_HINT_WINDOW_GROUP)
+ return;
+
+ if (g_aXcbClientLeaderAtom == 0)
+ {
+ const char* const leader_name = "WM_CLIENT_LEADER\0";
+ xcb_intern_atom_cookie_t atom_cookie
+ = xcb_intern_atom(conn, 1, strlen(leader_name), leader_name);
+ xcb_intern_atom_reply_t* atom_reply = xcb_intern_atom_reply(conn, atom_cookie, nullptr);
+ if (!atom_reply)
+ return;
+ g_aXcbClientLeaderAtom = atom_reply->atom;
+ free(atom_reply);
+ }
+
+ g_bNeedsWmHintsWindowGroup = true;
+
+ prop_cookie = xcb_get_property(conn, 0, win, g_aXcbClientLeaderAtom, XCB_ATOM_WINDOW, 0, 1);
+ xcb_get_property_reply_t* prop_reply = xcb_get_property_reply(conn, prop_cookie, nullptr);
+ if (!prop_reply)
+ return;
+
+ if (xcb_get_property_value_length(prop_reply) != 4)
+ {
+ free(prop_reply);
+ return;
+ }
+
+ xcb_window_t leader = *static_cast<xcb_window_t*>(xcb_get_property_value(prop_reply));
+ free(prop_reply);
+
+ hints.flags |= XCB_ICCCM_WM_HINT_WINDOW_GROUP;
+ hints.window_group = leader;
+ xcb_icccm_set_wm_hints(conn, win, &hints);
+#else
+ (void)this; // avoid loplugin:staticmethods
+#endif
+}
+
+Qt5Frame::~Qt5Frame()
+{
+ Qt5Instance* pInst = static_cast<Qt5Instance*>(GetSalData()->m_pInstance);
+ pInst->eraseFrame(this);
+ delete asChild();
+ m_aSystemData.aShellWindow = 0;
+}
+
+void Qt5Frame::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()));
+}
+
+void Qt5Frame::InitQt5SvpGraphics(Qt5SvpGraphics* pQt5SvpGraphics)
+{
+ int width = 640;
+ int height = 480;
+ m_pSvpGraphics = pQt5SvpGraphics;
+ m_pSurface.reset(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height));
+ m_pSvpGraphics->setSurface(m_pSurface.get(), basegfx::B2IVector(width, height));
+ cairo_surface_set_user_data(m_pSurface.get(), Qt5SvpGraphics::getDamageKey(), &m_aDamageHandler,
+ nullptr);
+}
+
+SalGraphics* Qt5Frame::AcquireGraphics()
+{
+ if (m_bGraphicsInUse)
+ return nullptr;
+
+ m_bGraphicsInUse = true;
+
+ if (m_bUseCairo)
+ {
+ if (!m_pOurSvpGraphics || m_bGraphicsInvalid)
+ {
+ m_pOurSvpGraphics.reset(new Qt5SvpGraphics(this));
+ InitQt5SvpGraphics(m_pOurSvpGraphics.get());
+ m_bGraphicsInvalid = false;
+ }
+ return m_pOurSvpGraphics.get();
+ }
+ else
+ {
+ if (!m_pQt5Graphics || m_bGraphicsInvalid)
+ {
+ m_pQt5Graphics.reset(new Qt5Graphics(this));
+ m_pQImage.reset(
+ new QImage(m_pQWidget->size() * devicePixelRatioF(), Qt5_DefaultFormat32));
+ m_pQImage->fill(Qt::transparent);
+ m_pQt5Graphics->ChangeQImage(m_pQImage.get());
+ m_bGraphicsInvalid = false;
+ }
+ return m_pQt5Graphics.get();
+ }
+}
+
+void Qt5Frame::ReleaseGraphics(SalGraphics* pSalGraph)
+{
+ (void)pSalGraph;
+ if (m_bUseCairo)
+ assert(pSalGraph == m_pOurSvpGraphics.get());
+ else
+ assert(pSalGraph == m_pQt5Graphics.get());
+ m_bGraphicsInUse = false;
+}
+
+bool Qt5Frame::PostEvent(std::unique_ptr<ImplSVEvent> pData)
+{
+ Qt5Instance* pInst = static_cast<Qt5Instance*>(GetSalData()->m_pInstance);
+ pInst->PostEvent(this, pData.release(), SalEvent::UserEvent);
+ return true;
+}
+
+QWidget* Qt5Frame::asChild() const { return m_pTopLevel ? m_pTopLevel : m_pQWidget; }
+
+qreal Qt5Frame::devicePixelRatioF() const { return asChild()->devicePixelRatioF(); }
+
+bool Qt5Frame::isWindow() const { return asChild()->isWindow(); }
+
+QWindow* Qt5Frame::windowHandle() const
+{
+ // set attribute 'Qt::WA_NativeWindow' first to make sure a window handle actually exists
+ QWidget* pChild = asChild();
+ pChild->setAttribute(Qt::WA_NativeWindow);
+ return pChild->windowHandle();
+}
+
+QScreen* Qt5Frame::screen() const
+{
+ QWindow* const pWindow = windowHandle();
+ return pWindow ? pWindow->screen() : nullptr;
+}
+
+bool Qt5Frame::isMinimized() const { return asChild()->isMinimized(); }
+
+bool Qt5Frame::isMaximized() const { return asChild()->isMaximized(); }
+
+void Qt5Frame::SetWindowStateImpl(Qt::WindowStates eState)
+{
+ return asChild()->setWindowState(eState);
+}
+
+void Qt5Frame::SetTitle(const OUString& rTitle)
+{
+ m_pQWidget->window()->setWindowTitle(toQString(rTitle));
+}
+
+void Qt5Frame::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 Qt5Frame::SetMenu(SalMenu* pMenu) { m_pSalMenu = static_cast<Qt5Menu*>(pMenu); }
+
+void Qt5Frame::DrawMenuBar() { /* not needed */}
+
+void Qt5Frame::SetExtendedFrameStyle(SalExtStyle /*nExtStyle*/) { /* not needed */}
+
+void Qt5Frame::Show(bool bVisible, bool /*bNoActivate*/)
+{
+ assert(m_pQWidget);
+
+ SetDefaultSize();
+ SetDefaultPos();
+
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ assert(pSalInst);
+ pSalInst->RunInMainThread([this, bVisible]() { asChild()->setVisible(bVisible); });
+}
+
+void Qt5Frame::SetMinClientSize(long nWidth, long nHeight)
+{
+ if (!isChild())
+ {
+ const qreal fRatio = devicePixelRatioF();
+ asChild()->setMinimumSize(round(nWidth / fRatio), round(nHeight / fRatio));
+ }
+}
+
+void Qt5Frame::SetMaxClientSize(long nWidth, long nHeight)
+{
+ if (!isChild())
+ {
+ const qreal fRatio = devicePixelRatioF();
+ asChild()->setMaximumSize(round(nWidth / fRatio), round(nHeight / fRatio));
+ }
+}
+
+void Qt5Frame::SetDefaultPos()
+{
+ if (!m_bDefaultPos)
+ return;
+
+ // center on parent
+ if (m_pParent)
+ {
+ const qreal fRatio = devicePixelRatioF();
+ QWidget* const pWindow = m_pParent->GetQWidget()->window();
+ QWidget* const pWidget = asChild();
+ QPoint aPos = pWindow->rect().center() - pWidget->rect().center();
+ SetPosSize(round(aPos.x() * fRatio), round(aPos.y() * fRatio), 0, 0,
+ SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y);
+ assert(!m_bDefaultPos);
+ }
+ else
+ m_bDefaultPos = false;
+}
+
+Size Qt5Frame::CalcDefaultSize()
+{
+ assert(isWindow());
+
+ Size aSize;
+ if (!m_bFullScreen)
+ {
+ const QScreen* pScreen = screen();
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ aSize = bestmaxFrameSizeForScreenSize(
+ toSize(pScreen ? pScreen->size() : QApplication::desktop()->screenGeometry(0).size()));
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ }
+ else
+ {
+ if (!m_bFullScreenSpanAll)
+ {
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ aSize = toSize(
+ QApplication::desktop()->screenGeometry(maGeometry.nDisplayScreenNumber).size());
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ }
+ else
+ {
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ int nLeftScreen = QApplication::desktop()->screenNumber(QPoint(0, 0));
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ aSize = toSize(QApplication::screens()[nLeftScreen]->availableVirtualGeometry().size());
+ }
+ }
+
+ return aSize;
+}
+
+void Qt5Frame::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 Qt5Frame::SetPosSize(long nX, long nY, long nWidth, 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.nWidth;
+ else if (!(nFlags & SAL_FRAME_POSSIZE_HEIGHT))
+ nHeight = maGeometry.nHeight;
+
+ 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.nWidth = nWidth;
+ if (nHeight > 0)
+ maGeometry.nHeight = nHeight;
+ }
+ }
+
+ if (nFlags & (SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y))
+ {
+ if (m_pParent)
+ {
+ const SalFrameGeometry& aParentGeometry = m_pParent->maGeometry;
+ if (QGuiApplication::isRightToLeft())
+ nX = aParentGeometry.nX + aParentGeometry.nWidth - nX - maGeometry.nWidth - 1;
+ else
+ nX += aParentGeometry.nX;
+ nY += aParentGeometry.nY;
+
+ Qt5MainWindow* pTopLevel = m_pParent->GetTopLevelWindow();
+ if (pTopLevel && pTopLevel->menuBar() && pTopLevel->menuBar()->isVisible())
+ nY += round(pTopLevel->menuBar()->geometry().height() * devicePixelRatioF());
+ }
+
+ if (!(nFlags & SAL_FRAME_POSSIZE_X))
+ nX = maGeometry.nX;
+ else if (!(nFlags & SAL_FRAME_POSSIZE_Y))
+ nY = maGeometry.nY;
+
+ // assume the reposition happened
+ // needed for calculations and will eventually be corrected by events later
+ maGeometry.nX = nX;
+ maGeometry.nY = nY;
+
+ m_bDefaultPos = false;
+ asChild()->move(round(nX / devicePixelRatioF()), round(nY / devicePixelRatioF()));
+ }
+}
+
+void Qt5Frame::GetClientSize(long& rWidth, long& rHeight)
+{
+ rWidth = round(m_pQWidget->width() * devicePixelRatioF());
+ rHeight = round(m_pQWidget->height() * devicePixelRatioF());
+}
+
+void Qt5Frame::GetWorkArea(tools::Rectangle& rRect)
+{
+ if (!isWindow())
+ return;
+ QScreen* pScreen = screen();
+ if (!pScreen)
+ return;
+
+ QSize aSize = pScreen->availableVirtualSize() * devicePixelRatioF();
+ rRect = tools::Rectangle(0, 0, aSize.width(), aSize.height());
+}
+
+SalFrame* Qt5Frame::GetParent() const { return m_pParent; }
+
+void Qt5Frame::SetModal(bool bModal)
+{
+ if (isWindow())
+ {
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ 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();
+
+ pChild->setWindowModality(bModal ? Qt::WindowModal : Qt::NonModal);
+
+ if (bWasVisible)
+ pChild->show();
+ });
+ }
+}
+
+bool Qt5Frame::GetModal() const { return isWindow() && windowHandle()->isModal(); }
+
+void Qt5Frame::SetWindowState(const SalFrameState* pState)
+{
+ if (!isWindow() || !pState || isChild(true, false))
+ return;
+
+ const WindowStateMask nMaxGeometryMask
+ = WindowStateMask::X | WindowStateMask::Y | WindowStateMask::Width | WindowStateMask::Height
+ | WindowStateMask::MaximizedX | WindowStateMask::MaximizedY
+ | WindowStateMask::MaximizedWidth | WindowStateMask::MaximizedHeight;
+
+ if ((pState->mnMask & WindowStateMask::State) && (pState->mnState & WindowStateState::Maximized)
+ && !isMaximized() && (pState->mnMask & nMaxGeometryMask) == nMaxGeometryMask)
+ {
+ const qreal fRatio = devicePixelRatioF();
+ QWidget* const pChild = asChild();
+ pChild->resize(ceil(pState->mnWidth / fRatio), ceil(pState->mnHeight / fRatio));
+ pChild->move(ceil(pState->mnX / fRatio), ceil(pState->mnY / fRatio));
+ SetWindowStateImpl(Qt::WindowMaximized);
+ }
+ else if (pState->mnMask
+ & (WindowStateMask::X | WindowStateMask::Y | WindowStateMask::Width
+ | WindowStateMask::Height))
+ {
+ sal_uInt16 nPosSizeFlags = 0;
+ if (pState->mnMask & WindowStateMask::X)
+ nPosSizeFlags |= SAL_FRAME_POSSIZE_X;
+ if (pState->mnMask & WindowStateMask::Y)
+ nPosSizeFlags |= SAL_FRAME_POSSIZE_Y;
+ if (pState->mnMask & WindowStateMask::Width)
+ nPosSizeFlags |= SAL_FRAME_POSSIZE_WIDTH;
+ if (pState->mnMask & WindowStateMask::Height)
+ nPosSizeFlags |= SAL_FRAME_POSSIZE_HEIGHT;
+ SetPosSize(pState->mnX, pState->mnY, pState->mnWidth, pState->mnHeight, nPosSizeFlags);
+ }
+ else if (pState->mnMask & WindowStateMask::State && !isChild())
+ {
+ if (pState->mnState & WindowStateState::Maximized)
+ SetWindowStateImpl(Qt::WindowMaximized);
+ else if (pState->mnState & WindowStateState::Minimized)
+ SetWindowStateImpl(Qt::WindowMinimized);
+ else
+ SetWindowStateImpl(Qt::WindowNoState);
+ }
+}
+
+bool Qt5Frame::GetWindowState(SalFrameState* pState)
+{
+ pState->mnState = WindowStateState::Normal;
+ pState->mnMask = WindowStateMask::State;
+ if (isMinimized() /*|| !windowHandle()*/)
+ pState->mnState |= WindowStateState::Minimized;
+ else if (isMaximized())
+ {
+ pState->mnState |= WindowStateState::Maximized;
+ }
+ else
+ {
+ // geometry() is the drawable area, which is wanted here
+ QRect rect = scaledQRect(asChild()->geometry(), devicePixelRatioF());
+ pState->mnX = rect.x();
+ pState->mnY = rect.y();
+ pState->mnWidth = rect.width();
+ pState->mnHeight = rect.height();
+ pState->mnMask |= WindowStateMask::X | WindowStateMask::Y | WindowStateMask::Width
+ | WindowStateMask::Height;
+ }
+
+ return true;
+}
+
+void Qt5Frame::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.nDisplayScreenNumber;
+ 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 Qt5Frame::StartPresentation(bool bStart)
+{
+// meh - so there's no Qt platform independent solution
+// https://forum.qt.io/topic/38504/solved-qdialog-in-fullscreen-disable-os-screensaver
+#if QT5_USING_X11
+ std::optional<unsigned int> aRootWindow;
+ std::optional<Display*> aDisplay;
+
+ if (QX11Info::isPlatformX11())
+ {
+ aRootWindow = QX11Info::appRootWindow();
+ aDisplay = QX11Info::display();
+ }
+
+ m_ScreenSaverInhibitor.inhibit(bStart, "presentation", QX11Info::isPlatformX11(), aRootWindow,
+ aDisplay);
+#else
+ (void)bStart;
+#endif
+}
+
+void Qt5Frame::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 Qt5Frame::ToTop(SalFrameToTop nFlags)
+{
+ QWidget* const pWidget = asChild();
+ if (isWindow() && !(nFlags & SalFrameToTop::GrabFocusOnly))
+ pWidget->raise();
+ if ((nFlags & SalFrameToTop::RestoreWhenMin) || (nFlags & SalFrameToTop::ForegroundTask))
+ pWidget->activateWindow();
+ else if ((nFlags & SalFrameToTop::GrabFocus) || (nFlags & SalFrameToTop::GrabFocusOnly))
+ {
+ pWidget->activateWindow();
+ pWidget->setFocus();
+ }
+}
+
+void Qt5Frame::SetPointer(PointerStyle ePointerStyle)
+{
+ QWindow* pWindow = m_pQWidget->window()->windowHandle();
+ if (!pWindow)
+ return;
+ if (ePointerStyle == m_ePointerStyle)
+ return;
+ m_ePointerStyle = ePointerStyle;
+
+ pWindow->setCursor(static_cast<Qt5Data*>(GetSalData())->getCursor(ePointerStyle));
+}
+
+void Qt5Frame::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 Qt5Frame::SetPointerPos(long nX, 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)));
+}
+
+void Qt5Frame::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 Qt5Widget::paintEvent() is
+ // destroyed, so that state should be safely flushed.
+}
+
+bool Qt5Frame::ShowTooltip(const OUString& rText, const tools::Rectangle& rHelpArea)
+{
+ QRect aHelpArea(toQRect(rHelpArea));
+ if (QGuiApplication::isRightToLeft())
+ aHelpArea.moveLeft(maGeometry.nWidth - aHelpArea.width() - aHelpArea.left() - 1);
+ QToolTip::showText(QCursor::pos(), toQString(rText), m_pQWidget, aHelpArea);
+ return true;
+}
+
+void Qt5Frame::SetInputContext(SalInputContext* pContext)
+{
+ if (!pContext)
+ return;
+
+ if (!(pContext->mnOptions & InputContextFlags::Text))
+ return;
+
+ m_pQWidget->setAttribute(Qt::WA_InputMethodEnabled);
+}
+
+void Qt5Frame::EndExtTextInput(EndExtTextInputFlags /*nFlags*/)
+{
+ Qt5Widget* pQt5Widget = static_cast<Qt5Widget*>(m_pQWidget);
+ if (pQt5Widget)
+ pQt5Widget->endExtTextInput();
+}
+
+OUString Qt5Frame::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_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 Qt5Frame::MapUnicodeToKeyCode(sal_Unicode /*aUnicode*/, LanguageType /*aLangType*/,
+ vcl::KeyCode& /*rKeyCode*/)
+{
+ // not supported yet
+ return false;
+}
+
+LanguageType Qt5Frame::GetInputLanguage()
+{
+ // fallback
+ return LANGUAGE_DONTKNOW;
+}
+
+static Color toColor(const QColor& rColor)
+{
+ return Color(rColor.red(), rColor.green(), rColor.blue());
+}
+
+void Qt5Frame::UpdateSettings(AllSettings& rSettings)
+{
+ if (Qt5Data::noNativeControls())
+ return;
+
+ StyleSettings style(rSettings.GetStyleSettings());
+
+ // 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.SetWindowTextColor(aText);
+ style.SetToolTextColor(aText);
+
+ // Base
+ style.SetFieldColor(aBase);
+ style.SetWindowColor(aBase);
+ style.SetActiveTabColor(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
+ style.SetHighlightColor(aHigh);
+ style.SetHighlightTextColor(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)));
+
+ const int flash_time = QApplication::cursorFlashTime();
+ style.SetCursorBlinkTime(flash_time != 0 ? flash_time / 2 : STYLE_CURSOR_NOBLINKTIME);
+
+ // 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());
+
+ // Icon theme
+ style.SetPreferredIconTheme(toOUString(QIcon::themeName()));
+
+ // 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)));
+
+ m_bGraphicsInvalid = true;
+ rSettings.SetStyleSettings(style);
+}
+
+void Qt5Frame::Beep() { QApplication::beep(); }
+
+SalFrame::SalPointerState Qt5Frame::GetPointerState()
+{
+ SalPointerState aState;
+ aState.maPos = toPoint(QCursor::pos() * devicePixelRatioF());
+ aState.maPos.Move(-maGeometry.nX, -maGeometry.nY);
+ aState.mnState = GetMouseModCode(QGuiApplication::mouseButtons())
+ | GetKeyModCode(QGuiApplication::keyboardModifiers());
+ return aState;
+}
+
+KeyIndicatorState Qt5Frame::GetIndicatorState() { return KeyIndicatorState(); }
+
+void Qt5Frame::SimulateKeyPress(sal_uInt16 nKeyCode)
+{
+ SAL_WARN("vcl.qt5", "missing simulate keypress " << nKeyCode);
+}
+
+void Qt5Frame::SetParent(SalFrame* pNewParent) { m_pParent = static_cast<Qt5Frame*>(pNewParent); }
+
+bool Qt5Frame::SetPluginParent(SystemParentData* /*pNewParent*/)
+{
+ //FIXME: no SetPluginParent impl. for qt5
+ return false;
+}
+
+void Qt5Frame::ResetClipRegion() { m_bNullRegion = true; }
+
+void Qt5Frame::BeginSetClipRegion(sal_uInt32)
+{
+ m_aRegion = QRegion(QRect(QPoint(0, 0), m_pQWidget->size()));
+}
+
+void Qt5Frame::UnionClipRegion(long nX, long nY, long nWidth, long nHeight)
+{
+ m_aRegion
+ = m_aRegion.united(scaledQRect(QRect(nX, nY, nWidth, nHeight), 1 / devicePixelRatioF()));
+}
+
+void Qt5Frame::EndSetClipRegion() { m_bNullRegion = false; }
+
+void Qt5Frame::SetScreenNumber(unsigned int nScreen)
+{
+ if (isWindow())
+ {
+ QWindow* const pWindow = windowHandle();
+ if (pWindow)
+ {
+ QList<QScreen*> screens = QApplication::screens();
+ if (static_cast<int>(nScreen) < screens.size() || m_bFullScreenSpanAll)
+ {
+ QRect screenGeo;
+
+ if (!m_bFullScreenSpanAll)
+ {
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ screenGeo = QApplication::desktop()->screenGeometry(nScreen);
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ pWindow->setScreen(QApplication::screens()[nScreen]);
+ }
+ else // special case: fullscreen over all available screens
+ {
+ assert(m_bFullScreen);
+ // left-most screen
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ int nLeftScreen = QApplication::desktop()->screenNumber(QPoint(0, 0));
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ // entire virtual desktop
+ screenGeo = QApplication::screens()[nLeftScreen]->availableVirtualGeometry();
+ pWindow->setScreen(QApplication::screens()[nLeftScreen]);
+ pWindow->setGeometry(screenGeo);
+ nScreen = nLeftScreen;
+ }
+
+ // 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.nDisplayScreenNumber = nScreen;
+ }
+ }
+}
+
+void Qt5Frame::SetApplicationID(const OUString& rWMClass)
+{
+#if QT5_USING_X11
+ if (QGuiApplication::platformName() != "xcb" || !m_pTopLevel)
+ return;
+
+ 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, m_pTopLevel->winId(),
+ XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 8, data_len, data);
+ delete[] data;
+#else
+ (void)rWMClass;
+#endif
+}
+
+// Drag'n'drop foo
+
+void Qt5Frame::registerDragSource(Qt5DragSource* pDragSource)
+{
+ assert(!m_pDragSource);
+ m_pDragSource = pDragSource;
+}
+
+void Qt5Frame::deregisterDragSource(Qt5DragSource const* pDragSource)
+{
+ assert(m_pDragSource == pDragSource);
+ (void)pDragSource;
+ m_pDragSource = nullptr;
+}
+
+void Qt5Frame::registerDropTarget(Qt5DropTarget* pDropTarget)
+{
+ assert(!m_pDropTarget);
+ m_pDropTarget = pDropTarget;
+ m_pQWidget->setAcceptDrops(true);
+}
+
+void Qt5Frame::deregisterDropTarget(Qt5DropTarget 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 Qt5MimeData* pQt5MimeData = dynamic_cast<const Qt5MimeData*>(pMimeData);
+ if (!pQt5MimeData)
+ xTransferable = new Qt5DnDTransferable(pMimeData);
+ else
+ xTransferable = pQt5MimeData->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
+ const Qt::KeyboardModifiers eKeyMod = pEvent->keyboardModifiers();
+ 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 Qt5MimeData*>(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 Qt5Frame::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);
+ const Point aPos = toPoint(pEvent->pos() * devicePixelRatioF());
+
+ 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 Qt5Frame::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());
+ const Point aPos = toPoint(pEvent->pos() * devicePixelRatioF());
+
+ 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())
+ {
+ Qt5Widget* pWidget = dynamic_cast<Qt5Widget*>(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 Qt5Frame::handleDragLeave()
+{
+ css::datatransfer::dnd::DropTargetEvent aEvent;
+ aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(m_pDropTarget);
+ m_pDropTarget->fire_dragExit(aEvent);
+ m_bInDrag = false;
+}
+
+cairo_t* Qt5Frame::getCairoContext() const
+{
+ cairo_t* cr = nullptr;
+ if (m_bUseCairo)
+ {
+ cr = cairo_create(m_pSurface.get());
+ assert(cr);
+ }
+ return cr;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5Graphics.cxx b/vcl/qt5/Qt5Graphics.cxx
new file mode 100644
index 000000000..34f610812
--- /dev/null
+++ b/vcl/qt5/Qt5Graphics.cxx
@@ -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 .
+ */
+
+#include <Qt5Graphics.hxx>
+
+#include <Qt5Data.hxx>
+#include <Qt5Font.hxx>
+#include <Qt5Frame.hxx>
+#include <Qt5Graphics_Controls.hxx>
+#include <Qt5Painter.hxx>
+
+#include <QtGui/QImage>
+#include <QtGui/QPainter>
+#include <QtWidgets/QPushButton>
+#include <QtWidgets/QWidget>
+
+Qt5Graphics::Qt5Graphics( Qt5Frame *pFrame, QImage *pQImage )
+ : m_pFrame( pFrame )
+ , m_pQImage( pQImage )
+ , m_aLineColor( 0x00, 0x00, 0x00 )
+ , m_aFillColor( 0xFF, 0xFF, 0XFF )
+ , m_eCompositionMode( QPainter::CompositionMode_SourceOver )
+ , m_pFontCollection( nullptr )
+ , m_pTextStyle{ nullptr, }
+ , m_aTextColor( 0x00, 0x00, 0x00 )
+{
+ ResetClipRegion();
+
+ if (!initWidgetDrawBackends(false))
+ {
+ if (!Qt5Data::noNativeControls())
+ m_pWidgetDraw.reset(new Qt5Graphics_Controls(*this));
+ }
+ if (m_pFrame)
+ setDevicePixelRatioF(m_pFrame->devicePixelRatioF());
+}
+
+Qt5Graphics::~Qt5Graphics() { ReleaseFonts(); }
+
+void Qt5Graphics::ChangeQImage(QImage* pQImage)
+{
+ m_pQImage = pQImage;
+ ResetClipRegion();
+}
+
+SalGraphicsImpl* Qt5Graphics::GetImpl() const { return nullptr; }
+
+SystemGraphicsData Qt5Graphics::GetGraphicsData() const { return SystemGraphicsData(); }
+
+bool Qt5Graphics::supportsOperation(OutDevSupportType eType) const
+{
+ switch (eType)
+ {
+ case OutDevSupportType::B2DDraw:
+ case OutDevSupportType::TransparentRect:
+ return true;
+ default:
+ return false;
+ }
+}
+
+#if ENABLE_CAIRO_CANVAS
+
+bool Qt5Graphics::SupportsCairo() const { return false; }
+
+cairo::SurfaceSharedPtr
+Qt5Graphics::CreateSurface(const cairo::CairoSurfaceSharedPtr& /*rSurface*/) const
+{
+ return nullptr;
+}
+
+cairo::SurfaceSharedPtr Qt5Graphics::CreateSurface(const OutputDevice& /*rRefDevice*/, int /*x*/,
+ int /*y*/, int /*width*/, int /*height*/) const
+{
+ return nullptr;
+}
+
+cairo::SurfaceSharedPtr Qt5Graphics::CreateBitmapSurface(const OutputDevice& /*rRefDevice*/,
+ const BitmapSystemData& /*rData*/,
+ const Size& /*rSize*/) const
+{
+ return nullptr;
+}
+
+css::uno::Any Qt5Graphics::GetNativeSurfaceHandle(cairo::SurfaceSharedPtr& /*rSurface*/,
+ const basegfx::B2ISize& /*rSize*/) const
+{
+ return css::uno::Any();
+}
+
+SystemFontData Qt5Graphics::GetSysFontData(int /*nFallbacklevel*/) const
+{
+ return SystemFontData();
+}
+
+#endif
+
+void Qt5Graphics::handleDamage(const tools::Rectangle& rDamagedRegion)
+{
+ assert(m_pWidgetDraw);
+ assert(dynamic_cast<Qt5Graphics_Controls*>(m_pWidgetDraw.get()));
+ assert(!rDamagedRegion.IsEmpty());
+
+ QImage* pImage = static_cast<Qt5Graphics_Controls*>(m_pWidgetDraw.get())->getImage();
+ QImage blit(*pImage);
+ blit.setDevicePixelRatio(1);
+ Qt5Painter aPainter(*this);
+ aPainter.drawImage(QPoint(rDamagedRegion.getX(), rDamagedRegion.getY()), blit);
+ aPainter.update(toQRect(rDamagedRegion));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5Graphics_Controls.cxx b/vcl/qt5/Qt5Graphics_Controls.cxx
new file mode 100644
index 000000000..dce9c1687
--- /dev/null
+++ b/vcl/qt5/Qt5Graphics_Controls.cxx
@@ -0,0 +1,1111 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <Qt5Graphics_Controls.hxx>
+
+#include <QtGui/QPainter>
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QFrame>
+#include <QtWidgets/QLabel>
+
+#include <qt5/Qt5Tools.hxx>
+#include <qt5/Qt5GraphicsBase.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_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;
+}
+
+Qt5Graphics_Controls::Qt5Graphics_Controls(const Qt5GraphicsBase& rGraphics)
+ : m_rGraphics(rGraphics)
+{
+}
+
+bool Qt5Graphics_Controls::isNativeControlSupported(ControlType type, ControlPart part)
+{
+ switch (type)
+ {
+ case ControlType::Tooltip:
+ case ControlType::Progress:
+ case ControlType::ListNode:
+ return (part == ControlPart::Entire);
+
+ case ControlType::Radiobutton:
+ case ControlType::Checkbox:
+ return (part == ControlPart::Entire) || (part == ControlPart::Focus);
+ case ControlType::Pushbutton:
+ return (part == ControlPart::Entire);
+
+ 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 Qt5Graphics_Controls::pixelMetric(QStyle::PixelMetric metric, const QStyleOption* option)
+{
+ return QApplication::style()->pixelMetric(metric, option);
+}
+
+inline QSize Qt5Graphics_Controls::sizeFromContents(QStyle::ContentsType type,
+ const QStyleOption* option,
+ const QSize& contentsSize)
+{
+ return QApplication::style()->sizeFromContents(type, option, contentsSize);
+}
+
+inline QRect Qt5Graphics_Controls::subControlRect(QStyle::ComplexControl control,
+ const QStyleOptionComplex* option,
+ QStyle::SubControl subControl)
+{
+ return QApplication::style()->subControlRect(control, option, subControl);
+}
+
+inline QRect Qt5Graphics_Controls::subElementRect(QStyle::SubElement element,
+ const QStyleOption* option)
+{
+ return QApplication::style()->subElementRect(element, option);
+}
+
+void Qt5Graphics_Controls::draw(QStyle::ControlElement element, QStyleOption* option, QImage* image,
+ QStyle::State const state, QRect rect)
+{
+ const QRect& targetRect = !rect.isNull() ? rect : image->rect();
+
+ option->state |= state;
+ option->rect = downscale(targetRect);
+
+ QPainter painter(image);
+ QApplication::style()->drawControl(element, option, &painter);
+}
+
+void Qt5Graphics_Controls::draw(QStyle::PrimitiveElement element, QStyleOption* option,
+ QImage* image, QStyle::State const state, QRect rect)
+{
+ const QRect& targetRect = !rect.isNull() ? rect : image->rect();
+
+ option->state |= state;
+ option->rect = downscale(targetRect);
+
+ QPainter painter(image);
+ QApplication::style()->drawPrimitive(element, option, &painter);
+}
+
+void Qt5Graphics_Controls::draw(QStyle::ComplexControl element, QStyleOptionComplex* option,
+ QImage* image, QStyle::State const state)
+{
+ const QRect& targetRect = image->rect();
+
+ option->state |= state;
+ option->rect = downscale(targetRect);
+
+ QPainter painter(image);
+ QApplication::style()->drawComplexControl(element, option, &painter);
+}
+
+void Qt5Graphics_Controls::drawFrame(QStyle::PrimitiveElement element, QImage* image,
+ 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;
+
+ QPainter painter(image);
+ if (bClip)
+ painter.setClipRegion(QRegion(aRect).subtracted(aRect.adjusted(fw, fw, -fw, -fw)));
+ QApplication::style()->drawPrimitive(element, &option, &painter);
+}
+
+void Qt5Graphics_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 Qt5Graphics_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 Qt5Graphics_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)
+ {
+ assert(part == ControlPart::Entire);
+ QStyleOptionButton option;
+ draw(QStyle::CE_PushButton, &option, m_image.get(),
+ 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());
+ }
+ else if (part == ControlPart::Entire)
+ {
+ QStyleOptionMenuItem option;
+ draw(QStyle::CE_MenuBarEmptyArea, &option, m_image.get(),
+ 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(),
+ 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(),
+ 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(),
+ 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());
+ // Try hard to get any frame!
+ QStyleOptionFrame frame;
+ draw(QStyle::PE_FrameMenu, &frame, m_image.get());
+ draw(QStyle::PE_FrameWindow, &frame, m_image.get());
+ 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());
+ }
+ else if ((type == ControlType::Toolbar) && (part == ControlPart::Entire))
+ {
+ QStyleOptionToolBar option;
+ draw(QStyle::CE_ToolBar, &option, m_image.get(),
+ 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(),
+ vclStateValue2StateFlag(nControlState, value), aRect);
+ }
+ else if (type == ControlType::Editbox || type == ControlType::MultilineEditbox)
+ {
+ drawFrame(QStyle::PE_FrameLineEdit, m_image.get(),
+ vclStateValue2StateFlag(nControlState, value), false);
+ }
+ else if (type == ControlType::Combobox)
+ {
+ QStyleOptionComboBox option;
+ option.editable = true;
+ draw(QStyle::CC_ComboBox, &option, m_image.get(),
+ 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(),
+ vclStateValue2StateFlag(nControlState, value), true,
+ QStyle::PM_ComboBoxFrameWidth);
+ break;
+ case ControlPart::SubEdit:
+ draw(QStyle::CE_ComboBoxLabel, &option, m_image.get(),
+ vclStateValue2StateFlag(nControlState, value));
+ break;
+ case ControlPart::Entire:
+ draw(QStyle::CC_ComboBox, &option, m_image.get(),
+ vclStateValue2StateFlag(nControlState, value));
+ break;
+ case ControlPart::ButtonDown:
+ option.subControls = QStyle::SC_ComboBoxArrow;
+ draw(QStyle::CC_ComboBox, &option, m_image.get(),
+ 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());
+ }
+ else if (type == ControlType::ListHeader)
+ {
+ QStyleOptionHeader option;
+ draw(QStyle::CE_HeaderSection, &option, m_image.get(),
+ 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(),
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ else if (part == ControlPart::Focus)
+ {
+ QStyleOptionFocusRect option;
+ draw(QStyle::PE_FrameFocusRect, &option, m_image.get(),
+ 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 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(),
+ 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(),
+ 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(),
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ else if (part == ControlPart::Focus)
+ {
+ QStyleOptionFocusRect option;
+ draw(QStyle::PE_FrameFocusRect, &option, m_image.get(),
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ }
+ else if (type == ControlType::Tooltip)
+ {
+ QStyleOption option;
+ draw(QStyle::PE_PanelTipLabel, &option, m_image.get(),
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ else if (type == ControlType::Frame)
+ {
+ drawFrame(QStyle::PE_Frame, m_image.get(), 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());
+ }
+ 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());
+ }
+ else if (type == ControlType::Progress && part == ControlPart::Entire)
+ {
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ QStyleOptionProgressBarV2 option;
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ option.minimum = 0;
+ option.maximum = widgetRect.width();
+ option.progress = value.getNumericVal();
+
+ draw(QStyle::CE_ProgressBar, &option, m_image.get(),
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ else if (type == ControlType::TabItem && part == ControlPart::Entire)
+ {
+ QStyleOptionTab sot;
+ fillQStyleOptionTab(value, sot);
+ draw(QStyle::CE_TabBarTabShape, &sot, m_image.get(),
+ 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(),
+ vclStateValue2StateFlag(nControlState, value), aRect);
+ }
+ else
+ {
+ returnVal = false;
+ }
+
+ return returnVal;
+}
+
+bool Qt5Graphics_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;
+ }
+ }
+ 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);
+ 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);
+ aContentSize.setHeight(QApplication::fontMetrics().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);
+ aContentSize.setHeight(QApplication::fontMetrics().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)
+ {
+ auto nStyle = static_cast<DrawFrameFlags>(val.getNumericVal() & 0xFFF0);
+ if (nStyle & DrawFrameFlags::NoDraw)
+ {
+ 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 Qt5Graphics_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 Qt5Graphics_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 Qt5Graphics_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 Qt5Graphics_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 Qt5Graphics_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 Qt5Graphics_Controls::downscale(const QSize& size, Round eRound)
+{
+ return QSize(downscale(size.width(), eRound), downscale(size.height(), eRound));
+}
+
+inline QSize Qt5Graphics_Controls::upscale(const QSize& size, Round eRound)
+{
+ return QSize(upscale(size.width(), eRound), upscale(size.height(), eRound));
+}
+
+inline QPoint Qt5Graphics_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/Qt5Graphics_GDI.cxx b/vcl/qt5/Qt5Graphics_GDI.cxx
new file mode 100644
index 000000000..cfebca7c6
--- /dev/null
+++ b/vcl/qt5/Qt5Graphics_GDI.cxx
@@ -0,0 +1,712 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <Qt5Graphics.hxx>
+
+#include <Qt5Bitmap.hxx>
+#include <Qt5Painter.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>
+
+static 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;
+}
+
+bool Qt5Graphics::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, !getAntiAliasB2DDraw(), false);
+ m_aClipPath.swap(aPath);
+ if (!m_aClipRegion.isEmpty())
+ {
+ QRegion aRegion;
+ m_aClipRegion.swap(aRegion);
+ }
+ }
+ return true;
+}
+
+void Qt5Graphics::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 Qt5Graphics::drawPixel(long nX, long nY)
+{
+ Qt5Painter aPainter(*this);
+ aPainter.drawPoint(nX, nY);
+ aPainter.update(nX, nY, 1, 1);
+}
+
+void Qt5Graphics::drawPixel(long nX, long nY, Color nColor)
+{
+ Qt5Painter aPainter(*this);
+ aPainter.setPen(toQColor(nColor));
+ aPainter.setPen(Qt::SolidLine);
+ aPainter.drawPoint(nX, nY);
+ aPainter.update(nX, nY, 1, 1);
+}
+
+void Qt5Graphics::drawLine(long nX1, long nY1, long nX2, long nY2)
+{
+ Qt5Painter aPainter(*this);
+ aPainter.drawLine(nX1, nY1, nX2, nY2);
+
+ long tmp;
+ if (nX1 > nX2)
+ {
+ tmp = nX1;
+ nX1 = nX2;
+ nX2 = tmp;
+ }
+ if (nY1 > nY2)
+ {
+ tmp = nY1;
+ nY1 = nY2;
+ nY2 = tmp;
+ }
+ aPainter.update(nX1, nY1, nX2 - nX1 + 1, nY2 - nY1 + 1);
+}
+
+void Qt5Graphics::drawRect(long nX, long nY, long nWidth, long nHeight)
+{
+ if (SALCOLOR_NONE == m_aFillColor && SALCOLOR_NONE == m_aLineColor)
+ return;
+
+ Qt5Painter aPainter(*this, true);
+ if (SALCOLOR_NONE != m_aFillColor)
+ aPainter.fillRect(nX, nY, nWidth, nHeight, aPainter.brush());
+ if (SALCOLOR_NONE != m_aLineColor)
+ aPainter.drawRect(nX, nY, nWidth - 1, nHeight - 1);
+ aPainter.update(nX, nY, nWidth, nHeight);
+}
+
+void Qt5Graphics::drawPolyLine(sal_uInt32 nPoints, const SalPoint* pPtAry)
+{
+ if (0 == nPoints)
+ return;
+
+ Qt5Painter aPainter(*this);
+ QPoint* pPoints = new QPoint[nPoints];
+ QPoint aTopLeft(pPtAry->mnX, pPtAry->mnY);
+ QPoint aBottomRight = aTopLeft;
+ for (sal_uInt32 i = 0; i < nPoints; ++i, ++pPtAry)
+ {
+ pPoints[i] = QPoint(pPtAry->mnX, pPtAry->mnY);
+ if (pPtAry->mnX < aTopLeft.x())
+ aTopLeft.setX(pPtAry->mnX);
+ if (pPtAry->mnY < aTopLeft.y())
+ aTopLeft.setY(pPtAry->mnY);
+ if (pPtAry->mnX > aBottomRight.x())
+ aBottomRight.setX(pPtAry->mnX);
+ if (pPtAry->mnY > aBottomRight.y())
+ aBottomRight.setY(pPtAry->mnY);
+ }
+ aPainter.drawPolyline(pPoints, nPoints);
+ delete[] pPoints;
+ aPainter.update(QRect(aTopLeft, aBottomRight));
+}
+
+void Qt5Graphics::drawPolygon(sal_uInt32 nPoints, const SalPoint* pPtAry)
+{
+ Qt5Painter aPainter(*this, true);
+ QPolygon aPolygon(nPoints);
+ for (sal_uInt32 i = 0; i < nPoints; ++i, ++pPtAry)
+ aPolygon.setPoint(i, pPtAry->mnX, pPtAry->mnY);
+ aPainter.drawPolygon(aPolygon);
+ aPainter.update(aPolygon.boundingRect());
+}
+
+void Qt5Graphics::drawPolyPolygon(sal_uInt32 nPolyCount, const sal_uInt32* pPoints,
+ PCONSTSALPOINT* ppPtAry)
+{
+ // ignore invisible polygons
+ if (SALCOLOR_NONE == m_aFillColor && SALCOLOR_NONE == m_aLineColor)
+ return;
+
+ QPainterPath aPath;
+ for (sal_uInt32 nPoly = 0; nPoly < nPolyCount; nPoly++)
+ {
+ const sal_uInt32 nPoints = pPoints[nPoly];
+ if (nPoints > 1)
+ {
+ const SalPoint* pPtAry = ppPtAry[nPoly];
+ aPath.moveTo(pPtAry->mnX, pPtAry->mnY);
+ pPtAry++;
+ for (sal_uInt32 nPoint = 1; nPoint < nPoints; nPoint++, pPtAry++)
+ aPath.lineTo(pPtAry->mnX, pPtAry->mnY);
+ aPath.closeSubpath();
+ }
+ }
+
+ Qt5Painter aPainter(*this, true);
+ aPainter.drawPath(aPath);
+ aPainter.update(aPath.boundingRect());
+}
+
+bool Qt5Graphics::drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon& rPolyPolygon, double fTransparency)
+{
+ // ignore invisible polygons
+ if (SALCOLOR_NONE == m_aFillColor && SALCOLOR_NONE == m_aLineColor)
+ return true;
+ if ((fTransparency >= 1.0) || (fTransparency < 0))
+ return true;
+
+ // Fallback: Transform to DeviceCoordinates
+ basegfx::B2DPolyPolygon aPolyPolygon(rPolyPolygon);
+ aPolyPolygon.transform(rObjectToDevice);
+
+ QPainterPath aPath;
+ // ignore empty polygons
+ if (!AddPolyPolygonToPath(aPath, aPolyPolygon, !getAntiAliasB2DDraw(),
+ m_aLineColor != SALCOLOR_NONE))
+ return true;
+
+ Qt5Painter aPainter(*this, true, 255 * (1.0 - fTransparency));
+ aPainter.drawPath(aPath);
+ aPainter.update(aPath.boundingRect());
+ return true;
+}
+
+bool Qt5Graphics::drawPolyLineBezier(sal_uInt32 /*nPoints*/, const SalPoint* /*pPtAry*/,
+ const PolyFlags* /*pFlgAry*/)
+{
+ return false;
+}
+
+bool Qt5Graphics::drawPolygonBezier(sal_uInt32 /*nPoints*/, const SalPoint* /*pPtAry*/,
+ const PolyFlags* /*pFlgAry*/)
+{
+ return false;
+}
+
+bool Qt5Graphics::drawPolyPolygonBezier(sal_uInt32 /*nPoly*/, const sal_uInt32* /*pPoints*/,
+ const SalPoint* const* /*pPtAry*/,
+ const PolyFlags* const* /*pFlgAry*/)
+{
+ return false;
+}
+
+bool Qt5Graphics::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 (SALCOLOR_NONE == m_aFillColor && SALCOLOR_NONE == m_aLineColor)
+ {
+ 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 Qt5,
+ // 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(), !getAntiAliasB2DDraw(), true);
+ }
+
+ Qt5Painter 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 Qt5Graphics::drawGradient(const tools::PolyPolygon&, const Gradient&) { return false; }
+
+void Qt5Graphics::drawScaledImage(const SalTwoRect& rPosAry, const QImage& rImage)
+{
+ Qt5Painter 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 Qt5Graphics::copyArea(long nDestX, long nDestY, long nSrcX, long nSrcY, long nSrcWidth,
+ long nSrcHeight, bool /*bWindowInvalidate*/)
+{
+ if (nDestX == nSrcX && nDestY == nSrcY)
+ return;
+
+ SalTwoRect aTR(nSrcX, nSrcY, nSrcWidth, nSrcHeight, nDestX, nDestY, nSrcWidth, nSrcHeight);
+ copyBits(aTR, this);
+}
+
+void Qt5Graphics::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 || this == 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<Qt5Graphics*>(pSrcGraphics)->m_pQImage;
+
+ drawScaledImage(aPosAry, *pImage);
+}
+
+void Qt5Graphics::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap)
+{
+ if (rPosAry.mnSrcWidth <= 0 || rPosAry.mnSrcHeight <= 0 || rPosAry.mnDestWidth <= 0
+ || rPosAry.mnDestHeight <= 0)
+ return;
+
+ Qt5Bitmap aRGBABitmap;
+ if (rSalBitmap.GetBitCount() == 4)
+ aRGBABitmap.Create(rSalBitmap, 32);
+ const QImage* pImage = (rSalBitmap.GetBitCount() != 4)
+ ? static_cast<const Qt5Bitmap*>(&rSalBitmap)->GetQImage()
+ : aRGBABitmap.GetQImage();
+ assert(pImage);
+
+ drawScaledImage(rPosAry, *pImage);
+}
+
+void Qt5Graphics::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 Qt5Graphics::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> Qt5Graphics::getBitmap(long nX, long nY, long nWidth, long nHeight)
+{
+ return std::make_shared<Qt5Bitmap>(m_pQImage->copy(nX, nY, nWidth, nHeight));
+}
+
+Color Qt5Graphics::getPixel(long nX, long nY) { return m_pQImage->pixel(nX, nY); }
+
+void Qt5Graphics::invert(long nX, long nY, long nWidth, long nHeight, SalInvert nFlags)
+{
+ Qt5Painter 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 Qt5Graphics::invert(sal_uInt32 /*nPoints*/, const SalPoint* /*pPtAry*/, SalInvert /*nFlags*/)
+{
+}
+
+bool Qt5Graphics::drawEPS(long /*nX*/, long /*nY*/, long /*nWidth*/, long /*nHeight*/,
+ void* /*pPtr*/, sal_uInt32 /*nSize*/)
+{
+ return false;
+}
+
+bool Qt5Graphics::blendBitmap(const SalTwoRect&, const SalBitmap& /*rBitmap*/) { return false; }
+
+bool Qt5Graphics::blendAlphaBitmap(const SalTwoRect&, const SalBitmap& /*rSrcBitmap*/,
+ const SalBitmap& /*rMaskBitmap*/,
+ const SalBitmap& /*rAlphaBitmap*/)
+{
+ return false;
+}
+
+static bool getAlphaImage(const SalBitmap& rSourceBitmap, const SalBitmap& rAlphaBitmap,
+ QImage& rAlphaImage)
+{
+ if (rAlphaBitmap.GetBitCount() != 8 && rAlphaBitmap.GetBitCount() != 1)
+ {
+ SAL_WARN("vcl.gdi", "unsupported alpha depth case: " << rAlphaBitmap.GetBitCount());
+ return false;
+ }
+
+ Qt5Bitmap aRGBABitmap;
+ if (rSourceBitmap.GetBitCount() == 4)
+ aRGBABitmap.Create(rSourceBitmap, 32);
+ const QImage* pBitmap = (rSourceBitmap.GetBitCount() != 4)
+ ? static_cast<const Qt5Bitmap*>(&rSourceBitmap)->GetQImage()
+ : aRGBABitmap.GetQImage();
+ const QImage* pAlpha = static_cast<const Qt5Bitmap*>(&rAlphaBitmap)->GetQImage();
+ rAlphaImage = pBitmap->convertToFormat(Qt5_DefaultFormat32);
+
+ if (rAlphaBitmap.GetBitCount() == 8)
+ {
+ for (int y = 0; y < rAlphaImage.height(); ++y)
+ {
+ uchar* image_line = rAlphaImage.scanLine(y);
+ const uchar* alpha_line = pAlpha->scanLine(y);
+ for (int x = 0; x < rAlphaImage.width(); ++x, image_line += 4)
+ image_line[3] = 255 - alpha_line[x];
+ }
+ }
+ else
+ {
+ for (int y = 0; y < rAlphaImage.height(); ++y)
+ {
+ uchar* image_line = rAlphaImage.scanLine(y);
+ const uchar* alpha_line = pAlpha->scanLine(y);
+ for (int x = 0; x < rAlphaImage.width(); ++x, image_line += 4)
+ {
+ if (x && !(x % 8))
+ ++alpha_line;
+ if (0 != (*alpha_line & (1 << (7 - x % 8))))
+ image_line[3] = 0;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool Qt5Graphics::drawAlphaBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSourceBitmap,
+ const SalBitmap& rAlphaBitmap)
+{
+ QImage aImage;
+ if (!getAlphaImage(rSourceBitmap, rAlphaBitmap, aImage))
+ return false;
+ drawScaledImage(rPosAry, aImage);
+ return true;
+}
+
+bool Qt5Graphics::drawTransformedBitmap(const basegfx::B2DPoint& rNull, const basegfx::B2DPoint& rX,
+ const basegfx::B2DPoint& rY, const SalBitmap& rSourceBitmap,
+ const SalBitmap* pAlphaBitmap)
+{
+ QImage aImage;
+ if (pAlphaBitmap && !getAlphaImage(rSourceBitmap, *pAlphaBitmap, aImage))
+ return false;
+ else
+ {
+ Qt5Bitmap aRGBABitmap;
+ if (rSourceBitmap.GetBitCount() == 4)
+ aRGBABitmap.Create(rSourceBitmap, 32);
+ const QImage* pBitmap = (rSourceBitmap.GetBitCount() != 4)
+ ? static_cast<const Qt5Bitmap*>(&rSourceBitmap)->GetQImage()
+ : aRGBABitmap.GetQImage();
+ aImage = pBitmap->convertToFormat(Qt5_DefaultFormat32);
+ }
+
+ Qt5Painter aPainter(*this);
+ const basegfx::B2DVector aXRel = rX - rNull;
+ const basegfx::B2DVector aYRel = rY - rNull;
+ 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 Qt5Graphics::drawAlphaRect(long nX, long nY, long nWidth, long nHeight,
+ sal_uInt8 nTransparency)
+{
+ if (SALCOLOR_NONE == m_aFillColor && SALCOLOR_NONE == m_aLineColor)
+ return true;
+ assert(nTransparency <= 100);
+ if (nTransparency > 100)
+ nTransparency = 100;
+ Qt5Painter aPainter(*this, true, (100 - nTransparency) * (255.0 / 100));
+ if (SALCOLOR_NONE != m_aFillColor)
+ aPainter.fillRect(nX, nY, nWidth, nHeight, aPainter.brush());
+ if (SALCOLOR_NONE != m_aLineColor)
+ aPainter.drawRect(nX, nY, nWidth - 1, nHeight - 1);
+ aPainter.update(nX, nY, nWidth, nHeight);
+ return true;
+}
+
+void Qt5Graphics::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 || !m_pFrame->GetQWidget()->window()->windowHandle())
+ return;
+
+ QScreen* pScreen = m_pFrame->GetQWidget()->window()->windowHandle()->screen();
+ rDPIX = pScreen->logicalDotsPerInchX() * pScreen->devicePixelRatio() + 0.5;
+ rDPIY = pScreen->logicalDotsPerInchY() * pScreen->devicePixelRatio() + 0.5;
+}
+
+sal_uInt16 Qt5Graphics::GetBitCount() const { return getFormatBits(m_pQImage->format()); }
+
+long Qt5Graphics::GetGraphicsWidth() const { return m_pQImage->width(); }
+
+void Qt5Graphics::SetLineColor() { m_aLineColor = SALCOLOR_NONE; }
+
+void Qt5Graphics::SetLineColor(Color nColor) { m_aLineColor = nColor; }
+
+void Qt5Graphics::SetFillColor() { m_aFillColor = SALCOLOR_NONE; }
+
+void Qt5Graphics::SetFillColor(Color nColor) { m_aFillColor = nColor; }
+
+void Qt5Graphics::SetXORMode(bool bSet, bool)
+{
+ if (bSet)
+ m_eCompositionMode = QPainter::CompositionMode_Xor;
+ else
+ m_eCompositionMode = QPainter::CompositionMode_SourceOver;
+}
+
+void Qt5Graphics::SetROPLineColor(SalROPColor /*nROPColor*/) {}
+
+void Qt5Graphics::SetROPFillColor(SalROPColor /*nROPColor*/) {}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5Graphics_Text.cxx b/vcl/qt5/Qt5Graphics_Text.cxx
new file mode 100644
index 000000000..ded886efd
--- /dev/null
+++ b/vcl/qt5/Qt5Graphics_Text.cxx
@@ -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 .
+ */
+
+#include <Qt5Graphics.hxx>
+#include <Qt5FontFace.hxx>
+#include <Qt5Font.hxx>
+#include <Qt5Painter.hxx>
+
+#include <vcl/fontcharmap.hxx>
+#include <unx/geninst.h>
+#include <unx/fontmanager.hxx>
+#include <unx/glyphcache.hxx>
+#include <unx/genpspgraphics.h>
+
+#include <sallayout.hxx>
+#include <PhysicalFontCollection.hxx>
+
+#include <QtGui/QGlyphRun>
+#include <QtGui/QFontDatabase>
+#include <QtGui/QRawFont>
+#include <QtCore/QStringList>
+
+void Qt5Graphics::SetTextColor(Color nColor) { m_aTextColor = nColor; }
+
+void Qt5Graphics::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<Qt5Font*>(pReqFont);
+}
+
+void Qt5Graphics::GetFontMetric(ImplFontMetricDataRef& rFMD, int nFallbackLevel)
+{
+ QRawFont aRawFont(QRawFont::fromFont(*m_pTextStyle[nFallbackLevel]));
+ Qt5FontFace::fillAttributesFromQFont(*m_pTextStyle[nFallbackLevel], *rFMD);
+
+ rFMD->ImplCalcLineSpacing(m_pTextStyle[nFallbackLevel].get());
+
+ rFMD->SetSlant(0);
+ rFMD->SetWidth(aRawFont.averageCharWidth());
+
+ rFMD->SetMinKashida(m_pTextStyle[nFallbackLevel]->GetKashidaWidth());
+}
+
+FontCharMapRef Qt5Graphics::GetFontCharMap() const
+{
+ if (!m_pTextStyle[0])
+ return FontCharMapRef(new FontCharMap());
+ return static_cast<const Qt5FontFace*>(m_pTextStyle[0]->GetFontFace())->GetFontCharMap();
+}
+
+bool Qt5Graphics::GetFontCapabilities(vcl::FontCapabilities& rFontCapabilities) const
+{
+ if (!m_pTextStyle[0])
+ return false;
+ return static_cast<const Qt5FontFace*>(m_pTextStyle[0]->GetFontFace())
+ ->GetFontCapabilities(rFontCapabilities);
+}
+
+void Qt5Graphics::GetDevFontList(PhysicalFontCollection* pPFC)
+{
+ static const bool bUseFontconfig = (nullptr == getenv("SAL_VCL_QT5_NO_FONTCONFIG"));
+
+ m_pFontCollection = pPFC;
+ if (pPFC->Count())
+ return;
+
+ QFontDatabase aFDB;
+ FreetypeManager& rFontManager = FreetypeManager::get();
+ psp::PrintFontManager& rMgr = psp::PrintFontManager::get();
+ ::std::vector<psp::fontID> aList;
+ psp::FastPrintFontInfo aInfo;
+
+ rMgr.getFontList(aList);
+ for (auto const& elem : aList)
+ {
+ if (!rMgr.getFontFastInfo(elem, aInfo))
+ continue;
+
+ // normalize face number to the FreetypeManager
+ int nFaceNum = rMgr.getFontFaceNumber(aInfo.m_nID);
+ int nVariantNum = rMgr.getFontFaceVariation(aInfo.m_nID);
+
+ // inform FreetypeManager about this font provided by the PsPrint subsystem
+ FontAttributes aDFA = GenPspGraphics::Info2FontAttributes(aInfo);
+ aDFA.IncreaseQualityBy(4096);
+ const OString& rFileName = rMgr.getFontFileSysPath(aInfo.m_nID);
+ rFontManager.AddFontFile(rFileName, nFaceNum, nVariantNum, aInfo.m_nID, aDFA);
+ }
+
+ if (bUseFontconfig)
+ SalGenericInstance::RegisterFontSubstitutors(pPFC);
+
+ for (auto& family : aFDB.families())
+ for (auto& style : aFDB.styles(family))
+ pPFC->Add(Qt5FontFace::fromQFontDatabase(family, style));
+}
+
+void Qt5Graphics::ClearDevFontCache() {}
+
+bool Qt5Graphics::AddTempDevFont(PhysicalFontCollection*, const OUString& /*rFileURL*/,
+ const OUString& /*rFontName*/)
+{
+ return false;
+}
+
+bool Qt5Graphics::CreateFontSubset(const OUString& /*rToFile*/, const PhysicalFontFace* /*pFont*/,
+ const sal_GlyphId* /*pGlyphIds*/, const sal_uInt8* /*pEncoding*/,
+ sal_Int32* /*pWidths*/, int /*nGlyphs*/,
+ FontSubsetInfo& /*rInfo*/)
+{
+ return false;
+}
+
+const void* Qt5Graphics::GetEmbedFontData(const PhysicalFontFace*, long* /*pDataLen*/)
+{
+ return nullptr;
+}
+
+void Qt5Graphics::FreeEmbedFontData(const void* /*pData*/, long /*nDataLen*/) {}
+
+void Qt5Graphics::GetGlyphWidths(const PhysicalFontFace* /*pPFF*/, bool /*bVertical*/,
+ std::vector<sal_Int32>& /*rWidths*/, Ucs2UIntMap& /*rUnicodeEnc*/)
+{
+}
+
+namespace
+{
+class Qt5CommonSalLayout : public GenericSalLayout
+{
+public:
+ Qt5CommonSalLayout(LogicalFontInstance& rLFI)
+ : GenericSalLayout(rLFI)
+ {
+ }
+
+ void SetOrientation(int nOrientation) { mnOrientation = nOrientation; }
+};
+}
+
+std::unique_ptr<GenericSalLayout> Qt5Graphics::GetTextLayout(int nFallbackLevel)
+{
+ assert(m_pTextStyle[nFallbackLevel]);
+ if (!m_pTextStyle[nFallbackLevel])
+ return nullptr;
+ return std::make_unique<Qt5CommonSalLayout>(*m_pTextStyle[nFallbackLevel]);
+}
+
+void Qt5Graphics::DrawTextLayout(const GenericSalLayout& rLayout)
+{
+ const Qt5Font* pFont = static_cast<const Qt5Font*>(&rLayout.GetFont());
+ assert(pFont);
+ QRawFont aRawFont(QRawFont::fromFont(*pFont));
+
+ QVector<quint32> glyphIndexes;
+ QVector<QPointF> positions;
+
+ // prevent glyph rotation inside the SalLayout
+ // probably better to add a parameter to GetNextGlyphs?
+ Qt5CommonSalLayout* pQt5Layout
+ = static_cast<Qt5CommonSalLayout*>(const_cast<GenericSalLayout*>(&rLayout));
+ int nOrientation = rLayout.GetOrientation();
+ if (nOrientation)
+ pQt5Layout->SetOrientation(0);
+
+ Point aPos;
+ const GlyphItem* pGlyph;
+ int nStart = 0;
+ while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart))
+ {
+ glyphIndexes.push_back(pGlyph->glyphId());
+ positions.push_back(QPointF(aPos.X(), aPos.Y()));
+ }
+
+ // seems to be common to try to layout an empty string...
+ if (positions.empty())
+ return;
+
+ if (nOrientation)
+ pQt5Layout->SetOrientation(nOrientation);
+
+ QGlyphRun aGlyphRun;
+ aGlyphRun.setPositions(positions);
+ aGlyphRun.setGlyphIndexes(glyphIndexes);
+ aGlyphRun.setRawFont(aRawFont);
+
+ Qt5Painter aPainter(*this);
+ 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) / 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/Qt5Instance.cxx b/vcl/qt5/Qt5Instance.cxx
new file mode 100644
index 000000000..06b959b91
--- /dev/null
+++ b/vcl/qt5/Qt5Instance.cxx
@@ -0,0 +1,651 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <Qt5Instance.hxx>
+#include <Qt5Instance.moc>
+
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+
+#include <Qt5Bitmap.hxx>
+#include <Qt5Clipboard.hxx>
+#include <Qt5Data.hxx>
+#include <Qt5DragAndDrop.hxx>
+#include <Qt5FilePicker.hxx>
+#include <Qt5Frame.hxx>
+#include <Qt5Menu.hxx>
+#include <Qt5Object.hxx>
+#include <Qt5OpenGLContext.hxx>
+#include "Qt5SvpVirtualDevice.hxx"
+#include <Qt5System.hxx>
+#include <Qt5Timer.hxx>
+#include <Qt5VirtualDevice.hxx>
+
+#include <headless/svpvd.hxx>
+
+#include <QtCore/QAbstractEventDispatcher>
+#include <QtCore/QThread>
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QWidget>
+
+#include <vclpluginapi.h>
+#include <tools/debug.hxx>
+#include <comphelper/flagguard.hxx>
+#include <sal/log.hxx>
+#include <osl/process.h>
+#include <unx/gstsink.hxx>
+#include <headless/svpbmp.hxx>
+
+#include <mutex>
+#include <condition_variable>
+
+namespace
+{
+/// TODO: not much Qt5 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 Qt5; 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 Qt5YieldMutex : 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 Qt5YieldMutex::IsCurrentThread() const
+{
+ auto const* pSalInst(static_cast<Qt5Instance const*>(GetSalData()->m_pInstance));
+ assert(pSalInst);
+ if (pSalInst->IsMainThread() && m_bNoYieldLock)
+ {
+ return true; // main thread has borrowed SolarMutex
+ }
+ return SalYieldMutex::IsCurrentThread();
+}
+
+void Qt5YieldMutex::doAcquire(sal_uInt32 nLockCount)
+{
+ auto const* pSalInst(static_cast<Qt5Instance const*>(GetSalData()->m_pInstance));
+ 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 Qt5YieldMutex::doRelease(bool const bUnlockAll)
+{
+ auto const* pSalInst(static_cast<Qt5Instance const*>(GetSalData()->m_pInstance));
+ 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 Qt5 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 Qt5Instance::RunInMainThread(std::function<void()> func)
+{
+ DBG_TESTSOLARMUTEX();
+ if (IsMainThread())
+ {
+ func();
+ return;
+ }
+
+ Qt5YieldMutex* const pMutex(static_cast<Qt5YieldMutex*>(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();
+ }
+ // wake up main thread in case it is blocked on event queue
+ // TriggerUserEventProcessing() appears to be insufficient in case the
+ // main thread does QEventLoop::WaitForMoreEvents
+ Q_EMIT ImplRunInMainSignal();
+ {
+ std::unique_lock<std::mutex> g(pMutex->m_RunInMainMutex);
+ pMutex->m_ResultCondition.wait(g, [pMutex]() { return pMutex->m_isResultReady; });
+ pMutex->m_isResultReady = false;
+ }
+}
+
+void Qt5Instance::ImplRunInMain()
+{
+ SolarMutexGuard g; // trigger the dispatch code in Qt5YieldMutex::doAcquire
+ (void)this; // suppress unhelpful [loplugin:staticmethods]; can't be static
+}
+
+Qt5Instance::Qt5Instance(std::unique_ptr<QApplication>& pQApp, bool bUseCairo)
+ : SalGenericInstance(std::make_unique<Qt5YieldMutex>())
+ , m_postUserEventId(-1)
+ , m_bUseCairo(bUseCairo)
+ , m_pQApplication(std::move(pQApp))
+ , m_aUpdateStyleTimer("vcl::qt5 m_aUpdateStyleTimer")
+ , m_bUpdateFonts(false)
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if (bUseCairo)
+ pSVData->maAppData.mxToolkitName = OUString("qt5+cairo");
+ else
+ pSVData->maAppData.mxToolkitName = OUString("qt5");
+
+ m_postUserEventId = QEvent::registerEventType();
+
+ // 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);
+ connect(this, &Qt5Instance::ImplRunInMainSignal, this, &Qt5Instance::ImplRunInMain,
+ Qt::QueuedConnection); // no Blocking!
+
+ // this one needs to be queued non-blocking
+ // in order to have this event arriving to correct event processing loop
+ connect(this, &Qt5Instance::deleteObjectLaterSignal, this,
+ [](QObject* pObject) { Qt5Instance::deleteObjectLater(pObject); },
+ Qt::QueuedConnection);
+
+ m_aUpdateStyleTimer.SetTimeout(50);
+ m_aUpdateStyleTimer.SetInvokeHandler(LINK(this, Qt5Instance, updateStyleHdl));
+}
+
+Qt5Instance::~Qt5Instance()
+{
+ // force freeing the QApplication before freeing the arguments,
+ // as it uses references to the provided arguments!
+ m_pQApplication.reset();
+}
+
+void Qt5Instance::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 Qt5Instance::deleteObjectLater(QObject* pObject) { pObject->deleteLater(); }
+
+SalFrame* Qt5Instance::CreateChildFrame(SystemParentData* /*pParent*/, SalFrameStyleFlags nStyle)
+{
+ return new Qt5Frame(nullptr, nStyle, m_bUseCairo);
+}
+
+SalFrame* Qt5Instance::CreateFrame(SalFrame* pParent, SalFrameStyleFlags nStyle)
+{
+ assert(!pParent || dynamic_cast<Qt5Frame*>(pParent));
+ return new Qt5Frame(static_cast<Qt5Frame*>(pParent), nStyle, m_bUseCairo);
+}
+
+void Qt5Instance::DestroyFrame(SalFrame* pFrame)
+{
+ if (pFrame)
+ {
+ assert(dynamic_cast<Qt5Frame*>(pFrame));
+ Q_EMIT deleteObjectLaterSignal(static_cast<Qt5Frame*>(pFrame));
+ }
+}
+
+SalObject* Qt5Instance::CreateObject(SalFrame* pParent, SystemWindowData*, bool bShow)
+{
+ assert(!pParent || dynamic_cast<Qt5Frame*>(pParent));
+ return new Qt5Object(static_cast<Qt5Frame*>(pParent), bShow);
+}
+
+void Qt5Instance::DestroyObject(SalObject* pObject)
+{
+ if (pObject)
+ {
+ assert(dynamic_cast<Qt5Object*>(pObject));
+ Q_EMIT deleteObjectLaterSignal(static_cast<Qt5Object*>(pObject));
+ }
+}
+
+std::unique_ptr<SalVirtualDevice> Qt5Instance::CreateVirtualDevice(SalGraphics* pGraphics,
+ long& nDX, long& nDY,
+ DeviceFormat eFormat,
+ const SystemGraphicsData* pGd)
+{
+ if (m_bUseCairo)
+ {
+ SvpSalGraphics* pSvpSalGraphics = dynamic_cast<Qt5SvpGraphics*>(pGraphics);
+ 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 Qt5SvpVirtualDevice(eFormat, pSvpSalGraphics->getSurface(), pPreExistingTarget));
+ pVD->SetSize(nDX, nDY);
+ return pVD;
+ }
+ else
+ {
+ std::unique_ptr<SalVirtualDevice> pVD(new Qt5VirtualDevice(eFormat, 1));
+ pVD->SetSize(nDX, nDY);
+ return pVD;
+ }
+}
+
+std::unique_ptr<SalMenu> Qt5Instance::CreateMenu(bool bMenuBar, Menu* pVCLMenu)
+{
+ std::unique_ptr<SalMenu> pRet;
+ RunInMainThread([&pRet, bMenuBar, pVCLMenu]() {
+ Qt5Menu* pSalMenu = new Qt5Menu(bMenuBar);
+ pRet.reset(pSalMenu);
+ pSalMenu->SetMenu(pVCLMenu);
+ });
+ assert(pRet);
+ return pRet;
+}
+
+std::unique_ptr<SalMenuItem> Qt5Instance::CreateMenuItem(const SalItemParams& rItemData)
+{
+ return std::unique_ptr<SalMenuItem>(new Qt5MenuItem(&rItemData));
+}
+
+SalTimer* Qt5Instance::CreateSalTimer() { return new Qt5Timer(); }
+
+SalSystem* Qt5Instance::CreateSalSystem() { return new Qt5System; }
+
+std::shared_ptr<SalBitmap> Qt5Instance::CreateSalBitmap()
+{
+ if (m_bUseCairo)
+ return std::make_shared<SvpSalBitmap>();
+ else
+ return std::make_shared<Qt5Bitmap>();
+}
+
+bool Qt5Instance::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 Qt5Instance::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 Qt5Instance::AnyInput(VclInputFlags /*nType*/) { return false; }
+
+OUString Qt5Instance::GetConnectionIdentifier() { return OUString(); }
+
+void Qt5Instance::AddToRecentDocumentList(const OUString&, const OUString&, const OUString&) {}
+
+OpenGLContext* Qt5Instance::CreateOpenGLContext() { return new Qt5OpenGLContext; }
+
+bool Qt5Instance::IsMainThread() const
+{
+ return !qApp || (qApp->thread() == QThread::currentThread());
+}
+
+void Qt5Instance::TriggerUserEventProcessing()
+{
+ QApplication::postEvent(this, new QEvent(QEvent::Type(m_postUserEventId)));
+}
+
+void Qt5Instance::ProcessEvent(SalUserEvent aEvent)
+{
+ aEvent.m_pFrame->CallCallback(aEvent.m_nEvent, aEvent.m_pData);
+}
+
+Qt5FilePicker*
+Qt5Instance::createPicker(css::uno::Reference<css::uno::XComponentContext> const& context,
+ QFileDialog::FileMode eMode)
+{
+ if (!IsMainThread())
+ {
+ SolarMutexGuard g;
+ Qt5FilePicker* pPicker;
+ RunInMainThread([&, this]() { pPicker = createPicker(context, eMode); });
+ assert(pPicker);
+ return pPicker;
+ }
+
+ return new Qt5FilePicker(context, eMode);
+}
+
+css::uno::Reference<css::ui::dialogs::XFilePicker2>
+Qt5Instance::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>
+Qt5Instance::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>
+Qt5Instance::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 Qt5Instance::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 = Qt5Clipboard::create(sel);
+ if (xClipboard.is())
+ m_aClipboards[sel] = xClipboard;
+
+ return xClipboard;
+}
+
+css::uno::Reference<css::uno::XInterface> Qt5Instance::CreateDragSource()
+{
+ return css::uno::Reference<css::uno::XInterface>(
+ static_cast<cppu::OWeakObject*>(new Qt5DragSource()));
+}
+
+css::uno::Reference<css::uno::XInterface> Qt5Instance::CreateDropTarget()
+{
+ return css::uno::Reference<css::uno::XInterface>(
+ static_cast<cppu::OWeakObject*>(new Qt5DropTarget()));
+}
+
+IMPL_LINK_NOARG(Qt5Instance, 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 Qt5Instance::UpdateStyle(bool bFontsChanged)
+{
+ if (bFontsChanged)
+ m_bUpdateFonts = true;
+ if (!m_aUpdateStyleTimer.IsActive())
+ m_aUpdateStyleTimer.Start();
+}
+
+void* Qt5Instance::CreateGStreamerSink(const SystemChildWindow* pWindow)
+{
+#if 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.qt5", "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
+ (void*)pWindow;
+ return nullptr;
+#endif
+}
+
+void Qt5Instance::AllocFakeCmdlineArgs(std::unique_ptr<char* []>& rFakeArgv,
+ std::unique_ptr<int>& rFakeArgc,
+ std::vector<FreeableCStr>& rFakeArgvFreeable)
+{
+ OString aVersion(qVersion());
+ SAL_INFO("vcl.qt5", "qt version string is " << aVersion);
+
+ const sal_uInt32 nParams = osl_getCommandArgCount();
+ OString aDisplay;
+ 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);
+ 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 Qt5Instance::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> Qt5Instance::CreateQApplication(int& nArgc, char** pArgv)
+{
+ QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+ // for scaled icons in the native menus
+ QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
+
+ 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;
+}
+
+extern "C" {
+VCLPLUG_QT5_PUBLIC SalInstance* create_SalInstance()
+{
+ static const bool bUseCairo = (nullptr != getenv("SAL_VCL_QT5_USE_CAIRO"));
+
+ std::unique_ptr<char* []> pFakeArgv;
+ std::unique_ptr<int> pFakeArgc;
+ std::vector<FreeableCStr> aFakeArgvFreeable;
+ Qt5Instance::AllocFakeCmdlineArgs(pFakeArgv, pFakeArgc, aFakeArgvFreeable);
+
+ std::unique_ptr<QApplication> pQApp
+ = Qt5Instance::CreateQApplication(*pFakeArgc, pFakeArgv.get());
+
+ Qt5Instance* pInstance = new Qt5Instance(pQApp, bUseCairo);
+ pInstance->MoveFakeCmdlineArgs(pFakeArgv, pFakeArgc, aFakeArgvFreeable);
+
+ new Qt5Data(pInstance);
+
+ return pInstance;
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5Instance_Print.cxx b/vcl/qt5/Qt5Instance_Print.cxx
new file mode 100644
index 000000000..cab5757a3
--- /dev/null
+++ b/vcl/qt5/Qt5Instance_Print.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 <Qt5Instance.hxx>
+#include <Qt5Printer.hxx>
+
+#include <vcl/svapp.hxx>
+#include <vcl/timer.hxx>
+#include <printerinfomanager.hxx>
+
+#include <jobset.h>
+#include <print.h>
+#include <salptype.hxx>
+#include <saldatabasic.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())
+ aDir = OStringToOUString(OString(getenv("HOME")), osl_getThreadTextEncoding());
+ break;
+ }
+ }
+ return aDir;
+}
+
+SalInfoPrinter* Qt5Instance::CreateInfoPrinter(SalPrinterQueueInfo* pQueueInfo,
+ ImplJobSetup* pJobSetup)
+{
+ // create and initialize SalInfoPrinter
+ PspSalInfoPrinter* pPrinter = new PspSalInfoPrinter;
+ configurePspInfoPrinter(pPrinter, pQueueInfo, pJobSetup);
+
+ return pPrinter;
+}
+
+void Qt5Instance::DestroyInfoPrinter(SalInfoPrinter* pPrinter) { delete pPrinter; }
+
+std::unique_ptr<SalPrinter> Qt5Instance::CreatePrinter(SalInfoPrinter* pInfoPrinter)
+{
+ // create and initialize SalPrinter
+ Qt5Printer* pPrinter = new Qt5Printer(pInfoPrinter);
+ pPrinter->m_aJobData = static_cast<PspSalInfoPrinter*>(pInfoPrinter)->m_aJobData;
+
+ return std::unique_ptr<SalPrinter>(pPrinter);
+}
+
+void Qt5Instance::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 Qt5Instance::GetPrinterQueueState(SalPrinterQueueInfo*) {}
+
+OUString Qt5Instance::GetDefaultPrinter()
+{
+ PrinterInfoManager& rManager(PrinterInfoManager::get());
+ return rManager.getDefaultPrinter();
+}
+
+void Qt5Instance::PostPrintersChanged() {}
+
+std::unique_ptr<GenPspGraphics> Qt5Instance::CreatePrintGraphics()
+{
+ return std::make_unique<GenPspGraphics>();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5MainWindow.cxx b/vcl/qt5/Qt5MainWindow.cxx
new file mode 100644
index 000000000..45d726ba2
--- /dev/null
+++ b/vcl/qt5/Qt5MainWindow.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 <Qt5MainWindow.hxx>
+#include <Qt5MainWindow.moc>
+#include <Qt5AccessibleWidget.hxx>
+
+#include <QtGui/QAccessible>
+#include <QtGui/QCloseEvent>
+
+Qt5MainWindow::Qt5MainWindow(Qt5Frame& rFrame, Qt::WindowFlags f)
+ : QMainWindow(nullptr, f)
+ , m_rFrame(rFrame)
+{
+ QAccessible::installFactory(Qt5AccessibleWidget::customFactory);
+}
+
+void Qt5MainWindow::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 Qt5MainWindow::moveEvent(QMoveEvent* pEvent)
+{
+ const qreal fRatio = m_rFrame.devicePixelRatioF();
+ m_rFrame.maGeometry.nX = round(pEvent->pos().x() * fRatio);
+ m_rFrame.maGeometry.nY = round(pEvent->pos().y() * fRatio);
+ m_rFrame.CallCallback(SalEvent::Move, nullptr);
+}
diff --git a/vcl/qt5/Qt5Menu.cxx b/vcl/qt5/Qt5Menu.cxx
new file mode 100644
index 000000000..986152470
--- /dev/null
+++ b/vcl/qt5/Qt5Menu.cxx
@@ -0,0 +1,704 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <Qt5Menu.hxx>
+#include <Qt5Menu.moc>
+
+#include <Qt5Frame.hxx>
+#include <Qt5Instance.hxx>
+#include <Qt5MainWindow.hxx>
+
+#include <QtWidgets/QMenuBar>
+#include <QtWidgets/QPushButton>
+
+#include <o3tl/safeint.hxx>
+#include <vcl/svapp.hxx>
+#include <sal/log.hxx>
+
+#include <strings.hrc>
+#include <bitmaps.hlst>
+
+#include <vcl/floatwin.hxx>
+#include <window.h>
+
+Qt5Menu::Qt5Menu(bool bMenuBar)
+ : mpVCLMenu(nullptr)
+ , mpParentSalMenu(nullptr)
+ , mpFrame(nullptr)
+ , mbMenuBar(bMenuBar)
+ , mpQMenuBar(nullptr)
+ , mpQMenu(nullptr)
+ , mpCloseButton(nullptr)
+{
+}
+
+bool Qt5Menu::VisibleMenuBar() { return true; }
+
+void Qt5Menu::InsertMenuItem(Qt5MenuItem* 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 (mpQMenuBar)
+ {
+ QMenu* pQMenu = new QMenu(toQString(aText), nullptr);
+ 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();
+ }
+
+ if (pSalMenuItem->mpSubMenu)
+ {
+ // submenu
+ QMenu* pQMenu = new QMenu(toQString(aText), nullptr);
+ 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);
+
+ const Qt5Frame* pFrame = GetFrame();
+ if (pFrame)
+ pAction->setShortcut(toQString(nAccelKey.GetName(pFrame->GetWindow())));
+
+ connect(pAction, &QAction::triggered, this,
+ [pSalMenuItem] { slotMenuTriggered(pSalMenuItem); });
+ }
+ }
+ }
+
+ QAction* pAction = pSalMenuItem->getAction();
+ if (pAction)
+ {
+ pAction->setEnabled(pSalMenuItem->mbEnabled);
+ pAction->setVisible(pSalMenuItem->mbVisible);
+ }
+}
+
+void Qt5Menu::ReinitializeActionGroup(unsigned nPos)
+{
+ const unsigned nCount = GetItemCount();
+
+ if (nCount == 0)
+ {
+ return;
+ }
+
+ if (nPos == MENU_APPEND)
+ {
+ nPos = nCount - 1;
+ }
+ else if (nPos >= nCount)
+ {
+ return;
+ }
+
+ Qt5MenuItem* pPrevItem = (nPos > 0) ? GetItemAtPos(nPos - 1) : nullptr;
+ Qt5MenuItem* pCurrentItem = GetItemAtPos(nPos);
+ Qt5MenuItem* 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)
+ {
+ Qt5MenuItem* 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)
+ {
+ Qt5MenuItem* pModifiedItem = GetItemAtPos(idx);
+
+ if ((!pModifiedItem) || (!pModifiedItem->mpActionGroup))
+ {
+ break;
+ }
+
+ pModifiedItem->mpActionGroup = pCurrentItem->mpActionGroup;
+ }
+ }
+ }
+}
+
+void Qt5Menu::ResetAllActionGroups()
+{
+ for (unsigned nItem = 0; nItem < GetItemCount(); ++nItem)
+ {
+ Qt5MenuItem* pSalMenuItem = GetItemAtPos(nItem);
+ pSalMenuItem->mpActionGroup.reset();
+ }
+}
+
+void Qt5Menu::UpdateActionGroupItem(const Qt5MenuItem* 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 Qt5Menu::InsertItem(SalMenuItem* pSalMenuItem, unsigned nPos)
+{
+ SolarMutexGuard aGuard;
+ Qt5MenuItem* pItem = static_cast<Qt5MenuItem*>(pSalMenuItem);
+
+ if (nPos == MENU_APPEND)
+ maItems.push_back(pItem);
+ else
+ maItems.insert(maItems.begin() + nPos, pItem);
+
+ pItem->mpParentMenu = this;
+
+ InsertMenuItem(pItem, nPos);
+}
+
+void Qt5Menu::RemoveItem(unsigned nPos)
+{
+ SolarMutexGuard aGuard;
+
+ if (nPos < maItems.size())
+ {
+ Qt5MenuItem* 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 Qt5Menu::SetSubMenu(SalMenuItem* pSalMenuItem, SalMenu* pSubMenu, unsigned nPos)
+{
+ SolarMutexGuard aGuard;
+ Qt5MenuItem* pItem = static_cast<Qt5MenuItem*>(pSalMenuItem);
+ Qt5Menu* pQSubMenu = static_cast<Qt5Menu*>(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 Qt5Menu::SetFrame(const SalFrame* pFrame)
+{
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ assert(pSalInst);
+ if (!pSalInst->IsMainThread())
+ {
+ pSalInst->RunInMainThread([this, pFrame]() { SetFrame(pFrame); });
+ return;
+ }
+
+ SolarMutexGuard aGuard;
+ assert(mbMenuBar);
+ mpFrame = const_cast<Qt5Frame*>(static_cast<const Qt5Frame*>(pFrame));
+
+ mpFrame->SetMenu(this);
+
+ Qt5MainWindow* pMainWindow = mpFrame->GetTopLevelWindow();
+ if (pMainWindow)
+ {
+ mpQMenuBar = pMainWindow->menuBar();
+ if (mpQMenuBar)
+ {
+ mpQMenuBar->clear();
+ QPushButton* pButton
+ = static_cast<QPushButton*>(mpQMenuBar->cornerWidget(Qt::TopRightCorner));
+ if (pButton && ((mpCloseButton != pButton) || !maCloseButtonConnection))
+ {
+ maCloseButtonConnection
+ = connect(pButton, &QPushButton::clicked, this, &Qt5Menu::slotCloseDocument);
+ mpCloseButton = pButton;
+ }
+ }
+
+ mpQMenu = nullptr;
+
+ DoFullMenuUpdate(mpVCLMenu);
+ }
+}
+
+void Qt5Menu::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++)
+ {
+ Qt5MenuItem* 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 = 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 Qt5Menu::ShowItem(unsigned nPos, bool bShow)
+{
+ if (nPos < maItems.size())
+ {
+ Qt5MenuItem* pSalMenuItem = GetItemAtPos(nPos);
+ QAction* pAction = pSalMenuItem->getAction();
+ if (pAction)
+ pAction->setVisible(bShow);
+ pSalMenuItem->mbVisible = bShow;
+ }
+}
+
+void Qt5Menu::SetItemBits(unsigned nPos, MenuItemBits)
+{
+ if (nPos < maItems.size())
+ {
+ Qt5MenuItem* pSalMenuItem = GetItemAtPos(nPos);
+ UpdateActionGroupItem(pSalMenuItem);
+ }
+}
+
+void Qt5Menu::CheckItem(unsigned nPos, bool bChecked)
+{
+ if (nPos < maItems.size())
+ {
+ Qt5MenuItem* pSalMenuItem = GetItemAtPos(nPos);
+ QAction* pAction = pSalMenuItem->getAction();
+ if (pAction)
+ {
+ pAction->setCheckable(true);
+ pAction->setChecked(bChecked);
+ }
+ }
+}
+
+void Qt5Menu::EnableItem(unsigned nPos, bool bEnable)
+{
+ if (nPos < maItems.size())
+ {
+ Qt5MenuItem* pSalMenuItem = GetItemAtPos(nPos);
+ QAction* pAction = pSalMenuItem->getAction();
+ if (pAction)
+ pAction->setEnabled(bEnable);
+ pSalMenuItem->mbEnabled = bEnable;
+ }
+}
+
+void Qt5Menu::SetItemText(unsigned, SalMenuItem* pItem, const OUString& rText)
+{
+ Qt5MenuItem* pSalMenuItem = static_cast<Qt5MenuItem*>(pItem);
+ QAction* pAction = pSalMenuItem->getAction();
+ if (pAction)
+ {
+ OUString aText(rText);
+ NativeItemText(aText);
+ pAction->setText(toQString(aText));
+ }
+}
+
+void Qt5Menu::SetItemImage(unsigned, SalMenuItem* pItem, const Image& rImage)
+{
+ Qt5MenuItem* pSalMenuItem = static_cast<Qt5MenuItem*>(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 Qt5Menu::SetAccelerator(unsigned, SalMenuItem* pItem, const vcl::KeyCode&,
+ const OUString& rText)
+{
+ Qt5MenuItem* pSalMenuItem = static_cast<Qt5MenuItem*>(pItem);
+ QAction* pAction = pSalMenuItem->getAction();
+ if (pAction)
+ pAction->setShortcut(QKeySequence(toQString(rText), QKeySequence::PortableText));
+}
+
+void Qt5Menu::GetSystemMenuData(SystemMenuData*) {}
+
+Qt5Menu* Qt5Menu::GetTopLevel()
+{
+ Qt5Menu* pMenu = this;
+ while (pMenu->mpParentSalMenu)
+ pMenu = pMenu->mpParentSalMenu;
+ return pMenu;
+}
+
+void Qt5Menu::ShowMenuBar(bool bVisible)
+{
+ if (mpQMenuBar)
+ mpQMenuBar->setVisible(bVisible);
+}
+
+const Qt5Frame* Qt5Menu::GetFrame() const
+{
+ SolarMutexGuard aGuard;
+ const Qt5Menu* pMenu = this;
+ while (pMenu && !pMenu->mpFrame)
+ pMenu = pMenu->mpParentSalMenu;
+ return pMenu ? pMenu->mpFrame : nullptr;
+}
+
+void Qt5Menu::slotMenuTriggered(Qt5MenuItem* pQItem)
+{
+ if (pQItem)
+ {
+ Qt5Menu* pSalMenu = pQItem->mpParentMenu;
+ Qt5Menu* 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 Qt5Menu::slotMenuAboutToShow(Qt5MenuItem* pQItem)
+{
+ if (pQItem)
+ {
+ Qt5Menu* pSalMenu = pQItem->mpSubMenu;
+ Qt5Menu* pTopLevel = pSalMenu->GetTopLevel();
+
+ Menu* pMenu = pSalMenu->GetMenu();
+
+ // following function may update the menu
+ pTopLevel->GetMenu()->HandleMenuActivateEvent(pMenu);
+ }
+}
+
+void Qt5Menu::slotMenuAboutToHide(Qt5MenuItem* pQItem)
+{
+ if (pQItem)
+ {
+ Qt5Menu* pSalMenu = pQItem->mpSubMenu;
+ Qt5Menu* pTopLevel = pSalMenu->GetTopLevel();
+
+ Menu* pMenu = pSalMenu->GetMenu();
+
+ pTopLevel->GetMenu()->HandleMenuDeActivateEvent(pMenu);
+ }
+}
+
+void Qt5Menu::NativeItemText(OUString& rItemText)
+{
+ // preserve literal '&'s in menu texts
+ rItemText = rItemText.replaceAll("&", "&&");
+
+ rItemText = rItemText.replace('~', '&');
+}
+
+void Qt5Menu::slotCloseDocument()
+{
+ MenuBar* pVclMenuBar = static_cast<MenuBar*>(mpVCLMenu.get());
+ if (pVclMenuBar)
+ Application::PostUserEvent(pVclMenuBar->GetCloseButtonClickHdl());
+}
+
+void Qt5Menu::ShowCloseButton(bool bShow)
+{
+ if (!mpQMenuBar)
+ return;
+
+ QPushButton* pButton = static_cast<QPushButton*>(mpQMenuBar->cornerWidget(Qt::TopRightCorner));
+ 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 = new QPushButton(mpQMenuBar);
+ pButton->setIcon(aIcon);
+ pButton->setFlat(true);
+ pButton->setFocusPolicy(Qt::NoFocus);
+ pButton->setToolTip(toQString(VclResId(SV_HELPTEXT_CLOSEDOCUMENT)));
+ mpQMenuBar->setCornerWidget(pButton, Qt::TopRightCorner);
+ maCloseButtonConnection
+ = connect(pButton, &QPushButton::clicked, this, &Qt5Menu::slotCloseDocument);
+ mpCloseButton = pButton;
+ }
+
+ if (bShow)
+ pButton->show();
+ else
+ pButton->hide();
+}
+
+bool Qt5Menu::ShowNativePopupMenu(FloatingWindow*, const tools::Rectangle&,
+ FloatWinPopupFlags nFlags)
+{
+ assert(mpQMenu);
+ DoFullMenuUpdate(mpVCLMenu);
+ mpQMenu->setTearOffEnabled(bool(nFlags & FloatWinPopupFlags::AllowTearOff));
+
+ const QPoint aPos = QCursor::pos();
+ mpQMenu->exec(aPos);
+
+ return true;
+}
+
+Qt5MenuItem::Qt5MenuItem(const SalItemParams* pItemData)
+ : mpParentMenu(nullptr)
+ , mpSubMenu(nullptr)
+ , mnId(pItemData->nId)
+ , mnType(pItemData->eType)
+ , mbVisible(true)
+ , mbEnabled(true)
+ , maImage(pItemData->aImage)
+{
+}
+
+QAction* Qt5MenuItem::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/Qt5Object.cxx b/vcl/qt5/Qt5Object.cxx
new file mode 100644
index 000000000..5bbfef5a5
--- /dev/null
+++ b/vcl/qt5/Qt5Object.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 <Qt5Object.hxx>
+#include <Qt5Object.moc>
+
+#include <Qt5Frame.hxx>
+#include <Qt5Widget.hxx>
+
+#include <QtGui/QGuiApplication>
+
+Qt5Object::Qt5Object(Qt5Frame* pParent, bool bShow)
+ : m_pParent(pParent)
+ , m_pQWidget(nullptr)
+ , m_pQWindow(nullptr)
+{
+ if (!m_pParent || !pParent->GetQWidget())
+ return;
+
+ m_pQWindow = new Qt5ObjectWindow(*this);
+ m_pQWidget = QWidget::createWindowContainer(m_pQWindow, pParent->GetQWidget());
+ m_pQWidget->setAttribute(Qt::WA_NoSystemBackground);
+ connect(m_pQWidget, &QObject::destroyed, this, [this]() { m_pQWidget = nullptr; });
+
+ if (bShow)
+ m_pQWidget->show();
+
+ m_aSystemData.aShellWindow = reinterpret_cast<sal_IntPtr>(this);
+ //m_aSystemData.pSalFrame = this;
+ m_aSystemData.pWidget = m_pQWidget;
+ //m_aSystemData.nScreen = m_nXScreen.getXScreen();
+ m_aSystemData.toolkit = SystemEnvData::Toolkit::Qt5;
+ m_aSystemData.platform = SystemEnvData::Platform::Xcb;
+ const bool bWayland = QGuiApplication::platformName() == "wayland";
+ if (!bWayland)
+ {
+ m_aSystemData.platform = SystemEnvData::Platform::Xcb;
+ m_aSystemData.aWindow = m_pQWindow->winId(); // ID of the embedded window
+ }
+ else
+ {
+ m_aSystemData.platform = SystemEnvData::Platform::Wayland;
+ // TODO implement as needed for Wayland,
+ // s.a. commit c0d4f3ad3307c which did this for gtk3
+ // QPlatformNativeInterface* native = QGuiApplication::platformNativeInterface();
+ // m_aSystemData.pDisplay = native->nativeResourceForWindow("display", nullptr);
+ // m_aSystemData.aWindow = reinterpret_cast<unsigned long>(
+ // native->nativeResourceForWindow("surface", m_pQWidget->windowHandle()));
+ }
+}
+
+Qt5Object::~Qt5Object()
+{
+ if (m_pQWidget)
+ {
+ m_pQWidget->setParent(nullptr);
+ delete m_pQWidget;
+ }
+}
+
+void Qt5Object::ResetClipRegion()
+{
+ if (m_pQWidget)
+ m_pRegion = QRegion(m_pQWidget->geometry());
+ else
+ m_pRegion = QRegion();
+}
+
+void Qt5Object::BeginSetClipRegion(sal_uInt32) { m_pRegion = QRegion(); }
+
+void Qt5Object::UnionClipRegion(long nX, long nY, long nWidth, long nHeight)
+{
+ m_pRegion += QRect(nX, nY, nWidth, nHeight);
+}
+
+void Qt5Object::EndSetClipRegion()
+{
+ if (m_pQWidget)
+ m_pRegion = m_pRegion.intersected(m_pQWidget->geometry());
+}
+
+void Qt5Object::SetPosSize(long nX, long nY, long nWidth, long nHeight)
+{
+ if (m_pQWidget)
+ {
+ m_pQWidget->move(nX, nY);
+ m_pQWidget->setFixedSize(nWidth, nHeight);
+ }
+}
+
+void Qt5Object::Show(bool bVisible)
+{
+ if (m_pQWidget)
+ m_pQWidget->setVisible(bVisible);
+}
+
+void Qt5Object::SetForwardKey(bool /*bEnable*/) {}
+
+Qt5ObjectWindow::Qt5ObjectWindow(Qt5Object& rParent)
+ : m_rParent(rParent)
+{
+ assert(m_rParent.frame() && m_rParent.frame()->GetQWidget());
+}
+
+void Qt5ObjectWindow::focusInEvent(QFocusEvent* pEvent)
+{
+ m_rParent.CallCallback(SalObjEvent::GetFocus);
+ QWindow::focusInEvent(pEvent);
+}
+
+void Qt5ObjectWindow::focusOutEvent(QFocusEvent* pEvent)
+{
+ m_rParent.CallCallback(SalObjEvent::LoseFocus);
+ QWindow::focusOutEvent(pEvent);
+}
+
+void Qt5ObjectWindow::mousePressEvent(QMouseEvent* pEvent)
+{
+ m_rParent.CallCallback(SalObjEvent::ToTop);
+ Qt5Widget::handleMousePressEvent(*m_rParent.frame(), pEvent);
+}
+
+void Qt5ObjectWindow::mouseReleaseEvent(QMouseEvent* pEvent)
+{
+ Qt5Widget::handleMouseReleaseEvent(*m_rParent.frame(), pEvent);
+}
+
+bool Qt5ObjectWindow::event(QEvent* pEvent)
+{
+ return Qt5Widget::handleEvent(*m_rParent.frame(), *m_rParent.widget(), pEvent)
+ || QWindow::event(pEvent);
+}
+
+void Qt5ObjectWindow::keyReleaseEvent(QKeyEvent* pEvent)
+{
+ if (!Qt5Widget::handleKeyReleaseEvent(*m_rParent.frame(), *m_rParent.widget(), pEvent))
+ QWindow::keyReleaseEvent(pEvent);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5OpenGLContext.cxx b/vcl/qt5/Qt5OpenGLContext.cxx
new file mode 100644
index 000000000..a33f7abde
--- /dev/null
+++ b/vcl/qt5/Qt5OpenGLContext.cxx
@@ -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 .
+ */
+
+#include <Qt5OpenGLContext.hxx>
+
+#include <vcl/sysdata.hxx>
+#include <opengl/zone.hxx>
+#include <sal/log.hxx>
+
+#include <window.h>
+
+#include <Qt5Object.hxx>
+
+#include <QtGui/QOpenGLContext>
+#include <QtGui/QWindow>
+
+bool Qt5OpenGLContext::g_bAnyCurrent = false;
+
+void Qt5OpenGLContext::swapBuffers()
+{
+ OpenGLZone aZone;
+
+ if (m_pContext && m_pWindow && m_pWindow->isExposed())
+ {
+ m_pContext->swapBuffers(m_pWindow);
+ }
+
+ BuffersSwapped();
+}
+
+void Qt5OpenGLContext::resetCurrent()
+{
+ clearCurrent();
+
+ OpenGLZone aZone;
+
+ if (m_pContext)
+ {
+ m_pContext->doneCurrent();
+ g_bAnyCurrent = false;
+ }
+}
+
+bool Qt5OpenGLContext::isCurrent()
+{
+ OpenGLZone aZone;
+ return g_bAnyCurrent && (QOpenGLContext::currentContext() == m_pContext);
+}
+
+bool Qt5OpenGLContext::isAnyCurrent()
+{
+ OpenGLZone aZone;
+ return g_bAnyCurrent && (QOpenGLContext::currentContext() != nullptr);
+}
+
+bool Qt5OpenGLContext::ImplInit()
+{
+ if (!m_pWindow)
+ {
+ SAL_WARN("vcl.opengl.qt5", "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.qt5", "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 Qt5OpenGLContext::makeCurrent()
+{
+ if (isCurrent())
+ return;
+
+ OpenGLZone aZone;
+
+ clearCurrent();
+
+ if (m_pContext && m_pWindow)
+ {
+ m_pContext->makeCurrent(m_pWindow);
+ g_bAnyCurrent = true;
+ }
+
+ registerAsCurrent();
+}
+
+void Qt5OpenGLContext::destroyCurrentContext()
+{
+ OpenGLZone aZone;
+
+ if (m_pContext)
+ {
+ m_pContext->doneCurrent();
+ g_bAnyCurrent = false;
+ }
+
+ if (glGetError() != GL_NO_ERROR)
+ {
+ SAL_WARN("vcl.opengl.qt5", "glError: " << glGetError());
+ }
+}
+
+void Qt5OpenGLContext::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<Qt5Object*>(m_pChildWindow->ImplGetWindowImpl()->mpSysObj)->windowHandle();
+}
diff --git a/vcl/qt5/Qt5Painter.cxx b/vcl/qt5/Qt5Painter.cxx
new file mode 100644
index 000000000..06eeb2895
--- /dev/null
+++ b/vcl/qt5/Qt5Painter.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 <Qt5Painter.hxx>
+
+#include <QtGui/QColor>
+
+Qt5Painter::Qt5Painter(Qt5Graphics& rGraphics, bool bPrepareBrush, sal_uInt8 nTransparency)
+ : m_rGraphics(rGraphics)
+{
+ if (rGraphics.m_pQImage)
+ begin(rGraphics.m_pQImage);
+ else
+ {
+ assert(rGraphics.m_pFrame);
+ begin(rGraphics.m_pFrame->GetQWidget());
+ }
+ if (!rGraphics.m_aClipPath.isEmpty())
+ setClipPath(rGraphics.m_aClipPath);
+ else
+ setClipRegion(rGraphics.m_aClipRegion);
+ if (SALCOLOR_NONE != rGraphics.m_aLineColor)
+ {
+ QColor aColor = toQColor(rGraphics.m_aLineColor);
+ aColor.setAlpha(nTransparency);
+ setPen(aColor);
+ }
+ else
+ setPen(Qt::NoPen);
+ if (bPrepareBrush && SALCOLOR_NONE != rGraphics.m_aFillColor)
+ {
+ QColor aColor = toQColor(rGraphics.m_aFillColor);
+ aColor.setAlpha(nTransparency);
+ setBrush(aColor);
+ }
+ setCompositionMode(rGraphics.m_eCompositionMode);
+}
diff --git a/vcl/qt5/Qt5Printer.cxx b/vcl/qt5/Qt5Printer.cxx
new file mode 100644
index 000000000..16a6a1115
--- /dev/null
+++ b/vcl/qt5/Qt5Printer.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 <Qt5Printer.hxx>
+
+Qt5Printer::Qt5Printer(SalInfoPrinter* pInfoPrinter)
+ : PspSalPrinter(pInfoPrinter)
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5SvpGraphics.cxx b/vcl/qt5/Qt5SvpGraphics.cxx
new file mode 100644
index 000000000..8885b9cb5
--- /dev/null
+++ b/vcl/qt5/Qt5SvpGraphics.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 <Qt5Data.hxx>
+#include <Qt5Frame.hxx>
+#include <Qt5Graphics_Controls.hxx>
+#include <Qt5SvpGraphics.hxx>
+#include <Qt5SvpSurface.hxx>
+#include <Qt5Tools.hxx>
+
+#include <QtGui/QScreen>
+#include <QtGui/QWindow>
+#include <QtWidgets/QWidget>
+
+Qt5SvpGraphics::Qt5SvpGraphics(Qt5Frame* pFrame)
+ : SvpSalGraphics()
+ , m_pFrame(pFrame)
+{
+ if (!Qt5Data::noNativeControls())
+ m_pWidgetDraw.reset(new Qt5Graphics_Controls(*this));
+ if (m_pFrame)
+ setDevicePixelRatioF(m_pFrame->devicePixelRatioF());
+}
+
+Qt5SvpGraphics::~Qt5SvpGraphics() {}
+
+void Qt5SvpGraphics::updateQWidget() const
+{
+ if (!m_pFrame)
+ return;
+ QWidget* pQWidget = m_pFrame->GetQWidget();
+ if (pQWidget)
+ pQWidget->update(pQWidget->rect());
+}
+
+#if ENABLE_CAIRO_CANVAS
+
+bool Qt5SvpGraphics::SupportsCairo() const { return true; }
+
+cairo::SurfaceSharedPtr
+Qt5SvpGraphics::CreateSurface(const cairo::CairoSurfaceSharedPtr& rSurface) const
+{
+ return std::make_shared<cairo::Qt5SvpSurface>(rSurface);
+}
+
+cairo::SurfaceSharedPtr Qt5SvpGraphics::CreateSurface(const OutputDevice& /*rRefDevice*/, int x,
+ int y, int width, int height) const
+{
+ return std::make_shared<cairo::Qt5SvpSurface>(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 Qt5SvpGraphics::handleDamage(const tools::Rectangle& rDamagedRegion)
+{
+ assert(m_pWidgetDraw);
+ assert(dynamic_cast<Qt5Graphics_Controls*>(m_pWidgetDraw.get()));
+ assert(!rDamagedRegion.IsEmpty());
+
+ QImage* pImage = static_cast<Qt5Graphics_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.getX(),
+ rDamagedRegion.getY(), rDamagedRegion.GetWidth(), rDamagedRegion.GetHeight());
+ drawBitmap(aTR, &aBuffer, CAIRO_OPERATOR_OVER);
+}
+
+void Qt5SvpGraphics::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 || !m_pFrame->GetQWidget()->window()->windowHandle())
+ return;
+
+ QScreen* pScreen = m_pFrame->GetQWidget()->window()->windowHandle()->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/Qt5SvpSurface.cxx b/vcl/qt5/Qt5SvpSurface.cxx
new file mode 100644
index 000000000..4bab42997
--- /dev/null
+++ b/vcl/qt5/Qt5SvpSurface.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 <Qt5SvpSurface.hxx>
+
+#include <Qt5SvpGraphics.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
+{
+Qt5SvpSurface::Qt5SvpSurface(const CairoSurfaceSharedPtr& pSurface)
+ : m_pGraphics(nullptr)
+ , m_pCairoContext(nullptr)
+ , m_pSurface(pSurface)
+{
+}
+
+Qt5SvpSurface::Qt5SvpSurface(const Qt5SvpGraphics* pGraphics, int x, int y, int width, int height)
+ : m_pGraphics(pGraphics)
+ , m_pCairoContext(pGraphics->getCairoContext(false))
+{
+ 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);
+}
+
+Qt5SvpSurface::~Qt5SvpSurface()
+{
+ if (m_pCairoContext)
+ cairo_destroy(m_pCairoContext);
+}
+
+CairoSharedPtr Qt5SvpSurface::getCairo() const
+{
+ return CairoSharedPtr(cairo_create(m_pSurface.get()), &cairo_destroy);
+}
+
+SurfaceSharedPtr Qt5SvpSurface::getSimilar(int cairo_content_type, int width, int height) const
+{
+ return std::make_shared<Qt5SvpSurface>(CairoSurfaceSharedPtr(
+ cairo_surface_create_similar(
+ m_pSurface.get(), static_cast<cairo_content_t>(cairo_content_type), width, height),
+ &cairo_surface_destroy));
+}
+
+void Qt5SvpSurface::flush() const
+{
+ cairo_surface_flush(m_pSurface.get());
+ if (m_pGraphics)
+ m_pGraphics->updateQWidget();
+}
+
+VclPtr<VirtualDevice> Qt5SvpSurface::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::DEFAULT);
+}
+
+} // namespace cairo
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5SvpVirtualDevice.hxx b/vcl/qt5/Qt5SvpVirtualDevice.hxx
new file mode 100644
index 000000000..0eb4ed26e
--- /dev/null
+++ b/vcl/qt5/Qt5SvpVirtualDevice.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 <headless/svpvd.hxx>
+#include <qt5/Qt5SvpGraphics.hxx>
+
+class VCL_DLLPUBLIC Qt5SvpVirtualDevice : public SvpSalVirtualDevice
+{
+public:
+ Qt5SvpVirtualDevice(DeviceFormat eFormat, cairo_surface_t* pRefSurface,
+ cairo_surface_t* pPreExistingTarget)
+ : SvpSalVirtualDevice(eFormat, pRefSurface, pPreExistingTarget)
+ {
+ }
+
+ SalGraphics* AcquireGraphics() override { return AddGraphics(new Qt5SvpGraphics(nullptr)); }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5System.cxx b/vcl/qt5/Qt5System.cxx
new file mode 100644
index 000000000..d769f7118
--- /dev/null
+++ b/vcl/qt5/Qt5System.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/.
+ */
+
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QDesktopWidget>
+
+#include <string.h>
+#include <tools/gen.hxx>
+#include <Qt5System.hxx>
+#include <Qt5Tools.hxx>
+
+unsigned int Qt5System::GetDisplayScreenCount()
+{
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ return QApplication::desktop()->screenCount();
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+}
+
+tools::Rectangle Qt5System::GetDisplayScreenPosSizePixel(unsigned int nScreen)
+{
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ QRect qRect = QApplication::desktop()->screenGeometry(nScreen);
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ return toRectangle(scaledQRect(qRect, qApp->devicePixelRatio()));
+}
+
+int Qt5System::ShowNativeDialog(const OUString&, const OUString&, const std::vector<OUString>&)
+{
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5Timer.cxx b/vcl/qt5/Qt5Timer.cxx
new file mode 100644
index 000000000..bbc2800e8
--- /dev/null
+++ b/vcl/qt5/Qt5Timer.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <Qt5Timer.hxx>
+#include <Qt5Timer.moc>
+
+#include <QtWidgets/QApplication>
+#include <QtCore/QThread>
+
+#include <vcl/svapp.hxx>
+#include <sal/log.hxx>
+
+Qt5Timer::Qt5Timer()
+{
+ 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 Qt5Timer::timeoutActivated()
+{
+ SolarMutexGuard aGuard;
+ CallCallback();
+}
+
+void Qt5Timer::startTimer(int nMS) { m_aTimer.start(nMS); }
+
+void Qt5Timer::Start(sal_uInt64 nMS) { Q_EMIT startTimerSignal(nMS); }
+
+void Qt5Timer::stopTimer() { m_aTimer.stop(); }
+
+void Qt5Timer::Stop() { Q_EMIT stopTimerSignal(); }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5Tools.cxx b/vcl/qt5/Qt5Tools.cxx
new file mode 100644
index 000000000..667a5af6b
--- /dev/null
+++ b/vcl/qt5/Qt5Tools.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <Qt5Tools.hxx>
+
+#include <cairo.h>
+
+#include <tools/stream.hxx>
+#include <vcl/event.hxx>
+#include <vcl/image.hxx>
+#include <vcl/pngwrite.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::MidButton)
+ 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;
+ vcl::PNGWriter aWriter(rImage.GetBitmapEx());
+ aWriter.Write(aMemStm);
+ aImage.loadFromData(static_cast<const uchar*>(aMemStm.GetData()), aMemStm.TellEnd());
+ }
+
+ return aImage;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5Transferable.cxx b/vcl/qt5/Qt5Transferable.cxx
new file mode 100644
index 000000000..2e0deef9b
--- /dev/null
+++ b/vcl/qt5/Qt5Transferable.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/.
+ *
+ */
+
+#include <Qt5Transferable.hxx>
+
+#include <comphelper/sequence.hxx>
+#include <sal/log.hxx>
+
+#include <QtWidgets/QApplication>
+
+#include <Qt5Instance.hxx>
+#include <Qt5Tools.hxx>
+
+#include <cassert>
+
+static bool lcl_textMimeInfo(const OUString& rMimeString, bool& bHaveNoCharset, bool& bHaveUTF16,
+ bool& bHaveUTF8)
+{
+ sal_Int32 nIndex = 0;
+ if (rMimeString.getToken(0, ';', nIndex) == "text/plain")
+ {
+ OUString aToken(rMimeString.getToken(0, ';', nIndex));
+ if (aToken == "charset=utf-16")
+ bHaveUTF16 = true;
+ else if (aToken == "charset=utf-8")
+ bHaveUTF8 = true;
+ else if (aToken.isEmpty())
+ bHaveNoCharset = true;
+ else // we just handle UTF-16 and UTF-8, everything else is "bytes"
+ return false;
+ return true;
+ }
+ return false;
+}
+
+Qt5Transferable::Qt5Transferable(const QMimeData* pMimeData)
+ : m_pMimeData(pMimeData)
+ , m_bConvertFromLocale(false)
+{
+ assert(pMimeData);
+}
+
+css::uno::Sequence<css::datatransfer::DataFlavor> SAL_CALL Qt5Transferable::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;
+ css::uno::Sequence<css::datatransfer::DataFlavor> aMimeTypeSeq(nMimeTypeSeqSize);
+
+ 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;
+ 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);
+ aMimeTypeSeq[nMimeTypeCount] = aFlavor;
+ nMimeTypeCount++;
+ }
+
+ m_bConvertFromLocale = bHaveNoCharset && !bHaveUTF16;
+ if (m_bConvertFromLocale)
+ {
+ aFlavor.MimeType = "text/plain;charset=utf-16";
+ aFlavor.DataType = cppu::UnoType<OUString>::get();
+ assert(nMimeTypeCount < nMimeTypeSeqSize);
+ aMimeTypeSeq[nMimeTypeCount] = aFlavor;
+ nMimeTypeCount++;
+ }
+
+ aMimeTypeSeq.realloc(nMimeTypeCount);
+
+ m_aMimeTypeSeq = aMimeTypeSeq;
+ return m_aMimeTypeSeq;
+}
+
+sal_Bool SAL_CALL
+Qt5Transferable::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
+Qt5Transferable::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_bConvertFromLocale)
+ {
+ 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;
+}
+
+Qt5ClipboardTransferable::Qt5ClipboardTransferable(const QClipboard::Mode aMode,
+ const QMimeData* pMimeData)
+ : Qt5Transferable(pMimeData)
+ , m_aMode(aMode)
+{
+}
+
+bool Qt5ClipboardTransferable::hasInFlightChanged() const
+{
+ const bool bChanged(mimeData() != QApplication::clipboard()->mimeData(m_aMode));
+ SAL_WARN_IF(bChanged, "vcl.qt5",
+ "In flight clipboard change detected - broken clipboard read!");
+ return bChanged;
+}
+
+css::uno::Any SAL_CALL
+Qt5ClipboardTransferable::getTransferData(const css::datatransfer::DataFlavor& rFlavor)
+{
+ css::uno::Any aAny;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ SolarMutexGuard g;
+ pSalInst->RunInMainThread([&, this]() {
+ if (!hasInFlightChanged())
+ aAny = Qt5Transferable::getTransferData(rFlavor);
+ });
+ return aAny;
+}
+
+css::uno::Sequence<css::datatransfer::DataFlavor>
+ SAL_CALL Qt5ClipboardTransferable::getTransferDataFlavors()
+{
+ css::uno::Sequence<css::datatransfer::DataFlavor> aSeq;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ SolarMutexGuard g;
+ pSalInst->RunInMainThread([&, this]() {
+ if (!hasInFlightChanged())
+ aSeq = Qt5Transferable::getTransferDataFlavors();
+ });
+ return aSeq;
+}
+
+sal_Bool SAL_CALL
+Qt5ClipboardTransferable::isDataFlavorSupported(const css::datatransfer::DataFlavor& rFlavor)
+{
+ bool bIsSupported = false;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ SolarMutexGuard g;
+ pSalInst->RunInMainThread([&, this]() {
+ if (!hasInFlightChanged())
+ bIsSupported = Qt5Transferable::isDataFlavorSupported(rFlavor);
+ });
+ return bIsSupported;
+}
+
+Qt5MimeData::Qt5MimeData(const css::uno::Reference<css::datatransfer::XTransferable>& xTrans)
+ : m_aContents(xTrans)
+ , m_bHaveNoCharset(false)
+ , m_bHaveUTF8(false)
+{
+ assert(xTrans.is());
+}
+
+bool Qt5MimeData::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 Qt5MimeData::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;
+}
+
+QVariant Qt5MimeData::retrieveData(const QString& mimeType, QVariant::Type) const
+{
+ 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(reinterpret_cast<const char*>(aUTF8String.getStr()),
+ aUTF8String.getLength());
+ }
+ else if (bWantNoCharset)
+ {
+ OString aLocaleString(OUStringToOString(aString, osl_getThreadTextEncoding()));
+ aByteArray = QByteArray(reinterpret_cast<const char*>(aLocaleString.getStr()),
+ aLocaleString.getLength());
+ }
+ 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 Qt5MimeData::hasFormat(const QString& mimeType) const { return formats().contains(mimeType); }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5VirtualDevice.cxx b/vcl/qt5/Qt5VirtualDevice.cxx
new file mode 100644
index 000000000..f1c7d9606
--- /dev/null
+++ b/vcl/qt5/Qt5VirtualDevice.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 <Qt5VirtualDevice.hxx>
+
+#include <Qt5Graphics.hxx>
+#include <Qt5Tools.hxx>
+
+#include <QtGui/QImage>
+
+Qt5VirtualDevice::Qt5VirtualDevice(DeviceFormat eFormat, double fScale)
+ : m_eFormat(eFormat)
+ , m_fScale(fScale)
+{
+}
+
+SalGraphics* Qt5VirtualDevice::AcquireGraphics()
+{
+ assert(m_pImage);
+ Qt5Graphics* pGraphics = new Qt5Graphics(m_pImage.get());
+ m_aGraphics.push_back(pGraphics);
+ return pGraphics;
+}
+
+void Qt5VirtualDevice::ReleaseGraphics(SalGraphics* pGraphics)
+{
+ m_aGraphics.remove(dynamic_cast<Qt5Graphics*>(pGraphics));
+ delete pGraphics;
+}
+
+bool Qt5VirtualDevice::SetSize(long nNewDX, long nNewDY)
+{
+ return SetSizeUsingBuffer(nNewDX, nNewDY, nullptr);
+}
+
+bool Qt5VirtualDevice::SetSizeUsingBuffer(long nNewDX, 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 (m_eFormat == DeviceFormat::BITMASK)
+ {
+ m_pImage.reset(new QImage(nNewDX, nNewDY, QImage::Format_Mono));
+ }
+ else
+ {
+ if (pBuffer)
+ m_pImage.reset(new QImage(pBuffer, nNewDX, nNewDY, Qt5_DefaultFormat32));
+ else
+ m_pImage.reset(new QImage(nNewDX, nNewDY, Qt5_DefaultFormat32));
+ }
+
+ m_pImage->fill(Qt::transparent);
+ m_pImage->setDevicePixelRatio(m_fScale);
+
+ // update device in existing graphics
+ for (auto pQt5Graph : m_aGraphics)
+ pQt5Graph->ChangeQImage(m_pImage.get());
+
+ return true;
+}
+
+long Qt5VirtualDevice::GetWidth() const { return m_pImage ? m_aFrameSize.width() : 0; }
+
+long Qt5VirtualDevice::GetHeight() const { return m_pImage ? m_aFrameSize.height() : 0; }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5Widget.cxx b/vcl/qt5/Qt5Widget.cxx
new file mode 100644
index 000000000..0ef305f42
--- /dev/null
+++ b/vcl/qt5/Qt5Widget.cxx
@@ -0,0 +1,740 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <Qt5Widget.hxx>
+#include <Qt5Widget.moc>
+
+#include <Qt5Frame.hxx>
+#include <Qt5Graphics.hxx>
+#include <Qt5Instance.hxx>
+#include <Qt5SvpGraphics.hxx>
+#include <Qt5Transferable.hxx>
+#include <Qt5Tools.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/QWidget>
+
+#include <cairo.h>
+#include <vcl/commandevent.hxx>
+#include <vcl/event.hxx>
+#include <window.h>
+#include <tools/diagnose_ex.h>
+
+#include <com/sun/star/accessibility/XAccessibleContext.hpp>
+#include <com/sun/star/accessibility/XAccessibleEditableText.hpp>
+
+using namespace com::sun::star;
+
+void Qt5Widget::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), Qt5_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 Qt5Widget::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.nWidth = nWidth;
+ m_rFrame.maGeometry.nHeight = nHeight;
+
+ if (m_rFrame.m_bUseCairo)
+ {
+ if (m_rFrame.m_pSvpGraphics)
+ {
+ 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);
+
+ int min_width = qMin(cairo_image_surface_get_width(old_surface.get()), nWidth);
+ int min_height = qMin(cairo_image_surface_get_height(old_surface.get()), nHeight);
+
+ SalTwoRect rect(0, 0, min_width, min_height, 0, 0, min_width, min_height);
+
+ m_rFrame.m_pSvpGraphics->copySource(rect, old_surface.get());
+ }
+ }
+ else
+ {
+ QImage* pImage = nullptr;
+
+ if (m_rFrame.m_pQImage)
+ pImage = new QImage(m_rFrame.m_pQImage->copy(0, 0, nWidth, nHeight));
+ else
+ {
+ pImage = new QImage(nWidth, nHeight, Qt5_DefaultFormat32);
+ pImage->fill(Qt::transparent);
+ }
+
+ m_rFrame.m_pQt5Graphics->ChangeQImage(pImage);
+ m_rFrame.m_pQImage.reset(pImage);
+ }
+
+ m_rFrame.CallCallback(SalEvent::Resize, nullptr);
+}
+
+void Qt5Widget::handleMouseButtonEvent(const Qt5Frame& rFrame, const QMouseEvent* pEvent,
+ const ButtonKeyState eState)
+{
+ SalMouseEvent aEvent;
+ switch (pEvent->button())
+ {
+ case Qt::LeftButton:
+ aEvent.mnButton = MOUSE_LEFT;
+ break;
+ case Qt::MidButton:
+ aEvent.mnButton = MOUSE_MIDDLE;
+ break;
+ case Qt::RightButton:
+ aEvent.mnButton = MOUSE_RIGHT;
+ break;
+ default:
+ return;
+ }
+
+ const qreal fRatio = rFrame.devicePixelRatioF();
+ const Point aPos = toPoint(pEvent->pos() * fRatio);
+
+ aEvent.mnX = QGuiApplication::isLeftToRight()
+ ? aPos.X()
+ : round(rFrame.GetQWidget()->width() * fRatio) - aPos.X();
+ aEvent.mnY = aPos.Y();
+ aEvent.mnTime = pEvent->timestamp();
+ aEvent.mnCode = GetKeyModCode(pEvent->modifiers()) | GetMouseModCode(pEvent->buttons());
+
+ SalEvent nEventType;
+ if (eState == ButtonKeyState::Pressed)
+ nEventType = SalEvent::MouseButtonDown;
+ else
+ nEventType = SalEvent::MouseButtonUp;
+ rFrame.CallCallback(nEventType, &aEvent);
+}
+
+void Qt5Widget::mousePressEvent(QMouseEvent* pEvent) { handleMousePressEvent(m_rFrame, pEvent); }
+
+void Qt5Widget::mouseReleaseEvent(QMouseEvent* pEvent)
+{
+ handleMouseReleaseEvent(m_rFrame, pEvent);
+}
+
+void Qt5Widget::mouseMoveEvent(QMouseEvent* pEvent)
+{
+ const qreal fRatio = m_rFrame.devicePixelRatioF();
+ const Point aPos = toPoint(pEvent->pos() * fRatio);
+
+ SalMouseEvent aEvent;
+ aEvent.mnX = QGuiApplication::isLeftToRight() ? aPos.X() : round(width() * fRatio) - aPos.X();
+ aEvent.mnY = aPos.Y();
+ aEvent.mnTime = pEvent->timestamp();
+ aEvent.mnCode = GetKeyModCode(pEvent->modifiers()) | GetMouseModCode(pEvent->buttons());
+ aEvent.mnButton = 0;
+
+ m_rFrame.CallCallback(SalEvent::MouseMove, &aEvent);
+ pEvent->accept();
+}
+
+void Qt5Widget::wheelEvent(QWheelEvent* pEvent)
+{
+ const Point aPos = toPoint(pEvent->pos() * m_rFrame.devicePixelRatioF());
+
+ SalWheelMouseEvent aEvent;
+ aEvent.mnX = QGuiApplication::isLeftToRight()
+ ? aPos.X()
+ : round(width() * m_rFrame.devicePixelRatioF()) - aPos.X();
+ aEvent.mnY = aPos.Y();
+ aEvent.mnTime = pEvent->timestamp();
+ aEvent.mnCode = GetKeyModCode(pEvent->modifiers()) | GetMouseModCode(pEvent->buttons());
+
+ // 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 Qt5Widget::dragEnterEvent(QDragEnterEvent* event)
+{
+ if (dynamic_cast<const Qt5MimeData*>(event->mimeData()))
+ event->accept();
+ else
+ event->acceptProposedAction();
+}
+
+// also called when a drop is rejected
+void Qt5Widget::dragLeaveEvent(QDragLeaveEvent*) { m_rFrame.handleDragLeave(); }
+
+void Qt5Widget::dragMoveEvent(QDragMoveEvent* pEvent) { m_rFrame.handleDragMove(pEvent); }
+
+void Qt5Widget::dropEvent(QDropEvent* pEvent) { m_rFrame.handleDrop(pEvent); }
+
+void Qt5Widget::moveEvent(QMoveEvent* pEvent)
+{
+ if (m_rFrame.m_pTopLevel)
+ return;
+
+ const Point aPos = toPoint(pEvent->pos() * m_rFrame.devicePixelRatioF());
+ m_rFrame.maGeometry.nX = aPos.X();
+ m_rFrame.maGeometry.nY = aPos.Y();
+ m_rFrame.CallCallback(SalEvent::Move, nullptr);
+}
+
+void Qt5Widget::showEvent(QShowEvent*)
+{
+ QSize aSize(m_rFrame.GetQWidget()->size() * m_rFrame.devicePixelRatioF());
+ // forcing an immediate update somehow interferes with the hide + show
+ // sequence from Qt5Frame::SetModal, if the frame was already set visible,
+ // resulting in a hidden / unmapped window
+ SalPaintEvent aPaintEvt(0, 0, aSize.width(), aSize.height());
+ m_rFrame.CallCallback(SalEvent::Paint, &aPaintEvt);
+}
+
+void Qt5Widget::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_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 Qt5Widget::commitText(Qt5Frame& 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);
+}
+
+bool Qt5Widget::handleKeyEvent(Qt5Frame& rFrame, const QWidget& rWidget, QKeyEvent* pEvent,
+ const ButtonKeyState eState)
+{
+ sal_uInt16 nCode = GetKeyCode(pEvent->key(), pEvent->modifiers());
+ if (eState == ButtonKeyState::Pressed && nCode == 0 && pEvent->text().length() > 1
+ && rWidget.testAttribute(Qt::WA_InputMethodEnabled))
+ {
+ commitText(rFrame, pEvent->text());
+ pEvent->accept();
+ return true;
+ }
+
+ 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 (eState == ButtonKeyState::Pressed)
+ bStopProcessingKey = rFrame.CallCallback(SalEvent::KeyInput, &aEvent);
+ else
+ bStopProcessingKey = rFrame.CallCallback(SalEvent::KeyUp, &aEvent);
+ if (bStopProcessingKey)
+ pEvent->accept();
+ return bStopProcessingKey;
+}
+
+bool Qt5Widget::handleEvent(Qt5Frame& rFrame, const QWidget& rWidget, QEvent* pEvent)
+{
+ 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),
+ ButtonKeyState::Pressed))
+ return true;
+ }
+ return false;
+}
+
+bool Qt5Widget::event(QEvent* pEvent)
+{
+ return handleEvent(m_rFrame, *this, pEvent) || QWidget::event(pEvent);
+}
+
+void Qt5Widget::keyReleaseEvent(QKeyEvent* pEvent)
+{
+ if (!handleKeyReleaseEvent(m_rFrame, *this, pEvent))
+ QWidget::keyReleaseEvent(pEvent);
+}
+
+void Qt5Widget::focusInEvent(QFocusEvent*) { m_rFrame.CallCallback(SalEvent::GetFocus, nullptr); }
+
+void Qt5Widget::focusOutEvent(QFocusEvent*)
+{
+ endExtTextInput();
+ m_rFrame.CallCallback(SalEvent::LoseFocus, nullptr);
+}
+
+Qt5Widget::Qt5Widget(Qt5Frame& rFrame, Qt::WindowFlags f)
+ : QWidget(Q_NULLPTR, f)
+ , m_rFrame(rFrame)
+ , m_bNonEmptyIMPreeditSeen(false)
+ , m_nDeltaX(0)
+ , m_nDeltaY(0)
+{
+ create();
+ setMouseTracking(true);
+ setFocusPolicy(Qt::StrongFocus);
+}
+
+static ExtTextInputAttr lcl_MapUndrelineStyle(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 Qt5Widget::inputMethodEvent(QInputMethodEvent* pEvent)
+{
+ if (!pEvent->commitString().isEmpty())
+ 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 (int i = 0; i < rAttrList.size(); ++i)
+ {
+ const QInputMethodEvent::Attribute& rAttr = rAttrList.at(i);
+ switch (rAttr.type)
+ {
+ case QInputMethodEvent::TextFormat:
+ {
+ QTextCharFormat aCharFormat
+ = qvariant_cast<QTextFormat>(rAttr.value).toCharFormat();
+ if (aCharFormat.isValid())
+ {
+ ExtTextInputAttr aETIP
+ = lcl_MapUndrelineStyle(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++)
+ 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.qt5", "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.qt5", "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 Qt5Widget::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);
+ [[fallthrough]];
+ }
+ case Qt::ImCursorPosition:
+ {
+ sal_Int32 nCursorPos, nAnchor;
+ if (lcl_retrieveSurrounding(nCursorPos, nAnchor, nullptr, nullptr))
+ return QVariant(static_cast<int>(nCursorPos));
+ [[fallthrough]];
+ }
+ case Qt::ImCursorRectangle:
+ {
+ SalExtTextInputPosEvent aPosEvent;
+ m_rFrame.CallCallback(SalEvent::ExtTextInputPos, &aPosEvent);
+ return QVariant(
+ QRect(aPosEvent.mnX, aPosEvent.mnY, aPosEvent.mnWidth, aPosEvent.mnHeight));
+ }
+ case Qt::ImAnchorPosition:
+ {
+ sal_Int32 nCursorPos, nAnchor;
+ if (lcl_retrieveSurrounding(nCursorPos, nAnchor, nullptr, nullptr))
+ return QVariant(static_cast<int>(nAnchor));
+ [[fallthrough]];
+ }
+ case Qt::ImCurrentSelection:
+ {
+ QString aSelection;
+ sal_Int32 nCursorPos, nAnchor;
+ if (lcl_retrieveSurrounding(nCursorPos, nAnchor, nullptr, &aSelection))
+ return QVariant(aSelection);
+ [[fallthrough]];
+ }
+ default:
+ return QWidget::inputMethodQuery(property);
+ }
+
+ return QVariant();
+}
+
+void Qt5Widget::endExtTextInput()
+{
+ if (m_bNonEmptyIMPreeditSeen)
+ {
+ m_rFrame.CallCallback(SalEvent::EndExtTextInput, nullptr);
+ m_bNonEmptyIMPreeditSeen = false;
+ }
+}
+
+void Qt5Widget::changeEvent(QEvent* pEvent)
+{
+ switch (pEvent->type())
+ {
+ case QEvent::FontChange:
+ [[fallthrough]];
+ case QEvent::PaletteChange:
+ [[fallthrough]];
+ case QEvent::StyleChange:
+ {
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ 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/Qt5XAccessible.cxx b/vcl/qt5/Qt5XAccessible.cxx
new file mode 100644
index 000000000..e9f0804d5
--- /dev/null
+++ b/vcl/qt5/Qt5XAccessible.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 <Qt5XAccessible.hxx>
+#include <Qt5XAccessible.moc>
+
+#include <Qt5Frame.hxx>
+#include <Qt5Tools.hxx>
+#include <Qt5Widget.hxx>
+
+#include <com/sun/star/accessibility/XAccessible.hpp>
+
+#include <sal/log.hxx>
+
+using namespace css::accessibility;
+using namespace css::uno;
+
+Qt5XAccessible::Qt5XAccessible(Reference<XAccessible> xAccessible)
+ : m_xAccessible(xAccessible)
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/quartz/ctfonts.cxx b/vcl/quartz/ctfonts.cxx
new file mode 100644
index 000000000..eb67d981f
--- /dev/null
+++ b/vcl/quartz/ctfonts.cxx
@@ -0,0 +1,560 @@
+/* -*- 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>
+
+#include <vcl/settings.hxx>
+
+
+#include <quartz/ctfonts.hxx>
+#include <impfont.hxx>
+#ifdef MACOSX
+#include <osx/saldata.hxx>
+#include <osx/salinst.h>
+#endif
+#include <fontinstance.hxx>
+#include <fontattributes.hxx>
+#include <impglyphitem.hxx>
+#include <PhysicalFontCollection.hxx>
+#include <quartz/salgdi.h>
+#include <quartz/utils.h>
+#include <sallayout.hxx>
+#include <hb-coretext.h>
+
+static double toRadian(int nDegree)
+{
+ return nDegree * (M_PI / 1800.0);
+}
+
+CoreTextStyle::CoreTextStyle(const PhysicalFontFace& rPFF, const FontSelectPattern& rFSP)
+ : LogicalFontInstance(rPFF, rFSP)
+ , mfFontStretch( 1.0 )
+ , mfFontRotation( 0.0 )
+ , mbFauxBold(false)
+ , mpStyleDict( nullptr )
+{
+ double fScaledFontHeight = rFSP.mfExactHeight;
+
+ // convert font rotation to radian
+ mfFontRotation = toRadian(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));
+ }
+
+ // create the style object for CoreText font attributes
+ static const CFIndex nMaxDictSize = 16; // TODO: does this really suffice?
+ mpStyleDict = CFDictionaryCreateMutable( nullptr, nMaxDictSize,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks );
+
+ CFBooleanRef pCFVertBool = rFSP.mbVertical ? kCFBooleanTrue : kCFBooleanFalse;
+ CFDictionarySetValue( mpStyleDict, kCTVerticalFormsAttributeName, pCFVertBool );
+
+ // fake bold
+ if ( (rFSP.GetWeight() >= WEIGHT_BOLD) &&
+ ((rPFF.GetWeight() < WEIGHT_SEMIBOLD) &&
+ (rPFF.GetWeight() != WEIGHT_DONTKNOW)) )
+ {
+ mbFauxBold = true;
+ }
+
+ // fake italic
+ if (((rFSP.GetItalic() == ITALIC_NORMAL) ||
+ (rFSP.GetItalic() == ITALIC_OBLIQUE)) &&
+ (rPFF.GetItalic() == ITALIC_NONE))
+ {
+ aMatrix = CGAffineTransformConcat(aMatrix, CGAffineTransformMake(1, 0, toRadian(120), 1, 0, 0));
+ }
+
+ CTFontDescriptorRef pFontDesc = reinterpret_cast<CTFontDescriptorRef>(rPFF.GetFontId());
+ CTFontRef pNewCTFont = CTFontCreateWithFontDescriptor( pFontDesc, fScaledFontHeight, &aMatrix );
+ CFDictionarySetValue( mpStyleDict, kCTFontAttributeName, pNewCTFont );
+ CFRelease( pNewCTFont);
+}
+
+CoreTextStyle::~CoreTextStyle()
+{
+ if( mpStyleDict )
+ CFRelease( mpStyleDict );
+}
+
+void CoreTextStyle::GetFontMetric( ImplFontMetricDataRef const & rxFontMetric )
+{
+ // get the matching CoreText font handle
+ // TODO: is it worth it to cache the CTFontRef in SetFont() and reuse it here?
+ CTFontRef aCTFontRef = static_cast<CTFontRef>(CFDictionaryGetValue( mpStyleDict, kCTFontAttributeName ));
+
+ rxFontMetric->ImplCalcLineSpacing(this);
+
+ // since ImplFontMetricData::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( aCTFontRef ) * mfFontStretch) );
+
+ rxFontMetric->SetMinKashida(GetKashidaWidth());
+}
+
+bool CoreTextStyle::ImplGetGlyphBoundRect(sal_GlyphId nId, tools::Rectangle& rRect, bool bVertical) const
+{
+ CGGlyph nCGGlyph = nId;
+ CTFontRef aCTFontRef = static_cast<CTFontRef>(CFDictionaryGetValue( mpStyleDict, kCTFontAttributeName ));
+
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH //TODO: 10.11 kCTFontDefaultOrientation
+ const CTFontOrientation aFontOrientation = kCTFontDefaultOrientation; // TODO: horz/vert
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ CGRect aCGRect = CTFontGetBoundingRectsForGlyphs(aCTFontRef, aFontOrientation, &nCGGlyph, nullptr, 1);
+
+ // Apply font rotation to non-vertical glyphs.
+ if (mfFontRotation && !bVertical)
+ aCGRect = CGRectApplyAffineTransform(aCGRect, CGAffineTransformMakeRotation(mfFontRotation));
+
+ long xMin = floor(aCGRect.origin.x);
+ long yMin = floor(aCGRect.origin.y);
+ long xMax = ceil(aCGRect.origin.x + aCGRect.size.width);
+ long yMax = ceil(aCGRect.origin.y + aCGRect.size.height);
+ rRect = tools::Rectangle(xMin, -yMax, xMax, -yMin);
+ return true;
+}
+
+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 CoreTextStyle::GetGlyphOutline(sal_GlyphId nId, basegfx::B2DPolyPolygon& rResult, bool) const
+{
+ rResult.clear();
+
+ CGGlyph nCGGlyph = nId;
+ CTFontRef pCTFont = static_cast<CTFontRef>(CFDictionaryGetValue( mpStyleDict, kCTFontAttributeName ));
+
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ const CTFontOrientation aFontOrientation = kCTFontDefaultOrientation;
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ CGRect aCGRect = CTFontGetBoundingRectsForGlyphs(pCTFont, 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( pCTFont, 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;
+}
+
+static hb_blob_t* getFontTable(hb_face_t* /*face*/, hb_tag_t nTableTag, void* pUserData)
+{
+ sal_uLong nLength = 0;
+ unsigned char* pBuffer = nullptr;
+ CoreTextFontFace* pFont = static_cast<CoreTextFontFace*>(pUserData);
+ nLength = pFont->GetFontTable(nTableTag, nullptr);
+ if (nLength > 0)
+ {
+ pBuffer = new unsigned char[nLength];
+ pFont->GetFontTable(nTableTag, pBuffer);
+ }
+
+ hb_blob_t* pBlob = nullptr;
+ if (pBuffer != nullptr)
+ pBlob = hb_blob_create(reinterpret_cast<const char*>(pBuffer), nLength, HB_MEMORY_MODE_READONLY,
+ pBuffer, [](void* data){ delete[] static_cast<unsigned char*>(data); });
+ return pBlob;
+}
+
+hb_font_t* CoreTextStyle::ImplInitHbFont()
+{
+ hb_face_t* pHbFace = hb_face_create_for_tables(getFontTable, GetFontFace(), nullptr);
+
+ return InitHbFont(pHbFace);
+}
+
+rtl::Reference<LogicalFontInstance> CoreTextFontFace::CreateFontInstance(const FontSelectPattern& rFSD) const
+{
+ return new CoreTextStyle(*this, rFSD);
+}
+
+int CoreTextFontFace::GetFontTable( const char pTagName[5], unsigned char* pResultBuf ) const
+{
+ SAL_WARN_IF( pTagName[4]!='\0', "vcl", "CoreTextFontFace::GetFontTable with invalid tagname!" );
+
+ const CTFontTableTag nTagCode = (pTagName[0]<<24) + (pTagName[1]<<16) + (pTagName[2]<<8) + (pTagName[3]<<0);
+
+ return GetFontTable(nTagCode, pResultBuf);
+}
+
+int CoreTextFontFace::GetFontTable(uint32_t nTagCode, unsigned char* pResultBuf ) const
+{
+ // get the raw table length
+ CTFontDescriptorRef pFontDesc = reinterpret_cast<CTFontDescriptorRef>( GetFontId());
+ CTFontRef rCTFont = CTFontCreateWithFontDescriptor( pFontDesc, 0.0, nullptr);
+ const uint32_t opts( kCTFontTableOptionNoOptions );
+ CFDataRef pDataRef = CTFontCopyTable( rCTFont, nTagCode, opts);
+ CFRelease( rCTFont);
+ if( !pDataRef)
+ return 0;
+
+ const CFIndex nByteLength = CFDataGetLength( pDataRef);
+
+ // get the raw table data if requested
+ if( pResultBuf && (nByteLength > 0))
+ {
+ const CFRange aFullRange = CFRangeMake( 0, nByteLength);
+ CFDataGetBytes( pDataRef, aFullRange, reinterpret_cast<UInt8*>(pResultBuf));
+ }
+
+ CFRelease( pDataRef);
+
+ return static_cast<int>(nByteLength);
+}
+
+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.SetSymbolFlag( 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 );
+ rDFA.SetSymbolFlag( (nSymbolTrait & kCTFontClassMaskTrait) == kCTFontSymbolicClass );
+ }
+
+ // get the font weight
+ double fWeight = 0;
+ CFNumberRef pWeightNum = static_cast<CFNumberRef>(CFDictionaryGetValue( pAttrDict, kCTFontWeightTrait ));
+ 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 ));
+ 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 ));
+ 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)
+ {
+ const sal_IntPtr nFontId = reinterpret_cast<sal_IntPtr>(pValue);
+ rtl::Reference<CoreTextFontFace> pFontData = new CoreTextFontFace( rDFA, nFontId );
+ 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( 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;
+}
+
+SystemFontList* GetCoretextFontList()
+{
+ SystemFontList* pList = new SystemFontList();
+ if( !pList->Init() )
+ {
+ delete pList;
+ return nullptr;
+ }
+
+ return pList;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/quartz/salbmp.cxx b/vcl/quartz/salbmp.cxx
new file mode 100644
index 000000000..8dd09d176
--- /dev/null
+++ b/vcl/quartz/salbmp.cxx
@@ -0,0 +1,963 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <basegfx/vector/b2ivector.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 <bmpfast.hxx>
+#include <quartz/salbmp.h>
+#include <quartz/utils.h>
+
+#ifdef MACOSX
+#include <osx/saldata.hxx>
+#else
+#include "saldatabasic.hxx"
+#endif
+
+static const unsigned long k32BitRedColorMask = 0x00ff0000;
+static const unsigned long k32BitGreenColorMask = 0x0000ff00;
+static const unsigned long k32BitBlueColorMask = 0x000000ff;
+
+static bool isValidBitCount( sal_uInt16 nBitCount )
+{
+ return (nBitCount == 1) || (nBitCount == 4) || (nBitCount == 8) ||
+ (nBitCount == 24) || (nBitCount == 32);
+}
+
+QuartzSalBitmap::QuartzSalBitmap()
+ : mxCachedImage( nullptr )
+ , mnBits(0)
+ , mnWidth(0)
+ , mnHeight(0)
+ , mnBytesPerRow(0)
+{
+}
+
+QuartzSalBitmap::~QuartzSalBitmap()
+{
+ doDestroy();
+}
+
+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;
+}
+
+bool QuartzSalBitmap::Create( const Size& rSize, sal_uInt16 nBits, const BitmapPalette& rBitmapPalette )
+{
+ if( !isValidBitCount( nBits ) )
+ return false;
+
+ maPalette = rBitmapPalette;
+ mnBits = nBits;
+ mnWidth = rSize.Width();
+ mnHeight = rSize.Height();
+ return AllocateUserData();
+}
+
+bool QuartzSalBitmap::Create( const SalBitmap& rSalBmp )
+{
+ return Create( rSalBmp, rSalBmp.GetBitCount() );
+}
+
+bool QuartzSalBitmap::Create( const SalBitmap& rSalBmp, SalGraphics* pGraphics )
+{
+ return Create( rSalBmp, pGraphics ? pGraphics->GetBitCount() : rSalBmp.GetBitCount() );
+}
+
+bool QuartzSalBitmap::Create( const SalBitmap& rSalBmp, sal_uInt16 nNewBitCount )
+{
+ const QuartzSalBitmap& rSourceBitmap = static_cast<const QuartzSalBitmap&>(rSalBmp);
+
+ if (isValidBitCount(nNewBitCount) && rSourceBitmap.m_pUserBuffer.get())
+ {
+ mnBits = nNewBitCount;
+ 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.get())
+ {
+ 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 4: mnBytesPerRow = (mnWidth + 1) >> 1; 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 m_pUserBuffer.get() != nullptr;
+}
+
+namespace {
+
+class ImplPixelFormat
+{
+public:
+ static std::unique_ptr<ImplPixelFormat> GetFormat( sal_uInt16 nBits, const BitmapPalette& rPalette );
+
+ 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 ~ImplPixelFormat() { }
+};
+
+class ImplPixelFormat32 : public ImplPixelFormat
+// currently ARGB-format for 32bit depth
+{
+ 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 c( pData[1], pData[2], pData[3] );
+ pData += 4;
+ return c;
+ }
+ virtual void WritePixel( Color nColor ) override
+ {
+ *pData++ = 0;
+ *pData++ = nColor.GetRed();
+ *pData++ = nColor.GetGreen();
+ *pData++ = nColor.GetBlue();
+ }
+};
+
+class ImplPixelFormat24 : public ImplPixelFormat
+// currently BGR-format for 24bit depth
+{
+ 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 c( pData[2], pData[1], pData[0] );
+ pData += 3;
+ return c;
+ }
+ virtual void WritePixel( Color nColor ) override
+ {
+ *pData++ = nColor.GetBlue();
+ *pData++ = nColor.GetGreen();
+ *pData++ = nColor.GetRed();
+ }
+};
+
+class ImplPixelFormat8 : public ImplPixelFormat
+{
+private:
+ sal_uInt8* pData;
+ const BitmapPalette& mrPalette;
+ const sal_uInt16 mnPaletteCount;
+
+public:
+ explicit ImplPixelFormat8( const BitmapPalette& rPalette )
+ : pData(nullptr)
+ , mrPalette(rPalette)
+ , mnPaletteCount(rPalette.GetEntryCount())
+ {
+ }
+ 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++);
+
+ // Caution(!) rPalette.GetEntryCount() may be != (depth^^2)-1 (!)
+ if(nIndex < mnPaletteCount)
+ return mrPalette[nIndex];
+ else
+ return COL_BLACK;
+ }
+ virtual void WritePixel( Color nColor ) override
+ {
+ *pData++ = static_cast< sal_uInt8 >( mrPalette.GetBestIndex( nColor ) );
+ }
+};
+
+class ImplPixelFormat4 : public ImplPixelFormat
+{
+private:
+ sal_uInt8* pData;
+ const BitmapPalette& mrPalette;
+ const sal_uInt16 mnPaletteCount;
+ sal_uInt32 mnX;
+ sal_uInt32 mnShift;
+
+public:
+ explicit ImplPixelFormat4( const BitmapPalette& rPalette )
+ : pData(nullptr)
+ , mrPalette(rPalette)
+ , mnPaletteCount(rPalette.GetEntryCount())
+ , mnX(0)
+ , mnShift(0)
+ {
+ }
+ virtual void SkipPixel( sal_uInt32 nPixel ) override
+ {
+ mnX += nPixel;
+ if( nPixel & 1 )
+ {
+ mnShift ^= 4;
+ }
+ }
+ virtual void StartLine( sal_uInt8* pLine ) override
+ {
+ pData = pLine;
+ mnX = 0;
+ mnShift = 4;
+ }
+ virtual Color ReadPixel() override
+ {
+ // Caution(!) rPalette.GetEntryCount() may be != (depth^^2)-1 (!)
+ const sal_uInt8 nIndex(( pData[mnX >> 1] >> mnShift) & 0x0f);
+ mnX++;
+ mnShift ^= 4;
+
+ if(nIndex < mnPaletteCount)
+ return mrPalette[nIndex];
+ else
+ return COL_BLACK;
+ }
+ virtual void WritePixel( Color nColor ) override
+ {
+ pData[mnX>>1] &= (0xf0 >> mnShift);
+ pData[mnX>>1] |= (static_cast< sal_uInt8 >( mrPalette.GetBestIndex( nColor ) ) & 0x0f);
+ mnX++;
+ mnShift ^= 4;
+ }
+};
+
+class ImplPixelFormat1 : public ImplPixelFormat
+{
+private:
+ sal_uInt8* pData;
+ const BitmapPalette& mrPalette;
+ const sal_uInt16 mnPaletteCount;
+ sal_uInt32 mnX;
+
+public:
+ explicit ImplPixelFormat1( const BitmapPalette& rPalette )
+ : pData(nullptr)
+ , mrPalette(rPalette)
+ , mnPaletteCount(rPalette.GetEntryCount())
+ , 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
+ {
+ // Caution(!) rPalette.GetEntryCount() may be != (depth^^2)-1 (!)
+ const sal_uInt8 nIndex( (pData[mnX >> 3 ] >> ( 7 - ( mnX & 7 ) )) & 1);
+ mnX++;
+
+ if(nIndex < mnPaletteCount)
+ 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<ImplPixelFormat> ImplPixelFormat::GetFormat( sal_uInt16 nBits, const BitmapPalette& rPalette )
+{
+ switch( nBits )
+ {
+ case 1: return std::make_unique<ImplPixelFormat1>( rPalette );
+ case 4: return std::make_unique<ImplPixelFormat4>( rPalette );
+ case 8: return std::make_unique<ImplPixelFormat8>( rPalette );
+ case 24: return std::make_unique<ImplPixelFormat24>();
+ case 32: return std::make_unique<ImplPixelFormat32>();
+ default:
+ assert(false);
+ return nullptr;
+ }
+
+ return nullptr;
+}
+
+} // namespace
+
+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
+
+ std::unique_ptr<ImplPixelFormat> pD = ImplPixelFormat::GetFormat( nDestBits, rDestPalette );
+ std::unique_ptr<ImplPixelFormat> pS = ImplPixelFormat::GetFormat( nSrcBits, rSrcPalette );
+
+ if( pD && pS )
+ {
+ sal_uInt32 nY = nHeight;
+ while( nY-- )
+ {
+ pD->StartLine( pDestData );
+ pS->StartLine( pSrcData );
+
+ sal_uInt32 nX = nWidth;
+ while( nX-- )
+ {
+ pD->WritePixel( pS->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;
+};
+
+}
+
+static 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 aDefPalette16;
+ static BitmapPalette aDefPalette2;
+ if( ! bDefPalInit )
+ {
+ bDefPalInit = true;
+ aDefPalette256.SetEntryCount( 256 );
+ aDefPalette16.SetEntryCount( 16 );
+ aDefPalette2.SetEntryCount( 2 );
+
+ // Standard colors
+ unsigned int i;
+ for( i = 0; i < 16; i++ )
+ {
+ aDefPalette16[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 4: return aDefPalette16;
+ case 8: return aDefPalette256;
+ default: break;
+ }
+
+ const static BitmapPalette aEmptyPalette;
+ return aEmptyPalette;
+}
+
+BitmapBuffer* QuartzSalBitmap::AcquireBuffer( BitmapAccessMode /*nMode*/ )
+{
+ // TODO: AllocateUserData();
+ if (!m_pUserBuffer.get())
+ 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 4:
+ pBuffer->mnFormat = ScanlineFormat::N4BitMsnPal;
+ 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 QuartzSalBitmap& rMask,
+ int nX, int nY, int nWidth, int nHeight ) const
+{
+ CGImageRef xImage( CreateCroppedImage( nX, nY, nWidth, nHeight ) );
+ if( !xImage )
+ return nullptr;
+
+ CGImageRef xMask = rMask.CreateCroppedImage( nX, nY, nWidth, nHeight );
+ if( !xMask )
+ return xImage;
+
+ // CGImageCreateWithMask() only likes masks or greyscale images => convert if needed
+ // TODO: isolate in an extra method?
+ if( !CGImageIsMask(xMask) || rMask.GetBitCount() != 8)//(CGImageGetColorSpace(xMask) != GetSalData()->mxGraySpace) )
+ {
+ 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;
+}
+
+/** 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.get() && (nX + nWidth <= mnWidth) && (nY + nHeight <= mnHeight))
+ {
+ const sal_uInt32 nDestBytesPerRow = nWidth << 2;
+ std::unique_ptr<sal_uInt32[]> pMaskBuffer(new (std::nothrow) sal_uInt32[ nHeight * nDestBytesPerRow / 4] );
+ sal_uInt32* pDest = pMaskBuffer.get();
+
+ std::unique_ptr<ImplPixelFormat> pSourcePixels = ImplPixelFormat::GetFormat( mnBits, maPalette );
+
+ if( pMaskBuffer && pSourcePixels )
+ {
+ 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();
+ // 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-- )
+ {
+ *pDest++ = ( pSourcePixels->ReadPixel() == 0 ) ? nColor : 0;
+ }
+ 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 maGraphicContextNew(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
+ maGraphicContextNew.saveState();
+ CGContextTranslateCTM (maGraphicContextNew.get(), 0, CGBitmapContextGetHeight(maGraphicContextNew.get()));
+ CGContextScaleCTM (maGraphicContextNew.get(), 1.0, -1.0);
+
+ CGContextDrawImage(maGraphicContextNew.get(), CGRectMake( 0, 0, CGImageGetWidth(xImage), CGImageGetHeight(xImage)), xImage);
+
+ // Flip back
+ CGContextRestoreGState( maGraphicContextNew.get() );
+ CGImageRelease( xImage );
+ maGraphicContext = maGraphicContextNew;
+ }
+
+ 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 000000000..83aebe2ab
--- /dev/null
+++ b/vcl/quartz/salgdi.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 <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 <comphelper/lok.hxx>
+
+#include <vcl/metric.hxx>
+#include <vcl/fontcharmap.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/sysdata.hxx>
+
+#include <quartz/ctfonts.hxx>
+#include <fontsubset.hxx>
+#include <impfont.hxx>
+#include <impfontcharmap.hxx>
+#include <impfontmetricdata.hxx>
+#include <outdev.h>
+#include <PhysicalFontCollection.hxx>
+
+#ifdef MACOSX
+#include <osx/salframe.h>
+#endif
+#include <quartz/utils.h>
+#ifdef IOS
+#include "saldatabasic.hxx"
+#endif
+#include <sallayout.hxx>
+#include <sft.hxx>
+
+using namespace vcl;
+
+namespace {
+
+class CoreTextGlyphFallbackSubstititution
+: public ImplGlyphFallbackFontSubstitution
+{
+public:
+ bool FindFontSubstitute(FontSelectPattern&, LogicalFontInstance* pLogicalFont, OUString&) const override;
+};
+
+}
+
+bool CoreTextGlyphFallbackSubstititution::FindFontSubstitute(FontSelectPattern& rPattern, LogicalFontInstance* pLogicalFont,
+ OUString& rMissingChars) const
+{
+ bool bFound = false;
+ CoreTextStyle* pStyle = static_cast<CoreTextStyle*>(pLogicalFont);
+ CTFontRef pFont = static_cast<CTFontRef>(CFDictionaryGetValue(pStyle->GetStyleDict(), kCTFontAttributeName));
+ CFStringRef pStr = CreateCFString(rMissingChars);
+ if (pStr)
+ {
+ CTFontRef pFallback = CTFontCreateForString(pFont, pStr, CFRangeMake(0, CFStringGetLength(pStr)));
+ if (pFallback)
+ {
+ bFound = true;
+
+ CTFontDescriptorRef pDesc = CTFontCopyFontDescriptor(pFallback);
+ FontAttributes rAttr = DevFontFromCTFontDescriptor(pDesc, nullptr);
+
+ rPattern.maSearchName = rAttr.GetFamilyName();
+
+ rPattern.SetWeight(rAttr.GetWeight());
+ rPattern.SetItalic(rAttr.GetItalic());
+ rPattern.SetPitch(rAttr.GetPitch());
+ rPattern.SetWidthType(rAttr.GetWidthType());
+
+ CFRelease(pFallback);
+ CFRelease(pDesc);
+ }
+ CFRelease(pStr);
+ }
+
+ return bFound;
+}
+
+CoreTextFontFace::CoreTextFontFace( const FontAttributes& rDFA, sal_IntPtr nFontId )
+ : PhysicalFontFace( rDFA )
+ , mnFontId( nFontId )
+ , mbFontCapabilitiesRead( false )
+{
+}
+
+CoreTextFontFace::~CoreTextFontFace()
+{
+}
+
+sal_IntPtr CoreTextFontFace::GetFontId() const
+{
+ return mnFontId;
+}
+
+FontCharMapRef CoreTextFontFace::GetFontCharMap() const
+{
+ // return the cached charmap
+ if( mxCharMap.is() )
+ return mxCharMap;
+
+ // set the default charmap
+ FontCharMapRef pCharMap( new FontCharMap() );
+ mxCharMap = pCharMap;
+
+ // get the CMAP byte size
+ // allocate a buffer for the CMAP raw data
+ const int nBufSize = GetFontTable( "cmap", nullptr );
+ SAL_WARN_IF( (nBufSize <= 0), "vcl", "CoreTextFontFace::GetFontCharMap : GetFontTable1 failed!");
+ if( nBufSize <= 0 )
+ return mxCharMap;
+
+ // get the CMAP raw data
+ std::vector<unsigned char> aBuffer( nBufSize );
+ const int nRawLength = GetFontTable( "cmap", aBuffer.data() );
+ SAL_WARN_IF( (nRawLength <= 0), "vcl", "CoreTextFontFace::GetFontCharMap : GetFontTable2 failed!");
+ if( nRawLength <= 0 )
+ return mxCharMap;
+
+ SAL_WARN_IF( (nBufSize!=nRawLength), "vcl", "CoreTextFontFace::GetFontCharMap : ByteCount mismatch!");
+
+ // parse the CMAP
+ CmapResult aCmapResult;
+ if( ParseCMAP( aBuffer.data(), nRawLength, aCmapResult ) )
+ {
+ FontCharMapRef xDefFontCharMap( new FontCharMap(aCmapResult) );
+ // create the matching charmap
+ mxCharMap = xDefFontCharMap;
+ }
+
+ return mxCharMap;
+}
+
+bool CoreTextFontFace::GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const
+{
+ // read this only once per font
+ if( mbFontCapabilitiesRead )
+ {
+ rFontCapabilities = maFontCapabilities;
+ return rFontCapabilities.oUnicodeRange || rFontCapabilities.oCodePageRange;
+ }
+ mbFontCapabilitiesRead = true;
+
+ int nBufSize = GetFontTable( "OS/2", nullptr );
+ if( nBufSize > 0 )
+ {
+ // allocate a buffer for the OS/2 raw data
+ std::vector<unsigned char> aBuffer( nBufSize );
+ // get the OS/2 raw data
+ const int nRawLength = GetFontTable( "OS/2", aBuffer.data() );
+ if( nRawLength > 0 )
+ {
+ const unsigned char* pOS2Table = aBuffer.data();
+ vcl::getTTCoverage( maFontCapabilities.oUnicodeRange,
+ maFontCapabilities.oCodePageRange,
+ pOS2Table, nRawLength);
+ }
+ }
+ rFontCapabilities = maFontCapabilities;
+ return rFontCapabilities.oUnicodeRange || rFontCapabilities.oCodePageRange;
+}
+
+AquaSalGraphics::AquaSalGraphics()
+ : mpXorEmulation( nullptr )
+ , mnXorMode( 0 )
+ , mnWidth( 0 )
+ , mnHeight( 0 )
+ , mnBitmapDepth( 0 )
+ , mnRealDPIX( 0 )
+ , mnRealDPIY( 0 )
+ , mxClipPath( nullptr )
+ , maLineColor( COL_WHITE )
+ , maFillColor( COL_BLACK )
+ , maTextColor( COL_BLACK )
+ , mbNonAntialiasedText( false )
+#ifdef MACOSX
+ , mpFrame( nullptr )
+#endif
+ , mbPrinter( false )
+ , mbVirDev( false )
+#ifdef MACOSX
+ , mbWindow( false )
+#else
+ , mbForeignContext( false )
+#endif
+{
+ SAL_INFO( "vcl.quartz", "AquaSalGraphics::AquaSalGraphics() this=" << this );
+
+ for (int i = 0; i < MAX_FALLBACK; ++i)
+ mpTextStyle[i] = nullptr;
+
+ if (comphelper::LibreOfficeKit::isActive())
+ initWidgetDrawBackends(true);
+}
+
+AquaSalGraphics::~AquaSalGraphics()
+{
+ SAL_INFO( "vcl.quartz", "AquaSalGraphics::~AquaSalGraphics() this=" << this );
+
+ if( mxClipPath )
+ {
+ CGPathRelease( mxClipPath );
+ }
+
+ ReleaseFonts();
+
+ if( mpXorEmulation )
+ delete mpXorEmulation;
+
+#ifdef IOS
+ if (mbForeignContext)
+ return;
+#endif
+ if (maLayer.isSet())
+ {
+ CGLayerRelease(maLayer.get());
+ }
+ else if (maContextHolder.isSet()
+#ifdef MACOSX
+ && mbWindow
+#endif
+ )
+ {
+ // destroy backbuffer bitmap context that we created ourself
+ CGContextRelease(maContextHolder.get());
+ maContextHolder.set(nullptr);
+ }
+}
+
+SalGraphicsImpl* AquaSalGraphics::GetImpl() const
+{
+ return nullptr;
+}
+
+void AquaSalGraphics::SetTextColor( Color nColor )
+{
+ maTextColor = RGBAColor( nColor );
+ // SAL_ DEBUG(std::hex << nColor << std::dec << "={" << maTextColor.GetRed() << ", " << maTextColor.GetGreen() << ", " << maTextColor.GetBlue() << ", " << maTextColor.GetAlpha() << "}");
+}
+
+void AquaSalGraphics::GetFontMetric(ImplFontMetricDataRef& rxFontMetric, int nFallbackLevel)
+{
+ if (nFallbackLevel < MAX_FALLBACK && mpTextStyle[nFallbackLevel])
+ {
+ mpTextStyle[nFallbackLevel]->GetFontMetric(rxFontMetric);
+ }
+}
+
+static bool AddTempDevFont(const OUString& rFontFileURL)
+{
+ OUString aUSytemPath;
+ OSL_VERIFY( !osl::FileBase::getSystemPathFromFileURL( rFontFileURL, aUSytemPath ) );
+ OString aCFileName = OUStringToOString( aUSytemPath, 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( PhysicalFontCollection* pFontCollection )
+{
+ SAL_WARN_IF( !pFontCollection, "vcl", "AquaSalGraphics::GetDevFontList(NULL) !");
+
+ AddLocalTempFontDirs();
+
+ // The idea is to cache the list of system fonts once it has been generated.
+ // SalData seems to be a good place for this caching. However we have to
+ // carefully make the access to the font list thread-safe. If we register
+ // a font-change event handler to update the font list in case fonts have
+ // changed on the system we have to lock access to the list. The right
+ // way to do that is the solar mutex since GetDevFontList is protected
+ // through it as should be all event handlers
+
+ SalData* pSalData = GetSalData();
+ if( !pSalData->mpFontList )
+ 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();
+ delete pSalData->mpFontList;
+ pSalData->mpFontList = nullptr;
+}
+
+bool AquaSalGraphics::AddTempDevFont( PhysicalFontCollection*,
+ const OUString& rFontFileURL, const OUString& /*rFontName*/ )
+{
+ return ::AddTempDevFont(rFontFileURL);
+}
+
+void AquaSalGraphics::DrawTextLayout(const GenericSalLayout& rLayout)
+{
+#ifdef IOS
+ if (!CheckContext())
+ {
+ SAL_WARN("vcl.quartz", "AquaSalGraphics::DrawTextLayout() without context");
+ return;
+ }
+#endif
+
+ const CoreTextStyle& rStyle = *static_cast<const CoreTextStyle*>(&rLayout.GetFont());
+ const FontSelectPattern& rFontSelect = rStyle.GetFontSelectPattern();
+ if (rFontSelect.mnHeight == 0)
+ {
+ SAL_WARN("vcl.quartz", "AquaSalGraphics::DrawTextLayout(): rFontSelect.mnHeight is zero!?");
+ return;
+ }
+
+ CTFontRef pFont = static_cast<CTFontRef>(CFDictionaryGetValue(rStyle.GetStyleDict(), kCTFontAttributeName));
+ CGAffineTransform aRotMatrix = CGAffineTransformMakeRotation(-rStyle.mfFontRotation);
+
+ Point 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.X(), -aPos.Y());
+
+ // Whether the glyph should be upright in vertical mode or not
+ bool bUprightGlyph = false;
+
+ if (rStyle.mfFontRotation)
+ {
+ if (pGlyph->IsVertical())
+ {
+ bUprightGlyph = true;
+ // Adjust the position of upright (vertical) glyphs.
+ aGCPos.y -= CTFontGetAscent(pFont) - CTFontGetDescent(pFont);
+ }
+ 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
+
+ maContextHolder.saveState();
+
+ // The view is vertically flipped (no idea why), flip it back.
+ CGContextScaleCTM(maContextHolder.get(), 1.0, -1.0);
+ CGContextSetShouldAntialias(maContextHolder.get(), !mbNonAntialiasedText);
+ CGContextSetFillColor(maContextHolder.get(), maTextColor.AsArray());
+
+ if (rStyle.mbFauxBold)
+ {
+
+ float fSize = rFontSelect.mnHeight / 23.0f;
+ CGContextSetStrokeColor(maContextHolder.get(), maTextColor.AsArray());
+ CGContextSetLineWidth(maContextHolder.get(), fSize);
+ CGContextSetTextDrawingMode(maContextHolder.get(), kCGTextFillStroke);
+ }
+
+ 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);
+
+ maContextHolder.saveState();
+ if (rStyle.mfFontRotation && !bUprightGlyph)
+ {
+ CGContextRotateCTM(maContextHolder.get(), rStyle.mfFontRotation);
+ }
+ CTFontDrawGlyphs(pFont, &aGlyphIds[nStartIndex], &aGlyphPos[nStartIndex], nLen, maContextHolder.get());
+ maContextHolder.restoreState();
+
+ aIt = aNext;
+ }
+
+ maContextHolder.restoreState();
+}
+
+void AquaSalGraphics::SetFont(LogicalFontInstance* pReqFont, int nFallbackLevel)
+{
+ // release the text style
+ for (int i = nFallbackLevel; i < MAX_FALLBACK; ++i)
+ {
+ if (!mpTextStyle[i])
+ break;
+ mpTextStyle[i].clear();
+ }
+
+ if (!pReqFont)
+ return;
+
+ // update the text style
+ mpTextStyle[nFallbackLevel] = static_cast<CoreTextStyle*>(pReqFont);
+}
+
+std::unique_ptr<GenericSalLayout> AquaSalGraphics::GetTextLayout(int nFallbackLevel)
+{
+ assert(mpTextStyle[nFallbackLevel]);
+ if (!mpTextStyle[nFallbackLevel])
+ return nullptr;
+ return std::make_unique<GenericSalLayout>(*mpTextStyle[nFallbackLevel]);
+}
+
+FontCharMapRef AquaSalGraphics::GetFontCharMap() const
+{
+ if (!mpTextStyle[0])
+ {
+ return FontCharMapRef( new FontCharMap() );
+ }
+
+ return static_cast<const CoreTextFontFace*>(mpTextStyle[0]->GetFontFace())->GetFontCharMap();
+}
+
+bool AquaSalGraphics::GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const
+{
+ if (!mpTextStyle[0])
+ return false;
+
+ return static_cast<const CoreTextFontFace*>(mpTextStyle[0]->GetFontFace())->GetFontCapabilities(rFontCapabilities);
+}
+
+// fake a SFNT font directory entry for a font table
+// see http://developer.apple.com/fonts/TTRefMan/RM06/Chap6.html#Directory
+static void FakeDirEntry( const char aTag[5], ByteCount nOfs, ByteCount nLen,
+ const unsigned char* /*pData*/, unsigned char*& rpDest )
+{
+ // write entry tag
+ rpDest[ 0] = aTag[0];
+ rpDest[ 1] = aTag[1];
+ rpDest[ 2] = aTag[2];
+ rpDest[ 3] = aTag[3];
+ // TODO: get entry checksum and write it
+ // not too important since the subsetter doesn't care currently
+ // for( pData+nOfs ... pData+nOfs+nLen )
+ // write entry offset
+ rpDest[ 8] = static_cast<char>(nOfs >> 24);
+ rpDest[ 9] = static_cast<char>(nOfs >> 16);
+ rpDest[10] = static_cast<char>(nOfs >> 8);
+ rpDest[11] = static_cast<char>(nOfs >> 0);
+ // write entry length
+ rpDest[12] = static_cast<char>(nLen >> 24);
+ rpDest[13] = static_cast<char>(nLen >> 16);
+ rpDest[14] = static_cast<char>(nLen >> 8);
+ rpDest[15] = static_cast<char>(nLen >> 0);
+ // advance to next entry
+ rpDest += 16;
+}
+
+// fake a TTF or CFF font as directly accessing font file is not possible
+// when only the fontid is known. This approach also handles *.font fonts.
+bool AquaSalGraphics::GetRawFontData( const PhysicalFontFace* pFontData,
+ std::vector<unsigned char>& rBuffer, bool* pJustCFF )
+{
+ const CoreTextFontFace* pMacFont = static_cast<const CoreTextFontFace*>(pFontData);
+
+ // short circuit for CFF-only fonts
+ const int nCffSize = pMacFont->GetFontTable( "CFF ", nullptr);
+ if( pJustCFF != nullptr )
+ {
+ *pJustCFF = (nCffSize > 0);
+ if( *pJustCFF)
+ {
+ rBuffer.resize( nCffSize);
+ const int nCffRead = pMacFont->GetFontTable( "CFF ", rBuffer.data());
+ if( nCffRead != nCffSize)
+ {
+ return false;
+ }
+ return true;
+ }
+ }
+
+ // get font table availability and size in bytes
+ const int nHeadSize = pMacFont->GetFontTable( "head", nullptr);
+ if( nHeadSize <= 0)
+ return false;
+
+ const int nMaxpSize = pMacFont->GetFontTable( "maxp", nullptr);
+ if( nMaxpSize <= 0)
+ return false;
+
+ const int nCmapSize = pMacFont->GetFontTable( "cmap", nullptr);
+ if( nCmapSize <= 0)
+ return false;
+
+ const int nNameSize = pMacFont->GetFontTable( "name", nullptr);
+ if( nNameSize <= 0)
+ return false;
+
+ const int nHheaSize = pMacFont->GetFontTable( "hhea", nullptr);
+ if( nHheaSize <= 0)
+ return false;
+
+ const int nHmtxSize = pMacFont->GetFontTable( "hmtx", nullptr);
+ if( nHmtxSize <= 0)
+ return false;
+
+ // get the ttf-glyf outline tables
+ int nLocaSize = 0;
+ int nGlyfSize = 0;
+ if( nCffSize <= 0)
+ {
+ nLocaSize = pMacFont->GetFontTable( "loca", nullptr);
+ if( nLocaSize <= 0)
+ return false;
+
+ nGlyfSize = pMacFont->GetFontTable( "glyf", nullptr);
+ if( nGlyfSize <= 0)
+ return false;
+ }
+
+ int nPrepSize = 0, nCvtSize = 0, nFpgmSize = 0;
+ if( nGlyfSize) // TODO: reduce PDF size by making hint subsetting optional
+ {
+ nPrepSize = pMacFont->GetFontTable( "prep", nullptr);
+ nCvtSize = pMacFont->GetFontTable( "cvt ", nullptr);
+ nFpgmSize = pMacFont->GetFontTable( "fpgm", nullptr);
+ }
+
+ // prepare a byte buffer for a fake font
+ int nTableCount = 7;
+ nTableCount += (nPrepSize>0?1:0) + (nCvtSize>0?1:0) + (nFpgmSize>0?1:0) + (nGlyfSize>0?1:0);
+ const ByteCount nFdirSize = 12 + 16*nTableCount;
+ ByteCount nTotalSize = nFdirSize;
+ nTotalSize += nHeadSize + nMaxpSize + nNameSize + nCmapSize;
+
+ if( nGlyfSize )
+ {
+ nTotalSize += nLocaSize + nGlyfSize;
+ }
+ else
+ {
+ nTotalSize += nCffSize;
+ }
+ nTotalSize += nHheaSize + nHmtxSize;
+ nTotalSize += nPrepSize + nCvtSize + nFpgmSize;
+ rBuffer.resize( nTotalSize );
+
+ // fake a SFNT font directory header
+ if( nTableCount < 16 )
+ {
+ int nLog2 = 0;
+ while( (nTableCount >> nLog2) > 1 ) ++nLog2;
+ rBuffer[ 1] = 1; // Win-TTF style scaler
+ rBuffer[ 5] = nTableCount; // table count
+ rBuffer[ 7] = nLog2*16; // searchRange
+ rBuffer[ 9] = nLog2; // entrySelector
+ rBuffer[11] = (nTableCount-nLog2)*16; // rangeShift
+ }
+
+ // get font table raw data and update the fake directory entries
+ ByteCount nOfs = nFdirSize;
+ unsigned char* pFakeEntry = &rBuffer[12];
+ if( nCmapSize != pMacFont->GetFontTable( "cmap", &rBuffer[nOfs]))
+ return false;
+
+ FakeDirEntry( "cmap", nOfs, nCmapSize, rBuffer.data(), pFakeEntry );
+ nOfs += nCmapSize;
+ if( nCvtSize )
+ {
+ if( nCvtSize != pMacFont->GetFontTable( "cvt ", &rBuffer[nOfs]))
+ return false;
+
+ FakeDirEntry( "cvt ", nOfs, nCvtSize, rBuffer.data(), pFakeEntry );
+ nOfs += nCvtSize;
+ }
+ if( nFpgmSize )
+ {
+ if( nFpgmSize != pMacFont->GetFontTable( "fpgm", &rBuffer[nOfs]))
+ return false;
+
+ FakeDirEntry( "fpgm", nOfs, nFpgmSize, rBuffer.data(), pFakeEntry );
+ nOfs += nFpgmSize;
+ }
+ if( nCffSize )
+ {
+ if( nCffSize != pMacFont->GetFontTable( "CFF ", &rBuffer[nOfs]))
+ return false;
+
+ FakeDirEntry( "CFF ", nOfs, nCffSize, rBuffer.data(), pFakeEntry );
+ nOfs += nGlyfSize;
+ }
+ else
+ {
+ if( nGlyfSize != pMacFont->GetFontTable( "glyf", &rBuffer[nOfs]))
+ return false;
+
+ FakeDirEntry( "glyf", nOfs, nGlyfSize, rBuffer.data(), pFakeEntry );
+ nOfs += nGlyfSize;
+
+ if( nLocaSize != pMacFont->GetFontTable( "loca", &rBuffer[nOfs]))
+ return false;
+
+ FakeDirEntry( "loca", nOfs, nLocaSize, rBuffer.data(), pFakeEntry );
+ nOfs += nLocaSize;
+ }
+ if( nHeadSize != pMacFont->GetFontTable( "head", &rBuffer[nOfs]))
+ return false;
+
+ FakeDirEntry( "head", nOfs, nHeadSize, rBuffer.data(), pFakeEntry );
+ nOfs += nHeadSize;
+
+ if( nHheaSize != pMacFont->GetFontTable( "hhea", &rBuffer[nOfs]))
+ return false;
+
+ FakeDirEntry( "hhea", nOfs, nHheaSize, rBuffer.data(), pFakeEntry );
+ nOfs += nHheaSize;
+ if( nHmtxSize != pMacFont->GetFontTable( "hmtx", &rBuffer[nOfs]))
+ return false;
+
+ FakeDirEntry( "hmtx", nOfs, nHmtxSize, rBuffer.data(), pFakeEntry );
+ nOfs += nHmtxSize;
+ if( nMaxpSize != pMacFont->GetFontTable( "maxp", &rBuffer[nOfs]))
+ return false;
+
+ FakeDirEntry( "maxp", nOfs, nMaxpSize, rBuffer.data(), pFakeEntry );
+ nOfs += nMaxpSize;
+ if( nNameSize != pMacFont->GetFontTable( "name", &rBuffer[nOfs]))
+ return false;
+
+ FakeDirEntry( "name", nOfs, nNameSize, rBuffer.data(), pFakeEntry );
+ nOfs += nNameSize;
+ if( nPrepSize )
+ {
+ if( nPrepSize != pMacFont->GetFontTable( "prep", &rBuffer[nOfs]))
+ return false;
+
+ FakeDirEntry( "prep", nOfs, nPrepSize, rBuffer.data(), pFakeEntry );
+ nOfs += nPrepSize;
+ }
+
+ SAL_WARN_IF( (nOfs!=nTotalSize), "vcl", "AquaSalGraphics::GetRawFontData (nOfs!=nTotalSize)");
+
+ return true;
+}
+
+void AquaSalGraphics::GetGlyphWidths( const PhysicalFontFace* pFontData, bool bVertical,
+ std::vector< sal_Int32 >& rGlyphWidths, Ucs2UIntMap& rUnicodeEnc )
+{
+ rGlyphWidths.clear();
+ rUnicodeEnc.clear();
+
+ std::vector<unsigned char> aBuffer;
+ if( !GetRawFontData( pFontData, aBuffer, nullptr ) )
+ return;
+
+ // TODO: modernize psprint's horrible fontsubset C-API
+ // this probably only makes sense after the switch to another SCM
+ // that can preserve change history after file renames
+
+ // use the font subsetter to get the widths
+ TrueTypeFont* pSftFont = nullptr;
+ SFErrCodes nRC = ::OpenTTFontBuffer( static_cast<void*>(aBuffer.data()), aBuffer.size(), 0, &pSftFont);
+ if( nRC != SFErrCodes::Ok )
+ return;
+
+ const int nGlyphCount = ::GetTTGlyphCount( pSftFont );
+ if( nGlyphCount > 0 )
+ {
+ // get glyph metrics
+ rGlyphWidths.resize(nGlyphCount);
+ std::vector<sal_uInt16> aGlyphIds(nGlyphCount);
+ for( int i = 0; i < nGlyphCount; i++ )
+ {
+ aGlyphIds[i] = static_cast<sal_uInt16>(i);
+ }
+
+ std::unique_ptr<sal_uInt16[]> pGlyphMetrics = ::GetTTSimpleGlyphMetrics( pSftFont, aGlyphIds.data(),
+ nGlyphCount, bVertical );
+ if( pGlyphMetrics )
+ {
+ for( int i = 0; i < nGlyphCount; ++i )
+ {
+ rGlyphWidths[i] = pGlyphMetrics[i];
+ }
+ pGlyphMetrics.reset();
+ }
+
+ rtl::Reference<CoreTextFontFace> rCTFontData(new CoreTextFontFace(*pFontData, pFontData->GetFontId()));
+ FontCharMapRef xFCMap = rCTFontData->GetFontCharMap();
+ SAL_WARN_IF( !xFCMap.is() || !xFCMap->GetCharCount(), "vcl", "no charmap" );
+
+ // get unicode<->glyph encoding
+ // TODO? avoid sft mapping by using the xFCMap itself
+ int nCharCount = xFCMap->GetCharCount();
+ sal_uInt32 nChar = xFCMap->GetFirstChar();
+ for( ; --nCharCount >= 0; nChar = xFCMap->GetNextChar( nChar ) )
+ {
+ if( nChar > 0xFFFF ) // TODO: allow UTF-32 chars
+ break;
+
+ sal_Ucs nUcsChar = static_cast<sal_Ucs>(nChar);
+ sal_uInt32 nGlyph = ::MapChar( pSftFont, nUcsChar );
+ if( nGlyph > 0 )
+ {
+ rUnicodeEnc[ nUcsChar ] = nGlyph;
+ }
+ }
+
+ xFCMap = nullptr;
+ }
+
+ ::CloseTTFont( pSftFont );
+}
+
+const void* AquaSalGraphics::GetEmbedFontData(const PhysicalFontFace*, long* /*pDataLen*/)
+{
+ return nullptr;
+}
+
+void AquaSalGraphics::FreeEmbedFontData( const void* pData, long /*nDataLen*/ )
+{
+ // TODO: implementing this only makes sense when the implementation of
+ // AquaSalGraphics::GetEmbedFontData() returns non-NULL
+ SAL_WARN_IF( (pData==nullptr), "vcl", "AquaSalGraphics::FreeEmbedFontData() is not implemented");
+}
+
+bool AquaSalGraphics::IsFlipped() const
+{
+#ifdef MACOSX
+ return mbWindow;
+#else
+ return false;
+#endif
+}
+
+void AquaSalGraphics::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(static_cast<long int>(lX-1),
+ static_cast<long int>(lY-1) ),
+ Size( static_cast<long int>(lWidth+4),
+ static_cast<long int>(lHeight+4) ) );
+ mpFrame->maInvalidRect.Union( aVclRect );
+ }
+#else
+ (void) lX;
+ (void) lY;
+ (void) lWidth;
+ (void) lHeight;
+ return;
+#endif
+}
+
+#ifdef IOS
+
+bool AquaSalGraphics::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;
+}
+
+CGContextRef AquaSalGraphics::GetContext()
+{
+ if (!maContextHolder.isSet())
+ CheckContext();
+
+ return maContextHolder.get();
+}
+
+#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 000000000..8b51e91c6
--- /dev/null
+++ b/vcl/quartz/salgdicommon.cxx
@@ -0,0 +1,2047 @@
+/* -*- 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 <vcl/sysdata.hxx>
+
+#include <fontsubset.hxx>
+#include <quartz/salbmp.h>
+#ifdef MACOSX
+#include <quartz/salgdi.h>
+#endif
+#include <quartz/utils.h>
+#ifdef IOS
+#include "saldatabasic.hxx"
+#endif
+#include <sft.hxx>
+
+using namespace vcl;
+
+static const basegfx::B2DPoint aHalfPointOfs ( 0.5, 0.5 );
+
+static 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 );
+ }
+}
+
+static void AddPolyPolygonToPath( CGMutablePathRef xPath,
+ const basegfx::B2DPolyPolygon& rPolyPoly,
+ bool bPixelSnap, bool bLineDraw )
+{
+ // short circuit if there is nothing to do
+ if( rPolyPoly.count() == 0 )
+ {
+ return;
+ }
+ for(auto const& rPolygon : rPolyPoly)
+ {
+ AddPolygonToPath( xPath, rPolygon, true, bPixelSnap, bLineDraw );
+ }
+}
+
+bool AquaSalGraphics::CreateFontSubset( const OUString& rToFile,
+ const PhysicalFontFace* pFontData,
+ const sal_GlyphId* pGlyphIds, const sal_uInt8* pEncoding,
+ sal_Int32* pGlyphWidths, int nGlyphCount,
+ FontSubsetInfo& rInfo )
+{
+ // TODO: move more of the functionality here into the generic subsetter code
+
+ // prepare the requested file name for writing the font-subset file
+ OUString aSysPath;
+ if( osl_File_E_None != osl_getSystemPathFromFileURL( rToFile.pData, &aSysPath.pData ) )
+ {
+ return false;
+ }
+
+ // get the raw-bytes from the font to be subset
+ std::vector<unsigned char> aBuffer;
+ bool bCffOnly = false;
+ if( !GetRawFontData( pFontData, aBuffer, &bCffOnly ) )
+ {
+ return false;
+ }
+ const OString aToFile( OUStringToOString( aSysPath,
+ osl_getThreadTextEncoding()));
+
+ // handle CFF-subsetting
+ if( bCffOnly )
+ {
+ // provide the raw-CFF data to the subsetter
+ ByteCount nCffLen = aBuffer.size();
+ rInfo.LoadFont( FontType::CFF_FONT, aBuffer.data(), nCffLen );
+
+ // NOTE: assuming that all glyphids requested on Aqua are fully translated
+
+ // make the subsetter provide the requested subset
+ FILE* pOutFile = fopen( aToFile.getStr(), "wb" );
+ bool bRC = rInfo.CreateFontSubset( FontType::TYPE1_PFB, pOutFile, nullptr,
+ pGlyphIds, pEncoding, nGlyphCount, pGlyphWidths );
+ fclose( pOutFile );
+ return bRC;
+ }
+
+ // TODO: modernize psprint's horrible fontsubset C-API
+ // this probably only makes sense after the switch to another SCM
+ // that can preserve change history after file renames
+
+ // prepare data for psprint's font subsetter
+ TrueTypeFont* pSftFont = nullptr;
+ SFErrCodes nRC = ::OpenTTFontBuffer( static_cast<void*>(aBuffer.data()), aBuffer.size(), 0, &pSftFont);
+ if( nRC != SFErrCodes::Ok )
+ {
+ return false;
+ }
+ // get details about the subsetted font
+ TTGlobalFontInfo aTTInfo;
+ ::GetTTGlobalFontInfo( pSftFont, &aTTInfo );
+ rInfo.m_nFontType = FontType::SFNT_TTF;
+ rInfo.m_aPSName = OUString( aTTInfo.psname, std::strlen(aTTInfo.psname),
+ RTL_TEXTENCODING_UTF8 );
+ 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;
+ }
+
+ // subset glyphs and get their properties
+ // take care that subset fonts require the NotDef glyph in pos 0
+ int nOrigCount = nGlyphCount;
+ sal_uInt16 aShortIDs[ 257 ];
+ sal_uInt8 aTempEncs[ 257 ];
+ int nNotDef = -1;
+
+ assert( (nGlyphCount <= 256 && "too many glyphs for subsetting" ));
+
+ for( int i = 0; i < nGlyphCount; ++i )
+ {
+ aTempEncs[i] = pEncoding[i];
+
+ sal_GlyphId aGlyphId(pGlyphIds[i]);
+ aShortIDs[i] = static_cast<sal_uInt16>( aGlyphId );
+ if( !aGlyphId && nNotDef < 0 )
+ {
+ nNotDef = i; // first NotDef glyph found
+ }
+ }
+
+ if( nNotDef != 0 )
+ {
+ // add fake NotDef glyph if needed
+ if( nNotDef < 0 )
+ {
+ nNotDef = nGlyphCount++;
+ }
+ // NotDef glyph must be in pos 0 => swap glyphids
+ aShortIDs[ nNotDef ] = aShortIDs[0];
+ aTempEncs[ nNotDef ] = aTempEncs[0];
+ aShortIDs[0] = 0;
+ aTempEncs[0] = 0;
+ }
+
+ // TODO: where to get bVertical?
+ const bool bVertical = false;
+
+ // fill the pGlyphWidths array
+ // while making sure that the NotDef glyph is at index==0
+ std::unique_ptr<sal_uInt16[]> pGlyphMetrics = ::GetTTSimpleGlyphMetrics( pSftFont, aShortIDs,
+ nGlyphCount, bVertical );
+ if( !pGlyphMetrics )
+ {
+ return false;
+ }
+
+ sal_uInt16 nNotDefAdv = pGlyphMetrics[0];
+ pGlyphMetrics[0] = pGlyphMetrics[nNotDef];
+ pGlyphMetrics[nNotDef] = nNotDefAdv;
+ for( int i = 0; i < nOrigCount; ++i )
+ {
+ pGlyphWidths[i] = pGlyphMetrics[i];
+ }
+ pGlyphMetrics.reset();
+
+ // write subset into destination file
+ nRC = ::CreateTTFromTTGlyphs( pSftFont, aToFile.getStr(), aShortIDs,
+ aTempEncs, nGlyphCount );
+ ::CloseTTFont(pSftFont);
+ return (nRC == SFErrCodes::Ok);
+}
+
+static void alignLinePoint( const SalPoint* i_pIn, float& o_fX, float& o_fY )
+{
+ o_fX = static_cast<float>(i_pIn->mnX ) + 0.5;
+ o_fY = static_cast<float>(i_pIn->mnY ) + 0.5;
+}
+
+void AquaSalGraphics::copyBits( const SalTwoRect& rPosAry, SalGraphics *pSrcGraphics )
+{
+
+ if( !pSrcGraphics )
+ {
+ pSrcGraphics = this;
+ }
+ //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;
+ }
+
+#ifdef IOS
+ // If called from idle layout, maContextHolder.get() is NULL, no idea what to do
+ if (!maContextHolder.isSet())
+ return;
+#endif
+
+ // accelerate trivial operations
+ /*const*/ AquaSalGraphics* pSrc = static_cast<AquaSalGraphics*>(pSrcGraphics);
+ const bool bSameGraphics = (this == pSrc)
+#ifdef MACOSX
+ || (mbWindow && mpFrame && pSrc->mbWindow && (mpFrame == pSrc->mpFrame))
+#endif
+ ;
+
+ 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;
+ }
+
+ ApplyXorContext();
+ pSrc->ApplyXorContext();
+
+ SAL_WARN_IF (!pSrc->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) &&
+ (!mnBitmapDepth || (aDstPoint.x + pSrc->mnWidth) <= mnWidth)
+ && pSrc->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 = maContextHolder;
+ if( mpXorEmulation && mpXorEmulation->IsEnabled() )
+ {
+ if( pSrcGraphics == this )
+ {
+ aCopyContext.set(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( pSrc->IsFlipped() )
+ {
+ CGContextTranslateCTM( aCopyContext.get(), 0, +mnHeight );
+ CGContextScaleCTM( aCopyContext.get(), +1, -1 );
+ }
+
+ // TODO: pSrc->size() != this->size()
+ CGContextDrawLayerAtPoint(aCopyContext.get(), aDstPoint, pSrc->maLayer.get());
+
+ aCopyContext.restoreState();
+ // mark the destination rectangle as updated
+ RefreshRect( aDstRect );
+ }
+ else
+ {
+ std::shared_ptr<SalBitmap> pBitmap = pSrc->getBitmap( rPosAry.mnSrcX, rPosAry.mnSrcY,
+ rPosAry.mnSrcWidth, rPosAry.mnSrcHeight );
+ if( pBitmap )
+ {
+ SalTwoRect aPosAry( rPosAry );
+ aPosAry.mnSrcX = 0;
+ aPosAry.mnSrcY = 0;
+ drawBitmap( aPosAry, *pBitmap );
+ }
+ }
+}
+
+static void DrawPattern50( void*, CGContextRef rContext )
+{
+ static const CGRect aRects[2] = { { {0,0}, { 2, 2 } }, { { 2, 2 }, { 2, 2 } } };
+ CGContextAddRects( rContext, aRects, 2 );
+ CGContextFillPath( rContext );
+}
+
+static void getBoundRect( sal_uInt32 nPoints, const SalPoint *pPtAry,
+ long &rX, long& rY, long& rWidth, long& rHeight )
+{
+ long nX1 = pPtAry->mnX;
+ long nX2 = nX1;
+ long nY1 = pPtAry->mnY;
+ long nY2 = nY1;
+
+ for( sal_uInt32 n = 1; n < nPoints; n++ )
+ {
+ if( pPtAry[n].mnX < nX1 )
+ {
+ nX1 = pPtAry[n].mnX;
+ }
+ else if( pPtAry[n].mnX > nX2 )
+ {
+ nX2 = pPtAry[n].mnX;
+ }
+ if( pPtAry[n].mnY < nY1 )
+ {
+ nY1 = pPtAry[n].mnY;
+ }
+ else if( pPtAry[n].mnY > nY2 )
+ {
+ nY2 = pPtAry[n].mnY;
+ }
+ }
+ rX = nX1;
+ rY = nY1;
+ rWidth = nX2 - nX1 + 1;
+ rHeight = nY2 - nY1 + 1;
+}
+
+static Color ImplGetROPColor( SalROPColor nROPColor )
+{
+ Color nColor;
+ if ( nROPColor == SalROPColor::N0 )
+ {
+ nColor = Color( 0, 0, 0 );
+ }
+ else
+ {
+ nColor = Color( 255, 255, 255 );
+ }
+ return nColor;
+}
+
+// apply the XOR mask to the target context if active and dirty
+void AquaSalGraphics::ApplyXorContext()
+{
+ if( !mpXorEmulation )
+ {
+ return;
+ }
+ if( mpXorEmulation->UpdateTarget() )
+ {
+ RefreshRect( 0, 0, mnWidth, mnHeight ); // TODO: refresh minimal changerect
+ }
+}
+
+void AquaSalGraphics::copyArea( long nDstX, long nDstY,long nSrcX, long nSrcY,
+ long nSrcWidth, long nSrcHeight, bool /*bWindowInvalidate*/ )
+{
+ SAL_WARN_IF (!maLayer.isSet(), "vcl.quartz",
+ "AquaSalGraphics::copyArea() for non-layered graphics this=" << this);
+
+#ifdef IOS
+ if (!maLayer.isSet())
+ return;
+#endif
+ float fScale = maLayer.getScale();
+
+ long nScaledSourceX = nSrcX * fScale;
+ long nScaledSourceY = nSrcY * fScale;
+
+ long nScaledTargetX = nDstX * fScale;
+ long nScaledTargetY = nDstY * fScale;
+
+ long nScaledSourceWidth = nSrcWidth * fScale;
+ long nScaledSourceHeight = nSrcHeight * fScale;
+
+ ApplyXorContext();
+
+ maContextHolder.saveState();
+
+ // in XOR mode the drawing context is redirected to the XOR mask
+ // copyArea() always works on the target context though
+ CGContextRef xCopyContext = maContextHolder.get();
+
+ if( mpXorEmulation && mpXorEmulation->IsEnabled() )
+ {
+ xCopyContext = 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(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( IsFlipped() )
+ {
+ CGContextTranslateCTM( xSrcContext, 0, +nScaledSourceHeight );
+ CGContextScaleCTM( xSrcContext, +1, -1 );
+ aSrcPoint.y = (nScaledSourceY + nScaledSourceHeight) - (mnHeight * fScale);
+ }
+ CGContextSetBlendMode(xSrcContext, kCGBlendModeCopy);
+
+ CGContextDrawLayerAtPoint(xSrcContext, aSrcPoint, maLayer.get());
+ }
+
+ // draw at new destination
+ const CGRect aTargetRect = CGRectMake(nScaledTargetX, nScaledTargetY, nScaledSourceWidth, nScaledSourceHeight);
+ CGContextSetBlendMode(xCopyContext, kCGBlendModeCopy);
+ CGContextDrawLayerInRect(xCopyContext, aTargetRect, sSourceLayerHolder.get());
+
+ maContextHolder.restoreState();
+
+ // cleanup
+ if (sSourceLayerHolder.get() != maLayer.get())
+ {
+ CGLayerRelease(sSourceLayerHolder.get());
+ }
+ // mark the destination rectangle as updated
+ RefreshRect( nDstX, nDstY, nSrcWidth, nSrcHeight );
+}
+
+#ifndef IOS
+
+void AquaSalGraphics::copyResolution( AquaSalGraphics& rGraphics )
+{
+ if( !rGraphics.mnRealDPIY && rGraphics.mbWindow && rGraphics.mpFrame )
+ {
+ rGraphics.initResolution( rGraphics.mpFrame->getNSWindow() );
+ }
+ mnRealDPIX = rGraphics.mnRealDPIX;
+ mnRealDPIY = rGraphics.mnRealDPIY;
+}
+
+#endif
+
+bool AquaSalGraphics::blendBitmap( const SalTwoRect&,
+ const SalBitmap& )
+{
+ return false;
+}
+
+bool AquaSalGraphics::blendAlphaBitmap( const SalTwoRect&,
+ const SalBitmap&,
+ const SalBitmap&,
+ const SalBitmap& )
+{
+ return false;
+}
+
+bool AquaSalGraphics::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;
+
+ const QuartzSalBitmap& rSrcSalBmp = static_cast<const QuartzSalBitmap&>(rSrcBitmap);
+ const QuartzSalBitmap& rMaskSalBmp = static_cast<const QuartzSalBitmap&>(rAlphaBmp);
+ CGImageRef xMaskedImage = rSrcSalBmp.CreateWithMask( rMaskSalBmp, rTR.mnSrcX,
+ rTR.mnSrcY, rTR.mnSrcWidth,
+ rTR.mnSrcHeight );
+ if( !xMaskedImage )
+ return false;
+
+ if ( CheckContext() )
+ {
+ const CGRect aDstRect = CGRectMake( rTR.mnDestX, rTR.mnDestY, rTR.mnDestWidth, rTR.mnDestHeight);
+ CGContextDrawImage( maContextHolder.get(), aDstRect, xMaskedImage );
+ RefreshRect( aDstRect );
+ }
+
+ CGImageRelease(xMaskedImage);
+
+ return true;
+}
+
+bool AquaSalGraphics::drawTransformedBitmap(
+ const basegfx::B2DPoint& rNull, const basegfx::B2DPoint& rX, const basegfx::B2DPoint& rY,
+ const SalBitmap& rSrcBitmap, const SalBitmap* pAlphaBmp )
+{
+ if( !CheckContext() )
+ return true;
+
+ // get the Quartz image
+ CGImageRef xImage = nullptr;
+ const Size aSize = rSrcBitmap.GetSize();
+ const QuartzSalBitmap& rSrcSalBmp = static_cast<const QuartzSalBitmap&>(rSrcBitmap);
+ const QuartzSalBitmap* pMaskSalBmp = static_cast<const QuartzSalBitmap*>(pAlphaBmp);
+
+ if( !pMaskSalBmp)
+ xImage = rSrcSalBmp.CreateCroppedImage( 0, 0, static_cast<int>(aSize.Width()), static_cast<int>(aSize.Height()) );
+ else
+ xImage = rSrcSalBmp.CreateWithMask( *pMaskSalBmp, 0, 0, static_cast<int>(aSize.Width()), static_cast<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
+ 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( maContextHolder.get(), aCGMat );
+
+ // draw the transformed image
+ const CGRect aSrcRect = CGRectMake(0, 0, aSize.Width(), aSize.Height());
+ CGContextDrawImage( maContextHolder.get(), aSrcRect, xImage );
+
+ CGImageRelease( xImage );
+ // restore the Quartz graphics state
+ maContextHolder.restoreState();
+
+ // mark the destination as painted
+ const CGRect aDstRect = CGRectApplyAffineTransform( aSrcRect, aCGMat );
+ RefreshRect( aDstRect );
+
+ return true;
+}
+
+bool AquaSalGraphics::drawAlphaRect( long nX, long nY, long nWidth,
+ long nHeight, sal_uInt8 nTransparency )
+{
+ if( !CheckContext() )
+ return true;
+
+ // save the current state
+ maContextHolder.saveState();
+ CGContextSetAlpha( maContextHolder.get(), (100-nTransparency) * (1.0/100) );
+
+ CGRect aRect = CGRectMake(nX, nY, nWidth-1, nHeight-1);
+ if( IsPenVisible() )
+ {
+ aRect.origin.x += 0.5;
+ aRect.origin.y += 0.5;
+ }
+
+ CGContextBeginPath( maContextHolder.get() );
+ CGContextAddRect( maContextHolder.get(), aRect );
+ CGContextDrawPath( maContextHolder.get(), kCGPathFill );
+
+ maContextHolder.restoreState();
+ RefreshRect( aRect );
+
+ return true;
+}
+
+void AquaSalGraphics::drawBitmap( const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap )
+{
+ if( !CheckContext() )
+ return;
+
+ const QuartzSalBitmap& rBitmap = static_cast<const QuartzSalBitmap&>(rSalBitmap);
+ CGImageRef xImage = rBitmap.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( maContextHolder.get(), aDstRect, xImage );
+
+ CGImageRelease( xImage );
+ RefreshRect( aDstRect );
+}
+
+void AquaSalGraphics::drawBitmap( const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap,
+ const SalBitmap& rTransparentBitmap )
+{
+ if( !CheckContext() )
+ return;
+
+ const QuartzSalBitmap& rBitmap = static_cast<const QuartzSalBitmap&>(rSalBitmap);
+ const QuartzSalBitmap& rMask = static_cast<const QuartzSalBitmap&>(rTransparentBitmap);
+ CGImageRef xMaskedImage( rBitmap.CreateWithMask( rMask, rPosAry.mnSrcX, rPosAry.mnSrcY,
+ rPosAry.mnSrcWidth, rPosAry.mnSrcHeight ) );
+ if( !xMaskedImage )
+ return;
+
+ const CGRect aDstRect = CGRectMake(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth, rPosAry.mnDestHeight);
+ CGContextDrawImage( maContextHolder.get(), aDstRect, xMaskedImage );
+ CGImageRelease( xMaskedImage );
+ RefreshRect( aDstRect );
+}
+
+#ifndef IOS
+
+bool AquaSalGraphics::drawEPS( long nX, long nY, long nWidth, long nHeight,
+ void* pEpsData, sal_uInt32 nByteCount )
+{
+ // convert the raw data to an NSImageRef
+ NSData* xNSData = [NSData dataWithBytes:pEpsData length:static_cast<int>(nByteCount)];
+ NSImageRep* xEpsImage = [NSEPSImageRep imageRepWithData: xNSData];
+ if( !xEpsImage )
+ {
+ return false;
+ }
+ // get the target context
+ if( !CheckContext() )
+ {
+ return false;
+ }
+ // NOTE: flip drawing, else the nsimage would be drawn upside down
+ maContextHolder.saveState();
+// CGContextTranslateCTM( maContextHolder.get(), 0, +mnHeight );
+ CGContextScaleCTM( 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: maContextHolder.get() flipped: 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
+
+ maContextHolder.restoreState();
+ // mark the destination rectangle as updated
+ RefreshRect( aDstRect );
+
+ return bOK;
+}
+
+#endif
+
+void AquaSalGraphics::drawLine( long nX1, long nY1, long nX2, long nY2 )
+{
+ if( nX1 == nX2 && nY1 == nY2 )
+ {
+ // #i109453# platform independent code expects at least one pixel to be drawn
+ drawPixel( nX1, nY1 );
+
+ return;
+ }
+
+ if( !CheckContext() )
+ return;
+
+ CGContextBeginPath( maContextHolder.get() );
+ CGContextMoveToPoint( maContextHolder.get(), static_cast<float>(nX1)+0.5, static_cast<float>(nY1)+0.5 );
+ CGContextAddLineToPoint( maContextHolder.get(), static_cast<float>(nX2)+0.5, static_cast<float>(nY2)+0.5 );
+ CGContextDrawPath( maContextHolder.get(), kCGPathStroke );
+
+ tools::Rectangle aRefreshRect( nX1, nY1, nX2, nY2 );
+ (void) aRefreshRect;
+ // Is a call to RefreshRect( aRefreshRect ) missing here?
+}
+
+void AquaSalGraphics::drawMask( const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap, Color nMaskColor )
+{
+ if( !CheckContext() )
+ return;
+
+ const QuartzSalBitmap& rBitmap = static_cast<const QuartzSalBitmap&>(rSalBitmap);
+ CGImageRef xImage = rBitmap.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( maContextHolder.get(), aDstRect, xImage );
+ CGImageRelease( xImage );
+ RefreshRect( aDstRect );
+}
+
+void AquaSalGraphics::drawPixel( long nX, long nY )
+{
+ // draw pixel with current line color
+ ImplDrawPixel( nX, nY, maLineColor );
+}
+
+void AquaSalGraphics::drawPixel( long nX, long nY, Color nColor )
+{
+ const RGBAColor aPixelColor( nColor );
+ ImplDrawPixel( nX, nY, aPixelColor );
+}
+
+bool AquaSalGraphics::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( !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(fMiterMinimumAngle / 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(),
+ !getAntiAliasB2DDraw(),
+ 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
+ maContextHolder.saveState();
+ CGContextBeginPath( maContextHolder.get() );
+ CGContextAddPath( maContextHolder.get(), xPath );
+ // draw path with antialiased line
+ CGContextSetShouldAntialias( maContextHolder.get(), true );
+ CGContextSetAlpha( maContextHolder.get(), 1.0 - fTransparency );
+ CGContextSetLineJoin( maContextHolder.get(), aCGLineJoin );
+ CGContextSetLineCap( maContextHolder.get(), aCGLineCap );
+ CGContextSetLineWidth( maContextHolder.get(), fLineWidth );
+ CGContextSetMiterLimit(maContextHolder.get(), fCGMiterLimit);
+ CGContextDrawPath( maContextHolder.get(), kCGPathStroke );
+ maContextHolder.restoreState();
+
+ // mark modified rectangle as updated
+ RefreshRect( aRefreshRect );
+ }
+
+ CGPathRelease( xPath );
+
+ return true;
+}
+
+bool AquaSalGraphics::drawPolyLineBezier( sal_uInt32, const SalPoint*, const PolyFlags* )
+{
+ return false;
+}
+
+bool AquaSalGraphics::drawPolyPolygon(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon& rPolyPolygon,
+ double fTransparency)
+{
+#ifdef IOS
+ if (!maContextHolder.isSet())
+ return true;
+#endif
+
+ // short circuit if there is nothing to do
+ if( rPolyPolygon.count() == 0 )
+ return true;
+
+ // ignore invisible polygons
+ if( (fTransparency >= 1.0) || (fTransparency < 0) )
+ return true;
+
+ // 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 : aPolyPolygon)
+ {
+ AddPolygonToPath( xPath, rPolygon, true, !getAntiAliasB2DDraw(), 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( IsBrushVisible() && IsPenVisible() )
+ {
+ eMode = kCGPathEOFillStroke;
+ }
+ else if( IsPenVisible() )
+ {
+ eMode = kCGPathStroke;
+ }
+ else if( IsBrushVisible() )
+ {
+ eMode = kCGPathEOFill;
+ }
+ else
+ {
+ SAL_WARN( "vcl.quartz", "Neither pen nor brush visible" );
+ CGPathRelease( xPath );
+ return true;
+ }
+
+ // use the path to prepare the graphics context
+ maContextHolder.saveState();
+ CGContextBeginPath( maContextHolder.get() );
+ CGContextAddPath( maContextHolder.get(), xPath );
+
+ // draw path with antialiased polygon
+ CGContextSetShouldAntialias( maContextHolder.get(), true );
+ CGContextSetAlpha( maContextHolder.get(), 1.0 - fTransparency );
+ CGContextDrawPath( maContextHolder.get(), eMode );
+ maContextHolder.restoreState();
+
+ // mark modified rectangle as updated
+ RefreshRect( aRefreshRect );
+ }
+
+ CGPathRelease( xPath );
+
+ return true;
+}
+
+void AquaSalGraphics::drawPolyPolygon( sal_uInt32 nPolyCount, const sal_uInt32 *pPoints, PCONSTSALPOINT *ppPtAry )
+{
+ if( nPolyCount <= 0 )
+ return;
+
+ if( !CheckContext() )
+ return;
+
+ // find bound rect
+ 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++ )
+ {
+ 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( IsBrushVisible() && IsPenVisible() )
+ {
+ eMode = kCGPathEOFillStroke;
+ }
+ else if( IsPenVisible() )
+ {
+ eMode = kCGPathStroke;
+ }
+ else if( IsBrushVisible() )
+ {
+ eMode = kCGPathEOFill;
+ }
+ else
+ {
+ SAL_WARN( "vcl.quartz", "Neither pen nor brush visible" );
+ return;
+ }
+
+ // convert to CGPath
+ CGContextBeginPath( maContextHolder.get() );
+ if( IsPenVisible() )
+ {
+ for( sal_uInt32 nPoly = 0; nPoly < nPolyCount; nPoly++ )
+ {
+ const sal_uInt32 nPoints = pPoints[nPoly];
+ if( nPoints > 1 )
+ {
+ const SalPoint *pPtAry = ppPtAry[nPoly];
+ float fX, fY;
+
+ alignLinePoint( pPtAry, fX, fY );
+ CGContextMoveToPoint( maContextHolder.get(), fX, fY );
+ pPtAry++;
+
+ for( sal_uInt32 nPoint = 1; nPoint < nPoints; nPoint++, pPtAry++ )
+ {
+ alignLinePoint( pPtAry, fX, fY );
+ CGContextAddLineToPoint( maContextHolder.get(), fX, fY );
+ }
+ CGContextClosePath(maContextHolder.get());
+ }
+ }
+ }
+ else
+ {
+ for( sal_uInt32 nPoly = 0; nPoly < nPolyCount; nPoly++ )
+ {
+ const sal_uInt32 nPoints = pPoints[nPoly];
+ if( nPoints > 1 )
+ {
+ const SalPoint *pPtAry = ppPtAry[nPoly];
+ CGContextMoveToPoint( maContextHolder.get(), pPtAry->mnX, pPtAry->mnY );
+ pPtAry++;
+ for( sal_uInt32 nPoint = 1; nPoint < nPoints; nPoint++, pPtAry++ )
+ {
+ CGContextAddLineToPoint( maContextHolder.get(), pPtAry->mnX, pPtAry->mnY );
+ }
+ CGContextClosePath(maContextHolder.get());
+ }
+ }
+ }
+
+ CGContextDrawPath( maContextHolder.get(), eMode );
+
+ RefreshRect( leftX, topY, maxWidth, maxHeight );
+}
+
+void AquaSalGraphics::drawPolygon( sal_uInt32 nPoints, const SalPoint *pPtAry )
+{
+ if( nPoints <= 1 )
+ return;
+
+ if( !CheckContext() )
+ return;
+
+ long nX = 0, nY = 0, nWidth = 0, nHeight = 0;
+ getBoundRect( nPoints, pPtAry, nX, nY, nWidth, nHeight );
+
+ CGPathDrawingMode eMode;
+ if( IsBrushVisible() && IsPenVisible() )
+ {
+ eMode = kCGPathEOFillStroke;
+ }
+ else if( IsPenVisible() )
+ {
+ eMode = kCGPathStroke;
+ }
+ else if( IsBrushVisible() )
+ {
+ eMode = kCGPathEOFill;
+ }
+ else
+ {
+ SAL_WARN( "vcl.quartz", "Neither pen nor brush visible" );
+ return;
+ }
+
+ CGContextBeginPath( maContextHolder.get() );
+
+ if( IsPenVisible() )
+ {
+ float fX, fY;
+ alignLinePoint( pPtAry, fX, fY );
+ CGContextMoveToPoint( maContextHolder.get(), fX, fY );
+ pPtAry++;
+ for( sal_uInt32 nPoint = 1; nPoint < nPoints; nPoint++, pPtAry++ )
+ {
+ alignLinePoint( pPtAry, fX, fY );
+ CGContextAddLineToPoint( maContextHolder.get(), fX, fY );
+ }
+ }
+ else
+ {
+ CGContextMoveToPoint( maContextHolder.get(), pPtAry->mnX, pPtAry->mnY );
+ pPtAry++;
+ for( sal_uInt32 nPoint = 1; nPoint < nPoints; nPoint++, pPtAry++ )
+ {
+ CGContextAddLineToPoint( maContextHolder.get(), pPtAry->mnX, pPtAry->mnY );
+ }
+ }
+
+ CGContextClosePath( maContextHolder.get() );
+ CGContextDrawPath( maContextHolder.get(), eMode );
+ RefreshRect( nX, nY, nWidth, nHeight );
+}
+
+bool AquaSalGraphics::drawPolygonBezier( sal_uInt32, const SalPoint*, const PolyFlags* )
+{
+ return false;
+}
+
+bool AquaSalGraphics::drawPolyPolygonBezier( sal_uInt32, const sal_uInt32*,
+ const SalPoint* const*, const PolyFlags* const* )
+{
+ return false;
+}
+
+void AquaSalGraphics::drawRect( long nX, long nY, long nWidth, long nHeight )
+{
+ if( !CheckContext() )
+ return;
+
+ CGRect aRect( CGRectMake(nX, nY, nWidth, nHeight) );
+ if( IsPenVisible() )
+ {
+ aRect.origin.x += 0.5;
+ aRect.origin.y += 0.5;
+ aRect.size.width -= 1;
+ aRect.size.height -= 1;
+ }
+
+ if( IsBrushVisible() )
+ {
+ CGContextFillRect( maContextHolder.get(), aRect );
+ }
+ if( IsPenVisible() )
+ {
+ CGContextStrokeRect( maContextHolder.get(), aRect );
+ }
+ RefreshRect( nX, nY, nWidth, nHeight );
+}
+
+void AquaSalGraphics::drawPolyLine( sal_uInt32 nPoints, const SalPoint *pPtAry )
+{
+ if( nPoints < 1 )
+ return;
+
+ if( !CheckContext() )
+ return;
+
+ long nX = 0, nY = 0, nWidth = 0, nHeight = 0;
+ getBoundRect( nPoints, pPtAry, nX, nY, nWidth, nHeight );
+
+ float fX, fY;
+ CGContextBeginPath( maContextHolder.get() );
+ alignLinePoint( pPtAry, fX, fY );
+ CGContextMoveToPoint( maContextHolder.get(), fX, fY );
+ pPtAry++;
+
+ for( sal_uInt32 nPoint = 1; nPoint < nPoints; nPoint++, pPtAry++ )
+ {
+ alignLinePoint( pPtAry, fX, fY );
+ CGContextAddLineToPoint( maContextHolder.get(), fX, fY );
+ }
+ CGContextStrokePath(maContextHolder.get());
+
+ RefreshRect( nX, nY, nWidth, nHeight );
+}
+
+sal_uInt16 AquaSalGraphics::GetBitCount() const
+{
+ sal_uInt16 nBits = mnBitmapDepth ? mnBitmapDepth : 32;//24;
+ return nBits;
+}
+
+std::shared_ptr<SalBitmap> AquaSalGraphics::getBitmap( long nX, long nY, long nDX, long nDY )
+{
+ SAL_WARN_IF(!maLayer.isSet(), "vcl.quartz", "AquaSalGraphics::getBitmap() with no layer this=" << this);
+
+ ApplyXorContext();
+
+ std::shared_ptr<QuartzSalBitmap> pBitmap = std::make_shared<QuartzSalBitmap>();
+ if (!pBitmap->Create(maLayer, mnBitmapDepth, nX, nY, nDX, nDY, IsFlipped()))
+ {
+ pBitmap = nullptr;
+ }
+ return pBitmap;
+}
+
+SystemGraphicsData AquaSalGraphics::GetGraphicsData() const
+{
+ SystemGraphicsData aRes;
+ aRes.nSize = sizeof(aRes);
+ aRes.rCGContext = maContextHolder.get();
+ return aRes;
+}
+
+long AquaSalGraphics::GetGraphicsWidth() const
+{
+ long w = 0;
+ if( maContextHolder.isSet() && (
+#ifndef IOS
+ mbWindow ||
+#endif
+ mbVirDev) )
+ {
+ w = mnWidth;
+ }
+
+#ifndef IOS
+ if( w == 0 )
+ {
+ if( mbWindow && mpFrame )
+ {
+ w = mpFrame->maGeometry.nWidth;
+ }
+ }
+#endif
+ return w;
+}
+
+Color AquaSalGraphics::getPixel( long nX, long nY )
+{
+ // return default value on printers or when out of bounds
+ if (!maLayer.isSet() || (nX < 0) || (nX >= mnWidth) ||
+ (nY < 0) || (nY >= mnHeight))
+ {
+ return sal_uInt32(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
+ ApplyXorContext();
+
+ // copy the requested pixel into the bitmap context
+ if( IsFlipped() )
+ {
+ nY = mnHeight - nY;
+ }
+ const CGPoint aCGPoint = CGPointMake(-nX, -nY);
+ CGContextDrawLayerAtPoint(xOnePixelContext, aCGPoint, 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( (mbWindow && mpFrame) ? 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 AquaSalGraphics::ImplDrawPixel( long nX, long nY, const RGBAColor& rColor )
+{
+ if( !CheckContext() )
+ {
+ return;
+ }
+ // overwrite the fill color
+ CGContextSetFillColor( maContextHolder.get(), rColor.AsArray() );
+ // draw 1x1 rect, there is no pixel drawing in Quartz
+ const CGRect aDstRect = CGRectMake(nX, nY, 1, 1);
+ CGContextFillRect( maContextHolder.get(), aDstRect );
+ RefreshRect( aDstRect );
+ // reset the fill color
+ CGContextSetFillColor( maContextHolder.get(), maFillColor.AsArray() );
+}
+
+#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<long>((CGDisplayPixelsWide( nDisplayID ) * 25.4) / aSize.width);
+ mnRealDPIY = static_cast<long>((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 AquaSalGraphics::invert( long nX, long nY, long nWidth, long nHeight, SalInvert nFlags )
+{
+ if ( CheckContext() )
+ {
+ CGRect aCGRect = CGRectMake( nX, nY, nWidth, nHeight);
+ maContextHolder.saveState();
+ if ( nFlags & SalInvert::TrackFrame )
+ {
+ const CGFloat dashLengths[2] = { 4.0, 4.0 }; // for drawing dashed line
+ CGContextSetBlendMode( maContextHolder.get(), kCGBlendModeDifference );
+ CGContextSetRGBStrokeColor ( maContextHolder.get(), 1.0, 1.0, 1.0, 1.0 );
+ CGContextSetLineDash ( maContextHolder.get(), 0, dashLengths, 2 );
+ CGContextSetLineWidth( maContextHolder.get(), 2.0);
+ CGContextStrokeRect ( maContextHolder.get(), aCGRect );
+ }
+ else if ( nFlags & SalInvert::N50 )
+ {
+ //CGContextSetAllowsAntialiasing( maContextHolder.get(), false );
+ CGContextSetBlendMode(maContextHolder.get(), kCGBlendModeDifference);
+ CGContextAddRect( maContextHolder.get(), aCGRect );
+ Pattern50Fill();
+ }
+ else // just invert
+ {
+ CGContextSetBlendMode(maContextHolder.get(), kCGBlendModeDifference);
+ CGContextSetRGBFillColor ( maContextHolder.get(),1.0, 1.0, 1.0 , 1.0 );
+ CGContextFillRect ( maContextHolder.get(), aCGRect );
+ }
+ maContextHolder.restoreState();
+ RefreshRect( aCGRect );
+ }
+}
+
+namespace {
+
+CGPoint* makeCGptArray(sal_uInt32 nPoints, const SalPoint* pPtAry)
+{
+ CGPoint *CGpoints = new CGPoint[nPoints];
+ for(sal_uLong i=0;i<nPoints;i++)
+ {
+ CGpoints[i].x = pPtAry[i].mnX;
+ CGpoints[i].y = pPtAry[i].mnY;
+ }
+ return CGpoints;
+}
+
+}
+
+void AquaSalGraphics::invert( sal_uInt32 nPoints, const SalPoint* pPtAry, SalInvert nSalFlags )
+{
+ if ( CheckContext() )
+ {
+ maContextHolder.saveState();
+ CGPoint* CGpoints = makeCGptArray(nPoints,pPtAry);
+ CGContextAddLines ( maContextHolder.get(), CGpoints, nPoints );
+ if ( nSalFlags & SalInvert::TrackFrame )
+ {
+ const CGFloat dashLengths[2] = { 4.0, 4.0 }; // for drawing dashed line
+ CGContextSetBlendMode( maContextHolder.get(), kCGBlendModeDifference );
+ CGContextSetRGBStrokeColor ( maContextHolder.get(), 1.0, 1.0, 1.0, 1.0 );
+ CGContextSetLineDash ( maContextHolder.get(), 0, dashLengths, 2 );
+ CGContextSetLineWidth( maContextHolder.get(), 2.0);
+ CGContextStrokePath ( maContextHolder.get() );
+ }
+ else if ( nSalFlags & SalInvert::N50 )
+ {
+ CGContextSetBlendMode(maContextHolder.get(), kCGBlendModeDifference);
+ Pattern50Fill();
+ }
+ else // just invert
+ {
+ CGContextSetBlendMode( maContextHolder.get(), kCGBlendModeDifference );
+ CGContextSetRGBFillColor( maContextHolder.get(), 1.0, 1.0, 1.0, 1.0 );
+ CGContextFillPath( maContextHolder.get() );
+ }
+ const CGRect aRefreshRect = CGContextGetClipBoundingBox(maContextHolder.get());
+ maContextHolder.restoreState();
+ delete [] CGpoints;
+ RefreshRect( aRefreshRect );
+ }
+}
+
+void AquaSalGraphics::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( !maContextHolder.get(), "vcl.quartz", "maContextHolder.get() is NULL" );
+ CGContextSetFillColorSpace( maContextHolder.get(), mxP50Space );
+ CGContextSetFillPattern( maContextHolder.get(), mxP50Pattern, aFillCol );
+ CGContextFillPath( maContextHolder.get() );
+}
+
+void AquaSalGraphics::ResetClipRegion()
+{
+ // release old path and indicate no clipping
+ if( mxClipPath )
+ {
+ CGPathRelease( mxClipPath );
+ mxClipPath = nullptr;
+ }
+ if( CheckContext() )
+ {
+ SetState();
+ }
+}
+
+void AquaSalGraphics::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 );
+ }
+}
+
+void AquaSalGraphics::SetLineColor()
+{
+ maLineColor.SetAlpha( 0.0 ); // transparent
+ if( CheckContext() )
+ {
+ CGContextSetRGBStrokeColor( maContextHolder.get(), maLineColor.GetRed(), maLineColor.GetGreen(),
+ maLineColor.GetBlue(), maLineColor.GetAlpha() );
+ }
+}
+
+void AquaSalGraphics::SetLineColor( Color nColor )
+{
+ maLineColor = RGBAColor( nColor );
+ if( CheckContext() )
+ {
+ CGContextSetRGBStrokeColor( maContextHolder.get(), maLineColor.GetRed(), maLineColor.GetGreen(),
+ maLineColor.GetBlue(), maLineColor.GetAlpha() );
+ }
+}
+
+void AquaSalGraphics::SetFillColor()
+{
+ maFillColor.SetAlpha( 0.0 ); // transparent
+ if( CheckContext() )
+ {
+ CGContextSetRGBFillColor( maContextHolder.get(), maFillColor.GetRed(), maFillColor.GetGreen(),
+ maFillColor.GetBlue(), maFillColor.GetAlpha() );
+ }
+}
+
+void AquaSalGraphics::SetFillColor( Color nColor )
+{
+ maFillColor = RGBAColor( nColor );
+ if( CheckContext() )
+ {
+ CGContextSetRGBFillColor( maContextHolder.get(), maFillColor.GetRed(), maFillColor.GetGreen(),
+ maFillColor.GetBlue(), maFillColor.GetAlpha() );
+ }
+}
+
+bool AquaSalGraphics::supportsOperation( OutDevSupportType eType ) const
+{
+ bool bRet = false;
+ switch( eType )
+ {
+ case OutDevSupportType::TransparentRect:
+ case OutDevSupportType::B2DDraw:
+ bRet = true;
+ break;
+ default:
+ break;
+ }
+ return bRet;
+}
+
+bool AquaSalGraphics::setClipRegion( const vcl::Region& i_rClip )
+{
+ // release old clip path
+ if( mxClipPath )
+ {
+ CGPathRelease( mxClipPath );
+ mxClipPath = nullptr;
+ }
+ mxClipPath = CGPathCreateMutable();
+
+ // set current path, either as polypolgon or sequence of rectangles
+ if(i_rClip.HasPolyPolygonOrB2DPolyPolygon())
+ {
+ const basegfx::B2DPolyPolygon aClip(i_rClip.GetAsB2DPolyPolygon());
+
+ AddPolyPolygonToPath( mxClipPath, aClip, !getAntiAliasB2DDraw(), false );
+ }
+ else
+ {
+ RectangleVector aRectangles;
+ i_rClip.GetRegionRectangles(aRectangles);
+
+ for(const auto& rRect : aRectangles)
+ {
+ const long nW(rRect.Right() - rRect.Left() + 1); // uses +1 logic in original
+
+ if(nW)
+ {
+ const 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( mxClipPath, nullptr, aRect );
+ }
+ }
+ }
+ }
+ // set the current path as clip region
+ if( CheckContext() )
+ {
+ SetState();
+ }
+ return true;
+}
+
+void AquaSalGraphics::SetROPFillColor( SalROPColor nROPColor )
+{
+ if( ! mbPrinter )
+ {
+ SetFillColor( ImplGetROPColor( nROPColor ) );
+ }
+}
+
+void AquaSalGraphics::SetROPLineColor( SalROPColor nROPColor )
+{
+ if( ! mbPrinter )
+ {
+ SetLineColor( ImplGetROPColor( nROPColor ) );
+ }
+}
+
+void AquaSalGraphics::SetXORMode( bool bSet, bool bInvertOnly )
+{
+ // return early if XOR mode remains unchanged
+ if( mbPrinter )
+ {
+ return;
+ }
+ if( ! bSet && mnXorMode == 2 )
+ {
+ CGContextSetBlendMode( maContextHolder.get(), kCGBlendModeNormal );
+ mnXorMode = 0;
+ return;
+ }
+ else if( bSet && bInvertOnly && mnXorMode == 0)
+ {
+ CGContextSetBlendMode( maContextHolder.get(), kCGBlendModeDifference );
+ mnXorMode = 2;
+ return;
+ }
+
+ if( (mpXorEmulation == nullptr) && !bSet )
+ {
+ return;
+ }
+ if( (mpXorEmulation != nullptr) && (bSet == mpXorEmulation->IsEnabled()) )
+ {
+ return;
+ }
+ if( !CheckContext() )
+ {
+ return;
+ }
+ // prepare XOR emulation
+ if( !mpXorEmulation )
+ {
+ mpXorEmulation = new XorEmulation();
+ mpXorEmulation->SetTarget(mnWidth, mnHeight, mnBitmapDepth, maContextHolder.get(), maLayer.get());
+ }
+
+ // change the XOR mode
+ if( bSet )
+ {
+ mpXorEmulation->Enable();
+ maContextHolder.set(mpXorEmulation->GetMaskContext());
+ mnXorMode = 1;
+ }
+ else
+ {
+ mpXorEmulation->UpdateTarget();
+ mpXorEmulation->Disable();
+ maContextHolder.set(mpXorEmulation->GetTargetContext());
+ mnXorMode = 0;
+ }
+}
+
+#ifndef IOS
+
+void AquaSalGraphics::updateResolution()
+{
+ SAL_WARN_IF( !mbWindow, "vcl", "updateResolution on inappropriate graphics" );
+
+ initResolution( (mbWindow && mpFrame) ? 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 );
+}
+
+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;
+}
+
+void AquaSalGraphics::SetVirDevGraphics(CGLayerHolder const & rLayer, CGContextRef xContext,
+ int nBitmapDepth)
+{
+ SAL_INFO( "vcl.quartz", "SetVirDevGraphics() this=" << this << " layer=" << rLayer.get() << " context=" << xContext );
+
+#ifndef IOS
+ mbWindow = false;
+#endif
+ mbPrinter = false;
+ mbVirDev = true;
+
+ // set graphics properties
+ maLayer = rLayer;
+ maContextHolder.set(xContext);
+
+ mnBitmapDepth = nBitmapDepth;
+
+#ifdef IOS
+ mbForeignContext = xContext != NULL;
+#endif
+
+ // return early if the virdev is being destroyed
+ if( !xContext )
+ return;
+
+ // get new graphics properties
+ if (!maLayer.isSet())
+ {
+ mnWidth = CGBitmapContextGetWidth( maContextHolder.get() );
+ mnHeight = CGBitmapContextGetHeight( maContextHolder.get() );
+ }
+ else
+ {
+ const CGSize aSize = CGLayerGetSize(maLayer.get());
+ mnWidth = static_cast<int>(aSize.width);
+ mnHeight = static_cast<int>(aSize.height);
+ }
+
+ // prepare graphics for drawing
+ const CGColorSpaceRef aCGColorSpace = GetSalData()->mxRGBSpace;
+ CGContextSetFillColorSpace( maContextHolder.get(), aCGColorSpace );
+ CGContextSetStrokeColorSpace( maContextHolder.get(), aCGColorSpace );
+
+ // re-enable XorEmulation for the new context
+ if( mpXorEmulation )
+ {
+ mpXorEmulation->SetTarget(mnWidth, mnHeight, mnBitmapDepth, maContextHolder.get(), maLayer.get());
+ if( mpXorEmulation->IsEnabled() )
+ {
+ maContextHolder.set(mpXorEmulation->GetMaskContext());
+ }
+ }
+
+ // initialize stack of CGContext states
+ maContextHolder.saveState();
+ SetState();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/quartz/salgdiutils.cxx b/vcl/quartz/salgdiutils.cxx
new file mode 100644
index 000000000..57953e536
--- /dev/null
+++ b/vcl/quartz/salgdiutils.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 <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>
+
+void AquaSalGraphics::SetWindowGraphics( AquaSalFrame* pFrame )
+{
+ mpFrame = pFrame;
+ mbWindow = true;
+ mbPrinter = false;
+ mbVirDev = false;
+}
+
+void AquaSalGraphics::SetPrinterGraphics( CGContextRef xContext, long nDPIX, long nDPIY )
+{
+ mbWindow = false;
+ mbPrinter = true;
+ mbVirDev = false;
+
+ maContextHolder.set(xContext);
+ mnRealDPIX = nDPIX;
+ mnRealDPIY = nDPIY;
+
+ // a previously set clip path is now invalid
+ if( mxClipPath )
+ {
+ CGPathRelease( mxClipPath );
+ mxClipPath = nullptr;
+ }
+
+ if (maContextHolder.isSet())
+ {
+ CGContextSetFillColorSpace( maContextHolder.get(), GetSalData()->mxRGBSpace );
+ CGContextSetStrokeColorSpace( maContextHolder.get(), GetSalData()->mxRGBSpace );
+ CGContextSaveGState( maContextHolder.get() );
+ SetState();
+ }
+}
+
+void AquaSalGraphics::InvalidateContext()
+{
+ UnsetState();
+
+ CGContextRelease(maContextHolder.get());
+ CGContextRelease(maBGContextHolder.get());
+ CGContextRelease(maCSContextHolder.get());
+
+ maContextHolder.set(nullptr);
+ maCSContextHolder.set(nullptr);
+ maBGContextHolder.set(nullptr);
+}
+
+void AquaSalGraphics::UnsetState()
+{
+ if (maBGContextHolder.isSet())
+ {
+ CGContextRelease(maBGContextHolder.get());
+ maBGContextHolder.set(nullptr);
+ }
+ if (maCSContextHolder.isSet())
+ {
+ CGContextRelease(maCSContextHolder.get());
+ maBGContextHolder.set(nullptr);
+ }
+ if (maContextHolder.isSet())
+ {
+ maContextHolder.restoreState();
+ maContextHolder.set(nullptr);
+ }
+ if( mxClipPath )
+ {
+ CGPathRelease( mxClipPath );
+ mxClipPath = nullptr;
+ }
+}
+
+/**
+ * (re-)create the off-screen maLayer we render everything to if
+ * necessary: eg. not initialized yet, or it has an incorrect size.
+ */
+bool AquaSalGraphics::CheckContext()
+{
+ if (mbWindow && mpFrame && (mpFrame->getNSWindow() || Application::IsBitmapRendering()))
+ {
+ const unsigned int nWidth = mpFrame->maGeometry.nWidth;
+ const unsigned int nHeight = mpFrame->maGeometry.nHeight;
+
+ // Let's get the window scaling factor if possible, or use 1.0
+ // as the scaling factor.
+ float fScale = 1.0f;
+ if (mpFrame->getNSWindow())
+ fScale = [mpFrame->getNSWindow() backingScaleFactor];
+
+ 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;
+ int nFlags = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host;
+ maBGContextHolder.set(CGBitmapContextCreate(
+ NULL, nScaledWidth, nScaledHeight, 8, nBytesPerRow, GetSalData()->mxRGBSpace, nFlags));
+
+ maLayer.set(CGLayerCreateWithContext(maBGContextHolder.get(), aLayerSize, nullptr));
+ maLayer.setScale(fScale);
+
+ nFlags = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
+ maCSContextHolder.set(CGBitmapContextCreate(
+ NULL, 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();
+}
+
+CGContextRef AquaSalGraphics::GetContext()
+{
+ if (!maContextHolder.isSet())
+ {
+ CheckContext();
+ }
+ return maContextHolder.get();
+}
+
+/**
+ * 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( !mpFrame )
+ {
+ return;
+ }
+
+ NSGraphicsContext* pContext = [NSGraphicsContext currentContext];
+ if (maLayer.isSet() && pContext != nullptr)
+ {
+ CGContextHolder rCGContextHolder([pContext CGContext]);
+
+ rCGContextHolder.saveState();
+
+ CGMutablePathRef rClip = mpFrame->getClipPath();
+ if (rClip)
+ {
+ CGContextBeginPath(rCGContextHolder.get());
+ CGContextAddPath(rCGContextHolder.get(), rClip );
+ CGContextClip(rCGContextHolder.get());
+ }
+
+ ApplyXorContext();
+
+ const CGSize aSize = maLayer.getSizePoints();
+ const CGRect aRect = CGRectMake(0, 0, aSize.width, aSize.height);
+ const CGRect aRectPoints = { CGPointZero, maLayer.getSizePixels() };
+ CGContextSetBlendMode(maCSContextHolder.get(), kCGBlendModeCopy);
+ CGContextDrawLayerInRect(maCSContextHolder.get(), aRectPoints, maLayer.get());
+
+ CGImageRef img = CGBitmapContextCreateImage(maCSContextHolder.get());
+ CGImageRef displayColorSpaceImage = CGImageCreateCopyWithColorSpace(img, [[mpFrame->getNSWindow() colorSpace] CGColorSpace]);
+ CGContextSetBlendMode(rCGContextHolder.get(), kCGBlendModeCopy);
+ CGContextDrawImage(rCGContextHolder.get(), aRect, displayColorSpaceImage);
+
+ CGImageRelease(img);
+ CGImageRelease(displayColorSpaceImage);
+
+ rCGContextHolder.restoreState();
+ }
+ else
+ {
+ SAL_WARN_IF( !mpFrame->mbInitShow, "vcl", "UpdateWindow called on uneligible graphics" );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/quartz/salvd.cxx b/vcl/quartz/salvd.cxx
new file mode 100644
index 000000000..c06ba33c5
--- /dev/null
+++ b/vcl/quartz/salvd.cxx
@@ -0,0 +1,303 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 "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* pGraphics,
+ long &nDX, 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* >( pGraphics ),
+ 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* >( pGraphics ),
+ nDX, nDY, eFormat, pData ));
+#endif
+}
+
+AquaSalVirtualDevice::AquaSalVirtualDevice( AquaSalGraphics* pGraphic, long &nDX, 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<long>(aSize.width);
+ nDY = static_cast<long>(aSize.height);
+ }
+ else
+ {
+ nDX = 0;
+ nDY = 0;
+ }
+
+ mpGraphics->SetVirDevGraphics(maLayer, pData->rCGContext);
+ }
+ else
+ {
+ // create empty new virtual device
+ mbForeignContext = false; // the mxContext is created within VCL
+ mpGraphics = new AquaSalGraphics(); // never fails
+ switch (eFormat)
+ {
+ case DeviceFormat::BITMASK:
+ mnBitmapDepth = 1;
+ break;
+#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( nullptr, nullptr );
+ delete mpGraphics;
+ mpGraphics = nullptr;
+ }
+ Destroy();
+}
+
+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(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);
+ }
+}
+
+SalGraphics* AquaSalVirtualDevice::AcquireGraphics()
+{
+ if( mbGraphicsUsed || !mpGraphics )
+ {
+ return nullptr;
+ }
+ mbGraphicsUsed = true;
+ return mpGraphics;
+}
+
+void AquaSalVirtualDevice::ReleaseGraphics( SalGraphics* )
+{
+ mbGraphicsUsed = false;
+}
+
+bool AquaSalVirtualDevice::SetSize( long nDX, 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 Quartz layer 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
+ {
+#ifdef MACOSX
+ // default to a NSView target context
+ AquaSalFrame* pSalFrame = mpGraphics->getGraphicsFrame();
+ if( !pSalFrame || !AquaSalFrame::isAlive( pSalFrame ))
+ {
+ pSalFrame = static_cast<AquaSalFrame*>( GetSalData()->mpInstance->anyFrame() );
+ if ( pSalFrame )
+ // update the frame reference
+ mpGraphics->setGraphicsFrame( pSalFrame );
+ }
+ if( pSalFrame )
+ {
+ // #i91990#
+ NSWindow* pNSWindow = pSalFrame->getNSWindow();
+ if ( pNSWindow )
+ {
+ NSGraphicsContext* pNSContext = [NSGraphicsContext graphicsContextWithWindow: pNSWindow];
+ if( pNSContext )
+ {
+ xCGContextHolder.set([pNSContext CGContext]);
+ }
+ }
+ }
+#endif
+
+ if (!xCGContextHolder.isSet())
+ {
+ // assert(Application::IsBitmapRendering());
+ mnBitmapDepth = 32;
+
+ const int nBytesPerRow = (mnBitmapDepth * nDX) / 8;
+ void* pRawData = std::malloc( nBytesPerRow * nDY );
+#ifdef MACOSX
+ const int nFlags = kCGImageAlphaNoneSkipFirst;
+#else
+ const int nFlags = kCGImageAlphaNoneSkipFirst | kCGImageByteOrder32Little;
+#endif
+ 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() );
+ mpGraphics->SetVirDevGraphics(maLayer.get(), xDrawContext, mnBitmapDepth);
+ }
+
+ return maLayer.isSet();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/quartz/utils.cxx b/vcl/quartz/utils.cxx
new file mode 100644
index 000000000..0e0ac8f4d
--- /dev/null
+++ b/vcl/quartz/utils.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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()];
+}
+
+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;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/skia/README b/vcl/skia/README
new file mode 100644
index 000000000..f1248d90c
--- /dev/null
+++ b/vcl/skia/README
@@ -0,0 +1,35 @@
+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 debugging)
+- Vulkan - Vulkan-based GPU drawing, this is the default
+
+There are more (OpenGL, Metal on Mac, etc.), but (as of now) they are not supported by VCL.
+
+GrContext 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
+GrContext. There is sk_app::WindowContext::getGrContext(), but each instance creates
+its own GrContext, 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 GrContext object
+and share it between instances. Additionally, using sk_app::WindowContext::SharedGrContext
+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 000000000..343ebc284
--- /dev/null
+++ b/vcl/skia/SkiaHelper.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/.
+ */
+
+#include <vcl/skia/SkiaHelper.hxx>
+
+#if !HAVE_FEATURE_SKIA
+
+namespace SkiaHelper
+{
+bool isVCLSkiaEnabled() { 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 <SkCanvas.h>
+#include <SkPaint.h>
+#include <SkSurface.h>
+#include <SkGraphics.h>
+#include <skia_compiler.hxx>
+
+#ifdef DBG_UTIL
+#include <fstream>
+#endif
+
+namespace SkiaHelper
+{
+static OUString getBlacklistFile()
+{
+ OUString url("$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER);
+ rtl::Bootstrap::expandMacros(url);
+
+ return url + "/skia/skia_blacklist_vulkan.xml";
+}
+
+static uint32_t driverVersion = 0;
+uint32_t vendorId = 0;
+
+static OUString versionAsString(uint32_t version)
+{
+ return OUString::number(version >> 22) + "." + OUString::number((version >> 12) & 0x3ff) + "."
+ + OUString::number(version & 0xfff);
+}
+
+static OUStringLiteral vendorAsString(uint32_t vendor)
+{
+ return DriverBlocklist::GetVendorNameFromId(vendor);
+}
+
+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.WriteCharPtr(key);
+ stream.WriteCharPtr(": ");
+ stream.WriteCharPtr(value);
+ stream.WriteChar('\n');
+}
+
+static void writeToLog(SvStream& stream, const char* key, const OUString& value)
+{
+ writeToLog(stream, key, OUStringToOString(value, RTL_TEXTENCODING_UTF8).getStr());
+}
+
+// Note that this function also logs system information about Vulkan.
+static bool isVulkanBlacklisted(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 blacklisted
+ = DriverBlocklist::IsDeviceBlocked(getBlacklistFile(), DriverBlocklist::VersionType::Vulkan,
+ driverVersionString, vendorIdStr, deviceIdStr);
+ writeToLog(logFile, "Blacklisted", blacklisted ? "yes" : "no");
+ return blacklisted;
+}
+
+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());
+}
+
+static sk_app::VulkanWindowContext::SharedGrContext getTemporaryGrContext();
+
+static void checkDeviceBlacklisted(bool blockDisable = false)
+{
+ static bool done = false;
+ if (!done)
+ {
+ SkiaZone zone;
+
+ switch (renderMethodToUse())
+ {
+ case RenderVulkan:
+ {
+ // First try if a GrContext already exists.
+ sk_app::VulkanWindowContext::SharedGrContext grContext
+ = sk_app::VulkanWindowContext::getSharedGrContext();
+ if (!grContext.getGrContext())
+ {
+ // 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 GrContext.
+ // Avoid the loop by creating a temporary GrContext
+ // 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.
+ grContext = getTemporaryGrContext();
+ }
+ bool blacklisted = true; // assume the worst
+ if (grContext.getGrContext()) // Vulkan was initialized properly
+ {
+ blacklisted = isVulkanBlacklisted(
+ sk_app::VulkanWindowContext::getPhysDeviceProperties());
+ SAL_INFO("vcl.skia", "Vulkan blacklisted: " << blacklisted);
+ }
+ else
+ SAL_INFO("vcl.skia", "Vulkan could not be initialized");
+ if (blacklisted && !blockDisable)
+ {
+ disableRenderMethod(RenderVulkan);
+ writeSkiaRasterInfo();
+ }
+ break;
+ }
+ case RenderRaster:
+ SAL_INFO("vcl.skia", "Using Skia raster mode");
+ writeSkiaRasterInfo();
+ return; // software, never blacklisted
+ }
+ 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;
+}
+
+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;
+
+ // 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 blacklisted
+ * * 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;
+ SkGraphics::Init();
+ // don't actually block if blacklisted, but log it if enabled, and also get the vendor id
+ checkDeviceBlacklisted(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)
+ {
+ SkGraphics::Init();
+ checkDeviceBlacklisted(); // switch to raster if driver is blacklisted
+ }
+
+ bRet = bEnable;
+ }
+
+ if (bRet)
+ WatchdogThread::start();
+
+ CrashReporter::addKeyValue("UseSkia", OUString::boolean(bRet), CrashReporter::Write);
+
+ return bRet;
+}
+
+static RenderMethod methodToUse = RenderRaster;
+
+static bool initRenderMethodToUse()
+{
+ if (const char* env = getenv("SAL_SKIA"))
+ {
+ if (strcmp(env, "raster") == 0)
+ {
+ methodToUse = RenderRaster;
+ return true;
+ }
+ if (strcmp(env, "vulkan") == 0)
+ {
+ methodToUse = RenderVulkan;
+ return true;
+ }
+ SAL_WARN("vcl.skia", "Unrecognized value of SAL_SKIA");
+ abort();
+ }
+ if (officecfg::Office::Common::VCL::ForceSkiaRaster::get())
+ {
+ methodToUse = RenderRaster;
+ return true;
+ }
+ methodToUse = RenderVulkan;
+ 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;
+}
+
+static sk_app::VulkanWindowContext::SharedGrContext* sharedGrContext;
+
+static std::unique_ptr<sk_app::WindowContext> (*createVulkanWindowContextFunction)(bool) = nullptr;
+static void setCreateVulkanWindowContext(std::unique_ptr<sk_app::WindowContext> (*function)(bool))
+{
+ createVulkanWindowContextFunction = function;
+}
+
+GrContext* getSharedGrContext()
+{
+ SkiaZone zone;
+ assert(renderMethodToUse() == RenderVulkan);
+ if (sharedGrContext)
+ return sharedGrContext->getGrContext();
+ // TODO mutex?
+ // Set up the shared GrContext from Skia's (patched) VulkanWindowContext, if it's been
+ // already set up.
+ sk_app::VulkanWindowContext::SharedGrContext context
+ = sk_app::VulkanWindowContext::getSharedGrContext();
+ GrContext* grContext = context.getGrContext();
+ if (grContext)
+ {
+ sharedGrContext = new sk_app::VulkanWindowContext::SharedGrContext(context);
+ return grContext;
+ }
+ static bool done = false;
+ if (done)
+ return nullptr;
+ done = true;
+ if (createVulkanWindowContextFunction == nullptr)
+ return nullptr; // not initialized properly (e.g. used from a VCL backend with no Skia support)
+ std::unique_ptr<sk_app::WindowContext> tmpContext = createVulkanWindowContextFunction(false);
+ // Set up using the shared context created by the call above, if successful.
+ context = sk_app::VulkanWindowContext::getSharedGrContext();
+ grContext = context.getGrContext();
+ if (grContext)
+ {
+ sharedGrContext = new sk_app::VulkanWindowContext::SharedGrContext(context);
+ return grContext;
+ }
+ disableRenderMethod(RenderVulkan);
+ return nullptr;
+}
+
+static sk_app::VulkanWindowContext::SharedGrContext getTemporaryGrContext()
+{
+ if (createVulkanWindowContextFunction == nullptr)
+ return sk_app::VulkanWindowContext::SharedGrContext();
+ std::unique_ptr<sk_app::WindowContext> tmpContext = createVulkanWindowContextFunction(true);
+ // Set up using the shared context created by the call above, if successful.
+ return sk_app::VulkanWindowContext::getSharedGrContext();
+}
+
+sk_sp<SkSurface> createSkSurface(int width, int height, SkColorType type)
+{
+ SkiaZone zone;
+ assert(type == kN32_SkColorType || type == kAlpha_8_SkColorType);
+ sk_sp<SkSurface> surface;
+ switch (SkiaHelper::renderMethodToUse())
+ {
+ case SkiaHelper::RenderVulkan:
+ {
+ if (GrContext* grContext = getSharedGrContext())
+ {
+ surface = SkSurface::MakeRenderTarget(
+ grContext, SkBudgeted::kNo,
+ SkImageInfo::Make(width, height, type, kPremul_SkAlphaType));
+ if (surface)
+ {
+#ifdef DBG_UTIL
+ prefillSurface(surface);
+#endif
+ return surface;
+ }
+ SAL_WARN("vcl.skia",
+ "cannot create Vulkan GPU offscreen surface, falling back to Raster");
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ // Create raster surface as a fallback.
+ surface = SkSurface::MakeRaster(SkImageInfo::Make(width, height, type, kPremul_SkAlphaType));
+ 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 (SkiaHelper::renderMethodToUse())
+ {
+ case SkiaHelper::RenderVulkan:
+ {
+ if (GrContext* grContext = getSharedGrContext())
+ {
+ sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(
+ grContext, SkBudgeted::kNo, bitmap.info().makeAlphaType(kPremul_SkAlphaType));
+ if (surface)
+ {
+ SkPaint paint;
+ paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha
+ surface->getCanvas()->drawBitmap(bitmap, 0, 0, &paint);
+ return makeCheckedImageSnapshot(surface);
+ }
+ // Try to fall back in non-debug builds.
+ SAL_WARN("vcl.skia",
+ "cannot create Vulkan GPU offscreen surface, falling back to Raster");
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ // Create raster image as a fallback.
+ sk_sp<SkImage> image = SkImage::MakeFromBitmap(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;
+ int 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_cache would be simpler, but it doesn't support
+// calculating cost of each item.
+static std::list<ImageCacheItem>* imageCache = nullptr;
+static int 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;
+ if (imageCache == nullptr)
+ imageCache = new std::list<ImageCacheItem>;
+ int size = 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 int MAX_CACHE_SIZE = 4 * 1000 * 1000 * 4; // 4x 1000px 32bpp images, 16MiB
+ while (imageCacheSize > MAX_CACHE_SIZE)
+ {
+ assert(!imageCache->empty());
+ imageCacheSize -= imageCache->back().size;
+ SAL_INFO("vcl.skia.trace",
+ "least used removal " << image << ":" << imageCache->back().size);
+ imageCache->pop_back();
+ }
+}
+
+sk_sp<SkImage> findCachedImage(const OString& key)
+{
+ if (imageCache != nullptr)
+ {
+ 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)
+{
+ if (imageCache == nullptr)
+ return;
+ for (auto it = imageCache->begin(); it != imageCache->end();)
+ {
+ if (it->image == image)
+ {
+ imageCacheSize -= it->size;
+ assert(imageCacheSize >= 0);
+ it = imageCache->erase(it);
+ }
+ else
+ ++it;
+ }
+}
+
+void cleanup()
+{
+ delete sharedGrContext;
+ sharedGrContext = nullptr;
+ delete imageCache;
+ imageCache = nullptr;
+ imageCacheSize = 0;
+}
+
+// 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 context that is
+// usually available only in the backend libs.
+void prepareSkia(std::unique_ptr<sk_app::WindowContext> (*createVulkanWindowContext)(bool))
+{
+ setCreateVulkanWindowContext(createVulkanWindowContext);
+ skiaSupportedByBackend = true;
+}
+
+#ifdef DBG_UTIL
+void prefillSurface(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));
+ surface->getCanvas()->drawPaint(paint);
+}
+
+void dump(const SkBitmap& bitmap, const char* file) { dump(SkImage::MakeFromBitmap(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)
+{
+ sk_sp<SkData> data = image->encodeToData();
+ std::ofstream ostream(file, std::ios::binary);
+ ostream.write(static_cast<const char*>(data->data()), data->size());
+}
+
+#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 000000000..bd497d7f8
--- /dev/null
+++ b/vcl/skia/gdiimpl.cxx
@@ -0,0 +1,1829 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/skia/SkiaHelper.hxx>
+#include <skia/utils.hxx>
+#include <skia/zone.hxx>
+
+#include <SkCanvas.h>
+#include <SkPath.h>
+#include <SkRegion.h>
+#include <SkDashPathEffect.h>
+#include <GrBackendSurface.h>
+#include <SkTextBlob.h>
+#include <SkRSXform.h>
+
+#include <numeric>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
+#include <o3tl/sorted_vector.hxx>
+
+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,
+ bool* hasOnlyOrthogonal = nullptr)
+{
+ const sal_uInt32 nPointCount(rPolygon.count());
+
+ if (nPointCount <= 1)
+ return;
+
+ const bool bClosePath(rPolygon.isClosed());
+ const bool bHasCurves(rPolygon.areControlPointsUsed());
+
+ bool bFirst = true;
+
+ 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;
+ 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))
+ {
+ 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)
+ {
+ rPath.close();
+ }
+}
+
+void addPolyPolygonToPath(const basegfx::B2DPolyPolygon& rPolyPolygon, SkPath& rPath,
+ bool* hasOnlyOrthogonal = nullptr)
+{
+ const sal_uInt32 nPolygonCount(rPolyPolygon.count());
+
+ if (nPolygonCount == 0)
+ return;
+
+ 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
+}
+
+SkColor toSkColor(Color color)
+{
+ return SkColorSetARGB(255 - color.GetTransparency(), color.GetRed(), color.GetGreen(),
+ color.GetBlue());
+}
+
+SkColor toSkColorWithTransparency(Color aColor, double fTransparency)
+{
+ return SkColorSetA(toSkColor(aColor), 255 * (1.0 - fTransparency));
+}
+
+Color fromSkColor(SkColor color)
+{
+ return Color(255 - SkColorGetA(color), SkColorGetR(color), SkColorGetG(color),
+ SkColorGetB(color));
+}
+
+// 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;
+}
+
+} // 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); }
+ const char* get_debug_name(SkiaSalGraphicsImpl* pGraphics)
+ {
+ // 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
+ const char* get_debug_name(SkiaSalGraphicsImpl*) { return "skia idle"; }
+#endif
+
+ virtual void Invoke() override
+ {
+ mpGraphics->performFlush();
+ Stop();
+ SetPriority(TaskPriority::HIGHEST);
+ }
+};
+
+SkiaSalGraphicsImpl::SkiaSalGraphicsImpl(SalGraphics& rParent, SalGeometryProvider* pProvider)
+ : mParent(rParent)
+ , mProvider(pProvider)
+ , mIsGPU(false)
+ , mLineColor(SALCOLOR_NONE)
+ , mFillColor(SALCOLOR_NONE)
+ , mXorMode(false)
+ , mFlush(new SkiaFlushIdle(this))
+{
+}
+
+SkiaSalGraphicsImpl::~SkiaSalGraphicsImpl()
+{
+ assert(!mSurface);
+ assert(!mWindowContext);
+}
+
+void SkiaSalGraphicsImpl::Init() {}
+
+void SkiaSalGraphicsImpl::createSurface()
+{
+ SkiaZone zone;
+ if (isOffscreen())
+ createOffscreenSurface();
+ else
+ createWindowSurface();
+ mSurface->getCanvas()->save(); // see SetClipRegion()
+ mClipRegion = vcl::Region(tools::Rectangle(0, 0, GetWidth(), GetHeight()));
+
+ // 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);
+ assert(!mWindowContext);
+ createWindowContext(forceRaster);
+ if (mWindowContext)
+ mSurface = mWindowContext->getBackbufferSurface();
+ if (!mSurface)
+ {
+ switch (SkiaHelper::renderMethodToUse())
+ {
+ case SkiaHelper::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 SkiaHelper::RenderRaster:
+ abort(); // This should not really happen, do not even try to cope with it.
+ }
+ }
+ mIsGPU = mSurface->getCanvas()->getGrContext() != nullptr;
+#ifdef DBG_UTIL
+ SkiaHelper::prefillSurface(mSurface);
+#endif
+}
+
+void SkiaSalGraphicsImpl::createOffscreenSurface()
+{
+ SkiaZone zone;
+ assert(isOffscreen());
+ assert(!mSurface);
+ assert(!mWindowContext);
+ // When created (especially on Windows), Init() gets called with size (0,0), which is invalid size
+ // for Skia. May happen also in rare cases such as shutting down (tdf#131939).
+ int width = std::max(1, GetWidth());
+ int height = std::max(1, GetHeight());
+ switch (SkiaHelper::renderMethodToUse())
+ {
+ case SkiaHelper::RenderVulkan:
+ {
+ if (SkiaHelper::getSharedGrContext())
+ {
+ mSurface = SkiaHelper::createSkSurface(width, height);
+ if (mSurface)
+ {
+ mIsGPU = mSurface->getCanvas()->getGrContext() != nullptr;
+ return;
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ // Create raster surface as a fallback.
+ mSurface = SkiaHelper::createSkSurface(width, height);
+ assert(mSurface);
+ assert(!mSurface->getCanvas()->getGrContext()); // is not GPU-backed
+ mIsGPU = false;
+}
+
+void SkiaSalGraphicsImpl::destroySurface()
+{
+ SkiaZone zone;
+ if (mSurface)
+ {
+ // check setClipRegion() invariant
+ assert(mSurface->getCanvas()->getSaveCount() == 2);
+ // if this fails, something forgot to use SkAutoCanvasRestore
+ assert(mSurface->getCanvas()->getTotalMatrix().isIdentity());
+ }
+ // If we use e.g. Vulkan, we must destroy the surface before the context,
+ // otherwise destroying the surface will reference the context. This is
+ // handled by calling destroySurface() before destroying the context.
+ // However we also need to flush the surface before destroying it,
+ // otherwise when destroying the context later there still could be queued
+ // commands referring to the surface data. This is probably a Skia bug,
+ // but work around it here.
+ if (mSurface)
+ mSurface->flushAndSubmit();
+ mSurface.reset();
+ mWindowContext.reset();
+ mIsGPU = false;
+}
+
+void SkiaSalGraphicsImpl::DeInit() { destroySurface(); }
+
+void SkiaSalGraphicsImpl::preDraw()
+{
+ assert(comphelper::SolarMutex::get()->IsCurrentThread());
+ SkiaZone::enter(); // matched in postDraw()
+ checkSurface();
+ checkPendingDrawing();
+}
+
+void SkiaSalGraphicsImpl::postDraw()
+{
+ scheduleFlush();
+ SkiaZone::leave(); // matched in preDraw()
+ // If there's a problem with the GPU context, abort.
+ if (GrContext* context = mSurface->getCanvas()->getGrContext())
+ {
+ // 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 (GetWidth() != mSurface->width() || GetHeight() != mSurface->height())
+ {
+ if (avoidRecreateByResize())
+ return;
+
+ if (!GetWidth() || !GetHeight())
+ {
+ SAL_WARN("vcl.skia", "recreate(" << this << "): can't create empty surface "
+ << Size(GetWidth(), GetHeight())
+ << " => keeping old one!");
+ return;
+ }
+
+ {
+ 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 = SkiaHelper::makeCheckedImageSnapshot(mSurface);
+ }
+
+ destroySurface();
+ createSurface();
+
+ if (snapshot)
+ {
+ SkPaint paint;
+ paint.setBlendMode(SkBlendMode::kSrc); // copy as is
+ mSurface->getCanvas()->drawImage(snapshot, 0, 0, &paint);
+ }
+ SAL_INFO("vcl.skia.trace", "recreate(" << this << "): old " << oldSize << " new "
+ << Size(mSurface->width(), mSurface->height())
+ << " requested "
+ << Size(GetWidth(), GetHeight()));
+ }
+ }
+}
+
+void SkiaSalGraphicsImpl::flushDrawing()
+{
+ if (!mSurface)
+ return;
+ checkPendingDrawing();
+ if (mXorMode)
+ applyXor();
+ mSurface->flushAndSubmit();
+}
+
+bool SkiaSalGraphicsImpl::setClipRegion(const vcl::Region& region)
+{
+ if (mClipRegion == region)
+ return true;
+ SkiaZone zone;
+ checkPendingDrawing();
+ checkSurface();
+ mClipRegion = region;
+ SAL_INFO("vcl.skia.trace", "setclipregion(" << this << "): " << region);
+ SkCanvas* canvas = mSurface->getCanvas();
+ // 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.
+ assert(canvas->getSaveCount() == 2); // = there is just one save()
+ canvas->restore();
+ canvas->save();
+ setCanvasClipRegion(canvas, region);
+ return true;
+}
+
+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);
+ 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; }
+
+long SkiaSalGraphicsImpl::GetGraphicsWidth() const { return GetWidth(); }
+
+void SkiaSalGraphicsImpl::SetLineColor()
+{
+ checkPendingDrawing();
+ mLineColor = SALCOLOR_NONE;
+}
+
+void SkiaSalGraphicsImpl::SetLineColor(Color nColor)
+{
+ checkPendingDrawing();
+ mLineColor = nColor;
+}
+
+void SkiaSalGraphicsImpl::SetFillColor()
+{
+ checkPendingDrawing();
+ mFillColor = SALCOLOR_NONE;
+}
+
+void SkiaSalGraphicsImpl::SetFillColor(Color nColor)
+{
+ checkPendingDrawing();
+ mFillColor = nColor;
+}
+
+void SkiaSalGraphicsImpl::SetXORMode(bool set, bool)
+{
+ if (mXorMode == set)
+ return;
+ checkPendingDrawing();
+ SAL_INFO("vcl.skia.trace", "setxormode(" << this << "): " << set);
+ if (set)
+ mXorRegion.setEmpty();
+ else
+ applyXor();
+ mXorMode = set;
+}
+
+SkCanvas* SkiaSalGraphicsImpl::getXorCanvas()
+{
+ SkiaZone zone;
+ assert(mXorMode);
+ // Skia does not implement xor drawing, so we need to handle it manually by redirecting
+ // to a temporary SkBitmap and then doing the xor operation on the data ourselves.
+ // There's no point in using SkSurface for GPU, we'd immediately need to get the pixels back.
+ if (!mXorCanvas)
+ {
+ // Use unpremultiplied alpha (see xor applying in applyXor()).
+ if (!mXorBitmap.tryAllocPixels(mSurface->imageInfo().makeAlphaType(kUnpremul_SkAlphaType)))
+ abort();
+ mXorBitmap.eraseARGB(0, 0, 0, 0);
+ mXorCanvas = std::make_unique<SkCanvas>(mXorBitmap);
+ setCanvasClipRegion(mXorCanvas.get(), mClipRegion);
+ }
+ return mXorCanvas.get();
+}
+
+void SkiaSalGraphicsImpl::applyXor()
+{
+ // Apply the result from the temporary bitmap manually. This is indeed
+ // slow, but it doesn't seem to be needed often and is optimized
+ // in each operation by extending mXorRegion with the area that should be
+ // updated.
+ assert(mXorMode);
+ if (!mSurface || !mXorCanvas
+ || !mXorRegion.op(SkIRect::MakeXYWH(0, 0, mSurface->width(), mSurface->height()),
+ SkRegion::kIntersect_Op))
+ {
+ mXorRegion.setEmpty();
+ return;
+ }
+ SAL_INFO("vcl.skia.trace", "applyxor(" << this << "): " << mXorRegion);
+ // Copy the surface contents to another pixmap.
+ SkBitmap surfaceBitmap;
+ // Use unpremultiplied alpha format, so that we do not have to do the conversions to get
+ // the RGB and back (Skia will do it when converting, but it'll be presumably faster at it).
+ if (!surfaceBitmap.tryAllocPixels(mSurface->imageInfo().makeAlphaType(kUnpremul_SkAlphaType)))
+ abort();
+ SkPaint paint;
+ paint.setBlendMode(SkBlendMode::kSrc); // copy as is
+ SkCanvas canvas(surfaceBitmap);
+ canvas.drawImageRect(SkiaHelper::makeCheckedImageSnapshot(mSurface), mXorRegion.getBounds(),
+ SkRect::Make(mXorRegion.getBounds()), &paint);
+ // xor to surfaceBitmap
+ assert(surfaceBitmap.info().alphaType() == kUnpremul_SkAlphaType);
+ assert(mXorBitmap.info().alphaType() == kUnpremul_SkAlphaType);
+ assert(surfaceBitmap.bytesPerPixel() == 4);
+ assert(mXorBitmap.bytesPerPixel() == 4);
+ for (SkRegion::Iterator it(mXorRegion); !it.done(); it.next())
+ {
+ for (int y = it.rect().top(); y < it.rect().bottom(); ++y)
+ {
+ uint8_t* data = static_cast<uint8_t*>(surfaceBitmap.getAddr(it.rect().x(), y));
+ const uint8_t* xordata = static_cast<uint8_t*>(mXorBitmap.getAddr(it.rect().x(), y));
+ for (int x = 0; x < it.rect().width(); ++x)
+ {
+ *data++ ^= *xordata++;
+ *data++ ^= *xordata++;
+ *data++ ^= *xordata++;
+ // alpha is not xor-ed
+ data++;
+ xordata++;
+ }
+ }
+ }
+ surfaceBitmap.notifyPixelsChanged();
+ mSurface->getCanvas()->drawBitmapRect(surfaceBitmap, mXorRegion.getBounds(),
+ SkRect::Make(mXorRegion.getBounds()), &paint);
+ mXorCanvas.reset();
+ mXorBitmap.reset();
+ mXorRegion.setEmpty();
+}
+
+void SkiaSalGraphicsImpl::SetROPLineColor(SalROPColor nROPColor)
+{
+ checkPendingDrawing();
+ switch (nROPColor)
+ {
+ case SalROPColor::N0:
+ mLineColor = Color(0, 0, 0);
+ break;
+ case SalROPColor::N1:
+ mLineColor = Color(0xff, 0xff, 0xff);
+ break;
+ case SalROPColor::Invert:
+ mLineColor = Color(0xff, 0xff, 0xff);
+ break;
+ }
+}
+
+void SkiaSalGraphicsImpl::SetROPFillColor(SalROPColor nROPColor)
+{
+ checkPendingDrawing();
+ switch (nROPColor)
+ {
+ case SalROPColor::N0:
+ mFillColor = Color(0, 0, 0);
+ break;
+ case SalROPColor::N1:
+ mFillColor = Color(0xff, 0xff, 0xff);
+ break;
+ case SalROPColor::Invert:
+ mFillColor = Color(0xff, 0xff, 0xff);
+ break;
+ }
+}
+
+void SkiaSalGraphicsImpl::drawPixel(long nX, long nY) { drawPixel(nX, nY, mLineColor); }
+
+void SkiaSalGraphicsImpl::drawPixel(long nX, long nY, Color nColor)
+{
+ if (nColor == SALCOLOR_NONE)
+ return;
+ preDraw();
+ SAL_INFO("vcl.skia.trace", "drawpixel(" << this << "): " << Point(nX, nY) << ":" << nColor);
+ addXorRegion(SkRect::MakeXYWH(nX, nY, 1, 1));
+ SkPaint paint;
+ paint.setColor(toSkColor(nColor));
+ // Apparently drawPixel() is actually expected to set the pixel and not draw it.
+ paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha
+ getDrawCanvas()->drawPoint(toSkX(nX), toSkY(nY), paint);
+ postDraw();
+}
+
+void SkiaSalGraphicsImpl::drawLine(long nX1, long nY1, long nX2, long nY2)
+{
+ if (mLineColor == SALCOLOR_NONE)
+ return;
+ preDraw();
+ SAL_INFO("vcl.skia.trace", "drawline(" << this << "): " << Point(nX1, nY1) << "->"
+ << Point(nX2, nY2) << ":" << mLineColor);
+ addXorRegion(SkRect::MakeLTRB(nX1, nY1, nX2, nY2).makeSorted());
+ SkPaint paint;
+ paint.setColor(toSkColor(mLineColor));
+ paint.setAntiAlias(mParent.getAntiAliasB2DDraw());
+ getDrawCanvas()->drawLine(toSkX(nX1), toSkY(nY1), toSkX(nX2), toSkY(nY2), paint);
+ postDraw();
+}
+
+void SkiaSalGraphicsImpl::privateDrawAlphaRect(long nX, long nY, long nWidth, long nHeight,
+ double fTransparency, bool blockAA)
+{
+ preDraw();
+ SAL_INFO("vcl.skia.trace",
+ "privatedrawrect(" << this << "): " << SkIRect::MakeXYWH(nX, nY, nWidth, nHeight)
+ << ":" << mLineColor << ":" << mFillColor << ":" << fTransparency);
+ addXorRegion(SkRect::MakeXYWH(nX, nY, nWidth, nHeight));
+ SkCanvas* canvas = getDrawCanvas();
+ SkPaint paint;
+ paint.setAntiAlias(!blockAA && mParent.getAntiAliasB2DDraw());
+ if (mFillColor != SALCOLOR_NONE)
+ {
+ paint.setColor(toSkColorWithTransparency(mFillColor, fTransparency));
+ paint.setStyle(SkPaint::kFill_Style);
+ // 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 (mLineColor == SALCOLOR_NONE && SkSize::Make(nWidth, nHeight).isEmpty())
+ paint.setStyle(SkPaint::kStroke_Style);
+ canvas->drawIRect(SkIRect::MakeXYWH(nX, nY, nWidth, nHeight), paint);
+ }
+ if (mLineColor != SALCOLOR_NONE)
+ {
+ paint.setColor(toSkColorWithTransparency(mLineColor, fTransparency));
+ paint.setStyle(SkPaint::kStroke_Style);
+ // 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->drawIRect(
+ SkIRect::MakeXYWH(nX, nY, std::max(1L, nWidth - 1), std::max(1L, nHeight - 1)), paint);
+ }
+ postDraw();
+}
+
+void SkiaSalGraphicsImpl::drawRect(long nX, long nY, long nWidth, long nHeight)
+{
+ privateDrawAlphaRect(nX, nY, nWidth, nHeight, 0.0, true);
+}
+
+void SkiaSalGraphicsImpl::drawPolyLine(sal_uInt32 nPoints, const SalPoint* pPtAry)
+{
+ basegfx::B2DPolygon aPolygon;
+ aPolygon.append(basegfx::B2DPoint(pPtAry->mnX, pPtAry->mnY), nPoints);
+ for (sal_uInt32 i = 1; i < nPoints; ++i)
+ aPolygon.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].mnX, pPtAry[i].mnY));
+ 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 SalPoint* pPtAry)
+{
+ basegfx::B2DPolygon aPolygon;
+ aPolygon.append(basegfx::B2DPoint(pPtAry->mnX, pPtAry->mnY), nPoints);
+ for (sal_uInt32 i = 1; i < nPoints; ++i)
+ aPolygon.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].mnX, pPtAry[i].mnY));
+
+ drawPolyPolygon(basegfx::B2DHomMatrix(), basegfx::B2DPolyPolygon(aPolygon), 0.0);
+}
+
+void SkiaSalGraphicsImpl::drawPolyPolygon(sal_uInt32 nPoly, const sal_uInt32* pPoints,
+ PCONSTSALPOINT* pPtAry)
+{
+ basegfx::B2DPolyPolygon aPolyPolygon;
+ for (sal_uInt32 nPolygon = 0; nPolygon < nPoly; ++nPolygon)
+ {
+ sal_uInt32 nPoints = pPoints[nPolygon];
+ if (nPoints)
+ {
+ PCONSTSALPOINT pSalPoints = pPtAry[nPolygon];
+ basegfx::B2DPolygon aPolygon;
+ aPolygon.append(basegfx::B2DPoint(pSalPoints->mnX, pSalPoints->mnY), nPoints);
+ for (sal_uInt32 i = 1; i < nPoints; ++i)
+ aPolygon.setB2DPoint(i, basegfx::B2DPoint(pSalPoints[i].mnX, pSalPoints[i].mnY));
+
+ aPolyPolygon.append(aPolygon);
+ }
+ }
+
+ drawPolyPolygon(basegfx::B2DHomMatrix(), aPolyPolygon, 0.0);
+}
+
+bool SkiaSalGraphicsImpl::drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon& rPolyPolygon,
+ double fTransparency)
+{
+ const bool bHasFill(mFillColor != SALCOLOR_NONE);
+ const bool bHasLine(mLineColor != SALCOLOR_NONE);
+
+ if (rPolyPolygon.count() == 0 || !(bHasFill || bHasLine) || fTransparency < 0.0
+ || fTransparency >= 1.0)
+ return true;
+
+ basegfx::B2DPolyPolygon aPolyPolygon(rPolyPolygon);
+ aPolyPolygon.transform(rObjectToDevice);
+
+ SAL_INFO("vcl.skia.trace", "drawpolypolygon(" << this << "): " << aPolyPolygon << ":"
+ << mLineColor << ":" << mFillColor);
+
+ if (delayDrawPolyPolygon(aPolyPolygon, fTransparency))
+ {
+ scheduleFlush();
+ return true;
+ }
+
+ performDrawPolyPolygon(aPolyPolygon, fTransparency, mParent.getAntiAliasB2DDraw());
+ return true;
+}
+
+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);
+ addXorRegion(polygonPath.getBounds());
+
+ SkPaint aPaint;
+ aPaint.setAntiAlias(useAA);
+
+ // 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 (mFillColor != SALCOLOR_NONE)
+ {
+ aPaint.setColor(toSkColorWithTransparency(mFillColor, fTransparency));
+ aPaint.setStyle(SkPaint::kFill_Style);
+ // 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 (mLineColor == SALCOLOR_NONE && polygonPath.getBounds().isEmpty())
+ aPaint.setStyle(SkPaint::kStroke_Style);
+ getDrawCanvas()->drawPath(polygonPath, aPaint);
+ }
+ if (mLineColor != SALCOLOR_NONE)
+ {
+ aPaint.setColor(toSkColorWithTransparency(mLineColor, fTransparency));
+ aPaint.setStyle(SkPaint::kStroke_Style);
+ getDrawCanvas()->drawPath(polygonPath, aPaint);
+ }
+ postDraw();
+#if defined LINUX
+ // WORKAROUND: The logo in the about dialog has drawing errors. This seems to happen
+ // only on Linux (not Windows on the same machine), with both AMDGPU and Mesa,
+ // and only when antialiasing is enabled. Flushing seems to avoid the problem.
+ if (useAA && SkiaHelper::getVendor() == DriverBlocklist::VendorAMD)
+ mSurface->flushAndSubmit();
+#endif
+}
+
+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.getAntiAliasB2DDraw())
+ return false;
+ // Only filled polygons without an outline are problematic.
+ if (mFillColor == SALCOLOR_NONE || mLineColor != SALCOLOR_NONE)
+ 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;
+}
+
+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
+ // TODO: tdf#136222 shows that basegfx::utils::mergeToSinglePolyPolygon() is unreliable
+ // in corner cases, possibly either a bug or rounding errors somewhere.
+ 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
+ || mLineColor == SALCOLOR_NONE)
+ {
+ return true;
+ }
+
+ preDraw();
+ SAL_INFO("vcl.skia.trace", "drawpolyline(" << this << "): " << rPolyLine << ":" << mLineColor);
+
+ // 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();
+
+ // Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline
+ basegfx::B2DPolyPolygon aPolyPolygonLine;
+ aPolyPolygonLine.append(rPolyLine);
+ aPolyPolygonLine.transform(rObjectToDevice);
+ if (bPixelSnapHairline)
+ {
+ aPolyPolygonLine = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyPolygonLine);
+ }
+
+ // Setup Line Join
+ SkPaint::Join eSkLineJoin = SkPaint::kMiter_Join;
+ switch (eLineJoin)
+ {
+ case basegfx::B2DLineJoin::Bevel:
+ eSkLineJoin = SkPaint::kBevel_Join;
+ break;
+ case basegfx::B2DLineJoin::Round:
+ eSkLineJoin = SkPaint::kRound_Join;
+ break;
+ case basegfx::B2DLineJoin::NONE:
+ case basegfx::B2DLineJoin::Miter:
+ eSkLineJoin = SkPaint::kMiter_Join;
+ break;
+ }
+
+ // convert miter minimum angle to miter limit
+ double fMiterLimit = 1.0 / std::sin(fMiterMinimumAngle / 2.0);
+
+ // Setup Line Cap
+ SkPaint::Cap eSkLineCap(SkPaint::kButt_Cap);
+
+ switch (eLineCap)
+ {
+ case css::drawing::LineCap_ROUND:
+ eSkLineCap = SkPaint::kRound_Cap;
+ break;
+ case css::drawing::LineCap_SQUARE:
+ eSkLineCap = SkPaint::kSquare_Cap;
+ break;
+ default: // css::drawing::LineCap_BUTT:
+ eSkLineCap = SkPaint::kButt_Cap;
+ break;
+ }
+
+ SkPaint aPaint;
+ aPaint.setStyle(SkPaint::kStroke_Style);
+ aPaint.setStrokeCap(eSkLineCap);
+ aPaint.setStrokeJoin(eSkLineJoin);
+ aPaint.setColor(toSkColorWithTransparency(mLineColor, fTransparency));
+ aPaint.setStrokeMiter(fMiterLimit);
+ aPaint.setStrokeWidth(fLineWidth);
+ aPaint.setAntiAlias(mParent.getAntiAliasB2DDraw());
+ // See the tdf#134346 comment above.
+ const SkScalar posFix = mParent.getAntiAliasB2DDraw() ? 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.setFillType(SkPathFillType::kEvenOdd);
+ for (sal_uInt32 a(0); a < aPolyPolygonLine.count(); a++)
+ addPolygonToPath(aPolyPolygonLine.getB2DPolygon(a), aPath);
+ aPath.offset(toSkX(0) + posFix, toSkY(0) + posFix, nullptr);
+ addXorRegion(aPath.getBounds());
+ getDrawCanvas()->drawPath(aPath, aPaint);
+ }
+ else
+ {
+ for (sal_uInt32 i = 0; i < aPolyPolygonLine.count(); ++i)
+ {
+ const basegfx::B2DPolygon& rPolygon = aPolyPolygonLine.getB2DPolygon(i);
+ sal_uInt32 nPoints = rPolygon.count();
+ bool bClosed = rPolygon.isClosed();
+ for (sal_uInt32 j = 0; j < (bClosed ? nPoints : nPoints - 1); ++j)
+ {
+ sal_uInt32 index1 = (j + 0) % nPoints;
+ sal_uInt32 index2 = (j + 1) % nPoints;
+ SkPath aPath;
+ aPath.moveTo(rPolygon.getB2DPoint(index1).getX(),
+ rPolygon.getB2DPoint(index1).getY());
+ aPath.lineTo(rPolygon.getB2DPoint(index2).getX(),
+ rPolygon.getB2DPoint(index2).getY());
+
+ aPath.offset(toSkX(0) + posFix, toSkY(0) + posFix, nullptr);
+ addXorRegion(aPath.getBounds());
+ getDrawCanvas()->drawPath(aPath, aPaint);
+ }
+ }
+ }
+
+ postDraw();
+
+ return true;
+}
+
+bool SkiaSalGraphicsImpl::drawPolyLineBezier(sal_uInt32, const SalPoint*, const PolyFlags*)
+{
+ // TODO?
+ return false;
+}
+
+bool SkiaSalGraphicsImpl::drawPolygonBezier(sal_uInt32, const SalPoint*, const PolyFlags*)
+{
+ // TODO?
+ return false;
+}
+
+bool SkiaSalGraphicsImpl::drawPolyPolygonBezier(sal_uInt32, const sal_uInt32*,
+ const SalPoint* const*, const PolyFlags* const*)
+{
+ // TODO?
+ return false;
+}
+
+static void copyArea(SkCanvas* canvas, sk_sp<SkSurface> surface, long nDestX, long nDestY,
+ long nSrcX, long nSrcY, long nSrcWidth, long nSrcHeight, bool srcIsRaster,
+ bool destIsRaster)
+{
+ // Using SkSurface::draw() should be more efficient than SkSurface::makeImageSnapshot(),
+ // because it may detect copying to itself and avoid some needless copies.
+ // But it has problems with drawing to itself
+ // (https://groups.google.com/forum/#!topic/skia-discuss/6yiuw24jv0I) and also
+ // raster surfaces do not avoid a copy of the source
+ // (https://groups.google.com/forum/#!topic/skia-discuss/S3FMpCi82k0).
+ // Finally, there's not much point if one of them is raster and the other is not (chrome/m86 even crashes).
+ if (canvas == surface->getCanvas() || srcIsRaster || (srcIsRaster != destIsRaster))
+ {
+ SkPaint paint;
+ paint.setBlendMode(SkBlendMode::kSrc); // copy as is, including alpha
+ canvas->drawImageRect(SkiaHelper::makeCheckedImageSnapshot(surface),
+ SkIRect::MakeXYWH(nSrcX, nSrcY, nSrcWidth, nSrcHeight),
+ SkRect::MakeXYWH(nDestX, nDestY, nSrcWidth, nSrcHeight), &paint);
+ return;
+ }
+ // SkCanvas::draw() cannot do a subrectangle, so clip.
+ canvas->save();
+ canvas->clipRect(SkRect::MakeXYWH(nDestX, nDestY, nSrcWidth, nSrcHeight));
+ SkPaint paint;
+ paint.setBlendMode(SkBlendMode::kSrc); // copy as is, including alpha
+ surface->draw(canvas, nDestX - nSrcX, nDestY - nSrcY, &paint);
+ canvas->restore();
+}
+
+void SkiaSalGraphicsImpl::copyArea(long nDestX, long nDestY, long nSrcX, long nSrcY, long nSrcWidth,
+ 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));
+ assert(!mXorMode);
+ ::copyArea(getDrawCanvas(), mSurface, nDestX, nDestY, nSrcX, nSrcY, nSrcWidth, nSrcHeight,
+ !isGPU(), !isGPU());
+ 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);
+ }
+ if (rPosAry.mnSrcWidth == rPosAry.mnDestWidth && rPosAry.mnSrcHeight == rPosAry.mnDestHeight)
+ {
+ 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() << " copy area: " << rPosAry);
+ ::copyArea(getDrawCanvas(), src->mSurface, rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnSrcX,
+ rPosAry.mnSrcY, rPosAry.mnDestWidth, rPosAry.mnDestHeight, !src->isGPU(),
+ !isGPU());
+ }
+ else
+ {
+ SAL_INFO("vcl.skia.trace", "copybits(" << this << "): (" << src << "): " << rPosAry);
+ // Do not use makeImageSnapshot(rect), as that one may make a needless data copy.
+ sk_sp<SkImage> image = SkiaHelper::makeCheckedImageSnapshot(src->mSurface);
+ SkPaint paint;
+ paint.setBlendMode(SkBlendMode::kSrc); // copy as is, including alpha
+ if (rPosAry.mnSrcWidth != rPosAry.mnDestWidth
+ || rPosAry.mnSrcHeight != rPosAry.mnDestHeight)
+ paint.setFilterQuality(kHigh_SkFilterQuality);
+ getDrawCanvas()->drawImageRect(image,
+ SkIRect::MakeXYWH(rPosAry.mnSrcX, rPosAry.mnSrcY,
+ rPosAry.mnSrcWidth, rPosAry.mnSrcHeight),
+ SkRect::MakeXYWH(rPosAry.mnDestX, rPosAry.mnDestY,
+ rPosAry.mnDestWidth, rPosAry.mnDestHeight),
+ &paint);
+ }
+ assert(!mXorMode);
+ postDraw();
+}
+
+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 which
+ // is actually one-minus-alpha (opacity). Therefore white=0xff=transparent,
+ // black=0x00=opaque. So the result is transparent only if both the inputs
+ // are transparent. Since for blending operations white=1.0 and black=0.0,
+ // kMultiply should handle exactly that (transparent*transparent=transparent,
+ // opaque*transparent=opaque). And guessing from the "floor" in TYPE_BLEND in opengl's
+ // combinedTextureFragmentShader.glsl, the layer is not even alpha values but
+ // simply yes-or-no mask.
+ // See also blendAlphaBitmap().
+ drawImage(rPosAry, rSkiaBitmap.GetSkImage(), SkBlendMode::kMultiply);
+ return true;
+}
+
+bool SkiaSalGraphicsImpl::blendAlphaBitmap(const SalTwoRect& rPosAry,
+ const SalBitmap& rSourceBitmap,
+ const SalBitmap& rMaskBitmap,
+ const SalBitmap& rAlphaBitmap)
+{
+ 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);
+
+ // 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().
+
+ // 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::kDstOut, rSkiaMaskBitmap.GetAlphaSkImage()->makeShader(),
+ rSkiaAlphaBitmap.GetAlphaSkImage()->makeShader());
+ // And now draw the bitmap with "1 - x", where x is the "( 1 - alpha ) * mask".
+ sk_sp<SkShader> shader = SkShaders::Blend(SkBlendMode::kSrcOut, shaderAlpha,
+ rSkiaSourceBitmap.GetSkImage()->makeShader());
+ 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);
+
+ drawImage(rPosAry, rSkiaSourceBitmap.GetSkImage());
+}
+
+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);
+ drawShader(rPosAry,
+ SkShaders::Blend(SkBlendMode::kDstOut, // VCL alpha is one-minus-alpha.
+ SkShaders::Color(toSkColor(nMaskColor)),
+ skiaBitmap.GetAlphaSkImage()->makeShader()));
+}
+
+std::shared_ptr<SalBitmap> SkiaSalGraphicsImpl::getBitmap(long nX, long nY, long nWidth,
+ 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 = SkiaHelper::makeCheckedImageSnapshot(
+ mSurface, SkIRect::MakeXYWH(nX, nY, nWidth, nHeight));
+ return std::make_shared<SkiaSalBitmap>(image);
+}
+
+Color SkiaSalGraphicsImpl::getPixel(long nX, 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(GetWidth(), GetHeight()))
+ abort();
+ if (!mSurface->readPixels(bitmap, 0, 0))
+ abort();
+ return fromSkColor(bitmap.getColor(nX, nY));
+}
+
+void SkiaSalGraphicsImpl::invert(basegfx::B2DPolygon const& rPoly, SalInvert eFlags)
+{
+ preDraw();
+ SAL_INFO("vcl.skia.trace", "invert(" << this << "): " << rPoly << ":" << int(eFlags));
+ assert(!mXorMode);
+ // Intel Vulkan drivers (up to current 0.401.3889) have a problem
+ // with SkBlendMode::kDifference(?) and surfaces wider than 1024 pixels, resulting
+ // in drawing errors. Work that around by fetching the relevant part of the surface
+ // and drawing using CPU.
+ bool intelHack
+ = (isGPU() && SkiaHelper::getVendor() == DriverBlocklist::VendorIntel && !mXorMode);
+ // TrackFrame just inverts a dashed path around the polygon
+ if (eFlags == SalInvert::TrackFrame)
+ {
+ SkPath aPath;
+ addPolygonToPath(rPoly, aPath);
+ aPath.setFillType(SkPathFillType::kEvenOdd);
+ // 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.
+ SkAutoCanvasRestore autoRestore(getDrawCanvas(), true);
+ getDrawCanvas()->clipRect(aPath.getBounds(), SkClipOp::kIntersect, false);
+ SkPaint aPaint;
+ aPaint.setStrokeWidth(2);
+ float intervals[] = { 4.0f, 4.0f };
+ aPaint.setStyle(SkPaint::kStroke_Style);
+ aPaint.setPathEffect(SkDashPathEffect::Make(intervals, SK_ARRAY_COUNT(intervals), 0));
+ aPaint.setColor(SkColorSetARGB(255, 255, 255, 255));
+ aPaint.setBlendMode(SkBlendMode::kDifference);
+ if (!intelHack)
+ getDrawCanvas()->drawPath(aPath, aPaint);
+ else
+ {
+ SkRect area;
+ aPath.getBounds().roundOut(&area);
+ SkRect size = SkRect::MakeWH(area.width(), area.height());
+ sk_sp<SkSurface> surface = SkSurface::MakeRasterN32Premul(area.width(), area.height());
+ SkPaint copy;
+ copy.setBlendMode(SkBlendMode::kSrc);
+ flushDrawing();
+ surface->getCanvas()->drawImageRect(SkiaHelper::makeCheckedImageSnapshot(mSurface),
+ area, size, &copy);
+ aPath.offset(-area.x(), -area.y());
+ surface->getCanvas()->drawPath(aPath, aPaint);
+ getDrawCanvas()->drawImageRect(SkiaHelper::makeCheckedImageSnapshot(surface), size,
+ area, &copy);
+ }
+ }
+ else
+ {
+ SkPath aPath;
+ addPolygonToPath(rPoly, aPath);
+ aPath.setFillType(SkPathFillType::kEvenOdd);
+ SkPaint aPaint;
+ aPaint.setColor(SkColorSetARGB(255, 255, 255, 255));
+ aPaint.setStyle(SkPaint::kFill_Style);
+ aPaint.setBlendMode(SkBlendMode::kDifference);
+
+ // N50 inverts in checker pattern
+ if (eFlags == SalInvert::N50)
+ {
+ // This creates 2x2 checker pattern bitmap
+ // TODO Use SkiaHelper::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));
+ }
+ if (!intelHack)
+ getDrawCanvas()->drawPath(aPath, aPaint);
+ else
+ {
+ SkRect area;
+ aPath.getBounds().roundOut(&area);
+ SkRect size = SkRect::MakeWH(area.width(), area.height());
+ sk_sp<SkSurface> surface = SkSurface::MakeRasterN32Premul(area.width(), area.height());
+ SkPaint copy;
+ copy.setBlendMode(SkBlendMode::kSrc);
+ flushDrawing();
+ surface->getCanvas()->drawImageRect(SkiaHelper::makeCheckedImageSnapshot(mSurface),
+ area, size, &copy);
+ aPath.offset(-area.x(), -area.y());
+ surface->getCanvas()->drawPath(aPath, aPaint);
+ getDrawCanvas()->drawImageRect(SkiaHelper::makeCheckedImageSnapshot(surface), size,
+ area, &copy);
+ }
+ }
+ postDraw();
+}
+
+void SkiaSalGraphicsImpl::invert(long nX, long nY, long nWidth, 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 SalPoint* pPointArray, SalInvert eFlags)
+{
+ basegfx::B2DPolygon aPolygon;
+ aPolygon.append(basegfx::B2DPoint(pPointArray[0].mnX, pPointArray[0].mnY), nPoints);
+ for (sal_uInt32 i = 1; i < nPoints; ++i)
+ {
+ aPolygon.setB2DPoint(i, basegfx::B2DPoint(pPointArray[i].mnX, pPointArray[i].mnY));
+ }
+ aPolygon.setClosed(true);
+
+ invert(aPolygon, eFlags);
+}
+
+bool SkiaSalGraphicsImpl::drawEPS(long, long, long, long, void*, sal_uInt32)
+{
+ // TODO?
+ 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.
+sk_sp<SkImage> SkiaSalGraphicsImpl::mergeCacheBitmaps(const SkiaSalBitmap& bitmap,
+ const SkiaSalBitmap* alphaBitmap,
+ const Size targetSize)
+{
+ sk_sp<SkImage> image;
+ // GPU-accelerated drawing with SkShader should be fast enough to not need caching.
+ if (isGPU())
+ return image;
+ if (targetSize.IsEmpty())
+ return image;
+ // Probably not much point in caching of just doing a copy.
+ if (alphaBitmap == nullptr && targetSize == bitmap.GetSize())
+ return image;
+ // Image too small to be worth caching.
+ if (bitmap.GetSize().Width() < 100 && bitmap.GetSize().Height() < 100
+ && targetSize.Width() < 100 && targetSize.Height() < 100)
+ return image;
+ // In some cases (tdf#134237) the draw 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.
+ if (targetSize.Width() > GetWidth() || targetSize.Height() > GetHeight())
+ {
+ // 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 = 1.0 * targetSize.Width() / bitmap.GetSize().Width()
+ * targetSize.Height() / bitmap.GetSize().Height();
+ const double oversizeRatio
+ = 1.0 * targetSize.Width() / GetWidth() * targetSize.Height() / GetHeight();
+ const double ratio = upscaleRatio * oversizeRatio;
+ if (ratio > 10)
+ {
+ SAL_INFO("vcl.skia.trace", "mergecachebitmaps("
+ << this << "): not caching upscaling, ratio:" << ratio
+ << ", " << bitmap.GetSize() << "->" << targetSize
+ << " in " << Size(GetWidth(), GetHeight()));
+ return image;
+ }
+ }
+ OString key;
+ OStringBuffer keyBuf;
+ keyBuf.append(targetSize.Width())
+ .append("x")
+ .append(targetSize.Height())
+ .append("_0x")
+ .append(reinterpret_cast<sal_IntPtr>(&bitmap), 16)
+ .append("_0x")
+ .append(reinterpret_cast<sal_IntPtr>(alphaBitmap), 16)
+ .append("_")
+ .append(static_cast<sal_Int64>(bitmap.GetSkImage()->uniqueID()));
+ if (alphaBitmap)
+ keyBuf.append("_").append(
+ static_cast<sal_Int64>(alphaBitmap->GetAlphaSkImage()->uniqueID()));
+ key = keyBuf.makeStringAndClear();
+ image = SkiaHelper::findCachedImage(key);
+ if (image)
+ {
+ assert(image->width() == targetSize.Width() && image->height() == targetSize.Height());
+ return image;
+ }
+ // Combine bitmap + alpha bitmap into one temporary bitmap with alpha.
+ // If scaling is needed, first apply the alpha, then scale, otherwise the scaling might affect the alpha values.
+ if (alphaBitmap && targetSize != bitmap.GetSize())
+ {
+ sk_sp<SkSurface> mergedSurface = SkiaHelper::createSkSurface(bitmap.GetSize());
+ if (!mergedSurface)
+ return nullptr;
+ SkPaint paint;
+ paint.setBlendMode(SkBlendMode::kSrc); // copy as is, including alpha
+ mergedSurface->getCanvas()->drawImage(bitmap.GetSkImage(), 0, 0, &paint);
+ paint.setBlendMode(SkBlendMode::kDstOut); // VCL alpha is one-minus-alpha
+ mergedSurface->getCanvas()->drawImage(alphaBitmap->GetAlphaSkImage(), 0, 0, &paint);
+ sk_sp<SkSurface> scaledSurface = SkiaHelper::createSkSurface(targetSize);
+ if (!scaledSurface)
+ return nullptr;
+ paint.setBlendMode(SkBlendMode::kSrc); // copy as is, including alpha
+ paint.setFilterQuality(kHigh_SkFilterQuality);
+ scaledSurface->getCanvas()->drawImageRect(
+ mergedSurface->makeImageSnapshot(),
+ SkRect::MakeXYWH(0, 0, bitmap.GetSize().Width(), bitmap.GetSize().Height()),
+ SkRect::MakeXYWH(0, 0, targetSize.Width(), targetSize.Height()), &paint);
+ image = scaledSurface->makeImageSnapshot();
+ }
+ else // No alpha or no scaling, scale directly.
+ {
+ sk_sp<SkSurface> tmpSurface = SkiaHelper::createSkSurface(targetSize);
+ if (!tmpSurface)
+ return nullptr;
+ SkCanvas* canvas = tmpSurface->getCanvas();
+ SkAutoCanvasRestore autoRestore(canvas, true);
+ SkPaint paint;
+ if (targetSize != bitmap.GetSize())
+ {
+ SkMatrix matrix;
+ matrix.set(SkMatrix::kMScaleX, 1.0 * targetSize.Width() / bitmap.GetSize().Width());
+ matrix.set(SkMatrix::kMScaleY, 1.0 * targetSize.Height() / bitmap.GetSize().Height());
+ canvas->concat(matrix);
+ paint.setFilterQuality(kHigh_SkFilterQuality);
+ }
+ paint.setBlendMode(SkBlendMode::kSrc); // copy as is, including alpha
+ canvas->drawImage(bitmap.GetSkImage(), 0, 0, &paint);
+ if (alphaBitmap != nullptr)
+ {
+ paint.setBlendMode(SkBlendMode::kDstOut); // VCL alpha is one-minus-alpha
+ canvas->drawImage(alphaBitmap->GetAlphaSkImage(), 0, 0, &paint);
+ }
+ image = SkiaHelper::makeCheckedImageSnapshot(tmpSurface);
+ }
+ SkiaHelper::addCachedImage(key, image);
+ return image;
+}
+
+bool SkiaSalGraphicsImpl::drawAlphaBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSourceBitmap,
+ const SalBitmap& rAlphaBitmap)
+{
+ assert(dynamic_cast<const SkiaSalBitmap*>(&rSourceBitmap));
+ assert(dynamic_cast<const SkiaSalBitmap*>(&rAlphaBitmap));
+ // In raster mode use mergeCacheBitmaps(), which will cache the result, avoiding repeated
+ // alpha blending or scaling. In GPU mode it is simpler to just use SkShader.
+ 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(static_cast<const SkiaSalBitmap&>(rSourceBitmap),
+ static_cast<const SkiaSalBitmap*>(&rAlphaBitmap), imageSize);
+ if (image)
+ drawImage(imagePosAry, image);
+ else
+ drawShader(
+ rPosAry,
+ SkShaders::Blend(
+ SkBlendMode::kDstOut, // VCL alpha is one-minus-alpha.
+ static_cast<const SkiaSalBitmap&>(rSourceBitmap).GetSkImage()->makeShader(),
+ static_cast<const SkiaSalBitmap*>(&rAlphaBitmap)->GetAlphaSkImage()->makeShader()));
+ return true;
+}
+
+void SkiaSalGraphicsImpl::drawImage(const SalTwoRect& rPosAry, const sk_sp<SkImage>& aImage,
+ SkBlendMode eBlendMode)
+{
+ SkRect aSourceRect
+ = SkRect::MakeXYWH(rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth, rPosAry.mnSrcHeight);
+ SkRect aDestinationRect = SkRect::MakeXYWH(rPosAry.mnDestX, rPosAry.mnDestY,
+ rPosAry.mnDestWidth, rPosAry.mnDestHeight);
+
+ SkPaint aPaint;
+ aPaint.setBlendMode(eBlendMode);
+ if (rPosAry.mnSrcWidth != rPosAry.mnDestWidth || rPosAry.mnSrcHeight != rPosAry.mnDestHeight)
+ aPaint.setFilterQuality(kHigh_SkFilterQuality);
+
+ preDraw();
+ SAL_INFO("vcl.skia.trace",
+ "drawimage(" << this << "): " << rPosAry << ":" << SkBlendMode_Name(eBlendMode));
+ addXorRegion(aDestinationRect);
+ getDrawCanvas()->drawImageRect(aImage, aSourceRect, aDestinationRect, &aPaint);
+ 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)
+{
+ preDraw();
+ SAL_INFO("vcl.skia.trace", "drawshader(" << this << "): " << rPosAry);
+ SkRect destinationRect = SkRect::MakeXYWH(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth,
+ rPosAry.mnDestHeight);
+ addXorRegion(destinationRect);
+ SkPaint paint;
+ paint.setShader(shader);
+ if (rPosAry.mnSrcWidth != rPosAry.mnDestWidth || rPosAry.mnSrcHeight != rPosAry.mnDestHeight)
+ paint.setFilterQuality(kHigh_SkFilterQuality);
+ 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);
+ assert(matrix.mapXY(rPosAry.mnSrcX, rPosAry.mnSrcY)
+ == SkPoint::Make(rPosAry.mnDestX, rPosAry.mnDestY));
+ assert(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::drawTransformedBitmap(const basegfx::B2DPoint& rNull,
+ const basegfx::B2DPoint& rX,
+ const basegfx::B2DPoint& rY,
+ const SalBitmap& rSourceBitmap,
+ const SalBitmap* pAlphaBitmap)
+{
+ 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);
+
+ // 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);
+
+ addXorRegion(SkRect::MakeWH(GetWidth(), GetHeight())); // can't tell, use whole area
+ // In raster mode scaling and alpha blending is still somewhat expensive if done repeatedly,
+ // so use mergeCacheBitmaps(), which will cache the result if useful.
+ // It is better to use SkShader if in GPU mode, if the operation is simple or if the temporary
+ // image would be very large.
+ sk_sp<SkImage> imageToDraw = mergeCacheBitmaps(
+ rSkiaBitmap, pSkiaAlphaBitmap, Size(round(aXRel.getLength()), round(aYRel.getLength())));
+ 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()) / imageToDraw->width());
+ matrix.set(SkMatrix::kMScaleY, round(aYRel.getY()) / imageToDraw->height());
+ matrix.set(SkMatrix::kMSkewY, aXRel.getY() / imageToDraw->width());
+ matrix.set(SkMatrix::kMSkewX, aYRel.getX() / imageToDraw->height());
+ matrix.set(SkMatrix::kMTransX, rNull.getX());
+ matrix.set(SkMatrix::kMTransY, rNull.getY());
+ SkCanvas* canvas = getDrawCanvas();
+ SkAutoCanvasRestore autoRestore(canvas, true);
+ canvas->concat(matrix);
+ SkPaint paint;
+ paint.setFilterQuality(kHigh_SkFilterQuality);
+ canvas->drawImage(imageToDraw, 0, 0, &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);
+ SkPaint paint;
+ paint.setFilterQuality(kHigh_SkFilterQuality);
+ if (pSkiaAlphaBitmap)
+ {
+ paint.setShader(SkShaders::Blend(SkBlendMode::kDstOut, // VCL alpha is one-minus-alpha.
+ rSkiaBitmap.GetSkImage()->makeShader(),
+ pSkiaAlphaBitmap->GetAlphaSkImage()->makeShader()));
+ canvas->drawRect(SkRect::MakeWH(aSize.Width(), aSize.Height()), paint);
+ }
+ else
+ {
+ canvas->drawImage(rSkiaBitmap.GetSkImage(), 0, 0, &paint);
+ }
+ }
+ postDraw();
+ return true;
+}
+
+bool SkiaSalGraphicsImpl::drawAlphaRect(long nX, long nY, long nWidth, long nHeight,
+ sal_uInt8 nTransparency)
+{
+ privateDrawAlphaRect(nX, nY, nWidth, nHeight, nTransparency / 100.0);
+ return true;
+}
+
+bool SkiaSalGraphicsImpl::drawGradient(const tools::PolyPolygon&, const Gradient&)
+{
+ // TODO?
+ return false;
+}
+
+static double toRadian(int degree10th) { return (3600 - degree10th) * M_PI / 1800.0; }
+static double toCos(int degree10th) { return SkScalarCos(toRadian(degree10th)); }
+static double toSin(int degree10th) { return SkScalarSin(toRadian(degree10th)); }
+
+void SkiaSalGraphicsImpl::drawGenericLayout(const GenericSalLayout& layout, Color textColor,
+ const SkFont& font, GlyphOrientation glyphOrientation)
+{
+ SkiaZone zone;
+ std::vector<SkGlyphID> glyphIds;
+ std::vector<SkRSXform> glyphForms;
+ glyphIds.reserve(256);
+ glyphForms.reserve(256);
+ Point aPos;
+ const GlyphItem* pGlyph;
+ int nStart = 0;
+ while (layout.GetNextGlyph(&pGlyph, aPos, nStart))
+ {
+ glyphIds.push_back(pGlyph->glyphId());
+ int angle = 0; // 10th of degree
+ if (glyphOrientation == GlyphOrientation::Apply)
+ {
+ angle = layout.GetOrientation();
+ if (pGlyph->IsVertical())
+ angle += 900; // 90 degree
+ }
+ SkRSXform form = SkRSXform::Make(toCos(angle), toSin(angle), aPos.X(), aPos.Y());
+ glyphForms.emplace_back(std::move(form));
+ }
+ if (glyphIds.empty())
+ return;
+ sk_sp<SkTextBlob> textBlob
+ = SkTextBlob::MakeFromRSXform(glyphIds.data(), glyphIds.size() * sizeof(SkGlyphID),
+ glyphForms.data(), font, SkTextEncoding::kGlyphID);
+ preDraw();
+ SAL_INFO("vcl.skia.trace",
+ "drawtextblob(" << this << "): " << textBlob->bounds() << ":" << textColor);
+ addXorRegion(textBlob->bounds());
+ SkPaint paint;
+ paint.setColor(toSkColor(textColor));
+ getDrawCanvas()->drawTextBlob(textBlob, 0, 0, paint);
+ postDraw();
+}
+
+bool SkiaSalGraphicsImpl::supportsOperation(OutDevSupportType eType) const
+{
+ switch (eType)
+ {
+ case OutDevSupportType::B2DDraw:
+ case OutDevSupportType::TransparentRect:
+ return true;
+ default:
+ return false;
+ }
+}
+
+#ifdef DBG_UTIL
+void SkiaSalGraphicsImpl::dump(const char* file) const
+{
+ assert(mSurface.get());
+ SkiaHelper::dump(mSurface, file);
+}
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/skia/salbmp.cxx b/vcl/skia/salbmp.cxx
new file mode 100644
index 000000000..0f62f7dc5
--- /dev/null
+++ b/vcl/skia/salbmp.cxx
@@ -0,0 +1,819 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <SkCanvas.h>
+#include <SkImage.h>
+#include <SkPixelRef.h>
+#include <SkSurface.h>
+#include <SkSwizzle.h>
+#include <SkColorFilter.h>
+#include <SkColorMatrix.h>
+
+#include <skia/utils.hxx>
+#include <skia/zone.hxx>
+
+#ifdef DBG_UTIL
+#include <fstream>
+#define CANARY "skia-canary"
+#endif
+
+SkiaSalBitmap::SkiaSalBitmap() {}
+
+SkiaSalBitmap::~SkiaSalBitmap() {}
+
+static bool isValidBitCount(sal_uInt16 nBitCount)
+{
+ return (nBitCount == 1) || (nBitCount == 4) || (nBitCount == 8) || (nBitCount == 24)
+ || (nBitCount == 32);
+}
+
+SkiaSalBitmap::SkiaSalBitmap(const sk_sp<SkImage>& image)
+{
+ ResetCachedData();
+ mBuffer.reset();
+ mImage = image;
+ mPalette = BitmapPalette();
+ mBitCount = 32;
+ mSize = mPixelsSize = Size(image->width(), image->height());
+#ifdef DBG_UTIL
+ mWriteAccessCount = 0;
+#endif
+ SAL_INFO("vcl.skia.trace", "bitmapfromimage(" << this << ")");
+}
+
+bool SkiaSalBitmap::Create(const Size& rSize, sal_uInt16 nBitCount, const BitmapPalette& rPal)
+{
+ ResetCachedData();
+ mBuffer.reset();
+ if (!isValidBitCount(nBitCount))
+ return false;
+ mPalette = rPal;
+ mBitCount = nBitCount;
+ mSize = mPixelsSize = rSize;
+#ifdef DBG_UTIL
+ mWriteAccessCount = 0;
+#endif
+ if (!CreateBitmapData())
+ {
+ mBitCount = 0;
+ mSize = mPixelsSize = Size();
+ mPalette = BitmapPalette();
+ return false;
+ }
+ SAL_INFO("vcl.skia.trace", "create(" << this << ")");
+ return true;
+}
+
+bool SkiaSalBitmap::CreateBitmapData()
+{
+ assert(!mBuffer);
+ // 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.
+ int bitScanlineWidth;
+ if (o3tl::checked_multiply<int>(mSize.Width(), mBitCount, bitScanlineWidth))
+ {
+ SAL_WARN("vcl.skia", "checked multiply failed");
+ return false;
+ }
+ mScanlineSize = AlignedWidth4Bytes(bitScanlineWidth);
+ if (mScanlineSize != 0 && mSize.Height() != 0)
+ {
+ size_t allocate = mScanlineSize * mSize.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
+ }
+ return true;
+}
+
+bool SkiaSalBitmap::Create(const SalBitmap& rSalBmp)
+{
+ return Create(rSalBmp, rSalBmp.GetBitCount());
+}
+
+bool SkiaSalBitmap::Create(const SalBitmap& rSalBmp, SalGraphics* pGraphics)
+{
+ return Create(rSalBmp, pGraphics ? pGraphics->GetBitCount() : rSalBmp.GetBitCount());
+}
+
+bool SkiaSalBitmap::Create(const SalBitmap& rSalBmp, sal_uInt16 nNewBitCount)
+{
+ const SkiaSalBitmap& src = static_cast<const SkiaSalBitmap&>(rSalBmp);
+ mImage = src.mImage;
+ mAlphaImage = src.mAlphaImage;
+ mBuffer = src.mBuffer;
+ mPalette = src.mPalette;
+ mBitCount = src.mBitCount;
+ mSize = src.mSize;
+ mPixelsSize = src.mPixelsSize;
+ mScanlineSize = src.mScanlineSize;
+ mScaleQuality = src.mScaleQuality;
+#ifdef DBG_UTIL
+ mWriteAccessCount = 0;
+#endif
+ if (nNewBitCount != 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)
+{
+ // TODO?
+ return false;
+}
+
+void SkiaSalBitmap::Destroy()
+{
+ SAL_INFO("vcl.skia.trace", "destroy(" << this << ")");
+#ifdef DBG_UTIL
+ assert(mWriteAccessCount == 0);
+#endif
+ ResetCachedData();
+ mBuffer.reset();
+}
+
+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;
+ break;
+ case BitmapAccessMode::Read:
+ EnsureBitmapData();
+ if (!mBuffer)
+ return nullptr;
+ break;
+ case BitmapAccessMode::Info:
+ 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;
+ buffer->mpBits = mBuffer.get();
+ buffer->mnScanlineSize = mScanlineSize;
+ switch (mBitCount)
+ {
+ case 1:
+ buffer->mnFormat = ScanlineFormat::N1BitMsbPal;
+ break;
+ case 4:
+ buffer->mnFormat = ScanlineFormat::N4BitMsnPal;
+ 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.
+// Use a macro to hide an unreachable code warning.
+#define GET_FORMAT \
+ (kN32_SkColorType == kBGRA_8888_SkColorType ? ScanlineFormat::N24BitTcBgr \
+ : ScanlineFormat::N24BitTcRgb)
+ buffer->mnFormat = GET_FORMAT;
+#undef GET_FORMAT
+ break;
+ }
+ case 32:
+#define GET_FORMAT \
+ (kN32_SkColorType == kBGRA_8888_SkColorType ? ScanlineFormat::N32BitTcBgra \
+ : ScanlineFormat::N32BitTcRgba)
+ buffer->mnFormat = GET_FORMAT;
+#undef GET_FORMAT
+ break;
+ default:
+ abort();
+ }
+ buffer->mnFormat |= ScanlineFormat::TopDown;
+#ifdef DBG_UTIL
+ if (nMode == BitmapAccessMode::Write)
+ ++mWriteAccessCount;
+#endif
+ return buffer;
+}
+
+void SkiaSalBitmap::ReleaseBuffer(BitmapBuffer* pBuffer, BitmapAccessMode nMode)
+{
+ if (nMode == BitmapAccessMode::Write)
+ {
+#ifdef DBG_UTIL
+ assert(mWriteAccessCount > 0);
+ --mWriteAccessCount;
+#endif
+ mPalette = pBuffer->maPalette;
+ ResetCachedData();
+ }
+ // Are there any more ground movements underneath us ?
+ assert(pBuffer->mnWidth == mSize.Width());
+ assert(pBuffer->mnHeight == mSize.Height());
+ assert(pBuffer->mnBitCount == mBitCount);
+ verify();
+ delete pBuffer;
+}
+
+bool SkiaSalBitmap::GetSystemData(BitmapSystemData&)
+{
+#ifdef DBG_UTIL
+ assert(mWriteAccessCount == 0);
+#endif
+ // TODO?
+ 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));
+
+ // 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'.
+ SkFilterQuality currentQuality;
+ switch (nScaleFlag)
+ {
+ case BmpScaleFlag::Fast:
+ currentQuality = kNone_SkFilterQuality;
+ break;
+ case BmpScaleFlag::Default:
+ currentQuality = kMedium_SkFilterQuality;
+ break;
+ case BmpScaleFlag::BestQuality:
+ currentQuality = kHigh_SkFilterQuality;
+ break;
+ default:
+ SAL_INFO("vcl.skia.trace", "scale(" << this << "): unsupported scale algorithm");
+ return false;
+ }
+ 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;
+ }
+ // if there is already one scale() pending, use the lowest quality of all requested
+ static_assert(kMedium_SkFilterQuality < kHigh_SkFilterQuality);
+ mScaleQuality = std::min(mScaleQuality, currentQuality);
+ // scaling will be actually done on-demand when needed, the need will be recognized
+ // by mSize != mPixelsSize
+ mSize = newSize;
+ // Do not reset cached data if mImage is possibly the only data we have.
+ if (mBuffer)
+ ResetCachedData();
+ // 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
+ // TODO?
+ 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)
+ {
+ if (mBitCount == 8 && mPalette.IsGreyPalette8Bit())
+ return true;
+ sk_sp<SkSurface> surface = SkiaHelper::createSkSurface(mPixelsSize);
+ 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, &paint);
+ mBitCount = 8;
+ mPalette = Bitmap::GetGreyPalette(256);
+ ResetToSkImage(SkiaHelper::makeCheckedImageSnapshot(surface));
+ 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;
+ // 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 (!mBuffer && mImage)
+ {
+ mBitCount = 8;
+ mPalette = Bitmap::GetGreyPalette(256);
+ ResetToSkImage(mImage); // keep mImage, it will be interpreted as 8bit if needed
+ SAL_INFO("vcl.skia.trace", "interpretas8bit(" << 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 SKIA_USE_BITMAP32
+ SkAlphaType alphaType = kPremul_SkAlphaType;
+#else
+ SkAlphaType alphaType = kUnpremul_SkAlphaType;
+#endif
+ if (!bitmap.installPixels(
+ SkImageInfo::MakeS32(mPixelsSize.Width(), mPixelsSize.Height(), alphaType),
+ data.release(), mPixelsSize.Width() * 4,
+ [](void* addr, void*) { delete[] static_cast<sal_uInt8*>(addr); }, nullptr))
+ abort();
+ bitmap.setImmutable();
+ }
+ 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();
+ for (long y = 0; y < mPixelsSize.Height(); ++y)
+ {
+ const sal_uInt8* src = mBuffer.get() + mScanlineSize * y;
+ // This 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).
+ SkExtendRGBToRGBA(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();
+ bitmap.setImmutable();
+ }
+ // Skia has a format for 8bit grayscale SkBitmap, but it seems to cause a problem
+ // with our PNG loader (tdf#121120), so convert it to RGBA below as well.
+ else
+ {
+// Use a macro to hide an unreachable code warning.
+#define GET_FORMAT \
+ (kN32_SkColorType == kBGRA_8888_SkColorType ? BitConvert::BGRA : BitConvert::RGBA)
+ std::unique_ptr<sal_uInt8[]> data
+ = convertDataBitCount(mBuffer.get(), mPixelsSize.Width(), mPixelsSize.Height(),
+ mBitCount, mScanlineSize, mPalette, GET_FORMAT);
+#undef GET_FORMAT
+ 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;
+}
+
+const sk_sp<SkImage>& SkiaSalBitmap::GetSkImage() const
+{
+#ifdef DBG_UTIL
+ assert(mWriteAccessCount == 0);
+#endif
+ if (mPixelsSize != mSize && !mImage
+ && SkiaHelper::renderMethodToUse() != SkiaHelper::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 = SkiaHelper::createSkImage(GetAsSkBitmap());
+ assert(image);
+ thisPtr->mSize = savedSize;
+ thisPtr->ResetToSkImage(image);
+ }
+ if (mImage)
+ {
+ if (mImage->width() != mSize.Width() || mImage->height() != mSize.Height())
+ {
+ assert(!mBuffer); // This code should be only called if only mImage holds data.
+ SkiaZone zone;
+ sk_sp<SkSurface> surface = SkiaHelper::createSkSurface(mSize);
+ assert(surface);
+ SkPaint paint;
+ paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha
+ paint.setFilterQuality(mScaleQuality);
+ surface->getCanvas()->drawImageRect(
+ mImage, SkRect::MakeWH(mImage->width(), mImage->height()),
+ SkRect::MakeWH(mSize.Width(), mSize.Height()), &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 = SkiaHelper::makeCheckedImageSnapshot(surface);
+ }
+ return mImage;
+ }
+ SkiaZone zone;
+ sk_sp<SkImage> image = SkiaHelper::createSkImage(GetAsSkBitmap());
+ assert(image);
+ const_cast<sk_sp<SkImage>&>(mImage) = image;
+ SAL_INFO("vcl.skia.trace", "getskimage(" << this << ")");
+ return mImage;
+}
+
+const sk_sp<SkImage>& SkiaSalBitmap::GetAlphaSkImage() const
+{
+#ifdef DBG_UTIL
+ assert(mWriteAccessCount == 0);
+#endif
+ if (mAlphaImage)
+ {
+ assert(mSize == mPixelsSize); // data has already been scaled if needed
+ return mAlphaImage;
+ }
+ if (mImage)
+ {
+ SkiaZone zone;
+ sk_sp<SkSurface> surface = SkiaHelper::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));
+ bool scaling = mImage->width() != mSize.Width() || mImage->height() != mSize.Height();
+ if (scaling)
+ {
+ assert(!mBuffer); // This code should be only called if only mImage holds data.
+ paint.setFilterQuality(mScaleQuality);
+ }
+ surface->getCanvas()->drawImageRect(mImage,
+ SkRect::MakeWH(mImage->width(), mImage->height()),
+ SkRect::MakeWH(mSize.Width(), mSize.Height()), &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");
+ SkiaSalBitmap* thisPtr = const_cast<SkiaSalBitmap*>(this);
+ thisPtr->mAlphaImage = SkiaHelper::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 = SkiaHelper::createSkImage(alphaBitmap);
+ assert(image);
+ const_cast<sk_sp<SkImage>&>(mAlphaImage) = image;
+ }
+ else
+ {
+ sk_sp<SkSurface> surface = SkiaHelper::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()->drawBitmap(GetAsSkBitmap(), 0, 0, &paint);
+ SkiaSalBitmap* thisPtr = const_cast<SkiaSalBitmap*>(this);
+ thisPtr->mAlphaImage = SkiaHelper::makeCheckedImageSnapshot(surface);
+ }
+ SAL_INFO("vcl.skia.trace", "getalphaskimage(" << this << ")");
+ return mAlphaImage;
+}
+
+void SkiaSalBitmap::EnsureBitmapData()
+{
+ 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(SkImage::MakeFromBitmap(GetAsSkBitmap()));
+ mSize = savedSize;
+ }
+ // Try to fill mBuffer from mImage.
+ if (!mImage)
+ return;
+ SkiaZone zone;
+ if (!CreateBitmapData())
+ abort();
+ SkAlphaType alphaType = kUnpremul_SkAlphaType;
+#if SKIA_USE_BITMAP32
+ if (mBitCount == 32)
+ alphaType = kPremul_SkAlphaType;
+#endif
+ SkBitmap bitmap;
+ 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 (mSize != mPixelsSize) // pending scaling?
+ {
+ paint.setFilterQuality(mScaleQuality);
+ canvas.drawImageRect(mImage,
+ SkRect::MakeWH(mPixelsSize.getWidth(), mPixelsSize.getHeight()),
+ SkRect::MakeWH(mSize.getWidth(), mSize.getHeight()), &paint);
+ SAL_INFO("vcl.skia.trace", "ensurebitmapdata(" << this << "): image scaled " << mPixelsSize
+ << "->" << mSize << ":"
+ << static_cast<int>(mScaleQuality));
+ mPixelsSize = mSize;
+ mScaleQuality = kHigh_SkFilterQuality;
+ // Information about the pending scaling has been discarded, so make sure we do not
+ // keep around any cached images that would still need scaling.
+ ResetCachedDataBySize();
+ }
+ else
+ canvas.drawImage(mImage, 0, 0, &paint);
+ canvas.flush();
+ bitmap.setImmutable();
+ assert(mBuffer != nullptr);
+ if (mBitCount == 32)
+ {
+ for (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
+ {
+ for (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;
+ for (long x = 0; x < mSize.Width(); ++x)
+ {
+ *dest++ = *src++;
+ *dest++ = *src++;
+ *dest++ = *src++;
+ ++src; // skip alpha
+ }
+ }
+ }
+ else if (mBitCount == 8 && mPalette.IsGreyPalette8Bit())
+ {
+ for (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;
+ // no actual data conversion, use one color channel as the gray value
+ for (long x = 0; x < mSize.Width(); ++x)
+ dest[x] = src[x * 4];
+ }
+ }
+ else
+ {
+ std::unique_ptr<vcl::ScanlineWriter> pWriter
+ = vcl::ScanlineWriter::Create(mBitCount, mPalette);
+ for (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 (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();
+ SAL_INFO("vcl.skia.trace", "ensurebitmapdata(" << this << ")");
+}
+
+void SkiaSalBitmap::EnsureBitmapUniqueData()
+{
+ EnsureBitmapData();
+ 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::ResetCachedData()
+{
+ SkiaZone zone;
+ // There may be a case when only mImage is set and CreatBitmapData() will create
+ // mBuffer from it if needed, in that case ResetToSkImage() should be used.
+ assert(mBuffer.get() || !mImage);
+ mImage.reset();
+ mAlphaImage.reset();
+}
+
+void SkiaSalBitmap::ResetToSkImage(sk_sp<SkImage> image)
+{
+ SkiaZone zone;
+ mBuffer.reset();
+ mImage = image;
+ mAlphaImage.reset();
+}
+
+void SkiaSalBitmap::ResetCachedDataBySize()
+{
+ SkiaZone zone;
+ assert(mSize == mPixelsSize);
+ if (mImage && (mImage->width() != mSize.getWidth() || mImage->height() != mSize.getHeight()))
+ mImage.reset();
+ if (mAlphaImage
+ && (mAlphaImage->width() != mSize.getWidth() || mAlphaImage->height() != mSize.getHeight()))
+ mAlphaImage.reset();
+}
+
+#ifdef DBG_UTIL
+void SkiaSalBitmap::dump(const char* file) const
+{
+ sk_sp<SkImage> saveImage = mImage;
+ sk_sp<SkImage> saveAlphaImage = mAlphaImage;
+ bool resetBuffer = !mBuffer;
+ int saveWriteAccessCount = mWriteAccessCount;
+ Size savePrescaleSize = mPixelsSize;
+ SkiaSalBitmap* thisPtr = const_cast<SkiaSalBitmap*>(this);
+ // avoid possible assert
+ thisPtr->mWriteAccessCount = 0;
+ SkiaHelper::dump(GetSkImage(), file);
+ // restore old state, so that debugging doesn't affect it
+ if (resetBuffer)
+ thisPtr->mBuffer.reset();
+ thisPtr->mImage = saveImage;
+ thisPtr->mAlphaImage = saveAlphaImage;
+ thisPtr->mWriteAccessCount = saveWriteAccessCount;
+ thisPtr->mPixelsSize = savePrescaleSize;
+}
+
+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_blacklist_vulkan.xml b/vcl/skia/skia_blacklist_vulkan.xml
new file mode 100644
index 000000000..8a222d005
--- /dev/null
+++ b/vcl/skia/skia_blacklist_vulkan.xml
@@ -0,0 +1,37 @@
+<?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>
+ <whitelist>
+ </whitelist>
+ <blacklist>
+ <entry os="all" vendor="intel" compare="less_equal" version="0.16.2">
+ <device id="all"/>
+ </entry>
+ <entry os="all" vendor="amd" compare="less_equal" version="2.0.49">
+ <device id="all"/>
+ </entry>
+ <entry os="windows" vendor="nvidia">
+ <device id="all"/>
+ </entry>
+ <entry os="7" vendor="all">
+ <device id="all"/>
+ </entry>
+ </blacklist>
+</root>
diff --git a/vcl/skia/win/gdiimpl.cxx b/vcl/skia/win/gdiimpl.cxx
new file mode 100644
index 000000000..a4cbe062f
--- /dev/null
+++ b/vcl/skia/win/gdiimpl.cxx
@@ -0,0 +1,393 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/win/gdiimpl.hxx>
+
+#include <win/saldata.hxx>
+#include <vcl/skia/SkiaHelper.hxx>
+#include <skia/utils.hxx>
+#include <skia/zone.hxx>
+#include <win/winlayout.hxx>
+#include <comphelper/windowserrorstring.hxx>
+
+#include <SkCanvas.h>
+#include <SkPaint.h>
+#include <SkPixelRef.h>
+#include <SkTypeface_win.h>
+#include <SkFont.h>
+#include <SkFontMgr.h>
+#include <SkFontLCDConfig.h>
+#include <tools/sk_app/win/WindowContextFactory_win.h>
+#include <tools/sk_app/WindowContext.h>
+
+#include <windows.h>
+
+WinSkiaSalGraphicsImpl::WinSkiaSalGraphicsImpl(WinSalGraphics& rGraphics,
+ SalGeometryProvider* mpProvider)
+ : SkiaSalGraphicsImpl(rGraphics, mpProvider)
+ , mWinParent(rGraphics)
+{
+}
+
+void WinSkiaSalGraphicsImpl::createWindowContext(bool forceRaster)
+{
+ SkiaZone zone;
+ sk_app::DisplayParams displayParams;
+ switch (forceRaster ? SkiaHelper::RenderRaster : SkiaHelper::renderMethodToUse())
+ {
+ case SkiaHelper::RenderRaster:
+ mWindowContext = sk_app::window_context_factory::MakeRasterForWin(mWinParent.gethWnd(),
+ displayParams);
+ break;
+ case SkiaHelper::RenderVulkan:
+ mWindowContext = sk_app::window_context_factory::MakeVulkanForWin(mWinParent.gethWnd(),
+ displayParams);
+ break;
+ }
+}
+
+void WinSkiaSalGraphicsImpl::DeInit()
+{
+ SkiaZone zone;
+ SkiaSalGraphicsImpl::DeInit();
+ mWindowContext.reset();
+}
+
+void WinSkiaSalGraphicsImpl::freeResources() {}
+
+void WinSkiaSalGraphicsImpl::performFlush()
+{
+ SkiaZone zone;
+ flushDrawing();
+ if (mWindowContext)
+ mWindowContext->swapBuffers();
+}
+
+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()));
+ 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()));
+ 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;
+}
+
+#ifdef SAL_LOG_INFO
+static 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_INFO, ::SAL_DETAIL_LOG_LEVEL_INFO, "vcl.skia",
+ 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
+
+sk_sp<SkTypeface> WinSkiaSalGraphicsImpl::createDirectWriteTypeface(const LOGFONTW& logFont)
+{
+ if (!dwriteDone)
+ {
+ if (SUCCEEDED(
+ CHECKHR(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory),
+ reinterpret_cast<IUnknown**>(&dwriteFactory)))))
+ {
+ if (SUCCEEDED(CHECKHR(dwriteFactory->GetGdiInterop(&dwriteGdiInterop))))
+ dwriteFontMgr = SkFontMgr_New_DirectWrite(dwriteFactory);
+ else
+ dwriteFactory->Release();
+ }
+ dwriteDone = true;
+ }
+ IDWriteFont* font = nullptr;
+ IDWriteFontFace* fontFace;
+ IDWriteFontFamily* fontFamily;
+ if (FAILED(CHECKHR(dwriteGdiInterop->CreateFontFromLOGFONT(&logFont, &font))))
+ return nullptr;
+ if (FAILED(CHECKHR(font->CreateFontFace(&fontFace))))
+ return nullptr;
+ if (FAILED(CHECKHR(font->GetFontFamily(&fontFamily))))
+ return nullptr;
+ return sk_sp<SkTypeface>(
+ SkCreateTypefaceDirectWrite(dwriteFontMgr, fontFace, font, fontFamily));
+}
+
+bool WinSkiaSalGraphicsImpl::DrawTextLayout(const GenericSalLayout& rLayout)
+{
+ const WinFontInstance& rWinFont = static_cast<const WinFontInstance&>(rLayout.GetFont());
+ float fHScale = rWinFont.getHScale();
+
+ assert(dynamic_cast<const WinFontInstance*>(&rLayout.GetFont()));
+ const WinFontInstance* pWinFont = static_cast<const WinFontInstance*>(&rLayout.GetFont());
+ const HFONT hLayoutFont = pWinFont->GetHFONT();
+ LOGFONTW logFont;
+ if (GetObjectW(hLayoutFont, sizeof(logFont), &logFont) == 0)
+ {
+ assert(false);
+ return false;
+ }
+ sk_sp<SkTypeface> typeface = createDirectWriteTypeface(logFont);
+ GlyphOrientation glyphOrientation = GlyphOrientation::Apply;
+ if (!typeface) // fall back to GDI text rendering
+ {
+ typeface.reset(SkCreateTypefaceFromLOGFONT(logFont));
+ glyphOrientation = GlyphOrientation::Ignore;
+ }
+ // lfHeight actually depends on DPI, so it's not really font height as such,
+ // but for LOGFONT-based typefaces Skia simply sets lfHeight back to this value
+ // directly.
+ double fontHeight = logFont.lfHeight;
+ if (fontHeight < 0)
+ fontHeight = -fontHeight;
+ SkFont font(typeface, fontHeight, fHScale, 0);
+ font.setEdging(getFontEdging());
+ 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));
+ // The font already is set up to have glyphs rotated as needed.
+ impl->drawGenericLayout(rLayout, salColor, font, glyphOrientation);
+ return true;
+}
+
+SkFont::Edging WinSkiaSalGraphicsImpl::getFontEdging()
+{
+ if (fontEdgingDone)
+ return fontEdging;
+ // 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;
+ SkFontLCDConfig::LCDOrder lcdOrder = SkFontLCDConfig::kNONE_LCDOrder;
+ 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)
+ lcdOrder = SkFontLCDConfig::kBGR_LCDOrder;
+ else
+ lcdOrder = SkFontLCDConfig::kRGB_LCDOrder; // default
+ }
+ else
+ fontEdging = SkFont::Edging::kAntiAlias;
+ }
+ SkFontLCDConfig::SetSubpixelOrder(lcdOrder);
+ // Cache this, it is actually visible a little bit when profiling.
+ fontEdgingDone = true;
+ return fontEdging;
+}
+
+void WinSkiaSalGraphicsImpl::ClearDevFontCache()
+{
+ dwriteFontMgr.reset();
+ dwriteDone = false;
+ fontEdgingDone = false;
+}
+
+SkiaCompatibleDC::SkiaCompatibleDC(SalGraphics& rGraphics, int x, int y, int width, int height)
+ : CompatibleDC(rGraphics, x, y, width, height, false)
+{
+}
+
+std::unique_ptr<CompatibleDC::Texture> SkiaCompatibleDC::getAsMaskTexture() const
+{
+ auto ret = std::make_unique<SkiaCompatibleDC::Texture>();
+ ret->image = getAsMaskImage();
+ return ret;
+}
+
+sk_sp<SkImage> SkiaCompatibleDC::getAsMaskImage() const
+{
+ SkiaZone zone;
+ // mpData is in the BGRA format, with A unused (and set to 0), and RGB are grey,
+ // so convert it to Skia format, then to 8bit and finally use as alpha mask
+ SkBitmap tmpBitmap;
+ if (!tmpBitmap.installPixels(SkImageInfo::Make(maRects.mnSrcWidth, maRects.mnSrcHeight,
+ kBGRA_8888_SkColorType, kOpaque_SkAlphaType),
+ mpData, maRects.mnSrcWidth * 4))
+ abort();
+ tmpBitmap.setImmutable();
+ SkBitmap bitmap8;
+ if (!bitmap8.tryAllocPixels(SkImageInfo::Make(maRects.mnSrcWidth, maRects.mnSrcHeight,
+ kGray_8_SkColorType, kOpaque_SkAlphaType)))
+ abort();
+ SkCanvas canvas8(bitmap8);
+ SkPaint paint8;
+ paint8.setBlendMode(SkBlendMode::kSrc); // copy and convert depth
+ // The data we got is upside-down.
+ SkMatrix matrix;
+ matrix.preTranslate(0, maRects.mnSrcHeight);
+ matrix.setConcat(matrix, SkMatrix::Scale(1, -1));
+ canvas8.concat(matrix);
+ canvas8.drawBitmap(tmpBitmap, 0, 0, &paint8);
+ bitmap8.setImmutable();
+ // use the 8bit data as an alpha channel
+ SkBitmap alpha;
+ alpha.setInfo(bitmap8.info().makeColorType(kAlpha_8_SkColorType), bitmap8.rowBytes());
+ alpha.setPixelRef(sk_ref_sp(bitmap8.pixelRef()), bitmap8.pixelRefOrigin().x(),
+ bitmap8.pixelRefOrigin().y());
+ alpha.setImmutable();
+ return SkiaHelper::createSkImage(alpha);
+}
+
+sk_sp<SkImage> SkiaCompatibleDC::getAsImage() const
+{
+ SkiaZone zone;
+ SkBitmap tmpBitmap;
+ if (!tmpBitmap.installPixels(SkImageInfo::Make(maRects.mnSrcWidth, maRects.mnSrcHeight,
+ kBGRA_8888_SkColorType, kUnpremul_SkAlphaType),
+ mpData, maRects.mnSrcWidth * 4))
+ abort();
+ tmpBitmap.setImmutable();
+ sk_sp<SkSurface> surface = SkiaHelper::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, maRects.mnSrcHeight);
+ matrix.setConcat(matrix, SkMatrix::Scale(1, -1));
+ canvas->concat(matrix);
+ canvas->drawBitmapRect(tmpBitmap,
+ SkRect::MakeXYWH(0, 0, maRects.mnSrcWidth, maRects.mnSrcHeight),
+ SkRect::MakeXYWH(0, 0, maRects.mnSrcWidth, maRects.mnSrcHeight), &paint);
+ canvas->restore();
+ return SkiaHelper::makeCheckedImageSnapshot(surface);
+}
+
+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)".
+ // TODO 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 = SkiaHelper::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->drawBitmap(tmpBitmap, 0, 0, &paint);
+ canvas->restore();
+ return SkiaHelper::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() { SkiaHelper::prepareSkia(createVulkanWindowContext); }
+
+/* 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 000000000..1602218c1
--- /dev/null
+++ b/vcl/skia/x11/gdiimpl.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/.
+ *
+ * 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>
+
+X11SkiaSalGraphicsImpl::X11SkiaSalGraphicsImpl(X11SalGraphics& rParent)
+ : SkiaSalGraphicsImpl(rParent, rParent.GetGeometryProvider())
+ , mX11Parent(rParent)
+{
+}
+
+X11SkiaSalGraphicsImpl::~X11SkiaSalGraphicsImpl() {}
+
+void X11SkiaSalGraphicsImpl::Init()
+{
+ // The m_pFrame and m_pVDev pointers are updated late in X11
+ setProvider(mX11Parent.GetGeometryProvider());
+ SkiaSalGraphicsImpl::Init();
+}
+
+void X11SkiaSalGraphicsImpl::createWindowContext(bool forceRaster)
+{
+ assert(mX11Parent.GetDrawable() != None);
+ mWindowContext = createWindowContext(
+ mX11Parent.GetXDisplay(), mX11Parent.GetDrawable(), &mX11Parent.GetVisual(), GetWidth(),
+ GetHeight(), forceRaster ? SkiaHelper::RenderRaster : SkiaHelper::renderMethodToUse(),
+ false);
+}
+
+std::unique_ptr<sk_app::WindowContext>
+X11SkiaSalGraphicsImpl::createWindowContext(Display* display, Drawable drawable,
+ const XVisualInfo* visual, int width, int height,
+ SkiaHelper::RenderMethod renderMethod, bool temporary)
+{
+ SkiaZone zone;
+ sk_app::DisplayParams displayParams;
+ displayParams.fColorType = kN32_SkColorType;
+ 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;
+#ifdef DBG_UTIL
+ // Our patched Skia has VulkanWindowContext that shares GrContext, 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
+ // checkDeviceBlacklisted() 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 SkiaHelper::RenderRaster:
+ // TODO The Skia Xlib code actually requires the non-native color type to work properly.
+ displayParams.fColorType
+ = (displayParams.fColorType == kBGRA_8888_SkColorType ? kRGBA_8888_SkColorType
+ : kBGRA_8888_SkColorType);
+ return sk_app::window_context_factory::MakeRasterForXlib(winInfo, displayParams);
+ case SkiaHelper::RenderVulkan:
+ return sk_app::window_context_factory::MakeVulkanForXlib(winInfo, displayParams);
+ }
+ abort();
+}
+
+bool X11SkiaSalGraphicsImpl::avoidRecreateByResize() const
+{
+ 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::DeInit()
+{
+ SkiaZone zone;
+ SkiaSalGraphicsImpl::DeInit();
+ mWindowContext.reset();
+}
+
+void X11SkiaSalGraphicsImpl::freeResources() {}
+
+void X11SkiaSalGraphicsImpl::performFlush()
+{
+ SkiaZone zone;
+ flushDrawing();
+ // TODO XPutImage() is somewhat inefficient, XShmPutImage() should be preferred.
+ mWindowContext->swapBuffers();
+}
+
+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, SkiaHelper::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 000000000..a4db9c75d
--- /dev/null
+++ b/vcl/skia/x11/salvd.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 <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();
+ m_pColormap = &pDisplay->GetColormap(m_nXScreen);
+
+ m_pVDev = pDevice;
+ m_pFrame = nullptr;
+
+ bWindow_ = pDisplay->IsDisplay();
+ bVirDev_ = true;
+
+ mxImpl->Init();
+}
+
+X11SkiaSalVirtualDevice::X11SkiaSalVirtualDevice(SalGraphics const* pGraphics, long nDX, 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 = pGraphics ? static_cast<X11SalGraphics const*>(pGraphics)->GetScreenNumber()
+ : vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetDefaultXScreen();
+ 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(long nDX, 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 000000000..13eff3012
--- /dev/null
+++ b/vcl/skia/x11/textrender.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 <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>
+
+void SkiaTextRender::DrawTextLayout(const GenericSalLayout& rLayout, const SalGraphics& rGraphics)
+{
+ const FreetypeFontInstance& rInstance = static_cast<FreetypeFontInstance&>(rLayout.GetFont());
+ const FreetypeFont& rFont = rInstance.GetFreetypeFont();
+ const FontSelectPattern& rFSD = rInstance.GetFontSelectPattern();
+ int nHeight = rFSD.mnHeight;
+ int nWidth = rFSD.mnWidth ? rFSD.mnWidth : nHeight;
+ if (nWidth == 0 || nHeight == 0)
+ return;
+
+ if (FreetypeFont::AlmostHorizontalDrainsRenderingPool(nWidth * 10 / nHeight, rFSD))
+ 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, nHeight);
+ // TODO are these correct?
+ if (rFont.NeedsArtificialItalic())
+ font.setSkewX(-0x6000L / 0x10000L);
+ if (rFont.NeedsArtificialBold())
+ font.setEmbolden(true);
+ font.setEdging(rFont.GetAntialiasAdvice() ? SkFont::Edging::kAntiAlias
+ : SkFont::Edging::kAlias);
+
+ assert(dynamic_cast<SkiaSalGraphicsImpl*>(rGraphics.GetImpl()));
+ SkiaSalGraphicsImpl* impl = static_cast<SkiaSalGraphicsImpl*>(rGraphics.GetImpl());
+ impl->drawGenericLayout(rLayout, mnTextColor, font,
+ SkiaSalGraphicsImpl::GlyphOrientation::Apply);
+}
+
+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 000000000..50f5e1ea7
--- /dev/null
+++ b/vcl/skia/zone.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 <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>
+
+/**
+ * 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)
+ {
+ 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 (SkiaHelper::renderMethodToUse())
+ {
+ case SkiaHelper::RenderVulkan:
+ {
+ static const CrashWatchdogTimingsValues vulkanValues = { 6, 20 }; /* 1.5s, 5s */
+ return vulkanValues;
+ }
+ case SkiaHelper::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/animate/Animation.cxx b/vcl/source/animate/Animation.cxx
new file mode 100644
index 000000000..d9eaed56c
--- /dev/null
+++ b/vcl/source/animate/Animation.cxx
@@ -0,0 +1,678 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <algorithm>
+#include <sal/config.h>
+
+#include <tools/stream.hxx>
+#include <tools/GenericTypeSerializer.hxx>
+#include <sal/log.hxx>
+
+#include <vcl/animate/Animation.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/dibtools.hxx>
+#include <vcl/BitmapColorQuantizationFilter.hxx>
+
+#include <impanmvw.hxx>
+
+sal_uLong Animation::mnAnimCount = 0;
+
+Animation::Animation()
+ : mnLoopCount(0)
+ , mnLoops(0)
+ , mnPos(0)
+ , mbIsInAnimation(false)
+ , mbLoopTerminated(false)
+{
+ maTimer.SetInvokeHandler(LINK(this, Animation, ImplTimeoutHdl));
+}
+
+Animation::Animation(const Animation& rAnimation)
+ : maBitmapEx(rAnimation.maBitmapEx)
+ , maGlobalSize(rAnimation.maGlobalSize)
+ , mnLoopCount(rAnimation.mnLoopCount)
+ , mnPos(rAnimation.mnPos)
+ , mbIsInAnimation(false)
+ , mbLoopTerminated(rAnimation.mbLoopTerminated)
+{
+ for (auto const& i : rAnimation.maList)
+ maList.emplace_back(new AnimationBitmap(*i));
+
+ maTimer.SetInvokeHandler(LINK(this, Animation, ImplTimeoutHdl));
+ mnLoops = mbLoopTerminated ? 0 : mnLoopCount;
+}
+
+Animation::~Animation()
+{
+ if (mbIsInAnimation)
+ Stop();
+}
+
+Animation& Animation::operator=(const Animation& rAnimation)
+{
+ if (this != &rAnimation)
+ {
+ Clear();
+
+ for (auto const& i : rAnimation.maList)
+ maList.emplace_back(new AnimationBitmap(*i));
+
+ maGlobalSize = rAnimation.maGlobalSize;
+ maBitmapEx = rAnimation.maBitmapEx;
+ mnLoopCount = rAnimation.mnLoopCount;
+ mnPos = rAnimation.mnPos;
+ mbLoopTerminated = rAnimation.mbLoopTerminated;
+ mnLoops = mbLoopTerminated ? 0 : mnLoopCount;
+ }
+ return *this;
+}
+
+bool Animation::operator==(const Animation& rAnimation) const
+{
+ return maList.size() == rAnimation.maList.size() && maBitmapEx == rAnimation.maBitmapEx
+ && maGlobalSize == rAnimation.maGlobalSize
+ && std::equal(maList.begin(), maList.end(), rAnimation.maList.begin(),
+ [](const std::unique_ptr<AnimationBitmap>& pAnim1,
+ const std::unique_ptr<AnimationBitmap>& pAnim2) -> bool {
+ return *pAnim1 == *pAnim2;
+ });
+}
+
+void Animation::Clear()
+{
+ maTimer.Stop();
+ mbIsInAnimation = false;
+ maGlobalSize = Size();
+ maBitmapEx.SetEmpty();
+ maList.clear();
+ maViewList.clear();
+}
+
+bool Animation::IsTransparent() const
+{
+ tools::Rectangle aRect{ Point(), maGlobalSize };
+
+ // If some small bitmap needs to be replaced by the background,
+ // we need to be transparent, in order to be displayed correctly
+ // as the application (?) does not invalidate on non-transparent
+ // graphics due to performance reasons.
+
+ return maBitmapEx.IsTransparent()
+ || std::any_of(maList.begin(), maList.end(),
+ [&aRect](const std::unique_ptr<AnimationBitmap>& pAnim) -> bool {
+ return pAnim->meDisposal == Disposal::Back
+ && tools::Rectangle{ pAnim->maPositionPixel,
+ pAnim->maSizePixel }
+ != aRect;
+ });
+}
+
+sal_uLong Animation::GetSizeBytes() const
+{
+ sal_uLong nSizeBytes = GetBitmapEx().GetSizeBytes();
+
+ for (auto const& pAnimationBitmap : maList)
+ {
+ nSizeBytes += pAnimationBitmap->maBitmapEx.GetSizeBytes();
+ }
+
+ return nSizeBytes;
+}
+
+BitmapChecksum Animation::GetChecksum() const
+{
+ SVBT32 aBT32;
+ BitmapChecksumOctetArray aBCOA;
+ BitmapChecksum nCrc = GetBitmapEx().GetChecksum();
+
+ UInt32ToSVBT32(maList.size(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(maGlobalSize.Width(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(maGlobalSize.Height(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ for (auto const& i : maList)
+ {
+ BCToBCOA(i->GetChecksum(), aBCOA);
+ nCrc = vcl_get_checksum(nCrc, aBCOA, BITMAP_CHECKSUM_SIZE);
+ }
+
+ return nCrc;
+}
+
+bool Animation::Start(OutputDevice* pOut, const Point& rDestPt, const Size& rDestSz,
+ long nExtraData, OutputDevice* pFirstFrameOutDev)
+{
+ bool bRet = false;
+
+ if (!maList.empty())
+ {
+ if ((pOut->GetOutDevType() == OUTDEV_WINDOW) && !mbLoopTerminated
+ && (ANIMATION_TIMEOUT_ON_CLICK != maList[mnPos]->mnWait))
+ {
+ bool differs = true;
+
+ auto itAnimView = std::find_if(
+ maViewList.begin(), maViewList.end(),
+ [pOut, nExtraData](const std::unique_ptr<ImplAnimView>& pAnimView) -> bool {
+ return pAnimView->matches(pOut, nExtraData);
+ });
+
+ if (itAnimView != maViewList.end())
+ {
+ if ((*itAnimView)->getOutPos() == rDestPt
+ && (*itAnimView)->getOutSizePix() == pOut->LogicToPixel(rDestSz))
+ {
+ (*itAnimView)->repaint();
+ differs = false;
+ }
+ else
+ maViewList.erase(itAnimView);
+ }
+
+ if (maViewList.empty())
+ {
+ maTimer.Stop();
+ mbIsInAnimation = false;
+ mnPos = 0;
+ }
+
+ if (differs)
+ maViewList.emplace_back(
+ new ImplAnimView(this, pOut, rDestPt, rDestSz, nExtraData, pFirstFrameOutDev));
+
+ if (!mbIsInAnimation)
+ {
+ ImplRestartTimer(maList[mnPos]->mnWait);
+ mbIsInAnimation = true;
+ }
+ }
+ else
+ Draw(pOut, rDestPt, rDestSz);
+
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+void Animation::Stop(OutputDevice* pOut, long nExtraData)
+{
+ maViewList.erase(std::remove_if(maViewList.begin(), maViewList.end(),
+ [=](const std::unique_ptr<ImplAnimView>& pAnimView) -> bool {
+ return pAnimView->matches(pOut, nExtraData);
+ }),
+ maViewList.end());
+
+ if (maViewList.empty())
+ {
+ maTimer.Stop();
+ mbIsInAnimation = false;
+ }
+}
+
+void Animation::Draw(OutputDevice* pOut, const Point& rDestPt) const
+{
+ Draw(pOut, rDestPt, pOut->PixelToLogic(maGlobalSize));
+}
+
+void Animation::Draw(OutputDevice* pOut, const Point& rDestPt, const Size& rDestSz) const
+{
+ const size_t nCount = maList.size();
+
+ if (nCount)
+ {
+ AnimationBitmap* pObj = maList[std::min(mnPos, nCount - 1)].get();
+
+ if (pOut->GetConnectMetaFile() || (pOut->GetOutDevType() == OUTDEV_PRINTER))
+ maList[0]->maBitmapEx.Draw(pOut, rDestPt, rDestSz);
+ else if (ANIMATION_TIMEOUT_ON_CLICK == pObj->mnWait)
+ pObj->maBitmapEx.Draw(pOut, rDestPt, rDestSz);
+ else
+ {
+ const size_t nOldPos = mnPos;
+ if (mbLoopTerminated)
+ const_cast<Animation*>(this)->mnPos = nCount - 1;
+
+ {
+ ImplAnimView{ const_cast<Animation*>(this), pOut, rDestPt, rDestSz, 0 };
+ }
+
+ const_cast<Animation*>(this)->mnPos = nOldPos;
+ }
+ }
+}
+
+namespace
+{
+constexpr sal_uLong constMinTimeout = 2;
+}
+
+void Animation::ImplRestartTimer(sal_uLong nTimeout)
+{
+ maTimer.SetTimeout(std::max(nTimeout, constMinTimeout) * 10);
+ maTimer.Start();
+}
+
+IMPL_LINK_NOARG(Animation, ImplTimeoutHdl, Timer*, void)
+{
+ const size_t nAnimCount = maList.size();
+
+ if (nAnimCount)
+ {
+ bool bGlobalPause = false;
+
+ if (maNotifyLink.IsSet())
+ {
+ std::vector<std::unique_ptr<AInfo>> aAInfoList;
+ // create AInfo-List
+ for (auto const& i : maViewList)
+ aAInfoList.emplace_back(i->createAInfo());
+
+ maNotifyLink.Call(this);
+
+ // set view state from AInfo structure
+ for (auto& pAInfo : aAInfoList)
+ {
+ ImplAnimView* pView = nullptr;
+ if (!pAInfo->pViewData)
+ {
+ pView = new ImplAnimView(this, pAInfo->pOutDev, pAInfo->aStartOrg,
+ pAInfo->aStartSize, pAInfo->nExtraData);
+
+ maViewList.push_back(std::unique_ptr<ImplAnimView>(pView));
+ }
+ else
+ pView = static_cast<ImplAnimView*>(pAInfo->pViewData);
+
+ pView->pause(pAInfo->bPause);
+ pView->setMarked(true);
+ }
+
+ // delete all unmarked views
+ auto removeStart = std::remove_if(maViewList.begin(), maViewList.end(),
+ [](const auto& pView) { return !pView->isMarked(); });
+ maViewList.erase(removeStart, maViewList.cend());
+
+ // check if every remaining view is paused
+ bGlobalPause = std::all_of(maViewList.cbegin(), maViewList.cend(),
+ [](const auto& pView) { return pView->isPause(); });
+
+ // reset marked state
+ std::for_each(maViewList.cbegin(), maViewList.cend(),
+ [](const auto& pView) { pView->setMarked(false); });
+ }
+
+ if (maViewList.empty())
+ Stop();
+ else if (bGlobalPause)
+ ImplRestartTimer(10);
+ else
+ {
+ AnimationBitmap* pStepBmp = (++mnPos < maList.size()) ? maList[mnPos].get() : nullptr;
+
+ if (!pStepBmp)
+ {
+ if (mnLoops == 1)
+ {
+ Stop();
+ mbLoopTerminated = true;
+ mnPos = nAnimCount - 1;
+ maBitmapEx = maList[mnPos]->maBitmapEx;
+ return;
+ }
+ else
+ {
+ if (mnLoops)
+ mnLoops--;
+
+ mnPos = 0;
+ pStepBmp = maList[mnPos].get();
+ }
+ }
+
+ // Paint all views.
+ std::for_each(maViewList.cbegin(), maViewList.cend(),
+ [this](const auto& pView) { pView->draw(mnPos); });
+ /*
+ * If a view is marked, remove the view, because
+ * area of output lies out of display area of window.
+ * Mark state is set from view itself.
+ */
+ auto removeStart = std::remove_if(maViewList.begin(), maViewList.end(),
+ [](const auto& pView) { return pView->isMarked(); });
+ maViewList.erase(removeStart, maViewList.cend());
+
+ // stop or restart timer
+ if (maViewList.empty())
+ Stop();
+ else
+ ImplRestartTimer(pStepBmp->mnWait);
+ }
+ }
+ else
+ Stop();
+}
+
+bool Animation::Insert(const AnimationBitmap& rStepBmp)
+{
+ bool bRet = false;
+
+ if (!IsInAnimation())
+ {
+ tools::Rectangle aGlobalRect(Point(), maGlobalSize);
+
+ maGlobalSize
+ = aGlobalRect.Union(tools::Rectangle(rStepBmp.maPositionPixel, rStepBmp.maSizePixel))
+ .GetSize();
+ maList.emplace_back(new AnimationBitmap(rStepBmp));
+
+ // As a start, we make the first BitmapEx the replacement BitmapEx
+ if (maList.size() == 1)
+ maBitmapEx = rStepBmp.maBitmapEx;
+
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+const AnimationBitmap& Animation::Get(sal_uInt16 nAnimation) const
+{
+ SAL_WARN_IF((nAnimation >= maList.size()), "vcl", "No object at this position");
+ return *maList[nAnimation];
+}
+
+void Animation::Replace(const AnimationBitmap& rNewAnimationBitmap, sal_uInt16 nAnimation)
+{
+ SAL_WARN_IF((nAnimation >= maList.size()), "vcl", "No object at this position");
+
+ maList[nAnimation].reset(new AnimationBitmap(rNewAnimationBitmap));
+
+ // If we insert at first position we also need to
+ // update the replacement BitmapEx
+ if ((!nAnimation && (!mbLoopTerminated || (maList.size() == 1)))
+ || ((nAnimation == maList.size() - 1) && mbLoopTerminated))
+ {
+ maBitmapEx = rNewAnimationBitmap.maBitmapEx;
+ }
+}
+
+void Animation::SetLoopCount(const sal_uInt32 nLoopCount)
+{
+ mnLoopCount = nLoopCount;
+ ResetLoopCount();
+}
+
+void Animation::ResetLoopCount()
+{
+ mnLoops = mnLoopCount;
+ mbLoopTerminated = false;
+}
+
+void Animation::Convert(BmpConversion eConversion)
+{
+ SAL_WARN_IF(IsInAnimation(), "vcl", "Animation modified while it is animated");
+
+ bool bRet;
+
+ if (!IsInAnimation() && !maList.empty())
+ {
+ bRet = true;
+
+ for (size_t i = 0, n = maList.size(); (i < n) && bRet; ++i)
+ bRet = maList[i]->maBitmapEx.Convert(eConversion);
+
+ maBitmapEx.Convert(eConversion);
+ }
+}
+
+bool Animation::ReduceColors(sal_uInt16 nNewColorCount)
+{
+ SAL_WARN_IF(IsInAnimation(), "vcl", "Animation modified while it is animated");
+
+ bool bRet;
+
+ if (!IsInAnimation() && !maList.empty())
+ {
+ bRet = true;
+
+ for (size_t i = 0, n = maList.size(); (i < n) && bRet; ++i)
+ {
+ bRet = BitmapFilter::Filter(maList[i]->maBitmapEx,
+ BitmapColorQuantizationFilter(nNewColorCount));
+ }
+
+ BitmapFilter::Filter(maBitmapEx, BitmapColorQuantizationFilter(nNewColorCount));
+ }
+ else
+ {
+ bRet = false;
+ }
+
+ return bRet;
+}
+
+bool Animation::Invert()
+{
+ SAL_WARN_IF(IsInAnimation(), "vcl", "Animation modified while it is animated");
+
+ bool bRet;
+
+ if (!IsInAnimation() && !maList.empty())
+ {
+ bRet = true;
+
+ for (size_t i = 0, n = maList.size(); (i < n) && bRet; ++i)
+ bRet = maList[i]->maBitmapEx.Invert();
+
+ maBitmapEx.Invert();
+ }
+ else
+ bRet = false;
+
+ return bRet;
+}
+
+void Animation::Mirror(BmpMirrorFlags nMirrorFlags)
+{
+ SAL_WARN_IF(IsInAnimation(), "vcl", "Animation modified while it is animated");
+
+ bool bRet;
+
+ if (!IsInAnimation() && !maList.empty())
+ {
+ bRet = true;
+
+ if (nMirrorFlags != BmpMirrorFlags::NONE)
+ {
+ for (size_t i = 0, n = maList.size(); (i < n) && bRet; ++i)
+ {
+ AnimationBitmap* pStepBmp = maList[i].get();
+ bRet = pStepBmp->maBitmapEx.Mirror(nMirrorFlags);
+ if (bRet)
+ {
+ if (nMirrorFlags & BmpMirrorFlags::Horizontal)
+ pStepBmp->maPositionPixel.setX(maGlobalSize.Width()
+ - pStepBmp->maPositionPixel.X()
+ - pStepBmp->maSizePixel.Width());
+
+ if (nMirrorFlags & BmpMirrorFlags::Vertical)
+ pStepBmp->maPositionPixel.setY(maGlobalSize.Height()
+ - pStepBmp->maPositionPixel.Y()
+ - pStepBmp->maSizePixel.Height());
+ }
+ }
+
+ maBitmapEx.Mirror(nMirrorFlags);
+ }
+ }
+}
+
+void Animation::Adjust(short nLuminancePercent, short nContrastPercent, short nChannelRPercent,
+ short nChannelGPercent, short nChannelBPercent, double fGamma, bool bInvert)
+{
+ SAL_WARN_IF(IsInAnimation(), "vcl", "Animation modified while it is animated");
+
+ bool bRet;
+
+ if (!IsInAnimation() && !maList.empty())
+ {
+ bRet = true;
+
+ for (size_t i = 0, n = maList.size(); (i < n) && bRet; ++i)
+ {
+ bRet = maList[i]->maBitmapEx.Adjust(nLuminancePercent, nContrastPercent,
+ nChannelRPercent, nChannelGPercent,
+ nChannelBPercent, fGamma, bInvert);
+ }
+
+ maBitmapEx.Adjust(nLuminancePercent, nContrastPercent, nChannelRPercent, nChannelGPercent,
+ nChannelBPercent, fGamma, bInvert);
+ }
+}
+
+SvStream& WriteAnimation(SvStream& rOStm, const Animation& rAnimation)
+{
+ const sal_uInt16 nCount = rAnimation.Count();
+
+ if (nCount)
+ {
+ const sal_uInt32 nDummy32 = 0;
+
+ // If no BitmapEx was set we write the first Bitmap of
+ // the Animation
+ if (!rAnimation.GetBitmapEx().GetBitmap())
+ WriteDIBBitmapEx(rAnimation.Get(0).maBitmapEx, rOStm);
+ else
+ WriteDIBBitmapEx(rAnimation.GetBitmapEx(), rOStm);
+
+ // Write identifier ( SDANIMA1 )
+ rOStm.WriteUInt32(0x5344414e).WriteUInt32(0x494d4931);
+
+ for (sal_uInt16 i = 0; i < nCount; i++)
+ {
+ const AnimationBitmap& rAnimationBitmap = rAnimation.Get(i);
+ const sal_uInt16 nRest = nCount - i - 1;
+
+ // Write AnimationBitmap
+ WriteDIBBitmapEx(rAnimationBitmap.maBitmapEx, rOStm);
+ tools::GenericTypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(rAnimationBitmap.maPositionPixel);
+ aSerializer.writeSize(rAnimationBitmap.maSizePixel);
+ aSerializer.writeSize(rAnimation.maGlobalSize);
+ rOStm.WriteUInt16((ANIMATION_TIMEOUT_ON_CLICK == rAnimationBitmap.mnWait)
+ ? 65535
+ : rAnimationBitmap.mnWait);
+ rOStm.WriteUInt16(static_cast<sal_uInt16>(rAnimationBitmap.meDisposal));
+ rOStm.WriteBool(rAnimationBitmap.mbUserInput);
+ rOStm.WriteUInt32(rAnimation.mnLoopCount);
+ rOStm.WriteUInt32(nDummy32); // Unused
+ rOStm.WriteUInt32(nDummy32); // Unused
+ rOStm.WriteUInt32(nDummy32); // Unused
+ write_uInt16_lenPrefixed_uInt8s_FromOString(rOStm, OString()); // dummy
+ rOStm.WriteUInt16(nRest); // Count of remaining structures
+ }
+ }
+
+ return rOStm;
+}
+
+SvStream& ReadAnimation(SvStream& rIStm, Animation& rAnimation)
+{
+ sal_uLong nStmPos;
+ sal_uInt32 nAnimMagic1, nAnimMagic2;
+ SvStreamEndian nOldFormat = rIStm.GetEndian();
+ bool bReadAnimations = false;
+
+ rIStm.SetEndian(SvStreamEndian::LITTLE);
+ nStmPos = rIStm.Tell();
+ rIStm.ReadUInt32(nAnimMagic1).ReadUInt32(nAnimMagic2);
+
+ rAnimation.Clear();
+
+ // If the BitmapEx at the beginning have already been read (by Graphic)
+ // we can start reading the AnimationBitmaps right away
+ if ((nAnimMagic1 == 0x5344414e) && (nAnimMagic2 == 0x494d4931) && !rIStm.GetError())
+ bReadAnimations = true;
+ // Else, we try reading the Bitmap(-Ex)
+ else
+ {
+ rIStm.Seek(nStmPos);
+ ReadDIBBitmapEx(rAnimation.maBitmapEx, rIStm);
+ nStmPos = rIStm.Tell();
+ rIStm.ReadUInt32(nAnimMagic1).ReadUInt32(nAnimMagic2);
+
+ if ((nAnimMagic1 == 0x5344414e) && (nAnimMagic2 == 0x494d4931) && !rIStm.GetError())
+ bReadAnimations = true;
+ else
+ rIStm.Seek(nStmPos);
+ }
+
+ // Read AnimationBitmaps
+ if (bReadAnimations)
+ {
+ AnimationBitmap aAnimationBitmap;
+ sal_uInt32 nTmp32;
+ sal_uInt16 nTmp16;
+ bool cTmp;
+
+ do
+ {
+ ReadDIBBitmapEx(aAnimationBitmap.maBitmapEx, rIStm);
+ tools::GenericTypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(aAnimationBitmap.maPositionPixel);
+ aSerializer.readSize(aAnimationBitmap.maSizePixel);
+ aSerializer.readSize(rAnimation.maGlobalSize);
+ rIStm.ReadUInt16(nTmp16);
+ aAnimationBitmap.mnWait = ((65535 == nTmp16) ? ANIMATION_TIMEOUT_ON_CLICK : nTmp16);
+ rIStm.ReadUInt16(nTmp16);
+ aAnimationBitmap.meDisposal = static_cast<Disposal>(nTmp16);
+ rIStm.ReadCharAsBool(cTmp);
+ aAnimationBitmap.mbUserInput = cTmp;
+ rIStm.ReadUInt32(rAnimation.mnLoopCount);
+ rIStm.ReadUInt32(nTmp32); // Unused
+ rIStm.ReadUInt32(nTmp32); // Unused
+ rIStm.ReadUInt32(nTmp32); // Unused
+ read_uInt16_lenPrefixed_uInt8s_ToOString(rIStm); // Unused
+ rIStm.ReadUInt16(nTmp16); // The rest to read
+
+ rAnimation.Insert(aAnimationBitmap);
+ } while (nTmp16 && !rIStm.GetError());
+
+ rAnimation.ResetLoopCount();
+ }
+
+ rIStm.SetEndian(nOldFormat);
+
+ return rIStm;
+}
+
+AInfo::AInfo()
+ : pOutDev(nullptr)
+ , pViewData(nullptr)
+ , nExtraData(0)
+ , bPause(false)
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/animate/AnimationBitmap.cxx b/vcl/source/animate/AnimationBitmap.cxx
new file mode 100644
index 000000000..7ebaf98d9
--- /dev/null
+++ b/vcl/source/animate/AnimationBitmap.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 <sal/config.h>
+#include <o3tl/underlyingenumvalue.hxx>
+#include <vcl/animate/AnimationBitmap.hxx>
+
+BitmapChecksum AnimationBitmap::GetChecksum() const
+{
+ BitmapChecksum nCrc = maBitmapEx.GetChecksum();
+ SVBT32 aBT32;
+
+ Int32ToSVBT32(maPositionPixel.X(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(maPositionPixel.Y(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(maSizePixel.Width(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(maSizePixel.Height(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(mnWait, aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ UInt32ToSVBT32(o3tl::underlyingEnumValue(meDisposal), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ UInt32ToSVBT32(sal_uInt32(mbUserInput), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ return nCrc;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/ITiledRenderable.cxx b/vcl/source/app/ITiledRenderable.cxx
new file mode 100644
index 000000000..e2a639dec
--- /dev/null
+++ b/vcl/source/app/ITiledRenderable.cxx
@@ -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/.
+ */
+
+#include <vcl/ITiledRenderable.hxx>
+
+namespace vcl
+{
+
+ /*
+ * Map directly to css cursor styles to avoid further mapping in the client.
+ * Gtk (via gdk_cursor_new_from_name) also supports the same css cursor styles.
+ *
+ * This was created partially with help of the mappings in gtkdata.cxx.
+ * The list is incomplete as some cursor style simply aren't supported
+ * by css, it might turn out to be worth mapping some of these missing cursors
+ * to available cursors?
+ */
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning( disable : 4592)
+#endif
+ const std::map <PointerStyle, OString> gaLOKPointerMap {
+ { PointerStyle::Arrow, "default" },
+ // PointerStyle::Null ?
+ { PointerStyle::Wait, "wait" },
+ { PointerStyle::Text, "text" },
+ { PointerStyle::Help, "help" },
+ { PointerStyle::Cross, "crosshair" },
+ { PointerStyle::Fill, "fill" },
+ { PointerStyle::Move, "move" },
+ { PointerStyle::NSize, "n-resize" },
+ { PointerStyle::SSize, "s-resize" },
+ { PointerStyle::WSize, "w-resize" },
+ { PointerStyle::ESize, "e-resize" },
+ { PointerStyle::NWSize, "ne-resize" },
+ { PointerStyle::NESize, "ne-resize" },
+ { PointerStyle::SWSize, "sw-resize" },
+ { PointerStyle::SESize, "se-resize" },
+ // WindowNSize through WindowSESize
+ { PointerStyle::HSplit, "col-resize" },
+ { PointerStyle::VSplit, "row-resize" },
+ { PointerStyle::HSizeBar, "col-resize" },
+ { PointerStyle::VSizeBar, "row-resize" },
+ { PointerStyle::Hand, "grab" },
+ { PointerStyle::RefHand, "pointer" },
+ // Pen, Magnify, Fill, Rotate
+ // HShear, VShear
+ // Mirror, Crook, Crop, MovePoint, MoveBezierWeight
+ // MoveData
+ { PointerStyle::CopyData, "copy" },
+ { PointerStyle::LinkData, "alias" },
+ // MoveDataLink, CopyDataLink
+ //MoveFile, CopyFile, LinkFile
+ // MoveFileLink, CopyFileLink, MoveFiless, CopyFiles
+ { PointerStyle::NotAllowed, "not-allowed" },
+ // DrawLine through DrawCaption
+ // Chart, Detective, PivotCol, PivotRow, PivotField, Chain, ChainNotAllowed
+ // TimeEventMove, TimeEventSize
+ // AutoScrollN through AutoScrollNSWE
+ // Airbrush
+ { PointerStyle::TextVertical, "vertical-text" }
+ // Pivot Delete, TabSelectS through TabSelectSW
+ // PaintBrush, HideWhiteSpace, ShowWhiteSpace
+ };
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+ITiledRenderable::~ITiledRenderable()
+{
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/IconThemeInfo.cxx b/vcl/source/app/IconThemeInfo.cxx
new file mode 100644
index 000000000..84d85883b
--- /dev/null
+++ b/vcl/source/app/IconThemeInfo.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 <vcl/IconThemeInfo.hxx>
+#include <rtl/character.hxx>
+
+#include <stdexcept>
+#include <algorithm>
+
+// constants for theme ids and display names. Only the theme id for high contrast is used
+// outside of this class and hence made public.
+
+const OUStringLiteral vcl::IconThemeInfo::HIGH_CONTRAST_ID("sifr");
+
+namespace {
+
+static const OUStringLiteral KARASA_JAGA_ID("karasa_jaga");
+static const OUStringLiteral KARASA_JAGA_DISPLAY_NAME("Karasa Jaga");
+static const OUStringLiteral HELPIMG_FAKE_THEME("helpimg");
+
+OUString
+filename_from_url(const OUString& url)
+{
+ sal_Int32 slashPosition = url.lastIndexOf( '/' );
+ if (slashPosition < 0) {
+ return OUString();
+ }
+ OUString filename = url.copy( slashPosition+1 );
+ return filename;
+}
+
+} // end anonymous namespace
+
+namespace vcl {
+
+static const char ICON_THEME_PACKAGE_PREFIX[] = "images_";
+
+static const char EXTENSION_FOR_ICON_PACKAGES[] = ".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(const OUString& themeName)
+{
+ if (themeName == "galaxy") { //kept for compiler because of unused parameter 'themeName'
+ return Size( 26, 26 );
+ }
+ else {
+ return Size( 24, 24 );
+ }
+}
+
+/*static*/ bool
+IconThemeInfo::UrlCanBeParsed(const OUString& 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(const OUString& filename)
+{
+ OUString r;
+ sal_Int32 positionOfLastDot = filename.lastIndexOf(EXTENSION_FOR_ICON_PACKAGES);
+ if (positionOfLastDot < 0) { // -1 means index not found
+ throw std::runtime_error("IconThemeInfo::FileNameToThemeId() called with invalid filename.");
+ }
+ sal_Int32 positionOfFirstUnderscore = filename.indexOf(ICON_THEME_PACKAGE_PREFIX);
+ if (positionOfFirstUnderscore < 0) { // -1 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.copy(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);
+
+ // special cases
+ if (aDisplayName.equalsIgnoreAsciiCase(KARASA_JAGA_ID)) {
+ aDisplayName = KARASA_JAGA_DISPLAY_NAME;
+ }
+ else
+ {
+ // make the first letter uppercase
+ sal_Unicode firstLetter = aDisplayName[0];
+ if (rtl::isAsciiLowerCase(firstLetter))
+ {
+ aDisplayName = OUStringChar(sal_Unicode(rtl::toAsciiUpperCase(firstLetter))) + aDisplayName.copy(1);
+ }
+ }
+
+ if (bIsSvg && bIsDark)
+ aDisplayName += " (SVG + dark)";
+ else if (bIsSvg)
+ aDisplayName += " (SVG)";
+ else if (bIsDark)
+ aDisplayName += " (dark)";
+
+ return aDisplayName;
+}
+
+namespace
+{
+ class SameTheme
+ {
+ private:
+ const OUString& m_rThemeId;
+ public:
+ explicit SameTheme(const OUString &rThemeId) : m_rThemeId(rThemeId) {}
+ bool operator()(const vcl::IconThemeInfo &rInfo)
+ {
+ return m_rThemeId == rInfo.GetThemeId();
+ }
+ };
+}
+
+/*static*/ const vcl::IconThemeInfo&
+IconThemeInfo::FindIconThemeById(const std::vector<vcl::IconThemeInfo>& themes, const OUString& themeId)
+{
+ std::vector<vcl::IconThemeInfo>::const_iterator it = std::find_if(themes.begin(), themes.end(),
+ SameTheme(themeId));
+ if (it == themes.end())
+ {
+ throw std::runtime_error("Could not find theme id in theme vector.");
+ }
+ return *it;
+}
+
+/*static*/ bool
+IconThemeInfo::IconThemeIsInVector(const std::vector<vcl::IconThemeInfo>& themes, const OUString& themeId)
+{
+ return std::any_of(themes.begin(), themes.end(), SameTheme(themeId));
+}
+
+} // end namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/IconThemeScanner.cxx b/vcl/source/app/IconThemeScanner.cxx
new file mode 100644
index 000000000..a9163af3c
--- /dev/null
+++ b/vcl/source/app/IconThemeScanner.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 <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>
+
+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(const OUString& paths)
+{
+ mFoundIconThemes.clear();
+
+ std::deque<OUString> aPaths;
+
+ sal_Int32 nIndex = 0;
+ do
+ {
+ aPaths.push_front(paths.getToken(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(const OUString &path)
+{
+ std::shared_ptr<IconThemeScanner> retval(new IconThemeScanner);
+ retval->ScanDirectoryForIconThemes(path);
+ return retval;
+}
+
+/*static*/ OUString
+IconThemeScanner::GetStandardIconThemePath()
+{
+ SvtPathOptions aPathOptions;
+ return aPathOptions.GetIconsetPath();
+}
+
+namespace
+{
+ class SameTheme
+ {
+ private:
+ const OUString& m_rThemeId;
+ public:
+ explicit SameTheme(const OUString &rThemeId) : m_rThemeId(rThemeId) {}
+ bool operator()(const vcl::IconThemeInfo &rInfo)
+ {
+ return m_rThemeId == rInfo.GetThemeId();
+ }
+ };
+}
+
+const vcl::IconThemeInfo&
+IconThemeScanner::GetIconThemeInfo(const OUString& themeId)
+{
+ std::vector<IconThemeInfo>::iterator info = std::find_if(mFoundIconThemes.begin(), mFoundIconThemes.end(),
+ SameTheme(themeId));
+ if (info == mFoundIconThemes.end()) {
+ SAL_WARN("vcl.app", "Requested information for icon theme with id '" << themeId
+ << "' which does not exist.");
+ throw std::runtime_error("Requested information on not-installed icon theme");
+ }
+ return *info;
+}
+
+} // end namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/IconThemeSelector.cxx b/vcl/source/app/IconThemeSelector.cxx
new file mode 100644
index 000000000..e98d63657
--- /dev/null
+++ b/vcl/source/app/IconThemeSelector.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 <comphelper/lok.hxx>
+
+#include <IconThemeSelector.hxx>
+
+#include <vcl/IconThemeInfo.hxx>
+#include <config_mpl.h>
+
+#include <algorithm>
+
+namespace vcl {
+
+/*static*/ const OUStringLiteral IconThemeSelector::FALLBACK_ICON_THEME_ID("colibre");
+
+namespace {
+
+ class SameTheme
+ {
+ private:
+ const OUString& m_rThemeId;
+ public:
+ explicit SameTheme(const OUString &rThemeId) : m_rThemeId(rThemeId) {}
+ bool operator()(const vcl::IconThemeInfo &rInfo)
+ {
+ return m_rThemeId == rInfo.GetThemeId();
+ }
+ };
+
+bool icon_theme_is_in_installed_themes(const OUString& theme,
+ const std::vector<IconThemeInfo>& installedThemes)
+{
+ return std::any_of(installedThemes.begin(), installedThemes.end(),
+ SameTheme(theme));
+}
+
+} // end anonymous namespace
+
+IconThemeSelector::IconThemeSelector()
+ : mUseHighContrastTheme(false)
+ , mPreferDarkIconTheme(false)
+{
+}
+
+/*static*/ OUString
+IconThemeSelector::GetIconThemeForDesktopEnvironment(const OUString& desktopEnvironment)
+{
+ if (comphelper::LibreOfficeKit::isActive())
+ return "colibre";
+
+#ifdef _WIN32
+ (void)desktopEnvironment;
+ return "colibre";
+#else
+ OUString r;
+ if ( desktopEnvironment.equalsIgnoreAsciiCase("plasma5") ||
+ desktopEnvironment.equalsIgnoreAsciiCase("lxqt") ) {
+ r = "breeze";
+ }
+ else if ( desktopEnvironment.equalsIgnoreAsciiCase("macosx") ) {
+ r = "sukapura";
+ }
+ else if ( desktopEnvironment.equalsIgnoreAsciiCase("gnome") ||
+ desktopEnvironment.equalsIgnoreAsciiCase("mate") ||
+ desktopEnvironment.equalsIgnoreAsciiCase("unity") ) {
+ r = "elementary";
+ } else
+ {
+ r = FALLBACK_ICON_THEME_ID;
+ }
+ return r;
+#endif // _WIN32
+}
+
+OUString
+IconThemeSelector::SelectIconThemeForDesktopEnvironment(
+ const std::vector<IconThemeInfo>& installedThemes,
+ const OUString& desktopEnvironment) const
+{
+ if (!mPreferredIconTheme.isEmpty()) {
+ if (icon_theme_is_in_installed_themes(mPreferredIconTheme, installedThemes)) {
+ return mPreferredIconTheme;
+ }
+ //if a dark variant is preferred, and we didn't have an exact match, then try our one and only dark theme
+ if (mPreferDarkIconTheme && icon_theme_is_in_installed_themes("breeze_dark", installedThemes)) {
+ return "breeze_dark";
+ }
+ }
+
+ OUString themeForDesktop = GetIconThemeForDesktopEnvironment(desktopEnvironment);
+ if (icon_theme_is_in_installed_themes(themeForDesktop, installedThemes)) {
+ return themeForDesktop;
+ }
+
+ return ReturnFallback(installedThemes);
+}
+
+OUString
+IconThemeSelector::SelectIconTheme(
+ const std::vector<IconThemeInfo>& installedThemes,
+ const OUString& theme) const
+{
+ if (mUseHighContrastTheme) {
+ if (icon_theme_is_in_installed_themes(IconThemeInfo::HIGH_CONTRAST_ID, installedThemes)) {
+ return IconThemeInfo::HIGH_CONTRAST_ID;
+ }
+ }
+
+ if (icon_theme_is_in_installed_themes(theme, installedThemes)) {
+ return theme;
+ }
+
+ return ReturnFallback(installedThemes);
+}
+
+void
+IconThemeSelector::SetUseHighContrastTheme(bool v)
+{
+ mUseHighContrastTheme = v;
+}
+
+void
+IconThemeSelector::SetPreferredIconTheme(const OUString& theme, bool bDarkIconTheme)
+{
+ // lower case theme name, and (tdf#120175) replace - with _
+ // see icon-themes/README
+ mPreferredIconTheme = theme.toAsciiLowerCase().replace('-','_');
+ mPreferDarkIconTheme = bDarkIconTheme;
+}
+
+bool
+IconThemeSelector::operator==(const vcl::IconThemeSelector& other) const
+{
+ if (this == &other) {
+ return true;
+ }
+ if (mPreferredIconTheme != other.mPreferredIconTheme) {
+ return false;
+ }
+ if (mPreferDarkIconTheme != other.mPreferDarkIconTheme) {
+ return false;
+ }
+ if (mUseHighContrastTheme != other.mUseHighContrastTheme) {
+ return false;
+ }
+ return true;
+}
+
+bool
+IconThemeSelector::operator!=(const vcl::IconThemeSelector& other) const
+{
+ return !(*this == other);
+}
+
+/*static*/ OUString
+IconThemeSelector::ReturnFallback(const std::vector<IconThemeInfo>& installedThemes)
+{
+ if (!installedThemes.empty()) {
+ return installedThemes.front().GetThemeId();
+ }
+ else {
+ return FALLBACK_ICON_THEME_ID;
+ }
+}
+
+} /* namespace vcl */
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/brand.cxx b/vcl/source/app/brand.cxx
new file mode 100644
index 000000000..290606106
--- /dev/null
+++ b/vcl/source/app/brand.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <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/pngread.hxx>
+#include <vcl/svapp.hxx>
+
+namespace {
+ bool loadPng( const OUString & rPath, BitmapEx &rBitmap)
+ {
+ INetURLObject aObj( rPath );
+ SvFileStream aStrm( aObj.PathToFileName(), StreamMode::STD_READ );
+ if ( !aStrm.GetError() ) {
+ vcl::PNGReader aReader( aStrm );
+ rBitmap = aReader.Read();
+ return !rBitmap.IsEmpty();
+ }
+ else
+ return false;
+ }
+ bool tryLoadPng( const OUString& rBaseDir, const OUString& rName, BitmapEx& rBitmap )
+ {
+ return loadPng( rBaseDir + "/" LIBO_ETC_FOLDER + rName, rBitmap);
+ }
+}
+
+bool Application::LoadBrandBitmap (const char* pName, BitmapEx &rBitmap)
+{
+ // TODO - if we want more flexibility we could add a branding path
+ // in an rc file perhaps fallback to "about.bmp"
+ OUString aBaseDir( "$BRAND_BASE_DIR");
+ rtl::Bootstrap::expandMacros( aBaseDir );
+ OUString aBaseName( "/" + OUString::createFromAscii( pName ) );
+ OUString aPng( ".png" );
+
+ rtl_Locale *pLoc = nullptr;
+ osl_getProcessLocale (&pLoc);
+ LanguageTag aLanguageTag( *pLoc);
+
+ ::std::vector< OUString > aFallbacks( aLanguageTag.getFallbackStrings( true));
+ for (const OUString & aFallback : aFallbacks)
+ {
+ if (tryLoadPng( aBaseDir, aBaseName + "-" + aFallback + aPng, rBitmap))
+ return true;
+ }
+
+ return tryLoadPng( aBaseDir, aBaseName + aPng, rBitmap);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/customweld.cxx b/vcl/source/app/customweld.cxx
new file mode 100644
index 000000000..c08a38208
--- /dev/null
+++ b/vcl/source/app/customweld.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 <vcl/customweld.hxx>
+
+namespace weld
+{
+CustomWidgetController::~CustomWidgetController() {}
+
+IMPL_LINK_NOARG(CustomWidgetController, DragBeginHdl, weld::DrawingArea&, bool)
+{
+ return StartDrag();
+}
+
+CustomWeld::CustomWeld(weld::Builder& rBuilder, const OString& rDrawingId,
+ CustomWidgetController& rWidgetController)
+ : m_rWidgetController(rWidgetController)
+ , m_xDrawingArea(rBuilder.weld_drawing_area(rDrawingId, rWidgetController.CreateAccessible(),
+ rWidgetController.GetUITestFactory(),
+ &rWidgetController))
+{
+ m_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_rWidgetController.SetDrawingArea(m_xDrawingArea.get());
+}
+
+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);
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/dbggui.cxx b/vcl/source/app/dbggui.cxx
new file mode 100644
index 000000000..cfeeeaf5d
--- /dev/null
+++ b/vcl/source/app/dbggui.cxx
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#ifndef NDEBUG
+
+#include <tools/debug.hxx>
+
+#include <svdata.hxx>
+#include <dbggui.hxx>
+
+#include <salinst.hxx>
+
+using namespace ::com::sun::star;
+
+static void ImplDbgTestSolarMutex()
+{
+ assert(ImplGetSVData()->mpDefInst->GetYieldMutex()->IsCurrentThread() && "SolarMutex not owned!");
+}
+
+void DbgGUIInitSolarMutexCheck()
+{
+ DbgSetTestSolarMutex( ImplDbgTestSolarMutex );
+}
+
+void DbgGUIDeInitSolarMutexCheck()
+{
+ DbgSetTestSolarMutex( nullptr );
+}
+
+#endif // NDEBUG
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/dndhelp.cxx b/vcl/source/app/dndhelp.cxx
new file mode 100644
index 000000000..ff4beb789
--- /dev/null
+++ b/vcl/source/app/dndhelp.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 <vcl/dndhelp.hxx>
+
+#include <cppuhelper/queryinterface.hxx>
+
+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& )
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/help.cxx b/vcl/source/app/help.cxx
new file mode 100644
index 000000000..ebe7588c5
--- /dev/null
+++ b/vcl/source/app/help.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 <comphelper/string.hxx>
+#include <sal/log.hxx>
+
+#include <tools/diagnose_ex.h>
+#include <tools/time.hxx>
+
+#include <vcl/window.hxx>
+#include <vcl/event.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/wrkwin.hxx>
+#include <vcl/help.hxx>
+#include <vcl/settings.hxx>
+
+#include <helpwin.hxx>
+#include <salframe.hxx>
+#include <svdata.hxx>
+
+#define HELPWINSTYLE_QUICK 0
+#define HELPWINSTYLE_BALLOON 1
+
+#define HELPTEXTMARGIN_QUICK 3
+#define HELPTEXTMARGIN_BALLOON 6
+
+#define HELPTEXTMAXLEN 150
+
+Help::Help()
+{
+}
+
+Help::~Help()
+{
+}
+
+bool Help::Start( const OUString&, const vcl::Window* )
+{
+ return false;
+}
+
+bool Help::Start(const OUString&, weld::Widget*)
+{
+ return false;
+}
+
+void Help::SearchKeyword( const OUString& )
+{
+}
+
+OUString Help::GetHelpText( const OUString&, const vcl::Window* )
+{
+ return OUString();
+}
+
+OUString Help::GetHelpText( const OUString&, const weld::Widget* )
+{
+ return OUString();
+}
+
+void Help::EnableContextHelp()
+{
+ ImplGetSVHelpData().mbContextHelp = true;
+}
+
+void Help::DisableContextHelp()
+{
+ ImplGetSVHelpData().mbContextHelp = false;
+}
+
+bool Help::IsContextHelpEnabled()
+{
+ return ImplGetSVHelpData().mbContextHelp;
+}
+
+void Help::EnableExtHelp()
+{
+ ImplGetSVHelpData().mbExtHelp = true;
+}
+
+void Help::DisableExtHelp()
+{
+ ImplGetSVHelpData().mbExtHelp = false;
+}
+
+bool Help::IsExtHelpEnabled()
+{
+ return ImplGetSVHelpData().mbExtHelp;
+}
+
+bool Help::StartExtHelp()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ ImplSVHelpData& aHelpData = ImplGetSVHelpData();
+
+ if ( aHelpData.mbExtHelp && !aHelpData.mbExtHelpMode )
+ {
+ aHelpData.mbExtHelpMode = true;
+ aHelpData.mbOldBalloonMode = aHelpData.mbBalloonHelp;
+ aHelpData.mbBalloonHelp = true;
+ if (pSVData->maFrameData.mpAppWin)
+ pSVData->maFrameData.mpAppWin->ImplGenerateMouseMove();
+ return true;
+ }
+
+ return false;
+}
+
+bool Help::EndExtHelp()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ ImplSVHelpData& aHelpData = ImplGetSVHelpData();
+
+ if ( aHelpData.mbExtHelp && aHelpData.mbExtHelpMode )
+ {
+ aHelpData.mbExtHelpMode = false;
+ aHelpData.mbBalloonHelp = aHelpData.mbOldBalloonMode;
+ if (pSVData->maFrameData.mpAppWin)
+ pSVData->maFrameData.mpAppWin->ImplGenerateMouseMove();
+ return true;
+ }
+
+ return false;
+}
+
+void Help::EnableBalloonHelp()
+{
+ ImplGetSVHelpData().mbBalloonHelp = true;
+}
+
+void Help::DisableBalloonHelp()
+{
+ ImplGetSVHelpData().mbBalloonHelp = false;
+}
+
+bool Help::IsBalloonHelpEnabled()
+{
+ return ImplGetSVHelpData().mbBalloonHelp;
+}
+
+void Help::ShowBalloon( vcl::Window* pParent,
+ const Point& rScreenPos, const tools::Rectangle& rRect,
+ const OUString& rHelpText )
+{
+ ImplShowHelpWindow( pParent, HELPWINSTYLE_BALLOON, QuickHelpFlags::NONE,
+ rHelpText, rScreenPos, rRect );
+}
+
+void Help::EnableQuickHelp()
+{
+ ImplGetSVHelpData().mbQuickHelp = true;
+}
+
+void Help::DisableQuickHelp()
+{
+ ImplGetSVHelpData().mbQuickHelp = false;
+}
+
+bool Help::IsQuickHelpEnabled()
+{
+ return ImplGetSVHelpData().mbQuickHelp;
+}
+
+void Help::ShowQuickHelp( vcl::Window* pParent,
+ const tools::Rectangle& rScreenRect,
+ const OUString& rHelpText,
+ QuickHelpFlags nStyle )
+{
+ sal_uInt16 nHelpWinStyle = ( nStyle & QuickHelpFlags::TipStyleBalloon ) ? HELPWINSTYLE_BALLOON : HELPWINSTYLE_QUICK;
+ ImplShowHelpWindow( pParent, nHelpWinStyle, nStyle,
+ rHelpText,
+ pParent->OutputToScreenPixel( pParent->GetPointerPosPixel() ), rScreenRect );
+}
+
+void Help::HideBalloonAndQuickHelp()
+{
+ HelpTextWindow const * pHelpWin = ImplGetSVHelpData().mpHelpWin;
+ bool const bIsVisible = ( pHelpWin != nullptr ) && pHelpWin->IsVisible();
+ ImplDestroyHelpWindow( bIsVisible );
+}
+
+void* Help::ShowPopover(vcl::Window* pParent, const tools::Rectangle& rScreenRect,
+ const OUString& rText, QuickHelpFlags nStyle)
+{
+ void* nId = pParent->ImplGetFrame()->ShowPopover(rText, pParent, rScreenRect, nStyle);
+ if (nId)
+ {
+ //popovers are handled natively, return early
+ return nId;
+ }
+
+ sal_uInt16 nHelpWinStyle = ( nStyle & QuickHelpFlags::TipStyleBalloon ) ? HELPWINSTYLE_BALLOON : HELPWINSTYLE_QUICK;
+ VclPtrInstance<HelpTextWindow> pHelpWin( pParent, rText, nHelpWinStyle, nStyle );
+
+ nId = pHelpWin.get();
+ UpdatePopover(nId, pParent, rScreenRect, rText);
+
+ pHelpWin->ShowHelp(true);
+ return nId;
+}
+
+void Help::UpdatePopover(void* nId, vcl::Window* pParent, const tools::Rectangle& rScreenRect,
+ const OUString& rText)
+{
+ if (pParent->ImplGetFrame()->UpdatePopover(nId, rText, pParent, rScreenRect))
+ {
+ //popovers are handled natively, return early
+ return;
+ }
+
+ HelpTextWindow* pHelpWin = static_cast< HelpTextWindow* >( nId );
+ ENSURE_OR_RETURN_VOID( pHelpWin != nullptr, "Help::UpdatePopover: invalid ID!" );
+
+ Size aSz = pHelpWin->CalcOutSize();
+ pHelpWin->SetOutputSizePixel( aSz );
+ ImplSetHelpWindowPos( pHelpWin, pHelpWin->GetWinStyle(), pHelpWin->GetStyle(),
+ pParent->OutputToScreenPixel( pParent->GetPointerPosPixel() ), rScreenRect );
+
+ pHelpWin->SetHelpText( rText );
+ pHelpWin->Invalidate();
+}
+
+void Help::HidePopover(vcl::Window const * pParent, void* nId)
+{
+ if (pParent->ImplGetFrame()->HidePopover(nId))
+ {
+ //popovers are handled natively, return early
+ return;
+ }
+
+ VclPtr<HelpTextWindow> pHelpWin = static_cast<HelpTextWindow*>(nId);
+ vcl::Window* pFrameWindow = pHelpWin->ImplGetFrameWindow();
+ pHelpWin->Hide();
+ // trigger update, so that a Paint is instantly triggered since we do not save the background
+ pFrameWindow->ImplUpdateAll();
+ pHelpWin.disposeAndClear();
+ ImplGetSVHelpData().mnLastHelpHideTime = tools::Time::GetSystemTicks();
+}
+
+HelpTextWindow::HelpTextWindow( vcl::Window* pParent, const OUString& rText, sal_uInt16 nHelpWinStyle, QuickHelpFlags nStyle ) :
+ FloatingWindow( pParent, WB_SYSTEMWINDOW|WB_TOOLTIPWIN ), // #105827# if we change the parent, mirroring will not work correctly when positioning this window
+ maHelpText( rText )
+{
+ SetType( WindowType::HELPTEXTWINDOW );
+ ImplSetMouseTransparent( true );
+ mnHelpWinStyle = nHelpWinStyle;
+ mnStyle = nStyle;
+
+ if( mnStyle & QuickHelpFlags::BiDiRtl )
+ {
+ ComplexTextLayoutFlags nLayoutMode = GetLayoutMode();
+ nLayoutMode |= ComplexTextLayoutFlags::BiDiRtl | ComplexTextLayoutFlags::TextOriginLeft;
+ SetLayoutMode( nLayoutMode );
+ }
+ SetHelpText( rText );
+ Window::SetHelpText( rText );
+
+ if ( ImplGetSVHelpData().mbSetKeyboardHelp )
+ ImplGetSVHelpData().mbKeyboardHelp = true;
+
+
+ maShowTimer.SetInvokeHandler( LINK( this, HelpTextWindow, TimerHdl ) );
+ maShowTimer.SetDebugName( "vcl::HelpTextWindow maShowTimer" );
+
+ const HelpSettings& rHelpSettings = pParent->GetSettings().GetHelpSettings();
+ maHideTimer.SetTimeout( rHelpSettings.GetTipTimeout() );
+ maHideTimer.SetInvokeHandler( LINK( this, HelpTextWindow, TimerHdl ) );
+ maHideTimer.SetDebugName( "vcl::HelpTextWindow maHideTimer" );
+}
+
+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(*this);
+ if ( mnHelpWinStyle == HELPWINSTYLE_QUICK && maHelpText.getLength() < HELPTEXTMAXLEN && maHelpText.indexOf('\n') < 0)
+ {
+ Size aSize;
+ aSize.setHeight( GetTextHeight() );
+ if ( mnStyle & QuickHelpFlags::CtrlText )
+ aSize.setWidth( 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;
+ comphelper::string::padToLength(aBuf, nCharsInLine, 'x');
+ OUString aXXX = aBuf.makeStringAndClear();
+ long nWidth = GetTextWidth( aXXX );
+ Size aTmpSize( nWidth, 0x7FFFFFFF );
+ tools::Rectangle aTry1( Point(), aTmpSize );
+ DrawTextFlags nDrawFlags = DrawTextFlags::MultiLine | DrawTextFlags::WordBreak |
+ DrawTextFlags::Left | DrawTextFlags::Top;
+ if ( mnStyle & QuickHelpFlags::CtrlText )
+ nDrawFlags |= DrawTextFlags::Mnemonic;
+ tools::Rectangle aTextRect = GetTextRect( aTry1, maHelpText, nDrawFlags );
+
+ // get a better width later...
+ maTextRect = aTextRect;
+
+ // safety distance...
+ maTextRect.SetPos( Point( HELPTEXTMARGIN_BALLOON, HELPTEXTMARGIN_BALLOON ) );
+ }
+
+ Size aSize( CalcOutSize() );
+ SetOutputSizePixel( aSize );
+}
+
+void HelpTextWindow::ImplShow()
+{
+ VclPtr<HelpTextWindow> xWindow( this );
+ Show( true, ShowFlags::NoActivate );
+ if( !xWindow->IsDisposed() )
+ PaintImmediately();
+}
+
+void HelpTextWindow::Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& )
+{
+ // paint native background
+ bool bNativeOK = false;
+ if (rRenderContext.IsNativeControlSupported(ControlType::Tooltip, ControlPart::Entire))
+ {
+ tools::Rectangle aCtrlRegion(Point(0, 0), GetOutputSizePixel());
+ ImplControlValue aControlValue;
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::Tooltip, ControlPart::Entire, aCtrlRegion,
+ ControlState::NONE, aControlValue, OUString());
+ }
+
+ // paint text
+ if (mnHelpWinStyle == HELPWINSTYLE_QUICK && maHelpText.getLength() < HELPTEXTMAXLEN && maHelpText.indexOf('\n') < 0)
+ {
+ if ( mnStyle & QuickHelpFlags::CtrlText )
+ rRenderContext.DrawCtrlText(maTextRect.TopLeft(), maHelpText);
+ else
+ rRenderContext.DrawText(maTextRect.TopLeft(), maHelpText);
+ }
+ else // HELPWINSTYLE_BALLOON
+ {
+ DrawTextFlags nDrawFlags = DrawTextFlags::MultiLine|DrawTextFlags::WordBreak|
+ DrawTextFlags::Left|DrawTextFlags::Top;
+ if (mnStyle & QuickHelpFlags::CtrlText)
+ nDrawFlags |= DrawTextFlags::Mnemonic;
+ rRenderContext.DrawText(maTextRect, maHelpText, nDrawFlags);
+ }
+
+ // border
+ if (!bNativeOK)
+ {
+ Size aSz = GetOutputSizePixel();
+ rRenderContext.DrawRect(tools::Rectangle(Point(), aSz));
+ if (mnHelpWinStyle == HELPWINSTYLE_BALLOON)
+ {
+ aSz.AdjustWidth( -2 );
+ aSz.AdjustHeight( -2 );
+ Color aColor(rRenderContext.GetLineColor());
+ rRenderContext.SetLineColor(COL_GRAY);
+ rRenderContext.DrawRect(tools::Rectangle(Point(1, 1), aSz));
+ rRenderContext.SetLineColor(aColor);
+ }
+ }
+}
+
+void HelpTextWindow::ShowHelp(bool bNoDelay)
+{
+ sal_uLong nTimeout = 0;
+ if (!bNoDelay)
+ {
+ // In case of ExtendedHelp display help sooner
+ if ( ImplGetSVHelpData().mbExtHelpMode )
+ nTimeout = 15;
+ else
+ {
+ if ( mnHelpWinStyle == HELPWINSTYLE_QUICK )
+ nTimeout = HelpSettings::GetTipDelay();
+ else
+ nTimeout = HelpSettings::GetBalloonDelay();
+ }
+ }
+
+ maShowTimer.SetTimeout( nTimeout );
+ maShowTimer.Start();
+}
+
+IMPL_LINK( HelpTextWindow, TimerHdl, Timer*, pTimer, void)
+{
+ if ( pTimer == &maShowTimer )
+ {
+ if ( mnHelpWinStyle == HELPWINSTYLE_QUICK )
+ {
+ // start auto-hide-timer for non-ShowTip windows
+ if ( this == ImplGetSVHelpData().mpHelpWin )
+ maHideTimer.Start();
+ }
+ ImplShow();
+ }
+ else
+ {
+ SAL_WARN_IF( pTimer != &maHideTimer, "vcl", "HelpTextWindow::TimerHdl with bad Timer" );
+ ImplDestroyHelpWindow( true );
+ }
+}
+
+Size HelpTextWindow::CalcOutSize() const
+{
+ Size aSz = maTextRect.GetSize();
+ aSz.AdjustWidth(2*maTextRect.Left() );
+ aSz.AdjustHeight(2*maTextRect.Top() );
+ return aSz;
+}
+
+void HelpTextWindow::RequestHelp( const HelpEvent& /*rHEvt*/ )
+{
+ // Just to assure that Window::RequestHelp() is not called by
+ // ShowQuickHelp/ShowBalloonHelp in the HelpTextWindow.
+}
+
+OUString HelpTextWindow::GetText() const
+{
+ return maHelpText;
+}
+
+void ImplShowHelpWindow( vcl::Window* pParent, sal_uInt16 nHelpWinStyle, QuickHelpFlags nStyle,
+ const OUString& rHelpText,
+ const Point& rScreenPos, const tools::Rectangle& rHelpArea )
+{
+ if (pParent->ImplGetFrame()->ShowTooltip(rHelpText, rHelpArea))
+ {
+ //tooltips are handled natively, return early
+ return;
+ }
+
+ ImplSVHelpData& aHelpData = ImplGetSVHelpData();
+
+ if (rHelpText.isEmpty() && !aHelpData.mbRequestingHelp)
+ return;
+
+ VclPtr<HelpTextWindow> pHelpWin = aHelpData.mpHelpWin;
+ bool bNoDelay = false;
+ if ( pHelpWin )
+ {
+ SAL_WARN_IF( pHelpWin == pParent, "vcl", "HelpInHelp ?!" );
+
+ if ( ( rHelpText.isEmpty()
+ || ( pHelpWin->GetWinStyle() != nHelpWinStyle )
+ )
+ && aHelpData.mbRequestingHelp
+ )
+ {
+ // remove help window if no HelpText or
+ // other help mode. but keep it if we are scrolling, ie not requesting help
+ bool bWasVisible = pHelpWin->IsVisible();
+ if ( bWasVisible )
+ bNoDelay = true; // display it quickly if we were already in quick help mode
+ pHelpWin = nullptr;
+ ImplDestroyHelpWindow( bWasVisible );
+ }
+ else
+ {
+ 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();
+ }
+ }
+ }
+
+ if (pHelpWin || rHelpText.isEmpty())
+ return;
+
+ sal_uInt64 nCurTime = tools::Time::GetSystemTicks();
+ if ( ( nCurTime - aHelpData.mnLastHelpHideTime ) < HelpSettings::GetTipDelay() )
+ bNoDelay = true;
+
+ pHelpWin = VclPtr<HelpTextWindow>::Create( pParent, rHelpText, nHelpWinStyle, nStyle );
+ aHelpData.mpHelpWin = pHelpWin;
+ pHelpWin->SetHelpArea( rHelpArea );
+
+ // positioning
+ Size aSz = pHelpWin->CalcOutSize();
+ pHelpWin->SetOutputSizePixel( aSz );
+ ImplSetHelpWindowPos( pHelpWin, nHelpWinStyle, nStyle, rScreenPos, rHelpArea );
+ // if not called from Window::RequestHelp, then without delay...
+ if ( !aHelpData.mbRequestingHelp )
+ bNoDelay = true;
+ pHelpWin->ShowHelp(bNoDelay);
+
+}
+
+void ImplDestroyHelpWindow( bool bUpdateHideTime )
+{
+ ImplDestroyHelpWindow(ImplGetSVHelpData(), bUpdateHideTime);
+}
+
+void ImplDestroyHelpWindow(ImplSVHelpData& rHelpData, bool bUpdateHideTime)
+{
+ VclPtr<HelpTextWindow> pHelpWin = rHelpData.mpHelpWin;
+ if( pHelpWin )
+ {
+ rHelpData.mpHelpWin = nullptr;
+ rHelpData.mbKeyboardHelp = false;
+ pHelpWin->Hide();
+ pHelpWin.disposeAndClear();
+ if( bUpdateHideTime )
+ rHelpData.mnLastHelpHideTime = tools::Time::GetSystemTicks();
+ }
+}
+
+void ImplSetHelpWindowPos( vcl::Window* pHelpWin, sal_uInt16 nHelpWinStyle, QuickHelpFlags nStyle,
+ const Point& rPos, const tools::Rectangle& rHelpArea )
+{
+ Point aPos = rPos;
+ Size aSz = pHelpWin->GetSizePixel();
+ tools::Rectangle aScreenRect = pHelpWin->ImplGetFrameWindow()->GetDesktopRectPixel();
+ aPos = pHelpWin->GetParent()->ImplGetFrameWindow()->OutputToAbsoluteScreenPixel( aPos );
+ // get mouse screen coords
+ Point aMousePos( pHelpWin->GetParent()->ImplGetFrameWindow()->GetPointerPosPixel() );
+ aMousePos = pHelpWin->GetParent()->ImplGetFrameWindow()->OutputToAbsoluteScreenPixel( aMousePos );
+
+ if ( nHelpWinStyle == HELPWINSTYLE_QUICK )
+ {
+ if ( !(nStyle & QuickHelpFlags::NoAutoPos) )
+ {
+ long nScreenHeight = aScreenRect.GetHeight();
+ aPos.AdjustX( -4 );
+ if ( aPos.Y() > aScreenRect.Top()+nScreenHeight-(nScreenHeight/4) )
+ aPos.AdjustY( -(aSz.Height()+4) );
+ else
+ aPos.AdjustY(21 );
+ }
+ }
+ else
+ {
+ // If it's the mouse position, move the window slightly
+ // so the mouse pointer does not cover it
+ if ( aPos == aMousePos )
+ {
+ aPos.AdjustX(12 );
+ aPos.AdjustY(16 );
+ }
+ }
+
+ if ( nStyle & QuickHelpFlags::NoAutoPos )
+ {
+ // convert help area to screen coords
+ tools::Rectangle devHelpArea(
+ pHelpWin->GetParent()->ImplGetFrameWindow()->OutputToAbsoluteScreenPixel( rHelpArea.TopLeft() ),
+ pHelpWin->GetParent()->ImplGetFrameWindow()->OutputToAbsoluteScreenPixel( rHelpArea.BottomRight() ) );
+
+ // which position of the rectangle?
+ aPos = devHelpArea.Center();
+
+ if ( nStyle & QuickHelpFlags::Left )
+ aPos.setX( devHelpArea.Left() );
+ else if ( nStyle & QuickHelpFlags::Right )
+ aPos.setX( devHelpArea.Right() );
+
+ if ( nStyle & QuickHelpFlags::Top )
+ aPos.setY( devHelpArea.Top() );
+ else if ( nStyle & QuickHelpFlags::Bottom )
+ aPos.setY( devHelpArea.Bottom() );
+
+ // which direction?
+ if ( nStyle & QuickHelpFlags::Left )
+ ;
+ else if ( nStyle & QuickHelpFlags::Right )
+ aPos.AdjustX( -(aSz.Width()) );
+ else
+ aPos.AdjustX( -(aSz.Width()/2) );
+
+ if ( nStyle & QuickHelpFlags::Top )
+ ;
+ else if ( nStyle & QuickHelpFlags::Bottom )
+ aPos.AdjustY( -(aSz.Height()) );
+ else
+ aPos.AdjustY( -(aSz.Height()/2) );
+ }
+
+ if ( aPos.X() < aScreenRect.Left() )
+ aPos.setX( aScreenRect.Left() );
+ else if ( ( aPos.X() + aSz.Width() ) > aScreenRect.Right() )
+ aPos.setX( aScreenRect.Right() - aSz.Width() );
+ if ( aPos.Y() < aScreenRect.Top() )
+ aPos.setY( aScreenRect.Top() );
+ else if ( ( aPos.Y() + aSz.Height() ) > aScreenRect.Bottom() )
+ aPos.setY( aScreenRect.Bottom() - aSz.Height() );
+
+ if( ! (nStyle & QuickHelpFlags::NoEvadePointer) )
+ {
+ /* the remark below should be obsolete by now as the helpwindow should
+ not be focusable, leaving it as a hint. However it is sensible in most
+ conditions to evade the mouse pointer so the content window is fully visible.
+
+ // the popup must not appear under the mouse
+ // otherwise it would directly be closed due to a focus change...
+ */
+ tools::Rectangle aHelpRect( aPos, aSz );
+ if( aHelpRect.IsInside( aMousePos ) )
+ {
+ Point delta(2,2);
+ Point aSize( aSz.Width(), aSz.Height() );
+ Point aTest( aMousePos - aSize - delta );
+ if( aTest.X() > aScreenRect.Left() && aTest.Y() > aScreenRect.Top() )
+ aPos = aTest;
+ else
+ aPos = aMousePos + delta;
+ }
+ }
+
+ vcl::Window* pWindow = pHelpWin->GetParent()->ImplGetFrameWindow();
+ aPos = pWindow->AbsoluteScreenToOutputPixel( aPos );
+ pHelpWin->SetPosPixel( aPos );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/i18nhelp.cxx b/vcl/source/app/i18nhelp.cxx
new file mode 100644
index 000000000..ae7eed0e2
--- /dev/null
+++ b/vcl/source/app/i18nhelp.cxx
@@ -0,0 +1,160 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <unotools/localedatawrapper.hxx>
+#include <unotools/transliterationwrapper.hxx>
+
+#include <i18nlangtag/languagetag.hxx>
+#include <i18nutil/transliteration.hxx>
+
+#include <rtl/ustrbuf.hxx>
+
+#include <vcl/i18nhelp.hxx>
+
+using namespace ::com::sun::star;
+
+vcl::I18nHelper::I18nHelper( const css::uno::Reference< css::uno::XComponentContext >& rxContext, const LanguageTag& rLanguageTag )
+ :
+ maLanguageTag( rLanguageTag)
+{
+ m_xContext = rxContext;
+ mpLocaleDataWrapper = nullptr;
+ mpTransliterationWrapper= nullptr;
+ mbTransliterateIgnoreCase = false;
+}
+
+vcl::I18nHelper::~I18nHelper()
+{
+ ImplDestroyWrappers();
+}
+
+void vcl::I18nHelper::ImplDestroyWrappers()
+{
+ mpLocaleDataWrapper.reset();
+ mpTransliterationWrapper.reset();
+}
+
+utl::TransliterationWrapper& vcl::I18nHelper::ImplGetTransliterationWrapper() const
+{
+ if ( !mpTransliterationWrapper )
+ {
+ TransliterationFlags nModules = TransliterationFlags::IGNORE_WIDTH;
+ if ( mbTransliterateIgnoreCase )
+ nModules |= TransliterationFlags::IGNORE_CASE;
+
+ const_cast<vcl::I18nHelper*>(this)->mpTransliterationWrapper.reset(new utl::TransliterationWrapper( m_xContext, nModules ));
+ const_cast<vcl::I18nHelper*>(this)->mpTransliterationWrapper->loadModuleIfNeeded( maLanguageTag.getLanguageType() );
+ }
+ return *mpTransliterationWrapper;
+}
+
+LocaleDataWrapper& vcl::I18nHelper::ImplGetLocaleDataWrapper() const
+{
+ if ( !mpLocaleDataWrapper )
+ {
+ const_cast<vcl::I18nHelper*>(this)->mpLocaleDataWrapper.reset(new LocaleDataWrapper( m_xContext, maLanguageTag ));
+ }
+ return *mpLocaleDataWrapper;
+}
+
+static bool is_formatting_mark( sal_Unicode c )
+{
+ if( (c >= 0x200B) && (c <= 0x200F) ) // BiDi and zero-width-markers
+ return true;
+ if( (c >= 0x2028) && (c <= 0x202E) ) // BiDi and paragraph-markers
+ return true;
+ return false;
+}
+
+/* #i100057# filter formatting marks out of strings before passing them to
+ the transliteration. The real solution would have been an additional TransliterationModule
+ to ignore these marks during transliteration; however changing the code in i18npool that actually
+ implements this could produce unwanted side effects.
+
+ Of course this copying around is not really good, but looking at i18npool, one more time
+ will not hurt.
+*/
+OUString vcl::I18nHelper::filterFormattingChars( const OUString& rStr )
+{
+ sal_Int32 nLength = rStr.getLength();
+ OUStringBuffer aBuf( nLength );
+ const sal_Unicode* pStr = rStr.getStr();
+ while( nLength-- )
+ {
+ if( ! is_formatting_mark( *pStr ) )
+ aBuf.append( *pStr );
+ pStr++;
+ }
+ return aBuf.makeStringAndClear();
+}
+
+sal_Int32 vcl::I18nHelper::CompareString( const OUString& rStr1, const OUString& rStr2 ) const
+{
+ ::osl::Guard< ::osl::Mutex > aGuard( const_cast<vcl::I18nHelper*>(this)->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
+{
+ ::osl::Guard< ::osl::Mutex > aGuard( const_cast<vcl::I18nHelper*>(this)->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( const OUString& rString, sal_Unicode cMnemonicChar ) const
+{
+ ::osl::Guard< ::osl::Mutex > aGuard( const_cast<vcl::I18nHelper*>(this)->maMutex );
+
+ bool bEqual = false;
+ sal_Int32 n = rString.indexOf( '~' );
+ if ( n != -1 )
+ {
+ OUString aMatchStr = rString.copy( n+1 ); // not only one char, because of transliteration...
+ bEqual = MatchString( OUString(cMnemonicChar), aMatchStr );
+ }
+ return bEqual;
+}
+
+OUString vcl::I18nHelper::GetNum( long nNumber, sal_uInt16 nDecimals, bool bUseThousandSep, bool bTrailingZeros ) const
+{
+ return ImplGetLocaleDataWrapper().getNum( nNumber, nDecimals, bUseThousandSep, bTrailingZeros );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/idle.cxx b/vcl/source/app/idle.cxx
new file mode 100644
index 000000000..26e1e5f22
--- /dev/null
+++ b/vcl/source/app/idle.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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()
+{
+ Task::Start();
+
+ sal_uInt64 nPeriod = Scheduler::ImmediateTimeoutMs;
+ if (Scheduler::GetDeterministicMode())
+ {
+ switch ( GetPriority() )
+ {
+ case TaskPriority::DEFAULT_IDLE:
+ case TaskPriority::LOWEST:
+ nPeriod = Scheduler::InfiniteTimeoutMs;
+ break;
+ default:
+ break;
+ }
+ }
+
+ Task::StartTimer(nPeriod);
+}
+
+sal_uInt64 Idle::UpdateMinPeriod( sal_uInt64 /* nTimeNow */ ) const
+{
+ return Scheduler::ImmediateTimeoutMs;
+}
+
+AutoIdle::AutoIdle( const char *pDebugName )
+ : Idle( true, pDebugName )
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/salplug.cxx b/vcl/source/app/salplug.cxx
new file mode 100644
index 000000000..1868853e9
--- /dev/null
+++ b/vcl/source/app/salplug.cxx
@@ -0,0 +1,355 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <rtl/bootstrap.hxx>
+#include <rtl/process.h>
+#include <sal/log.hxx>
+
+#include <salframe.hxx>
+#include <salinst.hxx>
+#include <config_vclplug.h>
+#include <desktop/crashreport.hxx>
+
+#ifndef _WIN32
+#include <headless/svpinst.hxx>
+#include <printerinfomanager.hxx>
+#include <unx/desktops.hxx>
+
+#include <unistd.h>
+#else
+#include <saldatabasic.hxx>
+#include <Windows.h>
+#endif
+
+#include <cstdio>
+
+#ifdef ANDROID
+#error "Android has no plugin infrastructure!"
+#endif
+
+#if !(defined _WIN32 || defined MACOSX)
+#define DESKTOPDETECT
+#define HEADLESS_VCLPLUG
+#endif
+
+extern "C" {
+typedef SalInstance*(*salFactoryProc)();
+}
+
+namespace {
+
+oslModule pCloseModule = nullptr;
+
+SalInstance* tryInstance( const OUString& rModuleBase, bool bForce = false )
+{
+#ifdef HEADLESS_VCLPLUG
+ 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 == "gtk3" || aUsedModuleBase == "gtk3_kde5" || 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;
+}
+
+#ifdef DESKTOPDETECT
+extern "C" typedef DesktopType Fn_get_desktop_environment();
+
+DesktopType get_desktop_environment()
+{
+ OUString aModule(DESKTOP_DETECTOR_DLL_NAME);
+ oslModule aMod = osl_loadModuleRelative(
+ reinterpret_cast< oslGenericFunction >( &tryInstance ), aModule.pData,
+ SAL_LOADMODULE_DEFAULT );
+ DesktopType ret = DESKTOP_UNKNOWN;
+ 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 );
+ return ret;
+}
+
+SalInstance* autodetect_plugin()
+{
+ static const char* const pKDEFallbackList[] =
+ {
+#if ENABLE_KF5
+ "kf5",
+#endif
+#if ENABLE_GTK3_KDE5
+ "gtk3_kde5",
+#endif
+ "gtk3", "gen", nullptr
+ };
+
+ static const char* const pStandardFallbackList[] =
+ {
+ "gtk3", "gen", nullptr
+ };
+
+#ifdef HEADLESS_VCLPLUG
+ static const char* const pHeadlessFallbackList[] =
+ {
+ "svp", nullptr
+ };
+#endif
+
+ DesktopType desktop = get_desktop_environment();
+ const char * const * pList = pStandardFallbackList;
+ int nListEntry = 0;
+
+#ifdef HEADLESS_VCLPLUG
+ // 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;
+
+ SalInstance* pInst = nullptr;
+ while( pList[nListEntry] && pInst == nullptr )
+ {
+ OUString aTry( OUString::createFromAscii( pList[nListEntry] ) );
+ pInst = tryInstance( aTry );
+ SAL_INFO_IF(
+ pInst, "vcl.plugadapt",
+ "plugin autodetection: " << pList[nListEntry]);
+ nListEntry++;
+ }
+
+ return pInst;
+}
+#endif // DESKTOPDETECT
+
+#ifdef HEADLESS_VCLPLUG
+// 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;
+}
+#endif
+
+} // anonymous namespace
+
+SalInstance *CreateSalInstance()
+{
+ SalInstance *pInst = nullptr;
+
+ OUString aUsePlugin;
+ rtl::Bootstrap::get("SAL_USE_VCLPLUGIN", aUsePlugin);
+ SAL_INFO_IF(!aUsePlugin.isEmpty(), "vcl", "Requested VCL plugin: " << aUsePlugin);
+#ifdef HEADLESS_VCLPLUG
+ if (Application::IsBitmapRendering() || (aUsePlugin.isEmpty() && IsHeadlessModeRequested()))
+ aUsePlugin = "svp";
+#endif
+
+ if (aUsePlugin == "svp")
+ {
+ Application::EnableBitmapRendering();
+#ifndef HEADLESS_VCLPLUG
+ aUsePlugin.clear();
+#endif
+ }
+ if( !aUsePlugin.isEmpty() )
+ pInst = tryInstance( aUsePlugin, true );
+
+#ifdef DESKTOPDETECT
+ if( ! pInst )
+ pInst = autodetect_plugin();
+#endif
+
+ // fallback, try everything
+ static const char* const pPlugin[] = {
+#ifdef _WIN32
+ "win"
+#else
+#ifdef MACOSX
+ "osx"
+#else
+ "gtk3", "kf5", "gen"
+#endif
+#endif
+ };
+
+ for ( int i = 0; !pInst && i != SAL_N_ELEMENTS(pPlugin); ++i )
+ pInst = tryInstance( OUString::createFromAscii( pPlugin[ i ] ) );
+
+ if( ! pInst )
+ {
+ std::fprintf( stderr, "no suitable windowing system found, exiting.\n" );
+ _exit( 1 );
+ }
+
+ // acquire SolarMutex
+ pInst->AcquireYieldMutex();
+
+ return pInst;
+}
+
+void DestroySalInstance( SalInstance *pInst )
+{
+ // release SolarMutex
+ pInst->ReleaseYieldMutexAll();
+
+ delete pInst;
+ if( pCloseModule )
+ osl_unloadModule( pCloseModule );
+}
+
+void SalAbort( const OUString& rErrorText, bool bDumpCore )
+{
+ if( rErrorText.isEmpty() )
+ std::fprintf( stderr, "Application Error\n" );
+ else
+ {
+ CrashReporter::addKeyValue("AbortMessage", rErrorText, CrashReporter::Write);
+ std::fprintf( stderr, "%s\n", OUStringToOString(rErrorText, osl_getThreadTextEncoding()).getStr() );
+ }
+ if( bDumpCore )
+ abort();
+ else
+ _exit(1);
+}
+
+const OUString& SalGetDesktopEnvironment()
+{
+#ifdef _WIN32
+ static OUString aDesktopEnvironment( "Windows" );
+
+#else
+#ifdef MACOSX
+ static OUString aDesktopEnvironment( "MacOSX" );
+#else
+ // Order to match desktops.hxx' DesktopType
+ static const char * const desktop_strings[] = {
+ "none", "unknown", "GNOME", "UNITY",
+ "XFCE", "MATE", "PLASMA5", "LXQT" };
+ static OUString aDesktopEnvironment;
+ if( aDesktopEnvironment.isEmpty())
+ {
+ aDesktopEnvironment = OUString::createFromAscii(
+ desktop_strings[get_desktop_environment()]);
+ }
+#endif
+#endif
+ return aDesktopEnvironment;
+}
+
+SalData::SalData() :
+ m_pInstance(nullptr),
+ m_pPIManager(nullptr)
+{
+}
+
+SalData::~SalData() COVERITY_NOEXCEPT_FALSE
+{
+#if (defined UNX && !defined MACOSX)
+ psp::PrinterInfoManager::release();
+#endif
+}
+
+#ifdef _WIN32
+bool HasAtHook()
+{
+ BOOL bIsRunning = FALSE;
+ // pvParam must be BOOL
+ return SystemParametersInfoW(SPI_GETSCREENREADER, 0, &bIsRunning, 0)
+ && bIsRunning;
+}
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/salusereventlist.cxx b/vcl/source/app/salusereventlist.cxx
new file mode 100644
index 000000000..088bc141f
--- /dev/null
+++ b/vcl/source/app/salusereventlist.cxx
@@ -0,0 +1,171 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <salusereventlist.hxx>
+#include <salwtype.hxx>
+
+#include <algorithm>
+#include <cstdlib>
+#include <exception>
+#include <typeinfo>
+
+#include <com/sun/star/uno/Exception.hpp>
+#include <tools/debug.hxx>
+#include <tools/diagnose_ex.h>
+#include <sal/log.hxx>
+#include <sal/types.h>
+#include <svdata.hxx>
+
+SalUserEventList::SalUserEventList()
+ : m_bAllUserEventProcessedSignaled( true )
+ , m_aProcessingThread(0)
+{
+}
+
+SalUserEventList::~SalUserEventList() COVERITY_NOEXCEPT_FALSE
+{
+}
+
+void SalUserEventList::insertFrame( SalFrame* pFrame )
+{
+ auto aPair = m_aFrames.insert( pFrame );
+ assert( aPair.second ); (void) aPair;
+}
+
+void SalUserEventList::eraseFrame( SalFrame* pFrame )
+{
+ auto it = m_aFrames.find( pFrame );
+ assert( it != m_aFrames.end() );
+ if ( it != m_aFrames.end() )
+ m_aFrames.erase( it );
+}
+
+bool SalUserEventList::DispatchUserEvents( bool bHandleAllCurrentEvents )
+{
+ bool bWasEvent = false;
+ oslThreadIdentifier aCurId = osl::Thread::getCurrentIdentifier();
+
+ DBG_TESTSOLARMUTEX();
+ osl::ResettableMutexGuard 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())
+ {
+ 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.clear();
+
+ if ( !isFrameAlive( aEvent.m_pFrame ) )
+ {
+ if ( aEvent.m_nEvent == SalEvent::UserEvent )
+ delete static_cast< ImplSVEvent* >( aEvent.m_pData );
+ aResettableListGuard.reset();
+ continue;
+ }
+
+#ifndef IOS
+ try
+#endif
+ {
+ ProcessEvent( aEvent );
+ }
+#ifndef IOS
+ catch (css::uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION("vcl", "Uncaught");
+ std::abort();
+ }
+ catch (std::exception& e)
+ {
+ SAL_WARN("vcl", "Uncaught " << typeid(e).name() << " " << e.what());
+ std::abort();
+ }
+ catch (...)
+ {
+ SAL_WARN("vcl", "Uncaught exception during DispatchUserEvents!");
+ std::abort();
+ }
+#endif
+ aResettableListGuard.reset();
+ if (!bHandleAllCurrentEvents)
+ break;
+ }
+ while( true );
+ }
+
+ if ( !m_bAllUserEventProcessedSignaled && !HasUserEvents() )
+ {
+ m_bAllUserEventProcessedSignaled = true;
+ TriggerAllUserEventsProcessed();
+ }
+
+ return bWasEvent;
+}
+
+void SalUserEventList::RemoveEvent( SalFrame* pFrame, void* pData, SalEvent nEvent )
+{
+ SalUserEvent aEvent( pFrame, pData, nEvent );
+
+ osl::MutexGuard 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() )
+ {
+ m_bAllUserEventProcessedSignaled = true;
+ TriggerAllUserEventsProcessed();
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/salvtables.cxx b/vcl/source/app/salvtables.cxx
new file mode 100644
index 000000000..d7c93164a
--- /dev/null
+++ b/vcl/source/app/salvtables.cxx
@@ -0,0 +1,6777 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/AccessibleRelationType.hpp>
+#include <com/sun/star/awt/XWindow.hpp>
+#include <com/sun/star/awt/XWindowPeer.hpp>
+#include <o3tl/sorted_vector.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <iconview.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/calendar.hxx>
+#include <vcl/toolkit/combobox.hxx>
+#include <vcl/toolkit/dialog.hxx>
+#include <vcl/fixed.hxx>
+#include <vcl/toolkit/fixedhyper.hxx>
+#include <vcl/fmtfield.hxx>
+#include <vcl/headbar.hxx>
+#include <vcl/ivctrl.hxx>
+#include <vcl/layout.hxx>
+#include <vcl/menubtn.hxx>
+#include <vcl/toolkit/prgsbar.hxx>
+#include <vcl/ptrstyle.hxx>
+#include <slider.hxx>
+#include <vcl/sysdata.hxx>
+#include <vcl/svlbitm.hxx>
+#include <vcl/toolkit/svtabbx.hxx>
+#include <vcl/tabctrl.hxx>
+#include <vcl/tabpage.hxx>
+#include <vcl/treelistentry.hxx>
+#include <vcl/toolkit/throbber.hxx>
+#include <vcl/toolkit/unowrap.hxx>
+#include <vcl/weld.hxx>
+#include <vcl/vclmedit.hxx>
+#include <vcl/viewdataentry.hxx>
+#include <vcl/virdev.hxx>
+#include <bitmaps.hlst>
+#include <wizdlg.hxx>
+#include <salvtables.hxx>
+
+#include <boost/property_tree/ptree.hpp>
+
+SalFrame::SalFrame()
+ : m_pWindow(nullptr)
+ , m_pProc(nullptr)
+{
+}
+
+// this file contains the virtual destructors of the sal interface
+// compilers usually put their vtables where the destructor is
+
+SalFrame::~SalFrame() {}
+
+void SalFrame::SetCallback(vcl::Window* pWindow, SALFRAMEPROC pProc)
+{
+ m_pWindow = pWindow;
+ m_pProc = pProc;
+}
+
+// default to full-frame flushes
+// on ports where partial-flushes are much cheaper this method should be overridden
+void SalFrame::Flush(const tools::Rectangle&) { Flush(); }
+
+void SalFrame::SetRepresentedURL(const OUString&)
+{
+ // currently this is Mac only functionality
+}
+
+SalInstance::SalInstance(std::unique_ptr<comphelper::SolarMutex> pMutex)
+ : m_pYieldMutex(std::move(pMutex))
+{
+}
+
+SalInstance::~SalInstance() {}
+
+comphelper::SolarMutex* SalInstance::GetYieldMutex() { return m_pYieldMutex.get(); }
+
+sal_uInt32 SalInstance::ReleaseYieldMutexAll() { return m_pYieldMutex->release(true); }
+
+void SalInstance::AcquireYieldMutex(sal_uInt32 nCount) { m_pYieldMutex->acquire(nCount); }
+
+std::unique_ptr<SalSession> SalInstance::CreateSalSession() { return nullptr; }
+
+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);
+}
+
+SalTimer::~SalTimer() COVERITY_NOEXCEPT_FALSE {}
+
+void SalBitmap::DropScaledCache()
+{
+ if (ImplSVData* pSVData = ImplGetSVData())
+ {
+ auto& rCache = pSVData->maGDIData.maScaleCache;
+
+ rCache.remove_if([this] (const lru_scale_cache::key_value_pair_t& rKeyValuePair)
+ { return rKeyValuePair.first.mpBitmap == this; });
+ }
+}
+
+SalBitmap::~SalBitmap() { DropScaledCache(); }
+
+SalSystem::~SalSystem() {}
+
+SalPrinter::~SalPrinter() {}
+
+bool SalPrinter::StartJob(const OUString*, const OUString&, const OUString&, ImplJobSetup*,
+ vcl::PrinterController&)
+{
+ return false;
+}
+
+SalInfoPrinter::~SalInfoPrinter() {}
+
+SalVirtualDevice::~SalVirtualDevice() {}
+
+SalObject::~SalObject() {}
+
+SalMenu::~SalMenu() {}
+
+bool SalMenu::ShowNativePopupMenu(FloatingWindow*, const tools::Rectangle&, FloatWinPopupFlags)
+{
+ return false;
+}
+
+void SalMenu::ShowCloseButton(bool) {}
+
+bool SalMenu::AddMenuBarButton(const SalMenuButtonItem&) { return false; }
+
+void SalMenu::RemoveMenuBarButton(sal_uInt16) {}
+
+tools::Rectangle SalMenu::GetMenuBarButtonRectPixel(sal_uInt16, SalFrame*)
+{
+ return tools::Rectangle();
+}
+
+int SalMenu::GetMenuBarHeight() const { return 0; }
+
+void SalMenu::ApplyPersona() {}
+
+SalMenuItem::~SalMenuItem() {}
+
+void SalInstanceWidget::ensure_event_listener()
+{
+ if (!m_bEventListener)
+ {
+ m_xWidget->AddEventListener(LINK(this, SalInstanceWidget, EventListener));
+ m_bEventListener = true;
+ }
+}
+
+// we want the ability to mark key events as handled, so use this variant
+// for those, we get all keystrokes in this case, so we will need to filter
+// them later
+void SalInstanceWidget::ensure_key_listener()
+{
+ if (!m_bKeyEventListener)
+ {
+ Application::AddKeyListener(LINK(this, SalInstanceWidget, KeyEventListener));
+ m_bKeyEventListener = true;
+ }
+}
+
+// we want the ability to know about mouse events that happen in our children
+// so use this variant, we will need to filter them later
+void SalInstanceWidget::ensure_mouse_listener()
+{
+ if (!m_bMouseEventListener)
+ {
+ Application::AddEventListener(LINK(this, SalInstanceWidget, MouseEventListener));
+ m_bMouseEventListener = true;
+ }
+}
+
+void SalInstanceWidget::set_background(const Color& rColor)
+{
+ m_xWidget->SetControlBackground(rColor);
+ m_xWidget->SetBackground(m_xWidget->GetControlBackground());
+ // turn off WB_CLIPCHILDREN otherwise the bg won't extend "under"
+ // transparent children of the widget
+ m_xWidget->SetStyle(m_xWidget->GetStyle() & ~WB_CLIPCHILDREN);
+}
+
+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)
+{
+}
+
+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() { m_xWidget->GrabFocus(); }
+
+bool SalInstanceWidget::has_focus() const { return m_xWidget->HasFocus(); }
+
+bool SalInstanceWidget::is_active() const { return m_xWidget->IsActive(); }
+
+void SalInstanceWidget::set_has_default(bool has_default)
+{
+ m_xWidget->set_property("has-default", OUString::boolean(has_default));
+}
+
+bool SalInstanceWidget::get_has_default() const { return m_xWidget->GetStyle() & WB_DEFBUTTON; }
+
+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); }
+
+OString SalInstanceWidget::get_buildable_name() const { return m_xWidget->get_id().toUtf8(); }
+
+void SalInstanceWidget::set_help_id(const OString& rId) { return m_xWidget->SetHelpId(rId); }
+
+OString SalInstanceWidget::get_help_id() const { return m_xWidget->GetHelpId(); }
+
+void SalInstanceWidget::set_grid_left_attach(int nAttach)
+{
+ m_xWidget->set_grid_left_attach(nAttach);
+}
+
+int SalInstanceWidget::get_grid_left_attach() const { return m_xWidget->get_grid_left_attach(); }
+
+void SalInstanceWidget::set_grid_width(int nCols) { m_xWidget->set_grid_width(nCols); }
+
+void SalInstanceWidget::set_grid_top_attach(int nAttach)
+{
+ m_xWidget->set_grid_top_attach(nAttach);
+}
+
+int SalInstanceWidget::get_grid_top_attach() const { return m_xWidget->get_grid_top_attach(); }
+
+void SalInstanceWidget::set_hexpand(bool bExpand) { m_xWidget->set_hexpand(bExpand); }
+
+bool SalInstanceWidget::get_hexpand() const { return m_xWidget->get_hexpand(); }
+
+void SalInstanceWidget::set_vexpand(bool bExpand) { m_xWidget->set_vexpand(bExpand); }
+
+bool SalInstanceWidget::get_vexpand() const { return m_xWidget->get_vexpand(); }
+
+void SalInstanceWidget::set_secondary(bool bSecondary) { m_xWidget->set_secondary(bSecondary); }
+
+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_left(int nMargin) { m_xWidget->set_margin_left(nMargin); }
+
+void SalInstanceWidget::set_margin_right(int nMargin) { m_xWidget->set_margin_bottom(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_left() const { return m_xWidget->get_margin_left(); }
+
+int SalInstanceWidget::get_margin_right() const { return m_xWidget->get_margin_bottom(); }
+
+void SalInstanceWidget::set_accessible_name(const OUString& rName)
+{
+ m_xWidget->SetAccessibleName(rName);
+}
+
+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)
+{
+ vcl::Window* pAtkLabel
+ = pLabel ? dynamic_cast<SalInstanceWidget&>(*pLabel).getWidget() : nullptr;
+ m_xWidget->SetAccessibleRelationLabeledBy(pAtkLabel);
+}
+
+void SalInstanceWidget::set_accessible_relation_label_for(weld::Widget* pLabeled)
+{
+ vcl::Window* pAtkLabeled
+ = pLabeled ? dynamic_cast<SalInstanceWidget&>(*pLabeled).getWidget() : nullptr;
+ m_xWidget->SetAccessibleRelationLabelFor(pAtkLabeled);
+}
+
+void SalInstanceWidget::set_tooltip_text(const OUString& rTip)
+{
+ m_xWidget->SetQuickHelpText(rTip);
+}
+
+OUString SalInstanceWidget::get_tooltip_text() const { return m_xWidget->GetQuickHelpText(); }
+
+void SalInstanceWidget::connect_focus_in(const Link<Widget&, void>& rLink)
+{
+ ensure_event_listener();
+ weld::Widget::connect_focus_in(rLink);
+}
+
+void SalInstanceWidget::connect_mnemonic_activate(const Link<Widget&, bool>& rLink)
+{
+ m_xWidget->SetMnemonicActivateHdl(LINK(this, SalInstanceWidget, MnemonicActivateHdl));
+ weld::Widget::connect_mnemonic_activate(rLink);
+}
+
+void SalInstanceWidget::connect_focus_out(const Link<Widget&, void>& rLink)
+{
+ ensure_event_listener();
+ weld::Widget::connect_focus_out(rLink);
+}
+
+void SalInstanceWidget::connect_size_allocate(const Link<const Size&, void>& rLink)
+{
+ ensure_event_listener();
+ weld::Widget::connect_size_allocate(rLink);
+}
+
+void SalInstanceWidget::connect_mouse_press(const Link<const MouseEvent&, bool>& rLink)
+{
+ ensure_mouse_listener();
+ weld::Widget::connect_mouse_press(rLink);
+}
+
+void SalInstanceWidget::connect_mouse_move(const Link<const MouseEvent&, bool>& rLink)
+{
+ ensure_mouse_listener();
+ weld::Widget::connect_mouse_move(rLink);
+}
+
+void SalInstanceWidget::connect_mouse_release(const Link<const MouseEvent&, bool>& rLink)
+{
+ ensure_mouse_listener();
+ weld::Widget::connect_mouse_release(rLink);
+}
+
+void SalInstanceWidget::connect_key_press(const Link<const KeyEvent&, bool>& rLink)
+{
+ ensure_key_listener();
+ weld::Widget::connect_key_press(rLink);
+}
+
+void SalInstanceWidget::connect_key_release(const Link<const KeyEvent&, bool>& rLink)
+{
+ ensure_key_listener();
+ weld::Widget::connect_key_release(rLink);
+}
+
+bool SalInstanceWidget::get_extents_relative_to(Widget& rRelative, int& x, int& y, int& width,
+ int& height)
+{
+ tools::Rectangle aRect(m_xWidget->GetWindowExtentsRelative(
+ dynamic_cast<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() { m_xWidget->SetUpdateMode(false); }
+
+void SalInstanceWidget::thaw() { m_xWidget->SetUpdateMode(true); }
+
+SalInstanceWidget::~SalInstanceWidget()
+{
+ if (m_aMnemonicActivateHdl.IsSet())
+ m_xWidget->SetMnemonicActivateHdl(Link<vcl::Window&, bool>());
+ if (m_bMouseEventListener)
+ Application::RemoveEventListener(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() { return m_xWidget; }
+
+void SalInstanceWidget::disable_notify_events() { ++m_nBlockNotify; }
+
+bool SalInstanceWidget::notify_events_disabled() { return m_nBlockNotify != 0; }
+
+void SalInstanceWidget::enable_notify_events() { --m_nBlockNotify; }
+
+OUString SalInstanceWidget::strip_mnemonic(const OUString& rLabel) const
+{
+ return rLabel.replaceFirst("~", "");
+}
+
+VclPtr<VirtualDevice> SalInstanceWidget::create_virtual_device() const
+{
+ // create with (annoying) separate alpha layer that LibreOffice itself uses
+ return VclPtr<VirtualDevice>::Create(*Application::GetDefaultDevice(), DeviceFormat::DEFAULT,
+ DeviceFormat::DEFAULT);
+}
+
+css::uno::Reference<css::datatransfer::dnd::XDropTarget> SalInstanceWidget::get_drop_target()
+{
+ return m_xWidget->GetDropTarget();
+}
+
+void SalInstanceWidget::connect_get_property_tree(
+ const Link<boost::property_tree::ptree&, void>& rLink)
+{
+ m_xWidget->SetDumpAsPropertyTreeHdl(rLink);
+}
+
+void SalInstanceWidget::set_stack_background()
+{
+ set_background(m_xWidget->GetSettings().GetStyleSettings().GetWindowColor());
+}
+
+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());
+}
+
+void SalInstanceWidget::HandleMouseEventListener(VclSimpleEvent& rEvent)
+{
+ if (rEvent.GetId() == VclEventId::WindowMouseButtonDown)
+ {
+ auto& rWinEvent = static_cast<VclWindowEvent&>(rEvent);
+ if (m_xWidget->IsWindowOrChild(rWinEvent.GetWindow()))
+ {
+ const MouseEvent* pMouseEvent = static_cast<const MouseEvent*>(rWinEvent.GetData());
+ m_aMousePressHdl.Call(*pMouseEvent);
+ }
+ }
+ else if (rEvent.GetId() == VclEventId::WindowMouseButtonUp)
+ {
+ auto& rWinEvent = static_cast<VclWindowEvent&>(rEvent);
+ if (m_xWidget->IsWindowOrChild(rWinEvent.GetWindow()))
+ {
+ const MouseEvent* pMouseEvent = static_cast<const MouseEvent*>(rWinEvent.GetData());
+ m_aMouseReleaseHdl.Call(*pMouseEvent);
+ }
+ }
+ else if (rEvent.GetId() == VclEventId::WindowMouseMove)
+ {
+ auto& rWinEvent = static_cast<VclWindowEvent&>(rEvent);
+ if (m_xWidget->IsWindowOrChild(rWinEvent.GetWindow()))
+ {
+ const MouseEvent* pMouseEvent = static_cast<const MouseEvent*>(rWinEvent.GetData());
+ m_aMouseMotionHdl.Call(*pMouseEvent);
+ }
+ }
+}
+
+bool SalInstanceWidget::HandleKeyEventListener(VclWindowEvent& rEvent)
+{
+ // we get all key events here, ignore them unless we have focus
+ if (!has_focus())
+ 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, VclSimpleEvent&, 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, TriState eCheckRadioFalse)
+{
+ const sal_uInt16 nNewid = nLastId + 1;
+
+ MenuItemBits nBits;
+ if (eCheckRadioFalse == TRISTATE_TRUE)
+ nBits = MenuItemBits::CHECKABLE;
+ else if (eCheckRadioFalse == TRISTATE_FALSE)
+ nBits = MenuItemBits::CHECKABLE | MenuItemBits::RADIOCHECK;
+ else
+ nBits = MenuItemBits::NONE;
+
+ pMenu->InsertItem(nNewid, rStr, nBits,
+ OUStringToOString(rId, RTL_TEXTENCODING_UTF8), pos == -1 ? MENU_APPEND : pos);
+ if (pIconName)
+ {
+ pMenu->SetItemImage(nNewid, createImage(*pIconName));
+ }
+ else if (pImageSurface)
+ {
+ pMenu->SetItemImage(nNewid, createImage(*pImageSurface));
+ }
+ return nNewid;
+}
+}
+
+SalInstanceMenu::SalInstanceMenu(PopupMenu* pMenu, bool bTakeOwnership)
+ : m_xMenu(pMenu)
+ , m_bTakeOwnership(bTakeOwnership)
+{
+ const auto nCount = m_xMenu->GetItemCount();
+ m_nLastId = nCount ? pMenu->GetItemId(nCount - 1) : 0;
+ m_xMenu->SetSelectHdl(LINK(this, SalInstanceMenu, SelectMenuHdl));
+}
+OString SalInstanceMenu::popup_at_rect(weld::Widget* pParent, const tools::Rectangle& rRect)
+{
+ SalInstanceWidget* pVclWidget = dynamic_cast<SalInstanceWidget*>(pParent);
+ assert(pVclWidget);
+ m_xMenu->Execute(pVclWidget->getWidget(), rRect,
+ PopupMenuFlags::ExecuteDown | PopupMenuFlags::NoMouseUpClose);
+ return m_xMenu->GetCurItemIdent();
+}
+void SalInstanceMenu::set_sensitive(const OString& rIdent, bool bSensitive)
+{
+ m_xMenu->EnableItem(rIdent, bSensitive);
+}
+void SalInstanceMenu::set_active(const OString& rIdent, bool bActive)
+{
+ m_xMenu->CheckItem(rIdent, bActive);
+}
+bool SalInstanceMenu::get_active(const OString& rIdent) const
+{
+ return m_xMenu->IsItemChecked(m_xMenu->GetItemId(rIdent));
+}
+void SalInstanceMenu::set_label(const OString& rIdent, const OUString& rLabel)
+{
+ m_xMenu->SetItemText(m_xMenu->GetItemId(rIdent), rLabel);
+}
+OUString SalInstanceMenu::get_label(const OString& rIdent) const
+{
+ return m_xMenu->GetItemText(m_xMenu->GetItemId(rIdent));
+}
+void SalInstanceMenu::set_visible(const OString& rIdent, bool bShow)
+{
+ m_xMenu->ShowItem(m_xMenu->GetItemId(rIdent), bShow);
+}
+void SalInstanceMenu::clear() { m_xMenu->Clear(); }
+void SalInstanceMenu::insert(int pos, const OUString& rId, const OUString& rStr,
+ const OUString* pIconName, VirtualDevice* pImageSurface,
+ TriState eCheckRadioFalse)
+{
+ m_nLastId
+ = insert_to_menu(m_nLastId, m_xMenu, pos, rId, rStr, pIconName, pImageSurface, eCheckRadioFalse);
+}
+void SalInstanceMenu::insert_separator(int pos, const OUString& rId)
+{
+ auto nInsertPos = pos == -1 ? MENU_APPEND : pos;
+ m_xMenu->InsertSeparator(rId.toUtf8(), nInsertPos);
+}
+void SalInstanceMenu::remove(const OString& rId)
+{
+ m_xMenu->RemoveItem(m_xMenu->GetItemPos(m_xMenu->GetItemId(rId)));
+}
+int SalInstanceMenu::n_children() const { return m_xMenu->GetItemCount(); }
+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;
+}
+
+namespace
+{
+class SalInstanceToolbar : public SalInstanceWidget, public virtual weld::Toolbar
+{
+private:
+ VclPtr<ToolBox> m_xToolBox;
+ std::map<sal_uInt16, VclPtr<vcl::Window>> m_aFloats;
+ std::map<sal_uInt16, VclPtr<PopupMenu>> m_aMenus;
+
+ OString 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)
+ : SalInstanceWidget(pToolBox, pBuilder, bTakeOwnership)
+ , m_xToolBox(pToolBox)
+ {
+ m_xToolBox->SetSelectHdl(LINK(this, SalInstanceToolbar, ClickHdl));
+ m_xToolBox->SetDropdownClickHdl(LINK(this, SalInstanceToolbar, DropdownClick));
+ }
+
+ virtual void set_item_sensitive(const OString& rIdent, bool bSensitive) override
+ {
+ m_xToolBox->EnableItem(m_xToolBox->GetItemId(OUString::fromUtf8(rIdent)), bSensitive);
+ }
+
+ virtual bool get_item_sensitive(const OString& rIdent) const override
+ {
+ return m_xToolBox->IsItemEnabled(m_xToolBox->GetItemId(OUString::fromUtf8(rIdent)));
+ }
+
+ virtual void set_item_visible(const OString& rIdent, bool bVisible) override
+ {
+ m_xToolBox->ShowItem(m_xToolBox->GetItemId(OUString::fromUtf8(rIdent)), bVisible);
+ }
+
+ virtual void set_item_help_id(const OString& rIdent, const OString& rHelpId) override
+ {
+ m_xToolBox->SetHelpId(m_xToolBox->GetItemId(OUString::fromUtf8(rIdent)), rHelpId);
+ }
+
+ virtual bool get_item_visible(const OString& rIdent) const override
+ {
+ return m_xToolBox->IsItemVisible(m_xToolBox->GetItemId(OUString::fromUtf8(rIdent)));
+ }
+
+ virtual void set_item_active(const OString& rIdent, bool bActive) override
+ {
+ sal_uInt16 nItemId = m_xToolBox->GetItemId(OUString::fromUtf8(rIdent));
+ m_xToolBox->CheckItem(nItemId, bActive);
+ }
+
+ virtual bool get_item_active(const OString& rIdent) const override
+ {
+ return m_xToolBox->IsItemChecked(m_xToolBox->GetItemId(OUString::fromUtf8(rIdent)));
+ }
+
+ void set_menu_item_active(const OString& rIdent, bool bActive) override
+ {
+ sal_uInt16 nItemId = m_xToolBox->GetItemId(OUString::fromUtf8(rIdent));
+ assert(m_xToolBox->GetItemBits(nItemId) & ToolBoxItemBits::DROPDOWN);
+
+ if (bActive)
+ {
+ m_sStartShowIdent = m_xToolBox->GetItemCommand(nItemId).toUtf8();
+ signal_toggle_menu(m_sStartShowIdent);
+ }
+
+ auto pFloat = m_aFloats[nItemId];
+ if (pFloat)
+ {
+ if (bActive)
+ vcl::Window::GetDockingManager()->StartPopupMode(m_xToolBox, pFloat,
+ FloatWinPopupFlags::GrabFocus);
+ else
+ vcl::Window::GetDockingManager()->EndPopupMode(pFloat);
+ }
+ auto pPopup = m_aMenus[nItemId];
+ if (pPopup)
+ {
+ if (bActive)
+ {
+ tools::Rectangle aRect = m_xToolBox->GetItemRect(nItemId);
+ pPopup->Execute(m_xToolBox, aRect, PopupMenuFlags::ExecuteDown);
+ }
+ else
+ pPopup->EndExecute();
+ }
+
+ m_sStartShowIdent.clear();
+ }
+
+ bool get_menu_item_active(const OString& rIdent) const override
+ {
+ sal_uInt16 nItemId = m_xToolBox->GetItemId(OUString::fromUtf8(rIdent));
+ assert(m_xToolBox->GetItemBits(nItemId) & ToolBoxItemBits::DROPDOWN);
+
+ if (rIdent == m_sStartShowIdent)
+ return true;
+
+ auto aFloat = m_aFloats.find(nItemId);
+ if (aFloat != m_aFloats.end())
+ {
+ return vcl::Window::GetDockingManager()->IsInPopupMode(aFloat->second);
+ }
+
+ auto aPopup = m_aMenus.find(nItemId);
+ if (aPopup != m_aMenus.end())
+ {
+ return PopupMenu::GetActivePopupMenu() == aPopup->second;
+ }
+
+ return false;
+ }
+
+ virtual void set_item_popover(const OString& rIdent, weld::Widget* pPopover) override
+ {
+ SalInstanceWidget* pPopoverWidget = dynamic_cast<SalInstanceWidget*>(pPopover);
+
+ vcl::Window* pFloat = pPopoverWidget ? pPopoverWidget->getWidget() : nullptr;
+ if (pFloat)
+ {
+ pFloat->AddEventListener(LINK(this, SalInstanceToolbar, MenuToggleListener));
+ pFloat->EnableDocking();
+ }
+
+ sal_uInt16 nId = m_xToolBox->GetItemId(OUString::fromUtf8(rIdent));
+ auto xOldFloat = m_aFloats[nId];
+ if (xOldFloat)
+ {
+ xOldFloat->RemoveEventListener(LINK(this, SalInstanceToolbar, MenuToggleListener));
+ }
+ m_aFloats[nId] = pFloat;
+ m_aMenus[nId] = nullptr;
+ }
+
+ virtual void set_item_menu(const OString& rIdent, weld::Menu* pMenu) override
+ {
+ SalInstanceMenu* pInstanceMenu = dynamic_cast<SalInstanceMenu*>(pMenu);
+
+ PopupMenu* pPopup = pInstanceMenu ? pInstanceMenu->getMenu() : nullptr;
+
+ sal_uInt16 nId = m_xToolBox->GetItemId(OUString::fromUtf8(rIdent));
+ m_aMenus[nId] = pPopup;
+ m_aFloats[nId] = nullptr;
+ }
+
+ virtual void insert_separator(int pos, const OUString& /*rId*/) override
+ {
+ auto nInsertPos = pos == -1 ? ToolBox::APPEND : pos;
+ m_xToolBox->InsertSeparator(nInsertPos, 5);
+ }
+
+ virtual int get_n_items() const override { return m_xToolBox->GetItemCount(); }
+
+ virtual OString get_item_ident(int nIndex) const override
+ {
+ return m_xToolBox->GetItemCommand(m_xToolBox->GetItemId(nIndex)).toUtf8();
+ }
+
+ virtual void set_item_ident(int nIndex, const OString& rIdent) override
+ {
+ return m_xToolBox->SetItemCommand(m_xToolBox->GetItemId(nIndex),
+ OUString::fromUtf8(rIdent));
+ }
+
+ virtual void set_item_label(int nIndex, const OUString& rLabel) override
+ {
+ m_xToolBox->SetItemText(m_xToolBox->GetItemId(nIndex), rLabel);
+ }
+
+ virtual OUString get_item_label(const OString& rIdent) const override
+ {
+ return m_xToolBox->GetItemText(m_xToolBox->GetItemId(OUString::fromUtf8(rIdent)));
+ }
+
+ virtual void set_item_label(const OString& rIdent, const OUString& rLabel) override
+ {
+ m_xToolBox->SetItemText(m_xToolBox->GetItemId(OUString::fromUtf8(rIdent)), rLabel);
+ }
+
+ virtual void set_item_icon_name(const OString& rIdent, const OUString& rIconName) override
+ {
+ m_xToolBox->SetItemImage(m_xToolBox->GetItemId(OUString::fromUtf8(rIdent)),
+ Image(StockImage::Yes, rIconName));
+ }
+
+ virtual void set_item_image(const OString& rIdent,
+ const css::uno::Reference<css::graphic::XGraphic>& rIcon) override
+ {
+ m_xToolBox->SetItemImage(m_xToolBox->GetItemId(OUString::fromUtf8(rIdent)), Image(rIcon));
+ }
+
+ virtual void set_item_image(const OString& rIdent, VirtualDevice* pDevice) override
+ {
+ if (pDevice)
+ m_xToolBox->SetItemImage(m_xToolBox->GetItemId(OUString::fromUtf8(rIdent)),
+ createImage(*pDevice));
+ else
+ m_xToolBox->SetItemImage(m_xToolBox->GetItemId(OUString::fromUtf8(rIdent)), Image());
+ }
+
+ virtual void set_item_image(int nIndex,
+ const css::uno::Reference<css::graphic::XGraphic>& rIcon) override
+ {
+ m_xToolBox->SetItemImage(m_xToolBox->GetItemId(nIndex), Image(rIcon));
+ }
+
+ virtual void set_item_tooltip_text(int nIndex, const OUString& rTip) override
+ {
+ m_xToolBox->SetQuickHelpText(m_xToolBox->GetItemId(nIndex), rTip);
+ }
+
+ virtual void set_item_tooltip_text(const OString& rIdent, const OUString& rTip) override
+ {
+ m_xToolBox->SetQuickHelpText(m_xToolBox->GetItemId(OUString::fromUtf8(rIdent)), rTip);
+ }
+
+ virtual OUString get_item_tooltip_text(const OString& rIdent) const override
+ {
+ return m_xToolBox->GetQuickHelpText(m_xToolBox->GetItemId(OUString::fromUtf8(rIdent)));
+ }
+
+ virtual vcl::ImageType get_icon_size() const override { return m_xToolBox->GetImageSize(); }
+
+ virtual void set_icon_size(vcl::ImageType eType) override
+ {
+ 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();
+ }
+ }
+
+ virtual sal_uInt16 get_modifier_state() const override
+ {
+ return m_xToolBox->GetModifier();
+ }
+
+ int get_drop_index(const Point& rPoint) const override
+ {
+ auto nRet = m_xToolBox->GetItemPos(rPoint);
+ if (nRet == ToolBox::ITEM_NOTFOUND)
+ return 0;
+ return nRet;
+ }
+
+ virtual ~SalInstanceToolbar() override
+ {
+ m_xToolBox->SetDropdownClickHdl(Link<ToolBox*, void>());
+ m_xToolBox->SetSelectHdl(Link<ToolBox*, void>());
+ }
+};
+
+}
+
+IMPL_LINK_NOARG(SalInstanceToolbar, ClickHdl, ToolBox*, void)
+{
+ sal_uInt16 nItemId = m_xToolBox->GetCurItemId();
+ signal_clicked(m_xToolBox->GetItemCommand(nItemId).toUtf8());
+}
+
+IMPL_LINK_NOARG(SalInstanceToolbar, DropdownClick, ToolBox*, void)
+{
+ sal_uInt16 nItemId = m_xToolBox->GetCurItemId();
+ set_menu_item_active(m_xToolBox->GetItemCommand(nItemId).toUtf8(), true);
+}
+
+IMPL_LINK(SalInstanceToolbar, MenuToggleListener, VclWindowEvent&, rEvent, void)
+{
+ if (rEvent.GetId() == VclEventId::WindowEndPopupMode)
+ {
+ for (auto& rFloat : m_aFloats)
+ {
+ if (rEvent.GetWindow() == rFloat.second)
+ {
+ sal_uInt16 nItemId = rFloat.first;
+ signal_toggle_menu(m_xToolBox->GetItemCommand(nItemId).toUtf8());
+ break;
+ }
+ }
+ }
+}
+
+namespace
+{
+class SalInstanceSizeGroup : public weld::SizeGroup
+{
+private:
+ std::shared_ptr<VclSizeGroup> m_xGroup;
+
+public:
+ SalInstanceSizeGroup()
+ : m_xGroup(std::make_shared<VclSizeGroup>())
+ {
+ }
+ virtual void add_widget(weld::Widget* pWidget) override
+ {
+ SalInstanceWidget* pVclWidget = dynamic_cast<SalInstanceWidget*>(pWidget);
+ assert(pVclWidget && pVclWidget->getWidget());
+ pVclWidget->getWidget()->add_to_size_group(m_xGroup);
+ }
+ virtual void set_mode(VclSizeGroupMode eMode) override { m_xGroup->set_mode(eMode); }
+};
+}
+
+void SalInstanceContainer::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);
+ }
+}
+
+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);
+ if (pNewVclParent)
+ pVclWidget->getWidget()->SetParent(pNewVclParent->getWidget());
+ else
+ pVclWidget->getWidget()->SetParentToDefaultWindow();
+}
+
+void SalInstanceContainer::recursively_unset_default_buttons()
+{
+ implResetDefault(m_xContainer.get());
+}
+
+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::draw(VirtualDevice& rOutput)
+{
+ rOutput.SetOutputSizePixel(m_xWidget->GetSizePixel());
+ m_xWidget->PaintToDevice(&rOutput, Point());
+}
+
+namespace
+{
+class SalInstanceBox : public SalInstanceContainer, public virtual weld::Box
+{
+public:
+ SalInstanceBox(vcl::Window* pContainer, SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : SalInstanceContainer(pContainer, pBuilder, bTakeOwnership)
+ {
+ }
+ virtual void reorder_child(weld::Widget* pWidget, int nNewPosition) override
+ {
+ SalInstanceWidget* pVclWidget = dynamic_cast<SalInstanceWidget*>(pWidget);
+ assert(pVclWidget);
+ pVclWidget->getWidget()->reorderWithinParent(nNewPosition);
+ }
+};
+
+void CollectChildren(const vcl::Window& rCurrent, const basegfx::B2IPoint& rTopLeft,
+ weld::ScreenShotCollection& rControlDataCollection)
+{
+ if (rCurrent.IsVisible())
+ {
+ 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)
+{
+ 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(); }
+
+void SalInstanceWindow::set_busy_cursor(bool bBusy)
+{
+ if (bBusy)
+ m_xWindow->EnterWait();
+ else
+ m_xWindow->LeaveWait();
+}
+
+css::uno::Reference<css::awt::XWindow> SalInstanceWindow::GetXWindow()
+{
+ css::uno::Reference<css::awt::XWindow> xWindow(m_xWindow->GetComponentInterface(),
+ css::uno::UNO_QUERY);
+ return xWindow;
+}
+
+void SalInstanceWindow::resize_to_request()
+{
+ if (SystemWindow* pSysWin = dynamic_cast<SystemWindow*>(m_xWindow.get()))
+ {
+ pSysWin->setOptimalLayoutSize();
+ return;
+ }
+ if (DockingWindow* pDockWin = dynamic_cast<DockingWindow*>(m_xWindow.get()))
+ {
+ pDockWin->setOptimalLayoutSize();
+ return;
+ }
+ assert(false && "must be system or docking window");
+}
+
+void SalInstanceWindow::set_modal(bool bModal) { m_xWindow->ImplGetFrame()->SetModal(bModal); }
+
+bool SalInstanceWindow::get_modal() const { return m_xWindow->ImplGetFrame()->GetModal(); }
+
+void SalInstanceWindow::window_move(int x, int y) { m_xWindow->SetPosPixel(Point(x, y)); }
+
+Size SalInstanceWindow::get_size() const { return m_xWindow->GetSizePixel(); }
+
+Point SalInstanceWindow::get_position() const { return m_xWindow->GetPosPixel(); }
+
+tools::Rectangle SalInstanceWindow::get_monitor_workarea() const
+{
+ return m_xWindow->GetDesktopRectPixel();
+}
+
+void SalInstanceWindow::set_centered_on_parent(bool /*bTrackGeometryRequests*/)
+{
+ if (vcl::Window* pParent = m_xWidget->GetParent())
+ {
+ Size aParentGeometry(pParent->GetSizePixel());
+ Size aGeometry(m_xWidget->get_preferred_size());
+ auto nX = (aParentGeometry.Width() - aGeometry.Width()) / 2;
+ auto nY = (aParentGeometry.Height() - aGeometry.Height()) / 2;
+ m_xWidget->SetPosPixel(Point(nX, nY));
+ }
+}
+
+bool SalInstanceWindow::get_resizable() const { return m_xWindow->GetStyle() & WB_SIZEABLE; }
+
+bool SalInstanceWindow::has_toplevel_focus() const { return m_xWindow->HasChildPathFocus(); }
+
+void SalInstanceWindow::present()
+{
+ m_xWindow->ToTop(ToTopFlags::RestoreWhenMin | ToTopFlags::ForegroundTask);
+}
+
+void SalInstanceWindow::set_window_state(const OString& rStr)
+{
+ SystemWindow* pSysWin = dynamic_cast<SystemWindow*>(m_xWindow.get());
+ assert(pSysWin);
+ pSysWin->SetWindowState(rStr);
+}
+
+OString SalInstanceWindow::get_window_state(WindowStateMask nMask) const
+{
+ SystemWindow* pSysWin = dynamic_cast<SystemWindow*>(m_xWindow.get());
+ assert(pSysWin);
+ return pSysWin->GetWindowState(nMask);
+}
+
+SystemEnvData SalInstanceWindow::get_system_data() const { return *m_xWindow->GetSystemData(); }
+
+void SalInstanceWindow::connect_toplevel_focus_changed(const Link<weld::Widget&, void>& rLink)
+{
+ ensure_event_listener();
+ weld::Window::connect_toplevel_focus_changed(rLink);
+}
+
+void SalInstanceWindow::HandleEventListener(VclWindowEvent& rEvent)
+{
+ if (rEvent.GetId() == VclEventId::WindowActivate
+ || rEvent.GetId() == VclEventId::WindowDeactivate)
+ {
+ signal_toplevel_focus_changed();
+ return;
+ }
+ SalInstanceContainer::HandleEventListener(rEvent);
+}
+
+void SalInstanceWindow::draw(VirtualDevice& rOutput)
+{
+ SystemWindow* pSysWin = dynamic_cast<SystemWindow*>(m_xWindow.get());
+ assert(pSysWin);
+ pSysWin->createScreenshot(rOutput);
+}
+
+weld::ScreenShotCollection SalInstanceWindow::collect_screenshot_data()
+{
+ weld::ScreenShotCollection aRet;
+
+ // collect all children. Choose start pos to be negative
+ // of target dialog's position to get all positions relative to (0,0)
+ const Point aParentPos(m_xWindow->GetPosPixel());
+ const basegfx::B2IPoint aTopLeft(-aParentPos.X(), -aParentPos.Y());
+ CollectChildren(*m_xWindow, aTopLeft, aRet);
+
+ return aRet;
+}
+
+SalInstanceWindow::~SalInstanceWindow() { 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)
+ pActionArea->sort_native_button_order();
+ 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)
+ pActionArea->sort_native_button_order();
+ 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();
+ 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();
+}
+
+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)
+ pActionArea->sort_native_button_order();
+ return m_xDialog->Execute();
+}
+
+void SalInstanceDialog::response(int nResponse) { m_xDialog->EndDialog(nResponse); }
+
+void SalInstanceDialog::add_button(const OUString& rText, int nResponse, const OString& rHelpId)
+{
+ VclButtonBox* pBox = m_xDialog->get_action_area();
+ VclPtr<PushButton> xButton(
+ VclPtr<PushButton>::Create(pBox, WB_CLIPCHILDREN | WB_CENTER | WB_VCENTER));
+ xButton->SetText(rText);
+ xButton->SetHelpId(rHelpId);
+
+ switch (nResponse)
+ {
+ case RET_OK:
+ xButton->set_id("ok");
+ break;
+ case RET_CLOSE:
+ xButton->set_id("close");
+ break;
+ case RET_CANCEL:
+ xButton->set_id("cancel");
+ break;
+ case RET_YES:
+ xButton->set_id("yes");
+ break;
+ case RET_NO:
+ xButton->set_id("no");
+ break;
+ }
+
+ xButton->Show();
+ m_xDialog->add_button(xButton, nResponse, true);
+}
+
+void SalInstanceDialog::set_modal(bool bModal)
+{
+ if (get_modal() == bModal)
+ return;
+ m_xDialog->SetModalInputMode(bModal);
+}
+
+bool SalInstanceDialog::get_modal() const { return m_xDialog->IsModalInputMode(); }
+
+void SalInstanceDialog::set_default_response(int nResponse)
+{
+ m_xDialog->set_default_response(nResponse);
+}
+
+weld::Container* SalInstanceDialog::weld_content_area()
+{
+ return new SalInstanceContainer(m_xDialog->get_content_area(), m_pBuilder, false);
+}
+
+IMPL_LINK(SalInstanceDialog, PopupScreenShotMenuHdl, const CommandEvent&, rCEvt, bool)
+{
+ if (CommandEventId::ContextMenu == rCEvt.GetCommand())
+ {
+ const Point aMenuPos(rCEvt.GetMousePosPixel());
+ ScopedVclPtrInstance<PopupMenu> aMenu;
+ sal_uInt16 nLocalID(1);
+
+ aMenu->InsertItem(nLocalID, VclResId(SV_BUTTONTEXT_SCREENSHOT));
+ aMenu->SetHelpText(nLocalID, VclResId(SV_HELPTEXT_SCREENSHOT));
+ aMenu->SetHelpId(nLocalID, "InteractiveScreenshotMode");
+ aMenu->EnableItem(nLocalID);
+
+ const sal_uInt16 nId(aMenu->Execute(m_xDialog, aMenuPos));
+
+ // 0 == no selection (so not usable as ID)
+ if (0 != nId)
+ {
+ // open screenshot annotation dialog
+ VclAbstractDialogFactory* pFact = VclAbstractDialogFactory::Create();
+ VclPtr<AbstractScreenshotAnnotationDlg> pTmp
+ = pFact->CreateScreenshotAnnotationDlg(*this);
+ ScopedVclPtr<AbstractScreenshotAnnotationDlg> pDialog(pTmp);
+
+ if (pDialog)
+ {
+ // currently just execute the dialog, no need to do
+ // different things for ok/cancel. This may change later,
+ // for that case use 'if (pDlg->Execute() == RET_OK)'
+ pDialog->Execute();
+ }
+ }
+
+ // consume event when:
+ // - CommandEventId::ContextMenu
+ // - bScreenshotMode
+ return true;
+ }
+
+ return false;
+}
+
+SalInstanceMessageDialog::SalInstanceMessageDialog(::MessageDialog* pDialog, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : SalInstanceDialog(pDialog, pBuilder, bTakeOwnership)
+ , m_xMessageDialog(pDialog)
+{
+}
+
+void SalInstanceMessageDialog::set_primary_text(const OUString& rText)
+{
+ m_xMessageDialog->set_primary_text(rText);
+}
+
+OUString SalInstanceMessageDialog::get_primary_text() const
+{
+ return m_xMessageDialog->get_primary_text();
+}
+
+void SalInstanceMessageDialog::set_secondary_text(const OUString& rText)
+{
+ m_xMessageDialog->set_secondary_text(rText);
+}
+
+OUString SalInstanceMessageDialog::get_secondary_text() const
+{
+ return m_xMessageDialog->get_secondary_text();
+}
+
+weld::Container* SalInstanceMessageDialog::weld_message_area()
+{
+ return new SalInstanceContainer(m_xMessageDialog->get_message_area(), m_pBuilder, false);
+}
+
+namespace
+{
+
+class SalInstanceAssistant : public SalInstanceDialog, public virtual weld::Assistant
+{
+private:
+ VclPtr<vcl::RoadmapWizard> m_xWizard;
+ std::vector<std::unique_ptr<SalInstanceContainer>> m_aPages;
+ std::vector<VclPtr<TabPage>> m_aAddedPages;
+ std::vector<int> m_aIds;
+ std::vector<VclPtr<VclGrid>> m_aAddedGrids;
+ Idle m_aUpdateRoadmapIdle;
+
+ int find_page(const OString& rIdent) const
+ {
+ for (size_t i = 0; i < m_aAddedPages.size(); ++i)
+ {
+ if (m_aAddedPages[i]->get_id().toUtf8() == rIdent)
+ return i;
+ }
+ return -1;
+ }
+
+ int find_id(int nId) const
+ {
+ for (size_t i = 0; i < m_aIds.size(); ++i)
+ {
+ if (nId == m_aIds[i])
+ return i;
+ }
+ return -1;
+ }
+
+ DECL_LINK(OnRoadmapItemSelected, LinkParamNone*, void);
+ DECL_LINK(UpdateRoadmap_Hdl, Timer*, void);
+
+public:
+ SalInstanceAssistant(vcl::RoadmapWizard* pDialog, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : SalInstanceDialog(pDialog, pBuilder, bTakeOwnership)
+ , m_xWizard(pDialog)
+ {
+ m_xWizard->SetItemSelectHdl(LINK(this, SalInstanceAssistant, OnRoadmapItemSelected));
+
+ m_aUpdateRoadmapIdle.SetInvokeHandler(LINK(this, SalInstanceAssistant, UpdateRoadmap_Hdl));
+ m_aUpdateRoadmapIdle.SetPriority(TaskPriority::HIGHEST);
+ }
+
+ virtual int get_current_page() const override { return find_id(m_xWizard->GetCurLevel()); }
+
+ virtual int get_n_pages() const override { return m_aAddedPages.size(); }
+
+ virtual OString get_page_ident(int nPage) const override
+ {
+ return m_aAddedPages[nPage]->get_id().toUtf8();
+ }
+
+ virtual OString get_current_page_ident() const override
+ {
+ return get_page_ident(get_current_page());
+ }
+
+ virtual void set_current_page(int nPage) override
+ {
+ disable_notify_events();
+
+ // take the first shown page as the size for all pages
+ if (m_xWizard->GetPageSizePixel().Width() == 0)
+ {
+ Size aFinalSize;
+ for (int i = 0, nPages = get_n_pages(); i < nPages; ++i)
+ {
+ TabPage* pPage = m_xWizard->GetPage(m_aIds[i]);
+ assert(pPage);
+ Size aPageSize(pPage->get_preferred_size());
+ if (aPageSize.Width() > aFinalSize.Width())
+ aFinalSize.setWidth(aPageSize.Width());
+ if (aPageSize.Height() > aFinalSize.Height())
+ aFinalSize.setHeight(aPageSize.Height());
+ }
+ m_xWizard->SetPageSizePixel(aFinalSize);
+ }
+
+ (void)m_xWizard->ShowPage(m_aIds[nPage]);
+ enable_notify_events();
+ }
+
+ virtual void set_current_page(const OString& rIdent) override
+ {
+ int nIndex = find_page(rIdent);
+ if (nIndex == -1)
+ return;
+ set_current_page(nIndex);
+ }
+
+ virtual void set_page_index(const OString& rIdent, int nNewIndex) override
+ {
+ int nOldIndex = find_page(rIdent);
+
+ if (nOldIndex == -1)
+ return;
+
+ if (nOldIndex == nNewIndex)
+ return;
+
+ disable_notify_events();
+
+ auto entry = std::move(m_aAddedPages[nOldIndex]);
+ m_aAddedPages.erase(m_aAddedPages.begin() + nOldIndex);
+ m_aAddedPages.insert(m_aAddedPages.begin() + nNewIndex, std::move(entry));
+
+ int nId = m_aIds[nOldIndex];
+ m_aIds.erase(m_aIds.begin() + nOldIndex);
+ m_aIds.insert(m_aIds.begin() + nNewIndex, nId);
+
+ m_aUpdateRoadmapIdle.Start();
+
+ enable_notify_events();
+ }
+
+ virtual weld::Container* append_page(const OString& rIdent) override
+ {
+ VclPtrInstance<TabPage> xPage(m_xWizard);
+ VclPtrInstance<VclGrid> xGrid(xPage);
+ xPage->set_id(OUString::fromUtf8(rIdent));
+ xPage->Show();
+ xGrid->set_hexpand(true);
+ xGrid->set_vexpand(true);
+ xGrid->Show();
+ m_xWizard->AddPage(xPage);
+ m_aIds.push_back(m_aAddedPages.size());
+ m_xWizard->SetPage(m_aIds.back(), xPage);
+ m_aAddedPages.push_back(xPage);
+ m_aAddedGrids.push_back(xGrid);
+
+ m_aUpdateRoadmapIdle.Start();
+
+ m_aPages.emplace_back(new SalInstanceContainer(xGrid, m_pBuilder, false));
+ return m_aPages.back().get();
+ }
+
+ virtual OUString get_page_title(const OString& rIdent) const override
+ {
+ int nIndex = find_page(rIdent);
+ if (nIndex == -1)
+ return OUString();
+ return m_aAddedPages[nIndex]->GetText();
+ }
+
+ virtual void set_page_title(const OString& rIdent, const OUString& rTitle) override
+ {
+ int nIndex = find_page(rIdent);
+ if (nIndex == -1)
+ return;
+ if (m_aAddedPages[nIndex]->GetText() != rTitle)
+ {
+ disable_notify_events();
+ m_aAddedPages[nIndex]->SetText(rTitle);
+ m_aUpdateRoadmapIdle.Start();
+ enable_notify_events();
+ }
+ }
+
+ virtual void set_page_sensitive(const OString& rIdent, bool bSensitive) override
+ {
+ int nIndex = find_page(rIdent);
+ if (nIndex == -1)
+ return;
+ if (m_aAddedPages[nIndex]->IsEnabled() != bSensitive)
+ {
+ disable_notify_events();
+ m_aAddedPages[nIndex]->Enable(bSensitive);
+ m_aUpdateRoadmapIdle.Start();
+ enable_notify_events();
+ }
+ }
+
+ virtual void set_page_side_help_id(const OString& rHelpId) override
+ {
+ m_xWizard->SetRoadmapHelpId(rHelpId);
+ }
+
+ weld::Button* weld_widget_for_response(int nResponse) override;
+
+ virtual ~SalInstanceAssistant() override
+ {
+ for (auto& rGrid : m_aAddedGrids)
+ rGrid.disposeAndClear();
+ for (auto& rPage : m_aAddedPages)
+ rPage.disposeAndClear();
+ }
+};
+
+}
+
+IMPL_LINK_NOARG(SalInstanceAssistant, OnRoadmapItemSelected, LinkParamNone*, void)
+{
+ if (notify_events_disabled())
+ return;
+ auto nCurItemId = m_xWizard->GetCurrentRoadmapItemID();
+ int nPageIndex(find_id(nCurItemId));
+ if (!signal_jump_page(get_page_ident(nPageIndex)) && nCurItemId != m_xWizard->GetCurLevel())
+ m_xWizard->SelectRoadmapItemByID(m_xWizard->GetCurLevel());
+}
+
+IMPL_LINK_NOARG(SalInstanceAssistant, UpdateRoadmap_Hdl, Timer*, void)
+{
+ disable_notify_events();
+
+ m_xWizard->DeleteRoadmapItems();
+
+ int nPos = 0;
+ for (size_t i = 0; i < m_aAddedPages.size(); ++i)
+ {
+ const OUString& rLabel = m_aAddedPages[i]->GetText();
+ bool bSensitive = m_aAddedPages[i]->IsEnabled();
+ if (rLabel.isEmpty())
+ continue;
+ m_xWizard->InsertRoadmapItem(nPos++, rLabel, m_aIds[i], bSensitive);
+ }
+
+ m_xWizard->SelectRoadmapItemByID(m_aIds[get_current_page()]);
+
+ m_xWizard->ShowRoadmap(nPos != 0);
+
+ enable_notify_events();
+}
+
+namespace
+{
+class SalInstanceFrame : public SalInstanceContainer, public virtual weld::Frame
+{
+private:
+ VclPtr<VclFrame> m_xFrame;
+
+public:
+ SalInstanceFrame(VclFrame* pFrame, SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : SalInstanceContainer(pFrame, pBuilder, bTakeOwnership)
+ , m_xFrame(pFrame)
+ {
+ }
+
+ virtual void set_label(const OUString& rText) override { m_xFrame->set_label(rText); }
+
+ virtual OUString get_label() const override { return m_xFrame->get_label(); }
+
+ virtual std::unique_ptr<weld::Label> weld_label_widget() const override;
+};
+
+class SalInstancePaned : public SalInstanceContainer, public virtual weld::Paned
+{
+private:
+ VclPtr<VclPaned> m_xPaned;
+
+public:
+ SalInstancePaned(VclPaned* pPaned, SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : SalInstanceContainer(pPaned, pBuilder, bTakeOwnership)
+ , m_xPaned(pPaned)
+ {
+ }
+
+ virtual void set_position(int nPos) override
+ {
+ m_xPaned->set_position(nPos);
+ }
+
+ virtual int get_position() const override
+ {
+ return m_xPaned->get_position();
+ }
+};
+
+class SalInstanceScrolledWindow : public SalInstanceContainer, public virtual weld::ScrolledWindow
+{
+private:
+ VclPtr<VclScrolledWindow> m_xScrolledWindow;
+ Link<ScrollBar*, void> m_aOrigVScrollHdl;
+ Link<ScrollBar*, void> m_aOrigHScrollHdl;
+ bool m_bUserManagedScrolling;
+
+ DECL_LINK(VscrollHdl, ScrollBar*, void);
+ DECL_LINK(HscrollHdl, ScrollBar*, void);
+
+public:
+ SalInstanceScrolledWindow(VclScrolledWindow* pScrolledWindow, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : SalInstanceContainer(pScrolledWindow, pBuilder, bTakeOwnership)
+ , m_xScrolledWindow(pScrolledWindow)
+ , m_bUserManagedScrolling(false)
+ {
+ 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));
+ }
+
+ virtual void hadjustment_configure(int value, int lower, int upper, int step_increment,
+ int page_increment, int page_size) override
+ {
+ ScrollBar& rHorzScrollBar = m_xScrolledWindow->getHorzScrollBar();
+ rHorzScrollBar.SetRangeMin(lower);
+ rHorzScrollBar.SetRangeMax(upper);
+ rHorzScrollBar.SetLineSize(step_increment);
+ rHorzScrollBar.SetPageSize(page_increment);
+ rHorzScrollBar.SetThumbPos(value);
+ rHorzScrollBar.SetVisibleSize(page_size);
+ }
+
+ virtual int hadjustment_get_value() const override
+ {
+ ScrollBar& rHorzScrollBar = m_xScrolledWindow->getHorzScrollBar();
+ return rHorzScrollBar.GetThumbPos();
+ }
+
+ virtual void hadjustment_set_value(int value) override
+ {
+ ScrollBar& rHorzScrollBar = m_xScrolledWindow->getHorzScrollBar();
+ rHorzScrollBar.SetThumbPos(value);
+ if (!m_bUserManagedScrolling)
+ m_aOrigHScrollHdl.Call(&rHorzScrollBar);
+ }
+
+ virtual int hadjustment_get_upper() const override
+ {
+ ScrollBar& rHorzScrollBar = m_xScrolledWindow->getHorzScrollBar();
+ return rHorzScrollBar.GetRangeMax();
+ }
+
+ virtual void hadjustment_set_upper(int upper) override
+ {
+ ScrollBar& rHorzScrollBar = m_xScrolledWindow->getHorzScrollBar();
+ rHorzScrollBar.SetRangeMax(upper);
+ }
+
+ virtual int hadjustment_get_page_size() const override
+ {
+ ScrollBar& rHorzScrollBar = m_xScrolledWindow->getHorzScrollBar();
+ return rHorzScrollBar.GetVisibleSize();
+ }
+
+ virtual void hadjustment_set_page_size(int size) override
+ {
+ ScrollBar& rHorzScrollBar = m_xScrolledWindow->getHorzScrollBar();
+ return rHorzScrollBar.SetVisibleSize(size);
+ }
+
+ virtual void hadjustment_set_page_increment(int size) override
+ {
+ ScrollBar& rHorzScrollBar = m_xScrolledWindow->getHorzScrollBar();
+ return rHorzScrollBar.SetPageSize(size);
+ }
+
+ virtual void hadjustment_set_step_increment(int size) override
+ {
+ ScrollBar& rHorzScrollBar = m_xScrolledWindow->getHorzScrollBar();
+ return rHorzScrollBar.SetLineSize(size);
+ }
+
+ virtual void set_hpolicy(VclPolicyType eHPolicy) override
+ {
+ WinBits nWinBits = m_xScrolledWindow->GetStyle() & ~(WB_AUTOHSCROLL | WB_HSCROLL);
+ if (eHPolicy == VclPolicyType::ALWAYS)
+ nWinBits |= WB_HSCROLL;
+ else if (eHPolicy == VclPolicyType::AUTOMATIC)
+ nWinBits |= WB_AUTOHSCROLL;
+ m_xScrolledWindow->SetStyle(nWinBits);
+ m_xScrolledWindow->queue_resize();
+ }
+
+ virtual VclPolicyType get_hpolicy() const override
+ {
+ WinBits nWinBits = m_xScrolledWindow->GetStyle();
+ if (nWinBits & WB_AUTOHSCROLL)
+ return VclPolicyType::AUTOMATIC;
+ else if (nWinBits & WB_HSCROLL)
+ return VclPolicyType::ALWAYS;
+ return VclPolicyType::NEVER;
+ }
+
+ virtual int get_hscroll_height() const override
+ {
+ return m_xScrolledWindow->getHorzScrollBar().get_preferred_size().Height();
+ }
+
+ virtual void vadjustment_configure(int value, int lower, int upper, int step_increment,
+ int page_increment, int page_size) override
+ {
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ rVertScrollBar.SetRangeMin(lower);
+ rVertScrollBar.SetRangeMax(upper);
+ rVertScrollBar.SetLineSize(step_increment);
+ rVertScrollBar.SetPageSize(page_increment);
+ rVertScrollBar.SetThumbPos(value);
+ rVertScrollBar.SetVisibleSize(page_size);
+ }
+
+ virtual int vadjustment_get_value() const override
+ {
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ return rVertScrollBar.GetThumbPos();
+ }
+
+ virtual void vadjustment_set_value(int value) override
+ {
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ rVertScrollBar.SetThumbPos(value);
+ if (!m_bUserManagedScrolling)
+ m_aOrigVScrollHdl.Call(&rVertScrollBar);
+ }
+
+ virtual int vadjustment_get_upper() const override
+ {
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ return rVertScrollBar.GetRangeMax();
+ }
+
+ virtual void vadjustment_set_upper(int upper) override
+ {
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ rVertScrollBar.SetRangeMax(upper);
+ }
+
+ virtual int vadjustment_get_lower() const override
+ {
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ return rVertScrollBar.GetRangeMin();
+ }
+
+ virtual void vadjustment_set_lower(int lower) override
+ {
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ rVertScrollBar.SetRangeMin(lower);
+ }
+
+ virtual int vadjustment_get_page_size() const override
+ {
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ return rVertScrollBar.GetVisibleSize();
+ }
+
+ virtual void vadjustment_set_page_size(int size) override
+ {
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ return rVertScrollBar.SetVisibleSize(size);
+ }
+
+ virtual void vadjustment_set_page_increment(int size) override
+ {
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ return rVertScrollBar.SetPageSize(size);
+ }
+
+ virtual void vadjustment_set_step_increment(int size) override
+ {
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ return rVertScrollBar.SetLineSize(size);
+ }
+
+ virtual void set_vpolicy(VclPolicyType eVPolicy) override
+ {
+ WinBits nWinBits = m_xScrolledWindow->GetStyle() & ~(WB_AUTOVSCROLL | WB_VSCROLL);
+ if (eVPolicy == VclPolicyType::ALWAYS)
+ nWinBits |= WB_VSCROLL;
+ else if (eVPolicy == VclPolicyType::AUTOMATIC)
+ nWinBits |= WB_AUTOVSCROLL;
+ m_xScrolledWindow->SetStyle(nWinBits);
+ m_xScrolledWindow->queue_resize();
+ }
+
+ virtual VclPolicyType get_vpolicy() const override
+ {
+ WinBits nWinBits = m_xScrolledWindow->GetStyle();
+ if (nWinBits & WB_AUTOVSCROLL)
+ return VclPolicyType::AUTOMATIC;
+ else if (nWinBits & WB_VSCROLL)
+ return VclPolicyType::ALWAYS;
+ return VclPolicyType::NEVER;
+ }
+
+ virtual int get_vscroll_width() const override
+ {
+ return m_xScrolledWindow->getVertScrollBar().get_preferred_size().Width();
+ }
+
+ virtual void set_user_managed_scrolling() override
+ {
+ m_bUserManagedScrolling = true;
+ m_xScrolledWindow->setUserManagedScrolling(true);
+ }
+
+ virtual ~SalInstanceScrolledWindow() override
+ {
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ rVertScrollBar.SetScrollHdl(m_aOrigVScrollHdl);
+ }
+};
+
+}
+
+IMPL_LINK(SalInstanceScrolledWindow, VscrollHdl, ScrollBar*, pScrollBar, void)
+{
+ signal_vadjustment_changed();
+ if (!m_bUserManagedScrolling)
+ m_aOrigVScrollHdl.Call(pScrollBar);
+}
+
+IMPL_LINK_NOARG(SalInstanceScrolledWindow, HscrollHdl, ScrollBar*, void)
+{
+ signal_hadjustment_changed();
+ if (!m_bUserManagedScrolling)
+ m_aOrigHScrollHdl.Call(&m_xScrolledWindow->getHorzScrollBar());
+}
+
+SalInstanceNotebook::SalInstanceNotebook(TabControl* pNotebook, SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : SalInstanceContainer(pNotebook, pBuilder, bTakeOwnership)
+ , m_xNotebook(pNotebook)
+{
+ m_xNotebook->SetActivatePageHdl(LINK(this, SalInstanceNotebook, ActivatePageHdl));
+ m_xNotebook->SetDeactivatePageHdl(LINK(this, SalInstanceNotebook, DeactivatePageHdl));
+}
+
+int SalInstanceNotebook::get_current_page() const
+{
+ return m_xNotebook->GetPagePos(m_xNotebook->GetCurPageId());
+}
+
+OString SalInstanceNotebook::get_page_ident(int nPage) const
+{
+ return m_xNotebook->GetPageName(m_xNotebook->GetPageId(nPage));
+}
+
+OString SalInstanceNotebook::get_current_page_ident() const
+{
+ return m_xNotebook->GetPageName(m_xNotebook->GetCurPageId());
+}
+
+weld::Container* SalInstanceNotebook::get_page(const OString& rIdent) const
+{
+ sal_uInt16 nPageId = m_xNotebook->GetPageId(rIdent);
+ sal_uInt16 nPageIndex = m_xNotebook->GetPagePos(nPageId);
+ if (nPageIndex == TAB_PAGE_NOTFOUND)
+ return nullptr;
+ TabPage* pPage = m_xNotebook->GetTabPage(nPageId);
+ vcl::Window* pChild = pPage->GetChild(0);
+ if (m_aPages.size() < nPageIndex + 1U)
+ m_aPages.resize(nPageIndex + 1U);
+ if (!m_aPages[nPageIndex])
+ m_aPages[nPageIndex] = std::make_shared<SalInstanceContainer>(pChild, m_pBuilder, false);
+ return m_aPages[nPageIndex].get();
+}
+
+void SalInstanceNotebook::set_current_page(int nPage)
+{
+ m_xNotebook->SetCurPageId(m_xNotebook->GetPageId(nPage));
+}
+
+void SalInstanceNotebook::set_current_page(const OString& rIdent)
+{
+ m_xNotebook->SetCurPageId(m_xNotebook->GetPageId(rIdent));
+}
+
+void SalInstanceNotebook::remove_page(const OString& rIdent)
+{
+ sal_uInt16 nPageId = m_xNotebook->GetPageId(rIdent);
+ sal_uInt16 nPageIndex = m_xNotebook->GetPagePos(nPageId);
+ if (nPageIndex == TAB_PAGE_NOTFOUND)
+ return;
+
+ m_xNotebook->RemovePage(nPageId);
+ if (nPageIndex < m_aPages.size())
+ m_aPages.erase(m_aPages.begin() + nPageIndex);
+
+ auto iter = m_aAddedPages.find(rIdent);
+ if (iter != m_aAddedPages.end())
+ {
+ iter->second.second.disposeAndClear();
+ iter->second.first.disposeAndClear();
+ m_aAddedPages.erase(iter);
+ }
+}
+
+void SalInstanceNotebook::insert_page(const OString& rIdent, const OUString& rLabel, int nPos)
+{
+ sal_uInt16 nPageCount = m_xNotebook->GetPageCount();
+ sal_uInt16 nLastPageId = nPageCount ? m_xNotebook->GetPageId(nPageCount - 1) : 0;
+ sal_uInt16 nNewPageId = nLastPageId + 1;
+ 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);
+}
+
+int SalInstanceNotebook::get_n_pages() const { return m_xNotebook->GetPageCount(); }
+
+OUString SalInstanceNotebook::get_tab_label_text(const OString& rIdent) const
+{
+ return m_xNotebook->GetPageText(m_xNotebook->GetPageId(rIdent));
+}
+
+void SalInstanceNotebook::set_tab_label_text(const OString& rIdent, const OUString& rText)
+{
+ return m_xNotebook->SetPageText(m_xNotebook->GetPageId(rIdent), rText);
+}
+
+SalInstanceNotebook::~SalInstanceNotebook()
+{
+ for (auto& rItem : m_aAddedPages)
+ {
+ rItem.second.second.disposeAndClear();
+ rItem.second.first.disposeAndClear();
+ }
+ m_xNotebook->SetActivatePageHdl(Link<TabControl*, void>());
+ m_xNotebook->SetDeactivatePageHdl(Link<TabControl*, bool>());
+}
+
+IMPL_LINK_NOARG(SalInstanceNotebook, DeactivatePageHdl, TabControl*, bool)
+{
+ return !m_aLeavePageHdl.IsSet() || m_aLeavePageHdl.Call(get_current_page_ident());
+}
+
+IMPL_LINK_NOARG(SalInstanceNotebook, ActivatePageHdl, TabControl*, void)
+{
+ m_aEnterPageHdl.Call(get_current_page_ident());
+}
+
+namespace
+{
+class SalInstanceVerticalNotebook : public SalInstanceContainer, 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)
+ : SalInstanceContainer(pNotebook, pBuilder, bTakeOwnership)
+ , m_xNotebook(pNotebook)
+ {
+ m_xNotebook->SetActivatePageHdl(LINK(this, SalInstanceVerticalNotebook, ActivatePageHdl));
+ m_xNotebook->SetDeactivatePageHdl(
+ LINK(this, SalInstanceVerticalNotebook, DeactivatePageHdl));
+ }
+
+ virtual int get_current_page() const override
+ {
+ return m_xNotebook->GetPagePos(m_xNotebook->GetCurPageId());
+ }
+
+ virtual OString get_page_ident(int nPage) const override
+ {
+ return m_xNotebook->GetPageId(nPage);
+ }
+
+ virtual OString get_current_page_ident() const override { return m_xNotebook->GetCurPageId(); }
+
+ virtual weld::Container* get_page(const OString& rIdent) const override
+ {
+ sal_uInt16 nPageIndex = m_xNotebook->GetPagePos(rIdent);
+ if (nPageIndex == TAB_PAGE_NOTFOUND)
+ return nullptr;
+ auto pChild = m_xNotebook->GetPage(rIdent);
+ if (m_aPages.size() < nPageIndex + 1U)
+ m_aPages.resize(nPageIndex + 1U);
+ if (!m_aPages[nPageIndex])
+ m_aPages[nPageIndex].reset(new SalInstanceContainer(pChild, m_pBuilder, false));
+ return m_aPages[nPageIndex].get();
+ }
+
+ virtual void set_current_page(int nPage) override
+ {
+ m_xNotebook->SetCurPageId(m_xNotebook->GetPageId(nPage));
+ }
+
+ virtual void set_current_page(const OString& rIdent) override
+ {
+ m_xNotebook->SetCurPageId(rIdent);
+ }
+
+ virtual void remove_page(const OString& rIdent) override
+ {
+ sal_uInt16 nPageIndex = m_xNotebook->GetPagePos(rIdent);
+ if (nPageIndex == TAB_PAGE_NOTFOUND)
+ return;
+ m_xNotebook->RemovePage(rIdent);
+ if (nPageIndex < m_aPages.size())
+ m_aPages.erase(m_aPages.begin() + nPageIndex);
+ }
+
+ virtual void insert_page(const OString& rIdent, const OUString& rLabel, int nPos) override
+ {
+ VclPtrInstance<VclGrid> xGrid(m_xNotebook->GetPageParent());
+ xGrid->set_hexpand(true);
+ xGrid->set_vexpand(true);
+ m_xNotebook->InsertPage(rIdent, rLabel, Image(), "", xGrid, nPos);
+ }
+
+ virtual int get_n_pages() const override { return m_xNotebook->GetPageCount(); }
+
+ virtual void set_tab_label_text(const OString& rIdent, const OUString& rText) override
+ {
+ return m_xNotebook->SetPageText(rIdent, rText);
+ }
+
+ virtual OUString get_tab_label_text(const OString& rIdent) const override
+ {
+ return m_xNotebook->GetPageText(rIdent);
+ }
+
+ virtual ~SalInstanceVerticalNotebook() override
+ {
+ m_xNotebook->SetActivatePageHdl(Link<VerticalTabControl*, void>());
+ m_xNotebook->SetDeactivatePageHdl(Link<VerticalTabControl*, bool>());
+ }
+};
+
+}
+
+IMPL_LINK_NOARG(SalInstanceVerticalNotebook, DeactivatePageHdl, VerticalTabControl*, bool)
+{
+ return !m_aLeavePageHdl.IsSet() || m_aLeavePageHdl.Call(get_current_page_ident());
+}
+
+IMPL_LINK_NOARG(SalInstanceVerticalNotebook, ActivatePageHdl, VerticalTabControl*, void)
+{
+ m_aEnterPageHdl.Call(get_current_page_ident());
+}
+
+SalInstanceButton::SalInstanceButton(::Button* pButton, SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : SalInstanceContainer(pButton, pBuilder, bTakeOwnership)
+ , m_xButton(pButton)
+ , m_aOldClickHdl(pButton->GetClickHdl())
+{
+ m_xButton->SetClickHdl(LINK(this, SalInstanceButton, ClickHdl));
+}
+
+void SalInstanceButton::set_label(const OUString& rText) { m_xButton->SetText(rText); }
+
+void SalInstanceButton::set_image(VirtualDevice* pDevice)
+{
+ m_xButton->SetImageAlign(ImageAlign::Left);
+ if (pDevice)
+ m_xButton->SetModeImage(createImage(*pDevice));
+ else
+ m_xButton->SetModeImage(Image());
+}
+
+void SalInstanceButton::set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage)
+{
+ m_xButton->SetImageAlign(ImageAlign::Left);
+ m_xButton->SetModeImage(Image(rImage));
+}
+
+void SalInstanceButton::set_from_icon_name(const OUString& rIconName)
+{
+ m_xButton->SetModeImage(Image(StockImage::Yes, rIconName));
+}
+
+void SalInstanceButton::set_label_line_wrap(bool wrap)
+{
+ WinBits nBits = m_xButton->GetStyle();
+ nBits &= ~WB_WORDBREAK;
+ if (wrap)
+ nBits |= WB_WORDBREAK;
+ m_xButton->SetStyle(nBits);
+ m_xButton->queue_resize();
+}
+
+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;
+}
+
+namespace
+{
+class SalInstanceMenuButton : public SalInstanceButton, public virtual weld::MenuButton
+{
+private:
+ 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)
+ : 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;
+ }
+ }
+
+ virtual void set_active(bool active) override
+ {
+ if (active == get_active())
+ return;
+ if (active)
+ m_xMenuButton->ExecuteMenu();
+ else
+ m_xMenuButton->CancelMenu();
+ }
+
+ virtual bool get_active() const override { return m_xMenuButton->InPopupMode(); }
+
+ virtual void set_inconsistent(bool /*inconsistent*/) override
+ {
+ //not available
+ }
+
+ virtual bool get_inconsistent() const override { return false; }
+
+ virtual void insert_item(int pos, const OUString& rId, const OUString& rStr,
+ const OUString* pIconName, VirtualDevice* pImageSurface,
+ TriState eCheckRadioFalse) override
+ {
+ m_nLastId = insert_to_menu(m_nLastId, m_xMenuButton->GetPopupMenu(), pos, rId, rStr,
+ pIconName, pImageSurface, eCheckRadioFalse);
+ }
+
+ virtual void insert_separator(int pos, const OUString& rId) override
+ {
+ auto nInsertPos = pos == -1 ? MENU_APPEND : pos;
+ m_xMenuButton->GetPopupMenu()->InsertSeparator(rId.toUtf8(), nInsertPos);
+ }
+
+ virtual void set_item_sensitive(const OString& rIdent, bool bSensitive) override
+ {
+ PopupMenu* pMenu = m_xMenuButton->GetPopupMenu();
+ pMenu->EnableItem(rIdent, bSensitive);
+ }
+
+ virtual void remove_item(const OString& rId) override
+ {
+ PopupMenu* pMenu = m_xMenuButton->GetPopupMenu();
+ pMenu->RemoveItem(pMenu->GetItemPos(pMenu->GetItemId(rId)));
+ }
+
+ virtual void clear() override
+ {
+ PopupMenu* pMenu = m_xMenuButton->GetPopupMenu();
+ pMenu->Clear();
+ }
+
+ virtual void set_item_active(const OString& rIdent, bool bActive) override
+ {
+ PopupMenu* pMenu = m_xMenuButton->GetPopupMenu();
+ pMenu->CheckItem(rIdent, bActive);
+ }
+
+ virtual void set_item_label(const OString& rIdent, const OUString& rText) override
+ {
+ PopupMenu* pMenu = m_xMenuButton->GetPopupMenu();
+ pMenu->SetItemText(pMenu->GetItemId(rIdent), rText);
+ }
+
+ virtual OUString get_item_label(const OString& rIdent) const override
+ {
+ PopupMenu* pMenu = m_xMenuButton->GetPopupMenu();
+ return pMenu->GetItemText(pMenu->GetItemId(rIdent));
+ }
+
+ virtual void set_item_visible(const OString& rIdent, bool bShow) override
+ {
+ PopupMenu* pMenu = m_xMenuButton->GetPopupMenu();
+ pMenu->ShowItem(pMenu->GetItemId(rIdent), bShow);
+ }
+
+ virtual void set_item_help_id(const OString& rIdent, const OString& rHelpId) override
+ {
+ PopupMenu* pMenu = m_xMenuButton->GetPopupMenu();
+ pMenu->SetHelpId(pMenu->GetItemId(rIdent), rHelpId);
+ }
+
+ virtual OString get_item_help_id(const OString& rIdent) const override
+ {
+ PopupMenu* pMenu = m_xMenuButton->GetPopupMenu();
+ return pMenu->GetHelpId(pMenu->GetItemId(rIdent));
+ }
+
+ virtual void set_popover(weld::Widget* pPopover) override
+ {
+ SalInstanceWidget* pPopoverWidget = dynamic_cast<SalInstanceWidget*>(pPopover);
+ m_xMenuButton->SetPopover(pPopoverWidget ? pPopoverWidget->getWidget() : nullptr);
+ }
+
+ virtual ~SalInstanceMenuButton() override
+ {
+ 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 SalInstanceLinkButton : public SalInstanceContainer, 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)
+ : SalInstanceContainer(pButton, pBuilder, bTakeOwnership)
+ , m_xButton(pButton)
+ {
+ m_aOrigClickHdl = m_xButton->GetClickHdl();
+ m_xButton->SetClickHdl(LINK(this, SalInstanceLinkButton, ClickHdl));
+ }
+
+ virtual void set_label(const OUString& rText) override { m_xButton->SetText(rText); }
+
+ virtual OUString get_label() const override { return m_xButton->GetText(); }
+
+ virtual void set_uri(const OUString& rUri) override { m_xButton->SetURL(rUri); }
+
+ virtual OUString get_uri() const override { return m_xButton->GetURL(); }
+
+ virtual ~SalInstanceLinkButton() override { m_xButton->SetClickHdl(m_aOrigClickHdl); }
+};
+
+}
+
+IMPL_LINK(SalInstanceLinkButton, ClickHdl, FixedHyperlink&, rButton, void)
+{
+ bool bConsumed = signal_activate_link();
+ if (!bConsumed)
+ m_aOrigClickHdl.Call(rButton);
+}
+
+namespace
+{
+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)
+ : SalInstanceButton(pButton, pBuilder, bTakeOwnership)
+ , m_xRadioButton(pButton)
+ {
+ m_xRadioButton->SetToggleHdl(LINK(this, SalInstanceRadioButton, ToggleHdl));
+ }
+
+ virtual void set_active(bool active) override
+ {
+ disable_notify_events();
+ m_xRadioButton->Check(active);
+ enable_notify_events();
+ }
+
+ virtual bool get_active() const override { return m_xRadioButton->IsChecked(); }
+
+ virtual void set_image(VirtualDevice* pDevice) override
+ {
+ m_xRadioButton->SetImageAlign(ImageAlign::Center);
+ if (pDevice)
+ m_xRadioButton->SetModeImage(createImage(*pDevice));
+ else
+ m_xRadioButton->SetModeImage(Image());
+ }
+
+ virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage) override
+ {
+ m_xRadioButton->SetImageAlign(ImageAlign::Center);
+ m_xRadioButton->SetModeImage(Image(rImage));
+ }
+
+ virtual void set_from_icon_name(const OUString& rIconName) override
+ {
+ m_xRadioButton->SetModeRadioImage(Image(StockImage::Yes, rIconName));
+ }
+
+ virtual void set_inconsistent(bool /*inconsistent*/) override
+ {
+ //not available
+ }
+
+ virtual bool get_inconsistent() const override { return false; }
+
+ virtual ~SalInstanceRadioButton() override
+ {
+ m_xRadioButton->SetToggleHdl(Link<::RadioButton&, void>());
+ }
+};
+
+}
+
+IMPL_LINK_NOARG(SalInstanceRadioButton, ToggleHdl, ::RadioButton&, void)
+{
+ if (notify_events_disabled())
+ return;
+ signal_toggled();
+}
+
+namespace
+{
+class SalInstanceToggleButton : public SalInstanceButton, public virtual weld::ToggleButton
+{
+private:
+ VclPtr<PushButton> m_xToggleButton;
+
+ DECL_LINK(ToggleListener, VclWindowEvent&, void);
+
+public:
+ SalInstanceToggleButton(PushButton* pButton, SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : SalInstanceButton(pButton, pBuilder, bTakeOwnership)
+ , m_xToggleButton(pButton)
+ {
+ }
+
+ virtual void connect_toggled(const Link<ToggleButton&, void>& rLink) override
+ {
+ assert(!m_aToggleHdl.IsSet());
+ m_xToggleButton->AddEventListener(LINK(this, SalInstanceToggleButton, ToggleListener));
+ weld::ToggleButton::connect_toggled(rLink);
+ }
+
+ virtual void set_active(bool active) override
+ {
+ disable_notify_events();
+ m_xToggleButton->Check(active);
+ enable_notify_events();
+ }
+
+ virtual bool get_active() const override { return m_xToggleButton->IsChecked(); }
+
+ virtual void set_inconsistent(bool inconsistent) override
+ {
+ disable_notify_events();
+ m_xToggleButton->SetState(inconsistent ? TRISTATE_INDET : TRISTATE_FALSE);
+ enable_notify_events();
+ }
+
+ virtual bool get_inconsistent() const override
+ {
+ return m_xToggleButton->GetState() == TRISTATE_INDET;
+ }
+
+ virtual ~SalInstanceToggleButton() override
+ {
+ if (m_aToggleHdl.IsSet())
+ m_xToggleButton->RemoveEventListener(
+ LINK(this, SalInstanceToggleButton, ToggleListener));
+ }
+};
+
+}
+
+IMPL_LINK(SalInstanceToggleButton, ToggleListener, VclWindowEvent&, rEvent, void)
+{
+ if (notify_events_disabled())
+ return;
+ if (rEvent.GetId() == VclEventId::PushbuttonToggle)
+ signal_toggled();
+}
+
+SalInstanceCheckButton::SalInstanceCheckButton(CheckBox* pButton, SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : SalInstanceButton(pButton, pBuilder, bTakeOwnership)
+ , m_xCheckButton(pButton)
+{
+ m_xCheckButton->SetToggleHdl(LINK(this, SalInstanceCheckButton, ToggleHdl));
+}
+
+void SalInstanceCheckButton::set_active(bool active)
+{
+ disable_notify_events();
+ m_xCheckButton->EnableTriState(false);
+ m_xCheckButton->Check(active);
+ enable_notify_events();
+}
+
+bool SalInstanceCheckButton::get_active() const { return m_xCheckButton->IsChecked(); }
+
+void SalInstanceCheckButton::set_inconsistent(bool inconsistent)
+{
+ disable_notify_events();
+ m_xCheckButton->EnableTriState(true);
+ m_xCheckButton->SetState(inconsistent ? TRISTATE_INDET : TRISTATE_FALSE);
+ enable_notify_events();
+}
+
+bool SalInstanceCheckButton::get_inconsistent() const
+{
+ return m_xCheckButton->GetState() == TRISTATE_INDET;
+}
+
+SalInstanceCheckButton::~SalInstanceCheckButton()
+{
+ m_xCheckButton->SetToggleHdl(Link<CheckBox&, void>());
+}
+
+IMPL_LINK_NOARG(SalInstanceCheckButton, ToggleHdl, CheckBox&, void)
+{
+ if (notify_events_disabled())
+ return;
+ m_xCheckButton->EnableTriState(false);
+ signal_toggled();
+}
+
+namespace
+{
+class SalInstanceScale : public SalInstanceWidget, public virtual weld::Scale
+{
+private:
+ VclPtr<Slider> m_xScale;
+
+ DECL_LINK(SlideHdl, Slider*, void);
+
+public:
+ SalInstanceScale(Slider* pScale, SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : SalInstanceWidget(pScale, pBuilder, bTakeOwnership)
+ , m_xScale(pScale)
+ {
+ m_xScale->SetSlideHdl(LINK(this, SalInstanceScale, SlideHdl));
+ }
+
+ virtual void set_value(int value) override { m_xScale->SetThumbPos(value); }
+
+ virtual void set_range(int min, int max) override
+ {
+ m_xScale->SetRangeMin(min);
+ m_xScale->SetRangeMax(max);
+ }
+
+ virtual int get_value() const override { return m_xScale->GetThumbPos(); }
+
+ virtual void set_increments(int step, int page) override
+ {
+ m_xScale->SetLineSize(step);
+ m_xScale->SetPageSize(page);
+ }
+
+ virtual void get_increments(int& step, int& page) const override
+ {
+ step = m_xScale->GetLineSize();
+ page = m_xScale->GetPageSize();
+ }
+
+ virtual ~SalInstanceScale() override { m_xScale->SetSlideHdl(Link<Slider*, void>()); }
+};
+
+}
+
+IMPL_LINK_NOARG(SalInstanceScale, SlideHdl, Slider*, void) { signal_value_changed(); }
+
+namespace
+{
+class SalInstanceSpinner : public SalInstanceWidget, public virtual weld::Spinner
+{
+private:
+ VclPtr<Throbber> m_xThrobber;
+
+public:
+ SalInstanceSpinner(Throbber* pThrobber, SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : SalInstanceWidget(pThrobber, pBuilder, bTakeOwnership)
+ , m_xThrobber(pThrobber)
+ {
+ }
+
+ virtual void start() override { m_xThrobber->start(); }
+
+ virtual void stop() override { m_xThrobber->stop(); }
+};
+
+class SalInstanceProgressBar : public SalInstanceWidget, public virtual weld::ProgressBar
+{
+private:
+ VclPtr<::ProgressBar> m_xProgressBar;
+
+public:
+ SalInstanceProgressBar(::ProgressBar* pProgressBar, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : SalInstanceWidget(pProgressBar, pBuilder, bTakeOwnership)
+ , m_xProgressBar(pProgressBar)
+ {
+ }
+
+ virtual void set_percentage(int value) override { m_xProgressBar->SetValue(value); }
+
+ virtual OUString get_text() const override { return m_xProgressBar->GetText(); }
+
+ virtual void set_text(const OUString& rText) override { m_xProgressBar->SetText(rText); }
+};
+
+class SalInstanceImage : public SalInstanceWidget, public virtual weld::Image
+{
+private:
+ VclPtr<FixedImage> m_xImage;
+
+public:
+ SalInstanceImage(FixedImage* pImage, SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : SalInstanceWidget(pImage, pBuilder, bTakeOwnership)
+ , m_xImage(pImage)
+ {
+ }
+
+ virtual void set_from_icon_name(const OUString& rIconName) override
+ {
+ m_xImage->SetImage(::Image(StockImage::Yes, rIconName));
+ }
+
+ virtual void set_image(VirtualDevice* pDevice) override
+ {
+ if (pDevice)
+ m_xImage->SetImage(createImage(*pDevice));
+ else
+ m_xImage->SetImage(::Image());
+ }
+
+ virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage) override
+ {
+ m_xImage->SetImage(::Image(rImage));
+ }
+};
+
+class SalInstanceCalendar : public SalInstanceWidget, public virtual weld::Calendar
+{
+private:
+ VclPtr<::Calendar> m_xCalendar;
+
+ DECL_LINK(SelectHdl, ::Calendar*, void);
+ DECL_LINK(ActivateHdl, ::Calendar*, void);
+
+public:
+ SalInstanceCalendar(::Calendar* pCalendar, SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : SalInstanceWidget(pCalendar, pBuilder, bTakeOwnership)
+ , m_xCalendar(pCalendar)
+ {
+ m_xCalendar->SetSelectHdl(LINK(this, SalInstanceCalendar, SelectHdl));
+ m_xCalendar->SetActivateHdl(LINK(this, SalInstanceCalendar, ActivateHdl));
+ }
+
+ virtual void set_date(const Date& rDate) override { m_xCalendar->SetCurDate(rDate); }
+
+ virtual Date get_date() const override { return m_xCalendar->GetFirstSelectedDate(); }
+
+ virtual ~SalInstanceCalendar() override
+ {
+ m_xCalendar->SetSelectHdl(Link<::Calendar*, void>());
+ m_xCalendar->SetActivateHdl(Link<::Calendar*, void>());
+ }
+};
+
+}
+
+IMPL_LINK_NOARG(SalInstanceCalendar, SelectHdl, ::Calendar*, void)
+{
+ if (notify_events_disabled())
+ return;
+ signal_selected();
+}
+
+IMPL_LINK_NOARG(SalInstanceCalendar, ActivateHdl, ::Calendar*, void)
+{
+ if (notify_events_disabled())
+ return;
+ signal_activated();
+}
+
+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();
+ m_xEntry->SetSelection(Selection(nStartPos, nEndPos < 0 ? SELECTION_MAX : nEndPos));
+ 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_message_type(weld::EntryMessageType eType)
+{
+ if (eType == weld::EntryMessageType::Error)
+ {
+ // tdf#114603: enable setting the background to a different color;
+ // relevant for GTK; see also #i75179#
+ m_xEntry->SetForceControlBackground(true);
+ m_xEntry->SetControlForeground(COL_WHITE);
+ m_xEntry->SetControlBackground(0xff6563);
+ }
+ else if (eType == weld::EntryMessageType::Warning)
+ {
+ // tdf#114603: enable setting the background to a different color;
+ // relevant for GTK; see also #i75179#
+ m_xEntry->SetForceControlBackground(true);
+ m_xEntry->SetControlForeground();
+ m_xEntry->SetControlBackground(COL_YELLOW);
+ }
+ else
+ {
+ m_xEntry->SetForceControlBackground(false);
+ m_xEntry->SetControlForeground();
+ m_xEntry->SetControlBackground();
+ }
+}
+
+void SalInstanceEntry::set_font(const vcl::Font& rFont)
+{
+ m_xEntry->SetPointFont(*m_xEntry, rFont);
+ m_xEntry->Invalidate();
+}
+
+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();
+}
+
+void SalInstanceEntry::copy_clipboard()
+{
+ m_xEntry->Copy();
+}
+
+void SalInstanceEntry::paste_clipboard()
+{
+ m_xEntry->Paste();
+}
+
+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); }
+
+namespace
+{
+struct SalInstanceTreeIter : 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;
+};
+
+TriState get_toggle(SvTreeListEntry* pEntry, int col)
+{
+ ++col; //skip dummy/expander column
+
+ 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;
+}
+
+bool get_text_emphasis(SvTreeListEntry* pEntry, int col)
+{
+ ++col; //skip dummy/expander column
+
+ 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();
+}
+}
+
+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);
+ }
+ };
+}
+
+class SalInstanceTreeView : public SalInstanceContainer, public virtual weld::TreeView
+{
+private:
+ // 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_bDisableCheckBoxAutoWidth;
+ 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, const HelpEvent&, bool);
+ DECL_LINK(CustomRenderHdl, svtree_render_args, void);
+ DECL_LINK(CustomMeasureHdl, svtree_measure_args, Size);
+
+ bool IsDummyEntry(SvTreeListEntry* pEntry) const
+ {
+ return m_xTreeView->GetEntryText(pEntry).trim() == "<dummy>";
+ }
+
+ SvTreeListEntry* GetPlaceHolderChild(SvTreeListEntry* pEntry) const
+ {
+ if (pEntry->HasChildren())
+ {
+ auto pChild = m_xTreeView->FirstChild(pEntry);
+ assert(pChild);
+ if (IsDummyEntry(pChild))
+ return pChild;
+ }
+ return nullptr;
+ }
+
+ static void set_font_color(SvTreeListEntry* pEntry, const Color& rColor)
+ {
+ if (rColor == COL_AUTO)
+ pEntry->SetTextColor(std::optional<Color>());
+ else
+ pEntry->SetTextColor(rColor);
+ }
+
+ void 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 InvalidateModelEntry(SvTreeListEntry* pEntry)
+ {
+ if (!m_xTreeView->GetModel()->IsEnableInvalidate())
+ return;
+ m_xTreeView->ModelHasEntryInvalidated(pEntry);
+ }
+
+public:
+ SalInstanceTreeView(SvTabListBox* pTreeView, SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : SalInstanceContainer(pTreeView, pBuilder, bTakeOwnership)
+ , m_xTreeView(pTreeView)
+ , m_aCheckButtonData(pTreeView, false)
+ , m_aRadioButtonData(pTreeView, true)
+ , m_bDisableCheckBoxAutoWidth(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 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));
+ }
+
+ virtual void connect_query_tooltip(const Link<const weld::TreeIter&, OUString>& rLink) override
+ {
+ weld::TreeView::connect_query_tooltip(rLink);
+ m_xTreeView->SetTooltipHdl(LINK(this, SalInstanceTreeView, TooltipHdl));
+ }
+
+ virtual void columns_autosize() override
+ {
+ std::vector<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);
+ }
+ }
+
+ virtual void freeze() override
+ {
+ SalInstanceWidget::freeze();
+ m_xTreeView->SetUpdateMode(false);
+ }
+
+ virtual void thaw() override
+ {
+ m_xTreeView->SetUpdateMode(true);
+ SalInstanceWidget::thaw();
+ }
+
+ virtual void set_column_fixed_widths(const std::vector<int>& rWidths) override
+ {
+ m_bDisableCheckBoxAutoWidth = true;
+ std::vector<long> aTabPositions;
+ aTabPositions.push_back(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);
+ 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]);
+ }
+ // call Resize to recalculate based on the new tabs
+ m_xTreeView->Resize();
+ }
+
+ virtual void set_column_editables(const std::vector<bool>& rEditables) override
+ {
+ size_t nTabCount = rEditables.size();
+ for (size_t i = 0 ; i < nTabCount; ++i)
+ m_xTreeView->SetTabEditable(i, rEditables[i]);
+ }
+
+ virtual void set_centered_column(int nCol) override
+ {
+ m_xTreeView->SetTabJustify(nCol, SvTabJustify::AdjustCenter);
+ }
+
+ virtual int get_column_width(int nColumn) const override
+ {
+ 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;
+ }
+
+ virtual OUString get_column_title(int nColumn) const override
+ {
+ LclHeaderTabListBox* pHeaderBox = dynamic_cast<LclHeaderTabListBox*>(m_xTreeView.get());
+ if (HeaderBar* pHeaderBar = pHeaderBox ? pHeaderBox->GetHeaderBar() : nullptr)
+ {
+ return pHeaderBar->GetItemText(pHeaderBar->GetItemId(nColumn));
+ }
+ return OUString();
+ }
+
+ virtual void set_column_title(int nColumn, const OUString& rTitle) override
+ {
+ LclHeaderTabListBox* pHeaderBox = dynamic_cast<LclHeaderTabListBox*>(m_xTreeView.get());
+ if (HeaderBar* pHeaderBar = pHeaderBox ? pHeaderBox->GetHeaderBar() : nullptr)
+ {
+ return pHeaderBar->SetItemText(pHeaderBar->GetItemId(nColumn), rTitle);
+ }
+ }
+
+ virtual void set_column_custom_renderer(int nColumn, bool bEnable) override
+ {
+ assert(n_children() == 0 && "tree must be empty");
+ if (bEnable)
+ m_aCustomRenders.insert(nColumn);
+ else
+ m_aCustomRenders.erase(nColumn);
+ }
+
+ virtual void show() override
+ {
+ if (LclHeaderTabListBox* pHeaderBox = dynamic_cast<LclHeaderTabListBox*>(m_xTreeView.get()))
+ pHeaderBox->GetParent()->Show();
+ SalInstanceContainer::show();
+ }
+
+ virtual void hide() override
+ {
+ if (LclHeaderTabListBox* pHeaderBox = dynamic_cast<LclHeaderTabListBox*>(m_xTreeView.get()))
+ pHeaderBox->GetParent()->Hide();
+ SalInstanceContainer::hide();
+ }
+
+ virtual void insert(const weld::TreeIter* pParent, int pos, const OUString* pStr,
+ const OUString* pId, const OUString* pIconName,
+ VirtualDevice* pImageSurface, const OUString* pExpanderName,
+ bool bChildrenOnDemand, weld::TreeIter* pRet) override
+ {
+ 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 (pIconName || pImageSurface)
+ {
+ Image aImage(pIconName ? createImage(*pIconName) : createImage(*pImageSurface));
+ pEntry->AddItem(std::make_unique<SvLBoxContextBmp>(aImage, aImage, false));
+ }
+ else
+ {
+ Image aDummy;
+ pEntry->AddItem(std::make_unique<SvLBoxContextBmp>(aDummy, aDummy, false));
+ }
+ if (pStr)
+ AddStringItem(pEntry, *pStr, 0);
+ pEntry->SetUserData(pUserData);
+ m_xTreeView->Insert(pEntry, iter, nInsertPos);
+
+ if (pExpanderName)
+ {
+ Image aImage(createImage(*pExpanderName));
+ m_xTreeView->SetExpandedEntryBmp(pEntry, aImage);
+ m_xTreeView->SetCollapsedEntryBmp(pEntry, aImage);
+ }
+
+ 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);
+ }
+ enable_notify_events();
+ }
+
+ virtual void
+ bulk_insert_for_each(int nSourceCount,
+ const std::function<void(weld::TreeIter&, int nSourceIndex)>& func,
+ const std::vector<int>* pFixedWidths) override
+ {
+ freeze();
+ clear();
+ SalInstanceTreeIter aVclIter(static_cast<SvTreeListEntry*>(nullptr));
+
+ m_xTreeView->nTreeFlags |= SvTreeFlags::MANINS;
+
+ if (pFixedWidths)
+ set_column_fixed_widths(*pFixedWidths);
+
+ Image aDummy;
+ for (int i = 0; i < nSourceCount; ++i)
+ {
+ aVclIter.iter = new SvTreeListEntry;
+ aVclIter.iter->AddItem(std::make_unique<SvLBoxContextBmp>(aDummy, aDummy, false));
+ m_xTreeView->Insert(aVclIter.iter, nullptr, 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);
+ SvViewDataItem* pViewDataItem = m_xTreeView->GetViewDataItem(aVclIter.iter, &rItem);
+ pViewDataItem->mnWidth = (*pFixedWidths)[j];
+ }
+ }
+
+ m_xTreeView->nTreeFlags &= ~SvTreeFlags::MANINS;
+
+ thaw();
+ }
+
+ virtual void set_font_color(int pos, const Color& rColor) override
+ {
+ SvTreeListEntry* pEntry = m_xTreeView->GetEntry(nullptr, pos);
+ set_font_color(pEntry, rColor);
+ }
+
+ virtual void set_font_color(const weld::TreeIter& rIter, const Color& rColor) override
+ {
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ set_font_color(rVclIter.iter, rColor);
+ }
+
+ virtual void remove(int pos) override
+ {
+ disable_notify_events();
+ SvTreeListEntry* pEntry = m_xTreeView->GetEntry(nullptr, pos);
+ m_xTreeView->RemoveEntry(pEntry);
+ enable_notify_events();
+ }
+
+ virtual int find_text(const OUString& rText) const override
+ {
+ for (SvTreeListEntry* pEntry = m_xTreeView->First(); pEntry;
+ pEntry = m_xTreeView->Next(pEntry))
+ {
+ if (SvTabListBox::GetEntryText(pEntry, 0) == rText)
+ return SvTreeList::GetRelPos(pEntry);
+ }
+ return -1;
+ }
+
+ virtual int find_id(const OUString& rId) const override
+ {
+ 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;
+ }
+
+ virtual void swap(int pos1, int pos2) override
+ {
+ 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);
+ }
+
+ virtual void clear() override
+ {
+ disable_notify_events();
+ m_xTreeView->Clear();
+ m_aUserData.clear();
+ enable_notify_events();
+ }
+
+ virtual int n_children() const override
+ {
+ return m_xTreeView->GetModel()->GetChildList(nullptr).size();
+ }
+
+ virtual int iter_n_children(const weld::TreeIter& rIter) const override
+ {
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ return m_xTreeView->GetModel()->GetChildList(rVclIter.iter).size();
+ }
+
+ virtual void select(int pos) override
+ {
+ 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);
+ m_xTreeView->Select(pEntry, true);
+ m_xTreeView->MakeVisible(pEntry);
+ }
+ enable_notify_events();
+ }
+
+ virtual int get_cursor_index() const override
+ {
+ SvTreeListEntry* pEntry = m_xTreeView->GetCurEntry();
+ if (!pEntry)
+ return -1;
+ return SvTreeList::GetRelPos(pEntry);
+ }
+
+ virtual void set_cursor(int pos) override
+ {
+ 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();
+ }
+
+ virtual void scroll_to_row(int pos) override
+ {
+ 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();
+ }
+
+ virtual bool is_selected(int pos) const override
+ {
+ SvTreeListEntry* pEntry = m_xTreeView->GetEntry(nullptr, pos);
+ return m_xTreeView->IsSelected(pEntry);
+ }
+
+ virtual void unselect(int pos) override
+ {
+ 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();
+ }
+
+ virtual std::vector<int> get_selected_rows() const override
+ {
+ 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;
+ }
+
+ static OUString get_text(SvTreeListEntry* pEntry, int col)
+ {
+ if (col == -1)
+ return SvTabListBox::GetEntryText(pEntry, 0);
+
+ ++col; //skip dummy/expander column
+
+ 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();
+ }
+
+ virtual OUString get_text(int pos, int col) const override
+ {
+ SvTreeListEntry* pEntry = m_xTreeView->GetEntry(nullptr, pos);
+ return get_text(pEntry, col);
+ }
+
+ void set_text(SvTreeListEntry* pEntry, const OUString& rText, int col)
+ {
+ if (col == -1)
+ {
+ m_xTreeView->SetEntryText(pEntry, rText);
+ return;
+ }
+
+ ++col; //skip dummy/expander column
+
+ // 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);
+ }
+
+ virtual void set_text(int pos, const OUString& rText, int col) override
+ {
+ SvTreeListEntry* pEntry = m_xTreeView->GetEntry(nullptr, pos);
+ set_text(pEntry, rText, col);
+ }
+
+ void set_sensitive(SvTreeListEntry* pEntry, bool bSensitive, int col)
+ {
+ if (col == -1)
+ {
+ auto nFlags = pEntry->GetFlags() & ~SvTLEntryFlags::SEMITRANSPARENT;
+ if (!bSensitive)
+ nFlags = nFlags | SvTLEntryFlags::SEMITRANSPARENT;
+ pEntry->SetFlags(nFlags);
+ const sal_uInt16 nCount = pEntry->ItemCount();
+ for (sal_uInt16 nCur = 0; nCur < nCount; ++nCur)
+ {
+ SvLBoxItem& rItem = pEntry->GetItem(nCur);
+ if (rItem.GetType() == SvLBoxItemType::String)
+ {
+ rItem.Enable(bSensitive);
+ InvalidateModelEntry(pEntry);
+ break;
+ }
+ }
+ return;
+ }
+
+ ++col; //skip dummy/expander column
+
+ assert(col >= 0 && o3tl::make_unsigned(col) < pEntry->ItemCount());
+ SvLBoxItem& rItem = pEntry->GetItem(col);
+ rItem.Enable(bSensitive);
+
+ InvalidateModelEntry(pEntry);
+ }
+
+ using SalInstanceWidget::set_sensitive;
+
+ virtual void set_sensitive(int pos, bool bSensitive, int col) override
+ {
+ SvTreeListEntry* pEntry = m_xTreeView->GetEntry(nullptr, pos);
+ set_sensitive(pEntry, bSensitive, col);
+ }
+
+ virtual void set_sensitive(const weld::TreeIter& rIter, bool bSensitive, int col) override
+ {
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ set_sensitive(rVclIter.iter, bSensitive, col);
+ }
+
+ virtual TriState get_toggle(int pos, int col) const override
+ {
+ SvTreeListEntry* pEntry = m_xTreeView->GetEntry(nullptr, pos);
+ return ::get_toggle(pEntry, col);
+ }
+
+ virtual TriState get_toggle(const weld::TreeIter& rIter, int col) const override
+ {
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ return ::get_toggle(rVclIter.iter, col);
+ }
+
+ void set_toggle(SvTreeListEntry* pEntry, TriState eState, int col)
+ {
+ bool bRadio = std::find(m_aRadioIndexes.begin(), m_aRadioIndexes.end(), col)
+ != m_aRadioIndexes.end();
+ ++col; //skip dummy/expander column
+
+ // 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 = bRadio ? &m_aRadioButtonData : &m_aCheckButtonData;
+
+ // if we want to have the implicit auto-sizing of the checkbox
+ // column we need to call EnableCheckButton and CheckBoxInserted to
+ // let it figure out that width. But we don't want to override any
+ // explicitly set column width, so disable this if we've set
+ // explicit column widths
+ if (!m_bDisableCheckBoxAutoWidth)
+ {
+ if (!(m_xTreeView->GetTreeFlags() & SvTreeFlags::CHKBTN))
+ {
+ m_xTreeView->EnableCheckButton(pData);
+ // EnableCheckButton clobbered this, restore it
+ pData->SetLink(LINK(this, SalInstanceTreeView, ToggleHdl));
+ }
+ }
+
+ pEntry->AddItem(std::make_unique<SvLBoxButton>(pData));
+ SvViewDataEntry* pViewData = m_xTreeView->GetViewDataEntry(pEntry);
+ m_xTreeView->InitViewData(pViewData, pEntry);
+
+ if (!m_bDisableCheckBoxAutoWidth)
+ m_xTreeView->CheckBoxInserted(pEntry);
+ }
+
+ assert(col >= 0 && o3tl::make_unsigned(col) < pEntry->ItemCount());
+ 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);
+ }
+
+ virtual void set_toggle(int pos, TriState eState, int col) override
+ {
+ SvTreeListEntry* pEntry = m_xTreeView->GetEntry(nullptr, pos);
+ set_toggle(pEntry, eState, col);
+ }
+
+ virtual void set_toggle(const weld::TreeIter& rIter, TriState eState, int col) override
+ {
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ set_toggle(rVclIter.iter, eState, col);
+ }
+
+ virtual void set_extra_row_indent(const weld::TreeIter& rIter, int nIndentLevel) override
+ {
+ weld::TreeIter& rNonConstIter = const_cast<weld::TreeIter&>(rIter);
+ SalInstanceTreeIter& rVclIter = static_cast<SalInstanceTreeIter&>(rNonConstIter);
+ rVclIter.iter->SetExtraIndent(nIndentLevel);
+ }
+
+ void set_text_emphasis(SvTreeListEntry* pEntry, bool bOn, int col)
+ {
+ ++col; //skip dummy/expander column
+
+ 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);
+ }
+
+ virtual void set_text_emphasis(const weld::TreeIter& rIter, bool bOn, int col) override
+ {
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ set_text_emphasis(rVclIter.iter, bOn, col);
+ }
+
+ virtual void set_text_emphasis(int pos, bool bOn, int col) override
+ {
+ SvTreeListEntry* pEntry = m_xTreeView->GetEntry(nullptr, pos);
+ set_text_emphasis(pEntry, bOn, col);
+ }
+
+ virtual bool get_text_emphasis(const weld::TreeIter& rIter, int col) const override
+ {
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ return ::get_text_emphasis(rVclIter.iter, col);
+ }
+
+ virtual bool get_text_emphasis(int pos, int col) const override
+ {
+ SvTreeListEntry* pEntry = m_xTreeView->GetEntry(nullptr, pos);
+ return ::get_text_emphasis(pEntry, col);
+ }
+
+ void set_text_align(SvTreeListEntry* pEntry, double fAlign, int col)
+ {
+ ++col; //skip dummy/expander column
+
+ 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);
+ }
+
+ virtual void set_text_align(const weld::TreeIter& rIter, double fAlign, int col) override
+ {
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ set_text_align(rVclIter.iter, fAlign, col);
+ }
+
+ virtual void set_text_align(int pos, double fAlign, int col) override
+ {
+ SvTreeListEntry* pEntry = m_xTreeView->GetEntry(nullptr, pos);
+ set_text_align(pEntry, fAlign, col);
+ }
+
+ virtual void connect_editing(
+ const Link<const weld::TreeIter&, bool>& rStartLink,
+ const Link<const std::pair<const weld::TreeIter&, OUString>&, bool>& rEndLink) override
+ {
+ m_xTreeView->EnableInplaceEditing(rStartLink.IsSet() || rEndLink.IsSet());
+ weld::TreeView::connect_editing(rStartLink, rEndLink);
+ }
+
+ virtual void start_editing(const weld::TreeIter& rIter) override
+ {
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ m_xTreeView->EditEntry(rVclIter.iter);
+ }
+
+ virtual void end_editing() override { m_xTreeView->EndEditing(); }
+
+ void set_image(SvTreeListEntry* pEntry, const Image& rImage, int col)
+ {
+ if (col == -1)
+ {
+ m_xTreeView->SetExpandedEntryBmp(pEntry, rImage);
+ m_xTreeView->SetCollapsedEntryBmp(pEntry, rImage);
+ return;
+ }
+
+ ++col; //skip dummy/expander column
+
+ // 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->SetEntryHeight(pEntry);
+ InvalidateModelEntry(pEntry);
+ }
+
+ virtual void set_image(int pos, const OUString& rImage, int col) override
+ {
+ set_image(m_xTreeView->GetEntry(nullptr, pos), createImage(rImage), col);
+ }
+
+ virtual void set_image(int pos, const css::uno::Reference<css::graphic::XGraphic>& rImage,
+ int col) override
+ {
+ set_image(m_xTreeView->GetEntry(nullptr, pos), Image(rImage), col);
+ }
+
+ virtual void set_image(int pos, VirtualDevice& rImage, int col) override
+ {
+ set_image(m_xTreeView->GetEntry(nullptr, pos), createImage(rImage), col);
+ }
+
+ virtual void set_image(const weld::TreeIter& rIter, const OUString& rImage, int col) override
+ {
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ set_image(rVclIter.iter, createImage(rImage), col);
+ }
+
+ virtual void set_image(const weld::TreeIter& rIter,
+ const css::uno::Reference<css::graphic::XGraphic>& rImage,
+ int col) override
+ {
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ set_image(rVclIter.iter, Image(rImage), col);
+ }
+
+ virtual void set_image(const weld::TreeIter& rIter, VirtualDevice& rImage, int col) override
+ {
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ set_image(rVclIter.iter, createImage(rImage), col);
+ }
+
+ const OUString* getEntryData(int index) const
+ {
+ SvTreeListEntry* pEntry = m_xTreeView->GetEntry(nullptr, index);
+ return pEntry ? static_cast<const OUString*>(pEntry->GetUserData()) : nullptr;
+ }
+
+ virtual OUString get_id(int pos) const override
+ {
+ const OUString* pRet = getEntryData(pos);
+ if (!pRet)
+ return OUString();
+ return *pRet;
+ }
+
+ void set_id(SvTreeListEntry* pEntry, const OUString& rId)
+ {
+ m_aUserData.emplace_back(std::make_unique<OUString>(rId));
+ pEntry->SetUserData(m_aUserData.back().get());
+ }
+
+ virtual void set_id(int pos, const OUString& rId) override
+ {
+ SvTreeListEntry* pEntry = m_xTreeView->GetEntry(nullptr, pos);
+ set_id(pEntry, rId);
+ }
+
+ virtual int get_selected_index() const override
+ {
+ assert(m_xTreeView->IsUpdateMode() && "don't request selection when frozen");
+ SvTreeListEntry* pEntry = m_xTreeView->FirstSelected();
+ if (!pEntry)
+ return -1;
+ return SvTreeList::GetRelPos(pEntry);
+ }
+
+ virtual OUString get_selected_text() const override
+ {
+ assert(m_xTreeView->IsUpdateMode() && "don't request selection when frozen");
+ if (SvTreeListEntry* pEntry = m_xTreeView->FirstSelected())
+ return m_xTreeView->GetEntryText(pEntry);
+ return OUString();
+ }
+
+ virtual OUString get_selected_id() const override
+ {
+ 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();
+ }
+
+ virtual std::unique_ptr<weld::TreeIter>
+ make_iterator(const weld::TreeIter* pOrig) const override
+ {
+ return std::unique_ptr<weld::TreeIter>(
+ new SalInstanceTreeIter(static_cast<const SalInstanceTreeIter*>(pOrig)));
+ }
+
+ virtual void copy_iterator(const weld::TreeIter& rSource, weld::TreeIter& rDest) const override
+ {
+ const SalInstanceTreeIter& rVclSource(static_cast<const SalInstanceTreeIter&>(rSource));
+ SalInstanceTreeIter& rVclDest(static_cast<SalInstanceTreeIter&>(rDest));
+ rVclDest.iter = rVclSource.iter;
+ }
+
+ virtual bool get_selected(weld::TreeIter* pIter) const override
+ {
+ SvTreeListEntry* pEntry = m_xTreeView->FirstSelected();
+ auto pVclIter = static_cast<SalInstanceTreeIter*>(pIter);
+ if (pVclIter)
+ pVclIter->iter = pEntry;
+ return pEntry != nullptr;
+ }
+
+ virtual bool get_cursor(weld::TreeIter* pIter) const override
+ {
+ SvTreeListEntry* pEntry = m_xTreeView->GetCurEntry();
+ auto pVclIter = static_cast<SalInstanceTreeIter*>(pIter);
+ if (pVclIter)
+ pVclIter->iter = pEntry;
+ return pEntry != nullptr;
+ }
+
+ virtual void set_cursor(const weld::TreeIter& rIter) override
+ {
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ disable_notify_events();
+ m_xTreeView->SetCurEntry(rVclIter.iter);
+ enable_notify_events();
+ }
+
+ virtual bool get_iter_first(weld::TreeIter& rIter) const override
+ {
+ SalInstanceTreeIter& rVclIter = static_cast<SalInstanceTreeIter&>(rIter);
+ rVclIter.iter = m_xTreeView->GetEntry(0);
+ return rVclIter.iter != nullptr;
+ }
+
+ virtual bool iter_next_sibling(weld::TreeIter& rIter) const override
+ {
+ SalInstanceTreeIter& rVclIter = static_cast<SalInstanceTreeIter&>(rIter);
+ rVclIter.iter = rVclIter.iter->NextSibling();
+ return rVclIter.iter != nullptr;
+ }
+
+ virtual bool iter_previous_sibling(weld::TreeIter& rIter) const override
+ {
+ SalInstanceTreeIter& rVclIter = static_cast<SalInstanceTreeIter&>(rIter);
+ rVclIter.iter = rVclIter.iter->PrevSibling();
+ return rVclIter.iter != nullptr;
+ }
+
+ virtual bool iter_next(weld::TreeIter& rIter) const override
+ {
+ 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;
+ }
+
+ virtual bool iter_previous(weld::TreeIter& rIter) const override
+ {
+ 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;
+ }
+
+ virtual bool iter_next_visible(weld::TreeIter& rIter) const override
+ {
+ SalInstanceTreeIter& rVclIter = static_cast<SalInstanceTreeIter&>(rIter);
+ rVclIter.iter = m_xTreeView->NextVisible(rVclIter.iter);
+ if (rVclIter.iter && IsDummyEntry(rVclIter.iter))
+ return iter_next_visible(rVclIter);
+ return rVclIter.iter != nullptr;
+ }
+
+ virtual bool iter_children(weld::TreeIter& rIter) const override
+ {
+ 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;
+ }
+
+ virtual bool iter_parent(weld::TreeIter& rIter) const override
+ {
+ SalInstanceTreeIter& rVclIter = static_cast<SalInstanceTreeIter&>(rIter);
+ rVclIter.iter = m_xTreeView->GetParent(rVclIter.iter);
+ return rVclIter.iter != nullptr;
+ }
+
+ virtual void remove(const weld::TreeIter& rIter) override
+ {
+ disable_notify_events();
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ m_xTreeView->RemoveEntry(rVclIter.iter);
+ enable_notify_events();
+ }
+
+ virtual void select(const weld::TreeIter& rIter) override
+ {
+ 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();
+ }
+
+ virtual void scroll_to_row(const weld::TreeIter& rIter) override
+ {
+ 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();
+ }
+
+ virtual void unselect(const weld::TreeIter& rIter) override
+ {
+ 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();
+ }
+
+ virtual int get_iter_depth(const weld::TreeIter& rIter) const override
+ {
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ return m_xTreeView->GetModel()->GetDepth(rVclIter.iter);
+ }
+
+ virtual bool iter_has_child(const weld::TreeIter& rIter) const override
+ {
+ weld::TreeIter& rNonConstIter = const_cast<weld::TreeIter&>(rIter);
+ SalInstanceTreeIter& rVclIter = static_cast<SalInstanceTreeIter&>(rNonConstIter);
+ SvTreeListEntry* restore(rVclIter.iter);
+ bool ret = iter_children(rNonConstIter);
+ rVclIter.iter = restore;
+ return ret;
+ }
+
+ virtual bool get_row_expanded(const weld::TreeIter& rIter) const override
+ {
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ return m_xTreeView->IsExpanded(rVclIter.iter);
+ }
+
+ virtual bool get_children_on_demand(const weld::TreeIter& rIter) const override
+ {
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ if (m_aExpandingPlaceHolderParents.count(rVclIter.iter))
+ return true;
+ return GetPlaceHolderChild(rVclIter.iter) != nullptr;
+ }
+
+ virtual void set_children_on_demand(const weld::TreeIter& rIter, bool bChildrenOnDemand) override
+ {
+ 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();
+ }
+
+ virtual void expand_row(const weld::TreeIter& rIter) override
+ {
+ assert(m_xTreeView->IsUpdateMode() && "don't expand when frozen");
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ if (!m_xTreeView->IsExpanded(rVclIter.iter) && signal_expanding(rIter))
+ m_xTreeView->Expand(rVclIter.iter);
+ }
+
+ virtual void collapse_row(const weld::TreeIter& rIter) override
+ {
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ if (m_xTreeView->IsExpanded(rVclIter.iter) && signal_collapsing(rIter))
+ m_xTreeView->Collapse(rVclIter.iter);
+ }
+
+ virtual OUString get_text(const weld::TreeIter& rIter, int col) const override
+ {
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ return get_text(rVclIter.iter, col);
+ }
+
+ virtual void set_text(const weld::TreeIter& rIter, const OUString& rText, int col) override
+ {
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ set_text(rVclIter.iter, rText, col);
+ }
+
+ virtual OUString get_id(const weld::TreeIter& rIter) const override
+ {
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ const OUString* pStr = static_cast<const OUString*>(rVclIter.iter->GetUserData());
+ if (pStr)
+ return *pStr;
+ return OUString();
+ }
+
+ virtual void set_id(const weld::TreeIter& rIter, const OUString& rId) override
+ {
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ set_id(rVclIter.iter, rId);
+ }
+
+ virtual void enable_drag_source(rtl::Reference<TransferDataContainer>& rHelper,
+ sal_uInt8 eDNDConstants) override
+ {
+ m_xTreeView->SetDragHelper(rHelper, eDNDConstants);
+ }
+
+ virtual void set_selection_mode(SelectionMode eMode) override
+ {
+ m_xTreeView->SetSelectionMode(eMode);
+ }
+
+ virtual void all_foreach(const std::function<bool(weld::TreeIter&)>& func) override
+ {
+ UpdateGuardIfHidden aGuard(*m_xTreeView);
+
+ SalInstanceTreeIter aVclIter(m_xTreeView->First());
+ while (aVclIter.iter)
+ {
+ if (func(aVclIter))
+ return;
+ iter_next(aVclIter);
+ }
+ }
+
+ virtual void selected_foreach(const std::function<bool(weld::TreeIter&)>& func) override
+ {
+ SalInstanceTreeIter aVclIter(m_xTreeView->FirstSelected());
+ while (aVclIter.iter)
+ {
+ if (func(aVclIter))
+ return;
+ aVclIter.iter = m_xTreeView->NextSelected(aVclIter.iter);
+ }
+ }
+
+ virtual void visible_foreach(const std::function<bool(weld::TreeIter&)>& func) override
+ {
+ SalInstanceTreeIter aVclIter(m_xTreeView->GetFirstEntryInView());
+ while (aVclIter.iter)
+ {
+ if (func(aVclIter))
+ return;
+ aVclIter.iter = m_xTreeView->GetNextEntryInView(aVclIter.iter);
+ }
+ }
+
+ virtual void connect_visible_range_changed(const Link<weld::TreeView&, void>& rLink) override
+ {
+ weld::TreeView::connect_visible_range_changed(rLink);
+ m_xTreeView->SetScrolledHdl(LINK(this, SalInstanceTreeView, VisibleRangeChangedHdl));
+ }
+
+ virtual void remove_selection() override
+ {
+ 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();
+ }
+
+ virtual bool is_selected(const weld::TreeIter& rIter) const override
+ {
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ return m_xTreeView->IsSelected(rVclIter.iter);
+ }
+
+ virtual int get_iter_index_in_parent(const weld::TreeIter& rIter) const override
+ {
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ return SvTreeList::GetRelPos(rVclIter.iter);
+ }
+
+ virtual int iter_compare(const weld::TreeIter& a, const weld::TreeIter& b) const override
+ {
+ 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;
+ }
+
+ virtual void move_subtree(weld::TreeIter& rNode, const weld::TreeIter* pNewParent,
+ int nIndexInNewParent) override
+ {
+ 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);
+ }
+
+ virtual int count_selected_rows() const override { return m_xTreeView->GetSelectionCount(); }
+
+ virtual int get_height_rows(int nRows) const override
+ {
+ return m_xTreeView->GetEntryHeight() * nRows;
+ }
+
+ virtual void make_sorted() override
+ {
+ 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);
+ }
+
+ virtual void set_sort_func(
+ const std::function<int(const weld::TreeIter&, const weld::TreeIter&)>& func) override
+ {
+ weld::TreeView::set_sort_func(func);
+ SvTreeList* pListModel = m_xTreeView->GetModel();
+ pListModel->Resort();
+ }
+
+ virtual void make_unsorted() override
+ {
+ m_xTreeView->SetStyle(m_xTreeView->GetStyle() & ~WB_SORT);
+ }
+
+ virtual void set_sort_order(bool bAscending) override
+ {
+ SvTreeList* pListModel = m_xTreeView->GetModel();
+ pListModel->SetSortMode(bAscending ? SortAscending : SortDescending);
+ pListModel->Resort();
+ }
+
+ virtual bool get_sort_order() const override
+ {
+ return m_xTreeView->GetModel()->GetSortMode() == SortAscending;
+ }
+
+ virtual void set_sort_indicator(TriState eState, int col) override
+ {
+ if (col == -1)
+ col = 0;
+
+ 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);
+ 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);
+ }
+ }
+
+ virtual TriState get_sort_indicator(int col) const override
+ {
+ if (col == -1)
+ col = 0;
+
+ 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;
+ }
+
+ virtual int get_sort_column() const override { return m_nSortColumn; }
+
+ virtual void set_sort_column(int nColumn) override
+ {
+ if (nColumn == -1)
+ {
+ make_unsorted();
+ m_nSortColumn = -1;
+ return;
+ }
+
+ if (nColumn != m_nSortColumn)
+ {
+ m_nSortColumn = nColumn;
+ m_xTreeView->GetModel()->Resort();
+ }
+ }
+
+ SvTabListBox& getTreeView() { return *m_xTreeView; }
+
+ virtual bool get_dest_row_at_pos(const Point& rPos, weld::TreeIter* pResult, bool bHighLightTarget) override
+ {
+ LclTabListBox* pTreeView = !bHighLightTarget ? dynamic_cast<LclTabListBox*>(m_xTreeView.get()) : nullptr;
+ SvTreeListEntry* pTarget = pTreeView ? pTreeView->GetTargetAtPoint(rPos, false) : m_xTreeView->GetDropTarget(rPos);
+
+ if (pTarget && pResult)
+ {
+ SalInstanceTreeIter& rSalIter = static_cast<SalInstanceTreeIter&>(*pResult);
+ rSalIter.iter = pTarget;
+ }
+
+ return pTarget != nullptr;
+ }
+
+ virtual void unset_drag_dest_row() override
+ {
+ m_xTreeView->UnsetDropTarget();
+ }
+
+ virtual tools::Rectangle get_row_area(const weld::TreeIter& rIter) const override
+ {
+ return m_xTreeView->GetBoundingRect(static_cast<const SalInstanceTreeIter&>(rIter).iter);
+ }
+
+ virtual TreeView* get_drag_source() const override { return g_DragSource; }
+
+ virtual int vadjustment_get_value() const override
+ {
+ int nValue = -1;
+ const SvTreeListEntry* pEntry = m_xTreeView->GetFirstEntryInView();
+ if (pEntry)
+ nValue = m_xTreeView->GetAbsPos(pEntry);
+ return nValue;
+ }
+
+ virtual void vadjustment_set_value(int nValue) override
+ {
+ 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);
+ }
+
+ virtual ~SalInstanceTreeView() override
+ {
+ LclHeaderTabListBox* pHeaderBox = dynamic_cast<LclHeaderTabListBox*>(m_xTreeView.get());
+ if (pHeaderBox)
+ {
+ if (HeaderBar* pHeaderBar = pHeaderBox->GetHeaderBar())
+ {
+ pHeaderBar->SetSelectHdl(Link<HeaderBar*, void>());
+ pHeaderBar->SetEndDragHdl(Link<HeaderBar*, void>());
+ }
+ }
+ else
+ {
+ static_cast<LclTabListBox&>(*m_xTreeView).SetEndDragHdl(Link<SvTreeListBox*, void>());
+ static_cast<LclTabListBox&>(*m_xTreeView).SetStartDragHdl(Link<SvTreeListBox*, bool>());
+ static_cast<LclTabListBox&>(*m_xTreeView)
+ .SetModelChangedHdl(Link<SvTreeListBox*, void>());
+ }
+ m_xTreeView->SetPopupMenuHdl(Link<const CommandEvent&, bool>());
+ m_xTreeView->SetExpandingHdl(Link<SvTreeListBox*, bool>());
+ m_xTreeView->SetDoubleClickHdl(Link<SvTreeListBox*, bool>());
+ m_xTreeView->SetSelectHdl(Link<SvTreeListBox*, void>());
+ m_xTreeView->SetDeselectHdl(Link<SvTreeListBox*, void>());
+ m_xTreeView->SetScrolledHdl(Link<SvTreeListBox*, void>());
+ m_xTreeView->SetTooltipHdl(Link<const HelpEvent&, bool>());
+ m_xTreeView->SetCustomRenderHdl(Link<svtree_render_args, void>());
+ m_xTreeView->SetCustomMeasureHdl(Link<svtree_measure_args, Size>());
+ }
+};
+
+IMPL_LINK(SalInstanceTreeView, TooltipHdl, const HelpEvent&, rHEvt, bool)
+{
+ if (notify_events_disabled())
+ return false;
+ Point aPos(m_xTreeView->ScreenToOutputPixel(rHEvt.GetMousePosPixel()));
+ SvTreeListEntry* pEntry = m_xTreeView->GetEntry(aPos);
+ if (pEntry)
+ {
+ SalInstanceTreeIter aIter(pEntry);
+ OUString aTooltip = signal_query_tooltip(aIter);
+ if (aTooltip.isEmpty())
+ return false;
+ Size aSize(m_xTreeView->GetOutputSizePixel().Width(), m_xTreeView->GetEntryHeight());
+ tools::Rectangle aScreenRect(
+ m_xTreeView->OutputToScreenPixel(m_xTreeView->GetEntryPosition(pEntry)), aSize);
+ Help::ShowQuickHelp(m_xTreeView, aScreenRect, aTooltip);
+ }
+ return true;
+}
+
+IMPL_LINK(SalInstanceTreeView, CustomRenderHdl, svtree_render_args, payload, void)
+{
+ vcl::RenderContext& rRenderDevice = std::get<0>(payload);
+ const tools::Rectangle& rRect = std::get<1>(payload);
+ const SvTreeListEntry& rEntry = std::get<2>(payload);
+ const OUString* pId = static_cast<const OUString*>(rEntry.GetUserData());
+ if (!pId)
+ return;
+ signal_custom_render(rRenderDevice, rRect, m_xTreeView->IsSelected(&rEntry), *pId);
+}
+
+IMPL_LINK(SalInstanceTreeView, CustomMeasureHdl, svtree_measure_args, payload, Size)
+{
+ vcl::RenderContext& rRenderDevice = payload.first;
+ const SvTreeListEntry& rEntry = payload.second;
+ const OUString* pId = static_cast<const OUString*>(rEntry.GetUserData());
+ if (!pId)
+ return Size();
+ return signal_custom_get_size(rRenderDevice, *pId);
+}
+
+IMPL_LINK(SalInstanceTreeView, CompareHdl, const SvSortData&, rSortData, sal_Int32)
+{
+ const SvTreeListEntry* pLHS = rSortData.pLeft;
+ const SvTreeListEntry* pRHS = rSortData.pRight;
+ assert(pLHS && pRHS);
+
+ if (m_aCustomSort)
+ return m_aCustomSort(SalInstanceTreeIter(const_cast<SvTreeListEntry*>(pLHS)),
+ SalInstanceTreeIter(const_cast<SvTreeListEntry*>(pRHS)));
+
+ const SvLBoxString* pLeftTextItem;
+ const SvLBoxString* pRightTextItem;
+
+ if (m_nSortColumn != -1)
+ {
+ size_t col = m_nSortColumn;
+
+ ++col; //skip dummy/expander column
+
+ 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);
+ }
+
+ // toggled signal handlers can query get_cursor to get which
+ // node was clicked
+ m_xTreeView->pImpl->m_pCursor = pEntry;
+
+ for (int i = 1, nCount = pEntry->ItemCount(); i < nCount; ++i)
+ {
+ SvLBoxItem& rItem = pEntry->GetItem(i);
+ if (&rItem == pBox)
+ {
+ int nRow = SvTreeList::GetRelPos(pEntry);
+ int nCol = i - 1; // less dummy/expander column
+ signal_toggled(std::make_pair(nRow, 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)
+ 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<long> aTabPositions;
+ aTabPositions.push_back(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
+
+ // 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(aIter);
+
+ 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(std::pair<const weld::TreeIter&, OUString>(
+ SalInstanceTreeIter(rIterString.first), rIterString.second));
+}
+
+class SalInstanceIconView : public SalInstanceContainer, 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);
+
+public:
+ SalInstanceIconView(::IconView* pIconView, SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : SalInstanceContainer(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));
+ }
+
+ virtual void freeze() override
+ {
+ SalInstanceWidget::freeze();
+ m_xIconView->SetUpdateMode(false);
+ }
+
+ virtual void thaw() override
+ {
+ m_xIconView->SetUpdateMode(true);
+ SalInstanceWidget::thaw();
+ }
+
+ virtual void insert(int pos, const OUString* pStr, const OUString* pId,
+ const OUString* pIconName, weld::TreeIter* pRet) override
+ {
+ 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();
+ }
+
+ virtual OUString get_selected_id() const override
+ {
+ 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();
+ }
+
+ virtual OUString get_selected_text() const override
+ {
+ assert(m_xIconView->IsUpdateMode() && "don't request selection when frozen");
+ if (SvTreeListEntry* pEntry = m_xIconView->FirstSelected())
+ return m_xIconView->GetEntryText(pEntry);
+ return OUString();
+ }
+
+ virtual int count_selected_items() const override { return m_xIconView->GetSelectionCount(); }
+
+ virtual void select(int pos) override
+ {
+ 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();
+ }
+
+ virtual void unselect(int pos) override
+ {
+ 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();
+ }
+
+ virtual int n_children() const override
+ {
+ return m_xIconView->GetModel()->GetChildList(nullptr).size();
+ }
+
+ virtual std::unique_ptr<weld::TreeIter>
+ make_iterator(const weld::TreeIter* pOrig) const override
+ {
+ return std::unique_ptr<weld::TreeIter>(
+ new SalInstanceTreeIter(static_cast<const SalInstanceTreeIter*>(pOrig)));
+ }
+
+ virtual bool get_selected(weld::TreeIter* pIter) const override
+ {
+ SvTreeListEntry* pEntry = m_xIconView->FirstSelected();
+ auto pVclIter = static_cast<SalInstanceTreeIter*>(pIter);
+ if (pVclIter)
+ pVclIter->iter = pEntry;
+ return pEntry != nullptr;
+ }
+
+ virtual bool get_cursor(weld::TreeIter* pIter) const override
+ {
+ SvTreeListEntry* pEntry = m_xIconView->GetCurEntry();
+ auto pVclIter = static_cast<SalInstanceTreeIter*>(pIter);
+ if (pVclIter)
+ pVclIter->iter = pEntry;
+ return pEntry != nullptr;
+ }
+
+ virtual void set_cursor(const weld::TreeIter& rIter) override
+ {
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ disable_notify_events();
+ m_xIconView->SetCurEntry(rVclIter.iter);
+ enable_notify_events();
+ }
+
+ virtual bool get_iter_first(weld::TreeIter& rIter) const override
+ {
+ SalInstanceTreeIter& rVclIter = static_cast<SalInstanceTreeIter&>(rIter);
+ rVclIter.iter = m_xIconView->GetEntry(0);
+ return rVclIter.iter != nullptr;
+ }
+
+ virtual void scroll_to_item(const weld::TreeIter& rIter) override
+ {
+ 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();
+ }
+
+ virtual void selected_foreach(const std::function<bool(weld::TreeIter&)>& func) override
+ {
+ SalInstanceTreeIter aVclIter(m_xIconView->FirstSelected());
+ while (aVclIter.iter)
+ {
+ if (func(aVclIter))
+ return;
+ aVclIter.iter = m_xIconView->NextSelected(aVclIter.iter);
+ }
+ }
+
+ virtual OUString get_id(const weld::TreeIter& rIter) const override
+ {
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ const OUString* pStr = static_cast<const OUString*>(rVclIter.iter->GetUserData());
+ if (pStr)
+ return *pStr;
+ return OUString();
+ }
+
+ virtual void clear() override
+ {
+ disable_notify_events();
+ m_xIconView->Clear();
+ m_aUserData.clear();
+ enable_notify_events();
+ }
+
+ virtual ~SalInstanceIconView() override
+ {
+ 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();
+}
+
+double SalInstanceSpinButton::toField(int nValue) const
+{
+ return static_cast<double>(nValue) / Power10(get_digits());
+ }
+
+int SalInstanceSpinButton::fromField(double fValue) const
+{
+ return FRound(fValue * Power10(get_digits()));
+}
+
+SalInstanceSpinButton::SalInstanceSpinButton(FormattedField* pButton, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : SalInstanceEntry(pButton, pBuilder, bTakeOwnership)
+ , m_xButton(pButton)
+{
+ m_xButton->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_xButton->SetOutputHdl(LINK(this, SalInstanceSpinButton, OutputHdl));
+ m_xButton->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));
+}
+
+int SalInstanceSpinButton::get_value() const
+{
+ return fromField(m_xButton->GetValue());
+ }
+
+void SalInstanceSpinButton::set_value(int value)
+{
+ m_xButton->SetValue(toField(value));
+ }
+
+void SalInstanceSpinButton::set_range(int min, int max)
+{
+ m_xButton->SetMinValue(toField(min));
+ m_xButton->SetMaxValue(toField(max));
+}
+
+void SalInstanceSpinButton::get_range(int& min, int& max) const
+{
+ min = fromField(m_xButton->GetMinValue());
+ max = fromField(m_xButton->GetMaxValue());
+}
+
+void SalInstanceSpinButton::set_increments(int step, int /*page*/)
+{
+ m_xButton->SetSpinSize(toField(step));
+}
+
+void SalInstanceSpinButton::get_increments(int& step, int& page) const
+{
+ step = fromField(m_xButton->GetSpinSize());
+ page = fromField(m_xButton->GetSpinSize());
+}
+
+void SalInstanceSpinButton::set_digits(unsigned int digits)
+{
+ m_xButton->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();
+}
+
+//so with hh::mm::ss, incrementing mm will not reset ss
+void SalInstanceSpinButton::DisableRemainderFactor()
+{
+ m_xButton->DisableRemainderFactor();
+}
+
+//off by default for direct SpinButtons, MetricSpinButton enables it
+void SalInstanceSpinButton::SetUseThousandSep()
+{
+ m_xButton->SetThousandsSep(true);
+}
+
+unsigned int SalInstanceSpinButton::get_digits() const
+{
+ return m_xButton->GetDecimalDigits();
+}
+
+SalInstanceSpinButton::~SalInstanceSpinButton()
+{
+ if (Edit* pEdit = m_xButton->GetSubEdit())
+ pEdit->SetActivateHdl(Link<Edit&, bool>());
+ else
+ m_xButton->SetActivateHdl(Link<Edit&, bool>());
+ m_xButton->SetInputHdl(Link<sal_Int64*, TriState>());
+ m_xButton->SetOutputHdl(Link<Edit&, 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, Edit&, bool) { return signal_output(); }
+
+IMPL_LINK(SalInstanceSpinButton, InputHdl, sal_Int64*, pResult, TriState)
+{
+ int nResult;
+ TriState eRet = signal_input(&nResult);
+ if (eRet == TRISTATE_TRUE)
+ *pResult = nResult;
+ return eRet;
+}
+
+namespace
+{
+class SalInstanceFormattedSpinButton : public SalInstanceEntry,
+ public virtual weld::FormattedSpinButton
+{
+private:
+ VclPtr<FormattedField> m_xButton;
+
+public:
+ SalInstanceFormattedSpinButton(FormattedField* pButton, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : SalInstanceEntry(pButton, pBuilder, bTakeOwnership)
+ , m_xButton(pButton)
+ {
+ // #i6278# allow more decimal places than the output format. As
+ // the numbers shown in the edit fields are used for input, it makes more
+ // sense to display the values in the input format rather than the output
+ // format.
+ m_xButton->UseInputStringForFormatting();
+ }
+
+ virtual double get_value() const override { return m_xButton->GetValue(); }
+
+ virtual void set_value(double value) override { m_xButton->SetValue(value); }
+
+ virtual void set_range(double min, double max) override
+ {
+ m_xButton->SetMinValue(min);
+ m_xButton->SetMaxValue(max);
+ }
+
+ virtual void get_range(double& min, double& max) const override
+ {
+ min = m_xButton->GetMinValue();
+ max = m_xButton->GetMaxValue();
+ }
+
+ virtual void set_formatter(SvNumberFormatter* pFormatter) override
+ {
+ m_xButton->SetFormatter(pFormatter);
+ }
+
+ virtual SvNumberFormatter* get_formatter() override { return m_xButton->GetFormatter(); }
+
+ virtual sal_Int32 get_format_key() const override { return m_xButton->GetFormatKey(); }
+
+ virtual void set_format_key(sal_Int32 nFormatKey) override
+ {
+ m_xButton->SetFormatKey(nFormatKey);
+ }
+
+ virtual void treat_as_number(bool bSet) override { m_xButton->TreatAsNumber(bSet); }
+
+ virtual void set_digits(unsigned int digits) override { m_xButton->SetDecimalDigits(digits); }
+};
+
+}
+
+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_message_type(weld::EntryMessageType eType)
+{
+ if (eType == weld::EntryMessageType::Error)
+ m_xLabel->SetControlBackground(
+ m_xLabel->GetSettings().GetStyleSettings().GetHighlightColor());
+ else if (eType == weld::EntryMessageType::Warning)
+ m_xLabel->SetControlBackground(COL_YELLOW);
+ else
+ m_xLabel->SetControlBackground();
+}
+
+void SalInstanceLabel::set_font(const vcl::Font& rFont)
+{
+ m_xLabel->SetPointFont(*m_xLabel, 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);
+}
+
+namespace
+{
+class SalInstanceTextView : public SalInstanceContainer, 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)
+ : SalInstanceContainer(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));
+ }
+
+ virtual void set_text(const OUString& rText) override
+ {
+ disable_notify_events();
+ m_xTextView->SetText(rText);
+ enable_notify_events();
+ }
+
+ virtual void replace_selection(const OUString& rText) override
+ {
+ disable_notify_events();
+ m_xTextView->ReplaceSelected(rText);
+ enable_notify_events();
+ }
+
+ virtual OUString get_text() const override { return m_xTextView->GetText(); }
+
+ bool get_selection_bounds(int& rStartPos, int& rEndPos) override
+ {
+ const Selection& rSelection = m_xTextView->GetSelection();
+ rStartPos = rSelection.Min();
+ rEndPos = rSelection.Max();
+ return rSelection.Len();
+ }
+
+ virtual void select_region(int nStartPos, int nEndPos) override
+ {
+ disable_notify_events();
+ m_xTextView->SetSelection(Selection(nStartPos, nEndPos < 0 ? SELECTION_MAX : nEndPos));
+ enable_notify_events();
+ }
+
+ virtual void set_editable(bool bEditable) override { m_xTextView->SetReadOnly(!bEditable); }
+
+ virtual void set_monospace(bool bMonospace) override
+ {
+ vcl::Font aOrigFont = m_xTextView->GetControlFont();
+ vcl::Font aFont;
+ if (bMonospace)
+ aFont = OutputDevice::GetDefaultFont(DefaultFontType::UI_FIXED, LANGUAGE_DONTKNOW,
+ GetDefaultFontFlags::OnlyOne, m_xTextView);
+ else
+ aFont = Application::GetSettings().GetStyleSettings().GetFieldFont();
+ aFont.SetFontHeight(aOrigFont.GetFontHeight());
+ m_xTextView->SetFont(aFont);
+ m_xTextView->SetControlFont(aFont);
+ }
+
+ virtual void connect_cursor_position(const Link<TextView&, void>& rLink) override
+ {
+ assert(!m_aCursorPositionHdl.IsSet());
+ m_xTextView->AddEventListener(LINK(this, SalInstanceTextView, CursorListener));
+ weld::TextView::connect_cursor_position(rLink);
+ }
+
+ virtual int vadjustment_get_value() const override
+ {
+ ScrollBar& rVertScrollBar = m_xTextView->GetVScrollBar();
+ return rVertScrollBar.GetThumbPos();
+ }
+
+ virtual void vadjustment_set_value(int value) override
+ {
+ ScrollBar& rVertScrollBar = m_xTextView->GetVScrollBar();
+ rVertScrollBar.SetThumbPos(value);
+ m_aOrigVScrollHdl.Call(&rVertScrollBar);
+ }
+
+ virtual int vadjustment_get_upper() const override
+ {
+ ScrollBar& rVertScrollBar = m_xTextView->GetVScrollBar();
+ return rVertScrollBar.GetRangeMax();
+ }
+
+ virtual int vadjustment_get_lower() const override
+ {
+ ScrollBar& rVertScrollBar = m_xTextView->GetVScrollBar();
+ return rVertScrollBar.GetRangeMin();
+ }
+
+ virtual int vadjustment_get_page_size() const override
+ {
+ ScrollBar& rVertScrollBar = m_xTextView->GetVScrollBar();
+ return rVertScrollBar.GetVisibleSize();
+ }
+
+ virtual ~SalInstanceTextView() override
+ {
+ 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();
+}
+
+namespace
+{
+class SalInstanceExpander : public SalInstanceContainer, public virtual weld::Expander
+{
+private:
+ VclPtr<VclExpander> m_xExpander;
+
+ DECL_LINK(ExpandedHdl, VclExpander&, void);
+
+public:
+ SalInstanceExpander(VclExpander* pExpander, SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : SalInstanceContainer(pExpander, pBuilder, bTakeOwnership)
+ , m_xExpander(pExpander)
+ {
+ m_xExpander->SetExpandedHdl(LINK(this, SalInstanceExpander, ExpandedHdl));
+ }
+
+ virtual bool get_expanded() const override { return m_xExpander->get_expanded(); }
+
+ virtual void set_expanded(bool bExpand) override { m_xExpander->set_expanded(bExpand); }
+
+ virtual ~SalInstanceExpander() override
+ {
+ m_xExpander->SetExpandedHdl(Link<VclExpander&, void>());
+ }
+};
+
+}
+
+IMPL_LINK_NOARG(SalInstanceExpander, ExpandedHdl, VclExpander&, void) { signal_expanded(); }
+
+namespace
+{
+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(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
+ {
+ if (rEvent.GetId() == VclEventId::WindowResize)
+ return;
+ SalInstanceWidget::HandleEventListener(rEvent);
+ }
+
+ virtual void HandleMouseEventListener(VclSimpleEvent& rEvent) override
+ {
+ if (rEvent.GetId() == VclEventId::WindowMouseButtonDown
+ || rEvent.GetId() == VclEventId::WindowMouseButtonUp
+ || rEvent.GetId() == VclEventId::WindowMouseMove)
+ {
+ return;
+ }
+ SalInstanceWidget::HandleMouseEventListener(rEvent);
+ }
+
+ virtual bool HandleKeyEventListener(VclWindowEvent& /*rEvent*/) override { return false; }
+
+public:
+ 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->SetStartDragHdl(LINK(this, SalInstanceDrawingArea, StartDragHdl));
+ }
+
+ virtual void queue_draw() override { m_xDrawingArea->Invalidate(); }
+
+ virtual void queue_draw_area(int x, int y, int width, int height) override
+ {
+ m_xDrawingArea->Invalidate(tools::Rectangle(Point(x, y), Size(width, height)));
+ }
+
+ virtual void queue_resize() override { m_xDrawingArea->queue_resize(); }
+
+ virtual void connect_size_allocate(const Link<const Size&, void>& rLink) override
+ {
+ weld::Widget::connect_size_allocate(rLink);
+ }
+
+ virtual void connect_key_press(const Link<const KeyEvent&, bool>& rLink) override
+ {
+ weld::Widget::connect_key_press(rLink);
+ }
+
+ virtual void connect_key_release(const Link<const KeyEvent&, bool>& rLink) override
+ {
+ weld::Widget::connect_key_release(rLink);
+ }
+
+ virtual void set_cursor(PointerStyle ePointerStyle) override
+ {
+ m_xDrawingArea->SetPointer(ePointerStyle);
+ }
+
+ virtual void set_input_context(const InputContext& rInputContext) override
+ {
+ m_xDrawingArea->SetInputContext(rInputContext);
+ }
+
+ virtual void im_context_set_cursor_location(const tools::Rectangle& rCursorRect, int nExtTextInputWidth) override
+ {
+ tools::Rectangle aCursorRect = m_xDrawingArea->PixelToLogic(rCursorRect);
+ m_xDrawingArea->SetCursorRect(&aCursorRect, m_xDrawingArea->PixelToLogic(Size(nExtTextInputWidth, 0)).Width());
+ }
+
+ virtual a11yref get_accessible_parent() override
+ {
+ vcl::Window* pParent = m_xDrawingArea->GetParent();
+ if (pParent)
+ return pParent->GetAccessible();
+ return css::uno::Reference<css::accessibility::XAccessible>();
+ }
+
+ virtual a11yrelationset get_accessible_relation_set() override
+ {
+ utl::AccessibleRelationSetHelper* pRelationSetHelper = new utl::AccessibleRelationSetHelper;
+ css::uno::Reference<css::accessibility::XAccessibleRelationSet> xSet = pRelationSetHelper;
+ 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 xSet;
+ }
+
+ virtual Point get_accessible_location() override
+ {
+ return m_xDrawingArea->OutputToAbsoluteScreenPixel(Point());
+ }
+
+ virtual void enable_drag_source(rtl::Reference<TransferDataContainer>& rHelper,
+ sal_uInt8 eDNDConstants) override
+ {
+ m_xDrawingArea->SetDragHelper(rHelper, eDNDConstants);
+ }
+
+ virtual ~SalInstanceDrawingArea() override
+ {
+ 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>());
+ }
+
+ virtual OutputDevice& get_ref_device() override { return *m_xDrawingArea; }
+};
+
+}
+
+IMPL_LINK(SalInstanceDrawingArea, PaintHdl, target_and_area, aPayload, void)
+{
+ m_aDrawHdl.Call(aPayload);
+ tools::Rectangle aFocusRect(m_aGetFocusRectHdl.Call(*this));
+ if (!aFocusRect.IsEmpty())
+ DrawFocusRect(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, 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_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; }
+
+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)
+{
+ if (eType == weld::EntryMessageType::Error)
+ m_xComboBox->SetControlForeground(Color(0xf0, 0, 0));
+ else if (eType == weld::EntryMessageType::Warning)
+ m_xComboBox->SetControlForeground(COL_YELLOW);
+ else
+ m_xComboBox->SetControlForeground();
+}
+
+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_entry_font(const vcl::Font& rFont)
+{
+ Edit* pEdit = m_xComboBox->GetSubEdit();
+ assert(pEdit);
+ pEdit->SetPointFont(*pEdit, rFont);
+ m_xComboBox->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);
+}
+
+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));
+ 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_entry_font(const vcl::Font& rFont) override
+ {
+ Edit& rEntry = m_pEntry->getEntry();
+ rEntry.SetPointFont(rEntry, rFont);
+ rEntry.Invalidate();
+ }
+
+ virtual vcl::Font get_entry_font() override
+ {
+ Edit& rEntry = m_pEntry->getEntry();
+ return rEntry.GetPointFont(rEntry);
+ }
+
+ virtual void set_entry_placeholder_text(const OUString& rText) override
+ {
+ Edit& rEntry = m_pEntry->getEntry();
+ rEntry.SetPlaceholderText(rText);
+ }
+
+ virtual void set_entry_editable(bool bEditable) override
+ {
+ Edit& rEntry = m_pEntry->getEntry();
+ rEntry.SetReadOnly(!bEditable);
+ }
+
+ virtual void cut_entry_clipboard() override
+ {
+ Edit& rEntry = m_pEntry->getEntry();
+ rEntry.Cut();
+ }
+
+ virtual void copy_entry_clipboard() override
+ {
+ Edit& rEntry = m_pEntry->getEntry();
+ rEntry.Copy();
+ }
+
+ virtual void paste_entry_clipboard() override
+ {
+ Edit& rEntry = m_pEntry->getEntry();
+ rEntry.Paste();
+ }
+
+ virtual void grab_focus() override { m_xEntry->grab_focus(); }
+
+ virtual void connect_focus_in(const Link<Widget&, void>& rLink) override
+ {
+ m_xEntry->connect_focus_in(rLink);
+ }
+
+ virtual void connect_focus_out(const Link<Widget&, void>& rLink) override
+ {
+ m_xEntry->connect_focus_out(rLink);
+ }
+
+ virtual bool changed_by_direct_pick() const override { return m_bTreeChange; }
+
+ virtual void set_custom_renderer(bool /*bOn*/) override
+ {
+ assert(false && "not implemented");
+ }
+
+ virtual int get_max_mru_count() const override
+ {
+ assert(false && "not implemented");
+ return 0;
+ }
+
+ virtual void set_max_mru_count(int) override
+ {
+ assert(false && "not implemented");
+ }
+
+ virtual OUString get_mru_entries() const override
+ {
+ assert(false && "not implemented");
+ return OUString();
+ }
+
+ virtual void set_mru_entries(const OUString&) override
+ {
+ assert(false && "not implemented");
+ }
+
+ virtual void set_item_menu(const OString&, weld::Menu*) override
+ {
+ assert(false && "not implemented");
+ }
+
+ int get_menu_button_width() const override
+ {
+ assert(false && "not implemented");
+ return 0;
+ }
+
+ VclPtr<VirtualDevice> create_render_virtual_device() const override
+ {
+ return VclPtr<VirtualDevice>::Create();
+ }
+
+ virtual ~SalInstanceEntryTreeView() override
+ {
+ Edit& rEntry = m_pEntry->getEntry();
+ rEntry.RemoveEventListener(LINK(this, SalInstanceEntryTreeView, KeyPressListener));
+ rEntry.SetAutocompleteHdl(Link<Edit&, void>());
+ }
+};
+
+IMPL_LINK(SalInstanceEntryTreeView, KeyPressListener, VclWindowEvent&, rEvent, void)
+{
+ if (rEvent.GetId() != VclEventId::WindowKeyInput)
+ return;
+ const KeyEvent& rKeyEvent = *static_cast<KeyEvent*>(rEvent.GetData());
+ sal_uInt16 nKeyCode = rKeyEvent.GetKeyCode().GetCode();
+ if (nKeyCode == KEY_UP || nKeyCode == KEY_DOWN || nKeyCode == KEY_PAGEUP
+ || nKeyCode == KEY_PAGEDOWN)
+ {
+ 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);
+ }
+}
+
+SalInstanceBuilder::SalInstanceBuilder(vcl::Window* pParent, const OUString& rUIRoot,
+ const OUString& rUIFile)
+ : weld::Builder()
+ , m_xBuilder(new VclBuilder(pParent, rUIRoot, rUIFile, OString(),
+ css::uno::Reference<css::frame::XFrame>(), false))
+{
+}
+
+std::unique_ptr<weld::MessageDialog> SalInstanceBuilder::weld_message_dialog(const OString& id,
+ bool bTakeOwnership)
+{
+ MessageDialog* pMessageDialog = m_xBuilder->get<MessageDialog>(id);
+ std::unique_ptr<weld::MessageDialog> pRet(
+ pMessageDialog ? new SalInstanceMessageDialog(pMessageDialog, this, false) : nullptr);
+ if (bTakeOwnership && pMessageDialog)
+ {
+ assert(!m_aOwnedToplevel && "only one toplevel per .ui allowed");
+ m_aOwnedToplevel.set(pMessageDialog);
+ m_xBuilder->drop_ownership(pMessageDialog);
+ }
+ return pRet;
+}
+
+std::unique_ptr<weld::Dialog> SalInstanceBuilder::weld_dialog(const OString& id,
+ bool bTakeOwnership)
+{
+ Dialog* pDialog = m_xBuilder->get<Dialog>(id);
+ std::unique_ptr<weld::Dialog> pRet(pDialog ? new SalInstanceDialog(pDialog, this, false)
+ : nullptr);
+ if (bTakeOwnership && pDialog)
+ {
+ assert(!m_aOwnedToplevel && "only one toplevel per .ui allowed");
+ m_aOwnedToplevel.set(pDialog);
+ m_xBuilder->drop_ownership(pDialog);
+ }
+ return pRet;
+}
+
+std::unique_ptr<weld::Assistant> SalInstanceBuilder::weld_assistant(const OString& id,
+ bool bTakeOwnership)
+{
+ vcl::RoadmapWizard* pDialog = m_xBuilder->get<vcl::RoadmapWizard>(id);
+ std::unique_ptr<weld::Assistant> pRet(pDialog ? new SalInstanceAssistant(pDialog, this, false)
+ : nullptr);
+ if (bTakeOwnership && 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::Window> SalInstanceBuilder::weld_window(const OString& id,
+ bool bTakeOwnership)
+{
+ SystemWindow* pWindow = m_xBuilder->get<SystemWindow>(id);
+ return pWindow ? std::make_unique<SalInstanceWindow>(pWindow, this, bTakeOwnership) : nullptr;
+}
+
+std::unique_ptr<weld::Widget> SalInstanceBuilder::weld_widget(const OString& id,
+ bool bTakeOwnership)
+{
+ vcl::Window* pWidget = m_xBuilder->get(id);
+ return pWidget ? std::make_unique<SalInstanceWidget>(pWidget, this, bTakeOwnership) : nullptr;
+}
+
+std::unique_ptr<weld::Container> SalInstanceBuilder::weld_container(const OString& id,
+ bool bTakeOwnership)
+{
+ vcl::Window* pContainer = m_xBuilder->get(id);
+ return pContainer ? std::make_unique<SalInstanceContainer>(pContainer, this, bTakeOwnership)
+ : nullptr;
+}
+
+std::unique_ptr<weld::Box> SalInstanceBuilder::weld_box(const OString& id, bool bTakeOwnership)
+{
+ vcl::Window* pContainer = m_xBuilder->get(id);
+ return pContainer ? std::make_unique<SalInstanceBox>(pContainer, this, bTakeOwnership)
+ : nullptr;
+}
+
+std::unique_ptr<weld::Paned> SalInstanceBuilder::weld_paned(const OString& id,
+ bool bTakeOwnership)
+{
+ VclPaned* pPaned = m_xBuilder->get<VclPaned>(id);
+ return pPaned ? std::make_unique<SalInstancePaned>(pPaned, this, bTakeOwnership)
+ : nullptr;
+}
+
+std::unique_ptr<weld::Frame> SalInstanceBuilder::weld_frame(const OString& id, bool bTakeOwnership)
+{
+ VclFrame* pFrame = m_xBuilder->get<VclFrame>(id);
+ std::unique_ptr<weld::Frame> pRet(pFrame ? new SalInstanceFrame(pFrame, this, false) : nullptr);
+ if (bTakeOwnership && pFrame)
+ {
+ assert(!m_aOwnedToplevel && "only one toplevel per .ui allowed");
+ m_aOwnedToplevel.set(pFrame);
+ m_xBuilder->drop_ownership(pFrame);
+ }
+ return pRet;
+}
+
+std::unique_ptr<weld::ScrolledWindow> SalInstanceBuilder::weld_scrolled_window(const OString& id,
+ bool bTakeOwnership)
+{
+ VclScrolledWindow* pScrolledWindow = m_xBuilder->get<VclScrolledWindow>(id);
+ return pScrolledWindow
+ ? std::make_unique<SalInstanceScrolledWindow>(pScrolledWindow, this, bTakeOwnership)
+ : nullptr;
+}
+
+std::unique_ptr<weld::Notebook> SalInstanceBuilder::weld_notebook(const OString& id,
+ bool bTakeOwnership)
+{
+ 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,
+ bTakeOwnership);
+ if (pNotebook->GetType() == WindowType::VERTICALTABCONTROL)
+ return std::make_unique<SalInstanceVerticalNotebook>(
+ static_cast<VerticalTabControl*>(pNotebook), this, bTakeOwnership);
+ return nullptr;
+}
+
+std::unique_ptr<weld::Button> SalInstanceBuilder::weld_button(const OString& id,
+ bool bTakeOwnership)
+{
+ Button* pButton = m_xBuilder->get<Button>(id);
+ return pButton ? std::make_unique<SalInstanceButton>(pButton, this, bTakeOwnership) : nullptr;
+}
+
+std::unique_ptr<weld::MenuButton> SalInstanceBuilder::weld_menu_button(const OString& id,
+ bool bTakeOwnership)
+{
+ MenuButton* pButton = m_xBuilder->get<MenuButton>(id);
+ return pButton ? std::make_unique<SalInstanceMenuButton>(pButton, this, bTakeOwnership)
+ : nullptr;
+}
+
+std::unique_ptr<weld::LinkButton> SalInstanceBuilder::weld_link_button(const OString& id,
+ bool bTakeOwnership)
+{
+ FixedHyperlink* pButton = m_xBuilder->get<FixedHyperlink>(id);
+ return pButton ? std::make_unique<SalInstanceLinkButton>(pButton, this, bTakeOwnership)
+ : nullptr;
+}
+
+std::unique_ptr<weld::ToggleButton> SalInstanceBuilder::weld_toggle_button(const OString& id,
+ bool bTakeOwnership)
+{
+ PushButton* pToggleButton = m_xBuilder->get<PushButton>(id);
+ return pToggleButton
+ ? std::make_unique<SalInstanceToggleButton>(pToggleButton, this, bTakeOwnership)
+ : nullptr;
+}
+
+std::unique_ptr<weld::RadioButton> SalInstanceBuilder::weld_radio_button(const OString& id,
+ bool bTakeOwnership)
+{
+ RadioButton* pRadioButton = m_xBuilder->get<RadioButton>(id);
+ return pRadioButton
+ ? std::make_unique<SalInstanceRadioButton>(pRadioButton, this, bTakeOwnership)
+ : nullptr;
+}
+
+std::unique_ptr<weld::CheckButton> SalInstanceBuilder::weld_check_button(const OString& id,
+ bool bTakeOwnership)
+{
+ CheckBox* pCheckButton = m_xBuilder->get<CheckBox>(id);
+ return pCheckButton
+ ? std::make_unique<SalInstanceCheckButton>(pCheckButton, this, bTakeOwnership)
+ : nullptr;
+}
+
+std::unique_ptr<weld::Scale> SalInstanceBuilder::weld_scale(const OString& id, bool bTakeOwnership)
+{
+ Slider* pSlider = m_xBuilder->get<Slider>(id);
+ return pSlider ? std::make_unique<SalInstanceScale>(pSlider, this, bTakeOwnership) : nullptr;
+}
+
+std::unique_ptr<weld::ProgressBar> SalInstanceBuilder::weld_progress_bar(const OString& id,
+ bool bTakeOwnership)
+{
+ ::ProgressBar* pProgress = m_xBuilder->get<::ProgressBar>(id);
+ return pProgress ? std::make_unique<SalInstanceProgressBar>(pProgress, this, bTakeOwnership)
+ : nullptr;
+}
+
+std::unique_ptr<weld::Spinner> SalInstanceBuilder::weld_spinner(const OString& id,
+ bool bTakeOwnership)
+{
+ Throbber* pThrobber = m_xBuilder->get<Throbber>(id);
+ return pThrobber ? std::make_unique<SalInstanceSpinner>(pThrobber, this, bTakeOwnership)
+ : nullptr;
+}
+
+std::unique_ptr<weld::Image> SalInstanceBuilder::weld_image(const OString& id, bool bTakeOwnership)
+{
+ FixedImage* pImage = m_xBuilder->get<FixedImage>(id);
+ return pImage ? std::make_unique<SalInstanceImage>(pImage, this, bTakeOwnership) : nullptr;
+}
+
+std::unique_ptr<weld::Calendar> SalInstanceBuilder::weld_calendar(const OString& id,
+ bool bTakeOwnership)
+{
+ Calendar* pCalendar = m_xBuilder->get<Calendar>(id);
+ return pCalendar ? std::make_unique<SalInstanceCalendar>(pCalendar, this, bTakeOwnership)
+ : nullptr;
+}
+
+std::unique_ptr<weld::Entry> SalInstanceBuilder::weld_entry(const OString& id, bool bTakeOwnership)
+{
+ Edit* pEntry = m_xBuilder->get<Edit>(id);
+ return pEntry ? std::make_unique<SalInstanceEntry>(pEntry, this, bTakeOwnership) : nullptr;
+}
+
+std::unique_ptr<weld::SpinButton> SalInstanceBuilder::weld_spin_button(const OString& id,
+ bool bTakeOwnership)
+{
+ FormattedField* pSpinButton = m_xBuilder->get<FormattedField>(id);
+ return pSpinButton ? std::make_unique<SalInstanceSpinButton>(pSpinButton, this, bTakeOwnership)
+ : nullptr;
+}
+
+std::unique_ptr<weld::MetricSpinButton>
+SalInstanceBuilder::weld_metric_spin_button(const OString& id, FieldUnit eUnit, bool bTakeOwnership)
+{
+ std::unique_ptr<weld::SpinButton> xButton(weld_spin_button(id, bTakeOwnership));
+ if (xButton)
+ {
+ SalInstanceSpinButton& rButton = dynamic_cast<SalInstanceSpinButton&>(*xButton);
+ rButton.SetUseThousandSep();
+ }
+ return std::make_unique<weld::MetricSpinButton>(std::move(xButton), eUnit);
+}
+
+std::unique_ptr<weld::FormattedSpinButton>
+SalInstanceBuilder::weld_formatted_spin_button(const OString& id, bool bTakeOwnership)
+{
+ FormattedField* pSpinButton = m_xBuilder->get<FormattedField>(id);
+ return pSpinButton
+ ? std::make_unique<SalInstanceFormattedSpinButton>(pSpinButton, this, bTakeOwnership)
+ : nullptr;
+}
+
+std::unique_ptr<weld::TimeSpinButton>
+SalInstanceBuilder::weld_time_spin_button(const OString& id, TimeFieldFormat eFormat,
+ bool bTakeOwnership)
+{
+ std::unique_ptr<weld::TimeSpinButton> pRet(
+ new weld::TimeSpinButton(weld_spin_button(id, bTakeOwnership), eFormat));
+ SalInstanceSpinButton& rButton = dynamic_cast<SalInstanceSpinButton&>(pRet->get_widget());
+ rButton.DisableRemainderFactor(); //so with hh::mm::ss, incrementing mm will not reset ss
+ return pRet;
+}
+
+std::unique_ptr<weld::ComboBox> SalInstanceBuilder::weld_combo_box(const OString& id,
+ bool bTakeOwnership)
+{
+ vcl::Window* pWidget = m_xBuilder->get(id);
+ ::ComboBox* pComboBox = dynamic_cast<::ComboBox*>(pWidget);
+ if (pComboBox)
+ return std::make_unique<SalInstanceComboBoxWithEdit>(pComboBox, this, bTakeOwnership);
+ ListBox* pListBox = dynamic_cast<ListBox*>(pWidget);
+ return pListBox
+ ? std::make_unique<SalInstanceComboBoxWithoutEdit>(pListBox, this, bTakeOwnership)
+ : nullptr;
+}
+
+std::unique_ptr<weld::EntryTreeView>
+SalInstanceBuilder::weld_entry_tree_view(const OString& containerid, const OString& entryid,
+ const OString& treeviewid, bool bTakeOwnership)
+{
+ vcl::Window* pContainer = m_xBuilder->get(containerid);
+ return pContainer ? std::make_unique<SalInstanceEntryTreeView>(
+ pContainer, this, bTakeOwnership, weld_entry(entryid, bTakeOwnership),
+ weld_tree_view(treeviewid, bTakeOwnership))
+ : nullptr;
+}
+
+std::unique_ptr<weld::TreeView> SalInstanceBuilder::weld_tree_view(const OString& id,
+ bool bTakeOwnership)
+{
+ SvTabListBox* pTreeView = m_xBuilder->get<SvTabListBox>(id);
+ return pTreeView ? std::make_unique<SalInstanceTreeView>(pTreeView, this, bTakeOwnership)
+ : nullptr;
+}
+
+std::unique_ptr<weld::IconView> SalInstanceBuilder::weld_icon_view(const OString& id,
+ bool bTakeOwnership)
+{
+ IconView* pIconView = m_xBuilder->get<IconView>(id);
+ return pIconView ? std::make_unique<SalInstanceIconView>(pIconView, this, bTakeOwnership)
+ : nullptr;
+}
+
+std::unique_ptr<weld::Label> SalInstanceBuilder::weld_label(const OString& id, bool bTakeOwnership)
+{
+ Control* pLabel = m_xBuilder->get<Control>(id);
+ return pLabel ? std::make_unique<SalInstanceLabel>(pLabel, this, bTakeOwnership) : nullptr;
+}
+
+std::unique_ptr<weld::TextView> SalInstanceBuilder::weld_text_view(const OString& id,
+ bool bTakeOwnership)
+{
+ VclMultiLineEdit* pTextView = m_xBuilder->get<VclMultiLineEdit>(id);
+ return pTextView ? std::make_unique<SalInstanceTextView>(pTextView, this, bTakeOwnership)
+ : nullptr;
+}
+
+std::unique_ptr<weld::Expander> SalInstanceBuilder::weld_expander(const OString& id,
+ bool bTakeOwnership)
+{
+ VclExpander* pExpander = m_xBuilder->get<VclExpander>(id);
+ return pExpander ? std::make_unique<SalInstanceExpander>(pExpander, this, bTakeOwnership)
+ : nullptr;
+}
+
+std::unique_ptr<weld::DrawingArea>
+SalInstanceBuilder::weld_drawing_area(const OString& id, const a11yref& rA11yImpl,
+ FactoryFunction pUITestFactoryFunction, void* pUserData,
+ bool bTakeOwnership)
+{
+ VclDrawingArea* pDrawingArea = m_xBuilder->get<VclDrawingArea>(id);
+ return pDrawingArea ? std::make_unique<SalInstanceDrawingArea>(pDrawingArea, this, rA11yImpl,
+ pUITestFactoryFunction,
+ pUserData, bTakeOwnership)
+ : nullptr;
+}
+
+std::unique_ptr<weld::Menu> SalInstanceBuilder::weld_menu(const OString& id, bool bTakeOwnership)
+{
+ PopupMenu* pMenu = m_xBuilder->get_menu(id);
+ return pMenu ? std::make_unique<SalInstanceMenu>(pMenu, bTakeOwnership) : nullptr;
+}
+
+std::unique_ptr<weld::Toolbar> SalInstanceBuilder::weld_toolbar(const OString& id,
+ bool bTakeOwnership)
+{
+ ToolBox* pToolBox = m_xBuilder->get<ToolBox>(id);
+ return pToolBox ? std::make_unique<SalInstanceToolbar>(pToolBox, this, bTakeOwnership)
+ : nullptr;
+}
+
+std::unique_ptr<weld::SizeGroup> SalInstanceBuilder::create_size_group()
+{
+ return std::make_unique<SalInstanceSizeGroup>();
+}
+
+OString SalInstanceBuilder::get_current_page_help_id() const
+{
+ TabControl* pCtrl = m_xBuilder->get<TabControl>("tabcontrol");
+ TabPage* pTabPage = pCtrl ? pCtrl->GetTabPage(pCtrl->GetCurPageId()) : nullptr;
+ vcl::Window* pTabChild = pTabPage ? pTabPage->GetWindow(GetWindowType::FirstChild) : nullptr;
+ pTabChild = pTabChild ? pTabChild->GetWindow(GetWindowType::FirstChild) : nullptr;
+ if (pTabChild)
+ return pTabChild->GetHelpId();
+ return OString();
+}
+
+SalInstanceBuilder::~SalInstanceBuilder()
+{
+ if (VclBuilderContainer* pOwnedToplevel
+ = dynamic_cast<VclBuilderContainer*>(m_aOwnedToplevel.get()))
+ pOwnedToplevel->m_pUIBuilder = std::move(m_xBuilder);
+ else
+ m_xBuilder.reset();
+ m_aOwnedToplevel.disposeAndClear();
+}
+
+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 new SalInstanceBuilder(pParentWidget, rUIRoot, rUIFile);
+}
+
+weld::Builder* SalInstance::CreateInterimBuilder(vcl::Window* pParent, const OUString& rUIRoot,
+ const OUString& rUIFile)
+{
+ return new 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;
+ OString sHelpId = pWidget->GetHelpId();
+ while (sHelpId.isEmpty())
+ {
+ pWidget = pWidget->GetParent();
+ if (!pWidget)
+ break;
+ sHelpId = pWidget->GetHelpId();
+ }
+ std::unique_ptr<weld::Widget> xTemp(
+ pWidget != m_xWindow ? new SalInstanceWidget(pWidget, m_pBuilder, false) : nullptr);
+ weld::Widget* pSource = xTemp ? xTemp.get() : this;
+ bool bRunNormalHelpRequest = !m_aHelpRequestHdl.IsSet() || m_aHelpRequestHdl.Call(*pSource);
+ Help* pHelp = bRunNormalHelpRequest ? Application::GetHelp() : nullptr;
+ if (pHelp)
+ {
+ // tdf#126007, there's a nice fallback route for offline help where
+ // the current page of a notebook will get checked when the help
+ // button is pressed and there was no help for the dialog found.
+ //
+ // But for online help that route doesn't get taken, so bodge this here
+ // by using the page help id if available and if the help button itself
+ // was the original id
+ if (m_pBuilder && sHelpId.endsWith("/help"))
+ {
+ OString sPageId = m_pBuilder->get_current_page_help_id();
+ if (!sPageId.isEmpty())
+ sHelpId = sPageId;
+ else
+ {
+ // tdf#129068 likewise the help for the wrapping dialog is less
+ // helpful than the help for the content area could be
+ vcl::Window* pContentArea = nullptr;
+ if (::Dialog* pDialog = dynamic_cast<::Dialog*>(m_xWindow.get()))
+ pContentArea = pDialog->get_content_area();
+ if (pContentArea)
+ {
+ vcl::Window* pContentWidget = pContentArea->GetWindow(GetWindowType::LastChild);
+ if (pContentWidget)
+ sHelpId = pContentWidget->GetHelpId();
+ }
+ }
+ }
+ pHelp->Start(OStringToOUString(sHelpId, RTL_TEXTENCODING_UTF8), pSource);
+ }
+}
+
+//iterate upwards through the hierarchy from this widgets through its parents
+//calling func with their helpid until func returns true or we run out of parents
+void SalInstanceWidget::help_hierarchy_foreach(const std::function<bool(const OString&)>& func)
+{
+ vcl::Window* pParent = m_xWidget;
+ while ((pParent = pParent->GetParent()))
+ {
+ if (func(pParent->GetHelpId()))
+ return;
+ }
+}
+
+weld::MessageDialog* SalInstance::CreateMessageDialog(weld::Widget* pParent,
+ VclMessageType eMessageType,
+ VclButtonsType eButtonsType,
+ const OUString& rPrimaryMessage)
+{
+ SalInstanceWidget* pParentInstance = dynamic_cast<SalInstanceWidget*>(pParent);
+ SystemWindow* pParentWidget = pParentInstance ? pParentInstance->getSystemWindow() : nullptr;
+ VclPtrInstance<MessageDialog> xMessageDialog(pParentWidget, rPrimaryMessage, eMessageType,
+ eButtonsType);
+ return new SalInstanceMessageDialog(xMessageDialog, nullptr, true);
+}
+
+weld::Window* SalInstance::GetFrameWeld(const css::uno::Reference<css::awt::XWindow>& rWindow)
+{
+ UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper();
+ if (!pWrapper)
+ return nullptr;
+ VclPtr<vcl::Window> xWindow = pWrapper->GetWindow(rWindow);
+ if (!xWindow)
+ return nullptr;
+ return xWindow->GetFrameWeld();
+}
+
+weld::Window* SalFrame::GetFrameWeld() const
+{
+ if (!m_xFrameWeld)
+ {
+ vcl::Window* pWindow = GetWindow();
+ pWindow = pWindow ? pWindow->ImplGetWindow() : nullptr;
+ assert(!pWindow || (pWindow->IsSystemWindow() || pWindow->IsDockingWindow()));
+ if (pWindow)
+ m_xFrameWeld.reset(new SalInstanceWindow(pWindow, nullptr, false));
+ }
+ return m_xFrameWeld.get();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/scheduler.cxx b/vcl/source/app/scheduler.cxx
new file mode 100644
index 000000000..aa716f88b
--- /dev/null
+++ b/vcl/source/app/scheduler.cxx
@@ -0,0 +1,659 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <cassert>
+#include <cstdlib>
+#include <exception>
+#include <typeinfo>
+
+#include <com/sun/star/uno/Exception.hpp>
+#include <sal/log.hxx>
+#include <sal/types.h>
+#include <svdata.hxx>
+#include <tools/time.hxx>
+#include <tools/debug.hxx>
+#include <tools/diagnose_ex.h>
+#include <unotools/configmgr.hxx>
+#include <vcl/TaskStopwatch.hxx>
+#include <vcl/scheduler.hxx>
+#include <vcl/idle.hxx>
+#include <saltimer.hxx>
+#include <salinst.hxx>
+#include <comphelper/profilezone.hxx>
+#include <schedulerimpl.hxx>
+
+namespace {
+
+template< typename charT, typename traits >
+std::basic_ostream<charT, traits> & operator <<(
+ std::basic_ostream<charT, traits> & stream, const Task& task )
+{
+ stream << "a: " << task.IsActive() << " p: " << static_cast<int>(task.GetPriority());
+ const char *name = task.GetDebugName();
+ if( nullptr == name )
+ return stream << " (nullptr)";
+ else
+ return stream << " " << name;
+}
+
+/**
+ * clang won't compile this in the Timer.hxx header, even with a class Idle
+ * forward definition, due to the incomplete Idle type in the template.
+ * Currently the code is just used in the Scheduler, so we keep it local.
+ *
+ * @see http://clang.llvm.org/compatibility.html#undep_incomplete
+ */
+template< typename charT, typename traits >
+std::basic_ostream<charT, traits> & operator <<(
+ std::basic_ostream<charT, traits> & stream, const Timer& timer )
+{
+ bool bIsIdle = (dynamic_cast<const Idle*>( &timer ) != nullptr);
+ stream << (bIsIdle ? "Idle " : "Timer")
+ << " a: " << timer.IsActive() << " p: " << static_cast<int>(timer.GetPriority());
+ const char *name = timer.GetDebugName();
+ if ( nullptr == name )
+ stream << " (nullptr)";
+ else
+ stream << " " << name;
+ if ( !bIsIdle )
+ stream << " " << timer.GetTimeout() << "ms";
+ stream << " (" << &timer << ")";
+ return stream;
+}
+
+template< typename charT, typename traits >
+std::basic_ostream<charT, traits> & operator <<(
+ std::basic_ostream<charT, traits> & stream, const Idle& idle )
+{
+ return stream << static_cast<const Timer*>( &idle );
+}
+
+template< typename charT, typename traits >
+std::basic_ostream<charT, traits> & operator <<(
+ std::basic_ostream<charT, traits> & stream, const ImplSchedulerData& data )
+{
+ stream << " i: " << data.mbInScheduler;
+ return stream;
+}
+
+} // end anonymous namespace
+
+unsigned int TaskStopwatch::m_nTimeSlice = TaskStopwatch::nDefaultTimeSlice;
+
+void Scheduler::ImplDeInitScheduler()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ assert( pSVData != nullptr );
+ ImplSchedulerContext &rSchedCtx = pSVData->maSchedCtx;
+
+ DBG_TESTSOLARMUTEX();
+
+ SchedulerGuard aSchedulerGuard;
+
+ int nTaskPriority = 0;
+#if OSL_DEBUG_LEVEL > 0
+ sal_uInt32 nTasks = 0;
+ for (nTaskPriority = 0; nTaskPriority < PRIO_COUNT; ++nTaskPriority)
+ {
+ ImplSchedulerData* pSchedulerData = rSchedCtx.mpFirstSchedulerData[nTaskPriority];
+ while ( pSchedulerData )
+ {
+ ++nTasks;
+ pSchedulerData = pSchedulerData->mpNext;
+ }
+ }
+ SAL_INFO( "vcl.schedule.deinit",
+ "DeInit the scheduler - pending tasks: " << nTasks );
+
+ // clean up all the sfx::SfxItemDisruptor_Impl Idles
+ ProcessEventsToIdle();
+#endif
+ rSchedCtx.mbActive = false;
+
+ assert( nullptr == rSchedCtx.mpSchedulerStack );
+ assert( 1 == rSchedCtx.maMutex.lockDepth() );
+
+ if (rSchedCtx.mpSalTimer) rSchedCtx.mpSalTimer->Stop();
+ DELETEZ( rSchedCtx.mpSalTimer );
+
+#if OSL_DEBUG_LEVEL > 0
+ sal_uInt32 nActiveTasks = 0, nIgnoredTasks = 0;
+#endif
+ nTaskPriority = 0;
+ ImplSchedulerData* pSchedulerData = nullptr;
+
+next_priority:
+ pSchedulerData = rSchedCtx.mpFirstSchedulerData[nTaskPriority];
+ while ( pSchedulerData )
+ {
+ Task *pTask = pSchedulerData->mpTask;
+ if ( pTask )
+ {
+ if ( pTask->mbActive )
+ {
+#if OSL_DEBUG_LEVEL > 0
+ const char *sIgnored = "";
+ ++nActiveTasks;
+ // TODO: shutdown these timers before Scheduler de-init
+ // TODO: remove Task from static object
+ if ( pTask->GetDebugName() && ( false
+ || !strcmp( pTask->GetDebugName(), "desktop::Desktop m_firstRunTimer" )
+ || !strcmp( pTask->GetDebugName(), "DrawWorkStartupTimer" )
+ || !strcmp( pTask->GetDebugName(), "editeng::ImpEditEngine aOnlineSpellTimer" )
+ || !strcmp( pTask->GetDebugName(), "ImplHandleMouseMsg SalData::mpMouseLeaveTimer" )
+ || !strcmp( pTask->GetDebugName(), "sc ScModule IdleTimer" )
+ || !strcmp( pTask->GetDebugName(), "sd::CacheConfiguration maReleaseTimer" )
+ || !strcmp( pTask->GetDebugName(), "svtools::GraphicCache maReleaseTimer" )
+ || !strcmp( pTask->GetDebugName(), "svtools::GraphicObject mpSwapOutTimer" )
+ || !strcmp( pTask->GetDebugName(), "svx OLEObjCache pTimer UnloadCheck" )
+ || !strcmp( pTask->GetDebugName(), "vcl SystemDependentDataBuffer aSystemDependentDataBuffer" )
+ ))
+ {
+ sIgnored = " (ignored)";
+ ++nIgnoredTasks;
+ }
+ const Timer *timer = dynamic_cast<Timer*>( pTask );
+ if ( timer )
+ SAL_WARN( "vcl.schedule.deinit", "DeInit task: " << *timer << sIgnored );
+ else
+ SAL_WARN( "vcl.schedule.deinit", "DeInit task: " << *pTask << sIgnored );
+#endif
+ pTask->mbActive = false;
+ }
+ pTask->mpSchedulerData = nullptr;
+ pTask->SetStatic();
+ }
+ ImplSchedulerData* pDeleteSchedulerData = pSchedulerData;
+ pSchedulerData = pSchedulerData->mpNext;
+ delete pDeleteSchedulerData;
+ }
+
+ ++nTaskPriority;
+ if (nTaskPriority < PRIO_COUNT)
+ goto next_priority;
+
+#if OSL_DEBUG_LEVEL > 0
+ SAL_INFO( "vcl.schedule.deinit", "DeInit the scheduler - finished" );
+ SAL_WARN_IF( 0 != nActiveTasks, "vcl.schedule.deinit", "DeInit active tasks: "
+ << nActiveTasks << " (ignored: " << nIgnoredTasks << ")" );
+// assert( nIgnoredTasks == nActiveTasks );
+#endif
+
+ for (nTaskPriority = 0; nTaskPriority < PRIO_COUNT; ++nTaskPriority)
+ {
+ rSchedCtx.mpFirstSchedulerData[nTaskPriority] = nullptr;
+ rSchedCtx.mpLastSchedulerData[nTaskPriority] = nullptr;
+ }
+ rSchedCtx.mnTimerPeriod = InfiniteTimeoutMs;
+}
+
+void SchedulerMutex::acquire( sal_uInt32 nLockCount )
+{
+ assert(nLockCount > 0);
+ for (sal_uInt32 i = 0; i != nLockCount; ++i) {
+ if (!maMutex.acquire())
+ abort();
+ }
+ mnLockDepth += nLockCount;
+}
+
+sal_uInt32 SchedulerMutex::release( bool bUnlockAll )
+{
+ assert(mnLockDepth > 0);
+ const sal_uInt32 nLockCount =
+ (bUnlockAll || 0 == mnLockDepth) ? mnLockDepth : 1;
+ mnLockDepth -= nLockCount;
+ for (sal_uInt32 i = 0; i != nLockCount; ++i) {
+ if (!maMutex.release())
+ abort();
+ }
+ return nLockCount;
+}
+
+void Scheduler::Lock( sal_uInt32 nLockCount )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ assert( pSVData != nullptr );
+ pSVData->maSchedCtx.maMutex.acquire( nLockCount );
+}
+
+sal_uInt32 Scheduler::Unlock( bool bUnlockAll )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ assert( pSVData != nullptr );
+ return pSVData->maSchedCtx.maMutex.release( bUnlockAll );
+}
+
+/**
+ * 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 );
+ }
+}
+
+void Scheduler::CallbackTaskScheduling()
+{
+ // this function is for the saltimer callback
+ Scheduler::ProcessTaskScheduling();
+}
+
+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;
+}
+
+bool Scheduler::ProcessTaskScheduling()
+{
+ ImplSVData *pSVData = ImplGetSVData();
+ ImplSchedulerContext &rSchedCtx = pSVData->maSchedCtx;
+
+ DBG_TESTSOLARMUTEX();
+
+ SchedulerGuard aSchedulerGuard;
+ if ( !rSchedCtx.mbActive || InfiniteTimeoutMs == rSchedCtx.mnTimerPeriod )
+ return false;
+
+ 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 false;
+ }
+
+ ImplSchedulerData* pSchedulerData = nullptr;
+ ImplSchedulerData* pPrevSchedulerData = nullptr;
+ ImplSchedulerData *pMostUrgent = nullptr;
+ ImplSchedulerData *pPrevMostUrgent = nullptr;
+ int nMostUrgentPriority = 0;
+ sal_uInt64 nMinPeriod = InfiniteTimeoutMs;
+ sal_uInt64 nReadyPeriod = InfiniteTimeoutMs;
+ unsigned nTasks = 0;
+ int nTaskPriority = 0;
+
+ for (; nTaskPriority < PRIO_COUNT; ++nTaskPriority)
+ {
+ pSchedulerData = rSchedCtx.mpFirstSchedulerData[nTaskPriority];
+ pPrevSchedulerData = nullptr;
+ while (pSchedulerData)
+ {
+ ++nTasks;
+ const Timer *timer = dynamic_cast<Timer*>( pSchedulerData->mpTask );
+ if ( timer )
+ SAL_INFO( "vcl.schedule", tools::Time::GetSystemTicks() << " "
+ << pSchedulerData << " " << *pSchedulerData << " " << *timer );
+ else if ( pSchedulerData->mpTask )
+ SAL_INFO( "vcl.schedule", tools::Time::GetSystemTicks() << " "
+ << pSchedulerData << " " << *pSchedulerData
+ << " " << *pSchedulerData->mpTask );
+ else
+ SAL_INFO( "vcl.schedule", tools::Time::GetSystemTicks() << " "
+ << pSchedulerData << " " << *pSchedulerData << " (to be deleted)" );
+
+ // Should the Task be released from scheduling?
+ assert(!pSchedulerData->mbInScheduler);
+ if (!pSchedulerData->mpTask || !pSchedulerData->mpTask->IsActive())
+ {
+ ImplSchedulerData * const pSchedulerDataNext =
+ DropSchedulerData(rSchedCtx, pPrevSchedulerData, pSchedulerData, nTaskPriority);
+ if ( pSchedulerData->mpTask )
+ pSchedulerData->mpTask->mpSchedulerData = nullptr;
+ delete pSchedulerData;
+ pSchedulerData = pSchedulerDataNext;
+ continue;
+ }
+
+ assert(pSchedulerData->mpTask);
+ if (pSchedulerData->mpTask->IsActive())
+ {
+ nReadyPeriod = pSchedulerData->mpTask->UpdateMinPeriod( nTime );
+ if (ImmediateTimeoutMs == nReadyPeriod)
+ {
+ if (!pMostUrgent)
+ {
+ pPrevMostUrgent = pPrevSchedulerData;
+ pMostUrgent = pSchedulerData;
+ nMostUrgentPriority = nTaskPriority;
+ }
+ else
+ {
+ nMinPeriod = ImmediateTimeoutMs;
+ break;
+ }
+ }
+ else if (nMinPeriod > nReadyPeriod)
+ nMinPeriod = nReadyPeriod;
+ }
+
+ pPrevSchedulerData = pSchedulerData;
+ pSchedulerData = pSchedulerData->mpNext;
+ }
+
+ if (ImmediateTimeoutMs == nMinPeriod)
+ break;
+ }
+
+ if ( InfiniteTimeoutMs != nMinPeriod )
+ SAL_INFO("vcl.schedule", "Calculated minimum timeout as " << nMinPeriod
+ << " of " << nTasks << " tasks" );
+ UpdateSystemTimer( rSchedCtx, nMinPeriod, true, nTime );
+
+ if ( pMostUrgent )
+ {
+ SAL_INFO( "vcl.schedule", tools::Time::GetSystemTicks() << " "
+ << pMostUrgent << " invoke-in " << *pMostUrgent->mpTask );
+
+ Task *pTask = pMostUrgent->mpTask;
+
+ comphelper::ProfileZone aZone( pTask->GetDebugName() );
+
+ // prepare Scheduler object for deletion after handling
+ pTask->SetDeletionFlags();
+
+ pMostUrgent->mbInScheduler = true;
+
+ // always push the stack, as we don't traverse the whole list to push later
+ DropSchedulerData(rSchedCtx, pPrevMostUrgent, pMostUrgent, nMostUrgentPriority);
+ pMostUrgent->mpNext = rSchedCtx.mpSchedulerStack;
+ rSchedCtx.mpSchedulerStack = pMostUrgent;
+ rSchedCtx.mpSchedulerStackTop = pMostUrgent;
+
+ // invoke the task
+ sal_uInt32 nLockCount = Unlock( true );
+ try
+ {
+ pTask->Invoke();
+ }
+ catch (css::uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION("vcl.schedule", "Uncaught");
+ std::abort();
+ }
+ catch (std::exception& e)
+ {
+ SAL_WARN("vcl.schedule", "Uncaught " << typeid(e).name() << " " << e.what());
+ std::abort();
+ }
+ catch (...)
+ {
+ SAL_WARN("vcl.schedule", "Uncaught exception during Task::Invoke()!");
+ std::abort();
+ }
+ Lock( nLockCount );
+ 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;
+
+ 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 );
+ }
+ }
+
+ return !!pMostUrgent;
+}
+
+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()
+{
+ 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();
+}
+
+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 )
+{
+}
+
+Task::Task( const Task& rTask )
+ : mpSchedulerData( nullptr )
+ , mpDebugName( rTask.mpDebugName )
+ , mePriority( rTask.mePriority )
+ , mbActive( false )
+ , mbStatic( false )
+{
+ if ( rTask.IsActive() )
+ Start();
+}
+
+Task::~Task() COVERITY_NOEXCEPT_FALSE
+{
+ if ( !IsStatic() )
+ {
+ SchedulerGuard aSchedulerGuard;
+ if ( mpSchedulerData )
+ mpSchedulerData->mpTask = nullptr;
+ }
+ else
+ assert(nullptr == mpSchedulerData || utl::ConfigManager::IsFuzzing());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/session.cxx b/vcl/source/app/session.cxx
new file mode 100644
index 000000000..98cad6bba
--- /dev/null
+++ b/vcl/source/app/session.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 <memory>
+#include <sal/config.h>
+#include <sal/log.hxx>
+
+#include <cppuhelper/basemutex.hxx>
+#include <cppuhelper/compbase.hxx>
+
+#include <tools/diagnose_ex.h>
+#include <vcl/svapp.hxx>
+
+#include <factory.hxx>
+#include <svdata.hxx>
+#include <salinst.hxx>
+#include <salsession.hxx>
+
+#include <com/sun/star/frame/XSessionManagerClient.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/frame/XSessionManagerListener2.hpp>
+
+#include <vector>
+
+using namespace com::sun::star::uno;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::frame;
+
+SalSession::~SalSession()
+{
+}
+
+namespace {
+
+class VCLSession:
+ private cppu::BaseMutex,
+ public cppu::WeakComponentImplHelper < XSessionManagerClient >
+{
+ struct Listener
+ {
+ css::uno::Reference< XSessionManagerListener > m_xListener;
+ bool m_bInteractionRequested;
+ bool m_bInteractionDone;
+ bool m_bSaveDone;
+
+ explicit Listener( const css::uno::Reference< XSessionManagerListener >& xListener )
+ : m_xListener( xListener ),
+ m_bInteractionRequested( false ),
+ m_bInteractionDone( false ),
+ m_bSaveDone( false )
+ {}
+ };
+
+ std::vector< Listener > m_aListeners;
+ std::unique_ptr< SalSession > m_xSession;
+ bool m_bInteractionRequested;
+ bool m_bInteractionGranted;
+ bool m_bInteractionDone;
+ bool m_bSaveDone;
+
+ static void SalSessionEventProc( void* pData, SalSessionEvent* pEvent );
+
+ virtual ~VCLSession() override {}
+
+ virtual void SAL_CALL addSessionManagerListener( const css::uno::Reference< XSessionManagerListener >& xListener ) override;
+ virtual void SAL_CALL removeSessionManagerListener( const css::uno::Reference< XSessionManagerListener>& xListener ) override;
+ virtual void SAL_CALL queryInteraction( const css::uno::Reference< XSessionManagerListener >& xListener ) override;
+ virtual void SAL_CALL interactionDone( const css::uno::Reference< XSessionManagerListener >& xListener ) override;
+ virtual void SAL_CALL saveDone( const css::uno::Reference< XSessionManagerListener >& xListener ) override;
+ virtual sal_Bool SAL_CALL cancelShutdown() override;
+
+ void SAL_CALL disposing() override;
+
+ void callSaveRequested( bool bShutdown );
+ void callShutdownCancelled();
+ void callInteractionGranted( bool bGranted );
+ void callQuit();
+
+public:
+ VCLSession();
+};
+
+}
+
+VCLSession::VCLSession()
+ : cppu::WeakComponentImplHelper< XSessionManagerClient >( m_aMutex ),
+ m_xSession( ImplGetSVData()->mpDefInst->CreateSalSession() ),
+ m_bInteractionRequested( false ),
+ m_bInteractionGranted( false ),
+ m_bInteractionDone( false ),
+ m_bSaveDone( false )
+{
+ SAL_INFO("vcl.se", "VCLSession::VCLSession" );
+
+ if (m_xSession)
+ m_xSession->SetCallback( SalSessionEventProc, this );
+}
+
+void VCLSession::callSaveRequested( bool bShutdown )
+{
+ SAL_INFO("vcl.se", "VCLSession::callSaveRequested" );
+
+ std::vector< Listener > aListeners;
+ {
+ osl::MutexGuard aGuard( m_aMutex );
+ // reset listener states
+ for (auto & listener : m_aListeners) {
+ listener.m_bSaveDone = listener.m_bInteractionRequested = listener.m_bInteractionDone = false;
+ }
+
+ // copy listener vector since calling a listener may remove it.
+ aListeners = m_aListeners;
+ // set back interaction state
+ m_bSaveDone = false;
+ m_bInteractionDone = false;
+ // without session we assume UI is always possible,
+ // so it was requested and granted
+ m_bInteractionRequested = m_bInteractionGranted = !m_xSession;
+
+ // answer the session manager even if no listeners available anymore
+ SAL_WARN_IF( aListeners.empty(), "vcl.se", "saveRequested but no listeners !" );
+
+ SAL_INFO("vcl.se.debug", " aListeners.empty() = " << (aListeners.empty() ? "true" : "false") <<
+ ", bShutdown = " << (bShutdown ? "true" : "false"));
+ if( aListeners.empty() )
+ {
+ if (m_xSession)
+ m_xSession->saveDone();
+ return;
+ }
+ }
+
+ SolarMutexReleaser aReleaser;
+ for (auto const & listener: aListeners)
+ listener.m_xListener->doSave( bShutdown, false/*bCancelable*/ );
+}
+
+void VCLSession::callInteractionGranted( bool bInteractionGranted )
+{
+ SAL_INFO("vcl.se", "VCLSession::callInteractionGranted" );
+
+ std::vector< Listener > aListeners;
+ {
+ osl::MutexGuard aGuard( m_aMutex );
+ // copy listener vector since calling a listener may remove it.
+ for (auto const & listener: m_aListeners)
+ if( listener.m_bInteractionRequested )
+ aListeners.push_back( listener );
+
+ m_bInteractionGranted = bInteractionGranted;
+
+ // answer the session manager even if no listeners available anymore
+ SAL_WARN_IF( aListeners.empty(), "vcl.se", "interactionGranted but no listeners !" );
+
+ SAL_INFO("vcl.se.debug", " aListeners.empty() = " << (aListeners.empty() ? "true" : "false") <<
+ ", bInteractionGranted = " << (bInteractionGranted ? "true" : "false"));
+ if( aListeners.empty() )
+ {
+ if (m_xSession)
+ m_xSession->interactionDone();
+ return;
+ }
+ }
+
+ SolarMutexReleaser aReleaser;
+ for (auto const & listener: aListeners)
+ listener.m_xListener->approveInteraction( bInteractionGranted );
+}
+
+void VCLSession::callShutdownCancelled()
+{
+ SAL_INFO("vcl.se", "VCLSession::callShutdownCancelled");
+
+ std::vector< Listener > aListeners;
+ {
+ osl::MutexGuard aGuard( m_aMutex );
+ // copy listener vector since calling a listener may remove it.
+ aListeners = m_aListeners;
+ // set back interaction state
+ m_bInteractionRequested = m_bInteractionDone = m_bInteractionGranted = false;
+ }
+
+ SolarMutexReleaser aReleaser;
+ for (auto const & listener: aListeners)
+ listener.m_xListener->shutdownCanceled();
+}
+
+void VCLSession::callQuit()
+{
+ SAL_INFO("vcl.se", "VCLSession::callQuit");
+
+ std::vector< Listener > aListeners;
+ {
+ osl::MutexGuard aGuard( m_aMutex );
+ // copy listener vector since calling a listener may remove it.
+ aListeners = m_aListeners;
+ // set back interaction state
+ m_bInteractionRequested = m_bInteractionDone = m_bInteractionGranted = false;
+ }
+
+ SolarMutexReleaser aReleaser;
+ for (auto const & listener: aListeners)
+ {
+ css::uno::Reference< XSessionManagerListener2 > xListener2( listener.m_xListener, UNO_QUERY );
+ if( xListener2.is() )
+ xListener2->doQuit();
+ }
+}
+
+void VCLSession::SalSessionEventProc( void* pData, SalSessionEvent* pEvent )
+{
+ SAL_INFO("vcl.se", "VCLSession::SalSessionEventProc");
+
+ VCLSession * pThis = static_cast< VCLSession * >( pData );
+ switch( pEvent->m_eType )
+ {
+ case Interaction:
+ {
+ SAL_INFO("vcl.se.debug", " EventProcType = Interaction");
+ SalSessionInteractionEvent* pIEv = static_cast<SalSessionInteractionEvent*>(pEvent);
+ pThis->callInteractionGranted( pIEv->m_bInteractionGranted );
+ }
+ break;
+ case SaveRequest:
+ {
+ SAL_INFO("vcl.se.debug", " EventProcType = SaveRequest");
+ SalSessionSaveRequestEvent* pSEv = static_cast<SalSessionSaveRequestEvent*>(pEvent);
+ pThis->callSaveRequested( pSEv->m_bShutdown );
+ }
+ break;
+ case ShutdownCancel:
+ SAL_INFO("vcl.se.debug", " EventProcType = ShutdownCancel");
+ pThis->callShutdownCancelled();
+ break;
+ case Quit:
+ SAL_INFO("vcl.se.debug", " EventProcType = Quit");
+ pThis->callQuit();
+ break;
+ }
+}
+
+void SAL_CALL VCLSession::addSessionManagerListener( const css::uno::Reference<XSessionManagerListener>& xListener )
+{
+ SAL_INFO("vcl.se", "VCLSession::addSessionManagerListener" );
+
+ osl::MutexGuard aGuard( m_aMutex );
+
+ SAL_INFO("vcl.se.debug", " m_aListeners.size() = " << m_aListeners.size() );
+ m_aListeners.emplace_back( xListener );
+}
+
+void SAL_CALL VCLSession::removeSessionManagerListener( const css::uno::Reference<XSessionManagerListener>& xListener )
+{
+ SAL_INFO("vcl.se", "VCLSession::removeSessionManagerListener" );
+
+ osl::MutexGuard aGuard( m_aMutex );
+
+ SAL_INFO("vcl.se.debug", " m_aListeners.size() = " << m_aListeners.size() );
+
+ m_aListeners.erase(std::remove_if(m_aListeners.begin(), m_aListeners.end(), [&](Listener& listener) {return xListener == listener.m_xListener;}), m_aListeners.end());
+}
+
+void SAL_CALL VCLSession::queryInteraction( const css::uno::Reference<XSessionManagerListener>& xListener )
+{
+ SAL_INFO("vcl.se", "VCLSession::queryInteraction");
+
+ SAL_INFO("vcl.se.debug", " m_bInteractionGranted = " << (m_bInteractionGranted ? "true" : "false") <<
+ ", m_bInteractionRequested = "<< (m_bInteractionRequested ? "true" : "false"));
+ if( m_bInteractionGranted )
+ {
+ if( m_bInteractionDone )
+ xListener->approveInteraction( false );
+ else
+ xListener->approveInteraction( true );
+ return;
+ }
+
+ osl::MutexGuard aGuard( m_aMutex );
+ if( ! m_bInteractionRequested )
+ {
+ m_xSession->queryInteraction();
+ m_bInteractionRequested = true;
+ }
+ for (auto & listener: m_aListeners)
+ {
+ if( listener.m_xListener == xListener )
+ {
+ SAL_INFO("vcl.se.debug", " listener.m_xListener == xListener");
+ listener.m_bInteractionRequested = true;
+ listener.m_bInteractionDone = false;
+ }
+ }
+}
+
+void SAL_CALL VCLSession::interactionDone( const css::uno::Reference< XSessionManagerListener >& xListener )
+{
+ SAL_INFO("vcl.se", "VCLSession::interactionDone");
+
+ osl::MutexGuard aGuard( m_aMutex );
+ int nRequested = 0, nDone = 0;
+ for (auto & listener: m_aListeners)
+ {
+ if( listener.m_bInteractionRequested )
+ {
+ nRequested++;
+ if( xListener == listener.m_xListener )
+ listener.m_bInteractionDone = true;
+ }
+ if( listener.m_bInteractionDone )
+ nDone++;
+ }
+
+ SAL_INFO("vcl.se.debug", " nDone = " << nDone <<
+ ", nRequested =" << nRequested);
+ if( nDone == nRequested && nDone > 0 )
+ {
+ m_bInteractionDone = true;
+ if (m_xSession)
+ m_xSession->interactionDone();
+ }
+}
+
+void SAL_CALL VCLSession::saveDone( const css::uno::Reference< XSessionManagerListener >& xListener )
+{
+ SAL_INFO("vcl.se", "VCLSession::saveDone");
+
+ osl::MutexGuard aGuard( m_aMutex );
+
+ bool bSaveDone = true;
+ for (auto & listener: m_aListeners)
+ {
+ if( listener.m_xListener == xListener )
+ listener.m_bSaveDone = true;
+ if( ! listener.m_bSaveDone )
+ bSaveDone = false;
+ }
+
+ SAL_INFO("vcl.se.debug", " bSaveDone = " << (bSaveDone ? "true" : "false"));
+
+ if( bSaveDone && !m_bSaveDone )
+ {
+ m_bSaveDone = true;
+ if (m_xSession)
+ m_xSession->saveDone();
+ }
+}
+
+sal_Bool SAL_CALL VCLSession::cancelShutdown()
+{
+ SAL_INFO("vcl.se", "VCLSession::cancelShutdown");
+
+ return m_xSession && m_xSession->cancelShutdown();
+}
+
+void VCLSession::disposing() {
+ SAL_INFO("vcl.se", "VCLSession::disposing");
+
+ std::vector<Listener> vector;
+ {
+ osl::MutexGuard g(m_aMutex);
+ vector.swap(m_aListeners);
+ }
+ css::lang::EventObject src(static_cast<OWeakObject *>(this));
+ for (auto const & listener: vector) {
+ try {
+ listener.m_xListener->disposing(src);
+ SAL_INFO("vcl.se.debug", " call Listener disposing");
+ } catch (css::uno::RuntimeException &) {
+ TOOLS_WARN_EXCEPTION("vcl.se", "ignoring");
+ }
+ }
+}
+
+// service implementation
+
+OUString vcl_session_getImplementationName()
+{
+ SAL_INFO("vcl.se", "vcl_session_getImplementationName");
+
+ return "com.sun.star.frame.VCLSessionManagerClient";
+}
+
+Sequence< OUString > vcl_session_getSupportedServiceNames()
+{
+ SAL_INFO("vcl.se", "vcl_session_getSupportedServiceNames");
+
+ Sequence< OUString > aRet { "com.sun.star.frame.SessionManagerClient" };
+ return aRet;
+}
+
+css::uno::Reference< XInterface > vcl_session_createInstance( SAL_UNUSED_PARAMETER const css::uno::Reference< XMultiServiceFactory > & )
+{
+ SAL_INFO("vcl.se", "vcl_session_createInstance");
+
+ return static_cast< cppu::OWeakObject * >(new VCLSession);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/settings.cxx b/vcl/source/app/settings.cxx
new file mode 100644
index 000000000..c2ca35835
--- /dev/null
+++ b/vcl/source/app/settings.cxx
@@ -0,0 +1,3233 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <config_folders.h>
+
+#include <officecfg/Office/Common.hxx>
+
+#ifdef _WIN32
+#include <win/svsys.h>
+#endif
+
+#include <comphelper/processfactory.hxx>
+#include <rtl/bootstrap.hxx>
+
+#include <i18nlangtag/mslangid.hxx>
+#include <i18nlangtag/languagetag.hxx>
+
+#include <comphelper/lok.hxx>
+
+#include <vcl/graphicfilter.hxx>
+#include <IconThemeScanner.hxx>
+#include <IconThemeSelector.hxx>
+#include <vcl/IconThemeInfo.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/event.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/i18nhelp.hxx>
+#include <configsettings.hxx>
+#include <vcl/outdev.hxx>
+
+#include <unotools/fontcfg.hxx>
+#include <unotools/localedatawrapper.hxx>
+#include <unotools/confignode.hxx>
+#include <unotools/configmgr.hxx>
+#include <unotools/syslocale.hxx>
+#include <unotools/syslocaleoptions.hxx>
+
+using namespace ::com::sun::star;
+
+#include <svdata.hxx>
+
+struct ImplMouseData
+{
+ MouseSettingsOptions mnOptions = MouseSettingsOptions::NONE;
+ sal_uInt64 mnDoubleClkTime = 500;
+ long mnDoubleClkWidth = 2;
+ long mnDoubleClkHeight = 2;
+ long mnStartDragWidth = 2 ;
+ long mnStartDragHeight = 2;
+ sal_uLong mnButtonRepeat = 90;
+ sal_uLong mnMenuDelay = 150;
+ MouseFollowFlags mnFollow = MouseFollowFlags::Menu | MouseFollowFlags::DDList;
+ MouseMiddleButtonAction mnMiddleButtonAction= MouseMiddleButtonAction::AutoScroll;
+ MouseWheelBehaviour mnWheelBehavior = MouseWheelBehaviour::ALWAYS;
+};
+
+struct ImplStyleData
+{
+ ImplStyleData();
+ ImplStyleData( const ImplStyleData& rData );
+
+ void SetStandardStyles();
+
+ Color maActiveBorderColor;
+ Color maActiveColor;
+ Color maActiveTextColor;
+ Color maAlternatingRowColor;
+ Color maDefaultButtonTextColor;
+ Color maButtonTextColor;
+ Color maDefaultActionButtonTextColor;
+ Color maActionButtonTextColor;
+ Color maFlatButtonTextColor;
+ Color maDefaultButtonRolloverTextColor;
+ Color maButtonRolloverTextColor;
+ Color maDefaultActionButtonRolloverTextColor;
+ Color maActionButtonRolloverTextColor;
+ Color maFlatButtonRolloverTextColor;
+ Color maDefaultButtonPressedRolloverTextColor;
+ Color maButtonPressedRolloverTextColor;
+ Color maDefaultActionButtonPressedRolloverTextColor;
+ Color maActionButtonPressedRolloverTextColor;
+ Color maFlatButtonPressedRolloverTextColor;
+ Color maCheckedColor;
+ Color maDarkShadowColor;
+ Color maDeactiveBorderColor;
+ Color maDeactiveColor;
+ Color maDeactiveTextColor;
+ Color maDialogColor;
+ Color maDialogTextColor;
+ Color maDisableColor;
+ Color maFaceColor;
+ Color maFieldColor;
+ Color maFieldTextColor;
+ Color maFieldRolloverTextColor;
+ Color maFontColor;
+ Color maGroupTextColor;
+ Color maHelpColor;
+ Color maHelpTextColor;
+ Color maHighlightColor;
+ Color maHighlightLinkColor;
+ Color maHighlightTextColor;
+ Color maLabelTextColor;
+ Color maLightBorderColor;
+ Color maLightColor;
+ Color maLinkColor;
+ Color maMenuBarColor;
+ Color maMenuBarRolloverColor;
+ Color maMenuBorderColor;
+ Color maMenuColor;
+ Color maMenuHighlightColor;
+ Color maMenuHighlightTextColor;
+ Color maMenuTextColor;
+ Color maMenuBarTextColor;
+ Color maMenuBarRolloverTextColor;
+ Color maMenuBarHighlightTextColor;
+ Color maMonoColor;
+ Color maRadioCheckTextColor;
+ Color maShadowColor;
+ Color 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;
+ long mnTitleHeight;
+ long mnFloatTitleHeight;
+ long mnScrollBarSize;
+ long mnSpinSize;
+ long mnCursorSize;
+ long 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;
+ long 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_uLong 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( long nDoubleClkWidth )
+{
+ CopyData();
+ mxData->mnDoubleClkWidth = nDoubleClkWidth;
+}
+
+long
+MouseSettings::GetDoubleClickWidth() const
+{
+ return mxData->mnDoubleClkWidth;
+}
+
+void
+MouseSettings::SetDoubleClickHeight( long nDoubleClkHeight )
+{
+ CopyData();
+ mxData->mnDoubleClkHeight = nDoubleClkHeight;
+}
+
+long
+MouseSettings::GetDoubleClickHeight() const
+{
+ return mxData->mnDoubleClkHeight;
+}
+
+void
+MouseSettings::SetStartDragWidth( long nDragWidth )
+{
+ CopyData();
+ mxData->mnStartDragWidth = nDragWidth;
+}
+
+long
+MouseSettings::GetStartDragWidth() const
+{
+ return mxData->mnStartDragWidth;
+}
+
+void
+MouseSettings::SetStartDragHeight( long nDragHeight )
+{
+ CopyData();
+ mxData->mnStartDragHeight = nDragHeight;
+}
+
+long
+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_uLong
+MouseSettings::GetScrollRepeat()
+{
+ return 100;
+}
+
+sal_uLong
+MouseSettings::GetButtonStartRepeat()
+{
+ return 370;
+}
+
+void
+MouseSettings::SetButtonRepeat( sal_uLong nRepeat )
+{
+ CopyData();
+ mxData->mnButtonRepeat = nRepeat;
+}
+
+sal_uLong
+MouseSettings::GetButtonRepeat() const
+{
+ return mxData->mnButtonRepeat;
+}
+
+sal_uLong
+MouseSettings::GetActionDelay()
+{
+ return 250;
+}
+
+void
+MouseSettings::SetMenuDelay( sal_uLong nDelay )
+{
+ CopyData();
+ mxData->mnMenuDelay = nDelay;
+}
+
+sal_uLong
+MouseSettings::GetMenuDelay() const
+{
+ return mxData->mnMenuDelay;
+}
+
+void
+MouseSettings::SetFollow( MouseFollowFlags nFollow )
+{
+ CopyData();
+ mxData->mnFollow = nFollow;
+}
+
+MouseFollowFlags
+MouseSettings::GetFollow() const
+{
+ return mxData->mnFollow;
+}
+
+void
+MouseSettings::SetMiddleButtonAction( MouseMiddleButtonAction nAction )
+{
+ CopyData();
+ mxData->mnMiddleButtonAction = nAction;
+}
+
+MouseMiddleButtonAction
+MouseSettings::GetMiddleButtonAction() const
+{
+ return mxData->mnMiddleButtonAction;
+}
+
+void
+MouseSettings::SetWheelBehavior( MouseWheelBehaviour nBehavior )
+{
+ CopyData();
+ mxData->mnWheelBehavior = nBehavior;
+}
+
+MouseWheelBehaviour
+MouseSettings::GetWheelBehavior() const
+{
+ return mxData->mnWheelBehavior;
+}
+
+bool
+MouseSettings::operator !=( const MouseSettings& rSet ) const
+{
+ return !(*this == rSet);
+}
+
+MouseSettings::MouseSettings()
+ : mxData(std::make_shared<ImplMouseData>())
+{
+}
+
+void MouseSettings::CopyData()
+{
+ // copy if other references exist
+ if (mxData.use_count() > 1)
+ {
+ mxData = std::make_shared<ImplMouseData>(*mxData);
+ }
+}
+
+bool MouseSettings::operator ==( const MouseSettings& rSet ) const
+{
+ if ( mxData == rSet.mxData )
+ return true;
+
+ return
+ (mxData->mnOptions == rSet.mxData->mnOptions) &&
+ (mxData->mnDoubleClkTime == rSet.mxData->mnDoubleClkTime) &&
+ (mxData->mnDoubleClkWidth == rSet.mxData->mnDoubleClkWidth) &&
+ (mxData->mnDoubleClkHeight == rSet.mxData->mnDoubleClkHeight) &&
+ (mxData->mnStartDragWidth == rSet.mxData->mnStartDragWidth) &&
+ (mxData->mnStartDragHeight == rSet.mxData->mnStartDragHeight) &&
+ (mxData->mnMiddleButtonAction == rSet.mxData->mnMiddleButtonAction) &&
+ (mxData->mnButtonRepeat == rSet.mxData->mnButtonRepeat) &&
+ (mxData->mnMenuDelay == rSet.mxData->mnMenuDelay) &&
+ (mxData->mnFollow == rSet.mxData->mnFollow) &&
+ (mxData->mnWheelBehavior == rSet.mxData->mnWheelBehavior );
+}
+
+ImplStyleData::ImplStyleData() :
+ mnScrollBarSize(16),
+ mnSpinSize(16),
+ mnCursorSize(2),
+ mnAntialiasedMin(0),
+ mnCursorBlinkTime(STYLE_CURSOR_NOBLINKTIME),
+ mnDragFullOptions(DragFullOptions::All),
+ mnSelectionOptions(SelectionOptions::NONE),
+ mnDisplayOptions(DisplayOptions::NONE),
+ mnToolbarIconSize(ToolbarIconSize::Unknown),
+ mnOptions(StyleSettingsOptions::NONE),
+ mbAutoMnemonic(true),
+ meUseImagesInMenus(TRISTATE_INDET),
+ mnMinThumbSize(16),
+ mIconThemeSelector(std::make_shared<vcl::IconThemeSelector>()),
+ meContextMenuShortcuts(TRISTATE_INDET),
+ mnEdgeBlending(35),
+ maEdgeBlendingTopLeftColor(Color(0xC0, 0xC0, 0xC0)),
+ maEdgeBlendingBottomRightColor(Color(0x40, 0x40, 0x40)),
+ mnListBoxMaximumLineCount(25),
+ // For some reason this isn't actually the column count that gets used, at least on iOS, but
+ // instead what SvtAccessibilityOptions_Impl::GetColorValueSetColumnCount() in
+ // svtools/source/config/accessibilityoptions.cxx returns.
+ mnColorValueSetColumnCount(12),
+#ifdef IOS
+ maListBoxPreviewDefaultLogicSize(Size(30, 30)),
+#else
+ maListBoxPreviewDefaultLogicSize(Size(15, 7)),
+#endif
+ maListBoxPreviewDefaultPixelSize(Size(0, 0)), // on-demand calculated in GetListBoxPreviewDefaultPixelSize(),
+ mbPreviewUsesCheckeredBackground(true)
+{
+ SetStandardStyles();
+}
+
+ImplStyleData::ImplStyleData( const ImplStyleData& rData ) :
+ maActiveBorderColor( rData.maActiveBorderColor ),
+ maActiveColor( rData.maActiveColor ),
+ maActiveTextColor( rData.maActiveTextColor ),
+ maAlternatingRowColor( rData.maAlternatingRowColor ),
+ maDefaultButtonTextColor( rData.maDefaultButtonTextColor ),
+ maButtonTextColor( rData.maButtonTextColor ),
+ maDefaultActionButtonTextColor( rData.maDefaultActionButtonTextColor ),
+ maActionButtonTextColor( rData.maActionButtonTextColor ),
+ maFlatButtonTextColor( rData.maFlatButtonTextColor ),
+ maDefaultButtonRolloverTextColor( rData.maDefaultButtonRolloverTextColor ),
+ maButtonRolloverTextColor( rData.maButtonRolloverTextColor ),
+ maDefaultActionButtonRolloverTextColor( rData.maDefaultActionButtonRolloverTextColor ),
+ maActionButtonRolloverTextColor( rData.maActionButtonRolloverTextColor ),
+ maFlatButtonRolloverTextColor( rData.maFlatButtonRolloverTextColor ),
+ maDefaultButtonPressedRolloverTextColor( rData.maDefaultButtonPressedRolloverTextColor ),
+ maButtonPressedRolloverTextColor( rData.maButtonPressedRolloverTextColor ),
+ maDefaultActionButtonPressedRolloverTextColor( rData.maDefaultActionButtonPressedRolloverTextColor ),
+ maActionButtonPressedRolloverTextColor( rData.maActionButtonPressedRolloverTextColor ),
+ maFlatButtonPressedRolloverTextColor( rData.maFlatButtonPressedRolloverTextColor ),
+ maCheckedColor( rData.maCheckedColor ),
+ maDarkShadowColor( rData.maDarkShadowColor ),
+ maDeactiveBorderColor( rData.maDeactiveBorderColor ),
+ maDeactiveColor( rData.maDeactiveColor ),
+ maDeactiveTextColor( rData.maDeactiveTextColor ),
+ maDialogColor( rData.maDialogColor ),
+ maDialogTextColor( rData.maDialogTextColor ),
+ maDisableColor( rData.maDisableColor ),
+ maFaceColor( rData.maFaceColor ),
+ maFieldColor( rData.maFieldColor ),
+ maFieldTextColor( rData.maFieldTextColor ),
+ maFieldRolloverTextColor( rData.maFieldRolloverTextColor ),
+ maFontColor( rData.maFontColor ),
+ maGroupTextColor( rData.maGroupTextColor ),
+ maHelpColor( rData.maHelpColor ),
+ maHelpTextColor( rData.maHelpTextColor ),
+ maHighlightColor( rData.maHighlightColor ),
+ maHighlightLinkColor( rData.maHighlightLinkColor ),
+ maHighlightTextColor( rData.maHighlightTextColor ),
+ maLabelTextColor( rData.maLabelTextColor ),
+ maLightBorderColor( rData.maLightBorderColor ),
+ maLightColor( rData.maLightColor ),
+ maLinkColor( rData.maLinkColor ),
+ maMenuBarColor( rData.maMenuBarColor ),
+ maMenuBarRolloverColor( rData.maMenuBarRolloverColor ),
+ maMenuBorderColor( rData.maMenuBorderColor ),
+ maMenuColor( rData.maMenuColor ),
+ maMenuHighlightColor( rData.maMenuHighlightColor ),
+ maMenuHighlightTextColor( rData.maMenuHighlightTextColor ),
+ maMenuTextColor( rData.maMenuTextColor ),
+ maMenuBarTextColor( rData.maMenuBarTextColor ),
+ maMenuBarRolloverTextColor( rData.maMenuBarRolloverTextColor ),
+ maMenuBarHighlightTextColor( rData.maMenuBarHighlightTextColor ),
+ maMonoColor( rData.maMonoColor ),
+ maRadioCheckTextColor( rData.maRadioCheckTextColor ),
+ maShadowColor( rData.maShadowColor ),
+ 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;
+
+ 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;
+ maActiveColor = COL_BLUE;
+ maActiveTextColor = COL_WHITE;
+ maActiveBorderColor = COL_LIGHTGRAY;
+ maDeactiveColor = COL_GRAY;
+ maDeactiveTextColor = COL_LIGHTGRAY;
+ maDeactiveBorderColor = COL_LIGHTGRAY;
+ maMenuColor = COL_LIGHTGRAY;
+ maMenuBarColor = COL_LIGHTGRAY;
+ maMenuBarRolloverColor = COL_BLUE;
+ maMenuBorderColor = COL_LIGHTGRAY;
+ maMenuTextColor = COL_BLACK;
+ maMenuBarTextColor = COL_BLACK;
+ maMenuBarRolloverTextColor = COL_WHITE;
+ maMenuBarHighlightTextColor = COL_WHITE;
+ maMenuHighlightColor = COL_BLUE;
+ maMenuHighlightTextColor = COL_WHITE;
+ maHighlightColor = COL_BLUE;
+ maHighlightTextColor = COL_WHITE;
+ 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;
+ maHighlightLinkColor = COL_LIGHTBLUE;
+ maFontColor = COL_BLACK;
+ maAlternatingRowColor = Color( 0xEE, 0xEE, 0xEE );
+
+ mnTitleHeight = 18;
+ mnFloatTitleHeight = 13;
+ mbHighContrast = false;
+ mbUseSystemUIFonts = true;
+ mbUseFontAAFromSystem = true;
+ mnUseFlatBorders = false;
+ mnUseFlatMenus = false;
+ mbPreferredUseImagesInMenus = true;
+ mbSkipDisabledInMenus = false;
+ mbHideDisabledMenuItems = false;
+ mbPreferredContextMenuShortcuts = true;
+ mbPrimaryButtonWarpsSlider = false;
+}
+
+StyleSettings::StyleSettings()
+ : mxData(std::make_shared<ImplStyleData>())
+{
+}
+
+void
+StyleSettings::SetFaceColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maFaceColor = rColor;
+}
+
+const Color&
+StyleSettings::GetFaceColor() const
+{
+ return mxData->maFaceColor;
+}
+
+void
+StyleSettings::SetCheckedColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maCheckedColor = rColor;
+}
+
+const Color&
+StyleSettings::GetCheckedColor() const
+{
+ return mxData->maCheckedColor;
+}
+
+void
+StyleSettings::SetLightColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maLightColor = rColor;
+}
+
+const Color&
+StyleSettings::GetLightColor() const
+{
+ return mxData->maLightColor;
+}
+
+void
+StyleSettings::SetLightBorderColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maLightBorderColor = rColor;
+}
+
+const Color&
+StyleSettings::GetLightBorderColor() const
+{
+ return mxData->maLightBorderColor;
+}
+
+void
+StyleSettings::SetShadowColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maShadowColor = rColor;
+}
+
+const Color&
+StyleSettings::GetShadowColor() const
+{
+ return mxData->maShadowColor;
+}
+
+void
+StyleSettings::SetDarkShadowColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maDarkShadowColor = rColor;
+}
+
+const Color&
+StyleSettings::GetDarkShadowColor() const
+{
+ return mxData->maDarkShadowColor;
+}
+
+void
+StyleSettings::SetDefaultButtonTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maDefaultButtonTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetDefaultButtonTextColor() const
+{
+ return mxData->maDefaultButtonTextColor;
+}
+
+void
+StyleSettings::SetButtonTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maButtonTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetButtonTextColor() const
+{
+ return mxData->maButtonTextColor;
+}
+
+void
+StyleSettings::SetDefaultActionButtonTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maDefaultActionButtonTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetDefaultActionButtonTextColor() const
+{
+ return mxData->maDefaultActionButtonTextColor;
+}
+
+void
+StyleSettings::SetActionButtonTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maActionButtonTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetActionButtonTextColor() const
+{
+ return mxData->maActionButtonTextColor;
+}
+
+void
+StyleSettings::SetFlatButtonTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maFlatButtonTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetFlatButtonTextColor() const
+{
+ return mxData->maFlatButtonTextColor;
+}
+
+void
+StyleSettings::SetDefaultButtonRolloverTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maDefaultButtonRolloverTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetDefaultButtonRolloverTextColor() const
+{
+ return mxData->maDefaultButtonRolloverTextColor;
+}
+
+void
+StyleSettings::SetButtonRolloverTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maButtonRolloverTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetButtonRolloverTextColor() const
+{
+ return mxData->maButtonRolloverTextColor;
+}
+
+void
+StyleSettings::SetDefaultActionButtonRolloverTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maDefaultActionButtonRolloverTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetDefaultActionButtonRolloverTextColor() const
+{
+ return mxData->maDefaultActionButtonRolloverTextColor;
+}
+
+void
+StyleSettings::SetActionButtonRolloverTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maActionButtonRolloverTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetActionButtonRolloverTextColor() const
+{
+ return mxData->maActionButtonRolloverTextColor;
+}
+
+void
+StyleSettings::SetFlatButtonRolloverTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maFlatButtonRolloverTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetFlatButtonRolloverTextColor() const
+{
+ return mxData->maFlatButtonRolloverTextColor;
+}
+
+void
+StyleSettings::SetDefaultButtonPressedRolloverTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maDefaultButtonPressedRolloverTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetDefaultButtonPressedRolloverTextColor() const
+{
+ return mxData->maDefaultButtonPressedRolloverTextColor;
+}
+
+void
+StyleSettings::SetButtonPressedRolloverTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maButtonPressedRolloverTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetButtonPressedRolloverTextColor() const
+{
+ return mxData->maButtonPressedRolloverTextColor;
+}
+
+void
+StyleSettings::SetDefaultActionButtonPressedRolloverTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maDefaultActionButtonPressedRolloverTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetDefaultActionButtonPressedRolloverTextColor() const
+{
+ return mxData->maDefaultActionButtonPressedRolloverTextColor;
+}
+
+void
+StyleSettings::SetActionButtonPressedRolloverTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maActionButtonPressedRolloverTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetActionButtonPressedRolloverTextColor() const
+{
+ return mxData->maActionButtonPressedRolloverTextColor;
+}
+
+void
+StyleSettings::SetFlatButtonPressedRolloverTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maFlatButtonPressedRolloverTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetFlatButtonPressedRolloverTextColor() const
+{
+ return mxData->maFlatButtonPressedRolloverTextColor;
+}
+
+void
+StyleSettings::SetRadioCheckTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maRadioCheckTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetRadioCheckTextColor() const
+{
+ return mxData->maRadioCheckTextColor;
+}
+
+void
+StyleSettings::SetGroupTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maGroupTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetGroupTextColor() const
+{
+ return mxData->maGroupTextColor;
+}
+
+void
+StyleSettings::SetLabelTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maLabelTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetLabelTextColor() const
+{
+ return mxData->maLabelTextColor;
+}
+
+void
+StyleSettings::SetWindowColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maWindowColor = rColor;
+}
+
+const Color&
+StyleSettings::GetWindowColor() const
+{
+ return mxData->maWindowColor;
+}
+
+void
+StyleSettings::SetWindowTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maWindowTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetWindowTextColor() const
+{
+ return mxData->maWindowTextColor;
+}
+
+void
+StyleSettings::SetDialogColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maDialogColor = rColor;
+}
+
+const Color&
+StyleSettings::GetDialogColor() const
+{
+ return mxData->maDialogColor;
+}
+
+void
+StyleSettings::SetDialogTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maDialogTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetDialogTextColor() const
+{
+ return mxData->maDialogTextColor;
+}
+
+void
+StyleSettings::SetWorkspaceColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maWorkspaceColor = rColor;
+}
+
+const Color&
+StyleSettings::GetWorkspaceColor() const
+{
+ return mxData->maWorkspaceColor;
+}
+
+void
+StyleSettings::SetFieldColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maFieldColor = rColor;
+}
+
+const Color&
+StyleSettings::GetFieldColor() const
+{
+ return mxData->maFieldColor;
+}
+
+void
+StyleSettings::SetFieldTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maFieldTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetFieldTextColor() const
+{
+ return mxData->maFieldTextColor;
+}
+
+void
+StyleSettings::SetFieldRolloverTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maFieldRolloverTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetFieldRolloverTextColor() const
+{
+ return mxData->maFieldRolloverTextColor;
+}
+
+void
+StyleSettings::SetActiveColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maActiveColor = rColor;
+}
+
+const Color&
+StyleSettings::GetActiveColor() const
+{
+ return mxData->maActiveColor;
+}
+
+void
+StyleSettings::SetActiveTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maActiveTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetActiveTextColor() const
+{
+ return mxData->maActiveTextColor;
+}
+
+void
+StyleSettings::SetActiveBorderColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maActiveBorderColor = rColor;
+}
+
+const Color&
+StyleSettings::GetActiveBorderColor() const
+{
+ return mxData->maActiveBorderColor;
+}
+
+void
+StyleSettings::SetDeactiveColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maDeactiveColor = rColor;
+}
+
+const Color&
+StyleSettings::GetDeactiveColor() const
+{
+ return mxData->maDeactiveColor;
+}
+
+void
+StyleSettings::SetDeactiveTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maDeactiveTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetDeactiveTextColor() const
+{
+ return mxData->maDeactiveTextColor;
+}
+
+void
+StyleSettings::SetDeactiveBorderColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maDeactiveBorderColor = rColor;
+}
+
+const Color&
+StyleSettings::GetDeactiveBorderColor() const
+{
+ return mxData->maDeactiveBorderColor;
+}
+
+void
+StyleSettings::SetHighlightColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maHighlightColor = rColor;
+}
+
+const Color&
+StyleSettings::GetHighlightColor() const
+{
+ return mxData->maHighlightColor;
+}
+
+void
+StyleSettings::SetHighlightTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maHighlightTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetHighlightTextColor() const
+{
+ return mxData->maHighlightTextColor;
+}
+
+void
+StyleSettings::SetDisableColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maDisableColor = rColor;
+}
+
+const Color&
+StyleSettings::GetDisableColor() const
+{
+ return mxData->maDisableColor;
+}
+
+void
+StyleSettings::SetHelpColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maHelpColor = rColor;
+}
+
+const Color&
+StyleSettings::GetHelpColor() const
+{
+ return mxData->maHelpColor;
+}
+
+void
+StyleSettings::SetHelpTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maHelpTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetHelpTextColor() const
+{
+ return mxData->maHelpTextColor;
+}
+
+void
+StyleSettings::SetMenuColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maMenuColor = rColor;
+}
+
+const Color&
+StyleSettings::GetMenuColor() const
+{
+ return mxData->maMenuColor;
+}
+
+void
+StyleSettings::SetMenuBarColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maMenuBarColor = rColor;
+}
+
+const Color&
+StyleSettings::GetMenuBarColor() const
+{
+ return mxData->maMenuBarColor;
+}
+
+void
+StyleSettings::SetMenuBarRolloverColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maMenuBarRolloverColor = rColor;
+}
+
+const Color&
+StyleSettings::GetMenuBarRolloverColor() const
+{
+ return mxData->maMenuBarRolloverColor;
+}
+
+void
+StyleSettings::SetMenuBorderColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maMenuBorderColor = rColor;
+}
+
+const Color&
+StyleSettings::GetMenuBorderColor() const
+{
+ return mxData->maMenuBorderColor;
+}
+
+void
+StyleSettings::SetMenuTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maMenuTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetMenuTextColor() const
+{
+ return mxData->maMenuTextColor;
+}
+
+void
+StyleSettings::SetMenuBarTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maMenuBarTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetMenuBarTextColor() const
+{
+ return mxData->maMenuBarTextColor;
+}
+
+void
+StyleSettings::SetMenuBarRolloverTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maMenuBarRolloverTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetMenuBarRolloverTextColor() const
+{
+ return mxData->maMenuBarRolloverTextColor;
+}
+
+void
+StyleSettings::SetMenuBarHighlightTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maMenuBarHighlightTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetMenuBarHighlightTextColor() const
+{
+ return mxData->maMenuBarHighlightTextColor;
+}
+
+void
+StyleSettings::SetMenuHighlightColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maMenuHighlightColor = rColor;
+}
+
+const Color&
+StyleSettings::GetMenuHighlightColor() const
+{
+ return mxData->maMenuHighlightColor;
+}
+
+void
+StyleSettings::SetMenuHighlightTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maMenuHighlightTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetMenuHighlightTextColor() const
+{
+ return mxData->maMenuHighlightTextColor;
+}
+
+void
+StyleSettings::SetTabTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maTabTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetTabTextColor() const
+{
+ return mxData->maTabTextColor;
+}
+
+void
+StyleSettings::SetTabRolloverTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maTabRolloverTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetTabRolloverTextColor() const
+{
+ return mxData->maTabRolloverTextColor;
+}
+
+void
+StyleSettings::SetTabHighlightTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maTabHighlightTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetTabHighlightTextColor() const
+{
+ return mxData->maTabHighlightTextColor;
+}
+
+void
+StyleSettings::SetLinkColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maLinkColor = rColor;
+}
+
+const Color&
+StyleSettings::GetLinkColor() const
+{
+ return mxData->maLinkColor;
+}
+
+void
+StyleSettings::SetVisitedLinkColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maVisitedLinkColor = rColor;
+}
+
+const Color&
+StyleSettings::GetVisitedLinkColor() const
+{
+ return mxData->maVisitedLinkColor;
+}
+
+void
+StyleSettings::SetToolTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maToolTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetToolTextColor() const
+{
+ return mxData->maToolTextColor;
+}
+
+const Color&
+StyleSettings::GetHighlightLinkColor() const
+{
+ return mxData->maHighlightLinkColor;
+}
+
+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;
+}
+
+long
+StyleSettings::GetBorderSize()
+{
+ return 1;
+}
+
+void
+StyleSettings::SetTitleHeight( long nSize )
+{
+ CopyData();
+ mxData->mnTitleHeight = nSize;
+}
+
+long
+StyleSettings::GetTitleHeight() const
+{
+ return mxData->mnTitleHeight;
+}
+
+void
+StyleSettings::SetFloatTitleHeight( long nSize )
+{
+ CopyData();
+ mxData->mnFloatTitleHeight = nSize;
+}
+
+long
+StyleSettings::GetFloatTitleHeight() const
+{
+ return mxData->mnFloatTitleHeight;
+}
+
+void
+StyleSettings::SetScrollBarSize( long nSize )
+{
+ CopyData();
+ mxData->mnScrollBarSize = nSize;
+}
+
+long
+StyleSettings::GetScrollBarSize() const
+{
+ return mxData->mnScrollBarSize;
+}
+
+void
+StyleSettings::SetMinThumbSize( long nSize )
+{
+ CopyData();
+ mxData->mnMinThumbSize = nSize;
+}
+
+long
+StyleSettings::GetMinThumbSize() const
+{
+ return mxData->mnMinThumbSize;
+}
+
+void
+StyleSettings::SetSpinSize( long nSize )
+{
+ CopyData();
+ mxData->mnSpinSize = nSize;
+}
+
+long
+StyleSettings::GetSpinSize() const
+{
+ return mxData->mnSpinSize;
+}
+
+long
+StyleSettings::GetSplitSize()
+{
+ return 3;
+}
+
+void
+StyleSettings::SetCursorSize( long nSize )
+{
+ CopyData();
+ mxData->mnCursorSize = nSize;
+}
+
+long
+StyleSettings::GetCursorSize() const
+{
+ return mxData->mnCursorSize;
+}
+
+void
+StyleSettings::SetCursorBlinkTime( sal_uInt64 nBlinkTime )
+{
+ CopyData();
+ mxData->mnCursorBlinkTime = nBlinkTime;
+}
+
+sal_uInt64
+StyleSettings::GetCursorBlinkTime() const
+{
+ return static_cast<long>(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( long nMinPixel )
+{
+ CopyData();
+ mxData->mnAntialiasedMin = nMinPixel;
+}
+
+sal_uLong
+StyleSettings::GetAntialiasingMinPixelHeight() const
+{
+ return mxData->mnAntialiasedMin;
+}
+
+void
+StyleSettings::SetOptions( StyleSettingsOptions nOptions )
+{
+ CopyData();
+ mxData->mnOptions = nOptions;
+}
+
+void
+StyleSettings::SetAutoMnemonic( bool bAutoMnemonic )
+{
+ CopyData();
+ mxData->mbAutoMnemonic = bAutoMnemonic;
+}
+
+bool
+StyleSettings::GetAutoMnemonic() const
+{
+ return mxData->mbAutoMnemonic;
+}
+
+bool
+StyleSettings::GetDockingFloatsSupported()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ return pSVData->maNWFData.mbCanDetermineWindowPosition;
+}
+
+void
+StyleSettings::SetFontColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maFontColor = rColor;
+}
+
+const Color&
+StyleSettings::GetFontColor() const
+{
+ return mxData->maFontColor;
+}
+
+void
+StyleSettings::SetToolbarIconSize( ToolbarIconSize nSize )
+{
+ CopyData();
+ mxData->mnToolbarIconSize = nSize;
+}
+
+ToolbarIconSize
+StyleSettings::GetToolbarIconSize() const
+{
+ return mxData->mnToolbarIconSize;
+}
+
+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::GetColorValueSetMaximumRowCount()
+{
+ return 10;
+}
+
+sal_uInt16
+StyleSettings::GetListBoxPreviewDefaultLineWidth()
+{
+ return 1;
+}
+
+void
+StyleSettings::SetPreviewUsesCheckeredBackground(bool bNew)
+{
+ CopyData();
+ mxData->mbPreviewUsesCheckeredBackground = bNew;
+}
+
+bool
+StyleSettings::GetPreviewUsesCheckeredBackground() const
+{
+ return mxData->mbPreviewUsesCheckeredBackground;
+}
+
+bool
+StyleSettings::operator !=( const StyleSettings& rSet ) const
+{
+ return !(*this == rSet);
+}
+
+void StyleSettings::SetListBoxPreviewDefaultLogicSize(Size const& rSize)
+{
+ mxData->maListBoxPreviewDefaultLogicSize = rSize;
+}
+
+const Size& StyleSettings::GetListBoxPreviewDefaultPixelSize() const
+{
+ if(0 == mxData->maListBoxPreviewDefaultPixelSize.Width() || 0 == mxData->maListBoxPreviewDefaultPixelSize.Height())
+ {
+ const_cast< StyleSettings* >(this)->mxData->maListBoxPreviewDefaultPixelSize =
+ Application::GetDefaultDevice()->LogicToPixel(mxData->maListBoxPreviewDefaultLogicSize, MapMode(MapUnit::MapAppFont));
+ }
+
+ return mxData->maListBoxPreviewDefaultPixelSize;
+}
+
+void StyleSettings::Set3DColors( const Color& rColor )
+{
+ CopyData();
+ mxData->maFaceColor = rColor;
+ mxData->maLightBorderColor = rColor;
+ mxData->maMenuBorderColor = rColor;
+ mxData->maDarkShadowColor = COL_BLACK;
+ if ( rColor != COL_LIGHTGRAY )
+ {
+ mxData->maLightColor = rColor;
+ mxData->maShadowColor = rColor;
+ mxData->maDarkShadowColor=rColor;
+ mxData->maLightColor.IncreaseLuminance( 64 );
+ mxData->maShadowColor.DecreaseLuminance( 64 );
+ mxData->maDarkShadowColor.DecreaseLuminance( 100 );
+ sal_uLong nRed = mxData->maLightColor.GetRed();
+ sal_uLong nGreen = mxData->maLightColor.GetGreen();
+ sal_uLong nBlue = mxData->maLightColor.GetBlue();
+ nRed += static_cast<sal_uLong>(mxData->maShadowColor.GetRed());
+ nGreen += static_cast<sal_uLong>(mxData->maShadowColor.GetGreen());
+ nBlue += static_cast<sal_uLong>(mxData->maShadowColor.GetBlue());
+ mxData->maCheckedColor = Color( static_cast<sal_uInt8>(nRed/2), static_cast<sal_uInt8>(nGreen/2), static_cast<sal_uInt8>(nBlue/2) );
+ }
+ else
+ {
+ mxData->maCheckedColor = Color( 0x99, 0x99, 0x99 );
+ mxData->maLightColor = COL_WHITE;
+ mxData->maShadowColor = COL_GRAY;
+ }
+}
+
+void StyleSettings::SetCheckedColorSpecialCase( )
+{
+ CopyData();
+ // Light gray checked color special case
+ if ( GetFaceColor() == COL_LIGHTGRAY )
+ mxData->maCheckedColor = Color( 0xCC, 0xCC, 0xCC );
+ else
+ {
+ sal_uInt8 nRed = static_cast<sal_uInt8>((static_cast<sal_uInt16>(mxData->maFaceColor.GetRed()) + static_cast<sal_uInt16>(mxData->maLightColor.GetRed()))/2);
+ sal_uInt8 nGreen = static_cast<sal_uInt8>((static_cast<sal_uInt16>(mxData->maFaceColor.GetGreen()) + static_cast<sal_uInt16>(mxData->maLightColor.GetGreen()))/2);
+ sal_uInt8 nBlue = static_cast<sal_uInt8>((static_cast<sal_uInt16>(mxData->maFaceColor.GetBlue()) + static_cast<sal_uInt16>(mxData->maLightColor.GetBlue()))/2);
+ mxData->maCheckedColor = Color( nRed, nGreen, nBlue );
+ }
+}
+
+bool StyleSettings::GetUseImagesInMenus() const
+{
+ // icon mode selected in Tools -> Options... -> OpenOffice.org -> View
+ switch (mxData->meUseImagesInMenus) {
+ case TRISTATE_FALSE:
+ return false;
+ case TRISTATE_TRUE:
+ return true;
+ default: // TRISTATE_INDET:
+ return GetPreferredUseImagesInMenus();
+ }
+}
+
+static BitmapEx readBitmapEx( const OUString& rPath )
+{
+ OUString aPath( rPath );
+ rtl::Bootstrap::expandMacros( aPath );
+
+ // import the image
+ Graphic aGraphic;
+ if ( GraphicFilter::LoadGraphic( aPath, OUString(), aGraphic ) != ERRCODE_NONE )
+ return BitmapEx();
+ return aGraphic.GetBitmapEx();
+}
+
+namespace {
+
+enum WhichPersona { PERSONA_HEADER, PERSONA_FOOTER };
+
+}
+
+/** Update the setting of the Persona header / footer in ImplStyleData */
+static void setupPersonaHeaderFooter( WhichPersona eWhich, OUString& rHeaderFooter, BitmapEx& rHeaderFooterBitmap, std::optional<Color>& rMenuBarTextColor )
+{
+ uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
+ if ( !xContext.is() )
+ return;
+
+ // 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( xContext ) );
+ OUString aPersonaSettings( officecfg::Office::Common::Misc::PersonaSettings::get( xContext ) );
+
+ // 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( 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->maButtonTextColor == rSet.mxData->maButtonTextColor) &&
+ (mxData->maDefaultActionButtonTextColor == rSet.mxData->maDefaultActionButtonTextColor) &&
+ (mxData->maActionButtonTextColor == rSet.mxData->maActionButtonTextColor) &&
+ (mxData->maButtonRolloverTextColor == rSet.mxData->maButtonRolloverTextColor) &&
+ (mxData->maActionButtonRolloverTextColor == rSet.mxData->maActionButtonRolloverTextColor) &&
+ (mxData->maRadioCheckTextColor == rSet.mxData->maRadioCheckTextColor) &&
+ (mxData->maGroupTextColor == rSet.mxData->maGroupTextColor) &&
+ (mxData->maLabelTextColor == rSet.mxData->maLabelTextColor) &&
+ (mxData->maWindowColor == rSet.mxData->maWindowColor) &&
+ (mxData->maWindowTextColor == rSet.mxData->maWindowTextColor) &&
+ (mxData->maDialogColor == rSet.mxData->maDialogColor) &&
+ (mxData->maDialogTextColor == rSet.mxData->maDialogTextColor) &&
+ (mxData->maWorkspaceColor == rSet.mxData->maWorkspaceColor) &&
+ (mxData->maMonoColor == rSet.mxData->maMonoColor) &&
+ (mxData->maFieldColor == rSet.mxData->maFieldColor) &&
+ (mxData->maFieldTextColor == rSet.mxData->maFieldTextColor) &&
+ (mxData->maActiveColor == rSet.mxData->maActiveColor) &&
+ (mxData->maActiveTextColor == rSet.mxData->maActiveTextColor) &&
+ (mxData->maActiveBorderColor == rSet.mxData->maActiveBorderColor) &&
+ (mxData->maDeactiveColor == rSet.mxData->maDeactiveColor) &&
+ (mxData->maDeactiveTextColor == rSet.mxData->maDeactiveTextColor) &&
+ (mxData->maDeactiveBorderColor == rSet.mxData->maDeactiveBorderColor) &&
+ (mxData->maMenuColor == rSet.mxData->maMenuColor) &&
+ (mxData->maMenuBarColor == rSet.mxData->maMenuBarColor) &&
+ (mxData->maMenuBarRolloverColor == rSet.mxData->maMenuBarRolloverColor) &&
+ (mxData->maMenuBorderColor == rSet.mxData->maMenuBorderColor) &&
+ (mxData->maMenuTextColor == rSet.mxData->maMenuTextColor) &&
+ (mxData->maMenuBarTextColor == rSet.mxData->maMenuBarTextColor) &&
+ (mxData->maMenuBarRolloverTextColor == rSet.mxData->maMenuBarRolloverTextColor) &&
+ (mxData->maMenuHighlightColor == rSet.mxData->maMenuHighlightColor) &&
+ (mxData->maMenuHighlightTextColor == rSet.mxData->maMenuHighlightTextColor) &&
+ (mxData->maHighlightColor == rSet.mxData->maHighlightColor) &&
+ (mxData->maHighlightTextColor == rSet.mxData->maHighlightTextColor) &&
+ (mxData->maTabTextColor == rSet.mxData->maTabTextColor) &&
+ (mxData->maTabRolloverTextColor == rSet.mxData->maTabRolloverTextColor) &&
+ (mxData->maTabHighlightTextColor == rSet.mxData->maTabHighlightTextColor) &&
+ (mxData->maActiveTabColor == rSet.mxData->maActiveTabColor) &&
+ (mxData->maInactiveTabColor == rSet.mxData->maInactiveTabColor) &&
+ (mxData->maDisableColor == rSet.mxData->maDisableColor) &&
+ (mxData->maHelpColor == rSet.mxData->maHelpColor) &&
+ (mxData->maHelpTextColor == rSet.mxData->maHelpTextColor) &&
+ (mxData->maLinkColor == rSet.mxData->maLinkColor) &&
+ (mxData->maVisitedLinkColor == rSet.mxData->maVisitedLinkColor) &&
+ (mxData->maToolTextColor == rSet.mxData->maToolTextColor) &&
+ (mxData->maHighlightLinkColor == rSet.mxData->maHighlightLinkColor) &&
+ (mxData->maAppFont == rSet.mxData->maAppFont) &&
+ (mxData->maHelpFont == rSet.mxData->maHelpFont) &&
+ (mxData->maTitleFont == rSet.mxData->maTitleFont) &&
+ (mxData->maFloatTitleFont == rSet.mxData->maFloatTitleFont) &&
+ (mxData->maMenuFont == rSet.mxData->maMenuFont) &&
+ (mxData->maToolFont == rSet.mxData->maToolFont) &&
+ (mxData->maGroupFont == rSet.mxData->maGroupFont) &&
+ (mxData->maLabelFont == rSet.mxData->maLabelFont) &&
+ (mxData->maRadioCheckFont == rSet.mxData->maRadioCheckFont) &&
+ (mxData->maPushButtonFont == rSet.mxData->maPushButtonFont) &&
+ (mxData->maFieldFont == rSet.mxData->maFieldFont) &&
+ (mxData->maIconFont == rSet.mxData->maIconFont) &&
+ (mxData->maTabFont == rSet.mxData->maTabFont) &&
+ (mxData->meUseImagesInMenus == rSet.mxData->meUseImagesInMenus) &&
+ (mxData->mbPreferredUseImagesInMenus == rSet.mxData->mbPreferredUseImagesInMenus) &&
+ (mxData->mbSkipDisabledInMenus == rSet.mxData->mbSkipDisabledInMenus) &&
+ (mxData->mbHideDisabledMenuItems == rSet.mxData->mbHideDisabledMenuItems) &&
+ (mxData->mbPreferredContextMenuShortcuts == rSet.mxData->mbPreferredContextMenuShortcuts)&&
+ (mxData->meContextMenuShortcuts == rSet.mxData->meContextMenuShortcuts) &&
+ (mxData->mbPrimaryButtonWarpsSlider == rSet.mxData->mbPrimaryButtonWarpsSlider) &&
+ (mxData->maFontColor == rSet.mxData->maFontColor) &&
+ (mxData->mnEdgeBlending == rSet.mxData->mnEdgeBlending) &&
+ (mxData->maEdgeBlendingTopLeftColor == rSet.mxData->maEdgeBlendingTopLeftColor) &&
+ (mxData->maEdgeBlendingBottomRightColor == rSet.mxData->maEdgeBlendingBottomRightColor) &&
+ (mxData->mnListBoxMaximumLineCount == rSet.mxData->mnListBoxMaximumLineCount) &&
+ (mxData->mnColorValueSetColumnCount == rSet.mxData->mnColorValueSetColumnCount) &&
+ (mxData->maListBoxPreviewDefaultLogicSize == rSet.mxData->maListBoxPreviewDefaultLogicSize) &&
+ (mxData->maListBoxPreviewDefaultPixelSize == rSet.mxData->maListBoxPreviewDefaultPixelSize) &&
+ (mxData->mbPreviewUsesCheckeredBackground == rSet.mxData->mbPreviewUsesCheckeredBackground);
+}
+
+ImplMiscData::ImplMiscData() :
+ mnEnableATT(TRISTATE_INDET),
+ mnDisablePrinting(TRISTATE_INDET)
+{
+ static const char* pEnv = getenv("SAL_DECIMALSEP_ENABLED" ); // set default without UI
+ mbEnableLocalizedDecimalSep = (pEnv != nullptr);
+}
+
+MiscSettings::MiscSettings()
+ : mxData(std::make_shared<ImplMiscData>())
+{
+}
+
+bool MiscSettings::operator ==( const MiscSettings& rSet ) const
+{
+ if ( mxData == rSet.mxData )
+ return true;
+
+ return (mxData->mnEnableATT == rSet.mxData->mnEnableATT ) &&
+ (mxData->mnDisablePrinting == rSet.mxData->mnDisablePrinting ) &&
+ (mxData->mbEnableLocalizedDecimalSep == rSet.mxData->mbEnableLocalizedDecimalSep );
+}
+
+bool
+MiscSettings::operator !=( const MiscSettings& rSet ) const
+{
+ return !(*this == rSet);
+}
+
+bool MiscSettings::GetDisablePrinting() const
+{
+ if( mxData->mnDisablePrinting == TRISTATE_INDET )
+ {
+ OUString aEnable =
+ vcl::SettingsConfigItem::get()->
+ getValue( "DesktopManagement",
+ "DisablePrinting" );
+ mxData->mnDisablePrinting = aEnable.equalsIgnoreAsciiCase("true") ? TRISTATE_TRUE : TRISTATE_FALSE;
+ }
+
+ return mxData->mnDisablePrinting != TRISTATE_FALSE;
+}
+
+bool MiscSettings::GetEnableATToolSupport() const
+{
+
+#ifdef _WIN32
+ if( mxData->mnEnableATT == TRISTATE_INDET )
+ {
+ // Check in the Windows registry if an AT tool wants Accessibility support to
+ // be activated ..
+ HKEY hkey;
+
+ if( ERROR_SUCCESS == RegOpenKeyW(HKEY_CURRENT_USER,
+ L"Software\\LibreOffice\\Accessibility\\AtToolSupport",
+ &hkey) )
+ {
+ DWORD dwType;
+ wchar_t Data[6]; // possible values: "true", "false", "1", "0", DWORD
+ DWORD cbData = sizeof(Data);
+
+ if( ERROR_SUCCESS == RegQueryValueExW(hkey, L"SupportAssistiveTechnology",
+ nullptr, &dwType, reinterpret_cast<LPBYTE>(Data), &cbData) )
+ {
+ switch (dwType)
+ {
+ case REG_SZ:
+ mxData->mnEnableATT = ((0 == wcsicmp(Data, L"1")) || (0 == wcsicmp(Data, L"true"))) ? TRISTATE_TRUE : TRISTATE_FALSE;
+ break;
+ case REG_DWORD:
+ switch (reinterpret_cast<DWORD *>(Data)[0]) {
+ case 0:
+ mxData->mnEnableATT = TRISTATE_FALSE;
+ break;
+ case 1:
+ mxData->mnEnableATT = TRISTATE_TRUE;
+ break;
+ default:
+ mxData->mnEnableATT = TRISTATE_INDET;
+ //TODO: or TRISTATE_TRUE?
+ break;
+ }
+ break;
+ default:
+ // Unsupported registry type
+ break;
+ }
+ }
+
+ RegCloseKey(hkey);
+ }
+ }
+#endif
+
+ if( mxData->mnEnableATT == TRISTATE_INDET )
+ {
+ static const char* pEnv = getenv("SAL_ACCESSIBILITY_ENABLED" );
+ if( !pEnv || !*pEnv )
+ {
+ OUString aEnable =
+ vcl::SettingsConfigItem::get()->
+ getValue( "Accessibility",
+ "EnableATToolSupport" );
+ mxData->mnEnableATT = aEnable.equalsIgnoreAsciiCase("true") ? TRISTATE_TRUE : TRISTATE_FALSE;
+ }
+ else
+ {
+ mxData->mnEnableATT = TRISTATE_TRUE;
+ }
+ }
+
+ return mxData->mnEnableATT != TRISTATE_FALSE;
+}
+
+#ifdef _WIN32
+void MiscSettings::SetEnableATToolSupport( bool bEnable )
+{
+ if ( (bEnable ? TRISTATE_TRUE : TRISTATE_FALSE) != mxData->mnEnableATT )
+ {
+ if( bEnable && !ImplInitAccessBridge() )
+ return;
+
+ HKEY hkey;
+
+ // If the accessibility key in the Windows registry exists, change it synchronously
+ if( ERROR_SUCCESS == RegOpenKeyW(HKEY_CURRENT_USER,
+ L"Software\\LibreOffice\\Accessibility\\AtToolSupport",
+ &hkey) )
+ {
+ DWORD dwType;
+ wchar_t Data[6]; // possible values: "true", "false", 1, 0
+ DWORD cbData = sizeof(Data);
+
+ if( ERROR_SUCCESS == RegQueryValueExW(hkey, L"SupportAssistiveTechnology",
+ nullptr, &dwType, reinterpret_cast<LPBYTE>(Data), &cbData) )
+ {
+ switch (dwType)
+ {
+ case REG_SZ:
+ RegSetValueExW(hkey, L"SupportAssistiveTechnology",
+ 0, dwType,
+ reinterpret_cast<const BYTE*>(bEnable ? L"true" : L"false"),
+ bEnable ? sizeof(L"true") : sizeof(L"false"));
+ break;
+ case REG_DWORD:
+ reinterpret_cast<DWORD *>(Data)[0] = bEnable ? 1 : 0;
+ RegSetValueExW(hkey, L"SupportAssistiveTechnology",
+ 0, dwType, reinterpret_cast<const BYTE*>(Data), sizeof(DWORD));
+ break;
+ default:
+ // Unsupported registry type
+ break;
+ }
+ }
+
+ RegCloseKey(hkey);
+ }
+
+ vcl::SettingsConfigItem::get()->
+ setValue( "Accessibility",
+ "EnableATToolSupport",
+ bEnable ? OUString("true") : OUString("false" ) );
+ mxData->mnEnableATT = bEnable ? TRISTATE_TRUE : TRISTATE_FALSE;
+ }
+}
+#endif
+
+void MiscSettings::SetEnableLocalizedDecimalSep( bool bEnable )
+{
+ // copy if other references exist
+ if (mxData.use_count() > 1)
+ {
+ mxData = std::make_shared<ImplMiscData>(*mxData);
+ }
+ mxData->mbEnableLocalizedDecimalSep = bEnable;
+}
+
+bool MiscSettings::GetEnableLocalizedDecimalSep() const
+{
+ return mxData->mbEnableLocalizedDecimalSep;
+}
+
+HelpSettings::HelpSettings()
+ : mxData(std::make_shared<ImplHelpData>())
+{
+}
+
+bool HelpSettings::operator ==( const HelpSettings& rSet ) const
+{
+ if ( mxData == rSet.mxData )
+ return true;
+
+ return (mxData->mnTipTimeout == rSet.mxData->mnTipTimeout );
+}
+
+sal_uLong
+HelpSettings::GetTipDelay()
+{
+ return 500;
+}
+
+void
+HelpSettings::SetTipTimeout( sal_uLong nTipTimeout )
+{
+ // copy if other references exist
+ if (mxData.use_count() > 1)
+ {
+ mxData = std::make_shared<ImplHelpData>(*mxData);
+ }
+ mxData->mnTipTimeout = nTipTimeout;
+}
+
+sal_uLong
+HelpSettings::GetTipTimeout() const
+{
+ return mxData->mnTipTimeout;
+}
+
+sal_uLong
+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)
+ {
+ 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
+ uno::Reference<uno::XComponentContext> xContext(comphelper::getProcessComponentContext());
+ if (xContext.is())
+ {
+ sTheme = officecfg::Office::Common::Misc::SymbolStyle::get(xContext);
+
+ 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)
+{
+ mxData->mIconThemeSelector->SetPreferredIconTheme(theme, bDarkIconTheme);
+}
+
+void
+AllSettings::SetMouseSettings( const MouseSettings& rSet )
+{
+ CopyData();
+ mxData->maMouseSettings = rSet;
+}
+
+const MouseSettings&
+AllSettings::GetMouseSettings() const
+{
+ return mxData->maMouseSettings;
+}
+
+void
+AllSettings::SetStyleSettings( const StyleSettings& rSet )
+{
+ CopyData();
+ mxData->maStyleSettings = rSet;
+}
+
+void
+AllSettings::SetMiscSettings( const MiscSettings& rSet )
+{
+ CopyData();
+ mxData->maMiscSettings = rSet;
+}
+
+const MiscSettings&
+AllSettings::GetMiscSettings() const
+{
+ return mxData->maMiscSettings;
+}
+
+void
+AllSettings::SetHelpSettings( const HelpSettings& rSet )
+{
+ CopyData();
+ mxData->maHelpSettings = rSet;
+}
+
+const HelpSettings&
+AllSettings::GetHelpSettings() const
+{
+ return mxData->maHelpSettings;
+}
+
+bool
+AllSettings::operator !=( const AllSettings& rSet ) const
+{
+ return !(*this == rSet);
+}
+
+SvtSysLocale&
+AllSettings::GetSysLocale()
+{
+ return mxData->maSysLocale;
+}
+
+
+void StyleSettings::BatchSetBackgrounds( const Color &aBackColor,
+ bool bCheckedColorSpecialCase )
+{
+ Set3DColors( aBackColor );
+ SetFaceColor( aBackColor );
+ SetDialogColor( aBackColor );
+ SetWorkspaceColor( aBackColor );
+
+ if (bCheckedColorSpecialCase)
+ SetCheckedColorSpecialCase();
+}
+
+void StyleSettings::BatchSetFonts( const vcl::Font& aAppFont,
+ const vcl::Font& aLabelFont )
+{
+ SetAppFont( aAppFont );
+ SetPushButtonFont( aAppFont );
+ SetToolFont( aAppFont );
+ SetHelpFont( aAppFont );
+
+ SetMenuFont( aLabelFont );
+ SetTabFont( aLabelFont );
+ SetLabelFont( aLabelFont );
+ SetRadioCheckFont( aLabelFont );
+ SetFieldFont( aLabelFont );
+ SetGroupFont( aLabelFont );
+ SetIconFont( aLabelFont );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/sound.cxx b/vcl/source/app/sound.cxx
new file mode 100644
index 000000000..5d1c28e20
--- /dev/null
+++ b/vcl/source/app/sound.cxx
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <vcl/svapp.hxx>
+#include <vcl/window.hxx>
+#include <vcl/sound.hxx>
+
+#include <salframe.hxx>
+#include <svdata.hxx>
+
+void Sound::Beep()
+{
+ // #i91990#
+ if ( Application::IsHeadlessModeEnabled() )
+ return;
+
+ vcl::Window* pWindow = ImplGetDefaultWindow();
+
+ pWindow->ImplGetFrame()->Beep();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/stdtext.cxx b/vcl/source/app/stdtext.cxx
new file mode 100644
index 000000000..7d6bae820
--- /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,
+ const OUString& 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(Image(StockImage::Yes, SV_RESID_BITMAP_ERRORBOX));
+ rImages.emplace_back(Image(StockImage::Yes, SV_RESID_BITMAP_QUERYBOX));
+ rImages.emplace_back(Image(StockImage::Yes, SV_RESID_BITMAP_WARNINGBOX));
+ rImages.emplace_back(Image(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 const char* aResIdAry[static_cast<int>(StandardButtonType::Count)] =
+ {
+ // http://lists.freedesktop.org/archives/libreoffice/2013-January/044513.html
+ // Under windows we don't want accelerators on ok/cancel but do on other
+ // buttons
+#ifdef _WIN32
+ SV_BUTTONTEXT_OK_NOMNEMONIC,
+ SV_BUTTONTEXT_CANCEL_NOMNEMONIC,
+#else
+ SV_BUTTONTEXT_OK,
+ SV_BUTTONTEXT_CANCEL,
+#endif
+ SV_BUTTONTEXT_YES,
+ SV_BUTTONTEXT_NO,
+ SV_BUTTONTEXT_RETRY,
+ SV_BUTTONTEXT_HELP,
+ SV_BUTTONTEXT_CLOSE,
+ SV_BUTTONTEXT_MORE,
+ SV_BUTTONTEXT_IGNORE,
+ SV_BUTTONTEXT_ABORT,
+ SV_BUTTONTEXT_LESS,
+ STR_WIZDLG_PREVIOUS,
+ STR_WIZDLG_NEXT,
+ STR_WIZDLG_FINISH,
+ };
+
+ return VclResId(aResIdAry[static_cast<sal_uInt16>(eButton)]);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/svapp.cxx b/vcl/source/app/svapp.cxx
new file mode 100644
index 000000000..720fb32d4
--- /dev/null
+++ b/vcl/source/app/svapp.cxx
@@ -0,0 +1,1707 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <config_features.h>
+
+#include <osl/file.hxx>
+#include <osl/thread.hxx>
+#include <osl/module.hxx>
+#include <rtl/ustrbuf.hxx>
+
+#include <sal/log.hxx>
+
+#include <tools/debug.hxx>
+#include <tools/time.hxx>
+#include <tools/stream.hxx>
+
+#include <unotools/configmgr.hxx>
+#include <unotools/syslocaleoptions.hxx>
+
+#include <vcl/toolkit/dialog.hxx>
+#include <vcl/dialoghelper.hxx>
+#include <vcl/lok.hxx>
+#include <vcl/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>
+#if HAVE_FEATURE_OPENGL
+#include <vcl/opengl/OpenGLWrapper.hxx>
+#endif
+#include <vcl/skia/SkiaHelper.hxx>
+
+#include <salinst.hxx>
+#include <salframe.hxx>
+#include <salsys.hxx>
+#include <svdata.hxx>
+#include <displayconnectiondispatch.hxx>
+#include <window.h>
+#include <accmgr.hxx>
+#include <strings.hrc>
+#include <strings.hxx>
+#if OSL_DEBUG_LEVEL > 0
+#include <schedulerimpl.hxx>
+#endif
+
+#include <com/sun/star/uno/Reference.h>
+#include <com/sun/star/awt/XToolkit.hpp>
+#include <comphelper/lok.hxx>
+#include <comphelper/solarmutex.hxx>
+#include <osl/process.h>
+
+#include <cassert>
+#include <utility>
+#include <thread>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+
+namespace {
+void InitSettings(ImplSVData* pSVData);
+}
+
+// keycodes handled internally by VCL
+static 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
+{
+ VclEventId mnEvent;
+ VclPtr<vcl::Window> mpWin;
+ ImplSVEvent * mnEventId;
+ KeyEvent maKeyEvent;
+ MouseEvent maMouseEvent;
+ GestureEvent maGestureEvent;
+
+ ImplPostEventData(VclEventId nEvent, vcl::Window* pWin, const KeyEvent& rKeyEvent)
+ : mnEvent(nEvent)
+ , mpWin(pWin)
+ , mnEventId(nullptr)
+ , maKeyEvent(rKeyEvent)
+ {}
+ ImplPostEventData(VclEventId nEvent, vcl::Window* pWin, const MouseEvent& rMouseEvent)
+ : mnEvent(nEvent)
+ , mpWin(pWin)
+ , mnEventId(nullptr)
+ , maMouseEvent(rMouseEvent)
+ {}
+ ImplPostEventData(VclEventId nEvent, vcl::Window* pWin, const GestureEvent& rGestureEvent)
+ : mnEvent(nEvent)
+ , mpWin(pWin)
+ , mnEventId(nullptr)
+ , 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;
+}
+
+Application::~Application()
+{
+ ImplDeInitSVData();
+ ImplGetSVData()->mpApp = nullptr;
+}
+
+int Application::Main()
+{
+ SAL_WARN("vcl", "Application is a base class and should be overridden.");
+ return EXIT_SUCCESS;
+}
+
+bool Application::QueryExit()
+{
+ WorkWindow* pAppWin = ImplGetSVData()->maFrameData.mpAppWin;
+
+ // call the close handler of the application window
+ if ( pAppWin )
+ return pAppWin->Close();
+ else
+ return true;
+}
+
+void Application::Shutdown()
+{
+}
+
+void Application::Init()
+{
+}
+
+void Application::InitFinished()
+{
+}
+
+void Application::DeInit()
+{
+}
+
+sal_uInt16 Application::GetCommandLineParamCount()
+{
+ return static_cast<sal_uInt16>(osl_getCommandArgCount());
+}
+
+OUString Application::GetCommandLineParam( sal_uInt16 nParam )
+{
+ OUString aParam;
+ osl_getCommandArg( nParam, &aParam.pData );
+ return aParam;
+}
+
+OUString Application::GetAppFileName()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ SAL_WARN_IF( !pSVData->maAppData.mxAppFileName, "vcl", "AppFileName should be set to something after SVMain!" );
+ if ( pSVData->maAppData.mxAppFileName )
+ return *pSVData->maAppData.mxAppFileName;
+
+ /*
+ * provide a fallback for people without initialized vcl here (like setup
+ * in responsefile mode)
+ */
+ OUString aAppFileName;
+ OUString aExeFileName;
+ osl_getExecutableFile(&aExeFileName.pData);
+
+ // convert path to native file format
+ osl::FileBase::getSystemPathFromFileURL(aExeFileName, aAppFileName);
+
+ return aAppFileName;
+}
+
+void Application::Exception( ExceptionCategory nCategory )
+{
+ switch ( nCategory )
+ {
+ // System has precedence (so do nothing)
+ case ExceptionCategory::System:
+ case ExceptionCategory::UserInterface:
+ break;
+
+#ifdef DBG_UTIL
+ case ExceptionCategory::ResourceNotLoaded:
+ Abort("Resource not loaded");
+ break;
+ default:
+ Abort("Unknown Error");
+ break;
+#else
+ default:
+ Abort(OUString());
+ break;
+#endif
+ }
+}
+
+void Application::Abort( const OUString& rErrorText )
+{
+ //HACK: Dump core iff --norestore command line argument is given (assuming
+ // this process is run by developers who are interested in cores, vs. end
+ // users who are not):
+#if OSL_DEBUG_LEVEL > 0
+ bool dumpCore = true;
+#else
+ bool dumpCore = false;
+ sal_uInt16 n = GetCommandLineParamCount();
+ for (sal_uInt16 i = 0; i != n; ++i) {
+ if (GetCommandLineParam(i) == "--norestore") {
+ dumpCore = true;
+ break;
+ }
+ }
+#endif
+
+ SalAbort( rErrorText, dumpCore );
+}
+
+size_t Application::GetReservedKeyCodeCount()
+{
+ return SAL_N_ELEMENTS(ReservedKeys);
+}
+
+const vcl::KeyCode* Application::GetReservedKeyCode( size_t i )
+{
+ if( i >= GetReservedKeyCodeCount() )
+ return nullptr;
+ else
+ return &ReservedKeys[i];
+}
+
+IMPL_STATIC_LINK_NOARG( ImplSVAppData, ImplEndAllPopupsMsg, void*, void )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ while (pSVData->mpWinData->mpFirstFloat)
+ pSVData->mpWinData->mpFirstFloat->EndPopupMode(FloatWinPopupEndFlags::Cancel);
+}
+
+IMPL_STATIC_LINK_NOARG( ImplSVAppData, ImplEndAllDialogsMsg, void*, void )
+{
+ vcl::Window* pAppWindow = Application::GetFirstTopLevelWindow();
+ while (pAppWindow)
+ {
+ vcl::EndAllDialogs(pAppWindow);
+ pAppWindow = Application::GetNextTopLevelWindow(pAppWindow);
+ }
+}
+
+void Application::EndAllDialogs()
+{
+ Application::PostUserEvent( LINK( nullptr, ImplSVAppData, ImplEndAllDialogsMsg ) );
+}
+
+void Application::EndAllPopups()
+{
+ Application::PostUserEvent( LINK( nullptr, ImplSVAppData, ImplEndAllPopupsMsg ) );
+}
+
+
+namespace
+{
+ VclPtr<vcl::Window> GetEventWindow()
+ {
+ VclPtr<vcl::Window> xWin(Application::GetFirstTopLevelWindow());
+ while (xWin)
+ {
+ if (xWin->IsVisible())
+ break;
+ xWin.reset(Application::GetNextTopLevelWindow(xWin));
+ }
+ return xWin;
+ }
+
+ bool InjectKeyEvent(SvStream& rStream)
+ {
+ VclPtr<vcl::Window> xWin(GetEventWindow());
+ if (!xWin)
+ return false;
+
+ // skip the first available cycle and insert on the next one when we
+ // are trying the initial event, flagged by a triggered but undeleted
+ // mpEventTestingIdle
+ ImplSVData* pSVData = ImplGetSVData();
+ if (pSVData->maAppData.mpEventTestingIdle)
+ {
+ delete pSVData->maAppData.mpEventTestingIdle;
+ pSVData->maAppData.mpEventTestingIdle = nullptr;
+ return false;
+ }
+
+ sal_uInt16 nCode, nCharCode;
+ rStream.ReadUInt16(nCode);
+ rStream.ReadUInt16(nCharCode);
+ if (!rStream.good())
+ return false;
+
+ KeyEvent aVCLKeyEvt(nCharCode, nCode);
+ Application::PostKeyEvent(VclEventId::WindowKeyInput, xWin.get(), &aVCLKeyEvt);
+ Application::PostKeyEvent(VclEventId::WindowKeyUp, xWin.get(), &aVCLKeyEvt);
+ return true;
+ }
+
+ void CloseDialogsAndQuit()
+ {
+ Application::EndAllPopups();
+ Application::EndAllDialogs();
+ Application::PostUserEvent( LINK( nullptr, ImplSVAppData, ImplPrepareExitMsg ) );
+ }
+}
+
+IMPL_LINK_NOARG(ImplSVAppData, VclEventTestingHdl, Timer *, void)
+{
+ if (Application::AnyInput())
+ {
+ mpEventTestingIdle->Start();
+ }
+ else
+ {
+ Application::PostUserEvent( LINK( nullptr, ImplSVAppData, ImplVclEventTestingHdl ) );
+ }
+}
+
+IMPL_STATIC_LINK_NOARG( ImplSVAppData, ImplVclEventTestingHdl, void*, void )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ SAL_INFO("vcl.eventtesting", "EventTestLimit is " << pSVData->maAppData.mnEventTestLimit);
+ if (pSVData->maAppData.mnEventTestLimit == 0)
+ {
+ delete pSVData->maAppData.mpEventTestInput;
+ SAL_INFO("vcl.eventtesting", "Event Limit reached, exiting" << pSVData->maAppData.mnEventTestLimit);
+ CloseDialogsAndQuit();
+ }
+ else
+ {
+ if (InjectKeyEvent(*pSVData->maAppData.mpEventTestInput))
+ --pSVData->maAppData.mnEventTestLimit;
+ if (!pSVData->maAppData.mpEventTestInput->good())
+ {
+ SAL_INFO("vcl.eventtesting", "Event Input exhausted, exit next cycle");
+ pSVData->maAppData.mnEventTestLimit = 0;
+ }
+ Application::PostUserEvent( LINK( nullptr, ImplSVAppData, ImplVclEventTestingHdl ) );
+ }
+}
+
+IMPL_STATIC_LINK_NOARG( ImplSVAppData, ImplPrepareExitMsg, void*, void )
+{
+ //now close top level frames
+ (void)GetpApp()->QueryExit();
+}
+
+void Application::Execute()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maAppData.mbInAppExecute = true;
+ pSVData->maAppData.mbAppQuit = false;
+
+ if (Application::IsEventTestingModeEnabled())
+ {
+ pSVData->maAppData.mnEventTestLimit = 50;
+ pSVData->maAppData.mpEventTestingIdle = new Idle("eventtesting");
+ pSVData->maAppData.mpEventTestingIdle->SetInvokeHandler(LINK(&(pSVData->maAppData), ImplSVAppData, VclEventTestingHdl));
+ pSVData->maAppData.mpEventTestingIdle->SetPriority(TaskPriority::HIGH_IDLE);
+ pSVData->maAppData.mpEventTestInput = new SvFileStream("eventtesting", StreamMode::READ);
+ pSVData->maAppData.mpEventTestingIdle->Start();
+ }
+
+ 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 )
+{
+ return ImplYield(false, i_bAllEvents);
+}
+
+void Scheduler::ProcessEventsToIdle()
+{
+ int nSanity = 1;
+ while( Application::Reschedule( 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)
+ {
+ if (pSchedulerData->mpTask && !pSchedulerData->mbInScheduler)
+ {
+ Idle *pIdle = dynamic_cast<Idle*>(pSchedulerData->mpTask);
+ if (pIdle && pIdle->IsActive())
+ {
+ SAL_WARN("vcl.schedule", "Unprocessed Idle: "
+ << pIdle << " " << pIdle->GetDebugName());
+ }
+ }
+ 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()
+{
+ ImplYield(true, false);
+}
+
+IMPL_STATIC_LINK_NOARG( ImplSVAppData, ImplQuitMsg, void*, void )
+{
+ assert(ImplGetSVData()->maAppData.mbAppQuit);
+}
+
+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::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.mpSettings );
+ 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.mpSettings )
+ {
+ InitSettings(pSVData);
+ *pSVData->maAppData.mpSettings = rSettings;
+ }
+ else
+ {
+ AllSettings aOldSettings = *pSVData->maAppData.mpSettings;
+ if (aOldSettings.GetUILanguageTag().getLanguageType() != rSettings.GetUILanguageTag().getLanguageType() &&
+ pSVData->mbResLocaleSet)
+ {
+ pSVData->mbResLocaleSet = false;
+ }
+ *pSVData->maAppData.mpSettings = rSettings;
+ AllSettingsFlags nChangeFlags = aOldSettings.GetChangeFlags( *pSVData->maAppData.mpSettings );
+ 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
+ long nOldDPIX = 0;
+ long nOldDPIY = 0;
+ if ( pFirstFrame )
+ {
+ nOldDPIX = pFirstFrame->GetDPIX();
+ nOldDPIY = pFirstFrame->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->GetDPIX() != nOldDPIX) ||
+ (pFirstFrame->GetDPIY() != nOldDPIY) )
+ {
+ VirtualDevice* pVirDev = pSVData->maGDIData.mpFirstVirDev;
+ while ( pVirDev )
+ {
+ if ( pVirDev->mbScreenComp &&
+ (pVirDev->GetDPIX() == nOldDPIX) &&
+ (pVirDev->GetDPIY() == nOldDPIY) )
+ {
+ pVirDev->SetDPIX( pFirstFrame->GetDPIX() );
+ pVirDev->SetDPIY( pFirstFrame->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.mpSettings )
+ {
+ InitSettings(pSVData);
+ }
+
+ return *(pSVData->maAppData.mpSettings);
+}
+
+namespace {
+
+void InitSettings(ImplSVData* pSVData)
+{
+ assert(!pSVData->maAppData.mpSettings && "initialization should not happen twice!");
+
+ pSVData->maAppData.mpSettings.reset(new AllSettings());
+ if (!utl::ConfigManager::IsFuzzing())
+ {
+ pSVData->maAppData.mpCfgListener = new LocaleConfigurationListener;
+ pSVData->maAppData.mpSettings->GetSysLocale().GetOptions().AddListener( pSVData->maAppData.mpCfgListener );
+ }
+}
+
+}
+
+void Application::NotifyAllWindows( DataChangedEvent& rDCEvt )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ vcl::Window* pFrame = pSVData->maFrameData.mpFirstFrame;
+ while ( pFrame )
+ {
+ pFrame->NotifyAllChildren( rDCEvt );
+
+ vcl::Window* pSysWin = pFrame->mpWindowImpl->mpFrameData->mpFirstOverlap;
+ while ( pSysWin )
+ {
+ pSysWin->NotifyAllChildren( rDCEvt );
+ pSysWin = pSysWin->mpWindowImpl->mpNextOverlap;
+ }
+
+ pFrame = pFrame->mpWindowImpl->mpFrameData->mpNextFrame;
+ }
+}
+
+void Application::ImplCallEventListenersApplicationDataChanged( void* pData )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ VclWindowEvent aEvent( nullptr, VclEventId::ApplicationDataChanged, pData );
+
+ pSVData->maAppData.maEventListeners.Call( aEvent );
+}
+
+void Application::ImplCallEventListeners( VclSimpleEvent& rEvent )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maAppData.maEventListeners.Call( rEvent );
+}
+
+void Application::AddEventListener( const Link<VclSimpleEvent&,void>& rEventListener )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maAppData.maEventListeners.addListener( rEventListener );
+}
+
+void Application::RemoveEventListener( const Link<VclSimpleEvent&,void>& rEventListener )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maAppData.maEventListeners.removeListener( rEventListener );
+}
+
+void Application::AddKeyListener( const Link<VclWindowEvent&,bool>& rKeyListener )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maAppData.maKeyListeners.push_back( rKeyListener );
+}
+
+void Application::RemoveKeyListener( const Link<VclWindowEvent&,bool>& rKeyListener )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ auto & rVec = pSVData->maAppData.maKeyListeners;
+ rVec.erase( std::remove(rVec.begin(), rVec.end(), rKeyListener ), rVec.end() );
+}
+
+bool Application::HandleKey( VclEventId nEvent, vcl::Window *pWin, KeyEvent* pKeyEvent )
+{
+ // let listeners process the key event
+ VclWindowEvent aEvent( pWin, nEvent, static_cast<void *>(pKeyEvent) );
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ if ( pSVData->maAppData.maKeyListeners.empty() )
+ return false;
+
+ bool bProcessed = false;
+ // Copy the list, because this can be destroyed when calling a Link...
+ std::vector<Link<VclWindowEvent&,bool>> aCopy( pSVData->maAppData.maKeyListeners );
+ for ( const Link<VclWindowEvent&,bool>& rLink : aCopy )
+ {
+ if( rLink.Call( aEvent ) )
+ {
+ bProcessed = true;
+ break;
+ }
+ }
+ return bProcessed;
+}
+
+ImplSVEvent * Application::PostKeyEvent( VclEventId nEvent, vcl::Window *pWin, KeyEvent const * pKeyEvent )
+{
+ const SolarMutexGuard aGuard;
+ ImplSVEvent * nEventId = nullptr;
+
+ if( pWin && pKeyEvent )
+ {
+ std::unique_ptr<ImplPostEventData> pPostEventData(new ImplPostEventData( nEvent, pWin, *pKeyEvent ));
+
+ nEventId = PostUserEvent(
+ LINK( nullptr, Application, PostEventHandler ),
+ pPostEventData.get() );
+
+ if( nEventId )
+ {
+ pPostEventData->mnEventId = nEventId;
+ ImplGetSVData()->maAppData.maPostedEventList.emplace_back( pWin, pPostEventData.release() );
+ }
+ }
+
+ return nEventId;
+}
+
+ImplSVEvent* Application::PostGestureEvent(VclEventId nEvent, vcl::Window* pWin, GestureEvent const * pGestureEvent)
+{
+ const SolarMutexGuard aGuard;
+ ImplSVEvent * nEventId = nullptr;
+
+ if (pWin && pGestureEvent)
+ {
+ Point aTransformedPosition(pGestureEvent->mnX, pGestureEvent->mnY);
+
+ aTransformedPosition.AdjustX(pWin->GetOutOffXPixel());
+ aTransformedPosition.AdjustY(pWin->GetOutOffYPixel());
+
+ const GestureEvent aGestureEvent(
+ sal_Int32(aTransformedPosition.X()),
+ sal_Int32(aTransformedPosition.Y()),
+ pGestureEvent->meEventType,
+ pGestureEvent->mnOffset,
+ pGestureEvent->meOrientation
+ );
+
+ std::unique_ptr<ImplPostEventData> pPostEventData(new ImplPostEventData(nEvent, pWin, aGestureEvent));
+
+ nEventId = PostUserEvent(
+ LINK( nullptr, Application, PostEventHandler ),
+ pPostEventData.get());
+
+ if (nEventId)
+ {
+ pPostEventData->mnEventId = nEventId;
+ ImplGetSVData()->maAppData.maPostedEventList.emplace_back(pWin, pPostEventData.release());
+ }
+ }
+
+ return nEventId;
+}
+
+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;
+ // Double check that this is indeed a vcl::Window instance.
+ assert(dynamic_cast<vcl::Window *>(
+ static_cast<OutputDevice *>(rLink.GetInstance())) ==
+ static_cast<vcl::Window *>(rLink.GetInstance()));
+ pSVEvent->mpInstanceRef = static_cast<vcl::Window *>(rLink.GetInstance());
+ }
+
+ auto pTmpEvent = pSVEvent.get();
+ if (!pDefWindow->ImplGetFrame()->PostEvent( std::move(pSVEvent) ))
+ return nullptr;
+ return pTmpEvent;
+}
+
+void Application::RemoveUserEvent( ImplSVEvent * nUserEvent )
+{
+ if(nUserEvent)
+ {
+ SAL_WARN_IF( nUserEvent->mpWindow, "vcl",
+ "Application::RemoveUserEvent(): Event is send to a window" );
+ SAL_WARN_IF( !nUserEvent->mbCall, "vcl",
+ "Application::RemoveUserEvent(): Event is already removed" );
+
+ nUserEvent->mpWindow.clear();
+ nUserEvent->mpInstanceRef.clear();
+ nUserEvent->mbCall = false;
+ }
+}
+
+vcl::Window* Application::GetFocusWindow()
+{
+ return ImplGetSVData()->mpWinData->mpFocusWin;
+}
+
+OutputDevice* Application::GetDefaultDevice()
+{
+ return ImplGetDefaultWindow();
+}
+
+vcl::Window* Application::GetFirstTopLevelWindow()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ return pSVData->maFrameData.mpFirstFrame;
+}
+
+vcl::Window* Application::GetNextTopLevelWindow( vcl::Window const * pWindow )
+{
+ return pWindow->mpWindowImpl->mpFrameData->mpNextFrame;
+}
+
+long Application::GetTopWindowCount()
+{
+ 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( long nIndex )
+{
+ 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(const char *pId, const bool bLocalize)
+{
+ if (bLocalize)
+ return VclResId(pId);
+ else
+ return Translate::get(pId, Translate::Create("vcl", LanguageTag("en-US")));
+}
+
+OUString Application::GetHWOSConfInfo(const int bSelection, const bool bLocalize)
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ OUStringBuffer aDetails;
+
+ const auto appendDetails = [&aDetails](const OUStringLiteral& sep, auto&& val) {
+ if (!aDetails.isEmpty() && sep.getLength())
+ aDetails.append(sep);
+ aDetails.append(std::move(val));
+ };
+
+ if (bSelection != hwUI) {
+ appendDetails("; ", Localize(SV_APP_CPUTHREADS, bLocalize)
+ + OUString::number(std::thread::hardware_concurrency()));
+
+ OUString aVersion;
+ if ( pSVData && pSVData->mpDefInst )
+ aVersion = pSVData->mpDefInst->getOSVersion();
+ else
+ aVersion = "-";
+
+ appendDetails("; ", Localize(SV_APP_OSVERSION, bLocalize) + aVersion);
+ }
+
+ if (bSelection != hwEnv) {
+ appendDetails("; ", Localize(SV_APP_UIRENDER, bLocalize));
+#if HAVE_FEATURE_SKIA
+ if ( SkiaHelper::isVCLSkiaEnabled() )
+ {
+ switch(SkiaHelper::renderMethodToUse())
+ {
+ case SkiaHelper::RenderVulkan:
+ appendDetails("", Localize(SV_APP_SKIA_VULKAN, bLocalize));
+ break;
+ case SkiaHelper::RenderRaster:
+ appendDetails("", Localize(SV_APP_SKIA_RASTER, bLocalize));
+ break;
+ }
+ }
+ else
+#endif
+#if HAVE_FEATURE_OPENGL
+ if ( OpenGLWrapper::isVCLOpenGLEnabled() )
+ appendDetails("", Localize(SV_APP_GL, bLocalize));
+ else
+#endif
+ appendDetails("", Localize(SV_APP_DEFAULT, bLocalize));
+
+#if (defined LINUX || defined _WIN32 || defined MACOSX)
+ appendDetails("; ", SV_APP_VCLBACKEND + GetToolkitName());
+#endif
+ }
+
+ return aDetails.makeStringAndClear();
+}
+
+void Application::SetDisplayName( const OUString& rName )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maAppData.mxDisplayName = rName;
+}
+
+OUString Application::GetDisplayName()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if ( pSVData->maAppData.mxDisplayName )
+ return *(pSVData->maAppData.mxDisplayName);
+ else if (pSVData->maFrameData.mpAppWin)
+ return pSVData->maFrameData.mpAppWin->GetText();
+ else
+ return OUString();
+}
+
+unsigned int Application::GetScreenCount()
+{
+ SalSystem* pSys = ImplGetSalSystem();
+ return pSys ? pSys->GetDisplayScreenCount() : 0;
+}
+
+bool Application::IsUnifiedDisplay()
+{
+ SalSystem* pSys = ImplGetSalSystem();
+ return pSys == nullptr || pSys->IsUnifiedDisplay();
+}
+
+unsigned int Application::GetDisplayBuiltInScreen()
+{
+ SalSystem* pSys = ImplGetSalSystem();
+ return pSys ? pSys->GetDisplayBuiltInScreen() : 0;
+}
+
+unsigned int Application::GetDisplayExternalScreen()
+{
+ // This is really unpleasant, in theory we could have multiple
+ // external displays etc.
+ int nExternal(0);
+ switch (GetDisplayBuiltInScreen())
+ {
+ case 0:
+ nExternal = 1;
+ break;
+ case 1:
+ nExternal = 0;
+ break;
+ default:
+ // When the built-in display is neither 0 nor 1
+ // then place the full-screen presentation on the
+ // first available screen.
+ nExternal = 0;
+ break;
+ }
+ return nExternal;
+}
+
+tools::Rectangle Application::GetScreenPosSizePixel( unsigned int nScreen )
+{
+ SalSystem* pSys = ImplGetSalSystem();
+ if (!pSys)
+ {
+ SAL_WARN("vcl", "Requesting screen size/pos for screen #" << nScreen << " failed");
+ assert(false);
+ return tools::Rectangle();
+ }
+ tools::Rectangle aRect = pSys->GetDisplayScreenPosSizePixel(nScreen);
+ if (aRect.getHeight() == 0)
+ SAL_WARN("vcl", "Requesting screen size/pos for screen #" << nScreen << " returned 0 height.");
+ return aRect;
+}
+
+namespace {
+unsigned long calcDistSquare( const Point& i_rPoint, const tools::Rectangle& i_rRect )
+{
+ const Point aRectCenter( (i_rRect.Left() + i_rRect.Right())/2,
+ (i_rRect.Top() + i_rRect.Bottom())/ 2 );
+ const long nDX = aRectCenter.X() - i_rPoint.X();
+ const long nDY = aRectCenter.Y() - i_rPoint.Y();
+ return nDX*nDX + nDY*nDY;
+}
+}
+
+unsigned int Application::GetBestScreen( const tools::Rectangle& i_rRect )
+{
+ if( !IsUnifiedDisplay() )
+ return GetDisplayBuiltInScreen();
+
+ const unsigned int nScreens = GetScreenCount();
+ unsigned int nBestMatchScreen = 0;
+ unsigned long nOverlap = 0;
+ for( unsigned int i = 0; i < nScreens; i++ )
+ {
+ const tools::Rectangle aCurScreenRect( GetScreenPosSizePixel( i ) );
+ // if a screen contains the rectangle completely it is obviously the best screen
+ if( aCurScreenRect.IsInside( i_rRect ) )
+ return i;
+ // next the screen which contains most of the area of the rect is the best
+ tools::Rectangle aIntersection( aCurScreenRect.GetIntersection( i_rRect ) );
+ if( ! aIntersection.IsEmpty() )
+ {
+ const unsigned long nCurOverlap( aIntersection.GetWidth() * aIntersection.GetHeight() );
+ if( nCurOverlap > nOverlap )
+ {
+ nOverlap = nCurOverlap;
+ nBestMatchScreen = i;
+ }
+ }
+ }
+ if( nOverlap > 0 )
+ return nBestMatchScreen;
+
+ // finally the screen which center is nearest to the rect is the best
+ const Point aCenter( (i_rRect.Left() + i_rRect.Right())/2,
+ (i_rRect.Top() + i_rRect.Bottom())/2 );
+ unsigned long nDist = ULONG_MAX;
+ for( unsigned int i = 0; i < nScreens; i++ )
+ {
+ const tools::Rectangle aCurScreenRect( GetScreenPosSizePixel( i ) );
+ const unsigned 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* Application::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;
+}
+
+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.get();
+}
+
+void Application::SetFilterHdl( const Link<ConvertData&,bool>& rLink )
+{
+ ImplGetSVData()->maGDIData.mpGrfConverter->SetFilterHdl( rLink );
+}
+
+const LocaleDataWrapper& Application::GetAppLocaleDataWrapper()
+{
+ return GetSettings().GetLocaleDataWrapper();
+}
+
+void Application::EnableHeadlessMode( bool dialogsAreFatal )
+{
+ DialogCancelMode eNewMode = dialogsAreFatal ? DialogCancelMode::Fatal : DialogCancelMode::Silent;
+ DialogCancelMode eOldMode = GetDialogCancelMode();
+ assert(eOldMode == DialogCancelMode::Off || GetDialogCancelMode() == eNewMode);
+ if (eOldMode != eNewMode)
+ SetDialogCancelMode( eNewMode );
+}
+
+bool Application::IsHeadlessModeEnabled()
+{
+ return IsDialogCancelEnabled() || comphelper::LibreOfficeKit::isActive();
+}
+
+void Application::EnableBitmapRendering()
+{
+ ImplGetSVData()->maAppData.mbRenderToBitmaps = true;
+}
+
+bool Application::IsBitmapRendering()
+{
+ return ImplGetSVData()->maAppData.mbRenderToBitmaps;
+}
+
+void Application::EnableConsoleOnly()
+{
+ EnableHeadlessMode(true);
+ EnableBitmapRendering();
+}
+
+static bool bEventTestingMode = false;
+
+bool Application::IsEventTestingModeEnabled()
+{
+ return bEventTestingMode;
+}
+
+void Application::EnableEventTestingMode()
+{
+ bEventTestingMode = true;
+}
+
+static bool bSafeMode = false;
+
+bool Application::IsSafeModeEnabled()
+{
+ return bSafeMode;
+}
+
+void Application::EnableSafeMode()
+{
+ bSafeMode = true;
+}
+
+void Application::ShowNativeErrorBox(const OUString& sTitle ,
+ const OUString& sMessage)
+{
+ int btn = ImplGetSalSystem()->ShowNativeMessageBox(
+ sTitle,
+ sMessage);
+ if (btn != SALSYSTEM_SHOWNATIVEMSGBOX_BTN_OK) {
+ SAL_WARN( "vcl", "ShowNativeMessageBox returned " << btn);
+ }
+}
+
+const OUString& Application::GetDesktopEnvironment()
+{
+ if (IsHeadlessModeEnabled())
+ {
+ static const OUString aNone("none");
+ return aNone;
+ }
+ else
+ return SalGetDesktopEnvironment();
+}
+
+void Application::AddToRecentDocumentList(const OUString& rFileUrl, const OUString& rMimeType, const OUString& rDocumentService)
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->mpDefInst->AddToRecentDocumentList(rFileUrl, rMimeType, rDocumentService);
+}
+
+bool InitAccessBridge()
+{
+// Disable MSAA bridge on UNIX
+#if defined UNX
+ return true;
+#else
+ bool bRet = ImplInitAccessBridge();
+
+ if( !bRet )
+ {
+ // disable accessibility if the user chooses to continue
+ AllSettings aSettings = Application::GetSettings();
+ MiscSettings aMisc = aSettings.GetMiscSettings();
+ aMisc.SetEnableATToolSupport( false );
+ aSettings.SetMiscSettings( aMisc );
+ Application::SetSettings( aSettings );
+ }
+ return bRet;
+#endif // !UNX
+}
+
+// MT: AppEvent was in oldsv.cxx, but is still needed...
+void Application::AppEvent( const ApplicationEvent& /*rAppEvent*/ )
+{
+}
+
+bool Application::hasNativeFileSelection()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ return pSVData->mpDefInst->hasNativeFileSelection();
+}
+
+Reference< ui::dialogs::XFilePicker2 >
+Application::createFilePicker( const Reference< uno::XComponentContext >& xSM )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ return pSVData->mpDefInst->createFilePicker( xSM );
+}
+
+Reference< ui::dialogs::XFolderPicker2 >
+Application::createFolderPicker( const Reference< uno::XComponentContext >& xSM )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ return pSVData->mpDefInst->createFolderPicker( xSM );
+}
+
+void Application::setDeInitHook(Link<LinkParamNone*,void> const & hook) {
+ ImplSVData * pSVData = ImplGetSVData();
+ assert(!pSVData->maDeInitHook.IsSet());
+ pSVData->maDeInitHook = hook;
+ // Fake this for VCLXToolkit ctor instantiated from
+ // postprocess/CppunitTest_services.mk:
+ pSVData->maAppData.mbInAppMain = true;
+}
+
+namespace vcl::lok {
+
+void registerPollCallbacks(
+ LibreOfficeKitPollCallback pPollCallback,
+ LibreOfficeKitWakeCallback pWakeCallback,
+ void *pData) {
+
+ ImplSVData * pSVData = ImplGetSVData();
+ if (pSVData)
+ {
+ pSVData->mpPollCallback = pPollCallback;
+ pSVData->mpWakeCallback = pWakeCallback;
+ pSVData->mpPollClosure = pData;
+ }
+}
+
+void unregisterPollCallbacks()
+{
+ ImplSVData * pSVData = ImplGetSVData();
+ if (pSVData)
+ {
+ // 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;
+}
+
+} // namespace lok, namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/svdata.cxx b/vcl/source/app/svdata.cxx
new file mode 100644
index 000000000..05d53060b
--- /dev/null
+++ b/vcl/source/app/svdata.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <string.h>
+
+#include <comphelper/lok.hxx>
+#include <comphelper/processfactory.hxx>
+#include <tools/diagnose_ex.h>
+#include <unotools/resmgr.hxx>
+#include <sal/log.hxx>
+
+#include <configsettings.hxx>
+#include <vcl/dockwin.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 <units.hrc>
+#include <print.h>
+
+#include <com/sun/star/accessibility/MSAAService.hpp>
+
+#include <config_features.h>
+#if HAVE_FEATURE_OPENGL
+#include <vcl/opengl/OpenGLContext.hxx>
+#endif
+#include <basegfx/utils/systemdependentdata.hxx>
+#include <cppuhelper/basemutex.hxx>
+
+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, protected cppu::BaseMutex
+ {
+ private:
+ std::unique_ptr<AutoTimer> maTimer;
+ EntryMap maEntries;
+
+ DECL_LINK(implTimeoutHdl, Timer *, void);
+
+ public:
+ SystemDependentDataBuffer(const char* pDebugName)
+ : basegfx::SystemDependentDataManager(),
+ 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
+ {
+ ::osl::MutexGuard 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
+ {
+ ::osl::MutexGuard aGuard(m_aMutex);
+ EntryMap::iterator aFound(maEntries.find(rData));
+
+ if(aFound != maEntries.end())
+ {
+ maEntries.erase(aFound);
+ }
+ }
+
+ void touchUsage(basegfx::SystemDependentData_SharedPtr& rData) override
+ {
+ ::osl::MutexGuard aGuard(m_aMutex);
+ EntryMap::iterator aFound(maEntries.find(rData));
+
+ if(aFound != maEntries.end())
+ {
+ aFound->second = rData->calculateCombinedHoldCyclesInSeconds();
+ }
+ }
+
+ void flushAll() override
+ {
+ ::osl::MutexGuard aGuard(m_aMutex);
+
+ if(maTimer)
+ {
+ maTimer->Stop();
+ maTimer.reset();
+ }
+
+ maEntries.clear();
+ }
+ };
+
+ IMPL_LINK_NOARG(SystemDependentDataBuffer, implTimeoutHdl, Timer *, void)
+ {
+ ::osl::MutexGuard 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" );
+
+#if HAVE_FEATURE_OPENGL
+ // Add a reference to the default context so it never gets deleted
+ rtl::Reference<OpenGLContext> pContext = pSVData->mpDefaultWin->GetGraphics()->GetOpenGLContext();
+ if( pContext.is() )
+ pContext->acquire();
+#endif
+ }
+ 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(const char* pId)
+{
+ return Translate::get(pId, ImplGetResLocale());
+}
+
+const FieldUnitStringList& ImplGetFieldUnits()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if( pSVData->maCtrlData.maFieldUnitStrings.empty() )
+ {
+ sal_uInt32 nUnits = SAL_N_ELEMENTS(SV_FUNIT_STRINGS);
+ pSVData->maCtrlData.maFieldUnitStrings.reserve( nUnits );
+ for (sal_uInt32 i = 0; i < nUnits; i++)
+ {
+ std::pair<OUString, FieldUnit> aElement(VclResId(SV_FUNIT_STRINGS[i].first), SV_FUNIT_STRINGS[i].second);
+ pSVData->maCtrlData.maFieldUnitStrings.push_back( aElement );
+ }
+ }
+ return pSVData->maCtrlData.maFieldUnitStrings;
+}
+
+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)
+ {
+ // If current one is the static, clean it up to avoid having lingering references.
+ if (pSVData->mpWinData == &private_aImplSVWinData::get())
+ {
+ pSVData->mpWinData->mpFocusWin.reset();
+ }
+
+ pSVData->mpWinData = pSVWinData;
+ if (pSVData->mpWinData == nullptr)
+ {
+ pSVData->mpWinData = &private_aImplSVWinData::get(); // Never leave it null.
+ }
+ }
+}
+
+ImplSVData::ImplSVData()
+{
+ mpHelpData = &private_aImplSVHelpData::get();
+ mpWinData = &private_aImplSVWinData::get();
+}
+
+ImplSVHelpData* CreateSVHelpData()
+{
+ if (!comphelper::LibreOfficeKit::isActive())
+ return nullptr;
+
+ ImplSVHelpData* pNewData = new ImplSVHelpData;
+
+ // Set options set globally
+ ImplSVHelpData& aStaticHelpData = private_aImplSVHelpData::get();
+ pNewData->mbContextHelp = aStaticHelpData.mbContextHelp;
+ pNewData->mbExtHelp = aStaticHelpData.mbExtHelp;
+ pNewData->mbExtHelpMode = aStaticHelpData.mbExtHelpMode;
+ pNewData->mbOldBalloonMode = aStaticHelpData.mbOldBalloonMode;
+ pNewData->mbBalloonHelp = aStaticHelpData.mbBalloonHelp;
+ pNewData->mbQuickHelp = aStaticHelpData.mbQuickHelp;
+
+ return pNewData;
+}
+
+void DestroySVHelpData(ImplSVHelpData* pSVHelpData)
+{
+ if (!comphelper::LibreOfficeKit::isActive())
+ return;
+
+ // Change the SVData's help date if necessary
+ if(ImplGetSVData()->mpHelpData == pSVHelpData)
+ {
+ ImplGetSVData()->mpHelpData = &private_aImplSVHelpData::get();
+ }
+
+ if(pSVHelpData)
+ {
+ ImplDestroyHelpWindow(*pSVHelpData, false);
+ delete pSVHelpData;
+ }
+}
+
+void SetSVHelpData(ImplSVHelpData* pSVHelpData)
+{
+ if (!comphelper::LibreOfficeKit::isActive())
+ return;
+
+ ImplSVData* pSVData = ImplGetSVData();
+ if (pSVData->mpHelpData != pSVHelpData)
+ {
+ // 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() {}
+ImplSVGDIData::~ImplSVGDIData() {}
+ImplSVFrameData::~ImplSVFrameData() {}
+ImplSVWinData::~ImplSVWinData() {}
+ImplSVHelpData::~ImplSVHelpData() {}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/svmain.cxx b/vcl/source/app/svmain.cxx
new file mode 100644
index 000000000..e571c2f26
--- /dev/null
+++ b/vcl/source/app/svmain.cxx
@@ -0,0 +1,684 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+
+#include <cassert>
+
+#include <osl/file.hxx>
+#include <osl/signal.h>
+
+#include <desktop/exithelper.h>
+
+#include <comphelper/processfactory.hxx>
+#include <comphelper/asyncnotification.hxx>
+#include <i18nlangtag/mslangid.hxx>
+#include <unotools/syslocaleoptions.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>
+
+#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 <svdata.hxx>
+#include <vcl/svmain.hxx>
+#include <dbggui.hxx>
+#include <accmgr.hxx>
+#include <PhysicalFontCollection.hxx>
+#include <print.h>
+#include <salsys.hxx>
+#include <saltimer.hxx>
+#include <displayconnectiondispatch.hxx>
+
+#include <config_features.h>
+#include <config_feature_opencl.h>
+
+#include <osl/process.h>
+#include <com/sun/star/lang/XComponent.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+
+#ifdef _WIN32
+#include <com/sun/star/datatransfer/clipboard/XClipboard.hpp>
+#endif
+
+#include <comphelper/lok.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <uno/current_context.hxx>
+
+#include <opencl/OpenCLZone.hxx>
+#include <opengl/zone.hxx>
+#include <skia/zone.hxx>
+#include <watchdog.hxx>
+
+#include <basegfx/utils/systemdependentdata.hxx>
+#include <tools/diagnose_ex.h>
+
+#if OSL_DEBUG_LEVEL > 0
+#include <typeinfo>
+#include <rtl/strbuf.hxx>
+#endif
+
+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
+ }
+
+ // RC
+ if ((pInfo->Signal == osl_Signal_User) &&
+ (pInfo->UserSignal == OSL_SIGNAL_USER_RESOURCEFAILURE) )
+ nVCLException = ExceptionCategory::ResourceNotLoaded;
+
+ // DISPLAY-Unix
+ if ((pInfo->Signal == osl_Signal_User) &&
+ (pInfo->UserSignal == OSL_SIGNAL_USER_X11SUBSYSTEMERROR) )
+ nVCLException = ExceptionCategory::UserInterface;
+
+ if ( nVCLException != ExceptionCategory::NONE )
+ {
+ bIn = true;
+
+ vcl::SolarMutexTryAndBuyGuard aLock;
+ if( aLock.isAcquired())
+ {
+ // do not stop timer because otherwise the UAE-Box will not be painted as well
+ ImplSVData* pSVData = ImplGetSVData();
+ if ( pSVData->mpApp )
+ {
+ SystemWindowFlags nOldMode = Application::GetSystemWindowMode();
+ Application::SetSystemWindowMode( nOldMode & ~SystemWindowFlags::NOAUTOMODE );
+ pSVData->mpApp->Exception( nVCLException );
+ Application::SetSystemWindowMode( nOldMode );
+ }
+ }
+ bIn = false;
+ }
+
+ return osl_Signal_ActCallNextHdl;
+
+}
+
+int ImplSVMain()
+{
+ // The 'real' SVMain()
+ ImplSVData* pSVData = ImplGetSVData();
+
+ SAL_WARN_IF( !pSVData->mpApp, "vcl", "no instance of class Application" );
+
+ int nReturn = EXIT_FAILURE;
+
+ const bool bWasInitVCL = IsVCLInit();
+ const bool bInit = bWasInitVCL || InitVCL();
+ int nRet = 0;
+ if (!bWasInitVCL && bInit && pSVData->mpDefInst->SVMainHook(&nRet))
+ return nRet;
+
+ if( bInit )
+ {
+ // call application main
+ pSVData->maAppData.mbInAppMain = true;
+ nReturn = pSVData->mpApp->Main();
+ pSVData->maAppData.mbInAppMain = false;
+ }
+
+ if( pSVData->mxDisplayConnection.is() )
+ {
+ pSVData->mxDisplayConnection->terminate();
+ pSVData->mxDisplayConnection.clear();
+ }
+
+ // This is a hack to work around the problem of the asynchronous nature
+ // of bridging accessibility through Java: on shutdown there might still
+ // be some events in the AWT EventQueue, which need the SolarMutex which
+ // - on the other hand - is destroyed in DeInitVCL(). So empty the queue
+ // here ..
+ if( pSVData->mxAccessBridge.is() )
+ {
+ {
+ SolarMutexReleaser aReleaser;
+ pSVData->mxAccessBridge->dispose();
+ }
+ pSVData->mxAccessBridge.clear();
+ }
+
+ WatchdogThread::stop();
+ DeInitVCL();
+
+ return nReturn;
+}
+
+int SVMain()
+{
+ return ImplSVMain();
+}
+
+// This variable is set when no Application object has been instantiated
+// before InitVCL is called
+static Application * pOwnSvApp = nullptr;
+
+// Exception handler. pExceptionHandler != NULL => VCL already inited
+static oslSignalHandler pExceptionHandler = nullptr;
+
+namespace {
+
+class DesktopEnvironmentContext: public cppu::WeakImplHelper< css::uno::XCurrentContext >
+{
+public:
+ explicit DesktopEnvironmentContext( const css::uno::Reference< css::uno::XCurrentContext > & ctx)
+ : m_xNextContext( ctx ) {}
+
+ // XCurrentContext
+ virtual css::uno::Any SAL_CALL getValueByName( const OUString& Name ) override;
+
+private:
+ css::uno::Reference< css::uno::XCurrentContext > m_xNextContext;
+};
+
+}
+
+uno::Any SAL_CALL DesktopEnvironmentContext::getValueByName( const OUString& Name)
+{
+ uno::Any retVal;
+
+ if ( Name == "system.desktop-environment" )
+ {
+ retVal <<= Application::GetDesktopEnvironment();
+ }
+ else if( m_xNextContext.is() )
+ {
+ // Call next context in chain if found
+ retVal = m_xNextContext->getValueByName( Name );
+ }
+ return retVal;
+}
+
+bool IsVCLInit()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ return pExceptionHandler != nullptr &&
+ pSVData->mpApp != nullptr &&
+ pSVData->mpDefInst != nullptr;
+}
+
+#ifdef DBG_UTIL
+namespace vclmain
+{
+ bool isAlive()
+ {
+ return ImplGetSVData()->mpDefInst;
+ }
+}
+#endif
+
+
+bool InitVCL()
+{
+ if (IsVCLInit())
+ {
+ SAL_INFO("vcl.app", "Double initialization of vcl");
+ return true;
+ }
+
+ if( pExceptionHandler != nullptr )
+ return false;
+
+ EmbeddedFontsHelper::clearTemporaryFontFiles();
+
+ if( !ImplGetSVData()->mpApp )
+ {
+ pOwnSvApp = new Application();
+ }
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ // remember Main-Thread-Id
+ pSVData->mnMainThreadId = ::osl::Thread::getCurrentIdentifier();
+
+ // Initialize Sal
+ pSVData->mpDefInst = CreateSalInstance();
+ if ( !pSVData->mpDefInst )
+ return false;
+
+ // 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(".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<PhysicalFontCollection>();
+ pSVData->maGDIData.mxScreenFontCache = std::make_shared<ImplFontCache>();
+ pSVData->maGDIData.mpGrfConverter = 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" );
+ long nTopWindowCount = Application::GetTopWindowCount();
+ long nBadTopWindows = nTopWindowCount;
+ for( long i = 0; i < nTopWindowCount; i++ )
+ {
+ vcl::Window* pWin = Application::GetTopWindow( i );
+ // default window will be destroyed further down
+ // but may still be useful during deinit up to that point
+ if( pWin == pSVData->mpDefaultWin )
+ nBadTopWindows--;
+ else
+ {
+ aBuf.append( "text = \"" );
+ aBuf.append( OUStringToOString( pWin->GetText(), osl_getThreadTextEncoding() ) );
+ aBuf.append( "\" type = \"" );
+ aBuf.append( typeid(*pWin).name() );
+ aBuf.append( "\", ptr = 0x" );
+ aBuf.append( reinterpret_cast<sal_Int64>( pWin ), 16 );
+ aBuf.append( "\n" );
+ }
+ }
+ SAL_WARN_IF( nBadTopWindows!=0, "vcl", aBuf.getStr() );
+#endif
+
+ ImageTree::get().shutdown();
+
+ osl_removeSignalHandler( pExceptionHandler);
+ pExceptionHandler = nullptr;
+
+ // free global data
+ if (pSVData->maGDIData.mpGrfConverter)
+ {
+ delete pSVData->maGDIData.mpGrfConverter;
+ pSVData->maGDIData.mpGrfConverter = nullptr;
+ }
+
+ pSVData->mpSettingsConfigItem.reset();
+
+ // prevent unnecessary painting during Scheduler shutdown
+ // as this processes all pending events in debug builds.
+ ImplGetSystemDependentDataManager().flushAll();
+
+ Scheduler::ImplDeInitScheduler();
+
+ pSVData->mpWinData->maMsgBoxImgList.clear();
+ pSVData->maCtrlData.maCheckImgList.clear();
+ pSVData->maCtrlData.maRadioImgList.clear();
+ pSVData->maCtrlData.mpDisclosurePlus.reset();
+ pSVData->maCtrlData.mpDisclosureMinus.reset();
+ pSVData->mpDefaultWin.disposeAndClear();
+
+#if defined _WIN32
+ // See GetSystemClipboard (vcl/source/treelist/transfer2.cxx):
+ if (auto const comp = css::uno::Reference<css::lang::XComponent>(
+ pSVData->m_xSystemClipboard, css::uno::UNO_QUERY))
+ {
+ SolarMutexReleaser r; // unblock pending "clipboard content changed" notifications
+ comp->dispose(); // will use CWinClipbImpl::s_aMutex
+ }
+#endif
+
+#ifndef NDEBUG
+ DbgGUIDeInitSolarMutexCheck();
+#endif
+
+ if ( pSVData->mpUnoWrapper )
+ {
+ try
+ {
+ uno::Reference<frame::XDesktop2> const xDesktop = frame::Desktop::create(
+ comphelper::getProcessComponentContext() );
+ xDesktop->addEventListener(new VCLUnoWrapperDeleter);
+ }
+ catch (uno::Exception const&)
+ {
+ // ignore
+ }
+ }
+
+ if( pSVData->mpApp || pSVData->maDeInitHook.IsSet() )
+ {
+ SolarMutexReleaser aReleaser;
+ // call deinit to deinitialize application class
+ // soffice/sfx implementation disposes the global service manager
+ // Warning: After this call you can't call uno services
+ if( pSVData->mpApp )
+ {
+ pSVData->mpApp->DeInit();
+ }
+ if( pSVData->maDeInitHook.IsSet() )
+ {
+ pSVData->maDeInitHook.Call(nullptr);
+ }
+ }
+
+ if ( pSVData->maAppData.mpSettings )
+ {
+ if ( pSVData->maAppData.mpCfgListener )
+ {
+ pSVData->maAppData.mpSettings->GetSysLocale().GetOptions().RemoveListener( pSVData->maAppData.mpCfgListener );
+ delete pSVData->maAppData.mpCfgListener;
+ }
+
+ pSVData->maAppData.mpSettings.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->maGDIData.maScaleCache.remove_if([](const lru_scale_cache::key_value_pair_t&)
+ { return true; });
+
+ pSVData->maGDIData.maThemeDrawCommandsCache.clear();
+ pSVData->maGDIData.maThemeImageCache.clear();
+
+ // Deinit Sal
+ if (pSVData->mpDefInst)
+ {
+ DestroySalInstance( pSVData->mpDefInst );
+ pSVData->mpDefInst = nullptr;
+ }
+
+ 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
+
+ unsigned uThreadID;
+ 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
+ &uThreadID )); // thread id to fill
+#else
+ hThreadID = osl_createThread( MainWorkerFunction, new WorkerThreadData( pWorker, pThreadData ) );
+#endif
+}
+
+void JoinMainLoopThread()
+{
+ if( hThreadID )
+ {
+#ifdef _WIN32
+ WaitForSingleObject(hThreadID, INFINITE);
+#else
+ osl_joinWithThread(hThreadID);
+ osl_destroyThread( hThreadID );
+#endif
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/timer.cxx b/vcl/source/app/timer.cxx
new file mode 100644
index 000000000..75a97d867
--- /dev/null
+++ b/vcl/source/app/timer.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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()
+{
+ Task::Start();
+ Task::StartTimer( mnTimeout );
+}
+
+void Timer::SetTimeout( sal_uInt64 nNewTimeout )
+{
+ mnTimeout = nNewTimeout;
+ // If timer is active, then renew clock.
+ if ( IsActive() )
+ StartTimer( mnTimeout );
+}
+
+AutoTimer::AutoTimer( const char *pDebugName )
+ : Timer( true, pDebugName )
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/unohelp.cxx b/vcl/source/app/unohelp.cxx
new file mode 100644
index 000000000..6d9e6f31a
--- /dev/null
+++ b/vcl/source/app/unohelp.cxx
@@ -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 .
+ */
+
+#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() );
+}
+
+void vcl::unohelper::NotifyAccessibleStateEventGlobally( const css::accessibility::AccessibleEventObject& rEventObject )
+{
+ css::uno::Reference< css::awt::XExtendedToolkit > xExtToolkit( Application::GetVCLToolkit(), uno::UNO_QUERY );
+ if ( xExtToolkit.is() )
+ {
+ // Only for focus events
+ sal_Int16 nType = css::accessibility::AccessibleStateType::INVALID;
+ rEventObject.NewValue >>= nType;
+ if ( nType == css::accessibility::AccessibleStateType::FOCUSED )
+ xExtToolkit->fireFocusGained( rEventObject.Source );
+ else
+ {
+ rEventObject.OldValue >>= nType;
+ if ( nType == css::accessibility::AccessibleStateType::FOCUSED )
+ xExtToolkit->fireFocusLost( rEventObject.Source );
+ }
+
+ }
+}
+
+float vcl::unohelper::ConvertFontWidth( FontWidth eWidth )
+{
+ if( eWidth == WIDTH_DONTKNOW )
+ return css::awt::FontWidth::DONTKNOW;
+ else if( eWidth == WIDTH_ULTRA_CONDENSED )
+ return css::awt::FontWidth::ULTRACONDENSED;
+ else if( eWidth == WIDTH_EXTRA_CONDENSED )
+ return css::awt::FontWidth::EXTRACONDENSED;
+ else if( eWidth == WIDTH_CONDENSED )
+ return css::awt::FontWidth::CONDENSED;
+ else if( eWidth == WIDTH_SEMI_CONDENSED )
+ return css::awt::FontWidth::SEMICONDENSED;
+ else if( eWidth == WIDTH_NORMAL )
+ return css::awt::FontWidth::NORMAL;
+ else if( eWidth == WIDTH_SEMI_EXPANDED )
+ return css::awt::FontWidth::SEMIEXPANDED;
+ else if( eWidth == WIDTH_EXPANDED )
+ return css::awt::FontWidth::EXPANDED;
+ else if( eWidth == WIDTH_EXTRA_EXPANDED )
+ return css::awt::FontWidth::EXTRAEXPANDED;
+ else if( eWidth == WIDTH_ULTRA_EXPANDED )
+ return css::awt::FontWidth::ULTRAEXPANDED;
+
+ OSL_FAIL( "Unknown FontWidth" );
+ return css::awt::FontWidth::DONTKNOW;
+}
+
+FontWidth vcl::unohelper::ConvertFontWidth( float f )
+{
+ if( f <= css::awt::FontWidth::DONTKNOW )
+ return WIDTH_DONTKNOW;
+ else if( f <= css::awt::FontWidth::ULTRACONDENSED )
+ return WIDTH_ULTRA_CONDENSED;
+ else if( f <= css::awt::FontWidth::EXTRACONDENSED )
+ return WIDTH_EXTRA_CONDENSED;
+ else if( f <= css::awt::FontWidth::CONDENSED )
+ return WIDTH_CONDENSED;
+ else if( f <= css::awt::FontWidth::SEMICONDENSED )
+ return WIDTH_SEMI_CONDENSED;
+ else if( f <= css::awt::FontWidth::NORMAL )
+ return WIDTH_NORMAL;
+ else if( f <= css::awt::FontWidth::SEMIEXPANDED )
+ return WIDTH_SEMI_EXPANDED;
+ else if( f <= css::awt::FontWidth::EXPANDED )
+ return WIDTH_EXPANDED;
+ else if( f <= css::awt::FontWidth::EXTRAEXPANDED )
+ return WIDTH_EXTRA_EXPANDED;
+ else if( f <= css::awt::FontWidth::ULTRAEXPANDED )
+ return WIDTH_ULTRA_EXPANDED;
+
+ OSL_FAIL( "Unknown FontWidth" );
+ return WIDTH_DONTKNOW;
+}
+
+float vcl::unohelper::ConvertFontWeight( FontWeight eWeight )
+{
+ if( eWeight == WEIGHT_DONTKNOW )
+ return css::awt::FontWeight::DONTKNOW;
+ else if( eWeight == WEIGHT_THIN )
+ return css::awt::FontWeight::THIN;
+ else if( eWeight == WEIGHT_ULTRALIGHT )
+ return css::awt::FontWeight::ULTRALIGHT;
+ else if( eWeight == WEIGHT_LIGHT )
+ return css::awt::FontWeight::LIGHT;
+ else if( eWeight == WEIGHT_SEMILIGHT )
+ return css::awt::FontWeight::SEMILIGHT;
+ else if( ( eWeight == WEIGHT_NORMAL ) || ( eWeight == WEIGHT_MEDIUM ) )
+ return css::awt::FontWeight::NORMAL;
+ else if( eWeight == WEIGHT_SEMIBOLD )
+ return css::awt::FontWeight::SEMIBOLD;
+ else if( eWeight == WEIGHT_BOLD )
+ return css::awt::FontWeight::BOLD;
+ else if( eWeight == WEIGHT_ULTRABOLD )
+ return css::awt::FontWeight::ULTRABOLD;
+ else if( eWeight == WEIGHT_BLACK )
+ return css::awt::FontWeight::BLACK;
+
+ OSL_FAIL( "Unknown FontWeight" );
+ return css::awt::FontWeight::DONTKNOW;
+}
+
+FontWeight vcl::unohelper::ConvertFontWeight( float f )
+{
+ if( f <= css::awt::FontWeight::DONTKNOW )
+ return WEIGHT_DONTKNOW;
+ else if( f <= css::awt::FontWeight::THIN )
+ return WEIGHT_THIN;
+ else if( f <= css::awt::FontWeight::ULTRALIGHT )
+ return WEIGHT_ULTRALIGHT;
+ else if( f <= css::awt::FontWeight::LIGHT )
+ return WEIGHT_LIGHT;
+ else if( f <= css::awt::FontWeight::SEMILIGHT )
+ return WEIGHT_SEMILIGHT;
+ else if( f <= css::awt::FontWeight::NORMAL )
+ return WEIGHT_NORMAL;
+ else if( f <= css::awt::FontWeight::SEMIBOLD )
+ return WEIGHT_SEMIBOLD;
+ else if( f <= css::awt::FontWeight::BOLD )
+ return WEIGHT_BOLD;
+ else if( f <= css::awt::FontWeight::ULTRABOLD )
+ return WEIGHT_ULTRABOLD;
+ else if( f <= css::awt::FontWeight::BLACK )
+ return WEIGHT_BLACK;
+
+ OSL_FAIL( "Unknown FontWeight" );
+ return WEIGHT_DONTKNOW;
+}
+
+css::awt::FontSlant vcl::unohelper::ConvertFontSlant(FontItalic eItalic)
+{
+ css::awt::FontSlant eRet(css::awt::FontSlant_DONTKNOW);
+ switch (eItalic)
+ {
+ case ITALIC_NONE:
+ eRet = css::awt::FontSlant_NONE;
+ break;
+ case ITALIC_OBLIQUE:
+ eRet = css::awt::FontSlant_OBLIQUE;
+ break;
+ case ITALIC_NORMAL:
+ eRet = css::awt::FontSlant_ITALIC;
+ break;
+ case ITALIC_DONTKNOW:
+ eRet = css::awt::FontSlant_DONTKNOW;
+ break;
+ case FontItalic_FORCE_EQUAL_SIZE:
+ eRet = css::awt::FontSlant::FontSlant_MAKE_FIXED_SIZE;
+ break;
+ }
+ return eRet;
+}
+
+FontItalic vcl::unohelper::ConvertFontSlant(css::awt::FontSlant eSlant)
+{
+ FontItalic eRet = ITALIC_DONTKNOW;
+ switch (eSlant)
+ {
+ case css::awt::FontSlant_NONE:
+ eRet = ITALIC_NONE;
+ break;
+ case css::awt::FontSlant_OBLIQUE:
+ eRet = ITALIC_OBLIQUE;
+ break;
+ case css::awt::FontSlant_ITALIC:
+ eRet = ITALIC_NORMAL;
+ break;
+ case css::awt::FontSlant_DONTKNOW:
+ eRet = ITALIC_DONTKNOW;
+ break;
+ case css::awt::FontSlant_REVERSE_OBLIQUE:
+ //there is no vcl reverse oblique
+ eRet = ITALIC_OBLIQUE;
+ break;
+ case css::awt::FontSlant_REVERSE_ITALIC:
+ //there is no vcl reverse normal
+ eRet = ITALIC_NORMAL;
+ break;
+ case css::awt::FontSlant::FontSlant_MAKE_FIXED_SIZE:
+ eRet = FontItalic_FORCE_EQUAL_SIZE;
+ break;
+ }
+ return eRet;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/unohelp2.cxx b/vcl/source/app/unohelp2.cxx
new file mode 100644
index 000000000..9005467d9
--- /dev/null
+++ b/vcl/source/app/unohelp2.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 <sal/log.hxx>
+#include <vcl/unohelp2.hxx>
+#include <sot/exchange.hxx>
+#include <sot/formats.hxx>
+#include <vcl/svapp.hxx>
+#include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboard.hpp>
+#include <com/sun/star/datatransfer/clipboard/XFlushableClipboard.hpp>
+#include <cppuhelper/queryinterface.hxx>
+
+using namespace ::com::sun::star;
+
+namespace vcl::unohelper {
+
+ TextDataObject::TextDataObject( const OUString& rText ) : maText( rText )
+ {
+ }
+
+ TextDataObject::~TextDataObject()
+ {
+ }
+
+ void TextDataObject::CopyStringTo( const OUString& rContent,
+ const uno::Reference< datatransfer::clipboard::XClipboard >& rxClipboard )
+ {
+ SAL_WARN_IF( !rxClipboard.is(), "vcl", "TextDataObject::CopyStringTo: invalid clipboard!" );
+ if ( !rxClipboard.is() )
+ return;
+
+ 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();
+ }
+ catch( const uno::Exception& )
+ {
+ }
+ }
+
+ // css::uno::XInterface
+ uno::Any TextDataObject::queryInterface( const uno::Type & rType )
+ {
+ uno::Any aRet = ::cppu::queryInterface( rType, static_cast< datatransfer::XTransferable* >(this) );
+ return (aRet.hasValue() ? aRet : OWeakObject::queryInterface( rType ));
+ }
+
+ // css::datatransfer::XTransferable
+ uno::Any TextDataObject::getTransferData( const datatransfer::DataFlavor& rFlavor )
+ {
+ SotClipboardFormatId nT = SotExchange::GetFormat( rFlavor );
+ if ( nT != SotClipboardFormatId::STRING )
+ {
+ throw datatransfer::UnsupportedFlavorException();
+ }
+ return uno::Any(maText);
+ }
+
+ uno::Sequence< datatransfer::DataFlavor > TextDataObject::getTransferDataFlavors( )
+ {
+ uno::Sequence< datatransfer::DataFlavor > aDataFlavors(1);
+ SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aDataFlavors.getArray()[0] );
+ return aDataFlavors;
+ }
+
+ sal_Bool TextDataObject::isDataFlavorSupported( const datatransfer::DataFlavor& rFlavor )
+ {
+ SotClipboardFormatId nT = SotExchange::GetFormat( rFlavor );
+ return ( nT == SotClipboardFormatId::STRING );
+ }
+
+} // namespace vcl::unohelper
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/vclevent.cxx b/vcl/source/app/vclevent.cxx
new file mode 100644
index 000000000..3dfad4a4c
--- /dev/null
+++ b/vcl/source/app/vclevent.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 <vcl/vclevent.hxx>
+#include <vcl/window.hxx>
+#include <vcl/menu.hxx>
+
+#include <vcleventlisteners.hxx>
+
+using ::com::sun::star::uno::Reference;
+using ::com::sun::star::accessibility::XAccessible;
+
+
+VclAccessibleEvent::VclAccessibleEvent( VclEventId n, const Reference<XAccessible>& rxAccessible ) :
+ VclSimpleEvent(n),
+ mxAccessible(rxAccessible)
+{
+}
+
+VclAccessibleEvent::~VclAccessibleEvent()
+{
+}
+
+
+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() );
+ if (VclWindowEvent* pWindowEvent = dynamic_cast<VclWindowEvent*>(&rEvent))
+ {
+ VclPtr<vcl::Window> xWin(pWindowEvent->GetWindow());
+ while ( aIter != aEnd && (!xWin || !xWin->IsDisposed()) )
+ {
+ Link<VclSimpleEvent&,void> &rLink = *aIter;
+ // check this hasn't been removed in some re-enterancy scenario fdo#47368
+ if( 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( 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 );
+}
+
+void VclEventListeners::removeListener( const Link<VclSimpleEvent&,void>& rListener )
+{
+ m_aListeners.erase( std::remove(m_aListeners.begin(), m_aListeners.end(), rListener ), m_aListeners.end() );
+}
+
+VclWindowEvent::VclWindowEvent( vcl::Window* pWin, VclEventId n, void* pDat ) : VclSimpleEvent(n)
+{
+ pWindow = pWin; pData = pDat;
+}
+
+VclWindowEvent::~VclWindowEvent() {}
+
+VclMenuEvent::VclMenuEvent( Menu* pM, VclEventId n, sal_uInt16 nPos )
+ : VclSimpleEvent(n), pMenu(pM), mnPos(nPos)
+{}
+
+VclMenuEvent::~VclMenuEvent()
+{}
+
+Menu* VclMenuEvent::GetMenu() const
+{
+ return pMenu;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/watchdog.cxx b/vcl/source/app/watchdog.cxx
new file mode 100644
index 000000000..c45e51c04
--- /dev/null
+++ b/vcl/source/app/watchdog.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/.
+ */
+
+#include <watchdog.hxx>
+
+#include <config_features.h>
+
+#include <osl/conditn.hxx>
+#include <rtl/ref.hxx>
+#include <rtl/string.hxx>
+#include <sal/log.hxx>
+#include <comphelper/debuggerinfo.hxx>
+#include <opengl/zone.hxx>
+#include <skia/zone.hxx>
+
+#include <stdlib.h>
+
+#if defined HAVE_VALGRIND_HEADERS
+#include <valgrind/memcheck.h>
+#endif
+
+namespace
+{
+static volatile bool gbWatchdogFiring = false;
+static osl::Condition* gpWatchdogExit = nullptr;
+static 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",
+ OStringLiteral("Watchdog triggered: hard disable ") + Zone::name());
+ Zone::hardDisable();
+ gbWatchdogFiring = false;
+ }
+ bFired = true;
+
+ // we can hang using VCL in the abort handling -> be impatient
+ if (bAbortFired)
+ {
+ SAL_WARN("vcl.watchdog",
+ OStringLiteral("Watchdog gave up: hard exiting ") + Zone::name());
+ _Exit(1);
+ }
+ }
+
+ // Not making even more progress
+ if (nUnchanged >= aTimingValues.mnAbortAfter)
+ {
+ if (!bAbortFired)
+ {
+ SAL_WARN("vcl.watchdog",
+ OStringLiteral("Watchdog gave up: aborting ") + Zone::name());
+ gbWatchdogFiring = true;
+ std::abort();
+ }
+ // coverity[dead_error_line] - we might have caught SIGABRT and failed to exit yet
+ bAbortFired = true;
+ }
+ }
+ else
+ {
+ nUnchanged = 0;
+ }
+ }
+};
+
+} // namespace
+
+WatchdogThread::WatchdogThread()
+ : salhelper::Thread("Crash Watchdog")
+{
+}
+
+void WatchdogThread::execute()
+{
+ TimeValue aQuarterSecond(0, 1000 * 1000 * 1000 * 0.25);
+ do
+ {
+#if HAVE_FEATURE_OPENGL
+ WatchdogHelper<OpenGLZone>::setLastEnters();
+#endif
+#if HAVE_FEATURE_SKIA
+ WatchdogHelper<SkiaZone>::setLastEnters();
+#endif
+
+ gpWatchdogExit->wait(&aQuarterSecond);
+
+#if defined HAVE_VALGRIND_HEADERS
+ if (RUNNING_ON_VALGRIND)
+ continue;
+#endif
+#if defined DBG_UTIL
+ if (comphelper::isDebuggerAttached())
+ continue;
+#endif
+
+#if HAVE_FEATURE_OPENGL
+ WatchdogHelper<OpenGLZone>::check();
+#endif
+#if HAVE_FEATURE_SKIA
+ WatchdogHelper<SkiaZone>::check();
+#endif
+
+ } while (!gpWatchdogExit->check());
+}
+
+void WatchdogThread::start()
+{
+ if (gxWatchdog != nullptr)
+ return; // already running
+ if (getenv("SAL_DISABLE_WATCHDOG"))
+ return;
+ gpWatchdogExit = new osl::Condition();
+ gxWatchdog.set(new WatchdogThread());
+ gxWatchdog->launch();
+}
+
+void WatchdogThread::stop()
+{
+ if (gbWatchdogFiring)
+ return; // in watchdog thread
+
+ if (gpWatchdogExit)
+ gpWatchdogExit->set();
+
+ if (gxWatchdog.is())
+ {
+ gxWatchdog->join();
+ gxWatchdog.clear();
+ }
+
+ delete gpWatchdogExit;
+ gpWatchdogExit = nullptr;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/weldutils.cxx b/vcl/source/app/weldutils.cxx
new file mode 100644
index 000000000..69562cf0d
--- /dev/null
+++ b/vcl/source/app/weldutils.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 <vcl/builderpage.hxx>
+#include <vcl/commandinfoprovider.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/weldutils.hxx>
+
+BuilderPage::BuilderPage(weld::Widget* pParent, weld::DialogController* pController,
+ const OUString& rUIXMLDescription, const OString& rID)
+ : m_pDialogController(pController)
+ , m_xBuilder(Application::CreateBuilder(pParent, rUIXMLDescription))
+ , m_xContainer(m_xBuilder->weld_container(rID))
+{
+}
+
+void BuilderPage::Activate() {}
+
+void BuilderPage::Deactivate() {}
+
+BuilderPage::~BuilderPage() COVERITY_NOEXCEPT_FALSE {}
+
+namespace weld
+{
+bool DialogController::runAsync(const std::shared_ptr<DialogController>& rController,
+ const std::function<void(sal_Int32)>& func)
+{
+ return rController->getDialog()->runAsync(rController, func);
+}
+
+DialogController::~DialogController() COVERITY_NOEXCEPT_FALSE {}
+
+Dialog* GenericDialogController::getDialog() { return m_xDialog.get(); }
+
+GenericDialogController::GenericDialogController(weld::Widget* pParent, const OUString& rUIFile,
+ const OString& rDialogId, bool bMobile)
+ : m_xBuilder(Application::CreateBuilder(pParent, rUIFile, bMobile))
+ , m_xDialog(m_xBuilder->weld_dialog(rDialogId))
+{
+}
+
+GenericDialogController::~GenericDialogController() COVERITY_NOEXCEPT_FALSE {}
+
+Dialog* MessageDialogController::getDialog() { return m_xDialog.get(); }
+
+MessageDialogController::MessageDialogController(weld::Widget* pParent, const OUString& rUIFile,
+ const OString& rDialogId,
+ const OString& rRelocateId)
+ : m_xBuilder(Application::CreateBuilder(pParent, rUIFile))
+ , m_xDialog(m_xBuilder->weld_message_dialog(rDialogId))
+ , m_xContentArea(m_xDialog->weld_message_area())
+{
+ if (!rRelocateId.isEmpty())
+ {
+ m_xRelocate = m_xBuilder->weld_container(rRelocateId);
+ m_xOrigParent = m_xRelocate->weld_parent();
+ //fdo#75121, a bit tricky because the widgets we want to align with
+ //don't actually exist in the ui description, they're implied
+ m_xOrigParent->move(m_xRelocate.get(), m_xContentArea.get());
+ }
+}
+
+MessageDialogController::~MessageDialogController()
+{
+ if (m_xRelocate)
+ {
+ m_xContentArea->move(m_xRelocate.get(), m_xOrigParent.get());
+ }
+}
+
+AssistantController::AssistantController(weld::Widget* pParent, const OUString& rUIFile,
+ const OString& rDialogId)
+ : m_xBuilder(Application::CreateBuilder(pParent, rUIFile))
+ , m_xAssistant(m_xBuilder->weld_assistant(rDialogId))
+{
+}
+
+Dialog* AssistantController::getDialog() { return m_xAssistant.get(); }
+
+AssistantController::~AssistantController() {}
+
+void TriStateEnabled::ButtonToggled(weld::ToggleButton& 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, 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);
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/winscheduler.cxx b/vcl/source/app/winscheduler.cxx
new file mode 100644
index 000000000..0398faaa4
--- /dev/null
+++ b/vcl/source/app/winscheduler.cxx
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifdef _WIN32
+
+#include <sal/config.h>
+
+#include <sal/log.hxx>
+#include <vcl/winscheduler.hxx>
+
+#include <svsys.h>
+#include <win/saldata.hxx>
+#include <win/salinst.h>
+
+namespace
+{
+void PostMessageToComWnd(UINT nMsg)
+{
+ bool const ret = PostMessageW(GetSalData()->mpInstance->mhComWnd, nMsg, 0, 0);
+ SAL_WARN_IF(!ret, "vcl.schedule", "ERROR: PostMessage() failed!");
+}
+}
+
+void WinScheduler::SetForceRealTimer() { PostMessageToComWnd(SAL_MSG_FORCE_REAL_TIMER); }
+
+void WinScheduler::PostDummyMessage() { PostMessageToComWnd(SAL_MSG_DUMMY); }
+
+#endif // _WIN32
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapAlphaClampFilter.cxx b/vcl/source/bitmap/BitmapAlphaClampFilter.cxx
new file mode 100644
index 000000000..3db6b43e2
--- /dev/null
+++ b/vcl/source/bitmap/BitmapAlphaClampFilter.cxx
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapAlphaClampFilter.hxx>
+
+#include <bitmapwriteaccess.hxx>
+
+BitmapEx BitmapAlphaClampFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ if (!rBitmapEx.IsTransparent())
+ return rBitmapEx;
+
+ AlphaMask aBitmapAlpha(rBitmapEx.GetAlpha());
+ {
+ AlphaScopedWriteAccess pWriteAlpha(aBitmapAlpha);
+ const Size aSize(rBitmapEx.GetSizePixel());
+
+ for (long nY = 0; nY < aSize.Height(); ++nY)
+ {
+ Scanline pScanAlpha = pWriteAlpha->GetScanline(nY);
+
+ for (long nX = 0; nX < aSize.Width(); ++nX)
+ {
+ BitmapColor aBitmapAlphaValue(pWriteAlpha->GetPixelFromData(pScanAlpha, nX));
+ if (aBitmapAlphaValue.GetIndex() > mcThreshold)
+ {
+ aBitmapAlphaValue.SetIndex(255);
+ pWriteAlpha->SetPixelOnData(pScanAlpha, nX, aBitmapAlphaValue);
+ }
+ }
+ }
+ }
+
+ return BitmapEx(rBitmapEx.GetBitmap(), aBitmapAlpha);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapBasicMorphologyFilter.cxx b/vcl/source/bitmap/BitmapBasicMorphologyFilter.cxx
new file mode 100644
index 000000000..fe4f63f90
--- /dev/null
+++ b/vcl/source/bitmap/BitmapBasicMorphologyFilter.cxx
@@ -0,0 +1,360 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/bitmapaccess.hxx>
+#include <vcl/BitmapBasicMorphologyFilter.hxx>
+
+#include <bitmapwriteaccess.hxx>
+
+#include <algorithm>
+
+namespace
+{
+struct FilterSharedData
+{
+ BitmapReadAccess* mpReadAccess;
+ BitmapWriteAccess* mpWriteAccess;
+ long mnRadius;
+ sal_uInt8 mnOutsideVal;
+ Color maOutsideColor;
+
+ FilterSharedData(Bitmap::ScopedReadAccess& rReadAccess, BitmapScopedWriteAccess& rWriteAccess,
+ long nRadius, sal_uInt8 nOutsideVal)
+ : mpReadAccess(rReadAccess.get())
+ , mpWriteAccess(rWriteAccess.get())
+ , mnRadius(nRadius)
+ , mnOutsideVal(nOutsideVal)
+ , maOutsideColor(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(BitmapReadAccess* pReadAccess, long x, long 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(BitmapWriteAccess* pWriteAccess, long x, long 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{ 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(BitmapReadAccess* pReadAccess, long x, long y, sal_uInt8* /*pHint*/ = nullptr)
+ {
+ const auto& rSource = pReadAccess->GetColor(y, x);
+ aResult = Color(MorphologyOp::apply(rSource.GetTransparency(), aResult.GetTransparency()),
+ MorphologyOp::apply(rSource.GetRed(), aResult.GetRed()),
+ MorphologyOp::apply(rSource.GetGreen(), aResult.GetGreen()),
+ MorphologyOp::apply(rSource.GetBlue(), aResult.GetBlue()));
+ }
+
+ void copy(BitmapWriteAccess* pWriteAccess, long x, long y, sal_uInt8* /*pHint*/ = nullptr)
+ {
+ pWriteAccess->SetPixel(y, x, aResult);
+ }
+};
+
+bool GetMinMax(long nCenter, long nRadius, long nMaxLimit, long& nMin, long& 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 long nStart, const long nEnd)
+ {
+ BitmapReadAccess* pReadAccess = rShared.mpReadAccess;
+ BitmapWriteAccess* pWriteAccess = rShared.mpWriteAccess;
+
+ const long nLastIndex = pReadAccess->Width() - 1;
+
+ for (long y = nStart; y <= nEnd; y++)
+ {
+ // Optimization
+ sal_uInt8* const pSourceHint = pReadAccess->GetScanline(y);
+ sal_uInt8* const pDestHint = pWriteAccess->GetScanline(y);
+ for (long 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
+ long iMin, iMax;
+ const bool bLookOutside = GetMinMax(x, rShared.mnRadius, nLastIndex, iMin, iMax);
+ Value<MorphologyOp, nComponentWidth> aResult(rShared, bLookOutside);
+ for (long i = iMin; i <= iMax; ++i)
+ aResult.apply(pReadAccess, i, y, pSourceHint);
+
+ aResult.copy(pWriteAccess, x, y, pDestHint);
+ }
+ }
+ }
+
+ static void Vertical(FilterSharedData const& rShared, const long nStart, const long nEnd)
+ {
+ BitmapReadAccess* pReadAccess = rShared.mpReadAccess;
+ BitmapWriteAccess* pWriteAccess = rShared.mpWriteAccess;
+
+ const long nLastIndex = pReadAccess->Height() - 1;
+
+ for (long x = nStart; x <= nEnd; x++)
+ {
+ for (long 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
+ long iMin, iMax;
+ const bool bLookOutside = GetMinMax(y, rShared.mnRadius, nLastIndex, iMin, iMax);
+ Value<MorphologyOp, nComponentWidth> aResult(rShared, bLookOutside);
+ for (long i = iMin; i <= iMax; ++i)
+ aResult.apply(pReadAccess, x, i);
+
+ aResult.copy(pWriteAccess, x, y);
+ }
+ }
+ }
+};
+
+typedef void (*passFn)(FilterSharedData const& rShared, long nStart, long nEnd);
+
+class FilterTask : public comphelper::ThreadTask
+{
+ passFn mpFunction;
+ FilterSharedData& mrShared;
+ long mnStart;
+ long mnEnd;
+
+public:
+ explicit FilterTask(const std::shared_ptr<comphelper::ThreadTaskTag>& pTag, passFn pFunction,
+ FilterSharedData& rShared, long nStart, long nEnd)
+ : comphelper::ThreadTask(pTag)
+ , mpFunction(pFunction)
+ , mrShared(rShared)
+ , mnStart(nStart)
+ , mnEnd(nEnd)
+ {
+ }
+
+ virtual void doWork() override { mpFunction(mrShared, mnStart, mnEnd); }
+};
+
+constexpr long nThreadStrip = 16;
+
+template <typename MorphologyOp, int nComponentWidth>
+void runFilter(Bitmap& rBitmap, const long nRadius, const bool bParallel, bool bUseValueOutside,
+ sal_uInt8 nValueOutside)
+{
+ using myPass = pass<MorphologyOp, nComponentWidth>;
+ const sal_uInt8 nOutsideVal = bUseValueOutside ? nValueOutside : MorphologyOp::initVal;
+ if (bParallel)
+ {
+ try
+ {
+ comphelper::ThreadPool& rShared = comphelper::ThreadPool::getSharedOptimalPool();
+ auto pTag = comphelper::ThreadPool::createThreadTaskTag();
+
+ {
+ Bitmap::ScopedReadAccess pReadAccess(rBitmap);
+ BitmapScopedWriteAccess pWriteAccess(rBitmap);
+ FilterSharedData aSharedData(pReadAccess, pWriteAccess, nRadius, nOutsideVal);
+
+ const long nLastIndex = pReadAccess->Height() - 1;
+ long nStripStart = 0;
+ for (; nStripStart < nLastIndex - nThreadStrip; nStripStart += nThreadStrip)
+ {
+ long nStripEnd = nStripStart + nThreadStrip - 1;
+ auto pTask(std::make_unique<FilterTask>(pTag, myPass::Horizontal, aSharedData,
+ nStripStart, nStripEnd));
+ rShared.pushTask(std::move(pTask));
+ }
+ // Do the last (or the only) strip in main thread without threading overhead
+ myPass::Horizontal(aSharedData, nStripStart, nLastIndex);
+ rShared.waitUntilDone(pTag);
+ }
+ {
+ Bitmap::ScopedReadAccess pReadAccess(rBitmap);
+ BitmapScopedWriteAccess pWriteAccess(rBitmap);
+ FilterSharedData aSharedData(pReadAccess, pWriteAccess, nRadius, nOutsideVal);
+
+ const long nLastIndex = pReadAccess->Width() - 1;
+ long nStripStart = 0;
+ for (; nStripStart < nLastIndex - nThreadStrip; nStripStart += nThreadStrip)
+ {
+ long nStripEnd = nStripStart + nThreadStrip - 1;
+ auto pTask(std::make_unique<FilterTask>(pTag, myPass::Vertical, aSharedData,
+ nStripStart, nStripEnd));
+ rShared.pushTask(std::move(pTask));
+ }
+ // Do the last (or the only) strip in main thread without threading overhead
+ myPass::Vertical(aSharedData, nStripStart, nLastIndex);
+ rShared.waitUntilDone(pTag);
+ }
+ }
+ catch (...)
+ {
+ SAL_WARN("vcl.gdi", "threaded bitmap blurring failed");
+ }
+ }
+ else
+ {
+ {
+ Bitmap::ScopedReadAccess pReadAccess(rBitmap);
+ BitmapScopedWriteAccess pWriteAccess(rBitmap);
+ FilterSharedData aSharedData(pReadAccess, pWriteAccess, nRadius, nOutsideVal);
+ long nFirstIndex = 0;
+ long nLastIndex = pReadAccess->Height() - 1;
+ myPass::Horizontal(aSharedData, nFirstIndex, nLastIndex);
+ }
+ {
+ Bitmap::ScopedReadAccess pReadAccess(rBitmap);
+ BitmapScopedWriteAccess pWriteAccess(rBitmap);
+ FilterSharedData aSharedData(pReadAccess, pWriteAccess, nRadius, nOutsideVal);
+ long nFirstIndex = 0;
+ long 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.GetMask());
+}
+
+Bitmap BitmapBasicMorphologyFilter::filter(Bitmap const& rBitmap) const
+{
+ Bitmap bitmapCopy(rBitmap);
+ ScanlineFormat nScanlineFormat;
+ {
+ Bitmap::ScopedReadAccess pReadAccess(bitmapCopy);
+ nScanlineFormat = pReadAccess->GetScanlineFormat();
+ }
+
+ switch (nScanlineFormat)
+ {
+ case ScanlineFormat::N24BitTcRgb:
+ case ScanlineFormat::N24BitTcBgr:
+ runFilter<24>(bitmapCopy, m_eOp, m_nRadius, m_bUseValueOutside, m_nValueOutside);
+ break;
+ case ScanlineFormat::N32BitTcMask:
+ case ScanlineFormat::N32BitTcBgra:
+ runFilter<32>(bitmapCopy, m_eOp, m_nRadius, m_bUseValueOutside, m_nValueOutside);
+ break;
+ case ScanlineFormat::N8BitPal:
+ runFilter<8>(bitmapCopy, m_eOp, m_nRadius, m_bUseValueOutside, m_nValueOutside);
+ break;
+ // TODO: handle 1-bit images
+ default:
+ // Use access' GetColor/SetPixel fallback
+ runFilter<0>(bitmapCopy, m_eOp, m_nRadius, m_bUseValueOutside, m_nValueOutside);
+ break;
+ }
+
+ return bitmapCopy;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapColorQuantizationFilter.cxx b/vcl/source/bitmap/BitmapColorQuantizationFilter.cxx
new file mode 100644
index 000000000..390319d8a
--- /dev/null
+++ b/vcl/source/bitmap/BitmapColorQuantizationFilter.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/.
+ *
+ */
+
+#include <sal/config.h>
+
+#include <algorithm>
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapColorQuantizationFilter.hxx>
+#include <vcl/bitmapaccess.hxx>
+
+#include <bitmapwriteaccess.hxx>
+
+#include <cstdlib>
+
+BitmapEx BitmapColorQuantizationFilter::execute(BitmapEx const& aBitmapEx) const
+{
+ Bitmap aBitmap = aBitmapEx.GetBitmap();
+
+ bool bRet = false;
+
+ if (aBitmap.GetColorCount() <= sal_Int64(mnNewColorCount))
+ {
+ bRet = true;
+ }
+ else
+ {
+ Bitmap::ScopedReadAccess pRAcc(aBitmap);
+ sal_uInt16 nBitCount;
+
+ auto const cappedNewColorCount = std::min(mnNewColorCount, sal_uInt16(256));
+
+ if (cappedNewColorCount < 17)
+ nBitCount = 4;
+ else
+ nBitCount = 8;
+
+ if (pRAcc)
+ {
+ const sal_uInt32 nValidBits = 4;
+ const sal_uInt32 nRightShiftBits = 8 - nValidBits;
+ const sal_uInt32 nLeftShiftBits1 = nValidBits;
+ const sal_uInt32 nLeftShiftBits2 = nValidBits << 1;
+ const sal_uInt32 nColorsPerComponent = 1 << nValidBits;
+ const sal_uInt32 nColorOffset = 256 / nColorsPerComponent;
+ const sal_uInt32 nTotalColors
+ = nColorsPerComponent * nColorsPerComponent * nColorsPerComponent;
+ const long nWidth = pRAcc->Width();
+ const long nHeight = pRAcc->Height();
+ std::unique_ptr<PopularColorCount[]> pCountTable(new PopularColorCount[nTotalColors]);
+
+ memset(pCountTable.get(), 0, nTotalColors * sizeof(PopularColorCount));
+
+ for (long nR = 0, nIndex = 0; nR < 256; nR += nColorOffset)
+ {
+ for (long nG = 0; nG < 256; nG += nColorOffset)
+ {
+ for (long nB = 0; nB < 256; nB += nColorOffset)
+ {
+ pCountTable[nIndex].mnIndex = nIndex;
+ nIndex++;
+ }
+ }
+ }
+
+ if (pRAcc->HasPalette())
+ {
+ for (long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanlineRead = pRAcc->GetScanline(nY);
+ for (long 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 (long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanlineRead = pRAcc->GetScanline(nY);
+ for (long 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(), nBitCount, &aNewPal);
+ BitmapScopedWriteAccess pWAcc(aNewBmp);
+
+ if (pWAcc)
+ {
+ BitmapColor aDstCol(sal_uInt8(0));
+ std::unique_ptr<sal_uInt8[]> pIndexMap(new sal_uInt8[nTotalColors]);
+
+ for (long nR = 0, nIndex = 0; nR < 256; nR += nColorOffset)
+ {
+ for (long nG = 0; nG < 256; nG += nColorOffset)
+ {
+ for (long 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 (long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWAcc->GetScanline(nY);
+ Scanline pScanlineRead = pRAcc->GetScanline(nY);
+ for (long 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 (long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWAcc->GetScanline(nY);
+ Scanline pScanlineRead = pRAcc->GetScanline(nY);
+
+ for (long nX = 0; nX < nWidth; nX++)
+ {
+ const BitmapColor aCol(pRAcc->GetPixelFromData(pScanlineRead, nX));
+ aDstCol.SetIndex(pIndexMap[((static_cast<sal_uInt32>(aCol.GetRed())
+ >> nRightShiftBits)
+ << nLeftShiftBits2)
+ | ((static_cast<sal_uInt32>(aCol.GetGreen())
+ >> nRightShiftBits)
+ << nLeftShiftBits1)
+ | (static_cast<sal_uInt32>(aCol.GetBlue())
+ >> nRightShiftBits)]);
+ pWAcc->SetPixelOnData(pScanline, nX, aDstCol);
+ }
+ }
+ }
+
+ pWAcc.reset();
+ bRet = true;
+ }
+
+ pCountTable.reset();
+ pRAcc.reset();
+
+ if (bRet)
+ {
+ const MapMode aMap(aBitmap.GetPrefMapMode());
+ const Size aSize(aBitmap.GetPrefSize());
+
+ aBitmap = aNewBmp;
+
+ aBitmap.SetPrefMapMode(aMap);
+ aBitmap.SetPrefSize(aSize);
+ }
+ }
+ }
+
+ if (bRet)
+ return BitmapEx(aBitmap);
+
+ return BitmapEx();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapColorizeFilter.cxx b/vcl/source/bitmap/BitmapColorizeFilter.cxx
new file mode 100644
index 000000000..343642b77
--- /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 <bitmapwriteaccess.hxx>
+#include <BitmapColorizeFilter.hxx>
+
+BitmapEx BitmapColorizeFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap = rBitmapEx.GetBitmap();
+ BitmapScopedWriteAccess pWriteAccess(aBitmap);
+
+ if (!pWriteAccess)
+ return rBitmapEx;
+
+ BitmapColor aBitmapColor;
+ const long nW = pWriteAccess->Width();
+ const long nH = pWriteAccess->Height();
+ std::vector<sal_uInt8> aMapR(256);
+ std::vector<sal_uInt8> aMapG(256);
+ std::vector<sal_uInt8> aMapB(256);
+ long nX;
+ long nY;
+
+ const sal_uInt8 cR = maColor.GetRed();
+ const sal_uInt8 cG = maColor.GetGreen();
+ const sal_uInt8 cB = maColor.GetBlue();
+
+ for (nX = 0; nX < 256; ++nX)
+ {
+ aMapR[nX] = MinMax((nX + cR) / 2, 0, 255);
+ aMapG[nX] = MinMax((nX + cG) / 2, 0, 255);
+ aMapB[nX] = MinMax((nX + cB) / 2, 0, 255);
+ }
+
+ if (pWriteAccess->HasPalette())
+ {
+ for (sal_uInt16 i = 0, nCount = pWriteAccess->GetPaletteEntryCount(); i < nCount; i++)
+ {
+ const BitmapColor& rCol = pWriteAccess->GetPaletteColor(i);
+ aBitmapColor.SetRed(aMapR[rCol.GetRed()]);
+ aBitmapColor.SetGreen(aMapG[rCol.GetGreen()]);
+ aBitmapColor.SetBlue(aMapB[rCol.GetBlue()]);
+ pWriteAccess->SetPaletteColor(i, aBitmapColor);
+ }
+ }
+ else if (pWriteAccess->GetScanlineFormat() == ScanlineFormat::N24BitTcBgr)
+ {
+ for (nY = 0; nY < nH; ++nY)
+ {
+ Scanline pScan = pWriteAccess->GetScanline(nY);
+
+ for (nX = 0; nX < nW; ++nX)
+ {
+ *pScan = aMapB[*pScan];
+ pScan++;
+ *pScan = aMapG[*pScan];
+ pScan++;
+ *pScan = aMapR[*pScan];
+ pScan++;
+ }
+ }
+ }
+ else
+ {
+ for (nY = 0; nY < nH; ++nY)
+ {
+ Scanline pScanline = pWriteAccess->GetScanline(nY);
+ for (nX = 0; nX < nW; ++nX)
+ {
+ aBitmapColor = pWriteAccess->GetPixelFromData(pScanline, nX);
+ aBitmapColor.SetRed(aMapR[aBitmapColor.GetRed()]);
+ aBitmapColor.SetGreen(aMapG[aBitmapColor.GetGreen()]);
+ aBitmapColor.SetBlue(aMapB[aBitmapColor.GetBlue()]);
+ pWriteAccess->SetPixelOnData(pScanline, nX, aBitmapColor);
+ }
+ }
+ }
+
+ return rBitmapEx;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapConvolutionMatrixFilter.cxx b/vcl/source/bitmap/BitmapConvolutionMatrixFilter.cxx
new file mode 100644
index 000000000..94e0c6e02
--- /dev/null
+++ b/vcl/source/bitmap/BitmapConvolutionMatrixFilter.cxx
@@ -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/.
+ *
+ */
+
+#include <tools/helpers.hxx>
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/bitmapaccess.hxx>
+#include <vcl/BitmapConvolutionMatrixFilter.hxx>
+#include <vcl/BitmapSharpenFilter.hxx>
+
+#include <bitmapwriteaccess.hxx>
+#include <array>
+
+BitmapEx BitmapConvolutionMatrixFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ const long nDivisor = 8;
+ Bitmap::ScopedReadAccess pReadAcc(aBitmap);
+ bool bRet = false;
+
+ if (pReadAcc)
+ {
+ Bitmap aNewBmp(aBitmap.GetSizePixel(), 24);
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if (pWriteAcc)
+ {
+ const long nWidth = pWriteAcc->Width(), nWidth2 = nWidth + 2;
+ const long nHeight = pWriteAcc->Height(), nHeight2 = nHeight + 2;
+ std::unique_ptr<long[]> pColm(new long[nWidth2]);
+ std::unique_ptr<long[]> pRows(new long[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;
+ long nY, nX, i, nSumR, nSumG, nSumB, nMatrixVal, nTmp;
+ std::array<std::array<long, 256>, 9> aKoeff;
+ long* pTmp;
+
+ // create LUT of products of matrix value and possible color component values
+ for (nY = 0; nY < 9; nY++)
+ {
+ for (nX = nTmp = 0, nMatrixVal = mrMatrix[nY]; nX < 256; nX++, nTmp += nMatrixVal)
+ {
+ aKoeff[nY][nX] = nTmp;
+ }
+ }
+
+ // create column LUT
+ for (i = 0; i < nWidth2; i++)
+ {
+ pColm[i] = (i > 0) ? (i - 1) : 0;
+ }
+
+ pColm[nWidth + 1] = pColm[nWidth];
+
+ // create row LUT
+ for (i = 0; i < nHeight2; i++)
+ {
+ pRows[i] = (i > 0) ? (i - 1) : 0;
+ }
+
+ pRows[nHeight + 1] = pRows[nHeight];
+
+ // read first three rows of bitmap color
+ for (i = 0; i < nWidth2; i++)
+ {
+ pColRow1[i] = pReadAcc->GetColor(pRows[0], pColm[i]);
+ pColRow2[i] = pReadAcc->GetColor(pRows[1], pColm[i]);
+ pColRow3[i] = pReadAcc->GetColor(pRows[2], pColm[i]);
+ }
+
+ // do convolution
+ for (nY = 0; nY < nHeight;)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (nX = 0; nX < nWidth; nX++)
+ {
+ // first row
+ pTmp = aKoeff[0].data();
+ pColor = pRowTmp1 + nX;
+ nSumR = pTmp[pColor->GetRed()];
+ nSumG = pTmp[pColor->GetGreen()];
+ nSumB = pTmp[pColor->GetBlue()];
+
+ pTmp = aKoeff[1].data();
+ nSumR += pTmp[(++pColor)->GetRed()];
+ nSumG += pTmp[pColor->GetGreen()];
+ nSumB += pTmp[pColor->GetBlue()];
+
+ pTmp = aKoeff[2].data();
+ nSumR += pTmp[(++pColor)->GetRed()];
+ nSumG += pTmp[pColor->GetGreen()];
+ nSumB += pTmp[pColor->GetBlue()];
+
+ // second row
+ pTmp = aKoeff[3].data();
+ pColor = pRowTmp2 + nX;
+ nSumR += pTmp[pColor->GetRed()];
+ nSumG += pTmp[pColor->GetGreen()];
+ nSumB += pTmp[pColor->GetBlue()];
+
+ pTmp = aKoeff[4].data();
+ nSumR += pTmp[(++pColor)->GetRed()];
+ nSumG += pTmp[pColor->GetGreen()];
+ nSumB += pTmp[pColor->GetBlue()];
+
+ pTmp = aKoeff[5].data();
+ nSumR += pTmp[(++pColor)->GetRed()];
+ nSumG += pTmp[pColor->GetGreen()];
+ nSumB += pTmp[pColor->GetBlue()];
+
+ // third row
+ pTmp = aKoeff[6].data();
+ pColor = pRowTmp3 + nX;
+ nSumR += pTmp[pColor->GetRed()];
+ nSumG += pTmp[pColor->GetGreen()];
+ nSumB += pTmp[pColor->GetBlue()];
+
+ pTmp = aKoeff[7].data();
+ nSumR += pTmp[(++pColor)->GetRed()];
+ nSumG += pTmp[pColor->GetGreen()];
+ nSumB += pTmp[pColor->GetBlue()];
+
+ pTmp = aKoeff[8].data();
+ nSumR += pTmp[(++pColor)->GetRed()];
+ nSumG += pTmp[pColor->GetGreen()];
+ nSumB += pTmp[pColor->GetBlue()];
+
+ // calculate destination color
+ pWriteAcc->SetPixelOnData(
+ pScanline, nX,
+ BitmapColor(static_cast<sal_uInt8>(MinMax(nSumR / nDivisor, 0, 255)),
+ static_cast<sal_uInt8>(MinMax(nSumG / nDivisor, 0, 255)),
+ static_cast<sal_uInt8>(MinMax(nSumB / nDivisor, 0, 255))));
+ }
+
+ if (++nY < nHeight)
+ {
+ if (pRowTmp1 == pColRow1.get())
+ {
+ pRowTmp1 = pColRow2.get();
+ pRowTmp2 = pColRow3.get();
+ pRowTmp3 = pColRow1.get();
+ }
+ else if (pRowTmp1 == pColRow2.get())
+ {
+ pRowTmp1 = pColRow3.get();
+ pRowTmp2 = pColRow1.get();
+ pRowTmp3 = pColRow2.get();
+ }
+ else
+ {
+ pRowTmp1 = pColRow1.get();
+ pRowTmp2 = pColRow2.get();
+ pRowTmp3 = pColRow3.get();
+ }
+
+ for (i = 0; i < nWidth2; i++)
+ {
+ pRowTmp3[i] = pReadAcc->GetColor(pRows[nY + 2], pColm[i]);
+ }
+ }
+ }
+
+ pWriteAcc.reset();
+
+ bRet = true;
+ }
+
+ pReadAcc.reset();
+
+ if (bRet)
+ {
+ const MapMode aMap(aBitmap.GetPrefMapMode());
+ const Size aPrefSize(aBitmap.GetPrefSize());
+
+ aBitmap = aNewBmp;
+
+ aBitmap.SetPrefMapMode(aMap);
+ aBitmap.SetPrefSize(aPrefSize);
+ }
+ }
+
+ if (bRet)
+ return BitmapEx(aBitmap);
+
+ return BitmapEx();
+}
+
+static const long g_SharpenMatrix[] = { -1, -1, -1, -1, 16, -1, -1, -1, -1 };
+
+BitmapSharpenFilter::BitmapSharpenFilter()
+ : BitmapConvolutionMatrixFilter(g_SharpenMatrix)
+{
+}
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapDisabledImageFilter.cxx b/vcl/source/bitmap/BitmapDisabledImageFilter.cxx
new file mode 100644
index 000000000..5ade2451a
--- /dev/null
+++ b/vcl/source/bitmap/BitmapDisabledImageFilter.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 <vcl/bitmapaccess.hxx>
+
+#include <bitmapwriteaccess.hxx>
+#include <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
+ sal_uInt16 nBitCount = rBitmapEx.GetBitCount();
+ if (nBitCount < 8)
+ nBitCount = 8;
+
+ const BitmapPalette* pPal = nBitCount == 8 ? &Bitmap::GetGreyPalette(256) : nullptr;
+ Bitmap aGrey(aSize, nBitCount, pPal);
+ BitmapScopedWriteAccess pGrey(aGrey);
+
+ BitmapEx aReturnBitmap;
+ Bitmap aReadBitmap(rBitmapEx.GetBitmap());
+ Bitmap::ScopedReadAccess pRead(aReadBitmap);
+ if (pRead && pGrey)
+ {
+ for (long nY = 0; nY < aSize.Height(); ++nY)
+ {
+ Scanline pGreyScan = pGrey->GetScanline(nY);
+ Scanline pReadScan = pRead->GetScanline(nY);
+
+ for (long nX = 0; nX < 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(nLum, nLum, nLum, aColor.GetAlpha());
+ pGrey->SetPixelOnData(pGreyScan, nX, aGreyValue);
+ }
+ }
+ }
+
+ if (rBitmapEx.IsTransparent())
+ {
+ aReturnBitmap = BitmapEx(aGrey, rBitmapEx.GetAlpha());
+ }
+ else
+ aReturnBitmap = BitmapEx(aGrey);
+
+ return aReturnBitmap;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapDuoToneFilter.cxx b/vcl/source/bitmap/BitmapDuoToneFilter.cxx
new file mode 100644
index 000000000..65d4b3f41
--- /dev/null
+++ b/vcl/source/bitmap/BitmapDuoToneFilter.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 <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapDuoToneFilter.hxx>
+#include <vcl/bitmapaccess.hxx>
+
+#include <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 long nWidth = aBitmap.GetSizePixel().Width();
+ const long nHeight = aBitmap.GetSizePixel().Height();
+
+ Bitmap aResultBitmap(aBitmap.GetSizePixel(), 24);
+ Bitmap::ScopedReadAccess pReadAcc(aBitmap);
+ BitmapScopedWriteAccess pWriteAcc(aResultBitmap);
+ const BitmapColor aColorOne(static_cast<sal_uInt8>(mnColorOne >> 16),
+ static_cast<sal_uInt8>(mnColorOne >> 8),
+ static_cast<sal_uInt8>(mnColorOne));
+ const BitmapColor aColorTwo(static_cast<sal_uInt8>(mnColorTwo >> 16),
+ static_cast<sal_uInt8>(mnColorTwo >> 8),
+ static_cast<sal_uInt8>(mnColorTwo));
+
+ for (long x = 0; x < nWidth; x++)
+ {
+ for (long y = 0; y < nHeight; y++)
+ {
+ BitmapColor aColor = pReadAcc->GetColor(y, x);
+ sal_uInt8 nLuminance = aColor.GetLuminance();
+ BitmapColor aResultColor(
+ lcl_getDuotoneColorComponent(nLuminance, aColorOne.GetRed(), aColorTwo.GetRed()),
+ lcl_getDuotoneColorComponent(nLuminance, aColorOne.GetGreen(),
+ aColorTwo.GetGreen()),
+ lcl_getDuotoneColorComponent(nLuminance, aColorOne.GetBlue(), aColorTwo.GetBlue()));
+ pWriteAcc->SetPixel(y, x, aResultColor);
+ }
+ }
+
+ pWriteAcc.reset();
+ pReadAcc.reset();
+ aBitmap.ReassignWithSize(aResultBitmap);
+
+ return BitmapEx(aBitmap);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapEmbossGreyFilter.cxx b/vcl/source/bitmap/BitmapEmbossGreyFilter.cxx
new file mode 100644
index 000000000..4ea239b1a
--- /dev/null
+++ b/vcl/source/bitmap/BitmapEmbossGreyFilter.cxx
@@ -0,0 +1,158 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <sal/config.h>
+
+#include <algorithm>
+
+#include <tools/helpers.hxx>
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/bitmapaccess.hxx>
+#include <vcl/BitmapEmbossGreyFilter.hxx>
+
+#include <bitmapwriteaccess.hxx>
+
+BitmapEx BitmapEmbossGreyFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ bool bRet = aBitmap.ImplMakeGreyscales(256);
+
+ if (bRet)
+ {
+ bRet = false;
+
+ Bitmap::ScopedReadAccess pReadAcc(aBitmap);
+
+ if (pReadAcc)
+ {
+ Bitmap aNewBmp(aBitmap.GetSizePixel(), 8, &pReadAcc->GetPalette());
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if (pWriteAcc)
+ {
+ BitmapColor aGrey(sal_uInt8(0));
+ const long nWidth = pWriteAcc->Width();
+ const long nHeight = pWriteAcc->Height();
+ long nGrey11, nGrey12, nGrey13;
+ long nGrey21, nGrey22, nGrey23;
+ long nGrey31, nGrey32, nGrey33;
+ double fAzim = basegfx::deg2rad(mnAzimuthAngle100 * 0.01);
+ double fElev = basegfx::deg2rad(mnElevationAngle100 * 0.01);
+ std::unique_ptr<long[]> pHMap(new long[nWidth + 2]);
+ std::unique_ptr<long[]> pVMap(new long[nHeight + 2]);
+ long nX, nY, nNx, nNy, nDotL;
+ const long nLx = FRound(cos(fAzim) * cos(fElev) * 255.0);
+ const long nLy = FRound(sin(fAzim) * cos(fElev) * 255.0);
+ const long nLz = FRound(sin(fElev) * 255.0);
+ const auto nZ2 = ((6 * 255) / 4) * ((6 * 255) / 4);
+ const long nNzLz = ((6 * 255) / 4) * nLz;
+ const sal_uInt8 cLz = static_cast<sal_uInt8>(std::clamp(nLz, 0L, 255L));
+
+ // fill mapping tables
+ pHMap[0] = 0;
+
+ for (nX = 1; nX <= nWidth; nX++)
+ {
+ pHMap[nX] = nX - 1;
+ }
+
+ pHMap[nWidth + 1] = nWidth - 1;
+
+ pVMap[0] = 0;
+
+ for (nY = 1; nY <= nHeight; nY++)
+ {
+ pVMap[nY] = nY - 1;
+ }
+
+ pVMap[nHeight + 1] = nHeight - 1;
+
+ for (nY = 0; nY < nHeight; nY++)
+ {
+ nGrey11 = pReadAcc->GetPixel(pVMap[nY], pHMap[0]).GetIndex();
+ nGrey12 = pReadAcc->GetPixel(pVMap[nY], pHMap[1]).GetIndex();
+ nGrey13 = pReadAcc->GetPixel(pVMap[nY], pHMap[2]).GetIndex();
+ nGrey21 = pReadAcc->GetPixel(pVMap[nY + 1], pHMap[0]).GetIndex();
+ nGrey22 = pReadAcc->GetPixel(pVMap[nY + 1], pHMap[1]).GetIndex();
+ nGrey23 = pReadAcc->GetPixel(pVMap[nY + 1], pHMap[2]).GetIndex();
+ nGrey31 = pReadAcc->GetPixel(pVMap[nY + 2], pHMap[0]).GetIndex();
+ nGrey32 = pReadAcc->GetPixel(pVMap[nY + 2], pHMap[1]).GetIndex();
+ nGrey33 = pReadAcc->GetPixel(pVMap[nY + 2], pHMap[2]).GetIndex();
+
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (nX = 0; nX < nWidth; nX++)
+ {
+ nNx = nGrey11 + nGrey21 + nGrey31 - nGrey13 - nGrey23 - nGrey33;
+ nNy = nGrey31 + nGrey32 + nGrey33 - nGrey11 - nGrey12 - nGrey13;
+
+ if (!nNx && !nNy)
+ {
+ aGrey.SetIndex(cLz);
+ }
+ else if ((nDotL = nNx * nLx + nNy * nLy + nNzLz) < 0)
+ {
+ aGrey.SetIndex(0);
+ }
+ else
+ {
+ const double fGrey
+ = nDotL / sqrt(static_cast<double>(nNx * nNx + nNy * nNy + nZ2));
+ aGrey.SetIndex(static_cast<sal_uInt8>(std::clamp(fGrey, 0.0, 255.0)));
+ }
+
+ pWriteAcc->SetPixelOnData(pScanline, nX, aGrey);
+
+ if (nX < (nWidth - 1))
+ {
+ const long nNextX = pHMap[nX + 3];
+
+ nGrey11 = nGrey12;
+ nGrey12 = nGrey13;
+ nGrey13 = pReadAcc->GetPixel(pVMap[nY], nNextX).GetIndex();
+ nGrey21 = nGrey22;
+ nGrey22 = nGrey23;
+ nGrey23 = pReadAcc->GetPixel(pVMap[nY + 1], nNextX).GetIndex();
+ nGrey31 = nGrey32;
+ nGrey32 = nGrey33;
+ nGrey33 = pReadAcc->GetPixel(pVMap[nY + 2], nNextX).GetIndex();
+ }
+ }
+ }
+
+ pHMap.reset();
+ pVMap.reset();
+ pWriteAcc.reset();
+ bRet = true;
+ }
+
+ pReadAcc.reset();
+
+ if (bRet)
+ {
+ const MapMode aMap(aBitmap.GetPrefMapMode());
+ const Size aPrefSize(aBitmap.GetPrefSize());
+
+ aBitmap = aNewBmp;
+
+ aBitmap.SetPrefMapMode(aMap);
+ aBitmap.SetPrefSize(aPrefSize);
+ }
+ }
+ }
+
+ if (bRet)
+ return BitmapEx(aBitmap);
+
+ return BitmapEx();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapFastScaleFilter.cxx b/vcl/source/bitmap/BitmapFastScaleFilter.cxx
new file mode 100644
index 000000000..cff8f3148
--- /dev/null
+++ b/vcl/source/bitmap/BitmapFastScaleFilter.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <vcl/bitmapex.hxx>
+#include <vcl/bitmapaccess.hxx>
+
+#include <bitmapwriteaccess.hxx>
+#include <BitmapFastScaleFilter.hxx>
+#include <sal/log.hxx>
+
+BitmapEx BitmapFastScaleFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ SAL_INFO("vcl.gdi", "BitmapFastScaleFilter::execute()");
+
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ const Size aSizePix(aBitmap.GetSizePixel());
+ const long nNewWidth = FRound(aSizePix.Width() * mfScaleX);
+ const long nNewHeight = FRound(aSizePix.Height() * mfScaleY);
+ bool bRet = false;
+
+ SAL_INFO("vcl.gdi", "New width: " << nNewWidth << "\nNew height: " << nNewHeight);
+
+ if (nNewWidth && nNewHeight)
+ {
+ Bitmap::ScopedReadAccess pReadAcc(aBitmap);
+
+ if (pReadAcc)
+ {
+ Bitmap aNewBmp(Size(nNewWidth, nNewHeight), aBitmap.GetBitCount(),
+ &pReadAcc->GetPalette());
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if (pWriteAcc)
+ {
+ const long nScanlineSize = pWriteAcc->GetScanlineSize();
+ const long nNewWidth1 = nNewWidth - 1;
+ const long nNewHeight1 = nNewHeight - 1;
+
+ if (nNewWidth1 && nNewHeight1)
+ {
+ const double nWidth = pReadAcc->Width();
+ const double nHeight = pReadAcc->Height();
+ std::unique_ptr<long[]> pLutX(new long[nNewWidth]);
+ std::unique_ptr<long[]> pLutY(new long[nNewHeight]);
+
+ for (long nX = 0; nX < nNewWidth; nX++)
+ {
+ pLutX[nX] = long(nX * nWidth / nNewWidth);
+ }
+
+ for (long nY = 0; nY < nNewHeight; nY++)
+ {
+ pLutY[nY] = long(nY * nHeight / nNewHeight);
+ }
+
+ long nActY = 0;
+ while (nActY < nNewHeight)
+ {
+ long nMapY = pLutY[nActY];
+ Scanline pScanline = pWriteAcc->GetScanline(nActY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nMapY);
+
+ for (long 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");
+ }
+ }
+ }
+
+ Bitmap aMask(rBitmapEx.GetMask());
+
+ if (bRet && (rBitmapEx.GetTransparentType() == TransparentType::Bitmap) && !aMask.IsEmpty())
+ bRet = aMask.Scale(maSize, BmpScaleFlag::Fast);
+
+ SAL_WARN_IF(!aMask.IsEmpty() && aBitmap.GetSizePixel() != aMask.GetSizePixel(), "vcl",
+ "BitmapEx::Scale(): size mismatch for bitmap and alpha mask.");
+
+ if (bRet)
+ return BitmapEx(aBitmap, aMask);
+
+ return BitmapEx();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapFilterStackBlur.cxx b/vcl/source/bitmap/BitmapFilterStackBlur.cxx
new file mode 100644
index 000000000..da51daedf
--- /dev/null
+++ b/vcl/source/bitmap/BitmapFilterStackBlur.cxx
@@ -0,0 +1,629 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/bitmapaccess.hxx>
+#include <bitmapwriteaccess.hxx>
+#include <sal/log.hxx>
+
+#include <comphelper/threadpool.hxx>
+
+namespace
+{
+static 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 };
+
+static 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;
+ long mnRadius;
+ long mnComponentWidth;
+ long mnDiv;
+ long mnColorChannels;
+
+ BlurSharedData(BitmapReadAccess* pReadAccess, BitmapWriteAccess* pWriteAccess, long aRadius,
+ long nComponentWidth, long 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<long> maPositionTable;
+ std::vector<long> maWeightTable;
+
+ std::vector<long> mnSumVector;
+ std::vector<long> mnInSumVector;
+ std::vector<long> 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(long nLastIndex)
+ {
+ for (long i = 0; i < maShared.mnDiv; i++)
+ {
+ maPositionTable[i] = std::clamp(i - maShared.mnRadius, 0L, nLastIndex);
+ maWeightTable[i] = maShared.mnRadius + 1 - std::abs(i - maShared.mnRadius);
+ }
+ }
+
+ long getMultiplyValue() { return static_cast<long>(constMultiplyTable[maShared.mnRadius]); }
+
+ long getShiftValue() { return static_cast<long>(constShiftTable[maShared.mnRadius]); }
+};
+
+typedef void (*BlurRangeFn)(BlurSharedData const& rShared, long nStartY, long nEndY);
+
+class BlurTask : public comphelper::ThreadTask
+{
+ BlurRangeFn mpBlurFunction;
+ BlurSharedData& mrShared;
+ long mnStartY;
+ long mnEndY;
+
+public:
+ explicit BlurTask(const std::shared_ptr<comphelper::ThreadTaskTag>& pTag,
+ BlurRangeFn pBlurFunction, BlurSharedData& rShared, long nStartY, long 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(long*& pValue1, long nConstant)
+ {
+ pValue1[0] += nConstant;
+ pValue1[1] += nConstant;
+ pValue1[2] += nConstant;
+ }
+
+ static inline void set(long*& pValue1, long nConstant)
+ {
+ pValue1[0] = nConstant;
+ pValue1[1] = nConstant;
+ pValue1[2] = nConstant;
+ }
+
+ static inline void add(long*& pValue1, sal_uInt8*& pValue2)
+ {
+ pValue1[0] += pValue2[0];
+ pValue1[1] += pValue2[1];
+ pValue1[2] += pValue2[2];
+ }
+
+ static inline void add(long*& pValue1, long*& pValue2)
+ {
+ pValue1[0] += pValue2[0];
+ pValue1[1] += pValue2[1];
+ pValue1[2] += pValue2[2];
+ }
+
+ static inline void sub(long*& pValue1, sal_uInt8*& pValue2)
+ {
+ pValue1[0] -= pValue2[0];
+ pValue1[1] -= pValue2[1];
+ pValue1[2] -= pValue2[2];
+ }
+
+ static inline void sub(long*& pValue1, long*& pValue2)
+ {
+ pValue1[0] -= pValue2[0];
+ pValue1[1] -= pValue2[1];
+ pValue1[2] -= pValue2[2];
+ }
+
+ static inline void assignPtr(sal_uInt8*& pValue1, sal_uInt8*& pValue2)
+ {
+ pValue1[0] = pValue2[0];
+ pValue1[1] = pValue2[1];
+ pValue1[2] = pValue2[2];
+ }
+
+ static inline void assignMulAndShr(sal_uInt8*& result, long*& sum, long multiply, long 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(long*& pValue1, long nConstant) { pValue1[0] += nConstant; }
+
+ static inline void set(long*& pValue1, long nConstant) { pValue1[0] = nConstant; }
+
+ static inline void add(long*& pValue1, sal_uInt8*& pValue2) { pValue1[0] += pValue2[0]; }
+
+ static inline void add(long*& pValue1, long*& pValue2) { pValue1[0] += pValue2[0]; }
+
+ static inline void sub(long*& pValue1, sal_uInt8*& pValue2) { pValue1[0] -= pValue2[0]; }
+
+ static inline void sub(long*& pValue1, long*& pValue2) { pValue1[0] -= pValue2[0]; }
+
+ static inline void assignPtr(sal_uInt8*& pValue1, sal_uInt8*& pValue2)
+ {
+ pValue1[0] = pValue2[0];
+ }
+
+ static inline void assignMulAndShr(sal_uInt8*& result, long*& sum, long multiply, long shift)
+ {
+ result[0] = (multiply * sum[0]) >> shift;
+ }
+};
+
+template <typename SumFunction>
+void stackBlurHorizontal(BlurSharedData const& rShared, long nStart, long nEnd)
+{
+ BitmapReadAccess* pReadAccess = rShared.mpReadAccess;
+ BitmapWriteAccess* pWriteAccess = rShared.mpWriteAccess;
+
+ BlurArrays aArrays(rShared);
+
+ sal_uInt8* pStack = aArrays.maStackBuffer.data();
+ sal_uInt8* pStackPtr;
+
+ long nWidth = pReadAccess->Width();
+ long nLastIndexX = nWidth - 1;
+
+ long nMultiplyValue = aArrays.getMultiplyValue();
+ long nShiftValue = aArrays.getShiftValue();
+
+ long nRadius = rShared.mnRadius;
+ long nComponentWidth = rShared.mnComponentWidth;
+ long nDiv = rShared.mnDiv;
+
+ Scanline pSourcePointer;
+ Scanline pDestinationPointer;
+
+ long nXPosition;
+ long nStackIndex;
+ long nStackIndexStart;
+ long nWeight;
+
+ aArrays.initializeWeightAndPositions(nLastIndexX);
+
+ long* nSum = aArrays.mnSumVector.data();
+ long* nInSum = aArrays.mnInSumVector.data();
+ long* nOutSum = aArrays.mnOutSumVector.data();
+
+ long* pPositionPointer = aArrays.maPositionTable.data();
+ long* pWeightPointer = aArrays.maWeightTable.data();
+
+ for (long y = nStart; y <= nEnd; y++)
+ {
+ SumFunction::set(nSum, 0L);
+ SumFunction::set(nInSum, 0L);
+ SumFunction::set(nOutSum, 0L);
+
+ // 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 (long 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 (long 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, long nStart, long nEnd)
+{
+ BitmapReadAccess* pReadAccess = rShared.mpReadAccess;
+ BitmapWriteAccess* pWriteAccess = rShared.mpWriteAccess;
+
+ BlurArrays aArrays(rShared);
+
+ sal_uInt8* pStack = aArrays.maStackBuffer.data();
+ sal_uInt8* pStackPtr;
+
+ long nHeight = pReadAccess->Height();
+ long nLastIndexY = nHeight - 1;
+
+ long nMultiplyValue = aArrays.getMultiplyValue();
+ long nShiftValue = aArrays.getShiftValue();
+
+ long nRadius = rShared.mnRadius;
+ long nComponentWidth = rShared.mnComponentWidth;
+ long nDiv = rShared.mnDiv;
+
+ Scanline pSourcePointer;
+ Scanline pDestinationPointer;
+
+ long nYPosition;
+ long nStackIndex;
+ long nStackIndexStart;
+ long nWeight;
+
+ aArrays.initializeWeightAndPositions(nLastIndexY);
+
+ long* nSum = aArrays.mnSumVector.data();
+ long* nInSum = aArrays.mnInSumVector.data();
+ long* nOutSum = aArrays.mnOutSumVector.data();
+ long* pPositionPointer = aArrays.maPositionTable.data();
+ long* pWeightPointer = aArrays.maWeightTable.data();
+
+ for (long x = nStart; x <= nEnd; x++)
+ {
+ SumFunction::set(nSum, 0L);
+ SumFunction::set(nInSum, 0L);
+ SumFunction::set(nOutSum, 0L);
+
+ // 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 (long 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 (long 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 long nThreadStrip = 16;
+
+void runStackBlur(Bitmap& rBitmap, const long nRadius, const long nComponentWidth,
+ const long nColorChannels, BlurRangeFn pBlurHorizontalFn,
+ BlurRangeFn pBlurVerticalFn, const bool bParallel)
+{
+ if (bParallel)
+ {
+ try
+ {
+ comphelper::ThreadPool& rShared = comphelper::ThreadPool::getSharedOptimalPool();
+ auto pTag = comphelper::ThreadPool::createThreadTaskTag();
+
+ {
+ Bitmap::ScopedReadAccess pReadAccess(rBitmap);
+ BitmapScopedWriteAccess pWriteAccess(rBitmap);
+ BlurSharedData aSharedData(pReadAccess.get(), pWriteAccess.get(), nRadius,
+ nComponentWidth, nColorChannels);
+
+ const long nLastIndex = pReadAccess->Height() - 1;
+ long nStripStart = 0;
+ for (; nStripStart < nLastIndex - nThreadStrip; nStripStart += nThreadStrip)
+ {
+ long nStripEnd = nStripStart + nThreadStrip - 1;
+ auto pTask(std::make_unique<BlurTask>(pTag, pBlurHorizontalFn, aSharedData,
+ nStripStart, nStripEnd));
+ rShared.pushTask(std::move(pTask));
+ }
+ // Do the last (or the only) strip in main thread without threading overhead
+ pBlurHorizontalFn(aSharedData, nStripStart, nLastIndex);
+ rShared.waitUntilDone(pTag);
+ }
+ {
+ Bitmap::ScopedReadAccess pReadAccess(rBitmap);
+ BitmapScopedWriteAccess pWriteAccess(rBitmap);
+ BlurSharedData aSharedData(pReadAccess.get(), pWriteAccess.get(), nRadius,
+ nComponentWidth, nColorChannels);
+
+ const long nLastIndex = pReadAccess->Width() - 1;
+ long nStripStart = 0;
+ for (; nStripStart < nLastIndex - nThreadStrip; nStripStart += nThreadStrip)
+ {
+ long nStripEnd = nStripStart + nThreadStrip - 1;
+ auto pTask(std::make_unique<BlurTask>(pTag, pBlurVerticalFn, aSharedData,
+ nStripStart, nStripEnd));
+ rShared.pushTask(std::move(pTask));
+ }
+ // Do the last (or the only) strip in main thread without threading overhead
+ pBlurVerticalFn(aSharedData, nStripStart, nLastIndex);
+ rShared.waitUntilDone(pTag);
+ }
+ }
+ catch (...)
+ {
+ SAL_WARN("vcl.gdi", "threaded bitmap blurring failed");
+ }
+ }
+ else
+ {
+ {
+ Bitmap::ScopedReadAccess pReadAccess(rBitmap);
+ BitmapScopedWriteAccess pWriteAccess(rBitmap);
+ BlurSharedData aSharedData(pReadAccess.get(), pWriteAccess.get(), nRadius,
+ nComponentWidth, nColorChannels);
+ long nFirstIndex = 0;
+ long nLastIndex = pReadAccess->Height() - 1;
+ pBlurHorizontalFn(aSharedData, nFirstIndex, nLastIndex);
+ }
+ {
+ Bitmap::ScopedReadAccess pReadAccess(rBitmap);
+ BitmapScopedWriteAccess pWriteAccess(rBitmap);
+ BlurSharedData aSharedData(pReadAccess.get(), pWriteAccess.get(), nRadius,
+ nComponentWidth, nColorChannels);
+ long nFirstIndex = 0;
+ long 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 long 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 long 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.GetMask());
+}
+
+Bitmap BitmapFilterStackBlur::filter(Bitmap const& rBitmap) const
+{
+ Bitmap bitmapCopy(rBitmap);
+ ScanlineFormat nScanlineFormat;
+ {
+ Bitmap::ScopedReadAccess pReadAccess(bitmapCopy);
+ nScanlineFormat = pReadAccess->GetScanlineFormat();
+ }
+
+ if (nScanlineFormat == ScanlineFormat::N24BitTcRgb
+ || nScanlineFormat == ScanlineFormat::N24BitTcBgr
+ || nScanlineFormat == ScanlineFormat::N32BitTcMask
+ || nScanlineFormat == ScanlineFormat::N32BitTcBgra)
+ {
+ int nComponentWidth = (nScanlineFormat == ScanlineFormat::N32BitTcMask
+ || nScanlineFormat == ScanlineFormat::N32BitTcBgra)
+ ? 4
+ : 3;
+
+ stackBlur24(bitmapCopy, mnRadius, nComponentWidth);
+ }
+ else if (nScanlineFormat == ScanlineFormat::N8BitPal)
+ {
+ int nComponentWidth = 1;
+
+ stackBlur8(bitmapCopy, mnRadius, nComponentWidth);
+ }
+
+ return bitmapCopy;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapGaussianSeparableBlurFilter.cxx b/vcl/source/bitmap/BitmapGaussianSeparableBlurFilter.cxx
new file mode 100644
index 000000000..c264dcd13
--- /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/bitmapaccess.hxx>
+#include <vcl/BitmapGaussianSeparableBlurFilter.hxx>
+
+#include <bitmapwriteaccess.hxx>
+
+BitmapEx BitmapGaussianSeparableBlurFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ const long nWidth = aBitmap.GetSizePixel().Width();
+ const long nHeight = aBitmap.GetSizePixel().Height();
+
+ // Prepare Blur Vector
+ int aNumberOfContributions;
+ std::vector<double> aBlurVector(makeBlurKernel(mfRadius, aNumberOfContributions));
+ std::vector<double> aWeights;
+ std::vector<int> aPixels;
+ std::vector<int> aCounts;
+
+ // Do horizontal filtering
+ blurContributions(nWidth, aNumberOfContributions, aBlurVector, aWeights, aPixels, aCounts);
+
+ Bitmap::ScopedReadAccess pReadAcc(aBitmap);
+
+ // switch coordinates as convolution pass transposes result
+ Bitmap aNewBitmap(Size(nHeight, nWidth), 24);
+
+ bool bResult = convolutionPass(aBitmap, aNewBitmap, pReadAcc.get(), aNumberOfContributions,
+ aWeights.data(), aPixels.data(), aCounts.data());
+
+ // Cleanup
+ pReadAcc.reset();
+ aWeights.clear();
+ aPixels.clear();
+ aCounts.clear();
+
+ if (!bResult)
+ {
+ aBlurVector.clear();
+ }
+ else
+ {
+ // Swap current bitmap with new bitmap
+ aBitmap.ReassignWithSize(aNewBitmap);
+
+ // Do vertical filtering
+ blurContributions(nHeight, aNumberOfContributions, aBlurVector, aWeights, aPixels, aCounts);
+
+ pReadAcc = Bitmap::ScopedReadAccess(aBitmap);
+ aNewBitmap = Bitmap(Size(nWidth, nHeight), 24);
+ bResult = convolutionPass(aBitmap, aNewBitmap, pReadAcc.get(), aNumberOfContributions,
+ aWeights.data(), aPixels.data(), aCounts.data());
+
+ // Cleanup
+ pReadAcc.reset();
+ aWeights.clear();
+ aCounts.clear();
+ aPixels.clear();
+ aBlurVector.clear();
+
+ if (bResult)
+ aBitmap.ReassignWithSize(aNewBitmap); // swap current bitmap with new bitmap
+ }
+
+ if (bResult)
+ return BitmapEx(aBitmap);
+
+ return BitmapEx();
+}
+
+bool BitmapGaussianSeparableBlurFilter::convolutionPass(const Bitmap& rBitmap, Bitmap& aNewBitmap,
+ BitmapReadAccess const* pReadAcc,
+ int aNumberOfContributions,
+ const double* pWeights, int const* pPixels,
+ const int* pCount)
+{
+ if (!pReadAcc)
+ return false;
+
+ BitmapScopedWriteAccess pWriteAcc(aNewBitmap);
+ if (!pWriteAcc)
+ return false;
+
+ const int nHeight = rBitmap.GetSizePixel().Height();
+ assert(rBitmap.GetSizePixel().Height() == aNewBitmap.GetSizePixel().Width());
+ const int nWidth = rBitmap.GetSizePixel().Width();
+ assert(rBitmap.GetSizePixel().Width() == aNewBitmap.GetSizePixel().Height());
+
+ BitmapColor aColor;
+ double aValueRed, aValueGreen, aValueBlue;
+ double aSum, aWeight;
+ int aBaseIndex, aIndex;
+
+ for (int nSourceY = 0; nSourceY < nHeight; ++nSourceY)
+ {
+ for (int nSourceX = 0; nSourceX < nWidth; ++nSourceX)
+ {
+ aBaseIndex = nSourceX * aNumberOfContributions;
+ aSum = aValueRed = aValueGreen = aValueBlue = 0.0;
+
+ for (int j = 0; j < pCount[nSourceX]; ++j)
+ {
+ aIndex = aBaseIndex + j;
+ aWeight = pWeights[aIndex];
+ aSum += aWeight;
+
+ aColor = pReadAcc->GetColor(nSourceY, pPixels[aIndex]);
+
+ aValueRed += aWeight * aColor.GetRed();
+ aValueGreen += aWeight * aColor.GetGreen();
+ aValueBlue += aWeight * aColor.GetBlue();
+ }
+
+ BitmapColor aResultColor(static_cast<sal_uInt8>(MinMax(aValueRed / aSum, 0, 255)),
+ static_cast<sal_uInt8>(MinMax(aValueGreen / aSum, 0, 255)),
+ static_cast<sal_uInt8>(MinMax(aValueBlue / aSum, 0, 255)));
+
+ int nDestX = nSourceY;
+ int nDestY = nSourceX;
+
+ pWriteAcc->SetPixel(nDestY, nDestX, aResultColor);
+ }
+ }
+ return true;
+}
+
+std::vector<double> BitmapGaussianSeparableBlurFilter::makeBlurKernel(const double radius,
+ int& rows)
+{
+ int intRadius = static_cast<int>(radius + 1.0);
+ rows = intRadius * 2 + 1;
+ std::vector<double> matrix(rows);
+
+ double sigma = radius / 3;
+ double radius2 = radius * radius;
+ int index = 0;
+ for (int row = -intRadius; row <= intRadius; row++)
+ {
+ double distance = row * row;
+ if (distance > radius2)
+ {
+ matrix[index] = 0.0;
+ }
+ else
+ {
+ matrix[index] = exp(-distance / (2.0 * sigma * sigma)) / sqrt(2.0 * M_PI * sigma);
+ }
+ index++;
+ }
+ return matrix;
+}
+
+void BitmapGaussianSeparableBlurFilter::blurContributions(
+ const int aSize, const int aNumberOfContributions, const std::vector<double>& rBlurVector,
+ std::vector<double>& rWeights, std::vector<int>& rPixels, std::vector<int>& rCounts)
+{
+ rWeights.resize(aSize * aNumberOfContributions);
+ rPixels.resize(aSize * aNumberOfContributions);
+ rCounts.resize(aSize);
+
+ int aLeft, aRight, aCurrentCount, aPixelIndex;
+ double aWeight;
+
+ for (int i = 0; i < aSize; i++)
+ {
+ aLeft = i - aNumberOfContributions / 2;
+ aRight = i + aNumberOfContributions / 2;
+ aCurrentCount = 0;
+ for (int j = aLeft; j <= aRight; j++)
+ {
+ aWeight = rBlurVector[aCurrentCount];
+
+ // Mirror edges
+ if (j < 0)
+ {
+ aPixelIndex = -j;
+ }
+ else if (j >= aSize)
+ {
+ aPixelIndex = (aSize - j) + aSize - 1;
+ }
+ else
+ {
+ aPixelIndex = j;
+ }
+
+ // Edge case for small bitmaps
+ if (aPixelIndex < 0 || aPixelIndex >= aSize)
+ {
+ aWeight = 0.0;
+ }
+
+ rWeights[i * aNumberOfContributions + aCurrentCount] = aWeight;
+ rPixels[i * aNumberOfContributions + aCurrentCount] = aPixelIndex;
+
+ aCurrentCount++;
+ }
+ rCounts[i] = aCurrentCount;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapInterpolateScaleFilter.cxx b/vcl/source/bitmap/BitmapInterpolateScaleFilter.cxx
new file mode 100644
index 000000000..73f521480
--- /dev/null
+++ b/vcl/source/bitmap/BitmapInterpolateScaleFilter.cxx
@@ -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 .
+ */
+
+#include <tools/helpers.hxx>
+#include <osl/diagnose.h>
+
+#include <vcl/bitmapex.hxx>
+#include <vcl/bitmapaccess.hxx>
+
+#include <bitmapwriteaccess.hxx>
+#include <BitmapFastScaleFilter.hxx>
+#include <BitmapInterpolateScaleFilter.hxx>
+
+BitmapEx BitmapInterpolateScaleFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ const Size aSizePix(aBitmap.GetSizePixel());
+ const long nNewWidth = FRound(aSizePix.Width() * mfScaleX);
+ const long nNewHeight = FRound(aSizePix.Height() * mfScaleY);
+ bool bRet = false;
+
+ if ((nNewWidth > 1) && (nNewHeight > 1))
+ {
+ Bitmap::ScopedReadAccess pReadAcc(aBitmap);
+ if (pReadAcc)
+ {
+ long nWidth = pReadAcc->Width();
+ long nHeight = pReadAcc->Height();
+ Bitmap aNewBmp(Size(nNewWidth, nHeight), 24);
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if (pWriteAcc)
+ {
+ const long nNewWidth1 = nNewWidth - 1;
+ const long nWidth1 = pReadAcc->Width() - 1;
+ const double fRevScaleX = static_cast<double>(nWidth1) / nNewWidth1;
+
+ std::unique_ptr<long[]> pLutInt(new long[nNewWidth]);
+ std::unique_ptr<long[]> pLutFrac(new long[nNewWidth]);
+
+ for (long nX = 0, nTemp = nWidth - 2; nX < nNewWidth; nX++)
+ {
+ double fTemp = nX * fRevScaleX;
+ pLutInt[nX] = MinMax(static_cast<long>(fTemp), 0, nTemp);
+ fTemp -= pLutInt[nX];
+ pLutFrac[nX] = static_cast<long>(fTemp * 1024.);
+ }
+
+ for (long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ if (1 == nWidth)
+ {
+ BitmapColor aCol0;
+ if (pReadAcc->HasPalette())
+ {
+ aCol0 = pReadAcc->GetPaletteColor(
+ pReadAcc->GetIndexFromData(pScanlineRead, 0));
+ }
+ else
+ {
+ aCol0 = pReadAcc->GetPixelFromData(pScanlineRead, 0);
+ }
+
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (long nX = 0; nX < nNewWidth; nX++)
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, aCol0);
+ }
+ }
+ else
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (long nX = 0; nX < nNewWidth; nX++)
+ {
+ long 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];
+
+ long lXR0 = aCol0.GetRed();
+ long lXG0 = aCol0.GetGreen();
+ long lXB0 = aCol0.GetBlue();
+ long lXR1 = aCol1.GetRed() - lXR0;
+ long lXG1 = aCol1.GetGreen() - lXG0;
+ long 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), 24);
+ pReadAcc = Bitmap::ScopedReadAccess(aBitmap);
+ pWriteAcc = BitmapScopedWriteAccess(aNewBmp);
+
+ if (pReadAcc && pWriteAcc)
+ {
+ const long nNewHeight1 = nNewHeight - 1;
+ const long nHeight1 = pReadAcc->Height() - 1;
+ const double fRevScaleY = static_cast<double>(nHeight1) / nNewHeight1;
+
+ std::unique_ptr<long[]> pLutInt(new long[nNewHeight]);
+ std::unique_ptr<long[]> pLutFrac(new long[nNewHeight]);
+
+ for (long nY = 0, nTemp = nHeight - 2; nY < nNewHeight; nY++)
+ {
+ double fTemp = nY * fRevScaleY;
+ pLutInt[nY] = MinMax(static_cast<long>(fTemp), 0, nTemp);
+ fTemp -= pLutInt[nY];
+ pLutFrac[nY] = static_cast<long>(fTemp * 1024.);
+ }
+
+ // after 1st step, bitmap *is* 24bit format (see above)
+ OSL_ENSURE(!pReadAcc->HasPalette(), "OOps, somehow ImplScaleInterpolate "
+ "in-between format has palette, should not "
+ "happen (!)");
+
+ for (long nX = 0; nX < nNewWidth; nX++)
+ {
+ if (1 == nHeight)
+ {
+ BitmapColor aCol0 = pReadAcc->GetPixel(0, nX);
+
+ for (long nY = 0; nY < nNewHeight; nY++)
+ {
+ pWriteAcc->SetPixel(nY, nX, aCol0);
+ }
+ }
+ else
+ {
+ for (long nY = 0; nY < nNewHeight; nY++)
+ {
+ long nTemp = pLutInt[nY];
+
+ BitmapColor aCol0 = pReadAcc->GetPixel(nTemp++, nX);
+ BitmapColor aCol1 = pReadAcc->GetPixel(nTemp, nX);
+
+ nTemp = pLutFrac[nY];
+
+ long lXR0 = aCol0.GetRed();
+ long lXG0 = aCol0.GetGreen();
+ long lXB0 = aCol0.GetBlue();
+ long lXR1 = aCol1.GetRed() - lXR0;
+ long lXG1 = aCol1.GetGreen() - lXG0;
+ long lXB1 = aCol1.GetBlue() - lXB0;
+
+ aCol0.SetRed(
+ static_cast<sal_uInt8>((lXR1 * nTemp + (lXR0 << 10)) >> 10));
+ aCol0.SetGreen(
+ static_cast<sal_uInt8>((lXG1 * nTemp + (lXG0 << 10)) >> 10));
+ aCol0.SetBlue(
+ static_cast<sal_uInt8>((lXB1 * nTemp + (lXB0 << 10)) >> 10));
+
+ pWriteAcc->SetPixel(nY, nX, aCol0);
+ }
+ }
+ }
+
+ bRet = true;
+ }
+
+ pReadAcc.reset();
+ pWriteAcc.reset();
+
+ if (bRet)
+ {
+ aOriginal.AdaptBitCount(aNewBmp);
+ aBitmap = aNewBmp;
+ }
+ }
+ }
+ }
+
+ if (!bRet)
+ {
+ // fallback to fast scale filter
+ BitmapEx aBmpEx(aBitmap);
+ bRet = BitmapFilter::Filter(aBmpEx, BitmapFastScaleFilter(mfScaleX, mfScaleY));
+ aBitmap = aBmpEx.GetBitmap();
+ }
+
+ if (bRet)
+ return BitmapEx(aBitmap);
+
+ return BitmapEx();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapLightenFilter.cxx b/vcl/source/bitmap/BitmapLightenFilter.cxx
new file mode 100644
index 000000000..45013b143
--- /dev/null
+++ b/vcl/source/bitmap/BitmapLightenFilter.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 <basegfx/color/bcolortools.hxx>
+
+#include <vcl/bitmapaccess.hxx>
+
+#include <bitmapwriteaccess.hxx>
+#include <BitmapLightenFilter.hxx>
+
+BitmapEx BitmapLightenFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ const Size aSize(rBitmapEx.GetSizePixel());
+
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+ Bitmap aDarkBitmap(aSize, 24);
+
+ Bitmap::ScopedReadAccess pRead(aBitmap);
+ BitmapScopedWriteAccess pWrite(aDarkBitmap);
+
+ if (pRead && pWrite)
+ {
+ for (long nY = 0; nY < aSize.Height(); ++nY)
+ {
+ Scanline pScanline = pWrite->GetScanline(nY);
+ Scanline pScanlineRead = pRead->GetScanline(nY);
+ for (long nX = 0; nX < aSize.Width(); ++nX)
+ {
+ BitmapColor aBmpColor
+ = pRead->HasPalette()
+ ? pRead->GetPaletteColor(pRead->GetIndexFromData(pScanlineRead, nX))
+ : pRead->GetPixelFromData(pScanlineRead, nX);
+ aBmpColor.Invert();
+ basegfx::BColor aBColor(aBmpColor.getBColor());
+ aBColor = basegfx::utils::rgb2hsl(aBColor);
+
+ double fHue = aBColor.getRed();
+ fHue += 180.0;
+
+ while (fHue > 360.0)
+ {
+ fHue -= 360.0;
+ }
+
+ aBColor.setRed(fHue);
+
+ aBColor = basegfx::utils::hsl2rgb(aBColor);
+ aBmpColor.SetRed((aBColor.getRed() * 255.0) + 0.5);
+ aBmpColor.SetGreen((aBColor.getGreen() * 255.0) + 0.5);
+ aBmpColor.SetBlue((aBColor.getBlue() * 255.0) + 0.5);
+
+ pWrite->SetPixelOnData(pScanline, nX, aBmpColor);
+ }
+ }
+ }
+ pWrite.reset();
+ pRead.reset();
+
+ return BitmapEx(aDarkBitmap, rBitmapEx.GetAlpha());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapMedianFilter.cxx b/vcl/source/bitmap/BitmapMedianFilter.cxx
new file mode 100644
index 000000000..60edc557a
--- /dev/null
+++ b/vcl/source/bitmap/BitmapMedianFilter.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 <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/bitmapaccess.hxx>
+#include <vcl/BitmapMedianFilter.hxx>
+
+#include <bitmapwriteaccess.hxx>
+
+#define S2(a, b) \
+ { \
+ long t; \
+ if ((t = b - a) < 0) \
+ { \
+ a += t; \
+ b -= t; \
+ } \
+ }
+#define MN3(a, b, c) \
+ S2(a, b); \
+ S2(a, c);
+#define MX3(a, b, c) \
+ S2(b, c); \
+ S2(a, c);
+#define MNMX3(a, b, c) \
+ MX3(a, b, c); \
+ S2(a, b);
+#define MNMX4(a, b, c, d) \
+ S2(a, b); \
+ S2(c, d); \
+ S2(a, c); \
+ S2(b, d);
+#define MNMX5(a, b, c, d, e) \
+ S2(a, b); \
+ S2(c, d); \
+ MN3(a, c, e); \
+ MX3(b, d, e);
+#define MNMX6(a, b, c, d, e, f) \
+ S2(a, d); \
+ S2(b, e); \
+ S2(c, f); \
+ MN3(a, b, c); \
+ MX3(d, e, f);
+
+BitmapEx BitmapMedianFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ Bitmap::ScopedReadAccess pReadAcc(aBitmap);
+ bool bRet = false;
+
+ if (pReadAcc)
+ {
+ Bitmap aNewBmp(aBitmap.GetSizePixel(), 24);
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if (pWriteAcc)
+ {
+ const long nWidth = pWriteAcc->Width(), nWidth2 = nWidth + 2;
+ const long nHeight = pWriteAcc->Height(), nHeight2 = nHeight + 2;
+ std::unique_ptr<long[]> pColm(new long[nWidth2]);
+ std::unique_ptr<long[]> pRows(new long[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;
+ long nY, nX, i;
+ long nR1, nR2, nR3, nR4, nR5, nR6, nR7, nR8, nR9;
+ long nG1, nG2, nG3, nG4, nG5, nG6, nG7, nG8, nG9;
+ long nB1, nB2, nB3, nB4, nB5, nB6, nB7, nB8, nB9;
+
+ // create column LUT
+ for (i = 0; i < nWidth2; i++)
+ pColm[i] = (i > 0) ? (i - 1) : 0;
+
+ pColm[nWidth + 1] = pColm[nWidth];
+
+ // create row LUT
+ for (i = 0; i < nHeight2; i++)
+ pRows[i] = (i > 0) ? (i - 1) : 0;
+
+ pRows[nHeight + 1] = pRows[nHeight];
+
+ // read first three rows of bitmap color
+ if (nHeight2 > 2)
+ {
+ for (i = 0; i < nWidth2; i++)
+ {
+ pColRow1[i] = pReadAcc->GetColor(pRows[0], pColm[i]);
+ pColRow2[i] = pReadAcc->GetColor(pRows[1], pColm[i]);
+ pColRow3[i] = pReadAcc->GetColor(pRows[2], pColm[i]);
+ }
+ }
+
+ // do median filtering
+ for (nY = 0; nY < nHeight;)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (nX = 0; nX < nWidth; nX++)
+ {
+ pColor = pRowTmp1 + nX;
+ nR1 = pColor->GetRed();
+ nG1 = pColor->GetGreen();
+ nB1 = pColor->GetBlue();
+ nR2 = (++pColor)->GetRed();
+ nG2 = pColor->GetGreen();
+ nB2 = pColor->GetBlue();
+ nR3 = (++pColor)->GetRed();
+ nG3 = pColor->GetGreen();
+ nB3 = pColor->GetBlue();
+
+ pColor = pRowTmp2 + nX;
+ nR4 = pColor->GetRed();
+ nG4 = pColor->GetGreen();
+ nB4 = pColor->GetBlue();
+ nR5 = (++pColor)->GetRed();
+ nG5 = pColor->GetGreen();
+ nB5 = pColor->GetBlue();
+ nR6 = (++pColor)->GetRed();
+ nG6 = pColor->GetGreen();
+ nB6 = pColor->GetBlue();
+
+ pColor = pRowTmp3 + nX;
+ nR7 = pColor->GetRed();
+ nG7 = pColor->GetGreen();
+ nB7 = pColor->GetBlue();
+ nR8 = (++pColor)->GetRed();
+ nG8 = pColor->GetGreen();
+ nB8 = pColor->GetBlue();
+ nR9 = (++pColor)->GetRed();
+ nG9 = pColor->GetGreen();
+ nB9 = pColor->GetBlue();
+
+ MNMX6(nR1, nR2, nR3, nR4, nR5, nR6);
+ MNMX5(nR7, nR2, nR3, nR4, nR5);
+ MNMX4(nR8, nR2, nR3, nR4);
+ MNMX3(nR9, nR2, nR3);
+
+ MNMX6(nG1, nG2, nG3, nG4, nG5, nG6);
+ MNMX5(nG7, nG2, nG3, nG4, nG5);
+ MNMX4(nG8, nG2, nG3, nG4);
+ MNMX3(nG9, nG2, nG3);
+
+ MNMX6(nB1, nB2, nB3, nB4, nB5, nB6);
+ MNMX5(nB7, nB2, nB3, nB4, nB5);
+ MNMX4(nB8, nB2, nB3, nB4);
+ MNMX3(nB9, nB2, nB3);
+
+ // set destination color
+ pWriteAcc->SetPixelOnData(pScanline, nX,
+ BitmapColor(static_cast<sal_uInt8>(nR2),
+ static_cast<sal_uInt8>(nG2),
+ static_cast<sal_uInt8>(nB2)));
+ }
+
+ if (++nY < nHeight)
+ {
+ if (pRowTmp1 == pColRow1.get())
+ {
+ pRowTmp1 = pColRow2.get();
+ pRowTmp2 = pColRow3.get();
+ pRowTmp3 = pColRow1.get();
+ }
+ else if (pRowTmp1 == pColRow2.get())
+ {
+ pRowTmp1 = pColRow3.get();
+ pRowTmp2 = pColRow1.get();
+ pRowTmp3 = pColRow2.get();
+ }
+ else
+ {
+ pRowTmp1 = pColRow1.get();
+ pRowTmp2 = pColRow2.get();
+ pRowTmp3 = pColRow3.get();
+ }
+
+ for (i = 0; i < nWidth2; i++)
+ pRowTmp3[i] = pReadAcc->GetColor(pRows[nY + 2], pColm[i]);
+ }
+ }
+
+ pWriteAcc.reset();
+
+ bRet = true;
+ }
+
+ pReadAcc.reset();
+
+ if (bRet)
+ {
+ const MapMode aMap(aBitmap.GetPrefMapMode());
+ const Size aPrefSize(aBitmap.GetPrefSize());
+
+ aBitmap = aNewBmp;
+
+ aBitmap.SetPrefMapMode(aMap);
+ aBitmap.SetPrefSize(aPrefSize);
+ }
+ }
+
+ if (bRet)
+ return BitmapEx(aBitmap);
+
+ return BitmapEx();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapMonochromeFilter.cxx b/vcl/source/bitmap/BitmapMonochromeFilter.cxx
new file mode 100644
index 000000000..72bacf849
--- /dev/null
+++ b/vcl/source/bitmap/BitmapMonochromeFilter.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/.
+ *
+ */
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapMonochromeFilter.hxx>
+#include <vcl/bitmapaccess.hxx>
+
+#include <bitmapwriteaccess.hxx>
+
+BitmapEx BitmapMonochromeFilter::execute(BitmapEx const& aBitmapEx) const
+{
+ Bitmap aBitmap = aBitmapEx.GetBitmap();
+ Bitmap::ScopedReadAccess pReadAcc(aBitmap);
+ bool bRet = false;
+
+ if (pReadAcc)
+ {
+ Bitmap aNewBmp(aBitmap.GetSizePixel(), 1);
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if (pWriteAcc)
+ {
+ const BitmapColor aBlack(pWriteAcc->GetBestMatchingColor(COL_BLACK));
+ const BitmapColor aWhite(pWriteAcc->GetBestMatchingColor(COL_WHITE));
+ const long nWidth = pWriteAcc->Width();
+ const long nHeight = pWriteAcc->Height();
+
+ if (pReadAcc->HasPalette())
+ {
+ for (long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for (long 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 (long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for (long nX = 0; nX < nWidth; nX++)
+ {
+ if (pReadAcc->GetPixelFromData(pScanlineRead, nX).GetLuminance()
+ >= mcThreshold)
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, aWhite);
+ }
+ else
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, aBlack);
+ }
+ }
+ }
+ }
+
+ pWriteAcc.reset();
+ bRet = true;
+ }
+
+ pReadAcc.reset();
+
+ if (bRet)
+ {
+ const MapMode aMap(aBitmap.GetPrefMapMode());
+ const Size aSize(aBitmap.GetPrefSize());
+
+ aBitmap = aNewBmp;
+
+ aBitmap.SetPrefMapMode(aMap);
+ aBitmap.SetPrefSize(aSize);
+ }
+ }
+
+ if (bRet)
+ return BitmapEx(aBitmap);
+
+ return BitmapEx();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapMosaicFilter.cxx b/vcl/source/bitmap/BitmapMosaicFilter.cxx
new file mode 100644
index 000000000..ee0d10275
--- /dev/null
+++ b/vcl/source/bitmap/BitmapMosaicFilter.cxx
@@ -0,0 +1,185 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/bitmapaccess.hxx>
+#include <vcl/BitmapMosaicFilter.hxx>
+
+#include <bitmapwriteaccess.hxx>
+
+BitmapEx BitmapMosaicFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ bool bRet = false;
+
+ if (mnTileWidth > 1 || mnTileHeight > 1)
+ {
+ std::unique_ptr<Bitmap> pNewBmp;
+ BitmapReadAccess* pReadAcc;
+ BitmapWriteAccess* pWriteAcc;
+
+ if (aBitmap.GetBitCount() > 8)
+ {
+ pReadAcc = pWriteAcc = aBitmap.AcquireWriteAccess();
+ }
+ else
+ {
+ pNewBmp.reset(new Bitmap(aBitmap.GetSizePixel(), 24));
+ pReadAcc = aBitmap.AcquireReadAccess();
+ pWriteAcc = pNewBmp->AcquireWriteAccess();
+ }
+
+ bool bConditionsMet = false;
+ long nWidth(0);
+ long nHeight(0);
+ if (pReadAcc && pWriteAcc)
+ {
+ nWidth = pReadAcc->Width();
+ nHeight = pReadAcc->Height();
+ bConditionsMet = (nWidth > 0 && nHeight > 0);
+ }
+
+ if (bConditionsMet)
+ {
+ BitmapColor aCol;
+ long 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;
+ }
+
+ Bitmap::ReleaseAccess(pReadAcc);
+
+ if (pNewBmp)
+ {
+ Bitmap::ReleaseAccess(pWriteAcc);
+
+ if (bRet)
+ {
+ const MapMode aMap(aBitmap.GetPrefMapMode());
+ const Size aPrefSize(aBitmap.GetPrefSize());
+
+ aBitmap = *pNewBmp;
+
+ aBitmap.SetPrefMapMode(aMap);
+ aBitmap.SetPrefSize(aPrefSize);
+ }
+ }
+ }
+
+ if (bRet)
+ return BitmapEx(aBitmap);
+
+ return BitmapEx();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapPopArtFilter.cxx b/vcl/source/bitmap/BitmapPopArtFilter.cxx
new file mode 100644
index 000000000..a335f04fb
--- /dev/null
+++ b/vcl/source/bitmap/BitmapPopArtFilter.cxx
@@ -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/.
+ *
+ */
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapPopArtFilter.hxx>
+
+#include <bitmapwriteaccess.hxx>
+
+BitmapEx BitmapPopArtFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ bool bRet = (aBitmap.GetBitCount() <= 8) || aBitmap.Convert(BmpConversion::N8BitColors);
+
+ if (bRet)
+ {
+ bRet = false;
+
+ BitmapScopedWriteAccess pWriteAcc(aBitmap);
+
+ if (pWriteAcc)
+ {
+ const long nWidth = pWriteAcc->Width();
+ const long nHeight = pWriteAcc->Height();
+ const int nEntryCount = 1 << pWriteAcc->GetBitCount();
+ int n = 0;
+ std::vector<PopArtEntry> aPopArtTable(nEntryCount);
+
+ for (n = 0; n < nEntryCount; n++)
+ {
+ PopArtEntry& rEntry = aPopArtTable[n];
+ rEntry.mnIndex = static_cast<sal_uInt16>(n);
+ rEntry.mnCount = 0;
+ }
+
+ // get pixel count for each palette entry
+ for (long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (long nX = 0; nX < nWidth; nX++)
+ {
+ aPopArtTable[pWriteAcc->GetIndexFromData(pScanline, nX)].mnCount++;
+ }
+ }
+
+ // sort table
+ std::sort(aPopArtTable.begin(), aPopArtTable.end(),
+ [](const PopArtEntry& lhs, const PopArtEntry& rhs) {
+ return lhs.mnCount < rhs.mnCount;
+ });
+
+ // get last used entry
+ sal_uLong nFirstEntry;
+ sal_uLong nLastEntry = 0;
+
+ for (n = 0; n < nEntryCount; n++)
+ {
+ if (aPopArtTable[n].mnCount)
+ nLastEntry = n;
+ }
+
+ // rotate palette (one entry)
+ const BitmapColor aFirstCol(pWriteAcc->GetPaletteColor(
+ sal::static_int_cast<sal_uInt16>(aPopArtTable[0].mnIndex)));
+
+ for (nFirstEntry = 0; nFirstEntry < nLastEntry; nFirstEntry++)
+ {
+ pWriteAcc->SetPaletteColor(
+ sal::static_int_cast<sal_uInt16>(aPopArtTable[nFirstEntry].mnIndex),
+ pWriteAcc->GetPaletteColor(
+ sal::static_int_cast<sal_uInt16>(aPopArtTable[nFirstEntry + 1].mnIndex)));
+ }
+
+ pWriteAcc->SetPaletteColor(
+ sal::static_int_cast<sal_uInt16>(aPopArtTable[nLastEntry].mnIndex), aFirstCol);
+
+ // cleanup
+ pWriteAcc.reset();
+ bRet = true;
+ }
+ }
+
+ if (bRet)
+ return BitmapEx(aBitmap);
+
+ return BitmapEx();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapScaleConvolutionFilter.cxx b/vcl/source/bitmap/BitmapScaleConvolutionFilter.cxx
new file mode 100644
index 000000000..b679e172d
--- /dev/null
+++ b/vcl/source/bitmap/BitmapScaleConvolutionFilter.cxx
@@ -0,0 +1,391 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/bitmapaccess.hxx>
+
+#include <bitmapwriteaccess.hxx>
+#include <BitmapScaleConvolutionFilter.hxx>
+
+#include <algorithm>
+#include <memory>
+
+namespace vcl
+{
+
+namespace
+{
+
+void ImplCalculateContributions(
+ const long aSourceSize,
+ const long aDestinationSize,
+ long& 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 = (long(fabs(ceil(fScaledRadius))) * 2) + 1;
+ const long nAllocSize(aDestinationSize * aNumberOfContributions);
+ rWeights.resize(nAllocSize);
+ rPixels.resize(nAllocSize);
+ rCounts.resize(aDestinationSize);
+
+ for(long i(0); i < aDestinationSize; i++)
+ {
+ const long 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)));
+ long 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 long aPixelIndex(MinMax(j, 0, aSourceSize - 1));
+ const long 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 long nWidth(rSource.GetSizePixel().Width());
+ const long nNewWidth(FRound(nWidth * rScaleX));
+
+ if(nWidth == nNewWidth)
+ {
+ return true;
+ }
+
+ Bitmap::ScopedReadAccess pReadAcc(rSource);
+
+ if(pReadAcc)
+ {
+ std::vector<sal_Int16> aWeights;
+ std::vector<sal_Int32> aPixels;
+ std::vector<sal_Int32> aCounts;
+ long aNumberOfContributions(0);
+
+ const long nHeight(rSource.GetSizePixel().Height());
+ ImplCalculateContributions(nWidth, nNewWidth, aNumberOfContributions, aWeights, aPixels, aCounts, aKernel);
+ rTarget = Bitmap(Size(nNewWidth, nHeight), 24);
+ BitmapScopedWriteAccess pWriteAcc(rTarget);
+ bool bResult(pWriteAcc);
+
+ if(bResult)
+ {
+ for(long y(0); y < nHeight; y++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline( y );
+ Scanline pScanlineRead = pReadAcc->GetScanline( y );
+ for(long x(0); x < nNewWidth; x++)
+ {
+ const long aBaseIndex(x * aNumberOfContributions);
+ sal_Int32 aSum(0);
+ sal_Int32 aValueRed(0);
+ sal_Int32 aValueGreen(0);
+ sal_Int32 aValueBlue(0);
+
+ for(long j(0); j < aCounts[x]; j++)
+ {
+ const long aIndex(aBaseIndex + j);
+ const sal_Int16 aWeight(aWeights[aIndex]);
+ BitmapColor aColor;
+
+ aSum += aWeight;
+
+ if(pReadAcc->HasPalette())
+ {
+ aColor = pReadAcc->GetPaletteColor(pReadAcc->GetIndexFromData(pScanlineRead, aPixels[aIndex]));
+ }
+ else
+ {
+ aColor = pReadAcc->GetPixelFromData(pScanlineRead, aPixels[aIndex]);
+ }
+
+ aValueRed += aWeight * aColor.GetRed();
+ aValueGreen += aWeight * aColor.GetGreen();
+ aValueBlue += aWeight * aColor.GetBlue();
+ }
+
+ assert(aSum != 0);
+
+ const BitmapColor aResultColor(
+ static_cast< sal_uInt8 >(MinMax(static_cast< sal_Int32 >(aValueRed / aSum), 0, 255)),
+ static_cast< sal_uInt8 >(MinMax(static_cast< sal_Int32 >(aValueGreen / aSum), 0, 255)),
+ static_cast< sal_uInt8 >(MinMax(static_cast< sal_Int32 >(aValueBlue / aSum), 0, 255)));
+
+ pWriteAcc->SetPixelOnData(pScanline, x, aResultColor);
+ }
+ }
+
+ pWriteAcc.reset();
+ }
+
+ aWeights.clear();
+ aCounts.clear();
+ aPixels.clear();
+
+ if(bResult)
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool ImplScaleConvolutionVer(Bitmap& rSource, Bitmap& rTarget, const double& rScaleY, const Kernel& aKernel)
+{
+ // Do vertical filtering
+ OSL_ENSURE(rScaleY > 0.0, "Error in scaling: Mirror given in non-mirror-capable method (!)");
+ const long nHeight(rSource.GetSizePixel().Height());
+ const long nNewHeight(FRound(nHeight * rScaleY));
+
+ if(nHeight == nNewHeight)
+ {
+ return true;
+ }
+
+ Bitmap::ScopedReadAccess pReadAcc(rSource);
+
+ if(pReadAcc)
+ {
+ std::vector<sal_Int16> aWeights;
+ std::vector<sal_Int32> aPixels;
+ std::vector<sal_Int32> aCounts;
+ long aNumberOfContributions(0);
+
+ const long nWidth(rSource.GetSizePixel().Width());
+ ImplCalculateContributions(nHeight, nNewHeight, aNumberOfContributions, aWeights, aPixels, aCounts, aKernel);
+ rTarget = Bitmap(Size(nWidth, nNewHeight), 24);
+ BitmapScopedWriteAccess pWriteAcc(rTarget);
+ bool bResult(pWriteAcc);
+
+ if(pWriteAcc)
+ {
+ std::vector<BitmapColor> aScanline(nHeight);
+ for(long x(0); x < nWidth; x++)
+ {
+ for(long y(0); y < nHeight; y++)
+ if(pReadAcc->HasPalette())
+ aScanline[y] = pReadAcc->GetPaletteColor(pReadAcc->GetPixelIndex(y, x));
+ else
+ aScanline[y] = pReadAcc->GetPixel(y, x);
+ for(long y(0); y < nNewHeight; y++)
+ {
+ const long aBaseIndex(y * aNumberOfContributions);
+ sal_Int32 aSum(0);
+ sal_Int32 aValueRed(0);
+ sal_Int32 aValueGreen(0);
+ sal_Int32 aValueBlue(0);
+
+ for(long j(0); j < aCounts[y]; j++)
+ {
+ const long aIndex(aBaseIndex + j);
+ const sal_Int16 aWeight(aWeights[aIndex]);
+ aSum += aWeight;
+ const BitmapColor & aColor = aScanline[aPixels[aIndex]];
+ aValueRed += aWeight * aColor.GetRed();
+ aValueGreen += aWeight * aColor.GetGreen();
+ aValueBlue += aWeight * aColor.GetBlue();
+ }
+
+ assert(aSum != 0);
+
+ const BitmapColor aResultColor(
+ static_cast< sal_uInt8 >(MinMax(static_cast< sal_Int32 >(aValueRed / aSum), 0, 255)),
+ static_cast< sal_uInt8 >(MinMax(static_cast< sal_Int32 >(aValueGreen / aSum), 0, 255)),
+ static_cast< sal_uInt8 >(MinMax(static_cast< sal_Int32 >(aValueBlue / aSum), 0, 255)));
+
+ if(pWriteAcc->HasPalette())
+ {
+ pWriteAcc->SetPixelIndex(y, x, static_cast< sal_uInt8 >(pWriteAcc->GetBestPaletteIndex(aResultColor)));
+ }
+ else
+ {
+ pWriteAcc->SetPixel(y, x, aResultColor);
+ }
+ }
+ }
+ }
+
+ aWeights.clear();
+ aCounts.clear();
+ aPixels.clear();
+
+ if(bResult)
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool ImplScaleConvolution(Bitmap& rBitmap, const double& rScaleX, const double& rScaleY, const Kernel& aKernel)
+{
+ const bool bMirrorHor(rScaleX < 0.0);
+ const bool bMirrorVer(rScaleY < 0.0);
+ const double fScaleX(bMirrorHor ? -rScaleX : rScaleX);
+ const double fScaleY(bMirrorVer ? -rScaleY : rScaleY);
+ const long nWidth(rBitmap.GetSizePixel().Width());
+ const long nHeight(rBitmap.GetSizePixel().Height());
+ const long nNewWidth(FRound(nWidth * fScaleX));
+ const long 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 long nStartSize(nWidth * nHeight);
+ const long nEndSize(nNewWidth * nNewHeight);
+
+ bMirrorAfter = nStartSize > nEndSize;
+
+ if(!bMirrorAfter)
+ {
+ bResult = rBitmap.Mirror(nMirrorFlags);
+ }
+ }
+
+ Bitmap aResult;
+
+ if (bResult)
+ {
+ const long nInBetweenSizeHorFirst(nHeight * nNewWidth);
+ const long nInBetweenSizeVerFirst(nNewHeight * nWidth);
+ Bitmap aSource(rBitmap);
+
+ if(nInBetweenSizeHorFirst < nInBetweenSizeVerFirst)
+ {
+ if(bScaleHor)
+ {
+ bResult = ImplScaleConvolutionHor(aSource, aResult, fScaleX, aKernel);
+ }
+
+ if(bResult && bScaleVer)
+ {
+ if(bScaleHor)
+ {
+ // copy partial result, independent of color depth
+ aSource = aResult;
+ }
+
+ bResult = ImplScaleConvolutionVer(aSource, aResult, fScaleY, aKernel);
+ }
+ }
+ else
+ {
+ if(bScaleVer)
+ {
+ bResult = ImplScaleConvolutionVer(aSource, aResult, fScaleY, aKernel);
+ }
+
+ if(bResult && bScaleHor)
+ {
+ if(bScaleVer)
+ {
+ // copy partial result, independent of color depth
+ aSource = aResult;
+ }
+
+ bResult = ImplScaleConvolutionHor(aSource, aResult, fScaleX, aKernel);
+ }
+ }
+ }
+
+ if(bResult && bMirrorAfter)
+ {
+ bResult = aResult.Mirror(nMirrorFlags);
+ }
+
+ if(bResult)
+ {
+ rBitmap.AdaptBitCount(aResult);
+ rBitmap = aResult;
+ }
+
+ return bResult;
+}
+
+} // end anonymous namespace
+
+BitmapEx BitmapScaleConvolutionFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ bool bRetval = false;
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ bRetval = ImplScaleConvolution(aBitmap, mrScaleX, mrScaleY, *mxKernel);
+
+ if (bRetval)
+ return BitmapEx(aBitmap);
+
+ return BitmapEx();
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapScaleSuperFilter.cxx b/vcl/source/bitmap/BitmapScaleSuperFilter.cxx
new file mode 100644
index 000000000..ff808ddcd
--- /dev/null
+++ b/vcl/source/bitmap/BitmapScaleSuperFilter.cxx
@@ -0,0 +1,1202 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/threadpool.hxx>
+
+#include <tools/helpers.hxx>
+#include <vcl/bitmapaccess.hxx>
+
+#include <bitmapwriteaccess.hxx>
+#include <BitmapScaleSuperFilter.hxx>
+
+#include <algorithm>
+#include <memory>
+#include <svdata.hxx>
+#include <sal/log.hxx>
+
+namespace {
+
+#define 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;
+ long mnDestW;
+ bool mbHMirr;
+ bool mbVMirr;
+ std::vector<long> maMapIX;
+ std::vector<long> maMapIY;
+ std::vector<BilinearWeightType> maMapFX;
+ std::vector<BilinearWeightType> maMapFY;
+
+ ScaleContext( BitmapReadAccess *pSrc,
+ BitmapWriteAccess *pDest,
+ long nSrcW, long nDestW,
+ long nSrcH, long 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(long nSourceLength, long nDestinationLength, bool bMirrored,
+ std::vector<long> & rMapIX, std::vector<BilinearWeightType> & rMapFX)
+ {
+ const double fRevScale = (nDestinationLength > 1) ? double(nSourceLength - 1) / (nDestinationLength - 1) : 0.0;
+
+ long nTemp = nSourceLength - 2;
+ long nTempX = nSourceLength - 1;
+
+ for (long i = 0; i < nDestinationLength; i++)
+ {
+ double fTemp = i * fRevScale;
+ if (bMirrored)
+ fTemp = nTempX - fTemp;
+ rMapIX[i] = MinMax(long(fTemp), 0, nTemp);
+ rMapFX[i] = BilinearWeightType((fTemp - rMapIX[i]) * (BilinearWeightType(1) << MAP_PRECISION));
+ }
+ }
+};
+
+constexpr long constScaleThreadStrip = 32;
+
+typedef void (*ScaleRangeFn)(ScaleContext &rContext, long nStartY, long nEndY);
+
+class ScaleTask : public comphelper::ThreadTask
+{
+ ScaleRangeFn mpScaleRangeFunction;
+ ScaleContext& mrContext;
+ long mnStartY;
+ long mnEndY;
+
+public:
+ explicit ScaleTask(const std::shared_ptr<comphelper::ThreadTaskTag>& pTag,
+ ScaleRangeFn pScaleRangeFunction,
+ ScaleContext& rContext,
+ long nStartY, long nEndY)
+ : comphelper::ThreadTask(pTag)
+ , mpScaleRangeFunction(pScaleRangeFunction)
+ , mrContext(rContext)
+ , mnStartY(nStartY)
+ , mnEndY(nEndY)
+ {}
+
+ virtual void doWork() override
+ {
+ mpScaleRangeFunction(mrContext, mnStartY, mnEndY);
+ }
+};
+
+void scaleUp32bit(ScaleContext &rCtx, long nStartY, long nEndY)
+{
+ const int nColorComponents = 4;
+
+ const long nStartX = 0;
+ const long nEndX = rCtx.mnDestW - 1;
+
+ for (long nY = nStartY; nY <= nEndY; nY++)
+ {
+ long 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);
+
+ sal_uInt8 nComponent1[nColorComponents];
+ sal_uInt8 nComponent2[nColorComponents];
+
+ Scanline pColorPtr0;
+ Scanline pColorPtr1;
+
+ for (long nX = nStartX; nX <= nEndX; nX++)
+ {
+ long nTempX = rCtx.maMapIX[nX];
+ BilinearWeightType nTempFX = rCtx.maMapFX[nX];
+
+ pColorPtr0 = pLine0 + nTempX * nColorComponents;
+ pColorPtr1 = pColorPtr0 + nColorComponents;
+
+ nComponent1[0] = MAP(*pColorPtr0, *pColorPtr1, nTempFX);
+ pColorPtr0++; pColorPtr1++;
+ nComponent1[1] = MAP(*pColorPtr0, *pColorPtr1, nTempFX);
+ pColorPtr0++; pColorPtr1++;
+ nComponent1[2] = MAP(*pColorPtr0, *pColorPtr1, nTempFX);
+ pColorPtr0++; pColorPtr1++;
+ nComponent1[3] = MAP(*pColorPtr0, *pColorPtr1, nTempFX);
+
+ pColorPtr0 = pLine1 + nTempX * nColorComponents;
+ pColorPtr1 = pColorPtr0 + nColorComponents;
+
+ nComponent2[0] = MAP(*pColorPtr0, *pColorPtr1, nTempFX);
+ pColorPtr0++; pColorPtr1++;
+ nComponent2[1] = MAP(*pColorPtr0, *pColorPtr1, nTempFX);
+ pColorPtr0++; pColorPtr1++;
+ nComponent2[2] = MAP(*pColorPtr0, *pColorPtr1, nTempFX);
+ pColorPtr0++; pColorPtr1++;
+ nComponent2[3] = MAP(*pColorPtr0, *pColorPtr1, nTempFX);
+
+ *pScanDest = MAP(nComponent1[0], nComponent2[0], nTempFY);
+ pScanDest++;
+ *pScanDest = MAP(nComponent1[1], nComponent2[1], nTempFY);
+ pScanDest++;
+ *pScanDest = MAP(nComponent1[2], nComponent2[2], nTempFY);
+ pScanDest++;
+ *pScanDest = MAP(nComponent1[3], nComponent2[3], nTempFY);
+ pScanDest++;
+ }
+ }
+}
+
+void scaleUpPalette8bit(ScaleContext &rCtx, long nStartY, long nEndY)
+{
+ const long nStartX = 0, nEndX = rCtx.mnDestW - 1;
+
+ for( long nY = nStartY; nY <= nEndY; nY++ )
+ {
+ long 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(long nX = nStartX, nXDst = 0; nX <= nEndX; nX++ )
+ {
+ long 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(ScaleContext &rCtx, long nStartY, long nEndY)
+{
+ const long nStartX = 0, nEndX = rCtx.mnDestW - 1;
+
+ for( long nY = nStartY; nY <= nEndY; nY++ )
+ {
+ long nTempY = rCtx.maMapIY[ nY ];
+ BilinearWeightType nTempFY = rCtx.maMapFY[ nY ];
+ Scanline pScanline = rCtx.mpDest->GetScanline( nY );
+
+ for( long nX = nStartX, nXDst = 0; nX <= nEndX; nX++ )
+ {
+ long 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 scaleUp24bit(ScaleContext &rCtx, long nStartY, long nEndY)
+{
+ const int nColorComponents = 3;
+
+ const long nStartX = 0;
+ const long nEndX = rCtx.mnDestW - 1;
+
+ for (long nY = nStartY; nY <= nEndY; nY++)
+ {
+ long 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);
+
+ sal_uInt8 nComponent1[nColorComponents];
+ sal_uInt8 nComponent2[nColorComponents];
+
+ Scanline pColorPtr0;
+ Scanline pColorPtr1;
+
+ for (long nX = nStartX; nX <= nEndX; nX++)
+ {
+ long nTempX = rCtx.maMapIX[nX];
+ BilinearWeightType nTempFX = rCtx.maMapFX[nX];
+
+ pColorPtr0 = pLine0 + nTempX * nColorComponents;
+ pColorPtr1 = pColorPtr0 + nColorComponents;
+
+ nComponent1[0] = MAP(*pColorPtr0, *pColorPtr1, nTempFX);
+ pColorPtr0++; pColorPtr1++;
+ nComponent1[1] = MAP(*pColorPtr0, *pColorPtr1, nTempFX);
+ pColorPtr0++; pColorPtr1++;
+ nComponent1[2] = MAP(*pColorPtr0, *pColorPtr1, nTempFX);
+
+ pColorPtr0 = pLine1 + nTempX * nColorComponents;
+ pColorPtr1 = pColorPtr0 + nColorComponents;
+
+ nComponent2[0] = MAP(*pColorPtr0, *pColorPtr1, nTempFX);
+ pColorPtr0++; pColorPtr1++;
+ nComponent2[1] = MAP(*pColorPtr0, *pColorPtr1, nTempFX);
+ pColorPtr0++; pColorPtr1++;
+ nComponent2[2] = MAP(*pColorPtr0, *pColorPtr1, nTempFX);
+
+ *pScanDest = MAP(nComponent1[0], nComponent2[0], nTempFY);
+ pScanDest++;
+ *pScanDest = MAP(nComponent1[1], nComponent2[1], nTempFY);
+ pScanDest++;
+ *pScanDest = MAP(nComponent1[2], nComponent2[2], nTempFY);
+ pScanDest++;
+ }
+ }
+}
+
+void scaleUpNonPaletteGeneral(ScaleContext &rCtx, long nStartY, long nEndY)
+{
+ const long nStartX = 0, nEndX = rCtx.mnDestW - 1;
+
+ for( long nY = nStartY; nY <= nEndY; nY++ )
+ {
+ long nTempY = rCtx.maMapIY[ nY ];
+ BilinearWeightType nTempFY = rCtx.maMapFY[ nY ];
+ Scanline pScanDest = rCtx.mpDest->GetScanline( nY );
+
+ for( long nX = nStartX, nXDst = 0; nX <= nEndX; nX++ )
+ {
+ long 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 scaleDown32bit(ScaleContext &rCtx, long nStartY, long nEndY)
+{
+ const int constColorComponents = 4;
+
+ const long nStartX = 0;
+ const long nEndX = rCtx.mnDestW - 1;
+
+ for (long nY = nStartY; nY <= nEndY; nY++)
+ {
+ long nTop = rCtx.mbVMirr ? (nY + 1) : nY;
+ long nBottom = rCtx.mbVMirr ? nY : (nY + 1);
+
+ long nLineStart;
+ long 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 (long nX = nStartX; nX <= nEndX; nX++)
+ {
+ long nLeft = rCtx.mbHMirr ? (nX + 1) : nX;
+ long nRight = rCtx.mbHMirr ? nX : (nX + 1);
+
+ long nRowStart;
+ long 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 nSum1 = 0;
+ int nSum2 = 0;
+ int nSum3 = 0;
+ int nSum4 = 0;
+ BilinearWeightType nTotalWeightY = 0;
+
+ for (long i = 0; i<= nLineRange; i++)
+ {
+ Scanline pTmpY = rCtx.mpSrc->GetScanline(nLineStart + i);
+ Scanline pTmpX = pTmpY + constColorComponents * nRowStart;
+
+ int nSumRow1 = 0;
+ int nSumRow2 = 0;
+ int nSumRow3 = 0;
+ int nSumRow4 = 0;
+ BilinearWeightType nTotalWeightX = 0;
+
+ for (long j = 0; j <= nRowRange; j++)
+ {
+ if (nX == nEndX)
+ {
+ nSumRow1 += (*pTmpX) << MAP_PRECISION; pTmpX++;
+ nSumRow2 += (*pTmpX) << MAP_PRECISION; pTmpX++;
+ nSumRow3 += (*pTmpX) << MAP_PRECISION; pTmpX++;
+ nSumRow4 += (*pTmpX) << MAP_PRECISION; pTmpX++;
+ nTotalWeightX += lclMaxWeight();
+ }
+ else if(j == 0)
+ {
+ BilinearWeightType nWeightX = lclMaxWeight() - rCtx.maMapFX[nLeft];
+ nSumRow1 += (nWeightX * (*pTmpX)); pTmpX++;
+ nSumRow2 += (nWeightX * (*pTmpX)); pTmpX++;
+ nSumRow3 += (nWeightX * (*pTmpX)); pTmpX++;
+ nSumRow4 += (nWeightX * (*pTmpX)); pTmpX++;
+ nTotalWeightX += nWeightX;
+ }
+ else if ( nRowRange == j )
+ {
+ BilinearWeightType nWeightX = rCtx.maMapFX[ nRight ] ;
+ nSumRow1 += (nWeightX * (*pTmpX)); pTmpX++;
+ nSumRow2 += (nWeightX * (*pTmpX)); pTmpX++;
+ nSumRow3 += (nWeightX * (*pTmpX)); pTmpX++;
+ nSumRow4 += (nWeightX * (*pTmpX)); pTmpX++;
+ nTotalWeightX += nWeightX;
+ }
+ else
+ {
+ nSumRow1 += (*pTmpX) << MAP_PRECISION; pTmpX++;
+ nSumRow2 += (*pTmpX) << MAP_PRECISION; pTmpX++;
+ nSumRow3 += (*pTmpX) << MAP_PRECISION; pTmpX++;
+ nSumRow4 += (*pTmpX) << MAP_PRECISION; pTmpX++;
+ 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)
+ {
+ nSumRow1 /= nTotalWeightX;
+ nSumRow2 /= nTotalWeightX;
+ nSumRow3 /= nTotalWeightX;
+ nSumRow4 /= nTotalWeightX;
+ }
+ nSum1 += nWeightY * nSumRow1;
+ nSum2 += nWeightY * nSumRow2;
+ nSum3 += nWeightY * nSumRow3;
+ nSum4 += nWeightY * nSumRow4;
+ nTotalWeightY += nWeightY;
+ }
+
+ if (nTotalWeightY)
+ {
+ nSum1 /= nTotalWeightY;
+ nSum2 /= nTotalWeightY;
+ nSum3 /= nTotalWeightY;
+ nSum4 /= nTotalWeightY;
+ }
+
+ // Write the calculated color components to the destination
+ *pScanDest = nSum1; pScanDest++;
+ *pScanDest = nSum2; pScanDest++;
+ *pScanDest = nSum3; pScanDest++;
+ *pScanDest = nSum4; pScanDest++;
+ }
+ }
+}
+
+void scaleDownPalette8bit(ScaleContext &rCtx, long nStartY, long nEndY)
+{
+ const long nStartX = 0, nEndX = rCtx.mnDestW - 1;
+
+ for( long nY = nStartY; nY <= nEndY; nY++ )
+ {
+ long nTop = rCtx.mbVMirr ? ( nY + 1 ) : nY;
+ long nBottom = rCtx.mbVMirr ? nY : ( nY + 1 ) ;
+
+ long 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( long nX = nStartX , nXDst = 0; nX <= nEndX; nX++ )
+ {
+ long nLeft = rCtx.mbHMirr ? ( nX + 1 ) : nX;
+ long nRight = rCtx.mbHMirr ? nX : ( nX + 1 ) ;
+
+ long nRowStart;
+ long 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(long 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(long 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(ScaleContext &rCtx, long nStartY, long nEndY)
+{
+ const long nStartX = 0, nEndX = rCtx.mnDestW - 1;
+
+ for( long nY = nStartY; nY <= nEndY; nY++ )
+ {
+ long nTop = rCtx.mbVMirr ? ( nY + 1 ) : nY;
+ long nBottom = rCtx.mbVMirr ? nY : ( nY + 1 ) ;
+
+ long 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( long nX = nStartX , nXDst = 0; nX <= nEndX; nX++ )
+ {
+ long nLeft = rCtx.mbHMirr ? ( nX + 1 ) : nX;
+ long nRight = rCtx.mbHMirr ? nX : ( nX + 1 ) ;
+
+ long 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(long 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(long 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();
+ }
+ }
+
+ long 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 scaleDown24bit(ScaleContext &rCtx, long nStartY, long nEndY)
+{
+ const int constColorComponents = 3;
+
+ const long nStartX = 0;
+ const long nEndX = rCtx.mnDestW - 1;
+
+ for (long nY = nStartY; nY <= nEndY; nY++)
+ {
+ long nTop = rCtx.mbVMirr ? (nY + 1) : nY;
+ long nBottom = rCtx.mbVMirr ? nY : (nY + 1);
+
+ long nLineStart;
+ long 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 (long nX = nStartX; nX <= nEndX; nX++)
+ {
+ long nLeft = rCtx.mbHMirr ? (nX + 1) : nX;
+ long nRight = rCtx.mbHMirr ? nX : (nX + 1);
+
+ long nRowStart;
+ long 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 nSum1 = 0;
+ int nSum2 = 0;
+ int nSum3 = 0;
+ BilinearWeightType nTotalWeightY = 0;
+
+ for (long i = 0; i<= nLineRange; i++)
+ {
+ Scanline pTmpY = rCtx.mpSrc->GetScanline(nLineStart + i);
+ Scanline pTmpX = pTmpY + constColorComponents * nRowStart;
+
+ int nSumRow1 = 0;
+ int nSumRow2 = 0;
+ int nSumRow3 = 0;
+ BilinearWeightType nTotalWeightX = 0;
+
+ for (long j = 0; j <= nRowRange; j++)
+ {
+ if (nX == nEndX)
+ {
+ nSumRow1 += (*pTmpX) << MAP_PRECISION; pTmpX++;
+ nSumRow2 += (*pTmpX) << MAP_PRECISION; pTmpX++;
+ nSumRow3 += (*pTmpX) << MAP_PRECISION; pTmpX++;
+ nTotalWeightX += lclMaxWeight();
+ }
+ else if(j == 0)
+ {
+ BilinearWeightType nWeightX = lclMaxWeight() - rCtx.maMapFX[nLeft];
+ nSumRow1 += (nWeightX * (*pTmpX)); pTmpX++;
+ nSumRow2 += (nWeightX * (*pTmpX)); pTmpX++;
+ nSumRow3 += (nWeightX * (*pTmpX)); pTmpX++;
+ nTotalWeightX += nWeightX;
+ }
+ else if ( nRowRange == j )
+ {
+ BilinearWeightType nWeightX = rCtx.maMapFX[ nRight ] ;
+ nSumRow1 += (nWeightX * (*pTmpX)); pTmpX++;
+ nSumRow2 += (nWeightX * (*pTmpX)); pTmpX++;
+ nSumRow3 += (nWeightX * (*pTmpX)); pTmpX++;
+ nTotalWeightX += nWeightX;
+ }
+ else
+ {
+ nSumRow1 += (*pTmpX) << MAP_PRECISION; pTmpX++;
+ nSumRow2 += (*pTmpX) << MAP_PRECISION; pTmpX++;
+ nSumRow3 += (*pTmpX) << MAP_PRECISION; pTmpX++;
+ 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)
+ {
+ nSumRow1 /= nTotalWeightX;
+ nSumRow2 /= nTotalWeightX;
+ nSumRow3 /= nTotalWeightX;
+ }
+ nSum1 += nWeightY * nSumRow1;
+ nSum2 += nWeightY * nSumRow2;
+ nSum3 += nWeightY * nSumRow3;
+ nTotalWeightY += nWeightY;
+ }
+
+ if (nTotalWeightY)
+ {
+ nSum1 /= nTotalWeightY;
+ nSum2 /= nTotalWeightY;
+ nSum3 /= nTotalWeightY;
+ }
+
+ // Write the calculated color components to the destination
+ *pScanDest = nSum1; pScanDest++;
+ *pScanDest = nSum2; pScanDest++;
+ *pScanDest = nSum3; pScanDest++;
+ }
+ }
+}
+
+void scaleDownNonPaletteGeneral(ScaleContext &rCtx, long nStartY, long nEndY)
+{
+ const long nStartX = 0, nEndX = rCtx.mnDestW - 1;
+
+ for( long nY = nStartY; nY <= nEndY; nY++ )
+ {
+ long nTop = rCtx.mbVMirr ? ( nY + 1 ) : nY;
+ long nBottom = rCtx.mbVMirr ? nY : ( nY + 1 ) ;
+
+ long 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( long nX = nStartX , nXDst = 0; nX <= nEndX; nX++ )
+ {
+ long nLeft = rCtx.mbHMirr ? ( nX + 1 ) : nX;
+ long nRight = rCtx.mbHMirr ? nX : ( nX + 1 ) ;
+
+ long 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(long 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(long 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 long nDstW = FRound(aSizePix.Width() * fScaleX);
+ const long nDstH = FRound(aSizePix.Height() * fScaleY);
+
+ const double fScaleThresh = 0.6;
+
+ if (nDstW <= 1 || nDstH <= 1)
+ return BitmapEx();
+
+ // check cache for a previously scaled version of this
+ ScaleCacheKey aKey(aBitmap.ImplGetSalBitmap().get(),
+ Size(nDstW, nDstH));
+
+ ImplSVData* pSVData = ImplGetSVData();
+ auto& rCache = pSVData->maGDIData.maScaleCache;
+ auto aFind = rCache.find(aKey);
+ if (aFind != rCache.end())
+ {
+ if (aFind->second.GetSizePixel().Width() == nDstW && aFind->second.GetSizePixel().Height() == nDstH)
+ return aFind->second;
+ else
+ SAL_WARN("vcl.gdi", "Error: size mismatch in scale cache");
+ }
+
+ {
+ Bitmap::ScopedReadAccess pReadAccess(aBitmap);
+
+ sal_uInt16 nSourceBitcount = aBitmap.GetBitCount();
+
+ Bitmap aOutBmp(Size(nDstW, nDstH), std::max(nSourceBitcount, sal_uInt16(24)));
+ Size aOutSize = aOutBmp.GetSizePixel();
+ sal_uInt16 nTargetBitcount = aOutBmp.GetBitCount();
+
+ if (!aOutSize.Width() || !aOutSize.Height())
+ {
+ SAL_WARN("vcl.gdi", "bmp creation failed");
+ return BitmapEx();
+ }
+
+ BitmapScopedWriteAccess pWriteAccess(aOutBmp);
+
+ const long nStartY = 0;
+ const long nEndY = nDstH - 1;
+
+ if (pReadAccess && pWriteAccess)
+ {
+ ScaleRangeFn pScaleRangeFn;
+ 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 (nSourceBitcount != nTargetBitcount)
+ {
+ 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 ? scaleUp24bit : scaleDown24bit;
+ break;
+ case ScanlineFormat::N32BitTcRgba:
+ case ScanlineFormat::N32BitTcBgra:
+ case ScanlineFormat::N32BitTcArgb:
+ case ScanlineFormat::N32BitTcAbgr:
+ pScaleRangeFn = bScaleUp ? scaleUp32bit : scaleDown32bit;
+ 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;
+
+ 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();
+
+ long nStripYStart = nStartY;
+ long nStripYEnd = nStripYStart + constScaleThreadStrip - 1;
+
+ while (nStripYEnd < nEndY)
+ {
+ std::unique_ptr<ScaleTask> pTask(new ScaleTask(pTag, pScaleRangeFn, aContext, nStripYStart, nStripYEnd));
+ rShared.pushTask(std::move(pTask));
+ nStripYStart += constScaleThreadStrip;
+ nStripYEnd += constScaleThreadStrip;
+ }
+ if (nStripYStart <= nEndY)
+ {
+ std::unique_ptr<ScaleTask> pTask(new ScaleTask(pTag, pScaleRangeFn, aContext, nStripYStart, nEndY));
+ rShared.pushTask(std::move(pTask));
+ }
+ rShared.waitUntilDone(pTag);
+ SAL_INFO("vcl.gdi", "All threaded scaling tasks complete");
+ }
+ catch (...)
+ {
+ SAL_WARN("vcl.gdi", "threaded bitmap scaling failed");
+ bUseThreads = false;
+ }
+ }
+
+ if (!bUseThreads)
+ pScaleRangeFn( aContext, nStartY, nEndY );
+
+ pWriteAccess.reset();
+ bRet = true;
+ aBitmap.AdaptBitCount(aOutBmp);
+ aBitmap = aOutBmp;
+ }
+ }
+
+ if (bRet)
+ {
+ tools::Rectangle aRect(Point(0, 0), Point(nDstW, nDstH));
+ aBitmap.Crop(aRect);
+ BitmapEx aRet(aBitmap);
+ rCache.insert(std::make_pair(aKey, aRet));
+ return aRet;
+ }
+
+ return BitmapEx();
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapSeparableUnsharpenFilter.cxx b/vcl/source/bitmap/BitmapSeparableUnsharpenFilter.cxx
new file mode 100644
index 000000000..58d5e3810
--- /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/bitmapaccess.hxx>
+#include <vcl/BitmapGaussianSeparableBlurFilter.hxx>
+#include <vcl/BitmapSeparableUnsharpenFilter.hxx>
+
+#include <bitmapwriteaccess.hxx>
+
+BitmapEx BitmapSeparableUnsharpenFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ const long nWidth = aBitmap.GetSizePixel().Width();
+ const long 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), 24);
+
+ Bitmap::ScopedReadAccess pReadAccBlur(aBlur);
+ Bitmap::ScopedReadAccess pReadAcc(aBitmap);
+ BitmapScopedWriteAccess pWriteAcc(aResultBitmap);
+
+ BitmapColor aColor, aColorBlur;
+
+ // For all pixels in original image subtract pixels values from blurred image
+ for (long y = 0; y < nHeight; y++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(y);
+ for (long x = 0; x < nWidth; x++)
+ {
+ aColorBlur = pReadAccBlur->GetColor(y, x);
+ aColor = pReadAcc->GetColor(y, x);
+
+ BitmapColor aResultColor(
+ static_cast<sal_uInt8>(MinMax(
+ aColor.GetRed() + (aColor.GetRed() - aColorBlur.GetRed()) * aAmount, 0, 255)),
+ static_cast<sal_uInt8>(MinMax(
+ aColor.GetGreen() + (aColor.GetGreen() - aColorBlur.GetGreen()) * aAmount, 0,
+ 255)),
+ static_cast<sal_uInt8>(
+ MinMax(aColor.GetBlue() + (aColor.GetBlue() - aColorBlur.GetBlue()) * aAmount,
+ 0, 255)));
+
+ pWriteAcc->SetPixelOnData(pScanline, x, aResultColor);
+ }
+ }
+
+ pWriteAcc.reset();
+ pReadAcc.reset();
+ pReadAccBlur.reset();
+ aBitmap.ReassignWithSize(aResultBitmap);
+
+ return BitmapEx(aBitmap);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapSepiaFilter.cxx b/vcl/source/bitmap/BitmapSepiaFilter.cxx
new file mode 100644
index 000000000..5123bf130
--- /dev/null
+++ b/vcl/source/bitmap/BitmapSepiaFilter.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 <sal/config.h>
+
+#include <algorithm>
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/bitmapaccess.hxx>
+#include <vcl/BitmapSepiaFilter.hxx>
+
+#include <bitmapwriteaccess.hxx>
+
+BitmapEx BitmapSepiaFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+ Bitmap::ScopedReadAccess pReadAcc(aBitmap);
+ bool bRet = false;
+
+ if (pReadAcc)
+ {
+ const long 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(), 8, &aSepiaPal);
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if (pWriteAcc)
+ {
+ BitmapColor aCol(sal_uInt8(0));
+ const long nWidth = pWriteAcc->Width();
+ const long 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 (long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for (long nX = 0; nX < nWidth; nX++)
+ {
+ aCol.SetIndex(pIndexMap[pReadAcc->GetIndexFromData(pScanlineRead, nX)]);
+ pWriteAcc->SetPixelOnData(pScanline, nX, aCol);
+ }
+ }
+ }
+ else
+ {
+ for (long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for (long nX = 0; nX < nWidth; nX++)
+ {
+ aCol.SetIndex(pReadAcc->GetPixelFromData(pScanlineRead, nX).GetLuminance());
+ pWriteAcc->SetPixelOnData(pScanline, nX, aCol);
+ }
+ }
+ }
+
+ pWriteAcc.reset();
+ bRet = true;
+ }
+
+ pReadAcc.reset();
+
+ if (bRet)
+ {
+ const MapMode aMap(aBitmap.GetPrefMapMode());
+ const Size aPrefSize(aBitmap.GetPrefSize());
+
+ aBitmap = aNewBmp;
+
+ aBitmap.SetPrefMapMode(aMap);
+ aBitmap.SetPrefSize(aPrefSize);
+ }
+ }
+
+ if (bRet)
+ return rBitmapEx;
+
+ return BitmapEx();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapSimpleColorQuantizationFilter.cxx b/vcl/source/bitmap/BitmapSimpleColorQuantizationFilter.cxx
new file mode 100644
index 000000000..1d21ed9e1
--- /dev/null
+++ b/vcl/source/bitmap/BitmapSimpleColorQuantizationFilter.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 <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/bitmapaccess.hxx>
+#include <vcl/BitmapSimpleColorQuantizationFilter.hxx>
+
+#include <bitmapwriteaccess.hxx>
+#include <bitmap/Octree.hxx>
+
+BitmapEx BitmapSimpleColorQuantizationFilter::execute(BitmapEx const& aBitmapEx) const
+{
+ Bitmap aBitmap = aBitmapEx.GetBitmap();
+
+ bool bRet = false;
+
+ if (aBitmap.GetColorCount() <= sal_Int64(mnNewColorCount))
+ {
+ bRet = true;
+ }
+ else
+ {
+ Bitmap aNewBmp;
+ Bitmap::ScopedReadAccess pRAcc(aBitmap);
+ const sal_uInt16 nColorCount = std::min(mnNewColorCount, sal_uInt16(256));
+ sal_uInt16 nBitCount = 0;
+
+ if (nColorCount <= 2)
+ nBitCount = 1;
+ else if (nColorCount <= 16)
+ nBitCount = 4;
+ else
+ nBitCount = 8;
+
+ if (pRAcc)
+ {
+ Octree aOct(*pRAcc, nColorCount);
+ const BitmapPalette& rPal = aOct.GetPalette();
+
+ aNewBmp = Bitmap(aBitmap.GetSizePixel(), nBitCount, &rPal);
+ BitmapScopedWriteAccess pWAcc(aNewBmp);
+
+ if (pWAcc)
+ {
+ const long nWidth = pRAcc->Width();
+ const long nHeight = pRAcc->Height();
+
+ if (pRAcc->HasPalette())
+ {
+ for (long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWAcc->GetScanline(nY);
+ Scanline pScanlineRead = pRAcc->GetScanline(nY);
+ for (long 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 (long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWAcc->GetScanline(nY);
+ Scanline pScanlineRead = pRAcc->GetScanline(nY);
+ for (long nX = 0; nX < nWidth; nX++)
+ {
+ auto c = pRAcc->GetPixelFromData(pScanlineRead, nX);
+ pWAcc->SetPixelOnData(
+ pScanline, nX,
+ BitmapColor(static_cast<sal_uInt8>(aOct.GetBestPaletteIndex(c))));
+ }
+ }
+ }
+
+ pWAcc.reset();
+ bRet = true;
+ }
+
+ pRAcc.reset();
+ }
+
+ if (bRet)
+ {
+ const MapMode aMap(aBitmap.GetPrefMapMode());
+ const Size aSize(aBitmap.GetPrefSize());
+
+ aBitmap = aNewBmp;
+
+ aBitmap.SetPrefMapMode(aMap);
+ aBitmap.SetPrefSize(aSize);
+ }
+ }
+
+ if (bRet)
+ return BitmapEx(aBitmap);
+
+ return BitmapEx();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapSmoothenFilter.cxx b/vcl/source/bitmap/BitmapSmoothenFilter.cxx
new file mode 100644
index 000000000..67cea0cb3
--- /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 rBitmapEx;
+
+ return BitmapEx();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapSobelGreyFilter.cxx b/vcl/source/bitmap/BitmapSobelGreyFilter.cxx
new file mode 100644
index 000000000..5ce4987a2
--- /dev/null
+++ b/vcl/source/bitmap/BitmapSobelGreyFilter.cxx
@@ -0,0 +1,170 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/bitmapaccess.hxx>
+#include <vcl/BitmapSobelGreyFilter.hxx>
+
+#include <bitmapwriteaccess.hxx>
+
+BitmapEx BitmapSobelGreyFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ bool bRet = aBitmap.ImplMakeGreyscales(256);
+
+ if (bRet)
+ {
+ bRet = false;
+
+ Bitmap::ScopedReadAccess pReadAcc(aBitmap);
+
+ if (pReadAcc)
+ {
+ Bitmap aNewBmp(aBitmap.GetSizePixel(), 8, &pReadAcc->GetPalette());
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if (pWriteAcc)
+ {
+ BitmapColor aGrey(sal_uInt8(0));
+ const long nWidth = pWriteAcc->Width();
+ const long nHeight = pWriteAcc->Height();
+ const long nMask111 = -1, nMask121 = 0, nMask131 = 1;
+ const long nMask211 = -2, nMask221 = 0, nMask231 = 2;
+ const long nMask311 = -1, nMask321 = 0, nMask331 = 1;
+ const long nMask112 = 1, nMask122 = 2, nMask132 = 1;
+ const long nMask212 = 0, nMask222 = 0, nMask232 = 0;
+ const long nMask312 = -1, nMask322 = -2, nMask332 = -1;
+ long nGrey11, nGrey12, nGrey13;
+ long nGrey21, nGrey22, nGrey23;
+ long nGrey31, nGrey32, nGrey33;
+ std::unique_ptr<long[]> pHMap(new long[nWidth + 2]);
+ std::unique_ptr<long[]> pVMap(new long[nHeight + 2]);
+ long 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<long>(
+ sqrt(static_cast<double>(nSum1 * nSum1 + nSum2 * nSum2)));
+
+ aGrey.SetIndex(~static_cast<sal_uInt8>(std::clamp(nSum1, 0L, 255L)));
+ pWriteAcc->SetPixelOnData(pScanline, nX, aGrey);
+
+ if (nX < (nWidth - 1))
+ {
+ const long nNextX = pHMap[nX + 3];
+
+ nGrey11 = nGrey12;
+ nGrey12 = nGrey13;
+ nGrey13 = pReadAcc->GetPixel(pVMap[nY], nNextX).GetIndex();
+ nGrey21 = nGrey22;
+ nGrey22 = nGrey23;
+ nGrey23 = pReadAcc->GetPixel(pVMap[nY + 1], nNextX).GetIndex();
+ nGrey31 = nGrey32;
+ nGrey32 = nGrey33;
+ nGrey33 = pReadAcc->GetPixel(pVMap[nY + 2], nNextX).GetIndex();
+ }
+ }
+ }
+
+ pHMap.reset();
+ pVMap.reset();
+ pWriteAcc.reset();
+ bRet = true;
+ }
+
+ pReadAcc.reset();
+
+ if (bRet)
+ {
+ const MapMode aMap(aBitmap.GetPrefMapMode());
+ const Size aPrefSize(aBitmap.GetPrefSize());
+
+ aBitmap = aNewBmp;
+
+ aBitmap.SetPrefMapMode(aMap);
+ aBitmap.SetPrefSize(aPrefSize);
+ }
+ }
+ }
+
+ if (bRet)
+ return BitmapEx(aBitmap);
+
+ return BitmapEx();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapSolarizeFilter.cxx b/vcl/source/bitmap/BitmapSolarizeFilter.cxx
new file mode 100644
index 000000000..bd7518b4c
--- /dev/null
+++ b/vcl/source/bitmap/BitmapSolarizeFilter.cxx
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapSolarizeFilter.hxx>
+
+#include <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 long nWidth = pWriteAcc->Width();
+ const long nHeight = pWriteAcc->Height();
+
+ for (long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (long 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 rBitmapEx;
+
+ return BitmapEx();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapSymmetryCheck.cxx b/vcl/source/bitmap/BitmapSymmetryCheck.cxx
new file mode 100644
index 000000000..9abb48086
--- /dev/null
+++ b/vcl/source/bitmap/BitmapSymmetryCheck.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 <BitmapSymmetryCheck.hxx>
+
+BitmapSymmetryCheck::BitmapSymmetryCheck()
+{}
+
+BitmapSymmetryCheck::~BitmapSymmetryCheck()
+{}
+
+bool BitmapSymmetryCheck::check(Bitmap& rBitmap)
+{
+ Bitmap::ScopedReadAccess aReadAccess(rBitmap);
+ return checkImpl(aReadAccess.get());
+}
+
+bool BitmapSymmetryCheck::checkImpl(BitmapReadAccess const * pReadAccess)
+{
+ long nHeight = pReadAccess->Height();
+ long nWidth = pReadAccess->Width();
+
+ long nHeightHalf = nHeight / 2;
+ long nWidthHalf = nWidth / 2;
+
+ bool bHeightEven = (nHeight % 2) == 0;
+ bool bWidthEven = (nWidth % 2) == 0;
+
+ for (long y = 0; y < nHeightHalf; ++y)
+ {
+ Scanline pScanlineRead = pReadAccess->GetScanline( y );
+ Scanline pScanlineRead2 = pReadAccess->GetScanline( nHeight - y - 1 );
+ for (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 (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 (long x = 0; x < nWidthHalf; ++x)
+ {
+ if (pReadAccess->GetPixelFromData(pScanlineRead, x) != pReadAccess->GetPixelFromData(pScanlineRead, nWidth - x - 1))
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapTools.cxx b/vcl/source/bitmap/BitmapTools.cxx
new file mode 100644
index 000000000..3f0226047
--- /dev/null
+++ b/vcl/source/bitmap/BitmapTools.cxx
@@ -0,0 +1,1131 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/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/bitmapaccess.hxx>
+#include <vcl/virdev.hxx>
+#if ENABLE_CAIRO_CANVAS
+#include <cairo.h>
+#endif
+#include <tools/diagnose_ex.h>
+#include <tools/fract.hxx>
+#include <tools/stream.hxx>
+#include <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 (Primitive2DReference 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)
+*/
+BitmapEx CreateFromData( sal_uInt8 const *pData, sal_Int32 nWidth, sal_Int32 nHeight, sal_Int32 nStride, sal_uInt16 nBitCount )
+{
+ assert(nStride >= (nWidth * nBitCount / 8));
+ assert( nBitCount == 1 || nBitCount == 24 || nBitCount == 32);
+ Bitmap aBmp( Size( nWidth, nHeight ), nBitCount );
+
+ BitmapScopedWriteAccess pWrite(aBmp);
+ assert(pWrite.get());
+ if( !pWrite )
+ return BitmapEx();
+ std::unique_ptr<AlphaMask> pAlphaMask;
+ AlphaScopedWriteAccess xMaskAcc;
+ if (nBitCount == 32)
+ {
+ pAlphaMask.reset( new AlphaMask( Size(nWidth, nHeight) ) );
+ xMaskAcc = AlphaScopedWriteAccess(*pAlphaMask);
+ }
+ if (nBitCount == 1)
+ {
+ for( long y = 0; y < nHeight; ++y )
+ {
+ Scanline pScanline = pWrite->GetScanline(y);
+ for (long x = 0; x < nWidth; ++x)
+ {
+ sal_uInt8 const *p = pData + y * nStride / 8;
+ int bitIndex = (y * nStride) % 8;
+ pWrite->SetPixelOnData(pScanline, x, BitmapColor((*p >> bitIndex) & 1));
+ }
+ }
+ }
+ else
+ {
+ for( long y = 0; y < nHeight; ++y )
+ {
+ sal_uInt8 const *p = pData + (y * nStride);
+ Scanline pScanline = pWrite->GetScanline(y);
+ for (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 = pData + (y * nStride) + 3;
+ Scanline pMaskScanLine = xMaskAcc->GetScanline(y);
+ for (long x = 0; x < nWidth; ++x)
+ {
+ xMaskAcc->SetPixelOnData(pMaskScanLine, x, BitmapColor(*p));
+ p += 4;
+ }
+ }
+ }
+ }
+ if (nBitCount == 32)
+ return BitmapEx(aBmp, *pAlphaMask);
+ else
+ return BitmapEx(aBmp);
+}
+
+/** Copy block of image data into the bitmap.
+ Assumes that the Bitmap has been constructed with the desired size.
+*/
+BitmapEx CreateFromData( RawBitmap&& rawBitmap )
+{
+ auto nBitCount = rawBitmap.GetBitCount();
+ assert( nBitCount == 24 || nBitCount == 32);
+ Bitmap aBmp( rawBitmap.maSize, nBitCount );
+
+ BitmapScopedWriteAccess pWrite(aBmp);
+ assert(pWrite.get());
+ if( !pWrite )
+ return BitmapEx();
+ std::unique_ptr<AlphaMask> pAlphaMask;
+ AlphaScopedWriteAccess xMaskAcc;
+ if (nBitCount == 32)
+ {
+ pAlphaMask.reset( new AlphaMask( rawBitmap.maSize ) );
+ xMaskAcc = AlphaScopedWriteAccess(*pAlphaMask);
+ }
+
+ auto nHeight = rawBitmap.maSize.getHeight();
+ auto nWidth = rawBitmap.maSize.getWidth();
+ auto nStride = nWidth * nBitCount / 8;
+ for( long y = 0; y < nHeight; ++y )
+ {
+ sal_uInt8 const *p = rawBitmap.mpData.get() + (y * nStride);
+ Scanline pScanline = pWrite->GetScanline(y);
+ for (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 (long x = 0; x < nWidth; ++x)
+ {
+ xMaskAcc->SetPixelOnData(pMaskScanLine, x, BitmapColor(*p));
+ p += 4;
+ }
+ }
+ }
+ if (nBitCount == 32)
+ return BitmapEx(aBmp, *pAlphaMask);
+ else
+ return BitmapEx(aBmp);
+}
+
+#if ENABLE_CAIRO_CANVAS
+BitmapEx* CreateFromCairoSurface(Size aSize, cairo_surface_t * pSurface)
+{
+ // FIXME: if we could teach VCL/ about cairo handles, life could
+ // be significantly better here perhaps.
+
+#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 12, 0)
+ cairo_surface_t *pPixels = cairo_surface_create_similar_image(pSurface,
+#else
+ cairo_surface_t *pPixels = cairo_image_surface_create(
+#endif
+ CAIRO_FORMAT_ARGB32, aSize.Width(), aSize.Height());
+ cairo_t *pCairo = cairo_create( pPixels );
+ if( !pPixels || !pCairo || cairo_status(pCairo) != CAIRO_STATUS_SUCCESS )
+ return nullptr;
+
+ // suck ourselves from the X server to this buffer so then we can fiddle with
+ // Alpha to turn it into the ultra-lame vcl required format and then push it
+ // all back again later at vast expense [ urgh ]
+ cairo_set_source_surface( pCairo, pSurface, 0, 0 );
+ cairo_set_operator( pCairo, CAIRO_OPERATOR_SOURCE );
+ cairo_paint( pCairo );
+
+ ::Bitmap aRGB( aSize, 24 );
+ ::AlphaMask aMask( aSize );
+
+ BitmapScopedWriteAccess pRGBWrite(aRGB);
+ assert(pRGBWrite);
+ if (!pRGBWrite)
+ return nullptr;
+
+ AlphaScopedWriteAccess pMaskWrite(aMask);
+ assert(pMaskWrite);
+ if (!pMaskWrite)
+ return nullptr;
+
+ cairo_surface_flush(pPixels);
+ unsigned char *pSrc = cairo_image_surface_get_data( pPixels );
+ unsigned int nStride = cairo_image_surface_get_stride( pPixels );
+ vcl::bitmap::lookup_table unpremultiply_table = vcl::bitmap::get_unpremultiply_table();
+ for( long y = 0; y < aSize.Height(); y++ )
+ {
+ sal_uInt32 *pPix = reinterpret_cast<sal_uInt32 *>(pSrc + nStride * y);
+ for( 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
+ nR = unpremultiply_table[nAlpha][nR];
+ nG = unpremultiply_table[nAlpha][nG];
+ nB = unpremultiply_table[nAlpha][nB];
+ }
+ pRGBWrite->SetPixel( y, x, BitmapColor( nR, nG, nB ) );
+ pMaskWrite->SetPixelIndex( y, x, 255 - nAlpha );
+ pPix++;
+ }
+ }
+
+ // ignore potential errors above. will get caller a
+ // uniformly white bitmap, but not that there would
+ // be error handling in calling code ...
+ ::BitmapEx *pBitmapEx = new ::BitmapEx( aRGB, aMask );
+
+ cairo_destroy( pCairo );
+ cairo_surface_destroy( pPixels );
+ return pBitmapEx;
+}
+#endif
+
+BitmapEx CanvasTransformBitmap( const BitmapEx& rBitmap,
+ const ::basegfx::B2DHomMatrix& rTransform,
+ ::basegfx::B2DRectangle const & rDestRect,
+ ::basegfx::B2DHomMatrix const & rLocalTransform )
+{
+ const Size aBmpSize( rBitmap.GetSizePixel() );
+ Bitmap aSrcBitmap( rBitmap.GetBitmap() );
+ Bitmap aSrcAlpha;
+
+ // differentiate mask and alpha channel (on-off
+ // vs. multi-level transparency)
+ if( rBitmap.IsTransparent() )
+ {
+ if( rBitmap.IsAlpha() )
+ aSrcAlpha = rBitmap.GetAlpha().GetBitmap();
+ else
+ aSrcAlpha = rBitmap.GetMask();
+ }
+
+ Bitmap::ScopedReadAccess pReadAccess( aSrcBitmap );
+ Bitmap::ScopedReadAccess pAlphaReadAccess( rBitmap.IsTransparent() ?
+ aSrcAlpha.AcquireReadAccess() :
+ nullptr,
+ aSrcAlpha );
+
+ if( pReadAccess.get() == nullptr ||
+ (pAlphaReadAccess.get() == nullptr && rBitmap.IsTransparent()) )
+ {
+ // 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.IsTransparent() )
+ {
+ 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
+ {
+ // mask transparency - determine used palette colors
+ const BitmapColor& rCol0( pAlphaReadAccess->GetPaletteColor( 0 ) );
+ const BitmapColor& rCol1( pAlphaReadAccess->GetPaletteColor( 1 ) );
+
+ // shortcut for true luminance calculation
+ // (assumes that palette is grey-level)
+ aAlphaMap[0] = rCol0.GetRed();
+ aAlphaMap[1] = rCol1.GetRed();
+ }
+ }
+ // 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.GetBitCount(), &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( long y=0; y<aDestBmpSize.Height(); ++y )
+ {
+ // differentiate mask and alpha channel (on-off
+ // vs. multi-level transparency)
+ if( rBitmap.IsTransparent() )
+ {
+ Scanline pScan = pWriteAccess->GetScanline( y );
+ Scanline pScanAlpha = pAlphaWriteAccess->GetScanline( y );
+ // Handling alpha and mask just the same...
+ for( long x=0; x<aDestBmpSize.Width(); ++x )
+ {
+ ::basegfx::B2DPoint aPoint(x,y);
+ aPoint *= aTransform;
+
+ const int nSrcX( ::basegfx::fround( aPoint.getX() ) );
+ const int nSrcY( ::basegfx::fround( aPoint.getY() ) );
+ if( nSrcX < 0 || nSrcX >= aBmpSize.Width() ||
+ nSrcY < 0 || nSrcY >= aBmpSize.Height() )
+ {
+ pAlphaWriteAccess->SetPixelOnData( pScanAlpha, x, BitmapColor(255) );
+ }
+ else
+ {
+ const sal_uInt8 cAlphaIdx = pAlphaReadAccess->GetPixelIndex( nSrcY, nSrcX );
+ pAlphaWriteAccess->SetPixelOnData( pScanAlpha, x, BitmapColor(aAlphaMap[ cAlphaIdx ]) );
+ pWriteAccess->SetPixelOnData( pScan, x, pReadAccess->GetPixel( nSrcY, nSrcX ) );
+ }
+ }
+ }
+ else
+ {
+ Scanline pScan = pWriteAccess->GetScanline( y );
+ Scanline pScanAlpha = pAlphaWriteAccess->GetScanline( y );
+ for( long x=0; x<aDestBmpSize.Width(); ++x )
+ {
+ ::basegfx::B2DPoint aPoint(x,y);
+ aPoint *= aTransform;
+
+ const int nSrcX( ::basegfx::fround( aPoint.getX() ) );
+ const int nSrcY( ::basegfx::fround( aPoint.getY() ) );
+ if( nSrcX < 0 || nSrcX >= aBmpSize.Width() ||
+ nSrcY < 0 || nSrcY >= aBmpSize.Height() )
+ {
+ pAlphaWriteAccess->SetPixelOnData( pScanAlpha, x, BitmapColor(255) );
+ }
+ else
+ {
+ pAlphaWriteAccess->SetPixelOnData( pScanAlpha, x, BitmapColor(0) );
+ pWriteAccess->SetPixelOnData( pScan, x, pReadAccess->GetPixel( nSrcY,
+ nSrcX ) );
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ // TODO(E2): Error handling!
+ ENSURE_OR_THROW( false,
+ "transformBitmap(): could not access bitmap" );
+ }
+ }
+
+ return BitmapEx(aDstBitmap, AlphaMask(aDstAlpha));
+}
+
+
+void DrawAlphaBitmapAndAlphaGradient(BitmapEx & rBitmapEx, bool bFixedTransparence, float fTransparence, AlphaMask & rNewMask)
+{
+ // mix existing and new alpha mask
+ AlphaMask aOldMask;
+
+ if(rBitmapEx.IsAlpha())
+ {
+ aOldMask = rBitmapEx.GetAlpha();
+ }
+ else if(TransparentType::Bitmap == rBitmapEx.GetTransparentType())
+ {
+ aOldMask = rBitmapEx.GetMask();
+ }
+ else if(TransparentType::Color == rBitmapEx.GetTransparentType())
+ {
+ aOldMask = rBitmapEx.GetBitmap().CreateMask(rBitmapEx.GetTransparentColor());
+ }
+
+ {
+ AlphaScopedWriteAccess pOld(aOldMask);
+
+ assert(pOld && "Got no access to old alpha mask (!)");
+
+ const double fFactor(1.0 / 255.0);
+
+ if(bFixedTransparence)
+ {
+ const double fOpNew(1.0 - fTransparence);
+
+ for(long y(0); y < pOld->Height(); y++)
+ {
+ Scanline pScanline = pOld->GetScanline( y );
+ for(long x(0); x < pOld->Width(); x++)
+ {
+ const double fOpOld(1.0 - (pOld->GetIndexFromData(pScanline, x) * fFactor));
+ const sal_uInt8 aCol(basegfx::fround((1.0 - (fOpOld * fOpNew)) * 255.0));
+
+ pOld->SetPixelOnData(pScanline, x, BitmapColor(aCol));
+ }
+ }
+ }
+ else
+ {
+ AlphaMask::ScopedReadAccess pNew(rNewMask);
+
+ assert(pNew && "Got no access to new alpha mask (!)");
+
+ assert(pOld->Width() == pNew->Width() && pOld->Height() == pNew->Height() &&
+ "Alpha masks have different sizes (!)");
+
+ for(long y(0); y < pOld->Height(); y++)
+ {
+ Scanline pScanline = pOld->GetScanline( y );
+ for(long x(0); x < pOld->Width(); x++)
+ {
+ const double fOpOld(1.0 - (pOld->GetIndexFromData(pScanline, x) * fFactor));
+ const double fOpNew(1.0 - (pNew->GetIndexFromData(pScanline, x) * fFactor));
+ const sal_uInt8 aCol(basegfx::fround((1.0 - (fOpOld * fOpNew)) * 255.0));
+
+ pOld->SetPixelOnData(pScanline, x, BitmapColor(aCol));
+ }
+ }
+ }
+
+ }
+
+ // apply combined bitmap as mask
+ rBitmapEx = BitmapEx(rBitmapEx.GetBitmap(), aOldMask);
+}
+
+
+void DrawAndClipBitmap(const Point& rPos, const Size& rSize, const BitmapEx& rBitmap, BitmapEx & aBmpEx, basegfx::B2DPolyPolygon const & rClipPath)
+{
+ ScopedVclPtrInstance< VirtualDevice > pVDev;
+ MapMode aMapMode( MapUnit::Map100thMM );
+ aMapMode.SetOrigin( Point( -rPos.X(), -rPos.Y() ) );
+ const Size aOutputSizePixel( pVDev->LogicToPixel( rSize, aMapMode ) );
+ const Size aSizePixel( rBitmap.GetSizePixel() );
+ if ( aOutputSizePixel.Width() && aOutputSizePixel.Height() )
+ {
+ aMapMode.SetScaleX( Fraction( aSizePixel.Width(), aOutputSizePixel.Width() ) );
+ aMapMode.SetScaleY( Fraction( aSizePixel.Height(), aOutputSizePixel.Height() ) );
+ }
+ pVDev->SetMapMode( aMapMode );
+ pVDev->SetOutputSizePixel( aSizePixel );
+ pVDev->SetFillColor( COL_BLACK );
+ const tools::PolyPolygon aClip( rClipPath );
+ pVDev->DrawPolyPolygon( aClip );
+
+ // #i50672# Extract whole VDev content (to match size of rBitmap)
+ pVDev->EnableMapMode( false );
+ const Bitmap aVDevMask(pVDev->GetBitmap(Point(), aSizePixel));
+
+ if(aBmpEx.IsTransparent())
+ {
+ // bitmap already uses a Mask or Alpha, we need to blend that with
+ // the new masking in pVDev
+ if(aBmpEx.IsAlpha())
+ {
+ // need to blend in AlphaMask quality (8Bit)
+ AlphaMask fromVDev(aVDevMask);
+ AlphaMask fromBmpEx(aBmpEx.GetAlpha());
+ AlphaMask::ScopedReadAccess pR(fromVDev);
+ AlphaScopedWriteAccess pW(fromBmpEx);
+
+ if(pR && pW)
+ {
+ const long nWidth(std::min(pR->Width(), pW->Width()));
+ const long nHeight(std::min(pR->Height(), pW->Height()));
+
+ for(long nY(0); nY < nHeight; nY++)
+ {
+ Scanline pScanlineR = pR->GetScanline( nY );
+ Scanline pScanlineW = pW->GetScanline( nY );
+ for(long nX(0); nX < nWidth; nX++)
+ {
+ const sal_uInt8 nIndR(pR->GetIndexFromData(pScanlineR, nX));
+ const sal_uInt8 nIndW(pW->GetIndexFromData(pScanlineW, nX));
+
+ // these values represent transparency (0 == no, 255 == fully transparent),
+ // so to blend these we have to multiply the inverse (opacity)
+ // and re-invert the result to transparence
+ const sal_uInt8 nCombined(0x00ff - (((0x00ff - nIndR) * (0x00ff - nIndW)) >> 8));
+
+ pW->SetPixelOnData(pScanlineW, nX, BitmapColor(nCombined));
+ }
+ }
+ }
+
+ pR.reset();
+ pW.reset();
+ aBmpEx = BitmapEx(aBmpEx.GetBitmap(), fromBmpEx);
+ }
+ else
+ {
+ // need to blend in Mask quality (1Bit)
+ Bitmap aMask(aVDevMask.CreateMask(COL_WHITE));
+
+ if ( rBitmap.GetTransparentColor() == COL_WHITE )
+ {
+ aMask.CombineSimple( rBitmap.GetMask(), BmpCombine::Or );
+ }
+ else
+ {
+ aMask.CombineSimple( rBitmap.GetMask(), BmpCombine::And );
+ }
+
+ aBmpEx = BitmapEx( rBitmap.GetBitmap(), aMask );
+ }
+ }
+ else
+ {
+ // no mask yet, create and add new mask. For better quality, use Alpha,
+ // this allows the drawn mask being processed with AntiAliasing (AAed)
+ aBmpEx = BitmapEx(rBitmap.GetBitmap(), aVDevMask);
+ }
+}
+
+
+css::uno::Sequence< sal_Int8 > GetMaskDIB(BitmapEx const & aBmpEx)
+{
+ if ( aBmpEx.IsAlpha() )
+ {
+ SvMemoryStream aMem;
+ WriteDIB(aBmpEx.GetAlpha().GetBitmap(), aMem, false, true);
+ return css::uno::Sequence< sal_Int8 >( static_cast<sal_Int8 const *>(aMem.GetData()), aMem.Tell() );
+ }
+ else if ( aBmpEx.IsTransparent() )
+ {
+ SvMemoryStream aMem;
+ WriteDIB(aBmpEx.GetMask(), 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, long nY, const long nWidth, unsigned char* data, long nOff )
+{
+ bool bIsAlpha = false;
+ long nX;
+ int nAlpha;
+ Scanline pReadScan;
+
+ nOff += 3;
+
+ switch( pAlphaReadAcc->GetScanlineFormat() )
+ {
+ case ScanlineFormat::N8BitTcMask:
+ pReadScan = pAlphaReadAcc->GetScanline( nY );
+ for( nX = 0; nX < nWidth; nX++ )
+ {
+ nAlpha = data[ nOff ] = 255 - ( *pReadScan++ );
+ if( nAlpha != 255 )
+ bIsAlpha = true;
+ nOff += 4;
+ }
+ break;
+ case ScanlineFormat::N8BitPal:
+ pReadScan = pAlphaReadAcc->GetScanline( nY );
+ for( nX = 0; nX < nWidth; nX++ )
+ {
+ BitmapColor const& rColor(
+ pAlphaReadAcc->GetPaletteColor(*pReadScan));
+ pReadScan++;
+ nAlpha = data[ nOff ] = 255 - rColor.GetIndex();
+ if( nAlpha != 255 )
+ bIsAlpha = true;
+ nOff += 4;
+ }
+ break;
+ default:
+ SAL_INFO( "canvas.cairo", "fallback to GetColor for alpha - slow, format: " << static_cast<int>(pAlphaReadAcc->GetScanlineFormat()) );
+ for( nX = 0; nX < nWidth; nX++ )
+ {
+ nAlpha = data[ nOff ] = 255 - pAlphaReadAcc->GetColor( nY, nX ).GetIndex();
+ if( nAlpha != 255 )
+ bIsAlpha = true;
+ nOff += 4;
+ }
+ }
+
+ return bIsAlpha;
+}
+
+
+
+/**
+ * @param data will be filled with alpha data, if xBitmap is alpha/transparent image
+ * @param bHasAlpha will be set to true if resulting surface has alpha
+ **/
+void CanvasCairoExtractBitmapData( BitmapEx const & aBmpEx, Bitmap & aBitmap, unsigned char*& data, bool& bHasAlpha, long& rnWidth, long& rnHeight )
+{
+ AlphaMask aAlpha = aBmpEx.GetAlpha();
+
+ ::BitmapReadAccess* pBitmapReadAcc = aBitmap.AcquireReadAccess();
+ ::BitmapReadAccess* pAlphaReadAcc = nullptr;
+ const long nWidth = rnWidth = pBitmapReadAcc->Width();
+ const long nHeight = rnHeight = pBitmapReadAcc->Height();
+ long nX, nY;
+ bool bIsAlpha = false;
+
+ if( aBmpEx.IsTransparent() || aBmpEx.IsAlpha() )
+ pAlphaReadAcc = aAlpha.AcquireReadAccess();
+
+ data = static_cast<unsigned char*>(malloc( nWidth*nHeight*4 ));
+
+ long nOff = 0;
+ ::Color aColor;
+ unsigned int nAlpha = 255;
+
+ vcl::bitmap::lookup_table premultiply_table = vcl::bitmap::get_premultiply_table();
+ for( nY = 0; nY < nHeight; nY++ )
+ {
+ ::Scanline pReadScan;
+
+ switch( pBitmapReadAcc->GetScanlineFormat() )
+ {
+ case ScanlineFormat::N8BitPal:
+ pReadScan = pBitmapReadAcc->GetScanline( nY );
+ if( pAlphaReadAcc )
+ if( readAlpha( pAlphaReadAcc, nY, nWidth, data, nOff ) )
+ bIsAlpha = true;
+
+ for( nX = 0; nX < nWidth; nX++ )
+ {
+#ifdef OSL_BIGENDIAN
+ if( pAlphaReadAcc )
+ nAlpha = data[ nOff++ ];
+ else
+ nAlpha = data[ nOff++ ] = 255;
+#else
+ if( pAlphaReadAcc )
+ nAlpha = data[ nOff + 3 ];
+ else
+ nAlpha = data[ nOff + 3 ] = 255;
+#endif
+ aColor = pBitmapReadAcc->GetPaletteColor(*pReadScan++);
+
+#ifdef OSL_BIGENDIAN
+ data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetRed()];
+ data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetGreen()];
+ data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetBlue()];
+#else
+ data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetBlue()];
+ data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetGreen()];
+ data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetRed()];
+ nOff++;
+#endif
+ }
+ break;
+ case ScanlineFormat::N24BitTcBgr:
+ pReadScan = pBitmapReadAcc->GetScanline( nY );
+ if( pAlphaReadAcc )
+ if( readAlpha( pAlphaReadAcc, nY, nWidth, data, nOff ) )
+ bIsAlpha = true;
+
+ for( nX = 0; nX < nWidth; nX++ )
+ {
+#ifdef OSL_BIGENDIAN
+ if( pAlphaReadAcc )
+ nAlpha = data[ nOff ];
+ else
+ nAlpha = data[ nOff ] = 255;
+ data[ nOff + 3 ] = premultiply_table[nAlpha][*pReadScan++];
+ data[ nOff + 2 ] = premultiply_table[nAlpha][*pReadScan++];
+ data[ nOff + 1 ] = premultiply_table[nAlpha][*pReadScan++];
+ nOff += 4;
+#else
+ if( pAlphaReadAcc )
+ nAlpha = data[ nOff + 3 ];
+ else
+ nAlpha = data[ nOff + 3 ] = 255;
+ data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++];
+ data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++];
+ data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++];
+ nOff++;
+#endif
+ }
+ break;
+ case ScanlineFormat::N24BitTcRgb:
+ pReadScan = pBitmapReadAcc->GetScanline( nY );
+ if( pAlphaReadAcc )
+ if( readAlpha( pAlphaReadAcc, nY, nWidth, data, nOff ) )
+ bIsAlpha = true;
+
+ for( nX = 0; nX < nWidth; nX++ )
+ {
+#ifdef OSL_BIGENDIAN
+ if( pAlphaReadAcc )
+ nAlpha = data[ nOff++ ];
+ else
+ nAlpha = data[ nOff++ ] = 255;
+ data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++];
+ data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++];
+ data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++];
+#else
+ if( pAlphaReadAcc )
+ nAlpha = data[ nOff + 3 ];
+ else
+ nAlpha = data[ nOff + 3 ] = 255;
+ data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 2 ]];
+ data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 1 ]];
+ data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 0 ]];
+ pReadScan += 3;
+ nOff++;
+#endif
+ }
+ break;
+ case ScanlineFormat::N32BitTcBgra:
+ pReadScan = pBitmapReadAcc->GetScanline( nY );
+ if( pAlphaReadAcc )
+ if( readAlpha( pAlphaReadAcc, nY, nWidth, data, nOff ) )
+ bIsAlpha = true;
+
+ for( nX = 0; nX < nWidth; nX++ )
+ {
+#ifdef OSL_BIGENDIAN
+ if( pAlphaReadAcc )
+ nAlpha = data[ nOff++ ];
+ else
+ nAlpha = data[ nOff++ ] = 255;
+ data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 2 ]];
+ data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 1 ]];
+ data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 0 ]];
+ pReadScan += 4;
+#else
+ if( pAlphaReadAcc )
+ nAlpha = data[ nOff + 3 ];
+ else
+ nAlpha = data[ nOff + 3 ] = 255;
+ data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++];
+ data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++];
+ data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++];
+ pReadScan++;
+ nOff++;
+#endif
+ }
+ break;
+ case ScanlineFormat::N32BitTcRgba:
+ pReadScan = pBitmapReadAcc->GetScanline( nY );
+ if( pAlphaReadAcc )
+ if( readAlpha( pAlphaReadAcc, nY, nWidth, data, nOff ) )
+ bIsAlpha = true;
+
+ for( nX = 0; nX < nWidth; nX++ )
+ {
+#ifdef OSL_BIGENDIAN
+ if( pAlphaReadAcc )
+ nAlpha = data[ nOff ++ ];
+ else
+ nAlpha = data[ nOff ++ ] = 255;
+ data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++];
+ data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++];
+ data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++];
+ pReadScan++;
+#else
+ if( pAlphaReadAcc )
+ nAlpha = data[ nOff + 3 ];
+ else
+ nAlpha = data[ nOff + 3 ] = 255;
+ data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 2 ]];
+ data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 1 ]];
+ data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 0 ]];
+ pReadScan += 4;
+ nOff++;
+#endif
+ }
+ break;
+ default:
+ SAL_INFO( "canvas.cairo", "fallback to GetColor - slow, format: " << static_cast<int>(pBitmapReadAcc->GetScanlineFormat()) );
+
+ if( pAlphaReadAcc )
+ if( readAlpha( pAlphaReadAcc, nY, nWidth, data, nOff ) )
+ bIsAlpha = true;
+
+ for( nX = 0; nX < nWidth; nX++ )
+ {
+ aColor = pBitmapReadAcc->GetColor( nY, nX );
+
+ // cairo need premultiplied color values
+ // TODO(rodo) handle endianness
+#ifdef OSL_BIGENDIAN
+ if( pAlphaReadAcc )
+ nAlpha = data[ nOff++ ];
+ else
+ nAlpha = data[ nOff++ ] = 255;
+ data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetRed()];
+ data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetGreen()];
+ data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetBlue()];
+#else
+ if( pAlphaReadAcc )
+ nAlpha = data[ nOff + 3 ];
+ else
+ nAlpha = data[ nOff + 3 ] = 255;
+ data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetBlue()];
+ data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetGreen()];
+ data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetRed()];
+ nOff ++;
+#endif
+ }
+ }
+ }
+
+ ::Bitmap::ReleaseAccess( pBitmapReadAcc );
+ if( pAlphaReadAcc )
+ aAlpha.ReleaseAccess( pAlphaReadAcc );
+
+ bHasAlpha = bIsAlpha;
+
+}
+
+ uno::Sequence< sal_Int8 > CanvasExtractBitmapData(BitmapEx const & rBitmapEx, const geometry::IntegerRectangle2D& rect)
+ {
+ Bitmap aBitmap( rBitmapEx.GetBitmap() );
+ Bitmap aAlpha( rBitmapEx.GetAlpha().GetBitmap() );
+
+ Bitmap::ScopedReadAccess pReadAccess( aBitmap );
+ Bitmap::ScopedReadAccess pAlphaReadAccess( aAlpha.IsEmpty() ?
+ nullptr : aAlpha.AcquireReadAccess(),
+ aAlpha );
+
+ assert( pReadAccess );
+
+ // TODO(F1): Support more formats.
+ const Size aBmpSize( aBitmap.GetSizePixel() );
+
+ // for the time being, always return as BGRA
+ uno::Sequence< sal_Int8 > aRes( 4*aBmpSize.Width()*aBmpSize.Height() );
+ sal_Int8* pRes = aRes.getArray();
+
+ int nCurrPos(0);
+ for( long y=rect.Y1;
+ y<aBmpSize.Height() && y<rect.Y2;
+ ++y )
+ {
+ if( pAlphaReadAccess.get() != nullptr )
+ {
+ Scanline pScanlineReadAlpha = pAlphaReadAccess->GetScanline( y );
+ for( long x=rect.X1;
+ x<aBmpSize.Width() && x<rect.X2;
+ ++x )
+ {
+ pRes[ nCurrPos++ ] = pReadAccess->GetColor( y, x ).GetRed();
+ pRes[ nCurrPos++ ] = pReadAccess->GetColor( y, x ).GetGreen();
+ pRes[ nCurrPos++ ] = pReadAccess->GetColor( y, x ).GetBlue();
+ pRes[ nCurrPos++ ] = pAlphaReadAccess->GetIndexFromData( pScanlineReadAlpha, x );
+ }
+ }
+ else
+ {
+ for( 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), 1, &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.IsTransparent())
+ {
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ if(8 == aBitmap.GetSizePixel().Width() && 8 == aBitmap.GetSizePixel().Height())
+ {
+ if(2 == aBitmap.GetColorCount())
+ {
+ BitmapReadAccess* pRead = aBitmap.AcquireReadAccess();
+
+ if(pRead)
+ {
+ if(pRead->HasPalette() && 2 == pRead->GetPaletteEntryCount())
+ {
+ const BitmapPalette& rPalette = pRead->GetPalette();
+
+ // #i123564# background and foreground were exchanged; of course
+ // rPalette[0] is the background color
+ o_rFront = rPalette[1];
+ o_rBack = rPalette[0];
+
+ bRet = true;
+ }
+
+ Bitmap::ReleaseAccess(pRead);
+ }
+ }
+ }
+ }
+
+ return bRet;
+ }
+
+ 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;
+ }
+
+ lookup_table get_unpremultiply_table()
+ {
+ static bool inited;
+ static sal_uInt8 unpremultiply_table[256][256];
+
+ if (!inited)
+ {
+ for (int a = 0; a < 256; ++a)
+ for (int c = 0; c < 256; ++c)
+ unpremultiply_table[a][c] = unpremultiply(c, a);
+ inited = true;
+ }
+
+ return unpremultiply_table;
+ }
+
+ lookup_table get_premultiply_table()
+ {
+ static bool inited;
+ static sal_uInt8 premultiply_table[256][256];
+
+ if (!inited)
+ {
+ for (int a = 0; a < 256; ++a)
+ for (int c = 0; c < 256; ++c)
+ premultiply_table[a][c] = premultiply(c, a);
+ inited = true;
+ }
+
+ return premultiply_table;
+ }
+
+bool convertBitmap32To24Plus8(BitmapEx const & rInput, BitmapEx & rResult)
+{
+ Bitmap aBitmap(rInput.GetBitmap());
+ if (aBitmap.GetBitCount() != 32)
+ return false;
+
+ Size aSize = aBitmap.GetSizePixel();
+ Bitmap aResultBitmap(aSize, 24);
+ AlphaMask aResultAlpha(aSize);
+ {
+ BitmapScopedWriteAccess pResultBitmapAccess(aResultBitmap);
+ AlphaScopedWriteAccess pResultAlphaAccess(aResultAlpha);
+
+ Bitmap::ScopedReadAccess pReadAccess(aBitmap);
+
+ for (long nY = 0; nY < aSize.Height(); ++nY)
+ {
+ Scanline aResultScan = pResultBitmapAccess->GetScanline(nY);
+ Scanline aResultScanAlpha = pResultAlphaAccess->GetScanline(nY);
+
+ Scanline aReadScan = pReadAccess->GetScanline(nY);
+
+ for (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.IsTransparent())
+ rResult = BitmapEx(aResultBitmap, rInput.GetAlpha());
+ else
+ rResult = BitmapEx(aResultBitmap, aResultAlpha);
+ return true;
+}
+
+} // end vcl::bitmap
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/Octree.cxx b/vcl/source/bitmap/Octree.cxx
new file mode 100644
index 000000000..44f911ef0
--- /dev/null
+++ b/vcl/source/bitmap/Octree.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 <vcl/bitmapaccess.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)
+ , mpColor(nullptr)
+ , mnPalIndex(0)
+{
+ const BitmapReadAccess* pAccess = &rReadAcc;
+ sal_uLong nMax(nColors);
+
+ if (!!*pAccess)
+ {
+ const long nWidth = pAccess->Width();
+ const long nHeight = pAccess->Height();
+
+ if (pAccess->HasPalette())
+ {
+ for (long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pAccess->GetScanline(nY);
+ for (long nX = 0; nX < nWidth; nX++)
+ {
+ mpColor = &pAccess->GetPaletteColor(pAccess->GetIndexFromData(pScanline, nX));
+ mnLevel = 0;
+ add(pTree);
+
+ while (mnLeafCount > nMax)
+ reduce();
+ }
+ }
+ }
+ else
+ {
+ BitmapColor aColor;
+
+ mpColor = &aColor;
+
+ for (long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pAccess->GetScanline(nY);
+ for (long nX = 0; nX < nWidth; nX++)
+ {
+ aColor = pAccess->GetPixelFromData(pScanline, nX);
+ mnLevel = 0;
+ add(pTree);
+
+ while (mnLeafCount > nMax)
+ reduce();
+ }
+ }
+ }
+ }
+}
+
+Octree::~Octree() {}
+
+void Octree::add(std::unique_ptr<OctreeNode>& rpNode)
+{
+ // 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 += mpColor->GetRed();
+ rpNode->nGreen += mpColor->GetGreen();
+ rpNode->nBlue += mpColor->GetBlue();
+ }
+ else
+ {
+ const sal_uLong nShift = 7 - mnLevel;
+ const sal_uInt8 cMask = 0x80 >> mnLevel;
+ const sal_uLong nIndex = (((mpColor->GetRed() & cMask) >> nShift) << 2)
+ | (((mpColor->GetGreen() & cMask) >> nShift) << 1)
+ | ((mpColor->GetBlue() & cMask) >> nShift);
+
+ mnLevel++;
+ add(rpNode->pChild[nIndex]);
+ }
+}
+
+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)
+{
+ if (pNode->bLeaf)
+ mnPalIndex = pNode->nPalIndex;
+ else
+ {
+ const sal_uLong nShift = 7 - mnLevel;
+ const sal_uInt8 cMask = 0x80 >> mnLevel;
+ mnLevel++;
+ const sal_uLong nIndex = (((mpColor->GetRed() & cMask) >> nShift) << 2)
+ | (((mpColor->GetGreen() & cMask) >> nShift) << 1)
+ | ((mpColor->GetBlue() & cMask) >> nShift);
+
+ GetPalIndex(pNode->pChild[nIndex].get());
+ }
+}
+
+const BitmapPalette& Octree::GetPalette()
+{
+ maPalette.SetEntryCount(sal_uInt16(mnLeafCount));
+ mnPalIndex = 0;
+ CreatePalette(pTree.get());
+ return maPalette;
+}
+
+sal_uInt16 Octree::GetBestPaletteIndex(const BitmapColor& rColor)
+{
+ mpColor = &rColor;
+ mnPalIndex = 65535;
+ mnLevel = 0;
+ GetPalIndex(pTree.get());
+ 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 long x = 1 << gnBits;
+ const long x2 = x >> 1;
+ sal_uLong r, g, b;
+ long rxx, gxx, bxx;
+
+ ImplCreateBuffers();
+
+ for (int nIndex = 0; nIndex < nColors; nIndex++)
+ {
+ const BitmapColor& rColor = rPal[static_cast<sal_uInt16>(nIndex)];
+ const long cRed = rColor.GetRed();
+ const long cGreen = rColor.GetGreen();
+ const long cBlue = rColor.GetBlue();
+
+ long rdist = cRed - x2;
+ long gdist = cGreen - x2;
+ long bdist = cBlue - x2;
+ rdist = rdist * rdist + gdist * gdist + bdist * bdist;
+
+ const long crinc = (xsqr - (cRed << gnBits)) << 1;
+ const long cginc = (xsqr - (cGreen << gnBits)) << 1;
+ const 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<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/bitmap.cxx b/vcl/source/bitmap/bitmap.cxx
new file mode 100644
index 000000000..af5a6f440
--- /dev/null
+++ b/vcl/source/bitmap/bitmap.cxx
@@ -0,0 +1,892 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/bitmap.hxx>
+#include <vcl/bitmapaccess.hxx>
+#include <vcl/outdev.hxx>
+
+#include <svdata.hxx>
+#include <salinst.hxx>
+#include <salbmp.hxx>
+#include <bitmapwriteaccess.hxx>
+
+#include <algorithm>
+#include <memory>
+
+Bitmap::Bitmap()
+{
+}
+
+Bitmap::Bitmap(const Bitmap& rBitmap)
+ : mxSalBmp(rBitmap.mxSalBmp)
+ , maPrefMapMode(rBitmap.maPrefMapMode)
+ , maPrefSize(rBitmap.maPrefSize)
+{
+}
+
+Bitmap::Bitmap(std::shared_ptr<SalBitmap> const & pSalBitmap)
+ : mxSalBmp(pSalBitmap)
+ , maPrefMapMode(MapMode(MapUnit::MapPixel))
+ , maPrefSize(mxSalBmp->GetSize())
+{
+}
+
+Bitmap::Bitmap( const Size& rSizePixel, sal_uInt16 nBitCount, const BitmapPalette* pPal )
+{
+ if (rSizePixel.Width() && rSizePixel.Height())
+ {
+ BitmapPalette aPal;
+ BitmapPalette* pRealPal = nullptr;
+
+ if( nBitCount <= 8 )
+ {
+ if( !pPal )
+ {
+ if( 1 == nBitCount )
+ {
+ aPal.SetEntryCount( 2 );
+ aPal[ 0 ] = COL_BLACK;
+ aPal[ 1 ] = COL_WHITE;
+ }
+ else if( ( 4 == nBitCount ) || ( 8 == nBitCount ) )
+ {
+ aPal.SetEntryCount( 1 << nBitCount );
+ aPal[ 0 ] = COL_BLACK;
+ aPal[ 1 ] = COL_BLUE;
+ aPal[ 2 ] = COL_GREEN;
+ aPal[ 3 ] = COL_CYAN;
+ aPal[ 4 ] = COL_RED;
+ aPal[ 5 ] = COL_MAGENTA;
+ aPal[ 6 ] = COL_BROWN;
+ aPal[ 7 ] = COL_GRAY;
+ aPal[ 8 ] = COL_LIGHTGRAY;
+ aPal[ 9 ] = COL_LIGHTBLUE;
+ aPal[ 10 ] = COL_LIGHTGREEN;
+ aPal[ 11 ] = COL_LIGHTCYAN;
+ aPal[ 12 ] = COL_LIGHTRED;
+ aPal[ 13 ] = COL_LIGHTMAGENTA;
+ aPal[ 14 ] = COL_YELLOW;
+ aPal[ 15 ] = COL_WHITE;
+
+ // Create dither palette
+ if( 8 == nBitCount )
+ {
+ sal_uInt16 nActCol = 16;
+
+ for( sal_uInt16 nB = 0; nB < 256; nB += 51 )
+ for( sal_uInt16 nG = 0; nG < 256; nG += 51 )
+ for( sal_uInt16 nR = 0; nR < 256; nR += 51 )
+ aPal[ nActCol++ ] = BitmapColor( static_cast<sal_uInt8>(nR), static_cast<sal_uInt8>(nG), static_cast<sal_uInt8>(nB) );
+
+ // Set standard Office colors
+ aPal[ nActCol++ ] = BitmapColor( 0, 184, 255 );
+ }
+ }
+ }
+ else
+ pRealPal = const_cast<BitmapPalette*>(pPal);
+ }
+
+ mxSalBmp = ImplGetSVData()->mpDefInst->CreateSalBitmap();
+ mxSalBmp->Create( rSizePixel, nBitCount, pRealPal ? *pRealPal : aPal );
+ }
+}
+
+Bitmap::~Bitmap()
+{
+}
+
+const BitmapPalette& Bitmap::GetGreyPalette( int nEntries )
+{
+ // Create greyscale palette with 2, 4, 16 or 256 entries
+ switch (nEntries)
+ {
+ case 2:
+ {
+ static const BitmapPalette aGreyPalette2 = [] {
+ BitmapPalette aPalette(2);
+ aPalette[0] = BitmapColor(0, 0, 0);
+ aPalette[1] = BitmapColor(255, 255, 255);
+ return aPalette;
+ }();
+
+ return aGreyPalette2;
+ }
+ case 4:
+ {
+ static const BitmapPalette aGreyPalette4 = [] {
+ BitmapPalette aPalette(4);
+ aPalette[0] = BitmapColor(0, 0, 0);
+ aPalette[1] = BitmapColor(85, 85, 85);
+ aPalette[2] = BitmapColor(170, 170, 170);
+ aPalette[3] = BitmapColor(255, 255, 255);
+ return aPalette;
+ }();
+
+ return aGreyPalette4;
+ }
+ case 16:
+ {
+ static const BitmapPalette aGreyPalette16 = [] {
+ sal_uInt8 cGrey = 0;
+ sal_uInt8 const cGreyInc = 17;
+
+ BitmapPalette aPalette(16);
+
+ for (sal_uInt16 i = 0; i < 16; ++i, cGrey += cGreyInc)
+ aPalette[i] = BitmapColor(cGrey, cGrey, cGrey);
+
+ return aPalette;
+ }();
+
+ return aGreyPalette16;
+ }
+ case 256:
+ {
+ static const BitmapPalette aGreyPalette256 = [] {
+ BitmapPalette aPalette(256);
+
+ for (sal_uInt16 i = 0; i < 256; ++i)
+ aPalette[i] = BitmapColor(static_cast<sal_uInt8>(i), static_cast<sal_uInt8>(i),
+ static_cast<sal_uInt8>(i));
+
+ return aPalette;
+ }();
+
+ return aGreyPalette256;
+ }
+ }
+ OSL_FAIL("Bitmap::GetGreyPalette: invalid entry count (2/4/16/256 allowed)");
+ return GetGreyPalette(2);
+}
+
+bool BitmapPalette::IsGreyPaletteAny() const
+{
+ 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(maBitmapColor[0]);
+ const BitmapColor& rCol1(maBitmapColor[1]);
+ bRet = rCol0.GetRed() == rCol0.GetGreen() && rCol0.GetRed() == rCol0.GetBlue() &&
+ rCol1.GetRed() == rCol1.GetGreen() && rCol1.GetRed() == rCol1.GetBlue();
+ }
+ return bRet;
+}
+
+bool BitmapPalette::IsGreyPalette8Bit() const
+{
+ 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( maBitmapColor[i] != BitmapColor(i, i, i))
+ return false;
+ }
+ return true;
+}
+
+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, aChecksum2;
+ rBmp.mxSalBmp->GetChecksum(aChecksum1);
+ mxSalBmp->GetChecksum(aChecksum2);
+ // 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() );
+}
+
+sal_uInt16 Bitmap::GetBitCount() const
+{
+ if (!mxSalBmp)
+ return 0;
+
+ sal_uInt16 nBitCount = mxSalBmp->GetBitCount();
+ if (nBitCount <= 1)
+ return 1;
+ if (nBitCount <= 4)
+ return 4;
+ if (nBitCount <= 8)
+ return 8;
+ if (nBitCount <= 24)
+ return 24;
+ if (nBitCount <= 32)
+ return 32;
+ return 0;
+}
+
+bool Bitmap::HasGreyPaletteAny() const
+{
+ const sal_uInt16 nBitCount = GetBitCount();
+ bool bRet = nBitCount == 1;
+
+ ScopedInfoAccess pIAcc(const_cast<Bitmap&>(*this));
+
+ if( pIAcc )
+ {
+ bRet = pIAcc->HasPalette() && pIAcc->GetPalette().IsGreyPaletteAny();
+ }
+
+ return bRet;
+}
+
+bool Bitmap::HasGreyPalette8Bit() const
+{
+ bool bRet = false;
+ ScopedInfoAccess pIAcc(const_cast<Bitmap&>(*this));
+
+ if( pIAcc )
+ {
+ bRet = pIAcc->HasPalette() && pIAcc->GetPalette().IsGreyPalette8Bit();
+ }
+
+ return bRet;
+}
+
+BitmapChecksum Bitmap::GetChecksum() const
+{
+ BitmapChecksum nRet = 0;
+
+ if( mxSalBmp )
+ {
+ mxSalBmp->GetChecksum(nRet);
+
+ 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, GetBitCount()))
+ {
+ Bitmap* pThis = const_cast<Bitmap*>(this);
+ pThis->mxSalBmp = xNewImpBmp;
+ mxSalBmp->GetChecksum(nRet);
+ }
+ }
+ }
+
+ return nRet;
+}
+
+void Bitmap::ImplMakeUnique()
+{
+ if (mxSalBmp && mxSalBmp.use_count() > 1)
+ {
+ std::shared_ptr<SalBitmap> xOldImpBmp = mxSalBmp;
+ mxSalBmp = ImplGetSVData()->mpDefInst->CreateSalBitmap();
+ (void)mxSalBmp->Create(*xOldImpBmp);
+ }
+}
+
+void Bitmap::ReassignWithSize(const Bitmap& rBitmap)
+{
+ const Size aOldSizePix(GetSizePixel());
+ const Size aNewSizePix(rBitmap.GetSizePixel());
+ const MapMode aOldMapMode(maPrefMapMode);
+ Size aNewPrefSize;
+
+ if ((aOldSizePix != aNewSizePix) && aOldSizePix.Width() && aOldSizePix.Height())
+ {
+ aNewPrefSize.setWidth(FRound(maPrefSize.Width() * aNewSizePix.Width() / aOldSizePix.Width()));
+ aNewPrefSize.setHeight(FRound(maPrefSize.Height() * aNewSizePix.Height() / aOldSizePix.Height()));
+ }
+ else
+ {
+ aNewPrefSize = maPrefSize;
+ }
+
+ *this = rBitmap;
+
+ maPrefSize = aNewPrefSize;
+ maPrefMapMode = aOldMapMode;
+}
+
+
+void Bitmap::ImplSetSalBitmap(const std::shared_ptr<SalBitmap>& xImpBmp)
+{
+ mxSalBmp = xImpBmp;
+}
+
+BitmapInfoAccess* Bitmap::AcquireInfoAccess()
+{
+ std::unique_ptr<BitmapInfoAccess> pInfoAccess(new BitmapInfoAccess( *this ));
+
+ if( !*pInfoAccess )
+ {
+ return nullptr;
+ }
+
+ return pInfoAccess.release();
+}
+
+BitmapReadAccess* Bitmap::AcquireReadAccess()
+{
+ std::unique_ptr<BitmapReadAccess> pReadAccess(new BitmapReadAccess( *this ));
+
+ if( !*pReadAccess )
+ {
+ return nullptr;
+ }
+
+ return pReadAccess.release();
+}
+
+BitmapWriteAccess* Bitmap::AcquireWriteAccess()
+{
+ std::unique_ptr<BitmapWriteAccess> pWriteAccess(new BitmapWriteAccess( *this ));
+
+ if( !*pWriteAccess )
+ {
+ return nullptr;
+ }
+
+ return pWriteAccess.release();
+}
+
+void Bitmap::ReleaseAccess( BitmapInfoAccess* pBitmapAccess )
+{
+ delete pBitmapAccess;
+}
+
+bool Bitmap::Crop( const tools::Rectangle& rRectPixel )
+{
+ const Size aSizePix( GetSizePixel() );
+ tools::Rectangle aRect( rRectPixel );
+ bool bRet = false;
+
+ aRect.Intersection( tools::Rectangle( Point(), aSizePix ) );
+
+ if( !aRect.IsEmpty() && aSizePix != aRect.GetSize())
+ {
+ ScopedReadAccess pReadAcc(*this);
+
+ if( pReadAcc )
+ {
+ const tools::Rectangle aNewRect( Point(), aRect.GetSize() );
+ Bitmap aNewBmp( aNewRect.GetSize(), GetBitCount(), &pReadAcc->GetPalette() );
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if( pWriteAcc )
+ {
+ const long nOldX = aRect.Left();
+ const long nOldY = aRect.Top();
+ const long nNewWidth = aNewRect.GetWidth();
+ const long nNewHeight = aNewRect.GetHeight();
+
+ for( long nY = 0, nY2 = nOldY; nY < nNewHeight; nY++, nY2++ )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY2);
+ for( long nX = 0, nX2 = nOldX; nX < nNewWidth; nX++, nX2++ )
+ pWriteAcc->SetPixelOnData( pScanline, nX, pReadAcc->GetPixelFromData( pScanlineRead, nX2 ) );
+ }
+
+ pWriteAcc.reset();
+ bRet = true;
+ }
+
+ pReadAcc.reset();
+
+ if( bRet )
+ ReassignWithSize( aNewBmp );
+ }
+ }
+
+ return bRet;
+};
+
+bool Bitmap::CopyPixel( const tools::Rectangle& rRectDst,
+ const tools::Rectangle& rRectSrc, const Bitmap* pBmpSrc )
+{
+ const Size aSizePix( GetSizePixel() );
+ tools::Rectangle aRectDst( rRectDst );
+ bool bRet = false;
+
+ aRectDst.Intersection( tools::Rectangle( Point(), aSizePix ) );
+
+ if( !aRectDst.IsEmpty() )
+ {
+ if( pBmpSrc && ( pBmpSrc->mxSalBmp != mxSalBmp ) )
+ {
+ Bitmap* pSrc = const_cast<Bitmap*>(pBmpSrc);
+ const Size aCopySizePix( pSrc->GetSizePixel() );
+ tools::Rectangle aRectSrc( rRectSrc );
+ const sal_uInt16 nSrcBitCount = pBmpSrc->GetBitCount();
+ const sal_uInt16 nDstBitCount = GetBitCount();
+
+ 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)
+ {
+ Convert( BmpConversion::N4BitColors );
+ nNextIndex = 2;
+ }
+
+ if( nNextIndex )
+ {
+ ScopedReadAccess pSrcAcc(*pSrc);
+ BitmapScopedWriteAccess pDstAcc(*this);
+
+ if( pSrcAcc && pDstAcc )
+ {
+ const int nSrcCount = pDstAcc->GetPaletteEntryCount();
+ const int nDstCount = 1 << nDstBitCount;
+
+ for (int i = 0; ( i < nSrcCount ) && ( nNextIndex < nSrcCount ); ++i)
+ {
+ const BitmapColor& rSrcCol = pSrcAcc->GetPaletteColor( static_cast<sal_uInt16>(i) );
+
+ bool bFound = false;
+
+ for (int j = 0; j < nDstCount; ++j)
+ {
+ if( rSrcCol == pDstAcc->GetPaletteColor( static_cast<sal_uInt16>(j) ) )
+ {
+ bFound = true;
+ break;
+ }
+ }
+
+ if( !bFound )
+ pDstAcc->SetPaletteColor( static_cast<sal_uInt16>(nNextIndex++), rSrcCol );
+ }
+ }
+ }
+ }
+
+ aRectSrc.Intersection( tools::Rectangle( Point(), aCopySizePix ) );
+
+ if( !aRectSrc.IsEmpty() )
+ {
+ ScopedReadAccess pReadAcc(*pSrc);
+
+ if( pReadAcc )
+ {
+ BitmapScopedWriteAccess pWriteAcc(*this);
+
+ if( pWriteAcc )
+ {
+ const long nWidth = std::min( aRectSrc.GetWidth(), aRectDst.GetWidth() );
+ const long nHeight = std::min( aRectSrc.GetHeight(), aRectDst.GetHeight() );
+ const long nSrcEndX = aRectSrc.Left() + nWidth;
+ const long nSrcEndY = aRectSrc.Top() + nHeight;
+ 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( long nSrcY = aRectSrc.Top(); nSrcY < nSrcEndY; nSrcY++, nDstY++ )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nDstY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nSrcY);
+ for( 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( long nSrcY = aRectSrc.Top(); nSrcY < nSrcEndY; nSrcY++, nDstY++ )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nDstY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nSrcY);
+ for( long nSrcX = aRectSrc.Left(), nDstX = aRectDst.Left(); nSrcX < nSrcEndX; nSrcX++, nDstX++ )
+ pWriteAcc->SetPixelOnData( pScanline, nDstX, pReadAcc->GetPaletteColor( pReadAcc->GetIndexFromData( pScanlineRead, nSrcX ) ) );
+ }
+ }
+ else
+ for( long nSrcY = aRectSrc.Top(); nSrcY < nSrcEndY; nSrcY++, nDstY++ )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nDstY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nSrcY);
+ for( long nSrcX = aRectSrc.Left(), nDstX = aRectDst.Left(); nSrcX < nSrcEndX; nSrcX++, nDstX++ )
+ pWriteAcc->SetPixelOnData( pScanline, nDstX, pReadAcc->GetPixelFromData( pScanlineRead, nSrcX ) );
+ }
+
+ pWriteAcc.reset();
+ bRet = ( nWidth > 0 ) && ( nHeight > 0 );
+ }
+
+ pReadAcc.reset();
+ }
+ }
+ }
+ else
+ {
+ tools::Rectangle aRectSrc( rRectSrc );
+
+ aRectSrc.Intersection( tools::Rectangle( Point(), aSizePix ) );
+
+ if( !aRectSrc.IsEmpty() && ( aRectSrc != aRectDst ) )
+ {
+ BitmapScopedWriteAccess pWriteAcc(*this);
+
+ if( pWriteAcc )
+ {
+ const long nWidth = std::min( aRectSrc.GetWidth(), aRectDst.GetWidth() );
+ const long nHeight = std::min( aRectSrc.GetHeight(), aRectDst.GetHeight() );
+ const long nSrcX = aRectSrc.Left();
+ const long nSrcY = aRectSrc.Top();
+ const long nSrcEndX1 = nSrcX + nWidth - 1;
+ const long nSrcEndY1 = nSrcY + nHeight - 1;
+ const long nDstX = aRectDst.Left();
+ const long nDstY = aRectDst.Top();
+ const long nDstEndX1 = nDstX + nWidth - 1;
+ const long nDstEndY1 = nDstY + nHeight - 1;
+
+ if( ( nDstX <= nSrcX ) && ( nDstY <= nSrcY ) )
+ {
+ for( long nY = nSrcY, nYN = nDstY; nY <= nSrcEndY1; nY++, nYN++ )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nYN);
+ Scanline pScanlineSrc = pWriteAcc->GetScanline(nY);
+ for( long nX = nSrcX, nXN = nDstX; nX <= nSrcEndX1; nX++, nXN++ )
+ pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) );
+ }
+ }
+ else if( ( nDstX <= nSrcX ) && ( nDstY >= nSrcY ) )
+ {
+ for( long nY = nSrcEndY1, nYN = nDstEndY1; nY >= nSrcY; nY--, nYN-- )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nYN);
+ Scanline pScanlineSrc = pWriteAcc->GetScanline(nY);
+ for( long nX = nSrcX, nXN = nDstX; nX <= nSrcEndX1; nX++, nXN++ )
+ pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) );
+ }
+ }
+ else if( ( nDstX >= nSrcX ) && ( nDstY <= nSrcY ) )
+ {
+ for( long nY = nSrcY, nYN = nDstY; nY <= nSrcEndY1; nY++, nYN++ )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nYN);
+ Scanline pScanlineSrc = pWriteAcc->GetScanline(nY);
+ for( long nX = nSrcEndX1, nXN = nDstEndX1; nX >= nSrcX; nX--, nXN-- )
+ pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) );
+ }
+ }
+ else
+ {
+ for( long nY = nSrcEndY1, nYN = nDstEndY1; nY >= nSrcY; nY--, nYN-- )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nYN);
+ Scanline pScanlineSrc = pWriteAcc->GetScanline(nY);
+ for( long nX = nSrcEndX1, nXN = nDstEndX1; nX >= nSrcX; nX--, nXN-- )
+ pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) );
+ }
+ }
+
+ pWriteAcc.reset();
+ bRet = true;
+ }
+ }
+ }
+ }
+
+ return bRet;
+}
+
+bool Bitmap::CopyPixel_AlphaOptimized( const tools::Rectangle& rRectDst, const tools::Rectangle& rRectSrc,
+ const Bitmap* pBmpSrc )
+{
+ // Note: this code is copied from Bitmap::CopyPixel but avoids any palette lookups
+ // This optimization is possible because the palettes of AlphaMasks are always identical (8bit GreyPalette, see ctor)
+ const Size aSizePix( GetSizePixel() );
+ tools::Rectangle aRectDst( rRectDst );
+ bool bRet = false;
+
+ aRectDst.Intersection( tools::Rectangle( Point(), aSizePix ) );
+
+ if( !aRectDst.IsEmpty() )
+ {
+ if( pBmpSrc && ( pBmpSrc->mxSalBmp != mxSalBmp ) )
+ {
+ Bitmap* pSrc = const_cast<Bitmap*>(pBmpSrc);
+ const Size aCopySizePix( pSrc->GetSizePixel() );
+ tools::Rectangle aRectSrc( rRectSrc );
+
+ aRectSrc.Intersection( tools::Rectangle( Point(), aCopySizePix ) );
+
+ if( !aRectSrc.IsEmpty() )
+ {
+ ScopedReadAccess pReadAcc(*pSrc);
+
+ if( pReadAcc )
+ {
+ BitmapScopedWriteAccess pWriteAcc(*this);
+
+ if( pWriteAcc )
+ {
+ const long nWidth = std::min( aRectSrc.GetWidth(), aRectDst.GetWidth() );
+ const long nHeight = std::min( aRectSrc.GetHeight(), aRectDst.GetHeight() );
+ const long nSrcEndX = aRectSrc.Left() + nWidth;
+ const long nSrcEndY = aRectSrc.Top() + nHeight;
+ long nDstY = aRectDst.Top();
+
+ for( long nSrcY = aRectSrc.Top(); nSrcY < nSrcEndY; nSrcY++, nDstY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nDstY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nSrcY);
+ for( long nSrcX = aRectSrc.Left(), nDstX = aRectDst.Left(); nSrcX < nSrcEndX; nSrcX++, nDstX++ )
+ pWriteAcc->SetPixelOnData( pScanline, nDstX, pReadAcc->GetPixelFromData( pScanlineRead, nSrcX ) );
+ }
+
+ pWriteAcc.reset();
+ bRet = ( nWidth > 0 ) && ( nHeight > 0 );
+ }
+
+ pReadAcc.reset();
+ }
+ }
+ }
+ else
+ {
+ tools::Rectangle aRectSrc( rRectSrc );
+
+ aRectSrc.Intersection( tools::Rectangle( Point(), aSizePix ) );
+
+ if( !aRectSrc.IsEmpty() && ( aRectSrc != aRectDst ) )
+ {
+ BitmapScopedWriteAccess pWriteAcc(*this);
+
+ if( pWriteAcc )
+ {
+ const long nWidth = std::min( aRectSrc.GetWidth(), aRectDst.GetWidth() );
+ const long nHeight = std::min( aRectSrc.GetHeight(), aRectDst.GetHeight() );
+ const long nSrcX = aRectSrc.Left();
+ const long nSrcY = aRectSrc.Top();
+ const long nSrcEndX1 = nSrcX + nWidth - 1;
+ const long nSrcEndY1 = nSrcY + nHeight - 1;
+ const long nDstX = aRectDst.Left();
+ const long nDstY = aRectDst.Top();
+ const long nDstEndX1 = nDstX + nWidth - 1;
+ const long nDstEndY1 = nDstY + nHeight - 1;
+
+ if( ( nDstX <= nSrcX ) && ( nDstY <= nSrcY ) )
+ {
+ for( long nY = nSrcY, nYN = nDstY; nY <= nSrcEndY1; nY++, nYN++ )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nYN);
+ Scanline pScanlineSrc = pWriteAcc->GetScanline(nY);
+ for( long nX = nSrcX, nXN = nDstX; nX <= nSrcEndX1; nX++, nXN++ )
+ pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) );
+ }
+ }
+ else if( ( nDstX <= nSrcX ) && ( nDstY >= nSrcY ) )
+ {
+ for( long nY = nSrcEndY1, nYN = nDstEndY1; nY >= nSrcY; nY--, nYN-- )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nYN);
+ Scanline pScanlineSrc = pWriteAcc->GetScanline(nY);
+ for( long nX = nSrcX, nXN = nDstX; nX <= nSrcEndX1; nX++, nXN++ )
+ pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) );
+ }
+ }
+ else if( ( nDstX >= nSrcX ) && ( nDstY <= nSrcY ) )
+ {
+ for( long nY = nSrcY, nYN = nDstY; nY <= nSrcEndY1; nY++, nYN++ )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nYN);
+ Scanline pScanlineSrc = pWriteAcc->GetScanline(nY);
+ for( long nX = nSrcEndX1, nXN = nDstEndX1; nX >= nSrcX; nX--, nXN-- )
+ pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) );
+ }
+ }
+ else
+ {
+ for( long nY = nSrcEndY1, nYN = nDstEndY1; nY >= nSrcY; nY--, nYN-- )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nYN);
+ Scanline pScanlineSrc = pWriteAcc->GetScanline(nY);
+ for( long nX = nSrcEndX1, nXN = nDstEndX1; nX >= nSrcX; nX--, nXN-- )
+ pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) );
+ }
+ }
+
+ pWriteAcc.reset();
+ bRet = true;
+ }
+ }
+ }
+ }
+
+ return bRet;
+
+}
+
+bool Bitmap::Expand( sal_uLong nDX, sal_uLong nDY, const Color* pInitColor )
+{
+ bool bRet = false;
+
+ if( nDX || nDY )
+ {
+ const Size aSizePixel( GetSizePixel() );
+ const long nWidth = aSizePixel.Width();
+ const long nHeight = aSizePixel.Height();
+ const Size aNewSize( nWidth + nDX, nHeight + nDY );
+ ScopedReadAccess pReadAcc(*this);
+
+ if( pReadAcc )
+ {
+ BitmapPalette aBmpPal( pReadAcc->GetPalette() );
+ Bitmap aNewBmp( aNewSize, GetBitCount(), &aBmpPal );
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if( pWriteAcc )
+ {
+ BitmapColor aColor;
+ const long nNewX = nWidth;
+ const long nNewY = nHeight;
+ const long nNewWidth = pWriteAcc->Width();
+ const long nNewHeight = pWriteAcc->Height();
+ long nX;
+ long nY;
+
+ if( pInitColor )
+ aColor = pWriteAcc->GetBestMatchingColor( *pInitColor );
+
+ for( nY = 0; nY < nHeight; nY++ )
+ {
+ pWriteAcc->CopyScanline( nY, *pReadAcc );
+
+ if( pInitColor && nDX )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for( nX = nNewX; nX < nNewWidth; nX++ )
+ pWriteAcc->SetPixelOnData( pScanline, nX, aColor );
+ }
+ }
+
+ if( pInitColor && nDY )
+ for( nY = nNewY; nY < nNewHeight; nY++ )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for( nX = 0; nX < nNewWidth; nX++ )
+ pWriteAcc->SetPixelOnData( pScanline, nX, aColor );
+ }
+
+ pWriteAcc.reset();
+ bRet = true;
+ }
+
+ pReadAcc.reset();
+
+ if (bRet)
+ ReassignWithSize(aNewBmp);
+ }
+ }
+
+ return bRet;
+}
+
+Bitmap Bitmap::CreateDisplayBitmap( OutputDevice* pDisplay ) const
+{
+ Bitmap aDispBmp( *this );
+
+ SalGraphics* pDispGraphics = pDisplay->GetGraphics();
+
+ if( mxSalBmp && pDispGraphics )
+ {
+ std::shared_ptr<SalBitmap> xImpDispBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
+ if (xImpDispBmp->Create(*mxSalBmp, pDispGraphics))
+ aDispBmp.ImplSetSalBitmap(xImpDispBmp);
+ }
+
+ return aDispBmp;
+}
+
+bool Bitmap::GetSystemData( BitmapSystemData& rData ) const
+{
+ return mxSalBmp && mxSalBmp->GetSystemData(rData);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/bitmapfilter.cxx b/vcl/source/bitmap/bitmapfilter.cxx
new file mode 100644
index 000000000..58a01f1fb
--- /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");
+ return false;
+ }
+
+ rBmpEx = aTmpBmpEx;
+ return true;
+}
+
+bool BitmapFilter::Filter(Animation& rAnimation, BitmapFilter const & rFilter)
+{
+ SAL_WARN_IF(rAnimation.IsInAnimation(), "vcl", "Animation modified while it is animated");
+
+ bool bRet = false;
+
+ if (!rAnimation.IsInAnimation() && !rAnimation.Count())
+ {
+ bRet = true;
+
+ std::vector<std::unique_ptr<AnimationBitmap>>& aList = rAnimation.GetAnimationFrames();
+ for (size_t i = 0, n = aList.size(); (i < n) && bRet; ++i)
+ {
+ bRet = BitmapFilter::Filter(aList[i]->maBitmapEx, rFilter);
+ }
+
+ BitmapEx aBmpEx(rAnimation.GetBitmapEx());
+ BitmapFilter::Filter(aBmpEx, rFilter);
+ rAnimation.SetBitmapEx(aBmpEx);
+ }
+
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/bitmappaint.cxx b/vcl/source/bitmap/bitmappaint.cxx
new file mode 100644
index 000000000..5c14e1476
--- /dev/null
+++ b/vcl/source/bitmap/bitmappaint.cxx
@@ -0,0 +1,1146 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/bitmapaccess.hxx>
+#include <vcl/alpha.hxx>
+
+#include <bitmapwriteaccess.hxx>
+#include <salbmp.hxx>
+#include <svdata.hxx>
+#include <salinst.hxx>
+
+#include <algorithm>
+#include <memory>
+
+bool Bitmap::Erase(const Color& rFillColor)
+{
+ if (IsEmpty())
+ return true;
+
+ BitmapScopedWriteAccess pWriteAcc(*this);
+ bool bRet = false;
+
+ if (pWriteAcc)
+ {
+ const ScanlineFormat nFormat = pWriteAcc->GetScanlineFormat();
+ sal_uInt8 cIndex = 0;
+ bool bFast = false;
+
+ switch (nFormat)
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ case ScanlineFormat::N1BitLsbPal:
+ {
+ cIndex = static_cast<sal_uInt8>(pWriteAcc->GetBestPaletteIndex(rFillColor));
+ cIndex = (cIndex ? 255 : 0);
+ bFast = true;
+ }
+ break;
+
+ case ScanlineFormat::N4BitMsnPal:
+ case ScanlineFormat::N4BitLsnPal:
+ {
+ cIndex = static_cast<sal_uInt8>(pWriteAcc->GetBestPaletteIndex(rFillColor));
+ cIndex = cIndex | (cIndex << 4);
+ bFast = true;
+ }
+ break;
+
+ case ScanlineFormat::N8BitPal:
+ {
+ cIndex = static_cast<sal_uInt8>(pWriteAcc->GetBestPaletteIndex(rFillColor));
+ bFast = true;
+ }
+ break;
+
+ case ScanlineFormat::N24BitTcBgr:
+ case ScanlineFormat::N24BitTcRgb:
+ {
+ if (rFillColor.GetRed() == rFillColor.GetGreen()
+ && rFillColor.GetRed() == rFillColor.GetBlue())
+ {
+ cIndex = rFillColor.GetRed();
+ bFast = true;
+ }
+ else
+ {
+ bFast = false;
+ }
+ }
+ break;
+
+ default:
+ bFast = false;
+ break;
+ }
+
+ if (bFast)
+ {
+ const sal_uLong nBufSize = pWriteAcc->GetScanlineSize() * pWriteAcc->Height();
+ memset(pWriteAcc->GetBuffer(), cIndex, nBufSize);
+ }
+ else
+ {
+ const tools::Rectangle aRect(Point(), Size(pWriteAcc->Width(), pWriteAcc->Height()));
+ pWriteAcc->SetFillColor(rFillColor);
+ pWriteAcc->FillRect(aRect);
+ }
+
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+bool Bitmap::Invert()
+{
+ BitmapScopedWriteAccess pAcc(*this);
+ bool bRet = false;
+
+ if (pAcc)
+ {
+ if (pAcc->HasPalette())
+ {
+ BitmapPalette aBmpPal(pAcc->GetPalette());
+ const sal_uInt16 nCount = aBmpPal.GetEntryCount();
+
+ for (sal_uInt16 i = 0; i < nCount; i++)
+ {
+ aBmpPal[i].Invert();
+ }
+
+ pAcc->SetPalette(aBmpPal);
+ }
+ else
+ {
+ const long nWidth = pAcc->Width();
+ const long nHeight = pAcc->Height();
+
+ for (long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pAcc->GetScanline(nY);
+ for (long nX = 0; nX < nWidth; nX++)
+ {
+ BitmapColor aBmpColor = pAcc->GetPixelFromData(pScanline, nX);
+ aBmpColor.Invert();
+ pAcc->SetPixelOnData(pScanline, nX, aBmpColor);
+ }
+ }
+ }
+
+ mxSalBmp->InvalidateChecksum();
+ pAcc.reset();
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+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 long nWidth = pAcc->Width();
+ const long nHeight = pAcc->Height();
+ const long nWidth1 = nWidth - 1;
+ const long nWidth_2 = nWidth >> 1;
+
+ for (long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pAcc->GetScanline(nY);
+ for (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 long nScanSize = pAcc->GetScanlineSize();
+ std::unique_ptr<sal_uInt8[]> pBuffer(new sal_uInt8[nScanSize]);
+ const long nHeight = pAcc->Height();
+ const long nHeight1 = nHeight - 1;
+ const long nHeight_2 = nHeight >> 1;
+
+ for (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 long nWidth = pAcc->Width();
+ const long nWidth1 = nWidth - 1;
+ const long nHeight = pAcc->Height();
+ long nHeight_2 = nHeight >> 1;
+
+ for (long nY = 0, nOtherY = nHeight - 1; nY < nHeight_2; nY++, nOtherY--)
+ {
+ Scanline pScanline = pAcc->GetScanline(nY);
+ Scanline pScanlineOther = pAcc->GetScanline(nOtherY);
+ for (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 (long nX = 0, nOtherX = nWidth1, nWidth_2 = nWidth >> 1; 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(long nAngle10, const Color& rFillColor)
+{
+ bool bRet = false;
+
+ nAngle10 %= 3600;
+ nAngle10 = (nAngle10 < 0) ? (3599L + nAngle10) : nAngle10;
+
+ if (!nAngle10)
+ bRet = true;
+ else if (nAngle10 == 1800)
+ bRet = Mirror(BmpMirrorFlags::Horizontal | BmpMirrorFlags::Vertical);
+ else
+ {
+ ScopedReadAccess pReadAcc(*this);
+ Bitmap aRotatedBmp;
+
+ if (pReadAcc)
+ {
+ const Size aSizePix(GetSizePixel());
+
+ if (nAngle10 == 900 || nAngle10 == 2700)
+ {
+ const Size aNewSizePix(aSizePix.Height(), aSizePix.Width());
+ Bitmap aNewBmp(aNewSizePix, GetBitCount(), &pReadAcc->GetPalette());
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if (pWriteAcc)
+ {
+ const long nWidth = aSizePix.Width();
+ const long nWidth1 = nWidth - 1;
+ const long nHeight = aSizePix.Height();
+ const long nHeight1 = nHeight - 1;
+ const long nNewWidth = aNewSizePix.Width();
+ const long nNewHeight = aNewSizePix.Height();
+
+ if (nAngle10 == 900)
+ {
+ for (long nY = 0, nOtherX = nWidth1; nY < nNewHeight; nY++, nOtherX--)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (long nX = 0, nOtherY = 0; nX < nNewWidth; nX++)
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX,
+ pReadAcc->GetPixel(nOtherY++, nOtherX));
+ }
+ }
+ }
+ else if (nAngle10 == 2700)
+ {
+ for (long nY = 0, nOtherX = 0; nY < nNewHeight; nY++, nOtherX++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (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, static_cast<sal_uInt16>(nAngle10));
+
+ tools::Rectangle aNewBound(aPoly.GetBoundRect());
+ const Size aNewSizePix(aNewBound.GetSize());
+ Bitmap aNewBmp(aNewSizePix, GetBitCount(), &pReadAcc->GetPalette());
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if (pWriteAcc)
+ {
+ const BitmapColor aFillColor(pWriteAcc->GetBestMatchingColor(rFillColor));
+ const double fCosAngle = cos(nAngle10 * F_PI1800);
+ const double fSinAngle = sin(nAngle10 * F_PI1800);
+ const double fXMin = aNewBound.Left();
+ const double fYMin = aNewBound.Top();
+ const long nWidth = aSizePix.Width();
+ const long nHeight = aSizePix.Height();
+ const long nNewWidth = aNewSizePix.Width();
+ const long nNewHeight = aNewSizePix.Height();
+ long nX;
+ long nY;
+ long nRotX;
+ long nRotY;
+ std::unique_ptr<long[]> pCosX(new long[nNewWidth]);
+ std::unique_ptr<long[]> pSinX(new long[nNewWidth]);
+ std::unique_ptr<long[]> pCosY(new long[nNewHeight]);
+ std::unique_ptr<long[]> pSinY(new long[nNewHeight]);
+
+ for (nX = 0; nX < nNewWidth; nX++)
+ {
+ const double fTmp = (fXMin + nX) * 64.;
+
+ pCosX[nX] = FRound(fCosAngle * fTmp);
+ pSinX[nX] = FRound(fSinAngle * fTmp);
+ }
+
+ for (nY = 0; nY < nNewHeight; nY++)
+ {
+ const double fTmp = (fYMin + nY) * 64.;
+
+ pCosY[nY] = FRound(fCosAngle * fTmp);
+ pSinY[nY] = FRound(fSinAngle * fTmp);
+ }
+
+ for (nY = 0; nY < nNewHeight; nY++)
+ {
+ long nSinY = pSinY[nY];
+ long nCosY = pCosY[nY];
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+
+ for (nX = 0; nX < nNewWidth; nX++)
+ {
+ nRotX = (pCosX[nX] - nSinY) >> 6;
+ nRotY = (pSinX[nX] + 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;
+ if (bRet)
+ ReassignWithSize(aRotatedBmp);
+ }
+
+ return bRet;
+};
+
+Bitmap Bitmap::CreateMask(const Color& rTransColor, sal_uInt8 nTol) const
+{
+ ScopedReadAccess pReadAcc(const_cast<Bitmap&>(*this));
+
+ if (!nTol && pReadAcc
+ && (pReadAcc->GetScanlineFormat() == ScanlineFormat::N1BitLsbPal
+ || pReadAcc->GetScanlineFormat() == ScanlineFormat::N1BitMsbPal)
+ && pReadAcc->GetBestMatchingColor(COL_WHITE) == pReadAcc->GetBestMatchingColor(rTransColor))
+ {
+ // if we're a 1 bit pixel already, and the transcolor matches the color that would replace it
+ // already, then just return a copy
+ return *this;
+ }
+
+ Bitmap aNewBmp(GetSizePixel(), 1);
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+ bool bRet = false;
+
+ if (pWriteAcc && pReadAcc)
+ {
+ const long nWidth = pReadAcc->Width();
+ const long nHeight = pReadAcc->Height();
+ const BitmapColor aBlack(pWriteAcc->GetBestMatchingColor(COL_BLACK));
+ const BitmapColor aWhite(pWriteAcc->GetBestMatchingColor(COL_WHITE));
+
+ if (!nTol)
+ {
+ const BitmapColor aTest(pReadAcc->GetBestMatchingColor(rTransColor));
+
+ if (pReadAcc->GetScanlineFormat() == ScanlineFormat::N4BitMsnPal
+ || pReadAcc->GetScanlineFormat() == ScanlineFormat::N4BitLsnPal)
+ {
+ // optimized for 4Bit-MSN/LSN source palette
+ const sal_uInt8 cTest = aTest.GetIndex();
+ const long nShiftInit
+ = ((pReadAcc->GetScanlineFormat() == ScanlineFormat::N4BitMsnPal) ? 4 : 0);
+
+ if (pWriteAcc->GetScanlineFormat() == ScanlineFormat::N1BitMsbPal
+ && aWhite.GetIndex() == 1)
+ {
+ // optimized for 1Bit-MSB destination palette
+ for (long nY = 0; nY < nHeight; ++nY)
+ {
+ Scanline pSrc = pReadAcc->GetScanline(nY);
+ Scanline pDst = pWriteAcc->GetScanline(nY);
+ for (long nX = 0, nShift = nShiftInit; nX < nWidth; nX++, nShift ^= 4)
+ {
+ if (cTest == ((pSrc[nX >> 1] >> nShift) & 0x0f))
+ pDst[nX >> 3] |= 1 << (7 - (nX & 7));
+ else
+ pDst[nX >> 3] &= ~(1 << (7 - (nX & 7)));
+ }
+ }
+ }
+ else
+ {
+ for (long nY = 0; nY < nHeight; ++nY)
+ {
+ Scanline pSrc = pReadAcc->GetScanline(nY);
+ Scanline pDst = pWriteAcc->GetScanline(nY);
+ for (long nX = 0, nShift = nShiftInit; nX < nWidth; nX++, nShift ^= 4)
+ {
+ if (cTest == ((pSrc[nX >> 1] >> nShift) & 0x0f))
+ pWriteAcc->SetPixelOnData(pDst, nX, aWhite);
+ else
+ pWriteAcc->SetPixelOnData(pDst, nX, aBlack);
+ }
+ }
+ }
+ }
+ else if (pReadAcc->GetScanlineFormat() == ScanlineFormat::N8BitPal)
+ {
+ // optimized for 8Bit source palette
+ const sal_uInt8 cTest = aTest.GetIndex();
+
+ if (pWriteAcc->GetScanlineFormat() == ScanlineFormat::N1BitMsbPal
+ && aWhite.GetIndex() == 1)
+ {
+ // optimized for 1Bit-MSB destination palette
+ for (long nY = 0; nY < nHeight; ++nY)
+ {
+ Scanline pSrc = pReadAcc->GetScanline(nY);
+ Scanline pDst = pWriteAcc->GetScanline(nY);
+ for (long nX = 0; nX < nWidth; ++nX)
+ {
+ if (cTest == pSrc[nX])
+ pDst[nX >> 3] |= 1 << (7 - (nX & 7));
+ else
+ pDst[nX >> 3] &= ~(1 << (7 - (nX & 7)));
+ }
+ }
+ }
+ else
+ {
+ for (long nY = 0; nY < nHeight; ++nY)
+ {
+ Scanline pSrc = pReadAcc->GetScanline(nY);
+ Scanline pDst = pWriteAcc->GetScanline(nY);
+ for (long nX = 0; nX < nWidth; ++nX)
+ {
+ if (cTest == pSrc[nX])
+ pWriteAcc->SetPixelOnData(pDst, nX, aWhite);
+ else
+ pWriteAcc->SetPixelOnData(pDst, nX, aBlack);
+ }
+ }
+ }
+ }
+ else if (pWriteAcc->GetScanlineFormat() == pReadAcc->GetScanlineFormat()
+ && aWhite.GetIndex() == 1
+ && (pReadAcc->GetScanlineFormat() == ScanlineFormat::N1BitLsbPal
+ || pReadAcc->GetScanlineFormat() == ScanlineFormat::N1BitMsbPal))
+ {
+ for (long nY = 0; nY < nHeight; ++nY)
+ {
+ Scanline pSrc = pReadAcc->GetScanline(nY);
+ Scanline pDst = pWriteAcc->GetScanline(nY);
+ assert(pWriteAcc->GetScanlineSize() == pReadAcc->GetScanlineSize());
+ const long nScanlineSize = pWriteAcc->GetScanlineSize();
+ for (long nX = 0; nX < nScanlineSize; ++nX)
+ pDst[nX] = ~pSrc[nX];
+ }
+ }
+ else
+ {
+ // not optimized
+ for (long nY = 0; nY < nHeight; ++nY)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for (long nX = 0; nX < nWidth; ++nX)
+ {
+ if (aTest == pReadAcc->GetPixelFromData(pScanlineRead, nX))
+ pWriteAcc->SetPixelOnData(pScanline, nX, aWhite);
+ else
+ pWriteAcc->SetPixelOnData(pScanline, nX, aBlack);
+ }
+ }
+ }
+ }
+ else
+ {
+ BitmapColor aCol;
+ long nR, nG, nB;
+ const long nMinR = MinMax<long>(rTransColor.GetRed() - nTol, 0, 255);
+ const long nMaxR = MinMax<long>(rTransColor.GetRed() + nTol, 0, 255);
+ const long nMinG = MinMax<long>(rTransColor.GetGreen() - nTol, 0, 255);
+ const long nMaxG = MinMax<long>(rTransColor.GetGreen() + nTol, 0, 255);
+ const long nMinB = MinMax<long>(rTransColor.GetBlue() - nTol, 0, 255);
+ const long nMaxB = MinMax<long>(rTransColor.GetBlue() + nTol, 0, 255);
+
+ if (pReadAcc->HasPalette())
+ {
+ for (long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for (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 (long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for (long nX = 0; nX < nWidth; nX++)
+ {
+ aCol = pReadAcc->GetPixelFromData(pScanlineRead, nX);
+ nR = aCol.GetRed();
+ nG = aCol.GetGreen();
+ nB = aCol.GetBlue();
+
+ if (nMinR <= nR && nMaxR >= nR && nMinG <= nG && nMaxG >= nG && nMinB <= nB
+ && nMaxB >= nB)
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, aWhite);
+ }
+ else
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, aBlack);
+ }
+ }
+ }
+ }
+ }
+
+ bRet = true;
+ }
+
+ pWriteAcc.reset();
+ pReadAcc.reset();
+
+ if (bRet)
+ {
+ aNewBmp.maPrefSize = maPrefSize;
+ aNewBmp.maPrefMapMode = maPrefMapMode;
+ }
+ else
+ aNewBmp = Bitmap();
+
+ return aNewBmp;
+}
+
+vcl::Region Bitmap::CreateRegion(const Color& rColor, const tools::Rectangle& rRect) const
+{
+ vcl::Region aRegion;
+ tools::Rectangle aRect(rRect);
+ ScopedReadAccess pReadAcc(const_cast<Bitmap&>(*this));
+
+ aRect.Intersection(tools::Rectangle(Point(), GetSizePixel()));
+ aRect.Justify();
+
+ if (pReadAcc)
+ {
+ const long nLeft = aRect.Left();
+ const long nTop = aRect.Top();
+ const long nRight = aRect.Right();
+ const long nBottom = aRect.Bottom();
+ const BitmapColor aMatch(pReadAcc->GetBestMatchingColor(rColor));
+
+ std::vector<long> aLine;
+ long nYStart(nTop);
+ long nY(nTop);
+
+ for (; nY <= nBottom; nY++)
+ {
+ std::vector<long> aNewLine;
+ long nX(nLeft);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+
+ for (; nX <= nRight;)
+ {
+ while ((nX <= nRight) && (aMatch != pReadAcc->GetPixelFromData(pScanlineRead, nX)))
+ nX++;
+
+ if (nX <= nRight)
+ {
+ aNewLine.push_back(nX);
+
+ while ((nX <= nRight)
+ && (aMatch == pReadAcc->GetPixelFromData(pScanlineRead, nX)))
+ {
+ nX++;
+ }
+
+ aNewLine.push_back(nX - 1);
+ }
+ }
+
+ if (aNewLine != aLine)
+ {
+ // need to write aLine, it's different from the next line
+ if (!aLine.empty())
+ {
+ tools::Rectangle aSubRect;
+
+ // enter y values and proceed ystart
+ aSubRect.SetTop(nYStart);
+ aSubRect.SetBottom(nY ? nY - 1 : 0);
+
+ for (size_t a(0); a < aLine.size();)
+ {
+ aSubRect.SetLeft(aLine[a++]);
+ aSubRect.SetRight(aLine[a++]);
+ aRegion.Union(aSubRect);
+ }
+ }
+
+ // copy line as new line
+ aLine = aNewLine;
+ nYStart = nY;
+ }
+ }
+
+ // write last line if used
+ if (!aLine.empty())
+ {
+ tools::Rectangle aSubRect;
+
+ // enter y values
+ aSubRect.SetTop(nYStart);
+ aSubRect.SetBottom(nY ? nY - 1 : 0);
+
+ for (size_t a(0); a < aLine.size();)
+ {
+ aSubRect.SetLeft(aLine[a++]);
+ aSubRect.SetRight(aLine[a++]);
+ aRegion.Union(aSubRect);
+ }
+ }
+
+ pReadAcc.reset();
+ }
+ else
+ {
+ aRegion = aRect;
+ }
+
+ return aRegion;
+}
+
+bool Bitmap::Replace(const Bitmap& rMask, const Color& rReplaceColor)
+{
+ ScopedReadAccess pMaskAcc(const_cast<Bitmap&>(rMask));
+ BitmapScopedWriteAccess pAcc(*this);
+ bool bRet = false;
+
+ if (pMaskAcc && pAcc)
+ {
+ const long nWidth = std::min(pMaskAcc->Width(), pAcc->Width());
+ const long nHeight = std::min(pMaskAcc->Height(), pAcc->Height());
+ const BitmapColor aMaskWhite(pMaskAcc->GetBestMatchingColor(COL_WHITE));
+ BitmapColor aReplace;
+
+ if (pAcc->HasPalette())
+ {
+ const sal_uInt16 nActColors = pAcc->GetPaletteEntryCount();
+ const sal_uInt16 nMaxColors = 1 << pAcc->GetBitCount();
+
+ // default to the nearest color
+ aReplace = pAcc->GetBestMatchingColor(rReplaceColor);
+
+ // for paletted images without a matching palette entry
+ // look for an unused palette entry (NOTE: expensive!)
+ if (pAcc->GetPaletteColor(aReplace.GetIndex()) != BitmapColor(rReplaceColor))
+ {
+ // if the palette has empty entries use the last one
+ if (nActColors < nMaxColors)
+ {
+ pAcc->SetPaletteEntryCount(nActColors + 1);
+ pAcc->SetPaletteColor(nActColors, rReplaceColor);
+ aReplace = BitmapColor(static_cast<sal_uInt8>(nActColors));
+ }
+ else
+ {
+ std::unique_ptr<bool[]> pFlags(new bool[nMaxColors]);
+
+ // Set all entries to false
+ std::fill(pFlags.get(), pFlags.get() + nMaxColors, false);
+
+ for (long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pAcc->GetScanline(nY);
+ for (long nX = 0; nX < nWidth; nX++)
+ pFlags[pAcc->GetIndexFromData(pScanline, nX)] = true;
+ }
+
+ for (sal_uInt16 i = 0; i < nMaxColors; i++)
+ {
+ // Hurray, we do have an unused entry
+ if (!pFlags[i])
+ {
+ pAcc->SetPaletteColor(i, rReplaceColor);
+ aReplace = BitmapColor(static_cast<sal_uInt8>(i));
+ }
+ }
+ }
+ }
+ }
+ else
+ aReplace = rReplaceColor;
+
+ for (long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pAcc->GetScanline(nY);
+ Scanline pScanlineMask = pMaskAcc->GetScanline(nY);
+ for (long nX = 0; nX < nWidth; nX++)
+ {
+ if (pMaskAcc->GetPixelFromData(pScanlineMask, nX) == aMaskWhite)
+ pAcc->SetPixelOnData(pScanline, nX, aReplace);
+ }
+ }
+
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+bool Bitmap::Replace(const AlphaMask& rAlpha, const Color& rMergeColor)
+{
+ Bitmap aNewBmp(GetSizePixel(), 24);
+ ScopedReadAccess pAcc(*this);
+ AlphaMask::ScopedReadAccess pAlphaAcc(const_cast<AlphaMask&>(rAlpha));
+ BitmapScopedWriteAccess pNewAcc(aNewBmp);
+ bool bRet = false;
+
+ if (pAcc && pAlphaAcc && pNewAcc)
+ {
+ BitmapColor aCol;
+ const long nWidth = std::min(pAlphaAcc->Width(), pAcc->Width());
+ const long nHeight = std::min(pAlphaAcc->Height(), pAcc->Height());
+
+ for (long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pNewAcc->GetScanline(nY);
+ Scanline pScanlineAlpha = pAlphaAcc->GetScanline(nY);
+ for (long nX = 0; nX < nWidth; nX++)
+ {
+ aCol = pAcc->GetColor(nY, nX);
+ aCol.Merge(rMergeColor, 255 - pAlphaAcc->GetIndexFromData(pScanlineAlpha, nX));
+ pNewAcc->SetPixelOnData(pScanline, nX, aCol);
+ }
+ }
+
+ bRet = true;
+ }
+
+ pAcc.reset();
+ pAlphaAcc.reset();
+ pNewAcc.reset();
+
+ if (bRet)
+ {
+ const MapMode aMap(maPrefMapMode);
+ const Size aSize(maPrefSize);
+
+ *this = aNewBmp;
+
+ maPrefMapMode = aMap;
+ maPrefSize = aSize;
+ }
+
+ return bRet;
+}
+
+bool Bitmap::Replace(const Color& rSearchColor, const Color& rReplaceColor, sal_uInt8 nTol)
+{
+ if (mxSalBmp)
+ {
+ // implementation specific replace
+ std::shared_ptr<SalBitmap> xImpBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
+ if (xImpBmp->Create(*mxSalBmp) && xImpBmp->Replace(rSearchColor, rReplaceColor, nTol))
+ {
+ ImplSetSalBitmap(xImpBmp);
+ maPrefMapMode = MapMode(MapUnit::MapPixel);
+ maPrefSize = xImpBmp->GetSize();
+ return true;
+ }
+ }
+
+ // Bitmaps with 1 bit color depth can cause problems if they have other entries than black/white
+ // in their palette
+ if (GetBitCount() == 1)
+ Convert(BmpConversion::N4BitColors);
+
+ BitmapScopedWriteAccess pAcc(*this);
+ bool bRet = false;
+
+ if (pAcc)
+ {
+ const long nMinR = MinMax<long>(rSearchColor.GetRed() - nTol, 0, 255);
+ const long nMaxR = MinMax<long>(rSearchColor.GetRed() + nTol, 0, 255);
+ const long nMinG = MinMax<long>(rSearchColor.GetGreen() - nTol, 0, 255);
+ const long nMaxG = MinMax<long>(rSearchColor.GetGreen() + nTol, 0, 255);
+ const long nMinB = MinMax<long>(rSearchColor.GetBlue() - nTol, 0, 255);
+ const long nMaxB = MinMax<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 (long nY = 0, nHeight = pAcc->Height(); nY < nHeight; nY++)
+ {
+ Scanline pScanline = pAcc->GetScanline(nY);
+ for (long nX = 0, nWidth = pAcc->Width(); nX < nWidth; nX++)
+ {
+ aCol = pAcc->GetPixelFromData(pScanline, nX);
+
+ if (nMinR <= aCol.GetRed() && nMaxR >= aCol.GetRed() && nMinG <= aCol.GetGreen()
+ && nMaxG >= aCol.GetGreen() && nMinB <= aCol.GetBlue()
+ && nMaxB >= aCol.GetBlue())
+ {
+ pAcc->SetPixelOnData(pScanline, nX, aReplace);
+ }
+ }
+ }
+ }
+
+ pAcc.reset();
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+bool Bitmap::Replace(const Color* pSearchColors, const Color* pReplaceColors, sal_uLong nColorCount,
+ sal_uInt8 const* pTols)
+{
+ // Bitmaps with 1 bit color depth can cause problems if they have other entries than black/white
+ // in their palette
+ if (GetBitCount() == 1)
+ Convert(BmpConversion::N4BitColors);
+
+ BitmapScopedWriteAccess pAcc(*this);
+ bool bRet = false;
+
+ if (pAcc)
+ {
+ std::unique_ptr<long[]> pMinR(new long[nColorCount]);
+ std::unique_ptr<long[]> pMaxR(new long[nColorCount]);
+ std::unique_ptr<long[]> pMinG(new long[nColorCount]);
+ std::unique_ptr<long[]> pMaxG(new long[nColorCount]);
+ std::unique_ptr<long[]> pMinB(new long[nColorCount]);
+ std::unique_ptr<long[]> pMaxB(new long[nColorCount]);
+
+ if (pTols)
+ {
+ for (sal_uLong i = 0; i < nColorCount; i++)
+ {
+ const Color& rCol = pSearchColors[i];
+ const sal_uInt8 nTol = pTols[i];
+
+ pMinR[i] = MinMax<long>(rCol.GetRed() - nTol, 0, 255);
+ pMaxR[i] = MinMax<long>(rCol.GetRed() + nTol, 0, 255);
+ pMinG[i] = MinMax<long>(rCol.GetGreen() - nTol, 0, 255);
+ pMaxG[i] = MinMax<long>(rCol.GetGreen() + nTol, 0, 255);
+ pMinB[i] = MinMax<long>(rCol.GetBlue() - nTol, 0, 255);
+ pMaxB[i] = MinMax<long>(rCol.GetBlue() + nTol, 0, 255);
+ }
+ }
+ else
+ {
+ for (sal_uLong i = 0; i < nColorCount; i++)
+ {
+ const Color& rCol = pSearchColors[i];
+
+ pMinR[i] = rCol.GetRed();
+ pMaxR[i] = rCol.GetRed();
+ pMinG[i] = rCol.GetGreen();
+ pMaxG[i] = rCol.GetGreen();
+ pMinB[i] = rCol.GetBlue();
+ pMaxB[i] = rCol.GetBlue();
+ }
+ }
+
+ if (pAcc->HasPalette())
+ {
+ for (sal_uInt16 nEntry = 0, nPalCount = pAcc->GetPaletteEntryCount();
+ nEntry < nPalCount; nEntry++)
+ {
+ const BitmapColor& rCol = pAcc->GetPaletteColor(nEntry);
+
+ for (sal_uLong i = 0; i < nColorCount; i++)
+ {
+ if (pMinR[i] <= rCol.GetRed() && pMaxR[i] >= rCol.GetRed()
+ && pMinG[i] <= rCol.GetGreen() && pMaxG[i] >= rCol.GetGreen()
+ && pMinB[i] <= rCol.GetBlue() && pMaxB[i] >= rCol.GetBlue())
+ {
+ pAcc->SetPaletteColor(nEntry, pReplaceColors[i]);
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ BitmapColor aCol;
+ std::unique_ptr<BitmapColor[]> pReplaces(new BitmapColor[nColorCount]);
+
+ for (sal_uLong i = 0; i < nColorCount; i++)
+ pReplaces[i] = pAcc->GetBestMatchingColor(pReplaceColors[i]);
+
+ for (long nY = 0, nHeight = pAcc->Height(); nY < nHeight; nY++)
+ {
+ Scanline pScanline = pAcc->GetScanline(nY);
+ for (long nX = 0, nWidth = pAcc->Width(); nX < nWidth; nX++)
+ {
+ aCol = pAcc->GetPixelFromData(pScanline, nX);
+
+ for (sal_uLong i = 0; i < nColorCount; i++)
+ {
+ if (pMinR[i] <= aCol.GetRed() && pMaxR[i] >= aCol.GetRed()
+ && pMinG[i] <= aCol.GetGreen() && pMaxG[i] >= aCol.GetGreen()
+ && pMinB[i] <= aCol.GetBlue() && pMaxB[i] >= aCol.GetBlue())
+ {
+ pAcc->SetPixelOnData(pScanline, nX, pReplaces[i]);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ pAcc.reset();
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+bool Bitmap::CombineSimple(const Bitmap& rMask, BmpCombine eCombine)
+{
+ ScopedReadAccess pMaskAcc(const_cast<Bitmap&>(rMask));
+ BitmapScopedWriteAccess pAcc(*this);
+ bool bRet = false;
+
+ if (pMaskAcc && pAcc)
+ {
+ const long nWidth = std::min(pMaskAcc->Width(), pAcc->Width());
+ const long nHeight = std::min(pMaskAcc->Height(), pAcc->Height());
+ const Color aColBlack(COL_BLACK);
+ const BitmapColor aWhite(pAcc->GetBestMatchingColor(COL_WHITE));
+ const BitmapColor aBlack(pAcc->GetBestMatchingColor(aColBlack));
+ const BitmapColor aMaskBlack(pMaskAcc->GetBestMatchingColor(aColBlack));
+
+ switch (eCombine)
+ {
+ case BmpCombine::And:
+ {
+ for (long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pAcc->GetScanline(nY);
+ Scanline pScanlineMask = pMaskAcc->GetScanline(nY);
+ for (long nX = 0; nX < nWidth; nX++)
+ {
+ if (pMaskAcc->GetPixelFromData(pScanlineMask, nX) != aMaskBlack
+ && pAcc->GetPixelFromData(pScanline, nX) != aBlack)
+ {
+ pAcc->SetPixelOnData(pScanline, nX, aWhite);
+ }
+ else
+ {
+ pAcc->SetPixelOnData(pScanline, nX, aBlack);
+ }
+ }
+ }
+ }
+ break;
+
+ case BmpCombine::Or:
+ {
+ for (long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pAcc->GetScanline(nY);
+ Scanline pScanlineMask = pMaskAcc->GetScanline(nY);
+ for (long nX = 0; nX < nWidth; nX++)
+ {
+ if (pMaskAcc->GetPixelFromData(pScanlineMask, nX) != aMaskBlack
+ || pAcc->GetPixelFromData(pScanline, nX) != aBlack)
+ {
+ pAcc->SetPixelOnData(pScanline, nX, aWhite);
+ }
+ else
+ {
+ pAcc->SetPixelOnData(pScanline, nX, aBlack);
+ }
+ }
+ }
+ }
+ break;
+ }
+
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+// TODO: Have a look at OutputDevice::ImplDrawAlpha() for some
+// optimizations. Might even consolidate the code here and there.
+bool Bitmap::Blend(const AlphaMask& rAlpha, const Color& rBackgroundColor)
+{
+ // Convert to a truecolor bitmap, if we're a paletted one. There's room for tradeoff decision here,
+ // maybe later for an overload (or a flag)
+ if (GetBitCount() <= 8)
+ Convert(BmpConversion::N24Bit);
+
+ AlphaMask::ScopedReadAccess pAlphaAcc(const_cast<AlphaMask&>(rAlpha));
+
+ BitmapScopedWriteAccess pAcc(*this);
+ bool bRet = false;
+
+ if (pAlphaAcc && pAcc)
+ {
+ const long nWidth = std::min(pAlphaAcc->Width(), pAcc->Width());
+ const long nHeight = std::min(pAlphaAcc->Height(), pAcc->Height());
+
+ for (long nY = 0; nY < nHeight; ++nY)
+ {
+ Scanline pScanline = pAcc->GetScanline(nY);
+ Scanline pScanlineAlpha = pAlphaAcc->GetScanline(nY);
+ for (long nX = 0; nX < nWidth; ++nX)
+ {
+ BitmapColor aBmpColor = pAcc->GetPixelFromData(pScanline, nX);
+ aBmpColor.Merge(rBackgroundColor,
+ 255 - pAlphaAcc->GetIndexFromData(pScanlineAlpha, nX));
+ pAcc->SetPixelOnData(pScanline, nX, aBmpColor);
+ }
+ }
+
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/checksum.cxx b/vcl/source/bitmap/checksum.cxx
new file mode 100644
index 000000000..d33d88403
--- /dev/null
+++ b/vcl/source/bitmap/checksum.cxx
@@ -0,0 +1,154 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/types.h>
+#include <vcl/checksum.hxx>
+
+
+/*========================================================================
+ *
+ * vcl_crc64Table (CRC polynomial 0x95AC9329AC4BC9B5ULL).
+ *
+ *======================================================================*/
+static const sal_uInt64 vcl_crc64Table[256] = {
+ 0x0000000000000000ULL, 0x7ad870c830358979ULL, 0xf5b0e190606b12f2ULL,
+ 0x8f689158505e9b8bULL, 0xc038e5739841b68fULL, 0xbae095bba8743ff6ULL,
+ 0x358804e3f82aa47dULL, 0x4f50742bc81f2d04ULL, 0xab28ecb46814fe75ULL,
+ 0xd1f09c7c5821770cULL, 0x5e980d24087fec87ULL, 0x24407dec384a65feULL,
+ 0x6b1009c7f05548faULL, 0x11c8790fc060c183ULL, 0x9ea0e857903e5a08ULL,
+ 0xe478989fa00bd371ULL, 0x7d08ff3b88be6f81ULL, 0x07d08ff3b88be6f8ULL,
+ 0x88b81eabe8d57d73ULL, 0xf2606e63d8e0f40aULL, 0xbd301a4810ffd90eULL,
+ 0xc7e86a8020ca5077ULL, 0x4880fbd87094cbfcULL, 0x32588b1040a14285ULL,
+ 0xd620138fe0aa91f4ULL, 0xacf86347d09f188dULL, 0x2390f21f80c18306ULL,
+ 0x594882d7b0f40a7fULL, 0x1618f6fc78eb277bULL, 0x6cc0863448deae02ULL,
+ 0xe3a8176c18803589ULL, 0x997067a428b5bcf0ULL, 0xfa11fe77117cdf02ULL,
+ 0x80c98ebf2149567bULL, 0x0fa11fe77117cdf0ULL, 0x75796f2f41224489ULL,
+ 0x3a291b04893d698dULL, 0x40f16bccb908e0f4ULL, 0xcf99fa94e9567b7fULL,
+ 0xb5418a5cd963f206ULL, 0x513912c379682177ULL, 0x2be1620b495da80eULL,
+ 0xa489f35319033385ULL, 0xde51839b2936bafcULL, 0x9101f7b0e12997f8ULL,
+ 0xebd98778d11c1e81ULL, 0x64b116208142850aULL, 0x1e6966e8b1770c73ULL,
+ 0x8719014c99c2b083ULL, 0xfdc17184a9f739faULL, 0x72a9e0dcf9a9a271ULL,
+ 0x08719014c99c2b08ULL, 0x4721e43f0183060cULL, 0x3df994f731b68f75ULL,
+ 0xb29105af61e814feULL, 0xc849756751dd9d87ULL, 0x2c31edf8f1d64ef6ULL,
+ 0x56e99d30c1e3c78fULL, 0xd9810c6891bd5c04ULL, 0xa3597ca0a188d57dULL,
+ 0xec09088b6997f879ULL, 0x96d1784359a27100ULL, 0x19b9e91b09fcea8bULL,
+ 0x636199d339c963f2ULL, 0xdf7adabd7a6e2d6fULL, 0xa5a2aa754a5ba416ULL,
+ 0x2aca3b2d1a053f9dULL, 0x50124be52a30b6e4ULL, 0x1f423fcee22f9be0ULL,
+ 0x659a4f06d21a1299ULL, 0xeaf2de5e82448912ULL, 0x902aae96b271006bULL,
+ 0x74523609127ad31aULL, 0x0e8a46c1224f5a63ULL, 0x81e2d7997211c1e8ULL,
+ 0xfb3aa75142244891ULL, 0xb46ad37a8a3b6595ULL, 0xceb2a3b2ba0eececULL,
+ 0x41da32eaea507767ULL, 0x3b024222da65fe1eULL, 0xa2722586f2d042eeULL,
+ 0xd8aa554ec2e5cb97ULL, 0x57c2c41692bb501cULL, 0x2d1ab4dea28ed965ULL,
+ 0x624ac0f56a91f461ULL, 0x1892b03d5aa47d18ULL, 0x97fa21650afae693ULL,
+ 0xed2251ad3acf6feaULL, 0x095ac9329ac4bc9bULL, 0x7382b9faaaf135e2ULL,
+ 0xfcea28a2faafae69ULL, 0x8632586aca9a2710ULL, 0xc9622c4102850a14ULL,
+ 0xb3ba5c8932b0836dULL, 0x3cd2cdd162ee18e6ULL, 0x460abd1952db919fULL,
+ 0x256b24ca6b12f26dULL, 0x5fb354025b277b14ULL, 0xd0dbc55a0b79e09fULL,
+ 0xaa03b5923b4c69e6ULL, 0xe553c1b9f35344e2ULL, 0x9f8bb171c366cd9bULL,
+ 0x10e3202993385610ULL, 0x6a3b50e1a30ddf69ULL, 0x8e43c87e03060c18ULL,
+ 0xf49bb8b633338561ULL, 0x7bf329ee636d1eeaULL, 0x012b592653589793ULL,
+ 0x4e7b2d0d9b47ba97ULL, 0x34a35dc5ab7233eeULL, 0xbbcbcc9dfb2ca865ULL,
+ 0xc113bc55cb19211cULL, 0x5863dbf1e3ac9decULL, 0x22bbab39d3991495ULL,
+ 0xadd33a6183c78f1eULL, 0xd70b4aa9b3f20667ULL, 0x985b3e827bed2b63ULL,
+ 0xe2834e4a4bd8a21aULL, 0x6debdf121b863991ULL, 0x1733afda2bb3b0e8ULL,
+ 0xf34b37458bb86399ULL, 0x8993478dbb8deae0ULL, 0x06fbd6d5ebd3716bULL,
+ 0x7c23a61ddbe6f812ULL, 0x3373d23613f9d516ULL, 0x49aba2fe23cc5c6fULL,
+ 0xc6c333a67392c7e4ULL, 0xbc1b436e43a74e9dULL, 0x95ac9329ac4bc9b5ULL,
+ 0xef74e3e19c7e40ccULL, 0x601c72b9cc20db47ULL, 0x1ac40271fc15523eULL,
+ 0x5594765a340a7f3aULL, 0x2f4c0692043ff643ULL, 0xa02497ca54616dc8ULL,
+ 0xdafce7026454e4b1ULL, 0x3e847f9dc45f37c0ULL, 0x445c0f55f46abeb9ULL,
+ 0xcb349e0da4342532ULL, 0xb1eceec59401ac4bULL, 0xfebc9aee5c1e814fULL,
+ 0x8464ea266c2b0836ULL, 0x0b0c7b7e3c7593bdULL, 0x71d40bb60c401ac4ULL,
+ 0xe8a46c1224f5a634ULL, 0x927c1cda14c02f4dULL, 0x1d148d82449eb4c6ULL,
+ 0x67ccfd4a74ab3dbfULL, 0x289c8961bcb410bbULL, 0x5244f9a98c8199c2ULL,
+ 0xdd2c68f1dcdf0249ULL, 0xa7f41839ecea8b30ULL, 0x438c80a64ce15841ULL,
+ 0x3954f06e7cd4d138ULL, 0xb63c61362c8a4ab3ULL, 0xcce411fe1cbfc3caULL,
+ 0x83b465d5d4a0eeceULL, 0xf96c151de49567b7ULL, 0x76048445b4cbfc3cULL,
+ 0x0cdcf48d84fe7545ULL, 0x6fbd6d5ebd3716b7ULL, 0x15651d968d029fceULL,
+ 0x9a0d8ccedd5c0445ULL, 0xe0d5fc06ed698d3cULL, 0xaf85882d2576a038ULL,
+ 0xd55df8e515432941ULL, 0x5a3569bd451db2caULL, 0x20ed197575283bb3ULL,
+ 0xc49581ead523e8c2ULL, 0xbe4df122e51661bbULL, 0x3125607ab548fa30ULL,
+ 0x4bfd10b2857d7349ULL, 0x04ad64994d625e4dULL, 0x7e7514517d57d734ULL,
+ 0xf11d85092d094cbfULL, 0x8bc5f5c11d3cc5c6ULL, 0x12b5926535897936ULL,
+ 0x686de2ad05bcf04fULL, 0xe70573f555e26bc4ULL, 0x9ddd033d65d7e2bdULL,
+ 0xd28d7716adc8cfb9ULL, 0xa85507de9dfd46c0ULL, 0x273d9686cda3dd4bULL,
+ 0x5de5e64efd965432ULL, 0xb99d7ed15d9d8743ULL, 0xc3450e196da80e3aULL,
+ 0x4c2d9f413df695b1ULL, 0x36f5ef890dc31cc8ULL, 0x79a59ba2c5dc31ccULL,
+ 0x037deb6af5e9b8b5ULL, 0x8c157a32a5b7233eULL, 0xf6cd0afa9582aa47ULL,
+ 0x4ad64994d625e4daULL, 0x300e395ce6106da3ULL, 0xbf66a804b64ef628ULL,
+ 0xc5bed8cc867b7f51ULL, 0x8aeeace74e645255ULL, 0xf036dc2f7e51db2cULL,
+ 0x7f5e4d772e0f40a7ULL, 0x05863dbf1e3ac9deULL, 0xe1fea520be311aafULL,
+ 0x9b26d5e88e0493d6ULL, 0x144e44b0de5a085dULL, 0x6e963478ee6f8124ULL,
+ 0x21c640532670ac20ULL, 0x5b1e309b16452559ULL, 0xd476a1c3461bbed2ULL,
+ 0xaeaed10b762e37abULL, 0x37deb6af5e9b8b5bULL, 0x4d06c6676eae0222ULL,
+ 0xc26e573f3ef099a9ULL, 0xb8b627f70ec510d0ULL, 0xf7e653dcc6da3dd4ULL,
+ 0x8d3e2314f6efb4adULL, 0x0256b24ca6b12f26ULL, 0x788ec2849684a65fULL,
+ 0x9cf65a1b368f752eULL, 0xe62e2ad306bafc57ULL, 0x6946bb8b56e467dcULL,
+ 0x139ecb4366d1eea5ULL, 0x5ccebf68aecec3a1ULL, 0x2616cfa09efb4ad8ULL,
+ 0xa97e5ef8cea5d153ULL, 0xd3a62e30fe90582aULL, 0xb0c7b7e3c7593bd8ULL,
+ 0xca1fc72bf76cb2a1ULL, 0x45775673a732292aULL, 0x3faf26bb9707a053ULL,
+ 0x70ff52905f188d57ULL, 0x0a2722586f2d042eULL, 0x854fb3003f739fa5ULL,
+ 0xff97c3c80f4616dcULL, 0x1bef5b57af4dc5adULL, 0x61372b9f9f784cd4ULL,
+ 0xee5fbac7cf26d75fULL, 0x9487ca0fff135e26ULL, 0xdbd7be24370c7322ULL,
+ 0xa10fceec0739fa5bULL, 0x2e675fb4576761d0ULL, 0x54bf2f7c6752e8a9ULL,
+ 0xcdcf48d84fe75459ULL, 0xb71738107fd2dd20ULL, 0x387fa9482f8c46abULL,
+ 0x42a7d9801fb9cfd2ULL, 0x0df7adabd7a6e2d6ULL, 0x772fdd63e7936bafULL,
+ 0xf8474c3bb7cdf024ULL, 0x829f3cf387f8795dULL, 0x66e7a46c27f3aa2cULL,
+ 0x1c3fd4a417c62355ULL, 0x935745fc4798b8deULL, 0xe98f353477ad31a7ULL,
+ 0xa6df411fbfb21ca3ULL, 0xdc0731d78f8795daULL, 0x536fa08fdfd90e51ULL,
+ 0x29b7d047efec8728ULL
+};
+
+/*========================================================================
+ *
+ * vcl_crc64 implementation.
+ *
+ *======================================================================*/
+#define UPDCRC64(crc, octet) \
+ (vcl_crc64Table[((crc) ^ (octet)) & 0xff] ^ ((crc) >> 8))
+
+/*
+ * vcl_crc64.
+ */
+sal_uInt64 vcl_crc64 (
+ sal_uInt64 Crc,
+ const void *Data, sal_uInt32 DatLen) SAL_THROW_EXTERN_C()
+{
+ if (Data)
+ {
+ const sal_uInt8 *p = static_cast<const sal_uInt8 *>(Data);
+ const sal_uInt8 *q = p + DatLen;
+
+ Crc = ~Crc;
+ while (p < q)
+ Crc = UPDCRC64(Crc, *(p++));
+ Crc = ~Crc;
+ }
+ return Crc;
+}
+
+/*
+ * vcl_get_crc64_table.
+ */
+const sal_uInt64* vcl_get_crc64_table()
+{
+ return vcl_crc64Table;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/salbmp.cxx b/vcl/source/bitmap/salbmp.cxx
new file mode 100644
index 000000000..f731a5690
--- /dev/null
+++ b/vcl/source/bitmap/salbmp.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 <salbmp.hxx>
+
+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();
+ nCrc = vcl_get_checksum(nCrc, pBuf->mpBits, pBuf->mnScanlineSize * pBuf->mnHeight);
+ 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;
+}
+
+} // 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 int bpp[] = { 1, 3, 3, 4, 4 };
+ std::unique_ptr< sal_uInt8[] > data( new sal_uInt8[width * height * bpp[ static_cast<int>(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;
+ }
+
+ 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::BGR :
+ while( nX-- )
+ {
+ const BitmapColor& c = pSrcFormat->ReadPixel();
+ *pDstData++ = c.GetBlue();
+ *pDstData++ = c.GetGreen();
+ *pDstData++ = c.GetRed();
+ }
+ break;
+ case BitConvert::RGB :
+ while( nX-- )
+ {
+ const BitmapColor& c = pSrcFormat->ReadPixel();
+ *pDstData++ = c.GetRed();
+ *pDstData++ = c.GetGreen();
+ *pDstData++ = c.GetBlue();
+ }
+ break;
+ case BitConvert::BGRA :
+ while( nX-- )
+ {
+ const BitmapColor& c = pSrcFormat->ReadPixel();
+ *pDstData++ = c.GetBlue();
+ *pDstData++ = c.GetGreen();
+ *pDstData++ = c.GetRed();
+ *pDstData++ = 0xff;
+ }
+ break;
+ case BitConvert::RGBA :
+ while( nX-- )
+ {
+ const BitmapColor& c = pSrcFormat->ReadPixel();
+ *pDstData++ = c.GetRed();
+ *pDstData++ = c.GetGreen();
+ *pDstData++ = c.GetBlue();
+ *pDstData++ = 0xff;
+ }
+ break;
+ }
+
+ pSrcData += bytesPerRow;
+ }
+ return data;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/components/dtranscomp.cxx b/vcl/source/components/dtranscomp.cxx
new file mode 100644
index 000000000..c7bda5361
--- /dev/null
+++ b/vcl/source/components/dtranscomp.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <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/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 <cppuhelper/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 cppu::WeakComponentImplHelper<
+ datatransfer::clipboard::XSystemClipboard,
+ XServiceInfo
+ >
+{
+ osl::Mutex m_aMutex;
+ Reference< css::datatransfer::XTransferable > m_aContents;
+ Reference< css::datatransfer::clipboard::XClipboardOwner > m_aOwner;
+ std::vector< Reference< css::datatransfer::clipboard::XClipboardListener > > m_aListeners;
+
+public:
+
+ GenericClipboard() : cppu::WeakComponentImplHelper<
+ datatransfer::clipboard::XSystemClipboard,
+ XServiceInfo
+ >( m_aMutex )
+ {}
+
+ /*
+ * 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 )
+{
+ osl::ClearableMutexGuard 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.clear();
+
+ 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 )
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ m_aListeners.push_back( listener );
+}
+
+void GenericClipboard::removeClipboardListener( const Reference< datatransfer::clipboard::XClipboardListener >& listener )
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ m_aListeners.erase(std::remove(m_aListeners.begin(), m_aListeners.end(), listener), m_aListeners.end());
+}
+
+namespace {
+
+class ClipboardFactory : public ::cppu::WeakComponentImplHelper<
+ css::lang::XSingleServiceFactory
+>
+{
+ osl::Mutex m_aMutex;
+public:
+ ClipboardFactory();
+
+ /*
+ * XSingleServiceFactory
+ */
+ virtual Reference< XInterface > SAL_CALL createInstance() override;
+ virtual Reference< XInterface > SAL_CALL createInstanceWithArguments( const Sequence< Any >& rArgs ) override;
+};
+
+}
+
+ClipboardFactory::ClipboardFactory() :
+ cppu::WeakComponentImplHelper<
+ css::lang::XSingleServiceFactory
+>( m_aMutex )
+{
+}
+
+Reference< XInterface > ClipboardFactory::createInstance()
+{
+ return createInstanceWithArguments( Sequence< Any >() );
+}
+
+Reference< XInterface > ClipboardFactory::createInstanceWithArguments( const Sequence< Any >& arguments )
+{
+ SolarMutexGuard aGuard;
+ Reference< XInterface > xResult = ImplGetSVData()->mpDefInst->CreateClipboard( arguments );
+ return xResult;
+}
+
+OUString Clipboard_getImplementationName()
+{
+ return
+ #if defined MACOSX
+ "com.sun.star.datatransfer.clipboard.AquaClipboard"
+ #elif defined IOS
+ "com.sun.star.datatransfer.clipboard.iOSClipboard"
+ #elif defined ANDROID
+ "com.sun.star.datatransfer.VCLGenericClipboard"
+ #elif defined UNX
+ "com.sun.star.datatransfer.X11ClipboardSupport"
+ #else
+ "com.sun.star.datatransfer.VCLGenericClipboard"
+ #endif
+ ;
+}
+
+Reference< XSingleServiceFactory > Clipboard_createFactory()
+{
+ return Reference< XSingleServiceFactory >( new ClipboardFactory() );
+}
+
+namespace {
+
+/*
+* generic DragSource dummy
+*/
+class GenericDragSource : public cppu::WeakComponentImplHelper<
+ datatransfer::dnd::XDragSource,
+ XInitialization,
+ css::lang::XServiceInfo
+ >
+{
+ osl::Mutex m_aMutex;
+public:
+ GenericDragSource() : WeakComponentImplHelper( m_aMutex ) {}
+
+ // 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 >&,
+ const Reference< datatransfer::dnd::XDragSourceListener >& listener
+ )
+{
+ 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 cppu::WeakComponentImplHelper<
+ datatransfer::dnd::XDropTarget,
+ XInitialization,
+ css::lang::XServiceInfo
+ >
+{
+ osl::Mutex m_aMutex;
+public:
+ GenericDropTarget() : WeakComponentImplHelper( m_aMutex )
+ {}
+
+ // 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);
+ }
+ if (comphelper::LibreOfficeKit::isActive()) {
+ // In LOK, each document view shall have its own clipboard instance, and the way that
+ // (happens to?) work is that apparently this function is called at most once for each such
+ // document view, so it is OK if we hand out a fresh instance on each call in LOK (whereas
+ // in non-LOK below we keep handing out one single instance; see also
+ // <https://lists.freedesktop.org/archives/libreoffice/2020-April/084824.html> "Re: Linux
+ // SAL_USE_VCLPLUGIN=svp and the clipboard"):
+ return Reference< XInterface >( static_cast<cppu::OWeakObject *>(new vcl::GenericClipboard()) );
+ }
+ DBG_TESTSOLARMUTEX();
+ if (!m_clipboard.is()) {
+ m_clipboard = static_cast<cppu::OWeakObject *>(new vcl::GenericClipboard());
+ }
+ return m_clipboard;
+}
+
+Reference< XInterface > SalInstance::CreateDragSource()
+{
+ return Reference< XInterface >( static_cast<cppu::OWeakObject *>(new vcl::GenericDragSource()) );
+}
+
+Reference< XInterface > SalInstance::CreateDropTarget()
+{
+ return Reference< XInterface >( static_cast<cppu::OWeakObject *>(new vcl::GenericDropTarget()) );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/components/factory.cxx b/vcl/source/components/factory.cxx
new file mode 100644
index 000000000..a82c71435
--- /dev/null
+++ b/vcl/source/components/factory.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 <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_session_getImplementationName().equalsAscii( pImplementationName ) )
+ {
+ xFactory = ::cppu::createOneInstanceFactory(
+ xMgr, vcl_session_getImplementationName(), vcl_session_createInstance,
+ vcl_session_getSupportedServiceNames() );
+ }
+ else if( vcl::FontIdentificator_getImplementationName().equalsAscii( pImplementationName ) )
+ {
+ xFactory = ::cppu::createSingleFactory(
+ xMgr, vcl::FontIdentificator_getImplementationName(), vcl::FontIdentificator_createInstance,
+ vcl::FontIdentificator_getSupportedServiceNames() );
+ }
+ else if( vcl::Clipboard_getImplementationName().equalsAscii( pImplementationName ) )
+ {
+ xFactory = vcl::Clipboard_createFactory();
+ }
+ else if( vcl::DragSource_getImplementationName().equalsAscii( pImplementationName ) )
+ {
+ xFactory = ::cppu::createSingleFactory(
+ xMgr, vcl::DragSource_getImplementationName(), vcl::DragSource_createInstance,
+ vcl::DragSource_getSupportedServiceNames() );
+ }
+ else if( vcl::DropTarget_getImplementationName().equalsAscii( pImplementationName ) )
+ {
+ xFactory = ::cppu::createSingleFactory(
+ xMgr, vcl::DropTarget_getImplementationName(), vcl::DropTarget_createInstance,
+ vcl::DropTarget_getSupportedServiceNames() );
+ }
+ if( xFactory.is() )
+ {
+ xFactory->acquire();
+ pRet = xFactory.get();
+ }
+ }
+ return pRet;
+ }
+
+} /* extern "C" */
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/components/fontident.cxx b/vcl/source/components/fontident.cxx
new file mode 100644
index 000000000..b5ad54e5d
--- /dev/null
+++ b/vcl/source/components/fontident.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/svapp.hxx>
+#include <vcl/font.hxx>
+
+#include <factory.hxx>
+#include <svdata.hxx>
+
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/beans/XMaterialHolder.hpp>
+#include <com/sun/star/awt/FontDescriptor.hpp>
+#include <com/sun/star/awt/FontFamily.hpp>
+#include <com/sun/star/awt/FontPitch.hpp>
+#include <com/sun/star/awt/FontWeight.hpp>
+#include <com/sun/star/awt/FontSlant.hpp>
+#include <com/sun/star/lang/XInitialization.hpp>
+
+#include <cppuhelper/implbase3.hxx>
+#include <cppuhelper/supportsservice.hxx>
+
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::beans;
+using namespace ::com::sun::star::awt;
+
+namespace vcl
+{
+
+namespace {
+
+class FontIdentificator : public ::cppu::WeakAggImplHelper3< XMaterialHolder, XInitialization, XServiceInfo >
+{
+ Font m_aFont;
+public:
+FontIdentificator() {}
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName( ) override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ) override;
+ virtual Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override;
+
+ // XInitialization
+ virtual void SAL_CALL initialize( const Sequence< Any >& ) override;
+
+ // XMaterialHolder
+ virtual Any SAL_CALL getMaterial() override;
+
+};
+
+}
+
+void SAL_CALL FontIdentificator::initialize( const Sequence<Any>& i_rArgs )
+{
+ if( !ImplGetSVData() )
+ return; // VCL not initialized
+
+ Sequence< sal_Int8 > aFontBuf;
+ for( const auto& rArg : i_rArgs )
+ {
+ if( rArg >>= aFontBuf )
+ {
+ m_aFont = Font::identifyFont( aFontBuf.getConstArray(), aFontBuf.getLength() );
+ break;
+ }
+ }
+}
+
+Any SAL_CALL FontIdentificator::getMaterial()
+{
+ if( !ImplGetSVData() )
+ return Any(); // VCL not initialized
+
+ FontDescriptor aFD;
+ aFD.Name = m_aFont.GetFamilyName();
+ aFD.Height = 0;
+ aFD.Width = 0;
+ aFD.StyleName = m_aFont.GetStyleName();
+ aFD.CharSet = 0;
+ aFD.CharacterWidth = 0;
+ aFD.Underline = 0;
+ aFD.Strikeout = 0;
+ aFD.Orientation = 0;
+ aFD.Kerning = false;
+ aFD.WordLineMode = false;
+ aFD.Type = 0;
+ switch( m_aFont.GetFamilyType() )
+ {
+ case FAMILY_DECORATIVE: aFD.Family = css::awt::FontFamily::DECORATIVE;break;
+ case FAMILY_MODERN: aFD.Family = css::awt::FontFamily::MODERN;break;
+ case FAMILY_ROMAN: aFD.Family = css::awt::FontFamily::ROMAN;break;
+ case FAMILY_SCRIPT: aFD.Family = css::awt::FontFamily::SCRIPT;break;
+ case FAMILY_SWISS: aFD.Family = css::awt::FontFamily::SWISS;break;
+ case FAMILY_SYSTEM: aFD.Family = css::awt::FontFamily::SYSTEM;break;
+ default:
+ aFD.Family = css::awt::FontFamily::DONTKNOW;
+ break;
+ }
+ switch( m_aFont.GetPitch() )
+ {
+ case PITCH_VARIABLE: aFD.Pitch = css::awt::FontPitch::VARIABLE;break;
+ case PITCH_FIXED: aFD.Pitch = css::awt::FontPitch::FIXED;break;
+ default:
+ aFD.Pitch = css::awt::FontPitch::DONTKNOW;
+ break;
+ }
+ switch( m_aFont.GetWeight() )
+ {
+ case WEIGHT_THIN: aFD.Weight = css::awt::FontWeight::THIN;break;
+ case WEIGHT_ULTRALIGHT: aFD.Weight = css::awt::FontWeight::ULTRALIGHT;break;
+ case WEIGHT_LIGHT: aFD.Weight = css::awt::FontWeight::LIGHT;break;
+ case WEIGHT_SEMILIGHT: aFD.Weight = css::awt::FontWeight::SEMILIGHT;break;
+ case WEIGHT_MEDIUM:
+ case WEIGHT_NORMAL: aFD.Weight = css::awt::FontWeight::NORMAL;break;
+ case WEIGHT_SEMIBOLD: aFD.Weight = css::awt::FontWeight::SEMIBOLD;break;
+ case WEIGHT_BOLD: aFD.Weight = css::awt::FontWeight::BOLD;break;
+ case WEIGHT_ULTRABOLD: aFD.Weight = css::awt::FontWeight::ULTRABOLD;break;
+ case WEIGHT_BLACK: aFD.Weight = css::awt::FontWeight::BLACK;break;
+ default:
+ aFD.Weight = css::awt::FontWeight::DONTKNOW;
+ break;
+ }
+ switch( m_aFont.GetItalic() )
+ {
+ case ITALIC_OBLIQUE: aFD.Slant = css::awt::FontSlant_OBLIQUE;break;
+ case ITALIC_NORMAL: aFD.Slant = css::awt::FontSlant_ITALIC;break;
+ default:
+ aFD.Slant = css::awt::FontSlant_DONTKNOW;
+ break;
+ }
+ return makeAny( aFD );
+}
+
+Sequence< OUString > FontIdentificator_getSupportedServiceNames()
+{
+ return Sequence< OUString >{ "com.sun.star.awt.FontIdentificator" };
+}
+
+OUString FontIdentificator_getImplementationName()
+{
+ return "vcl::FontIdentificator";
+}
+
+Reference< XInterface > FontIdentificator_createInstance( const Reference< XMultiServiceFactory >& )
+{
+ return static_cast< ::cppu::OWeakObject * >( new FontIdentificator );
+}
+
+// XServiceInfo
+OUString SAL_CALL FontIdentificator::getImplementationName()
+{
+ return FontIdentificator_getImplementationName();
+}
+
+sal_Bool SAL_CALL FontIdentificator::supportsService( const OUString& i_rServiceName )
+{
+ return cppu::supportsService(this, i_rServiceName);
+}
+
+Sequence< OUString > SAL_CALL FontIdentificator::getSupportedServiceNames()
+{
+ return FontIdentificator_getSupportedServiceNames();
+}
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/InterimItemWindow.cxx b/vcl/source/control/InterimItemWindow.cxx
new file mode 100644
index 000000000..f9f54d757
--- /dev/null
+++ b/vcl/source/control/InterimItemWindow.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/.
+ */
+
+#include <vcl/InterimItemWindow.hxx>
+#include <vcl/layout.hxx>
+
+InterimItemWindow::InterimItemWindow(vcl::Window* pParent, const OUString& rUIXMLDescription,
+ const OString& rID)
+ : Control(pParent, WB_TABSTOP | WB_DIALOGCONTROL)
+{
+ m_xVclContentArea = VclPtr<VclVBox>::Create(this);
+ m_xVclContentArea->Show();
+ m_xBuilder.reset(Application::CreateInterimBuilder(m_xVclContentArea, rUIXMLDescription));
+ m_xContainer = m_xBuilder->weld_container(rID);
+
+ SetBackground();
+ SetPaintTransparent(true);
+}
+
+InterimItemWindow::~InterimItemWindow() { disposeOnce(); }
+
+void InterimItemWindow::dispose()
+{
+ m_xContainer.reset();
+ m_xBuilder.reset();
+ m_xVclContentArea.disposeAndClear();
+
+ Control::dispose();
+}
+
+void InterimItemWindow::Resize()
+{
+ 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::GetFocus()
+{
+ /* let toolbox know this item window has focus so it updates its mnHighItemId to point
+ to this toolitem in case tab means to move to another toolitem within
+ the toolbox
+ */
+ vcl::Window* pToolBox = GetParent();
+ NotifyEvent aNEvt(MouseNotifyEvent::GETFOCUS, this);
+ pToolBox->EventNotify(aNEvt);
+}
+
+bool InterimItemWindow::ChildKeyInput(const KeyEvent& rKEvt)
+{
+ sal_uInt16 nCode = rKEvt.GetKeyCode().GetCode();
+ if (nCode != KEY_TAB)
+ return false;
+
+ /* if the native widget has focus, then no vcl window has focus.
+
+ We want to grab focus to this vcl widget so that pressing tab will traverse
+ to the next vcl widget.
+
+ But just using GrabFocus will, because no vcl widget has focus, trigger
+ bringing the toplevel to front with the expectation that a suitable widget
+ will be picked for focus when that happen, which is no use to us here.
+
+ SetFakeFocus avoids the problem, allowing GrabFocus to do the expected thing
+ then sending the Tab to our parent will do the right traversal
+ */
+ SetFakeFocus(true);
+ GrabFocus();
+
+ /* now give focus to our toolbox parent */
+ vcl::Window* pToolBox = GetParent();
+ pToolBox->GrabFocus();
+
+ /* let toolbox know this item window has focus so it updates its mnHighItemId to point
+ to this toolitem in case tab means to move to another toolitem within
+ the toolbox
+ */
+ NotifyEvent aNEvt(MouseNotifyEvent::GETFOCUS, this);
+ pToolBox->EventNotify(aNEvt);
+
+ /* send parent the tab */
+ pToolBox->KeyInput(rKEvt);
+
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/source/control/button.cxx b/vcl/source/control/button.cxx
new file mode 100644
index 000000000..3ad6fded0
--- /dev/null
+++ b/vcl/source/control/button.cxx
@@ -0,0 +1,3705 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/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/fixed.hxx>
+#include <vcl/toolkit/button.hxx>
+#include <vcl/salnativewidgets.hxx>
+#include <vcl/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 <controldata.hxx>
+#include <vclstatuslistener.hxx>
+#include <osl/diagnose.h>
+
+#include <comphelper/dispatchcommand.hxx>
+#include <comphelper/lok.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <boost/property_tree/ptree.hpp>
+
+
+using namespace css;
+
+static 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;
+static constexpr auto RADIOBUTTON_VIEW_STYLE = WB_3DLOOK |
+ WB_LEFT | WB_CENTER | WB_RIGHT |
+ WB_TOP | WB_VCENTER | WB_BOTTOM |
+ WB_WORDBREAK | WB_NOLABEL;
+static 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;
+ long mnSeparatorX;
+ DrawButtonFlags mnButtonState;
+ bool mbSmallSymbol;
+
+ Image maImage;
+ ImageAlign meImageAlign;
+ SymbolAlign meSymbolAlign;
+
+ /** StatusListener. Updates the button as the slot state changes */
+ rtl::Reference<VclStatusListener<Button>> mpStatusListener;
+};
+
+ImplCommonButtonData::ImplCommonButtonData() : maFocusRect(), mnSeparatorX(0), mnButtonState(DrawButtonFlags::NONE),
+mbSmallSymbol(false), maImage(), meImageAlign(ImageAlign::Top), meSymbolAlign(SymbolAlign::LEFT)
+{
+}
+
+Button::Button( WindowType nType ) :
+ Control( nType ),
+ mpButtonData( std::make_unique<ImplCommonButtonData>() )
+{
+}
+
+Button::~Button()
+{
+ disposeOnce();
+}
+
+void Button::dispose()
+{
+ if (mpButtonData->mpStatusListener.is())
+ mpButtonData->mpStatusListener->dispose();
+ Control::dispose();
+}
+
+void Button::SetCommandHandler(const OUString& aCommand)
+{
+ maCommand = aCommand;
+ SetClickHdl( LINK( this, Button, dispatchCommandHandler) );
+
+ mpButtonData->mpStatusListener = new VclStatusListener<Button>(this, aCommand);
+ mpButtonData->mpStatusListener->startListening();
+}
+
+void Button::Click()
+{
+ ImplCallEventListenersAndHandler( VclEventId::ButtonClick, [this] () { maClickHdl.Call(this); } );
+}
+
+void Button::SetModeImage( const Image& rImage )
+{
+ if ( rImage != mpButtonData->maImage )
+ {
+ mpButtonData->maImage = rImage;
+ StateChanged( StateChangedType::Data );
+ queue_resize();
+ }
+}
+
+Image const & Button::GetModeImage( ) const
+{
+ return mpButtonData->maImage;
+}
+
+bool Button::HasImage() const
+{
+ return !!(mpButtonData->maImage);
+}
+
+void Button::SetImageAlign( ImageAlign eAlign )
+{
+ if ( mpButtonData->meImageAlign != eAlign )
+ {
+ mpButtonData->meImageAlign = eAlign;
+ StateChanged( StateChangedType::Data );
+ }
+}
+
+ImageAlign Button::GetImageAlign() const
+{
+ return mpButtonData->meImageAlign;
+}
+
+long Button::ImplGetSeparatorX() const
+{
+ return mpButtonData->mnSeparatorX;
+}
+
+void Button::ImplSetSeparatorX( long nX )
+{
+ mpButtonData->mnSeparatorX = nX;
+}
+
+DrawTextFlags Button::ImplGetTextStyle( WinBits nWinStyle, DrawFlags nDrawFlags )
+{
+ const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
+ DrawTextFlags nTextStyle = FixedText::ImplGetTextStyle(nWinStyle & ~WB_DEFBUTTON);
+
+ if (!IsEnabled())
+ nTextStyle |= DrawTextFlags::Disable;
+
+ if ((nDrawFlags & DrawFlags::Mono) ||
+ (rStyleSettings.GetOptions() & StyleSettingsOptions::Mono))
+ {
+ nTextStyle |= DrawTextFlags::Mono;
+ }
+
+ return nTextStyle;
+}
+
+void Button::ImplDrawAlignedImage(OutputDevice* pDev, Point& rPos,
+ Size& rSize,
+ sal_uLong 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().isEmpty() && textRect.getWidth() > rSize.getWidth())
+ SetQuickHelpText(aText);
+
+ 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;
+ Size aMax;
+ Point aImagePos = rPos;
+ Point aTextPos = rPos;
+ tools::Rectangle aUnion(aImagePos, aImageSize);
+ tools::Rectangle aSymbol;
+ 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)
+ {
+ 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)
+ {
+ long nDiff = (aImageSize.Height() - aTextSize.Height()) / 3;
+ if (nDiff > 0)
+ nImageSep += nDiff;
+ }
+ }
+
+ 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)
+ long nXOffset = 0;
+ 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->setY(aTextPos.Y() + nYOffset);
+ }
+ }
+
+ DrawImageFlags nStyle = DrawImageFlags::NONE;
+
+ if (!IsEnabled())
+ {
+ nStyle |= DrawImageFlags::Disable;
+ }
+
+ if (IsZoom())
+ pDev->DrawImage(aImagePos, aImageSize, *pImage, nStyle);
+ else
+ pDev->DrawImage(aImagePos, *pImage, nStyle);
+
+ if (bDrawText)
+ {
+ const tools::Rectangle aTOutRect(aTextPos, aTextSize);
+ ImplSetFocusRect(aTOutRect);
+ DrawControlText(*pDev, aTOutRect, aText, nTextStyle, nullptr, nullptr, &aDeviceTextSize);
+ }
+ else
+ {
+ ImplSetFocusRect(tools::Rectangle(aImagePos, aImageSize));
+ }
+}
+
+void Button::ImplSetFocusRect(const tools::Rectangle &rFocusRect)
+{
+ tools::Rectangle aFocusRect = rFocusRect;
+ tools::Rectangle aOutputRect(Point(), GetOutputSizePixel());
+
+ if (!aFocusRect.IsEmpty())
+ {
+ aFocusRect.AdjustLeft( -1 );
+ aFocusRect.AdjustTop( -1 );
+ aFocusRect.AdjustRight( 1 );
+ aFocusRect.AdjustBottom( 1 );
+ }
+
+ if (aFocusRect.Left() < aOutputRect.Left())
+ aFocusRect.SetLeft( aOutputRect.Left() );
+ if (aFocusRect.Top() < aOutputRect.Top())
+ aFocusRect.SetTop( aOutputRect.Top() );
+ if (aFocusRect.Right() > aOutputRect.Right())
+ aFocusRect.SetRight( aOutputRect.Right() );
+ if (aFocusRect.Bottom() > aOutputRect.Bottom())
+ aFocusRect.SetBottom( aOutputRect.Bottom() );
+
+ mpButtonData->maFocusRect = aFocusRect;
+}
+
+const tools::Rectangle& Button::ImplGetFocusRect() const
+{
+ return mpButtonData->maFocusRect;
+}
+
+DrawButtonFlags& Button::GetButtonState()
+{
+ return mpButtonData->mnButtonState;
+}
+
+DrawButtonFlags Button::GetButtonState() const
+{
+ return mpButtonData->mnButtonState;
+}
+
+void Button::ImplSetSymbolAlign( SymbolAlign eAlign )
+{
+ if ( mpButtonData->meSymbolAlign != eAlign )
+ {
+ mpButtonData->meSymbolAlign = eAlign;
+ StateChanged( StateChangedType::Data );
+ }
+}
+
+void Button::SetSmallSymbol()
+{
+ mpButtonData->mbSmallSymbol = true;
+}
+
+bool Button::IsSmallSymbol () const
+{
+ return mpButtonData->mbSmallSymbol;
+}
+
+bool Button::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "image-position")
+ {
+ ImageAlign eAlign = ImageAlign::Left;
+ if (rValue == "left")
+ eAlign = ImageAlign::Left;
+ else if (rValue == "right")
+ eAlign = ImageAlign::Right;
+ else if (rValue == "top")
+ eAlign = ImageAlign::Top;
+ else if (rValue == "bottom")
+ eAlign = ImageAlign::Bottom;
+ SetImageAlign(eAlign);
+ }
+ else if (rKey == "focus-on-click")
+ {
+ WinBits nBits = GetStyle();
+ nBits &= ~WB_NOPOINTERFOCUS;
+ if (!toBool(rValue))
+ nBits |= WB_NOPOINTERFOCUS;
+ SetStyle(nBits);
+ }
+ else
+ return Control::set_property(rKey, rValue);
+ return true;
+}
+
+void Button::statusChanged(const css::frame::FeatureStateEvent& rEvent)
+{
+ Enable(rEvent.IsEnabled);
+}
+
+FactoryFunction Button::GetUITestFactory() const
+{
+ return ButtonUIObject::create;
+}
+
+boost::property_tree::ptree Button::DumpAsPropertyTree()
+{
+ boost::property_tree::ptree aTree(Control::DumpAsPropertyTree());
+ aTree.put("text", GetText());
+ return aTree;
+}
+
+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 )
+ {
+ 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();
+ 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.IsInside( rPos );
+}
+
+DrawTextFlags PushButton::ImplGetTextStyle( DrawFlags nDrawFlags ) const
+{
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+
+ DrawTextFlags nTextStyle = DrawTextFlags::Mnemonic | DrawTextFlags::MultiLine | DrawTextFlags::EndEllipsis;
+
+ if ( ( rStyleSettings.GetOptions() & StyleSettingsOptions::Mono ) ||
+ ( nDrawFlags & DrawFlags::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, DrawFlags nDrawFlags,
+ const tools::Rectangle &rRect, bool bMenuBtnSep,
+ DrawButtonFlags nButtonFlags)
+{
+ const StyleSettings &rStyleSettings = GetSettings().GetStyleSettings();
+ tools::Rectangle aInRect = rRect;
+ Color aColor;
+ DrawTextFlags nTextStyle = ImplGetTextStyle(nDrawFlags);
+ DrawSymbolFlags nStyle;
+
+ if (aInRect.Right() < aInRect.Left() || aInRect.Bottom() < aInRect.Top())
+ return;
+
+ pDev->Push(PushFlags::CLIPREGION);
+ pDev->IntersectClipRegion(aInRect);
+
+ if (nDrawFlags & DrawFlags::Mono)
+ aColor = COL_BLACK;
+
+ // Custom foreground color is reasonable on stock controls only. Stock controls are used if a custom background has been set
+ // (and thus no native controls are able to be used) or no native controls are available.
+
+ else if (IsControlForeground()
+ && (IsControlBackground() || !IsNativeControlSupported(ControlType::Pushbutton, ControlPart::Entire)))
+ aColor = GetControlForeground();
+
+ // Button types with possibly different text coloring are flat buttons and regular buttons. Regular buttons may be action
+ // buttons and may have an additional default status. Moreover all buttons may have an additional pressed and rollover
+ // (highlight) status. Pressed buttons are always in rollover status.
+
+ else if (GetStyle() & WB_FLATBUTTON)
+ if (nButtonFlags & DrawButtonFlags::Pressed)
+ aColor = rStyleSettings.GetFlatButtonPressedRolloverTextColor();
+ else if (nButtonFlags & DrawButtonFlags::Highlight)
+ aColor = rStyleSettings.GetFlatButtonRolloverTextColor();
+ else
+ aColor = rStyleSettings.GetFlatButtonTextColor();
+ else
+ if (isAction() && (nButtonFlags & DrawButtonFlags::Default))
+ if (nButtonFlags & DrawButtonFlags::Pressed)
+ aColor = rStyleSettings.GetDefaultActionButtonPressedRolloverTextColor();
+ else if (nButtonFlags & DrawButtonFlags::Highlight)
+ aColor = rStyleSettings.GetDefaultActionButtonRolloverTextColor();
+ else
+ aColor = rStyleSettings.GetDefaultActionButtonTextColor();
+ else if (isAction())
+ if (nButtonFlags & DrawButtonFlags::Pressed)
+ aColor = rStyleSettings.GetActionButtonPressedRolloverTextColor();
+ else if (nButtonFlags & DrawButtonFlags::Highlight)
+ aColor = rStyleSettings.GetActionButtonRolloverTextColor();
+ else
+ aColor = rStyleSettings.GetActionButtonTextColor();
+ else if (nButtonFlags & DrawButtonFlags::Default)
+ if (nButtonFlags & DrawButtonFlags::Pressed)
+ aColor = rStyleSettings.GetDefaultButtonPressedRolloverTextColor();
+ else if (nButtonFlags & DrawButtonFlags::Highlight)
+ aColor = rStyleSettings.GetDefaultButtonRolloverTextColor();
+ else
+ aColor = rStyleSettings.GetDefaultButtonTextColor();
+ else
+ if (nButtonFlags & DrawButtonFlags::Pressed)
+ aColor = rStyleSettings.GetButtonPressedRolloverTextColor();
+ else if (nButtonFlags & DrawButtonFlags::Highlight)
+ aColor = rStyleSettings.GetButtonRolloverTextColor();
+ else
+ aColor = rStyleSettings.GetButtonTextColor();
+
+ pDev->SetTextColor(aColor);
+
+ if ( IsEnabled() )
+ nStyle = DrawSymbolFlags::NONE;
+ else
+ nStyle = DrawSymbolFlags::Disable;
+
+ Size aSize = rRect.GetSize();
+ Point aPos = rRect.TopLeft();
+
+ sal_uLong nImageSep = 1 + (pDev->GetTextHeight()-10)/2;
+ if( nImageSep < 1 )
+ nImageSep = 1;
+ if ( mnDDStyle == PushButtonDropdownStyle::MenuButton ||
+ mnDDStyle == PushButtonDropdownStyle::SplitMenuButton )
+ {
+ long nSeparatorX = 0;
+ tools::Rectangle aSymbolRect = aInRect;
+
+ // calculate symbol size
+ long nSymbolSize = pDev->GetTextHeight() / 2 + 1;
+
+ nSeparatorX = aInRect.Right() - 2*nSymbolSize;
+ aSize.AdjustWidth( -(2*nSymbolSize) );
+
+ // center symbol rectangle in the separated area
+ aSymbolRect.AdjustRight( -(nSymbolSize/2) );
+ aSymbolRect.SetLeft( aSymbolRect.Right() - nSymbolSize );
+
+ ImplDrawAlignedImage( pDev, aPos, aSize, nImageSep,
+ nTextStyle, nullptr, true );
+
+ long nDistance = (aSymbolRect.GetHeight() > 10) ? 2 : 1;
+ DecorationView aDecoView( pDev );
+ if( bMenuBtnSep && nSeparatorX > 0 )
+ {
+ Point aStartPt( nSeparatorX, aSymbolRect.Top()+nDistance );
+ Point aEndPt( nSeparatorX, aSymbolRect.Bottom()-nDistance );
+ aDecoView.DrawSeparator( aStartPt, aEndPt );
+ }
+ ImplSetSeparatorX( nSeparatorX );
+
+ aDecoView.DrawSymbol( aSymbolRect, SymbolType::SPIN_DOWN, aColor, nStyle );
+
+ }
+ else
+ {
+ tools::Rectangle aSymbolRect;
+ ImplDrawAlignedImage( pDev, aPos, aSize, nImageSep,
+ nTextStyle, IsSymbol() ? &aSymbolRect : nullptr, true );
+
+ if ( IsSymbol() )
+ {
+ DecorationView aDecoView( pDev );
+ aDecoView.DrawSymbol( aSymbolRect, meSymbol, aColor, nStyle );
+ }
+ }
+
+ pDev->Pop(); // restore clipregion
+}
+
+void PushButton::ImplDrawPushButton(vcl::RenderContext& rRenderContext)
+{
+ HideFocus();
+
+ DrawButtonFlags nButtonStyle = GetButtonState();
+ Size aOutSz(GetOutputSizePixel());
+ tools::Rectangle aRect(Point(), aOutSz);
+ tools::Rectangle aInRect = aRect;
+ bool bNativeOK = false;
+
+ // adjust style if button should be rendered 'pressed'
+ if (mbPressed || mbIsActive)
+ nButtonStyle |= DrawButtonFlags::Pressed;
+
+ // TODO: move this to Window class or make it a member !!!
+ ControlType aCtrlType = ControlType::Generic;
+ switch(GetParent()->GetType())
+ {
+ case WindowType::LISTBOX:
+ case WindowType::MULTILISTBOX:
+ case WindowType::TREELISTBOX:
+ aCtrlType = ControlType::Listbox;
+ break;
+
+ case WindowType::COMBOBOX:
+ case WindowType::PATTERNBOX:
+ case WindowType::NUMERICBOX:
+ case WindowType::METRICBOX:
+ case WindowType::CURRENCYBOX:
+ case WindowType::DATEBOX:
+ case WindowType::TIMEBOX:
+ case WindowType::LONGCURRENCYBOX:
+ aCtrlType = ControlType::Combobox;
+ break;
+ default:
+ break;
+ }
+
+ bool bDropDown = (IsSymbol() && (GetSymbol() == SymbolType::SPIN_DOWN) && GetText().isEmpty());
+
+ if( bDropDown && (aCtrlType == ControlType::Combobox || aCtrlType == ControlType::Listbox))
+ {
+ if (GetParent()->IsNativeControlSupported(aCtrlType, ControlPart::Entire))
+ {
+ // skip painting if the button was already drawn by the theme
+ if (aCtrlType == ControlType::Combobox)
+ {
+ Edit* pEdit = static_cast<Edit*>(GetParent());
+ if (pEdit->ImplUseNativeBorder(rRenderContext, pEdit->GetStyle()))
+ bNativeOK = true;
+ }
+ else if (GetParent()->IsNativeControlSupported(aCtrlType, ControlPart::HasBackgroundTexture))
+ {
+ bNativeOK = true;
+ }
+
+ if (!bNativeOK && GetParent()->IsNativeControlSupported(aCtrlType, ControlPart::ButtonDown))
+ {
+ // let the theme draw it, note we then need support
+ // for ControlType::Listbox/ControlPart::ButtonDown and ControlType::Combobox/ControlPart::ButtonDown
+
+ ImplControlValue aControlValue;
+ ControlState nState = ControlState::NONE;
+
+ if (mbPressed || mbIsActive)
+ nState |= ControlState::PRESSED;
+ if (GetButtonState() & DrawButtonFlags::Pressed)
+ nState |= ControlState::PRESSED;
+ if (HasFocus())
+ nState |= ControlState::FOCUSED;
+ if (GetButtonState() & DrawButtonFlags::Default)
+ nState |= ControlState::DEFAULT;
+ if (Window::IsEnabled())
+ nState |= ControlState::ENABLED;
+
+ if (IsMouseOver() && aInRect.IsInside(GetPointerPosPixel()))
+ nState |= ControlState::ROLLOVER;
+
+ if ( IsMouseOver() && aInRect.IsInside(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.IsInside(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_BEVELBUTTON)
+ aControlValue.mbBevelButton = true;
+
+ // draw frame into invisible window to have aInRect modified correctly
+ // but do not shift the inner rect for pressed buttons (ie remove DrawButtonFlags::Pressed)
+ // this assumes the theme has enough visual cues to signalize the button was pressed
+ //Window aWin( this );
+ //ImplDrawPushButtonFrame( &aWin, aInRect, nButtonStyle & ~DrawButtonFlags::Pressed );
+
+ // looks better this way as symbols were displaced slightly using the above approach
+ aInRect.AdjustTop(4 );
+ aInRect.AdjustBottom( -4 );
+ aInRect.AdjustLeft(4 );
+ aInRect.AdjustRight( -4 );
+
+ // prepare single line hint (needed on mac to decide between normal push button and
+ // rectangular bevel button look)
+ Size aFontSize(Application::GetSettings().GetStyleSettings().GetPushButtonFont().GetFontSize());
+ aFontSize = rRenderContext.LogicToPixel(aFontSize, MapMode(MapUnit::MapPoint));
+ Size aInRectSize(rRenderContext.LogicToPixel(Size(aInRect.GetWidth(), aInRect.GetHeight())));
+ aControlValue.mbSingleLine = (aInRectSize.Height() < 2 * aFontSize.Height());
+
+ if ((nState & ControlState::ROLLOVER) || !(GetStyle() & WB_FLATBUTTON)
+ || (HasFocus() && mpWindowImpl->mbUseNativeFocus
+ && !IsNativeControlSupported(ControlType::Pushbutton, ControlPart::Focus)))
+ {
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::Pushbutton, ControlPart::Entire, aCtrlRegion, nState,
+ aControlValue, OUString() /*PushButton::GetText()*/);
+ }
+ else
+ {
+ bNativeOK = true;
+ }
+
+ // draw content using the same aInRect as non-native VCL would do
+ ImplDrawPushButtonContent(&rRenderContext, DrawFlags::NONE,
+ aInRect, bDrawMenuSep, nButtonStyle);
+
+ if (HasFocus())
+ ShowFocus(ImplGetFocusRect());
+ }
+
+ if (!bNativeOK)
+ {
+ // draw PushButtonFrame, aInRect has content size afterwards
+ if (GetStyle() & WB_FLATBUTTON)
+ {
+ tools::Rectangle aTempRect(aInRect);
+ if (bRollOver)
+ ImplDrawPushButtonFrame(rRenderContext, aTempRect, nButtonStyle);
+ aInRect.AdjustLeft(2 );
+ aInRect.AdjustTop(2 );
+ aInRect.AdjustRight( -2 );
+ aInRect.AdjustBottom( -2 );
+ }
+ else
+ {
+ ImplDrawPushButtonFrame(rRenderContext, aInRect, nButtonStyle);
+ }
+
+ // draw content
+ ImplDrawPushButtonContent(&rRenderContext, DrawFlags::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() ) )
+ {
+ 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
+{
+ mpControlData->mpLayoutData.reset( new vcl::ControlLayoutData );
+ const_cast<PushButton*>(this)->Invalidate();
+}
+
+void PushButton::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&)
+{
+ ImplDrawPushButton(rRenderContext);
+}
+
+void PushButton::Draw( OutputDevice* pDev, const Point& rPos,
+ DrawFlags 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 & DrawFlags::Mono )
+ {
+ pDev->SetTextColor( COL_BLACK );
+ }
+ else
+ {
+ pDev->SetTextColor( GetTextColor() );
+
+ // 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->OutputDevice::SetSettings( aSettings );
+ }
+ pDev->SetTextFillColor();
+
+ DecorationView aDecoView( pDev );
+ DrawButtonFlags nButtonStyle = DrawButtonFlags::NONE;
+ if ( nFlags & DrawFlags::Mono )
+ nButtonStyle |= DrawButtonFlags::Mono;
+ if ( IsChecked() )
+ nButtonStyle |= DrawButtonFlags::Checked;
+ aRect = aDecoView.DrawButton( aRect, nButtonStyle );
+
+ ImplDrawPushButtonContent( pDev, nFlags, aRect, true, nButtonStyle );
+ pDev->Pop();
+}
+
+void PushButton::Resize()
+{
+ Control::Resize();
+ Invalidate();
+}
+
+void PushButton::GetFocus()
+{
+ ShowFocus( ImplGetFocusRect() );
+ SetInputContext( InputContext( GetFont() ) );
+ Button::GetFocus();
+}
+
+void PushButton::LoseFocus()
+{
+ EndSelection();
+ HideFocus();
+ Button::LoseFocus();
+}
+
+void PushButton::StateChanged( StateChangedType nType )
+{
+ Button::StateChanged( nType );
+
+ if ( (nType == StateChangedType::Enable) ||
+ (nType == StateChangedType::Text) ||
+ (nType == StateChangedType::Data) ||
+ (nType == StateChangedType::State) ||
+ (nType == StateChangedType::UpdateMode) )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::Style )
+ {
+ SetStyle( ImplInitStyle( GetWindow( GetWindowType::Prev ), GetStyle() ) );
+
+ bool bIsDefButton = ( GetStyle() & WB_DEFBUTTON ) != 0;
+ bool bWasDefButton = ( GetPrevStyle() & WB_DEFBUTTON ) != 0;
+ if ( bIsDefButton != bWasDefButton )
+ ImplSetDefButton( bIsDefButton );
+
+ if ( IsReallyVisible() && IsUpdateMode() )
+ {
+ if ( (GetPrevStyle() & PUSHBUTTON_VIEW_STYLE) !=
+ (GetStyle() & PUSHBUTTON_VIEW_STYLE) )
+ Invalidate();
+ }
+ }
+ else if ( (nType == StateChangedType::Zoom) ||
+ (nType == StateChangedType::ControlFont) )
+ {
+ ImplInitSettings( false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ ImplInitSettings( false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ImplInitSettings( true );
+ Invalidate();
+ }
+}
+
+void PushButton::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Button::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
+ {
+ ImplInitSettings( true );
+ Invalidate();
+ }
+}
+
+bool PushButton::PreNotify( NotifyEvent& rNEvt )
+{
+ if( rNEvt.GetType() == MouseNotifyEvent::MOUSEMOVE )
+ {
+ const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent();
+ if( pMouseEvt && (pMouseEvt->IsEnterWindow() || pMouseEvt->IsLeaveWindow()) )
+ {
+ // trigger redraw as mouse over state has changed
+
+ // TODO: move this to Window class or make it a member !!!
+ ControlType aCtrlType = ControlType::Generic;
+ switch( GetParent()->GetType() )
+ {
+ case WindowType::LISTBOX:
+ case WindowType::MULTILISTBOX:
+ case WindowType::TREELISTBOX:
+ aCtrlType = ControlType::Listbox;
+ break;
+
+ case WindowType::COMBOBOX:
+ case WindowType::PATTERNBOX:
+ case WindowType::NUMERICBOX:
+ case WindowType::METRICBOX:
+ case WindowType::CURRENCYBOX:
+ case WindowType::DATEBOX:
+ case WindowType::TIMEBOX:
+ case WindowType::LONGCURRENCYBOX:
+ aCtrlType = ControlType::Combobox;
+ break;
+ default:
+ break;
+ }
+
+ bool bDropDown = ( IsSymbol() && (GetSymbol()==SymbolType::SPIN_DOWN) && GetText().isEmpty() );
+
+ if( bDropDown && GetParent()->IsNativeControlSupported( aCtrlType, ControlPart::Entire) &&
+ !GetParent()->IsNativeControlSupported( aCtrlType, ControlPart::ButtonDown) )
+ {
+ vcl::Window *pBorder = GetParent()->GetWindow( GetWindowType::Border );
+ if(aCtrlType == ControlType::Combobox)
+ {
+ // only paint the button part to avoid flickering of the combobox text
+ tools::Rectangle aClipRect( Point(), GetOutputSizePixel() );
+ aClipRect.SetPos(pBorder->ScreenToOutputPixel(OutputToScreenPixel(aClipRect.TopLeft())));
+ pBorder->Invalidate( aClipRect );
+ }
+ else
+ {
+ pBorder->Invalidate( InvalidateFlags::NoErase );
+ }
+ }
+ else if( (GetStyle() & WB_FLATBUTTON) ||
+ IsNativeControlSupported(ControlType::Pushbutton, ControlPart::Entire) )
+ {
+ Invalidate();
+ }
+ }
+ }
+
+ return Button::PreNotify(rNEvt);
+}
+
+void PushButton::Toggle()
+{
+ ImplCallEventListenersAndHandler( VclEventId::PushbuttonToggle, nullptr );
+}
+
+void PushButton::SetSymbol( SymbolType eSymbol )
+{
+ if ( meSymbol != eSymbol )
+ {
+ meSymbol = eSymbol;
+ CompatStateChanged( StateChangedType::Data );
+ }
+}
+
+void PushButton::SetSymbolAlign( SymbolAlign eAlign )
+{
+ ImplSetSymbolAlign( eAlign );
+}
+
+void PushButton::SetDropDown( PushButtonDropdownStyle nStyle )
+{
+ if ( mnDDStyle != nStyle )
+ {
+ mnDDStyle = nStyle;
+ CompatStateChanged( StateChangedType::Data );
+ }
+}
+
+void PushButton::SetState( TriState eState )
+{
+ if ( meState != eState )
+ {
+ 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);
+ fprintf(stderr, "State is %d\n", rEvent.State.has<bool>());
+ 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 )
+ {
+ 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( DrawFlags::NONE ) ).GetSize();
+ aSize.AdjustWidth(textSize.Width() );
+ aSize.setHeight( std::max( aSize.Height(), long( textSize.Height() * 1.15 ) ) );
+ }
+
+ // cf. ImplDrawPushButton ...
+ if( (GetStyle() & WB_SMALLSTYLE) == 0 )
+ {
+ aSize.AdjustWidth(24 );
+ aSize.AdjustHeight(12 );
+ }
+
+ return CalcWindowSize( aSize );
+}
+
+Size PushButton::GetOptimalSize() const
+{
+ return CalcMinimumSize();
+}
+
+bool PushButton::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "has-default")
+ {
+ WinBits nBits = GetStyle();
+ nBits &= ~WB_DEFBUTTON;
+ if (toBool(rValue))
+ nBits |= WB_DEFBUTTON;
+ SetStyle(nBits);
+ }
+ else
+ return Button::set_property(rKey, rValue);
+ return true;
+}
+
+void PushButton::ShowFocus(const tools::Rectangle& rRect)
+{
+ if (IsNativeControlSupported(ControlType::Pushbutton, ControlPart::Focus))
+ {
+ PushButtonValue aControlValue;
+ aControlValue.mbIsAction = isAction();
+ tools::Rectangle aInRect(Point(), GetOutputSizePixel());
+ GetOutDev()->DrawNativeControl(ControlType::Pushbutton, ControlPart::Focus, aInRect,
+ ControlState::FOCUSED, aControlValue, OUString());
+ }
+ Button::ShowFocus(rRect);
+}
+
+void OKButton::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ set_id("ok");
+ PushButton::ImplInit( pParent, nStyle );
+
+ SetText( GetStandardText( StandardButtonType::OK ) );
+}
+
+OKButton::OKButton( vcl::Window* pParent, WinBits nStyle ) :
+ PushButton( WindowType::OKBUTTON )
+{
+ ImplInit( pParent, nStyle );
+}
+
+void OKButton::Click()
+{
+ // close parent if no link set
+ if ( !GetClickHdl() )
+ {
+ vcl::Window* pParent = getNonLayoutParent(this);
+ if ( pParent->IsSystemWindow() )
+ {
+ if ( pParent->IsDialog() )
+ {
+ VclPtr<Dialog> xParent( static_cast<Dialog*>(pParent) );
+ if ( xParent->IsInExecute() )
+ xParent->EndDialog( RET_OK );
+ // prevent recursive calls
+ else if ( !xParent->IsInClose() )
+ {
+ if ( pParent->GetStyle() & WB_CLOSEABLE )
+ xParent->Close();
+ }
+ }
+ else
+ {
+ if ( pParent->GetStyle() & WB_CLOSEABLE )
+ static_cast<SystemWindow*>(pParent)->Close();
+ }
+ }
+ }
+ else
+ {
+ PushButton::Click();
+ }
+}
+
+void CancelButton::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ set_id("cancel");
+ PushButton::ImplInit( pParent, nStyle );
+
+ SetText( GetStandardText( StandardButtonType::Cancel ) );
+}
+
+CancelButton::CancelButton( vcl::Window* pParent, WinBits nStyle ) :
+ PushButton( WindowType::CANCELBUTTON )
+{
+ ImplInit( pParent, nStyle );
+}
+
+void CancelButton::Click()
+{
+ // close parent if link not set
+ if ( !GetClickHdl() )
+ {
+ vcl::Window* pParent = getNonLayoutParent(this);
+ if ( pParent->IsSystemWindow() )
+ {
+ if ( pParent->IsDialog() )
+ {
+ if ( static_cast<Dialog*>(pParent)->IsInExecute() )
+ static_cast<Dialog*>(pParent)->EndDialog();
+ // prevent recursive calls
+ else if ( !static_cast<Dialog*>(pParent)->IsInClose() )
+ {
+ if ( pParent->GetStyle() & WB_CLOSEABLE )
+ static_cast<Dialog*>(pParent)->Close();
+ }
+ }
+ else
+ {
+ if ( pParent->GetStyle() & WB_CLOSEABLE )
+ static_cast<SystemWindow*>(pParent)->Close();
+ }
+ }
+ }
+ else
+ {
+ PushButton::Click();
+ }
+}
+
+CloseButton::CloseButton( vcl::Window* pParent, WinBits nStyle )
+ : CancelButton(pParent, nStyle)
+{
+ 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 )
+ 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 )
+{
+ 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 )
+ {
+ 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.IsInside(GetPointerPosPixel()))
+ nState |= ControlState::ROLLOVER;
+
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::Radiobutton, ControlPart::Entire, aCtrlRect,
+ nState, aControlValue, OUString());
+ }
+
+ if (!bNativeOK)
+ {
+ 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());
+ }
+ }
+}
+
+void RadioButton::ImplDraw( OutputDevice* pDev, DrawFlags nDrawFlags,
+ const Point& rPos, const Size& rSize,
+ const Size& rImageSize, tools::Rectangle& rStateRect,
+ tools::Rectangle& rMouseRect )
+{
+ WinBits nWinStyle = GetStyle();
+ OUString aText( GetText() );
+
+ pDev->Push( PushFlags::CLIPREGION );
+ pDev->IntersectClipRegion( tools::Rectangle( rPos, rSize ) );
+
+ // no image radio button
+ if ( !maImage )
+ {
+ if (!aText.isEmpty() || HasImage())
+ {
+ DrawTextFlags nTextStyle = Button::ImplGetTextStyle( nWinStyle, nDrawFlags );
+
+ const long nImageSep = GetDrawPixel( pDev, ImplGetImageToTextDistance() );
+ Size aSize( rSize );
+ Point aPos( rPos );
+ aPos.AdjustX(rImageSize.Width() + nImageSep );
+ aSize.AdjustWidth( -(rImageSize.Width() + nImageSep) );
+
+ // 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());
+ rMouseRect.SetTop(rPos.Y());
+
+ 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() );
+ }
+ 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 );
+ long nTextHeight = pDev->GetTextHeight();
+ 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, DrawFlags::NONE, Point(), GetOutputSizePixel(),
+ aImageSize, maStateRect, maMouseRect);
+
+ if (!maImage && HasFocus())
+ ShowFocus(ImplGetFocusRect());
+
+ ImplDrawRadioButtonState(rRenderContext);
+}
+
+void RadioButton::group(RadioButton &rOther)
+{
+ if (&rOther == this)
+ return;
+
+ if (!m_xGroup)
+ {
+ m_xGroup = std::make_shared<std::vector<VclPtr<RadioButton> >>();
+ m_xGroup->push_back(this);
+ }
+
+ auto aFind = std::find(m_xGroup->begin(), m_xGroup->end(), VclPtr<RadioButton>(&rOther));
+ if (aFind == m_xGroup->end())
+ {
+ m_xGroup->push_back(&rOther);
+
+ if (rOther.m_xGroup)
+ {
+ std::vector< VclPtr<RadioButton> > aOthers(rOther.GetRadioButtonGroup(false));
+ //make all members of the group share the same button group
+ for (auto const& elem : aOthers)
+ {
+ aFind = std::find(m_xGroup->begin(), m_xGroup->end(), elem);
+ if (aFind == m_xGroup->end())
+ m_xGroup->push_back(elem);
+ }
+ }
+
+ //make all members of the group share the same button group
+ for (VclPtr<RadioButton> const & pButton : *m_xGroup)
+ {
+ pButton->m_xGroup = m_xGroup;
+ }
+ }
+
+ //if this one is checked, uncheck all the others
+ if (mbChecked)
+ ImplUncheckAllOther();
+}
+
+std::vector< VclPtr<RadioButton> > RadioButton::GetRadioButtonGroup(bool bIncludeThis) const
+{
+ if (m_xGroup)
+ {
+ if (bIncludeThis)
+ return *m_xGroup;
+ std::vector< VclPtr<RadioButton> > aGroup;
+ for (VclPtr<RadioButton> const & pRadioButton : *m_xGroup)
+ {
+ if (pRadioButton == this)
+ continue;
+ aGroup.push_back(pRadioButton);
+ }
+ return aGroup;
+ }
+
+ std::vector<VclPtr<RadioButton>> aGroup;
+ if (mbUsesExplicitGroup)
+ return aGroup;
+
+ //old-school
+
+ // go back to first in group;
+ vcl::Window* pFirst = const_cast<RadioButton*>(this);
+ while( ( pFirst->GetStyle() & WB_GROUP ) == 0 )
+ {
+ vcl::Window* pWindow = pFirst->GetWindow( GetWindowType::Prev );
+ if( pWindow )
+ pFirst = pWindow;
+ else
+ break;
+ }
+ // insert radiobuttons up to next group
+ do
+ {
+ if( pFirst->GetType() == WindowType::RADIOBUTTON )
+ {
+ if( pFirst != this || bIncludeThis )
+ aGroup.emplace_back(static_cast<RadioButton*>(pFirst) );
+ }
+ pFirst = pFirst->GetWindow( GetWindowType::Next );
+ } while( pFirst && ( ( pFirst->GetStyle() & WB_GROUP ) == 0 ) );
+
+ return aGroup;
+}
+
+void RadioButton::ImplUncheckAllOther()
+{
+ mpWindowImpl->mnStyle |= WB_TABSTOP;
+
+ std::vector<VclPtr<RadioButton> > aGroup(GetRadioButtonGroup(false));
+ // iterate over radio button group and checked buttons
+ for (VclPtr<RadioButton>& pWindow : aGroup)
+ {
+ if ( pWindow->IsChecked() )
+ {
+ pWindow->SetState( false );
+ if ( pWindow->IsDisposed() )
+ return;
+ }
+
+ // not inside if clause to always remove wrongly set WB_TABSTOPS
+ pWindow->mpWindowImpl->mnStyle &= ~WB_TABSTOP;
+ }
+}
+
+void RadioButton::ImplCallClick( bool bGrabFocus, GetFocusFlags nFocusFlags )
+{
+ mbStateChanged = !mbChecked;
+ mbChecked = true;
+ mpWindowImpl->mnStyle |= WB_TABSTOP;
+ Invalidate();
+ VclPtr<vcl::Window> xWindow = this;
+ if ( mbRadioCheck )
+ ImplUncheckAllOther();
+ if ( xWindow->IsDisposed() )
+ return;
+ if ( bGrabFocus )
+ ImplGrabFocus( nFocusFlags );
+ if ( xWindow->IsDisposed() )
+ return;
+ if ( mbStateChanged )
+ Toggle();
+ if ( xWindow->IsDisposed() )
+ return;
+ Click();
+ if ( xWindow->IsDisposed() )
+ return;
+ mbStateChanged = false;
+}
+
+RadioButton::RadioButton(vcl::Window* pParent, bool bUsesExplicitGroup, WinBits nStyle)
+ : Button(WindowType::RADIOBUTTON)
+ , mbUsesExplicitGroup(bUsesExplicitGroup)
+{
+ ImplInitRadioButtonData();
+ ImplInit( pParent, nStyle );
+}
+
+RadioButton::~RadioButton()
+{
+ disposeOnce();
+}
+
+void RadioButton::dispose()
+{
+ if (m_xGroup)
+ {
+ m_xGroup->erase(std::remove(m_xGroup->begin(), m_xGroup->end(), VclPtr<RadioButton>(this)),
+ m_xGroup->end());
+ m_xGroup.reset();
+ }
+ Button::dispose();
+}
+
+void RadioButton::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ if ( rMEvt.IsLeft() && maMouseRect.IsInside( 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.IsInside( 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
+{
+ mpControlData->mpLayoutData.reset( new vcl::ControlLayoutData );
+ const_cast<RadioButton*>(this)->Invalidate();
+}
+
+void RadioButton::Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& )
+{
+ ImplDrawRadioButton(rRenderContext);
+}
+
+void RadioButton::Draw( OutputDevice* pDev, const Point& rPos,
+ DrawFlags nFlags )
+{
+ if ( !maImage )
+ {
+ MapMode aResMapMode( MapUnit::Map100thMM );
+ Point aPos = pDev->LogicToPixel( rPos );
+ Size aSize = GetSizePixel();
+ Size aImageSize = pDev->LogicToPixel( Size( 300, 300 ), aResMapMode );
+ Size aBrd1Size = pDev->LogicToPixel( Size( 20, 20 ), aResMapMode );
+ Size aBrd2Size = pDev->LogicToPixel( Size( 60, 60 ), aResMapMode );
+ vcl::Font aFont = GetDrawPixelFont( pDev );
+ tools::Rectangle aStateRect;
+ tools::Rectangle aMouseRect;
+
+ aImageSize.setWidth( CalcZoom( aImageSize.Width() ) );
+ aImageSize.setHeight( CalcZoom( aImageSize.Height() ) );
+ aBrd1Size.setWidth( CalcZoom( aBrd1Size.Width() ) );
+ aBrd1Size.setHeight( CalcZoom( aBrd1Size.Height() ) );
+ aBrd2Size.setWidth( CalcZoom( aBrd2Size.Width() ) );
+ aBrd2Size.setHeight( CalcZoom( aBrd2Size.Height() ) );
+
+ if ( !aBrd1Size.Width() )
+ aBrd1Size.setWidth( 1 );
+ if ( !aBrd1Size.Height() )
+ aBrd1Size.setHeight( 1 );
+ if ( !aBrd2Size.Width() )
+ aBrd2Size.setWidth( 1 );
+ if ( !aBrd2Size.Height() )
+ aBrd2Size.setHeight( 1 );
+
+ pDev->Push();
+ pDev->SetMapMode();
+ pDev->SetFont( aFont );
+ if ( nFlags & DrawFlags::Mono )
+ pDev->SetTextColor( COL_BLACK );
+ else
+ pDev->SetTextColor( GetTextColor() );
+ pDev->SetTextFillColor();
+
+ ImplDraw( pDev, nFlags, aPos, aSize,
+ aImageSize, aStateRect, aMouseRect );
+
+ Point aCenterPos = aStateRect.Center();
+ long nRadX = aImageSize.Width()/2;
+ long nRadY = aImageSize.Height()/2;
+
+ pDev->SetLineColor();
+ pDev->SetFillColor( COL_BLACK );
+ pDev->DrawPolygon( tools::Polygon( aCenterPos, nRadX, nRadY ) );
+ nRadX -= aBrd1Size.Width();
+ nRadY -= aBrd1Size.Height();
+ pDev->SetFillColor( COL_WHITE );
+ pDev->DrawPolygon( tools::Polygon( aCenterPos, nRadX, nRadY ) );
+ if ( mbChecked )
+ {
+ nRadX -= aBrd1Size.Width();
+ nRadY -= aBrd1Size.Height();
+ if ( !nRadX )
+ nRadX = 1;
+ if ( !nRadY )
+ nRadY = 1;
+ pDev->SetFillColor( COL_BLACK );
+ pDev->DrawPolygon( tools::Polygon( aCenterPos, nRadX, nRadY ) );
+ }
+
+ pDev->Pop();
+ }
+ else
+ {
+ OSL_FAIL( "RadioButton::Draw() - not implemented for RadioButton with Image" );
+ }
+}
+
+void RadioButton::Resize()
+{
+ Control::Resize();
+ Invalidate();
+}
+
+void RadioButton::GetFocus()
+{
+ ShowFocus( ImplGetFocusRect() );
+ SetInputContext( InputContext( GetFont() ) );
+ Button::GetFocus();
+}
+
+void RadioButton::LoseFocus()
+{
+ if ( GetButtonState() & DrawButtonFlags::Pressed )
+ {
+ GetButtonState() &= ~DrawButtonFlags::Pressed;
+ Invalidate();
+ }
+
+ HideFocus();
+ Button::LoseFocus();
+}
+
+void RadioButton::StateChanged( StateChangedType nType )
+{
+ Button::StateChanged( nType );
+
+ if ( nType == StateChangedType::State )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ Invalidate( maStateRect );
+ }
+ else if ( (nType == StateChangedType::Enable) ||
+ (nType == StateChangedType::Text) ||
+ (nType == StateChangedType::Data) ||
+ (nType == StateChangedType::UpdateMode) )
+ {
+ if ( IsUpdateMode() )
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::Style )
+ {
+ SetStyle( ImplInitStyle( GetWindow( GetWindowType::Prev ), GetStyle() ) );
+
+ if ( (GetPrevStyle() & RADIOBUTTON_VIEW_STYLE) !=
+ (GetStyle() & RADIOBUTTON_VIEW_STYLE) )
+ {
+ if ( IsUpdateMode() )
+ Invalidate();
+ }
+ }
+ else if ( (nType == StateChangedType::Zoom) ||
+ (nType == StateChangedType::ControlFont) )
+ {
+ ImplInitSettings( false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ ImplInitSettings( false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ImplInitSettings( true );
+ Invalidate();
+ }
+}
+
+void RadioButton::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Button::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
+ {
+ ImplInitSettings( true );
+ Invalidate();
+ }
+}
+
+bool RadioButton::PreNotify( NotifyEvent& rNEvt )
+{
+ if( rNEvt.GetType() == MouseNotifyEvent::MOUSEMOVE )
+ {
+ const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent();
+ if( pMouseEvt && !pMouseEvt->GetButtons() && !pMouseEvt->IsSynthetic() && !pMouseEvt->IsModifierChanged() )
+ {
+ // trigger redraw if mouse over state has changed
+ if( IsNativeControlSupported(ControlType::Radiobutton, ControlPart::Entire) )
+ {
+ if (maMouseRect.IsInside(GetPointerPosPixel()) != maMouseRect.IsInside(GetLastPointerPosPixel()) ||
+ pMouseEvt->IsLeaveWindow() || pMouseEvt->IsEnterWindow())
+ {
+ Invalidate( maStateRect );
+ }
+ }
+ }
+ }
+
+ return Button::PreNotify(rNEvt);
+}
+
+void RadioButton::Toggle()
+{
+ ImplCallEventListenersAndHandler( VclEventId::RadiobuttonToggle, [this] () { maToggleHdl.Call(*this); } );
+}
+
+void RadioButton::SetModeRadioImage( const Image& rImage )
+{
+ if ( rImage != maImage )
+ {
+ maImage = rImage;
+ CompatStateChanged( StateChangedType::Data );
+ queue_resize();
+ }
+}
+
+
+void RadioButton::SetState( bool bCheck )
+{
+ // carry the TabStop flag along correctly
+ if ( bCheck )
+ mpWindowImpl->mnStyle |= WB_TABSTOP;
+ else
+ mpWindowImpl->mnStyle &= ~WB_TABSTOP;
+
+ if ( mbChecked != bCheck )
+ {
+ mbChecked = bCheck;
+ CompatStateChanged( StateChangedType::State );
+ Toggle();
+ }
+}
+
+bool RadioButton::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "active")
+ SetState(toBool(rValue));
+ else if (rKey == "image-position")
+ {
+ WinBits nBits = GetStyle();
+ if (rValue == "left")
+ {
+ nBits &= ~(WB_CENTER | WB_RIGHT);
+ nBits |= WB_LEFT;
+ }
+ else if (rValue == "right")
+ {
+ nBits &= ~(WB_CENTER | WB_LEFT);
+ nBits |= WB_RIGHT;
+ }
+ else if (rValue == "top")
+ {
+ nBits &= ~(WB_VCENTER | WB_BOTTOM);
+ nBits |= WB_TOP;
+ }
+ else if (rValue == "bottom")
+ {
+ nBits &= ~(WB_VCENTER | WB_TOP);
+ nBits |= WB_BOTTOM;
+ }
+ //It's rather mad to have to set these bits when there is the other
+ //image align. Looks like e.g. the radiobuttons etc weren't converted
+ //over to image align fully.
+ SetStyle(nBits);
+ //Deliberate to set the sane ImageAlign property
+ return Button::set_property(rKey, rValue);
+ }
+ else
+ return Button::set_property(rKey, rValue);
+ return true;
+}
+
+void RadioButton::Check( bool bCheck )
+{
+ // TabStop-Flag richtig mitfuehren
+ if ( bCheck )
+ mpWindowImpl->mnStyle |= WB_TABSTOP;
+ else
+ mpWindowImpl->mnStyle &= ~WB_TABSTOP;
+
+ if ( mbChecked != bCheck )
+ {
+ mbChecked = bCheck;
+ VclPtr<vcl::Window> xWindow = this;
+ CompatStateChanged( StateChangedType::State );
+ if ( xWindow->IsDisposed() )
+ return;
+ if ( bCheck && mbRadioCheck )
+ ImplUncheckAllOther();
+ if ( xWindow->IsDisposed() )
+ return;
+ Toggle();
+ }
+}
+
+long RadioButton::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()
+{
+ Push( 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 );
+ }
+ }
+
+ Pop();
+}
+
+Size RadioButton::CalcMinimumSize(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
+
+ DrawNativeControl(ControlType::Radiobutton, ControlPart::Focus, aInRect,
+ ControlState::FOCUSED, aControlValue, OUString());
+ }
+ Button::ShowFocus(rRect);
+}
+
+boost::property_tree::ptree RadioButton::DumpAsPropertyTree()
+{
+ boost::property_tree::ptree aTree(Button::DumpAsPropertyTree());
+ aTree.put("checked", IsChecked());
+ return aTree;
+}
+
+FactoryFunction RadioButton::GetUITestFactory() const
+{
+ return RadioButtonUIObject::create;
+}
+
+void CheckBox::ImplInitCheckBoxData()
+{
+ meState = TRISTATE_FALSE;
+ meSaveValue = 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 )
+ {
+ 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.IsInside(GetPointerPosPixel()))
+ nState |= ControlState::ROLLOVER;
+
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::Checkbox, ControlPart::Entire, aCtrlRegion,
+ nState, aControlValue, OUString());
+ }
+
+ if (!bNativeOK)
+ {
+ 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, DrawFlags nDrawFlags,
+ const Point& rPos, const Size& rSize,
+ const Size& rImageSize, tools::Rectangle& rStateRect,
+ tools::Rectangle& rMouseRect )
+{
+ WinBits nWinStyle = GetStyle();
+ OUString aText( GetText() );
+
+ pDev->Push( PushFlags::CLIPREGION | PushFlags::LINECOLOR );
+ pDev->IntersectClipRegion( tools::Rectangle( rPos, rSize ) );
+
+ if (!aText.isEmpty() || HasImage())
+ {
+ DrawTextFlags nTextStyle = Button::ImplGetTextStyle( nWinStyle, nDrawFlags );
+
+ const long nImageSep = GetDrawPixel( pDev, ImplGetImageToTextDistance() );
+ Size aSize( rSize );
+ Point aPos( rPos );
+ aPos.AdjustX(rImageSize.Width() + nImageSep );
+ aSize.AdjustWidth( -(rImageSize.Width() + nImageSep) );
+
+ // 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() );
+ }
+ else
+ {
+ if ( mbLegacyNoTextAlign && ( nWinStyle & WB_CENTER ) )
+ rStateRect.SetLeft( rPos.X()+((rSize.Width()-rImageSize.Width())/2) );
+ else if ( mbLegacyNoTextAlign && ( nWinStyle & WB_RIGHT ) )
+ rStateRect.SetLeft( rPos.X()+rSize.Width()-rImageSize.Width() );
+ 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, DrawFlags::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 ), mbLegacyNoTextAlign( false )
+{
+ ImplInitCheckBoxData();
+ ImplInit( pParent, nStyle );
+}
+
+void CheckBox::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ if ( rMEvt.IsLeft() && maMouseRect.IsInside( 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.IsInside( 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
+{
+ mpControlData->mpLayoutData.reset( new vcl::ControlLayoutData );
+ const_cast<CheckBox*>(this)->Invalidate();
+}
+
+void CheckBox::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&)
+{
+ ImplDrawCheckBox(rRenderContext);
+}
+
+void CheckBox::Draw( OutputDevice* pDev, const Point& rPos,
+ DrawFlags nFlags )
+{
+ MapMode aResMapMode( MapUnit::Map100thMM );
+ Point aPos = pDev->LogicToPixel( rPos );
+ Size aSize = GetSizePixel();
+ Size aImageSize = pDev->LogicToPixel( Size( 300, 300 ), aResMapMode );
+ Size aBrd1Size = pDev->LogicToPixel( Size( 20, 20 ), aResMapMode );
+ Size aBrd2Size = pDev->LogicToPixel( Size( 30, 30 ), aResMapMode );
+ 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 & DrawFlags::Mono )
+ pDev->SetTextColor( COL_BLACK );
+ else
+ pDev->SetTextColor( GetTextColor() );
+ pDev->SetTextFillColor();
+
+ ImplDraw( pDev, nFlags, aPos, aSize,
+ aImageSize, aStateRect, aMouseRect );
+
+ pDev->SetLineColor();
+ pDev->SetFillColor( COL_BLACK );
+ pDev->DrawRect( aStateRect );
+ aStateRect.AdjustLeft(aBrd1Size.Width() );
+ aStateRect.AdjustTop(aBrd1Size.Height() );
+ aStateRect.AdjustRight( -(aBrd1Size.Width()) );
+ aStateRect.AdjustBottom( -(aBrd1Size.Height()) );
+ if ( meState == TRISTATE_INDET )
+ pDev->SetFillColor( COL_LIGHTGRAY );
+ else
+ pDev->SetFillColor( COL_WHITE );
+ pDev->DrawRect( aStateRect );
+
+ if ( meState == TRISTATE_TRUE )
+ {
+ aStateRect.AdjustLeft(aBrd2Size.Width() );
+ aStateRect.AdjustTop(aBrd2Size.Height() );
+ aStateRect.AdjustRight( -(aBrd2Size.Width()) );
+ aStateRect.AdjustBottom( -(aBrd2Size.Height()) );
+ Point aPos11( aStateRect.TopLeft() );
+ Point aPos12( aStateRect.BottomRight() );
+ Point aPos21( aStateRect.TopRight() );
+ Point aPos22( aStateRect.BottomLeft() );
+ Point aTempPos11( aPos11 );
+ Point aTempPos12( aPos12 );
+ Point aTempPos21( aPos21 );
+ Point aTempPos22( aPos22 );
+ pDev->SetLineColor( COL_BLACK );
+ long nDX = 0;
+ for ( long i = 0; i < nCheckWidth; i++ )
+ {
+ if ( !(i % 2) )
+ {
+ aTempPos11.setX( aPos11.X()+nDX );
+ aTempPos12.setX( aPos12.X()+nDX );
+ aTempPos21.setX( aPos21.X()+nDX );
+ aTempPos22.setX( aPos22.X()+nDX );
+ }
+ else
+ {
+ nDX++;
+ aTempPos11.setX( aPos11.X()-nDX );
+ aTempPos12.setX( aPos12.X()-nDX );
+ aTempPos21.setX( aPos21.X()-nDX );
+ aTempPos22.setX( aPos22.X()-nDX );
+ }
+ pDev->DrawLine( aTempPos11, aTempPos12 );
+ pDev->DrawLine( aTempPos21, aTempPos22 );
+ }
+ }
+
+ pDev->Pop();
+}
+
+void CheckBox::Resize()
+{
+ Control::Resize();
+ Invalidate();
+}
+
+void CheckBox::GetFocus()
+{
+ if (GetText().isEmpty())
+ {
+ // increase button size to have space for focus rect
+ // checkboxes without text will draw focusrect around the check
+ // See CheckBox::ImplDraw()
+ Point aPos( GetPosPixel() );
+ Size aSize( GetSizePixel() );
+ aPos.Move(-1,-1);
+ aSize.AdjustHeight(2 );
+ aSize.AdjustWidth(2 );
+ setPosSizePixel( aPos.X(), aPos.Y(), aSize.Width(), aSize.Height() );
+ Invalidate();
+ // Trigger drawing to initialize the mouse rectangle, otherwise the mouse button down
+ // handler would ignore the mouse event.
+ PaintImmediately();
+ }
+ else
+ ShowFocus( ImplGetFocusRect() );
+
+ SetInputContext( InputContext( GetFont() ) );
+ Button::GetFocus();
+}
+
+void CheckBox::LoseFocus()
+{
+ if ( GetButtonState() & DrawButtonFlags::Pressed )
+ {
+ GetButtonState() &= ~DrawButtonFlags::Pressed;
+ Invalidate();
+ }
+
+ HideFocus();
+ Button::LoseFocus();
+
+ if (GetText().isEmpty())
+ {
+ // decrease button size again (see GetFocus())
+ // checkboxes without text will draw focusrect around the check
+ Point aPos( GetPosPixel() );
+ Size aSize( GetSizePixel() );
+ aPos.Move(1,1);
+ aSize.AdjustHeight( -2 );
+ aSize.AdjustWidth( -2 );
+ setPosSizePixel( aPos.X(), aPos.Y(), aSize.Width(), aSize.Height() );
+ Invalidate();
+ }
+}
+
+void CheckBox::StateChanged( StateChangedType nType )
+{
+ Button::StateChanged( nType );
+
+ if ( nType == StateChangedType::State )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ Invalidate( maStateRect );
+ }
+ else if ( (nType == StateChangedType::Enable) ||
+ (nType == StateChangedType::Text) ||
+ (nType == StateChangedType::Data) ||
+ (nType == StateChangedType::UpdateMode) )
+ {
+ if ( IsUpdateMode() )
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::Style )
+ {
+ SetStyle( ImplInitStyle( GetWindow( GetWindowType::Prev ), GetStyle() ) );
+
+ if ( (GetPrevStyle() & CHECKBOX_VIEW_STYLE) !=
+ (GetStyle() & CHECKBOX_VIEW_STYLE) )
+ {
+ if ( IsUpdateMode() )
+ Invalidate();
+ }
+ }
+ else if ( (nType == StateChangedType::Zoom) ||
+ (nType == StateChangedType::ControlFont) )
+ {
+ ImplInitSettings( false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ ImplInitSettings( false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ImplInitSettings( true );
+ Invalidate();
+ }
+}
+
+void CheckBox::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Button::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
+ {
+ ImplInitSettings( true );
+ Invalidate();
+ }
+}
+
+bool CheckBox::PreNotify( NotifyEvent& rNEvt )
+{
+ if( rNEvt.GetType() == MouseNotifyEvent::MOUSEMOVE )
+ {
+ const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent();
+ if( pMouseEvt && !pMouseEvt->GetButtons() && !pMouseEvt->IsSynthetic() && !pMouseEvt->IsModifierChanged() )
+ {
+ // trigger redraw if mouse over state has changed
+ if( IsNativeControlSupported(ControlType::Checkbox, ControlPart::Entire) )
+ {
+ if (maMouseRect.IsInside(GetPointerPosPixel()) != maMouseRect.IsInside(GetLastPointerPosPixel()) ||
+ pMouseEvt->IsLeaveWindow() || pMouseEvt->IsEnterWindow())
+ {
+ Invalidate( maStateRect );
+ }
+ }
+ }
+ }
+
+ return Button::PreNotify(rNEvt);
+}
+
+void CheckBox::Toggle()
+{
+ ImplCallEventListenersAndHandler( VclEventId::CheckboxToggle, [this] () { maToggleHdl.Call(*this); } );
+}
+
+void CheckBox::SetState( TriState eState )
+{
+ if ( !mbTriState && (eState == TRISTATE_INDET) )
+ eState = TRISTATE_FALSE;
+
+ if ( meState != eState )
+ {
+ meState = eState;
+ StateChanged( StateChangedType::State );
+ Toggle();
+ }
+}
+
+bool CheckBox::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "active")
+ SetState(toBool(rValue) ? TRISTATE_TRUE : TRISTATE_FALSE);
+ else
+ return Button::set_property(rKey, rValue);
+ return true;
+}
+
+void CheckBox::EnableTriState( bool bTriState )
+{
+ if ( mbTriState != bTriState )
+ {
+ mbTriState = bTriState;
+
+ if ( !bTriState && (meState == TRISTATE_INDET) )
+ SetState( TRISTATE_FALSE );
+ }
+}
+
+long CheckBox::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 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()
+{
+ Push( 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 );
+ }
+ }
+
+ Pop();
+}
+
+Size CheckBox::CalcMinimumSize( 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
+
+ DrawNativeControl(ControlType::Checkbox, ControlPart::Focus, aInRect,
+ ControlState::FOCUSED, aControlValue, OUString());
+ }
+ Button::ShowFocus(rRect);
+}
+
+boost::property_tree::ptree CheckBox::DumpAsPropertyTree()
+{
+ boost::property_tree::ptree aTree(Button::DumpAsPropertyTree());
+ aTree.put("checked", IsChecked());
+ return aTree;
+}
+
+FactoryFunction CheckBox::GetUITestFactory() const
+{
+ return CheckBoxUIObject::create;
+}
+
+ImageButton::ImageButton( vcl::Window* pParent, WinBits nStyle ) :
+ PushButton( pParent, nStyle )
+{
+ ImplInitStyle();
+}
+
+void ImageButton::ImplInitStyle()
+{
+ WinBits nStyle = GetStyle();
+
+ if ( ! ( nStyle & ( WB_RIGHT | WB_LEFT ) ) )
+ nStyle |= WB_CENTER;
+
+ if ( ! ( nStyle & ( WB_TOP | WB_BOTTOM ) ) )
+ nStyle |= WB_VCENTER;
+
+ SetStyle( nStyle );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/calendar.cxx b/vcl/source/control/calendar.cxx
new file mode 100644
index 000000000..0baecf397
--- /dev/null
+++ b/vcl/source/control/calendar.cxx
@@ -0,0 +1,1554 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/help.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/event.hxx>
+#include <vcl/commandevent.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 <vcl/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;
+ mbSelection = false;
+ mbMenuDown = false;
+ mbSpinDown = false;
+ mbPrevIn = false;
+ mbNextIn = false;
+ mbTravelSelect = false;
+ mbAllSel = 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(*this, 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;
+
+ OUString const a99Text("99");
+
+ long n99TextWidth = GetTextWidth( a99Text );
+ 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;
+ 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
+ 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();
+ 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));
+ long nOffX = (mnDayWidth-GetTextWidth( aDayOfWeek ))/2;
+ if ( !nDayOfWeek )
+ nStartOffX = nOffX;
+ else
+ nOffX -= nStartOffX;
+ nOffX += nDayOfWeek * mnDayWidth;
+ mnDayOfWeekAry[nDayOfWeek] = nOffX;
+ maDayOfWeekText += aDayOfWeek;
+ nDay++;
+ nDay %= 7;
+ }
+
+ mbCalc = false;
+ }
+
+ // calculate number of days
+
+ DayOfWeek eStartDay = ImplGetWeekStart();
+
+ sal_uInt16 nWeekDay;
+ Date aTempDate = GetFirstMonth();
+ maFirstDate = aTempDate;
+ nWeekDay = static_cast<sal_uInt16>(aTempDate.GetDayOfWeek());
+ nWeekDay = (nWeekDay+(7-static_cast<sal_uInt16>(eStartDay))) % 7;
+ maFirstDate.AddDays( -nWeekDay );
+ mnDayCount = nWeekDay;
+ sal_uInt16 nDaysInMonth;
+ sal_uInt16 nMonthCount = static_cast<sal_uInt16>(mnMonthPerLine*mnLines);
+ for ( sal_uInt16 i = 0; i < nMonthCount; i++ )
+ {
+ nDaysInMonth = aTempDate.GetDaysInMonth();
+ mnDayCount += nDaysInMonth;
+ aTempDate.AddDays( nDaysInMonth );
+ }
+ Date aTempDate2 = aTempDate;
+ --aTempDate2;
+ nDaysInMonth = aTempDate2.GetDaysInMonth();
+ aTempDate2.AddDays( -(nDaysInMonth-1) );
+ nWeekDay = static_cast<sal_uInt16>(aTempDate2.GetDayOfWeek());
+ nWeekDay = (nWeekDay+(7-static_cast<sal_uInt16>(eStartDay))) % 7;
+ mnDayCount += 42-nDaysInMonth-nWeekDay;
+
+ // determine colours
+ maOtherColor = COL_LIGHTGRAY;
+ if ( maOtherColor.IsRGBEqual( GetBackground().GetColor() ) )
+ maOtherColor = COL_GRAY;
+
+ Date aLastDate = GetLastDate();
+ if ( (maOldFormatLastDate != aLastDate) ||
+ (maOldFormatFirstDate != maFirstDate) )
+ {
+ maOldFormatFirstDate = maFirstDate;
+ maOldFormatLastDate = aLastDate;
+ }
+
+ // get DateInfo
+ sal_Int16 nNewFirstYear = maFirstDate.GetYear();
+ sal_Int16 nNewLastYear = GetLastDate().GetYear();
+ if ( mnFirstYear )
+ {
+ if ( nNewFirstYear < mnFirstYear )
+ {
+ mnFirstYear = nNewFirstYear;
+ }
+ if ( nNewLastYear > mnLastYear )
+ {
+ mnLastYear = nNewLastYear;
+ }
+ }
+ else
+ {
+ mnFirstYear = nNewFirstYear;
+ mnLastYear = nNewLastYear;
+ }
+
+ mbFormat = false;
+}
+
+sal_uInt16 Calendar::ImplHitTest( const Point& rPos, Date& rDate ) const
+{
+ if ( mbFormat )
+ return 0;
+
+ if ( maPrevRect.IsInside( rPos ) )
+ return CALENDAR_HITTEST_PREV;
+ else if ( maNextRect.IsInside( rPos ) )
+ return CALENDAR_HITTEST_NEXT;
+
+ long nY;
+ long nOffX;
+ sal_Int32 nDay;
+ DayOfWeek eStartDay = ImplGetWeekStart();
+
+ rDate = GetFirstMonth();
+ nY = 0;
+ for ( long i = 0; i < mnLines; i++ )
+ {
+ if ( rPos.Y() < nY )
+ return 0;
+
+ long nX = 0;
+ long nYMonth = nY+mnMonthHeight;
+ for ( 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
+ {
+ long nDayX = nX+mnDaysOffX;
+ 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)
+{
+ long i;
+ long n;
+ long nLines;
+ long nHeight = rRect.GetHeight();
+ 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,
+ long nX, long nY,
+ sal_uInt16 nDay, sal_uInt16 nMonth, sal_Int16 nYear,
+ bool bOther, sal_Int32 nToday )
+{
+ Color const * pTextColor = nullptr;
+ const OUString& rDay = maDayTexts[nDay - 1];
+ tools::Rectangle aDateRect(nX, nY, nX + mnDayWidth - 1, nY + mnDayHeight - 1);
+
+ bool bSel = false;
+ bool bFocus = false;
+ // actual day
+ if ((nDay == maCurDate.GetDay()) &&
+ (nMonth == maCurDate.GetMonth()) &&
+ (nYear == maCurDate.GetYear()))
+ {
+ bFocus = true;
+ }
+ if (mpSelectTable)
+ {
+ if (mpSelectTable->find(Date(nDay, nMonth, nYear).GetDate()) != mpSelectTable->end())
+ bSel = true;
+ }
+
+ // get textcolour
+ if (bSel)
+ pTextColor = &maSelColor;
+ else if (bOther)
+ pTextColor = &maOtherColor;
+
+ if (bFocus)
+ HideFocus();
+
+ // display background
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ if (bSel)
+ {
+ rRenderContext.SetLineColor();
+ rRenderContext.SetFillColor(rStyleSettings.GetHighlightColor());
+ rRenderContext.DrawRect(aDateRect);
+ }
+
+ // display text
+ long nTextX = nX + (mnDayWidth - GetTextWidth(rDay)) - (DAY_OFFX / 2);
+ 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());
+ long i;
+ long j;
+ long nY;
+ long nDeltaX;
+ long nDeltaY;
+ long nDayX;
+ 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);
+ }
+
+ 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);
+ long nMonthTextWidth = rRenderContext.GetTextWidth(aMonthText);
+ long nMonthOffX1 = 0;
+ long nMonthOffX2 = 0;
+ if (i == 0)
+ {
+ if (j == 0)
+ nMonthOffX1 = maPrevRect.Right() + 1;
+ if (j == mnMonthPerLine - 1)
+ nMonthOffX2 = aOutSize.Width() - maNextRect.Left() + 1;
+ }
+ 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);
+ }
+ 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));
+ rRenderContext.DrawTextArray(Point(nDayX + mnDayOfWeekAry[0], nDayY), maDayOfWeekText, &(mnDayOfWeekAry[1]));
+
+ // 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 )
+{
+ std::unique_ptr<IntDateSet> pOldSel(new IntDateSet( *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 = *pOldSel != *mpSelectTable;
+ if ( (maCurDate != aOldDate) || bNewSel )
+ {
+ HideFocus();
+ if ( bNewSel )
+ ImplUpdateSelection( pOldSel.get() );
+ if ( !bNewSel || pOldSel->find( aOldDate.GetDate() ) == pOldSel->end() )
+ ImplUpdateDate( aOldDate );
+ // assure focus rectangle is displayed again
+ if ( HasFocus() || !bNewSel
+ || mpSelectTable->find( maCurDate.GetDate() ) == mpSelectTable->end() )
+ ImplUpdateDate( maCurDate );
+ }
+}
+
+void Calendar::ImplUpdate( bool bCalcNew )
+{
+ if (IsReallyVisible() && IsUpdateMode())
+ {
+ if (bCalcNew && !mbCalc)
+ {
+ Invalidate();
+ }
+ else if (!mbFormat && !mbCalc)
+ {
+ Invalidate();
+ }
+ }
+
+ if (bCalcNew)
+ mbCalc = true;
+ mbFormat = true;
+}
+
+void Calendar::ImplScroll( bool bPrev )
+{
+ Date aNewFirstMonth = GetFirstMonth();
+ if ( bPrev )
+ {
+ --aNewFirstMonth;
+ aNewFirstMonth.AddDays( -(aNewFirstMonth.GetDaysInMonth()-1));
+ }
+ else
+ aNewFirstMonth.AddDays( aNewFirstMonth.GetDaysInMonth());
+ SetFirstDate( aNewFirstMonth );
+}
+
+void Calendar::ImplShowMenu( const Point& rPos, const Date& rDate )
+{
+ EndSelection();
+
+ Date aOldFirstDate = GetFirstMonth();
+ ScopedVclPtrInstance<PopupMenu> aPopupMenu;
+ sal_uInt16 nMonthOff;
+ sal_uInt16 nCurItemId;
+ sal_uInt16 nYear = rDate.GetYear()-1;
+ sal_uInt16 i;
+ sal_uInt16 j;
+ sal_uInt16 nYearIdCount = 1000;
+
+ nMonthOff = (rDate.GetYear()-aOldFirstDate.GetYear())*12;
+ if ( aOldFirstDate.GetMonth() < rDate.GetMonth() )
+ nMonthOff += rDate.GetMonth()-aOldFirstDate.GetMonth();
+ else
+ nMonthOff -= aOldFirstDate.GetMonth()-rDate.GetMonth();
+
+ // construct menu (include years with different months)
+ for ( i = 0; i < MENU_YEAR_COUNT; i++ )
+ {
+ VclPtrInstance<PopupMenu> pYearPopupMenu;
+ for ( j = 1; j <= 12; j++ )
+ pYearPopupMenu->InsertItem( nYearIdCount+j,
+ maCalendarWrapper.getDisplayName(
+ i18n::CalendarDisplayIndex::MONTH, j-1, 1));
+ aPopupMenu->InsertItem( 10+i, OUString::number( nYear+i ) );
+ aPopupMenu->SetPopupMenu( 10+i, pYearPopupMenu );
+ nYearIdCount += 1000;
+ }
+
+ mbMenuDown = true;
+ nCurItemId = aPopupMenu->Execute( this, rPos );
+ mbMenuDown = false;
+
+ if ( !nCurItemId )
+ return;
+
+ sal_uInt16 nTempMonthOff = nMonthOff % 12;
+ sal_uInt16 nTempYearOff = nMonthOff / 12;
+ sal_uInt16 nNewMonth = nCurItemId % 1000;
+ sal_uInt16 nNewYear = nYear+((nCurItemId-1000)/1000);
+ if ( nTempMonthOff < nNewMonth )
+ nNewMonth = nNewMonth - nTempMonthOff;
+ else
+ {
+ nNewYear--;
+ nNewMonth = 12-(nTempMonthOff-nNewMonth);
+ }
+ nNewYear = nNewYear - nTempYearOff;
+ SetFirstDate( Date( 1, nNewMonth, nNewYear ) );
+}
+
+void Calendar::ImplTracking( const Point& rPos, bool bRepeat )
+{
+ Date aTempDate = maCurDate;
+ sal_uInt16 nHitTest = ImplHitTest( rPos, aTempDate );
+
+ if ( mbSpinDown )
+ {
+ mbPrevIn = (nHitTest & CALENDAR_HITTEST_PREV) != 0;
+ mbNextIn = (nHitTest & CALENDAR_HITTEST_NEXT) != 0;
+
+ if ( bRepeat && (mbPrevIn || mbNextIn) )
+ {
+ ImplScroll( mbPrevIn );
+ }
+ }
+ else
+ ImplMouseSelect( aTempDate, nHitTest );
+}
+
+void Calendar::ImplEndTracking( bool bCancel )
+{
+ bool bSelection = mbSelection;
+ bool bSpinDown = mbSpinDown;
+
+ mbDrag = false;
+ mbSelection = false;
+ mbSpinDown = false;
+ mbPrevIn = false;
+ mbNextIn = false;
+
+ if ( bCancel )
+ {
+ if ( maOldFirstDate != maFirstDate )
+ SetFirstDate( maOldFirstDate );
+
+ if ( !bSpinDown )
+ {
+ std::unique_ptr<IntDateSet> pOldSel(new IntDateSet( *mpSelectTable ));
+ Date aOldDate = maCurDate;
+ maCurDate = maOldCurDate;
+ *mpSelectTable = *mpOldSelectTable;
+ HideFocus();
+ ImplUpdateSelection( pOldSel.get() );
+ if ( pOldSel->find( aOldDate.GetDate() ) == pOldSel->end() )
+ ImplUpdateDate( aOldDate );
+ // assure focus rectangle is displayed again
+ if ( HasFocus() || mpSelectTable->find( maCurDate.GetDate() ) == mpSelectTable->end() )
+ ImplUpdateDate( maCurDate );
+ }
+ }
+
+ if ( bSpinDown )
+ return;
+
+ if ( !bCancel )
+ {
+ // determine if we should scroll the visible area
+ if ( !mpSelectTable->empty() )
+ {
+ Date aFirstSelDate( *mpSelectTable->begin() );
+ Date aLastSelDate( *mpSelectTable->rbegin() );
+ if ( aLastSelDate < GetFirstMonth() )
+ ImplScroll( true );
+ else if ( GetLastMonth() < aFirstSelDate )
+ ImplScroll( false );
+ }
+ }
+
+ if ( mbAllSel ||
+ (!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 = ImplHitTest( rMEvt.GetPosPixel(), aTempDate );
+ if ( nHitTest )
+ {
+ if ( nHitTest & CALENDAR_HITTEST_MONTHTITLE )
+ ImplShowMenu( rMEvt.GetPosPixel(), aTempDate );
+ else
+ {
+ maOldFirstDate = maFirstDate;
+
+ mbPrevIn = (nHitTest & CALENDAR_HITTEST_PREV) != 0;
+ mbNextIn = (nHitTest & CALENDAR_HITTEST_NEXT) != 0;
+ if ( mbPrevIn || mbNextIn )
+ {
+ mbSpinDown = true;
+ ImplScroll( mbPrevIn );
+ // it should really read BUTTONREPEAT, therefore do not
+ // change it to SCROLLREPEAT, check with TH,
+ // why it could be different (71775)
+ StartTracking( StartTrackingFlags::ButtonRepeat );
+ }
+ else
+ {
+ if ( (rMEvt.GetClicks() != 2) || !(nHitTest & CALENDAR_HITTEST_DAY) )
+ {
+ maOldCurDate = maCurDate;
+ mpOldSelectTable.reset(new IntDateSet( *mpSelectTable ));
+
+ if ( !mbSelection )
+ {
+ mbDrag = true;
+ StartTracking();
+ }
+
+ ImplMouseSelect( aTempDate, nHitTest );
+ }
+ if (rMEvt.GetClicks() == 2)
+ maActivateHdl.Call(this);
+ }
+ }
+ }
+
+ return;
+ }
+
+ Control::MouseButtonDown( rMEvt );
+}
+
+void Calendar::MouseButtonUp( const MouseEvent& rMEvt )
+{
+ if ( rMEvt.IsLeft() && mbSelection )
+ ImplEndTracking( false );
+ else
+ Control::MouseButtonUp( rMEvt );
+}
+
+void Calendar::MouseMove( const MouseEvent& rMEvt )
+{
+ if ( mbSelection && rMEvt.GetButtons() )
+ ImplTracking( rMEvt.GetPosPixel(), false );
+ else
+ Control::MouseMove( 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 );
+ mbTravelSelect = true;
+ Select();
+ mbTravelSelect = false;
+ }
+
+ 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 ( !mbSelection && rCEvt.IsMouseEvent() )
+ {
+ Date aTempDate = maCurDate;
+ sal_uInt16 nHitTest = ImplHitTest( 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 )
+ {
+ long nNotchDelta = pData->GetNotchDelta();
+ if ( nNotchDelta < 0 )
+ {
+ while ( nNotchDelta < 0 )
+ {
+ ImplScroll( true );
+ nNotchDelta++;
+ }
+ }
+ else
+ {
+ while ( nNotchDelta > 0 )
+ {
+ ImplScroll( false );
+ nNotchDelta--;
+ }
+ }
+
+ return;
+ }
+ }
+
+ Control::Command( rCEvt );
+}
+
+void Calendar::StateChanged( StateChangedType nType )
+{
+ Control::StateChanged( nType );
+
+ if ( nType == StateChangedType::InitShow )
+ ImplFormat();
+}
+
+void Calendar::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Control::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
+ {
+ ImplInitSettings();
+ Invalidate();
+ }
+}
+
+void Calendar::Select()
+{
+ maSelectHdl.Call( this );
+}
+
+Date Calendar::GetFirstSelectedDate() const
+{
+ if ( !mpSelectTable->empty() )
+ return Date( *mpSelectTable->begin() );
+ else
+ {
+ Date aDate( 0, 0, 0 );
+ return aDate;
+ }
+}
+
+void Calendar::SetCurDate( const Date& rNewDate )
+{
+ if ( !rNewDate.IsValidAndGregorian() )
+ return;
+
+ if ( maCurDate == rNewDate )
+ return;
+
+ bool bUpdate = IsVisible() && IsUpdateMode();
+ Date aOldDate = maCurDate;
+ maCurDate = rNewDate;
+
+ ImplCalendarSelectDate( mpSelectTable.get(), aOldDate, false );
+ ImplCalendarSelectDate( mpSelectTable.get(), maCurDate, true );
+
+ // shift actual date in the visible area
+ if ( mbFormat || (maCurDate < GetFirstMonth()) )
+ SetFirstDate( maCurDate );
+ else if ( maCurDate > GetLastMonth() )
+ {
+ Date aTempDate = GetLastMonth();
+ 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 = ImplHitTest( 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;
+
+ long nX;
+ 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 ( long i = 0; i < mnLines; i++ )
+ {
+ nX = 0;
+ for ( long j = 0; j < mnMonthPerLine; j++ )
+ {
+ sal_uInt16 nDaysInMonth = aDate.GetDaysInMonth();
+
+ // month is called
+ if ( (aDate.GetMonth() == rDate.GetMonth()) &&
+ (aDate.GetYear() == rDate.GetYear()) )
+ {
+ long nDayX = nX+mnDaysOffX;
+ 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::StartSelection()
+{
+ maOldCurDate = maCurDate;
+ mpOldSelectTable.reset(new IntDateSet( *mpSelectTable ));
+
+ mbSelection = true;
+}
+
+void Calendar::EndSelection()
+{
+ if ( mbDrag || mbSpinDown || mbSelection )
+ {
+ if ( !mbSelection )
+ ReleaseMouse();
+
+ mbDrag = false;
+ mbSelection = false;
+ mbSpinDown = false;
+ mbPrevIn = false;
+ mbNextIn = false;
+ }
+}
+
+Size Calendar::CalcWindowSizePixel() const
+{
+ OUString const a99Text("99");
+
+ Size aSize;
+ long n99TextWidth = GetTextWidth( a99Text );
+ 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();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/combobox.cxx b/vcl/source/control/combobox.cxx
new file mode 100644
index 000000000..0de42f530
--- /dev/null
+++ b/vcl/source/control/combobox.cxx
@@ -0,0 +1,1542 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/lstbox.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/event.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/uitest/uiobject.hxx>
+#include <sal/log.hxx>
+
+#include <listbox.hxx>
+#include <controldata.hxx>
+#include <comphelper/lok.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, const OUString& rText, sal_Unicode cTokenSep, const ImplEntryList* pEntryList )
+{
+ if (rText.isEmpty())
+ return;
+
+ sal_Int32 nIdx{0};
+ do {
+ const sal_Int32 nPos = pEntryList->FindEntry(comphelper::string::strip(rText.getToken(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 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
+ m_pImpl->m_pImplLB->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;
+ OUString aToken = aText.getToken( 0, m_cMultiSep, nIndex );
+ sal_Int32 nTokenLen = aToken.getLength();
+ 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, "" );
+ nIndex = nIndex - nTokenLen;
+ sal_Int32 nSepCount=0;
+ if ((nPrevIndex+nSepCount < aText.getLength()) && (aText[nPrevIndex+nSepCount] == m_cMultiSep))
+ {
+ nIndex--;
+ ++nSepCount;
+ }
+ aText = aText.replaceAt( nPrevIndex, nSepCount, "" );
+ }
+ 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() )
+ {
+ 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( long nX, long nY, long nWidth, 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
+{
+ mpControlData->mpLayoutData.reset( new vcl::ControlLayoutData );
+ AppendLayoutData( *m_pImpl->m_pSubEdit );
+ m_pImpl->m_pSubEdit->SetLayoutDataParent( this );
+ ImplListBoxWindow* rMainWindow = m_pImpl->m_pImplLB->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() ) );
+ m_pImpl->m_pImplLB->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)) )
+ {
+ if (m_pImpl->m_pBtn)
+ {
+ m_pImpl->m_pBtn->SetSettings( GetSettings() );
+ ImplInitDropDownButton( m_pImpl->m_pBtn );
+ }
+ Resize();
+ m_pImpl->m_pImplLB->Resize(); // not called by ComboBox::Resize() if ImplLB is unchanged
+
+ SetBackground(); // due to a hack in Window::UpdateSettings the background must be reset
+ // otherwise it will overpaint NWF drawn comboboxes
+ }
+}
+
+bool ComboBox::EventNotify( NotifyEvent& rNEvt )
+{
+ bool bDone = false;
+ if ((rNEvt.GetType() == MouseNotifyEvent::KEYINPUT)
+ && (rNEvt.GetWindow() == m_pImpl->m_pSubEdit)
+ && !IsReadOnly())
+ {
+ KeyEvent aKeyEvt = *rNEvt.GetKeyEvent();
+ sal_uInt16 nKeyCode = aKeyEvt.GetKeyCode().GetCode();
+ switch( nKeyCode )
+ {
+ case KEY_UP:
+ case KEY_DOWN:
+ case KEY_PAGEUP:
+ case KEY_PAGEDOWN:
+ {
+ m_pImpl->ImplUpdateFloatSelection();
+ if ((nKeyCode == KEY_DOWN) && m_pImpl->m_pFloatWin
+ && !m_pImpl->m_pFloatWin->IsInPopupMode()
+ && aKeyEvt.GetKeyCode().IsMod2())
+ {
+ CallEventListeners( VclEventId::DropdownPreOpen );
+ m_pImpl->m_pBtn->SetPressed( true );
+ if (m_pImpl->m_pImplLB->GetEntryList()->GetMRUCount())
+ m_pImpl->m_pImplLB->SelectEntry( 0 , true );
+ SetSelection( Selection( 0, SELECTION_MAX ) );
+ m_pImpl->m_pFloatWin->StartFloat( false );
+ CallEventListeners( VclEventId::DropdownOpen );
+ bDone = true;
+ }
+ else if ((nKeyCode == KEY_UP) && m_pImpl->m_pFloatWin
+ && m_pImpl->m_pFloatWin->IsInPopupMode()
+ && aKeyEvt.GetKeyCode().IsMod2())
+ {
+ m_pImpl->m_pFloatWin->EndPopupMode();
+ bDone = true;
+ }
+ else
+ {
+ bDone = m_pImpl->m_pImplLB->ProcessKeyInput( aKeyEvt );
+ }
+ }
+ break;
+
+ case KEY_RETURN:
+ {
+ if ((rNEvt.GetWindow() == m_pImpl->m_pSubEdit) && IsInDropDown())
+ {
+ m_pImpl->m_pImplLB->ProcessKeyInput( aKeyEvt );
+ bDone = true;
+ }
+ }
+ break;
+ }
+ }
+ else if ((rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS) && m_pImpl->m_pFloatWin)
+ {
+ if (m_pImpl->m_pFloatWin->HasChildPathFocus())
+ m_pImpl->m_pSubEdit->GrabFocus();
+ else if (m_pImpl->m_pFloatWin->IsInPopupMode() && !HasChildPathFocus(true))
+ m_pImpl->m_pFloatWin->EndPopupMode();
+ }
+ else if( (rNEvt.GetType() == MouseNotifyEvent::COMMAND) &&
+ (rNEvt.GetCommandEvent()->GetCommand() == CommandEventId::Wheel) &&
+ (rNEvt.GetWindow() == m_pImpl->m_pSubEdit) )
+ {
+ MouseWheelBehaviour nWheelBehavior( GetSettings().GetMouseSettings().GetWheelBehavior() );
+ if ( ( nWheelBehavior == MouseWheelBehaviour::ALWAYS )
+ || ( ( nWheelBehavior == MouseWheelBehaviour::FocusOnly )
+ && HasChildPathFocus()
+ )
+ )
+ {
+ bDone = m_pImpl->m_pImplLB->HandleWheelAsCursorTravel( *rNEvt.GetCommandEvent() );
+ }
+ else
+ {
+ bDone = false; // don't eat this event, let the default handling happen (i.e. scroll the context)
+ }
+ }
+ else if ((rNEvt.GetType() == MouseNotifyEvent::MOUSEBUTTONDOWN)
+ && (rNEvt.GetWindow() == m_pImpl->m_pImplLB->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( const OUString& 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();
+}
+
+long ComboBox::getMaxWidthScrollBarAndDownButton() const
+{
+ long nButtonDownWidth = 0;
+
+ vcl::Window *pBorder = GetWindow( GetWindowType::Border );
+ ImplControlValue aControlValue;
+ tools::Rectangle aContent, aBound;
+
+ // use the full extent of the control
+ tools::Rectangle aArea( Point(), pBorder->GetOutputSizePixel() );
+
+ if ( GetNativeControlRegion(ControlType::Combobox, ControlPart::ButtonDown,
+ aArea, ControlState::NONE, aControlValue, aBound, aContent) )
+ {
+ nButtonDownWidth = aContent.getWidth();
+ }
+
+ 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)
+ {
+ 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() )
+ {
+ long nEntryHeight = CalcBlockSize( 1, 1 ).Height();
+ 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;
+}
+
+long ComboBox::GetDropDownEntryHeight() const
+{
+ return m_pImpl->m_pImplLB->GetEntryHeight();
+}
+
+void ComboBox::GetMaxVisColumnsAndLines( sal_uInt16& rnCols, sal_uInt16& rnLines ) const
+{
+ long nCharWidth = GetTextWidth(OUString(u'x'));
+ if ( !IsDropDownBox() )
+ {
+ Size aOutSz = m_pImpl->m_pImplLB->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, DrawFlags nFlags )
+{
+ m_pImpl->m_pImplLB->GetMainWindow()->ApplySettings(*pDev);
+
+ Point aPos = pDev->LogicToPixel( rPos );
+ Size aSize = GetSizePixel();
+ vcl::Font aFont = m_pImpl->m_pImplLB->GetMainWindow()->GetDrawPixelFont( pDev );
+
+ pDev->Push();
+ pDev->SetMapMode();
+ pDev->SetFont( aFont );
+ pDev->SetTextFillColor();
+
+ // Border/Background
+ pDev->SetLineColor();
+ pDev->SetFillColor();
+ bool bBorder = (GetStyle() & WB_BORDER);
+ bool bBackground = IsControlBackground();
+ if ( bBorder || bBackground )
+ {
+ tools::Rectangle aRect( aPos, aSize );
+ // aRect.Top() += nEditHeight;
+ if ( bBorder )
+ {
+ ImplDrawFrame( pDev, aRect );
+ }
+ if ( bBackground )
+ {
+ pDev->SetFillColor( GetControlBackground() );
+ pDev->DrawRect( aRect );
+ }
+ }
+
+ // contents
+ if ( !IsDropDownBox() )
+ {
+ long nOnePixel = GetDrawPixel( pDev, 1 );
+ long nTextHeight = pDev->GetTextHeight();
+ long nEditHeight = nTextHeight + 6*nOnePixel;
+ DrawTextFlags nTextStyle = DrawTextFlags::VCenter;
+
+ // First, draw the edit part
+ Size aOrigSize(m_pImpl->m_pSubEdit->GetSizePixel());
+ m_pImpl->m_pSubEdit->SetSizePixel(Size(aSize.Width(), nEditHeight));
+ m_pImpl->m_pSubEdit->Draw( pDev, aPos, nFlags );
+ m_pImpl->m_pSubEdit->SetSizePixel(aOrigSize);
+
+ // Second, draw the listbox
+ if ( GetStyle() & WB_CENTER )
+ nTextStyle |= DrawTextFlags::Center;
+ else if ( GetStyle() & WB_RIGHT )
+ nTextStyle |= DrawTextFlags::Right;
+ else
+ nTextStyle |= DrawTextFlags::Left;
+
+ if ( nFlags & DrawFlags::Mono )
+ {
+ pDev->SetTextColor( COL_BLACK );
+ }
+ else
+ {
+ if ( !IsEnabled() )
+ {
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ pDev->SetTextColor( rStyleSettings.GetDisableColor() );
+ }
+ else
+ {
+ pDev->SetTextColor( GetTextColor() );
+ }
+ }
+
+ tools::Rectangle aClip( aPos, aSize );
+ pDev->IntersectClipRegion( aClip );
+ sal_Int32 nLines = static_cast<sal_Int32>( nTextHeight > 0 ? (aSize.Height()-nEditHeight)/nTextHeight : 1 );
+ if ( !nLines )
+ nLines = 1;
+ const sal_Int32 nTEntry = IsReallyVisible() ? m_pImpl->m_pImplLB->GetTopEntry() : 0;
+
+ tools::Rectangle aTextRect( aPos, aSize );
+
+ aTextRect.AdjustLeft(3*nOnePixel );
+ aTextRect.AdjustRight( -(3*nOnePixel) );
+ aTextRect.AdjustTop(nEditHeight + nOnePixel );
+ aTextRect.SetBottom( aTextRect.Top() + nTextHeight );
+
+ // the drawing starts here
+ for ( sal_Int32 n = 0; n < nLines; ++n )
+ {
+ pDev->DrawText( aTextRect, m_pImpl->m_pImplLB->GetEntryList()->GetEntryText( n+nTEntry ), nTextStyle );
+ aTextRect.AdjustTop(nTextHeight );
+ aTextRect.AdjustBottom(nTextHeight );
+ }
+ }
+
+ pDev->Pop();
+
+ // Call Edit::Draw after restoring the MapMode...
+ if ( IsDropDownBox() )
+ {
+ Size aOrigSize(m_pImpl->m_pSubEdit->GetSizePixel());
+ m_pImpl->m_pSubEdit->SetSizePixel(GetSizePixel());
+ m_pImpl->m_pSubEdit->Draw( pDev, rPos, nFlags );
+ m_pImpl->m_pSubEdit->SetSizePixel(aOrigSize);
+ // DD-Button ?
+ }
+}
+
+void ComboBox::SetUserDrawHdl(const Link<UserDrawEvent*, void>& rLink)
+{
+ m_pImpl->m_pImplLB->SetUserDrawHdl(rLink);
+}
+
+void ComboBox::SetUserItemSize( const Size& rSz )
+{
+ m_pImpl->m_pImplLB->GetMainWindow()->SetUserItemSize( rSz );
+}
+
+void ComboBox::EnableUserDraw( bool bUserDraw )
+{
+ m_pImpl->m_pImplLB->GetMainWindow()->EnableUserDraw( bUserDraw );
+}
+
+bool ComboBox::IsUserDrawEnabled() const
+{
+ return m_pImpl->m_pImplLB->GetMainWindow()->IsUserDrawEnabled();
+}
+
+void ComboBox::DrawEntry(const UserDrawEvent& rEvt)
+{
+ SAL_WARN_IF(rEvt.GetWindow() != m_pImpl->m_pImplLB->GetMainWindow(), "vcl", "DrawEntry?!");
+ m_pImpl->m_pImplLB->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( const OUString& 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(const_cast<ComboBox*>(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 == 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 = m_pImpl->m_pImplLB->GetMainWindow()->GetBoundingRectangle( nItem );
+ tools::Rectangle aOffset = m_pImpl->m_pImplLB->GetMainWindow()->GetWindowExtentsRelative( static_cast<vcl::Window*>(const_cast<ComboBox *>(this)) );
+ aRect.Move( aOffset.TopLeft().X(), aOffset.TopLeft().Y() );
+ return aRect;
+}
+
+void ComboBox::SetBorderStyle( WindowBorderStyle nBorderStyle )
+{
+ Window::SetBorderStyle( nBorderStyle );
+ if ( !IsDropDownBox() )
+ {
+ m_pImpl->m_pSubEdit->SetBorderStyle( nBorderStyle );
+ m_pImpl->m_pImplLB->SetBorderStyle( nBorderStyle );
+ }
+}
+
+long ComboBox::GetIndexForPoint( const Point& rPoint, sal_Int32& rPos ) const
+{
+ if( !HasLayoutData() )
+ FillLayoutData();
+
+ // check whether rPoint fits at all
+ long nIndex = Control::GetIndexForPoint( rPoint );
+ if( nIndex != -1 )
+ {
+ // point must be either in main list window
+ // or in impl window (dropdown case)
+ ImplListBoxWindow* rMain = m_pImpl->m_pImplLB->GetMainWindow();
+
+ // convert coordinates to ImplListBoxWindow pixel coordinate space
+ Point aConvPoint = LogicToPixel( rPoint );
+ aConvPoint = OutputToAbsoluteScreenPixel( aConvPoint );
+ aConvPoint = rMain->AbsoluteScreenToOutputPixel( aConvPoint );
+ aConvPoint = rMain->PixelToLogic( aConvPoint );
+
+ // try to find entry
+ sal_Int32 nEntry = rMain->GetEntryPosForPoint( aConvPoint );
+ if( nEntry == LISTBOX_ENTRY_NOTFOUND )
+ nIndex = -1;
+ else
+ rPos = nEntry;
+ }
+
+ // get line relative index
+ if( nIndex != -1 )
+ nIndex = ToRelativeLineIndex( nIndex );
+
+ return nIndex;
+}
+
+ComboBoxBounds ComboBox::Impl::calcComboBoxDropDownComponentBounds(
+ const Size &rOutSz, const Size &rBorderOutSz) const
+{
+ ComboBoxBounds aBounds;
+
+ long nTop = 0;
+ long nBottom = rOutSz.Height();
+
+ vcl::Window *pBorder = m_rThis.GetWindow( GetWindowType::Border );
+ ImplControlValue aControlValue;
+ Point aPoint;
+ tools::Rectangle aContent, aBound;
+
+ // use the full extent of the control
+ tools::Rectangle aArea( aPoint, rBorderOutSz );
+
+ if (m_rThis.GetNativeControlRegion(ControlType::Combobox, ControlPart::ButtonDown,
+ aArea, ControlState::NONE, aControlValue, aBound, aContent) )
+ {
+ // convert back from border space to local coordinates
+ aPoint = pBorder->ScreenToOutputPixel(m_rThis.OutputToScreenPixel(aPoint));
+ aContent.Move(-aPoint.X(), -aPoint.Y());
+
+ aBounds.aButtonPos = Point(aContent.Left(), nTop);
+ aBounds.aButtonSize = Size(aContent.getWidth(), (nBottom-nTop));
+
+ // adjust the size of the edit field
+ if (m_rThis.GetNativeControlRegion(ControlType::Combobox, ControlPart::SubEdit,
+ aArea, ControlState::NONE, aControlValue, aBound, aContent) )
+ {
+ // convert back from border space to local coordinates
+ aContent.Move(-aPoint.X(), -aPoint.Y());
+
+ // use the themes drop down size
+ aBounds.aSubEditPos = aContent.TopLeft();
+ aBounds.aSubEditSize = aContent.GetSize();
+ }
+ else
+ {
+ // use the themes drop down size for the button
+ aBounds.aSubEditSize = Size(rOutSz.Width() - aContent.getWidth(), rOutSz.Height());
+ }
+ }
+ else
+ {
+ long nSBWidth = m_rThis.GetSettings().GetStyleSettings().GetScrollBarSize();
+ nSBWidth = m_rThis.CalcZoom( nSBWidth );
+ aBounds.aSubEditSize = Size(rOutSz.Width() - nSBWidth, rOutSz.Height());
+ aBounds.aButtonPos = Point(rOutSz.Width() - nSBWidth, nTop);
+ aBounds.aButtonSize = Size(nSBWidth, (nBottom-nTop));
+ }
+ return aBounds;
+}
+
+void ComboBox::SetWidthInChars(sal_Int32 nWidthInChars)
+{
+ if (nWidthInChars != m_pImpl->m_nWidthInChars)
+ {
+ m_pImpl->m_nWidthInChars = nWidthInChars;
+ queue_resize();
+ }
+}
+
+void ComboBox::setMaxWidthChars(sal_Int32 nWidth)
+{
+ if (nWidth != m_pImpl->m_nMaxWidthChars)
+ {
+ m_pImpl->m_nMaxWidthChars = nWidth;
+ queue_resize();
+ }
+}
+
+bool ComboBox::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "width-chars")
+ SetWidthInChars(rValue.toInt32());
+ else if (rKey == "max-width-chars")
+ setMaxWidthChars(rValue.toInt32());
+ else if (rKey == "can-focus")
+ {
+ // as far as I can see in Gtk, setting a ComboBox as can.focus means
+ // the focus gets stuck in it, so try here to behave like gtk does
+ // with the settings that work, i.e. can.focus of false doesn't
+ // set the hard WB_NOTABSTOP
+ WinBits nBits = GetStyle();
+ nBits &= ~(WB_TABSTOP|WB_NOTABSTOP);
+ if (toBool(rValue))
+ nBits |= WB_TABSTOP;
+ SetStyle(nBits);
+ }
+ else if (rKey == "placeholder-text")
+ SetPlaceholderText(rValue);
+ else
+ return Control::set_property(rKey, rValue);
+ return true;
+}
+
+FactoryFunction ComboBox::GetUITestFactory() const
+{
+ return ComboBoxUIObject::create;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/ctrl.cxx b/vcl/source/control/ctrl.cxx
new file mode 100644
index 000000000..81fea0000
--- /dev/null
+++ b/vcl/source/control/ctrl.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 <vcl/svapp.hxx>
+#include <vcl/event.hxx>
+#include <vcl/ctrl.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/uitest/logger.hxx>
+#include <sal/log.hxx>
+
+#include <textlayout.hxx>
+#include <svdata.hxx>
+#include <controldata.hxx>
+
+using namespace vcl;
+
+void Control::ImplInitControlData()
+{
+ mbHasControlFocus = false;
+ mbShowAccelerator = false;
+ mpControlData.reset(new ImplControlData);
+}
+
+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()
+{
+ mpControlData.reset();
+ Window::dispose();
+}
+
+void Control::EnableRTL( bool bEnable )
+{
+ // convenience: for controls also switch layout mode
+ SetLayoutMode( bEnable ? ComplexTextLayoutFlags::BiDiRtl | ComplexTextLayoutFlags::TextOriginLeft :
+ ComplexTextLayoutFlags::TextOriginLeft );
+ CompatStateChanged( StateChangedType::Mirroring );
+ OutputDevice::EnableRTL(bEnable);
+}
+
+void Control::Resize()
+{
+ ImplClearLayoutData();
+ Window::Resize();
+}
+
+void Control::FillLayoutData() const
+{
+}
+
+void Control::CreateLayoutData() const
+{
+ SAL_WARN_IF( mpControlData->mpLayoutData, "vcl", "Control::CreateLayoutData: should be called with non-existent layout data only!" );
+ mpControlData->mpLayoutData.reset( new vcl::ControlLayoutData );
+}
+
+bool Control::HasLayoutData() const
+{
+ return mpControlData && mpControlData->mpLayoutData != nullptr;
+}
+
+void Control::SetText( const OUString& rStr )
+{
+ ImplClearLayoutData();
+ Window::SetText( rStr );
+}
+
+ControlLayoutData::ControlLayoutData() : m_pParent( nullptr )
+{
+}
+
+tools::Rectangle ControlLayoutData::GetCharacterBounds( long nIndex ) const
+{
+ return (nIndex >= 0 && nIndex < static_cast<long>(m_aUnicodeBoundRects.size())) ? m_aUnicodeBoundRects[ nIndex ] : tools::Rectangle();
+}
+
+tools::Rectangle Control::GetCharacterBounds( long nIndex ) const
+{
+ if( !HasLayoutData() )
+ FillLayoutData();
+ return mpControlData->mpLayoutData ? mpControlData->mpLayoutData->GetCharacterBounds( nIndex ) : tools::Rectangle();
+}
+
+long ControlLayoutData::GetIndexForPoint( const Point& rPoint ) const
+{
+ long nIndex = -1;
+ for( 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;
+}
+
+long Control::GetIndexForPoint( const Point& rPoint ) const
+{
+ if( ! HasLayoutData() )
+ FillLayoutData();
+ return mpControlData->mpLayoutData ? mpControlData->mpLayoutData->GetIndexForPoint( rPoint ) : -1;
+}
+
+long ControlLayoutData::GetLineCount() const
+{
+ long nLines = m_aLineIndices.size();
+ if( nLines == 0 && !m_aDisplayText.isEmpty() )
+ nLines = 1;
+ return nLines;
+}
+
+Pair ControlLayoutData::GetLineStartEnd( 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( long nLine ) const
+{
+ if( !HasLayoutData() )
+ FillLayoutData();
+ return mpControlData->mpLayoutData ? mpControlData->mpLayoutData->GetLineStartEnd( nLine ) : Pair( -1, -1 );
+}
+
+long ControlLayoutData::ToRelativeLineIndex( 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;
+}
+
+long Control::ToRelativeLineIndex( long nIndex ) const
+{
+ if( !HasLayoutData() )
+ FillLayoutData();
+ return mpControlData->mpLayoutData ? mpControlData->mpLayoutData->ToRelativeLineIndex( nIndex ) : -1;
+}
+
+OUString Control::GetDisplayText() const
+{
+ if( !HasLayoutData() )
+ FillLayoutData();
+ return mpControlData->mpLayoutData ? mpControlData->mpLayoutData->m_aDisplayText : GetText();
+}
+
+bool Control::EventNotify( NotifyEvent& rNEvt )
+{
+ // tdf#91081 if control is not valid, skip the emission - chaining to the parent
+ if (mpControlData)
+ {
+ if ( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS )
+ {
+ if ( !mbHasControlFocus )
+ {
+ mbHasControlFocus = true;
+ CompatStateChanged( StateChangedType::ControlFocus );
+ if ( ImplCallEventListenersAndHandler( VclEventId::ControlGetFocus, {} ) )
+ // been destroyed within the handler
+ return true;
+ }
+ }
+ else
+ {
+ if ( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
+ {
+ vcl::Window* pFocusWin = Application::GetFocusWindow();
+ if ( !pFocusWin || !ImplIsWindowOrChild( pFocusWin ) )
+ {
+ mbHasControlFocus = false;
+ CompatStateChanged( StateChangedType::ControlFocus );
+ if ( ImplCallEventListenersAndHandler( VclEventId::ControlLoseFocus, [this] () { maLoseFocusHdl.Call(*this); } ) )
+ // been destroyed within the handler
+ return true;
+ }
+ }
+ }
+ }
+ return Window::EventNotify( rNEvt );
+}
+
+void Control::StateChanged( StateChangedType nStateChange )
+{
+ if( nStateChange == StateChangedType::InitShow ||
+ nStateChange == StateChangedType::Visible ||
+ nStateChange == StateChangedType::Zoom ||
+ nStateChange == StateChangedType::ControlFont
+ )
+ {
+ ImplClearLayoutData();
+ }
+ Window::StateChanged( nStateChange );
+}
+
+void Control::AppendLayoutData( const Control& rSubControl ) const
+{
+ if( !rSubControl.HasLayoutData() )
+ rSubControl.FillLayoutData();
+ if( !rSubControl.HasLayoutData() || rSubControl.mpControlData->mpLayoutData->m_aDisplayText.isEmpty() )
+ return;
+
+ long nCurrentIndex = mpControlData->mpLayoutData->m_aDisplayText.getLength();
+ mpControlData->mpLayoutData->m_aDisplayText += rSubControl.mpControlData->mpLayoutData->m_aDisplayText;
+ int nLines = rSubControl.mpControlData->mpLayoutData->m_aLineIndices.size();
+ int n;
+ mpControlData->mpLayoutData->m_aLineIndices.push_back( nCurrentIndex );
+ for( n = 1; n < nLines; n++ )
+ mpControlData->mpLayoutData->m_aLineIndices.push_back( rSubControl.mpControlData->mpLayoutData->m_aLineIndices[n] + nCurrentIndex );
+ int nRectangles = rSubControl.mpControlData->mpLayoutData->m_aUnicodeBoundRects.size();
+ tools::Rectangle aRel = const_cast<Control&>(rSubControl).GetWindowExtentsRelative( const_cast<Control*>(this) );
+ for( n = 0; n < nRectangles; n++ )
+ {
+ tools::Rectangle aRect = rSubControl.mpControlData->mpLayoutData->m_aUnicodeBoundRects[n];
+ aRect.Move( aRel.Left(), aRel.Top() );
+ mpControlData->mpLayoutData->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() )
+ mpControlData->mpLayoutData->m_pParent = pParent;
+}
+
+void Control::ImplClearLayoutData() const
+{
+ if (mpControlData)
+ mpControlData->mpLayoutData.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 ( mpControlData->mpReferenceDevice == _referenceDevice )
+ return;
+
+ mpControlData->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 != mpControlData->mpReferenceDevice && mpControlData->mpReferenceDevice->isDisposed())
+ {
+ const_cast<Control*>(this)->SetReferenceDevice(nullptr);
+ }
+
+ return mpControlData->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(*this);
+}
+
+tools::Rectangle Control::DrawControlText( OutputDevice& _rTargetDevice, const tools::Rectangle& rRect, const OUString& _rStr,
+ DrawTextFlags _nStyle, MetricVector* _pVector, OUString* _pDisplayText, const Size* i_pDeviceSize ) const
+{
+ OUString rPStr = _rStr;
+ DrawTextFlags nPStyle = _nStyle;
+
+ bool autoacc = ImplGetSVData()->maNWFData.mbAutoAccel;
+
+ if (autoacc && !mbShowAccelerator)
+ {
+ rPStr = GetNonMnemonicString( _rStr );
+ nPStyle &= ~DrawTextFlags::HideMnemonic;
+ }
+
+ if( !GetReferenceDevice() || ( GetReferenceDevice() == &_rTargetDevice ) )
+ {
+ const tools::Rectangle aRet = _rTargetDevice.GetTextRect(rRect, rPStr, nPStyle);
+ _rTargetDevice.DrawText(aRet, rPStr, nPStyle, _pVector, _pDisplayText);
+ return aRet;
+ }
+
+ ControlTextRenderer aRenderer( *this, _rTargetDevice, *GetReferenceDevice() );
+ return aRenderer.DrawText(rRect, rPStr, nPStyle, _pVector, _pDisplayText, i_pDeviceSize);
+}
+
+tools::Rectangle Control::GetControlTextRect( OutputDevice& _rTargetDevice, const tools::Rectangle & rRect,
+ const OUString& _rStr, DrawTextFlags _nStyle, Size* o_pDeviceSize ) const
+{
+ OUString rPStr = _rStr;
+ DrawTextFlags nPStyle = _nStyle;
+
+ bool autoacc = ImplGetSVData()->maNWFData.mbAutoAccel;
+
+ if (autoacc && !mbShowAccelerator)
+ {
+ rPStr = GetNonMnemonicString( _rStr );
+ nPStyle &= ~DrawTextFlags::HideMnemonic;
+ }
+
+ if ( !GetReferenceDevice() || ( GetReferenceDevice() == &_rTargetDevice ) )
+ {
+ tools::Rectangle aRet = _rTargetDevice.GetTextRect( rRect, rPStr, nPStyle );
+ if (o_pDeviceSize)
+ {
+ *o_pDeviceSize = aRet.GetSize();
+ }
+ return aRet;
+ }
+
+ ControlTextRenderer aRenderer( *this, _rTargetDevice, *GetReferenceDevice() );
+ return aRenderer.GetTextRect(rRect, rPStr, nPStyle, o_pDeviceSize);
+}
+
+Font
+Control::GetUnzoomedControlPointFont() const
+{
+ Font aFont(GetCanonicalFont(GetSettings().GetStyleSettings()));
+ if (IsControlFont())
+ aFont.Merge(GetControlFont());
+ return aFont;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/edit.cxx b/vcl/source/control/edit.cxx
new file mode 100644
index 000000000..313de30a2
--- /dev/null
+++ b/vcl/source/control/edit.cxx
@@ -0,0 +1,2939 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <vcl/builder.hxx>
+#include <vcl/event.hxx>
+#include <vcl/cursor.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/edit.hxx>
+#include <vcl/weld.hxx>
+#include <vcl/specialchars.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/uitest/uiobject.hxx>
+#include <vcl/ptrstyle.hxx>
+
+#include <window.h>
+#include <svdata.hxx>
+#include <strings.hrc>
+#include <controldata.hxx>
+
+#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 <officecfg/Office/Common.hxx>
+
+#include <algorithm>
+#include <memory>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::lang;
+
+// - Redo
+// - if Tracking-Cancel recreate DefaultSelection
+
+static FncGetSpecialChars pImplFncGetSpecialChars = nullptr;
+
+#define EDIT_ALIGN_LEFT 1
+#define EDIT_ALIGN_CENTER 2
+#define EDIT_ALIGN_RIGHT 3
+
+#define EDIT_DEL_LEFT 1
+#define EDIT_DEL_RIGHT 2
+
+#define EDIT_DELMODE_SIMPLE 11
+#define EDIT_DELMODE_RESTOFWORD 12
+#define EDIT_DELMODE_RESTOFCONTENT 13
+
+struct DDInfo
+{
+ vcl::Cursor aCursor;
+ Selection aDndStartSel;
+ sal_Int32 nDropPos;
+ bool bStarterOfDD;
+ bool bDroppedInMe;
+ bool bVisCursor;
+ bool bIsStringSupported;
+
+ DDInfo()
+ {
+ aCursor.SetStyle( CURSOR_SHADOW );
+ nDropPos = 0;
+ bStarterOfDD = false;
+ bDroppedInMe = false;
+ bVisCursor = false;
+ bIsStringSupported = false;
+ }
+};
+
+struct Impl_IMEInfos
+{
+ OUString aOldTextAfterStartPos;
+ std::unique_ptr<ExtTextInputAttr[]>
+ pAttribs;
+ sal_Int32 nPos;
+ sal_Int32 nLen;
+ bool bCursor;
+ bool bWasCursorOverwrite;
+
+ Impl_IMEInfos(sal_Int32 nPos, const OUString& rOldTextAfterStartPos);
+
+ void CopyAttribs(const ExtTextInputAttr* pA, sal_Int32 nL);
+ void DestroyAttribs();
+};
+
+Impl_IMEInfos::Impl_IMEInfos(sal_Int32 nP, const OUString& rOldTextAfterStartPos)
+ : aOldTextAfterStartPos(rOldTextAfterStartPos),
+ nPos(nP),
+ nLen(0),
+ bCursor(true),
+ bWasCursorOverwrite(false)
+{
+}
+
+void Impl_IMEInfos::CopyAttribs(const ExtTextInputAttr* pA, sal_Int32 nL)
+{
+ nLen = nL;
+ pAttribs.reset(new ExtTextInputAttr[ nL ]);
+ memcpy( pAttribs.get(), pA, nL*sizeof(ExtTextInputAttr) );
+}
+
+void Impl_IMEInfos::DestroyAttribs()
+{
+ pAttribs.reset();
+ nLen = 0;
+}
+
+Edit::Edit( WindowType nType )
+ : Control( nType )
+{
+ ImplInitEditData();
+}
+
+Edit::Edit( vcl::Window* pParent, WinBits nStyle )
+ : Control( WindowType::EDIT )
+{
+ ImplInitEditData();
+ ImplInit( pParent, nStyle );
+}
+
+void Edit::SetWidthInChars(sal_Int32 nWidthInChars)
+{
+ if (mnWidthInChars != nWidthInChars)
+ {
+ mnWidthInChars = nWidthInChars;
+ queue_resize();
+ }
+}
+
+void Edit::setMaxWidthChars(sal_Int32 nWidth)
+{
+ if (nWidth != mnMaxWidthChars)
+ {
+ mnMaxWidthChars = nWidth;
+ queue_resize();
+ }
+}
+
+bool Edit::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "width-chars")
+ SetWidthInChars(rValue.toInt32());
+ else if (rKey == "max-width-chars")
+ setMaxWidthChars(rValue.toInt32());
+ else if (rKey == "max-length")
+ {
+ sal_Int32 nTextLen = rValue.toInt32();
+ SetMaxTextLen(nTextLen == 0 ? EDIT_NOLIMIT : nTextLen);
+ }
+ else if (rKey == "editable")
+ {
+ SetReadOnly(!toBool(rValue));
+ }
+ else if (rKey == "overwrite-mode")
+ {
+ SetInsertMode(!toBool(rValue));
+ }
+ else if (rKey == "visibility")
+ {
+ mbPassword = false;
+ if (!toBool(rValue))
+ mbPassword = true;
+ }
+ else if (rKey == "placeholder-text")
+ SetPlaceholderText(rValue);
+ else
+ 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;
+ mbModified = false;
+ 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 );
+
+ vcl::unohelper::DragAndDropWrapper* pDnDWrapper = new vcl::unohelper::DragAndDropWrapper( this );
+ mxDnDListener = pDnDWrapper;
+}
+
+bool Edit::ImplUseNativeBorder(vcl::RenderContext const & rRenderContext, WinBits nStyle)
+{
+ 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(*this);
+
+ 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::ImplModified()
+{
+ mbModified = true;
+ Modify();
+}
+
+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());
+ }
+}
+
+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
+ long nExtraOffset = 0;
+ if( ( GetStyle() & WB_BORDER ) || ( mbIsSubEdit && ( GetParent()->GetStyle() & WB_BORDER ) ) )
+ nExtraOffset = 2;
+
+ return nExtraOffset;
+}
+
+long Edit::ImplGetExtraYOffset() const
+{
+ 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;
+ 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();
+}
+
+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();
+
+ long nDXBuffer[256];
+ std::unique_ptr<long[]> pDXBuffer;
+ long* pDX = nDXBuffer;
+
+ if (nLen)
+ {
+ if (o3tl::make_unsigned(2 * nLen) > SAL_N_ELEMENTS(nDXBuffer))
+ {
+ pDXBuffer.reset(new long[2 * (nLen + 1)]);
+ pDX = pDXBuffer.get();
+ }
+
+ GetCaretPositions(aText, pDX, 0, nLen);
+ }
+
+ 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(PushFlags::FILLCOLOR | PushFlags::LINECOLOR);
+ rRenderContext.SetLineColor();
+ rRenderContext.SetFillColor(GetControlBackground());
+ rRenderContext.DrawRect(tools::Rectangle(aPos, Size(GetOutputSizePixel().Width() - 2 * mnXOffset, GetOutputSizePixel().Height())));
+ rRenderContext.Pop();
+
+ rRenderContext.SetTextFillColor(GetControlBackground());
+ }
+ else if (IsPaintTransparent() || ImplUseNativeBorder(rRenderContext, GetStyle()))
+ rRenderContext.SetTextFillColor();
+ else
+ rRenderContext.SetTextFillColor(IsControlBackground() ? GetControlBackground() : rStyleSettings.GetFieldColor());
+
+ ImplPaintBorder(rRenderContext);
+
+ bool bDrawSelection = maSelection.Len() && (HasFocus() || (GetStyle() & WB_NOHIDESELECTION) || mbActivePopup);
+
+ aPos.setX( mnXOffset + ImplGetExtraXOffset() );
+ if (bPaintPlaceholderText)
+ {
+ rRenderContext.DrawText(aPos, maPlaceholderText);
+ }
+ else if (!bDrawSelection && !mpIMEInfos)
+ {
+ rRenderContext.DrawText(aPos, aText, 0, nLen);
+ }
+ else
+ {
+ // save graphics state
+ rRenderContext.Push();
+ // first calculate highlighted and non highlighted clip regions
+ vcl::Region aHighlightClipRegion;
+ vcl::Region aNormalClipRegion;
+ Selection aTmpSel(maSelection);
+ aTmpSel.Justify();
+ // selection is highlighted
+ for(sal_Int32 i = 0; i < nLen; ++i)
+ {
+ tools::Rectangle aRect(aPos, Size(10, nTH));
+ aRect.SetLeft( pDX[2 * i] + mnXOffset + ImplGetExtraXOffset() );
+ aRect.SetRight( pDX[2 * i + 1] + mnXOffset + ImplGetExtraXOffset() );
+ aRect.Justify();
+ bool bHighlight = false;
+ if (i >= aTmpSel.Min() && i < aTmpSel.Max())
+ bHighlight = true;
+
+ if (mpIMEInfos && mpIMEInfos->pAttribs &&
+ i >= mpIMEInfos->nPos && i < (mpIMEInfos->nPos+mpIMEInfos->nLen) &&
+ (mpIMEInfos->pAttribs[i - mpIMEInfos->nPos] & ExtTextInputAttr::Highlight))
+ {
+ bHighlight = true;
+ }
+
+ if (bHighlight)
+ aHighlightClipRegion.Union(aRect);
+ else
+ aNormalClipRegion.Union(aRect);
+ }
+ // draw normal text
+ Color aNormalTextColor = rRenderContext.GetTextColor();
+ rRenderContext.SetClipRegion(aNormalClipRegion);
+
+ if (IsPaintTransparent())
+ rRenderContext.SetTextFillColor();
+ else
+ {
+ // Set background color when part of the text is selected
+ if (ImplUseNativeBorder(rRenderContext, GetStyle()))
+ {
+ if( mbForceControlBackground && IsControlBackground() )
+ rRenderContext.SetTextFillColor(GetControlBackground());
+ else
+ rRenderContext.SetTextFillColor();
+ }
+ else
+ {
+ rRenderContext.SetTextFillColor(IsControlBackground() ? GetControlBackground() : rStyleSettings.GetFieldColor());
+ }
+ }
+ rRenderContext.DrawText(aPos, aText, 0, nLen);
+
+ // draw highlighted text
+ rRenderContext.SetClipRegion(aHighlightClipRegion);
+ rRenderContext.SetTextColor(rStyleSettings.GetHighlightTextColor());
+ rRenderContext.SetTextFillColor(rStyleSettings.GetHighlightColor());
+ rRenderContext.DrawText(aPos, aText, 0, nLen);
+
+ // if IME info exists loop over portions and output different font attributes
+ if (mpIMEInfos && mpIMEInfos->pAttribs)
+ {
+ for(int n = 0; n < 2; n++)
+ {
+ vcl::Region aRegion;
+ if (n == 0)
+ {
+ rRenderContext.SetTextColor(aNormalTextColor);
+ if (IsPaintTransparent())
+ rRenderContext.SetTextFillColor();
+ else
+ rRenderContext.SetTextFillColor(IsControlBackground() ? GetControlBackground() : rStyleSettings.GetFieldColor());
+ aRegion = aNormalClipRegion;
+ }
+ else
+ {
+ rRenderContext.SetTextColor(rStyleSettings.GetHighlightTextColor());
+ rRenderContext.SetTextFillColor(rStyleSettings.GetHighlightColor());
+ aRegion = aHighlightClipRegion;
+ }
+
+ for(int i = 0; i < mpIMEInfos->nLen; )
+ {
+ ExtTextInputAttr nAttr = mpIMEInfos->pAttribs[i];
+ vcl::Region aClip;
+ int nIndex = i;
+ while (nIndex < mpIMEInfos->nLen && mpIMEInfos->pAttribs[nIndex] == nAttr) // #112631# check nIndex before using it
+ {
+ tools::Rectangle aRect( aPos, Size( 10, nTH ) );
+ aRect.SetLeft( pDX[2 * (nIndex + mpIMEInfos->nPos)] + mnXOffset + ImplGetExtraXOffset() );
+ aRect.SetRight( pDX[2 * (nIndex + mpIMEInfos->nPos) + 1] + mnXOffset + ImplGetExtraXOffset() );
+ aRect.Justify();
+ aClip.Union(aRect);
+ nIndex++;
+ }
+ i = nIndex;
+ aClip.Intersect(aRegion);
+ if (!aClip.IsEmpty() && nAttr != ExtTextInputAttr::NONE)
+ {
+ vcl::Font aFont = rRenderContext.GetFont();
+ if (nAttr & ExtTextInputAttr::Underline)
+ aFont.SetUnderline(LINESTYLE_SINGLE);
+ else if (nAttr & ExtTextInputAttr::BoldUnderline)
+ aFont.SetUnderline( LINESTYLE_BOLD);
+ else if (nAttr & ExtTextInputAttr::DottedUnderline)
+ aFont.SetUnderline( LINESTYLE_DOTTED);
+ else if (nAttr & ExtTextInputAttr::DashDotUnderline)
+ aFont.SetUnderline( LINESTYLE_DASHDOT);
+ else if (nAttr & ExtTextInputAttr::GrayWaveline)
+ {
+ aFont.SetUnderline(LINESTYLE_WAVE);
+ rRenderContext.SetTextLineColor(COL_LIGHTGRAY);
+ }
+ rRenderContext.SetFont(aFont);
+
+ if (nAttr & ExtTextInputAttr::RedText)
+ rRenderContext.SetTextColor(COL_RED);
+ else if (nAttr & ExtTextInputAttr::HalfToneText)
+ rRenderContext.SetTextColor(COL_LIGHTGRAY);
+
+ rRenderContext.SetClipRegion(aClip);
+ rRenderContext.DrawText(aPos, aText, 0, nLen);
+ }
+ }
+ }
+ }
+
+ // restore graphics state
+ rRenderContext.Pop();
+ }
+
+ if (bVisCursor && (!mpIMEInfos || mpIMEInfos->bCursor))
+ pCursor->Show();
+}
+
+void Edit::ImplDelete( const Selection& rSelection, sal_uInt8 nDirection, sal_uInt8 nMode )
+{
+ const sal_Int32 nTextLen = ImplGetText().getLength();
+
+ // deleting possible?
+ if ( !rSelection.Len() &&
+ (((rSelection.Min() == 0) && (nDirection == EDIT_DEL_LEFT)) ||
+ ((rSelection.Max() == nTextLen) && (nDirection == EDIT_DEL_RIGHT))) )
+ return;
+
+ ImplClearLayoutData();
+
+ Selection aSelection( rSelection );
+ aSelection.Justify();
+
+ if ( !aSelection.Len() )
+ {
+ uno::Reference < i18n::XBreakIterator > xBI = ImplGetBreakIterator();
+ if ( nDirection == EDIT_DEL_LEFT )
+ {
+ if ( nMode == EDIT_DELMODE_RESTOFWORD )
+ {
+ i18n::Boundary aBoundary = xBI->getWordBoundary( maText.toString(), aSelection.Min(),
+ GetSettings().GetLanguageTag().getLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES, true );
+ auto startPos = aBoundary.startPos;
+ if ( startPos == aSelection.Min() )
+ {
+ aBoundary = xBI->previousWord( maText.toString(), aSelection.Min(),
+ GetSettings().GetLanguageTag().getLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES );
+ startPos = std::max(aBoundary.startPos, sal_Int32(0));
+ }
+ aSelection.Min() = startPos;
+ }
+ else if ( nMode == EDIT_DELMODE_RESTOFCONTENT )
+ {
+ aSelection.Min() = 0;
+ }
+ else
+ {
+ sal_Int32 nCount = 1;
+ aSelection.Min() = xBI->previousCharacters( maText.toString(), aSelection.Min(),
+ GetSettings().GetLanguageTag().getLocale(), i18n::CharacterIteratorMode::SKIPCHARACTER, nCount, nCount );
+ }
+ }
+ else
+ {
+ if ( nMode == EDIT_DELMODE_RESTOFWORD )
+ {
+ i18n::Boundary aBoundary = xBI->nextWord( maText.toString(), aSelection.Max(),
+ GetSettings().GetLanguageTag().getLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES );
+ aSelection.Max() = aBoundary.startPos;
+ }
+ else if ( nMode == EDIT_DELMODE_RESTOFCONTENT )
+ {
+ aSelection.Max() = nTextLen;
+ }
+ else
+ {
+ sal_Int32 nCount = 1;
+ aSelection.Max() = xBI->nextCharacters( maText.toString(), aSelection.Max(),
+ GetSettings().GetLanguageTag().getLocale(), i18n::CharacterIteratorMode::SKIPCHARACTER, nCount, nCount );
+ }
+ }
+ }
+
+ const auto nSelectionMin = aSelection.Min();
+ maText.remove( nSelectionMin, aSelection.Len() );
+ maSelection.Min() = nSelectionMin;
+ maSelection.Max() = nSelectionMin;
+ ImplAlignAndPaint();
+ mbInternModified = true;
+}
+
+OUString Edit::ImplGetValidString( const OUString& rString )
+{
+ OUString aValidString = rString.replaceAll("\n", "").replaceAll("\r", "");
+ aValidString = aValidString.replace('\t', ' ');
+ return aValidString;
+}
+
+uno::Reference <i18n::XBreakIterator> const& Edit::ImplGetBreakIterator()
+{
+ if (!mxBreakIterator)
+ mxBreakIterator = i18n::BreakIterator::create(::comphelper::getProcessComponentContext());
+ return mxBreakIterator;
+}
+
+uno::Reference <i18n::XExtendedInputSequenceChecker> const& Edit::ImplGetInputSequenceChecker()
+{
+ if (!mxISC.is())
+ mxISC = i18n::InputSequenceChecker::create(::comphelper::getProcessComponentContext());
+ return mxISC;
+}
+
+void Edit::ShowTruncationWarning(weld::Widget* pParent)
+{
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pParent, VclMessageType::Warning,
+ VclButtonsType::Ok, VclResId(SV_EDIT_WARNING_STR)));
+ xBox->run();
+}
+
+bool Edit::ImplTruncateToMaxLen( OUString& rStr, sal_Int32 nSelectionLen ) const
+{
+ bool bWasTruncated = false;
+ if (maText.getLength() - nSelectionLen > mnMaxTextLen - rStr.getLength())
+ {
+ sal_Int32 nErasePos = mnMaxTextLen - maText.getLength() + nSelectionLen;
+ rStr = rStr.copy( 0, nErasePos );
+ bWasTruncated = true;
+ }
+ return bWasTruncated;
+}
+
+void Edit::ImplInsertText( const OUString& rStr, const Selection* pNewSel, bool bIsUserInput )
+{
+ Selection aSelection( maSelection );
+ aSelection.Justify();
+
+ OUString aNewText( ImplGetValidString( rStr ) );
+
+ // as below, if there's no selection, but we're in overwrite mode and not beyond
+ // the end of the existing text then that's like a selection of 1
+ auto nSelectionLen = aSelection.Len();
+ if (!nSelectionLen && !mbInsertMode && aSelection.Max() < maText.getLength())
+ nSelectionLen = 1;
+ ImplTruncateToMaxLen( aNewText, nSelectionLen );
+
+ ImplClearLayoutData();
+
+ if ( aSelection.Len() )
+ maText.remove( aSelection.Min(), aSelection.Len() );
+ else if (!mbInsertMode && aSelection.Max() < maText.getLength())
+ maText.remove( aSelection.Max(), 1 );
+
+ // take care of input-sequence-checking now
+ if (bIsUserInput && !rStr.isEmpty())
+ {
+ SAL_WARN_IF( rStr.getLength() != 1, "vcl", "unexpected string length. User input is expected to provide 1 char only!" );
+
+ // determine if input-sequence-checking should be applied or not
+
+ uno::Reference < i18n::XBreakIterator > xBI = ImplGetBreakIterator();
+ bool bIsInputSequenceChecking = rStr.getLength() == 1 &&
+ officecfg::Office::Common::I18N::CTL::CTLFont::get() &&
+ officecfg::Office::Common::I18N::CTL::CTLSequenceChecking::get() &&
+ aSelection.Min() > 0 && /* first char needs not to be checked */
+ xBI.is() && i18n::ScriptType::COMPLEX == xBI->getScriptType( rStr, 0 );
+
+ if (bIsInputSequenceChecking)
+ {
+ uno::Reference < i18n::XExtendedInputSequenceChecker > xISC = ImplGetInputSequenceChecker();
+ if (xISC.is())
+ {
+ sal_Unicode cChar = rStr[0];
+ sal_Int32 nTmpPos = aSelection.Min();
+ sal_Int16 nCheckMode = officecfg::Office::Common::I18N::CTL::CTLSequenceCheckingRestricted::get()?
+ i18n::InputSequenceCheckMode::STRICT : i18n::InputSequenceCheckMode::BASIC;
+
+ // the text that needs to be checked is only the one
+ // before the current cursor position
+ const OUString aOldText( maText.getStr(), nTmpPos);
+ OUString aTmpText( aOldText );
+ if (officecfg::Office::Common::I18N::CTL::CTLSequenceCheckingTypeAndReplace::get())
+ {
+ xISC->correctInputSequence( aTmpText, nTmpPos - 1, cChar, nCheckMode );
+
+ // find position of first character that has changed
+ sal_Int32 nOldLen = aOldText.getLength();
+ sal_Int32 nTmpLen = aTmpText.getLength();
+ const sal_Unicode *pOldTxt = aOldText.getStr();
+ const sal_Unicode *pTmpTxt = aTmpText.getStr();
+ sal_Int32 nChgPos = 0;
+ while ( nChgPos < nOldLen && nChgPos < nTmpLen &&
+ pOldTxt[nChgPos] == pTmpTxt[nChgPos] )
+ ++nChgPos;
+
+ const OUString aChgText( aTmpText.copy( nChgPos ) );
+
+ // remove text from first pos to be changed to current pos
+ maText.remove( nChgPos, nTmpPos - nChgPos );
+
+ if (!aChgText.isEmpty())
+ {
+ aNewText = aChgText;
+ aSelection.Min() = nChgPos; // position for new text to be inserted
+ }
+ else
+ aNewText.clear();
+ }
+ else
+ {
+ // should the character be ignored (i.e. not get inserted) ?
+ if (!xISC->checkInputSequence( aOldText, nTmpPos - 1, cChar, nCheckMode ))
+ aNewText.clear();
+ }
+ }
+ }
+
+ // at this point now we will insert the non-empty text 'normally' some lines below...
+ }
+
+ if ( !aNewText.isEmpty() )
+ maText.insert( aSelection.Min(), aNewText );
+
+ if ( !pNewSel )
+ {
+ maSelection.Min() = aSelection.Min() + aNewText.getLength();
+ maSelection.Max() = maSelection.Min();
+ }
+ else
+ {
+ maSelection = *pNewSel;
+ if ( maSelection.Min() > maText.getLength() )
+ maSelection.Min() = maText.getLength();
+ if ( maSelection.Max() > maText.getLength() )
+ maSelection.Max() = maText.getLength();
+ }
+
+ ImplAlignAndPaint();
+ mbInternModified = true;
+}
+
+void Edit::ImplSetText( const OUString& rText, const Selection* pNewSelection )
+{
+ // we delete text by "selecting" the old text completely then calling InsertText; this is flicker free
+ if ( ( rText.getLength() <= mnMaxTextLen ) &&
+ ( (rText != maText.getStr()) || (pNewSelection && (*pNewSelection != maSelection)) ) )
+ {
+ 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::LONGCURRENCYFIELD:
+ case WindowType::NUMERICFIELD:
+ 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, long nXStart, 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())
+ {
+ // 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)
+ {
+ // 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->GetClipRegion());
+ pBorder->SetClipRegion(aClipRgn);
+
+ pBorder->Paint(*pBorder, tools::Rectangle());
+
+ pBorder->SetClipRegion(oldRgn);
+ }
+ else
+ {
+ pBorder->Paint(*pBorder, tools::Rectangle());
+ }
+ }
+ }
+}
+
+void Edit::ImplShowCursor( bool bOnlyIfVisible )
+{
+ if ( !IsUpdateMode() || ( bOnlyIfVisible && !IsReallyVisible() ) )
+ return;
+
+ vcl::Cursor* pCursor = GetCursor();
+ OUString aText = ImplGetText();
+
+ long nTextPos = 0;
+
+ long nDXBuffer[256];
+ std::unique_ptr<long[]> pDXBuffer;
+ long* pDX = nDXBuffer;
+
+ if( !aText.isEmpty() )
+ {
+ if( o3tl::make_unsigned(2*aText.getLength()) > SAL_N_ELEMENTS(nDXBuffer) )
+ {
+ pDXBuffer.reset(new long[2*(aText.getLength()+1)]);
+ pDX = pDXBuffer.get();
+ }
+
+ GetCaretPositions( aText, pDX, 0, aText.getLength() );
+
+ if( maSelection.Max() < aText.getLength() )
+ nTextPos = pDX[ 2*maSelection.Max() ];
+ else
+ nTextPos = pDX[ 2*aText.getLength()-1 ];
+ }
+
+ long nCursorWidth = 0;
+ if ( !mbInsertMode && !maSelection.Len() && (maSelection.Max() < aText.getLength()) )
+ nCursorWidth = GetTextWidth(aText, maSelection.Max(), 1);
+ long nCursorPosX = nTextPos + mnXOffset + ImplGetExtraXOffset();
+
+ // cursor should land in visible area
+ const Size aOutSize = GetOutputSizePixel();
+ if ( (nCursorPosX < 0) || (nCursorPosX >= aOutSize.Width()) )
+ {
+ long nOldXOffset = mnXOffset;
+
+ if ( nCursorPosX < 0 )
+ {
+ mnXOffset = - nTextPos;
+ 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 )
+ {
+ 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 long nTextHeight = GetTextHeight();
+ const 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;
+ }
+
+ long nTextWidth = GetTextWidth( ImplGetText() );
+ long nOutWidth = GetOutputSizePixel().Width();
+
+ if ( mnAlign == EDIT_ALIGN_LEFT )
+ {
+ if (nTextWidth < nOutWidth)
+ mnXOffset = 0;
+ }
+ else if ( mnAlign == EDIT_ALIGN_RIGHT )
+ {
+ 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();
+
+ long nDXBuffer[256];
+ std::unique_ptr<long[]> pDXBuffer;
+ long* pDX = nDXBuffer;
+ if( o3tl::make_unsigned(2*aText.getLength()) > SAL_N_ELEMENTS(nDXBuffer) )
+ {
+ pDXBuffer.reset(new long[2*(aText.getLength()+1)]);
+ pDX = pDXBuffer.get();
+ }
+
+ GetCaretPositions( aText, pDX, 0, aText.getLength() );
+ long nX = rWindowPos.X() - mnXOffset - ImplGetExtraXOffset();
+ for (sal_Int32 i = 0; i < aText.getLength(); aText.iterateCodePoints(&i))
+ {
+ if( (pDX[2*i] >= nX && pDX[2*i+1] <= nX) ||
+ (pDX[2*i+1] >= nX && pDX[2*i] <= nX))
+ {
+ nIndex = i;
+ if( pDX[2*i] < pDX[2*i+1] )
+ {
+ if( nX > (pDX[2*i]+pDX[2*i+1])/2 )
+ aText.iterateCodePoints(&nIndex);
+ }
+ else
+ {
+ if( nX < (pDX[2*i]+pDX[2*i+1])/2 )
+ aText.iterateCodePoints(&nIndex);
+ }
+ break;
+ }
+ }
+ if( nIndex == EDIT_NOLIMIT )
+ {
+ nIndex = 0;
+ sal_Int32 nFinalIndex = 0;
+ long nDiff = std::abs( pDX[0]-nX );
+ sal_Int32 i = 0;
+ if (!aText.isEmpty())
+ {
+ aText.iterateCodePoints(&i); //skip the first character
+ }
+ while (i < aText.getLength())
+ {
+ long nNewDiff = std::abs( pDX[2*i]-nX );
+
+ if( nNewDiff < nDiff )
+ {
+ nIndex = i;
+ nDiff = nNewDiff;
+ }
+
+ nFinalIndex = i;
+
+ aText.iterateCodePoints(&i);
+ }
+ if (nIndex == nFinalIndex && std::abs( pDX[2*nIndex+1] - nX ) < nDiff)
+ nIndex = EDIT_NOLIMIT;
+ }
+
+ return nIndex;
+}
+
+void Edit::ImplSetCursorPos( sal_Int32 nChar, bool bSelect )
+{
+ Selection aSelection( maSelection );
+ aSelection.Max() = nChar;
+ if ( !bSelect )
+ aSelection.Min() = aSelection.Max();
+ ImplSetSelection( aSelection );
+}
+
+void Edit::ImplCopyToSelectionClipboard()
+{
+ if ( GetSelection().Len() )
+ {
+ css::uno::Reference<css::datatransfer::clipboard::XClipboard> aSelection(GetPrimarySelection());
+ 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() )
+ {
+ uno::Reference< datatransfer::XTransferable > xDataObj;
+
+ try
+ {
+ SolarMutexReleaser aReleaser;
+ xDataObj = rxClipboard->getContents();
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+
+ if ( xDataObj.is() )
+ {
+ datatransfer::DataFlavor aFlavor;
+ SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor );
+ try
+ {
+ uno::Any aData = xDataObj->getTransferData( aFlavor );
+ OUString aText;
+ aData >>= aText;
+
+ Selection aSelection(maSelection);
+ aSelection.Justify();
+ if (ImplTruncateToMaxLen(aText, aSelection.Len()))
+ ShowTruncationWarning(GetFrameWeld());
+
+ ReplaceSelected( aText );
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+ }
+ }
+}
+
+void Edit::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ if ( mpSubEdit )
+ {
+ Control::MouseButtonDown( rMEvt );
+ return;
+ }
+
+ sal_Int32 nCharPos = ImplGetCharPos( rMEvt.GetPosPixel() );
+ Selection aSelection( maSelection );
+ aSelection.Justify();
+
+ if ( rMEvt.GetClicks() < 4 )
+ {
+ mbClickedInSelection = false;
+ if ( rMEvt.GetClicks() == 3 )
+ {
+ ImplSetSelection( Selection( 0, EDIT_NOLIMIT) );
+ ImplCopyToSelectionClipboard();
+
+ }
+ else if ( rMEvt.GetClicks() == 2 )
+ {
+ uno::Reference < i18n::XBreakIterator > xBI = ImplGetBreakIterator();
+ i18n::Boundary aBoundary = xBI->getWordBoundary( maText.toString(), aSelection.Max(),
+ GetSettings().GetLanguageTag().getLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES, true );
+ ImplSetSelection( Selection( aBoundary.startPos, aBoundary.endPos ) );
+ ImplCopyToSelectionClipboard();
+ }
+ else if ( !rMEvt.IsShift() && HasFocus() && aSelection.IsInside( 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(Window::GetPrimarySelection());
+ ImplPaste( aSelection );
+ ImplModified();
+ }
+}
+
+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();
+ ImplModified();
+ 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 );
+ ImplModified();
+ }
+ 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 )
+ {
+ i18n::Boundary aBoundary = xBI->getWordBoundary( maText.toString(), aSel.Max(),
+ GetSettings().GetLanguageTag().getLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES, true );
+ if ( aBoundary.startPos == aSel.Max() )
+ aBoundary = xBI->previousWord( maText.toString(), 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 )
+ ImplModified();
+ bDone = true;
+ }
+ }
+ break;
+
+ case KEY_INSERT:
+ {
+ if ( !mpIMEInfos && !mbReadOnly && !rKEvt.GetKeyCode().IsMod2() )
+ {
+ SetInsertMode( !mbInsertMode );
+ bDone = true;
+ }
+ }
+ break;
+
+ case KEY_RETURN:
+ if (maActivateHdl.IsSet())
+ {
+ 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 )
+ ImplModified();
+
+ return bDone;
+}
+
+void Edit::KeyInput( const KeyEvent& rKEvt )
+{
+ if ( mpSubEdit || !ImplHandleKeyEvent( rKEvt ) )
+ Control::KeyInput( rKEvt );
+}
+
+void Edit::FillLayoutData() const
+{
+ mpControlData->mpLayoutData.reset( new vcl::ControlLayoutData );
+ 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, DrawFlags 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 & DrawFlags::Mono )
+ pDev->SetTextColor( COL_BLACK );
+ else
+ {
+ if ( !IsEnabled() )
+ {
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ pDev->SetTextColor( rStyleSettings.GetDisableColor() );
+ }
+ else
+ {
+ pDev->SetTextColor( GetTextColor() );
+ }
+ }
+
+ const long nOnePixel = GetDrawPixel( pDev, 1 );
+ const 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();
+ long nTextHeight = pDev->GetTextHeight();
+ long nTextWidth = pDev->GetTextWidth( aText );
+ long nOffY = (aSize.Height() - nTextHeight) / 2;
+
+ // Clipping?
+ if ( (nOffY < 0) ||
+ ((nOffY+nTextHeight) > aSize.Height()) ||
+ ((nOffX+nTextWidth) > aSize.Width()) )
+ {
+ tools::Rectangle aClip( aPos, aSize );
+ if ( nTextHeight > aSize.Height() )
+ aClip.AdjustBottom(nTextHeight-aSize.Height()+1 ); // prevent HP printers from 'optimizing'
+ pDev->IntersectClipRegion( aClip );
+ }
+
+ pDev->DrawText( aTextRect, aText, nTextStyle );
+ pDev->Pop();
+
+ if ( GetSubEdit() )
+ {
+ Size aOrigSize(GetSubEdit()->GetSizePixel());
+ GetSubEdit()->SetSizePixel(GetSizePixel());
+ GetSubEdit()->Draw(pDev, rPos, nFlags);
+ GetSubEdit()->SetSizePixel(aOrigSize);
+ }
+}
+
+void Edit::ImplInvalidateOutermostBorder( vcl::Window* pWin )
+{
+ // allow control to show focused state
+ vcl::Window *pInvalWin = pWin;
+ for (;;)
+ {
+ vcl::Window* pBorder = pInvalWin->GetWindow( GetWindowType::Border );
+ if (pBorder == pInvalWin || !pBorder ||
+ pInvalWin->ImplGetFrame() != pBorder->ImplGetFrame() )
+ break;
+ pInvalWin = pBorder;
+ }
+
+ pInvalWin->Invalidate( InvalidateFlags::Children | InvalidateFlags::Update );
+}
+
+void Edit::GetFocus()
+{
+ if ( mpSubEdit )
+ mpSubEdit->ImplGrabFocus( GetGetFocusFlags() );
+ else if ( !mbActivePopup )
+ {
+ maUndoText = maText.toString();
+ SelectionOptions nSelOptions = GetSettings().GetStyleSettings().GetSelectionOptions();
+ if ( !( GetStyle() & (WB_NOHIDESELECTION|WB_READONLY) )
+ && ( GetGetFocusFlags() & (GetFocusFlags::Init|GetFocusFlags::Tab|GetFocusFlags::CURSOR|GetFocusFlags::Mnemonic) ) )
+ {
+ if ( nSelOptions & SelectionOptions::ShowFirst )
+ {
+ maSelection.Min() = maText.getLength();
+ maSelection.Max() = 0;
+ }
+ else
+ {
+ maSelection.Min() = 0;
+ maSelection.Max() = maText.getLength();
+ }
+ if ( mbIsSubEdit )
+ static_cast<Edit*>(GetParent())->CallEventListeners( VclEventId::EditSelectionChanged );
+ else
+ CallEventListeners( VclEventId::EditSelectionChanged );
+ }
+
+ ImplShowCursor();
+
+ // FIXME: this is currently only on macOS
+ // check for other platforms that need similar handling
+ if( ImplGetSVData()->maNWFData.mbNoFocusRects &&
+ IsNativeWidgetEnabled() &&
+ IsNativeControlSupported( ControlType::Editbox, ControlPart::Entire ) )
+ {
+ ImplInvalidateOutermostBorder( mbIsSubEdit ? GetParent() : this );
+ }
+ else if ( maSelection.Len() )
+ {
+ // paint the selection
+ if ( !HasPaintEvent() )
+ ImplInvalidateOrRepaint();
+ else
+ Invalidate();
+ }
+
+ SetInputContext( InputContext( GetFont(), !IsReadOnly() ? InputContextFlags::Text|InputContextFlags::ExtText : InputContextFlags::NONE ) );
+ }
+
+ Control::GetFocus();
+}
+
+void Edit::LoseFocus()
+{
+ if ( !mpSubEdit )
+ {
+ // FIXME: this is currently only on macOS
+ // check for other platforms that need similar handling
+ if( ImplGetSVData()->maNWFData.mbNoFocusRects &&
+ IsNativeWidgetEnabled() &&
+ IsNativeControlSupported( ControlType::Editbox, ControlPart::Entire ) )
+ {
+ ImplInvalidateOutermostBorder( mbIsSubEdit ? GetParent() : this );
+ }
+
+ if ( !mbActivePopup && !( GetStyle() & WB_NOHIDESELECTION ) && maSelection.Len() )
+ ImplInvalidateOrRepaint(); // paint the selection
+ }
+
+ Control::LoseFocus();
+}
+
+void Edit::Command( const CommandEvent& rCEvt )
+{
+ if ( rCEvt.GetCommand() == CommandEventId::ContextMenu )
+ {
+ VclPtr<PopupMenu> pPopup = Edit::CreatePopupMenu();
+
+ bool bEnableCut = true;
+ bool bEnableCopy = true;
+ bool bEnableDelete = true;
+ bool bEnablePaste = true;
+ bool bEnableSpecialChar = true;
+
+ if ( !maSelection.Len() )
+ {
+ bEnableCut = false;
+ bEnableCopy = false;
+ bEnableDelete = false;
+ }
+
+ if ( IsReadOnly() )
+ {
+ bEnableCut = false;
+ bEnablePaste = false;
+ bEnableDelete = false;
+ bEnableSpecialChar = false;
+ }
+ else
+ {
+ // only paste if text available in clipboard
+ bool bData = false;
+ uno::Reference< datatransfer::clipboard::XClipboard > xClipboard = GetClipboard();
+
+ if ( xClipboard.is() )
+ {
+ uno::Reference< datatransfer::XTransferable > xDataObj;
+ {
+ SolarMutexReleaser aReleaser;
+ xDataObj = xClipboard->getContents();
+ }
+ if ( xDataObj.is() )
+ {
+ datatransfer::DataFlavor aFlavor;
+ SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor );
+ bData = xDataObj->isDataFlavorSupported( aFlavor );
+ }
+ }
+ bEnablePaste = bData;
+ }
+
+ pPopup->EnableItem(pPopup->GetItemId("cut"), bEnableCut);
+ pPopup->EnableItem(pPopup->GetItemId("copy"), bEnableCopy);
+ pPopup->EnableItem(pPopup->GetItemId("delete"), bEnableDelete);
+ pPopup->EnableItem(pPopup->GetItemId("paste"), bEnablePaste);
+ pPopup->EnableItem(pPopup->GetItemId("specialchar"), bEnableSpecialChar);
+ pPopup->EnableItem(pPopup->GetItemId("undo"), maUndoText != maText.getStr());
+ bool bAllSelected = maSelection.Min() == 0 && maSelection.Max() == maText.getLength();
+ pPopup->EnableItem(pPopup->GetItemId("selectall"), !bAllSelected);
+ pPopup->ShowItem(pPopup->GetItemId("specialchar"), pImplFncGetSpecialChars != nullptr);
+
+ mbActivePopup = true;
+ Selection aSaveSel = GetSelection(); // if someone changes selection in Get/LoseFocus, e.g. URL bar
+ Point aPos = rCEvt.GetMousePosPixel();
+ if ( !rCEvt.IsMouseEvent() )
+ {
+ // Show menu eventually centered in selection
+ Size aSize = GetOutputSizePixel();
+ aPos = Point( aSize.Width()/2, aSize.Height()/2 );
+ }
+ sal_uInt16 n = pPopup->Execute( this, aPos );
+ SetSelection( aSaveSel );
+ OString sCommand = pPopup->GetItemIdent(n);
+ if (sCommand == "undo")
+ {
+ Undo();
+ ImplModified();
+ }
+ else if (sCommand == "cut")
+ {
+ Cut();
+ ImplModified();
+ }
+ else if (sCommand == "copy")
+ {
+ Copy();
+ }
+ else if (sCommand == "paste")
+ {
+ Paste();
+ ImplModified();
+ }
+ else if (sCommand == "delete")
+ {
+ DeleteSelected();
+ ImplModified();
+ }
+ 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 );
+ ImplModified();
+ }
+ }
+ }
+ pPopup.clear();
+ mbActivePopup = false;
+ }
+ else if ( rCEvt.GetCommand() == CommandEventId::StartExtTextInput )
+ {
+ DeleteSelected();
+ sal_Int32 nPos = maSelection.Max();
+ mpIMEInfos.reset(new Impl_IMEInfos( nPos, OUString(maText.getStr() + nPos ) ));
+ mpIMEInfos->bWasCursorOverwrite = !IsInsertMode();
+ }
+ else if ( rCEvt.GetCommand() == CommandEventId::EndExtTextInput )
+ {
+ bool bInsertMode = !mpIMEInfos->bWasCursorOverwrite;
+ mpIMEInfos.reset();
+
+ SetInsertMode(bInsertMode);
+ ImplModified();
+
+ 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.copy( 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();
+ long nDXBuffer[256];
+ std::unique_ptr<long[]> pDXBuffer;
+ long* pDX = nDXBuffer;
+
+ if( !aText.isEmpty() )
+ {
+ if( o3tl::make_unsigned(2*aText.getLength()) > SAL_N_ELEMENTS(nDXBuffer) )
+ {
+ pDXBuffer.reset(new long[2*(aText.getLength()+1)]);
+ pDX = pDXBuffer.get();
+ }
+
+ GetCaretPositions( aText, pDX, 0, aText.getLength() );
+ }
+ long nTH = GetTextHeight();
+ Point aPos( mnXOffset, ImplGetTextYPosition() );
+
+ std::unique_ptr<tools::Rectangle[]> aRects(new tools::Rectangle[ mpIMEInfos->nLen ]);
+ for ( int nIndex = 0; nIndex < mpIMEInfos->nLen; ++nIndex )
+ {
+ tools::Rectangle aRect( aPos, Size( 10, nTH ) );
+ aRect.SetLeft( pDX[2*(nIndex+mpIMEInfos->nPos)] + mnXOffset + ImplGetExtraXOffset() );
+ aRects[ nIndex ] = aRect;
+ }
+ SetCompositionCharRect( aRects.get(), 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)
+ SetLayoutMode(ComplexTextLayoutFlags::BiDiRtl | ComplexTextLayoutFlags::TextOriginLeft);
+ }
+ else if (mbIsSubEdit && !GetParent()->IsRTLEnabled())
+ {
+ if (nType == StateChangedType::Mirroring)
+ SetLayoutMode(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(*this);
+ ImplShowCursor();
+ Invalidate();
+ }
+ }
+ else if ((nType == StateChangedType::ControlForeground) || (nType == StateChangedType::ControlBackground))
+ {
+ if (!mpSubEdit)
+ {
+ ApplySettings(*this);
+ 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(*this);
+ ImplShowCursor();
+ Invalidate();
+ }
+ }
+
+ Control::DataChanged( rDCEvt );
+}
+
+void Edit::ImplShowDDCursor()
+{
+ if (!mpDDInfo->bVisCursor)
+ {
+ long nTextWidth = GetTextWidth( maText.toString(), 0, mpDDInfo->nDropPos );
+ long nTextHeight = GetTextHeight();
+ tools::Rectangle aCursorRect( Point( nTextWidth + mnXOffset, (GetOutputSize().Height()-nTextHeight)/2 ), Size( 2, nTextHeight ) );
+ mpDDInfo->aCursor.SetWindow( this );
+ mpDDInfo->aCursor.SetPos( aCursorRect.TopLeft() );
+ mpDDInfo->aCursor.SetSize( aCursorRect.GetSize() );
+ mpDDInfo->aCursor.Show();
+ mpDDInfo->bVisCursor = true;
+ }
+}
+
+void Edit::ImplHideDDCursor()
+{
+ if ( mpDDInfo && mpDDInfo->bVisCursor )
+ {
+ mpDDInfo->aCursor.Hide();
+ mpDDInfo->bVisCursor = false;
+ }
+}
+
+TextFilter::TextFilter(const OUString &rForbiddenChars)
+ : sForbiddenChars(rForbiddenChars)
+{
+}
+
+TextFilter::~TextFilter()
+{
+}
+
+OUString TextFilter::filter(const OUString &rText)
+{
+ OUString sTemp(rText);
+ for (sal_Int32 i = 0; i < sForbiddenChars.getLength(); ++i)
+ {
+ sTemp = sTemp.replaceAll(OUStringChar(sForbiddenChars[i]), "");
+ }
+ return sTemp;
+}
+
+void Edit::filterText()
+{
+ Selection aSel = GetSelection();
+ const OUString sOrig = GetText();
+ const OUString sNew = mpFilterText->filter(GetText());
+ if (sOrig != sNew)
+ {
+ sal_Int32 nDiff = sOrig.getLength() - sNew.getLength();
+ if (nDiff)
+ {
+ aSel.setMin(aSel.getMin() - nDiff);
+ aSel.setMax(aSel.getMin());
+ }
+ SetText(sNew);
+ SetSelection(aSel);
+ }
+}
+
+void Edit::Modify()
+{
+ if (mpFilterText)
+ filterText();
+
+ if ( mbIsSubEdit )
+ {
+ static_cast<Edit*>(GetParent())->Modify();
+ }
+ else
+ {
+ if ( ImplCallEventListenersAndHandler( VclEventId::EditModify, [this] () { maModifyHdl.Call(*this); } ) )
+ // have been destroyed while calling into the handlers
+ return;
+
+ // #i13677# notify edit listeners about caret position change
+ CallEventListeners( VclEventId::EditCaretChanged );
+ // FIXME: this is currently only on macOS
+ // check for other platforms that need similar handling
+ if( ImplGetSVData()->maNWFData.mbNoFocusRects &&
+ IsNativeWidgetEnabled() &&
+ IsNativeControlSupported( ControlType::Editbox, ControlPart::Entire ) )
+ {
+ ImplInvalidateOutermostBorder( this );
+ }
+ }
+}
+
+void Edit::SetEchoChar( sal_Unicode c )
+{
+ mcEchoChar = c;
+ if ( mpSubEdit )
+ mpSubEdit->SetEchoChar( c );
+}
+
+void Edit::SetReadOnly( bool bReadOnly )
+{
+ if ( mbReadOnly != bReadOnly )
+ {
+ mbReadOnly = bReadOnly;
+ if ( mpSubEdit )
+ mpSubEdit->SetReadOnly( bReadOnly );
+
+ CompatStateChanged( StateChangedType::ReadOnly );
+ }
+}
+
+void Edit::SetInsertMode( bool bInsert )
+{
+ if ( bInsert != mbInsertMode )
+ {
+ mbInsertMode = bInsert;
+ if ( mpSubEdit )
+ mpSubEdit->SetInsertMode( bInsert );
+ else
+ ImplShowCursor();
+ }
+}
+
+bool Edit::IsInsertMode() const
+{
+ if ( mpSubEdit )
+ return mpSubEdit->IsInsertMode();
+ else
+ return mbInsertMode;
+}
+
+void Edit::SetMaxTextLen(sal_Int32 nMaxLen)
+{
+ mnMaxTextLen = nMaxLen > 0 ? nMaxLen : EDIT_NOLIMIT;
+
+ if ( mpSubEdit )
+ mpSubEdit->SetMaxTextLen( mnMaxTextLen );
+ else
+ {
+ if ( maText.getLength() > mnMaxTextLen )
+ ImplDelete( Selection( mnMaxTextLen, maText.getLength() ), EDIT_DEL_RIGHT, EDIT_DELMODE_SIMPLE );
+ }
+}
+
+void Edit::SetSelection( const Selection& rSelection )
+{
+ // If the selection was changed from outside, e.g. by MouseButtonDown, don't call Tracking()
+ // directly afterwards which would change the selection again
+ if ( IsTracking() )
+ EndTracking();
+ else if ( mpSubEdit && mpSubEdit->IsTracking() )
+ mpSubEdit->EndTracking();
+
+ ImplSetSelection( rSelection );
+}
+
+void Edit::ImplSetSelection( const Selection& rSelection, bool bPaint )
+{
+ if ( mpSubEdit )
+ mpSubEdit->ImplSetSelection( rSelection );
+ else
+ {
+ if ( rSelection != maSelection )
+ {
+ Selection aOld( maSelection );
+ Selection aNew( rSelection );
+
+ if ( aNew.Min() > maText.getLength() )
+ aNew.Min() = maText.getLength();
+ if ( aNew.Max() > maText.getLength() )
+ aNew.Max() = maText.getLength();
+ if ( aNew.Min() < 0 )
+ aNew.Min() = 0;
+ if ( aNew.Max() < 0 )
+ aNew.Max() = 0;
+
+ if ( aNew != maSelection )
+ {
+ ImplClearLayoutData();
+ Selection aTemp = maSelection;
+ maSelection = aNew;
+
+ if ( bPaint && ( aOld.Len() || aNew.Len() || IsPaintTransparent() ) )
+ ImplInvalidateOrRepaint();
+ ImplShowCursor();
+
+ bool bCaret = false, bSelection = false;
+ long nB=aNew.Max(), nA=aNew.Min(),oB=aTemp.Max(), oA=aTemp.Min();
+ long nGap = nB-nA, oGap = oB-oA;
+ if (nB != oB)
+ bCaret = true;
+ if (nGap != 0 || oGap != 0)
+ bSelection = true;
+
+ if (bSelection)
+ {
+ if ( mbIsSubEdit )
+ static_cast<Edit*>(GetParent())->CallEventListeners( VclEventId::EditSelectionChanged );
+ else
+ CallEventListeners( VclEventId::EditSelectionChanged );
+ }
+
+ if (bCaret)
+ {
+ if ( mbIsSubEdit )
+ static_cast<Edit*>(GetParent())->CallEventListeners( VclEventId::EditCaretChanged );
+ else
+ CallEventListeners( VclEventId::EditCaretChanged );
+ }
+
+ // #103511# notify combobox listeners of deselection
+ if( !maSelection && GetParent() && GetParent()->GetType() == WindowType::COMBOBOX )
+ static_cast<Edit*>(GetParent())->CallEventListeners( VclEventId::ComboboxDeselect );
+ }
+ }
+ }
+}
+
+const Selection& Edit::GetSelection() const
+{
+ if ( mpSubEdit )
+ return mpSubEdit->GetSelection();
+ else
+ return maSelection;
+}
+
+void Edit::ReplaceSelected( const OUString& rStr )
+{
+ if ( mpSubEdit )
+ mpSubEdit->ReplaceSelected( rStr );
+ else
+ ImplInsertText( rStr );
+}
+
+void Edit::DeleteSelected()
+{
+ if ( mpSubEdit )
+ mpSubEdit->DeleteSelected();
+ else
+ {
+ if ( maSelection.Len() )
+ ImplDelete( maSelection, EDIT_DEL_RIGHT, EDIT_DELMODE_SIMPLE );
+ }
+}
+
+OUString Edit::GetSelected() const
+{
+ if ( mpSubEdit )
+ return mpSubEdit->GetSelected();
+ else
+ {
+ Selection aSelection( maSelection );
+ aSelection.Justify();
+ return OUString( maText.getStr() + aSelection.Min(), aSelection.Len() );
+ }
+}
+
+void Edit::Cut()
+{
+ if ( !mbPassword )
+ {
+ Copy();
+ ReplaceSelected( OUString() );
+ }
+}
+
+void Edit::Copy()
+{
+ if ( !mbPassword )
+ {
+ css::uno::Reference<css::datatransfer::clipboard::XClipboard> aClipboard(GetClipboard());
+ ImplCopy( aClipboard );
+ }
+}
+
+void Edit::Paste()
+{
+ css::uno::Reference<css::datatransfer::clipboard::XClipboard> aClipboard(GetClipboard());
+ ImplPaste( aClipboard );
+}
+
+void Edit::Undo()
+{
+ if ( mpSubEdit )
+ mpSubEdit->Undo();
+ else
+ {
+ const OUString aText( maText.toString() );
+ ImplDelete( Selection( 0, aText.getLength() ), EDIT_DEL_RIGHT, EDIT_DELMODE_SIMPLE );
+ ImplInsertText( maUndoText );
+ ImplSetSelection( Selection( 0, maUndoText.getLength() ) );
+ maUndoText = aText;
+ }
+}
+
+void Edit::SetText( const OUString& rStr )
+{
+ if ( mpSubEdit )
+ mpSubEdit->SetText( rStr ); // not directly ImplSetText if SetText overridden
+ else
+ {
+ Selection aNewSel( 0, 0 ); // prevent scrolling
+ ImplSetText( rStr, &aNewSel );
+ }
+}
+
+void Edit::SetText( const OUString& rStr, const Selection& rSelection )
+{
+ if ( mpSubEdit )
+ mpSubEdit->SetText( rStr, rSelection );
+ else
+ ImplSetText( rStr, &rSelection );
+}
+
+OUString Edit::GetText() const
+{
+ if ( mpSubEdit )
+ return mpSubEdit->GetText();
+ else
+ return maText.toString();
+}
+
+void Edit::SetCursorAtLast(){
+ ImplSetCursorPos( GetText().getLength(), false );
+}
+
+void Edit::SetPlaceholderText( const OUString& rStr )
+{
+ if ( mpSubEdit )
+ mpSubEdit->SetPlaceholderText( rStr );
+ else if ( maPlaceholderText != rStr )
+ {
+ maPlaceholderText = rStr;
+ if ( GetText().isEmpty() )
+ Invalidate();
+ }
+}
+
+void Edit::SetModifyFlag()
+{
+ if ( mpSubEdit )
+ mpSubEdit->mbModified = true;
+ else
+ mbModified = true;
+}
+
+void Edit::ClearModifyFlag()
+{
+ if ( mpSubEdit )
+ mpSubEdit->mbModified = false;
+ else
+ mbModified = false;
+}
+
+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::GetMinimumEditSize()
+{
+ vcl::Window* pDefWin = ImplGetDefaultWindow();
+ ScopedVclPtrInstance< Edit > aEdit( pDefWin, WB_BORDER );
+ Size aSize( aEdit->CalcMinimumSize() );
+ return aSize;
+}
+
+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
+ Size aSz( GetTextWidth( "x" ), GetTextHeight() );
+ aSz.setWidth( aSz.Width() * nChars );
+ 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();
+ sal_Int32 nCharWidth = GetTextWidth( "x" );
+ return nCharWidth ? nOutWidth/nCharWidth : 0;
+}
+
+namespace vcl
+{
+ void SetGetSpecialCharsFunction( FncGetSpecialChars fn )
+ {
+ pImplFncGetSpecialChars = fn;
+ }
+
+ FncGetSpecialChars GetGetSpecialCharsFunction()
+ {
+ return pImplFncGetSpecialChars;
+ }
+}
+
+VclPtr<PopupMenu> Edit::CreatePopupMenu()
+{
+ if (!mpUIBuilder)
+ mpUIBuilder.reset(new VclBuilder(nullptr, VclBuilderContainer::getUIRootDir(), "vcl/ui/editmenu.ui", ""));
+ VclPtr<PopupMenu> pPopup = mpUIBuilder->get_menu("menu");
+ const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
+ if (rStyleSettings.GetHideDisabledMenuItems())
+ pPopup->SetMenuFlags( MenuFlags::HideDisabledEntries );
+ else
+ pPopup->SetMenuFlags ( MenuFlags::AlwaysShowDisabledEntries );
+ if (rStyleSettings.GetContextMenuShortcuts())
+ {
+ pPopup->SetAccelKey(pPopup->GetItemId("undo"), vcl::KeyCode( KeyFuncType::UNDO));
+ pPopup->SetAccelKey(pPopup->GetItemId("cut"), vcl::KeyCode( KeyFuncType::CUT));
+ pPopup->SetAccelKey(pPopup->GetItemId("copy"), vcl::KeyCode( KeyFuncType::COPY));
+ pPopup->SetAccelKey(pPopup->GetItemId("paste"), vcl::KeyCode( KeyFuncType::PASTE));
+ pPopup->SetAccelKey(pPopup->GetItemId("delete"), vcl::KeyCode( KeyFuncType::DELETE));
+ pPopup->SetAccelKey(pPopup->GetItemId("selectall"), vcl::KeyCode( KEY_A, false, true, false, false));
+ pPopup->SetAccelKey(pPopup->GetItemId("specialchar"), vcl::KeyCode( KEY_S, true, true, false, false));
+ }
+ return pPopup;
+}
+
+// css::datatransfer::dnd::XDragGestureListener
+void Edit::dragGestureRecognized( const css::datatransfer::dnd::DragGestureEvent& rDGE )
+{
+ SolarMutexGuard aVclGuard;
+
+ if ( !IsTracking() && maSelection.Len() &&
+ !mbPassword && (!mpDDInfo || !mpDDInfo->bStarterOfDD) ) // no repeated D&D
+ {
+ Selection aSel( maSelection );
+ aSel.Justify();
+
+ // only if mouse in the selection...
+ Point aMousePos( rDGE.DragOriginX, rDGE.DragOriginY );
+ sal_Int32 nCharPos = ImplGetCharPos( aMousePos );
+ if ( (nCharPos >= aSel.Min()) && (nCharPos < aSel.Max()) )
+ {
+ if ( !mpDDInfo )
+ mpDDInfo.reset(new DDInfo);
+
+ mpDDInfo->bStarterOfDD = true;
+ mpDDInfo->aDndStartSel = aSel;
+
+ if ( IsTracking() )
+ EndTracking(); // before D&D disable tracking
+
+ 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 )
+ {
+ long nLen = aSel.Len();
+ aSel.Min() += nLen;
+ aSel.Max() += nLen;
+ }
+ }
+ ImplDelete( aSel, EDIT_DEL_RIGHT, EDIT_DELMODE_SIMPLE );
+ ImplModified();
+ }
+
+ ImplHideDDCursor();
+ mpDDInfo.reset();
+}
+
+// css::datatransfer::dnd::XDropTargetListener
+void Edit::drop( const css::datatransfer::dnd::DropTargetDropEvent& rDTDE )
+{
+ SolarMutexGuard aVclGuard;
+
+ bool bChanges = false;
+ if ( !mbReadOnly && mpDDInfo )
+ {
+ ImplHideDDCursor();
+
+ Selection aSel( maSelection );
+ aSel.Justify();
+
+ if ( aSel.Len() && !mpDDInfo->bStarterOfDD )
+ ImplDelete( aSel, EDIT_DEL_RIGHT, EDIT_DELMODE_SIMPLE );
+
+ mpDDInfo->bDroppedInMe = true;
+
+ aSel.Min() = mpDDInfo->nDropPos;
+ aSel.Max() = mpDDInfo->nDropPos;
+ ImplSetSelection( aSel );
+
+ uno::Reference< datatransfer::XTransferable > xDataObj = rDTDE.Transferable;
+ if ( xDataObj.is() )
+ {
+ datatransfer::DataFlavor aFlavor;
+ SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor );
+ if ( xDataObj->isDataFlavorSupported( aFlavor ) )
+ {
+ uno::Any aData = xDataObj->getTransferData( aFlavor );
+ OUString aText;
+ aData >>= aText;
+ ImplInsertText( aText );
+ bChanges = true;
+ ImplModified();
+ }
+ }
+
+ 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 OUString aMimetype = rFlavor.MimeType.getToken( 0, ';', nIndex );
+ return aMimetype == "text/plain";
+ });
+}
+
+void Edit::dragExit( const css::datatransfer::dnd::DropTargetEvent& )
+{
+ SolarMutexGuard aVclGuard;
+
+ ImplHideDDCursor();
+}
+
+void Edit::dragOver( const css::datatransfer::dnd::DropTargetDragEvent& rDTDE )
+{
+ SolarMutexGuard aVclGuard;
+
+ Point aMousePos( rDTDE.LocationX, rDTDE.LocationY );
+
+ sal_Int32 nPrevDropPos = mpDDInfo->nDropPos;
+ mpDDInfo->nDropPos = ImplGetCharPos( aMousePos );
+
+ /*
+ Size aOutSize = GetOutputSizePixel();
+ if ( ( aMousePos.X() < 0 ) || ( aMousePos.X() > aOutSize.Width() ) )
+ {
+ // Scroll?
+ // No, I will not receive events in this case...
+ }
+ */
+
+ Selection aSel( maSelection );
+ aSel.Justify();
+
+ // Don't accept drop in selection or read-only field...
+ if ( IsReadOnly() || aSel.IsInside( 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();
+}
+
+FactoryFunction Edit::GetUITestFactory() const
+{
+ return EditUIObject::create;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/field.cxx b/vcl/source/control/field.cxx
new file mode 100644
index 000000000..d81c3b3e2
--- /dev/null
+++ b/vcl/source/control/field.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 <sal/config.h>
+
+#include <string_view>
+
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+#include <comphelper/string.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>
+
+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')) ||
+ string::equals(rLocaleDataWrapper.getNumDecimalSep(), cChar) ||
+ (bThousandSep && string::equals(rLocaleDataWrapper.getNumThousandSep(), cChar)) ||
+ string::equals(rLocaleDataWrapper.getNumDecimalSepAlt(), 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, aStrFrac, aStrNum, aStrDenom;
+ bool bNegative = false;
+ bool bFrac = false;
+ sal_Int32 nDecPos, nFracDivPos, nFracNumPos;
+ 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;
+ nFracNumPos = aStr.lastIndexOf(' ', nFracDivPos);
+
+ // If in "a b/c" format.
+ if(nFracNumPos != -1 )
+ {
+ aStr1.append(std::u16string_view(aStr).substr(0, nFracNumPos));
+ aStrNum.append(std::u16string_view(aStr).substr(nFracNumPos+1, nFracDivPos-nFracNumPos-1));
+ aStrDenom.append(std::u16string_view(aStr).substr(nFracDivPos+1));
+ }
+ // "a/b" format, or not a fraction at all
+ else
+ {
+ aStrNum.append(std::u16string_view(aStr).substr(0, nFracDivPos));
+ aStrDenom.append(std::u16string_view(aStr).substr(nFracDivPos+1));
+ }
+
+ }
+ // parse decimal strings
+ else if ( nDecPos >= 0)
+ {
+ aStr1.append(std::u16string_view(aStr).substr(0, nDecPos));
+ aStr2.append(std::u16string_view(aStr).substr(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 = aStr1.makeStringAndClear().toInt64();
+ sal_Int64 nNum = aStrNum.makeStringAndClear().toInt64();
+ sal_Int64 nDenom = aStrDenom.makeStringAndClear().toInt64();
+ if (nDenom == 0) return false; // Division by zero
+ double nFrac2Dec = nWholeNum + static_cast<double>(nNum)/nDenom; // Convert to double for floating point precision
+ aStrFrac.append(nFrac2Dec);
+ // Reconvert division result to string and parse
+ nDecPos = aStrFrac.indexOf('.');
+ if ( nDecPos >= 0)
+ {
+ aStr1.append(aStrFrac.getStr(), nDecPos);
+ aStr2.append(aStrFrac.getStr()+nDecPos+1);
+ }
+ else
+ aStr1 = aStrFrac;
+ }
+
+ // prune and round fraction
+ bool bRound = false;
+ if (aStr2.getLength() > nDecDigits)
+ {
+ if (aStr2[nDecDigits] >= '5')
+ bRound = true;
+ string::truncateToLength(aStr2, nDecDigits);
+ }
+ if (aStr2.getLength() < nDecDigits)
+ string::padToLength(aStr2, nDecDigits, '0');
+
+ aStr = aStr1.makeStringAndClear() + aStr2.makeStringAndClear();
+
+ // check range
+ nValue = aStr.toInt64();
+ if( nValue == 0 )
+ {
+ // check if string is equivalent to zero
+ sal_Int32 nIndex = bNegative ? 1 : 0;
+ while (nIndex < aStr.getLength() && aStr[nIndex] == '0')
+ ++nIndex;
+ if( nIndex < aStr.getLength() )
+ {
+ rValue = bNegative ? SAL_MIN_INT64 : SAL_MAX_INT64;
+ return true;
+ }
+ }
+ if (bRound)
+ {
+ if ( !bNegative )
+ nValue++;
+ else
+ nValue--;
+ }
+
+ rValue = nValue;
+
+ return true;
+}
+
+void ImplUpdateSeparatorString( OUString& io_rText,
+ const OUString& rOldDecSep, const OUString& rNewDecSep,
+ const OUString& rOldThSep, const OUString& rNewThSep )
+{
+ OUStringBuffer aBuf( io_rText.getLength() );
+ sal_Int32 nIndexDec = 0, nIndexTh = 0, nIndex = 0;
+
+ const sal_Unicode* pBuffer = io_rText.getStr();
+ while( nIndex != -1 )
+ {
+ nIndexDec = io_rText.indexOf( rOldDecSep, nIndex );
+ nIndexTh = io_rText.indexOf( rOldThSep, nIndex );
+ if( (nIndexTh != -1 && nIndexDec != -1 && nIndexTh < nIndexDec )
+ || (nIndexTh != -1 && nIndexDec == -1)
+ )
+ {
+ aBuf.append( pBuffer + nIndex, nIndexTh - nIndex );
+ aBuf.append( rNewThSep );
+ nIndex = nIndexTh + rOldThSep.getLength();
+ }
+ else if( nIndexDec != -1 )
+ {
+ aBuf.append( pBuffer + nIndex, nIndexDec - nIndex );
+ aBuf.append( rNewDecSep );
+ nIndex = nIndexDec + rOldDecSep.getLength();
+ }
+ else
+ {
+ aBuf.append( pBuffer + nIndex );
+ nIndex = -1;
+ }
+ }
+
+ io_rText = aBuf.makeStringAndClear();
+}
+
+void ImplUpdateSeparators( const OUString& rOldDecSep, const OUString& rNewDecSep,
+ const OUString& rOldThSep, const OUString& rNewThSep,
+ Edit* pEdit )
+{
+ bool bChangeDec = (rOldDecSep != rNewDecSep);
+ bool bChangeTh = (rOldThSep != rNewThSep );
+
+ if( bChangeDec || bChangeTh )
+ {
+ 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 )
+ {
+ const_cast<FormatterBase*>(this)->mpLocaleDataWrapper.reset( new LocaleDataWrapper( GetLanguageTag() ) );
+ }
+ return *mpLocaleDataWrapper;
+}
+
+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)
+ , mbWrapOnLimits(false)
+ , 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() )
+ {
+ // !!! We should check why we do not validate in ImplSetUserValue() if the value was
+ // changed. This should be done there as well since otherwise the call to Modify would not
+ // be allowed. Anyway, the paths from ImplNewFieldValue, ImplSetUserValue, and ImplSetText
+ // should be checked and clearly traced (with comment) in order to find out what happens.
+
+ Selection aSelection = GetField()->GetSelection();
+ aSelection.Justify();
+ OUString aText = GetField()->GetText();
+ // leave it as is if selected until end
+ if ( static_cast<sal_Int32>(aSelection.Max()) == aText.getLength() )
+ {
+ if ( !aSelection.Len() )
+ aSelection.Min() = SELECTION_MAX;
+ aSelection.Max() = SELECTION_MAX;
+ }
+
+ sal_Int64 nOldLastValue = mnLastValue;
+ ImplSetUserValue( nNewValue, &aSelection );
+ mnLastValue = nOldLastValue;
+
+ // Modify during Edit is only set during KeyInput
+ if ( GetField()->GetText() != aText )
+ {
+ GetField()->SetModifyFlag();
+ GetField()->Modify();
+ }
+ }
+}
+
+sal_Int64 NumericFormatter::ClipAgainstMinMax(sal_Int64 nValue) const
+{
+ if (nValue > mnMax)
+ nValue = mbWrapOnLimits ? ((nValue - mnMin) % (mnMax + 1)) + mnMin
+ : mnMax;
+ else if (nValue < mnMin)
+ nValue = mbWrapOnLimits ? ((nValue + mnMax + 1 - mnMin) % (mnMax + 1)) + mnMin
+ : mnMin;
+ return nValue;
+}
+
+NumericField::NumericField(vcl::Window* pParent, WinBits nWinStyle)
+ : SpinField(pParent, nWinStyle)
+ , NumericFormatter(this)
+{
+ Reformat();
+}
+
+void NumericField::dispose()
+{
+ ClearField();
+ SpinField::dispose();
+}
+
+bool NumericField::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "digits")
+ SetDecimalDigits(rValue.toInt32());
+ else if (rKey == "spin-size")
+ SetSpinSize(rValue.toInt32());
+ else if (rKey == "wrap")
+ mbWrapOnLimits = toBool(rValue);
+ else
+ return SpinField::set_property(rKey, rValue);
+ return true;
+}
+
+bool NumericField::PreNotify( NotifyEvent& rNEvt )
+{
+ if ( (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) && !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() )
+ {
+ if ( ImplNumericProcessKeyInput( *rNEvt.GetKeyEvent(), IsStrictFormat(), IsUseThousandSep(), ImplGetLocaleDataWrapper() ) )
+ return true;
+ }
+
+ return SpinField::PreNotify( rNEvt );
+}
+
+bool NumericField::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() && (!GetText().isEmpty() || !IsEmptyFieldValueEnabled()) )
+ Reformat();
+ }
+
+ return SpinField::EventNotify( rNEvt );
+}
+
+void NumericField::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ SpinField::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::LOCALE) )
+ {
+ OUString sOldDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
+ OUString sOldThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
+ ImplGetLocaleDataWrapper().setLanguageTag( GetSettings().GetLanguageTag() );
+ OUString sNewDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
+ OUString sNewThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
+ ImplUpdateSeparators( sOldDecSep, sNewDecSep, sOldThSep, sNewThSep, this );
+ ReformatAll();
+ }
+}
+
+void NumericField::Modify()
+{
+ MarkToBeReformatted( true );
+ SpinField::Modify();
+}
+
+void NumericField::Up()
+{
+ FieldUp();
+ SpinField::Up();
+}
+
+void NumericField::Down()
+{
+ FieldDown();
+ SpinField::Down();
+}
+
+void NumericField::First()
+{
+ FieldFirst();
+ SpinField::First();
+}
+
+void NumericField::Last()
+{
+ FieldLast();
+ SpinField::Last();
+}
+
+boost::property_tree::ptree NumericField::DumpAsPropertyTree()
+{
+ boost::property_tree::ptree aTree(SpinField::DumpAsPropertyTree());
+ aTree.put("min", GetMin());
+ aTree.put("max", GetMax());
+ return aTree;
+}
+
+namespace
+{
+ Size calcMinimumSize(const Edit &rSpinField, const NumericFormatter &rFormatter)
+ {
+ OUStringBuffer aBuf;
+ sal_Int32 nTextLen;
+
+ nTextLen = OUString(OUString::number(rFormatter.GetMin())).getLength();
+ string::padToLength(aBuf, nTextLen, '9');
+ Size aMinTextSize = rSpinField.CalcMinimumSizeForText(
+ rFormatter.CreateFieldText(aBuf.toString().toInt64()));
+ aBuf.setLength(0);
+
+ nTextLen = OUString(OUString::number(rFormatter.GetMax())).getLength();
+ string::padToLength(aBuf, nTextLen, '9');
+ Size aMaxTextSize = rSpinField.CalcMinimumSizeForText(
+ rFormatter.CreateFieldText(aBuf.toString().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;
+ }
+}
+
+Size NumericField::CalcMinimumSize() const
+{
+ return calcMinimumSize(*this, *this);
+}
+
+NumericBox::NumericBox(vcl::Window* pParent, WinBits nWinStyle)
+ : ComboBox(pParent, nWinStyle)
+ , NumericFormatter(this)
+{
+ Reformat();
+ if ( !(nWinStyle & WB_HIDE ) )
+ Show();
+}
+
+void NumericBox::dispose()
+{
+ ClearField();
+ ComboBox::dispose();
+}
+
+Size NumericBox::CalcMinimumSize() const
+{
+ Size aRet(calcMinimumSize(*this, *this));
+
+ if (IsDropDownBox())
+ {
+ Size aComboSugg(ComboBox::CalcMinimumSize());
+ aRet.setWidth( std::max(aRet.Width(), aComboSugg.Width()) );
+ aRet.setHeight( std::max(aRet.Height(), aComboSugg.Height()) );
+ }
+
+ return aRet;
+}
+
+bool NumericBox::PreNotify( NotifyEvent& rNEvt )
+{
+ if ( (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) && !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() )
+ {
+ if ( ImplNumericProcessKeyInput( *rNEvt.GetKeyEvent(), IsStrictFormat(), IsUseThousandSep(), ImplGetLocaleDataWrapper() ) )
+ return true;
+ }
+
+ return ComboBox::PreNotify( rNEvt );
+}
+
+bool NumericBox::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() && (!GetText().isEmpty() || !IsEmptyFieldValueEnabled()) )
+ Reformat();
+ }
+
+ return ComboBox::EventNotify( rNEvt );
+}
+
+void NumericBox::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ ComboBox::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::LOCALE) )
+ {
+ OUString sOldDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
+ OUString sOldThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
+ ImplGetLocaleDataWrapper().setLanguageTag( GetSettings().GetLanguageTag() );
+ OUString sNewDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
+ OUString sNewThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
+ ImplUpdateSeparators( sOldDecSep, sNewDecSep, sOldThSep, sNewThSep, this );
+ ReformatAll();
+ }
+}
+
+void NumericBox::Modify()
+{
+ MarkToBeReformatted( true );
+ ComboBox::Modify();
+}
+
+void NumericBox::ImplNumericReformat( const OUString& rStr, sal_Int64& rValue,
+ OUString& rOutStr )
+{
+ if (ImplNumericGetValue(rStr, rValue, GetDecimalDigits(), ImplGetLocaleDataWrapper()))
+ {
+ sal_Int64 nTempVal = ClipAgainstMinMax(rValue);
+ rOutStr = CreateFieldText( nTempVal );
+ }
+}
+
+void NumericBox::ReformatAll()
+{
+ sal_Int64 nValue;
+ OUString aStr;
+ SetUpdateMode( false );
+ sal_Int32 nEntryCount = GetEntryCount();
+ for ( sal_Int32 i=0; i < nEntryCount; i++ )
+ {
+ ImplNumericReformat( GetEntry( i ), nValue, aStr );
+ RemoveEntryAt(i);
+ InsertEntry( aStr, i );
+ }
+ NumericFormatter::Reformat();
+ SetUpdateMode( true );
+}
+
+static bool ImplMetricProcessKeyInput( const KeyEvent& rKEvt,
+ bool bUseThousandSep, const LocaleDataWrapper& rWrapper )
+{
+ // no meaningful strict format; therefore allow all characters
+ return ImplNumericProcessKeyInput( rKEvt, false, bUseThousandSep, rWrapper );
+}
+
+static OUString ImplMetricGetUnitText(const OUString& rStr)
+{
+ // fetch unit text
+ OUStringBuffer aStr;
+ for (sal_Int32 i = rStr.getLength()-1; i >= 0; --i)
+ {
+ sal_Unicode c = rStr[i];
+ if ( (c == '\'') || (c == '\"') || (c == '%') || (c == 0x2032) || (c == 0x2033) || unicode::isAlpha(c) || unicode::isControl(c) )
+ aStr.insert(0, c);
+ else
+ {
+ if (!aStr.isEmpty())
+ break;
+ }
+ }
+ return aStr.makeStringAndClear();
+}
+
+// #104355# support localized measurements
+
+static OUString ImplMetricToString( FieldUnit rUnit )
+{
+ // return unit's default string (ie, the first one )
+ for (auto const& elem : ImplGetFieldUnits())
+ {
+ if (elem.second == rUnit)
+ return elem.first;
+ }
+
+ return OUString();
+}
+
+namespace vcl
+{
+ FieldUnit StringToMetric(const OUString &rMetricString)
+ {
+ // return FieldUnit
+ OUString aStr = rMetricString.toAsciiLowerCase().replaceAll(" ", "");
+ for (auto const& elem : ImplGetCleanedFieldUnits())
+ {
+ if ( elem.first == aStr )
+ return elem.second;
+ }
+
+ return FieldUnit::NONE;
+ }
+}
+
+static FieldUnit ImplMetricGetUnit(const OUString& rStr)
+{
+ OUString aStr = ImplMetricGetUnitText(rStr);
+ return vcl::StringToMetric(aStr);
+}
+
+#define K *1000L
+#define M *1000000LL
+#define X *5280L
+
+// twip in km = 254 / 14 400 000 000
+// expressions too big for default size 32 bit need LL to avoid overflow
+
+static const sal_Int64 aImplFactor[sal_uInt16(FieldUnit::LINE) + 1]
+ [sal_uInt16(FieldUnit::LINE) + 1] =
+{ /*
+mm/100 mm cm m km twip point pica inch foot mile char line */
+{ 1, 100, 1 K, 100 K, 100 M, 2540, 2540, 2540, 2540,2540*12,2540*12 X , 53340, 396240},
+{ 1, 1, 10, 1 K, 1 M, 2540, 2540, 2540, 2540,2540*12,2540*12 X , 5334, 396240},
+{ 1, 1, 1, 100, 100 K, 254, 254, 254, 254, 254*12, 254*12 X , 5334, 39624},
+{ 1, 1, 1, 1, 1 K, 254, 254, 254, 254, 254*12, 254*12 X , 533400, 39624},
+{ 1, 1, 1, 1, 1, 254, 254, 254, 254, 254*12, 254*12 X ,533400 K, 39624},
+{ 1440,144 K,144 K,14400 K,14400LL M, 1, 20, 240, 1440,1440*12,1440*12 X , 210, 3120},
+{ 72, 7200, 7200, 720 K, 720 M, 1, 1, 12, 72, 72*12, 72*12 X , 210, 156},
+{ 6, 600, 600, 60 K, 60 M, 1, 1, 1, 6, 6*12, 6*12 X , 210, 10},
+{ 1, 100, 100, 10 K, 10 M, 1, 1, 1, 1, 12, 12 X , 210, 45},
+{ 1, 100, 100, 10 K, 10 M, 1, 1, 1, 1, 1, 1 X , 210, 45},
+{ 1, 100, 100, 10 K, 10 M, 1, 1, 1, 1, 1, 1 , 210, 45},
+{ 144, 1440,14400, 14400, 14400, 1, 20, 240, 1440,1440*12, 1440*12 X, 1, 156 },
+{ 720,72000,72000, 7200 K,7200LL M, 20, 10, 13, 11, 11*12, 11*12 X, 105, 1 }
+};
+#undef X
+#undef M
+#undef K
+
+static FieldUnit ImplMap2FieldUnit( MapUnit meUnit, 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>( 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, long nDigits, FieldUnit eInUnit, FieldUnit eOutUnit )
+{
+ if ( nDigits < 0 )
+ {
+ while ( nDigits )
+ {
+ nValue += 5;
+ nValue /= 10;
+ nDigits++;
+ }
+ }
+ else
+ {
+ nValue *= ImplPower10(nDigits);
+ }
+
+ if ( eInUnit != eOutUnit )
+ {
+ sal_Int64 nDiv = aImplFactor[sal_uInt16(eInUnit)][sal_uInt16(eOutUnit)];
+ sal_Int64 nMult = aImplFactor[sal_uInt16(eOutUnit)][sal_uInt16(eInUnit)];
+
+ SAL_WARN_IF( nMult <= 0, "vcl", "illegal *" );
+ SAL_WARN_IF( nDiv <= 0, "vcl", "illegal /" );
+
+ if ( nMult != 1 && nMult > 0)
+ nValue *= nMult;
+ if ( nDiv != 1 && nDiv > 0 )
+ {
+ nValue += (nValue < 0) ? (-nDiv/2) : (nDiv/2);
+ nValue /= nDiv;
+ }
+ }
+ 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;
+ }
+
+ 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 )
+ {
+ sal_Int64 nMult = 1, nDiv = 1;
+
+ if (eInUnit == FieldUnit::PERCENT)
+ {
+ if ( (mnBaseValue <= 0) || (nValue <= 0) )
+ return nValue;
+ nDiv = 100 * ImplPower10(nDecDigits);
+
+ nMult = mnBaseValue;
+ }
+ else if ( eOutUnit == FieldUnit::PERCENT ||
+ eOutUnit == FieldUnit::CUSTOM ||
+ eOutUnit == FieldUnit::NONE ||
+ eOutUnit == FieldUnit::DEGREE ||
+ eOutUnit == FieldUnit::SECOND ||
+ eOutUnit == FieldUnit::MILLISECOND ||
+ eOutUnit == FieldUnit::PIXEL ||
+ eInUnit == FieldUnit::CUSTOM ||
+ eInUnit == FieldUnit::NONE ||
+ eInUnit == FieldUnit::DEGREE ||
+ eInUnit == FieldUnit::MILLISECOND ||
+ eInUnit == FieldUnit::PIXEL )
+ return nValue;
+ else
+ {
+ if (eOutUnit == FieldUnit::MM_100TH)
+ eOutUnit = FieldUnit::NONE;
+ if (eInUnit == FieldUnit::MM_100TH)
+ eInUnit = FieldUnit::NONE;
+
+ nDiv = aImplFactor[sal_uInt16(eInUnit)][sal_uInt16(eOutUnit)];
+ nMult = aImplFactor[sal_uInt16(eOutUnit)][sal_uInt16(eInUnit)];
+
+ SAL_WARN_IF( nMult <= 0, "vcl", "illegal *" );
+ SAL_WARN_IF( nDiv <= 0, "vcl", "illegal /" );
+ }
+
+ if ( nMult != 1 && nMult > 0 )
+ nValue *= nMult;
+ if ( nDiv != 1 && nDiv > 0 )
+ {
+ nValue += ( nValue < 0 ) ? (-nDiv/2) : (nDiv/2);
+ nValue /= nDiv;
+ }
+ }
+
+ return nValue;
+ }
+
+ double ConvertDoubleValue(double nValue, sal_uInt16 nDigits,
+ MapUnit eInUnit, FieldUnit eOutUnit)
+ {
+ if ( !checkConversionUnits(eInUnit, eOutUnit) )
+ {
+ OSL_FAIL( "invalid parameters" );
+ return nValue;
+ }
+
+ 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;
+ }
+
+ long nDecDigits = nDigits;
+ FieldUnit eFieldUnit = ImplMap2FieldUnit( eOutUnit, nDecDigits );
+
+ if ( nDecDigits < 0 )
+ {
+ nValue *= ImplPower10(-nDecDigits);
+ }
+ else
+ {
+ nValue /= ImplPower10(nDecDigits);
+ }
+
+ if ( eFieldUnit != eInUnit )
+ {
+ sal_Int64 nDiv = aImplFactor[sal_uInt16(eInUnit)][sal_uInt16(eFieldUnit)];
+ sal_Int64 nMult = aImplFactor[sal_uInt16(eFieldUnit)][sal_uInt16(eInUnit)];
+
+ SAL_WARN_IF( nMult <= 0, "vcl", "illegal *" );
+ SAL_WARN_IF( nDiv <= 0, "vcl", "illegal /" );
+
+ if( nMult != 1 && nMult > 0 )
+ nValue *= nMult;
+ if( nDiv != 1 && nDiv > 0 )
+ {
+ nValue += (nValue < 0) ? (-nDiv/2) : (nDiv/2);
+ nValue /= nDiv;
+ }
+ }
+ return nValue;
+ }
+}
+
+namespace vcl
+{
+ bool TextToValue(const OUString& rStr, double& rValue, sal_Int64 nBaseValue,
+ sal_uInt16 nDecDigits, const LocaleDataWrapper& rLocaleDataWrapper, FieldUnit eUnit)
+ {
+ // Get value
+ sal_Int64 nValue;
+ if ( !ImplNumericGetValue( rStr, nValue, nDecDigits, rLocaleDataWrapper ) )
+ return false;
+
+ // Determine unit
+ FieldUnit eEntryUnit = ImplMetricGetUnit( rStr );
+
+ // Recalculate unit
+ // caution: conversion to double loses precision
+ rValue = vcl::ConvertDoubleValue(static_cast<double>(nValue), nBaseValue, nDecDigits, eEntryUnit, eUnit);
+
+ return true;
+ }
+}
+
+void MetricFormatter::ImplMetricReformat( const OUString& rStr, double& rValue, OUString& rOutStr )
+{
+ if (!vcl::TextToValue(rStr, rValue, 0, GetDecimalDigits(), ImplGetLocaleDataWrapper(), meUnit))
+ return;
+
+ double nTempVal = rValue;
+ // caution: precision loss in double cast
+ if ( nTempVal > GetMax() )
+ nTempVal = static_cast<double>(GetMax());
+ else if ( nTempVal < GetMin())
+ nTempVal = static_cast<double>(GetMin());
+ rOutStr = CreateFieldText( static_cast<sal_Int64>(nTempVal) );
+}
+
+MetricFormatter::MetricFormatter(Edit* pEdit)
+ : NumericFormatter(pEdit)
+ , meUnit(FieldUnit::NONE)
+{
+}
+
+MetricFormatter::~MetricFormatter()
+{
+}
+
+void MetricFormatter::SetUnit( FieldUnit eNewUnit )
+{
+ if (eNewUnit == FieldUnit::MM_100TH)
+ {
+ SetDecimalDigits( GetDecimalDigits() + 2 );
+ meUnit = FieldUnit::MM;
+ }
+ else
+ meUnit = eNewUnit;
+ ReformatAll();
+}
+
+void MetricFormatter::SetCustomUnitText( const OUString& rStr )
+{
+ maCustomUnitText = rStr;
+ ReformatAll();
+}
+
+void MetricFormatter::SetValue( sal_Int64 nNewValue, FieldUnit eInUnit )
+{
+ SetUserValue( nNewValue, eInUnit );
+}
+
+OUString MetricFormatter::CreateFieldText( sal_Int64 nValue ) const
+{
+ //whether percent is separated from its number is locale
+ //specific, pawn it off to icu to decide
+ if (meUnit == FieldUnit::PERCENT)
+ {
+ double dValue = nValue;
+ dValue /= ImplPower10(GetDecimalDigits());
+ return unicode::formatPercent(dValue, GetLanguageTag());
+ }
+
+ OUString aStr = NumericFormatter::CreateFieldText( nValue );
+
+ if( meUnit == FieldUnit::CUSTOM )
+ aStr += maCustomUnitText;
+ else
+ {
+ OUString aSuffix = ImplMetricToString( meUnit );
+ if (meUnit != FieldUnit::NONE && meUnit != FieldUnit::DEGREE && meUnit != FieldUnit::INCH && meUnit != FieldUnit::FOOT)
+ aStr += " ";
+ if (meUnit == FieldUnit::INCH)
+ {
+ OUString sDoublePrime = u"\u2033";
+ if (aSuffix != "\"" && aSuffix != sDoublePrime)
+ aStr += " ";
+ else
+ aSuffix = sDoublePrime;
+ }
+ else if (meUnit == FieldUnit::FOOT)
+ {
+ OUString sPrime = u"\u2032";
+ if (aSuffix != "'" && aSuffix != sPrime)
+ aStr += " ";
+ else
+ aSuffix = sPrime;
+ }
+
+ assert(meUnit != FieldUnit::PERCENT);
+ aStr += aSuffix;
+ }
+ return aStr;
+}
+
+void MetricFormatter::SetUserValue( sal_Int64 nNewValue, FieldUnit eInUnit )
+{
+ // convert to previously configured units
+ nNewValue = vcl::ConvertValue( nNewValue, 0, GetDecimalDigits(), eInUnit, meUnit );
+ NumericFormatter::SetUserValue( nNewValue );
+}
+
+sal_Int64 MetricFormatter::GetValueFromStringUnit(const OUString& rStr, FieldUnit eOutUnit) const
+{
+ double nTempValue;
+ // caution: precision loss in double cast
+ if (!vcl::TextToValue(rStr, nTempValue, 0, GetDecimalDigits(), ImplGetLocaleDataWrapper(), meUnit))
+ nTempValue = static_cast<double>(mnLastValue);
+
+ // caution: precision loss in double cast
+ if (nTempValue > mnMax)
+ nTempValue = static_cast<double>(mnMax);
+ else if (nTempValue < mnMin)
+ nTempValue = static_cast<double>(mnMin);
+
+ // convert to requested units
+ return vcl::ConvertValue(static_cast<sal_Int64>(nTempValue), 0, GetDecimalDigits(), meUnit, eOutUnit);
+}
+
+sal_Int64 MetricFormatter::GetValueFromString(const OUString& rStr) const
+{
+ return GetValueFromStringUnit(rStr, FieldUnit::NONE);
+}
+
+sal_Int64 MetricFormatter::GetValue( FieldUnit eOutUnit ) const
+{
+ return GetField() ? GetValueFromStringUnit(GetField()->GetText(), eOutUnit) : 0;
+}
+
+void MetricFormatter::SetValue( sal_Int64 nValue )
+{
+ // Implementation not inline, because it is a virtual Function
+ SetValue( nValue, FieldUnit::NONE );
+}
+
+void MetricFormatter::SetMin( sal_Int64 nNewMin, FieldUnit eInUnit )
+{
+ // convert to requested units
+ NumericFormatter::SetMin(vcl::ConvertValue(nNewMin, 0, GetDecimalDigits(), eInUnit, meUnit));
+}
+
+sal_Int64 MetricFormatter::GetMin( FieldUnit eOutUnit ) const
+{
+ // convert to requested units
+ return vcl::ConvertValue(NumericFormatter::GetMin(), 0, GetDecimalDigits(), meUnit, eOutUnit);
+}
+
+void MetricFormatter::SetMax( sal_Int64 nNewMax, FieldUnit eInUnit )
+{
+ // convert to requested units
+ NumericFormatter::SetMax(vcl::ConvertValue(nNewMax, 0, GetDecimalDigits(), eInUnit, meUnit));
+}
+
+sal_Int64 MetricFormatter::GetMax( FieldUnit eOutUnit ) const
+{
+ // convert to requested units
+ return vcl::ConvertValue(NumericFormatter::GetMax(), 0, GetDecimalDigits(), meUnit, eOutUnit);
+}
+
+void MetricFormatter::Reformat()
+{
+ if ( !GetField() )
+ return;
+
+ OUString aText = GetField()->GetText();
+
+ OUString aStr;
+ // caution: precision loss in double cast
+ double nTemp = static_cast<double>(mnLastValue);
+ ImplMetricReformat( aText, nTemp, aStr );
+ mnLastValue = static_cast<sal_Int64>(nTemp);
+
+ if ( !aStr.isEmpty() )
+ {
+ ImplSetText( aStr );
+ }
+ else
+ SetValue( mnLastValue );
+}
+
+sal_Int64 MetricFormatter::GetCorrectedValue( FieldUnit eOutUnit ) const
+{
+ // convert to requested units
+ return vcl::ConvertValue(0/*nCorrectedValue*/, 0, GetDecimalDigits(),
+ meUnit, eOutUnit);
+}
+
+MetricField::MetricField(vcl::Window* pParent, WinBits nWinStyle)
+ : SpinField(pParent, nWinStyle, WindowType::METRICFIELD)
+ , MetricFormatter(this)
+{
+ Reformat();
+}
+
+void MetricField::dispose()
+{
+ ClearField();
+ SpinField::dispose();
+}
+
+Size MetricField::CalcMinimumSize() const
+{
+ return calcMinimumSize(*this, *this);
+}
+
+bool MetricField::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "digits")
+ SetDecimalDigits(rValue.toInt32());
+ else if (rKey == "spin-size")
+ SetSpinSize(rValue.toInt32());
+ else
+ return SpinField::set_property(rKey, rValue);
+ return true;
+}
+
+void MetricField::SetUnit( FieldUnit nNewUnit )
+{
+ sal_Int64 nRawMax = GetMax( nNewUnit );
+ sal_Int64 nMax = Denormalize( nRawMax );
+ sal_Int64 nMin = Denormalize( GetMin( nNewUnit ) );
+ sal_Int64 nFirst = Denormalize( GetFirst( nNewUnit ) );
+ sal_Int64 nLast = Denormalize( GetLast( nNewUnit ) );
+
+ MetricFormatter::SetUnit( nNewUnit );
+
+ SetMax( Normalize( nMax ), nNewUnit );
+ SetMin( Normalize( nMin ), nNewUnit );
+ SetFirst( Normalize( nFirst ), nNewUnit );
+ SetLast( Normalize( nLast ), nNewUnit );
+}
+
+void MetricField::SetFirst( sal_Int64 nNewFirst, FieldUnit eInUnit )
+{
+ // convert
+ nNewFirst = vcl::ConvertValue(nNewFirst, 0, GetDecimalDigits(), eInUnit, meUnit);
+ mnFirst = nNewFirst;
+}
+
+sal_Int64 MetricField::GetFirst( FieldUnit eOutUnit ) const
+{
+ // convert
+ return vcl::ConvertValue(mnFirst, 0, GetDecimalDigits(), meUnit, eOutUnit);
+}
+
+void MetricField::SetLast( sal_Int64 nNewLast, FieldUnit eInUnit )
+{
+ // convert
+ nNewLast = vcl::ConvertValue(nNewLast, 0, GetDecimalDigits(), eInUnit, meUnit);
+ mnLast = nNewLast;
+}
+
+sal_Int64 MetricField::GetLast( FieldUnit eOutUnit ) const
+{
+ // convert
+ return vcl::ConvertValue(mnLast, 0, GetDecimalDigits(), meUnit, eOutUnit);
+}
+
+bool MetricField::PreNotify( NotifyEvent& rNEvt )
+{
+ if ( (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) && !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() )
+ {
+ if ( ImplMetricProcessKeyInput( *rNEvt.GetKeyEvent(), IsUseThousandSep(), ImplGetLocaleDataWrapper() ) )
+ return true;
+ }
+
+ return SpinField::PreNotify( rNEvt );
+}
+
+bool MetricField::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() && (!GetText().isEmpty() || !IsEmptyFieldValueEnabled()) )
+ Reformat();
+ }
+
+ return SpinField::EventNotify( rNEvt );
+}
+
+void MetricField::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ SpinField::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::LOCALE) )
+ {
+ OUString sOldDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
+ OUString sOldThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
+ ImplGetLocaleDataWrapper().setLanguageTag( GetSettings().GetLanguageTag() );
+ 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();
+}
+
+boost::property_tree::ptree MetricField::DumpAsPropertyTree()
+{
+ boost::property_tree::ptree aTree(SpinField::DumpAsPropertyTree());
+ aTree.put("min", GetMin());
+ aTree.put("max", GetMax());
+ aTree.put("unit", FieldUnitToString(GetUnit()));
+ OUString sValue = Application::GetSettings().GetNeutralLocaleDataWrapper().
+ getNum(GetValue(), GetDecimalDigits(), false, false);
+ aTree.put("value", sValue.toUtf8().getStr());
+
+ return aTree;
+}
+
+FactoryFunction MetricField::GetUITestFactory() const
+{
+ return MetricFieldUIObject::create;
+}
+
+MetricBox::MetricBox(vcl::Window* pParent, WinBits nWinStyle)
+ : ComboBox(pParent, nWinStyle)
+ , MetricFormatter(this)
+{
+ Reformat();
+}
+
+void MetricBox::dispose()
+{
+ ClearField();
+ ComboBox::dispose();
+}
+
+Size MetricBox::CalcMinimumSize() const
+{
+ Size aRet(calcMinimumSize(*this, *this));
+
+ if (IsDropDownBox())
+ {
+ Size aComboSugg(ComboBox::CalcMinimumSize());
+ aRet.setWidth( std::max(aRet.Width(), aComboSugg.Width()) );
+ aRet.setHeight( std::max(aRet.Height(), aComboSugg.Height()) );
+ }
+
+ return aRet;
+}
+
+bool MetricBox::PreNotify( NotifyEvent& rNEvt )
+{
+ if ( (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) && !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() )
+ {
+ if ( ImplMetricProcessKeyInput( *rNEvt.GetKeyEvent(), IsUseThousandSep(), ImplGetLocaleDataWrapper() ) )
+ return true;
+ }
+
+ return ComboBox::PreNotify( rNEvt );
+}
+
+bool MetricBox::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() && (!GetText().isEmpty() || !IsEmptyFieldValueEnabled()) )
+ Reformat();
+ }
+
+ return ComboBox::EventNotify( rNEvt );
+}
+
+void MetricBox::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ ComboBox::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::LOCALE) )
+ {
+ OUString sOldDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
+ OUString sOldThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
+ ImplGetLocaleDataWrapper().setLanguageTag( GetSettings().GetLanguageTag() );
+ OUString sNewDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
+ OUString sNewThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
+ ImplUpdateSeparators( sOldDecSep, sNewDecSep, sOldThSep, sNewThSep, this );
+ ReformatAll();
+ }
+}
+
+void MetricBox::Modify()
+{
+ MarkToBeReformatted( true );
+ ComboBox::Modify();
+}
+
+void MetricBox::ReformatAll()
+{
+ double nValue;
+ OUString aStr;
+ SetUpdateMode( false );
+ sal_Int32 nEntryCount = GetEntryCount();
+ for ( sal_Int32 i=0; i < nEntryCount; i++ )
+ {
+ ImplMetricReformat( GetEntry( i ), nValue, aStr );
+ RemoveEntryAt(i);
+ InsertEntry( aStr, i );
+ }
+ MetricFormatter::Reformat();
+ SetUpdateMode( true );
+}
+
+static bool ImplCurrencyProcessKeyInput( const KeyEvent& rKEvt,
+ bool bUseThousandSep, const LocaleDataWrapper& rWrapper )
+{
+ // no strict format set; therefore allow all characters
+ return ImplNumericProcessKeyInput( rKEvt, false, bUseThousandSep, rWrapper );
+}
+
+static bool ImplCurrencyGetValue( const OUString& rStr, sal_Int64& rValue,
+ sal_uInt16 nDecDigits, const LocaleDataWrapper& rWrapper )
+{
+ // fetch number
+ return ImplNumericGetValue( rStr, rValue, nDecDigits, rWrapper, true );
+}
+
+void CurrencyFormatter::ImplCurrencyReformat( const OUString& rStr, OUString& rOutStr )
+{
+ sal_Int64 nValue;
+ if ( !ImplNumericGetValue( rStr, nValue, GetDecimalDigits(), ImplGetLocaleDataWrapper(), true ) )
+ return;
+
+ sal_Int64 nTempVal = nValue;
+ if ( nTempVal > GetMax() )
+ nTempVal = GetMax();
+ else if ( nTempVal < GetMin())
+ nTempVal = GetMin();
+ rOutStr = CreateFieldText( nTempVal );
+}
+
+CurrencyFormatter::CurrencyFormatter(Edit* pField)
+ : NumericFormatter(pField)
+{
+}
+
+CurrencyFormatter::~CurrencyFormatter()
+{
+}
+
+void CurrencyFormatter::SetValue( sal_Int64 nNewValue )
+{
+ SetUserValue( nNewValue );
+ SetEmptyFieldValueData( false );
+}
+
+OUString CurrencyFormatter::CreateFieldText( sal_Int64 nValue ) const
+{
+ return ImplGetLocaleDataWrapper().getCurr( nValue, GetDecimalDigits(),
+ ImplGetLocaleDataWrapper().getCurrSymbol(),
+ IsUseThousandSep() );
+}
+
+sal_Int64 CurrencyFormatter::GetValueFromString(const OUString& rStr) const
+{
+ sal_Int64 nTempValue;
+ if ( ImplCurrencyGetValue( rStr, nTempValue, GetDecimalDigits(), ImplGetLocaleDataWrapper() ) )
+ {
+ return ClipAgainstMinMax(nTempValue);
+ }
+ else
+ return mnLastValue;
+}
+
+void CurrencyFormatter::Reformat()
+{
+ if ( !GetField() )
+ return;
+
+ OUString aStr;
+ ImplCurrencyReformat( GetField()->GetText(), aStr );
+
+ if ( !aStr.isEmpty() )
+ {
+ ImplSetText( aStr );
+ sal_Int64 nTemp = mnLastValue;
+ ImplCurrencyGetValue( aStr, nTemp, GetDecimalDigits(), ImplGetLocaleDataWrapper() );
+ mnLastValue = nTemp;
+ }
+ else
+ SetValue( mnLastValue );
+}
+
+CurrencyField::CurrencyField(vcl::Window* pParent, WinBits nWinStyle)
+ : SpinField(pParent, nWinStyle)
+ , CurrencyFormatter(this)
+{
+ Reformat();
+}
+
+void CurrencyField::dispose()
+{
+ ClearField();
+ SpinField::dispose();
+}
+
+bool CurrencyField::PreNotify( NotifyEvent& rNEvt )
+{
+ if ( (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) && !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() )
+ {
+ if ( ImplCurrencyProcessKeyInput( *rNEvt.GetKeyEvent(), IsUseThousandSep(), ImplGetLocaleDataWrapper() ) )
+ return true;
+ }
+
+ return SpinField::PreNotify( rNEvt );
+}
+
+bool CurrencyField::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() && (!GetText().isEmpty() || !IsEmptyFieldValueEnabled()) )
+ Reformat();
+ }
+
+ return SpinField::EventNotify( rNEvt );
+}
+
+void CurrencyField::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ SpinField::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::LOCALE) )
+ {
+ OUString sOldDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
+ OUString sOldThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
+ ImplGetLocaleDataWrapper().setLanguageTag( GetSettings().GetLanguageTag() );
+ OUString sNewDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
+ OUString sNewThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
+ ImplUpdateSeparators( sOldDecSep, sNewDecSep, sOldThSep, sNewThSep, this );
+ ReformatAll();
+ }
+}
+
+void CurrencyField::Modify()
+{
+ MarkToBeReformatted( true );
+ SpinField::Modify();
+}
+
+void CurrencyField::Up()
+{
+ FieldUp();
+ SpinField::Up();
+}
+
+void CurrencyField::Down()
+{
+ FieldDown();
+ SpinField::Down();
+}
+
+void CurrencyField::First()
+{
+ FieldFirst();
+ SpinField::First();
+}
+
+void CurrencyField::Last()
+{
+ FieldLast();
+ SpinField::Last();
+}
+
+CurrencyBox::CurrencyBox(vcl::Window* pParent, WinBits nWinStyle)
+ : ComboBox(pParent, nWinStyle)
+ , CurrencyFormatter(this)
+{
+ Reformat();
+}
+
+void CurrencyBox::dispose()
+{
+ ClearField();
+ ComboBox::dispose();
+}
+
+bool CurrencyBox::PreNotify( NotifyEvent& rNEvt )
+{
+ if ( (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) && !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() )
+ {
+ if ( ImplCurrencyProcessKeyInput( *rNEvt.GetKeyEvent(), IsUseThousandSep(), ImplGetLocaleDataWrapper() ) )
+ return true;
+ }
+
+ return ComboBox::PreNotify( rNEvt );
+}
+
+bool CurrencyBox::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() && (!GetText().isEmpty() || !IsEmptyFieldValueEnabled()) )
+ Reformat();
+ }
+
+ return ComboBox::EventNotify( rNEvt );
+}
+
+void CurrencyBox::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ ComboBox::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::LOCALE) )
+ {
+ OUString sOldDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
+ OUString sOldThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
+ ImplGetLocaleDataWrapper().setLanguageTag( GetSettings().GetLanguageTag() );
+ OUString sNewDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
+ OUString sNewThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
+ ImplUpdateSeparators( sOldDecSep, sNewDecSep, sOldThSep, sNewThSep, this );
+ ReformatAll();
+ }
+}
+
+void CurrencyBox::Modify()
+{
+ MarkToBeReformatted( true );
+ ComboBox::Modify();
+}
+
+void CurrencyBox::ReformatAll()
+{
+ OUString aStr;
+ SetUpdateMode( false );
+ sal_Int32 nEntryCount = GetEntryCount();
+ for ( sal_Int32 i=0; i < nEntryCount; i++ )
+ {
+ ImplCurrencyReformat( GetEntry( i ), aStr );
+ RemoveEntryAt(i);
+ InsertEntry( aStr, i );
+ }
+ CurrencyFormatter::Reformat();
+ SetUpdateMode( true );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/field2.cxx b/vcl/source/control/field2.cxx
new file mode 100644
index 000000000..da400911d
--- /dev/null
+++ b/vcl/source/control/field2.cxx
@@ -0,0 +1,2708 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <algorithm>
+#include <string_view>
+
+#include <tools/diagnose_ex.h>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/string.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/event.hxx>
+#include <vcl/toolkit/field.hxx>
+#include <vcl/unohelp.hxx>
+#include <vcl/settings.hxx>
+
+#include <svdata.hxx>
+
+#include <com/sun/star/i18n/XCharacterClassification.hpp>
+
+#include <unotools/localedatawrapper.hxx>
+#include <unotools/calendarwrapper.hxx>
+#include <unotools/charclass.hxx>
+#include <unotools/misccfg.hxx>
+
+using namespace ::com::sun::star;
+using namespace ::comphelper;
+
+#define EDITMASK_LITERAL 'L'
+#define EDITMASK_ALPHA 'a'
+#define EDITMASK_UPPERALPHA 'A'
+#define EDITMASK_ALPHANUM 'c'
+#define EDITMASK_UPPERALPHANUM 'C'
+#define EDITMASK_NUM 'N'
+#define EDITMASK_NUMSPACE 'n'
+#define EDITMASK_ALLCHAR 'x'
+#define EDITMASK_UPPERALLCHAR 'X'
+
+uno::Reference< i18n::XCharacterClassification > const & ImplGetCharClass()
+{
+ ImplSVData *const pSVData = ImplGetSVData();
+ assert(pSVData);
+
+ if (!pSVData->m_xCharClass.is())
+ {
+ pSVData->m_xCharClass = vcl::unohelper::CreateCharacterClassification();
+ }
+
+ return pSVData->m_xCharClass;
+}
+
+static sal_Unicode* ImplAddString( sal_Unicode* pBuf, const OUString& rStr )
+{
+ memcpy( pBuf, rStr.getStr(), rStr.getLength() * sizeof(sal_Unicode) );
+ pBuf += rStr.getLength();
+ return pBuf;
+}
+
+static sal_Unicode* ImplAddNum( sal_Unicode* pBuf, sal_uLong nNumber, int nMinLen )
+{
+ // fill temp buffer with digits
+ sal_Unicode aTempBuf[30];
+ sal_Unicode* pTempBuf = aTempBuf;
+ do
+ {
+ *pTempBuf = static_cast<sal_Unicode>(nNumber % 10) + '0';
+ pTempBuf++;
+ nNumber /= 10;
+ if ( nMinLen )
+ nMinLen--;
+ }
+ while ( nNumber );
+
+ // fill with zeros up to the minimal length
+ while ( nMinLen > 0 )
+ {
+ *pBuf = '0';
+ pBuf++;
+ nMinLen--;
+ }
+
+ // copy temp buffer to real buffer
+ do
+ {
+ pTempBuf--;
+ *pBuf = *pTempBuf;
+ pBuf++;
+ }
+ while ( pTempBuf != aTempBuf );
+
+ return pBuf;
+}
+
+static sal_Unicode* ImplAddSNum( sal_Unicode* pBuf, sal_Int32 nNumber, int nMinLen )
+{
+ if (nNumber < 0)
+ {
+ *pBuf++ = '-';
+ nNumber = -nNumber;
+ }
+ return ImplAddNum( pBuf, nNumber, nMinLen);
+}
+
+static sal_uInt16 ImplGetNum( const sal_Unicode*& rpBuf, bool& rbError )
+{
+ if ( !*rpBuf )
+ {
+ rbError = true;
+ return 0;
+ }
+
+ sal_uInt16 nNumber = 0;
+ while( ( *rpBuf >= '0' ) && ( *rpBuf <= '9' ) )
+ {
+ nNumber *= 10;
+ nNumber += *rpBuf - '0';
+ rpBuf++;
+ }
+
+ return nNumber;
+}
+
+static void ImplSkipDelimiters( const sal_Unicode*& rpBuf )
+{
+ while( ( *rpBuf == ',' ) || ( *rpBuf == '.' ) || ( *rpBuf == ';' ) ||
+ ( *rpBuf == ':' ) || ( *rpBuf == '-' ) || ( *rpBuf == '/' ) )
+ {
+ rpBuf++;
+ }
+}
+
+static bool ImplIsPatternChar( sal_Unicode cChar, char cEditMask )
+{
+ sal_Int32 nType = 0;
+
+ try
+ {
+ OUString aCharStr(cChar);
+ nType = ImplGetCharClass()->getStringType( aCharStr, 0, aCharStr.getLength(),
+ Application::GetSettings().GetLanguageTag().getLocale() );
+ }
+ catch (const css::uno::Exception&)
+ {
+ DBG_UNHANDLED_EXCEPTION("vcl.control");
+ return false;
+ }
+
+ if ( (cEditMask == EDITMASK_ALPHA) || (cEditMask == EDITMASK_UPPERALPHA) )
+ {
+ if( !CharClass::isLetterType( nType ) )
+ return false;
+ }
+ else if ( cEditMask == EDITMASK_NUM )
+ {
+ if( !CharClass::isNumericType( nType ) )
+ return false;
+ }
+ else if ( (cEditMask == EDITMASK_ALPHANUM) || (cEditMask == EDITMASK_UPPERALPHANUM) )
+ {
+ if( !CharClass::isLetterNumericType( nType ) )
+ return false;
+ }
+ else if ( (cEditMask == EDITMASK_ALLCHAR) || (cEditMask == EDITMASK_UPPERALLCHAR) )
+ {
+ if ( cChar < 32 )
+ return false;
+ }
+ else if ( cEditMask == EDITMASK_NUMSPACE )
+ {
+ if ( !CharClass::isNumericType( nType ) && ( cChar != ' ' ) )
+ return false;
+ }
+ else
+ return false;
+
+ return true;
+}
+
+static sal_Unicode ImplPatternChar( sal_Unicode cChar, char cEditMask )
+{
+ if ( ImplIsPatternChar( cChar, cEditMask ) )
+ {
+ if ( (cEditMask == EDITMASK_UPPERALPHA) ||
+ (cEditMask == EDITMASK_UPPERALPHANUM) ||
+ ( cEditMask == EDITMASK_UPPERALLCHAR ) )
+ {
+ cChar = ImplGetCharClass()->toUpper(OUString(cChar), 0, 1,
+ Application::GetSettings().GetLanguageTag().getLocale())[0];
+ }
+ return cChar;
+ }
+ else
+ return 0;
+}
+
+static bool ImplCommaPointCharEqual( sal_Unicode c1, sal_Unicode c2 )
+{
+ if ( c1 == c2 )
+ return true;
+ else if ( ((c1 == '.') || (c1 == ',')) &&
+ ((c2 == '.') || (c2 == ',')) )
+ return true;
+ else
+ return false;
+}
+
+static OUString ImplPatternReformat( const OUString& rStr,
+ const OString& rEditMask,
+ const OUString& rLiteralMask,
+ sal_uInt16 nFormatFlags )
+{
+ if (rEditMask.isEmpty())
+ return rStr;
+
+ OUStringBuffer aOutStr = rLiteralMask;
+ sal_Unicode cTempChar;
+ sal_Unicode cChar;
+ sal_Unicode cLiteral;
+ char cMask;
+ sal_Int32 nStrIndex = 0;
+ sal_Int32 i = 0;
+ sal_Int32 n;
+
+ while ( i < rEditMask.getLength() )
+ {
+ if ( nStrIndex >= rStr.getLength() )
+ break;
+
+ cChar = rStr[nStrIndex];
+ cLiteral = rLiteralMask[i];
+ cMask = rEditMask[i];
+
+ // current position is a literal
+ if ( cMask == EDITMASK_LITERAL )
+ {
+ // if it is a literal copy otherwise ignore because it might be the next valid
+ // character of the string
+ if ( ImplCommaPointCharEqual( cChar, cLiteral ) )
+ nStrIndex++;
+ else
+ {
+ // Otherwise we check if it is an invalid character. This is the case if it does not
+ // fit in the pattern of the next non-literal character.
+ n = i+1;
+ while ( n < rEditMask.getLength() )
+ {
+ if ( rEditMask[n] != EDITMASK_LITERAL )
+ {
+ if ( !ImplIsPatternChar( cChar, rEditMask[n] ) )
+ nStrIndex++;
+ break;
+ }
+
+ n++;
+ }
+ }
+ }
+ else
+ {
+ // valid character at this position
+ cTempChar = ImplPatternChar( cChar, cMask );
+ if ( cTempChar )
+ {
+ // use this character
+ aOutStr[i] = cTempChar;
+ nStrIndex++;
+ }
+ else
+ {
+ // copy if it is a literal character
+ if ( cLiteral == cChar )
+ nStrIndex++;
+ else
+ {
+ // If the invalid character might be the next literal character then we jump
+ // ahead to it, otherwise we ignore it. Do only if empty literals are allowed.
+ if ( nFormatFlags & PATTERN_FORMAT_EMPTYLITERALS )
+ {
+ n = i;
+ while ( n < rEditMask.getLength() )
+ {
+ if ( rEditMask[n] == EDITMASK_LITERAL )
+ {
+ if ( ImplCommaPointCharEqual( cChar, rLiteralMask[n] ) )
+ i = n+1;
+
+ break;
+ }
+
+ n++;
+ }
+ }
+
+ nStrIndex++;
+ continue;
+ }
+ }
+ }
+
+ i++;
+ }
+
+ return aOutStr.makeStringAndClear();
+}
+
+static void ImplPatternMaxPos( const OUString& rStr, const OString& rEditMask,
+ sal_uInt16 nFormatFlags, bool bSameMask,
+ sal_Int32 nCursorPos, sal_Int32& rPos )
+{
+
+ // last position must not be longer than the contained string
+ sal_Int32 nMaxPos = rStr.getLength();
+
+ // if non empty literals are allowed ignore blanks at the end as well
+ if ( bSameMask && !(nFormatFlags & PATTERN_FORMAT_EMPTYLITERALS) )
+ {
+ while ( nMaxPos )
+ {
+ if ( (rEditMask[nMaxPos-1] != EDITMASK_LITERAL) &&
+ (rStr[nMaxPos-1] != ' ') )
+ break;
+ nMaxPos--;
+ }
+
+ // if we are in front of a literal, continue search until first character after the literal
+ sal_Int32 nTempPos = nMaxPos;
+ while ( nTempPos < rEditMask.getLength() )
+ {
+ if ( rEditMask[nTempPos] != EDITMASK_LITERAL )
+ {
+ nMaxPos = nTempPos;
+ break;
+ }
+ nTempPos++;
+ }
+ }
+
+ if ( rPos > nMaxPos )
+ rPos = nMaxPos;
+
+ // character should not move left
+ if ( rPos < nCursorPos )
+ rPos = nCursorPos;
+}
+
+static void ImplPatternProcessStrictModify( Edit* pEdit,
+ const OString& rEditMask,
+ const OUString& rLiteralMask,
+ bool bSameMask )
+{
+ OUString aText = pEdit->GetText();
+
+ // 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 );
+ }
+
+ OUString aNewText = ImplPatternReformat(aText, rEditMask, rLiteralMask, 0);
+ if ( aNewText != aText )
+ {
+ // 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 sal_Int32 ImplPatternLeftPos(const OString& rEditMask, sal_Int32 nCursorPos)
+{
+ // search non-literal predecessor
+ sal_Int32 nNewPos = nCursorPos;
+ sal_Int32 nTempPos = nNewPos;
+ while ( nTempPos )
+ {
+ if ( rEditMask[nTempPos-1] != EDITMASK_LITERAL )
+ {
+ nNewPos = nTempPos-1;
+ break;
+ }
+ nTempPos--;
+ }
+ return nNewPos;
+}
+
+static sal_Int32 ImplPatternRightPos( const OUString& rStr, const OString& rEditMask,
+ sal_uInt16 nFormatFlags, bool bSameMask,
+ sal_Int32 nCursorPos )
+{
+ // search non-literal successor
+ sal_Int32 nNewPos = nCursorPos;
+ ;
+ for(sal_Int32 nTempPos = nNewPos+1; nTempPos < rEditMask.getLength(); ++nTempPos )
+ {
+ if ( rEditMask[nTempPos] != EDITMASK_LITERAL )
+ {
+ nNewPos = nTempPos;
+ break;
+ }
+ }
+ ImplPatternMaxPos( rStr, rEditMask, nFormatFlags, bSameMask, nCursorPos, nNewPos );
+ return nNewPos;
+}
+
+static bool ImplPatternProcessKeyInput( Edit* pEdit, const KeyEvent& rKEvt,
+ const OString& rEditMask,
+ const OUString& rLiteralMask,
+ bool bStrictFormat,
+ bool bSameMask,
+ bool& rbInKeyInput )
+{
+ if ( rEditMask.isEmpty() || !bStrictFormat )
+ return false;
+
+ sal_uInt16 nFormatFlags = 0;
+ Selection aOldSel = pEdit->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();
+ pEdit->SetSelection( aSel );
+ return true;
+ }
+ else if ( nKeyCode == KEY_RIGHT )
+ {
+ // Use the start of selection as minimum; even a small position is allowed in case that
+ // all was selected by the focus
+ Selection aSel( aOldSel );
+ aSel.Justify();
+ nCursorPos = aSel.Min();
+ aSel.Max() = ImplPatternRightPos( pEdit->GetText(), rEditMask, nFormatFlags, bSameMask, nCursorPos );
+ if ( bShift )
+ aSel.Min() = aOldSel.Min();
+ else
+ aSel.Min() = aSel.Max();
+ pEdit->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();
+ pEdit->SetSelection( aSel );
+ return true;
+ }
+ else if ( nKeyCode == KEY_END )
+ {
+ // End is position of last non-literal character
+ nNewPos = rEditMask.getLength();
+ while ( nNewPos &&
+ (rEditMask[nNewPos-1] == EDITMASK_LITERAL) )
+ nNewPos--;
+ // Use the start of selection as minimum; even a small position is allowed in case that
+ // all was selected by the focus
+ Selection aSel( aOldSel );
+ aSel.Justify();
+ nCursorPos = static_cast<sal_Int32>(aSel.Min());
+ ImplPatternMaxPos( pEdit->GetText(), rEditMask, nFormatFlags, bSameMask, nCursorPos, nNewPos );
+ aSel.Max() = nNewPos;
+ if ( bShift )
+ aSel.Min() = aOldSel.Min();
+ else
+ aSel.Min() = aSel.Max();
+ pEdit->SetSelection( aSel );
+ return true;
+ }
+ else if ( (nKeyCode == KEY_BACKSPACE) || (nKeyCode == KEY_DELETE) )
+ {
+ OUString aOldStr( pEdit->GetText() );
+ OUStringBuffer aStr( aOldStr );
+ Selection aSel = aOldSel;
+
+ aSel.Justify();
+ nNewPos = static_cast<sal_Int32>(aSel.Min());
+
+ // if selection then delete it
+ if ( aSel.Len() )
+ {
+ if ( bSameMask )
+ aStr.remove( static_cast<sal_Int32>(aSel.Min()), static_cast<sal_Int32>(aSel.Len()) );
+ else
+ {
+ OUString aRep = rLiteralMask.copy( static_cast<sal_Int32>(aSel.Min()), static_cast<sal_Int32>(aSel.Len()) );
+ aStr.remove( aSel.Min(), aRep.getLength() );
+ aStr.insert( aSel.Min(), aRep );
+ }
+ }
+ else
+ {
+ if ( nKeyCode == KEY_BACKSPACE )
+ {
+ nTempPos = nNewPos;
+ nNewPos = ImplPatternLeftPos( rEditMask, nTempPos );
+ }
+ else
+ nTempPos = ImplPatternRightPos( aStr.toString(), rEditMask, nFormatFlags, bSameMask, nNewPos );
+
+ if ( nNewPos != nTempPos )
+ {
+ if ( bSameMask )
+ {
+ if ( rEditMask[nNewPos] != EDITMASK_LITERAL )
+ aStr.remove( nNewPos, 1 );
+ }
+ else
+ {
+ aStr[nNewPos] = rLiteralMask[nNewPos];
+ }
+ }
+ }
+
+ if ( aOldStr != aStr.toString() )
+ {
+ if ( bSameMask )
+ aStr = ImplPatternReformat( aStr.toString(), rEditMask, rLiteralMask, nFormatFlags );
+ rbInKeyInput = true;
+ pEdit->SetText( aStr.toString(), Selection( nNewPos ) );
+ pEdit->SetModifyFlag();
+ pEdit->Modify();
+ rbInKeyInput = false;
+ }
+ else
+ pEdit->SetSelection( Selection( nNewPos ) );
+
+ return true;
+ }
+ else if ( nKeyCode == KEY_INSERT )
+ {
+ // you can only set InsertMode for a PatternField if the
+ // mask is equal at all input positions
+ if ( !bSameMask )
+ {
+ return true;
+ }
+ }
+ }
+
+ if ( rKEvt.GetKeyCode().IsMod2() || (cChar < 32) || (cChar == 127) )
+ return false;
+
+ Selection aSel = aOldSel;
+ aSel.Justify();
+ nNewPos = aSel.Min();
+
+ if ( nNewPos < rEditMask.getLength() )
+ {
+ sal_Unicode cPattChar = ImplPatternChar( cChar, rEditMask[nNewPos] );
+ if ( cPattChar )
+ cChar = cPattChar;
+ else
+ {
+ // If no valid character, check if the user wanted to jump to next literal. We do this
+ // only if we're after a character, so that literals that were skipped automatically
+ // do not influence the position anymore.
+ if ( nNewPos &&
+ (rEditMask[nNewPos-1] != EDITMASK_LITERAL) &&
+ !aSel.Len() )
+ {
+ // search for next character not being a literal
+ nTempPos = nNewPos;
+ while ( nTempPos < rEditMask.getLength() )
+ {
+ if ( rEditMask[nTempPos] == EDITMASK_LITERAL )
+ {
+ // only valid if no literal present
+ if ( (rEditMask[nTempPos+1] != EDITMASK_LITERAL ) &&
+ ImplCommaPointCharEqual( cChar, rLiteralMask[nTempPos] ) )
+ {
+ nTempPos++;
+ ImplPatternMaxPos( pEdit->GetText(), rEditMask, nFormatFlags, bSameMask, nNewPos, nTempPos );
+ if ( nTempPos > nNewPos )
+ {
+ pEdit->SetSelection( Selection( nTempPos ) );
+ return true;
+ }
+ }
+ break;
+ }
+ nTempPos++;
+ }
+ }
+
+ cChar = 0;
+ }
+ }
+ else
+ cChar = 0;
+ if ( cChar )
+ {
+ OUStringBuffer aStr = pEdit->GetText();
+ bool bError = false;
+ if ( bSameMask && pEdit->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( std::u16string_view(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
+ OUString aRep = rLiteralMask.copy( aSel.Min(), aSel.Len() );
+ aStr.remove( aSel.Min(), aRep.getLength() );
+ aStr.insert( aSel.Min(), aRep );
+ }
+
+ if ( nNewPos < aStr.getLength() )
+ aStr[nNewPos] = cChar;
+ else if ( nNewPos < rEditMask.getLength() )
+ aStr.append(cChar);
+ }
+
+ if ( !bError )
+ {
+ rbInKeyInput = true;
+ Selection aNewSel( ImplPatternRightPos( aStr.toString(), rEditMask, nFormatFlags, bSameMask, nNewPos ) );
+ pEdit->SetText( aStr.toString(), aNewSel );
+ pEdit->SetModifyFlag();
+ pEdit->Modify();
+ rbInKeyInput = false;
+ }
+ }
+
+ return true;
+}
+
+void PatternFormatter::ImplSetMask(const OString& rEditMask, const OUString& rLiteralMask)
+{
+ m_aEditMask = rEditMask;
+ maLiteralMask = rLiteralMask;
+ mbSameMask = true;
+
+ if ( m_aEditMask.getLength() != maLiteralMask.getLength() )
+ {
+ OUStringBuffer aBuf(maLiteralMask);
+ if (m_aEditMask.getLength() < aBuf.getLength())
+ aBuf.remove(m_aEditMask.getLength(), aBuf.getLength() - m_aEditMask.getLength());
+ else
+ comphelper::string::padToLength(aBuf, m_aEditMask.getLength(), ' ');
+ maLiteralMask = 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) )
+ {
+ mbSameMask = false;
+ break;
+ }
+ if ( i < rLiteralMask.getLength() )
+ {
+ if ( rLiteralMask[i] != ' ' )
+ {
+ mbSameMask = false;
+ break;
+ }
+ }
+ if ( !c )
+ c = cTemp;
+ if ( cTemp != c )
+ {
+ mbSameMask = false;
+ break;
+ }
+ }
+ i++;
+ }
+}
+
+PatternFormatter::PatternFormatter(Edit* pEdit)
+ : FormatterBase(pEdit)
+{
+ mbSameMask = true;
+ mbInPattKeyInput = false;
+}
+
+PatternFormatter::~PatternFormatter()
+{
+}
+
+void PatternFormatter::SetMask( const OString& rEditMask,
+ const OUString& rLiteralMask )
+{
+ ImplSetMask( rEditMask, rLiteralMask );
+ ReformatAll();
+}
+
+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();
+}
+
+bool PatternField::PreNotify( NotifyEvent& rNEvt )
+{
+ if ( (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) && !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() )
+ {
+ if ( ImplPatternProcessKeyInput( GetField(), *rNEvt.GetKeyEvent(), GetEditMask(), GetLiteralMask(),
+ IsStrictFormat(),
+ ImplIsSameMask(), ImplGetInPattKeyInput() ) )
+ return true;
+ }
+
+ return SpinField::PreNotify( rNEvt );
+}
+
+bool PatternField::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() && (!GetText().isEmpty() || !IsEmptyFieldValueEnabled()) )
+ Reformat();
+ }
+
+ return SpinField::EventNotify( rNEvt );
+}
+
+void PatternField::Modify()
+{
+ if ( !ImplGetInPattKeyInput() )
+ {
+ if ( IsStrictFormat() )
+ ImplPatternProcessStrictModify( GetField(), GetEditMask(), GetLiteralMask(), ImplIsSameMask() );
+ else
+ MarkToBeReformatted( true );
+ }
+
+ SpinField::Modify();
+}
+
+PatternBox::PatternBox(vcl::Window* pParent, WinBits nWinStyle)
+ : ComboBox( pParent, nWinStyle )
+ , PatternFormatter(this)
+{
+ Reformat();
+}
+
+void PatternBox::dispose()
+{
+ ClearField();
+ ComboBox::dispose();
+}
+
+bool PatternBox::PreNotify( NotifyEvent& rNEvt )
+{
+ if ( (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) && !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() )
+ {
+ if ( ImplPatternProcessKeyInput( GetField(), *rNEvt.GetKeyEvent(), GetEditMask(), GetLiteralMask(),
+ IsStrictFormat(),
+ ImplIsSameMask(), ImplGetInPattKeyInput() ) )
+ return true;
+ }
+
+ return ComboBox::PreNotify( rNEvt );
+}
+
+bool PatternBox::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() && (!GetText().isEmpty() || !IsEmptyFieldValueEnabled()) )
+ Reformat();
+ }
+
+ return ComboBox::EventNotify( rNEvt );
+}
+
+void PatternBox::Modify()
+{
+ if ( !ImplGetInPattKeyInput() )
+ {
+ if ( IsStrictFormat() )
+ ImplPatternProcessStrictModify( GetField(), GetEditMask(), GetLiteralMask(), ImplIsSameMask() );
+ else
+ MarkToBeReformatted( true );
+ }
+
+ ComboBox::Modify();
+}
+
+void PatternBox::ReformatAll()
+{
+ OUString aStr;
+ SetUpdateMode( false );
+ const sal_Int32 nEntryCount = GetEntryCount();
+ for ( sal_Int32 i=0; i < nEntryCount; ++i )
+ {
+ aStr = ImplPatternReformat( GetEntry( i ), GetEditMask(), GetLiteralMask(), 0/*nFormatFlags*/ );
+ RemoveEntryAt(i);
+ InsertEntry( aStr, i );
+ }
+ PatternFormatter::Reformat();
+ SetUpdateMode( true );
+}
+
+static ExtDateFieldFormat ImplGetExtFormat( DateOrder eOld )
+{
+ switch( eOld )
+ {
+ case DateOrder::DMY: return ExtDateFieldFormat::ShortDDMMYY;
+ case DateOrder::MDY: return ExtDateFieldFormat::ShortMMDDYY;
+ 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 = rStr.copy(i1, i2-i1).toInt32();
+ rStr = rStr.copy(std::min(i2+1, rStr.getLength()));
+ return nValue;
+}
+
+static bool ImplCutMonthName( OUString& rStr, const OUString& _rLookupMonthName )
+{
+ sal_Int32 index = 0;
+ rStr = rStr.replaceFirst(_rLookupMonthName, "", &index);
+ return index >= 0;
+}
+
+static sal_uInt16 ImplCutMonthFromString( OUString& rStr, const CalendarWrapper& rCalendarWrapper )
+{
+ // search for a month' name
+ for ( sal_uInt16 i=1; i <= 12; i++ )
+ {
+ OUString aMonthName = rCalendarWrapper.getMonths()[i-1].FullName;
+ // long month name?
+ if ( ImplCutMonthName( rStr, aMonthName ) )
+ return i;
+
+ // short month name?
+ OUString aAbbrevMonthName = rCalendarWrapper.getMonths()[i-1].AbbrevName;
+ if ( ImplCutMonthName( rStr, aAbbrevMonthName ) )
+ return i;
+ }
+
+ 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]));
+}
+
+static bool ImplDateGetValue( 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 )
+ {
+ DateOrder eFormat = rLocaleDataWrapper.getLongDateOrder();
+ switch( eFormat )
+ {
+ case DateOrder::MDY:
+ nMonth = ImplCutMonthFromString( aStr, rCalendarWrapper );
+ nDay = ImplCutNumberFromString( aStr );
+ nYear = ImplCutNumberFromString( aStr );
+ break;
+ case DateOrder::DMY:
+ nDay = ImplCutNumberFromString( aStr );
+ nMonth = ImplCutMonthFromString( aStr, rCalendarWrapper );
+ nYear = ImplCutNumberFromString( aStr );
+ break;
+ case DateOrder::YMD:
+ default:
+ nYear = ImplCutNumberFromString( aStr );
+ nMonth = ImplCutMonthFromString( aStr, rCalendarWrapper );
+ nDay = ImplCutNumberFromString( aStr );
+ break;
+ }
+ }
+ 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, utl::MiscCfg().GetYear2000() );
+ if ( aNewDate.IsValidDate() )
+ {
+ rDate = aNewDate;
+ return true;
+ }
+ return false;
+}
+
+void DateFormatter::ImplDateReformat( const OUString& rStr, OUString& rOutStr )
+{
+ Date aDate( Date::EMPTY );
+ if ( !ImplDateGetValue( rStr, aDate, GetExtDateFormat(true), ImplGetLocaleDataWrapper(), GetCalendarWrapper() ) )
+ return;
+
+ Date aTempDate = aDate;
+ if ( aTempDate > GetMax() )
+ aTempDate = GetMax();
+ else if ( aTempDate < GetMin() )
+ aTempDate = GetMin();
+
+ rOutStr = ImplGetDateAsText( aTempDate );
+}
+
+OUString DateFormatter::ImplGetDateAsText( const Date& rDate ) const
+{
+ bool bShowCentury = false;
+ switch ( GetExtDateFormat() )
+ {
+ 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 = utl::MiscCfg().GetYear2000();
+ 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;
+
+ OUString aDateSep = ImplGetDateSep( ImplGetLocaleDataWrapper(), GetExtDateFormat( true ) );
+ 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 ( GetExtDateFormat( true ) )
+ {
+ case ExtDateFieldFormat::SystemLong:
+ {
+ return ImplGetLocaleDataWrapper().getLongDate( rDate, GetCalendarWrapper(), !bShowCentury );
+ }
+ 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);
+}
+
+static void ImplDateIncrementDay( Date& rDate, bool bUp )
+{
+ DateFormatter::ExpandCentury( rDate );
+
+ if ( bUp )
+ {
+ if ( (rDate.GetDay() != 31) || (rDate.GetMonth() != 12) || (rDate.GetYear() != SAL_MAX_INT16) )
+ ++rDate;
+ }
+ else
+ {
+ if ( (rDate.GetDay() != 1 ) || (rDate.GetMonth() != 1) || (rDate.GetYear() != SAL_MIN_INT16) )
+ --rDate;
+ }
+}
+
+static void ImplDateIncrementMonth( Date& rDate, bool bUp )
+{
+ DateFormatter::ExpandCentury( rDate );
+
+ sal_uInt16 nMonth = rDate.GetMonth();
+ sal_Int16 nYear = rDate.GetYear();
+ if ( bUp )
+ {
+ if ( (nMonth == 12) && (nYear < SAL_MAX_INT16) )
+ {
+ rDate.SetMonth( 1 );
+ rDate.SetYear( rDate.GetNextYear() );
+ }
+ else
+ {
+ if ( nMonth < 12 )
+ rDate.SetMonth( nMonth + 1 );
+ }
+ }
+ else
+ {
+ if ( (nMonth == 1) && (nYear > SAL_MIN_INT16) )
+ {
+ rDate.SetMonth( 12 );
+ rDate.SetYear( rDate.GetPrevYear() );
+ }
+ else
+ {
+ if ( nMonth > 1 )
+ rDate.SetMonth( nMonth - 1 );
+ }
+ }
+
+ sal_uInt16 nDaysInMonth = Date::GetDaysInMonth( rDate.GetMonth(), rDate.GetYear());
+ if ( rDate.GetDay() > nDaysInMonth )
+ rDate.SetDay( nDaysInMonth );
+}
+
+static void ImplDateIncrementYear( Date& rDate, bool bUp )
+{
+ DateFormatter::ExpandCentury( rDate );
+
+ sal_Int16 nYear = rDate.GetYear();
+ sal_uInt16 nMonth = rDate.GetMonth();
+ if ( bUp )
+ {
+ if ( nYear < SAL_MAX_INT16 )
+ rDate.SetYear( rDate.GetNextYear() );
+ }
+ else
+ {
+ if ( nYear > SAL_MIN_INT16 )
+ rDate.SetYear( rDate.GetPrevYear() );
+ }
+ if (nMonth == 2)
+ {
+ // Handle February 29 from leap year to non-leap year.
+ sal_uInt16 nDay = rDate.GetDay();
+ if (nDay > 28)
+ {
+ // The check would not be necessary if it was guaranteed that the
+ // date was valid before and actually was a leap year,
+ // de-/incrementing a leap year with 29 always results in 28.
+ sal_uInt16 nDaysInMonth = Date::GetDaysInMonth( nMonth, rDate.GetYear());
+ if (nDay > nDaysInMonth)
+ rDate.SetDay( nDaysInMonth);
+ }
+ }
+}
+
+bool DateFormatter::ImplAllowMalformedInput() const
+{
+ return !IsEnforceValidValue();
+}
+
+void DateField::ImplDateSpinArea( bool bUp )
+{
+ // increment days if all is selected
+ if ( GetField() )
+ {
+ Date aDate( GetDate() );
+ Selection aSelection = GetField()->GetSelection();
+ aSelection.Justify();
+ OUString aText( GetText() );
+ if ( static_cast<sal_Int32>(aSelection.Len()) == aText.getLength() )
+ ImplDateIncrementDay( aDate, bUp );
+ else
+ {
+ sal_Int8 nDateArea = 0;
+
+ ExtDateFieldFormat eFormat = GetExtDateFormat( true );
+ if ( eFormat == ExtDateFieldFormat::SystemLong )
+ {
+ eFormat = ImplGetExtFormat( ImplGetLocaleDataWrapper().getLongDateOrder() );
+ nDateArea = 1;
+ }
+ else
+ {
+ // search area
+ sal_Int32 nPos = 0;
+ OUString aDateSep = ImplGetDateSep( ImplGetLocaleDataWrapper(), eFormat );
+ for ( sal_Int8 i = 1; i <= 3; i++ )
+ {
+ nPos = aText.indexOf( aDateSep, nPos );
+ if (nPos < 0 || nPos >= static_cast<sal_Int32>(aSelection.Max()))
+ {
+ nDateArea = i;
+ break;
+ }
+ else
+ nPos++;
+ }
+ }
+
+ 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 <= ExtDateFieldFormat::SystemShortYYYY ) )
+ {
+ bool bShowCentury = (eDateFormat == ExtDateFieldFormat::SystemShortYYYY);
+ switch ( ImplGetLocaleDataWrapper().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;
+}
+
+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();
+}
+
+void DateFormatter::SetShowDateCentury( bool bShowDateCentury )
+{
+ mbShowDateCentury = bShowDateCentury;
+
+ // #91913# Remove LongFormat and DateShowCentury - redundant
+ if ( bShowDateCentury )
+ {
+ switch ( GetExtDateFormat() )
+ {
+ case ExtDateFieldFormat::SystemShort:
+ case ExtDateFieldFormat::SystemShortYY:
+ SetExtDateFormat( ExtDateFieldFormat::SystemShortYYYY ); break;
+ case ExtDateFieldFormat::ShortDDMMYY:
+ SetExtDateFormat( ExtDateFieldFormat::ShortDDMMYYYY ); break;
+ case ExtDateFieldFormat::ShortMMDDYY:
+ SetExtDateFormat( ExtDateFieldFormat::ShortMMDDYYYY ); break;
+ case ExtDateFieldFormat::ShortYYMMDD:
+ SetExtDateFormat( ExtDateFieldFormat::ShortYYYYMMDD ); break;
+ case ExtDateFieldFormat::ShortYYMMDD_DIN5008:
+ SetExtDateFormat( ExtDateFieldFormat::ShortYYYYMMDD_DIN5008 ); break;
+ default:
+ ;
+ }
+ }
+ else
+ {
+ switch ( GetExtDateFormat() )
+ {
+ case ExtDateFieldFormat::SystemShort:
+ case ExtDateFieldFormat::SystemShortYYYY:
+ SetExtDateFormat( ExtDateFieldFormat::SystemShortYY ); break;
+ case ExtDateFieldFormat::ShortDDMMYYYY:
+ SetExtDateFormat( ExtDateFieldFormat::ShortDDMMYY ); break;
+ case ExtDateFieldFormat::ShortMMDDYYYY:
+ SetExtDateFormat( ExtDateFieldFormat::ShortMMDDYY ); break;
+ case ExtDateFieldFormat::ShortYYYYMMDD:
+ SetExtDateFormat( ExtDateFieldFormat::ShortYYMMDD ); break;
+ case ExtDateFieldFormat::ShortYYYYMMDD_DIN5008:
+ SetExtDateFormat( ExtDateFieldFormat::ShortYYMMDD_DIN5008 ); break;
+ default:
+ ;
+ }
+ }
+
+ 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() )
+ {
+ Selection aSelection = GetField()->GetSelection();
+ aSelection.Justify();
+ OUString aText = GetField()->GetText();
+
+ // If selected until the end then keep it that way
+ if ( static_cast<sal_Int32>(aSelection.Max()) == aText.getLength() )
+ {
+ if ( !aSelection.Len() )
+ aSelection.Min() = SELECTION_MAX;
+ aSelection.Max() = SELECTION_MAX;
+ }
+
+ Date aOldLastDate = maLastDate;
+ ImplSetUserDate( rDate, &aSelection );
+ maLastDate = aOldLastDate;
+
+ // Modify at Edit is only set at KeyInput
+ if ( GetField()->GetText() != aText )
+ {
+ GetField()->SetModifyFlag();
+ GetField()->Modify();
+ }
+ }
+}
+
+Date DateFormatter::GetDate() const
+{
+ Date aDate( Date::EMPTY );
+
+ if ( GetField() )
+ {
+ if ( ImplDateGetValue( 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 = !ImplDateGetValue( 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)ImplDateGetValue(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, utl::MiscCfg().GetYear2000() );
+}
+
+void DateFormatter::ExpandCentury( Date& rDate, sal_uInt16 nTwoDigitYearStart )
+{
+ sal_Int16 nDateYear = rDate.GetYear();
+ if ( 0 <= nDateYear && nDateYear < 100 )
+ {
+ sal_uInt16 nCentury = nTwoDigitYearStart / 100;
+ if ( nDateYear < (nTwoDigitYearStart % 100) )
+ nCentury++;
+ rDate.SetYear( nDateYear + (nCentury*100) );
+ }
+}
+
+DateField::DateField( vcl::Window* pParent, WinBits nWinStyle ) :
+ SpinField( pParent, nWinStyle ),
+ DateFormatter(this),
+ maFirst( GetMin() ),
+ maLast( GetMax() )
+{
+ SetText( ImplGetLocaleDataWrapper().getDate( ImplGetFieldDate() ) );
+ Reformat();
+ ResetLastDate();
+}
+
+void DateField::dispose()
+{
+ ClearField();
+ SpinField::dispose();
+}
+
+bool DateField::PreNotify( NotifyEvent& rNEvt )
+{
+ if ( (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) && IsStrictFormat() &&
+ ( GetExtDateFormat() != ExtDateFieldFormat::SystemLong ) &&
+ !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() )
+ {
+ if ( ImplDateProcessKeyInput( *rNEvt.GetKeyEvent(), GetExtDateFormat( true ), ImplGetLocaleDataWrapper() ) )
+ return true;
+ }
+
+ return SpinField::PreNotify( rNEvt );
+}
+
+bool DateField::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() )
+ {
+ // !!! We should find out why dates are treated differently than other fields (see
+ // also bug: 52384)
+
+ bool bTextLen = !GetText().isEmpty();
+ if ( bTextLen || !IsEmptyFieldValueEnabled() )
+ {
+ if ( !ImplAllowMalformedInput() )
+ Reformat();
+ else
+ {
+ Date aDate( 0, 0, 0 );
+ if ( ImplDateGetValue( 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)
+ ImplGetLocaleDataWrapper().setLanguageTag( GetSettings().GetLanguageTag() );
+ ReformatAll();
+ }
+}
+
+void DateField::Modify()
+{
+ MarkToBeReformatted( true );
+ SpinField::Modify();
+}
+
+void DateField::Up()
+{
+ ImplDateSpinArea( true );
+ SpinField::Up();
+}
+
+void DateField::Down()
+{
+ ImplDateSpinArea( false );
+ SpinField::Down();
+}
+
+void DateField::First()
+{
+ ImplNewFieldValue( maFirst );
+ SpinField::First();
+}
+
+void DateField::Last()
+{
+ ImplNewFieldValue( maLast );
+ SpinField::Last();
+}
+
+DateBox::DateBox(vcl::Window* pParent, WinBits nWinStyle)
+ : ComboBox( pParent, nWinStyle )
+ , DateFormatter(this)
+{
+ SetText( ImplGetLocaleDataWrapper().getDate( ImplGetFieldDate() ) );
+ Reformat();
+}
+
+void DateBox::dispose()
+{
+ ClearField();
+ ComboBox::dispose();
+}
+
+bool DateBox::PreNotify( NotifyEvent& rNEvt )
+{
+ if ( (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) && IsStrictFormat() &&
+ ( GetExtDateFormat() != ExtDateFieldFormat::SystemLong ) &&
+ !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() )
+ {
+ if ( ImplDateProcessKeyInput( *rNEvt.GetKeyEvent(), GetExtDateFormat( true ), ImplGetLocaleDataWrapper() ) )
+ return true;
+ }
+
+ return ComboBox::PreNotify( rNEvt );
+}
+
+void DateBox::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ ComboBox::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::LOCALE) )
+ {
+ ImplGetLocaleDataWrapper().setLanguageTag( GetSettings().GetLanguageTag() );
+ ReformatAll();
+ }
+}
+
+bool DateBox::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() )
+ {
+ bool bTextLen = !GetText().isEmpty();
+ if ( bTextLen || !IsEmptyFieldValueEnabled() )
+ Reformat();
+ else
+ {
+ ResetLastDate();
+ SetEmptyFieldValueData( true );
+ }
+ }
+ }
+
+ return ComboBox::EventNotify( rNEvt );
+}
+
+void DateBox::Modify()
+{
+ MarkToBeReformatted( true );
+ ComboBox::Modify();
+}
+
+void DateBox::ReformatAll()
+{
+ OUString aStr;
+ SetUpdateMode( false );
+ const sal_Int32 nEntryCount = GetEntryCount();
+ for ( sal_Int32 i=0; i < nEntryCount; ++i )
+ {
+ ImplDateReformat( GetEntry( i ), aStr );
+ RemoveEntryAt(i);
+ InsertEntry( aStr, i );
+ }
+ DateFormatter::Reformat();
+ SetUpdateMode( true );
+}
+
+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')) ||
+ string::equals(rLocaleDataWrapper.getTimeSep(), 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) && string::equals(rLocaleDataWrapper.getTime100SecSep(), cChar)) ||
+ (bDuration && (cChar == '-')) )
+ return false;
+ else
+ return true;
+ }
+}
+
+static bool ImplIsOnlyDigits( const OUStringBuffer& _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 OUStringBuffer& _rStr )
+{
+ if ( !_bSkipInvalidCharacters )
+ {
+ if ( ( _rStr.getLength() > 2 ) || _rStr.isEmpty() || !ImplIsOnlyDigits( _rStr ) )
+ return false;
+ }
+ return true;
+}
+
+static bool ImplCutTimePortion( OUStringBuffer& _rStr, sal_Int32 _nSepPos, bool _bSkipInvalidCharacters, short* _pPortion )
+{
+ OUString sPortion(_rStr.getStr(), _nSepPos );
+
+ if (_nSepPos < _rStr.getLength())
+ _rStr.remove(0, _nSepPos + 1);
+ else
+ _rStr.truncate();
+
+ if ( !ImplIsValidTimePortion( _bSkipInvalidCharacters, sPortion ) )
+ return false;
+ *_pPortion = static_cast<short>(sPortion.toInt32());
+ return true;
+}
+
+bool TimeFormatter::TextToTime(const OUString& 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.isEmpty() )
+ 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 (string::equals(rLocaleDataWrapper.getTimeSep(), 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 = aStr.toString().toInt64();
+ }
+ else
+ nSecond = static_cast<short>(aStr.toString().toInt32());
+ }
+ else
+ nMinute = static_cast<short>(aStr.toString().toInt32());
+ }
+ else if ( nSepPos < 0 )
+ {
+ nSecond = static_cast<short>(aStr.toString().toInt32());
+ nMinute += nSecond / 60;
+ nSecond %= 60;
+ nHour += nMinute / 60;
+ nMinute %= 60;
+ }
+ else
+ {
+ nSecond = static_cast<short>(aStr.copy( 0, nSepPos ).makeStringAndClear().toInt32());
+ 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>(aStr.copy( 0, nSepPos ).makeStringAndClear().toInt32());
+ 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>(aStr.copy( 0, nSepPos ).makeStringAndClear().toInt32());
+ aStr.remove( 0, nSepPos+1 );
+ }
+ else
+ {
+ nHour += nMinute / 60;
+ nMinute %= 60;
+ }
+ }
+ else
+ {
+ nMinute += nSecond / 60;
+ nSecond %= 60;
+ nHour += nMinute / 60;
+ nMinute %= 60;
+ }
+ nNanoSec = aStr.toString().toInt64();
+ }
+
+ 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( const OUString& rStr, OUString& rOutStr )
+{
+ tools::Time aTime( 0, 0, 0 );
+ if ( !TextToTime( rStr, aTime, GetFormat(), IsDuration(), ImplGetLocaleDataWrapper() ) )
+ return;
+
+ tools::Time aTempTime = aTime;
+ if ( aTempTime > GetMax() )
+ aTempTime = GetMax() ;
+ else if ( aTempTime < GetMin() )
+ aTempTime = GetMin();
+
+ bool bSecond = false;
+ bool b100Sec = false;
+ if ( meFormat != TimeFieldFormat::F_NONE )
+ bSecond = true;
+
+ if ( meFormat == TimeFieldFormat::F_SEC_CS )
+ {
+ sal_uLong n = aTempTime.GetHour() * 3600L;
+ n += aTempTime.GetMin() * 60L;
+ n += aTempTime.GetSec();
+ rOutStr = OUString::number( n );
+ rOutStr += ImplGetLocaleDataWrapper().getTime100SecSep();
+ std::ostringstream ostr;
+ ostr.fill('0');
+ ostr.width(9);
+ ostr << aTempTime.GetNanoSec();
+ rOutStr += OUString::createFromAscii(ostr.str().c_str());
+ }
+ else if ( mbDuration )
+ rOutStr = ImplGetLocaleDataWrapper().getDuration( aTempTime, bSecond, b100Sec );
+ else
+ {
+ rOutStr = ImplGetLocaleDataWrapper().getTime( aTempTime, bSecond, b100Sec );
+ if ( GetTimeFormat() == TimeFormat::Hour12 )
+ {
+ if ( aTempTime.GetHour() > 12 )
+ {
+ tools::Time aT( aTempTime );
+ aT.SetHour( aT.GetHour() % 12 );
+ rOutStr = ImplGetLocaleDataWrapper().getTime( aT, bSecond, b100Sec );
+ }
+ // Don't use LocaleDataWrapper, we want AM/PM
+ if ( aTempTime.GetHour() < 12 )
+ rOutStr += "AM"; // ImplGetLocaleDataWrapper().getTimeAM();
+ else
+ rOutStr += "PM"; // ImplGetLocaleDataWrapper().getTimePM();
+ }
+ }
+}
+
+bool TimeFormatter::ImplAllowMalformedInput() const
+{
+ return !IsEnforceValidValue();
+}
+
+int TimeFormatter::GetTimeArea(TimeFieldFormat eFormat, const OUString& 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 ( sal_Int32 i = 1, nPos = 0; i <= 4; i++ )
+ {
+ sal_Int32 nPos1 = rText.indexOf(rLocaleDataWrapper.getTimeSep(), nPos);
+ sal_Int32 nPos2 = rText.indexOf(rLocaleDataWrapper.getTime100SecSep(), nPos);
+ //which ever comes first, bearing in mind that one might not be there
+ if (nPos1 >= 0 && nPos2 >= 0)
+ nPos = std::min(nPos1, nPos2);
+ else if (nPos1 >= 0)
+ nPos = nPos1;
+ else
+ nPos = nPos2;
+ if (nPos < 0 || nPos >= nCursor)
+ {
+ nTimeArea = i;
+ break;
+ }
+ else
+ nPos++;
+ }
+ }
+ else
+ {
+ sal_Int32 nPos = rText.indexOf(rLocaleDataWrapper.getTime100SecSep());
+ if (nPos < 0 || nPos >= nCursor)
+ nTimeArea = 3;
+ else
+ nTimeArea = 4;
+ }
+
+ return nTimeArea;
+}
+
+tools::Time TimeFormatter::SpinTime(bool bUp, const tools::Time& rTime, TimeFieldFormat eFormat,
+ bool bDuration, const OUString& 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() )
+ {
+ Selection aSelection = GetField()->GetSelection();
+ aSelection.Justify();
+ OUString aText = GetField()->GetText();
+
+ // If selected until the end then keep it that way
+ if ( static_cast<sal_Int32>(aSelection.Max()) == aText.getLength() )
+ {
+ if ( !aSelection.Len() )
+ aSelection.Min() = SELECTION_MAX;
+ aSelection.Max() = SELECTION_MAX;
+ }
+
+ tools::Time aOldLastTime = maLastTime;
+ ImplSetUserTime( rTime, &aSelection );
+ maLastTime = aOldLastTime;
+
+ // Modify at Edit is only set at KeyInput
+ if ( GetField()->GetText() != aText )
+ {
+ GetField()->SetModifyFlag();
+ GetField()->Modify();
+ }
+ }
+}
+
+OUString TimeFormatter::FormatTime(const tools::Time& rNewTime, TimeFieldFormat eFormat, TimeFormat eHourFormat, bool bDuration, const LocaleDataWrapper& rLocaleData)
+{
+ OUString aStr;
+ bool bSec = false;
+ bool b100Sec = false;
+ if ( eFormat != TimeFieldFormat::F_NONE )
+ bSec = true;
+ if ( eFormat == TimeFieldFormat::F_SEC_CS )
+ b100Sec = true;
+ if ( eFormat == TimeFieldFormat::F_SEC_CS )
+ {
+ sal_uLong n = rNewTime.GetHour() * 3600L;
+ n += rNewTime.GetMin() * 60L;
+ n += rNewTime.GetSec();
+ aStr = OUString::number( n ) + rLocaleData.getTime100SecSep();
+ std::ostringstream ostr;
+ ostr.fill('0');
+ ostr.width(9);
+ ostr << rNewTime.GetNanoSec();
+ aStr += OUString::createFromAscii(ostr.str().c_str());
+ }
+ else if ( bDuration )
+ {
+ aStr = rLocaleData.getDuration( rNewTime, bSec, b100Sec );
+ }
+ else
+ {
+ aStr = rLocaleData.getTime( rNewTime, bSec, b100Sec );
+ if ( eHourFormat == TimeFormat::Hour12 )
+ {
+ if ( rNewTime.GetHour() > 12 )
+ {
+ tools::Time aT( rNewTime );
+ aT.SetHour( aT.GetHour() % 12 );
+ aStr = rLocaleData.getTime( aT, bSec, b100Sec );
+ }
+ // Don't use LocaleDataWrapper, we want AM/PM
+ if ( rNewTime.GetHour() < 12 )
+ aStr += "AM"; // rLocaleData.getTimeAM();
+ else
+ aStr += "PM"; // rLocaleData.getTimePM();
+ }
+ }
+
+ return aStr;
+}
+
+void TimeFormatter::ImplSetUserTime( const tools::Time& rNewTime, Selection const * pNewSelection )
+{
+ tools::Time aNewTime = rNewTime;
+ if ( aNewTime > GetMax() )
+ aNewTime = GetMax();
+ else if ( aNewTime < GetMin() )
+ aNewTime = GetMin();
+ maLastTime = aNewTime;
+
+ if ( GetField() )
+ {
+ OUString aStr = TimeFormatter::FormatTime(aNewTime, meFormat, GetTimeFormat(), mbDuration, ImplGetLocaleDataWrapper());
+ ImplSetText( aStr, pNewSelection );
+ }
+}
+
+void TimeFormatter::SetUserTime( const tools::Time& rNewTime )
+{
+ ImplSetUserTime( rNewTime );
+}
+
+tools::Time TimeFormatter::GetTime() const
+{
+ tools::Time aTime( 0, 0, 0 );
+
+ if ( GetField() )
+ {
+ bool bAllowMailformed = ImplAllowMalformedInput();
+ if ( TextToTime( GetField()->GetText(), aTime, GetFormat(), IsDuration(), ImplGetLocaleDataWrapper(), !bAllowMailformed ) )
+ {
+ if ( aTime > GetMax() )
+ aTime = GetMax();
+ else if ( aTime < GetMin() )
+ aTime = GetMin();
+ }
+ else
+ {
+ if ( bAllowMailformed )
+ aTime = tools::Time( 99, 99, 99 ); // set invalid time
+ else
+ aTime = maLastTime;
+ }
+ }
+
+ return aTime;
+}
+
+void TimeFormatter::Reformat()
+{
+ if ( !GetField() )
+ return;
+
+ if ( GetField()->GetText().isEmpty() && ImplGetEmptyFieldValue() )
+ return;
+
+ OUString aStr;
+ ImplTimeReformat( GetField()->GetText(), aStr );
+
+ if ( !aStr.isEmpty() )
+ {
+ ImplSetText( aStr );
+ (void)TextToTime(aStr, maLastTime, GetFormat(), IsDuration(), ImplGetLocaleDataWrapper());
+ }
+ else
+ SetTime( maLastTime );
+}
+
+TimeField::TimeField( vcl::Window* pParent, WinBits nWinStyle ) :
+ SpinField( pParent, nWinStyle ),
+ TimeFormatter(this),
+ maFirst( GetMin() ),
+ maLast( GetMax() )
+{
+ SetText( ImplGetLocaleDataWrapper().getTime( maFieldTime, false ) );
+ Reformat();
+}
+
+void TimeField::dispose()
+{
+ ClearField();
+ SpinField::dispose();
+}
+
+bool TimeField::PreNotify( NotifyEvent& rNEvt )
+{
+ if ( (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) && !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() )
+ {
+ if ( ImplTimeProcessKeyInput( *rNEvt.GetKeyEvent(), IsStrictFormat(), IsDuration(), GetFormat(), ImplGetLocaleDataWrapper() ) )
+ return true;
+ }
+
+ return SpinField::PreNotify( rNEvt );
+}
+
+bool TimeField::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() && (!GetText().isEmpty() || !IsEmptyFieldValueEnabled()) )
+ {
+ if ( !ImplAllowMalformedInput() )
+ Reformat();
+ else
+ {
+ tools::Time aTime( 0, 0, 0 );
+ if ( TextToTime( GetText(), aTime, GetFormat(), IsDuration(), ImplGetLocaleDataWrapper(), false ) )
+ // even with strict text analysis, our text is a valid time -> do a complete
+ // reformat
+ Reformat();
+ }
+ }
+ }
+
+ return SpinField::EventNotify( rNEvt );
+}
+
+void TimeField::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ SpinField::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::LOCALE) )
+ {
+ ImplGetLocaleDataWrapper().setLanguageTag( GetSettings().GetLanguageTag() );
+ ReformatAll();
+ }
+}
+
+void TimeField::Modify()
+{
+ MarkToBeReformatted( true );
+ SpinField::Modify();
+}
+
+void TimeField::Up()
+{
+ ImplTimeSpinArea( true );
+ SpinField::Up();
+}
+
+void TimeField::Down()
+{
+ ImplTimeSpinArea( false );
+ SpinField::Down();
+}
+
+void TimeField::First()
+{
+ ImplNewFieldValue( maFirst );
+ SpinField::First();
+}
+
+void TimeField::Last()
+{
+ ImplNewFieldValue( maLast );
+ SpinField::Last();
+}
+
+void TimeField::SetExtFormat( ExtTimeFieldFormat eFormat )
+{
+ switch ( eFormat )
+ {
+ case ExtTimeFieldFormat::Short24H:
+ {
+ SetTimeFormat( TimeFormat::Hour24 );
+ SetDuration( false );
+ SetFormat( TimeFieldFormat::F_NONE );
+ }
+ break;
+ case ExtTimeFieldFormat::Long24H:
+ {
+ SetTimeFormat( TimeFormat::Hour24 );
+ SetDuration( false );
+ SetFormat( TimeFieldFormat::F_SEC );
+ }
+ break;
+ case ExtTimeFieldFormat::Short12H:
+ {
+ SetTimeFormat( TimeFormat::Hour12 );
+ SetDuration( false );
+ SetFormat( TimeFieldFormat::F_NONE );
+ }
+ break;
+ case ExtTimeFieldFormat::Long12H:
+ {
+ SetTimeFormat( TimeFormat::Hour12 );
+ SetDuration( false );
+ SetFormat( TimeFieldFormat::F_SEC );
+ }
+ break;
+ case ExtTimeFieldFormat::ShortDuration:
+ {
+ SetDuration( true );
+ SetFormat( TimeFieldFormat::F_NONE );
+ }
+ break;
+ case ExtTimeFieldFormat::LongDuration:
+ {
+ SetDuration( true );
+ SetFormat( TimeFieldFormat::F_SEC );
+ }
+ break;
+ default: OSL_FAIL( "ExtTimeFieldFormat unknown!" );
+ }
+
+ if ( GetField() && !GetField()->GetText().isEmpty() )
+ SetUserTime( GetTime() );
+ ReformatAll();
+}
+
+TimeBox::TimeBox(vcl::Window* pParent, WinBits nWinStyle)
+ : ComboBox(pParent, nWinStyle)
+ , TimeFormatter(this)
+{
+ SetText( ImplGetLocaleDataWrapper().getTime( maFieldTime, false ) );
+ Reformat();
+}
+
+void TimeBox::dispose()
+{
+ ClearField();
+ ComboBox::dispose();
+}
+
+bool TimeBox::PreNotify( NotifyEvent& rNEvt )
+{
+ if ( (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) && !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() )
+ {
+ if ( ImplTimeProcessKeyInput( *rNEvt.GetKeyEvent(), IsStrictFormat(), IsDuration(), GetFormat(), ImplGetLocaleDataWrapper() ) )
+ return true;
+ }
+
+ return ComboBox::PreNotify( rNEvt );
+}
+
+bool TimeBox::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() && (!GetText().isEmpty() || !IsEmptyFieldValueEnabled()) )
+ Reformat();
+ }
+
+ return ComboBox::EventNotify( rNEvt );
+}
+
+void TimeBox::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ ComboBox::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::LOCALE) )
+ {
+ ImplGetLocaleDataWrapper().setLanguageTag( GetSettings().GetLanguageTag() );
+ 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 );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/fixed.cxx b/vcl/source/control/fixed.cxx
new file mode 100644
index 000000000..4a3b7c10d
--- /dev/null
+++ b/vcl/source/control/fixed.cxx
@@ -0,0 +1,972 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/decoview.hxx>
+#include <vcl/event.hxx>
+#include <vcl/fixed.hxx>
+#include <vcl/settings.hxx>
+
+#include <comphelper/string.hxx>
+#include <sal/log.hxx>
+#include <controldata.hxx>
+
+#define FIXEDLINE_TEXT_BORDER 4
+
+static 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;
+static constexpr auto FIXEDLINE_VIEW_STYLE = WB_3DLOOK | WB_NOLABEL;
+static constexpr auto FIXEDBITMAP_VIEW_STYLE = WB_3DLOOK |
+ WB_LEFT | WB_CENTER | WB_RIGHT |
+ WB_TOP | WB_VCENTER | WB_BOTTOM |
+ WB_SCALE;
+static 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 )
+{
+ long nX;
+ 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(*this);
+}
+
+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_HYPHENATION ) == WB_HYPHENATION )
+ nTextStyle |= DrawTextFlags::WordBreakHyphenation;
+ }
+ if ( nWinStyle & WB_NOLABEL )
+ nTextStyle &= ~DrawTextFlags::Mnemonic;
+
+ return nTextStyle;
+}
+
+void FixedText::ImplDraw(OutputDevice* pDev, DrawFlags nDrawFlags,
+ 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 ( (nDrawFlags & DrawFlags::Mono) ||
+ (rStyleSettings.GetOptions() & StyleSettingsOptions::Mono) )
+ nTextStyle |= DrawTextFlags::Mono;
+
+ if( bFillLayout )
+ mpControlData->mpLayoutData->m_aDisplayText.clear();
+
+ const tools::Rectangle aRect(aPos, rSize);
+ DrawControlText(*pDev, aRect, aText, nTextStyle,
+ bFillLayout ? &mpControlData->mpLayoutData->m_aUnicodeBoundRects : nullptr,
+ bFillLayout ? &mpControlData->mpLayoutData->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, DrawFlags::NONE, Point(), GetOutputSizePixel());
+}
+
+void FixedText::Draw( OutputDevice* pDev, const Point& rPos,
+ DrawFlags 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 & DrawFlags::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(*this);
+ Invalidate();
+ }
+ }
+ else if ( (nType == StateChangedType::Zoom) ||
+ (nType == StateChangedType::ControlFont) )
+ {
+ ApplySettings(*this);
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ ApplySettings(*this);
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ApplySettings(*this);
+ 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(*this);
+ Invalidate();
+ }
+}
+
+Size FixedText::getTextDimensions(Control const *pControl, const OUString &rTxt, 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, 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( long nMaxWidth ) const
+{
+ return CalcWindowSize( CalcMinimumTextSize ( this, nMaxWidth ) );
+}
+
+Size FixedText::GetOptimalSize() const
+{
+ sal_Int32 nMaxAvailWidth = 0x7fffffff;
+ if (m_nMaxWidthChars != -1)
+ {
+ OUStringBuffer aBuf;
+ comphelper::string::padToLength(aBuf, m_nMaxWidthChars, 'x');
+ nMaxAvailWidth = getTextDimensions(this,
+ aBuf.makeStringAndClear(), 0x7fffffff).Width();
+ }
+ Size aRet = CalcMinimumSize(nMaxAvailWidth);
+ if (m_nMinWidthChars != -1)
+ {
+ OUStringBuffer aBuf;
+ 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
+{
+ mpControlData->mpLayoutData.reset( new vcl::ControlLayoutData );
+ ImplDraw(const_cast<FixedText*>(this), DrawFlags::NONE, Point(), GetOutputSizePixel(), true);
+ //const_cast<FixedText*>(this)->Invalidate();
+}
+
+void FixedText::setMaxWidthChars(sal_Int32 nWidth)
+{
+ if (nWidth != m_nMaxWidthChars)
+ {
+ m_nMaxWidthChars = nWidth;
+ queue_resize();
+ }
+}
+
+void FixedText::setMinWidthChars(sal_Int32 nWidth)
+{
+ if (nWidth != m_nMinWidthChars)
+ {
+ m_nMinWidthChars = nWidth;
+ queue_resize();
+ }
+}
+
+bool FixedText::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "max-width-chars")
+ setMaxWidthChars(rValue.toInt32());
+ else if (rKey == "width-chars")
+ setMinWidthChars(rValue.toInt32());
+ else if (rKey == "ellipsize")
+ {
+ WinBits nBits = GetStyle();
+ nBits &= ~WB_PATHELLIPSIS;
+ if (rValue != "none")
+ {
+ SAL_WARN_IF(rValue != "end", "vcl.layout", "Only endellipsis support for now");
+ nBits |= WB_PATHELLIPSIS;
+ }
+ SetStyle(nBits);
+ }
+ else
+ return Control::set_property(rKey, rValue);
+ return true;
+}
+
+vcl::Window* FixedText::getAccessibleRelationLabelFor() const
+{
+ vcl::Window *pWindow = Control::getAccessibleRelationLabelFor();
+ if (pWindow)
+ return pWindow;
+ return get_mnemonic_widget();
+}
+
+void FixedText::set_mnemonic_widget(vcl::Window *pWindow)
+{
+ if (pWindow == m_pMnemonicWindow)
+ return;
+ if (m_pMnemonicWindow)
+ {
+ vcl::Window *pTempReEntryGuard = m_pMnemonicWindow;
+ m_pMnemonicWindow = nullptr;
+ pTempReEntryGuard->remove_mnemonic_label(this);
+ }
+ m_pMnemonicWindow = pWindow;
+ if (m_pMnemonicWindow)
+ m_pMnemonicWindow->add_mnemonic_label(this);
+}
+
+FixedText::~FixedText()
+{
+ disposeOnce();
+}
+
+void FixedText::dispose()
+{
+ set_mnemonic_widget(nullptr);
+ m_pMnemonicWindow.clear();
+ Control::dispose();
+}
+
+SelectableFixedText::SelectableFixedText(vcl::Window* pParent, WinBits nStyle)
+ : Edit(pParent, nStyle)
+{
+ // no border
+ SetBorderStyle( WindowBorderStyle::NOBORDER );
+ // read-only
+ SetReadOnly();
+ // make it transparent
+ SetPaintTransparent(true);
+ SetControlBackground();
+}
+
+void SelectableFixedText::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ rRenderContext.SetBackground();
+}
+
+void SelectableFixedText::LoseFocus()
+{
+ Edit::LoseFocus();
+ // clear cursor
+ Invalidate();
+}
+
+void FixedLine::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ nStyle = ImplInitStyle( nStyle );
+ Control::ImplInit( pParent, nStyle, nullptr );
+ ApplySettings(*this);
+}
+
+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)
+ {
+ long nX = (aOutSize.Width() - 1) / 2;
+ aDecoView.DrawSeparator(Point(nX, 0), Point(nX, aOutSize.Height() - 1));
+ }
+ else
+ {
+ long nY = (aOutSize.Height() - 1) / 2;
+ aDecoView.DrawSeparator(Point(0, nY), Point(aOutSize.Width() - 1, nY), false);
+ }
+ }
+ else if (nWinStyle & WB_VERT)
+ {
+ long nWidth = rRenderContext.GetTextWidth(aText);
+ rRenderContext.Push(PushFlags::FONT);
+ vcl::Font aFont(rRenderContext.GetFont());
+ aFont.SetOrientation(900);
+ 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(*this, aRect, aText, nStyle, nullptr, nullptr);
+
+ 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
+{
+ mpControlData->mpLayoutData.reset( new vcl::ControlLayoutData );
+ 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&, DrawFlags )
+{
+}
+
+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(*this);
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ ApplySettings(*this);
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ApplySettings(*this);
+ 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(*this);
+ Invalidate();
+ }
+}
+
+Size FixedLine::GetOptimalSize() const
+{
+ return CalcWindowSize( FixedText::CalcMinimumTextSize ( this ) );
+}
+
+void FixedBitmap::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ nStyle = ImplInitStyle( nStyle );
+ Control::ImplInit( pParent, nStyle, nullptr );
+ ApplySettings(*this);
+}
+
+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 )
+ {
+ 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,
+ DrawFlags )
+{
+ 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(*this);
+ Invalidate();
+ }
+}
+
+void FixedBitmap::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Control::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) )
+ {
+ ApplySettings(*this);
+ 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(*this);
+}
+
+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,
+ DrawFlags )
+{
+ 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(*this);
+ Invalidate();
+ }
+}
+
+void FixedImage::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Control::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) )
+ {
+ ApplySettings(*this);
+ Invalidate();
+ }
+}
+
+void FixedImage::SetImage( const Image& rImage )
+{
+ if ( rImage != maImage )
+ {
+ maImage = rImage;
+ CompatStateChanged( StateChangedType::Data );
+ queue_resize();
+ }
+}
+
+void FixedImage::SetModeImage( const Image& rImage )
+{
+ SetImage( rImage );
+}
+
+Image FixedImage::loadThemeImage(const OUString &rFileName)
+{
+ return Image(StockImage::Yes, rFileName);
+}
+
+bool FixedImage::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "pixbuf" || rKey == "icon-name")
+ {
+ SetImage(loadThemeImage(rValue));
+ }
+ else 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;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/fixedhyper.cxx b/vcl/source/control/fixedhyper.cxx
new file mode 100644
index 000000000..b7f3500fc
--- /dev/null
+++ b/vcl/source/control/fixedhyper.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 <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 = GetCtrlTextWidth( GetText() );
+
+ SetClickHdl(LINK(this, FixedHyperlink, HandleClick));
+}
+
+bool FixedHyperlink::ImplIsOverText(Point aPosition)
+{
+ Size aSize = GetOutputSizePixel();
+
+ bool bIsOver = false;
+
+ if (GetStyle() & WB_RIGHT)
+ {
+ return aPosition.X() > (aSize.Width() - m_nTextLen);
+ }
+ else if (GetStyle() & WB_CENTER)
+ {
+ bIsOver = aPosition.X() > (aSize.Width() / 2 - m_nTextLen / 2) &&
+ aPosition.X() < (aSize.Width() / 2 + m_nTextLen / 2);
+ }
+ else
+ {
+ bIsOver = aPosition.X() < m_nTextLen;
+ }
+
+ return bIsOver;
+}
+
+void FixedHyperlink::MouseMove( const MouseEvent& rMEvt )
+{
+ // changes the pointer if the control is enabled and the mouse is over the text.
+ if ( !rMEvt.IsLeaveWindow() && IsEnabled() && ImplIsOverText(GetPointerPosPixel()) )
+ SetPointer( PointerStyle::RefHand );
+ else
+ SetPointer( m_aOldPointer );
+}
+
+void FixedHyperlink::MouseButtonUp( const MouseEvent& )
+{
+ // calls the link if the control is enabled and the mouse is over the text.
+ if ( IsEnabled() && ImplIsOverText(GetPointerPosPixel()) )
+ ImplCallEventListenersAndHandler( VclEventId::ButtonClick, [this] () { m_aClickHdl.Call(*this); } );
+}
+
+void FixedHyperlink::RequestHelp( const HelpEvent& rHEvt )
+{
+ if ( IsEnabled() && ImplIsOverText(GetPointerPosPixel()) )
+ FixedText::RequestHelp( rHEvt );
+}
+
+void FixedHyperlink::GetFocus()
+{
+ Size aSize = GetSizePixel();
+ tools::Rectangle aFocusRect(Point(1, 1), Size(m_nTextLen + 4, aSize.Height() - 2));
+ if (GetStyle() & WB_RIGHT)
+ aFocusRect.Move(aSize.Width() - aFocusRect.getWidth(), 0);
+ else if (GetStyle() & WB_CENTER)
+ aFocusRect.Move((aSize.Width() - aFocusRect.getWidth()) / 2, 0);
+
+ Invalidate(aFocusRect);
+ ShowFocus(aFocusRect);
+}
+
+void FixedHyperlink::LoseFocus()
+{
+ SetTextColor( GetControlForeground() );
+ Invalidate(tools::Rectangle(Point(), GetSizePixel()));
+ HideFocus();
+}
+
+void FixedHyperlink::KeyInput( const KeyEvent& rKEvt )
+{
+ switch ( rKEvt.GetKeyCode().GetCode() )
+ {
+ case KEY_SPACE:
+ case KEY_RETURN:
+ m_aClickHdl.Call( *this );
+ break;
+
+ default:
+ FixedText::KeyInput( rKEvt );
+ }
+}
+
+void FixedHyperlink::SetURL( const OUString& rNewURL )
+{
+ m_sURL = rNewURL;
+ SetQuickHelpText( m_sURL );
+}
+
+
+void FixedHyperlink::SetText(const OUString& rNewDescription)
+{
+ FixedText::SetText(rNewDescription);
+ m_nTextLen = GetCtrlTextWidth(GetText());
+}
+
+bool FixedHyperlink::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "uri")
+ SetURL(rValue);
+ else
+ return FixedText::set_property(rKey, rValue);
+ return true;
+}
+
+IMPL_LINK(FixedHyperlink, HandleClick, FixedHyperlink&, rHyperlink, void)
+{
+ if ( rHyperlink.m_sURL.isEmpty() ) // Nothing to do, when the URL is empty
+ return;
+
+ try
+ {
+ uno::Reference< system::XSystemShellExecute > xSystemShellExecute(
+ system::SystemShellExecute::create(comphelper::getProcessComponentContext()));
+ //throws css::lang::IllegalArgumentException, css::system::SystemShellExecuteException
+ xSystemShellExecute->execute( rHyperlink.m_sURL, OUString(), system::SystemShellExecuteFlags::URIS_ONLY );
+ }
+ catch ( const uno::Exception& )
+ {
+ uno::Any exc(cppu::getCaughtException());
+ OUString msg(comphelper::anyToString(exc));
+ SolarMutexGuard g;
+ std::unique_ptr<weld::MessageDialog> xErrorBox(Application::CreateMessageDialog(GetFrameWeld(), VclMessageType::Error, VclButtonsType::Ok, msg));
+ xErrorBox->set_title(rHyperlink.GetText());
+ xErrorBox->run();
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/fmtfield.cxx b/vcl/source/control/fmtfield.cxx
new file mode 100644
index 000000000..87ff8b7b6
--- /dev/null
+++ b/vcl/source/control/fmtfield.cxx
@@ -0,0 +1,1291 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/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 <map>
+#include <rtl/math.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+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
+{
+ namespace {
+
+ // the states of our automat.
+ enum State
+ {
+ START, // at the very start of the string
+ NUM_START, // the very start of the number
+
+ DIGIT_PRE_COMMA, // some pre-comma digits are read, perhaps including some thousand separators
+
+ DIGIT_POST_COMMA, // reading digits after the comma
+ EXPONENT_START, // at the very start of the exponent value
+ // (means: not including the "e" which denotes the exponent)
+ EXPONENT_DIGIT, // currently reading the digits of the exponent
+
+ END // reached the end of the string
+ };
+
+ }
+
+ // a row in the transition table (means the set of states to be reached from a given state)
+ typedef ::std::map< sal_Unicode, State > StateTransitions;
+
+ // a single transition
+ typedef StateTransitions::value_type Transition;
+
+ // the complete transition table
+ typedef ::std::map< State, StateTransitions > TransitionTable;
+
+ // the validator class
+ class NumberValidator
+ {
+ private:
+ TransitionTable m_aTransitions;
+
+ public:
+ NumberValidator( const sal_Unicode _cThSep, const sal_Unicode _cDecSep );
+
+ bool isValidNumericFragment( const OUString& _rText );
+
+ private:
+ bool implValidateNormalized( const OUString& _rText );
+ };
+
+ 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( const OUString& _rText )
+ {
+ if ( _rText.isEmpty() )
+ // empty strings are always allowed
+ return true;
+
+ // normalize the string
+ OUString sNormalized = "_" + _rText + "_";
+
+ return implValidateNormalized( sNormalized );
+ }
+}
+
+SvNumberFormatter* FormattedField::StaticFormatter::s_cFormatter = nullptr;
+sal_uLong FormattedField::StaticFormatter::s_nReferences = 0;
+
+SvNumberFormatter* FormattedField::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;
+}
+
+FormattedField::StaticFormatter::StaticFormatter()
+{
+ ++s_nReferences;
+}
+
+FormattedField::StaticFormatter::~StaticFormatter()
+{
+ if (--s_nReferences == 0)
+ {
+ delete s_cFormatter;
+ s_cFormatter = nullptr;
+ }
+}
+
+FormattedField::FormattedField(vcl::Window* pParent, WinBits nStyle)
+ :SpinField(pParent, nStyle, WindowType::FORMATTEDFIELD)
+ ,m_aLastSelection(0,0)
+ ,m_dMinValue(0)
+ ,m_dMaxValue(0)
+ ,m_bHasMin(false)
+ ,m_bHasMax(false)
+ ,m_bWrapOnLimits(false)
+ ,m_bStrictFormat(true)
+ ,m_bEnableEmptyField(true)
+ ,m_bAutoColor(false)
+ ,m_bEnableNaN(false)
+ ,m_bDisableRemainderFactor(false)
+ ,m_ValueState(valueDirty)
+ ,m_dCurrentValue(0)
+ ,m_dDefaultValue(0)
+ ,m_nFormatKey(0)
+ ,m_pFormatter(nullptr)
+ ,m_dSpinSize(1)
+ ,m_dSpinFirst(-1000000)
+ ,m_dSpinLast(1000000)
+ ,m_bTreatAsNumber(true)
+ ,m_pLastOutputColor(nullptr)
+ ,m_bUseInputStringForFormatting(false)
+{
+}
+
+void FormattedField::SetText(const OUString& rStr)
+{
+
+ SpinField::SetText(rStr);
+ m_ValueState = valueDirty;
+}
+
+void FormattedField::SetText( const OUString& rStr, const Selection& rNewSelection )
+{
+
+ SpinField::SetText( rStr, rNewSelection );
+ m_ValueState = valueDirty;
+}
+
+void FormattedField::SetTextFormatted(const OUString& rStr)
+{
+ SAL_INFO_IF(ImplGetFormatter()->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() &&
+ ImplGetFormatter()->IsNumberFormat(m_sCurrentTextValue, nTempFormatKey, dNumber) )
+ {
+ ImplGetFormatter()->GetInputLineString(dNumber, m_nFormatKey, sFormatted);
+ }
+ else
+ {
+ ImplGetFormatter()->GetOutputString(m_sCurrentTextValue,
+ m_nFormatKey,
+ sFormatted,
+ &m_pLastOutputColor);
+ }
+
+ // calculate the new selection
+ Selection aSel(GetSelection());
+ Selection aNewSel(aSel);
+ aNewSel.Justify();
+ sal_Int32 nNewLen = sFormatted.getLength();
+ sal_Int32 nCurrentLen = GetText().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 = GetSettings().GetStyleSettings().GetSelectionOptions();
+ 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
+ SpinField::SetText(sFormatted, aNewSel);
+ m_ValueState = valueString;
+}
+
+OUString const & FormattedField::GetTextValue() const
+{
+ if (m_ValueState != valueString )
+ {
+ const_cast<FormattedField*>(this)->m_sCurrentTextValue = GetText();
+ const_cast<FormattedField*>(this)->m_ValueState = valueString;
+ }
+ return m_sCurrentTextValue;
+}
+
+void FormattedField::EnableNotANumber( bool _bEnable )
+{
+ if ( m_bEnableNaN == _bEnable )
+ return;
+
+ m_bEnableNaN = _bEnable;
+}
+
+void FormattedField::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
+ if (m_pLastOutputColor)
+ SetControlForeground(*m_pLastOutputColor);
+ else
+ SetControlForeground();
+ }
+}
+
+void FormattedField::impl_Modify(bool makeValueDirty)
+{
+
+ if (!IsStrictFormat())
+ {
+ if(makeValueDirty)
+ m_ValueState = valueDirty;
+ SpinField::Modify();
+ return;
+ }
+
+ OUString sCheck = GetText();
+ if (CheckText(sCheck))
+ {
+ m_sLastValidText = sCheck;
+ m_aLastSelection = GetSelection();
+ if(makeValueDirty)
+ m_ValueState = valueDirty;
+ }
+ else
+ {
+ ImplSetTextImpl(m_sLastValidText, &m_aLastSelection);
+ }
+
+ SpinField::Modify();
+}
+
+void FormattedField::Modify()
+{
+
+ impl_Modify();
+}
+
+void FormattedField::ImplSetTextImpl(const OUString& rNew, Selection const * pNewSel)
+{
+
+ if (m_bAutoColor)
+ {
+ if (m_pLastOutputColor)
+ SetControlForeground(*m_pLastOutputColor);
+ else
+ SetControlForeground();
+ }
+
+ if (pNewSel)
+ SpinField::SetText(rNew, *pNewSel);
+ else
+ {
+ Selection aSel(GetSelection());
+ aSel.Justify();
+
+ sal_Int32 nNewLen = rNew.getLength();
+ sal_Int32 nCurrentLen = GetText().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;
+ SpinField::SetText(rNew, aSel);
+ }
+
+ m_ValueState = valueDirty; // not always necessary, but better re-evaluate for safety reasons
+}
+
+bool FormattedField::PreNotify(NotifyEvent& rNEvt)
+{
+ if (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT)
+ m_aLastSelection = GetSelection();
+ return SpinField::PreNotify(rNEvt);
+}
+
+void FormattedField::ImplSetFormatKey(sal_uLong nFormatKey)
+{
+
+ m_nFormatKey = nFormatKey;
+ bool bNeedFormatter = (m_pFormatter == nullptr) && (nFormatKey != 0);
+ if (bNeedFormatter)
+ {
+ ImplGetFormatter(); // 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 FormattedField::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 FormattedField::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 FormattedField::GetFormat(LanguageType& eLang) const
+{
+ const SvNumberformat* pFormatEntry = ImplGetFormatter()->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 FormattedField::SetFormat(const OUString& rFormatString, LanguageType eLang)
+{
+ sal_uInt32 nNewKey = ImplGetFormatter()->TestNewString(rFormatString, eLang);
+ if (nNewKey == NUMBERFORMAT_ENTRY_NOT_FOUND)
+ {
+ sal_Int32 nCheckPos;
+ SvNumFormatType nType;
+ OUString rFormat(rFormatString);
+ if (!ImplGetFormatter()->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 FormattedField::GetThousandsSep() const
+{
+ DBG_ASSERT(!ImplGetFormatter()->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;
+ ImplGetFormatter()->GetFormatSpecialInfo(m_nFormatKey, bThousand, IsRed, nPrecision, nLeadingCnt);
+
+ return bThousand;
+}
+
+void FormattedField::SetThousandsSep(bool _bUseSeparator)
+{
+ DBG_ASSERT(!ImplGetFormatter()->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;
+ ImplGetFormatter()->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 = ImplGetFormatter()->GenerateFormat(m_nFormatKey, eLang, _bUseSeparator, IsRed, nPrecision, nLeadingCnt);
+ // ... and introduce it to the formatter
+ sal_Int32 nCheckPos = 0;
+ sal_uInt32 nNewKey;
+ SvNumFormatType nType;
+ ImplGetFormatter()->PutEntry(sFmtDescription, nCheckPos, nType, nNewKey, eLang);
+
+ // set the new key
+ ImplSetFormatKey(nNewKey);
+ FormatChanged(FORMAT_CHANGE_TYPE::THOUSANDSSEP);
+}
+
+sal_uInt16 FormattedField::GetDecimalDigits() const
+{
+ DBG_ASSERT(!ImplGetFormatter()->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;
+ ImplGetFormatter()->GetFormatSpecialInfo(m_nFormatKey, bThousand, IsRed, nPrecision, nLeadingCnt);
+
+ return nPrecision;
+}
+
+void FormattedField::SetDecimalDigits(sal_uInt16 _nPrecision)
+{
+ DBG_ASSERT(!ImplGetFormatter()->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;
+ ImplGetFormatter()->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 = ImplGetFormatter()->GenerateFormat(m_nFormatKey, eLang, bThousand, IsRed, _nPrecision, nLeadingCnt);
+ // ... and introduce it to the formatter
+ sal_Int32 nCheckPos = 0;
+ sal_uInt32 nNewKey;
+ SvNumFormatType nType;
+ ImplGetFormatter()->PutEntry(sFmtDescription, nCheckPos, nType, nNewKey, eLang);
+
+ // set the new key
+ ImplSetFormatKey(nNewKey);
+ FormatChanged(FORMAT_CHANGE_TYPE::PRECISION);
+}
+
+void FormattedField::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 FormattedField::Commit()
+{
+ // remember the old text
+ OUString sOld( GetText() );
+
+ // do the reformat
+ ReFormat();
+
+ // did the text change?
+ if ( GetText() != 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!)
+ impl_Modify(false);
+ }
+}
+
+void FormattedField::ReFormat()
+{
+ if (!IsEmptyFieldEnabled() || !GetText().isEmpty())
+ {
+ if (TreatingAsNumber())
+ {
+ double dValue = GetValue();
+ if ( m_bEnableNaN && std::isnan( dValue ) )
+ return;
+ ImplSetValue( dValue, true );
+ }
+ else
+ SetTextFormatted(GetTextValue());
+ }
+}
+
+bool FormattedField::EventNotify(NotifyEvent& rNEvt)
+{
+
+ if ((rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) && !IsReadOnly())
+ {
+ const KeyEvent& rKEvt = *rNEvt.GetKeyEvent();
+ sal_uInt16 nMod = rKEvt.GetKeyCode().GetModifier();
+ switch ( rKEvt.GetKeyCode().GetCode() )
+ {
+ case KEY_UP:
+ case KEY_DOWN:
+ case KEY_PAGEUP:
+ case KEY_PAGEDOWN:
+ if (!nMod && ImplGetFormatter()->IsTextFormat(m_nFormatKey))
+ {
+ // the base class would translate this into calls to Up/Down/First/Last,
+ // but we don't want this if we are text-formatted
+ return true;
+ }
+ }
+ }
+
+ if ((rNEvt.GetType() == MouseNotifyEvent::COMMAND) && !IsReadOnly())
+ {
+ const CommandEvent* pCommand = rNEvt.GetCommandEvent();
+ if (pCommand->GetCommand() == CommandEventId::Wheel)
+ {
+ const CommandWheelData* pData = rNEvt.GetCommandEvent()->GetWheelData();
+ if ((pData->GetMode() == CommandWheelMode::SCROLL) && ImplGetFormatter()->IsTextFormat(m_nFormatKey))
+ {
+ // same as above : prevent the base class from doing Up/Down-calls
+ // (normally I should put this test into the Up/Down methods itself, shouldn't I ?)
+ // FS - 71553 - 19.01.00
+ return true;
+ }
+ }
+ }
+
+ if (rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS)
+ {
+ // special treatment for empty texts
+ if (GetText().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();
+ }
+ }
+
+ return SpinField::EventNotify( rNEvt );
+}
+
+void FormattedField::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 FormattedField::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 FormattedField::SetTextValue(const OUString& rText)
+{
+ SetText(rText);
+ ReFormat();
+}
+
+// currently used by online
+void FormattedField::SetValueFromString(const OUString& rStr)
+{
+ sal_Int32 nEnd;
+ rtl_math_ConversionStatus eStatus;
+ double fValue = ::rtl::math::stringToDouble(rStr, '.', GetDecimalDigits(), &eStatus, &nEnd );
+
+ if (eStatus == rtl_math_ConversionStatus_Ok &&
+ nEnd == rStr.getLength())
+ {
+ SetValue(fValue);
+ SetModifyFlag();
+ Modify();
+
+ // Notify the value has changed
+ SpinField::Up();
+ }
+ else
+ {
+ SAL_WARN("vcl", "fail to convert the value: " << rStr);
+ }
+}
+
+void FormattedField::EnableEmptyField(bool bEnable)
+{
+ if (bEnable == m_bEnableEmptyField)
+ return;
+
+ m_bEnableEmptyField = bEnable;
+ if (!m_bEnableEmptyField && GetText().isEmpty())
+ ImplSetValue(m_dCurrentValue, true);
+}
+
+void FormattedField::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(ImplGetFormatter() != nullptr, "FormattedField::ImplSetValue : can't set a value without a formatter !");
+
+ m_ValueState = valueDouble;
+ m_dCurrentValue = dVal;
+
+ if (!m_aOutputHdl.IsSet() || !m_aOutputHdl.Call(*this))
+ {
+ OUString sNewText;
+ if (ImplGetFormatter()->IsTextFormat(m_nFormatKey))
+ {
+ // first convert the number as string in standard format
+ OUString sTemp;
+ ImplGetFormatter()->GetOutputString(dVal, 0, sTemp, &m_pLastOutputColor);
+ // then encode the string in the corresponding text format
+ ImplGetFormatter()->GetOutputString(sTemp, m_nFormatKey, sNewText, &m_pLastOutputColor);
+ }
+ else
+ {
+ if( IsUsingInputStringForFormatting())
+ {
+ ImplGetFormatter()->GetInputLineString(dVal, m_nFormatKey, sNewText);
+ }
+ else
+ {
+ ImplGetFormatter()->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 FormattedField::ImplGetValue(double& dNewVal)
+{
+ dNewVal = m_dCurrentValue;
+ if (m_ValueState == valueDouble)
+ return true;
+
+ dNewVal = m_dDefaultValue;
+ OUString sText(GetText());
+ 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(ImplGetFormatter() != nullptr, "FormattedField::ImplGetValue : can't give you a current value without a formatter !");
+
+ sal_uInt32 nFormatKey = m_nFormatKey; // IsNumberFormat changes the FormatKey!
+
+ if (ImplGetFormatter()->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 (ImplGetFormatter()->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 (!ImplGetFormatter()->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 FormattedField::SetValue(double dVal)
+{
+ ImplSetValue(dVal, m_ValueState != valueDouble);
+}
+
+double FormattedField::GetValue()
+{
+
+ if ( !ImplGetValue( m_dCurrentValue ) )
+ {
+ if ( m_bEnableNaN )
+ ::rtl::math::setNan( &m_dCurrentValue );
+ else
+ m_dCurrentValue = m_dDefaultValue;
+ }
+
+ m_ValueState = valueDouble;
+ return m_dCurrentValue;
+}
+
+void FormattedField::DisableRemainderFactor()
+{
+ m_bDisableRemainderFactor = true;
+}
+
+bool FormattedField::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "digits")
+ SetDecimalDigits(rValue.toInt32());
+ else if (rKey == "wrap")
+ m_bWrapOnLimits = toBool(rValue);
+ else
+ return SpinField::set_property(rKey, rValue);
+ return true;
+}
+
+void FormattedField::Up()
+{
+ auto nScale = weld::SpinButton::Power10(GetDecimalDigits());
+
+ sal_Int64 nValue = std::round(GetValue() * nScale);
+ sal_Int64 nSpinSize = std::round(m_dSpinSize * nScale);
+ sal_Int64 nRemainder = m_bDisableRemainderFactor ? 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
+ SetValue(static_cast<double>(nValue) / nScale);
+ SetModifyFlag();
+ Modify();
+
+ SpinField::Up();
+}
+
+void FormattedField::Down()
+{
+ auto nScale = weld::SpinButton::Power10(GetDecimalDigits());
+
+ sal_Int64 nValue = std::round(GetValue() * nScale);
+ sal_Int64 nSpinSize = std::round(m_dSpinSize * nScale);
+ sal_Int64 nRemainder = m_bDisableRemainderFactor ? 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
+ SetValue(static_cast<double>(nValue) / nScale);
+ SetModifyFlag();
+ Modify();
+
+ SpinField::Down();
+}
+
+void FormattedField::First()
+{
+ if (m_bHasMin)
+ {
+ SetValue(m_dMinValue);
+ SetModifyFlag();
+ Modify();
+ }
+
+ SpinField::First();
+}
+
+void FormattedField::Last()
+{
+ if (m_bHasMax)
+ {
+ SetValue(m_dMaxValue);
+ SetModifyFlag();
+ Modify();
+ }
+
+ SpinField::Last();
+}
+
+void FormattedField::UseInputStringForFormatting()
+{
+ m_bUseInputStringForFormatting = true;
+}
+
+boost::property_tree::ptree FormattedField::DumpAsPropertyTree()
+{
+ boost::property_tree::ptree aTree(SpinField::DumpAsPropertyTree());
+ aTree.put("min", rtl::math::doubleToString(GetMinValue(),
+ rtl_math_StringFormat_F, GetDecimalDigits(), '.').getStr());
+ aTree.put("max", rtl::math::doubleToString(GetMaxValue(),
+ rtl_math_StringFormat_F, GetDecimalDigits(), '.').getStr());
+ aTree.put("value", rtl::math::doubleToString(GetValue(),
+ rtl_math_StringFormat_F, GetDecimalDigits(), '.').getStr());
+
+ return aTree;
+}
+
+FactoryFunction FormattedField::GetUITestFactory() const
+{
+ return FormattedFieldUIObject::create;
+}
+
+DoubleNumericField::DoubleNumericField(vcl::Window* pParent, WinBits nStyle)
+ : FormattedField(pParent, nStyle)
+{
+ ResetConformanceTester();
+}
+
+DoubleNumericField::~DoubleNumericField() = default;
+
+void DoubleNumericField::FormatChanged(FORMAT_CHANGE_TYPE nWhat)
+{
+ ResetConformanceTester();
+ FormattedField::FormatChanged(nWhat);
+}
+
+bool DoubleNumericField::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 DoubleNumericField::ResetConformanceTester()
+{
+ // the thousands and the decimal separator are language dependent
+ const SvNumberformat* pFormatEntry = ImplGetFormatter()->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 ));
+}
+
+DoubleCurrencyField::DoubleCurrencyField(vcl::Window* pParent, WinBits nStyle)
+ :FormattedField(pParent, nStyle)
+ ,m_bChangingFormat(false)
+{
+ m_bPrependCurrSym = false;
+
+ // initialize with a system currency format
+ m_sCurrencySymbol = SvtSysLocale().GetLocaleData().getCurrSymbol();
+ UpdateCurrencyFormat();
+}
+
+void DoubleCurrencyField::FormatChanged(FORMAT_CHANGE_TYPE nWhat)
+{
+ if (m_bChangingFormat)
+ {
+ FormattedField::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)
+ 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;
+ }
+
+ FormattedField::FormatChanged(nWhat);
+}
+
+void DoubleCurrencyField::setCurrencySymbol(const OUString& rSymbol)
+{
+ if (m_sCurrencySymbol == rSymbol)
+ return;
+
+ m_sCurrencySymbol = rSymbol;
+ UpdateCurrencyFormat();
+ FormatChanged(FORMAT_CHANGE_TYPE::CURRENCY_SYMBOL);
+}
+
+void DoubleCurrencyField::setPrependCurrSym(bool _bPrepend)
+{
+ if (m_bPrependCurrSym == _bPrepend)
+ return;
+
+ m_bPrependCurrSym = _bPrepend;
+ UpdateCurrencyFormat();
+ FormatChanged(FORMAT_CHANGE_TYPE::CURRSYM_POSITION);
+}
+
+void DoubleCurrencyField::UpdateCurrencyFormat()
+{
+ // the old settings
+ LanguageType eLanguage;
+ GetFormat(eLanguage);
+ bool bThSep = GetThousandsSep();
+ sal_uInt16 nDigits = 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)' */
+ LanguageTag aLanguageTag( eLanguage);
+ LocaleDataWrapper aLocaleInfo( aLanguageTag );
+
+ OUStringBuffer sNewFormat;
+ if (bThSep)
+ {
+ sNewFormat.append('#');
+ sNewFormat.append(aLocaleInfo.getNumThousandSep());
+ sNewFormat.append("##0");
+ }
+ else
+ sNewFormat.append('0');
+
+ if (nDigits)
+ {
+ sNewFormat.append(aLocaleInfo.getNumDecimalSep());
+
+ OUStringBuffer sTemp;
+ comphelper::string::padToLength(sTemp, nDigits, '0');
+ sNewFormat.append(sTemp);
+ }
+
+ if (getPrependCurrSym())
+ {
+ OUString sSymbol = getCurrencySymbol();
+ sSymbol = comphelper::string::stripStart(sSymbol, ' ');
+ sSymbol = comphelper::string::stripEnd(sSymbol, ' ');
+
+ OUStringBuffer sTemp("[$");
+ sTemp.append(sSymbol);
+ sTemp.append("] ");
+ sTemp.append(sNewFormat);
+
+ // for negative values : $ -0.00, not -$ 0.00...
+ // (the real solution would be a possibility to choose a "positive currency format" and a "negative currency format"...
+ // But not now... (and hey, you could take a formatted field for this...))
+ // FS - 31.03.00 74642
+ sTemp.append(";[$");
+ sTemp.append(sSymbol);
+ sTemp.append("] -");
+ sTemp.append(sNewFormat);
+
+ sNewFormat = sTemp;
+ }
+ else
+ {
+ OUString sTemp = getCurrencySymbol();
+ sTemp = comphelper::string::stripStart(sTemp, ' ');
+ sTemp = comphelper::string::stripEnd(sTemp, ' ');
+
+ sNewFormat.append(" [$");
+ sNewFormat.append(sTemp);
+ sNewFormat.append(']');
+ }
+
+ // set this new basic format
+ m_bChangingFormat = true;
+ SetFormat(sNewFormat.makeStringAndClear(), eLanguage);
+ m_bChangingFormat = false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/hyperlabel.cxx b/vcl/source/control/hyperlabel.cxx
new file mode 100644
index 000000000..f42debe51
--- /dev/null
+++ b/vcl/source/control/hyperlabel.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 <tools/color.hxx>
+#include <vcl/event.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/ptrstyle.hxx>
+#include <hyperlabel.hxx>
+
+namespace vcl
+{
+ class HyperLabelImpl
+ {
+ public:
+ sal_Int16 ID;
+ sal_Int32 Index;
+ bool bInteractive;
+ Size m_aMinSize;
+ bool m_bHyperMode;
+
+ HyperLabelImpl();
+ };
+
+
+ HyperLabelImpl::HyperLabelImpl()
+ : ID(0)
+ , Index(0)
+ , bInteractive(false)
+ , m_bHyperMode(false)
+ {
+ }
+
+ HyperLabel::HyperLabel( vcl::Window* _pParent, WinBits _nWinStyle )
+ :FixedText( _pParent, _nWinStyle )
+ ,m_pImpl( new HyperLabelImpl )
+ {
+ implInit();
+ }
+
+ Size const & HyperLabel::CalcMinimumSize( long nMaxWidth ) const
+ {
+ m_pImpl->m_aMinSize = FixedText::CalcMinimumSize( nMaxWidth );
+ // the MinimumSize is used to size the FocusRectangle
+ // and for the MouseMove method
+ m_pImpl->m_aMinSize.AdjustHeight(2 );
+ m_pImpl->m_aMinSize.AdjustWidth(1 );
+ return m_pImpl->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() && m_pImpl->bInteractive)
+ {
+ Point aPoint = GetPointerPosPixel();
+ if (aPoint.X() < m_pImpl->m_aMinSize.Width())
+ bHyperMode = true;
+ }
+
+ m_pImpl->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_pImpl->m_bHyperMode && m_pImpl->bInteractive )
+ {
+ maClickHdl.Call( this );
+ }
+ }
+
+ void HyperLabel::GetFocus()
+ {
+ if ( IsEnabled() && m_pImpl->bInteractive )
+ {
+ Point aPoint(0,0);
+ tools::Rectangle rRect(aPoint, Size( m_pImpl->m_aMinSize.Width(), GetSizePixel().Height() ) );
+ ShowFocus( rRect );
+ }
+ }
+
+ void HyperLabel::LoseFocus()
+ {
+ HideFocus();
+ }
+
+ HyperLabel::~HyperLabel( )
+ {
+ disposeOnce();
+ }
+
+ void HyperLabel::dispose()
+ {
+ m_pImpl.reset();
+ FixedText::dispose();
+ }
+
+ void HyperLabel::SetInteractive( bool _bInteractive )
+ {
+ m_pImpl->bInteractive = ( _bInteractive && IsEnabled() );
+ }
+
+ sal_Int16 HyperLabel::GetID() const
+ {
+ return m_pImpl->ID;
+ }
+
+ sal_Int32 HyperLabel::GetIndex() const
+ {
+ return m_pImpl->Index;
+ }
+
+ void HyperLabel::SetID( sal_Int16 ID )
+ {
+ m_pImpl->ID = ID;
+ }
+
+ void HyperLabel::SetIndex( sal_Int32 Index )
+ {
+ m_pImpl->Index = Index;
+ }
+
+ void HyperLabel::SetLabel( const OUString& _rText )
+ {
+ SetText(_rText);
+ }
+
+ void HyperLabel::ApplySettings(vcl::RenderContext& rRenderContext)
+ {
+ FixedText::ApplySettings(rRenderContext);
+
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ if (GetControlBackground() == COL_TRANSPARENT)
+ rRenderContext.SetTextColor(rStyleSettings.GetFieldTextColor());
+ else
+ rRenderContext.SetTextColor(rStyleSettings.GetHighlightTextColor());
+ }
+
+ void HyperLabel::DataChanged( const DataChangedEvent& rDCEvt )
+ {
+ FixedText::DataChanged( rDCEvt );
+
+ if ((( rDCEvt.GetType() == DataChangedEventType::SETTINGS ) ||
+ ( rDCEvt.GetType() == DataChangedEventType::DISPLAY )) &&
+ ( rDCEvt.GetFlags() & AllSettingsFlags::STYLE ))
+ {
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ if (GetControlBackground() != COL_TRANSPARENT)
+ SetControlBackground(rStyleSettings.GetHighlightColor());
+ Invalidate();
+ }
+ }
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/imgctrl.cxx b/vcl/source/control/imgctrl.cxx
new file mode 100644
index 000000000..4ecda5b1e
--- /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( long( _rBitmapSize.Width() * nRatioMin ), 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->GetLineColor();
+ Color oldFillCol = pBorderWindow->GetFillColor();
+ pBorderWindow->SetFillColor();
+ pBorderWindow->SetLineColor(bFlat ? COL_WHITE : COL_BLACK);
+ pBorderWindow->DrawRect(aRect);
+ aRect.AdjustLeft( 1 );
+ aRect.AdjustRight( -1 );
+ aRect.AdjustTop( 1 );
+ aRect.AdjustBottom( -1 );
+ pBorderWindow->SetLineColor(bFlat ? COL_BLACK : COL_WHITE);
+ pBorderWindow->DrawRect(aRect);
+ pBorderWindow->SetLineColor(oldLineCol);
+ pBorderWindow->SetFillColor(oldFillCol);
+
+}
+
+void ImageControl::Draw( OutputDevice* pDev, const Point& rPos, DrawFlags )
+{
+ const Point aPos = pDev->LogicToPixel( rPos );
+ const Size aSize = GetSizePixel();
+ tools::Rectangle aRect( aPos, aSize );
+
+ pDev->Push();
+ pDev->SetMapMode();
+
+ // Border
+ if ( GetStyle() & WB_BORDER )
+ {
+ ImplDrawFrame( pDev, aRect );
+ }
+ pDev->IntersectClipRegion( aRect );
+ ImplDraw( *pDev, aRect.TopLeft(), aRect.GetSize() );
+
+ pDev->Pop();
+}
+
+void ImageControl::GetFocus()
+{
+ FixedImage::GetFocus();
+ GetWindow( GetWindowType::Border )->Invalidate();
+}
+
+void ImageControl::LoseFocus()
+{
+ FixedImage::GetFocus();
+ GetWindow( GetWindowType::Border )->Invalidate();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/imivctl.hxx b/vcl/source/control/imivctl.hxx
new file mode 100644
index 000000000..797cf6ebb
--- /dev/null
+++ b/vcl/source/control/imivctl.hxx
@@ -0,0 +1,513 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_SVTOOLS_SOURCE_CONTNR_IMIVCTL_HXX
+#define INCLUDED_SVTOOLS_SOURCE_CONTNR_IMIVCTL_HXX
+
+#include <sal/config.h>
+
+#include <o3tl/safeint.hxx>
+#include <vcl/ivctrl.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/scrbar.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/idle.hxx>
+#include <vcl/vclptr.hxx>
+#include <tools/debug.hxx>
+#include <vcl/svtaccessiblefactory.hxx>
+
+#include <limits.h>
+
+
+#include <memory>
+#include <map>
+
+class IcnCursor_Impl;
+class SvtIconChoiceCtrl;
+class SvxIconChoiceCtrlEntry;
+class IcnViewEdit_Impl;
+class IcnGridMap_Impl;
+
+
+// some defines
+
+#define PAINTFLAG_HOR_CENTERED 0x0001
+#define PAINTFLAG_VER_CENTERED 0x0002
+
+enum class IconChoiceFlags {
+ NONE = 0x0000,
+ AddMode = 0x0001,
+ SelectingRect = 0x0002,
+ DownCtrl = 0x0004,
+ DownDeselect = 0x0008,
+ EntryListPosValid = 0x0010,
+ ClearingSelection = 0x0020,
+ Arranging = 0x0040
+};
+namespace o3tl {
+ template<> struct typed_flags<IconChoiceFlags> : is_typed_flags<IconChoiceFlags, 0x007f> {};
+}
+
+// unit = pixels
+// distances from window borders
+#define LROFFS_WINBORDER 4
+#define TBOFFS_WINBORDER 4
+// for the bounding rectangle
+#define LROFFS_BOUND 2
+#define TBOFFS_BOUND 2
+// distance icon to text
+#define HOR_DIST_BMP_STRING 3
+#define VER_DIST_BMP_STRING 3
+// width offset of highlight rectangle for Text
+#define LROFFS_TEXT 2
+
+#define DEFAULT_MAX_VIRT_WIDTH 200
+#define DEFAULT_MAX_VIRT_HEIGHT 200
+
+#define VIEWMODE_MASK (WB_ICON | WB_SMALLICON | WB_DETAILS)
+
+
+enum class IcnViewFieldType
+{
+ Image,
+ Text
+};
+
+
+// Data about the focus of entries
+
+struct LocalFocus
+{
+ tools::Rectangle aRect;
+ Color aPenColor;
+};
+
+
+// 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;
+
+ bool bChooseWithCursor;
+ 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;
+ long nMaxVirtWidth; // max. width aVirtOutputSize for ALIGN_TOP
+ long nMaxVirtHeight; // max. height aVirtOutputSize for ALIGN_LEFT
+ std::vector< SvxIconChoiceCtrlEntry* > maZOrderList;
+ std::unique_ptr<SvxIconChoiceCtrlColumnInfoMap> m_pColumns;
+ WinBits nWinBits;
+ 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( long nRealWidth, long nRealHeight );
+ static long GetScrollBarPageSize( long nVisibleRange )
+ {
+ return ((nVisibleRange*75)/100);
+ }
+ 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;
+ sal_uLong 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:
+
+ long nGridDX;
+ long nGridDY;
+ long nHorSBarHeight;
+ long nVerSBarWidth;
+
+ SvxIconChoiceCtrl_Impl( SvtIconChoiceCtrl* pView, WinBits nWinStyle );
+ ~SvxIconChoiceCtrl_Impl();
+
+ void SetChoiceWithCursor() { bChooseWithCursor = true; }
+ 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 CreateAutoMnemonics( MnemonicGenerator* _pGenerator );
+ 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,
+ long nSetMaxVirtWidth,
+ 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
+ );
+
+ long CalcBoundingWidth() const;
+ 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( long nDeltaX, 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;
+ void SetSelectionMode( SelectionMode eMode ) { eSelectionMode=eMode; }
+ 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;
+ long nCols;
+ long nRows;
+ short nDeltaWidth;
+ short nDeltaHeight;
+ SvxIconChoiceCtrlEntry* pCurEntry;
+ void SetDeltas();
+ void ImplCreate();
+ void Create() { if( !xColumns ) ImplCreate(); }
+
+ sal_uInt16 GetSortListPos(
+ SvxIconChoiceCtrlEntryPtrVec& rList,
+ 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 );
+};
+
+
+typedef sal_uLong GridId;
+
+#define GRID_NOT_FOUND ((GridId)ULONG_MAX)
+
+class IcnGridMap_Impl
+{
+ tools::Rectangle _aLastOccupiedGrid;
+ SvxIconChoiceCtrl_Impl* _pView;
+ std::unique_ptr<bool[]> _pGridMap;
+ sal_uInt16 _nGridCols, _nGridRows;
+
+ void Expand();
+ void Create_Impl();
+ void Create() { if(!_pGridMap) Create_Impl(); }
+
+ void GetMinMapSize( sal_uInt16& rDX, sal_uInt16& rDY ) const;
+
+public:
+ explicit IcnGridMap_Impl(SvxIconChoiceCtrl_Impl* pView);
+ ~IcnGridMap_Impl();
+
+ void Clear();
+
+ GridId GetGrid( const Point& rDocPos );
+ GridId GetGrid( sal_uInt16 nGridX, sal_uInt16 nGridY );
+ GridId GetUnoccupiedGrid();
+
+ void OccupyGrids( const SvxIconChoiceCtrlEntry* );
+ void OccupyGrid( GridId nId )
+ {
+ DBG_ASSERT(!_pGridMap || nId<o3tl::make_unsigned(_nGridCols*_nGridRows),"OccupyGrid: Bad GridId");
+ if(_pGridMap && nId < o3tl::make_unsigned(_nGridCols *_nGridRows) )
+ _pGridMap[ nId ] = true;
+ }
+
+ tools::Rectangle GetGridRect( GridId );
+ void GetGridCoord( GridId, sal_uInt16& rGridX, sal_uInt16& rGridY );
+ static sal_uLong GetGridCount(
+ const Size& rSizePixel,
+ sal_uInt16 nGridWidth,
+ sal_uInt16 nGridHeight
+ );
+
+ void OutputSizeChanged();
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/imivctl1.cxx b/vcl/source/control/imivctl1.cxx
new file mode 100644
index 000000000..4efb7596a
--- /dev/null
+++ b/vcl/source/control/imivctl1.cxx
@@ -0,0 +1,2980 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/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/ivctrl.hxx>
+#include "imivctl.hxx"
+
+#include <algorithm>
+#include <memory>
+#include <vcl/idle.hxx>
+
+static 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
+) :
+ bChooseWithCursor(false),
+ 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 contnr SvxIconChoiceCtrl_Impl AutoArrange" ),
+ aDocRectChangedIdle ( "svtools contnr SvxIconChoiceCtrl_Impl DocRectChanged" ),
+ aVisRectChangedIdle ( "svtools contnr SvxIconChoiceCtrl_Impl VisRectChanged" ),
+ aCallSelectHdlIdle ( "svtools contnr SvxIconChoiceCtrl_Impl CallSelectHdl" ),
+ 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));
+ aAutoArrangeIdle.SetDebugName( "svtools::SvxIconChoiceCtrl_Impl aAutoArrangeIdle" );
+
+ aCallSelectHdlIdle.SetPriority( TaskPriority::LOWEST );
+ aCallSelectHdlIdle.SetInvokeHandler( LINK(this,SvxIconChoiceCtrl_Impl,CallSelectHdlHdl));
+ aCallSelectHdlIdle.SetDebugName( "svtools::SvxIconChoiceCtrl_Impl aCallSelectHdlIdle" );
+
+ aDocRectChangedIdle.SetPriority( TaskPriority::HIGH_IDLE );
+ aDocRectChangedIdle.SetInvokeHandler(LINK(this,SvxIconChoiceCtrl_Impl,DocRectChangedHdl));
+ aDocRectChangedIdle.SetDebugName( "svtools::SvxIconChoiceCtrl_Impl aDocRectChangedIdle" );
+
+ aVisRectChangedIdle.SetPriority( TaskPriority::HIGH_IDLE );
+ aVisRectChangedIdle.SetInvokeHandler(LINK(this,SvxIconChoiceCtrl_Impl,VisRectChangedHdl));
+ aVisRectChangedIdle.SetDebugName( "svtools::SvxIconChoiceCtrl_Impl aVisRectChangedIdle" );
+
+ 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.IsOver( 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();
+}
+
+void SvxIconChoiceCtrl_Impl::CreateAutoMnemonics( MnemonicGenerator* _pGenerator )
+{
+ std::unique_ptr< MnemonicGenerator > pAutoDeleteOwnGenerator;
+ if ( !_pGenerator )
+ {
+ _pGenerator = new MnemonicGenerator;
+ pAutoDeleteOwnGenerator.reset( _pGenerator );
+ }
+
+ sal_uLong nEntryCount = GetEntryCount();
+ sal_uLong i;
+
+ // insert texts in generator
+ for( i = 0; i < nEntryCount; ++i )
+ {
+ DBG_ASSERT( GetEntry( i ), "-SvxIconChoiceCtrl_Impl::CreateAutoMnemonics(): more expected than provided!" );
+
+ _pGenerator->RegisterMnemonic( GetEntry( i )->GetText() );
+ }
+
+ // exchange texts with generated mnemonics
+ for( i = 0; i < nEntryCount; ++i )
+ {
+ SvxIconChoiceCtrlEntry* pEntry = GetEntry( i );
+ OUString aTxt = pEntry->GetText();
+
+ OUString aNewText = _pGenerator->CreateMnemonic( aTxt );
+ if( aNewText != aTxt )
+ pEntry->SetText( aNewText );
+ }
+}
+
+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 )
+{
+ long nHeightOffs = 0;
+ 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, long nSetMaxVirtWidth, 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 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 (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 (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(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.IsOver(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.IsOver(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 );
+ }
+ }
+
+ if ( bChooseWithCursor && pNewCursor != nullptr )
+ {
+ pHdlEntry = pNewCursor;//GetCurEntry();
+ pCurHighlightFrame = pHdlEntry;
+ pView->ClickIcon();
+ pCurHighlightFrame = nullptr;
+ }
+ }
+ 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 );
+
+ if ( bChooseWithCursor && pNewCursor != nullptr)
+ {
+ pHdlEntry = pNewCursor;//GetCurEntry();
+ pCurHighlightFrame = pHdlEntry;
+ pView->ClickIcon();
+ pCurHighlightFrame = nullptr;
+ }
+ }
+ 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( long nRealWidth, 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()
+{
+ long nVirtHeight = aVirtOutputSize.Height();
+ long nVirtWidth = aVirtOutputSize.Width();
+
+ Size aOSize( pView->Control::GetOutputSizePixel() );
+ long nRealHeight = aOSize.Height();
+ long nRealWidth = aOSize.Width();
+
+ PositionScrollBars( nRealWidth, nRealHeight );
+
+ const MapMode& rMapMode = pView->GetMapMode();
+ Point aOrigin( rMapMode.GetOrigin() );
+
+ long nVisibleWidth;
+ if( nRealWidth > nVirtWidth )
+ nVisibleWidth = nVirtWidth + aOrigin.X();
+ else
+ nVisibleWidth = nRealWidth;
+
+ 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
+ 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() )
+ {
+ long nWidth = aOutputSize.Width();
+ const size_t nCount = maZOrderList.size();
+ long nMostRight = 0;
+ for( size_t nCur = 0; nCur < nCount; nCur++ )
+ {
+ SvxIconChoiceCtrlEntry* pEntry = maZOrderList[ nCur ];
+ 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() )
+ {
+ long nDeepest = 0;
+ long nHeight = aOutputSize.Height();
+ const size_t nCount = maZOrderList.size();
+ for( size_t nCur = 0; nCur < nCount; nCur++ )
+ {
+ SvxIconChoiceCtrlEntry* pEntry = maZOrderList[ nCur ];
+ 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(PushFlags::FONT | 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.IsOver(aTextRect) || aOutputArea.IsOver(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.IsInside( rDocPos ) )
+ {
+ if( bHit )
+ {
+ tools::Rectangle aRect = CalcBmpRect( pEntry );
+ aRect.AdjustTop( -3 );
+ aRect.AdjustBottom(3 );
+ aRect.AdjustLeft( -3 );
+ aRect.AdjustRight(3 );
+ if( aRect.IsInside( rDocPos ) )
+ return pEntry;
+ aRect = CalcTextRect( pEntry );
+ if( aRect.IsInside( 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() );
+ long nBoundWidth = aBound.GetWidth();
+ 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 );
+}
+
+
+long SvxIconChoiceCtrl_Impl::CalcBoundingWidth() const
+{
+ long nStringWidth = GetItemSize( IcnViewFieldType::Text ).Width();
+ 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;
+}
+
+long SvxIconChoiceCtrl_Impl::CalcBoundingHeight() const
+{
+ long nStringHeight = GetItemSize(IcnViewFieldType::Text).Height();
+ 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.IsInside( aDocRect ))
+ return false;
+ Size aDocSize( aDocRect.GetSize() );
+ Size aVisSize( aVisRect.GetSize() );
+ bool bHor = aDocSize.Width() > aVisSize.Width();
+ bool bVer = aDocSize.Height() > aVisSize.Height();
+
+ long nScrollDX = 0, nScrollDY = 0;
+
+ switch( rCmd.GetCommand() )
+ {
+ case CommandEventId::StartAutoScroll:
+ {
+ pView->EndTracking();
+ StartAutoScrollFlags nScrollFlags = StartAutoScrollFlags::NONE;
+ if( bHor )
+ nScrollFlags |= StartAutoScrollFlags::Horz;
+ if( bVer )
+ nScrollFlags |= StartAutoScrollFlags::Vert;
+ if( nScrollFlags != StartAutoScrollFlags::NONE )
+ {
+ pView->StartAutoScroll( nScrollFlags );
+ return true;
+ }
+ }
+ break;
+
+ case CommandEventId::Wheel:
+ {
+ const CommandWheelData* pData = rCmd.GetWheelData();
+ if( pData && (CommandWheelMode::SCROLL == pData->GetMode()) && !pData->IsHorz() )
+ {
+ sal_uLong nScrollLines = pData->GetScrollLines();
+ if( nScrollLines == COMMAND_WHEEL_PAGESCROLL )
+ {
+ nScrollDY = GetScrollBarPageSize( aVisSize.Width() );
+ if( pData->GetDelta() < 0 )
+ nScrollDY *= -1;
+ }
+ else
+ {
+ nScrollDY = pData->GetNotchDelta() * static_cast<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.IsInside( aVirtRect ) )
+ return; // is already visible
+
+ 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;
+
+ 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)
+ OUString const aStrDummy( "XXX" );
+ Size aTextSize( pView->GetTextWidth( aStrDummy ), 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() );
+ long nHeight = aBoundRect.GetSize().Height();
+ nHeight = nHeight - aDefaultTextSize.Height();
+ nHeight /= 2;
+ aBoundRect.AdjustTop(nHeight );
+ aBoundRect.AdjustBottom( -nHeight );
+ }
+ return aBoundRect;
+}
+
+void SvxIconChoiceCtrl_Impl::SetDefaultTextSize()
+{
+ long nDY = nGridDY;
+ nDY -= aImageSize.Height();
+ nDY -= VER_DIST_BMP_STRING;
+ nDY -= 2 * TBOFFS_BOUND;
+ if (nDY <= 0)
+ nDY = 2;
+
+ long nDX = nGridDX;
+ nDX -= 2 * LROFFS_BOUND;
+ nDX -= 2;
+ if (nDX <= 0)
+ nDX = 2;
+
+ 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
+ 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( long nDeltaX, long nDeltaY )
+{
+ const MapMode& rMapMode = pView->GetMapMode();
+ Point aOrigin( rMapMode.GetOrigin() );
+ // convert to document coordinate
+ aOrigin *= -1;
+ aOrigin.AdjustY(nDeltaY );
+ aOrigin.AdjustX(nDeltaX );
+ tools::Rectangle aRect( aOrigin, aOutputSize );
+ MakeVisible( aRect, true/*bScrollBar*/ );
+}
+
+
+const Size& SvxIconChoiceCtrl_Impl::GetItemSize( IcnViewFieldType eItem ) const
+{
+ if (eItem == IcnViewFieldType::Text)
+ return aDefaultTextSize;
+ return aImageSize; // IcnViewFieldType::Image
+}
+
+tools::Rectangle SvxIconChoiceCtrl_Impl::CalcFocusRect( SvxIconChoiceCtrlEntry* pEntry )
+{
+ tools::Rectangle aTextRect( CalcTextRect( pEntry ) );
+ tools::Rectangle aBoundRect( GetEntryBoundRect( pEntry ) );
+ return tools::Rectangle(
+ aBoundRect.Left(), aBoundRect.Top() - 1, aBoundRect.Right() - 1,
+ aTextRect.Bottom() + 1);
+}
+
+// the hot spot is the inner 50% of the rectangle
+static tools::Rectangle GetHotSpot( const tools::Rectangle& rRect )
+{
+ tools::Rectangle aResult( rRect );
+ aResult.Justify();
+ Size aSize( rRect.GetSize() );
+ long nDelta = aSize.Width() / 4;
+ aResult.AdjustLeft(nDelta );
+ aResult.AdjustRight( -nDelta );
+ nDelta = aSize.Height() / 4;
+ aResult.AdjustTop(nDelta );
+ aResult.AdjustBottom( -nDelta );
+ return aResult;
+}
+
+void SvxIconChoiceCtrl_Impl::SelectRect( SvxIconChoiceCtrlEntry* pEntry1, SvxIconChoiceCtrlEntry* pEntry2,
+ bool bAdd, std::vector<tools::Rectangle>* pOtherRects )
+{
+ DBG_ASSERT(pEntry1 && pEntry2,"SelectEntry: Invalid Entry-Ptr");
+ tools::Rectangle aRect( GetEntryBoundRect( pEntry1 ) );
+ aRect.Union( GetEntryBoundRect( pEntry2 ) );
+ SelectRect( aRect, bAdd, pOtherRects );
+}
+
+void SvxIconChoiceCtrl_Impl::SelectRect( const tools::Rectangle& rRect, bool bAdd,
+ std::vector<tools::Rectangle>* pOtherRects )
+{
+ aCurSelectionRect = rRect;
+ if( maZOrderList.empty() )
+ return;
+
+ // set flag, so ToTop won't be called in Select
+ bool bAlreadySelectingRect(nFlags & IconChoiceFlags::SelectingRect);
+ nFlags |= IconChoiceFlags::SelectingRect;
+
+ CheckBoundingRects();
+ pView->PaintImmediately();
+ const size_t nCount = maZOrderList.size();
+
+ tools::Rectangle aRect( rRect );
+ aRect.Justify();
+ bool bCalcOverlap = (bAdd && pOtherRects && !pOtherRects->empty());
+
+ bool bResetClipRegion = false;
+ if( !pView->IsClipRegion() )
+ {
+ bResetClipRegion = true;
+ pView->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.IsOver( 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.IsOver( 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->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.IsOver( rRect ))
+ return true;
+ }
+ return false;
+}
+
+void SvxIconChoiceCtrl_Impl::AddSelectedRect( SvxIconChoiceCtrlEntry* pEntry1,
+ SvxIconChoiceCtrlEntry* pEntry2 )
+{
+ DBG_ASSERT(pEntry1 && pEntry2,"SelectEntry: Invalid Entry-Ptr");
+ tools::Rectangle aRect( GetEntryBoundRect( pEntry1 ) );
+ aRect.Union( GetEntryBoundRect( pEntry2 ) );
+ AddSelectedRect( aRect );
+}
+
+void SvxIconChoiceCtrl_Impl::AddSelectedRect( const tools::Rectangle& rRect )
+{
+ tools::Rectangle newRect = rRect;
+ newRect.Justify();
+ aSelectedRectList.push_back( newRect );
+}
+
+void SvxIconChoiceCtrl_Impl::ClearSelectedRectList()
+{
+ aSelectedRectList.clear();
+}
+
+IMPL_LINK_NOARG(SvxIconChoiceCtrl_Impl, AutoArrangeHdl, Timer *, void)
+{
+ aAutoArrangeIdle.Stop();
+ Arrange( IsAutoArrange(), 0, 0 );
+}
+
+IMPL_LINK_NOARG(SvxIconChoiceCtrl_Impl, VisRectChangedHdl, Timer *, void)
+{
+ aVisRectChangedIdle.Stop();
+}
+
+IMPL_LINK_NOARG(SvxIconChoiceCtrl_Impl, DocRectChangedHdl, Timer *, void)
+{
+ aDocRectChangedIdle.Stop();
+}
+
+#ifdef DBG_UTIL
+void SvxIconChoiceCtrl_Impl::SetEntryTextMode( SvxIconChoiceCtrlTextMode eMode, SvxIconChoiceCtrlEntry* pEntry )
+{
+ if( !pEntry )
+ {
+ if( eTextMode != eMode )
+ {
+ eTextMode = eMode;
+ Arrange( true, 0, 0 );
+ }
+ }
+ else
+ {
+ if( pEntry->eTextMode != eMode )
+ {
+ pEntry->eTextMode = eMode;
+ InvalidateEntry( pEntry );
+ pView->Invalidate( GetEntryBoundRect( pEntry ) );
+ AdjustVirtSize( pEntry->aRect );
+ }
+ }
+}
+#endif
+
+// Draw my own focusrect, because the focusrect of the outputdevice has got the inverted color
+// of the background. But what will we see, if the backgroundcolor is gray ? - We will see
+// a gray focusrect on a gray background !!!
+
+void SvxIconChoiceCtrl_Impl::ShowFocus ( tools::Rectangle const & rRect )
+{
+ Color aBkgColor(pView->GetBackground().GetColor());
+ Color aPenColor;
+ sal_uInt16 nColor = ( aBkgColor.GetRed() + aBkgColor.GetGreen() + aBkgColor.GetBlue() ) / 3;
+ if (nColor > 128)
+ aPenColor = COL_BLACK;
+ else
+ aPenColor = COL_WHITE;
+
+ aFocus.aPenColor = aPenColor;
+ aFocus.aRect = rRect;
+}
+
+void SvxIconChoiceCtrl_Impl::DrawFocusRect(vcl::RenderContext& rRenderContext)
+{
+ rRenderContext.SetLineColor(aFocus.aPenColor);
+ rRenderContext.SetFillColor();
+ tools::Polygon aPolygon (aFocus.aRect);
+
+ LineInfo aLineInfo(LineStyle::Dash);
+
+ aLineInfo.SetDashLen(1);
+ aLineInfo.SetDotLen(1);
+ aLineInfo.SetDistance(1);
+ aLineInfo.SetDotCount(1);
+
+ rRenderContext.DrawPolyLine(aPolygon, aLineInfo);
+}
+
+bool SvxIconChoiceCtrl_Impl::IsMnemonicChar( sal_Unicode cChar, sal_uLong& rPos ) const
+{
+ bool bRet = false;
+ const vcl::I18nHelper& rI18nHelper = Application::GetSettings().GetUILocaleI18nHelper();
+ size_t nEntryCount = GetEntryCount();
+ for ( size_t i = 0; i < nEntryCount; ++i )
+ {
+ if ( rI18nHelper.MatchMnemonic( GetEntry( i )->GetText(), cChar ) )
+ {
+ bRet = true;
+ rPos = i;
+ break;
+ }
+ }
+
+ return bRet;
+}
+
+
+IMPL_LINK(SvxIconChoiceCtrl_Impl, UserEventHdl, void*, nId, void )
+{
+ if( nId == EVENTID_ADJUST_SCROLLBARS )
+ {
+ nUserEventAdjustScrBars = nullptr;
+ AdjustScrollBars();
+ }
+ else if( nId == EVENTID_SHOW_CURSOR )
+ {
+ ShowCursor( true );
+ }
+}
+
+void SvxIconChoiceCtrl_Impl::CancelUserEvents()
+{
+ if( nUserEventAdjustScrBars )
+ {
+ Application::RemoveUserEvent( nUserEventAdjustScrBars );
+ nUserEventAdjustScrBars = nullptr;
+ }
+}
+
+void SvxIconChoiceCtrl_Impl::InvalidateEntry( SvxIconChoiceCtrlEntry* pEntry )
+{
+ if( pEntry == pCursor )
+ ShowCursor( false );
+ pView->Invalidate( pEntry->aRect );
+ Center( pEntry );
+ pView->Invalidate( pEntry->aRect );
+ if( pEntry == pCursor )
+ ShowCursor( true );
+}
+
+SvxIconChoiceCtrlEntry* SvxIconChoiceCtrl_Impl::GetFirstSelectedEntry() const
+{
+ if( !GetSelectionCount() )
+ return nullptr;
+
+ if( (nWinBits & WB_HIGHLIGHTFRAME) && (eSelectionMode == SelectionMode::NONE) )
+ {
+ return pCurHighlightFrame;
+ }
+
+ size_t nCount = maEntries.size();
+ if( !pHead )
+ {
+ for( size_t nCur = 0; nCur < nCount; nCur++ )
+ {
+ SvxIconChoiceCtrlEntry* pEntry = maEntries[ nCur ].get();
+ if( pEntry->IsSelected() )
+ {
+ return pEntry;
+ }
+ }
+ }
+ else
+ {
+ SvxIconChoiceCtrlEntry* pEntry = pHead;
+ while( nCount-- )
+ {
+ if( pEntry->IsSelected() )
+ {
+ return pEntry;
+ }
+ pEntry = pEntry->pflink;
+ if( nCount && pEntry == pHead )
+ {
+ OSL_FAIL("SvxIconChoiceCtrl_Impl::GetFirstSelectedEntry > infinite loop!");
+ return nullptr;
+ }
+ }
+ }
+ return nullptr;
+}
+
+void SvxIconChoiceCtrl_Impl::SelectAll()
+{
+ size_t nCount = maEntries.size();
+ for( size_t nCur = 0; nCur < nCount; nCur++ )
+ {
+ SvxIconChoiceCtrlEntry* pEntry = maEntries[ nCur ].get();
+ SelectEntry( pEntry, true/*bSelect*/, true );
+ }
+ nFlags &= ~IconChoiceFlags::AddMode;
+ pAnchor = nullptr;
+}
+
+
+
+
+sal_Int32 SvxIconChoiceCtrl_Impl::GetEntryListPos( SvxIconChoiceCtrlEntry const * pEntry ) const
+{
+ if( !(nFlags & IconChoiceFlags::EntryListPosValid ))
+ const_cast<SvxIconChoiceCtrl_Impl*>(this)->SetListPositions();
+ return pEntry->nPos;
+}
+
+void SvxIconChoiceCtrl_Impl::InitSettings()
+{
+ const StyleSettings& rStyleSettings = pView->GetSettings().GetStyleSettings();
+
+ // unit (from settings) is Point
+ vcl::Font aFont( rStyleSettings.GetFieldFont() );
+ aFont.SetColor( rStyleSettings.GetWindowTextColor() );
+ pView->SetPointFont( aFont );
+ SetDefaultTextSize();
+
+ pView->SetTextColor( rStyleSettings.GetFieldTextColor() );
+ pView->SetTextFillColor();
+
+ pView->SetBackground( rStyleSettings.GetFieldColor());
+
+ 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() );
+ sal_uLong nGrid = GetPredecessorGrid( aNewPos );
+ size_t nCount = maEntries.size();
+ if( nGrid == ULONG_MAX )
+ 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( sal_uLong nCur = 0; nCur < nGrid; nCur++ )
+ pCur = pCur->pflink;
+
+ return pCur;
+}
+
+sal_uLong SvxIconChoiceCtrl_Impl::GetPredecessorGrid( const Point& rPos) const
+{
+ Point aPos( rPos );
+ aPos.AdjustX( -(LROFFS_WINBORDER) );
+ aPos.AdjustY( -(TBOFFS_WINBORDER) );
+ long nMaxCol = aVirtOutputSize.Width() / nGridDX;
+ if( nMaxCol )
+ nMaxCol--;
+ long nGridX = aPos.X() / nGridDX;
+ if( nGridX > nMaxCol )
+ nGridX = nMaxCol;
+ long nGridY = aPos.Y() / nGridDY;
+ long nGridsX = aOutputSize.Width() / nGridDX;
+ sal_uLong nGrid = (nGridY * nGridsX) + nGridX;
+ long nMiddle = (nGridX * nGridDX) + (nGridDX / 2);
+ if( rPos.X() < nMiddle )
+ {
+ if( !nGrid )
+ nGrid = ULONG_MAX;
+ 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.IsInside( 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);
+ long nBorder = 2;
+ if (aImageSize.Width() < 32)
+ nBorder = 1;
+ aBmpRect.AdjustRight(nBorder );
+ aBmpRect.AdjustLeft( -nBorder );
+ aBmpRect.AdjustBottom(nBorder );
+ aBmpRect.AdjustTop( -nBorder );
+
+ DecorationView aDecoView(&rRenderContext);
+ DrawHighlightFrameStyle nDecoFlags;
+ if (bHighlightFramePressed)
+ nDecoFlags = DrawHighlightFrameStyle::In;
+ else
+ nDecoFlags = DrawHighlightFrameStyle::Out;
+ aDecoView.DrawHighlightFrame(aBmpRect, nDecoFlags);
+}
+
+void SvxIconChoiceCtrl_Impl::SetEntryHighlightFrame( SvxIconChoiceCtrlEntry* pEntry,
+ bool bKeepHighlightFlags )
+{
+ if( pEntry == pCurHighlightFrame )
+ return;
+
+ if( !bKeepHighlightFlags )
+ bHighlightFramePressed = false;
+
+ if (pCurHighlightFrame)
+ {
+ tools::Rectangle aInvalidationRect(GetEntryBoundRect(pCurHighlightFrame));
+ aInvalidationRect.expand(5);
+ pCurHighlightFrame = nullptr;
+ pView->Invalidate(aInvalidationRect);
+ }
+
+ pCurHighlightFrame = pEntry;
+ if (pEntry)
+ {
+ tools::Rectangle aInvalidationRect(GetEntryBoundRect(pEntry));
+ aInvalidationRect.expand(5);
+ pView->Invalidate(aInvalidationRect);
+ }
+}
+
+void SvxIconChoiceCtrl_Impl::CallSelectHandler()
+{
+ // When single-click mode is active, the selection handler should be called
+ // synchronously, as the selection is automatically taken away once the
+ // mouse cursor doesn't touch the object any more. Else, we might run into
+ // missing calls to Select if the object is selected from a mouse movement,
+ // because when starting the timer, the mouse cursor might have already left
+ // the object.
+ // In special cases (=>SfxFileDialog!), synchronous calls can be forced via
+ // WB_NOASYNCSELECTHDL.
+ if( nWinBits & (WB_NOASYNCSELECTHDL | WB_HIGHLIGHTFRAME) )
+ {
+ pHdlEntry = nullptr;
+ pView->ClickIcon();
+ //pView->Select();
+ }
+ else
+ aCallSelectHdlIdle.Start();
+}
+
+IMPL_LINK_NOARG(SvxIconChoiceCtrl_Impl, CallSelectHdlHdl, Timer *, void)
+{
+ pHdlEntry = nullptr;
+ pView->ClickIcon();
+ //pView->Select();
+}
+
+void SvxIconChoiceCtrl_Impl::SetOrigin( const Point& rPos )
+{
+ MapMode aMapMode( pView->GetMapMode() );
+ aMapMode.SetOrigin( rPos );
+ pView->SetMapMode( aMapMode );
+}
+
+void SvxIconChoiceCtrl_Impl::CallEventListeners( VclEventId nEvent, void* pData )
+{
+ pView->CallImplEventListeners( nEvent, pData );
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/imivctl2.cxx b/vcl/source/control/imivctl2.cxx
new file mode 100644
index 000000000..284921f8f
--- /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, long nValue,
+ bool bVertical )
+{
+ sal_uInt16 nCount = rList.size();
+ if( !nCount )
+ return 0;
+
+ sal_uInt16 nCurPos = 0;
+ long nPrevValue = LONG_MIN;
+ while( nCount )
+ {
+ const tools::Rectangle& rRect = pView->GetEntryBoundRect( rList[nCurPos] );
+ 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);
+
+ 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 );
+ 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);
+
+ 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 );
+ 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;
+
+ long nCurCol = nX;
+
+ 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 long nPos = static_cast<long>(pView->GetEntryListPos( pStart ));
+ long nEntriesInView = pView->aOutputSize.Height() / pView->nGridDY;
+ nEntriesInView *=
+ ((pView->aOutputSize.Width()+(pView->nGridDX/2)) / pView->nGridDX );
+ long nNewPos = nPos;
+ if( bDown )
+ {
+ nNewPos += nEntriesInView;
+ if( nNewPos >= static_cast<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;
+ }
+ 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;
+
+ long nPrevErr = LONG_MAX;
+
+ SvxIconChoiceCtrlEntry* pPrev = pStart;
+ SvxIconChoiceCtrlEntry* pNext = GoUpDown( pStart, bDown );
+ while( pNext )
+ {
+ long nCur = pView->GetEntryBoundRect( pNext ).Top();
+ 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;
+
+ long nCurRow = nY;
+
+ 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
+{
+ 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;
+
+ long nDX = nX / _pView->nGridDX;
+ 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();
+
+ long nX = rDocPos.X();
+ 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 long nLeft = nGridX * _pView->nGridDX+ LROFFS_WINBORDER;
+ const 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)
+{
+ long ndx = (rSizePixel.Width() - LROFFS_WINBORDER) / nDX;
+ if( ndx < 0 ) ndx *= -1;
+ long ndy = (rSizePixel.Height() - TBOFFS_WINBORDER) / nDY;
+ if( ndy < 0 ) ndy *= -1;
+ return static_cast<sal_uLong>(ndx * ndy);
+}
+
+void IcnGridMap_Impl::OutputSizeChanged()
+{
+ if( !_pGridMap )
+ return;
+
+ sal_uInt16 nCols, nRows;
+ GetMinMapSize( nCols, nRows );
+ if( _pView->nWinBits & WB_ALIGN_TOP )
+ {
+ if( nCols != _nGridCols )
+ Clear();
+ else if( nRows >= _nGridRows )
+ Expand();
+ }
+ else
+ {
+ if( nRows != _nGridRows )
+ Clear();
+ else if( nCols >= _nGridCols )
+ Expand();
+ }
+}
+
+// Independently of the view's alignment (TOP or LEFT), the gridmap
+// should contain the data in a continuous region, to make it possible
+// to copy the whole block if the gridmap needs to be expanded.
+void IcnGridMap_Impl::GetGridCoord( GridId nId, sal_uInt16& rGridX, sal_uInt16& rGridY )
+{
+ Create();
+ if( _pView->nWinBits & WB_ALIGN_TOP )
+ {
+ rGridX = static_cast<sal_uInt16>(nId % _nGridCols);
+ rGridY = static_cast<sal_uInt16>(nId / _nGridCols);
+ }
+ else
+ {
+ rGridX = static_cast<sal_uInt16>(nId / _nGridRows);
+ rGridY = static_cast<sal_uInt16>(nId % _nGridRows);
+ }
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/imp_listbox.cxx b/vcl/source/control/imp_listbox.cxx
new file mode 100644
index 000000000..f264d6868
--- /dev/null
+++ b/vcl/source/control/imp_listbox.cxx
@@ -0,0 +1,3116 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <memory>
+
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/event.hxx>
+#include <vcl/scrbar.hxx>
+#include <vcl/lstbox.hxx>
+#include <vcl/i18nhelp.hxx>
+#include <vcl/naturalsort.hxx>
+
+#include <listbox.hxx>
+#include <controldata.hxx>
+#include <svdata.hxx>
+#include <window.h>
+
+#include <com/sun/star/accessibility/AccessibleRole.hpp>
+
+#include <rtl/instance.hxx>
+#include <sal/log.hxx>
+#include <o3tl/safeint.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;
+
+static constexpr 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::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
+{
+ struct theSorter
+ : public rtl::StaticWithInit< comphelper::string::NaturalStringSorter, theSorter >
+ {
+ comphelper::string::NaturalStringSorter operator () ()
+ {
+ return comphelper::string::NaturalStringSorter(
+ ::comphelper::getProcessComponentContext(),
+ Application::GetSettings().GetLanguageTag().getLocale());
+ }
+ };
+}
+
+namespace vcl
+{
+ sal_Int32 NaturalSortCompare(const OUString &rA, const OUString &rB)
+ {
+ const comphelper::string::NaturalStringSorter &rSorter = theSorter::get();
+ 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 = theSorter::get();
+
+ 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( const OUString& 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;
+}
+
+long ImplEntryList::GetAddedHeight( sal_Int32 i_nEndIndex, sal_Int32 i_nBeginIndex ) const
+{
+ 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 )
+ {
+ long nPosHeight = GetEntryPtr( nIndex )->getHeightWithMargin();
+ if (nHeight > ::std::numeric_limits<long>::max() - nPosHeight)
+ {
+ SAL_WARN( "vcl", "ImplEntryList::GetAddedHeight: truncated");
+ break;
+ }
+ nHeight += nPosHeight;
+ nIndex++;
+ }
+ }
+ else
+ nHeight = 0;
+ return i_nEndIndex > i_nBeginIndex ? nHeight : -nHeight;
+}
+
+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 */ )
+{
+ 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 ),
+ maQuickSelectionEngine( *this )
+{
+ mpEntryList.reset(new ImplEntryList( this ));
+
+ mnTop = 0;
+ mnLeft = 0;
+ mnSelectModifier = 0;
+ mnUserDrawEntry = LISTBOX_ENTRY_NOTFOUND;
+ mbTrack = false;
+ mbTravelSelect = false;
+ mbTrackingSelect = false;
+ mbSelectionChanged = false;
+ mbMouseMoveSelect = false;
+ mbMulti = false;
+ mbStackMode = 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;
+
+ // pb: #106948# explicit mirroring for calc
+ mbMirroring = false;
+
+ mnCurrentPos = LISTBOX_ENTRY_NOTFOUND;
+ mnTrackingSaveSelection = LISTBOX_ENTRY_NOTFOUND;
+
+ SetLineColor();
+ SetTextFillColor();
+ SetBackground( Wallpaper( GetSettings().GetStyleSettings().GetFieldColor() ) );
+
+ ApplySettings(*this);
+ ImplCalcMetrics();
+}
+
+ImplListBoxWindow::~ImplListBoxWindow()
+{
+ disposeOnce();
+}
+
+void ImplListBoxWindow::dispose()
+{
+ mpEntryList.reset();
+ Control::dispose();
+}
+
+void ImplListBoxWindow::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+
+ ApplyControlFont(rRenderContext, rStyleSettings.GetFieldFont());
+ ApplyControlForeground(rRenderContext, rStyleSettings.GetFieldTextColor());
+
+ if (IsControlBackground())
+ rRenderContext.SetBackground(GetControlBackground());
+ else
+ rRenderContext.SetBackground(rStyleSettings.GetFieldColor());
+}
+
+void ImplListBoxWindow::ImplCalcMetrics()
+{
+ mnMaxWidth = 0;
+ mnMaxTxtWidth = 0;
+ mnMaxImgWidth = 0;
+ mnMaxImgTxtWidth= 0;
+ mnMaxImgHeight = 0;
+
+ mnTextHeight = static_cast<sal_uInt16>(GetTextHeight());
+ mnMaxTxtHeight = mnTextHeight + gnBorder;
+ mnMaxHeight = mnMaxTxtHeight;
+
+ if ( maUserItemSize.Height() > mnMaxHeight )
+ mnMaxHeight = static_cast<sal_uInt16>(maUserItemSize.Height());
+ if ( maUserItemSize.Width() > mnMaxWidth )
+ mnMaxWidth= static_cast<sal_uInt16>(maUserItemSize.Width());
+
+ for ( sal_Int32 n = mpEntryList->GetEntryCount(); n; )
+ {
+ ImplEntryType* pEntry = mpEntryList->GetMutableEntryPtr( --n );
+ ImplUpdateEntryMetrics( *pEntry );
+ }
+
+ if( mnCurrentPos != LISTBOX_ENTRY_NOTFOUND )
+ {
+ Size aSz( GetOutputSizePixel().Width(), mpEntryList->GetEntryPtr( mnCurrentPos )->getHeightWithMargin() );
+ maFocusRect.SetSize( aSz );
+ }
+}
+
+void ImplListBoxWindow::Clear()
+{
+ mpEntryList->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;
+ long nEntryWidth;
+ long nEntryHeight;
+ long nTextWidth;
+ long nImgWidth;
+ long nImgHeight;
+};
+
+}
+
+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, nullptr, SalLayoutFlags::GlyphItemsOnly);
+ if (!pLayout)
+ return nullptr;
+
+ const SalLayoutGlyphs* pGlyphs = pLayout->GetGlyphs();
+ if (!pGlyphs)
+ return nullptr;
+
+ // Remember the calculation result.
+ maStrGlyphs = *pGlyphs;
+
+ 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(this);
+ 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(mpEntryList->GetEntryCount() < LISTBOX_MAX_ENTRIES);
+
+ ImplClearLayoutData();
+ sal_Int32 nNewPos = mpEntryList->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();
+ mpEntryList->RemoveEntry( nPos );
+ if( mnCurrentPos >= mpEntryList->GetEntryCount() )
+ mnCurrentPos = LISTBOX_ENTRY_NOTFOUND;
+ ImplCalcMetrics();
+}
+
+void ImplListBoxWindow::SetEntryFlags( sal_Int32 nPos, ListBoxEntryFlags nFlags )
+{
+ mpEntryList->SetEntryFlags( nPos, nFlags );
+ ImplEntryType* pEntry = mpEntryList->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
+{
+ long nY = gnBorder;
+
+ sal_Int32 nSelect = mnTop;
+ const ImplEntryType* pEntry = mpEntryList->GetEntryPtr( nSelect );
+ while (pEntry)
+ {
+ long nEntryHeight = pEntry->getHeightWithMargin();
+ if (rPoint.Y() <= nEntryHeight + nY)
+ break;
+ nY += nEntryHeight;
+ pEntry = mpEntryList->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( mpEntryList->GetAddedHeight( i_nEntry, mnTop ) <
+ PixelToLogic( GetSizePixel() ).Height() )
+ {
+ bRet = true;
+ }
+ }
+
+ return bRet;
+}
+
+long ImplListBoxWindow::GetEntryHeightWithMargin() const
+{
+ long nMargin = ImplGetSVData()->maNWFData.mnListBoxEntryMargin;
+ return mnMaxHeight + nMargin;
+}
+
+sal_Int32 ImplListBoxWindow::GetLastVisibleEntry() const
+{
+ sal_Int32 nPos = mnTop;
+ long nWindowHeight = GetSizePixel().Height();
+ sal_Int32 nCount = mpEntryList->GetEntryCount();
+ long nDiff;
+ for( nDiff = 0; nDiff < nWindowHeight && nPos < nCount; nDiff = mpEntryList->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() )
+ {
+ if ( mbStackMode && IsMouseMoveSelect() && IsReallyVisible() )
+ {
+ if ( rMEvt.GetPosPixel().Y() < 0 )
+ {
+ DeselectAll();
+ mnCurrentPos = LISTBOX_ENTRY_NOTFOUND;
+ SetTopEntry( 0 );
+ if ( mbStackMode )
+ {
+ mbTravelSelect = true;
+ mnSelectModifier = rMEvt.GetModifier();
+ ImplCallSelect();
+ mbTravelSelect = false;
+ }
+
+ }
+ }
+ }
+ else if ( ( ( !mbMulti && IsMouseMoveSelect() ) || mbStackMode ) && mpEntryList->GetEntryCount() )
+ {
+ tools::Rectangle aRect( Point(), GetOutputSizePixel() );
+ if( aRect.IsInside( rMEvt.GetPosPixel() ) )
+ {
+ if ( IsMouseMoveSelect() )
+ {
+ sal_Int32 nSelect = GetEntryPosForPoint( rMEvt.GetPosPixel() );
+ if( nSelect == LISTBOX_ENTRY_NOTFOUND )
+ nSelect = mpEntryList->GetEntryCount() - 1;
+ nSelect = std::min( nSelect, GetLastVisibleEntry() );
+ nSelect = std::min( nSelect, static_cast<sal_Int32>( mpEntryList->GetEntryCount() - 1 ) );
+ // Select only visible Entries with MouseMove, otherwise Tracking...
+ if ( IsVisible( nSelect ) &&
+ mpEntryList->IsEntrySelectable( nSelect ) &&
+ ( ( nSelect != mnCurrentPos ) || !GetEntryList()->GetSelectedEntryCount() || ( nSelect != GetEntryList()->GetSelectedEntryPos( 0 ) ) ) )
+ {
+ mbTrackingSelect = true;
+ if ( SelectEntries( nSelect, LET_TRACKING ) )
+ {
+ if ( mbStackMode )
+ {
+ mbTravelSelect = true;
+ mnSelectModifier = rMEvt.GetModifier();
+ ImplCallSelect();
+ mbTravelSelect = false;
+ }
+ // When list box selection change by mouse move, notify
+ // VclEventId::ListboxSelect vcl event.
+ else
+ {
+ 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;
+
+ if ( mbStackMode && ( mpEntryList->GetSelectionAnchor() == LISTBOX_ENTRY_NOTFOUND ) )
+ mpEntryList->SetSelectionAnchor( 0 );
+
+ 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( (mpEntryList->IsEntryPosSelected( nPos ) != bSelect) && mpEntryList->IsEntrySelectable( nPos ) )
+ {
+ 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();
+ }
+ }
+ mpEntryList->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
+ {
+ mpEntryList->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() && mpEntryList->IsEntrySelectable( nSelect ) )
+ {
+ bool bFocusChanged = false;
+
+ // here (Single-ListBox) only one entry can be deselected
+ if( !mbMulti )
+ {
+ sal_Int32 nDeselect = mpEntryList->GetSelectedEntryPos( 0 );
+ if( nSelect != nDeselect )
+ {
+ SelectEntry( nSelect, true );
+ mpEntryList->SetLastSelected( nSelect );
+ bFocusChanged = true;
+ bSelectionChanged = true;
+ }
+ }
+ // MultiListBox without Modifier
+ else if( mbSimpleMode && !bCtrl && !bShift )
+ {
+ sal_Int32 nEntryCount = mpEntryList->GetEntryCount();
+ for ( sal_Int32 nPos = 0; nPos < nEntryCount; nPos++ )
+ {
+ bool bSelect = nPos == nSelect;
+ if ( mpEntryList->IsEntryPosSelected( nPos ) != bSelect )
+ {
+ SelectEntry( nPos, bSelect );
+ bFocusChanged = true;
+ bSelectionChanged = true;
+ }
+ }
+ mpEntryList->SetLastSelected( nSelect );
+ mpEntryList->SetSelectionAnchor( nSelect );
+ }
+ // MultiListBox only with CTRL/SHIFT or not in SimpleMode
+ else if( ( !mbSimpleMode /* && !bShift */ ) || ( (mbSimpleMode && ( bCtrl || bShift )) || mbStackMode ) )
+ {
+ // Space for selection change
+ if( !bShift && ( ( eLET == LET_KEYSPACE ) || ( eLET == LET_MBDOWN ) ) )
+ {
+ bool bSelect = ( mbStackMode && IsMouseMoveSelect() ) || !mpEntryList->IsEntryPosSelected( nSelect );
+ if ( mbStackMode )
+ {
+ sal_Int32 n;
+ if ( bSelect )
+ {
+ // All entries before nSelect must be selected...
+ for ( n = 0; n < nSelect; n++ )
+ SelectEntry( n, true );
+ }
+ if ( !bSelect )
+ {
+ for ( n = nSelect+1; n < mpEntryList->GetEntryCount(); n++ )
+ SelectEntry( n, false );
+ }
+ }
+ SelectEntry( nSelect, bSelect );
+ mpEntryList->SetLastSelected( nSelect );
+ mpEntryList->SetSelectionAnchor( mbStackMode ? 0 : nSelect );
+ if ( !mpEntryList->IsEntryPosSelected( nSelect ) )
+ mpEntryList->SetSelectionAnchor( LISTBOX_ENTRY_NOTFOUND );
+ bFocusChanged = true;
+ bSelectionChanged = true;
+ }
+ else if( ( ( eLET == LET_TRACKING ) && ( nSelect != mnCurrentPos ) ) ||
+ ( (bShift||mbStackMode) && ( ( eLET == LET_KEYMOVE ) || ( eLET == LET_MBDOWN ) ) ) )
+ {
+ mnCurrentPos = nSelect;
+ bFocusChanged = true;
+
+ sal_Int32 nAnchor = mpEntryList->GetSelectionAnchor();
+ if( ( nAnchor == LISTBOX_ENTRY_NOTFOUND ) && ( mpEntryList->GetSelectedEntryCount() || mbStackMode ) )
+ {
+ nAnchor = mbStackMode ? 0 : mpEntryList->GetSelectedEntryPos( mpEntryList->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 ( !mpEntryList->IsEntryPosSelected( n ) )
+ {
+ SelectEntry( n, true );
+ bSelectionChanged = true;
+ }
+ }
+
+ // if appropriate some more has to be deselected...
+ sal_Int32 nLast = mpEntryList->GetLastSelected();
+ if ( nLast != LISTBOX_ENTRY_NOTFOUND )
+ {
+ if ( ( nLast > nSelect ) && ( nLast > nAnchor ) )
+ {
+ for ( sal_Int32 n = nSelect+1; n <= nLast; n++ )
+ {
+ if ( mpEntryList->IsEntryPosSelected( n ) )
+ {
+ SelectEntry( n, false );
+ bSelectionChanged = true;
+ }
+ }
+ }
+ else if ( ( nLast < nSelect ) && ( nLast < nAnchor ) )
+ {
+ for ( sal_Int32 n = nLast; n < nSelect; n++ )
+ {
+ if ( mpEntryList->IsEntryPosSelected( n ) )
+ {
+ SelectEntry( n, false );
+ bSelectionChanged = true;
+ }
+ }
+ }
+ }
+ mpEntryList->SetLastSelected( nSelect );
+ }
+ }
+ else if( eLET != LET_TRACKING )
+ {
+ ImplHideFocusRect();
+ Invalidate();
+ bFocusChanged = true;
+ }
+ }
+ else if( bShift )
+ {
+ bFocusChanged = true;
+ }
+
+ if( bSelectionChanged )
+ mbSelectionChanged = true;
+
+ if( bFocusChanged )
+ {
+ long nHeightDiff = mpEntryList->GetAddedHeight( nSelect, mnTop );
+ maFocusRect.SetPos( Point( 0, nHeightDiff ) );
+ Size aSz( maFocusRect.GetWidth(),
+ mpEntryList->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.IsInside( 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 )
+ {
+ long nHeightDiff = mpEntryList->GetAddedHeight( mnCurrentPos, mnTop );
+ maFocusRect.SetPos( Point( 0, nHeightDiff ) );
+ Size aSz( maFocusRect.GetWidth(),
+ mpEntryList->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>(mpEntryList->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>( mpEntryList->GetEntryCount() - 1 ) );
+ }
+
+ if ( bInside )
+ {
+ if ( ( nSelect != mnCurrentPos ) || !GetEntryList()->GetSelectedEntryCount() )
+ {
+ mbTrackingSelect = true;
+ if ( SelectEntries( nSelect, LET_TRACKING, bShift, bCtrl ) )
+ {
+ if ( mbStackMode )
+ {
+ mbTravelSelect = true;
+ mnSelectModifier = rTEvt.GetMouseEvent().GetModifier();
+ ImplCallSelect();
+ mbTravelSelect = false;
+ }
+ }
+ mbTrackingSelect = false;
+ }
+ }
+ else
+ {
+ if ( !mbMulti && GetEntryList()->GetSelectedEntryCount() )
+ {
+ mbTrackingSelect = true;
+ SelectEntry( GetEntryList()->GetSelectedEntryPos( 0 ), false );
+ mbTrackingSelect = false;
+ }
+ else if ( mbStackMode )
+ {
+ if ( ( rTEvt.GetMouseEvent().GetPosPixel().X() > 0 ) && ( rTEvt.GetMouseEvent().GetPosPixel().X() < aRect.Right() ) )
+ {
+ if ( ( rTEvt.GetMouseEvent().GetPosPixel().Y() < 0 ) || ( rTEvt.GetMouseEvent().GetPosPixel().Y() > GetOutputSizePixel().Height() ) )
+ {
+ bool bSelectionChanged = false;
+ if ( ( rTEvt.GetMouseEvent().GetPosPixel().Y() < 0 )
+ && !mnCurrentPos )
+ {
+ if ( mpEntryList->IsEntryPosSelected( 0 ) )
+ {
+ SelectEntry( 0, false );
+ bSelectionChanged = true;
+ nSelect = LISTBOX_ENTRY_NOTFOUND;
+
+ }
+ }
+ else
+ {
+ mbTrackingSelect = true;
+ bSelectionChanged = SelectEntries( nSelect, LET_TRACKING, bShift, bCtrl );
+ mbTrackingSelect = false;
+ }
+
+ if ( bSelectionChanged )
+ {
+ mbSelectionChanged = true;
+ mbTravelSelect = true;
+ mnSelectModifier = rTEvt.GetMouseEvent().GetModifier();
+ ImplCallSelect();
+ mbTravelSelect = false;
+ }
+ }
+ }
+ }
+ }
+ mnCurrentPos = nSelect;
+ if ( mnCurrentPos == LISTBOX_ENTRY_NOTFOUND )
+ {
+ ImplHideFocusRect();
+ }
+ else
+ {
+ long nHeightDiff = mpEntryList->GetAddedHeight( mnCurrentPos, mnTop );
+ maFocusRect.SetPos( Point( 0, nHeightDiff ) );
+ Size aSz( maFocusRect.GetWidth(), mpEntryList->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 = mpEntryList->FindFirstSelectable( 0 );
+ }
+ else if ( mnCurrentPos )
+ {
+ // search first selectable above the current position
+ nSelect = mpEntryList->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 = mpEntryList->FindFirstSelectable( 0 );
+ }
+ else if ( (mnCurrentPos+1) < mpEntryList->GetEntryCount() )
+ {
+ // search first selectable below the current position
+ nSelect = mpEntryList->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 = mpEntryList->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 = mpEntryList->FindFirstSelectable( mnTop );
+ }
+ bDone = true;
+ }
+ maQuickSelectionEngine.Reset();
+ }
+ break;
+
+ case KEY_PAGEDOWN:
+ {
+ if ( IsReadOnly() )
+ {
+ SetTopEntry( GetLastVisibleEntry() );
+ }
+ else if ( !bCtrl && !bMod2 )
+ {
+ if( mnCurrentPos == LISTBOX_ENTRY_NOTFOUND )
+ {
+ nSelect = mpEntryList->FindFirstSelectable( 0 );
+ }
+ else if ( (mnCurrentPos+1) < mpEntryList->GetEntryCount() )
+ {
+ sal_Int32 nCount = mpEntryList->GetEntryCount();
+ sal_Int32 nCurVis = GetLastVisibleEntry() - mnTop;
+ sal_Int32 nTmp = std::min( nCurVis, nCount );
+ nTmp += mnTop - 1;
+ if( mnCurrentPos == nTmp && mnCurrentPos != nCount - 1 )
+ {
+ long nTmp2 = std::min( static_cast<long>(nCount-nCurVis), static_cast<long>(static_cast<long>(mnTop)+static_cast<long>(nCurVis)-1) );
+ nTmp2 = std::max( 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 = mpEntryList->FindFirstSelectable( nTmp, false );
+ }
+ bDone = true;
+ }
+ maQuickSelectionEngine.Reset();
+ }
+ break;
+
+ case KEY_HOME:
+ {
+ if ( IsReadOnly() )
+ {
+ SetTopEntry( 0 );
+ }
+ else if ( !bCtrl && !bMod2 && mnCurrentPos )
+ {
+ nSelect = mpEntryList->FindFirstSelectable( mpEntryList->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 = mpEntryList->FindFirstSelectable( 0 );
+ }
+ else if ( (mnCurrentPos+1) < mpEntryList->GetEntryCount() )
+ {
+ sal_Int32 nCount = mpEntryList->GetEntryCount();
+ nSelect = mpEntryList->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 ) || mbStackMode ) )
+ {
+ 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 = mpEntryList->GetEntryCount();
+ for( sal_Int32 i = 0; i < nEntryCount; i++ )
+ SelectEntry( i, true );
+
+ // 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 )
+ && ( ( !mpEntryList->IsEntryPosSelected( nSelect ) )
+ || ( eLET == LET_KEYSPACE )
+ )
+ )
+ {
+ SAL_WARN_IF( mpEntryList->IsEntryPosSelected( nSelect ) && !mbMulti, "vcl", "ImplListBox: Selecting same Entry" );
+ sal_Int32 nCount = mpEntryList->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 ( mpEntryList->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 < mpEntryList->GetEntryCount(), "ImplListBoxWindow::SelectEntry: how that?" );
+ sal_Int32 nCount = mpEntryList->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 = mpEntryList->GetEntryPtr( nPos );
+ if (!pEntry)
+ return;
+
+ long nWidth = GetOutputSizePixel().Width();
+ long nY = mpEntryList->GetAddedHeight(nPos, mnTop);
+ tools::Rectangle aRect(Point(0, nY), Size(nWidth, pEntry->getHeightWithMargin()));
+
+ bool bSelected = mpEntryList->IsEntryPosSelected(nPos);
+ if (bSelected)
+ {
+ rRenderContext.SetTextColor(!IsEnabled() ? rStyleSettings.GetDisableColor() : rStyleSettings.GetHighlightTextColor());
+ rRenderContext.SetFillColor(rStyleSettings.GetHighlightColor());
+ rRenderContext.SetLineColor();
+ rRenderContext.DrawRect(aRect);
+ }
+ else
+ {
+ ApplySettings(rRenderContext);
+ if (!IsEnabled())
+ rRenderContext.SetTextColor(rStyleSettings.GetDisableColor());
+ }
+ rRenderContext.SetTextFillColor();
+
+ if (IsUserDrawEnabled())
+ {
+ mbInUserDraw = true;
+ mnUserDrawEntry = nPos;
+ aRect.AdjustLeft( -mnLeft );
+ if (nPos < GetEntryList()->GetMRUCount())
+ nPos = GetEntryList()->FindEntry(GetEntryList()->GetEntryText(nPos));
+ nPos = nPos - GetEntryList()->GetMRUCount();
+
+ UserDrawEvent aUDEvt(this, &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 = mpEntryList->GetEntryPtr(nPos);
+ if (!pEntry)
+ return;
+
+ 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
+
+ long nY = mpEntryList->GetAddedHeight(nPos, mnTop);
+ Size aImgSz;
+
+ if (bDrawImage && mpEntryList->HasImages())
+ {
+ Image aImage = mpEntryList->GetEntryImage(nPos);
+ if (!!aImage)
+ {
+ aImgSz = aImage.GetSizePixel();
+ Point aPtImg(gnBorder - mnLeft, nY + ((nEntryHeight - aImgSz.Height()) / 2));
+
+ // pb: #106948# explicit mirroring for calc
+ if (mbMirroring)
+ // right aligned
+ aPtImg.setX( mnMaxWidth + gnBorder - aImgSz.Width() - mnLeft );
+
+ 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(mpEntryList->GetEntryText(nPos));
+ if (!aStr.isEmpty())
+ {
+ 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 (mpEntryList->HasEntryImage(nPos) || IsUserDrawEnabled())
+ {
+ long nImageWidth = std::max(mnMaxImgWidth, maUserItemSize.Width());
+ aTextRect.AdjustLeft(nImageWidth + IMG_TXT_DISTANCE );
+ }
+
+ // pb: #106948# explicit mirroring for calc
+ if (mbMirroring)
+ {
+ // right aligned
+ aTextRect.SetLeft( nMaxWidth + gnBorder - rRenderContext.GetTextWidth(aStr) - mnLeft );
+ if (aImgSz.Width() > 0)
+ aTextRect.AdjustLeft( -(aImgSz.Width() + 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
+{
+ mpControlData->mpLayoutData.reset( new vcl::ControlLayoutData );
+ const_cast<ImplListBoxWindow*>(this)->Invalidate(tools::Rectangle(Point(0, 0), GetOutputSize()));
+}
+
+void ImplListBoxWindow::ImplDoPaint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ sal_Int32 nCount = mpEntryList->GetEntryCount();
+
+ bool bShowFocusRect = mbHasFocusRect;
+ if (mbHasFocusRect)
+ ImplHideFocusRect();
+
+ long nY = 0; // + gnBorder;
+ long nHeight = GetOutputSizePixel().Height();// - mnMaxHeight + gnBorder;
+
+ for (sal_Int32 i = mnTop; i < nCount && nY < nHeight + mnMaxHeight; i++)
+ {
+ const ImplEntryType* pEntry = mpEntryList->GetEntryPtr(i);
+ long nEntryHeight = pEntry->getHeightWithMargin();
+ if (nY + nEntryHeight >= rRect.Top() &&
+ nY <= rRect.Bottom() + mnMaxHeight)
+ {
+ ImplPaint(rRenderContext, i);
+ }
+ nY += nEntryHeight;
+ }
+
+ long nHeightDiff = mpEntryList->GetAddedHeight(mnCurrentPos, mnTop);
+ maFocusRect.SetPos(Point(0, nHeightDiff));
+ Size aSz(maFocusRect.GetWidth(), mpEntryList->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 = mpEntryList->GetEntryCount()-mnTop;
+ 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(), mpEntryList->GetEntryHeight( mnCurrentPos ) );
+ maFocusRect.SetSize( aSz );
+ }
+
+ if ( bShowFocusRect )
+ ImplShowFocusRect();
+
+ ImplClearLayoutData();
+}
+
+void ImplListBoxWindow::GetFocus()
+{
+ sal_Int32 nPos = mnCurrentPos;
+ if ( nPos == LISTBOX_ENTRY_NOTFOUND )
+ nPos = 0;
+ long nHeightDiff = mpEntryList->GetAddedHeight( nPos, mnTop );
+ maFocusRect.SetPos( Point( 0, nHeightDiff ) );
+ Size aSz( maFocusRect.GetWidth(), mpEntryList->GetEntryHeight( nPos ) );
+ maFocusRect.SetSize( aSz );
+ ImplShowFocusRect();
+ Control::GetFocus();
+}
+
+void ImplListBoxWindow::LoseFocus()
+{
+ ImplHideFocusRect();
+ Control::LoseFocus();
+}
+
+void ImplListBoxWindow::SetTopEntry( sal_Int32 nTop )
+{
+ if( mpEntryList->GetEntryCount() == 0 )
+ return;
+
+ long nWHeight = PixelToLogic( GetSizePixel() ).Height();
+
+ sal_Int32 nLastEntry = mpEntryList->GetEntryCount()-1;
+ if( nTop > nLastEntry )
+ nTop = nLastEntry;
+ const ImplEntryType* pLast = mpEntryList->GetEntryPtr( nLastEntry );
+ while( nTop > 0 && mpEntryList->GetAddedHeight( nLastEntry, nTop-1 ) + pLast->getHeightWithMargin() <= nWHeight )
+ nTop--;
+
+ if ( nTop != mnTop )
+ {
+ ImplClearLayoutData();
+ long nDiff = mpEntryList->GetAddedHeight( mnTop, nTop );
+ PaintImmediately();
+ ImplHideFocusRect();
+ mnTop = nTop;
+ Scroll( 0, nDiff );
+ PaintImmediately();
+ if( HasFocus() )
+ ImplShowFocusRect();
+ maScrollHdl.Call( this );
+ }
+}
+
+void ImplListBoxWindow::ShowProminentEntry( sal_Int32 nEntryPos )
+{
+ SetTopEntry( nEntryPos );
+}
+
+void ImplListBoxWindow::SetLeftIndent( long n )
+{
+ ScrollHorz( n - mnLeft );
+}
+
+void ImplListBoxWindow::ScrollHorz( long n )
+{
+ long nDiff = 0;
+ if ( n > 0 )
+ {
+ long nWidth = GetOutputSizePixel().Width();
+ if( ( mnMaxWidth - mnLeft + n ) > nWidth )
+ nDiff = n;
+ }
+ else if ( n < 0 )
+ {
+ if( mnLeft )
+ {
+ 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 = mpEntryList->GetEntryPtr( nItem );
+ Size aSz( GetSizePixel().Width(), pEntry ? pEntry->getHeightWithMargin() : GetEntryHeightWithMargin() );
+ long nY = mpEntryList->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(*this);
+ ImplCalcMetrics();
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::UpdateMode )
+ {
+ if ( IsUpdateMode() && IsReallyVisible() )
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlFont )
+ {
+ ApplySettings(*this);
+ ImplCalcMetrics();
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ ApplySettings(*this);
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ApplySettings(*this);
+ 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(*this);
+ ImplCalcMetrics();
+ Invalidate();
+ }
+}
+
+DrawTextFlags ImplListBoxWindow::ImplGetTextStyle() const
+{
+ DrawTextFlags nTextStyle = DrawTextFlags::VCenter;
+
+ if (mpEntryList->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)
+{
+ 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 )
+ {
+ long nWidth = static_cast<sal_uInt16>(aOutSz.Width());
+ if ( mbVScroll )
+ nWidth -= mpVScrollBar->GetSizePixel().Width();
+
+ 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();
+ long nSBWidth = GetSettings().GetStyleSettings().GetScrollBarSize();
+ nSBWidth = CalcZoom( nSBWidth );
+
+ Size aInnerSz( aOutSz );
+ if ( mbVScroll )
+ aInnerSz.AdjustWidth( -nSBWidth );
+ if ( mbHScroll )
+ aInnerSz.AdjustHeight( -nSBWidth );
+
+ // pb: #106948# explicit mirroring for calc
+ // Scrollbar on left or right side?
+ bool bMirroring = maLBWindow->IsMirroring();
+ Point aWinPos( bMirroring && mbVScroll ? nSBWidth : 0, 0 );
+ maLBWindow->SetPosSizePixel( aWinPos, aInnerSz );
+
+ // ScrollBarBox
+ if( mbVScroll && mbHScroll )
+ {
+ Point aBoxPos( bMirroring ? 0 : 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( bMirroring ? 0 : 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( ( bMirroring && mbVScroll ) ? nSBWidth : 0, aOutSz.Height() - nSBWidth );
+ mpHScrollBar->SetPosSizePixel( aHPos, Size( aInnerSz.Width(), nSBWidth ) );
+ mpHScrollBar->Show();
+ }
+ else
+ {
+ mpHScrollBar->Hide();
+ SetLeftIndent( 0 );
+ }
+}
+
+void ImplListBox::StateChanged( StateChangedType nType )
+{
+ if ( nType == StateChangedType::InitShow )
+ {
+ ImplCheckScrollBars();
+ }
+ else if ( ( nType == StateChangedType::UpdateMode ) || ( nType == StateChangedType::Data ) )
+ {
+ bool bUpdate = IsUpdateMode();
+ maLBWindow->SetUpdateMode( bUpdate );
+ if ( bUpdate && IsReallyVisible() )
+ ImplCheckScrollBars();
+ }
+ else if( nType == StateChangedType::Enable )
+ {
+ mpHScrollBar->Enable( IsEnabled() );
+ mpVScrollBar->Enable( IsEnabled() );
+ mpScrollBarBox->Enable( IsEnabled() );
+ maLBWindow->Enable( IsEnabled() );
+
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::Zoom )
+ {
+ maLBWindow->SetZoom( GetZoom() );
+ Resize();
+ }
+ else if ( nType == StateChangedType::ControlFont )
+ {
+ maLBWindow->SetControlFont( GetControlFont() );
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ maLBWindow->SetControlForeground( GetControlForeground() );
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ maLBWindow->SetControlBackground( GetControlBackground() );
+ }
+ else if( nType == StateChangedType::Mirroring )
+ {
+ maLBWindow->EnableRTL( IsRTLEnabled() );
+ mpHScrollBar->EnableRTL( IsRTLEnabled() );
+ mpVScrollBar->EnableRTL( IsRTLEnabled() );
+ ImplResizeControls();
+ }
+
+ Control::StateChanged( nType );
+}
+
+bool ImplListBox::EventNotify( NotifyEvent& rNEvt )
+{
+ bool bDone = false;
+ if ( rNEvt.GetType() == MouseNotifyEvent::COMMAND )
+ {
+ const CommandEvent& rCEvt = *rNEvt.GetCommandEvent();
+ if ( rCEvt.GetCommand() == CommandEventId::Wheel )
+ {
+ const CommandWheelData* pData = rCEvt.GetWheelData();
+ if( !pData->GetModifier() && ( pData->GetMode() == CommandWheelMode::SCROLL ) )
+ {
+ bDone = HandleScrollCommand( rCEvt, mpHScrollBar, mpVScrollBar );
+ }
+ }
+ else if (rCEvt.GetCommand() == CommandEventId::Gesture)
+ {
+ bDone = HandleScrollCommand(rCEvt, mpHScrollBar, mpVScrollBar);
+ }
+ }
+
+ return bDone || Window::EventNotify( rNEvt );
+}
+
+const Wallpaper& ImplListBox::GetDisplayBackground() const
+{
+ return maLBWindow->GetDisplayBackground();
+}
+
+bool ImplListBox::HandleWheelAsCursorTravel( const CommandEvent& rCEvt )
+{
+ bool bDone = false;
+ if ( rCEvt.GetCommand() == CommandEventId::Wheel )
+ {
+ const CommandWheelData* pData = rCEvt.GetWheelData();
+ if( !pData->GetModifier() && ( pData->GetMode() == CommandWheelMode::SCROLL ) )
+ {
+ sal_uInt16 nKey = ( pData->GetDelta() < 0 ) ? KEY_DOWN : KEY_UP;
+ KeyEvent aKeyEvent( 0, vcl::KeyCode( nKey ) );
+ bDone = ProcessKeyInput( aKeyEvent );
+ }
+ }
+ return bDone;
+}
+
+void ImplListBox::SetMRUEntries( const OUString& 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 = rEntries.getToken( 0, cSep, nIndex );
+ // Accept only existing entries
+ if ( GetEntryList()->FindEntry( aEntry ) != LISTBOX_ENTRY_NOTFOUND )
+ {
+ ImplEntryType* pNewEntry = new ImplEntryType( aEntry );
+ maLBWindow->InsertEntry(nMRUCount++, pNewEntry, false);
+ bChanges = true;
+ }
+ }
+ while ( nIndex >= 0 );
+
+ if ( bChanges )
+ {
+ maLBWindow->GetEntryList()->SetMRUCount( nMRUCount );
+ SetSeparatorPos( nMRUCount ? nMRUCount-1 : 0 );
+ CompatStateChanged( StateChangedType::Data );
+ }
+}
+
+OUString ImplListBox::GetMRUEntries( sal_Unicode cSep ) const
+{
+ OUStringBuffer aEntries;
+ for ( sal_Int32 n = 0; n < GetEntryList()->GetMRUCount(); n++ )
+ {
+ aEntries.append(GetEntryList()->GetEntryText( n ));
+ if( n < ( GetEntryList()->GetMRUCount() - 1 ) )
+ aEntries.append(cSep);
+ }
+ return aEntries.makeStringAndClear();
+}
+
+void ImplListBox::SetEdgeBlending(bool bNew)
+{
+ if(mbEdgeBlending != bNew)
+ {
+ mbEdgeBlending = bNew;
+ maLBWindow->SetEdgeBlending(GetEdgeBlending());
+ }
+}
+
+ImplWin::ImplWin( vcl::Window* pParent, WinBits nWinStyle ) :
+ Control ( pParent, nWinStyle )
+{
+ if ( IsNativeControlSupported(ControlType::Listbox, ControlPart::Entire)
+ && ! IsNativeControlSupported(ControlType::Listbox, ControlPart::ButtonDown) )
+ SetBackground();
+ else
+ SetBackground( Wallpaper( GetSettings().GetStyleSettings().GetFieldColor() ) );
+
+ ImplGetWindowImpl()->mbUseNativeFocus = ImplGetSVData()->maNWFData.mbNoFocusRects;
+
+ mbEdgeBlending = false;
+ mnItemPos = LISTBOX_ENTRY_NOTFOUND;
+}
+
+void ImplWin::MouseButtonDown( const MouseEvent& )
+{
+ if( IsEnabled() )
+ {
+ maMBDownHdl.Call(this);
+ }
+}
+
+void ImplWin::FillLayoutData() const
+{
+ mpControlData->mpLayoutData.reset( new vcl::ControlLayoutData );
+ ImplWin* pThis = const_cast<ImplWin*>(this);
+ pThis->ImplDraw(*pThis, true);
+}
+
+bool ImplWin::PreNotify( NotifyEvent& rNEvt )
+{
+ if( rNEvt.GetType() == MouseNotifyEvent::MOUSEMOVE )
+ {
+ const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent();
+ if( pMouseEvt && (pMouseEvt->IsEnterWindow() || pMouseEvt->IsLeaveWindow()) )
+ {
+ // trigger redraw as mouse over state has changed
+ if ( IsNativeControlSupported(ControlType::Listbox, ControlPart::Entire)
+ && ! IsNativeControlSupported(ControlType::Listbox, ControlPart::ButtonDown) )
+ {
+ GetParent()->GetWindow( GetWindowType::Border )->Invalidate( InvalidateFlags::NoErase );
+ }
+ }
+ }
+
+ return Control::PreNotify(rNEvt);
+}
+
+void ImplWin::ImplDraw(vcl::RenderContext& rRenderContext, bool bLayout)
+{
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+
+ if (!bLayout)
+ {
+ bool bNativeOK = false;
+ bool bHasFocus = HasFocus();
+ bool bIsEnabled = IsEnabled();
+
+ ControlState nState = ControlState::ENABLED;
+ if (rRenderContext.IsNativeControlSupported(ControlType::Listbox, ControlPart::Entire)
+ && rRenderContext.IsNativeControlSupported(ControlType::Listbox, ControlPart::HasBackgroundTexture) )
+ {
+ // Repaint the (focused) area similarly to
+ // ImplSmallBorderWindowView::DrawWindow() in
+ // vcl/source/window/brdwin.cxx
+ vcl::Window *pWin = GetParent();
+
+ ImplControlValue aControlValue;
+ bIsEnabled &= pWin->IsEnabled();
+ if ( !bIsEnabled )
+ nState &= ~ControlState::ENABLED;
+ bHasFocus |= pWin->HasFocus();
+ if ( bHasFocus )
+ nState |= ControlState::FOCUSED;
+
+ // The listbox is painted over the entire control including the
+ // border, but ImplWin does not contain the border => correction
+ // needed.
+ sal_Int32 nLeft, nTop, nRight, nBottom;
+ pWin->GetBorder( nLeft, nTop, nRight, nBottom );
+ Point aPoint( -nLeft, -nTop );
+ tools::Rectangle aCtrlRegion( aPoint - GetPosPixel(), pWin->GetSizePixel() );
+
+ bool bMouseOver = false;
+ vcl::Window *pChild = pWin->GetWindow( GetWindowType::FirstChild );
+ while( pChild )
+ {
+ bMouseOver = pChild->IsMouseOver();
+ if (bMouseOver)
+ break;
+ pChild = pChild->GetWindow( GetWindowType::Next );
+ }
+ if( bMouseOver )
+ nState |= ControlState::ROLLOVER;
+
+ // if parent has no border, then nobody has drawn the background
+ // since no border window exists. so draw it here.
+ WinBits nParentStyle = pWin->GetStyle();
+ if( ! (nParentStyle & WB_BORDER) || (nParentStyle & WB_NOBORDER) )
+ {
+ tools::Rectangle aParentRect( Point( 0, 0 ), pWin->GetSizePixel() );
+ pWin->DrawNativeControl( ControlType::Listbox, ControlPart::Entire, aParentRect,
+ nState, aControlValue, OUString() );
+ }
+
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::Listbox, ControlPart::Entire, aCtrlRegion,
+ nState, aControlValue, OUString());
+ }
+
+ if (bIsEnabled)
+ {
+ if (bHasFocus && !ImplGetSVData()->maNWFData.mbDDListBoxNoTextArea)
+ {
+ if ( !ImplGetSVData()->maNWFData.mbNoFocusRects )
+ {
+ rRenderContext.SetFillColor( rStyleSettings.GetHighlightColor() );
+ rRenderContext.SetTextColor( rStyleSettings.GetHighlightTextColor() );
+ }
+ else
+ {
+ rRenderContext.SetLineColor();
+ rRenderContext.SetFillColor();
+ rRenderContext.SetTextColor( rStyleSettings.GetFieldTextColor() );
+ }
+ rRenderContext.DrawRect( maFocusRect );
+ }
+ else
+ {
+ Color aColor;
+ if (IsControlForeground())
+ aColor = GetControlForeground();
+ else if (ImplGetSVData()->maNWFData.mbDDListBoxNoTextArea)
+ {
+ if( bNativeOK && (nState & ControlState::ROLLOVER) )
+ aColor = rStyleSettings.GetButtonRolloverTextColor();
+ else
+ aColor = rStyleSettings.GetButtonTextColor();
+ }
+ else
+ {
+ if( bNativeOK && (nState & ControlState::ROLLOVER) )
+ aColor = rStyleSettings.GetFieldRolloverTextColor();
+ else
+ aColor = rStyleSettings.GetFieldTextColor();
+ }
+ rRenderContext.SetTextColor(aColor);
+ if (!bNativeOK)
+ rRenderContext.Erase(maFocusRect);
+ }
+ }
+ else // Disabled
+ {
+ rRenderContext.SetTextColor(rStyleSettings.GetDisableColor());
+ if (!bNativeOK)
+ rRenderContext.Erase(maFocusRect);
+ }
+ }
+
+ DrawEntry(rRenderContext, bLayout);
+}
+
+void ImplWin::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+
+ ApplyControlFont(rRenderContext, rStyleSettings.GetFieldFont());
+ ApplyControlForeground(rRenderContext, rStyleSettings.GetFieldTextColor());
+
+ if (IsControlBackground())
+ rRenderContext.SetBackground(GetControlBackground());
+ else
+ rRenderContext.SetBackground(rStyleSettings.GetFieldColor());
+}
+
+void ImplWin::Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& )
+{
+ ImplDraw(rRenderContext);
+}
+
+void ImplWin::DrawEntry(vcl::RenderContext& rRenderContext, bool bLayout)
+{
+ 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 );
+ }
+
+ MetricVector* pVector = bLayout ? &mpControlData->mpLayoutData->m_aUnicodeBoundRects : nullptr;
+ OUString* pDisplayText = bLayout ? &mpControlData->mpLayoutData->m_aDisplayText : nullptr;
+ rRenderContext.DrawText( aTextRect, maString, nTextStyle, pVector, pDisplayText );
+ }
+
+ if( HasFocus() && !bLayout )
+ ShowFocus( maFocusRect );
+}
+
+void ImplWin::Resize()
+{
+ Control::Resize();
+ maFocusRect.SetSize( GetOutputSizePixel() );
+ Invalidate();
+}
+
+void ImplWin::GetFocus()
+{
+ ShowFocus( maFocusRect );
+ if( ImplGetSVData()->maNWFData.mbNoFocusRects &&
+ IsNativeWidgetEnabled() &&
+ IsNativeControlSupported( ControlType::Listbox, ControlPart::Entire ) )
+ {
+ vcl::Window* pWin = GetParent()->GetWindow( GetWindowType::Border );
+ if( ! pWin )
+ pWin = GetParent();
+ pWin->Invalidate();
+ }
+ else
+ Invalidate();
+ Control::GetFocus();
+}
+
+void ImplWin::LoseFocus()
+{
+ HideFocus();
+ if( ImplGetSVData()->maNWFData.mbNoFocusRects &&
+ IsNativeWidgetEnabled() &&
+ IsNativeControlSupported( ControlType::Listbox, ControlPart::Entire ) )
+ {
+ vcl::Window* pWin = GetParent()->GetWindow( GetWindowType::Border );
+ if( ! pWin )
+ pWin = GetParent();
+ pWin->Invalidate();
+ }
+ else
+ Invalidate();
+ Control::LoseFocus();
+}
+
+void ImplWin::ShowFocus(const tools::Rectangle& rRect)
+{
+ if (IsNativeControlSupported(ControlType::Listbox, ControlPart::Focus))
+ {
+ ImplControlValue aControlValue;
+
+ vcl::Window *pWin = GetParent();
+ tools::Rectangle aParentRect(Point(0, 0), pWin->GetSizePixel());
+ pWin->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
+{
+ mpImplLB = nullptr;
+ mnDDLineCount = 0;
+ mbAutoWidth = false;
+
+ mnPopupModeStartSaveSelection = LISTBOX_ENTRY_NOTFOUND;
+
+ vcl::Window * pBorderWindow = ImplGetBorderWindow();
+ if( pBorderWindow )
+ {
+ SetAccessibleRole(accessibility::AccessibleRole::PANEL);
+ pBorderWindow->SetAccessibleRole(accessibility::AccessibleRole::WINDOW);
+ }
+ else
+ {
+ SetAccessibleRole(accessibility::AccessibleRole::WINDOW);
+ }
+
+}
+
+ImplListBoxFloatingWindow::~ImplListBoxFloatingWindow()
+{
+ disposeOnce();
+}
+
+void ImplListBoxFloatingWindow::dispose()
+{
+ mpImplLB.clear();
+ FloatingWindow::dispose();
+}
+
+
+bool ImplListBoxFloatingWindow::PreNotify( NotifyEvent& rNEvt )
+{
+ if( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
+ {
+ if( !GetParent()->HasChildPathFocus( true ) )
+ EndPopupMode();
+ }
+
+ return FloatingWindow::PreNotify( rNEvt );
+}
+
+void ImplListBoxFloatingWindow::setPosSizePixel( long nX, long nY, long nWidth, 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()
+{
+ 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 );
+ 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
+ long nSBWidth = GetSettings().GetStyleSettings().GetScrollBarSize();
+ aFloatSz.AdjustWidth(nSBWidth );
+ }
+
+ long nDesktopWidth = GetDesktopRectPixel().getWidth();
+ if (aFloatSz.Width() > nDesktopWidth)
+ // Don't exceed the desktop width.
+ aFloatSz.setWidth( nDesktopWidth );
+ }
+
+ if ( aFloatSz.Height() > nMaxHeight )
+ aFloatSz.setHeight( nMaxHeight );
+
+ // Minimal height, in case height is not set to Float height.
+ // The parent of FloatWin must be DropDown-Combo/Listbox.
+ Size aParentSz = GetParent()->GetSizePixel();
+ if( (!mnDDLineCount || !nLines) && ( aFloatSz.Height() < aParentSz.Height() ) )
+ aFloatSz.setHeight( aParentSz.Height() );
+
+ // do not get narrower than the parent...
+ if( aFloatSz.Width() < aParentSz.Width() )
+ aFloatSz.setWidth( aParentSz.Width() );
+
+ // align height to entries...
+ long nInnerHeight = aFloatSz.Height() - nTop - nBottom;
+ 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.
+ 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->ImplIsAntiparallel() )
+ pGrandparentOutDev->ReMirror( aRect );
+
+ // mouse-button right: close the List-Box-Float-win and don't stop the handling fdo#84795
+ StartPopupMode( aRect, FloatWinPopupFlags::Down | FloatWinPopupFlags::NoHorzPlacement | FloatWinPopupFlags::AllMouseButtonClose );
+
+ if( nPos != LISTBOX_ENTRY_NOTFOUND )
+ mpImplLB->ShowProminentEntry( nPos );
+
+ if( bStartTracking )
+ mpImplLB->GetMainWindow()->EnableMouseMoveSelect( true );
+
+ if ( mpImplLB->GetMainWindow()->IsGrabFocusAllowed() )
+ mpImplLB->GetMainWindow()->GrabFocus();
+
+ mpImplLB->GetMainWindow()->ImplClearLayoutData();
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/ivctrl.cxx b/vcl/source/control/ivctrl.cxx
new file mode 100644
index 000000000..b995ddfde
--- /dev/null
+++ b/vcl/source/control/ivctrl.cxx
@@ -0,0 +1,633 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/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>
+
+using namespace ::com::sun::star::accessibility;
+
+/*****************************************************************************
+|
+| class : SvxIconChoiceCtrlEntry
+|
+\*****************************************************************************/
+
+SvxIconChoiceCtrlEntry::SvxIconChoiceCtrlEntry( const OUString& rText,
+ const Image& rImage )
+ : aImage(rImage)
+ , aText(rText)
+ , pUserData(nullptr)
+ , 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 ) )
+{
+ SetLineColor();
+ _pImpl->InitSettings();
+ _pImpl->SetPositionMode( SvxIconChoiceCtrlPositionMode::AutoArrange );
+}
+
+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(*this)) //FIXME
+ {
+ Control::SetPointFont(*this, rFont); //FIXME
+ _pImpl->FontModified();
+ }
+}
+
+SvxIconChoiceCtrlEntry* SvtIconChoiceCtrl::GetEntry( const Point& rPixPos ) const
+{
+ Point aPos( rPixPos );
+ aPos -= GetMapMode().GetOrigin();
+ return const_cast<SvtIconChoiceCtrl*>(this)->_pImpl->GetEntry( aPos );
+}
+
+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;
+}
+
+void SvtIconChoiceCtrl::CreateAutoMnemonics( MnemonicGenerator& _rUsedMnemonics )
+{
+ _pImpl->CreateAutoMnemonics( &_rUsedMnemonics );
+}
+
+SvxIconChoiceCtrlEntry* SvtIconChoiceCtrl::GetSelectedEntry() const
+{
+ return _pImpl ? _pImpl->GetFirstSelectedEntry() : nullptr;
+}
+
+void SvtIconChoiceCtrl::ClickIcon()
+{
+ GetSelectedEntry();
+ _aClickIconHdl.Call( this );
+}
+
+void SvtIconChoiceCtrl::SetChoiceWithCursor()
+{
+ _pImpl->SetChoiceWithCursor();
+}
+
+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::InvalidateEntry( SvxIconChoiceCtrlEntry* pEntry )
+{
+ _pImpl->InvalidateEntry( 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().IsTransparent() ||
+ (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 );
+}
+
+void SvtIconChoiceCtrl::SetSelectionMode( SelectionMode eMode )
+{
+ _pImpl->SetSelectionMode( eMode );
+}
+
+tools::Rectangle SvtIconChoiceCtrl::GetBoundingBox( SvxIconChoiceCtrlEntry* pEntry ) const
+{
+ return _pImpl->GetEntryBoundRect( pEntry );
+}
+
+void SvtIconChoiceCtrl::FillLayoutData() const
+{
+ CreateLayoutData();
+ const_cast<SvtIconChoiceCtrl*>(this)->Invalidate();
+}
+
+tools::Rectangle SvtIconChoiceCtrl::GetEntryCharacterBounds( const sal_Int32 _nEntryPos, const sal_Int32 _nCharacterIndex ) const
+{
+ tools::Rectangle aRect;
+
+ Pair aEntryCharacterRange = GetLineStartEnd( _nEntryPos );
+ if ( aEntryCharacterRange.A() + _nCharacterIndex < aEntryCharacterRange.B() )
+ {
+ aRect = GetCharacterBounds( aEntryCharacterRange.A() + _nCharacterIndex );
+ }
+
+ return aRect;
+}
+
+void SvtIconChoiceCtrl::SetNoSelection()
+{
+ _pImpl->SetNoSelection();
+}
+
+void SvtIconChoiceCtrl::CallImplEventListeners(VclEventId nEvent, void* pData)
+{
+ CallEventListeners(nEvent, pData);
+}
+css::uno::Reference< XAccessible > SvtIconChoiceCtrl::CreateAccessible()
+{
+ vcl::Window* pParent = GetAccessibleParentWindow();
+ DBG_ASSERT( pParent, "SvTreeListBox::CreateAccessible - accessible parent not found" );
+
+ css::uno::Reference< XAccessible > xAccessible;
+ if ( pParent )
+ {
+ css::uno::Reference< XAccessible > xAccParent = pParent->GetAccessible();
+ if ( xAccParent.is() )
+ {
+ css::uno::Reference< css::awt::XWindowPeer > xHoldAlive(GetComponentInterface());
+ xAccessible = _pImpl->GetAccessibleFactory().createAccessibleIconChoiceCtrl( *this, xAccParent );
+ }
+ }
+ return xAccessible;
+}
+
+struct VerticalTabPageData
+{
+ OString sId;
+ SvxIconChoiceCtrlEntry* pEntry;
+ VclPtr<vcl::Window> xPage; ///< the TabPage itself
+};
+
+VerticalTabControl::VerticalTabControl(vcl::Window* pParent)
+ : VclHBox(pParent)
+ , m_xChooser(VclPtr<SvtIconChoiceCtrl>::Create(this, WB_3DLOOK | WB_ICON | WB_BORDER |
+ WB_NOCOLUMNHEADER | WB_HIGHLIGHTFRAME |
+ WB_NODRAGSELECTION | WB_TABSTOP | WB_CLIPCHILDREN |
+ WB_ALIGN_LEFT | WB_NOHSCROLL))
+ , m_xBox(VclPtr<VclVBox>::Create(this))
+{
+ SetStyle(GetStyle() | WB_DIALOGCONTROL);
+ SetType(WindowType::VERTICALTABCONTROL);
+ m_xChooser->SetSelectionMode(SelectionMode::Single);
+ m_xChooser->SetClickHdl(LINK(this, VerticalTabControl, ChosePageHdl_Impl));
+ m_xChooser->set_width_request(110);
+ m_xChooser->set_height_request(400);
+ m_xChooser->SetSizePixel(Size(110, 400));
+ m_xBox->set_vexpand(true);
+ m_xBox->set_hexpand(true);
+ m_xBox->set_expand(true);
+ m_xBox->Show();
+ m_xChooser->Show();
+}
+
+VerticalTabControl::~VerticalTabControl()
+{
+ disposeOnce();
+}
+
+void VerticalTabControl::dispose()
+{
+ m_xChooser.disposeAndClear();
+ m_xBox.disposeAndClear();
+ VclHBox::dispose();
+}
+
+IMPL_LINK_NOARG(VerticalTabControl, ChosePageHdl_Impl, SvtIconChoiceCtrl*, void)
+{
+ SvxIconChoiceCtrlEntry *pEntry = m_xChooser->GetSelectedEntry();
+ if (!pEntry)
+ pEntry = m_xChooser->GetCursor();
+
+ VerticalTabPageData* pData = GetPageData(pEntry);
+
+ if (pData->sId != m_sCurrentPageId)
+ SetCurPageId(pData->sId);
+}
+
+void VerticalTabControl::ActivatePage()
+{
+ m_aActivateHdl.Call( this );
+}
+
+bool VerticalTabControl::DeactivatePage()
+{
+ return !m_aDeactivateHdl.IsSet() || m_aDeactivateHdl.Call(this);
+}
+
+VerticalTabPageData* VerticalTabControl::GetPageData(const SvxIconChoiceCtrlEntry* pEntry) const
+{
+ VerticalTabPageData* pRet = nullptr;
+ for (auto & pData : maPageList)
+ {
+ if (pData->pEntry == pEntry)
+ {
+ pRet = pData.get();
+ break;
+ }
+ }
+ return pRet;
+}
+
+VerticalTabPageData* VerticalTabControl::GetPageData(const OString& rId) const
+{
+ VerticalTabPageData* pRet = nullptr;
+ for (auto & pData : maPageList)
+ {
+ if (pData->sId == rId)
+ {
+ pRet = pData.get();
+ break;
+ }
+ }
+ return pRet;
+}
+
+void VerticalTabControl::SetCurPageId(const OString& rId)
+{
+ OString sOldPageId = GetCurPageId();
+ if (sOldPageId == rId)
+ return;
+
+ VerticalTabPageData* pOldData = GetPageData(sOldPageId);
+ if (pOldData && pOldData->xPage)
+ {
+ if (!DeactivatePage())
+ return;
+ pOldData->xPage->Hide();
+ }
+
+ m_sCurrentPageId = "";
+
+ VerticalTabPageData* pNewData = GetPageData(rId);
+ if (pNewData && pNewData->xPage)
+ {
+ m_sCurrentPageId = rId;
+ m_xChooser->SetCursor(pNewData->pEntry);
+
+ ActivatePage();
+ pNewData->xPage->Show();
+ }
+}
+
+OString VerticalTabControl::GetPageId(sal_uInt16 nIndex) const
+{
+ return maPageList[nIndex]->sId;
+}
+
+void VerticalTabControl::InsertPage(const rtl::OString &rIdent, const rtl::OUString& rLabel, const Image& rImage,
+ const rtl::OUString& rTooltip, VclPtr<vcl::Window> xPage, int nPos)
+{
+ SvxIconChoiceCtrlEntry* pEntry = m_xChooser->InsertEntry(rLabel, rImage);
+ pEntry->SetQuickHelpText(rTooltip);
+ m_xChooser->ArrangeIcons();
+ VerticalTabPageData* pNew;
+ if (nPos == -1)
+ {
+ maPageList.emplace_back(new VerticalTabPageData);
+ pNew = maPageList.back().get();
+ }
+ else
+ {
+ maPageList.emplace(maPageList.begin() + nPos, new VerticalTabPageData);
+ pNew = maPageList[nPos].get();
+ }
+ pNew->sId = rIdent;
+ pNew->pEntry = pEntry;
+ pNew->xPage = xPage;
+ Size aOrigPrefSize(m_xBox->get_preferred_size());
+ Size aPagePrefSize(xPage->get_preferred_size());
+ m_xBox->set_width_request(std::max(aOrigPrefSize.Width(), aPagePrefSize.Width()));
+ m_xBox->set_height_request(std::max(aOrigPrefSize.Height(), aPagePrefSize.Height()));
+ pNew->xPage->Hide();
+}
+
+void VerticalTabControl::RemovePage(const rtl::OString &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(const OString& rPageId) const
+{
+ VerticalTabPageData* pData = GetPageData(rPageId);
+ if (!pData)
+ return TAB_PAGE_NOTFOUND;
+ return m_xChooser->GetEntryListPos(pData->pEntry);
+}
+
+VclPtr<vcl::Window> VerticalTabControl::GetPage(const OString& rPageId)
+{
+ VerticalTabPageData* pData = GetPageData(rPageId);
+ if (!pData)
+ return nullptr;
+ return pData->xPage;
+}
+
+OUString VerticalTabControl::GetPageText(const OString& rPageId) const
+{
+ VerticalTabPageData* pData = GetPageData(rPageId);
+ if (!pData)
+ return OUString();
+ return pData->pEntry->GetText();
+}
+
+void VerticalTabControl::SetPageText(const OString& rPageId, const OUString& rText)
+{
+ VerticalTabPageData* pData = GetPageData(rPageId);
+ if (!pData)
+ return;
+ pData->pEntry->SetText(rText);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/listbox.cxx b/vcl/source/control/listbox.cxx
new file mode 100644
index 000000000..47c4b792c
--- /dev/null
+++ b/vcl/source/control/listbox.cxx
@@ -0,0 +1,1444 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/lstbox.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/uitest/uiobject.hxx>
+#include <sal/log.hxx>
+
+#include <svdata.hxx>
+#include <controldata.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>
+
+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;
+ mnSaveValue = LISTBOX_ENTRY_NOTFOUND;
+ 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() )
+ {
+ const ImplEntryList* pEntryList = mpImplLB->GetEntryList();
+ if ( pEntryList->IsEntryPosSelected( nChanged ) )
+ {
+ // FIXME? This should've been turned into an ImplPaintEntry some time ago...
+ if ( nChanged < pEntryList->GetMRUCount() )
+ nChanged = pEntryList->FindEntry( pEntryList->GetEntryText( nChanged ) );
+ mpImplWin->SetItemPos( nChanged );
+ mpImplWin->SetString( mpImplLB->GetEntryList()->GetEntryText( nChanged ) );
+ if( mpImplLB->GetEntryList()->HasImages() )
+ {
+ Image aImage = mpImplLB->GetEntryList()->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() )
+ {
+ 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() )
+ {
+ 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, DrawFlags 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 & DrawFlags::Mono )
+ {
+ pDev->SetTextColor( COL_BLACK );
+ }
+ else
+ {
+ if ( !IsEnabled() )
+ {
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ pDev->SetTextColor( rStyleSettings.GetDisableColor() );
+ }
+ else
+ {
+ pDev->SetTextColor( GetTextColor() );
+ }
+ }
+
+ const long nOnePixel = GetDrawPixel( pDev, 1 );
+ const 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();
+ long nTextHeight = pDev->GetTextHeight();
+ long nTextWidth = pDev->GetTextWidth( aText );
+ 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
+ {
+ 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)) )
+ {
+ 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->SetSettings( GetSettings() ); // If not yet set...
+ mpImplWin->ApplySettings(*mpImplWin);
+
+ mpBtn->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( long nX, long nY, long nWidth, 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
+ long nSBWidth = GetSettings().GetStyleSettings().GetScrollBarSize();
+ 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() );
+ 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
+{
+ mpControlData->mpLayoutData.reset( new vcl::ControlLayoutData );
+ 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 );
+ }
+}
+
+long ListBox::GetIndexForPoint( const Point& rPoint, sal_Int32& rPos ) const
+{
+ if( !HasLayoutData() )
+ FillLayoutData();
+
+ // Check whether rPoint fits at all
+ long nIndex = Control::GetIndexForPoint( rPoint );
+ if( nIndex != -1 )
+ {
+ // Point must be either in main list window
+ // or in impl window (dropdown case)
+ ImplListBoxWindow* rMain = mpImplLB->GetMainWindow();
+
+ // Convert coordinates to ImplListBoxWindow pixel coordinate space
+ Point aConvPoint = LogicToPixel( rPoint );
+ aConvPoint = OutputToAbsoluteScreenPixel( aConvPoint );
+ aConvPoint = rMain->AbsoluteScreenToOutputPixel( aConvPoint );
+ aConvPoint = rMain->PixelToLogic( aConvPoint );
+
+ // Try to find entry
+ sal_Int32 nEntry = rMain->GetEntryPosForPoint( aConvPoint );
+ if( nEntry == LISTBOX_ENTRY_NOTFOUND )
+ {
+ // Not found, maybe dropdown case
+ if( mpImplWin && mpImplWin->IsReallyVisible() )
+ {
+ // Convert to impl window pixel coordinates
+ aConvPoint = LogicToPixel( rPoint );
+ aConvPoint = OutputToAbsoluteScreenPixel( aConvPoint );
+ aConvPoint = mpImplWin->AbsoluteScreenToOutputPixel( aConvPoint );
+
+ // Check whether converted point is inside impl window
+ Size aImplWinSize = mpImplWin->GetOutputSizePixel();
+ if( aConvPoint.X() >= 0 && aConvPoint.Y() >= 0 && aConvPoint.X() < aImplWinSize.Width() && aConvPoint.Y() < aImplWinSize.Height() )
+ {
+ // Inside the impl window, the position is the current item pos
+ rPos = mpImplWin->GetItemPos();
+ }
+ else
+ nIndex = -1;
+ }
+ else
+ nIndex = -1;
+ }
+ else
+ rPos = nEntry;
+
+ SAL_WARN_IF( nIndex == -1, "vcl", "found index for point, but relative index failed" );
+ }
+
+ // Get line relative index
+ if( nIndex != -1 )
+ nIndex = ToRelativeLineIndex( nIndex );
+
+ return nIndex;
+}
+
+void ListBox::StateChanged( StateChangedType nType )
+{
+ if( nType == StateChangedType::ReadOnly )
+ {
+ if( mpImplWin )
+ mpImplWin->Enable( !IsReadOnly() );
+ if( mpBtn )
+ mpBtn->Enable( !IsReadOnly() );
+ }
+ else if( nType == StateChangedType::Enable )
+ {
+ mpImplLB->Enable( IsEnabled() );
+ if( mpImplWin )
+ {
+ mpImplWin->Enable( IsEnabled() );
+ if ( IsNativeControlSupported(ControlType::Listbox, ControlPart::Entire)
+ && ! IsNativeControlSupported(ControlType::Listbox, ControlPart::ButtonDown) )
+ {
+ GetWindow( GetWindowType::Border )->Invalidate( InvalidateFlags::NoErase );
+ }
+ else
+ mpImplWin->Invalidate();
+ }
+ if( mpBtn )
+ mpBtn->Enable( IsEnabled() );
+ }
+ else if( nType == StateChangedType::UpdateMode )
+ {
+ mpImplLB->SetUpdateMode( IsUpdateMode() );
+ }
+ else if ( nType == StateChangedType::Zoom )
+ {
+ mpImplLB->SetZoom( GetZoom() );
+ if ( mpImplWin )
+ {
+ mpImplWin->SetZoom( GetZoom() );
+ mpImplWin->SetFont( mpImplLB->GetMainWindow()->GetFont() );
+ mpImplWin->Invalidate();
+ }
+ Resize();
+ }
+ else if ( nType == StateChangedType::ControlFont )
+ {
+ mpImplLB->SetControlFont( GetControlFont() );
+ if ( mpImplWin )
+ {
+ mpImplWin->SetControlFont( GetControlFont() );
+ mpImplWin->SetFont( mpImplLB->GetMainWindow()->GetFont() );
+ mpImplWin->Invalidate();
+ }
+ Resize();
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ mpImplLB->SetControlForeground( GetControlForeground() );
+ if ( mpImplWin )
+ {
+ mpImplWin->SetControlForeground( GetControlForeground() );
+ mpImplWin->SetTextColor( GetControlForeground() );
+ mpImplWin->SetFont( mpImplLB->GetMainWindow()->GetFont() );
+ mpImplWin->Invalidate();
+ }
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ mpImplLB->SetControlBackground( GetControlBackground() );
+ if ( mpImplWin )
+ {
+ if ( mpImplWin->IsNativeControlSupported(ControlType::Listbox, ControlPart::Entire) )
+ {
+ // Transparent background
+ mpImplWin->SetBackground();
+ mpImplWin->SetControlBackground();
+ }
+ else
+ {
+ mpImplWin->SetBackground( mpImplLB->GetMainWindow()->GetControlBackground() );
+ mpImplWin->SetControlBackground( mpImplLB->GetMainWindow()->GetControlBackground() );
+ }
+ mpImplWin->SetFont( mpImplLB->GetMainWindow()->GetFont() );
+ mpImplWin->Invalidate();
+ }
+ }
+ else if ( nType == StateChangedType::Style )
+ {
+ SetStyle( ImplInitStyle( GetStyle() ) );
+ mpImplLB->GetMainWindow()->EnableSort( ( GetStyle() & WB_SORT ) != 0 );
+ bool bSimpleMode = ( GetStyle() & WB_SIMPLEMODE ) != 0;
+ mpImplLB->SetMultiSelectionSimpleMode( bSimpleMode );
+ }
+ else if( nType == StateChangedType::Mirroring )
+ {
+ if( mpBtn )
+ {
+ mpBtn->EnableRTL( IsRTLEnabled() );
+ ImplInitDropDownButton( mpBtn );
+ }
+ mpImplLB->EnableRTL( IsRTLEnabled() );
+ if( mpImplWin )
+ mpImplWin->EnableRTL( IsRTLEnabled() );
+ Resize();
+ }
+
+ Control::StateChanged( nType );
+}
+
+bool ListBox::PreNotify( NotifyEvent& rNEvt )
+{
+ bool bDone = false;
+ if ( mpImplLB )
+ {
+ if( ( rNEvt.GetType() == MouseNotifyEvent::KEYINPUT ) && ( rNEvt.GetWindow() == mpImplWin ) )
+ {
+ KeyEvent aKeyEvt = *rNEvt.GetKeyEvent();
+ switch( aKeyEvt.GetKeyCode().GetCode() )
+ {
+ case KEY_DOWN:
+ {
+ if( mpFloatWin && !mpFloatWin->IsInPopupMode() &&
+ aKeyEvt.GetKeyCode().IsMod2() )
+ {
+ CallEventListeners( VclEventId::DropdownPreOpen );
+ mpBtn->SetPressed( true );
+ mpFloatWin->StartFloat( false );
+ CallEventListeners( VclEventId::DropdownOpen );
+ bDone = true;
+ }
+ else
+ {
+ bDone = mpImplLB->ProcessKeyInput( aKeyEvt );
+ }
+ }
+ break;
+ case KEY_UP:
+ {
+ if( mpFloatWin && mpFloatWin->IsInPopupMode() &&
+ aKeyEvt.GetKeyCode().IsMod2() )
+ {
+ mpFloatWin->EndPopupMode();
+ bDone = true;
+ }
+ else
+ {
+ bDone = mpImplLB->ProcessKeyInput( aKeyEvt );
+ }
+ }
+ break;
+ case KEY_RETURN:
+ {
+ if( IsInDropDown() )
+ {
+ mpImplLB->ProcessKeyInput( aKeyEvt );
+ bDone = true;
+ }
+ }
+ break;
+
+ default:
+ {
+ bDone = mpImplLB->ProcessKeyInput( aKeyEvt );
+ }
+ }
+ }
+ else if ( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
+ {
+ if ( IsInDropDown() && !HasChildPathFocus( true ) )
+ mpFloatWin->EndPopupMode();
+ }
+ else if ( (rNEvt.GetType() == MouseNotifyEvent::COMMAND) &&
+ (rNEvt.GetCommandEvent()->GetCommand() == CommandEventId::Wheel) &&
+ (rNEvt.GetWindow() == mpImplWin) )
+ {
+ MouseWheelBehaviour nWheelBehavior( GetSettings().GetMouseSettings().GetWheelBehavior() );
+ if ( ( nWheelBehavior == MouseWheelBehaviour::ALWAYS )
+ || ( ( nWheelBehavior == MouseWheelBehaviour::FocusOnly )
+ && HasChildPathFocus()
+ )
+ )
+ {
+ bDone = mpImplLB->HandleWheelAsCursorTravel( *rNEvt.GetCommandEvent() );
+ }
+ else
+ {
+ bDone = false; // Don't consume this event, let the default handling take it (i.e. scroll the context)
+ }
+ }
+ }
+
+ return bDone || Control::PreNotify( rNEvt );
+}
+
+void ListBox::Select()
+{
+ ImplCallEventListenersAndHandler( VclEventId::ListboxSelect, [this] () { maSelectHdl.Call(*this); } );
+}
+
+void ListBox::DoubleClick()
+{
+ ImplCallEventListenersAndHandler( VclEventId::ListboxDoubleClick, {} );
+}
+
+void ListBox::Clear()
+{
+ if (!mpImplLB)
+ return;
+ mpImplLB->Clear();
+ if( IsDropDownBox() )
+ {
+ mpImplWin->SetItemPos( LISTBOX_ENTRY_NOTFOUND );
+ mpImplWin->SetString( OUString() );
+ Image aImage;
+ mpImplWin->SetImage( aImage );
+ mpImplWin->Invalidate();
+ }
+ CallEventListeners( VclEventId::ListboxItemRemoved, reinterpret_cast<void*>(-1) );
+}
+
+void ListBox::SetNoSelection()
+{
+ mpImplLB->SetNoSelection();
+ if( IsDropDownBox() )
+ {
+ mpImplWin->SetItemPos( LISTBOX_ENTRY_NOTFOUND );
+ mpImplWin->SetString( OUString() );
+ Image aImage;
+ mpImplWin->SetImage( aImage );
+ mpImplWin->Invalidate();
+ }
+}
+
+sal_Int32 ListBox::InsertEntry( const OUString& rStr, sal_Int32 nPos )
+{
+ sal_Int32 nRealPos = mpImplLB->InsertEntry( nPos + mpImplLB->GetEntryList()->GetMRUCount(), rStr );
+ nRealPos = sal::static_int_cast<sal_Int32>(nRealPos - mpImplLB->GetEntryList()->GetMRUCount());
+ CallEventListeners( VclEventId::ListboxItemAdded, reinterpret_cast<void*>(nRealPos) );
+ return nRealPos;
+}
+
+sal_Int32 ListBox::InsertEntry( const OUString& rStr, const Image& rImage, sal_Int32 nPos )
+{
+ sal_Int32 nRealPos = mpImplLB->InsertEntry( nPos + mpImplLB->GetEntryList()->GetMRUCount(), rStr, rImage );
+ nRealPos = sal::static_int_cast<sal_Int32>(nRealPos - mpImplLB->GetEntryList()->GetMRUCount());
+ CallEventListeners( VclEventId::ListboxItemAdded, reinterpret_cast<void*>(nRealPos) );
+ return nRealPos;
+}
+
+void ListBox::RemoveEntry( sal_Int32 nPos )
+{
+ mpImplLB->RemoveEntry( nPos + mpImplLB->GetEntryList()->GetMRUCount() );
+ CallEventListeners( VclEventId::ListboxItemRemoved, reinterpret_cast<void*>(nPos) );
+}
+
+Image ListBox::GetEntryImage( sal_Int32 nPos ) const
+{
+ if ( mpImplLB && mpImplLB->GetEntryList()->HasEntryImage( nPos ) )
+ return mpImplLB->GetEntryList()->GetEntryImage( nPos );
+ return Image();
+}
+
+sal_Int32 ListBox::GetEntryPos( const OUString& 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 || !mpImplLB->GetEntryList())
+ 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( const OUString& 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.TopLeft().X(), aOffset.TopLeft().Y() );
+ return aRect;
+}
+
+void ListBox::EnableMultiSelection( bool bMulti )
+{
+ EnableMultiSelection( bMulti, false );
+}
+
+void ListBox::EnableMultiSelection( bool bMulti, bool bStackSelection )
+{
+ mpImplLB->EnableMultiSelection( bMulti, bStackSelection );
+
+ // WB_SIMPLEMODE:
+ // The MultiListBox behaves just like a normal ListBox
+ // MultiSelection is possible via corresponding additional keys
+ bool bSimpleMode = ( GetStyle() & WB_SIMPLEMODE ) != 0;
+ mpImplLB->SetMultiSelectionSimpleMode( bSimpleMode );
+
+ // In a MultiSelection, we can't see us travelling without focus
+ if ( mpFloatWin )
+ mpImplLB->GetMainWindow()->AllowGrabFocus( bMulti );
+}
+
+bool ListBox::IsMultiSelectionEnabled() const
+{
+ return mpImplLB->IsMultiSelectionEnabled();
+}
+
+Size ListBox::CalcMinimumSize() const
+{
+ Size aSz;
+
+ if (!mpImplLB)
+ return aSz;
+
+ aSz = CalcSubEditSize();
+
+ bool bAddScrollWidth = false;
+
+ if (IsDropDownBox())
+ {
+ aSz.AdjustHeight(4 ); // add a space between entry and border
+ aSz.AdjustWidth(4 ); // add a little breathing space
+ bAddScrollWidth = true;
+ }
+ else
+ bAddScrollWidth = (GetStyle() & WB_VSCROLL) == WB_VSCROLL;
+
+ if (bAddScrollWidth)
+ {
+ // Try native borders; scrollbar size may not be a good indicator
+ // See how large the edit area inside is to estimate what is needed for the dropdown
+ ImplControlValue aControlValue;
+ tools::Rectangle aContent, aBound;
+ Size aTestSize( 100, 20 );
+ tools::Rectangle aArea( Point(), aTestSize );
+ if( GetNativeControlRegion( ControlType::Listbox, ControlPart::SubEdit, aArea, ControlState::NONE,
+ aControlValue, aBound, aContent) )
+ {
+ // use the themes drop down size
+ aSz.AdjustWidth(aTestSize.Width() - aContent.GetWidth() );
+ }
+ else
+ aSz.AdjustWidth(GetSettings().GetStyleSettings().GetScrollBarSize() );
+ }
+
+ aSz = CalcWindowSize( aSz );
+
+ if (IsDropDownBox()) // Check minimum height of dropdown box
+ {
+ ImplControlValue aControlValue;
+ tools::Rectangle aRect( Point( 0, 0 ), aSz );
+ tools::Rectangle aContent, aBound;
+ if( GetNativeControlRegion( ControlType::Listbox, ControlPart::Entire, aRect, ControlState::NONE,
+ aControlValue, aBound, aContent) )
+ {
+ if( aBound.GetHeight() > aSz.Height() )
+ aSz.setHeight( aBound.GetHeight() );
+ }
+ }
+
+ return aSz;
+}
+
+Size ListBox::CalcSubEditSize() const
+{
+ Size aSz;
+
+ if (!mpImplLB)
+ return aSz;
+
+ if ( !IsDropDownBox() )
+ aSz = mpImplLB->CalcSize (mnLineCount ? mnLineCount : mpImplLB->GetEntryList()->GetEntryCount());
+ else
+ {
+ aSz.setHeight( mpImplLB->GetEntryHeight() );
+ // Size to maximum entry width
+ aSz.setWidth( mpImplLB->GetMaxEntryWidth() );
+
+ if (m_nMaxWidthChars != -1)
+ {
+ 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() )
+ {
+ long nEntryHeight = CalcBlockSize( 1, 1 ).Height();
+ 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();
+}
+
+void ListBox::EnableMirroring()
+{
+ mpImplLB->EnableMirroring();
+}
+
+tools::Rectangle ListBox::GetDropDownPosSizePixel() const
+{
+ return mpFloatWin ? mpFloatWin->GetWindowExtentsRelative( const_cast<ListBox*>(this) ) : tools::Rectangle();
+}
+
+const Wallpaper& ListBox::GetDisplayBackground() const
+{
+ // !!! Recursion does not occur because the ImplListBox is initialized by default
+ // to a non-transparent color in Window::ImplInitData
+ return mpImplLB->GetDisplayBackground();
+}
+
+void ListBox::setMaxWidthChars(sal_Int32 nWidth)
+{
+ if (nWidth != m_nMaxWidthChars)
+ {
+ m_nMaxWidthChars = nWidth;
+ queue_resize();
+ }
+}
+
+bool ListBox::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "active")
+ SelectEntryPos(rValue.toInt32());
+ else if (rKey == "max-width-chars")
+ setMaxWidthChars(rValue.toInt32());
+ else if (rKey == "can-focus")
+ {
+ // as far as I can see in Gtk, setting a ComboBox as can.focus means
+ // the focus gets stuck in it, so try here to behave like gtk does
+ // with the settings that work, i.e. can.focus of false doesn't
+ // set the hard WB_NOTABSTOP
+ WinBits nBits = GetStyle();
+ nBits &= ~(WB_TABSTOP|WB_NOTABSTOP);
+ if (toBool(rValue))
+ nBits |= WB_TABSTOP;
+ SetStyle(nBits);
+ }
+ else
+ return Control::set_property(rKey, rValue);
+ return true;
+}
+
+FactoryFunction ListBox::GetUITestFactory() const
+{
+ return ListBoxUIObject::create;
+}
+
+boost::property_tree::ptree ListBox::DumpAsPropertyTree()
+{
+ boost::property_tree::ptree aTree(Control::DumpAsPropertyTree());
+ boost::property_tree::ptree aEntries;
+
+ for (int i = 0; i < GetEntryCount(); ++i)
+ {
+ boost::property_tree::ptree aEntry;
+ aEntry.put("", GetEntry(i));
+ aEntries.push_back(std::make_pair("", aEntry));
+ }
+
+ aTree.add_child("entries", aEntries);
+
+ boost::property_tree::ptree aSelected;
+
+ for (int i = 0; i < GetSelectedEntryCount(); ++i)
+ {
+ boost::property_tree::ptree aEntry;
+ aEntry.put("", GetSelectedEntryPos(i));
+ aSelected.push_back(std::make_pair("", aEntry));
+ }
+
+ aTree.put("selectedCount", GetSelectedEntryCount());
+ aTree.add_child("selectedEntries", aSelected);
+
+ return aTree;
+}
+
+MultiListBox::MultiListBox( vcl::Window* pParent, WinBits nStyle ) :
+ ListBox( WindowType::MULTILISTBOX )
+{
+ ImplInit( pParent, nStyle );
+ EnableMultiSelection( true );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/longcurr.cxx b/vcl/source/control/longcurr.cxx
new file mode 100644
index 000000000..ab2ba50e7
--- /dev/null
+++ b/vcl/source/control/longcurr.cxx
@@ -0,0 +1,516 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include <comphelper/string.hxx>
+#include <tools/bigint.hxx>
+#include <sal/log.hxx>
+
+#include <vcl/event.hxx>
+#include <vcl/toolkit/field.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, const OUString& rCurrSymbol, bool bShowThousandSep )
+{
+ SAL_WARN_IF( nDigits >= 10, "vcl", "LongCurrency may only have 9 decimal places" );
+
+ if ( rNumber.IsZero() || static_cast<long>(rNumber) )
+ return rLocaleDataWrapper.getCurr( static_cast<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<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<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.copy( 0, nDecPos );
+ aStr2.append(std::u16string_view(aStr).substr(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);
+ }
+ if (aStr2.getLength() < 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 );
+}
+
+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(), rFormatter.IsUseThousandSep() );
+ return true;
+ }
+}
+
+void LongCurrencyFormatter::ImpInit()
+{
+ mnLastValue = 0;
+ mnMin = 0;
+ mnMax = 0x7FFFFFFF;
+ mnMax *= 0x7FFFFFFF;
+ mnDecimalDigits = 0;
+ mbThousandSep = true;
+ SetDecimalDigits( 0 );
+}
+
+LongCurrencyFormatter::LongCurrencyFormatter(Edit* pEdit)
+ : FormatterBase(pEdit)
+{
+ ImpInit();
+}
+
+LongCurrencyFormatter::~LongCurrencyFormatter()
+{
+}
+
+void LongCurrencyFormatter::SetCurrencySymbol( const OUString& rStr )
+{
+ maCurrencySymbol= rStr;
+ ReformatAll();
+}
+
+OUString const & LongCurrencyFormatter::GetCurrencySymbol() const
+{
+ return !maCurrencySymbol.isEmpty() ? maCurrencySymbol : 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(), IsUseThousandSep() );
+ 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::SetMin(const BigInt& rNewMin)
+{
+ mnMin = rNewMin;
+ ReformatAll();
+}
+
+void LongCurrencyFormatter::SetMax(const BigInt& rNewMax)
+{
+ mnMax = rNewMax;
+ ReformatAll();
+}
+
+void LongCurrencyFormatter::SetUseThousandSep( bool b )
+{
+ mbThousandSep = b;
+ ReformatAll();
+}
+
+void LongCurrencyFormatter::SetDecimalDigits( sal_uInt16 nDigits )
+{
+ if ( nDigits > 9 )
+ nDigits = 9;
+
+ mnDecimalDigits = nDigits;
+ ReformatAll();
+}
+
+
+void ImplNewLongCurrencyFieldValue(LongCurrencyField* pField, const BigInt& rNewValue)
+{
+ Selection aSelect = pField->GetSelection();
+ aSelect.Justify();
+ OUString aText = pField->GetText();
+ bool bLastSelected = aSelect.Max() == aText.getLength();
+
+ BigInt nOldLastValue = pField->mnLastValue;
+ pField->SetUserValue(rNewValue);
+ pField->mnLastValue = nOldLastValue;
+
+ if ( bLastSelected )
+ {
+ if ( !aSelect.Len() )
+ aSelect.Min() = SELECTION_MAX;
+ aSelect.Max() = SELECTION_MAX;
+ }
+ pField->SetSelection( aSelect );
+ pField->SetModifyFlag();
+ pField->Modify();
+}
+
+LongCurrencyField::LongCurrencyField(vcl::Window* pParent, WinBits nWinStyle)
+ : SpinField( pParent, nWinStyle )
+ , LongCurrencyFormatter(this)
+{
+ mnSpinSize = 1;
+ mnFirst = mnMin;
+ mnLast = mnMax;
+
+ Reformat();
+}
+
+bool LongCurrencyField::EventNotify( NotifyEvent& rNEvt )
+{
+ if( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS )
+ {
+ MarkToBeReformatted( false );
+ }
+ else if( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() )
+ {
+ Reformat();
+ SpinField::Modify();
+ }
+ }
+ return SpinField::EventNotify( rNEvt );
+}
+
+void LongCurrencyField::Modify()
+{
+ MarkToBeReformatted( true );
+ SpinField::Modify();
+}
+
+void LongCurrencyField::Up()
+{
+ BigInt nValue = GetValue();
+ nValue += mnSpinSize;
+ if ( nValue > mnMax )
+ nValue = mnMax;
+
+ ImplNewLongCurrencyFieldValue( this, nValue );
+ SpinField::Up();
+}
+
+void LongCurrencyField::Down()
+{
+ BigInt nValue = GetValue();
+ nValue -= mnSpinSize;
+ if ( nValue < mnMin )
+ nValue = mnMin;
+
+ ImplNewLongCurrencyFieldValue( this, nValue );
+ SpinField::Down();
+}
+
+void LongCurrencyField::First()
+{
+ ImplNewLongCurrencyFieldValue( this, mnFirst );
+ SpinField::First();
+}
+
+void LongCurrencyField::Last()
+{
+ ImplNewLongCurrencyFieldValue( this, mnLast );
+ SpinField::Last();
+}
+
+LongCurrencyBox::LongCurrencyBox(vcl::Window* pParent, WinBits nWinStyle)
+ : ComboBox(pParent, nWinStyle)
+ , LongCurrencyFormatter(this)
+{
+ Reformat();
+}
+
+bool LongCurrencyBox::EventNotify( NotifyEvent& rNEvt )
+{
+ if( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS )
+ {
+ MarkToBeReformatted( false );
+ }
+ else if( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() )
+ {
+ Reformat();
+ ComboBox::Modify();
+ }
+ }
+ return ComboBox::EventNotify( rNEvt );
+}
+
+void LongCurrencyBox::Modify()
+{
+ MarkToBeReformatted( true );
+ ComboBox::Modify();
+}
+
+void LongCurrencyBox::ReformatAll()
+{
+ OUString aStr;
+ SetUpdateMode( false );
+ const sal_Int32 nEntryCount = GetEntryCount();
+ for ( sal_Int32 i=0; i < nEntryCount; ++i )
+ {
+ ImplLongCurrencyReformat( GetEntry( i ), mnMin, mnMax,
+ GetDecimalDigits(), GetLocaleDataWrapper(),
+ aStr, *this );
+ RemoveEntryAt(i);
+ InsertEntry( aStr, i );
+ }
+ LongCurrencyFormatter::Reformat();
+ SetUpdateMode( true );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/menubtn.cxx b/vcl/source/control/menubtn.cxx
new file mode 100644
index 000000000..7402d361f
--- /dev/null
+++ b/vcl/source/control/menubtn.cxx
@@ -0,0 +1,250 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/floatwin.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/menubtn.hxx>
+#include <vcl/settings.hxx>
+
+void MenuButton::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ if ( !(nStyle & WB_NOTABSTOP) )
+ nStyle |= WB_TABSTOP;
+
+ PushButton::ImplInit( pParent, nStyle );
+ EnableRTL( AllSettings::GetLayoutRTL() );
+}
+
+void MenuButton::ExecuteMenu()
+{
+ mbStartingMenu = true;
+
+ Activate();
+
+ if (!mpMenu && !mpFloatingWindow)
+ {
+ mbStartingMenu = false;
+ return;
+ }
+
+ Size aSize = GetSizePixel();
+ SetPressed( true );
+ EndSelection();
+ if (mpMenu)
+ {
+ Point aPos(0, 1);
+ tools::Rectangle aRect(aPos, aSize );
+ mpMenu->Execute(this, aRect, PopupMenuFlags::ExecuteDown);
+
+ if (IsDisposed())
+ return;
+
+ mnCurItemId = mpMenu->GetCurItemId();
+ msCurItemIdent = mpMenu->GetCurItemIdent();
+ }
+ else
+ {
+ Point aPos(GetParent()->OutputToScreenPixel(GetPosPixel()));
+ tools::Rectangle aRect(aPos, aSize );
+ FloatWinPopupFlags nFlags = FloatWinPopupFlags::Down | FloatWinPopupFlags::GrabFocus;
+ if (mpFloatingWindow->GetType() == WindowType::FLOATINGWINDOW)
+ static_cast<FloatingWindow*>(mpFloatingWindow.get())->StartPopupMode(aRect, nFlags);
+ else
+ {
+ mpFloatingWindow->EnableDocking();
+ vcl::Window::GetDockingManager()->StartPopupMode(mpFloatingWindow, aRect, nFlags);
+ }
+ }
+
+ mbStartingMenu = false;
+
+ SetPressed(false);
+ if (mnCurItemId)
+ {
+ Select();
+ mnCurItemId = 0;
+ msCurItemIdent.clear();
+ }
+}
+
+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);
+ }
+}
+
+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()
+{
+ 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;
+}
+
+//class MenuToggleButton ----------------------------------------------------
+
+MenuToggleButton::MenuToggleButton( vcl::Window* pParent, WinBits nWinBits )
+ : MenuButton( pParent, nWinBits )
+{
+}
+
+MenuToggleButton::~MenuToggleButton()
+{
+ disposeOnce();
+}
+
+void MenuToggleButton::SetActive( bool bSel )
+{
+ mbIsActive = bSel;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/notebookbar.cxx b/vcl/source/control/notebookbar.cxx
new file mode 100644
index 000000000..08f82b49a
--- /dev/null
+++ b/vcl/source/control/notebookbar.cxx
@@ -0,0 +1,297 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/layout.hxx>
+#include <vcl/notebookbar.hxx>
+#include <vcl/syswin.hxx>
+#include <vcl/taskpanelist.hxx>
+#include <vcl/NotebookbarContextControl.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <comphelper/processfactory.hxx>
+#include <rtl/bootstrap.hxx>
+#include <osl/file.hxx>
+#include <config_folders.h>
+#include <com/sun/star/frame/XFrame.hpp>
+#include <com/sun/star/ui/ContextChangeEventMultiplexer.hpp>
+
+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(const OUString& sUIDir, const OUString& sUIFile)
+{
+ OUString sUri = sUIDir + sUIFile;
+ osl::File file(sUri);
+ return( file.open(0) == osl::FileBase::E_None );
+}
+
+/**
+ * split from the main class since it needs different ref-counting mana
+ */
+class NotebookBarContextChangeEventListener : public ::cppu::WeakImplHelper<css::ui::XContextChangeEventListener>
+{
+ VclPtr<NotebookBar> mpParent;
+public:
+ explicit NotebookBarContextChangeEventListener(NotebookBar *p) : mpParent(p) {}
+
+ // XContextChangeEventListener
+ virtual void SAL_CALL notifyContextChangeEvent(const css::ui::ContextChangeEventObject& rEvent) override;
+
+ virtual void SAL_CALL disposing(const ::css::lang::EventObject&) override;
+};
+
+NotebookBar::NotebookBar(Window* pParent, const OString& rID, const OUString& rUIXMLDescription,
+ const css::uno::Reference<css::frame::XFrame>& rFrame,
+ const NotebookBarAddonsItem& aNotebookBarAddonsItem)
+ : Control(pParent)
+ , m_pEventListener(new NotebookBarContextChangeEventListener(this))
+{
+ SetStyle(GetStyle() | WB_DIALOGCONTROL);
+ OUString sUIDir = getUIRootDir();
+ bool doesCustomizedUIExist = doesFileExist(getCustomizedUIRootDir(), rUIXMLDescription);
+ if ( doesCustomizedUIExist )
+ sUIDir = getCustomizedUIRootDir();
+ m_pUIBuilder.reset(
+ new VclBuilder(this, sUIDir, rUIXMLDescription, rID, rFrame, true, &aNotebookBarAddonsItem));
+ mxFrame = rFrame;
+ // In the Notebookbar's .ui file must exist control handling context
+ // - implementing NotebookbarContextControl interface with id "ContextContainer"
+ // or "ContextContainerX" where X is a number >= 1
+ NotebookbarContextControl* pContextContainer = nullptr;
+ int i = 0;
+ do
+ {
+ OUString aName = "ContextContainer";
+ if (i)
+ aName += OUString::number(i);
+
+ pContextContainer = dynamic_cast<NotebookbarContextControl*>(m_pUIBuilder->get<Window>(OUStringToOString(aName, RTL_TEXTENCODING_UTF8)));
+ if (pContextContainer)
+ m_pContextContainers.push_back(pContextContainer);
+ i++;
+ }
+ while( pContextContainer != nullptr );
+
+ UpdateBackground();
+}
+
+NotebookBar::~NotebookBar()
+{
+ disposeOnce();
+}
+
+void NotebookBar::dispose()
+{
+ m_pContextContainers.clear();
+ if (m_pSystemWindow && m_pSystemWindow->ImplIsInTaskPaneList(this))
+ m_pSystemWindow->GetTaskPaneList()->RemoveWindow(this);
+ m_pSystemWindow.clear();
+ disposeBuilder();
+ assert(m_alisteningControllers.empty());
+ m_pEventListener.clear();
+ Control::dispose();
+}
+
+bool NotebookBar::PreNotify(NotifyEvent& rNEvt)
+{
+ // capture KeyEvents for taskpane cycling
+ if (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT)
+ {
+ if (m_pSystemWindow)
+ return m_pSystemWindow->PreNotify(rNEvt);
+ }
+ return Window::PreNotify( rNEvt );
+}
+
+Size NotebookBar::GetOptimalSize() const
+{
+ if (isLayoutEnabled(this))
+ return VclContainer::getLayoutRequisition(*GetWindow(GetWindowType::FirstChild));
+
+ return Control::GetOptimalSize();
+}
+
+void NotebookBar::setPosSizePixel(long nX, long nY, long nWidth, 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);
+ }
+ }
+ Control::Resize();
+}
+
+void NotebookBar::SetSystemWindow(SystemWindow* pSystemWindow)
+{
+ m_pSystemWindow = pSystemWindow;
+ if (!m_pSystemWindow->ImplIsInTaskPaneList(this))
+ m_pSystemWindow->GetTaskPaneList()->AddWindow(this);
+}
+
+void SAL_CALL NotebookBarContextChangeEventListener::notifyContextChangeEvent(const css::ui::ContextChangeEventObject& rEvent)
+{
+ if (mpParent)
+ {
+ for (NotebookbarContextControl* pControl : mpParent->m_pContextContainers)
+ pControl->SetContext(vcl::EnumContext::GetContextEnum(rEvent.ContextName));
+ }
+}
+
+void NotebookBar::ControlListenerForCurrentController(bool bListen)
+{
+ auto xController = mxFrame->getController();
+ if(bListen)
+ {
+ // add listeners
+ if (m_alisteningControllers.count(xController) == 0)
+ {
+ auto xMultiplexer(css::ui::ContextChangeEventMultiplexer::get(
+ ::comphelper::getProcessComponentContext()));
+ xMultiplexer->addContextChangeEventListener(m_pEventListener, xController);
+ m_alisteningControllers.insert(xController);
+ }
+ }
+ else
+ {
+ // remove listeners
+ if (m_alisteningControllers.count(xController))
+ {
+ auto xMultiplexer(css::ui::ContextChangeEventMultiplexer::get(
+ ::comphelper::getProcessComponentContext()));
+ xMultiplexer->removeContextChangeEventListener(m_pEventListener, xController);
+ m_alisteningControllers.erase(xController);
+ }
+ }
+}
+
+void NotebookBar::StopListeningAllControllers()
+{
+ auto xMultiplexer(
+ css::ui::ContextChangeEventMultiplexer::get(comphelper::getProcessComponentContext()));
+ xMultiplexer->removeAllContextChangeEventListeners(m_pEventListener);
+ m_alisteningControllers.clear();
+}
+
+void SAL_CALL NotebookBarContextChangeEventListener::disposing(const ::css::lang::EventObject&)
+{
+ mpParent.clear();
+}
+
+void NotebookBar::DataChanged(const DataChangedEvent& rDCEvt)
+{
+ UpdateBackground();
+ Control::DataChanged(rDCEvt);
+}
+
+void NotebookBar::StateChanged(const StateChangedType nStateChange )
+{
+ UpdateBackground();
+ Control::StateChanged(nStateChange);
+ Invalidate();
+}
+
+void NotebookBar::UpdateBackground()
+{
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ const BitmapEx& aPersona = rStyleSettings.GetPersonaHeader();
+ Wallpaper aWallpaper(aPersona);
+ aWallpaper.SetStyle(WallpaperStyle::TopRight);
+ if (!aPersona.IsEmpty())
+ {
+ SetBackground(aWallpaper);
+ UpdatePersonaSettings();
+ SetSettings( PersonaSettings );
+ }
+ else
+ {
+ SetBackground(rStyleSettings.GetDialogColor());
+ UpdateDefaultSettings();
+ SetSettings( DefaultSettings );
+ }
+
+ Invalidate(tools::Rectangle(Point(0,0), GetSizePixel()));
+}
+
+void NotebookBar::UpdateDefaultSettings()
+{
+ AllSettings aAllSettings( GetSettings() );
+ StyleSettings aStyleSet( aAllSettings.GetStyleSettings() );
+
+ ::Color aTextColor = aStyleSet.GetFieldTextColor();
+ aStyleSet.SetDialogTextColor( aTextColor );
+ aStyleSet.SetButtonTextColor( aTextColor );
+ aStyleSet.SetRadioCheckTextColor( aTextColor );
+ aStyleSet.SetGroupTextColor( aTextColor );
+ aStyleSet.SetLabelTextColor( aTextColor );
+ aStyleSet.SetWindowTextColor( aTextColor );
+ aStyleSet.SetTabTextColor(aTextColor);
+ aStyleSet.SetToolTextColor(aTextColor);
+
+ aAllSettings.SetStyleSettings(aStyleSet);
+ DefaultSettings = aAllSettings;
+}
+
+void NotebookBar::UpdatePersonaSettings()
+{
+ AllSettings aAllSettings( GetSettings() );
+ StyleSettings aStyleSet( aAllSettings.GetStyleSettings() );
+
+ ::Color aTextColor = aStyleSet.GetPersonaMenuBarTextColor().value_or(COL_BLACK );
+ aStyleSet.SetDialogTextColor( aTextColor );
+ aStyleSet.SetButtonTextColor( aTextColor );
+ aStyleSet.SetRadioCheckTextColor( aTextColor );
+ aStyleSet.SetGroupTextColor( aTextColor );
+ aStyleSet.SetLabelTextColor( aTextColor );
+ aStyleSet.SetWindowTextColor( aTextColor );
+ aStyleSet.SetTabTextColor(aTextColor);
+ aStyleSet.SetToolTextColor(aTextColor);
+
+ aAllSettings.SetStyleSettings(aStyleSet);
+ PersonaSettings = aAllSettings;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/prgsbar.cxx b/vcl/source/control/prgsbar.cxx
new file mode 100644
index 000000000..7082042df
--- /dev/null
+++ b/vcl/source/control/prgsbar.cxx
@@ -0,0 +1,229 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#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()
+{
+ mnPercent = 0;
+ mbCalcNew = true;
+
+ ImplInitSettings( true, true, true );
+}
+
+static WinBits clearProgressBarBorder( vcl::Window const * pParent, WinBits nOrgStyle )
+{
+ WinBits nOutStyle = nOrgStyle;
+ if( pParent && (nOrgStyle & WB_BORDER) != 0 )
+ {
+ if( pParent->IsNativeControlSupported( ControlType::Progress, ControlPart::Entire ) )
+ nOutStyle &= WB_BORDER;
+ }
+ return nOutStyle;
+}
+
+Size ProgressBar::GetOptimalSize() const
+{
+ return Size(150, 20);
+}
+
+ProgressBar::ProgressBar( vcl::Window* pParent, WinBits nWinStyle ) :
+ Window( pParent, clearProgressBarBorder( pParent, nWinStyle ) )
+{
+ SetOutputSizePixel( GetOptimalSize() );
+ ImplInit();
+}
+
+void ProgressBar::ImplInitSettings( bool bFont,
+ bool bForeground, bool bBackground )
+{
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+
+/* FIXME: !!! We do not support text output at the moment
+ if ( bFont )
+ ApplyControlFont(*this, rStyleSettings.GetAppFont());
+*/
+
+ if ( bBackground )
+ {
+ if( !IsControlBackground() &&
+ IsNativeControlSupported( ControlType::Progress, ControlPart::Entire ) )
+ {
+ if( GetStyle() & WB_BORDER )
+ SetBorderStyle( WindowBorderStyle::REMOVEBORDER );
+ EnableChildTransparentMode();
+ SetPaintTransparent( true );
+ SetBackground();
+ SetParentClipMode( ParentClipMode::NoClip );
+ }
+ else
+ {
+ Color aColor;
+ if ( IsControlBackground() )
+ aColor = GetControlBackground();
+ else
+ aColor = rStyleSettings.GetFaceColor();
+ SetBackground( aColor );
+ }
+ }
+
+ if ( bForeground || bFont )
+ {
+ Color aColor = rStyleSettings.GetHighlightColor();
+ if ( IsControlForeground() )
+ aColor = GetControlForeground();
+ if ( aColor.IsRGBEqual( GetBackground().GetColor() ) )
+ {
+ if ( aColor.GetLuminance() > 100 )
+ aColor.DecreaseLuminance( 64 );
+ else
+ aColor.IncreaseLuminance( 64 );
+ }
+ SetLineColor();
+ 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 );
+ long nMaxWidth = aSize.Width() - (PROGRESSBAR_WIN_OFFSET * 2) + PROGRESSBAR_OFFSET;
+ sal_uInt16 nMaxCount = static_cast<sal_uInt16>(nMaxWidth / (mnPrgsWidth+PROGRESSBAR_OFFSET));
+ if (nMaxCount <= 1)
+ {
+ nMaxCount = 1;
+ }
+ else
+ {
+ while (((10000 / (10000 / nMaxCount)) * (mnPrgsWidth + PROGRESSBAR_OFFSET)) > nMaxWidth)
+ {
+ nMaxCount--;
+ }
+ }
+ mnPercentCount = 10000 / nMaxCount;
+ nMaxWidth = ((10000 / (10000 / nMaxCount)) * (mnPrgsWidth + PROGRESSBAR_OFFSET)) - PROGRESSBAR_OFFSET;
+ maPos.setX( (aSize.Width() - nMaxWidth) / 2 );
+ }
+
+ ::DrawProgress(this, rRenderContext, maPos, PROGRESSBAR_OFFSET, mnPrgsWidth, mnPrgsHeight,
+ /*nPercent1=*/0, nNewPerc * 100, mnPercentCount,
+ tools::Rectangle(Point(), GetSizePixel()));
+}
+
+void ProgressBar::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rRect*/)
+{
+ ImplDrawProgress(rRenderContext, mnPercent);
+}
+
+void ProgressBar::Resize()
+{
+ mbCalcNew = true;
+ if ( IsReallyVisible() )
+ Invalidate();
+}
+
+void ProgressBar::SetValue( sal_uInt16 nNewPercent )
+{
+ SAL_WARN_IF( nNewPercent > 100, "vcl", "StatusBar::SetProgressValue(): nPercent > 100" );
+
+ if ( nNewPercent < mnPercent )
+ {
+ mbCalcNew = true;
+ mnPercent = nNewPercent;
+ if ( IsReallyVisible() )
+ {
+ Invalidate();
+ PaintImmediately();
+ }
+ }
+ else if ( mnPercent != nNewPercent )
+ {
+ mnPercent = nNewPercent;
+ Invalidate();
+
+ // Make sure the progressbar is actually painted even if the caller is busy with its task,
+ // so the main loop would not be invoked.
+ Idle aIdle("ProgressBar::SetValue aIdle");
+ aIdle.SetPriority(TaskPriority::POST_PAINT);
+ aIdle.Start();
+ while (aIdle.IsActive())
+ {
+ Application::Yield();
+ }
+ }
+}
+
+void ProgressBar::StateChanged( StateChangedType nType )
+{
+/* FIXME: !!! We do not support text output at the moment
+ if ( (nType == StateChangedType::Zoom) ||
+ (nType == StateChangedType::ControlFont) )
+ {
+ ImplInitSettings( true, false, false );
+ Invalidate();
+ }
+ else
+*/
+ if ( nType == StateChangedType::ControlForeground )
+ {
+ ImplInitSettings( false, true, false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ImplInitSettings( false, false, true );
+ Invalidate();
+ }
+
+ Window::StateChanged( nType );
+}
+
+void ProgressBar::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) )
+ {
+ ImplInitSettings( true, true, true );
+ Invalidate();
+ }
+
+ Window::DataChanged( rDCEvt );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/quickselectionengine.cxx b/vcl/source/control/quickselectionengine.cxx
new file mode 100644
index 000000000..e3b060896
--- /dev/null
+++ b/vcl/source/control/quickselectionengine.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 )
+ ,sCurrentSearchString()
+ ,aSingleSearchChar()
+ ,aSearchTimeout()
+ {
+ aSearchTimeout.SetTimeout( 2500 );
+ aSearchTimeout.SetInvokeHandler( LINK( this, QuickSelectionEngine_Data, SearchStringTimeout ) );
+ aSearchTimeout.SetDebugName( "vcl::QuickSelectionEngine_Data aSearchTimeout" );
+ }
+
+ ~QuickSelectionEngine_Data()
+ {
+ aSearchTimeout.Stop();
+ }
+
+ DECL_LINK( SearchStringTimeout, Timer*, void );
+ };
+
+ namespace
+ {
+ void lcl_reset( QuickSelectionEngine_Data& _data )
+ {
+ _data.sCurrentSearchString.clear();
+ _data.aSingleSearchChar.reset();
+ _data.aSearchTimeout.Stop();
+ }
+ }
+
+ IMPL_LINK_NOARG( QuickSelectionEngine_Data, SearchStringTimeout, Timer*, void )
+ {
+ lcl_reset( *this );
+ }
+
+ static StringEntryIdentifier findMatchingEntry( const OUString& _searchString, QuickSelectionEngine_Data const & _engineData )
+ {
+ const vcl::I18nHelper& rI18nHelper = Application::GetSettings().GetLocaleI18nHelper();
+ // TODO: do we really need the Window's settings here? The original code used it ...
+
+ OUString sEntryText;
+ // get the "current + 1" entry
+ StringEntryIdentifier pSearchEntry = _engineData.rEntryList.CurrentEntry( sEntryText );
+ if ( pSearchEntry )
+ pSearchEntry = _engineData.rEntryList.NextEntry( pSearchEntry, sEntryText );
+ // loop 'til we find another matching entry
+ StringEntryIdentifier pStartedWith = pSearchEntry;
+ while ( pSearchEntry )
+ {
+ if ( rI18nHelper.MatchString( _searchString, sEntryText ) )
+ break;
+
+ pSearchEntry = _engineData.rEntryList.NextEntry( pSearchEntry, sEntryText );
+ if ( pSearchEntry == pStartedWith )
+ pSearchEntry = nullptr;
+ }
+
+ return pSearchEntry;
+ }
+
+ QuickSelectionEngine::QuickSelectionEngine( ISearchableStringList& _entryList )
+ :m_pData( new QuickSelectionEngine_Data( _entryList ) )
+ {
+ }
+
+ QuickSelectionEngine::~QuickSelectionEngine()
+ {
+ }
+
+ bool QuickSelectionEngine::HandleKeyEvent( const KeyEvent& _keyEvent )
+ {
+ sal_Unicode c = _keyEvent.GetCharCode();
+
+ if ( ( c >= 32 ) && ( c != 127 ) && !_keyEvent.GetKeyCode().IsMod2() )
+ {
+ m_pData->sCurrentSearchString += OUStringChar(c);
+ SAL_INFO( "vcl", "QuickSelectionEngine::HandleKeyEvent: searching for " << m_pData->sCurrentSearchString );
+
+ if ( m_pData->sCurrentSearchString.getLength() == 1 )
+ { // first character in the search -> remember
+ m_pData->aSingleSearchChar = c;
+ }
+ else if ( m_pData->sCurrentSearchString.getLength() > 1 )
+ {
+ if ( !!m_pData->aSingleSearchChar && ( *m_pData->aSingleSearchChar != c ) )
+ // we already have a "single char", but the current one is different -> reset
+ m_pData->aSingleSearchChar.reset();
+ }
+
+ OUString aSearchTemp( m_pData->sCurrentSearchString );
+
+ StringEntryIdentifier pMatchingEntry = findMatchingEntry( aSearchTemp, *m_pData );
+ SAL_INFO( "vcl", "QuickSelectionEngine::HandleKeyEvent: found " << pMatchingEntry );
+ if ( !pMatchingEntry && (aSearchTemp.getLength() > 1) && !!m_pData->aSingleSearchChar )
+ {
+ // if there's only one letter in the search string, use a different search mode
+ aSearchTemp = OUString(*m_pData->aSingleSearchChar);
+ pMatchingEntry = findMatchingEntry( aSearchTemp, *m_pData );
+ }
+
+ if ( pMatchingEntry )
+ {
+ m_pData->rEntryList.SelectEntry( pMatchingEntry );
+ m_pData->aSearchTimeout.Start();
+ }
+ else
+ {
+ lcl_reset( *m_pData );
+ }
+
+ return true;
+ }
+ return false;
+ }
+
+ void QuickSelectionEngine::Reset()
+ {
+ lcl_reset( *m_pData );
+ }
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/roadmap.cxx b/vcl/source/control/roadmap.cxx
new file mode 100644
index 000000000..9f7e6a0e1
--- /dev/null
+++ b/vcl/source/control/roadmap.cxx
@@ -0,0 +1,842 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <vcl/event.hxx>
+#include <vcl/toolkit/roadmap.hxx>
+#include <vcl/settings.hxx>
+#include <hyperlabel.hxx>
+#include <tools/color.hxx>
+#include <rtl/ustring.hxx>
+
+constexpr long LABELBASEMAPHEIGHT = 8;
+constexpr long ROADMAP_INDENT_X = 4;
+constexpr long ROADMAP_INDENT_Y = 27;
+constexpr 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 ) && ( _nItemIndex < static_cast<ItemIndex>(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 )
+{
+ DeselectOldRoadmapItems();
+ RoadmapItem* pItem = GetByID( _nNewID );
+ if ( pItem != nullptr )
+ {
+ if ( pItem->IsEnabled() )
+ {
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ pItem->ToggleBackgroundColor( rStyleSettings.GetHighlightColor() ); //HighlightColor
+
+ 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())
+ {
+ Size aBitmapSize = m_pImpl->getPicture().GetSizePixel();
+ Size aMySize(GetOutputSizePixel());
+
+ Point aBitmapPos(aMySize.Width() - aBitmapSize.Width(), aMySize.Height() - aBitmapSize.Height());
+
+ // draw it
+ rRenderContext.DrawBitmapEx( aBitmapPos, m_pImpl->getPicture() );
+ }
+
+ // draw the headline
+ DrawHeadline(rRenderContext);
+}
+
+void ORoadmap::DrawHeadline(vcl::RenderContext& rRenderContext)
+{
+ Point aTextPos = LogicToPixel(Point(ROADMAP_INDENT_X, 8), MapMode(MapUnit::MapAppFont));
+
+ Size aOutputSize(GetOutputSizePixel());
+
+ // draw it
+ rRenderContext.DrawText(tools::Rectangle(aTextPos, aOutputSize), GetText(),
+ DrawTextFlags::Left | DrawTextFlags::Top | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak);
+ rRenderContext.DrawTextLine(aTextPos, aOutputSize.Width(), STRIKEOUT_NONE, LINESTYLE_SINGLE, LINESTYLE_NONE);
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ rRenderContext.SetLineColor(rStyleSettings.GetFieldTextColor());
+ rRenderContext.SetTextColor(rStyleSettings.GetFieldTextColor());
+}
+
+RoadmapItem* ORoadmap::GetByPointer(vcl::Window const * pWindow)
+{
+ const HL_Vector& rItems = m_pImpl->getHyperLabels();
+ for (auto const& item : rItems)
+ {
+ if ( item->Contains( pWindow ) )
+ return item;
+ }
+ return nullptr;
+}
+
+bool ORoadmap::PreNotify(NotifyEvent& _rNEvt)
+{
+ // capture KeyEvents for taskpane cycling
+ if ( _rNEvt.GetType() == MouseNotifyEvent::KEYINPUT )
+ {
+ vcl::Window* pWindow = _rNEvt.GetWindow();
+ RoadmapItem* pItem = GetByPointer( pWindow );
+ if ( pItem != nullptr )
+ {
+ sal_Int16 nKeyCode = _rNEvt.GetKeyEvent()->GetKeyCode().GetCode();
+ switch( nKeyCode )
+ {
+ case KEY_UP:
+ { // Note: Performance wise this is not optimal, because we search for an ID in the labels
+ // and afterwards we search again for a label with the appropriate ID ->
+ // unnecessarily we search twice!!!
+ ItemId nPrevItemID = GetPreviousAvailableItemId( pItem->GetIndex() );
+ if ( nPrevItemID != -1 )
+ return SelectRoadmapItemByID( nPrevItemID );
+ }
+ break;
+ case KEY_DOWN:
+ {
+ ItemId nNextItemID = GetNextAvailableItemId( pItem->GetIndex() );
+ if ( nNextItemID != -1 )
+ return SelectRoadmapItemByID( nNextItemID );
+ }
+ break;
+ case KEY_SPACE:
+ return SelectRoadmapItemByID( pItem->GetID() );
+ }
+ }
+ }
+ return Window::PreNotify( _rNEvt );
+}
+
+IMPL_LINK(ORoadmap, ImplClickHdl, HyperLabel*, CurHyperLabel, void)
+{
+ SelectRoadmapItemByID( CurHyperLabel->GetID() );
+}
+
+void ORoadmap::DataChanged(const DataChangedEvent& rDCEvt)
+{
+ if (!((( rDCEvt.GetType() == DataChangedEventType::SETTINGS ) ||
+ ( rDCEvt.GetType() == DataChangedEventType::DISPLAY )) &&
+ ( rDCEvt.GetFlags() & AllSettingsFlags::STYLE )))
+ return;
+
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ SetBackground( Wallpaper( rStyleSettings.GetFieldColor() ) );
+ Color aTextColor = rStyleSettings.GetFieldTextColor();
+ vcl::Font aFont = GetFont();
+ aFont.SetColor( aTextColor );
+ SetFont( aFont );
+ RoadmapTypes::ItemId curItemID = GetCurrentRoadmapItemID();
+ RoadmapItem* pLabelItem = GetByID( curItemID );
+ if (pLabelItem != nullptr)
+ {
+ pLabelItem->ToggleBackgroundColor(rStyleSettings.GetHighlightColor());
+ }
+ Invalidate();
+}
+
+void ORoadmap::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ Color aTextColor = rStyleSettings.GetFieldTextColor();
+ vcl::Font aFont = rRenderContext.GetFont();
+ aFont.SetColor(aTextColor);
+ aFont.SetWeight(WEIGHT_BOLD);
+ aFont.SetUnderline(LINESTYLE_SINGLE);
+ rRenderContext.SetFont(aFont);
+ rRenderContext.SetBackground(rStyleSettings.GetFieldColor());
+}
+
+RoadmapItem::RoadmapItem(ORoadmap& _rParent, const Size& _rItemPlayground)
+ : m_aItemPlayground(_rItemPlayground)
+{
+ mpID = VclPtr<IDLabel>::Create( &_rParent, WB_WORDBREAK );
+ mpID->Show();
+ mpDescription = VclPtr<HyperLabel>::Create( &_rParent, WB_NOTABSTOP | WB_WORDBREAK );
+ mpDescription->Show();
+}
+
+RoadmapItem::~RoadmapItem()
+{
+ mpID.disposeAndClear();
+ mpDescription.disposeAndClear();
+}
+
+bool RoadmapItem::Contains(const vcl::Window* _pWindow) const
+{
+ return ( mpID == _pWindow ) || ( mpDescription == _pWindow );
+}
+
+void RoadmapItem::GrabFocus()
+{
+ if ( mpDescription )
+ mpDescription->GrabFocus();
+}
+
+void RoadmapItem::SetInteractive(bool _bInteractive)
+{
+ if ( mpDescription )
+ mpDescription->SetInteractive(_bInteractive);
+}
+
+void RoadmapItem::SetID(sal_Int16 ID)
+{
+ if ( mpDescription )
+ mpDescription->SetID(ID);
+}
+
+sal_Int16 RoadmapItem::GetID() const
+{
+ return mpDescription ? mpDescription->GetID() : sal_Int16(-1);
+}
+
+void RoadmapItem::ImplUpdateIndex(const ItemIndex _nIndex)
+{
+ mpDescription->SetIndex( _nIndex );
+
+ OUString aIDText = OUString::number( _nIndex + 1 ) + ".";
+ mpID->SetText( aIDText );
+
+ // update the geometry of both controls
+ ImplUpdatePosSize();
+}
+
+void RoadmapItem::SetIndex(ItemIndex Index)
+{
+ ImplUpdateIndex(Index);
+}
+
+RoadmapTypes::ItemIndex RoadmapItem::GetIndex() const
+{
+ return mpDescription ? mpDescription->GetIndex() : ItemIndex(-1);
+}
+
+void RoadmapItem::SetPosition(RoadmapItem const * _pOldItem)
+{
+ Point aIDPos;
+ if ( _pOldItem == nullptr )
+ {
+ aIDPos = mpID->LogicToPixel(Point(ROADMAP_INDENT_X, ROADMAP_INDENT_Y), MapMode(MapUnit::MapAppFont));
+ }
+ else
+ {
+ Size aOldSize = _pOldItem->mpDescription->GetSizePixel();
+
+ aIDPos = _pOldItem->mpID->GetPosPixel();
+ aIDPos.AdjustY(aOldSize.Height() );
+ aIDPos.AdjustY(mpID->GetParent()->LogicToPixel( Size( 0, ROADMAP_ITEM_DISTANCE_Y ) ).Height() );
+ }
+ mpID->SetPosPixel( aIDPos );
+
+ sal_Int32 nDescPos = aIDPos.X() + mpID->GetSizePixel().Width();
+ mpDescription->SetPosPixel( Point( nDescPos, aIDPos.Y() ) );
+}
+
+void RoadmapItem::Enable(bool _bEnable)
+{
+ mpID->Enable(_bEnable);
+ mpDescription->Enable(_bEnable);
+}
+
+bool RoadmapItem::IsEnabled() const
+{
+ return mpID->IsEnabled();
+}
+
+void RoadmapItem::ToggleBackgroundColor(const Color& _rGBColor)
+{
+ if (_rGBColor == COL_TRANSPARENT)
+ mpID->SetControlBackground();
+ else
+ mpID->SetControlBackground( mpID->GetSettings().GetStyleSettings().GetHighlightColor() );
+ mpDescription->ToggleBackgroundColor(_rGBColor);
+}
+
+void RoadmapItem::ImplUpdatePosSize()
+{
+ // calculate widths
+ long nIDWidth = mpID->GetTextWidth( mpID->GetText() );
+ long nMaxIDWidth = mpID->GetTextWidth( "100." );
+ nIDWidth = ::std::min( nIDWidth, nMaxIDWidth );
+
+ // check how many space the description would need
+ Size aDescriptionSize = mpDescription->CalcMinimumSize( m_aItemPlayground.Width() - nIDWidth );
+
+ // position and size both controls
+ Size aIDSize( nIDWidth, aDescriptionSize.Height() );
+ mpID->SetSizePixel( aIDSize );
+
+ Point aIDPos = mpID->GetPosPixel();
+ mpDescription->SetPosPixel( Point( aIDPos.X() + nIDWidth, aIDPos.Y() ) );
+ mpDescription->SetSizePixel( aDescriptionSize );
+}
+
+void RoadmapItem::Update(ItemIndex RMIndex, const OUString& _rText)
+{
+ // update description label
+ mpDescription->SetLabel( _rText );
+
+ // update the index in both controls, which triggers updating the geometry of both
+ ImplUpdateIndex( RMIndex );
+}
+
+void RoadmapItem::SetClickHdl(const Link<HyperLabel*,void>& rLink)
+{
+ if ( mpDescription )
+ mpDescription->SetClickHdl( rLink);
+}
+
+IDLabel::IDLabel(vcl::Window* _pParent, WinBits _nWinStyle)
+ : FixedText(_pParent, _nWinStyle)
+{
+}
+
+void IDLabel::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ FixedText::ApplySettings(rRenderContext);
+
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ if (GetControlBackground() == COL_TRANSPARENT)
+ rRenderContext.SetTextColor(rStyleSettings.GetFieldTextColor());
+ else
+ rRenderContext.SetTextColor(rStyleSettings.GetHighlightTextColor());
+}
+
+void IDLabel::DataChanged(const DataChangedEvent& rDCEvt)
+{
+ FixedText::DataChanged( rDCEvt );
+
+ if ((( rDCEvt.GetType() == DataChangedEventType::SETTINGS ) ||
+ ( rDCEvt.GetType() == DataChangedEventType::DISPLAY )) &&
+ ( rDCEvt.GetFlags() & AllSettingsFlags::STYLE ))
+ {
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ if (GetControlBackground() != COL_TRANSPARENT)
+ SetControlBackground(rStyleSettings.GetHighlightColor());
+ Invalidate();
+ }
+}
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/roadmapwizard.cxx b/vcl/source/control/roadmapwizard.cxx
new file mode 100644
index 000000000..d73cc1a18
--- /dev/null
+++ b/vcl/source/control/roadmapwizard.cxx
@@ -0,0 +1,858 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <vcl/toolkit/roadmap.hxx>
+#include <tools/debug.hxx>
+#include <osl/diagnose.h>
+
+#include <strings.hrc>
+#include <svdata.hxx>
+#include <wizdlg.hxx>
+
+#include <vector>
+#include <map>
+#include <set>
+
+#include "wizimpldata.hxx"
+#include <uiobject-internal.hxx>
+
+namespace vcl
+{
+ using namespace RoadmapWizardTypes;
+
+ namespace
+ {
+ typedef ::std::set< WizardTypes::WizardState > StateSet;
+
+ typedef ::std::map<
+ PathId,
+ WizardPath
+ > Paths;
+
+ typedef ::std::map<
+ WizardTypes::WizardState,
+ ::std::pair<
+ OUString,
+ RoadmapPageFactory
+ >
+ > StateDescriptions;
+ }
+
+ struct RoadmapWizardImpl
+ {
+ ScopedVclPtr<ORoadmap> pRoadmap;
+ Paths aPaths;
+ PathId nActivePath;
+ StateDescriptions aStateDescriptors;
+ StateSet aDisabledStates;
+ bool bActivePathIsDefinite;
+
+ RoadmapWizardImpl()
+ :pRoadmap( nullptr )
+ ,nActivePath( -1 )
+ ,bActivePathIsDefinite( false )
+ {
+ }
+
+ /// returns the index of the current state in given path, or -1
+ static sal_Int32 getStateIndexInPath( WizardTypes::WizardState _nState, const WizardPath& _rPath );
+ /// returns the index of the current state in the path with the given id, or -1
+ sal_Int32 getStateIndexInPath( WizardTypes::WizardState _nState, PathId _nPathId );
+ /// returns the index of the first state in which the two given paths differ
+ static sal_Int32 getFirstDifferentIndex( const WizardPath& _rLHS, const WizardPath& _rRHS );
+ };
+
+
+ sal_Int32 RoadmapWizardImpl::getStateIndexInPath( WizardTypes::WizardState _nState, const WizardPath& _rPath )
+ {
+ sal_Int32 nStateIndexInPath = 0;
+ bool bFound = false;
+ for (auto const& path : _rPath)
+ {
+ if (path == _nState)
+ {
+ bFound = true;
+ break;
+ }
+ ++nStateIndexInPath;
+ }
+ if (!bFound)
+ nStateIndexInPath = -1;
+ return nStateIndexInPath;
+ }
+
+
+ sal_Int32 RoadmapWizardImpl::getStateIndexInPath( WizardTypes::WizardState _nState, PathId _nPathId )
+ {
+ sal_Int32 nStateIndexInPath = -1;
+ Paths::const_iterator aPathPos = aPaths.find( _nPathId );
+ if ( aPathPos != aPaths.end( ) )
+ nStateIndexInPath = getStateIndexInPath( _nState, aPathPos->second );
+ return nStateIndexInPath;
+ }
+
+
+ sal_Int32 RoadmapWizardImpl::getFirstDifferentIndex( const WizardPath& _rLHS, const WizardPath& _rRHS )
+ {
+ sal_Int32 nMinLength = ::std::min( _rLHS.size(), _rRHS.size() );
+ for ( sal_Int32 nCheck = 0; nCheck < nMinLength; ++nCheck )
+ {
+ if ( _rLHS[ nCheck ] != _rRHS[ nCheck ] )
+ return nCheck;
+ }
+ return nMinLength;
+ }
+
+ //= RoadmapWizard
+ RoadmapWizard::RoadmapWizard(vcl::Window* pParent, WinBits nStyle, InitFlag eFlag)
+ : Dialog(pParent, nStyle, eFlag)
+ , m_pFinish(nullptr)
+ , m_pCancel(nullptr)
+ , m_pNextPage(nullptr)
+ , m_pPrevPage(nullptr)
+ , m_pHelp(nullptr)
+ , m_xWizardImpl(new WizardMachineImplData)
+ , m_xRoadmapImpl(new RoadmapWizardImpl)
+ {
+ mpFirstPage = nullptr;
+ mpFirstBtn = nullptr;
+ mpCurTabPage = nullptr;
+ mpPrevBtn = nullptr;
+ mpNextBtn = nullptr;
+ mpViewWindow = nullptr;
+ mnCurLevel = 0;
+ mbEmptyViewMargin = false;
+ mnLeftAlignCount = 0;
+
+ maWizardLayoutIdle.SetPriority(TaskPriority::RESIZE);
+ maWizardLayoutIdle.SetInvokeHandler( LINK( this, RoadmapWizard, ImplHandleWizardLayoutTimerHdl ) );
+
+ implConstruct(WizardButtonFlags::NEXT | WizardButtonFlags::PREVIOUS | WizardButtonFlags::FINISH | WizardButtonFlags::CANCEL | WizardButtonFlags::HELP);
+
+ SetLeftAlignedButtonCount( 1 );
+ mbEmptyViewMargin = true;
+
+ m_xRoadmapImpl->pRoadmap.disposeAndReset( VclPtr<ORoadmap>::Create( this, WB_TABSTOP ) );
+ m_xRoadmapImpl->pRoadmap->SetText( VclResId( STR_WIZDLG_ROADMAP_TITLE ) );
+ m_xRoadmapImpl->pRoadmap->SetPosPixel( Point( 0, 0 ) );
+ m_xRoadmapImpl->pRoadmap->SetItemSelectHdl( LINK( this, RoadmapWizard, OnRoadmapItemSelected ) );
+
+ Size aRoadmapSize = LogicToPixel(Size(85, 0), MapMode(MapUnit::MapAppFont));
+ aRoadmapSize.setHeight( GetSizePixel().Height() );
+ m_xRoadmapImpl->pRoadmap->SetSizePixel( aRoadmapSize );
+
+ mpViewWindow = m_xRoadmapImpl->pRoadmap;
+ m_xRoadmapImpl->pRoadmap->Show();
+ }
+
+ RoadmapWizardMachine::RoadmapWizardMachine(weld::Window* pParent)
+ : WizardMachine(pParent, WizardButtonFlags::NEXT | WizardButtonFlags::PREVIOUS | WizardButtonFlags::FINISH | WizardButtonFlags::CANCEL | WizardButtonFlags::HELP)
+ , m_pImpl( new RoadmapWizardImpl )
+ {
+ m_xAssistant->connect_jump_page(LINK(this, RoadmapWizardMachine, OnRoadmapItemSelected));
+ }
+
+ void RoadmapWizard::ShowRoadmap(bool bShow)
+ {
+ m_xRoadmapImpl->pRoadmap->Show(bShow);
+ CalcAndSetSize();
+ }
+
+ RoadmapWizard::~RoadmapWizard()
+ {
+ disposeOnce();
+ }
+
+ RoadmapWizardMachine::~RoadmapWizardMachine()
+ {
+ }
+
+ void RoadmapWizard::dispose()
+ {
+ m_xRoadmapImpl.reset();
+
+ m_pFinish.disposeAndClear();
+ m_pCancel.disposeAndClear();
+ m_pNextPage.disposeAndClear();
+ m_pPrevPage.disposeAndClear();
+ m_pHelp.disposeAndClear();
+
+ if (m_xWizardImpl)
+ {
+ for (WizardTypes::WizardState i = 0; i < m_xWizardImpl->nFirstUnknownPage; ++i)
+ {
+ TabPage *pPage = GetPage(i);
+ if (pPage)
+ pPage->disposeOnce();
+ }
+ m_xWizardImpl.reset();
+ }
+
+ maWizardLayoutIdle.Stop();
+
+ // Remove all buttons
+ while ( mpFirstBtn )
+ RemoveButton( mpFirstBtn->mpButton );
+
+ // Remove all pages
+ while ( mpFirstPage )
+ RemovePage( mpFirstPage->mpPage );
+
+ mpCurTabPage.clear();
+ mpPrevBtn.clear();
+ mpNextBtn.clear();
+ mpViewWindow.clear();
+ Dialog::dispose();
+ }
+
+ void RoadmapWizard::SetRoadmapHelpId( const OString& _rId )
+ {
+ m_xRoadmapImpl->pRoadmap->SetHelpId( _rId );
+ }
+
+ void RoadmapWizardMachine::SetRoadmapHelpId(const OString& rId)
+ {
+ m_xAssistant->set_page_side_help_id(rId);
+ }
+
+ void RoadmapWizardMachine::declarePath( PathId _nPathId, const WizardPath& _lWizardStates)
+ {
+ m_pImpl->aPaths.emplace( _nPathId, _lWizardStates );
+
+ if ( m_pImpl->aPaths.size() == 1 )
+ // the very first path -> activate it
+ activatePath( _nPathId );
+ else
+ implUpdateRoadmap( );
+ }
+
+ void RoadmapWizardMachine::activatePath( PathId _nPathId, bool _bDecideForIt )
+ {
+ if ( ( _nPathId == m_pImpl->nActivePath ) && ( _bDecideForIt == m_pImpl->bActivePathIsDefinite ) )
+ // nothing to do
+ return;
+
+ // does the given path exist?
+ Paths::const_iterator aNewPathPos = m_pImpl->aPaths.find( _nPathId );
+ DBG_ASSERT( aNewPathPos != m_pImpl->aPaths.end(), "RoadmapWizard::activate: there is no such path!" );
+ if ( aNewPathPos == m_pImpl->aPaths.end() )
+ return;
+
+ // determine the index of the current state in the current path
+ sal_Int32 nCurrentStatePathIndex = -1;
+ if ( m_pImpl->nActivePath != -1 )
+ nCurrentStatePathIndex = m_pImpl->getStateIndexInPath( getCurrentState(), m_pImpl->nActivePath );
+
+ DBG_ASSERT( static_cast<sal_Int32>(aNewPathPos->second.size()) > nCurrentStatePathIndex,
+ "RoadmapWizard::activate: you cannot activate a path which has less states than we've already advanced!" );
+ // If this asserts, this for instance means that we are already in state number, say, 5
+ // of our current path, and the caller tries to activate a path which has less than 5
+ // states
+ if ( static_cast<sal_Int32>(aNewPathPos->second.size()) <= nCurrentStatePathIndex )
+ return;
+
+ // assert that the current and the new path are equal, up to nCurrentStatePathIndex
+ Paths::const_iterator aActivePathPos = m_pImpl->aPaths.find( m_pImpl->nActivePath );
+ if ( aActivePathPos != m_pImpl->aPaths.end() )
+ {
+ if ( RoadmapWizardImpl::getFirstDifferentIndex( aActivePathPos->second, aNewPathPos->second ) <= nCurrentStatePathIndex )
+ {
+ OSL_FAIL( "RoadmapWizard::activate: you cannot activate a path which conflicts with the current one *before* the current state!" );
+ return;
+ }
+ }
+
+ m_pImpl->nActivePath = _nPathId;
+ m_pImpl->bActivePathIsDefinite = _bDecideForIt;
+
+ implUpdateRoadmap( );
+ }
+
+ void RoadmapWizard::implUpdateRoadmap( )
+ {
+ DBG_ASSERT( m_xRoadmapImpl->aPaths.find( m_xRoadmapImpl->nActivePath ) != m_xRoadmapImpl->aPaths.end(),
+ "RoadmapWizard::implUpdateRoadmap: there is no such path!" );
+ const WizardPath& rActivePath( m_xRoadmapImpl->aPaths[ m_xRoadmapImpl->nActivePath ] );
+
+ sal_Int32 nCurrentStatePathIndex = RoadmapWizardImpl::getStateIndexInPath( getCurrentState(), rActivePath );
+ if (nCurrentStatePathIndex < 0)
+ return;
+
+ // determine up to which index (in the new path) we have to display the items
+ RoadmapTypes::ItemIndex nUpperStepBoundary = static_cast<RoadmapTypes::ItemIndex>(rActivePath.size());
+ bool bIncompletePath = false;
+ if ( !m_xRoadmapImpl->bActivePathIsDefinite )
+ {
+ for (auto const& path : m_xRoadmapImpl->aPaths)
+ {
+ if ( path.first == m_xRoadmapImpl->nActivePath )
+ // it's the path we are just activating -> no need to check anything
+ continue;
+ // the index from which on both paths differ
+ sal_Int32 nDivergenceIndex = RoadmapWizardImpl::getFirstDifferentIndex( rActivePath, path.second );
+ if ( nDivergenceIndex <= nCurrentStatePathIndex )
+ // they differ in an index which we have already left behind us
+ // -> this is no conflict anymore
+ continue;
+
+ // the path conflicts with our new path -> don't activate the
+ // *complete* new path, but only up to the step which is unambiguous
+ nUpperStepBoundary = nDivergenceIndex;
+ bIncompletePath = true;
+ }
+ }
+
+ // can we advance from the current page?
+ bool bCurrentPageCanAdvance = true;
+ TabPage* 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 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
+ );
+ }
+
+ // 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_xRoadmapImpl->aDisabledStates.find( nState ) == m_xRoadmapImpl->aDisabledStates.end() );
+
+ m_xRoadmapImpl->pRoadmap->EnableRoadmapItem( m_xRoadmapImpl->pRoadmap->GetItemID( nItemIndex ), bEnable );
+ }
+
+ m_xRoadmapImpl->pRoadmap->SetRoadmapComplete( !bIncompletePath );
+ }
+
+ void RoadmapWizardMachine::implUpdateRoadmap( )
+ {
+
+ DBG_ASSERT( m_pImpl->aPaths.find( m_pImpl->nActivePath ) != m_pImpl->aPaths.end(),
+ "RoadmapWizard::implUpdateRoadmap: there is no such path!" );
+ const WizardPath& rActivePath( m_pImpl->aPaths[ m_pImpl->nActivePath ] );
+
+ sal_Int32 nCurrentStatePathIndex = RoadmapWizardImpl::getStateIndexInPath( getCurrentState(), rActivePath );
+ if (nCurrentStatePathIndex < 0)
+ return;
+
+ // determine up to which index (in the new path) we have to display the items
+ RoadmapTypes::ItemIndex nUpperStepBoundary = static_cast<RoadmapTypes::ItemIndex>(rActivePath.size());
+ if ( !m_pImpl->bActivePathIsDefinite )
+ {
+ for (auto const& path : m_pImpl->aPaths)
+ {
+ if ( path.first == m_pImpl->nActivePath )
+ // it's the path we are just activating -> no need to check anything
+ continue;
+ // the index from which on both paths differ
+ sal_Int32 nDivergenceIndex = RoadmapWizardImpl::getFirstDifferentIndex( rActivePath, path.second );
+ if ( nDivergenceIndex <= nCurrentStatePathIndex )
+ // they differ in an index which we have already left behind us
+ // -> this is no conflict anymore
+ continue;
+
+ // the path conflicts with our new path -> don't activate the
+ // *complete* new path, but only up to the step which is unambiguous
+ nUpperStepBoundary = nDivergenceIndex;
+ }
+ }
+
+ // can we advance from the current page?
+ bool bCurrentPageCanAdvance = true;
+ BuilderPage* pCurrentPage = GetPage( getCurrentState() );
+ if ( pCurrentPage )
+ {
+ const IWizardPageController* pController = getPageController( GetPage( getCurrentState() ) );
+ OSL_ENSURE( pController != nullptr, "RoadmapWizard::implUpdateRoadmap: no controller for the current page!" );
+ bCurrentPageCanAdvance = !pController || pController->canAdvance();
+ }
+
+ // now, we have to remove all items after nCurrentStatePathIndex, and insert the items from the active
+ // path, up to (excluding) nUpperStepBoundary
+ RoadmapTypes::ItemIndex nRoadmapItems = m_xAssistant->get_n_pages();
+ RoadmapTypes::ItemIndex nLoopUntil = ::std::max( nUpperStepBoundary, nRoadmapItems );
+ for ( RoadmapTypes::ItemIndex nItemIndex = nCurrentStatePathIndex; nItemIndex < nLoopUntil; ++nItemIndex )
+ {
+ bool bExistentItem = ( nItemIndex < nRoadmapItems );
+ bool bNeedItem = ( nItemIndex < nUpperStepBoundary );
+
+ bool bInsertItem = false;
+ if ( bExistentItem )
+ {
+ if ( !bNeedItem )
+ {
+ int nPages = nRoadmapItems;
+ for (int i = nPages - 1; i >= nItemIndex; --i)
+ {
+ m_xAssistant->set_page_title(m_xAssistant->get_page_ident(i), "");
+ --nRoadmapItems;
+ }
+ break;
+ }
+ else
+ {
+ // there is an item with this index in the roadmap - does it match what is requested by
+ // the respective state in the active path?
+ RoadmapTypes::ItemId nPresentItemId = m_xAssistant->get_page_ident(nItemIndex).toInt32();
+ WizardTypes::WizardState nRequiredState = rActivePath[ nItemIndex ];
+ if ( nPresentItemId != nRequiredState )
+ {
+ m_xAssistant->set_page_title(OString::number(nPresentItemId), "");
+ bInsertItem = true;
+ }
+ }
+ }
+ else
+ {
+ DBG_ASSERT( bNeedItem, "RoadmapWizard::implUpdateRoadmap: ehm - none needed, none present - why did the loop not terminate?" );
+ bInsertItem = bNeedItem;
+ }
+
+ WizardTypes::WizardState nState( rActivePath[ nItemIndex ] );
+
+ if ( bInsertItem )
+ {
+ GetOrCreatePage(nState);
+ }
+
+ OString sIdent(OString::number(nState));
+ m_xAssistant->set_page_index(sIdent, nItemIndex);
+ m_xAssistant->set_page_title(sIdent, getStateDisplayName(nState));
+
+ // if the item is *after* the current state, but the current page does not
+ // allow advancing, the disable the state. This relieves derived classes
+ // from disabling all future states just because the current state does not
+ // (yet) allow advancing.
+ const bool bUnconditionedDisable = !bCurrentPageCanAdvance && ( nItemIndex > nCurrentStatePathIndex );
+ const bool bEnable = !bUnconditionedDisable && ( m_pImpl->aDisabledStates.find( nState ) == m_pImpl->aDisabledStates.end() );
+ m_xAssistant->set_page_sensitive(sIdent, bEnable);
+ }
+ }
+
+ WizardTypes::WizardState RoadmapWizard::determineNextState( WizardTypes::WizardState _nCurrentState ) const
+ {
+ sal_Int32 nCurrentStatePathIndex = -1;
+
+ Paths::const_iterator aActivePathPos = m_xRoadmapImpl->aPaths.find( m_xRoadmapImpl->nActivePath );
+ if ( aActivePathPos != m_xRoadmapImpl->aPaths.end() )
+ nCurrentStatePathIndex = RoadmapWizardImpl::getStateIndexInPath( _nCurrentState, aActivePathPos->second );
+
+ DBG_ASSERT( nCurrentStatePathIndex != -1, "RoadmapWizard::determineNextState: ehm - how can we travel if there is no (valid) active path?" );
+ if ( nCurrentStatePathIndex == -1 )
+ return WZS_INVALID_STATE;
+
+ sal_Int32 nNextStateIndex = nCurrentStatePathIndex + 1;
+
+ while ( ( nNextStateIndex < static_cast<sal_Int32>(aActivePathPos->second.size()) )
+ && ( m_xRoadmapImpl->aDisabledStates.find( aActivePathPos->second[ nNextStateIndex ] ) != m_xRoadmapImpl->aDisabledStates.end() )
+ )
+ {
+ ++nNextStateIndex;
+ }
+
+ if ( nNextStateIndex >= static_cast<sal_Int32>(aActivePathPos->second.size()) )
+ // there is no next state in the current path (at least none which is enabled)
+ return WZS_INVALID_STATE;
+
+ return aActivePathPos->second[ nNextStateIndex ];
+ }
+
+ WizardTypes::WizardState RoadmapWizardMachine::determineNextState( WizardTypes::WizardState _nCurrentState ) const
+ {
+ sal_Int32 nCurrentStatePathIndex = -1;
+
+ Paths::const_iterator aActivePathPos = m_pImpl->aPaths.find( m_pImpl->nActivePath );
+ if ( aActivePathPos != m_pImpl->aPaths.end() )
+ nCurrentStatePathIndex = RoadmapWizardImpl::getStateIndexInPath( _nCurrentState, aActivePathPos->second );
+
+ DBG_ASSERT( nCurrentStatePathIndex != -1, "RoadmapWizard::determineNextState: ehm - how can we travel if there is no (valid) active path?" );
+ if ( nCurrentStatePathIndex == -1 )
+ return WZS_INVALID_STATE;
+
+ sal_Int32 nNextStateIndex = nCurrentStatePathIndex + 1;
+
+ while ( ( nNextStateIndex < static_cast<sal_Int32>(aActivePathPos->second.size()) )
+ && ( m_pImpl->aDisabledStates.find( aActivePathPos->second[ nNextStateIndex ] ) != m_pImpl->aDisabledStates.end() )
+ )
+ {
+ ++nNextStateIndex;
+ }
+
+ if ( nNextStateIndex >= static_cast<sal_Int32>(aActivePathPos->second.size()) )
+ // there is no next state in the current path (at least none which is enabled)
+ return WZS_INVALID_STATE;
+
+ return aActivePathPos->second[ nNextStateIndex ];
+ }
+
+ bool RoadmapWizard::canAdvance() const
+ {
+ if ( !m_xRoadmapImpl->bActivePathIsDefinite )
+ {
+ // check how many paths are still allowed
+ const WizardPath& rActivePath( m_xRoadmapImpl->aPaths[ m_xRoadmapImpl->nActivePath ] );
+ sal_Int32 nCurrentStatePathIndex = RoadmapWizardImpl::getStateIndexInPath( getCurrentState(), rActivePath );
+
+ size_t nPossiblePaths(0);
+ for (auto const& path : m_xRoadmapImpl->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_xRoadmapImpl->aPaths[ m_xRoadmapImpl->nActivePath ];
+ return *rPath.rbegin() != getCurrentState();
+ }
+
+ bool RoadmapWizardMachine::canAdvance() const
+ {
+ if ( !m_pImpl->bActivePathIsDefinite )
+ {
+ // check how many paths are still allowed
+ const WizardPath& rActivePath( m_pImpl->aPaths[ m_pImpl->nActivePath ] );
+
+ // if current path has only the base item, it is not possible to proceed without activating another path
+ if(rActivePath.size()<=1)
+ return false;
+
+ sal_Int32 nCurrentStatePathIndex = RoadmapWizardImpl::getStateIndexInPath( getCurrentState(), rActivePath );
+
+ size_t nPossiblePaths(0);
+ for (auto const& path : m_pImpl->aPaths)
+ {
+ // the index from which on both paths differ
+ sal_Int32 nDivergenceIndex = RoadmapWizardImpl::getFirstDifferentIndex( rActivePath, path.second );
+
+ if ( nDivergenceIndex > nCurrentStatePathIndex )
+ // this path is still a possible path
+ nPossiblePaths += 1;
+ }
+
+ // if we have more than one path which is still possible, then we assume
+ // to always have a next state. Though there might be scenarios where this
+ // is not true, but this is too sophisticated (means not really needed) right now.
+ if ( nPossiblePaths > 1 )
+ return true;
+ }
+
+ const WizardPath& rPath = m_pImpl->aPaths[ m_pImpl->nActivePath ];
+ return *rPath.rbegin() != getCurrentState();
+ }
+
+ void RoadmapWizardMachine::updateTravelUI()
+ {
+ WizardMachine::updateTravelUI();
+
+ // disable the "Previous" button if all states in our history are disabled
+ std::vector< WizardTypes::WizardState > aHistory;
+ getStateHistory( aHistory );
+ bool bHaveEnabledState = false;
+ for (auto const& state : aHistory)
+ {
+ if ( isStateEnabled(state) )
+ {
+ bHaveEnabledState = true;
+ break;
+ }
+ }
+
+ enableButtons( WizardButtonFlags::PREVIOUS, bHaveEnabledState );
+
+ implUpdateRoadmap();
+ }
+
+ IMPL_LINK_NOARG(RoadmapWizard, OnRoadmapItemSelected, LinkParamNone*, void)
+ {
+ RoadmapTypes::ItemId nCurItemId = m_xRoadmapImpl->pRoadmap->GetCurrentRoadmapItemID();
+ if ( nCurItemId == getCurrentState() )
+ // nothing to do
+ return;
+
+ if ( isTravelingSuspended() )
+ return;
+
+ RoadmapWizardTravelSuspension aTravelGuard( *this );
+
+ sal_Int32 nCurrentIndex = m_xRoadmapImpl->getStateIndexInPath( getCurrentState(), m_xRoadmapImpl->nActivePath );
+ sal_Int32 nNewIndex = m_xRoadmapImpl->getStateIndexInPath( nCurItemId, m_xRoadmapImpl->nActivePath );
+
+ DBG_ASSERT( ( nCurrentIndex != -1 ) && ( nNewIndex != -1 ),
+ "RoadmapWizard::OnRoadmapItemSelected: something's wrong here!" );
+ if ( ( nCurrentIndex == -1 ) || ( nNewIndex == -1 ) )
+ {
+ return;
+ }
+
+ bool bResult = true;
+ if ( nNewIndex > nCurrentIndex )
+ {
+ bResult = skipUntil( static_cast<WizardTypes::WizardState>(nCurItemId) );
+ WizardTypes::WizardState nTemp = static_cast<WizardTypes::WizardState>(nCurItemId);
+ while( nTemp )
+ {
+ if( m_xRoadmapImpl->aDisabledStates.find( --nTemp ) != m_xRoadmapImpl->aDisabledStates.end() )
+ removePageFromHistory( nTemp );
+ }
+ }
+ else
+ bResult = skipBackwardUntil( static_cast<WizardTypes::WizardState>(nCurItemId) );
+
+ if ( !bResult )
+ m_xRoadmapImpl->pRoadmap->SelectRoadmapItemByID( getCurrentState() );
+ }
+
+ IMPL_LINK(RoadmapWizardMachine, OnRoadmapItemSelected, const OString&, rCurItemId, bool)
+ {
+ int nCurItemId = rCurItemId.toInt32();
+
+ if ( nCurItemId == getCurrentState() )
+ // nothing to do
+ return false;
+
+ if ( isTravelingSuspended() )
+ return false;
+
+ WizardTravelSuspension aTravelGuard( *this );
+
+ sal_Int32 nCurrentIndex = m_pImpl->getStateIndexInPath( getCurrentState(), m_pImpl->nActivePath );
+ sal_Int32 nNewIndex = m_pImpl->getStateIndexInPath( nCurItemId, m_pImpl->nActivePath );
+
+ DBG_ASSERT( ( nCurrentIndex != -1 ) && ( nNewIndex != -1 ),
+ "RoadmapWizard::OnRoadmapItemSelected: something's wrong here!" );
+ if ( ( nCurrentIndex == -1 ) || ( nNewIndex == -1 ) )
+ {
+ return false;
+ }
+
+ bool bResult = true;
+ if ( nNewIndex > nCurrentIndex )
+ {
+ bResult = skipUntil( static_cast<WizardTypes::WizardState>(nCurItemId) );
+ WizardTypes::WizardState nTemp = static_cast<WizardTypes::WizardState>(nCurItemId);
+ while( nTemp )
+ {
+ if( m_pImpl->aDisabledStates.find( --nTemp ) != m_pImpl->aDisabledStates.end() )
+ removePageFromHistory( nTemp );
+ }
+ }
+ else
+ bResult = skipBackwardUntil( static_cast<WizardTypes::WizardState>(nCurItemId) );
+
+ return bResult;
+ }
+
+ void RoadmapWizard::enterState(WizardTypes::WizardState nState)
+ {
+ // tell the page
+ IWizardPageController* pController = getPageController( GetPage( nState ) );
+ if (pController)
+ {
+ pController->initializePage();
+
+ if ( isAutomaticNextButtonStateEnabled() )
+ enableButtons( WizardButtonFlags::NEXT, canAdvance() );
+
+ enableButtons( WizardButtonFlags::PREVIOUS, !m_xWizardImpl->aStateHistory.empty() );
+
+ // set the new title - it depends on the current page (i.e. state)
+ implUpdateTitle();
+ }
+
+ // synchronize the roadmap
+ implUpdateRoadmap( );
+ m_xRoadmapImpl->pRoadmap->SelectRoadmapItemByID( getCurrentState() );
+ }
+
+ void RoadmapWizardMachine::enterState( WizardTypes::WizardState _nState )
+ {
+ WizardMachine::enterState( _nState );
+
+ // synchronize the roadmap
+ implUpdateRoadmap();
+ }
+
+ OUString RoadmapWizard::getStateDisplayName( WizardTypes::WizardState _nState ) const
+ {
+ OUString sDisplayName;
+
+ StateDescriptions::const_iterator pos = m_xRoadmapImpl->aStateDescriptors.find( _nState );
+ OSL_ENSURE( pos != m_xRoadmapImpl->aStateDescriptors.end(),
+ "RoadmapWizard::getStateDisplayName: no default implementation available for this state!" );
+ if ( pos != m_xRoadmapImpl->aStateDescriptors.end() )
+ sDisplayName = pos->second.first;
+
+ return sDisplayName;
+ }
+
+ OUString RoadmapWizardMachine::getStateDisplayName( WizardTypes::WizardState _nState ) const
+ {
+ OUString sDisplayName;
+
+ StateDescriptions::const_iterator pos = m_pImpl->aStateDescriptors.find( _nState );
+ OSL_ENSURE( pos != m_pImpl->aStateDescriptors.end(),
+ "RoadmapWizard::getStateDisplayName: no default implementation available for this state!" );
+ if ( pos != m_pImpl->aStateDescriptors.end() )
+ sDisplayName = pos->second.first;
+
+ return sDisplayName;
+ }
+
+ VclPtr<TabPage> RoadmapWizard::createPage( WizardTypes::WizardState _nState )
+ {
+ VclPtr<TabPage> pPage;
+
+ StateDescriptions::const_iterator pos = m_xRoadmapImpl->aStateDescriptors.find( _nState );
+ OSL_ENSURE( pos != m_xRoadmapImpl->aStateDescriptors.end(),
+ "RoadmapWizard::createPage: no default implementation available for this state!" );
+ if ( pos != m_xRoadmapImpl->aStateDescriptors.end() )
+ {
+ RoadmapPageFactory pFactory = pos->second.second;
+ pPage = (*pFactory)( *this );
+ }
+
+ return pPage;
+ }
+
+ void RoadmapWizardMachine::enableState( WizardTypes::WizardState _nState, bool _bEnable )
+ {
+ // remember this (in case the state appears in the roadmap later on)
+ if ( _bEnable )
+ m_pImpl->aDisabledStates.erase( _nState );
+ else
+ {
+ m_pImpl->aDisabledStates.insert( _nState );
+ removePageFromHistory( _nState );
+ }
+
+ // if the state is currently in the roadmap, reflect it's new status
+ m_xAssistant->set_page_sensitive(OString::number(_nState), _bEnable);
+ }
+
+ bool RoadmapWizardMachine::knowsState( WizardTypes::WizardState i_nState ) const
+ {
+ for (auto const& path : m_pImpl->aPaths)
+ {
+ for (auto const& state : path.second)
+ {
+ if ( state == i_nState )
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool RoadmapWizardMachine::isStateEnabled( WizardTypes::WizardState _nState ) const
+ {
+ return m_pImpl->aDisabledStates.find( _nState ) == m_pImpl->aDisabledStates.end();
+ }
+
+ void RoadmapWizard::InsertRoadmapItem(int nItemIndex, const OUString& rText, int nItemId, bool bEnable)
+ {
+ m_xRoadmapImpl->pRoadmap->InsertRoadmapItem(nItemIndex, rText, nItemId, bEnable);
+ }
+
+ void RoadmapWizard::SelectRoadmapItemByID(int nItemId)
+ {
+ m_xRoadmapImpl->pRoadmap->SelectRoadmapItemByID(nItemId);
+ }
+
+ void RoadmapWizard::DeleteRoadmapItems()
+ {
+ while (m_xRoadmapImpl->pRoadmap->GetItemCount())
+ m_xRoadmapImpl->pRoadmap->DeleteRoadmapItem(0);
+ }
+
+ void RoadmapWizard::SetItemSelectHdl( const Link<LinkParamNone*,void>& _rHdl )
+ {
+ m_xRoadmapImpl->pRoadmap->SetItemSelectHdl(_rHdl);
+ }
+
+ int RoadmapWizard::GetCurrentRoadmapItemID() const
+ {
+ return m_xRoadmapImpl->pRoadmap->GetCurrentRoadmapItemID();
+ }
+
+ FactoryFunction RoadmapWizard::GetUITestFactory() const
+ {
+ return RoadmapWizardUIObject::create;
+ }
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/scrbar.cxx b/vcl/source/control/scrbar.cxx
new file mode 100644
index 000000000..8e008465e
--- /dev/null
+++ b/vcl/source/control/scrbar.cxx
@@ -0,0 +1,1469 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <vcl/event.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/scrbar.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/vclevent.hxx>
+
+#include <sal/log.hxx>
+
+/* #i77549#
+ HACK: for scrollbars in case of thumb rect, page up and page down rect we
+ abuse the HitTestNativeScrollbar interface. All theming engines but macOS
+ are actually able to draw the thumb according to our internal representation.
+ However macOS draws a little outside. The canonical way would be to enhance the
+ HitTestNativeScrollbar passing a ScrollbarValue additionally so all necessary
+ information is available in the call.
+ .
+ However since there is only this one small exception we will deviate a little and
+ instead pass the respective rect as control region to allow for a small correction.
+
+ So all places using HitTestNativeScrollbar on ControlPart::ThumbHorz, ControlPart::ThumbVert,
+ ControlPart::TrackHorzLeft, ControlPart::TrackHorzRight, ControlPart::TrackVertUpper, ControlPart::TrackVertLower
+ do not use the control rectangle as region but the actual part rectangle, making
+ only small deviations feasible.
+*/
+
+#include "thumbpos.hxx"
+
+#define SCRBAR_DRAW_BTN1 (sal_uInt16(0x0001))
+#define SCRBAR_DRAW_BTN2 (sal_uInt16(0x0002))
+#define SCRBAR_DRAW_PAGE1 (sal_uInt16(0x0004))
+#define SCRBAR_DRAW_PAGE2 (sal_uInt16(0x0008))
+#define SCRBAR_DRAW_THUMB (sal_uInt16(0x0010))
+#define SCRBAR_DRAW_BACKGROUND (sal_uInt16(0x0020))
+
+#define SCRBAR_STATE_BTN1_DOWN (sal_uInt16(0x0001))
+#define SCRBAR_STATE_BTN1_DISABLE (sal_uInt16(0x0002))
+#define SCRBAR_STATE_BTN2_DOWN (sal_uInt16(0x0004))
+#define SCRBAR_STATE_BTN2_DISABLE (sal_uInt16(0x0008))
+#define SCRBAR_STATE_PAGE1_DOWN (sal_uInt16(0x0010))
+#define SCRBAR_STATE_PAGE2_DOWN (sal_uInt16(0x0020))
+#define SCRBAR_STATE_THUMB_DOWN (sal_uInt16(0x0040))
+
+#define SCRBAR_VIEW_STYLE (WB_3DLOOK | WB_HORZ | WB_VERT)
+
+struct ImplScrollBarData
+{
+ AutoTimer maTimer; // Timer
+ 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 );
+
+ 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 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 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();
+ }
+}
+
+long ScrollBar::ImplCalcThumbPos( long nPixPos )
+{
+ // Calculate position
+ long nCalcThumbPos;
+ nCalcThumbPos = ImplMulDiv( nPixPos, mnMaxRange-mnVisibleSize-mnMinRange,
+ mnThumbPixRange-mnThumbPixSize );
+ nCalcThumbPos += mnMinRange;
+ return nCalcThumbPos;
+}
+
+long ScrollBar::ImplCalcThumbPosPix( long nPos )
+{
+ 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 long nMinThumbSize = GetSettings().GetStyleSettings().GetMinThumbSize();
+
+ if ( mbCalcSize )
+ {
+ Size aOldSize = getCurrentCalcSize();
+
+ const tools::Rectangle aControlRegion( Point(0,0), aSize );
+ tools::Rectangle aBtn1Region, aBtn2Region, aTrackRegion, aBoundingRegion;
+
+ // reset rectangles to empty *and* (0,0) position
+ maThumbRect = tools::Rectangle();
+ maPage1Rect = tools::Rectangle();
+ maPage2Rect = tools::Rectangle();
+
+ if ( GetStyle() & WB_HORZ )
+ {
+ if ( GetNativeControlRegion( ControlType::Scrollbar, IsRTLEnabled()? ControlPart::ButtonRight: ControlPart::ButtonLeft,
+ aControlRegion, ControlState::NONE, ImplControlValue(), aBoundingRegion, aBtn1Region ) &&
+ GetNativeControlRegion( ControlType::Scrollbar, IsRTLEnabled()? ControlPart::ButtonLeft: ControlPart::ButtonRight,
+ aControlRegion, ControlState::NONE, ImplControlValue(), aBoundingRegion, aBtn2Region ) )
+ {
+ maBtn1Rect = aBtn1Region;
+ maBtn2Rect = aBtn2Region;
+ }
+ else
+ {
+ Size aBtnSize( aSize.Height(), aSize.Height() );
+ maBtn2Rect.SetTop( maBtn1Rect.Top() );
+ maBtn2Rect.SetLeft( aSize.Width()-aSize.Height() );
+ maBtn1Rect.SetSize( aBtnSize );
+ maBtn2Rect.SetSize( aBtnSize );
+ }
+
+ if ( GetNativeControlRegion( ControlType::Scrollbar, ControlPart::TrackHorzArea,
+ aControlRegion, ControlState::NONE, ImplControlValue(), aBoundingRegion, aTrackRegion ) )
+ maTrackRect = aTrackRegion;
+ else
+ maTrackRect = tools::Rectangle::Justify( maBtn1Rect.TopRight(), maBtn2Rect.BottomLeft() );
+
+ // Check if available space is big enough for thumb ( min thumb size = ScrBar width/height )
+ mnThumbPixRange = maTrackRect.Right() - maTrackRect.Left();
+ if( mnThumbPixRange > 0 )
+ {
+ maPage1Rect.SetLeft( maTrackRect.Left() );
+ maPage1Rect.SetBottom( maTrackRect.Bottom() );
+ maPage2Rect.SetBottom (maTrackRect.Bottom() );
+ maThumbRect.SetBottom( maTrackRect.Bottom() );
+ }
+ else
+ mnThumbPixRange = 0;
+ }
+ else // WB_VERT
+ {
+ if ( GetNativeControlRegion( ControlType::Scrollbar, ControlPart::ButtonUp,
+ aControlRegion, ControlState::NONE, ImplControlValue(), aBoundingRegion, aBtn1Region ) &&
+ GetNativeControlRegion( ControlType::Scrollbar, ControlPart::ButtonDown,
+ aControlRegion, ControlState::NONE, ImplControlValue(), aBoundingRegion, aBtn2Region ) )
+ {
+ maBtn1Rect = aBtn1Region;
+ maBtn2Rect = aBtn2Region;
+ }
+ else
+ {
+ const Size aBtnSize( aSize.Width(), aSize.Width() );
+ maBtn2Rect.SetLeft( maBtn1Rect.Left() );
+ maBtn2Rect.SetTop( aSize.Height()-aSize.Width() );
+ maBtn1Rect.SetSize( aBtnSize );
+ maBtn2Rect.SetSize( aBtnSize );
+ }
+
+ if ( GetNativeControlRegion( ControlType::Scrollbar, ControlPart::TrackVertArea,
+ aControlRegion, ControlState::NONE, ImplControlValue(), aBoundingRegion, aTrackRegion ) )
+ maTrackRect = aTrackRegion;
+ else
+ maTrackRect = tools::Rectangle::Justify( maBtn1Rect.BottomLeft()+Point(0,1), maBtn2Rect.TopRight() );
+
+ // Check if available space is big enough for thumb
+ mnThumbPixRange = maTrackRect.Bottom() - maTrackRect.Top();
+ if( mnThumbPixRange > 0 )
+ {
+ maPage1Rect.SetTop( maTrackRect.Top() );
+ maPage1Rect.SetRight( maTrackRect.Right() );
+ maPage2Rect.SetRight( maTrackRect.Right() );
+ maThumbRect.SetRight( maTrackRect.Right() );
+ }
+ else
+ mnThumbPixRange = 0;
+ }
+
+ mbCalcSize = false;
+
+ Size aNewSize = getCurrentCalcSize();
+ if (aOldSize != aNewSize)
+ {
+ queue_resize();
+ }
+ }
+
+ if ( mnThumbPixRange )
+ {
+ // Calculate values
+ if ( (mnVisibleSize >= (mnMaxRange-mnMinRange)) ||
+ ((mnMaxRange-mnMinRange) <= 0) )
+ {
+ mnThumbPos = mnMinRange;
+ mnThumbPixPos = 0;
+ mnThumbPixSize = mnThumbPixRange;
+ }
+ else
+ {
+ if ( mnVisibleSize )
+ mnThumbPixSize = ImplMulDiv( mnThumbPixRange, mnVisibleSize, mnMaxRange-mnMinRange );
+ else
+ {
+ if ( GetStyle() & WB_HORZ )
+ mnThumbPixSize = maThumbRect.GetWidth();
+ else
+ mnThumbPixSize = maThumbRect.GetHeight();
+ }
+ if ( mnThumbPixSize < nMinThumbSize )
+ mnThumbPixSize = nMinThumbSize;
+ if ( mnThumbPixSize > mnThumbPixRange )
+ mnThumbPixSize = mnThumbPixRange;
+ mnThumbPixPos = ImplCalcThumbPosPix( mnThumbPos );
+ }
+ }
+
+ // If we're ought to output again and we have been triggered
+ // a Paint event via an Action, we don't output directly,
+ // but invalidate everything
+ if ( bUpdate && HasPaintEvent() )
+ {
+ Invalidate();
+ bUpdate = false;
+ }
+ ImplUpdateRects( bUpdate );
+}
+
+void ScrollBar::Draw( OutputDevice* pDev, const Point& rPos, DrawFlags nFlags )
+{
+ Point aPos = pDev->LogicToPixel( rPos );
+
+ pDev->Push();
+ pDev->SetMapMode();
+ if ( !(nFlags & DrawFlags::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 nDrawFlags)
+{
+ 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 ((nDrawFlags & SCRBAR_DRAW_PAGE1) || (nDrawFlags & 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 (nDrawFlags & SCRBAR_DRAW_PAGE1)
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::Scrollbar, part1, aCtrlRegion1, nState1, scrValue, OUString());
+
+ if (nDrawFlags & SCRBAR_DRAW_PAGE2)
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::Scrollbar, part2, aCtrlRegion2, nState2, scrValue, OUString());
+ }
+ if ((nDrawFlags & SCRBAR_DRAW_BTN1) || (nDrawFlags & 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 (nDrawFlags & SCRBAR_DRAW_BTN1)
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::Scrollbar, part1, aCtrlRegion1, nState1, scrValue, OUString());
+
+ if (nDrawFlags & SCRBAR_DRAW_BTN2)
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::Scrollbar, part2, aCtrlRegion2, nState2, scrValue, OUString());
+ }
+ if ((nDrawFlags & 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));
+ }
+}
+
+long ScrollBar::ImplScroll( long nNewPos, bool bCallEndScroll )
+{
+ long nOldPos = mnThumbPos;
+ SetThumbPos( nNewPos );
+ long nDelta = mnThumbPos-nOldPos;
+ if ( nDelta )
+ {
+ mnDelta = nDelta;
+ Scroll();
+ if ( bCallEndScroll )
+ EndScroll();
+ mnDelta = 0;
+ }
+ return nDelta;
+}
+
+long ScrollBar::ImplDoAction( bool bCallEndScroll )
+{
+ 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 ( HitTestNativeScrollbar( bHorizontal? (IsRTLEnabled()? ControlPart::ButtonRight: ControlPart::ButtonLeft): ControlPart::ButtonUp,
+ aControlRegion, rMousePos, bIsInside )?
+ bIsInside:
+ maBtn1Rect.IsInside( rMousePos ) )
+ {
+ bAction = bCallAction;
+ mnStateFlags |= SCRBAR_STATE_BTN1_DOWN;
+ }
+ else
+ mnStateFlags &= ~SCRBAR_STATE_BTN1_DOWN;
+ break;
+
+ case ScrollType::LineDown:
+ if ( HitTestNativeScrollbar( bHorizontal? (IsRTLEnabled()? ControlPart::ButtonLeft: ControlPart::ButtonRight): ControlPart::ButtonDown,
+ aControlRegion, rMousePos, bIsInside )?
+ bIsInside:
+ maBtn2Rect.IsInside( 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 ( HitTestNativeScrollbar( bHorizontal? ControlPart::TrackHorzLeft: ControlPart::TrackVertUpper,
+ maPage1Rect, rMousePos, bIsInside )?
+ bIsInside:
+ maPage1Rect.IsInside( 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 ( HitTestNativeScrollbar( bHorizontal? ControlPart::TrackHorzRight: ControlPart::TrackVertLower,
+ maPage2Rect, rMousePos, bIsInside )?
+ bIsInside:
+ maPage2Rect.IsInside( 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 )
+{
+ 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 )
+ {
+ mnThumbPixPos += nMovePix;
+ if ( mnThumbPixPos < 0 )
+ mnThumbPixPos = 0;
+ if ( mnThumbPixPos > (mnThumbPixRange-mnThumbPixSize) )
+ mnThumbPixPos = mnThumbPixRange-mnThumbPixSize;
+ long nOldPos = mnThumbPos;
+ mnThumbPos = ImplCalcThumbPos( mnThumbPixPos );
+ ImplUpdateRects();
+ if ( mbFullDrag && (nOldPos != mnThumbPos) )
+ {
+ // 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(*this);
+
+ 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.
+ Push(PushFlags::MAPMODE);
+ EnableMapMode();
+ MapMode aMapMode = GetMapMode();
+ aMapMode.SetOrigin(Point(0, 0));
+ SetMapMode(aMapMode);
+ aPosPixel = LogicToPixel(rMEvt.GetPosPixel());
+ 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 ( HitTestNativeScrollbar( bHorizontal? (IsRTLEnabled()? ControlPart::ButtonRight: ControlPart::ButtonLeft): ControlPart::ButtonUp,
+ aControlRegion, rMousePos, bIsInside )?
+ bIsInside:
+ maBtn1Rect.IsInside( rMousePos ) )
+ {
+ if (rMEvt.IsLeft() && !(mnStateFlags & SCRBAR_STATE_BTN1_DISABLE) )
+ {
+ nTrackFlags = StartTrackingFlags::ButtonRepeat;
+ meScrollType = ScrollType::LineUp;
+ }
+ }
+ else if ( HitTestNativeScrollbar( bHorizontal? (IsRTLEnabled()? ControlPart::ButtonLeft: ControlPart::ButtonRight): ControlPart::ButtonDown,
+ aControlRegion, rMousePos, bIsInside )?
+ bIsInside:
+ maBtn2Rect.IsInside( rMousePos ) )
+ {
+ if (rMEvt.IsLeft() && !(mnStateFlags & SCRBAR_STATE_BTN2_DISABLE) )
+ {
+ nTrackFlags = StartTrackingFlags::ButtonRepeat;
+ meScrollType = ScrollType::LineDown;
+ }
+ }
+ else
+ {
+ bool bThumbHit = HitTestNativeScrollbar( bHorizontal? ControlPart::ThumbHorz : ControlPart::ThumbVert,
+ maThumbRect, rMousePos, bIsInside )
+ ? bIsInside : maThumbRect.IsInside( 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 && (!HitTestNativeScrollbar( bHorizontal? ControlPart::TrackHorzArea : ControlPart::TrackVertArea,
+ aControlRegion, rMousePos, bIsInside ) ||
+ bIsInside) )
+ {
+ nTrackFlags = StartTrackingFlags::ButtonRepeat;
+
+ // HitTestNativeScrollbar, see remark at top of file
+ if ( HitTestNativeScrollbar( bHorizontal? ControlPart::TrackHorzLeft : ControlPart::TrackVertUpper,
+ maPage1Rect, rMousePos, bIsInside )?
+ bIsInside:
+ maPage1Rect.IsInside( rMousePos ) )
+ {
+ meScrollType = ScrollType::PageUp;
+ }
+ else
+ {
+ meScrollType = ScrollType::PageDown;
+ }
+ }
+ }
+
+ // Should we start Tracking?
+ if ( meScrollType != ScrollType::DontKnow )
+ {
+ // 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() )
+ {
+ 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.
+ Push(PushFlags::MAPMODE);
+ EnableMapMode();
+ MapMode aMapMode = GetMapMode();
+ aMapMode.SetOrigin(Point(0, 0));
+ SetMapMode(aMapMode);
+ aPosPixel = LogicToPixel(rTEvt.GetMouseEvent().GetPosPixel());
+ Pop();
+ }
+ const Point rMousePos = (GetMapMode().GetMapUnit() != MapUnit::MapTwip ? rTEvt.GetMouseEvent().GetPosPixel() : aPosPixel);
+
+ // Dragging is treated in a special way
+ if ( meScrollType == ScrollType::Drag )
+ ImplDragThumb( rMousePos );
+ else
+ ImplDoMouseAction( rMousePos, rTEvt.IsTrackingRepeat() );
+
+ // If ScrollBar values are translated in a way that there's
+ // nothing left to track, we cancel here
+ if ( !IsVisible() || (mnVisibleSize >= (mnMaxRange-mnMinRange)) )
+ EndTracking();
+ }
+}
+
+void ScrollBar::KeyInput( const KeyEvent& rKEvt )
+{
+ if ( !rKEvt.GetKeyCode().GetModifier() )
+ {
+ switch ( rKEvt.GetKeyCode().GetCode() )
+ {
+ case KEY_HOME:
+ DoScroll( 0 );
+ break;
+
+ case KEY_END:
+ DoScroll( GetRangeMax() );
+ break;
+
+ case KEY_LEFT:
+ case KEY_UP:
+ DoScrollAction( ScrollType::LineUp );
+ break;
+
+ case KEY_RIGHT:
+ case KEY_DOWN:
+ DoScrollAction( ScrollType::LineDown );
+ break;
+
+ case KEY_PAGEUP:
+ DoScrollAction( ScrollType::PageUp );
+ break;
+
+ case KEY_PAGEDOWN:
+ DoScrollAction( ScrollType::PageDown );
+ break;
+
+ default:
+ Control::KeyInput( rKEvt );
+ break;
+ }
+ }
+ else
+ Control::KeyInput( rKEvt );
+}
+
+void ScrollBar::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ rRenderContext.SetBackground();
+}
+
+void ScrollBar::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&)
+{
+ ImplDraw(rRenderContext);
+}
+
+void ScrollBar::Move()
+{
+ Control::Move();
+ mbCalcSize = true;
+ if (IsReallyVisible())
+ ImplCalc(false);
+ Invalidate();
+}
+
+void ScrollBar::Resize()
+{
+ Control::Resize();
+ mbCalcSize = true;
+ if ( IsReallyVisible() )
+ ImplCalc( false );
+ Invalidate();
+}
+
+IMPL_LINK_NOARG(ScrollBar, ImplAutoTimerHdl, Timer *, void)
+{
+ if( mpData && mpData->mbHide )
+ return;
+ ImplInvert();
+}
+
+void ScrollBar::ImplInvert()
+{
+ tools::Rectangle aRect( maThumbRect );
+ if( aRect.getWidth() > 4 )
+ {
+ aRect.AdjustLeft(2 );
+ aRect.AdjustRight( -2 );
+ }
+ if( aRect.getHeight() > 4 )
+ {
+ aRect.AdjustTop(2 );
+ aRect.AdjustBottom( -2 );
+ }
+
+ Invert( aRect );
+}
+
+void ScrollBar::GetFocus()
+{
+ if( !mpData )
+ {
+ mpData.reset(new ImplScrollBarData);
+ mpData->maTimer.SetInvokeHandler( LINK( this, ScrollBar, ImplAutoTimerHdl ) );
+ mpData->maTimer.SetDebugName( "vcl::ScrollBar mpData->maTimer" );
+ mpData->mbHide = false;
+ }
+ ImplInvert(); // react immediately
+ mpData->maTimer.SetTimeout( GetSettings().GetStyleSettings().GetCursorBlinkTime() );
+ mpData->maTimer.Start();
+ Control::GetFocus();
+}
+
+void ScrollBar::LoseFocus()
+{
+ if( mpData )
+ mpData->maTimer.Stop();
+ Invalidate();
+
+ Control::LoseFocus();
+}
+
+void ScrollBar::StateChanged( StateChangedType nType )
+{
+ Control::StateChanged( nType );
+
+ if ( nType == StateChangedType::InitShow )
+ ImplCalc( false );
+ else if ( nType == StateChangedType::Data )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ ImplCalc();
+ }
+ else if ( nType == StateChangedType::UpdateMode )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ {
+ ImplCalc( false );
+ Invalidate();
+ }
+ }
+ else if ( nType == StateChangedType::Enable )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::Style )
+ {
+ ImplInitStyle( GetStyle() );
+ if ( IsReallyVisible() && IsUpdateMode() )
+ {
+ if ( (GetPrevStyle() & SCRBAR_VIEW_STYLE) !=
+ (GetStyle() & SCRBAR_VIEW_STYLE) )
+ {
+ mbCalcSize = true;
+ ImplCalc( false );
+ Invalidate();
+ }
+ }
+ }
+}
+
+void ScrollBar::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Control::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) )
+ {
+ mbCalcSize = true;
+ ImplCalc( false );
+ Invalidate();
+ }
+}
+
+tools::Rectangle* ScrollBar::ImplFindPartRect( const Point& rPt )
+{
+ bool bHorizontal = ( GetStyle() & WB_HORZ ) != 0;
+ bool bIsInside = false;
+
+ Point aPoint( 0, 0 );
+ tools::Rectangle aControlRegion( aPoint, GetOutputSizePixel() );
+
+ if( HitTestNativeScrollbar( bHorizontal? (IsRTLEnabled()? ControlPart::ButtonRight: ControlPart::ButtonLeft): ControlPart::ButtonUp,
+ aControlRegion, rPt, bIsInside )?
+ bIsInside:
+ maBtn1Rect.IsInside( rPt ) )
+ return &maBtn1Rect;
+ else if( HitTestNativeScrollbar( bHorizontal? (IsRTLEnabled()? ControlPart::ButtonLeft: ControlPart::ButtonRight): ControlPart::ButtonDown,
+ aControlRegion, rPt, bIsInside )?
+ bIsInside:
+ maBtn2Rect.IsInside( rPt ) )
+ return &maBtn2Rect;
+ // HitTestNativeScrollbar, see remark at top of file
+ else if( HitTestNativeScrollbar( bHorizontal ? ControlPart::TrackHorzLeft : ControlPart::TrackVertUpper,
+ maPage1Rect, rPt, bIsInside)?
+ bIsInside:
+ maPage1Rect.IsInside( rPt ) )
+ return &maPage1Rect;
+ // HitTestNativeScrollbar, see remark at top of file
+ else if( HitTestNativeScrollbar( bHorizontal ? ControlPart::TrackHorzRight : ControlPart::TrackVertLower,
+ maPage2Rect, rPt, bIsInside)?
+ bIsInside:
+ maPage2Rect.IsInside( rPt ) )
+ return &maPage2Rect;
+ // HitTestNativeScrollbar, see remark at top of file
+ else if( HitTestNativeScrollbar( bHorizontal ? ControlPart::ThumbHorz : ControlPart::ThumbVert,
+ maThumbRect, rPt, bIsInside)?
+ bIsInside:
+ maThumbRect.IsInside( rPt ) )
+ return &maThumbRect;
+ else
+ return nullptr;
+}
+
+bool ScrollBar::PreNotify( NotifyEvent& rNEvt )
+{
+ if( rNEvt.GetType() == MouseNotifyEvent::MOUSEMOVE )
+ {
+ const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent();
+ if( pMouseEvt && !pMouseEvt->GetButtons() && !pMouseEvt->IsSynthetic() && !pMouseEvt->IsModifierChanged() )
+ {
+ // Trigger a redraw if mouse over state has changed
+ if( IsNativeControlSupported(ControlType::Scrollbar, ControlPart::Entire) )
+ {
+ tools::Rectangle* pRect = ImplFindPartRect( GetPointerPosPixel() );
+ tools::Rectangle* pLastRect = ImplFindPartRect( GetLastPointerPosPixel() );
+ if( pRect != pLastRect || pMouseEvt->IsLeaveWindow() || pMouseEvt->IsEnterWindow() )
+ {
+ vcl::Region aRgn( 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 );
+ }
+
+ SetClipRegion( aClipRegion );
+ Invalidate(aClipRegion.GetBoundRect());
+
+ 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); } );
+}
+
+long ScrollBar::DoScroll( long nNewPos )
+{
+ if ( meScrollType != ScrollType::DontKnow )
+ return 0;
+
+ SAL_INFO("vcl.scrollbar", "DoScroll(" << nNewPos << ")");
+ meScrollType = ScrollType::Drag;
+ long nDelta = ImplScroll( nNewPos, true );
+ meScrollType = ScrollType::DontKnow;
+ return nDelta;
+}
+
+long ScrollBar::DoScrollAction( ScrollType eScrollType )
+{
+ if ( (meScrollType != ScrollType::DontKnow) ||
+ (eScrollType == ScrollType::DontKnow) ||
+ (eScrollType == ScrollType::Drag) )
+ return 0;
+
+ meScrollType = eScrollType;
+ long nDelta = ImplDoAction( true );
+ meScrollType = ScrollType::DontKnow;
+ return nDelta;
+}
+
+void ScrollBar::SetRangeMin( long nNewRange )
+{
+ SetRange( Range( nNewRange, GetRangeMax() ) );
+}
+
+void ScrollBar::SetRangeMax( long nNewRange )
+{
+ SetRange( Range( GetRangeMin(), nNewRange ) );
+}
+
+void ScrollBar::SetRange( const Range& rRange )
+{
+ // Adapt Range
+ Range aRange = rRange;
+ aRange.Justify();
+ long nNewMinRange = aRange.Min();
+ long nNewMaxRange = aRange.Max();
+
+ // If Range differs, set a new one
+ if ( (mnMinRange != nNewMinRange) ||
+ (mnMaxRange != nNewMaxRange) )
+ {
+ mnMinRange = nNewMinRange;
+ mnMaxRange = nNewMaxRange;
+
+ // Adapt Thumb
+ if ( mnThumbPos > mnMaxRange-mnVisibleSize )
+ mnThumbPos = mnMaxRange-mnVisibleSize;
+ if ( mnThumbPos < mnMinRange )
+ mnThumbPos = mnMinRange;
+
+ CompatStateChanged( StateChangedType::Data );
+ }
+}
+
+void ScrollBar::SetThumbPos( 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( 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 long nMinThumbSize = GetSettings().GetStyleSettings().GetMinThumbSize();
+
+ if (GetStyle() & WB_HORZ)
+ {
+ aRet.setWidth( maBtn1Rect.GetWidth() + nMinThumbSize + maBtn2Rect.GetWidth() );
+ }
+ else
+ {
+ aRet.setHeight( maBtn1Rect.GetHeight() + nMinThumbSize + maBtn2Rect.GetHeight() );
+ }
+
+ return aRet;
+}
+
+Size ScrollBar::getCurrentCalcSize() const
+{
+ tools::Rectangle aCtrlRegion;
+ aCtrlRegion.Union(maBtn1Rect);
+ aCtrlRegion.Union(maBtn2Rect);
+ aCtrlRegion.Union(maPage1Rect);
+ aCtrlRegion.Union(maPage2Rect);
+ aCtrlRegion.Union(maThumbRect);
+ return aCtrlRegion.GetSize();
+}
+
+void ScrollBarBox::ImplInit(vcl::Window* pParent, WinBits nStyle)
+{
+ Window::ImplInit( pParent, nStyle, nullptr );
+
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ long nScrollSize = rStyleSettings.GetScrollBarSize();
+ SetSizePixel(Size(nScrollSize, nScrollSize));
+}
+
+ScrollBarBox::ScrollBarBox( vcl::Window* pParent, WinBits nStyle ) :
+ Window( WindowType::SCROLLBARBOX )
+{
+ ImplInit( pParent, nStyle );
+}
+
+void ScrollBarBox::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ if (rRenderContext.IsBackground())
+ {
+ Color aColor = rRenderContext.GetSettings().GetStyleSettings().GetFaceColor();
+ ApplyControlBackground(rRenderContext, aColor);
+ }
+}
+
+void ScrollBarBox::StateChanged( StateChangedType nType )
+{
+ Window::StateChanged( nType );
+
+ if (nType == StateChangedType::ControlBackground)
+ {
+ Invalidate();
+ }
+}
+
+void ScrollBarBox::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Window::DataChanged( rDCEvt );
+
+ if ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE))
+ {
+ Invalidate();
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/slider.cxx b/vcl/source/control/slider.cxx
new file mode 100644
index 000000000..61841ef4c
--- /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 )
+ {
+ if ( aOldThumbRect != maThumbRect )
+ {
+ 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 );
+ }
+ }
+ }
+}
+
+long Slider::ImplCalcThumbPos( long nPixPos )
+{
+ // calculate position
+ long nCalcThumbPos;
+ nCalcThumbPos = ImplMulDiv( nPixPos-mnThumbPixOffset, mnMaxRange-mnMinRange, mnThumbPixRange-1 );
+ nCalcThumbPos += mnMinRange;
+ return nCalcThumbPos;
+}
+
+long Slider::ImplCalcThumbPosPix( long nPos )
+{
+ // calculate position
+ 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;
+ }
+ }
+
+ long nOldChannelPixRange = mnChannelPixRange;
+ long nOldChannelPixTop = mnChannelPixTop;
+ long nOldChannelPixBottom = mnChannelPixBottom;
+ long nCalcWidth;
+ 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.IsInside(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);
+ DrawButtonFlags nStyle;
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ bool bEnabled = IsEnabled();
+
+ if (!maChannel1Rect.IsEmpty())
+ {
+ 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())
+ {
+ 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())
+ {
+ if (bEnabled)
+ {
+ 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 )
+{
+ 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.IsInside( rPos );
+}
+
+bool Slider::ImplIsPageDown( const Point& rPos )
+{
+ 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.IsInside( rPos );
+}
+
+long Slider::ImplSlide( long nNewPos )
+{
+ long nOldPos = mnThumbPos;
+ SetThumbPos( nNewPos );
+ long nDelta = mnThumbPos-nOldPos;
+ if ( nDelta )
+ {
+ Slide();
+ }
+ return nDelta;
+}
+
+long Slider::ImplDoAction()
+{
+ 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( 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() )
+ {
+ const Point& rMousePos = rMEvt.GetPosPixel();
+ StartTrackingFlags nTrackFlags = StartTrackingFlags::NONE;
+
+ if ( maThumbRect.IsInside( 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 )
+ {
+ 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;
+ long nOldPos = mnThumbPos;
+ mnThumbPos = ImplCalcThumbPos( mnThumbPixPos );
+ if ( nOldPos != mnThumbPos )
+ {
+ ImplUpdateRects();
+ PaintImmediately();
+ if ( nOldPos != mnThumbPos )
+ {
+ Slide();
+ }
+ }
+ }
+ }
+ else
+ ImplDoMouseAction( rMousePos, rTEvt.IsTrackingRepeat() );
+
+ // end tracking if ScrollBar values indicate we are done
+ if ( !IsVisible() )
+ EndTracking();
+ }
+}
+
+void Slider::KeyInput( const KeyEvent& rKEvt )
+{
+ if ( !rKEvt.GetKeyCode().GetModifier() )
+ {
+ switch ( rKEvt.GetKeyCode().GetCode() )
+ {
+ case KEY_HOME:
+ ImplDoSlide( GetRangeMin() );
+ break;
+ case KEY_END:
+ ImplDoSlide( GetRangeMax() );
+ break;
+
+ case KEY_LEFT:
+ case KEY_UP:
+ ImplDoSlideAction( ScrollType::LineUp );
+ break;
+
+ case KEY_RIGHT:
+ case KEY_DOWN:
+ ImplDoSlideAction( ScrollType::LineDown );
+ break;
+
+ case KEY_PAGEUP:
+ ImplDoSlideAction( ScrollType::PageUp );
+ break;
+
+ case KEY_PAGEDOWN:
+ ImplDoSlideAction( ScrollType::PageDown );
+ break;
+
+ default:
+ Control::KeyInput( rKEvt );
+ break;
+ }
+ }
+ else
+ Control::KeyInput( rKEvt );
+}
+
+void Slider::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rRect*/)
+{
+ ImplDraw(rRenderContext);
+}
+
+void Slider::Resize()
+{
+ Control::Resize();
+ mbCalcSize = true;
+ if ( IsReallyVisible() )
+ ImplCalc( false );
+ Invalidate(InvalidateFlags::NoChildren | InvalidateFlags::NoErase);
+}
+
+void Slider::StateChanged( StateChangedType nType )
+{
+ Control::StateChanged( nType );
+
+ if ( nType == StateChangedType::InitShow )
+ ImplCalc( false );
+ else if ( nType == StateChangedType::Data )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ ImplCalc();
+ }
+ else if ( nType == StateChangedType::UpdateMode )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ {
+ ImplCalc( false );
+ Invalidate();
+ }
+ }
+ else if ( nType == StateChangedType::Enable )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ {
+ Invalidate();
+ }
+ }
+ else if ( nType == StateChangedType::Style )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ {
+ if ( (GetPrevStyle() & SLIDER_VIEW_STYLE) !=
+ (GetStyle() & SLIDER_VIEW_STYLE) )
+ {
+ mbCalcSize = true;
+ ImplCalc( false );
+ Invalidate();
+ }
+ }
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ImplInitSettings();
+ Invalidate();
+ }
+}
+
+void Slider::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Control::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) )
+ {
+ ImplInitSettings();
+ Invalidate();
+ }
+}
+
+void Slider::Slide()
+{
+ maSlideHdl.Call( this );
+}
+
+void Slider::SetRangeMin(long nNewRange)
+{
+ SetRange(Range(nNewRange, GetRangeMax()));
+}
+
+void Slider::SetRangeMax(long nNewRange)
+{
+ SetRange(Range(GetRangeMin(), nNewRange));
+}
+
+void Slider::SetRange( const Range& rRange )
+{
+ // adjust Range
+ Range aRange = rRange;
+ aRange.Justify();
+ long nNewMinRange = aRange.Min();
+ 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( long nNewThumbPos )
+{
+ if ( nNewThumbPos < mnMinRange )
+ nNewThumbPos = mnMinRange;
+ if ( nNewThumbPos > mnMaxRange )
+ nNewThumbPos = mnMaxRange;
+
+ if ( mnThumbPos != nNewThumbPos )
+ {
+ mnThumbPos = nNewThumbPos;
+ CompatStateChanged( StateChangedType::Data );
+ }
+}
+
+Size Slider::CalcWindowSizePixel()
+{
+ long nWidth = mnMaxRange - mnMinRange + mnThumbSize + 1;
+ long nHeight = SLIDER_HEIGHT;
+ Size aSize;
+ if ( GetStyle() & WB_HORZ )
+ {
+ aSize.setWidth( nWidth );
+ aSize.setHeight( nHeight );
+ }
+ else
+ {
+ aSize.setHeight( nWidth );
+ aSize.setWidth( nHeight );
+ }
+ return aSize;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/spinbtn.cxx b/vcl/source/control/spinbtn.cxx
new file mode 100644
index 000000000..35e67a1b5
--- /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)
+ , mbUpperIsFocused(false)
+{
+ ImplInit(pParent, nStyle);
+}
+
+IMPL_LINK(SpinButton, ImplTimeout, Timer*, pTimer, void)
+{
+ if (pTimer->GetTimeout() == 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, DrawFlags nFlags)
+{
+ Point aPos = pDev->LogicToPixel(rPos);
+ Size aSize = GetSizePixel();
+
+ pDev->Push();
+ pDev->SetMapMode();
+ if ( !(nFlags & DrawFlags::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.IsInside( rMEvt.GetPosPixel() ) && ( ImplIsUpperEnabled() ) )
+ {
+ mbUpperIn = true;
+ mbInitialUp = true;
+ Invalidate( maUpperRect );
+ }
+ else if ( maLowerRect.IsInside( 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.IsInside( rMEvt.GetPosPixel() ) &&
+ mbUpperIn && mbInitialUp )
+ {
+ mbUpperIn = false;
+ maRepeatTimer.Stop();
+ Invalidate( maUpperRect );
+ }
+ else if ( !maLowerRect.IsInside( rMEvt.GetPosPixel() ) &&
+ mbLowerIn && mbInitialDown )
+ {
+ mbLowerIn = false;
+ maRepeatTimer.Stop();
+ Invalidate( maLowerRect );
+ }
+ else if ( maUpperRect.IsInside( rMEvt.GetPosPixel() ) &&
+ !mbUpperIn && mbInitialUp )
+ {
+ mbUpperIn = true;
+ if ( mbRepeat )
+ maRepeatTimer.Start();
+ Invalidate( maUpperRect );
+ }
+ else if ( maLowerRect.IsInside( 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( long nNewRange )
+{
+ SetRange( Range( nNewRange, GetRangeMax() ) );
+}
+
+void SpinButton::SetRangeMax( long nNewRange )
+{
+ SetRange( Range( GetRangeMin(), nNewRange ) );
+}
+
+void SpinButton::SetRange( const Range& rRange )
+{
+ // adjust rage
+ Range aRange = rRange;
+ aRange.Justify();
+ long nNewMinRange = aRange.Min();
+ long nNewMaxRange = aRange.Max();
+
+ // do something only if old and new range differ
+ if ( (mnMinRange != nNewMinRange) ||
+ (mnMaxRange != nNewMaxRange) )
+ {
+ 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( 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.IsInside( rPt ) )
+ return &maUpperRect;
+ else if( maLowerRect.IsInside( rPt ) )
+ return &maLowerRect;
+ else
+ return nullptr;
+}
+
+bool SpinButton::PreNotify( NotifyEvent& rNEvt )
+{
+ if (rNEvt.GetType() == MouseNotifyEvent::MOUSEMOVE)
+ {
+ const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent();
+ if (pMouseEvt && !pMouseEvt->GetButtons() && !pMouseEvt->IsSynthetic() && !pMouseEvt->IsModifierChanged())
+ {
+ // trigger redraw if mouse over state has changed
+ if (IsNativeControlSupported(ControlType::Spinbox, ControlPart::Entire) ||
+ IsNativeControlSupported(ControlType::Spinbox, ControlPart::AllButtons) )
+ {
+ tools::Rectangle* pRect = ImplFindPartRect( GetPointerPosPixel() );
+ tools::Rectangle* pLastRect = ImplFindPartRect( GetLastPointerPosPixel() );
+ if (pRect != pLastRect || (pMouseEvt->IsLeaveWindow() || pMouseEvt->IsEnterWindow()))
+ {
+ vcl::Region aRgn(GetActiveClipRegion());
+ if (pLastRect)
+ {
+ SetClipRegion(vcl::Region(*pLastRect));
+ Invalidate(*pLastRect);
+ SetClipRegion( aRgn );
+ }
+ if (pRect)
+ {
+ SetClipRegion(vcl::Region(*pRect));
+ Invalidate(*pRect);
+ SetClipRegion(aRgn);
+ }
+ }
+ }
+ }
+ }
+
+ return Control::PreNotify(rNEvt);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/spinfld.cxx b/vcl/source/control/spinfld.cxx
new file mode 100644
index 000000000..a5206cf72
--- /dev/null
+++ b/vcl/source/control/spinfld.cxx
@@ -0,0 +1,1007 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/spinfld.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/uitest/uiobject.hxx>
+#include <sal/log.hxx>
+
+#include <controldata.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.IsInside(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.IsInside(aPointerPos) && !rUpperRect.IsInside(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->GetClipRegion();
+ pBorder->SetClipRegion(vcl::Region(aClipRect));
+
+ pContext = pBorder;
+ }
+
+ 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->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::LONGCURRENCYFIELD:
+ case WindowType::NUMERICFIELD:
+ 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
+ long nTempSize1 = aUpRect.GetWidth();
+ 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;
+}
+
+void SpinField::ImplInit(vcl::Window* pParent, WinBits nWinStyle)
+{
+ Edit::ImplInit( pParent, nWinStyle );
+
+ if (nWinStyle & (WB_SPIN | WB_DROPDOWN))
+ {
+ 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(*this, 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)
+{
+ 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.IsInside(rMEvt.GetPosPixel()))
+ {
+ mbUpperIn = true;
+ mbInitialUp = true;
+ Invalidate(maUpperRect);
+ }
+ else if (maLowerRect.IsInside(rMEvt.GetPosPixel()))
+ {
+ mbLowerIn = true;
+ mbInitialDown = true;
+ Invalidate(maLowerRect);
+ }
+ else if (maDropDownRect.IsInside(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.IsInside(rMEvt.GetPosPixel());
+ if (bNewUpperIn != mbUpperIn)
+ {
+ if (bNewUpperIn)
+ {
+ if (mbRepeat)
+ maRepeatTimer.Start();
+ }
+ else
+ maRepeatTimer.Stop();
+
+ mbUpperIn = bNewUpperIn;
+ Invalidate(maUpperRect);
+ }
+ }
+ else if (mbInitialDown)
+ {
+ bool bNewLowerIn = maLowerRect.IsInside(rMEvt.GetPosPixel());
+ if (bNewLowerIn != mbLowerIn)
+ {
+ if (bNewLowerIn)
+ {
+ if (mbRepeat)
+ maRepeatTimer.Start();
+ }
+ else
+ maRepeatTimer.Stop();
+
+ mbLowerIn = bNewLowerIn;
+ Invalidate(maLowerRect);
+ }
+ }
+ }
+
+ Edit::MouseMove(rMEvt);
+}
+
+bool SpinField::EventNotify(NotifyEvent& rNEvt)
+{
+ bool bDone = false;
+ if (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT)
+ {
+ const KeyEvent& rKEvt = *rNEvt.GetKeyEvent();
+ if (!IsReadOnly())
+ {
+ sal_uInt16 nMod = rKEvt.GetKeyCode().GetModifier();
+ switch (rKEvt.GetKeyCode().GetCode())
+ {
+ case KEY_UP:
+ {
+ if (!nMod)
+ {
+ Up();
+ bDone = true;
+ }
+ }
+ break;
+ case KEY_DOWN:
+ {
+ if (!nMod)
+ {
+ Down();
+ bDone = true;
+ }
+ else if ((nMod == KEY_MOD2) && !mbInDropDown && (GetStyle() & WB_DROPDOWN))
+ {
+ mbInDropDown = ShowDropDown(true);
+ Invalidate(tools::Rectangle(Point(), GetOutputSizePixel()));
+ bDone = true;
+ }
+ }
+ break;
+ case KEY_PAGEUP:
+ {
+ if (!nMod)
+ {
+ Last();
+ bDone = true;
+ }
+ }
+ break;
+ case KEY_PAGEDOWN:
+ {
+ if (!nMod)
+ {
+ First();
+ bDone = true;
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ if (rNEvt.GetType() == MouseNotifyEvent::COMMAND)
+ {
+ if ((rNEvt.GetCommandEvent()->GetCommand() == CommandEventId::Wheel) && !IsReadOnly())
+ {
+ MouseWheelBehaviour nWheelBehavior(GetSettings().GetMouseSettings().GetWheelBehavior());
+ if (nWheelBehavior == MouseWheelBehaviour::ALWAYS
+ || (nWheelBehavior == MouseWheelBehaviour::FocusOnly && HasChildPathFocus()))
+ {
+ const CommandWheelData* pData = rNEvt.GetCommandEvent()->GetWheelData();
+ if (pData->GetMode() == CommandWheelMode::SCROLL)
+ {
+ if (pData->GetDelta() < 0)
+ Down();
+ else
+ Up();
+ bDone = true;
+ }
+ }
+ 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)
+ {
+ mpControlData->mpLayoutData.reset( new vcl::ControlLayoutData );
+ AppendLayoutData(*GetSubEdit());
+ GetSubEdit()->SetLayoutDataParent(this);
+ }
+ else
+ Edit::FillLayoutData();
+}
+
+void SpinField::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ if (mbSpin)
+ {
+ bool bEnable = IsEnabled();
+ ImplDrawSpinButton(rRenderContext, this, maUpperRect, maLowerRect,
+ mbUpperIn, mbLowerIn, bEnable, bEnable);
+ }
+
+ 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(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)
+ {
+ 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)
+ {
+ long nBottom1 = aSize.Height()/2;
+ long nBottom2 = aSize.Height()-1;
+ 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 = static_cast<vcl::Window*>(pDev);
+ 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)
+ {
+ Control::Resize();
+ Size aSize = GetOutputSizePixel();
+ bool bSubEditPositioned = false;
+
+ if (GetStyle() & (WB_SPIN | WB_DROPDOWN))
+ {
+ ImplCalcButtonAreas( this, 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.IsInside(rPt))
+ return &maUpperRect;
+ else if (maLowerRect.IsInside(rPt))
+ return &maLowerRect;
+ else
+ return nullptr;
+}
+
+bool SpinField::PreNotify(NotifyEvent& rNEvt)
+{
+ if (rNEvt.GetType() == MouseNotifyEvent::MOUSEMOVE)
+ {
+ const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent();
+ if (pMouseEvt && !pMouseEvt->GetButtons() && !pMouseEvt->IsSynthetic() && !pMouseEvt->IsModifierChanged())
+ {
+ // trigger redraw if mouse over state has changed
+ if( IsNativeControlSupported(ControlType::Spinbox, ControlPart::Entire) ||
+ IsNativeControlSupported(ControlType::Spinbox, ControlPart::AllButtons) )
+ {
+ tools::Rectangle* pRect = ImplFindPartRect( GetPointerPosPixel() );
+ tools::Rectangle* pLastRect = ImplFindPartRect( GetLastPointerPosPixel() );
+ if( pRect != pLastRect || (pMouseEvt->IsLeaveWindow() || pMouseEvt->IsEnterWindow()) )
+ {
+ // FIXME: this is currently only on macOS
+ // check for other platforms that need similar handling
+ if (ImplGetSVData()->maNWFData.mbNoFocusRects && IsNativeWidgetEnabled() &&
+ IsNativeControlSupported(ControlType::Editbox, ControlPart::Entire))
+ {
+ ImplInvalidateOutermostBorder(this);
+ }
+ else
+ {
+ // paint directly
+ vcl::Region aRgn( GetActiveClipRegion() );
+ if (pLastRect)
+ {
+ SetClipRegion(vcl::Region(*pLastRect));
+ Invalidate(*pLastRect);
+ SetClipRegion( aRgn );
+ }
+ if (pRect)
+ {
+ SetClipRegion(vcl::Region(*pRect));
+ Invalidate(*pRect);
+ 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() == MouseSettings::GetButtonStartRepeat() )
+ {
+ pTimer->SetTimeout( GetSettings().GetMouseSettings().GetButtonRepeat() );
+ pTimer->Start();
+ }
+ else
+ {
+ if ( mbInitialUp )
+ Up();
+ else
+ Down();
+ }
+}
+
+void SpinField::Draw(OutputDevice* pDev, const Point& rPos, DrawFlags nFlags)
+{
+ Edit::Draw(pDev, rPos, nFlags);
+
+ WinBits nFieldStyle = GetStyle();
+ if ( (nFlags & DrawFlags::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 & DrawFlags::Mono)
+ aButtonTextColor = COL_BLACK;
+ else
+ aButtonTextColor = GetSettings().GetStyleSettings().GetButtonTextColor();
+
+ if (GetStyle() & WB_DROPDOWN)
+ {
+ DecorationView aView( pDev );
+ tools::Rectangle aInnerRect = aView.DrawButton( aDD, DrawButtonFlags::NoLightBorder );
+ DrawSymbolFlags nSymbolStyle = IsEnabled() ? DrawSymbolFlags::NONE : DrawSymbolFlags::Disable;
+ aView.DrawSymbol(aInnerRect, SymbolType::SPIN_DOWN, aButtonTextColor, nSymbolStyle);
+ }
+
+ if (GetStyle() & WB_SPIN)
+ {
+ ImplDrawSpinButton(*pDev, this, aUp, aDown, false, false);
+ }
+
+ pDev->Pop();
+ pDev->SetSettings(aOldSettings);
+
+}
+
+FactoryFunction SpinField::GetUITestFactory() const
+{
+ return SpinFieldUIObject::create;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/tabctrl.cxx b/vcl/source/control/tabctrl.cxx
new file mode 100644
index 000000000..6079b5a76
--- /dev/null
+++ b/vcl/source/control/tabctrl.cxx
@@ -0,0 +1,2382 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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.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/toolkit/controllayout.hxx>
+#include <vcl/layout.hxx>
+#include <vcl/lstbox.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/uitest/uiobject.hxx>
+#include <bitmaps.hlst>
+
+#include <controldata.hxx>
+#include <svdata.hxx>
+#include <window.h>
+
+#include <deque>
+#include <unordered_map>
+#include <vector>
+
+class ImplTabItem final
+{
+ sal_uInt16 m_nId;
+
+public:
+ VclPtr<TabPage> mpTabPage;
+ OUString maText;
+ OUString maFormatText;
+ OUString maHelpText;
+ OString maTabName;
+ tools::Rectangle maRect;
+ sal_uInt16 mnLine;
+ bool mbFullVisible;
+ bool m_bEnabled; ///< the tab / page is selectable
+ bool m_bVisible; ///< the tab / page can be visible
+ Image maTabImage;
+
+ ImplTabItem(sal_uInt16 nId);
+
+ sal_uInt16 id() const { return m_nId; }
+};
+
+ImplTabItem::ImplTabItem(sal_uInt16 nId)
+ : m_nId(nId)
+ , mnLine(0)
+ , mbFullVisible(false)
+ , m_bEnabled(true)
+ , m_bVisible(true)
+{
+}
+
+struct ImplTabCtrlData
+{
+ std::unordered_map< int, int > maLayoutPageIdToLine;
+ std::unordered_map< int, int > maLayoutLineToPageId;
+ Point maItemsOffset; // offset of the tabitems
+ 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;
+ 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 )
+ {
+ vcl::Window* pParent = GetParent();
+ if ( !IsControlBackground() &&
+ (pParent->IsChildTransparentModeEnabled()
+ || IsNativeControlSupported(ControlType::TabPane, ControlPart::Entire)
+ || IsNativeControlSupported(ControlType::TabItem, ControlPart::Entire) ) )
+
+ {
+ // set transparent mode for NWF tabcontrols to have
+ // the background always cleared properly
+ EnableChildTransparentMode();
+ SetParentClipMode( ParentClipMode::NoClip );
+ SetPaintTransparent( true );
+ SetBackground();
+ ImplGetWindowImpl()->mbUseNativeFocus = ImplGetSVData()->maNWFData.mbNoFocusRects;
+ }
+ else
+ {
+ EnableChildTransparentMode( false );
+ SetParentClipMode();
+ SetPaintTransparent( false );
+
+ if ( IsControlBackground() )
+ SetBackground( GetControlBackground() );
+ else
+ SetBackground( pParent->GetBackground() );
+ }
+ }
+}
+
+void TabControl::ImplFreeLayoutData()
+{
+ if( HasLayoutData() )
+ {
+ ImplClearLayoutData();
+ mpTabCtrlData->maLayoutPageIdToLine.clear();
+ mpTabCtrlData->maLayoutLineToPageId.clear();
+ }
+}
+
+TabControl::TabControl( vcl::Window* pParent, WinBits nStyle ) :
+ Control( WindowType::TABCONTROL )
+{
+ ImplInit( pParent, nStyle );
+ SAL_INFO( "vcl", "*** TABCONTROL no notabs? " << (( GetStyle() & WB_NOBORDER ) ? "true" : "false") );
+}
+
+TabControl::~TabControl()
+{
+ disposeOnce();
+}
+
+void TabControl::dispose()
+{
+ Window *pParent = GetParent();
+ if (pParent && pParent->IsDialog())
+ GetParent()->RemoveChildEventListener( LINK( this, TabControl, ImplWindowEventListener ) );
+
+ ImplFreeLayoutData();
+
+ // delete TabCtrl data
+ if (mpTabCtrlData)
+ mpTabCtrlData->mpListBox.disposeAndClear();
+ mpTabCtrlData.reset();
+ Control::dispose();
+}
+
+ImplTabItem* TabControl::ImplGetItem( sal_uInt16 nId ) const
+{
+ for (auto & item : mpTabCtrlData->maItemList)
+ {
+ if (item.id() == nId)
+ return &item;
+ }
+
+ return nullptr;
+}
+
+Size TabControl::ImplGetItemSize( ImplTabItem* pItem, long nMaxWidth )
+{
+ pItem->maFormatText = pItem->maText;
+ Size aSize( GetCtrlTextWidth( pItem->maFormatText ), GetTextHeight() );
+ Size aImageSize( 0, 0 );
+ if( !!pItem->maTabImage )
+ {
+ aImageSize = pItem->maTabImage.GetSizePixel();
+ if( !pItem->maFormatText.isEmpty() )
+ aImageSize.AdjustWidth(GetTextHeight()/4 );
+ }
+ aSize.AdjustWidth(aImageSize.Width() );
+ if( aImageSize.Height() > aSize.Height() )
+ aSize.setHeight( aImageSize.Height() );
+
+ aSize.AdjustWidth(TAB_TABOFFSET_X*2 );
+ aSize.AdjustHeight(TAB_TABOFFSET_Y*2 );
+
+ tools::Rectangle aCtrlRegion( Point( 0, 0 ), aSize );
+ tools::Rectangle aBoundingRgn, aContentRgn;
+ const TabitemValue aControlValue(tools::Rectangle(TAB_TABOFFSET_X, TAB_TABOFFSET_Y,
+ aSize.Width() - TAB_TABOFFSET_X * 2,
+ aSize.Height() - TAB_TABOFFSET_Y * 2));
+ if(GetNativeControlRegion( ControlType::TabItem, ControlPart::Entire, aCtrlRegion,
+ ControlState::ENABLED, aControlValue,
+ aBoundingRgn, aContentRgn ) )
+ {
+ return aContentRgn.GetSize();
+ }
+
+ // For languages with short names (e.g. Chinese), because the space is
+ // normally only one pixel per char
+ if ( pItem->maFormatText.getLength() < TAB_EXTRASPACE_X )
+ aSize.AdjustWidth(TAB_EXTRASPACE_X-pItem->maFormatText.getLength() );
+
+ // shorten Text if needed
+ if ( aSize.Width()+4 >= nMaxWidth )
+ {
+ OUString aAppendStr("...");
+ pItem->maFormatText += aAppendStr;
+ do
+ {
+ if (pItem->maFormatText.getLength() > aAppendStr.getLength())
+ pItem->maFormatText = pItem->maFormatText.replaceAt( pItem->maFormatText.getLength()-aAppendStr.getLength()-1, 1, "" );
+ aSize.setWidth( GetCtrlTextWidth( pItem->maFormatText ) );
+ aSize.AdjustWidth(aImageSize.Width() );
+ aSize.AdjustWidth(TAB_TABOFFSET_X*2 );
+ }
+ while ( (aSize.Width()+4 >= nMaxWidth) && (pItem->maFormatText.getLength() > aAppendStr.getLength()) );
+ if ( aSize.Width()+4 >= nMaxWidth )
+ {
+ pItem->maFormatText = ".";
+ aSize.setWidth( 1 );
+ }
+ }
+
+ if( pItem->maFormatText.isEmpty() )
+ {
+ if( aSize.Height() < aImageSize.Height()+4 ) //leave space for focus rect
+ aSize.setHeight( aImageSize.Height()+4 );
+ }
+
+ return aSize;
+}
+
+// Feel free to move this to some more general place for reuse
+// http://en.wikipedia.org/wiki/Word_wrap#Minimum_raggedness
+// Mostly based on Alexey Frunze's nifty example at
+// http://stackoverflow.com/questions/9071205/balanced-word-wrap-minimum-raggedness-in-php
+namespace MinimumRaggednessWrap
+{
+ static std::deque<size_t> GetEndOfLineIndexes(const std::vector<sal_Int32>& rWidthsOf, sal_Int32 nLineWidth)
+ {
+ ++nLineWidth;
+
+ size_t nWidthsCount = rWidthsOf.size();
+ std::vector<sal_Int32> aCosts(nWidthsCount * nWidthsCount);
+
+ // cost function c(i, j) that computes the cost of a line consisting of
+ // the words Word[i] to Word[j]
+ for (size_t i = 0; i < nWidthsCount; ++i)
+ {
+ for (size_t j = 0; j < nWidthsCount; ++j)
+ {
+ if (j >= i)
+ {
+ sal_Int32 c = nLineWidth - (j - i);
+ for (size_t k = i; k <= j; ++k)
+ c -= rWidthsOf[k];
+ c = (c >= 0) ? c * c : SAL_MAX_INT32;
+ aCosts[j * nWidthsCount + i] = c;
+ }
+ else
+ {
+ aCosts[j * nWidthsCount + i] = SAL_MAX_INT32;
+ }
+ }
+ }
+
+ std::vector<sal_Int32> aFunction(nWidthsCount);
+ std::vector<sal_Int32> aWrapPoints(nWidthsCount);
+
+ // f(j) in aFunction[], collect wrap points in aWrapPoints[]
+ for (size_t j = 0; j < nWidthsCount; ++j)
+ {
+ aFunction[j] = aCosts[j * nWidthsCount];
+ if (aFunction[j] == SAL_MAX_INT32)
+ {
+ for (size_t k = 0; k < j; ++k)
+ {
+ sal_Int32 s;
+ if (aFunction[k] == SAL_MAX_INT32 || aCosts[j * nWidthsCount + k + 1] == SAL_MAX_INT32)
+ s = SAL_MAX_INT32;
+ else
+ s = aFunction[k] + aCosts[j * nWidthsCount + k + 1];
+ if (aFunction[j] > s)
+ {
+ aFunction[j] = s;
+ aWrapPoints[j] = k + 1;
+ }
+ }
+ }
+ }
+
+ std::deque<size_t> aSolution;
+
+ // no solution
+ if (aFunction[nWidthsCount - 1] == SAL_MAX_INT32)
+ return aSolution;
+
+ // optimal solution
+ size_t j = nWidthsCount - 1;
+ while (true)
+ {
+ aSolution.push_front(j);
+ if (!aWrapPoints[j])
+ break;
+ j = aWrapPoints[j] - 1;
+ }
+
+ return aSolution;
+ }
+};
+
+static void lcl_AdjustSingleLineTabs(long nMaxWidth, ImplTabCtrlData *pTabCtrlData)
+{
+ if (!ImplGetSVData()->maNWFData.mbCenteredTabs)
+ return;
+
+ int nRightSpace = nMaxWidth; // space left on the right by the tabs
+ for (auto const& item : pTabCtrlData->maItemList)
+ {
+ if (!item.m_bVisible)
+ continue;
+ nRightSpace -= item.maRect.Right() - item.maRect.Left();
+ }
+ nRightSpace /= 2;
+
+ for (auto& item : pTabCtrlData->maItemList)
+ {
+ if (!item.m_bVisible)
+ continue;
+ item.maRect.AdjustLeft(nRightSpace);
+ item.maRect.AdjustRight(nRightSpace);
+ }
+}
+
+bool TabControl::ImplPlaceTabs( long nWidth )
+{
+ if ( nWidth <= 0 )
+ return false;
+ if ( mpTabCtrlData->maItemList.empty() )
+ return false;
+
+ long nMaxWidth = nWidth;
+
+ const long nOffsetX = 2 + GetItemsOffset().X();
+ const long nOffsetY = 2 + GetItemsOffset().Y();
+
+ //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));
+
+ nMaxWidth -= GetItemsOffset().X();
+
+ long nX = nOffsetX;
+ long nY = nOffsetY;
+
+ sal_uInt16 nLines = 0;
+ sal_uInt16 nCurLine = 0;
+
+ 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
+ {
+ long nLineHeightAry[100];
+ 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)) + GetItemsOffset().Y();
+ else
+ nLineHeightAry[i] = nIH*(i-nCurLine-1) + GetItemsOffset().Y();
+ }
+
+ nLinePosAry[nLines+1] = static_cast<sal_uInt16>(mpTabCtrlData->maItemList.size());
+
+ long nDX = 0;
+ long nModDX = 0;
+ 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, long nWidth, long nHeight )
+{
+ Size aWinSize = Control::GetOutputSizePixel();
+ if ( nWidth < 0 )
+ nWidth = aWinSize.Width();
+ if ( nHeight < 0 )
+ nHeight = aWinSize.Height();
+
+ if ( mpTabCtrlData->maItemList.empty() )
+ {
+ long nW = nWidth-TAB_OFFSET*2;
+ 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;
+
+ long nW = nWidth-TAB_OFFSET*2;
+ long nH = nHeight-aRect.Bottom()-TAB_OFFSET*2;
+ return (nW > 0 && nH > 0)
+ ? tools::Rectangle( Point( TAB_OFFSET, aRect.Bottom()+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, long nWidth, long nHeight)
+{
+ if ((nWidth <= 1) || (nHeight <= 0) || !pItem || !pItem->m_bVisible)
+ return tools::Rectangle();
+
+ nWidth -= 1;
+
+ if ( mbFormat || (mnLastWidth != nWidth) || (mnLastHeight != nHeight) )
+ {
+ vcl::Font aFont( GetFont() );
+ aFont.SetTransparent( true );
+ SetFont( aFont );
+
+ bool bRet = ImplPlaceTabs( nWidth );
+ if ( !bRet )
+ return tools::Rectangle();
+
+ mnLastWidth = nWidth;
+ mnLastHeight = nHeight;
+ mbFormat = false;
+ }
+
+ return pItem->maRect;
+}
+
+void TabControl::ImplChangeTabPage( sal_uInt16 nId, sal_uInt16 nOldId )
+{
+ ImplFreeLayoutData();
+
+ ImplTabItem* pOldItem = ImplGetItem( nOldId );
+ ImplTabItem* pItem = ImplGetItem( nId );
+ TabPage* pOldPage = pOldItem ? pOldItem->mpTabPage.get() : nullptr;
+ TabPage* pPage = pItem ? pItem->mpTabPage.get() : nullptr;
+ vcl::Window* pCtrlParent = GetParent();
+
+ if ( IsReallyVisible() && IsUpdateMode() )
+ {
+ sal_uInt16 nPos = GetPagePos( nId );
+ tools::Rectangle aRect = ImplGetTabRect( nPos );
+
+ if ( !pOldItem || !pItem || (pOldItem->mnLine != pItem->mnLine) )
+ {
+ aRect.SetLeft( 0 );
+ aRect.SetTop( 0 );
+ aRect.SetRight( Control::GetOutputSizePixel().Width() );
+ }
+ else
+ {
+ aRect.AdjustLeft( -3 );
+ aRect.AdjustTop( -2 );
+ aRect.AdjustRight(3 );
+ Invalidate( aRect );
+ nPos = GetPagePos( nOldId );
+ aRect = ImplGetTabRect( nPos );
+ aRect.AdjustLeft( -3 );
+ aRect.AdjustTop( -2 );
+ aRect.AdjustRight(3 );
+ }
+ Invalidate( aRect );
+ }
+
+ if ( pOldPage == pPage )
+ return;
+
+ tools::Rectangle aRect = ImplGetTabRect( TAB_PAGERECT );
+
+ if ( pOldPage )
+ {
+ if ( mbRestoreHelpId )
+ pCtrlParent->SetHelpId( OString() );
+ }
+
+ if ( pPage )
+ {
+ if ( GetStyle() & WB_NOBORDER )
+ {
+ tools::Rectangle aRectNoTab(Point(0, 0), GetSizePixel());
+ pPage->SetPosSizePixel( aRectNoTab.TopLeft(), aRectNoTab.GetSize() );
+ }
+ else
+ pPage->SetPosSizePixel( aRect.TopLeft(), aRect.GetSize() );
+
+ // activate page here so the controls can be switched
+ // also set the help id of the parent window to that of the tab page
+ if ( GetHelpId().isEmpty() )
+ {
+ mbRestoreHelpId = true;
+ pCtrlParent->SetHelpId( pPage->GetHelpId() );
+ }
+
+ pPage->Show();
+
+ if ( pOldPage && pOldPage->HasChildPathFocus() )
+ {
+ vcl::Window* pFirstChild = pPage->ImplGetDlgWindow( 0, GetDlgWindowType::First );
+ if ( pFirstChild )
+ pFirstChild->ImplControlFocus( GetFocusFlags::Init );
+ else
+ GrabFocus();
+ }
+ }
+
+ if ( pOldPage )
+ pOldPage->Hide();
+
+ // Invalidate the same region that will be send to NWF
+ // to always allow for bitmap caching
+ // see Window::DrawNativeControl()
+ if( IsNativeControlSupported( ControlType::TabPane, ControlPart::Entire ) )
+ {
+ aRect.AdjustLeft( -(TAB_OFFSET) );
+ aRect.AdjustTop( -(TAB_OFFSET) );
+ aRect.AdjustRight(TAB_OFFSET );
+ aRect.AdjustBottom(TAB_OFFSET );
+ }
+
+ Invalidate( aRect );
+}
+
+bool TabControl::ImplPosCurTabPage()
+{
+ // resize/position current TabPage
+ ImplTabItem* pItem = ImplGetItem( GetCurPageId() );
+ if ( pItem && pItem->mpTabPage )
+ {
+ if ( GetStyle() & WB_NOBORDER )
+ {
+ tools::Rectangle aRectNoTab(Point(0, 0), GetSizePixel());
+ pItem->mpTabPage->SetPosSizePixel( aRectNoTab.TopLeft(), aRectNoTab.GetSize() );
+ return true;
+ }
+ tools::Rectangle aRect = ImplGetTabRect( TAB_PAGERECT );
+ pItem->mpTabPage->SetPosSizePixel( aRect.TopLeft(), aRect.GetSize() );
+ return true;
+ }
+
+ return false;
+}
+
+void TabControl::ImplActivateTabPage( bool bNext )
+{
+ sal_uInt16 nCurPos = GetPagePos( GetCurPageId() );
+
+ if ( bNext )
+ nCurPos = (nCurPos + 1) % GetPageCount();
+ else
+ {
+ if ( !nCurPos )
+ nCurPos = GetPageCount()-1;
+ else
+ nCurPos--;
+ }
+
+ SelectTabPage( GetPageId( nCurPos ) );
+}
+
+void TabControl::ImplShowFocus()
+{
+ if ( !GetPageCount() || mpTabCtrlData->mpListBox )
+ return;
+
+ sal_uInt16 nCurPos = GetPagePos( mnCurPageId );
+ tools::Rectangle aRect = ImplGetTabRect( nCurPos );
+ const ImplTabItem& rItem = mpTabCtrlData->maItemList[ nCurPos ];
+ Size aTabSize = aRect.GetSize();
+ Size aImageSize( 0, 0 );
+ long nTextHeight = GetTextHeight();
+ long nTextWidth = 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
+ long nXPos = aRect.Left()+((aTabSize.Width()-nTextWidth-aImageSize.Width())/2)-nOff-1;
+ 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;
+ long nLeftBottom = aRect.Bottom();
+ 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.IsInside(aLeftTestPos))
+ bLeftBorder = false;
+ aRightTestPos.AdjustX(2 );
+ if (rCurRect.IsInside(aRightTestPos))
+ bRightBorder = false;
+ }
+ else
+ {
+ if (rCurRect.IsInside(aLeftTestPos))
+ nLeftBottom -= 2;
+ if (rCurRect.IsInside(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.IsInside(GetPointerPosPixel()))
+ {
+ nState |= ControlState::ROLLOVER;
+ for (auto const& item : mpTabCtrlData->maItemList)
+ if ((&item != pItem) && item.m_bVisible && item.maRect.IsInside(GetPointerPosPixel()))
+ {
+ nState &= ~ControlState::ROLLOVER; // avoid multiple highlighted tabs
+ break;
+ }
+ assert(nState & ControlState::ROLLOVER);
+ }
+
+ bNativeOK = rRenderContext.IsNativeControlSupported(ControlType::TabItem, ControlPart::Entire);
+ if ( bNativeOK )
+ {
+ TabitemValue tiValue(tools::Rectangle(pItem->maRect.Left() + TAB_TABOFFSET_X,
+ pItem->maRect.Top() + TAB_TABOFFSET_Y,
+ pItem->maRect.Right() - TAB_TABOFFSET_X,
+ pItem->maRect.Bottom() - TAB_TABOFFSET_Y));
+ if (pItem->maRect.Left() < 5)
+ tiValue.mnAlignment |= TabitemFlags::LeftAligned;
+ if (pItem->maRect.Right() > mnLastWidth - 5)
+ tiValue.mnAlignment |= TabitemFlags::RightAligned;
+ if (bFirstInGroup)
+ tiValue.mnAlignment |= TabitemFlags::FirstInGroup;
+ if (bLastInGroup)
+ tiValue.mnAlignment |= TabitemFlags::LastInGroup;
+
+ tools::Rectangle aCtrlRegion( pItem->maRect );
+ aCtrlRegion.AdjustBottom(TabPaneValue::m_nOverlap);
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::TabItem, ControlPart::Entire,
+ aCtrlRegion, nState, tiValue, OUString() );
+ }
+
+ if (!bNativeOK)
+ {
+ if (!(rStyleSettings.GetOptions() & StyleSettingsOptions::Mono))
+ {
+ rRenderContext.SetLineColor(rStyleSettings.GetLightColor());
+ rRenderContext.DrawPixel(Point(aRect.Left() + 1 - nOff2, aRect.Top() + 1 - nOff2)); // diagonally indented top-left pixel
+ if (bLeftBorder)
+ {
+ rRenderContext.DrawLine(Point(aRect.Left() - nOff2, aRect.Top() + 2 - nOff2),
+ Point(aRect.Left() - nOff2, nLeftBottom - 1));
+ }
+ rRenderContext.DrawLine(Point(aRect.Left() + 2 - nOff2, aRect.Top() - nOff2), // top line starting 2px from left border
+ Point(aRect.Right() + nOff2 - 3, aRect.Top() - nOff2)); // ending 3px from right border
+
+ if (bRightBorder)
+ {
+ rRenderContext.SetLineColor(rStyleSettings.GetShadowColor());
+ rRenderContext.DrawLine(Point(aRect.Right() + nOff2 - 2, aRect.Top() + 1 - nOff2),
+ Point(aRect.Right() + nOff2 - 2, nRightBottom - 1));
+
+ rRenderContext.SetLineColor(rStyleSettings.GetDarkShadowColor());
+ rRenderContext.DrawLine(Point(aRect.Right() + nOff2 - 1, aRect.Top() + 3 - nOff2),
+ Point(aRect.Right() + nOff2 - 1, nRightBottom - 1));
+ }
+ }
+ else
+ {
+ rRenderContext.SetLineColor(COL_BLACK);
+ rRenderContext.DrawPixel(Point(aRect.Left() + 1 - nOff2, aRect.Top() + 1 - nOff2));
+ rRenderContext.DrawPixel(Point(aRect.Right() + nOff2 - 2, aRect.Top() + 1 - nOff2));
+ if (bLeftBorder)
+ {
+ rRenderContext.DrawLine(Point(aRect.Left() - nOff2, aRect.Top() + 2 - nOff2),
+ Point(aRect.Left() - nOff2, nLeftBottom - 1));
+ }
+ rRenderContext.DrawLine(Point(aRect.Left() + 2 - nOff2, aRect.Top() - nOff2),
+ Point(aRect.Right() - 3, aRect.Top() - nOff2));
+ if (bRightBorder)
+ {
+ rRenderContext.DrawLine(Point(aRect.Right() + nOff2 - 1, aRect.Top() + 2 - nOff2),
+ Point(aRect.Right() + nOff2 - 1, nRightBottom - 1));
+ }
+ }
+ }
+
+ // set font accordingly, current item is painted bold
+ // we set the font attributes always before drawing to be re-entrant (DrawNativeControl may trigger additional paints)
+ vcl::Font aFont(rRenderContext.GetFont());
+ aFont.SetTransparent(true);
+ rRenderContext.SetFont(aFont);
+
+ Size aTabSize = aRect.GetSize();
+ Size aImageSize(0, 0);
+ long nTextHeight = rRenderContext.GetTextHeight();
+ long nTextWidth = rRenderContext.GetCtrlTextWidth(pItem->maFormatText);
+ if (!!pItem->maTabImage)
+ {
+ aImageSize = pItem->maTabImage.GetSizePixel();
+ if (!pItem->maFormatText.isEmpty())
+ aImageSize.AdjustWidth(GetTextHeight() / 4 );
+ }
+ long nXPos = aRect.Left() + ((aTabSize.Width() - nTextWidth - aImageSize.Width()) / 2) - nOff - nOff3;
+ 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.get() != nullptr || !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 = rRenderContext.IsNativeControlSupported(ControlType::TabPane, ControlPart::TabPaneWithHeader);
+ tools::Rectangle aHeaderRect(aRect.Left(), 0, aRect.Right(), aRect.Top());
+ if (bPaneWithHeader)
+ {
+ aRect.SetTop(0);
+ if (mpTabCtrlData->maItemList.size())
+ {
+ long nRight = 0;
+ for (const auto &item : mpTabCtrlData->maItemList)
+ if (item.m_bVisible)
+ nRight = item.maRect.Right();
+ assert(nRight);
+ aHeaderRect.SetRight(nRight);
+ }
+ }
+ const TabPaneValue aTabPaneValue(aHeaderRect, pCurItem ? pCurItem->maRect : tools::Rectangle());
+
+ ControlState nState = ControlState::ENABLED;
+ if (!IsEnabled())
+ nState &= ~ControlState::ENABLED;
+ if (HasFocus())
+ nState |= ControlState::FOCUSED;
+
+ if (lcl_canPaint(rRenderContext, rRect, aRect))
+ rRenderContext.DrawNativeControl(ControlType::TabPane, ControlPart::Entire,
+ aRect, nState, aTabPaneValue, OUString());
+
+ if (!bPaneWithHeader && rRenderContext.IsNativeControlSupported(ControlType::TabHeader, ControlPart::Entire)
+ && lcl_canPaint(rRenderContext, rRect, aHeaderRect))
+ rRenderContext.DrawNativeControl(ControlType::TabHeader, ControlPart::Entire,
+ aHeaderRect, nState, aTabPaneValue, OUString());
+ }
+ else
+ {
+ long nTopOff = 1;
+ if (!(rStyleSettings.GetOptions() & StyleSettingsOptions::Mono))
+ rRenderContext.SetLineColor(rStyleSettings.GetLightColor());
+ else
+ rRenderContext.SetLineColor(COL_BLACK);
+ if (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 (!mpTabCtrlData->maItemList.empty() && mpTabCtrlData->mpListBox == nullptr)
+ {
+ // Some native toolkits (GTK+) draw tabs right-to-left, with an
+ // overlap between adjacent tabs
+ bool bDrawTabsRTL = rRenderContext.IsNativeControlSupported(ControlType::TabItem, ControlPart::TabsDrawRtl);
+ ImplTabItem* pFirstTab = nullptr;
+ ImplTabItem* pLastTab = nullptr;
+ size_t idx;
+
+ // Even though there is a tab overlap with GTK+, the first tab is not
+ // overlapped on the left side. Other toolkits ignore this option.
+ if (bDrawTabsRTL)
+ {
+ pFirstTab = mpTabCtrlData->maItemList.data();
+ pLastTab = pFirstTab + mpTabCtrlData->maItemList.size() - 1;
+ idx = mpTabCtrlData->maItemList.size() - 1;
+ }
+ else
+ {
+ pLastTab = mpTabCtrlData->maItemList.data();
+ pFirstTab = pLastTab + mpTabCtrlData->maItemList.size() - 1;
+ idx = 0;
+ }
+
+ while (idx < mpTabCtrlData->maItemList.size())
+ {
+ ImplTabItem* pItem = &mpTabCtrlData->maItemList[idx];
+
+ if (pItem != pCurItem && pItem->m_bVisible && lcl_canPaint(rRenderContext, rRect, pItem->maRect))
+ ImplDrawItem(rRenderContext, pItem, aCurRect, pItem == pFirstTab, pItem == pLastTab);
+
+ if (bDrawTabsRTL)
+ idx--;
+ else
+ idx++;
+ }
+
+ if (pCurItem && lcl_canPaint(rRenderContext, rRect, pCurItem->maRect))
+ ImplDrawItem(rRenderContext, pCurItem, aCurRect, pCurItem == pFirstTab, pCurItem == pLastTab);
+ }
+
+ if (HasFocus())
+ ImplShowFocus();
+
+ mbSmallInvalidate = true;
+}
+
+void TabControl::setAllocation(const Size &rAllocation)
+{
+ ImplFreeLayoutData();
+
+ if ( !IsReallyShown() )
+ return;
+
+ if( mpTabCtrlData->mpListBox )
+ {
+ // get the listbox' preferred size
+ Size aTabCtrlSize( GetSizePixel() );
+ 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;
+ 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 )
+ {
+ ImplShowFocus();
+ SetInputContext( InputContext( GetFont() ) );
+ }
+ 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;
+ // show tooltip if not text but image is set and helptext is available
+ if ( !rHelpText.isEmpty() && pItem->maText.isEmpty() && !!pItem->maTabImage )
+ {
+ tools::Rectangle aItemRect = ImplGetTabRect( GetPagePos( nItemId ) );
+ Point aPt = OutputToScreenPixel( aItemRect.TopLeft() );
+ aItemRect.SetLeft( aPt.X() );
+ aItemRect.SetTop( aPt.Y() );
+ aPt = OutputToScreenPixel( aItemRect.BottomRight() );
+ aItemRect.SetRight( aPt.X() );
+ aItemRect.SetBottom( aPt.Y() );
+ Help::ShowQuickHelp( this, aItemRect, rHelpText );
+ return;
+ }
+ }
+ }
+
+ Control::RequestHelp( rHEvt );
+}
+
+void TabControl::Command( const CommandEvent& rCEvt )
+{
+ if( (mpTabCtrlData->mpListBox == nullptr) && (rCEvt.GetCommand() == CommandEventId::ContextMenu) && (GetPageCount() > 1) )
+ {
+ Point aMenuPos;
+ bool bMenu;
+ if ( rCEvt.IsMouseEvent() )
+ {
+ aMenuPos = rCEvt.GetMousePosPixel();
+ bMenu = GetPageId( aMenuPos ) != 0;
+ }
+ else
+ {
+ aMenuPos = ImplGetTabRect( GetPagePos( mnCurPageId ) ).Center();
+ bMenu = true;
+ }
+
+ if ( bMenu )
+ {
+ ScopedVclPtrInstance<PopupMenu> aMenu;
+ for (auto const& item : mpTabCtrlData->maItemList)
+ {
+ aMenu->InsertItem(item.id(), item.maText, MenuItemBits::CHECKABLE | MenuItemBits::RADIOCHECK);
+ if (item.id() == mnCurPageId)
+ aMenu->CheckItem(item.id());
+ aMenu->SetHelpId(item.id(), OString());
+ }
+
+ sal_uInt16 nId = aMenu->Execute( this, aMenuPos );
+ if ( nId && (nId != mnCurPageId) )
+ SelectTabPage( nId );
+ return;
+ }
+ }
+
+ Control::Command( rCEvt );
+}
+
+void TabControl::StateChanged( StateChangedType nType )
+{
+ Control::StateChanged( nType );
+
+ if ( nType == StateChangedType::InitShow )
+ {
+ ImplPosCurTabPage();
+ if( mpTabCtrlData->mpListBox )
+ Resize();
+ }
+ else if ( nType == StateChangedType::UpdateMode )
+ {
+ if ( IsUpdateMode() )
+ Invalidate();
+ }
+ else if ( (nType == StateChangedType::Zoom) ||
+ (nType == StateChangedType::ControlFont) )
+ {
+ ImplInitSettings( false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ ImplInitSettings( false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ImplInitSettings( true );
+ Invalidate();
+ }
+}
+
+void TabControl::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Control::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
+ {
+ ImplInitSettings( true );
+ Invalidate();
+ }
+}
+
+ImplTabItem* TabControl::ImplGetItem(const Point& rPt) const
+{
+ ImplTabItem* pFoundItem = nullptr;
+ int nFound = 0;
+ for (auto & item : mpTabCtrlData->maItemList)
+ {
+ if (item.m_bVisible && item.maRect.IsInside(rPt))
+ {
+ nFound++;
+ pFoundItem = &item;
+ }
+ }
+
+ // assure that only one tab is highlighted at a time
+ assert(nFound <= 1);
+ return nFound == 1 ? pFoundItem : nullptr;
+}
+
+bool TabControl::PreNotify( NotifyEvent& rNEvt )
+{
+ if( rNEvt.GetType() == MouseNotifyEvent::MOUSEMOVE )
+ {
+ const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent();
+ if( pMouseEvt && !pMouseEvt->GetButtons() && !pMouseEvt->IsSynthetic() && !pMouseEvt->IsModifierChanged() )
+ {
+ // trigger redraw if mouse over state has changed
+ if( IsNativeControlSupported(ControlType::TabItem, ControlPart::Entire) )
+ {
+ ImplTabItem *pItem = ImplGetItem(GetPointerPosPixel());
+ ImplTabItem *pLastItem = ImplGetItem(GetLastPointerPosPixel());
+ if ((pItem != pLastItem) || pMouseEvt->IsLeaveWindow() || pMouseEvt->IsEnterWindow())
+ {
+ vcl::Region aClipRgn;
+ if (pLastItem)
+ {
+ // allow for slightly bigger tabitems
+ // as used by gtk
+ // TODO: query for the correct sizes
+ tools::Rectangle aRect(pLastItem->maRect);
+ aRect.AdjustLeft( -2 );
+ aRect.AdjustRight(2 );
+ aRect.AdjustTop( -3 );
+ aClipRgn.Union( aRect );
+ }
+
+ if (pItem)
+ {
+ // allow for slightly bigger tabitems
+ // as used by gtk
+ // TODO: query for the correct sizes
+ tools::Rectangle aRect(pItem->maRect);
+ aRect.AdjustLeft( -2 );
+ aRect.AdjustRight(2 );
+ aRect.AdjustTop( -3 );
+ aClipRgn.Union( aRect );
+ }
+
+ if( !aClipRgn.IsEmpty() )
+ Invalidate( aClipRgn );
+ }
+ }
+ }
+ }
+
+ return Control::PreNotify(rNEvt);
+}
+
+bool TabControl::EventNotify( NotifyEvent& rNEvt )
+{
+ bool bRet = false;
+
+ if ( rNEvt.GetType() == MouseNotifyEvent::KEYINPUT )
+ bRet = ImplHandleKeyEvent( *rNEvt.GetKeyEvent() );
+
+ return bRet || Control::EventNotify( rNEvt );
+}
+
+void TabControl::ActivatePage()
+{
+ maActivateHdl.Call( this );
+}
+
+bool TabControl::DeactivatePage()
+{
+ return !maDeactivateHdl.IsSet() || maDeactivateHdl.Call( this );
+}
+
+void TabControl::SetTabPageSizePixel( const Size& rSize )
+{
+ ImplFreeLayoutData();
+
+ Size aNewSize( rSize );
+ aNewSize.AdjustWidth(TAB_OFFSET*2 );
+ tools::Rectangle aRect = ImplGetTabRect( TAB_PAGERECT,
+ aNewSize.Width(), aNewSize.Height() );
+ aNewSize.AdjustHeight(aRect.Top()+TAB_OFFSET );
+ Window::SetOutputSizePixel( aNewSize );
+}
+
+void TabControl::InsertPage( sal_uInt16 nPageId, const OUString& rText,
+ sal_uInt16 nPos )
+{
+ SAL_WARN_IF( !nPageId, "vcl", "TabControl::InsertPage(): PageId == 0" );
+ SAL_WARN_IF( GetPagePos( nPageId ) != TAB_PAGE_NOTFOUND, "vcl",
+ "TabControl::InsertPage(): PageId already exists" );
+
+ // insert new page item
+ ImplTabItem* pItem = nullptr;
+ if( nPos == TAB_APPEND || size_t(nPos) >= mpTabCtrlData->maItemList.size() )
+ {
+ mpTabCtrlData->maItemList.emplace_back(nPageId);
+ pItem = &mpTabCtrlData->maItemList.back();
+ if( mpTabCtrlData->mpListBox )
+ mpTabCtrlData->mpListBox->InsertEntry( rText );
+ }
+ else
+ {
+ std::vector< ImplTabItem >::iterator new_it =
+ mpTabCtrlData->maItemList.emplace(mpTabCtrlData->maItemList.begin() + nPos, nPageId);
+ pItem = &(*new_it);
+ if( mpTabCtrlData->mpListBox )
+ mpTabCtrlData->mpListBox->InsertEntry( rText, nPos);
+ }
+ if( mpTabCtrlData->mpListBox )
+ {
+ if( ! mnCurPageId )
+ mpTabCtrlData->mpListBox->SelectEntryPos( 0 );
+ mpTabCtrlData->mpListBox->SetDropDownLineCount( mpTabCtrlData->mpListBox->GetEntryCount() );
+ }
+
+ // set current page id
+ if ( !mnCurPageId )
+ mnCurPageId = nPageId;
+
+ // init new page item
+ pItem->maText = rText;
+ pItem->mbFullVisible = false;
+
+ mbFormat = true;
+ if ( IsUpdateMode() )
+ Invalidate();
+
+ ImplFreeLayoutData();
+ if( mpTabCtrlData->mpListBox ) // reposition/resize listbox
+ Resize();
+
+ CallEventListeners( VclEventId::TabpageInserted, reinterpret_cast<void*>(nPageId) );
+}
+
+void TabControl::RemovePage( sal_uInt16 nPageId )
+{
+ sal_uInt16 nPos = GetPagePos( nPageId );
+
+ // does the item exist ?
+ if ( nPos != TAB_PAGE_NOTFOUND )
+ {
+ //remove page item
+ std::vector< ImplTabItem >::iterator it = mpTabCtrlData->maItemList.begin() + nPos;
+ bool bIsCurrentPage = (it->id() == mnCurPageId);
+ mpTabCtrlData->maItemList.erase( it );
+ if( mpTabCtrlData->mpListBox )
+ {
+ mpTabCtrlData->mpListBox->RemoveEntry( nPos );
+ mpTabCtrlData->mpListBox->SetDropDownLineCount( mpTabCtrlData->mpListBox->GetEntryCount() );
+ }
+
+ // If current page is removed, then first page gets the current page
+ if ( bIsCurrentPage )
+ {
+ mnCurPageId = 0;
+
+ if( ! mpTabCtrlData->maItemList.empty() )
+ {
+ // don't do this by simply setting mnCurPageId to pFirstItem->id()
+ // this leaves a lot of stuff (such trivia as _showing_ the new current page) undone
+ // instead, call SetCurPageId
+ // without this, the next (outside) call to SetCurPageId with the id of the first page
+ // will result in doing nothing (as we assume that nothing changed, then), and the page
+ // will never be shown.
+ // 86875 - 05/11/2001 - frank.schoenheit@germany.sun.com
+
+ SetCurPageId(mpTabCtrlData->maItemList[0].id());
+ }
+ }
+
+ mbFormat = true;
+ if ( IsUpdateMode() )
+ Invalidate();
+
+ ImplFreeLayoutData();
+
+ CallEventListeners( VclEventId::TabpageRemoved, reinterpret_cast<void*>(nPageId) );
+ }
+}
+
+void TabControl::SetPageEnabled( sal_uInt16 i_nPageId, bool i_bEnable )
+{
+ ImplTabItem* pItem = ImplGetItem( i_nPageId );
+
+ if (pItem && pItem->m_bEnabled != i_bEnable)
+ {
+ 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
+{
+ const auto &rList = mpTabCtrlData->maItemList;
+ const auto it = std::find_if(rList.begin(), rList.end(), [&rPos, this](const auto &item) {
+ return const_cast<TabControl*>(this)->ImplGetTabRect(&item).IsInside(rPos); });
+ return (it != rList.end()) ? it->id() : 0;
+}
+
+sal_uInt16 TabControl::GetPageId( const OString& rName ) const
+{
+ const auto &rList = mpTabCtrlData->maItemList;
+ const auto it = std::find_if(rList.begin(), rList.end(), [&rName](const auto &item) {
+ return item.maTabName == rName; });
+ return (it != rList.end()) ? it->id() : 0;
+}
+
+void TabControl::SetCurPageId( sal_uInt16 nPageId )
+{
+ sal_uInt16 nPos = GetPagePos( nPageId );
+ while (nPos != TAB_PAGE_NOTFOUND && !mpTabCtrlData->maItemList[nPos].m_bEnabled)
+ {
+ nPos++;
+ if( size_t(nPos) >= mpTabCtrlData->maItemList.size() )
+ nPos = 0;
+ if (mpTabCtrlData->maItemList[nPos].id() == nPageId)
+ break;
+ }
+
+ if( nPos != TAB_PAGE_NOTFOUND )
+ {
+ 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) )
+ {
+ ImplFreeLayoutData();
+
+ CallEventListeners( VclEventId::TabpageDeactivate, reinterpret_cast<void*>(mnCurPageId) );
+ if ( DeactivatePage() )
+ {
+ mnActPageId = nPageId;
+ ActivatePage();
+ // Page could have been switched by the Activate handler
+ nPageId = mnActPageId;
+ mnActPageId = 0;
+ SetCurPageId( nPageId );
+ if( mpTabCtrlData->mpListBox )
+ mpTabCtrlData->mpListBox->SelectEntryPos( GetPagePos( nPageId ) );
+ CallEventListeners( VclEventId::TabpageActivate, reinterpret_cast<void*>(nPageId) );
+ }
+ }
+}
+
+void TabControl::SetTabPage( sal_uInt16 nPageId, TabPage* pTabPage )
+{
+ ImplTabItem* pItem = ImplGetItem( nPageId );
+
+ if ( pItem && (pItem->mpTabPage.get() != pTabPage) )
+ {
+ 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 )
+ {
+ pItem->maText = rText;
+ mbFormat = true;
+ if( mpTabCtrlData->mpListBox )
+ {
+ sal_uInt16 nPos = GetPagePos( nPageId );
+ mpTabCtrlData->mpListBox->RemoveEntry( nPos );
+ mpTabCtrlData->mpListBox->InsertEntry( rText, nPos );
+ }
+ if ( IsUpdateMode() )
+ Invalidate();
+ ImplFreeLayoutData();
+ CallEventListeners( VclEventId::TabpagePageTextChanged, reinterpret_cast<void*>(nPageId) );
+ }
+}
+
+OUString const & TabControl::GetPageText( sal_uInt16 nPageId ) const
+{
+ ImplTabItem* pItem = ImplGetItem( nPageId );
+
+ assert( pItem );
+
+ return pItem->maText;
+}
+
+void TabControl::SetHelpText( sal_uInt16 nPageId, const OUString& rText )
+{
+ ImplTabItem* pItem = ImplGetItem( nPageId );
+
+ assert( pItem );
+
+ pItem->maHelpText = rText;
+}
+
+const OUString& TabControl::GetHelpText( sal_uInt16 nPageId ) const
+{
+ ImplTabItem* pItem = ImplGetItem( nPageId );
+ assert( pItem );
+ return pItem->maHelpText;
+}
+
+void TabControl::SetPageName( sal_uInt16 nPageId, const OString& rName ) const
+{
+ ImplTabItem* pItem = ImplGetItem( nPageId );
+
+ if ( pItem )
+ pItem->maTabName = rName;
+}
+
+OString TabControl::GetPageName( sal_uInt16 nPageId ) const
+{
+ ImplTabItem* pItem = ImplGetItem( nPageId );
+
+ if (pItem)
+ return pItem->maTabName;
+
+ return OString();
+}
+
+void TabControl::SetPageImage( sal_uInt16 i_nPageId, const Image& i_rImage )
+{
+ ImplTabItem* pItem = ImplGetItem( i_nPageId );
+
+ if ( pItem )
+ {
+ pItem->maTabImage = i_rImage;
+ mbFormat = true;
+ if ( IsUpdateMode() )
+ Invalidate();
+ }
+}
+
+tools::Rectangle TabControl::GetCharacterBounds( sal_uInt16 nPageId, long nIndex ) const
+{
+ tools::Rectangle aRet;
+
+ if( !HasLayoutData() || mpTabCtrlData->maLayoutPageIdToLine.empty() )
+ FillLayoutData();
+
+ if( HasLayoutData() )
+ {
+ std::unordered_map< int, int >::const_iterator it = mpTabCtrlData->maLayoutPageIdToLine.find( static_cast<int>(nPageId) );
+ if( it != mpTabCtrlData->maLayoutPageIdToLine.end() )
+ {
+ Pair aPair = mpControlData->mpLayoutData->GetLineStartEnd( it->second );
+ if( (aPair.B() - aPair.A()) >= nIndex )
+ aRet = mpControlData->mpLayoutData->GetCharacterBounds( aPair.A() + nIndex );
+ }
+ }
+
+ return aRet;
+}
+
+long TabControl::GetIndexForPoint( const Point& rPoint, sal_uInt16& rPageId ) const
+{
+ long nRet = -1;
+
+ if( !HasLayoutData() || mpTabCtrlData->maLayoutPageIdToLine.empty() )
+ FillLayoutData();
+
+ if( HasLayoutData() )
+ {
+ int nIndex = mpControlData->mpLayoutData->GetIndexForPoint( rPoint );
+ if( nIndex != -1 )
+ {
+ // what line (->pageid) is this index in ?
+ int nLines = mpControlData->mpLayoutData->GetLineCount();
+ int nLine = -1;
+ while( ++nLine < nLines )
+ {
+ Pair aPair = mpControlData->mpLayoutData->GetLineStartEnd( nLine );
+ if( aPair.A() <= nIndex && aPair.B() >= nIndex )
+ {
+ nRet = nIndex - aPair.A();
+ rPageId = static_cast<sal_uInt16>(mpTabCtrlData->maLayoutLineToPageId[ nLine ]);
+ break;
+ }
+ }
+ }
+ }
+
+ return nRet;
+}
+
+void TabControl::FillLayoutData() const
+{
+ mpTabCtrlData->maLayoutLineToPageId.clear();
+ mpTabCtrlData->maLayoutPageIdToLine.clear();
+ const_cast<TabControl*>(this)->Invalidate();
+}
+
+tools::Rectangle TabControl::GetTabBounds( sal_uInt16 nPageId ) const
+{
+ tools::Rectangle aRet;
+
+ ImplTabItem* pItem = ImplGetItem( nPageId );
+ if (pItem && pItem->m_bVisible)
+ aRet = pItem->maRect;
+
+ return aRet;
+}
+
+void TabControl::SetItemsOffset( const Point& rOffs )
+{
+ if( mpTabCtrlData )
+ mpTabCtrlData->maItemsOffset = rOffs;
+}
+
+Point TabControl::GetItemsOffset() const
+{
+ if( mpTabCtrlData )
+ return mpTabCtrlData->maItemsOffset;
+ else
+ return Point();
+}
+
+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();
+ }
+
+ long nTabLabelsBottom = 0, nTabLabelsRight = 0;
+ 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;
+}
+
+FactoryFunction TabControl::GetUITestFactory() const
+{
+ return TabControlUIObject::create;
+}
+
+boost::property_tree::ptree TabControl::DumpAsPropertyTree()
+{
+ boost::property_tree::ptree aTree = Control::DumpAsPropertyTree();
+
+ boost::property_tree::ptree aTabs;
+ for(auto id : GetPageIDs())
+ {
+ boost::property_tree::ptree aTab;
+ aTab.put("text", GetPageText(id));
+ aTab.put("id", id);
+ aTabs.push_back(std::make_pair("", aTab));
+ }
+
+ aTree.add_child("tabs", aTabs);
+
+ return aTree;
+}
+
+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)
+ {
+ bool bHandled = false;
+
+ for (int nChild = 0; nChild < GetPageCount(); ++nChild)
+ {
+ sal_uInt16 nPageId = TabControl::GetPageId(nChild);
+ TabPage* pPage = GetTabPage(nPageId);
+
+ if (pPage)
+ {
+ SetPageVisible(nPageId, pPage->HasContext(eContext) || pPage->HasContext(vcl::EnumContext::Context::Any));
+
+ if (!bHandled && bLastContextWasSupported
+ && pPage->HasContext(vcl::EnumContext::Context::Default))
+ {
+ SetCurPageId(nPageId);
+ }
+
+ if (pPage->HasContext(eContext) && eContext != vcl::EnumContext::Context::Any)
+ {
+ SetCurPageId(nPageId);
+ bHandled = true;
+ bLastContextWasSupported = true;
+ }
+ }
+ }
+
+ if (!bHandled)
+ bLastContextWasSupported = false;
+ eLastContext = eContext;
+ }
+}
+
+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( long nWidth )
+{
+ if ( nWidth <= 0 )
+ return false;
+ if ( mpTabCtrlData->maItemList.empty() )
+ return false;
+ if (!m_pOpenMenu || m_pOpenMenu->isDisposed())
+ return false;
+
+ const long nHamburgerWidth = m_pOpenMenu->GetSizePixel().Width();
+ long nMaxWidth = nWidth - nHamburgerWidth;
+ long nShortcutsWidth = m_pShortcuts != nullptr ? m_pShortcuts->GetSizePixel().getWidth() + 1 : 0;
+ long nFullWidth = nShortcutsWidth;
+
+ const long nOffsetX = 2 + GetItemsOffset().X() + nShortcutsWidth;
+ const long nOffsetY = 2 + GetItemsOffset().Y();
+
+ //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)
+ {
+ long nTabWidth = 0;
+ if (item.m_bVisible)
+ {
+ nTabWidth = ImplGetItemSize(&item, nMaxWidth).getWidth();
+ if (!item.maText.isEmpty() && nTabWidth < 100)
+ nTabWidth = 100;
+ }
+ nFullWidth += nTabWidth;
+ }
+
+ nMaxWidth -= GetItemsOffset().X();
+
+ long nX = nOffsetX;
+ long nY = nOffsetY;
+
+ 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)
+ {
+ long nPosY = (m_nHeaderHeight - m_pShortcuts->GetSizePixel().getHeight()) / 2;
+ m_pShortcuts->SetPosPixel(Point(0, nPosY));
+ }
+
+ 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);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/throbber.cxx b/vcl/source/control/throbber.cxx
new file mode 100644
index 000000000..850edf27b
--- /dev/null
+++ b/vcl/source/control/throbber.cxx
@@ -0,0 +1,232 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <vcl/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.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;
+ aImageSets.push_back( lcl_loadImageSet( ImageSet::N16px ) );
+ aImageSets.push_back( lcl_loadImageSet( ImageSet::N32px ) );
+ aImageSets.push_back( lcl_loadImageSet( ImageSet::N64px ) );
+
+ // find the best matching image set (size-wise)
+ const ::Size aWindowSizePixel = GetSizePixel();
+ size_t nPreferredSet = 0;
+ if ( aImageSets.size() > 1 )
+ {
+ long nMinimalDistance = ::std::numeric_limits< 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( aImageSets[nPreferredSet] );
+ }
+ catch( const Exception& )
+ {
+ }
+}
+
+void Throbber::start()
+{
+ maWaitTimer.Start();
+}
+
+void Throbber::stop()
+{
+ maWaitTimer.Stop();
+}
+
+bool Throbber::isRunning() const
+{
+ return maWaitTimer.IsActive();
+}
+
+void Throbber::setImageList( ::std::vector< Image > const& i_images )
+{
+ SAL_WARN_IF( i_images.size()>=SAL_MAX_INT32, "vcl.control", "Throbber::setImageList: too many images!" );
+
+ maImageList = i_images;
+
+ const Image aInitialImage( !maImageList.empty() ? maImageList[ 0 ] : Image() );
+ SetImage( aInitialImage );
+}
+
+::std::vector< OUString > Throbber::getDefaultImageURLs( const ImageSet i_imageSet )
+{
+ ::std::vector< OUString > aImageURLs;
+
+ char const* const pResolutions[] = { "16", "32", "64" };
+ size_t const nImageCounts[] = { 6, 12, 12 };
+
+ size_t index = 0;
+ switch ( i_imageSet )
+ {
+ case ImageSet::N16px: index = 0; break;
+ case ImageSet::N32px: index = 1; break;
+ case ImageSet::N64px: index = 2; break;
+ }
+
+ aImageURLs.reserve( nImageCounts[index] );
+ for ( size_t i=0; i<nImageCounts[index]; ++i )
+ {
+ OUStringBuffer aURL;
+ aURL.append( "private:graphicrepository/vcl/res/spinner-" );
+ aURL.appendAscii( pResolutions[index] );
+ aURL.append( "-" );
+ if ( i < 9 )
+ aURL.append( "0" );
+ aURL.append ( sal_Int32( i + 1 ) );
+ aURL.append( ".png" );
+
+ aImageURLs.push_back( aURL.makeStringAndClear() );
+ }
+
+ return aImageURLs;
+}
+
+IMPL_LINK_NOARG(Throbber, TimeOutHdl, Timer *, void)
+{
+ SolarMutexGuard aGuard;
+ if ( maImageList.empty() )
+ return;
+
+ if ( mnCurStep < static_cast<sal_Int32>(maImageList.size()-1) )
+ ++mnCurStep;
+ else
+ {
+ if ( mbRepeat )
+ {
+ // start over
+ mnCurStep = 0;
+ }
+ else
+ {
+ stop();
+ }
+ }
+
+ SetImage( maImageList[ mnCurStep ] );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/thumbpos.hxx b/vcl/source/control/thumbpos.hxx
new file mode 100644
index 000000000..1992bd853
--- /dev/null
+++ b/vcl/source/control/thumbpos.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/.
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_CONTROL_THUMBPOS_HXX
+#define INCLUDED_VCL_SOURCE_CONTROL_THUMBPOS_HXX
+
+inline long ImplMulDiv( long nNumber, long nNumerator, long nDenominator )
+{
+ if (!nDenominator)
+ return 0;
+ double n = (static_cast<double>(nNumber) * static_cast<double>(nNumerator)) / static_cast<double>(nDenominator);
+ return static_cast<long>(n);
+}
+
+#endif // INCLUDED_VCL_SOURCE_CONTROL_THUMBPOS_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/wizardmachine.cxx b/vcl/source/control/wizardmachine.cxx
new file mode 100644
index 000000000..6272e0073
--- /dev/null
+++ b/vcl/source/control/wizardmachine.cxx
@@ -0,0 +1,1496 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <comphelper/lok.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <vcl/event.hxx>
+#include <tools/debug.hxx>
+#include <tools/diagnose_ex.h>
+#include <strings.hrc>
+#include <svdata.hxx>
+#include <wizdlg.hxx>
+#include <stack>
+#include "wizimpldata.hxx"
+
+#define HID_WIZARD_NEXT "SVT_HID_WIZARD_NEXT"
+#define HID_WIZARD_PREVIOUS "SVT_HID_WIZARD_PREVIOUS"
+
+#define WIZARDDIALOG_BUTTON_OFFSET_Y 6
+#define WIZARDDIALOG_BUTTON_DLGOFFSET_X 6
+#define WIZARDDIALOG_VIEW_DLGOFFSET_X 6
+#define WIZARDDIALOG_VIEW_DLGOFFSET_Y 6
+
+namespace vcl
+{
+ //= WizardPageImplData
+ OWizardPage::OWizardPage(weld::Container* pPage, weld::DialogController* pController, const OUString& rUIXMLDescription, const OString& rID)
+ : BuilderPage(pPage, pController, rUIXMLDescription, rID)
+ {
+ }
+
+ OWizardPage::~OWizardPage()
+ {
+ }
+
+ void OWizardPage::initializePage()
+ {
+ }
+
+ void OWizardPage::Activate()
+ {
+ BuilderPage::Activate();
+ updateDialogTravelUI();
+ }
+
+ void OWizardPage::updateDialogTravelUI()
+ {
+ auto pWizardMachine = dynamic_cast<RoadmapWizardMachine*>(m_pDialogController);
+ if (pWizardMachine)
+ pWizardMachine->updateTravelUI();
+ }
+
+ bool OWizardPage::canAdvance() const
+ {
+ return true;
+ }
+
+ bool OWizardPage::commitPage( WizardTypes::CommitPageReason )
+ {
+ return true;
+ }
+
+ void RoadmapWizard::SetLeftAlignedButtonCount( sal_Int16 _nCount )
+ {
+ mnLeftAlignCount = _nCount;
+ }
+
+ void RoadmapWizard::ImplCalcSize( Size& rSize )
+ {
+ // calculate ButtonBar height and width
+ long nMaxHeight = 0;
+ 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();
+ long nBtnWidth = 0;
+ long nMaxHeight = 0;
+ long nOffY = aDlgSize.Height();
+
+ ImplWizButtonData* pBtnData = mpFirstBtn;
+ int j = 0;
+ while ( pBtnData )
+ {
+ if (j >= mnLeftAlignCount)
+ {
+ Size aBtnSize = pBtnData->mpButton->GetSizePixel();
+ long nBtnHeight = aBtnSize.Height();
+ if ( nBtnHeight > nMaxHeight )
+ nMaxHeight = nBtnHeight;
+ nBtnWidth += aBtnSize.Width();
+ nBtnWidth += pBtnData->mnOffset;
+ }
+ pBtnData = pBtnData->mpNext;
+ j++;
+ }
+
+ if ( nMaxHeight )
+ {
+ long nOffX = aDlgSize.Width()-nBtnWidth-WIZARDDIALOG_BUTTON_DLGOFFSET_X;
+ 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;
+
+ long nViewOffX = 0;
+ long nViewOffY = 0;
+ long nViewWidth = 0;
+ long nViewHeight = 0;
+ 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 );
+ }
+
+ long RoadmapWizard::LogicalCoordinateToPixel(int iCoordinate){
+ 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
+ long nMaxHeight = 0;
+ ImplWizButtonData* pBtnData = mpFirstBtn;
+ while ( pBtnData )
+ {
+ 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 );
+ long nOffX = 0;
+ long nOffY = 0;
+ if ( mpViewWindow && mpViewWindow->IsVisible() )
+ {
+ Size aViewSize = mpViewWindow->GetSizePixel();
+ // align left
+ long nViewOffset = mbEmptyViewMargin ? 0 : WIZARDDIALOG_VIEW_DLGOFFSET_X;
+ nOffX += aViewSize.Width() + nViewOffset;
+ aDlgSize.AdjustWidth( -nOffX );
+ }
+ Point aPos( nOffX, nOffY );
+ mpCurTabPage->SetPosSizePixel( aPos, aDlgSize );
+ }
+
+ void RoadmapWizard::ImplShowTabPage( TabPage* pTabPage )
+ {
+ if ( mpCurTabPage == pTabPage )
+ return;
+
+ TabPage* pOldTabPage = mpCurTabPage;
+
+ mpCurTabPage = pTabPage;
+ if ( pTabPage )
+ {
+ ImplPosTabPage();
+ pTabPage->Show();
+ }
+
+ if ( pOldTabPage )
+ pOldTabPage->Hide();
+ }
+
+ TabPage* RoadmapWizard::ImplGetPage( sal_uInt16 nLevel ) const
+ {
+ sal_uInt16 nTempLevel = 0;
+ ImplWizPageData* pPageData = mpFirstPage;
+ while ( pPageData )
+ {
+ if ( (nTempLevel == nLevel) || !pPageData->mpNext )
+ break;
+
+ nTempLevel++;
+ pPageData = pPageData->mpNext;
+ }
+
+ if ( pPageData )
+ return pPageData->mpPage;
+ return nullptr;
+ }
+
+ void RoadmapWizard::implConstruct( const WizardButtonFlags _nButtonFlags )
+ {
+ m_xWizardImpl->sTitleBase = GetText();
+
+ // create the buttons according to the wizard button flags
+ // the help button
+ if (_nButtonFlags & WizardButtonFlags::HELP)
+ {
+ m_pHelp= VclPtr<HelpButton>::Create(this, WB_TABSTOP);
+ m_pHelp->SetSizePixel(LogicToPixel(Size(50, 14), MapMode(MapUnit::MapAppFont)));
+ m_pHelp->Show();
+ AddButton( m_pHelp, WIZARDDIALOG_BUTTON_STDOFFSET_X);
+ }
+
+ // the previous button
+ if (_nButtonFlags & WizardButtonFlags::PREVIOUS)
+ {
+ m_pPrevPage = VclPtr<PushButton>::Create(this, WB_TABSTOP);
+ m_pPrevPage->SetHelpId( HID_WIZARD_PREVIOUS );
+ m_pPrevPage->SetSizePixel(LogicToPixel(Size(50, 14), MapMode(MapUnit::MapAppFont)));
+ m_pPrevPage->SetText(VclResId(STR_WIZDLG_PREVIOUS));
+ m_pPrevPage->Show();
+ m_pPrevPage->set_id("previous");
+
+ if (_nButtonFlags & WizardButtonFlags::NEXT)
+ AddButton( m_pPrevPage, ( WIZARDDIALOG_BUTTON_SMALLSTDOFFSET_X) ); // half x-offset to the next button
+ else
+ AddButton( m_pPrevPage, WIZARDDIALOG_BUTTON_STDOFFSET_X );
+ mpPrevBtn = m_pPrevPage;
+ m_pPrevPage->SetClickHdl( LINK( this, RoadmapWizard, OnPrevPage ) );
+ }
+
+ // the next button
+ if (_nButtonFlags & WizardButtonFlags::NEXT)
+ {
+ m_pNextPage = VclPtr<PushButton>::Create(this, WB_TABSTOP);
+ m_pNextPage->SetHelpId( HID_WIZARD_NEXT );
+ m_pNextPage->SetSizePixel(LogicToPixel(Size(50, 14), MapMode(MapUnit::MapAppFont)));
+ m_pNextPage->SetText(VclResId(STR_WIZDLG_NEXT));
+ m_pNextPage->Show();
+ m_pNextPage->set_id("next");
+
+ AddButton( m_pNextPage, WIZARDDIALOG_BUTTON_STDOFFSET_X );
+ mpNextBtn = m_pNextPage;
+ m_pNextPage->SetClickHdl( LINK( this, RoadmapWizard, OnNextPage ) );
+ }
+
+ // the finish button
+ if (_nButtonFlags & WizardButtonFlags::FINISH)
+ {
+ m_pFinish = VclPtr<OKButton>::Create(this, WB_TABSTOP);
+ m_pFinish->SetSizePixel(LogicToPixel(Size(50, 14), MapMode(MapUnit::MapAppFont)));
+ m_pFinish->SetText(VclResId(STR_WIZDLG_FINISH));
+ m_pFinish->Show();
+ m_pFinish->set_id("finish");
+
+ AddButton( m_pFinish, WIZARDDIALOG_BUTTON_STDOFFSET_X );
+ m_pFinish->SetClickHdl( LINK( this, RoadmapWizard, OnFinish ) );
+ }
+
+ // the cancel button
+ if (_nButtonFlags & WizardButtonFlags::CANCEL)
+ {
+ m_pCancel = VclPtr<CancelButton>::Create(this, WB_TABSTOP);
+ m_pCancel->SetSizePixel(LogicToPixel(Size(50, 14), MapMode(MapUnit::MapAppFont)));
+ m_pCancel->Show();
+
+ AddButton( m_pCancel, WIZARDDIALOG_BUTTON_STDOFFSET_X );
+ }
+ }
+
+ void RoadmapWizard::Resize()
+ {
+ if ( IsReallyShown() && !IsInInitShow() )
+ {
+ ImplPosCtrls();
+ ImplPosTabPage();
+ }
+
+ Dialog::Resize();
+ }
+
+ void RoadmapWizard::implUpdateTitle()
+ {
+ OUString sCompleteTitle(m_xWizardImpl->sTitleBase);
+
+ // append the page title
+ TabPage* pCurrentPage = GetPage(getCurrentState());
+ if ( pCurrentPage && !pCurrentPage->GetText().isEmpty() )
+ {
+ sCompleteTitle += " - " + pCurrentPage->GetText();
+ }
+
+ SetText(sCompleteTitle);
+ }
+
+ void RoadmapWizard::CalcAndSetSize()
+ {
+ Size aDlgSize = GetPageSizePixel();
+ if ( !aDlgSize.Width() || !aDlgSize.Height() )
+ {
+ ImplWizPageData* pPageData = mpFirstPage;
+ while ( pPageData )
+ {
+ if ( pPageData->mpPage )
+ {
+ Size aPageSize = pPageData->mpPage->GetSizePixel();
+ if ( aPageSize.Width() > aDlgSize.Width() )
+ aDlgSize.setWidth( aPageSize.Width() );
+ if ( aPageSize.Height() > aDlgSize.Height() )
+ aDlgSize.setHeight( aPageSize.Height() );
+ }
+
+ pPageData = pPageData->mpNext;
+ }
+ }
+ ImplCalcSize( aDlgSize );
+ SetMinOutputSizePixel( aDlgSize );
+ SetOutputSizePixel( aDlgSize );
+ }
+
+ void RoadmapWizard::StateChanged( StateChangedType nType )
+ {
+ if ( nType == StateChangedType::InitShow )
+ {
+ if ( IsDefaultSize() )
+ {
+ CalcAndSetSize();
+ }
+
+ ImplPosCtrls();
+ ImplPosTabPage();
+ ImplShowTabPage( ImplGetPage( mnCurLevel ) );
+ }
+
+ Dialog::StateChanged( nType );
+ }
+
+ bool RoadmapWizard::EventNotify( NotifyEvent& rNEvt )
+ {
+ if ( (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) && mpPrevBtn && mpNextBtn )
+ {
+ const KeyEvent* pKEvt = rNEvt.GetKeyEvent();
+ vcl::KeyCode aKeyCode = pKEvt->GetKeyCode();
+ sal_uInt16 nKeyCode = aKeyCode.GetCode();
+
+ if ( aKeyCode.IsMod1() )
+ {
+ if ( aKeyCode.IsShift() || (nKeyCode == KEY_PAGEUP) )
+ {
+ if ( (nKeyCode == KEY_TAB) || (nKeyCode == KEY_PAGEUP) )
+ {
+ if ( mpPrevBtn->IsVisible() &&
+ mpPrevBtn->IsEnabled() && mpPrevBtn->IsInputEnabled() )
+ {
+ mpPrevBtn->SetPressed( true );
+ mpPrevBtn->SetPressed( false );
+ mpPrevBtn->Click();
+ }
+ return true;
+ }
+ }
+ else
+ {
+ if ( (nKeyCode == KEY_TAB) || (nKeyCode == KEY_PAGEDOWN) )
+ {
+ if ( mpNextBtn->IsVisible() &&
+ mpNextBtn->IsEnabled() && mpNextBtn->IsInputEnabled() )
+ {
+ mpNextBtn->SetPressed( true );
+ mpNextBtn->SetPressed( false );
+ mpNextBtn->Click();
+ }
+ return true;
+ }
+ }
+ }
+ }
+
+ return Dialog::EventNotify( rNEvt );
+ }
+
+ TabPage* RoadmapWizard::GetOrCreatePage( const WizardTypes::WizardState i_nState )
+ {
+ if ( nullptr == GetPage( i_nState ) )
+ {
+ 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 );
+ }
+ return GetPage( i_nState );
+ }
+
+ 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;
+ }
+
+ bool RoadmapWizard::Finish( long nResult )
+ {
+ if ( IsInExecute() )
+ EndDialog( nResult );
+ else if ( GetStyle() & WB_CLOSEABLE )
+ Close();
+ return true;
+ }
+
+ void RoadmapWizard::AddPage( TabPage* pPage )
+ {
+ ImplWizPageData* pNewPageData = new ImplWizPageData;
+ pNewPageData->mpNext = nullptr;
+ pNewPageData->mpPage = pPage;
+
+ if ( !mpFirstPage )
+ mpFirstPage = pNewPageData;
+ else
+ {
+ ImplWizPageData* pPageData = mpFirstPage;
+ while ( pPageData->mpNext )
+ pPageData = pPageData->mpNext;
+ pPageData->mpNext = pNewPageData;
+ }
+ }
+
+ void RoadmapWizard::RemovePage( TabPage* pPage )
+ {
+ ImplWizPageData* pPrevPageData = nullptr;
+ ImplWizPageData* pPageData = mpFirstPage;
+ while ( pPageData )
+ {
+ if ( pPageData->mpPage == pPage )
+ {
+ if ( pPrevPageData )
+ pPrevPageData->mpNext = pPageData->mpNext;
+ else
+ mpFirstPage = pPageData->mpNext;
+ if ( pPage == mpCurTabPage )
+ mpCurTabPage = nullptr;
+ delete pPageData;
+ return;
+ }
+
+ pPrevPageData = pPageData;
+ pPageData = pPageData->mpNext;
+ }
+
+ OSL_FAIL( "RoadmapWizard::RemovePage() - Page not in list" );
+ }
+
+ void RoadmapWizard::SetPage( sal_uInt16 nLevel, TabPage* pPage )
+ {
+ sal_uInt16 nTempLevel = 0;
+ ImplWizPageData* pPageData = mpFirstPage;
+ while ( pPageData )
+ {
+ if ( (nTempLevel == nLevel) || !pPageData->mpNext )
+ break;
+
+ nTempLevel++;
+ pPageData = pPageData->mpNext;
+ }
+
+ if ( pPageData )
+ {
+ if ( pPageData->mpPage == mpCurTabPage )
+ mpCurTabPage = nullptr;
+ pPageData->mpPage = pPage;
+ }
+ }
+
+ TabPage* RoadmapWizard::GetPage( sal_uInt16 nLevel ) const
+ {
+ sal_uInt16 nTempLevel = 0;
+
+ for (ImplWizPageData* pPageData = mpFirstPage; pPageData;
+ pPageData = pPageData->mpNext)
+ {
+ if ( nTempLevel == nLevel )
+ return pPageData->mpPage;
+ nTempLevel++;
+ }
+
+ return nullptr;
+ }
+
+ void RoadmapWizard::AddButton( Button* pButton, 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" );
+ }
+
+ void RoadmapWizard::enableButtons(WizardButtonFlags _nWizardButtonFlags, bool _bEnable)
+ {
+ if (m_pFinish && (_nWizardButtonFlags & WizardButtonFlags::FINISH))
+ m_pFinish->Enable(_bEnable);
+ if (m_pNextPage && (_nWizardButtonFlags & WizardButtonFlags::NEXT))
+ m_pNextPage->Enable(_bEnable);
+ if (m_pPrevPage && (_nWizardButtonFlags & WizardButtonFlags::PREVIOUS))
+ m_pPrevPage->Enable(_bEnable);
+ if (m_pHelp && (_nWizardButtonFlags & WizardButtonFlags::HELP))
+ m_pHelp->Enable(_bEnable);
+ if (m_pCancel && (_nWizardButtonFlags & WizardButtonFlags::CANCEL))
+ m_pCancel->Enable(_bEnable);
+ }
+
+ IMPL_LINK_NOARG(RoadmapWizard, OnFinish, Button*, void)
+ {
+ if ( isTravelingSuspended() )
+ return;
+ RoadmapWizardTravelSuspension aTravelGuard( *this );
+ if (!prepareLeaveCurrentState(WizardTypes::eFinish))
+ {
+ return;
+ }
+ Finish( RET_OK );
+ }
+
+ bool RoadmapWizard::prepareLeaveCurrentState( WizardTypes::CommitPageReason _eReason )
+ {
+ IWizardPageController* pController = getPageController( GetPage( getCurrentState() ) );
+ ENSURE_OR_RETURN( pController != nullptr, "RoadmapWizard::prepareLeaveCurrentState: no controller for the current page!", true );
+ return pController->commitPage( _eReason );
+ }
+
+ bool RoadmapWizard::skipBackwardUntil( WizardTypes::WizardState _nTargetState )
+ {
+ // allowed to leave the current page?
+ if (!prepareLeaveCurrentState(WizardTypes::eTravelBackward))
+ return false;
+
+ // 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();
+
+ // allowed to leave the current page?
+ if ( !prepareLeaveCurrentState( nCurrentState < _nTargetState ? WizardTypes::eTravelForward : WizardTypes::eTravelBackward ) )
+ return false;
+
+ // 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;
+ }
+
+ bool RoadmapWizard::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_xWizardImpl->aStateHistory.push(nCurrentState);
+ if (!ShowPage(nNextState))
+ {
+ m_xWizardImpl->aStateHistory.pop();
+ return false;
+ }
+
+ return true;
+ }
+
+ bool RoadmapWizard::travelPrevious()
+ {
+ DBG_ASSERT(!m_xWizardImpl->aStateHistory.empty(), "RoadmapWizard::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_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);
+ return false;
+ }
+
+ // all fine
+ return true;
+ }
+
+ 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();
+ }
+ }
+
+ bool RoadmapWizard::isAutomaticNextButtonStateEnabled() const
+ {
+ return m_xWizardImpl->m_bAutoNextButtonState;
+ }
+
+ 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();
+ }
+
+ IWizardPageController* RoadmapWizard::getPageController( TabPage* _pCurrentPage )
+ {
+ IWizardPageController* pController = dynamic_cast< IWizardPageController* >( _pCurrentPage );
+ return pController;
+ }
+
+ bool RoadmapWizard::isTravelingSuspended() const
+ {
+ return m_xWizardImpl->m_bTravelingSuspended;
+ }
+
+ void RoadmapWizard::suspendTraveling( AccessGuard )
+ {
+ DBG_ASSERT( !m_xWizardImpl->m_bTravelingSuspended, "RoadmapWizard::suspendTraveling: already suspended!" );
+ m_xWizardImpl->m_bTravelingSuspended = true;
+ }
+
+ void RoadmapWizard::resumeTraveling( AccessGuard )
+ {
+ DBG_ASSERT( m_xWizardImpl->m_bTravelingSuspended, "RoadmapWizard::resumeTraveling: nothing to resume!" );
+ m_xWizardImpl->m_bTravelingSuspended = false;
+ }
+
+ WizardMachine::WizardMachine(weld::Window* pParent, WizardButtonFlags nButtonFlags)
+ : AssistantController(pParent, "vcl/ui/wizard.ui", "Wizard")
+ , m_pCurTabPage(nullptr)
+ , m_nCurState(0)
+ , m_pFirstPage(nullptr)
+ , m_xFinish(m_xAssistant->weld_widget_for_response(RET_OK))
+ , m_xCancel(m_xAssistant->weld_widget_for_response(RET_CANCEL))
+ , m_xNextPage(m_xAssistant->weld_widget_for_response(RET_YES))
+ , m_xPrevPage(m_xAssistant->weld_widget_for_response(RET_NO))
+ , m_xHelp(m_xAssistant->weld_widget_for_response(RET_HELP))
+ , m_pImpl(new WizardMachineImplData)
+ {
+ implConstruct(nButtonFlags);
+ }
+
+ void WizardMachine::implConstruct(const WizardButtonFlags nButtonFlags)
+ {
+ m_pImpl->sTitleBase = m_xAssistant->get_title();
+
+ const bool bHideHelp = comphelper::LibreOfficeKit::isActive() &&
+ officecfg::Office::Common::Help::HelpRootURL::get().isEmpty();
+ // create the buttons according to the wizard button flags
+ // the help button
+ if (nButtonFlags & WizardButtonFlags::HELP && !bHideHelp)
+ m_xHelp->show();
+ else
+ m_xHelp->hide();
+
+ // the previous button
+ if (nButtonFlags & WizardButtonFlags::PREVIOUS)
+ {
+ m_xPrevPage->set_help_id( HID_WIZARD_PREVIOUS );
+ m_xPrevPage->show();
+
+ m_xPrevPage->connect_clicked( LINK( this, WizardMachine, OnPrevPage ) );
+ }
+ else
+ m_xPrevPage->hide();
+
+ // the next button
+ if (nButtonFlags & WizardButtonFlags::NEXT)
+ {
+ m_xNextPage->set_help_id( HID_WIZARD_NEXT );
+ m_xNextPage->show();
+
+ m_xNextPage->connect_clicked( LINK( this, WizardMachine, OnNextPage ) );
+ }
+ else
+ m_xNextPage->hide();
+
+ // the finish button
+ if (nButtonFlags & WizardButtonFlags::FINISH)
+ {
+ m_xFinish->show();
+
+ m_xFinish->connect_clicked( LINK( this, WizardMachine, OnFinish ) );
+ }
+ else
+ m_xFinish->hide();
+
+ // the cancel button
+ if (nButtonFlags & WizardButtonFlags::CANCEL)
+ {
+ m_xCancel->show();
+ m_xCancel->connect_clicked( LINK( this, WizardMachine, OnCancel ) );
+ }
+ else
+ m_xCancel->hide();
+ }
+
+ WizardMachine::~WizardMachine()
+ {
+ if (m_pImpl)
+ {
+ while (m_pFirstPage)
+ RemovePage(m_pFirstPage->mxPage.get());
+ m_pImpl.reset();
+ }
+ }
+
+ void WizardMachine::implUpdateTitle()
+ {
+ OUString sCompleteTitle(m_pImpl->sTitleBase);
+
+ // append the page title
+ BuilderPage* pCurrentPage = GetPage(getCurrentState());
+ if ( pCurrentPage && !pCurrentPage->GetPageTitle().isEmpty() )
+ {
+ sCompleteTitle += " - " + pCurrentPage->GetPageTitle();
+ }
+
+ m_xAssistant->set_title(sCompleteTitle);
+ }
+
+ void WizardMachine::setTitleBase(const OUString& _rTitleBase)
+ {
+ m_pImpl->sTitleBase = _rTitleBase;
+ implUpdateTitle();
+ }
+
+ BuilderPage* WizardMachine::GetOrCreatePage( const WizardTypes::WizardState i_nState )
+ {
+ if ( nullptr == GetPage( i_nState ) )
+ {
+ std::unique_ptr<BuilderPage> xNewPage = createPage( i_nState );
+ DBG_ASSERT( xNewPage, "WizardMachine::GetOrCreatePage: invalid new page (NULL)!" );
+
+ // fill up the page sequence of our base class (with dummies)
+ while ( m_pImpl->nFirstUnknownPage < i_nState )
+ {
+ AddPage( nullptr );
+ ++m_pImpl->nFirstUnknownPage;
+ }
+
+ if ( m_pImpl->nFirstUnknownPage == i_nState )
+ {
+ // encountered this page number the first time
+ AddPage(std::move(xNewPage));
+ ++m_pImpl->nFirstUnknownPage;
+ }
+ else
+ // already had this page - just change it
+ SetPage(i_nState, std::move(xNewPage));
+ }
+ return GetPage( i_nState );
+ }
+
+ void WizardMachine::ActivatePage()
+ {
+ WizardTypes::WizardState nCurrentLevel = m_nCurState;
+ GetOrCreatePage( nCurrentLevel );
+
+ enterState( nCurrentLevel );
+ }
+
+ bool WizardMachine::DeactivatePage()
+ {
+ WizardTypes::WizardState nCurrentState = getCurrentState();
+ return leaveState(nCurrentState);
+ }
+
+ void WizardMachine::defaultButton(WizardButtonFlags _nWizardButtonFlags)
+ {
+ // the new default button
+ weld::Button* pNewDefButton = nullptr;
+ if (_nWizardButtonFlags & WizardButtonFlags::FINISH)
+ pNewDefButton = m_xFinish.get();
+ if (_nWizardButtonFlags & WizardButtonFlags::NEXT)
+ pNewDefButton = m_xNextPage.get();
+ if (_nWizardButtonFlags & WizardButtonFlags::PREVIOUS)
+ pNewDefButton = m_xPrevPage.get();
+ if (_nWizardButtonFlags & WizardButtonFlags::HELP)
+ pNewDefButton = m_xHelp.get();
+ if (_nWizardButtonFlags & WizardButtonFlags::CANCEL)
+ pNewDefButton = m_xCancel.get();
+
+ if ( pNewDefButton )
+ defaultButton( pNewDefButton );
+ else
+ m_xAssistant->recursively_unset_default_buttons();
+ }
+
+ 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
+ m_xAssistant->recursively_unset_default_buttons();
+
+ // set its new style
+ if (_pNewDefButton)
+ _pNewDefButton->set_has_default(true);
+ }
+
+ void WizardMachine::enableButtons(WizardButtonFlags _nWizardButtonFlags, bool _bEnable)
+ {
+ if (_nWizardButtonFlags & WizardButtonFlags::FINISH)
+ m_xFinish->set_sensitive(_bEnable);
+ if (_nWizardButtonFlags & WizardButtonFlags::NEXT)
+ m_xNextPage->set_sensitive(_bEnable);
+ if (_nWizardButtonFlags & WizardButtonFlags::PREVIOUS)
+ m_xPrevPage->set_sensitive(_bEnable);
+ if (_nWizardButtonFlags & WizardButtonFlags::HELP)
+ m_xHelp->set_sensitive(_bEnable);
+ if (_nWizardButtonFlags & WizardButtonFlags::CANCEL)
+ m_xCancel->set_sensitive(_bEnable);
+ }
+
+ void WizardMachine::enterState(WizardTypes::WizardState _nState)
+ {
+ // tell the page
+ IWizardPageController* pController = getPageController( GetPage( _nState ) );
+ OSL_ENSURE( pController, "WizardMachine::enterState: no controller for the given page!" );
+ if ( pController )
+ pController->initializePage();
+
+ if ( isAutomaticNextButtonStateEnabled() )
+ enableButtons( WizardButtonFlags::NEXT, canAdvance() );
+
+ enableButtons( WizardButtonFlags::PREVIOUS, !m_pImpl->aStateHistory.empty() );
+
+ // set the new title - it depends on the current page (i.e. state)
+ implUpdateTitle();
+ }
+
+ bool WizardMachine::leaveState(WizardTypes::WizardState)
+ {
+ // no need to ask the page here.
+ // If we reach this point, we already gave the current page the chance to commit it's data,
+ // and it was allowed to commit it's data
+
+ return true;
+ }
+
+ bool WizardMachine::onFinish()
+ {
+ return Finish(RET_OK);
+ }
+
+ IMPL_LINK_NOARG(WizardMachine, OnFinish, weld::Button&, void)
+ {
+ if ( isTravelingSuspended() )
+ return;
+
+ // prevent WizardTravelSuspension from using this instance
+ // after will be destructed due to onFinish and async response call
+ {
+ WizardTravelSuspension aTravelGuard( *this );
+ if (!prepareLeaveCurrentState(WizardTypes::eFinish))
+ {
+ return;
+ }
+ }
+
+ onFinish();
+ }
+
+ IMPL_LINK_NOARG(WizardMachine, OnCancel, weld::Button&, void)
+ {
+ m_xAssistant->response(RET_CANCEL);
+ }
+
+ WizardTypes::WizardState WizardMachine::determineNextState(WizardTypes::WizardState _nCurrentState ) const
+ {
+ return _nCurrentState + 1;
+ }
+
+ bool WizardMachine::prepareLeaveCurrentState( WizardTypes::CommitPageReason _eReason )
+ {
+ IWizardPageController* pController = getPageController( GetPage( getCurrentState() ) );
+ ENSURE_OR_RETURN( pController != nullptr, "WizardMachine::prepareLeaveCurrentState: no controller for the current page!", true );
+ return pController->commitPage( _eReason );
+ }
+
+ bool WizardMachine::skipBackwardUntil(WizardTypes::WizardState _nTargetState)
+ {
+ // allowed to leave the current page?
+ if ( !prepareLeaveCurrentState( WizardTypes::eTravelBackward ) )
+ return false;
+
+ // don't travel directly on m_pImpl->aStateHistory, in case something goes wrong
+ std::stack< WizardTypes::WizardState > aTravelVirtually = m_pImpl->aStateHistory;
+ std::stack< WizardTypes::WizardState > aOldStateHistory = m_pImpl->aStateHistory;
+
+ WizardTypes::WizardState nCurrentRollbackState = getCurrentState();
+ while ( nCurrentRollbackState != _nTargetState )
+ {
+ DBG_ASSERT( !aTravelVirtually.empty(), "WizardMachine::skipBackwardUntil: this target state does not exist in the history!" );
+ nCurrentRollbackState = aTravelVirtually.top();
+ aTravelVirtually.pop();
+ }
+ m_pImpl->aStateHistory = aTravelVirtually;
+ if ( !ShowPage( _nTargetState ) )
+ {
+ m_pImpl->aStateHistory = aOldStateHistory;
+ return false;
+ }
+ return true;
+ }
+
+ bool WizardMachine::skipUntil( WizardTypes::WizardState _nTargetState )
+ {
+ WizardTypes::WizardState nCurrentState = getCurrentState();
+
+ // allowed to leave the current page?
+ if ( !prepareLeaveCurrentState( nCurrentState < _nTargetState ? WizardTypes::eTravelForward : WizardTypes::eTravelBackward ) )
+ return false;
+
+ // don't travel directly on m_pImpl->aStateHistory, in case something goes wrong
+ std::stack< WizardTypes::WizardState > aTravelVirtually = m_pImpl->aStateHistory;
+ std::stack< WizardTypes::WizardState > aOldStateHistory = m_pImpl->aStateHistory;
+ while ( nCurrentState != _nTargetState )
+ {
+ WizardTypes::WizardState nNextState = determineNextState( nCurrentState );
+ if ( WZS_INVALID_STATE == nNextState )
+ {
+ OSL_FAIL( "WizardMachine::skipUntil: the given target state does not exist!" );
+ return false;
+ }
+
+ // remember the skipped state in the history
+ aTravelVirtually.push( nCurrentState );
+
+ // get the next state
+ nCurrentState = nNextState;
+ }
+ m_pImpl->aStateHistory = aTravelVirtually;
+ // show the target page
+ if ( !ShowPage( nCurrentState ) )
+ {
+ // argh! prepareLeaveCurrentPage succeeded, determineNextState succeeded,
+ // but ShowPage doesn't? Somebody behaves very strange here...
+ OSL_FAIL( "WizardMachine::skipUntil: very unpolite..." );
+ m_pImpl->aStateHistory = aOldStateHistory;
+ return false;
+ }
+ return true;
+ }
+
+ void WizardMachine::skip()
+ {
+ // allowed to leave the current page?
+ if ( !prepareLeaveCurrentState( WizardTypes::eTravelForward ) )
+ return;
+
+ WizardTypes::WizardState nCurrentState = getCurrentState();
+ WizardTypes::WizardState nNextState = determineNextState(nCurrentState);
+
+ if (WZS_INVALID_STATE == nNextState)
+ return;
+
+ // remember the skipped state in the history
+ m_pImpl->aStateHistory.push(nCurrentState);
+
+ // get the next state
+ nCurrentState = nNextState;
+
+ // show the (n+1)th page
+ if (!ShowPage(nCurrentState))
+ {
+ // TODO: this leaves us in a state where we have no current page and an inconsistent state history.
+ // Perhaps we should rollback the skipping here...
+ OSL_FAIL("RoadmapWizard::skip: very unpolite...");
+ // if somebody does a skip and then does not allow to leave...
+ // (can't be a commit error, as we've already committed the current page. So if ShowPage fails here,
+ // somebody behaves really strange ...)
+ return;
+ }
+
+ // all fine
+ }
+
+ bool WizardMachine::travelNext()
+ {
+ // allowed to leave the current page?
+ if ( !prepareLeaveCurrentState( WizardTypes::eTravelForward ) )
+ return false;
+
+ // determine the next state to travel to
+ WizardTypes::WizardState nCurrentState = getCurrentState();
+ WizardTypes::WizardState nNextState = determineNextState(nCurrentState);
+ if (WZS_INVALID_STATE == nNextState)
+ return false;
+
+ // the state history is used by the enterState method
+ // all fine
+ m_pImpl->aStateHistory.push(nCurrentState);
+ if (!ShowPage(nNextState))
+ {
+ m_pImpl->aStateHistory.pop();
+ return false;
+ }
+
+ return true;
+ }
+
+ bool WizardMachine::ShowPage(WizardTypes::WizardState nState)
+ {
+ if (DeactivatePage())
+ {
+ BuilderPage* pOldTabPage = m_pCurTabPage;
+
+ m_nCurState = nState;
+ ActivatePage();
+
+ if (pOldTabPage)
+ pOldTabPage->Deactivate();
+
+ m_xAssistant->set_current_page(OString::number(nState));
+
+ m_pCurTabPage = GetPage(m_nCurState);
+ m_pCurTabPage->Activate();
+
+ return true;
+ }
+ return false;
+ }
+
+ bool WizardMachine::ShowNextPage()
+ {
+ return ShowPage(m_nCurState + 1);
+ }
+
+ bool WizardMachine::ShowPrevPage()
+ {
+ if (!m_nCurState)
+ return false;
+ return ShowPage(m_nCurState - 1);
+ }
+
+ bool WizardMachine::travelPrevious()
+ {
+ DBG_ASSERT(!m_pImpl->aStateHistory.empty(), "WizardMachine::travelPrevious: have no previous page!");
+
+ // allowed to leave the current page?
+ if ( !prepareLeaveCurrentState( WizardTypes::eTravelBackward ) )
+ return false;
+
+ // the next state to switch to
+ WizardTypes::WizardState nPreviousState = m_pImpl->aStateHistory.top();
+
+ // the state history is used by the enterState method
+ m_pImpl->aStateHistory.pop();
+ // show this page
+ if (!ShowPage(nPreviousState))
+ {
+ m_pImpl->aStateHistory.push(nPreviousState);
+ return false;
+ }
+
+ // all fine
+ return true;
+ }
+
+
+ void WizardMachine::removePageFromHistory( WizardTypes::WizardState nToRemove )
+ {
+
+ std::stack< WizardTypes::WizardState > aTemp;
+ while(!m_pImpl->aStateHistory.empty())
+ {
+ WizardTypes::WizardState nPreviousState = m_pImpl->aStateHistory.top();
+ m_pImpl->aStateHistory.pop();
+ if(nPreviousState != nToRemove)
+ aTemp.push( nPreviousState );
+ else
+ break;
+ }
+ while(!aTemp.empty())
+ {
+ m_pImpl->aStateHistory.push( aTemp.top() );
+ aTemp.pop();
+ }
+ }
+
+
+ void WizardMachine::enableAutomaticNextButtonState()
+ {
+ m_pImpl->m_bAutoNextButtonState = true;
+ }
+
+
+ bool WizardMachine::isAutomaticNextButtonStateEnabled() const
+ {
+ return m_pImpl->m_bAutoNextButtonState;
+ }
+
+ IMPL_LINK_NOARG(WizardMachine, OnPrevPage, weld::Button&, void)
+ {
+ if ( isTravelingSuspended() )
+ return;
+ WizardTravelSuspension aTravelGuard( *this );
+ travelPrevious();
+ }
+
+ IMPL_LINK_NOARG(WizardMachine, OnNextPage, weld::Button&, void)
+ {
+ if ( isTravelingSuspended() )
+ return;
+ WizardTravelSuspension aTravelGuard( *this );
+ travelNext();
+ }
+
+ IWizardPageController* WizardMachine::getPageController(BuilderPage* pCurrentPage) const
+ {
+ IWizardPageController* pController = dynamic_cast<IWizardPageController*>(pCurrentPage);
+ return pController;
+ }
+
+ void WizardMachine::getStateHistory( std::vector< WizardTypes::WizardState >& _out_rHistory )
+ {
+ std::stack< WizardTypes::WizardState > aHistoryCopy( m_pImpl->aStateHistory );
+ while ( !aHistoryCopy.empty() )
+ {
+ _out_rHistory.push_back( aHistoryCopy.top() );
+ aHistoryCopy.pop();
+ }
+ }
+
+ bool WizardMachine::canAdvance() const
+ {
+ return WZS_INVALID_STATE != determineNextState( getCurrentState() );
+ }
+
+ void WizardMachine::updateTravelUI()
+ {
+ const IWizardPageController* pController = getPageController( GetPage( getCurrentState() ) );
+ OSL_ENSURE( pController != nullptr, "RoadmapWizard::updateTravelUI: no controller for the current page!" );
+
+ bool bCanAdvance =
+ ( !pController || pController->canAdvance() ) // the current page allows to advance
+ && canAdvance(); // the dialog as a whole allows to advance
+ enableButtons( WizardButtonFlags::NEXT, bCanAdvance );
+ }
+
+ bool WizardMachine::isTravelingSuspended() const
+ {
+ return m_pImpl->m_bTravelingSuspended;
+ }
+
+ void WizardMachine::suspendTraveling( AccessGuard )
+ {
+ DBG_ASSERT( !m_pImpl->m_bTravelingSuspended, "WizardMachine::suspendTraveling: already suspended!" );
+ m_pImpl->m_bTravelingSuspended = true;
+ }
+
+ void WizardMachine::resumeTraveling( AccessGuard )
+ {
+ if (!m_pImpl)
+ return;
+
+ DBG_ASSERT( m_pImpl->m_bTravelingSuspended, "WizardMachine::resumeTraveling: nothing to resume!" );
+ m_pImpl->m_bTravelingSuspended = false;
+ }
+
+ bool WizardMachine::Finish(short nResult)
+ {
+ if ( DeactivatePage() )
+ {
+ if (m_pCurTabPage)
+ m_pCurTabPage->Deactivate();
+
+ m_xAssistant->response(nResult);
+ return true;
+ }
+ else
+ return false;
+ }
+
+ void WizardMachine::AddPage(std::unique_ptr<BuilderPage> xPage)
+ {
+ WizPageData* pNewPageData = new WizPageData;
+ pNewPageData->mpNext = nullptr;
+ pNewPageData->mxPage = std::move(xPage);
+
+ if ( !m_pFirstPage )
+ m_pFirstPage = pNewPageData;
+ else
+ {
+ WizPageData* pPageData = m_pFirstPage;
+ while ( pPageData->mpNext )
+ pPageData = pPageData->mpNext;
+ pPageData->mpNext = pNewPageData;
+ }
+ }
+
+ void WizardMachine::RemovePage(const BuilderPage* pPage)
+ {
+ WizPageData* pPrevPageData = nullptr;
+ WizPageData* pPageData = m_pFirstPage;
+ while ( pPageData )
+ {
+ if (pPageData->mxPage.get() == pPage)
+ {
+ if (pPrevPageData)
+ pPrevPageData->mpNext = pPageData->mpNext;
+ else
+ m_pFirstPage = pPageData->mpNext;
+ if (pPage == m_pCurTabPage)
+ m_pCurTabPage = nullptr;
+ delete pPageData;
+ return;
+ }
+
+ pPrevPageData = pPageData;
+ pPageData = pPageData->mpNext;
+ }
+
+ OSL_FAIL( "WizardMachine::RemovePage() - Page not in list" );
+ }
+
+ void WizardMachine::SetPage(WizardTypes::WizardState nLevel, std::unique_ptr<BuilderPage> xPage)
+ {
+ sal_uInt16 nTempLevel = 0;
+ WizPageData* pPageData = m_pFirstPage;
+ while ( pPageData )
+ {
+ if ( (nTempLevel == nLevel) || !pPageData->mpNext )
+ break;
+
+ nTempLevel++;
+ pPageData = pPageData->mpNext;
+ }
+
+ if ( pPageData )
+ {
+ if (pPageData->mxPage.get() == m_pCurTabPage)
+ m_pCurTabPage = nullptr;
+ pPageData->mxPage = std::move(xPage);
+ }
+ }
+
+ BuilderPage* WizardMachine::GetPage(WizardTypes::WizardState nLevel) const
+ {
+ sal_uInt16 nTempLevel = 0;
+
+ for (WizPageData* pPageData = m_pFirstPage; pPageData;
+ pPageData = pPageData->mpNext)
+ {
+ if ( nTempLevel == nLevel )
+ return pPageData->mxPage.get();
+ nTempLevel++;
+ }
+
+ return nullptr;
+ }
+} // namespace svt
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/wizimpldata.hxx b/vcl/source/control/wizimpldata.hxx
new file mode 100644
index 000000000..fdac4baef
--- /dev/null
+++ b/vcl/source/control/wizimpldata.hxx
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_CONTROL_WIZIMPLDATA_HXX
+#define INCLUDED_VCL_SOURCE_CONTROL_WIZIMPLDATA_HXX
+
+#include <stack>
+
+struct WizPageData
+{
+ WizPageData* mpNext;
+ std::unique_ptr<BuilderPage> mxPage;
+};
+
+struct ImplWizButtonData
+{
+ ImplWizButtonData* mpNext;
+ VclPtr<Button> mpButton;
+ long mnOffset;
+};
+
+namespace vcl
+{
+ struct WizardMachineImplData
+ {
+ OUString sTitleBase; // the base for the title
+ std::stack<WizardTypes::WizardState> aStateHistory; // the history of all states (used for implementing "Back")
+
+ WizardTypes::WizardState nFirstUnknownPage;
+ // the WizardDialog does not allow non-linear transitions (e.g. it's
+ // not possible to add pages in a non-linear order), so we need some own maintenance data
+
+ bool m_bAutoNextButtonState;
+
+ bool m_bTravelingSuspended;
+
+ WizardMachineImplData()
+ :nFirstUnknownPage( 0 )
+ ,m_bAutoNextButtonState( false )
+ ,m_bTravelingSuspended( false )
+ {
+ }
+ };
+} // namespace svt
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/edit/textdat2.hxx b/vcl/source/edit/textdat2.hxx
new file mode 100644
index 000000000..b5f2fc1de
--- /dev/null
+++ b/vcl/source/edit/textdat2.hxx
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_EDIT_TEXTDAT2_HXX
+#define INCLUDED_VCL_SOURCE_EDIT_TEXTDAT2_HXX
+
+#include <vcl/seleng.hxx>
+#include <vcl/cursor.hxx>
+#include <vcl/idle.hxx>
+#include <vcl/textdata.hxx>
+
+#include <cstddef>
+#include <limits>
+#include <vector>
+
+class TextNode;
+class TextView;
+
+#define PORTIONKIND_TEXT 0
+#define PORTIONKIND_TAB 1
+
+#define DELMODE_SIMPLE 0
+#define DELMODE_RESTOFWORD 1
+#define DELMODE_RESTOFCONTENT 2
+
+#define DEL_LEFT 1
+#define DEL_RIGHT 2
+#define TRAVEL_X_DONTKNOW 0xFFFF
+#define MAXCHARSINPARA 0x3FFF-CHARPOSGROW
+
+#define LINE_SEP 0x0A
+
+class TETextPortion
+{
+private:
+ sal_Int32 nLen;
+ long nWidth;
+ sal_uInt8 nKind;
+ bool bRightToLeft;
+
+public:
+ TETextPortion( sal_Int32 nL )
+ : nLen {nL}
+ , nWidth {-1}
+ , nKind {PORTIONKIND_TEXT}
+ , bRightToLeft {false}
+ {}
+
+ sal_Int32& GetLen() { return nLen; }
+ 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<std::unique_ptr<TETextPortion>> maPortions;
+
+public:
+ static constexpr auto npos = std::numeric_limits<std::size_t>::max();
+
+ TETextPortionList();
+ ~TETextPortionList();
+
+ TETextPortion* operator[]( std::size_t nPos );
+ std::vector<std::unique_ptr<TETextPortion>>::iterator begin();
+ std::vector<std::unique_ptr<TETextPortion>>::const_iterator begin() const;
+ std::vector<std::unique_ptr<TETextPortion>>::iterator end();
+ std::vector<std::unique_ptr<TETextPortion>>::const_iterator end() const;
+ bool empty() const;
+ std::size_t size() const;
+ std::vector<std::unique_ptr<TETextPortion>>::iterator erase( const std::vector<std::unique_ptr<TETextPortion>>::iterator& aIter );
+ std::vector<std::unique_ptr<TETextPortion>>::iterator insert( const std::vector<std::unique_ptr<TETextPortion>>::iterator& aIter,
+ std::unique_ptr<TETextPortion> pTP );
+ void push_back( std::unique_ptr<TETextPortion> pTP );
+
+ void Reset();
+ std::size_t FindPortion( sal_Int32 nCharPos, sal_Int32& rPortionStart, bool bPreferStartingPortion = false );
+ void DeleteFromPortion( std::size_t nDelFrom );
+};
+
+struct TEWritingDirectionInfo
+{
+ bool bLeftToRight;
+ sal_Int32 nStartPos;
+ sal_Int32 nEndPos;
+ TEWritingDirectionInfo( bool LeftToRight, sal_Int32 Start, sal_Int32 End )
+ : bLeftToRight {LeftToRight}
+ , nStartPos {Start}
+ , nEndPos {End}
+ {}
+};
+
+class TextLine
+{
+private:
+ sal_Int32 mnStart;
+ sal_Int32 mnEnd;
+ std::size_t mnStartPortion;
+ std::size_t mnEndPortion;
+
+ short mnStartX;
+
+ bool mbInvalid; // for clever formatting/output
+
+public:
+ TextLine()
+ : mnStart {0}
+ , mnEnd {0}
+ , mnStartPortion {0}
+ , mnEndPortion {0}
+ , mnStartX {0}
+ , mbInvalid {true}
+ {}
+
+ bool IsIn( sal_Int32 nIndex, bool bInclEnd ) const
+ { return nIndex >= mnStart && ( bInclEnd ? nIndex <= mnEnd : nIndex < mnEnd ); }
+
+ void SetStart( sal_Int32 n ) { mnStart = n; }
+ sal_Int32 GetStart() const { return mnStart; }
+
+ void SetEnd( sal_Int32 n ) { mnEnd = n; }
+ sal_Int32 GetEnd() const { return mnEnd; }
+
+ void SetStartPortion( std::size_t n ) { mnStartPortion = n; }
+ std::size_t GetStartPortion() const { return mnStartPortion; }
+
+ void SetEndPortion( std::size_t n ) { mnEndPortion = n; }
+ std::size_t GetEndPortion() const { return mnEndPortion; }
+
+ sal_Int32 GetLen() const { return mnEnd - mnStart; }
+
+ bool IsInvalid() const { return mbInvalid; }
+ bool IsValid() const { return !mbInvalid; }
+ void SetInvalid() { mbInvalid = true; }
+ void SetValid() { mbInvalid = false; }
+
+ short GetStartX() const { return mnStartX; }
+ void SetStartX( short n ) { mnStartX = n; }
+
+ inline bool operator == ( const TextLine& rLine ) const;
+};
+
+inline bool TextLine::operator == ( const TextLine& rLine ) const
+{
+ return mnStart == rLine.mnStart &&
+ mnEnd == rLine.mnEnd &&
+ mnStartPortion == rLine.mnStartPortion &&
+ mnEndPortion == rLine.mnEndPortion;
+}
+
+class TEParaPortion
+{
+private:
+ TextNode* mpNode;
+
+ std::vector<TextLine> maLines;
+ TETextPortionList maTextPortions;
+ std::vector<TEWritingDirectionInfo> maWritingDirectionInfos;
+
+ sal_Int32 mnInvalidPosStart;
+ sal_Int32 mnInvalidDiff;
+
+ bool mbInvalid;
+ bool mbSimple; // only type linearly
+
+public:
+ TEParaPortion( TextNode* pNode );
+ ~TEParaPortion();
+
+ TEParaPortion( const TEParaPortion& ) = delete;
+ void operator=( const TEParaPortion& ) = delete;
+
+ bool IsInvalid() const { return mbInvalid; }
+ bool IsSimpleInvalid() const { return mbSimple; }
+ void SetNotSimpleInvalid() { mbSimple = false; }
+ void SetValid() { mbInvalid = false; mbSimple = true;}
+
+ void MarkInvalid( sal_Int32 nStart, sal_Int32 nDiff );
+ void MarkSelectionInvalid( sal_Int32 nStart );
+
+ sal_Int32 GetInvalidPosStart() const { return mnInvalidPosStart; }
+ sal_Int32 GetInvalidDiff() const { return mnInvalidDiff; }
+
+ TextNode* GetNode() const { return mpNode; }
+ std::vector<TextLine>& GetLines() { return maLines; }
+ TETextPortionList& GetTextPortions() { return maTextPortions; }
+ std::vector<TEWritingDirectionInfo>& GetWritingDirectionInfos() { return maWritingDirectionInfos; }
+
+ std::vector<TextLine>::size_type GetLineNumber( sal_Int32 nIndex, bool bInclEnd );
+ void CorrectValuesBehindLastFormattedLine( sal_uInt16 nLastFormattedLine );
+};
+
+class TEParaPortions
+{
+private:
+ std::vector<std::unique_ptr<TEParaPortion>> mvData;
+
+public:
+ TEParaPortions() : mvData() {}
+ ~TEParaPortions();
+
+ sal_uInt32 Count() const { return static_cast<sal_uInt32>(mvData.size()); }
+ TEParaPortion* GetObject( sal_uInt32 nIndex ) { return mvData[nIndex].get(); }
+ void Insert( TEParaPortion* pObject, sal_uInt32 nPos ) { mvData.emplace( mvData.begin()+nPos, pObject ); }
+ void Remove( sal_uInt32 nPos ) { mvData.erase( mvData.begin()+nPos ); }
+};
+
+class TextSelFunctionSet: public FunctionSet
+{
+private:
+ TextView* mpView;
+
+public:
+ TextSelFunctionSet( TextView* pView );
+
+ virtual void BeginDrag() override;
+
+ virtual void CreateAnchor() override;
+
+ virtual void SetCursorAtPoint( const Point& rPointPixel, bool bDontSelectAtCursor = false ) override;
+
+ virtual bool IsSelectionAtPoint( const Point& rPointPixel ) override;
+ virtual void DeselectAll() override;
+
+ virtual void DeselectAtPoint( const Point& ) override;
+ virtual void DestroyAnchor() override;
+};
+
+class IdleFormatter : public Idle
+{
+private:
+ TextView* mpView;
+ sal_uInt16 mnRestarts;
+
+public:
+ IdleFormatter();
+ virtual ~IdleFormatter() override;
+
+ void DoIdleFormat( TextView* pV, sal_uInt16 nMaxRestarts );
+ void ForceTimeout();
+ TextView* GetView() { return mpView; }
+};
+
+struct TextDDInfo
+{
+ vcl::Cursor maCursor;
+ TextPaM maDropPos;
+
+ bool mbStarterOfDD;
+ bool mbVisCursor;
+
+ TextDDInfo()
+ : maCursor()
+ , maDropPos()
+ , mbStarterOfDD {false}
+ , mbVisCursor {false}
+ {
+ maCursor.SetStyle( CURSOR_SHADOW );
+ }
+};
+
+#endif // INCLUDED_VCL_SOURCE_EDIT_TEXTDAT2_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/edit/textdata.cxx b/vcl/source/edit/textdata.cxx
new file mode 100644
index 000000000..165dbfde9
--- /dev/null
+++ b/vcl/source/edit/textdata.cxx
@@ -0,0 +1,343 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+#include <cstddef>
+
+#include <vcl/textdata.hxx>
+#include "textdat2.hxx"
+
+
+TextSelection::TextSelection()
+{
+}
+
+TextSelection::TextSelection( const TextPaM& rPaM ) :
+ maStartPaM( rPaM ), maEndPaM( rPaM )
+{
+}
+
+TextSelection::TextSelection( const TextPaM& rStart, const TextPaM& rEnd ) :
+ maStartPaM( rStart ), maEndPaM( rEnd )
+{
+}
+
+void TextSelection::Justify()
+{
+ if ( maEndPaM < maStartPaM )
+ {
+ TextPaM aTemp( maStartPaM );
+ maStartPaM = maEndPaM;
+ maEndPaM = aTemp;
+ }
+}
+
+TETextPortionList::TETextPortionList()
+{
+}
+
+TETextPortionList::~TETextPortionList()
+{
+ Reset();
+}
+
+TETextPortion* TETextPortionList::operator[]( std::size_t nPos )
+{
+ return maPortions[ nPos ].get();
+}
+
+std::vector<std::unique_ptr<TETextPortion>>::iterator TETextPortionList::begin()
+{
+ return maPortions.begin();
+}
+
+std::vector<std::unique_ptr<TETextPortion>>::const_iterator TETextPortionList::begin() const
+{
+ return maPortions.begin();
+}
+
+std::vector<std::unique_ptr<TETextPortion>>::iterator TETextPortionList::end()
+{
+ return maPortions.end();
+}
+
+std::vector<std::unique_ptr<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<std::unique_ptr<TETextPortion>>::iterator TETextPortionList::erase( const std::vector<std::unique_ptr<TETextPortion>>::iterator& aIter )
+{
+ return maPortions.erase( aIter );
+}
+
+std::vector<std::unique_ptr<TETextPortion>>::iterator TETextPortionList::insert( const std::vector<std::unique_ptr<TETextPortion>>::iterator& aIter,
+ std::unique_ptr<TETextPortion> pTP )
+{
+ return maPortions.insert( aIter, std::move(pTP) );
+}
+
+void TETextPortionList::push_back( std::unique_ptr<TETextPortion> pTP )
+{
+ maPortions.push_back( std::move(pTP) );
+}
+
+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* pPortion = maPortions[ nPortion ].get();
+ nTmpPos += pPortion->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 - pPortion->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 ) )
+ {
+ 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 )
+ {
+ 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()
+ : 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_uLong nValue ) : SfxHint( Id ), mnValue(nValue)
+{
+}
+
+TEIMEInfos::TEIMEInfos( const TextPaM& rPos, const OUString& rOldTextAfterStartPos )
+ : aOldTextAfterStartPos(rOldTextAfterStartPos)
+ , aPos(rPos)
+ , nLen(0)
+ , bWasCursorOverwrite(false)
+{
+}
+
+TEIMEInfos::~TEIMEInfos()
+{
+}
+
+void TEIMEInfos::CopyAttribs(const ExtTextInputAttr* pA, sal_Int32 nL)
+{
+ nLen = nL;
+ pAttribs.reset( new ExtTextInputAttr[ nL ] );
+ memcpy( pAttribs.get(), pA, nL*sizeof(ExtTextInputAttr) );
+}
+
+void TEIMEInfos::DestroyAttribs()
+{
+ pAttribs.reset();
+ nLen = 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/edit/textdoc.cxx b/vcl/source/edit/textdoc.cxx
new file mode 100644
index 000000000..cba52bc5d
--- /dev/null
+++ b/vcl/source/edit/textdoc.cxx
@@ -0,0 +1,536 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <memory>
+#include "textdoc.hxx"
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+#include <rtl/ustrbuf.hxx>
+
+// compare function called by QuickSort
+static bool CompareStart( const std::unique_ptr<TextCharAttrib>& pFirst, const std::unique_ptr<TextCharAttrib>& pSecond )
+{
+ return pFirst->GetStart() < pSecond->GetStart();
+}
+
+TextCharAttrib::TextCharAttrib( const TextAttrib& rAttr, sal_Int32 nStart, sal_Int32 nEnd )
+ : mpAttr(rAttr.Clone())
+ , mnStart(nStart)
+ , mnEnd(nEnd)
+{
+}
+
+TextCharAttrib::TextCharAttrib( const TextCharAttrib& rTextCharAttrib )
+ : mpAttr(rTextCharAttrib.mpAttr->Clone())
+ , mnStart(rTextCharAttrib.mnStart)
+ , mnEnd(rTextCharAttrib.mnEnd)
+{
+}
+
+TextCharAttribList::TextCharAttribList()
+ : mbHasEmptyAttribs(false)
+{
+}
+
+TextCharAttribList::~TextCharAttribList()
+{
+ // PTRARR_DEL
+}
+
+void TextCharAttribList::Clear()
+{
+ maAttribs.clear();
+}
+
+void TextCharAttribList::InsertAttrib( std::unique_ptr<TextCharAttrib> pAttrib )
+{
+ if ( pAttrib->IsEmpty() )
+ mbHasEmptyAttribs = true;
+
+ const sal_Int32 nStart = pAttrib->GetStart(); // maybe better for Comp.Opt.
+ bool bInserted = false;
+ auto it = std::find_if(maAttribs.begin(), maAttribs.end(),
+ [nStart](std::unique_ptr<TextCharAttrib>& rAttrib) { return rAttrib->GetStart() > nStart; });
+ if (it != maAttribs.end())
+ {
+ maAttribs.insert( it, std::move(pAttrib) );
+ bInserted = true;
+ }
+ if ( !bInserted )
+ maAttribs.push_back( std::move(pAttrib) );
+}
+
+void TextCharAttribList::ResortAttribs()
+{
+ std::sort( maAttribs.begin(), maAttribs.end(), CompareStart );
+}
+
+TextCharAttrib* TextCharAttribList::FindAttrib( sal_uInt16 nWhich, sal_Int32 nPos )
+{
+ for (std::vector<std::unique_ptr<TextCharAttrib> >::reverse_iterator it = maAttribs.rbegin(); it != maAttribs.rend(); ++it)
+ {
+ if ( (*it)->GetEnd() < nPos )
+ return nullptr;
+
+ if ( ( (*it)->Which() == nWhich ) && (*it)->IsIn(nPos) )
+ return it->get();
+ }
+ return nullptr;
+}
+
+bool TextCharAttribList::HasBoundingAttrib( sal_Int32 nBound )
+{
+ for (std::vector<std::unique_ptr<TextCharAttrib> >::reverse_iterator it = maAttribs.rbegin(); it != maAttribs.rend(); ++it)
+ {
+ if ( (*it)->GetEnd() < nBound )
+ return false;
+
+ if ( ( (*it)->GetStart() == nBound ) || ( (*it)->GetEnd() == nBound ) )
+ return true;
+ }
+ return false;
+}
+
+TextCharAttrib* TextCharAttribList::FindEmptyAttrib( sal_uInt16 nWhich, sal_Int32 nPos )
+{
+ if ( !mbHasEmptyAttribs )
+ return nullptr;
+
+ for (auto const& attrib : maAttribs)
+ {
+ if ( attrib->GetStart() > nPos )
+ return nullptr;
+
+ if ( ( attrib->GetStart() == nPos ) && ( attrib->GetEnd() == nPos ) && ( attrib->Which() == nWhich ) )
+ return attrib.get();
+ }
+ return nullptr;
+}
+
+void TextCharAttribList::DeleteEmptyAttribs()
+{
+ maAttribs.erase(
+ std::remove_if( maAttribs.begin(), maAttribs.end(),
+ [] (const std::unique_ptr<TextCharAttrib>& rAttrib) { return rAttrib->IsEmpty(); } ),
+ maAttribs.end() );
+ mbHasEmptyAttribs = false;
+}
+
+TextNode::TextNode( const OUString& rText ) :
+ maText( rText )
+{
+}
+
+void TextNode::ExpandAttribs( sal_Int32 nIndex, sal_Int32 nNew )
+{
+ if ( !nNew )
+ return;
+
+ bool bResort = false;
+ sal_uInt16 nAttribs = maCharAttribs.Count();
+ for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ )
+ {
+ TextCharAttrib& rAttrib = maCharAttribs.GetAttrib( nAttr );
+ if ( rAttrib.GetEnd() >= nIndex )
+ {
+ // move all attributes that are behind the cursor
+ if ( rAttrib.GetStart() > nIndex )
+ {
+ rAttrib.MoveForward( nNew );
+ }
+ // 0: expand empty attribute, if at cursor
+ else if ( rAttrib.IsEmpty() )
+ {
+ // Do not check the index; empty one may only be here.
+ // If checking later anyway, special case:
+ // Start == 0; AbsLen == 1, nNew = 1 => Expand due to new paragraph!
+ // Start <= nIndex, End >= nIndex => Start=End=nIndex!
+ rAttrib.Expand( nNew );
+ }
+ // 1: attribute starts before and reaches up to index
+ else if ( rAttrib.GetEnd() == nIndex ) // start must be before
+ {
+ // Only expand if no feature and not in Exclude list!
+ // Otherwise e.g. an UL would go until the new ULDB, thus expand both.
+ if ( !maCharAttribs.FindEmptyAttrib( rAttrib.Which(), nIndex ) )
+ {
+ rAttrib.Expand( nNew );
+ }
+ else
+ bResort = true;
+ }
+ // 2: attribute starts before and reaches past the index
+ else if ( ( rAttrib.GetStart() < nIndex ) && ( rAttrib.GetEnd() > nIndex ) )
+ {
+ rAttrib.Expand( nNew );
+ }
+ // 3: attribute starts at Index
+ else if ( rAttrib.GetStart() == nIndex )
+ {
+ if ( nIndex == 0 )
+ {
+ rAttrib.Expand( nNew );
+ }
+ else
+ rAttrib.MoveForward( nNew );
+ }
+ }
+
+ SAL_WARN_IF( rAttrib.GetStart() > rAttrib.GetEnd(), "vcl", "Expand: attribute twisted!" );
+ SAL_WARN_IF( ( rAttrib.GetEnd() > maText.getLength() ), "vcl", "Expand: attribute greater than paragraph!" );
+ SAL_WARN_IF( rAttrib.IsEmpty(), "vcl", "Empty attribute after ExpandAttribs?" );
+ }
+
+ if ( bResort )
+ maCharAttribs.ResortAttribs();
+}
+
+void TextNode::CollapseAttribs( sal_Int32 nIndex, sal_Int32 nDeleted )
+{
+ if ( !nDeleted )
+ return;
+
+ bool bResort = false;
+ const sal_Int32 nEndChanges = nIndex+nDeleted;
+
+ for ( sal_uInt16 nAttr = 0; nAttr < maCharAttribs.Count(); nAttr++ )
+ {
+ TextCharAttrib& rAttrib = maCharAttribs.GetAttrib( nAttr );
+ bool bDelAttr = false;
+ if ( rAttrib.GetEnd() >= nIndex )
+ {
+ // move all attributes that are behind the cursor
+ if ( rAttrib.GetStart() >= nEndChanges )
+ {
+ rAttrib.MoveBackward( nDeleted );
+ }
+ // 1. delete inner attributes
+ else if ( ( rAttrib.GetStart() >= nIndex ) && ( rAttrib.GetEnd() <= nEndChanges ) )
+ {
+ // special case: attribute covers the region exactly
+ // => keep as an empty attribute
+ if ( ( rAttrib.GetStart() == nIndex ) && ( rAttrib.GetEnd() == nEndChanges ) )
+ rAttrib.SetEnd(nIndex); // empty
+ else
+ bDelAttr = true;
+ }
+ // 2. attribute starts before, ends inside or after
+ else if ( ( rAttrib.GetStart() <= nIndex ) && ( rAttrib.GetEnd() > nIndex ) )
+ {
+ if ( rAttrib.GetEnd() <= nEndChanges ) // ends inside
+ rAttrib.SetEnd(nIndex);
+ else
+ rAttrib.Collaps( nDeleted ); // ends after
+ }
+ // 3. attribute starts inside, ends after
+ else if ( ( rAttrib.GetStart() >= nIndex ) && ( rAttrib.GetEnd() > nEndChanges ) )
+ {
+ // features are not allowed to expand!
+ rAttrib.SetStart(nEndChanges);
+ rAttrib.MoveBackward( nDeleted );
+ }
+ }
+
+ SAL_WARN_IF( rAttrib.GetStart() > rAttrib.GetEnd(), "vcl", "Collaps: attribute twisted!" );
+ SAL_WARN_IF( ( rAttrib.GetEnd() > maText.getLength()) && !bDelAttr, "vcl", "Collaps: attribute greater than paragraph!" );
+ if ( bDelAttr /* || rAttrib.IsEmpty() */ )
+ {
+ bResort = true;
+ maCharAttribs.RemoveAttrib( nAttr );
+ nAttr--;
+ }
+ else if ( rAttrib.IsEmpty() )
+ maCharAttribs.HasEmptyAttribs() = true;
+ }
+
+ if ( bResort )
+ maCharAttribs.ResortAttribs();
+}
+
+void TextNode::InsertText( sal_Int32 nPos, const OUString& rText )
+{
+ maText = maText.replaceAt( nPos, 0, rText );
+ ExpandAttribs( nPos, rText.getLength() );
+}
+
+void TextNode::InsertText( sal_Int32 nPos, sal_Unicode c )
+{
+ maText = maText.replaceAt( nPos, 0, OUString(c) );
+ ExpandAttribs( nPos, 1 );
+}
+
+void TextNode::RemoveText( sal_Int32 nPos, sal_Int32 nChars )
+{
+ maText = maText.replaceAt( nPos, nChars, "" );
+ CollapseAttribs( nPos, nChars );
+}
+
+std::unique_ptr<TextNode> TextNode::Split( sal_Int32 nPos )
+{
+ OUString aNewText;
+ if ( nPos < maText.getLength() )
+ {
+ aNewText = maText.copy( nPos );
+ maText = maText.copy(0, nPos);
+ }
+ std::unique_ptr<TextNode> pNew(new TextNode( aNewText ));
+
+ for ( sal_uInt16 nAttr = 0; nAttr < maCharAttribs.Count(); nAttr++ )
+ {
+ TextCharAttrib& rAttrib = maCharAttribs.GetAttrib( nAttr );
+ if ( rAttrib.GetEnd() < nPos )
+ {
+ // no change
+ ;
+ }
+ else if ( rAttrib.GetEnd() == nPos )
+ {
+ // must be copied as an empty attribute
+ // !FindAttrib only sensible if traversing backwards through the list!
+ if ( !pNew->maCharAttribs.FindAttrib( rAttrib.Which(), 0 ) )
+ {
+ std::unique_ptr<TextCharAttrib> pNewAttrib(new TextCharAttrib( rAttrib ));
+ pNewAttrib->SetStart(0);
+ pNewAttrib->SetEnd(0);
+ pNew->maCharAttribs.InsertAttrib( std::move(pNewAttrib) );
+ }
+ }
+ else if ( rAttrib.IsInside( nPos ) || ( !nPos && !rAttrib.GetStart() ) )
+ {
+ // If cutting at the very beginning, the attribute has to be
+ // copied and changed
+ std::unique_ptr<TextCharAttrib> pNewAttrib(new TextCharAttrib( rAttrib ));
+ pNewAttrib->SetStart(0);
+ pNewAttrib->SetEnd(rAttrib.GetEnd()-nPos);
+ pNew->maCharAttribs.InsertAttrib( std::move(pNewAttrib) );
+ // trim
+ rAttrib.SetEnd(nPos);
+ }
+ else
+ {
+ SAL_WARN_IF( rAttrib.GetStart() < nPos, "vcl", "Start < nPos!" );
+ SAL_WARN_IF( rAttrib.GetEnd() < nPos, "vcl", "End < nPos!" );
+ // move all into the new node (this)
+ pNew->maCharAttribs.InsertAttrib(maCharAttribs.RemoveAttrib(nAttr));
+ rAttrib.SetStart( rAttrib.GetStart() - nPos );
+ rAttrib.SetEnd( rAttrib.GetEnd() - nPos );
+ nAttr--;
+ }
+ }
+ return pNew;
+}
+
+void TextNode::Append( const TextNode& rNode )
+{
+ sal_Int32 nOldLen = maText.getLength();
+
+ maText += rNode.GetText();
+
+ const sal_uInt16 nAttribs = rNode.GetCharAttribs().Count();
+ for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ )
+ {
+ const TextCharAttrib& rAttrib = rNode.GetCharAttrib( nAttr );
+ bool bMelted = false;
+ if ( rAttrib.GetStart() == 0 )
+ {
+ // potentially merge attributes
+ sal_uInt16 nTmpAttribs = maCharAttribs.Count();
+ for ( sal_uInt16 nTmpAttr = 0; nTmpAttr < nTmpAttribs; nTmpAttr++ )
+ {
+ TextCharAttrib& rTmpAttrib = maCharAttribs.GetAttrib( nTmpAttr );
+
+ if ( rTmpAttrib.GetEnd() == nOldLen )
+ {
+ if ( ( rTmpAttrib.Which() == rAttrib.Which() ) &&
+ ( rTmpAttrib.GetAttr() == rAttrib.GetAttr() ) )
+ {
+ rTmpAttrib.SetEnd( rTmpAttrib.GetEnd() + rAttrib.GetLen() );
+ bMelted = true;
+ break; // there can be only one of this type at this position
+ }
+ }
+ }
+ }
+
+ if ( !bMelted )
+ {
+ std::unique_ptr<TextCharAttrib> pNewAttrib(new TextCharAttrib( rAttrib ));
+ pNewAttrib->SetStart( pNewAttrib->GetStart() + nOldLen );
+ pNewAttrib->SetEnd( pNewAttrib->GetEnd() + nOldLen );
+ maCharAttribs.InsertAttrib( std::move(pNewAttrib) );
+ }
+ }
+}
+
+TextDoc::TextDoc()
+ : mnLeftMargin(0)
+{
+};
+
+TextDoc::~TextDoc()
+{
+ DestroyTextNodes();
+}
+
+void TextDoc::Clear()
+{
+ DestroyTextNodes();
+}
+
+void TextDoc::DestroyTextNodes()
+{
+ maTextNodes.clear();
+}
+
+OUString TextDoc::GetText( const sal_Unicode* pSep ) const
+{
+ sal_uInt32 nNodes = static_cast<sal_uInt32>(maTextNodes.size());
+
+ OUStringBuffer aASCIIText;
+ const sal_uInt32 nLastNode = nNodes-1;
+ for ( sal_uInt32 nNode = 0; nNode < nNodes; ++nNode )
+ {
+ TextNode* pNode = maTextNodes[ nNode ].get();
+ aASCIIText.append(pNode->GetText());
+ if ( pSep && ( nNode != nLastNode ) )
+ aASCIIText.append(pSep);
+ }
+
+ return aASCIIText.makeStringAndClear();
+}
+
+OUString TextDoc::GetText( sal_uInt32 nPara ) const
+{
+ TextNode* pNode = ( nPara < maTextNodes.size() ) ? maTextNodes[ nPara ].get() : nullptr;
+ if ( pNode )
+ return pNode->GetText();
+
+ return OUString();
+}
+
+sal_Int32 TextDoc::GetTextLen( const sal_Unicode* pSep, const TextSelection* pSel ) const
+{
+ sal_Int32 nLen = 0;
+ sal_uInt32 nNodes = static_cast<sal_uInt32>(maTextNodes.size());
+ if ( nNodes )
+ {
+ sal_uInt32 nStartNode = 0;
+ sal_uInt32 nEndNode = nNodes-1;
+ if ( pSel )
+ {
+ nStartNode = pSel->GetStart().GetPara();
+ nEndNode = pSel->GetEnd().GetPara();
+ }
+
+ for ( sal_uInt32 nNode = nStartNode; nNode <= nEndNode; ++nNode )
+ {
+ TextNode* pNode = maTextNodes[ nNode ].get();
+
+ sal_Int32 nS = 0;
+ sal_Int32 nE = pNode->GetText().getLength();
+ if ( pSel && ( nNode == pSel->GetStart().GetPara() ) )
+ nS = pSel->GetStart().GetIndex();
+ if ( pSel && ( nNode == pSel->GetEnd().GetPara() ) )
+ nE = pSel->GetEnd().GetIndex();
+
+ nLen += ( nE - nS );
+ }
+
+ if ( pSep )
+ nLen += (nEndNode-nStartNode) * rtl_ustr_getLength(pSep);
+ }
+
+ return nLen;
+}
+
+TextPaM TextDoc::InsertText( const TextPaM& rPaM, sal_Unicode c )
+{
+ SAL_WARN_IF( c == 0x0A, "vcl", "TextDoc::InsertText: Line separator in paragraph not allowed!" );
+ SAL_WARN_IF( c == 0x0D, "vcl", "TextDoc::InsertText: Line separator in paragraph not allowed!" );
+
+ TextNode* pNode = maTextNodes[ rPaM.GetPara() ].get();
+ pNode->InsertText( rPaM.GetIndex(), c );
+
+ TextPaM aPaM( rPaM.GetPara(), rPaM.GetIndex()+1 );
+ return aPaM;
+}
+
+TextPaM TextDoc::InsertText( const TextPaM& rPaM, const OUString& rStr )
+{
+ SAL_WARN_IF( rStr.indexOf( 0x0A ) != -1, "vcl", "TextDoc::InsertText: Line separator in paragraph not allowed!" );
+ SAL_WARN_IF( rStr.indexOf( 0x0D ) != -1, "vcl", "TextDoc::InsertText: Line separator in paragraph not allowed!" );
+
+ TextNode* pNode = maTextNodes[ rPaM.GetPara() ].get();
+ pNode->InsertText( rPaM.GetIndex(), rStr );
+
+ TextPaM aPaM( rPaM.GetPara(), rPaM.GetIndex()+rStr.getLength() );
+ return aPaM;
+}
+
+TextPaM TextDoc::InsertParaBreak( const TextPaM& rPaM )
+{
+ TextNode* pNode = maTextNodes[ rPaM.GetPara() ].get();
+ std::unique_ptr<TextNode> pNew = pNode->Split( rPaM.GetIndex() );
+
+ SAL_WARN_IF( maTextNodes.size()>=SAL_MAX_UINT32, "vcl", "InsertParaBreak: more than 4Gi paragraphs!" );
+ maTextNodes.insert( maTextNodes.begin() + rPaM.GetPara() + 1, std::move(pNew) );
+
+ TextPaM aPaM( rPaM.GetPara()+1, 0 );
+ return aPaM;
+}
+
+TextPaM TextDoc::ConnectParagraphs( TextNode* pLeft, const TextNode* pRight )
+{
+ sal_Int32 nPrevLen = pLeft->GetText().getLength();
+ pLeft->Append( *pRight );
+
+ // the paragraph on the right vanishes
+ maTextNodes.erase( std::find_if( maTextNodes.begin(), maTextNodes.end(),
+ [&] (std::unique_ptr<TextNode> const & p) { return p.get() == pRight; } ) );
+
+ sal_uLong nLeft = ::std::find_if( maTextNodes.begin(), maTextNodes.end(),
+ [&] (std::unique_ptr<TextNode> const & p) { return p.get() == pLeft; } )
+ - maTextNodes.begin();
+ TextPaM aPaM( nLeft, nPrevLen );
+ return aPaM;
+}
+
+void TextDoc::RemoveChars( const TextPaM& rPaM, sal_Int32 nChars )
+{
+ TextNode* pNode = maTextNodes[ rPaM.GetPara() ].get();
+ pNode->RemoveText( rPaM.GetIndex(), nChars );
+}
+
+bool TextDoc::IsValidPaM( const TextPaM& rPaM )
+{
+ if ( rPaM.GetPara() >= maTextNodes.size() )
+ {
+ OSL_FAIL( "PaM: Para out of range" );
+ return false;
+ }
+ TextNode * pNode = maTextNodes[ rPaM.GetPara() ].get();
+ if ( rPaM.GetIndex() > pNode->GetText().getLength() )
+ {
+ OSL_FAIL( "PaM: Index out of range" );
+ return false;
+ }
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/edit/textdoc.hxx b/vcl/source/edit/textdoc.hxx
new file mode 100644
index 000000000..11657463e
--- /dev/null
+++ b/vcl/source/edit/textdoc.hxx
@@ -0,0 +1,129 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_EDIT_TEXTDOC_HXX
+#define INCLUDED_VCL_SOURCE_EDIT_TEXTDOC_HXX
+
+#include <rtl/ustring.hxx>
+#include <vcl/textdata.hxx>
+#include <vcl/txtattr.hxx>
+#include <vector>
+#include <memory>
+
+class TextCharAttribList
+{
+private:
+ TextCharAttribList(const TextCharAttribList&) = delete;
+ TextCharAttribList& operator=(const TextCharAttribList&) = delete;
+
+ std::vector<std::unique_ptr<TextCharAttrib> > maAttribs;
+ bool mbHasEmptyAttribs;
+
+public:
+ TextCharAttribList();
+ ~TextCharAttribList();
+
+ void Clear();
+ sal_uInt16 Count() const { return maAttribs.size(); }
+
+ const TextCharAttrib& GetAttrib( sal_uInt16 n ) const { return *maAttribs[n]; }
+ TextCharAttrib& GetAttrib( sal_uInt16 n ) { return *maAttribs[n]; }
+ std::unique_ptr<TextCharAttrib> RemoveAttrib( sal_uInt16 n )
+ {
+ std::unique_ptr<TextCharAttrib> pReleased = std::move(maAttribs[n]);
+ maAttribs.erase( maAttribs.begin() + n );
+ return pReleased;
+ }
+
+ void InsertAttrib( std::unique_ptr<TextCharAttrib> pAttrib );
+
+ void DeleteEmptyAttribs();
+ void ResortAttribs();
+
+ bool& HasEmptyAttribs() { return mbHasEmptyAttribs; }
+
+ TextCharAttrib* FindAttrib( sal_uInt16 nWhich, sal_Int32 nPos );
+ TextCharAttrib* FindEmptyAttrib( sal_uInt16 nWhich, sal_Int32 nPos );
+ bool HasBoundingAttrib( sal_Int32 nBound );
+};
+
+class TextNode
+{
+ OUString maText;
+ TextCharAttribList maCharAttribs;
+
+ void ExpandAttribs( sal_Int32 nIndex, sal_Int32 nNewChars );
+ void CollapseAttribs( sal_Int32 nIndex, sal_Int32 nDelChars );
+
+public:
+ TextNode( const OUString& rText );
+
+ TextNode( const TextNode& ) = delete;
+ void operator=( const TextNode& ) = delete;
+
+ const OUString& GetText() const { return maText; }
+
+ const TextCharAttrib& GetCharAttrib(sal_uInt16 nPos) const { return maCharAttribs.GetAttrib(nPos); }
+ const TextCharAttribList& GetCharAttribs() const { return maCharAttribs; }
+ TextCharAttribList& GetCharAttribs() { return maCharAttribs; }
+
+ void InsertText( sal_Int32 nPos, const OUString& rText );
+ void InsertText( sal_Int32 nPos, sal_Unicode c );
+ void RemoveText( sal_Int32 nPos, sal_Int32 nChars );
+
+ std::unique_ptr<TextNode> Split( sal_Int32 nPos );
+ void Append( const TextNode& rNode );
+};
+
+class TextDoc
+{
+ std::vector<std::unique_ptr<TextNode>> maTextNodes;
+ sal_uInt16 mnLeftMargin;
+
+ void DestroyTextNodes();
+
+public:
+ TextDoc();
+ ~TextDoc();
+
+ void Clear();
+
+ std::vector<std::unique_ptr<TextNode>>& GetNodes() { return maTextNodes; }
+ const std::vector<std::unique_ptr<TextNode>>& GetNodes() const { return maTextNodes; }
+
+ void RemoveChars( const TextPaM& rPaM, sal_Int32 nChars );
+ TextPaM InsertText( const TextPaM& rPaM, sal_Unicode c );
+ TextPaM InsertText( const TextPaM& rPaM, const OUString& rStr );
+
+ TextPaM InsertParaBreak( const TextPaM& rPaM );
+ TextPaM ConnectParagraphs( TextNode* pLeft, const TextNode* pRight );
+
+ sal_Int32 GetTextLen( const sal_Unicode* pSep, const TextSelection* pSel = nullptr ) const;
+ OUString GetText( const sal_Unicode* pSep ) const;
+ OUString GetText( sal_uInt32 nPara ) const;
+
+ void SetLeftMargin( sal_uInt16 n ) { mnLeftMargin = n; }
+ sal_uInt16 GetLeftMargin() const { return mnLeftMargin; }
+
+ bool IsValidPaM( const TextPaM& rPaM );
+};
+
+#endif // INCLUDED_VCL_SOURCE_EDIT_TEXTDOC_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/edit/texteng.cxx b/vcl/source/edit/texteng.cxx
new file mode 100644
index 000000000..8e0f7a161
--- /dev/null
+++ b/vcl/source/edit/texteng.cxx
@@ -0,0 +1,2896 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/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 <set>
+#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 ) );
+ mpIdleFormatter->SetDebugName( "vcl::TextEngine mpIdleFormatter" );
+
+ mpRefDev = VclPtr<VirtualDevice>::Create();
+
+ ImpInitLayoutMode( mpRefDev );
+
+ ImpInitDoc();
+
+ vcl::Font aFont;
+ aFont.SetTransparent( false );
+ Color aFillColor( aFont.GetFillColor() );
+ aFillColor.SetTransparency( 0 );
+ 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.SetTransparency( 0 );
+ 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( long nMaxWidth )
+{
+ if ( nMaxWidth>=0 && nMaxWidth != mnMaxTextWidth )
+ {
+ mnMaxTextWidth = nMaxWidth;
+ FormatFullDoc();
+ UpdateViews();
+ }
+}
+
+static const sal_Unicode static_aLFText[] = { '\n', 0 };
+static const sal_Unicode static_aCRText[] = { '\r', 0 };
+static 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( std::u16string_view(pTEParaPortion->GetNode()->GetText()).substr(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(std::u16string_view(pNode->GetText()).substr(nStartPos, nEndPos-nStartPos));
+ if ( nNode < nEndPara )
+ aText.append(pSep);
+ }
+ return aText.makeStringAndClear();
+}
+
+void TextEngine::ImpRemoveText()
+{
+ ImpInitDoc();
+
+ const TextSelection aEmptySel;
+ for (TextView* pView : *mpViews)
+ {
+ pView->ImpSetSelection( aEmptySel );
+ }
+ ResetUndo();
+}
+
+void TextEngine::SetText( const OUString& rText )
+{
+ ImpRemoveText();
+
+ const bool bUndoCurrentlyEnabled = IsUndoEnabled();
+ // the manually inserted text cannot be reversed by the user
+ EnableUndo( false );
+
+ const TextSelection aEmptySel;
+
+ TextPaM aPaM;
+ if ( !rText.isEmpty() )
+ aPaM = ImpInsertText( aEmptySel, rText );
+
+ for (TextView* pView : *mpViews)
+ {
+ pView->ImpSetSelection( aEmptySel );
+
+ // if no text, then no Format&Update => the text remains
+ if ( rText.isEmpty() && GetUpdateMode() )
+ pView->Invalidate();
+ }
+
+ if( rText.isEmpty() ) // otherwise needs invalidation later; !bFormatted is sufficient
+ mnCurTextHeight = 0;
+
+ FormatAndUpdate();
+
+ EnableUndo( bUndoCurrentlyEnabled );
+ SAL_WARN_IF( HasUndoManager() && GetUndoManager().GetUndoActionCount(), "vcl", "SetText: Undo!" );
+}
+
+void TextEngine::CursorMoved( sal_uInt32 nNode )
+{
+ // delete empty attribute; but only if paragraph is not empty!
+ TextNode* pNode = mpDoc->GetNodes()[ nNode ].get();
+ if ( pNode && pNode->GetCharAttribs().HasEmptyAttribs() && !pNode->GetText().isEmpty() )
+ pNode->GetCharAttribs().DeleteEmptyAttribs();
+}
+
+void TextEngine::ImpRemoveChars( const TextPaM& rPaM, sal_Int32 nChars )
+{
+ SAL_WARN_IF( !nChars, "vcl", "ImpRemoveChars: 0 Chars?!" );
+ if ( IsUndoEnabled() && !IsInUndo() )
+ {
+ // attributes have to be saved for UNDO before RemoveChars!
+ TextNode* pNode = mpDoc->GetNodes()[ rPaM.GetPara() ].get();
+ OUString aStr( pNode->GetText().copy( rPaM.GetIndex(), nChars ) );
+
+ // check if attributes are being deleted or changed
+ const sal_Int32 nStart = rPaM.GetIndex();
+ const sal_Int32 nEnd = nStart + nChars;
+ for ( sal_uInt16 nAttr = pNode->GetCharAttribs().Count(); nAttr; )
+ {
+ TextCharAttrib& rAttr = pNode->GetCharAttribs().GetAttrib( --nAttr );
+ if ( ( rAttr.GetEnd() >= nStart ) && ( rAttr.GetStart() < nEnd ) )
+ {
+ break; // for
+ }
+ }
+ InsertUndo( std::make_unique<TextUndoRemoveChars>( this, rPaM, aStr ) );
+ }
+
+ mpDoc->RemoveChars( rPaM, nChars );
+ ImpCharsRemoved( rPaM.GetPara(), rPaM.GetIndex(), nChars );
+}
+
+TextPaM TextEngine::ImpConnectParagraphs( sal_uInt32 nLeft, sal_uInt32 nRight )
+{
+ SAL_WARN_IF( nLeft == nRight, "vcl", "ImpConnectParagraphs: connect the very same paragraph ?" );
+
+ TextNode* pLeft = mpDoc->GetNodes()[ nLeft ].get();
+ TextNode* pRight = mpDoc->GetNodes()[ nRight ].get();
+
+ if ( IsUndoEnabled() && !IsInUndo() )
+ InsertUndo( std::make_unique<TextUndoConnectParas>( this, nLeft, pLeft->GetText().getLength() ) );
+
+ // first lookup Portions, as pRight is gone after ConnectParagraphs
+ TEParaPortion* pLeftPortion = mpTEParaPortions->GetObject( nLeft );
+ TEParaPortion* pRightPortion = mpTEParaPortions->GetObject( nRight );
+ SAL_WARN_IF( !pLeft || !pLeftPortion, "vcl", "ImpConnectParagraphs(1): Hidden Portion" );
+ SAL_WARN_IF( !pRight || !pRightPortion, "vcl", "ImpConnectParagraphs(2): Hidden Portion" );
+
+ TextPaM aPaM = mpDoc->ConnectParagraphs( pLeft, pRight );
+ ImpParagraphRemoved( nRight );
+
+ pLeftPortion->MarkSelectionInvalid( aPaM.GetIndex() );
+
+ mpTEParaPortions->Remove( nRight );
+ // the right Node is deleted by EditDoc::ConnectParagraphs()
+
+ return aPaM;
+}
+
+TextPaM TextEngine::ImpDeleteText( const TextSelection& rSel )
+{
+ if ( !rSel.HasRange() )
+ return rSel.GetStart();
+
+ TextSelection aSel( rSel );
+ aSel.Justify();
+ TextPaM aStartPaM( aSel.GetStart() );
+ TextPaM aEndPaM( aSel.GetEnd() );
+
+ CursorMoved( aStartPaM.GetPara() ); // so that newly-adjusted attributes vanish
+ CursorMoved( aEndPaM.GetPara() ); // so that newly-adjusted attributes vanish
+
+ SAL_WARN_IF( !mpDoc->IsValidPaM( aStartPaM ), "vcl", "ImpDeleteText(1): bad Index" );
+ SAL_WARN_IF( !mpDoc->IsValidPaM( aEndPaM ), "vcl", "ImpDeleteText(2): bad Index" );
+
+ const sal_uInt32 nStartNode = aStartPaM.GetPara();
+ sal_uInt32 nEndNode = aEndPaM.GetPara();
+
+ // remove all Nodes inbetween
+ for ( sal_uInt32 z = nStartNode+1; z < nEndNode; ++z )
+ {
+ // always nStartNode+1, because of Remove()!
+ ImpRemoveParagraph( nStartNode+1 );
+ }
+
+ if ( nStartNode != nEndNode )
+ {
+ // the remainder of StartNodes...
+ TextNode* pLeft = mpDoc->GetNodes()[ nStartNode ].get();
+ sal_Int32 nChars = pLeft->GetText().getLength() - aStartPaM.GetIndex();
+ if ( nChars )
+ {
+ ImpRemoveChars( aStartPaM, nChars );
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( nStartNode );
+ SAL_WARN_IF( !pPortion, "vcl", "ImpDeleteText(3): bad Index" );
+ pPortion->MarkSelectionInvalid( aStartPaM.GetIndex() );
+ }
+
+ // the beginning of EndNodes...
+ nEndNode = nStartNode+1; // the other paragraphs were deleted
+ nChars = aEndPaM.GetIndex();
+ if ( nChars )
+ {
+ aEndPaM.GetPara() = nEndNode;
+ aEndPaM.GetIndex() = 0;
+ ImpRemoveChars( aEndPaM, nChars );
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( nEndNode );
+ SAL_WARN_IF( !pPortion, "vcl", "ImpDeleteText(4): bad Index" );
+ pPortion->MarkSelectionInvalid( 0 );
+ }
+
+ // connect...
+ aStartPaM = ImpConnectParagraphs( nStartNode, nEndNode );
+ }
+ else
+ {
+ const sal_Int32 nChars = aEndPaM.GetIndex() - aStartPaM.GetIndex();
+ ImpRemoveChars( aStartPaM, nChars );
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( nStartNode );
+ SAL_WARN_IF( !pPortion, "vcl", "ImpDeleteText(5): bad Index" );
+ pPortion->MarkInvalid( aEndPaM.GetIndex(), aStartPaM.GetIndex() - aEndPaM.GetIndex() );
+ }
+
+// UpdateSelections();
+ TextModified();
+ return aStartPaM;
+}
+
+void TextEngine::ImpRemoveParagraph( sal_uInt32 nPara )
+{
+ std::unique_ptr<TextNode> pNode = std::move(mpDoc->GetNodes()[ nPara ]);
+
+ // the Node is handled by Undo and is deleted if appropriate
+ mpDoc->GetNodes().erase( mpDoc->GetNodes().begin() + nPara );
+ if ( IsUndoEnabled() && !IsInUndo() )
+ InsertUndo( std::make_unique<TextUndoDelPara>( this, pNode.release(), nPara ) );
+
+ mpTEParaPortions->Remove( nPara );
+
+ ImpParagraphRemoved( nPara );
+}
+
+uno::Reference < i18n::XExtendedInputSequenceChecker > const & TextEngine::GetInputSequenceChecker()
+{
+ if ( !mxISC.is() )
+ {
+ mxISC = i18n::InputSequenceChecker::create( ::comphelper::getProcessComponentContext() );
+ }
+ return mxISC;
+}
+
+bool TextEngine::IsInputSequenceCheckingRequired( sal_Unicode c, const TextSelection& rCurSel ) const
+{
+ SvtCTLOptions aCTLOptions;
+
+ // get the index that really is first
+ const sal_Int32 nFirstPos = std::min(rCurSel.GetStart().GetIndex(), rCurSel.GetEnd().GetIndex());
+
+ bool bIsSequenceChecking =
+ aCTLOptions.IsCTLFontEnabled() &&
+ aCTLOptions.IsCTLSequenceChecking() &&
+ nFirstPos != 0; /* first char needs not to be checked */
+
+ if (bIsSequenceChecking)
+ {
+ uno::Reference< i18n::XBreakIterator > xBI = const_cast<TextEngine *>(this)->GetBreakIterator();
+ bIsSequenceChecking = xBI.is() && i18n::ScriptType::COMPLEX == xBI->getScriptType( OUString( c ), 0 );
+ }
+
+ return bIsSequenceChecking;
+}
+
+TextPaM TextEngine::ImpInsertText( const TextSelection& rCurSel, sal_Unicode c, bool bOverwrite )
+{
+ return ImpInsertText( c, rCurSel, bOverwrite );
+}
+
+TextPaM TextEngine::ImpInsertText( sal_Unicode c, const TextSelection& rCurSel, bool bOverwrite, bool bIsUserInput )
+{
+ SAL_WARN_IF( c == '\n', "vcl", "InsertText: NewLine!" );
+ SAL_WARN_IF( c == '\r', "vcl", "InsertText: NewLine!" );
+
+ TextPaM aPaM( rCurSel.GetStart() );
+ TextNode* pNode = mpDoc->GetNodes()[ aPaM.GetPara() ].get();
+
+ bool bDoOverwrite = bOverwrite && ( aPaM.GetIndex() < pNode->GetText().getLength() );
+
+ bool bUndoAction = rCurSel.HasRange() || bDoOverwrite;
+
+ if ( bUndoAction )
+ UndoActionStart();
+
+ if ( rCurSel.HasRange() )
+ {
+ aPaM = ImpDeleteText( rCurSel );
+ }
+ else if ( bDoOverwrite )
+ {
+ // if selection, then don't overwrite a character
+ TextSelection aTmpSel( aPaM );
+ ++aTmpSel.GetEnd().GetIndex();
+ ImpDeleteText( aTmpSel );
+ }
+
+ if (bIsUserInput && IsInputSequenceCheckingRequired( c, rCurSel ))
+ {
+ uno::Reference < i18n::XExtendedInputSequenceChecker > xISC = GetInputSequenceChecker();
+ SvtCTLOptions aCTLOptions;
+
+ if (xISC.is())
+ {
+ sal_Int32 nTmpPos = aPaM.GetIndex();
+ sal_Int16 nCheckMode = aCTLOptions.IsCTLSequenceCheckingRestricted() ?
+ i18n::InputSequenceCheckMode::STRICT : i18n::InputSequenceCheckMode::BASIC;
+
+ // the text that needs to be checked is only the one
+ // before the current cursor position
+ OUString aOldText( mpDoc->GetText( aPaM.GetPara() ).copy(0, nTmpPos) );
+ if (aCTLOptions.IsCTLSequenceCheckingTypeAndReplace())
+ {
+ OUString aNewText( aOldText );
+ xISC->correctInputSequence( aNewText, nTmpPos - 1, c, nCheckMode );
+
+ // find position of first character that has changed
+ const sal_Int32 nOldLen = aOldText.getLength();
+ const sal_Int32 nNewLen = aNewText.getLength();
+ const sal_Unicode *pOldTxt = aOldText.getStr();
+ const sal_Unicode *pNewTxt = aNewText.getStr();
+ sal_Int32 nChgPos = 0;
+ while ( nChgPos < nOldLen && nChgPos < nNewLen &&
+ pOldTxt[nChgPos] == pNewTxt[nChgPos] )
+ ++nChgPos;
+
+ OUString aChgText( aNewText.copy( nChgPos ) );
+
+ // select text from first pos to be changed to current pos
+ TextSelection aSel( TextPaM( aPaM.GetPara(), nChgPos ), aPaM );
+
+ if (!aChgText.isEmpty())
+ // ImpInsertText implicitly handles undo...
+ return ImpInsertText( aSel, aChgText );
+ else
+ return aPaM;
+ }
+ else
+ {
+ // should the character be ignored (i.e. not get inserted) ?
+ if (!xISC->checkInputSequence( aOldText, nTmpPos - 1, c, nCheckMode ))
+ return aPaM; // nothing to be done -> no need for undo
+ }
+ }
+
+ // at this point now we will insert the character 'normally' some lines below...
+ }
+
+ if ( IsUndoEnabled() && !IsInUndo() )
+ {
+ std::unique_ptr<TextUndoInsertChars> pNewUndo(new TextUndoInsertChars( this, aPaM, OUString(c) ));
+ bool bTryMerge = !bDoOverwrite && ( c != ' ' );
+ InsertUndo( std::move(pNewUndo), bTryMerge );
+ }
+
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( aPaM.GetPara() );
+ pPortion->MarkInvalid( aPaM.GetIndex(), 1 );
+ if ( c == '\t' )
+ pPortion->SetNotSimpleInvalid();
+ aPaM = mpDoc->InsertText( aPaM, c );
+ ImpCharsInserted( aPaM.GetPara(), aPaM.GetIndex()-1, 1 );
+
+ TextModified();
+
+ if ( bUndoAction )
+ UndoActionEnd();
+
+ return aPaM;
+}
+
+TextPaM TextEngine::ImpInsertText( const TextSelection& rCurSel, const OUString& rStr )
+{
+ UndoActionStart();
+
+ TextPaM aPaM;
+
+ if ( rCurSel.HasRange() )
+ aPaM = ImpDeleteText( rCurSel );
+ else
+ aPaM = rCurSel.GetEnd();
+
+ OUString aText(convertLineEnd(rStr, LINEEND_LF));
+
+ sal_Int32 nStart = 0;
+ while ( nStart < aText.getLength() )
+ {
+ sal_Int32 nEnd = aText.indexOf( LINE_SEP, nStart );
+ if (nEnd == -1)
+ nEnd = aText.getLength(); // do not dereference!
+
+ // Start == End => empty line
+ if ( nEnd > nStart )
+ {
+ OUString aLine(aText.copy(nStart, nEnd-nStart));
+ if ( IsUndoEnabled() && !IsInUndo() )
+ InsertUndo( std::make_unique<TextUndoInsertChars>( this, aPaM, aLine ) );
+
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( aPaM.GetPara() );
+ pPortion->MarkInvalid( aPaM.GetIndex(), aLine.getLength() );
+ if (aLine.indexOf( '\t' ) != -1)
+ pPortion->SetNotSimpleInvalid();
+
+ aPaM = mpDoc->InsertText( aPaM, aLine );
+ ImpCharsInserted( aPaM.GetPara(), aPaM.GetIndex()-aLine.getLength(), aLine.getLength() );
+
+ }
+ if ( nEnd < aText.getLength() )
+ aPaM = ImpInsertParaBreak( aPaM );
+
+ if ( nEnd == aText.getLength() ) // #108611# prevent overflow in "nStart = nEnd+1" calculation
+ break;
+
+ nStart = nEnd+1;
+ }
+
+ UndoActionEnd();
+
+ TextModified();
+ return aPaM;
+}
+
+TextPaM TextEngine::ImpInsertParaBreak( const TextSelection& rCurSel )
+{
+ TextPaM aPaM;
+ if ( rCurSel.HasRange() )
+ aPaM = ImpDeleteText( rCurSel );
+ else
+ aPaM = rCurSel.GetEnd();
+
+ return ImpInsertParaBreak( aPaM );
+}
+
+TextPaM TextEngine::ImpInsertParaBreak( const TextPaM& rPaM )
+{
+ if ( IsUndoEnabled() && !IsInUndo() )
+ InsertUndo( std::make_unique<TextUndoSplitPara>( this, rPaM.GetPara(), rPaM.GetIndex() ) );
+
+ TextNode* pNode = mpDoc->GetNodes()[ rPaM.GetPara() ].get();
+ bool bFirstParaContentChanged = rPaM.GetIndex() < pNode->GetText().getLength();
+
+ TextPaM aPaM( mpDoc->InsertParaBreak( rPaM ) );
+
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( rPaM.GetPara() );
+ SAL_WARN_IF( !pPortion, "vcl", "ImpInsertParaBreak: Hidden Portion" );
+ pPortion->MarkInvalid( rPaM.GetIndex(), 0 );
+
+ TextNode* pNewNode = mpDoc->GetNodes()[ aPaM.GetPara() ].get();
+ TEParaPortion* pNewPortion = new TEParaPortion( pNewNode );
+ mpTEParaPortions->Insert( pNewPortion, aPaM.GetPara() );
+ ImpParagraphInserted( aPaM.GetPara() );
+
+ CursorMoved( rPaM.GetPara() ); // if empty attribute created
+ TextModified();
+
+ if ( bFirstParaContentChanged )
+ Broadcast( TextHint( SfxHintId::TextParaContentChanged, rPaM.GetPara() ) );
+
+ return aPaM;
+}
+
+tools::Rectangle TextEngine::PaMtoEditCursor( const TextPaM& rPaM, bool bSpecial )
+{
+ SAL_WARN_IF( !GetUpdateMode(), "vcl", "PaMtoEditCursor: GetUpdateMode()" );
+
+ tools::Rectangle aEditCursor;
+ 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...
+
+ */
+
+ 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
+ long nX = ImpGetXPos( rPaM.GetPara(), pLine, rPaM.GetIndex(), bPreferPortionStart );
+ aEditCursor.SetLeft(nX);
+ aEditCursor.SetRight(nX);
+ return aEditCursor;
+}
+
+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* pPortion = pParaPortion->GetTextPortions()[ nTextPortion ];
+
+ long nX = ImpGetPortionXOffset( nPara, pLine, nTextPortion );
+
+ long nPortionTextWidth = pPortion->GetWidth();
+
+ if ( nTextPortionStart != nIndex )
+ {
+ // Search within portion...
+ if ( nIndex == ( nTextPortionStart + pPortion->GetLen() ) )
+ {
+ // End of Portion
+ if ( ( pPortion->GetKind() == PORTIONKIND_TAB ) ||
+ ( !IsRightToLeft() && !pPortion->IsRightToLeft() ) ||
+ ( IsRightToLeft() && pPortion->IsRightToLeft() ) )
+ {
+ nX += nPortionTextWidth;
+ if ( ( pPortion->GetKind() == PORTIONKIND_TAB ) && ( (nTextPortion+1) < pParaPortion->GetTextPortions().size() ) )
+ {
+ TETextPortion* pNextPortion = pParaPortion->GetTextPortions()[ nTextPortion+1 ];
+ if (pNextPortion->GetKind() != PORTIONKIND_TAB && IsRightToLeft() != pNextPortion->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 ( pPortion->GetKind() == PORTIONKIND_TEXT )
+ {
+ SAL_WARN_IF( nIndex == pLine->GetStart(), "vcl", "ImpGetXPos: Strange behavior" );
+
+ long nPosInPortion = CalcTextWidth( nPara, nTextPortionStart, nIndex-nTextPortionStart );
+
+ if (IsRightToLeft() == pPortion->IsRightToLeft())
+ {
+ nX += nPosInPortion;
+ }
+ else
+ {
+ nX += nPortionTextWidth - nPosInPortion;
+ }
+ }
+ }
+ else // if ( nIndex == pLine->GetStart() )
+ {
+ if (pPortion->GetKind() != PORTIONKIND_TAB && IsRightToLeft() != pPortion->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()" );
+
+ long nY = 0;
+ for ( sal_uInt32 nPortion = 0; nPortion < mpTEParaPortions->Count(); ++nPortion )
+ {
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPortion );
+ 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;
+
+ 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, long nXPos )
+{
+
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPortion );
+ TextLine& rLine = pPortion->GetLines()[ nLine ];
+
+ sal_Int32 nCurIndex = rLine.GetStart();
+
+ long nTmpX = rLine.GetStartX();
+ if ( nXPos <= nTmpX )
+ return nCurIndex;
+
+ for ( std::size_t i = rLine.GetStartPortion(); i <= rLine.GetEndPortion(); i++ )
+ {
+ TETextPortion* pTextPortion = pPortion->GetTextPortions()[ i ];
+ nTmpX += pTextPortion->GetWidth();
+
+ if ( nTmpX > nXPos )
+ {
+ if( pTextPortion->GetLen() > 1 )
+ {
+ nTmpX -= pTextPortion->GetWidth(); // position before Portion
+ // TODO: Optimize: no GetTextBreak if fixed-width Font
+ vcl::Font aFont;
+ SeekCursor( nPortion, nCurIndex+1, aFont, nullptr );
+ mpRefDev->SetFont( aFont);
+ long nPosInPortion = nXPos-nTmpX;
+ if ( IsRightToLeft() != pTextPortion->IsRightToLeft() )
+ nPosInPortion = pTextPortion->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 += pTextPortion->GetLen();
+ }
+ return nCurIndex;
+}
+
+long TextEngine::GetTextHeight() const
+{
+ SAL_WARN_IF( !GetUpdateMode(), "vcl", "GetTextHeight: GetUpdateMode()" );
+
+ if ( !IsFormatted() && !IsFormatting() )
+ const_cast<TextEngine*>(this)->FormatAndUpdate();
+
+ return mnCurTextHeight;
+}
+
+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 );
+}
+
+long TextEngine::CalcTextWidth( sal_uInt32 nPara )
+{
+ long nParaWidth = 0;
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPara );
+ for ( auto nLine = pPortion->GetLines().size(); nLine; )
+ {
+ long nLineWidth = 0;
+ TextLine& rLine = pPortion->GetLines()[ --nLine ];
+ for ( std::size_t nTP = rLine.GetStartPortion(); nTP <= rLine.GetEndPortion(); nTP++ )
+ {
+ TETextPortion* pTextPortion = pPortion->GetTextPortions()[ nTP ];
+ nLineWidth += pTextPortion->GetWidth();
+ }
+ if ( nLineWidth > nParaWidth )
+ nParaWidth = nLineWidth;
+ }
+ return nParaWidth;
+}
+
+long TextEngine::CalcTextWidth()
+{
+ if ( !IsFormatted() && !IsFormatting() )
+ FormatAndUpdate();
+
+ if ( mnCurTextWidth < 0 )
+ {
+ mnCurTextWidth = 0;
+ for ( sal_uInt32 nPara = mpTEParaPortions->Count(); nPara; )
+ {
+ const long nParaWidth = CalcTextWidth( --nPara );
+ if ( nParaWidth > mnCurTextWidth )
+ mnCurTextWidth = nParaWidth;
+ }
+ }
+ return mnCurTextWidth+1;// wider by 1, as CreateLines breaks at >=
+}
+
+long TextEngine::CalcTextHeight()
+{
+ SAL_WARN_IF( !GetUpdateMode(), "vcl", "CalcTextHeight: GetUpdateMode()" );
+
+ long nY = 0;
+ for ( auto nPortion = mpTEParaPortions->Count(); nPortion; )
+ nY += CalcParaHeight( --nPortion );
+ return nY;
+}
+
+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();
+ 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* pTextPortion = pParaPortion->GetTextPortions()[ i ];
+ if (nStart + pTextPortion->GetLen() > rPaM.GetIndex())
+ {
+ nEnd = nStart + pTextPortion->GetLen();
+ return;
+ }
+ else
+ {
+ nStart += pTextPortion->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;
+}
+
+long TextEngine::CalcParaHeight( sal_uInt32 nParagraph ) const
+{
+ 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 ) ) )
+ {
+ ExtTextInputAttr nAttr = mpIMEInfos->pAttribs[ nPos - mpIMEInfos->aPos.GetIndex() - 1 ];
+ if ( nAttr & ExtTextInputAttr::Underline )
+ rFont.SetUnderline( LINESTYLE_SINGLE );
+ 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;
+
+ 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 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 long nWidth = mnMaxTextWidth
+ ? mnMaxTextWidth
+ : std::numeric_limits<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 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 long nNewHeight = CalcTextHeight();
+ const 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();
+
+ std::unique_ptr<TETextPortion> pDummyPortion(new TETextPortion( 0 ));
+ pDummyPortion->GetWidth() = 0;
+ pTEParaPortion->GetTextPortions().push_back( std::move(pDummyPortion) );
+
+ 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, 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* pTP = pTEParaPortion->GetTextPortions()[ nEndPortion ];
+ SAL_WARN_IF( nBreakPos <= pLine->GetStart(), "vcl", "ImpBreakLine: SplitTextPortion at beginning of line?" );
+ pTP->GetWidth() = CalcTextWidth( nPara, nBreakPos-pTP->GetLen(), pTP->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* pTP = pTEParaPortion->GetTextPortions()[nSplitPortion];
+ nTmpPos += pTP->GetLen();
+ if ( nTmpPos >= nPos )
+ {
+ if ( nTmpPos == nPos ) // nothing needs splitting
+ return nSplitPortion;
+ pTextPortion = pTP;
+ break;
+ }
+ }
+
+ SAL_WARN_IF( !pTextPortion, "vcl", "SplitTextPortion: position outside of region!" );
+
+ const sal_Int32 nOverlapp = nTmpPos - nPos;
+ pTextPortion->GetLen() -= nOverlapp;
+ std::unique_ptr<TETextPortion> pNewPortion( new TETextPortion( nOverlapp ) );
+ pTEParaPortion->GetTextPortions().insert( pTEParaPortion->GetTextPortions().begin() + nSplitPortion + 1, std::move(pNewPortion) );
+ pTextPortion->GetWidth() = CalcTextWidth( nPara, nPos-pTextPortion->GetLen(), pTextPortion->GetLen() );
+
+ 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!" );
+
+ std::set<sal_Int32> aPositions;
+ std::set<sal_Int32>::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* pTmpPortion = pTEParaPortion->GetTextPortions()[nP];
+ nPortionStart += pTmpPortion->GetLen();
+ if ( nPortionStart >= nStartPos )
+ {
+ nPortionStart -= pTmpPortion->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() )
+ {
+ std::set<sal_Int32>::iterator nextIt = aPositionsIt;
+ for ( ++nextIt; nextIt != aPositions.end(); ++aPositionsIt, ++nextIt )
+ {
+ std::unique_ptr<TETextPortion> pNew( new TETextPortion( *nextIt - *aPositionsIt ) );
+ pTEParaPortion->GetTextPortions().push_back( std::move(pNew) );
+ }
+ }
+ 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
+ {
+ std::unique_ptr<TETextPortion> pNewPortion(new TETextPortion( nNewChars ));
+ pTEParaPortion->GetTextPortions().insert( pTEParaPortion->GetTextPortions().begin() + nNewPortionPos, std::move(pNewPortion) );
+ }
+ }
+ else
+ {
+ sal_Int32 nPortionStart {0};
+ const std::size_t nTP = pTEParaPortion->GetTextPortions().FindPortion( nStartPos, nPortionStart );
+ TETextPortion* const pTP = pTEParaPortion->GetTextPortions()[ nTP ];
+ SAL_WARN_IF( !pTP, "vcl", "RecalcTextPortion: Portion not found!" );
+ pTP->GetLen() += nNewChars;
+ pTP->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 = dynamic_cast<vcl::Window*>(pOutDev);
+ const bool bTransparent = (pOutWin && pOutWin->IsPaintTransparent());
+
+ 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 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* pTextPortion = pPortion->GetTextPortions()[ y ];
+ SAL_WARN_IF( !pTextPortion, "vcl", "ImpPaint: Bad pTextPortion!" );
+
+ ImpInitLayoutMode( pOutDev /*, pTextPortion->IsRightToLeft() */);
+
+ const long nTxtWidth = pTextPortion->GetWidth();
+ aTmpPos.setX( rStartPos.X() + ImpGetOutputOffset( nPara, &rLine, nIndex, nIndex ) );
+
+ // only print if starting in the visible region
+ if ( ( aTmpPos.X() + nTxtWidth ) >= 0 )
+ {
+ switch ( pTextPortion->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 + pTextPortion->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 += pTextPortion->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 & pTP : pTEParaPortion->GetTextPortions() )
+ {
+ // there must be no Start/End in the deleted region
+ nPos += pTP->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();
+ long nTmpWidth = mpDoc->GetLeftMargin();
+ // do not subtract margin; it is included in TmpWidth
+ long nXWidth = std::max(
+ mnMaxTextWidth ? mnMaxTextWidth : std::numeric_limits<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' )
+ {
+ long nCurPos = nTmpWidth-mpDoc->GetLeftMargin();
+ nTmpWidth = ((nCurPos/mnDefTab)+1)*mnDefTab+mpDoc->GetLeftMargin();
+ pPortion->GetWidth() = nTmpWidth - nCurPos - mpDoc->GetLeftMargin();
+ // infinite loop, if this is the first token of the line and nTmpWidth > aPaperSize.Width !!!
+ if ( ( nTmpWidth >= nXWidth ) && ( nTmpPortion == pLine->GetStartPortion() ) )
+ {
+ // adjust Tab
+ pPortion->GetWidth() = nXWidth-1;
+ nTmpWidth = pPortion->GetWidth();
+ bEOL = true;
+ bBrokenLine = true;
+ }
+ pPortion->GetKind() = PORTIONKIND_TAB;
+ }
+ else
+ {
+
+ pPortion->GetWidth() = CalcTextWidth( nPara, nTmpPos, pPortion->GetLen() );
+ nTmpWidth += pPortion->GetWidth();
+
+ pPortion->SetRightToLeft( ImpGetRightToLeft( nPara, nTmpPos+1 ) );
+ pPortion->GetKind() = PORTIONKIND_TEXT;
+ }
+
+ nTmpPos += pPortion->GetLen();
+ nPortionEnd = nTmpPos;
+ nTmpPortion++;
+ }
+
+ // this was perhaps one Portion too far
+ bool bFixedEnd = false;
+ if ( nTmpWidth > nXWidth )
+ {
+ nPortionEnd = nTmpPos;
+ nTmpPos -= pPortion->GetLen();
+ nPortionStart = nTmpPos;
+ nTmpPortion--;
+ bEOL = false;
+
+ nTmpWidth -= pPortion->GetWidth();
+ if ( pPortion->GetKind() == PORTIONKIND_TAB )
+ {
+ bEOL = true;
+ bFixedEnd = true;
+ }
+ }
+ else
+ {
+ bEOL = true;
+ pLine->SetEnd( nPortionEnd );
+ OSL_ENSURE(pTEParaPortion->GetTextPortions().size(),
+ "CreateLines: No TextPortions?");
+ pLine->SetEndPortion( pTEParaPortion->GetTextPortions().size() - 1 );
+ }
+
+ if ( bFixedEnd )
+ {
+ pLine->SetEnd( nPortionStart );
+ pLine->SetEndPortion( nTmpPortion-1 );
+ }
+ else if ( bBrokenLine )
+ {
+ pLine->SetEnd( nPortionStart+1 );
+ pLine->SetEndPortion( nTmpPortion-1 );
+ }
+ else if ( !bEOL )
+ {
+ SAL_WARN_IF( (nPortionEnd-nPortionStart) != pPortion->GetLen(), "vcl", "CreateLines: There is a Portion after all?!" );
+ const long nRemainingWidth = mnMaxTextWidth - nTmpWidth;
+ ImpBreakLine( nPara, pLine, nPortionStart, nRemainingWidth );
+ }
+
+ if ( ( ImpGetAlign() == TxtAlign::Center ) || ( ImpGetAlign() == TxtAlign::Right ) )
+ {
+ // adjust
+ long nTextWidth = 0;
+ for ( std::size_t nTP = pLine->GetStartPortion(); nTP <= pLine->GetEndPortion(); nTP++ )
+ {
+ TETextPortion* pTextPortion = pTEParaPortion->GetTextPortions()[ nTP ];
+ nTextWidth += pTextPortion->GetWidth();
+ }
+ const 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 < 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 );
+
+ OString 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() )
+ {
+ 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() )
+ {
+ 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;
+
+ long nCount = ubidi_countRuns( pBidi, &nError );
+
+ int32_t nStart = 0;
+ int32_t nEnd;
+ UBiDiLevel nCurrDir;
+
+ for ( 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;
+}
+
+long TextEngine::ImpGetPortionXOffset( sal_uInt32 nPara, TextLine const * pLine, std::size_t nTextPortion )
+{
+ long nX = pLine->GetStartX();
+
+ TEParaPortion* pParaPortion = mpTEParaPortions->GetObject( nPara );
+
+ for ( std::size_t i = pLine->GetStartPortion(); i < nTextPortion; i++ )
+ {
+ TETextPortion* pPortion = pParaPortion->GetTextPortions()[ i ];
+ nX += pPortion->GetWidth();
+ }
+
+ TETextPortion* pDestPortion = pParaPortion->GetTextPortions()[ nTextPortion ];
+ if ( pDestPortion->GetKind() != PORTIONKIND_TAB )
+ {
+ if ( !IsRightToLeft() && pDestPortion->IsRightToLeft() )
+ {
+ // Portions behind must be added, visual before this portion
+ std::size_t nTmpPortion = nTextPortion+1;
+ while ( nTmpPortion <= pLine->GetEndPortion() )
+ {
+ TETextPortion* pNextTextPortion = pParaPortion->GetTextPortions()[ nTmpPortion ];
+ if ( pNextTextPortion->IsRightToLeft() && ( pNextTextPortion->GetKind() != PORTIONKIND_TAB ) )
+ nX += pNextTextPortion->GetWidth();
+ else
+ break;
+ nTmpPortion++;
+ }
+ // Portions before must be removed, visual behind this portion
+ nTmpPortion = nTextPortion;
+ while ( nTmpPortion > pLine->GetStartPortion() )
+ {
+ --nTmpPortion;
+ TETextPortion* pPrevTextPortion = pParaPortion->GetTextPortions()[ nTmpPortion ];
+ if ( pPrevTextPortion->IsRightToLeft() && ( pPrevTextPortion->GetKind() != PORTIONKIND_TAB ) )
+ nX -= pPrevTextPortion->GetWidth();
+ else
+ break;
+ }
+ }
+ else if ( IsRightToLeft() && !pDestPortion->IsRightToLeft() )
+ {
+ // Portions behind must be removed, visual behind this portion
+ std::size_t nTmpPortion = nTextPortion+1;
+ while ( nTmpPortion <= pLine->GetEndPortion() )
+ {
+ TETextPortion* pNextTextPortion = pParaPortion->GetTextPortions()[ nTmpPortion ];
+ if ( !pNextTextPortion->IsRightToLeft() && ( pNextTextPortion->GetKind() != PORTIONKIND_TAB ) )
+ nX += pNextTextPortion->GetWidth();
+ else
+ break;
+ nTmpPortion++;
+ }
+ // Portions before must be added, visual before this portion
+ nTmpPortion = nTextPortion;
+ while ( nTmpPortion > pLine->GetStartPortion() )
+ {
+ --nTmpPortion;
+ TETextPortion* pPrevTextPortion = pParaPortion->GetTextPortions()[ nTmpPortion ];
+ if ( !pPrevTextPortion->IsRightToLeft() && ( pPrevTextPortion->GetKind() != PORTIONKIND_TAB ) )
+ nX -= pPrevTextPortion->GetWidth();
+ else
+ break;
+ }
+ }
+ }
+
+ return nX;
+}
+
+void TextEngine::ImpInitLayoutMode( OutputDevice* pOutDev )
+{
+ ComplexTextLayoutFlags nLayoutMode = pOutDev->GetLayoutMode();
+
+ nLayoutMode &= ~ComplexTextLayoutFlags(ComplexTextLayoutFlags::BiDiRtl | 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;
+}
+
+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* pTextPortion = pPortion->GetTextPortions()[ nPortion ];
+
+ 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 -pTextPortion->GetWidth();
+ }
+ }
+ else
+ {
+ nX = ImpGetXPos( nPara, pLine, nIndex, nIndex == nPortionStart );
+ if ( nIndex2 != nIndex )
+ {
+ const long nX2 = ImpGetXPos( nPara, pLine, nIndex2 );
+ if ( ( !IsRightToLeft() && ( nX2 < nX ) ) ||
+ ( IsRightToLeft() && ( nX2 > nX ) ) )
+ {
+ nX = nX2;
+ }
+ }
+ if ( IsRightToLeft() )
+ {
+ nX = -nX;
+ }
+ }
+
+ return nX;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/edit/textund2.hxx b/vcl/source/edit/textund2.hxx
new file mode 100644
index 000000000..729a130ad
--- /dev/null
+++ b/vcl/source/edit/textund2.hxx
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#ifndef INCLUDED_VCL_SOURCE_EDIT_TEXTUND2_HXX
+#define INCLUDED_VCL_SOURCE_EDIT_TEXTUND2_HXX
+
+#include "textundo.hxx"
+#include <vcl/textdata.hxx>
+
+class TextUndoDelPara : public TextUndo
+{
+private:
+ bool mbDelObject;
+ sal_uInt32 mnPara;
+ TextNode* mpNode; // points at the valid not-destroyed object
+
+public:
+ TextUndoDelPara( TextEngine* pTextEngine, TextNode* pNode, sal_uInt32 nPara );
+ virtual ~TextUndoDelPara() override;
+
+ virtual void Undo() override;
+ virtual void Redo() override;
+
+ virtual OUString GetComment () const override;
+};
+
+class TextUndoConnectParas : public TextUndo
+{
+private:
+ sal_uInt32 mnPara;
+ sal_Int32 mnSepPos;
+
+public:
+ TextUndoConnectParas( TextEngine* pTextEngine, sal_uInt32 nPara, sal_Int32 nSepPos );
+ virtual ~TextUndoConnectParas() override;
+
+ virtual void Undo() override;
+ virtual void Redo() override;
+
+ virtual OUString GetComment () const override;
+};
+
+class TextUndoSplitPara : public TextUndo
+{
+private:
+ sal_uInt32 mnPara;
+ sal_Int32 mnSepPos;
+
+public:
+ TextUndoSplitPara( TextEngine* pTextEngine, sal_uInt32 nPara, sal_Int32 nSepPos );
+ virtual ~TextUndoSplitPara() override;
+
+ virtual void Undo() override;
+ virtual void Redo() override;
+
+ virtual OUString GetComment () const override;
+};
+
+class TextUndoInsertChars : public TextUndo
+{
+private:
+ TextPaM maTextPaM;
+ OUString maText;
+
+public:
+ TextUndoInsertChars( TextEngine* pTextEngine, const TextPaM& rTextPaM, const OUString& rStr );
+
+ virtual void Undo() override;
+ virtual void Redo() override;
+
+ virtual bool Merge( SfxUndoAction *pNextAction ) override;
+
+ virtual OUString GetComment () const override;
+};
+
+class TextUndoRemoveChars : public TextUndo
+{
+private:
+ TextPaM maTextPaM;
+ OUString maText;
+
+public:
+ TextUndoRemoveChars( TextEngine* pTextEngine, const TextPaM& rTextPaM, const OUString& rStr );
+
+ virtual void Undo() override;
+ virtual void Redo() override;
+
+ virtual OUString GetComment () const override;
+};
+
+#endif // INCLUDED_VCL_SOURCE_EDIT_TEXTUND2_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/edit/textundo.cxx b/vcl/source/edit/textundo.cxx
new file mode 100644
index 000000000..b910be112
--- /dev/null
+++ b/vcl/source/edit/textundo.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 "textundo.hxx"
+#include "textund2.hxx"
+#include <strings.hrc>
+
+#include <sal/log.hxx>
+#include <vcl/texteng.hxx>
+#include <vcl/textview.hxx>
+#include <vcl/textdata.hxx>
+#include "textdoc.hxx"
+#include "textdat2.hxx"
+#include <svdata.hxx>
+
+namespace
+{
+
+// Shorten() -- inserts ellipsis (...) in the middle of a long text
+void Shorten (OUString& rString)
+{
+ auto const nLen = rString.getLength();
+ if (nLen > 48)
+ {
+ // 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 =
+ rString.copy(0, iFirst + 1) +
+ "..." +
+ rString.copy(iLast);
+ }
+}
+
+} // namespace
+
+TextUndoManager::TextUndoManager( TextEngine* p )
+{
+ mpTextEngine = p;
+}
+
+TextUndoManager::~TextUndoManager()
+{
+}
+
+bool TextUndoManager::Undo()
+{
+ if ( GetUndoActionCount() == 0 )
+ return false;
+
+ UndoRedoStart();
+
+ mpTextEngine->SetIsInUndo( true );
+ bool bDone = SfxUndoManager::Undo();
+ mpTextEngine->SetIsInUndo( false );
+
+ UndoRedoEnd();
+
+ return bDone;
+}
+
+bool TextUndoManager::Redo()
+{
+ if ( GetRedoActionCount() == 0 )
+ return false;
+
+ UndoRedoStart();
+
+ mpTextEngine->SetIsInUndo( true );
+ bool bDone = SfxUndoManager::Redo();
+ mpTextEngine->SetIsInUndo( false );
+
+ UndoRedoEnd();
+
+ return bDone;
+}
+
+void TextUndoManager::UndoRedoStart()
+{
+ SAL_WARN_IF( !GetView(), "vcl", "Undo/Redo: Active View?" );
+}
+
+void TextUndoManager::UndoRedoEnd()
+{
+ if ( GetView() )
+ {
+ TextSelection aNewSel( GetView()->GetSelection() );
+ aNewSel.GetStart() = aNewSel.GetEnd();
+ GetView()->ImpSetSelection( aNewSel );
+ }
+
+ mpTextEngine->FormatAndUpdate( GetView() );
+}
+
+TextUndo::TextUndo( TextEngine* p )
+{
+ mpTextEngine = p;
+}
+
+TextUndo::~TextUndo()
+{
+}
+
+OUString TextUndo::GetComment() const
+{
+ return OUString();
+}
+
+void TextUndo::SetSelection( const TextSelection& rSel )
+{
+ if ( GetView() )
+ GetView()->ImpSetSelection( rSel );
+}
+
+TextUndoDelPara::TextUndoDelPara( TextEngine* pTextEngine, TextNode* pNode, sal_uInt32 nPara )
+ : TextUndo( pTextEngine )
+ , mbDelObject( true)
+ , mnPara( nPara )
+ , mpNode( pNode )
+{
+}
+
+TextUndoDelPara::~TextUndoDelPara()
+{
+ if ( mbDelObject )
+ delete mpNode;
+}
+
+void TextUndoDelPara::Undo()
+{
+ GetTextEngine()->InsertContent( std::unique_ptr<TextNode>(mpNode), mnPara );
+ mbDelObject = false; // belongs again to the engine
+
+ if ( GetView() )
+ {
+ TextSelection aSel( TextPaM( mnPara, 0 ), TextPaM( mnPara, mpNode->GetText().getLength() ) );
+ SetSelection( aSel );
+ }
+}
+
+void TextUndoDelPara::Redo()
+{
+ auto & rDocNodes = GetDoc()->GetNodes();
+ // pNode is not valid anymore in case an Undo joined paragraphs
+ mpNode = rDocNodes[ mnPara ].get();
+
+ GetTEParaPortions()->Remove( mnPara );
+
+ // do not delete Node because of Undo!
+ auto it = ::std::find_if( rDocNodes.begin(), rDocNodes.end(),
+ [&] (std::unique_ptr<TextNode> const & p) { return p.get() == mpNode; } );
+ assert(it != rDocNodes.end());
+ it->release();
+ GetDoc()->GetNodes().erase( it );
+
+ GetTextEngine()->ImpParagraphRemoved( mnPara );
+
+ mbDelObject = true; // belongs again to the Undo
+
+ const sal_uInt32 nParas = static_cast<sal_uInt32>(GetDoc()->GetNodes().size());
+ const sal_uInt32 n = mnPara < nParas ? mnPara : nParas-1;
+ TextNode* pN = GetDoc()->GetNodes()[ n ].get();
+ TextPaM aPaM( n, pN->GetText().getLength() );
+ SetSelection( aPaM );
+}
+
+OUString TextUndoDelPara::GetComment () const
+{
+ return VclResId(STR_TEXTUNDO_DELPARA);
+}
+
+TextUndoConnectParas::TextUndoConnectParas( TextEngine* pTextEngine, sal_uInt32 nPara, sal_Int32 nPos )
+ : TextUndo( pTextEngine )
+ , mnPara( nPara )
+ , mnSepPos( nPos )
+{
+}
+
+TextUndoConnectParas::~TextUndoConnectParas()
+{
+}
+
+void TextUndoConnectParas::Undo()
+{
+ TextPaM aPaM = GetTextEngine()->SplitContent( mnPara, mnSepPos );
+ SetSelection( aPaM );
+}
+
+void TextUndoConnectParas::Redo()
+{
+ TextPaM aPaM = GetTextEngine()->ConnectContents( mnPara );
+ SetSelection( aPaM );
+}
+
+OUString TextUndoConnectParas::GetComment () const
+{
+ return VclResId(STR_TEXTUNDO_CONNECTPARAS);
+}
+
+TextUndoSplitPara::TextUndoSplitPara( TextEngine* pTextEngine, sal_uInt32 nPara, sal_Int32 nPos )
+ : TextUndo( pTextEngine )
+ , mnPara( nPara )
+ , mnSepPos ( nPos )
+{
+}
+
+TextUndoSplitPara::~TextUndoSplitPara()
+{
+}
+
+void TextUndoSplitPara::Undo()
+{
+ TextPaM aPaM = GetTextEngine()->ConnectContents( mnPara );
+ SetSelection( aPaM );
+}
+
+void TextUndoSplitPara::Redo()
+{
+ TextPaM aPaM = GetTextEngine()->SplitContent( mnPara, mnSepPos );
+ SetSelection( aPaM );
+}
+
+OUString TextUndoSplitPara::GetComment () const
+{
+ return VclResId(STR_TEXTUNDO_SPLITPARA);
+}
+
+TextUndoInsertChars::TextUndoInsertChars( TextEngine* pTextEngine, const TextPaM& rTextPaM, const OUString& rStr )
+ : TextUndo( pTextEngine ),
+ maTextPaM( rTextPaM ), maText( rStr )
+{
+}
+
+void TextUndoInsertChars::Undo()
+{
+ TextSelection aSel( maTextPaM, maTextPaM );
+ aSel.GetEnd().GetIndex() += maText.getLength();
+ TextPaM aPaM = GetTextEngine()->ImpDeleteText( aSel );
+ SetSelection( aPaM );
+}
+
+void TextUndoInsertChars::Redo()
+{
+ TextSelection aSel( maTextPaM, maTextPaM );
+ GetTextEngine()->ImpInsertText( aSel, maText );
+ TextPaM aNewPaM( maTextPaM );
+ aNewPaM.GetIndex() += maText.getLength();
+ SetSelection( TextSelection( aSel.GetStart(), aNewPaM ) );
+}
+
+bool TextUndoInsertChars::Merge( SfxUndoAction* pNextAction )
+{
+ if ( nullptr == dynamic_cast< const TextUndoInsertChars*>( pNextAction ) )
+ return false;
+
+ TextUndoInsertChars* pNext = static_cast<TextUndoInsertChars*>(pNextAction);
+
+ if ( maTextPaM.GetPara() != pNext->maTextPaM.GetPara() )
+ return false;
+
+ if ( ( maTextPaM.GetIndex() + maText.getLength() ) == pNext->maTextPaM.GetIndex() )
+ {
+ maText += pNext->maText;
+ return true;
+ }
+ return false;
+}
+
+OUString TextUndoInsertChars::GetComment () const
+{
+ // multiple lines?
+ OUString sText(maText);
+ Shorten(sText);
+ return VclResId(STR_TEXTUNDO_INSERTCHARS).replaceAll("$1", sText);
+}
+
+TextUndoRemoveChars::TextUndoRemoveChars( TextEngine* pTextEngine, const TextPaM& rTextPaM, const OUString& rStr )
+ : TextUndo( pTextEngine ),
+ maTextPaM( rTextPaM ), maText( rStr )
+{
+}
+
+void TextUndoRemoveChars::Undo()
+{
+ TextSelection aSel( maTextPaM, maTextPaM );
+ GetTextEngine()->ImpInsertText( aSel, maText );
+ aSel.GetEnd().GetIndex() += maText.getLength();
+ SetSelection( aSel );
+}
+
+void TextUndoRemoveChars::Redo()
+{
+ TextSelection aSel( maTextPaM, maTextPaM );
+ aSel.GetEnd().GetIndex() += maText.getLength();
+ TextPaM aPaM = GetTextEngine()->ImpDeleteText( aSel );
+ SetSelection( aPaM );
+}
+
+OUString TextUndoRemoveChars::GetComment () const
+{
+ // multiple lines?
+ OUString sText(maText);
+ Shorten(sText);
+ return VclResId(STR_TEXTUNDO_REMOVECHARS).replaceAll("$1", sText);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/edit/textundo.hxx b/vcl/source/edit/textundo.hxx
new file mode 100644
index 000000000..0ec4a9aba
--- /dev/null
+++ b/vcl/source/edit/textundo.hxx
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#ifndef INCLUDED_VCL_SOURCE_EDIT_TEXTUNDO_HXX
+#define INCLUDED_VCL_SOURCE_EDIT_TEXTUNDO_HXX
+
+#include <svl/undo.hxx>
+#include <vcl/texteng.hxx>
+
+class TextEngine;
+class TextView;
+class TextSelection;
+class TextDoc;
+class TEParaPortions;
+
+class TextUndoManager : public SfxUndoManager
+{
+ TextEngine* mpTextEngine;
+
+protected:
+
+ void UndoRedoStart();
+ void UndoRedoEnd();
+
+ TextView* GetView() const { return mpTextEngine->GetActiveView(); }
+
+public:
+ explicit TextUndoManager( TextEngine* pTextEngine );
+ virtual ~TextUndoManager() override;
+
+ using SfxUndoManager::Undo;
+ virtual bool Undo() override;
+ using SfxUndoManager::Redo;
+ virtual bool Redo() override;
+
+};
+
+class TextUndo : public SfxUndoAction
+{
+private:
+ TextEngine* mpTextEngine;
+
+protected:
+
+ TextView* GetView() const { return mpTextEngine->GetActiveView(); }
+ void SetSelection( const TextSelection& rSel );
+
+ TextDoc* GetDoc() const { return mpTextEngine->mpDoc.get(); }
+ TEParaPortions* GetTEParaPortions() const { return mpTextEngine->mpTEParaPortions.get(); }
+
+public:
+ explicit TextUndo( TextEngine* pTextEngine );
+ virtual ~TextUndo() override;
+
+ TextEngine* GetTextEngine() const { return mpTextEngine; }
+
+ virtual void Undo() override = 0;
+ virtual void Redo() override = 0;
+
+ virtual OUString GetComment() const override;
+};
+
+#endif // INCLUDED_VCL_SOURCE_EDIT_TEXTUNDO_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/edit/textview.cxx b/vcl/source/edit/textview.cxx
new file mode 100644
index 000000000..3582e6742
--- /dev/null
+++ b/vcl/source/edit/textview.cxx
@@ -0,0 +1,2302 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <memory>
+#include <i18nutil/searchopt.hxx>
+#include <o3tl/deleter.hxx>
+#include <vcl/textview.hxx>
+#include <vcl/texteng.hxx>
+#include <vcl/settings.hxx>
+#include "textdoc.hxx"
+#include <vcl/textdata.hxx>
+#include <vcl/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/edit.hxx>
+
+#include <sot/exchange.hxx>
+
+#include <algorithm>
+#include <cstddef>
+
+TETextDataObject::TETextDataObject( const OUString& rText ) : maText( rText )
+{
+}
+
+// css::uno::XInterface
+css::uno::Any TETextDataObject::queryInterface( const css::uno::Type & rType )
+{
+ css::uno::Any aRet = ::cppu::queryInterface( rType, static_cast< css::datatransfer::XTransferable* >(this) );
+ return (aRet.hasValue() ? aRet : OWeakObject::queryInterface( rType ));
+}
+
+// css::datatransfer::XTransferable
+css::uno::Any TETextDataObject::getTransferData( const css::datatransfer::DataFlavor& rFlavor )
+{
+ css::uno::Any aAny;
+
+ SotClipboardFormatId nT = SotExchange::GetFormat( rFlavor );
+ if ( nT == SotClipboardFormatId::STRING )
+ {
+ aAny <<= maText;
+ }
+ else if ( nT == SotClipboardFormatId::HTML )
+ {
+ sal_uLong 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 mbHighlightSelection : 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->mbHighlightSelection = 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 ) );
+
+ if ( pWindow->GetSettings().GetStyleSettings().GetSelectionOptions() & SelectionOptions::Invert )
+ mpImpl->mbHighlightSelection = true;
+
+ pWindow->SetLineColor();
+
+ if ( pWindow->GetDragGestureRecognizer().is() )
+ {
+ vcl::unohelper::DragAndDropWrapper* pDnDWrapper = new vcl::unohelper::DragAndDropWrapper( this );
+ mpImpl->mxDnDListener = pDnDWrapper;
+
+ 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.SetTransparency(0);
+ 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->mbHighlightSelection && mpImpl->maSelection.HasRange())
+ pDrawSelection = &mpImpl->maSelection;
+
+ Point aStartPos = ImpGetOutputStartPos(mpImpl->maStartDocPos);
+ ImpPaint(rRenderContext, aStartPos, &rRect, pDrawSelection);
+ if (mpImpl->mbHighlightSelection)
+ ImpHighlight(mpImpl->maSelection);
+}
+
+void TextView::ImpHighlight( const TextSelection& rSel )
+{
+ TextSelection aSel( rSel );
+ aSel.Justify();
+ if ( aSel.HasRange() && !mpImpl->mpTextEngine->IsInUndo() && mpImpl->mpTextEngine->GetUpdateMode() )
+ {
+ mpImpl->mpCursor->Hide();
+
+ SAL_WARN_IF( mpImpl->mpTextEngine->mpIdleFormatter->IsActive(), "vcl", "ImpHighlight: Not formatted!" );
+
+ tools::Rectangle aVisArea( mpImpl->maStartDocPos, mpImpl->mpWindow->GetOutputSizePixel() );
+ long nY = 0;
+ const sal_uInt32 nStartPara = aSel.GetStart().GetPara();
+ const sal_uInt32 nEndPara = aSel.GetEnd().GetPara();
+ for ( sal_uInt32 nPara = 0; nPara <= nEndPara; ++nPara )
+ {
+ const long nParaHeight = mpImpl->mpTextEngine->CalcParaHeight( nPara );
+ if ( ( nPara >= nStartPara ) && ( ( nY + nParaHeight ) > aVisArea.Top() ) )
+ {
+ TEParaPortion* pTEParaPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( nPara );
+ std::vector<TextLine>::size_type nStartLine = 0;
+ std::vector<TextLine>::size_type nEndLine = pTEParaPortion->GetLines().size() -1;
+ if ( nPara == nStartPara )
+ nStartLine = pTEParaPortion->GetLineNumber( aSel.GetStart().GetIndex(), false );
+ if ( nPara == nEndPara )
+ nEndLine = pTEParaPortion->GetLineNumber( aSel.GetEnd().GetIndex(), true );
+
+ // iterate over all lines
+ for ( std::vector<TextLine>::size_type nLine = nStartLine; nLine <= nEndLine; nLine++ )
+ {
+ TextLine& rLine = pTEParaPortion->GetLines()[ nLine ];
+ sal_Int32 nStartIndex = rLine.GetStart();
+ sal_Int32 nEndIndex = rLine.GetEnd();
+ if ( ( nPara == nStartPara ) && ( nLine == nStartLine ) )
+ nStartIndex = aSel.GetStart().GetIndex();
+ if ( ( nPara == nEndPara ) && ( nLine == nEndLine ) )
+ nEndIndex = aSel.GetEnd().GetIndex();
+
+ // possible if at the beginning of a wrapped line
+ if ( nEndIndex < nStartIndex )
+ nEndIndex = nStartIndex;
+
+ tools::Rectangle aTmpRect( mpImpl->mpTextEngine->GetEditCursor( TextPaM( nPara, nStartIndex ), false ) );
+ aTmpRect.AdjustTop(nY );
+ aTmpRect.AdjustBottom(nY );
+ Point aTopLeft( aTmpRect.TopLeft() );
+
+ aTmpRect = mpImpl->mpTextEngine->GetEditCursor( TextPaM( nPara, nEndIndex ), true );
+ aTmpRect.AdjustTop(nY );
+ aTmpRect.AdjustBottom(nY );
+ Point aBottomRight( aTmpRect.BottomRight() );
+ aBottomRight.AdjustX( -1 );
+
+ // only paint if in the visible region
+ if ( ( aTopLeft.X() < aBottomRight.X() ) && ( aBottomRight.Y() >= aVisArea.Top() ) )
+ {
+ Point aPnt1( GetWindowPos( aTopLeft ) );
+ Point aPnt2( GetWindowPos( aBottomRight ) );
+
+ tools::Rectangle aRect( aPnt1, aPnt2 );
+ mpImpl->mpWindow->Invert( aRect );
+ }
+ }
+ }
+ nY += nParaHeight;
+
+ if ( nY >= aVisArea.Bottom() )
+ break;
+ }
+ }
+}
+
+void TextView::ImpSetSelection( const TextSelection& rSelection )
+{
+ if (rSelection != mpImpl->maSelection)
+ {
+ 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() )
+ {
+ if ( mpImpl->mbHighlightSelection )
+ {
+ ImpHighlight( *pRangeOrSelection );
+ }
+ else
+ {
+ 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(GetWindow()->GetPrimarySelection());
+ Copy( aSelection );
+ }
+ bMoved = true;
+ if ( nCode == KEY_END )
+ bEndKey = true;
+ }
+ else
+ bDone = false;
+ }
+ break;
+ case KEY_BACKSPACE:
+ case KEY_DELETE:
+ case css::awt::Key::DELETE_WORD_BACKWARD:
+ case css::awt::Key::DELETE_WORD_FORWARD:
+ case css::awt::Key::DELETE_TO_BEGIN_OF_LINE:
+ case css::awt::Key::DELETE_TO_END_OF_LINE:
+ {
+ if ( !mpImpl->mbReadOnly && !rKeyEvent.GetKeyCode().IsMod2() )
+ {
+ sal_uInt8 nDel = ( nCode == KEY_DELETE ) ? DEL_RIGHT : DEL_LEFT;
+ sal_uInt8 nMode = rKeyEvent.GetKeyCode().IsMod1() ? DELMODE_RESTOFWORD : DELMODE_SIMPLE;
+ if ( ( nMode == DELMODE_RESTOFWORD ) && rKeyEvent.GetKeyCode().IsShift() )
+ nMode = DELMODE_RESTOFCONTENT;
+
+ switch( nCode )
+ {
+ case css::awt::Key::DELETE_WORD_BACKWARD:
+ nDel = DEL_LEFT;
+ nMode = DELMODE_RESTOFWORD;
+ break;
+ case css::awt::Key::DELETE_WORD_FORWARD:
+ nDel = DEL_RIGHT;
+ nMode = DELMODE_RESTOFWORD;
+ break;
+ case css::awt::Key::DELETE_TO_BEGIN_OF_LINE:
+ nDel = DEL_LEFT;
+ nMode = DELMODE_RESTOFCONTENT;
+ break;
+ case css::awt::Key::DELETE_TO_END_OF_LINE:
+ nDel = DEL_RIGHT;
+ nMode = DELMODE_RESTOFCONTENT;
+ break;
+ default: break;
+ }
+
+ mpImpl->mpTextEngine->UndoActionStart();
+ aCurSel = ImpDelete( nDel, nMode );
+ mpImpl->mpTextEngine->UndoActionEnd();
+ bModified = true;
+ bAllowIdle = false;
+ }
+ else
+ bDone = false;
+ }
+ break;
+ case KEY_TAB:
+ {
+ if ( !mpImpl->mbReadOnly && !rKeyEvent.GetKeyCode().IsShift() &&
+ !rKeyEvent.GetKeyCode().IsMod1() && !rKeyEvent.GetKeyCode().IsMod2() &&
+ ImplCheckTextLen( OUString('x') ) )
+ {
+ aCurSel = mpImpl->mpTextEngine->ImpInsertText( aCurSel, '\t', !IsInsertMode() );
+ bModified = true;
+ }
+ else
+ bDone = false;
+ }
+ break;
+ case KEY_RETURN:
+ {
+ // do not swallow Shift-RETURN, as this would disable multi-line entries
+ // in dialogs & property editors
+ if ( !mpImpl->mbReadOnly && !rKeyEvent.GetKeyCode().IsMod1() &&
+ !rKeyEvent.GetKeyCode().IsMod2() && ImplCheckTextLen( OUString('x') ) )
+ {
+ mpImpl->mpTextEngine->UndoActionStart();
+ aCurSel = mpImpl->mpTextEngine->ImpInsertParaBreak( aCurSel );
+ if ( mpImpl->mbAutoIndent )
+ {
+ TextNode* pPrev = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aCurSel.GetEnd().GetPara() - 1 ].get();
+ sal_Int32 n = 0;
+ while ( ( n < pPrev->GetText().getLength() ) && (
+ ( pPrev->GetText()[ n ] == ' ' ) ||
+ ( pPrev->GetText()[ n ] == '\t' ) ) )
+ {
+ n++;
+ }
+ if ( n )
+ aCurSel = mpImpl->mpTextEngine->ImpInsertText( aCurSel, pPrev->GetText().copy( 0, n ) );
+ }
+ mpImpl->mpTextEngine->UndoActionEnd();
+ bModified = true;
+ }
+ else
+ bDone = false;
+ }
+ break;
+ case KEY_INSERT:
+ {
+ if ( !mpImpl->mbReadOnly )
+ SetInsertMode( !IsInsertMode() );
+ }
+ break;
+ default:
+ {
+ if ( TextEngine::IsSimpleCharInput( rKeyEvent ) )
+ {
+ sal_Unicode nCharCode = rKeyEvent.GetCharCode();
+ if ( !mpImpl->mbReadOnly && ImplCheckTextLen( OUString(nCharCode) ) ) // otherwise swallow the character anyway
+ {
+ aCurSel = mpImpl->mpTextEngine->ImpInsertText( nCharCode, aCurSel, !IsInsertMode(), true );
+ bModified = true;
+ }
+ }
+ else
+ bDone = false;
+ }
+ }
+ }
+
+ if ( aCurSel != aOldSel ) // Check if changed, maybe other method already changed mpImpl->maSelection, don't overwrite that!
+ ImpSetSelection( aCurSel );
+
+ if ( ( nCode != KEY_UP ) && ( nCode != KEY_DOWN ) )
+ mpImpl->mnTravelXPos = TRAVEL_X_DONTKNOW;
+
+ if ( bModified )
+ {
+ // Idle-Formatter only if AnyInput
+ if ( bAllowIdle && Application::AnyInput( VclInputFlags::KEYBOARD) )
+ mpImpl->mpTextEngine->IdleFormatAndUpdate( this );
+ else
+ mpImpl->mpTextEngine->FormatAndUpdate( this);
+ }
+ else if ( bMoved )
+ {
+ // selection is painted now in ImpMoveCursor
+ ImpShowCursor( mpImpl->mbAutoScroll, true, bEndKey );
+ }
+
+ if ( mpImpl->mpTextEngine->IsModified() )
+ mpImpl->mpTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
+ else if ( bWasModified )
+ mpImpl->mpTextEngine->SetModified( true );
+
+ return bDone;
+}
+
+void TextView::MouseButtonUp( const MouseEvent& rMouseEvent )
+{
+ mpImpl->mbClickedInSelection = false;
+ mpImpl->mnTravelXPos = TRAVEL_X_DONTKNOW;
+ mpImpl->mpSelEngine->SelMouseButtonUp( rMouseEvent );
+ if ( rMouseEvent.IsMiddle() && !IsReadOnly() &&
+ ( GetWindow()->GetSettings().GetMouseSettings().GetMiddleButtonAction() == MouseMiddleButtonAction::PasteSelection ) )
+ {
+ css::uno::Reference<css::datatransfer::clipboard::XClipboard> aSelection(GetWindow()->GetPrimarySelection());
+ 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(GetWindow()->GetPrimarySelection());
+ 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 ) )
+ {
+ 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 ) );
+
+ 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( long ndX, 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 );
+
+ long nDiffX = mpImpl->maStartDocPos.X() - aNewStartPos.X();
+ 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() )
+ {
+ 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() )
+ {
+ css::uno::Reference< css::datatransfer::XTransferable > xDataObj;
+
+ try
+ {
+ SolarMutexReleaser aReleaser;
+ xDataObj = rxClipboard->getContents();
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+
+ if ( xDataObj.is() )
+ {
+ css::datatransfer::DataFlavor aFlavor;
+ SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor );
+ if ( xDataObj->isDataFlavorSupported( aFlavor ) )
+ {
+ 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()
+{
+ return GetSelected( GetSystemLineEnd() );
+}
+
+OUString TextView::GetSelected( LineEnd aSeparator )
+{
+ 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: aPaM = bCtrl ? CursorStartOfDoc() : CursorStartOfLine( aPaM );
+ 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::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 );
+
+ 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 );
+
+ 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 );
+ 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* pTextPortion = pParaPortion->GetTextPortions()[ nTextPortion ];
+ if ( pTextPortion->GetKind() == PORTIONKIND_TAB )
+ {
+ aEditCursor.AdjustRight(pTextPortion->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()
+ )
+ {
+ long nVisStartY = mpImpl->maStartDocPos.Y();
+ long nVisEndY = mpImpl->maStartDocPos.Y() + aOutSz.Height();
+ long nVisStartX = mpImpl->maStartDocPos.X();
+ long nVisEndX = mpImpl->maStartDocPos.X() + aOutSz.Width();
+ 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();
+ 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
+ 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() )
+ {
+ 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 )
+{
+ TextSelection aSel = mpImpl->maSelection;
+ aSel.Justify();
+
+ const sal_uInt32 nStartNode = aSel.GetStart().GetPara();
+ const sal_uInt32 nEndNode = aSel.GetEnd().GetPara();
+ const sal_uInt32 nCurNode = rPaM.GetPara();
+
+ if ( ( nCurNode > nStartNode ) && ( nCurNode < nEndNode ) )
+ return true;
+
+ if ( nStartNode == nEndNode )
+ {
+ if ( nCurNode == nStartNode )
+ if ( ( rPaM.GetIndex() >= aSel.GetStart().GetIndex() ) && ( rPaM.GetIndex() < aSel.GetEnd().GetIndex() ) )
+ return true;
+ }
+ else if ( ( nCurNode == nStartNode ) && ( rPaM.GetIndex() >= aSel.GetStart().GetIndex() ) )
+ return true;
+ else if ( ( nCurNode == nEndNode ) && ( rPaM.GetIndex() < aSel.GetEnd().GetIndex() ) )
+ return true;
+
+ return false;
+}
+
+void TextView::ImpHideDDCursor()
+{
+ if ( mpImpl->mpDDInfo && mpImpl->mpDDInfo->mbVisCursor )
+ {
+ mpImpl->mpDDInfo->maCursor.Hide();
+ mpImpl->mpDDInfo->mbVisCursor = false;
+ }
+}
+
+void TextView::ImpShowDDCursor()
+{
+ if ( !mpImpl->mpDDInfo->mbVisCursor )
+ {
+ tools::Rectangle aCursor = mpImpl->mpTextEngine->PaMtoEditCursor( mpImpl->mpDDInfo->maDropPos, true );
+ aCursor.AdjustRight( 1 );
+ aCursor.SetPos( GetWindowPos( aCursor.TopLeft() ) );
+
+ mpImpl->mpDDInfo->maCursor.SetWindow( mpImpl->mpWindow );
+ mpImpl->mpDDInfo->maCursor.SetPos( aCursor.TopLeft() );
+ mpImpl->mpDDInfo->maCursor.SetSize( aCursor.GetSize() );
+ mpImpl->mpDDInfo->maCursor.Show();
+ mpImpl->mpDDInfo->mbVisCursor = true;
+ }
+}
+
+void TextView::SetPaintSelection( bool bPaint )
+{
+ if ( bPaint != mpImpl->mbPaintSelection )
+ {
+ mpImpl->mbPaintSelection = bPaint;
+ ShowSelection( mpImpl->maSelection );
+ }
+}
+
+void TextView::Read( SvStream& rInput )
+{
+ mpImpl->mpTextEngine->Read( rInput, &mpImpl->maSelection );
+ ShowCursor();
+}
+
+bool TextView::ImplTruncateNewText( OUString& rNewText ) const
+{
+ bool bTruncated = false;
+
+ const sal_Int32 nMaxLen = mpImpl->mpTextEngine->GetMaxTextLen();
+ // 0 means unlimited
+ if( nMaxLen != 0 )
+ {
+ const sal_Int32 nCurLen = mpImpl->mpTextEngine->GetTextLen();
+
+ const sal_Int32 nNewLen = rNewText.getLength();
+ if ( nCurLen + nNewLen > nMaxLen )
+ {
+ // see how much text will be replaced
+ const sal_Int32 nSelLen = mpImpl->mpTextEngine->GetTextLen( mpImpl->maSelection );
+ if ( nCurLen + nNewLen - nSelLen > nMaxLen )
+ {
+ const sal_Int32 nTruncatedLen = nMaxLen - (nCurLen - nSelLen);
+ rNewText = rNewText.copy( 0, nTruncatedLen );
+ bTruncated = true;
+ }
+ }
+ }
+ return bTruncated;
+}
+
+bool TextView::ImplCheckTextLen( const OUString& rNewText )
+{
+ bool bOK = true;
+ if ( mpImpl->mpTextEngine->GetMaxTextLen() )
+ {
+ sal_Int32 n = mpImpl->mpTextEngine->GetTextLen() + rNewText.getLength();
+ if ( n > mpImpl->mpTextEngine->GetMaxTextLen() )
+ {
+ // calculate how much text is being deleted
+ n -= mpImpl->mpTextEngine->GetTextLen( mpImpl->maSelection );
+ if ( n > mpImpl->mpTextEngine->GetMaxTextLen() )
+ bOK = false;
+ }
+ }
+ return bOK;
+}
+
+void TextView::dragGestureRecognized( const css::datatransfer::dnd::DragGestureEvent& rDGE )
+{
+ if ( mpImpl->mbClickedInSelection )
+ {
+ 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;
+
+ 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();
+ long nVisStartY = mpImpl->maStartDocPos.Y();
+ long nVisEndY = mpImpl->maStartDocPos.Y() + aOutSz.Height();
+
+ // Retrieve the coordinates of the PaM
+ tools::Rectangle aRect = mpImpl->mpTextEngine->PaMtoEditCursor(rPaM);
+
+ // Recalculate the offset of the center y-coordinates and scroll
+ Scroll(0, (nVisStartY + nVisEndY) / 2 - aRect.TopLeft().getY());
+}
+
+bool TextView::Search( const i18nutil::SearchOptions& rSearchOptions, bool bForward )
+{
+ bool bFound = false;
+ TextSelection aSel( GetSelection() );
+ if ( static_cast<ExtTextEngine*>(GetTextEngine())->Search( aSel, rSearchOptions, bForward ) )
+ {
+ bFound = true;
+ // First add the beginning of the word to the selection,
+ // so that the whole word is in the visible region.
+ SetSelection( aSel.GetStart() );
+ ShowCursor( true, false );
+ }
+ else
+ {
+ aSel = GetSelection().GetEnd();
+ }
+
+ SetSelection( aSel );
+ // tdf#49482: Move the start of the selection to the center of the textview
+ if (bFound)
+ {
+ CenterPaM( aSel.GetStart() );
+ }
+ ShowCursor();
+
+ return bFound;
+}
+
+sal_uInt16 TextView::Replace( const i18nutil::SearchOptions& rSearchOptions, bool bAll, bool bForward )
+{
+ sal_uInt16 nFound = 0;
+
+ if ( !bAll )
+ {
+ if ( GetSelection().HasRange() )
+ {
+ InsertText( rSearchOptions.replaceString );
+ nFound = 1;
+ Search( rSearchOptions, bForward ); // right away to the next
+ }
+ else
+ {
+ if( Search( rSearchOptions, bForward ) )
+ nFound = 1;
+ }
+ }
+ else
+ {
+ // the writer replaces all, from beginning to end
+
+ ExtTextEngine* pTextEngine = static_cast<ExtTextEngine*>(GetTextEngine());
+
+ // HideSelection();
+ TextSelection aSel;
+
+ bool bSearchInSelection = (0 != (rSearchOptions.searchFlag & css::util::SearchFlags::REG_NOT_BEGINOFLINE) );
+ if ( bSearchInSelection )
+ {
+ aSel = GetSelection();
+ aSel.Justify();
+ }
+
+ TextSelection aSearchSel( aSel );
+
+ bool bFound = pTextEngine->Search( aSel, rSearchOptions );
+ if ( bFound )
+ pTextEngine->UndoActionStart();
+ while ( bFound )
+ {
+ nFound++;
+
+ TextPaM aNewStart = pTextEngine->ImpInsertText( aSel, rSearchOptions.replaceString );
+ // tdf#64690 - extend selection to include inserted text portions
+ if ( aSel.GetEnd().GetPara() == aSearchSel.GetEnd().GetPara() )
+ {
+ aSearchSel.GetEnd().GetIndex() += rSearchOptions.replaceString.getLength() - 1;
+ }
+ aSel = aSearchSel;
+ aSel.GetStart() = aNewStart;
+ bFound = pTextEngine->Search( aSel, rSearchOptions );
+ }
+ if ( nFound )
+ {
+ SetSelection( aSel.GetStart() );
+ pTextEngine->FormatAndUpdate( this );
+ pTextEngine->UndoActionEnd();
+ }
+ }
+ return nFound;
+}
+
+bool TextView::ImpIndentBlock( bool bRight )
+{
+ bool bDone = false;
+
+ TextSelection aSel = GetSelection();
+ aSel.Justify();
+
+ HideSelection();
+ GetTextEngine()->UndoActionStart();
+
+ const sal_uInt32 nStartPara = aSel.GetStart().GetPara();
+ sal_uInt32 nEndPara = aSel.GetEnd().GetPara();
+ if ( aSel.HasRange() && !aSel.GetEnd().GetIndex() )
+ {
+ nEndPara--; // do not indent
+ }
+
+ for ( sal_uInt32 nPara = nStartPara; nPara <= nEndPara; ++nPara )
+ {
+ if ( bRight )
+ {
+ // add tabs
+ GetTextEngine()->ImpInsertText( TextPaM( nPara, 0 ), '\t' );
+ bDone = true;
+ }
+ else
+ {
+ // remove Tabs/Blanks
+ OUString aText = GetTextEngine()->GetText( nPara );
+ if ( !aText.isEmpty() && (
+ ( aText[ 0 ] == '\t' ) ||
+ ( aText[ 0 ] == ' ' ) ) )
+ {
+ GetTextEngine()->ImpDeleteText( TextSelection( TextPaM( nPara, 0 ), TextPaM( nPara, 1 ) ) );
+ bDone = true;
+ }
+ }
+ }
+
+ GetTextEngine()->UndoActionEnd();
+
+ bool bRange = aSel.HasRange();
+ if ( bRight )
+ {
+ ++aSel.GetStart().GetIndex();
+ if ( bRange && ( aSel.GetEnd().GetPara() == nEndPara ) )
+ ++aSel.GetEnd().GetIndex();
+ }
+ else
+ {
+ if ( aSel.GetStart().GetIndex() )
+ --aSel.GetStart().GetIndex();
+ if ( bRange && aSel.GetEnd().GetIndex() )
+ --aSel.GetEnd().GetIndex();
+ }
+
+ ImpSetSelection( aSel );
+ GetTextEngine()->FormatAndUpdate( this );
+
+ return bDone;
+}
+
+bool TextView::IndentBlock()
+{
+ return ImpIndentBlock( true );
+}
+
+bool TextView::UnindentBlock()
+{
+ return ImpIndentBlock( false );
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/edit/txtattr.cxx b/vcl/source/edit/txtattr.cxx
new file mode 100644
index 000000000..8e979c1e3
--- /dev/null
+++ b/vcl/source/edit/txtattr.cxx
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <vcl/txtattr.hxx>
+#include <vcl/font.hxx>
+
+TextAttrib::~TextAttrib()
+{
+}
+
+bool TextAttrib::operator==( const TextAttrib& rAttr ) const
+{
+ return mnWhich == rAttr.mnWhich;
+}
+
+TextAttribFontColor::TextAttribFontColor( const Color& rColor )
+ : TextAttrib( TEXTATTR_FONTCOLOR ), maColor( rColor )
+{
+}
+
+void TextAttribFontColor::SetFont( vcl::Font& rFont ) const
+{
+ rFont.SetColor( maColor );
+}
+
+std::unique_ptr<TextAttrib> TextAttribFontColor::Clone() const
+{
+ return std::unique_ptr<TextAttrib>(new TextAttribFontColor( *this ));
+}
+
+bool TextAttribFontColor::operator==( const TextAttrib& rAttr ) const
+{
+ return ( ( TextAttrib::operator==(rAttr ) ) &&
+ ( maColor == static_cast<const TextAttribFontColor&>(rAttr).maColor ) );
+}
+
+TextAttribFontWeight::TextAttribFontWeight( FontWeight eWeight )
+ : TextAttrib( TEXTATTR_FONTWEIGHT ), meWeight( eWeight )
+{
+}
+
+void TextAttribFontWeight::SetFont( vcl::Font& rFont ) const
+{
+ rFont.SetWeight( meWeight );
+}
+
+std::unique_ptr<TextAttrib> TextAttribFontWeight::Clone() const
+{
+ return std::unique_ptr<TextAttrib>(new TextAttribFontWeight( *this ));
+}
+
+bool TextAttribFontWeight::operator==( const TextAttrib& rAttr ) const
+{
+ return ( ( TextAttrib::operator==(rAttr ) ) &&
+ ( meWeight == static_cast<const TextAttribFontWeight&>(rAttr).meWeight ) );
+}
+
+TextAttribProtect::TextAttribProtect() :
+ TextAttrib( TEXTATTR_PROTECTED )
+{
+}
+
+void TextAttribProtect::SetFont( vcl::Font& ) const
+{
+}
+
+std::unique_ptr<TextAttrib> TextAttribProtect::Clone() const
+{
+ return std::unique_ptr<TextAttrib>(new TextAttribProtect());
+}
+
+bool TextAttribProtect::operator==( const TextAttrib& rAttr ) const
+{
+ return TextAttrib::operator==(rAttr );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/edit/vclmedit.cxx b/vcl/source/edit/vclmedit.cxx
new file mode 100644
index 000000000..021ef7658
--- /dev/null
+++ b/vcl/source/edit/vclmedit.cxx
@@ -0,0 +1,1539 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/specialchars.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/vclmedit.hxx>
+#include <vcl/xtextedt.hxx>
+#include <vcl/textview.hxx>
+#include <vcl/ptrstyle.hxx>
+
+#include <svl/undo.hxx>
+#include <svl/lstner.hxx>
+#include <vcl/uitest/uiobject.hxx>
+
+#include <vcl/scrbar.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/weld.hxx>
+#include <osl/diagnose.h>
+
+class ImpVclMEdit : public SfxListener
+{
+private:
+ VclPtr<VclMultiLineEdit> pVclMultiLineEdit;
+
+ VclPtr<TextWindow> mpTextWindow;
+ VclPtr<ScrollBar> mpHScrollBar;
+ VclPtr<ScrollBar> mpVScrollBar;
+ VclPtr<ScrollBarBox> mpScrollBox;
+
+ long mnTextWidth;
+ mutable Selection maSelection;
+
+protected:
+ virtual void Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) override;
+ void ImpUpdateSrollBarVis( 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 );
+ bool IsModified() const;
+
+ void SetReadOnly( bool bRdOnly );
+ bool IsReadOnly() const;
+
+ void SetMaxTextLen(sal_Int32 nLen);
+ sal_Int32 GetMaxTextLen() const;
+
+ void SetMaxTextWidth(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::ImpUpdateSrollBarVis( 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() );
+ 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 )
+{
+ ImpUpdateSrollBarVis( 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 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()
+{
+ 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 )
+{
+ 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 );
+}
+
+bool ImpVclMEdit::IsModified() const
+{
+ return mpTextWindow->GetTextEngine()->IsModified();
+}
+
+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(long nMaxWidth)
+{
+ mpTextWindow->GetTextEngine()->SetMaxTextWidth(nMaxWidth);
+}
+
+void ImpVclMEdit::Resize()
+{
+ int nIteration = 1;
+ do
+ {
+ WinBits nWinStyle( pVclMultiLineEdit->GetStyle() );
+ if ( ( nWinStyle & WB_AUTOVSCROLL ) == WB_AUTOVSCROLL )
+ ImpUpdateSrollBarVis( nWinStyle );
+
+ Size aSz = pVclMultiLineEdit->GetOutputSizePixel();
+ Size aEditSize = aSz;
+ 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 )
+ ImpUpdateSrollBarVis( 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() )
+ {
+ long nOutHeight = mpTextWindow->GetOutputSizePixel().Height();
+ 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 long nWidth = mpTextWindow->GetTextEngine()->CalcTextWidth();
+ if ( nWidth != mnTextWidth )
+ {
+ mnTextWidth = nWidth;
+ mpHScrollBar->SetRange( Range( 0, mnTextWidth-1 ) );
+ ImpSetHScrollBarThumbPos();
+ }
+ }
+ break;
+
+ case SfxHintId::TextModified:
+ ImpUpdateSrollBarVis(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();
+
+ long nEnd = std::max( aNewSelection.Min(), aNewSelection.Max() );
+ TextSelection aTextSel;
+ sal_uInt32 nPara = 0;
+ sal_Int32 nChar = 0;
+ long x = 0;
+ while ( x <= nEnd )
+ {
+ if ( x == aNewSelection.Min() )
+ aTextSel.GetStart() = TextPaM( nPara, nChar );
+ if ( x == aNewSelection.Max() )
+ aTextSel.GetEnd() = TextPaM( nPara, nChar );
+
+ if ( ( x < aText.getLength() ) && ( aText[ x ] == '\n' ) )
+ {
+ nPara++;
+ nChar = 0;
+ }
+ else
+ nChar++;
+ x++;
+ }
+ mpTextWindow->GetTextView()->SetSelection( aTextSel );
+}
+
+const Selection& ImpVclMEdit::GetSelection() const
+{
+ maSelection = Selection();
+ TextSelection aTextSel( mpTextWindow->GetTextView()->GetSelection() );
+ aTextSel.Justify();
+ // flatten selection => every line-break a character
+
+ ExtTextEngine* pExtTextEngine = mpTextWindow->GetTextEngine();
+ // paragraphs before
+ for ( sal_uInt32 n = 0; n < aTextSel.GetStart().GetPara(); ++n )
+ {
+ maSelection.Min() += pExtTextEngine->GetTextLen( n );
+ maSelection.Min()++;
+ }
+
+ // first paragraph with selection
+ maSelection.Max() = maSelection.Min();
+ maSelection.Min() += aTextSel.GetStart().GetIndex();
+
+ for ( sal_uInt32 n = aTextSel.GetStart().GetPara(); n < aTextSel.GetEnd().GetPara(); ++n )
+ {
+ maSelection.Max() += pExtTextEngine->GetTextLen( n );
+ maSelection.Max()++;
+ }
+
+ maSelection.Max() += aTextSel.GetEnd().GetIndex();
+
+ return maSelection;
+}
+
+Size ImpVclMEdit::CalcMinimumSize() const
+{
+ Size aSz( mpTextWindow->GetTextEngine()->CalcTextWidth(),
+ mpTextWindow->GetTextEngine()->GetTextHeight() );
+
+ if (mpHScrollBar->IsVisible())
+ aSz.AdjustHeight(mpHScrollBar->GetSizePixel().Height() );
+ if (mpVScrollBar->IsVisible())
+ aSz.AdjustWidth(mpVScrollBar->GetSizePixel().Width() );
+
+ return aSz;
+}
+
+Size ImpVclMEdit::CalcBlockSize( sal_uInt16 nColumns, sal_uInt16 nLines ) const
+{
+ static const sal_Unicode sampleChar = 'X';
+
+ Size aSz;
+ Size aCharSz;
+ aCharSz.setWidth( mpTextWindow->GetTextWidth( OUString(sampleChar) ) );
+ aCharSz.setHeight( mpTextWindow->GetTextHeight() );
+
+ if ( nLines )
+ aSz.setHeight( nLines*aCharSz.Height() );
+ else
+ aSz.setHeight( mpTextWindow->GetTextEngine()->GetTextHeight() );
+
+ if ( nColumns )
+ aSz.setWidth( nColumns*aCharSz.Width() );
+ else
+ aSz.setWidth( mpTextWindow->GetTextEngine()->CalcTextWidth() );
+
+ if (mpHScrollBar->IsVisible())
+ aSz.AdjustHeight(mpHScrollBar->GetSizePixel().Height() );
+ if (mpVScrollBar->IsVisible())
+ aSz.AdjustWidth(mpVScrollBar->GetSizePixel().Width() );
+
+ return aSz;
+}
+
+void ImpVclMEdit::GetMaxVisColumnsAndLines( sal_uInt16& rnCols, sal_uInt16& rnLines ) const
+{
+ static const sal_Unicode sampleChar = { 'x' };
+ Size aOutSz = mpTextWindow->GetOutputSizePixel();
+ Size aCharSz( mpTextWindow->GetTextWidth( OUString(sampleChar) ), mpTextWindow->GetTextHeight() );
+ rnCols = static_cast<sal_uInt16>(aOutSz.Width()/aCharSz.Width());
+ rnLines = static_cast<sal_uInt16>(aOutSz.Height()/aCharSz.Height());
+}
+
+void ImpVclMEdit::Enable( bool bEnable )
+{
+ mpTextWindow->Enable( bEnable );
+ if (mpHScrollBar->IsVisible())
+ mpHScrollBar->Enable( bEnable );
+ if (mpVScrollBar->IsVisible())
+ mpVScrollBar->Enable( bEnable );
+}
+
+bool ImpVclMEdit::HandleCommand( const CommandEvent& rCEvt )
+{
+ bool bDone = false;
+ CommandEventId nCommand = rCEvt.GetCommand();
+ if (nCommand == CommandEventId::Wheel ||
+ nCommand == CommandEventId::StartAutoScroll ||
+ nCommand == CommandEventId::AutoScroll ||
+ nCommand == CommandEventId::Gesture)
+ {
+ ScrollBar* pHScrollBar = mpHScrollBar->IsVisible() ? mpHScrollBar.get() : nullptr;
+ ScrollBar* pVScrollBar = mpVScrollBar->IsVisible() ? mpVScrollBar.get() : nullptr;
+ mpTextWindow->HandleScrollCommand(rCEvt, pHScrollBar, pVScrollBar);
+ bDone = true;
+ }
+ return bDone;
+}
+
+TextWindow::TextWindow(Edit* pParent)
+ : Window(pParent)
+ , mxParent(pParent)
+{
+ mbInMBDown = false;
+ mbFocusSelectionHide = false;
+ mbIgnoreTab = false;
+ mbActivePopup = false;
+ mbSelectOnTab = true;
+
+ SetPointer( PointerStyle::Text );
+
+ mpExtTextEngine.reset(new ExtTextEngine);
+ mpExtTextEngine->SetMaxTextLen(EDIT_NOLIMIT);
+ if( pParent->GetStyle() & WB_BORDER )
+ mpExtTextEngine->SetLeftMargin( 2 );
+ mpExtTextEngine->SetLocale( GetSettings().GetLanguageTag().getLocale() );
+ mpExtTextView.reset(new TextView( mpExtTextEngine.get(), this ));
+ mpExtTextEngine->InsertView( mpExtTextView.get() );
+ mpExtTextEngine->EnableUndo( true );
+ mpExtTextView->ShowCursor();
+
+ Color aBackgroundColor = GetSettings().GetStyleSettings().GetWorkspaceColor();
+ SetBackground( aBackgroundColor );
+ pParent->SetBackground( aBackgroundColor );
+}
+
+TextWindow::~TextWindow()
+{
+ disposeOnce();
+}
+
+void TextWindow::dispose()
+{
+ mxParent.clear();
+ mpExtTextView.reset();
+ mpExtTextEngine.reset();
+ Window::dispose();
+}
+
+void TextWindow::MouseMove( const MouseEvent& rMEvt )
+{
+ mpExtTextView->MouseMove( rMEvt );
+ Window::MouseMove( rMEvt );
+}
+
+void TextWindow::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ mbInMBDown = true; // so that GetFocus does not select everything
+ mpExtTextView->MouseButtonDown( rMEvt );
+ GrabFocus();
+ mbInMBDown = false;
+}
+
+void TextWindow::MouseButtonUp( const MouseEvent& rMEvt )
+{
+ mpExtTextView->MouseButtonUp( rMEvt );
+}
+
+void TextWindow::KeyInput( const KeyEvent& rKEvent )
+{
+ bool bDone = false;
+ sal_uInt16 nCode = rKEvent.GetKeyCode().GetCode();
+ if ( nCode == css::awt::Key::SELECT_ALL ||
+ ( (nCode == KEY_A) && rKEvent.GetKeyCode().IsMod1() && !rKEvent.GetKeyCode().IsMod2() )
+ )
+ {
+ mpExtTextView->SetSelection( TextSelection( TextPaM( 0, 0 ), TextPaM( TEXT_PARA_ALL, TEXT_INDEX_ALL ) ) );
+ bDone = true;
+ }
+ else if ( (nCode == KEY_S) && rKEvent.GetKeyCode().IsShift() && rKEvent.GetKeyCode().IsMod1() )
+ {
+ if ( vcl::GetGetSpecialCharsFunction() )
+ {
+ // to maintain the selection
+ mbActivePopup = true;
+ OUString aChars = vcl::GetGetSpecialCharsFunction()(GetFrameWeld(), GetFont());
+ if (!aChars.isEmpty())
+ {
+ mpExtTextView->InsertText( aChars );
+ mpExtTextView->GetTextEngine()->SetModified( true );
+ }
+ mbActivePopup = false;
+ bDone = true;
+ }
+ }
+ else if ( nCode == KEY_TAB )
+ {
+ if ( !mbIgnoreTab || rKEvent.GetKeyCode().IsMod1() )
+ bDone = mpExtTextView->KeyInput( rKEvent );
+ }
+ else
+ {
+ bDone = mpExtTextView->KeyInput( rKEvent );
+ }
+
+ if ( !bDone )
+ Window::KeyInput( rKEvent );
+}
+
+void TextWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ mpExtTextView->Paint(rRenderContext, rRect);
+}
+
+void TextWindow::Resize()
+{
+}
+
+void TextWindow::Command( const CommandEvent& rCEvt )
+{
+ if ( rCEvt.GetCommand() == CommandEventId::ContextMenu )
+ {
+ VclPtr<PopupMenu> pPopup = mxParent->CreatePopupMenu();
+ bool bEnableCut = true;
+ bool bEnableCopy = true;
+ bool bEnableDelete = true;
+ bool bEnablePaste = true;
+ bool bEnableSpecialChar = true;
+ bool bEnableUndo = true;
+
+ if ( !mpExtTextView->HasSelection() )
+ {
+ bEnableCut = false;
+ bEnableCopy = false;
+ bEnableDelete = false;
+ }
+ if ( mpExtTextView->IsReadOnly() )
+ {
+ bEnableCut = false;
+ bEnablePaste = false;
+ bEnableDelete = false;
+ bEnableSpecialChar = false;
+ }
+ if ( !mpExtTextView->GetTextEngine()->HasUndoManager() || !mpExtTextView->GetTextEngine()->GetUndoManager().GetUndoActionCount() )
+ {
+ bEnableUndo = false;
+ }
+ pPopup->EnableItem(pPopup->GetItemId("cut"), bEnableCut);
+ pPopup->EnableItem(pPopup->GetItemId("copy"), bEnableCopy);
+ pPopup->EnableItem(pPopup->GetItemId("delete"), bEnableDelete);
+ pPopup->EnableItem(pPopup->GetItemId("paste"), bEnablePaste);
+ pPopup->EnableItem(pPopup->GetItemId("specialchar"), bEnableSpecialChar);
+ pPopup->EnableItem(pPopup->GetItemId("undo"), bEnableUndo);
+ pPopup->ShowItem(pPopup->GetItemId("specialchar"), !vcl::GetGetSpecialCharsFunction());
+
+ mbActivePopup = true;
+ Point aPos = rCEvt.GetMousePosPixel();
+ if ( !rCEvt.IsMouseEvent() )
+ {
+ // Sometime do show Menu centered in the selection !!!
+ Size aSize = GetOutputSizePixel();
+ aPos = Point( aSize.Width()/2, aSize.Height()/2 );
+ }
+ sal_uInt16 n = pPopup->Execute( this, aPos );
+ OString sCommand = pPopup->GetItemIdent(n);
+ if (sCommand == "undo")
+ {
+ mpExtTextView->Undo();
+ mpExtTextEngine->SetModified( true );
+ mpExtTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
+ }
+ else if (sCommand == "cut")
+ {
+ mpExtTextView->Cut();
+ mpExtTextEngine->SetModified( true );
+ mpExtTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
+ }
+ else if (sCommand == "copy")
+ {
+ mpExtTextView->Copy();
+ }
+ else if (sCommand == "paste")
+ {
+ mpExtTextView->Paste();
+ mpExtTextEngine->SetModified( true );
+ mpExtTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
+ }
+ else if (sCommand == "delete")
+ {
+ mpExtTextView->DeleteSelected();
+ mpExtTextEngine->SetModified( true );
+ mpExtTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
+ }
+ else if (sCommand == "selectall")
+ {
+ mpExtTextView->SetSelection( TextSelection( TextPaM( 0, 0 ), TextPaM( TEXT_PARA_ALL, TEXT_INDEX_ALL ) ) );
+ }
+ else if (sCommand == "specialchar")
+ {
+ OUString aChars = vcl::GetGetSpecialCharsFunction()(GetFrameWeld(), GetFont());
+ if (!aChars.isEmpty())
+ {
+ mpExtTextView->InsertText( aChars );
+ mpExtTextEngine->SetModified( true );
+ mpExtTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
+ }
+ }
+ pPopup.clear();
+ mbActivePopup = false;
+ }
+ else
+ {
+ mpExtTextView->Command( rCEvt );
+ }
+ Window::Command( rCEvt );
+}
+
+void TextWindow::GetFocus()
+{
+ Window::GetFocus();
+ if ( !mbActivePopup )
+ {
+ bool bGotoCursor = !mpExtTextView->IsReadOnly();
+ if ( mbFocusSelectionHide && IsReallyVisible()
+ && ( mbSelectOnTab &&
+ (!mbInMBDown || ( GetSettings().GetStyleSettings().GetSelectionOptions() & SelectionOptions::Focus ) )) )
+ {
+ // 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 );
+ pUpdateDataTimer = nullptr;
+
+ SetCompoundControl( true );
+ SetStyle( ImplInitStyle( nWinStyle ) );
+}
+
+VclMultiLineEdit::~VclMultiLineEdit()
+{
+ disposeOnce();
+}
+
+void VclMultiLineEdit::dispose()
+{
+ pImpVclMEdit.reset();
+ pUpdateDataTimer.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(*this, 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)
+ {
+ 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 );
+
+ if ( pUpdateDataTimer )
+ pUpdateDataTimer->Start();
+}
+
+void VclMultiLineEdit::SelectionChanged()
+{
+ CallEventListeners(VclEventId::EditSelectionChanged);
+}
+
+void VclMultiLineEdit::CaretChanged()
+{
+ CallEventListeners(VclEventId::EditCaretChanged);
+}
+
+IMPL_LINK_NOARG(VclMultiLineEdit, ImpUpdateDataHdl, Timer *, void)
+{
+ UpdateData();
+}
+
+void VclMultiLineEdit::UpdateData()
+{
+}
+
+void VclMultiLineEdit::SetModifyFlag()
+{
+ pImpVclMEdit->SetModified( true );
+}
+
+void VclMultiLineEdit::ClearModifyFlag()
+{
+ pImpVclMEdit->SetModified( false );
+}
+
+bool VclMultiLineEdit::IsModified() const
+{
+ return pImpVclMEdit->IsModified();
+}
+
+void VclMultiLineEdit::EnableUpdateData( sal_uLong nTimeout )
+{
+ if ( !nTimeout )
+ DisableUpdateData();
+ else
+ {
+ if ( !pUpdateDataTimer )
+ {
+ pUpdateDataTimer.reset(new Timer("MultiLineEditTimer"));
+ pUpdateDataTimer->SetInvokeHandler( LINK( this, VclMultiLineEdit, ImpUpdateDataHdl ) );
+ }
+ pUpdateDataTimer->SetTimeout( nTimeout );
+ }
+}
+
+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(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
+
+ long nHeight = aSz.Height() - nTop - nBottom;
+ long nLineHeight = pImpVclMEdit->CalcBlockSize( 1, 1 ).Height();
+ 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, DrawFlags nFlags )
+{
+ ImplInitSettings(true);
+
+ Point aPos = pDev->LogicToPixel( rPos );
+ Size aSize = GetSizePixel();
+
+ vcl::Font aFont = pImpVclMEdit->GetTextWindow()->GetDrawPixelFont(pDev);
+ aFont.SetTransparent( true );
+ OutDevType eOutDevType = pDev->GetOutDevType();
+
+ 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 );
+ }
+ }
+
+ // contents
+ if ( ( nFlags & DrawFlags::Mono ) || ( eOutDevType == OUTDEV_PRINTER ) )
+ pDev->SetTextColor( COL_BLACK );
+ else
+ {
+ if ( !IsEnabled() )
+ {
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ pDev->SetTextColor( rStyleSettings.GetDisableColor() );
+ }
+ else
+ {
+ pDev->SetTextColor( GetTextColor() );
+ }
+ }
+
+ 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() );
+ long nOnePixel = GetDrawPixel( pDev, 1 );
+ long nOffX = 3*nOnePixel;
+ long nOffY = 2*nOnePixel;
+
+ // Clipping?
+ if ( ( nOffY < 0 ) || ( (nOffY+aTextSz.Height()) > aSize.Height() ) || ( (nOffX+aTextSz.Width()) > aSize.Width() ) )
+ {
+ tools::Rectangle aClip( aPos, aSize );
+ if ( aTextSz.Height() > aSize.Height() )
+ aClip.AdjustBottom(aTextSz.Height() - aSize.Height() + 1 ); // so that HP-printer does not 'optimize-away'
+ pDev->IntersectClipRegion( aClip );
+ }
+
+ ExtTextEngine aTE;
+ aTE.SetText( GetText() );
+ aTE.SetMaxTextWidth( aSize.Width() );
+ aTE.SetFont( aFont );
+ aTE.SetTextAlign( pImpVclMEdit->GetTextWindow()->GetTextEngine()->GetTextAlign() );
+ aTE.Draw( pDev, Point( aPos.X() + nOffX, aPos.Y() + nOffY ) );
+
+ pDev->Pop();
+}
+
+bool VclMultiLineEdit::EventNotify( NotifyEvent& rNEvt )
+{
+ bool bDone = false;
+ if( rNEvt.GetType() == MouseNotifyEvent::COMMAND )
+ {
+ bDone = pImpVclMEdit->HandleCommand( *rNEvt.GetCommandEvent() );
+ }
+ return bDone || Edit::EventNotify( rNEvt );
+}
+
+bool VclMultiLineEdit::PreNotify( NotifyEvent& rNEvt )
+{
+ bool bDone = false;
+
+ if( ( rNEvt.GetType() == MouseNotifyEvent::KEYINPUT ) && ( !GetTextView()->IsCursorEnabled() ) )
+ {
+ const KeyEvent& rKEvent = *rNEvt.GetKeyEvent();
+ if ( !rKEvent.GetKeyCode().IsShift() && ( rKEvent.GetKeyCode().GetGroup() == KEYGROUP_CURSOR ) )
+ {
+ bDone = true;
+ TextSelection aSel = pImpVclMEdit->GetTextWindow()->GetTextView()->GetSelection();
+ if ( aSel.HasRange() )
+ {
+ aSel.GetStart() = aSel.GetEnd();
+ pImpVclMEdit->GetTextWindow()->GetTextView()->SetSelection( aSel );
+ }
+ else
+ {
+ switch ( rKEvent.GetKeyCode().GetCode() )
+ {
+ case KEY_UP:
+ {
+ if ( pImpVclMEdit->GetVScrollBar().IsVisible() )
+ pImpVclMEdit->GetVScrollBar().DoScrollAction( ScrollType::LineUp );
+ }
+ break;
+ case KEY_DOWN:
+ {
+ if ( pImpVclMEdit->GetVScrollBar().IsVisible() )
+ pImpVclMEdit->GetVScrollBar().DoScrollAction( ScrollType::LineDown );
+ }
+ break;
+ case KEY_PAGEUP :
+ {
+ if ( pImpVclMEdit->GetVScrollBar().IsVisible() )
+ pImpVclMEdit->GetVScrollBar().DoScrollAction( ScrollType::PageUp );
+ }
+ break;
+ case KEY_PAGEDOWN:
+ {
+ if ( pImpVclMEdit->GetVScrollBar().IsVisible() )
+ pImpVclMEdit->GetVScrollBar().DoScrollAction( ScrollType::PageDown );
+ }
+ break;
+ case KEY_LEFT:
+ {
+ if ( pImpVclMEdit->GetHScrollBar().IsVisible() )
+ pImpVclMEdit->GetHScrollBar().DoScrollAction( ScrollType::LineUp );
+ }
+ break;
+ case KEY_RIGHT:
+ {
+ if ( pImpVclMEdit->GetHScrollBar().IsVisible() )
+ pImpVclMEdit->GetHScrollBar().DoScrollAction( ScrollType::LineDown );
+ }
+ break;
+ case KEY_HOME:
+ {
+ if ( rKEvent.GetKeyCode().IsMod1() )
+ pImpVclMEdit->GetTextWindow()->GetTextView()->
+ SetSelection( TextSelection( TextPaM( 0, 0 ) ) );
+ }
+ break;
+ case KEY_END:
+ {
+ if ( rKEvent.GetKeyCode().IsMod1() )
+ pImpVclMEdit->GetTextWindow()->GetTextView()->
+ SetSelection( TextSelection( TextPaM( TEXT_PARA_ALL, TEXT_INDEX_ALL ) ) );
+ }
+ break;
+ default:
+ {
+ bDone = false;
+ }
+ }
+ }
+ }
+ }
+
+ return bDone || Edit::PreNotify( rNEvt );
+}
+
+// Internals for derived classes, e.g. TextComponent
+
+ExtTextEngine* VclMultiLineEdit::GetTextEngine() const
+{
+ return pImpVclMEdit->GetTextWindow()->GetTextEngine();
+}
+
+TextView* VclMultiLineEdit::GetTextView() const
+{
+ return pImpVclMEdit->GetTextWindow()->GetTextView();
+}
+
+ScrollBar& VclMultiLineEdit::GetVScrollBar() const
+{
+ return pImpVclMEdit->GetVScrollBar();
+}
+
+void VclMultiLineEdit::EnableFocusSelectionHide( bool bHide )
+{
+ pImpVclMEdit->GetTextWindow()->SetAutoFocusHide( bHide );
+}
+
+void VclMultiLineEdit::SetRightToLeft( bool bRightToLeft )
+{
+ if ( GetTextEngine() )
+ {
+ GetTextEngine()->SetRightToLeft( bRightToLeft );
+ GetTextView()->ShowCursor();
+ }
+}
+
+void VclMultiLineEdit::DisableSelectionOnFocus()
+{
+ pImpVclMEdit->GetTextWindow()->DisableSelectionOnFocus();
+}
+
+void VclMultiLineEdit::EnableCursor( bool bEnable )
+{
+ GetTextView()->EnableCursor( bEnable );
+}
+
+TextWindow* VclMultiLineEdit::GetTextWindow()
+{
+ return pImpVclMEdit->GetTextWindow();
+}
+
+FactoryFunction VclMultiLineEdit::GetUITestFactory() const
+{
+ return MultiLineEditUIObject::create;
+}
+
+bool VclMultiLineEdit::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "cursor-visible")
+ EnableCursor(toBool(rValue));
+ else if (rKey == "accepts-tab")
+ pImpVclMEdit->GetTextWindow()->SetIgnoreTab(!toBool(rValue));
+ else
+ return Edit::set_property(rKey, rValue);
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/edit/xtextedt.cxx b/vcl/source/edit/xtextedt.cxx
new file mode 100644
index 000000000..4d11f275b
--- /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;
+
+static const std::wstring gaGroupChars = L"(){}[]";
+
+ExtTextEngine::ExtTextEngine()
+{
+}
+
+ExtTextEngine::~ExtTextEngine()
+{
+}
+
+TextSelection ExtTextEngine::MatchGroup( const TextPaM& rCursor ) const
+{
+ TextSelection aSel( rCursor );
+ const sal_Int32 nPos = rCursor.GetIndex();
+ sal_uInt32 nPara = rCursor.GetPara();
+ const sal_uInt32 nParas = GetParagraphCount();
+ if ( ( nPara < nParas ) && ( nPos < GetTextLen( nPara ) ) )
+ {
+ size_t nMatchIndex = gaGroupChars.find( GetText( rCursor.GetPara() )[ nPos ] );
+ if ( nMatchIndex != std::wstring::npos )
+ {
+ if ( ( nMatchIndex % 2 ) == 0 )
+ {
+ // search forwards
+ sal_Unicode nSC = gaGroupChars[ nMatchIndex ];
+ sal_Unicode nEC = gaGroupChars[ nMatchIndex+1 ];
+
+ sal_Int32 nCur = nPos+1;
+ sal_uInt16 nLevel = 1;
+ while ( nLevel && ( nPara < nParas ) )
+ {
+ OUString aStr = GetText( nPara );
+ while ( nCur < aStr.getLength() )
+ {
+ if ( aStr[nCur] == nSC )
+ nLevel++;
+ else if ( aStr[nCur] == nEC )
+ {
+ nLevel--;
+ if ( !nLevel )
+ break; // while nCur...
+ }
+ nCur++;
+ }
+
+ if ( nLevel )
+ {
+ nPara++;
+ nCur = 0;
+ }
+ }
+ if ( nLevel == 0 ) // found
+ {
+ aSel.GetStart() = rCursor;
+ aSel.GetEnd() = TextPaM( nPara, nCur+1 );
+ }
+ }
+ else
+ {
+ // search backwards
+ sal_Unicode nEC = gaGroupChars[ nMatchIndex ];
+ sal_Unicode nSC = gaGroupChars[ nMatchIndex-1 ];
+
+ sal_Int32 nCur = rCursor.GetIndex()-1;
+ sal_uInt16 nLevel = 1;
+ while ( nLevel )
+ {
+ if ( GetTextLen( nPara ) )
+ {
+ OUString aStr = GetText( nPara );
+ while ( nCur )
+ {
+ if ( aStr[nCur] == nSC )
+ {
+ nLevel--;
+ if ( !nLevel )
+ break; // while nCur...
+ }
+ else if ( aStr[nCur] == nEC )
+ nLevel++;
+
+ nCur--;
+ }
+ }
+
+ if ( nLevel )
+ {
+ if ( nPara )
+ {
+ nPara--;
+ nCur = GetTextLen( nPara )-1; // no matter if negative, as if Len()
+ }
+ else
+ break;
+ }
+ }
+
+ if ( nLevel == 0 ) // found
+ {
+ aSel.GetStart() = rCursor;
+ ++aSel.GetStart().GetIndex(); // behind the char
+ aSel.GetEnd() = TextPaM( nPara, nCur );
+ }
+ }
+ }
+ }
+ return aSel;
+}
+
+bool ExtTextEngine::Search( TextSelection& rSel, const i18nutil::SearchOptions& rSearchOptions, bool bForward )
+{
+ TextSelection aSel( rSel );
+ aSel.Justify();
+
+ bool bSearchInSelection = (0 != (rSearchOptions.searchFlag & util::SearchFlags::REG_NOT_BEGINOFLINE) );
+
+ TextPaM aStartPaM( aSel.GetEnd() );
+ if ( aSel.HasRange() && ( ( bSearchInSelection && bForward ) || ( !bSearchInSelection && !bForward ) ) )
+ {
+ aStartPaM = aSel.GetStart();
+ }
+
+ bool bFound = false;
+ sal_uInt32 nEndNode;
+
+ if ( bSearchInSelection )
+ nEndNode = bForward ? aSel.GetEnd().GetPara() : aSel.GetStart().GetPara();
+ else
+ nEndNode = bForward ? (GetParagraphCount()-1) : 0;
+
+ const sal_uInt32 nStartNode = aStartPaM.GetPara();
+
+ i18nutil::SearchOptions aOptions( rSearchOptions );
+ aOptions.Locale = Application::GetSettings().GetLanguageTag().getLocale();
+ utl::TextSearch aSearcher( utl::TextSearch::UpgradeToSearchOptions2(aOptions) );
+
+ // iterate over the paragraphs
+ for ( sal_uInt32 nNode = nStartNode;
+ bForward ? ( nNode <= nEndNode) : ( nNode >= nEndNode );
+ bForward ? nNode++ : nNode-- )
+ {
+ OUString aText = GetText( nNode );
+ sal_Int32 nStartPos = 0;
+ sal_Int32 nEndPos = aText.getLength();
+ if ( nNode == nStartNode )
+ {
+ if ( bForward )
+ nStartPos = aStartPaM.GetIndex();
+ else
+ nEndPos = aStartPaM.GetIndex();
+ }
+ if ( ( nNode == nEndNode ) && bSearchInSelection )
+ {
+ if ( bForward )
+ nEndPos = aSel.GetEnd().GetIndex();
+ else
+ nStartPos = aSel.GetStart().GetIndex();
+ }
+
+ if ( bForward )
+ bFound = aSearcher.SearchForward( aText, &nStartPos, &nEndPos );
+ else
+ bFound = aSearcher.SearchBackward( aText, &nEndPos, &nStartPos );
+
+ if ( bFound )
+ {
+ rSel.GetStart().GetPara() = nNode;
+ rSel.GetStart().GetIndex() = nStartPos;
+ rSel.GetEnd().GetPara() = nNode;
+ rSel.GetEnd().GetIndex() = nEndPos;
+ // Select over the paragraph?
+ // FIXME This should be max long...
+ if( nEndPos == -1)
+ {
+ if ( (rSel.GetEnd().GetPara()+1) < GetParagraphCount() )
+ {
+ rSel.GetEnd().GetPara()++;
+ rSel.GetEnd().GetIndex() = 0;
+ }
+ else
+ {
+ rSel.GetEnd().GetIndex() = nStartPos;
+ bFound = false;
+ }
+ }
+
+ break;
+ }
+
+ if ( !bForward && !nNode ) // if searching backwards, if nEndNode == 0:
+ break;
+ }
+
+ return bFound;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/FilterConfigCache.cxx b/vcl/source/filter/FilterConfigCache.cxx
new file mode 100644
index 000000000..5321ea259
--- /dev/null
+++ b/vcl/source/filter/FilterConfigCache.cxx
@@ -0,0 +1,539 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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_XBM, IMP_XPM,
+ EXP_BMP, EXP_JPEG, EXP_PNG, IMP_MOV, nullptr
+};
+
+const char* FilterConfigCache::FilterConfigCacheEntry::InternalVectorFilterNameList[] =
+{
+ IMP_SVMETAFILE, IMP_WMF, IMP_EMF, IMP_SVG, IMP_PDF,
+ EXP_SVMETAFILE, EXP_WMF, EXP_EMF, EXP_SVG, EXP_PDF, nullptr
+};
+
+const char* FilterConfigCache::FilterConfigCacheEntry::ExternalPixelFilterNameList[] =
+{
+ "egi", "icd", "ipd", "ipx", "ipb", "epb", "epg",
+ "epp", "ira", "era", "itg", "iti", "eti", "exp", nullptr
+};
+
+void FilterConfigCache::FilterConfigCacheEntry::CreateFilterName( const OUString& rUserDataEntry )
+{
+ bIsPixelFormat = bIsInternalFilter = false;
+ sFilterName = rUserDataEntry;
+ const char** pPtr;
+ for ( pPtr = InternalPixelFilterNameList; *pPtr && !bIsInternalFilter; pPtr++ )
+ {
+ if ( sFilterName.equalsIgnoreAsciiCaseAscii( *pPtr ) )
+ {
+ bIsInternalFilter = true;
+ bIsPixelFormat = true;
+ }
+ }
+ for ( pPtr = InternalVectorFilterNameList; *pPtr && !bIsInternalFilter; pPtr++ )
+ {
+ if ( sFilterName.equalsIgnoreAsciiCaseAscii( *pPtr ) )
+ bIsInternalFilter = true;
+ }
+ if ( !bIsInternalFilter )
+ {
+ for ( pPtr = ExternalPixelFilterNameList; *pPtr && !bIsPixelFormat; pPtr++ )
+ {
+ if ( sFilterName.equalsIgnoreAsciiCaseAscii( *pPtr ) )
+ bIsPixelFormat = true;
+ }
+ sExternalFilterName = sFilterName;
+ sFilterName = SVLIBRARY("gie");
+ }
+}
+
+OUString FilterConfigCache::FilterConfigCacheEntry::GetShortName()
+{
+ OUString aShortName;
+ if ( !lExtensionList.empty() )
+ {
+ aShortName = lExtensionList[ 0 ];
+ if ( aShortName.startsWith( "*." ) )
+ aShortName = aShortName.replaceAt( 0, 2, "" );
+ }
+ 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 );
+
+ Sequence< Any > lParams(1);
+ 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" );
+ lParams[0] <<= aParam;
+
+ // get access to file
+ xCfg = xConfigProvider->createInstanceWithArguments("com.sun.star.configuration.ConfigurationAccess", lParams);
+ }
+ catch(const RuntimeException&)
+ { throw; }
+ catch(const Exception&)
+ { xCfg.clear(); }
+
+ return xCfg;
+}
+
+void FilterConfigCache::ImplInit()
+{
+ OUString const STYPE ( "Type" );
+ OUString const SUINAME ( "UIName" );
+ OUString const SFLAGS ( "Flags" );
+ OUString const SMEDIATYPE ( "MediaType" );
+ OUString const SEXTENSIONS ( "Extensions" );
+ OUString const SFORMATNAME ( "FormatName" );
+ OUString const SREALFILTERNAME ( "RealFilterName" );
+
+ // get access to config
+ Reference< XNameAccess > xTypeAccess ( openConfig("types" ), UNO_QUERY );
+ Reference< XNameAccess > xFilterAccess( openConfig("filters"), UNO_QUERY );
+
+ if ( xTypeAccess.is() && xFilterAccess.is() )
+ {
+ 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;
+
+ 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.getLength() != 3)
+ 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","idx",
+ "eps","1","ips",
+ "eps","2","eps",
+ "gif","1","SVIGIF",
+ "gif","2","egi",
+ "jpg","1","SVIJPEG",
+ "jpg","2","SVEJPEG",
+ "mov","1","SVMOV",
+ "mov","2","SVMOV",
+ "met","1","ime",
+ "png","1","SVIPNG",
+ "png","2","SVEPNG",
+ "pct","1","ipt",
+ "pct","2","ept",
+ "pcd","1","icd",
+ "psd","1","ipd",
+ "pcx","1","ipx",
+ "pbm","1","ipb",
+ "pgm","1","ipb",
+ "ppm","1","ipb",
+ "ras","1","ira",
+ "ras","2","era",
+ "svm","1","SVMETAFILE",
+ "svm","2","SVMETAFILE",
+ "tga","1","itg",
+ "tif","1","iti",
+ "tif","2","eti",
+ "emf","1","SVEMF",
+ "emf","2","SVEMF",
+ "wmf","1","SVWMF",
+ "wmf","2","SVWMF",
+ "xbm","1","SVIXBM",
+ "xpm","1","SVIXPM",
+ "xpm","2","exp",
+ "svg","1","SVISVG",
+ "svg","2","SVESVG",
+ 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( const OUString& 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( const OUString& 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( const OUString& 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( const OUString& 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::GetExternalFilterName(sal_uInt16 nFormat, bool bExport)
+{
+ if (bExport)
+ {
+ if (nFormat < aExport.size())
+ return aExport[nFormat].sExternalFilterName;
+ }
+ else
+ {
+ if (nFormat < aImport.size())
+ return aImport[nFormat].sExternalFilterName;
+ }
+ return OUString();
+}
+
+OUString FilterConfigCache::GetImportWildcard(sal_uInt16 nFormat, sal_Int32 nEntry)
+{
+ OUString aWildcard( GetImportFormatExtension( nFormat, nEntry ) );
+ if ( !aWildcard.isEmpty() )
+ aWildcard = aWildcard.replaceAt( 0, 0, "*." );
+ return aWildcard;
+}
+
+bool FilterConfigCache::IsImportInternalFilter( sal_uInt16 nFormat )
+{
+ return (nFormat < aImport.size()) && aImport[ nFormat ].bIsInternalFilter;
+}
+
+OUString FilterConfigCache::GetExportFilterName( sal_uInt16 nFormat )
+{
+ if( nFormat < aExport.size() )
+ return aExport[ nFormat ].sFilterName;
+ return OUString();
+}
+
+sal_uInt16 FilterConfigCache::GetExportFormatNumber(const OUString& 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( const OUString& 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( const OUString& 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( const OUString& 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, "*." );
+ return aWildcard;
+}
+
+bool FilterConfigCache::IsExportInternalFilter( sal_uInt16 nFormat )
+{
+ return (nFormat < aExport.size()) && aExport[ nFormat ].bIsInternalFilter;
+}
+
+bool FilterConfigCache::IsExportPixelFormat( sal_uInt16 nFormat )
+{
+ return (nFormat < aExport.size()) && aExport[ nFormat ].bIsPixelFormat;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/FilterConfigCache.hxx b/vcl/source/filter/FilterConfigCache.hxx
new file mode 100644
index 000000000..ec1c2dc46
--- /dev/null
+++ b/vcl/source/filter/FilterConfigCache.hxx
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_FILTER_FILTERCONFIGCACHE_HXX
+#define INCLUDED_VCL_SOURCE_FILTER_FILTERCONFIGCACHE_HXX
+
+#include <rtl/ustring.hxx>
+#include <vector>
+
+/** Cache to keep list of graphic filters + the filters themselves. */
+class FilterConfigCache
+{
+ struct FilterConfigCacheEntry
+ {
+ OUString sInternalFilterName;
+ OUString sType;
+ std::vector< OUString > lExtensionList;
+ OUString sUIName;
+ OUString sExternalFilterName;
+
+ OUString sMediaType;
+ OUString sFilterType;
+
+ sal_Int32 nFlags;
+
+ // user data
+ OUString sFilterName;
+ bool bIsInternalFilter : 1;
+ bool bIsPixelFormat : 1;
+
+ void CreateFilterName( const OUString& rUserDataEntry );
+ OUString GetShortName( );
+
+ static const char* InternalPixelFilterNameList[];
+ static const char* InternalVectorFilterNameList[];
+ static const char* ExternalPixelFilterNameList[];
+ };
+
+
+ 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( const OUString& rFormatName );
+ sal_uInt16 GetImportFormatNumberForShortName( const OUString& rShortName );
+ sal_uInt16 GetImportFormatNumberForTypeName( const OUString& rType );
+ sal_uInt16 GetImportFormatNumberForExtension( const OUString& 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 );
+ OUString GetExternalFilterName(sal_uInt16 nFormat, bool bExport);
+
+
+ bool IsImportInternalFilter( sal_uInt16 nFormat );
+
+ sal_uInt16 GetExportFormatCount() const
+ { return sal::static_int_cast< sal_uInt16 >(aExport.size()); };
+ sal_uInt16 GetExportFormatNumber( const OUString& rFormatName );
+ sal_uInt16 GetExportFormatNumberForMediaType( const OUString& rMediaType );
+ sal_uInt16 GetExportFormatNumberForShortName( const OUString& rShortName );
+ sal_uInt16 GetExportFormatNumberForTypeName( const OUString& 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 IsExportInternalFilter( sal_uInt16 nFormat );
+ bool IsExportPixelFormat( sal_uInt16 nFormat );
+
+ explicit FilterConfigCache( bool bUseConfig );
+ ~FilterConfigCache();
+};
+
+#endif // INCLUDED_VCL_SOURCE_FILTER_FILTERCONFIGCACHE_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/FilterConfigItem.cxx b/vcl/source/filter/FilterConfigItem.cxx
new file mode 100644
index 000000000..41c9ec401
--- /dev/null
+++ b/vcl/source/filter/FilterConfigItem.cxx
@@ -0,0 +1,393 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <osl/diagnose.h>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/configuration/theDefaultProvider.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/util/XChangesBatch.hpp>
+#include <com/sun/star/beans/XPropertySetInfo.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/container/XHierarchicalNameAccess.hpp>
+#include <com/sun/star/awt/Size.hpp>
+#include <com/sun/star/task/XStatusIndicator.hpp>
+
+using namespace ::com::sun::star::lang ; // XMultiServiceFactory
+using namespace ::com::sun::star::beans ; // PropertyValue
+using namespace ::com::sun::star::uno ; // Reference
+using namespace ::com::sun::star::util ; // XChangesBatch
+using namespace ::com::sun::star::awt ; // Size
+using namespace ::com::sun::star::container ;
+using namespace ::com::sun::star::configuration;
+using namespace ::com::sun::star::task ; // XStatusIndicator
+
+static bool ImpIsTreeAvailable( Reference< XMultiServiceFactory > const & rXCfgProv, const OUString& rTree )
+{
+ bool bAvailable = !rTree.isEmpty();
+ if ( bAvailable )
+ {
+ sal_Int32 nIdx{0};
+ if ( rTree[0] == '/' )
+ ++nIdx;
+
+ // creation arguments: nodepath
+ PropertyValue aPathArgument;
+ aPathArgument.Name = "nodepath";
+ aPathArgument.Value <<= rTree.getToken(0, '/', nIdx);
+
+ Sequence< Any > aArguments( 1 );
+ aArguments[ 0 ] <<= aPathArgument;
+
+ Reference< XInterface > xReadAccess;
+ try
+ {
+ xReadAccess = rXCfgProv->createInstanceWithArguments(
+ "com.sun.star.configuration.ConfigurationAccess",
+ aArguments );
+ }
+ catch (const css::uno::Exception&)
+ {
+ bAvailable = false;
+ }
+ if ( xReadAccess.is() )
+ {
+ const sal_Int32 nEnd {rTree.getLength()};
+ while (bAvailable && nIdx>=0 && nIdx<nEnd)
+ {
+ Reference< XHierarchicalNameAccess > xHierarchicalNameAccess
+ ( xReadAccess, UNO_QUERY );
+
+ if ( !xHierarchicalNameAccess.is() )
+ bAvailable = false;
+ else
+ {
+ const OUString aNode( rTree.getToken(0, '/', nIdx) );
+ if ( !xHierarchicalNameAccess->hasByHierarchicalName( aNode ) )
+ bAvailable = false;
+ else
+ {
+ Any a( xHierarchicalNameAccess->getByHierarchicalName( aNode ) );
+ bAvailable = (a >>= xReadAccess);
+ }
+ }
+ }
+ }
+ }
+ return bAvailable;
+}
+
+void FilterConfigItem::ImpInitTree( const OUString& rSubTree )
+{
+ bModified = false;
+
+ Reference< XComponentContext > xContext( comphelper::getProcessComponentContext() );
+
+ Reference< XMultiServiceFactory > xCfgProv = theDefaultProvider::get( xContext );
+
+ OUString sTree = "/org.openoffice." + rSubTree;
+ if ( ImpIsTreeAvailable(xCfgProv, sTree) )
+ {
+ // creation arguments: nodepath
+ PropertyValue aPathArgument;
+ aPathArgument.Name = "nodepath";
+ aPathArgument.Value <<= sTree;
+
+ Sequence< Any > aArguments( 1 );
+ aArguments[ 0 ] <<= 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( const OUString& rSubTree )
+{
+ ImpInitTree( rSubTree );
+}
+
+FilterConfigItem::FilterConfigItem( css::uno::Sequence< css::beans::PropertyValue > const * pFilterData )
+ : bModified(false)
+{
+ if ( pFilterData )
+ aFilterData = *pFilterData;
+}
+
+FilterConfigItem::FilterConfigItem( const OUString& rSubTree,
+ css::uno::Sequence< css::beans::PropertyValue > const * pFilterData )
+{
+ ImpInitTree( rSubTree );
+
+ if ( pFilterData )
+ aFilterData = *pFilterData;
+};
+
+FilterConfigItem::~FilterConfigItem()
+{
+ WriteModifiedConfig();
+}
+
+void FilterConfigItem::WriteModifiedConfig()
+{
+ if ( xUpdatableView.is() )
+ {
+ if ( xPropSet.is() && bModified )
+ {
+ 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
+PropertyValue* FilterConfigItem::GetPropertyValue( 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(rPropSeq.begin(), rPropSeq.end(),
+ [&rPropValue](const PropertyValue& rProp) { return rProp.Name == rPropValue.Name; });
+ sal_Int32 i = std::distance(rPropSeq.begin(), pProp);
+ sal_Int32 nCount = rPropSeq.getLength();
+ if ( i == nCount )
+ rPropSeq.realloc( ++nCount );
+
+ rPropSeq[ i ] = rPropValue;
+
+ bRet = true;
+ }
+ return bRet;
+}
+
+bool FilterConfigItem::ReadBool( const OUString& rKey, bool bDefault )
+{
+ Any aAny;
+ bool bRetValue = bDefault;
+ 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;
+ 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 );
+ 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() )
+ {
+ Any aAny;
+ if ( ImplGetPropertyValue( aAny, xPropSet, rKey ) )
+ {
+ bool bOldValue(true);
+ if ( aAny >>= bOldValue )
+ {
+ 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() )
+ {
+ Any aAny;
+
+ if ( ImplGetPropertyValue( aAny, xPropSet, rKey ) )
+ {
+ sal_Int32 nOldValue = 0;
+ if ( aAny >>= nOldValue )
+ {
+ 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;
+ const OUString sStatusIndicator( "StatusIndicator" );
+
+ auto pPropVal = std::find_if(aFilterData.begin(), aFilterData.end(),
+ [&sStatusIndicator](const css::beans::PropertyValue& rPropVal) {
+ return rPropVal.Name == sStatusIndicator; });
+ if (pPropVal != aFilterData.end())
+ {
+ pPropVal->Value >>= xStatusIndicator;
+ }
+ return xStatusIndicator;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/GraphicFormatDetector.cxx b/vcl/source/filter/GraphicFormatDetector.cxx
new file mode 100644
index 000000000..56f7a9f2d
--- /dev/null
+++ b/vcl/source/filter/GraphicFormatDetector.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 <algorithm>
+
+#include <graphic/GraphicFormatDetector.hxx>
+#include <graphic/DetectorTools.hxx>
+#include <tools/solar.h>
+#include <tools/zcodec.hxx>
+
+namespace vcl
+{
+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
+
+ if (x1 > x2 || y1 > y2 || // bad bdbox
+ (x1 == x2 && y1 == y2) || // 1 pixel picture
+ x2 - x1 > 2048 || y2 - y1 > 2048) // picture abnormally big
+ bdBoxOk = false;
+
+ // read version op
+ rStream.ReadBytes(sBuf, 3);
+ // see http://developer.apple.com/legacy/mac/library/documentation/mac/pdf/Imaging_With_QuickDraw/Appendix_A.pdf
+ // normal version 2 - page A23 and A24
+ if (sBuf[0] == 0x00 && sBuf[1] == 0x11 && sBuf[2] == 0x02)
+ return true;
+ // normal version 1 - page A25
+ else if (sBuf[0] == 0x11 && sBuf[1] == 0x01 && bdBoxOk)
+ return true;
+ }
+ return false;
+}
+
+} // end anonymous namespace
+
+GraphicFormatDetector::GraphicFormatDetector(SvStream& rStream, OUString const& rFormatExtension)
+ : mrStream(rStream)
+ , maExtension(rFormatExtension)
+ , mnFirstLong(0)
+ , mnSecondLong(0)
+ , mnStreamPosition(0)
+ , mnStreamLength(0)
+{
+}
+
+bool GraphicFormatDetector::detect()
+{
+ maFirstBytes.clear();
+ maFirstBytes.resize(256, 0);
+
+ mnFirstLong = 0;
+ mnSecondLong = 0;
+
+ mnStreamPosition = mrStream.Tell();
+ mnStreamLength = mrStream.remainingSize();
+
+ if (!mnStreamLength)
+ {
+ SvLockBytes* pLockBytes = mrStream.GetLockBytes();
+ if (pLockBytes)
+ pLockBytes->SetSynchronMode();
+ mnStreamLength = mrStream.remainingSize();
+ }
+
+ if (mnStreamLength == 0)
+ {
+ return false; // this prevents at least a STL assertion
+ }
+ else if (mnStreamLength >= maFirstBytes.size())
+ {
+ // load first 256 bytes into a buffer
+ sal_uInt64 nRead = mrStream.ReadBytes(maFirstBytes.data(), maFirstBytes.size());
+ if (nRead < maFirstBytes.size())
+ mnStreamLength = nRead;
+ }
+ else
+ {
+ mnStreamLength = mrStream.ReadBytes(maFirstBytes.data(), mnStreamLength);
+ }
+
+ if (mrStream.GetError())
+ return false;
+
+ for (int i = 0; i < 4; ++i)
+ {
+ mnFirstLong = (mnFirstLong << 8) | sal_uInt32(maFirstBytes[i]);
+ mnSecondLong = (mnSecondLong << 8) | sal_uInt32(maFirstBytes[i + 4]);
+ }
+ return true;
+}
+
+bool GraphicFormatDetector::checkMET()
+{
+ if (maFirstBytes[2] != 0xd3)
+ return false;
+ mrStream.SetEndian(SvStreamEndian::BIG);
+ mrStream.Seek(mnStreamPosition);
+ sal_uInt16 nFieldSize;
+ sal_uInt8 nMagic;
+
+ mrStream.ReadUInt16(nFieldSize).ReadUChar(nMagic);
+ for (int i = 0; i < 3; i++)
+ {
+ if (nFieldSize < 6)
+ return false;
+ if (mnStreamLength < mrStream.Tell() + nFieldSize)
+ return false;
+ mrStream.SeekRel(nFieldSize - 3);
+ mrStream.ReadUInt16(nFieldSize).ReadUChar(nMagic);
+ if (nMagic != 0xd3)
+ return false;
+ }
+ mrStream.SetEndian(SvStreamEndian::LITTLE);
+
+ if (mrStream.GetError())
+ return false;
+
+ msDetectedFormat = "MET";
+ return true;
+}
+
+bool GraphicFormatDetector::checkBMP()
+{
+ sal_uInt8 nOffset;
+
+ // We're possibly also able to read an OS/2 bitmap array
+ // ('BA'), therefore we must adjust the offset to discover the
+ // first bitmap in the array
+ if (maFirstBytes[0] == 0x42 && maFirstBytes[1] == 0x41)
+ nOffset = 14;
+ else
+ nOffset = 0;
+
+ // Now we initially test on 'BM'
+ if (maFirstBytes[0 + nOffset] == 0x42 && maFirstBytes[1 + nOffset] == 0x4d)
+ {
+ // OS/2 can set the Reserved flags to a value other than 0
+ // (which they really should not do...);
+ // In this case we test the size of the BmpInfoHeaders
+ if ((maFirstBytes[6 + nOffset] == 0x00 && maFirstBytes[7 + nOffset] == 0x00
+ && maFirstBytes[8 + nOffset] == 0x00 && maFirstBytes[9 + nOffset] == 0x00)
+ || maFirstBytes[14 + nOffset] == 0x28 || maFirstBytes[14 + nOffset] == 0x0c)
+ {
+ msDetectedFormat = "BMP";
+ return true;
+ }
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkWMForEMF()
+{
+ if (mnFirstLong == 0xd7cdc69a || mnFirstLong == 0x01000900)
+ {
+ msDetectedFormat = "WMF";
+ return true;
+ }
+ else if (mnFirstLong == 0x01000000 && maFirstBytes[40] == 0x20 && maFirstBytes[41] == 0x45
+ && maFirstBytes[42] == 0x4d && maFirstBytes[43] == 0x46)
+ {
+ msDetectedFormat = "EMF";
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkPCX()
+{
+ if (maFirstBytes[0] != 0x0a)
+ return false;
+
+ sal_uInt8 nVersion = maFirstBytes[1];
+ sal_uInt8 nEncoding = maFirstBytes[2];
+ if ((nVersion == 0 || nVersion == 2 || nVersion == 3 || nVersion == 5) && nEncoding <= 1)
+ {
+ msDetectedFormat = "PCX";
+ return true;
+ }
+
+ return false;
+}
+
+bool GraphicFormatDetector::checkTIF()
+{
+ if (mnFirstLong == 0x49492a00 || mnFirstLong == 0x4d4d002a)
+ {
+ msDetectedFormat = "TIF";
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkGIF()
+{
+ if (mnFirstLong == 0x47494638 && (maFirstBytes[4] == 0x37 || maFirstBytes[4] == 0x39)
+ && maFirstBytes[5] == 0x61)
+ {
+ msDetectedFormat = "GIF";
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkPNG()
+{
+ if (mnFirstLong == 0x89504e47 && mnSecondLong == 0x0d0a1a0a)
+ {
+ msDetectedFormat = "PNG";
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkJPG()
+{
+ if ((mnFirstLong == 0xffd8ffe0 && maFirstBytes[6] == 0x4a && maFirstBytes[7] == 0x46
+ && maFirstBytes[8] == 0x49 && maFirstBytes[9] == 0x46)
+ || (mnFirstLong == 0xffd8fffe) || (0xffd8ff00 == (mnFirstLong & 0xffffff00)))
+ {
+ msDetectedFormat = "JPG";
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkSVM()
+{
+ if (mnFirstLong == 0x53564744 && maFirstBytes[4] == 0x49)
+ {
+ msDetectedFormat = "SVM";
+ return true;
+ }
+ else if (maFirstBytes[0] == 0x56 && maFirstBytes[1] == 0x43 && maFirstBytes[2] == 0x4C
+ && maFirstBytes[3] == 0x4D && maFirstBytes[4] == 0x54 && maFirstBytes[5] == 0x46)
+ {
+ msDetectedFormat = "SVM";
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkPCD()
+{
+ if (mnStreamLength < 2055)
+ return false;
+ char sBuffer[8];
+ mrStream.Seek(mnStreamPosition + 2048);
+ mrStream.ReadBytes(sBuffer, 7);
+
+ if (strncmp(sBuffer, "PCD_IPI", 7) == 0)
+ {
+ msDetectedFormat = "PCD";
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkPSD()
+{
+ if ((mnFirstLong == 0x38425053) && ((mnSecondLong >> 16) == 1))
+ {
+ msDetectedFormat = "PSD";
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkEPS()
+{
+ const char* pFirstBytesAsCharArray = reinterpret_cast<char*>(maFirstBytes.data());
+
+ if (mnFirstLong == 0xC5D0D3C6)
+ {
+ msDetectedFormat = "EPS";
+ return true;
+ }
+ else if (checkArrayForMatchingStrings(pFirstBytesAsCharArray, 30, { "%!PS-Adobe", " EPS" }))
+ {
+ msDetectedFormat = "EPS";
+ return true;
+ }
+
+ return false;
+}
+
+bool GraphicFormatDetector::checkDXF()
+{
+ if (strncmp(reinterpret_cast<char*>(maFirstBytes.data()), "AutoCAD Binary DXF", 18) == 0)
+ {
+ msDetectedFormat = "DXF";
+ return true;
+ }
+
+ // ASCII DXF File Format
+ int i = 0;
+ while (i < 256 && maFirstBytes[i] <= 32)
+ {
+ ++i;
+ }
+
+ if (i < 256 && maFirstBytes[i] == '0')
+ {
+ ++i;
+
+ // only now do we have sufficient data to make a judgement
+ // based on a '0' + 'SECTION' == DXF argument
+
+ while (i < 256 && maFirstBytes[i] <= 32)
+ {
+ ++i;
+ }
+
+ if (i + 7 < 256
+ && (strncmp(reinterpret_cast<char*>(maFirstBytes.data() + i), "SECTION", 7) == 0))
+ {
+ msDetectedFormat = "DXF";
+ return true;
+ }
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkPCT()
+{
+ if (isPCT(mrStream, mnStreamPosition, mnStreamLength))
+ {
+ msDetectedFormat = "PCT";
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkPBMorPGMorPPM()
+{
+ if (maFirstBytes[0] == 'P')
+ {
+ switch (maFirstBytes[1])
+ {
+ case '1':
+ case '4':
+ msDetectedFormat = "PBM";
+ return true;
+
+ case '2':
+ case '5':
+ msDetectedFormat = "PGM";
+ return true;
+
+ case '3':
+ case '6':
+ msDetectedFormat = "PPM";
+ return true;
+ }
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkRAS()
+{
+ if (mnFirstLong == 0x59a66a95)
+ {
+ msDetectedFormat = "RAS";
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkXPM()
+{
+ const char* pFirstBytesAsCharArray = reinterpret_cast<char*>(maFirstBytes.data());
+ if (matchArrayWithString(pFirstBytesAsCharArray, 256, "/* XPM */"))
+ {
+ msDetectedFormat = "XPM";
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkXBM()
+{
+ sal_uInt64 nSize = std::min<sal_uInt64>(mnStreamLength, 2048);
+ std::unique_ptr<sal_uInt8[]> pBuffer(new sal_uInt8[nSize]);
+
+ mrStream.Seek(mnStreamPosition);
+ mrStream.ReadBytes(pBuffer.get(), nSize);
+
+ const char* pBufferAsCharArray = reinterpret_cast<char*>(pBuffer.get());
+
+ if (checkArrayForMatchingStrings(pBufferAsCharArray, nSize, { "#define", "_width" }))
+ {
+ msDetectedFormat = "XBM";
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkSVG()
+{
+ sal_uInt8* pCheckArray = maFirstBytes.data();
+ sal_uInt64 nCheckSize = std::min<sal_uInt64>(mnStreamLength, 256);
+
+ sal_uInt8 sExtendedOrDecompressedFirstBytes[2048];
+ sal_uInt64 nDecompressedSize = nCheckSize;
+
+ bool bIsGZip(false);
+
+ // check if it is gzipped -> svgz
+ if (maFirstBytes[0] == 0x1F && maFirstBytes[1] == 0x8B)
+ {
+ ZCodec aCodec;
+ mrStream.Seek(mnStreamPosition);
+ aCodec.BeginCompression(ZCODEC_DEFAULT_COMPRESSION, /*gzLib*/ true);
+ nDecompressedSize = aCodec.Read(mrStream, sExtendedOrDecompressedFirstBytes, 2048);
+ nCheckSize = std::min<sal_uInt64>(nDecompressedSize, 256);
+ aCodec.EndCompression();
+ pCheckArray = sExtendedOrDecompressedFirstBytes;
+
+ bIsGZip = true;
+ }
+
+ bool bIsSvg(false);
+
+ const char* pCheckArrayAsCharArray = reinterpret_cast<char*>(pCheckArray);
+
+ // check for XML
+ // #119176# SVG files which have no xml header at all have shown up this is optional
+ // check for "xml" then "version" then "DOCTYPE" and "svg" tags
+ if (checkArrayForMatchingStrings(pCheckArrayAsCharArray, nCheckSize,
+ { "<?xml", "version", "DOCTYPE", "svg" }))
+ {
+ bIsSvg = true;
+ }
+
+ // check for svg element in 1st 256 bytes
+ // search for '<svg'
+ if (!bIsSvg && checkArrayForMatchingStrings(pCheckArrayAsCharArray, nCheckSize, { "<svg" }))
+ {
+ bIsSvg = true;
+ }
+
+ // extended search for svg element
+ if (!bIsSvg)
+ {
+ // it's a xml, look for '<svg' in full file. Should not happen too
+ // often since the tests above will handle most cases, but can happen
+ // with Svg files containing big comment headers or Svg as the host
+ // language
+
+ pCheckArrayAsCharArray = reinterpret_cast<char*>(sExtendedOrDecompressedFirstBytes);
+
+ if (bIsGZip)
+ {
+ nCheckSize = std::min<sal_uInt64>(nDecompressedSize, 2048);
+ }
+ else
+ {
+ nCheckSize = std::min<sal_uInt64>(mnStreamLength, 2048);
+ mrStream.Seek(mnStreamPosition);
+ nCheckSize = mrStream.ReadBytes(sExtendedOrDecompressedFirstBytes, nCheckSize);
+ }
+
+ // search for '<svg'
+ if (checkArrayForMatchingStrings(pCheckArrayAsCharArray, nCheckSize, { "<svg" }))
+ {
+ bIsSvg = true;
+ }
+ }
+
+ if (bIsSvg)
+ {
+ msDetectedFormat = "SVG";
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkTGA()
+{
+ if (maExtension.startsWith("TGA"))
+ {
+ msDetectedFormat = "TGA";
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkMOV()
+{
+ if ((maFirstBytes[4] == 'f' && maFirstBytes[5] == 't' && maFirstBytes[6] == 'y'
+ && maFirstBytes[7] == 'p' && maFirstBytes[8] == 'q' && maFirstBytes[9] == 't')
+ || (maFirstBytes[4] == 'm' && maFirstBytes[5] == 'o' && maFirstBytes[6] == 'o'
+ && maFirstBytes[7] == 'v' && maFirstBytes[11] == 'l' && maFirstBytes[12] == 'm'))
+ {
+ msDetectedFormat = "MOV";
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkPDF()
+{
+ if (maFirstBytes[0] == '%' && maFirstBytes[1] == 'P' && maFirstBytes[2] == 'D'
+ && maFirstBytes[3] == 'F' && maFirstBytes[4] == '-')
+ {
+ msDetectedFormat = "PDF";
+ return true;
+ }
+ return false;
+}
+
+} // vcl namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/GraphicNativeMetadata.cxx b/vcl/source/filter/GraphicNativeMetadata.cxx
new file mode 100644
index 000000000..6eb60cd67
--- /dev/null
+++ b/vcl/source/filter/GraphicNativeMetadata.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 <vcl/GraphicNativeMetadata.hxx>
+#include <vcl/gfxlink.hxx>
+#include "jpeg/Exif.hxx"
+#include <memory>
+
+GraphicNativeMetadata::GraphicNativeMetadata()
+ : mRotation(0)
+{
+}
+
+GraphicNativeMetadata::~GraphicNativeMetadata() {}
+
+bool GraphicNativeMetadata::read(Graphic const& rGraphic)
+{
+ GfxLink aLink = rGraphic.GetGfxLink();
+ if (aLink.GetType() != GfxLinkType::NativeJpg)
+ return false;
+
+ sal_uInt32 aDataSize = aLink.GetDataSize();
+ if (!aDataSize)
+ return false;
+
+ std::unique_ptr<sal_uInt8[]> aBuffer(new sal_uInt8[aDataSize]);
+
+ memcpy(aBuffer.get(), aLink.GetData(), aDataSize);
+ SvMemoryStream aMemoryStream(aBuffer.get(), aDataSize, StreamMode::READ);
+
+ read(aMemoryStream);
+
+ return true;
+}
+
+bool GraphicNativeMetadata::read(SvStream& rStream)
+{
+ Exif aExif;
+ aExif.read(rStream);
+ mRotation = aExif.getRotation();
+
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/GraphicNativeTransform.cxx b/vcl/source/filter/GraphicNativeTransform.cxx
new file mode 100644
index 000000000..8083dd4c2
--- /dev/null
+++ b/vcl/source/filter/GraphicNativeTransform.cxx
@@ -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 .
+ */
+
+#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 "jpeg/Exif.hxx"
+#include "jpeg/JpegTransform.hxx"
+
+using namespace ::exif;
+
+GraphicNativeTransform::GraphicNativeTransform(Graphic& rGraphic)
+ : mrGraphic(rGraphic)
+{
+}
+
+GraphicNativeTransform::~GraphicNativeTransform() {}
+
+void GraphicNativeTransform::rotate(sal_uInt16 aInputRotation)
+{
+ // Rotation can be between 0 and 3600
+ sal_uInt16 aRotation = aInputRotation % 3600;
+
+ if (aRotation == 0)
+ {
+ return; // No rotation is needed
+ }
+ else if (aRotation != 900 && aRotation != 1800 && aRotation != 2700)
+ {
+ return;
+ }
+
+ GfxLink aLink = mrGraphic.GetGfxLink();
+ if (aLink.GetType() == GfxLinkType::NativeJpg)
+ {
+ rotateJPEG(aRotation);
+ }
+ else if (aLink.GetType() == GfxLinkType::NativePng)
+ {
+ rotateGeneric(aRotation, "png");
+ }
+ else if (aLink.GetType() == GfxLinkType::NativeGif)
+ {
+ rotateGeneric(aRotation, "gif");
+ }
+ else if (aLink.GetType() == GfxLinkType::NONE)
+ {
+ rotateBitmapOnly(aRotation);
+ }
+}
+
+bool GraphicNativeTransform::rotateBitmapOnly(sal_uInt16 aRotation)
+{
+ if (mrGraphic.IsAnimated())
+ {
+ return false;
+ }
+
+ BitmapEx aBitmap = mrGraphic.GetBitmapEx();
+ aBitmap.Rotate(aRotation, COL_BLACK);
+ mrGraphic = aBitmap;
+
+ return true;
+}
+
+bool GraphicNativeTransform::rotateGeneric(sal_uInt16 aRotation, const OUString& aType)
+{
+ // Can't rotate animations yet
+ if (mrGraphic.IsAnimated())
+ {
+ return false;
+ }
+
+ SvMemoryStream aStream;
+
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+
+ css::uno::Sequence<css::beans::PropertyValue> aFilterData(3);
+ aFilterData[0].Name = "Interlaced";
+ aFilterData[0].Value <<= sal_Int32(0);
+ aFilterData[1].Name = "Compression";
+ aFilterData[1].Value <<= sal_Int32(9);
+ aFilterData[2].Name = "Quality";
+ aFilterData[2].Value <<= sal_Int32(90);
+
+ sal_uInt16 nFilterFormat = rFilter.GetExportFormatNumberForShortName(aType);
+
+ BitmapEx aBitmap = mrGraphic.GetBitmapEx();
+ aBitmap.Rotate(aRotation, COL_BLACK);
+ rFilter.ExportGraphic(aBitmap, "none", aStream, nFilterFormat, &aFilterData);
+
+ aStream.Seek(STREAM_SEEK_TO_BEGIN);
+
+ Graphic aGraphic;
+ rFilter.ImportGraphic(aGraphic, "import", aStream);
+
+ mrGraphic = aGraphic;
+ return true;
+}
+
+void GraphicNativeTransform::rotateJPEG(sal_uInt16 aRotation)
+{
+ BitmapEx aBitmap = mrGraphic.GetBitmapEx();
+
+ if (aBitmap.GetSizePixel().Width() % 16 != 0 || aBitmap.GetSizePixel().Height() % 16 != 0)
+ {
+ rotateGeneric(aRotation, "png");
+ }
+ else
+ {
+ GfxLink aLink = mrGraphic.GetGfxLink();
+
+ SvMemoryStream aSourceStream;
+ aSourceStream.WriteBytes(aLink.GetData(), aLink.GetDataSize());
+ aSourceStream.Seek(STREAM_SEEK_TO_BEGIN);
+
+ Orientation aOrientation = 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 != TOP_LEFT)
+ {
+ exif.setOrientation(TOP_LEFT);
+ exif.write(aTargetStream);
+ }
+
+ aTargetStream.Seek(STREAM_SEEK_TO_BEGIN);
+
+ Graphic aGraphic;
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ rFilter.ImportGraphic(aGraphic, "import", aTargetStream);
+ mrGraphic = aGraphic;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/graphicfilter.cxx b/vcl/source/filter/graphicfilter.cxx
new file mode 100644
index 000000000..9593847dd
--- /dev/null
+++ b/vcl/source/filter/graphicfilter.cxx
@@ -0,0 +1,2303 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <osl/mutex.hxx>
+#include <comphelper/processfactory.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 <vcl/dibtools.hxx>
+#include <fltcall.hxx>
+#include <vcl/salctype.hxx>
+#include <vcl/pngread.hxx>
+#include <vcl/pngwrite.hxx>
+#include <vcl/vectorgraphicdata.hxx>
+#include <vcl/virdev.hxx>
+#include <impgraph.hxx>
+#include <vcl/svapp.hxx>
+#include <osl/file.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include <vcl/wmf.hxx>
+#include "igif/gifread.hxx"
+#include <vcl/pdfread.hxx>
+#include "jpeg/jpeg.hxx"
+#include "ixbm/xbmread.hxx"
+#include "ixpm/xpmread.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 <rtl/instance.hxx>
+#include <tools/svlibrary.h>
+#include <comphelper/string.hxx>
+#include <unotools/ucbhelper.hxx>
+#include <vector>
+#include <memory>
+
+#include "FilterConfigCache.hxx"
+#include "graphicfilter_internal.hxx"
+
+#include <graphic/GraphicFormatDetector.hxx>
+#include <graphic/GraphicReader.hxx>
+
+#define PMGCHUNG_msOG 0x6d734f47 // Microsoft Office Animated GIF
+
+typedef ::std::vector< GraphicFilter* > FilterList_impl;
+static FilterList_impl* pFilterHdlList = nullptr;
+
+static ::osl::Mutex& getListMutex()
+{
+ static ::osl::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.Flush(); }
+ virtual void SAL_CALL closeOutput() override {}
+
+public:
+
+ explicit ImpFilterOutputStream( SvStream& rStm ) : mrStm( rStm ) {}
+};
+
+}
+
+// Helper functions
+
+sal_uInt8* ImplSearchEntry( sal_uInt8* pSource, sal_uInt8 const * pDest, sal_uLong nComp, sal_uLong nSize )
+{
+ while ( nComp-- >= nSize )
+ {
+ sal_uLong i;
+ for ( i = 0; i < nSize; i++ )
+ {
+ if ( ( pSource[i]&~0x20 ) != ( pDest[i]&~0x20 ) )
+ break;
+ }
+ if ( i == nSize )
+ return pSource;
+ pSource++;
+ }
+ return nullptr;
+}
+
+static OUString ImpGetExtension( const OUString &rPath )
+{
+ OUString aExt;
+ INetURLObject aURL( rPath );
+ aExt = aURL.GetFileExtension().toAsciiUpperCase();
+ return aExt;
+}
+
+bool isPCT(SvStream& rStream, sal_uLong nStreamPos, sal_uLong nStreamLen)
+{
+ sal_uInt8 sBuf[3];
+ // store number format
+ SvStreamEndian oldNumberFormat = rStream.GetEndian();
+ sal_uInt32 nOffset; // in MS documents the pict format is used without the first 512 bytes
+ for ( nOffset = 0; ( nOffset <= 512 ) && ( ( nStreamPos + nOffset + 14 ) <= nStreamLen ); nOffset += 512 )
+ {
+ short y1,x1,y2,x2;
+ bool bdBoxOk = true;
+
+ rStream.Seek( nStreamPos + nOffset);
+ // size of the pict in version 1 pict ( 2bytes) : ignored
+ rStream.SeekRel(2);
+ // bounding box (bytes 2 -> 9)
+ rStream.SetEndian(SvStreamEndian::BIG);
+ rStream.ReadInt16( y1 ).ReadInt16( x1 ).ReadInt16( y2 ).ReadInt16( x2 );
+ rStream.SetEndian(oldNumberFormat); // reset format
+
+ if (x1 > x2 || y1 > y2 || // bad bdbox
+ (x1 == x2 && y1 == y2) || // 1 pixel picture
+ x2-x1 > 2048 || y2-y1 > 2048 ) // picture abnormally big
+ bdBoxOk = false;
+
+ // read version op
+ rStream.ReadBytes(sBuf, 3);
+ // see http://developer.apple.com/legacy/mac/library/documentation/mac/pdf/Imaging_With_QuickDraw/Appendix_A.pdf
+ // normal version 2 - page A23 and A24
+ if ( sBuf[ 0 ] == 0x00 && sBuf[ 1 ] == 0x11 && sBuf[ 2 ] == 0x02)
+ return true;
+ // normal version 1 - page A25
+ else if (sBuf[ 0 ] == 0x11 && sBuf[ 1 ] == 0x01 && bdBoxOk)
+ return true;
+ }
+ return false;
+}
+
+/*************************************************************************
+ *
+ * ImpPeekGraphicFormat()
+ *
+ * Description:
+ * This function is two-fold:
+ * 1.) Start reading file, determine the file format:
+ * Input parameters:
+ * rPath - file path
+ * rFormatExtension - content matter
+ * bTest - set false
+ * Output parameters:
+ * Return value - true if success
+ * rFormatExtension - on success: normal file extension in capitals
+ * 2.) Start reading file, verify file format
+ * Input parameters:
+ * rPath - file path
+ * rFormatExtension - normal file extension in capitals
+ * bTest - set true
+ * Output parameters:
+ * Return value - false, if cannot verify the file type
+ * passed to the function
+ * true, when the format is PROBABLY verified or
+ * WHEN THE FORMAT IS NOT KNOWN!
+ *
+ *************************************************************************/
+
+bool ImpPeekGraphicFormat( SvStream& rStream, OUString& rFormatExtension, bool bTest )
+{
+ vcl::GraphicFormatDetector aDetector(rStream, rFormatExtension);
+ if (!aDetector.detect())
+ return false;
+
+ // The following variable is used when bTest == true. It remains false
+ // if the format (rFormatExtension) has not yet been set.
+ bool bSomethingTested = false;
+
+ // Now the different formats are checked. The order *does* matter. e.g. a MET file
+ // could also go through the BMP test, however, a BMP file can hardly go through the MET test.
+ // So MET should be tested prior to BMP. However, theoretically a BMP file could conceivably
+ // go through the MET test. These problems are of course not only in MET and BMP.
+ // Therefore, in the case of a format check (bTest == true) we only test *exactly* this
+ // format. Everything else could have fatal consequences, for example if the user says it is
+ // a BMP file (and it is a BMP) file, and the file would go through the MET test ...
+
+ if (!bTest || rFormatExtension.startsWith("MET"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkMET())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("BMP"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkBMP())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ if (!bTest ||
+ rFormatExtension.startsWith("WMF") ||
+ rFormatExtension.startsWith("EMF"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkWMForEMF())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("PCX"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkPCX())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("TIF"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkTIF())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("GIF"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkGIF())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("PNG"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkPNG())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("JPG"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkJPG())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("SVM"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkSVM())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("PCD"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkPCD())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("PSD"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkPSD())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("EPS"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkEPS())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("DXF"))
+ {
+ if (aDetector.checkDXF())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("PCT"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkPCT())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ if (!bTest ||
+ rFormatExtension.startsWith("PBM") ||
+ rFormatExtension.startsWith("PGM") ||
+ rFormatExtension.startsWith("PPM"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkPBMorPGMorPPM())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("RAS"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkRAS())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ if (!bTest)
+ {
+ bSomethingTested = true;
+ if (aDetector.checkXPM())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+
+ }
+ else if (rFormatExtension.startsWith("XPM"))
+ {
+ return true;
+ }
+
+ if (!bTest)
+ {
+ if (aDetector.checkXBM())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+ else if (rFormatExtension.startsWith("XBM"))
+ {
+ return true;
+ }
+
+ if (!bTest)
+ {
+ if (aDetector.checkSVG())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+ else if (rFormatExtension.startsWith("SVG"))
+ {
+ return true;
+ }
+
+ if (!bTest || rFormatExtension.startsWith("TGA"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkTGA())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("MOV"))
+ {
+ if (aDetector.checkMOV())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("PDF"))
+ {
+ if (aDetector.checkPDF())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ return bTest && !bSomethingTested;
+}
+
+ErrCode GraphicFilter::ImpTestOrFindFormat( const OUString& rPath, SvStream& rStream, sal_uInt16& rFormat )
+{
+ // determine or check the filter/format by reading into it
+ if( rFormat == GRFILTER_FORMAT_DONTKNOW )
+ {
+ OUString aFormatExt;
+ if( ImpPeekGraphicFormat( rStream, aFormatExt, false ) )
+ {
+ rFormat = pConfig->GetImportFormatNumberForExtension( aFormatExt );
+ if( rFormat != GRFILTER_FORMAT_DONTKNOW )
+ return ERRCODE_NONE;
+ }
+ // determine filter by file extension
+ if( !rPath.isEmpty() )
+ {
+ 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( !ImpPeekGraphicFormat( 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( "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::min( std::max( 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;
+}
+
+static OUString ImpCreateFullFilterPath( const OUString& rPath, const OUString& rFilterName )
+{
+ OUString aPathURL;
+
+ ::osl::FileBase::getFileURLFromSystemPath( rPath, aPathURL );
+ aPathURL += "/";
+
+ OUString aSystemPath;
+ ::osl::FileBase::getSystemPathFromFileURL( aPathURL, aSystemPath );
+ aSystemPath += rFilterName;
+
+ return aSystemPath;
+}
+
+namespace {
+
+class ImpFilterLibCache;
+
+struct ImpFilterLibCacheEntry
+{
+ ImpFilterLibCacheEntry* mpNext;
+#ifndef DISABLE_DYNLOADING
+ osl::Module maLibrary;
+#endif
+ OUString maFiltername;
+ OUString maFormatName;
+ PFilterCall mpfnImport;
+
+ ImpFilterLibCacheEntry(const OUString& rPathname, const OUString& rFiltername, const OUString& rFormatName);
+ bool operator==( const OUString& rFiltername ) const { return maFiltername == rFiltername; }
+
+ PFilterCall GetImportFunction();
+};
+
+}
+
+ImpFilterLibCacheEntry::ImpFilterLibCacheEntry( const OUString& rPathname, const OUString& rFiltername, const OUString& rFormatName ) :
+ mpNext ( nullptr ),
+#ifndef DISABLE_DYNLOADING
+ maLibrary ( rPathname ),
+#endif
+ maFiltername ( rFiltername ),
+ maFormatName ( rFormatName ),
+ mpfnImport ( nullptr )
+{
+#ifdef DISABLE_DYNLOADING
+ (void) rPathname;
+#endif
+}
+
+#ifdef DISABLE_DYNLOADING
+
+extern "C" bool icdGraphicImport( SvStream& rStream, Graphic& rGraphic, FilterConfigItem* pConfigItem );
+extern "C" bool idxGraphicImport( SvStream& rStream, Graphic& rGraphic, FilterConfigItem* pConfigItem );
+extern "C" bool imeGraphicImport( SvStream& rStream, Graphic& rGraphic, FilterConfigItem* pConfigItem );
+extern "C" bool ipbGraphicImport( SvStream& rStream, Graphic& rGraphic, FilterConfigItem* pConfigItem );
+extern "C" bool ipdGraphicImport( SvStream& rStream, Graphic& rGraphic, FilterConfigItem* pConfigItem );
+extern "C" bool ipsGraphicImport( SvStream& rStream, Graphic& rGraphic, FilterConfigItem* pConfigItem );
+extern "C" bool iptGraphicImport( SvStream& rStream, Graphic& rGraphic, FilterConfigItem* pConfigItem );
+extern "C" bool ipxGraphicImport( SvStream& rStream, Graphic& rGraphic, FilterConfigItem* pConfigItem );
+extern "C" bool iraGraphicImport( SvStream& rStream, Graphic& rGraphic, FilterConfigItem* pConfigItem );
+extern "C" bool itgGraphicImport( SvStream& rStream, Graphic& rGraphic, FilterConfigItem* pConfigItem );
+extern "C" bool itiGraphicImport( SvStream& rStream, Graphic& rGraphic, FilterConfigItem* pConfigItem );
+
+#endif
+
+PFilterCall ImpFilterLibCacheEntry::GetImportFunction()
+{
+ if( !mpfnImport )
+ {
+#ifndef DISABLE_DYNLOADING
+ if (maFormatName == "icd")
+ mpfnImport = reinterpret_cast<PFilterCall>(maLibrary.getFunctionSymbol("icdGraphicImport"));
+ else if (maFormatName == "idx")
+ mpfnImport = reinterpret_cast<PFilterCall>(maLibrary.getFunctionSymbol("idxGraphicImport"));
+ else if (maFormatName == "ime")
+ mpfnImport = reinterpret_cast<PFilterCall>(maLibrary.getFunctionSymbol("imeGraphicImport"));
+ else if (maFormatName == "ipb")
+ mpfnImport = reinterpret_cast<PFilterCall>(maLibrary.getFunctionSymbol("ipbGraphicImport"));
+ else if (maFormatName == "ipd")
+ mpfnImport = reinterpret_cast<PFilterCall>(maLibrary.getFunctionSymbol("ipdGraphicImport"));
+ else if (maFormatName == "ips")
+ mpfnImport = reinterpret_cast<PFilterCall>(maLibrary.getFunctionSymbol("ipsGraphicImport"));
+ else if (maFormatName == "ipt")
+ mpfnImport = reinterpret_cast<PFilterCall>(maLibrary.getFunctionSymbol("iptGraphicImport"));
+ else if (maFormatName == "ipx")
+ mpfnImport = reinterpret_cast<PFilterCall>(maLibrary.getFunctionSymbol("ipxGraphicImport"));
+ else if (maFormatName == "ira")
+ mpfnImport = reinterpret_cast<PFilterCall>(maLibrary.getFunctionSymbol("iraGraphicImport"));
+ else if (maFormatName == "itg")
+ mpfnImport = reinterpret_cast<PFilterCall>(maLibrary.getFunctionSymbol("itgGraphicImport"));
+ else if (maFormatName == "iti")
+ mpfnImport = reinterpret_cast<PFilterCall>(maLibrary.getFunctionSymbol("itiGraphicImport"));
+ #else
+ if (maFormatName == "icd")
+ mpfnImport = icdGraphicImport;
+ else if (maFormatName == "idx")
+ mpfnImport = idxGraphicImport;
+ else if (maFormatName == "ime")
+ mpfnImport = imeGraphicImport;
+ else if (maFormatName == "ipb")
+ mpfnImport = ipbGraphicImport;
+ else if (maFormatName == "ipd")
+ mpfnImport = ipdGraphicImport;
+ else if (maFormatName == "ips")
+ mpfnImport = ipsGraphicImport;
+ else if (maFormatName == "ipt")
+ mpfnImport = iptGraphicImport;
+ else if (maFormatName == "ipx")
+ mpfnImport = ipxGraphicImport;
+ else if (maFormatName == "ira")
+ mpfnImport = iraGraphicImport;
+ else if (maFormatName == "itg")
+ mpfnImport = itgGraphicImport;
+ else if (maFormatName == "iti")
+ mpfnImport = itiGraphicImport;
+ #endif
+ }
+
+ return mpfnImport;
+}
+
+namespace {
+
+class ImpFilterLibCache
+{
+ ImpFilterLibCacheEntry* mpFirst;
+ ImpFilterLibCacheEntry* mpLast;
+
+public:
+ ImpFilterLibCache();
+ ~ImpFilterLibCache();
+
+ ImpFilterLibCacheEntry* GetFilter( const OUString& rFilterPath, const OUString& rFiltername, const OUString& rFormatName );
+};
+
+}
+
+ImpFilterLibCache::ImpFilterLibCache() :
+ mpFirst ( nullptr ),
+ mpLast ( nullptr )
+{
+}
+
+ImpFilterLibCache::~ImpFilterLibCache()
+{
+ ImpFilterLibCacheEntry* pEntry = mpFirst;
+ while( pEntry )
+ {
+ ImpFilterLibCacheEntry* pNext = pEntry->mpNext;
+ delete pEntry;
+ pEntry = pNext;
+ }
+}
+
+ImpFilterLibCacheEntry* ImpFilterLibCache::GetFilter(const OUString& rFilterPath, const OUString& rFilterName, const OUString& rFormatName)
+{
+ ImpFilterLibCacheEntry* pEntry = mpFirst;
+
+ while( pEntry )
+ {
+ if( *pEntry == rFilterName && pEntry->maFormatName == rFormatName )
+ break;
+ else
+ pEntry = pEntry->mpNext;
+ }
+ if( !pEntry )
+ {
+ OUString aPhysicalName( ImpCreateFullFilterPath( rFilterPath, rFilterName ) );
+ pEntry = new ImpFilterLibCacheEntry(aPhysicalName, rFilterName, rFormatName );
+#ifndef DISABLE_DYNLOADING
+ if ( pEntry->maLibrary.is() )
+#endif
+ {
+ if( !mpFirst )
+ mpFirst = mpLast = pEntry;
+ else
+ mpLast = mpLast->mpNext = pEntry;
+ }
+#ifndef DISABLE_DYNLOADING
+ else
+ {
+ delete pEntry;
+ pEntry = nullptr;
+ }
+#endif
+ }
+ return pEntry;
+};
+
+namespace { struct Cache : public rtl::Static<ImpFilterLibCache, Cache> {}; }
+
+GraphicFilter::GraphicFilter( bool bConfig )
+ : bUseConfig(bConfig)
+{
+ ImplInit();
+}
+
+GraphicFilter::~GraphicFilter()
+{
+ {
+ ::osl::MutexGuard aGuard( getListMutex() );
+ auto it = std::find(pFilterHdlList->begin(), pFilterHdlList->end(), this);
+ if( it != pFilterHdlList->end() )
+ pFilterHdlList->erase( it );
+
+ if( pFilterHdlList->empty() )
+ {
+ delete pFilterHdlList;
+ pFilterHdlList = nullptr;
+ delete pConfig;
+ }
+ }
+
+ pErrorEx.reset();
+}
+
+void GraphicFilter::ImplInit()
+{
+ {
+ ::osl::MutexGuard aGuard( getListMutex() );
+
+ if ( !pFilterHdlList )
+ {
+ pFilterHdlList = new FilterList_impl;
+ pConfig = new FilterConfigCache( bUseConfig );
+ }
+ else
+ pConfig = pFilterHdlList->front()->pConfig;
+
+ pFilterHdlList->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);
+ }
+
+ pErrorEx.reset( new FilterErrorEx );
+}
+
+ErrCode GraphicFilter::ImplSetError( ErrCode nError, const SvStream* pStm )
+{
+ pErrorEx->nStreamError = pStm ? pStm->GetError() : ERRCODE_NONE;
+ return nError;
+}
+
+sal_uInt16 GraphicFilter::GetImportFormatCount() const
+{
+ return pConfig->GetImportFormatCount();
+}
+
+sal_uInt16 GraphicFilter::GetImportFormatNumber( const OUString& rFormatName )
+{
+ return pConfig->GetImportFormatNumber( rFormatName );
+}
+
+sal_uInt16 GraphicFilter::GetImportFormatNumberForShortName( const OUString& rShortName )
+{
+ return pConfig->GetImportFormatNumberForShortName( rShortName );
+}
+
+sal_uInt16 GraphicFilter::GetImportFormatNumberForTypeName( const OUString& 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( const OUString& rFormatName )
+{
+ return pConfig->GetExportFormatNumber( rFormatName );
+}
+
+sal_uInt16 GraphicFilter::GetExportFormatNumberForMediaType( const OUString& rMediaType )
+{
+ return pConfig->GetExportFormatNumberForMediaType( rMediaType );
+}
+
+sal_uInt16 GraphicFilter::GetExportFormatNumberForShortName( const OUString& rShortName )
+{
+ return pConfig->GetExportFormatNumberForShortName( rShortName );
+}
+
+OUString GraphicFilter::GetExportInternalFilterName( sal_uInt16 nFormat )
+{
+ return pConfig->GetExportInternalFilterName( nFormat );
+}
+
+sal_uInt16 GraphicFilter::GetExportFormatNumberForTypeName( const OUString& 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( const OUString& rMainUrl, SvStream& rIStream,
+ sal_uInt16 nFormat, sal_uInt16* pDeterminedFormat )
+{
+ sal_uLong 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;
+}
+
+ErrCode GraphicFilter::ImportGraphic(
+ Graphic& rGraphic,
+ const OUString& rPath,
+ SvStream& rIStream,
+ sal_uInt16 nFormat,
+ sal_uInt16* pDeterminedFormat,
+ GraphicFilterImportFlags nImportFlags,
+ WmfExternal const *pExtHeader)
+{
+ return ImportGraphic( rGraphic, rPath, rIStream, nFormat, pDeterminedFormat, nImportFlags, nullptr, pExtHeader );
+}
+
+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;
+ /// 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 (!ImportJPEG(*rContext.m_pStream, *rContext.m_pGraphic, rContext.m_nImportFlags | GraphicFilterImportFlags::UseExistingBitmap, rContext.m_pAccess.get()))
+ rContext.m_nStatus = ERRCODE_GRFILTER_FILTERERROR;
+ else
+ rContext.m_eLinkType = GfxLinkType::NativeJpg;
+}
+
+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(OUString(), *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_nImportFlags = GraphicFilterImportFlags::SetLogsizeForJpeg;
+
+ if (!ImportJPEG( *rContext.m_pStream, *rContext.m_pGraphic, rContext.m_nImportFlags | GraphicFilterImportFlags::OnlyCreateBitmap, nullptr))
+ rContext.m_nStatus = ERRCODE_GRFILTER_FILTERERROR;
+ else
+ {
+ 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;
+ }
+ }
+ }
+
+ rSharedPool.waitUntilDone(pTag);
+
+ // Process data after import.
+ for (auto& rContext : aContexts)
+ {
+ rContext.m_pAccess.reset();
+
+ if (rContext.m_nStatus == ERRCODE_NONE && (rContext.m_eLinkType != GfxLinkType::NONE) && !rContext.m_pGraphic->GetReaderContext())
+ {
+ std::unique_ptr<sal_uInt8[]> pGraphicContent;
+
+ const sal_uInt64 nStreamEnd = rContext.m_pStream->Tell();
+ sal_Int32 nGraphicContentSize = nStreamEnd - rContext.m_nStreamBegin;
+
+ if (nGraphicContentSize > 0)
+ {
+ try
+ {
+ pGraphicContent.reset(new sal_uInt8[nGraphicContentSize]);
+ }
+ catch (const std::bad_alloc&)
+ {
+ rContext.m_nStatus = ERRCODE_GRFILTER_TOOBIG;
+ }
+
+ if (rContext.m_nStatus == ERRCODE_NONE)
+ {
+ rContext.m_pStream->Seek(rContext.m_nStreamBegin);
+ rContext.m_pStream->ReadBytes(pGraphicContent.get(), nGraphicContentSize);
+ }
+ }
+
+ if (rContext.m_nStatus == ERRCODE_NONE)
+ rContext.m_pGraphic->SetGfxLink(std::make_shared<GfxLink>(std::move(pGraphicContent), nGraphicContentSize, rContext.m_eLinkType));
+ }
+
+ if (rContext.m_nStatus != ERRCODE_NONE)
+ rContext.m_pGraphic = nullptr;
+
+ rGraphics.push_back(rContext.m_pGraphic);
+ }
+}
+
+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_uLong nStreamBegin = rIStream.Tell();
+
+ rIStream.Seek(nStreamBegin);
+
+ ErrCode nStatus = ImpTestOrFindFormat("", rIStream, nFormat);
+
+ rIStream.Seek(nStreamBegin);
+ sal_uInt32 nStreamLength(rIStream.remainingSize());
+ if (sizeLimit && sizeLimit < nStreamLength)
+ nStreamLength = sizeLimit;
+
+ OUString aFilterName = pConfig->GetImportFilterName(nFormat);
+ OUString aExternalFilterName = pConfig->GetExternalFilterName(nFormat, false);
+
+ std::unique_ptr<sal_uInt8[]> pGraphicContent;
+ sal_Int32 nGraphicContentSize = 0;
+
+ // read graphic
+ if (pConfig->IsImportInternalFilter(nFormat))
+ {
+ if (aFilterName.equalsIgnoreAsciiCase(IMP_GIF))
+ {
+ eLinkType = GfxLinkType::NativeGif;
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_PNG))
+ {
+ vcl::PNGReader aPNGReader(rIStream);
+
+ // check if this PNG contains a GIF chunk!
+ const std::vector<vcl::PNGReader::ChunkData>& rChunkData = aPNGReader.GetChunks();
+ for (auto const& chunk : rChunkData)
+ {
+ // Microsoft Office is storing Animated GIFs in following chunk
+ if (chunk.nType == PMGCHUNG_msOG)
+ {
+ sal_uInt32 nChunkSize = chunk.aData.size();
+
+ if (nChunkSize > 11)
+ {
+ const std::vector<sal_uInt8>& rData = chunk.aData;
+ nGraphicContentSize = nChunkSize - 11;
+ SvMemoryStream aIStrm(const_cast<sal_uInt8*>(&rData[11]), nGraphicContentSize, StreamMode::READ);
+ pGraphicContent.reset(new sal_uInt8[nGraphicContentSize]);
+ sal_uInt64 aCurrentPosition = aIStrm.Tell();
+ aIStrm.ReadBytes(pGraphicContent.get(), nGraphicContentSize);
+ aIStrm.Seek(aCurrentPosition);
+ eLinkType = GfxLinkType::NativeGif;
+ break;
+ }
+ }
+ }
+ if (eLinkType == GfxLinkType::NONE)
+ {
+ 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;
+ long nMemoryLength;
+
+ aCodec.BeginCompression(ZCODEC_DEFAULT_COMPRESSION, /*gzLib*/true);
+ nMemoryLength = aCodec.Decompress(rIStream, aMemStream);
+ aCodec.EndCompression();
+
+ if (!rIStream.GetError() && nMemoryLength >= 0)
+ {
+ nGraphicContentSize = nMemoryLength;
+ pGraphicContent.reset(new sal_uInt8[nGraphicContentSize]);
+
+ aMemStream.Seek(STREAM_SEEK_TO_BEGIN);
+ aMemStream.ReadBytes(pGraphicContent.get(), nGraphicContentSize);
+
+ bOkay = true;
+ }
+ }
+ else
+ {
+ nGraphicContentSize = nStreamLength;
+ pGraphicContent.reset(new sal_uInt8[nGraphicContentSize]);
+ rIStream.ReadBytes(pGraphicContent.get(), nStreamLength);
+
+ bOkay = true;
+ }
+ }
+
+ if (bOkay)
+ {
+ eLinkType = GfxLinkType::NativeSvg;
+ }
+ else
+ {
+ nStatus = ERRCODE_GRFILTER_FILTERERROR;
+ }
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_BMP))
+ {
+ eLinkType = GfxLinkType::NativeBmp;
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_MOV))
+ {
+ eLinkType = GfxLinkType::NativeMov;
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_WMF) ||
+ aFilterName.equalsIgnoreAsciiCase(IMP_EMF))
+ {
+ nGraphicContentSize = nStreamLength;
+ pGraphicContent.reset(new sal_uInt8[nGraphicContentSize]);
+
+ rIStream.Seek(nStreamBegin);
+ rIStream.ReadBytes(pGraphicContent.get(), nStreamLength);
+
+ if (!rIStream.GetError())
+ {
+ eLinkType = GfxLinkType::NativeWmf;
+ }
+ else
+ {
+ nStatus = ERRCODE_GRFILTER_FILTERERROR;
+ }
+ }
+ else if (aFilterName == IMP_PDF)
+ {
+ eLinkType = GfxLinkType::NativePdf;
+ }
+ else
+ {
+ nStatus = ERRCODE_GRFILTER_FILTERERROR;
+ }
+ }
+ else
+ {
+ ImpFilterLibCacheEntry* pFilter = nullptr;
+
+ if (!aFilterPath.isEmpty())
+ {
+ // find first filter in filter paths
+ ImpFilterLibCache &rCache = Cache::get();
+ sal_Int32 nIdx{0};
+ do {
+ pFilter = rCache.GetFilter(aFilterPath.getToken(0, ';', nIdx), aFilterName, aExternalFilterName);
+ } while (nIdx>=0 && pFilter==nullptr);
+ }
+
+ if( !pFilter )
+ nStatus = ERRCODE_GRFILTER_FILTERERROR;
+ else
+ {
+ PFilterCall pFunc = pFilter->GetImportFunction();
+
+ if (!pFunc)
+ nStatus = ERRCODE_GRFILTER_FILTERERROR;
+ else
+ {
+ OUString aShortName;
+ if (nFormat != GRFILTER_FORMAT_DONTKNOW)
+ aShortName = GetImportFormatShortName(nFormat).toAsciiUpperCase();
+
+ if (aShortName.startsWith(TIF_SHORTNAME))
+ eLinkType = GfxLinkType::NativeTif;
+ else if( aShortName.startsWith(MET_SHORTNAME))
+ eLinkType = GfxLinkType::NativeMet;
+ else if( aShortName.startsWith(PCT_SHORTNAME))
+ eLinkType = GfxLinkType::NativePct;
+ }
+ }
+ }
+
+ if (nStatus == ERRCODE_NONE && eLinkType != GfxLinkType::NONE)
+ {
+ if (!pGraphicContent)
+ {
+ nGraphicContentSize = nStreamLength;
+
+ if (nGraphicContentSize > 0)
+ {
+ try
+ {
+ pGraphicContent.reset(new sal_uInt8[nGraphicContentSize]);
+ }
+ catch (const std::bad_alloc&)
+ {
+ nStatus = ERRCODE_GRFILTER_TOOBIG;
+ }
+
+ if (nStatus == ERRCODE_NONE)
+ {
+ rIStream.Seek(nStreamBegin);
+ rIStream.ReadBytes(pGraphicContent.get(), nGraphicContentSize);
+ }
+ }
+ }
+
+ if( nStatus == ERRCODE_NONE )
+ {
+ bool bAnimated = false;
+ if (eLinkType == GfxLinkType::NativeGif)
+ {
+ SvMemoryStream aMemoryStream(pGraphicContent.get(), nGraphicContentSize, StreamMode::READ);
+ bAnimated = IsGIFAnimated(aMemoryStream);
+ }
+ aGraphic.SetGfxLink(std::make_shared<GfxLink>(std::move(pGraphicContent), nGraphicContentSize, eLinkType));
+ aGraphic.ImplGetImpGraphic()->ImplSetPrepared(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;
+}
+
+void GraphicFilter::preload()
+{
+ sal_Int32 nTokenCount = comphelper::string::getTokenCount(aFilterPath, ';');
+ ImpFilterLibCache& rCache = Cache::get();
+ static const std::initializer_list<OUStringLiteral> aFilterNames = {
+ "icd", "idx", "ime", "ipb", "ipd", "ips", "ipt", "ipx", "ira", "itg", "iti",
+ };
+
+ // Load library for each filter.
+ for (const auto& rFilterName : aFilterNames)
+ {
+ ImpFilterLibCacheEntry* pFilter = nullptr;
+ // Look at the library in each element inside the filter path.
+ for (sal_Int32 i = 0; i < nTokenCount; ++i)
+ {
+ pFilter = rCache.GetFilter(aFilterPath.getToken(i, ';'), SVLIBRARY("gie"), rFilterName);
+ if (pFilter)
+ {
+ break;
+ }
+ }
+ }
+}
+
+ErrCode GraphicFilter::ImportGraphic( Graphic& rGraphic, const OUString& rPath, SvStream& rIStream,
+ sal_uInt16 nFormat, sal_uInt16* pDeterminedFormat, GraphicFilterImportFlags nImportFlags,
+ const css::uno::Sequence< css::beans::PropertyValue >* /*pFilterData*/,
+ WmfExternal const *pExtHeader )
+{
+ OUString aFilterName;
+ OUString aExternalFilterName;
+ sal_uLong nStreamBegin;
+ ErrCode nStatus;
+ GfxLinkType eLinkType = GfxLinkType::NONE;
+ const bool bLinkSet = rGraphic.IsGfxLink();
+
+ std::unique_ptr<sal_uInt8[]> pGraphicContent;
+ sal_Int32 nGraphicContentSize = 0;
+
+ ResetLastError();
+
+ std::shared_ptr<GraphicReader> pContext = rGraphic.GetReaderContext();
+ bool bDummyContext = rGraphic.IsDummyContext();
+ if( !pContext || bDummyContext )
+ {
+ if( bDummyContext )
+ {
+ rGraphic.SetDummyContext( false );
+ nStreamBegin = 0;
+ }
+ else
+ nStreamBegin = rIStream.Tell();
+
+ nStatus = ImpTestOrFindFormat( rPath, rIStream, nFormat );
+ // if pending, return ERRCODE_NONE in order to request more bytes
+ if( rIStream.GetError() == ERRCODE_IO_PENDING )
+ {
+ rGraphic.SetDummyContext(true);
+ rIStream.ResetError();
+ rIStream.Seek( nStreamBegin );
+ return ImplSetError( ERRCODE_NONE );
+ }
+
+ rIStream.Seek( nStreamBegin );
+
+ if( ( nStatus != ERRCODE_NONE ) || rIStream.GetError() )
+ return ImplSetError( ( nStatus != ERRCODE_NONE ) ? nStatus : ERRCODE_GRFILTER_OPENERROR, &rIStream );
+
+ if( pDeterminedFormat )
+ *pDeterminedFormat = nFormat;
+
+ aFilterName = pConfig->GetImportFilterName( nFormat );
+ aExternalFilterName = pConfig->GetExternalFilterName(nFormat, false);
+ }
+ else
+ {
+ aFilterName = pContext->GetUpperFilterName();
+
+ nStreamBegin = 0;
+ nStatus = ERRCODE_NONE;
+ }
+
+ // read graphic
+ if ( pConfig->IsImportInternalFilter( nFormat ) )
+ {
+ if( aFilterName.equalsIgnoreAsciiCase( IMP_GIF ) )
+ {
+ if( !ImportGIF( rIStream, rGraphic ) )
+ nStatus = ERRCODE_GRFILTER_FILTERERROR;
+ else
+ eLinkType = GfxLinkType::NativeGif;
+ }
+ else if( aFilterName.equalsIgnoreAsciiCase( IMP_PNG ) )
+ {
+ vcl::PNGReader aPNGReader( rIStream );
+
+ {
+ // check if this PNG contains a GIF chunk!
+ const std::vector<vcl::PNGReader::ChunkData>& rChunkData = aPNGReader.GetChunks();
+ for (auto const& chunk : rChunkData)
+ {
+ // Microsoft Office is storing Animated GIFs in following chunk
+ if (chunk.nType == PMGCHUNG_msOG)
+ {
+ sal_uInt32 nChunkSize = chunk.aData.size();
+
+ if (nChunkSize > 11)
+ {
+ const std::vector<sal_uInt8>& rData = chunk.aData;
+ nGraphicContentSize = nChunkSize - 11;
+ SvMemoryStream aIStrm(const_cast<sal_uInt8*>(&rData[11]), nGraphicContentSize, StreamMode::READ);
+ pGraphicContent.reset(new sal_uInt8[nGraphicContentSize]);
+ sal_uInt64 aCurrentPosition = aIStrm.Tell();
+ aIStrm.ReadBytes(pGraphicContent.get(), nGraphicContentSize);
+ aIStrm.Seek(aCurrentPosition);
+ ImportGIF(aIStrm, rGraphic);
+ eLinkType = GfxLinkType::NativeGif;
+ break;
+ }
+ }
+ }
+ }
+
+ if ( eLinkType == GfxLinkType::NONE )
+ {
+ BitmapEx aBmpEx( aPNGReader.Read() );
+ if ( aBmpEx.IsEmpty() )
+ nStatus = ERRCODE_GRFILTER_FILTERERROR;
+ else
+ {
+ rGraphic = aBmpEx;
+ eLinkType = GfxLinkType::NativePng;
+ }
+ }
+ }
+ else if( aFilterName.equalsIgnoreAsciiCase( IMP_JPEG ) )
+ {
+ // set LOGSIZE flag always, if not explicitly disabled
+ // (see #90508 and #106763)
+ if( !( nImportFlags & GraphicFilterImportFlags::DontSetLogsizeForJpeg ) )
+ nImportFlags |= GraphicFilterImportFlags::SetLogsizeForJpeg;
+
+ sal_uInt64 nPosition = rIStream.Tell();
+ if( !ImportJPEG( rIStream, rGraphic, nImportFlags | GraphicFilterImportFlags::OnlyCreateBitmap, nullptr ) )
+ nStatus = ERRCODE_GRFILTER_FILTERERROR;
+ else
+ {
+ Bitmap& rBitmap = const_cast<Bitmap&>(rGraphic.GetBitmapExRef().GetBitmap());
+ BitmapScopedWriteAccess pWriteAccess(rBitmap);
+ rIStream.Seek(nPosition);
+ if( !ImportJPEG( rIStream, rGraphic, nImportFlags | GraphicFilterImportFlags::UseExistingBitmap, &pWriteAccess ) )
+ nStatus = ERRCODE_GRFILTER_FILTERERROR;
+ else
+ eLinkType = GfxLinkType::NativeJpg;
+ }
+ }
+ else if( aFilterName.equalsIgnoreAsciiCase( IMP_SVG ) )
+ {
+ const sal_uInt32 nStreamPosition(rIStream.Tell());
+ const sal_uInt32 nStreamLength(rIStream.remainingSize());
+
+ bool bOkay(false);
+
+ if(nStreamLength > 0)
+ {
+ std::vector<sal_uInt8> aTwoBytes(2);
+ rIStream.ReadBytes(aTwoBytes.data(), 2);
+ rIStream.Seek(nStreamPosition);
+
+ if(aTwoBytes[0] == 0x1F && aTwoBytes[1] == 0x8B)
+ {
+ SvMemoryStream aMemStream;
+ ZCodec aCodec;
+ long nMemoryLength;
+
+ aCodec.BeginCompression(ZCODEC_DEFAULT_COMPRESSION, /*gzLib*/true);
+ nMemoryLength = aCodec.Decompress(rIStream, aMemStream);
+ aCodec.EndCompression();
+
+ if (!rIStream.GetError() && nMemoryLength >= 0)
+ {
+ VectorGraphicDataArray aNewData(nMemoryLength);
+ aMemStream.Seek(STREAM_SEEK_TO_BEGIN);
+ aMemStream.ReadBytes(aNewData.begin(), nMemoryLength);
+
+ // Make a uncompressed copy for GfxLink
+ nGraphicContentSize = nMemoryLength;
+ pGraphicContent.reset(new sal_uInt8[nGraphicContentSize]);
+ std::copy(aNewData.begin(), aNewData.end(), pGraphicContent.get());
+
+ if(!aMemStream.GetError() )
+ {
+ auto aVectorGraphicDataPtr = std::make_shared<VectorGraphicData>(aNewData, rPath, VectorGraphicDataType::Svg);
+ rGraphic = Graphic(aVectorGraphicDataPtr);
+ bOkay = true;
+ }
+ }
+ }
+ else
+ {
+ VectorGraphicDataArray aNewData(nStreamLength);
+ rIStream.ReadBytes(aNewData.begin(), nStreamLength);
+
+ if(!rIStream.GetError())
+ {
+ auto aVectorGraphicDataPtr = std::make_shared<VectorGraphicData>(aNewData, rPath, VectorGraphicDataType::Svg);
+ rGraphic = Graphic(aVectorGraphicDataPtr);
+ bOkay = true;
+ }
+ }
+ }
+
+ if (bOkay)
+ {
+ eLinkType = GfxLinkType::NativeSvg;
+ }
+ else
+ {
+ nStatus = ERRCODE_GRFILTER_FILTERERROR;
+ }
+ }
+ else if( aFilterName.equalsIgnoreAsciiCase( IMP_XBM ) )
+ {
+ if( !ImportXBM( rIStream, rGraphic ) )
+ nStatus = ERRCODE_GRFILTER_FILTERERROR;
+ }
+ else if( aFilterName.equalsIgnoreAsciiCase( IMP_XPM ) )
+ {
+ if( !ImportXPM( rIStream, rGraphic ) )
+ nStatus = ERRCODE_GRFILTER_FILTERERROR;
+ }
+ else if( aFilterName.equalsIgnoreAsciiCase( IMP_BMP ) ||
+ aFilterName.equalsIgnoreAsciiCase( IMP_SVMETAFILE ) )
+ {
+ // SV internal filters for import bitmaps and MetaFiles
+ ReadGraphic( rIStream, rGraphic );
+ if( rIStream.GetError() )
+ {
+ nStatus = ERRCODE_GRFILTER_FORMATERROR;
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_BMP))
+ {
+ // #i15508# added BMP type (checked, works)
+ eLinkType = GfxLinkType::NativeBmp;
+ }
+ }
+ else if( aFilterName.equalsIgnoreAsciiCase( IMP_MOV ) )
+ {
+ ReadGraphic( rIStream, rGraphic );
+ if( rIStream.GetError() )
+ nStatus = ERRCODE_GRFILTER_FORMATERROR;
+ else
+ {
+ rGraphic.SetDefaultType();
+ rIStream.Seek( STREAM_SEEK_TO_END );
+ eLinkType = GfxLinkType::NativeMov;
+ }
+ }
+ else if( aFilterName.equalsIgnoreAsciiCase( IMP_WMF ) ||
+ aFilterName.equalsIgnoreAsciiCase( IMP_EMF ) )
+ {
+ // use new UNO API service, do not directly import but create a
+ // Graphic that contains the original data and decomposes to
+ // primitives on demand
+
+ const sal_uInt32 nStreamLength(rIStream.remainingSize());
+ VectorGraphicDataArray aNewData(nStreamLength);
+ bool bOkay(false);
+
+ rIStream.ReadBytes(aNewData.begin(), nStreamLength);
+
+ if (!rIStream.GetError())
+ {
+ const bool bIsWmf(aFilterName.equalsIgnoreAsciiCase(IMP_WMF));
+ const VectorGraphicDataType aDataType(bIsWmf ? VectorGraphicDataType::Wmf : VectorGraphicDataType::Emf);
+ auto aVectorGraphicDataPtr =
+ std::make_shared<VectorGraphicData>(
+ aNewData,
+ rPath,
+ aDataType);
+
+ if (pExtHeader)
+ {
+ aVectorGraphicDataPtr->setWmfExternalHeader(*pExtHeader);
+ }
+
+ rGraphic = Graphic(aVectorGraphicDataPtr);
+ bOkay = true;
+ }
+
+ if (bOkay)
+ {
+ eLinkType = GfxLinkType::NativeWmf;
+ }
+ else
+ {
+ nStatus = ERRCODE_GRFILTER_FILTERERROR;
+ }
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_PDF))
+ {
+ if (vcl::ImportPDF(rIStream, rGraphic))
+ eLinkType = GfxLinkType::NativePdf;
+ else
+ nStatus = ERRCODE_GRFILTER_FILTERERROR;
+ }
+ else
+ nStatus = ERRCODE_GRFILTER_FILTERERROR;
+ }
+ else
+ {
+ ImpFilterLibCacheEntry* pFilter = nullptr;
+
+ if (!aFilterPath.isEmpty())
+ {
+ // find first filter in filter paths
+ ImpFilterLibCache &rCache = Cache::get();
+ sal_Int32 nIdx{0};
+ do {
+ pFilter = rCache.GetFilter(aFilterPath.getToken(0, ';', nIdx), aFilterName, aExternalFilterName);
+ } while (nIdx>=0 && pFilter==nullptr);
+ }
+
+ if( !pFilter )
+ nStatus = ERRCODE_GRFILTER_FILTERERROR;
+ else
+ {
+ PFilterCall pFunc = pFilter->GetImportFunction();
+
+ if( !pFunc )
+ nStatus = ERRCODE_GRFILTER_FILTERERROR;
+ else
+ {
+ std::unique_ptr<FilterConfigItem> pFilterConfigItem;
+ OUString aShortName;
+ if( nFormat != GRFILTER_FORMAT_DONTKNOW )
+ {
+ aShortName = GetImportFormatShortName( nFormat ).toAsciiUpperCase();
+ if (aShortName == "PCD" && !utl::ConfigManager::IsFuzzing())
+ {
+ OUString aFilterConfigPath( "Office.Common/Filter/Graphic/Import/PCD" );
+ pFilterConfigItem = std::make_unique<FilterConfigItem>( aFilterConfigPath );
+ }
+ }
+ if( !(*pFunc)( rIStream, rGraphic, pFilterConfigItem.get() ) )
+ nStatus = ERRCODE_GRFILTER_FORMATERROR;
+ else
+ {
+ // try to set link type if format matches
+ if( nFormat != GRFILTER_FORMAT_DONTKNOW )
+ {
+ if( aShortName.startsWith( TIF_SHORTNAME ) )
+ eLinkType = GfxLinkType::NativeTif;
+ else if( aShortName.startsWith( MET_SHORTNAME ) )
+ eLinkType = GfxLinkType::NativeMet;
+ else if( aShortName.startsWith( PCT_SHORTNAME ) )
+ eLinkType = GfxLinkType::NativePct;
+ }
+ }
+ }
+ }
+ }
+
+ if( nStatus == ERRCODE_NONE && ( eLinkType != GfxLinkType::NONE ) && !rGraphic.GetReaderContext() && !bLinkSet )
+ {
+ if (!pGraphicContent)
+ {
+ const sal_uLong nStreamEnd = rIStream.Tell();
+ nGraphicContentSize = nStreamEnd - nStreamBegin;
+
+ if (nGraphicContentSize > 0)
+ {
+ try
+ {
+ pGraphicContent.reset(new sal_uInt8[nGraphicContentSize]);
+ }
+ catch (const std::bad_alloc&)
+ {
+ nStatus = ERRCODE_GRFILTER_TOOBIG;
+ }
+
+ if( nStatus == ERRCODE_NONE )
+ {
+ rIStream.Seek(nStreamBegin);
+ rIStream.ReadBytes(pGraphicContent.get(), nGraphicContentSize);
+ }
+ }
+ }
+ if( nStatus == ERRCODE_NONE )
+ {
+ rGraphic.SetGfxLink(std::make_shared<GfxLink>(std::move(pGraphicContent), nGraphicContentSize, eLinkType));
+ }
+ }
+
+ // Set error code or try to set native buffer
+ if( nStatus != ERRCODE_NONE )
+ {
+ ImplSetError( nStatus, &rIStream );
+ rIStream.Seek( nStreamBegin );
+ rGraphic.Clear();
+ }
+
+ return nStatus;
+}
+
+ErrCode GraphicFilter::ExportGraphic( const Graphic& rGraphic, const INetURLObject& rPath,
+ sal_uInt16 nFormat, const css::uno::Sequence< css::beans::PropertyValue >* pFilterData )
+{
+ SAL_INFO( "vcl.filter", "GraphicFilter::ExportGraphic() (thb)" );
+ ErrCode nRetValue = ERRCODE_GRFILTER_FORMATERROR;
+ SAL_WARN_IF( rPath.GetProtocol() == INetProtocol::NotValid, "vcl.filter", "GraphicFilter::ExportGraphic() : ProtType == INetProtocol::NotValid" );
+
+ OUString aMainUrl(rPath.GetMainURL(INetURLObject::DecodeMechanism::NONE));
+ bool bAlreadyExists = utl::UCBContentHelper::IsDocument(aMainUrl);
+
+ std::unique_ptr<SvStream> xStream(::utl::UcbStreamHelper::CreateStream( aMainUrl, StreamMode::WRITE | StreamMode::TRUNC ));
+ if (xStream)
+ {
+ nRetValue = ExportGraphic( rGraphic, aMainUrl, *xStream, nFormat, pFilterData );
+ xStream.reset();
+
+ if( ( ERRCODE_NONE != nRetValue ) && !bAlreadyExists )
+ utl::UCBContentHelper::Kill(aMainUrl);
+ }
+ return nRetValue;
+}
+
+#ifdef DISABLE_DYNLOADING
+
+extern "C" bool egiGraphicExport( SvStream& rStream, Graphic& rGraphic, FilterConfigItem* pConfigItem );
+extern "C" bool epsGraphicExport( SvStream& rStream, Graphic& rGraphic, FilterConfigItem* pConfigItem );
+extern "C" bool etiGraphicExport( SvStream& rStream, Graphic& rGraphic, FilterConfigItem* pConfigItem );
+
+#endif
+
+ErrCode GraphicFilter::ExportGraphic( const Graphic& rGraphic, const OUString& rPath,
+ SvStream& rOStm, sal_uInt16 nFormat, const css::uno::Sequence< css::beans::PropertyValue >* pFilterData )
+{
+ SAL_INFO( "vcl.filter", "GraphicFilter::ExportGraphic() (thb)" );
+ sal_uInt16 nFormatCount = GetExportFormatCount();
+
+ ResetLastError();
+
+ if( nFormat == GRFILTER_FORMAT_DONTKNOW )
+ {
+ INetURLObject aURL( rPath );
+ OUString aExt( aURL.GetFileExtension().toAsciiUpperCase() );
+
+ for( sal_uInt16 i = 0; i < nFormatCount; i++ )
+ {
+ if ( pConfig->GetExportFormatExtension( i ).equalsIgnoreAsciiCase( aExt ) )
+ {
+ nFormat=i;
+ break;
+ }
+ }
+ }
+ if( nFormat >= nFormatCount )
+ return ImplSetError( ERRCODE_GRFILTER_FORMATERROR );
+
+ FilterConfigItem aConfigItem( pFilterData );
+ OUString aFilterName( pConfig->GetExportFilterName( nFormat ) );
+ OUString aExternalFilterName(pConfig->GetExternalFilterName(nFormat, true));
+ 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.get(),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 ( pConfig->IsExportInternalFilter( nFormat ) )
+ {
+ if( aFilterName.equalsIgnoreAsciiCase( EXP_BMP ) )
+ {
+ BitmapEx aBmp( aGraphic.GetBitmapEx() );
+ BmpConversion nColorRes = static_cast<BmpConversion>(aConfigItem.ReadInt32( "Colors", 0 ));
+ if ( nColorRes != BmpConversion::NNONE && ( nColorRes <= BmpConversion::N24Bit) )
+ {
+ if( !aBmp.Convert( nColorRes ) )
+ aBmp = aGraphic.GetBitmapEx();
+ }
+ bool bRleCoding = aConfigItem.ReadBool( "RLE_Coding", true );
+ // save RLE encoded?
+ WriteDIB(aBmp, rOStm, bRleCoding);
+
+ 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());
+
+ aMTF.Write( rOStm );
+
+ if( rOStm.GetError() )
+ nStatus = ERRCODE_GRFILTER_IOERROR;
+ }
+ else if ( aFilterName.equalsIgnoreAsciiCase( EXP_WMF ) )
+ {
+ bool bDone(false);
+
+ // do we have a native Vector Graphic Data RenderGraphic, whose data can be written directly?
+ auto const & rVectorGraphicDataPtr(rGraphic.getVectorGraphicData());
+
+ if (rVectorGraphicDataPtr
+ && rVectorGraphicDataPtr->getVectorGraphicDataArrayLength()
+ && VectorGraphicDataType::Wmf == rVectorGraphicDataPtr->getVectorGraphicDataType())
+ {
+ rOStm.WriteBytes(rVectorGraphicDataPtr->getVectorGraphicDataArray().getConstArray(), rVectorGraphicDataPtr->getVectorGraphicDataArrayLength());
+
+ if (rOStm.GetError())
+ {
+ nStatus = ERRCODE_GRFILTER_IOERROR;
+ }
+ else
+ {
+ bDone = true;
+ }
+ }
+
+ if (!bDone)
+ {
+ // #i119735# just use GetGDIMetaFile, it will create a buffered version of contained bitmap now automatically
+ if (!ConvertGDIMetaFileToWMF(aGraphic.GetGDIMetaFile(), rOStm, &aConfigItem))
+ nStatus = ERRCODE_GRFILTER_FORMATERROR;
+
+ if (rOStm.GetError())
+ nStatus = ERRCODE_GRFILTER_IOERROR;
+ }
+ }
+ else if ( aFilterName.equalsIgnoreAsciiCase( EXP_EMF ) )
+ {
+ bool bDone(false);
+
+ // do we have a native Vector Graphic Data RenderGraphic, whose data can be written directly?
+ auto const & rVectorGraphicDataPtr(rGraphic.getVectorGraphicData());
+
+ if (rVectorGraphicDataPtr
+ && rVectorGraphicDataPtr->getVectorGraphicDataArrayLength()
+ && VectorGraphicDataType::Emf == rVectorGraphicDataPtr->getVectorGraphicDataType())
+ {
+ rOStm.WriteBytes(rVectorGraphicDataPtr->getVectorGraphicDataArray().getConstArray(), rVectorGraphicDataPtr->getVectorGraphicDataArrayLength());
+
+ if (rOStm.GetError())
+ {
+ nStatus = ERRCODE_GRFILTER_IOERROR;
+ }
+ else
+ {
+ bDone = true;
+ }
+ }
+
+ if (!bDone)
+ {
+ // #i119735# just use GetGDIMetaFile, it will create a buffered version of contained bitmap now automatically
+ if (!ConvertGDIMetaFileToEMF(aGraphic.GetGDIMetaFile(), rOStm))
+ nStatus = ERRCODE_GRFILTER_FORMATERROR;
+
+ if (rOStm.GetError())
+ nStatus = ERRCODE_GRFILTER_IOERROR;
+ }
+ }
+ else if( aFilterName.equalsIgnoreAsciiCase( EXP_JPEG ) )
+ {
+ bool bExportedGrayJPEG = false;
+ if( !ExportJPEG( rOStm, aGraphic, pFilterData, &bExportedGrayJPEG ) )
+ nStatus = ERRCODE_GRFILTER_FORMATERROR;
+
+ if( rOStm.GetError() )
+ nStatus = ERRCODE_GRFILTER_IOERROR;
+ }
+ else if ( aFilterName.equalsIgnoreAsciiCase( EXP_PNG ) )
+ {
+ vcl::PNGWriter aPNGWriter( aGraphic.GetBitmapEx(), pFilterData );
+ if ( pFilterData )
+ {
+ for ( const auto& rPropVal : *pFilterData )
+ {
+ if ( rPropVal.Name == "AdditionalChunks" )
+ {
+ css::uno::Sequence< css::beans::PropertyValue > aAdditionalChunkSequence;
+ if ( rPropVal.Value >>= aAdditionalChunkSequence )
+ {
+ for ( const auto& rAdditionalChunk : std::as_const(aAdditionalChunkSequence) )
+ {
+ if ( rAdditionalChunk.Name.getLength() == 4 )
+ {
+ sal_uInt32 nChunkType = 0;
+ for ( sal_Int32 k = 0; k < 4; k++ )
+ {
+ nChunkType <<= 8;
+ nChunkType |= static_cast<sal_uInt8>(rAdditionalChunk.Name[ k ]);
+ }
+ css::uno::Sequence< sal_Int8 > aByteSeq;
+ if ( rAdditionalChunk.Value >>= aByteSeq )
+ {
+ std::vector< vcl::PNGWriter::ChunkData >& rChunkData = aPNGWriter.GetChunks();
+ if ( !rChunkData.empty() )
+ {
+ sal_uInt32 nChunkLen = aByteSeq.getLength();
+
+ vcl::PNGWriter::ChunkData aChunkData;
+ aChunkData.nType = nChunkType;
+ if ( nChunkLen )
+ {
+ aChunkData.aData.resize( nChunkLen );
+ memcpy( aChunkData.aData.data(), aByteSeq.getConstArray(), nChunkLen );
+ }
+ std::vector< vcl::PNGWriter::ChunkData >::iterator aIter = rChunkData.end() - 1;
+ rChunkData.insert( aIter, aChunkData );
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ aPNGWriter.Write( rOStm );
+
+ if( rOStm.GetError() )
+ nStatus = ERRCODE_GRFILTER_IOERROR;
+ }
+ else if( aFilterName.equalsIgnoreAsciiCase( EXP_SVG ) )
+ {
+ bool bDone(false);
+
+ // do we have a native Vector Graphic Data RenderGraphic, whose data can be written directly?
+ auto const & rVectorGraphicDataPtr(rGraphic.getVectorGraphicData());
+
+ if (rVectorGraphicDataPtr
+ && rVectorGraphicDataPtr->getVectorGraphicDataArrayLength()
+ && VectorGraphicDataType::Svg == rVectorGraphicDataPtr->getVectorGraphicDataType())
+ {
+ rOStm.WriteBytes(rVectorGraphicDataPtr->getVectorGraphicDataArray().getConstArray(), rVectorGraphicDataPtr->getVectorGraphicDataArrayLength());
+
+ if( rOStm.GetError() )
+ {
+ nStatus = ERRCODE_GRFILTER_IOERROR;
+ }
+ else
+ {
+ bDone = true;
+ }
+ }
+
+ if( !bDone )
+ {
+ // do the normal GDIMetaFile export instead
+ try
+ {
+ css::uno::Reference< css::uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() );
+
+ css::uno::Reference< css::xml::sax::XDocumentHandler > xSaxWriter(
+ css::xml::sax::Writer::create( xContext ), css::uno::UNO_QUERY_THROW);
+ css::uno::Sequence< css::uno::Any > aArguments( 1 );
+ aArguments[ 0 ] <<= aConfigItem.GetFilterData();
+ css::uno::Reference< css::svg::XSVGWriter > xSVGWriter(
+ xContext->getServiceManager()->createInstanceWithArgumentsAndContext( "com.sun.star.svg.SVGWriter", aArguments, xContext),
+ css::uno::UNO_QUERY );
+ if( xSaxWriter.is() && xSVGWriter.is() )
+ {
+ css::uno::Reference< css::io::XActiveDataSource > xActiveDataSource(
+ xSaxWriter, css::uno::UNO_QUERY );
+
+ if( xActiveDataSource.is() )
+ {
+ const css::uno::Reference< css::uno::XInterface > xStmIf(
+ static_cast< ::cppu::OWeakObject* >( new ImpFilterOutputStream( rOStm ) ) );
+
+ SvMemoryStream aMemStm( 65535, 65535 );
+
+ // #i119735# just use GetGDIMetaFile, it will create a buffered version of contained bitmap now automatically
+ const_cast<GDIMetaFile&>( aGraphic.GetGDIMetaFile() ).Write( aMemStm );
+
+ 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
+ nStatus = ERRCODE_GRFILTER_FILTERERROR;
+ }
+ else
+ {
+ sal_Int32 nIdx {aFilterPath.isEmpty() ? -1 : 0};
+ while (nIdx>=0)
+ {
+#ifndef DISABLE_DYNLOADING
+ OUString aPhysicalName( ImpCreateFullFilterPath( aFilterPath.getToken(0, ';', nIdx), aFilterName ) );
+ osl::Module aLibrary( aPhysicalName );
+
+ PFilterCall pFunc = nullptr;
+ if (aExternalFilterName == "egi")
+ pFunc = reinterpret_cast<PFilterCall>(aLibrary.getFunctionSymbol("egiGraphicExport"));
+ else if (aExternalFilterName == "eps")
+ pFunc = reinterpret_cast<PFilterCall>(aLibrary.getFunctionSymbol("epsGraphicExport"));
+ else if (aExternalFilterName == "eti")
+ pFunc = reinterpret_cast<PFilterCall>(aLibrary.getFunctionSymbol("etiGraphicExport"));
+ // Execute dialog in DLL
+ #else
+ --nIdx; // Just one iteration
+ PFilterCall pFunc = NULL;
+ if (aExternalFilterName == "egi")
+ pFunc = egiGraphicExport;
+ else if (aExternalFilterName == "eps")
+ pFunc = epsGraphicExport;
+ else if (aExternalFilterName == "eti")
+ pFunc = etiGraphicExport;
+ #endif
+ if( pFunc )
+ {
+ if ( !(*pFunc)( rOStm, aGraphic, &aConfigItem ) )
+ nStatus = ERRCODE_GRFILTER_FORMATERROR;
+ break;
+ }
+ else
+ nStatus = ERRCODE_GRFILTER_FILTERERROR;
+ }
+ }
+ }
+ if( nStatus != ERRCODE_NONE )
+ {
+ ImplSetError( nStatus, &rOStm );
+ }
+ return nStatus;
+}
+
+
+void GraphicFilter::ResetLastError()
+{
+ pErrorEx->nStreamError = 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;
+ OString 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;
+
+ default:
+ break;
+ }
+ if( GraphicType::NONE == rData.maGraphic.GetType() || rData.maGraphic.GetReaderContext() ) // Import
+ {
+ // Import
+ nFormat = GetImportFormatNumberForShortName( OStringToOUString( aShortName, RTL_TEXTENCODING_UTF8) );
+ bRet = ImportGraphic( rData.maGraphic, OUString(), rData.mrStm, nFormat ) == ERRCODE_NONE;
+ }
+ else if( !aShortName.isEmpty() )
+ {
+ // Export
+#if defined(IOS) || defined(ANDROID)
+ if (aShortName == PNG_SHORTNAME)
+ {
+ aFilterData.realloc(aFilterData.getLength() + 1);
+ aFilterData[aFilterData.getLength() - 1].Name = "Compression";
+ // We "know" that this gets passed to zlib's deflateInit2_(). 1 means best speed.
+ aFilterData[aFilterData.getLength() - 1].Value <<= static_cast<sal_Int32>(1);
+ }
+#endif
+ nFormat = GetExportFormatNumberForShortName( OStringToOUString(aShortName, RTL_TEXTENCODING_UTF8) );
+ bRet = ExportGraphic( rData.maGraphic, OUString(), rData.mrStm, nFormat, &aFilterData ) == ERRCODE_NONE;
+ }
+
+ return bRet;
+}
+
+namespace
+{
+ class StandardGraphicFilter
+ {
+ public:
+ StandardGraphicFilter()
+ {
+ m_aFilter.GetImportFormatCount();
+ }
+ GraphicFilter m_aFilter;
+ };
+
+ class theGraphicFilter : public rtl::Static<StandardGraphicFilter, theGraphicFilter> {};
+}
+
+GraphicFilter& GraphicFilter::GetGraphicFilter()
+{
+ return theGraphicFilter::get().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(1);
+ aFilterData[0].Name = "Compression";
+ aFilterData[0].Value <<= sal_uInt32(9);
+
+ sal_uInt16 nFilterFormat = GetExportFormatNumberForShortName("PNG");
+ return ExportGraphic(rGraphic, OUString(), rOutputStream, nFilterFormat, &aFilterData);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/graphicfilter2.cxx b/vcl/source/filter/graphicfilter2.cxx
new file mode 100644
index 000000000..0faaaeb81
--- /dev/null
+++ b/vcl/source/filter/graphicfilter2.cxx
@@ -0,0 +1,1165 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <TypeSerializer.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <unotools/ucbstreamhelper.hxx>
+#include "graphicfilter_internal.hxx"
+
+#define DATA_SIZE 640
+
+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;
+
+ rStm.SetEndian( nOldFormat );
+ }
+ return bRet;
+}
+
+void GraphicDescriptor::ImpConstruct()
+{
+ nFormat = GraphicFileFormat::NOT;
+ nBitsPerPixel = 0;
+ nPlanes = 0;
+ mnNumberOfImageComponents = 0;
+ bIsTransparent = false;
+ bIsAlpha = false;
+}
+
+bool GraphicDescriptor::ImpDetectBMP( SvStream& rStm, bool bExtendedInfo )
+{
+ sal_uInt16 nTemp16 = 0;
+ bool bRet = false;
+ sal_Int32 nStmPos = rStm.Tell();
+
+ rStm.SetEndian( SvStreamEndian::LITTLE );
+ rStm.ReadUInt16( nTemp16 );
+
+ // OS/2-BitmapArray
+ if ( nTemp16 == 0x4142 )
+ {
+ rStm.SeekRel( 0x0c );
+ rStm.ReadUInt16( nTemp16 );
+ }
+
+ // Bitmap
+ if ( nTemp16 == 0x4d42 )
+ {
+ nFormat = GraphicFileFormat::BMP;
+ bRet = true;
+
+ if ( bExtendedInfo )
+ {
+ sal_uInt32 nTemp32;
+ sal_uInt32 nCompression;
+
+ // up to first info
+ rStm.SeekRel( 0x10 );
+
+ // Pixel width
+ rStm.ReadUInt32( nTemp32 );
+ aPixSize.setWidth( nTemp32 );
+
+ // Pixel height
+ rStm.ReadUInt32( nTemp32 );
+ aPixSize.setHeight( nTemp32 );
+
+ // Planes
+ rStm.ReadUInt16( nTemp16 );
+ nPlanes = nTemp16;
+
+ // BitCount
+ rStm.ReadUInt16( nTemp16 );
+ nBitsPerPixel = nTemp16;
+
+ // Compression
+ rStm.ReadUInt32( nTemp32 );
+ nCompression = nTemp32;
+
+ // logical width
+ rStm.SeekRel( 4 );
+ rStm.ReadUInt32( nTemp32 );
+ if ( nTemp32 )
+ aLogSize.setWidth( ( aPixSize.Width() * 100000 ) / nTemp32 );
+
+ // logical height
+ rStm.ReadUInt32( nTemp32 );
+ if ( nTemp32 )
+ aLogSize.setHeight( ( aPixSize.Height() * 100000 ) / nTemp32 );
+
+ // further validation, check for rational values
+ if ( ( nBitsPerPixel > 24 ) || ( nCompression > 3 ) )
+ {
+ nFormat = GraphicFileFormat::NOT;
+ bRet = false;
+ }
+ }
+ }
+ rStm.Seek( nStmPos );
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectGIF( SvStream& rStm, bool bExtendedInfo )
+{
+ sal_uInt32 n32 = 0;
+ sal_uInt16 n16 = 0;
+ bool bRet = false;
+ sal_uInt8 cByte = 0;
+
+ sal_Int32 nStmPos = rStm.Tell();
+ rStm.SetEndian( SvStreamEndian::LITTLE );
+ rStm.ReadUInt32( n32 );
+
+ if ( n32 == 0x38464947 )
+ {
+ rStm.ReadUInt16( n16 );
+ if ( ( n16 == 0x6137 ) || ( n16 == 0x6139 ) )
+ {
+ nFormat = GraphicFileFormat::GIF;
+ bRet = true;
+
+ if ( bExtendedInfo )
+ {
+ sal_uInt16 nTemp16 = 0;
+
+ // Pixel width
+ rStm.ReadUInt16( nTemp16 );
+ aPixSize.setWidth( nTemp16 );
+
+ // Pixel height
+ rStm.ReadUInt16( nTemp16 );
+ aPixSize.setHeight( nTemp16 );
+
+ // Bits/Pixel
+ rStm.ReadUChar( cByte );
+ nBitsPerPixel = ( ( cByte & 112 ) >> 4 ) + 1;
+ }
+ }
+ }
+ rStm.Seek( nStmPos );
+ return bRet;
+}
+
+// returns the next jpeg marker, a return value of 0 represents an error
+static sal_uInt8 ImpDetectJPG_GetNextMarker( SvStream& rStm )
+{
+ sal_uInt8 nByte;
+ do
+ {
+ do
+ {
+ rStm.ReadUChar( nByte );
+ if (!rStm.good()) // as 0 is not allowed as marker,
+ return 0; // we can use it as errorcode
+ }
+ while ( nByte != 0xff );
+ do
+ {
+ rStm.ReadUChar( nByte );
+ if (!rStm.good())
+ return 0;
+ }
+ while( nByte == 0xff );
+ }
+ while( nByte == 0 ); // 0xff00 represents 0xff and not a marker,
+ // the marker detection has to be restarted.
+ return nByte;
+}
+
+bool GraphicDescriptor::ImpDetectJPG( SvStream& rStm, bool bExtendedInfo )
+{
+ sal_uInt32 nTemp32 = 0;
+ bool bRet = false;
+
+ sal_Int32 nStmPos = rStm.Tell();
+
+ rStm.SetEndian( SvStreamEndian::BIG );
+ rStm.ReadUInt32( nTemp32 );
+
+ // compare upper 24 bits
+ if( 0xffd8ff00 == ( nTemp32 & 0xffffff00 ) )
+ {
+ nFormat = GraphicFileFormat::JPG;
+ bRet = true;
+
+ if ( bExtendedInfo )
+ {
+ rStm.SeekRel( -2 );
+
+ ErrCode nError( rStm.GetError() );
+
+ bool bScanFailure = false;
+ bool bScanFinished = false;
+ MapMode aMap;
+
+ while (!bScanFailure && !bScanFinished && rStm.good())
+ {
+ sal_uInt8 nMarker = ImpDetectJPG_GetNextMarker( rStm );
+ switch( nMarker )
+ {
+ // fixed size marker, not having a two byte length parameter
+ case 0xd0 : // RST0
+ case 0xd1 :
+ case 0xd2 :
+ case 0xd3 :
+ case 0xd4 :
+ case 0xd5 :
+ case 0xd6 :
+ case 0xd7 : // RST7
+ case 0x01 : // TEM
+ break;
+
+ case 0xd8 : // SOI (has already been checked, there should not be a second one)
+ case 0x00 : // marker is invalid, we should stop now
+ bScanFailure = true;
+ break;
+
+ case 0xd9 : // EOI
+ bScanFinished = true;
+ break;
+
+ // per default we assume marker segments containing a length parameter
+ default :
+ {
+ sal_uInt16 nLength = 0;
+ rStm.ReadUInt16( nLength );
+
+ if ( nLength < 2 )
+ bScanFailure = true;
+ else
+ {
+ sal_uInt32 nNextMarkerPos = rStm.Tell() + nLength - 2;
+ switch( nMarker )
+ {
+ case 0xe0 : // APP0 Marker
+ {
+ if ( nLength == 16 )
+ {
+ sal_Int32 nIdentifier = 0;
+ rStm.ReadInt32( nIdentifier );
+ if ( nIdentifier == 0x4a464946 ) // JFIF Identifier
+ {
+ sal_uInt8 nStringTerminator = 0;
+ sal_uInt8 nMajorRevision = 0;
+ sal_uInt8 nMinorRevision = 0;
+ sal_uInt8 nUnits = 0;
+ sal_uInt16 nHorizontalResolution = 0;
+ sal_uInt16 nVerticalResolution = 0;
+ sal_uInt8 nHorzThumbnailPixelCount = 0;
+ sal_uInt8 nVertThumbnailPixelCount = 0;
+
+ rStm.ReadUChar( nStringTerminator )
+ .ReadUChar( nMajorRevision )
+ .ReadUChar( nMinorRevision )
+ .ReadUChar( nUnits )
+ .ReadUInt16( nHorizontalResolution )
+ .ReadUInt16( nVerticalResolution )
+ .ReadUChar( nHorzThumbnailPixelCount )
+ .ReadUChar( nVertThumbnailPixelCount );
+
+ // setting the logical size
+ if ( nUnits && nHorizontalResolution && nVerticalResolution )
+ {
+ aMap.SetMapUnit( nUnits == 1 ? MapUnit::MapInch : MapUnit::MapCM );
+ aMap.SetScaleX( Fraction( 1, nHorizontalResolution ) );
+ aMap.SetScaleY( Fraction( 1, nVerticalResolution ) );
+ aLogSize = OutputDevice::LogicToLogic( aPixSize, aMap, MapMode( MapUnit::Map100thMM ) );
+ }
+ }
+ }
+ }
+ break;
+
+ // Start of Frame Markers
+ case 0xc0 : // SOF0
+ case 0xc1 : // SOF1
+ case 0xc2 : // SOF2
+ case 0xc3 : // SOF3
+ case 0xc5 : // SOF5
+ case 0xc6 : // SOF6
+ case 0xc7 : // SOF7
+ case 0xc9 : // SOF9
+ case 0xca : // SOF10
+ case 0xcb : // SOF11
+ case 0xcd : // SOF13
+ case 0xce : // SOF14
+ case 0xcf : // SOF15
+ {
+ sal_uInt8 nSamplePrecision = 0;
+ sal_uInt16 nNumberOfLines = 0;
+ sal_uInt16 nSamplesPerLine = 0;
+ sal_uInt8 nNumberOfImageComponents = 0;
+ sal_uInt8 nComponentsIdentifier = 0;
+ sal_uInt8 nSamplingFactor = 0;
+ sal_uInt8 nQuantizationTableDestinationSelector = 0;
+ rStm.ReadUChar( nSamplePrecision )
+ .ReadUInt16( nNumberOfLines )
+ .ReadUInt16( nSamplesPerLine )
+ .ReadUChar( nNumberOfImageComponents )
+ .ReadUChar( nComponentsIdentifier )
+ .ReadUChar( nSamplingFactor )
+ .ReadUChar( nQuantizationTableDestinationSelector );
+ mnNumberOfImageComponents = nNumberOfImageComponents;
+
+ // nSamplingFactor (lower nibble: vertical,
+ // upper nibble: horizontal) is unused
+
+ aPixSize.setHeight( nNumberOfLines );
+ aPixSize.setWidth( nSamplesPerLine );
+ nBitsPerPixel = ( nNumberOfImageComponents == 3 ? 24 : nNumberOfImageComponents == 1 ? 8 : 0 );
+ nPlanes = 1;
+
+ if (aMap.GetMapUnit() != MapUnit::MapPixel)
+ // We already know the DPI, but the
+ // pixel size arrived later, so do the
+ // conversion again.
+ aLogSize = OutputDevice::LogicToLogic(
+ aPixSize, aMap, MapMode(MapUnit::Map100thMM));
+
+ bScanFinished = true;
+ }
+ break;
+ }
+ rStm.Seek( nNextMarkerPos );
+ }
+ }
+ break;
+ }
+ }
+ rStm.SetError( nError );
+ }
+ }
+ rStm.Seek( nStmPos );
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectPCD( SvStream& rStm, bool )
+{
+ bool bRet = false;
+
+ sal_Int32 nStmPos = rStm.Tell();
+ rStm.SetEndian( SvStreamEndian::LITTLE );
+
+ sal_uInt32 nTemp32 = 0;
+ sal_uInt16 nTemp16 = 0;
+ sal_uInt8 cByte = 0;
+
+ rStm.SeekRel( 2048 );
+ rStm.ReadUInt32( nTemp32 );
+ rStm.ReadUInt16( nTemp16 );
+ rStm.ReadUChar( cByte );
+
+ if ( ( nTemp32 == 0x5f444350 ) &&
+ ( nTemp16 == 0x5049 ) &&
+ ( cByte == 0x49 ) )
+ {
+ nFormat = GraphicFileFormat::PCD;
+ bRet = true;
+ }
+ rStm.Seek( nStmPos );
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectPCX( SvStream& rStm )
+{
+ // ! Because 0x0a can be interpreted as LF too ...
+ // we can't be sure that this special sign represent a PCX file only.
+ // Every Ascii file is possible here :-(
+ // We must detect the whole header.
+
+ bool bRet = false;
+ sal_uInt8 cByte = 0;
+
+ sal_Int32 nStmPos = rStm.Tell();
+ rStm.SetEndian( SvStreamEndian::LITTLE );
+ rStm.ReadUChar( cByte );
+
+ if ( cByte == 0x0a )
+ {
+ nFormat = GraphicFileFormat::PCX;
+
+ sal_uInt16 nTemp16;
+ sal_uInt16 nXmin;
+ sal_uInt16 nXmax;
+ sal_uInt16 nYmin;
+ sal_uInt16 nYmax;
+ sal_uInt16 nDPIx;
+ sal_uInt16 nDPIy;
+
+ rStm.SeekRel( 1 );
+
+ // compression
+ rStm.ReadUChar( cByte );
+
+ bRet = (cByte==0 || cByte ==1);
+ if (bRet)
+ {
+ // Bits/Pixel
+ rStm.ReadUChar( cByte );
+ nBitsPerPixel = cByte;
+
+ // image dimensions
+ rStm.ReadUInt16( nTemp16 );
+ nXmin = nTemp16;
+ rStm.ReadUInt16( nTemp16 );
+ nYmin = nTemp16;
+ rStm.ReadUInt16( nTemp16 );
+ nXmax = nTemp16;
+ rStm.ReadUInt16( nTemp16 );
+ nYmax = nTemp16;
+
+ aPixSize.setWidth( nXmax - nXmin + 1 );
+ aPixSize.setHeight( nYmax - nYmin + 1 );
+
+ // resolution
+ rStm.ReadUInt16( nTemp16 );
+ nDPIx = nTemp16;
+ rStm.ReadUInt16( nTemp16 );
+ nDPIy = nTemp16;
+
+ // set logical size
+ MapMode aMap( MapUnit::MapInch, Point(),
+ Fraction( 1, nDPIx ), Fraction( 1, nDPIy ) );
+ aLogSize = OutputDevice::LogicToLogic( aPixSize, aMap,
+ MapMode( MapUnit::Map100thMM ) );
+
+ // number of color planes
+ cByte = 5; // Illegal value in case of EOF.
+ rStm.SeekRel( 49 );
+ rStm.ReadUChar( cByte );
+ nPlanes = cByte;
+
+ bRet = (nPlanes<=4);
+ }
+ }
+
+ rStm.Seek( nStmPos );
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectPNG( SvStream& rStm, bool bExtendedInfo )
+{
+ sal_uInt32 nTemp32 = 0;
+ bool bRet = false;
+
+ sal_Int32 nStmPos = rStm.Tell();
+ rStm.SetEndian( SvStreamEndian::BIG );
+ rStm.ReadUInt32( nTemp32 );
+
+ if ( nTemp32 == 0x89504e47 )
+ {
+ rStm.ReadUInt32( nTemp32 );
+ if ( nTemp32 == 0x0d0a1a0a )
+ {
+ nFormat = GraphicFileFormat::PNG;
+ bRet = true;
+
+ if ( bExtendedInfo )
+ {
+ do {
+ sal_uInt8 cByte = 0;
+
+ // IHDR-Chunk
+ rStm.SeekRel( 8 );
+
+ // width
+ rStm.ReadUInt32( nTemp32 );
+ if (!rStm.good())
+ break;
+ aPixSize.setWidth( nTemp32 );
+
+ // height
+ rStm.ReadUInt32( nTemp32 );
+ if (!rStm.good())
+ break;
+ aPixSize.setHeight( nTemp32 );
+
+ // Bits/Pixel
+ rStm.ReadUChar( cByte );
+ if (!rStm.good())
+ break;
+ nBitsPerPixel = cByte;
+
+ // Colour type - check whether it supports alpha values
+ sal_uInt8 cColType = 0;
+ rStm.ReadUChar( cColType );
+ if (!rStm.good())
+ break;
+ bIsAlpha = bIsTransparent = ( cColType == 4 || cColType == 6 );
+
+ // Planes always 1;
+ // compression always
+ nPlanes = 1;
+
+ sal_uInt32 nLen32 = 0;
+ nTemp32 = 0;
+
+ rStm.SeekRel( 7 );
+
+ // read up to the start of the image
+ rStm.ReadUInt32( nLen32 );
+ rStm.ReadUInt32( nTemp32 );
+ while( ( nTemp32 != 0x49444154 ) && rStm.good() )
+ {
+ if ( nTemp32 == 0x70485973 ) // physical pixel dimensions
+ {
+ sal_uLong nXRes;
+ sal_uLong nYRes;
+
+ // horizontal resolution
+ nTemp32 = 0;
+ rStm.ReadUInt32( nTemp32 );
+ nXRes = nTemp32;
+
+ // vertical resolution
+ nTemp32 = 0;
+ rStm.ReadUInt32( nTemp32 );
+ nYRes = nTemp32;
+
+ // unit
+ cByte = 0;
+ rStm.ReadUChar( cByte );
+
+ if ( cByte )
+ {
+ if ( nXRes )
+ aLogSize.setWidth( (aPixSize.Width() * 100000) / nXRes );
+
+ if ( nYRes )
+ aLogSize.setHeight( (aPixSize.Height() * 100000) / nYRes );
+ }
+
+ nLen32 -= 9;
+ }
+ else if ( nTemp32 == 0x74524e53 ) // transparency
+ {
+ bIsTransparent = true;
+ bIsAlpha = ( cColType != 0 && cColType != 2 );
+ }
+
+ // skip forward to next chunk
+ rStm.SeekRel( 4 + nLen32 );
+ rStm.ReadUInt32( nLen32 );
+ rStm.ReadUInt32( nTemp32 );
+ }
+ } while (false);
+ }
+ }
+ }
+ rStm.Seek( nStmPos );
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectTIF( SvStream& rStm, bool bExtendedInfo )
+{
+ bool bRet = false;
+ sal_uInt8 cByte1 = 0;
+ sal_uInt8 cByte2 = 1;
+
+ sal_Int32 nStmPos = rStm.Tell();
+ rStm.ReadUChar( cByte1 );
+ rStm.ReadUChar( cByte2 );
+ if ( cByte1 == cByte2 )
+ {
+ bool bDetectOk = false;
+
+ if ( cByte1 == 0x49 )
+ {
+ rStm.SetEndian( SvStreamEndian::LITTLE );
+ bDetectOk = true;
+ }
+ else if ( cByte1 == 0x4d )
+ {
+ rStm.SetEndian( SvStreamEndian::BIG );
+ bDetectOk = true;
+ }
+
+ if ( bDetectOk )
+ {
+ sal_uInt16 nTemp16 = 0;
+
+ rStm.ReadUInt16( nTemp16 );
+ if ( nTemp16 == 0x2a )
+ {
+ nFormat = GraphicFileFormat::TIF;
+ bRet = true;
+
+ if ( bExtendedInfo )
+ {
+ sal_uLong nCount;
+ sal_uLong nMax = DATA_SIZE - 48;
+ sal_uInt32 nTemp32 = 0;
+
+ // Offset of the first IFD
+ rStm.ReadUInt32( nTemp32 );
+ nCount = nTemp32 + 2;
+ rStm.SeekRel( nCount - 0x08 );
+
+ if ( nCount < nMax )
+ {
+ bool bOk = false;
+
+ // read tags till we find Tag256 ( Width )
+ // do not read more bytes than DATA_SIZE
+ rStm.ReadUInt16( nTemp16 );
+ while ( nTemp16 != 256 )
+ {
+ bOk = nCount < nMax;
+ if ( !bOk )
+ {
+ break;
+ }
+ rStm.SeekRel( 10 );
+ rStm.ReadUInt16( nTemp16 );
+ nCount += 12;
+ }
+
+ if ( bOk )
+ {
+ // width
+ rStm.ReadUInt16( nTemp16 );
+ rStm.SeekRel( 4 );
+ if ( nTemp16 == 3 )
+ {
+ rStm.ReadUInt16( nTemp16 );
+ aPixSize.setWidth( nTemp16 );
+ rStm.SeekRel( 2 );
+ }
+ else
+ {
+ rStm.ReadUInt32( nTemp32 );
+ aPixSize.setWidth( nTemp32 );
+ }
+
+ // height
+ rStm.SeekRel( 2 );
+ rStm.ReadUInt16( nTemp16 );
+ rStm.SeekRel( 4 );
+ if ( nTemp16 == 3 )
+ {
+ rStm.ReadUInt16( nTemp16 );
+ aPixSize.setHeight( nTemp16 );
+ rStm.SeekRel( 2 );
+ }
+ else
+ {
+ rStm.ReadUInt32( nTemp32 );
+ aPixSize.setHeight( nTemp32 );
+ }
+
+ // Bits/Pixel
+ rStm.ReadUInt16( nTemp16 );
+ if ( nTemp16 == 258 )
+ {
+ rStm.SeekRel( 6 );
+ rStm.ReadUInt16( nTemp16 );
+ nBitsPerPixel = nTemp16;
+ rStm.SeekRel( 2 );
+ }
+ else
+ rStm.SeekRel( -2 );
+
+ // compression
+ rStm.ReadUInt16( nTemp16 );
+ if ( nTemp16 == 259 )
+ {
+ rStm.SeekRel( 6 );
+ rStm.ReadUInt16( nTemp16 ); // compression
+ rStm.SeekRel( 2 );
+ }
+ else
+ rStm.SeekRel( -2 );
+ }
+ }
+ }
+ }
+ }
+ }
+ rStm.Seek( nStmPos );
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectXBM( SvStream&, bool )
+{
+ bool bRet = aPathExt.startsWith( "xbm" );
+ if (bRet)
+ nFormat = GraphicFileFormat::XBM;
+
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectXPM( SvStream&, bool )
+{
+ bool bRet = aPathExt.startsWith( "xpm" );
+ if (bRet)
+ nFormat = GraphicFileFormat::XPM;
+
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectPBM( SvStream& rStm, bool )
+{
+ bool bRet = false;
+
+ // check file extension first, as this trumps the 2 ID bytes
+ if ( aPathExt.startsWith( "pbm" ) )
+ bRet = true;
+ else
+ {
+ sal_Int32 nStmPos = rStm.Tell();
+ sal_uInt8 nFirst = 0, nSecond = 0;
+ rStm.ReadUChar( nFirst ).ReadUChar( nSecond );
+ if ( nFirst == 'P' && ( ( nSecond == '1' ) || ( nSecond == '4' ) ) )
+ bRet = true;
+ rStm.Seek( nStmPos );
+ }
+
+ if ( bRet )
+ nFormat = GraphicFileFormat::PBM;
+
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectPGM( SvStream& rStm, bool )
+{
+ bool bRet = false;
+
+ if ( aPathExt.startsWith( "pgm" ) )
+ bRet = true;
+ else
+ {
+ sal_uInt8 nFirst = 0, nSecond = 0;
+ sal_Int32 nStmPos = rStm.Tell();
+ rStm.ReadUChar( nFirst ).ReadUChar( nSecond );
+ if ( nFirst == 'P' && ( ( nSecond == '2' ) || ( nSecond == '5' ) ) )
+ bRet = true;
+ rStm.Seek( nStmPos );
+ }
+
+ if ( bRet )
+ nFormat = GraphicFileFormat::PGM;
+
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectPPM( SvStream& rStm, bool )
+{
+ bool bRet = false;
+
+ if ( aPathExt.startsWith( "ppm" ) )
+ bRet = true;
+ else
+ {
+ sal_uInt8 nFirst = 0, nSecond = 0;
+ sal_Int32 nStmPos = rStm.Tell();
+ rStm.ReadUChar( nFirst ).ReadUChar( nSecond );
+ if ( nFirst == 'P' && ( ( nSecond == '3' ) || ( nSecond == '6' ) ) )
+ bRet = true;
+ rStm.Seek( nStmPos );
+ }
+
+ if ( bRet )
+ nFormat = GraphicFileFormat::PPM;
+
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectRAS( SvStream& rStm, bool )
+{
+ sal_uInt32 nMagicNumber = 0;
+ bool bRet = false;
+ sal_Int32 nStmPos = rStm.Tell();
+ rStm.SetEndian( SvStreamEndian::BIG );
+ rStm.ReadUInt32( nMagicNumber );
+ if ( nMagicNumber == 0x59a66a95 )
+ {
+ nFormat = GraphicFileFormat::RAS;
+ bRet = true;
+ }
+ rStm.Seek( nStmPos );
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectTGA( SvStream&, bool )
+{
+ bool bRet = aPathExt.startsWith( "tga" );
+ if (bRet)
+ nFormat = GraphicFileFormat::TGA;
+
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectPSD( SvStream& rStm, bool bExtendedInfo )
+{
+ bool bRet = false;
+
+ sal_uInt32 nMagicNumber = 0;
+ sal_Int32 nStmPos = rStm.Tell();
+ rStm.SetEndian( SvStreamEndian::BIG );
+ rStm.ReadUInt32( nMagicNumber );
+ if ( nMagicNumber == 0x38425053 )
+ {
+ sal_uInt16 nVersion = 0;
+ rStm.ReadUInt16( nVersion );
+ if ( nVersion == 1 )
+ {
+ bRet = true;
+ if ( bExtendedInfo )
+ {
+ sal_uInt16 nChannels = 0;
+ sal_uInt32 nRows = 0;
+ sal_uInt32 nColumns = 0;
+ sal_uInt16 nDepth = 0;
+ sal_uInt16 nMode = 0;
+ rStm.SeekRel( 6 ); // Pad
+ rStm.ReadUInt16( nChannels ).ReadUInt32( nRows ).ReadUInt32( nColumns ).ReadUInt16( nDepth ).ReadUInt16( nMode );
+ if ( ( nDepth == 1 ) || ( nDepth == 8 ) || ( nDepth == 16 ) )
+ {
+ nBitsPerPixel = ( nDepth == 16 ) ? 8 : nDepth;
+ switch ( nChannels )
+ {
+ case 4 :
+ case 3 :
+ nBitsPerPixel = 24;
+ [[fallthrough]];
+ case 2 :
+ case 1 :
+ aPixSize.setWidth( nColumns );
+ aPixSize.setHeight( nRows );
+ break;
+ default:
+ bRet = false;
+ }
+ }
+ else
+ bRet = false;
+ }
+ }
+ }
+
+ if ( bRet )
+ nFormat = GraphicFileFormat::PSD;
+ rStm.Seek( nStmPos );
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectEPS( SvStream& rStm, bool )
+{
+ // check the EPS preview and the file extension
+ sal_uInt32 nFirstLong = 0;
+ sal_uInt8 nFirstBytes[20] = {};
+ bool bRet = false;
+
+ sal_Int32 nStmPos = rStm.Tell();
+ rStm.SetEndian( SvStreamEndian::BIG );
+ rStm.ReadUInt32( nFirstLong );
+ rStm.SeekRel( -4 );
+ rStm.ReadBytes( &nFirstBytes, 20 );
+
+ if ( ( nFirstLong == 0xC5D0D3C6 ) || aPathExt.startsWith( "eps" ) ||
+ ( ImplSearchEntry( nFirstBytes, reinterpret_cast<sal_uInt8 const *>("%!PS-Adobe"), 10, 10 )
+ && ImplSearchEntry( &nFirstBytes[15], reinterpret_cast<sal_uInt8 const *>("EPS"), 3, 3 ) ) )
+ {
+ nFormat = GraphicFileFormat::EPS;
+ bRet = true;
+ }
+ rStm.Seek( nStmPos );
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectDXF( SvStream&, bool )
+{
+ bool bRet = aPathExt.startsWith( "dxf" );
+ if (bRet)
+ nFormat = GraphicFileFormat::DXF;
+
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectMET( SvStream&, bool )
+{
+ bool bRet = aPathExt.startsWith( "met" );
+ if (bRet)
+ nFormat = GraphicFileFormat::MET;
+
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectPCT( SvStream& rStm, bool )
+{
+ bool bRet = aPathExt.startsWith( "pct" );
+ if (bRet)
+ nFormat = GraphicFileFormat::PCT;
+ else
+ {
+ sal_uInt64 const nStreamPos = rStm.Tell();
+ sal_uInt64 const nStreamLen = rStm.remainingSize();
+ if (isPCT(rStm, nStreamPos, nStreamLen))
+ {
+ bRet = true;
+ nFormat = GraphicFileFormat::PCT;
+ }
+ rStm.Seek(nStreamPos);
+ }
+
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectSVM( SvStream& rStm, bool bExtendedInfo )
+{
+ sal_uInt32 n32 = 0;
+ bool bRet = false;
+ sal_uInt8 cByte = 0;
+
+ sal_Int32 nStmPos = rStm.Tell();
+ rStm.SetEndian( SvStreamEndian::LITTLE );
+ rStm.ReadUInt32( n32 );
+ if ( n32 == 0x44475653 )
+ {
+ cByte = 0;
+ rStm.ReadUChar( cByte );
+ if ( cByte == 0x49 )
+ {
+ nFormat = GraphicFileFormat::SVM;
+ bRet = true;
+
+ if ( bExtendedInfo )
+ {
+ sal_uInt32 nTemp32;
+ sal_uInt16 nTemp16;
+
+ rStm.SeekRel( 0x04 );
+
+ // width
+ nTemp32 = 0;
+ rStm.ReadUInt32( nTemp32 );
+ aLogSize.setWidth( nTemp32 );
+
+ // height
+ nTemp32 = 0;
+ rStm.ReadUInt32( nTemp32 );
+ aLogSize.setHeight( nTemp32 );
+
+ // read MapUnit and determine PrefSize
+ nTemp16 = 0;
+ rStm.ReadUInt16( nTemp16 );
+ aLogSize = OutputDevice::LogicToLogic( aLogSize,
+ MapMode( static_cast<MapUnit>(nTemp16) ),
+ MapMode( MapUnit::Map100thMM ) );
+ }
+ }
+ }
+ else
+ {
+ rStm.SeekRel( -4 );
+ n32 = 0;
+ rStm.ReadUInt32( n32 );
+
+ if( n32 == 0x4D4C4356 )
+ {
+ sal_uInt16 nTmp16 = 0;
+
+ rStm.ReadUInt16( nTmp16 );
+
+ if( nTmp16 == 0x4654 )
+ {
+ nFormat = GraphicFileFormat::SVM;
+ bRet = true;
+
+ if( bExtendedInfo )
+ {
+ MapMode aMapMode;
+
+ rStm.SeekRel( 0x06 );
+ ReadMapMode( rStm, aMapMode );
+ TypeSerializer aSerializer(rStm);
+ aSerializer.readSize(aLogSize);
+ aLogSize = OutputDevice::LogicToLogic( aLogSize, aMapMode, MapMode( MapUnit::Map100thMM ) );
+ }
+ }
+ }
+ }
+ rStm.Seek( nStmPos );
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectWMF( SvStream&, bool )
+{
+ bool bRet = aPathExt.startsWith( "wmf" );
+ if (bRet)
+ nFormat = GraphicFileFormat::WMF;
+
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectEMF( SvStream& rStm, bool bExtendedInfo )
+{
+ sal_uInt32 nRecordType = 0;
+ bool bRet = false;
+
+ sal_Int32 nStmPos = rStm.Tell();
+ rStm.SetEndian( SvStreamEndian::LITTLE );
+ rStm.ReadUInt32( nRecordType );
+
+ if ( nRecordType == 0x00000001 )
+ {
+ sal_uInt32 nHeaderSize = 0;
+ sal_Int32 nBoundLeft = 0, nBoundTop = 0, nBoundRight = 0, nBoundBottom = 0;
+ sal_Int32 nFrameLeft = 0, nFrameTop = 0, nFrameRight = 0, nFrameBottom = 0;
+ sal_uInt32 nSignature = 0;
+
+ rStm.ReadUInt32( nHeaderSize );
+ rStm.ReadInt32( nBoundLeft );
+ rStm.ReadInt32( nBoundTop );
+ rStm.ReadInt32( nBoundRight );
+ rStm.ReadInt32( nBoundBottom );
+ rStm.ReadInt32( nFrameLeft );
+ rStm.ReadInt32( nFrameTop );
+ rStm.ReadInt32( nFrameRight );
+ rStm.ReadInt32( nFrameBottom );
+ rStm.ReadUInt32( nSignature );
+
+ if ( nSignature == 0x464d4520 )
+ {
+ nFormat = GraphicFileFormat::EMF;
+ bRet = true;
+
+ if ( bExtendedInfo )
+ {
+ // size in pixels
+ aPixSize.setWidth( nBoundRight - nBoundLeft + 1 );
+ aPixSize.setHeight( nBoundBottom - nBoundTop + 1 );
+
+ // size in 0.01mm units
+ aLogSize.setWidth( nFrameRight - nFrameLeft + 1 );
+ aLogSize.setHeight( nFrameBottom - nFrameTop + 1 );
+ }
+ }
+ }
+
+ rStm.Seek( nStmPos );
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectSVG( SvStream& /*rStm*/, bool /*bExtendedInfo*/ )
+{
+ bool bRet = aPathExt.startsWith( "svg" );
+ if (bRet)
+ nFormat = GraphicFileFormat::SVG;
+
+ return bRet;
+}
+
+OUString GraphicDescriptor::GetImportFormatShortName( GraphicFileFormat nFormat )
+{
+ const char *pKeyName = nullptr;
+
+ switch( nFormat )
+ {
+ case GraphicFileFormat::BMP : pKeyName = "bmp"; break;
+ case GraphicFileFormat::GIF : pKeyName = "gif"; break;
+ case GraphicFileFormat::JPG : pKeyName = "jpg"; break;
+ case GraphicFileFormat::PCD : pKeyName = "pcd"; break;
+ case GraphicFileFormat::PCX : pKeyName = "pcx"; break;
+ case GraphicFileFormat::PNG : pKeyName = "png"; break;
+ case GraphicFileFormat::XBM : pKeyName = "xbm"; break;
+ case GraphicFileFormat::XPM : pKeyName = "xpm"; break;
+ case GraphicFileFormat::PBM : pKeyName = "pbm"; break;
+ case GraphicFileFormat::PGM : pKeyName = "pgm"; break;
+ case GraphicFileFormat::PPM : pKeyName = "ppm"; break;
+ case GraphicFileFormat::RAS : pKeyName = "ras"; break;
+ case GraphicFileFormat::TGA : pKeyName = "tga"; break;
+ case GraphicFileFormat::PSD : pKeyName = "psd"; break;
+ case GraphicFileFormat::EPS : pKeyName = "eps"; break;
+ case GraphicFileFormat::TIF : pKeyName = "tif"; break;
+ case GraphicFileFormat::DXF : pKeyName = "dxf"; break;
+ case GraphicFileFormat::MET : pKeyName = "met"; break;
+ case GraphicFileFormat::PCT : pKeyName = "pct"; break;
+ case GraphicFileFormat::SVM : pKeyName = "svm"; break;
+ case GraphicFileFormat::WMF : pKeyName = "wmf"; break;
+ case GraphicFileFormat::EMF : pKeyName = "emf"; break;
+ case GraphicFileFormat::SVG : pKeyName = "svg"; break;
+ default: assert(false);
+ }
+
+ return OUString::createFromAscii(pKeyName);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/graphicfilter_internal.hxx b/vcl/source/filter/graphicfilter_internal.hxx
new file mode 100644
index 000000000..cf8443f50
--- /dev/null
+++ b/vcl/source/filter/graphicfilter_internal.hxx
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_FILTER_GRAPHICFILTER_INTERNAL_HXX
+#define INCLUDED_VCL_SOURCE_FILTER_GRAPHICFILTER_INTERNAL_HXX
+
+#include <tools/solar.h>
+#include <tools/stream.hxx>
+
+sal_uInt8* ImplSearchEntry( sal_uInt8*, sal_uInt8 const *, sal_uLong, sal_uLong );
+
+extern bool isPCT(SvStream& rStream, sal_uLong nStreamPos, sal_uLong nStreamLen);
+
+#endif // INCLUDED_VCL_SOURCE_FILTER_GRAPHICFILTER_INTERNAL_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/igif/decode.cxx b/vcl/source/filter/igif/decode.cxx
new file mode 100644
index 000000000..7a2af0f79
--- /dev/null
+++ b/vcl/source/filter/igif/decode.cxx
@@ -0,0 +1,215 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "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()
+{
+ sal_uInt16 nCode;
+ 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
+ nCode = sal::static_int_cast< sal_uInt16 >(
+ static_cast<sal_uInt16>(nInputBitsBuf) & ( ~( 0xffff << nCodeSize ) ));
+ nInputBitsBuf >>= nCodeSize;
+ nInputBitsBufSize = nInputBitsBufSize - nCodeSize;
+
+ if ( nCode < nClearCode )
+ {
+ bool bOk = true;
+ if ( nOldCode != 0xffff )
+ bOk = AddToTable(nOldCode, nCode);
+ if (!bOk)
+ return false;
+ }
+ else if ( ( nCode > nEOICode ) && ( nCode <= nTableSize ) )
+ {
+ if ( nOldCode != 0xffff )
+ {
+ bool bOk;
+ if ( nCode == nTableSize )
+ bOk = AddToTable( nOldCode, nOldCode );
+ else
+ bOk = AddToTable( nOldCode, nCode );
+ if (!bOk)
+ return false;
+ }
+ }
+ else
+ {
+ if ( nCode == nClearCode )
+ {
+ nTableSize = nEOICode + 1;
+ nCodeSize = nDataSize + 1;
+ nOldCode = 0xffff;
+ nOutBufDataLen = 0;
+ }
+ else
+ bEOIFound = true;
+
+ return true;
+ }
+
+ nOldCode = nCode;
+
+ if (nCode >= 4096)
+ return false;
+
+ // write character(/-sequence) of code nCode in the output buffer:
+ GIFLZWTableEntry* pE = pTable.get() + nCode;
+ do
+ {
+ if (pOutBufData == pOutBuf.data()) //can't go back past start
+ return false;
+ nOutBufDataLen++;
+ *(--pOutBufData) = pE->nData;
+ pE = pE->pPrev;
+ }
+ while( pE );
+
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/igif/decode.hxx b/vcl/source/filter/igif/decode.hxx
new file mode 100644
index 000000000..6e16730a3
--- /dev/null
+++ b/vcl/source/filter/igif/decode.hxx
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_FILTER_IGIF_DECODE_HXX
+#define INCLUDED_VCL_SOURCE_FILTER_IGIF_DECODE_HXX
+
+#include <tools/solar.h>
+#include <vcl/Scanline.hxx>
+#include <array>
+#include <memory>
+
+struct GIFLZWTableEntry;
+
+class GIFLZWDecompressor
+{
+ std::unique_ptr<GIFLZWTableEntry[]>
+ pTable;
+ std::array<sal_uInt8, 4096>
+ pOutBuf;
+ sal_uInt8* pOutBufData;
+ sal_uInt8* pBlockBuf;
+ sal_uLong nInputBitsBuf;
+ bool bEOIFound;
+ sal_uInt8 nDataSize;
+ sal_uInt8 nBlockBufSize;
+ sal_uInt8 nBlockBufPos;
+ sal_uInt16 nClearCode;
+ sal_uInt16 nEOICode;
+ sal_uInt16 nTableSize;
+ sal_uInt16 nCodeSize;
+ sal_uInt16 nOldCode;
+ sal_uInt16 nOutBufDataLen;
+ sal_uInt16 nInputBitsBufSize;
+
+ bool AddToTable(sal_uInt16 nPrevCode, sal_uInt16 nCodeFirstData);
+ bool ProcessOneCode();
+
+ GIFLZWDecompressor(const GIFLZWDecompressor&) = delete;
+ GIFLZWDecompressor& operator=(const GIFLZWDecompressor&) = delete;
+public:
+
+ explicit GIFLZWDecompressor( sal_uInt8 cDataSize );
+ ~GIFLZWDecompressor();
+
+ Scanline DecompressBlock( sal_uInt8* pSrc, sal_uInt8 cBufSize, sal_uLong& rCount, bool& rEOI );
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/igif/gifread.cxx b/vcl/source/filter/igif/gifread.cxx
new file mode 100644
index 000000000..47d9acb53
--- /dev/null
+++ b/vcl/source/filter/igif/gifread.cxx
@@ -0,0 +1,994 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/log.hxx>
+#include <tools/stream.hxx>
+#include "decode.hxx"
+#include "gifread.hxx"
+#include <memory>
+#include <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;
+ long nYAcc;
+ long nLastPos;
+ sal_uInt64 nMaxStreamData;
+ sal_uInt32 nLogWidth100;
+ sal_uInt32 nLogHeight100;
+ sal_uInt16 nTimer;
+ sal_uInt16 nGlobalWidth; // maximum imagewidth from header
+ sal_uInt16 nGlobalHeight; // maximum imageheight from header
+ sal_uInt16 nImageWidth; // maximum screenwidth from header
+ sal_uInt16 nImageHeight; // maximum screenheight from header
+ sal_uInt16 nImagePosX;
+ sal_uInt16 nImagePosY;
+ sal_uInt16 nImageX; // maximum screenwidth from header
+ sal_uInt16 nImageY; // maximum screenheight from header
+ sal_uInt16 nLastImageY;
+ sal_uInt16 nLastInterCount;
+ sal_uInt16 nLoops;
+ GIFAction eActAction;
+ bool bStatus;
+ bool bGCTransparent; // is the image transparent, if yes:
+ bool bInterlaced;
+ bool bOverreadBlock;
+ bool bImGraphicReady;
+ bool bGlobalPalette;
+ sal_uInt8 nBackgroundColor; // backgroundcolour
+ sal_uInt8 nGCTransparentIndex; // pixels of this index are transparent
+ sal_uInt8 nGCDisposalMethod; // 'Disposal Method' (see GIF docs)
+ sal_uInt8 cTransIndex1;
+ sal_uInt8 cNonTransIndex1;
+
+ void ReadPaletteEntries( BitmapPalette* pPal, sal_uLong nCount );
+ void ClearImageExtensions();
+ void CreateBitmaps( long nWidth, 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();
+ Graphic GetIntermediateGraphic();
+
+ explicit GIFReader( SvStream& rStm );
+};
+
+}
+
+GIFReader::GIFReader( SvStream& rStm )
+ : nAnimationByteSize(0)
+ , nAnimationMinFileData(0)
+ , aGPalette ( 256 )
+ , aLPalette ( 256 )
+ , rIStm ( rStm )
+ , nYAcc ( 0 )
+ , nLastPos ( rStm.Tell() )
+ , nMaxStreamData( rStm.remainingSize() )
+ , nLogWidth100 ( 0 )
+ , nLogHeight100 ( 0 )
+ , nGlobalWidth ( 0 )
+ , nGlobalHeight ( 0 )
+ , nImageWidth ( 0 )
+ , nImageHeight ( 0 )
+ , nImagePosX ( 0 )
+ , nImagePosY ( 0 )
+ , nImageX ( 0 )
+ , nImageY ( 0 )
+ , nLastImageY ( 0 )
+ , nLastInterCount ( 0 )
+ , nLoops ( 1 )
+ , eActAction ( GLOBAL_HEADER_READING )
+ , bStatus ( false )
+ , bGCTransparent ( false )
+ , bInterlaced ( false)
+ , bOverreadBlock ( false )
+ , bImGraphicReady ( false )
+ , bGlobalPalette ( false )
+ , nBackgroundColor ( 0 )
+ , nGCTransparentIndex ( 0 )
+ , cTransIndex1 ( 0 )
+ , cNonTransIndex1 ( 0 )
+{
+ maUpperName = "SVIGIF";
+ aSrcBuf.resize(256); // Memory buffer for ReadNextBlock
+ ClearImageExtensions();
+}
+
+void GIFReader::ClearImageExtensions()
+{
+ nGCDisposalMethod = 0;
+ bGCTransparent = false;
+ nTimer = 0;
+}
+
+void GIFReader::CreateBitmaps(long nWidth, 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, 1);
+
+ if (!aAnimation.Count())
+ aBmp1.Erase(aWhite);
+
+ pAcc1 = BitmapScopedWriteAccess(aBmp1);
+
+ if (pAcc1)
+ {
+ cTransIndex1 = static_cast<sal_uInt8>(pAcc1->GetBestPaletteIndex(aWhite));
+ cNonTransIndex1 = cTransIndex1 ? 0 : 1;
+ }
+ else
+ {
+ bStatus = false;
+ }
+ }
+
+ if (bStatus)
+ {
+ aBmp8 = Bitmap(aSize, 8, pPal);
+
+ if (!!aBmp8 && bWatchForBackgroundColor && aAnimation.Count())
+ aBmp8.Erase((*pPal)[nBackgroundColor]);
+ else
+ aBmp8.Erase(COL_WHITE);
+
+ pAcc8 = BitmapScopedWriteAccess(aBmp8);
+ bStatus = bool(pAcc8);
+ }
+}
+
+bool GIFReader::ReadGlobalHeader()
+{
+ char pBuf[ 7 ];
+ sal_uInt8 nRF;
+ sal_uInt8 nAspect;
+ bool bRet = false;
+
+ rIStm.ReadBytes( pBuf, 6 );
+ if( NO_PENDING( rIStm ) )
+ {
+ pBuf[ 6 ] = 0;
+ if( !strcmp( pBuf, "GIF87a" ) || !strcmp( pBuf, "GIF89a" ) )
+ {
+ rIStm.ReadBytes( pBuf, 7 );
+ if( NO_PENDING( rIStm ) )
+ {
+ 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 ) )
+ {
+ sal_uInt8* pTmp = pBuf.get();
+
+ for (sal_uLong i = 0; i < nCount; ++i)
+ {
+ BitmapColor& rColor = (*pPal)[i];
+
+ rColor.SetRed( *pTmp++ );
+ rColor.SetGreen( *pTmp++ );
+ rColor.SetBlue( *pTmp++ );
+ }
+
+ // if possible accommodate some standard colours
+ if( nCount < 256 )
+ {
+ (*pPal)[ 255UL ] = COL_WHITE;
+
+ if( nCount < 255 )
+ (*pPal)[ 254UL ] = COL_BLACK;
+ }
+ }
+}
+
+bool GIFReader::ReadExtension()
+{
+ bool bRet = false;
+
+ // Extension-Label
+ sal_uInt8 cFunction(0);
+ rIStm.ReadUChar( cFunction );
+ if( NO_PENDING( rIStm ) )
+ {
+ bool bOverreadDataBlocks = false;
+ sal_uInt8 cSize(0);
+ // Block length
+ rIStm.ReadUChar( cSize );
+ switch( cFunction )
+ {
+ // 'Graphic Control Extension'
+ case 0xf9 :
+ {
+ sal_uInt8 cFlags(0);
+ rIStm.ReadUChar(cFlags);
+ rIStm.ReadUInt16(nTimer);
+ rIStm.ReadUChar(nGCTransparentIndex);
+ sal_uInt8 cByte(0);
+ rIStm.ReadUChar(cByte);
+
+ if ( NO_PENDING( rIStm ) )
+ {
+ nGCDisposalMethod = ( cFlags >> 2) & 7;
+ bGCTransparent = ( cFlags & 1 );
+ bStatus = ( cSize == 4 ) && ( cByte == 0 );
+ bRet = true;
+ }
+ }
+ break;
+
+ // Application extension
+ case 0xff :
+ {
+ if ( NO_PENDING( rIStm ) )
+ {
+ // by default overread this extension
+ bOverreadDataBlocks = true;
+
+ // Appl. extension has length 11
+ if ( cSize == 0x0b )
+ {
+ OString aAppId = read_uInt8s_ToOString(rIStm, 8);
+ OString aAppCode = read_uInt8s_ToOString(rIStm, 3);
+ rIStm.ReadUChar( cSize );
+
+ // NetScape-Extension
+ if( aAppId == "NETSCAPE" && aAppCode == "2.0" && cSize == 3 )
+ {
+ sal_uInt8 cByte(0);
+ rIStm.ReadUChar( cByte );
+
+ // Loop-Extension
+ if ( cByte == 0x01 )
+ {
+ rIStm.ReadUChar( cByte );
+ nLoops = cByte;
+ rIStm.ReadUChar( cByte );
+ nLoops |= ( static_cast<sal_uInt16>(cByte) << 8 );
+ rIStm.ReadUChar( cByte );
+
+ bStatus = ( cByte == 0 );
+ bRet = NO_PENDING( rIStm );
+ bOverreadDataBlocks = false;
+
+ // Netscape interprets the loop count
+ // as pure number of _repeats_;
+ // here it is the total number of loops
+ if( nLoops )
+ nLoops++;
+ }
+ else
+ rIStm.SeekRel( -1 );
+ }
+ else if ( aAppId == "STARDIV " && aAppCode == "5.0" && cSize == 9 )
+ {
+ sal_uInt8 cByte(0);
+ rIStm.ReadUChar( cByte );
+
+ // Loop extension
+ if ( cByte == 0x01 )
+ {
+ rIStm.ReadUInt32( nLogWidth100 ).ReadUInt32( nLogHeight100 );
+ rIStm.ReadUChar( cByte );
+ bStatus = ( cByte == 0 );
+ bRet = NO_PENDING( rIStm );
+ bOverreadDataBlocks = false;
+ }
+ else
+ rIStm.SeekRel( -1 );
+ }
+
+ }
+ }
+ }
+ break;
+
+ // overread everything else
+ default:
+ bOverreadDataBlocks = true;
+ break;
+ }
+
+ // overread sub-blocks
+ if ( bOverreadDataBlocks )
+ {
+ bRet = true;
+ while( cSize && bStatus && !rIStm.eof() )
+ {
+ sal_uInt16 nCount = static_cast<sal_uInt16>(cSize) + 1;
+ const sal_uInt64 nMaxPossible = rIStm.remainingSize();
+ if (nCount > nMaxPossible)
+ nCount = nMaxPossible;
+
+ if (nCount)
+ rIStm.SeekRel( nCount - 1 ); // Skip subblock data
+
+ bRet = false;
+ std::size_t nRead = rIStm.ReadBytes(&cSize, 1);
+ if (NO_PENDING(rIStm) && nRead == 1)
+ {
+ bRet = true;
+ }
+ else
+ cSize = 0;
+ }
+ }
+ }
+
+ return bRet;
+}
+
+bool GIFReader::ReadLocalHeader()
+{
+ sal_uInt8 pBuf[ 9 ];
+ bool bRet = false;
+
+ std::size_t nRead = rIStm.ReadBytes(pBuf, 9);
+ if (NO_PENDING(rIStm) && nRead == 9)
+ {
+ SvMemoryStream aMemStm;
+ BitmapPalette* pPal;
+
+ aMemStm.SetBuffer( pBuf, 9, 9 );
+ aMemStm.ReadUInt16( nImagePosX );
+ aMemStm.ReadUInt16( nImagePosY );
+ aMemStm.ReadUInt16( nImageWidth );
+ aMemStm.ReadUInt16( nImageHeight );
+ sal_uInt8 nFlags(0);
+ aMemStm.ReadUChar(nFlags);
+
+ // if interlaced, first define startvalue
+ bInterlaced = ( ( nFlags & 0x40 ) == 0x40 );
+ nLastInterCount = 7;
+ nLastImageY = 0;
+
+ if( nFlags & 0x80 )
+ {
+ pPal = &aLPalette;
+ ReadPaletteEntries( pPal, sal_uLong(1) << ( (nFlags & 7 ) + 1 ) );
+ }
+ else
+ pPal = &aGPalette;
+
+ // if we could read everything, we will create the local image;
+ // if the global colour table is valid for the image, we will
+ // consider the BackGroudColorIndex.
+ if( NO_PENDING( rIStm ) )
+ {
+ CreateBitmaps( nImageWidth, nImageHeight, pPal, bGlobalPalette && ( pPal == &aGPalette ) );
+ bRet = true;
+ }
+ }
+
+ return bRet;
+}
+
+sal_uLong GIFReader::ReadNextBlock()
+{
+ sal_uLong nRet = 0;
+ sal_uLong nRead;
+ 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_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 )
+ {
+ long nT1;
+
+ // lines will be copied if interlaced
+ if( nLastInterCount )
+ {
+ long nMinY = std::min( static_cast<long>(nLastImageY) + 1, static_cast<long>(nImageHeight) - 1 );
+ long nMaxY = std::min( static_cast<long>(nLastImageY) + nLastInterCount, static_cast<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( 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 )
+ {
+ long nT2 = nImageY - ( ( nImageHeight + 7 ) >> 3 );
+ nT1 = ( nT2 << 3 ) + 4;
+ nLastInterCount = 3;
+
+ if( nT1 >= nImageHeight )
+ {
+ nT2 -= ( nImageHeight + 3 ) >> 3;
+ nT1 = ( nT2 << 2 ) + 2;
+ nLastInterCount = 1;
+
+ if( nT1 >= nImageHeight )
+ {
+ nT2 -= ( nImageHeight + 1 ) >> 2;
+ nT1 = ( nT2 << 1 ) + 1;
+ nLastInterCount = 0;
+ }
+ }
+ }
+
+ nLastImageY = static_cast<sal_uInt16>(nT1);
+ nYAcc = nT1;
+ }
+ else
+ {
+ nLastImageY = ++nImageY;
+ nYAcc = nImageY;
+ }
+
+ // line starts from the beginning
+ nImageX = 0;
+ }
+
+ if( nImageY < nImageHeight )
+ {
+ const sal_uInt8 cTmp = pBytes[ i ];
+
+ if( bGCTransparent )
+ {
+ if( cTmp == nGCTransparentIndex )
+ pAcc1->SetPixelIndex( nYAcc, nImageX++, cTransIndex1 );
+ else
+ {
+ pAcc8->SetPixelIndex( nYAcc, nImageX, cTmp );
+ pAcc1->SetPixelIndex( nYAcc, nImageX++, cNonTransIndex1 );
+ }
+ }
+ else
+ pAcc8->SetPixelIndex( nYAcc, nImageX++, cTmp );
+ }
+ else
+ {
+ bOverreadBlock = true;
+ break;
+ }
+ }
+}
+
+void GIFReader::CreateNewBitmaps()
+{
+ AnimationBitmap aAnimationBitmap;
+
+ pAcc8.reset();
+
+ if( bGCTransparent )
+ {
+ pAcc1.reset();
+ aAnimationBitmap.maBitmapEx = BitmapEx( aBmp8, aBmp1 );
+ }
+ else
+ aAnimationBitmap.maBitmapEx = BitmapEx( aBmp8 );
+
+ aAnimationBitmap.maPositionPixel = Point( nImagePosX, nImagePosY );
+ aAnimationBitmap.maSizePixel = Size( nImageWidth, nImageHeight );
+ aAnimationBitmap.mnWait = ( nTimer != 65535 ) ? nTimer : ANIMATION_TIMEOUT_ON_CLICK;
+ aAnimationBitmap.mbUserInput = false;
+
+ // tdf#104121 . Internet Explorer, Firefox, Chrome and Safari all set a minimum default playback speed.
+ // IE10 Consumer Preview sets default of 100ms for rates less that 20ms. We do the same
+ if (aAnimationBitmap.mnWait < 2) // 20ms, specified in 100's of a second
+ aAnimationBitmap.mnWait = 10;
+
+ if( nGCDisposalMethod == 2 )
+ aAnimationBitmap.meDisposal = Disposal::Back;
+ else if( nGCDisposalMethod == 3 )
+ aAnimationBitmap.meDisposal = Disposal::Previous;
+ else
+ aAnimationBitmap.meDisposal = Disposal::Not;
+
+ nAnimationByteSize += aAnimationBitmap.maBitmapEx.GetSizeBytes();
+ nAnimationMinFileData += static_cast<sal_uInt64>(nImageWidth) * nImageHeight / 2560;
+ aAnimation.Insert(aAnimationBitmap);
+
+ if( aAnimation.Count() == 1 )
+ {
+ aAnimation.SetDisplaySizePixel( Size( nGlobalWidth, nGlobalHeight ) );
+ aAnimation.SetLoopCount( nLoops );
+ }
+}
+
+Graphic GIFReader::GetIntermediateGraphic()
+{
+ Graphic aImGraphic;
+
+ // only create intermediate graphic, if data is available
+ // but graphic still not completely read
+ if ( bImGraphicReady && !aAnimation.Count() )
+ {
+ pAcc8.reset();
+
+ if ( bGCTransparent )
+ {
+ pAcc1.reset();
+ aImGraphic = BitmapEx( aBmp8, aBmp1 );
+
+ pAcc1 = BitmapScopedWriteAccess(aBmp1);
+ bStatus = bStatus && pAcc1;
+ }
+ else
+ aImGraphic = aBmp8;
+
+ pAcc8 = BitmapScopedWriteAccess(aBmp8);
+ bStatus = bStatus && pAcc8;
+ }
+
+ return aImGraphic;
+}
+
+bool GIFReader::ProcessGIF()
+{
+ bool bRead = false;
+ bool bEnd = false;
+
+ if ( !bStatus )
+ eActAction = ABORT_READING;
+
+ // set stream to right position
+ rIStm.Seek( nLastPos );
+
+ switch( eActAction )
+ {
+ // read next marker
+ case MARKER_READING:
+ {
+ sal_uInt8 cByte;
+
+ rIStm.ReadUChar( cByte );
+
+ if( rIStm.eof() )
+ eActAction = END_READING;
+ else if( NO_PENDING( rIStm ) )
+ {
+ bRead = true;
+
+ if( cByte == '!' )
+ eActAction = EXTENSION_READING;
+ else if( cByte == ',' )
+ eActAction = LOCAL_HEADER_READING;
+ else if( cByte == ';' )
+ eActAction = END_READING;
+ else
+ eActAction = ABORT_READING;
+ }
+ }
+ break;
+
+ // read ScreenDescriptor
+ case GLOBAL_HEADER_READING:
+ {
+ bRead = ReadGlobalHeader();
+ if( bRead )
+ {
+ ClearImageExtensions();
+ eActAction = MARKER_READING;
+ }
+ }
+ break;
+
+ // read extension
+ case EXTENSION_READING:
+ {
+ bRead = ReadExtension();
+ if( bRead )
+ eActAction = MARKER_READING;
+ }
+ break;
+
+ // read Image-Descriptor
+ case LOCAL_HEADER_READING:
+ {
+ bRead = ReadLocalHeader();
+ if( bRead )
+ {
+ nYAcc = nImageX = nImageY = 0;
+ eActAction = FIRST_BLOCK_READING;
+ }
+ }
+ break;
+
+ // read first data block
+ case FIRST_BLOCK_READING:
+ {
+ sal_uInt8 cDataSize;
+
+ rIStm.ReadUChar( cDataSize );
+
+ if( rIStm.eof() )
+ eActAction = ABORT_READING;
+ else if( cDataSize > 12 )
+ bStatus = false;
+ else if( NO_PENDING( rIStm ) )
+ {
+ bRead = true;
+ pDecomp = std::make_unique<GIFLZWDecompressor>( cDataSize );
+ eActAction = NEXT_BLOCK_READING;
+ bOverreadBlock = false;
+ }
+ else
+ eActAction = FIRST_BLOCK_READING;
+ }
+ break;
+
+ // read next data block
+ case NEXT_BLOCK_READING:
+ {
+ sal_uInt16 nLastX = nImageX;
+ sal_uInt16 nLastY = nImageY;
+ sal_uLong nRet = ReadNextBlock();
+
+ // Return: 0:Pending / 1:OK; / 2:OK and last block: / 3:EOI / 4:HardAbort
+ if( nRet )
+ {
+ bRead = true;
+
+ if ( nRet == 1 )
+ {
+ bImGraphicReady = true;
+ eActAction = NEXT_BLOCK_READING;
+ bOverreadBlock = false;
+ }
+ else
+ {
+ if( nRet == 2 )
+ {
+ pDecomp.reset();
+ CreateNewBitmaps();
+ eActAction = MARKER_READING;
+ ClearImageExtensions();
+ }
+ else if( nRet == 3 )
+ {
+ eActAction = NEXT_BLOCK_READING;
+ bOverreadBlock = true;
+ }
+ else
+ {
+ pDecomp.reset();
+ CreateNewBitmaps();
+ eActAction = ABORT_READING;
+ ClearImageExtensions();
+ }
+ }
+ }
+ else
+ {
+ nImageX = nLastX;
+ nImageY = nLastY;
+ }
+ }
+ break;
+
+ // an error occurred
+ case ABORT_READING:
+ {
+ bEnd = true;
+ eActAction = END_READING;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ // set stream to right position,
+ // if data could be read put it at the old
+ // position otherwise at the actual one
+ if( bRead || bEnd )
+ nLastPos = rIStm.Tell();
+
+ return bRead;
+}
+
+bool GIFReader::ReadIsAnimated()
+{
+ ReadState eReadState;
+
+ bStatus = true;
+
+ while( ProcessGIF() && ( eActAction != END_READING ) ) {}
+
+ if( !bStatus )
+ eReadState = GIFREAD_ERROR;
+ else if( eActAction == END_READING )
+ eReadState = GIFREAD_OK;
+ else
+ {
+ if ( rIStm.GetError() == ERRCODE_IO_PENDING )
+ rIStm.ResetError();
+
+ eReadState = GIFREAD_NEED_MORE;
+ }
+
+ if (eReadState == GIFREAD_OK)
+ return aAnimation.Count() > 1;
+ return false;
+}
+
+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)
+{
+ GIFReader aReader(rStm);
+
+ SvStreamEndian nOldFormat = rStm.GetEndian();
+ rStm.SetEndian(SvStreamEndian::LITTLE);
+ bool bResult = aReader.ReadIsAnimated();
+ rStm.SetEndian(nOldFormat);
+
+ return bResult;
+}
+
+VCL_DLLPUBLIC bool ImportGIF( SvStream & rStm, Graphic& rGraphic )
+{
+ std::shared_ptr<GraphicReader> pContext = rGraphic.GetReaderContext();
+ rGraphic.SetReaderContext(nullptr);
+ GIFReader* pGIFReader = dynamic_cast<GIFReader*>( pContext.get() );
+ if (!pGIFReader)
+ {
+ pContext = std::make_shared<GIFReader>( rStm );
+ pGIFReader = static_cast<GIFReader*>( pContext.get() );
+ }
+
+ SvStreamEndian nOldFormat = rStm.GetEndian();
+ rStm.SetEndian( SvStreamEndian::LITTLE );
+
+ bool bRet = true;
+
+ ReadState eReadState = pGIFReader->ReadGIF(rGraphic);
+
+ if (eReadState == GIFREAD_ERROR)
+ {
+ bRet = false;
+ }
+ else if (eReadState == GIFREAD_NEED_MORE)
+ {
+ rGraphic = pGIFReader->GetIntermediateGraphic();
+ rGraphic.SetReaderContext(pContext);
+ }
+
+ rStm.SetEndian(nOldFormat);
+
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/igif/gifread.hxx b/vcl/source/filter/igif/gifread.hxx
new file mode 100644
index 000000000..25e72f6cc
--- /dev/null
+++ b/vcl/source/filter/igif/gifread.hxx
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_FILTER_IGIF_GIFREAD_HXX
+#define INCLUDED_VCL_SOURCE_FILTER_IGIF_GIFREAD_HXX
+
+#include <vcl/graph.hxx>
+
+VCL_DLLPUBLIC bool ImportGIF( SvStream& rStream, Graphic& rGraphic );
+bool IsGIFAnimated(SvStream& rStream);
+
+#endif // INCLUDED_VCL_SOURCE_FILTER_IGIF_GIFREAD_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/ipdf/pdfdocument.cxx b/vcl/source/filter/ipdf/pdfdocument.cxx
new file mode 100644
index 000000000..b64742188
--- /dev/null
+++ b/vcl/source/filter/ipdf/pdfdocument.cxx
@@ -0,0 +1,3100 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 <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 <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>
+
+using namespace com::sun::star;
+
+namespace vcl::filter
+{
+const int MAX_SIGNATURE_CONTENT_LENGTH = 50000;
+
+class PDFTrailerElement;
+
+namespace
+{
+/// A one-liner comment.
+class PDFCommentElement : public PDFElement
+{
+ PDFDocument& m_rDoc;
+ OString m_aComment;
+
+public:
+ explicit PDFCommentElement(PDFDocument& rDoc);
+ bool Read(SvStream& rStream) override;
+};
+}
+
+class PDFReferenceElement;
+
+namespace
+{
+/// End of a dictionary: '>>'.
+class PDFEndDictionaryElement : public PDFElement
+{
+ /// Offset before the '>>' token.
+ sal_uInt64 m_nLocation = 0;
+
+public:
+ PDFEndDictionaryElement();
+ bool Read(SvStream& rStream) override;
+ sal_uInt64 GetLocation() const;
+};
+
+/// End of a stream: 'endstream' keyword.
+class PDFEndStreamElement : public PDFElement
+{
+public:
+ bool Read(SvStream& rStream) override;
+};
+
+/// End of an object: 'endobj' keyword.
+class PDFEndObjectElement : public PDFElement
+{
+public:
+ bool Read(SvStream& rStream) override;
+};
+
+/// End of an array: ']'.
+class PDFEndArrayElement : public PDFElement
+{
+ /// Location before the ']' token.
+ sal_uInt64 m_nOffset = 0;
+
+public:
+ PDFEndArrayElement();
+ bool Read(SvStream& rStream) override;
+ sal_uInt64 GetOffset() const;
+};
+
+/// Boolean object: a 'true' or a 'false'.
+class PDFBooleanElement : public PDFElement
+{
+public:
+ explicit PDFBooleanElement(bool bValue);
+ bool Read(SvStream& rStream) override;
+};
+
+/// Null object: the 'null' singleton.
+class PDFNullElement : public PDFElement
+{
+public:
+ bool Read(SvStream& rStream) override;
+};
+}
+
+/// The trailer singleton is at the end of the doc.
+class PDFTrailerElement : public PDFElement
+{
+ PDFDocument& m_rDoc;
+ std::map<OString, PDFElement*> m_aDictionary;
+ /// Location of the end of the trailer token.
+ sal_uInt64 m_nOffset = 0;
+
+public:
+ explicit PDFTrailerElement(PDFDocument& rDoc);
+ bool Read(SvStream& rStream) override;
+ PDFElement* Lookup(const OString& rDictionaryKey);
+ sal_uInt64 GetLocation() const;
+};
+
+XRefEntry::XRefEntry() = 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();
+}
+
+const std::vector<size_t>& PDFDocument::GetEOFs() const { return m_aEOFs; }
+
+sal_uInt32 PDFDocument::GetNextSignature()
+{
+ sal_uInt32 nRet = 0;
+ for (const auto& pSignature : GetSignatureWidgets())
+ {
+ auto pT = dynamic_cast<PDFLiteralStringElement*>(pSignature->Lookup("T"));
+ if (!pT)
+ continue;
+
+ const OString& rValue = pT->GetValue();
+ const OString aPrefix = "Signature";
+ if (!rValue.startsWith(aPrefix))
+ continue;
+
+ nRet = std::max(nRet, rValue.copy(aPrefix.getLength()).toUInt32());
+ }
+
+ return nRet + 1;
+}
+
+sal_Int32 PDFDocument::WriteSignatureObject(const OUString& rDescription, bool bAdES,
+ sal_uInt64& rLastByteRangeOffset,
+ sal_Int64& rContentOffset)
+{
+ // Write signature object.
+ sal_Int32 nSignatureId = m_aXRef.size();
+ XRefEntry aSignatureEntry;
+ aSignatureEntry.SetOffset(m_aEditBuffer.Tell());
+ aSignatureEntry.SetDirty(true);
+ m_aXRef[nSignatureId] = aSignatureEntry;
+ OStringBuffer aSigBuffer;
+ aSigBuffer.append(nSignatureId);
+ aSigBuffer.append(" 0 obj\n");
+ aSigBuffer.append("<</Contents <");
+ rContentOffset = aSignatureEntry.GetOffset() + aSigBuffer.getLength();
+ // Reserve space for the PKCS#7 object.
+ OStringBuffer aContentFiller(MAX_SIGNATURE_CONTENT_LENGTH);
+ comphelper::string::padToLength(aContentFiller, MAX_SIGNATURE_CONTENT_LENGTH, '0');
+ aSigBuffer.append(aContentFiller.makeStringAndClear());
+ aSigBuffer.append(">\n/Type/Sig/SubFilter");
+ if (bAdES)
+ aSigBuffer.append("/ETSI.CAdES.detached");
+ else
+ aSigBuffer.append("/adbe.pkcs7.detached");
+
+ // Time of signing.
+ aSigBuffer.append(" /M (");
+ aSigBuffer.append(vcl::PDFWriter::GetDateTime());
+ aSigBuffer.append(")");
+
+ // Byte range: we can write offset1-length1 and offset2 right now, will
+ // write length2 later.
+ aSigBuffer.append(" /ByteRange [ 0 ");
+ // -1 and +1 is the leading "<" and the trailing ">" around the hex string.
+ aSigBuffer.append(rContentOffset - 1);
+ aSigBuffer.append(" ");
+ aSigBuffer.append(rContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1);
+ aSigBuffer.append(" ");
+ rLastByteRangeOffset = aSignatureEntry.GetOffset() + aSigBuffer.getLength();
+ // We don't know how many bytes we need for the last ByteRange value, this
+ // should be enough.
+ OStringBuffer aByteRangeFiller;
+ comphelper::string::padToLength(aByteRangeFiller, 100, ' ');
+ aSigBuffer.append(aByteRangeFiller.makeStringAndClear());
+ // Finish the Sig obj.
+ aSigBuffer.append(" /Filter/Adobe.PPKMS");
+
+ if (!rDescription.isEmpty())
+ {
+ aSigBuffer.append("/Reason<");
+ vcl::PDFWriter::AppendUnicodeTextString(rDescription, aSigBuffer);
+ aSigBuffer.append(">");
+ }
+
+ aSigBuffer.append(" >>\nendobj\n\n");
+ m_aEditBuffer.WriteOString(aSigBuffer.toString());
+
+ return nSignatureId;
+}
+
+sal_Int32 PDFDocument::WriteAppearanceObject()
+{
+ // Write appearance object.
+ sal_Int32 nAppearanceId = m_aXRef.size();
+ XRefEntry aAppearanceEntry;
+ aAppearanceEntry.SetOffset(m_aEditBuffer.Tell());
+ aAppearanceEntry.SetDirty(true);
+ m_aXRef[nAppearanceId] = aAppearanceEntry;
+ m_aEditBuffer.WriteUInt32AsString(nAppearanceId);
+ m_aEditBuffer.WriteCharPtr(" 0 obj\n");
+ m_aEditBuffer.WriteCharPtr("<</Type/XObject\n/Subtype/Form\n");
+ m_aEditBuffer.WriteCharPtr("/BBox[0 0 0 0]\n/Length 0\n>>\n");
+ m_aEditBuffer.WriteCharPtr("stream\n\nendstream\nendobj\n\n");
+
+ return nAppearanceId;
+}
+
+sal_Int32 PDFDocument::WriteAnnotObject(PDFObjectElement const& rFirstPage, sal_Int32 nSignatureId,
+ sal_Int32 nAppearanceId)
+{
+ // Decide what identifier to use for the new signature.
+ sal_uInt32 nNextSignature = GetNextSignature();
+
+ // Write the Annot object, references nSignatureId and nAppearanceId.
+ sal_Int32 nAnnotId = m_aXRef.size();
+ XRefEntry aAnnotEntry;
+ aAnnotEntry.SetOffset(m_aEditBuffer.Tell());
+ aAnnotEntry.SetDirty(true);
+ m_aXRef[nAnnotId] = aAnnotEntry;
+ m_aEditBuffer.WriteUInt32AsString(nAnnotId);
+ m_aEditBuffer.WriteCharPtr(" 0 obj\n");
+ m_aEditBuffer.WriteCharPtr("<</Type/Annot/Subtype/Widget/F 132\n");
+ m_aEditBuffer.WriteCharPtr("/Rect[0 0 0 0]\n");
+ m_aEditBuffer.WriteCharPtr("/FT/Sig\n");
+ m_aEditBuffer.WriteCharPtr("/P ");
+ m_aEditBuffer.WriteUInt32AsString(rFirstPage.GetObjectValue());
+ m_aEditBuffer.WriteCharPtr(" 0 R\n");
+ m_aEditBuffer.WriteCharPtr("/T(Signature");
+ m_aEditBuffer.WriteUInt32AsString(nNextSignature);
+ m_aEditBuffer.WriteCharPtr(")\n");
+ m_aEditBuffer.WriteCharPtr("/V ");
+ m_aEditBuffer.WriteUInt32AsString(nSignatureId);
+ m_aEditBuffer.WriteCharPtr(" 0 R\n");
+ m_aEditBuffer.WriteCharPtr("/DV ");
+ m_aEditBuffer.WriteUInt32AsString(nSignatureId);
+ m_aEditBuffer.WriteCharPtr(" 0 R\n");
+ m_aEditBuffer.WriteCharPtr("/AP<<\n/N ");
+ m_aEditBuffer.WriteUInt32AsString(nAppearanceId);
+ m_aEditBuffer.WriteCharPtr(" 0 R\n>>\n");
+ m_aEditBuffer.WriteCharPtr(">>\nendobj\n\n");
+
+ return nAnnotId;
+}
+
+bool PDFDocument::WritePageObject(PDFObjectElement& rFirstPage, sal_Int32 nAnnotId)
+{
+ PDFElement* pAnnots = rFirstPage.Lookup("Annots");
+ auto pAnnotsReference = dynamic_cast<PDFReferenceElement*>(pAnnots);
+ if (pAnnotsReference)
+ {
+ // Write the updated Annots key of the Page object.
+ PDFObjectElement* pAnnotsObject = pAnnotsReference->LookupObject();
+ if (!pAnnotsObject)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Sign: invalid Annots reference");
+ return false;
+ }
+
+ sal_uInt32 nAnnotsId = pAnnotsObject->GetObjectValue();
+ m_aXRef[nAnnotsId].SetType(XRefEntryType::NOT_COMPRESSED);
+ m_aXRef[nAnnotsId].SetOffset(m_aEditBuffer.Tell());
+ m_aXRef[nAnnotsId].SetDirty(true);
+ m_aEditBuffer.WriteUInt32AsString(nAnnotsId);
+ m_aEditBuffer.WriteCharPtr(" 0 obj\n[");
+
+ // Write existing references.
+ PDFArrayElement* pArray = pAnnotsObject->GetArray();
+ if (!pArray)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Sign: Page Annots is a reference to a non-array");
+ return false;
+ }
+
+ for (size_t i = 0; i < pArray->GetElements().size(); ++i)
+ {
+ auto pReference = dynamic_cast<PDFReferenceElement*>(pArray->GetElements()[i]);
+ if (!pReference)
+ continue;
+
+ if (i)
+ m_aEditBuffer.WriteCharPtr(" ");
+ m_aEditBuffer.WriteUInt32AsString(pReference->GetObjectValue());
+ m_aEditBuffer.WriteCharPtr(" 0 R");
+ }
+ // Write our reference.
+ m_aEditBuffer.WriteCharPtr(" ");
+ m_aEditBuffer.WriteUInt32AsString(nAnnotId);
+ m_aEditBuffer.WriteCharPtr(" 0 R");
+
+ m_aEditBuffer.WriteCharPtr("]\nendobj\n\n");
+ }
+ else
+ {
+ // Write the updated first page object, references nAnnotId.
+ sal_uInt32 nFirstPageId = rFirstPage.GetObjectValue();
+ if (nFirstPageId >= m_aXRef.size())
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Sign: invalid first page obj id");
+ return false;
+ }
+ m_aXRef[nFirstPageId].SetOffset(m_aEditBuffer.Tell());
+ m_aXRef[nFirstPageId].SetDirty(true);
+ m_aEditBuffer.WriteUInt32AsString(nFirstPageId);
+ m_aEditBuffer.WriteCharPtr(" 0 obj\n");
+ m_aEditBuffer.WriteCharPtr("<<");
+ auto pAnnotsArray = dynamic_cast<PDFArrayElement*>(pAnnots);
+ if (!pAnnotsArray)
+ {
+ // No Annots key, just write the key with a single reference.
+ m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData())
+ + rFirstPage.GetDictionaryOffset(),
+ rFirstPage.GetDictionaryLength());
+ m_aEditBuffer.WriteCharPtr("/Annots[");
+ m_aEditBuffer.WriteUInt32AsString(nAnnotId);
+ m_aEditBuffer.WriteCharPtr(" 0 R]");
+ }
+ else
+ {
+ // Annots key is already there, insert our reference at the end.
+ PDFDictionaryElement* pDictionary = rFirstPage.GetDictionary();
+
+ // Offset right before the end of the Annots array.
+ sal_uInt64 nAnnotsEndOffset = pDictionary->GetKeyOffset("Annots")
+ + pDictionary->GetKeyValueLength("Annots") - 1;
+ // Length of beginning of the dictionary -> Annots end.
+ sal_uInt64 nAnnotsBeforeEndLength = nAnnotsEndOffset - rFirstPage.GetDictionaryOffset();
+ m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData())
+ + rFirstPage.GetDictionaryOffset(),
+ nAnnotsBeforeEndLength);
+ m_aEditBuffer.WriteCharPtr(" ");
+ m_aEditBuffer.WriteUInt32AsString(nAnnotId);
+ m_aEditBuffer.WriteCharPtr(" 0 R");
+ // Length of Annots end -> end of the dictionary.
+ sal_uInt64 nAnnotsAfterEndLength = rFirstPage.GetDictionaryOffset()
+ + rFirstPage.GetDictionaryLength()
+ - nAnnotsEndOffset;
+ m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData())
+ + nAnnotsEndOffset,
+ nAnnotsAfterEndLength);
+ }
+ m_aEditBuffer.WriteCharPtr(">>");
+ m_aEditBuffer.WriteCharPtr("\nendobj\n\n");
+ }
+
+ return true;
+}
+
+bool PDFDocument::WriteCatalogObject(sal_Int32 nAnnotId, PDFReferenceElement*& pRoot)
+{
+ if (m_pXRefStream)
+ pRoot = dynamic_cast<PDFReferenceElement*>(m_pXRefStream->Lookup("Root"));
+ else
+ {
+ if (!m_pTrailer)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Sign: found no trailer");
+ return false;
+ }
+ pRoot = dynamic_cast<PDFReferenceElement*>(m_pTrailer->Lookup("Root"));
+ }
+ if (!pRoot)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Sign: trailer has no root reference");
+ return false;
+ }
+ PDFObjectElement* pCatalog = pRoot->LookupObject();
+ if (!pCatalog)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Sign: invalid catalog reference");
+ return false;
+ }
+ sal_uInt32 nCatalogId = pCatalog->GetObjectValue();
+ if (nCatalogId >= m_aXRef.size())
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Sign: invalid catalog obj id");
+ return false;
+ }
+ PDFElement* pAcroForm = pCatalog->Lookup("AcroForm");
+ auto pAcroFormReference = dynamic_cast<PDFReferenceElement*>(pAcroForm);
+ if (pAcroFormReference)
+ {
+ // Write the updated AcroForm key of the Catalog object.
+ PDFObjectElement* pAcroFormObject = pAcroFormReference->LookupObject();
+ if (!pAcroFormObject)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Sign: invalid AcroForm reference");
+ return false;
+ }
+
+ sal_uInt32 nAcroFormId = pAcroFormObject->GetObjectValue();
+ m_aXRef[nAcroFormId].SetType(XRefEntryType::NOT_COMPRESSED);
+ m_aXRef[nAcroFormId].SetOffset(m_aEditBuffer.Tell());
+ m_aXRef[nAcroFormId].SetDirty(true);
+ m_aEditBuffer.WriteUInt32AsString(nAcroFormId);
+ m_aEditBuffer.WriteCharPtr(" 0 obj\n");
+
+ // If this is nullptr, then the AcroForm object is not in an object stream.
+ SvMemoryStream* pStreamBuffer = pAcroFormObject->GetStreamBuffer();
+
+ if (!pAcroFormObject->Lookup("Fields"))
+ {
+ SAL_WARN("vcl.filter",
+ "PDFDocument::Sign: AcroForm object without required Fields key");
+ return false;
+ }
+
+ PDFDictionaryElement* pAcroFormDictionary = pAcroFormObject->GetDictionary();
+ if (!pAcroFormDictionary)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Sign: AcroForm object has no dictionary");
+ return false;
+ }
+
+ // Offset right before the end of the Fields array.
+ sal_uInt64 nFieldsEndOffset = pAcroFormDictionary->GetKeyOffset("Fields")
+ + pAcroFormDictionary->GetKeyValueLength("Fields")
+ - strlen("]");
+ // Length of beginning of the object dictionary -> Fields end.
+ sal_uInt64 nFieldsBeforeEndLength = nFieldsEndOffset;
+ if (pStreamBuffer)
+ m_aEditBuffer.WriteBytes(pStreamBuffer->GetData(), nFieldsBeforeEndLength);
+ else
+ {
+ nFieldsBeforeEndLength -= pAcroFormObject->GetDictionaryOffset();
+ m_aEditBuffer.WriteCharPtr("<<");
+ m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData())
+ + pAcroFormObject->GetDictionaryOffset(),
+ nFieldsBeforeEndLength);
+ }
+
+ // Append our reference at the end of the Fields array.
+ m_aEditBuffer.WriteCharPtr(" ");
+ m_aEditBuffer.WriteUInt32AsString(nAnnotId);
+ m_aEditBuffer.WriteCharPtr(" 0 R");
+
+ // Length of Fields end -> end of the object dictionary.
+ if (pStreamBuffer)
+ {
+ sal_uInt64 nFieldsAfterEndLength = pStreamBuffer->GetSize() - nFieldsEndOffset;
+ m_aEditBuffer.WriteBytes(static_cast<const char*>(pStreamBuffer->GetData())
+ + nFieldsEndOffset,
+ nFieldsAfterEndLength);
+ }
+ else
+ {
+ sal_uInt64 nFieldsAfterEndLength = pAcroFormObject->GetDictionaryOffset()
+ + pAcroFormObject->GetDictionaryLength()
+ - nFieldsEndOffset;
+ m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData())
+ + nFieldsEndOffset,
+ nFieldsAfterEndLength);
+ m_aEditBuffer.WriteCharPtr(">>");
+ }
+
+ m_aEditBuffer.WriteCharPtr("\nendobj\n\n");
+ }
+ else
+ {
+ // Write the updated Catalog object, references nAnnotId.
+ auto pAcroFormDictionary = dynamic_cast<PDFDictionaryElement*>(pAcroForm);
+ m_aXRef[nCatalogId].SetOffset(m_aEditBuffer.Tell());
+ m_aXRef[nCatalogId].SetDirty(true);
+ m_aEditBuffer.WriteUInt32AsString(nCatalogId);
+ m_aEditBuffer.WriteCharPtr(" 0 obj\n");
+ m_aEditBuffer.WriteCharPtr("<<");
+ if (!pAcroFormDictionary)
+ {
+ // No AcroForm key, assume no signatures.
+ m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData())
+ + pCatalog->GetDictionaryOffset(),
+ pCatalog->GetDictionaryLength());
+ m_aEditBuffer.WriteCharPtr("/AcroForm<</Fields[\n");
+ m_aEditBuffer.WriteUInt32AsString(nAnnotId);
+ m_aEditBuffer.WriteCharPtr(" 0 R\n]/SigFlags 3>>\n");
+ }
+ else
+ {
+ // AcroForm key is already there, insert our reference at the Fields end.
+ auto it = pAcroFormDictionary->GetItems().find("Fields");
+ if (it == pAcroFormDictionary->GetItems().end())
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Sign: AcroForm without required Fields key");
+ return false;
+ }
+
+ auto pFields = dynamic_cast<PDFArrayElement*>(it->second);
+ if (!pFields)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Sign: AcroForm Fields is not an array");
+ return false;
+ }
+
+ // Offset right before the end of the Fields array.
+ sal_uInt64 nFieldsEndOffset = pAcroFormDictionary->GetKeyOffset("Fields")
+ + pAcroFormDictionary->GetKeyValueLength("Fields") - 1;
+ // Length of beginning of the Catalog dictionary -> Fields end.
+ sal_uInt64 nFieldsBeforeEndLength = nFieldsEndOffset - pCatalog->GetDictionaryOffset();
+ m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData())
+ + pCatalog->GetDictionaryOffset(),
+ nFieldsBeforeEndLength);
+ m_aEditBuffer.WriteCharPtr(" ");
+ m_aEditBuffer.WriteUInt32AsString(nAnnotId);
+ m_aEditBuffer.WriteCharPtr(" 0 R");
+ // Length of Fields end -> end of the Catalog dictionary.
+ sal_uInt64 nFieldsAfterEndLength = pCatalog->GetDictionaryOffset()
+ + pCatalog->GetDictionaryLength() - nFieldsEndOffset;
+ m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData())
+ + nFieldsEndOffset,
+ nFieldsAfterEndLength);
+ }
+ m_aEditBuffer.WriteCharPtr(">>\nendobj\n\n");
+ }
+
+ return true;
+}
+
+void PDFDocument::WriteXRef(sal_uInt64 nXRefOffset, PDFReferenceElement const* pRoot)
+{
+ if (m_pXRefStream)
+ {
+ // Write the xref stream.
+ // This is a bit meta: the xref stream stores its own offset.
+ sal_Int32 nXRefStreamId = m_aXRef.size();
+ XRefEntry aXRefStreamEntry;
+ aXRefStreamEntry.SetOffset(nXRefOffset);
+ aXRefStreamEntry.SetDirty(true);
+ m_aXRef[nXRefStreamId] = aXRefStreamEntry;
+
+ // Write stream data.
+ SvMemoryStream aXRefStream;
+ const size_t nOffsetLen = 3;
+ // 3 additional bytes: predictor, the first and the third field.
+ const size_t nLineLength = nOffsetLen + 3;
+ // This is the line as it appears before tweaking according to the predictor.
+ std::vector<unsigned char> aOrigLine(nLineLength);
+ // This is the previous line.
+ std::vector<unsigned char> aPrevLine(nLineLength);
+ // This is the line as written to the stream.
+ std::vector<unsigned char> aFilteredLine(nLineLength);
+ for (const auto& rXRef : m_aXRef)
+ {
+ const XRefEntry& rEntry = rXRef.second;
+
+ if (!rEntry.GetDirty())
+ continue;
+
+ // Predictor.
+ size_t nPos = 0;
+ // PNG prediction: up (on all rows).
+ aOrigLine[nPos++] = 2;
+
+ // First field.
+ unsigned char nType = 0;
+ switch (rEntry.GetType())
+ {
+ case XRefEntryType::FREE:
+ nType = 0;
+ break;
+ case XRefEntryType::NOT_COMPRESSED:
+ nType = 1;
+ break;
+ case XRefEntryType::COMPRESSED:
+ nType = 2;
+ break;
+ }
+ aOrigLine[nPos++] = nType;
+
+ // Second field.
+ for (size_t i = 0; i < nOffsetLen; ++i)
+ {
+ size_t nByte = nOffsetLen - i - 1;
+ // Fields requiring more than one byte are stored with the
+ // high-order byte first.
+ unsigned char nCh = (rEntry.GetOffset() & (0xff << (nByte * 8))) >> (nByte * 8);
+ aOrigLine[nPos++] = nCh;
+ }
+
+ // Third field.
+ aOrigLine[nPos++] = 0;
+
+ // Now apply the predictor.
+ aFilteredLine[0] = aOrigLine[0];
+ for (size_t i = 1; i < nLineLength; ++i)
+ {
+ // Count the delta vs the previous line.
+ aFilteredLine[i] = aOrigLine[i] - aPrevLine[i];
+ // Remember the new reference.
+ aPrevLine[i] = aOrigLine[i];
+ }
+
+ aXRefStream.WriteBytes(aFilteredLine.data(), aFilteredLine.size());
+ }
+
+ m_aEditBuffer.WriteUInt32AsString(nXRefStreamId);
+ m_aEditBuffer.WriteCharPtr(
+ " 0 obj\n<</DecodeParms<</Columns 5/Predictor 12>>/Filter/FlateDecode");
+
+ // ID.
+ auto pID = dynamic_cast<PDFArrayElement*>(m_pXRefStream->Lookup("ID"));
+ if (pID)
+ {
+ const std::vector<PDFElement*>& rElements = pID->GetElements();
+ m_aEditBuffer.WriteCharPtr("/ID [ <");
+ for (size_t i = 0; i < rElements.size(); ++i)
+ {
+ auto pIDString = dynamic_cast<PDFHexStringElement*>(rElements[i]);
+ if (!pIDString)
+ continue;
+
+ m_aEditBuffer.WriteOString(pIDString->GetValue());
+ if ((i + 1) < rElements.size())
+ m_aEditBuffer.WriteCharPtr("> <");
+ }
+ m_aEditBuffer.WriteCharPtr("> ] ");
+ }
+
+ // Index.
+ m_aEditBuffer.WriteCharPtr("/Index [ ");
+ for (const auto& rXRef : m_aXRef)
+ {
+ if (!rXRef.second.GetDirty())
+ continue;
+
+ m_aEditBuffer.WriteUInt32AsString(rXRef.first);
+ m_aEditBuffer.WriteCharPtr(" 1 ");
+ }
+ m_aEditBuffer.WriteCharPtr("] ");
+
+ // Info.
+ auto pInfo = dynamic_cast<PDFReferenceElement*>(m_pXRefStream->Lookup("Info"));
+ if (pInfo)
+ {
+ m_aEditBuffer.WriteCharPtr("/Info ");
+ m_aEditBuffer.WriteUInt32AsString(pInfo->GetObjectValue());
+ m_aEditBuffer.WriteCharPtr(" ");
+ m_aEditBuffer.WriteUInt32AsString(pInfo->GetGenerationValue());
+ m_aEditBuffer.WriteCharPtr(" R ");
+ }
+
+ // Length.
+ m_aEditBuffer.WriteCharPtr("/Length ");
+ {
+ ZCodec aZCodec;
+ aZCodec.BeginCompression();
+ aXRefStream.Seek(0);
+ SvMemoryStream aStream;
+ aZCodec.Compress(aXRefStream, aStream);
+ aZCodec.EndCompression();
+ aXRefStream.Seek(0);
+ aXRefStream.SetStreamSize(0);
+ aStream.Seek(0);
+ aXRefStream.WriteStream(aStream);
+ }
+ m_aEditBuffer.WriteUInt32AsString(aXRefStream.GetSize());
+
+ if (!m_aStartXRefs.empty())
+ {
+ // Write location of the previous cross-reference section.
+ m_aEditBuffer.WriteCharPtr("/Prev ");
+ m_aEditBuffer.WriteUInt32AsString(m_aStartXRefs.back());
+ }
+
+ // Root.
+ m_aEditBuffer.WriteCharPtr("/Root ");
+ m_aEditBuffer.WriteUInt32AsString(pRoot->GetObjectValue());
+ m_aEditBuffer.WriteCharPtr(" ");
+ m_aEditBuffer.WriteUInt32AsString(pRoot->GetGenerationValue());
+ m_aEditBuffer.WriteCharPtr(" R ");
+
+ // Size.
+ m_aEditBuffer.WriteCharPtr("/Size ");
+ m_aEditBuffer.WriteUInt32AsString(m_aXRef.size());
+
+ m_aEditBuffer.WriteCharPtr("/Type/XRef/W[1 3 1]>>\nstream\n");
+ aXRefStream.Seek(0);
+ m_aEditBuffer.WriteStream(aXRefStream);
+ m_aEditBuffer.WriteCharPtr("\nendstream\nendobj\n\n");
+ }
+ else
+ {
+ // Write the xref table.
+ m_aEditBuffer.WriteCharPtr("xref\n");
+ for (const auto& rXRef : m_aXRef)
+ {
+ size_t nObject = rXRef.first;
+ size_t nOffset = rXRef.second.GetOffset();
+ if (!rXRef.second.GetDirty())
+ continue;
+
+ m_aEditBuffer.WriteUInt32AsString(nObject);
+ m_aEditBuffer.WriteCharPtr(" 1\n");
+ OStringBuffer aBuffer;
+ aBuffer.append(static_cast<sal_Int32>(nOffset));
+ while (aBuffer.getLength() < 10)
+ aBuffer.insert(0, "0");
+ if (nObject == 0)
+ aBuffer.append(" 65535 f \n");
+ else
+ aBuffer.append(" 00000 n \n");
+ m_aEditBuffer.WriteOString(aBuffer.toString());
+ }
+
+ // Write the trailer.
+ m_aEditBuffer.WriteCharPtr("trailer\n<</Size ");
+ m_aEditBuffer.WriteUInt32AsString(m_aXRef.size());
+ m_aEditBuffer.WriteCharPtr("/Root ");
+ m_aEditBuffer.WriteUInt32AsString(pRoot->GetObjectValue());
+ m_aEditBuffer.WriteCharPtr(" ");
+ m_aEditBuffer.WriteUInt32AsString(pRoot->GetGenerationValue());
+ m_aEditBuffer.WriteCharPtr(" R\n");
+ auto pInfo = dynamic_cast<PDFReferenceElement*>(m_pTrailer->Lookup("Info"));
+ if (pInfo)
+ {
+ m_aEditBuffer.WriteCharPtr("/Info ");
+ m_aEditBuffer.WriteUInt32AsString(pInfo->GetObjectValue());
+ m_aEditBuffer.WriteCharPtr(" ");
+ m_aEditBuffer.WriteUInt32AsString(pInfo->GetGenerationValue());
+ m_aEditBuffer.WriteCharPtr(" R\n");
+ }
+ auto pID = dynamic_cast<PDFArrayElement*>(m_pTrailer->Lookup("ID"));
+ if (pID)
+ {
+ const std::vector<PDFElement*>& rElements = pID->GetElements();
+ m_aEditBuffer.WriteCharPtr("/ID [ <");
+ for (size_t i = 0; i < rElements.size(); ++i)
+ {
+ auto pIDString = dynamic_cast<PDFHexStringElement*>(rElements[i]);
+ if (!pIDString)
+ continue;
+
+ m_aEditBuffer.WriteOString(pIDString->GetValue());
+ if ((i + 1) < rElements.size())
+ m_aEditBuffer.WriteCharPtr(">\n<");
+ }
+ m_aEditBuffer.WriteCharPtr("> ]\n");
+ }
+
+ if (!m_aStartXRefs.empty())
+ {
+ // Write location of the previous cross-reference section.
+ m_aEditBuffer.WriteCharPtr("/Prev ");
+ m_aEditBuffer.WriteUInt32AsString(m_aStartXRefs.back());
+ }
+
+ m_aEditBuffer.WriteCharPtr(">>\n");
+ }
+}
+
+bool PDFDocument::Sign(const uno::Reference<security::XCertificate>& xCertificate,
+ const OUString& rDescription, bool bAdES)
+{
+ m_aEditBuffer.Seek(STREAM_SEEK_TO_END);
+ m_aEditBuffer.WriteCharPtr("\n");
+
+ sal_uInt64 nSignatureLastByteRangeOffset = 0;
+ sal_Int64 nSignatureContentOffset = 0;
+ sal_Int32 nSignatureId = WriteSignatureObject(
+ rDescription, bAdES, nSignatureLastByteRangeOffset, nSignatureContentOffset);
+
+ sal_Int32 nAppearanceId = WriteAppearanceObject();
+
+ std::vector<PDFObjectElement*> aPages = GetPages();
+ if (aPages.empty() || !aPages[0])
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Sign: found no pages");
+ return false;
+ }
+
+ PDFObjectElement& rFirstPage = *aPages[0];
+ sal_Int32 nAnnotId = WriteAnnotObject(rFirstPage, nSignatureId, nAppearanceId);
+
+ if (!WritePageObject(rFirstPage, nAnnotId))
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Sign: failed to write the updated Page object");
+ return false;
+ }
+
+ PDFReferenceElement* pRoot = nullptr;
+ if (!WriteCatalogObject(nAnnotId, pRoot))
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Sign: failed to write the updated Catalog object");
+ return false;
+ }
+
+ sal_uInt64 nXRefOffset = m_aEditBuffer.Tell();
+ WriteXRef(nXRefOffset, pRoot);
+
+ // Write startxref.
+ m_aEditBuffer.WriteCharPtr("startxref\n");
+ m_aEditBuffer.WriteUInt32AsString(nXRefOffset);
+ m_aEditBuffer.WriteCharPtr("\n%%EOF\n");
+
+ // Finalize the signature, now that we know the total file size.
+ // Calculate the length of the last byte range.
+ sal_uInt64 nFileEnd = m_aEditBuffer.Tell();
+ sal_Int64 nLastByteRangeLength
+ = nFileEnd - (nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1);
+ // Write the length to the buffer.
+ m_aEditBuffer.Seek(nSignatureLastByteRangeOffset);
+ OString aByteRangeBuffer = OString::number(nLastByteRangeLength) + " ]";
+ m_aEditBuffer.WriteOString(aByteRangeBuffer);
+
+ // Create the PKCS#7 object.
+ css::uno::Sequence<sal_Int8> aDerEncoded = xCertificate->getEncoded();
+ if (!aDerEncoded.hasElements())
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Sign: empty certificate");
+ return false;
+ }
+
+ m_aEditBuffer.Seek(0);
+ sal_uInt64 nBufferSize1 = nSignatureContentOffset - 1;
+ std::unique_ptr<char[]> aBuffer1(new char[nBufferSize1]);
+ m_aEditBuffer.ReadBytes(aBuffer1.get(), nBufferSize1);
+
+ m_aEditBuffer.Seek(nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1);
+ sal_uInt64 nBufferSize2 = nLastByteRangeLength;
+ std::unique_ptr<char[]> aBuffer2(new char[nBufferSize2]);
+ m_aEditBuffer.ReadBytes(aBuffer2.get(), nBufferSize2);
+
+ OStringBuffer aCMSHexBuffer;
+ svl::crypto::Signing aSigning(xCertificate);
+ aSigning.AddDataRange(aBuffer1.get(), nBufferSize1);
+ aSigning.AddDataRange(aBuffer2.get(), nBufferSize2);
+ if (!aSigning.Sign(aCMSHexBuffer))
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Sign: PDFWriter::Sign() failed");
+ return false;
+ }
+
+ assert(aCMSHexBuffer.getLength() <= MAX_SIGNATURE_CONTENT_LENGTH);
+
+ m_aEditBuffer.Seek(nSignatureContentOffset);
+ m_aEditBuffer.WriteOString(aCMSHexBuffer.toString());
+
+ 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 nDictionaryDepth = 0;
+ // Array depth, only the offset/length of the toplevel array is tracked.
+ int nArrayDepth = 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()));
+ ++nDictionaryDepth;
+ }
+ 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()));
+ --nDictionaryDepth;
+ 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 (nDictionaryDepth == 0 && nArrayDepth == 0)
+ {
+ // The array is attached directly, inform the object.
+ pArray = pArr;
+ if (pObject)
+ {
+ pObject->SetArray(pArray);
+ pObject->SetArrayOffset(rStream.Tell());
+ }
+ }
+ ++nArrayDepth;
+ 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()));
+ --nArrayDepth;
+ if (nArrayDepth == 0)
+ pArray = nullptr;
+ rStream.SeekRel(-1);
+ if (nDictionaryDepth == 0 && nArrayDepth == 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 == '-')
+ {
+ // 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 && !nDictionaryDepth && !nArrayDepth && 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 (pArray)
+ // Reference is part of a direct (non-dictionary) array, inform the array.
+ pArray->PushBack(rElements.back().get());
+ if (bInObject && nDictionaryDepth > 0 && pObject)
+ // Inform the object about a new in-dictionary reference.
+ pObject->AddDictionaryReference(pReference);
+ }
+ if (!rElements.back()->Read(rStream))
+ {
+ SAL_WARN("vcl.filter",
+ "PDFDocument::Tokenize: PDFElement::Read() failed");
+ return false;
+ }
+ }
+ else if (aKeyword == "stream")
+ {
+ // Look up the length of the stream from the parent object's dictionary.
+ size_t nLength = 0;
+ for (size_t nElement = 0; nElement < rElements.size(); ++nElement)
+ {
+ // Iterate in reverse order.
+ size_t nIndex = rElements.size() - nElement - 1;
+ PDFElement* pElement = rElements[nIndex].get();
+ auto pObj = dynamic_cast<PDFObjectElement*>(pElement);
+ if (!pObj)
+ continue;
+
+ PDFElement* pLookup = pObj->Lookup("Length");
+ auto pReference = dynamic_cast<PDFReferenceElement*>(pLookup);
+ if (pReference)
+ {
+ // Length is provided as a reference.
+ nLength = pReference->LookupNumber(rStream);
+ break;
+ }
+
+ auto pNumber = dynamic_cast<PDFNumberElement*>(pLookup);
+ if (pNumber)
+ {
+ // Length is provided directly.
+ nLength = pNumber->GetValue();
+ break;
+ }
+
+ SAL_WARN(
+ "vcl.filter",
+ "PDFDocument::Tokenize: found no Length key for stream keyword");
+ return false;
+ }
+
+ PDFDocument::SkipLineBreaks(rStream);
+ auto pStreamElement = new PDFStreamElement(nLength);
+ if (pObject)
+ pObject->SetStream(pStreamElement);
+ rElements.push_back(std::unique_ptr<PDFElement>(pStreamElement));
+ if (!rElements.back()->Read(rStream))
+ {
+ SAL_WARN("vcl.filter",
+ "PDFDocument::Tokenize: PDFStreamElement::Read() failed");
+ return false;
+ }
+ }
+ else if (aKeyword == "endstream")
+ {
+ rElements.push_back(std::unique_ptr<PDFElement>(new PDFEndStreamElement));
+ if (!rElements.back()->Read(rStream))
+ {
+ SAL_WARN("vcl.filter",
+ "PDFDocument::Tokenize: PDFEndStreamElement::Read() failed");
+ return false;
+ }
+ }
+ else if (aKeyword == "endobj")
+ {
+ rElements.push_back(std::unique_ptr<PDFElement>(new PDFEndObjectElement));
+ if (!rElements.back()->Read(rStream))
+ {
+ SAL_WARN("vcl.filter",
+ "PDFDocument::Tokenize: PDFEndObjectElement::Read() failed");
+ return false;
+ }
+ if (eMode == TokenizeMode::END_OF_OBJECT)
+ {
+ // Found endobj and only object parsing was requested, we're done.
+ return true;
+ }
+
+ if (pObjectStream)
+ {
+ // We're at the end of an object stream, parse the stored objects.
+ pObjectStream->ParseStoredObjects();
+ pObjectStream = nullptr;
+ pObjectKey = nullptr;
+ }
+ bInObject = false;
+ }
+ else if (aKeyword == "true" || aKeyword == "false")
+ rElements.push_back(std::unique_ptr<PDFElement>(
+ new PDFBooleanElement(aKeyword.toBoolean())));
+ else if (aKeyword == "null")
+ rElements.push_back(std::unique_ptr<PDFElement>(new PDFNullElement));
+ else if (aKeyword == "xref")
+ // Allow 'f' and 'n' keywords.
+ bInXRef = true;
+ else if (bInXRef && (aKeyword == "f" || aKeyword == "n"))
+ {
+ }
+ else if (aKeyword == "trailer")
+ {
+ auto pTrailer = new PDFTrailerElement(*this);
+
+ // Make it possible to find this trailer later by offset.
+ pTrailer->Read(rStream);
+ m_aOffsetTrailers[pTrailer->GetLocation()] = pTrailer;
+
+ // When reading till the first EOF token only, remember
+ // just the first trailer token.
+ if (eMode != TokenizeMode::EOF_TOKEN || !m_pTrailer)
+ m_pTrailer = pTrailer;
+ rElements.push_back(std::unique_ptr<PDFElement>(pTrailer));
+ }
+ else if (aKeyword == "startxref")
+ {
+ bInStartXRef = true;
+ }
+ else
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Tokenize: unexpected '"
+ << aKeyword << "' keyword at byte position "
+ << rStream.Tell());
+ return false;
+ }
+ }
+ else
+ {
+ if (!rtl::isAsciiWhiteSpace(static_cast<unsigned char>(ch)))
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Tokenize: unexpected character: "
+ << ch << " at byte position " << rStream.Tell());
+ return false;
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ return true;
+}
+
+void PDFDocument::SetIDObject(size_t nID, PDFObjectElement* pObject)
+{
+ m_aIDObjects[nID] = pObject;
+}
+
+bool PDFDocument::Read(SvStream& rStream)
+{
+ // Check file magic.
+ std::vector<sal_Int8> aHeader(5);
+ rStream.Seek(0);
+ rStream.ReadBytes(aHeader.data(), aHeader.size());
+ if (aHeader[0] != '%' || aHeader[1] != 'P' || aHeader[2] != 'D' || aHeader[3] != 'F'
+ || aHeader[4] != '-')
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Read: header mismatch");
+ return false;
+ }
+
+ // Allow later editing of the contents in-memory.
+ rStream.Seek(0);
+ m_aEditBuffer.WriteStream(rStream);
+
+ // Look up the offset of the xref table.
+ size_t nStartXRef = FindStartXRef(rStream);
+ SAL_INFO("vcl.filter", "PDFDocument::Read: nStartXRef is " << nStartXRef);
+ if (nStartXRef == 0)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Read: found no xref start offset");
+ return false;
+ }
+ while (true)
+ {
+ rStream.Seek(nStartXRef);
+ OString aKeyword = ReadKeyword(rStream);
+ if (aKeyword.isEmpty())
+ ReadXRefStream(rStream);
+
+ else
+ {
+ if (aKeyword != "xref")
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Read: xref is not the first keyword");
+ return false;
+ }
+ ReadXRef(rStream);
+ if (!Tokenize(rStream, TokenizeMode::EOF_TOKEN, m_aElements, nullptr))
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Read: failed to tokenizer trailer after xref");
+ return false;
+ }
+ }
+
+ PDFNumberElement* pPrev = nullptr;
+ if (m_pTrailer)
+ {
+ pPrev = dynamic_cast<PDFNumberElement*>(m_pTrailer->Lookup("Prev"));
+
+ // Remember the offset of this trailer in the correct order. It's
+ // possible that newer trailers don't have a larger offset.
+ m_aTrailerOffsets.push_back(m_pTrailer->GetLocation());
+ }
+ else if (m_pXRefStream)
+ pPrev = dynamic_cast<PDFNumberElement*>(m_pXRefStream->Lookup("Prev"));
+ if (pPrev)
+ nStartXRef = pPrev->GetValue();
+
+ // Reset state, except the edit buffer.
+ m_aElements.clear();
+ m_aOffsetObjects.clear();
+ m_aIDObjects.clear();
+ m_aStartXRefs.clear();
+ m_aEOFs.clear();
+ m_pTrailer = nullptr;
+ m_pXRefStream = nullptr;
+ if (!pPrev)
+ break;
+ }
+
+ // Then we can tokenize the stream.
+ rStream.Seek(0);
+ return Tokenize(rStream, TokenizeMode::END_OF_STREAM, m_aElements, nullptr);
+}
+
+OString PDFDocument::ReadKeyword(SvStream& rStream)
+{
+ OStringBuffer aBuf;
+ char ch;
+ rStream.ReadChar(ch);
+ if (rStream.eof())
+ return OString();
+ while (rtl::isAsciiAlpha(static_cast<unsigned char>(ch)))
+ {
+ aBuf.append(ch);
+ rStream.ReadChar(ch);
+ if (rStream.eof())
+ return aBuf.toString();
+ }
+ rStream.SeekRel(-1);
+ return aBuf.toString();
+}
+
+size_t PDFDocument::FindStartXRef(SvStream& rStream)
+{
+ // Find the "startxref" token, somewhere near the end of the document.
+ std::vector<char> aBuf(1024);
+ rStream.Seek(STREAM_SEEK_TO_END);
+ if (rStream.Tell() > aBuf.size())
+ rStream.SeekRel(static_cast<sal_Int64>(-1) * aBuf.size());
+ else
+ // The document is really short, then just read it from the start.
+ rStream.Seek(0);
+ size_t nBeforePeek = rStream.Tell();
+ size_t nSize = rStream.ReadBytes(aBuf.data(), aBuf.size());
+ rStream.Seek(nBeforePeek);
+ if (nSize != aBuf.size())
+ aBuf.resize(nSize);
+ OString aPrefix("startxref");
+ // Find the last startxref at the end of the document.
+ auto itLastValid = aBuf.end();
+ auto it = aBuf.begin();
+ while (true)
+ {
+ it = std::search(it, aBuf.end(), aPrefix.getStr(), aPrefix.getStr() + aPrefix.getLength());
+ if (it == aBuf.end())
+ break;
+
+ itLastValid = it;
+ ++it;
+ }
+ if (itLastValid == aBuf.end())
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::FindStartXRef: found no startxref");
+ return 0;
+ }
+
+ rStream.SeekRel(itLastValid - aBuf.begin() + aPrefix.getLength());
+ if (rStream.eof())
+ {
+ SAL_WARN("vcl.filter",
+ "PDFDocument::FindStartXRef: unexpected end of stream after startxref");
+ return 0;
+ }
+
+ PDFDocument::SkipWhitespace(rStream);
+ PDFNumberElement aNumber;
+ if (!aNumber.Read(rStream))
+ return 0;
+ return aNumber.GetValue();
+}
+
+void PDFDocument::ReadXRefStream(SvStream& rStream)
+{
+ // Look up the stream length in the object dictionary.
+ if (!Tokenize(rStream, TokenizeMode::END_OF_OBJECT, m_aElements, nullptr))
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: failed to read object");
+ return;
+ }
+
+ if (m_aElements.empty())
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: no tokens found");
+ return;
+ }
+
+ PDFObjectElement* pObject = nullptr;
+ for (const auto& pElement : m_aElements)
+ {
+ if (auto pObj = dynamic_cast<PDFObjectElement*>(pElement.get()))
+ {
+ pObject = pObj;
+ break;
+ }
+ }
+ if (!pObject)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: no object token found");
+ return;
+ }
+
+ // So that the Prev key can be looked up later.
+ m_pXRefStream = pObject;
+
+ PDFElement* pLookup = pObject->Lookup("Length");
+ auto pNumber = dynamic_cast<PDFNumberElement*>(pLookup);
+ if (!pNumber)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: stream length is not provided");
+ return;
+ }
+ sal_uInt64 nLength = pNumber->GetValue();
+
+ // Look up the stream offset.
+ PDFStreamElement* pStream = nullptr;
+ for (const auto& pElement : m_aElements)
+ {
+ if (auto pS = dynamic_cast<PDFStreamElement*>(pElement.get()))
+ {
+ pStream = pS;
+ break;
+ }
+ }
+ if (!pStream)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: no stream token found");
+ return;
+ }
+
+ // Read and decompress it.
+ rStream.Seek(pStream->GetOffset());
+ std::vector<char> aBuf(nLength);
+ rStream.ReadBytes(aBuf.data(), aBuf.size());
+
+ auto pFilter = dynamic_cast<PDFNameElement*>(pObject->Lookup("Filter"));
+ if (!pFilter)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: no Filter found");
+ return;
+ }
+
+ if (pFilter->GetValue() != "FlateDecode")
+ {
+ SAL_WARN("vcl.filter",
+ "PDFDocument::ReadXRefStream: unexpected filter: " << pFilter->GetValue());
+ return;
+ }
+
+ int nColumns = 1;
+ int nPredictor = 1;
+ if (auto pDecodeParams = dynamic_cast<PDFDictionaryElement*>(pObject->Lookup("DecodeParms")))
+ {
+ const std::map<OString, PDFElement*>& rItems = pDecodeParams->GetItems();
+ auto it = rItems.find("Columns");
+ if (it != rItems.end())
+ if (auto pColumns = dynamic_cast<PDFNumberElement*>(it->second))
+ nColumns = pColumns->GetValue();
+ it = rItems.find("Predictor");
+ if (it != rItems.end())
+ if (auto pPredictor = dynamic_cast<PDFNumberElement*>(it->second))
+ nPredictor = pPredictor->GetValue();
+ }
+
+ SvMemoryStream aSource(aBuf.data(), aBuf.size(), StreamMode::READ);
+ SvMemoryStream aStream;
+ ZCodec aZCodec;
+ aZCodec.BeginCompression();
+ aZCodec.Decompress(aSource, aStream);
+ if (!aZCodec.EndCompression())
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: decompression failed");
+ return;
+ }
+
+ // Look up the first and the last entry we need to read.
+ auto pIndex = dynamic_cast<PDFArrayElement*>(pObject->Lookup("Index"));
+ std::vector<size_t> aFirstObjects;
+ std::vector<size_t> aNumberOfObjects;
+ if (!pIndex)
+ {
+ auto pSize = dynamic_cast<PDFNumberElement*>(pObject->Lookup("Size"));
+ if (pSize)
+ {
+ aFirstObjects.push_back(0);
+ aNumberOfObjects.push_back(pSize->GetValue());
+ }
+ else
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: Index and Size not found");
+ return;
+ }
+ }
+ else
+ {
+ const std::vector<PDFElement*>& rIndexElements = pIndex->GetElements();
+ size_t nFirstObject = 0;
+ for (size_t i = 0; i < rIndexElements.size(); ++i)
+ {
+ if (i % 2 == 0)
+ {
+ auto pFirstObject = dynamic_cast<PDFNumberElement*>(rIndexElements[i]);
+ if (!pFirstObject)
+ {
+ SAL_WARN("vcl.filter",
+ "PDFDocument::ReadXRefStream: Index has no first object");
+ return;
+ }
+ nFirstObject = pFirstObject->GetValue();
+ continue;
+ }
+
+ auto pNumberOfObjects = dynamic_cast<PDFNumberElement*>(rIndexElements[i]);
+ if (!pNumberOfObjects)
+ {
+ SAL_WARN("vcl.filter",
+ "PDFDocument::ReadXRefStream: Index has no number of objects");
+ return;
+ }
+ aFirstObjects.push_back(nFirstObject);
+ aNumberOfObjects.push_back(pNumberOfObjects->GetValue());
+ }
+ }
+
+ // Look up the format of a single entry.
+ const int nWSize = 3;
+ auto pW = dynamic_cast<PDFArrayElement*>(pObject->Lookup("W"));
+ if (!pW || pW->GetElements().size() < nWSize)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: W not found or has < 3 elements");
+ return;
+ }
+ int aW[nWSize];
+ // First character is the (kind of) repeated predictor.
+ int nLineLength = 1;
+ for (size_t i = 0; i < nWSize; ++i)
+ {
+ auto pI = dynamic_cast<PDFNumberElement*>(pW->GetElements()[i]);
+ if (!pI)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: W contains non-number");
+ return;
+ }
+ aW[i] = pI->GetValue();
+ nLineLength += aW[i];
+ }
+
+ if (nPredictor > 1 && nLineLength - 1 != nColumns)
+ {
+ SAL_WARN("vcl.filter",
+ "PDFDocument::ReadXRefStream: /DecodeParms/Columns is inconsistent with /W");
+ return;
+ }
+
+ aStream.Seek(0);
+ for (size_t nSubSection = 0; nSubSection < aFirstObjects.size(); ++nSubSection)
+ {
+ size_t nFirstObject = aFirstObjects[nSubSection];
+ size_t nNumberOfObjects = aNumberOfObjects[nSubSection];
+
+ // This is the line as read from the stream.
+ std::vector<unsigned char> aOrigLine(nLineLength);
+ // This is the line as it appears after tweaking according to nPredictor.
+ std::vector<unsigned char> aFilteredLine(nLineLength);
+ for (size_t nEntry = 0; nEntry < nNumberOfObjects; ++nEntry)
+ {
+ size_t nIndex = nFirstObject + nEntry;
+
+ aStream.ReadBytes(aOrigLine.data(), aOrigLine.size());
+ if (nPredictor > 1 && aOrigLine[0] + 10 != nPredictor)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: in-stream predictor is "
+ "inconsistent with /DecodeParms/Predictor for object #"
+ << nIndex);
+ return;
+ }
+
+ for (int i = 0; i < nLineLength; ++i)
+ {
+ switch (nPredictor)
+ {
+ case 1:
+ // No prediction.
+ break;
+ case 12:
+ // PNG prediction: up (on all rows).
+ aFilteredLine[i] = aFilteredLine[i] + aOrigLine[i];
+ break;
+ default:
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: unexpected predictor: "
+ << nPredictor);
+ return;
+ break;
+ }
+ }
+
+ // First character is already handled above.
+ int nPos = 1;
+ size_t nType = 0;
+ // Start of the current field in the stream data.
+ int nOffset = nPos;
+ for (; nPos < nOffset + aW[0]; ++nPos)
+ {
+ unsigned char nCh = aFilteredLine[nPos];
+ nType = (nType << 8) + nCh;
+ }
+
+ // Start of the object in the file stream.
+ size_t nStreamOffset = 0;
+ nOffset = nPos;
+ for (; nPos < nOffset + aW[1]; ++nPos)
+ {
+ unsigned char nCh = aFilteredLine[nPos];
+ nStreamOffset = (nStreamOffset << 8) + nCh;
+ }
+
+ // Generation number of the object.
+ size_t nGenerationNumber = 0;
+ nOffset = nPos;
+ for (; nPos < nOffset + aW[2]; ++nPos)
+ {
+ unsigned char nCh = aFilteredLine[nPos];
+ nGenerationNumber = (nGenerationNumber << 8) + nCh;
+ }
+
+ // Ignore invalid nType.
+ if (nType <= 2)
+ {
+ if (m_aXRef.find(nIndex) == m_aXRef.end())
+ {
+ XRefEntry aEntry;
+ switch (nType)
+ {
+ case 0:
+ aEntry.SetType(XRefEntryType::FREE);
+ break;
+ case 1:
+ aEntry.SetType(XRefEntryType::NOT_COMPRESSED);
+ break;
+ case 2:
+ aEntry.SetType(XRefEntryType::COMPRESSED);
+ break;
+ }
+ aEntry.SetOffset(nStreamOffset);
+ m_aXRef[nIndex] = aEntry;
+ }
+ }
+ }
+ }
+}
+
+void PDFDocument::ReadXRef(SvStream& rStream)
+{
+ PDFDocument::SkipWhitespace(rStream);
+
+ while (true)
+ {
+ PDFNumberElement aFirstObject;
+ if (!aFirstObject.Read(rStream))
+ {
+ // Next token is not a number, it'll be the trailer.
+ return;
+ }
+
+ if (aFirstObject.GetValue() < 0)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRef: expected first object number >= 0");
+ return;
+ }
+
+ PDFDocument::SkipWhitespace(rStream);
+ PDFNumberElement aNumberOfEntries;
+ if (!aNumberOfEntries.Read(rStream))
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRef: failed to read number of entries");
+ return;
+ }
+
+ if (aNumberOfEntries.GetValue() < 0)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRef: expected zero or more entries");
+ return;
+ }
+
+ size_t nSize = aNumberOfEntries.GetValue();
+ for (size_t nEntry = 0; nEntry < nSize; ++nEntry)
+ {
+ size_t nIndex = aFirstObject.GetValue() + nEntry;
+ PDFDocument::SkipWhitespace(rStream);
+ PDFNumberElement aOffset;
+ if (!aOffset.Read(rStream))
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRef: failed to read offset");
+ return;
+ }
+
+ PDFDocument::SkipWhitespace(rStream);
+ PDFNumberElement aGenerationNumber;
+ if (!aGenerationNumber.Read(rStream))
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRef: failed to read generation number");
+ return;
+ }
+
+ PDFDocument::SkipWhitespace(rStream);
+ OString aKeyword = ReadKeyword(rStream);
+ if (aKeyword != "f" && aKeyword != "n")
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRef: unexpected keyword");
+ return;
+ }
+ // xrefs are read in reverse order, so never update an existing
+ // offset with an older one.
+ if (m_aXRef.find(nIndex) == m_aXRef.end())
+ {
+ XRefEntry aEntry;
+ aEntry.SetOffset(aOffset.GetValue());
+ // Initially only the first entry is dirty.
+ if (nIndex == 0)
+ aEntry.SetDirty(true);
+ m_aXRef[nIndex] = aEntry;
+ }
+ PDFDocument::SkipWhitespace(rStream);
+ }
+ }
+}
+
+void PDFDocument::SkipWhitespace(SvStream& rStream)
+{
+ char ch = 0;
+
+ while (true)
+ {
+ rStream.ReadChar(ch);
+ if (rStream.eof())
+ break;
+
+ if (!rtl::isAsciiWhiteSpace(static_cast<unsigned char>(ch)))
+ {
+ rStream.SeekRel(-1);
+ return;
+ }
+ }
+}
+
+void PDFDocument::SkipLineBreaks(SvStream& rStream)
+{
+ char ch = 0;
+
+ while (true)
+ {
+ rStream.ReadChar(ch);
+ if (rStream.eof())
+ break;
+
+ if (ch != '\n' && ch != '\r')
+ {
+ rStream.SeekRel(-1);
+ return;
+ }
+ }
+}
+
+size_t PDFDocument::GetObjectOffset(size_t nIndex) const
+{
+ auto it = m_aXRef.find(nIndex);
+ if (it == m_aXRef.end() || it->second.GetType() == XRefEntryType::COMPRESSED)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::GetObjectOffset: wanted to look up index #"
+ << nIndex << ", but failed");
+ return 0;
+ }
+
+ return it->second.GetOffset();
+}
+
+const std::vector<std::unique_ptr<PDFElement>>& PDFDocument::GetElements() const
+{
+ return m_aElements;
+}
+
+/// Visits the page tree recursively, looking for page objects.
+static void visitPages(PDFObjectElement* pPages, std::vector<PDFObjectElement*>& rRet)
+{
+ auto pKids = dynamic_cast<PDFArrayElement*>(pPages->Lookup("Kids"));
+ if (!pKids)
+ {
+ SAL_WARN("vcl.filter", "visitPages: pages has no kids");
+ return;
+ }
+
+ pPages->setVisiting(true);
+
+ for (const auto& pKid : pKids->GetElements())
+ {
+ auto pReference = dynamic_cast<PDFReferenceElement*>(pKid);
+ if (!pReference)
+ continue;
+
+ PDFObjectElement* pKidObject = pReference->LookupObject();
+ if (!pKidObject)
+ continue;
+
+ // detect if visiting reenters itself
+ if (pKidObject->alreadyVisiting())
+ {
+ SAL_WARN("vcl.filter", "visitPages: loop in hierarchy");
+ continue;
+ }
+
+ auto pName = dynamic_cast<PDFNameElement*>(pKidObject->Lookup("Type"));
+ if (pName && pName->GetValue() == "Pages")
+ // Pages inside pages: recurse.
+ visitPages(pKidObject, rRet);
+ else
+ // Found an actual page.
+ rRet.push_back(pKidObject);
+ }
+
+ pPages->setVisiting(false);
+}
+
+PDFObjectElement* PDFDocument::GetCatalog()
+{
+ PDFReferenceElement* pRoot = nullptr;
+
+ PDFTrailerElement* pTrailer = nullptr;
+ if (!m_aTrailerOffsets.empty())
+ {
+ // Get access to the latest trailer, and work with the keys of that
+ // one.
+ auto it = m_aOffsetTrailers.find(m_aTrailerOffsets[0]);
+ if (it != m_aOffsetTrailers.end())
+ pTrailer = it->second;
+ }
+
+ if (pTrailer)
+ pRoot = dynamic_cast<PDFReferenceElement*>(pTrailer->Lookup("Root"));
+ else if (m_pXRefStream)
+ pRoot = dynamic_cast<PDFReferenceElement*>(m_pXRefStream->Lookup("Root"));
+
+ if (!pRoot)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::GetCatalog: trailer has no Root key");
+ return nullptr;
+ }
+
+ return pRoot->LookupObject();
+}
+
+std::vector<PDFObjectElement*> PDFDocument::GetPages()
+{
+ std::vector<PDFObjectElement*> aRet;
+
+ PDFObjectElement* pCatalog = GetCatalog();
+ if (!pCatalog)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::GetPages: trailer has no catalog");
+ return aRet;
+ }
+
+ PDFObjectElement* pPages = pCatalog->LookupObject("Pages");
+ if (!pPages)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::GetPages: catalog (obj " << pCatalog->GetObjectValue()
+ << ") has no pages");
+ return aRet;
+ }
+
+ visitPages(pPages, aRet);
+
+ return aRet;
+}
+
+void PDFDocument::PushBackEOF(size_t nOffset) { m_aEOFs.push_back(nOffset); }
+
+std::vector<PDFObjectElement*> PDFDocument::GetSignatureWidgets()
+{
+ std::vector<PDFObjectElement*> aRet;
+
+ std::vector<PDFObjectElement*> aPages = GetPages();
+
+ for (const auto& pPage : aPages)
+ {
+ if (!pPage)
+ continue;
+
+ PDFElement* pAnnotsElement = pPage->Lookup("Annots");
+ auto pAnnots = dynamic_cast<PDFArrayElement*>(pAnnotsElement);
+ if (!pAnnots)
+ {
+ // Annots is not an array, see if it's a reference to an object
+ // with a direct array.
+ auto pAnnotsRef = dynamic_cast<PDFReferenceElement*>(pAnnotsElement);
+ if (pAnnotsRef)
+ {
+ if (PDFObjectElement* pAnnotsObject = pAnnotsRef->LookupObject())
+ {
+ pAnnots = pAnnotsObject->GetArray();
+ }
+ }
+ }
+
+ if (!pAnnots)
+ continue;
+
+ for (const auto& pAnnot : pAnnots->GetElements())
+ {
+ auto pReference = dynamic_cast<PDFReferenceElement*>(pAnnot);
+ if (!pReference)
+ continue;
+
+ PDFObjectElement* pAnnotObject = pReference->LookupObject();
+ if (!pAnnotObject)
+ continue;
+
+ auto pFT = dynamic_cast<PDFNameElement*>(pAnnotObject->Lookup("FT"));
+ if (!pFT || pFT->GetValue() != "Sig")
+ continue;
+
+ aRet.push_back(pAnnotObject);
+ }
+ }
+
+ return aRet;
+}
+
+int PDFDocument::GetMDPPerm()
+{
+ int nRet = 3;
+
+ std::vector<PDFObjectElement*> aSignatures = GetSignatureWidgets();
+ if (aSignatures.empty())
+ {
+ return nRet;
+ }
+
+ for (const auto& pSignature : aSignatures)
+ {
+ vcl::filter::PDFObjectElement* pSig = pSignature->LookupObject("V");
+ if (!pSig)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::GetMDPPerm: can't find signature object");
+ continue;
+ }
+
+ auto pReference = dynamic_cast<PDFArrayElement*>(pSig->Lookup("Reference"));
+ if (!pReference || pReference->GetElements().empty())
+ {
+ continue;
+ }
+
+ auto pFirstReference = dynamic_cast<PDFDictionaryElement*>(pReference->GetElements()[0]);
+ if (!pFirstReference)
+ {
+ SAL_WARN("vcl.filter",
+ "PDFDocument::GetMDPPerm: reference array doesn't contain a dictionary");
+ continue;
+ }
+
+ PDFElement* pTransformParams = pFirstReference->LookupElement("TransformParams");
+ auto pTransformParamsDict = dynamic_cast<PDFDictionaryElement*>(pTransformParams);
+ if (!pTransformParamsDict)
+ {
+ auto pTransformParamsRef = dynamic_cast<PDFReferenceElement*>(pTransformParams);
+ if (pTransformParamsRef)
+ {
+ PDFObjectElement* pTransformParamsObj = pTransformParamsRef->LookupObject();
+ if (pTransformParamsObj)
+ {
+ pTransformParamsDict = pTransformParamsObj->GetDictionary();
+ }
+ }
+ }
+
+ if (!pTransformParamsDict)
+ {
+ continue;
+ }
+
+ auto pP = dynamic_cast<PDFNumberElement*>(pTransformParamsDict->LookupElement("P"));
+ if (!pP)
+ {
+ return 2;
+ }
+
+ return pP->GetValue();
+ }
+
+ return nRet;
+}
+
+std::vector<unsigned char> PDFDocument::DecodeHexString(PDFHexStringElement const* pElement)
+{
+ return svl::crypto::DecodeHexString(pElement->GetValue());
+}
+
+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')
+ {
+ // If the comment ends with a \r\n, count the \n as well to match Adobe Acrobat
+ // behavior.
+ 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 != '.')
+ {
+ rStream.SeekRel(-1);
+ return false;
+ }
+ while (!rStream.eof())
+ {
+ if (!rtl::isAsciiDigit(static_cast<unsigned char>(ch)) && ch != '-' && ch != '.')
+ {
+ rStream.SeekRel(-1);
+ m_nLength = rStream.Tell() - m_nOffset;
+ m_fValue = aBuf.makeStringAndClear().toDouble();
+ 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; }
+
+PDFBooleanElement::PDFBooleanElement(bool /*bValue*/) {}
+
+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)
+{
+}
+
+bool PDFTrailerElement::Read(SvStream& rStream)
+{
+ m_nOffset = rStream.Tell();
+ return true;
+}
+
+PDFElement* PDFTrailerElement::Lookup(const OString& rDictionaryKey)
+{
+ if (m_aDictionary.empty())
+ PDFDictionaryElement::Parse(m_rDoc.GetElements(), this, m_aDictionary);
+
+ return PDFDictionaryElement::Lookup(m_aDictionary, 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)
+{
+}
+
+bool PDFObjectElement::Read(SvStream& /*rStream*/)
+{
+ SAL_INFO("vcl.filter",
+ "PDFObjectElement::Read: " << m_fObjectValue << " " << m_fGenerationValue << " obj");
+ return true;
+}
+
+PDFDictionaryElement::PDFDictionaryElement() = default;
+
+size_t PDFDictionaryElement::Parse(const std::vector<std::unique_ptr<PDFElement>>& rElements,
+ PDFElement* pThis, std::map<OString, PDFElement*>& rDictionary)
+{
+ // The index of last parsed element, in case of nested dictionaries.
+ size_t nRet = 0;
+
+ if (!rDictionary.empty())
+ return nRet;
+
+ pThis->setParsing(true);
+
+ auto pThisObject = dynamic_cast<PDFObjectElement*>(pThis);
+ // This is set to non-nullptr here for nested dictionaries only.
+ auto pThisDictionary = dynamic_cast<PDFDictionaryElement*>(pThis);
+
+ // Find out where the dictionary for this object starts.
+ size_t nIndex = 0;
+ for (size_t i = 0; i < rElements.size(); ++i)
+ {
+ if (rElements[i].get() == pThis)
+ {
+ nIndex = i;
+ break;
+ }
+ }
+
+ OString aName;
+ sal_uInt64 nNameOffset = 0;
+ std::vector<PDFNumberElement*> aNumbers;
+ // The array value we're in -- if any.
+ PDFArrayElement* pArray = nullptr;
+ sal_uInt64 nDictionaryOffset = 0;
+ int nDictionaryDepth = 0;
+ // Toplevel dictionary found (not inside an array).
+ bool bDictionaryFound = false;
+ // Toplevel array found (not inside a dictionary).
+ bool bArrayFound = false;
+ for (size_t i = nIndex; i < rElements.size(); ++i)
+ {
+ // Dictionary tokens can be nested, track enter/leave.
+ if (auto pDictionary = dynamic_cast<PDFDictionaryElement*>(rElements[i].get()))
+ {
+ bDictionaryFound = true;
+ if (++nDictionaryDepth == 1)
+ {
+ // First dictionary start, track start offset.
+ nDictionaryOffset = pDictionary->m_nLocation;
+ if (pThisObject)
+ {
+ if (!bArrayFound)
+ // Then the toplevel dictionary of the object.
+ pThisObject->SetDictionary(pDictionary);
+ pThisDictionary = pDictionary;
+ pThisObject->SetDictionaryOffset(nDictionaryOffset);
+ }
+ }
+ else if (!pDictionary->alreadyParsing())
+ {
+ // Nested dictionary.
+ const size_t nexti
+ = PDFDictionaryElement::Parse(rElements, pDictionary, pDictionary->m_aItems);
+ if (nexti >= i) // ensure we go forwards and not endlessly loop
+ {
+ i = nexti;
+ if (pArray)
+ {
+ // Dictionary value inside an array.
+ pArray->PushBack(pDictionary);
+ }
+ else
+ {
+ // Dictionary toplevel value.
+ rDictionary[aName] = pDictionary;
+ aName.clear();
+ }
+ }
+ }
+ }
+
+ if (auto pEndDictionary = dynamic_cast<PDFEndDictionaryElement*>(rElements[i].get()))
+ {
+ if (--nDictionaryDepth == 0)
+ {
+ // Last dictionary end, track length and stop parsing.
+ if (pThisObject)
+ pThisObject->SetDictionaryLength(pEndDictionary->GetLocation()
+ - nDictionaryOffset);
+ nRet = i;
+ break;
+ }
+ }
+
+ auto pName = dynamic_cast<PDFNameElement*>(rElements[i].get());
+ if (pName)
+ {
+ if (!aNumbers.empty())
+ {
+ PDFNumberElement* pNumber = aNumbers.back();
+ rDictionary[aName] = pNumber;
+ if (pThisDictionary)
+ {
+ pThisDictionary->SetKeyOffset(aName, nNameOffset);
+ pThisDictionary->SetKeyValueLength(
+ aName, pNumber->GetLocation() + pNumber->GetLength() - nNameOffset);
+ }
+ aName.clear();
+ aNumbers.clear();
+ }
+
+ if (aName.isEmpty())
+ {
+ // Remember key.
+ aName = pName->GetValue();
+ nNameOffset = pName->GetLocation();
+ }
+ else
+ {
+ if (pArray)
+ {
+ if (bDictionaryFound)
+ // Array inside dictionary.
+ pArray->PushBack(pName);
+ }
+ else
+ {
+ // Name-name key-value.
+ rDictionary[aName] = pName;
+ if (pThisDictionary)
+ {
+ pThisDictionary->SetKeyOffset(aName, nNameOffset);
+ pThisDictionary->SetKeyValueLength(aName, pName->GetLocation()
+ + PDFNameElement::GetLength()
+ - nNameOffset);
+ }
+ aName.clear();
+ }
+ }
+ continue;
+ }
+
+ auto pArr = dynamic_cast<PDFArrayElement*>(rElements[i].get());
+ if (pArr)
+ {
+ bArrayFound = true;
+ pArray = pArr;
+ continue;
+ }
+
+ auto pEndArr = dynamic_cast<PDFEndArrayElement*>(rElements[i].get());
+ if (pArray && pEndArr)
+ {
+ for (auto& pNumber : aNumbers)
+ pArray->PushBack(pNumber);
+ aNumbers.clear();
+ rDictionary[aName] = pArray;
+ if (pThisDictionary)
+ {
+ pThisDictionary->SetKeyOffset(aName, nNameOffset);
+ // Include the ending ']' in the length of the key - (array)value pair length.
+ pThisDictionary->SetKeyValueLength(aName, pEndArr->GetOffset() - nNameOffset + 1);
+ }
+ aName.clear();
+ pArray = nullptr;
+ continue;
+ }
+
+ auto pReference = dynamic_cast<PDFReferenceElement*>(rElements[i].get());
+ if (pReference)
+ {
+ if (!pArray)
+ {
+ rDictionary[aName] = pReference;
+ if (pThisDictionary)
+ {
+ pThisDictionary->SetKeyOffset(aName, nNameOffset);
+ pThisDictionary->SetKeyValueLength(aName,
+ pReference->GetOffset() - nNameOffset);
+ }
+ aName.clear();
+ }
+ else
+ {
+ if (bDictionaryFound)
+ // Array inside dictionary.
+ pArray->PushBack(pReference);
+ }
+ aNumbers.clear();
+ continue;
+ }
+
+ auto pLiteralString = dynamic_cast<PDFLiteralStringElement*>(rElements[i].get());
+ if (pLiteralString)
+ {
+ rDictionary[aName] = pLiteralString;
+ if (pThisDictionary)
+ pThisDictionary->SetKeyOffset(aName, nNameOffset);
+ aName.clear();
+ continue;
+ }
+
+ auto pBoolean = dynamic_cast<PDFBooleanElement*>(rElements[i].get());
+ if (pBoolean)
+ {
+ rDictionary[aName] = pBoolean;
+ if (pThisDictionary)
+ pThisDictionary->SetKeyOffset(aName, nNameOffset);
+ aName.clear();
+ continue;
+ }
+
+ auto pHexString = dynamic_cast<PDFHexStringElement*>(rElements[i].get());
+ if (pHexString)
+ {
+ if (!pArray)
+ {
+ rDictionary[aName] = pHexString;
+ if (pThisDictionary)
+ pThisDictionary->SetKeyOffset(aName, nNameOffset);
+ aName.clear();
+ }
+ else
+ {
+ pArray->PushBack(pHexString);
+ }
+ continue;
+ }
+
+ if (dynamic_cast<PDFEndObjectElement*>(rElements[i].get()))
+ break;
+
+ // Just remember this, so that in case it's not a reference parameter,
+ // we can handle it later.
+ auto pNumber = dynamic_cast<PDFNumberElement*>(rElements[i].get());
+ if (pNumber)
+ aNumbers.push_back(pNumber);
+ }
+
+ if (!aNumbers.empty())
+ {
+ rDictionary[aName] = aNumbers.back();
+ if (pThisDictionary)
+ pThisDictionary->SetKeyOffset(aName, nNameOffset);
+ aName.clear();
+ aNumbers.clear();
+ }
+
+ pThis->setParsing(false);
+
+ return nRet;
+}
+
+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);
+}
+
+PDFElement* PDFObjectElement::Lookup(const OString& rDictionaryKey)
+{
+ if (m_aDictionary.empty())
+ {
+ if (!m_aElements.empty())
+ // This is a stored object in an object stream.
+ PDFDictionaryElement::Parse(m_aElements, this, m_aDictionary);
+ else
+ // Normal object: elements are stored as members of the document itself.
+ PDFDictionaryElement::Parse(m_rDoc.GetElements(), this, m_aDictionary);
+ }
+
+ return PDFDictionaryElement::Lookup(m_aDictionary, 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()
+{
+ if (m_aDictionary.empty())
+ PDFDictionaryElement::Parse(m_rDoc.GetElements(), this, m_aDictionary);
+
+ 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()
+{
+ if (m_aDictionary.empty())
+ PDFDictionaryElement::Parse(m_rDoc.GetElements(), this, m_aDictionary);
+
+ return m_nDictionaryLength;
+}
+
+void PDFObjectElement::SetArrayLength(sal_uInt64 nArrayLength) { m_nArrayLength = nArrayLength; }
+
+sal_uInt64 PDFObjectElement::GetArrayLength() const { return m_nArrayLength; }
+
+PDFDictionaryElement* PDFObjectElement::GetDictionary()
+{
+ if (m_aDictionary.empty())
+ PDFDictionaryElement::Parse(m_rDoc.GetElements(), this, m_aDictionary);
+ 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()
+{
+ if (m_aDictionary.empty())
+ PDFDictionaryElement::Parse(m_rDoc.GetElements(), this, m_aDictionary);
+
+ return m_aDictionary;
+}
+
+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() const { return m_pArrayElement; }
+
+void PDFObjectElement::ParseStoredObjects()
+{
+ if (!m_pStreamElement)
+ {
+ SAL_WARN("vcl.filter", "PDFObjectElement::ParseStoredObjects: no stream");
+ return;
+ }
+
+ auto pType = dynamic_cast<PDFNameElement*>(Lookup("Type"));
+ if (!pType || pType->GetValue() != "ObjStm")
+ {
+ if (!pType)
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: missing unexpected type");
+ else
+ SAL_WARN("vcl.filter",
+ "PDFDocument::ReadXRefStream: unexpected type: " << pType->GetValue());
+ return;
+ }
+
+ auto pFilter = dynamic_cast<PDFNameElement*>(Lookup("Filter"));
+ if (!pFilter || pFilter->GetValue() != "FlateDecode")
+ {
+ if (!pFilter)
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: missing filter");
+ else
+ SAL_WARN("vcl.filter",
+ "PDFDocument::ReadXRefStream: unexpected filter: " << pFilter->GetValue());
+ return;
+ }
+
+ auto pFirst = dynamic_cast<PDFNumberElement*>(Lookup("First"));
+ if (!pFirst)
+ {
+ SAL_WARN("vcl.filter", "PDFObjectElement::ParseStoredObjects: no First");
+ return;
+ }
+
+ auto pN = dynamic_cast<PDFNumberElement*>(Lookup("N"));
+ if (!pN)
+ {
+ SAL_WARN("vcl.filter", "PDFObjectElement::ParseStoredObjects: no N");
+ return;
+ }
+ size_t nN = pN->GetValue();
+
+ auto pLength = dynamic_cast<PDFNumberElement*>(Lookup("Length"));
+ if (!pLength)
+ {
+ SAL_WARN("vcl.filter", "PDFObjectElement::ParseStoredObjects: no length");
+ return;
+ }
+ size_t nLength = pLength->GetValue();
+
+ // Read and decompress it.
+ SvMemoryStream& rEditBuffer = m_rDoc.GetEditBuffer();
+ rEditBuffer.Seek(m_pStreamElement->GetOffset());
+ std::vector<char> aBuf(nLength);
+ rEditBuffer.ReadBytes(aBuf.data(), aBuf.size());
+ SvMemoryStream aSource(aBuf.data(), aBuf.size(), StreamMode::READ);
+ SvMemoryStream aStream;
+ ZCodec aZCodec;
+ aZCodec.BeginCompression();
+ aZCodec.Decompress(aSource, aStream);
+ if (!aZCodec.EndCompression())
+ {
+ SAL_WARN("vcl.filter", "PDFObjectElement::ParseStoredObjects: decompression failed");
+ return;
+ }
+
+ nLength = aStream.TellEnd();
+ aStream.Seek(0);
+ std::vector<size_t> aObjNums;
+ std::vector<size_t> aOffsets;
+ std::vector<size_t> aLengths;
+ // First iterate over and find out the lengths.
+ for (size_t nObject = 0; nObject < nN; ++nObject)
+ {
+ PDFNumberElement aObjNum;
+ if (!aObjNum.Read(aStream))
+ {
+ SAL_WARN("vcl.filter",
+ "PDFObjectElement::ParseStoredObjects: failed to read object number");
+ return;
+ }
+ aObjNums.push_back(aObjNum.GetValue());
+
+ PDFDocument::SkipWhitespace(aStream);
+
+ PDFNumberElement aByteOffset;
+ if (!aByteOffset.Read(aStream))
+ {
+ SAL_WARN("vcl.filter",
+ "PDFObjectElement::ParseStoredObjects: failed to read byte offset");
+ return;
+ }
+ aOffsets.push_back(pFirst->GetValue() + aByteOffset.GetValue());
+
+ if (aOffsets.size() > 1)
+ aLengths.push_back(aOffsets.back() - aOffsets[aOffsets.size() - 2]);
+ if (nObject + 1 == nN)
+ aLengths.push_back(nLength - aOffsets.back());
+
+ PDFDocument::SkipWhitespace(aStream);
+ }
+
+ // Now create streams with the proper length and tokenize the data.
+ for (size_t nObject = 0; nObject < nN; ++nObject)
+ {
+ size_t nObjNum = aObjNums[nObject];
+ size_t nOffset = aOffsets[nObject];
+ size_t nLen = aLengths[nObject];
+
+ aStream.Seek(nOffset);
+ m_aStoredElements.push_back(std::make_unique<PDFObjectElement>(m_rDoc, nObjNum, 0));
+ PDFObjectElement* pStored = m_aStoredElements.back().get();
+
+ aBuf.clear();
+ aBuf.resize(nLen);
+ aStream.ReadBytes(aBuf.data(), aBuf.size());
+ SvMemoryStream aStoredStream(aBuf.data(), aBuf.size(), StreamMode::READ);
+
+ m_rDoc.Tokenize(aStoredStream, TokenizeMode::STORED_OBJECT, pStored->GetStoredElements(),
+ pStored);
+ // This is how references know the object is stored inside this object stream.
+ m_rDoc.SetIDObject(nObjNum, pStored);
+
+ // Store the stream of the object in the object stream for later use.
+ std::unique_ptr<SvMemoryStream> pStreamBuffer(new SvMemoryStream());
+ aStoredStream.Seek(0);
+ pStreamBuffer->WriteStream(aStoredStream);
+ pStored->SetStreamBuffer(pStreamBuffer);
+ }
+}
+
+std::vector<std::unique_ptr<PDFElement>>& PDFObjectElement::GetStoredElements()
+{
+ return m_aElements;
+}
+
+SvMemoryStream* PDFObjectElement::GetStreamBuffer() const { return m_pStreamBuffer.get(); }
+
+void PDFObjectElement::SetStreamBuffer(std::unique_ptr<SvMemoryStream>& pStreamBuffer)
+{
+ m_pStreamBuffer = std::move(pStreamBuffer);
+}
+
+PDFDocument& PDFObjectElement::GetDocument() { return m_rDoc; }
+
+PDFReferenceElement::PDFReferenceElement(PDFDocument& rDoc, PDFNumberElement& rObject,
+ PDFNumberElement const& rGeneration)
+ : m_rDoc(rDoc)
+ , m_fObjectValue(rObject.GetValue())
+ , m_fGenerationValue(rGeneration.GetValue())
+ , m_rObject(rObject)
+{
+}
+
+PDFNumberElement& PDFReferenceElement::GetObjectElement() const { return m_rObject; }
+
+bool PDFReferenceElement::Read(SvStream& rStream)
+{
+ SAL_INFO("vcl.filter",
+ "PDFReferenceElement::Read: " << m_fObjectValue << " " << m_fGenerationValue << " R");
+ m_nOffset = rStream.Tell();
+ return true;
+}
+
+sal_uInt64 PDFReferenceElement::GetOffset() const { return m_nOffset; }
+
+double PDFReferenceElement::LookupNumber(SvStream& rStream) const
+{
+ size_t nOffset = m_rDoc.GetObjectOffset(m_fObjectValue);
+ if (nOffset == 0)
+ {
+ SAL_WARN("vcl.filter", "PDFReferenceElement::LookupNumber: found no offset for object #"
+ << m_fObjectValue);
+ return 0;
+ }
+
+ sal_uInt64 nOrigPos = rStream.Tell();
+ comphelper::ScopeGuard g([&]() { rStream.Seek(nOrigPos); });
+
+ rStream.Seek(nOffset);
+ {
+ PDFDocument::SkipWhitespace(rStream);
+ PDFNumberElement aNumber;
+ bool bRet = aNumber.Read(rStream);
+ if (!bRet || aNumber.GetValue() != m_fObjectValue)
+ {
+ SAL_WARN("vcl.filter",
+ "PDFReferenceElement::LookupNumber: offset points to not matching object");
+ return 0;
+ }
+ }
+
+ {
+ PDFDocument::SkipWhitespace(rStream);
+ PDFNumberElement aNumber;
+ bool bRet = aNumber.Read(rStream);
+ if (!bRet || aNumber.GetValue() != m_fGenerationValue)
+ {
+ SAL_WARN("vcl.filter",
+ "PDFReferenceElement::LookupNumber: offset points to not matching generation");
+ return 0;
+ }
+ }
+
+ {
+ PDFDocument::SkipWhitespace(rStream);
+ OString aKeyword = PDFDocument::ReadKeyword(rStream);
+ if (aKeyword != "obj")
+ {
+ SAL_WARN("vcl.filter",
+ "PDFReferenceElement::LookupNumber: offset doesn't point to an obj keyword");
+ return 0;
+ }
+ }
+
+ PDFDocument::SkipWhitespace(rStream);
+ PDFNumberElement aNumber;
+ if (!aNumber.Read(rStream))
+ {
+ SAL_WARN("vcl.filter",
+ "PDFReferenceElement::LookupNumber: failed to read referenced number");
+ return 0;
+ }
+
+ return aNumber.GetValue();
+}
+
+PDFObjectElement* PDFReferenceElement::LookupObject()
+{
+ return m_rDoc.LookupObject(m_fObjectValue);
+}
+
+PDFObjectElement* PDFDocument::LookupObject(size_t nObjectNumber)
+{
+ auto itIDObjects = m_aIDObjects.find(nObjectNumber);
+
+ if (itIDObjects != m_aIDObjects.end())
+ return itIDObjects->second;
+
+ SAL_WARN("vcl.filter", "PDFDocument::LookupObject: can't find obj " << nObjectNumber);
+ return nullptr;
+}
+
+SvMemoryStream& PDFDocument::GetEditBuffer() { return m_aEditBuffer; }
+
+int PDFReferenceElement::GetObjectValue() const { return m_fObjectValue; }
+
+int PDFReferenceElement::GetGenerationValue() const { return m_fGenerationValue; }
+
+bool PDFDictionaryElement::Read(SvStream& rStream)
+{
+ char ch;
+ rStream.ReadChar(ch);
+ if (ch != '<')
+ {
+ SAL_WARN("vcl.filter", "PDFDictionaryElement::Read: unexpected character: " << ch);
+ return false;
+ }
+
+ if (rStream.eof())
+ {
+ SAL_WARN("vcl.filter", "PDFDictionaryElement::Read: unexpected end of file");
+ return false;
+ }
+
+ rStream.ReadChar(ch);
+ if (ch != '<')
+ {
+ SAL_WARN("vcl.filter", "PDFDictionaryElement::Read: unexpected character: " << ch);
+ return false;
+ }
+
+ m_nLocation = rStream.Tell();
+
+ SAL_INFO("vcl.filter", "PDFDictionaryElement::Read: '<<'");
+
+ return true;
+}
+
+PDFEndDictionaryElement::PDFEndDictionaryElement() = default;
+
+sal_uInt64 PDFEndDictionaryElement::GetLocation() const { return m_nLocation; }
+
+bool PDFEndDictionaryElement::Read(SvStream& rStream)
+{
+ m_nLocation = rStream.Tell();
+ char ch;
+ rStream.ReadChar(ch);
+ if (ch != '>')
+ {
+ SAL_WARN("vcl.filter", "PDFEndDictionaryElement::Read: unexpected character: " << ch);
+ return false;
+ }
+
+ if (rStream.eof())
+ {
+ SAL_WARN("vcl.filter", "PDFEndDictionaryElement::Read: unexpected end of file");
+ return false;
+ }
+
+ rStream.ReadChar(ch);
+ if (ch != '>')
+ {
+ SAL_WARN("vcl.filter", "PDFEndDictionaryElement::Read: unexpected character: " << ch);
+ return false;
+ }
+
+ SAL_INFO("vcl.filter", "PDFEndDictionaryElement::Read: '>>'");
+
+ return true;
+}
+
+PDFNameElement::PDFNameElement() = default;
+
+bool PDFNameElement::Read(SvStream& rStream)
+{
+ char ch;
+ rStream.ReadChar(ch);
+ if (ch != '/')
+ {
+ SAL_WARN("vcl.filter", "PDFNameElement::Read: unexpected character: " << ch);
+ return false;
+ }
+ m_nLocation = rStream.Tell();
+
+ if (rStream.eof())
+ {
+ SAL_WARN("vcl.filter", "PDFNameElement::Read: unexpected end of file");
+ return false;
+ }
+
+ // Read till the first white-space.
+ OStringBuffer aBuf;
+ rStream.ReadChar(ch);
+ while (!rStream.eof())
+ {
+ if (rtl::isAsciiWhiteSpace(static_cast<unsigned char>(ch)) || ch == '/' || ch == '['
+ || ch == ']' || ch == '<' || ch == '>' || ch == '(')
+ {
+ rStream.SeekRel(-1);
+ m_aValue = aBuf.makeStringAndClear();
+ SAL_INFO("vcl.filter", "PDFNameElement::Read: m_aValue is '" << m_aValue << "'");
+ return true;
+ }
+ aBuf.append(ch);
+ rStream.ReadChar(ch);
+ }
+
+ return false;
+}
+
+const OString& PDFNameElement::GetValue() const { return m_aValue; }
+
+sal_uInt64 PDFNameElement::GetLocation() const { return m_nLocation; }
+
+PDFStreamElement::PDFStreamElement(size_t nLength)
+ : m_nLength(nLength)
+ , m_nOffset(0)
+{
+}
+
+bool PDFStreamElement::Read(SvStream& rStream)
+{
+ SAL_INFO("vcl.filter", "PDFStreamElement::Read: length is " << m_nLength);
+ m_nOffset = rStream.Tell();
+ std::vector<unsigned char> aBytes(m_nLength);
+ rStream.ReadBytes(aBytes.data(), aBytes.size());
+ m_aMemory.WriteBytes(aBytes.data(), aBytes.size());
+
+ return rStream.good();
+}
+
+SvMemoryStream& PDFStreamElement::GetMemory() { return m_aMemory; }
+
+sal_uInt64 PDFStreamElement::GetOffset() const { return m_nOffset; }
+
+bool PDFEndStreamElement::Read(SvStream& /*rStream*/) { return true; }
+
+bool PDFEndObjectElement::Read(SvStream& /*rStream*/) { return true; }
+
+PDFArrayElement::PDFArrayElement(PDFObjectElement* pObject)
+ : m_pObject(pObject)
+{
+}
+
+bool PDFArrayElement::Read(SvStream& rStream)
+{
+ char ch;
+ rStream.ReadChar(ch);
+ if (ch != '[')
+ {
+ SAL_WARN("vcl.filter", "PDFArrayElement::Read: unexpected character: " << ch);
+ return false;
+ }
+
+ SAL_INFO("vcl.filter", "PDFArrayElement::Read: '['");
+
+ return true;
+}
+
+void PDFArrayElement::PushBack(PDFElement* pElement)
+{
+ if (m_pObject)
+ SAL_INFO("vcl.filter",
+ "PDFArrayElement::PushBack: object is " << m_pObject->GetObjectValue());
+ m_aElements.push_back(pElement);
+}
+
+const std::vector<PDFElement*>& PDFArrayElement::GetElements() const { return m_aElements; }
+
+PDFEndArrayElement::PDFEndArrayElement() = default;
+
+bool PDFEndArrayElement::Read(SvStream& rStream)
+{
+ m_nOffset = rStream.Tell();
+ char ch;
+ rStream.ReadChar(ch);
+ if (ch != ']')
+ {
+ SAL_WARN("vcl.filter", "PDFEndArrayElement::Read: unexpected character: " << ch);
+ return false;
+ }
+
+ SAL_INFO("vcl.filter", "PDFEndArrayElement::Read: ']'");
+
+ return true;
+}
+
+sal_uInt64 PDFEndArrayElement::GetOffset() const { return m_nOffset; }
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/ipdf/pdfread.cxx b/vcl/source/filter/ipdf/pdfread.cxx
new file mode 100644
index 000000000..bf1157808
--- /dev/null
+++ b/vcl/source/filter/ipdf/pdfread.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/.
+ */
+
+#include <vcl/pdfread.hxx>
+
+#include <config_features.h>
+
+#if HAVE_FEATURE_PDFIUM
+#include <fpdfview.h>
+#include <fpdf_edit.h>
+#include <fpdf_save.h>
+#endif
+
+#include <vcl/graph.hxx>
+#include <bitmapwriteaccess.hxx>
+#include <unotools/ucbstreamhelper.hxx>
+
+#include <vcl/filter/PDFiumLibrary.hxx>
+#include <sal/log.hxx>
+
+using namespace com::sun::star;
+
+namespace
+{
+#if HAVE_FEATURE_PDFIUM
+
+/// Callback class to be used with FPDF_SaveWithVersion().
+struct CompatibleWriter : public FPDF_FILEWRITE
+{
+ SvMemoryStream m_aStream;
+};
+
+int CompatibleWriterCallback(FPDF_FILEWRITE* pFileWrite, const void* pData, unsigned long nSize)
+{
+ auto pImpl = static_cast<CompatibleWriter*>(pFileWrite);
+ pImpl->m_aStream.WriteBytes(pData, nSize);
+ return 1;
+}
+
+/// Convert to inch, then assume 96 DPI.
+inline double pointToPixel(const double fPoint, const double fResolutionDPI)
+{
+ return fPoint * fResolutionDPI / 72.;
+}
+
+/// 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 = OString(aFirstBytes[5]).toInt32();
+ sal_Int32 nMinor = OString(aFirstBytes[7]).toInt32();
+ return !(nMajor > 1 || (nMajor == 1 && nMinor > 6));
+}
+
+/// 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
+ {
+ // Downconvert to PDF-1.6.
+ auto pPdfium = vcl::pdf::PDFiumLibrary::get();
+
+ // Read input into a buffer.
+ SvMemoryStream aInBuffer;
+ aInBuffer.WriteStream(rInStream, nSize);
+
+ // Load the buffer using pdfium.
+ FPDF_DOCUMENT pPdfDocument
+ = FPDF_LoadMemDocument(aInBuffer.GetData(), aInBuffer.GetSize(), /*password=*/nullptr);
+ if (!pPdfDocument)
+ return false;
+
+ CompatibleWriter aWriter;
+ aWriter.version = 1;
+ aWriter.WriteBlock = &CompatibleWriterCallback;
+
+ // 16 means PDF-1.6.
+ if (!FPDF_SaveWithVersion(pPdfDocument, &aWriter, 0, 16))
+ return false;
+
+ FPDF_CloseDocument(pPdfDocument);
+
+ aWriter.m_aStream.Seek(STREAM_SEEK_TO_BEGIN);
+ rOutStream.WriteStream(aWriter.m_aStream);
+ }
+
+ return rOutStream.good();
+}
+#else
+bool getCompatibleStream(SvStream& rInStream, SvStream& rOutStream)
+{
+ rInStream.Seek(STREAM_SEEK_TO_BEGIN);
+ rOutStream.WriteStream(rInStream, STREAM_SEEK_TO_END);
+ return rOutStream.good();
+}
+#endif // HAVE_FEATURE_PDFIUM
+
+VectorGraphicDataArray createVectorGraphicDataArray(SvStream& rStream)
+{
+ // Save the original PDF stream for later use.
+ SvMemoryStream aMemoryStream;
+ if (!getCompatibleStream(rStream, aMemoryStream))
+ return VectorGraphicDataArray();
+
+ const sal_uInt32 nStreamLength = aMemoryStream.TellEnd();
+
+ VectorGraphicDataArray aPdfData(nStreamLength);
+
+ aMemoryStream.Seek(STREAM_SEEK_TO_BEGIN);
+ aMemoryStream.ReadBytes(aPdfData.begin(), nStreamLength);
+ if (aMemoryStream.GetError())
+ return VectorGraphicDataArray();
+
+ return aPdfData;
+}
+
+} // end anonymous namespace
+
+namespace vcl
+{
+size_t RenderPDFBitmaps(const void* pBuffer, int nSize, std::vector<Bitmap>& rBitmaps,
+ const size_t nFirstPage, int nPages, const basegfx::B2DTuple* pSizeHint)
+{
+#if HAVE_FEATURE_PDFIUM
+ const double fResolutionDPI = 96;
+ auto pPdfium = vcl::pdf::PDFiumLibrary::get();
+
+ // Load the buffer using pdfium.
+ FPDF_DOCUMENT pPdfDocument = FPDF_LoadMemDocument(pBuffer, nSize, /*password=*/nullptr);
+ if (!pPdfDocument)
+ return 0;
+
+ const int nPageCount = FPDF_GetPageCount(pPdfDocument);
+ 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.
+ FPDF_PAGE pPdfPage = FPDF_LoadPage(pPdfDocument, nPageIndex);
+ if (!pPdfPage)
+ break;
+
+ // Calculate the bitmap size in points.
+ size_t nPageWidthPoints = FPDF_GetPageWidth(pPdfPage);
+ size_t nPageHeightPoints = FPDF_GetPageHeight(pPdfPage);
+ if (pSizeHint && pSizeHint->getX() && pSizeHint->getY())
+ {
+ // Have a size hint, prefer that over the logic size from the PDF.
+ nPageWidthPoints = convertMm100ToTwip(pSizeHint->getX()) / 20;
+ nPageHeightPoints = convertMm100ToTwip(pSizeHint->getY()) / 20;
+ }
+
+ // Returned unit is points, convert that to pixel.
+ const size_t nPageWidth = pointToPixel(nPageWidthPoints, fResolutionDPI);
+ const size_t nPageHeight = pointToPixel(nPageHeightPoints, fResolutionDPI);
+ FPDF_BITMAP pPdfBitmap = FPDFBitmap_Create(nPageWidth, nPageHeight, /*alpha=*/1);
+ if (!pPdfBitmap)
+ break;
+
+ const FPDF_DWORD nColor = FPDFPage_HasTransparency(pPdfPage) ? 0x00000000 : 0xFFFFFFFF;
+ FPDFBitmap_FillRect(pPdfBitmap, 0, 0, nPageWidth, nPageHeight, nColor);
+ FPDF_RenderPageBitmap(pPdfBitmap, pPdfPage, /*start_x=*/0, /*start_y=*/0, nPageWidth,
+ nPageHeight, /*rotate=*/0, /*flags=*/0);
+
+ // Save the buffer as a bitmap.
+ Bitmap aBitmap(Size(nPageWidth, nPageHeight), 24);
+ {
+ BitmapScopedWriteAccess pWriteAccess(aBitmap);
+ const auto pPdfBuffer = static_cast<ConstScanline>(FPDFBitmap_GetBuffer(pPdfBitmap));
+ const int nStride = FPDFBitmap_GetStride(pPdfBitmap);
+ for (size_t nRow = 0; nRow < nPageHeight; ++nRow)
+ {
+ ConstScanline pPdfLine = pPdfBuffer + (nStride * nRow);
+ // pdfium byte order is BGRA.
+ pWriteAccess->CopyScanline(nRow, pPdfLine, ScanlineFormat::N32BitTcBgra, nStride);
+ }
+ }
+
+ rBitmaps.emplace_back(std::move(aBitmap));
+ FPDFBitmap_Destroy(pPdfBitmap);
+ FPDF_ClosePage(pPdfPage);
+ }
+
+ FPDF_CloseDocument(pPdfDocument);
+
+ return rBitmaps.size();
+#else
+ (void)pBuffer;
+ (void)nSize;
+ (void)rBitmaps;
+ (void)nFirstPage;
+ (void)nPages;
+ (void)pSizeHint;
+ return 0;
+#endif // HAVE_FEATURE_PDFIUM
+}
+
+bool ImportPDF(SvStream& rStream, Graphic& rGraphic)
+{
+ VectorGraphicDataArray aPdfDataArray = createVectorGraphicDataArray(rStream);
+ if (!aPdfDataArray.hasElements())
+ {
+ SAL_WARN("vcl.filter", "ImportPDF: empty PDF data array");
+ return false;
+ }
+
+ auto aVectorGraphicDataPtr = std::make_shared<VectorGraphicData>(aPdfDataArray, OUString(),
+ VectorGraphicDataType::Pdf);
+
+ rGraphic = Graphic(aVectorGraphicDataPtr);
+ return true;
+}
+
+size_t ImportPDFUnloaded(const OUString& rURL, std::vector<std::pair<Graphic, Size>>& rGraphics)
+{
+#if HAVE_FEATURE_PDFIUM
+ std::unique_ptr<SvStream> xStream(
+ ::utl::UcbStreamHelper::CreateStream(rURL, StreamMode::READ | StreamMode::SHARE_DENYNONE));
+
+ // Save the original PDF stream for later use.
+ VectorGraphicDataArray aPdfDataArray = createVectorGraphicDataArray(*xStream);
+ if (!aPdfDataArray.hasElements())
+ return 0;
+
+ // Prepare the link with the PDF stream.
+ const size_t nGraphicContentSize = aPdfDataArray.getLength();
+ std::unique_ptr<sal_uInt8[]> pGraphicContent(new sal_uInt8[nGraphicContentSize]);
+
+ std::copy(aPdfDataArray.begin(), aPdfDataArray.end(), pGraphicContent.get());
+
+ auto pGfxLink = std::make_shared<GfxLink>(std::move(pGraphicContent), nGraphicContentSize,
+ GfxLinkType::NativePdf);
+
+ auto pPdfium = vcl::pdf::PDFiumLibrary::get();
+
+ // Load the buffer using pdfium.
+ FPDF_DOCUMENT pPdfDocument
+ = FPDF_LoadMemDocument(pGfxLink->GetData(), pGfxLink->GetDataSize(), /*password=*/nullptr);
+ if (!pPdfDocument)
+ return 0;
+
+ const int nPageCount = FPDF_GetPageCount(pPdfDocument);
+ if (nPageCount <= 0)
+ return 0;
+
+ for (int nPageIndex = 0; nPageIndex < nPageCount; ++nPageIndex)
+ {
+ double fPageWidth = 0;
+ double fPageHeight = 0;
+ if (FPDF_GetPageSizeByIndex(pPdfDocument, nPageIndex, &fPageWidth, &fPageHeight) == 0)
+ continue;
+
+ // Returned unit is points, convert that to twip
+ // 1 pt = 20 twips
+ constexpr double pointToTwipconversionRatio = 20;
+
+ long nPageWidth = convertTwipToMm100(fPageWidth * pointToTwipconversionRatio);
+ long nPageHeight = convertTwipToMm100(fPageHeight * pointToTwipconversionRatio);
+
+ auto aVectorGraphicDataPtr = std::make_shared<VectorGraphicData>(
+ aPdfDataArray, OUString(), VectorGraphicDataType::Pdf, nPageIndex);
+
+ // 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(aVectorGraphicDataPtr);
+ aGraphic.SetGfxLink(pGfxLink);
+
+ rGraphics.emplace_back(std::move(aGraphic), Size(nPageWidth, nPageHeight));
+ }
+
+ FPDF_CloseDocument(pPdfDocument);
+
+ return rGraphics.size();
+#else
+ (void)rURL;
+ (void)rGraphics;
+ return 0;
+#endif // HAVE_FEATURE_PDFIUM
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/ixbm/xbmread.cxx b/vcl/source/filter/ixbm/xbmread.cxx
new file mode 100644
index 000000000..76d41bf22
--- /dev/null
+++ b/vcl/source/filter/ixbm/xbmread.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <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;
+ long nLastPos;
+ long nWidth;
+ 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;
+ long nRow = 0;
+ long nCol = 0;
+ long nBits = ( eFormat == XBM10 ) ? 16 : 8;
+ 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 ), 1 );
+ pAcc1 = BitmapScopedWriteAccess(aBmp1);
+
+ if( pAcc1 )
+ {
+ aWhite = pAcc1->GetBestMatchingColor( COL_WHITE );
+ aBlack = pAcc1->GetBestMatchingColor( COL_BLACK );
+ ParseData( &rIStm, aLine, eFormat );
+ }
+ else
+ bStatus = false;
+ }
+ }
+ }
+ }
+ }
+
+ if (bStatus && pAcc1)
+ {
+ Bitmap aBlackBmp( Size( pAcc1->Width(), pAcc1->Height() ), 1 );
+
+ pAcc1.reset();
+ aBlackBmp.Erase( COL_BLACK );
+ rGraphic = BitmapEx( aBlackBmp, aBmp1 );
+ eReadState = XBMREAD_OK;
+ }
+ else
+ eReadState = XBMREAD_ERROR;
+ }
+ else
+ {
+ rIStm.ResetError();
+ eReadState = XBMREAD_NEED_MORE;
+ }
+
+ return eReadState;
+}
+
+VCL_DLLPUBLIC bool ImportXBM( SvStream& rStm, Graphic& rGraphic )
+{
+ std::shared_ptr<GraphicReader> pContext = rGraphic.GetReaderContext();
+ rGraphic.SetReaderContext(nullptr);
+ XBMReader* pXBMReader = dynamic_cast<XBMReader*>( pContext.get() );
+ if (!pXBMReader)
+ {
+ pContext = std::make_shared<XBMReader>( rStm );
+ pXBMReader = static_cast<XBMReader*>( pContext.get() );
+ }
+
+ bool bRet = true;
+
+ ReadState eReadState = pXBMReader->ReadXBM( rGraphic );
+
+ if( eReadState == XBMREAD_ERROR )
+ {
+ bRet = false;
+ }
+ else if( eReadState == XBMREAD_NEED_MORE )
+ rGraphic.SetReaderContext( pContext );
+
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/ixbm/xbmread.hxx b/vcl/source/filter/ixbm/xbmread.hxx
new file mode 100644
index 000000000..6d9a7094b
--- /dev/null
+++ b/vcl/source/filter/ixbm/xbmread.hxx
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_FILTER_IXBM_XBMREAD_HXX
+#define INCLUDED_VCL_SOURCE_FILTER_IXBM_XBMREAD_HXX
+
+#include <vcl/graph.hxx>
+
+VCL_DLLPUBLIC bool ImportXBM( SvStream& rStream, Graphic& rGraphic );
+
+#endif // INCLUDED_VCL_SOURCE_FILTER_IXBM_XBMREAD_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/ixpm/rgbtable.hxx b/vcl/source/filter/ixpm/rgbtable.hxx
new file mode 100644
index 000000000..bff3f0d84
--- /dev/null
+++ b/vcl/source/filter/ixpm/rgbtable.hxx
@@ -0,0 +1,696 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_FILTER_IXPM_RGBTABLE_HXX
+#define INCLUDED_VCL_SOURCE_FILTER_IXPM_RGBTABLE_HXX
+
+#include <sal/types.h>
+
+struct XPMRGBTab
+{
+ const char* name;
+ sal_uInt8 red;
+ sal_uInt8 green;
+ sal_uInt8 blue;
+};
+
+static const XPMRGBTab pRGBTable[] = {
+{ "white", 255, 255, 255 },
+{ "black", 0, 0, 0 },
+{ "snow", 255, 250, 250 },
+{ "GhostWhite", 248, 248, 255 },
+{ "WhiteSmoke", 245, 245, 245 },
+{ "gainsboro", 220, 220, 220 },
+{ "FloralWhite", 255, 250, 240 },
+{ "OldLace", 253, 245, 230 },
+{ "linen", 250, 240, 230 },
+{ "AntiqueWhite", 250, 235, 215 },
+{ "PapayaWhip", 255, 239, 213 },
+{ "BlanchedAlmond", 255, 235, 205 },
+{ "bisque", 255, 228, 196 },
+{ "PeachPuff", 255, 218, 185 },
+{ "NavajoWhite", 255, 222, 173 },
+{ "moccasin", 255, 228, 181 },
+{ "cornsilk", 255, 248, 220 },
+{ "ivory", 255, 255, 240 },
+{ "LemonChiffon", 255, 250, 205 },
+{ "seashell", 255, 245, 238 },
+{ "honeydew", 240, 255, 240 },
+{ "MintCream", 245, 255, 250 },
+{ "azure", 240, 255, 255 },
+{ "AliceBlue", 240, 248, 255 },
+{ "lavender", 230, 230, 250 },
+{ "LavenderBlush", 255, 240, 245 },
+{ "MistyRose", 255, 228, 225 },
+{ "DarkSlateGray", 47, 79, 79 },
+{ "DarkSlateGrey", 47, 79, 79 },
+{ "DimGray", 105, 105, 105 },
+{ "DimGrey", 105, 105, 105 },
+{ "SlateGray", 112, 128, 144 },
+{ "SlateGrey", 112, 128, 144 },
+{ "LightSlateGray", 119, 136, 153 },
+{ "LightSlateGrey", 119, 136, 153 },
+{ "gray", 190, 190, 190 },
+{ "grey", 190, 190, 190 },
+{ "LightGrey", 211, 211, 211 },
+{ "LightGray", 211, 211, 211 },
+{ "MidnightBlue", 25, 25, 112 },
+{ "navy", 0, 0, 128 },
+{ "NavyBlue", 0, 0, 128 },
+{ "CornflowerBlue", 100, 149, 237 },
+{ "DarkSlateBlue", 72, 61, 139 },
+{ "SlateBlue", 106, 90, 205 },
+{ "MediumSlateBlue", 123, 104, 238 },
+{ "LightSlateBlue", 132, 112, 255 },
+{ "MediumBlue", 0, 0, 205 },
+{ "RoyalBlue", 65, 105, 225 },
+{ "blue", 0, 0, 255 },
+{ "DodgerBlue", 30, 144, 255 },
+{ "DeepSkyBlue", 0, 191, 255 },
+{ "SkyBlue", 135, 206, 235 },
+{ "LightSkyBlue", 135, 206, 250 },
+{ "SteelBlue", 70, 130, 180 },
+{ "LightSteelBlue", 176, 196, 222 },
+{ "LightBlue", 173, 216, 230 },
+{ "PowderBlue", 176, 224, 230 },
+{ "PaleTurquoise", 175, 238, 238 },
+{ "DarkTurquoise", 0, 206, 209 },
+{ "MediumTurquoise", 72, 209, 204 },
+{ "turquoise", 64, 224, 208 },
+{ "cyan", 0, 255, 255 },
+{ "LightCyan", 224, 255, 255 },
+{ "CadetBlue", 95, 158, 160 },
+{ "MediumAquamarine", 102, 205, 170 },
+{ "aquamarine", 127, 255, 212 },
+{ "DarkGreen", 0, 100, 0 },
+{ "DarkOliveGreen", 85, 107, 47 },
+{ "DarkSeaGreen", 143, 188, 143 },
+{ "SeaGreen", 46, 139, 87 },
+{ "MediumSeaGreen", 60, 179, 113 },
+{ "LightSeaGreen", 32, 178, 170 },
+{ "PaleGreen", 152, 251, 152 },
+{ "SpringGreen", 0, 255, 127 },
+{ "LawnGreen", 124, 252, 0 },
+{ "green", 0, 255, 0 },
+{ "chartreuse", 127, 255, 0 },
+{ "MediumSpringGreen", 0, 250, 154 },
+{ "GreenYellow", 173, 255 , 47 },
+{ "LimeGreen", 50, 205, 50 },
+{ "YellowGreen", 154, 205, 50 },
+{ "ForestGreen", 34, 139, 34 },
+{ "OliveDrab", 107, 142, 35 },
+{ "DarkKhaki", 189, 183, 107 },
+{ "khaki", 240, 230, 140 },
+{ "PaleGoldenrod", 238, 232, 170 },
+{ "LightGoldenrodYellow", 250, 250, 210 },
+{ "LightYellow", 255, 255, 224 },
+{ "yellow", 255, 255, 0 },
+{ "gold", 255, 215, 0 },
+{ "LightGoldenrod", 238, 221, 130 },
+{ "goldenrod", 218, 165, 32 },
+{ "DarkGoldenrod", 184, 134, 11 },
+{ "RosyBrown", 188, 143, 143 },
+{ "IndianRed", 205, 92, 92 },
+{ "SaddleBrown", 139, 69, 19 },
+{ "sienna", 160, 82, 45 },
+{ "peru", 205, 133, 63 },
+{ "burlywood", 222, 184, 135 },
+{ "beige", 245, 245, 220 },
+{ "wheat", 245, 222, 179 },
+{ "SandyBrown", 244, 164, 96 },
+{ "tan", 210, 180, 140 },
+{ "chocolate", 210, 105, 30 },
+{ "firebrick", 178, 34, 34 },
+{ "brown", 165, 42, 42 },
+{ "DarkSalmon", 233, 150, 122 },
+{ "salmon", 250, 128, 114 },
+{ "LightSalmon", 255, 160, 122 },
+{ "orange", 255, 165, 0 },
+{ "DarkOrange", 255, 140, 0 },
+{ "coral", 255, 127, 80 },
+{ "LightCoral", 240, 128, 128 },
+{ "tomato", 255, 99, 71 },
+{ "OrangeRed", 255, 69, 0 },
+{ "red", 255, 0, 0 },
+{ "HotPink", 255, 105, 180 },
+{ "DeepPink", 255, 20, 147 },
+{ "pink", 255, 192, 203 },
+{ "LightPink", 255, 182, 193 },
+{ "PaleVioletRed", 219, 112, 147 },
+{ "maroon", 176, 48, 96 },
+{ "MediumVioletRed", 199, 21, 133 },
+{ "VioletRed", 208, 32, 144 },
+{ "magenta", 255, 0, 255 },
+{ "violet", 238, 130, 238 },
+{ "plum", 221, 160, 221 },
+{ "orchid", 218, 112, 214 },
+{ "MediumOrchid", 186, 85, 211 },
+{ "DarkOrchid", 153, 50, 204 },
+{ "DarkViolet", 148, 0, 211 },
+{ "BlueViolet", 138, 43, 226 },
+{ "purple", 160, 32, 240 },
+{ "MediumPurple", 147, 112, 219 },
+{ "thistle", 216, 191, 216 },
+{ "snow1", 255, 250, 250 },
+{ "snow2", 238, 233, 233 },
+{ "snow3", 205, 201, 201 },
+{ "snow4", 139, 137, 137 },
+{ "seashell1", 255, 245, 238 },
+{ "seashell2", 238, 229, 222 },
+{ "seashell3", 205, 197, 191 },
+{ "seashell4", 139, 134, 130 },
+{ "AntiqueWhite1", 255, 239, 219 },
+{ "AntiqueWhite2", 238, 223, 204 },
+{ "AntiqueWhite3", 205, 192, 176 },
+{ "AntiqueWhite4", 139, 131, 120 },
+{ "bisque1", 255, 228, 196 },
+{ "bisque2", 238, 213, 183 },
+{ "bisque3", 205, 183, 158 },
+{ "bisque4", 139, 125, 107 },
+{ "PeachPuff1", 255, 218, 185 },
+{ "PeachPuff2", 238, 203, 173 },
+{ "PeachPuff3", 205, 175, 149 },
+{ "PeachPuff4", 139, 119, 101 },
+{ "NavajoWhite1", 255, 222, 173 },
+{ "NavajoWhite2", 238, 207, 161 },
+{ "NavajoWhite3", 205, 179, 139 },
+{ "NavajoWhite4", 139, 121, 94 },
+{ "LemonChiffon1", 255, 250, 205 },
+{ "LemonChiffon2", 238, 233, 191 },
+{ "LemonChiffon3", 205, 201, 165 },
+{ "LemonChiffon4", 139, 137, 112 },
+{ "cornsilk1", 255, 248, 220 },
+{ "cornsilk2", 238, 232, 205 },
+{ "cornsilk3", 205, 200, 177 },
+{ "cornsilk4", 139, 136, 120 },
+{ "ivory1", 255, 255, 240 },
+{ "ivory2", 238, 238, 224 },
+{ "ivory3", 205, 205, 193 },
+{ "ivory4", 139, 139, 131 },
+{ "honeydew1", 240, 255, 240 },
+{ "honeydew2", 224, 238, 224 },
+{ "honeydew3", 193, 205, 193 },
+{ "honeydew4", 131, 139, 131 },
+{ "LavenderBlush1", 255, 240, 245 },
+{ "LavenderBlush2", 238, 224, 229 },
+{ "LavenderBlush3", 205, 193, 197 },
+{ "LavenderBlush4", 139, 131, 134 },
+{ "MistyRose1", 255, 228, 225 },
+{ "MistyRose2", 238, 213, 210 },
+{ "MistyRose3", 205, 183, 181 },
+{ "MistyRose4", 139, 125, 123 },
+{ "azure1", 240, 255, 255 },
+{ "azure2", 224, 238, 238 },
+{ "azure3", 193, 205, 205 },
+{ "azure4", 131, 139, 139 },
+{ "SlateBlue1", 131, 111, 255 },
+{ "SlateBlue2", 122, 103, 238 },
+{ "SlateBlue3", 105, 89, 205 },
+{ "SlateBlue4", 71, 60, 139 },
+{ "RoyalBlue1", 72, 118, 255 },
+{ "RoyalBlue2", 67, 110, 238 },
+{ "RoyalBlue3", 58, 95, 205 },
+{ "RoyalBlue4", 39, 64, 139 },
+{ "blue1", 0, 0, 255 },
+{ "blue2", 0, 0, 238 },
+{ "blue3", 0, 0, 205 },
+{ "blue4", 0, 0, 139 },
+{ "DodgerBlue1", 30, 144, 255 },
+{ "DodgerBlue2", 28, 134, 238 },
+{ "DodgerBlue3", 24, 116, 205 },
+{ "DodgerBlue4", 16, 78, 139 },
+{ "SteelBlue1", 99, 184, 255 },
+{ "SteelBlue2", 92, 172, 238 },
+{ "SteelBlue3", 79, 148, 205 },
+{ "SteelBlue4", 54, 100, 139 },
+{ "DeepSkyBlue1", 0, 191, 255 },
+{ "DeepSkyBlue2", 0, 178, 238 },
+{ "DeepSkyBlue3", 0, 154, 205 },
+{ "DeepSkyBlue4", 0, 104, 139 },
+{ "SkyBlue1", 135, 206, 255 },
+{ "SkyBlue2", 126, 192, 238 },
+{ "SkyBlue3", 108, 166, 205 },
+{ "SkyBlue4", 74, 112, 139 },
+{ "LightSkyBlue1", 176, 226, 255 },
+{ "LightSkyBlue2", 164, 211, 238 },
+{ "LightSkyBlue3", 141, 182, 205 },
+{ "LightSkyBlue4", 96, 123, 139 },
+{ "SlateGray1", 198, 226, 255 },
+{ "SlateGray2", 185, 211, 238 },
+{ "SlateGray3", 159, 182, 205 },
+{ "SlateGray4", 108, 123, 139 },
+{ "LightSteelBlue1", 202, 225, 255 },
+{ "LightSteelBlue2", 188, 210, 238 },
+{ "LightSteelBlue3", 162, 181, 205 },
+{ "LightSteelBlue4", 110, 123, 139 },
+{ "LightBlue1", 191, 239, 255 },
+{ "LightBlue2", 178, 223, 238 },
+{ "LightBlue3", 154, 192, 205 },
+{ "LightBlue4", 104, 131, 139 },
+{ "LightCyan1", 224, 255, 255 },
+{ "LightCyan2", 209, 238, 238 },
+{ "LightCyan3", 180, 205, 205 },
+{ "LightCyan4", 122, 139, 139 },
+{ "PaleTurquoise1", 187, 255, 255 },
+{ "PaleTurquoise2", 174, 238, 238 },
+{ "PaleTurquoise3", 150, 205, 205 },
+{ "PaleTurquoise4", 102, 139, 139 },
+{ "CadetBlue1", 152, 245, 255 },
+{ "CadetBlue2", 142, 229, 238 },
+{ "CadetBlue3", 122, 197, 205 },
+{ "CadetBlue4", 83, 134, 139 },
+{ "turquoise1", 0, 245, 255 },
+{ "turquoise2", 0, 229, 238 },
+{ "turquoise3", 0, 197, 205 },
+{ "turquoise4", 0, 134, 139 },
+{ "cyan1", 0, 255, 255 },
+{ "cyan2", 0, 238, 238 },
+{ "cyan3", 0, 205, 205 },
+{ "cyan4", 0, 139, 139 },
+{ "DarkSlateGray1", 151, 255, 255 },
+{ "DarkSlateGray2", 141, 238, 238 },
+{ "DarkSlateGray3", 121, 205, 205 },
+{ "DarkSlateGray4", 82, 139, 139 },
+{ "aquamarine1", 127, 255, 212 },
+{ "aquamarine2", 118, 238, 198 },
+{ "aquamarine3", 102, 205, 170 },
+{ "aquamarine4", 69, 139, 116 },
+{ "DarkSeaGreen1", 193, 255, 193 },
+{ "DarkSeaGreen2", 180, 238, 180 },
+{ "DarkSeaGreen3", 155, 205, 155 },
+{ "DarkSeaGreen4", 105, 139, 105 },
+{ "SeaGreen1", 84, 255, 159 },
+{ "SeaGreen2", 78, 238, 148 },
+{ "SeaGreen3", 67, 205, 128 },
+{ "SeaGreen4", 46, 139, 87 },
+{ "PaleGreen1", 154, 255, 154 },
+{ "PaleGreen2", 144, 238, 144 },
+{ "PaleGreen3", 124, 205, 124 },
+{ "PaleGreen4", 84, 139, 84 },
+{ "SpringGreen1", 0, 255, 127 },
+{ "SpringGreen2", 0, 238, 118 },
+{ "SpringGreen3", 0, 205, 102 },
+{ "SpringGreen4", 0, 139, 69 },
+{ "green1", 0, 255, 0 },
+{ "green2", 0, 238, 0 },
+{ "green3", 0, 205, 0 },
+{ "green4", 0, 139, 0 },
+{ "chartreuse1", 127, 255, 0 },
+{ "chartreuse2", 118, 238, 0 },
+{ "chartreuse3", 102, 205, 0 },
+{ "chartreuse4", 69, 139, 0 },
+{ "OliveDrab1", 192, 255, 62 },
+{ "OliveDrab2", 179, 238, 58 },
+{ "OliveDrab3", 154, 205, 50 },
+{ "OliveDrab4", 105, 139, 34 },
+{ "DarkOliveGreen1", 202, 255, 112 },
+{ "DarkOliveGreen2", 188, 238, 104 },
+{ "DarkOliveGreen3", 162, 205, 90 },
+{ "DarkOliveGreen4", 110, 139, 61 },
+{ "khaki1", 255, 246, 143 },
+{ "khaki2", 238, 230, 133 },
+{ "khaki3", 205, 198, 115 },
+{ "khaki4", 139, 134, 78 },
+{ "LightGoldenrod1", 255, 236, 139 },
+{ "LightGoldenrod2", 238, 220, 130 },
+{ "LightGoldenrod3", 205, 190, 112 },
+{ "LightGoldenrod4", 139, 129, 76 },
+{ "LightYellow1", 255, 255, 224 },
+{ "LightYellow2", 238, 238, 209 },
+{ "LightYellow3", 205, 205, 180 },
+{ "LightYellow4", 139, 139, 122 },
+{ "yellow1", 255, 255, 0 },
+{ "yellow2", 238, 238, 0 },
+{ "yellow3", 205, 205, 0 },
+{ "yellow4", 139, 139, 0 },
+{ "gold1", 255, 215, 0 },
+{ "gold2", 238, 201, 0 },
+{ "gold3", 205, 173, 0 },
+{ "gold4", 139, 117, 0 },
+{ "goldenrod1", 255, 193, 37 },
+{ "goldenrod2", 238, 180, 34 },
+{ "goldenrod3", 205, 155, 29 },
+{ "goldenrod4", 139, 105, 20 },
+{ "DarkGoldenrod1", 255, 185, 15 },
+{ "DarkGoldenrod2", 238, 173, 14 },
+{ "DarkGoldenrod3", 205, 149, 12 },
+{ "DarkGoldenrod4", 139, 101, 8 },
+{ "RosyBrown1", 255, 193, 193 },
+{ "RosyBrown2", 238, 180, 180 },
+{ "RosyBrown3", 205, 155, 155 },
+{ "RosyBrown4", 139, 105, 105 },
+{ "IndianRed1", 255, 106, 106 },
+{ "IndianRed2", 238, 99, 99 },
+{ "IndianRed3", 205, 85, 85 },
+{ "IndianRed4", 139, 58, 58 },
+{ "sienna1", 255, 130, 71 },
+{ "sienna2", 238, 121, 66 },
+{ "sienna3", 205, 104, 57 },
+{ "sienna4", 139, 71, 38 },
+{ "burlywood1", 255, 211, 155 },
+{ "burlywood2", 238, 197, 145 },
+{ "burlywood3", 205, 170, 125 },
+{ "burlywood4", 139, 115, 85 },
+{ "wheat1", 255, 231, 186 },
+{ "wheat2", 238, 216, 174 },
+{ "wheat3", 205, 186, 150 },
+{ "wheat4", 139, 126, 102 },
+{ "tan1", 255, 165, 79 },
+{ "tan2", 238, 154, 73 },
+{ "tan3", 205, 133, 63 },
+{ "tan4", 139 , 90, 43 },
+{ "chocolate1", 255, 127, 36 },
+{ "chocolate2", 238, 118, 33 },
+{ "chocolate3", 205, 102, 29 },
+{ "chocolate4", 139, 69, 19 },
+{ "firebrick1", 255, 48, 48 },
+{ "firebrick2", 238, 44, 44 },
+{ "firebrick3", 205, 38, 38 },
+{ "firebrick4", 139, 26, 26 },
+{ "brown1", 255, 64, 64 },
+{ "brown2", 238, 59, 59 },
+{ "brown3", 205, 51, 51 },
+{ "brown4", 139, 35, 35 },
+{ "salmon1", 255, 140, 105 },
+{ "salmon2", 238, 130, 98 },
+{ "salmon3", 205, 112, 84 },
+{ "salmon4", 139, 76, 57 },
+{ "LightSalmon1", 255, 160, 122 },
+{ "LightSalmon2", 238, 149, 114 },
+{ "LightSalmon3", 205, 129, 98 },
+{ "LightSalmon4", 139, 87, 66 },
+{ "orange1", 255, 165, 0 },
+{ "orange2", 238, 154, 0 },
+{ "orange3", 205, 133, 0 },
+{ "orange4", 139 , 90, 0 },
+{ "DarkOrange1", 255, 127, 0 },
+{ "DarkOrange2", 238, 118, 0 },
+{ "DarkOrange3", 205, 102, 0 },
+{ "DarkOrange4", 139 , 69, 0 },
+{ "coral1", 255, 114, 86 },
+{ "coral2", 238, 106, 80 },
+{ "coral3", 205, 91, 69 },
+{ "coral4", 139, 62, 47 },
+{ "tomato1", 255, 99, 71 },
+{ "tomato2", 238, 92, 66 },
+{ "tomato3", 205, 79, 57 },
+{ "tomato4", 139, 54, 38 },
+{ "OrangeRed1", 255, 69, 0 },
+{ "OrangeRed2", 238, 64, 0 },
+{ "OrangeRed3", 205, 55, 0 },
+{ "OrangeRed4", 139, 37, 0 },
+{ "red1", 255, 0, 0 },
+{ "red2", 238, 0, 0 },
+{ "red3", 205, 0, 0 },
+{ "red4", 139, 0, 0 },
+{ "DeepPink1", 255, 20, 147 },
+{ "DeepPink2", 238, 18, 137 },
+{ "DeepPink3", 205, 16, 118 },
+{ "DeepPink4", 139, 10, 80 },
+{ "HotPink1", 255, 110, 180 },
+{ "HotPink2", 238, 106, 167 },
+{ "HotPink3", 205, 96, 144 },
+{ "HotPink4", 139, 58, 98 },
+{ "pink1", 255, 181, 197 },
+{ "pink2", 238, 169, 184 },
+{ "pink3", 205, 145, 158 },
+{ "pink4", 139, 99, 108 },
+{ "LightPink1", 255, 174, 185 },
+{ "LightPink2", 238, 162, 173 },
+{ "LightPink3", 205, 140, 149 },
+{ "LightPink4", 139, 95, 101 },
+{ "PaleVioletRed1", 255, 130, 171 },
+{ "PaleVioletRed2", 238, 121, 159 },
+{ "PaleVioletRed3", 205, 104, 137 },
+{ "PaleVioletRed4", 139, 71, 93 },
+{ "maroon1", 255, 52, 179 },
+{ "maroon2", 238, 48, 167 },
+{ "maroon3", 205, 41, 144 },
+{ "maroon4", 139, 28, 98 },
+{ "VioletRed1", 255, 62, 150 },
+{ "VioletRed2", 238, 58, 140 },
+{ "VioletRed3", 205, 50, 120 },
+{ "VioletRed4", 139, 34, 82 },
+{ "magenta1", 255, 0, 255 },
+{ "magenta2", 238, 0, 238 },
+{ "magenta3", 205, 0, 205 },
+{ "magenta4", 139, 0, 139 },
+{ "orchid1", 255, 131, 250 },
+{ "orchid2", 238, 122, 233 },
+{ "orchid3", 205, 105, 201 },
+{ "orchid4", 139, 71, 137 },
+{ "plum1", 255, 187, 255 },
+{ "plum2", 238, 174, 238 },
+{ "plum3", 205, 150, 205 },
+{ "plum4", 139, 102, 139 },
+{ "MediumOrchid1", 224, 102, 255 },
+{ "MediumOrchid2", 209, 95, 238 },
+{ "MediumOrchid3", 180, 82, 205 },
+{ "MediumOrchid4", 122, 55, 139 },
+{ "DarkOrchid1", 191, 62, 255 },
+{ "DarkOrchid2", 178, 58, 238 },
+{ "DarkOrchid3", 154, 50, 205 },
+{ "DarkOrchid4", 104, 34, 139 },
+{ "purple1", 155, 48, 255 },
+{ "purple2", 145, 44, 238 },
+{ "purple3", 125, 38, 205 },
+{ "purple4", 85, 26, 139 },
+{ "MediumPurple1", 171, 130, 255 },
+{ "MediumPurple2", 159, 121, 238 },
+{ "MediumPurple3", 137, 104, 205 },
+{ "MediumPurple4", 93, 71, 139 },
+{ "thistle1", 255, 225, 255 },
+{ "thistle2", 238, 210, 238 },
+{ "thistle3", 205, 181, 205 },
+{ "thistle4", 139, 123, 139 },
+{ "gray0", 0, 0, 0 },
+{ "grey0", 0, 0, 0 },
+{ "gray1", 3, 3, 3 },
+{ "grey1", 3, 3, 3 },
+{ "gray2", 5, 5, 5 },
+{ "grey2", 5, 5, 5 },
+{ "gray3", 8, 8, 8 },
+{ "grey3", 8, 8, 8 },
+{ "gray4", 10, 10, 10 },
+{ "grey4", 10, 10, 10 },
+{ "gray5", 13, 13, 13 },
+{ "grey5", 13, 13, 13 },
+{ "gray6", 15, 15, 15 },
+{ "grey6", 15, 15, 15 },
+{ "gray7", 18, 18, 18 },
+{ "grey7", 18, 18, 18 },
+{ "gray8", 20, 20, 20 },
+{ "grey8", 20, 20, 20 },
+{ "gray9", 23, 23, 23 },
+{ "grey9", 23, 23, 23 },
+{ "gray10", 26, 26, 26 },
+{ "grey10", 26, 26, 26 },
+{ "gray11", 28, 28, 28 },
+{ "grey11", 28, 28, 28 },
+{ "gray12", 31, 31, 31 },
+{ "grey12", 31, 31, 31 },
+{ "gray13", 33, 33, 33 },
+{ "grey13", 33, 33, 33 },
+{ "gray14", 36, 36, 36 },
+{ "grey14", 36, 36, 36 },
+{ "gray15", 38, 38, 38 },
+{ "grey15", 38, 38, 38 },
+{ "gray16", 41, 41, 41 },
+{ "grey16", 41, 41, 41 },
+{ "gray17", 43, 43, 43 },
+{ "grey17", 43, 43, 43 },
+{ "gray18", 46, 46, 46 },
+{ "grey18", 46, 46, 46 },
+{ "gray19", 48, 48, 48 },
+{ "grey19", 48, 48, 48 },
+{ "gray20", 51, 51, 51 },
+{ "grey20", 51, 51, 51 },
+{ "gray21", 54, 54, 54 },
+{ "grey21", 54, 54, 54 },
+{ "gray22", 56, 56, 56 },
+{ "grey22", 56, 56, 56 },
+{ "gray23", 59, 59, 59 },
+{ "grey23", 59, 59, 59 },
+{ "gray24", 61, 61, 61 },
+{ "grey24", 61, 61, 61 },
+{ "gray25", 64, 64, 64 },
+{ "grey25", 64, 64, 64 },
+{ "gray26", 66, 66, 66 },
+{ "grey26", 66, 66, 66 },
+{ "gray27", 69, 69, 69 },
+{ "grey27", 69, 69, 69 },
+{ "gray28", 71, 71, 71 },
+{ "grey28", 71, 71, 71 },
+{ "gray29", 74, 74, 74 },
+{ "grey29", 74, 74, 74 },
+{ "gray30", 77, 77, 77 },
+{ "grey30", 77, 77, 77 },
+{ "gray31", 79, 79, 79 },
+{ "grey31", 79, 79, 79 },
+{ "gray32", 82, 82, 82 },
+{ "grey32", 82, 82, 82 },
+{ "gray33", 84, 84, 84 },
+{ "grey33", 84, 84, 84 },
+{ "gray34", 87, 87, 87 },
+{ "grey34", 87, 87, 87 },
+{ "gray35", 89, 89, 89 },
+{ "grey35", 89, 89, 89 },
+{ "gray36", 92, 92, 92 },
+{ "grey36", 92, 92, 92 },
+{ "gray37", 94, 94, 94 },
+{ "grey37", 94, 94, 94 },
+{ "gray38", 97, 97, 97 },
+{ "grey38", 97, 97, 97 },
+{ "gray39", 99, 99, 99 },
+{ "grey39", 99, 99, 99 },
+{ "gray40", 102, 102, 102 },
+{ "grey40", 102, 102, 102 },
+{ "gray41", 105, 105, 105 },
+{ "grey41", 105, 105, 105 },
+{ "gray42", 107, 107, 107 },
+{ "grey42", 107, 107, 107 },
+{ "gray43", 110, 110, 110 },
+{ "grey43", 110, 110, 110 },
+{ "gray44", 112, 112, 112 },
+{ "grey44", 112, 112, 112 },
+{ "gray45", 115, 115, 115 },
+{ "grey45", 115, 115, 115 },
+{ "gray46", 117, 117, 117 },
+{ "grey46", 117, 117, 117 },
+{ "gray47", 120, 120, 120 },
+{ "grey47", 120, 120, 120 },
+{ "gray48", 122, 122, 122 },
+{ "grey48", 122, 122, 122 },
+{ "gray49", 125, 125, 125 },
+{ "grey49", 125, 125, 125 },
+{ "gray50", 127, 127, 127 },
+{ "grey50", 127, 127, 127 },
+{ "gray51", 130, 130, 130 },
+{ "grey51", 130, 130, 130 },
+{ "gray52", 133, 133, 133 },
+{ "grey52", 133, 133, 133 },
+{ "gray53", 135, 135, 135 },
+{ "grey53", 135, 135, 135 },
+{ "gray54", 138, 138, 138 },
+{ "grey54", 138, 138, 138 },
+{ "gray55", 140, 140, 140 },
+{ "grey55", 140, 140, 140 },
+{ "gray56", 143, 143, 143 },
+{ "grey56", 143, 143, 143 },
+{ "gray57", 145, 145, 145 },
+{ "grey57", 145, 145, 145 },
+{ "gray58", 148, 148, 148 },
+{ "grey58", 148, 148, 148 },
+{ "gray59", 150, 150, 150 },
+{ "grey59", 150, 150, 150 },
+{ "gray60", 153, 153, 153 },
+{ "grey60", 153, 153, 153 },
+{ "gray61", 156, 156, 156 },
+{ "grey61", 156, 156, 156 },
+{ "gray62", 158, 158, 158 },
+{ "grey62", 158, 158, 158 },
+{ "gray63", 161, 161, 161 },
+{ "grey63", 161, 161, 161 },
+{ "gray64", 163, 163, 163 },
+{ "grey64", 163, 163, 163 },
+{ "gray65", 166, 166, 166 },
+{ "grey65", 166, 166, 166 },
+{ "gray66", 168, 168, 168 },
+{ "grey66", 168, 168, 168 },
+{ "gray67", 171, 171, 171 },
+{ "grey67", 171, 171, 171 },
+{ "gray68", 173, 173, 173 },
+{ "grey68", 173, 173, 173 },
+{ "gray69", 176, 176, 176 },
+{ "grey69", 176, 176, 176 },
+{ "gray70", 179, 179, 179 },
+{ "grey70", 179, 179, 179 },
+{ "gray71", 181, 181, 181 },
+{ "grey71", 181, 181, 181 },
+{ "gray72", 184, 184, 184 },
+{ "grey72", 184, 184, 184 },
+{ "gray73", 186, 186, 186 },
+{ "grey73", 186, 186, 186 },
+{ "gray74", 189, 189, 189 },
+{ "grey74", 189, 189, 189 },
+{ "gray75", 191, 191, 191 },
+{ "grey75", 191, 191, 191 },
+{ "gray76", 194, 194, 194 },
+{ "grey76", 194, 194, 194 },
+{ "gray77", 196, 196, 196 },
+{ "grey77", 196, 196, 196 },
+{ "gray78", 199, 199, 199 },
+{ "grey78", 199, 199, 199 },
+{ "gray79", 201, 201, 201 },
+{ "grey79", 201, 201, 201 },
+{ "gray80", 204, 204, 204 },
+{ "grey80", 204, 204, 204 },
+{ "gray81", 207, 207, 207 },
+{ "grey81", 207, 207, 207 },
+{ "gray82", 209, 209, 209 },
+{ "grey82", 209, 209, 209 },
+{ "gray83", 212, 212, 212 },
+{ "grey83", 212, 212, 212 },
+{ "gray84", 214, 214, 214 },
+{ "grey84", 214, 214, 214 },
+{ "gray85", 217, 217, 217 },
+{ "grey85", 217, 217, 217 },
+{ "gray86", 219, 219, 219 },
+{ "grey86", 219, 219, 219 },
+{ "gray87", 222, 222, 222 },
+{ "grey87", 222, 222, 222 },
+{ "gray88", 224, 224, 224 },
+{ "grey88", 224, 224, 224 },
+{ "gray89", 227, 227, 227 },
+{ "grey89", 227, 227, 227 },
+{ "gray90", 229, 229, 229 },
+{ "grey90", 229, 229, 229 },
+{ "gray91", 232, 232, 232 },
+{ "grey91", 232, 232, 232 },
+{ "gray92", 235, 235, 235 },
+{ "grey92", 235, 235, 235 },
+{ "gray93", 237, 237, 237 },
+{ "grey93", 237, 237, 237 },
+{ "gray94", 240, 240, 240 },
+{ "grey94", 240, 240, 240 },
+{ "gray95", 242, 242, 242 },
+{ "grey95", 242, 242, 242 },
+{ "gray96", 245, 245, 245 },
+{ "grey96", 245, 245, 245 },
+{ "gray97", 247, 247, 247 },
+{ "grey97", 247, 247, 247 },
+{ "gray98", 250, 250, 250 },
+{ "grey98", 250, 250, 250 },
+{ "gray99", 252, 252, 252 },
+{ "grey99", 252, 252, 252 },
+{ "gray100", 255, 255, 255 },
+{ "grey100", 255, 255, 255 },
+{ "DarkGrey", 169, 169, 169 },
+{ "DarkGray", 169, 169, 169 },
+{ "DarkBlue", 0, 0, 139 },
+{ "DarkCyan", 0, 139, 139 },
+{ "DarkMagenta", 139, 0, 139 },
+{ "DarkRed", 139, 0, 0 },
+{ "LightGreen", 144, 238, 144 },
+{ nullptr, 0 , 0, 0}
+};
+
+#endif // INCLUDED_VCL_SOURCE_FILTER_IXPM_RGBTABLE_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/ixpm/xpmread.cxx b/vcl/source/filter/ixpm/xpmread.cxx
new file mode 100644
index 000000000..fa71bfa3c
--- /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 <bitmapwriteaccess.hxx>
+#include <vcl/graph.hxx>
+#include <tools/stream.hxx>
+#include <graphic/GraphicReader.hxx>
+#include "rgbtable.hxx"
+#include "xpmread.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;
+ 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
+ sal_uInt16 nBits = 1;
+ if ( mnColors > 256 )
+ nBits = 24;
+ else if ( mnColors > 16 )
+ nBits = 8;
+ else if ( mnColors > 2 )
+ nBits = 4;
+ else
+ nBits = 1;
+
+ maBmp = Bitmap( Size( mnWidth, mnHeight ), nBits );
+ mpAcc = BitmapScopedWriteAccess(maBmp);
+
+ // mbTransparent is TRUE if at least one colour is transparent
+ if ( mbTransparent )
+ {
+ maMaskBmp = Bitmap( Size( mnWidth, mnHeight ), 1 );
+ mpMaskAcc = BitmapScopedWriteAccess(maMaskBmp);
+ if ( !mpMaskAcc )
+ mbStatus = false;
+ }
+ if( mpAcc && mbStatus )
+ {
+ if (mnColors <= 256) // palette is only needed by using less than 257
+ { // colors
+ sal_uInt8 i = 0;
+ for (auto& elem : maColMap)
+ {
+ mpAcc->SetPaletteColor(i, Color(elem.second[1], elem.second[2], elem.second[3]));
+ //reuse map entry, overwrite color with palette index
+ elem.second[1] = i;
+ i++;
+ }
+ }
+
+ // now we get the bitmap data
+ mnIdentifier = XPMPIXELS;
+ for (sal_uLong i = 0; i < mnHeight; ++i)
+ {
+ if ( !ImplGetScanLine( i ) )
+ {
+ mbStatus = false;
+ break;
+ }
+ }
+ mnIdentifier = XPMEXTENSIONS;
+ }
+ }
+ }
+
+ delete[] mpStringBuf;
+ delete[] mpTempBuf;
+
+ }
+ if( mbStatus )
+ {
+ mpAcc.reset();
+ if ( mpMaskAcc )
+ {
+ mpMaskAcc.reset();
+ rGraphic = Graphic( BitmapEx( maBmp, maMaskBmp ) );
+ }
+ else
+ {
+ rGraphic = 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;
+ 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/ixpm/xpmread.hxx b/vcl/source/filter/ixpm/xpmread.hxx
new file mode 100644
index 000000000..1d7a071c3
--- /dev/null
+++ b/vcl/source/filter/ixpm/xpmread.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 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_FILTER_IXPM_XPMREAD_HXX
+#define INCLUDED_VCL_SOURCE_FILTER_IXPM_XPMREAD_HXX
+
+#include <tools/stream.hxx>
+
+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/source/filter/jpeg/Exif.cxx b/vcl/source/filter/jpeg/Exif.cxx
new file mode 100644
index 000000000..8f282583f
--- /dev/null
+++ b/vcl/source/filter/jpeg/Exif.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 "Exif.hxx"
+#include <memory>
+#include <osl/endian.h>
+#include <tools/stream.hxx>
+
+using namespace ::exif;
+
+Exif::Exif() :
+ maOrientation(TOP_LEFT),
+ mbExifPresent(false)
+{}
+
+Exif::~Exif()
+{}
+
+
+void Exif::setOrientation(Orientation aOrientation) {
+ maOrientation = aOrientation;
+}
+
+Orientation Exif::convertToOrientation(sal_Int32 value)
+{
+ switch(value) {
+ case 1: return TOP_LEFT;
+ case 2: return TOP_RIGHT;
+ case 3: return BOTTOM_RIGHT;
+ case 4: return BOTTOM_LEFT;
+ case 5: return LEFT_TOP;
+ case 6: return RIGHT_TOP;
+ case 7: return RIGHT_BOTTOM;
+ case 8: return LEFT_BOTTOM;
+ }
+ return TOP_LEFT;
+}
+
+sal_Int32 Exif::getRotation() const
+{
+ switch(maOrientation) {
+ case TOP_LEFT:
+ return 0;
+ case BOTTOM_RIGHT:
+ return 1800;
+ case RIGHT_TOP:
+ return 2700;
+ case LEFT_BOTTOM:
+ return 900;
+ default:
+ break;
+ }
+ return 0;
+}
+
+
+bool Exif::read(SvStream& rStream)
+{
+ sal_Int32 nStreamPosition = rStream.Tell();
+ bool result = processJpeg(rStream, false);
+ rStream.Seek( nStreamPosition );
+
+ return result;
+}
+
+void Exif::write(SvStream& rStream)
+{
+ sal_Int32 nStreamPosition = rStream.Tell();
+ processJpeg(rStream, true);
+ rStream.Seek( nStreamPosition );
+}
+
+bool Exif::processJpeg(SvStream& rStream, bool bSetValue)
+{
+ sal_uInt16 aMagic16;
+ sal_uInt16 aLength;
+
+ sal_uInt32 aSize = rStream.TellEnd();
+ rStream.Seek(STREAM_SEEK_TO_BEGIN);
+
+ rStream.SetEndian( SvStreamEndian::BIG );
+ rStream.ReadUInt16( aMagic16 );
+
+ // Compare JPEG magic bytes
+ if( 0xFFD8 != aMagic16 )
+ {
+ return false;
+ }
+
+ sal_uInt32 aPreviousPosition = STREAM_SEEK_TO_BEGIN;
+
+ while(true)
+ {
+ sal_uInt8 aMarker = 0xD9;
+ sal_Int32 aCount;
+
+ for (aCount = 0; aCount < 7; aCount++)
+ {
+ rStream.ReadUChar( aMarker );
+ if (aMarker != 0xFF)
+ {
+ break;
+ }
+ if (aCount >= 6)
+ {
+ return false;
+ }
+ }
+
+ rStream.ReadUInt16( aLength );
+
+ if (aLength < 8 || aLength > rStream.remainingSize())
+ {
+ return false;
+ }
+
+ if (aMarker == 0xE1)
+ {
+ return processExif(rStream, aLength, bSetValue);
+ }
+ else if (aMarker == 0xD9)
+ {
+ return false;
+ }
+ else
+ {
+ sal_uInt32 aCurrentPosition = rStream.SeekRel(aLength-1);
+ if (aCurrentPosition == aPreviousPosition || aCurrentPosition > aSize)
+ {
+ return false;
+ }
+ aPreviousPosition = aCurrentPosition;
+ }
+ }
+ return false;
+}
+
+namespace {
+
+sal_uInt16 read16(sal_uInt8 const (& data)[2], bool littleEndian) {
+ if (littleEndian) {
+ return data[0] | (sal_uInt16(data[1]) << 8);
+ } else {
+ return data[1] | (sal_uInt16(data[0]) << 8);
+ }
+}
+
+void write16(sal_uInt16 value, sal_uInt8 (& data)[2], bool littleEndian) {
+ if (littleEndian) {
+ data[0] = value & 0xFF;
+ data[1] = value >> 8;
+ } else {
+ data[1] = value & 0xFF;
+ data[0] = value >> 8;
+ }
+}
+
+void write32(sal_uInt32 value, sal_uInt8 (& data)[4], bool littleEndian) {
+ if (littleEndian) {
+ data[0] = value & 0xFF;
+ data[1] = (value >> 8) & 0xFF;
+ data[2] = (value >> 16) & 0xFF;
+ data[3] = value >> 24;
+ } else {
+ data[3] = value & 0xFF;
+ data[2] = (value >> 8) & 0xFF;
+ data[1] = (value >> 16) & 0xFF;
+ data[0] = value >> 24;
+ }
+}
+
+}
+
+void Exif::processIFD(sal_uInt8* pExifData, sal_uInt16 aLength, sal_uInt16 aOffset, sal_uInt16 aNumberOfTags, bool bSetValue, bool littleEndian)
+{
+ ExifIFD* ifd = nullptr;
+
+ while (aOffset <= aLength - 12 && aNumberOfTags > 0)
+ {
+ ifd = reinterpret_cast<ExifIFD*>(&pExifData[aOffset]);
+ sal_uInt16 tag = read16(ifd->tag, littleEndian);
+
+ if (tag == ORIENTATION)
+ {
+ if(bSetValue)
+ {
+ write16(3, ifd->type, littleEndian);
+ write32(1, ifd->count, littleEndian);
+ write16(
+ maOrientation, reinterpret_cast<sal_uInt8 (&)[2]>(ifd->offset), littleEndian);
+ }
+ else
+ {
+ sal_uInt16 nIfdOffset = read16(
+ reinterpret_cast<sal_uInt8 (&)[2]>(ifd->offset), littleEndian);
+ maOrientation = convertToOrientation(nIfdOffset);
+ }
+ }
+
+ aNumberOfTags--;
+ aOffset += 12;
+ }
+}
+
+bool Exif::processExif(SvStream& rStream, sal_uInt16 aSectionLength, bool bSetValue)
+{
+ sal_uInt32 aMagic32;
+ sal_uInt16 aMagic16;
+
+ rStream.ReadUInt32( aMagic32 );
+ rStream.ReadUInt16( aMagic16 );
+
+ // Compare EXIF magic bytes
+ if( 0x45786966 != aMagic32 || 0x0000 != aMagic16)
+ {
+ return false;
+ }
+
+ sal_uInt16 aLength = aSectionLength - 6; // Length = Section - Header
+
+ std::unique_ptr<sal_uInt8[]> aExifData(new sal_uInt8[aLength]);
+ sal_uInt32 aExifDataBeginPosition = rStream.Tell();
+
+ rStream.ReadBytes(aExifData.get(), aLength);
+
+ // Exif detected
+ mbExifPresent = true;
+
+ TiffHeader* aTiffHeader = reinterpret_cast<TiffHeader*>(&aExifData[0]);
+
+ bool bIntel = aTiffHeader->byteOrder == 0x4949; //little-endian
+ bool bMotorola = aTiffHeader->byteOrder == 0x4D4D; //big-endian
+
+ if (!bIntel && !bMotorola)
+ {
+ return false;
+ }
+
+ bool bSwap = false;
+
+#ifdef OSL_BIGENDIAN
+ if (bIntel)
+ bSwap = true;
+#else
+ if (bMotorola)
+ bSwap = true;
+#endif
+
+ if (bSwap)
+ {
+ aTiffHeader->tagAlign = OSL_SWAPWORD(aTiffHeader->tagAlign);
+ aTiffHeader->offset = OSL_SWAPDWORD(aTiffHeader->offset);
+ }
+
+ if (aTiffHeader->tagAlign != 0x002A) // TIFF tag
+ {
+ return false;
+ }
+
+ sal_uInt16 aOffset = aTiffHeader->offset;
+
+ sal_uInt16 aNumberOfTags = aExifData[aOffset];
+ if (bSwap)
+ {
+ aNumberOfTags = ((aExifData[aOffset] << 8) | aExifData[aOffset+1]);
+ }
+
+ processIFD(aExifData.get(), aLength, aOffset+2, aNumberOfTags, bSetValue, bIntel);
+
+ if (bSetValue)
+ {
+ rStream.Seek(aExifDataBeginPosition);
+ rStream.WriteBytes(aExifData.get(), aLength);
+ }
+
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/jpeg/Exif.hxx b/vcl/source/filter/jpeg/Exif.hxx
new file mode 100644
index 000000000..6052a4484
--- /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>
+
+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();
+ ~Exif();
+
+ bool hasExif() const { return mbExifPresent;}
+
+ exif::Orientation getOrientation() const { return maOrientation;}
+ sal_Int32 getRotation() const;
+
+ void setOrientation(exif::Orientation orientation);
+
+ bool read(SvStream& rStream);
+ void write(SvStream& rStream);
+
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/jpeg/JpegReader.cxx b/vcl/source/filter/jpeg/JpegReader.cxx
new file mode 100644
index 000000000..df7374770
--- /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 = FALSE;
+}
+
+}
+
+static long StreamRead( SvStream* pStream, void* pBuffer, long nBufferSize )
+{
+ long nRead = 0;
+
+ if( pStream->GetError() != ERRCODE_IO_PENDING )
+ {
+ long nInitialPosition = pStream->Tell();
+
+ nRead = static_cast<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 = TRUE;
+ 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)
+ {
+ while (numberOfBytes > static_cast<long>(source->pub.bytes_in_buffer))
+ {
+ numberOfBytes -= static_cast<long>(source->pub.bytes_in_buffer);
+ (void) fill_input_buffer(cinfo);
+
+ /* note we assume that fill_input_buffer will never return false,
+ * so suspension need not be handled.
+ */
+ }
+ source->pub.next_input_byte += static_cast<size_t>(numberOfBytes);
+ source->pub.bytes_in_buffer -= static_cast<size_t>(numberOfBytes);
+ }
+}
+
+static void term_source (j_decompress_ptr)
+{
+ /* no work necessary here */
+}
+
+}
+
+void jpeg_svstream_src (j_decompress_ptr cinfo, void* input)
+{
+ SourceManagerStruct * source;
+ SvStream* stream = static_cast<SvStream*>(input);
+
+ /* The source object and input buffer are made permanent so that a series
+ * of JPEG images can be read from the same file by calling jpeg_stdio_src
+ * only before the first one. (If we discarded the buffer at the end of
+ * one image, we'd likely lose the start of the next one.)
+ * This makes it unsafe to use this manager and a different source
+ * manager serially with the same JPEG object. Caveat programmer.
+ */
+
+ if (cinfo->src == nullptr)
+ { /* first time for this JPEG object? */
+ cinfo->src = static_cast<jpeg_source_mgr *>(
+ (*cinfo->mem->alloc_small) (reinterpret_cast<j_common_ptr>(cinfo), JPOOL_PERMANENT, sizeof(SourceManagerStruct)));
+ source = reinterpret_cast<SourceManagerStruct *>(cinfo->src);
+ source->buffer = static_cast<JOCTET *>(
+ (*cinfo->mem->alloc_small) (reinterpret_cast<j_common_ptr>(cinfo), JPOOL_PERMANENT, BUFFER_SIZE * sizeof(JOCTET)));
+ }
+
+ source = reinterpret_cast<SourceManagerStruct*>(cinfo->src);
+ source->pub.init_source = init_source;
+ source->pub.fill_input_buffer = fill_input_buffer;
+ source->pub.skip_input_data = skip_input_data;
+ source->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */
+ source->pub.term_source = term_source;
+ source->stream = stream;
+ source->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */
+ source->pub.next_input_byte = nullptr; /* until buffer loaded */
+}
+
+JPEGReader::JPEGReader( SvStream& rStream, GraphicFilterImportFlags nImportFlags ) :
+ mrStream ( rStream ),
+ mnLastPos ( rStream.Tell() ),
+ mnLastLines ( 0 ),
+ mbSetLogSize ( nImportFlags & GraphicFilterImportFlags::SetLogsizeForJpeg )
+{
+ maUpperName = "SVIJPEG";
+
+ if (!(nImportFlags & GraphicFilterImportFlags::UseExistingBitmap))
+ {
+ mpBitmap.reset(new Bitmap());
+ mpIncompleteAlpha.reset(new Bitmap());
+ }
+}
+
+JPEGReader::~JPEGReader()
+{
+}
+
+bool JPEGReader::CreateBitmap(JPEGCreateBitmapParam const & rParam)
+{
+ if (rParam.nWidth > SAL_MAX_INT32 / 8 || rParam.nHeight > SAL_MAX_INT32 / 8)
+ return false; // avoid overflows later
+
+ if (rParam.nWidth == 0 || rParam.nHeight == 0)
+ return false;
+
+ Size aSize(rParam.nWidth, rParam.nHeight);
+ bool bGray = rParam.bGray;
+
+ mpBitmap.reset(new Bitmap());
+
+ sal_uInt64 nSize = aSize.Width() * aSize.Height();
+
+ if (nSize > SAL_MAX_INT32 / (bGray?1:3))
+ return false;
+
+ if( bGray )
+ {
+ BitmapPalette aGrayPal( 256 );
+
+ for( sal_uInt16 n = 0; n < 256; n++ )
+ {
+ const sal_uInt8 cGray = static_cast<sal_uInt8>(n);
+ aGrayPal[ n ] = BitmapColor( cGray, cGray, cGray );
+ }
+
+ mpBitmap.reset(new Bitmap(aSize, 8, &aGrayPal));
+ }
+ else
+ {
+ mpBitmap.reset(new Bitmap(aSize, 24));
+ }
+
+ 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(long nLines)
+{
+ Graphic aGraphic;
+ const Size aSizePixel(mpBitmap->GetSizePixel());
+
+ if (!mnLastLines)
+ {
+ mpIncompleteAlpha.reset(new Bitmap(aSizePixel, 1));
+ mpIncompleteAlpha->Erase(COL_WHITE);
+ }
+
+ if (nLines && (nLines < aSizePixel.Height()))
+ {
+ const long nNewLines = nLines - mnLastLines;
+
+ if (nNewLines > 0)
+ {
+ {
+ BitmapScopedWriteAccess pAccess(*mpIncompleteAlpha);
+ pAccess->SetFillColor(COL_BLACK);
+ pAccess->FillRect(tools::Rectangle(Point(0, mnLastLines), Size(pAccess->Width(), nNewLines)));
+ }
+
+ aGraphic = BitmapEx(*mpBitmap, *mpIncompleteAlpha);
+ }
+ else
+ {
+ aGraphic = *mpBitmap;
+ }
+ }
+ else
+ {
+ aGraphic = *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
+ 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 = *mpBitmap;
+ }
+
+ bRet = true;
+ }
+ else if( mrStream.GetError() == ERRCODE_IO_PENDING )
+ {
+ bRet = true;
+ }
+
+ // Set status ( Pending has priority )
+ if (mrStream.GetError() == ERRCODE_IO_PENDING)
+ {
+ eReadState = JPEGREAD_NEED_MORE;
+ mrStream.ResetError();
+ }
+ else
+ {
+ eReadState = bRet ? JPEGREAD_OK : JPEGREAD_ERROR;
+ }
+
+ return eReadState;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/jpeg/JpegReader.hxx b/vcl/source/filter/jpeg/JpegReader.hxx
new file mode 100644
index 000000000..c6a8ef927
--- /dev/null
+++ b/vcl/source/filter/jpeg/JpegReader.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEGREADER_HXX
+#define INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEGREADER_HXX
+
+#include <vcl/graph.hxx>
+#include <vcl/bitmap.hxx>
+#include <bitmapwriteaccess.hxx>
+#include <graphic/GraphicReader.hxx>
+
+enum class GraphicFilterImportFlags;
+
+enum ReadState
+{
+ JPEGREAD_OK,
+ JPEGREAD_ERROR,
+ JPEGREAD_NEED_MORE
+};
+
+struct JPEGCreateBitmapParam
+{
+ unsigned long nWidth;
+ unsigned long nHeight;
+ unsigned long density_unit;
+ unsigned long X_density;
+ unsigned long Y_density;
+
+ bool bGray;
+};
+
+class JPEGReader : public GraphicReader
+{
+ SvStream& mrStream;
+ std::unique_ptr<Bitmap> mpBitmap;
+ std::unique_ptr<Bitmap> mpIncompleteAlpha;
+
+ long mnLastPos;
+ long mnLastLines;
+ bool mbSetLogSize;
+
+ Graphic CreateIntermediateGraphic(long nLines);
+
+public:
+ JPEGReader( SvStream& rStream, GraphicFilterImportFlags nImportFlags );
+ virtual ~JPEGReader() override;
+
+ ReadState Read(Graphic& rGraphic, GraphicFilterImportFlags nImportFlags, BitmapScopedWriteAccess* ppAccess);
+
+ bool CreateBitmap(JPEGCreateBitmapParam const & param);
+
+ Bitmap& GetBitmap() { return *mpBitmap; }
+};
+
+#endif // INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEGREADER_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/jpeg/JpegTransform.cxx b/vcl/source/filter/jpeg/JpegTransform.cxx
new file mode 100644
index 000000000..f3b1e7723
--- /dev/null
+++ b/vcl/source/filter/jpeg/JpegTransform.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 )
+{}
+
+JpegTransform::~JpegTransform()
+{}
+
+void JpegTransform::perform()
+{
+ Transform( &mrInputStream, &mrOutputStream, maRotate );
+}
+
+void JpegTransform::setRotate(sal_uInt16 aRotate)
+{
+ maRotate = aRotate;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/jpeg/JpegTransform.hxx b/vcl/source/filter/jpeg/JpegTransform.hxx
new file mode 100644
index 000000000..cb6d3acbc
--- /dev/null
+++ b/vcl/source/filter/jpeg/JpegTransform.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_SOURCE_FILTER_JPEG_JPEGTRANSFORM_HXX
+#define INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEGTRANSFORM_HXX
+
+#include <tools/stream.hxx>
+
+class JpegTransform final
+{
+ sal_uInt16 maRotate;
+ SvStream& mrInputStream;
+ SvStream& mrOutputStream;
+
+public:
+
+ JpegTransform(SvStream& rInputStream, SvStream& rOutputStream);
+ ~JpegTransform();
+
+ void setRotate(sal_uInt16 aRotate);
+ void perform();
+};
+
+#endif // INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEGTRANSFORM_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/jpeg/JpegWriter.cxx b/vcl/source/filter/jpeg/JpegWriter.cxx
new file mode 100644
index 000000000..da0774667
--- /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/bitmapaccess.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( long nY )
+{
+ void* pScanline = nullptr;
+
+ if( mpReadAccess )
+ {
+ if( mbNative )
+ {
+ pScanline = mpReadAccess->GetScanline( nY );
+ }
+ else if( mpBuffer )
+ {
+ BitmapColor aColor;
+ long nWidth = mpReadAccess->Width();
+ sal_uInt8* pTmp = mpBuffer;
+
+ if( mpReadAccess->HasPalette() )
+ {
+ Scanline pScanlineRead = mpReadAccess->GetScanline( nY );
+ for( 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( long nX = 0; nX < nWidth; nX++ )
+ {
+ aColor = mpReadAccess->GetPixelFromData( pScanlineRead, nX );
+ *pTmp++ = aColor.GetRed();
+ if ( !mbGreys )
+ {
+ *pTmp++ = aColor.GetGreen();
+ *pTmp++ = aColor.GetBlue();
+ }
+ }
+ }
+
+ pScanline = mpBuffer;
+ }
+ }
+
+ return pScanline;
+}
+
+bool JPEGWriter::Write( const Graphic& rGraphic )
+{
+ bool bRet = false;
+
+ if ( mxStatusIndicator.is() )
+ {
+ mxStatusIndicator->start( OUString(), 100 );
+ }
+
+ // This slightly weird logic is here to match the behaviour in ImpGraphic::ImplGetBitmap
+ // and is necessary to match pre-existing behaviour. We should probably pass down the expected
+ // background color for alpha from the higher layers.
+ Bitmap aGraphicBmp;
+ if (rGraphic.GetType() == GraphicType::Bitmap)
+ aGraphicBmp = rGraphic.GetBitmapEx().GetBitmap(COL_WHITE);
+ else
+ aGraphicBmp = rGraphic.GetBitmapEx().GetBitmap();
+
+ if ( mbGreys )
+ {
+ if ( !aGraphicBmp.Convert( BmpConversion::N8BitGreys ) )
+ aGraphicBmp = rGraphic.GetBitmapEx().GetBitmap();
+ }
+
+ mpReadAccess = Bitmap::ScopedReadAccess(aGraphicBmp);
+ if( mpReadAccess )
+ {
+ if ( !mbGreys ) // bitmap was not explicitly converted into greyscale,
+ { // check if source is greyscale only
+ bool bIsGrey = true;
+
+ long nWidth = mpReadAccess->Width();
+ for ( long nY = 0; bIsGrey && ( nY < mpReadAccess->Height() ); nY++ )
+ {
+ BitmapColor aColor;
+ Scanline pScanlineRead = mpReadAccess->GetScanline( nY );
+ for( long nX = 0; bIsGrey && ( nX < nWidth ); nX++ )
+ {
+ aColor = mpReadAccess->HasPalette() ? mpReadAccess->GetPaletteColor( mpReadAccess->GetIndexFromData( pScanlineRead, nX ) )
+ : mpReadAccess->GetPixelFromData( pScanlineRead, nX );
+ bIsGrey = ( aColor.GetRed() == aColor.GetGreen() ) && ( aColor.GetRed() == aColor.GetBlue() );
+ }
+ }
+ if ( bIsGrey )
+ mbGreys = true;
+ }
+ if( mpExpWasGrey )
+ *mpExpWasGrey = mbGreys;
+
+ if ( mbGreys )
+ mbNative = ( mpReadAccess->GetScanlineFormat() == ScanlineFormat::N8BitPal && aGraphicBmp.HasGreyPalette8Bit());
+ else
+ mbNative = ( mpReadAccess->GetScanlineFormat() == ScanlineFormat::N24BitTcRgb );
+
+ if( !mbNative )
+ mpBuffer = new sal_uInt8[ AlignedWidth4Bytes( mbGreys ? mpReadAccess->Width() * 8L : mpReadAccess->Width() * 24L ) ];
+
+ SAL_INFO("vcl", "\nJPEG Export - DPI X: " << rGraphic.GetPPI().getX() << "\nJPEG Export - DPI Y: " << rGraphic.GetPPI().getY());
+
+ bRet = WriteJPEG( this, &mrStream, mpReadAccess->Width(),
+ mpReadAccess->Height(), rGraphic.GetPPI(), mbGreys,
+ mnQuality, maChromaSubsampling, mxStatusIndicator );
+
+ delete[] mpBuffer;
+ mpBuffer = nullptr;
+
+ mpReadAccess.reset();
+ }
+ if ( mxStatusIndicator.is() )
+ mxStatusIndicator->end();
+
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/jpeg/JpegWriter.hxx b/vcl/source/filter/jpeg/JpegWriter.hxx
new file mode 100644
index 000000000..a8e7f2566
--- /dev/null
+++ b/vcl/source/filter/jpeg/JpegWriter.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_SOURCE_FILTER_JPEG_JPEGWRITER_HXX
+#define INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEGWRITER_HXX
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapaccess.hxx>
+#include <vcl/graph.hxx>
+#include <com/sun/star/uno/Sequence.h>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/task/XStatusIndicator.hpp>
+
+class JPEGWriter final
+{
+ SvStream& mrStream;
+ Bitmap::ScopedReadAccess mpReadAccess;
+ sal_uInt8* mpBuffer;
+ bool mbNative;
+ bool mbGreys;
+ sal_Int32 mnQuality;
+ sal_Int32 maChromaSubsampling;
+
+ bool* mpExpWasGrey;
+
+ css::uno::Reference< css::task::XStatusIndicator > mxStatusIndicator;
+
+public:
+ JPEGWriter( SvStream& rStream,
+ const css::uno::Sequence< css::beans::PropertyValue >* pFilterData,
+ bool* pExportWasGrey );
+
+ void* GetScanline( long nY );
+ bool Write( const Graphic& rGraphic );
+
+};
+
+#endif // INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEGWRITER_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/jpeg/jinclude.h b/vcl/source/filter/jpeg/jinclude.h
new file mode 100644
index 000000000..5bd55a6e3
--- /dev/null
+++ b/vcl/source/filter/jpeg/jinclude.h
@@ -0,0 +1,81 @@
+/*
+ * jinclude.h
+ *
+ * Copyright (C) 1991-1994, Thomas G. Lane.
+ * This file is part of the Independent JPEG Group's software.
+ * For conditions of distribution and use, see the accompanying README file.
+ *
+ * This file exists to provide a single place to fix any problems with
+ * including the wrong system include files. (Common problems are taken
+ * care of by the standard jconfig symbols, but on really weird systems
+ * you may have to edit this file.)
+ *
+ * NOTE: this file is NOT intended to be included by applications using the
+ * JPEG library. Most applications need only include jpeglib.h.
+ */
+
+/* Include auto-config file to find out which system include files we need. */
+
+#include <jconfig.h> /* auto configuration options */
+#define JCONFIG_INCLUDED /* so that jpeglib.h doesn't do it again */
+
+/*
+ * We need the NULL macro and size_t typedef.
+ * On an ANSI-conforming system it is sufficient to include <stddef.h>.
+ * Otherwise, we get them from <stdlib.h> or <stdio.h>; we may have to
+ * pull in <sys/types.h> as well.
+ * Note that the core JPEG library does not require <stdio.h>;
+ * only the default error handler and data source/destination modules do.
+ * But we must pull it in because of the references to FILE in jpeglib.h.
+ * You can remove those references if you want to compile without <stdio.h>.
+ */
+
+#ifdef HAVE_STDDEF_H
+#include <stddef.h>
+#endif
+
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+
+#ifdef NEED_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#include <stdio.h>
+
+/*
+ * We need memory copying and zeroing functions, plus strncpy().
+ * ANSI and System V implementations declare these in <string.h>.
+ * BSD doesn't have the mem() functions, but it does have bcopy()/bzero().
+ * Some systems may declare memset and memcpy in <memory.h>.
+ *
+ * NOTE: we assume the size parameters to these functions are of type size_t.
+ * Change the casts in these macros if not!
+ */
+
+#ifdef NEED_BSD_STRINGS
+
+#include <strings.h>
+#define MEMZERO(target,size) bzero((void *)(target), (size_t)(size))
+#define MEMCOPY(dest,src,size) bcopy((const void *)(src), (void *)(dest), (size_t)(size))
+
+#else /* not BSD, assume ANSI/SysV string lib */
+
+#include <string.h>
+#define MEMZERO(target,size) memset((void *)(target), 0, (size_t)(size))
+#define MEMCOPY(dest,src,size) memcpy((void *)(dest), (const void *)(src), (size_t)(size))
+
+#endif
+
+/*
+ * In ANSI C, and indeed any rational implementation, size_t is also the
+ * type returned by sizeof(). However, it seems there are some irrational
+ * implementations out there, in which sizeof() returns an int even though
+ * size_t is defined as long or unsigned long. To ensure consistent results
+ * we always use this SIZEOF() macro in place of using sizeof() directly.
+ */
+
+#define SIZEOF(object) ((size_t) sizeof(object))
+
+
diff --git a/vcl/source/filter/jpeg/jpeg.cxx b/vcl/source/filter/jpeg/jpeg.cxx
new file mode 100644
index 000000000..e7158b858
--- /dev/null
+++ b/vcl/source/filter/jpeg/jpeg.cxx
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "JpegReader.hxx"
+#include "JpegWriter.hxx"
+#include "jpeg.hxx"
+
+#include <vcl/graphicfilter.hxx>
+
+VCL_DLLPUBLIC bool ImportJPEG( SvStream& rInputStream, Graphic& rGraphic, GraphicFilterImportFlags nImportFlags, BitmapScopedWriteAccess* ppAccess )
+{
+ bool bReturn = true;
+
+ std::shared_ptr<GraphicReader> pContext = rGraphic.GetReaderContext();
+ rGraphic.SetReaderContext(nullptr);
+ JPEGReader* pJPEGReader = dynamic_cast<JPEGReader*>( pContext.get() );
+ if (!pJPEGReader)
+ {
+ pContext = std::make_shared<JPEGReader>( rInputStream, nImportFlags );
+ pJPEGReader = static_cast<JPEGReader*>( pContext.get() );
+ }
+
+ ReadState eReadState = pJPEGReader->Read( rGraphic, nImportFlags, ppAccess );
+
+ if( eReadState == JPEGREAD_ERROR )
+ {
+ bReturn = false;
+ }
+ else if( eReadState == JPEGREAD_NEED_MORE )
+ {
+ rGraphic.SetReaderContext( pContext );
+ }
+
+ return bReturn;
+}
+
+bool ExportJPEG(SvStream& rOutputStream, const Graphic& rGraphic,
+ const css::uno::Sequence<css::beans::PropertyValue>* pFilterData,
+ bool* pExportWasGrey)
+{
+ JPEGWriter aJPEGWriter( rOutputStream, pFilterData, pExportWasGrey );
+ bool bReturn = aJPEGWriter.Write( rGraphic );
+ return bReturn;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/jpeg/jpeg.h b/vcl/source/filter/jpeg/jpeg.h
new file mode 100644
index 000000000..2f951e065
--- /dev/null
+++ b/vcl/source/filter/jpeg/jpeg.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEG_H
+#define INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEG_H
+
+#include <sal/config.h>
+
+#include <com/sun/star/uno/Reference.hxx>
+#include <basegfx/vector/b2dsize.hxx>
+#include <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,
+ long nWidth, long nHeight, basegfx::B2DSize const & aPPI, bool bGreyScale,
+ long nQualityPercent, long aChromaSubsampling,
+ css::uno::Reference<css::task::XStatusIndicator> const & status);
+
+void ReadJPEG( JPEGReader* pJPEGReader, void* pInputStream, long* pLines,
+ GraphicFilterImportFlags nImportFlags,
+ BitmapScopedWriteAccess* ppAccess );
+
+void Transform(void* pInputStream, void* pOutputStream, long 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? */
+ boolean no_data_available;
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/jpeg/jpeg.hxx b/vcl/source/filter/jpeg/jpeg.hxx
new file mode 100644
index 000000000..e097ed40b
--- /dev/null
+++ b/vcl/source/filter/jpeg/jpeg.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_SOURCE_FILTER_JPEG_JPEG_HXX
+#define INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEG_HXX
+
+#include <vcl/graph.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <com/sun/star/uno/Sequence.h>
+#include <bitmapwriteaccess.hxx>
+
+VCL_DLLPUBLIC bool ImportJPEG( SvStream& rInputStream, Graphic& rGraphic, GraphicFilterImportFlags nImportFlags, BitmapScopedWriteAccess* ppAccess );
+
+bool ExportJPEG(SvStream& rOutputStream,
+ const Graphic& rGraphic,
+ const css::uno::Sequence< css::beans::PropertyValue >* pFilterData,
+ bool* pExportWasGrey);
+
+#endif // INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEG_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/jpeg/jpegc.cxx b/vcl/source/filter/jpeg/jpegc.cxx
new file mode 100644
index 000000000..c1fbb535a
--- /dev/null
+++ b/vcl/source/filter/jpeg/jpegc.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 <sal/config.h>
+#include <sal/log.hxx>
+#include <o3tl/float_int_conversion.hxx>
+#include <o3tl/safeint.hxx>
+
+#include <stdio.h>
+#include <setjmp.h>
+#include <jpeglib.h>
+
+#include <com/sun/star/task/XStatusIndicator.hpp>
+
+extern "C" {
+#include "transupp.h"
+}
+
+#include "jpeg.h"
+#include "JpegReader.hxx"
+#include "JpegWriter.hxx"
+#include <memory>
+#include <unotools/configmgr.hxx>
+#include <vcl/graphicfilter.hxx>
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning (disable: 4324) /* disable to __declspec(align()) aligned warning */
+#endif
+
+namespace {
+
+struct ErrorManagerStruct
+{
+ jpeg_error_mgr pub;
+ jmp_buf setjmp_buffer;
+};
+
+}
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+extern "C" {
+
+static void errorExit (j_common_ptr cinfo)
+{
+ char buffer[JMSG_LENGTH_MAX];
+ (*cinfo->err->format_message) (cinfo, buffer);
+ SAL_WARN("vcl.filter", "fatal failure reading JPEG: " << buffer);
+ ErrorManagerStruct * error = reinterpret_cast<ErrorManagerStruct *>(cinfo->err);
+ longjmp(error->setjmp_buffer, 1);
+}
+
+static void outputMessage (j_common_ptr cinfo)
+{
+ char buffer[JMSG_LENGTH_MAX];
+ (*cinfo->err->format_message) (cinfo, buffer);
+ SAL_WARN("vcl.filter", "failure reading JPEG: " << buffer);
+}
+
+}
+
+static int GetWarningLimit()
+{
+ return utl::ConfigManager::IsFuzzing() ? 5 : 1000;
+}
+
+extern "C" {
+
+static void emitMessage (j_common_ptr cinfo, int msg_level)
+{
+ if (msg_level < 0)
+ {
+ // ofz#3002 try to retain some degree of recoverability up to some
+ // reasonable limit (initially using ImageMagick's current limit of
+ // 1000), then bail.
+ // https://libjpeg-turbo.org/pmwiki/uploads/About/TwoIssueswiththeJPEGStandard.pdf
+ if (++cinfo->err->num_warnings > GetWarningLimit())
+ cinfo->err->error_exit(cinfo);
+ else
+ cinfo->err->output_message(cinfo);
+ }
+ else if (cinfo->err->trace_level >= msg_level)
+ cinfo->err->output_message(cinfo);
+}
+
+}
+
+namespace {
+
+class JpegDecompressOwner
+{
+public:
+ void set(jpeg_decompress_struct *cinfo)
+ {
+ m_cinfo = cinfo;
+ }
+ ~JpegDecompressOwner()
+ {
+ if (m_cinfo != nullptr)
+ {
+ jpeg_destroy_decompress(m_cinfo);
+ }
+ }
+private:
+ jpeg_decompress_struct *m_cinfo = nullptr;
+};
+
+class JpegCompressOwner
+{
+public:
+ void set(jpeg_compress_struct *cinfo)
+ {
+ m_cinfo = cinfo;
+ }
+ ~JpegCompressOwner()
+ {
+ if (m_cinfo != nullptr)
+ {
+ jpeg_destroy_compress(m_cinfo);
+ }
+ }
+private:
+ jpeg_compress_struct *m_cinfo = nullptr;
+};
+
+struct JpegStuff
+{
+ jpeg_decompress_struct cinfo;
+ ErrorManagerStruct jerr;
+ JpegDecompressOwner aOwner;
+ std::unique_ptr<BitmapScopedWriteAccess> pScopedAccess;
+ std::vector<sal_uInt8> pScanLineBuffer;
+ std::vector<sal_uInt8> pCYMKBuffer;
+};
+
+}
+
+static void ReadJPEG(JpegStuff& rContext, JPEGReader* pJPEGReader, void* pInputStream, long* pLines,
+ GraphicFilterImportFlags nImportFlags,
+ BitmapScopedWriteAccess* ppAccess)
+{
+ if (setjmp(rContext.jerr.setjmp_buffer))
+ {
+ return;
+ }
+
+ rContext.cinfo.err = jpeg_std_error(&rContext.jerr.pub);
+ rContext.jerr.pub.error_exit = errorExit;
+ rContext.jerr.pub.output_message = outputMessage;
+ rContext.jerr.pub.emit_message = emitMessage;
+
+ jpeg_create_decompress(&rContext.cinfo);
+ rContext.aOwner.set(&rContext.cinfo);
+ jpeg_svstream_src(&rContext.cinfo, pInputStream);
+ SourceManagerStruct *source = reinterpret_cast<SourceManagerStruct*>(rContext.cinfo.src);
+ jpeg_read_header(&rContext.cinfo, TRUE);
+
+ rContext.cinfo.scale_num = 1;
+ rContext.cinfo.scale_denom = 1;
+ rContext.cinfo.output_gamma = 1.0;
+ rContext.cinfo.raw_data_out = FALSE;
+ rContext.cinfo.quantize_colors = FALSE;
+
+ jpeg_calc_output_dimensions(&rContext.cinfo);
+
+ long nWidth = rContext.cinfo.output_width;
+ long nHeight = rContext.cinfo.output_height;
+
+ long nResult = 0;
+ if (utl::ConfigManager::IsFuzzing() && (o3tl::checked_multiply(nWidth, nHeight, nResult) || nResult > 4000000))
+ return;
+
+ bool bGray = (rContext.cinfo.output_components == 1);
+
+ JPEGCreateBitmapParam aCreateBitmapParam;
+
+ aCreateBitmapParam.nWidth = nWidth;
+ aCreateBitmapParam.nHeight = nHeight;
+
+ aCreateBitmapParam.density_unit = rContext.cinfo.density_unit;
+ aCreateBitmapParam.X_density = rContext.cinfo.X_density;
+ aCreateBitmapParam.Y_density = rContext.cinfo.Y_density;
+ aCreateBitmapParam.bGray = bGray;
+
+ const auto bOnlyCreateBitmap = static_cast<bool>(nImportFlags & GraphicFilterImportFlags::OnlyCreateBitmap);
+ const auto bUseExistingBitmap = static_cast<bool>(nImportFlags & GraphicFilterImportFlags::UseExistingBitmap);
+ bool bBitmapCreated = bUseExistingBitmap;
+ if (!bBitmapCreated)
+ bBitmapCreated = pJPEGReader->CreateBitmap(aCreateBitmapParam);
+
+ if (bBitmapCreated && !bOnlyCreateBitmap)
+ {
+ if (nImportFlags & GraphicFilterImportFlags::UseExistingBitmap)
+ // ppAccess must be set if this flag is used.
+ assert(ppAccess);
+ else
+ rContext.pScopedAccess.reset(new BitmapScopedWriteAccess(pJPEGReader->GetBitmap()));
+
+ BitmapScopedWriteAccess& pAccess = bUseExistingBitmap ? *ppAccess : *rContext.pScopedAccess;
+
+ if (pAccess)
+ {
+ int nPixelSize = 3;
+ J_COLOR_SPACE best_out_color_space = JCS_RGB;
+ ScanlineFormat eScanlineFormat = ScanlineFormat::N24BitTcRgb;
+ ScanlineFormat eFinalFormat = pAccess->GetScanlineFormat();
+
+ if (bGray)
+ {
+ best_out_color_space = JCS_GRAYSCALE;
+ eScanlineFormat = ScanlineFormat::N8BitPal;
+ nPixelSize = 1;
+ }
+#if defined(JCS_EXTENSIONS)
+ else if (eFinalFormat == ScanlineFormat::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);
+ }
+
+ for (*pLines = 0; *pLines < nHeight && !source->no_data_available; (*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 (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, long* pLines,
+ GraphicFilterImportFlags nImportFlags,
+ BitmapScopedWriteAccess* ppAccess )
+{
+ JpegStuff aContext;
+ ReadJPEG(aContext, pJPEGReader, pInputStream, pLines, nImportFlags, ppAccess);
+}
+
+bool WriteJPEG( JPEGWriter* pJPEGWriter, void* pOutputStream,
+ long nWidth, long nHeight, basegfx::B2DSize const & rPPI, bool bGreys,
+ long nQualityPercent, long aChromaSubsampling,
+ css::uno::Reference<css::task::XStatusIndicator> const & status )
+{
+ jpeg_compress_struct cinfo;
+ ErrorManagerStruct jerr;
+ void* pScanline;
+ long nY;
+
+ JpegCompressOwner aOwner;
+
+ if ( setjmp( jerr.setjmp_buffer ) )
+ {
+ return false;
+ }
+
+ cinfo.err = jpeg_std_error( &jerr.pub );
+ jerr.pub.error_exit = errorExit;
+ jerr.pub.output_message = outputMessage;
+
+ jpeg_create_compress( &cinfo );
+ aOwner.set(&cinfo);
+ jpeg_svstream_dest( &cinfo, pOutputStream );
+
+ cinfo.image_width = static_cast<JDIMENSION>(nWidth);
+ cinfo.image_height = static_cast<JDIMENSION>(nHeight);
+ if ( bGreys )
+ {
+ cinfo.input_components = 1;
+ cinfo.in_color_space = JCS_GRAYSCALE;
+ }
+ else
+ {
+ cinfo.input_components = 3;
+ cinfo.in_color_space = JCS_RGB;
+ }
+
+ jpeg_set_defaults( &cinfo );
+ jpeg_set_quality( &cinfo, static_cast<int>(nQualityPercent), FALSE );
+
+ if (o3tl::convertsToAtMost(rPPI.getX(), 65535) && o3tl::convertsToAtMost(rPPI.getY(), 65535))
+ {
+ cinfo.density_unit = 1;
+ cinfo.X_density = rPPI.getX();
+ cinfo.Y_density = rPPI.getY();
+ }
+ else
+ {
+ SAL_WARN("vcl.filter", "ignoring too large PPI " << rPPI);
+ }
+
+ if ( ( nWidth > 128 ) || ( nHeight > 128 ) )
+ jpeg_simple_progression( &cinfo );
+
+ if (aChromaSubsampling == 1) // YUV 4:4:4
+ {
+ cinfo.comp_info[0].h_samp_factor = 1;
+ cinfo.comp_info[0].v_samp_factor = 1;
+ }
+ else if (aChromaSubsampling == 2) // YUV 4:2:2
+ {
+ cinfo.comp_info[0].h_samp_factor = 2;
+ cinfo.comp_info[0].v_samp_factor = 1;
+ }
+ else if (aChromaSubsampling == 3) // YUV 4:2:0
+ {
+ cinfo.comp_info[0].h_samp_factor = 2;
+ cinfo.comp_info[0].v_samp_factor = 2;
+ }
+
+ jpeg_start_compress( &cinfo, TRUE );
+
+ for( nY = 0; nY < nHeight; nY++ )
+ {
+ pScanline = pJPEGWriter->GetScanline( nY );
+
+ if( pScanline )
+ {
+ jpeg_write_scanlines( &cinfo, reinterpret_cast<JSAMPARRAY>(&pScanline), 1 );
+ }
+
+ if( status.is() )
+ {
+ status->setValue( nY * 100L / nHeight );
+ }
+ }
+
+ jpeg_finish_compress(&cinfo);
+
+ return true;
+}
+
+void Transform(void* pInputStream, void* pOutputStream, long 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)
+ {
+ case 2700:
+ aTransformOption.transform = JXFORM_ROT_90;
+ break;
+ case 1800:
+ aTransformOption.transform = JXFORM_ROT_180;
+ break;
+ case 900:
+ aTransformOption.transform = JXFORM_ROT_270;
+ break;
+ default:
+ aTransformOption.transform = JXFORM_NONE;
+ }
+
+ // Decompression
+ aSourceInfo.err = jpeg_std_error(&aSourceError.pub);
+ aSourceInfo.err->error_exit = errorExit;
+ aSourceInfo.err->output_message = outputMessage;
+
+ // Compression
+ aDestinationInfo.err = jpeg_std_error(&aDestinationError.pub);
+ aDestinationInfo.err->error_exit = errorExit;
+ aDestinationInfo.err->output_message = outputMessage;
+
+ aDestinationInfo.optimize_coding = TRUE;
+
+ JpegDecompressOwner aDecompressOwner;
+ JpegCompressOwner aCompressOwner;
+
+ if (setjmp(aSourceError.setjmp_buffer))
+ {
+ jpeg_destroy_decompress(&aSourceInfo);
+ jpeg_destroy_compress(&aDestinationInfo);
+ return;
+ }
+ if (setjmp(aDestinationError.setjmp_buffer))
+ {
+ jpeg_destroy_decompress(&aSourceInfo);
+ jpeg_destroy_compress(&aDestinationInfo);
+ return;
+ }
+
+ jpeg_create_decompress(&aSourceInfo);
+ aDecompressOwner.set(&aSourceInfo);
+ jpeg_create_compress(&aDestinationInfo);
+ aCompressOwner.set(&aDestinationInfo);
+
+ jpeg_svstream_src (&aSourceInfo, pInputStream);
+
+ jcopy_markers_setup(&aSourceInfo, aCopyOption);
+ jpeg_read_header(&aSourceInfo, TRUE);
+ jtransform_request_workspace(&aSourceInfo, &aTransformOption);
+
+ aSourceCoefArrays = jpeg_read_coefficients(&aSourceInfo);
+ jpeg_copy_critical_parameters(&aSourceInfo, &aDestinationInfo);
+
+ aDestinationCoefArrays = jtransform_adjust_parameters(&aSourceInfo, &aDestinationInfo, aSourceCoefArrays, &aTransformOption);
+ jpeg_svstream_dest (&aDestinationInfo, pOutputStream);
+
+ // Compute optimal Huffman coding tables instead of precomputed tables
+ aDestinationInfo.optimize_coding = TRUE;
+ jpeg_write_coefficients(&aDestinationInfo, aDestinationCoefArrays);
+ jcopy_markers_execute(&aSourceInfo, &aDestinationInfo, aCopyOption);
+ jtransform_execute_transformation(&aSourceInfo, &aDestinationInfo, aSourceCoefArrays, &aTransformOption);
+
+ jpeg_finish_compress(&aDestinationInfo);
+
+ jpeg_finish_decompress(&aSourceInfo);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/jpeg/jpegcomp.h b/vcl/source/filter/jpeg/jpegcomp.h
new file mode 100644
index 000000000..9b3e36775
--- /dev/null
+++ b/vcl/source/filter/jpeg/jpegcomp.h
@@ -0,0 +1,18 @@
+/*
+ * jpegcomp.h
+ *
+ * Copyright (C) 2010, D. R. Commander
+ * For conditions of distribution and use, see the accompanying README file.
+ *
+ * JPEG compatibility macros
+ * These declarations are considered internal to the JPEG library; most
+ * applications using the library shouldn't need to include this file.
+ */
+
+#if JPEG_LIB_VERSION >= 70
+#define min_DCT_h_scaled_size_ min_DCT_h_scaled_size
+#define min_DCT_v_scaled_size_ min_DCT_v_scaled_size
+#else
+#define min_DCT_h_scaled_size_ min_DCT_scaled_size
+#define min_DCT_v_scaled_size_ min_DCT_scaled_size
+#endif
diff --git a/vcl/source/filter/jpeg/transupp.c b/vcl/source/filter/jpeg/transupp.c
new file mode 100644
index 000000000..a81445812
--- /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 coping with that case
+ * isn't worth extra code space. But we check it to avoid crashing.)
+ */
+ if (((dstinfo->jpeg_color_space == JCS_YCbCr &&
+ dstinfo->num_components == 3) ||
+ (dstinfo->jpeg_color_space == JCS_GRAYSCALE &&
+ dstinfo->num_components == 1)) &&
+ srcinfo->comp_info[0].h_samp_factor == srcinfo->max_h_samp_factor &&
+ srcinfo->comp_info[0].v_samp_factor == srcinfo->max_v_samp_factor) {
+ /* We use jpeg_set_colorspace to make sure subsidiary settings get fixed
+ * properly. Among other things, it sets the target h_samp_factor &
+ * v_samp_factor to 1, which typically won't match the source.
+ * We have to preserve the source's quantization table number, however.
+ */
+ int sv_quant_tbl_no = dstinfo->comp_info[0].quant_tbl_no;
+ jpeg_set_colorspace(dstinfo, JCS_GRAYSCALE);
+ dstinfo->comp_info[0].quant_tbl_no = sv_quant_tbl_no;
+ } else {
+ /* Sorry, can't do it */
+ ERREXIT(dstinfo, JERR_CONVERSION_NOTIMPL);
+ }
+ } else if (info->num_components == 1) {
+ /* For a single-component source, we force the destination sampling factors
+ * to 1x1, with or without force_grayscale. This is useful because some
+ * decoders choke on grayscale images with other sampling factors.
+ */
+ dstinfo->comp_info[0].h_samp_factor = 1;
+ dstinfo->comp_info[0].v_samp_factor = 1;
+ }
+
+ /* Correct the destination's image dimensions as necessary
+ * for rotate/flip, resize, and crop operations.
+ */
+#if JPEG_LIB_VERSION >= 70
+ dstinfo->jpeg_width = info->output_width;
+ dstinfo->jpeg_height = info->output_height;
+#endif
+
+ /* Transpose destination image parameters */
+ switch (info->transform) {
+ case JXFORM_TRANSPOSE:
+ case JXFORM_TRANSVERSE:
+ case JXFORM_ROT_90:
+ case JXFORM_ROT_270:
+#if JPEG_LIB_VERSION < 70
+ dstinfo->image_width = info->output_height;
+ dstinfo->image_height = info->output_width;
+#endif
+ transpose_critical_parameters(dstinfo);
+ break;
+ default:
+#if JPEG_LIB_VERSION < 70
+ dstinfo->image_width = info->output_width;
+ dstinfo->image_height = info->output_height;
+#endif
+ break;
+ }
+
+ /* Adjust Exif properties */
+ if (srcinfo->marker_list != NULL &&
+ srcinfo->marker_list->marker == JPEG_APP0+1 &&
+ srcinfo->marker_list->data_length >= 6 &&
+ GETJOCTET(srcinfo->marker_list->data[0]) == 0x45 &&
+ GETJOCTET(srcinfo->marker_list->data[1]) == 0x78 &&
+ GETJOCTET(srcinfo->marker_list->data[2]) == 0x69 &&
+ GETJOCTET(srcinfo->marker_list->data[3]) == 0x66 &&
+ GETJOCTET(srcinfo->marker_list->data[4]) == 0 &&
+ GETJOCTET(srcinfo->marker_list->data[5]) == 0) {
+ /* Suppress output of JFIF marker */
+ dstinfo->write_JFIF_header = FALSE;
+#if JPEG_LIB_VERSION >= 70
+ /* Adjust Exif image parameters */
+ if (dstinfo->jpeg_width != srcinfo->image_width ||
+ dstinfo->jpeg_height != srcinfo->image_height)
+ /* Align data segment to start of TIFF structure for parsing */
+ adjust_exif_parameters(srcinfo->marker_list->data + 6,
+ srcinfo->marker_list->data_length - 6,
+ dstinfo->jpeg_width, dstinfo->jpeg_height);
+#endif
+ }
+
+ /* Return the appropriate output data set */
+ if (info->workspace_coef_arrays != NULL)
+ return info->workspace_coef_arrays;
+ return src_coef_arrays;
+}
+
+
+/* Execute the actual transformation, if any.
+ *
+ * This must be called *after* jpeg_write_coefficients, because it depends
+ * on jpeg_write_coefficients to have computed subsidiary values such as
+ * the per-component width and height fields in the destination object.
+ *
+ * Note that some transformations will modify the source data arrays!
+ */
+
+GLOBAL(void)
+jtransform_execute_transform (j_decompress_ptr srcinfo,
+ j_compress_ptr dstinfo,
+ jvirt_barray_ptr *src_coef_arrays,
+ jpeg_transform_info *info)
+{
+ jvirt_barray_ptr *dst_coef_arrays = info->workspace_coef_arrays;
+
+ /* Note: conditions tested here should match those in switch statement
+ * in jtransform_request_workspace()
+ */
+ switch (info->transform) {
+ case JXFORM_NONE:
+ if (info->x_crop_offset != 0 || info->y_crop_offset != 0)
+ do_crop(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset,
+ src_coef_arrays, dst_coef_arrays);
+ break;
+ case JXFORM_FLIP_H:
+ if (info->y_crop_offset != 0 || info->slow_hflip)
+ do_flip_h(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset,
+ src_coef_arrays, dst_coef_arrays);
+ else
+ do_flip_h_no_crop(srcinfo, dstinfo, info->x_crop_offset,
+ src_coef_arrays);
+ break;
+ case JXFORM_FLIP_V:
+ do_flip_v(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset,
+ src_coef_arrays, dst_coef_arrays);
+ break;
+ case JXFORM_TRANSPOSE:
+ do_transpose(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset,
+ src_coef_arrays, dst_coef_arrays);
+ break;
+ case JXFORM_TRANSVERSE:
+ do_transverse(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset,
+ src_coef_arrays, dst_coef_arrays);
+ break;
+ case JXFORM_ROT_90:
+ do_rot_90(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset,
+ src_coef_arrays, dst_coef_arrays);
+ break;
+ case JXFORM_ROT_180:
+ do_rot_180(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset,
+ src_coef_arrays, dst_coef_arrays);
+ break;
+ case JXFORM_ROT_270:
+ do_rot_270(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset,
+ src_coef_arrays, dst_coef_arrays);
+ break;
+ }
+}
+
+/* jtransform_perfect_transform
+ *
+ * Determine whether lossless transformation is perfectly
+ * possible for a specified image and transformation.
+ *
+ * Inputs:
+ * image_width, image_height: source image dimensions.
+ * MCU_width, MCU_height: pixel dimensions of MCU.
+ * transform: transformation identifier.
+ * Parameter sources from initialized jpeg_struct
+ * (after reading source header):
+ * image_width = cinfo.image_width
+ * image_height = cinfo.image_height
+ * MCU_width = cinfo.max_h_samp_factor * cinfo.block_size
+ * MCU_height = cinfo.max_v_samp_factor * cinfo.block_size
+ * Result:
+ * TRUE = perfect transformation possible
+ * FALSE = perfect transformation not possible
+ * (may use custom action then)
+ */
+
+GLOBAL(boolean)
+jtransform_perfect_transform(JDIMENSION image_width, JDIMENSION image_height,
+ int MCU_width, int MCU_height,
+ JXFORM_CODE transform)
+{
+ boolean result = TRUE; /* initialize TRUE */
+
+ switch (transform) {
+ case JXFORM_FLIP_H:
+ case JXFORM_ROT_270:
+ if (image_width % (JDIMENSION) MCU_width)
+ result = FALSE;
+ break;
+ case JXFORM_FLIP_V:
+ case JXFORM_ROT_90:
+ if (image_height % (JDIMENSION) MCU_height)
+ result = FALSE;
+ break;
+ case JXFORM_TRANSVERSE:
+ case JXFORM_ROT_180:
+ if (image_width % (JDIMENSION) MCU_width)
+ result = FALSE;
+ if (image_height % (JDIMENSION) MCU_height)
+ result = FALSE;
+ break;
+ default:
+ break;
+ }
+
+ return result;
+}
+
+#endif /* TRANSFORMS_SUPPORTED */
+
+
+/* Setup decompression object to save desired markers in memory.
+ * This must be called before jpeg_read_header() to have the desired effect.
+ */
+
+GLOBAL(void)
+jcopy_markers_setup (j_decompress_ptr srcinfo, JCOPY_OPTION option)
+{
+#ifdef SAVE_MARKERS_SUPPORTED
+ int m;
+
+ /* Save comments except under NONE option */
+ if (option != JCOPYOPT_NONE) {
+ jpeg_save_markers(srcinfo, JPEG_COM, 0xFFFF);
+ }
+ /* Save all types of APPn markers iff ALL option */
+ if (option == JCOPYOPT_ALL) {
+ for (m = 0; m < 16; m++)
+ jpeg_save_markers(srcinfo, JPEG_APP0 + m, 0xFFFF);
+ }
+#else
+ (void) srcinfo; (void) option;
+#endif /* SAVE_MARKERS_SUPPORTED */
+}
+
+/* Copy markers saved in the given source object to the destination object.
+ * This should be called just after jpeg_start_compress() or
+ * jpeg_write_coefficients().
+ * Note that those routines will have written the SOI, and also the
+ * JFIF APP0 or Adobe APP14 markers if selected.
+ */
+
+GLOBAL(void)
+jcopy_markers_execute (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JCOPY_OPTION option)
+{
+ jpeg_saved_marker_ptr marker;
+
+ /* In the current implementation, we don't actually need to examine the
+ * option flag here; we just copy everything that got saved.
+ * But to avoid confusion, we do not output JFIF and Adobe APP14 markers
+ * if the encoder library already wrote one.
+ */
+ (void)option;
+
+ for (marker = srcinfo->marker_list; marker != NULL; marker = marker->next) {
+ if (dstinfo->write_JFIF_header &&
+ marker->marker == JPEG_APP0 &&
+ marker->data_length >= 5 &&
+ GETJOCTET(marker->data[0]) == 0x4A &&
+ GETJOCTET(marker->data[1]) == 0x46 &&
+ GETJOCTET(marker->data[2]) == 0x49 &&
+ GETJOCTET(marker->data[3]) == 0x46 &&
+ GETJOCTET(marker->data[4]) == 0)
+ continue; /* reject duplicate JFIF */
+ if (dstinfo->write_Adobe_marker &&
+ marker->marker == JPEG_APP0+14 &&
+ marker->data_length >= 5 &&
+ GETJOCTET(marker->data[0]) == 0x41 &&
+ GETJOCTET(marker->data[1]) == 0x64 &&
+ GETJOCTET(marker->data[2]) == 0x6F &&
+ GETJOCTET(marker->data[3]) == 0x62 &&
+ GETJOCTET(marker->data[4]) == 0x65)
+ continue; /* reject duplicate Adobe */
+#ifdef NEED_FAR_POINTERS
+ /* We could use jpeg_write_marker if the data weren't FAR... */
+ {
+ unsigned int i;
+ jpeg_write_m_header(dstinfo, marker->marker, marker->data_length);
+ for (i = 0; i < marker->data_length; i++)
+ jpeg_write_m_byte(dstinfo, marker->data[i]);
+ }
+#else
+ jpeg_write_marker(dstinfo, marker->marker,
+ marker->data, marker->data_length);
+#endif
+ }
+}
diff --git a/vcl/source/filter/jpeg/transupp.h b/vcl/source/filter/jpeg/transupp.h
new file mode 100644
index 000000000..a5d403335
--- /dev/null
+++ b/vcl/source/filter/jpeg/transupp.h
@@ -0,0 +1,212 @@
+/*
+ * transupp.h
+ *
+ * Copyright (C) 1997-2011, Thomas G. Lane, Guido Vollbeding.
+ * This file is part of the Independent JPEG Group's software.
+ * For conditions of distribution and use, see the accompanying README file.
+ *
+ * This file contains declarations for image transformation routines and
+ * other utility code used by the jpegtran sample application. These are
+ * NOT part of the core JPEG library. But we keep these routines separate
+ * from jpegtran.c to ease the task of maintaining jpegtran-like programs
+ * that have other user interfaces.
+ *
+ * NOTE: all the routines declared here have very specific requirements
+ * about when they are to be executed during the reading and writing of the
+ * source and destination files. See the comments in transupp.c, or see
+ * jpegtran.c for an example of correct usage.
+ */
+
+/* If you happen not to want the image transform support, disable it here */
+#ifndef TRANSFORMS_SUPPORTED
+#define TRANSFORMS_SUPPORTED 1 /* 0 disables transform code */
+#endif
+
+/*
+ * Although rotating and flipping data expressed as DCT coefficients is not
+ * hard, there is an asymmetry in the JPEG format specification for images
+ * whose dimensions aren't multiples of the iMCU size. The right and bottom
+ * image edges are padded out to the next iMCU boundary with junk data; but
+ * no padding is possible at the top and left edges. If we were to flip
+ * the whole image including the pad data, then pad garbage would become
+ * visible at the top and/or left, and real pixels would disappear into the
+ * pad margins --- perhaps permanently, since encoders & decoders may not
+ * bother to preserve DCT blocks that appear to be completely outside the
+ * nominal image area. So, we have to exclude any partial iMCUs from the
+ * basic transformation.
+ *
+ * Transpose is the only transformation that can handle partial iMCUs at the
+ * right and bottom edges completely cleanly. flip_h can flip partial iMCUs
+ * at the bottom, but leaves any partial iMCUs at the right edge untouched.
+ * Similarly flip_v leaves any partial iMCUs at the bottom edge untouched.
+ * The other transforms are defined as combinations of these basic transforms
+ * and process edge blocks in a way that preserves the equivalence.
+ *
+ * The "trim" option causes untransformable partial iMCUs to be dropped;
+ * this is not strictly lossless, but it usually gives the best-looking
+ * result for odd-size images. Note that when this option is active,
+ * the expected mathematical equivalences between the transforms may not hold.
+ * (For example, -rot 270 -trim trims only the bottom edge, but -rot 90 -trim
+ * followed by -rot 180 -trim trims both edges.)
+ *
+ * We also offer a lossless-crop option, which discards data outside a given
+ * image region but losslessly preserves what is inside. Like the rotate and
+ * flip transforms, lossless crop is restricted by the JPEG format: the upper
+ * left corner of the selected region must fall on an iMCU boundary. If this
+ * does not hold for the given crop parameters, we silently move the upper left
+ * corner up and/or left to make it so, simultaneously increasing the region
+ * dimensions to keep the lower right crop corner unchanged. (Thus, the
+ * output image covers at least the requested region, but may cover more.)
+ * The adjustment of the region dimensions may be optionally disabled.
+ *
+ * We also provide a lossless-resize option, which is kind of a lossless-crop
+ * operation in the DCT coefficient block domain - it discards higher-order
+ * coefficients and losslessly preserves lower-order coefficients of a
+ * sub-block.
+ *
+ * Rotate/flip transform, resize, and crop can be requested together in a
+ * single invocation. The crop is applied last --- that is, the crop region
+ * is specified in terms of the destination image after transform/resize.
+ *
+ * We also offer a "force to grayscale" option, which simply discards the
+ * chrominance channels of a YCbCr image. This is lossless in the sense that
+ * the luminance channel is preserved exactly. It's not the same kind of
+ * thing as the rotate/flip transformations, but it's convenient to handle it
+ * as part of this package, mainly because the transformation routines have to
+ * be aware of the option to know how many components to work on.
+ */
+
+/* Short forms of external names for systems with brain-damaged linkers. */
+
+#ifdef NEED_SHORT_EXTERNAL_NAMES
+#define jtransform_parse_crop_spec jTrParCrop
+#define jtransform_request_workspace jTrRequest
+#define jtransform_adjust_parameters jTrAdjust
+#define jtransform_execute_transform jTrExec
+#define jtransform_perfect_transform jTrPerfect
+#define jcopy_markers_setup jCMrkSetup
+#define jcopy_markers_execute jCMrkExec
+#endif /* NEED_SHORT_EXTERNAL_NAMES */
+
+/*
+ * Codes for supported types of image transformations.
+ */
+
+typedef enum {
+ JXFORM_NONE, /* no transformation */
+ JXFORM_FLIP_H, /* horizontal flip */
+ JXFORM_FLIP_V, /* vertical flip */
+ JXFORM_TRANSPOSE, /* transpose across UL-to-LR axis */
+ JXFORM_TRANSVERSE, /* transpose across UR-to-LL axis */
+ JXFORM_ROT_90, /* 90-degree clockwise rotation */
+ JXFORM_ROT_180, /* 180-degree rotation */
+ JXFORM_ROT_270 /* 270-degree clockwise (or 90 ccw) */
+} JXFORM_CODE;
+
+/*
+ * Codes for crop parameters, which can individually be unspecified,
+ * positive or negative for xoffset or yoffset,
+ * positive or forced for width or height.
+ */
+
+typedef enum {
+ JCROP_UNSET,
+ JCROP_POS,
+ JCROP_NEG,
+ JCROP_FORCE
+} JCROP_CODE;
+
+/*
+ * Transform parameters struct.
+ * NB: application must not change any elements of this struct after
+ * calling jtransform_request_workspace.
+ */
+
+typedef struct {
+ /* Options: set by caller */
+ JXFORM_CODE transform; /* image transform operator */
+ boolean perfect; /* if TRUE, fail if partial MCUs are requested */
+ boolean trim; /* if TRUE, trim partial MCUs as needed */
+ boolean force_grayscale; /* if TRUE, convert color image to grayscale */
+ boolean crop; /* if TRUE, crop source image */
+ boolean slow_hflip; /* For best performance, the JXFORM_FLIP_H transform
+ normally modifies the source coefficients in place.
+ Setting this to TRUE will instead use a slower,
+ double-buffered algorithm, which leaves the source
+ coefficients intact (necessary if other transformed
+ images must be generated from the same set of
+ coefficients. */
+
+ /* Crop parameters: application need not set these unless crop is TRUE.
+ * These can be filled in by jtransform_parse_crop_spec().
+ */
+ JDIMENSION crop_width; /* Width of selected region */
+ JCROP_CODE crop_width_set; /* (forced disables adjustment) */
+ JDIMENSION crop_height; /* Height of selected region */
+ JCROP_CODE crop_height_set; /* (forced disables adjustment) */
+ JDIMENSION crop_xoffset; /* X offset of selected region */
+ JCROP_CODE crop_xoffset_set; /* (negative measures from right edge) */
+ JDIMENSION crop_yoffset; /* Y offset of selected region */
+ JCROP_CODE crop_yoffset_set; /* (negative measures from bottom edge) */
+
+ /* Internal workspace: caller should not touch these */
+ int num_components; /* # of components in workspace */
+ jvirt_barray_ptr * workspace_coef_arrays; /* workspace for transformations */
+ JDIMENSION output_width; /* cropped destination dimensions */
+ JDIMENSION output_height;
+ JDIMENSION x_crop_offset; /* destination crop offsets measured in iMCUs */
+ JDIMENSION y_crop_offset;
+ int iMCU_sample_width; /* destination iMCU size */
+ int iMCU_sample_height;
+} jpeg_transform_info;
+
+#if TRANSFORMS_SUPPORTED
+
+/* Request any required workspace */
+EXTERN(boolean) jtransform_request_workspace
+ (j_decompress_ptr srcinfo, jpeg_transform_info *info);
+/* Adjust output image parameters */
+EXTERN(jvirt_barray_ptr *) jtransform_adjust_parameters
+ (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ jvirt_barray_ptr *src_coef_arrays,
+ jpeg_transform_info *info);
+/* Execute the actual transformation, if any */
+EXTERN(void) jtransform_execute_transform
+ (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ jvirt_barray_ptr *src_coef_arrays,
+ jpeg_transform_info *info);
+/* Determine whether lossless transformation is perfectly
+ * possible for a specified image and transformation.
+ */
+EXTERN(boolean) jtransform_perfect_transform
+ (JDIMENSION image_width, JDIMENSION image_height,
+ int MCU_width, int MCU_height,
+ JXFORM_CODE transform);
+
+/* jtransform_execute_transform used to be called
+ * jtransform_execute_transformation, but some compilers complain about
+ * routine names that long. This macro is here to avoid breaking any
+ * old source code that uses the original name...
+ */
+#define jtransform_execute_transformation jtransform_execute_transform
+
+#endif /* TRANSFORMS_SUPPORTED */
+
+/*
+ * Support for copying optional markers from source to destination file.
+ */
+
+typedef enum {
+ JCOPYOPT_NONE, /* copy no optional markers */
+ JCOPYOPT_COMMENTS, /* copy only comment (COM) markers */
+ JCOPYOPT_ALL /* copy all optional markers */
+} JCOPY_OPTION;
+
+
+/* Setup decompression object to save desired markers in memory */
+EXTERN(void) jcopy_markers_setup
+ (j_decompress_ptr srcinfo, JCOPY_OPTION option);
+/* Copy markers saved in the given source object to the destination object */
+EXTERN(void) jcopy_markers_execute
+ (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JCOPY_OPTION option);
diff --git a/vcl/source/filter/png/PngImageReader.cxx b/vcl/source/filter/png/PngImageReader.cxx
new file mode 100644
index 000000000..958cae34e
--- /dev/null
+++ b/vcl/source/filter/png/PngImageReader.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/.
+ *
+ */
+
+#include <vcl/filter/PngImageReader.hxx>
+#include <png.h>
+#include <tools/stream.hxx>
+#include <bitmapwriteaccess.hxx>
+#include <vcl/bitmap.hxx>
+#include <vcl/alpha.hxx>
+#include <vcl/BitmapTools.hxx>
+#include <svdata.hxx>
+#include <salinst.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)
+ png_error(pPng, "Error reading");
+}
+
+bool reader(SvStream& rStream, BitmapEx& rBitmapEx, bool bUseBitmap32)
+{
+ enum
+ {
+ PNG_SIGNATURE_SIZE = 8
+ };
+
+ // Check signature bytes
+ sal_uInt8 aHeader[PNG_SIGNATURE_SIZE];
+ rStream.ReadBytes(aHeader, PNG_SIGNATURE_SIZE);
+
+ if (png_sig_cmp(aHeader, 0, PNG_SIGNATURE_SIZE))
+ return false;
+
+ png_structp pPng = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+ if (!pPng)
+ return false;
+
+ png_infop pInfo = png_create_info_struct(pPng);
+ if (!pInfo)
+ {
+ png_destroy_read_struct(&pPng, nullptr, nullptr);
+ return false;
+ }
+
+ if (setjmp(png_jmpbuf(pPng)))
+ {
+ png_destroy_read_struct(&pPng, &pInfo, nullptr);
+ return false;
+ }
+
+ png_set_option(pPng, PNG_MAXIMUM_INFLATE_WINDOW, PNG_OPTION_ON);
+
+ png_set_read_fn(pPng, &rStream, lclReadStream);
+
+ png_set_crc_action(pPng, PNG_CRC_ERROR_QUIT, PNG_CRC_WARN_DISCARD);
+
+ 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)
+ {
+ png_destroy_read_struct(&pPng, &pInfo, nullptr);
+ 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);
+
+ if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA)
+ {
+ 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)
+ {
+ png_destroy_read_struct(&pPng, &pInfo, nullptr);
+ return false;
+ }
+
+ if (bitDepth != 8
+ || !(colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_RGB_ALPHA))
+ {
+ png_destroy_read_struct(&pPng, &pInfo, nullptr);
+ return false;
+ }
+
+ {
+ if (colorType == PNG_COLOR_TYPE_RGB)
+ {
+ size_t aRowSizeBytes = png_get_rowbytes(pPng, pInfo);
+
+ Bitmap aBitmap(Size(width, height), 24);
+ {
+ BitmapScopedWriteAccess pWriteAccess(aBitmap);
+ ScanlineFormat eFormat = pWriteAccess->GetScanlineFormat();
+ if (eFormat == ScanlineFormat::N24BitTcBgr)
+ png_set_bgr(pPng);
+
+ 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);
+ png_bytep pRow = aRows[y].data();
+ png_read_row(pPng, pRow, nullptr);
+ size_t iColor = 0;
+ for (size_t i = 0; i < aRowSizeBytes; i += 3)
+ {
+ pScanline[iColor++] = pRow[i + 0];
+ pScanline[iColor++] = pRow[i + 1];
+ pScanline[iColor++] = pRow[i + 2];
+ }
+ }
+ }
+ }
+ rBitmapEx = BitmapEx(aBitmap);
+ }
+ else if (colorType == PNG_COLOR_TYPE_RGB_ALPHA)
+ {
+ size_t aRowSizeBytes = png_get_rowbytes(pPng, pInfo);
+
+ if (bUseBitmap32)
+ {
+ Bitmap aBitmap(Size(width, height), 32);
+ {
+ BitmapScopedWriteAccess pWriteAccess(aBitmap);
+ ScanlineFormat eFormat = pWriteAccess->GetScanlineFormat();
+ if (eFormat == ScanlineFormat::N32BitTcAbgr
+ || eFormat == ScanlineFormat::N32BitTcBgra)
+ {
+ png_set_bgr(pPng);
+ }
+
+ 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);
+ png_bytep pRow = aRows[y].data();
+ png_read_row(pPng, pRow, nullptr);
+ size_t iColor = 0;
+ for (size_t i = 0; i < aRowSizeBytes; i += 4)
+ {
+ sal_Int8 alpha = pRow[i + 3];
+ pScanline[iColor++] = vcl::bitmap::premultiply(pRow[i + 0], alpha);
+ pScanline[iColor++] = vcl::bitmap::premultiply(pRow[i + 1], alpha);
+ pScanline[iColor++] = vcl::bitmap::premultiply(pRow[i + 2], alpha);
+ pScanline[iColor++] = alpha;
+ }
+ }
+ }
+ }
+ rBitmapEx = BitmapEx(aBitmap);
+ }
+ else
+ {
+ Bitmap aBitmap(Size(width, height), 24);
+ AlphaMask aBitmapAlpha(Size(width, height), nullptr);
+ {
+ BitmapScopedWriteAccess pWriteAccess(aBitmap);
+ ScanlineFormat eFormat = pWriteAccess->GetScanlineFormat();
+ if (eFormat == ScanlineFormat::N24BitTcBgr)
+ png_set_bgr(pPng);
+
+ AlphaScopedWriteAccess pWriteAccessAlpha(aBitmapAlpha);
+
+ std::vector<std::vector<png_byte>> aRows(height);
+ for (auto& rRow : aRows)
+ rRow.resize(aRowSizeBytes, 0);
+
+ for (int pass = 0; pass < nNumberOfPasses; pass++)
+ {
+ for (png_uint_32 y = 0; y < height; y++)
+ {
+ Scanline pScanline = pWriteAccess->GetScanline(y);
+ Scanline pScanAlpha = pWriteAccessAlpha->GetScanline(y);
+ png_bytep pRow = aRows[y].data();
+ png_read_row(pPng, pRow, nullptr);
+ size_t iAlpha = 0;
+ size_t iColor = 0;
+ for (size_t i = 0; i < aRowSizeBytes; i += 4)
+ {
+ pScanline[iColor++] = pRow[i + 0];
+ pScanline[iColor++] = pRow[i + 1];
+ pScanline[iColor++] = pRow[i + 2];
+ pScanAlpha[iAlpha++] = 0xFF - pRow[i + 3];
+ }
+ }
+ }
+ }
+ rBitmapEx = BitmapEx(aBitmap, aBitmapAlpha);
+ }
+ }
+ }
+
+ png_read_end(pPng, pInfo);
+
+ png_destroy_read_struct(&pPng, &pInfo, nullptr);
+
+ return true;
+}
+
+} // anonymous namespace
+
+namespace vcl
+{
+PngImageReader::PngImageReader(SvStream& rStream)
+ : mrStream(rStream)
+{
+}
+
+bool PngImageReader::read(BitmapEx& rBitmapEx)
+{
+ auto pBackendCapabilities = ImplGetSVData()->mpDefInst->GetBackendCapabilities();
+ bool bSupportsBitmap32 = pBackendCapabilities->mbSupportsBitmap32;
+
+ return reader(mrStream, rBitmapEx, bSupportsBitmap32);
+}
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/png/pngread.cxx b/vcl/source/filter/png/pngread.cxx
new file mode 100644
index 000000000..bd530689f
--- /dev/null
+++ b/vcl/source/filter/png/pngread.cxx
@@ -0,0 +1,1715 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/safeint.hxx>
+#include <osl/diagnose.h>
+
+#include <cassert>
+#include <memory>
+#include <unotools/configmgr.hxx>
+#include <vcl/pngread.hxx>
+
+#include <cmath>
+#include <rtl/crc.h>
+#include <tools/zcodec.hxx>
+#include <tools/stream.hxx>
+#include <vcl/alpha.hxx>
+#include <osl/endian.h>
+#include <bitmapwriteaccess.hxx>
+
+namespace vcl
+{
+
+#define PNGCHUNK_IHDR 0x49484452
+#define PNGCHUNK_PLTE 0x504c5445
+#define PNGCHUNK_IDAT 0x49444154
+#define PNGCHUNK_IEND 0x49454e44
+#define PNGCHUNK_bKGD 0x624b4744
+#define PNGCHUNK_gAMA 0x67414d41
+#define PNGCHUNK_pHYs 0x70485973
+#define PNGCHUNK_tRNS 0x74524e53
+
+#define VIEWING_GAMMA 2.35
+#define DISPLAY_GAMMA 1.0
+
+
+static const sal_uInt8 mpDefaultColorTable[ 256 ] =
+{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+ 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
+ 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
+ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
+ 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
+ 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
+ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
+ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
+ 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
+ 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
+ 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
+ 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
+ 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
+ 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
+};
+
+class PNGReaderImpl
+{
+private:
+ SvStream& mrPNGStream;
+ SvStreamEndian mnOrigStreamMode;
+
+ std::vector<vcl::PNGReader::ChunkData> maChunkSeq;
+ std::vector<vcl::PNGReader::ChunkData>::iterator maChunkIter;
+ std::vector<sal_uInt8>::iterator maDataIter;
+
+ std::unique_ptr<Bitmap> mpBmp;
+ BitmapScopedWriteAccess mxAcc;
+ std::unique_ptr<Bitmap> mpMaskBmp;
+ BitmapScopedWriteAccess mxMaskAcc;
+ std::unique_ptr<AlphaMask> mpAlphaMask;
+ AlphaScopedWriteAccess mxAlphaAcc;
+ BitmapWriteAccess* mpMaskAcc;
+
+ ZCodec mpZCodec;
+ std::unique_ptr<sal_uInt8[]>
+ mpInflateInBuf; // as big as the size of a scanline + alphachannel + 1
+ std::unique_ptr<sal_uInt8[]>
+ mpScanPrior; // pointer to the latest scanline
+ std::unique_ptr<sal_uInt8[]>
+ mpTransTab; // for transparency in images with palette colortype
+ sal_uInt8* mpScanCurrent; // pointer into the current scanline
+ sal_uInt8* mpColorTable;
+ std::size_t mnStreamSize; // estimate of PNG file size
+ sal_uInt32 mnChunkType; // Type of current PNG chunk
+ sal_Int32 mnChunkLen; // Length of current PNG chunk
+ Size maOrigSize; // pixel size of the full image
+ Size maTargetSize; // pixel size of the result image
+ Size maPhysSize; // preferred size in MapUnit::Map100thMM units
+ sal_uInt32 mnBPP; // number of bytes per pixel
+ sal_uInt32 mnScansize; // max size of scanline
+ sal_uInt32 mnYpos; // latest y position in full image
+ int mnPass; // if interlaced the latest pass ( 1..7 ) else 7
+ sal_uInt32 mnXStart; // the starting X for the current pass
+ sal_uInt32 mnXAdd; // the increment for input images X coords for the current pass
+ sal_uInt32 mnYAdd; // the increment for input images Y coords for the current pass
+ int mnPreviewShift; // shift to convert orig image coords into preview image coords
+ int mnPreviewMask; // == ((1 << mnPreviewShift) - 1)
+ sal_uInt16 mnTargetDepth; // pixel depth of target bitmap
+ sal_uInt8 mnTransRed;
+ sal_uInt8 mnTransGreen;
+ sal_uInt8 mnTransBlue;
+ sal_uInt8 mnPngDepth; // pixel depth of PNG data
+ sal_uInt8 mnColorType;
+ sal_uInt8 mnCompressionType;
+ sal_uInt8 mnFilterType;
+ sal_uInt8 mnInterlaceType;
+ const BitmapColor mcTranspColor; // transparency mask's transparency "color"
+ const BitmapColor mcOpaqueColor; // transparency mask's opaque "color"
+ bool mbTransparent : 1; // graphic includes a tRNS Chunk or an alpha Channel
+ bool mbAlphaChannel : 1; // is true for ColorType 4 and 6
+ bool mbRGBTriple : 1;
+ bool mbPalette : 1; // false if we need a Palette
+ bool mbGrayScale : 1;
+ bool mbzCodecInUse : 1;
+ bool mbStatus : 1;
+ bool mbIDATStarted : 1; // true if IDAT seen
+ bool mbIDATComplete : 1; // true if finished with enough IDAT chunks
+ bool mbpHYs : 1; // true if physical size of pixel available
+ bool mbIgnoreGammaChunk : 1;
+ bool mbIgnoreCRC : 1; // skip checking CRCs while fuzzing
+
+#if OSL_DEBUG_LEVEL > 0
+ // do some checks in debug mode
+ sal_Int32 mnAllocSizeScanline;
+ sal_Int32 mnAllocSizeScanlineAlpha;
+#endif
+ // the temporary Scanline (and alpha) for direct scanline copy to Bitmap
+ std::unique_ptr<sal_uInt8[]>
+ mpScanline;
+ std::unique_ptr<sal_uInt8[]>
+ mpScanlineAlpha;
+
+ bool ReadNextChunk();
+
+ void ImplSetPixel( sal_uInt32 y, sal_uInt32 x, const BitmapColor & );
+ void ImplSetPixel( sal_uInt32 y, sal_uInt32 x, sal_uInt8 nPalIndex );
+ void ImplSetTranspPixel( sal_uInt32 y, sal_uInt32 x, const BitmapColor &, bool bTrans );
+ void ImplSetAlphaPixel( sal_uInt32 y, sal_uInt32 x, sal_uInt8 nPalIndex, sal_uInt8 nAlpha );
+ void ImplSetAlphaPixel( sal_uInt32 y, sal_uInt32 x, const BitmapColor&, sal_uInt8 nAlpha );
+ void ImplReadIDAT();
+ bool ImplPreparePass();
+ void ImplApplyFilter();
+ void ImplDrawScanline( sal_uInt32 nXStart, sal_uInt32 nXAdd );
+ bool ImplReadTransparent();
+ void ImplGetGamma();
+ void ImplGetBackground();
+ sal_uInt8 ImplScaleColor();
+ bool ImplReadHeader();
+ bool ImplReadPalette();
+ void ImplGetGrayPalette( sal_uInt16 );
+ sal_uInt32 ImplReadsal_uInt32();
+
+public:
+
+ explicit PNGReaderImpl( SvStream& );
+ ~PNGReaderImpl();
+
+ BitmapEx GetBitmapEx();
+ const std::vector<vcl::PNGReader::ChunkData>& GetAllChunks();
+ void SetIgnoreGammaChunk( bool bIgnore ){ mbIgnoreGammaChunk = bIgnore; };
+};
+
+PNGReaderImpl::PNGReaderImpl( SvStream& rPNGStream )
+: mrPNGStream( rPNGStream ),
+ mpMaskAcc ( nullptr ),
+ mpScanCurrent ( nullptr ),
+ mpColorTable ( const_cast<sal_uInt8*>(mpDefaultColorTable) ),
+ mnChunkType ( 0 ),
+ mnChunkLen ( 0 ),
+ mnBPP ( 0 ),
+ mnScansize ( 0 ),
+ mnYpos ( 0 ),
+ mnPass ( 0 ),
+ mnXStart ( 0 ),
+ mnXAdd ( 0 ),
+ mnYAdd ( 0 ),
+ mnTargetDepth ( 0 ),
+ mnTransRed ( 0 ),
+ mnTransGreen ( 0 ),
+ mnTransBlue ( 0 ),
+ mnPngDepth ( 0 ),
+ mnColorType ( 0 ),
+ mnCompressionType( 0 ),
+ mnFilterType ( 0 ),
+ mnInterlaceType ( 0 ),
+ mcTranspColor ( BitmapColor( 0xFF )),
+ mcOpaqueColor ( BitmapColor( 0x00 )),
+ mbTransparent( false ),
+ mbAlphaChannel( false ),
+ mbRGBTriple( false ),
+ mbPalette( false ),
+ mbGrayScale( false ),
+ mbzCodecInUse ( false ),
+ mbStatus( true ),
+ mbIDATStarted( false ),
+ mbIDATComplete( false ),
+ mbpHYs ( false ),
+ mbIgnoreGammaChunk ( false ),
+ mbIgnoreCRC( utl::ConfigManager::IsFuzzing() )
+#if OSL_DEBUG_LEVEL > 0
+ ,mnAllocSizeScanline(0),
+ mnAllocSizeScanlineAlpha(0)
+#endif
+{
+ // prepare the PNG data stream
+ mnOrigStreamMode = mrPNGStream.GetEndian();
+ mrPNGStream.SetEndian( SvStreamEndian::BIG );
+
+ // prepare the chunk reader
+ maChunkSeq.reserve( 16 );
+ maChunkIter = maChunkSeq.begin();
+
+ // estimate PNG file size (to allow sanity checks)
+ mnStreamSize = mrPNGStream.TellEnd();
+
+ // check the PNG header magic
+ sal_uInt32 nDummy = 0;
+ mrPNGStream.ReadUInt32( nDummy );
+ mbStatus = (nDummy == 0x89504e47);
+ mrPNGStream.ReadUInt32( nDummy );
+ mbStatus = (nDummy == 0x0d0a1a0a) && mbStatus;
+
+ mnPreviewShift = 0;
+ mnPreviewMask = (1 << mnPreviewShift) - 1;
+}
+
+PNGReaderImpl::~PNGReaderImpl()
+{
+ mrPNGStream.SetEndian( mnOrigStreamMode );
+
+ if ( mbzCodecInUse )
+ mpZCodec.EndCompression();
+
+ if( mpColorTable != mpDefaultColorTable )
+ delete[] mpColorTable;
+}
+
+bool PNGReaderImpl::ReadNextChunk()
+{
+ if( maChunkIter == maChunkSeq.end() )
+ {
+ // get the next chunk from the stream
+
+ // unless we are at the end of the PNG stream
+ if (!mrPNGStream.good())
+ return false;
+ if( !maChunkSeq.empty() && (maChunkSeq.back().nType == PNGCHUNK_IEND) )
+ return false;
+
+ PNGReader::ChunkData aDummyChunk;
+ maChunkIter = maChunkSeq.insert( maChunkSeq.end(), aDummyChunk );
+ PNGReader::ChunkData& rChunkData = *maChunkIter;
+
+ // read the chunk header
+ mnChunkLen = 0;
+ mnChunkType = 0;
+ mrPNGStream.ReadInt32( mnChunkLen ).ReadUInt32( mnChunkType );
+ rChunkData.nType = mnChunkType;
+
+ // fdo#61847 truncate over-long, trailing chunks
+ const std::size_t nStreamPos = mrPNGStream.Tell();
+ if( mnChunkLen < 0 || nStreamPos + mnChunkLen >= mnStreamSize )
+ mnChunkLen = mnStreamSize - nStreamPos;
+
+ // calculate chunktype CRC (swap it back to original byte order)
+ sal_uInt32 nChunkType = mnChunkType;
+ #if defined(__LITTLEENDIAN) || defined(OSL_LITENDIAN)
+ nChunkType = OSL_SWAPDWORD( nChunkType );
+ #endif
+ sal_uInt32 nCRC32 = rtl_crc32( 0, &nChunkType, 4 );
+
+ // read the chunk data and check the CRC
+ if( mnChunkLen && !mrPNGStream.eof() )
+ {
+ rChunkData.aData.resize( mnChunkLen );
+
+ sal_Int32 nBytesRead = 0;
+ do
+ {
+ sal_uInt8& rPtr = rChunkData.aData[nBytesRead];
+ nBytesRead += mrPNGStream.ReadBytes(&rPtr, mnChunkLen - nBytesRead);
+ } while (nBytesRead < mnChunkLen && mrPNGStream.good());
+
+ nCRC32 = rtl_crc32( nCRC32, rChunkData.aData.data(), mnChunkLen );
+ maDataIter = rChunkData.aData.begin();
+ }
+ sal_uInt32 nCheck(0);
+ mrPNGStream.ReadUInt32( nCheck );
+ if (!mbIgnoreCRC && nCRC32 != nCheck)
+ return false;
+ }
+ else
+ {
+ // the next chunk was already read
+ mnChunkType = (*maChunkIter).nType;
+ mnChunkLen = (*maChunkIter).aData.size();
+ maDataIter = (*maChunkIter).aData.begin();
+ }
+
+ ++maChunkIter;
+ return mnChunkType != PNGCHUNK_IEND;
+}
+
+const std::vector< vcl::PNGReader::ChunkData >& PNGReaderImpl::GetAllChunks()
+{
+ // read the remaining chunks from mrPNGStream
+ while( ReadNextChunk() ) ;
+ return maChunkSeq;
+}
+
+BitmapEx PNGReaderImpl::GetBitmapEx()
+{
+ // reset to the first chunk
+ maChunkIter = maChunkSeq.begin();
+
+ // first chunk must be IDHR
+ if( mbStatus && ReadNextChunk() )
+ {
+ if (mnChunkType == PNGCHUNK_IHDR)
+ mbStatus = ImplReadHeader();
+ else
+ mbStatus = false;
+ }
+
+ // parse the remaining chunks
+ while (mbStatus && !mbIDATComplete && ReadNextChunk())
+ {
+ switch( mnChunkType )
+ {
+ case PNGCHUNK_IHDR :
+ {
+ mbStatus = false; //IHDR should only appear as the first chunk
+ }
+ break;
+
+ case PNGCHUNK_gAMA : // the gamma chunk must precede
+ { // the 'IDAT' and also the 'PLTE'(if available )
+ if (!mbIgnoreGammaChunk && !mbIDATComplete)
+ ImplGetGamma();
+ }
+ break;
+
+ case PNGCHUNK_PLTE :
+ {
+ if (!mbPalette && !mbIDATStarted)
+ mbStatus = ImplReadPalette();
+ }
+ break;
+
+ case PNGCHUNK_tRNS :
+ {
+ if (!mbIDATComplete) // the tRNS chunk must precede the IDAT
+ mbStatus = ImplReadTransparent();
+ }
+ break;
+
+ case PNGCHUNK_bKGD : // the background chunk must appear
+ {
+ if (!mbIDATComplete && mbPalette) // before the 'IDAT' and after the
+ ImplGetBackground(); // PLTE(if available ) chunk.
+ }
+ break;
+
+ case PNGCHUNK_IDAT :
+ {
+ if ( !mpInflateInBuf ) // taking care that the header has properly been read
+ mbStatus = false;
+ else if (!mbIDATComplete) // the gfx is finished, but there may be left a zlibCRC of about 4Bytes
+ ImplReadIDAT();
+ }
+ break;
+
+ case PNGCHUNK_pHYs :
+ {
+ if (!mbIDATComplete && mnChunkLen == 9)
+ {
+ sal_uInt32 nXPixelPerMeter = ImplReadsal_uInt32();
+ sal_uInt32 nYPixelPerMeter = ImplReadsal_uInt32();
+
+ sal_uInt8 nUnitSpecifier = *maDataIter++;
+ if( (nUnitSpecifier == 1) && nXPixelPerMeter && nYPixelPerMeter )
+ {
+ mbpHYs = true;
+
+ // convert into MapUnit::Map100thMM
+ maPhysSize.setWidth( static_cast<sal_Int32>( (100000.0 * maOrigSize.Width()) / nXPixelPerMeter ) );
+ maPhysSize.setHeight( static_cast<sal_Int32>( (100000.0 * maOrigSize.Height()) / nYPixelPerMeter ) );
+ }
+ }
+ }
+ break;
+
+ case PNGCHUNK_IEND:
+ mbStatus = mbIDATComplete; // there is a problem if the image is not complete yet
+ break;
+ }
+ }
+
+ // release write access of the bitmaps
+ mxAcc.reset();
+ mxMaskAcc.reset();
+ mxAlphaAcc.reset();
+ mpMaskAcc = nullptr;
+
+ // return the resulting BitmapEx
+ BitmapEx aRet;
+
+ if (!mbStatus || !mbIDATComplete)
+ aRet.Clear();
+ else
+ {
+ if ( mpAlphaMask )
+ aRet = BitmapEx( *mpBmp, *mpAlphaMask );
+ else if ( mpMaskBmp )
+ aRet = BitmapEx( *mpBmp, *mpMaskBmp );
+ else
+ aRet = *mpBmp;
+
+ if ( mbpHYs && maPhysSize.Width() && maPhysSize.Height() )
+ {
+ aRet.SetPrefMapMode(MapMode(MapUnit::Map100thMM));
+ aRet.SetPrefSize( maPhysSize );
+ }
+ }
+ return aRet;
+}
+
+bool PNGReaderImpl::ImplReadHeader()
+{
+ if( mnChunkLen < 13 )
+ return false;
+
+ maOrigSize.setWidth( ImplReadsal_uInt32() );
+ maOrigSize.setHeight( ImplReadsal_uInt32() );
+
+ if (maOrigSize.IsEmpty())
+ return false;
+
+ mnPngDepth = *(maDataIter++);
+ mnColorType = *(maDataIter++);
+
+ mnCompressionType = *(maDataIter++);
+ if( mnCompressionType != 0 ) // unknown compression type
+ return false;
+
+ mnFilterType = *(maDataIter++);
+ if( mnFilterType != 0 ) // unknown filter type
+ return false;
+
+ mnInterlaceType = *(maDataIter++);
+ switch ( mnInterlaceType ) // filter type valid ?
+ {
+ case 0 : // progressive image
+ mnPass = 7;
+ break;
+ case 1 : // Adam7-interlaced image
+ mnPass = 0;
+ break;
+ default:
+ return false;
+ }
+
+ mbPalette = true;
+ mbIDATStarted = mbIDATComplete = mbAlphaChannel = mbTransparent = false;
+ mbGrayScale = mbRGBTriple = false;
+ mnTargetDepth = mnPngDepth;
+ sal_uInt64 nScansize64 = ( ( static_cast< sal_uInt64 >( maOrigSize.Width() ) * mnPngDepth ) + 7 ) >> 3;
+
+ // valid color types are 0,2,3,4 & 6
+ switch ( mnColorType )
+ {
+ case 0 : // each pixel is a grayscale
+ {
+ switch ( mnPngDepth )
+ {
+ case 2 : // 2bit target not available -> use four bits
+ mnTargetDepth = 4; // we have to expand the bitmap
+ mbGrayScale = true;
+ break;
+ case 16 :
+ mnTargetDepth = 8; // we have to reduce the bitmap
+ [[fallthrough]];
+ case 1 :
+ case 4 :
+ case 8 :
+ mbGrayScale = true;
+ break;
+ default :
+ return false;
+ }
+ }
+ break;
+
+ case 2 : // each pixel is an RGB triple
+ {
+ mbRGBTriple = true;
+ nScansize64 *= 3;
+ switch ( mnPngDepth )
+ {
+ case 16 : // we have to reduce the bitmap
+ case 8 :
+ mnTargetDepth = 24;
+ break;
+ default :
+ return false;
+ }
+ }
+ break;
+
+ case 3 : // each pixel is a palette index
+ {
+ switch ( mnPngDepth )
+ {
+ case 2 :
+ mnTargetDepth = 4; // we have to expand the bitmap
+ mbPalette = false;
+ break;
+ case 1 :
+ case 4 :
+ case 8 :
+ mbPalette = false;
+ break;
+ default :
+ return false;
+ }
+ }
+ break;
+
+ case 4 : // each pixel is a grayscale sample followed by an alpha sample
+ {
+ nScansize64 *= 2;
+ mbAlphaChannel = true;
+ switch ( mnPngDepth )
+ {
+ case 16 :
+ mnTargetDepth = 8; // we have to reduce the bitmap
+ [[fallthrough]];
+ case 8 :
+ mbGrayScale = true;
+ break;
+ default :
+ return false;
+ }
+ }
+ break;
+
+ case 6 : // each pixel is an RGB triple followed by an alpha sample
+ {
+ mbRGBTriple = true;
+ nScansize64 *= 4;
+ mbAlphaChannel = true;
+ switch (mnPngDepth )
+ {
+ case 16 : // we have to reduce the bitmap
+ case 8 :
+ mnTargetDepth = 24;
+ break;
+ default :
+ return false;
+ }
+ }
+ break;
+
+ default :
+ return false;
+ }
+
+ mnBPP = static_cast< sal_uInt32 >( nScansize64 / maOrigSize.Width() );
+ if ( !mnBPP )
+ mnBPP = 1;
+
+ nScansize64++; // each scanline includes one filterbyte
+
+ if ( nScansize64 > SAL_MAX_UINT32 )
+ return false;
+
+ // assume max theoretical compression of 1:1032
+ sal_uInt64 nMinSizeRequired = (nScansize64 * maOrigSize.Height()) / 1032;
+ if (nMinSizeRequired > mnStreamSize)
+ {
+ SAL_WARN("vcl.gdi", "overlarge png dimensions: " <<
+ maOrigSize.Width() << " x " << maOrigSize.Height() << " depth: " << static_cast<int>(mnPngDepth) <<
+ " couldn't be supplied by file length " << mnStreamSize << " at least " << nMinSizeRequired << " needed ");
+ return false;
+ }
+
+ mnScansize = static_cast< sal_uInt32 >( nScansize64 );
+
+ maTargetSize.setWidth( (maOrigSize.Width() + mnPreviewMask) >> mnPreviewShift );
+ maTargetSize.setHeight( (maOrigSize.Height() + mnPreviewMask) >> mnPreviewShift );
+
+ //round bits up to nearest multiple of 8 and divide by 8 to get num of bytes per pixel
+ int nBytesPerPixel = ((mnTargetDepth + 7) & ~7)/8;
+
+ //stupidly big, forget about it
+ if (maTargetSize.Width() >= SAL_MAX_INT32 / nBytesPerPixel / maTargetSize.Height())
+ {
+ SAL_WARN( "vcl.gdi", "overlarge png dimensions: " <<
+ maTargetSize.Width() << " x " << maTargetSize.Height() << " depth: " << mnTargetDepth);
+ return false;
+ }
+
+ // TODO: switch between both scanlines instead of copying
+ mpInflateInBuf.reset( new (std::nothrow) sal_uInt8[ mnScansize ] );
+ mpScanCurrent = mpInflateInBuf.get();
+ mpScanPrior.reset( new (std::nothrow) sal_uInt8[ mnScansize ] );
+
+ if ( !mpInflateInBuf || !mpScanPrior )
+ return false;
+
+ mpBmp = std::make_unique<Bitmap>( maTargetSize, mnTargetDepth );
+ mxAcc = BitmapScopedWriteAccess(*mpBmp);
+ if (!mxAcc)
+ return false;
+
+ if ( mbAlphaChannel )
+ {
+ mpAlphaMask = std::make_unique<AlphaMask>( maTargetSize );
+ mpAlphaMask->Erase( 128 );
+ mxAlphaAcc = AlphaScopedWriteAccess(*mpAlphaMask);
+ mpMaskAcc = mxAlphaAcc.get();
+ if (!mpMaskAcc)
+ return false;
+ }
+
+ if ( mbGrayScale )
+ ImplGetGrayPalette( mnPngDepth );
+
+ ImplPreparePass();
+
+ return true;
+}
+
+void PNGReaderImpl::ImplGetGrayPalette( sal_uInt16 nBitDepth )
+{
+ if( nBitDepth > 8 )
+ nBitDepth = 8;
+
+ sal_uInt16 nPaletteEntryCount = 1 << nBitDepth;
+ sal_uInt32 nAdd = nBitDepth ? 256 / (nPaletteEntryCount - 1) : 0;
+
+ // no bitdepth==2 available
+ // but bitdepth==4 with two unused bits is close enough
+ if( nBitDepth == 2 )
+ nPaletteEntryCount = 16;
+
+ mxAcc->SetPaletteEntryCount( nPaletteEntryCount );
+ for ( sal_uInt32 i = 0, nStart = 0; nStart < 256; i++, nStart += nAdd )
+ mxAcc->SetPaletteColor( static_cast<sal_uInt16>(i), BitmapColor( mpColorTable[ nStart ],
+ mpColorTable[ nStart ], mpColorTable[ nStart ] ) );
+}
+
+bool PNGReaderImpl::ImplReadPalette()
+{
+ sal_uInt16 nCount = static_cast<sal_uInt16>( mnChunkLen / 3 );
+
+ if ( ( ( mnChunkLen % 3 ) == 0 ) && ( ( 0 < nCount ) && ( nCount <= 256 ) ) && mxAcc )
+ {
+ mbPalette = true;
+ mxAcc->SetPaletteEntryCount( nCount );
+
+ for ( sal_uInt16 i = 0; i < nCount; i++ )
+ {
+ sal_uInt8 nRed = mpColorTable[ *maDataIter++ ];
+ sal_uInt8 nGreen = mpColorTable[ *maDataIter++ ];
+ sal_uInt8 nBlue = mpColorTable[ *maDataIter++ ];
+ mxAcc->SetPaletteColor( i, Color( nRed, nGreen, nBlue ) );
+ }
+ }
+ else
+ mbStatus = false;
+
+ return mbStatus;
+}
+
+bool PNGReaderImpl::ImplReadTransparent()
+{
+ bool bNeedAlpha = false;
+
+ if ( mpTransTab == nullptr )
+ {
+ switch ( mnColorType )
+ {
+ case 0 :
+ {
+ if ( mnChunkLen == 2 )
+ {
+ mpTransTab.reset( new sal_uInt8[ 256 ] );
+ memset( mpTransTab.get(), 0xff, 256);
+ // color type 0 and 4 is always greyscale,
+ // so the return value can be used as index
+ sal_uInt8 nIndex = ImplScaleColor();
+ mpTransTab[ nIndex ] = 0;
+ mbTransparent = true;
+ }
+ }
+ break;
+
+ case 2 :
+ {
+ if ( mnChunkLen == 6 )
+ {
+ mnTransRed = ImplScaleColor();
+ mnTransGreen = ImplScaleColor();
+ mnTransBlue = ImplScaleColor();
+ mbTransparent = true;
+ }
+ }
+ break;
+
+ case 3 :
+ {
+ if ( mnChunkLen <= 256 )
+ {
+ mbTransparent = true;
+ mpTransTab.reset( new sal_uInt8 [ 256 ] );
+ memset( mpTransTab.get(), 0xff, 256 );
+ if (mnChunkLen > 0)
+ {
+ memcpy( mpTransTab.get(), &(*maDataIter), mnChunkLen );
+ maDataIter += mnChunkLen;
+ // need alpha transparency if not on/off masking
+ for( int i = 0; i < mnChunkLen; ++i )
+ bNeedAlpha |= (mpTransTab[i]!=0x00) && (mpTransTab[i]!=0xFF);
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ if( mbTransparent && !mbAlphaChannel && !mpMaskBmp )
+ {
+ if( bNeedAlpha)
+ {
+ mpAlphaMask = std::make_unique<AlphaMask>( maTargetSize );
+ mxAlphaAcc = AlphaScopedWriteAccess(*mpAlphaMask);
+ mpMaskAcc = mxAlphaAcc.get();
+ }
+ else
+ {
+ mpMaskBmp = std::make_unique<Bitmap>( maTargetSize, 1 );
+ mxMaskAcc = BitmapScopedWriteAccess(*mpMaskBmp);
+ mpMaskAcc = mxMaskAcc.get();
+ }
+ mbTransparent = (mpMaskAcc != nullptr);
+ if( !mbTransparent )
+ return false;
+ mpMaskAcc->Erase( Color(0,0,0) );
+ }
+
+ return true;
+}
+
+void PNGReaderImpl::ImplGetGamma()
+{
+ if( mnChunkLen < 4 )
+ return;
+
+ sal_uInt32 nGammaValue = ImplReadsal_uInt32();
+ double fGamma = ( VIEWING_GAMMA / DISPLAY_GAMMA ) * ( static_cast<double>(nGammaValue) / 100000 );
+ double fInvGamma = ( fGamma <= 0.0 || fGamma > 10.0 ) ? 1.0 : ( 1.0 / fGamma );
+
+ if ( fInvGamma != 1.0 )
+ {
+ if ( mpColorTable == mpDefaultColorTable )
+ mpColorTable = new sal_uInt8[ 256 ];
+
+ for ( sal_Int32 i = 0; i < 256; i++ )
+ mpColorTable[ i ] = static_cast<sal_uInt8>(pow(static_cast<double>(i)/255.0, fInvGamma) * 255.0 + 0.5);
+
+ if ( mbGrayScale )
+ ImplGetGrayPalette( mnPngDepth );
+ }
+}
+
+void PNGReaderImpl::ImplGetBackground()
+{
+ switch (mnColorType)
+ {
+ case 3:
+ {
+ if (mnChunkLen == 1)
+ {
+ sal_uInt16 nCol = *maDataIter++;
+
+ if (nCol < mxAcc->GetPaletteEntryCount())
+ {
+ mxAcc->Erase(mxAcc->GetPaletteColor(static_cast<sal_uInt8>(nCol)));
+ break;
+ }
+ }
+ }
+ break;
+
+ case 0:
+ case 4:
+ {
+ if (mnChunkLen == 2)
+ {
+ // the color type 0 and 4 is always greyscale,
+ // so the return value can be used as index
+ mxAcc->Erase(mxAcc->GetPaletteColor(ImplScaleColor()));
+ }
+ }
+ break;
+
+ case 2:
+ case 6:
+ {
+ if (mnChunkLen == 6)
+ {
+ sal_uInt8 nRed = ImplScaleColor();
+ sal_uInt8 nGreen = ImplScaleColor();
+ sal_uInt8 nBlue = ImplScaleColor();
+ // ofz#18653 slow and uninteresting
+ if (utl::ConfigManager::IsFuzzing())
+ return;
+ mxAcc->Erase(Color(nRed, nGreen, nBlue));
+ }
+ }
+ break;
+ }
+}
+
+// for color type 0 and 4 (greyscale) the return value is always index to the color
+// 2 and 6 (RGB) the return value is always the 8 bit color component
+sal_uInt8 PNGReaderImpl::ImplScaleColor()
+{
+ sal_uInt32 nMask = ( 1 << mnPngDepth ) - 1;
+ sal_uInt16 nCol = ( *maDataIter++ << 8 );
+
+ nCol += *maDataIter++ & static_cast<sal_uInt16>(nMask);
+
+ if ( mnPngDepth > 8 ) // convert 16bit graphics to 8
+ nCol >>= 8;
+
+ return static_cast<sal_uInt8>(nCol);
+}
+
+// ImplReadIDAT reads as much image data as needed
+
+void PNGReaderImpl::ImplReadIDAT()
+{
+ //when fuzzing with a max len set, max decompress to 250 times that limit
+ static size_t nMaxAllowedDecompression = [](const char* pEnv) { size_t nRet = pEnv ? std::atoi(pEnv) : 0; return nRet * 250; }(std::getenv("FUZZ_MAX_INPUT_LEN"));
+ size_t nTotalDataRead = 0;
+
+ if( mnChunkLen > 0 )
+ {
+ mbIDATStarted = true;
+
+ if ( !mbzCodecInUse )
+ {
+ mbzCodecInUse = true;
+ mpZCodec.BeginCompression( ZCODEC_NO_COMPRESSION );
+ }
+ mpZCodec.SetBreak( mnChunkLen );
+ SvMemoryStream aIStrm( &(*maDataIter), mnChunkLen, StreamMode::READ );
+
+ while ( mpZCodec.GetBreak() )
+ {
+ // get bytes needed to fill the current scanline
+ sal_Int32 nToRead = mnScansize - (mpScanCurrent - mpInflateInBuf.get());
+ sal_Int32 nRead = mpZCodec.ReadAsynchron( aIStrm, mpScanCurrent, nToRead );
+ if ( nRead < 0 )
+ {
+ mbStatus = false;
+ break;
+ }
+ nTotalDataRead += nRead;
+ if (nMaxAllowedDecompression && nTotalDataRead > nMaxAllowedDecompression)
+ {
+ mbStatus = false;
+ break;
+ }
+ if ( nRead < nToRead )
+ {
+ mpScanCurrent += nRead; // more ZStream data in the next IDAT chunk
+ break;
+ }
+ else // this scanline is Finished
+ {
+ mpScanCurrent = mpInflateInBuf.get();
+ ImplApplyFilter();
+
+ ImplDrawScanline( mnXStart, mnXAdd );
+ mnYpos += mnYAdd;
+ }
+
+ if ( mnYpos >= o3tl::make_unsigned(maOrigSize.Height()) )
+ {
+ if( (mnPass < 7) && mnInterlaceType )
+ if( ImplPreparePass() )
+ continue;
+ mbIDATComplete = true;
+ break;
+ }
+ }
+ }
+
+ if (mbIDATComplete)
+ {
+ mpZCodec.EndCompression();
+ mbzCodecInUse = false;
+ }
+}
+
+bool PNGReaderImpl::ImplPreparePass()
+{
+ struct InterlaceParams{ int mnXStart, mnYStart, mnXAdd, mnYAdd; };
+ static const InterlaceParams aInterlaceParams[8] =
+ {
+ // non-interlaced
+ { 0, 0, 1, 1 },
+ // Adam7-interlaced
+ { 0, 0, 8, 8 }, // pass 1
+ { 4, 0, 8, 8 }, // pass 2
+ { 0, 4, 4, 8 }, // pass 3
+ { 2, 0, 4, 4 }, // pass 4
+ { 0, 2, 2, 4 }, // pass 5
+ { 1, 0, 2, 2 }, // pass 6
+ { 0, 1, 1, 2 } // pass 7
+ };
+
+ const InterlaceParams* pParam = &aInterlaceParams[ 0 ];
+ if( mnInterlaceType )
+ {
+ while( ++mnPass <= 7 )
+ {
+ pParam = &aInterlaceParams[ mnPass ];
+
+ // skip this pass if the original image is too small for it
+ if( (pParam->mnXStart < maOrigSize.Width())
+ && (pParam->mnYStart < maOrigSize.Height()) )
+ break;
+ }
+ if( mnPass > 7 )
+ return false;
+
+ // skip the last passes if possible (for scaled down target images)
+ if( mnPreviewMask & (pParam->mnXStart | pParam->mnYStart) )
+ return false;
+ }
+
+ mnYpos = pParam->mnYStart;
+ mnXStart = pParam->mnXStart;
+ mnXAdd = pParam->mnXAdd;
+ mnYAdd = pParam->mnYAdd;
+
+ // in Interlace mode the size of scanline is not constant
+ // so first we calculate the number of entries
+ long nScanWidth = (maOrigSize.Width() - mnXStart + mnXAdd - 1) / mnXAdd;
+ mnScansize = nScanWidth;
+
+ if( mbRGBTriple )
+ mnScansize = 3 * nScanWidth;
+
+ if( mbAlphaChannel )
+ mnScansize += nScanWidth;
+
+ // convert to width in bytes
+ mnScansize = ( mnScansize*mnPngDepth + 7 ) >> 3;
+
+ ++mnScansize; // scan size also needs room for the filtertype byte
+ memset( mpScanPrior.get(), 0, mnScansize );
+
+ return true;
+}
+
+// ImplApplyFilter writes the complete Scanline (nY)
+// in interlace mode the parameter nXStart and nXAdd are non-zero
+
+void PNGReaderImpl::ImplApplyFilter()
+{
+ OSL_ASSERT( mnScansize >= mnBPP + 1 );
+ const sal_uInt8* const pScanEnd = mpInflateInBuf.get() + mnScansize;
+
+ sal_uInt8 nFilterType = mpInflateInBuf[0]; // the filter type may change each scanline
+ switch ( nFilterType )
+ {
+ default: // unknown Scanline Filter Type
+ case 0: // Filter Type "None"
+ // we let the pixels pass and display the data unfiltered
+ break;
+
+ case 1: // Scanline Filter Type "Sub"
+ {
+ sal_uInt8* p1 = mpInflateInBuf.get() + 1;
+ const sal_uInt8* p2 = p1;
+ p1 += mnBPP;
+
+ // use left pixels
+ while (p1 < pScanEnd)
+ {
+ *p1 = static_cast<sal_uInt8>( *p1 + *(p2++) );
+ ++p1;
+ }
+ }
+ break;
+
+ case 2: // Scanline Filter Type "Up"
+ {
+ sal_uInt8* p1 = mpInflateInBuf.get() + 1;
+ const sal_uInt8* p2 = mpScanPrior.get() + 1;
+
+ // use pixels from prior line
+ while( p1 < pScanEnd )
+ {
+ *p1 = static_cast<sal_uInt8>( *p1 + *(p2++) );
+ ++p1;
+ }
+ }
+ break;
+
+ case 3: // Scanline Filter Type "Average"
+ {
+ sal_uInt8* p1 = mpInflateInBuf.get() + 1;
+ const sal_uInt8* p2 = mpScanPrior.get() + 1;
+ const sal_uInt8* p3 = p1;
+
+ // use one pixel from prior line
+ for( int n = mnBPP; --n >= 0; ++p1, ++p2)
+ *p1 = static_cast<sal_uInt8>( *p1 + (*p2 >> 1) );
+
+ // predict by averaging the left and prior line pixels
+ while( p1 < pScanEnd )
+ {
+ *p1 = static_cast<sal_uInt8>( *p1 + ((*(p2++) + *(p3++)) >> 1) );
+ ++p1;
+ }
+ }
+ break;
+
+ case 4: // Scanline Filter Type "PathPredictor"
+ {
+ sal_uInt8* p1 = mpInflateInBuf.get() + 1;
+ const sal_uInt8* p2 = mpScanPrior.get() + 1;
+ const sal_uInt8* p3 = p1;
+ const sal_uInt8* p4 = p2;
+
+ // use one pixel from prior line
+ for( int n = mnBPP; --n >= 0; ++p1)
+ *p1 = static_cast<sal_uInt8>( *p1 + *(p2++) );
+
+ // predict by using the left and the prior line pixels
+ while( p1 < pScanEnd )
+ {
+ int na = *(p2++);
+ int nb = *(p3++);
+ int nc = *(p4++);
+
+ int npa = nb - nc;
+ int npb = na - nc;
+ int npc = npa + npb;
+
+ if( npa < 0 )
+ npa =-npa;
+ if( npb < 0 )
+ npb =-npb;
+ if( npc < 0 )
+ npc =-npc;
+
+ if( npa > npb )
+ {
+ na = nb;
+ npa = npb;
+ }
+ if( npa > npc )
+ na = nc;
+
+ *p1 = static_cast<sal_uInt8>( *p1 + na );
+ ++p1;
+ }
+ }
+ break;
+ }
+
+ memcpy( mpScanPrior.get(), mpInflateInBuf.get(), mnScansize );
+}
+
+namespace
+{
+ sal_uInt8 SanitizePaletteIndex(sal_uInt8 nIndex, sal_uInt16 nPaletteEntryCount)
+ {
+ if (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 nIndex;
+ }
+
+ void SanitizePaletteIndexes(sal_uInt8* pEntries, int nLen, const BitmapScopedWriteAccess& rAcc)
+ {
+ sal_uInt16 nPaletteEntryCount = rAcc->GetPaletteEntryCount();
+ for (int nX = 0; nX < nLen; ++nX)
+ {
+ if (pEntries[nX] >= nPaletteEntryCount)
+ {
+ SAL_WARN("vcl.gdi", "invalid colormap index: "
+ << static_cast<unsigned int>(pEntries[nX]) << ", colormap len is: "
+ << nPaletteEntryCount);
+ pEntries[nX] = pEntries[nX] % nPaletteEntryCount;
+ }
+ }
+ }
+}
+
+// ImplDrawScanlines draws the complete Scanline (nY) into the target bitmap
+// In interlace mode the parameter nXStart and nXAdd append to the currently used pass
+
+void PNGReaderImpl::ImplDrawScanline( sal_uInt32 nXStart, sal_uInt32 nXAdd )
+{
+ // optimization for downscaling
+ if( mnYpos & mnPreviewMask )
+ return;
+ if( nXStart & mnPreviewMask )
+ return;
+
+ // convert nY to pixel units in the target image
+ // => TODO; also do this for nX here instead of in the ImplSet*Pixel() methods
+ const sal_uInt32 nY = mnYpos >> mnPreviewShift;
+
+ sal_uInt8* pTmp = mpInflateInBuf.get() + 1;
+ if ( mxAcc->HasPalette() ) // alphachannel is not allowed by pictures including palette entries
+ {
+ switch ( mxAcc->GetBitCount() )
+ {
+ case 1 :
+ {
+ if ( mbTransparent )
+ {
+ for ( long nX = nXStart, nShift = 0; nX < maOrigSize.Width(); nX += nXAdd )
+ {
+ sal_uInt8 nCol;
+ nShift = (nShift - 1) & 7;
+ if ( nShift == 0 )
+ nCol = *(pTmp++);
+ else
+ nCol = static_cast<sal_uInt8>( *pTmp >> nShift );
+ nCol &= 1;
+
+ ImplSetAlphaPixel( nY, nX, nCol, mpTransTab[ nCol ] );
+ }
+ }
+ else
+ { // ScanlineFormat::N1BitMsbPal
+ for ( long nX = nXStart, nShift = 0; nX < maOrigSize.Width(); nX += nXAdd )
+ {
+ nShift = (nShift - 1) & 7;
+
+ sal_uInt8 nCol;
+ if ( nShift == 0 )
+ nCol = *(pTmp++);
+ else
+ nCol = static_cast<sal_uInt8>( *pTmp >> nShift );
+ nCol &= 1;
+
+ ImplSetPixel( nY, nX, nCol );
+ }
+ }
+ }
+ break;
+
+ case 4 :
+ {
+ if ( mbTransparent )
+ {
+ if ( mnPngDepth == 4 ) // check if source has a two bit pixel format
+ {
+ for ( long nX = nXStart, nXIndex = 0; nX < maOrigSize.Width(); nX += nXAdd, ++nXIndex )
+ {
+ if( nXIndex & 1 )
+ {
+ ImplSetAlphaPixel( nY, nX, *pTmp & 0x0f, mpTransTab[ *pTmp & 0x0f ] );
+ pTmp++;
+ }
+ else
+ {
+ ImplSetAlphaPixel( nY, nX, ( *pTmp >> 4 ) & 0x0f, mpTransTab[ *pTmp >> 4 ] );
+ }
+ }
+ }
+ else // if ( mnPngDepth == 2 )
+ {
+ for ( long nX = nXStart, nXIndex = 0; nX < maOrigSize.Width(); nX += nXAdd, nXIndex++ )
+ {
+ sal_uInt8 nCol;
+ switch( nXIndex & 3 )
+ {
+ case 0 :
+ nCol = *pTmp >> 6;
+ break;
+
+ case 1 :
+ nCol = ( *pTmp >> 4 ) & 0x03 ;
+ break;
+
+ case 2 :
+ nCol = ( *pTmp >> 2 ) & 0x03;
+ break;
+
+ case 3 :
+ nCol = ( *pTmp++ ) & 0x03;
+ break;
+
+ default: // get rid of nCol uninitialized warning
+ nCol = 0;
+ break;
+ }
+
+ ImplSetAlphaPixel( nY, nX, nCol, mpTransTab[ nCol ] );
+ }
+ }
+ }
+ else
+ {
+ if ( mnPngDepth == 4 ) // maybe the source is a two bitmap graphic
+ { // ScanlineFormat::N4BitLsnPal
+ for ( long nX = nXStart, nXIndex = 0; nX < maOrigSize.Width(); nX += nXAdd, nXIndex++ )
+ {
+ if( nXIndex & 1 )
+ ImplSetPixel( nY, nX, *pTmp++ & 0x0f );
+ else
+ ImplSetPixel( nY, nX, ( *pTmp >> 4 ) & 0x0f );
+ }
+ }
+ else // if ( mnPngDepth == 2 )
+ {
+ for ( long nX = nXStart, nXIndex = 0; nX < maOrigSize.Width(); nX += nXAdd, nXIndex++ )
+ {
+ switch( nXIndex & 3 )
+ {
+ case 0 :
+ ImplSetPixel( nY, nX, *pTmp >> 6 );
+ break;
+
+ case 1 :
+ ImplSetPixel( nY, nX, ( *pTmp >> 4 ) & 0x03 );
+ break;
+
+ case 2 :
+ ImplSetPixel( nY, nX, ( *pTmp >> 2 ) & 0x03 );
+ break;
+
+ case 3 :
+ ImplSetPixel( nY, nX, *pTmp++ & 0x03 );
+ break;
+ }
+ }
+ }
+ }
+ }
+ break;
+
+ case 8 :
+ {
+ if ( mbAlphaChannel )
+ {
+ if ( mnPngDepth == 8 ) // maybe the source is a 16 bit grayscale
+ {
+ for ( long nX = nXStart; nX < maOrigSize.Width(); nX += nXAdd, pTmp += 2 )
+ ImplSetAlphaPixel( nY, nX, pTmp[ 0 ], pTmp[ 1 ] );
+ }
+ else
+ {
+ assert(mnPngDepth == 16);
+ for ( long nX = nXStart; nX < maOrigSize.Width(); nX += nXAdd, pTmp += 4 )
+ ImplSetAlphaPixel( nY, nX, pTmp[ 0 ], pTmp[ 2 ] );
+ }
+ }
+ else if ( mbTransparent )
+ {
+ if ( mnPngDepth == 8 ) // maybe the source is a 16 bit grayscale
+ {
+ for ( long nX = nXStart; nX < maOrigSize.Width(); nX += nXAdd, pTmp++ )
+ ImplSetAlphaPixel( nY, nX, *pTmp, mpTransTab[ *pTmp ] );
+ }
+ else if (mnPngDepth == 1 )
+ {
+ for ( long nX = nXStart, nShift = 0; nX < maOrigSize.Width(); nX += nXAdd )
+ {
+ nShift = (nShift - 1) & 7;
+
+ sal_uInt8 nCol;
+ if ( nShift == 0 )
+ nCol = *(pTmp++);
+ else
+ nCol = static_cast<sal_uInt8>( *pTmp >> nShift );
+ nCol &= 1;
+
+ ImplSetAlphaPixel( nY, nX, nCol, mpTransTab[ nCol ] );
+ }
+ }
+ else
+ {
+ for ( long nX = nXStart; nX < maOrigSize.Width(); nX += nXAdd, pTmp += 2 )
+ ImplSetAlphaPixel( nY, nX, *pTmp, mpTransTab[ *pTmp ] );
+ }
+ }
+ else // neither alpha nor transparency
+ {
+ if ( mnPngDepth == 8 ) // maybe the source is a 16 bit grayscale or 1 bit indexed
+ {
+ if( nXAdd == 1 && mnPreviewShift == 0 ) // copy raw line data if possible
+ {
+ int nLineBytes = maOrigSize.Width();
+ if (mbPalette)
+ SanitizePaletteIndexes(pTmp, nLineBytes, mxAcc);
+ mxAcc->CopyScanline( nY, pTmp, ScanlineFormat::N8BitPal, nLineBytes );
+ }
+ else
+ {
+ for ( long nX = nXStart; nX < maOrigSize.Width(); nX += nXAdd )
+ ImplSetPixel( nY, nX, *pTmp++ );
+ }
+ }
+ else if (mnPngDepth == 1 )
+ {
+ for ( long nX = nXStart, nShift = 0; nX < maOrigSize.Width(); nX += nXAdd )
+ {
+ nShift = (nShift - 1) & 7;
+
+ sal_uInt8 nCol;
+ if ( nShift == 0 )
+ nCol = *(pTmp++);
+ else
+ nCol = static_cast<sal_uInt8>( *pTmp >> nShift );
+ nCol &= 1;
+
+ ImplSetPixel( nY, nX, nCol );
+ }
+ }
+ else
+ {
+ for ( long nX = nXStart; nX < maOrigSize.Width(); nX += nXAdd, pTmp += 2 )
+ ImplSetPixel( nY, nX, *pTmp );
+ }
+ }
+ }
+ break;
+
+ default :
+ mbStatus = false;
+ break;
+ }
+ }
+ else // no palette => truecolor
+ {
+ if( mbAlphaChannel )
+ {
+ // has RGB + alpha
+ if ( mnPngDepth == 8 ) // maybe the source has 16 bit per sample
+ {
+ // ScanlineFormat::N32BitTcRgba
+ // only use DirectScanline when we have no preview shifting stuff and accesses to content and alpha
+ const bool bDoDirectScanline(
+ !nXStart && 1 == nXAdd && !mnPreviewShift && mpMaskAcc);
+ const bool bCustomColorTable(mpColorTable != mpDefaultColorTable);
+
+ if(bDoDirectScanline)
+ {
+ // allocate scanlines on demand, reused for next line
+ if(!mpScanline)
+ {
+#if OSL_DEBUG_LEVEL > 0
+ mnAllocSizeScanline = maOrigSize.Width() * 3;
+#endif
+ mpScanline.reset( new sal_uInt8[maOrigSize.Width() * 3] );
+ }
+
+ if(!mpScanlineAlpha)
+ {
+#if OSL_DEBUG_LEVEL > 0
+ mnAllocSizeScanlineAlpha = maOrigSize.Width();
+#endif
+ mpScanlineAlpha.reset( new sal_uInt8[maOrigSize.Width()] );
+ }
+ }
+
+ if(bDoDirectScanline)
+ {
+ OSL_ENSURE(mpScanline, "No Scanline allocated (!)");
+ OSL_ENSURE(mpScanlineAlpha, "No ScanlineAlpha allocated (!)");
+#if OSL_DEBUG_LEVEL > 0
+ OSL_ENSURE(mnAllocSizeScanline >= maOrigSize.Width() * 3, "Allocated Scanline too small (!)");
+ OSL_ENSURE(mnAllocSizeScanlineAlpha >= maOrigSize.Width(), "Allocated ScanlineAlpha too small (!)");
+#endif
+ sal_uInt8* pScanline(mpScanline.get());
+ sal_uInt8* pScanlineAlpha(mpScanlineAlpha.get());
+
+ for (long nX(0); nX < maOrigSize.Width(); nX++, pTmp += 4)
+ {
+ // prepare content line as BGR by reordering when copying
+ // do not forget to invert alpha (source is alpha, target is opacity)
+ if(bCustomColorTable)
+ {
+ *pScanline++ = mpColorTable[pTmp[2]];
+ *pScanline++ = mpColorTable[pTmp[1]];
+ *pScanline++ = mpColorTable[pTmp[0]];
+ *pScanlineAlpha++ = ~pTmp[3];
+ }
+ else
+ {
+ *pScanline++ = pTmp[2];
+ *pScanline++ = pTmp[1];
+ *pScanline++ = pTmp[0];
+ *pScanlineAlpha++ = ~pTmp[3];
+ }
+ }
+
+ // copy scanlines directly to bitmaps for content and alpha; use the formats which
+ // are able to copy directly to BitmapBuffer
+ mxAcc->CopyScanline(nY, mpScanline.get(), ScanlineFormat::N24BitTcBgr, maOrigSize.Width() * 3);
+ mpMaskAcc->CopyScanline(nY, mpScanlineAlpha.get(), ScanlineFormat::N8BitPal, maOrigSize.Width());
+ }
+ else
+ {
+ for ( long nX = nXStart; nX < maOrigSize.Width(); nX += nXAdd, pTmp += 4 )
+ {
+ if(bCustomColorTable)
+ {
+ ImplSetAlphaPixel(
+ nY,
+ nX,
+ BitmapColor(
+ mpColorTable[ pTmp[ 0 ] ],
+ mpColorTable[ pTmp[ 1 ] ],
+ mpColorTable[ pTmp[ 2 ] ]),
+ pTmp[ 3 ]);
+ }
+ else
+ {
+ ImplSetAlphaPixel(
+ nY,
+ nX,
+ BitmapColor(
+ pTmp[0],
+ pTmp[1],
+ pTmp[2]),
+ pTmp[3]);
+ }
+ }
+ }
+ }
+ else
+ {
+ // BMP_FORMAT_64BIT_TC_RGBA
+ for ( long nX = nXStart; nX < maOrigSize.Width(); nX += nXAdd, pTmp += 8 )
+ {
+ ImplSetAlphaPixel(
+ nY,
+ nX,
+ BitmapColor(
+ mpColorTable[ pTmp[ 0 ] ],
+ mpColorTable[ pTmp[ 2 ] ],
+ mpColorTable[ pTmp[ 4 ] ]),
+ pTmp[6]);
+ }
+ }
+ }
+ else if( mbTransparent ) // has RGB + transparency
+ {
+ // ScanlineFormat::N24BitTcRgb
+ // no support currently for DirectScanline, found no real usages in current PNGs, may be added on demand
+ if ( mnPngDepth == 8 ) // maybe the source has 16 bit per sample
+ {
+ for ( long nX = nXStart; nX < maOrigSize.Width(); nX += nXAdd, pTmp += 3 )
+ {
+ sal_uInt8 nRed = pTmp[ 0 ];
+ sal_uInt8 nGreen = pTmp[ 1 ];
+ sal_uInt8 nBlue = pTmp[ 2 ];
+ bool bTransparent = ( ( nRed == mnTransRed )
+ && ( nGreen == mnTransGreen )
+ && ( nBlue == mnTransBlue ) );
+
+ ImplSetTranspPixel( nY, nX, BitmapColor( mpColorTable[ nRed ],
+ mpColorTable[ nGreen ],
+ mpColorTable[ nBlue ] ), bTransparent );
+ }
+ }
+ else
+ {
+ // BMP_FORMAT_48BIT_TC_RGB
+ for ( long nX = nXStart; nX < maOrigSize.Width(); nX += nXAdd, pTmp += 6 )
+ {
+ sal_uInt8 nRed = pTmp[ 0 ];
+ sal_uInt8 nGreen = pTmp[ 2 ];
+ sal_uInt8 nBlue = pTmp[ 4 ];
+ bool bTransparent = ( ( nRed == mnTransRed )
+ && ( nGreen == mnTransGreen )
+ && ( nBlue == mnTransBlue ) );
+
+ ImplSetTranspPixel( nY, nX, BitmapColor( mpColorTable[ nRed ],
+ mpColorTable[ nGreen ],
+ mpColorTable[ nBlue ] ), bTransparent );
+ }
+ }
+ }
+ else // has RGB but neither alpha nor transparency
+ {
+ // ScanlineFormat::N24BitTcRgb
+ // only use DirectScanline when we have no preview shifting stuff and access to content
+ const bool bDoDirectScanline(
+ !nXStart && 1 == nXAdd && !mnPreviewShift);
+ const bool bCustomColorTable(mpColorTable != mpDefaultColorTable);
+
+ if(bDoDirectScanline && !mpScanline)
+ {
+ // allocate scanlines on demand, reused for next line
+#if OSL_DEBUG_LEVEL > 0
+ mnAllocSizeScanline = maOrigSize.Width() * 3;
+#endif
+ mpScanline.reset( new sal_uInt8[maOrigSize.Width() * 3] );
+ }
+
+ if ( mnPngDepth == 8 ) // maybe the source has 16 bit per sample
+ {
+ if(bDoDirectScanline)
+ {
+ OSL_ENSURE(mpScanline, "No Scanline allocated (!)");
+#if OSL_DEBUG_LEVEL > 0
+ OSL_ENSURE(mnAllocSizeScanline >= maOrigSize.Width() * 3, "Allocated Scanline too small (!)");
+#endif
+ sal_uInt8* pScanline(mpScanline.get());
+
+ for (long nX(0); nX < maOrigSize.Width(); nX++, pTmp += 3)
+ {
+ // prepare content line as BGR by reordering when copying
+ if(bCustomColorTable)
+ {
+ *pScanline++ = mpColorTable[pTmp[2]];
+ *pScanline++ = mpColorTable[pTmp[1]];
+ *pScanline++ = mpColorTable[pTmp[0]];
+ }
+ else
+ {
+ *pScanline++ = pTmp[2];
+ *pScanline++ = pTmp[1];
+ *pScanline++ = pTmp[0];
+ }
+ }
+
+ // copy scanline directly to bitmap for content; use the format which is able to
+ // copy directly to BitmapBuffer
+ mxAcc->CopyScanline(nY, mpScanline.get(), ScanlineFormat::N24BitTcBgr, maOrigSize.Width() * 3);
+ }
+ else
+ {
+ for ( long nX = nXStart; nX < maOrigSize.Width(); nX += nXAdd, pTmp += 3 )
+ {
+ if(bCustomColorTable)
+ {
+ ImplSetPixel(
+ nY,
+ nX,
+ BitmapColor(
+ mpColorTable[ pTmp[ 0 ] ],
+ mpColorTable[ pTmp[ 1 ] ],
+ mpColorTable[ pTmp[ 2 ] ]));
+ }
+ else
+ {
+ ImplSetPixel(
+ nY,
+ nX,
+ BitmapColor(
+ pTmp[0],
+ pTmp[1],
+ pTmp[2]));
+ }
+ }
+ }
+ }
+ else
+ {
+ // BMP_FORMAT_48BIT_TC_RGB
+ // no support currently for DirectScanline, found no real usages in current PNGs, may be added on demand
+ for ( long nX = nXStart; nX < maOrigSize.Width(); nX += nXAdd, pTmp += 6 )
+ {
+ ImplSetPixel(
+ nY,
+ nX,
+ BitmapColor(
+ mpColorTable[ pTmp[ 0 ] ],
+ mpColorTable[ pTmp[ 2 ] ],
+ mpColorTable[ pTmp[ 4 ] ]));
+ }
+ }
+ }
+ }
+}
+
+void PNGReaderImpl::ImplSetPixel( sal_uInt32 nY, sal_uInt32 nX, const BitmapColor& rBitmapColor )
+{
+ // TODO: get preview mode checks out of inner loop
+ if( nX & mnPreviewMask )
+ return;
+ nX >>= mnPreviewShift;
+
+ mxAcc->SetPixel( nY, nX, rBitmapColor );
+}
+
+void PNGReaderImpl::ImplSetPixel( sal_uInt32 nY, sal_uInt32 nX, sal_uInt8 nPalIndex )
+{
+ // TODO: get preview mode checks out of inner loop
+ if( nX & mnPreviewMask )
+ return;
+ nX >>= mnPreviewShift;
+
+ mxAcc->SetPixelIndex(nY, nX, SanitizePaletteIndex(nPalIndex, mxAcc->GetPaletteEntryCount()));
+}
+
+void PNGReaderImpl::ImplSetTranspPixel( sal_uInt32 nY, sal_uInt32 nX, const BitmapColor& rBitmapColor, bool bTrans )
+{
+ // TODO: get preview mode checks out of inner loop
+ if( nX & mnPreviewMask )
+ return;
+ nX >>= mnPreviewShift;
+
+ mxAcc->SetPixel( nY, nX, rBitmapColor );
+
+ if ( bTrans )
+ mpMaskAcc->SetPixel( nY, nX, mcTranspColor );
+ else
+ mpMaskAcc->SetPixel( nY, nX, mcOpaqueColor );
+}
+
+void PNGReaderImpl::ImplSetAlphaPixel( sal_uInt32 nY, sal_uInt32 nX,
+ sal_uInt8 nPalIndex, sal_uInt8 nAlpha )
+{
+ // TODO: get preview mode checks out of inner loop
+ if( nX & mnPreviewMask )
+ return;
+ nX >>= mnPreviewShift;
+
+ mxAcc->SetPixelIndex(nY, nX, SanitizePaletteIndex(nPalIndex, mxAcc->GetPaletteEntryCount()));
+ mpMaskAcc->SetPixel(nY, nX, BitmapColor(~nAlpha));
+}
+
+void PNGReaderImpl::ImplSetAlphaPixel( sal_uInt32 nY, sal_uInt32 nX,
+ const BitmapColor& rBitmapColor, sal_uInt8 nAlpha )
+{
+ // TODO: get preview mode checks out of inner loop
+ if( nX & mnPreviewMask )
+ return;
+ nX >>= mnPreviewShift;
+
+ mxAcc->SetPixel( nY, nX, rBitmapColor );
+ if (!mpMaskAcc)
+ return;
+ mpMaskAcc->SetPixel(nY, nX, BitmapColor(~nAlpha));
+}
+
+sal_uInt32 PNGReaderImpl::ImplReadsal_uInt32()
+{
+ sal_uInt32 nRet;
+ nRet = *maDataIter++;
+ nRet <<= 8;
+ nRet |= *maDataIter++;
+ nRet <<= 8;
+ nRet |= *maDataIter++;
+ nRet <<= 8;
+ nRet |= *maDataIter++;
+ return nRet;
+}
+
+PNGReader::PNGReader(SvStream& rIStream) :
+ mpImpl(new vcl::PNGReaderImpl(rIStream))
+{
+}
+
+PNGReader::~PNGReader()
+{
+}
+
+BitmapEx PNGReader::Read()
+{
+ return mpImpl->GetBitmapEx();
+}
+
+const std::vector< vcl::PNGReader::ChunkData >& PNGReader::GetChunks() const
+{
+ return mpImpl->GetAllChunks();
+}
+
+void PNGReader::SetIgnoreGammaChunk(bool bIgnoreGammaChunk)
+{
+ mpImpl->SetIgnoreGammaChunk(bIgnoreGammaChunk);
+}
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/png/pngwrite.cxx b/vcl/source/filter/png/pngwrite.cxx
new file mode 100644
index 000000000..8190c1efd
--- /dev/null
+++ b/vcl/source/filter/png/pngwrite.cxx
@@ -0,0 +1,720 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <vcl/pngwrite.hxx>
+#include <vcl/bitmapex.hxx>
+
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/uno/Sequence.hxx>
+
+#include <limits>
+#include <rtl/crc.h>
+#include <tools/zcodec.hxx>
+#include <tools/stream.hxx>
+#include <vcl/bitmapaccess.hxx>
+#include <vcl/alpha.hxx>
+#include <osl/endian.h>
+#include <memory>
+#include <vcl/BitmapTools.hxx>
+
+#define PNG_DEF_COMPRESSION 6
+
+#define PNGCHUNK_IHDR 0x49484452
+#define PNGCHUNK_PLTE 0x504c5445
+#define PNGCHUNK_IDAT 0x49444154
+#define PNGCHUNK_IEND 0x49454e44
+#define PNGCHUNK_pHYs 0x70485973
+#define PNGCHUNK_tRNS 0x74524e53
+
+namespace vcl
+{
+
+class PNGWriterImpl
+{
+public:
+
+ PNGWriterImpl(const BitmapEx& BmpEx,
+ const css::uno::Sequence<css::beans::PropertyValue>* pFilterData);
+
+ bool Write(SvStream& rOutStream);
+
+ std::vector<vcl::PNGWriter::ChunkData>& GetChunks()
+ {
+ return maChunkSeq;
+ }
+
+private:
+
+ std::vector<vcl::PNGWriter::ChunkData> maChunkSeq;
+
+ sal_Int32 mnCompLevel;
+ sal_Int32 mnInterlaced;
+ sal_uInt32 mnMaxChunkSize;
+ bool mbStatus;
+
+ Bitmap::ScopedReadAccess mpAccess;
+ BitmapReadAccess* mpMaskAccess;
+ ZCodec mpZCodec;
+
+ std::unique_ptr<sal_uInt8[]> mpDeflateInBuf; // as big as the size of a scanline + alphachannel + 1
+ std::unique_ptr<sal_uInt8[]> mpPreviousScan; // as big as mpDeflateInBuf
+ std::unique_ptr<sal_uInt8[]> mpCurrentScan;
+ sal_uLong mnDeflateInSize;
+
+ sal_uLong mnWidth;
+ sal_uLong mnHeight;
+ sal_uInt8 mnBitsPerPixel;
+ sal_uInt8 mnFilterType; // 0 or 4;
+ sal_uLong mnBBP; // bytes per pixel ( needed for filtering )
+ bool mbTrueAlpha;
+
+ void ImplWritepHYs(const BitmapEx& rBitmapEx);
+ void ImplWriteIDAT();
+ sal_uLong ImplGetFilter(sal_uLong nY, sal_uLong nXStart = 0, sal_uLong nXAdd = 1);
+ void ImplClearFirstScanline();
+ void ImplWriteTransparent();
+ bool ImplWriteHeader();
+ void ImplWritePalette();
+ void ImplOpenChunk(sal_uLong nChunkType);
+ void ImplWriteChunk(sal_uInt8 nNumb);
+ void ImplWriteChunk(sal_uInt32 nNumb);
+ void ImplWriteChunk(unsigned char const * pSource, sal_uInt32 nDatSize);
+};
+
+PNGWriterImpl::PNGWriterImpl( const BitmapEx& rBitmapEx,
+ const css::uno::Sequence<css::beans::PropertyValue>* pFilterData )
+ : mnCompLevel(PNG_DEF_COMPRESSION)
+ , mnInterlaced(0)
+ , mnMaxChunkSize(0)
+ , mbStatus(true)
+ , mpMaskAccess(nullptr)
+ , mnDeflateInSize(0)
+ , mnWidth(0)
+ , mnHeight(0)
+ , mnBitsPerPixel(0)
+ , mnFilterType(0)
+ , mnBBP(0)
+ , mbTrueAlpha(false)
+{
+ if (!rBitmapEx.IsEmpty())
+ {
+ BitmapEx aBitmapEx;
+
+ if (rBitmapEx.GetBitmap().GetBitCount() == 32)
+ {
+ if (!vcl::bitmap::convertBitmap32To24Plus8(rBitmapEx, aBitmapEx))
+ return;
+ }
+ else
+ {
+ aBitmapEx = rBitmapEx;
+ }
+
+ Bitmap aBmp(aBitmapEx.GetBitmap());
+
+ mnMaxChunkSize = std::numeric_limits<sal_uInt32>::max();
+
+ if (pFilterData)
+ {
+ for (const auto& rPropVal : *pFilterData)
+ {
+ if (rPropVal.Name == "Compression")
+ rPropVal.Value >>= mnCompLevel;
+ else if (rPropVal.Name == "Interlaced")
+ rPropVal.Value >>= mnInterlaced;
+ else if (rPropVal.Name == "MaxChunkSize")
+ {
+ sal_Int32 nVal = 0;
+ if (rPropVal.Value >>= nVal)
+ mnMaxChunkSize = static_cast<sal_uInt32>(nVal);
+ }
+ }
+ }
+ mnBitsPerPixel = static_cast<sal_uInt8>(aBmp.GetBitCount());
+
+ if (aBitmapEx.IsTransparent())
+ {
+ if (mnBitsPerPixel <= 8 && aBitmapEx.IsAlpha())
+ {
+ aBmp.Convert( BmpConversion::N24Bit );
+ mnBitsPerPixel = 24;
+ }
+
+ if (mnBitsPerPixel <= 8) // transparent palette
+ {
+ aBmp.Convert(BmpConversion::N8BitTrans);
+ aBmp.Replace(aBitmapEx.GetMask(), BMP_COL_TRANS);
+ mnBitsPerPixel = 8;
+ mpAccess = Bitmap::ScopedReadAccess(aBmp);
+ if (mpAccess)
+ {
+ if (ImplWriteHeader())
+ {
+ ImplWritepHYs(aBitmapEx);
+ ImplWritePalette();
+ ImplWriteTransparent();
+ ImplWriteIDAT();
+ }
+ mpAccess.reset();
+ }
+ else
+ {
+ mbStatus = false;
+ }
+ }
+ else
+ {
+ mpAccess = Bitmap::ScopedReadAccess(aBmp); // true RGB with alphachannel
+ if (mpAccess)
+ {
+ mbTrueAlpha = aBitmapEx.IsAlpha();
+ if (mbTrueAlpha)
+ {
+ AlphaMask aMask(aBitmapEx.GetAlpha());
+ mpMaskAccess = aMask.AcquireReadAccess();
+ if (mpMaskAccess)
+ {
+ if (ImplWriteHeader())
+ {
+ ImplWritepHYs(aBitmapEx);
+ ImplWriteIDAT();
+ }
+ aMask.ReleaseAccess(mpMaskAccess);
+ mpMaskAccess = nullptr;
+ }
+ else
+ {
+ mbStatus = false;
+ }
+ }
+ else
+ {
+ Bitmap aMask(aBitmapEx.GetMask());
+ mpMaskAccess = aMask.AcquireReadAccess();
+ if (mpMaskAccess)
+ {
+ if (ImplWriteHeader())
+ {
+ ImplWritepHYs(aBitmapEx);
+ ImplWriteIDAT();
+ }
+ Bitmap::ReleaseAccess(mpMaskAccess);
+ mpMaskAccess = nullptr;
+ }
+ else
+ {
+ mbStatus = false;
+ }
+ }
+ mpAccess.reset();
+ }
+ else
+ {
+ mbStatus = false;
+ }
+ }
+ }
+ else
+ {
+ mpAccess = Bitmap::ScopedReadAccess(aBmp); // palette + RGB without alphachannel
+ if (mpAccess)
+ {
+ if (ImplWriteHeader())
+ {
+ ImplWritepHYs(aBitmapEx);
+ if (mpAccess->HasPalette())
+ ImplWritePalette();
+
+ ImplWriteIDAT();
+ }
+ mpAccess.reset();
+ }
+ else
+ {
+ mbStatus = false;
+ }
+ }
+
+ if (mbStatus)
+ {
+ ImplOpenChunk(PNGCHUNK_IEND); // create an IEND chunk
+ }
+ }
+}
+
+bool PNGWriterImpl::Write(SvStream& rOStm)
+{
+ /* png signature is always an array of 8 bytes */
+ SvStreamEndian nOldMode = rOStm.GetEndian();
+ rOStm.SetEndian(SvStreamEndian::BIG);
+ rOStm.WriteUInt32(0x89504e47);
+ rOStm.WriteUInt32(0x0d0a1a0a);
+
+ for (auto const& chunk : maChunkSeq)
+ {
+ sal_uInt32 nType = chunk.nType;
+ #if defined(__LITTLEENDIAN) || defined(OSL_LITENDIAN)
+ nType = OSL_SWAPDWORD(nType);
+ #endif
+ sal_uInt32 nCRC = rtl_crc32(0, &nType, 4);
+ sal_uInt32 nDataSize = chunk.aData.size();
+ if (nDataSize)
+ nCRC = rtl_crc32(nCRC, chunk.aData.data(), nDataSize);
+ rOStm.WriteUInt32(nDataSize);
+ rOStm.WriteUInt32(chunk.nType);
+ if (nDataSize)
+ rOStm.WriteBytes(chunk.aData.data(), nDataSize);
+ rOStm.WriteUInt32(nCRC);
+ }
+ rOStm.SetEndian(nOldMode);
+ return mbStatus;
+}
+
+
+bool PNGWriterImpl::ImplWriteHeader()
+{
+ ImplOpenChunk(PNGCHUNK_IHDR);
+ mnWidth = mpAccess->Width();
+ ImplWriteChunk(sal_uInt32(mnWidth));
+ mnHeight = mpAccess->Height();
+ ImplWriteChunk(sal_uInt32(mnHeight));
+
+ if (mnWidth && mnHeight && mnBitsPerPixel && mbStatus)
+ {
+ sal_uInt8 nBitDepth = mnBitsPerPixel;
+ if (mnBitsPerPixel <= 8)
+ mnFilterType = 0;
+ else
+ mnFilterType = 4;
+
+ sal_uInt8 nColorType = 2; // colortype:
+
+ // bit 0 -> palette is used
+ if (mpAccess->HasPalette()) // bit 1 -> color is used
+ nColorType |= 1; // bit 2 -> alpha channel is used
+ else
+ nBitDepth /= 3;
+
+ if (mpMaskAccess)
+ nColorType |= 4;
+
+ ImplWriteChunk(nBitDepth);
+ ImplWriteChunk(nColorType); // colortype
+ ImplWriteChunk(static_cast<sal_uInt8>(0)); // compression type
+ ImplWriteChunk(static_cast<sal_uInt8>(0)); // filter type - is not supported in this version
+ ImplWriteChunk(static_cast<sal_uInt8>(mnInterlaced)); // interlace type
+ }
+ else
+ {
+ mbStatus = false;
+ }
+ return mbStatus;
+}
+
+void PNGWriterImpl::ImplWritePalette()
+{
+ const sal_uLong nCount = mpAccess->GetPaletteEntryCount();
+ std::unique_ptr<sal_uInt8[]> pTempBuf(new sal_uInt8[nCount * 3]);
+ sal_uInt8* pTmp = pTempBuf.get();
+
+ ImplOpenChunk(PNGCHUNK_PLTE);
+
+ for ( sal_uLong i = 0; i < nCount; i++ )
+ {
+ const BitmapColor& rColor = mpAccess->GetPaletteColor(i);
+ *pTmp++ = rColor.GetRed();
+ *pTmp++ = rColor.GetGreen();
+ *pTmp++ = rColor.GetBlue();
+ }
+ ImplWriteChunk(pTempBuf.get(), nCount * 3);
+}
+
+void PNGWriterImpl::ImplWriteTransparent()
+{
+ const sal_uLong nTransIndex = mpAccess->GetBestPaletteIndex(BMP_COL_TRANS);
+
+ ImplOpenChunk(PNGCHUNK_tRNS);
+
+ for (sal_uLong n = 0; n <= nTransIndex; n++)
+ {
+ ImplWriteChunk((nTransIndex == n) ? static_cast<sal_uInt8>(0x0) : static_cast<sal_uInt8>(0xff));
+ }
+}
+
+void PNGWriterImpl::ImplWritepHYs(const BitmapEx& rBmpEx)
+{
+ if (rBmpEx.GetPrefMapMode().GetMapUnit() == MapUnit::Map100thMM)
+ {
+ Size aPrefSize(rBmpEx.GetPrefSize());
+
+ if (aPrefSize.Width() && aPrefSize.Height() && mnWidth && mnHeight)
+ {
+ ImplOpenChunk(PNGCHUNK_pHYs);
+ sal_uInt32 nPrefSizeX = static_cast<sal_uInt32>(100000.0 / (static_cast<double>(aPrefSize.Width()) / mnWidth) + 0.5);
+ sal_uInt32 nPrefSizeY = static_cast<sal_uInt32>(100000.0 / (static_cast<double>(aPrefSize.Height()) / mnHeight) + 0.5);
+ ImplWriteChunk(nPrefSizeX);
+ ImplWriteChunk(nPrefSizeY);
+ ImplWriteChunk(sal_uInt8(1)); // nMapUnit
+ }
+ }
+}
+
+void PNGWriterImpl::ImplWriteIDAT()
+{
+ mnDeflateInSize = mnBitsPerPixel;
+
+ if (mpMaskAccess)
+ mnDeflateInSize += 8;
+
+ mnBBP = (mnDeflateInSize + 7) >> 3;
+
+ mnDeflateInSize = mnBBP * mnWidth + 1;
+
+ mpDeflateInBuf.reset(new sal_uInt8[mnDeflateInSize]);
+
+ if (mnFilterType) // using filter type 4 we need memory for the scanline 3 times
+ {
+ mpPreviousScan.reset(new sal_uInt8[mnDeflateInSize]);
+ mpCurrentScan.reset(new sal_uInt8[mnDeflateInSize]);
+ ImplClearFirstScanline();
+ }
+ mpZCodec.BeginCompression(mnCompLevel);
+ SvMemoryStream aOStm;
+ if (mnInterlaced == 0)
+ {
+ for (sal_uLong nY = 0; nY < mnHeight; nY++)
+ {
+ mpZCodec.Write(aOStm, mpDeflateInBuf.get(), ImplGetFilter(nY));
+ }
+ }
+ else
+ {
+ // interlace mode
+ sal_uLong nY;
+ for (nY = 0; nY < mnHeight; nY += 8) // pass 1
+ {
+ mpZCodec.Write(aOStm, mpDeflateInBuf.get(), ImplGetFilter(nY, 0, 8));
+ }
+ ImplClearFirstScanline();
+
+ for (nY = 0; nY < mnHeight; nY += 8) // pass 2
+ {
+ mpZCodec.Write(aOStm, mpDeflateInBuf.get(), ImplGetFilter(nY, 4, 8));
+ }
+ ImplClearFirstScanline();
+
+ if (mnHeight >= 5) // pass 3
+ {
+ for (nY = 4; nY < mnHeight; nY += 8)
+ {
+ mpZCodec.Write(aOStm, mpDeflateInBuf.get(), ImplGetFilter(nY, 0, 4));
+ }
+ ImplClearFirstScanline();
+ }
+
+ for (nY = 0; nY < mnHeight; nY += 4) // pass 4
+ {
+ mpZCodec.Write(aOStm, mpDeflateInBuf.get(), ImplGetFilter(nY, 2, 4));
+ }
+ ImplClearFirstScanline();
+
+ if (mnHeight >= 3) // pass 5
+ {
+ for (nY = 2; nY < mnHeight; nY += 4)
+ {
+ mpZCodec.Write(aOStm, mpDeflateInBuf.get(), ImplGetFilter(nY, 0, 2));
+ }
+ ImplClearFirstScanline();
+ }
+
+ for (nY = 0; nY < mnHeight; nY += 2) // pass 6
+ {
+ mpZCodec.Write(aOStm, mpDeflateInBuf.get(), ImplGetFilter(nY, 1, 2));
+ }
+ ImplClearFirstScanline();
+
+ if (mnHeight >= 2) // pass 7
+ {
+ for (nY = 1; nY < mnHeight; nY += 2)
+ {
+ mpZCodec.Write(aOStm, mpDeflateInBuf.get(), ImplGetFilter (nY));
+ }
+ }
+ }
+ mpZCodec.EndCompression();
+
+ if (mnFilterType) // using filter type 4 we need memory for the scanline 3 times
+ {
+ mpCurrentScan.reset();
+ mpPreviousScan.reset();
+ }
+ mpDeflateInBuf.reset();
+
+ sal_uInt32 nIDATSize = aOStm.Tell();
+ sal_uInt32 nBytes, nBytesToWrite = nIDATSize;
+ while(nBytesToWrite)
+ {
+ nBytes = nBytesToWrite <= mnMaxChunkSize ? nBytesToWrite : mnMaxChunkSize;
+ ImplOpenChunk(PNGCHUNK_IDAT);
+ ImplWriteChunk(const_cast<unsigned char *>(static_cast<unsigned char const *>(aOStm.GetData())) + (nIDATSize - nBytesToWrite), nBytes);
+ nBytesToWrite -= nBytes;
+ }
+}
+
+// ImplGetFilter writes the complete Scanline (nY) - in interlace mode the parameter nXStart and nXAdd
+// appends to the currently used pass
+// the complete size of scanline will be returned - in interlace mode zero is possible!
+
+sal_uLong PNGWriterImpl::ImplGetFilter (sal_uLong nY, sal_uLong nXStart, sal_uLong nXAdd)
+{
+ sal_uInt8* pDest;
+
+ if (mnFilterType)
+ pDest = mpCurrentScan.get();
+ else
+ pDest = mpDeflateInBuf.get();
+
+ if (nXStart < mnWidth)
+ {
+ *pDest++ = mnFilterType; // in this version the filter type is either 0 or 4
+
+ if (mpAccess->HasPalette()) // alphachannel is not allowed by pictures including palette entries
+ {
+ switch (mnBitsPerPixel)
+ {
+ case 1:
+ {
+ Scanline pScanline = mpAccess->GetScanline( nY );
+ sal_uLong nX, nXIndex;
+ for (nX = nXStart, nXIndex = 0; nX < mnWidth; nX += nXAdd, nXIndex++)
+ {
+ sal_uLong nShift = (nXIndex & 7) ^ 7;
+ if (nShift == 7)
+ *pDest = mpAccess->GetIndexFromData(pScanline, nX) << nShift;
+ else if (nShift == 0)
+ *pDest++ |= mpAccess->GetIndexFromData(pScanline, nX) << nShift;
+ else
+ *pDest |= mpAccess->GetIndexFromData(pScanline, nX) << nShift;
+ }
+ if ( (nXIndex & 7) != 0 )
+ pDest++; // byte is not completely used, so the bufferpointer is to correct
+ }
+ break;
+
+ case 4:
+ {
+ Scanline pScanline = mpAccess->GetScanline( nY );
+ sal_uLong nX, nXIndex;
+ for (nX = nXStart, nXIndex = 0; nX < mnWidth; nX += nXAdd, nXIndex++)
+ {
+ if(nXIndex & 1)
+ *pDest++ |= mpAccess->GetIndexFromData(pScanline, nX);
+ else
+ *pDest = mpAccess->GetIndexFromData(pScanline, nX) << 4;
+ }
+ if (nXIndex & 1)
+ pDest++;
+ }
+ break;
+
+ case 8:
+ {
+ Scanline pScanline = mpAccess->GetScanline( nY );
+ for (sal_uLong nX = nXStart; nX < mnWidth; nX += nXAdd)
+ {
+ *pDest++ = mpAccess->GetIndexFromData( pScanline, nX );
+ }
+ }
+ break;
+
+ default :
+ mbStatus = false;
+ break;
+ }
+ }
+ else
+ {
+ if (mpMaskAccess) // mpMaskAccess != NULL -> alphachannel is to create
+ {
+ if (mbTrueAlpha)
+ {
+ Scanline pScanline = mpAccess->GetScanline( nY );
+ Scanline pScanlineMask = mpMaskAccess->GetScanline( nY );
+ for (sal_uLong nX = nXStart; nX < mnWidth; nX += nXAdd)
+ {
+ const BitmapColor& rColor = mpAccess->GetPixelFromData(pScanline, nX);
+ *pDest++ = rColor.GetRed();
+ *pDest++ = rColor.GetGreen();
+ *pDest++ = rColor.GetBlue();
+ *pDest++ = 255 - mpMaskAccess->GetIndexFromData(pScanlineMask, nX);
+ }
+ }
+ else
+ {
+ const BitmapColor aTrans(mpMaskAccess->GetBestMatchingColor(COL_WHITE));
+ Scanline pScanline = mpAccess->GetScanline( nY );
+ Scanline pScanlineMask = mpMaskAccess->GetScanline( nY );
+
+ for (sal_uLong nX = nXStart; nX < mnWidth; nX += nXAdd)
+ {
+ const BitmapColor& rColor = mpAccess->GetPixelFromData(pScanline, nX);
+ *pDest++ = rColor.GetRed();
+ *pDest++ = rColor.GetGreen();
+ *pDest++ = rColor.GetBlue();
+
+ if(mpMaskAccess->GetPixelFromData(pScanlineMask, nX) == aTrans)
+ *pDest++ = 0;
+ else
+ *pDest++ = 0xff;
+ }
+ }
+ }
+ else
+ {
+ Scanline pScanline = mpAccess->GetScanline( nY );
+ for (sal_uLong nX = nXStart; nX < mnWidth; nX += nXAdd)
+ {
+ const BitmapColor& rColor = mpAccess->GetPixelFromData(pScanline, nX);
+ *pDest++ = rColor.GetRed();
+ *pDest++ = rColor.GetGreen();
+ *pDest++ = rColor.GetBlue();
+ }
+ }
+ }
+ }
+ // filter type4 ( PAETH ) will be used only for 24bit graphics
+ if (mnFilterType)
+ {
+ mnDeflateInSize = pDest - mpCurrentScan.get();
+ pDest = mpDeflateInBuf.get();
+ *pDest++ = 4; // filter type
+
+ sal_uInt8* p1 = mpCurrentScan.get() + 1; // Current Pixel
+ sal_uInt8* p2 = p1 - mnBBP; // left pixel
+ sal_uInt8* p3 = mpPreviousScan.get(); // upper pixel
+ sal_uInt8* p4 = p3 - mnBBP; // upperleft Pixel;
+
+ while (pDest < mpDeflateInBuf.get() + mnDeflateInSize)
+ {
+ sal_uLong nb = *p3++;
+ sal_uLong na, nc;
+ if (p2 >= mpCurrentScan.get() + 1)
+ {
+ na = *p2;
+ nc = *p4;
+ }
+ else
+ {
+ na = nc = 0;
+ }
+
+ long np = na + nb - nc;
+ long npa = np - na;
+ long npb = np - nb;
+ long npc = np - nc;
+
+ if (npa < 0)
+ npa =-npa;
+ if (npb < 0)
+ npb =-npb;
+ if (npc < 0)
+ npc =-npc;
+
+ if (npa <= npb && npa <= npc)
+ *pDest++ = *p1++ - static_cast<sal_uInt8>(na);
+ else if ( npb <= npc )
+ *pDest++ = *p1++ - static_cast<sal_uInt8>(nb);
+ else
+ *pDest++ = *p1++ - static_cast<sal_uInt8>(nc);
+
+ p4++;
+ p2++;
+ }
+ for (long i = 0; i < static_cast<long>(mnDeflateInSize - 1); i++)
+ {
+ mpPreviousScan[i] = mpCurrentScan[i + 1];
+ }
+ }
+ else
+ {
+ mnDeflateInSize = pDest - mpDeflateInBuf.get();
+ }
+ return mnDeflateInSize;
+}
+
+void PNGWriterImpl::ImplClearFirstScanline()
+{
+ if (mnFilterType)
+ memset(mpPreviousScan.get(), 0, mnDeflateInSize);
+}
+
+void PNGWriterImpl::ImplOpenChunk (sal_uLong nChunkType)
+{
+ maChunkSeq.emplace_back();
+ maChunkSeq.back().nType = nChunkType;
+}
+
+void PNGWriterImpl::ImplWriteChunk (sal_uInt8 nSource)
+{
+ maChunkSeq.back().aData.push_back(nSource);
+}
+
+void PNGWriterImpl::ImplWriteChunk (sal_uInt32 nSource)
+{
+ vcl::PNGWriter::ChunkData& rChunkData = maChunkSeq.back();
+ rChunkData.aData.push_back(static_cast<sal_uInt8>(nSource >> 24));
+ rChunkData.aData.push_back(static_cast<sal_uInt8>(nSource >> 16));
+ rChunkData.aData.push_back(static_cast<sal_uInt8>(nSource >> 8));
+ rChunkData.aData.push_back(static_cast<sal_uInt8>(nSource));
+}
+
+void PNGWriterImpl::ImplWriteChunk (unsigned char const * pSource, sal_uInt32 nDatSize)
+{
+ if (nDatSize)
+ {
+ vcl::PNGWriter::ChunkData& rChunkData = maChunkSeq.back();
+ sal_uInt32 nSize = rChunkData.aData.size();
+ rChunkData.aData.resize(nSize + nDatSize);
+ memcpy(&rChunkData.aData[nSize], pSource, nDatSize);
+ }
+}
+
+PNGWriter::PNGWriter(const BitmapEx& rBmpEx,
+ const css::uno::Sequence<css::beans::PropertyValue>* pFilterData)
+ : mpImpl(new vcl::PNGWriterImpl(rBmpEx, pFilterData))
+{
+}
+
+PNGWriter::~PNGWriter()
+{
+}
+
+bool PNGWriter::Write(SvStream& rStream)
+{
+ return mpImpl->Write(rStream);
+}
+
+std::vector<vcl::PNGWriter::ChunkData>& PNGWriter::GetChunks()
+{
+ return mpImpl->GetChunks();
+}
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/wmf/emfwr.cxx b/vcl/source/filter/wmf/emfwr.cxx
new file mode 100644
index 000000000..020499967
--- /dev/null
+++ b/vcl/source/filter/wmf/emfwr.cxx
@@ -0,0 +1,1483 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+
+#include <algorithm>
+
+#include "emfwr.hxx"
+#include <tools/helpers.hxx>
+#include <tools/fract.hxx>
+#include <tools/stream.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <vcl/lineinfo.hxx>
+#include <vcl/dibtools.hxx>
+#include <vcl/metaact.hxx>
+#include <memory>
+
+#define WIN_EMR_POLYGON 3
+#define WIN_EMR_POLYLINE 4
+#define WIN_EMR_POLYBEZIERTO 5
+#define WIN_EMR_POLYLINETO 6
+#define WIN_EMR_POLYPOLYGON 8
+#define WIN_EMR_SETWINDOWEXTEX 9
+#define WIN_EMR_SETWINDOWORGEX 10
+#define WIN_EMR_SETVIEWPORTEXTEX 11
+#define WIN_EMR_SETVIEWPORTORGEX 12
+#define WIN_EMR_EOF 14
+#define WIN_EMR_SETPIXELV 15
+#define WIN_EMR_SETMAPMODE 17
+#define WIN_EMR_SETBKMODE 18
+#define WIN_EMR_SETROP2 20
+#define WIN_EMR_SETTEXTALIGN 22
+#define WIN_EMR_SETTEXTCOLOR 24
+#define WIN_EMR_MOVETOEX 27
+#define WIN_EMR_INTERSECTCLIPRECT 30
+#define WIN_EMR_SAVEDC 33
+#define WIN_EMR_RESTOREDC 34
+#define WIN_EMR_SELECTOBJECT 37
+#define WIN_EMR_CREATEPEN 38
+#define WIN_EMR_CREATEBRUSHINDIRECT 39
+#define WIN_EMR_DELETEOBJECT 40
+#define WIN_EMR_ELLIPSE 42
+#define WIN_EMR_RECTANGLE 43
+#define WIN_EMR_ROUNDRECT 44
+#define WIN_EMR_LINETO 54
+#define WIN_EMR_BEGINPATH 59
+#define WIN_EMR_ENDPATH 60
+#define WIN_EMR_CLOSEFIGURE 61
+#define WIN_EMR_FILLPATH 62
+#define WIN_EMR_STROKEPATH 64
+
+#define WIN_EMR_GDICOMMENT 70
+#define WIN_EMR_STRETCHDIBITS 81
+#define WIN_EMR_EXTCREATEFONTINDIRECTW 82
+#define WIN_EMR_EXTTEXTOUTW 84
+
+#define WIN_SRCCOPY 0x00CC0020L
+#define WIN_SRCPAINT 0x00EE0086L
+#define WIN_SRCAND 0x008800C6L
+#define WIN_SRCINVERT 0x00660046L
+#define WIN_EMR_COMMENT_EMFPLUS 0x2B464D45L
+
+#define HANDLE_INVALID 0xffffffff
+#define MAXHANDLES 65000
+
+#define LINE_SELECT 0x00000001
+#define FILL_SELECT 0x00000002
+#define TEXT_SELECT 0x00000004
+
+/* Text Alignment Options */
+#define TA_RIGHT 2
+
+#define TA_TOP 0
+#define TA_BOTTOM 8
+#define TA_BASELINE 24
+#define TA_RTLREADING 256
+
+#define MM_ANISOTROPIC 8
+
+enum class EmfPlusRecordType
+{
+ Header = 0x4001,
+ EndOfFile = 0x4002,
+ GetDC = 0x4004,
+ FillPolygon = 0x400C,
+ SetAntiAliasMode = 0x401E,
+ SetInterpolationMode = 0x4021,
+ SetPixelOffsetMode = 0x4022,
+ SetCompositingQuality = 0x4024
+};
+
+void EMFWriter::ImplBeginCommentRecord( sal_Int32 nCommentType )
+{
+ ImplBeginRecord( WIN_EMR_GDICOMMENT );
+ m_rStm.SeekRel( 4 );
+ m_rStm.WriteInt32( nCommentType );
+}
+
+void EMFWriter::ImplEndCommentRecord()
+{
+ if( mbRecordOpen )
+ {
+ sal_Int32 nActPos = m_rStm.Tell();
+ m_rStm.Seek( mnRecordPos + 8 );
+ m_rStm.WriteUInt32( nActPos - mnRecordPos - 0xc );
+ m_rStm.Seek( nActPos );
+ }
+ ImplEndRecord();
+}
+
+void EMFWriter::ImplBeginPlusRecord( EmfPlusRecordType nType, sal_uInt16 nFlags )
+{
+ SAL_WARN_IF( mbRecordPlusOpen, "vcl", "Another EMF+ record is already opened!" );
+
+ if( !mbRecordPlusOpen )
+ {
+ mbRecordPlusOpen = true;
+ mnRecordPlusPos = m_rStm.Tell();
+
+ m_rStm.WriteUInt16( static_cast<sal_uInt16>(nType) ).WriteUInt16( nFlags );
+ m_rStm.SeekRel( 8 );
+ }
+}
+
+void EMFWriter::ImplEndPlusRecord()
+{
+ SAL_WARN_IF( !mbRecordPlusOpen, "vcl", "EMF+ Record was not opened!" );
+
+ if( mbRecordPlusOpen )
+ {
+ sal_Int32 nActPos = m_rStm.Tell();
+ sal_Int32 nSize = nActPos - mnRecordPlusPos;
+ m_rStm.Seek( mnRecordPlusPos + 4 );
+ m_rStm.WriteUInt32( nSize ) // Size
+ .WriteUInt32( nSize - 0xc ); // Data Size
+ m_rStm.Seek( nActPos );
+ mbRecordPlusOpen = false;
+ }
+}
+
+void EMFWriter::ImplPlusRecord( EmfPlusRecordType nType, sal_uInt16 nFlags )
+{
+ ImplBeginPlusRecord( nType, nFlags );
+ ImplEndPlusRecord();
+}
+
+void EMFWriter::WriteEMFPlusHeader( const Size &rMtfSizePix, const Size &rMtfSizeLog )
+{
+ ImplBeginCommentRecord( WIN_EMR_COMMENT_EMFPLUS );
+
+ sal_Int32 nDPIX = rMtfSizePix.Width()*25;
+ sal_Int32 nDivX = rMtfSizeLog.Width()/100;
+ if (nDivX)
+ nDPIX /= nDivX; // DPI X
+
+ sal_Int32 nDPIY = rMtfSizePix.Height()*25;
+ sal_Int32 nDivY = rMtfSizeLog.Height()/100;
+ if (nDivY)
+ nDPIY /= nDivY; // DPI Y
+
+ m_rStm.WriteInt16( sal_Int16(EmfPlusRecordType::Header) );
+ m_rStm.WriteInt16( 0x01 ) // Flags - Dual Mode // TODO: Check this
+ .WriteInt32( 0x1C ) // Size
+ .WriteInt32( 0x10 ) // Data Size
+ .WriteInt32( 0xdbc01002 ) // (lower 12bits) 1-> v1 2-> v1.1 // TODO: Check this
+ .WriteInt32( 0x01 ) // Video display
+ .WriteInt32( nDPIX )
+ .WriteInt32( nDPIY );
+ ImplEndCommentRecord();
+
+ // Write more properties
+ ImplBeginCommentRecord( WIN_EMR_COMMENT_EMFPLUS );
+ ImplPlusRecord( EmfPlusRecordType::SetPixelOffsetMode, 0x0 );
+ ImplPlusRecord( EmfPlusRecordType::SetAntiAliasMode, 0x09 ); // TODO: Check actual values for AntiAlias
+ ImplPlusRecord( EmfPlusRecordType::SetCompositingQuality, 0x0100 ); // Default Quality
+ ImplPlusRecord( EmfPlusRecordType::SetInterpolationMode, 0x00 ); // Default
+ ImplPlusRecord( EmfPlusRecordType::GetDC, 0x00 );
+ ImplEndCommentRecord();
+}
+
+void EMFWriter::ImplWritePlusEOF()
+{
+ ImplBeginCommentRecord( WIN_EMR_COMMENT_EMFPLUS );
+ ImplPlusRecord( EmfPlusRecordType::EndOfFile, 0x0 );
+ ImplEndCommentRecord();
+}
+
+void EMFWriter::ImplWritePlusColor( const Color& rColor, sal_uInt32 nTrans )
+{
+ sal_uInt32 nAlpha = ((100-nTrans)*0xFF)/100;
+ sal_uInt32 nCol = rColor.GetBlue();
+
+ nCol |= static_cast<sal_uInt32>(rColor.GetGreen()) << 8;
+ nCol |= static_cast<sal_uInt32>(rColor.GetRed()) << 16;
+ nCol |= ( nAlpha << 24 );
+ m_rStm.WriteUInt32( nCol );
+}
+
+void EMFWriter::ImplWritePlusPoint( const Point& rPoint )
+{
+ // Convert to pixels
+ const Point aPoint(maVDev->LogicToPixel( rPoint, maDestMapMode ));
+ m_rStm.WriteUInt16( aPoint.X() ).WriteUInt16( aPoint.Y() );
+}
+
+void EMFWriter::ImplWritePlusFillPolygonRecord( const tools::Polygon& rPoly, sal_uInt32 nTrans )
+{
+ ImplBeginCommentRecord( WIN_EMR_COMMENT_EMFPLUS );
+ if( rPoly.GetSize() )
+ {
+ ImplBeginPlusRecord( EmfPlusRecordType::FillPolygon, 0xC000 ); // Sets the color as well
+ ImplWritePlusColor( maVDev->GetFillColor(), nTrans );
+ m_rStm.WriteUInt32( rPoly.GetSize() );
+ for( sal_uInt16 i = 0; i < rPoly.GetSize(); i++ )
+ ImplWritePlusPoint( rPoly[ i ] );
+ ImplEndPlusRecord();
+ }
+ ImplEndCommentRecord();
+}
+
+bool EMFWriter::WriteEMF(const GDIMetaFile& rMtf)
+{
+ const sal_uLong 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_uLong nEndPos = m_rStm.Tell(); m_rStm.Seek( nHeaderPos );
+
+ m_rStm.WriteUInt32( 0x00000001 ).WriteUInt32( 108 ) //use [MS-EMF 2.2.11] HeaderExtension2 Object
+ .WriteInt32( 0 ).WriteInt32( 0 ).WriteInt32( aMtfSizePix.Width() - 1 ).WriteInt32( aMtfSizePix.Height() - 1 )
+ .WriteInt32( 0 ).WriteInt32( 0 ).WriteInt32( aMtfSizeLog.Width() - 1 ).WriteInt32( aMtfSizeLog.Height() - 1 )
+ .WriteUInt32( 0x464d4520 ).WriteUInt32( 0x10000 ).WriteUInt32( nEndPos - nHeaderPos )
+ .WriteUInt32( mnRecordCount ).WriteUInt16( mnHandleCount + 1 ).WriteUInt16( 0 ).WriteUInt32( 0 ).WriteUInt32( 0 ).WriteUInt32( 0 )
+ .WriteInt32( aMtfSizePix.Width() ).WriteInt32( aMtfSizePix.Height() )
+ .WriteInt32( aMtfSizeLog.Width() / 100 ).WriteInt32( aMtfSizeLog.Height() / 100 )
+ .WriteUInt32( 0 ).WriteUInt32( 0 ).WriteUInt32( 0 )
+ .WriteInt32( aMtfSizeLog.Width() * 10 ).WriteInt32( aMtfSizeLog.Height() * 10 ); //use [MS-EMF 2.2.11] HeaderExtension2 Object
+
+ m_rStm.Seek( nEndPos );
+ mHandlesUsed.clear();
+
+ return( m_rStm.GetError() == ERRCODE_NONE );
+}
+
+sal_uLong EMFWriter::ImplAcquireHandle()
+{
+ sal_uLong nHandle = HANDLE_INVALID;
+
+ for( sal_uLong i = 0; i < mHandlesUsed.size() && ( HANDLE_INVALID == nHandle ); i++ )
+ {
+ if( !mHandlesUsed[ i ] )
+ {
+ mHandlesUsed[ i ] = true;
+
+ if( ( nHandle = i ) == mnHandleCount )
+ mnHandleCount++;
+ }
+ }
+
+ SAL_WARN_IF( nHandle == HANDLE_INVALID, "vcl", "No more handles available" );
+ return( nHandle != HANDLE_INVALID ? nHandle + 1 : HANDLE_INVALID );
+}
+
+void EMFWriter::ImplReleaseHandle( sal_uLong nHandle )
+{
+ SAL_WARN_IF( !nHandle || ( nHandle >= mHandlesUsed.size() ), "vcl", "Handle out of range" );
+ mHandlesUsed[ nHandle - 1 ] = false;
+}
+
+void EMFWriter::ImplBeginRecord( sal_uInt32 nType )
+{
+ SAL_WARN_IF( mbRecordOpen, "vcl", "Another record is already opened!" );
+
+ if( !mbRecordOpen )
+ {
+ mbRecordOpen = true;
+ mnRecordPos = m_rStm.Tell();
+
+ m_rStm.WriteUInt32( nType );
+ m_rStm.SeekRel( 4 );
+ }
+}
+
+void EMFWriter::ImplEndRecord()
+{
+ SAL_WARN_IF( !mbRecordOpen, "vcl", "Record was not opened!" );
+
+ if( !mbRecordOpen )
+ return;
+
+ sal_Int32 nFillBytes, nActPos = m_rStm.Tell();
+ m_rStm.Seek( mnRecordPos + 4 );
+ nFillBytes = nActPos - mnRecordPos;
+ nFillBytes += 3; // each record has to be dword aligned
+ nFillBytes ^= 3;
+ nFillBytes &= 3;
+ m_rStm.WriteUInt32( ( nActPos - mnRecordPos ) + nFillBytes );
+ m_rStm.Seek( nActPos );
+ while( nFillBytes-- )
+ m_rStm.WriteUChar( 0 );
+ mnRecordCount++;
+ mbRecordOpen = false;
+
+}
+
+bool EMFWriter::ImplPrepareHandleSelect( sal_uInt32& rHandle, sal_uLong nSelectType )
+{
+ if( rHandle != HANDLE_INVALID )
+ {
+ sal_uInt32 nStockObject = 0x80000000;
+
+ if( LINE_SELECT == nSelectType )
+ nStockObject |= 0x00000007;
+ else if( FILL_SELECT == nSelectType )
+ nStockObject |= 0x00000001;
+ else if( TEXT_SELECT == nSelectType )
+ nStockObject |= 0x0000000a;
+
+ // select stock object first
+ ImplBeginRecord( WIN_EMR_SELECTOBJECT );
+ m_rStm.WriteUInt32( nStockObject );
+ ImplEndRecord();
+
+ // destroy handle of created object
+ ImplBeginRecord( WIN_EMR_DELETEOBJECT );
+ m_rStm.WriteUInt32( rHandle );
+ ImplEndRecord();
+
+ // mark handle as free
+ ImplReleaseHandle( rHandle );
+ }
+
+ rHandle = ImplAcquireHandle();
+
+ return( HANDLE_INVALID != rHandle );
+}
+
+void EMFWriter::ImplCheckLineAttr()
+{
+ if( mbLineChanged && ImplPrepareHandleSelect( mnLineHandle, LINE_SELECT ) )
+ {
+ 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 ) )
+ {
+ 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;
+
+ ImplBeginRecord( WIN_EMR_EXTCREATEFONTINDIRECTW );
+ m_rStm.WriteUInt32( mnTextHandle );
+ ImplWriteExtent( -rFont.GetFontSize().Height() );
+ ImplWriteExtent( rFont.GetFontSize().Width() );
+ m_rStm.WriteInt32( rFont.GetOrientation() ).WriteInt32( rFont.GetOrientation() );
+
+ 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( 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() )
+ {
+ if( rPoly.HasFlags() )
+ ImplWritePath( rPoly, bClose );
+ else
+ {
+ if( bClose )
+ ImplCheckFillAttr();
+
+ ImplCheckLineAttr();
+
+ ImplBeginRecord( bClose ? WIN_EMR_POLYGON : WIN_EMR_POLYLINE );
+ ImplWriteRect( rPoly.GetBoundRect() );
+ m_rStm.WriteUInt32( rPoly.GetSize() );
+
+ for( sal_uInt16 i = 0; i < rPoly.GetSize(); i++ )
+ ImplWritePoint( rPoly[ i ] );
+
+ ImplEndRecord();
+ }
+ }
+}
+
+void EMFWriter::ImplWritePolyPolygonRecord( const tools::PolyPolygon& rPolyPoly )
+{
+ sal_uInt16 n, i, nPolyCount = rPolyPoly.Count();
+
+ if( nPolyCount )
+ {
+ 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 )
+ 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_uLong nOffPos = m_rStm.Tell();
+ m_rStm.SeekRel( 16 );
+
+ m_rStm.WriteUInt32( 0 ).WriteInt32( ( RasterOp::Xor == maVDev->GetRasterOp() && WIN_SRCCOPY == nROP ) ? WIN_SRCINVERT : nROP );
+ ImplWriteSize( rSz );
+
+ WriteDIB(rBmp, aMemStm, true, false);
+
+ sal_uInt32 nDIBSize = aMemStm.Tell(), nHeaderSize, nCompression, nColsUsed, nPalCount, nImageSize;
+ sal_uInt16 nBitCount;
+
+ // get DIB parameters
+ aMemStm.Seek( 0 );
+ aMemStm.ReadUInt32( nHeaderSize );
+ aMemStm.SeekRel( 10 );
+ aMemStm.ReadUInt16( nBitCount ).ReadUInt32( nCompression ).ReadUInt32( nImageSize );
+ aMemStm.SeekRel( 8 );
+ aMemStm.ReadUInt32( nColsUsed );
+
+ if (nBitCount <= 8)
+ {
+ if (nColsUsed)
+ nPalCount = nColsUsed;
+ else
+ nPalCount = 1 << static_cast<sal_uInt32>(nBitCount);
+ }
+ else
+ {
+ if (nCompression == BITFIELDS)
+ nPalCount = 3;
+ else
+ nPalCount = 0;
+ }
+
+ sal_uInt32 nPalSize = nPalCount * 4;
+
+ m_rStm.WriteBytes( aMemStm.GetData(), nDIBSize );
+
+ const sal_uLong 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, const long* pDXArray, sal_uInt32 nWidth )
+{
+ sal_Int32 nLen = rText.getLength(), i;
+
+ if( !nLen )
+ return;
+
+ sal_uInt32 nNormWidth;
+ std::unique_ptr<long[]> pOwnArray;
+ long* pDX;
+
+ // get text sizes
+ if( pDXArray )
+ {
+ nNormWidth = maVDev->GetTextWidth( rText );
+ pDX = const_cast<long*>(pDXArray);
+ }
+ else
+ {
+ pOwnArray.reset(new long[ nLen ]);
+ nNormWidth = maVDev->GetTextArray( rText, pOwnArray.get() );
+ pDX = pOwnArray.get();
+ }
+
+ if( nLen > 1 )
+ {
+ nNormWidth = pDX[ nLen - 2 ] + maVDev->GetTextWidth( OUString(rText[ nLen - 1 ]) );
+
+ if( nWidth && nNormWidth && ( nWidth != nNormWidth ) )
+ {
+ const double fFactor = static_cast<double>(nWidth) / nNormWidth;
+
+ for( i = 0; i < ( nLen - 1 ); i++ )
+ pDX[ i ] = FRound( pDX[ 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())
+ {
+ basegfx::B2DPolyPolygon aLinePolyPolygon(rLinePolygon);
+ basegfx::B2DPolyPolygon aFillPolyPolygon;
+
+ rInfo.applyToB2DPolyPolygon(aLinePolyPolygon, aFillPolyPolygon);
+
+ if(aLinePolyPolygon.count())
+ {
+ for(auto const& rB2DPolygon : aLinePolyPolygon)
+ {
+ ImplWritePolygonRecord( tools::Polygon(rB2DPolygon), false );
+ }
+ }
+
+ if(aFillPolyPolygon.count())
+ {
+ const Color aOldLineColor(maVDev->GetLineColor());
+ const Color aOldFillColor(maVDev->GetFillColor());
+
+ maVDev->SetLineColor();
+ maVDev->SetFillColor(aOldLineColor);
+
+ for(auto const& rB2DPolygon : 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;
+
+ maVDev->AddGradientActions( pA->GetRect(), pA->GetGradient(), 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;
+ long nMoveX, nMoveY;
+
+ if( fScaleX != 1.0 || fScaleY != 1.0 )
+ {
+ aTmpMtf.Scale( fScaleX, fScaleY );
+ aSrcPt.setX( FRound( aSrcPt.X() * fScaleX ) );
+ aSrcPt.setY( FRound( aSrcPt.Y() * fScaleY ) );
+ }
+
+ nMoveX = aDestPt.X() - aSrcPt.X();
+ nMoveY = aDestPt.Y() - aSrcPt.Y();
+
+ if( nMoveX || nMoveY )
+ aTmpMtf.Move( nMoveX, nMoveY );
+
+ ImplCheckFillAttr();
+ ImplCheckLineAttr();
+ ImplCheckTextAttr();
+ ImplWrite( aTmpMtf );
+ }
+ break;
+
+ case MetaActionType::EPS:
+ {
+ const MetaEPSAction* pA = static_cast<const MetaEPSAction*>(pAction);
+ const GDIMetaFile& aSubstitute( pA->GetSubstitute() );
+
+ for( size_t i = 0, nCount = aSubstitute.GetActionSize(); i < nCount; i++ )
+ {
+ const MetaAction* pSubstAct = aSubstitute.GetAction( i );
+ if( pSubstAct->GetType() == MetaActionType::BMPSCALE )
+ {
+ maVDev->Push();
+ ImplBeginRecord( WIN_EMR_SAVEDC );
+ ImplEndRecord();
+
+ MapMode aMapMode( aSubstitute.GetPrefMapMode() );
+ Size aOutSize( OutputDevice::LogicToLogic( pA->GetSize(), maVDev->GetMapMode(), aMapMode ) );
+ aMapMode.SetScaleX( Fraction( aOutSize.Width(), aSubstitute.GetPrefSize().Width() ) );
+ aMapMode.SetScaleY( Fraction( aOutSize.Height(), aSubstitute.GetPrefSize().Height() ) );
+ aMapMode.SetOrigin( OutputDevice::LogicToLogic( pA->GetPoint(), maVDev->GetMapMode(), aMapMode ) );
+ maVDev->SetMapMode( aMapMode );
+ ImplWrite( aSubstitute );
+
+ maVDev->Pop();
+ ImplBeginRecord( WIN_EMR_RESTOREDC );
+ m_rStm.WriteInt32( -1 );
+ ImplEndRecord();
+ break;
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::BMP:
+ {
+ const MetaBmpAction* pA = static_cast<const MetaBmpAction *>(pAction);
+ ImplWriteBmpRecord( pA->GetBitmap(), pA->GetPoint(), maVDev->PixelToLogic( pA->GetBitmap().GetSizePixel() ), WIN_SRCCOPY );
+ }
+ break;
+
+ case MetaActionType::BMPSCALE:
+ {
+ const MetaBmpScaleAction* pA = static_cast<const MetaBmpScaleAction*>(pAction);
+ ImplWriteBmpRecord( pA->GetBitmap(), pA->GetPoint(), pA->GetSize(), WIN_SRCCOPY );
+ }
+ break;
+
+ case MetaActionType::BMPSCALEPART:
+ {
+ const MetaBmpScalePartAction* pA = static_cast<const MetaBmpScalePartAction*>(pAction);
+ Bitmap aTmp( pA->GetBitmap() );
+
+ if( aTmp.Crop( tools::Rectangle( pA->GetSrcPoint(), pA->GetSrcSize() ) ) )
+ ImplWriteBmpRecord( aTmp, pA->GetDestPoint(), pA->GetDestSize(), WIN_SRCCOPY );
+ }
+ break;
+
+ case MetaActionType::BMPEX:
+ {
+ const MetaBmpExAction* pA = static_cast<const MetaBmpExAction *>(pAction);
+ Bitmap aBmp( pA->GetBitmapEx().GetBitmap() );
+ Bitmap aMsk( pA->GetBitmapEx().GetMask() );
+
+ if( !!aMsk )
+ {
+ aBmp.Replace( aMsk, COL_WHITE );
+ aMsk.Invert();
+ ImplWriteBmpRecord( aMsk, pA->GetPoint(), maVDev->PixelToLogic( aMsk.GetSizePixel() ), WIN_SRCPAINT );
+ ImplWriteBmpRecord( aBmp, pA->GetPoint(), maVDev->PixelToLogic( aBmp.GetSizePixel() ), WIN_SRCAND );
+ }
+ else
+ ImplWriteBmpRecord( aBmp, pA->GetPoint(), aBmp.GetSizePixel(), WIN_SRCCOPY );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ {
+ const MetaBmpExScaleAction* pA = static_cast<const MetaBmpExScaleAction*>(pAction);
+ Bitmap aBmp( pA->GetBitmapEx().GetBitmap() );
+ Bitmap aMsk( pA->GetBitmapEx().GetMask() );
+
+ if( !!aMsk )
+ {
+ aBmp.Replace( aMsk, COL_WHITE );
+ aMsk.Invert();
+ ImplWriteBmpRecord( aMsk, pA->GetPoint(), pA->GetSize(), WIN_SRCPAINT );
+ ImplWriteBmpRecord( aBmp, pA->GetPoint(), pA->GetSize(), WIN_SRCAND );
+ }
+ else
+ ImplWriteBmpRecord( aBmp, pA->GetPoint(), pA->GetSize(), WIN_SRCCOPY );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ {
+ const MetaBmpExScalePartAction* pA = static_cast<const MetaBmpExScalePartAction*>(pAction);
+ BitmapEx aBmpEx( pA->GetBitmapEx() );
+ aBmpEx.Crop( tools::Rectangle( pA->GetSrcPoint(), pA->GetSrcSize() ) );
+ Bitmap aBmp( aBmpEx.GetBitmap() );
+ Bitmap aMsk( aBmpEx.GetMask() );
+
+ if( !!aMsk )
+ {
+ aBmp.Replace( aMsk, COL_WHITE );
+ aMsk.Invert();
+ ImplWriteBmpRecord( aMsk, pA->GetDestPoint(), pA->GetDestSize(), WIN_SRCPAINT );
+ ImplWriteBmpRecord( aBmp, pA->GetDestPoint(), pA->GetDestSize(), WIN_SRCAND );
+ }
+ else
+ ImplWriteBmpRecord( aBmp, pA->GetDestPoint(), pA->GetDestSize(), WIN_SRCCOPY );
+ }
+ break;
+
+ case MetaActionType::TEXT:
+ {
+ const MetaTextAction* pA = static_cast<const MetaTextAction*>(pAction);
+ const OUString aText = pA->GetText().copy( pA->GetIndex(), std::min(pA->GetText().getLength() - pA->GetIndex(), pA->GetLen()) );
+
+ ImplCheckTextAttr();
+ ImplWriteTextRecord( pA->GetPoint(), aText, nullptr, 0 );
+ }
+ break;
+
+ case MetaActionType::TEXTRECT:
+ {
+ const MetaTextRectAction* pA = static_cast<const MetaTextRectAction*>(pAction);
+ const OUString& aText( pA->GetText() );
+
+ ImplCheckTextAttr();
+ ImplWriteTextRecord( pA->GetRect().TopLeft(), aText, nullptr, 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, nullptr, 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:
+ {
+ ComplexTextLayoutFlags nLayoutMode = static_cast<const MetaLayoutModeAction*>(pAction)->GetLayoutMode();
+ mnHorTextAlign = 0;
+ if ((nLayoutMode & ComplexTextLayoutFlags::BiDiRtl) != ComplexTextLayoutFlags::Default)
+ {
+ mnHorTextAlign = TA_RIGHT | TA_RTLREADING;
+ }
+ if ((nLayoutMode & ComplexTextLayoutFlags::TextOriginRight) != ComplexTextLayoutFlags::Default)
+ mnHorTextAlign |= TA_RIGHT;
+ else if ((nLayoutMode & ComplexTextLayoutFlags::TextOriginLeft) != ComplexTextLayoutFlags::Default)
+ mnHorTextAlign &= ~TA_RIGHT;
+ break;
+ }
+
+ case MetaActionType::COMMENT:
+ {
+ MetaCommentAction const*const pCommentAction(
+ static_cast<MetaCommentAction const*>(pAction));
+ if (pCommentAction->GetComment() == "EMF_PLUS")
+ {
+ ImplBeginCommentRecord(WIN_EMR_COMMENT_EMFPLUS);
+ m_rStm.WriteBytes(pCommentAction->GetData(),
+ pCommentAction->GetDataSize());
+ ImplEndCommentRecord();
+ }
+ }
+ break;
+
+ case MetaActionType::MASK:
+ case MetaActionType::MASKSCALE:
+ case MetaActionType::MASKSCALEPART:
+ case MetaActionType::WALLPAPER:
+ case MetaActionType::TEXTLINE:
+ case MetaActionType::GRADIENTEX:
+ // Explicitly ignored cases
+ break;
+
+ default:
+ // TODO: Implement more cases as necessary. Let's not bother with a warning.
+ break;
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/wmf/emfwr.hxx b/vcl/source/filter/wmf/emfwr.hxx
new file mode 100644
index 000000000..e227010ac
--- /dev/null
+++ b/vcl/source/filter/wmf/emfwr.hxx
@@ -0,0 +1,114 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_FILTER_WMF_EMFWR_HXX
+#define INCLUDED_VCL_SOURCE_FILTER_WMF_EMFWR_HXX
+
+#include <vcl/gdimtf.hxx>
+#include <vcl/virdev.hxx>
+
+class LineInfo;
+namespace basegfx { class B2DPolygon; }
+enum class EmfPlusRecordType;
+
+class EMFWriter
+{
+private:
+
+ ScopedVclPtr<VirtualDevice> maVDev;
+ MapMode maDestMapMode;
+ SvStream& m_rStm;
+ std::vector<bool> mHandlesUsed;
+ sal_uLong mnHandleCount;
+ sal_uLong mnRecordCount;
+ sal_uLong mnRecordPos;
+ sal_uLong mnRecordPlusPos;
+ bool mbRecordOpen;
+ bool mbRecordPlusOpen;
+ bool mbLineChanged;
+ sal_uInt32 mnLineHandle;
+ bool mbFillChanged;
+ sal_uInt32 mnFillHandle;
+ bool mbTextChanged;
+ sal_uInt32 mnTextHandle;
+ sal_uInt32 mnHorTextAlign;
+
+ void ImplBeginRecord( sal_uInt32 nType );
+ void ImplEndRecord();
+ void ImplBeginPlusRecord( EmfPlusRecordType nType, sal_uInt16 nFlags );
+ void ImplEndPlusRecord();
+ void ImplPlusRecord( EmfPlusRecordType nType, sal_uInt16 nFlags );
+ void ImplBeginCommentRecord( sal_Int32 nCommentType );
+ void ImplEndCommentRecord();
+
+ sal_uLong ImplAcquireHandle();
+ void ImplReleaseHandle( sal_uLong nHandle );
+
+ bool ImplPrepareHandleSelect( sal_uInt32& rHandle, sal_uLong nSelectType );
+ void ImplCheckLineAttr();
+ void ImplCheckFillAttr();
+ void ImplCheckTextAttr();
+
+ void ImplWriteColor( const Color& rColor );
+ void ImplWriteRasterOp( RasterOp eRop );
+ void ImplWriteExtent( 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, const long* pDXArray, sal_uInt32 nWidth );
+
+ void Impl_handleLineInfoPolyPolygons(const LineInfo& rInfo, const basegfx::B2DPolygon& rLinePolygon);
+ void ImplWrite( const GDIMetaFile& rMtf );
+ void WriteEMFPlusHeader( const Size &rMtfSizePix, const Size &rMtfSizeLog );
+ void ImplWritePlusEOF();
+ void ImplWritePlusFillPolygonRecord( const tools::Polygon& rPoly, sal_uInt32 nTrans );
+ void ImplWritePlusColor( const Color& rColor, sal_uInt32 nTrans );
+ void ImplWritePlusPoint( const Point& rPoint );
+
+public:
+
+ explicit EMFWriter(SvStream &rStream)
+ : maVDev( VclPtr<VirtualDevice>::Create() )
+ , m_rStm(rStream)
+ , mnHandleCount(0)
+ , mnRecordCount(0)
+ , mnRecordPos(0)
+ , mnRecordPlusPos(0)
+ , mbRecordOpen(false)
+ , mbRecordPlusOpen(false)
+ , mbLineChanged(false)
+ , mnLineHandle(0)
+ , mbFillChanged(false)
+ , mnFillHandle(0)
+ , mbTextChanged(false)
+ , mnTextHandle(0)
+ , mnHorTextAlign(0)
+ {
+ }
+
+ bool WriteEMF(const GDIMetaFile& rMtf);
+};
+
+#endif // INCLUDED_VCL_SOURCE_FILTER_WMF_EMFWR_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/wmf/wmf.cxx b/vcl/source/filter/wmf/wmf.cxx
new file mode 100644
index 000000000..7818309ed
--- /dev/null
+++ b/vcl/source/filter/wmf/wmf.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 "emfwr.hxx"
+#include "wmfwr.hxx"
+#include <vcl/wmf.hxx>
+#include <vcl/gdimetafiletools.hxx>
+#include <vcl/graph.hxx>
+
+bool ReadWindowMetafile( SvStream& rStream, GDIMetaFile& rMTF )
+{
+ // tdf#111484 Use new method to import Metafile. Take current StreamPos
+ // into account (used by SwWW8ImplReader::ReadGrafFile and by
+ // SwWw6ReadMetaStream, so do *not* ignore. OTOH XclImpDrawing::ReadWmf
+ // is nice enough to copy to an own MemStream to avoid that indirect
+ // parameter passing...)
+ const sal_uInt32 nStreamStart(rStream.Tell());
+ const sal_uInt32 nStreamEnd(rStream.TellEnd());
+
+ if (nStreamStart >= nStreamEnd)
+ {
+ return false;
+ }
+
+ // Read binary data to mem array
+ const sal_uInt32 nStreamLength(nStreamEnd - nStreamStart);
+ VectorGraphicDataArray aNewData(nStreamLength);
+ rStream.ReadBytes(aNewData.begin(), 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>(
+ aNewData,
+ OUString(),
+ 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);
+ }
+
+ return aWMFWriter.WriteWMF( aGdiMetaFile, rTargetStream, pConfigItem, bPlaceable );
+}
+
+bool ConvertGDIMetaFileToEMF(const GDIMetaFile & rMTF, SvStream & rTargetStream)
+{
+ EMFWriter aEMFWriter(rTargetStream);
+ GDIMetaFile aGdiMetaFile(rMTF);
+
+ if(usesClipActions(aGdiMetaFile))
+ {
+ // #i121267# It is necessary to prepare the metafile since the export does *not* support
+ // clip regions. This tooling method clips the geometry content of the metafile internally
+ // against its own clip regions, so that the export is safe to ignore clip regions
+ clipMetafileContentAgainstOwnRegions(aGdiMetaFile);
+ }
+
+ return aEMFWriter.WriteEMF(aGdiMetaFile);
+}
+
+bool WriteWindowMetafileBits( SvStream& rStream, const GDIMetaFile& rMTF )
+{
+ return WMFWriter().WriteWMF( rMTF, rStream, nullptr, false );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/wmf/wmfexternal.cxx b/vcl/source/filter/wmf/wmfexternal.cxx
new file mode 100644
index 000000000..4080d3f04
--- /dev/null
+++ b/vcl/source/filter/wmf/wmfexternal.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/wmfexternal.hxx>
+#include <com/sun/star/beans/PropertyValue.hpp>
+
+// formally known as WMF_EXTERNALHEADER
+WmfExternal::WmfExternal() :
+ xExt(0),
+ yExt(0),
+ mapMode(0)
+{
+}
+
+css::uno::Sequence< css::beans::PropertyValue > WmfExternal::getSequence() const
+{
+ css::uno::Sequence< css::beans::PropertyValue > aSequence;
+
+ if (0 != xExt || 0 != yExt || 0 != mapMode)
+ {
+ aSequence.realloc(3);
+ aSequence[0].Name = "Width";
+ aSequence[0].Value <<= static_cast<sal_Int16>(xExt);
+ aSequence[1].Name = "Height";
+ aSequence[1].Value <<= static_cast<sal_Int16>(yExt);
+ aSequence[2].Name = "MapMode";
+ aSequence[2].Value <<= static_cast<sal_Int16>(mapMode);
+ }
+
+ return aSequence;
+}
+
+bool WmfExternal::setSequence(const css::uno::Sequence< css::beans::PropertyValue >& rSequence)
+{
+ bool bRetval(false);
+
+ for (const auto& rPropVal : rSequence)
+ {
+ const OUString aName(rPropVal.Name);
+
+ if (aName == "Width")
+ {
+ rPropVal.Value >>= xExt;
+ bRetval = true;
+ }
+ else if (aName == "Height")
+ {
+ rPropVal.Value >>= yExt;
+ bRetval = true;
+ }
+ else if (aName == "MapMode")
+ {
+ rPropVal.Value >>= mapMode;
+ bRetval = true;
+ }
+ }
+
+ return bRetval;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/wmf/wmfwr.cxx b/vcl/source/filter/wmf/wmfwr.cxx
new file mode 100644
index 000000000..d9fa0d61f
--- /dev/null
+++ b/vcl/source/filter/wmf/wmfwr.cxx
@@ -0,0 +1,1895 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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>
+
+// 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() )
+ {
+ 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() ).WriteInt16( rFont.GetOrientation() );
+
+ 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, const long* pDXAry )
+{
+ bool bEscapeUsed = false;
+
+ sal_uInt32 i, nStringLen = rUniStr.getLength();
+ if ( nStringLen )
+ {
+ // first we will check if a comment is necessary
+ if ( aSrcFont.GetCharSet() != RTL_TEXTENCODING_SYMBOL ) // symbol is always byte character, so there is no unicode loss
+ {
+ const sal_Unicode* pBuf = rUniStr.getStr();
+ const rtl_TextEncoding aTextEncodingOrg = aSrcFont.GetCharSet();
+ OString aByteStr(OUStringToOString(rUniStr, aTextEncodingOrg));
+ OUString aUniStr2(OStringToOUString(aByteStr, aTextEncodingOrg));
+ const sal_Unicode* pConversion = aUniStr2.getStr(); // this is the unicode array after bytestring <-> unistring conversion
+ for ( i = 0; i < nStringLen; i++ )
+ {
+ if ( *pBuf++ != *pConversion++ )
+ break;
+ }
+
+ if ( i != nStringLen ) // after conversion the characters are not original,
+ { // try again, with determining a better charset from unicode char
+ pBuf = rUniStr.getStr();
+ const sal_Unicode* pCheckChar = pBuf;
+ rtl_TextEncoding aTextEncoding = getBestMSEncodingByChar(*pCheckChar); // try the first character
+ if (aTextEncoding == RTL_TEXTENCODING_DONTKNOW) {
+ aTextEncoding = aTextEncodingOrg;
+ }
+ for ( i = 1; i < nStringLen; i++)
+ {
+ if (aTextEncoding != aTextEncodingOrg) // found something
+ break;
+ pCheckChar++;
+ aTextEncoding = getBestMSEncodingByChar(*pCheckChar); // try the next character
+ if (aTextEncoding == RTL_TEXTENCODING_DONTKNOW) {
+ aTextEncoding = aTextEncodingOrg;
+ }
+ }
+
+ aByteStr = OUStringToOString(rUniStr, aTextEncoding);
+ aUniStr2 = OStringToOUString(aByteStr, aTextEncoding);
+ pConversion = aUniStr2.getStr(); // this is the unicode array after bytestring <-> unistring conversion
+ for ( i = 0; i < nStringLen; i++ )
+ {
+ if ( *pBuf++ != *pConversion++ )
+ break;
+ }
+ if (i == nStringLen)
+ {
+ aSrcFont.SetCharSet (aTextEncoding);
+ SetAllAttr();
+ }
+ }
+
+ if ( ( i != nStringLen ) || IsStarSymbol( aSrcFont.GetFamilyName() ) ) // after conversion the characters are not original, so we
+ { // will store the unicode string and a polypoly replacement
+ Color aOldFillColor( aSrcFillColor );
+ Color aOldLineColor( aSrcLineColor );
+ aSrcLineInfo = LineInfo();
+ aSrcFillColor = aSrcTextColor;
+ aSrcLineColor = COL_TRANSPARENT;
+ SetLineAndFillAttr();
+ pVirDev->SetFont( aSrcFont );
+ std::vector<tools::PolyPolygon> aPolyPolyVec;
+ if ( pVirDev->GetTextOutlines( aPolyPolyVec, rUniStr ) )
+ {
+ sal_uInt32 nDXCount = pDXAry ? nStringLen : 0;
+ sal_uInt32 nSkipActions = aPolyPolyVec.size();
+ sal_Int32 nStrmLen = 8 +
+ + sizeof( nStringLen ) + ( nStringLen * 2 )
+ + sizeof( nDXCount ) + ( nDXCount * 4 )
+ + sizeof( nSkipActions );
+
+ SvMemoryStream aMemoryStream( nStrmLen );
+ Point aPt( OutputDevice::LogicToLogic( rPoint, aSrcMapMode, aTargetMapMode ) );
+ aMemoryStream.WriteInt32( aPt.X() )
+ .WriteInt32( aPt.Y() )
+ .WriteUInt32( nStringLen );
+ for ( i = 0; i < nStringLen; i++ )
+ aMemoryStream.WriteUInt16( rUniStr[ i ] );
+ aMemoryStream.WriteUInt32( nDXCount );
+ for ( i = 0; i < nDXCount; i++ )
+ aMemoryStream.WriteInt32( pDXAry[ i ] );
+ aMemoryStream.WriteUInt32( nSkipActions );
+ WMFRecord_Escape( PRIVATE_ESCAPE_UNICODE, nStrmLen, static_cast<const sal_Int8*>(aMemoryStream.GetData()) );
+
+ for ( const auto& rPolyPoly : aPolyPolyVec )
+ {
+ tools::PolyPolygon aPolyPoly( rPolyPoly );
+ aPolyPoly.Move( rPoint.X(), rPoint.Y() );
+ WMFRecord_PolyPolygon( aPolyPoly );
+ }
+ aSrcFillColor = aOldFillColor;
+ aSrcLineColor = aOldLineColor;
+ bEscapeUsed = true;
+ }
+ }
+ }
+ }
+ return bEscapeUsed;
+}
+
+void WMFWriter::WMFRecord_ExtTextOut( const Point& rPoint,
+ const OUString& rString,
+ const long* pDXAry )
+{
+ sal_Int32 nOriginalTextLen = rString.getLength();
+
+ if ( (nOriginalTextLen <= 1) || (pDXAry == nullptr) )
+ {
+ WMFRecord_TextOut(rPoint, rString);
+ return;
+ }
+ rtl_TextEncoding eChrSet = aSrcFont.GetCharSet();
+ OString aByteString(OUStringToOString(rString, eChrSet));
+ TrueExtTextOut(rPoint, rString, aByteString, pDXAry);
+}
+
+void WMFWriter::TrueExtTextOut( const Point& rPoint, const OUString& rString,
+ const OString& rByteString, const long* pDXAry )
+{
+ WriteRecordHeader( 0, W_META_EXTTEXTOUT );
+ WritePointYX( rPoint );
+ sal_uInt16 nNewTextLen = static_cast<sal_uInt16>(rByteString.getLength());
+ pWMF->WriteUInt16( nNewTextLen ).WriteUInt16( 0 );
+ write_uInt8s_FromOString(*pWMF, rByteString, nNewTextLen);
+ if ( nNewTextLen & 1 )
+ pWMF->WriteUChar( 0 );
+
+ sal_Int32 nOriginalTextLen = rString.getLength();
+ std::unique_ptr<sal_Int16[]> pConvertedDXAry(new sal_Int16[ nOriginalTextLen ]);
+ sal_Int32 j = 0;
+ pConvertedDXAry[ j++ ] = static_cast<sal_Int16>(ScaleWidth( pDXAry[ 0 ] ));
+ for (sal_Int32 i = 1; i < ( nOriginalTextLen - 1 ); ++i)
+ pConvertedDXAry[ j++ ] = static_cast<sal_Int16>(ScaleWidth( pDXAry[ i ] - pDXAry[ i - 1 ] ));
+ pConvertedDXAry[ j ] = static_cast<sal_Int16>(ScaleWidth( pDXAry[ nOriginalTextLen - 2 ] / ( nOriginalTextLen - 1 ) ));
+
+ for (sal_Int32 i = 0; i < nOriginalTextLen; ++i)
+ {
+ sal_Int16 nDx = pConvertedDXAry[ i ];
+ pWMF->WriteInt16( nDx );
+ if ( nOriginalTextLen < nNewTextLen )
+ {
+ sal_Unicode nUniChar = rString[i];
+ OString aTemp(&nUniChar, 1, aSrcFont.GetCharSet());
+ j = aTemp.getLength();
+ while ( --j > 0 )
+ pWMF->WriteUInt16( 0 );
+ }
+ }
+ pConvertedDXAry.reset();
+ UpdateRecordHeader();
+}
+
+void WMFWriter::WMFRecord_LineTo(const Point & rPoint)
+{
+ WriteRecordHeader(0x00000005,W_META_LINETO);
+ WritePointYX(rPoint);
+}
+
+void WMFWriter::WMFRecord_MoveTo(const Point & rPoint)
+{
+ WriteRecordHeader(0x00000005,W_META_MOVETO);
+ WritePointYX(rPoint);
+}
+
+void WMFWriter::WMFRecord_Pie(const tools::Rectangle & rRect, const Point & rStartPt, const Point & rEndPt)
+{
+ WriteRecordHeader(0x0000000b,W_META_PIE);
+ WritePointYX(rEndPt);
+ WritePointYX(rStartPt);
+ WriteRectangle(rRect);
+}
+
+void WMFWriter::WMFRecord_Polygon(const tools::Polygon & rPoly)
+{
+ tools::Polygon aSimplePoly;
+ if ( rPoly.HasFlags() )
+ rPoly.AdaptiveSubdivide( aSimplePoly );
+ else
+ aSimplePoly = rPoly;
+ const sal_uInt16 nSize = aSimplePoly.GetSize();
+ WriteRecordHeader(static_cast<sal_uInt32>(nSize)*2+4,W_META_POLYGON);
+ pWMF->WriteUInt16( nSize );
+ for (sal_uInt16 i=0; i<nSize; ++i)
+ WritePointXY(aSimplePoly.GetPoint(i));
+}
+
+void WMFWriter::WMFRecord_PolyLine(const tools::Polygon & rPoly)
+{
+ tools::Polygon aSimplePoly;
+ if ( rPoly.HasFlags() )
+ rPoly.AdaptiveSubdivide( aSimplePoly );
+ else
+ aSimplePoly = rPoly;
+ const sal_uInt16 nSize = aSimplePoly.GetSize();
+ WriteRecordHeader(static_cast<sal_uInt32>(nSize)*2+4,W_META_POLYLINE);
+ pWMF->WriteUInt16( nSize );
+ for (sal_uInt16 i=0; i<nSize; ++i)
+ WritePointXY(aSimplePoly.GetPoint(i));
+}
+
+void WMFWriter::WMFRecord_PolyPolygon(const tools::PolyPolygon & rPolyPoly)
+{
+ const tools::Polygon * pPoly;
+ sal_uInt16 nCount,nSize,i,j;
+
+ nCount=rPolyPoly.Count();
+ tools::PolyPolygon aSimplePolyPoly( rPolyPoly );
+ for ( i = 0; i < nCount; i++ )
+ {
+ if ( aSimplePolyPoly[ i ].HasFlags() )
+ {
+ tools::Polygon aSimplePoly;
+ aSimplePolyPoly[ i ].AdaptiveSubdivide( aSimplePoly );
+ aSimplePolyPoly[ i ] = aSimplePoly;
+ }
+ }
+ WriteRecordHeader(0,W_META_POLYPOLYGON);
+ pWMF->WriteUInt16( nCount );
+ for (i=0; i<nCount; i++) pWMF->WriteUInt16( aSimplePolyPoly.GetObject(i).GetSize() );
+ for (i=0; i<nCount; i++) {
+ pPoly=&(aSimplePolyPoly.GetObject(i));
+ nSize=pPoly->GetSize();
+ for (j=0; j<nSize; j++) WritePointXY(pPoly->GetPoint(j));
+ }
+ UpdateRecordHeader();
+}
+
+void WMFWriter::WMFRecord_Rectangle(const tools::Rectangle & rRect)
+{
+ WriteRecordHeader( 0x00000007,W_META_RECTANGLE );
+ WriteRectangle( rRect );
+}
+
+void WMFWriter::WMFRecord_RestoreDC()
+{
+ WriteRecordHeader(0x00000004,W_META_RESTOREDC);
+ pWMF->WriteInt16( -1 );
+}
+
+void WMFWriter::WMFRecord_RoundRect(const tools::Rectangle & rRect, long nHorzRound, 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(FontAlign 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, const OUString & 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 )
+ {
+ 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())
+ {
+ basegfx::B2DPolyPolygon aLinePolyPolygon(rLinePolygon);
+ basegfx::B2DPolyPolygon aFillPolyPolygon;
+
+ rInfo.applyToB2DPolyPolygon(aLinePolyPolygon, aFillPolyPolygon);
+
+ if(aLinePolyPolygon.count())
+ {
+ aSrcLineInfo = rInfo;
+ SetLineAndFillAttr();
+
+ for(auto const& rB2DPolygon : aLinePolyPolygon)
+ {
+ WMFRecord_PolyLine( tools::Polygon(rB2DPolygon) );
+ }
+ }
+
+ if(aFillPolyPolygon.count())
+ {
+ const Color aOldLineColor(aSrcLineColor);
+ const Color aOldFillColor(aSrcFillColor);
+
+ aSrcLineColor = COL_TRANSPARENT;
+ aSrcFillColor = aOldLineColor;
+ SetLineAndFillAttr();
+
+ for(auto const& rB2DPolygon : aFillPolyPolygon)
+ {
+ WMFRecord_Polygon( tools::Polygon(rB2DPolygon) );
+ }
+
+ aSrcLineColor = aOldLineColor;
+ aSrcFillColor = aOldFillColor;
+ SetLineAndFillAttr();
+ }
+ }
+}
+
+void WMFWriter::WriteRecords( const GDIMetaFile & rMTF )
+{
+ if( bStatus )
+ {
+ 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, nullptr ) )
+ 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, nullptr ) )
+ WMFRecord_TextOut( pA->GetPoint(), aTemp );
+ }
+ break;
+
+ case MetaActionType::TEXTARRAY:
+ {
+ const MetaTextArrayAction* pA = static_cast<const MetaTextArrayAction*>(pMA);
+
+ OUString aTemp = pA->GetText().copy( pA->GetIndex(), std::min<sal_Int32>(pA->GetText().getLength() - pA->GetIndex(), pA->GetLen()) );
+ aSrcLineInfo = LineInfo();
+ SetAllAttr();
+ if ( !WMFRecord_Escape_Unicode( pA->GetPoint(), aTemp, pA->GetDXArray() ) )
+ WMFRecord_ExtTextOut( pA->GetPoint(), aTemp, pA->GetDXArray() );
+ }
+ break;
+
+ case MetaActionType::STRETCHTEXT:
+ {
+ const MetaStretchTextAction* pA = static_cast<const MetaStretchTextAction *>(pMA);
+ OUString aTemp = pA->GetText().copy( pA->GetIndex(), std::min<sal_Int32>(pA->GetText().getLength() - pA->GetIndex(), pA->GetLen()) );
+
+ pVirDev->SetFont( aSrcFont );
+ const sal_Int32 nLen = aTemp.getLength();
+ std::unique_ptr<long[]> pDXAry(nLen ? new long[ nLen ] : nullptr);
+ const sal_Int32 nNormSize = pVirDev->GetTextArray( aTemp, pDXAry.get() );
+ if (nLen && nNormSize == 0)
+ {
+ OSL_FAIL("Impossible div by 0 action: MetaStretchTextAction!");
+ }
+ else
+ {
+ for ( sal_Int32 i = 0; i < ( nLen - 1 ); i++ )
+ pDXAry[ i ] = pDXAry[ i ] * static_cast<sal_Int32>(pA->GetWidth()) / nNormSize;
+ if ( ( nLen <= 1 ) || ( static_cast<sal_Int32>(pA->GetWidth()) == nNormSize ) )
+ pDXAry.reset();
+ aSrcLineInfo = LineInfo();
+ SetAllAttr();
+ if ( !WMFRecord_Escape_Unicode( pA->GetPoint(), aTemp, pDXAry.get() ) )
+ WMFRecord_ExtTextOut( pA->GetPoint(), aTemp, pDXAry.get() );
+ }
+ }
+ break;
+
+ case MetaActionType::BMP:
+ {
+ const MetaBmpAction* pA = static_cast<const MetaBmpAction *>(pMA);
+ WMFRecord_StretchDIB( pA->GetPoint(), pA->GetBitmap().GetSizePixel(), pA->GetBitmap() );
+ }
+ break;
+
+ case MetaActionType::BMPSCALE:
+ {
+ const MetaBmpScaleAction* pA = static_cast<const MetaBmpScaleAction*>(pMA);
+ WMFRecord_StretchDIB( pA->GetPoint(), pA->GetSize(), pA->GetBitmap() );
+ }
+ break;
+
+ case MetaActionType::BMPSCALEPART:
+ {
+ const MetaBmpScalePartAction* pA = static_cast<const MetaBmpScalePartAction*>(pMA);
+ Bitmap aTmp( pA->GetBitmap() );
+
+ if( aTmp.Crop( tools::Rectangle( pA->GetSrcPoint(), pA->GetSrcSize() ) ) )
+ WMFRecord_StretchDIB( pA->GetDestPoint(), pA->GetDestSize(), aTmp );
+ }
+ break;
+
+ case MetaActionType::BMPEX:
+ {
+ const MetaBmpExAction* pA = static_cast<const MetaBmpExAction *>(pMA);
+ Bitmap aBmp( pA->GetBitmapEx().GetBitmap() );
+ Bitmap aMsk( pA->GetBitmapEx().GetMask() );
+
+ if( !!aMsk )
+ {
+ aBmp.Replace( aMsk, COL_WHITE );
+ aMsk.Invert();
+ WMFRecord_StretchDIB( pA->GetPoint(), aMsk.GetSizePixel(), aBmp, W_SRCPAINT );
+ WMFRecord_StretchDIB( pA->GetPoint(), aBmp.GetSizePixel(), aBmp, W_SRCAND );
+ }
+ else
+ WMFRecord_StretchDIB( pA->GetPoint(), aBmp.GetSizePixel(), aBmp );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ {
+ const MetaBmpExScaleAction* pA = static_cast<const MetaBmpExScaleAction*>(pMA);
+ Bitmap aBmp( pA->GetBitmapEx().GetBitmap() );
+ Bitmap aMsk( pA->GetBitmapEx().GetMask() );
+
+ if( !!aMsk )
+ {
+ aBmp.Replace( aMsk, COL_WHITE );
+ aMsk.Invert();
+ WMFRecord_StretchDIB( pA->GetPoint(), pA->GetSize(), aMsk, W_SRCPAINT );
+ WMFRecord_StretchDIB( pA->GetPoint(), pA->GetSize(), aBmp, W_SRCAND );
+ }
+ else
+ WMFRecord_StretchDIB( pA->GetPoint(), pA->GetSize(), aBmp );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ {
+ const MetaBmpExScalePartAction* pA = static_cast<const MetaBmpExScalePartAction*>(pMA);
+ BitmapEx aBmpEx( pA->GetBitmapEx() );
+ aBmpEx.Crop( tools::Rectangle( pA->GetSrcPoint(), pA->GetSrcSize() ) );
+ Bitmap aBmp( aBmpEx.GetBitmap() );
+ Bitmap aMsk( aBmpEx.GetMask() );
+
+ if( !!aMsk )
+ {
+ aBmp.Replace( aMsk, COL_WHITE );
+ aMsk.Invert();
+ WMFRecord_StretchDIB( pA->GetDestPoint(), pA->GetDestSize(), aMsk, W_SRCPAINT );
+ WMFRecord_StretchDIB( pA->GetDestPoint(), pA->GetDestSize(), aBmp, W_SRCAND );
+ }
+ else
+ WMFRecord_StretchDIB( pA->GetDestPoint(), pA->GetDestSize(), aBmp );
+ }
+ break;
+
+ case MetaActionType::GRADIENT:
+ {
+ const MetaGradientAction* pA = static_cast<const MetaGradientAction*>(pMA);
+ GDIMetaFile aTmpMtf;
+
+ pVirDev->AddGradientActions( pA->GetRect(), pA->GetGradient(), 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<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<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 & PushFlags::LINECOLOR )
+ aSrcLineColor = pAt->aLineColor;
+ aDstFillColor = pAt->aFillColor;
+ if ( pAt->nFlags & PushFlags::FILLCOLOR )
+ aSrcFillColor = pAt->aFillColor;
+ eDstROP2 = pAt->eRasterOp;
+ if ( pAt->nFlags & PushFlags::RASTEROP )
+ eSrcRasterOp = pAt->eRasterOp;
+ aDstFont = pAt->aFont;
+ if ( pAt->nFlags & PushFlags::FONT )
+ aSrcFont = pAt->aFont;
+ eDstTextAlign = pAt->eTextAlign;
+ if ( pAt->nFlags & ( PushFlags::FONT | PushFlags::TEXTALIGN ) )
+ eSrcTextAlign = pAt->eTextAlign;
+ aDstTextColor = pAt->aTextColor;
+ if ( pAt->nFlags & ( PushFlags::FONT | PushFlags::TEXTCOLOR ) )
+ aSrcTextColor = pAt->aTextColor;
+ if ( pAt->nFlags & PushFlags::MAPMODE )
+ aSrcMapMode = pAt->aMapMode;
+ aDstClipRegion = pAt->aClipRegion;
+ if ( pAt->nFlags & 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;
+ 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:
+ {
+ ComplexTextLayoutFlags nLayoutMode = static_cast<const MetaLayoutModeAction*>(pMA)->GetLayoutMode();
+ eSrcHorTextAlign = 0; // TA_LEFT
+ if ((nLayoutMode & ComplexTextLayoutFlags::BiDiRtl) != ComplexTextLayoutFlags::Default)
+ {
+ eSrcHorTextAlign = W_TA_RIGHT | W_TA_RTLREADING;
+ }
+ if ((nLayoutMode & ComplexTextLayoutFlags::TextOriginRight) != ComplexTextLayoutFlags::Default)
+ eSrcHorTextAlign |= W_TA_RIGHT;
+ else if ((nLayoutMode & ComplexTextLayoutFlags::TextOriginLeft) != 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 );
+ }
+ }
+ 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 long nUnit = pVirDev->LogicToPixel( Size( 1, 1 ), aTargetMapMode ).Width();
+ const Fraction aFrac( 1, nUnit );
+
+ aTargetMapMode.SetScaleX( aFrac );
+ aTargetMapMode.SetScaleY( aFrac );
+ aTargetSize = OutputDevice::LogicToLogic( rMTF.GetPrefSize(), aSrcMapMode, aTargetMapMode );
+ }
+
+ pVirDev->SetMapMode( aTargetMapMode );
+
+ pAttrStack=nullptr;
+
+ for (bool & rn : bHandleAllocated)
+ rn=false;
+
+ nDstPenHandle=0xffff;
+ nDstFontHandle=0xffff;
+ nDstBrushHandle=0xffff;
+
+ nNumberOfActions=0;
+ nNumberOfBitmaps=0;
+ nWrittenActions=0;
+ nWrittenBitmaps=0;
+ nActBitmapPercent=0;
+
+ CountActionsAndBitmaps(rMTF);
+
+ WriteHeader(bPlaceable);
+ if( bEmbedEMF )
+ WriteEmbeddedEMF( rMTF );
+ WMFRecord_SetWindowOrg(Point(0,0));
+ WMFRecord_SetWindowExt(rMTF.GetPrefSize());
+ WMFRecord_SetBkMode( true );
+
+ eDstROP2 = eSrcRasterOp = RasterOp::OverPaint;
+ WMFRecord_SetROP2(eDstROP2);
+
+ aDstLineInfo = LineInfo();
+ aDstLineColor = aSrcLineColor = COL_BLACK;
+ CreateSelectDeletePen( aDstLineColor, aDstLineInfo );
+
+ aDstFillColor = aSrcFillColor = COL_WHITE;
+ CreateSelectDeleteBrush( aDstFillColor );
+
+ aDstClipRegion = aSrcClipRegion = vcl::Region();
+
+ vcl::Font aFont;
+ aFont.SetCharSet( GetExtendedTextEncoding( RTL_TEXTENCODING_MS_1252 ) );
+ aFont.SetColor( COL_WHITE );
+ aFont.SetAlignment( ALIGN_BASELINE );
+ aDstFont = aSrcFont = aFont;
+ CreateSelectDeleteFont(aDstFont);
+
+ eDstTextAlign = eSrcTextAlign = ALIGN_BASELINE;
+ eDstHorTextAlign = eSrcHorTextAlign = W_TA_LEFT;
+ WMFRecord_SetTextAlign( eDstTextAlign, eDstHorTextAlign );
+
+ aDstTextColor = aSrcTextColor = COL_WHITE;
+ WMFRecord_SetTextColor(aDstTextColor);
+
+ // Write records
+ WriteRecords(rMTF);
+
+ WriteRecordHeader(0x00000003,0x0000); // end of file
+ UpdateHeader();
+
+ while(pAttrStack)
+ {
+ pAt=pAttrStack;
+ pAttrStack=pAt->pSucc;
+ delete pAt;
+ }
+
+ pVirDev.disposeAndClear();
+
+ if ( xStatusIndicator.is() )
+ xStatusIndicator->end();
+
+ return bStatus;
+}
+
+sal_uInt16 WMFWriter::CalcSaveTargetMapMode(MapMode& rMapMode,
+ const Size& rPrefSize)
+{
+ Fraction aDivFrac(2, 1);
+ sal_uInt16 nDivisor = 1;
+
+ Size aSize = OutputDevice::LogicToLogic( rPrefSize, aSrcMapMode, rMapMode );
+
+ while( nDivisor <= 64 && (aSize.Width() > 32767 || aSize.Height() > 32767) )
+ {
+ Fraction aFrac = rMapMode.GetScaleX();
+
+ aFrac *= aDivFrac;
+ rMapMode.SetScaleX(aFrac);
+ aFrac = rMapMode.GetScaleY();
+ aFrac *= aDivFrac;
+ rMapMode.SetScaleY(aFrac);
+ nDivisor <<= 1;
+ aSize = OutputDevice::LogicToLogic( rPrefSize, aSrcMapMode, rMapMode );
+ }
+
+ return nDivisor;
+}
+
+void WMFWriter::WriteEmbeddedEMF( const GDIMetaFile& rMTF )
+{
+ SvMemoryStream aStream;
+ EMFWriter aEMFWriter(aStream);
+
+ if( !aEMFWriter.WriteEMF( rMTF ) )
+ return;
+
+ sal_uInt64 const nTotalSize = aStream.Tell();
+ if( nTotalSize > SAL_MAX_UINT32 )
+ return;
+ aStream.Seek( 0 );
+ sal_uInt32 nRemainingSize = static_cast< sal_uInt32 >( nTotalSize );
+ sal_uInt32 nRecCounts = ( (nTotalSize - 1) / 0x2000 ) + 1;
+ sal_uInt16 nCheckSum = 0, nWord;
+
+ sal_uInt32 nPos = 0;
+
+ while( nPos + 1 < nTotalSize )
+ {
+ aStream.ReadUInt16( nWord );
+ nCheckSum ^= nWord;
+ nPos += 2;
+ }
+
+ nCheckSum = static_cast< sal_uInt16 >( nCheckSum * -1 );
+
+ aStream.Seek( 0 );
+ while( nRemainingSize > 0 )
+ {
+ sal_uInt32 nCurSize;
+ if( nRemainingSize > 0x2000 )
+ {
+ nCurSize = 0x2000;
+ nRemainingSize -= 0x2000;
+ }
+ else
+ {
+ nCurSize = nRemainingSize;
+ nRemainingSize = 0;
+ }
+ WriteEMFRecord( aStream,
+ nCurSize,
+ nRemainingSize,
+ nTotalSize,
+ nRecCounts,
+ nCheckSum );
+ nCheckSum = 0;
+ }
+
+}
+
+void WMFWriter::WriteEMFRecord( SvMemoryStream& rStream, sal_uInt32 nCurSize, sal_uInt32 nRemainingSize,
+ sal_uInt32 nTotalSize, sal_uInt32 nRecCounts, sal_uInt16 nCheckSum )
+{
+ // according to http://msdn.microsoft.com/en-us/library/dd366152%28PROT.13%29.aspx
+ WriteRecordHeader( 0, W_META_ESCAPE );
+ pWMF->WriteUInt16( W_MFCOMMENT ) // same as META_ESCAPE_ENHANCED_METAFILE
+ .WriteUInt16( nCurSize + 34 ) // we will always have a 34 byte escape header:
+ .WriteUInt32( 0x43464D57 ) // WMFC
+ .WriteUInt32( 0x00000001 ) // Comment type
+ .WriteUInt32( 0x00010000 ) // version
+ .WriteUInt16( nCheckSum ) // check sum
+ .WriteUInt32( 0 ) // flags = 0
+ .WriteUInt32( nRecCounts ) // total number of records
+ .WriteUInt32( nCurSize ) // size of this record's data
+ .WriteUInt32( nRemainingSize ) // remaining size of data in following records, missing in MSDN documentation
+ .WriteUInt32( nTotalSize ); // total size of EMF stream
+
+ pWMF->WriteBytes(static_cast<const char*>(rStream.GetData()) + rStream.Tell(), nCurSize);
+ rStream.SeekRel( nCurSize );
+ UpdateRecordHeader();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/wmf/wmfwr.hxx b/vcl/source/filter/wmf/wmfwr.hxx
new file mode 100644
index 000000000..49fef3532
--- /dev/null
+++ b/vcl/source/filter/wmf/wmfwr.hxx
@@ -0,0 +1,205 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_FILTER_WMF_WMFWR_HXX
+#define INCLUDED_VCL_SOURCE_FILTER_WMF_WMFWR_HXX
+
+#include <vcl/gdimtf.hxx>
+#include <vcl/lineinfo.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include <com/sun/star/task/XStatusIndicator.hpp>
+#include <tools/stream.hxx>
+
+#define MAXOBJECTHANDLES 16
+
+struct WMFWriterAttrStackMember
+{
+ struct WMFWriterAttrStackMember * pSucc;
+ Color aLineColor;
+ Color aFillColor;
+ Color aTextColor;
+ LineInfo aLineInfo;
+ TextAlign eTextAlign;
+ RasterOp eRasterOp;
+ vcl::Font aFont;
+ MapMode aMapMode;
+ vcl::Region aClipRegion;
+ 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;
+ FontAlign 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;
+ FontAlign 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, const long* pDXAry );
+ void WMFRecord_ExtTextOut(const Point& rPoint, const OUString& rString, const long* pDXAry);
+
+ void TrueExtTextOut(const Point& rPoint, const OUString& rString,
+ const OString& rByteString, const long* 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, long nHorzRound, 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(FontAlign 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, const OUString & rString);
+ void WMFRecord_IntersectClipRect( const tools::Rectangle& rRect);
+
+ sal_uInt16 AllocHandle();
+ void FreeHandle(sal_uInt16 nObjectHandle);
+ void CreateSelectDeletePen( const Color& rColor, const LineInfo& rLineInfo );
+ void CreateSelectDeleteFont(const vcl::Font & rFont);
+ void CreateSelectDeleteBrush(const Color& rColor);
+
+ void SetLineAndFillAttr();
+ void SetAllAttr();
+
+ void HandleLineInfoPolyPolygons(const LineInfo& rInfo, const basegfx::B2DPolygon& rLinePolygon);
+ void WriteRecords(const GDIMetaFile & rMTF);
+
+ void WriteHeader(bool bPlaceable);
+ void UpdateHeader();
+
+ void WriteEmbeddedEMF( const GDIMetaFile& rMTF );
+ void WriteEMFRecord( SvMemoryStream& rStream, sal_uInt32 nCurSize,
+ sal_uInt32 nRemainingSize,
+ sal_uInt32 nTotalSize,
+ sal_uInt32 nRecCounts,
+ sal_uInt16 nCheckSum );
+
+ sal_uInt16 CalcSaveTargetMapMode(MapMode& rMapMode, const Size& rPrefSize);
+
+public:
+ WMFWriter();
+ bool WriteWMF(const GDIMetaFile & rMTF, SvStream & rTargetStream, FilterConfigItem const * pFilterConfigItem, bool bPlaceable);
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/font/Feature.cxx b/vcl/source/font/Feature.cxx
new file mode 100644
index 000000000..99a3910b8
--- /dev/null
+++ b/vcl/source/font/Feature.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/.
+ *
+ */
+
+#include <utility>
+#include <vcl/font/Feature.hxx>
+#include <svdata.hxx>
+
+#include <hb.h>
+
+namespace vcl::font
+{
+OUString featureCodeAsString(uint32_t nFeature)
+{
+ std::vector<char> aString(5, 0);
+ aString[0] = char(nFeature >> 24 & 0xff);
+ aString[1] = char(nFeature >> 16 & 0xff);
+ aString[2] = char(nFeature >> 8 & 0xff);
+ aString[3] = char(nFeature >> 0 & 0xff);
+
+ return OStringToOUString(aString.data(), RTL_TEXTENCODING_ASCII_US);
+}
+
+// Feature
+Feature::Feature()
+ : m_aID({ 0, 0, 0 })
+ , m_eType(FeatureType::OpenType)
+{
+}
+
+Feature::Feature(FeatureID const& rID, FeatureType eType)
+ : m_aID(rID)
+ , m_eType(eType)
+{
+}
+
+// FeatureSetting
+FeatureSetting::FeatureSetting(OString feature)
+ : m_nTag(0)
+ , m_nValue(0)
+ , m_nStart(0)
+ , m_nEnd(0)
+{
+ hb_feature_t aFeat;
+ if (hb_feature_from_string(feature.getStr(), feature.getLength(), &aFeat))
+ {
+ m_nTag = aFeat.tag;
+ m_nValue = aFeat.value;
+ m_nStart = aFeat.start;
+ m_nEnd = aFeat.end;
+ }
+}
+
+// FeatureParameter
+
+FeatureParameter::FeatureParameter(uint32_t nCode, OUString aDescription)
+ : m_nCode(nCode)
+ , m_sDescription(std::move(aDescription))
+ , m_pDescriptionID(nullptr)
+{
+}
+
+FeatureParameter::FeatureParameter(uint32_t nCode, const char* 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_pDescriptionID(nullptr)
+ , m_nCode(0)
+ , m_nDefault(0)
+ , m_eType(FeatureParameterType::BOOL)
+{
+}
+
+FeatureDefinition::FeatureDefinition(uint32_t nCode, OUString const& rDescription,
+ FeatureParameterType eType,
+ std::vector<FeatureParameter> const& rEnumParameters,
+ uint32_t nDefault)
+ : m_sDescription(rDescription)
+ , m_pDescriptionID(nullptr)
+ , m_nCode(nCode)
+ , m_nDefault(nDefault)
+ , m_eType(eType)
+ , m_aEnumParameters(rEnumParameters)
+{
+}
+
+FeatureDefinition::FeatureDefinition(uint32_t nCode, const char* pDescriptionID,
+ OUString const& rNumericPart)
+ : m_pDescriptionID(pDescriptionID)
+ , m_sNumericPart(rNumericPart)
+ , m_nCode(nCode)
+ , m_nDefault(0)
+ , m_eType(FeatureParameterType::BOOL)
+{
+}
+
+FeatureDefinition::FeatureDefinition(uint32_t nCode, const char* pDescriptionID,
+ std::vector<FeatureParameter> aEnumParameters)
+ : m_pDescriptionID(pDescriptionID)
+ , m_nCode(nCode)
+ , m_nDefault(0)
+ , m_eType(FeatureParameterType::ENUM)
+ , m_aEnumParameters(std::move(aEnumParameters))
+{
+}
+
+const std::vector<FeatureParameter>& FeatureDefinition::getEnumParameters() const
+{
+ return m_aEnumParameters;
+}
+
+OUString FeatureDefinition::getDescription() const
+{
+ if (m_pDescriptionID)
+ {
+ OUString sTranslatedDescription = VclResId(m_pDescriptionID);
+ if (!m_sNumericPart.isEmpty())
+ return sTranslatedDescription.replaceFirst("%1", m_sNumericPart);
+ return sTranslatedDescription;
+ }
+ else if (!m_sDescription.isEmpty())
+ {
+ return m_sDescription;
+ }
+ else
+ {
+ return vcl::font::featureCodeAsString(m_nCode);
+ }
+}
+
+uint32_t FeatureDefinition::getCode() const { return m_nCode; }
+
+FeatureParameterType FeatureDefinition::getType() const { return m_eType; }
+
+FeatureDefinition::operator bool() const { return m_nCode != 0; }
+
+uint32_t FeatureDefinition::getDefault() const { return m_nDefault; }
+} // end vcl::font namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/font/FeatureCollector.cxx b/vcl/source/font/FeatureCollector.cxx
new file mode 100644
index 000000000..e8219b07c
--- /dev/null
+++ b/vcl/source/font/FeatureCollector.cxx
@@ -0,0 +1,158 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <font/FeatureCollector.hxx>
+#include <font/OpenTypeFeatureDefinitionList.hxx>
+
+#include <hb-ot.h>
+#include <hb-graphite2.h>
+
+namespace vcl::font
+{
+bool FeatureCollector::collectGraphite()
+{
+ gr_face* grFace = hb_graphite2_face_get_gr_face(m_pHbFace);
+
+ if (grFace == nullptr)
+ return false;
+
+ gr_uint16 nUILanguage = gr_uint16(m_eLanguageType);
+
+ gr_uint16 nNumberOfFeatures = gr_face_n_fref(grFace);
+ gr_feature_val* pfeatureValues
+ = gr_face_featureval_for_lang(grFace, 0); // shame we don't know which lang
+
+ for (gr_uint16 i = 0; i < nNumberOfFeatures; ++i)
+ {
+ const gr_feature_ref* pFeatureRef = gr_face_fref(grFace, i);
+ gr_uint32 nFeatureCode = gr_fref_id(pFeatureRef);
+
+ if (nFeatureCode == 0) // illegal feature code - skip
+ continue;
+
+ gr_uint16 nValue = gr_fref_feature_value(pFeatureRef, pfeatureValues);
+ gr_uint32 nLabelLength = 0;
+ void* pLabel = gr_fref_label(pFeatureRef, &nUILanguage, gr_utf8, &nLabelLength);
+ OUString sLabel(OUString::createFromAscii(static_cast<char*>(pLabel)));
+ gr_label_destroy(pLabel);
+
+ std::vector<vcl::font::FeatureParameter> aParameters;
+ gr_uint16 nNumberOfValues = gr_fref_n_values(pFeatureRef);
+
+ if (nNumberOfValues > 0)
+ {
+ for (gr_uint16 j = 0; j < nNumberOfValues; ++j)
+ {
+ gr_uint32 nValueLabelLength = 0;
+ void* pValueLabel = gr_fref_value_label(pFeatureRef, j, &nUILanguage, gr_utf8,
+ &nValueLabelLength);
+ OUString sValueLabel(OUString::createFromAscii(static_cast<char*>(pValueLabel)));
+ gr_uint16 nParamValue = gr_fref_value(pFeatureRef, j);
+ aParameters.emplace_back(sal_uInt32(nParamValue), sValueLabel);
+ gr_label_destroy(pValueLabel);
+ }
+
+ auto eFeatureParameterType = vcl::font::FeatureParameterType::ENUM;
+
+ // Check if the parameters are boolean
+ if (aParameters.size() == 2
+ && (aParameters[0].getDescription() == "True"
+ || aParameters[0].getDescription() == "False"))
+ {
+ eFeatureParameterType = vcl::font::FeatureParameterType::BOOL;
+ aParameters.clear();
+ }
+
+ m_rFontFeatures.emplace_back(
+ FeatureID{ nFeatureCode, HB_OT_TAG_DEFAULT_SCRIPT, HB_OT_TAG_DEFAULT_LANGUAGE },
+ vcl::font::FeatureType::Graphite);
+ vcl::font::Feature& rFeature = m_rFontFeatures.back();
+ rFeature.m_aDefinition = vcl::font::FeatureDefinition(
+ nFeatureCode, sLabel, eFeatureParameterType, aParameters, sal_uInt32(nValue));
+ }
+ }
+ gr_featureval_destroy(pfeatureValues);
+ return true;
+}
+
+void FeatureCollector::collectForLanguage(hb_tag_t aTableTag, sal_uInt32 nScript,
+ hb_tag_t aScriptTag, sal_uInt32 nLanguage,
+ hb_tag_t aLanguageTag)
+{
+ unsigned int nFeatureCount = hb_ot_layout_language_get_feature_tags(
+ m_pHbFace, aTableTag, nScript, nLanguage, 0, nullptr, nullptr);
+ std::vector<hb_tag_t> aFeatureTags(nFeatureCount);
+ hb_ot_layout_language_get_feature_tags(m_pHbFace, aTableTag, nScript, nLanguage, 0,
+ &nFeatureCount, aFeatureTags.data());
+ aFeatureTags.resize(nFeatureCount);
+
+ for (hb_tag_t aFeatureTag : aFeatureTags)
+ {
+ if (OpenTypeFeatureDefinitionList::get().isRequired(aFeatureTag))
+ continue;
+
+ m_rFontFeatures.emplace_back();
+ vcl::font::Feature& rFeature = m_rFontFeatures.back();
+ rFeature.m_aID = { aFeatureTag, aScriptTag, aLanguageTag };
+
+ FeatureDefinition aDefinition
+ = OpenTypeFeatureDefinitionList::get().getDefinition(aFeatureTag);
+ if (aDefinition)
+ {
+ rFeature.m_aDefinition = aDefinition;
+ }
+ }
+}
+
+void FeatureCollector::collectForScript(hb_tag_t aTableTag, sal_uInt32 nScript, hb_tag_t aScriptTag)
+{
+ collectForLanguage(aTableTag, nScript, aScriptTag, HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX,
+ HB_OT_TAG_DEFAULT_LANGUAGE);
+
+ unsigned int nLanguageCount
+ = hb_ot_layout_script_get_language_tags(m_pHbFace, aTableTag, nScript, 0, nullptr, nullptr);
+ std::vector<hb_tag_t> aLanguageTags(nLanguageCount);
+ hb_ot_layout_script_get_language_tags(m_pHbFace, aTableTag, nScript, 0, &nLanguageCount,
+ aLanguageTags.data());
+ aLanguageTags.resize(nLanguageCount);
+ for (sal_uInt32 nLanguage = 0; nLanguage < sal_uInt32(nLanguageCount); ++nLanguage)
+ collectForLanguage(aTableTag, nScript, aScriptTag, nLanguage, aLanguageTags[nLanguage]);
+}
+
+void FeatureCollector::collectForTable(hb_tag_t aTableTag)
+{
+ unsigned int nScriptCount
+ = hb_ot_layout_table_get_script_tags(m_pHbFace, aTableTag, 0, nullptr, nullptr);
+ std::vector<hb_tag_t> aScriptTags(nScriptCount);
+ hb_ot_layout_table_get_script_tags(m_pHbFace, aTableTag, 0, &nScriptCount, aScriptTags.data());
+ aScriptTags.resize(nScriptCount);
+
+ for (sal_uInt32 nScript = 0; nScript < sal_uInt32(nScriptCount); ++nScript)
+ collectForScript(aTableTag, nScript, aScriptTags[nScript]);
+}
+
+bool FeatureCollector::collect()
+{
+ gr_face* grFace = hb_graphite2_face_get_gr_face(m_pHbFace);
+
+ if (grFace)
+ {
+ return collectGraphite();
+ }
+ else
+ {
+ collectForTable(HB_OT_TAG_GSUB); // substitution
+ collectForTable(HB_OT_TAG_GPOS); // positioning
+ return true;
+ }
+}
+
+} // end namespace vcl::font
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/font/FeatureParser.cxx b/vcl/source/font/FeatureParser.cxx
new file mode 100644
index 000000000..c886482df
--- /dev/null
+++ b/vcl/source/font/FeatureParser.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 <vcl/font/FeatureParser.hxx>
+#include <vcl/font/Feature.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(OUString const& rFontName)
+{
+ sal_Int32 nPrefixIdx{ rFontName.indexOf(vcl::font::FeaturePrefix) };
+
+ if (nPrefixIdx < 0)
+ return;
+
+ OUString sName = rFontName.copy(++nPrefixIdx);
+ sal_Int32 nIndex = 0;
+ do
+ {
+ OUString sToken = sName.getToken(0, vcl::font::FeatureSeparator, nIndex);
+
+ sal_Int32 nInnerIdx{ 0 };
+ OUString sID = sToken.getToken(0, '=', nInnerIdx);
+
+ if (sID == "lang")
+ {
+ m_sLanguage = sToken.getToken(0, '=', nInnerIdx);
+ }
+ else
+ {
+ OString sFeature = OUStringToOString(sToken, RTL_TEXTENCODING_ASCII_US);
+ FeatureSetting aFeature(sFeature);
+ if (aFeature.m_nTag != 0)
+ m_aFeatures.push_back(aFeature);
+ }
+ } while (nIndex >= 0);
+}
+
+std::unordered_map<uint32_t, uint32_t> FeatureParser::getFeaturesMap() const
+{
+ std::unordered_map<uint32_t, uint32_t> aResultMap;
+ for (auto const& rFeat : m_aFeatures)
+ {
+ if (rFeat.m_nValue != 0)
+ aResultMap.emplace(rFeat.m_nTag, rFeat.m_nValue);
+ }
+ return aResultMap;
+}
+
+} // end vcl::font namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/font/OpenTypeFeatureDefinitionList.cxx b/vcl/source/font/OpenTypeFeatureDefinitionList.cxx
new file mode 100644
index 000000000..9c3a0ca6a
--- /dev/null
+++ b/vcl/source/font/OpenTypeFeatureDefinitionList.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 <font/OpenTypeFeatureDefinitionList.hxx>
+#include <font/OpenTypeFeatureStrings.hrc>
+
+#include <rtl/character.hxx>
+
+#include <algorithm>
+
+namespace vcl::font
+{
+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,
+ std::vector<FeatureParameter>{ { 0, STR_FONT_FEATURE_ID_FRAC_PARAM_0 },
+ { 1, STR_FONT_FEATURE_ID_FRAC_PARAM_1 },
+ { 2, STR_FONT_FEATURE_ID_FRAC_PARAM_2 } } },
+ { featureCode("fwid"), STR_FONT_FEATURE_ID_FWID },
+ { featureCode("halt"), STR_FONT_FEATURE_ID_HALT },
+ { featureCode("hist"), STR_FONT_FEATURE_ID_HIST },
+ { featureCode("hkna"), STR_FONT_FEATURE_ID_HKNA },
+ { featureCode("hlig"), STR_FONT_FEATURE_ID_HLIG },
+ { featureCode("hngl"), STR_FONT_FEATURE_ID_HNGL },
+ { featureCode("hojo"), STR_FONT_FEATURE_ID_HOJO },
+ { featureCode("hwid"), STR_FONT_FEATURE_ID_HWID },
+ { featureCode("ital"), STR_FONT_FEATURE_ID_ITAL },
+ { featureCode("jalt"), STR_FONT_FEATURE_ID_JALT },
+ { featureCode("jp78"), STR_FONT_FEATURE_ID_JP78 },
+ { featureCode("jp83"), STR_FONT_FEATURE_ID_JP83 },
+ { featureCode("jp90"), STR_FONT_FEATURE_ID_JP90 },
+ { featureCode("jp04"), STR_FONT_FEATURE_ID_JP04 },
+ { featureCode("kern"), STR_FONT_FEATURE_ID_KERN },
+ { featureCode("lfbd"), STR_FONT_FEATURE_ID_LFBD },
+ { featureCode("liga"), STR_FONT_FEATURE_ID_LIGA },
+ { featureCode("lnum"), STR_FONT_FEATURE_ID_LNUM },
+ { featureCode("mgrk"), STR_FONT_FEATURE_ID_MGRK },
+ { featureCode("nalt"), STR_FONT_FEATURE_ID_NALT },
+ { featureCode("nlck"), STR_FONT_FEATURE_ID_NLCK },
+ { featureCode("numr"), STR_FONT_FEATURE_ID_NUMR },
+ { featureCode("onum"), STR_FONT_FEATURE_ID_ONUM },
+ { featureCode("opbd"), STR_FONT_FEATURE_ID_OPBD },
+ { featureCode("ordn"), STR_FONT_FEATURE_ID_ORDN },
+ { featureCode("ornm"), STR_FONT_FEATURE_ID_ORNM },
+ { featureCode("palt"), STR_FONT_FEATURE_ID_PALT },
+ { featureCode("pcap"), STR_FONT_FEATURE_ID_PCAP },
+ { featureCode("pkna"), STR_FONT_FEATURE_ID_PKNA },
+ { featureCode("pnum"), STR_FONT_FEATURE_ID_PNUM },
+ { featureCode("pwid"), STR_FONT_FEATURE_ID_PWID },
+ { featureCode("qwid"), STR_FONT_FEATURE_ID_QWID },
+ { featureCode("rtbd"), STR_FONT_FEATURE_ID_RTBD },
+ { featureCode("ruby"), STR_FONT_FEATURE_ID_RUBY },
+ { featureCode("salt"), STR_FONT_FEATURE_ID_SALT },
+ { featureCode("sinf"), STR_FONT_FEATURE_ID_SINF },
+ { featureCode("smcp"), STR_FONT_FEATURE_ID_SMCP },
+ { featureCode("smpl"), STR_FONT_FEATURE_ID_SMPL },
+ { featureCode("subs"), STR_FONT_FEATURE_ID_SUBS },
+ { featureCode("sups"), STR_FONT_FEATURE_ID_SUPS },
+ { featureCode("swsh"), STR_FONT_FEATURE_ID_SWSH },
+ { featureCode("titl"), STR_FONT_FEATURE_ID_TITL },
+ { featureCode("tnam"), STR_FONT_FEATURE_ID_TNAM },
+ { featureCode("tnum"), STR_FONT_FEATURE_ID_TNUM },
+ { featureCode("trad"), STR_FONT_FEATURE_ID_TRAD },
+ { featureCode("twid"), STR_FONT_FEATURE_ID_TWID },
+ { featureCode("unic"), STR_FONT_FEATURE_ID_UNIC },
+ { featureCode("valt"), STR_FONT_FEATURE_ID_VALT },
+ { featureCode("vhal"), STR_FONT_FEATURE_ID_VHAL },
+ { featureCode("vkna"), STR_FONT_FEATURE_ID_VKNA },
+ { featureCode("vkrn"), STR_FONT_FEATURE_ID_VKRN },
+ { featureCode("vpal"), STR_FONT_FEATURE_ID_VPAL },
+ { featureCode("vrt2"), STR_FONT_FEATURE_ID_VRT2 },
+ { featureCode("vrtr"), STR_FONT_FEATURE_ID_VRTR },
+ { featureCode("zero"), STR_FONT_FEATURE_ID_ZERO },
+ });
+
+ for (size_t i = 0; i < m_aFeatureDefinition.size(); ++i)
+ {
+ m_aCodeToIndex.emplace(m_aFeatureDefinition[i].getCode(), i);
+ }
+
+ m_aRequiredFeatures.assign({
+ featureCode("abvf"), featureCode("abvm"), featureCode("abvs"), featureCode("akhn"),
+ featureCode("blwf"), featureCode("blwm"), featureCode("blws"), featureCode("ccmp"),
+ featureCode("cfar"), featureCode("cjct"), featureCode("curs"), featureCode("dist"),
+ featureCode("dtls"), featureCode("fin2"), featureCode("fin3"), featureCode("fina"),
+ featureCode("flac"), featureCode("half"), featureCode("haln"), featureCode("init"),
+ featureCode("isol"), featureCode("ljmo"), featureCode("locl"), featureCode("ltra"),
+ featureCode("ltrm"), featureCode("mark"), featureCode("med2"), featureCode("medi"),
+ featureCode("mkmk"), featureCode("mset"), featureCode("nukt"), featureCode("pref"),
+ featureCode("pres"), featureCode("pstf"), featureCode("psts"), featureCode("rand"),
+ featureCode("rclt"), featureCode("rkrf"), featureCode("rlig"), featureCode("rphf"),
+ featureCode("rtla"), featureCode("rtlm"), featureCode("rvrn"), featureCode("size"),
+ featureCode("ssty"), featureCode("stch"), featureCode("tjmo"), featureCode("vatu"),
+ featureCode("vert"), featureCode("vjmo"),
+ });
+}
+
+namespace
+{
+bool isCharacterVariantCode(sal_uInt32 nFeatureCode)
+{
+ return char((sal_uInt32(nFeatureCode) >> 24) & 0xFF) == 'c'
+ && char((sal_uInt32(nFeatureCode) >> 16) & 0xFF) == 'v';
+}
+
+bool isStylisticSetCode(sal_uInt32 nFeatureCode)
+{
+ return char((sal_uInt32(nFeatureCode) >> 24) & 0xFF) == 's'
+ && char((sal_uInt32(nFeatureCode) >> 16) & 0xFF) == 's';
+}
+
+OUString getNumericLowerPart(sal_uInt32 nFeatureCode)
+{
+ char cChar1((sal_uInt32(nFeatureCode) >> 8) & 0xFF);
+ char cChar2((sal_uInt32(nFeatureCode) >> 0) & 0xFF);
+
+ if (rtl::isAsciiDigit(static_cast<unsigned char>(cChar1))
+ && rtl::isAsciiDigit(static_cast<unsigned char>(cChar2)))
+ {
+ return OUStringChar(cChar1) + OUStringChar(cChar2);
+ }
+ return OUString();
+}
+
+} // end anonymous namespace
+
+bool OpenTypeFeatureDefinitionListPrivate::isSpecialFeatureCode(sal_uInt32 nFeatureCode)
+{
+ return isCharacterVariantCode(nFeatureCode) || isStylisticSetCode(nFeatureCode);
+}
+
+FeatureDefinition
+OpenTypeFeatureDefinitionListPrivate::handleSpecialFeatureCode(sal_uInt32 nFeatureCode)
+{
+ FeatureDefinition aFeatureDefinition;
+ OUString sNumericPart = getNumericLowerPart(nFeatureCode);
+ if (!sNumericPart.isEmpty())
+ {
+ if (isCharacterVariantCode(nFeatureCode))
+ aFeatureDefinition = { nFeatureCode, STR_FONT_FEATURE_ID_CVXX, sNumericPart };
+ else if (isStylisticSetCode(nFeatureCode))
+ aFeatureDefinition = { nFeatureCode, STR_FONT_FEATURE_ID_SSXX, sNumericPart };
+ }
+ return aFeatureDefinition;
+}
+
+FeatureDefinition OpenTypeFeatureDefinitionListPrivate::getDefinition(sal_uInt32 nFeatureCode)
+{
+ if (isSpecialFeatureCode(nFeatureCode))
+ {
+ return handleSpecialFeatureCode(nFeatureCode);
+ }
+
+ if (m_aCodeToIndex.find(nFeatureCode) != m_aCodeToIndex.end())
+ {
+ size_t nIndex = m_aCodeToIndex.at(nFeatureCode);
+ return m_aFeatureDefinition[nIndex];
+ }
+ return FeatureDefinition();
+}
+
+bool OpenTypeFeatureDefinitionListPrivate::isRequired(sal_uInt32 nFeatureCode)
+{
+ return std::find(m_aRequiredFeatures.begin(), m_aRequiredFeatures.end(), nFeatureCode)
+ != m_aRequiredFeatures.end();
+}
+
+} // end vcl::font namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/font/PhysicalFontCollection.cxx b/vcl/source/font/PhysicalFontCollection.cxx
new file mode 100644
index 000000000..43ca203ff
--- /dev/null
+++ b/vcl/source/font/PhysicalFontCollection.cxx
@@ -0,0 +1,1267 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <i18nlangtag/mslangid.hxx>
+#include <unotools/configmgr.hxx>
+#include <unotools/fontdefs.hxx>
+
+#include <outdev.h>
+#include <PhysicalFontCollection.hxx>
+
+static ImplFontAttrs lcl_IsCJKFont( const OUString& rFontName )
+{
+ // Test, if Fontname includes CJK characters --> In this case we
+ // mention that it is a CJK font
+ for(int i = 0; i < rFontName.getLength(); i++)
+ {
+ const sal_Unicode ch = rFontName[i];
+ // japanese
+ if ( ((ch >= 0x3040) && (ch <= 0x30FF)) ||
+ ((ch >= 0x3190) && (ch <= 0x319F)) )
+ return ImplFontAttrs::CJK|ImplFontAttrs::CJK_JP;
+
+ // korean
+ if ( ((ch >= 0xAC00) && (ch <= 0xD7AF)) ||
+ ((ch >= 0xA960) && (ch <= 0xA97F)) ||
+ ((ch >= 0xD7B0) && (ch <= 0xD7FF)) ||
+ ((ch >= 0x3130) && (ch <= 0x318F)) ||
+ ((ch >= 0x1100) && (ch <= 0x11FF)) )
+ return ImplFontAttrs::CJK|ImplFontAttrs::CJK_KR;
+
+ // chinese
+ if ( (ch >= 0x3400) && (ch <= 0x9FFF) )
+ return ImplFontAttrs::CJK|ImplFontAttrs::CJK_TC|ImplFontAttrs::CJK_SC;
+
+ // cjk
+ if ( ((ch >= 0x3000) && (ch <= 0xD7AF)) ||
+ ((ch >= 0xFF00) && (ch <= 0xFFEE)) )
+ return ImplFontAttrs::CJK;
+
+ }
+
+ return ImplFontAttrs::None;
+}
+
+PhysicalFontCollection::PhysicalFontCollection()
+ : mbMatchData( false )
+ , mpPreMatchHook( nullptr )
+ , mpFallbackHook( nullptr )
+ , mnFallbackCount( -1 )
+{}
+
+PhysicalFontCollection::~PhysicalFontCollection()
+{
+ Clear();
+}
+
+void PhysicalFontCollection::SetPreMatchHook( ImplPreMatchFontSubstitution* pHook )
+{
+ mpPreMatchHook = pHook;
+}
+
+void PhysicalFontCollection::SetFallbackHook( ImplGlyphFallbackFontSubstitution* 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 );
+
+ // 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;
+
+ while( nStrIndex < rMissingCodes.getLength() )
+ {
+ cChar = rMissingCodes.iterateCodePoints( &nStrIndex );
+ bCached = pFontInstance->GetFallbackForUnicode( cChar, rFontSelData.GetWeight(), &aFontName );
+ if( !bCached || (rFontSelData.maSearchName != aFontName) )
+ 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();
+
+ // See fdo#32665 for an example. FreeSerif that has glyphs in normal
+ // font, but not in the italic or bold version
+ bool bSubSetOfFontRequiresPropertyFaking = rFontSelData.mbEmbolden || rFontSelData.maItalicMatrix != ItalicMatrix();
+
+ // Cache the result even if there was no match, unless its from part of a font for which the properties need
+ // to be faked. We need to rework this cache to take into account that fontconfig can return different fonts
+ // for different input sizes, weights, etc. Basically the cache is way to naive
+ if (!bSubSetOfFontRequiresPropertyFaking)
+ {
+ for(;;)
+ {
+ if( !pFontInstance->GetFallbackForUnicode( cChar, rFontSelData.GetWeight(), &rFontSelData.maSearchName ) )
+ pFontInstance->AddFallbackForUnicode( cChar, rFontSelData.GetWeight(), rFontSelData.maSearchName );
+ 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( const OUString& rFontName ) const
+{
+ return ImplFindFontFamilyBySearchName( GetEnglishSearchFontName( rFontName ) );
+}
+
+PhysicalFontFamily *PhysicalFontCollection::FindOrCreateFontFamily( const OUString &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(const OUString& rTokenStr) const
+{
+ PhysicalFontFamily* pFoundData = nullptr;
+
+ // use normalized font name tokens to find the font
+ for( sal_Int32 nTokenPos = 0; nTokenPos != -1; )
+ {
+ OUString aFamilyName = GetNextFontToken( rTokenStr, nTokenPos );
+ if( aFamilyName.isEmpty() )
+ continue;
+
+ pFoundData = FindFontFamily( aFamilyName );
+
+ if( pFoundData )
+ break;
+ }
+
+ return pFoundData;
+}
+
+PhysicalFontFamily* PhysicalFontCollection::ImplFindFontFamilyBySubstFontAttr( const utl::FontNameAttr& 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;
+ const OUString aSearchName;
+
+ pFoundData = FindFontFamilyByAttributes( nSearchType,
+ eSearchWeight, eSearchWidth, eSearchSlant, aSearchName );
+
+ 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,
+ const OUString& 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;
+
+ 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
+ long nTestMatch = 0;
+
+ // test CJK script attributes
+ if ( nSearchType & ImplFontAttrs::CJK )
+ {
+ // Matching language
+ if( ImplFontAttrs::None == ((nSearchType ^ nMatchType) & ImplFontAttrs::CJK_AllLang) )
+ nTestMatch += 10000000*3;
+ if( nMatchType & ImplFontAttrs::CJK )
+ nTestMatch += 10000000*2;
+ if( nMatchType & ImplFontAttrs::Full )
+ nTestMatch += 10000000;
+ }
+ else if ( nMatchType & ImplFontAttrs::CJK )
+ {
+ nTestMatch -= 10000000;
+ }
+
+ // test CTL script attributes
+ if( nSearchType & ImplFontAttrs::CTL )
+ {
+ if( nMatchType & ImplFontAttrs::CTL )
+ nTestMatch += 10000000*2;
+ if( nMatchType & ImplFontAttrs::Full )
+ nTestMatch += 10000000;
+ }
+ else if ( nMatchType & ImplFontAttrs::CTL )
+ {
+ nTestMatch -= 10000000;
+ }
+
+ // test LATIN script attributes
+ if( nSearchType & ImplFontAttrs::NoneLatin )
+ {
+ if( nMatchType & ImplFontAttrs::NoneLatin )
+ nTestMatch += 10000000*2;
+ if( nMatchType & ImplFontAttrs::Full )
+ nTestMatch += 10000000;
+ }
+
+ // test SYMBOL attributes
+ if ( nSearchType & ImplFontAttrs::Symbol )
+ {
+ const OUString& rSearchName = family.first;
+ // prefer some special known symbol fonts
+ if ( rSearchName == "starsymbol" )
+ {
+ nTestMatch += 10000000*6+(10000*3);
+ }
+ else if ( rSearchName == "opensymbol" )
+ {
+ nTestMatch += 10000000*6;
+ }
+ else if ( rSearchName == "starbats" ||
+ rSearchName == "wingdings" ||
+ rSearchName == "monotypesorts" ||
+ rSearchName == "dingbats" ||
+ rSearchName == "zapfdingbats" )
+ {
+ nTestMatch += 10000000*5;
+ }
+ else if ( pData->GetTypeFaces() & FontTypeFaces::Symbol )
+ {
+ nTestMatch += 10000000*4;
+ }
+ else
+ {
+ if( nMatchType & ImplFontAttrs::Symbol )
+ nTestMatch += 10000000*2;
+ if( nMatchType & ImplFontAttrs::Full )
+ nTestMatch += 10000000;
+ }
+ }
+ else if ( (pData->GetTypeFaces() & (FontTypeFaces::Symbol | FontTypeFaces::NoneSymbol)) == FontTypeFaces::Symbol )
+ {
+ nTestMatch -= 10000000;
+ }
+ else if ( nMatchType & ImplFontAttrs::Symbol )
+ {
+ nTestMatch -= 10000;
+ }
+
+ // match stripped family name
+ if( !rSearchFamilyName.isEmpty() && (rSearchFamilyName == pData->GetMatchFamilyName()) )
+ {
+ nTestMatch += 1000000*3;
+ }
+
+ // match ALLSCRIPT? attribute
+ if( nSearchType & ImplFontAttrs::AllScript )
+ {
+ if( nMatchType & ImplFontAttrs::AllScript )
+ {
+ nTestMatch += 1000000*2;
+ }
+ if( nSearchType & ImplFontAttrs::AllSubscript )
+ {
+ if( ImplFontAttrs::None == ((nSearchType ^ nMatchType) & ImplFontAttrs::AllSubscript) )
+ nTestMatch += 1000000*2;
+ if( ImplFontAttrs::None != ((nSearchType ^ nMatchType) & ImplFontAttrs::BrushScript) )
+ nTestMatch -= 1000000;
+ }
+ }
+ else if( nMatchType & ImplFontAttrs::AllScript )
+ {
+ nTestMatch -= 1000000;
+ }
+
+ // test MONOSPACE+TYPEWRITER attributes
+ if( nSearchType & ImplFontAttrs::Fixed )
+ {
+ if( nMatchType & ImplFontAttrs::Fixed )
+ nTestMatch += 1000000*2;
+ // a typewriter attribute is even better
+ if( ImplFontAttrs::None == ((nSearchType ^ nMatchType) & ImplFontAttrs::Typewriter) )
+ nTestMatch += 10000*2;
+ }
+ else if( nMatchType & ImplFontAttrs::Fixed )
+ {
+ nTestMatch -= 1000000;
+ }
+
+ // test SPECIAL attribute
+ if( nSearchType & ImplFontAttrs::Special )
+ {
+ if( nMatchType & ImplFontAttrs::Special )
+ {
+ nTestMatch += 10000;
+ }
+ else if( !(nSearchType & ImplFontAttrs::AllSerifStyle) )
+ {
+ if( nMatchType & ImplFontAttrs::Serif )
+ {
+ nTestMatch += 1000*2;
+ }
+ else if( nMatchType & ImplFontAttrs::SansSerif )
+ {
+ nTestMatch += 1000;
+ }
+ }
+ }
+ else if( (nMatchType & ImplFontAttrs::Special) && !(nSearchType & ImplFontAttrs::Symbol) )
+ {
+ nTestMatch -= 1000000;
+ }
+
+ // test DECORATIVE attribute
+ if( nSearchType & ImplFontAttrs::Decorative )
+ {
+ if( nMatchType & ImplFontAttrs::Decorative )
+ {
+ nTestMatch += 10000;
+ }
+ else if( !(nSearchType & ImplFontAttrs::AllSerifStyle) )
+ {
+ if( nMatchType & ImplFontAttrs::Serif )
+ nTestMatch += 1000*2;
+ else if ( nMatchType & ImplFontAttrs::SansSerif )
+ nTestMatch += 1000;
+ }
+ }
+ else if( nMatchType & ImplFontAttrs::Decorative )
+ {
+ nTestMatch -= 1000000;
+ }
+
+ // test TITLE+CAPITALS attributes
+ if( nSearchType & (ImplFontAttrs::Titling | ImplFontAttrs::Capitals) )
+ {
+ if( nMatchType & (ImplFontAttrs::Titling | ImplFontAttrs::Capitals) )
+ {
+ nTestMatch += 1000000*2;
+ }
+ if( ImplFontAttrs::None == ((nSearchType^nMatchType) & ImplFontAttrs(ImplFontAttrs::Titling | ImplFontAttrs::Capitals)))
+ {
+ nTestMatch += 1000000;
+ }
+ else if( (nMatchType & (ImplFontAttrs::Titling | ImplFontAttrs::Capitals)) &&
+ (nMatchType & (ImplFontAttrs::Standard | ImplFontAttrs::Default)) )
+ {
+ nTestMatch += 1000000;
+ }
+ }
+ else if( nMatchType & (ImplFontAttrs::Titling | ImplFontAttrs::Capitals) )
+ {
+ nTestMatch -= 1000000;
+ }
+
+ // test OUTLINE+SHADOW attributes
+ if( nSearchType & (ImplFontAttrs::Outline | ImplFontAttrs::Shadow) )
+ {
+ if( nMatchType & (ImplFontAttrs::Outline | ImplFontAttrs::Shadow) )
+ {
+ nTestMatch += 1000000*2;
+ }
+ if( ImplFontAttrs::None == ((nSearchType ^ nMatchType) & ImplFontAttrs(ImplFontAttrs::Outline | ImplFontAttrs::Shadow)) )
+ {
+ nTestMatch += 1000000;
+ }
+ else if( (nMatchType & (ImplFontAttrs::Outline | ImplFontAttrs::Shadow)) &&
+ (nMatchType & (ImplFontAttrs::Standard | ImplFontAttrs::Default)) )
+ {
+ nTestMatch += 1000000;
+ }
+ }
+ else if ( nMatchType & (ImplFontAttrs::Outline | ImplFontAttrs::Shadow) )
+ {
+ nTestMatch -= 1000000;
+ }
+
+ // test font name substrings
+ // TODO: calculate name matching score using e.g. Levenstein distance
+ if( (rSearchFamilyName.getLength() >= 4) &&
+ (pData->GetMatchFamilyName().getLength() >= 4) &&
+ ((rSearchFamilyName.indexOf( pData->GetMatchFamilyName() ) != -1) ||
+ (pData->GetMatchFamilyName().indexOf( rSearchFamilyName ) != -1)) )
+ {
+ nTestMatch += 5000;
+ }
+ // test SERIF attribute
+ if( nSearchType & ImplFontAttrs::Serif )
+ {
+ if( nMatchType & ImplFontAttrs::Serif )
+ nTestMatch += 1000000*2;
+ else if( nMatchType & ImplFontAttrs::SansSerif )
+ nTestMatch -= 1000000;
+ }
+
+ // test SANSERIF attribute
+ if( nSearchType & ImplFontAttrs::SansSerif )
+ {
+ if( nMatchType & ImplFontAttrs::SansSerif )
+ nTestMatch += 1000000;
+ else if ( nMatchType & ImplFontAttrs::Serif )
+ nTestMatch -= 1000000;
+ }
+
+ // test ITALIC attribute
+ if( nSearchType & ImplFontAttrs::Italic )
+ {
+ if( pData->GetTypeFaces() & FontTypeFaces::Italic )
+ nTestMatch += 1000000*3;
+ if( nMatchType & ImplFontAttrs::Italic )
+ nTestMatch += 1000000;
+ }
+ else if( !(nSearchType & ImplFontAttrs::AllScript) &&
+ ((nMatchType & ImplFontAttrs::Italic) ||
+ !(pData->GetTypeFaces() & FontTypeFaces::NoneItalic)) )
+ {
+ nTestMatch -= 1000000*2;
+ }
+
+ // test WIDTH attribute
+ if( (eSearchWidth != WIDTH_DONTKNOW) && (eSearchWidth != WIDTH_NORMAL) )
+ {
+ if( eSearchWidth < WIDTH_NORMAL )
+ {
+ if( eSearchWidth == eMatchWidth )
+ nTestMatch += 1000000*3;
+ else if( (eMatchWidth < WIDTH_NORMAL) && (eMatchWidth != WIDTH_DONTKNOW) )
+ nTestMatch += 1000000;
+ }
+ else
+ {
+ if( eSearchWidth == eMatchWidth )
+ nTestMatch += 1000000*3;
+ else if( eMatchWidth > WIDTH_NORMAL )
+ nTestMatch += 1000000;
+ }
+ }
+ else if( (eMatchWidth != WIDTH_DONTKNOW) && (eMatchWidth != WIDTH_NORMAL) )
+ {
+ nTestMatch -= 1000000;
+ }
+
+ // test WEIGHT attribute
+ if( (eSearchWeight != WEIGHT_DONTKNOW) &&
+ (eSearchWeight != WEIGHT_NORMAL) &&
+ (eSearchWeight != WEIGHT_MEDIUM) )
+ {
+ if( eSearchWeight < WEIGHT_NORMAL )
+ {
+ if( pData->GetTypeFaces() & FontTypeFaces::Light )
+ nTestMatch += 1000000;
+ if( (eMatchWeight < WEIGHT_NORMAL) && (eMatchWeight != WEIGHT_DONTKNOW) )
+ nTestMatch += 1000000;
+ }
+ else
+ {
+ if( pData->GetTypeFaces() & FontTypeFaces::Bold )
+ nTestMatch += 1000000;
+ if( eMatchWeight > WEIGHT_BOLD )
+ nTestMatch += 1000000;
+ }
+ }
+ else if( ((eMatchWeight != WEIGHT_DONTKNOW) &&
+ (eMatchWeight != WEIGHT_NORMAL) &&
+ (eMatchWeight != WEIGHT_MEDIUM)) ||
+ !(pData->GetTypeFaces() & FontTypeFaces::Normal) )
+ {
+ nTestMatch -= 1000000;
+ }
+
+ // prefer scalable fonts
+ if( pData->GetTypeFaces() & FontTypeFaces::Scalable )
+ nTestMatch += 10000*4;
+ else
+ nTestMatch -= 10000*4;
+
+ // test STANDARD+DEFAULT+FULL+NORMAL attributes
+ if( nMatchType & ImplFontAttrs::Standard )
+ nTestMatch += 10000*2;
+ if( nMatchType & ImplFontAttrs::Default )
+ nTestMatch += 10000;
+ if( nMatchType & ImplFontAttrs::Full )
+ nTestMatch += 10000;
+ if( nMatchType & ImplFontAttrs::Normal )
+ nTestMatch += 10000;
+
+ // test OTHERSTYLE attribute
+ if( ((nSearchType ^ nMatchType) & ImplFontAttrs::OtherStyle) != ImplFontAttrs::None )
+ {
+ nTestMatch -= 10000;
+ }
+
+ // test ROUNDED attribute
+ if( ImplFontAttrs::None == ((nSearchType ^ nMatchType) & ImplFontAttrs::Rounded) )
+ nTestMatch += 1000;
+
+ // test TYPEWRITER attribute
+ if( ImplFontAttrs::None == ((nSearchType ^ nMatchType) & ImplFontAttrs::Typewriter) )
+ nTestMatch += 1000;
+
+ // test GOTHIC attribute
+ if( nSearchType & ImplFontAttrs::Gothic )
+ {
+ if( nMatchType & ImplFontAttrs::Gothic )
+ nTestMatch += 1000*3;
+ if( nMatchType & ImplFontAttrs::SansSerif )
+ nTestMatch += 1000*2;
+ }
+
+ // test SCHOOLBOOK attribute
+ if( nSearchType & ImplFontAttrs::Schoolbook )
+ {
+ if( nMatchType & ImplFontAttrs::Schoolbook )
+ nTestMatch += 1000*3;
+ if( nMatchType & ImplFontAttrs::Serif )
+ nTestMatch += 1000*2;
+ }
+
+ // compare with best matching font yet
+ if ( nTestMatch > nBestMatch )
+ {
+ pFoundData = pData;
+ nBestMatch = nTestMatch;
+ nBestType = nMatchType;
+ }
+ else if( nTestMatch == nBestMatch )
+ {
+ // some fonts are more suitable defaults
+ if( nMatchType & ImplFontAttrs::Default )
+ {
+ pFoundData = pData;
+ nBestType = nMatchType;
+ }
+ else if( (nMatchType & ImplFontAttrs::Standard) &&
+ !(nBestType & ImplFontAttrs::Default) )
+ {
+ pFoundData = pData;
+ nBestType = nMatchType;
+ }
+ }
+ }
+
+ return pFoundData;
+}
+
+PhysicalFontFamily* PhysicalFontCollection::ImplFindFontFamilyOfDefaultFont() const
+{
+ // try to find one of the default fonts of the
+ // UNICODE, SANSSERIF, SERIF or FIXED default font lists
+ PhysicalFontFamily* pFoundData = nullptr;
+ if (!utl::ConfigManager::IsFuzzing())
+ {
+ const utl::DefaultFontConfiguration& rDefaults = utl::DefaultFontConfiguration::get();
+ LanguageTag aLanguageTag("en");
+ OUString aFontname = rDefaults.getDefaultFont( aLanguageTag, DefaultFontType::SANS_UNICODE );
+ pFoundData = FindFontFamilyByTokenNames( aFontname );
+
+ if( pFoundData )
+ return pFoundData;
+
+ aFontname = rDefaults.getDefaultFont( aLanguageTag, DefaultFontType::SANS );
+ pFoundData = FindFontFamilyByTokenNames( aFontname );
+ if( pFoundData )
+ return pFoundData;
+
+ aFontname = rDefaults.getDefaultFont( aLanguageTag, DefaultFontType::SERIF );
+ pFoundData = FindFontFamilyByTokenNames( aFontname );
+ if( pFoundData )
+ return pFoundData;
+
+ aFontname = rDefaults.getDefaultFont( aLanguageTag, DefaultFontType::FIXED );
+ pFoundData = FindFontFamilyByTokenNames( aFontname );
+ if( pFoundData )
+ return pFoundData;
+ }
+
+ // now try to find a reasonable non-symbol font
+
+ ImplInitMatchData();
+
+ for (auto const& family : maPhysicalFontFamilies)
+ {
+ PhysicalFontFamily* pData = family.second.get();
+ if( pData->GetMatchType() & ImplFontAttrs::Symbol )
+ continue;
+
+ pFoundData = pData;
+ if( pData->GetMatchType() & (ImplFontAttrs::Default|ImplFontAttrs::Standard) )
+ break;
+ }
+ if( pFoundData )
+ return pFoundData;
+
+ // finding any font is better than finding no font at all
+ auto it = maPhysicalFontFamilies.begin();
+ if( it != maPhysicalFontFamilies.end() )
+ pFoundData = (*it).second.get();
+
+ return pFoundData;
+}
+
+std::shared_ptr<PhysicalFontCollection> PhysicalFontCollection::Clone() const
+{
+ auto xClonedCollection = std::make_shared<PhysicalFontCollection>();
+ xClonedCollection->mpPreMatchHook = mpPreMatchHook;
+ xClonedCollection->mpFallbackHook = mpFallbackHook;
+
+ // TODO: clone the config-font attributes too?
+ xClonedCollection->mbMatchData = false;
+
+ for (auto const& family : maPhysicalFontFamilies)
+ {
+ const PhysicalFontFamily* pFontFace = family.second.get();
+ pFontFace->UpdateCloneFontList(*xClonedCollection);
+ }
+
+ return xClonedCollection;
+}
+
+std::unique_ptr<ImplDeviceFontList> PhysicalFontCollection::GetDeviceFontList() const
+{
+ std::unique_ptr<ImplDeviceFontList> pDeviceFontList(new ImplDeviceFontList);
+
+ for (auto const& family : maPhysicalFontFamilies)
+ {
+ const PhysicalFontFamily* pFontFamily = family.second.get();
+ pFontFamily->UpdateDevFontList( *pDeviceFontList );
+ }
+
+ return pDeviceFontList;
+}
+
+std::unique_ptr<ImplDeviceFontSizeList> PhysicalFontCollection::GetDeviceFontSizeList( const OUString& rFontName ) const
+{
+ std::unique_ptr<ImplDeviceFontSizeList> pDeviceFontSizeList(new ImplDeviceFontSizeList);
+
+ PhysicalFontFamily* pFontFamily = FindFontFamily( rFontName );
+ if( pFontFamily != nullptr )
+ {
+ std::set<int> rHeights;
+ pFontFamily->GetFontHeights( rHeights );
+
+ for( const auto& rHeight : rHeights )
+ pDeviceFontSizeList->Add( rHeight );
+ }
+
+ return pDeviceFontSizeList;
+}
+
+// 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.
+static 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;
+
+ if (getenv("SAL_NO_FONT_LOOKUP") != nullptr)
+ {
+ // Hard code the use of Liberation Sans and skip font search.
+ sal_Int32 nIndex = 0;
+ rFSD.maTargetName = GetNextFontToken(rFSD.GetFamilyName(), nIndex);
+ rFSD.maSearchName = "liberationsans";
+ PhysicalFontFamily* pFont = ImplFindFontFamilyBySearchName(rFSD.maSearchName);
+ assert(pFont);
+ return pFont;
+ }
+
+ bool bMultiToken = false;
+ sal_Int32 nTokenPos = 0;
+ OUString& aSearchName = rFSD.maSearchName; // TODO: get rid of reference
+ for(;;)
+ {
+ rFSD.maTargetName = GetNextFontToken( rFSD.GetFamilyName(), nTokenPos );
+ aSearchName = rFSD.maTargetName;
+
+ // Until features are properly supported, they are appended to the
+ // font name, so we need to strip them off so the font is found.
+ sal_Int32 nFeat = aSearchName.indexOf(FontSelectPattern::FEAT_PREFIX);
+ OUString aOrigName = rFSD.maTargetName;
+ OUString aBaseFontName = aSearchName.copy( 0, (nFeat != -1) ? nFeat : aSearchName.getLength() );
+
+ if (nFeat != -1)
+ {
+ aSearchName = aBaseFontName;
+ rFSD.maTargetName = aBaseFontName;
+ }
+
+ aSearchName = GetEnglishSearchFontName( aSearchName );
+ ImplFontSubstitute( aSearchName );
+ // #114999# special emboldening for Ricoh fonts
+ // TODO: smarter check for special cases by using PreMatch infrastructure?
+ if( (rFSD.GetWeight() > WEIGHT_MEDIUM) &&
+ aSearchName.startsWithIgnoreAsciiCase( "hg" ) )
+ {
+ OUString aBoldName;
+ if( aSearchName.startsWithIgnoreAsciiCase( "hggothicb" ) )
+ aBoldName = "hggothice";
+ else if( aSearchName.startsWithIgnoreAsciiCase( "hgpgothicb" ) )
+ aBoldName = "hgpgothice";
+ else if( aSearchName.startsWithIgnoreAsciiCase( "hgminchol" ) )
+ aBoldName = "hgminchob";
+ else if( aSearchName.startsWithIgnoreAsciiCase( "hgpminchol" ) )
+ aBoldName = "hgpminchob";
+ else if( aSearchName.equalsIgnoreAsciiCase( "hgminchob" ) )
+ aBoldName = "hgminchoe";
+ else if( aSearchName.equalsIgnoreAsciiCase( "hgpminchob" ) )
+ aBoldName = "hgpminchoe";
+
+ if( !aBoldName.isEmpty() && ImplFindFontFamilyBySearchName( aBoldName ) )
+ {
+ // the other font is available => use it
+ aSearchName = aBoldName;
+ // prevent synthetic emboldening of bold version
+ rFSD.SetWeight(WEIGHT_DONTKNOW);
+ }
+ }
+
+ // restore the features to make the font selection data unique
+ rFSD.maTargetName = aOrigName;
+
+ // check if the current font name token or its substitute is valid
+ PhysicalFontFamily* pFoundData = ImplFindFontFamilyBySearchName( aSearchName );
+ if( pFoundData )
+ return pFoundData;
+
+ // some systems provide special customization
+ // e.g. they suggest "serif" as UI-font, but this name cannot be used directly
+ // because the system wants to map it to another font first, e.g. "Helvetica"
+
+ // use the target name to search in the prematch hook
+ rFSD.maTargetName = aBaseFontName;
+
+ // Related: fdo#49271 RTF files often contain weird-ass
+ // Win 3.1/Win95 style fontnames which attempt to put the
+ // charset encoding into the filename
+ // http://www.webcenter.ru/~kazarn/eng/fonts_ttf.htm
+ OUString sStrippedName = StripScriptFromName(rFSD.maTargetName);
+ if (sStrippedName != rFSD.maTargetName)
+ {
+ rFSD.maTargetName = sStrippedName;
+ aSearchName = GetEnglishSearchFontName(rFSD.maTargetName);
+ pFoundData = ImplFindFontFamilyBySearchName(aSearchName);
+ if( pFoundData )
+ return pFoundData;
+ }
+
+ if (FindMetricCompatibleFont(rFSD) ||
+ (mpPreMatchHook && mpPreMatchHook->FindFontSubstitute(rFSD)))
+ {
+ aSearchName = GetEnglishSearchFontName(aSearchName);
+ }
+
+ // the prematch hook uses the target name to search, but we now need
+ // to restore the features to make the font selection data unique
+ rFSD.maTargetName = aOrigName;
+
+ pFoundData = ImplFindFontFamilyBySearchName( aSearchName );
+ if( pFoundData )
+ return pFoundData;
+
+ // break after last font name token was checked unsuccessfully
+ if( nTokenPos == -1)
+ break;
+ bMultiToken = true;
+ }
+
+ // if the first font was not available find the next available font in
+ // the semicolon separated list of font names. A font is also considered
+ // available when there is a matching entry in the Tools->Options->Fonts
+ // dialog with neither ALWAYS nor SCREENONLY flags set and the substitution
+ // font is available
+ for( nTokenPos = 0; nTokenPos != -1; )
+ {
+ if( bMultiToken )
+ {
+ rFSD.maTargetName = GetNextFontToken( rFSD.GetFamilyName(), nTokenPos );
+ aSearchName = GetEnglishSearchFontName( rFSD.maTargetName );
+ }
+ else
+ nTokenPos = -1;
+ if (FindMetricCompatibleFont(rFSD) ||
+ (mpPreMatchHook && mpPreMatchHook->FindFontSubstitute(rFSD)))
+ {
+ aSearchName = GetEnglishSearchFontName( aSearchName );
+ }
+ ImplFontSubstitute( aSearchName );
+ PhysicalFontFamily* pFoundData = ImplFindFontFamilyBySearchName( aSearchName );
+ if( pFoundData )
+ return pFoundData;
+ }
+
+ // if no font with a directly matching name is available use the
+ // first font name token and get its attributes to find a replacement
+ if ( bMultiToken )
+ {
+ nTokenPos = 0;
+ rFSD.maTargetName = GetNextFontToken( rFSD.GetFamilyName(), nTokenPos );
+ aSearchName = GetEnglishSearchFontName( rFSD.maTargetName );
+ }
+
+ OUString aSearchShortName;
+ OUString aSearchFamilyName;
+ FontWeight eSearchWeight = rFSD.GetWeight();
+ FontWidth eSearchWidth = rFSD.GetWidthType();
+ ImplFontAttrs nSearchType = ImplFontAttrs::None;
+ utl::FontSubstConfiguration::getMapName( aSearchName, aSearchShortName, aSearchFamilyName,
+ eSearchWeight, eSearchWidth, nSearchType );
+
+ // note: the search name was already translated to english (if possible)
+ // use the font's shortened name if needed
+ if ( aSearchShortName != aSearchName )
+ {
+ PhysicalFontFamily* pFoundData = ImplFindFontFamilyBySearchName( aSearchShortName );
+ if( pFoundData )
+ {
+#ifdef UNX
+ /* #96738# don't use mincho as a replacement for "MS Mincho" on X11: Mincho is
+ a korean bitmap font that is not suitable here. Use the font replacement table,
+ that automatically leads to the desired "HG Mincho Light J". Same story for
+ MS Gothic, there are thai and korean "Gothic" fonts, so we even prefer Andale */
+ if ((aSearchName != "msmincho") && (aSearchName != "msgothic"))
+ // TODO: add heuristic to only throw out the fake ms* fonts
+#endif
+ {
+ return pFoundData;
+ }
+ }
+ }
+
+ // use font fallback
+ const utl::FontNameAttr* pFontAttr = nullptr;
+ if (!aSearchName.isEmpty() && !utl::ConfigManager::IsFuzzing())
+ {
+ // get fallback info using FontSubstConfiguration and
+ // the target name, it's shortened name and family name in that order
+ const utl::FontSubstConfiguration& rFontSubst = utl::FontSubstConfiguration::get();
+ pFontAttr = rFontSubst.getSubstInfo( aSearchName );
+ if ( !pFontAttr && (aSearchShortName != aSearchName) )
+ pFontAttr = rFontSubst.getSubstInfo( aSearchShortName );
+ if ( !pFontAttr && (aSearchFamilyName != aSearchShortName) )
+ pFontAttr = rFontSubst.getSubstInfo( aSearchFamilyName );
+
+ // try the font substitutions suggested by the fallback info
+ if( pFontAttr )
+ {
+ PhysicalFontFamily* pFoundData = ImplFindFontFamilyBySubstFontAttr( *pFontAttr );
+ if( pFoundData )
+ return pFoundData;
+ }
+ }
+
+ // if a target symbol font is not available use a default symbol font
+ if( rFSD.IsSymbolFont() )
+ {
+ LanguageTag aDefaultLanguageTag("en");
+ if (utl::ConfigManager::IsFuzzing())
+ aSearchName = "OpenSymbol";
+ else
+ aSearchName = utl::DefaultFontConfiguration::get().getDefaultFont( aDefaultLanguageTag, DefaultFontType::SYMBOL );
+ PhysicalFontFamily* pFoundData = FindFontFamilyByTokenNames( aSearchName );
+ if( pFoundData )
+ return pFoundData;
+ }
+
+ // now try the other font name tokens
+ while( nTokenPos != -1 )
+ {
+ rFSD.maTargetName = GetNextFontToken( rFSD.GetFamilyName(), nTokenPos );
+ if( rFSD.maTargetName.isEmpty() )
+ continue;
+
+ aSearchName = GetEnglishSearchFontName( rFSD.maTargetName );
+
+ OUString aTempShortName;
+ OUString aTempFamilyName;
+ ImplFontAttrs nTempType = ImplFontAttrs::None;
+ FontWeight eTempWeight = rFSD.GetWeight();
+ FontWidth eTempWidth = WIDTH_DONTKNOW;
+ utl::FontSubstConfiguration::getMapName( aSearchName, aTempShortName, aTempFamilyName,
+ eTempWeight, eTempWidth, nTempType );
+
+ // use a shortened token name if available
+ if( aTempShortName != aSearchName )
+ {
+ PhysicalFontFamily* pFoundData = ImplFindFontFamilyBySearchName( aTempShortName );
+ if( pFoundData )
+ return pFoundData;
+ }
+
+ const utl::FontNameAttr* pTempFontAttr = nullptr;
+ if (!utl::ConfigManager::IsFuzzing())
+ {
+ // use a font name from font fallback list to determine font attributes
+ // get fallback info using FontSubstConfiguration and
+ // the target name, it's shortened name and family name in that order
+ const utl::FontSubstConfiguration& rFontSubst = utl::FontSubstConfiguration::get();
+ pTempFontAttr = rFontSubst.getSubstInfo( aSearchName );
+
+ if ( !pTempFontAttr && (aTempShortName != aSearchName) )
+ pTempFontAttr = rFontSubst.getSubstInfo( aTempShortName );
+
+ if ( !pTempFontAttr && (aTempFamilyName != aTempShortName) )
+ pTempFontAttr = rFontSubst.getSubstInfo( aTempFamilyName );
+ }
+
+ // try the font substitutions suggested by the fallback info
+ if( pTempFontAttr )
+ {
+ PhysicalFontFamily* pFoundData = ImplFindFontFamilyBySubstFontAttr( *pTempFontAttr );
+ if( pFoundData )
+ return pFoundData;
+ if( !pFontAttr )
+ pFontAttr = pTempFontAttr;
+ }
+ }
+
+ // if still needed use the font request's attributes to find a good match
+ if (MsLangId::isSimplifiedChinese(rFSD.meLanguage))
+ nSearchType |= ImplFontAttrs::CJK | ImplFontAttrs::CJK_SC;
+ else if (MsLangId::isTraditionalChinese(rFSD.meLanguage))
+ nSearchType |= ImplFontAttrs::CJK | ImplFontAttrs::CJK_TC;
+ else if (MsLangId::isKorean(rFSD.meLanguage))
+ nSearchType |= ImplFontAttrs::CJK | ImplFontAttrs::CJK_KR;
+ else if (rFSD.meLanguage == LANGUAGE_JAPANESE)
+ nSearchType |= ImplFontAttrs::CJK | ImplFontAttrs::CJK_JP;
+ else
+ {
+ nSearchType |= lcl_IsCJKFont( rFSD.GetFamilyName() );
+ if( rFSD.IsSymbolFont() )
+ nSearchType |= ImplFontAttrs::Symbol;
+ }
+
+ PhysicalFontFamily::CalcType( nSearchType, eSearchWeight, eSearchWidth, rFSD.GetFamilyType(), pFontAttr );
+ PhysicalFontFamily* pFoundData = FindFontFamilyByAttributes( nSearchType,
+ eSearchWeight, eSearchWidth, rFSD.GetItalic(), aSearchFamilyName );
+
+ if( pFoundData )
+ {
+ // overwrite font selection attributes using info from the typeface flags
+ if( (eSearchWeight >= WEIGHT_BOLD) &&
+ (eSearchWeight > rFSD.GetWeight()) &&
+ (pFoundData->GetTypeFaces() & FontTypeFaces::Bold) )
+ {
+ rFSD.SetWeight( eSearchWeight );
+ }
+ else if( (eSearchWeight < WEIGHT_NORMAL) &&
+ (eSearchWeight < rFSD.GetWeight()) &&
+ (eSearchWeight != WEIGHT_DONTKNOW) &&
+ (pFoundData->GetTypeFaces() & FontTypeFaces::Light) )
+ {
+ rFSD.SetWeight( eSearchWeight );
+ }
+
+ if( (nSearchType & ImplFontAttrs::Italic) &&
+ ((rFSD.GetItalic() == ITALIC_DONTKNOW) ||
+ (rFSD.GetItalic() == ITALIC_NONE)) &&
+ (pFoundData->GetTypeFaces() & FontTypeFaces::Italic) )
+ {
+ rFSD.SetItalic( ITALIC_NORMAL );
+ }
+ }
+ else
+ {
+ // if still needed fall back to default fonts
+ pFoundData = ImplFindFontFamilyOfDefaultFont();
+ }
+
+ return pFoundData;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
+
diff --git a/vcl/source/font/PhysicalFontFace.cxx b/vcl/source/font/PhysicalFontFace.cxx
new file mode 100644
index 000000000..b16488ddc
--- /dev/null
+++ b/vcl/source/font/PhysicalFontFace.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 <sal/types.h>
+#include <tools/fontenum.hxx>
+#include <unotools/fontdefs.hxx>
+
+#include <fontattributes.hxx>
+#include <fontselect.hxx>
+
+#include <PhysicalFontFace.hxx>
+
+PhysicalFontFace::PhysicalFontFace( const FontAttributes& rDFA )
+ : FontAttributes( rDFA )
+ , mnWidth(0)
+ , mnHeight(0)
+{
+ // StarSymbol is a unicode font, but it still deserves the symbol flag
+ if( !IsSymbolFont() )
+ if ( IsStarSymbol( GetFamilyName() ) )
+ SetSymbolFlag( true );
+}
+
+sal_Int32 PhysicalFontFace::CompareIgnoreSize( const PhysicalFontFace& rOther ) const
+{
+ // compare their width, weight, italic, style name and family name
+ if( GetWidthType() < rOther.GetWidthType() )
+ return -1;
+ else if( GetWidthType() > rOther.GetWidthType() )
+ return 1;
+
+ if( GetWeight() < rOther.GetWeight() )
+ return -1;
+ else if( GetWeight() > rOther.GetWeight() )
+ return 1;
+
+ if( GetItalic() < rOther.GetItalic() )
+ return -1;
+ else if( GetItalic() > rOther.GetItalic() )
+ return 1;
+
+ sal_Int32 nRet = GetFamilyName().compareTo( rOther.GetFamilyName() );
+
+ if (nRet == 0)
+ {
+ nRet = GetStyleName().compareTo( rOther.GetStyleName() );
+ }
+
+ return nRet;
+}
+
+sal_Int32 PhysicalFontFace::CompareWithSize( const PhysicalFontFace& rOther ) const
+{
+ sal_Int32 nCompare = CompareIgnoreSize( rOther );
+ if (nCompare != 0)
+ return nCompare;
+
+ if( mnHeight < rOther.mnHeight )
+ return -1;
+ else if( mnHeight > rOther.mnHeight )
+ return 1;
+
+ if( mnWidth < rOther.mnWidth )
+ return -1;
+ else if( mnWidth > rOther.mnWidth )
+ return 1;
+
+ return 0;
+}
+
+bool PhysicalFontFace::IsBetterMatch( const FontSelectPattern& rFSD, FontMatchStatus& rStatus ) const
+{
+ int nMatch = 0;
+
+ const OUString& rFontName = rFSD.maTargetName;
+ if( rFontName.equalsIgnoreAsciiCase( GetFamilyName() ) )
+ nMatch += 240000;
+
+ if( rStatus.mpTargetStyleName
+ && GetStyleName().equalsIgnoreAsciiCase( *rStatus.mpTargetStyleName ) )
+ nMatch += 120000;
+
+ if( (rFSD.GetPitch() != PITCH_DONTKNOW) && (rFSD.GetPitch() == GetPitch()) )
+ nMatch += 20000;
+
+ // prefer NORMAL font width
+ // TODO: change when the upper layers can tell their width preference
+ if( GetWidthType() == WIDTH_NORMAL )
+ nMatch += 400;
+ else if( (GetWidthType() == WIDTH_SEMI_EXPANDED) || (GetWidthType() == WIDTH_SEMI_CONDENSED) )
+ nMatch += 300;
+
+ if( rFSD.GetWeight() != WEIGHT_DONTKNOW )
+ {
+ // if not bold or requiring emboldening prefer light fonts to bold fonts
+ FontWeight ePatternWeight = rFSD.mbEmbolden ? WEIGHT_NORMAL : rFSD.GetWeight();
+
+ int nReqWeight = static_cast<int>(ePatternWeight);
+ if ( ePatternWeight > WEIGHT_MEDIUM )
+ nReqWeight += 100;
+
+ int nGivenWeight = static_cast<int>(GetWeight());
+ if( GetWeight() > 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 // requested weight == WEIGHT_DONTKNOW
+ {
+ // prefer NORMAL font weight
+ // TODO: change when the upper layers can tell their weight preference
+ if( GetWeight() == WEIGHT_NORMAL )
+ nMatch += 450;
+ else if( GetWeight() == WEIGHT_MEDIUM )
+ nMatch += 350;
+ else if( (GetWeight() == WEIGHT_SEMILIGHT) || (GetWeight() == WEIGHT_SEMIBOLD) )
+ nMatch += 200;
+ else if( GetWeight() == WEIGHT_LIGHT )
+ nMatch += 150;
+ }
+
+ // if requiring custom matrix to fake italic, prefer upright font
+ FontItalic ePatternItalic = rFSD.maItalicMatrix != ItalicMatrix() ? ITALIC_NONE : rFSD.GetItalic();
+
+ if ( ePatternItalic == ITALIC_NONE )
+ {
+ if( GetItalic() == ITALIC_NONE )
+ nMatch += 900;
+ }
+ else
+ {
+ if( ePatternItalic == GetItalic() )
+ nMatch += 900;
+ else if( GetItalic() != ITALIC_NONE )
+ nMatch += 600;
+ }
+
+ int nHeightMatch = 0;
+ int nWidthMatch = 0;
+
+ if( rFSD.mnOrientation != 0 )
+ nMatch += 80;
+ else if( rFSD.mnWidth != 0 )
+ nMatch += 25;
+ else
+ nMatch += 5;
+
+ if( rStatus.mnFaceMatch > nMatch )
+ return false;
+ else if( rStatus.mnFaceMatch < nMatch )
+ {
+ rStatus.mnFaceMatch = nMatch;
+ rStatus.mnHeightMatch = nHeightMatch;
+ rStatus.mnWidthMatch = nWidthMatch;
+ return true;
+ }
+
+ // when two fonts are still competing prefer the
+ // one with the best matching height
+ if( rStatus.mnHeightMatch > nHeightMatch )
+ return false;
+ else if( rStatus.mnHeightMatch < nHeightMatch )
+ {
+ rStatus.mnHeightMatch = nHeightMatch;
+ rStatus.mnWidthMatch = nWidthMatch;
+ return true;
+ }
+
+ if( rStatus.mnWidthMatch > nWidthMatch )
+ return false;
+
+ rStatus.mnWidthMatch = nWidthMatch;
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/font/PhysicalFontFamily.cxx b/vcl/source/font/PhysicalFontFamily.cxx
new file mode 100644
index 000000000..b8c630b6a
--- /dev/null
+++ b/vcl/source/font/PhysicalFontFamily.cxx
@@ -0,0 +1,273 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <unotools/fontdefs.hxx>
+
+#include <outdev.h>
+#include <PhysicalFontCollection.hxx>
+
+void PhysicalFontFamily::CalcType( ImplFontAttrs& rType, FontWeight& rWeight, FontWidth& rWidth,
+ FontFamily eFamily, const utl::FontNameAttr* pFontAttr )
+{
+ if ( eFamily != FAMILY_DONTKNOW )
+ {
+ if ( eFamily == FAMILY_SWISS )
+ rType |= ImplFontAttrs::SansSerif;
+ else if ( eFamily == FAMILY_ROMAN )
+ rType |= ImplFontAttrs::Serif;
+ else if ( eFamily == FAMILY_SCRIPT )
+ rType |= ImplFontAttrs::Script;
+ else if ( eFamily == FAMILY_MODERN )
+ rType |= ImplFontAttrs::Fixed;
+ else if ( eFamily == FAMILY_DECORATIVE )
+ rType |= ImplFontAttrs::Decorative;
+ }
+
+ if ( pFontAttr )
+ {
+ rType |= pFontAttr->Type;
+
+ if ( ((rWeight == WEIGHT_DONTKNOW) || (rWeight == WEIGHT_NORMAL)) &&
+ (pFontAttr->Weight != WEIGHT_DONTKNOW) )
+ rWeight = pFontAttr->Weight;
+ if ( ((rWidth == WIDTH_DONTKNOW) || (rWidth == WIDTH_NORMAL)) &&
+ (pFontAttr->Width != WIDTH_DONTKNOW) )
+ rWidth = pFontAttr->Width;
+ }
+}
+
+static ImplFontAttrs lcl_IsCJKFont( const OUString& rFontName )
+{
+ // Test, if Fontname includes CJK characters --> In this case we
+ // mention that it is a CJK font
+ for(int i = 0; i < rFontName.getLength(); i++)
+ {
+ const sal_Unicode ch = rFontName[i];
+ // japanese
+ if ( ((ch >= 0x3040) && (ch <= 0x30FF)) ||
+ ((ch >= 0x3190) && (ch <= 0x319F)) )
+ return ImplFontAttrs::CJK|ImplFontAttrs::CJK_JP;
+
+ // korean
+ if ( ((ch >= 0xAC00) && (ch <= 0xD7AF)) ||
+ ((ch >= 0xA960) && (ch <= 0xA97F)) ||
+ ((ch >= 0xD7B0) && (ch <= 0xD7FF)) ||
+ ((ch >= 0x3130) && (ch <= 0x318F)) ||
+ ((ch >= 0x1100) && (ch <= 0x11FF)) )
+ return ImplFontAttrs::CJK|ImplFontAttrs::CJK_KR;
+
+ // chinese
+ if ( (ch >= 0x3400) && (ch <= 0x9FFF) )
+ return ImplFontAttrs::CJK|ImplFontAttrs::CJK_TC|ImplFontAttrs::CJK_SC;
+
+ // cjk
+ if ( ((ch >= 0x3000) && (ch <= 0xD7AF)) ||
+ ((ch >= 0xFF00) && (ch <= 0xFFEE)) )
+ return ImplFontAttrs::CJK;
+
+ }
+
+ return ImplFontAttrs::None;
+}
+
+PhysicalFontFamily::PhysicalFontFamily( const OUString& rSearchName )
+: maSearchName( rSearchName ),
+ mnTypeFaces( FontTypeFaces::NONE ),
+ meFamily( FAMILY_DONTKNOW ),
+ mePitch( PITCH_DONTKNOW ),
+ mnMinQuality( -1 ),
+ mnMatchType( ImplFontAttrs::None ),
+ meMatchWeight( WEIGHT_DONTKNOW ),
+ meMatchWidth( WIDTH_DONTKNOW )
+{}
+
+PhysicalFontFamily::~PhysicalFontFamily()
+{
+}
+
+void PhysicalFontFamily::AddFontFace( PhysicalFontFace* pNewFontFace )
+{
+ if( maFontFaces.empty() )
+ {
+ maFamilyName = pNewFontFace->GetFamilyName();
+ maMapNames = pNewFontFace->GetMapNames();
+ meFamily = pNewFontFace->GetFamilyType();
+ mePitch = pNewFontFace->GetPitch();
+ mnMinQuality = pNewFontFace->GetQuality();
+ }
+ else
+ {
+ if( meFamily == FAMILY_DONTKNOW )
+ meFamily = pNewFontFace->GetFamilyType();
+ if( mePitch == PITCH_DONTKNOW )
+ mePitch = pNewFontFace->GetPitch();
+ if( mnMinQuality > pNewFontFace->GetQuality() )
+ mnMinQuality = pNewFontFace->GetQuality();
+ }
+
+ // set attributes for attribute based font matching
+ mnTypeFaces |= FontTypeFaces::Scalable;
+
+ if( pNewFontFace->IsSymbolFont() )
+ mnTypeFaces |= FontTypeFaces::Symbol;
+ else
+ mnTypeFaces |= FontTypeFaces::NoneSymbol;
+
+ if( pNewFontFace->GetWeight() != WEIGHT_DONTKNOW )
+ {
+ if( pNewFontFace->GetWeight() >= WEIGHT_SEMIBOLD )
+ mnTypeFaces |= FontTypeFaces::Bold;
+ else if( pNewFontFace->GetWeight() <= WEIGHT_SEMILIGHT )
+ mnTypeFaces |= FontTypeFaces::Light;
+ else
+ mnTypeFaces |= FontTypeFaces::Normal;
+ }
+
+ if( pNewFontFace->GetItalic() == ITALIC_NONE )
+ mnTypeFaces |= FontTypeFaces::NoneItalic;
+ else if( (pNewFontFace->GetItalic() == ITALIC_NORMAL)
+ || (pNewFontFace->GetItalic() == ITALIC_OBLIQUE) )
+ mnTypeFaces |= FontTypeFaces::Italic;
+
+ // reassign name (sharing saves memory)
+ if( pNewFontFace->GetFamilyName() == GetFamilyName() )
+ pNewFontFace->SetFamilyName( GetFamilyName() );
+
+ // add the new physical font face, replacing existing font face if necessary
+ // TODO: get rid of linear search?
+ auto it(maFontFaces.begin());
+ for (; it != maFontFaces.end(); ++it)
+ {
+ PhysicalFontFace* pFoundFontFace = it->get();
+ sal_Int32 eComp = pNewFontFace->CompareWithSize( *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 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,0,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( ImplDeviceFontList& 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::GetFontHeights( std::set<int>& rHeights ) const
+{
+ // add all available font heights
+ for (auto const& font : maFontFaces)
+ {
+ PhysicalFontFace *pFoundFontFace = font.get();
+ rHeights.insert( pFoundFontFace->GetHeight() );
+ }
+}
+
+void PhysicalFontFamily::UpdateCloneFontList(PhysicalFontCollection& rFontCollection) const
+{
+ OUString aFamilyName = GetEnglishSearchFontName( GetFamilyName() );
+ PhysicalFontFamily* pFamily(nullptr);
+
+ for (auto const& font : maFontFaces)
+ {
+ PhysicalFontFace *pFoundFontFace = font.get();
+
+ if (!pFamily)
+ { // tdf#98989 lazy create as family without faces won't work
+ pFamily = rFontCollection.FindOrCreateFontFamily(aFamilyName);
+ }
+ assert(pFamily);
+ pFamily->AddFontFace( pFoundFontFace );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/font/font.cxx b/vcl/source/font/font.cxx
new file mode 100644
index 000000000..4db757cd7
--- /dev/null
+++ b/vcl/source/font/font.cxx
@@ -0,0 +1,894 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/fontcfg.hxx>
+#include <unotools/fontdefs.hxx>
+
+#include <vcl/font.hxx>
+
+#include <impfont.hxx>
+#include <fontattributes.hxx>
+#include <sft.hxx>
+
+#include <algorithm>
+
+#include <rtl/instance.hxx>
+#include <TypeSerializer.hxx>
+
+using namespace vcl;
+
+namespace
+{
+ struct theGlobalDefault :
+ public rtl::Static< Font::ImplType, theGlobalDefault > {};
+}
+
+Font::Font() : mpImplFont(theGlobalDefault::get())
+{
+}
+
+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 ) : mpImplFont()
+{
+ mpImplFont->SetFamilyName( rFamilyName );
+ mpImplFont->SetFontSize( rSize );
+}
+
+Font::Font( const OUString& rFamilyName, const OUString& rStyleName, const Size& rSize ) : mpImplFont()
+{
+ mpImplFont->SetFamilyName( rFamilyName );
+ mpImplFont->SetStyleName( rStyleName );
+ mpImplFont->SetFontSize( rSize );
+}
+
+Font::Font( FontFamily eFamily, const Size& rSize ) : mpImplFont()
+{
+ mpImplFont->SetFamilyType( eFamily );
+ mpImplFont->SetFontSize( rSize );
+}
+
+Font::~Font()
+{
+}
+
+void Font::SetColor( const Color& rColor )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->maColor != rColor)
+ {
+ mpImplFont->maColor = rColor;
+ }
+}
+
+void Font::SetFillColor( const Color& rColor )
+{
+ mpImplFont->maFillColor = rColor;
+ if ( rColor.GetTransparency() )
+ mpImplFont->mbTransparent = true;
+}
+
+void Font::SetTransparent( bool bTransparent )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->mbTransparent != bTransparent)
+ mpImplFont->mbTransparent = bTransparent;
+}
+
+void Font::SetAlignment( FontAlign eAlign )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->meAlign != eAlign)
+ mpImplFont->SetAlignment(eAlign);
+}
+
+void Font::SetFamilyName( const OUString& rFamilyName )
+{
+ mpImplFont->SetFamilyName( rFamilyName );
+}
+
+void Font::SetStyleName( const OUString& rStyleName )
+{
+ mpImplFont->maStyleName = rStyleName;
+}
+
+void Font::SetFontSize( const Size& rSize )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->GetFontSize() != rSize)
+ mpImplFont->SetFontSize( rSize );
+}
+
+void Font::SetFamily( FontFamily eFamily )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->GetFamilyTypeNoAsk() != eFamily)
+ mpImplFont->SetFamilyType( eFamily );
+}
+
+void Font::SetCharSet( rtl_TextEncoding eCharSet )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->GetCharSet() != eCharSet)
+ {
+ mpImplFont->SetCharSet( eCharSet );
+
+ if ( eCharSet == RTL_TEXTENCODING_SYMBOL )
+ mpImplFont->SetSymbolFlag( true );
+ else
+ mpImplFont->SetSymbolFlag( false );
+ }
+}
+
+bool Font::IsSymbolFont() const
+{
+ return mpImplFont->IsSymbolFont();
+}
+
+void Font::SetSymbolFlag( bool bSymbol )
+{
+ mpImplFont->SetSymbolFlag( bSymbol );
+
+ if ( IsSymbolFont() )
+ {
+ mpImplFont->SetCharSet( RTL_TEXTENCODING_SYMBOL );
+ }
+ else
+ {
+ if ( mpImplFont->GetCharSet() == RTL_TEXTENCODING_SYMBOL )
+ mpImplFont->SetCharSet( RTL_TEXTENCODING_DONTKNOW );
+ }
+}
+
+void Font::SetLanguageTag( const LanguageTag& rLanguageTag )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->maLanguageTag != rLanguageTag)
+ mpImplFont->maLanguageTag = rLanguageTag;
+}
+
+void Font::SetCJKContextLanguageTag( const LanguageTag& rLanguageTag )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->maCJKLanguageTag != rLanguageTag)
+ mpImplFont->maCJKLanguageTag = rLanguageTag;
+}
+
+void Font::SetLanguage( LanguageType eLanguage )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->maLanguageTag.getLanguageType(false) != eLanguage)
+ mpImplFont->maLanguageTag.reset( eLanguage);
+}
+
+void Font::SetCJKContextLanguage( LanguageType eLanguage )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->maCJKLanguageTag.getLanguageType(false) != eLanguage)
+ mpImplFont->maCJKLanguageTag.reset( eLanguage);
+}
+
+void Font::SetPitch( FontPitch ePitch )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->GetPitchNoAsk() != ePitch)
+ mpImplFont->SetPitch( ePitch );
+}
+
+void Font::SetOrientation( short nOrientation )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->mnOrientation != nOrientation)
+ mpImplFont->mnOrientation = nOrientation;
+}
+
+void Font::SetVertical( bool bVertical )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->mbVertical != bVertical)
+ mpImplFont->mbVertical = bVertical;
+}
+
+void Font::SetKerning( FontKerning eKerning )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->meKerning != eKerning)
+ mpImplFont->meKerning = eKerning;
+}
+
+bool Font::IsKerning() const
+{
+ return mpImplFont->meKerning != FontKerning::NONE;
+}
+
+void Font::SetWeight( FontWeight eWeight )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->GetWeightNoAsk() != eWeight)
+ mpImplFont->SetWeight( eWeight );
+}
+
+void Font::SetWidthType( FontWidth eWidth )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->GetWidthTypeNoAsk() != eWidth)
+ mpImplFont->SetWidthType( eWidth );
+}
+
+void Font::SetItalic( FontItalic eItalic )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->GetItalicNoAsk() != eItalic)
+ mpImplFont->SetItalic( eItalic );
+}
+
+void Font::SetOutline( bool bOutline )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->mbOutline != bOutline)
+ mpImplFont->mbOutline = bOutline;
+}
+
+void Font::SetShadow( bool bShadow )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->mbShadow != bShadow)
+ mpImplFont->mbShadow = bShadow;
+}
+
+void Font::SetUnderline( FontLineStyle eUnderline )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->meUnderline != eUnderline)
+ mpImplFont->meUnderline = eUnderline;
+}
+
+void Font::SetOverline( FontLineStyle eOverline )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->meOverline != eOverline)
+ mpImplFont->meOverline = eOverline;
+}
+
+void Font::SetStrikeout( FontStrikeout eStrikeout )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->meStrikeout != eStrikeout)
+ mpImplFont->meStrikeout = eStrikeout;
+}
+
+void Font::SetRelief( FontRelief eRelief )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->meRelief != eRelief)
+ mpImplFont->meRelief = eRelief;
+}
+
+void Font::SetEmphasisMark( FontEmphasisMark eEmphasisMark )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->meEmphasisMark != eEmphasisMark )
+ mpImplFont->meEmphasisMark = eEmphasisMark;
+}
+
+void Font::SetWordLineMode( bool bWordLine )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->mbWordLine != bWordLine)
+ mpImplFont->mbWordLine = bWordLine;
+}
+
+Font& Font::operator=( const vcl::Font& rFont )
+{
+ mpImplFont = rFont.mpImplFont;
+ return *this;
+}
+
+Font& Font::operator=( vcl::Font&& rFont ) noexcept
+{
+ mpImplFont = std::move(rFont.mpImplFont);
+ return *this;
+}
+
+bool Font::operator==( const vcl::Font& rFont ) const
+{
+ return mpImplFont == rFont.mpImplFont;
+}
+
+void Font::Merge( const vcl::Font& rFont )
+{
+ if ( !rFont.GetFamilyName().isEmpty() )
+ {
+ SetFamilyName( rFont.GetFamilyName() );
+ SetStyleName( rFont.GetStyleName() );
+ SetCharSet( GetCharSet() );
+ SetLanguageTag( rFont.GetLanguageTag() );
+ SetCJKContextLanguageTag( rFont.GetCJKContextLanguageTag() );
+ // don't use access methods here, might lead to AskConfig(), if DONTKNOW
+ SetFamily( rFont.mpImplFont->GetFamilyTypeNoAsk() );
+ SetPitch( rFont.mpImplFont->GetPitchNoAsk() );
+ }
+
+ // don't use access methods here, might lead to AskConfig(), if DONTKNOW
+ if ( rFont.mpImplFont->GetWeightNoAsk() != WEIGHT_DONTKNOW )
+ SetWeight( rFont.GetWeight() );
+ if ( rFont.mpImplFont->GetItalicNoAsk() != ITALIC_DONTKNOW )
+ SetItalic( rFont.GetItalic() );
+ if ( rFont.mpImplFont->GetWidthTypeNoAsk() != WIDTH_DONTKNOW )
+ SetWidthType( rFont.GetWidthType() );
+
+ if ( rFont.GetFontSize().Height() )
+ SetFontSize( rFont.GetFontSize() );
+ if ( rFont.GetUnderline() != LINESTYLE_DONTKNOW )
+ {
+ SetUnderline( rFont.GetUnderline() );
+ SetWordLineMode( rFont.IsWordLineMode() );
+ }
+ if ( rFont.GetOverline() != LINESTYLE_DONTKNOW )
+ {
+ SetOverline( rFont.GetOverline() );
+ SetWordLineMode( rFont.IsWordLineMode() );
+ }
+ if ( rFont.GetStrikeout() != STRIKEOUT_DONTKNOW )
+ {
+ SetStrikeout( rFont.GetStrikeout() );
+ SetWordLineMode( rFont.IsWordLineMode() );
+ }
+
+ // Defaults?
+ SetOrientation( rFont.GetOrientation() );
+ SetVertical( rFont.IsVertical() );
+ SetEmphasisMark( rFont.GetEmphasisMark() );
+ SetKerning( rFont.IsKerning() ? FontKerning::FontSpecific : FontKerning::NONE );
+ SetOutline( rFont.IsOutline() );
+ SetShadow( rFont.IsShadow() );
+ SetRelief( rFont.GetRelief() );
+}
+
+void Font::GetFontAttributes( FontAttributes& rAttrs ) const
+{
+ rAttrs.SetFamilyName( mpImplFont->GetFamilyName() );
+ rAttrs.SetStyleName( mpImplFont->maStyleName );
+ rAttrs.SetFamilyType( mpImplFont->GetFamilyTypeNoAsk() );
+ rAttrs.SetPitch( mpImplFont->GetPitchNoAsk() );
+ rAttrs.SetItalic( mpImplFont->GetItalicNoAsk() );
+ rAttrs.SetWeight( mpImplFont->GetWeightNoAsk() );
+ rAttrs.SetWidthType( WIDTH_DONTKNOW );
+ rAttrs.SetSymbolFlag( mpImplFont->GetCharSet() == RTL_TEXTENCODING_SYMBOL );
+}
+
+SvStream& ReadImplFont( SvStream& rIStm, ImplFont& rImplFont )
+{
+ VersionCompat aCompat( rIStm, StreamMode::READ );
+ sal_uInt16 nTmp16(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);
+
+ 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( rImplFont.mnOrientation );
+
+ rIStm.ReadCharAsBool( bTmp ); rImplFont.mbWordLine = bTmp;
+ rIStm.ReadCharAsBool( bTmp ); rImplFont.mbOutline = bTmp;
+ rIStm.ReadCharAsBool( bTmp ); rImplFont.mbShadow = bTmp;
+ rIStm.ReadUChar( nTmp8 ); rImplFont.meKerning = static_cast<FontKerning>(nTmp8);
+
+ if( aCompat.GetVersion() >= 2 )
+ {
+ rIStm.ReadUChar( nTmp8 ); rImplFont.meRelief = static_cast<FontRelief>(nTmp8);
+ rIStm.ReadUInt16( nTmp16 ); rImplFont.maCJKLanguageTag.reset( LanguageType(nTmp16) );
+ rIStm.ReadCharAsBool( bTmp ); rImplFont.mbVertical = bTmp;
+ rIStm.ReadUInt16( nTmp16 ); rImplFont.meEmphasisMark = static_cast<FontEmphasisMark>(nTmp16);
+ }
+
+ if( aCompat.GetVersion() >= 3 )
+ {
+ rIStm.ReadUInt16( nTmp16 ); rImplFont.meOverline = static_cast<FontLineStyle>(nTmp16);
+ }
+
+ // Relief
+ // CJKContextLanguage
+
+ return rIStm;
+}
+
+SvStream& WriteImplFont( SvStream& rOStm, const ImplFont& rImplFont )
+{
+ VersionCompat aCompat( rOStm, StreamMode::WRITE, 3 );
+ 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 );
+
+ 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<sal_uChar>(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 );
+
+ return rOStm;
+}
+
+SvStream& ReadFont( SvStream& rIStm, vcl::Font& rFont )
+{
+ return ReadImplFont( rIStm, *rFont.mpImplFont );
+}
+
+SvStream& WriteFont( SvStream& rOStm, const vcl::Font& rFont )
+{
+ return WriteImplFont( rOStm, *rFont.mpImplFont );
+}
+
+namespace
+{
+ bool identifyTrueTypeFont( const void* i_pBuffer, sal_uInt32 i_nSize, Font& o_rResult )
+ {
+ bool bResult = false;
+ TrueTypeFont* pTTF = nullptr;
+ if( OpenTTFontBuffer( i_pBuffer, i_nSize, 0, &pTTF ) == SFErrCodes::Ok )
+ {
+ TTGlobalFontInfo aInfo;
+ GetTTGlobalFontInfo( pTTF, &aInfo );
+ // most importantly: the family name
+ if( aInfo.ufamily )
+ o_rResult.SetFamilyName( aInfo.ufamily );
+ else if( aInfo.family )
+ o_rResult.SetFamilyName( OStringToOUString( aInfo.family, RTL_TEXTENCODING_ASCII_US ) );
+ // set weight
+ if( aInfo.weight )
+ {
+ if( aInfo.weight < FW_EXTRALIGHT )
+ o_rResult.SetWeight( WEIGHT_THIN );
+ else if( aInfo.weight < FW_LIGHT )
+ o_rResult.SetWeight( WEIGHT_ULTRALIGHT );
+ else if( aInfo.weight < FW_NORMAL )
+ o_rResult.SetWeight( WEIGHT_LIGHT );
+ else if( aInfo.weight < FW_MEDIUM )
+ o_rResult.SetWeight( WEIGHT_NORMAL );
+ else if( aInfo.weight < FW_SEMIBOLD )
+ o_rResult.SetWeight( WEIGHT_MEDIUM );
+ else if( aInfo.weight < FW_BOLD )
+ o_rResult.SetWeight( WEIGHT_SEMIBOLD );
+ else if( aInfo.weight < FW_EXTRABOLD )
+ o_rResult.SetWeight( WEIGHT_BOLD );
+ else if( aInfo.weight < FW_BLACK )
+ o_rResult.SetWeight( WEIGHT_ULTRABOLD );
+ else
+ o_rResult.SetWeight( WEIGHT_BLACK );
+ }
+ else
+ o_rResult.SetWeight( (aInfo.macStyle & 1) ? WEIGHT_BOLD : WEIGHT_NORMAL );
+ // set width
+ if( aInfo.width )
+ {
+ if( aInfo.width == FWIDTH_ULTRA_CONDENSED )
+ o_rResult.SetAverageFontWidth( WIDTH_ULTRA_CONDENSED );
+ else if( aInfo.width == FWIDTH_EXTRA_CONDENSED )
+ o_rResult.SetAverageFontWidth( WIDTH_EXTRA_CONDENSED );
+ else if( aInfo.width == FWIDTH_CONDENSED )
+ o_rResult.SetAverageFontWidth( WIDTH_CONDENSED );
+ else if( aInfo.width == FWIDTH_SEMI_CONDENSED )
+ o_rResult.SetAverageFontWidth( WIDTH_SEMI_CONDENSED );
+ else if( aInfo.width == FWIDTH_NORMAL )
+ o_rResult.SetAverageFontWidth( WIDTH_NORMAL );
+ else if( aInfo.width == FWIDTH_SEMI_EXPANDED )
+ o_rResult.SetAverageFontWidth( WIDTH_SEMI_EXPANDED );
+ else if( aInfo.width == FWIDTH_EXPANDED )
+ o_rResult.SetAverageFontWidth( WIDTH_EXPANDED );
+ else if( aInfo.width == FWIDTH_EXTRA_EXPANDED )
+ o_rResult.SetAverageFontWidth( WIDTH_EXTRA_EXPANDED );
+ else if( aInfo.width >= FWIDTH_ULTRA_EXPANDED )
+ o_rResult.SetAverageFontWidth( WIDTH_ULTRA_EXPANDED );
+ }
+ // set italic
+ o_rResult.SetItalic( (aInfo.italicAngle != 0) ? ITALIC_NORMAL : ITALIC_NONE );
+
+ // set pitch
+ o_rResult.SetPitch( (aInfo.pitch == 0) ? PITCH_VARIABLE : PITCH_FIXED );
+
+ // set style name
+ if( aInfo.usubfamily )
+ o_rResult.SetStyleName( OUString( aInfo.usubfamily ) );
+ else if( aInfo.subfamily )
+ o_rResult.SetStyleName( OUString::createFromAscii( aInfo.subfamily ) );
+
+ // cleanup
+ CloseTTFont( pTTF );
+ // success
+ bResult = true;
+ }
+ return bResult;
+ }
+
+ static 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( OString( 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; }
+
+FontAlign Font::GetAlignment() const { return mpImplFont->GetAlignment(); }
+
+const OUString& Font::GetFamilyName() const { return mpImplFont->GetFamilyName(); }
+const OUString& Font::GetStyleName() const { return mpImplFont->maStyleName; }
+
+const Size& Font::GetFontSize() const { return mpImplFont->GetFontSize(); }
+void Font::SetFontHeight( long nHeight ) { SetFontSize( Size( mpImplFont->GetFontSize().Width(), nHeight ) ); }
+long Font::GetFontHeight() const { return mpImplFont->GetFontSize().Height(); }
+void Font::SetAverageFontWidth( long nWidth ) { SetFontSize( Size( nWidth, mpImplFont->GetFontSize().Height() ) ); }
+long Font::GetAverageFontWidth() const { return mpImplFont->GetFontSize().Width(); }
+
+rtl_TextEncoding Font::GetCharSet() const { return mpImplFont->GetCharSet(); }
+
+const LanguageTag& Font::GetLanguageTag() const { return mpImplFont->maLanguageTag; }
+const LanguageTag& Font::GetCJKContextLanguageTag() const { return mpImplFont->maCJKLanguageTag; }
+LanguageType Font::GetLanguage() const { return mpImplFont->maLanguageTag.getLanguageType( false); }
+LanguageType Font::GetCJKContextLanguage() const { return mpImplFont->maCJKLanguageTag.getLanguageType( false); }
+
+short Font::GetOrientation() const { return mpImplFont->mnOrientation; }
+bool Font::IsVertical() const { return mpImplFont->mbVertical; }
+FontKerning Font::GetKerning() const { return mpImplFont->meKerning; }
+
+FontPitch Font::GetPitch() { return mpImplFont->GetPitch(); }
+FontWeight Font::GetWeight() { return mpImplFont->GetWeight(); }
+FontWidth Font::GetWidthType() { return mpImplFont->GetWidthType(); }
+FontItalic Font::GetItalic() { return mpImplFont->GetItalic(); }
+FontFamily Font::GetFamilyType() { return mpImplFont->GetFamilyType(); }
+
+FontPitch Font::GetPitch() const { return mpImplFont->GetPitchNoAsk(); }
+FontWeight Font::GetWeight() const { return mpImplFont->GetWeightNoAsk(); }
+FontWidth Font::GetWidthType() const { return mpImplFont->GetWidthTypeNoAsk(); }
+FontItalic Font::GetItalic() const { return mpImplFont->GetItalicNoAsk(); }
+FontFamily Font::GetFamilyType() const { return mpImplFont->GetFamilyTypeNoAsk(); }
+
+int Font::GetQuality() const { return mpImplFont->GetQuality(); }
+void Font::SetQuality( int nQuality ) { mpImplFont->SetQuality( nQuality ); }
+void Font::IncreaseQualityBy( int nQualityAmount ) { mpImplFont->IncreaseQualityBy( nQualityAmount ); }
+void Font::DecreaseQualityBy( int nQualityAmount ) { mpImplFont->DecreaseQualityBy( nQualityAmount ); }
+
+bool Font::IsOutline() const { return mpImplFont->mbOutline; }
+bool Font::IsShadow() const { return mpImplFont->mbShadow; }
+FontRelief Font::GetRelief() const { return mpImplFont->meRelief; }
+FontLineStyle Font::GetUnderline() const { return mpImplFont->meUnderline; }
+FontLineStyle Font::GetOverline() const { return mpImplFont->meOverline; }
+FontStrikeout Font::GetStrikeout() const { return mpImplFont->meStrikeout; }
+FontEmphasisMark Font::GetEmphasisMark() const { return mpImplFont->meEmphasisMark; }
+bool Font::IsWordLineMode() const { return mpImplFont->mbWordLine; }
+bool Font::IsSameInstance( const vcl::Font& rFont ) const { return (mpImplFont == rFont.mpImplFont); }
+
+
+ImplFont::ImplFont() :
+ meWeight( WEIGHT_DONTKNOW ),
+ meFamily( FAMILY_DONTKNOW ),
+ mePitch( PITCH_DONTKNOW ),
+ meWidthType( WIDTH_DONTKNOW ),
+ meItalic( ITALIC_NONE ),
+ meAlign( ALIGN_TOP ),
+ meUnderline( LINESTYLE_NONE ),
+ meOverline( LINESTYLE_NONE ),
+ meStrikeout( STRIKEOUT_NONE ),
+ meRelief( FontRelief::NONE ),
+ meEmphasisMark( FontEmphasisMark::NONE ),
+ meKerning( FontKerning::FontSpecific ),
+ meCharSet( RTL_TEXTENCODING_DONTKNOW ),
+ maLanguageTag( LANGUAGE_DONTKNOW ),
+ maCJKLanguageTag( LANGUAGE_DONTKNOW ),
+ mbSymbolFlag( false ),
+ mbOutline( false ),
+ mbConfigLookup( false ),
+ mbShadow( false ),
+ mbVertical( false ),
+ mbTransparent( true ),
+ maColor( COL_TRANSPARENT ),
+ maFillColor( COL_TRANSPARENT ),
+ mbWordLine( false ),
+ mnOrientation( 0 ),
+ mnQuality( 0 )
+{}
+
+ImplFont::ImplFont( const ImplFont& rImplFont ) :
+ maFamilyName( rImplFont.maFamilyName ),
+ maStyleName( rImplFont.maStyleName ),
+ meWeight( rImplFont.meWeight ),
+ meFamily( rImplFont.meFamily ),
+ mePitch( rImplFont.mePitch ),
+ meWidthType( rImplFont.meWidthType ),
+ meItalic( rImplFont.meItalic ),
+ meAlign( rImplFont.meAlign ),
+ meUnderline( rImplFont.meUnderline ),
+ meOverline( rImplFont.meOverline ),
+ meStrikeout( rImplFont.meStrikeout ),
+ meRelief( rImplFont.meRelief ),
+ meEmphasisMark( rImplFont.meEmphasisMark ),
+ meKerning( rImplFont.meKerning ),
+ maAverageFontSize( rImplFont.maAverageFontSize ),
+ meCharSet( rImplFont.meCharSet ),
+ maLanguageTag( rImplFont.maLanguageTag ),
+ maCJKLanguageTag( rImplFont.maCJKLanguageTag ),
+ mbSymbolFlag( rImplFont.mbSymbolFlag ),
+ mbOutline( rImplFont.mbOutline ),
+ mbConfigLookup( rImplFont.mbConfigLookup ),
+ mbShadow( rImplFont.mbShadow ),
+ mbVertical( rImplFont.mbVertical ),
+ mbTransparent( rImplFont.mbTransparent ),
+ maColor( rImplFont.maColor ),
+ maFillColor( rImplFont.maFillColor ),
+ mbWordLine( rImplFont.mbWordLine ),
+ mnOrientation( rImplFont.mnOrientation ),
+ mnQuality( rImplFont.mnQuality )
+{}
+
+bool ImplFont::operator==( 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( (maColor != rOther.maColor)
+ || (maFillColor != rOther.maFillColor) )
+ return false;
+
+ if( (meUnderline != rOther.meUnderline)
+ || (meOverline != rOther.meOverline)
+ || (meStrikeout != rOther.meStrikeout)
+ || (meRelief != rOther.meRelief)
+ || (meEmphasisMark != rOther.meEmphasisMark)
+ || (mbWordLine != rOther.mbWordLine)
+ || (mbOutline != rOther.mbOutline)
+ || (mbShadow != rOther.mbShadow)
+ || (meKerning != rOther.meKerning)
+ || (mbTransparent != rOther.mbTransparent) )
+ return false;
+
+ return true;
+}
+
+void ImplFont::AskConfig()
+{
+ if( mbConfigLookup )
+ return;
+
+ mbConfigLookup = true;
+
+ // prepare the FontSubst configuration lookup
+ const utl::FontSubstConfiguration& rFontSubst = utl::FontSubstConfiguration::get();
+
+ OUString aShortName;
+ OUString aFamilyName;
+ ImplFontAttrs nType = ImplFontAttrs::None;
+ FontWeight eWeight = WEIGHT_DONTKNOW;
+ FontWidth eWidthType = WIDTH_DONTKNOW;
+ OUString aMapName = GetEnglishSearchFontName( maFamilyName );
+
+ utl::FontSubstConfiguration::getMapName( aMapName,
+ aShortName, aFamilyName, eWeight, eWidthType, nType );
+
+ // lookup the font name in the configuration
+ const utl::FontNameAttr* pFontAttr = rFontSubst.getSubstInfo( aMapName );
+
+ // if the direct lookup failed try again with an alias name
+ if ( !pFontAttr && (aShortName != aMapName) )
+ pFontAttr = rFontSubst.getSubstInfo( aShortName );
+
+ if( pFontAttr )
+ {
+ // the font was found in the configuration
+ if( meFamily == FAMILY_DONTKNOW )
+ {
+ if ( pFontAttr->Type & ImplFontAttrs::Serif )
+ meFamily = FAMILY_ROMAN;
+ else if ( pFontAttr->Type & ImplFontAttrs::SansSerif )
+ meFamily = FAMILY_SWISS;
+ else if ( pFontAttr->Type & ImplFontAttrs::Typewriter )
+ meFamily = FAMILY_MODERN;
+ else if ( pFontAttr->Type & ImplFontAttrs::Italic )
+ meFamily = FAMILY_SCRIPT;
+ else if ( pFontAttr->Type & ImplFontAttrs::Decorative )
+ meFamily = FAMILY_DECORATIVE;
+ }
+
+ if( mePitch == PITCH_DONTKNOW )
+ {
+ if ( pFontAttr->Type & ImplFontAttrs::Fixed )
+ mePitch = PITCH_FIXED;
+ }
+ }
+
+ // if some attributes are still unknown then use the FontSubst magic
+ if( meFamily == FAMILY_DONTKNOW )
+ {
+ if( nType & ImplFontAttrs::Serif )
+ meFamily = FAMILY_ROMAN;
+ else if( nType & ImplFontAttrs::SansSerif )
+ meFamily = FAMILY_SWISS;
+ else if( nType & ImplFontAttrs::Typewriter )
+ meFamily = FAMILY_MODERN;
+ else if( nType & ImplFontAttrs::Italic )
+ meFamily = FAMILY_SCRIPT;
+ else if( nType & ImplFontAttrs::Decorative )
+ meFamily = FAMILY_DECORATIVE;
+ }
+
+ if( GetWeight() == WEIGHT_DONTKNOW )
+ SetWeight( eWeight );
+ if( meWidthType == WIDTH_DONTKNOW )
+ meWidthType = eWidthType;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/font/fontattributes.cxx b/vcl/source/font/fontattributes.cxx
new file mode 100644
index 000000000..20e8e1b65
--- /dev/null
+++ b/vcl/source/font/fontattributes.cxx
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <fontattributes.hxx>
+
+FontAttributes::FontAttributes()
+: meWeight( WEIGHT_DONTKNOW ),
+ meFamily( FAMILY_DONTKNOW ),
+ mePitch( PITCH_DONTKNOW ),
+ meWidthType ( WIDTH_DONTKNOW ),
+ meItalic ( ITALIC_NONE ),
+ meCharSet( RTL_TEXTENCODING_DONTKNOW ),
+ mbSymbolFlag( false ),
+ mnQuality( 0 )
+{}
+
+bool FontAttributes::CompareDeviceIndependentFontAttributes(const FontAttributes& rOther) const
+{
+ if (maFamilyName != rOther.maFamilyName)
+ return false;
+
+ if (maStyleName != rOther.maStyleName)
+ return false;
+
+ if (meWeight != rOther.meWeight)
+ return false;
+
+ if (meItalic != rOther.meItalic)
+ return false;
+
+ if (meFamily != rOther.meFamily)
+ return false;
+
+ if (mePitch != rOther.mePitch)
+ return false;
+
+ if (meWidthType != rOther.meWidthType)
+ return false;
+
+ if (mbSymbolFlag != rOther.mbSymbolFlag)
+ return false;
+
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/font/fontcache.cxx b/vcl/source/font/fontcache.cxx
new file mode 100644
index 000000000..a37154d27
--- /dev/null
+++ b/vcl/source/font/fontcache.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <fontinstance.hxx>
+#include <impfontcache.hxx>
+#include <PhysicalFontCollection.hxx>
+#include <PhysicalFontFace.hxx>
+#include <PhysicalFontFamily.hxx>
+#include <sal/log.hxx>
+
+#if !(defined(_WIN32) || defined(MACOSX) || defined(IOS))
+#include <unx/glyphcache.hxx>
+#endif
+
+size_t ImplFontCache::IFSD_Hash::operator()( const FontSelectPattern& rFSD ) const
+{
+ return rFSD.hashCode();
+}
+
+bool ImplFontCache::IFSD_Equal::operator()(const FontSelectPattern& rA, const FontSelectPattern& rB) const
+{
+ // check normalized font family name
+ if( rA.maSearchName != rB.maSearchName )
+ return false;
+
+ // check font transformation
+ if( (rA.mnHeight != rB.mnHeight)
+ || (rA.mnWidth != rB.mnWidth)
+ || (rA.mnOrientation != rB.mnOrientation) )
+ return false;
+
+ // check mapping relevant attributes
+ if( (rA.mbVertical != rB.mbVertical)
+ || (rA.meLanguage != rB.meLanguage) )
+ return false;
+
+ // check font face attributes
+ if( (rA.GetWeight() != rB.GetWeight())
+ || (rA.GetItalic() != rB.GetItalic())
+// || (rA.meFamily != rB.meFamily) // TODO: remove this mostly obsolete member
+ || (rA.GetPitch() != rB.GetPitch()) )
+ return false;
+
+ // check style name
+ if( rA.GetStyleName() != rB.GetStyleName() )
+ return false;
+
+ // Symbol fonts may recode from one type to another So they are only
+ // safely equivalent for equal targets
+ if (rA.IsSymbolFont() || rB.IsSymbolFont())
+ {
+ if (rA.maTargetName != rB.maTargetName)
+ return false;
+ }
+
+ // check for features
+ if ((rA.maTargetName.indexOf(FontSelectPattern::FEAT_PREFIX)
+ != -1 ||
+ rB.maTargetName.indexOf(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(0)
+ // The cache limit is set by the rough number of characters needed to read your average Asian newspaper.
+ , m_aBoundRectCache(3000)
+{}
+
+ImplFontCache::~ImplFontCache()
+{
+ for (const auto & rLFI : maFontInstanceList)
+ {
+ rLFI.second->mpFontCache = nullptr;
+ }
+}
+
+rtl::Reference<LogicalFontInstance> ImplFontCache::GetFontInstance( PhysicalFontCollection const * pFontList,
+ const vcl::Font& rFont, const Size& rSize, float fExactHeight, bool bNonAntialias )
+{
+ // initialize internal font request object
+ FontSelectPattern aFontSelData(rFont, rFont.GetFamilyName(), rSize, fExactHeight, bNonAntialias);
+ return GetFontInstance( pFontList, aFontSelData );
+}
+
+rtl::Reference<LogicalFontInstance> ImplFontCache::GetFontInstance( PhysicalFontCollection const * pFontList,
+ FontSelectPattern& aFontSelData )
+{
+ rtl::Reference<LogicalFontInstance> pFontInstance;
+ PhysicalFontFamily* pFontFamily = nullptr;
+
+ // check if a directly matching logical font instance is already cached,
+ // the most recently used font usually has a hit rate of >50%
+ if (mpLastHitCacheEntry && IFSD_Equal()(aFontSelData, mpLastHitCacheEntry->GetFontSelectPattern()))
+ pFontInstance = mpLastHitCacheEntry;
+ else
+ {
+ FontInstanceList::const_iterator it = maFontInstanceList.find( aFontSelData );
+ if( it != maFontInstanceList.end() )
+ pFontInstance = (*it).second;
+ }
+
+ if( !pFontInstance ) // no direct cache hit
+ {
+ // find the best matching logical font family and update font selector accordingly
+ pFontFamily = pFontList->FindFontFamily( aFontSelData );
+ SAL_WARN_IF( (pFontFamily == nullptr), "vcl", "ImplFontCache::Get() No logical font found!" );
+ if( pFontFamily )
+ {
+ aFontSelData.maSearchName = pFontFamily->GetSearchName();
+
+ // check if an indirectly matching logical font instance is already cached
+ FontInstanceList::const_iterator it = maFontInstanceList.find( aFontSelData );
+ if( it != maFontInstanceList.end() )
+ pFontInstance = (*it).second;
+ }
+ }
+
+ if( !pFontInstance && pFontFamily) // still no cache hit => create a new font instance
+ {
+ PhysicalFontFace* pFontData = pFontFamily->FindBestFontFace(aFontSelData);
+
+ // create a new logical font instance from this physical font face
+ pFontInstance = pFontData->CreateFontInstance( aFontSelData );
+ pFontInstance->mpFontCache = this;
+
+ // if we're substituting from or to a symbol font we may need a symbol
+ // conversion table
+ if( pFontData->IsSymbolFont() || aFontSelData.IsSymbolFont() )
+ {
+ if( aFontSelData.maTargetName != aFontSelData.maSearchName )
+ pFontInstance->mpConversion = ConvertChar::GetRecodeData( aFontSelData.maTargetName, aFontSelData.maSearchName );
+ }
+
+#ifdef MACOSX
+ //It might be better to dig out the font version of the target font
+ //to see if it's a modern re-coded apple symbol font in case that
+ //font shows up on a different platform
+ if (!pFontInstance->mpConversion &&
+ aFontSelData.maTargetName.equalsIgnoreAsciiCase("symbol") &&
+ aFontSelData.maSearchName.equalsIgnoreAsciiCase("symbol"))
+ {
+ pFontInstance->mpConversion = ConvertChar::GetRecodeData( "Symbol", "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] (FontInstanceListPair 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] (GlyphBoundRectCachePair 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,
+ 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("EUDC");
+ if (!pFallbackData)
+ pFallbackData = pFontCollection->GetGlyphFallbackFont(rFontSelData, pFontInstance, rMissingCodes, nFallbackLevel-1);
+ // escape when there are no font candidates
+ if( !pFallbackData )
+ return nullptr;
+ // override the font name
+ rFontSelData.SetFamilyName( pFallbackData->GetFamilyName() );
+ // clear the cached normalized name
+ rFontSelData.maSearchName.clear();
+ }
+
+ rtl::Reference<LogicalFontInstance> pFallbackFont = GetFontInstance( pFontCollection, rFontSelData );
+ return pFallbackFont;
+}
+
+void ImplFontCache::Invalidate()
+{
+ // #112304# make sure the font cache is really clean
+ mpLastHitCacheEntry = nullptr;
+ for (auto const & pair : maFontInstanceList)
+ pair.second->mpFontCache = nullptr;
+ maFontInstanceList.clear();
+ m_aBoundRectCache.clear();
+}
+
+bool ImplFontCache::GetCachedGlyphBoundRect(const LogicalFontInstance *pFont, sal_GlyphId nID, tools::Rectangle &rRect)
+{
+ if (!pFont->GetFontCache())
+ return false;
+ assert(pFont->GetFontCache() == this);
+ if (pFont->GetFontCache() != this)
+ return false;
+
+ auto it = m_aBoundRectCache.find({pFont, nID});
+ if (it != m_aBoundRectCache.end())
+ {
+ rRect = it->second;
+ return true;
+ }
+ return false;
+}
+
+void ImplFontCache::CacheGlyphBoundRect(const LogicalFontInstance *pFont, sal_GlyphId nID, tools::Rectangle &rRect)
+{
+ if (!pFont->GetFontCache())
+ return;
+ assert(pFont->GetFontCache() == this);
+ if (pFont->GetFontCache() != this)
+ return;
+
+ m_aBoundRectCache.insert({{pFont, nID}, rRect});
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/font/fontcharmap.cxx b/vcl/source/font/fontcharmap.cxx
new file mode 100644
index 000000000..af8533451
--- /dev/null
+++ b/vcl/source/font/fontcharmap.cxx
@@ -0,0 +1,634 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#include <vcl/fontcharmap.hxx>
+#include <impfontcharmap.hxx>
+#include <rtl/textcvt.h>
+#include <rtl/textenc.h>
+#include <sal/log.hxx>
+
+#include <vector>
+#include <set>
+
+CmapResult::CmapResult( bool bSymbolic,
+ const sal_UCS4* pRangeCodes, int nRangeCount )
+: mpRangeCodes( pRangeCodes)
+, mpStartGlyphs( nullptr)
+, mpGlyphIds( nullptr)
+, mnRangeCount( nRangeCount)
+, mbSymbolic( bSymbolic)
+, mbRecoded( false)
+{}
+
+static ImplFontCharMapRef g_pDefaultImplFontCharMap;
+static const sal_UCS4 aDefaultUnicodeRanges[] = {0x0020,0xD800, 0xE000,0xFFF0};
+static const sal_UCS4 aDefaultSymbolRanges[] = {0x0020,0x0100, 0xF020,0xF100};
+
+ImplFontCharMap::~ImplFontCharMap()
+{
+ if( !isDefaultMap() )
+ {
+ delete[] mpRangeCodes;
+ delete[] mpStartGlyphs;
+ delete[] mpGlyphIds;
+ }
+}
+
+ImplFontCharMap::ImplFontCharMap( const CmapResult& rCR )
+: mpRangeCodes( rCR.mpRangeCodes )
+, mpStartGlyphs( rCR.mpStartGlyphs )
+, mpGlyphIds( rCR.mpGlyphIds )
+, mnRangeCount( rCR.mnRangeCount )
+, mnCharCount( 0 )
+{
+ const sal_UCS4* pRangePtr = mpRangeCodes;
+ for( int i = mnRangeCount; --i >= 0; pRangePtr += 2 )
+ {
+ sal_UCS4 cFirst = pRangePtr[0];
+ sal_UCS4 cLast = pRangePtr[1];
+ mnCharCount += cLast - cFirst;
+ }
+}
+
+ImplFontCharMapRef const & ImplFontCharMap::getDefaultMap( bool bSymbols )
+{
+ const sal_UCS4* pRangeCodes = aDefaultUnicodeRanges;
+ int nCodesCount = SAL_N_ELEMENTS(aDefaultUnicodeRanges);
+ if( bSymbols )
+ {
+ pRangeCodes = aDefaultSymbolRanges;
+ nCodesCount = SAL_N_ELEMENTS(aDefaultSymbolRanges);
+ }
+
+ CmapResult aDefaultCR( bSymbols, pRangeCodes, nCodesCount/2 );
+ g_pDefaultImplFontCharMap = ImplFontCharMapRef(new ImplFontCharMap(aDefaultCR));
+
+ return g_pDefaultImplFontCharMap;
+}
+
+bool ImplFontCharMap::isDefaultMap() const
+{
+ const bool bIsDefault = (mpRangeCodes == aDefaultUnicodeRanges) || (mpRangeCodes == aDefaultSymbolRanges);
+ return bIsDefault;
+}
+
+static unsigned GetUInt( const unsigned char* p ) { return((p[0]<<24)+(p[1]<<16)+(p[2]<<8)+p[3]);}
+static unsigned GetUShort( const unsigned char* p ){ return((p[0]<<8) | p[1]);}
+static int GetSShort( const unsigned char* p ){ return static_cast<sal_Int16>((p[0]<<8)|p[1]);}
+
+// TODO: move CMAP parsing directly into the ImplFontCharMap class
+bool ParseCMAP( const unsigned char* pCmap, int nLength, CmapResult& rResult )
+{
+ rResult.mpRangeCodes = nullptr;
+ rResult.mpStartGlyphs= nullptr;
+ rResult.mpGlyphIds = nullptr;
+ rResult.mnRangeCount = 0;
+ rResult.mbRecoded = false;
+ rResult.mbSymbolic = false;
+
+ // parse the table header and check for validity
+ if( !pCmap || (nLength < 24) )
+ return false;
+
+ if( GetUShort( pCmap ) != 0x0000 ) // simple check for CMAP corruption
+ return false;
+
+ int nSubTables = GetUShort( pCmap + 2 );
+ if( (nSubTables <= 0) || (nLength < (24 + 8*nSubTables)) )
+ return false;
+
+ const unsigned char* pEndValidArea = pCmap + nLength;
+
+ // find the most interesting subtable in the CMAP
+ rtl_TextEncoding eRecodeFrom = RTL_TEXTENCODING_UNICODE;
+ int nOffset = 0;
+ int nFormat = -1;
+ int nBestVal = 0;
+ for( const unsigned char* p = pCmap + 4; --nSubTables >= 0; p += 8 )
+ {
+ int nPlatform = GetUShort( p );
+ int nEncoding = GetUShort( p+2 );
+ int nPlatformEncoding = (nPlatform << 8) + nEncoding;
+
+ int nValue;
+ rtl_TextEncoding eTmpEncoding = RTL_TEXTENCODING_UNICODE;
+ switch( nPlatformEncoding )
+ {
+ case 0x000: nValue = 20; break; // Unicode 1.0
+ case 0x001: nValue = 21; break; // Unicode 1.1
+ case 0x002: nValue = 22; break; // iso10646_1993
+ case 0x003: nValue = 23; break; // UCS-2
+ case 0x004: nValue = 24; break; // UCS-4
+ case 0x100: nValue = 22; break; // Mac Unicode<2.0
+ case 0x103: nValue = 23; break; // Mac Unicode>2.0
+ case 0x300: nValue = 5; rResult.mbSymbolic = true; break; // Win Symbol
+ case 0x301: nValue = 28; break; // Win UCS-2
+ case 0x30A: nValue = 29; break; // Win-UCS-4
+ case 0x302: nValue = 11; eTmpEncoding = RTL_TEXTENCODING_SHIFT_JIS; break;
+ case 0x303: nValue = 12; eTmpEncoding = RTL_TEXTENCODING_GB_18030; break;
+ case 0x304: nValue = 11; eTmpEncoding = RTL_TEXTENCODING_BIG5; break;
+ case 0x305: nValue = 11; eTmpEncoding = RTL_TEXTENCODING_MS_949; break;
+ case 0x306: nValue = 11; eTmpEncoding = RTL_TEXTENCODING_MS_1361; break;
+ default: nValue = 0; break;
+ }
+
+ if( nValue <= 0 ) // ignore unknown encodings
+ continue;
+
+ int nTmpOffset = GetUInt( p+4 );
+
+ if (nTmpOffset > nLength - 2 || nTmpOffset < 0)
+ continue;
+
+ int nTmpFormat = GetUShort( pCmap + nTmpOffset );
+ if( nTmpFormat == 12 ) // 32bit code -> glyph map format
+ nValue += 3;
+ else if( nTmpFormat != 4 ) // 16bit code -> glyph map format
+ continue; // ignore other formats
+
+ if( nBestVal < nValue )
+ {
+ nBestVal = nValue;
+ nOffset = nTmpOffset;
+ nFormat = nTmpFormat;
+ eRecodeFrom = eTmpEncoding;
+ }
+ }
+
+ // parse the best CMAP subtable
+ int nRangeCount = 0;
+ sal_UCS4* pCodePairs = nullptr;
+ int* pStartGlyphs = nullptr;
+
+ std::vector<sal_uInt16> aGlyphIdArray;
+ aGlyphIdArray.reserve( 0x1000 );
+ aGlyphIdArray.push_back( 0 );
+
+ // format 4, the most common 16bit char mapping table
+ if( (nFormat == 4) && ((nOffset+16) < nLength) )
+ {
+ int nSegCountX2 = GetUShort( pCmap + nOffset + 6 );
+ nRangeCount = nSegCountX2/2 - 1;
+ if (nRangeCount < 0)
+ {
+ SAL_WARN("vcl.gdi", "negative RangeCount");
+ nRangeCount = 0;
+ }
+
+ const unsigned char* pLimitBase = pCmap + nOffset + 14;
+ const unsigned char* pBeginBase = pLimitBase + nSegCountX2 + 2;
+ const unsigned char* pDeltaBase = pBeginBase + nSegCountX2;
+ const unsigned char* pOffsetBase = pDeltaBase + nSegCountX2;
+
+ const int nOffsetBaseStart = pOffsetBase - pCmap;
+ const int nRemainingLen = nLength - nOffsetBaseStart;
+ const int nMaxPossibleRangeOffsets = nRemainingLen / 2;
+ if (nRangeCount > nMaxPossibleRangeOffsets)
+ {
+ SAL_WARN("vcl.gdi", "more range offsets requested then space available");
+ nRangeCount = std::max(0, nMaxPossibleRangeOffsets);
+ }
+
+ pCodePairs = new sal_UCS4[ nRangeCount * 2 ];
+ pStartGlyphs = new int[ nRangeCount ];
+
+ sal_UCS4* pCP = pCodePairs;
+ for( int i = 0; i < nRangeCount; ++i )
+ {
+ const sal_UCS4 cMinChar = GetUShort( pBeginBase + 2*i );
+ const sal_UCS4 cMaxChar = GetUShort( pLimitBase + 2*i );
+ const int nGlyphDelta = GetSShort( pDeltaBase + 2*i );
+ const int nRangeOffset = GetUShort( pOffsetBase + 2*i );
+ if( cMinChar > cMaxChar ) { // no sane font should trigger this
+ SAL_WARN("vcl.gdi", "Min char should never be more than the max char!");
+ break;
+ }
+ if( cMaxChar == 0xFFFF ) {
+ SAL_WARN("vcl.gdi", "Format 4 char should not be 0xFFFF");
+ break;
+ }
+ if( !nRangeOffset ) {
+ // glyphid can be calculated directly
+ pStartGlyphs[i] = (cMinChar + nGlyphDelta) & 0xFFFF;
+ } else {
+ // update the glyphid-array with the glyphs in this range
+ pStartGlyphs[i] = -static_cast<int>(aGlyphIdArray.size());
+ const unsigned char* pGlyphIdPtr = pOffsetBase + 2*i + nRangeOffset;
+ const size_t nRemainingSize = pEndValidArea >= pGlyphIdPtr ? pEndValidArea - pGlyphIdPtr : 0;
+ const size_t nMaxPossibleRecords = nRemainingSize/2;
+ if (nMaxPossibleRecords == 0) { // no sane font should trigger this
+ SAL_WARN("vcl.gdi", "More indexes claimed that space available in font!");
+ break;
+ }
+ const size_t nMaxLegalChar = cMinChar + nMaxPossibleRecords-1;
+ if (cMaxChar > nMaxLegalChar) { // no sane font should trigger this
+ SAL_WARN("vcl.gdi", "More indexes claimed that space available in font!");
+ break;
+ }
+ for( sal_UCS4 c = cMinChar; c <= cMaxChar; ++c, pGlyphIdPtr+=2 ) {
+ const int nGlyphIndex = GetUShort( pGlyphIdPtr ) + nGlyphDelta;
+ aGlyphIdArray.push_back( static_cast<sal_uInt16>(nGlyphIndex) );
+ }
+ }
+ *(pCP++) = cMinChar;
+ *(pCP++) = cMaxChar + 1;
+ }
+ nRangeCount = (pCP - pCodePairs) / 2;
+ }
+ // format 12, the most common 32bit char mapping table
+ else if( (nFormat == 12) && ((nOffset+16) < nLength) )
+ {
+ nRangeCount = GetUInt( pCmap + nOffset + 12 );
+ if (nRangeCount < 0)
+ {
+ SAL_WARN("vcl.gdi", "negative RangeCount");
+ nRangeCount = 0;
+ }
+
+ const int nGroupOffset = nOffset + 16;
+ const int nRemainingLen = nLength - nGroupOffset;
+ const int nMaxPossiblePairs = nRemainingLen / 12;
+ if (nRangeCount > nMaxPossiblePairs)
+ {
+ SAL_WARN("vcl.gdi", "more code pairs requested then space available");
+ nRangeCount = std::max(0, nMaxPossiblePairs);
+ }
+
+ pCodePairs = new sal_UCS4[ nRangeCount * 2 ];
+ pStartGlyphs = new int[ nRangeCount ];
+
+ const unsigned char* pGroup = pCmap + nGroupOffset;
+ sal_UCS4* pCP = pCodePairs;
+ for( int i = 0; i < nRangeCount; ++i )
+ {
+ sal_UCS4 cMinChar = GetUInt( pGroup + 0 );
+ sal_UCS4 cMaxChar = GetUInt( pGroup + 4 );
+ int nGlyphId = GetUInt( pGroup + 8 );
+ pGroup += 12;
+
+ if( cMinChar > cMaxChar ) { // no sane font should trigger this
+ SAL_WARN("vcl.gdi", "Min char should never be more than the max char!");
+ break;
+ }
+
+ *(pCP++) = cMinChar;
+ *(pCP++) = cMaxChar + 1;
+ pStartGlyphs[i] = nGlyphId;
+ }
+ nRangeCount = (pCP - pCodePairs) / 2;
+ }
+
+ // check if any subtable resulted in something usable
+ if( nRangeCount <= 0 )
+ {
+ delete[] pCodePairs;
+ delete[] pStartGlyphs;
+
+ // even when no CMAP is available we know it for symbol fonts
+ if( rResult.mbSymbolic )
+ {
+ pCodePairs = new sal_UCS4[4];
+ pCodePairs[0] = 0x0020; // aliased symbols
+ pCodePairs[1] = 0x0100;
+ pCodePairs[2] = 0xF020; // original symbols
+ pCodePairs[3] = 0xF100;
+ rResult.mpRangeCodes = pCodePairs;
+ rResult.mnRangeCount = 2;
+ return true;
+ }
+
+ return false;
+ }
+
+ // recode the code ranges to their unicode encoded ranges if needed
+ rtl_TextToUnicodeConverter aConverter = nullptr;
+ rtl_UnicodeToTextContext aCvtContext = nullptr;
+
+ rResult.mbRecoded = ( eRecodeFrom != RTL_TEXTENCODING_UNICODE );
+ if( rResult.mbRecoded )
+ {
+ aConverter = rtl_createTextToUnicodeConverter( eRecodeFrom );
+ aCvtContext = rtl_createTextToUnicodeContext( aConverter );
+ }
+
+ if( aConverter && aCvtContext )
+ {
+ // determine the set of supported code points from encoded ranges
+ std::set<sal_UCS4> aSupportedCodePoints;
+
+ static const int NINSIZE = 64;
+ static const int NOUTSIZE = 64;
+ std::vector<char> cCharsInp;
+ cCharsInp.reserve(NINSIZE);
+ sal_Unicode cCharsOut[ NOUTSIZE ];
+ sal_UCS4* pCP = pCodePairs;
+ for( int i = 0; i < nRangeCount; ++i )
+ {
+ sal_UCS4 cMin = *(pCP++);
+ sal_UCS4 cEnd = *(pCP++);
+ // ofz#25868 the conversion only makes sense with
+ // input codepoints in 0..SAL_MAX_UINT16 range
+ while (cMin < cEnd && cMin <= SAL_MAX_UINT16)
+ {
+ for (int j = 0; (cMin < cEnd) && (j < NINSIZE); ++cMin, ++j)
+ {
+ if( cMin >= 0x0100 )
+ cCharsInp.push_back(static_cast<char>(cMin >> 8));
+ if( (cMin >= 0x0100) || (cMin < 0x00A0) )
+ cCharsInp.push_back(static_cast<char>(cMin));
+ }
+
+ sal_uInt32 nCvtInfo;
+ sal_Size nSrcCvtBytes;
+ int nOutLen = rtl_convertTextToUnicode(
+ aConverter, aCvtContext,
+ cCharsInp.data(), cCharsInp.size(), cCharsOut, NOUTSIZE,
+ RTL_TEXTTOUNICODE_FLAGS_INVALID_IGNORE
+ | RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_IGNORE,
+ &nCvtInfo, &nSrcCvtBytes );
+
+ cCharsInp.clear();
+
+ for (int j = 0; j < nOutLen; ++j)
+ aSupportedCodePoints.insert( cCharsOut[j] );
+ }
+ }
+
+ rtl_destroyTextToUnicodeConverter( aCvtContext );
+ rtl_destroyTextToUnicodeConverter( aConverter );
+
+ // convert the set of supported code points to ranges
+ std::vector<sal_UCS4> aSupportedRanges;
+
+ for (auto const& supportedPoint : aSupportedCodePoints)
+ {
+ if( aSupportedRanges.empty()
+ || (aSupportedRanges.back() != supportedPoint) )
+ {
+ // add new range beginning with current unicode
+ aSupportedRanges.push_back(supportedPoint);
+ aSupportedRanges.push_back( 0 );
+ }
+
+ // extend existing range to include current unicode
+ aSupportedRanges.back() = supportedPoint + 1;
+ }
+
+ // glyph mapping for non-unicode fonts not implemented
+ delete[] pStartGlyphs;
+ pStartGlyphs = nullptr;
+ aGlyphIdArray.clear();
+
+ // make a pCodePairs array using the vector from above
+ delete[] pCodePairs;
+ nRangeCount = aSupportedRanges.size() / 2;
+ if( nRangeCount <= 0 )
+ return false;
+ pCodePairs = new sal_UCS4[ nRangeCount * 2 ];
+ pCP = pCodePairs;
+ for (auto const& supportedRange : aSupportedRanges)
+ *(pCP++) = supportedRange;
+ }
+
+ // prepare the glyphid-array if needed
+ // TODO: merge ranges if they are close enough?
+ sal_uInt16* pGlyphIds = nullptr;
+ if( !aGlyphIdArray.empty())
+ {
+ pGlyphIds = new sal_uInt16[ aGlyphIdArray.size() ];
+ sal_uInt16* pOut = pGlyphIds;
+ for (auto const& glyphId : aGlyphIdArray)
+ *(pOut++) = glyphId;
+ }
+
+ // update the result struct
+ rResult.mpRangeCodes = pCodePairs;
+ rResult.mpStartGlyphs = pStartGlyphs;
+ rResult.mnRangeCount = nRangeCount;
+ rResult.mpGlyphIds = pGlyphIds;
+ return true;
+}
+
+FontCharMap::FontCharMap()
+ : mpImplFontCharMap( ImplFontCharMap::getDefaultMap() )
+{
+}
+
+FontCharMap::FontCharMap( ImplFontCharMapRef const & pIFCMap )
+ : mpImplFontCharMap( pIFCMap )
+{
+}
+
+FontCharMap::FontCharMap( const CmapResult& rCR )
+ : mpImplFontCharMap(new ImplFontCharMap(rCR))
+{
+}
+
+FontCharMap::~FontCharMap()
+{
+ mpImplFontCharMap = nullptr;
+}
+
+FontCharMapRef FontCharMap::GetDefaultMap( bool bSymbol )
+{
+ FontCharMapRef xFontCharMap( new FontCharMap( ImplFontCharMap::getDefaultMap( bSymbol ) ) );
+ return xFontCharMap;
+}
+
+bool FontCharMap::IsDefaultMap() const
+{
+ return mpImplFontCharMap->isDefaultMap();
+}
+
+int FontCharMap::GetCharCount() const
+{
+ return mpImplFontCharMap->mnCharCount;
+}
+
+int FontCharMap::CountCharsInRange( sal_UCS4 cMin, sal_UCS4 cMax ) const
+{
+ int nCount = 0;
+
+ // find and adjust range and char count for cMin
+ int nRangeMin = findRangeIndex( cMin );
+ if( nRangeMin & 1 )
+ ++nRangeMin;
+ else if( cMin > mpImplFontCharMap->mpRangeCodes[ nRangeMin ] )
+ nCount -= cMin - mpImplFontCharMap->mpRangeCodes[ nRangeMin ];
+
+ // find and adjust range and char count for cMax
+ int nRangeMax = findRangeIndex( cMax );
+ if( nRangeMax & 1 )
+ --nRangeMax;
+ else
+ nCount -= mpImplFontCharMap->mpRangeCodes[ nRangeMax+1 ] - cMax - 1;
+
+ // count chars in complete ranges between cMin and cMax
+ for( int i = nRangeMin; i <= nRangeMax; i+=2 )
+ nCount += mpImplFontCharMap->mpRangeCodes[i+1] - mpImplFontCharMap->mpRangeCodes[i];
+
+ return nCount;
+}
+
+bool FontCharMap::HasChar( sal_UCS4 cChar ) const
+{
+ bool bHasChar = false;
+
+ if( mpImplFontCharMap->mpStartGlyphs == nullptr ) { // only the char-ranges are known
+ const int nRange = findRangeIndex( cChar );
+ if( nRange==0 && cChar < mpImplFontCharMap->mpRangeCodes[0] )
+ return false;
+ bHasChar = ((nRange & 1) == 0); // inside a range
+ } else { // glyph mapping is available
+ const int nGlyphIndex = GetGlyphIndex( cChar );
+ bHasChar = (nGlyphIndex != 0); // not the notdef-glyph
+ }
+
+ return bHasChar;
+}
+
+sal_UCS4 FontCharMap::GetFirstChar() const
+{
+ return mpImplFontCharMap->mpRangeCodes[0];
+}
+
+sal_UCS4 FontCharMap::GetLastChar() const
+{
+ return (mpImplFontCharMap->mpRangeCodes[ 2*mpImplFontCharMap->mnRangeCount-1 ] - 1);
+}
+
+sal_UCS4 FontCharMap::GetNextChar( sal_UCS4 cChar ) const
+{
+ if( cChar < GetFirstChar() )
+ return GetFirstChar();
+ if( cChar >= GetLastChar() )
+ return GetLastChar();
+
+ int nRange = findRangeIndex( cChar + 1 );
+ if( nRange & 1 ) // outside of range?
+ return mpImplFontCharMap->mpRangeCodes[ nRange + 1 ]; // => first in next range
+ return (cChar + 1);
+}
+
+sal_UCS4 FontCharMap::GetPrevChar( sal_UCS4 cChar ) const
+{
+ if( cChar <= GetFirstChar() )
+ return GetFirstChar();
+ if( cChar > GetLastChar() )
+ return GetLastChar();
+
+ int nRange = findRangeIndex( cChar - 1 );
+ if( nRange & 1 ) // outside a range?
+ return (mpImplFontCharMap->mpRangeCodes[ nRange ] - 1); // => last in prev range
+ return (cChar - 1);
+}
+
+int FontCharMap::GetIndexFromChar( sal_UCS4 cChar ) const
+{
+ // TODO: improve linear walk?
+ int nCharIndex = 0;
+ const sal_UCS4* pRange = &mpImplFontCharMap->mpRangeCodes[0];
+ for( int i = 0; i < mpImplFontCharMap->mnRangeCount; ++i )
+ {
+ sal_UCS4 cFirst = *(pRange++);
+ sal_UCS4 cLast = *(pRange++);
+ if( cChar >= cLast )
+ nCharIndex += cLast - cFirst;
+ else if( cChar >= cFirst )
+ return nCharIndex + (cChar - cFirst);
+ else
+ break;
+ }
+
+ return -1;
+}
+
+sal_UCS4 FontCharMap::GetCharFromIndex( int nIndex ) const
+{
+ // TODO: improve linear walk?
+ const sal_UCS4* pRange = &mpImplFontCharMap->mpRangeCodes[0];
+ for( int i = 0; i < mpImplFontCharMap->mnRangeCount; ++i )
+ {
+ sal_UCS4 cFirst = *(pRange++);
+ sal_UCS4 cLast = *(pRange++);
+ nIndex -= cLast - cFirst;
+ if( nIndex < 0 )
+ return (cLast + nIndex);
+ }
+
+ // we can only get here with an out-of-bounds charindex
+ return mpImplFontCharMap->mpRangeCodes[0];
+}
+
+int FontCharMap::findRangeIndex( sal_UCS4 cChar ) const
+{
+ int nLower = 0;
+ int nMid = mpImplFontCharMap->mnRangeCount;
+ int nUpper = 2 * mpImplFontCharMap->mnRangeCount - 1;
+ while( nLower < nUpper )
+ {
+ if( cChar >= mpImplFontCharMap->mpRangeCodes[ nMid ] )
+ nLower = nMid;
+ else
+ nUpper = nMid - 1;
+ nMid = (nLower + nUpper + 1) / 2;
+ }
+
+ return nMid;
+}
+
+int FontCharMap::GetGlyphIndex( sal_UCS4 cChar ) const
+{
+ // return -1 if the object doesn't know the glyph ids
+ if( !mpImplFontCharMap->mpStartGlyphs )
+ return -1;
+
+ // return 0 if the unicode doesn't have a matching glyph
+ int nRange = findRangeIndex( cChar );
+ // check that we are inside any range
+ if( (nRange == 0) && (cChar < mpImplFontCharMap->mpRangeCodes[0]) ) {
+ // symbol aliasing gives symbol fonts a second chance
+ const bool bSymbolic = cChar <= 0xFF && (mpImplFontCharMap->mpRangeCodes[0]>=0xF000) &&
+ (mpImplFontCharMap->mpRangeCodes[1]<=0xF0FF);
+ if( !bSymbolic )
+ return 0;
+ // check for symbol aliasing (U+F0xx -> U+00xx)
+ cChar |= 0xF000;
+ nRange = findRangeIndex( cChar );
+ if( (nRange == 0) && (cChar < mpImplFontCharMap->mpRangeCodes[0]) ) {
+ return 0;
+ }
+ }
+ // check that we are inside a range
+ if( (nRange & 1) != 0 )
+ return 0;
+
+ // get glyph index directly or indirectly
+ int nGlyphIndex = cChar - mpImplFontCharMap->mpRangeCodes[ nRange ];
+ const int nStartIndex = mpImplFontCharMap->mpStartGlyphs[ nRange/2 ];
+ if( nStartIndex >= 0 ) {
+ // the glyph index can be calculated
+ nGlyphIndex += nStartIndex;
+ } else {
+ // the glyphid array has the glyph index
+ nGlyphIndex = mpImplFontCharMap->mpGlyphIds[ nGlyphIndex - nStartIndex];
+ }
+
+ return nGlyphIndex;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/font/fontinstance.cxx b/vcl/source/font/fontinstance.cxx
new file mode 100644
index 000000000..40e334bd6
--- /dev/null
+++ b/vcl/source/font/fontinstance.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <hb-ot.h>
+#include <hb-graphite2.h>
+
+#include <fontinstance.hxx>
+#include <impfontcache.hxx>
+
+#include <PhysicalFontFace.hxx>
+
+// 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;
+ boost::hash_combine(seed, rData.first);
+ boost::hash_combine(seed, rData.second);
+ return seed;
+ }
+ };
+}
+
+
+LogicalFontInstance::LogicalFontInstance(const PhysicalFontFace& rFontFace, const FontSelectPattern& rFontSelData )
+ : mxFontMetric( new ImplFontMetricData( rFontSelData ))
+ , mpConversion( nullptr )
+ , mnLineHeight( 0 )
+ , mnOwnOrientation( 0 )
+ , mnOrientation( 0 )
+ , mbInit( false )
+ , mpFontCache( nullptr )
+ , m_aFontSelData(rFontSelData)
+ , m_pHbFont(nullptr)
+ , m_nAveWidthFactor(1.0f)
+ , m_pFontFace(&const_cast<PhysicalFontFace&>(rFontFace))
+{
+}
+
+LogicalFontInstance::~LogicalFontInstance()
+{
+ mpUnicodeFallbackList.reset();
+ mpFontCache = nullptr;
+ mxFontMetric = nullptr;
+
+ if (m_pHbFont)
+ hb_font_destroy(m_pHbFont);
+}
+
+hb_font_t* LogicalFontInstance::InitHbFont(hb_face_t* pHbFace)
+{
+ assert(pHbFace);
+ hb_font_t* pHbFont = hb_font_create(pHbFace);
+ unsigned int nUPEM = hb_face_get_upem(pHbFace);
+ hb_font_set_scale(pHbFont, nUPEM, nUPEM);
+ hb_ot_font_set_funcs(pHbFont);
+ // hb_font_t keeps a reference to hb_face_t, so destroy this one.
+ hb_face_destroy(pHbFace);
+ return pHbFont;
+}
+
+int LogicalFontInstance::GetKashidaWidth()
+{
+ hb_font_t* pHbFont = GetHbFont();
+ hb_position_t nWidth = 0;
+ hb_codepoint_t nIndex = 0;
+
+ if (hb_font_get_glyph(pHbFont, 0x0640, 0, &nIndex))
+ {
+ double nXScale = 0;
+ GetScale(&nXScale, nullptr);
+ nWidth = hb_font_get_glyph_h_advance(pHbFont, nIndex) * nXScale;
+ }
+
+ return nWidth;
+}
+
+void LogicalFontInstance::GetScale(double* nXScale, double* nYScale)
+{
+ hb_face_t* pHbFace = hb_font_get_face(GetHbFont());
+ unsigned int nUPEM = hb_face_get_upem(pHbFace);
+
+ double nHeight(m_aFontSelData.mnHeight);
+
+ // On Windows, mnWidth is relative to average char width not font height,
+ // and we need to keep it that way for GDI to correctly scale the glyphs.
+ // Here we compensate for this so that HarfBuzz gives us the correct glyph
+ // positions.
+ double nWidth(m_aFontSelData.mnWidth ? m_aFontSelData.mnWidth * m_nAveWidthFactor : nHeight);
+
+ if (nYScale)
+ *nYScale = nHeight / nUPEM;
+
+ if (nXScale)
+ *nXScale = nWidth / nUPEM;
+}
+
+void LogicalFontInstance::AddFallbackForUnicode( sal_UCS4 cChar, FontWeight eWeight, const OUString& rFontName )
+{
+ if( !mpUnicodeFallbackList )
+ mpUnicodeFallbackList.reset(new UnicodeFallbackList);
+ (*mpUnicodeFallbackList)[ std::pair< sal_UCS4, FontWeight >(cChar,eWeight) ] = rFontName;
+}
+
+bool LogicalFontInstance::GetFallbackForUnicode( sal_UCS4 cChar, FontWeight eWeight, OUString* pFontName ) const
+{
+ if( !mpUnicodeFallbackList )
+ return false;
+
+ UnicodeFallbackList::const_iterator it = mpUnicodeFallbackList->find( std::pair< sal_UCS4, FontWeight >(cChar,eWeight) );
+ if( it == mpUnicodeFallbackList->end() )
+ return false;
+
+ *pFontName = (*it).second;
+ return true;
+}
+
+void LogicalFontInstance::IgnoreFallbackForUnicode( sal_UCS4 cChar, FontWeight eWeight, const OUString& rFontName )
+{
+ UnicodeFallbackList::iterator it = mpUnicodeFallbackList->find( std::pair< sal_UCS4,FontWeight >(cChar,eWeight) );
+ if( it == mpUnicodeFallbackList->end() )
+ return;
+ if( (*it).second == rFontName )
+ mpUnicodeFallbackList->erase( it );
+}
+
+bool LogicalFontInstance::GetGlyphBoundRect(sal_GlyphId nID, tools::Rectangle &rRect, bool bVertical) const
+{
+ if (mpFontCache && mpFontCache->GetCachedGlyphBoundRect(this, nID, rRect))
+ return true;
+
+ bool res = ImplGetGlyphBoundRect(nID, rRect, bVertical);
+ if (mpFontCache && res)
+ mpFontCache->CacheGlyphBoundRect(this, nID, rRect);
+ return res;
+}
+
+bool LogicalFontInstance::IsGraphiteFont()
+{
+ if (!m_xbIsGraphiteFont)
+ {
+ m_xbIsGraphiteFont = hb_graphite2_face_get_gr_face(hb_font_get_face(GetHbFont())) != nullptr;
+ }
+ return *m_xbIsGraphiteFont;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/font/fontmetric.cxx b/vcl/source/font/fontmetric.cxx
new file mode 100644
index 000000000..3b7a7ed09
--- /dev/null
+++ b/vcl/source/font/fontmetric.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/mslangid.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <unotools/configmgr.hxx>
+#include <vcl/metric.hxx>
+#include <vcl/outdev.hxx>
+#include <sal/log.hxx>
+
+#include <fontinstance.hxx>
+#include <fontselect.hxx>
+#include <impfontmetricdata.hxx>
+#include <sft.hxx>
+
+#include <com/sun/star/uno/Sequence.hxx>
+#include <comphelper/sequence.hxx>
+
+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 ),
+ mbFullstopCentered( false )
+{}
+
+FontMetric::FontMetric( const FontMetric& rFontMetric ) = default;
+
+FontMetric::~FontMetric()
+{
+}
+
+FontMetric& FontMetric::operator=(const FontMetric& rFontMetric) = default;
+
+FontMetric& FontMetric::operator=(FontMetric&& rFontMetric) = default;
+
+bool FontMetric::operator==( const FontMetric& r ) const
+{
+ if( Font::operator!=(r) )
+ return false;
+ 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;
+}
+
+ImplFontMetricData::ImplFontMetricData( const 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 )
+ , 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( GetNextFontToken( rFontSelData.GetFamilyName(), nTokenPos ) );
+ SetStyleName( rFontSelData.GetStyleName() );
+}
+
+void ImplFontMetricData::ImplInitTextLineSize( const OutputDevice* pDev )
+{
+ 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;
+
+ long nLineHeight = ((nDescent*25)+50) / 100;
+ if ( !nLineHeight )
+ nLineHeight = 1;
+ long nLineHeight2 = nLineHeight / 2;
+ if ( !nLineHeight2 )
+ nLineHeight2 = 1;
+
+ long nBLineHeight = ((nDescent*50)+50) / 100;
+ if ( nBLineHeight == nLineHeight )
+ nBLineHeight++;
+ long nBLineHeight2 = nBLineHeight/2;
+ if ( !nBLineHeight2 )
+ nBLineHeight2 = 1;
+
+ long n2LineHeight = ((nDescent*16)+50) / 100;
+ if ( !n2LineHeight )
+ n2LineHeight = 1;
+ long n2LineDY = n2LineHeight;
+ /* #117909#
+ * add some pixels to minimum double line distance on higher resolution devices
+ */
+ long nMin2LineDY = 1 + pDev->GetDPIY()/150;
+ if ( n2LineDY < nMin2LineDY )
+ n2LineDY = nMin2LineDY;
+ long n2LineDY2 = n2LineDY/2;
+ if ( !n2LineDY2 )
+ n2LineDY2 = 1;
+
+ const vcl::Font& rFont ( pDev->GetFont() );
+ bool bCJKVertical = MsLangId::isCJK(rFont.GetLanguage()) && rFont.IsVertical();
+ long nUnderlineOffset = bCJKVertical ? mnDescent : (mnDescent/2 + 1);
+ long nStrikeoutOffset = -((mnAscent - mnIntLeading) / 3);
+
+ mnUnderlineSize = nLineHeight;
+ mnUnderlineOffset = nUnderlineOffset - nLineHeight2;
+
+ mnBUnderlineSize = nBLineHeight;
+ mnBUnderlineOffset = nUnderlineOffset - nBLineHeight2;
+
+ mnDUnderlineSize = n2LineHeight;
+ mnDUnderlineOffset1 = nUnderlineOffset - n2LineDY2 - n2LineHeight;
+ mnDUnderlineOffset2 = mnDUnderlineOffset1 + n2LineDY + n2LineHeight;
+
+ long nWCalcSize = mnDescent;
+ if ( nWCalcSize < 6 )
+ {
+ if ( (nWCalcSize == 1) || (nWCalcSize == 2) )
+ mnWUnderlineSize = nWCalcSize;
+ else
+ mnWUnderlineSize = 3;
+ }
+ else
+ mnWUnderlineSize = ((nWCalcSize*50)+50) / 100;
+
+
+ // Don't assume that wavelines are never placed below the descent, because for most fonts the waveline
+ // is drawn into the text
+ mnWUnderlineOffset = nUnderlineOffset;
+
+ mnStrikeoutSize = nLineHeight;
+ mnStrikeoutOffset = nStrikeoutOffset - nLineHeight2;
+
+ mnBStrikeoutSize = nBLineHeight;
+ mnBStrikeoutOffset = nStrikeoutOffset - nBLineHeight2;
+
+ mnDStrikeoutSize = n2LineHeight;
+ mnDStrikeoutOffset1 = nStrikeoutOffset - n2LineDY2 - n2LineHeight;
+ mnDStrikeoutOffset2 = mnDStrikeoutOffset1 + n2LineDY + n2LineHeight;
+
+ mnBulletOffset = ( pDev->GetTextWidth( OUString( u' ' ) ) - pDev->GetTextWidth( OUString( u'\x00b7' ) ) ) >> 1 ;
+
+}
+
+
+void ImplFontMetricData::ImplInitAboveTextLineSize()
+{
+ 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;
+ }
+
+ long nLineHeight = ((nIntLeading*25)+50) / 100;
+ if ( !nLineHeight )
+ nLineHeight = 1;
+
+ long nBLineHeight = ((nIntLeading*50)+50) / 100;
+ if ( nBLineHeight == nLineHeight )
+ nBLineHeight++;
+
+ long n2LineHeight = ((nIntLeading*16)+50) / 100;
+ if ( !n2LineHeight )
+ n2LineHeight = 1;
+
+ long nCeiling = -mnAscent;
+
+ mnAboveUnderlineSize = nLineHeight;
+ mnAboveUnderlineOffset = nCeiling + (nIntLeading - nLineHeight + 1) / 2;
+
+ mnAboveBUnderlineSize = nBLineHeight;
+ mnAboveBUnderlineOffset = nCeiling + (nIntLeading - nBLineHeight + 1) / 2;
+
+ mnAboveDUnderlineSize = n2LineHeight;
+ mnAboveDUnderlineOffset1 = nCeiling + (nIntLeading - 3*n2LineHeight + 1) / 2;
+ mnAboveDUnderlineOffset2 = nCeiling + (nIntLeading + n2LineHeight + 1) / 2;
+
+ long nWCalcSize = nIntLeading;
+ if ( nWCalcSize < 6 )
+ {
+ if ( (nWCalcSize == 1) || (nWCalcSize == 2) )
+ mnAboveWUnderlineSize = nWCalcSize;
+ else
+ mnAboveWUnderlineSize = 3;
+ }
+ else
+ mnAboveWUnderlineSize = ((nWCalcSize*50)+50) / 100;
+
+ mnAboveWUnderlineOffset = nCeiling + (nIntLeading + 1) / 2;
+}
+
+void ImplFontMetricData::ImplInitFlags( const OutputDevice* pDev )
+{
+ const vcl::Font& rFont ( pDev->GetFont() );
+ bool bCentered = true;
+ if (MsLangId::isCJK(rFont.GetLanguage()))
+ {
+ const OUString sFullstop( u'\x3001' ); // Fullwidth fullstop
+ tools::Rectangle aRect;
+ pDev->GetTextBoundRect( aRect, sFullstop );
+ const auto nH = rFont.GetFontSize().Height();
+ const auto nB = aRect.Left();
+ // Use 18.75% as a threshold to define a centered fullwidth fullstop.
+ // In general, nB/nH < 5% for most Japanese fonts.
+ bCentered = nB > (((nH >> 1)+nH)>>3);
+ }
+ SetFullstopCenteredFlag( bCentered );
+}
+
+bool ImplFontMetricData::ShouldUseWinMetrics(const vcl::TTGlobalFontInfo& rInfo)
+{
+ if (utl::ConfigManager::IsFuzzing())
+ return false;
+
+ OUString aFontIdentifier(
+ GetFamilyName() + ","
+ + OUString::number(rInfo.ascender) + "," + OUString::number(rInfo.descender) + ","
+ + OUString::number(rInfo.typoAscender) + "," + OUString::number(rInfo.typoDescender) + ","
+ + OUString::number(rInfo.winAscent) + "," + OUString::number(rInfo.winDescent));
+
+ css::uno::Sequence<OUString> rWinMetricFontList(
+ officecfg::Office::Common::Misc::FontsUseWinMetrics::get());
+ if (comphelper::findValue(rWinMetricFontList, aFontIdentifier) != -1)
+ {
+ SAL_INFO("vcl.gdi.fontmetric", "Using win metrics for: " << aFontIdentifier);
+ return true;
+ }
+ return false;
+}
+
+/*
+ * Calculate line spacing:
+ *
+ * - hhea metrics should be used, since hhea is a mandatory font table and
+ * should always be present.
+ * - But if OS/2 is present, it should be used since it is mandatory in
+ * Windows.
+ * OS/2 has Typo and Win metrics, but the later was meant to control
+ * text clipping not line spacing and can be ridiculously large.
+ * Unfortunately many Windows application incorrectly use the Win metrics
+ * (thanks to GDI’s TEXTMETRIC) and old fonts might be designed with this
+ * in mind, so OpenType introduced a flag for fonts to indicate that they
+ * really want to use Typo metrics. So for best backward compatibility:
+ * - Use Win metrics if available.
+ * - Unless USE_TYPO_METRICS flag is set, in which case use Typo metrics.
+*/
+void ImplFontMetricData::ImplCalcLineSpacing(LogicalFontInstance *pFontInstance)
+{
+ mnAscent = mnDescent = mnExtLeading = mnIntLeading = 0;
+
+ hb_font_t* pHbFont = pFontInstance->GetHbFont();
+ hb_face_t* pHbFace = hb_font_get_face(pHbFont);
+
+ hb_blob_t* pHhea = hb_face_reference_table(pHbFace, HB_TAG('h', 'h', 'e', 'a'));
+ hb_blob_t* pOS2 = hb_face_reference_table(pHbFace, HB_TAG('O', 'S', '/', '2'));
+
+ vcl::TTGlobalFontInfo rInfo = {};
+ GetTTFontMetrics(reinterpret_cast<const uint8_t*>(hb_blob_get_data(pHhea, nullptr)), hb_blob_get_length(pHhea),
+ reinterpret_cast<const uint8_t*>(hb_blob_get_data(pOS2, nullptr)), hb_blob_get_length(pOS2),
+ &rInfo);
+
+ hb_blob_destroy(pHhea);
+ hb_blob_destroy(pOS2);
+
+ double nUPEM = hb_face_get_upem(pHbFace);
+ double fScale = mnHeight / nUPEM;
+ double fAscent = 0, fDescent = 0, fExtLeading = 0;
+
+ // Try hhea table first.
+ // tdf#107605: Some fonts have weird values here, so check that ascender is
+ // +ve and descender is -ve as they normally should.
+ if (rInfo.ascender >= 0 && rInfo.descender <= 0)
+ {
+ fAscent = rInfo.ascender * fScale;
+ fDescent = -rInfo.descender * fScale;
+ fExtLeading = rInfo.linegap * fScale;
+ }
+
+ // But if OS/2 is present, prefer it.
+ if (rInfo.winAscent || rInfo.winDescent ||
+ rInfo.typoAscender || rInfo.typoDescender)
+ {
+ if (ShouldUseWinMetrics(rInfo) || (fAscent == 0.0 && fDescent == 0.0))
+ {
+ fAscent = rInfo.winAscent * fScale;
+ fDescent = rInfo.winDescent * fScale;
+ fExtLeading = 0;
+ }
+
+ const uint16_t kUseTypoMetricsMask = 1 << 7;
+ if (rInfo.fsSelection & kUseTypoMetricsMask &&
+ rInfo.typoAscender >= 0 && rInfo.typoDescender <= 0)
+ {
+ fAscent = rInfo.typoAscender * fScale;
+ fDescent = -rInfo.typoDescender * fScale;
+ fExtLeading = rInfo.typoLineGap * fScale;
+ }
+ }
+
+ mnAscent = round(fAscent);
+ mnDescent = round(fDescent);
+ mnExtLeading = round(fExtLeading);
+
+ if (mnAscent || mnDescent)
+ mnIntLeading = mnAscent + mnDescent - mnHeight;
+
+ SAL_INFO("vcl.gdi.fontmetric", GetFamilyName()
+ << ": fsSelection: " << rInfo.fsSelection
+ << ", typoAscender: " << rInfo.typoAscender
+ << ", typoDescender: " << rInfo.typoDescender
+ << ", typoLineGap: " << rInfo.typoLineGap
+ << ", winAscent: " << rInfo.winAscent
+ << ", winDescent: " << rInfo.winDescent
+ << ", ascender: " << rInfo.ascender
+ << ", descender: " << rInfo.descender
+ << ", linegap: " << rInfo.linegap
+ );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/font/fontselect.cxx b/vcl/source/font/fontselect.cxx
new file mode 100644
index 000000000..faafdb61b
--- /dev/null
+++ b/vcl/source/font/fontselect.cxx
@@ -0,0 +1,150 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+#include <o3tl/safeint.hxx>
+#include <tools/gen.hxx>
+#include <vcl/font.hxx>
+
+#include <PhysicalFontFace.hxx>
+#include <fontselect.hxx>
+
+// These mustn't conflict with font name lists which use ; and ,
+const char FontSelectPattern::FEAT_PREFIX = ':';
+const char FontSelectPattern::FEAT_SEPARATOR = '&';
+
+FontSelectPattern::FontSelectPattern( const vcl::Font& rFont,
+ const OUString& rSearchName, const Size& rSize, float fExactHeight, bool bNonAntialias)
+ : maSearchName( rSearchName )
+ , mnWidth( rSize.Width() )
+ , mnHeight( rSize.Height() )
+ , mfExactHeight( fExactHeight)
+ , mnOrientation( rFont.GetOrientation() )
+ , meLanguage( rFont.GetLanguage() )
+ , mbVertical( rFont.IsVertical() )
+ , mbNonAntialiased(bNonAntialias)
+ , mbEmbolden( false )
+{
+ maTargetName = GetFamilyName();
+
+ rFont.GetFontAttributes( *this );
+
+ // normalize orientation between 0 and 3600
+ if( 3600 <= static_cast<unsigned>(mnOrientation) )
+ {
+ if( mnOrientation >= 0 )
+ mnOrientation %= 3600;
+ else
+ mnOrientation = 3600 - (-mnOrientation % 3600);
+ }
+
+ // 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;
+ 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/fontsubset/cff.cxx b/vcl/source/fontsubset/cff.cxx
new file mode 100644
index 000000000..dd7da8d92
--- /dev/null
+++ b/vcl/source/fontsubset/cff.cxx
@@ -0,0 +1,2061 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <cstdio>
+#include <cstring>
+#include <vector>
+#include <assert.h>
+
+#include <fontsubset.hxx>
+
+#include <o3tl/safeint.hxx>
+#include <strhelper.hxx>
+#include <sal/log.hxx>
+
+typedef sal_uInt8 U8;
+typedef sal_uInt16 U16;
+typedef sal_Int64 S64;
+
+typedef double RealType;
+typedef RealType ValType;
+
+static const char* pStringIds[] = {
+/*0*/ ".notdef", "space", "exclam", "quotedbl",
+ "numbersign", "dollar", "percent", "ampersand",
+ "quoteright", "parenleft", "parenright", "asterisk",
+ "plus", "comma", "hyphen", "period",
+/*16*/ "slash", "zero", "one", "two",
+ "three", "four", "five", "six",
+ "seven", "eight", "nine", "colon",
+ "semicolon", "less", "equal", "greater",
+/*32*/ "question", "at", "A", "B",
+ "C", "D", "E", "F",
+ "G", "H", "I", "J",
+ "K", "L", "M", "N",
+/*48*/ "O", "P", "Q", "R",
+ "S", "T", "U", "V",
+ "W", "X", "Y", "Z",
+ "bracketleft", "backslash", "bracketright", "asciicircum",
+/*64*/ "underscore", "quoteleft", "a", "b",
+ "c", "d", "e", "f",
+ "g", "h", "i", "j",
+ "k", "l", "m", "n",
+/*80*/ "o", "p", "q", "r",
+ "s", "t", "u", "v",
+ "w", "x", "y", "z",
+ "braceleft", "bar", "braceright", "asciitilde",
+/*96*/ "exclamdown", "cent", "sterlin", "fraction",
+ "yen", "florin", "section", "currency",
+ "quotesingle", "quotedblleft", "guillemotleft", "guilsinglleft",
+ "guilsinglright", "fi", "fl", "endash",
+/*112*/ "dagger", "daggerdbl", "periodcentered", "paragraph",
+ "bullet", "quotesinglbase", "quotedblbase", "quotedblright",
+ "guillemotright", "ellipsis", "perthousand", "questiondown",
+ "grave", "acute", "circumflex", "tilde",
+/*128*/ "macron", "breve", "dotaccent", "dieresis",
+ "ring", "cedilla", "hungarumlaut", "ogonek",
+ "caron", "emdash", "AE", "ordfeminine",
+ "Lslash", "Oslash", "OE", "ordmasculine",
+/*144*/ "ae", "dotlessi", "lslash", "oslash",
+ "oe", "germandbls", "onesuperior", "logicalnot",
+ "mu", "trademark", "Eth", "onehalf",
+ "plusminus", "Thorn", "onequarter", "divide",
+/*160*/ "brokenbar", "degree", "thorn", "threequarters",
+ "twosuperior", "registered", "minus", "eth",
+ "multiply", "threesuperior", "copyright", "Aacute",
+ "Acircumflex", "Adieresis", "Agrave", "Aring",
+/*176*/ "Atilde", "Ccedilla", "Eacute", "Ecircumflex",
+ "Edieresis", "Egrave", "Iacute", "Icircumflex",
+ "Idieresis", "Igrave", "Ntilde", "Oacute",
+ "Ocircumflex", "Odieresis", "Ograve", "Otilde",
+/*192*/ "Scaron", "Uacute", "Ucircumflex", "Udieresis",
+ "Ugrave", "Yacute", "Ydieresis", "Zcaron",
+ "aacute", "acircumflex", "adieresis", "agrave",
+ "aring", "atilde", "ccedilla", "eacute",
+/*208*/ "ecircumflex", "edieresis", "egrave", "iacute",
+ "icircumflex", "idieresis", "igrave", "ntilde",
+ "oacute", "ocircumflex", "odieresis", "ograve",
+ "otilde", "scaron", "uacute", "ucircumflex",
+/*224*/ "udieresis", "ugrave", "yacute", "ydieresis",
+ "zcaron", "exclamsmall", "Hungarumlautsmall","dollaroldstyle",
+ "dollarsuperior", "ampersandsmall", "Acutesmall", "parenleftsuperior",
+ "parenrightsuperior","twodotenleader", "onedotenleader", "zerooldstyle",
+/*240*/ "oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle",
+ "fiveoldstyle", "sixoldstyle", "sevenoldstyle", "eightoldstyle",
+ "nineoldstile", "commasuperior", "threequartersemdash","periodsuperior",
+ "questionsmall", "asuperior", "bsuperior", "centsuperior",
+/*256*/ "dsuperior", "esuperior", "isuperior", "lsuperior",
+ "msuperior", "nsuperior", "osuperior", "rsuperior",
+ "ssuperior", "tsuperior", "ff", "ffi",
+ "ffl", "parenleftinferior","parenrightinferior","Circumflexsmall",
+/*272*/ "hyphensuperior","Gravesmall", "Asmall", "Bsmall",
+ "Csmall", "Dsmall", "Esmall", "Fsmall",
+ "Gsmall", "Hsmall", "Ismall", "Jsmall",
+ "Ksmall", "Lsmall", "Msmall", "Nsmall",
+/*288*/ "Osmall", "Psmall", "Qsmall", "Rsmall",
+ "Ssmall", "Tsmall", "Usmall", "Vsmall",
+ "Wsmall", "Xsmall", "Ysmall", "Zsmall",
+ "colonmonetary", "onefitted", "rupia", "Tildesmall",
+/*304*/ "exclamdownsmall","centoldstyle", "Lslashsmall", "Scaronsmall",
+ "Zcaronsmall", "Dieresissmall", "Brevesmall", "Caronsmall",
+ "Dotaccentsmall", "Macronsmall", "figuredash", "hypheninferior",
+ "Ogoneksmall", "Ringsmall", "Cedillasmall", "questiondownsmall",
+/*320*/ "oneeight", "threeeights", "fiveeights", "seveneights",
+ "onethird", "twothirds", "zerosuperior", "foursuperior",
+ "fivesuperior", "sixsuperior", "sevensuperior", "eightsuperior",
+ "ninesuperior", "zeroinferior", "oneinferior", "twoinferior",
+/*336*/ "threeinferior","fourinferior", "fiveinferior", "sixinferior",
+ "seveninferior", "eightinferior", "nineinferior", "centinferior",
+ "dollarinferior", "periodinferior", "commainferior", "Agravesmall",
+ "Aacutesmall", "Acircumflexsmall", "Atildesmall", "Adieresissmall",
+/*352*/ "Aringsmall", "AEsmall", "Ccedillasmall", "Egravesmall",
+ "Eacutesmall", "Ecircumflexsmall", "Edieresissmall", "Igravesmall",
+ "Iacutesmall", "Icircumflexsmall", "Idieresissmall", "Ethsmall",
+ "Ntildesmall", "Ogravesmall", "Oacutesmall", "Ocircumflexsmall",
+/*368*/ "Otildesmall", "Odieressissmall", "OEsmall", "Oslashsmall",
+ "Ugravesmall", "Uacutesmall", "Ucircumflexsmall", "Udieresissmall",
+ "Yacutesmall", "Thornsmall", "Ydieresissmall", "001.000",
+ "001.001", "001.002", "001.003", "Black",
+/*384*/ "Bold", "Book", "Light", "Medium",
+ "Regular", "Roman", "Semibold"
+};
+
+// TOP DICT keywords (also covers PRIV DICT keywords)
+static const char* pDictOps[] = {
+ "sVersion", "sNotice", "sFullName", "sFamilyName",
+ "sWeight", "aFontBBox", "dBlueValues", "dOtherBlues",
+ "dFamilyBlues", "dFamilyOtherBlues", "nStdHW", "nStdVW",
+ "xESC", "nUniqueID", "aXUID", "nCharset",
+ "nEncoding", "nCharStrings", "PPrivate", "nSubrs",
+ "nDefaultWidthX", "nNominalWidthX", nullptr, nullptr,
+ nullptr, nullptr, nullptr, nullptr,
+ "shortint", "longint", "BCD", nullptr
+};
+
+// TOP DICT escapes (also covers PRIV DICT escapes)
+static const char* pDictEscs[] = {
+ "sCopyright", "bIsFixedPitch", "nItalicAngle", "nUnderlinePosition",
+ "nUnderlineThickness", "nPaintType", "tCharstringType", "aFontMatrix",
+ "nStrokeWidth", "nBlueScale", "nBlueShift", "nBlueFuzz",
+ "dStemSnapH", "dStemSnapV", "bForceBold", nullptr,
+ nullptr, "nLanguageGroup", "nExpansionFactor", "nInitialRandomSeed",
+ "nSyntheticBase", "sPostScript", "sBaseFontName", "dBaseFontBlend",
+ nullptr, nullptr, nullptr, nullptr,
+ nullptr, nullptr, "rROS", "nCIDFontVersion",
+ "nCIDFontRevision", "nCIDFontType", "nCIDCount", "nUIDBase",
+ "nFDArray", "nFDSelect", "sFontName"
+};
+
+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;
+};
+
+class CffSubsetterContext
+: private CffGlobal
+{
+public:
+ static const int NMAXSTACK = 48; // see CFF.appendixB
+ static const int NMAXHINTS = 2*96; // see CFF.appendixB
+ static const int NMAXTRANS = 32; // see CFF.appendixB
+
+ explicit CffSubsetterContext( const U8* pBasePtr, int nBaseLen);
+
+ bool initialCffRead();
+ void emitAsType1( class Type1Emitter&,
+ const sal_GlyphId* pGlyphIds, const U8* pEncoding,
+ sal_Int32* pGlyphWidths, int nGlyphCount, FontSubsetInfo& );
+
+private:
+ int convert2Type1Ops( CffLocal*, const U8* pType2Ops, int nType2Len, U8* pType1Ops);
+ void convertOneTypeOp();
+ void convertOneTypeEsc();
+ void callType2Subr( bool bGlobal, int nSubrNumber);
+ sal_Int32 getReadOfs() const { return static_cast<sal_Int32>(mpReadPtr - mpBasePtr);}
+
+ const U8* mpBasePtr;
+ const U8* mpBaseEnd;
+
+ const U8* mpReadPtr;
+ const U8* mpReadEnd;
+
+ U8* mpWritePtr;
+ bool mbNeedClose;
+ bool mbIgnoreHints;
+ sal_Int32 mnCntrMask;
+
+ int seekIndexData( int nIndexBase, int nDataIndex);
+ void seekIndexEnd( int nIndexBase);
+
+ CffLocal maCffLocal[256];
+ CffLocal* mpCffLocal;
+
+ void readDictOp();
+ RealType readRealVal();
+ const char* getString( int nStringID);
+ int getFDSelect( int nGlyphIndex) const;
+ int getGlyphSID( int nGlyphIndex) const;
+ const char* getGlyphName( int nGlyphIndex);
+
+ 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;
+};
+
+}
+
+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)
+{
+// 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;
+
+ int nInt = static_cast<int>(aVal);
+ 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;
+}
+
+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( 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;
+}
+
+static const int MAX_T1OPS_SIZE = 81920; // TODO: use dynamic value
+
+int CffSubsetterContext::convert2Type1Ops( CffLocal* pCffLocal, const U8* const pT2Ops, int nT2Len, U8* const pT1Ops)
+{
+ mpCffLocal = pCffLocal;
+
+ // prepare the charstring conversion
+ mpWritePtr = pT1Ops;
+ U8 aType1Ops[ MAX_T1OPS_SIZE];
+ if( !pT1Ops)
+ mpWritePtr = aType1Ops;
+ *const_cast<U8**>(&pT1Ops) = mpWritePtr;
+
+ // prepend random seed for T1crypt
+ *(mpWritePtr++) = 0x48;
+ *(mpWritePtr++) = 0x44;
+ *(mpWritePtr++) = 0x55;
+ *(mpWritePtr++) = ' ';
+
+ // convert the Type2 charstring to Type1
+ mpReadPtr = pT2Ops;
+ mpReadEnd = pT2Ops + nT2Len;
+ // prepend "hsbw" or "sbw"
+ // TODO: only emit hsbw when charwidth is known
+ writeType1Val(0); // TODO: aSubsetterContext.getLeftSideBearing();
+ U8* pCharWidthPtr=mpWritePtr; // need to overwrite that later
+ // pad out 5 bytes for the char width with default val 1000 (to be
+ // filled with the actual value below)
+ *(mpWritePtr++) = 255;
+ *(mpWritePtr++) = static_cast<U8>(0);
+ *(mpWritePtr++) = static_cast<U8>(0);
+ *(mpWritePtr++) = static_cast<U8>(250);
+ *(mpWritePtr++) = static_cast<U8>(124);
+ writeTypeOp(TYPE1OP::HSBW);
+ mbNeedClose = false;
+ mbIgnoreHints = false;
+ mnHintSize=mnHorzHintSize=mnStackIdx=0; maCharWidth=-1;//#######
+ mnCntrMask = 0;
+ while( mpReadPtr < mpReadEnd)
+ convertOneTypeOp();
+ if( maCharWidth != -1 )
+ {
+ // overwrite earlier charWidth value, which we only now have
+ // parsed out of mpReadPtr buffer (by way of
+ // convertOneTypeOp()s above)
+ const int nInt = static_cast<int>(maCharWidth);
+ *(pCharWidthPtr++) = 255;
+ *(pCharWidthPtr++) = static_cast<U8>(nInt >> 24);
+ *(pCharWidthPtr++) = static_cast<U8>(nInt >> 16);
+ *(pCharWidthPtr++) = static_cast<U8>(nInt >> 8);
+ *(pCharWidthPtr++) = static_cast<U8>(nInt);
+ }
+
+ const int nType1Len = mpWritePtr - pT1Ops;
+
+ // encrypt the Type1 charstring
+ unsigned nRDCryptR = 4330; // TODO: mnRDCryptSeed;
+ for( U8* p = pT1Ops; p < mpWritePtr; ++p) {
+ *p ^= (nRDCryptR >> 8);
+ nRDCryptR = (*p + nRDCryptR) * 52845 + 22719;
+ }
+
+ return nType1Len;
+}
+
+RealType CffSubsetterContext::readRealVal()
+{
+ // TODO: more thorough number validity test
+ bool bComma = false;
+ int nExpVal = 0;
+ int nExpSign = 0;
+ S64 nNumber = 0;
+ RealType fReal = +1.0;
+ for(;;){
+ const U8 c = *(mpReadPtr++); // read nibbles
+ // parse high nibble
+ const U8 nH = c >> 4U;
+ if( nH <= 9) {
+ nNumber = nNumber * 10 + nH;
+ --nExpVal;
+ } else if( nH == 10) { // comma
+ nExpVal = 0;
+ bComma = true;
+ } else if( nH == 11) { // +exp
+ fReal *= nNumber;
+ nExpSign = +1;
+ nNumber = 0;
+ } else if( nH == 12) { // -exp
+ fReal *= nNumber;
+ nExpSign = -1;
+ nNumber = 0;
+ } else if( nH == 13) { // reserved
+ // TODO: ignore or error?
+ } else if( nH == 14) // minus
+ fReal = -fReal;
+ else if( nH == 15) // end
+ break;
+ // parse low nibble
+ const U8 nL = c & 0x0F;
+ if( nL <= 9) {
+ nNumber = nNumber * 10 + nL;
+ --nExpVal;
+ } else if( nL == 10) { // comma
+ nExpVal = 0;
+ bComma = true;
+ } else if( nL == 11) { // +exp
+ fReal *= nNumber;
+ nNumber = 0;
+ nExpSign = +1;
+ } else if( nL == 12) { // -exp
+ fReal *= nNumber;
+ nNumber = 0;
+ nExpSign = -1;
+ } else if( nL == 13) { // reserved
+ // TODO: ignore or error?
+ } else if( nL == 14) // minus
+ fReal = -fReal;
+ else if( nL == 15) // end
+ break;
+ }
+
+ // merge exponents
+ if( !bComma)
+ nExpVal = 0;
+ if( !nExpSign) { fReal *= nNumber;}
+ else if( nExpSign > 0) { nExpVal += static_cast<int>(nNumber);}
+ else if( nExpSign < 0) { nExpVal -= static_cast<int>(nNumber);}
+
+ // apply exponents
+ if( !nExpVal) { /*nothing to apply*/}
+ else if( nExpVal > 0) { while( --nExpVal >= 0) fReal *= 10.0;}
+ else if( nExpVal < 0) { while( ++nExpVal <= 0) fReal /= 10.0;}
+ return fReal;
+}
+
+// prepare to access an element inside a CFF/CID index table
+int CffSubsetterContext::seekIndexData( int nIndexBase, int nDataIndex)
+{
+ assert( (nIndexBase > 0) && (mpBasePtr + nIndexBase + 3 <= mpBaseEnd));
+ if( nDataIndex < 0)
+ return -1;
+ mpReadPtr = mpBasePtr + nIndexBase;
+ const int nDataCount = (mpReadPtr[0]<<8) + mpReadPtr[1];
+ if( nDataIndex >= nDataCount)
+ return -1;
+ const int nDataOfsSz = mpReadPtr[2];
+ mpReadPtr += 3 + (nDataOfsSz * nDataIndex);
+ int nOfs1 = 0;
+ switch( nDataOfsSz) {
+ default: SAL_WARN("vcl.fonts", "\tINVALID nDataOfsSz=" << nDataOfsSz); return -1;
+ case 1: nOfs1 = mpReadPtr[0]; break;
+ case 2: nOfs1 = (mpReadPtr[0]<<8) + mpReadPtr[1]; break;
+ case 3: nOfs1 = (mpReadPtr[0]<<16) + (mpReadPtr[1]<<8) + mpReadPtr[2]; break;
+ case 4: nOfs1 = (mpReadPtr[0]<<24) + (mpReadPtr[1]<<16) + (mpReadPtr[2]<<8) + mpReadPtr[3]; break;
+ }
+ mpReadPtr += nDataOfsSz;
+
+ int nOfs2 = 0;
+ switch( nDataOfsSz) {
+ case 1: nOfs2 = mpReadPtr[0]; break;
+ case 2: nOfs2 = (mpReadPtr[0]<<8) + mpReadPtr[1]; break;
+ case 3: nOfs2 = (mpReadPtr[0]<<16) + (mpReadPtr[1]<<8) + mpReadPtr[2]; break;
+ case 4: nOfs2 = (mpReadPtr[0]<<24) + (mpReadPtr[1]<<16) + (mpReadPtr[2]<<8) + mpReadPtr[3]; break;
+ }
+
+ mpReadPtr = mpBasePtr + (nIndexBase + 2) + nDataOfsSz * (nDataCount + 1) + nOfs1;
+ mpReadEnd = mpReadPtr + (nOfs2 - nOfs1);
+ assert( nOfs1 >= 0);
+ assert( nOfs2 >= nOfs1);
+ assert( mpReadPtr <= mpBaseEnd);
+ assert( mpReadEnd <= mpBaseEnd);
+ return (nOfs2 - nOfs1);
+}
+
+// skip over a CFF/CID index table
+void CffSubsetterContext::seekIndexEnd( int nIndexBase)
+{
+ assert( (nIndexBase > 0) && (mpBasePtr + nIndexBase + 3 <= mpBaseEnd));
+ mpReadPtr = mpBasePtr + nIndexBase;
+ const int nDataCount = (mpReadPtr[0]<<8) + mpReadPtr[1];
+ const int nDataOfsSz = mpReadPtr[2];
+ mpReadPtr += 3 + nDataOfsSz * nDataCount;
+ assert( mpReadPtr <= mpBaseEnd);
+ int nEndOfs = 0;
+ switch( nDataOfsSz) {
+ default: SAL_WARN("vcl.fonts", "\tINVALID nDataOfsSz=" << nDataOfsSz); return;
+ case 1: nEndOfs = mpReadPtr[0]; break;
+ case 2: nEndOfs = (mpReadPtr[0]<<8) + mpReadPtr[1]; break;
+ case 3: nEndOfs = (mpReadPtr[0]<<16) + (mpReadPtr[1]<<8) + mpReadPtr[2];break;
+ case 4: nEndOfs = (mpReadPtr[0]<<24) + (mpReadPtr[1]<<16) + (mpReadPtr[2]<<8) + mpReadPtr[3]; break;
+ }
+ mpReadPtr += nDataOfsSz;
+ mpReadPtr += nEndOfs - 1;
+ mpReadEnd = mpBaseEnd;
+ assert( nEndOfs >= 0);
+ assert( mpReadEnd <= mpBaseEnd);
+}
+
+// initialize FONTDICT specific values
+CffLocal::CffLocal()
+: mnPrivDictBase( 0)
+, mnPrivDictSize( 0)
+, mnLocalSubrOffs( 0)
+, mnLocalSubrBase( 0)
+, mnLocalSubrBias( 0)
+, maNominalWidth( 0)
+, maDefaultWidth( 0)
+, maStemStdHW( 0)
+, maStemStdVW( 0)
+, mfBlueScale( 0.0)
+, mfBlueShift( 0.0)
+, mfBlueFuzz( 0.0)
+, mfExpFactor( 0.0)
+, mnLangGroup( 0)
+, mbForceBold( false)
+{
+}
+
+CffGlobal::CffGlobal()
+: mnNameIdxBase( 0)
+, mnStringIdxBase( 0)
+, mbCIDFont( false)
+, mnCharStrBase( 0)
+, mnCharStrCount( 0)
+, mnCharsetBase( 0)
+, mnGlobalSubrBase( 0)
+, mnGlobalSubrCount( 0)
+, mnGlobalSubrBias( 0)
+, mnFDSelectBase( 0)
+, mnFontDictBase( 0)
+, mnFDAryCount( 1)
+, mnFontNameSID( 0)
+, mnFullNameSID( 0)
+{
+}
+
+bool CffSubsetterContext::initialCffRead()
+{
+ // get the CFFHeader
+ mpReadPtr = mpBasePtr;
+ const U8 nVerMajor = *(mpReadPtr++);
+ const U8 nVerMinor = *(mpReadPtr++);
+ const U8 nHeaderSize = *(mpReadPtr++);
+ const U8 nOffsetSize = *(mpReadPtr++);
+ // TODO: is the version number useful for anything else?
+ assert( (nVerMajor == 1) && (nVerMinor == 0));
+ (void)(nVerMajor + nVerMinor + nOffsetSize); // avoid compiler warnings
+
+ // prepare access to the NameIndex
+ mnNameIdxBase = nHeaderSize;
+ mpReadPtr = mpBasePtr + nHeaderSize;
+ seekIndexEnd( mnNameIdxBase);
+
+ // get the TopDict index
+ const sal_Int32 nTopDictBase = getReadOfs();
+ const int nTopDictCount = (mpReadPtr[0]<<8) + mpReadPtr[1];
+ if( nTopDictCount) {
+ for( int i = 0; i < nTopDictCount; ++i) {
+ seekIndexData( nTopDictBase, i);
+ while( mpReadPtr < mpReadEnd)
+ readDictOp();
+ assert( mpReadPtr == mpReadEnd);
+ }
+ }
+
+ // prepare access to the String index
+ mnStringIdxBase = getReadOfs();
+ seekIndexEnd( mnStringIdxBase);
+
+ // prepare access to the GlobalSubr index
+ mnGlobalSubrBase = getReadOfs();
+ mnGlobalSubrCount = (mpReadPtr[0]<<8) + mpReadPtr[1];
+ mnGlobalSubrBias = (mnGlobalSubrCount<1240)?107:(mnGlobalSubrCount<33900)?1131:32768;
+ // skip past the last GlobalSubr entry
+// seekIndexEnd( mnGlobalSubrBase);
+
+ // get/skip the Encodings (we got mnEncodingBase from TOPDICT)
+// seekEncodingsEnd( mnEncodingBase);
+ // get/skip the Charsets (we got mnCharsetBase from TOPDICT)
+// seekCharsetsEnd( mnCharStrBase);
+ // get/skip FDSelect (CID only) data
+
+ // prepare access to the CharStrings index (we got the base from TOPDICT)
+ mpReadPtr = mpBasePtr + mnCharStrBase;
+ mnCharStrCount = (mpReadPtr[0]<<8) + mpReadPtr[1];
+// seekIndexEnd( mnCharStrBase);
+
+ // read the FDArray index (CID only)
+ if( mbCIDFont) {
+// assert( mnFontDictBase == tellRel());
+ mpReadPtr = mpBasePtr + mnFontDictBase;
+ mnFDAryCount = (mpReadPtr[0]<<8) + mpReadPtr[1];
+ if (o3tl::make_unsigned(mnFDAryCount) >= SAL_N_ELEMENTS(maCffLocal))
+ {
+ SAL_INFO("vcl.fonts", "CffSubsetterContext: too many CFF in font");
+ return false;
+ }
+
+ // read FDArray details to get access to the PRIVDICTs
+ for( int i = 0; i < mnFDAryCount; ++i) {
+ mpCffLocal = &maCffLocal[i];
+ seekIndexData( mnFontDictBase, i);
+ while( mpReadPtr < mpReadEnd)
+ readDictOp();
+ assert( mpReadPtr == mpReadEnd);
+ }
+ }
+
+ for( int i = 0; i < mnFDAryCount; ++i) {
+ mpCffLocal = &maCffLocal[i];
+
+ // get the PrivateDict index
+ // (we got mnPrivDictSize and mnPrivDictBase from TOPDICT or FDArray)
+ if( mpCffLocal->mnPrivDictSize != 0) {
+ assert( mpCffLocal->mnPrivDictSize > 0);
+ // get the PrivDict data
+ mpReadPtr = mpBasePtr + mpCffLocal->mnPrivDictBase;
+ mpReadEnd = mpReadPtr + mpCffLocal->mnPrivDictSize;
+ assert( mpReadEnd <= mpBaseEnd);
+ // read PrivDict details
+ while( mpReadPtr < mpReadEnd)
+ readDictOp();
+ }
+
+ // prepare access to the LocalSubrs (we got mnLocalSubrOffs from PRIVDICT)
+ if( mpCffLocal->mnLocalSubrOffs) {
+ // read LocalSubrs summary
+ mpCffLocal->mnLocalSubrBase = mpCffLocal->mnPrivDictBase + mpCffLocal->mnLocalSubrOffs;
+ mpReadPtr = mpBasePtr + mpCffLocal->mnLocalSubrBase;
+ const int nSubrCount = (mpReadPtr[0] << 8) + mpReadPtr[1];
+ mpCffLocal->mnLocalSubrBias = (nSubrCount<1240)?107:(nSubrCount<33900)?1131:32768;
+// seekIndexEnd( mpCffLocal->mnLocalSubrBase);
+ }
+ }
+
+ // ignore the Notices info
+
+ return true;
+}
+
+// get a cstring from a StringID
+const char* CffSubsetterContext::getString( int nStringID)
+{
+ // get a standard string if possible
+ const static int nStdStrings = SAL_N_ELEMENTS(pStringIds);
+ if( (nStringID >= 0) && (nStringID < nStdStrings))
+ return pStringIds[ nStringID];
+
+ // else get the string from the StringIndex table
+ const U8* pReadPtr = mpReadPtr;
+ const U8* pReadEnd = mpReadEnd;
+ nStringID -= nStdStrings;
+ int nLen = seekIndexData( mnStringIdxBase, nStringID);
+ // assert( nLen >= 0);
+ // TODO: just return the undecorated name
+ // TODO: get rid of static char buffer
+ static char aNameBuf[ 2560];
+ if( nLen < 0) {
+ sprintf( aNameBuf, "name[%d].notfound!", nStringID);
+ } else {
+ const int nMaxLen = sizeof(aNameBuf) - 1;
+ if( nLen >= nMaxLen)
+ nLen = nMaxLen;
+ for( int i = 0; i < nLen; ++i)
+ aNameBuf[i] = *(mpReadPtr++);
+ aNameBuf[ nLen] = '\0';
+ }
+ mpReadPtr = pReadPtr;
+ mpReadEnd = pReadEnd;
+ return aNameBuf;
+}
+
+// access a CID's FDSelect table
+int CffSubsetterContext::getFDSelect( int nGlyphIndex) const
+{
+ assert( nGlyphIndex >= 0);
+ assert( nGlyphIndex < mnCharStrCount);
+ if( !mbCIDFont)
+ return 0;
+
+ const U8* pReadPtr = mpBasePtr + mnFDSelectBase;
+ const U8 nFDSelFormat = *(pReadPtr++);
+ switch( nFDSelFormat) {
+ case 0: { // FDSELECT format 0
+ pReadPtr += nGlyphIndex;
+ const U8 nFDIdx = *(pReadPtr++);
+ return nFDIdx;
+ } //break;
+ case 3: { // FDSELECT format 3
+ const U16 nRangeCount = (pReadPtr[0]<<8) + pReadPtr[1];
+ assert( nRangeCount > 0);
+ assert( nRangeCount <= mnCharStrCount);
+ U16 nPrev = (pReadPtr[2]<<8) + pReadPtr[3];
+ assert( nPrev == 0);
+ (void)nPrev;
+ pReadPtr += 4;
+ // TODO? binary search
+ for( int i = 0; i < nRangeCount; ++i) {
+ const U8 nFDIdx = pReadPtr[0];
+ const U16 nNext = (pReadPtr[1]<<8) + pReadPtr[2];
+ assert( nPrev < nNext);
+ if( nGlyphIndex < nNext)
+ return nFDIdx;
+ pReadPtr += 3;
+ nPrev = nNext;
+ }
+ } break;
+ default: // invalid FDselect format
+ SAL_WARN("vcl.fonts", "invalid CFF.FdselType=" << nFDSelFormat);
+ break;
+ }
+
+ assert( false);
+ return -1;
+}
+
+int CffSubsetterContext::getGlyphSID( int nGlyphIndex) const
+{
+ if( nGlyphIndex == 0)
+ return 0; // ".notdef"
+ assert( nGlyphIndex >= 0);
+ assert( nGlyphIndex < mnCharStrCount);
+ if( (nGlyphIndex < 0) || (nGlyphIndex >= mnCharStrCount))
+ return -1;
+
+ // get the SID/CID from the Charset table
+ const U8* pReadPtr = mpBasePtr + mnCharsetBase;
+ const U8 nCSetFormat = *(pReadPtr++);
+ int nGlyphsToSkip = nGlyphIndex - 1;
+ switch( nCSetFormat) {
+ case 0: // charset format 0
+ pReadPtr += 2 * nGlyphsToSkip;
+ nGlyphsToSkip = 0;
+ break;
+ case 1: // charset format 1
+ while( nGlyphsToSkip >= 0) {
+ const int nLeft = pReadPtr[2];
+ if( nGlyphsToSkip <= nLeft)
+ break;
+ nGlyphsToSkip -= nLeft + 1;
+ pReadPtr += 3;
+ }
+ break;
+ case 2: // charset format 2
+ while( nGlyphsToSkip >= 0) {
+ const int nLeft = (pReadPtr[2]<<8) + pReadPtr[3];
+ if( nGlyphsToSkip <= nLeft)
+ break;
+ nGlyphsToSkip -= nLeft + 1;
+ pReadPtr += 4;
+ }
+ break;
+ default:
+ SAL_WARN("vcl.fonts", "ILLEGAL CFF-Charset format " << nCSetFormat);
+ return -2;
+ }
+
+ int nSID = (pReadPtr[0]<<8) + pReadPtr[1];
+ nSID += nGlyphsToSkip;
+ // NOTE: for CID-fonts the resulting SID is interpreted as CID
+ return nSID;
+}
+
+// NOTE: the result becomes invalid with the next call to this method
+const char* CffSubsetterContext::getGlyphName( int nGlyphIndex)
+{
+ // the first glyph is always the .notdef glyph
+ const char* pGlyphName = ".notdef";
+ if( nGlyphIndex == 0)
+ return pGlyphName;
+
+ // prepare a result buffer
+ // TODO: get rid of static buffer
+ static char aDefaultGlyphName[64];
+ pGlyphName = aDefaultGlyphName;
+
+ // get the glyph specific name
+ const int nSID = getGlyphSID( nGlyphIndex);
+ if( nSID < 0) // default glyph name
+ sprintf( aDefaultGlyphName, "gly%03d", nGlyphIndex);
+ else if( mbCIDFont) // default glyph name in CIDs
+ sprintf( aDefaultGlyphName, "cid%03d", nSID);
+ else { // glyph name from string table
+ const char* pSidName = getString( nSID);
+ // check validity of glyph name
+ if( pSidName) {
+ const char* p = pSidName;
+ while( (*p >= '0') && (*p <= 'z')) ++p;
+ if( (p >= pSidName+1) && (*p == '\0'))
+ pGlyphName = pSidName;
+ }
+ // if needed invent a fallback name
+ if( pGlyphName != pSidName)
+ sprintf( aDefaultGlyphName, "bad%03d", nSID);
+ }
+
+ return pGlyphName;
+}
+
+namespace {
+
+class Type1Emitter
+{
+public:
+ explicit Type1Emitter( FILE* pOutFile, bool bPfbSubset);
+ ~Type1Emitter();
+ void setSubsetName( const char* );
+
+ size_t emitRawData( const char* pData, size_t nLength) const;
+ void emitAllRaw();
+ void emitAllHex();
+ void emitAllCrypted();
+ int tellPos() const;
+ void updateLen( int nTellPos, size_t nLength);
+ void emitValVector( const char* pLineHead, const char* pLineTail, const std::vector<ValType>&);
+private:
+ FILE* mpFileOut;
+ char maBuffer[MAX_T1OPS_SIZE]; // TODO: dynamic allocation
+ unsigned mnEECryptR;
+public:
+ char* mpPtr;
+
+ char maSubsetName[256];
+ bool mbPfbSubset;
+ int mnHexLineCol;
+};
+
+}
+
+Type1Emitter::Type1Emitter( FILE* pOutFile, bool bPfbSubset)
+: mpFileOut( pOutFile)
+, maBuffer{}
+, mnEECryptR( 55665) // default eexec seed, TODO: mnEECryptSeed
+, mpPtr( maBuffer)
+, mbPfbSubset( bPfbSubset)
+, mnHexLineCol( 0)
+{
+ maSubsetName[0] = '\0';
+}
+
+Type1Emitter::~Type1Emitter()
+{
+ if( !mpFileOut)
+ return;
+ mpFileOut = nullptr;
+}
+
+void Type1Emitter::setSubsetName( const char* pSubsetName)
+{
+ maSubsetName[0] = '\0';
+ if( pSubsetName)
+ strncpy( maSubsetName, pSubsetName, sizeof(maSubsetName) - 1);
+ maSubsetName[sizeof(maSubsetName)-1] = '\0';
+}
+
+int Type1Emitter::tellPos() const
+{
+ int nTellPos = ftell( mpFileOut);
+ return nTellPos;
+}
+
+void Type1Emitter::updateLen( int nTellPos, size_t nLength)
+{
+ // update PFB segment header length
+ U8 cData[4];
+ cData[0] = static_cast<U8>(nLength >> 0);
+ cData[1] = static_cast<U8>(nLength >> 8);
+ cData[2] = static_cast<U8>(nLength >> 16);
+ cData[3] = static_cast<U8>(nLength >> 24);
+ const long nCurrPos = ftell(mpFileOut);
+ if (nCurrPos < 0)
+ return;
+ if (fseek( mpFileOut, nTellPos, SEEK_SET) != 0)
+ return;
+ fwrite(cData, 1, sizeof(cData), mpFileOut);
+ (void)fseek(mpFileOut, nCurrPos, SEEK_SET);
+}
+
+inline size_t Type1Emitter::emitRawData(const char* pData, size_t nLength) const
+{
+ return fwrite( pData, 1, nLength, mpFileOut);
+}
+
+inline void Type1Emitter::emitAllRaw()
+{
+ // writeout raw data
+ assert( (mpPtr - maBuffer) < int(sizeof(maBuffer)));
+ emitRawData( maBuffer, mpPtr - maBuffer);
+ // reset the raw buffer
+ mpPtr = maBuffer;
+}
+
+inline void Type1Emitter::emitAllHex()
+{
+ assert( (mpPtr - maBuffer) < int(sizeof(maBuffer)));
+ for( const char* p = maBuffer; p < mpPtr;) {
+ // convert binary chunk to hex
+ char aHexBuf[0x4000];
+ char* pOut = aHexBuf;
+ while( (p < mpPtr) && (pOut < aHexBuf+sizeof(aHexBuf)-4)) {
+ // convert each byte to hex
+ char cNibble = (static_cast<unsigned char>(*p) >> 4) & 0x0F;
+ cNibble += (cNibble < 10) ? '0' : 'A'-10;
+ *(pOut++) = cNibble;
+ cNibble = *(p++) & 0x0F;
+ cNibble += (cNibble < 10) ? '0' : 'A'-10;
+ *(pOut++) = cNibble;
+ // limit the line length
+ if( (++mnHexLineCol & 0x3F) == 0)
+ *(pOut++) = '\n';
+ }
+ // writeout hex-converted chunk
+ emitRawData( aHexBuf, pOut-aHexBuf);
+ }
+ // reset the raw buffer
+ mpPtr = maBuffer;
+}
+
+void Type1Emitter::emitAllCrypted()
+{
+ // apply t1crypt
+ for( char* p = maBuffer; p < mpPtr; ++p) {
+ *p ^= (mnEECryptR >> 8);
+ mnEECryptR = (*reinterpret_cast<U8*>(p) + mnEECryptR) * 52845 + 22719;
+ }
+
+ // emit the t1crypt result
+ if( mbPfbSubset)
+ emitAllRaw();
+ else
+ emitAllHex();
+}
+
+// #i110387# quick-and-dirty double->ascii conversion
+// needed because sprintf/ecvt/etc. alone are too localized (LC_NUMERIC)
+// also strip off trailing zeros in fraction while we are at it
+static int dbl2str( char* pOut, double fVal)
+{
+ const int nLen = psp::getValueOfDouble( pOut, fVal, 6);
+ return nLen;
+}
+
+void Type1Emitter::emitValVector( const char* pLineHead, const char* pLineTail,
+ const std::vector<ValType>& rVector)
+{
+ // ignore empty vectors
+ if( rVector.empty())
+ return;
+
+ // emit the line head
+ mpPtr += sprintf( mpPtr, "%s", pLineHead);
+ // emit the vector values
+ std::vector<ValType>::value_type aVal = 0;
+ for( std::vector<ValType>::const_iterator it = rVector.begin();;) {
+ aVal = *it;
+ if( ++it == rVector.end() )
+ break;
+ mpPtr += dbl2str( mpPtr, aVal);
+ *(mpPtr++) = ' ';
+ }
+ // emit the last value
+ mpPtr += dbl2str( mpPtr, aVal);
+ // emit the line tail
+ mpPtr += sprintf( mpPtr, "%s", pLineTail);
+}
+
+void CffSubsetterContext::emitAsType1( Type1Emitter& rEmitter,
+ const sal_GlyphId* pReqGlyphIds, const U8* pReqEncoding,
+ sal_Int32* pGlyphWidths, int nGlyphCount, FontSubsetInfo& rFSInfo)
+{
+ // prepare some fontdirectory details
+ static const int nUniqueIdBase = 4100000; // using private-interchange UniqueIds
+ static int nUniqueId = nUniqueIdBase;
+ ++nUniqueId;
+
+ char* pFontName = rEmitter.maSubsetName;
+ if( !*pFontName ) {
+ if( mnFontNameSID) {
+ // get the fontname directly if available
+ strncpy( pFontName, getString( mnFontNameSID), sizeof(rEmitter.maSubsetName) - 1);
+ pFontName[sizeof(rEmitter.maSubsetName) - 1] = 0;
+ } else if( mnFullNameSID) {
+ // approximate fontname as fullname-whitespace
+ const char* pI = getString( mnFullNameSID);
+ char* pO = pFontName;
+ const char* pLimit = pFontName + sizeof(rEmitter.maSubsetName) - 1;
+ while( pO < pLimit) {
+ const char c = *(pI++);
+ if( c != ' ')
+ *(pO++) = c;
+ if( !c)
+ break;
+ }
+ *pO = '\0';
+ } else {
+ // fallback name of last resort
+ strncpy( pFontName, "DummyName", sizeof(rEmitter.maSubsetName));
+ }
+ }
+ const char* pFullName = pFontName;
+ const char* pFamilyName = pFontName;
+
+ char*& pOut = rEmitter.mpPtr; // convenience reference, TODO: cleanup
+
+ // create a PFB+Type1 header
+ if( rEmitter.mbPfbSubset ) {
+ static const char aPfbHeader[] = "\x80\x01\x00\x00\x00\x00";
+ rEmitter.emitRawData( aPfbHeader, sizeof(aPfbHeader)-1);
+ }
+
+ pOut += sprintf( pOut, "%%!FontType1-1.0: %s 001.003\n", rEmitter.maSubsetName);
+ // emit TOPDICT
+ pOut += sprintf( pOut,
+ "11 dict begin\n" // TODO: dynamic entry count for TOPDICT
+ "/FontType 1 def\n"
+ "/PaintType 0 def\n");
+ pOut += sprintf( pOut, "/FontName /%s def\n", rEmitter.maSubsetName);
+ pOut += sprintf( pOut, "/UniqueID %d def\n", nUniqueId);
+ // emit FontMatrix
+ if( maFontMatrix.size() == 6)
+ rEmitter.emitValVector( "/FontMatrix [", "]readonly def\n", maFontMatrix);
+ else // emit default FontMatrix if needed
+ pOut += sprintf( pOut, "/FontMatrix [0.001 0 0 0.001 0 0]readonly def\n");
+ // emit FontBBox
+ if( maFontBBox.size() == 4)
+ rEmitter.emitValVector( "/FontBBox {", "}readonly def\n", maFontBBox);
+ else // emit default FontBBox if needed
+ pOut += sprintf( pOut, "/FontBBox {0 0 999 999}readonly def\n");
+ // emit FONTINFO into TOPDICT
+ pOut += sprintf( pOut,
+ "/FontInfo 2 dict dup begin\n" // TODO: check fontinfo entry count
+ " /FullName (%s) readonly def\n"
+ " /FamilyName (%s) readonly def\n"
+ "end readonly def\n",
+ pFullName, pFamilyName);
+
+ pOut += sprintf( pOut,
+ "/Encoding 256 array\n"
+ "0 1 255 {1 index exch /.notdef put} for\n");
+ for( int i = 1; (i < nGlyphCount) && (i < 256); ++i) {
+ const char* pGlyphName = getGlyphName( pReqGlyphIds[i]);
+ pOut += sprintf( pOut, "dup %d /%s put\n", pReqEncoding[i], pGlyphName);
+ }
+ pOut += sprintf( pOut, "readonly def\n");
+ pOut += sprintf( pOut,
+ // TODO: more topdict entries
+ "currentdict end\n"
+ "currentfile eexec\n");
+
+ // emit PFB header
+ rEmitter.emitAllRaw();
+ if( rEmitter.mbPfbSubset) {
+ // update PFB header segment
+ const int nPfbHeaderLen = rEmitter.tellPos() - 6;
+ rEmitter.updateLen( 2, nPfbHeaderLen);
+
+ // prepare start of eexec segment
+ rEmitter.emitRawData( "\x80\x02\x00\x00\x00\x00", 6); // segment start
+ }
+ const int nEExecSegTell = rEmitter.tellPos();
+
+ // which always starts with a privdict
+ // count the privdict entries
+ int nPrivEntryCount = 9;
+ // emit blue hints only if non-default values
+ nPrivEntryCount += int(!mpCffLocal->maOtherBlues.empty());
+ nPrivEntryCount += int(!mpCffLocal->maFamilyBlues.empty());
+ nPrivEntryCount += int(!mpCffLocal->maFamilyOtherBlues.empty());
+ nPrivEntryCount += int(mpCffLocal->mfBlueScale != 0.0);
+ nPrivEntryCount += int(mpCffLocal->mfBlueShift != 0.0);
+ nPrivEntryCount += int(mpCffLocal->mfBlueFuzz != 0.0);
+ // emit stem hints only if non-default values
+ nPrivEntryCount += int(mpCffLocal->maStemStdHW != 0);
+ nPrivEntryCount += int(mpCffLocal->maStemStdVW != 0);
+ nPrivEntryCount += int(!mpCffLocal->maStemSnapH.empty());
+ nPrivEntryCount += int(!mpCffLocal->maStemSnapV.empty());
+ // emit other hints only if non-default values
+ nPrivEntryCount += int(mpCffLocal->mfExpFactor != 0.0);
+ nPrivEntryCount += int(mpCffLocal->mnLangGroup != 0);
+ nPrivEntryCount += int(mpCffLocal->mnLangGroup == 1);
+ nPrivEntryCount += int(mpCffLocal->mbForceBold);
+ // emit the privdict header
+ pOut += sprintf( pOut,
+ "\110\104\125 "
+ "dup\n/Private %d dict dup begin\n"
+ "/RD{string currentfile exch readstring pop}executeonly def\n"
+ "/ND{noaccess def}executeonly def\n"
+ "/NP{noaccess put}executeonly def\n"
+ "/MinFeature{16 16}ND\n"
+ "/password 5839 def\n", // TODO: mnRDCryptSeed?
+ nPrivEntryCount);
+
+ // emit blue hint related privdict entries
+ if( !mpCffLocal->maBlueValues.empty())
+ rEmitter.emitValVector( "/BlueValues [", "]ND\n", mpCffLocal->maBlueValues);
+ else
+ pOut += sprintf( pOut, "/BlueValues []ND\n"); // default to empty BlueValues
+ rEmitter.emitValVector( "/OtherBlues [", "]ND\n", mpCffLocal->maOtherBlues);
+ rEmitter.emitValVector( "/FamilyBlues [", "]ND\n", mpCffLocal->maFamilyBlues);
+ rEmitter.emitValVector( "/FamilyOtherBlues [", "]ND\n", mpCffLocal->maFamilyOtherBlues);
+
+ if( mpCffLocal->mfBlueScale) {
+ pOut += sprintf( pOut, "/BlueScale ");
+ pOut += dbl2str( pOut, mpCffLocal->mfBlueScale);
+ pOut += sprintf( pOut, " def\n");
+ }
+ if( mpCffLocal->mfBlueShift) { // default BlueShift==7
+ pOut += sprintf( pOut, "/BlueShift ");
+ pOut += dbl2str( pOut, mpCffLocal->mfBlueShift);
+ pOut += sprintf( pOut, " def\n");
+ }
+ if( mpCffLocal->mfBlueFuzz) { // default BlueFuzz==1
+ pOut += sprintf( pOut, "/BlueFuzz ");
+ pOut += dbl2str( pOut, mpCffLocal->mfBlueFuzz);
+ pOut += sprintf( pOut, " def\n");
+ }
+
+ // emit stem hint related privdict entries
+ if( mpCffLocal->maStemStdHW) {
+ pOut += sprintf( pOut, "/StdHW [");
+ pOut += dbl2str( pOut, mpCffLocal->maStemStdHW);
+ pOut += sprintf( pOut, "] def\n");
+ }
+ if( mpCffLocal->maStemStdVW) {
+ pOut += sprintf( pOut, "/StdVW [");
+ pOut += dbl2str( pOut, mpCffLocal->maStemStdVW);
+ pOut += sprintf( pOut, "] def\n");
+ }
+ rEmitter.emitValVector( "/StemSnapH [", "]ND\n", mpCffLocal->maStemSnapH);
+ rEmitter.emitValVector( "/StemSnapV [", "]ND\n", mpCffLocal->maStemSnapV);
+
+ // emit other hints
+ if( mpCffLocal->mbForceBold)
+ pOut += sprintf( pOut, "/ForceBold true def\n");
+ if( mpCffLocal->mnLangGroup != 0)
+ pOut += sprintf( pOut, "/LanguageGroup %d def\n", mpCffLocal->mnLangGroup);
+ if( mpCffLocal->mnLangGroup == 1) // compatibility with ancient printers
+ pOut += sprintf( pOut, "/RndStemUp false def\n");
+ if( mpCffLocal->mfExpFactor) {
+ pOut += sprintf( pOut, "/ExpansionFactor ");
+ pOut += dbl2str( pOut, mpCffLocal->mfExpFactor);
+ pOut += sprintf( pOut, " def\n");
+ }
+
+ // emit remaining privdict entries
+ pOut += sprintf( pOut, "/UniqueID %d def\n", nUniqueId);
+ // TODO?: more privdict entries?
+
+ static const char aOtherSubrs[] =
+ "/OtherSubrs\n"
+ "% Dummy code for faking flex hints\n"
+ "[ {} {} {} {systemdict /internaldict known not {pop 3}\n"
+ "{1183615869 systemdict /internaldict get exec\n"
+ "dup /startlock known\n"
+ "{/startlock get exec}\n"
+ "{dup /strtlck known\n"
+ "{/strtlck get exec}\n"
+ "{pop 3}\nifelse}\nifelse}\nifelse\n} executeonly\n"
+ "] ND\n";
+ memcpy( pOut, aOtherSubrs, sizeof(aOtherSubrs)-1);
+ pOut += sizeof(aOtherSubrs)-1;
+
+ // emit used GlobalSubr charstrings
+ // these are the just the default subrs
+ // TODO: do we need them as the flex hints are resolved differently?
+ static const char aSubrs[] =
+ "/Subrs 5 array\n"
+ "dup 0 15 RD \x5F\x3D\x6B\xAC\x3C\xBD\x74\x3D\x3E\x17\xA0\x86\x58\x08\x85 NP\n"
+ "dup 1 9 RD \x5F\x3D\x6B\xD8\xA6\xB5\x68\xB6\xA2 NP\n"
+ "dup 2 9 RD \x5F\x3D\x6B\xAC\x39\x46\xB9\x43\xF9 NP\n"
+ "dup 3 5 RD \x5F\x3D\x6B\xAC\xB9 NP\n"
+ "dup 4 12 RD \x5F\x3D\x6B\xAC\x3E\x5D\x48\x54\x62\x76\x39\x03 NP\n"
+ "ND\n";
+ memcpy( pOut, aSubrs, sizeof(aSubrs)-1);
+ pOut += sizeof(aSubrs)-1;
+
+ // TODO: emit more GlobalSubr charstrings?
+ // TODO: emit used LocalSubr charstrings?
+
+ // emit the CharStrings for the requested glyphs
+ pOut += sprintf( pOut,
+ "2 index /CharStrings %d dict dup begin\n", nGlyphCount);
+ rEmitter.emitAllCrypted();
+ for( int i = 0; i < nGlyphCount; ++i) {
+ const int nCffGlyphId = pReqGlyphIds[i];
+ assert( (nCffGlyphId >= 0) && (nCffGlyphId < mnCharStrCount));
+ // 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);
+ U8 aType1Ops[ MAX_T1OPS_SIZE]; // TODO: dynamic allocation
+ const int nT1Len = convert2Type1Ops( mpCffLocal, mpReadPtr, nT2Len, aType1Ops);
+ // get the glyph name
+ const char* pGlyphName = getGlyphName( nCffGlyphId);
+ // emit the encrypted Type1op charstring
+ pOut += sprintf( pOut, "/%s %d RD ", pGlyphName, nT1Len);
+ memcpy( pOut, aType1Ops, nT1Len);
+ pOut += nT1Len;
+ pOut += sprintf( pOut, " ND\n");
+ rEmitter.emitAllCrypted();
+ // provide individual glyphwidths if requested
+ if( pGlyphWidths ) {
+ ValType aCharWidth = maCharWidth;
+ if( maFontMatrix.size() >= 4)
+ aCharWidth *= 1000.0F * maFontMatrix[0];
+ pGlyphWidths[i] = static_cast<sal_Int32>(aCharWidth);
+ }
+ }
+ pOut += sprintf( pOut, "end end\nreadonly put\nput\n");
+ pOut += sprintf( pOut, "dup/FontName get exch definefont pop\n");
+ pOut += sprintf( pOut, "mark currentfile closefile\n");
+ rEmitter.emitAllCrypted();
+
+ // mark stop of eexec encryption
+ if( rEmitter.mbPfbSubset) {
+ const int nEExecLen = rEmitter.tellPos() - nEExecSegTell;
+ rEmitter.updateLen( nEExecSegTell-4, nEExecLen);
+ }
+
+ // create PFB footer
+ static const char aPfxFooter[] = "\x80\x01\x14\x02\x00\x00\n" // TODO: check segment len
+ "0000000000000000000000000000000000000000000000000000000000000000\n"
+ "0000000000000000000000000000000000000000000000000000000000000000\n"
+ "0000000000000000000000000000000000000000000000000000000000000000\n"
+ "0000000000000000000000000000000000000000000000000000000000000000\n"
+ "0000000000000000000000000000000000000000000000000000000000000000\n"
+ "0000000000000000000000000000000000000000000000000000000000000000\n"
+ "0000000000000000000000000000000000000000000000000000000000000000\n"
+ "0000000000000000000000000000000000000000000000000000000000000000\n"
+ "cleartomark\n"
+ "\x80\x03";
+ if( rEmitter.mbPfbSubset)
+ rEmitter.emitRawData( aPfxFooter, sizeof(aPfxFooter)-1);
+ else
+ rEmitter.emitRawData( aPfxFooter+6, sizeof(aPfxFooter)-9);
+
+ // provide details to the subset requesters, TODO: move into own method?
+ // note: Top and Bottom are flipped between Type1 and VCL
+ // note: the rest of VCL expects the details below to be scaled like for an emUnits==1000 font
+ ValType fXFactor = 1.0;
+ ValType fYFactor = 1.0;
+ if( maFontMatrix.size() >= 4) {
+ fXFactor = 1000.0F * maFontMatrix[0];
+ fYFactor = 1000.0F * maFontMatrix[3];
+ }
+ rFSInfo.m_aFontBBox = tools::Rectangle( Point( static_cast<sal_Int32>(maFontBBox[0] * fXFactor),
+ static_cast<sal_Int32>(maFontBBox[1] * fYFactor) ),
+ Point( static_cast<sal_Int32>(maFontBBox[2] * fXFactor),
+ static_cast<sal_Int32>(maFontBBox[3] * fYFactor) ) );
+ // PDF-Spec says the values below mean the ink bounds!
+ // TODO: use better approximations for these ink bounds
+ rFSInfo.m_nAscent = +rFSInfo.m_aFontBBox.Bottom(); // for capital letters
+ rFSInfo.m_nDescent = -rFSInfo.m_aFontBBox.Top(); // for all letters
+ rFSInfo.m_nCapHeight = rFSInfo.m_nAscent; // for top-flat capital letters
+
+ rFSInfo.m_nFontType = rEmitter.mbPfbSubset ? FontType::TYPE1_PFB : FontType::TYPE1_PFA;
+ rFSInfo.m_aPSName = OUString( rEmitter.maSubsetName, strlen(rEmitter.maSubsetName), RTL_TEXTENCODING_UTF8 );
+}
+
+bool FontSubsetInfo::CreateFontSubsetFromCff( sal_Int32* pOutGlyphWidths )
+{
+ CffSubsetterContext aCff( mpInFontBytes, mnInByteLength);
+ bool bRC = aCff.initialCffRead();
+ if (!bRC)
+ return bRC;
+
+ // emit Type1 subset from the CFF input
+ // TODO: also support CFF->CFF subsetting (when PDF-export and PS-printing need it)
+ const bool bPfbSubset(mnReqFontTypeMask & FontType::TYPE1_PFB);
+ Type1Emitter aType1Emitter( mpOutFile, bPfbSubset);
+ aType1Emitter.setSubsetName( mpReqFontName);
+ aCff.emitAsType1( aType1Emitter,
+ mpReqGlyphIds, mpReqEncodedIds,
+ pOutGlyphWidths, mnReqGlyphCount, *this);
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/fontsubset/fontsubset.cxx b/vcl/source/fontsubset/fontsubset.cxx
new file mode 100644
index 000000000..8a4eeb9aa
--- /dev/null
+++ b/vcl/source/fontsubset/fontsubset.cxx
@@ -0,0 +1,163 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+
+#include <fontsubset.hxx>
+#include <sft.hxx>
+
+FontSubsetInfo::FontSubsetInfo()
+ : m_nAscent( 0)
+ , m_nDescent( 0)
+ , m_nCapHeight( 0)
+ , m_nFontType( FontType::NO_FONT)
+ , mpInFontBytes( nullptr)
+ , mnInByteLength( 0)
+ , meInFontType( FontType::NO_FONT)
+ , mpSftTTFont( nullptr)
+ , mnReqFontTypeMask( FontType::NO_FONT )
+ , mpOutFile(nullptr)
+ , mpReqFontName(nullptr)
+ , mpReqGlyphIds(nullptr)
+ , mpReqEncodedIds(nullptr)
+ , mnReqGlyphCount(0)
+{
+}
+
+FontSubsetInfo::~FontSubsetInfo()
+{
+}
+
+// prepare subsetting for fonts where the input font file is mapped
+void FontSubsetInfo::LoadFont(
+ FontType eInFontType,
+ const unsigned char* pInFontBytes, int nInByteLength)
+{
+ SAL_WARN_IF( (mpSftTTFont != nullptr), "vcl", "Subset from SFT and from mapped font-file requested");
+ meInFontType = eInFontType;
+ mpInFontBytes = pInFontBytes;
+ mnInByteLength = nInByteLength;
+}
+
+// prepare subsetting for fonts that are known to the SFT-parser
+void FontSubsetInfo::LoadFont( vcl::TrueTypeFont* pSftTTFont )
+{
+ SAL_WARN_IF( (mpInFontBytes != nullptr), "vcl", "Subset from SFT and from mapped font-file requested");
+ mpSftTTFont = pSftTTFont;
+ meInFontType = FontType::ANY_SFNT;
+}
+
+bool FontSubsetInfo::CreateFontSubset(
+ FontType nReqFontTypeMask,
+ FILE* pOutFile, const char* pReqFontName,
+ const sal_GlyphId* pReqGlyphIds, const sal_uInt8* pReqEncodedIds, int nReqGlyphCount,
+ sal_Int32* pOutGlyphWidths)
+{
+ // prepare request details needed by all underlying subsetters
+ mnReqFontTypeMask = nReqFontTypeMask;
+ mpOutFile = pOutFile;
+ mpReqFontName = pReqFontName;
+ mpReqGlyphIds = pReqGlyphIds;
+ mpReqEncodedIds = pReqEncodedIds;
+ mnReqGlyphCount = nReqGlyphCount;
+
+ // TODO: move the glyphid/encid/notdef reshuffling from the callers to here
+
+ // dispatch to underlying subsetters
+ bool bOK = false;
+
+ // TODO: better match available input-type to possible subset-types
+ switch( meInFontType) {
+ case FontType::SFNT_TTF:
+ case FontType::SFNT_CFF:
+ case FontType::ANY_SFNT:
+ bOK = CreateFontSubsetFromSfnt( pOutGlyphWidths);
+ break;
+ case FontType::CFF_FONT:
+ bOK = CreateFontSubsetFromCff( pOutGlyphWidths);
+ break;
+ case FontType::TYPE1_PFA:
+ case FontType::TYPE1_PFB:
+ case FontType::ANY_TYPE1:
+ bOK = CreateFontSubsetFromType1( pOutGlyphWidths);
+ break;
+ case FontType::NO_FONT:
+ default:
+ OSL_FAIL( "unhandled type in CreateFontSubset()");
+ break;
+ }
+
+ return bOK;
+}
+
+// TODO: move function to sft.cxx to replace dummy implementation
+bool FontSubsetInfo::CreateFontSubsetFromSfnt( sal_Int32* pOutGlyphWidths )
+{
+ // handle SFNT_CFF fonts
+ int nCffLength = 0;
+ const sal_uInt8* pCffBytes = nullptr;
+ if( GetSfntTable( mpSftTTFont, vcl::O_CFF, &pCffBytes, &nCffLength))
+ {
+ LoadFont( FontType::CFF_FONT, pCffBytes, nCffLength);
+ const bool bOK = CreateFontSubsetFromCff( pOutGlyphWidths);
+ return bOK;
+ }
+
+ // handle SFNT_TTF fonts
+ // by forwarding the subset request to AG's sft subsetter
+#if 1 // TODO: remove conversion tp 16bit glyphids when sft-subsetter has been updated
+ std::vector<sal_uInt16> aShortGlyphIds;
+ aShortGlyphIds.reserve(mnReqGlyphCount);
+ for (int i = 0; i < mnReqGlyphCount; ++i)
+ aShortGlyphIds.push_back(static_cast<sal_uInt16>(mpReqGlyphIds[i]));
+ // remove const_cast when sft-subsetter is const-correct
+ sal_uInt8* pEncArray = const_cast<sal_uInt8*>( mpReqEncodedIds );
+#endif
+ vcl::SFErrCodes nSFTErr = vcl::SFErrCodes::BadArg;
+ if( mnReqFontTypeMask & FontType::TYPE42_FONT )
+ {
+ nSFTErr = CreateT42FromTTGlyphs( mpSftTTFont, mpOutFile, mpReqFontName,
+ aShortGlyphIds.data(), pEncArray, mnReqGlyphCount );
+ }
+ else if( mnReqFontTypeMask & FontType::TYPE3_FONT )
+ {
+ nSFTErr = CreateT3FromTTGlyphs( mpSftTTFont, mpOutFile, mpReqFontName,
+ aShortGlyphIds.data(), pEncArray, mnReqGlyphCount,
+ 0 /* 0 = horizontal, 1 = vertical */ );
+ }
+ else if( mnReqFontTypeMask & FontType::SFNT_TTF )
+ {
+ // TODO: use CreateTTFromTTGlyphs()
+ // TODO: move functionality from callers here
+ }
+
+ return (nSFTErr != vcl::SFErrCodes::Ok);
+}
+
+// TODO: replace dummy implementation
+bool FontSubsetInfo::CreateFontSubsetFromType1( const sal_Int32* /*pOutGlyphWidths*/)
+{
+ SAL_WARN("vcl.fonts",
+ "CreateFontSubsetFromType1: replace dummy implementation.");
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/fontsubset/list.cxx b/vcl/source/fontsubset/list.cxx
new file mode 100644
index 000000000..aca585678
--- /dev/null
+++ b/vcl/source/fontsubset/list.cxx
@@ -0,0 +1,229 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+/*[]---------------------------------------------------[]*/
+/*| |*/
+/*| list.c - bidirectional list class |*/
+/*| |*/
+/*| |*/
+/*| Author: Alexander Gelfenbain |*/
+/*[]---------------------------------------------------[]*/
+
+#include <assert.h>
+#include <cstdlib>
+
+#include "list.h"
+
+namespace {
+
+/*- private data types */
+struct lnode {
+ struct lnode *next;
+ struct lnode *prev;
+
+ void *value;
+
+};
+
+}
+
+struct list_ {
+ lnode *head, *tail, *cptr;
+ size_t aCount;
+ list_destructor eDtor;
+};
+
+/*- private methods */
+
+static lnode *newNode(void *el)
+{
+ lnode *ptr = static_cast<lnode *>(std::malloc(sizeof(lnode)));
+ assert(ptr != nullptr);
+
+ ptr->value = el;
+
+ return ptr;
+}
+
+static lnode *appendPrim(list pThis, void *el)
+{
+ lnode *ptr = newNode(el);
+ lnode **flink, *blink;
+
+ if (pThis->tail != nullptr) {
+ flink = &(pThis->tail->next);
+ blink = pThis->tail;
+ } else {
+ flink = &pThis->head;
+ blink = nullptr;
+ pThis->cptr = ptr; /*- list was empty - set current to this element */
+ }
+
+ *flink = ptr;
+ pThis->tail = ptr;
+
+ ptr->prev = blink;
+ ptr->next = nullptr;
+
+ pThis->aCount++;
+ return ptr;
+}
+
+/*- public methods */
+list listNewEmpty() /*- default ctor */
+{
+ list pThis = static_cast<list>(std::malloc(sizeof(struct list_)));
+ assert(pThis != nullptr);
+
+ pThis->aCount = 0;
+ pThis->eDtor = nullptr;
+ pThis->head = pThis->tail = pThis->cptr = nullptr;
+
+ return pThis;
+}
+
+void listDispose(list pThis) /*- dtor */
+{
+ assert(pThis != nullptr);
+ listClear(pThis);
+ std::free(pThis);
+}
+
+void listSetElementDtor(list pThis, list_destructor f)
+{
+ assert(pThis != nullptr);
+ pThis->eDtor = f;
+}
+
+/* calling this function on an empty list is a run-time error */
+void *listCurrent(list pThis)
+{
+ assert(pThis != nullptr);
+ assert(pThis->cptr != nullptr);
+ return pThis->cptr->value;
+}
+
+int listCount(list pThis)
+{
+ assert(pThis != nullptr);
+ return pThis->aCount;
+}
+
+int listIsEmpty(list pThis)
+{
+ assert(pThis != nullptr);
+ return pThis->aCount == 0;
+}
+
+int listNext(list pThis)
+{
+ return listSkipForward(pThis, 1);
+}
+
+int listSkipForward(list pThis, int n)
+{
+ int m = 0;
+ assert(pThis != nullptr);
+
+ if (pThis->cptr == nullptr) return 0;
+
+ while (n != 0) {
+ if (pThis->cptr->next == nullptr) break;
+ pThis->cptr = pThis->cptr->next;
+ n--;
+ m++;
+ }
+ return m;
+}
+
+int listToFirst(list pThis)
+{
+ assert(pThis != nullptr);
+
+ if (pThis->cptr != pThis->head) {
+ pThis->cptr = pThis->head;
+ return 1;
+ }
+ return 0;
+}
+
+int listToLast(list pThis)
+{
+ assert(pThis != nullptr);
+
+ if (pThis->cptr != pThis->tail) {
+ pThis->cptr = pThis->tail;
+ return 1;
+ }
+ return 0;
+}
+
+list listAppend(list pThis, void *el)
+{
+ assert(pThis != nullptr);
+
+ appendPrim(pThis, el);
+ return pThis;
+}
+
+list listRemove(list pThis)
+{
+ lnode *ptr = nullptr;
+ if (pThis->cptr == nullptr) return pThis;
+
+ if (pThis->cptr->next != nullptr) {
+ ptr = pThis->cptr->next;
+ pThis->cptr->next->prev = pThis->cptr->prev;
+ } else {
+ pThis->tail = pThis->cptr->prev;
+ }
+
+ if (pThis->cptr->prev != nullptr) {
+ if (ptr == nullptr) ptr = pThis->cptr->prev;
+ pThis->cptr->prev->next = pThis->cptr->next;
+ } else {
+ pThis->head = pThis->cptr->next;
+ }
+
+ if (pThis->eDtor) pThis->eDtor(pThis->cptr->value); /* call the dtor callback */
+
+ std::free(pThis->cptr);
+ pThis->aCount--;
+ pThis->cptr = ptr;
+ return pThis;
+}
+
+list listClear(list pThis)
+{
+ lnode *node = pThis->head, *ptr;
+
+ while (node) {
+ ptr = node->next;
+ if (pThis->eDtor) pThis->eDtor(node->value); /* call the dtor callback */
+ std::free(node);
+ pThis->aCount--;
+ node = ptr;
+ }
+
+ pThis->head = pThis->tail = pThis->cptr = nullptr;
+ assert(pThis->aCount == 0);
+ return pThis;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/fontsubset/list.h b/vcl/source/fontsubset/list.h
new file mode 100644
index 000000000..18fda151a
--- /dev/null
+++ b/vcl/source/fontsubset/list.h
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+/*[]---------------------------------------------------[]*/
+/*| |*/
+/*| Implementation of the list data type |*/
+/*| |*/
+/*| |*/
+/*| Author: Alexander Gelfenbain |*/
+/*[]---------------------------------------------------[]*/
+
+#ifndef INCLUDED_VCL_INC_LIST_H
+#define INCLUDED_VCL_INC_LIST_H
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/*
+ * List of void * pointers
+ */
+
+ typedef struct list_ *list;
+ typedef void (*list_destructor)(void *);
+
+/*- constructors and a destructor */
+ list listNewEmpty(void);
+#ifdef TEST
+ list listNewCopy(list);
+#endif
+ void listDispose(list);
+ void listSetElementDtor(list, list_destructor); /*- this function will be executed when the element is removed via listRemove() or listClear() */
+
+/*- queries */
+ void * listCurrent(list);
+ int listCount(list);
+ int listIsEmpty(list);
+#ifdef TEST
+ int listAtFirst(list);
+ int listAtLast(list);
+ int listPosition(list); /* Expensive! */
+#endif
+
+/*- positioning functions */
+/*- return the number of elements by which the current position in the list changes */
+ int listNext(list);
+ int listSkipForward(list, int n);
+ int listToFirst(list);
+ int listToLast(list);
+
+/*- adding and removing elements */
+ list listAppend(list, void *);
+#ifdef TEST
+ list listPrepend(list, void *);
+ list listInsertAfter(list, void *);
+ list listInsertBefore(list, void *);
+#endif
+ list listRemove(list); /* removes the current element */
+ list listClear(list); /* removes all elements */
+
+#ifdef TEST
+/*- forall */
+ void listForAll(list, void (*f)(void *));
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // INCLUDED_VCL_INC_LIST_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/fontsubset/sft.cxx b/vcl/source/fontsubset/sft.cxx
new file mode 100644
index 000000000..efba8ff59
--- /dev/null
+++ b/vcl/source/fontsubset/sft.cxx
@@ -0,0 +1,2635 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 "ttcr.hxx"
+#include "xlat.hxx"
+#include <rtl/crc.h>
+#include <rtl/ustring.hxx>
+#include <sal/log.hxx>
+#include <o3tl/safeint.hxx>
+#include <osl/endian.h>
+#include <algorithm>
+
+namespace vcl
+{
+
+/*- module identification */
+
+static const char * const modname = "SunTypeTools-TT";
+static const char * const modver = "1.0";
+static const char * const modextra = "gelf";
+
+/*- private functions, constants and data types */
+
+namespace {
+
+enum PathSegmentType {
+ PS_NOOP = 0,
+ PS_MOVETO = 1,
+ PS_LINETO = 2,
+ PS_CURVETO = 3,
+ PS_CLOSEPATH = 4
+};
+
+struct PSPathElement
+{
+ PathSegmentType type;
+ int x1, y1;
+ int x2, y2;
+ int x3, y3;
+
+ explicit PSPathElement( PathSegmentType i_eType ) : type( i_eType ),
+ x1( 0 ), y1( 0 ),
+ x2( 0 ), y2( 0 ),
+ x3( 0 ), y3( 0 )
+ {
+ }
+};
+
+/*- In horizontal writing mode right sidebearing is calculated using this formula
+ *- rsb = aw - (lsb + xMax - xMin) -*/
+struct TTGlyphMetrics {
+ sal_Int16 xMin;
+ sal_Int16 yMin;
+ sal_Int16 xMax;
+ sal_Int16 yMax;
+ sal_uInt16 aw; /*- Advance Width (horizontal writing mode) */
+ sal_Int16 lsb; /*- Left sidebearing (horizontal writing mode) */
+ sal_uInt16 ah; /*- advance height (vertical writing mode) */
+};
+
+#define HFORMAT_LINELEN 64
+
+struct HexFmt {
+ FILE *o;
+ char buffer[HFORMAT_LINELEN];
+ size_t bufpos;
+ int total;
+};
+
+struct GlyphOffsets {
+ sal_uInt32 nGlyphs; /* number of glyphs in the font + 1 */
+ sal_uInt32 *offs; /* array of nGlyphs offsets */
+};
+
+}
+
+static void *smalloc(size_t size)
+{
+ void *res = malloc(size);
+ assert(res != nullptr);
+ return res;
+}
+
+static void *scalloc(size_t n, size_t size)
+{
+ void *res = calloc(n, size);
+ assert(res != nullptr);
+ return res;
+}
+
+/*- Data access methods for data stored in big-endian format */
+static sal_Int16 GetInt16(const sal_uInt8 *ptr, size_t offset)
+{
+ sal_Int16 t;
+ assert(ptr != nullptr);
+
+ t = (ptr+offset)[0] << 8 | (ptr+offset)[1];
+
+ return t;
+}
+
+static sal_uInt16 GetUInt16(const sal_uInt8 *ptr, size_t offset)
+{
+ sal_uInt16 t;
+ assert(ptr != nullptr);
+
+ t = (ptr+offset)[0] << 8 | (ptr+offset)[1];
+
+ return t;
+}
+
+static sal_Int32 GetInt32(const sal_uInt8 *ptr, size_t offset)
+{
+ sal_Int32 t;
+ assert(ptr != nullptr);
+
+ t = (ptr+offset)[0] << 24 | (ptr+offset)[1] << 16 |
+ (ptr+offset)[2] << 8 | (ptr+offset)[3];
+
+ return t;
+}
+
+static sal_uInt32 GetUInt32(const sal_uInt8 *ptr, size_t offset)
+{
+ sal_uInt32 t;
+ assert(ptr != nullptr);
+
+ t = (ptr+offset)[0] << 24 | (ptr+offset)[1] << 16 |
+ (ptr+offset)[2] << 8 | (ptr+offset)[3];
+
+ return t;
+}
+
+#if defined(OSL_BIGENDIAN)
+#define Int16FromMOTA(a) (a)
+#define Int32FromMOTA(a) (a)
+#else
+static sal_uInt16 Int16FromMOTA(sal_uInt16 a) {
+ return static_cast<sal_uInt16>(static_cast<sal_uInt8>(a >> 8) | (static_cast<sal_uInt8>(a) << 8));
+}
+static sal_uInt32 Int32FromMOTA(sal_uInt32 a) {
+ return ((a>>24)&0xFF) | (((a>>8)&0xFF00) | ((a&0xFF00)<<8) | ((a&0xFF)<<24));
+}
+#endif
+
+static F16Dot16 fixedMul(F16Dot16 a, F16Dot16 b)
+{
+ unsigned int a1, b1;
+ unsigned int a2, b2;
+ F16Dot16 res;
+ int sign;
+
+ sign = (a & 0x80000000) ^ (b & 0x80000000);
+ if (a < 0) a = -a;
+ if (b < 0) b = -b;
+
+ a1 = a >> 16;
+ b1 = a & 0xFFFF;
+ a2 = b >> 16;
+ b2 = b & 0xFFFF;
+
+ res = a1 * a2;
+
+ /* if (res > 0x7FFF) assert(!"fixedMul: F16Dot16 overflow"); */
+
+ res <<= 16;
+ res += a1 * b2 + b1 * a2 + ((b1 * b2) >> 16);
+
+ return sign ? -res : res;
+}
+
+static F16Dot16 fixedDiv(F16Dot16 a, F16Dot16 b)
+{
+ unsigned int f, r;
+ F16Dot16 res;
+ int sign;
+
+ sign = (a & 0x80000000) ^ (b & 0x80000000);
+ if (a < 0) a = -a;
+ if (b < 0) b = -b;
+
+ f = a / b;
+ r = a % b;
+
+ /* if (f > 0x7FFFF) assert(!"fixedDiv: F16Dot16 overflow"); */
+
+ while (r > 0xFFFF) {
+ r >>= 1;
+ b >>= 1;
+ }
+
+ res = (f << 16) + (r << 16) / b;
+
+ return sign ? -res : res;
+}
+
+/*- returns a * b / c -*/
+/* XXX provide a real implementation that preserves accuracy */
+static F16Dot16 fixedMulDiv(F16Dot16 a, F16Dot16 b, F16Dot16 c)
+{
+ F16Dot16 res;
+
+ res = fixedMul(a, b);
+ return fixedDiv(res, c);
+}
+
+/*- Translate units from TT to PS (standard 1/1000) -*/
+static int XUnits(int unitsPerEm, int n)
+{
+ return (n * 1000) / unitsPerEm;
+}
+
+static const sal_uInt8* getTable( TrueTypeFont const *ttf, sal_uInt32 ord)
+{
+ return ttf->tables[ord];
+}
+
+static sal_uInt32 getTableSize(TrueTypeFont const *ttf, sal_uInt32 ord)
+{
+ return ttf->tlens[ord];
+}
+
+static char toHex(sal_uInt8 nIndex)
+{
+ /* Hex Formatter functions */
+ static const char HexChars[] = "0123456789ABCDEF";
+ assert(nIndex < SAL_N_ELEMENTS(HexChars));
+ return HexChars[nIndex];
+}
+
+static HexFmt *HexFmtNew(FILE *outf)
+{
+ HexFmt* res = static_cast<HexFmt*>(smalloc(sizeof(HexFmt)));
+ res->bufpos = res->total = 0;
+ res->o = outf;
+ return res;
+}
+
+static bool HexFmtFlush(HexFmt *_this)
+{
+ bool bRet = true;
+ if (_this->bufpos) {
+ size_t nWritten = fwrite(_this->buffer, 1, _this->bufpos, _this->o);
+ bRet = nWritten == _this->bufpos;
+ _this->bufpos = 0;
+ }
+ return bRet;
+}
+
+static void HexFmtOpenString(HexFmt *_this)
+{
+ fputs("<\n", _this->o);
+}
+
+static void HexFmtCloseString(HexFmt *_this)
+{
+ HexFmtFlush(_this);
+ fputs("00\n>\n", _this->o);
+}
+
+static void HexFmtDispose(HexFmt *_this)
+{
+ HexFmtFlush(_this);
+ free(_this);
+}
+
+static void HexFmtBlockWrite(HexFmt *_this, const void *ptr, sal_uInt32 size)
+{
+ sal_uInt8 Ch;
+ sal_uInt32 i;
+
+ if (_this->total + size > 65534) {
+ HexFmtFlush(_this);
+ HexFmtCloseString(_this);
+ _this->total = 0;
+ HexFmtOpenString(_this);
+ }
+ for (i=0; i<size; i++) {
+ Ch = static_cast<sal_uInt8 const *>(ptr)[i];
+ _this->buffer[_this->bufpos++] = toHex(Ch >> 4);
+ _this->buffer[_this->bufpos++] = toHex(Ch & 0xF);
+ if (_this->bufpos == HFORMAT_LINELEN) {
+ HexFmtFlush(_this);
+ fputc('\n', _this->o);
+ }
+
+ }
+ _this->total += size;
+}
+
+/* Outline Extraction functions */
+
+/* fills the aw and lsb entries of the TTGlyphMetrics structure from hmtx table -*/
+static void GetMetrics(TrueTypeFont const *ttf, sal_uInt32 glyphID, TTGlyphMetrics *metrics)
+{
+ const sal_uInt8* table = getTable( ttf, O_hmtx );
+
+ metrics->aw = metrics->lsb = metrics->ah = 0;
+ if (!table || !ttf->numberOfHMetrics) return;
+
+ if (glyphID < ttf->numberOfHMetrics) {
+ metrics->aw = GetUInt16(table, 4 * glyphID);
+ metrics->lsb = GetInt16(table, 4 * glyphID + 2);
+ } else {
+ metrics->aw = GetUInt16(table, 4 * (ttf->numberOfHMetrics - 1));
+ metrics->lsb = GetInt16(table + ttf->numberOfHMetrics * 4, (glyphID - ttf->numberOfHMetrics) * 2);
+ }
+
+ table = getTable(ttf, O_vmtx);
+ if( !table || !ttf->numOfLongVerMetrics )
+ return;
+
+ if (glyphID < ttf->numOfLongVerMetrics) {
+ metrics->ah = GetUInt16(table, 4 * glyphID);
+ } else {
+ metrics->ah = GetUInt16(table, 4 * (ttf->numOfLongVerMetrics - 1));
+ }
+}
+
+static int GetTTGlyphOutline(TrueTypeFont *, sal_uInt32 , ControlPoint **, TTGlyphMetrics *, std::vector< sal_uInt32 >* );
+
+/* returns the number of control points, allocates the pointArray */
+static int GetSimpleTTOutline(TrueTypeFont const *ttf, sal_uInt32 glyphID, ControlPoint **pointArray, TTGlyphMetrics *metrics)
+{
+ const sal_uInt8* table = getTable(ttf, O_glyf);
+ const sal_uInt32 nTableSize = getTableSize(ttf, O_glyf);
+ sal_uInt8 flag, n;
+ int i, j, z;
+
+ *pointArray = nullptr;
+
+ /* printf("GetSimpleTTOutline(%d)\n", glyphID); */
+
+ if( glyphID >= ttf->nglyphs ) /*- glyph is not present in the font */
+ return 0;
+ const sal_uInt8* ptr = table + ttf->goffsets[glyphID];
+ const sal_Int16 numberOfContours = GetInt16(ptr, GLYF_numberOfContours_offset);
+ if( numberOfContours <= 0 ) /*- glyph is not simple */
+ 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;
+ const sal_Int32 nMaxContours = (nTableSize - 10)/2;
+ if (numberOfContours > nMaxContours)
+ return 0;
+ for (i=0; i<numberOfContours; i++)
+ {
+ const sal_uInt16 t = GetUInt16(ptr, 10+i*2);
+ if (t > lastPoint)
+ lastPoint = t;
+ }
+
+ sal_uInt16 instLen = GetUInt16(ptr, 10 + numberOfContours*2);
+ sal_uInt32 nOffset = 10 + 2 * numberOfContours + 2 + instLen;
+ if (nOffset > nTableSize)
+ return 0;
+ const sal_uInt8* p = ptr + nOffset;
+
+ const sal_uInt32 nBytesRemaining = nTableSize - nOffset;
+ const sal_uInt16 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->fname) <<
+ "claimed a palen of "
+ << palen << " but max bytes remaining is " << nBytesRemaining);
+ return 0;
+ }
+
+ ControlPoint* pa = static_cast<ControlPoint*>(calloc(palen, sizeof(ControlPoint)));
+
+ i = 0;
+ while (i <= lastPoint) {
+ flag = *p++;
+ pa[i++].flags = static_cast<sal_uInt32>(flag);
+ if (flag & 8) { /*- repeat flag */
+ n = *p++;
+ // coverity[tainted_data] - i > lastPoint extra checks the n loop bound
+ for (j=0; j<n; j++) {
+ if (i > lastPoint) { /*- if the font is really broken */
+ free(pa);
+ return 0;
+ }
+ pa[i++].flags = flag;
+ }
+ }
+ }
+
+ /*- Process the X coordinate */
+ z = 0;
+ for (i = 0; i <= lastPoint; i++) {
+ if (pa[i].flags & 0x02) {
+ if (pa[i].flags & 0x10) {
+ z += static_cast<int>(*p++);
+ } else {
+ z -= static_cast<int>(*p++);
+ }
+ } else if ( !(pa[i].flags & 0x10)) {
+ z += GetInt16(p, 0);
+ p += 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 (pa[i].flags & 0x20) {
+ z += *p++;
+ } else {
+ z -= *p++;
+ }
+ } else if ( !(pa[i].flags & 0x20)) {
+ z += GetInt16(p, 0);
+ p += 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->fname) <<
+ " contour " << i << " claimed an illegal offset of "
+ << offset << " but max offset is " << palen-1);
+ if (offset >= palen)
+ continue;
+ pa[offset].flags |= 0x00008000; /*- set the end contour flag */
+ }
+
+ *pointArray = pa;
+ return lastPoint + 1;
+}
+
+static F16Dot16 fromF2Dot14(sal_Int16 n)
+{
+ // Avoid undefined shift of negative values prior to C++2a:
+ return sal_uInt32(n) << 2;
+}
+
+static int GetCompoundTTOutline(TrueTypeFont *ttf, sal_uInt32 glyphID, ControlPoint **pointArray, TTGlyphMetrics *metrics, std::vector< sal_uInt32 >& glyphlist)
+{
+ sal_uInt16 flags, index;
+ sal_Int16 e, f;
+ const sal_uInt8* table = getTable( ttf, O_glyf );
+ std::vector<ControlPoint> myPoints;
+ ControlPoint *nextComponent, *pa;
+ int i, np;
+ F16Dot16 a = 0x10000, b = 0, c = 0, d = 0x10000, m, n, abs1, abs2, abs3;
+
+ *pointArray = nullptr;
+ /* printf("GetCompoundTTOutline(%d)\n", glyphID); */
+
+ if (glyphID >= ttf->nglyphs) /*- incorrect glyphID */
+ return 0;
+
+ const sal_uInt8* ptr = table + ttf->goffsets[glyphID];
+ 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);
+ }
+
+ ptr += 10;
+
+ do {
+ flags = GetUInt16(ptr, 0);
+ /* printf("flags: 0x%X\n", flags); */
+ index = GetUInt16(ptr, 2);
+ ptr += 4;
+
+ if( std::find( glyphlist.begin(), glyphlist.end(), index ) != glyphlist.end() )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.fonts", "Endless loop found in a compound glyph.");
+
+ std::ostringstream oss;
+ oss << index << " -> [";
+ for( const auto& rGlyph : glyphlist )
+ {
+ oss << (int) rGlyph << " ";
+ }
+ oss << "]";
+ SAL_INFO("vcl.fonts", oss.str());
+ /**/
+#endif
+ }
+
+ glyphlist.push_back( index );
+
+ if ((np = GetTTGlyphOutline(ttf, index, &nextComponent, nullptr, &glyphlist)) == 0)
+ {
+ /* XXX that probably indicates a corrupted font */
+#if OSL_DEBUG_LEVEL > 1
+ SAL_WARN("vcl.fonts", "An empty compound!");
+ /* assert(!"An empty compound"); */
+#endif
+ }
+
+ if( ! glyphlist.empty() )
+ glyphlist.pop_back();
+
+ if ((flags & USE_MY_METRICS) && metrics)
+ GetMetrics(ttf, index, metrics);
+
+ if (flags & ARG_1_AND_2_ARE_WORDS) {
+ e = GetInt16(ptr, 0);
+ f = GetInt16(ptr, 2);
+ /* printf("ARG_1_AND_2_ARE_WORDS: %d %d\n", e & 0xFFFF, f & 0xFFFF); */
+ ptr += 4;
+ } else {
+ 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++;
+ }
+
+ }
+
+ a = d = 0x10000;
+ b = c = 0;
+
+ if (flags & WE_HAVE_A_SCALE) {
+ a = fromF2Dot14(GetInt16(ptr, 0));
+ d = a;
+ ptr += 2;
+ } else if (flags & WE_HAVE_AN_X_AND_Y_SCALE) {
+ a = fromF2Dot14(GetInt16(ptr, 0));
+ d = fromF2Dot14(GetInt16(ptr, 2));
+ ptr += 4;
+ } else if (flags & WE_HAVE_A_TWO_BY_TWO) {
+ a = fromF2Dot14(GetInt16(ptr, 0));
+ b = fromF2Dot14(GetInt16(ptr, 2));
+ c = fromF2Dot14(GetInt16(ptr, 4));
+ d = fromF2Dot14(GetInt16(ptr, 6));
+ ptr += 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, "vcl.fonts", "Parsing error in " << OUString::createFromAscii(ttf->fname) <<
+ ": divide by zero");
+
+ if (m != 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 = 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 = fixedMulDiv(b, x << 16, n) + fixedMulDiv(d, y << 16, n) + sal_Int32(sal_uInt16(f) << 16);
+ cp.y = static_cast<sal_Int16>(fixedMul(t, n) >> 16);
+
+ myPoints.push_back( cp );
+ }
+ }
+
+ free(nextComponent);
+
+ } while (flags & MORE_COMPONENTS);
+
+ // #i123417# some fonts like IFAOGrec have no outline points in some compound glyphs
+ // so this unlikely but possible scenario should be handled gracefully
+ if( myPoints.empty() )
+ return 0;
+
+ np = myPoints.size();
+ if (np > 0)
+ {
+ pa = static_cast<ControlPoint*>(calloc(np, sizeof(ControlPoint)));
+ assert(pa != nullptr);
+
+ memcpy(pa, myPoints.data(), np * sizeof(ControlPoint));
+
+ *pointArray = pa;
+ }
+ return np;
+}
+
+/* NOTE: GetTTGlyphOutline() returns -1 if the glyphID is incorrect,
+ * but Get{Simple|Compound}GlyphOutline returns 0 in such a case.
+ *
+ * NOTE: glyphlist is the stack of glyphs traversed while constructing
+ * a composite glyph. This is a safeguard against endless recursion
+ * in corrupted fonts.
+ */
+static int GetTTGlyphOutline(TrueTypeFont *ttf, sal_uInt32 glyphID, ControlPoint **pointArray, TTGlyphMetrics *metrics, std::vector< sal_uInt32 >* glyphlist)
+{
+ const sal_uInt8 *table = getTable( ttf, O_glyf );
+ sal_Int16 numberOfContours;
+ int res;
+ *pointArray = nullptr;
+
+ if (metrics) {
+ memset(metrics, 0, sizeof(TTGlyphMetrics)); /*- metrics is initialized to all zeroes */
+ }
+
+ if (glyphID >= ttf->nglyphs) return -1; /**/
+
+ const sal_uInt8* ptr = table + ttf->goffsets[glyphID];
+ int length = ttf->goffsets[glyphID+1] - ttf->goffsets[glyphID];
+
+ if (length == 0) { /*- empty glyphs still have hmtx and vmtx metrics values */
+ if (metrics) GetMetrics(ttf, glyphID, metrics);
+ return 0;
+ }
+
+ numberOfContours = GetInt16(ptr, 0);
+
+ if (numberOfContours >= 0)
+ {
+ res=GetSimpleTTOutline(ttf, glyphID, pointArray, metrics);
+ }
+ else
+ {
+ std::vector< sal_uInt32 > aPrivList;
+ aPrivList.push_back( glyphID );
+ res = GetCompoundTTOutline(ttf, glyphID, pointArray, metrics, glyphlist ? *glyphlist : aPrivList );
+ }
+
+ return res;
+}
+
+/*- returns the number of items in the path -*/
+
+static int BSplineToPSPath(ControlPoint const *srcA, int srcCount, PSPathElement **path)
+{
+ std::vector< PSPathElement > aPathList;
+ int nPathCount = 0;
+ PSPathElement p( PS_NOOP );
+
+ int x0 = 0, y0 = 0, x1 = 0, y1 = 0, x2, y2, curx, cury;
+ bool lastOff = false; /*- last point was off-contour */
+ int scflag = 1; /*- start contour flag */
+ bool ecflag = false; /*- end contour flag */
+ int cp = 0; /*- current point */
+ int StartContour = 0, EndContour = 1;
+
+ *path = nullptr;
+
+ /* if (srcCount > 0) for(;;) */
+ while (srcCount > 0) { /*- srcCount does not get changed inside the loop. */
+ if (scflag) {
+ int l = cp;
+ StartContour = cp;
+ while (!(srcA[l].flags & 0x8000)) l++;
+ EndContour = l;
+ if (StartContour == EndContour) {
+ if (cp + 1 < srcCount) {
+ cp++;
+ continue;
+ } else {
+ break;
+ }
+ }
+ p = PSPathElement(PS_MOVETO);
+ if (!(srcA[cp].flags & 1)) {
+ if (!(srcA[EndContour].flags & 1)) {
+ p.x1 = x0 = (srcA[cp].x + srcA[EndContour].x + 1) / 2;
+ p.y1 = y0 = (srcA[cp].y + srcA[EndContour].y + 1) / 2;
+ } else {
+ p.x1 = x0 = srcA[EndContour].x;
+ p.y1 = y0 = srcA[EndContour].y;
+ }
+ } else {
+ p.x1 = x0 = srcA[cp].x;
+ p.y1 = y0 = srcA[cp].y;
+ cp++;
+ }
+ aPathList.push_back( p );
+ lastOff = false;
+ scflag = 0;
+ }
+
+ curx = srcA[cp].x;
+ cury = srcA[cp].y;
+
+ if (srcA[cp].flags & 1)
+ {
+ if (lastOff)
+ {
+ p = PSPathElement(PS_CURVETO);
+ p.x1 = x0 + (2 * (x1 - x0) + 1) / 3;
+ p.y1 = y0 + (2 * (y1 - y0) + 1) / 3;
+ p.x2 = x1 + (curx - x1 + 1) / 3;
+ p.y2 = y1 + (cury - y1 + 1) / 3;
+ p.x3 = curx;
+ p.y3 = cury;
+ aPathList.push_back( p );
+ }
+ else
+ {
+ if (!(x0 == curx && y0 == cury))
+ { /* eliminate empty lines */
+ p = PSPathElement(PS_LINETO);
+ p.x1 = curx;
+ p.y1 = cury;
+ aPathList.push_back( p );
+ }
+ }
+ x0 = curx; y0 = cury; lastOff = false;
+ }
+ else
+ {
+ if (lastOff)
+ {
+ x2 = (x1 + curx + 1) / 2;
+ y2 = (y1 + cury + 1) / 2;
+ p = PSPathElement(PS_CURVETO);
+ p.x1 = x0 + (2 * (x1 - x0) + 1) / 3;
+ p.y1 = y0 + (2 * (y1 - y0) + 1) / 3;
+ p.x2 = x1 + (x2 - x1 + 1) / 3;
+ p.y2 = y1 + (y2 - y1 + 1) / 3;
+ p.x3 = x2;
+ p.y3 = y2;
+ aPathList.push_back( p );
+ x0 = x2; y0 = y2;
+ x1 = curx; y1 = cury;
+ } else {
+ x1 = curx; y1 = cury;
+ }
+ lastOff = true;
+ }
+
+ if (ecflag) {
+ aPathList.emplace_back(PS_CLOSEPATH );
+ scflag = 1;
+ ecflag = false;
+ cp = EndContour + 1;
+ if (cp >= srcCount) break;
+ continue;
+ }
+
+ if (cp == EndContour) {
+ cp = StartContour;
+ ecflag = true;
+ } else {
+ cp++;
+ }
+ }
+
+ if( (nPathCount = static_cast<int>(aPathList.size())) > 0)
+ {
+ *path = static_cast<PSPathElement*>(calloc(nPathCount, sizeof(PSPathElement)));
+ assert(*path != nullptr);
+ memcpy( *path, aPathList.data(), nPathCount * sizeof(PSPathElement) );
+ }
+
+ return nPathCount;
+}
+
+/*- Extracts a string from the name table and allocates memory for it -*/
+
+static char *nameExtract( const sal_uInt8* name, int nTableSize, int n, int dbFlag, sal_Unicode** ucs2result )
+{
+ char *res;
+ const sal_uInt8* ptr = name + GetUInt16(name, 4) + GetUInt16(name + 6, 12 * n + 10);
+ int len = GetUInt16(name+6, 12 * n + 8);
+
+ // sanity check
+ const sal_uInt8* end_table = name+nTableSize;
+ const int available_space = ptr > end_table ? 0 : (end_table - ptr);
+ if( (len <= 0) || len > available_space)
+ {
+ if( ucs2result )
+ *ucs2result = nullptr;
+ return nullptr;
+ }
+
+ if( ucs2result )
+ *ucs2result = nullptr;
+ if (dbFlag) {
+ res = static_cast<char*>(malloc(1 + len/2));
+ assert(res != nullptr);
+ for (int i = 0; i < len/2; i++)
+ res[i] = *(ptr + i * 2 + 1);
+ res[len/2] = 0;
+ if( ucs2result )
+ {
+ *ucs2result = static_cast<sal_Unicode*>(malloc( len+2 ));
+ for (int i = 0; i < len/2; i++ )
+ (*ucs2result)[i] = GetUInt16( ptr, 2*i );
+ (*ucs2result)[len/2] = 0;
+ }
+ } else {
+ res = static_cast<char*>(malloc(1 + len));
+ assert(res != nullptr);
+ memcpy(res, ptr, len);
+ res[len] = 0;
+ }
+
+ return res;
+}
+
+static int findname( const sal_uInt8 *name, sal_uInt16 n, sal_uInt16 platformID,
+ sal_uInt16 encodingID, sal_uInt16 languageID, sal_uInt16 nameID )
+{
+ if (n == 0) return -1;
+
+ int l = 0, r = n-1;
+ sal_uInt32 t1, t2;
+ sal_uInt32 m1, m2;
+
+ m1 = (platformID << 16) | encodingID;
+ m2 = (languageID << 16) | nameID;
+
+ do {
+ const int i = (l + r) >> 1;
+ t1 = GetUInt32(name + 6, i * 12 + 0);
+ t2 = GetUInt32(name + 6, i * 12 + 4);
+
+ if (! ((m1 < t1) || ((m1 == t1) && (m2 < t2)))) l = i + 1;
+ if (! ((m1 > t1) || ((m1 == t1) && (m2 > t2)))) r = i - 1;
+ } while (l <= r);
+
+ if (l - r == 2) {
+ return l - 1;
+ }
+
+ return -1;
+}
+
+/* XXX marlett.ttf uses (3, 0, 1033) instead of (3, 1, 1033) and does not have any Apple tables.
+ * Fix: if (3, 1, 1033) is not found - need to check for (3, 0, 1033)
+ *
+ * /d/fonts/ttzh_tw/Big5/Hanyi/ma6b5p uses (1, 0, 19) for English strings, instead of (1, 0, 0)
+ * and does not have (3, 1, 1033)
+ * Fix: if (1, 0, 0) and (3, 1, 1033) are not found need to look for (1, 0, *) - that will
+ * require a change in algorithm
+ *
+ * /d/fonts/fdltest/Korean/h2drrm has unsorted names and an unknown (to me) Mac LanguageID,
+ * but (1, 0, 1042) strings usable
+ * Fix: change algorithm, and use (1, 0, *) if both standard Mac and MS strings are not found
+ */
+
+static void GetNames(TrueTypeFont *t)
+{
+ const sal_uInt8* table = getTable( t, O_name );
+ const sal_uInt32 nTableSize = getTableSize(t, O_name);
+
+ if (nTableSize < 6)
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_WARN("vcl.fonts", "O_name table too small.");
+#endif
+ return;
+ }
+
+ sal_uInt16 n = GetUInt16(table, 2);
+
+ /* simple sanity check for name table entry count */
+ const size_t nMinRecordSize = 12;
+ const size_t nSpaceAvailable = nTableSize - 6;
+ const size_t nMaxRecords = nSpaceAvailable/nMinRecordSize;
+ if (n >= nMaxRecords)
+ n = 0;
+
+ int i, r;
+ bool bPSNameOK = true;
+
+ /* PostScript name: preferred Microsoft */
+ t->psname = nullptr;
+ if ((r = findname(table, n, 3, 1, 0x0409, 6)) != -1)
+ t->psname = nameExtract(table, nTableSize, r, 1, nullptr);
+ if ( ! t->psname && (r = findname(table, n, 1, 0, 0, 6)) != -1)
+ t->psname = nameExtract(table, nTableSize, r, 0, nullptr);
+ if ( ! t->psname && (r = findname(table, n, 3, 0, 0x0409, 6)) != -1)
+ {
+ // some symbol fonts like Marlett have a 3,0 name!
+ t->psname = nameExtract(table, nTableSize, r, 1, nullptr);
+ }
+ // for embedded font in Ghostscript PDFs
+ if ( ! t->psname && (r = findname(table, n, 2, 2, 0, 6)) != -1)
+ {
+ t->psname = nameExtract(table, nTableSize, r, 0, nullptr);
+ }
+ if ( ! t->psname )
+ {
+ if ( t->fname )
+ {
+ char* pReverse = t->fname + strlen(t->fname);
+ /* take only last token of filename */
+ while(pReverse != t->fname && *pReverse != '/') pReverse--;
+ if(*pReverse == '/') pReverse++;
+ t->psname = strdup(pReverse);
+ assert(t->psname != nullptr);
+ for (i=strlen(t->psname) - 1; i > 0; i--)
+ {
+ /*- Remove the suffix -*/
+ if (t->psname[i] == '.' ) {
+ t->psname[i] = 0;
+ break;
+ }
+ }
+ }
+ else
+ t->psname = strdup( "Unknown" );
+ }
+
+ /* Font family and subfamily names: preferred Apple */
+ t->family = nullptr;
+ if ((r = findname(table, n, 0, 0, 0, 1)) != -1)
+ t->family = nameExtract(table, nTableSize, r, 1, &t->ufamily);
+ if ( ! t->family && (r = findname(table, n, 3, 1, 0x0409, 1)) != -1)
+ t->family = nameExtract(table, nTableSize, r, 1, &t->ufamily);
+ if ( ! t->family && (r = findname(table, n, 1, 0, 0, 1)) != -1)
+ t->family = nameExtract(table, nTableSize, r, 0, nullptr);
+ if ( ! t->family && (r = findname(table, n, 3, 1, 0x0411, 1)) != -1)
+ t->family = nameExtract(table, nTableSize, r, 1, &t->ufamily);
+ if ( ! t->family && (r = findname(table, n, 3, 0, 0x0409, 1)) != -1)
+ t->family = nameExtract(table, nTableSize, r, 1, &t->ufamily);
+ if ( ! t->family )
+ {
+ t->family = strdup(t->psname);
+ assert(t->family != nullptr);
+ }
+
+ t->subfamily = nullptr;
+ t->usubfamily = nullptr;
+ if ((r = findname(table, n, 1, 0, 0, 2)) != -1)
+ t->subfamily = nameExtract(table, nTableSize, r, 0, &t->usubfamily);
+ if ( ! t->subfamily && (r = findname(table, n, 3, 1, 0x0409, 2)) != -1)
+ t->subfamily = nameExtract(table, nTableSize, r, 1, &t->usubfamily);
+ if ( ! t->subfamily )
+ {
+ t->subfamily = strdup("");
+ }
+
+ /* #i60349# sanity check psname
+ * psname practically has to be 7bit ASCII and should not contain spaces
+ * there is a class of broken fonts which do not fulfill that at all, so let's try
+ * if the family name is 7bit ASCII and take it instead if so
+ */
+ /* check psname */
+ for( i = 0; t->psname[i] != 0 && bPSNameOK; i++ )
+ if( t->psname[ i ] < 33 || (t->psname[ i ] & 0x80) )
+ bPSNameOK = false;
+ if( !bPSNameOK )
+ {
+ /* check if family is a suitable replacement */
+ if( t->ufamily && t->family )
+ {
+ bool bReplace = true;
+
+ for( i = 0; t->ufamily[ i ] != 0 && bReplace; i++ )
+ if( t->ufamily[ i ] < 33 || t->ufamily[ i ] > 127 )
+ bReplace = false;
+ if( bReplace )
+ {
+ free( t->psname );
+ t->psname = strdup( t->family );
+ }
+ }
+ }
+}
+
+namespace {
+
+enum cmapType {
+ CMAP_NOT_USABLE = -1,
+ CMAP_MS_Symbol = 10,
+ CMAP_MS_Unicode = 11,
+ CMAP_MS_ShiftJIS = 12,
+ CMAP_MS_PRC = 13,
+ CMAP_MS_Big5 = 14,
+ CMAP_MS_Wansung = 15,
+ CMAP_MS_Johab = 16
+};
+
+}
+
+#define MISSING_GLYPH_INDEX 0
+
+static sal_uInt32 getGlyph0(const sal_uInt8* cmap, sal_uInt32, sal_uInt32 c) {
+ if (c <= 255) {
+ return *(cmap + 6 + c);
+ } else {
+ return MISSING_GLYPH_INDEX;
+ }
+}
+
+namespace {
+
+struct subHeader2 {
+ sal_uInt16 firstCode;
+ sal_uInt16 entryCount;
+ sal_uInt16 idDelta;
+ sal_uInt16 idRangeOffset;
+};
+
+}
+
+static sal_uInt32 getGlyph2(const sal_uInt8 *cmap, const sal_uInt32 nMaxCmapSize, sal_uInt32 c) {
+ sal_uInt16 const *CMAP2 = reinterpret_cast<sal_uInt16 const *>(cmap);
+ sal_uInt8 theHighByte;
+
+ sal_uInt8 theLowByte;
+ subHeader2 const * subHeader2s;
+ sal_uInt16 const * subHeader2Keys;
+ sal_uInt16 firstCode;
+ int k = -1;
+ sal_uInt32 ToReturn;
+
+ theHighByte = static_cast<sal_uInt8>((c >> 8) & 0x00ff);
+ theLowByte = static_cast<sal_uInt8>(c & 0x00ff);
+ subHeader2Keys = CMAP2 + 3;
+ subHeader2s = reinterpret_cast<subHeader2 const *>(subHeader2Keys + 256);
+ if(reinterpret_cast<sal_uInt8 const *>(&subHeader2Keys[theHighByte]) - cmap < int(nMaxCmapSize - 2))
+ {
+ k = Int16FromMOTA(subHeader2Keys[theHighByte]) / 8;
+ // check if the subheader record fits into available space
+ if(reinterpret_cast<sal_uInt8 const *>(&subHeader2s[k]) - cmap >= int(nMaxCmapSize - sizeof(subHeader2)))
+ k = -1;
+ }
+
+ if(k == 0) {
+ firstCode = Int16FromMOTA(subHeader2s[0].firstCode);
+ if(theLowByte >= firstCode && theLowByte < (firstCode + Int16FromMOTA(subHeader2s[k].entryCount))) {
+ sal_uInt16 const * pGlyph = (&(subHeader2s[0].idRangeOffset))
+ + (Int16FromMOTA(subHeader2s[0].idRangeOffset)/2) /* + offset */
+ + theLowByte /* + to_look */
+ - firstCode
+ ;
+ if (reinterpret_cast<sal_uInt8 const *>(pGlyph) - cmap < int(nMaxCmapSize) - 4)
+ return *pGlyph;
+ else
+ return MISSING_GLYPH_INDEX;
+ } else {
+ return MISSING_GLYPH_INDEX;
+ }
+ } else if (k > 0) {
+ firstCode = Int16FromMOTA(subHeader2s[k].firstCode);
+ if(theLowByte >= firstCode && theLowByte < (firstCode + Int16FromMOTA(subHeader2s[k].entryCount))) {
+ ToReturn = *((&(subHeader2s[k].idRangeOffset))
+ + (Int16FromMOTA(subHeader2s[k].idRangeOffset)/2)
+ + theLowByte - firstCode);
+ if(ToReturn == 0) {
+ return MISSING_GLYPH_INDEX;
+ } else {
+ ToReturn += Int16FromMOTA(subHeader2s[k].idDelta);
+ return (ToReturn & 0xFFFF);
+ }
+ } else {
+ return MISSING_GLYPH_INDEX;
+ }
+ } else {
+ return MISSING_GLYPH_INDEX;
+ }
+}
+
+static sal_uInt32 getGlyph6(const sal_uInt8 *cmap, sal_uInt32, sal_uInt32 c) {
+ sal_uInt16 firstCode, lastCode, count;
+ sal_uInt16 const *CMAP6 = reinterpret_cast<sal_uInt16 const *>(cmap);
+
+ firstCode = Int16FromMOTA(*(CMAP6 + 3));
+ count = Int16FromMOTA(*(CMAP6 + 4));
+ lastCode = firstCode + count - 1;
+ if (c < firstCode || c > lastCode) {
+ return MISSING_GLYPH_INDEX;
+ } else {
+ return *((CMAP6 + 5)/*glyphIdArray*/ + (c - firstCode));
+ }
+}
+
+static sal_uInt16 GEbinsearch(sal_uInt16 const *ar, sal_uInt16 length, sal_uInt16 toSearch) {
+ signed int low, high, lastfound = 0xffff;
+ sal_uInt16 res;
+ if(length == sal_uInt16(0) || length == sal_uInt16(0xFFFF)) {
+ return sal_uInt16(0xFFFF);
+ }
+ low = 0;
+ high = length - 1;
+ while(high >= low) {
+ int mid = (high + low)/2;
+ res = Int16FromMOTA(*(ar+mid));
+ if(res >= toSearch) {
+ lastfound = mid;
+ high = --mid;
+ } else {
+ low = ++mid;
+ }
+ }
+ return static_cast<sal_uInt16>(lastfound);
+}
+
+static sal_uInt32 getGlyph4(const sal_uInt8 *cmap, const sal_uInt32 nMaxCmapSize, sal_uInt32 c) {
+ sal_uInt16 i;
+ int ToReturn;
+ sal_uInt16 segCount;
+ sal_uInt16 const * startCode;
+ sal_uInt16 const * endCode;
+ sal_uInt16 const * idDelta;
+ /* sal_uInt16 * glyphIdArray; */
+ sal_uInt16 const * idRangeOffset;
+ /*sal_uInt16 * glyphIndexArray;*/
+ sal_uInt16 const *CMAP4 = reinterpret_cast<sal_uInt16 const *>(cmap);
+ /* sal_uInt16 GEbinsearch(sal_uInt16 *ar, sal_uInt16 length, sal_uInt16 toSearch); */
+
+ segCount = Int16FromMOTA(*(CMAP4 + 3))/2;
+ endCode = CMAP4 + 7;
+ i = GEbinsearch(endCode, segCount, static_cast<sal_uInt16>(c));
+
+ if (i == sal_uInt16(0xFFFF)) {
+ return MISSING_GLYPH_INDEX;
+ }
+ startCode = endCode + segCount + 1;
+
+ if((reinterpret_cast<sal_uInt8 const *>(&startCode[i]) - cmap >= int(nMaxCmapSize - 2)) || Int16FromMOTA(startCode[i]) > c) {
+ return MISSING_GLYPH_INDEX;
+ }
+ idDelta = startCode + segCount;
+ idRangeOffset = idDelta + segCount;
+ /*glyphIndexArray = idRangeOffset + segCount;*/
+
+ if((reinterpret_cast<sal_uInt8 const *>(&idRangeOffset[i]) - cmap < int(nMaxCmapSize - 2)) && Int16FromMOTA(idRangeOffset[i]) != 0) {
+ sal_uInt16 const * pGlyphOffset = &(idRangeOffset[i]) + (Int16FromMOTA(idRangeOffset[i])/2 + (c - Int16FromMOTA(startCode[i])));
+ if(reinterpret_cast<sal_uInt8 const *>(pGlyphOffset) - cmap >= int(nMaxCmapSize - 2))
+ return MISSING_GLYPH_INDEX;
+ c = Int16FromMOTA(*pGlyphOffset);
+ }
+
+ ToReturn = (Int16FromMOTA(idDelta[i]) + c) & 0xFFFF;
+ return ToReturn;
+}
+
+static sal_uInt32 getGlyph12(const sal_uInt8 *pCmap, sal_uInt32, sal_uInt32 cChar) {
+ const sal_uInt32* pCMAP12 = reinterpret_cast<const sal_uInt32*>(pCmap);
+ int nLength = Int32FromMOTA( pCMAP12[1] );
+ int nGroups = Int32FromMOTA( pCMAP12[3] );
+ int nLower = 0;
+ int nUpper = nGroups;
+
+ if( nUpper > (nLength-16)/12 )
+ nUpper = (nLength-16)/12;
+
+ /* binary search in "segmented coverage" subtable */
+ while( nLower < nUpper ) {
+ int nIndex = (nLower + nUpper) / 2;
+ const sal_uInt32* pEntry = &pCMAP12[ 4 + 3*nIndex ];
+ sal_uInt32 cStart = Int32FromMOTA( pEntry[0] );
+ sal_uInt32 cLast = Int32FromMOTA( pEntry[1] );
+ if( cChar < cStart )
+ nUpper = nIndex;
+ else if( cChar > cLast )
+ nLower = nIndex + 1;
+ else { /* found matching entry! */
+ sal_uInt32 nGlyph = Int32FromMOTA( pEntry[2] );
+ nGlyph += cChar - cStart;
+ return nGlyph;
+ }
+ }
+
+ return MISSING_GLYPH_INDEX;
+}
+
+static void FindCmap(TrueTypeFont *ttf)
+{
+ const sal_uInt8* table = getTable(ttf, O_cmap);
+ sal_uInt32 table_size = getTableSize(ttf, O_cmap);
+ if (table_size < 4)
+ {
+ SAL_WARN("vcl.fonts", "Parsing error in " << OUString::createFromAscii(ttf->fname) <<
+ "cmap table size too short");
+ return;
+ }
+ sal_uInt16 ncmaps = GetUInt16(table, 2);
+ sal_uInt32 AppleUni = 0; // Apple Unicode
+ sal_uInt32 ThreeZero = 0; /* MS Symbol */
+ sal_uInt32 ThreeOne = 0; /* MS UCS-2 */
+ sal_uInt32 ThreeTwo = 0; /* MS ShiftJIS */
+ sal_uInt32 ThreeThree = 0; /* MS PRC */
+ sal_uInt32 ThreeFour = 0; /* MS Big5 */
+ sal_uInt32 ThreeFive = 0; /* MS Wansung */
+ sal_uInt32 ThreeSix = 0; /* MS Johab */
+
+ const sal_uInt32 remaining_table_size = table_size-4;
+ const sal_uInt32 nMinRecordSize = 8;
+ const sal_uInt32 nMaxRecords = remaining_table_size / nMinRecordSize;
+ if (ncmaps > nMaxRecords)
+ {
+ SAL_WARN("vcl.fonts", "Parsing error in " << OUString::createFromAscii(ttf->fname) <<
+ ": " << nMaxRecords << " max possible entries, but " <<
+ ncmaps << " claimed, truncating");
+ ncmaps = nMaxRecords;
+ }
+
+ for (unsigned int i = 0; i < ncmaps; i++) {
+ /* sanity check, cmap entry must lie within table */
+ sal_uInt32 nLargestFixedOffsetPos = 8 + i * 8;
+ sal_uInt32 nMinSize = nLargestFixedOffsetPos + sizeof(sal_uInt32);
+ if (nMinSize > table_size)
+ {
+ SAL_WARN( "vcl.fonts", "Font " << OUString::createFromAscii(ttf->fname) << " claimed to have "
+ << ncmaps << " cmaps, but only space for " << i);
+ break;
+ }
+
+ sal_uInt16 pID = GetUInt16(table, 4 + i * 8);
+ sal_uInt16 eID = GetUInt16(table, 6 + i * 8);
+ sal_uInt32 offset = GetUInt32(table, nLargestFixedOffsetPos);
+
+ /* sanity check, cmap must lie within file */
+ if( (table - ttf->ptr) + offset > static_cast<sal_uInt32>(ttf->fsize) )
+ continue;
+
+ /* Unicode tables in Apple fonts */
+ if (pID == 0) {
+ AppleUni = offset;
+ }
+
+ if (pID == 3) {
+ switch (eID) {
+ case 0: ThreeZero = offset; break;
+ case 10: // UCS-4
+ case 1: ThreeOne = offset; break;
+ case 2: ThreeTwo = offset; break;
+ case 3: ThreeThree = offset; break;
+ case 4: ThreeFour = offset; break;
+ case 5: ThreeFive = offset; break;
+ case 6: ThreeSix = offset; break;
+ }
+ }
+ }
+
+ // fall back to AppleUnicode if there are no ThreeOne/Threezero tables
+ if( AppleUni && !ThreeZero && !ThreeOne)
+ ThreeOne = AppleUni;
+
+ if (ThreeOne) {
+ ttf->cmapType = CMAP_MS_Unicode;
+ ttf->cmap = table + ThreeOne;
+ } else if (ThreeTwo) {
+ ttf->cmapType = CMAP_MS_ShiftJIS;
+ ttf->cmap = table + ThreeTwo;
+ } else if (ThreeThree) {
+ ttf->cmapType = CMAP_MS_PRC;
+ ttf->cmap = table + ThreeThree;
+ } else if (ThreeFour) {
+ ttf->cmapType = CMAP_MS_Big5;
+ ttf->cmap = table + ThreeFour;
+ } else if (ThreeFive) {
+ ttf->cmapType = CMAP_MS_Wansung;
+ ttf->cmap = table + ThreeFive;
+ } else if (ThreeSix) {
+ ttf->cmapType = CMAP_MS_Johab;
+ ttf->cmap = table + ThreeSix;
+ } else if (ThreeZero) {
+ ttf->cmapType = CMAP_MS_Symbol;
+ ttf->cmap = table + ThreeZero;
+ } else {
+ ttf->cmapType = CMAP_NOT_USABLE;
+ ttf->cmap = nullptr;
+ }
+
+ if (ttf->cmapType != CMAP_NOT_USABLE) {
+ if( (ttf->cmap - ttf->ptr + 2U) > static_cast<sal_uInt32>(ttf->fsize) ) {
+ ttf->cmapType = CMAP_NOT_USABLE;
+ ttf->cmap = nullptr;
+ }
+ }
+
+ if (ttf->cmapType != CMAP_NOT_USABLE) {
+ switch (GetUInt16(ttf->cmap, 0)) {
+ case 0: ttf->mapper = getGlyph0; break;
+ case 2: ttf->mapper = getGlyph2; break;
+ case 4: ttf->mapper = getGlyph4; break;
+ case 6: ttf->mapper = getGlyph6; break;
+ case 12: ttf->mapper= getGlyph12; break;
+ default:
+#if OSL_DEBUG_LEVEL > 1
+ /*- if the cmap table is really broken */
+ SAL_WARN("vcl.fonts", ttf->fname << ": "
+ << GetUInt16(ttf->cmap, 0)
+ << " is not a recognized cmap format..");
+#endif
+ ttf->cmapType = CMAP_NOT_USABLE;
+ ttf->cmap = nullptr;
+ ttf->mapper = nullptr;
+ }
+ }
+}
+
+/*- Public functions */
+
+int CountTTCFonts(const char* fname)
+{
+ int nFonts = 0;
+ sal_uInt8 buffer[12];
+ FILE* fd = fopen(fname, "rb");
+ if( fd ) {
+ if (fread(buffer, 1, 12, fd) == 12) {
+ if(GetUInt32(buffer, 0) == T_ttcf )
+ nFonts = GetUInt32(buffer, 8);
+ }
+ fclose(fd);
+ }
+ return nFonts;
+}
+
+static void allocTrueTypeFont( TrueTypeFont** ttf )
+{
+ *ttf = static_cast<TrueTypeFont*>(calloc(1,sizeof(TrueTypeFont)));
+ if( *ttf != nullptr )
+ {
+ (*ttf)->fname = nullptr;
+ (*ttf)->fsize = -1;
+ (*ttf)->ptr = nullptr;
+ (*ttf)->nglyphs = 0xFFFFFFFF;
+ }
+}
+
+/* forward declaration for the two entry points to use*/
+static SFErrCodes doOpenTTFont( sal_uInt32 facenum, TrueTypeFont* t );
+
+#if !defined(_WIN32)
+SFErrCodes OpenTTFontFile( const char* fname, sal_uInt32 facenum, TrueTypeFont** ttf )
+{
+ SFErrCodes ret;
+ int fd = -1;
+ struct stat st;
+
+ if (!fname || !*fname) return SFErrCodes::BadFile;
+
+ allocTrueTypeFont( ttf );
+ if( ! *ttf )
+ return SFErrCodes::Memory;
+
+ (*ttf)->fname = strdup(fname);
+ if( ! (*ttf)->fname )
+ {
+ ret = SFErrCodes::Memory;
+ goto cleanup;
+ }
+
+ fd = open(fname, O_RDONLY);
+
+ if (fd == -1) {
+ ret = SFErrCodes::BadFile;
+ goto cleanup;
+ }
+
+ if (fstat(fd, &st) == -1) {
+ ret = SFErrCodes::FileIo;
+ goto cleanup;
+ }
+
+ (*ttf)->fsize = st.st_size;
+
+ /* On Mac OS, most likely will happen if a Mac user renames a font file
+ * to be .ttf when it's really a Mac resource-based font.
+ * Size will be 0, but fonts smaller than 4 bytes would be broken anyway.
+ */
+ if ((*ttf)->fsize == 0) {
+ ret = SFErrCodes::BadFile;
+ goto cleanup;
+ }
+
+ if (((*ttf)->ptr = static_cast<sal_uInt8 *>(mmap(nullptr, (*ttf)->fsize, PROT_READ, MAP_SHARED, fd, 0))) == MAP_FAILED) {
+ ret = SFErrCodes::Memory;
+ goto cleanup;
+ }
+ close(fd);
+
+ return doOpenTTFont( facenum, *ttf );
+
+cleanup:
+ if (fd != -1) close(fd);
+ /*- t and t->fname have been allocated! */
+ free((*ttf)->fname);
+ free(*ttf);
+ *ttf = nullptr;
+ return ret;
+}
+#endif
+
+SFErrCodes OpenTTFontBuffer(const void* pBuffer, sal_uInt32 nLen, sal_uInt32 facenum, TrueTypeFont** ttf)
+{
+ allocTrueTypeFont( ttf );
+ if( *ttf == nullptr )
+ return SFErrCodes::Memory;
+
+ (*ttf)->fname = nullptr;
+ (*ttf)->fsize = nLen;
+ (*ttf)->ptr = const_cast<sal_uInt8 *>(static_cast<sal_uInt8 const *>(pBuffer));
+
+ return doOpenTTFont( facenum, *ttf );
+}
+
+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;
+}
+
+class TTFontCloser
+{
+ TrueTypeFont* m_font;
+public:
+ TTFontCloser(TrueTypeFont* t)
+ : m_font(t)
+ {
+ }
+ void clear() { m_font = nullptr; }
+ ~TTFontCloser()
+ {
+ if (m_font)
+ CloseTTFont(m_font);
+ }
+};
+
+}
+
+static SFErrCodes doOpenTTFont( sal_uInt32 facenum, TrueTypeFont* t )
+{
+ TTFontCloser aCloseGuard(t);
+
+ if (t->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(t->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), t->fsize)) {
+ return SFErrCodes::FontNo;
+ }
+ sal_uInt32 Version = GetUInt32(t->ptr, 4);
+ if (Version != 0x00010000 && Version != 0x00020000) {
+ return SFErrCodes::TtFormat;
+ }
+ if (facenum >= GetUInt32(t->ptr, 8)) {
+ return SFErrCodes::FontNo;
+ }
+ tdoffset = GetUInt32(t->ptr, 12 + 4 * facenum);
+ } else {
+ return SFErrCodes::TtFormat;
+ }
+
+ if (withinBounds(tdoffset, 0, 4 + sizeof(sal_uInt16), t->fsize)) {
+ t->ntables = GetUInt16(t->ptr + tdoffset, 4);
+ }
+
+ if (t->ntables >= 128 || t->ntables == 0) {
+ return SFErrCodes::TtFormat;
+ }
+
+ /* parse the tables */
+ for (i=0; i<static_cast<int>(t->ntables); i++) {
+ int nIndex;
+ const sal_uInt32 nStart = tdoffset + 12;
+ const sal_uInt32 nOffset = 16 * i;
+ if (withinBounds(nStart, nOffset, sizeof(sal_uInt32), t->fsize))
+ tag = GetUInt32(t->ptr + nStart, nOffset);
+ else
+ tag = static_cast<sal_uInt32>(-1);
+ switch( tag ) {
+ case T_maxp: nIndex = O_maxp; break;
+ case T_glyf: nIndex = O_glyf; break;
+ case T_head: nIndex = O_head; break;
+ case T_loca: nIndex = O_loca; break;
+ case T_name: nIndex = O_name; break;
+ case T_hhea: nIndex = O_hhea; break;
+ case T_hmtx: nIndex = O_hmtx; break;
+ case T_cmap: nIndex = O_cmap; break;
+ case T_vhea: nIndex = O_vhea; break;
+ case T_vmtx: nIndex = O_vmtx; break;
+ case T_OS2 : nIndex = O_OS2; break;
+ case T_post: nIndex = O_post; break;
+ case T_cvt : nIndex = O_cvt; break;
+ case T_prep: nIndex = O_prep; break;
+ case T_fpgm: nIndex = O_fpgm; break;
+ case T_gsub: nIndex = O_gsub; break;
+ case T_CFF: nIndex = O_CFF; break;
+ default: nIndex = -1; break;
+ }
+
+ if ((nIndex >= 0) && withinBounds(nStart, nOffset, 12 + sizeof(sal_uInt32), t->fsize)) {
+ sal_uInt32 nTableOffset = GetUInt32(t->ptr + nStart, nOffset + 8);
+ length = GetUInt32(t->ptr + nStart, nOffset + 12);
+ t->tables[nIndex] = t->ptr + nTableOffset;
+ t->tlens[nIndex] = length;
+ }
+ }
+
+ /* Fixup offsets when only a TTC extract was provided */
+ if( facenum == sal_uInt32(~0) ) {
+ sal_uInt8* pHead = const_cast<sal_uInt8*>(t->tables[O_head]);
+ if (!pHead) {
+ return SFErrCodes::TtFormat;
+ }
+ /* limit Head candidate to TTC extract's limits */
+ if( pHead > t->ptr + (t->fsize - 54) )
+ pHead = t->ptr + (t->fsize - 54);
+ /* TODO: find better method than searching head table's magic */
+ sal_uInt8* p = nullptr;
+ for( p = pHead + 12; p > t->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( t->tables[j] )
+ *reinterpret_cast<char const **>(&t->tables[j]) -= nDelta;
+ break;
+ }
+ }
+ if (p <= t->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( t->tables[i] < t->ptr )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_WARN_IF(t->tables[i], "vcl.fonts", "font file " << t->fname
+ << " has bad table offset "
+ << (sal_uInt8*)t->tables[i]-t->ptr
+ << "d (tagnum=" << i << ").");
+#endif
+ t->tlens[i] = 0;
+ t->tables[i] = nullptr;
+ }
+ else if( const_cast<sal_uInt8*>(t->tables[i]) + t->tlens[i] > t->ptr + t->fsize )
+ {
+ sal_PtrDiff nMaxLen = (t->ptr + t->fsize) - t->tables[i];
+ if( nMaxLen < 0 )
+ nMaxLen = 0;
+ t->tlens[i] = nMaxLen;
+#if OSL_DEBUG_LEVEL > 1
+ SAL_WARN("vcl.fonts", "font file " << t->fname
+ << " 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 */
+
+ /* The following tables are absolutely required:
+ * maxp, head, name, cmap
+ */
+
+ if( !(getTable(t, O_maxp) && getTable(t, O_head) && getTable(t, O_name) && getTable(t, O_cmap)) ) {
+ return SFErrCodes::TtFormat;
+ }
+
+ const sal_uInt8* table = getTable(t, O_maxp);
+ sal_uInt32 table_size = getTableSize(t, O_maxp);
+ t->nglyphs = table_size >= 6 ? GetUInt16(table, 4) : 0;
+
+ table = getTable(t, O_head);
+ table_size = getTableSize(t, O_head);
+ if (table_size < HEAD_Length) {
+ return SFErrCodes::TtFormat;
+ }
+ t->unitsPerEm = GetUInt16(table, HEAD_unitsPerEm_offset);
+ int indexfmt = GetInt16(table, HEAD_indexToLocFormat_offset);
+
+ if( ((indexfmt != 0) && (indexfmt != 1)) || (t->unitsPerEm <= 0) ) {
+ return SFErrCodes::TtFormat;
+ }
+
+ if( getTable(t, O_glyf) && getTable(t, O_loca) ) /* TTF or TTF-OpenType */
+ {
+ int k = (getTableSize(t, O_loca) / (indexfmt ? 4 : 2)) - 1;
+ if( k < static_cast<int>(t->nglyphs) ) /* Hack for broken Chinese fonts */
+ t->nglyphs = k;
+
+ table = getTable(t, O_loca);
+ t->goffsets = static_cast<sal_uInt32 *>(calloc(1+t->nglyphs, sizeof(sal_uInt32)));
+ assert(t->goffsets != nullptr);
+
+ for( i = 0; i <= static_cast<int>(t->nglyphs); ++i )
+ t->goffsets[i] = indexfmt ? GetUInt32(table, i << 2) : static_cast<sal_uInt32>(GetUInt16(table, i << 1)) << 1;
+ } else if( getTable(t, O_CFF) ) { /* PS-OpenType */
+ int k = (getTableSize(t, O_CFF) / 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>(t->nglyphs) )
+ t->nglyphs = k;
+ t->goffsets = static_cast<sal_uInt32 *>(calloc(1+t->nglyphs, sizeof(sal_uInt32)));
+ /* TODO: implement to get subsetting */
+ assert(t->goffsets != nullptr);
+ } else {
+ // Bitmap font, accept for now.
+ t->goffsets = static_cast<sal_uInt32 *>(calloc(1+t->nglyphs, sizeof(sal_uInt32)));
+ /* TODO: implement to get subsetting */
+ assert(t->goffsets != nullptr);
+ }
+
+ table = getTable(t, O_hhea);
+ table_size = getTableSize(t, O_hhea);
+ t->numberOfHMetrics = (table && table_size >= 36) ? GetUInt16(table, 34) : 0;
+
+ table = getTable(t, O_vhea);
+ table_size = getTableSize(t, O_vhea);
+ t->numOfLongVerMetrics = (table && table_size >= 36) ? GetUInt16(table, 34) : 0;
+
+ GetNames(t);
+ FindCmap(t);
+
+ aCloseGuard.clear();
+
+ return SFErrCodes::Ok;
+}
+
+void CloseTTFont(TrueTypeFont *ttf)
+{
+#if !defined(_WIN32)
+ if( ttf->fname )
+ munmap(ttf->ptr, ttf->fsize);
+#endif
+ free(ttf->fname);
+ free(ttf->goffsets);
+ free(ttf->psname);
+ free(ttf->family);
+ if( ttf->ufamily )
+ free( ttf->ufamily );
+ free(ttf->subfamily);
+ if( ttf->usubfamily )
+ free( ttf->usubfamily );
+
+ free(ttf);
+}
+
+int GetTTGlyphPoints(TrueTypeFont *ttf, sal_uInt32 glyphID, ControlPoint **pointArray)
+{
+ return GetTTGlyphOutline(ttf, glyphID, pointArray, nullptr, nullptr);
+}
+
+int GetTTGlyphComponents(TrueTypeFont *ttf, sal_uInt32 glyphID, std::vector< sal_uInt32 >& glyphlist)
+{
+ int n = 1;
+
+ if( glyphID >= ttf->nglyphs )
+ return 0;
+
+ const sal_uInt8* glyf = getTable(ttf, O_glyf);
+ const sal_uInt8* ptr = glyf + ttf->goffsets[glyphID];
+ const sal_uInt8* nptr = glyf + ttf->goffsets[glyphID+1];
+ if (nptr <= ptr)
+ return 0;
+
+ glyphlist.push_back( glyphID );
+
+ if (GetInt16(ptr, 0) == -1) {
+ sal_uInt16 flags, index;
+ ptr += 10;
+ do {
+ flags = GetUInt16(ptr, 0);
+ index = GetUInt16(ptr, 2);
+
+ ptr += 4;
+ n += GetTTGlyphComponents(ttf, index, glyphlist);
+
+ if (flags & ARG_1_AND_2_ARE_WORDS) {
+ ptr += 4;
+ } else {
+ ptr += 2;
+ }
+
+ if (flags & WE_HAVE_A_SCALE) {
+ ptr += 2;
+ } else if (flags & WE_HAVE_AN_X_AND_Y_SCALE) {
+ ptr += 4;
+ } else if (flags & WE_HAVE_A_TWO_BY_TWO) {
+ ptr += 8;
+ }
+ } while (flags & MORE_COMPONENTS);
+ }
+
+ return n;
+}
+
+SFErrCodes CreateT3FromTTGlyphs(TrueTypeFont *ttf, FILE *outf, const char *fname,
+ sal_uInt16 const *glyphArray, sal_uInt8 *encoding, int nGlyphs,
+ int wmode)
+{
+ ControlPoint *pa;
+ PSPathElement *path;
+ int i, j, n;
+ const sal_uInt8* table = getTable(ttf, O_head);
+ TTGlyphMetrics metrics;
+ int UPEm = ttf->unitsPerEm;
+
+ const char * const h01 = "%%!PS-AdobeFont-%d.%d-%d.%d\n";
+ const char * const h02 = "%% Creator: %s %s %s\n";
+ const char * const h09 = "%% Original font name: %s\n";
+
+ const char * const h10 =
+ "30 dict begin\n"
+ "/PaintType 0 def\n"
+ "/FontType 3 def\n"
+ "/StrokeWidth 0 def\n";
+
+ const char * const h11 = "/FontName (%s) cvn def\n";
+
+ /*
+ const char *h12 = "%/UniqueID %d def\n";
+ */
+ const char * const h13 = "/FontMatrix [.001 0 0 .001 0 0] def\n";
+ const char * const h14 = "/FontBBox [%d %d %d %d] def\n";
+
+ const char * const h15=
+ "/Encoding 256 array def\n"
+ " 0 1 255 {Encoding exch /.notdef put} for\n";
+
+ const char * const h16 = " Encoding %d /glyph%d put\n";
+ const char * const h17 = "/XUID [103 0 0 16#%08" SAL_PRIXUINT32 " %d 16#%08" SAL_PRIXUINT32 " 16#%08" SAL_PRIXUINT32 "] def\n";
+
+ const char * const h30 = "/CharProcs %d dict def\n";
+ const char * const h31 = " CharProcs begin\n";
+ const char * const h32 = " /.notdef {} def\n";
+ const char * const h33 = " /glyph%d {\n";
+ const char * const h34 = " } bind def\n";
+ const char * const h35 = " end\n";
+
+ const char * const h40 =
+ "/BuildGlyph {\n"
+ " exch /CharProcs get exch\n"
+ " 2 copy known not\n"
+ " {pop /.notdef} if\n"
+ " get exec\n"
+ "} bind def\n"
+ "/BuildChar {\n"
+ " 1 index /Encoding get exch get\n"
+ " 1 index /BuildGlyph get exec\n"
+ "} bind def\n"
+ "currentdict end\n";
+
+ const char * const h41 = "(%s) cvn exch definefont pop\n";
+
+ if (!((nGlyphs > 0) && (nGlyphs <= 256))) return SFErrCodes::GlyphNum;
+ if (!glyphArray) return SFErrCodes::BadArg;
+ if (!fname) fname = ttf->psname;
+
+ fprintf(outf, h01, GetInt16(table, 0), GetUInt16(table, 2), GetInt16(table, 4), GetUInt16(table, 6));
+ fprintf(outf, h02, modname, modver, modextra);
+ fprintf(outf, h09, ttf->psname);
+
+ fprintf(outf, "%s", h10);
+ fprintf(outf, h11, fname);
+/* fprintf(outf, h12, 4000000); */
+
+ /* XUID generation:
+ * 103 0 0 C1 C2 C3 C4
+ * C1 - CRC-32 of the entire source TrueType font
+ * C2 - number of glyphs in the subset
+ * C3 - CRC-32 of the glyph array
+ * C4 - CRC-32 of the encoding array
+ *
+ * All CRC-32 numbers are presented as hexadecimal numbers
+ */
+
+ fprintf(outf, h17, rtl_crc32(0, ttf->ptr, ttf->fsize), nGlyphs, rtl_crc32(0, glyphArray, nGlyphs * 2), rtl_crc32(0, encoding, nGlyphs));
+ fprintf(outf, "%s", h13);
+ fprintf(outf, h14, XUnits(UPEm, GetInt16(table, 36)), XUnits(UPEm, GetInt16(table, 38)), XUnits(UPEm, GetInt16(table, 40)), XUnits(UPEm, GetInt16(table, 42)));
+ fprintf(outf, "%s", h15);
+
+ for (i = 0; i < nGlyphs; i++) {
+ fprintf(outf, h16, encoding[i], i);
+ }
+
+ fprintf(outf, h30, nGlyphs+1);
+ fprintf(outf, "%s", h31);
+ fprintf(outf, "%s", h32);
+
+ for (i = 0; i < nGlyphs; i++) {
+ fprintf(outf, h33, i);
+ int r = GetTTGlyphOutline(ttf, glyphArray[i] < ttf->nglyphs ? glyphArray[i] : 0, &pa, &metrics, nullptr);
+
+ if (r > 0) {
+ n = BSplineToPSPath(pa, r, &path);
+ } else {
+ n = 0; /* glyph might have zero contours but valid metrics ??? */
+ path = nullptr;
+ if (r < 0) { /* glyph is not present in the font - pa array was not allocated, so no need to free it */
+ continue;
+ }
+ }
+ fprintf(outf, "\t%d %d %d %d %d %d setcachedevice\n",
+ wmode == 0 ? XUnits(UPEm, metrics.aw) : 0,
+ wmode == 0 ? 0 : -XUnits(UPEm, metrics.ah),
+ XUnits(UPEm, metrics.xMin),
+ XUnits(UPEm, metrics.yMin),
+ XUnits(UPEm, metrics.xMax),
+ XUnits(UPEm, metrics.yMax));
+
+ for (j = 0; j < n; j++)
+ {
+ switch (path[j].type)
+ {
+ case PS_MOVETO:
+ fprintf(outf, "\t%d %d moveto\n", XUnits(UPEm, path[j].x1), XUnits(UPEm, path[j].y1));
+ break;
+
+ case PS_LINETO:
+ fprintf(outf, "\t%d %d lineto\n", XUnits(UPEm, path[j].x1), XUnits(UPEm, path[j].y1));
+ break;
+
+ case PS_CURVETO:
+ fprintf(outf, "\t%d %d %d %d %d %d curveto\n", XUnits(UPEm, path[j].x1), XUnits(UPEm, path[j].y1), XUnits(UPEm, path[j].x2), XUnits(UPEm, path[j].y2), XUnits(UPEm, path[j].x3), XUnits(UPEm, path[j].y3));
+ break;
+
+ case PS_CLOSEPATH:
+ fprintf(outf, "\tclosepath\n");
+ break;
+ case PS_NOOP:
+ break;
+ }
+ }
+ if (n > 0) fprintf(outf, "\tfill\n"); /* if glyph is not a whitespace character */
+
+ fprintf(outf, "%s", h34);
+
+ free(pa);
+ free(path);
+ }
+ fprintf(outf, "%s", h35);
+
+ fprintf(outf, "%s", h40);
+ fprintf(outf, h41, fname);
+
+ return SFErrCodes::Ok;
+}
+
+SFErrCodes CreateTTFromTTGlyphs(TrueTypeFont *ttf,
+ const char *fname,
+ sal_uInt16 const *glyphArray,
+ sal_uInt8 const *encoding,
+ int nGlyphs)
+{
+ TrueTypeCreator *ttcr;
+ TrueTypeTable *head=nullptr, *hhea=nullptr, *maxp=nullptr, *cvt=nullptr, *prep=nullptr, *glyf=nullptr, *fpgm=nullptr, *cmap=nullptr, *name=nullptr, *post = nullptr, *os2 = nullptr;
+ int i;
+ SFErrCodes res;
+
+ TrueTypeCreatorNewEmpty(T_true, &ttcr);
+
+ /** name **/
+
+ NameRecord *names;
+ int n = GetTTNameRecords(ttf, &names);
+ name = TrueTypeTableNew_name(n, names);
+ DisposeNameRecords(names, n);
+
+ /** maxp **/
+ maxp = TrueTypeTableNew_maxp(getTable(ttf, O_maxp), getTableSize(ttf, O_maxp));
+
+ /** hhea **/
+ const sal_uInt8* p = getTable(ttf, O_hhea);
+ if (p) {
+ hhea = TrueTypeTableNew_hhea(GetInt16(p, HHEA_ascender_offset), GetInt16(p, HHEA_descender_offset), GetInt16(p, HHEA_lineGap_offset), GetInt16(p, HHEA_caretSlopeRise_offset), GetInt16(p, HHEA_caretSlopeRun_offset));
+ } else {
+ hhea = TrueTypeTableNew_hhea(0, 0, 0, 0, 0);
+ }
+
+ /** head **/
+
+ p = getTable(ttf, O_head);
+ assert(p != nullptr);
+ head = TrueTypeTableNew_head(GetInt32(p, HEAD_fontRevision_offset),
+ GetUInt16(p, HEAD_flags_offset),
+ GetUInt16(p, HEAD_unitsPerEm_offset),
+ p+HEAD_created_offset,
+ GetUInt16(p, HEAD_macStyle_offset),
+ GetUInt16(p, HEAD_lowestRecPPEM_offset),
+ GetInt16(p, HEAD_fontDirectionHint_offset));
+
+ /** glyf **/
+
+ glyf = TrueTypeTableNew_glyf();
+ sal_uInt32* gID = static_cast<sal_uInt32*>(scalloc(nGlyphs, sizeof(sal_uInt32)));
+
+ for (i = 0; i < nGlyphs; i++) {
+ gID[i] = glyfAdd(glyf, GetTTRawGlyphData(ttf, glyphArray[i]), ttf);
+ }
+
+ /** cmap **/
+ cmap = TrueTypeTableNew_cmap();
+
+ for (i=0; i < nGlyphs; i++) {
+ cmapAdd(cmap, 0x010000, encoding[i], gID[i]);
+ }
+
+ /** cvt **/
+ if ((p = getTable(ttf, O_cvt)) != nullptr) {
+ cvt = TrueTypeTableNew(T_cvt, getTableSize(ttf, O_cvt), p);
+ }
+
+ /** prep **/
+ if ((p = getTable(ttf, O_prep)) != nullptr) {
+ prep = TrueTypeTableNew(T_prep, getTableSize(ttf, O_prep), p);
+ }
+
+ /** fpgm **/
+ if ((p = getTable(ttf, O_fpgm)) != nullptr) {
+ fpgm = TrueTypeTableNew(T_fpgm, getTableSize(ttf, O_fpgm), p);
+ }
+
+ /** post **/
+ if ((p = getTable(ttf, O_post)) != nullptr) {
+ post = TrueTypeTableNew_post(0x00030000,
+ GetInt32(p, POST_italicAngle_offset),
+ GetInt16(p, POST_underlinePosition_offset),
+ GetInt16(p, POST_underlineThickness_offset),
+ GetUInt32(p, POST_isFixedPitch_offset));
+ } else {
+ post = TrueTypeTableNew_post(0x00030000, 0, 0, 0, 0);
+ }
+
+ AddTable(ttcr, name); AddTable(ttcr, maxp); AddTable(ttcr, hhea);
+ AddTable(ttcr, head); AddTable(ttcr, glyf); AddTable(ttcr, cmap);
+ AddTable(ttcr, cvt ); AddTable(ttcr, prep); AddTable(ttcr, fpgm);
+ AddTable(ttcr, post); AddTable(ttcr, os2);
+
+ res = StreamToFile(ttcr, fname);
+#if OSL_DEBUG_LEVEL > 1
+ SAL_WARN_IF(res != SFErrCodes::Ok, "vcl.fonts", "StreamToFile: error code: "
+ << (int) res << ".");
+#endif
+
+ TrueTypeCreatorDispose(ttcr);
+ free(gID);
+
+ return res;
+}
+
+static GlyphOffsets *GlyphOffsetsNew(sal_uInt8 *sfntP, sal_uInt32 sfntLen)
+{
+ GlyphOffsets* res = static_cast<GlyphOffsets*>(smalloc(sizeof(GlyphOffsets)));
+ sal_uInt8 *loca = nullptr;
+ sal_uInt16 numTables = GetUInt16(sfntP, 4);
+ sal_uInt32 locaLen = 0;
+ sal_Int16 indexToLocFormat = 0;
+
+ sal_uInt32 nMaxPossibleTables = sfntLen / (3*sizeof(sal_uInt32)); /*the three GetUInt32 calls*/
+ if (numTables > nMaxPossibleTables)
+ {
+ SAL_WARN( "vcl.fonts", "GlyphOffsetsNew claimed to have "
+ << numTables << " tables, but that's impossibly large");
+ numTables = nMaxPossibleTables;
+ }
+
+ for (sal_uInt16 i = 0; i < numTables; i++) {
+ sal_uInt32 nLargestFixedOffsetPos = 12 + 16 * i + 12;
+ sal_uInt32 nMinSize = nLargestFixedOffsetPos + sizeof(sal_uInt32);
+ if (nMinSize > sfntLen)
+ {
+ SAL_WARN( "vcl.fonts", "GlyphOffsetsNew claimed to have "
+ << numTables << " tables, but only space for " << i);
+ break;
+ }
+
+ sal_uInt32 tag = GetUInt32(sfntP, 12 + 16 * i);
+ sal_uInt32 off = GetUInt32(sfntP, 12 + 16 * i + 8);
+ sal_uInt32 len = GetUInt32(sfntP, nLargestFixedOffsetPos);
+
+ if (tag == T_loca) {
+ loca = sfntP + off;
+ locaLen = len;
+ } else if (tag == T_head) {
+ indexToLocFormat = GetInt16(sfntP + off, 50);
+ }
+ }
+
+ res->nGlyphs = locaLen / ((indexToLocFormat == 1) ? 4 : 2);
+ assert(res->nGlyphs != 0);
+ res->offs = static_cast<sal_uInt32*>(scalloc(res->nGlyphs, sizeof(sal_uInt32)));
+
+ for (sal_uInt32 i = 0; i < res->nGlyphs; i++) {
+ if (indexToLocFormat == 1) {
+ res->offs[i] = GetUInt32(loca, i * 4);
+ } else {
+ res->offs[i] = GetUInt16(loca, i * 2) << 1;
+ }
+ }
+ return res;
+}
+
+static void GlyphOffsetsDispose(GlyphOffsets *_this)
+{
+ if (_this) {
+ free(_this->offs);
+ free(_this);
+ }
+}
+
+static void DumpSfnts(FILE *outf, sal_uInt8 *sfntP, sal_uInt32 sfntLen)
+{
+ if (sfntLen < 12)
+ {
+ SAL_WARN( "vcl.fonts", "DumpSfnts sfntLen is too short: "
+ << sfntLen << " legal min is: " << 12);
+ return;
+ }
+
+ const sal_uInt32 nSpaceForTables = sfntLen - 12;
+ const sal_uInt32 nTableSize = 16;
+ const sal_uInt32 nMaxPossibleTables = nSpaceForTables/nTableSize;
+
+ HexFmt *h = HexFmtNew(outf);
+ sal_uInt16 i, numTables = GetUInt16(sfntP, 4);
+ GlyphOffsets *go = GlyphOffsetsNew(sfntP, sfntLen);
+ sal_uInt8 const pad[] = {0,0,0,0}; /* zeroes */
+
+ if (numTables > nMaxPossibleTables)
+ {
+ SAL_WARN( "vcl.fonts", "DumpSfnts claimed to have "
+ << numTables << " tables, but only space for " << nMaxPossibleTables);
+ numTables = nMaxPossibleTables;
+ }
+
+ assert(numTables <= 9); /* Type42 has 9 required tables */
+
+ sal_uInt32* offs = static_cast<sal_uInt32*>(scalloc(numTables, sizeof(sal_uInt32)));
+
+ fputs("/sfnts [", outf);
+ HexFmtOpenString(h);
+ HexFmtBlockWrite(h, sfntP, 12); /* stream out the Offset Table */
+ HexFmtBlockWrite(h, sfntP+12, 16 * numTables); /* stream out the Table Directory */
+
+ for (i=0; i<numTables; i++)
+ {
+ sal_uInt32 nLargestFixedOffsetPos = 12 + 16 * i + 12;
+ sal_uInt32 nMinSize = nLargestFixedOffsetPos + sizeof(sal_uInt32);
+ if (nMinSize > sfntLen)
+ {
+ SAL_WARN( "vcl.fonts", "DumpSfnts claimed to have "
+ << numTables << " tables, but only space for " << i);
+ break;
+ }
+
+ sal_uInt32 tag = GetUInt32(sfntP, 12 + 16 * i);
+ sal_uInt32 off = GetUInt32(sfntP, 12 + 16 * i + 8);
+ if (off > sfntLen)
+ {
+ SAL_WARN( "vcl.fonts", "DumpSfnts claims offset of "
+ << off << " but max possible is " << sfntLen);
+ break;
+ }
+ sal_uInt8 *pRecordStart = sfntP + off;
+ sal_uInt32 len = GetUInt32(sfntP, nLargestFixedOffsetPos);
+ sal_uInt32 nMaxLenPossible = sfntLen - off;
+ if (len > nMaxLenPossible)
+ {
+ SAL_WARN( "vcl.fonts", "DumpSfnts claims len of "
+ << len << " but only space for " << nMaxLenPossible);
+ break;
+ }
+
+ if (tag != T_glyf)
+ {
+ HexFmtBlockWrite(h, pRecordStart, len);
+ }
+ else
+ {
+ sal_uInt8 *glyf = pRecordStart;
+ for (sal_uInt32 j = 0; j < go->nGlyphs - 1; j++)
+ {
+ sal_uInt32 o = go->offs[j];
+ sal_uInt32 l = go->offs[j + 1] - o;
+ HexFmtBlockWrite(h, glyf + o, l);
+ }
+ }
+ HexFmtBlockWrite(h, pad, (4 - (len & 3)) & 3);
+ }
+ HexFmtCloseString(h);
+ fputs("] def\n", outf);
+ GlyphOffsetsDispose(go);
+ HexFmtDispose(h);
+ free(offs);
+}
+
+SFErrCodes CreateT42FromTTGlyphs(TrueTypeFont *ttf,
+ FILE *outf,
+ const char *psname,
+ sal_uInt16 const *glyphArray,
+ sal_uInt8 *encoding,
+ int nGlyphs)
+{
+ TrueTypeCreator *ttcr;
+ TrueTypeTable *head=nullptr, *hhea=nullptr, *maxp=nullptr, *cvt=nullptr, *prep=nullptr, *glyf=nullptr, *fpgm=nullptr;
+ int i;
+ SFErrCodes res;
+
+ sal_uInt16 ver;
+ sal_Int32 rev;
+
+ sal_uInt8 *sfntP;
+ sal_uInt32 sfntLen;
+ int UPEm = ttf->unitsPerEm;
+
+ if (nGlyphs >= 256) return SFErrCodes::GlyphNum;
+
+ assert(psname != nullptr);
+
+ TrueTypeCreatorNewEmpty(T_true, &ttcr);
+
+ /* head */
+ const sal_uInt8* p = getTable(ttf, O_head);
+ const sal_uInt8* headP = p;
+ assert(p != nullptr);
+ head = TrueTypeTableNew_head(GetInt32(p, HEAD_fontRevision_offset), GetUInt16(p, HEAD_flags_offset), GetUInt16(p, HEAD_unitsPerEm_offset), p+HEAD_created_offset, GetUInt16(p, HEAD_macStyle_offset), GetUInt16(p, HEAD_lowestRecPPEM_offset), GetInt16(p, HEAD_fontDirectionHint_offset));
+ ver = GetUInt16(p, HEAD_majorVersion_offset);
+ rev = GetInt32(p, HEAD_fontRevision_offset);
+
+ /** hhea **/
+ p = getTable(ttf, O_hhea);
+ if (p) {
+ hhea = TrueTypeTableNew_hhea(GetInt16(p, HHEA_ascender_offset), GetInt16(p, HHEA_descender_offset), GetInt16(p, HHEA_lineGap_offset), GetInt16(p, HHEA_caretSlopeRise_offset), GetInt16(p, HHEA_caretSlopeRun_offset));
+ } else {
+ hhea = TrueTypeTableNew_hhea(0, 0, 0, 0, 0);
+ }
+
+ /** maxp **/
+ maxp = TrueTypeTableNew_maxp(getTable(ttf, O_maxp), getTableSize(ttf, O_maxp));
+
+ /** cvt **/
+ if ((p = getTable(ttf, O_cvt)) != nullptr) {
+ cvt = TrueTypeTableNew(T_cvt, getTableSize(ttf, O_cvt), p);
+ }
+
+ /** prep **/
+ if ((p = getTable(ttf, O_prep)) != nullptr) {
+ prep = TrueTypeTableNew(T_prep, getTableSize(ttf, O_prep), p);
+ }
+
+ /** fpgm **/
+ if ((p = getTable(ttf, O_fpgm)) != nullptr) {
+ fpgm = TrueTypeTableNew(T_fpgm, getTableSize(ttf, O_fpgm), p);
+ }
+
+ /** glyf **/
+ glyf = TrueTypeTableNew_glyf();
+ sal_uInt16* gID = static_cast<sal_uInt16*>(scalloc(nGlyphs, sizeof(sal_uInt32)));
+
+ for (i = 0; i < nGlyphs; i++) {
+ gID[i] = static_cast<sal_uInt16>(glyfAdd(glyf, GetTTRawGlyphData(ttf, glyphArray[i]), ttf));
+ }
+
+ AddTable(ttcr, head); AddTable(ttcr, hhea); AddTable(ttcr, maxp); AddTable(ttcr, cvt);
+ AddTable(ttcr, prep); AddTable(ttcr, glyf); AddTable(ttcr, fpgm);
+
+ if ((res = StreamToMemory(ttcr, &sfntP, &sfntLen)) != SFErrCodes::Ok) {
+ TrueTypeCreatorDispose(ttcr);
+ free(gID);
+ return res;
+ }
+
+ fprintf(outf, "%%!PS-TrueTypeFont-%d.%d-%d.%d\n", static_cast<int>(ver), static_cast<int>(ver & 0xFF), static_cast<int>(rev>>16), static_cast<int>(rev & 0xFFFF));
+ fprintf(outf, "%%%%Creator: %s %s %s\n", modname, modver, modextra);
+ fprintf(outf, "%%- Font subset generated from a source font file: '%s'\n", ttf->fname);
+ fprintf(outf, "%%- Original font name: %s\n", ttf->psname);
+ fprintf(outf, "%%- Original font family: %s\n", ttf->family);
+ fprintf(outf, "%%- Original font sub-family: %s\n", ttf->subfamily);
+ fprintf(outf, "11 dict begin\n");
+ fprintf(outf, "/FontName (%s) cvn def\n", psname);
+ fprintf(outf, "/PaintType 0 def\n");
+ fprintf(outf, "/FontMatrix [1 0 0 1 0 0] def\n");
+ fprintf(outf, "/FontBBox [%d %d %d %d] def\n", XUnits(UPEm, GetInt16(headP, HEAD_xMin_offset)), XUnits(UPEm, GetInt16(headP, HEAD_yMin_offset)), XUnits(UPEm, GetInt16(headP, HEAD_xMax_offset)), XUnits(UPEm, GetInt16(headP, HEAD_yMax_offset)));
+ fprintf(outf, "/FontType 42 def\n");
+ fprintf(outf, "/Encoding 256 array def\n");
+ fprintf(outf, " 0 1 255 {Encoding exch /.notdef put} for\n");
+
+ for (i = 1; i<nGlyphs; i++) {
+ fprintf(outf, "Encoding %d /glyph%u put\n", encoding[i], gID[i]);
+ }
+ fprintf(outf, "/XUID [103 0 1 16#%08X %u 16#%08X 16#%08X] def\n", static_cast<unsigned int>(rtl_crc32(0, ttf->ptr, ttf->fsize)), static_cast<unsigned int>(nGlyphs), static_cast<unsigned int>(rtl_crc32(0, glyphArray, nGlyphs * 2)), static_cast<unsigned int>(rtl_crc32(0, encoding, nGlyphs)));
+
+ DumpSfnts(outf, sfntP, sfntLen);
+
+ /* dump charstrings */
+ fprintf(outf, "/CharStrings %d dict dup begin\n", nGlyphs);
+ fprintf(outf, "/.notdef 0 def\n");
+ for (i = 1; i < static_cast<int>(glyfCount(glyf)); i++) {
+ fprintf(outf,"/glyph%d %d def\n", i, i);
+ }
+ fprintf(outf, "end readonly def\n");
+
+ fprintf(outf, "FontName currentdict end definefont pop\n");
+ TrueTypeCreatorDispose(ttcr);
+ free(gID);
+ free(sfntP);
+ return SFErrCodes::Ok;
+}
+
+#if defined(_WIN32) || defined(MACOSX) || defined(IOS)
+sal_uInt16 MapChar(TrueTypeFont const *ttf, sal_uInt16 ch)
+{
+ switch (ttf->cmapType) {
+ case CMAP_MS_Symbol:
+ {
+ const sal_uInt32 nMaxCmapSize = ttf->ptr + ttf->fsize - ttf->cmap;
+ if( ttf->mapper == getGlyph0 && ( ch & 0xf000 ) == 0xf000 )
+ ch &= 0x00ff;
+ return static_cast<sal_uInt16>(ttf->mapper(ttf->cmap, nMaxCmapSize, ch ));
+ }
+
+ case CMAP_MS_Unicode: break;
+ case CMAP_MS_ShiftJIS: ch = TranslateChar12(ch); break;
+ case CMAP_MS_PRC: ch = TranslateChar13(ch); break;
+ case CMAP_MS_Big5: ch = TranslateChar14(ch); break;
+ case CMAP_MS_Wansung: ch = TranslateChar15(ch); break;
+ case CMAP_MS_Johab: ch = TranslateChar16(ch); break;
+ default: return 0;
+ }
+ const sal_uInt32 nMaxCmapSize = ttf->ptr + ttf->fsize - ttf->cmap;
+ ch = static_cast<sal_uInt16>(ttf->mapper(ttf->cmap, nMaxCmapSize, ch));
+ return ch;
+}
+#endif
+
+
+int GetTTGlyphCount( TrueTypeFont const * ttf )
+{
+ return ttf->nglyphs;
+}
+
+bool GetSfntTable( TrueTypeFont const * ttf, int nSubtableIndex,
+ const sal_uInt8** ppRawBytes, int* pRawLength )
+{
+ if( (nSubtableIndex < 0) || (nSubtableIndex >= NUM_TAGS) )
+ return false;
+ *pRawLength = ttf->tlens[ nSubtableIndex ];
+ *ppRawBytes = ttf->tables[ nSubtableIndex ];
+ bool bOk = (*pRawLength > 0) && (*ppRawBytes != nullptr);
+ return bOk;
+}
+
+std::unique_ptr<sal_uInt16[]> GetTTSimpleGlyphMetrics(TrueTypeFont const *ttf, const sal_uInt16 *glyphArray, int nGlyphs, bool vertical)
+{
+ const sal_uInt8* pTable;
+ sal_uInt32 n;
+ int nTableSize;
+
+ if (!vertical) {
+ n = ttf->numberOfHMetrics;
+ pTable = getTable( ttf, O_hmtx );
+ nTableSize = getTableSize( ttf, O_hmtx );
+ } else {
+ n = ttf->numOfLongVerMetrics;
+ pTable = getTable( ttf, O_vmtx );
+ nTableSize = getTableSize( ttf, O_vmtx );
+ }
+
+ if (!nGlyphs || !glyphArray) return nullptr; /* invalid parameters */
+ if (!n || !pTable) return nullptr; /* the font does not contain the requested metrics */
+
+ std::unique_ptr<sal_uInt16[]> res(new sal_uInt16[nGlyphs]);
+
+ const int UPEm = ttf->unitsPerEm;
+ for( int i = 0; i < nGlyphs; ++i) {
+ int nAdvOffset;
+ sal_uInt16 glyphID = glyphArray[i];
+
+ if (glyphID < n) {
+ nAdvOffset = 4 * glyphID;
+ } else {
+ nAdvOffset = 4 * (n - 1);
+ }
+
+ if( nAdvOffset >= nTableSize)
+ res[i] = 0; /* better than a crash for buggy fonts */
+ else
+ res[i] = static_cast<sal_uInt16>(
+ XUnits( UPEm, GetUInt16( pTable, nAdvOffset) ) );
+ }
+
+ return res;
+}
+
+// TODO, clean up table parsing and re-use it elsewhere in this file.
+void GetTTFontMetrics(const uint8_t *pHhea, size_t nHhea,
+ const uint8_t *pOs2, size_t nOs2,
+ TTGlobalFontInfo *info)
+{
+ /* There are 3 different versions of OS/2 table: original (68 bytes long),
+ * Microsoft old (78 bytes long) and Microsoft new (86 bytes long,)
+ * Apple's documentation recommends looking at the table length.
+ */
+ if (nOs2 >= OS2_V0_length)
+ {
+ info->fsSelection = GetUInt16(pOs2, OS2_fsSelection_offset);
+ info->typoAscender = GetInt16(pOs2, OS2_typoAscender_offset);
+ info->typoDescender = GetInt16(pOs2, OS2_typoDescender_offset);
+ info->typoLineGap = GetInt16(pOs2, OS2_typoLineGap_offset);
+ info->winAscent = GetUInt16(pOs2, OS2_winAscent_offset);
+ info->winDescent = GetUInt16(pOs2, OS2_winDescent_offset);
+ }
+
+ if (nHhea >= HHEA_lineGap_offset + 2) {
+ info->ascender = GetInt16(pHhea, HHEA_ascender_offset);
+ info->descender = GetInt16(pHhea, HHEA_descender_offset);
+ info->linegap = GetInt16(pHhea, HHEA_lineGap_offset);
+ }
+}
+
+void GetTTGlobalFontInfo(TrueTypeFont *ttf, TTGlobalFontInfo *info)
+{
+ int UPEm = ttf->unitsPerEm;
+
+ memset(info, 0, sizeof(TTGlobalFontInfo));
+
+ info->family = ttf->family;
+ info->ufamily = ttf->ufamily;
+ info->subfamily = ttf->subfamily;
+ info->usubfamily = ttf->usubfamily;
+ info->psname = ttf->psname;
+ info->symbolEncoded = (ttf->cmapType == CMAP_MS_Symbol);
+
+ const sal_uInt8* table = getTable(ttf, O_OS2);
+ sal_uInt32 table_size = getTableSize(ttf, O_OS2);
+ if (table && table_size >= 42) {
+ info->weight = GetUInt16(table, OS2_usWeightClass_offset);
+ info->width = GetUInt16(table, OS2_usWidthClass_offset);
+
+ if (table_size >= OS2_V0_length) {
+ 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 = getTable(ttf, O_post);
+ if (table && getTableSize(ttf, O_post) >= 12+sizeof(sal_uInt32)) {
+ info->pitch = GetUInt32(table, POST_isFixedPitch_offset);
+ info->italicAngle = GetInt32(table, POST_italicAngle_offset);
+ }
+
+ table = getTable(ttf, O_head); /* 'head' tables is always there */
+ table_size = getTableSize(ttf, O_head);
+ if (table_size >= 46) {
+ info->xMin = XUnits(UPEm, GetInt16(table, HEAD_xMin_offset));
+ info->yMin = XUnits(UPEm, GetInt16(table, HEAD_yMin_offset));
+ info->xMax = XUnits(UPEm, GetInt16(table, HEAD_xMax_offset));
+ info->yMax = XUnits(UPEm, GetInt16(table, HEAD_yMax_offset));
+ info->macStyle = GetUInt16(table, HEAD_macStyle_offset);
+ }
+
+ table = getTable(ttf, O_hhea);
+ table_size = getTableSize(ttf, O_hhea);
+ if (table && table_size >= 10) {
+ info->ascender = XUnits(UPEm, GetInt16(table, HHEA_ascender_offset));
+ info->descender = XUnits(UPEm, GetInt16(table, HHEA_descender_offset));
+ info->linegap = XUnits(UPEm, GetInt16(table, HHEA_lineGap_offset));
+ }
+}
+
+GlyphData *GetTTRawGlyphData(TrueTypeFont *ttf, sal_uInt32 glyphID)
+{
+ const sal_uInt8* glyf = getTable(ttf, O_glyf);
+ const sal_uInt8* hmtx = getTable(ttf, O_hmtx);
+ int n;
+
+ if( glyphID >= ttf->nglyphs )
+ return nullptr;
+
+ /* #127161# check the glyph offsets */
+ sal_uInt32 length = getTableSize( ttf, O_glyf );
+ if( length < ttf->goffsets[ glyphID+1 ] )
+ return nullptr;
+
+ length = ttf->goffsets[glyphID+1] - ttf->goffsets[glyphID];
+
+ GlyphData* d = static_cast<GlyphData*>(malloc(sizeof(GlyphData))); assert(d != nullptr);
+
+ if (length > 0) {
+ const sal_uInt8* srcptr = glyf + ttf->goffsets[glyphID];
+ const size_t nChunkLen = ((length + 1) & ~1);
+ d->ptr = static_cast<sal_uInt8*>(malloc(nChunkLen)); assert(d->ptr != nullptr);
+ memcpy(d->ptr, srcptr, length);
+ memset(d->ptr + length, 0, nChunkLen - length);
+ d->compflag = (GetInt16( srcptr, 0 ) < 0);
+ } else {
+ d->ptr = nullptr;
+ d->compflag = false;
+ }
+
+ d->glyphID = glyphID;
+ d->nbytes = static_cast<sal_uInt16>((length + 1) & ~1);
+
+ /* now calculate npoints and ncontours */
+ ControlPoint *cp;
+ n = GetTTGlyphPoints(ttf, glyphID, &cp);
+ if (n > 0)
+ {
+ int m = 0;
+ for (int i = 0; i < n; i++)
+ {
+ if (cp[i].flags & 0x8000)
+ m++;
+ }
+ d->npoints = static_cast<sal_uInt16>(n);
+ d->ncontours = static_cast<sal_uInt16>(m);
+ free(cp);
+ } else {
+ d->npoints = 0;
+ d->ncontours = 0;
+ }
+
+ /* get advance width and left sidebearing */
+ if (glyphID < ttf->numberOfHMetrics) {
+ d->aw = GetUInt16(hmtx, 4 * glyphID);
+ d->lsb = GetInt16(hmtx, 4 * glyphID + 2);
+ } else {
+ d->aw = GetUInt16(hmtx, 4 * (ttf->numberOfHMetrics - 1));
+ d->lsb = GetInt16(hmtx + ttf->numberOfHMetrics * 4, (glyphID - ttf->numberOfHMetrics) * 2);
+ }
+
+ return d;
+}
+
+int GetTTNameRecords(TrueTypeFont const *ttf, NameRecord **nr)
+{
+ const sal_uInt8* table = getTable(ttf, O_name);
+ int nTableSize = getTableSize(ttf, O_name );
+
+ if (nTableSize < 6)
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_WARN("vcl.fonts", "O_name table too small.");
+#endif
+ return 0;
+ }
+
+ sal_uInt16 n = GetUInt16(table, 2);
+ int nStrBase = GetUInt16(table, 4);
+ int i;
+
+ *nr = nullptr;
+ if (n == 0) return 0;
+
+ const sal_uInt32 remaining_table_size = nTableSize-6;
+ const sal_uInt32 nMinRecordSize = 12;
+ const sal_uInt32 nMaxRecords = remaining_table_size / nMinRecordSize;
+ if (n > nMaxRecords)
+ {
+ SAL_WARN("vcl.fonts", "Parsing error in " << OUString::createFromAscii(ttf->fname) <<
+ ": " << nMaxRecords << " max possible entries, but " <<
+ n << " claimed, truncating");
+ n = nMaxRecords;
+ }
+
+ NameRecord* rec = static_cast<NameRecord*>(calloc(n, sizeof(NameRecord)));
+ assert(rec);
+
+ for (i = 0; i < n; i++) {
+ int nLargestFixedOffsetPos = 6 + 10 + 12 * i;
+ int nMinSize = nLargestFixedOffsetPos + sizeof(sal_uInt16);
+ if (nMinSize > nTableSize)
+ {
+ SAL_WARN( "vcl.fonts", "Font " << OUString::createFromAscii(ttf->fname) << " claimed to have "
+ << n << " name records, but only space for " << i);
+ n = i;
+ break;
+ }
+
+ rec[i].platformID = GetUInt16(table, 6 + 0 + 12 * i);
+ rec[i].encodingID = GetUInt16(table, 6 + 2 + 12 * i);
+ rec[i].languageID = LanguageType(GetUInt16(table, 6 + 4 + 12 * i));
+ rec[i].nameID = GetUInt16(table, 6 + 6 + 12 * i);
+ rec[i].slen = GetUInt16(table, 6 + 8 + 12 * i);
+ int nStrOffset = GetUInt16(table, nLargestFixedOffsetPos);
+ if (rec[i].slen) {
+ if( nStrBase+nStrOffset+rec[i].slen >= nTableSize ) {
+ rec[i].sptr = nullptr;
+ rec[i].slen = 0;
+ continue;
+ }
+
+ const sal_uInt8* rec_string = table + nStrBase + nStrOffset;
+ // sanity check
+ const sal_uInt8* end_table = ttf->ptr + ttf->fsize;
+ const size_t available_space = rec_string > end_table ? 0 : (end_table - rec_string);
+ if (rec[i].slen <= available_space)
+ {
+ rec[i].sptr = static_cast<sal_uInt8 *>(malloc(rec[i].slen)); assert(rec[i].sptr != nullptr);
+ memcpy(rec[i].sptr, rec_string, rec[i].slen);
+ }
+ else
+ {
+ rec[i].sptr = nullptr;
+ rec[i].slen = 0;
+ }
+ } else {
+ rec[i].sptr = nullptr;
+ }
+ // some fonts have 3.0 names => fix them to 3.1
+ if( (rec[i].platformID == 3) && (rec[i].encodingID == 0) )
+ rec[i].encodingID = 1;
+ }
+
+ *nr = rec;
+ return n;
+}
+
+void DisposeNameRecords(NameRecord* nr, int n)
+{
+ int i;
+ for (i = 0; i < n; i++) {
+ if (nr[i].sptr) free(nr[i].sptr);
+ }
+ free(nr);
+}
+
+template<size_t N> static void
+append(std::bitset<N> & rSet, size_t const nOffset, sal_uInt32 const nValue)
+{
+ for (size_t i = 0; i < 32; ++i)
+ {
+ rSet.set(nOffset + i, (nValue & (1 << 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
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/fontsubset/ttcr.cxx b/vcl/source/fontsubset/ttcr.cxx
new file mode 100644
index 000000000..62690a215
--- /dev/null
+++ b/vcl/source/fontsubset/ttcr.cxx
@@ -0,0 +1,1505 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+/*
+ * TrueTypeCreator method implementation
+ */
+
+#include <iomanip>
+#include <assert.h>
+
+#include <sal/log.hxx>
+
+#include "ttcr.hxx"
+#include "list.h"
+#include <string.h>
+
+namespace vcl
+{
+
+/*
+ * Private Data Types
+ */
+
+ struct TrueTypeCreator {
+ sal_uInt32 tag; /**< TrueType file tag */
+ list tables; /**< List of table tags and pointers */
+ };
+
+namespace {
+
+struct TableEntry {
+ sal_uInt32 tag;
+ sal_uInt32 length;
+ sal_uInt8 *data;
+};
+
+}
+
+/*- Data access macros for data stored in big-endian or little-endian format */
+static sal_Int16 GetInt16( const sal_uInt8* ptr, sal_uInt32 offset)
+{
+ assert(ptr != nullptr);
+ sal_Int16 t = (ptr+offset)[0] << 8 | (ptr+offset)[1];
+ return t;
+}
+
+static sal_uInt16 GetUInt16( const sal_uInt8* ptr, sal_uInt32 offset)
+{
+ assert(ptr != nullptr);
+ sal_uInt16 t = (ptr+offset)[0] << 8 | (ptr+offset)[1];
+ return t;
+}
+
+static void PutInt16(sal_Int16 val, sal_uInt8 *ptr, sal_uInt32 offset)
+{
+ assert(ptr != nullptr);
+
+ ptr[offset] = static_cast<sal_uInt8>((val >> 8) & 0xFF);
+ ptr[offset+1] = static_cast<sal_uInt8>(val & 0xFF);
+}
+
+static void PutUInt16(sal_uInt16 val, sal_uInt8 *ptr, sal_uInt32 offset)
+{
+ assert(ptr != nullptr);
+
+ ptr[offset] = static_cast<sal_uInt8>((val >> 8) & 0xFF);
+ ptr[offset+1] = static_cast<sal_uInt8>(val & 0xFF);
+}
+
+static void PutUInt32(sal_uInt32 val, sal_uInt8 *ptr, sal_uInt32 offset)
+{
+ assert(ptr != nullptr);
+
+ ptr[offset] = static_cast<sal_uInt8>((val >> 24) & 0xFF);
+ ptr[offset+1] = static_cast<sal_uInt8>((val >> 16) & 0xFF);
+ ptr[offset+2] = static_cast<sal_uInt8>((val >> 8) & 0xFF);
+ ptr[offset+3] = static_cast<sal_uInt8>(val & 0xFF);
+}
+
+static int TableEntryCompareF(const void *l, const void *r)
+{
+ sal_uInt32 const ltag(static_cast<TableEntry const*>(l)->tag);
+ sal_uInt32 const rtag(static_cast<TableEntry const*>(r)->tag);
+ return (ltag == rtag) ? 0 : (ltag < rtag) ? -1 : 1;
+}
+
+static int NameRecordCompareF(const void *l, const void *r)
+{
+ NameRecord const *ll = static_cast<NameRecord const *>(l);
+ NameRecord const *rr = static_cast<NameRecord const *>(r);
+
+ if (ll->platformID != rr->platformID) {
+ return (ll->platformID < rr->platformID) ? -1 : 1;
+ } else if (ll->encodingID != rr->encodingID) {
+ return (ll->encodingID < rr->encodingID) ? -1 : 1;
+ } else if (ll->languageID != rr->languageID) {
+ return (ll->languageID < rr->languageID) ? -1 : 1;
+ } else if (ll->nameID != rr->nameID) {
+ return (ll->nameID < rr->nameID) ? -1 : 1;
+ }
+ return 0;
+}
+
+static sal_uInt32 CheckSum(sal_uInt32 *ptr, sal_uInt32 length)
+{
+ sal_uInt32 sum = 0;
+ sal_uInt32 *endptr = ptr + ((length + 3) & sal_uInt32(~3)) / 4;
+
+ while (ptr < endptr) sum += *ptr++;
+
+ return sum;
+}
+
+static void *smalloc(sal_uInt32 size)
+{
+ void *res = malloc(size);
+ assert(res != nullptr);
+ return res;
+}
+
+static void *scalloc(sal_uInt32 n, sal_uInt32 size)
+{
+ void *res = calloc(n, size);
+ assert(res != nullptr);
+ return res;
+}
+
+/*
+ * Public functions
+ */
+
+void TrueTypeCreatorNewEmpty(sal_uInt32 tag, TrueTypeCreator **_this)
+{
+ TrueTypeCreator* ptr = static_cast<TrueTypeCreator*>(smalloc(sizeof(TrueTypeCreator)));
+
+ ptr->tables = listNewEmpty();
+ listSetElementDtor(ptr->tables, TrueTypeTableDispose);
+
+ ptr->tag = tag;
+
+ *_this = ptr;
+}
+
+void AddTable(TrueTypeCreator *_this, TrueTypeTable *table)
+{
+ if (table != nullptr) {
+ listAppend(_this->tables, table);
+ }
+}
+
+void RemoveTable(TrueTypeCreator *_this, sal_uInt32 tag)
+{
+ if (listCount(_this->tables))
+ {
+ listToFirst(_this->tables);
+ int done = 0;
+ do {
+ if (static_cast<TrueTypeTable *>(listCurrent(_this->tables))->tag == tag)
+ {
+ listRemove(_this->tables);
+ }
+ else
+ {
+ if (listNext(_this->tables))
+ {
+ done = 1;
+ }
+ }
+ } while (!done);
+ }
+}
+
+static void ProcessTables(TrueTypeCreator *);
+
+SFErrCodes StreamToMemory(TrueTypeCreator *_this, sal_uInt8 **ptr, sal_uInt32 *length)
+{
+ sal_uInt16 searchRange=1, entrySelector=0, rangeShift;
+ sal_uInt32 s, offset, checkSumAdjustment = 0;
+ sal_uInt32 *p;
+ sal_uInt8 *head = nullptr; /* saved pointer to the head table data for checkSumAdjustment calculation */
+
+ if (listIsEmpty(_this->tables)) return SFErrCodes::TtFormat;
+
+ ProcessTables(_this);
+
+ /* ProcessTables() adds 'loca' and 'hmtx' */
+
+ sal_uInt16 numTables = listCount(_this->tables);
+
+ TableEntry* te = static_cast<TableEntry*>(scalloc(numTables, sizeof(TableEntry)));
+ TableEntry* e = te;
+
+ listToFirst(_this->tables);
+ do {
+ GetRawData(static_cast<TrueTypeTable *>(listCurrent(_this->tables)), &e->data, &e->length, &e->tag);
+ ++e;
+ } while (listNext(_this->tables));
+
+ qsort(te, numTables, sizeof(TableEntry), TableEntryCompareF);
+
+ do {
+ searchRange *= 2;
+ entrySelector++;
+ } while (searchRange <= numTables);
+
+ searchRange *= 8;
+ entrySelector--;
+ rangeShift = numTables * 16 - searchRange;
+
+ s = offset = 12 + 16 * numTables;
+
+ for (int i = 0; i < numTables; ++i) {
+ s += (te[i].length + 3) & sal_uInt32(~3);
+ /* if ((te[i].length & 3) != 0) s += (4 - (te[i].length & 3)) & 3; */
+ }
+
+ sal_uInt8* ttf = static_cast<sal_uInt8*>(smalloc(s));
+
+ /* Offset Table */
+ PutUInt32(_this->tag, ttf, 0);
+ PutUInt16(numTables, ttf, 4);
+ PutUInt16(searchRange, ttf, 6);
+ PutUInt16(entrySelector, ttf, 8);
+ PutUInt16(rangeShift, ttf, 10);
+
+ /* Table Directory */
+ for (int i = 0; i < numTables; ++i) {
+ PutUInt32(te[i].tag, ttf + 12, 16 * i);
+ PutUInt32(CheckSum(reinterpret_cast<sal_uInt32 *>(te[i].data), te[i].length), ttf + 12, 16 * i + 4);
+ PutUInt32(offset, ttf + 12, 16 * i + 8);
+ PutUInt32(te[i].length, ttf + 12, 16 * i + 12);
+
+ if (te[i].tag == T_head) {
+ head = ttf + offset;
+ }
+
+ memcpy(ttf+offset, te[i].data, (te[i].length + 3) & sal_uInt32(~3) );
+ offset += (te[i].length + 3) & sal_uInt32(~3);
+ /* if ((te[i].length & 3) != 0) offset += (4 - (te[i].length & 3)) & 3; */
+ }
+
+ free(te);
+
+ p = reinterpret_cast<sal_uInt32 *>(ttf);
+ for (int i = 0; i < static_cast<int>(s) / 4; ++i) checkSumAdjustment += p[i];
+ PutUInt32(0xB1B0AFBA - checkSumAdjustment, head, 8);
+
+ *ptr = ttf;
+ *length = s;
+
+ return SFErrCodes::Ok;
+}
+
+SFErrCodes StreamToFile(TrueTypeCreator *_this, const char* fname)
+{
+ sal_uInt8 *ptr;
+ sal_uInt32 length;
+ SFErrCodes r;
+ FILE* fd;
+
+ if ((r = StreamToMemory(_this, &ptr, &length)) != SFErrCodes::Ok) return r;
+ r = SFErrCodes::BadFile;
+ if (fname)
+ {
+ fd = fopen(fname, "wb");
+ if (fd)
+ {
+ if (fwrite(ptr, 1, length, fd) != length) {
+ r = SFErrCodes::FileIo;
+ } else {
+ r = SFErrCodes::Ok;
+ }
+ fclose(fd);
+ }
+ }
+ free(ptr);
+ return r;
+}
+
+/*
+ * TrueTypeTable private methods
+ */
+
+/* Table data points to
+ * --------------------------------------------
+ * generic tdata_generic struct
+ * 'head' HEAD_Length bytes of memory
+ * 'hhea' HHEA_Length bytes of memory
+ * 'loca' tdata_loca struct
+ * 'maxp' MAXP_Version1Length bytes of memory
+ * 'glyf' list of GlyphData structs (defined in sft.h)
+ * 'name' list of NameRecord structs (defined in sft.h)
+ * 'post' tdata_post struct
+ *
+ */
+
+#define CMAP_SUBTABLE_INIT 10
+#define CMAP_SUBTABLE_INCR 10
+#define CMAP_PAIR_INIT 500
+#define CMAP_PAIR_INCR 500
+
+namespace {
+
+struct CmapSubTable {
+ sal_uInt32 id; /* subtable ID (platform/encoding ID) */
+ sal_uInt32 n; /* number of used translation pairs */
+ sal_uInt32 m; /* number of allocated translation pairs */
+ sal_uInt32 *xc; /* character array */
+ sal_uInt32 *xg; /* glyph array */
+};
+
+struct table_cmap {
+ sal_uInt32 n; /* number of used CMAP sub-tables */
+ sal_uInt32 m; /* number of allocated CMAP sub-tables */
+ CmapSubTable *s; /* sorted array of sub-tables */
+};
+
+struct tdata_generic {
+ sal_uInt32 tag;
+ sal_uInt32 nbytes;
+ sal_uInt8 *ptr;
+};
+
+struct tdata_loca {
+ sal_uInt32 nbytes; /* number of bytes in loca table */
+ sal_uInt8 *ptr; /* pointer to the data */
+};
+
+struct tdata_post {
+ sal_uInt32 format;
+ sal_uInt32 italicAngle;
+ sal_Int16 underlinePosition;
+ sal_Int16 underlineThickness;
+ sal_uInt32 isFixedPitch;
+ void *ptr; /* format-specific pointer */
+};
+
+}
+
+/* allocate memory for a TT table */
+static sal_uInt8 *ttmalloc(sal_uInt32 nbytes)
+{
+ sal_uInt32 n;
+
+ n = (nbytes + 3) & sal_uInt32(~3);
+ sal_uInt8* res = static_cast<sal_uInt8*>(calloc(n, 1));
+ assert(res != nullptr);
+
+ return res;
+}
+
+static void FreeGlyphData(void *ptr)
+{
+ GlyphData *p = static_cast<GlyphData *>(ptr);
+ if (p->ptr) free(p->ptr);
+ free(p);
+}
+
+static void TrueTypeTableDispose_generic(TrueTypeTable *_this)
+{
+ if (_this) {
+ if (_this->data) {
+ tdata_generic *pdata = static_cast<tdata_generic *>(_this->data);
+ if (pdata->nbytes) free(pdata->ptr);
+ free(_this->data);
+ }
+ free(_this);
+ }
+}
+
+static void TrueTypeTableDispose_head(TrueTypeTable *_this)
+{
+ if (_this) {
+ if (_this->data) free(_this->data);
+ free(_this);
+ }
+}
+
+static void TrueTypeTableDispose_hhea(TrueTypeTable *_this)
+{
+ if (_this) {
+ if (_this->data) free(_this->data);
+ free(_this);
+ }
+}
+
+static void TrueTypeTableDispose_loca(TrueTypeTable *_this)
+{
+ if (_this) {
+ if (_this->data) {
+ tdata_loca *p = static_cast<tdata_loca *>(_this->data);
+ if (p->ptr) free(p->ptr);
+ free(_this->data);
+ }
+ free(_this);
+ }
+}
+
+static void TrueTypeTableDispose_maxp(TrueTypeTable *_this)
+{
+ if (_this) {
+ if (_this->data) free(_this->data);
+ free(_this);
+ }
+}
+
+static void TrueTypeTableDispose_glyf(TrueTypeTable *_this)
+{
+ if (_this) {
+ if (_this->data) listDispose(static_cast<list>(_this->data));
+ free(_this);
+ }
+}
+
+static void TrueTypeTableDispose_cmap(TrueTypeTable *_this)
+{
+ if (_this) {
+ table_cmap *t = static_cast<table_cmap *>(_this->data);
+ if (t) {
+ CmapSubTable *s = t->s;
+ if (s) {
+ for (sal_uInt32 i = 0; i < t->m; i++) {
+ if (s[i].xc) free(s[i].xc);
+ if (s[i].xg) free(s[i].xg);
+ }
+ free(s);
+ }
+ free(t);
+ }
+ free(_this);
+ }
+}
+
+static void TrueTypeTableDispose_name(TrueTypeTable *_this)
+{
+ if (_this) {
+ if (_this->data) listDispose(static_cast<list>(_this->data));
+ free(_this);
+ }
+}
+
+static void TrueTypeTableDispose_post(TrueTypeTable *_this)
+{
+ if (_this) {
+ tdata_post *p = static_cast<tdata_post *>(_this->data);
+ if (p) {
+ if (p->format == 0x00030000) {
+ /* do nothing */
+ } else {
+ SAL_WARN("vcl.fonts", "Unsupported format of a 'post' table: "
+ << std::setfill('0')
+ << std::setw(8)
+ << std::hex
+ << std::uppercase
+ << static_cast<int>(p->format) << ".");
+ }
+ free(p);
+ }
+ free(_this);
+ }
+}
+
+/* destructor vtable */
+
+static struct {
+ sal_uInt32 tag;
+ void (*f)(TrueTypeTable *);
+} const vtable1[] =
+{
+ {0, TrueTypeTableDispose_generic},
+ {T_head, TrueTypeTableDispose_head},
+ {T_hhea, TrueTypeTableDispose_hhea},
+ {T_loca, TrueTypeTableDispose_loca},
+ {T_maxp, TrueTypeTableDispose_maxp},
+ {T_glyf, TrueTypeTableDispose_glyf},
+ {T_cmap, TrueTypeTableDispose_cmap},
+ {T_name, TrueTypeTableDispose_name},
+ {T_post, TrueTypeTableDispose_post}
+
+};
+
+static int GetRawData_generic(TrueTypeTable *_this, sal_uInt8 **ptr, sal_uInt32 *len, sal_uInt32 *tag)
+{
+ assert(_this != nullptr);
+ assert(_this->data != nullptr);
+
+ *ptr = static_cast<tdata_generic *>(_this->data)->ptr;
+ *len = static_cast<tdata_generic *>(_this->data)->nbytes;
+ *tag = static_cast<tdata_generic *>(_this->data)->tag;
+
+ return TTCR_OK;
+}
+
+static int GetRawData_head(TrueTypeTable *_this, sal_uInt8 **ptr, sal_uInt32 *len, sal_uInt32 *tag)
+{
+ *len = HEAD_Length;
+ *ptr = static_cast<sal_uInt8 *>(_this->data);
+ *tag = T_head;
+
+ return TTCR_OK;
+}
+
+static int GetRawData_hhea(TrueTypeTable *_this, sal_uInt8 **ptr, sal_uInt32 *len, sal_uInt32 *tag)
+{
+ *len = HHEA_Length;
+ *ptr = static_cast<sal_uInt8 *>(_this->data);
+ *tag = T_hhea;
+
+ return TTCR_OK;
+}
+
+static int GetRawData_loca(TrueTypeTable *_this, sal_uInt8 **ptr, sal_uInt32 *len, sal_uInt32 *tag)
+{
+ tdata_loca *p;
+
+ assert(_this->data != nullptr);
+
+ p = static_cast<tdata_loca *>(_this->data);
+
+ if (p->nbytes == 0) return TTCR_ZEROGLYPHS;
+
+ *ptr = p->ptr;
+ *len = p->nbytes;
+ *tag = T_loca;
+
+ return TTCR_OK;
+}
+
+static int GetRawData_maxp(TrueTypeTable *_this, sal_uInt8 **ptr, sal_uInt32 *len, sal_uInt32 *tag)
+{
+ *len = MAXP_Version1Length;
+ *ptr = static_cast<sal_uInt8 *>(_this->data);
+ *tag = T_maxp;
+
+ return TTCR_OK;
+}
+
+static int GetRawData_glyf(TrueTypeTable *_this, sal_uInt8 **ptr, sal_uInt32 *len, sal_uInt32 *tag)
+{
+ sal_uInt32 n, nbytes = 0;
+ list l = static_cast<list>(_this->data);
+ /* sal_uInt16 curID = 0; */ /* to check if glyph IDs are sequential and start from zero */
+ sal_uInt8 *p;
+
+ *ptr = nullptr;
+ *len = 0;
+ *tag = 0;
+
+ if (listCount(l) == 0) return TTCR_ZEROGLYPHS;
+
+ listToFirst(l);
+ do {
+ /* if (((GlyphData *) listCurrent(l))->glyphID != curID++) return TTCR_GLYPHSEQ; */
+ nbytes += static_cast<GlyphData *>(listCurrent(l))->nbytes;
+ } while (listNext(l));
+
+ p = _this->rawdata = ttmalloc(nbytes);
+
+ listToFirst(l);
+ do {
+ n = static_cast<GlyphData *>(listCurrent(l))->nbytes;
+ if (n != 0) {
+ memcpy(p, static_cast<GlyphData *>(listCurrent(l))->ptr, n);
+ p += n;
+ }
+ } while (listNext(l));
+
+ *len = nbytes;
+ *ptr = _this->rawdata;
+ *tag = T_glyf;
+
+ return TTCR_OK;
+}
+
+/* cmap packers */
+static sal_uInt8 *PackCmapType0(CmapSubTable const *s, sal_uInt32 *length)
+{
+ sal_uInt8* ptr = static_cast<sal_uInt8*>(smalloc(262));
+ sal_uInt8 *p = ptr + 6;
+ sal_uInt32 i, j;
+ sal_uInt16 g;
+
+ PutUInt16(0, ptr, 0);
+ PutUInt16(262, ptr, 2);
+ PutUInt16(0, ptr, 4);
+
+ for (i = 0; i < 256; i++) {
+ g = 0;
+ for (j = 0; j < s->n; j++) {
+ if (s->xc[j] == i) {
+ g = static_cast<sal_uInt16>(s->xg[j]);
+ }
+ }
+ p[i] = static_cast<sal_uInt8>(g);
+ }
+ *length = 262;
+ return ptr;
+}
+
+static sal_uInt8 *PackCmapType6(CmapSubTable const *s, sal_uInt32 *length)
+{
+ sal_uInt8* ptr = static_cast<sal_uInt8*>(smalloc(s->n*2 + 10));
+ sal_uInt8 *p = ptr + 10;
+ sal_uInt32 i, j;
+ sal_uInt16 g;
+
+ PutUInt16(6, ptr, 0);
+ PutUInt16(static_cast<sal_uInt16>(s->n*2+10), ptr, 2);
+ PutUInt16(0, ptr, 4);
+ PutUInt16(0, ptr, 6);
+ PutUInt16(static_cast<sal_uInt16>(s->n), ptr, 8 );
+
+ for (i = 0; i < s->n; i++) {
+ g = 0;
+ for (j = 0; j < s->n; j++) {
+ if (s->xc[j] == i) {
+ g = static_cast<sal_uInt16>(s->xg[j]);
+ }
+ }
+ PutUInt16( g, p, 2*i );
+ }
+ *length = s->n*2+10;
+ return ptr;
+}
+
+/* XXX it only handles Format 0 encoding tables */
+static sal_uInt8 *PackCmap(CmapSubTable const *s, sal_uInt32 *length)
+{
+ if( s->xg[s->n-1] > 0xff )
+ return PackCmapType6(s, length);
+ else
+ return PackCmapType0(s, length);
+}
+
+static int GetRawData_cmap(TrueTypeTable *_this, sal_uInt8 **ptr, sal_uInt32 *len, sal_uInt32 *tag)
+{
+ table_cmap *t;
+ sal_uInt32 i;
+ sal_uInt32 tlen = 0;
+ sal_uInt32 l;
+ sal_uInt32 cmapsize;
+ sal_uInt8 *cmap;
+ sal_uInt32 coffset;
+
+ assert(_this != nullptr);
+ t = static_cast<table_cmap *>(_this->data);
+ assert(t != nullptr);
+ assert(t->n != 0);
+
+ sal_uInt8** subtables = static_cast<sal_uInt8**>(scalloc(t->n, sizeof(sal_uInt8 *)));
+ sal_uInt32* sizes = static_cast<sal_uInt32*>(scalloc(t->n, sizeof(sal_uInt32)));
+
+ for (i = 0; i < t->n; i++) {
+ subtables[i] = PackCmap(t->s+i, &l);
+ sizes[i] = l;
+ tlen += l;
+ }
+
+ cmapsize = tlen + 4 + 8 * t->n;
+ _this->rawdata = cmap = ttmalloc(cmapsize);
+
+ PutUInt16(0, cmap, 0);
+ PutUInt16(static_cast<sal_uInt16>(t->n), cmap, 2);
+ coffset = 4 + t->n * 8;
+
+ for (i = 0; i < t->n; i++) {
+ PutUInt16(static_cast<sal_uInt16>(t->s[i].id >> 16), cmap + 4, i * 8);
+ PutUInt16(static_cast<sal_uInt16>(t->s[i].id & 0xFF), cmap + 4, 2 + i * 8);
+ PutUInt32(coffset, cmap + 4, 4 + i * 8);
+ memcpy(cmap + coffset, subtables[i], sizes[i]);
+ free(subtables[i]);
+ coffset += sizes[i];
+ }
+
+ free(subtables);
+ free(sizes);
+
+ *ptr = cmap;
+ *len = cmapsize;
+ *tag = T_cmap;
+
+ return TTCR_OK;
+}
+
+static int GetRawData_name(TrueTypeTable *_this, sal_uInt8 **ptr, sal_uInt32 *len, sal_uInt32 *tag)
+{
+ list l;
+ sal_Int16 i=0, n; /* number of Name Records */
+ int stringLen = 0;
+ sal_uInt8 *p1, *p2;
+
+ *ptr = nullptr;
+ *len = 0;
+ *tag = 0;
+
+ assert(_this != nullptr);
+ l = static_cast<list>(_this->data);
+ assert(l != nullptr);
+
+ if ((n = static_cast<sal_Int16>(listCount(l))) == 0) return TTCR_NONAMES;
+
+ NameRecord* nr = static_cast<NameRecord*>(scalloc(n, sizeof(NameRecord)));
+
+ listToFirst(l);
+
+ do {
+ memcpy(nr+i, listCurrent(l), sizeof(NameRecord));
+ stringLen += nr[i].slen;
+ i++;
+ } while (listNext(l));
+
+ if (stringLen > 65535) {
+ free(nr);
+ return TTCR_NAMETOOLONG;
+ }
+
+ qsort(nr, n, sizeof(NameRecord), NameRecordCompareF);
+
+ int nameLen = stringLen + 12 * n + 6;
+ sal_uInt8* name = ttmalloc(nameLen);
+
+ PutUInt16(0, name, 0);
+ PutUInt16(n, name, 2);
+ PutUInt16(static_cast<sal_uInt16>(6 + 12 * n), name, 4);
+
+ p1 = name + 6;
+ p2 = p1 + 12 * n;
+
+ for (i = 0; i < n; i++) {
+ PutUInt16(nr[i].platformID, p1, 0);
+ PutUInt16(nr[i].encodingID, p1, 2);
+ PutUInt16(static_cast<sal_uInt16>(nr[i].languageID), p1, 4);
+ PutUInt16(nr[i].nameID, p1, 6);
+ PutUInt16(nr[i].slen, p1, 8);
+ PutUInt16(static_cast<sal_uInt16>(p2 - (name + 6 + 12 * n)), p1, 10);
+ if (nr[i].slen) {
+ memcpy(p2, nr[i].sptr, nr[i].slen);
+ }
+ /* {int j; for(j=0; j<nr[i].slen; j++) printf("%c", nr[i].sptr[j]); printf("\n"); }; */
+ p2 += nr[i].slen;
+ p1 += 12;
+ }
+
+ free(nr);
+ _this->rawdata = name;
+
+ *ptr = name;
+ *len = static_cast<sal_uInt16>(nameLen);
+ *tag = T_name;
+
+ /*{int j; for(j=0; j<nameLen; j++) printf("%c", name[j]); }; */
+
+ return TTCR_OK;
+}
+
+static int GetRawData_post(TrueTypeTable *_this, sal_uInt8 **ptr, sal_uInt32 *len, sal_uInt32 *tag)
+{
+ tdata_post *p = static_cast<tdata_post *>(_this->data);
+ sal_uInt8 *post = nullptr;
+ sal_uInt32 postLen = 0;
+ int ret;
+
+ if (_this->rawdata) free(_this->rawdata);
+
+ if (p->format == 0x00030000) {
+ postLen = 32;
+ post = ttmalloc(postLen);
+ PutUInt32(0x00030000, post, 0);
+ PutUInt32(p->italicAngle, post, 4);
+ PutUInt16(p->underlinePosition, post, 8);
+ PutUInt16(p->underlineThickness, post, 10);
+ PutUInt16(static_cast<sal_uInt16>(p->isFixedPitch), post, 12);
+ ret = TTCR_OK;
+ } else {
+ SAL_WARN("vcl.fonts", "Unrecognized format of a post table: "
+ << std::setfill('0')
+ << std::setw(8)
+ << std::hex
+ << std::uppercase
+ << static_cast<int>(p->format) << ".");
+ ret = TTCR_POSTFORMAT;
+ }
+
+ *ptr = _this->rawdata = post;
+ *len = postLen;
+ *tag = T_post;
+
+ return ret;
+}
+
+static struct {
+ sal_uInt32 tag;
+ int (*f)(TrueTypeTable *, sal_uInt8 **, sal_uInt32 *, sal_uInt32 *);
+} const vtable2[] =
+{
+ {0, GetRawData_generic},
+ {T_head, GetRawData_head},
+ {T_hhea, GetRawData_hhea},
+ {T_loca, GetRawData_loca},
+ {T_maxp, GetRawData_maxp},
+ {T_glyf, GetRawData_glyf},
+ {T_cmap, GetRawData_cmap},
+ {T_name, GetRawData_name},
+ {T_post, GetRawData_post}
+
+};
+
+/*
+ * TrueTypeTable public methods
+ */
+
+/* Note: Type42 fonts only need these tables:
+ * head, hhea, loca, maxp, cvt, prep, glyf, hmtx, fpgm
+ *
+ * Microsoft required tables
+ * cmap, glyf, head, hhea, hmtx, loca, maxp, name, post, OS/2
+ *
+ * Apple required tables
+ * cmap, glyf, head, hhea, hmtx, loca, maxp, name, post
+ *
+ */
+
+TrueTypeTable *TrueTypeTableNew(sal_uInt32 tag,
+ sal_uInt32 nbytes,
+ const sal_uInt8* ptr)
+{
+ TrueTypeTable* table = static_cast<TrueTypeTable*>(smalloc(sizeof(TrueTypeTable)));
+ tdata_generic* pdata = static_cast<tdata_generic*>(smalloc(sizeof(tdata_generic)));
+ pdata->nbytes = nbytes;
+ pdata->tag = tag;
+ if (nbytes) {
+ pdata->ptr = ttmalloc(nbytes);
+ memcpy(pdata->ptr, ptr, nbytes);
+ } else {
+ pdata->ptr = nullptr;
+ }
+
+ table->tag = 0;
+ table->data = pdata;
+ table->rawdata = nullptr;
+
+ return table;
+}
+
+TrueTypeTable *TrueTypeTableNew_head(sal_uInt32 fontRevision,
+ sal_uInt16 flags,
+ sal_uInt16 unitsPerEm,
+ const sal_uInt8* created,
+ sal_uInt16 macStyle,
+ sal_uInt16 lowestRecPPEM,
+ sal_Int16 fontDirectionHint)
+{
+ assert(created != nullptr);
+
+ TrueTypeTable* table = static_cast<TrueTypeTable*>(smalloc(sizeof(TrueTypeTable)));
+ sal_uInt8* ptr = ttmalloc(HEAD_Length);
+
+ PutUInt32(0x00010000, ptr, 0); /* version */
+ PutUInt32(fontRevision, ptr, 4);
+ PutUInt32(0x5F0F3CF5, ptr, 12); /* magic number */
+ PutUInt16(flags, ptr, 16);
+ PutUInt16(unitsPerEm, ptr, 18);
+ memcpy(ptr+20, created, 8); /* Created Long Date */
+ memset(ptr+28, 0, 8); /* Modified Long Date */
+ PutUInt16(macStyle, ptr, 44);
+ PutUInt16(lowestRecPPEM, ptr, 46);
+ PutUInt16(fontDirectionHint, ptr, 48);
+ PutUInt16(0, ptr, 52); /* glyph data format: 0 */
+
+ table->data = static_cast<void *>(ptr);
+ table->tag = T_head;
+ table->rawdata = nullptr;
+
+ return table;
+}
+
+TrueTypeTable *TrueTypeTableNew_hhea(sal_Int16 ascender,
+ sal_Int16 descender,
+ sal_Int16 linegap,
+ sal_Int16 caretSlopeRise,
+ sal_Int16 caretSlopeRun)
+{
+ TrueTypeTable* table = static_cast<TrueTypeTable*>(smalloc(sizeof(TrueTypeTable)));
+ sal_uInt8* ptr = ttmalloc(HHEA_Length);
+
+ PutUInt32(0x00010000, ptr, 0); /* version */
+ PutUInt16(ascender, ptr, 4);
+ PutUInt16(descender, ptr, 6);
+ PutUInt16(linegap, ptr, 8);
+ PutUInt16(caretSlopeRise, ptr, 18);
+ PutUInt16(caretSlopeRun, ptr, 20);
+ PutUInt16(0, ptr, 22); /* reserved 1 */
+ PutUInt16(0, ptr, 24); /* reserved 2 */
+ PutUInt16(0, ptr, 26); /* reserved 3 */
+ PutUInt16(0, ptr, 28); /* reserved 4 */
+ PutUInt16(0, ptr, 30); /* reserved 5 */
+ PutUInt16(0, ptr, 32); /* metricDataFormat */
+
+ table->data = static_cast<void *>(ptr);
+ table->tag = T_hhea;
+ table->rawdata = nullptr;
+
+ return table;
+}
+
+TrueTypeTable *TrueTypeTableNew_loca()
+{
+ TrueTypeTable* table = static_cast<TrueTypeTable*>(smalloc(sizeof(TrueTypeTable)));
+ table->data = smalloc(sizeof(tdata_loca));
+
+ static_cast<tdata_loca *>(table->data)->nbytes = 0;
+ static_cast<tdata_loca *>(table->data)->ptr = nullptr;
+
+ table->tag = T_loca;
+ table->rawdata = nullptr;
+
+ return table;
+}
+
+TrueTypeTable *TrueTypeTableNew_maxp( const sal_uInt8* maxp, int size)
+{
+ TrueTypeTable* table = static_cast<TrueTypeTable*>(smalloc(sizeof(TrueTypeTable)));
+ table->data = ttmalloc(MAXP_Version1Length);
+
+ if (maxp && size == MAXP_Version1Length) {
+ memcpy(table->data, maxp, MAXP_Version1Length);
+ }
+
+ table->tag = T_maxp;
+ table->rawdata = nullptr;
+
+ return table;
+}
+
+TrueTypeTable *TrueTypeTableNew_glyf()
+{
+ TrueTypeTable* table = static_cast<TrueTypeTable*>(smalloc(sizeof(TrueTypeTable)));
+ list l = listNewEmpty();
+
+ assert(l != nullptr);
+
+ listSetElementDtor(l, FreeGlyphData);
+
+ table->data = l;
+ table->rawdata = nullptr;
+ table->tag = T_glyf;
+
+ return table;
+}
+
+TrueTypeTable *TrueTypeTableNew_cmap()
+{
+ TrueTypeTable* table = static_cast<TrueTypeTable*>(smalloc(sizeof(TrueTypeTable)));
+ table_cmap* cmap = static_cast<table_cmap*>(smalloc(sizeof(table_cmap)));
+
+ cmap->n = 0;
+ cmap->m = CMAP_SUBTABLE_INIT;
+ cmap->s = static_cast<CmapSubTable *>(scalloc(CMAP_SUBTABLE_INIT, sizeof(CmapSubTable)));
+
+ table->data = cmap;
+
+ table->rawdata = nullptr;
+ table->tag = T_cmap;
+
+ return table;
+}
+
+static void DisposeNameRecord(void *ptr)
+{
+ if (ptr != nullptr) {
+ NameRecord *nr = static_cast<NameRecord *>(ptr);
+ if (nr->sptr) free(nr->sptr);
+ free(ptr);
+ }
+}
+
+static NameRecord* NameRecordNewCopy(NameRecord const *nr)
+{
+ NameRecord* p = static_cast<NameRecord*>(smalloc(sizeof(NameRecord)));
+
+ memcpy(p, nr, sizeof(NameRecord));
+
+ if (p->slen) {
+ p->sptr = static_cast<sal_uInt8*>(smalloc(p->slen));
+ memcpy(p->sptr, nr->sptr, p->slen);
+ }
+
+ return p;
+}
+
+TrueTypeTable *TrueTypeTableNew_name(int n, NameRecord const *nr)
+{
+ TrueTypeTable* table = static_cast<TrueTypeTable*>(smalloc(sizeof(TrueTypeTable)));
+ list l = listNewEmpty();
+
+ assert(l != nullptr);
+
+ listSetElementDtor(l, DisposeNameRecord);
+
+ if (n != 0) {
+ int i;
+ for (i = 0; i < n; i++) {
+ listAppend(l, NameRecordNewCopy(nr+i));
+ }
+ }
+
+ table->data = l;
+ table->rawdata = nullptr;
+ table->tag = T_name;
+
+ return table;
+}
+
+TrueTypeTable *TrueTypeTableNew_post(sal_Int32 format,
+ sal_Int32 italicAngle,
+ sal_Int16 underlinePosition,
+ sal_Int16 underlineThickness,
+ sal_uInt32 isFixedPitch)
+{
+ assert(format == 0x00030000); /* Only format 3.0 is supported at this time */
+ TrueTypeTable* table = static_cast<TrueTypeTable*>(smalloc(sizeof(TrueTypeTable)));
+ tdata_post* post = static_cast<tdata_post*>(smalloc(sizeof(tdata_post)));
+
+ post->format = format;
+ post->italicAngle = italicAngle;
+ post->underlinePosition = underlinePosition;
+ post->underlineThickness = underlineThickness;
+ post->isFixedPitch = isFixedPitch;
+ post->ptr = nullptr;
+
+ table->data = post;
+ table->rawdata = nullptr;
+ table->tag = T_post;
+
+ return table;
+}
+
+int GetRawData(TrueTypeTable *_this, sal_uInt8 **ptr, sal_uInt32 *len, sal_uInt32 *tag)
+{
+ /* XXX do a binary search */
+ assert(_this != nullptr);
+ assert(ptr != nullptr);
+ assert(len != nullptr);
+ assert(tag != nullptr);
+
+ *ptr = nullptr; *len = 0; *tag = 0;
+
+ if (_this->rawdata) {
+ free(_this->rawdata);
+ _this->rawdata = nullptr;
+ }
+
+ for(size_t i=0; i < SAL_N_ELEMENTS(vtable2); i++) {
+ if (_this->tag == vtable2[i].tag) {
+ return vtable2[i].f(_this, ptr, len, tag);
+ }
+ }
+
+ assert(!"Unknown TrueType table.");
+ return TTCR_UNKNOWN;
+}
+
+void cmapAdd(TrueTypeTable *table, sal_uInt32 id, sal_uInt32 c, sal_uInt32 g)
+{
+ sal_uInt32 i, found;
+ table_cmap *t;
+ CmapSubTable *s;
+
+ assert(table != nullptr);
+ assert(table->tag == T_cmap);
+ t = static_cast<table_cmap *>(table->data); assert(t != nullptr);
+ s = t->s; assert(s != nullptr);
+
+ found = 0;
+
+ for (i = 0; i < t->n; i++) {
+ if (s[i].id == id) {
+ found = 1;
+ break;
+ }
+ }
+
+ if (!found) {
+ if (t->n == t->m) {
+ CmapSubTable* tmp = static_cast<CmapSubTable*>(scalloc(t->m + CMAP_SUBTABLE_INCR, sizeof(CmapSubTable)));
+ memcpy(tmp, s, sizeof(CmapSubTable) * t->m);
+ t->m += CMAP_SUBTABLE_INCR;
+ free(s);
+ s = tmp;
+ t->s = s;
+ }
+
+ for (i = 0; i < t->n; i++) {
+ if (s[i].id > id) break;
+ }
+
+ if (i < t->n) {
+ memmove(s+i+1, s+i, t->n-i);
+ }
+
+ t->n++;
+
+ s[i].id = id;
+ s[i].n = 0;
+ s[i].m = CMAP_PAIR_INIT;
+ s[i].xc = static_cast<sal_uInt32*>(scalloc(CMAP_PAIR_INIT, sizeof(sal_uInt32)));
+ s[i].xg = static_cast<sal_uInt32*>(scalloc(CMAP_PAIR_INIT, sizeof(sal_uInt32)));
+ }
+
+ if (s[i].n == s[i].m) {
+ sal_uInt32* tmp1 = static_cast<sal_uInt32*>(scalloc(s[i].m + CMAP_PAIR_INCR, sizeof(sal_uInt32)));
+ sal_uInt32* tmp2 = static_cast<sal_uInt32*>(scalloc(s[i].m + CMAP_PAIR_INCR, sizeof(sal_uInt32)));
+ assert(tmp1 != nullptr);
+ assert(tmp2 != nullptr);
+ memcpy(tmp1, s[i].xc, sizeof(sal_uInt32) * s[i].m);
+ memcpy(tmp2, s[i].xg, sizeof(sal_uInt32) * s[i].m);
+ s[i].m += CMAP_PAIR_INCR;
+ free(s[i].xc);
+ free(s[i].xg);
+ s[i].xc = tmp1;
+ s[i].xg = tmp2;
+ }
+
+ s[i].xc[s[i].n] = c;
+ s[i].xg[s[i].n] = g;
+ s[i].n++;
+}
+
+sal_uInt32 glyfAdd(TrueTypeTable *table, GlyphData *glyphdata, TrueTypeFont *fnt)
+{
+ list l;
+ sal_uInt32 currentID;
+ int ret, n, ncomponents;
+
+ assert(table != nullptr);
+ assert(table->tag == T_glyf);
+
+ if (!glyphdata) return sal_uInt32(~0);
+
+ std::vector< sal_uInt32 > glyphlist;
+
+ ncomponents = GetTTGlyphComponents(fnt, glyphdata->glyphID, glyphlist);
+
+ l = static_cast<list>(table->data);
+ if (listCount(l) > 0) {
+ listToLast(l);
+ ret = n = static_cast<GlyphData *>(listCurrent(l))->newID + 1;
+ } else {
+ ret = n = 0;
+ }
+ glyphdata->newID = n++;
+ listAppend(l, glyphdata);
+
+ if (ncomponents > 1 && glyphlist.size() > 1 )
+ {
+ std::vector< sal_uInt32 >::const_iterator it = glyphlist.begin();
+ ++it;
+ /* glyphData->glyphID is always the first glyph on the list */
+ do
+ {
+ int found = 0;
+ currentID = *it;
+ /* XXX expensive! should be rewritten with sorted arrays! */
+ listToFirst(l);
+ do {
+ if (static_cast<GlyphData *>(listCurrent(l))->glyphID == currentID) {
+ found = 1;
+ break;
+ }
+ } while (listNext(l));
+
+ if (!found) {
+ GlyphData *gd = GetTTRawGlyphData(fnt, currentID);
+ gd->newID = n++;
+ listAppend(l, gd);
+ }
+ } while( ++it != glyphlist.end() );
+ }
+
+ return ret;
+}
+
+sal_uInt32 glyfCount(const TrueTypeTable *table)
+{
+ assert(table != nullptr);
+ assert(table->tag == T_glyf);
+ return listCount(static_cast<list>(table->data));
+}
+
+static TrueTypeTable *FindTable(TrueTypeCreator *tt, sal_uInt32 tag)
+{
+ if (listIsEmpty(tt->tables)) return nullptr;
+
+ listToFirst(tt->tables);
+
+ do {
+ if (static_cast<TrueTypeTable *>(listCurrent(tt->tables))->tag == tag) {
+ return static_cast<TrueTypeTable*>(listCurrent(tt->tables));
+ }
+ } while (listNext(tt->tables));
+
+ return nullptr;
+}
+
+/* This function processes all the tables and synchronizes them before creating
+ * the output TrueType stream.
+ *
+ * *** It adds two TrueType tables to the font: 'loca' and 'hmtx' ***
+ *
+ * It does:
+ *
+ * - Re-numbers glyph IDs and creates 'glyf', 'loca', and 'hmtx' tables.
+ * - Calculates xMin, yMin, xMax, and yMax and stores values in 'head' table.
+ * - Stores indexToLocFormat in 'head'
+ * - updates 'maxp' table
+ * - Calculates advanceWidthMax, minLSB, minRSB, xMaxExtent and numberOfHMetrics
+ * in 'hhea' table
+ *
+ */
+static void ProcessTables(TrueTypeCreator *tt)
+{
+ TrueTypeTable *glyf, *loca, *head, *maxp, *hhea;
+ list glyphlist;
+ sal_uInt32 nGlyphs, locaLen = 0, glyfLen = 0;
+ sal_Int16 xMin = 0, yMin = 0, xMax = 0, yMax = 0;
+ sal_uInt32 i = 0;
+ sal_Int16 indexToLocFormat;
+ sal_uInt8 *hmtxPtr, *hheaPtr;
+ sal_uInt32 hmtxSize;
+ sal_uInt8 *p1, *p2;
+ sal_uInt16 maxPoints = 0, maxContours = 0, maxCompositePoints = 0, maxCompositeContours = 0;
+ int nlsb = 0;
+ sal_uInt32 *gid; /* array of old glyphIDs */
+
+ glyf = FindTable(tt, T_glyf);
+ glyphlist = static_cast<list>(glyf->data);
+ nGlyphs = listCount(glyphlist);
+ assert(nGlyphs != 0);
+ gid = static_cast<sal_uInt32*>(scalloc(nGlyphs, sizeof(sal_uInt32)));
+
+ RemoveTable(tt, T_loca);
+ RemoveTable(tt, T_hmtx);
+
+ /* XXX Need to make sure that composite glyphs do not break during glyph renumbering */
+
+ listToFirst(glyphlist);
+ do {
+ GlyphData *gd = static_cast<GlyphData *>(listCurrent(glyphlist));
+ sal_Int16 z;
+ 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 != 0) {
+ z = GetInt16(gd->ptr, 2);
+ if (z < xMin) xMin = z;
+
+ z = GetInt16(gd->ptr, 4);
+ if (z < yMin) yMin = z;
+
+ z = GetInt16(gd->ptr, 6);
+ if (z > xMax) xMax = z;
+
+ z = GetInt16(gd->ptr, 8);
+ if (z > yMax) yMax = z;
+ }
+
+ if (!gd->compflag) { /* non-composite glyph */
+ if (gd->npoints > maxPoints) maxPoints = gd->npoints;
+ if (gd->ncontours > maxContours) maxContours = gd->ncontours;
+ } else { /* composite glyph */
+ if (gd->npoints > maxCompositePoints) maxCompositePoints = gd->npoints;
+ if (gd->ncontours > maxCompositeContours) maxCompositeContours = gd->ncontours;
+ }
+
+ } while (listNext(glyphlist));
+
+ indexToLocFormat = (glyfLen / 2 > 0xFFFF) ? 1 : 0;
+ locaLen = indexToLocFormat ? (nGlyphs + 1) << 2 : (nGlyphs + 1) << 1;
+
+ sal_uInt8* glyfPtr = ttmalloc(glyfLen);
+ sal_uInt8* locaPtr = ttmalloc(locaLen);
+ TTSimpleGlyphMetrics* met = static_cast<TTSimpleGlyphMetrics*>(scalloc(nGlyphs, sizeof(TTSimpleGlyphMetrics)));
+ i = 0;
+
+ listToFirst(glyphlist);
+ p1 = glyfPtr;
+ p2 = locaPtr;
+ do {
+ GlyphData *gd = static_cast<GlyphData *>(listCurrent(glyphlist));
+
+ if (gd->compflag) { /* re-number all components */
+ sal_uInt16 flags, index;
+ sal_uInt8 *ptr = gd->ptr + 10;
+ do {
+ sal_uInt32 j;
+ flags = GetUInt16(ptr, 0);
+ index = GetUInt16(ptr, 2);
+ /* XXX use the sorted array of old to new glyphID mapping and do a binary search */
+ 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;
+
+ if (flags & ARG_1_AND_2_ARE_WORDS) {
+ ptr += 4;
+ } else {
+ ptr += 2;
+ }
+
+ if (flags & WE_HAVE_A_SCALE) {
+ ptr += 2;
+ } else if (flags & WE_HAVE_AN_X_AND_Y_SCALE) {
+ ptr += 4;
+ } else if (flags & WE_HAVE_A_TWO_BY_TWO) {
+ ptr += 8;
+ }
+ } while (flags & MORE_COMPONENTS);
+ }
+
+ if (gd->nbytes != 0) {
+ memcpy(p1, gd->ptr, gd->nbytes);
+ }
+ if (indexToLocFormat == 1) {
+ PutUInt32(p1 - glyfPtr, p2, 0);
+ p2 += 4;
+ } else {
+ PutUInt16(static_cast<sal_uInt16>((p1 - glyfPtr) >> 1), p2, 0);
+ p2 += 2;
+ }
+ p1 += gd->nbytes;
+
+ /* fill the array of metrics */
+ met[i].adv = gd->aw;
+ met[i].sb = gd->lsb;
+ i++;
+ } while (listNext(glyphlist));
+
+ free(gid);
+
+ if (indexToLocFormat == 1) {
+ PutUInt32(p1 - glyfPtr, p2, 0);
+ } else {
+ PutUInt16(static_cast<sal_uInt16>((p1 - glyfPtr) >> 1), p2, 0);
+ }
+
+ glyf->rawdata = glyfPtr;
+
+ loca = TrueTypeTableNew_loca(); assert(loca != nullptr);
+ static_cast<tdata_loca *>(loca->data)->ptr = locaPtr;
+ static_cast<tdata_loca *>(loca->data)->nbytes = locaLen;
+
+ AddTable(tt, loca);
+
+ head = FindTable(tt, T_head);
+ sal_uInt8* const pHeadData = static_cast<sal_uInt8*>(head->data);
+ PutInt16(xMin, pHeadData, HEAD_xMin_offset);
+ PutInt16(yMin, pHeadData, HEAD_yMin_offset);
+ PutInt16(xMax, pHeadData, HEAD_xMax_offset);
+ PutInt16(yMax, pHeadData, HEAD_yMax_offset);
+ PutInt16(indexToLocFormat, pHeadData, HEAD_indexToLocFormat_offset);
+
+ maxp = FindTable(tt, T_maxp);
+
+ sal_uInt8* const pMaxpData = static_cast<sal_uInt8*>(maxp->data);
+ PutUInt16(static_cast<sal_uInt16>(nGlyphs), pMaxpData, MAXP_numGlyphs_offset);
+ PutUInt16(maxPoints, pMaxpData, MAXP_maxPoints_offset);
+ PutUInt16(maxContours, pMaxpData, MAXP_maxContours_offset);
+ PutUInt16(maxCompositePoints, pMaxpData, MAXP_maxCompositePoints_offset);
+ PutUInt16(maxCompositeContours, pMaxpData, MAXP_maxCompositeContours_offset);
+
+ /*
+ * Generate an htmx table and update hhea table
+ */
+ hhea = FindTable(tt, T_hhea); assert(hhea != nullptr);
+ hheaPtr = static_cast<sal_uInt8 *>(hhea->data);
+ if (nGlyphs > 2) {
+ for (i = nGlyphs - 1; i > 0; i--) {
+ if (met[i].adv != met[i-1].adv) break;
+ }
+ nlsb = nGlyphs - 1 - i;
+ }
+ hmtxSize = (nGlyphs - nlsb) * 4 + nlsb * 2;
+ hmtxPtr = ttmalloc(hmtxSize);
+ p1 = hmtxPtr;
+
+ for (i = 0; i < nGlyphs; i++) {
+ if (i < nGlyphs - nlsb) {
+ PutUInt16(met[i].adv, p1, 0);
+ PutUInt16(met[i].sb, p1, 2);
+ p1 += 4;
+ } else {
+ PutUInt16(met[i].sb, p1, 0);
+ p1 += 2;
+ }
+ }
+
+ AddTable(tt, TrueTypeTableNew(T_hmtx, hmtxSize, hmtxPtr));
+ PutUInt16(static_cast<sal_uInt16>(nGlyphs - nlsb), hheaPtr, 34);
+ free(hmtxPtr);
+ free(met);
+}
+
+} // namespace vcl
+
+extern "C"
+{
+ /**
+ * TrueTypeCreator destructor. It calls destructors for all TrueTypeTables added to it.
+ */
+ void TrueTypeCreatorDispose(vcl::TrueTypeCreator *_this)
+ {
+ listDispose(_this->tables);
+ free(_this);
+ }
+
+ /**
+ * Destructor for the TrueTypeTable object.
+ */
+ void TrueTypeTableDispose(void * arg)
+ {
+ vcl::TrueTypeTable *_this = static_cast<vcl::TrueTypeTable *>(arg);
+ /* XXX do a binary search */
+ assert(_this != nullptr);
+
+ if (_this->rawdata) free(_this->rawdata);
+
+ for(size_t i=0; i < SAL_N_ELEMENTS(vcl::vtable1); i++) {
+ if (_this->tag == vcl::vtable1[i].tag) {
+ vcl::vtable1[i].f(_this);
+ return;
+ }
+ }
+ assert(!"Unknown TrueType table.");
+ }
+}
+
+#ifdef TEST_TTCR
+static sal_uInt32 mkTag(sal_uInt8 a, sal_uInt8 b, sal_uInt8 c, sal_uInt8 d) {
+ return (a << 24) | (b << 16) | (c << 8) | d;
+}
+
+int main()
+{
+ TrueTypeCreator *ttcr;
+ sal_uInt8 *t1, *t2, *t3, *t4, *t5, *t6;
+
+ TrueTypeCreatorNewEmpty(mkTag('t','r','u','e'), &ttcr);
+
+ t1 = malloc(1000); memset(t1, 'a', 1000);
+ t2 = malloc(2000); memset(t2, 'b', 2000);
+ t3 = malloc(3000); memset(t3, 'c', 3000);
+ t4 = malloc(4000); memset(t4, 'd', 4000);
+ t5 = malloc(5000); memset(t5, 'e', 5000);
+ t6 = malloc(6000); memset(t6, 'f', 6000);
+
+ AddTable(ttcr, TrueTypeTableNew(T_maxp, 1000, t1));
+ AddTable(ttcr, TrueTypeTableNew(T_OS2, 2000, t2));
+ AddTable(ttcr, TrueTypeTableNew(T_cmap, 3000, t3));
+ AddTable(ttcr, TrueTypeTableNew(T_loca, 4000, t4));
+ AddTable(ttcr, TrueTypeTableNew(T_hhea, 5000, t5));
+ AddTable(ttcr, TrueTypeTableNew(T_glyf, 6000, t6));
+
+ free(t1);
+ free(t2);
+ free(t3);
+ free(t4);
+ free(t5);
+ free(t6);
+
+ StreamToFile(ttcr, "ttcrout.ttf");
+
+ TrueTypeCreatorDispose(ttcr);
+ return 0;
+}
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/fontsubset/ttcr.hxx b/vcl/source/fontsubset/ttcr.hxx
new file mode 100644
index 000000000..26298f78c
--- /dev/null
+++ b/vcl/source/fontsubset/ttcr.hxx
@@ -0,0 +1,220 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+/**
+ * @file ttcr.hxx
+ * @brief TrueType font creator
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_FONTSUBSET_TTCR_HXX
+#define INCLUDED_VCL_SOURCE_FONTSUBSET_TTCR_HXX
+
+#include <sft.hxx>
+
+namespace vcl
+{
+ struct TrueTypeCreator;
+
+/* TrueType data types */
+ typedef struct {
+ sal_uInt16 aw;
+ sal_Int16 lsb;
+ } longHorMetrics;
+
+/* A generic base class for all TrueType tables */
+ struct TrueTypeTable {
+ sal_uInt32 tag; /* table tag */
+ sal_uInt8 *rawdata; /* raw data allocated by GetRawData_*() */
+ void *data; /* table specific data */
+ };
+
+/** Error codes for most functions */
+ enum TTCRErrCodes {
+ TTCR_OK, /**< no error */
+ TTCR_ZEROGLYPHS, /**< At least one glyph should be defined */
+ TTCR_UNKNOWN, /**< Unknown TrueType table */
+ TTCR_NONAMES, /**< 'name' table does not contain any names */
+ TTCR_NAMETOOLONG, /**< 'name' table is too long (string data > 64K) */
+ TTCR_POSTFORMAT /**< unsupported format of a 'post' table */
+ };
+
+/**
+ * TrueTypeCreator constructor.
+ * Allocates all internal structures.
+ */
+ void TrueTypeCreatorNewEmpty(sal_uInt32 tag, TrueTypeCreator **_this);
+
+/**
+ * Adds a TrueType table to the TrueType creator.
+ */
+ void AddTable(TrueTypeCreator *_this, TrueTypeTable *table);
+
+/**
+ * Removes a TrueType table from the TrueType creator if it is stored there.
+ * It also calls a TrueTypeTable destructor.
+ * Note: all generic tables (with tag 0) will be removed if this function is
+ * called with the second argument of 0.
+ * @return value of SFErrCodes type
+ */
+ void RemoveTable(TrueTypeCreator *_this, sal_uInt32 tag);
+
+/**
+ * Writes a TrueType font generated by the TrueTypeCreator to a segment of
+ * memory that this method allocates. When it is not needed anymore the caller
+ * is supposed to call free() on it.
+ * @return value of SFErrCodes type
+ */
+ SFErrCodes StreamToMemory(TrueTypeCreator *_this, sal_uInt8 **ptr, sal_uInt32 *length);
+
+/**
+ * Writes a TrueType font generated by the TrueTypeCreator to a file
+ * @return value of SFErrCodes type
+ */
+ SFErrCodes StreamToFile(TrueTypeCreator *_this, const char* fname);
+
+/**
+ * This function converts the data of a TrueType table to a raw array of bytes.
+ * It may allocates the memory for it and returns the size of the raw data in bytes.
+ * If memory is allocated it does not need to be freed by the caller of this function,
+ * since the pointer to it is stored in the TrueTypeTable and it is freed by the destructor
+ * @return TTCRErrCode
+ *
+ */
+
+ int GetRawData(TrueTypeTable *, sal_uInt8 **ptr, sal_uInt32 *len, sal_uInt32 *tag);
+
+/**
+ *
+ * Creates a new raw TrueType table. The difference between this constructor and
+ * TrueTypeTableNew_tag constructors is that the latter create structured tables
+ * while this constructor just copies memory pointed to by ptr to its buffer
+ * and stores its length. This constructor is suitable for data that is not
+ * supposed to be processed in any way, just written to the resulting TTF file.
+ */
+ TrueTypeTable *TrueTypeTableNew(sal_uInt32 tag,
+ sal_uInt32 nbytes,
+ const sal_uInt8* ptr);
+
+/**
+ * Creates a new 'head' table for a TrueType font.
+ * Allocates memory for it. Since a lot of values in the 'head' table depend on the
+ * rest of the tables in the TrueType font this table should be the last one added
+ * to the font.
+ */
+ TrueTypeTable *TrueTypeTableNew_head(sal_uInt32 fontRevision,
+ sal_uInt16 flags,
+ sal_uInt16 unitsPerEm,
+ const sal_uInt8 *created,
+ sal_uInt16 macStyle,
+ sal_uInt16 lowestRecPPEM,
+ sal_Int16 fontDirectionHint);
+
+/**
+ * Creates a new 'hhea' table for a TrueType font.
+ * Allocates memory for it and stores it in the hhea pointer.
+ */
+ TrueTypeTable *TrueTypeTableNew_hhea(sal_Int16 ascender,
+ sal_Int16 descender,
+ sal_Int16 linegap,
+ sal_Int16 caretSlopeRise,
+ sal_Int16 caretSlopeRun);
+
+/**
+ * Creates a new empty 'loca' table for a TrueType font.
+ *
+ * INTERNAL: gets called only from ProcessTables();
+ */
+ TrueTypeTable *TrueTypeTableNew_loca();
+
+/**
+ * Creates a new 'maxp' table based on an existing maxp table.
+ * If maxp is 0, a new empty maxp table is created
+ * size specifies the size of existing maxp table for
+ * error-checking purposes
+ */
+ TrueTypeTable *TrueTypeTableNew_maxp( const sal_uInt8* maxp, int size);
+
+/**
+ * Creates a new empty 'glyf' table.
+ */
+ TrueTypeTable *TrueTypeTableNew_glyf();
+
+/**
+ * Creates a new empty 'cmap' table.
+ */
+ TrueTypeTable *TrueTypeTableNew_cmap();
+
+/**
+ * Creates a new 'name' table. If n != 0 the table gets populated by
+ * the Name Records stored in the nr array. This function allocates
+ * memory for its own copy of NameRecords, so nr array has to
+ * be explicitly deallocated when it is not needed.
+ */
+ TrueTypeTable *TrueTypeTableNew_name(int n, NameRecord const *nr);
+
+/**
+ * Creates a new 'post' table of one of the supported formats
+ */
+ TrueTypeTable *TrueTypeTableNew_post(sal_Int32 format,
+ sal_Int32 italicAngle,
+ sal_Int16 underlinePosition,
+ sal_Int16 underlineThickness,
+ sal_uInt32 isFixedPitch);
+
+// Table manipulation functions
+
+/**
+ * Add a character/glyph pair to a cmap table
+ */
+ void cmapAdd(TrueTypeTable *, sal_uInt32 id, sal_uInt32 c, sal_uInt32 g);
+
+/**
+ * Add a glyph to a glyf table.
+ *
+ * @return glyphID of the glyph in the new font
+ *
+ * NOTE: This function does not duplicate GlyphData, so memory will be
+ * deallocated in the table destructor
+ */
+ sal_uInt32 glyfAdd(TrueTypeTable *, GlyphData *glyphdata, TrueTypeFont *fnt);
+
+/**
+ * Query the number of glyphs currently stored in the 'glyf' table
+ *
+ */
+ sal_uInt32 glyfCount(const TrueTypeTable *);
+
+} // namespace
+
+extern "C"
+{
+/**
+ * Destructor for the TrueTypeTable object.
+ */
+ void TrueTypeTableDispose(void *);
+
+/**
+ * TrueTypeCreator destructor. It calls destructors for all TrueTypeTables added to it.
+ */
+ void TrueTypeCreatorDispose(vcl::TrueTypeCreator *_this);
+}
+
+#endif // INCLUDED_VCL_SOURCE_FONTSUBSET_TTCR_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/fontsubset/xlat.cxx b/vcl/source/fontsubset/xlat.cxx
new file mode 100644
index 000000000..b7966f279
--- /dev/null
+++ b/vcl/source/fontsubset/xlat.cxx
@@ -0,0 +1,144 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <rtl/textcvt.h>
+#include "xlat.hxx"
+
+namespace {
+
+#define MAX_CVT_SELECT 6
+
+class ConverterCache
+{
+public:
+ explicit ConverterCache();
+ ~ConverterCache();
+ sal_uInt16 convertOne( int nSelect, sal_Unicode );
+private:
+ void ensureConverter( int nSelect );
+ rtl_UnicodeToTextConverter maConverterCache[ MAX_CVT_SELECT+1 ];
+ rtl_UnicodeToTextContext maContexts[ MAX_CVT_SELECT+1 ];
+};
+
+ConverterCache::ConverterCache()
+{
+ for( int i = 0; i <= MAX_CVT_SELECT; ++i)
+ {
+ maConverterCache[i] = nullptr;
+ maContexts[i] = nullptr;
+ }
+}
+
+ConverterCache::~ConverterCache()
+{
+ for( int i = 0; i <= MAX_CVT_SELECT; ++i)
+ {
+ if( !maContexts[i] )
+ continue;
+ rtl_destroyUnicodeToTextContext( maConverterCache[i], maContexts[i] );
+ rtl_destroyUnicodeToTextConverter( maConverterCache[i] );
+ }
+}
+
+void ConverterCache::ensureConverter( int nSelect )
+{
+ // SAL_WARN_IF( (2>nSelect) || (nSelect>MAX_CVT_SELECT)), "vcl", "invalid XLAT.Converter requested" );
+ rtl_UnicodeToTextContext aContext = maContexts[ nSelect ];
+ if( !aContext )
+ {
+ rtl_TextEncoding eRecodeFrom = RTL_TEXTENCODING_UNICODE;
+ switch( nSelect )
+ {
+ default: nSelect = 1; [[fallthrough]]; // to unicode recoding
+ case 1: eRecodeFrom = RTL_TEXTENCODING_UNICODE; break;
+ case 2: eRecodeFrom = RTL_TEXTENCODING_SHIFT_JIS; break;
+ case 3: eRecodeFrom = RTL_TEXTENCODING_GB_2312; break;
+ case 4: eRecodeFrom = RTL_TEXTENCODING_BIG5; break;
+ case 5: eRecodeFrom = RTL_TEXTENCODING_MS_949; break;
+ case 6: eRecodeFrom = RTL_TEXTENCODING_MS_1361; break;
+ }
+ rtl_UnicodeToTextConverter aRecodeConverter = rtl_createUnicodeToTextConverter( eRecodeFrom );
+ maConverterCache[ nSelect ] = aRecodeConverter;
+
+ aContext = rtl_createUnicodeToTextContext( aRecodeConverter );
+ maContexts[ nSelect ] = aContext;
+ }
+
+ rtl_resetUnicodeToTextContext( maConverterCache[ nSelect ], aContext );
+}
+
+sal_uInt16 ConverterCache::convertOne( int nSelect, sal_Unicode aChar )
+{
+ ensureConverter( nSelect );
+
+ sal_Unicode aUCS2Char = aChar;
+ char aTempArray[8];
+ sal_Size nTempSize;
+ sal_uInt32 nCvtInfo;
+
+ // TODO: use direct unicode->mbcs converter should there ever be one
+ int nCodeLen = rtl_convertUnicodeToText(
+ maConverterCache[ nSelect ], maContexts[ nSelect ],
+ &aUCS2Char, 1, aTempArray, sizeof(aTempArray),
+ RTL_UNICODETOTEXT_FLAGS_UNDEFINED_0
+ | RTL_UNICODETOTEXT_FLAGS_INVALID_0,
+ &nCvtInfo, &nTempSize );
+
+ sal_uInt16 aCode = aTempArray[0];
+ for( int i = 1; i < nCodeLen; ++i )
+ aCode = (aCode << 8) + (aTempArray[i] & 0xFF);
+ return aCode;
+}
+
+} // anonymous namespace
+
+namespace vcl
+{
+
+static ConverterCache aCC;
+
+sal_uInt16 TranslateChar12(sal_uInt16 src)
+{
+ return aCC.convertOne( 2, src);
+}
+
+sal_uInt16 TranslateChar13(sal_uInt16 src)
+{
+ return aCC.convertOne( 3, src);
+}
+
+sal_uInt16 TranslateChar14(sal_uInt16 src)
+{
+ return aCC.convertOne( 4, src);
+}
+
+sal_uInt16 TranslateChar15(sal_uInt16 src)
+{
+ return aCC.convertOne( 5, src);
+}
+
+sal_uInt16 TranslateChar16(sal_uInt16 src)
+{
+ return aCC.convertOne( 6, src);
+}
+
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/fontsubset/xlat.hxx b/vcl/source/fontsubset/xlat.hxx
new file mode 100644
index 000000000..c9994b48f
--- /dev/null
+++ b/vcl/source/fontsubset/xlat.hxx
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+/*| Author: Alexander Gelfenbain |*/
+
+#ifndef INCLUDED_VCL_SOURCE_FONTSUBSET_XLAT_HXX
+#define INCLUDED_VCL_SOURCE_FONTSUBSET_XLAT_HXX
+
+#include <sal/types.h>
+
+namespace vcl
+{
+// TODO: sal_UCS4
+
+ sal_uInt16 TranslateChar12(sal_uInt16);
+ sal_uInt16 TranslateChar13(sal_uInt16);
+ sal_uInt16 TranslateChar14(sal_uInt16);
+ sal_uInt16 TranslateChar15(sal_uInt16);
+ sal_uInt16 TranslateChar16(sal_uInt16);
+}
+
+#endif // INCLUDED_VCL_SOURCE_FONTSUBSET_XLAT_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/CommonSalLayout.cxx b/vcl/source/gdi/CommonSalLayout.cxx
new file mode 100644
index 000000000..d5ce806fa
--- /dev/null
+++ b/vcl/source/gdi/CommonSalLayout.cxx
@@ -0,0 +1,854 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <hb-icu.h>
+#include <hb-ot.h>
+#include <hb-graphite2.h>
+
+#include <sallayout.hxx>
+
+#include <sal/log.hxx>
+#include <unotools/configmgr.hxx>
+#include <vcl/unohelp.hxx>
+#include <vcl/font/Feature.hxx>
+#include <vcl/font/FeatureParser.hxx>
+#include <scrptrun.h>
+#include <com/sun/star/i18n/CharacterIteratorMode.hpp>
+#include <salgdi.hxx>
+#include <unicode/uchar.h>
+
+#include <fontselect.hxx>
+
+#if !HB_VERSION_ATLEAST(1, 1, 0)
+// Disabled Unicode compatibility decomposition, see fdo#66715
+static unsigned int unicodeDecomposeCompatibility(hb_unicode_funcs_t* /*ufuncs*/,
+ hb_codepoint_t /*u*/,
+ hb_codepoint_t* /*decomposed*/,
+ void* /*user_data*/)
+{
+ return 0;
+}
+
+static hb_unicode_funcs_t* getUnicodeFuncs()
+{
+ static hb_unicode_funcs_t* ufuncs = hb_unicode_funcs_create(hb_icu_get_unicode_funcs());
+ hb_unicode_funcs_set_decompose_compatibility_func(ufuncs, unicodeDecomposeCompatibility, nullptr, nullptr);
+ return ufuncs;
+}
+#endif
+
+GenericSalLayout::GenericSalLayout(LogicalFontInstance &rFont)
+ : mpVertGlyphs(nullptr)
+ , mbFuzzing(utl::ConfigManager::IsFuzzing())
+{
+ new SalLayoutGlyphsImpl(m_GlyphItems, rFont);
+}
+
+GenericSalLayout::~GenericSalLayout()
+{
+}
+
+void GenericSalLayout::ParseFeatures(const OUString& 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 vcl {
+ namespace {
+
+ 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 TextLayoutCache
+ {
+ public:
+ std::vector<vcl::Run> runs;
+ 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 vcl
+
+namespace {
+#if U_ICU_VERSION_MAJOR_NUM >= 63
+ enum class VerticalOrientation {
+ Upright = U_VO_UPRIGHT,
+ Rotated = U_VO_ROTATED,
+ TransformedUpright = U_VO_TRANSFORMED_UPRIGHT,
+ TransformedRotated = U_VO_TRANSFORMED_ROTATED
+ };
+#else
+ #include "VerticalOrientationData.cxx"
+
+ // These must match the values in the file included above.
+ enum class VerticalOrientation {
+ Upright = 0,
+ Rotated = 1,
+ TransformedUpright = 2,
+ TransformedRotated = 3
+ };
+#endif
+
+ VerticalOrientation GetVerticalOrientation(sal_UCS4 cCh, const LanguageTag& rTag)
+ {
+ // Override orientation of fullwidth colon , semi-colon,
+ // and Bopomofo tonal marks.
+ if ((cCh == 0xff1a || cCh == 0xff1b
+ || cCh == 0x2ca || cCh == 0x2cb || cCh == 0x2c7 || cCh == 0x2d9)
+ && rTag.getLanguage() == "zh")
+ return VerticalOrientation::TransformedUpright;
+
+#if U_ICU_VERSION_MAJOR_NUM >= 63
+ int32_t nRet = u_getIntPropertyValue(cCh, UCHAR_VERTICAL_ORIENTATION);
+#else
+ uint8_t nRet = 1;
+
+ if (cCh < 0x10000)
+ {
+ nRet = sVerticalOrientationValues[sVerticalOrientationPages[0][cCh >> kVerticalOrientationCharBits]]
+ [cCh & ((1 << kVerticalOrientationCharBits) - 1)];
+ }
+ else if (cCh < (kVerticalOrientationMaxPlane + 1) * 0x10000)
+ {
+ nRet = sVerticalOrientationValues[sVerticalOrientationPages[sVerticalOrientationPlanes[(cCh >> 16) - 1]]
+ [(cCh & 0xffff) >> kVerticalOrientationCharBits]]
+ [cCh & ((1 << kVerticalOrientationCharBits) - 1)];
+ }
+ else
+ {
+ // Default value for unassigned
+ SAL_WARN("vcl.gdi", "Getting VerticalOrientation for codepoint outside Unicode range");
+ }
+#endif
+
+ return VerticalOrientation(nRet);
+ }
+
+} // namespace
+
+std::shared_ptr<vcl::TextLayoutCache> GenericSalLayout::CreateTextLayoutCache(OUString const& rString)
+{
+ return std::make_shared<vcl::TextLayoutCache>(rString.getStr(), rString.getLength());
+}
+
+const SalLayoutGlyphs* GenericSalLayout::GetGlyphs() const
+{
+ return &m_GlyphItems;
+}
+
+void GenericSalLayout::SetNeedFallback(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;
+ sal_Int32 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);
+ sal_Int32 nGraphemeStartPos =
+ mxBreak->previousCharacters(rArgs.mrStr, nCharPos, aLocale,
+ i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
+
+ rArgs.NeedFallback(nGraphemeStartPos, nGraphemeEndPos, bRightToLeft);
+}
+
+void GenericSalLayout::AdjustLayout(ImplLayoutArgs& rArgs)
+{
+ SalLayout::AdjustLayout(rArgs);
+
+ if (rArgs.mpDXArray)
+ ApplyDXArray(rArgs);
+ else if (rArgs.mnLayoutWidth)
+ Justify(rArgs.mnLayoutWidth);
+ // apply asian kerning if the glyphs are not already formatted
+ else if ((rArgs.mnFlags & SalLayoutFlags::KerningAsian)
+ && !(rArgs.mnFlags & SalLayoutFlags::Vertical))
+ ApplyAsianKerning(rArgs.mrStr);
+}
+
+void GenericSalLayout::DrawText(SalGraphics& rSalGraphics) const
+{
+ //call platform dependent DrawText functions
+ rSalGraphics.DrawTextLayout( *this );
+}
+
+// Find if the nominal glyph of the character is an input to “vert†feature.
+// We don’t check for a specific script or language as it shouldn’t matter
+// here; if the glyph would be the result from applying “vert†for any
+// script/language then we want to always treat it as upright glyph.
+bool GenericSalLayout::HasVerticalAlternate(sal_UCS4 aChar, sal_UCS4 aVariationSelector)
+{
+ hb_codepoint_t nGlyphIndex = 0;
+ hb_font_t *pHbFont = GetFont().GetHbFont();
+ if (!hb_font_get_glyph(pHbFont, aChar, aVariationSelector, &nGlyphIndex))
+ return false;
+
+ if (!mpVertGlyphs)
+ {
+ hb_face_t* pHbFace = hb_font_get_face(pHbFont);
+ mpVertGlyphs = hb_set_create();
+
+ // Find all GSUB lookups for “vert†feature.
+ hb_set_t* pLookups = hb_set_create();
+ hb_tag_t const pFeatures[] = { HB_TAG('v','e','r','t'), HB_TAG_NONE };
+ hb_ot_layout_collect_lookups(pHbFace, HB_OT_TAG_GSUB, nullptr, nullptr, pFeatures, pLookups);
+ if (!hb_set_is_empty(pLookups))
+ {
+ // Find the output glyphs in each lookup (i.e. the glyphs that
+ // would result from applying this lookup).
+ hb_codepoint_t nIdx = HB_SET_VALUE_INVALID;
+ while (hb_set_next(pLookups, &nIdx))
+ {
+ hb_set_t* pGlyphs = hb_set_create();
+ hb_ot_layout_lookup_collect_glyphs(pHbFace, HB_OT_TAG_GSUB, nIdx,
+ nullptr, // glyphs before
+ pGlyphs, // glyphs input
+ nullptr, // glyphs after
+ nullptr); // glyphs out
+ hb_set_union(mpVertGlyphs, pGlyphs);
+ }
+ }
+ }
+
+ return hb_set_has(mpVertGlyphs, nGlyphIndex) != 0;
+}
+
+bool GenericSalLayout::LayoutText(ImplLayoutArgs& rArgs, const SalLayoutGlyphs* 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;
+ // Some flags are set as a side effect of text layout, restore them here.
+ rArgs.mnFlags |= pGlyphs->Impl()->mnFlags;
+ return true;
+ }
+
+ hb_font_t *pHbFont = GetFont().GetHbFont();
+ bool isGraphite = GetFont().IsGraphiteFont();
+
+ int nGlyphCapacity = 2 * (rArgs.mnEndCharPos - rArgs.mnMinCharPos);
+ m_GlyphItems.Impl()->reserve(nGlyphCapacity);
+
+ const int nLength = rArgs.mrStr.getLength();
+ const sal_Unicode *pStr = rArgs.mrStr.getStr();
+
+ std::unique_ptr<vcl::TextLayoutCache> pNewScriptRun;
+ vcl::TextLayoutCache const* pTextLayout;
+ if (rArgs.m_pTextLayoutCache)
+ {
+ pTextLayout = rArgs.m_pTextLayoutCache; // use cache!
+ }
+ else
+ {
+ pNewScriptRun.reset(new vcl::TextLayoutCache(pStr, rArgs.mnEndCharPos));
+ pTextLayout = pNewScriptRun.get();
+ }
+
+ hb_buffer_t* pHbBuffer = hb_buffer_create();
+ hb_buffer_pre_allocate(pHbBuffer, nGlyphCapacity);
+#if !HB_VERSION_ATLEAST(1, 1, 0)
+ static hb_unicode_funcs_t* pHbUnicodeFuncs = getUnicodeFuncs();
+ hb_buffer_set_unicode_funcs(pHbBuffer, pHbUnicodeFuncs);
+#endif
+
+ const 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) });
+ }
+
+ ParseFeatures(rFontSelData.maTargetName);
+
+ double nXScale = 0;
+ double nYScale = 0;
+ GetFont().GetScale(&nXScale, &nYScale);
+
+ Point 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::Run const& rRun(pTextLayout->runs[k]);
+ if (rRun.nStart <= nCurrentPos && nCurrentPos < rRun.nEnd)
+ {
+ break;
+ }
+ }
+
+ if (isGraphite)
+ {
+ hb_script_t aScript = hb_icu_script_to_script(pTextLayout->runs[k].nCode);
+ aSubRuns.push_back({ nBidiMinRunPos, nBidiEndRunPos, aScript, bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR });
+ }
+ else
+ {
+ while (nCurrentPos < nBidiEndRunPos && k < pTextLayout->runs.size())
+ {
+ int32_t nMinRunPos = nCurrentPos;
+ int32_t nEndRunPos = std::min(pTextLayout->runs[k].nEnd, nBidiEndRunPos);
+ hb_direction_t aDirection = bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR;
+ hb_script_t aScript = hb_icu_script_to_script(pTextLayout->runs[k].nCode);
+ // For vertical text, further divide the runs based on character
+ // orientation.
+ if (rArgs.mnFlags & SalLayoutFlags::Vertical)
+ {
+ sal_Int32 nIdx = nMinRunPos;
+ while (nIdx < nEndRunPos)
+ {
+ sal_Int32 nPrevIdx = nIdx;
+ sal_UCS4 aChar = rArgs.mrStr.iterateCodePoints(&nIdx);
+ VerticalOrientation aVo = GetVerticalOrientation(aChar, rArgs.maLanguageTag);
+
+ sal_UCS4 aVariationSelector = 0;
+ if (nIdx < nEndRunPos)
+ {
+ sal_Int32 nNextIdx = nIdx;
+ sal_UCS4 aNextChar = rArgs.mrStr.iterateCodePoints(&nNextIdx);
+ if (u_hasBinaryProperty(aNextChar, UCHAR_VARIATION_SELECTOR))
+ {
+ nIdx = nNextIdx;
+ aVariationSelector = aNextChar;
+ }
+ }
+
+ // Charters with U and Tu vertical orientation should
+ // be shaped in vertical direction. But characters
+ // with Tr should be shaped in vertical direction
+ // only if they have vertical alternates, otherwise
+ // they should be shaped in horizontal direction
+ // and then rotated.
+ // See http://unicode.org/reports/tr50/#vo
+ if (aVo == VerticalOrientation::Upright ||
+ aVo == VerticalOrientation::TransformedUpright ||
+ (aVo == VerticalOrientation::TransformedRotated &&
+ HasVerticalAlternate(aChar, aVariationSelector)))
+ {
+ aDirection = HB_DIRECTION_TTB;
+ }
+ else
+ {
+ aDirection = bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR;
+ }
+
+ if (aSubRuns.empty() || aSubRuns.back().maDirection != aDirection)
+ aSubRuns.push_back({ nPrevIdx, nIdx, aScript, aDirection });
+ else
+ aSubRuns.back().mnEnd = nIdx;
+ }
+ }
+ else
+ {
+ aSubRuns.push_back({ nMinRunPos, nEndRunPos, aScript, aDirection });
+ }
+
+ nCurrentPos = nEndRunPos;
+ ++k;
+ }
+ }
+
+ // RTL subruns should be reversed to ensure that final glyph order is
+ // correct.
+ if (bRightToLeft)
+ std::reverse(aSubRuns.begin(), aSubRuns.end());
+
+ for (const auto& aSubRun : aSubRuns)
+ {
+ hb_buffer_clear_contents(pHbBuffer);
+
+ const int nMinRunPos = aSubRun.mnMin;
+ const int nEndRunPos = aSubRun.mnEnd;
+ const int nRunLen = nEndRunPos - nMinRunPos;
+
+ OString sLanguage = msLanguage;
+ if (sLanguage.isEmpty())
+ sLanguage = OUStringToOString(rArgs.maLanguageTag.getBcp47(), RTL_TEXTENCODING_ASCII_US);
+
+ int nHbFlags = HB_BUFFER_FLAGS_DEFAULT;
+ if (nMinRunPos == 0)
+ nHbFlags |= HB_BUFFER_FLAG_BOT; /* Beginning-of-text */
+ if (nEndRunPos == nLength)
+ nHbFlags |= HB_BUFFER_FLAG_EOT; /* End-of-text */
+
+ hb_buffer_set_direction(pHbBuffer, aSubRun.maDirection);
+ hb_buffer_set_script(pHbBuffer, aSubRun.maScript);
+ hb_buffer_set_language(pHbBuffer, hb_language_from_string(sLanguage.getStr(), -1));
+ hb_buffer_set_flags(pHbBuffer, static_cast<hb_buffer_flags_t>(nHbFlags));
+ hb_buffer_add_utf16(
+ pHbBuffer, reinterpret_cast<uint16_t const *>(pStr), nLength,
+ nMinRunPos, nRunLen);
+ hb_buffer_set_cluster_level(pHbBuffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS);
+
+ // The shapers that we want HarfBuzz to use, in the order of
+ // preference. The coretext_aat shaper is available only on macOS,
+ // but there is no harm in always including it, HarfBuzz will
+ // ignore unavailable shapers.
+ const char*const pHbShapers[] = { "graphite2", "coretext_aat", "ot", "fallback", nullptr };
+ bool ok = hb_shape_full(pHbFont, pHbBuffer, maFeatures.data(), maFeatures.size(), pHbShapers);
+ assert(ok);
+ (void) ok;
+
+ int nRunGlyphCount = hb_buffer_get_length(pHbBuffer);
+ hb_glyph_info_t *pHbGlyphInfos = hb_buffer_get_glyph_infos(pHbBuffer, nullptr);
+ hb_glyph_position_t *pHbPositions = hb_buffer_get_glyph_positions(pHbBuffer, nullptr);
+
+ for (int i = 0; i < nRunGlyphCount; ++i) {
+ int32_t nGlyphIndex = pHbGlyphInfos[i].codepoint;
+ int32_t nCharPos = pHbGlyphInfos[i].cluster;
+ int32_t nCharCount = 0;
+ bool bInCluster = false;
+ bool bClusterStart = false;
+
+ // Find the number of characters that make up this glyph.
+ if (!bRightToLeft)
+ {
+ // If the cluster is the same as previous glyph, then this
+ // already consumed, skip.
+ if (i > 0 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i - 1].cluster)
+ {
+ nCharCount = 0;
+ bInCluster = true;
+ }
+ else
+ {
+ // Find the next glyph with a different cluster, or the
+ // end of text.
+ int j = i;
+ int32_t nNextCharPos = nCharPos;
+ while (nNextCharPos == nCharPos && j < nRunGlyphCount)
+ nNextCharPos = pHbGlyphInfos[j++].cluster;
+
+ if (nNextCharPos == nCharPos)
+ nNextCharPos = nEndRunPos;
+ nCharCount = nNextCharPos - nCharPos;
+ if ((i == 0 || pHbGlyphInfos[i].cluster != pHbGlyphInfos[i - 1].cluster) &&
+ (i < nRunGlyphCount - 1 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i + 1].cluster))
+ bClusterStart = true;
+ }
+ }
+ else
+ {
+ // If the cluster is the same as previous glyph, then this
+ // will be consumed later, skip.
+ if (i < nRunGlyphCount - 1 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i + 1].cluster)
+ {
+ nCharCount = 0;
+ bInCluster = true;
+ }
+ else
+ {
+ // Find the previous glyph with a different cluster, or
+ // the end of text.
+ int j = i;
+ int32_t nNextCharPos = nCharPos;
+ while (nNextCharPos == nCharPos && j >= 0)
+ nNextCharPos = pHbGlyphInfos[j--].cluster;
+
+ if (nNextCharPos == nCharPos)
+ nNextCharPos = nEndRunPos;
+ nCharCount = nNextCharPos - nCharPos;
+ if ((i == nRunGlyphCount - 1 || pHbGlyphInfos[i].cluster != pHbGlyphInfos[i + 1].cluster) &&
+ (i > 0 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i - 1].cluster))
+ bClusterStart = true;
+ }
+ }
+
+ // if needed request glyph fallback by updating LayoutArgs
+ if (!nGlyphIndex)
+ {
+ SetNeedFallback(rArgs, nCharPos, bRightToLeft);
+ if (SalLayoutFlags::ForFallback & rArgs.mnFlags)
+ continue;
+ }
+
+ GlyphItemFlags nGlyphFlags = GlyphItemFlags::NONE;
+ if (bRightToLeft)
+ nGlyphFlags |= GlyphItemFlags::IS_RTL_GLYPH;
+
+ if (bClusterStart)
+ nGlyphFlags |= GlyphItemFlags::IS_CLUSTER_START;
+
+ if (bInCluster)
+ nGlyphFlags |= GlyphItemFlags::IS_IN_CLUSTER;
+
+ sal_Int32 indexUtf16 = nCharPos;
+ sal_UCS4 aChar = rArgs.mrStr.iterateCodePoints(&indexUtf16, 0);
+
+ if (u_getIntPropertyValue(aChar, UCHAR_GENERAL_CATEGORY) == U_NON_SPACING_MARK)
+ nGlyphFlags |= GlyphItemFlags::IS_DIACRITIC;
+
+ if (u_isUWhiteSpace(aChar))
+ nGlyphFlags |= GlyphItemFlags::IS_SPACING;
+
+ if (aSubRun.maScript == HB_SCRIPT_ARABIC &&
+ HB_DIRECTION_IS_BACKWARD(aSubRun.maDirection) &&
+ !(nGlyphFlags & GlyphItemFlags::IS_SPACING))
+ {
+ nGlyphFlags |= GlyphItemFlags::ALLOW_KASHIDA;
+ rArgs.mnFlags |= SalLayoutFlags::KashidaJustification;
+ }
+
+ DeviceCoordinate nAdvance, nXOffset, nYOffset;
+ if (aSubRun.maDirection == HB_DIRECTION_TTB)
+ {
+ nGlyphFlags |= GlyphItemFlags::IS_VERTICAL;
+
+ // We have glyph offsets that is relative to h origin now,
+ // add the origin back so it is relative to v origin.
+ hb_font_add_glyph_origin_for_direction(pHbFont,
+ nGlyphIndex,
+ HB_DIRECTION_TTB,
+ &pHbPositions[i].x_offset ,
+ &pHbPositions[i].y_offset );
+ nAdvance = -pHbPositions[i].y_advance;
+ nXOffset = -pHbPositions[i].y_offset;
+ nYOffset = -pHbPositions[i].x_offset;
+ }
+ else
+ {
+ nAdvance = pHbPositions[i].x_advance;
+ nXOffset = pHbPositions[i].x_offset;
+ nYOffset = -pHbPositions[i].y_offset;
+ }
+
+ nAdvance = std::lround(nAdvance * nXScale);
+ nXOffset = std::lround(nXOffset * nXScale);
+ nYOffset = std::lround(nYOffset * nYScale);
+
+ Point aNewPos(aCurrPos.X() + nXOffset, aCurrPos.Y() + nYOffset);
+ const GlyphItem aGI(nCharPos, nCharCount, nGlyphIndex, aNewPos, nGlyphFlags,
+ nAdvance, nXOffset, &GetFont());
+ m_GlyphItems.Impl()->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.Impl()->mnFlags = rArgs.mnFlags;
+
+ return true;
+}
+
+void GenericSalLayout::GetCharWidths(DeviceCoordinate* pCharWidths) const
+{
+ const int nCharCount = mnEndCharPos - mnMinCharPos;
+
+ for (int i = 0; i < nCharCount; ++i)
+ pCharWidths[i] = 0;
+
+ for (auto const& aGlyphItem : *m_GlyphItems.Impl())
+ {
+ const int nIndex = aGlyphItem.charPos() - mnMinCharPos;
+ if (nIndex >= nCharCount)
+ continue;
+ pCharWidths[nIndex] += aGlyphItem.m_nNewWidth;
+ }
+}
+
+// A note on how Kashida justification is implemented (because it took me 5
+// years to figure it out):
+// The decision to insert Kashidas, where and how much is taken by Writer.
+// This decision is communicated to us in a very indirect way; by increasing
+// the width of the character after which Kashidas should be inserted by the
+// desired amount.
+//
+// Writer eventually calls IsKashidaPosValid() to check whether it can insert a
+// Kashida between two characters or not.
+//
+// Here we do:
+// - In LayoutText() set KashidaJustification flag based on text script.
+// - In ApplyDXArray():
+// * Check the above flag to decide whether to insert Kashidas or not.
+// * For any RTL glyph that has DX adjustment, insert enough Kashidas to
+// fill in the added space.
+
+void GenericSalLayout::ApplyDXArray(const ImplLayoutArgs& rArgs)
+{
+ if (rArgs.mpDXArray == nullptr)
+ return;
+
+ int nCharCount = mnEndCharPos - mnMinCharPos;
+ std::unique_ptr<DeviceCoordinate[]> const pOldCharWidths(new DeviceCoordinate[nCharCount]);
+ std::unique_ptr<DeviceCoordinate[]> const pNewCharWidths(new DeviceCoordinate[nCharCount]);
+
+ // Get the natural character widths (i.e. before applying DX adjustments).
+ GetCharWidths(pOldCharWidths.get());
+
+ // Calculate the character widths after DX adjustments.
+ for (int i = 0; i < nCharCount; ++i)
+ {
+ if (i == 0)
+ pNewCharWidths[i] = rArgs.mpDXArray[i];
+ else
+ pNewCharWidths[i] = rArgs.mpDXArray[i] - rArgs.mpDXArray[i - 1];
+ }
+
+ bool bKashidaJustify = false;
+ DeviceCoordinate nKashidaWidth = 0;
+ hb_codepoint_t nKashidaIndex = 0;
+ if (rArgs.mnFlags & SalLayoutFlags::KashidaJustification)
+ {
+ hb_font_t *pHbFont = GetFont().GetHbFont();
+ // Find Kashida glyph width and index.
+ if (hb_font_get_glyph(pHbFont, 0x0640, 0, &nKashidaIndex))
+ nKashidaWidth = GetFont().GetKashidaWidth();
+ bKashidaJustify = nKashidaWidth != 0;
+ }
+
+ // Map of Kashida insertion points (in the glyph items vector) and the
+ // requested width.
+ std::map<size_t, DeviceCoordinate> pKashidas;
+
+ // The accumulated difference in X position.
+ DeviceCoordinate nDelta = 0;
+
+ // Apply the DX adjustments to glyph positions and widths.
+ size_t i = 0;
+ while (i < m_GlyphItems.Impl()->size())
+ {
+ // Accumulate the width difference for all characters corresponding to
+ // this glyph.
+ int nCharPos = (*m_GlyphItems.Impl())[i].charPos() - mnMinCharPos;
+ DeviceCoordinate nDiff = 0;
+ for (int j = 0; j < (*m_GlyphItems.Impl())[i].charCount(); j++)
+ nDiff += pNewCharWidths[nCharPos + j] - pOldCharWidths[nCharPos + j];
+
+ if (!(*m_GlyphItems.Impl())[i].IsRTLGlyph())
+ {
+ // Adjust the width and position of the first (leftmost) glyph in
+ // the cluster.
+ (*m_GlyphItems.Impl())[i].m_nNewWidth += nDiff;
+ (*m_GlyphItems.Impl())[i].m_aLinearPos.AdjustX(nDelta);
+
+ // Adjust the position of the rest of the glyphs in the cluster.
+ while (++i < m_GlyphItems.Impl()->size())
+ {
+ if (!(*m_GlyphItems.Impl())[i].IsInCluster())
+ break;
+ (*m_GlyphItems.Impl())[i].m_aLinearPos.AdjustX(nDelta);
+ }
+ }
+ else if ((*m_GlyphItems.Impl())[i].IsInCluster())
+ {
+ // RTL glyph in the middle of the cluster, will be handled in the
+ // loop below.
+ i++;
+ }
+ else
+ {
+ // Adjust the width and position of the first (rightmost) glyph in
+ // the cluster.
+ // For RTL, we put all the adjustment to the left of the glyph.
+ (*m_GlyphItems.Impl())[i].m_nNewWidth += nDiff;
+ (*m_GlyphItems.Impl())[i].m_aLinearPos.AdjustX(nDelta + nDiff);
+
+ // Adjust the X position of all glyphs in the cluster.
+ size_t j = i;
+ while (j > 0)
+ {
+ --j;
+ if (!(*m_GlyphItems.Impl())[j].IsInCluster())
+ break;
+ (*m_GlyphItems.Impl())[j].m_aLinearPos.AdjustX(nDelta + nDiff);
+ }
+
+ // If this glyph is Kashida-justifiable, then mark this as a
+ // Kashida position. Since this must be a RTL glyph, we mark the
+ // last glyph in the cluster not the first as this would be the
+ // base glyph.
+ if (bKashidaJustify && (*m_GlyphItems.Impl())[i].AllowKashida() &&
+ nDiff > (*m_GlyphItems.Impl())[i].charCount()) // Rounding errors, 1 pixel per character!
+ {
+ pKashidas[i] = nDiff;
+ // Move any non-spacing marks attached to this cluster as well.
+ // Looping backward because this is RTL glyph.
+ while (j > 0)
+ {
+ if (!(*m_GlyphItems.Impl())[j].IsDiacritic())
+ break;
+ (*m_GlyphItems.Impl())[j--].m_aLinearPos.AdjustX(nDiff);
+ }
+ }
+ i++;
+ }
+
+ // Increment the delta, the loop above makes sure we do so only once
+ // for every character (cluster) not for every glyph (otherwise we
+ // would apply it multiple times for each glyphs belonging to the same
+ // character which is wrong since DX adjustments are character based).
+ nDelta += nDiff;
+ }
+
+ // Insert Kashida glyphs.
+ if (bKashidaJustify && !pKashidas.empty())
+ {
+ size_t nInserted = 0;
+ for (auto const& pKashida : pKashidas)
+ {
+ auto pGlyphIter = m_GlyphItems.Impl()->begin() + nInserted + pKashida.first;
+
+ // The total Kashida width.
+ DeviceCoordinate nTotalWidth = pKashida.second;
+
+ // Number of times to repeat each Kashida.
+ int nCopies = 1;
+ if (nTotalWidth > nKashidaWidth)
+ nCopies = nTotalWidth / nKashidaWidth;
+
+ // See if we can improve the fit by adding an extra Kashidas and
+ // squeezing them together a bit.
+ DeviceCoordinate nOverlap = 0;
+ DeviceCoordinate nShortfall = nTotalWidth - nKashidaWidth * nCopies;
+ if (nShortfall > 0)
+ {
+ ++nCopies;
+ DeviceCoordinate nExcess = nCopies * nKashidaWidth - nTotalWidth;
+ if (nExcess > 0)
+ nOverlap = nExcess / (nCopies - 1);
+ }
+
+ Point aPos(pGlyphIter->m_aLinearPos.getX() - nTotalWidth, 0);
+ int nCharPos = pGlyphIter->charPos();
+ GlyphItemFlags const nFlags = GlyphItemFlags::IS_IN_CLUSTER | GlyphItemFlags::IS_RTL_GLYPH;
+ while (nCopies--)
+ {
+ GlyphItem aKashida(nCharPos, 0, nKashidaIndex, aPos, nFlags, nKashidaWidth, 0, &GetFont());
+ pGlyphIter = m_GlyphItems.Impl()->insert(pGlyphIter, aKashida);
+ aPos.AdjustX(nKashidaWidth );
+ aPos.AdjustX( -nOverlap );
+ ++pGlyphIter;
+ ++nInserted;
+ }
+ }
+ }
+}
+
+bool GenericSalLayout::IsKashidaPosValid(int nCharPos) const
+{
+ for (auto pIter = m_GlyphItems.Impl()->begin(); pIter != m_GlyphItems.Impl()->end(); ++pIter)
+ {
+ if (pIter->charPos() == nCharPos)
+ {
+ // The position is the first glyph, this would happen if we
+ // changed the text styling in the middle of a word. Since we don’t
+ // do ligatures across layout engine instances, this can’t be a
+ // ligature so it should be fine.
+ if (pIter == m_GlyphItems.Impl()->begin())
+ return true;
+
+ // If the character is not supported by this layout, return false
+ // so that fallback layouts would be checked for it.
+ if (pIter->glyphId() == 0)
+ break;
+
+ // Search backwards for previous glyph belonging to a different
+ // character. We are looking backwards because we are dealing with
+ // RTL glyphs, which will be in visual order.
+ for (auto pPrev = pIter - 1; pPrev != m_GlyphItems.Impl()->begin(); --pPrev)
+ {
+ if (pPrev->charPos() != nCharPos)
+ {
+ // Check if the found glyph belongs to the next character,
+ // otherwise the current glyph will be a ligature which is
+ // invalid kashida position.
+ if (pPrev->charPos() == (nCharPos + 1))
+ return true;
+ break;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/FileDefinitionWidgetDraw.cxx b/vcl/source/gdi/FileDefinitionWidgetDraw.cxx
new file mode 100644
index 000000000..29234254b
--- /dev/null
+++ b/vcl/source/gdi/FileDefinitionWidgetDraw.cxx
@@ -0,0 +1,1080 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <FileDefinitionWidgetDraw.hxx>
+#include <widgetdraw/WidgetDefinitionReader.hxx>
+
+#include <sal/config.h>
+#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>
+
+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(OUString const& 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(OString const& rValue, int nDefault)
+{
+ if (rValue.isEmpty())
+ return nDefault;
+ if (!comphelper::string::isdigitAsciiString(rValue))
+ return nDefault;
+ return rValue.toInt32();
+}
+
+bool getSettingValueBool(OString const& rValue, bool bDefault)
+{
+ if (rValue.isEmpty())
+ 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("online");
+#ifdef IOS
+ if (!m_pWidgetDefinition)
+ m_pWidgetDefinition = getWidgetDefinitionForTheme("ios");
+#endif
+
+ if (m_pWidgetDefinition)
+ {
+ auto& pSettings = m_pWidgetDefinition->mpSettings;
+
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maNWFData.mbNoFocusRects = true;
+ pSVData->maNWFData.mbNoFocusRectsForFlatButtons = true;
+ pSVData->maNWFData.mbNoActiveTabTextRaise
+ = getSettingValueBool(pSettings->msNoActiveTabTextRaise, true);
+ pSVData->maNWFData.mbCenteredTabs = getSettingValueBool(pSettings->msCenteredTabs, true);
+ pSVData->maNWFData.mbProgressNeedsErase = true;
+ pSVData->maNWFData.mnStatusBarLowerRightOffset = 10;
+ pSVData->maNWFData.mbCanDrawWidgetAnySize = true;
+
+ int nDefaultListboxEntryMargin = pSVData->maNWFData.mnListBoxEntryMargin;
+ pSVData->maNWFData.mnListBoxEntryMargin
+ = getSettingValueInteger(pSettings->msListBoxEntryMargin, nDefaultListboxEntryMargin);
+
+ m_bIsActive = true;
+ }
+}
+
+bool FileDefinitionWidgetDraw::isNativeControlSupported(ControlType eType, ControlPart ePart)
+{
+ switch (eType)
+ {
+ case ControlType::Generic:
+ case ControlType::Pushbutton:
+ case ControlType::Radiobutton:
+ case ControlType::Checkbox:
+ return true;
+ case ControlType::Combobox:
+ if (ePart == ControlPart::HasBackgroundTexture)
+ return false;
+ return true;
+ case ControlType::Editbox:
+ case ControlType::EditboxNoBorder:
+ case ControlType::MultilineEditbox:
+ return true;
+ case ControlType::Listbox:
+ if (ePart == ControlPart::HasBackgroundTexture)
+ return false;
+ return true;
+ case ControlType::Spinbox:
+ if (ePart == ControlPart::AllButtons)
+ return false;
+ return true;
+ case ControlType::SpinButtons:
+ return false;
+ case ControlType::TabItem:
+ case ControlType::TabPane:
+ case ControlType::TabHeader:
+ case ControlType::TabBody:
+ return true;
+ case ControlType::Scrollbar:
+ if (ePart == ControlPart::DrawBackgroundHorz
+ || ePart == ControlPart::DrawBackgroundVert)
+ return false;
+ return true;
+ case ControlType::Slider:
+ case ControlType::Fixedline:
+ case ControlType::Toolbar:
+ return true;
+ case ControlType::Menubar:
+ case ControlType::MenuPopup:
+ return true;
+ case ControlType::Progress:
+ return true;
+ case ControlType::IntroProgress:
+ return false;
+ case ControlType::Tooltip:
+ return true;
+ case ControlType::WindowBackground:
+ case ControlType::Frame:
+ case ControlType::ListNode:
+ case ControlType::ListNet:
+ case ControlType::ListHeader:
+ return true;
+ }
+
+ return false;
+}
+
+bool FileDefinitionWidgetDraw::hitTestNativeControl(
+ ControlType /*eType*/, ControlPart /*ePart*/,
+ const tools::Rectangle& /*rBoundingControlRegion*/, const Point& /*aPos*/, bool& /*rIsInside*/)
+{
+ return false;
+}
+
+namespace
+{
+void drawFromDrawCommands(gfx::DrawRoot const& rDrawRoot, SalGraphics& rGraphics, long nX, long nY,
+ long nWidth, 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));
+ rGraphics.DrawPolyPolygon(basegfx::B2DHomMatrix(),
+ basegfx::B2DPolyPolygon(aB2DPolygon),
+ 1.0 - rRectangle.mnOpacity, nullptr);
+ }
+ 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.SetTransparency(rStop.mfOpacity * (1.0f - rRectangle.mnOpacity));
+ aGradient.maStops.emplace_back(aColor, rStop.mfOffset);
+ }
+ rGraphics.DrawGradient(basegfx::B2DPolyPolygon(aB2DPolygon), aGradient);
+ }
+ }
+ if (rRectangle.mpStrokeColor)
+ {
+ rGraphics.SetLineColor(Color(*rRectangle.mpStrokeColor));
+ rGraphics.SetFillColor();
+ rGraphics.DrawPolyLine(basegfx::B2DHomMatrix(), aB2DPolygon,
+ 1.0 - rRectangle.mnOpacity, rRectangle.mnStrokeWidth,
+ nullptr, // MM01
+ basegfx::B2DLineJoin::Round, css::drawing::LineCap_ROUND,
+ 0.0f, false, nullptr);
+ }
+ }
+ 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));
+ rGraphics.DrawPolyPolygon(basegfx::B2DHomMatrix(), aPolyPolygon,
+ 1.0 - rPath.mnOpacity, nullptr);
+ }
+ if (rPath.mpStrokeColor)
+ {
+ rGraphics.SetLineColor(Color(*rPath.mpStrokeColor));
+ rGraphics.SetFillColor();
+ for (auto const& rPolygon : aPolyPolygon)
+ {
+ rGraphics.DrawPolyLine(basegfx::B2DHomMatrix(), rPolygon,
+ 1.0 - rPath.mnOpacity, rPath.mnStrokeWidth,
+ nullptr, // MM01
+ basegfx::B2DLineJoin::Round,
+ css::drawing::LineCap_ROUND, 0.0f, false, nullptr);
+ }
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+void munchDrawCommands(std::vector<std::shared_ptr<WidgetDrawAction>> const& rDrawActions,
+ SalGraphics& rGraphics, long nX, long nY, long nWidth, 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);
+ rGraphics.DrawPolyPolygon(basegfx::B2DHomMatrix(),
+ basegfx::B2DPolyPolygon(aB2DPolygon), 0.0f, nullptr);
+ rGraphics.SetLineColor(rWidgetDraw.maStrokeColor);
+ rGraphics.SetFillColor();
+ rGraphics.DrawPolyLine(
+ basegfx::B2DHomMatrix(), aB2DPolygon, 0.0f, rWidgetDraw.mnStrokeWidth,
+ nullptr, // MM01
+ basegfx::B2DLineJoin::Round, css::drawing::LineCap_ROUND, 0.0f, false, nullptr);
+ }
+ 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) },
+ };
+
+ rGraphics.DrawPolyLine(
+ basegfx::B2DHomMatrix(), aB2DPolygon, 0.0f, rWidgetDraw.mnStrokeWidth,
+ nullptr, // MM01
+ basegfx::B2DLineJoin::Round, css::drawing::LineCap_ROUND, 0.0f, false, nullptr);
+ }
+ 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)
+ {
+ rCacheImages.insert(std::make_pair(rCacheKey, aBitmap));
+ }
+ }
+ else
+ {
+ aBitmap = aIterator->second;
+ }
+
+ long nImageWidth = aBitmap.GetSizePixel().Width();
+ long nImageHeight = aBitmap.GetSizePixel().Height();
+ SalTwoRect aTR(0, 0, nImageWidth, nImageHeight, nX, nY, nImageWidth / nScaleFactor,
+ nImageHeight / nScaleFactor);
+ if (!!aBitmap)
+ {
+ const std::shared_ptr<SalBitmap> pSalBitmap
+ = aBitmap.GetBitmap().ImplGetSalBitmap();
+ if (aBitmap.IsAlpha())
+ {
+ const std::shared_ptr<SalBitmap> pSalBitmapAlpha
+ = aBitmap.GetAlpha().ImplGetSalBitmap();
+ rGraphics.DrawBitmap(aTR, *pSalBitmap, *pSalBitmapAlpha, nullptr);
+ }
+ else
+ {
+ rGraphics.DrawBitmap(aTR, *pSalBitmap, nullptr);
+ }
+ }
+ }
+ 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, long nX, long nY,
+ long nWidth, 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.getAntiAliasB2DDraw();
+ m_rGraphics.setAntiAliasB2DDraw(true);
+
+ long nWidth = rControlRegion.GetWidth() - 1;
+ long nHeight = rControlRegion.GetHeight() - 1;
+ long nX = rControlRegion.Left();
+ 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;
+
+ long nUpperX = pSpinVal->maUpperRect.Left();
+ long nUpperY = pSpinVal->maUpperRect.Top();
+ long nUpperWidth = pSpinVal->maUpperRect.GetWidth() - 1;
+ 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;
+
+ long nLowerX = pSpinVal->maLowerRect.Left();
+ long nLowerY = pSpinVal->maLowerRect.Top();
+ long nLowerWidth = pSpinVal->maLowerRect.GetWidth() - 1;
+ 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);
+ long nThumbX = pSliderValue->maThumbRect.Left();
+ long nThumbY = pSliderValue->maThumbRect.Top();
+ long nThumbWidth = pSliderValue->maThumbRect.GetWidth() - 1;
+ long nThumbHeight = pSliderValue->maThumbRect.GetHeight() - 1;
+
+ if (ePart == ControlPart::TrackHorzArea)
+ {
+ 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)
+ {
+ long nCenterY = nThumbY + nThumbHeight / 2;
+
+ bOK = resolveDefinition(eType, ControlPart::TrackVertUpper, eState, rValue, nX, nY,
+ nWidth, nCenterY - nY);
+ if (bOK)
+ bOK = resolveDefinition(eType, ControlPart::TrackVertLower, eState, rValue, nY,
+ nCenterY, nWidth, nY + nHeight - nCenterY);
+ }
+
+ if (bOK)
+ {
+ bOK = resolveDefinition(eType, ControlPart::Button,
+ eState | pSliderValue->mnThumbState, rValue, nThumbX,
+ nThumbY, nThumbWidth, nThumbHeight);
+ }
+ }
+ break;
+ case ControlType::Fixedline:
+ {
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ break;
+ case ControlType::Toolbar:
+ {
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ break;
+ case ControlType::Menubar:
+ case ControlType::MenuPopup:
+ {
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ break;
+ case ControlType::Progress:
+ {
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ break;
+ case ControlType::IntroProgress:
+ break;
+ case ControlType::Tooltip:
+ {
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ break;
+ case ControlType::WindowBackground:
+ case ControlType::Frame:
+ {
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ break;
+ case ControlType::ListNode:
+ case ControlType::ListNet:
+ case ControlType::ListHeader:
+ {
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ break;
+ default:
+ break;
+ }
+
+ m_rGraphics.setAntiAliasB2DDraw(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;
+
+ long nWidth = std::max(rBoundingControlRegion.GetWidth() + pPart->mnMarginWidth,
+ long(pPart->mnWidth));
+ long nHeight = std::max(rBoundingControlRegion.GetHeight() + pPart->mnMarginHeight,
+ long(pPart->mnHeight));
+
+ rNativeBoundingRegion = tools::Rectangle(aLocation, Size(nWidth, nHeight));
+ rNativeContentRegion = rNativeBoundingRegion;
+ return true;
+ }
+ case ControlType::Editbox:
+ case ControlType::EditboxNoBorder:
+ case ControlType::MultilineEditbox:
+ {
+ sal_Int32 nHeight = rBoundingControlRegion.GetHeight();
+
+ auto const& pPart = m_pWidgetDefinition->getDefinition(eType, ControlPart::Entire);
+ if (pPart)
+ nHeight = std::max(nHeight, pPart->mnHeight);
+
+ Size aSize(rBoundingControlRegion.GetWidth(), nHeight);
+ rNativeContentRegion = tools::Rectangle(aLocation, aSize);
+ rNativeBoundingRegion = rNativeContentRegion;
+ rNativeBoundingRegion.expand(2);
+ return true;
+ }
+ break;
+ case ControlType::Scrollbar:
+ {
+ if (ePart == ControlPart::ButtonUp || ePart == ControlPart::ButtonDown
+ || ePart == ControlPart::ButtonLeft || ePart == ControlPart::ButtonRight)
+ {
+ rNativeContentRegion = tools::Rectangle(aLocation, Size(0, 0));
+ rNativeBoundingRegion = rNativeContentRegion;
+ return true;
+ }
+ else
+ {
+ rNativeBoundingRegion = rBoundingControlRegion;
+ rNativeContentRegion = rNativeBoundingRegion;
+ return true;
+ }
+ }
+ break;
+ case ControlType::Combobox:
+ case ControlType::Listbox:
+ {
+ auto const& pPart = m_pWidgetDefinition->getDefinition(eType, ControlPart::ButtonDown);
+ Size aComboButtonSize(pPart->mnWidth, pPart->mnHeight);
+
+ if (ePart == ControlPart::ButtonDown)
+ {
+ Point aPoint(aLocation.X() + rBoundingControlRegion.GetWidth()
+ - aComboButtonSize.Width() - 1,
+ aLocation.Y());
+ rNativeContentRegion = tools::Rectangle(aPoint, aComboButtonSize);
+ rNativeBoundingRegion = rNativeContentRegion;
+ return true;
+ }
+ else if (ePart == ControlPart::SubEdit)
+ {
+ Size aSize(rBoundingControlRegion.GetWidth() - aComboButtonSize.Width(),
+ aComboButtonSize.Height());
+ rNativeContentRegion = tools::Rectangle(aLocation + Point(1, 1), aSize);
+ rNativeBoundingRegion = rNativeContentRegion;
+ return true;
+ }
+ else if (ePart == ControlPart::Entire)
+ {
+ Size aSize(rBoundingControlRegion.GetWidth(), aComboButtonSize.Height());
+ rNativeContentRegion = tools::Rectangle(aLocation, aSize);
+ rNativeBoundingRegion = rNativeContentRegion;
+ rNativeBoundingRegion.expand(2);
+ return true;
+ }
+ }
+ break;
+ case ControlType::Slider:
+ if (ePart == ControlPart::ThumbHorz || ePart == ControlPart::ThumbVert)
+ {
+ rNativeContentRegion = tools::Rectangle(aLocation, Size(28, 28));
+ rNativeBoundingRegion = rNativeContentRegion;
+ return true;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+bool FileDefinitionWidgetDraw::updateSettings(AllSettings& rSettings)
+{
+ StyleSettings aStyleSet = rSettings.GetStyleSettings();
+
+ auto& pDefinitionStyle = m_pWidgetDefinition->mpStyle;
+
+ aStyleSet.SetFaceColor(pDefinitionStyle->maFaceColor);
+ aStyleSet.SetCheckedColor(pDefinitionStyle->maCheckedColor);
+ aStyleSet.SetLightColor(pDefinitionStyle->maLightColor);
+ aStyleSet.SetLightBorderColor(pDefinitionStyle->maLightBorderColor);
+ aStyleSet.SetShadowColor(pDefinitionStyle->maShadowColor);
+ aStyleSet.SetDarkShadowColor(pDefinitionStyle->maDarkShadowColor);
+ aStyleSet.SetDefaultButtonTextColor(pDefinitionStyle->maDefaultButtonTextColor);
+ aStyleSet.SetButtonTextColor(pDefinitionStyle->maButtonTextColor);
+ aStyleSet.SetDefaultActionButtonTextColor(pDefinitionStyle->maDefaultActionButtonTextColor);
+ aStyleSet.SetActionButtonTextColor(pDefinitionStyle->maActionButtonTextColor);
+ aStyleSet.SetFlatButtonTextColor(pDefinitionStyle->maFlatButtonTextColor);
+ aStyleSet.SetDefaultButtonRolloverTextColor(pDefinitionStyle->maDefaultButtonRolloverTextColor);
+ aStyleSet.SetButtonRolloverTextColor(pDefinitionStyle->maButtonRolloverTextColor);
+ aStyleSet.SetDefaultActionButtonRolloverTextColor(
+ pDefinitionStyle->maDefaultActionButtonRolloverTextColor);
+ aStyleSet.SetActionButtonRolloverTextColor(pDefinitionStyle->maActionButtonRolloverTextColor);
+ aStyleSet.SetFlatButtonRolloverTextColor(pDefinitionStyle->maFlatButtonRolloverTextColor);
+ aStyleSet.SetDefaultButtonPressedRolloverTextColor(
+ pDefinitionStyle->maDefaultButtonPressedRolloverTextColor);
+ aStyleSet.SetButtonPressedRolloverTextColor(pDefinitionStyle->maButtonPressedRolloverTextColor);
+ aStyleSet.SetDefaultActionButtonPressedRolloverTextColor(
+ pDefinitionStyle->maDefaultActionButtonPressedRolloverTextColor);
+ aStyleSet.SetActionButtonPressedRolloverTextColor(
+ pDefinitionStyle->maActionButtonPressedRolloverTextColor);
+ aStyleSet.SetFlatButtonPressedRolloverTextColor(
+ pDefinitionStyle->maFlatButtonPressedRolloverTextColor);
+ aStyleSet.SetRadioCheckTextColor(pDefinitionStyle->maRadioCheckTextColor);
+ aStyleSet.SetGroupTextColor(pDefinitionStyle->maGroupTextColor);
+ aStyleSet.SetLabelTextColor(pDefinitionStyle->maLabelTextColor);
+ aStyleSet.SetWindowColor(pDefinitionStyle->maWindowColor);
+ aStyleSet.SetWindowTextColor(pDefinitionStyle->maWindowTextColor);
+ aStyleSet.SetDialogColor(pDefinitionStyle->maDialogColor);
+ aStyleSet.SetDialogTextColor(pDefinitionStyle->maDialogTextColor);
+ aStyleSet.SetWorkspaceColor(pDefinitionStyle->maWorkspaceColor);
+ aStyleSet.SetMonoColor(pDefinitionStyle->maMonoColor);
+ aStyleSet.SetFieldColor(pDefinitionStyle->maFieldColor);
+ aStyleSet.SetFieldTextColor(pDefinitionStyle->maFieldTextColor);
+ aStyleSet.SetFieldRolloverTextColor(pDefinitionStyle->maFieldRolloverTextColor);
+ aStyleSet.SetActiveColor(pDefinitionStyle->maActiveColor);
+ aStyleSet.SetActiveTextColor(pDefinitionStyle->maActiveTextColor);
+ aStyleSet.SetActiveBorderColor(pDefinitionStyle->maActiveBorderColor);
+ aStyleSet.SetDeactiveColor(pDefinitionStyle->maDeactiveColor);
+ aStyleSet.SetDeactiveTextColor(pDefinitionStyle->maDeactiveTextColor);
+ aStyleSet.SetDeactiveBorderColor(pDefinitionStyle->maDeactiveBorderColor);
+ aStyleSet.SetMenuColor(pDefinitionStyle->maMenuColor);
+ aStyleSet.SetMenuBarColor(pDefinitionStyle->maMenuBarColor);
+ aStyleSet.SetMenuBarRolloverColor(pDefinitionStyle->maMenuBarRolloverColor);
+ aStyleSet.SetMenuBorderColor(pDefinitionStyle->maMenuBorderColor);
+ aStyleSet.SetMenuTextColor(pDefinitionStyle->maMenuTextColor);
+ aStyleSet.SetMenuBarTextColor(pDefinitionStyle->maMenuBarTextColor);
+ aStyleSet.SetMenuBarRolloverTextColor(pDefinitionStyle->maMenuBarRolloverTextColor);
+ aStyleSet.SetMenuBarHighlightTextColor(pDefinitionStyle->maMenuBarHighlightTextColor);
+ aStyleSet.SetMenuHighlightColor(pDefinitionStyle->maMenuHighlightColor);
+ aStyleSet.SetMenuHighlightTextColor(pDefinitionStyle->maMenuHighlightTextColor);
+ aStyleSet.SetHighlightColor(pDefinitionStyle->maHighlightColor);
+ aStyleSet.SetHighlightTextColor(pDefinitionStyle->maHighlightTextColor);
+ aStyleSet.SetActiveTabColor(pDefinitionStyle->maActiveTabColor);
+ aStyleSet.SetInactiveTabColor(pDefinitionStyle->maInactiveTabColor);
+ aStyleSet.SetTabTextColor(pDefinitionStyle->maTabTextColor);
+ aStyleSet.SetTabRolloverTextColor(pDefinitionStyle->maTabRolloverTextColor);
+ aStyleSet.SetTabHighlightTextColor(pDefinitionStyle->maTabHighlightTextColor);
+ aStyleSet.SetDisableColor(pDefinitionStyle->maDisableColor);
+ aStyleSet.SetHelpColor(pDefinitionStyle->maHelpColor);
+ aStyleSet.SetHelpTextColor(pDefinitionStyle->maHelpTextColor);
+ aStyleSet.SetLinkColor(pDefinitionStyle->maLinkColor);
+ aStyleSet.SetVisitedLinkColor(pDefinitionStyle->maVisitedLinkColor);
+ aStyleSet.SetToolTextColor(pDefinitionStyle->maToolTextColor);
+ aStyleSet.SetFontColor(pDefinitionStyle->maFontColor);
+
+ auto& pSettings = m_pWidgetDefinition->mpSettings;
+
+ int nFontSize = getSettingValueInteger(pSettings->msDefaultFontSize, 10);
+ vcl::Font aFont(FAMILY_SWISS, Size(0, nFontSize));
+ aFont.SetCharSet(osl_getThreadTextEncoding());
+ aFont.SetWeight(WEIGHT_NORMAL);
+ aFont.SetFamilyName("Liberation Sans");
+ aStyleSet.SetAppFont(aFont);
+ aStyleSet.SetHelpFont(aFont);
+ aStyleSet.SetMenuFont(aFont);
+ aStyleSet.SetToolFont(aFont);
+ aStyleSet.SetGroupFont(aFont);
+ aStyleSet.SetLabelFont(aFont);
+ aStyleSet.SetRadioCheckFont(aFont);
+ aStyleSet.SetPushButtonFont(aFont);
+ aStyleSet.SetFieldFont(aFont);
+ aStyleSet.SetIconFont(aFont);
+ aStyleSet.SetTabFont(aFont);
+
+ aFont.SetWeight(WEIGHT_BOLD);
+ aStyleSet.SetFloatTitleFont(aFont);
+ aStyleSet.SetTitleFont(aFont);
+
+ int nTitleHeight = getSettingValueInteger(pSettings->msTitleHeight, aStyleSet.GetTitleHeight());
+ aStyleSet.SetTitleHeight(nTitleHeight);
+
+ int nFloatTitleHeight
+ = getSettingValueInteger(pSettings->msFloatTitleHeight, aStyleSet.GetFloatTitleHeight());
+ aStyleSet.SetFloatTitleHeight(nFloatTitleHeight);
+
+ int nLogicWidth = getSettingValueInteger(pSettings->msListBoxPreviewDefaultLogicWidth,
+ 15); // See vcl/source/app/settings.cxx
+ int nLogicHeight = getSettingValueInteger(pSettings->msListBoxPreviewDefaultLogicHeight, 7);
+ aStyleSet.SetListBoxPreviewDefaultLogicSize(Size(nLogicWidth, nLogicHeight));
+
+ rSettings.SetStyleSettings(aStyleSet);
+
+ return true;
+}
+
+} // end vcl namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/TypeSerializer.cxx b/vcl/source/gdi/TypeSerializer.cxx
new file mode 100644
index 000000000..e2f399f2d
--- /dev/null
+++ b/vcl/source/gdi/TypeSerializer.cxx
@@ -0,0 +1,419 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <TypeSerializer.hxx>
+#include <tools/vcompat.hxx>
+#include <sal/log.hxx>
+#include <comphelper/fileformat.h>
+#include <vcl/gdimtf.hxx>
+#include <vcl/dibtools.hxx>
+
+TypeSerializer::TypeSerializer(SvStream& rStream)
+ : GenericTypeSerializer(rStream)
+{
+}
+
+void TypeSerializer::readGradient(Gradient& rGradient)
+{
+ VersionCompat aCompat(mrStream, StreamMode::READ);
+
+ sal_uInt16 nStyle;
+ Color aStartColor;
+ Color aEndColor;
+ sal_uInt16 nAngle;
+ sal_uInt16 nBorder;
+ sal_uInt16 nOffsetX;
+ sal_uInt16 nOffsetY;
+ sal_uInt16 nIntensityStart;
+ sal_uInt16 nIntensityEnd;
+ sal_uInt16 nStepCount;
+
+ mrStream.ReadUInt16(nStyle);
+ readColor(aStartColor);
+ readColor(aEndColor);
+ mrStream.ReadUInt16(nAngle);
+ mrStream.ReadUInt16(nBorder);
+ mrStream.ReadUInt16(nOffsetX);
+ mrStream.ReadUInt16(nOffsetY);
+ mrStream.ReadUInt16(nIntensityStart);
+ mrStream.ReadUInt16(nIntensityEnd);
+ mrStream.ReadUInt16(nStepCount);
+
+ rGradient.SetStyle(static_cast<GradientStyle>(nStyle));
+ rGradient.SetStartColor(aStartColor);
+ rGradient.SetEndColor(aEndColor);
+ rGradient.SetAngle(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)
+{
+ VersionCompat aCompat(mrStream, StreamMode::WRITE, 1);
+
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(rGradient.GetStyle()));
+ writeColor(rGradient.GetStartColor());
+ writeColor(rGradient.GetEndColor());
+ mrStream.WriteUInt16(rGradient.GetAngle());
+ 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;
+
+ {
+ VersionCompat aCompat(mrStream, StreamMode::READ);
+
+ // Version 1
+ mrStream.ReadUInt16(nType);
+ mrStream.ReadUInt32(nDataSize);
+ mrStream.ReadUInt32(nUserId);
+
+ if (aCompat.GetVersion() >= 2)
+ {
+ readSize(aSize);
+ ReadMapMode(mrStream, aMapMode);
+ bMapAndSizeValid = true;
+ }
+ }
+
+ auto nRemainingData = mrStream.remainingSize();
+ if (nDataSize > nRemainingData)
+ {
+ SAL_WARN("vcl", "graphic link stream is smaller than requested size");
+ nDataSize = nRemainingData;
+ }
+
+ std::unique_ptr<sal_uInt8[]> pBuffer(new sal_uInt8[nDataSize]);
+ mrStream.ReadBytes(pBuffer.get(), nDataSize);
+
+ rGfxLink = GfxLink(std::move(pBuffer), nDataSize, static_cast<GfxLinkType>(nType));
+ rGfxLink.SetUserId(nUserId);
+
+ if (bMapAndSizeValid)
+ {
+ rGfxLink.SetPrefSize(aSize);
+ rGfxLink.SetPrefMapMode(aMapMode);
+ }
+}
+
+void TypeSerializer::writeGfxLink(const GfxLink& rGfxLink)
+{
+ {
+ VersionCompat aCompat(mrStream, StreamMode::WRITE, 2);
+
+ // Version 1
+ mrStream.WriteUInt16(sal_uInt16(rGfxLink.GetType()));
+ mrStream.WriteUInt32(rGfxLink.GetDataSize());
+ mrStream.WriteUInt32(rGfxLink.GetUserId());
+
+ // Version 2
+ writeSize(rGfxLink.GetPrefSize());
+ WriteMapMode(mrStream, 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')
+
+constexpr sal_uInt32 constSvgMagic = createMagic('s', 'v', 'g', '0');
+constexpr sal_uInt32 constWmfMagic = createMagic('w', 'm', 'f', '0');
+constexpr sal_uInt32 constEmfMagic = createMagic('e', 'm', 'f', '0');
+constexpr sal_uInt32 constPdfMagic = createMagic('p', 'd', 'f', '0');
+
+} // end anonymous namespace
+
+void TypeSerializer::readGraphic(Graphic& rGraphic)
+{
+ if (mrStream.GetError())
+ return;
+
+ const sal_uLong nInitialStreamPosition = mrStream.Tell();
+ sal_uInt32 nType;
+
+ // read Id
+ mrStream.ReadUInt32(nType);
+
+ // if there is no more data, avoid further expensive
+ // reading which will create VDevs and other stuff, just to
+ // read nothing. CAUTION: Eof is only true AFTER reading another
+ // byte, a speciality of SvMemoryStream (!)
+ if (!mrStream.good())
+ return;
+
+ if (NATIVE_FORMAT_50 == nType)
+ {
+ Graphic aGraphic;
+ GfxLink aLink;
+
+ // read compat info, destructor writes stuff into the header
+ {
+ VersionCompat aCompat(mrStream, StreamMode::READ);
+ }
+
+ 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;
+ sal_uInt64 nBeginPoisition = mrStream.Tell();
+
+ mrStream.ReadUInt32(nMagic1);
+ mrStream.ReadUInt32(nMagic2);
+ mrStream.Seek(nBeginPoisition);
+
+ 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();
+ ReadGDIMetaFile(mrStream, 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)
+ {
+ VectorGraphicDataArray aData(nLength);
+
+ mrStream.ReadBytes(aData.getArray(), nLength);
+ OUString aPath = mrStream.ReadUniOrByteString(mrStream.GetStreamCharSet());
+
+ 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>(aData, aPath, 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
+ {
+ VersionCompat aCompat(mrStream, StreamMode::WRITE, 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->getVectorGraphicDataType())
+ {
+ 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->getVectorGraphicDataArrayLength();
+ mrStream.WriteUInt32(nSize);
+ mrStream.WriteBytes(
+ pVectorGraphicData->getVectorGraphicDataArray().getConstArray(), nSize);
+ mrStream.WriteUniOrByteString(pVectorGraphicData->getPath(),
+ mrStream.GetStreamCharSet());
+ }
+ else if (aGraphic.IsAnimated())
+ {
+ WriteAnimation(mrStream, aGraphic.GetAnimation());
+ }
+ else
+ {
+ WriteDIBBitmapEx(aGraphic.GetBitmapEx(), mrStream);
+ }
+ }
+ break;
+
+ default:
+ {
+ if (aGraphic.IsSupportedGraphic())
+ WriteGDIMetaFile(mrStream, rGraphic.GetGDIMetaFile());
+ }
+ break;
+ }
+ mrStream.SetEndian(nOldFormat);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/VerticalOrientationData.cxx b/vcl/source/gdi/VerticalOrientationData.cxx
new file mode 100644
index 000000000..1016b3656
--- /dev/null
+++ b/vcl/source/gdi/VerticalOrientationData.cxx
@@ -0,0 +1,78 @@
+
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/*
+ * Derived from the Unicode Character Database by genVerticalOrientationData.pl
+ *
+ * For Unicode terms of use, see http://www.unicode.org/terms_of_use.html
+ */
+
+/*
+ * Created on Wed Nov 9 21:29:02 2016 from UCD data files with version info:
+ *
+
+
+# VerticalOrientation-17.txt
+# Date: 2016-10-20, 07:00:00 GMT [EM, KI, LI]
+
+ *
+ * * * * * This file contains MACHINE-GENERATED DATA, do not edit! * * * * *
+ */
+
+#define kVerticalOrientationMaxPlane 16
+#define kVerticalOrientationIndexBits 9
+#define kVerticalOrientationCharBits 7
+static const uint8_t sVerticalOrientationPlanes[16] = {1,2,2,3,3,3,3,3,3,3,3,3,3,3,2,2};
+
+static const uint8_t sVerticalOrientationPages[4][512] = {
+ {0,1,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,0,0,0,0,4,3,3,3,3,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,7,8,9,10,0,11,12,13,3,0,14,15,3,16,17,0,0,0,0,0,0,18,19,0,0,0,0,0,3,3,3,20,21,22,23,3,3,24,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,25,0,0,0,0,0,0,0,0,26,0,0,0,0,0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,0,0,0,27,0,28,29},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,30,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3,3,3,3,3,3,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,26,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3,3,0,0,3,0,0,0,0,0,0,0,0,0,3,3,3,3,3,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3,3,32,3,3,3,3,3,3,3,3,3,3,3,0,0,3,3,0,0,0,0,0,0,0,0,0,0,0,0},
+ {3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,33},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
+};
+
+static const uint8_t sVerticalOrientationValues[34][128] = {
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,1,1,1,0,1,1,0,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1},
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,0,1,1,1,1,0,0,0,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {0,0,1,0,0,0,0,0,0,0,1,1,1,1,1,0,1,1,1,0,0,1,0,0,1,1,1,1,1,1,0,0,0,0,0,0,1,0,1,0,1,0,1,1,1,1,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,3,3,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {0,2,2,0,0,0,0,0,3,3,3,3,3,3,3,3,3,3,0,0,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,2,0,2,0,2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {0,0,0,2,0,2,0,2,0,0,0,0,0,0,2,0,0,0,0,0,0,2,2,0,0,0,0,2,2,0,0,0,3,2,0,2,0,2,0,2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,2,0,2,0,0,0,0,0,0,2,0,0,0,0,0,0,2,2,0,0,0,0,0,3,0,0,0},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2},
+ {2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,2,2,2,0,0,0,0,0,1,3,3,3,3,3,3,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {1,2,0,0,0,0,0,0,3,3,0,0,2,1,2,0,0,0,0,0,0,0,0,0,0,0,3,3,1,1,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,3,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3,3,3,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,3,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,0,0,1,1},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1}
+};
+/*
+ * * * * * This file contains MACHINE-GENERATED DATA, do not edit! * * * * *
+ */
diff --git a/vcl/source/gdi/WidgetDefinition.cxx b/vcl/source/gdi/WidgetDefinition.cxx
new file mode 100644
index 000000000..3c93f1ac5
--- /dev/null
+++ b/vcl/source/gdi/WidgetDefinition.cxx
@@ -0,0 +1,186 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <widgetdraw/WidgetDefinition.hxx>
+
+#include <sal/config.h>
+#include <unordered_map>
+
+namespace vcl
+{
+std::shared_ptr<WidgetDefinitionPart> WidgetDefinition::getDefinition(ControlType eType,
+ ControlPart ePart)
+{
+ auto aIterator = maDefinitions.find(ControlTypeAndPart(eType, ePart));
+
+ if (aIterator != maDefinitions.end())
+ return aIterator->second;
+ return std::shared_ptr<WidgetDefinitionPart>();
+}
+
+std::vector<std::shared_ptr<WidgetDefinitionState>>
+WidgetDefinitionPart::getStates(ControlType eType, ControlPart ePart, ControlState eState,
+ ImplControlValue const& rValue)
+{
+ std::vector<std::shared_ptr<WidgetDefinitionState>> aStatesToAdd;
+
+ for (const auto& state : maStates)
+ {
+ bool bAdd = true;
+
+ if (state->msEnabled != "any"
+ && !((state->msEnabled == "true" && eState & ControlState::ENABLED)
+ || (state->msEnabled == "false" && !(eState & ControlState::ENABLED))))
+ bAdd = false;
+ if (state->msFocused != "any"
+ && !((state->msFocused == "true" && eState & ControlState::FOCUSED)
+ || (state->msFocused == "false" && !(eState & ControlState::FOCUSED))))
+ bAdd = false;
+ if (state->msPressed != "any"
+ && !((state->msPressed == "true" && eState & ControlState::PRESSED)
+ || (state->msPressed == "false" && !(eState & ControlState::PRESSED))))
+ bAdd = false;
+ if (state->msRollover != "any"
+ && !((state->msRollover == "true" && eState & ControlState::ROLLOVER)
+ || (state->msRollover == "false" && !(eState & ControlState::ROLLOVER))))
+ bAdd = false;
+ if (state->msDefault != "any"
+ && !((state->msDefault == "true" && eState & ControlState::DEFAULT)
+ || (state->msDefault == "false" && !(eState & ControlState::DEFAULT))))
+ bAdd = false;
+ if (state->msSelected != "any"
+ && !((state->msSelected == "true" && eState & ControlState::SELECTED)
+ || (state->msSelected == "false" && !(eState & ControlState::SELECTED))))
+ bAdd = false;
+
+ ButtonValue eButtonValue = rValue.getTristateVal();
+
+ if (state->msButtonValue != "any"
+ && !((state->msButtonValue == "true" && eButtonValue == ButtonValue::On)
+ || (state->msButtonValue == "false" && eButtonValue == ButtonValue::Off)))
+ {
+ bAdd = false;
+ }
+
+ OString sExtra = "any";
+
+ switch (eType)
+ {
+ case ControlType::TabItem:
+ {
+ auto const& rTabItemValue = static_cast<TabitemValue const&>(rValue);
+
+ if (rTabItemValue.isLeftAligned() && rTabItemValue.isRightAligned()
+ && rTabItemValue.isFirst() && rTabItemValue.isLast())
+ sExtra = "first_last";
+ else if (rTabItemValue.isLeftAligned() || rTabItemValue.isFirst())
+ sExtra = "first";
+ else if (rTabItemValue.isRightAligned() || rTabItemValue.isLast())
+ sExtra = "last";
+ else
+ sExtra = "middle";
+ }
+ break;
+ case ControlType::ListHeader:
+ {
+ if (ePart == ControlPart::Arrow)
+ {
+ if (rValue.getNumericVal() == 1)
+ sExtra = "down";
+ else
+ sExtra = "up";
+ }
+ }
+ break;
+ case ControlType::Pushbutton:
+ {
+ auto const& rPushButtonValue = static_cast<PushButtonValue const&>(rValue);
+ if (rPushButtonValue.mbIsAction)
+ sExtra = "action";
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (state->msExtra != "any" && state->msExtra != sExtra)
+ {
+ bAdd = false;
+ }
+
+ if (bAdd)
+ aStatesToAdd.push_back(state);
+ }
+
+ return aStatesToAdd;
+}
+
+WidgetDefinitionState::WidgetDefinitionState(OString const& sEnabled, OString const& sFocused,
+ OString const& sPressed, OString const& sRollover,
+ OString const& sDefault, OString const& sSelected,
+ OString const& sButtonValue, OString const& sExtra)
+ : msEnabled(sEnabled)
+ , msFocused(sFocused)
+ , msPressed(sPressed)
+ , msRollover(sRollover)
+ , msDefault(sDefault)
+ , msSelected(sSelected)
+ , msButtonValue(sButtonValue)
+ , msExtra(sExtra)
+{
+}
+
+void WidgetDefinitionState::addDrawRectangle(Color aStrokeColor, sal_Int32 nStrokeWidth,
+ Color aFillColor, float fX1, float fY1, float fX2,
+ float fY2, sal_Int32 nRx, sal_Int32 nRy)
+{
+ auto pCommand(std::make_shared<WidgetDrawActionRectangle>());
+ pCommand->maStrokeColor = aStrokeColor;
+ pCommand->maFillColor = aFillColor;
+ pCommand->mnStrokeWidth = nStrokeWidth;
+ pCommand->mnRx = nRx;
+ pCommand->mnRy = nRy;
+ pCommand->mfX1 = fX1;
+ pCommand->mfY1 = fY1;
+ pCommand->mfX2 = fX2;
+ pCommand->mfY2 = fY2;
+ mpWidgetDrawActions.push_back(std::move(pCommand));
+}
+
+void WidgetDefinitionState::addDrawLine(Color aStrokeColor, sal_Int32 nStrokeWidth, float fX1,
+ float fY1, float fX2, float fY2)
+{
+ auto pCommand(std::make_shared<WidgetDrawActionLine>());
+ pCommand->maStrokeColor = aStrokeColor;
+ pCommand->mnStrokeWidth = nStrokeWidth;
+ pCommand->mfX1 = fX1;
+ pCommand->mfY1 = fY1;
+ pCommand->mfX2 = fX2;
+ pCommand->mfY2 = fY2;
+ mpWidgetDrawActions.push_back(std::move(pCommand));
+}
+
+void WidgetDefinitionState::addDrawImage(OUString const& sSource)
+{
+ auto pCommand(std::make_shared<WidgetDrawActionImage>());
+ pCommand->msSource = sSource;
+ mpWidgetDrawActions.push_back(std::move(pCommand));
+}
+
+void WidgetDefinitionState::addDrawExternal(OUString const& sSource)
+{
+ auto pCommand(std::make_shared<WidgetDrawActionExternal>());
+ pCommand->msSource = sSource;
+ mpWidgetDrawActions.push_back(std::move(pCommand));
+}
+
+} // end vcl namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/WidgetDefinitionReader.cxx b/vcl/source/gdi/WidgetDefinitionReader.cxx
new file mode 100644
index 000000000..7d3fb7c4c
--- /dev/null
+++ b/vcl/source/gdi/WidgetDefinitionReader.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/.
+ *
+ */
+
+#include <widgetdraw/WidgetDefinitionReader.hxx>
+
+#include <sal/config.h>
+#include <osl/file.hxx>
+#include <tools/stream.hxx>
+#include <unordered_map>
+
+namespace vcl
+{
+namespace
+{
+bool lcl_fileExists(OUString const& sFilename)
+{
+ osl::File aFile(sFilename);
+ osl::FileBase::RC eRC = aFile.open(osl_File_OpenFlag_Read);
+ return osl::FileBase::E_None == eRC;
+}
+
+int lcl_gethex(char aChar)
+{
+ if (aChar >= '0' && aChar <= '9')
+ return aChar - '0';
+ else if (aChar >= 'a' && aChar <= 'f')
+ return aChar - 'a' + 10;
+ else if (aChar >= 'A' && aChar <= 'F')
+ return aChar - 'A' + 10;
+ else
+ return 0;
+}
+
+bool readColor(OString const& rString, Color& rColor)
+{
+ if (rString.getLength() != 7)
+ return false;
+
+ const char aChar(rString[0]);
+
+ if (aChar != '#')
+ return false;
+
+ rColor.SetRed((lcl_gethex(rString[1]) << 4) | lcl_gethex(rString[2]));
+ rColor.SetGreen((lcl_gethex(rString[3]) << 4) | lcl_gethex(rString[4]));
+ rColor.SetBlue((lcl_gethex(rString[5]) << 4) | lcl_gethex(rString[6]));
+
+ return true;
+}
+
+bool readSetting(OString const& rInputString, OString& rOutputString)
+{
+ if (!rInputString.isEmpty())
+ rOutputString = rInputString;
+ return true;
+}
+
+OString getValueOrAny(OString const& rInputString)
+{
+ if (rInputString.isEmpty())
+ return "any";
+ return rInputString;
+}
+
+ControlPart xmlStringToControlPart(OString const& sPart)
+{
+ if (sPart.equalsIgnoreAsciiCase("NONE"))
+ return ControlPart::NONE;
+ else if (sPart.equalsIgnoreAsciiCase("Entire"))
+ return ControlPart::Entire;
+ else if (sPart.equalsIgnoreAsciiCase("ListboxWindow"))
+ return ControlPart::ListboxWindow;
+ else if (sPart.equalsIgnoreAsciiCase("Button"))
+ return ControlPart::Button;
+ else if (sPart.equalsIgnoreAsciiCase("ButtonUp"))
+ return ControlPart::ButtonUp;
+ else if (sPart.equalsIgnoreAsciiCase("ButtonDown"))
+ return ControlPart::ButtonDown;
+ else if (sPart.equalsIgnoreAsciiCase("ButtonLeft"))
+ return ControlPart::ButtonLeft;
+ else if (sPart.equalsIgnoreAsciiCase("ButtonRight"))
+ return ControlPart::ButtonRight;
+ else if (sPart.equalsIgnoreAsciiCase("AllButtons"))
+ return ControlPart::AllButtons;
+ else if (sPart.equalsIgnoreAsciiCase("SeparatorHorz"))
+ return ControlPart::SeparatorHorz;
+ else if (sPart.equalsIgnoreAsciiCase("SeparatorVert"))
+ return ControlPart::SeparatorVert;
+ else if (sPart.equalsIgnoreAsciiCase("TrackHorzLeft"))
+ return ControlPart::TrackHorzLeft;
+ else if (sPart.equalsIgnoreAsciiCase("TrackVertUpper"))
+ return ControlPart::TrackVertUpper;
+ else if (sPart.equalsIgnoreAsciiCase("TrackHorzRight"))
+ return ControlPart::TrackHorzRight;
+ else if (sPart.equalsIgnoreAsciiCase("TrackVertLower"))
+ return ControlPart::TrackVertLower;
+ else if (sPart.equalsIgnoreAsciiCase("TrackHorzArea"))
+ return ControlPart::TrackHorzArea;
+ else if (sPart.equalsIgnoreAsciiCase("TrackVertArea"))
+ return ControlPart::TrackVertArea;
+ else if (sPart.equalsIgnoreAsciiCase("Arrow"))
+ return ControlPart::Arrow;
+ else if (sPart.equalsIgnoreAsciiCase("ThumbHorz"))
+ return ControlPart::ThumbHorz;
+ else if (sPart.equalsIgnoreAsciiCase("ThumbVert"))
+ return ControlPart::ThumbVert;
+ else if (sPart.equalsIgnoreAsciiCase("MenuItem"))
+ return ControlPart::MenuItem;
+ else if (sPart.equalsIgnoreAsciiCase("MenuItemCheckMark"))
+ return ControlPart::MenuItemCheckMark;
+ else if (sPart.equalsIgnoreAsciiCase("MenuItemRadioMark"))
+ return ControlPart::MenuItemRadioMark;
+ else if (sPart.equalsIgnoreAsciiCase("Separator"))
+ return ControlPart::Separator;
+ else if (sPart.equalsIgnoreAsciiCase("SubmenuArrow"))
+ return ControlPart::SubmenuArrow;
+ else if (sPart.equalsIgnoreAsciiCase("SubEdit"))
+ return ControlPart::SubEdit;
+ else if (sPart.equalsIgnoreAsciiCase("DrawBackgroundHorz"))
+ return ControlPart::DrawBackgroundHorz;
+ else if (sPart.equalsIgnoreAsciiCase("DrawBackgroundVert"))
+ return ControlPart::DrawBackgroundVert;
+ else if (sPart.equalsIgnoreAsciiCase("TabsDrawRtl"))
+ return ControlPart::TabsDrawRtl;
+ else if (sPart.equalsIgnoreAsciiCase("HasBackgroundTexture"))
+ return ControlPart::HasBackgroundTexture;
+ else if (sPart.equalsIgnoreAsciiCase("HasThreeButtons"))
+ return ControlPart::HasThreeButtons;
+ else if (sPart.equalsIgnoreAsciiCase("BackgroundWindow"))
+ return ControlPart::BackgroundWindow;
+ else if (sPart.equalsIgnoreAsciiCase("BackgroundDialog"))
+ return ControlPart::BackgroundDialog;
+ else if (sPart.equalsIgnoreAsciiCase("Border"))
+ return ControlPart::Border;
+ else if (sPart.equalsIgnoreAsciiCase("Focus"))
+ return ControlPart::Focus;
+ return ControlPart::NONE;
+}
+
+bool getControlTypeForXmlString(OString const& rString, ControlType& reType)
+{
+ static std::unordered_map<OString, ControlType> aPartMap = {
+ { "pushbutton", ControlType::Pushbutton },
+ { "radiobutton", ControlType::Radiobutton },
+ { "checkbox", ControlType::Checkbox },
+ { "combobox", ControlType::Combobox },
+ { "editbox", ControlType::Editbox },
+ { "listbox", ControlType::Listbox },
+ { "scrollbar", ControlType::Scrollbar },
+ { "spinbox", ControlType::Spinbox },
+ { "slider", ControlType::Slider },
+ { "fixedline", ControlType::Fixedline },
+ { "progress", ControlType::Progress },
+ { "tabitem", ControlType::TabItem },
+ { "tabheader", ControlType::TabHeader },
+ { "tabpane", ControlType::TabPane },
+ { "tabbody", ControlType::TabBody },
+ { "frame", ControlType::Frame },
+ { "windowbackground", ControlType::WindowBackground },
+ { "toolbar", ControlType::Toolbar },
+ { "listnode", ControlType::ListNode },
+ { "listnet", ControlType::ListNet },
+ { "listheader", ControlType::ListHeader },
+ { "menubar", ControlType::Menubar },
+ { "menupopup", ControlType::MenuPopup },
+ { "tooltip", ControlType::Tooltip },
+ };
+
+ auto const& rIterator = aPartMap.find(rString);
+ if (rIterator != aPartMap.end())
+ {
+ reType = rIterator->second;
+ return true;
+ }
+ return false;
+}
+
+} // end anonymous namespace
+
+WidgetDefinitionReader::WidgetDefinitionReader(OUString const& rDefinitionFile,
+ OUString const& rResourcePath)
+ : m_rDefinitionFile(rDefinitionFile)
+ , m_rResourcePath(rResourcePath)
+{
+}
+
+void WidgetDefinitionReader::readDrawingDefinition(
+ tools::XmlWalker& rWalker, const std::shared_ptr<WidgetDefinitionState>& rpState)
+{
+ rWalker.children();
+ while (rWalker.isValid())
+ {
+ if (rWalker.name() == "rect")
+ {
+ Color aStrokeColor;
+ readColor(rWalker.attribute("stroke"), aStrokeColor);
+ Color aFillColor;
+ readColor(rWalker.attribute("fill"), aFillColor);
+ OString sStrokeWidth = rWalker.attribute("stroke-width");
+ sal_Int32 nStrokeWidth = -1;
+ if (!sStrokeWidth.isEmpty())
+ nStrokeWidth = sStrokeWidth.toInt32();
+
+ sal_Int32 nRx = -1;
+ OString sRx = rWalker.attribute("rx");
+ if (!sRx.isEmpty())
+ nRx = sRx.toInt32();
+
+ sal_Int32 nRy = -1;
+ OString sRy = rWalker.attribute("ry");
+ if (!sRy.isEmpty())
+ nRy = sRy.toInt32();
+
+ OString sX1 = rWalker.attribute("x1");
+ float fX1 = sX1.isEmpty() ? 0.0 : sX1.toFloat();
+
+ OString sY1 = rWalker.attribute("y1");
+ float fY1 = sY1.isEmpty() ? 0.0 : sY1.toFloat();
+
+ OString sX2 = rWalker.attribute("x2");
+ float fX2 = sX2.isEmpty() ? 1.0 : sX2.toFloat();
+
+ OString sY2 = rWalker.attribute("y2");
+ float fY2 = sY2.isEmpty() ? 1.0 : sY2.toFloat();
+
+ rpState->addDrawRectangle(aStrokeColor, nStrokeWidth, aFillColor, fX1, fY1, fX2, fY2,
+ nRx, nRy);
+ }
+ else if (rWalker.name() == "line")
+ {
+ Color aStrokeColor;
+ readColor(rWalker.attribute("stroke"), aStrokeColor);
+
+ OString sStrokeWidth = rWalker.attribute("stroke-width");
+ sal_Int32 nStrokeWidth = -1;
+ if (!sStrokeWidth.isEmpty())
+ nStrokeWidth = sStrokeWidth.toInt32();
+
+ OString sX1 = rWalker.attribute("x1");
+ float fX1 = sX1.isEmpty() ? -1.0 : sX1.toFloat();
+
+ OString sY1 = rWalker.attribute("y1");
+ float fY1 = sY1.isEmpty() ? -1.0 : sY1.toFloat();
+
+ OString sX2 = rWalker.attribute("x2");
+ float fX2 = sX2.isEmpty() ? -1.0 : sX2.toFloat();
+
+ OString sY2 = rWalker.attribute("y2");
+ float fY2 = sY2.isEmpty() ? -1.0 : sY2.toFloat();
+
+ rpState->addDrawLine(aStrokeColor, nStrokeWidth, fX1, fY1, fX2, fY2);
+ }
+ else if (rWalker.name() == "image")
+ {
+ OString sSource = rWalker.attribute("source");
+ rpState->addDrawImage(m_rResourcePath
+ + OStringToOUString(sSource, RTL_TEXTENCODING_UTF8));
+ }
+ else if (rWalker.name() == "external")
+ {
+ OString sSource = rWalker.attribute("source");
+ rpState->addDrawExternal(m_rResourcePath
+ + OStringToOUString(sSource, RTL_TEXTENCODING_UTF8));
+ }
+ rWalker.next();
+ }
+ rWalker.parent();
+}
+
+void WidgetDefinitionReader::readDefinition(tools::XmlWalker& rWalker,
+ WidgetDefinition& rWidgetDefinition, ControlType eType)
+{
+ rWalker.children();
+ while (rWalker.isValid())
+ {
+ if (rWalker.name() == "part")
+ {
+ OString sPart = rWalker.attribute("value");
+ ControlPart ePart = xmlStringToControlPart(sPart);
+
+ std::shared_ptr<WidgetDefinitionPart> pPart = std::make_shared<WidgetDefinitionPart>();
+
+ OString sWidth = rWalker.attribute("width");
+ if (!sWidth.isEmpty())
+ {
+ sal_Int32 nWidth = sWidth.isEmpty() ? 0 : sWidth.toInt32();
+ pPart->mnWidth = nWidth;
+ }
+
+ OString sHeight = rWalker.attribute("height");
+ if (!sHeight.isEmpty())
+ {
+ sal_Int32 nHeight = sHeight.isEmpty() ? 0 : sHeight.toInt32();
+ pPart->mnHeight = nHeight;
+ }
+
+ OString sMarginHeight = rWalker.attribute("margin-height");
+ if (!sMarginHeight.isEmpty())
+ {
+ sal_Int32 nMarginHeight = sMarginHeight.isEmpty() ? 0 : sMarginHeight.toInt32();
+ pPart->mnMarginHeight = nMarginHeight;
+ }
+
+ OString sMarginWidth = rWalker.attribute("margin-width");
+ if (!sMarginWidth.isEmpty())
+ {
+ sal_Int32 nMarginWidth = sMarginWidth.isEmpty() ? 0 : sMarginWidth.toInt32();
+ pPart->mnMarginWidth = nMarginWidth;
+ }
+
+ OString sOrientation = rWalker.attribute("orientation");
+ if (!sOrientation.isEmpty())
+ {
+ pPart->msOrientation = sOrientation;
+ }
+
+ rWidgetDefinition.maDefinitions.emplace(ControlTypeAndPart(eType, ePart), pPart);
+ readPart(rWalker, pPart);
+ }
+ rWalker.next();
+ }
+ rWalker.parent();
+}
+
+void WidgetDefinitionReader::readPart(tools::XmlWalker& rWalker,
+ std::shared_ptr<WidgetDefinitionPart> rpPart)
+{
+ rWalker.children();
+ while (rWalker.isValid())
+ {
+ if (rWalker.name() == "state")
+ {
+ OString sEnabled = getValueOrAny(rWalker.attribute("enabled"));
+ OString sFocused = getValueOrAny(rWalker.attribute("focused"));
+ OString sPressed = getValueOrAny(rWalker.attribute("pressed"));
+ OString sRollover = getValueOrAny(rWalker.attribute("rollover"));
+ OString sDefault = getValueOrAny(rWalker.attribute("default"));
+ OString sSelected = getValueOrAny(rWalker.attribute("selected"));
+ OString sButtonValue = getValueOrAny(rWalker.attribute("button-value"));
+ OString sExtra = getValueOrAny(rWalker.attribute("extra"));
+
+ std::shared_ptr<WidgetDefinitionState> pState = std::make_shared<WidgetDefinitionState>(
+ sEnabled, sFocused, sPressed, sRollover, sDefault, sSelected, sButtonValue, sExtra);
+
+ rpPart->maStates.push_back(pState);
+ readDrawingDefinition(rWalker, pState);
+ }
+ rWalker.next();
+ }
+ rWalker.parent();
+}
+
+bool WidgetDefinitionReader::read(WidgetDefinition& rWidgetDefinition)
+{
+ if (!lcl_fileExists(m_rDefinitionFile))
+ return false;
+
+ auto pStyle = std::make_shared<WidgetDefinitionStyle>();
+
+ std::unordered_map<OString, Color*> aStyleColorMap = {
+ { "faceColor", &pStyle->maFaceColor },
+ { "checkedColor", &pStyle->maCheckedColor },
+ { "lightColor", &pStyle->maLightColor },
+ { "lightBorderColor", &pStyle->maLightBorderColor },
+ { "shadowColor", &pStyle->maShadowColor },
+ { "darkShadowColor", &pStyle->maDarkShadowColor },
+ { "buttonTextColor", &pStyle->maButtonTextColor },
+ { "defaultActionButtonTextColor", &pStyle->maDefaultActionButtonTextColor },
+ { "actionButtonTextColor", &pStyle->maActionButtonTextColor },
+ { "actionButtonRolloverTextColor", &pStyle->maActionButtonRolloverTextColor },
+ { "buttonRolloverTextColor", &pStyle->maButtonRolloverTextColor },
+ { "radioCheckTextColor", &pStyle->maRadioCheckTextColor },
+ { "groupTextColor", &pStyle->maGroupTextColor },
+ { "labelTextColor", &pStyle->maLabelTextColor },
+ { "windowColor", &pStyle->maWindowColor },
+ { "windowTextColor", &pStyle->maWindowTextColor },
+ { "dialogColor", &pStyle->maDialogColor },
+ { "dialogTextColor", &pStyle->maDialogTextColor },
+ { "workspaceColor", &pStyle->maWorkspaceColor },
+ { "monoColor", &pStyle->maMonoColor },
+ { "fieldColor", &pStyle->maFieldColor },
+ { "fieldTextColor", &pStyle->maFieldTextColor },
+ { "fieldRolloverTextColor", &pStyle->maFieldRolloverTextColor },
+ { "activeColor", &pStyle->maActiveColor },
+ { "activeTextColor", &pStyle->maActiveTextColor },
+ { "activeBorderColor", &pStyle->maActiveBorderColor },
+ { "deactiveColor", &pStyle->maDeactiveColor },
+ { "deactiveTextColor", &pStyle->maDeactiveTextColor },
+ { "deactiveBorderColor", &pStyle->maDeactiveBorderColor },
+ { "menuColor", &pStyle->maMenuColor },
+ { "menuBarColor", &pStyle->maMenuBarColor },
+ { "menuBarRolloverColor", &pStyle->maMenuBarRolloverColor },
+ { "menuBorderColor", &pStyle->maMenuBorderColor },
+ { "menuTextColor", &pStyle->maMenuTextColor },
+ { "menuBarTextColor", &pStyle->maMenuBarTextColor },
+ { "menuBarRolloverTextColor", &pStyle->maMenuBarRolloverTextColor },
+ { "menuBarHighlightTextColor", &pStyle->maMenuBarHighlightTextColor },
+ { "menuHighlightColor", &pStyle->maMenuHighlightColor },
+ { "menuHighlightTextColor", &pStyle->maMenuHighlightTextColor },
+ { "highlightColor", &pStyle->maHighlightColor },
+ { "highlightTextColor", &pStyle->maHighlightTextColor },
+ { "activeTabColor", &pStyle->maActiveTabColor },
+ { "inactiveTabColor", &pStyle->maInactiveTabColor },
+ { "tabTextColor", &pStyle->maTabTextColor },
+ { "tabRolloverTextColor", &pStyle->maTabRolloverTextColor },
+ { "tabHighlightTextColor", &pStyle->maTabHighlightTextColor },
+ { "disableColor", &pStyle->maDisableColor },
+ { "helpColor", &pStyle->maHelpColor },
+ { "helpTextColor", &pStyle->maHelpTextColor },
+ { "linkColor", &pStyle->maLinkColor },
+ { "visitedLinkColor", &pStyle->maVisitedLinkColor },
+ { "toolTextColor", &pStyle->maToolTextColor },
+ { "fontColor", &pStyle->maFontColor },
+ };
+
+ rWidgetDefinition.mpStyle = pStyle;
+
+ auto pSettings = std::make_shared<WidgetDefinitionSettings>();
+
+ std::unordered_map<OString, OString*> aSettingMap = {
+ { "noActiveTabTextRaise", &pSettings->msNoActiveTabTextRaise },
+ { "centeredTabs", &pSettings->msCenteredTabs },
+ { "listBoxEntryMargin", &pSettings->msListBoxEntryMargin },
+ { "defaultFontSize", &pSettings->msDefaultFontSize },
+ { "titleHeight", &pSettings->msTitleHeight },
+ { "floatTitleHeight", &pSettings->msFloatTitleHeight },
+ { "listBoxPreviewDefaultLogicWidth", &pSettings->msListBoxPreviewDefaultLogicWidth },
+ { "listBoxPreviewDefaultLogicHeight", &pSettings->msListBoxPreviewDefaultLogicHeight },
+ };
+
+ rWidgetDefinition.mpSettings = pSettings;
+
+ SvFileStream aFileStream(m_rDefinitionFile, StreamMode::READ);
+
+ tools::XmlWalker aWalker;
+ if (!aWalker.open(&aFileStream))
+ return false;
+
+ if (aWalker.name() != "widgets")
+ return false;
+
+ aWalker.children();
+ while (aWalker.isValid())
+ {
+ ControlType eType;
+ if (aWalker.name() == "style")
+ {
+ aWalker.children();
+ while (aWalker.isValid())
+ {
+ auto pair = aStyleColorMap.find(aWalker.name());
+ if (pair != aStyleColorMap.end())
+ {
+ readColor(aWalker.attribute("value"), *pair->second);
+ }
+ aWalker.next();
+ }
+ aWalker.parent();
+ }
+ if (aWalker.name() == "settings")
+ {
+ aWalker.children();
+ while (aWalker.isValid())
+ {
+ auto pair = aSettingMap.find(aWalker.name());
+ if (pair != aSettingMap.end())
+ {
+ readSetting(aWalker.attribute("value"), *pair->second);
+ }
+ aWalker.next();
+ }
+ aWalker.parent();
+ }
+ else if (getControlTypeForXmlString(aWalker.name(), eType))
+ {
+ readDefinition(aWalker, rWidgetDefinition, eType);
+ }
+ aWalker.next();
+ }
+ aWalker.parent();
+
+ return true;
+}
+
+} // end vcl namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/alpha.cxx b/vcl/source/gdi/alpha.cxx
new file mode 100644
index 000000000..3fa43c8ea
--- /dev/null
+++ b/vcl/source/gdi/alpha.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/bitmapaccess.hxx>
+#include <tools/color.hxx>
+#include <vcl/alpha.hxx>
+#include <bitmapwriteaccess.hxx>
+#include <sal/log.hxx>
+
+AlphaMask::AlphaMask() = default;
+
+AlphaMask::AlphaMask( const Bitmap& rBitmap ) :
+ Bitmap( rBitmap )
+{
+ if( !!rBitmap )
+ Convert( BmpConversion::N8BitNoConversion );
+}
+
+AlphaMask::AlphaMask( const AlphaMask& ) = default;
+
+AlphaMask::AlphaMask( AlphaMask&& ) = default;
+
+AlphaMask::AlphaMask( const Size& rSizePixel, const sal_uInt8* pEraseTransparency ) :
+ Bitmap( rSizePixel, 8, &Bitmap::GetGreyPalette( 256 ) )
+{
+ if( pEraseTransparency )
+ Bitmap::Erase( Color( *pEraseTransparency, *pEraseTransparency, *pEraseTransparency ) );
+}
+
+AlphaMask::~AlphaMask() = default;
+
+AlphaMask& AlphaMask::operator=( const Bitmap& rBitmap )
+{
+ *static_cast<Bitmap*>(this) = rBitmap;
+
+ if( !!rBitmap )
+ Convert( BmpConversion::N8BitNoConversion );
+
+ return *this;
+}
+
+const Bitmap& AlphaMask::ImplGetBitmap() const
+{
+ return *this;
+}
+
+void AlphaMask::ImplSetBitmap( const Bitmap& rBitmap )
+{
+ SAL_WARN_IF( 8 != rBitmap.GetBitCount(), "vcl.gdi", "Bitmap should be 8bpp, not " << rBitmap.GetBitCount() << "bpp" );
+ SAL_WARN_IF( !rBitmap.HasGreyPalette8Bit(), "vcl.gdi", "Bitmap isn't greyscale" );
+ *static_cast<Bitmap*>(this) = rBitmap;
+}
+
+Bitmap const & AlphaMask::GetBitmap() const
+{
+ return ImplGetBitmap();
+}
+
+void AlphaMask::Erase( sal_uInt8 cTransparency )
+{
+ Bitmap::Erase( Color( cTransparency, cTransparency, cTransparency ) );
+}
+
+void AlphaMask::Replace( const Bitmap& rMask, sal_uInt8 cReplaceTransparency )
+{
+ Bitmap::ScopedReadAccess pMaskAcc( const_cast<Bitmap&>(rMask) );
+ AlphaScopedWriteAccess pAcc(*this);
+
+ if( pMaskAcc && pAcc )
+ {
+ const BitmapColor aReplace( cReplaceTransparency );
+ const long nWidth = std::min( pMaskAcc->Width(), pAcc->Width() );
+ const long nHeight = std::min( pMaskAcc->Height(), pAcc->Height() );
+ const BitmapColor aMaskWhite( pMaskAcc->GetBestMatchingColor( COL_WHITE ) );
+
+ for( long nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pScanline = pAcc->GetScanline(nY);
+ Scanline pScanlineMask = pMaskAcc->GetScanline(nY);
+ for( long nX = 0; nX < nWidth; nX++ )
+ if( pMaskAcc->GetPixelFromData( pScanlineMask, nX ) == aMaskWhite )
+ pAcc->SetPixelOnData( pScanline, nX, aReplace );
+ }
+ }
+}
+
+void AlphaMask::Replace( sal_uInt8 cSearchTransparency, sal_uInt8 cReplaceTransparency )
+{
+ AlphaScopedWriteAccess pAcc(*this);
+
+ if( pAcc && pAcc->GetBitCount() == 8 )
+ {
+ const long nWidth = pAcc->Width(), nHeight = pAcc->Height();
+
+ if( pAcc->GetScanlineFormat() == ScanlineFormat::N8BitPal )
+ {
+ for( long nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pScan = pAcc->GetScanline( nY );
+
+ for( long nX = 0; nX < nWidth; nX++, pScan++ )
+ {
+ if( *pScan == cSearchTransparency )
+ *pScan = cReplaceTransparency;
+ }
+ }
+ }
+ else
+ {
+ BitmapColor aReplace( cReplaceTransparency );
+
+ for( long nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pScanline = pAcc->GetScanline(nY);
+ for( long nX = 0; nX < nWidth; nX++ )
+ {
+ if( pAcc->GetIndexFromData( pScanline, nX ) == cSearchTransparency )
+ pAcc->SetPixelOnData( pScanline, nX, aReplace );
+ }
+ }
+ }
+ }
+}
+
+void AlphaMask::BlendWith(const Bitmap& rOther)
+{
+ AlphaMask aOther(rOther); // to 8 bits
+ Bitmap::ScopedReadAccess pOtherAcc(aOther);
+ AlphaScopedWriteAccess pAcc(*this);
+ if (pOtherAcc && pAcc && pOtherAcc->GetBitCount() == 8 && pAcc->GetBitCount() == 8)
+ {
+ const long nHeight = std::min(pOtherAcc->Height(), pAcc->Height());
+ const long nWidth = std::min(pOtherAcc->Width(), pAcc->Width());
+ for (long y = 0; y < nHeight; ++y)
+ {
+ Scanline scanline = pAcc->GetScanline( y );
+ ConstScanline otherScanline = pOtherAcc->GetScanline( y );
+ for (long x = 0; x < nWidth; ++x)
+ {
+ // Use sal_uInt16 for following multiplication
+ const sal_uInt16 nGrey1 = *scanline;
+ const sal_uInt16 nGrey2 = *otherScanline;
+ *scanline = static_cast<sal_uInt8>(nGrey1 + nGrey2 - nGrey1 * nGrey2 / 255);
+ ++scanline;
+ ++otherScanline;
+ }
+ }
+ }
+}
+
+void AlphaMask::ReleaseAccess( BitmapReadAccess* pAccess )
+{
+ if( pAccess )
+ {
+ Bitmap::ReleaseAccess( pAccess );
+ Convert( BmpConversion::N8BitNoConversion );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/bitmap3.cxx b/vcl/source/gdi/bitmap3.cxx
new file mode 100644
index 000000000..ec80b03c6
--- /dev/null
+++ b/vcl/source/gdi/bitmap3.cxx
@@ -0,0 +1,1146 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <vcl/bitmapaccess.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/bitmap.hxx>
+#include <config_features.h>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+#include <tools/helpers.hxx>
+#if HAVE_FEATURE_OPENGL
+#include <vcl/opengl/OpenGLHelper.hxx>
+#endif
+#if HAVE_FEATURE_SKIA
+#include <vcl/skia/SkiaHelper.hxx>
+#endif
+#include <vcl/BitmapMonochromeFilter.hxx>
+
+#include <BitmapScaleSuperFilter.hxx>
+#include <BitmapScaleConvolutionFilter.hxx>
+#include <BitmapFastScaleFilter.hxx>
+#include <BitmapInterpolateScaleFilter.hxx>
+#include <bitmapwriteaccess.hxx>
+#include <bitmap/impoctree.hxx>
+#include <bitmap/Octree.hxx>
+#include <svdata.hxx>
+#include <salinst.hxx>
+#include <salbmp.hxx>
+
+#include "impvect.hxx"
+
+#include <memory>
+
+#define GAMMA( _def_cVal, _def_InvGamma ) (static_cast<sal_uInt8>(MinMax(FRound(pow( _def_cVal/255.0,_def_InvGamma)*255.0),0,255)))
+
+#define CALC_ERRORS \
+ nTemp = p1T[nX++] >> 12; \
+ nBErr = MinMax( nTemp, 0, 255 ); \
+ nBErr = nBErr - FloydIndexMap[ nBC = FloydMap[nBErr] ]; \
+ nTemp = p1T[nX++] >> 12; \
+ nGErr = MinMax( nTemp, 0, 255 ); \
+ nGErr = nGErr - FloydIndexMap[ nGC = FloydMap[nGErr] ]; \
+ nTemp = p1T[nX] >> 12; \
+ nRErr = MinMax( nTemp, 0, 255 ); \
+ nRErr = nRErr - FloydIndexMap[ nRC = FloydMap[nRErr] ];
+
+#define CALC_TABLES3 \
+ p2T[nX++] += FloydError3[nBErr]; \
+ p2T[nX++] += FloydError3[nGErr]; \
+ p2T[nX++] += FloydError3[nRErr];
+
+#define CALC_TABLES5 \
+ p2T[nX++] += FloydError5[nBErr]; \
+ p2T[nX++] += FloydError5[nGErr]; \
+ p2T[nX++] += FloydError5[nRErr];
+
+#define CALC_TABLES7 \
+ p1T[++nX] += FloydError7[nBErr]; \
+ p2T[nX++] += FloydError1[nBErr]; \
+ p1T[nX] += FloydError7[nGErr]; \
+ p2T[nX++] += FloydError1[nGErr]; \
+ p1T[nX] += FloydError7[nRErr]; \
+ p2T[nX] += FloydError1[nRErr];
+
+const extern sal_uLong nVCLRLut[ 6 ] = { 16, 17, 18, 19, 20, 21 };
+const extern sal_uLong nVCLGLut[ 6 ] = { 0, 6, 12, 18, 24, 30 };
+const extern sal_uLong nVCLBLut[ 6 ] = { 0, 36, 72, 108, 144, 180 };
+
+const extern sal_uLong nVCLDitherLut[ 256 ] =
+{
+ 0, 49152, 12288, 61440, 3072, 52224, 15360, 64512, 768, 49920, 13056,
+ 62208, 3840, 52992, 16128, 65280, 32768, 16384, 45056, 28672, 35840, 19456,
+ 48128, 31744, 33536, 17152, 45824, 29440, 36608, 20224, 48896, 32512, 8192,
+ 57344, 4096, 53248, 11264, 60416, 7168, 56320, 8960, 58112, 4864, 54016,
+ 12032, 61184, 7936, 57088, 40960, 24576, 36864, 20480, 44032, 27648, 39936,
+ 23552, 41728, 25344, 37632, 21248, 44800, 28416, 40704, 24320, 2048, 51200,
+ 14336, 63488, 1024, 50176, 13312, 62464, 2816, 51968, 15104, 64256, 1792,
+ 50944, 14080, 63232, 34816, 18432, 47104, 30720, 33792, 17408, 46080, 29696,
+ 35584, 19200, 47872, 31488, 34560, 18176, 46848, 30464, 10240, 59392, 6144,
+ 55296, 9216, 58368, 5120, 54272, 11008, 60160, 6912, 56064, 9984, 59136,
+ 5888, 55040, 43008, 26624, 38912, 22528, 41984, 25600, 37888, 21504, 43776,
+ 27392, 39680, 23296, 42752, 26368, 38656, 22272, 512, 49664, 12800, 61952,
+ 3584, 52736, 15872, 65024, 256, 49408, 12544, 61696, 3328, 52480, 15616,
+ 64768, 33280, 16896, 45568, 29184, 36352, 19968, 48640, 32256, 33024, 16640,
+ 45312, 28928, 36096, 19712, 48384, 32000, 8704, 57856, 4608, 53760, 11776,
+ 60928, 7680, 56832, 8448, 57600, 4352, 53504, 11520, 60672, 7424, 56576,
+ 41472, 25088, 37376, 20992, 44544, 28160, 40448, 24064, 41216, 24832, 37120,
+ 20736, 44288, 27904, 40192, 23808, 2560, 51712, 14848, 64000, 1536, 50688,
+ 13824, 62976, 2304, 51456, 14592, 63744, 1280, 50432, 13568, 62720, 35328,
+ 18944, 47616, 31232, 34304, 17920, 46592, 30208, 35072, 18688, 47360, 30976,
+ 34048, 17664, 46336, 29952, 10752, 59904, 6656, 55808, 9728, 58880, 5632,
+ 54784, 10496, 59648, 6400, 55552, 9472, 58624, 5376, 54528, 43520, 27136,
+ 39424, 23040, 42496, 26112, 38400, 22016, 43264, 26880, 39168, 22784, 42240,
+ 25856, 38144, 21760
+};
+
+const extern sal_uLong nVCLLut[ 256 ] =
+{
+ 0, 1286, 2572, 3858, 5144, 6430, 7716, 9002,
+ 10288, 11574, 12860, 14146, 15432, 16718, 18004, 19290,
+ 20576, 21862, 23148, 24434, 25720, 27006, 28292, 29578,
+ 30864, 32150, 33436, 34722, 36008, 37294, 38580, 39866,
+ 41152, 42438, 43724, 45010, 46296, 47582, 48868, 50154,
+ 51440, 52726, 54012, 55298, 56584, 57870, 59156, 60442,
+ 61728, 63014, 64300, 65586, 66872, 68158, 69444, 70730,
+ 72016, 73302, 74588, 75874, 77160, 78446, 79732, 81018,
+ 82304, 83590, 84876, 86162, 87448, 88734, 90020, 91306,
+ 92592, 93878, 95164, 96450, 97736, 99022,100308,101594,
+ 102880,104166,105452,106738,108024,109310,110596,111882,
+ 113168,114454,115740,117026,118312,119598,120884,122170,
+ 123456,124742,126028,127314,128600,129886,131172,132458,
+ 133744,135030,136316,137602,138888,140174,141460,142746,
+ 144032,145318,146604,147890,149176,150462,151748,153034,
+ 154320,155606,156892,158178,159464,160750,162036,163322,
+ 164608,165894,167180,168466,169752,171038,172324,173610,
+ 174896,176182,177468,178754,180040,181326,182612,183898,
+ 185184,186470,187756,189042,190328,191614,192900,194186,
+ 195472,196758,198044,199330,200616,201902,203188,204474,
+ 205760,207046,208332,209618,210904,212190,213476,214762,
+ 216048,217334,218620,219906,221192,222478,223764,225050,
+ 226336,227622,228908,230194,231480,232766,234052,235338,
+ 236624,237910,239196,240482,241768,243054,244340,245626,
+ 246912,248198,249484,250770,252056,253342,254628,255914,
+ 257200,258486,259772,261058,262344,263630,264916,266202,
+ 267488,268774,270060,271346,272632,273918,275204,276490,
+ 277776,279062,280348,281634,282920,284206,285492,286778,
+ 288064,289350,290636,291922,293208,294494,295780,297066,
+ 298352,299638,300924,302210,303496,304782,306068,307354,
+ 308640,309926,311212,312498,313784,315070,316356,317642,
+ 318928,320214,321500,322786,324072,325358,326644,327930
+};
+
+const long FloydMap[256] =
+{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5
+};
+
+const long FloydError1[61] =
+{
+ -7680, -7424, -7168, -6912, -6656, -6400, -6144,
+ -5888, -5632, -5376, -5120, -4864, -4608, -4352,
+ -4096, -3840, -3584, -3328, -3072, -2816, -2560,
+ -2304, -2048, -1792, -1536, -1280, -1024, -768,
+ -512, -256, 0, 256, 512, 768, 1024, 1280, 1536,
+ 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584,
+ 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632,
+ 5888, 6144, 6400, 6656, 6912, 7168, 7424, 7680
+};
+
+const long FloydError3[61] =
+{
+ -23040, -22272, -21504, -20736, -19968, -19200,
+ -18432, -17664, -16896, -16128, -15360, -14592,
+ -13824, -13056, -12288, -11520, -10752, -9984,
+ -9216, -8448, -7680, -6912, -6144, -5376, -4608,
+ -3840, -3072, -2304, -1536, -768, 0, 768, 1536,
+ 2304, 3072, 3840, 4608, 5376, 6144, 6912, 7680,
+ 8448, 9216, 9984, 10752, 11520, 12288, 13056,
+ 13824, 14592, 15360, 16128, 16896, 17664, 18432,
+ 19200, 19968, 20736, 21504, 22272, 23040
+};
+
+const long FloydError5[61] =
+{
+ -38400, -37120, -35840, -34560, -33280, -32000,
+ -30720, -29440, -28160, -26880, -25600, -24320,
+ -23040, -21760, -20480, -19200, -17920, -16640,
+ -15360, -14080, -12800, -11520, -10240, -8960,
+ -7680, -6400, -5120, -3840, -2560, -1280, 0,
+ 1280, 2560, 3840, 5120, 6400, 7680, 8960, 10240,
+ 11520, 12800, 14080, 15360, 16640, 17920, 19200,
+ 20480, 21760, 23040, 24320, 25600, 26880, 28160,
+ 29440, 30720, 32000, 33280, 34560, 35840, 37120,
+ 38400
+};
+
+const long FloydError7[61] =
+{
+ -53760, -51968, -50176, -48384, -46592, -44800,
+ -43008, -41216, -39424, -37632, -35840, -34048,
+ -32256, -30464, -28672, -26880, -25088, -23296,
+ -21504, -19712, -17920, -16128, -14336, -12544,
+ -10752, -8960, -7168, -5376, -3584, -1792, 0,
+ 1792, 3584, 5376, 7168, 8960, 10752, 12544, 14336,
+ 16128, 17920, 19712, 21504, 23296, 25088, 26880,
+ 28672, 30464, 32256, 34048, 35840, 37632, 39424,
+ 41216, 43008, 44800, 46592, 48384, 50176, 51968,
+ 53760
+};
+
+const long FloydIndexMap[6] =
+{
+ -30, 21, 72, 123, 174, 225
+};
+
+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)
+ {
+ 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 = GetBitCount ();
+ bool bRet = false;
+
+ switch( eConversion )
+ {
+ case BmpConversion::N1BitThreshold:
+ {
+ BitmapEx aBmpEx(*this);
+ bRet = BitmapFilter::Filter(aBmpEx, BitmapMonochromeFilter(128));
+ *this = aBmpEx.GetBitmap();
+ }
+ break;
+
+ case BmpConversion::N4BitGreys:
+ bRet = ImplMakeGreyscales( 16 );
+ break;
+
+ case BmpConversion::N4BitColors:
+ {
+ if( nBitCount < 4 )
+ bRet = ImplConvertUp( 4 );
+ else if( nBitCount > 4 )
+ bRet = ImplConvertDown( 4 );
+ else
+ bRet = true;
+ }
+ break;
+
+ case BmpConversion::N8BitGreys:
+ case BmpConversion::N8BitNoConversion:
+ bRet = ImplMakeGreyscales( 256 );
+ break;
+
+ case BmpConversion::N8BitColors:
+ {
+ if( nBitCount < 8 )
+ bRet = ImplConvertUp( 8 );
+ else if( nBitCount > 8 )
+ bRet = ImplConvertDown( 8 );
+ else
+ bRet = true;
+ }
+ break;
+
+ case BmpConversion::N8BitTrans:
+ {
+ Color aTrans( BMP_COL_TRANS );
+
+ if( nBitCount < 8 )
+ bRet = ImplConvertUp( 8, &aTrans );
+ else
+ bRet = ImplConvertDown( 8, &aTrans );
+ }
+ break;
+
+ case BmpConversion::N24Bit:
+ {
+ if( nBitCount < 24 )
+ bRet = ImplConvertUp( 24 );
+ else
+ bRet = true;
+ }
+ break;
+
+ case BmpConversion::N32Bit:
+ {
+ if( nBitCount < 32 )
+ bRet = ImplConvertUp( 32 );
+ else
+ bRet = true;
+ }
+ break;
+
+ default:
+ OSL_FAIL( "Bitmap::Convert(): Unsupported conversion" );
+ break;
+ }
+
+ return bRet;
+}
+
+bool Bitmap::ImplMakeGreyscales( sal_uInt16 nGreys )
+{
+ SAL_WARN_IF( nGreys != 16 && nGreys != 256, "vcl", "Only 16 or 256 greyscales are supported!" );
+
+ ScopedReadAccess pReadAcc(*this);
+ bool bRet = false;
+
+ if( pReadAcc )
+ {
+ const BitmapPalette& rPal = GetGreyPalette( nGreys );
+ sal_uLong nShift = ( ( nGreys == 16 ) ? 4UL : 0UL );
+ bool bPalDiffers = !pReadAcc->HasPalette() || ( rPal.GetEntryCount() != pReadAcc->GetPaletteEntryCount() );
+
+ if( !bPalDiffers )
+ bPalDiffers = ( rPal != pReadAcc->GetPalette() );
+
+ if( bPalDiffers )
+ {
+ Bitmap aNewBmp( GetSizePixel(), ( nGreys == 16 ) ? 4 : 8, &rPal );
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if( pWriteAcc )
+ {
+ const long nWidth = pWriteAcc->Width();
+ const long nHeight = pWriteAcc->Height();
+
+ if( pReadAcc->HasPalette() )
+ {
+ for( long nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for( 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( long nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pReadScan = pReadAcc->GetScanline( nY );
+ Scanline pWriteScan = pWriteAcc->GetScanline( nY );
+
+ for( 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( long nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pReadScan = pReadAcc->GetScanline( nY );
+ Scanline pWriteScan = pWriteAcc->GetScanline( nY );
+
+ for( 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( long nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for( long nX = 0; nX < nWidth; nX++ )
+ pWriteAcc->SetPixelOnData( pScanline, nX, BitmapColor(pReadAcc->GetPixelFromData( pScanlineRead, nX ).GetLuminance() >> nShift) );
+ }
+ }
+
+ pWriteAcc.reset();
+ bRet = true;
+ }
+
+ pReadAcc.reset();
+
+ if( bRet )
+ {
+ const MapMode aMap( maPrefMapMode );
+ const Size aSize( maPrefSize );
+
+ *this = aNewBmp;
+
+ maPrefMapMode = aMap;
+ maPrefSize = aSize;
+ }
+ }
+ else
+ {
+ pReadAcc.reset();
+ bRet = true;
+ }
+ }
+
+ return bRet;
+}
+
+bool Bitmap::ImplConvertUp(sal_uInt16 nBitCount, Color const * pExtColor)
+{
+ SAL_WARN_IF( nBitCount <= GetBitCount(), "vcl", "New BitCount must be greater!" );
+
+ Bitmap::ScopedReadAccess pReadAcc(*this);
+ bool bRet = false;
+
+ if (pReadAcc)
+ {
+ BitmapPalette aPalette;
+ Bitmap aNewBmp(GetSizePixel(), nBitCount, pReadAcc->HasPalette() ? &pReadAcc->GetPalette() : &aPalette);
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if (pWriteAcc)
+ {
+ const long nWidth = pWriteAcc->Width();
+ const long nHeight = pWriteAcc->Height();
+
+ if (pWriteAcc->HasPalette())
+ {
+ const BitmapPalette& rOldPalette = pReadAcc->GetPalette();
+ const sal_uInt16 nOldCount = rOldPalette.GetEntryCount();
+ assert(nOldCount <= (1 << GetBitCount()));
+
+ aPalette.SetEntryCount(1 << nBitCount);
+
+ for (sal_uInt16 i = 0; i < nOldCount; i++)
+ aPalette[i] = rOldPalette[i];
+
+ if (pExtColor)
+ aPalette[aPalette.GetEntryCount() - 1] = *pExtColor;
+
+ pWriteAcc->SetPalette(aPalette);
+
+ for (long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for (long nX = 0; nX < nWidth; nX++)
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, pReadAcc->GetPixelFromData(pScanlineRead, nX));
+ }
+ }
+ }
+ else
+ {
+ if (pReadAcc->HasPalette())
+ {
+ for (long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for (long nX = 0; nX < nWidth; nX++)
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, pReadAcc->GetPaletteColor(pReadAcc->GetIndexFromData(pScanlineRead, nX)));
+ }
+ }
+ }
+ else
+ {
+ for (long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for (long nX = 0; nX < nWidth; nX++)
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, pReadAcc->GetPixelFromData(pScanlineRead, nX));
+ }
+ }
+ }
+ }
+ bRet = true;
+ }
+
+ if (bRet)
+ {
+ const MapMode aMap(maPrefMapMode);
+ const Size aSize(maPrefSize);
+
+ *this = aNewBmp;
+
+ maPrefMapMode = aMap;
+ maPrefSize = aSize;
+ }
+ }
+
+ return bRet;
+}
+
+bool Bitmap::ImplConvertDown(sal_uInt16 nBitCount, Color const * pExtColor)
+{
+ SAL_WARN_IF(nBitCount > GetBitCount(), "vcl", "New BitCount must be lower ( or equal when pExtColor is set )!");
+
+ Bitmap::ScopedReadAccess pReadAcc(*this);
+ bool bRet = false;
+
+ if (pReadAcc)
+ {
+ BitmapPalette aPalette;
+ Bitmap aNewBmp(GetSizePixel(), nBitCount, &aPalette);
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if (pWriteAcc)
+ {
+ const sal_uInt16 nCount = 1 << nBitCount;
+ const long nWidth = pWriteAcc->Width();
+ const long nWidth1 = nWidth - 1;
+ const 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;
+ 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 (long nY = 0; nY < std::min(nHeight, 2L); nY++, nYTmp++)
+ {
+ pQLine2 = !nY ? aErrQuad1.data() : aErrQuad2.data();
+ Scanline pScanlineRead = pReadAcc->GetScanline(nYTmp);
+ for (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 (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));
+
+ long nX;
+ for (nX = 1; nX < nWidth1; nX++)
+ {
+ aColor = pQLine1[nX].ImplGetColor();
+ cIndex = static_cast<sal_uInt8>(aColorMap.GetBestPaletteIndex(aColor));
+ aErrQuad = (ImpErrorQuad(aColor) -= pWriteAcc->GetPaletteColor(cIndex));
+ pQLine1[++nX].ImplAddColorError7(aErrQuad);
+ pQLine2[nX--].ImplAddColorError1(aErrQuad);
+ pQLine2[nX--].ImplAddColorError5(aErrQuad);
+ pQLine2[nX++].ImplAddColorError3(aErrQuad);
+ pWriteAcc->SetPixelOnData(pScanline, nX, BitmapColor(cIndex));
+ }
+
+ // Last RowPixel
+ if (nX < nWidth)
+ {
+ cIndex = static_cast<sal_uInt8>(aColorMap.GetBestPaletteIndex(pQLine1[nWidth1].ImplGetColor()));
+ pWriteAcc->SetPixelOnData(pScanline, nX, BitmapColor(cIndex));
+ }
+
+ // Refill/copy row buffer
+ pQLine1 = pQLine2;
+ bQ1 = !bQ1;
+ pQLine2 = bQ1 ? aErrQuad2.data() : aErrQuad1.data();
+
+ if (nYTmp < nHeight)
+ {
+ Scanline pScanlineRead = pReadAcc->GetScanline(nYTmp);
+ for (nX = 0; nX < nWidth; nX++)
+ {
+ if (pReadAcc->HasPalette())
+ pQLine2[nX] = pReadAcc->GetPaletteColor(pReadAcc->GetIndexFromData(pScanlineRead, nX));
+ else
+ pQLine2[nX] = pReadAcc->GetPixelFromData(pScanlineRead, nX);
+ }
+ }
+ }
+
+ bRet = true;
+ }
+ pWriteAcc.reset();
+
+ if(bRet)
+ {
+ const MapMode aMap(maPrefMapMode);
+ const Size aSize(maPrefSize);
+
+ *this = aNewBmp;
+
+ maPrefMapMode = aMap;
+ maPrefSize = aSize;
+ }
+ }
+
+ return bRet;
+}
+
+bool Bitmap::Scale( const double& rScaleX, const double& rScaleY, BmpScaleFlag nScaleFlag )
+{
+ if(basegfx::fTools::equalZero(rScaleX) || basegfx::fTools::equalZero(rScaleY))
+ {
+ // no scale
+ return true;
+ }
+
+ if(basegfx::fTools::equal(rScaleX, 1.0) && basegfx::fTools::equal(rScaleY, 1.0))
+ {
+ // no scale
+ return true;
+ }
+
+ const sal_uInt16 nStartCount(GetBitCount());
+
+ if (mxSalBmp && mxSalBmp->ScalingSupported())
+ {
+ // implementation specific scaling
+ std::shared_ptr<SalBitmap> xImpBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
+ if (xImpBmp->Create(*mxSalBmp) && xImpBmp->Scale(rScaleX, rScaleY, nScaleFlag))
+ {
+ ImplSetSalBitmap(xImpBmp);
+ SAL_INFO( "vcl.opengl", "Ref count: " << mxSalBmp.use_count() );
+ maPrefMapMode = MapMode( MapUnit::MapPixel );
+ maPrefSize = xImpBmp->GetSize();
+ return true;
+ }
+ }
+
+ // fdo#33455
+ //
+ // If we start with a 1 bit image, then after scaling it in any mode except
+ // BmpScaleFlag::Fast we have a 24bit image which is perfectly correct, but we
+ // are going to down-shift it to mono again and Bitmap::MakeMonochrome just
+ // has "Bitmap aNewBmp( GetSizePixel(), 1 );" to create a 1 bit bitmap which
+ // will default to black/white and the colors mapped to which ever is closer
+ // to black/white
+ //
+ // So the easiest thing to do to retain the colors of 1 bit bitmaps is to
+ // just use the fast scale rather than attempting to count unique colors in
+ // the other converters and pass all the info down through
+ // Bitmap::MakeMonochrome
+ if (nStartCount == 1)
+ nScaleFlag = BmpScaleFlag::Fast;
+
+ BitmapEx aBmpEx(*this);
+ bool bRetval(false);
+
+ switch(nScaleFlag)
+ {
+ case BmpScaleFlag::Default:
+ if (GetSizePixel().Width() < 2 || GetSizePixel().Height() < 2)
+ bRetval = BitmapFilter::Filter(aBmpEx, BitmapFastScaleFilter(rScaleX, rScaleY));
+ else
+ bRetval = BitmapFilter::Filter(aBmpEx, BitmapScaleSuperFilter(rScaleX, rScaleY));
+ break;
+
+ case BmpScaleFlag::Fast:
+ case BmpScaleFlag::NearestNeighbor:
+ bRetval = BitmapFilter::Filter(aBmpEx, BitmapFastScaleFilter(rScaleX, rScaleY));
+ break;
+
+ case BmpScaleFlag::Interpolate:
+ bRetval = BitmapFilter::Filter(aBmpEx, BitmapInterpolateScaleFilter(rScaleX, rScaleY));
+ break;
+
+ case BmpScaleFlag::Super:
+ bRetval = BitmapFilter::Filter(aBmpEx, BitmapScaleSuperFilter(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 || nStartCount == GetBitCount(), "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
+#if HAVE_FEATURE_OPENGL
+ if( OpenGLHelper::isVCLOpenGLEnabled())
+ 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(GetBitCount() != rNew.GetBitCount())
+ {
+ switch(GetBitCount())
+ {
+ case 1:
+ {
+ rNew.Convert(BmpConversion::N1BitThreshold);
+ break;
+ }
+ case 4:
+ {
+ if(HasGreyPaletteAny())
+ {
+ rNew.Convert(BmpConversion::N4BitGreys);
+ }
+ else
+ {
+ rNew.Convert(BmpConversion::N4BitColors);
+ }
+ break;
+ }
+ case 8:
+ {
+ if(HasGreyPaletteAny())
+ {
+ rNew.Convert(BmpConversion::N8BitGreys);
+ }
+ else
+ {
+ rNew.Convert(BmpConversion::N8BitColors);
+ }
+ break;
+ }
+ case 24:
+ {
+ rNew.Convert(BmpConversion::N24Bit);
+ break;
+ }
+ case 32:
+ {
+ rNew.Convert(BmpConversion::N32Bit);
+ break;
+ }
+ default:
+ {
+ SAL_WARN("vcl", "BitDepth adaptation failed, from " << rNew.GetBitCount() << " to " << GetBitCount());
+ break;
+ }
+ }
+ }
+}
+
+bool Bitmap::Dither()
+{
+ const Size aSize( GetSizePixel() );
+
+ if( aSize.Width() == 1 || aSize.Height() == 1 )
+ return true;
+
+ bool bRet = false;
+
+ if( ( aSize.Width() > 3 ) && ( aSize.Height() > 2 ) )
+ {
+ ScopedReadAccess pReadAcc(*this);
+ Bitmap aNewBmp( GetSizePixel(), 8 );
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if( pReadAcc && pWriteAcc )
+ {
+ BitmapColor aColor;
+ long nWidth = pReadAcc->Width();
+ long nWidth1 = nWidth - 1;
+ long nHeight = pReadAcc->Height();
+ long nX;
+ long nW = nWidth * 3;
+ long nW2 = nW - 3;
+ long nRErr, nGErr, nBErr;
+ long nRC, nGC, nBC;
+ std::unique_ptr<long[]> p1(new long[ nW ]);
+ std::unique_ptr<long[]> p2(new long[ nW ]);
+ long* p1T = p1.get();
+ long* p2T = p2.get();
+ long* pTmp;
+ bool bPal = pReadAcc->HasPalette();
+
+ pTmp = p2T;
+
+ if( bPal )
+ {
+ Scanline pScanlineRead = pReadAcc->GetScanline(0);
+ for( long nZ = 0; nZ < nWidth; nZ++ )
+ {
+ aColor = pReadAcc->GetPaletteColor( pReadAcc->GetIndexFromData( pScanlineRead, nZ ) );
+
+ *pTmp++ = static_cast<long>(aColor.GetBlue()) << 12;
+ *pTmp++ = static_cast<long>(aColor.GetGreen()) << 12;
+ *pTmp++ = static_cast<long>(aColor.GetRed()) << 12;
+ }
+ }
+ else
+ {
+ Scanline pScanlineRead = pReadAcc->GetScanline(0);
+ for( long nZ = 0; nZ < nWidth; nZ++ )
+ {
+ aColor = pReadAcc->GetPixelFromData( pScanlineRead, nZ );
+
+ *pTmp++ = static_cast<long>(aColor.GetBlue()) << 12;
+ *pTmp++ = static_cast<long>(aColor.GetGreen()) << 12;
+ *pTmp++ = static_cast<long>(aColor.GetRed()) << 12;
+ }
+ }
+
+ for( long nY = 1, nYAcc = 0; nY <= nHeight; nY++, nYAcc++ )
+ {
+ pTmp = p1T;
+ p1T = p2T;
+ p2T = pTmp;
+
+ if( nY < nHeight )
+ {
+ if( bPal )
+ {
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for( long nZ = 0; nZ < nWidth; nZ++ )
+ {
+ aColor = pReadAcc->GetPaletteColor( pReadAcc->GetIndexFromData( pScanlineRead, nZ ) );
+
+ *pTmp++ = static_cast<long>(aColor.GetBlue()) << 12;
+ *pTmp++ = static_cast<long>(aColor.GetGreen()) << 12;
+ *pTmp++ = static_cast<long>(aColor.GetRed()) << 12;
+ }
+ }
+ else
+ {
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for( long nZ = 0; nZ < nWidth; nZ++ )
+ {
+ aColor = pReadAcc->GetPixelFromData( pScanlineRead, nZ );
+
+ *pTmp++ = static_cast<long>(aColor.GetBlue()) << 12;
+ *pTmp++ = static_cast<long>(aColor.GetGreen()) << 12;
+ *pTmp++ = static_cast<long>(aColor.GetRed()) << 12;
+ }
+ }
+ }
+
+ // Examine first Pixel separately
+ nX = 0;
+ long nTemp;
+ CALC_ERRORS;
+ CALC_TABLES7;
+ nX -= 5;
+ CALC_TABLES5;
+ Scanline pScanline = pWriteAcc->GetScanline(nYAcc);
+ pWriteAcc->SetPixelOnData( pScanline, 0, BitmapColor(static_cast<sal_uInt8>(nVCLBLut[ nBC ] + nVCLGLut[nGC ] + nVCLRLut[nRC ])) );
+
+ // Get middle Pixels using a loop
+ long nXAcc;
+ for ( nX = 3, nXAcc = 1; nX < nW2; nXAcc++ )
+ {
+ CALC_ERRORS;
+ CALC_TABLES7;
+ nX -= 8;
+ CALC_TABLES3;
+ CALC_TABLES5;
+ pWriteAcc->SetPixelOnData( pScanline, nXAcc, BitmapColor(static_cast<sal_uInt8>(nVCLBLut[ nBC ] + nVCLGLut[nGC ] + nVCLRLut[nRC ])) );
+ }
+
+ // Treat last Pixel separately
+ CALC_ERRORS;
+ nX -= 5;
+ CALC_TABLES3;
+ CALC_TABLES5;
+ pWriteAcc->SetPixelOnData( pScanline, nWidth1, BitmapColor(static_cast<sal_uInt8>(nVCLBLut[ nBC ] + nVCLGLut[nGC ] + nVCLRLut[nRC ])) );
+ }
+
+ bRet = true;
+ }
+
+ pReadAcc.reset();
+ pWriteAcc.reset();
+
+ if( bRet )
+ {
+ const MapMode aMap( maPrefMapMode );
+ const Size aPrefSize( maPrefSize );
+
+ *this = aNewBmp;
+
+ maPrefMapMode = aMap;
+ maPrefSize = aPrefSize;
+ }
+ }
+
+ return bRet;
+}
+
+void Bitmap::Vectorize( GDIMetaFile& rMtf, sal_uInt8 cReduce, const Link<long,void>* pProgress )
+{
+ ImplVectorizer::ImplVectorize( *this, rMtf, cReduce, pProgress );
+}
+
+bool Bitmap::Adjust( short nLuminancePercent, short nContrastPercent,
+ short nChannelRPercent, short nChannelGPercent, short nChannelBPercent,
+ double fGamma, bool bInvert, bool msoBrightness )
+{
+ bool bRet = false;
+
+ // nothing to do => return quickly
+ if( !nLuminancePercent && !nContrastPercent &&
+ !nChannelRPercent && !nChannelGPercent && !nChannelBPercent &&
+ ( fGamma == 1.0 ) && !bInvert )
+ {
+ bRet = true;
+ }
+ else
+ {
+ BitmapScopedWriteAccess pAcc(*this);
+
+ if( pAcc )
+ {
+ BitmapColor aCol;
+ const long nW = pAcc->Width();
+ const long nH = pAcc->Height();
+ std::unique_ptr<sal_uInt8[]> cMapR(new sal_uInt8[ 256 ]);
+ std::unique_ptr<sal_uInt8[]> cMapG(new sal_uInt8[ 256 ]);
+ std::unique_ptr<sal_uInt8[]> cMapB(new sal_uInt8[ 256 ]);
+ double fM, fROff, fGOff, fBOff, fOff;
+
+ // calculate slope
+ if( nContrastPercent >= 0 )
+ fM = 128.0 / ( 128.0 - 1.27 * MinMax( nContrastPercent, 0, 100 ) );
+ else
+ fM = ( 128.0 + 1.27 * MinMax( nContrastPercent, -100, 0 ) ) / 128.0;
+
+ if(!msoBrightness)
+ // total offset = luminance offset + contrast offset
+ fOff = MinMax( nLuminancePercent, -100, 100 ) * 2.55 + 128.0 - fM * 128.0;
+ else
+ fOff = MinMax( nLuminancePercent, -100, 100 ) * 2.55;
+
+ // channel offset = channel offset + total offset
+ fROff = nChannelRPercent * 2.55 + fOff;
+ fGOff = nChannelGPercent * 2.55 + fOff;
+ fBOff = nChannelBPercent * 2.55 + fOff;
+
+ // calculate gamma value
+ fGamma = ( fGamma <= 0.0 || fGamma > 10.0 ) ? 1.0 : ( 1.0 / fGamma );
+ const bool bGamma = ( fGamma != 1.0 );
+
+ // create mapping table
+ for( long nX = 0; nX < 256; nX++ )
+ {
+ if(!msoBrightness)
+ {
+ cMapR[ nX ] = static_cast<sal_uInt8>(MinMax( FRound( nX * fM + fROff ), 0, 255 ));
+ cMapG[ nX ] = static_cast<sal_uInt8>(MinMax( FRound( nX * fM + fGOff ), 0, 255 ));
+ cMapB[ nX ] = static_cast<sal_uInt8>(MinMax( FRound( nX * fM + fBOff ), 0, 255 ));
+ }
+ else
+ {
+ // LO simply uses (in a somewhat optimized form) "newcolor = (oldcolor-128)*contrast+brightness+128"
+ // as the formula, i.e. contrast first, brightness afterwards. MSOffice, for whatever weird reason,
+ // use neither first, but apparently it applies half of brightness before contrast and half afterwards.
+ cMapR[ nX ] = static_cast<sal_uInt8>(MinMax( FRound( (nX+fROff/2-128) * fM + 128 + fROff/2 ), 0, 255 ));
+ cMapG[ nX ] = static_cast<sal_uInt8>(MinMax( FRound( (nX+fGOff/2-128) * fM + 128 + fGOff/2 ), 0, 255 ));
+ cMapB[ nX ] = static_cast<sal_uInt8>(MinMax( FRound( (nX+fBOff/2-128) * fM + 128 + fBOff/2 ), 0, 255 ));
+ }
+ if( bGamma )
+ {
+ cMapR[ nX ] = GAMMA( cMapR[ nX ], fGamma );
+ cMapG[ nX ] = GAMMA( cMapG[ nX ], fGamma );
+ cMapB[ nX ] = GAMMA( cMapB[ nX ], fGamma );
+ }
+
+ if( bInvert )
+ {
+ cMapR[ nX ] = ~cMapR[ nX ];
+ cMapG[ nX ] = ~cMapG[ nX ];
+ cMapB[ nX ] = ~cMapB[ nX ];
+ }
+ }
+
+ // do modifying
+ if( pAcc->HasPalette() )
+ {
+ BitmapColor aNewCol;
+
+ for( sal_uInt16 i = 0, nCount = pAcc->GetPaletteEntryCount(); i < nCount; i++ )
+ {
+ const BitmapColor& rCol = pAcc->GetPaletteColor( i );
+ aNewCol.SetRed( cMapR[ rCol.GetRed() ] );
+ aNewCol.SetGreen( cMapG[ rCol.GetGreen() ] );
+ aNewCol.SetBlue( cMapB[ rCol.GetBlue() ] );
+ pAcc->SetPaletteColor( i, aNewCol );
+ }
+ }
+ else if( pAcc->GetScanlineFormat() == ScanlineFormat::N24BitTcBgr )
+ {
+ for( long nY = 0; nY < nH; nY++ )
+ {
+ Scanline pScan = pAcc->GetScanline( nY );
+
+ for( 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( long nY = 0; nY < nH; nY++ )
+ {
+ Scanline pScan = pAcc->GetScanline( nY );
+
+ for( long nX = 0; nX < nW; nX++ )
+ {
+ *pScan = cMapR[ *pScan ]; pScan++;
+ *pScan = cMapG[ *pScan ]; pScan++;
+ *pScan = cMapB[ *pScan ]; pScan++;
+ }
+ }
+ }
+ else
+ {
+ for( long nY = 0; nY < nH; nY++ )
+ {
+ Scanline pScanline = pAcc->GetScanline(nY);
+ for( long nX = 0; nX < nW; nX++ )
+ {
+ aCol = pAcc->GetPixelFromData( pScanline, nX );
+ aCol.SetRed( cMapR[ aCol.GetRed() ] );
+ aCol.SetGreen( cMapG[ aCol.GetGreen() ] );
+ aCol.SetBlue( cMapB[ aCol.GetBlue() ] );
+ pAcc->SetPixelOnData( pScanline, nX, aCol );
+ }
+ }
+ }
+
+ pAcc.reset();
+ bRet = true;
+ }
+ }
+
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/bitmapex.cxx b/vcl/source/gdi/bitmapex.cxx
new file mode 100644
index 000000000..f7e80fd72
--- /dev/null
+++ b/vcl/source/gdi/bitmapex.cxx
@@ -0,0 +1,1685 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/log.hxx>
+#include <rtl/math.hxx>
+#include <o3tl/underlyingenumvalue.hxx>
+#include <osl/diagnose.h>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/color/bcolormodifier.hxx>
+
+#include <vcl/ImageTree.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/alpha.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/bitmapaccess.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 <bitmapwriteaccess.hxx>
+
+#include <o3tl/any.hxx>
+
+#include <com/sun/star/beans/XFastPropertySet.hpp>
+
+#include <memory>
+
+using namespace ::com::sun::star;
+
+BitmapEx::BitmapEx()
+ : meTransparent(TransparentType::NONE)
+ , mbAlpha(false)
+{
+}
+
+BitmapEx::BitmapEx( const BitmapEx& ) = default;
+
+BitmapEx::BitmapEx( const BitmapEx& rBitmapEx, Point aSrc, Size aSize )
+ : meTransparent(TransparentType::NONE)
+ , mbAlpha(false)
+{
+ if( rBitmapEx.IsEmpty() )
+ return;
+
+ maBitmap = Bitmap( aSize, rBitmapEx.maBitmap.GetBitCount() );
+ SetSizePixel(aSize);
+ if( rBitmapEx.IsAlpha() )
+ {
+ mbAlpha = true;
+ maMask = AlphaMask( aSize ).ImplGetBitmap();
+ }
+ else if( rBitmapEx.IsTransparent() )
+ maMask = Bitmap( aSize, rBitmapEx.maMask.GetBitCount() );
+
+ tools::Rectangle aDestRect( Point( 0, 0 ), aSize );
+ tools::Rectangle aSrcRect( aSrc, aSize );
+ CopyPixel( aDestRect, aSrcRect, &rBitmapEx );
+}
+
+BitmapEx::BitmapEx( Size aSize, sal_uInt16 nBitCount )
+ : meTransparent(TransparentType::NONE)
+ , mbAlpha(false)
+{
+ maBitmap = Bitmap( aSize, nBitCount );
+ SetSizePixel(aSize);
+}
+
+BitmapEx::BitmapEx( const OUString& rIconName )
+ : meTransparent(TransparentType::NONE)
+ , mbAlpha(false)
+{
+ 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() ),
+ meTransparent( TransparentType::NONE ),
+ mbAlpha ( false )
+{
+}
+
+BitmapEx::BitmapEx( const Bitmap& rBmp, const Bitmap& rMask ) :
+ maBitmap ( rBmp ),
+ maMask ( rMask ),
+ maBitmapSize ( maBitmap.GetSizePixel() ),
+ meTransparent ( !rMask ? TransparentType::NONE : TransparentType::Bitmap ),
+ mbAlpha ( false )
+{
+ // Ensure a mask is exactly one bit deep
+ if( !!maMask && maMask.GetBitCount() != 1 )
+ {
+ SAL_WARN( "vcl", "BitmapEx: forced mask to monochrome");
+ BitmapEx aMaskEx(maMask);
+ BitmapFilter::Filter(aMaskEx, BitmapMonochromeFilter(255));
+ maMask = aMaskEx.GetBitmap();
+ }
+
+ if (!!maBitmap && !!maMask && maBitmap.GetSizePixel() != maMask.GetSizePixel())
+ {
+ OSL_ENSURE(false, "Mask size differs from Bitmap size, corrected Mask (!)");
+ maMask.Scale(maBitmap.GetSizePixel());
+ }
+}
+
+BitmapEx::BitmapEx( const Bitmap& rBmp, const AlphaMask& rAlphaMask ) :
+ maBitmap ( rBmp ),
+ maMask ( rAlphaMask.ImplGetBitmap() ),
+ maBitmapSize ( maBitmap.GetSizePixel() ),
+ meTransparent ( !rAlphaMask ? TransparentType::NONE : TransparentType::Bitmap ),
+ mbAlpha ( !rAlphaMask.IsEmpty() )
+{
+ if (!!maBitmap && !!maMask && maBitmap.GetSizePixel() != maMask.GetSizePixel())
+ {
+ OSL_ENSURE(false, "Alpha size differs from Bitmap size, corrected Mask (!)");
+ maMask.Scale(rBmp.GetSizePixel());
+ }
+
+ // #i75531# the workaround below can go when
+ // X11SalGraphics::drawAlphaBitmap()'s render acceleration
+ // can handle the bitmap depth mismatch directly
+ if( maBitmap.GetBitCount() < maMask.GetBitCount() )
+ maBitmap.Convert( BmpConversion::N24Bit );
+}
+
+BitmapEx::BitmapEx( const Bitmap& rBmp, const Color& rTransparentColor ) :
+ maBitmap ( rBmp ),
+ maBitmapSize ( maBitmap.GetSizePixel() ),
+ maTransparentColor ( rTransparentColor ),
+ meTransparent ( TransparentType::Bitmap ),
+ mbAlpha ( false )
+{
+ maMask = maBitmap.CreateMask( maTransparentColor );
+
+ SAL_WARN_IF(rBmp.GetSizePixel() != maMask.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 (meTransparent != rBitmapEx.meTransparent)
+ return false;
+
+ if (GetSizePixel() != rBitmapEx.GetSizePixel())
+ return false;
+
+ if (meTransparent != rBitmapEx.meTransparent)
+ return false;
+
+ if (meTransparent == TransparentType::Color
+ && maTransparentColor != rBitmapEx.maTransparentColor)
+ return false;
+
+ if (mbAlpha != rBitmapEx.mbAlpha)
+ return false;
+
+ if (maBitmap != rBitmapEx.maBitmap)
+ return false;
+
+ return maMask == rBitmapEx.maMask;
+}
+
+bool BitmapEx::IsEmpty() const
+{
+ return( maBitmap.IsEmpty() && maMask.IsEmpty() );
+}
+
+void BitmapEx::SetEmpty()
+{
+ maBitmap.SetEmpty();
+ maMask.SetEmpty();
+ meTransparent = TransparentType::NONE;
+ mbAlpha = false;
+}
+
+void BitmapEx::Clear()
+{
+ SetEmpty();
+}
+
+bool BitmapEx::IsTransparent() const
+{
+ return( meTransparent != TransparentType::NONE );
+}
+
+bool BitmapEx::IsAlpha() const
+{
+ return( IsTransparent() && mbAlpha );
+}
+
+const Bitmap& BitmapEx::GetBitmap() const
+{
+ return maBitmap;
+}
+
+Bitmap BitmapEx::GetBitmap( Color aTransparentReplaceColor ) const
+{
+ Bitmap aRetBmp( maBitmap );
+
+ if( meTransparent != TransparentType::NONE )
+ {
+ Bitmap aTempMask;
+
+ if( meTransparent == TransparentType::Color )
+ aTempMask = maBitmap.CreateMask( maTransparentColor );
+ else
+ aTempMask = maMask;
+
+ if( !IsAlpha() )
+ aRetBmp.Replace( aTempMask, aTransparentReplaceColor );
+ else
+ aRetBmp.Replace( GetAlpha(), aTransparentReplaceColor );
+ }
+
+ return aRetBmp;
+}
+
+Bitmap BitmapEx::GetMask() const
+{
+ if (!IsAlpha())
+ return maMask;
+
+ BitmapEx aMaskEx(maMask);
+ BitmapFilter::Filter(aMaskEx, BitmapMonochromeFilter(255));
+ return aMaskEx.GetBitmap();
+}
+
+AlphaMask BitmapEx::GetAlpha() const
+{
+ if( IsAlpha() )
+ {
+ AlphaMask aAlpha;
+ aAlpha.ImplSetBitmap( maMask );
+ return aAlpha;
+ }
+ else
+ {
+ return AlphaMask(maMask);
+ }
+}
+
+sal_uLong BitmapEx::GetSizeBytes() const
+{
+ sal_uLong nSizeBytes = maBitmap.GetSizeBytes();
+
+ if( meTransparent == TransparentType::Bitmap )
+ nSizeBytes += maMask.GetSizeBytes();
+
+ return nSizeBytes;
+}
+
+BitmapChecksum BitmapEx::GetChecksum() const
+{
+ BitmapChecksum nCrc = maBitmap.GetChecksum();
+ SVBT32 aBT32;
+ BitmapChecksumOctetArray aBCOA;
+
+ UInt32ToSVBT32( o3tl::underlyingEnumValue(meTransparent), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ UInt32ToSVBT32( sal_uInt32(mbAlpha), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ if( ( TransparentType::Bitmap == meTransparent ) && !maMask.IsEmpty() )
+ {
+ BCToBCOA( maMask.GetChecksum(), aBCOA );
+ nCrc = vcl_get_checksum( nCrc, aBCOA, BITMAP_CHECKSUM_SIZE );
+ }
+
+ return nCrc;
+}
+
+void BitmapEx::SetSizePixel(const Size& rNewSize)
+{
+ maBitmapSize = rNewSize;
+}
+
+bool BitmapEx::Invert()
+{
+ bool bRet = false;
+
+ if (!!maBitmap)
+ {
+ bRet = maBitmap.Invert();
+
+ if (bRet && (meTransparent == TransparentType::Color))
+ maTransparentColor.Invert();
+ }
+
+ return bRet;
+}
+
+bool BitmapEx::Mirror( BmpMirrorFlags nMirrorFlags )
+{
+ bool bRet = false;
+
+ if( !!maBitmap )
+ {
+ bRet = maBitmap.Mirror( nMirrorFlags );
+
+ if( bRet && ( meTransparent == TransparentType::Bitmap ) && !!maMask )
+ maMask.Mirror( nMirrorFlags );
+ }
+
+ return bRet;
+}
+
+bool BitmapEx::Scale( const double& rScaleX, const double& rScaleY, BmpScaleFlag nScaleFlag )
+{
+ bool bRet = false;
+
+ if( !!maBitmap )
+ {
+ bRet = maBitmap.Scale( rScaleX, rScaleY, nScaleFlag );
+
+ if( bRet && ( meTransparent == TransparentType::Bitmap ) && !!maMask )
+ {
+ maMask.Scale( rScaleX, rScaleY, nScaleFlag );
+ }
+
+ SetSizePixel(maBitmap.GetSizePixel());
+
+ SAL_WARN_IF( !!maMask && maBitmap.GetSizePixel() != maMask.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( long nAngle10, const Color& rFillColor )
+{
+ bool bRet = false;
+
+ if( !!maBitmap )
+ {
+ const bool bTransRotate = ( COL_TRANSPARENT == rFillColor );
+
+ if( bTransRotate )
+ {
+ if( meTransparent == TransparentType::Color )
+ bRet = maBitmap.Rotate( nAngle10, maTransparentColor );
+ else
+ {
+ bRet = maBitmap.Rotate( nAngle10, COL_BLACK );
+
+ if( meTransparent == TransparentType::NONE )
+ {
+ maMask = Bitmap(GetSizePixel(), 1);
+ maMask.Erase( COL_BLACK );
+ meTransparent = TransparentType::Bitmap;
+ }
+
+ if( bRet && !!maMask )
+ maMask.Rotate( nAngle10, COL_WHITE );
+ }
+ }
+ else
+ {
+ bRet = maBitmap.Rotate( nAngle10, rFillColor );
+
+ if( bRet && ( meTransparent == TransparentType::Bitmap ) && !!maMask )
+ maMask.Rotate( nAngle10, COL_WHITE );
+ }
+
+ SetSizePixel(maBitmap.GetSizePixel());
+
+ SAL_WARN_IF(!!maMask && maBitmap.GetSizePixel() != maMask.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 )
+ {
+ bRet = maBitmap.Crop( rRectPixel );
+
+ if( bRet && ( meTransparent == TransparentType::Bitmap ) && !!maMask )
+ maMask.Crop( rRectPixel );
+
+ SetSizePixel(maBitmap.GetSizePixel());
+
+ SAL_WARN_IF(!!maMask && maBitmap.GetSizePixel() != maMask.GetSizePixel(), "vcl",
+ "BitmapEx::Crop(): size mismatch for bitmap and alpha mask.");
+ }
+
+ return bRet;
+}
+
+bool BitmapEx::Convert( BmpConversion eConversion )
+{
+ return !!maBitmap && maBitmap.Convert( eConversion );
+}
+
+void BitmapEx::Expand( sal_uLong nDX, sal_uLong nDY, bool bExpandTransparent )
+{
+ bool bRet = false;
+
+ if( !!maBitmap )
+ {
+ bRet = maBitmap.Expand( nDX, nDY );
+
+ if( bRet && ( meTransparent == TransparentType::Bitmap ) && !!maMask )
+ {
+ Color aColor( bExpandTransparent ? COL_WHITE : COL_BLACK );
+ maMask.Expand( nDX, nDY, &aColor );
+ }
+
+ SetSizePixel(maBitmap.GetSizePixel());
+
+ SAL_WARN_IF(!!maMask && maBitmap.GetSizePixel() != maMask.GetSizePixel(), "vcl",
+ "BitmapEx::Expand(): size mismatch for bitmap and alpha mask.");
+ }
+}
+
+bool BitmapEx::CopyPixel( const tools::Rectangle& rRectDst, const tools::Rectangle& rRectSrc,
+ const BitmapEx* pBmpExSrc )
+{
+ bool bRet = false;
+
+ if( !pBmpExSrc || pBmpExSrc->IsEmpty() )
+ {
+ if( !maBitmap.IsEmpty() )
+ {
+ bRet = maBitmap.CopyPixel( rRectDst, rRectSrc );
+
+ if( bRet && ( meTransparent == TransparentType::Bitmap ) && !!maMask )
+ maMask.CopyPixel( rRectDst, rRectSrc );
+ }
+ }
+ else
+ {
+ if( !maBitmap.IsEmpty() )
+ {
+ bRet = maBitmap.CopyPixel( rRectDst, rRectSrc, &pBmpExSrc->maBitmap );
+
+ if( bRet )
+ {
+ if( pBmpExSrc->IsAlpha() )
+ {
+ if( IsAlpha() )
+ // cast to use the optimized AlphaMask::CopyPixel
+ maMask.CopyPixel_AlphaOptimized( rRectDst, rRectSrc, &pBmpExSrc->maMask );
+ else if( IsTransparent() )
+ {
+ std::unique_ptr<AlphaMask> pAlpha(new AlphaMask( maMask ));
+
+ maMask = pAlpha->ImplGetBitmap();
+ pAlpha.reset();
+ mbAlpha = true;
+ maMask.CopyPixel( rRectDst, rRectSrc, &pBmpExSrc->maMask );
+ }
+ else
+ {
+ sal_uInt8 cBlack = 0;
+ std::unique_ptr<AlphaMask> pAlpha(new AlphaMask(GetSizePixel(), &cBlack));
+
+ maMask = pAlpha->ImplGetBitmap();
+ pAlpha.reset();
+ meTransparent = TransparentType::Bitmap;
+ mbAlpha = true;
+ maMask.CopyPixel( rRectDst, rRectSrc, &pBmpExSrc->maMask );
+ }
+ }
+ else if( pBmpExSrc->IsTransparent() )
+ {
+ if (IsAlpha())
+ {
+ AlphaMask aAlpha( pBmpExSrc->maMask );
+ maMask.CopyPixel( rRectDst, rRectSrc, &aAlpha.ImplGetBitmap() );
+ }
+ else if (IsTransparent())
+ {
+ maMask.CopyPixel( rRectDst, rRectSrc, &pBmpExSrc->maMask );
+ }
+ else
+ {
+ maMask = Bitmap(GetSizePixel(), 1);
+ maMask.Erase(COL_BLACK);
+ meTransparent = TransparentType::Bitmap;
+ maMask.CopyPixel( rRectDst, rRectSrc, &pBmpExSrc->maMask );
+ }
+ }
+ else if (IsAlpha())
+ {
+ sal_uInt8 cBlack = 0;
+ const AlphaMask aAlphaSrc(pBmpExSrc->GetSizePixel(), &cBlack);
+
+ maMask.CopyPixel( rRectDst, rRectSrc, &aAlphaSrc.ImplGetBitmap() );
+ }
+ else if (IsTransparent())
+ {
+ Bitmap aMaskSrc(pBmpExSrc->GetSizePixel(), 1);
+
+ aMaskSrc.Erase( COL_BLACK );
+ maMask.CopyPixel( rRectDst, rRectSrc, &aMaskSrc );
+ }
+ }
+ }
+ }
+
+ return bRet;
+}
+
+bool BitmapEx::Erase( const Color& rFillColor )
+{
+ bool bRet = false;
+
+ if( !!maBitmap )
+ {
+ bRet = maBitmap.Erase( rFillColor );
+
+ if( bRet && ( meTransparent == TransparentType::Bitmap ) && !!maMask )
+ {
+ // Respect transparency on fill color
+ if( rFillColor.GetTransparency() )
+ {
+ const Color aFill( rFillColor.GetTransparency(), rFillColor.GetTransparency(), rFillColor.GetTransparency() );
+ maMask.Erase( aFill );
+ }
+ else
+ {
+ const Color aBlack( COL_BLACK );
+ maMask.Erase( aBlack );
+ }
+ }
+ }
+
+ return bRet;
+}
+
+void BitmapEx::Replace( const Color& rSearchColor, const Color& rReplaceColor )
+{
+ if (!!maBitmap)
+ maBitmap.Replace( rSearchColor, rReplaceColor );
+}
+
+void BitmapEx::Replace( const Color* pSearchColors, const Color* pReplaceColors, sal_uLong nColorCount )
+{
+ if (!!maBitmap)
+ 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 && 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 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();
+
+ Size aScaledSize;
+ 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;
+ }
+
+ aScaledSize = Size( 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(),
+ DeviceFormat::DEFAULT, DeviceFormat::BITMASK);
+ aVirDevice->SetOutputSizePixel( aStdSize );
+ aVirDevice->SetFillColor( COL_TRANSPARENT );
+ aVirDevice->SetLineColor( COL_TRANSPARENT );
+
+ // Draw a rect into virDevice
+ aVirDevice->DrawRect( aRect );
+ Point aPointPixel( static_cast<long>(imgposX), static_cast<long>(imgposY) );
+ aVirDevice->DrawBitmapEx( aPointPixel, aRet );
+ aRet = aVirDevice->GetBitmapEx( aEmptyPoint, aStdSize );
+
+ return aRet;
+}
+
+sal_uInt8 BitmapEx::GetTransparency(sal_Int32 nX, sal_Int32 nY) const
+{
+ sal_uInt8 nTransparency(0xff);
+
+ if(!maBitmap.IsEmpty())
+ {
+ if (nX >= 0 && nX < GetSizePixel().Width() && nY >= 0 && nY < GetSizePixel().Height())
+ {
+ if (maBitmap.GetBitCount() == 32)
+ return GetPixelColor(nX, nY).GetTransparency();
+ switch(meTransparent)
+ {
+ case TransparentType::NONE:
+ {
+ // Not transparent, ergo all covered
+ nTransparency = 0x00;
+ break;
+ }
+ case TransparentType::Color:
+ {
+ Bitmap aTestBitmap(maBitmap);
+ Bitmap::ScopedReadAccess pRead(aTestBitmap);
+
+ if(pRead)
+ {
+ const BitmapColor aBmpColor = pRead->GetColor(nY, nX);
+
+ // If color is not equal to TransparentColor, we are not transparent
+ if (aBmpColor != maTransparentColor)
+ nTransparency = 0x00;
+
+ }
+ break;
+ }
+ case TransparentType::Bitmap:
+ {
+ if(!maMask.IsEmpty())
+ {
+ Bitmap aTestBitmap(maMask);
+ Bitmap::ScopedReadAccess pRead(aTestBitmap);
+
+ if(pRead)
+ {
+ const BitmapColor aBitmapColor(pRead->GetPixel(nY, nX));
+
+ if(mbAlpha)
+ {
+ nTransparency = aBitmapColor.GetIndex();
+ }
+ else
+ {
+ if(0x00 == aBitmapColor.GetIndex())
+ {
+ nTransparency = 0x00;
+ }
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ return nTransparency;
+}
+
+
+Color BitmapEx::GetPixelColor(sal_Int32 nX, sal_Int32 nY) const
+{
+ Bitmap::ScopedReadAccess pReadAccess( const_cast<Bitmap&>(maBitmap) );
+ assert(pReadAccess);
+
+ BitmapColor aColor = pReadAccess->GetColor(nY, nX);
+
+ if (IsAlpha())
+ {
+ AlphaMask aAlpha = GetAlpha();
+ AlphaMask::ScopedReadAccess pAlphaReadAccess(aAlpha);
+ aColor.SetTransparency(pAlphaReadAccess->GetPixel(nY, nX).GetIndex());
+ }
+ else if (maBitmap.GetBitCount() != 32)
+ {
+ aColor.SetTransparency(0);
+ }
+ 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, 24);
+ BitmapScopedWriteAccess xWrite(aDestination);
+
+ if(xWrite)
+ {
+ Bitmap::ScopedReadAccess xRead(const_cast< Bitmap& >(rSource));
+
+ if (xRead)
+ {
+ const Size aDestinationSizePixel(aDestination.GetSizePixel());
+ const BitmapColor aOutside(BitmapColor(0xff, 0xff, 0xff));
+
+ for(long y(0); y < aDestinationSizePixel.getHeight(); y++)
+ {
+ Scanline pScanline = xWrite->GetScanline( y );
+ for(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, F_2PI );
+ if (fRotate < 0)
+ {
+ fRotate += F_2PI;
+ }
+ if (!rtl::math::approxEqual(fRotate, 0)
+ && !rtl::math::approxEqual(fRotate, F_PI2)
+ && !rtl::math::approxEqual(fRotate, F_PI)
+ && !rtl::math::approxEqual(fRotate, 3 * F_PI2))
+ {
+ 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(IsTransparent())
+ {
+ if(IsAlpha())
+ {
+ const Bitmap aAlpha(impTransformBitmap(GetAlpha().GetBitmap(), aDestinationSize, rTransformation, bSmooth));
+ return BitmapEx(aDestination, AlphaMask(aAlpha));
+ }
+ else
+ {
+ const Bitmap aLclMask(impTransformBitmap(GetMask(), aDestinationSize, rTransformation, false));
+ return BitmapEx(aDestination, aLclMask);
+ }
+ }
+
+ 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(IsTransparent())
+ {
+ // clear bitmap with dest color
+ if(aChangedBitmap.GetBitCount() <= 8)
+ {
+ // For e.g. 8bit Bitmaps, the nearest color to the given erase color is
+ // determined and used -> this may be different from what is wanted here.
+ // Better create a new bitmap with the needed color explicitly.
+ Bitmap::ScopedReadAccess xReadAccess(aChangedBitmap);
+ OSL_ENSURE(xReadAccess, "Got no Bitmap ReadAccess ?!?");
+
+ if(xReadAccess)
+ {
+ BitmapPalette aNewPalette(xReadAccess->GetPalette());
+ aNewPalette[0] = BitmapColor(Color(pReplace->getBColor()));
+ aChangedBitmap = Bitmap(
+ aChangedBitmap.GetSizePixel(),
+ aChangedBitmap.GetBitCount(),
+ &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(long y(0); y < xContent->Height(); y++)
+ {
+ Scanline pScan = xContent->GetScanline(y);
+
+ for(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(long y(0); y < xContent->Height(); y++)
+ {
+ Scanline pScan = xContent->GetScanline(y);
+
+ for(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(long y(0); y < xContent->Height(); y++)
+ {
+ Scanline pScanline = xContent->GetScanline( y );
+ for(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(IsTransparent())
+ {
+ if(IsAlpha())
+ {
+ return BitmapEx(aChangedBitmap, GetAlpha());
+ }
+ else
+ {
+ return BitmapEx(aChangedBitmap, GetMask());
+ }
+ }
+ else
+ {
+ return BitmapEx(aChangedBitmap);
+ }
+ }
+}
+
+BitmapEx createBlendFrame(
+ const Size& rSize,
+ sal_uInt8 nAlpha,
+ Color aColorTopLeft,
+ Color aColorBottomRight)
+{
+ const sal_uInt32 nW(rSize.Width());
+ const sal_uInt32 nH(rSize.Height());
+
+ if(nW || nH)
+ {
+ Color aColTopRight(aColorTopLeft);
+ Color aColBottomLeft(aColorTopLeft);
+ const sal_uInt32 nDE(nW + nH);
+
+ aColTopRight.Merge(aColorBottomRight, 255 - sal_uInt8((nW * 255) / nDE));
+ aColBottomLeft.Merge(aColorBottomRight, 255 - sal_uInt8((nH * 255) / nDE));
+
+ return createBlendFrame(rSize, nAlpha, aColorTopLeft, aColTopRight, aColorBottomRight, aColBottomLeft);
+ }
+
+ return BitmapEx();
+}
+
+BitmapEx createBlendFrame(
+ const Size& rSize,
+ sal_uInt8 nAlpha,
+ Color aColorTopLeft,
+ Color aColorTopRight,
+ Color aColorBottomRight,
+ Color aColorBottomLeft)
+{
+ BlendFrameCache* pBlendFrameCache = ImplGetBlendFrameCache();
+
+ if(pBlendFrameCache->m_aLastSize == rSize
+ && pBlendFrameCache->m_nLastAlpha == nAlpha
+ && pBlendFrameCache->m_aLastColorTopLeft == aColorTopLeft
+ && pBlendFrameCache->m_aLastColorTopRight == aColorTopRight
+ && pBlendFrameCache->m_aLastColorBottomRight == aColorBottomRight
+ && pBlendFrameCache->m_aLastColorBottomLeft == aColorBottomLeft)
+ {
+ return pBlendFrameCache->m_aLastResult;
+ }
+
+ pBlendFrameCache->m_aLastSize = rSize;
+ pBlendFrameCache->m_nLastAlpha = nAlpha;
+ pBlendFrameCache->m_aLastColorTopLeft = aColorTopLeft;
+ pBlendFrameCache->m_aLastColorTopRight = aColorTopRight;
+ pBlendFrameCache->m_aLastColorBottomRight = aColorBottomRight;
+ pBlendFrameCache->m_aLastColorBottomLeft = aColorBottomLeft;
+ pBlendFrameCache->m_aLastResult.Clear();
+
+ const long nW(rSize.Width());
+ const long nH(rSize.Height());
+
+ if(nW > 1 && nH > 1)
+ {
+ sal_uInt8 aEraseTrans(0xff);
+ Bitmap aContent(rSize, 24);
+ AlphaMask aAlpha(rSize, &aEraseTrans);
+
+ aContent.Erase(COL_BLACK);
+
+ BitmapScopedWriteAccess pContent(aContent);
+ AlphaScopedWriteAccess pAlpha(aAlpha);
+
+ if(pContent && pAlpha)
+ {
+ long x(0);
+ 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,
+ sal_uLong nColorCount,
+ sal_uInt8 const * pTols )
+{
+ maBitmap.Replace( pSearchColors, pReplaceColors, nColorCount, pTols );
+}
+
+void BitmapEx::ReplaceTransparency(const Color& rColor)
+{
+ if( IsTransparent() )
+ {
+ maBitmap.Replace( GetMask(), rColor );
+ maMask = Bitmap();
+ maBitmapSize = maBitmap.GetSizePixel();
+ maTransparentColor = Color();
+ meTransparent = TransparentType::NONE;
+ mbAlpha = false;
+ }
+}
+
+static Bitmap DetectEdges( const Bitmap& rBmp )
+{
+ constexpr sal_uInt8 cEdgeDetectThreshold = 128;
+ const Size aSize( rBmp.GetSizePixel() );
+ Bitmap aRetBmp;
+
+ if( ( aSize.Width() > 2 ) && ( aSize.Height() > 2 ) )
+ {
+ Bitmap aWorkBmp( rBmp );
+
+ if( aWorkBmp.Convert( BmpConversion::N8BitGreys ) )
+ {
+ bool bRet = false;
+
+ ScopedVclPtr<VirtualDevice> pVirDev(VclPtr<VirtualDevice>::Create());
+ pVirDev->SetOutputSizePixel(aSize);
+ Bitmap::ScopedReadAccess pReadAcc(aWorkBmp);
+
+ if( pReadAcc )
+ {
+ const long nWidth = aSize.Width();
+ const long nWidth2 = nWidth - 2;
+ const long nHeight = aSize.Height();
+ const long nHeight2 = nHeight - 2;
+ const long lThres2 = static_cast<long>(cEdgeDetectThreshold) * cEdgeDetectThreshold;
+ long nSum1;
+ long nSum2;
+ 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( 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( long nX = 0, nXDst = 1, nXTmp; nX < nWidth2; nX++, nXDst++ )
+ {
+ nXTmp = nX;
+
+ nSum2 = pReadAcc->GetIndexFromData( pScanlineRead, nXTmp++ );
+ nSum1 = -nSum2;
+ nSum2 += static_cast<long>(pReadAcc->GetIndexFromData( pScanlineRead, nXTmp++ )) << 1;
+ lGray = pReadAcc->GetIndexFromData( pScanlineRead, nXTmp );
+ nSum1 += lGray;
+ nSum2 += lGray;
+
+ nSum1 += static_cast<long>(pReadAcc->GetIndexFromData( pScanlineRead1, nXTmp )) << 1;
+ nXTmp -= 2;
+ nSum1 -= static_cast<long>(pReadAcc->GetIndexFromData( pScanlineRead1, nXTmp )) << 1;
+
+ lGray = -static_cast<long>(pReadAcc->GetIndexFromData( pScanlineRead2, nXTmp++ ));
+ nSum1 += lGray;
+ nSum2 += lGray;
+ nSum2 -= static_cast<long>(pReadAcc->GetIndexFromData( pScanlineRead2, nXTmp++ )) << 1;
+ lGray = static_cast<long>(pReadAcc->GetIndexFromData( pScanlineRead2, nXTmp ));
+ nSum1 += lGray;
+ nSum2 -= lGray;
+
+ if( ( nSum1 * nSum1 + nSum2 * nSum2 ) < lThres2 )
+ pVirDev->DrawPixel( Point(nXDst, nY), COL_WHITE );
+ else
+ pVirDev->DrawPixel( Point(nXDst, nY), COL_BLACK );
+ }
+ }
+
+ bRet = true;
+ }
+
+ pReadAcc.reset();
+
+ if( bRet )
+ aRetBmp = pVirDev->GetBitmap(Point(0,0), aSize);
+ }
+ }
+
+ if( !aRetBmp )
+ aRetBmp = rBmp;
+ else
+ {
+ aRetBmp.SetPrefMapMode( rBmp.GetPrefMapMode() );
+ aRetBmp.SetPrefSize( rBmp.GetPrefSize() );
+ }
+
+ return aRetBmp;
+}
+
+/** Get contours in image */
+tools::Polygon BitmapEx::GetContour( bool bContourEdgeDetect,
+ const tools::Rectangle* pWorkRectPixel )
+{
+ Bitmap aWorkBmp;
+ tools::Polygon aRetPoly;
+ tools::Rectangle aWorkRect( Point(), maBitmap.GetSizePixel() );
+
+ if( pWorkRectPixel )
+ aWorkRect.Intersection( *pWorkRectPixel );
+
+ aWorkRect.Justify();
+
+ if( ( aWorkRect.GetWidth() > 4 ) && ( aWorkRect.GetHeight() > 4 ) )
+ {
+ // if the flag is set, we need to detect edges
+ if( bContourEdgeDetect )
+ aWorkBmp = DetectEdges( maBitmap );
+ else
+ aWorkBmp = maBitmap;
+
+ BitmapReadAccess* pAcc = aWorkBmp.AcquireReadAccess();
+
+ const long nWidth = pAcc ? pAcc->Width() : 0;
+ const 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 long nStartX1 = aWorkRect.Left() + 1;
+ const long nEndX1 = aWorkRect.Right();
+ const long nStartX2 = nEndX1 - 1;
+ const long nStartY1 = aWorkRect.Top() + 1;
+ const long nEndY1 = aWorkRect.Bottom();
+ std::unique_ptr<Point[]> pPoints1;
+ std::unique_ptr<Point[]> pPoints2;
+ long nX, nY;
+ sal_uInt16 nPolyPos = 0;
+ const BitmapColor aBlack = pAcc->GetBestMatchingColor( COL_BLACK );
+
+ pPoints1.reset(new Point[ nHeight ]);
+ pPoints2.reset(new Point[ nHeight ]);
+
+ for ( nY = nStartY1; nY < nEndY1; nY++ )
+ {
+ nX = nStartX1;
+ Scanline pScanline = pAcc->GetScanline( nY );
+
+ // scan row from left to right
+ while( nX < nEndX1 )
+ {
+ if( aBlack == pAcc->GetPixelFromData( pScanline, nX ) )
+ {
+ pPoints1[ nPolyPos ] = Point( nX, nY );
+ nX = nStartX2;
+
+ // this loop always breaks eventually as there is at least one pixel
+ while( true )
+ {
+ if( aBlack == pAcc->GetPixelFromData( pScanline, nX ) )
+ {
+ pPoints2[ nPolyPos ] = Point( nX, nY );
+ break;
+ }
+
+ nX--;
+ }
+
+ nPolyPos++;
+ break;
+ }
+
+ nX++;
+ }
+ }
+
+ const sal_uInt16 nNewSize1 = nPolyPos << 1;
+
+ aRetPoly = tools::Polygon( nPolyPos, pPoints1.get() );
+ aRetPoly.SetSize( nNewSize1 + 1 );
+ aRetPoly[ nNewSize1 ] = aRetPoly[ 0 ];
+
+ for( sal_uInt16 j = nPolyPos; nPolyPos < nNewSize1; )
+ aRetPoly[ nPolyPos++ ] = pPoints2[ --j ];
+
+ if( ( fFactorX != 0. ) && ( fFactorY != 0. ) )
+ aRetPoly.Scale( fFactorX, fFactorY );
+ }
+
+ Bitmap::ReleaseAccess(pAcc);
+ }
+
+ return aRetPoly;
+}
+
+void BitmapEx::setAlphaFrom( sal_uInt8 cIndexFrom, sal_Int8 nAlphaTo )
+{
+ AlphaMask aAlphaMask(GetAlpha());
+ BitmapScopedWriteAccess pWriteAccess(aAlphaMask);
+ Bitmap::ScopedReadAccess pReadAccess(maBitmap);
+ assert( pReadAccess.get() && pWriteAccess.get() );
+ if ( pReadAccess.get() && pWriteAccess.get() )
+ {
+ for ( long nY = 0; nY < pReadAccess->Height(); nY++ )
+ {
+ Scanline pScanline = pWriteAccess->GetScanline( nY );
+ Scanline pScanlineRead = pReadAccess->GetScanline( nY );
+ for ( long nX = 0; nX < pReadAccess->Width(); nX++ )
+ {
+ const sal_uInt8 cIndex = pReadAccess->GetPixelFromData( pScanlineRead, nX ).GetIndex();
+ if ( cIndex == cIndexFrom )
+ pWriteAccess->SetPixelOnData( pScanline, nX, BitmapColor(nAlphaTo) );
+ }
+ }
+ }
+ *this = BitmapEx( GetBitmap(), aAlphaMask );
+}
+
+void BitmapEx::AdjustTransparency(sal_uInt8 cTrans)
+{
+ AlphaMask aAlpha;
+
+ if (!IsTransparent())
+ {
+ aAlpha = AlphaMask(GetSizePixel(), &cTrans);
+ }
+ else if( !IsAlpha() )
+ {
+ aAlpha = GetMask();
+ aAlpha.Replace( 0, cTrans );
+ }
+ else
+ {
+ aAlpha = GetAlpha();
+ BitmapScopedWriteAccess pA(aAlpha);
+ assert(pA);
+
+ if( !pA )
+ return;
+
+ sal_uLong nTrans = cTrans, nNewTrans;
+ const long nWidth = pA->Width(), nHeight = pA->Height();
+
+ if( pA->GetScanlineFormat() == ScanlineFormat::N8BitPal )
+ {
+ for( long nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pAScan = pA->GetScanline( nY );
+
+ for( long nX = 0; nX < nWidth; nX++ )
+ {
+ nNewTrans = nTrans + *pAScan;
+ *pAScan++ = static_cast<sal_uInt8>( ( nNewTrans & 0xffffff00 ) ? 255 : nNewTrans );
+ }
+ }
+ }
+ else
+ {
+ BitmapColor aAlphaValue( 0 );
+
+ for( long nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pScanline = pA->GetScanline( nY );
+ for( long nX = 0; nX < nWidth; nX++ )
+ {
+ nNewTrans = nTrans + pA->GetIndexFromData( pScanline, nX );
+ aAlphaValue.SetIndex( static_cast<sal_uInt8>( ( nNewTrans & 0xffffff00 ) ? 255 : nNewTrans ) );
+ pA->SetPixelOnData( pScanline, nX, aAlphaValue );
+ }
+ }
+ }
+ }
+ *this = BitmapEx( GetBitmap(), aAlpha );
+}
+
+void BitmapEx::CombineMaskOr(Color maskColor, sal_uInt8 nTol)
+{
+ Bitmap aNewMask = maBitmap.CreateMask( maskColor, nTol );
+ if ( IsTransparent() )
+ aNewMask.CombineSimple( maMask, BmpCombine::Or );
+ maMask = aNewMask;
+ meTransparent = TransparentType::Bitmap;
+}
+
+/**
+ * Retrieves the color model data we need for the XImageConsumer stuff.
+ */
+void BitmapEx::GetColorModel(css::uno::Sequence< sal_Int32 >& rRGBPalette,
+ sal_uInt32& rnRedMask, sal_uInt32& rnGreenMask, sal_uInt32& rnBlueMask, sal_uInt32& rnAlphaMask, sal_uInt32& rnTransparencyIndex,
+ sal_uInt32& rnWidth, sal_uInt32& rnHeight, sal_uInt8& rnBitCount)
+{
+ Bitmap::ScopedReadAccess pReadAccess( maBitmap );
+ assert( pReadAccess );
+
+ if( pReadAccess->HasPalette() )
+ {
+ sal_uInt16 nPalCount = pReadAccess->GetPaletteEntryCount();
+
+ if( nPalCount )
+ {
+ rRGBPalette = css::uno::Sequence< sal_Int32 >( nPalCount + 1 );
+
+ sal_Int32* pTmp = rRGBPalette.getArray();
+
+ for( sal_uInt32 i = 0; i < nPalCount; i++, pTmp++ )
+ {
+ const BitmapColor& rCol = pReadAccess->GetPaletteColor( static_cast<sal_uInt16>(i) );
+
+ *pTmp = static_cast<sal_Int32>(rCol.GetRed()) << sal_Int32(24);
+ *pTmp |= static_cast<sal_Int32>(rCol.GetGreen()) << sal_Int32(16);
+ *pTmp |= static_cast<sal_Int32>(rCol.GetBlue()) << sal_Int32(8);
+ *pTmp |= sal_Int32(0x000000ffL);
+ }
+
+ if( IsTransparent() )
+ {
+ // append transparent entry
+ *pTmp = sal_Int32(0xffffff00L);
+ rnTransparencyIndex = nPalCount;
+ nPalCount++;
+ }
+ else
+ rnTransparencyIndex = 0;
+ }
+ }
+ else
+ {
+ rnRedMask = 0xff000000UL;
+ rnGreenMask = 0x00ff0000UL;
+ rnBlueMask = 0x0000ff00UL;
+ rnAlphaMask = 0x000000ffUL;
+ rnTransparencyIndex = 0;
+ }
+
+ rnWidth = pReadAccess->Width();
+ rnHeight = pReadAccess->Height();
+ rnBitCount = pReadAccess->GetBitCount();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/bmpacc.cxx b/vcl/source/gdi/bmpacc.cxx
new file mode 100644
index 000000000..6910c6e3e
--- /dev/null
+++ b/vcl/source/gdi/bmpacc.cxx
@@ -0,0 +1,464 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/bitmap.hxx>
+#include <vcl/bitmapaccess.hxx>
+
+#include <bitmapwriteaccess.hxx>
+#include <salbmp.hxx>
+#include <svdata.hxx>
+#include <salinst.hxx>
+
+#include <string.h>
+#include <sal/log.hxx>
+#include <tools/debug.hxx>
+
+BitmapInfoAccess::BitmapInfoAccess( Bitmap& rBitmap, BitmapAccessMode nMode ) :
+ mpBuffer ( nullptr ),
+ mnAccessMode ( nMode )
+{
+ std::shared_ptr<SalBitmap> xImpBmp = rBitmap.ImplGetSalBitmap();
+
+ assert( xImpBmp && "Forbidden Access to empty bitmap!" );
+
+ if( !xImpBmp )
+ return;
+
+ if (mnAccessMode == BitmapAccessMode::Write)
+ {
+ xImpBmp->DropScaledCache();
+
+ if (xImpBmp.use_count() > 2)
+ {
+ xImpBmp.reset();
+ rBitmap.ImplMakeUnique();
+ xImpBmp = rBitmap.ImplGetSalBitmap();
+ }
+ }
+
+ mpBuffer = xImpBmp->AcquireBuffer( mnAccessMode );
+
+ if( !mpBuffer )
+ {
+ std::shared_ptr<SalBitmap> xNewImpBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
+ if (xNewImpBmp->Create(*xImpBmp, rBitmap.GetBitCount()))
+ {
+ xImpBmp = xNewImpBmp;
+ rBitmap.ImplSetSalBitmap( xImpBmp );
+ mpBuffer = xImpBmp->AcquireBuffer( mnAccessMode );
+ }
+ }
+
+ maBitmap = rBitmap;
+}
+
+BitmapInfoAccess::~BitmapInfoAccess()
+{
+ std::shared_ptr<SalBitmap> xImpBmp = maBitmap.ImplGetSalBitmap();
+
+ if (mpBuffer && xImpBmp)
+ {
+ xImpBmp->ReleaseBuffer( mpBuffer, mnAccessMode );
+ }
+}
+
+sal_uInt16 BitmapInfoAccess::GetBestPaletteIndex( const BitmapColor& rBitmapColor ) const
+{
+ return( HasPalette() ? mpBuffer->maPalette.GetBestIndex( rBitmapColor ) : 0 );
+}
+
+BitmapReadAccess::BitmapReadAccess( Bitmap& rBitmap, BitmapAccessMode nMode ) :
+ BitmapInfoAccess( rBitmap, nMode ),
+ mFncGetPixel ( nullptr ),
+ mFncSetPixel ( nullptr )
+{
+ if (!mpBuffer)
+ return;
+
+ const std::shared_ptr<SalBitmap>& xImpBmp = rBitmap.ImplGetSalBitmap();
+ if (!xImpBmp)
+ return;
+
+ maColorMask = mpBuffer->maColorMask;
+
+ bool bOk = ImplSetAccessPointers(RemoveScanline(mpBuffer->mnFormat));
+
+ if (!bOk)
+ {
+ xImpBmp->ReleaseBuffer( mpBuffer, mnAccessMode );
+ mpBuffer = nullptr;
+ }
+}
+
+BitmapReadAccess::~BitmapReadAccess()
+{
+}
+
+namespace
+{
+ bool Bitmap32IsPreMultipled()
+ {
+ auto pBackendCapabilities = ImplGetSVData()->mpDefInst->GetBackendCapabilities();
+ return pBackendCapabilities->mbSupportsBitmap32;
+ }
+}
+
+bool BitmapReadAccess::ImplSetAccessPointers( ScanlineFormat nFormat )
+{
+ bool bRet = true;
+
+ switch( nFormat )
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ {
+ mFncGetPixel = GetPixelForN1BitMsbPal;
+ mFncSetPixel = SetPixelForN1BitMsbPal;
+ }
+ break;
+ case ScanlineFormat::N1BitLsbPal:
+ {
+ mFncGetPixel = GetPixelForN1BitLsbPal;
+ mFncSetPixel = SetPixelForN1BitLsbPal;
+ }
+ break;
+ case ScanlineFormat::N4BitMsnPal:
+ {
+ mFncGetPixel = GetPixelForN4BitMsnPal;
+ mFncSetPixel = SetPixelForN4BitMsnPal;
+ }
+ break;
+ case ScanlineFormat::N4BitLsnPal:
+ {
+ mFncGetPixel = GetPixelForN4BitLsnPal;
+ mFncSetPixel = SetPixelForN4BitLsnPal;
+ }
+ break;
+ case ScanlineFormat::N8BitPal:
+ {
+ mFncGetPixel = GetPixelForN8BitPal;
+ mFncSetPixel = SetPixelForN8BitPal;
+ }
+ break;
+ case ScanlineFormat::N8BitTcMask:
+ {
+ mFncGetPixel = GetPixelForN8BitTcMask;
+ mFncSetPixel = SetPixelForN8BitTcMask;
+ }
+ break;
+ case ScanlineFormat::N24BitTcBgr:
+ {
+ mFncGetPixel = GetPixelForN24BitTcBgr;
+ mFncSetPixel = SetPixelForN24BitTcBgr;
+ }
+ break;
+ case ScanlineFormat::N24BitTcRgb:
+ {
+ mFncGetPixel = GetPixelForN24BitTcRgb;
+ mFncSetPixel = SetPixelForN24BitTcRgb;
+ }
+ break;
+ case ScanlineFormat::N32BitTcAbgr:
+ {
+ if (Bitmap32IsPreMultipled())
+ {
+ mFncGetPixel = GetPixelForN32BitTcAbgr;
+ mFncSetPixel = SetPixelForN32BitTcAbgr;
+ }
+ else
+ {
+ mFncGetPixel = GetPixelForN32BitTcXbgr;
+ mFncSetPixel = SetPixelForN32BitTcXbgr;
+ }
+ }
+ break;
+ case ScanlineFormat::N32BitTcArgb:
+ {
+ if (Bitmap32IsPreMultipled())
+ {
+ mFncGetPixel = GetPixelForN32BitTcArgb;
+ mFncSetPixel = SetPixelForN32BitTcArgb;
+ }
+ else
+ {
+ mFncGetPixel = GetPixelForN32BitTcXrgb;
+ mFncSetPixel = SetPixelForN32BitTcXrgb;
+ }
+ }
+ break;
+ case ScanlineFormat::N32BitTcBgra:
+ {
+ if (Bitmap32IsPreMultipled())
+ {
+ mFncGetPixel = GetPixelForN32BitTcBgra;
+ mFncSetPixel = SetPixelForN32BitTcBgra;
+ }
+ else
+ {
+ mFncGetPixel = GetPixelForN32BitTcBgrx;
+ mFncSetPixel = SetPixelForN32BitTcBgrx;
+ }
+ }
+ break;
+ case ScanlineFormat::N32BitTcRgba:
+ {
+ if (Bitmap32IsPreMultipled())
+ {
+ mFncGetPixel = GetPixelForN32BitTcRgba;
+ mFncSetPixel = SetPixelForN32BitTcRgba;
+ }
+ else
+ {
+ mFncGetPixel = GetPixelForN32BitTcRgbx;
+ mFncSetPixel = SetPixelForN32BitTcRgbx;
+ }
+ }
+ break;
+ case ScanlineFormat::N32BitTcMask:
+ {
+ mFncGetPixel = GetPixelForN32BitTcMask;
+ mFncSetPixel = SetPixelForN32BitTcMask;
+ }
+ break;
+
+ default:
+ bRet = false;
+ break;
+ }
+
+ return bRet;
+}
+
+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;
+}
+
+BitmapWriteAccess::BitmapWriteAccess(Bitmap& rBitmap)
+ : BitmapReadAccess(rBitmap, BitmapAccessMode::Write)
+{
+}
+
+BitmapWriteAccess::~BitmapWriteAccess()
+{
+}
+
+void BitmapWriteAccess::CopyScanline( 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
+ {
+ // TODO: use fastbmp infrastructure
+ Scanline pScanline = GetScanline( nY );
+ Scanline pScanlineRead = rReadAcc.GetScanline(nY);
+ for( long nX = 0, nWidth = std::min( mpBuffer->mnWidth, rReadAcc.Width() ); nX < nWidth; nX++ )
+ SetPixelOnData( pScanline, nX, rReadAcc.GetPixelFromData( pScanlineRead, nX ) );
+ }
+}
+
+void BitmapWriteAccess::CopyScanline( 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_uLong nCount = std::min( GetScanlineSize(), nSrcScanlineSize );
+
+ if( nCount )
+ {
+ if( GetScanlineFormat() == RemoveScanline( nSrcScanlineFormat ) )
+ memcpy(GetScanline(nY), aSrcScanline, nCount);
+ else
+ {
+ DBG_ASSERT( nFormat != ScanlineFormat::N8BitTcMask &&
+ nFormat != ScanlineFormat::N32BitTcMask,
+ "No support for pixel formats with color masks yet!" );
+
+ // TODO: use fastbmp infrastructure
+ FncGetPixel pFncGetPixel;
+
+ switch( nFormat )
+ {
+ case ScanlineFormat::N1BitMsbPal: pFncGetPixel = GetPixelForN1BitMsbPal; break;
+ case ScanlineFormat::N1BitLsbPal: pFncGetPixel = GetPixelForN1BitLsbPal; break;
+ case ScanlineFormat::N4BitMsnPal: pFncGetPixel = GetPixelForN4BitMsnPal; break;
+ case ScanlineFormat::N4BitLsnPal: pFncGetPixel = GetPixelForN4BitLsnPal; break;
+ case ScanlineFormat::N8BitPal: pFncGetPixel = GetPixelForN8BitPal; break;
+ case ScanlineFormat::N8BitTcMask: pFncGetPixel = GetPixelForN8BitTcMask; 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 (long nX = 0, nWidth = mpBuffer->mnWidth; nX < nWidth; ++nX)
+ SetPixelOnData(pScanline, nX, pFncGetPixel(aSrcScanline, nX, aDummyMask));
+ }
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/bmpacc2.cxx b/vcl/source/gdi/bmpacc2.cxx
new file mode 100644
index 000000000..9210d5222
--- /dev/null
+++ b/vcl/source/gdi/bmpacc2.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 <vcl/bitmapaccess.hxx>
+#include <vcl/BitmapTools.hxx>
+
+BitmapColor BitmapReadAccess::GetPixelForN1BitMsbPal(ConstScanline pScanline, long nX, const ColorMask&)
+{
+ return BitmapColor( pScanline[ nX >> 3 ] & ( 1 << ( 7 - ( nX & 7 ) ) ) ? 1 : 0 );
+}
+
+void BitmapReadAccess::SetPixelForN1BitMsbPal(const Scanline pScanline, long nX, const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ sal_uInt8& rByte = pScanline[ nX >> 3 ];
+
+ if ( rBitmapColor.GetIndex() & 1 )
+ rByte |= 1 << ( 7 - ( nX & 7 ) );
+ else
+ rByte &= ~( 1 << ( 7 - ( nX & 7 ) ) );
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN1BitLsbPal(ConstScanline pScanline, long nX, const ColorMask&)
+{
+ return BitmapColor( pScanline[ nX >> 3 ] & ( 1 << ( nX & 7 ) ) ? 1 : 0 );
+}
+
+void BitmapReadAccess::SetPixelForN1BitLsbPal(const Scanline pScanline, long nX, const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ sal_uInt8& rByte = pScanline[ nX >> 3 ];
+
+ if ( rBitmapColor.GetIndex() & 1 )
+ rByte |= 1 << ( nX & 7 );
+ else
+ rByte &= ~( 1 << ( nX & 7 ) );
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN4BitMsnPal(ConstScanline pScanline, long nX, const ColorMask&)
+{
+ return BitmapColor( ( pScanline[ nX >> 1 ] >> ( nX & 1 ? 0 : 4 ) ) & 0x0f );
+}
+
+void BitmapReadAccess::SetPixelForN4BitMsnPal(const Scanline pScanline, long nX, const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ sal_uInt8& rByte = pScanline[ nX >> 1 ];
+
+ if ( nX & 1 )
+ {
+ rByte &= 0xf0;
+ rByte |= ( rBitmapColor.GetIndex() & 0x0f );
+ }
+ else
+ {
+ rByte &= 0x0f;
+ rByte |= ( rBitmapColor.GetIndex() << 4 );
+ }
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN4BitLsnPal(ConstScanline pScanline, long nX, const ColorMask&)
+{
+ return BitmapColor( ( pScanline[ nX >> 1 ] >> ( nX & 1 ? 4 : 0 ) ) & 0x0f );
+}
+
+void BitmapReadAccess::SetPixelForN4BitLsnPal(const Scanline pScanline, long nX, const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ sal_uInt8& rByte = pScanline[ nX >> 1 ];
+
+ if ( nX & 1 )
+ {
+ rByte &= 0x0f;
+ rByte |= ( rBitmapColor.GetIndex() << 4 );
+ }
+ else
+ {
+ rByte &= 0xf0;
+ rByte |= ( rBitmapColor.GetIndex() & 0x0f );
+ }
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN8BitPal(ConstScanline pScanline, long nX, const ColorMask&)
+{
+ return BitmapColor( pScanline[ nX ] );
+}
+
+void BitmapReadAccess::SetPixelForN8BitPal(Scanline pScanline, long nX, const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline[ nX ] = rBitmapColor.GetIndex();
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN8BitTcMask(ConstScanline pScanline, long nX, const ColorMask& rMask)
+{
+ BitmapColor aColor;
+ rMask.GetColorFor8Bit( aColor, pScanline + nX );
+ return aColor;
+}
+
+void BitmapReadAccess::SetPixelForN8BitTcMask(Scanline pScanline, long nX, const BitmapColor& rBitmapColor, const ColorMask& rMask)
+{
+ rMask.SetColorFor8Bit( rBitmapColor, pScanline + nX );
+}
+
+
+BitmapColor BitmapReadAccess::GetPixelForN24BitTcBgr(ConstScanline pScanline, 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, 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, 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, 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, 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(
+ vcl::bitmap::unpremultiply(r, a),
+ vcl::bitmap::unpremultiply(g, a),
+ vcl::bitmap::unpremultiply(b, a),
+ 0xFF - a);
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN32BitTcXbgr(ConstScanline pScanline, 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, long nX, const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline = pScanline + nX * 4;
+
+ sal_uInt8 alpha = 0xFF - 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, 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, 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(
+ vcl::bitmap::unpremultiply(r, a),
+ vcl::bitmap::unpremultiply(g, a),
+ vcl::bitmap::unpremultiply(b, a),
+ 0xFF - a);
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN32BitTcXrgb(ConstScanline pScanline, 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, long nX, const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline = pScanline + nX * 4;
+
+ sal_uInt8 alpha = 0xFF - 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, 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, 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(
+ vcl::bitmap::unpremultiply(r, a),
+ vcl::bitmap::unpremultiply(g, a),
+ vcl::bitmap::unpremultiply(b, a),
+ 0xFF - a);
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN32BitTcBgrx(ConstScanline pScanline, 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, long nX, const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline = pScanline + nX * 4;
+
+ sal_uInt8 alpha = 0xFF - 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, 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, 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(
+ vcl::bitmap::unpremultiply(r, a),
+ vcl::bitmap::unpremultiply(g, a),
+ vcl::bitmap::unpremultiply(b, a),
+ 0xFF - a);
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN32BitTcRgbx(ConstScanline pScanline, 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, long nX, const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline = pScanline + nX * 4;
+
+ sal_uInt8 alpha = 0xFF - 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, 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, long nX, const ColorMask& rMask)
+{
+ BitmapColor aColor;
+ rMask.GetColorFor32Bit( aColor, pScanline + ( nX << 2 ) );
+ return aColor;
+}
+
+void BitmapReadAccess::SetPixelForN32BitTcMask(Scanline pScanline, 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/gdi/bmpacc3.cxx b/vcl/source/gdi/bmpacc3.cxx
new file mode 100644
index 000000000..f2fc66427
--- /dev/null
+++ b/vcl/source/gdi/bmpacc3.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/bitmap.hxx>
+
+#include <bmpfast.hxx>
+#include <bitmapwriteaccess.hxx>
+
+void BitmapWriteAccess::SetLineColor( const Color& rColor )
+{
+ if (rColor.GetTransparency() == 255)
+ {
+ 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.GetTransparency() == 255)
+ {
+ 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 long nEndX = aRect.Right();
+ for (long nX = 0; nX <= nEndX; ++nX)
+ SetPixelOnData(pFirstScanline, nX, rColor);
+ const auto nScanlineSize = GetScanlineSize();
+ const long nEndY = aRect.Bottom();
+ for (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)
+ {
+ const BitmapColor& rLineColor = *mpLineColor;
+ long nX, nY;
+
+ if (rStart.X() == rEnd.X())
+ {
+ // Vertical Line
+ const 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 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 long nDX = labs( rEnd.X() - rStart.X() );
+ const long nDY = labs( rEnd.Y() - rStart.Y() );
+ long nX1;
+ long nY1;
+ long nX2;
+ 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 long nDYX = (nDY - nDX) << 1;
+ const long nDY2 = nDY << 1;
+ 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 long nDYX = (nDX - nDY) << 1;
+ const long nDY2 = nDX << 1;
+ 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)
+ {
+ const BitmapColor& rFillColor = *mpFillColor;
+ tools::Rectangle aRect(Point(), maBitmap.GetSizePixel());
+
+ aRect.Intersection(rRect);
+
+ if (!aRect.IsEmpty())
+ {
+ const long nStartX = rRect.Left();
+ const long nStartY = rRect.Top();
+ const long nEndX = rRect.Right();
+ const long nEndY = rRect.Bottom();
+
+ for (long nY = nStartY; nY <= nEndY; nY++)
+ {
+ Scanline pScanline = GetScanline( nY );
+ for (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/gdi/bmpfast.cxx b/vcl/source/gdi/bmpfast.cxx
new file mode 100644
index 000000000..cbf72d809
--- /dev/null
+++ b/vcl/source/gdi/bmpfast.cxx
@@ -0,0 +1,743 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <bmpfast.hxx>
+#include <vcl/bitmapaccess.hxx>
+#include <vcl/salgtype.hxx>
+#include <bitmapwriteaccess.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 0; }
+ 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 0; }
+ 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;
+ }
+};
+
+template <>
+class TrueColorPixelPtr<ScanlineFormat::N8BitTcMask> : public BasePixelPtr
+{
+public:
+ void operator++() { mpPixel += 1; }
+ PIXBYTE GetAlpha() const { return mpPixel[0]; }
+};
+
+// TODO: for some reason many Alpha maps are ScanlineFormat::N8BitPal
+// they should be ScanlineFormat::N8BitTcMask
+template <>
+class TrueColorPixelPtr<ScanlineFormat::N8BitPal>
+: public TrueColorPixelPtr<ScanlineFormat::N8BitTcMask>
+{};
+
+}
+
+// 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 )
+ {
+ ImplBlendPixels(aDst, aSrc, aMsk.GetAlpha());
+ ++aDst;
+ ++aSrc;
+ ++aMsk;
+ }
+}
+
+static bool ImplCopyImage( BitmapBuffer& rDstBuffer, const BitmapBuffer& rSrcBuffer )
+{
+ const int nSrcLinestep = rSrcBuffer.mnScanlineSize;
+ int nDstLinestep = rDstBuffer.mnScanlineSize;
+
+ const PIXBYTE* pRawSrc = rSrcBuffer.mpBits;
+ PIXBYTE* pRawDst = rDstBuffer.mpBits;
+
+ // source and destination don't match upside down
+ if( ScanlineFormat::TopDown & (rSrcBuffer.mnFormat ^ rDstBuffer.mnFormat) )
+ {
+ pRawDst += (rSrcBuffer.mnHeight - 1) * nDstLinestep;
+ nDstLinestep = -rDstBuffer.mnScanlineSize;
+ }
+ else if( nSrcLinestep == nDstLinestep )
+ {
+ memcpy( pRawDst, pRawSrc, rSrcBuffer.mnHeight * nDstLinestep );
+ return true;
+ }
+
+ int nByteWidth = nSrcLinestep;
+ if( nByteWidth > rDstBuffer.mnScanlineSize )
+ nByteWidth = rDstBuffer.mnScanlineSize;
+
+ for( int y = rSrcBuffer.mnHeight; --y >= 0; )
+ {
+ memcpy( pRawDst, pRawSrc, nByteWidth );
+ pRawSrc += nSrcLinestep;
+ pRawDst += nDstLinestep;
+ }
+
+ return true;
+}
+
+template <ScanlineFormat DSTFMT,ScanlineFormat SRCFMT>
+static bool ImplConvertToBitmap( TrueColorPixelPtr<SRCFMT>& rSrcLine,
+ BitmapBuffer& rDstBuffer, const BitmapBuffer& rSrcBuffer )
+{
+ // help the compiler to avoid instantiations of unneeded conversions
+ SAL_WARN_IF( SRCFMT == DSTFMT, "vcl.gdi", "ImplConvertToBitmap into same format");
+ if( SRCFMT == DSTFMT )
+ return false;
+
+ const int nSrcLinestep = rSrcBuffer.mnScanlineSize;
+ int nDstLinestep = rDstBuffer.mnScanlineSize;
+
+ TrueColorPixelPtr<DSTFMT> aDstLine; aDstLine.SetRawPtr( rDstBuffer.mpBits );
+
+ // source and destination don't match upside down
+ if( ScanlineFormat::TopDown & (rSrcBuffer.mnFormat ^ rDstBuffer.mnFormat) )
+ {
+ aDstLine.AddByteOffset( (rSrcBuffer.mnHeight - 1) * nDstLinestep );
+ nDstLinestep = -nDstLinestep;
+ }
+
+ for( int y = rSrcBuffer.mnHeight; --y >= 0; )
+ {
+ ImplConvertLine( aDstLine, rSrcLine, rSrcBuffer.mnWidth );
+ rSrcLine.AddByteOffset( nSrcLinestep );
+ aDstLine.AddByteOffset( nDstLinestep );
+ }
+
+ return true;
+}
+
+template <ScanlineFormat SRCFMT>
+static bool ImplConvertFromBitmap( BitmapBuffer& rDst, const BitmapBuffer& rSrc )
+{
+ TrueColorPixelPtr<SRCFMT> aSrcType; aSrcType.SetRawPtr( rSrc.mpBits );
+
+ // select the matching instantiation for the destination's bitmap format
+ switch (RemoveScanline(rDst.mnFormat))
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ case ScanlineFormat::N1BitLsbPal:
+ case ScanlineFormat::N4BitMsnPal:
+ case ScanlineFormat::N4BitLsnPal:
+ case ScanlineFormat::N8BitPal:
+ break;
+
+ case ScanlineFormat::N8BitTcMask:
+// return ImplConvertToBitmap<ScanlineFormat::N8BitTcMask>( aSrcType, rDst, rSrc );
+ case ScanlineFormat::N32BitTcMask:
+// return ImplConvertToBitmap<ScanlineFormat::N32BitTcMask>( aSrcType, rDst, rSrc );
+ break;
+
+ case ScanlineFormat::N24BitTcBgr:
+ return ImplConvertToBitmap<ScanlineFormat::N24BitTcBgr>( aSrcType, rDst, rSrc );
+ case ScanlineFormat::N24BitTcRgb:
+ return ImplConvertToBitmap<ScanlineFormat::N24BitTcRgb>( aSrcType, rDst, rSrc );
+
+ case ScanlineFormat::N32BitTcAbgr:
+ return ImplConvertToBitmap<ScanlineFormat::N32BitTcAbgr>( aSrcType, rDst, rSrc );
+ case ScanlineFormat::N32BitTcArgb:
+ return ImplConvertToBitmap<ScanlineFormat::N32BitTcArgb>( aSrcType, rDst, rSrc );
+ case ScanlineFormat::N32BitTcBgra:
+ return ImplConvertToBitmap<ScanlineFormat::N32BitTcBgra>( aSrcType, rDst, rSrc );
+ case ScanlineFormat::N32BitTcRgba:
+ return ImplConvertToBitmap<ScanlineFormat::N32BitTcRgba>( aSrcType, rDst, rSrc );
+ default: break;
+ }
+
+ static int nNotAccelerated = 0;
+ SAL_WARN_IF( rSrc.mnWidth * rSrc.mnHeight >= 4000 && ++nNotAccelerated == 100,
+ "vcl.gdi",
+ "ImplConvertFromBitmap for not accelerated case (" << std::hex << static_cast<int>(rSrc.mnFormat) << "->" << static_cast<int>(rDst.mnFormat) << ")" );
+
+ return false;
+}
+
+// A universal stretching conversion is overkill in most common situations
+// => performance benefits for speeding up the non-stretching cases
+bool ImplFastBitmapConversion( BitmapBuffer& rDst, const BitmapBuffer& rSrc,
+ const SalTwoRect& rTR )
+{
+ // TODO:horizontal mirroring not implemented yet
+ if( rTR.mnDestWidth < 0 )
+ return false;
+ // vertical mirroring
+ if( rTR.mnDestHeight < 0 )
+ // TODO: rDst.mnFormat ^= ScanlineFormat::TopDown;
+ return false;
+
+ // offsetted conversion is not implemented yet
+ if( rTR.mnSrcX || rTR.mnSrcY )
+ return false;
+ if( rTR.mnDestX || rTR.mnDestY )
+ return false;
+
+ // stretched conversion is not implemented yet
+ if( rTR.mnDestWidth != rTR.mnSrcWidth )
+ return false;
+ if( rTR.mnDestHeight!= rTR.mnSrcHeight )
+ return false;
+
+ // check source image size
+ if( rSrc.mnWidth < rTR.mnSrcX + rTR.mnSrcWidth )
+ return false;
+ if( rSrc.mnHeight < rTR.mnSrcY + rTR.mnSrcHeight )
+ return false;
+
+ // check dest image size
+ if( rDst.mnWidth < rTR.mnDestX + rTR.mnDestWidth )
+ return false;
+ if( rDst.mnHeight < rTR.mnDestY + rTR.mnDestHeight )
+ return false;
+
+ const ScanlineFormat nSrcFormat = RemoveScanline(rSrc.mnFormat);
+ const ScanlineFormat nDstFormat = RemoveScanline(rDst.mnFormat);
+
+ // special handling of trivial cases
+ if( nSrcFormat == nDstFormat )
+ {
+ // accelerated palette conversions not yet implemented
+ if( rSrc.maPalette != rDst.maPalette )
+ return false;
+ return ImplCopyImage( rDst, rSrc );
+ }
+
+ // select the matching instantiation for the source's bitmap format
+ switch( nSrcFormat )
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ case ScanlineFormat::N1BitLsbPal:
+ case ScanlineFormat::N4BitMsnPal:
+ case ScanlineFormat::N4BitLsnPal:
+ case ScanlineFormat::N8BitPal:
+ break;
+
+ case ScanlineFormat::N8BitTcMask:
+// return ImplConvertFromBitmap<ScanlineFormat::N8BitTcMask>( rDst, rSrc );
+ case ScanlineFormat::N32BitTcMask:
+// return ImplConvertFromBitmap<ScanlineFormat::N32BitTcMask>( 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;
+}
+
+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::N8BitPal, "vcl.gdi", "FastBmp BlendImage: unusual MSKFMT" );
+
+ const int nSrcLinestep = rSrcBuffer.mnScanlineSize;
+ int nMskLinestep = rMskBuffer.mnScanlineSize;
+ int nDstLinestep = rDstBuffer.mnScanlineSize;
+
+ TrueColorPixelPtr<ScanlineFormat::N8BitPal> aMskLine; aMskLine.SetRawPtr( rMskBuffer.mpBits );
+ TrueColorPixelPtr<DSTFMT> aDstLine; aDstLine.SetRawPtr( rDstBuffer.mpBits );
+
+ // special case for single line masks
+ if( rMskBuffer.mnHeight == 1 )
+ nMskLinestep = 0;
+
+ // source and mask don't match: upside down
+ if( (rSrcBuffer.mnFormat ^ rMskBuffer.mnFormat) & ScanlineFormat::TopDown )
+ {
+ aMskLine.AddByteOffset( (rSrcBuffer.mnHeight - 1) * nMskLinestep );
+ nMskLinestep = -nMskLinestep;
+ }
+
+ // source and destination don't match: upside down
+ if( (rSrcBuffer.mnFormat ^ rDstBuffer.mnFormat) & ScanlineFormat::TopDown )
+ {
+ aDstLine.AddByteOffset( (rDstBuffer.mnHeight - 1) * nDstLinestep );
+ nDstLinestep = -nDstLinestep;
+ }
+
+ assert(rDstBuffer.mnHeight <= rSrcBuffer.mnHeight && "not sure about that?");
+ for (int y = rDstBuffer.mnHeight; --y >= 0;)
+ {
+ ImplBlendLines(aDstLine, rSrcLine, aMskLine, rDstBuffer.mnWidth);
+ aDstLine.AddByteOffset( nDstLinestep );
+ rSrcLine.AddByteOffset( nSrcLinestep );
+ aMskLine.AddByteOffset( nMskLinestep );
+ }
+
+ return true;
+}
+
+// some specializations to reduce the code size
+template <>
+bool ImplBlendToBitmap<ScanlineFormat::N24BitTcBgr,ScanlineFormat::N24BitTcBgr>(
+ TrueColorPixelPtr<ScanlineFormat::N24BitTcBgr>&,
+ BitmapBuffer& rDstBuffer, const BitmapBuffer& rSrcBuffer,
+ const BitmapBuffer& rMskBuffer )
+ {
+ TrueColorPixelPtr<ScanlineFormat::N24BitTcRgb> aSrcType; aSrcType.SetRawPtr( rSrcBuffer.mpBits );
+ return ImplBlendToBitmap<ScanlineFormat::N24BitTcRgb>( aSrcType, rDstBuffer, rSrcBuffer, rMskBuffer );
+ }
+
+template <>
+bool ImplBlendToBitmap<ScanlineFormat::N32BitTcAbgr,ScanlineFormat::N32BitTcAbgr>(
+ TrueColorPixelPtr<ScanlineFormat::N32BitTcAbgr>&,
+ BitmapBuffer& rDstBuffer, const BitmapBuffer& rSrcBuffer,
+ const BitmapBuffer& rMskBuffer )
+ {
+ TrueColorPixelPtr<ScanlineFormat::N32BitTcArgb> aSrcType; aSrcType.SetRawPtr( rSrcBuffer.mpBits );
+ return ImplBlendToBitmap<ScanlineFormat::N32BitTcArgb>( aSrcType, rDstBuffer, rSrcBuffer, rMskBuffer );
+ }
+
+template <>
+bool ImplBlendToBitmap<ScanlineFormat::N32BitTcBgra,ScanlineFormat::N32BitTcBgra>(
+ TrueColorPixelPtr<ScanlineFormat::N32BitTcBgra>&,
+ BitmapBuffer& rDstBuffer, const BitmapBuffer& rSrcBuffer,
+ const BitmapBuffer& rMskBuffer )
+ {
+ TrueColorPixelPtr<ScanlineFormat::N32BitTcRgba> aSrcType; aSrcType.SetRawPtr( rSrcBuffer.mpBits );
+ return ImplBlendToBitmap<ScanlineFormat::N32BitTcRgba>( aSrcType, rDstBuffer, rSrcBuffer, rMskBuffer );
+ }
+
+template <ScanlineFormat SRCFMT>
+static bool ImplBlendFromBitmap( BitmapBuffer& rDst, const BitmapBuffer& rSrc, const BitmapBuffer& rMsk )
+{
+ TrueColorPixelPtr<SRCFMT> aSrcType; aSrcType.SetRawPtr( rSrc.mpBits );
+
+ // select the matching instantiation for the destination's bitmap format
+ switch (RemoveScanline(rDst.mnFormat))
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ case ScanlineFormat::N1BitLsbPal:
+ case ScanlineFormat::N4BitMsnPal:
+ case ScanlineFormat::N4BitLsnPal:
+ case ScanlineFormat::N8BitPal:
+ break;
+
+ case ScanlineFormat::N8BitTcMask:
+// return ImplBlendToBitmap<ScanlineFormat::N8BitTcMask>( aSrcType, rDst, rSrc, rMsk );
+ case ScanlineFormat::N32BitTcMask:
+// return ImplBlendToBitmap<ScanlineFormat::N32BitTcMask>( aSrcType, rDst, rSrc, rMsk );
+ break;
+
+ case ScanlineFormat::N24BitTcBgr:
+ return ImplBlendToBitmap<ScanlineFormat::N24BitTcBgr>( aSrcType, rDst, rSrc, rMsk );
+ case ScanlineFormat::N24BitTcRgb:
+ return ImplBlendToBitmap<ScanlineFormat::N24BitTcRgb>( aSrcType, rDst, rSrc, rMsk );
+
+ case ScanlineFormat::N32BitTcAbgr:
+ return ImplBlendToBitmap<ScanlineFormat::N32BitTcAbgr>( aSrcType, rDst, rSrc, rMsk );
+ case ScanlineFormat::N32BitTcArgb:
+ return ImplBlendToBitmap<ScanlineFormat::N32BitTcArgb>( aSrcType, rDst, rSrc, rMsk );
+ case ScanlineFormat::N32BitTcBgra:
+ return ImplBlendToBitmap<ScanlineFormat::N32BitTcBgra>( aSrcType, rDst, rSrc, rMsk );
+ case ScanlineFormat::N32BitTcRgba:
+ return ImplBlendToBitmap<ScanlineFormat::N32BitTcRgba>( aSrcType, rDst, rSrc, rMsk );
+ default: break;
+ }
+
+ static int nNotAccelerated = 0;
+ SAL_WARN_IF( rSrc.mnWidth * rSrc.mnHeight >= 4000 && ++nNotAccelerated == 100,
+ "vcl.gdi",
+ "ImplBlendFromBitmap for not accelerated case (" << std::hex << static_cast<int>(rSrc.mnFormat) << "*" << static_cast<int>(rMsk.mnFormat) << "->" << static_cast<int>(rDst.mnFormat) );
+ return false;
+}
+
+bool ImplFastBitmapBlending( BitmapWriteAccess const & rDstWA,
+ const BitmapReadAccess& rSrcRA, const BitmapReadAccess& rMskRA,
+ const SalTwoRect& rTR )
+{
+ // accelerated blending of paletted bitmaps not implemented yet
+ if( rSrcRA.HasPalette() )
+ return false;
+ if( rDstWA.HasPalette() )
+ return false;
+ // TODO: either get rid of mask's use of 8BIT_PAL or check the palette
+
+ // horizontal mirroring not implemented yet
+ if( rTR.mnDestWidth < 0 )
+ return false;
+ // vertical mirroring
+ if( rTR.mnDestHeight < 0 )
+ // TODO: rDst.mnFormat ^= ScanlineFormat::TopDown;
+ return false;
+
+ // offsetted blending is not implemented yet
+ if( rTR.mnSrcX || rTR.mnSrcY )
+ return false;
+ if( rTR.mnDestX || rTR.mnDestY )
+ return false;
+
+ // stretched blending is not implemented yet
+ if( rTR.mnDestWidth != rTR.mnSrcWidth )
+ return false;
+ if( rTR.mnDestHeight!= rTR.mnSrcHeight )
+ return false;
+
+ // check source image size
+ if( rSrcRA.Width() < rTR.mnSrcX + rTR.mnSrcWidth )
+ return false;
+ if( rSrcRA.Height() < rTR.mnSrcY + rTR.mnSrcHeight )
+ return false;
+
+ // check mask image size
+ if( rMskRA.Width() < rTR.mnSrcX + rTR.mnSrcWidth )
+ return false;
+ if( rMskRA.Height() < rTR.mnSrcY + rTR.mnSrcHeight )
+ if( rMskRA.Height() != 1 )
+ return false;
+
+ // check dest image size
+ if( rDstWA.Width() < rTR.mnDestX + rTR.mnDestWidth )
+ return false;
+ if( rDstWA.Height() < rTR.mnDestY + rTR.mnDestHeight )
+ return false;
+
+ BitmapBuffer& rDst = *rDstWA.ImplGetBitmapBuffer();
+ const BitmapBuffer& rSrc = *rSrcRA.ImplGetBitmapBuffer();
+ const BitmapBuffer& rMsk = *rMskRA.ImplGetBitmapBuffer();
+
+ const ScanlineFormat nSrcFormat = RemoveScanline(rSrc.mnFormat);
+
+ // select the matching instantiation for the source's bitmap format
+ switch( nSrcFormat )
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ case ScanlineFormat::N1BitLsbPal:
+ case ScanlineFormat::N4BitMsnPal:
+ case ScanlineFormat::N4BitLsnPal:
+ case ScanlineFormat::N8BitPal:
+ break;
+
+ case ScanlineFormat::N8BitTcMask:
+// return ImplBlendFromBitmap<ScanlineFormat::N8BitTcMask>( rDst, rSrc );
+ case ScanlineFormat::N32BitTcMask:
+// return ImplBlendFromBitmap<ScanlineFormat::N32BitTcMask>( rDst, rSrc );
+ break;
+
+ case ScanlineFormat::N24BitTcBgr:
+ return ImplBlendFromBitmap<ScanlineFormat::N24BitTcBgr>( rDst, rSrc, rMsk );
+ case ScanlineFormat::N24BitTcRgb:
+ return ImplBlendFromBitmap<ScanlineFormat::N24BitTcRgb>( rDst, rSrc, rMsk );
+
+ case ScanlineFormat::N32BitTcAbgr:
+ return ImplBlendFromBitmap<ScanlineFormat::N32BitTcAbgr>( rDst, rSrc, rMsk );
+ case ScanlineFormat::N32BitTcArgb:
+ return ImplBlendFromBitmap<ScanlineFormat::N32BitTcArgb>( rDst, rSrc, rMsk );
+ case ScanlineFormat::N32BitTcBgra:
+ return ImplBlendFromBitmap<ScanlineFormat::N32BitTcBgra>( rDst, rSrc, rMsk );
+ case ScanlineFormat::N32BitTcRgba:
+ return ImplBlendFromBitmap<ScanlineFormat::N32BitTcRgba>( rDst, rSrc, rMsk );
+ default: break;
+ }
+
+ static int nNotAccelerated = 0;
+ SAL_WARN_IF( rSrc.mnWidth * rSrc.mnHeight >= 4000 && ++nNotAccelerated == 100,
+ "vcl.gdi",
+ "ImplFastBlend for not accelerated case (" << std::hex << static_cast<int>(rSrc.mnFormat) << "*" << static_cast<int>(rMsk.mnFormat) << "->" << static_cast<int>(rDst.mnFormat) << ")" );
+
+ return false;
+}
+
+bool ImplFastEraseBitmap( BitmapBuffer& rDst, const BitmapColor& rColor )
+{
+ const ScanlineFormat nDstFormat = RemoveScanline(rDst.mnFormat);
+
+ // erasing a bitmap is often just a byte-wise memory fill
+ bool bByteFill = true;
+ sal_uInt8 nFillByte;
+
+ switch( nDstFormat )
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ case ScanlineFormat::N1BitLsbPal:
+ nFillByte = rColor.GetIndex();
+ nFillByte = static_cast<sal_uInt8>( -(nFillByte & 1) ); // 0x00 or 0xFF
+ break;
+ case ScanlineFormat::N4BitMsnPal:
+ case ScanlineFormat::N4BitLsnPal:
+ nFillByte = rColor.GetIndex();
+ nFillByte &= 0x0F;
+ nFillByte |= (nFillByte << 4);
+ break;
+ case ScanlineFormat::N8BitPal:
+ case ScanlineFormat::N8BitTcMask:
+ 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 )
+ {
+ 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/gdi/configsettings.cxx b/vcl/source/gdi/configsettings.cxx
new file mode 100644
index 000000000..8d477ec37
--- /dev/null
+++ b/vcl/source/gdi/configsettings.cxx
@@ -0,0 +1,137 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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;
+
+#define SETTINGS_CONFIGNODE "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
+ Sequence< OUString > aKeys( GetNodeNames( aKeyName ) );
+ Sequence< OUString > aSettingsKeys( aKeys.getLength() );
+ std::transform(aKeys.begin(), aKeys.end(), aSettingsKeys.begin(),
+ [&aKeyName](const OUString& rKey) -> OUString { return aKeyName + "/" + rKey; });
+ Sequence< Any > aValues( GetProperties( aSettingsKeys ) );
+ const OUString* pFrom = aKeys.getConstArray();
+ const Any* pValue = aValues.getConstArray();
+ for( int i = 0; i < aValues.getLength(); i++, pValue++ )
+ {
+ if( auto pLine = o3tl::tryAccess<OUString>(*pValue) )
+ {
+ if( !pLine->isEmpty() )
+ m_aSettings[ aKeyName ][ pFrom[i] ] = *pLine;
+#if OSL_DEBUG_LEVEL > 2
+ SAL_INFO( "vcl", " \"" << aKeys.getConstArray()[i] << "\"=\"" << *pLine << "\"" );
+#endif
+ }
+ }
+ }
+}
+
+OUString SettingsConfigItem::getValue( const OUString& rGroup, const OUString& rKey ) const
+{
+ std::unordered_map< OUString, SmallOUStrMap >::const_iterator group = m_aSettings.find( rGroup );
+ if( group == m_aSettings.end() || group->second.find( rKey ) == group->second.end() )
+ {
+ return OUString();
+ }
+ return group->second.find(rKey)->second;
+}
+
+void SettingsConfigItem::setValue( const OUString& rGroup, const OUString& rKey, const OUString& rValue )
+{
+ bool bModified = m_aSettings[ rGroup ][ rKey ] != rValue;
+ if( bModified )
+ {
+ m_aSettings[ rGroup ][ rKey ] = rValue;
+ SetModified();
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/cvtgrf.cxx b/vcl/source/gdi/cvtgrf.cxx
new file mode 100644
index 000000000..9e9ecfb6e
--- /dev/null
+++ b/vcl/source/gdi/cvtgrf.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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()
+{
+}
+
+GraphicConverter::~GraphicConverter()
+{
+}
+
+ErrCode GraphicConverter::Import( SvStream& rIStm, Graphic& rGraphic, ConvertDataFormat nFormat )
+{
+ GraphicConverter* pCvt = ImplGetSVData()->maGDIData.mpGrfConverter;
+ 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.mpGrfConverter;
+ 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/dibtools.cxx b/vcl/source/gdi/dibtools.cxx
new file mode 100644
index 000000000..f2164b94d
--- /dev/null
+++ b/vcl/source/gdi/dibtools.cxx
@@ -0,0 +1,1904 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+#include <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/bitmapaccess.hxx>
+#include <vcl/outdev.hxx>
+#include <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()
+ : aXyzRed(),
+ aXyzGreen(),
+ aXyzBlue()
+ {}
+};
+
+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()
+ : DIBInfoHeader(),
+ nV5RedMask(0),
+ nV5GreenMask(0),
+ nV5BlueMask(0),
+ nV5AlphaMask(0),
+ nV5CSType(0),
+ aV5Endpoints(),
+ nV5GammaRed(0),
+ nV5GammaGreen(0),
+ nV5GammaBlue(0),
+ nV5Intent(0),
+ nV5ProfileData(0),
+ nV5ProfileSize(0),
+ nV5Reserved(0)
+ {}
+};
+
+sal_uInt16 discretizeBitcount( sal_uInt16 nInputCount )
+{
+ return ( nInputCount <= 1 ) ? 1 :
+ ( nInputCount <= 4 ) ? 4 :
+ ( nInputCount <= 8 ) ? 8 : 24;
+}
+
+bool isBitfieldCompression( ScanlineFormat nScanlineFormat )
+{
+ return ScanlineFormat::N32BitTcMask == nScanlineFormat;
+}
+
+bool ImplReadDIBInfoHeader(SvStream& rIStm, DIBV5Header& rHeader, bool& bTopDown, bool bMSOFormat)
+{
+ // 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 );
+
+ // seek to EndPos
+ if (!checkSeek(rIStm, aStartPos + rHeader.nSize))
+ return false;
+ }
+
+ if (rHeader.nHeight == SAL_MIN_INT32)
+ return false;
+
+ if ( rHeader.nHeight < 0 )
+ {
+ bTopDown = true;
+ rHeader.nHeight *= -1;
+ }
+ else
+ {
+ bTopDown = false;
+ }
+
+ if ( rHeader.nWidth < 0 || rHeader.nXPelsPerMeter < 0 || rHeader.nYPelsPerMeter < 0 )
+ {
+ rIStm.SetError( SVSTREAM_FILEFORMAT_ERROR );
+ }
+
+ // #144105# protect a little against damaged files
+ assert(rHeader.nHeight >= 0);
+ if (rHeader.nHeight != 0 && rHeader.nWidth >= 0
+ && (rHeader.nSizeImage / 16 / static_cast<sal_uInt32>(rHeader.nHeight)
+ > o3tl::make_unsigned(rHeader.nWidth)))
+ {
+ rHeader.nSizeImage = 0;
+ }
+
+
+ if (rHeader.nPlanes != 1)
+ return false;
+
+ if (rHeader.nBitCount != 0 && rHeader.nBitCount != 1 &&
+ rHeader.nBitCount != 4 && rHeader.nBitCount != 8 &&
+ rHeader.nBitCount != 16 && rHeader.nBitCount != 24 &&
+ rHeader.nBitCount != 32)
+ {
+ return false;
+ }
+
+ return rIStm.good();
+}
+
+bool ImplReadDIBPalette(SvStream& rIStm, BitmapPalette& rPal, bool bQuad)
+{
+ const sal_uInt16 nColors = rPal.GetEntryCount();
+ const sal_uLong nPalSize = nColors * ( bQuad ? 4UL : 3UL );
+ BitmapColor aPalColor;
+
+ std::unique_ptr<sal_uInt8[]> pEntries(new sal_uInt8[ nPalSize ]);
+ if (rIStm.ReadBytes(pEntries.get(), nPalSize) != nPalSize)
+ {
+ return false;
+ }
+
+ sal_uInt8* pTmpEntry = pEntries.get();
+ for( sal_uInt16 i = 0; i < nColors; i++ )
+ {
+ aPalColor.SetBlue( *pTmpEntry++ );
+ aPalColor.SetGreen( *pTmpEntry++ );
+ aPalColor.SetRed( *pTmpEntry++ );
+
+ if( bQuad )
+ pTmpEntry++;
+
+ rPal[i] = aPalColor;
+ }
+
+ return rIStm.GetError() == ERRCODE_NONE;
+}
+
+BitmapColor SanitizePaletteIndex(sal_uInt8 nIndex, BitmapPalette& rPalette, bool bForceToMonoWhileReading)
+{
+ const sal_uInt16 nPaletteEntryCount = rPalette.GetEntryCount();
+ if (nPaletteEntryCount && nIndex >= nPaletteEntryCount)
+ {
+ auto nSanitizedIndex = nIndex % nPaletteEntryCount;
+ SAL_WARN_IF(nIndex != nSanitizedIndex, "vcl", "invalid colormap index: "
+ << static_cast<unsigned int>(nIndex) << ", colormap len is: "
+ << nPaletteEntryCount);
+ nIndex = nSanitizedIndex;
+ }
+
+ if (nPaletteEntryCount && bForceToMonoWhileReading)
+ {
+ return BitmapColor(static_cast<sal_uInt8>(rPalette[nIndex].GetLuminance() >= 255));
+ }
+
+ return BitmapColor(nIndex);
+}
+
+BitmapColor SanitizeColor(const BitmapColor &rColor, bool bForceToMonoWhileReading)
+{
+ if (!bForceToMonoWhileReading)
+ return rColor;
+ return BitmapColor(static_cast<sal_uInt8>(rColor.GetLuminance() >= 255));
+}
+
+bool ImplDecodeRLE(sal_uInt8* pBuffer, DIBV5Header const & rHeader, BitmapWriteAccess& rAcc, BitmapPalette& rPalette, bool bForceToMonoWhileReading, bool bRLE4)
+{
+ Scanline pRLE = pBuffer;
+ Scanline pEndRLE = pBuffer + rHeader.nSizeImage;
+ long nY = rHeader.nHeight - 1;
+ const sal_uLong nWidth = rAcc.Width();
+ sal_uLong nCountByte;
+ sal_uLong nRunByte;
+ sal_uLong nX = 0;
+ sal_uInt8 cTmp;
+ bool bEndDecoding = false;
+
+ do
+ {
+ if (pRLE == pEndRLE)
+ return false;
+ if( ( nCountByte = *pRLE++ ) == 0 )
+ {
+ if (pRLE == pEndRLE)
+ return false;
+ nRunByte = *pRLE++;
+
+ if( nRunByte > 2 )
+ {
+ Scanline pScanline = rAcc.GetScanline(nY);
+ if( bRLE4 )
+ {
+ nCountByte = nRunByte >> 1;
+
+ for( sal_uLong i = 0; i < nCountByte; i++ )
+ {
+ if (pRLE == pEndRLE)
+ return false;
+
+ cTmp = *pRLE++;
+
+ if( nX < nWidth )
+ rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(cTmp >> 4, rPalette, bForceToMonoWhileReading));
+
+ if( nX < nWidth )
+ rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(cTmp & 0x0f, rPalette, bForceToMonoWhileReading));
+ }
+
+ if( nRunByte & 1 )
+ {
+ if (pRLE == pEndRLE)
+ return false;
+
+ if( nX < nWidth )
+ rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(*pRLE >> 4, rPalette, bForceToMonoWhileReading));
+
+ pRLE++;
+ }
+
+ if( ( ( nRunByte + 1 ) >> 1 ) & 1 )
+ {
+ if (pRLE == pEndRLE)
+ return false;
+
+ pRLE++;
+ }
+ }
+ else
+ {
+ for( sal_uLong i = 0; i < nRunByte; i++ )
+ {
+ if (pRLE == pEndRLE)
+ return false;
+
+ if( nX < nWidth )
+ rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(*pRLE, rPalette, bForceToMonoWhileReading));
+
+ pRLE++;
+ }
+
+ if( nRunByte & 1 )
+ {
+ if (pRLE == pEndRLE)
+ return false;
+
+ pRLE++;
+ }
+ }
+ }
+ else if( !nRunByte )
+ {
+ nY--;
+ nX = 0;
+ }
+ else if( nRunByte == 1 )
+ bEndDecoding = true;
+ else
+ {
+ if (pRLE == pEndRLE)
+ return false;
+
+ nX += *pRLE++;
+
+ if (pRLE == pEndRLE)
+ return false;
+
+ nY -= *pRLE++;
+ }
+ }
+ else
+ {
+ if (pRLE == pEndRLE)
+ return false;
+ cTmp = *pRLE++;
+
+ Scanline pScanline = rAcc.GetScanline(nY);
+ if( bRLE4 )
+ {
+ nRunByte = nCountByte >> 1;
+
+ for (sal_uLong i = 0; i < nRunByte && nX < nWidth; ++i)
+ {
+ rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(cTmp >> 4, rPalette, bForceToMonoWhileReading));
+ if( nX < nWidth )
+ rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(cTmp & 0x0f, rPalette, bForceToMonoWhileReading));
+ }
+
+ if( ( nCountByte & 1 ) && ( nX < nWidth ) )
+ rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(cTmp >> 4, rPalette, bForceToMonoWhileReading));
+ }
+ else
+ {
+ for (sal_uLong i = 0; i < nCountByte && nX < nWidth; ++i)
+ rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(cTmp, rPalette, bForceToMonoWhileReading));
+ }
+ }
+ }
+ while (!bEndDecoding && (nY >= 0));
+
+ return true;
+}
+
+bool ImplReadDIBBits(SvStream& rIStm, DIBV5Header& rHeader, BitmapWriteAccess& rAcc, BitmapPalette& rPalette, BitmapWriteAccess* pAccAlpha,
+ bool bTopDown, bool& rAlphaUsed, const sal_uInt64 nAlignedWidth,
+ const bool bForceToMonoWhileReading)
+{
+ sal_uInt32 nRMask(( rHeader.nBitCount == 16 ) ? 0x00007c00UL : 0x00ff0000UL);
+ sal_uInt32 nGMask(( rHeader.nBitCount == 16 ) ? 0x000003e0UL : 0x0000ff00UL);
+ sal_uInt32 nBMask(( rHeader.nBitCount == 16 ) ? 0x0000001fUL : 0x000000ffUL);
+ bool bNative(false);
+ bool bTCMask(!pAccAlpha && ((16 == rHeader.nBitCount) || (32 == rHeader.nBitCount)));
+ bool bRLE((RLE_8 == rHeader.nCompression && 8 == rHeader.nBitCount) || (RLE_4 == rHeader.nCompression && 4 == rHeader.nBitCount));
+
+ // Is native format?
+ switch(rAcc.GetScanlineFormat())
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ case ScanlineFormat::N24BitTcBgr:
+ {
+ // we can't trust arbitrary-sourced index based formats to have correct indexes, so we exclude the pal formats
+ // from raw read and force checking their colormap indexes
+ bNative = ( ( rAcc.IsBottomUp() != bTopDown ) && !bRLE && !bTCMask && ( rAcc.GetScanlineSize() == nAlignedWidth ) );
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+
+ // Read data
+ if (bNative)
+ {
+ if (nAlignedWidth
+ > std::numeric_limits<std::size_t>::max() / rHeader.nHeight)
+ {
+ return false;
+ }
+ std::size_t n = nAlignedWidth * rHeader.nHeight;
+ if (rIStm.ReadBytes(rAcc.GetBuffer(), n) != n)
+ {
+ return false;
+ }
+ }
+ else
+ {
+ // Read color mask
+ if(bTCMask && BITFIELDS == rHeader.nCompression)
+ {
+ rIStm.SeekRel( -12 );
+ rIStm.ReadUInt32( nRMask );
+ rIStm.ReadUInt32( nGMask );
+ rIStm.ReadUInt32( nBMask );
+ }
+
+ const long nWidth(rHeader.nWidth);
+ const long nHeight(rHeader.nHeight);
+ long nResult = 0;
+ if (utl::ConfigManager::IsFuzzing() && (o3tl::checked_multiply(nWidth, nHeight, nResult) || nResult > 4000000))
+ return false;
+
+ if (bRLE)
+ {
+ if(!rHeader.nSizeImage)
+ {
+ rHeader.nSizeImage = rIStm.remainingSize();
+ }
+
+ if (rHeader.nSizeImage > rIStm.remainingSize())
+ return false;
+ std::vector<sal_uInt8> aBuffer(rHeader.nSizeImage);
+ if (rIStm.ReadBytes(aBuffer.data(), rHeader.nSizeImage) != rHeader.nSizeImage)
+ return false;
+ if (!ImplDecodeRLE(aBuffer.data(), rHeader, rAcc, rPalette, bForceToMonoWhileReading, RLE_4 == rHeader.nCompression))
+ return false;
+ }
+ else
+ {
+ if (nAlignedWidth > rIStm.remainingSize())
+ {
+ // ofz#11188 avoid timeout
+ // all following paths will enter a case statement, and nCount
+ // is always at least 1, so we can check here before allocation
+ // if at least one row can be read
+ return false;
+ }
+ std::vector<sal_uInt8> aBuf(nAlignedWidth);
+
+ const long nI(bTopDown ? 1 : -1);
+ long nY(bTopDown ? 0 : nHeight - 1);
+ 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( long nX = 0, nShift = 8; nX < nWidth; nX++ )
+ {
+ if( !nShift )
+ {
+ nShift = 8;
+ cTmp = *pTmp++;
+ }
+
+ auto nIndex = (cTmp >> --nShift) & 1;
+ rAcc.SetPixelOnData(pScanline, nX, SanitizePaletteIndex(nIndex, rPalette, bForceToMonoWhileReading));
+ }
+ }
+ }
+ break;
+
+ case 4:
+ {
+ for( ; nCount--; nY += nI )
+ {
+ sal_uInt8 * pTmp = aBuf.data();
+ if (rIStm.ReadBytes(pTmp, nAlignedWidth)
+ != nAlignedWidth)
+ {
+ return false;
+ }
+ sal_uInt8 cTmp = *pTmp++;
+ Scanline pScanline = rAcc.GetScanline(nY);
+ for( long nX = 0, nShift = 2; nX < nWidth; nX++ )
+ {
+ if( !nShift )
+ {
+ nShift = 2;
+ cTmp = *pTmp++;
+ }
+
+ auto nIndex = (cTmp >> ( --nShift << 2 ) ) & 0x0f;
+ rAcc.SetPixelOnData(pScanline, nX, SanitizePaletteIndex(nIndex, rPalette, bForceToMonoWhileReading));
+ }
+ }
+ }
+ break;
+
+ case 8:
+ {
+ for( ; nCount--; nY += nI )
+ {
+ sal_uInt8 * pTmp = aBuf.data();
+ if (rIStm.ReadBytes(pTmp, nAlignedWidth)
+ != nAlignedWidth)
+ {
+ return false;
+ }
+
+ Scanline pScanline = rAcc.GetScanline(nY);
+ for( long nX = 0; nX < nWidth; nX++ )
+ {
+ auto nIndex = *pTmp++;
+ rAcc.SetPixelOnData(pScanline, nX, SanitizePaletteIndex(nIndex, rPalette, bForceToMonoWhileReading));
+ }
+ }
+ }
+ break;
+
+ case 16:
+ {
+ ColorMaskElement aRedMask(nRMask);
+ if (!aRedMask.CalcMaskShift())
+ return false;
+ ColorMaskElement aGreenMask(nGMask);
+ if (!aGreenMask.CalcMaskShift())
+ return false;
+ ColorMaskElement aBlueMask(nBMask);
+ if (!aBlueMask.CalcMaskShift())
+ return false;
+
+ ColorMask aMask(aRedMask, aGreenMask, aBlueMask);
+ BitmapColor aColor;
+
+ for( ; nCount--; nY += nI )
+ {
+ sal_uInt16 * pTmp16 = reinterpret_cast<sal_uInt16*>(aBuf.data());
+ if (rIStm.ReadBytes(pTmp16, nAlignedWidth)
+ != nAlignedWidth)
+ {
+ return false;
+ }
+
+ Scanline pScanline = rAcc.GetScanline(nY);
+ for( long nX = 0; nX < nWidth; nX++ )
+ {
+ aMask.GetColorFor16BitLSB( aColor, reinterpret_cast<sal_uInt8*>(pTmp16++) );
+ rAcc.SetPixelOnData(pScanline, nX, SanitizeColor(aColor, bForceToMonoWhileReading));
+ }
+ }
+ }
+ break;
+
+ case 24:
+ {
+ BitmapColor aPixelColor;
+
+ for( ; nCount--; nY += nI )
+ {
+ sal_uInt8* pTmp = aBuf.data();
+ if (rIStm.ReadBytes(pTmp, nAlignedWidth)
+ != nAlignedWidth)
+ {
+ return false;
+ }
+
+ Scanline pScanline = rAcc.GetScanline(nY);
+ for( long nX = 0; nX < nWidth; nX++ )
+ {
+ aPixelColor.SetBlue( *pTmp++ );
+ aPixelColor.SetGreen( *pTmp++ );
+ aPixelColor.SetRed( *pTmp++ );
+ rAcc.SetPixelOnData(pScanline, nX, SanitizeColor(aPixelColor, bForceToMonoWhileReading));
+ }
+ }
+ }
+ break;
+
+ case 32:
+ {
+ ColorMaskElement aRedMask(nRMask);
+ if (!aRedMask.CalcMaskShift())
+ return false;
+ ColorMaskElement aGreenMask(nGMask);
+ if (!aGreenMask.CalcMaskShift())
+ return false;
+ ColorMaskElement aBlueMask(nBMask);
+ if (!aBlueMask.CalcMaskShift())
+ return false;
+ ColorMask aMask(aRedMask, aGreenMask, aBlueMask);
+
+ BitmapColor aColor;
+ sal_uInt32* pTmp32;
+
+ if(pAccAlpha)
+ {
+ sal_uInt8 aAlpha;
+
+ for( ; nCount--; nY += nI )
+ {
+ pTmp32 = reinterpret_cast<sal_uInt32*>(aBuf.data());
+ if (rIStm.ReadBytes(pTmp32, nAlignedWidth)
+ != nAlignedWidth)
+ {
+ return false;
+ }
+
+ Scanline pScanline = rAcc.GetScanline(nY);
+ Scanline pAlphaScanline = pAccAlpha->GetScanline(nY);
+ for( long nX = 0; nX < nWidth; nX++ )
+ {
+ aMask.GetColorAndAlphaFor32Bit( aColor, aAlpha, reinterpret_cast<sal_uInt8*>(pTmp32++) );
+ rAcc.SetPixelOnData(pScanline, nX, SanitizeColor(aColor, bForceToMonoWhileReading));
+ pAccAlpha->SetPixelOnData(pAlphaScanline, nX, BitmapColor(sal_uInt8(0xff) - aAlpha));
+ rAlphaUsed |= 0xff != aAlpha;
+ }
+ }
+ }
+ else
+ {
+ for( ; nCount--; nY += nI )
+ {
+ pTmp32 = reinterpret_cast<sal_uInt32*>(aBuf.data());
+ if (rIStm.ReadBytes(pTmp32, nAlignedWidth)
+ != nAlignedWidth)
+ {
+ return false;
+ }
+
+ Scanline pScanline = rAcc.GetScanline(nY);
+ for( long nX = 0; nX < nWidth; nX++ )
+ {
+ aMask.GetColorFor32Bit( aColor, reinterpret_cast<sal_uInt8*>(pTmp32++) );
+ rAcc.SetPixelOnData(pScanline, nX, SanitizeColor(aColor, bForceToMonoWhileReading));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return rIStm.GetError() == ERRCODE_NONE;
+}
+
+bool ImplReadDIBBody(SvStream& rIStm, Bitmap& rBmp, AlphaMask* pBmpAlpha, sal_uLong nOffset, bool bIsMask, bool bMSOFormat)
+{
+ DIBV5Header aHeader;
+ const sal_uLong 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 long nRead = aCodec.Read(*pMemStm, aData.data() + nDataPos, sal_uInt32(nToRead));
+ if (nRead > 0)
+ {
+ nDataPos += static_cast<unsigned long>(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)
+ {
+ pIStm->SeekRel(nOffset - (pIStm->Tell() - nStmPos));
+ }
+
+ const sal_Int64 nBitsPerLine (static_cast<sal_Int64>(aHeader.nWidth) * static_cast<sal_Int64>(aHeader.nBitCount));
+ if (nBitsPerLine > SAL_MAX_UINT32)
+ return false;
+ const sal_uInt64 nAlignedWidth(AlignedWidth4Bytes(static_cast<sal_uLong>(nBitsPerLine)));
+
+ switch (aHeader.nCompression)
+ {
+ case RLE_8:
+ {
+ if (aHeader.nBitCount != 8)
+ return false;
+ // (partially) check the image dimensions to avoid potential large bitmap allocation if the input is damaged
+ sal_uInt64 nMaxWidth = pIStm->remainingSize();
+ nMaxWidth *= 256; //assume generous compression ratio
+ nMaxWidth /= aHeader.nHeight;
+ if (nMaxWidth < o3tl::make_unsigned(aHeader.nWidth))
+ return false;
+ break;
+ }
+ case RLE_4:
+ {
+ if (aHeader.nBitCount != 4)
+ return false;
+ sal_uInt64 nMaxWidth = pIStm->remainingSize();
+ nMaxWidth *= 512; //assume generous compression ratio
+ nMaxWidth /= aHeader.nHeight;
+ if (nMaxWidth < o3tl::make_unsigned(aHeader.nWidth))
+ return false;
+ break;
+ }
+ default:
+ // tdf#122958 invalid compression value used
+ if (aHeader.nCompression & 0x000F)
+ {
+ // lets assume that there was an error in the generating application
+ // and allow through as COMPRESS_NONE if the bottom byte is 0
+ SAL_WARN( "vcl", "bad bmp compression scheme: " << aHeader.nCompression << ", rejecting bmp");
+ return false;
+ }
+ else
+ SAL_WARN( "vcl", "bad bmp compression scheme: " << aHeader.nCompression << ", assuming meant to be COMPRESS_NONE");
+ [[fallthrough]];
+ case BITFIELDS:
+ case ZCOMPRESS:
+ case COMPRESS_NONE:
+ {
+ // (partially) check the image dimensions to avoid potential large bitmap allocation if the input is damaged
+ sal_uInt64 nMaxWidth = pIStm->remainingSize();
+ nMaxWidth /= aHeader.nHeight;
+ if (nMaxWidth < nAlignedWidth)
+ return false;
+ break;
+ }
+ }
+
+ const Size aSizePixel(aHeader.nWidth, aHeader.nHeight);
+ AlphaMask aNewBmpAlpha;
+ AlphaScopedWriteAccess pAccAlpha;
+ bool bAlphaPossible(pBmpAlpha && aHeader.nBitCount == 32);
+
+ if (bAlphaPossible)
+ {
+ const bool bRedSet(0 != aHeader.nV5RedMask);
+ const bool bGreenSet(0 != aHeader.nV5GreenMask);
+ const bool bBlueSet(0 != aHeader.nV5BlueMask);
+
+ // some clipboard entries have alpha mask on zero to say that there is
+ // no alpha; do only use this when the other masks are set. The MS docu
+ // says that masks are only to be set when bV5Compression is set to
+ // BI_BITFIELDS, but there seem to exist a wild variety of usages...
+ if((bRedSet || bGreenSet || bBlueSet) && (0 == aHeader.nV5AlphaMask))
+ {
+ bAlphaPossible = false;
+ }
+ }
+
+ if (bAlphaPossible)
+ {
+ aNewBmpAlpha = AlphaMask(aSizePixel);
+ pAccAlpha = AlphaScopedWriteAccess(aNewBmpAlpha);
+ }
+
+ sal_uInt16 nBitCount(discretizeBitcount(aHeader.nBitCount));
+ const BitmapPalette* pPal = &aPalette;
+ //ofz#948 match the surrounding logic of case TransparentType::Bitmap of
+ //ReadDIBBitmapEx but do it while reading for performance
+ const bool bIsAlpha = (nBitCount == 8 && !!aPalette && aPalette.IsGreyPalette8Bit());
+ const bool bForceToMonoWhileReading = (bIsMask && !bIsAlpha && nBitCount != 1);
+ if (bForceToMonoWhileReading)
+ {
+ pPal = nullptr;
+ nBitCount = 1;
+ SAL_WARN( "vcl", "forcing mask to monochrome");
+ }
+
+ Bitmap aNewBmp(aSizePixel, nBitCount, pPal);
+ BitmapScopedWriteAccess pAcc(aNewBmp);
+ if (!pAcc)
+ return false;
+ if (pAcc->Width() != aHeader.nWidth || pAcc->Height() != aHeader.nHeight)
+ {
+ return false;
+ }
+
+ // read bits
+ bool bAlphaUsed(false);
+ bool bRet = ImplReadDIBBits(*pIStm, aHeader, *pAcc, aPalette, pAccAlpha.get(), bTopDown, bAlphaUsed, nAlignedWidth, bForceToMonoWhileReading);
+
+ if (bRet && aHeader.nXPelsPerMeter && aHeader.nYPelsPerMeter)
+ {
+ MapMode aMapMode(
+ MapUnit::MapMM,
+ Point(),
+ Fraction(1000, aHeader.nXPelsPerMeter),
+ Fraction(1000, aHeader.nYPelsPerMeter));
+
+ aNewBmp.SetPrefMapMode(aMapMode);
+ aNewBmp.SetPrefSize(Size(aHeader.nWidth, aHeader.nHeight));
+ }
+
+ pAcc.reset();
+
+ if (bAlphaPossible)
+ {
+ pAccAlpha.reset();
+
+ if(!bAlphaUsed)
+ {
+ bAlphaPossible = false;
+ }
+ }
+
+ if (bRet)
+ {
+ rBmp = aNewBmp;
+
+ if(bAlphaPossible)
+ {
+ *pBmpAlpha = aNewBmpAlpha;
+ }
+ }
+
+ return bRet;
+}
+
+bool ImplReadDIBFileHeader( SvStream& rIStm, sal_uLong& rOffset )
+{
+ bool bRet = false;
+
+ const sal_uInt64 nStreamLength = rIStm.TellEnd();
+
+ sal_uInt16 nTmp16 = 0;
+ rIStm.ReadUInt16( nTmp16 );
+
+ if ( ( 0x4D42 == nTmp16 ) || ( 0x4142 == nTmp16 ) )
+ {
+ sal_uInt32 nTmp32(0);
+ if ( 0x4142 == nTmp16 )
+ {
+ rIStm.SeekRel( 12 );
+ rIStm.ReadUInt16( nTmp16 );
+ rIStm.SeekRel( 8 );
+ rIStm.ReadUInt32( nTmp32 );
+ rOffset = nTmp32 - 28;
+ bRet = ( 0x4D42 == nTmp16 );
+ }
+ else // 0x4D42 == nTmp16, 'MB' from BITMAPFILEHEADER
+ {
+ rIStm.SeekRel( 8 ); // we are on bfSize member of BITMAPFILEHEADER, forward to bfOffBits
+ rIStm.ReadUInt32( nTmp32 ); // read bfOffBits
+ rOffset = nTmp32 - 14; // adapt offset by sizeof(BITMAPFILEHEADER)
+ bRet = rIStm.GetError() == ERRCODE_NONE;
+ }
+
+ if ( rOffset >= nStreamLength )
+ {
+ // Offset claims that image starts past the end of the
+ // stream. Unlikely.
+ rIStm.SetError( SVSTREAM_FILEFORMAT_ERROR );
+ bRet = false;
+ }
+ }
+ else
+ rIStm.SetError( SVSTREAM_FILEFORMAT_ERROR );
+
+ return bRet;
+}
+
+bool ImplWriteDIBPalette( SvStream& rOStm, BitmapReadAccess const & rAcc )
+{
+ const sal_uInt16 nColors = rAcc.GetPaletteEntryCount();
+ const sal_uLong nPalSize = nColors * 4UL;
+ std::unique_ptr<sal_uInt8[]> pEntries(new sal_uInt8[ nPalSize ]);
+ sal_uInt8* pTmpEntry = pEntries.get();
+
+ for( sal_uInt16 i = 0; i < nColors; i++ )
+ {
+ const BitmapColor& rPalColor = rAcc.GetPaletteColor( i );
+
+ *pTmpEntry++ = rPalColor.GetBlue();
+ *pTmpEntry++ = rPalColor.GetGreen();
+ *pTmpEntry++ = rPalColor.GetRed();
+ *pTmpEntry++ = 0;
+ }
+
+ rOStm.WriteBytes( pEntries.get(), nPalSize );
+
+ return rOStm.GetError() == ERRCODE_NONE;
+}
+
+bool ImplWriteRLE( SvStream& rOStm, BitmapReadAccess const & rAcc, bool bRLE4 )
+{
+ const sal_uLong nWidth = rAcc.Width();
+ const sal_uLong nHeight = rAcc.Height();
+ sal_uLong nX;
+ sal_uLong nSaveIndex;
+ sal_uLong nCount;
+ sal_uLong nBufCount;
+ std::vector<sal_uInt8> aBuf(( nWidth << 1 ) + 2);
+ sal_uInt8 cPix;
+ sal_uInt8 cLast;
+ bool bFound;
+
+ for ( long nY = nHeight - 1; nY >= 0; nY-- )
+ {
+ sal_uInt8* pTmp = aBuf.data();
+ nX = nBufCount = 0;
+ Scanline pScanline = rAcc.GetScanline( nY );
+
+ while( nX < nWidth )
+ {
+ nCount = 1;
+ cPix = rAcc.GetIndexFromData( pScanline, nX++ );
+
+ while( ( nX < nWidth ) && ( nCount < 255 )
+ && ( cPix == rAcc.GetIndexFromData( pScanline, nX ) ) )
+ {
+ nX++;
+ nCount++;
+ }
+
+ if ( nCount > 1 )
+ {
+ *pTmp++ = static_cast<sal_uInt8>(nCount);
+ *pTmp++ = ( bRLE4 ? ( ( cPix << 4 ) | cPix ) : cPix );
+ nBufCount += 2;
+ }
+ else
+ {
+ cLast = cPix;
+ nSaveIndex = nX - 1;
+ bFound = false;
+
+ while( ( nX < nWidth ) && ( nCount < 256 ) )
+ {
+ cPix = rAcc.GetIndexFromData( pScanline, nX );
+ if (cPix == cLast)
+ break;
+ nX++; nCount++;
+ cLast = cPix;
+ bFound = true;
+ }
+
+ if ( bFound )
+ nX--;
+
+ if ( nCount > 3 )
+ {
+ *pTmp++ = 0;
+ *pTmp++ = static_cast<sal_uInt8>(--nCount);
+
+ if( bRLE4 )
+ {
+ for ( sal_uLong i = 0; i < nCount; i++, pTmp++ )
+ {
+ *pTmp = rAcc.GetIndexFromData( pScanline, nSaveIndex++ ) << 4;
+
+ if ( ++i < nCount )
+ *pTmp |= rAcc.GetIndexFromData( pScanline, nSaveIndex++ );
+ }
+
+ nCount = ( nCount + 1 ) >> 1;
+ }
+ else
+ {
+ for( sal_uLong i = 0; i < nCount; i++ )
+ *pTmp++ = rAcc.GetIndexFromData( pScanline, nSaveIndex++ );
+ }
+
+ if ( nCount & 1 )
+ {
+ *pTmp++ = 0;
+ nBufCount += ( nCount + 3 );
+ }
+ else
+ nBufCount += ( nCount + 2 );
+ }
+ else
+ {
+ *pTmp++ = 1;
+ *pTmp++ = rAcc.GetIndexFromData( pScanline, nSaveIndex ) << (bRLE4 ? 4 : 0);
+
+ if ( nCount == 3 )
+ {
+ *pTmp++ = 1;
+ *pTmp++ = rAcc.GetIndexFromData( pScanline, ++nSaveIndex ) << ( bRLE4 ? 4 : 0 );
+ nBufCount += 4;
+ }
+ else
+ nBufCount += 2;
+ }
+ }
+ }
+
+ aBuf[ nBufCount++ ] = 0;
+ aBuf[ nBufCount++ ] = 0;
+
+ rOStm.WriteBytes(aBuf.data(), nBufCount);
+ }
+
+ rOStm.WriteUChar( 0 );
+ rOStm.WriteUChar( 1 );
+
+ return rOStm.GetError() == ERRCODE_NONE;
+}
+
+bool ImplWriteDIBBits(SvStream& rOStm, BitmapReadAccess const & rAcc, BitmapReadAccess const * pAccAlpha, sal_uLong nCompression, sal_uInt32& rImageSize)
+{
+ if(!pAccAlpha && BITFIELDS == nCompression)
+ {
+ const ColorMask& rMask = rAcc.GetColorMask();
+ SVBT32 aVal32;
+
+ UInt32ToSVBT32( rMask.GetRedMask(), aVal32 );
+ rOStm.WriteBytes( aVal32, 4UL );
+
+ UInt32ToSVBT32( rMask.GetGreenMask(), aVal32 );
+ rOStm.WriteBytes( aVal32, 4UL );
+
+ UInt32ToSVBT32( rMask.GetBlueMask(), aVal32 );
+ rOStm.WriteBytes( aVal32, 4UL );
+
+ rImageSize = rOStm.Tell();
+
+ if( rAcc.IsBottomUp() )
+ rOStm.WriteBytes(rAcc.GetBuffer(), rAcc.Height() * rAcc.GetScanlineSize());
+ else
+ {
+ for( long nY = rAcc.Height() - 1, nScanlineSize = rAcc.GetScanlineSize(); nY >= 0; nY-- )
+ rOStm.WriteBytes( rAcc.GetScanline(nY), nScanlineSize );
+ }
+ }
+ else if(!pAccAlpha && ((RLE_4 == nCompression) || (RLE_8 == nCompression)))
+ {
+ rImageSize = rOStm.Tell();
+ ImplWriteRLE( rOStm, rAcc, RLE_4 == nCompression );
+ }
+ else if(!nCompression)
+ {
+ // #i5xxx# Limit bitcount to 24bit, the 32 bit cases are not
+ // handled properly below (would have to set color masks, and
+ // nCompression=BITFIELDS - but color mask is not set for
+ // formats != *_TC_*). Note that this very problem might cause
+ // trouble at other places - the introduction of 32 bit RGBA
+ // bitmaps is relatively recent.
+ // #i59239# discretize bitcount for aligned width to 1,4,8,24
+ // (other cases are not written below)
+ const sal_uInt16 nBitCount(pAccAlpha ? 32 : discretizeBitcount(rAcc.GetBitCount()));
+ const sal_uLong nAlignedWidth(AlignedWidth4Bytes(rAcc.Width() * nBitCount));
+ bool bNative(false);
+
+ switch(rAcc.GetScanlineFormat())
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ case ScanlineFormat::N4BitMsnPal:
+ case ScanlineFormat::N8BitPal:
+ case ScanlineFormat::N24BitTcBgr:
+ {
+ if(!pAccAlpha && rAcc.IsBottomUp() && (rAcc.GetScanlineSize() == nAlignedWidth))
+ {
+ bNative = true;
+ }
+
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+
+ rImageSize = rOStm.Tell();
+
+ if(bNative)
+ {
+ rOStm.WriteBytes(rAcc.GetBuffer(), nAlignedWidth * rAcc.Height());
+ }
+ else
+ {
+ const long nWidth(rAcc.Width());
+ const long nHeight(rAcc.Height());
+ std::vector<sal_uInt8> aBuf(nAlignedWidth);
+ switch( nBitCount )
+ {
+ case 1:
+ {
+ //valgrind, zero out the trailing unused alignment bytes
+ size_t nUnusedBytes = nAlignedWidth - ((nWidth+7) / 8);
+ memset(aBuf.data() + nAlignedWidth - nUnusedBytes, 0, nUnusedBytes);
+
+ for( long nY = nHeight - 1; nY >= 0; nY-- )
+ {
+ sal_uInt8* pTmp = aBuf.data();
+ sal_uInt8 cTmp = 0;
+ Scanline pScanline = rAcc.GetScanline( nY );
+
+ for( long nX = 0, nShift = 8; nX < nWidth; nX++ )
+ {
+ if( !nShift )
+ {
+ nShift = 8;
+ *pTmp++ = cTmp;
+ cTmp = 0;
+ }
+
+ cTmp |= rAcc.GetIndexFromData( pScanline, nX ) << --nShift;
+ }
+
+ *pTmp = cTmp;
+ rOStm.WriteBytes(aBuf.data(), nAlignedWidth);
+ }
+ }
+ break;
+
+ case 4:
+ {
+ //valgrind, zero out the trailing unused alignment bytes
+ size_t nUnusedBytes = nAlignedWidth - ((nWidth+1) / 2);
+ memset(aBuf.data() + nAlignedWidth - nUnusedBytes, 0, nUnusedBytes);
+
+ for( long nY = nHeight - 1; nY >= 0; nY-- )
+ {
+ sal_uInt8* pTmp = aBuf.data();
+ sal_uInt8 cTmp = 0;
+ Scanline pScanline = rAcc.GetScanline( nY );
+
+ for( long nX = 0, nShift = 2; nX < nWidth; nX++ )
+ {
+ if( !nShift )
+ {
+ nShift = 2;
+ *pTmp++ = cTmp;
+ cTmp = 0;
+ }
+
+ cTmp |= rAcc.GetIndexFromData( pScanline, nX ) << ( --nShift << 2 );
+ }
+ *pTmp = cTmp;
+ rOStm.WriteBytes(aBuf.data(), nAlignedWidth);
+ }
+ }
+ break;
+
+ case 8:
+ {
+ for( long nY = nHeight - 1; nY >= 0; nY-- )
+ {
+ sal_uInt8* pTmp = aBuf.data();
+ Scanline pScanline = rAcc.GetScanline( nY );
+
+ for( long nX = 0; nX < nWidth; nX++ )
+ *pTmp++ = rAcc.GetIndexFromData( pScanline, nX );
+
+ rOStm.WriteBytes(aBuf.data(), nAlignedWidth);
+ }
+ }
+ break;
+
+ case 24:
+ {
+ //valgrind, zero out the trailing unused alignment bytes
+ size_t nUnusedBytes = nAlignedWidth - nWidth * 3;
+ memset(aBuf.data() + nAlignedWidth - nUnusedBytes, 0, nUnusedBytes);
+ }
+ [[fallthrough]];
+ // #i59239# fallback to 24 bit format, if bitcount is non-default
+ default:
+ {
+ BitmapColor aPixelColor;
+ const bool bWriteAlpha(32 == nBitCount && pAccAlpha);
+
+ for( long nY = nHeight - 1; nY >= 0; nY-- )
+ {
+ sal_uInt8* pTmp = aBuf.data();
+ Scanline pScanlineAlpha = bWriteAlpha ? pAccAlpha->GetScanline( nY ) : nullptr;
+
+ for( long nX = 0; nX < nWidth; nX++ )
+ {
+ // when alpha is used, this may be non-24bit main bitmap, so use GetColor
+ // instead of GetPixel to ensure RGB value
+ aPixelColor = rAcc.GetColor( nY, nX );
+
+ *pTmp++ = aPixelColor.GetBlue();
+ *pTmp++ = aPixelColor.GetGreen();
+ *pTmp++ = aPixelColor.GetRed();
+
+ if(bWriteAlpha)
+ {
+ *pTmp++ = sal_uInt8(0xff) - pAccAlpha->GetIndexFromData( pScanlineAlpha, nX );
+ }
+ }
+
+ rOStm.WriteBytes(aBuf.data(), nAlignedWidth);
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ rImageSize = rOStm.Tell() - rImageSize;
+
+ return (!rOStm.GetError());
+}
+
+bool ImplWriteDIBBody(const Bitmap& rBitmap, SvStream& rOStm, BitmapReadAccess const & rAcc, BitmapReadAccess const * pAccAlpha, bool bCompressed)
+{
+ const MapMode aMapPixel(MapUnit::MapPixel);
+ DIBV5Header aHeader;
+ sal_uLong nImageSizePos(0);
+ sal_uLong nEndPos(0);
+ sal_uInt32 nCompression(COMPRESS_NONE);
+ bool bRet(false);
+
+ aHeader.nSize = pAccAlpha ? DIBV5HEADERSIZE : DIBINFOHEADERSIZE; // size dependent on CF_DIB type to use
+ aHeader.nWidth = rAcc.Width();
+ aHeader.nHeight = rAcc.Height();
+ aHeader.nPlanes = 1;
+
+ if(!pAccAlpha && isBitfieldCompression(rAcc.GetScanlineFormat()))
+ {
+ aHeader.nBitCount = 32;
+ aHeader.nSizeImage = rAcc.Height() * rAcc.GetScanlineSize();
+ nCompression = BITFIELDS;
+ }
+ else
+ {
+ // #i5xxx# Limit bitcount to 24bit, the 32 bit cases are
+ // not handled properly below (would have to set color
+ // masks, and nCompression=BITFIELDS - but color mask is
+ // not set for formats != *_TC_*). Note that this very
+ // problem might cause trouble at other places - the
+ // introduction of 32 bit RGBA bitmaps is relatively
+ // recent.
+ // #i59239# discretize bitcount to 1,4,8,24 (other cases
+ // are not written below)
+ const sal_uInt16 nBitCount(pAccAlpha ? 32 : discretizeBitcount(rAcc.GetBitCount()));
+ aHeader.nBitCount = nBitCount;
+ aHeader.nSizeImage = rAcc.Height() * AlignedWidth4Bytes(rAcc.Width() * aHeader.nBitCount);
+
+ if(bCompressed)
+ {
+ if(4 == nBitCount)
+ {
+ nCompression = RLE_4;
+ }
+ else if(8 == nBitCount)
+ {
+ nCompression = RLE_8;
+ }
+ }
+ }
+
+ if((rOStm.GetCompressMode() & SvStreamCompressFlags::ZBITMAP) && (rOStm.GetVersion() >= SOFFICE_FILEFORMAT_40))
+ {
+ aHeader.nCompression = ZCOMPRESS;
+ }
+ else
+ {
+ aHeader.nCompression = nCompression;
+ }
+
+ if(rBitmap.GetPrefSize().Width() && rBitmap.GetPrefSize().Height() && (rBitmap.GetPrefMapMode() != aMapPixel))
+ {
+ // #i48108# Try to recover xpels/ypels as previously stored on
+ // disk. The problem with just converting maPrefSize to 100th
+ // mm and then relating that to the bitmap pixel size is that
+ // MapMode is integer-based, and suffers from roundoffs,
+ // especially if maPrefSize is small. Trying to circumvent
+ // that by performing part of the math in floating point.
+ const Size aScale100000(OutputDevice::LogicToLogic(Size(100000, 100000), MapMode(MapUnit::Map100thMM), rBitmap.GetPrefMapMode()));
+ const double fBmpWidthM(static_cast<double>(rBitmap.GetPrefSize().Width()) / aScale100000.Width());
+ const double fBmpHeightM(static_cast<double>(rBitmap.GetPrefSize().Height()) / aScale100000.Height());
+
+ if(!basegfx::fTools::equalZero(fBmpWidthM) && !basegfx::fTools::equalZero(fBmpHeightM))
+ {
+ aHeader.nXPelsPerMeter = basegfx::fround(rAcc.Width() / fabs(fBmpWidthM));
+ aHeader.nYPelsPerMeter = basegfx::fround(rAcc.Height() / fabs(fBmpHeightM));
+ }
+ }
+
+ aHeader.nColsUsed = ((!pAccAlpha && aHeader.nBitCount <= 8) ? rAcc.GetPaletteEntryCount() : 0);
+ aHeader.nColsImportant = 0;
+
+ rOStm.WriteUInt32( aHeader.nSize );
+ rOStm.WriteInt32( aHeader.nWidth );
+ rOStm.WriteInt32( aHeader.nHeight );
+ rOStm.WriteUInt16( aHeader.nPlanes );
+ rOStm.WriteUInt16( aHeader.nBitCount );
+ rOStm.WriteUInt32( aHeader.nCompression );
+
+ nImageSizePos = rOStm.Tell();
+ rOStm.SeekRel( sizeof( aHeader.nSizeImage ) );
+
+ rOStm.WriteInt32( aHeader.nXPelsPerMeter );
+ rOStm.WriteInt32( aHeader.nYPelsPerMeter );
+ rOStm.WriteUInt32( aHeader.nColsUsed );
+ rOStm.WriteUInt32( aHeader.nColsImportant );
+
+ if(pAccAlpha) // only write DIBV5 when asked to do so
+ {
+ aHeader.nV5CSType = 0x57696E20; // LCS_WINDOWS_COLOR_SPACE
+ aHeader.nV5Intent = 0x00000004; // LCS_GM_IMAGES
+
+ rOStm.WriteUInt32( aHeader.nV5RedMask );
+ rOStm.WriteUInt32( aHeader.nV5GreenMask );
+ rOStm.WriteUInt32( aHeader.nV5BlueMask );
+ rOStm.WriteUInt32( aHeader.nV5AlphaMask );
+ rOStm.WriteUInt32( aHeader.nV5CSType );
+
+ rOStm.WriteInt32( aHeader.aV5Endpoints.aXyzRed.aXyzX );
+ rOStm.WriteInt32( aHeader.aV5Endpoints.aXyzRed.aXyzY );
+ rOStm.WriteInt32( aHeader.aV5Endpoints.aXyzRed.aXyzZ );
+ rOStm.WriteInt32( aHeader.aV5Endpoints.aXyzGreen.aXyzX );
+ rOStm.WriteInt32( aHeader.aV5Endpoints.aXyzGreen.aXyzY );
+ rOStm.WriteInt32( aHeader.aV5Endpoints.aXyzGreen.aXyzZ );
+ rOStm.WriteInt32( aHeader.aV5Endpoints.aXyzBlue.aXyzX );
+ rOStm.WriteInt32( aHeader.aV5Endpoints.aXyzBlue.aXyzY );
+ rOStm.WriteInt32( aHeader.aV5Endpoints.aXyzBlue.aXyzZ );
+
+ rOStm.WriteUInt32( aHeader.nV5GammaRed );
+ rOStm.WriteUInt32( aHeader.nV5GammaGreen );
+ rOStm.WriteUInt32( aHeader.nV5GammaBlue );
+ rOStm.WriteUInt32( aHeader.nV5Intent );
+ rOStm.WriteUInt32( aHeader.nV5ProfileData );
+ rOStm.WriteUInt32( aHeader.nV5ProfileSize );
+ rOStm.WriteUInt32( aHeader.nV5Reserved );
+ }
+
+ if(ZCOMPRESS == aHeader.nCompression)
+ {
+ ZCodec aCodec;
+ SvMemoryStream aMemStm(aHeader.nSizeImage + 4096, 65535);
+ sal_uLong nCodedPos(rOStm.Tell());
+ sal_uLong nLastPos(0);
+ sal_uInt32 nCodedSize(0);
+ sal_uInt32 nUncodedSize(0);
+
+ // write uncoded data palette
+ if(aHeader.nColsUsed)
+ {
+ ImplWriteDIBPalette(aMemStm, rAcc);
+ }
+
+ // write uncoded bits
+ bRet = ImplWriteDIBBits(aMemStm, rAcc, pAccAlpha, nCompression, aHeader.nSizeImage);
+
+ // get uncoded size
+ nUncodedSize = aMemStm.Tell();
+
+ // seek over compress info
+ rOStm.SeekRel(12);
+
+ // write compressed data
+ aCodec.BeginCompression(3);
+ aCodec.Write(rOStm, static_cast<sal_uInt8 const *>(aMemStm.GetData()), nUncodedSize);
+ aCodec.EndCompression();
+
+ // update compress info ( coded size, uncoded size, uncoded compression )
+ nLastPos = rOStm.Tell();
+ nCodedSize = nLastPos - nCodedPos - 12;
+ rOStm.Seek(nCodedPos);
+ rOStm.WriteUInt32( nCodedSize ).WriteUInt32( nUncodedSize ).WriteUInt32( nCompression );
+ rOStm.Seek(nLastPos);
+
+ if(bRet)
+ {
+ bRet = (ERRCODE_NONE == rOStm.GetError());
+ }
+ }
+ else
+ {
+ if(aHeader.nColsUsed)
+ {
+ ImplWriteDIBPalette(rOStm, rAcc);
+ }
+
+ bRet = ImplWriteDIBBits(rOStm, rAcc, pAccAlpha, aHeader.nCompression, aHeader.nSizeImage);
+ }
+
+ nEndPos = rOStm.Tell();
+ rOStm.Seek(nImageSizePos);
+ rOStm.WriteUInt32( aHeader.nSizeImage );
+ rOStm.Seek(nEndPos);
+
+ return bRet;
+}
+
+bool ImplWriteDIBFileHeader(SvStream& rOStm, BitmapReadAccess const & rAcc)
+{
+ const sal_uInt32 nPalCount((rAcc.HasPalette() ? rAcc.GetPaletteEntryCount() : isBitfieldCompression(rAcc.GetScanlineFormat()) ? 3UL : 0UL));
+ const sal_uInt32 nOffset(14 + DIBINFOHEADERSIZE + nPalCount * 4UL);
+
+ rOStm.WriteUInt16( 0x4D42 ); // 'MB' from BITMAPFILEHEADER
+ rOStm.WriteUInt32( nOffset + (rAcc.Height() * rAcc.GetScanlineSize()) );
+ rOStm.WriteUInt16( 0 );
+ rOStm.WriteUInt16( 0 );
+ rOStm.WriteUInt32( nOffset );
+
+ return rOStm.GetError() == ERRCODE_NONE;
+}
+
+bool ImplReadDIB(
+ Bitmap& rTarget,
+ AlphaMask* pTargetAlpha,
+ SvStream& rIStm,
+ bool bFileHeader,
+ bool bIsMask=false,
+ bool bMSOFormat=false)
+{
+ const SvStreamEndian nOldFormat(rIStm.GetEndian());
+ const sal_uLong nOldPos(rIStm.Tell());
+ sal_uLong nOffset(0);
+ bool bRet(false);
+
+ rIStm.SetEndian(SvStreamEndian::LITTLE);
+
+ if(bFileHeader)
+ {
+ if(ImplReadDIBFileHeader(rIStm, nOffset))
+ {
+ bRet = ImplReadDIBBody(rIStm, rTarget, nOffset >= DIBV5HEADERSIZE ? pTargetAlpha : nullptr, nOffset, bIsMask, bMSOFormat);
+ }
+ }
+ else
+ {
+ bRet = ImplReadDIBBody(rIStm, rTarget, nullptr, nOffset, bIsMask, bMSOFormat);
+ }
+
+ if(!bRet)
+ {
+ if(!rIStm.GetError())
+ {
+ rIStm.SetError(SVSTREAM_GENERALERROR);
+ }
+
+ rIStm.Seek(nOldPos);
+ }
+
+ rIStm.SetEndian(nOldFormat);
+
+ return bRet;
+}
+
+bool ImplWriteDIB(
+ const Bitmap& rSource,
+ SvStream& rOStm,
+ bool bCompressed,
+ bool bFileHeader)
+{
+ const Size aSizePix(rSource.GetSizePixel());
+ bool bRet(false);
+
+ if(aSizePix.Width() && aSizePix.Height())
+ {
+ Bitmap::ScopedReadAccess pAcc(const_cast< Bitmap& >(rSource));
+ Bitmap::ScopedReadAccess pAccAlpha;
+ const SvStreamEndian nOldFormat(rOStm.GetEndian());
+ const sal_uLong nOldPos(rOStm.Tell());
+
+ rOStm.SetEndian(SvStreamEndian::LITTLE);
+
+ if (pAcc)
+ {
+ if(bFileHeader)
+ {
+ if(ImplWriteDIBFileHeader(rOStm, *pAcc))
+ {
+ bRet = ImplWriteDIBBody(rSource, rOStm, *pAcc, pAccAlpha.get(), bCompressed);
+ }
+ }
+ else
+ {
+ bRet = ImplWriteDIBBody(rSource, rOStm, *pAcc, pAccAlpha.get(), bCompressed);
+ }
+
+ pAcc.reset();
+ }
+
+ pAccAlpha.reset();
+
+ if(!bRet)
+ {
+ rOStm.SetError(SVSTREAM_GENERALERROR);
+ rOStm.Seek(nOldPos);
+ }
+
+ rOStm.SetEndian(nOldFormat);
+ }
+
+ return bRet;
+}
+
+} // unnamed namespace
+
+bool ReadDIB(
+ Bitmap& rTarget,
+ SvStream& rIStm,
+ bool bFileHeader,
+ bool bMSOFormat)
+{
+ return ImplReadDIB(rTarget, nullptr, rIStm, bFileHeader, false, bMSOFormat);
+}
+
+bool ReadDIBBitmapEx(
+ BitmapEx& rTarget,
+ SvStream& rIStm,
+ bool bFileHeader,
+ bool bMSOFormat)
+{
+ Bitmap aBmp;
+ bool bRetval(ImplReadDIB(aBmp, nullptr, rIStm, bFileHeader, /*bMask*/false, bMSOFormat) && !rIStm.GetError());
+
+ if(bRetval)
+ {
+ // base bitmap was read, set as return value and try to read alpha extra-data
+ const sal_uLong nStmPos(rIStm.Tell());
+ sal_uInt32 nMagic1(0);
+ sal_uInt32 nMagic2(0);
+
+ rTarget = BitmapEx(aBmp);
+ rIStm.ReadUInt32( nMagic1 ).ReadUInt32( nMagic2 );
+ bRetval = (0x25091962 == nMagic1) && (0xACB20201 == nMagic2) && !rIStm.GetError();
+
+ if(bRetval)
+ {
+ sal_uInt8 tmp = 0;
+ rIStm.ReadUChar( tmp );
+ TransparentType transparent = static_cast<TransparentType>(tmp);
+ bRetval = !rIStm.GetError();
+
+ if(bRetval)
+ {
+ switch (transparent)
+ {
+ case TransparentType::Bitmap:
+ {
+ Bitmap aMask;
+
+ bRetval = ImplReadDIB(aMask, nullptr, rIStm, true, true);
+
+ if(bRetval)
+ {
+ if(!!aMask)
+ {
+ // do we have an alpha mask?
+ if((8 == aMask.GetBitCount()) && aMask.HasGreyPalette8Bit())
+ {
+ AlphaMask aAlpha;
+
+ // create alpha mask quickly (without greyscale conversion)
+ aAlpha.ImplSetBitmap(aMask);
+ rTarget = BitmapEx(aBmp, aAlpha);
+ }
+ else
+ {
+ rTarget = BitmapEx(aBmp, aMask);
+ }
+ }
+ }
+ break;
+ }
+ case TransparentType::Color:
+ {
+ Color aTransparentColor;
+
+ tools::GenericTypeSerializer aSerializer(rIStm);
+ aSerializer.readColor(aTransparentColor);
+
+ bRetval = !rIStm.GetError();
+
+ if(bRetval)
+ {
+ rTarget = BitmapEx(aBmp, aTransparentColor);
+ }
+ break;
+ }
+ default: break;
+ }
+ }
+ }
+
+ if(!bRetval)
+ {
+ // alpha extra data could not be read; reset, but use base bitmap as result
+ rIStm.ResetError();
+ rIStm.Seek(nStmPos);
+ bRetval = true;
+ }
+ }
+
+ return bRetval;
+}
+
+bool ReadDIBV5(
+ Bitmap& rTarget,
+ AlphaMask& rTargetAlpha,
+ SvStream& rIStm)
+{
+ return ImplReadDIB(rTarget, &rTargetAlpha, rIStm, true);
+}
+
+bool ReadRawDIB(
+ BitmapEx& rTarget,
+ const unsigned char* pBuf,
+ const ScanlineFormat nFormat,
+ const int nHeight,
+ const int nStride)
+{
+ BitmapScopedWriteAccess pWriteAccess(rTarget.maBitmap.AcquireWriteAccess(), rTarget.maBitmap);
+ for (int nRow = 0; nRow < nHeight; ++nRow)
+ {
+ pWriteAccess->CopyScanline(nRow, pBuf + (nStride * nRow), nFormat, nStride);
+ }
+
+ return true;
+}
+
+bool WriteDIB(
+ const Bitmap& rSource,
+ SvStream& rOStm,
+ bool bCompressed,
+ bool bFileHeader)
+{
+ return ImplWriteDIB(rSource, rOStm, bCompressed, bFileHeader);
+}
+
+bool WriteDIB(
+ const BitmapEx& rSource,
+ SvStream& rOStm,
+ bool bCompressed)
+{
+ return ImplWriteDIB(rSource.GetBitmap(), rOStm, bCompressed, /*bFileHeader*/true);
+}
+
+bool WriteDIBBitmapEx(
+ const BitmapEx& rSource,
+ SvStream& rOStm)
+{
+ if(ImplWriteDIB(rSource.GetBitmap(), rOStm, true, true))
+ {
+ rOStm.WriteUInt32( 0x25091962 );
+ rOStm.WriteUInt32( 0xACB20201 );
+ rOStm.WriteUChar( static_cast<sal_uChar>(rSource.meTransparent) );
+
+ if(TransparentType::Bitmap == rSource.meTransparent)
+ {
+ return ImplWriteDIB(rSource.maMask, rOStm, true, true);
+ }
+ else if(TransparentType::Color == rSource.meTransparent)
+ {
+ tools::GenericTypeSerializer aSerializer(rOStm);
+ aSerializer.writeColor(rSource.maTransparentColor);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+sal_uInt32 getDIBV5HeaderSize()
+{
+ return DIBV5HEADERSIZE;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/embeddedfontshelper.cxx b/vcl/source/gdi/embeddedfontshelper.cxx
new file mode 100644
index 000000000..e59f94071
--- /dev/null
+++ b/vcl/source/gdi/embeddedfontshelper.cxx
@@ -0,0 +1,332 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 <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 <outdev.h>
+#include <PhysicalFontCollection.hxx>
+#include <salgdi.hxx>
+#include <sft.hxx>
+
+
+#if ENABLE_EOT
+extern "C"
+{
+namespace libeot
+{
+#include <libeot/libeot.h>
+} // namespace libeot
+} // extern "C"
+#endif
+
+using namespace com::sun::star;
+using namespace vcl;
+
+static void clearDir( const OUString& path )
+{
+ osl::Directory dir( path );
+ if( dir.reset() == osl::Directory::E_None )
+ {
+ for(;;)
+ {
+ osl::DirectoryItem item;
+ if( dir.getNextItem( item ) != osl::Directory::E_None )
+ break;
+ osl::FileStatus status( osl_FileStatus_Mask_FileURL );
+ if( item.getFileStatus( status ) == osl::File::E_None )
+ osl::File::remove( status.getFileURL());
+ }
+ }
+}
+
+void EmbeddedFontsHelper::clearTemporaryFontFiles()
+{
+ OUString path = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}";
+ rtl::Bootstrap::expandMacros( path );
+ path += "/user/temp/embeddedfonts/";
+ clearDir( path + "fromdocs/" );
+ clearDir( path + "fromsystem/" );
+}
+
+bool EmbeddedFontsHelper::addEmbeddedFont( const uno::Reference< io::XInputStream >& stream, const OUString& fontName,
+ const char* extra, std::vector< unsigned char > 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 );
+ for( sal_uInt64 pos = 0;
+ pos < read && keyPos < key.size();
+ ++pos )
+ buffer[ pos ] ^= key[ keyPos++ ];
+ // if eot, don't write the file out yet, since we need to unpack it first.
+ if( !eot && read > 0 )
+ {
+ sal_uInt64 writtenTotal = 0;
+ while( writtenTotal < read )
+ {
+ sal_uInt64 written;
+ file.write( buffer.getConstArray(), read, written );
+ writtenTotal += written;
+ }
+ }
+ fontData.insert( fontData.end(), buffer.getConstArray(), buffer.getConstArray() + read );
+ if( read <= 0 )
+ break;
+ }
+ bool sufficientFontRights(false);
+#if ENABLE_EOT
+ if( eot )
+ {
+ unsigned uncompressedFontSize = 0;
+ unsigned char *nakedPointerToUncompressedFont = nullptr;
+ libeot::EOTMetadata eotMetadata;
+ libeot::EOTError uncompressError =
+ libeot::EOT2ttf_buffer( reinterpret_cast<unsigned char *>(fontData.data()), fontData.size(), &eotMetadata, &nakedPointerToUncompressedFont, &uncompressedFontSize );
+ std::shared_ptr<unsigned char> uncompressedFont( nakedPointerToUncompressedFont, libeot::EOTfreeBuffer );
+ if( uncompressError != libeot::EOT_SUCCESS )
+ {
+ SAL_WARN( "vcl.fonts", "Failed to uncompress font" );
+ osl::File::remove( fileUrl );
+ return false;
+ }
+ sal_uInt64 writtenTotal = 0;
+ while( writtenTotal < uncompressedFontSize )
+ {
+ sal_uInt64 written;
+ if( file.write( uncompressedFont.get() + writtenTotal, uncompressedFontSize - writtenTotal, written ) != osl::File::E_None )
+ {
+ SAL_WARN( "vcl.fonts", "Error writing temporary font file" );
+ osl::File::remove( fileUrl );
+ return false;
+ }
+ writtenTotal += written;
+ }
+ sufficientFontRights = libeot::EOTcanLegallyEdit( &eotMetadata );
+ libeot::EOTfreeMetadata( &eotMetadata );
+ }
+#endif
+
+ if( file.close() != osl::File::E_None )
+ {
+ SAL_WARN( "vcl.fonts", "Writing temporary font file failed" );
+ osl::File::remove( fileUrl );
+ return false;
+ }
+ if( !eot )
+ {
+ sufficientFontRights = sufficientTTFRights(fontData.data(), fontData.size(), FontRights::EditingAllowed);
+ }
+ if( !sufficientFontRights )
+ {
+ // It would be actually better to open the document in read-only mode in this case,
+ // warn the user about this, and provide a button to drop the font(s) in order
+ // to switch to editing.
+ SAL_INFO( "vcl.fonts", "Ignoring embedded font that is not usable for editing" );
+ osl::File::remove( fileUrl );
+ return false;
+ }
+ m_aAccumulatedFonts.emplace_back(std::make_pair(fontName, fileUrl));
+ return true;
+}
+
+namespace
+{
+ struct UpdateFontsGuard
+ {
+ UpdateFontsGuard()
+ {
+ OutputDevice::ImplClearAllFontData(true);
+ }
+
+ ~UpdateFontsGuard()
+ {
+ OutputDevice::ImplRefreshAllFontData(true);
+ }
+ };
+}
+
+void EmbeddedFontsHelper::activateFonts()
+{
+ if (m_aAccumulatedFonts.empty())
+ return;
+ UpdateFontsGuard aUpdateFontsGuard;
+ for (const auto& rEntry : m_aAccumulatedFonts)
+ EmbeddedFontsHelper::activateFont(rEntry.first, rEntry.second);
+ m_aAccumulatedFonts.clear();
+}
+
+OUString EmbeddedFontsHelper::fileUrlForTemporaryFont( const OUString& fontName, const char* extra )
+{
+ OUString path = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}";
+ rtl::Bootstrap::expandMacros( path );
+ path += "/user/temp/embeddedfonts/fromdocs/";
+ osl::Directory::createPath( path );
+ OUString filename = fontName;
+ static int uniqueCounter = 0;
+ if( strcmp( extra, "?" ) == 0 )
+ filename += OUString::number( uniqueCounter++ );
+ else
+ filename += OStringToOUString( extra, RTL_TEXTENCODING_ASCII_US );
+ filename += ".ttf"; // TODO is it always ttf?
+ return path + filename;
+}
+
+void EmbeddedFontsHelper::activateFont( const OUString& fontName, const OUString& fileUrl )
+{
+ OutputDevice *pDevice = Application::GetDefaultDevice();
+ pDevice->AddTempDevFont(fileUrl, fontName);
+}
+
+// Check if it's (legally) allowed to embed the font file into a document
+// (ttf has a flag allowing this). PhysicalFontFace::IsEmbeddable() appears
+// to have a different meaning (guessing from code, IsSubsettable() might
+// possibly mean it's ttf, while IsEmbeddable() might mean it's type1).
+// So just try to open the data as ttf and see.
+bool EmbeddedFontsHelper::sufficientTTFRights( const void* data, 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( const OUString& 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 = 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();
+ PhysicalFontCollection fonts;
+ graphics->GetDevFontList( &fonts );
+ std::unique_ptr< ImplDeviceFontList > fontInfo( fonts.GetDeviceFontList());
+ PhysicalFontFace* selected = nullptr;
+ for( int i = 0;
+ i < fontInfo->Count();
+ ++i )
+ {
+ PhysicalFontFace* f = fontInfo->Get( i );
+ if( f->GetFamilyName() == familyName )
+ {
+ // Ignore comparing text encodings, at least for now. They cannot be trivially compared
+ // (e.g. UCS2 and UTF8 are technically the same characters, just have different encoding,
+ // and just having a unicode font doesn't say what glyphs it actually contains).
+ // It is possible that it still may be needed to do at least some checks here
+ // for some encodings (can one font have more font files for more encodings?).
+ if(( family == FAMILY_DONTKNOW || f->GetFamilyType() == family )
+ && ( italic == ITALIC_DONTKNOW || f->GetItalic() == italic )
+ && ( weight == WEIGHT_DONTKNOW || f->GetWeight() == weight )
+ && ( pitch == PITCH_DONTKNOW || f->GetPitch() == pitch ))
+ { // Exact match, return it immediately.
+ selected = f;
+ break;
+ }
+ if(( f->GetFamilyType() == FAMILY_DONTKNOW || family == FAMILY_DONTKNOW || f->GetFamilyType() == family )
+ && ( f->GetItalic() == ITALIC_DONTKNOW || italic == ITALIC_DONTKNOW || f->GetItalic() == italic )
+ && ( f->GetWeight() == WEIGHT_DONTKNOW || weight == WEIGHT_DONTKNOW || f->GetWeight() == weight )
+ && ( f->GetPitch() == PITCH_DONTKNOW || pitch == PITCH_DONTKNOW || f->GetPitch() == pitch ))
+ { // Some fonts specify 'DONTKNOW' for some things, still a good match, if we don't find a better one.
+ selected = f;
+ }
+ }
+ }
+ if( selected != nullptr )
+ {
+ long size;
+ if (const void* data = graphics->GetEmbedFontData(selected, &size))
+ {
+ if( sufficientTTFRights( data, size, rights ))
+ {
+ osl::File file( url );
+ if( file.open( osl_File_OpenFlag_Write | osl_File_OpenFlag_Create ) == osl::File::E_None )
+ {
+ sal_uInt64 written = 0;
+ sal_uInt64 totalSize = size;
+ bool error = false;
+ while( written < totalSize && !error)
+ {
+ sal_uInt64 nowWritten;
+ switch( file.write( static_cast< const char* >( data ) + written, size - written, nowWritten ))
+ {
+ case osl::File::E_None:
+ written += nowWritten;
+ break;
+ case osl::File::E_AGAIN:
+ case osl::File::E_INTR:
+ break;
+ default:
+ error = true;
+ break;
+ }
+ }
+ file.close();
+ if( error )
+ osl::File::remove( url );
+ else
+ ok = true;
+ }
+ }
+ graphics->FreeEmbedFontData( data, size );
+ }
+ }
+ return ok ? url : "";
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/extoutdevdata.cxx b/vcl/source/gdi/extoutdevdata.cxx
new file mode 100644
index 000000000..49f188c85
--- /dev/null
+++ b/vcl/source/gdi/extoutdevdata.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <vcl/extoutdevdata.hxx>
+
+namespace vcl
+{
+
+ExtOutDevData::~ExtOutDevData()
+{
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/gdimetafiletools.cxx b/vcl/source/gdi/gdimetafiletools.cxx
new file mode 100644
index 000000000..6a74c4d69
--- /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.IsTransparent())
+ {
+ // use given alpha channel
+ aVDev->DrawBitmap(Point(0, 0), rBitmapEx.GetAlpha().GetBitmap());
+ }
+ else
+ {
+ // reset alpha channel
+ aVDev->SetBackground(Wallpaper(COL_BLACK));
+ aVDev->Erase();
+ }
+
+ // transform polygon from clipping to pixel coordinates
+ basegfx::B2DPolyPolygon aPixelPoly(aClipOfBitmap);
+ basegfx::B2DHomMatrix aTransform;
+
+ aTransform.translate(-aLogicBitmapRange.getMinX(), -aLogicBitmapRange.getMinY());
+ aTransform.scale(
+ static_cast< double >(aSizePixel.Width()) / aLogicBitmapRange.getWidth(),
+ static_cast< double >(aSizePixel.Height()) / aLogicBitmapRange.getHeight());
+ aPixelPoly.transform(aTransform);
+
+ // to fill the non-covered parts, use the Xor fill rule of
+ // tools::PolyPolygon painting. Start with an all-covering polygon and
+ // add the clip polygon one
+ basegfx::B2DPolyPolygon aInvertPixelPoly;
+
+ aInvertPixelPoly.append(
+ basegfx::utils::createPolygonFromRect(
+ basegfx::B2DRange(
+ 0.0, 0.0,
+ aSizePixel.Width(), aSizePixel.Height())));
+ aInvertPixelPoly.append(aPixelPoly);
+
+ // paint as alpha
+ aVDev->DrawPolyPolygon(aInvertPixelPoly);
+
+ // get created alpha mask and set defaults
+ AlphaMask aAlpha(
+ aVDev->GetBitmap(
+ Point(0, 0),
+ aSizePixel));
+
+ aAlpha.SetPrefSize(rBitmapEx.GetPrefSize());
+ aAlpha.SetPrefMapMode(rBitmapEx.GetPrefMapMode());
+
+ // add new action replacing the old one
+ rTarget.AddAction(
+ new MetaBmpExScaleAction(
+ Point(
+ basegfx::fround(aLogicBitmapRange.getMinX()),
+ basegfx::fround(aLogicBitmapRange.getMinY())),
+ Size(
+ basegfx::fround(aLogicBitmapRange.getWidth()),
+ basegfx::fround(aLogicBitmapRange.getHeight())),
+ BitmapEx(rBitmapEx.GetBitmap(), aAlpha)));
+
+ return true;
+ }
+
+ void addSvtGraphicStroke(const SvtGraphicStroke& rStroke, GDIMetaFile& rTarget)
+ {
+ // write SvtGraphicFill
+ SvMemoryStream aMemStm;
+ WriteSvtGraphicStroke( aMemStm, rStroke );
+ rTarget.AddAction(
+ new MetaCommentAction(
+ "XPATHSTROKE_SEQ_BEGIN",
+ 0,
+ static_cast< const sal_uInt8* >(aMemStm.GetData()),
+ aMemStm.TellEnd()));
+ }
+
+ void addSvtGraphicFill(const SvtGraphicFill &rFilling, GDIMetaFile& rTarget)
+ {
+ // write SvtGraphicFill
+ SvMemoryStream aMemStm;
+ WriteSvtGraphicFill( aMemStm, rFilling );
+ rTarget.AddAction(
+ new MetaCommentAction(
+ "XPATHFILL_SEQ_BEGIN",
+ 0,
+ static_cast< const sal_uInt8* >(aMemStm.GetData()),
+ aMemStm.TellEnd()));
+ }
+} // end of anonymous namespace
+
+// #i121267# Tooling to internally clip geometry against internal clip regions
+
+void clipMetafileContentAgainstOwnRegions(GDIMetaFile& rSource)
+{
+ const sal_uLong nObjCount(rSource.GetActionSize());
+
+ if(!nObjCount)
+ {
+ return;
+ }
+
+ // prepare target data container and push/pop stack data
+ GDIMetaFile aTarget;
+ bool bChanged(false);
+ std::vector< basegfx::B2DPolyPolygon > aClips;
+ std::vector< 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 long aHorMove(pA->GetHorzMove());
+ const 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 PushFlags nFlags(pA->GetFlags());
+
+ aPushFlags.push_back(nFlags);
+
+ if(nFlags & PushFlags::CLIPREGION)
+ {
+ aClips.push_back(aClips.back());
+ }
+
+ if(nFlags & PushFlags::MAPMODE)
+ {
+ aMapModes.push_back(aMapModes.back());
+ }
+ break;
+ }
+
+ case MetaActionType::POP :
+ {
+
+ if(!aPushFlags.empty())
+ {
+ const PushFlags nFlags(aPushFlags.back());
+ aPushFlags.pop_back();
+
+ if(nFlags & PushFlags::CLIPREGION)
+ {
+ if(aClips.size() > 1)
+ {
+ aClips.pop_back();
+ }
+ else
+ {
+ OSL_ENSURE(false, "Wrong POP() in ClipRegions (!)");
+ }
+ }
+
+ if(nFlags & 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::max(0.0, std::min(1.0, fRadiusX));
+ fRadiusY = std::max(0.0, std::min(1.0, fRadiusY));
+
+ aOutline = basegfx::utils::createPolygonFromRect(aRange, fRadiusX, fRadiusY);
+ }
+ else
+ {
+ aOutline = basegfx::utils::createPolygonFromRect(aRange);
+ }
+
+ bDone = handleGeometricContent(
+ aClips.back(),
+ basegfx::B2DPolyPolygon(aOutline),
+ aTarget,
+ false); // stroke
+ }
+ break;
+ }
+
+ case MetaActionType::ELLIPSE :
+ {
+ const MetaEllipseAction* pA = static_cast< const MetaEllipseAction* >(pAction);
+ const tools::Rectangle& rRect = pA->GetRect();
+
+ if(rRect.IsEmpty())
+ {
+ bDone = true;
+ }
+ else
+ {
+ const basegfx::B2DRange aRange(vcl::unotools::b2DRectangleFromRectangle(rRect));
+
+ bDone = handleGeometricContent(
+ aClips.back(),
+ basegfx::B2DPolyPolygon(
+ basegfx::utils::createPolygonFromEllipse(
+ aRange.getCenter(),
+ aRange.getWidth() * 0.5,
+ aRange.getHeight() * 0.5)),
+ aTarget,
+ false); // stroke
+ }
+ break;
+ }
+
+ case MetaActionType::ARC :
+ {
+ const MetaArcAction* pA = static_cast< const MetaArcAction* >(pAction);
+ const tools::Rectangle& rRect = pA->GetRect();
+
+ if(rRect.IsEmpty())
+ {
+ bDone = true;
+ }
+ else
+ {
+ const tools::Polygon aToolsPoly(
+ rRect,
+ pA->GetStartPoint(),
+ pA->GetEndPoint(),
+ PolyStyle::Arc);
+
+ bDone = handleGeometricContent(
+ aClips.back(),
+ basegfx::B2DPolyPolygon(aToolsPoly.getB2DPolygon()),
+ aTarget,
+ true); // stroke
+ }
+ break;
+ }
+
+ case MetaActionType::PIE :
+ {
+ const MetaPieAction* pA = static_cast< const MetaPieAction* >(pAction);
+ const tools::Rectangle& rRect = pA->GetRect();
+
+ if(rRect.IsEmpty())
+ {
+ bDone = true;
+ }
+ else
+ {
+ const tools::Polygon aToolsPoly(
+ rRect,
+ pA->GetStartPoint(),
+ pA->GetEndPoint(),
+ PolyStyle::Pie);
+
+ bDone = handleGeometricContent(
+ aClips.back(),
+ basegfx::B2DPolyPolygon(aToolsPoly.getB2DPolygon()),
+ aTarget,
+ false); // stroke
+ }
+ break;
+ }
+
+ case MetaActionType::CHORD :
+ {
+ const MetaChordAction* pA = static_cast< const MetaChordAction* >(pAction);
+ const tools::Rectangle& rRect = pA->GetRect();
+
+ if(rRect.IsEmpty())
+ {
+ bDone = true;
+ }
+ else
+ {
+ const tools::Polygon aToolsPoly(
+ rRect,
+ pA->GetStartPoint(),
+ pA->GetEndPoint(),
+ PolyStyle::Chord);
+
+ bDone = handleGeometricContent(
+ aClips.back(),
+ basegfx::B2DPolyPolygon(aToolsPoly.getB2DPolygon()),
+ aTarget,
+ false); // stroke
+ }
+ break;
+ }
+
+ case MetaActionType::POLYLINE :
+ {
+ const MetaPolyLineAction* pA = static_cast< const MetaPolyLineAction* >(pAction);
+
+ bDone = handleGeometricContent(
+ aClips.back(),
+ basegfx::B2DPolyPolygon(pA->GetPolygon().getB2DPolygon()),
+ aTarget,
+ true); // stroke
+ break;
+ }
+
+ case MetaActionType::POLYGON :
+ {
+ const MetaPolygonAction* pA = static_cast< const MetaPolygonAction* >(pAction);
+
+ bDone = handleGeometricContent(
+ aClips.back(),
+ basegfx::B2DPolyPolygon(pA->GetPolygon().getB2DPolygon()),
+ aTarget,
+ false); // stroke
+ break;
+ }
+
+ case MetaActionType::POLYPOLYGON :
+ {
+ const MetaPolyPolygonAction* pA = static_cast< const MetaPolyPolygonAction* >(pAction);
+ const tools::PolyPolygon& rPoly = pA->GetPolyPolygon();
+
+ bDone = handleGeometricContent(
+ aClips.back(),
+ rPoly.getB2DPolyPolygon(),
+ aTarget,
+ false); // stroke
+ break;
+ }
+
+ // bitmap actions, create BitmapEx with alpha channel derived
+ // from clipping
+
+ case MetaActionType::BMPEX :
+ {
+ const MetaBmpExAction* pA = static_cast< const MetaBmpExAction* >(pAction);
+ const BitmapEx& rBitmapEx = pA->GetBitmapEx();
+
+ // the logical size depends on the PrefSize of the given bitmap in
+ // combination with the current MapMode
+ Size aLogicalSize(rBitmapEx.GetPrefSize());
+
+ if(MapUnit::MapPixel == rBitmapEx.GetPrefMapMode().GetMapUnit())
+ {
+ aLogicalSize = Application::GetDefaultDevice()->PixelToLogic(aLogicalSize, aMapModes.back());
+ }
+ else
+ {
+ aLogicalSize = OutputDevice::LogicToLogic(aLogicalSize, rBitmapEx.GetPrefMapMode(), aMapModes.back());
+ }
+
+ bDone = handleBitmapContent(
+ aClips.back(),
+ pA->GetPoint(),
+ aLogicalSize,
+ rBitmapEx,
+ aTarget);
+ break;
+ }
+
+ case MetaActionType::BMP :
+ {
+ const MetaBmpAction* pA = static_cast< const MetaBmpAction* >(pAction);
+ const Bitmap& rBitmap = pA->GetBitmap();
+
+ // the logical size depends on the PrefSize of the given bitmap in
+ // combination with the current MapMode
+ Size aLogicalSize(rBitmap.GetPrefSize());
+
+ if(MapUnit::MapPixel == rBitmap.GetPrefMapMode().GetMapUnit())
+ {
+ aLogicalSize = Application::GetDefaultDevice()->PixelToLogic(aLogicalSize, aMapModes.back());
+ }
+ else
+ {
+ aLogicalSize = OutputDevice::LogicToLogic(aLogicalSize, rBitmap.GetPrefMapMode(), aMapModes.back());
+ }
+
+ bDone = handleBitmapContent(
+ aClips.back(),
+ pA->GetPoint(),
+ aLogicalSize,
+ BitmapEx(rBitmap),
+ aTarget);
+ break;
+ }
+
+ case MetaActionType::BMPEXSCALE :
+ {
+ const MetaBmpExScaleAction* pA = static_cast< const MetaBmpExScaleAction* >(pAction);
+
+ bDone = handleBitmapContent(
+ aClips.back(),
+ pA->GetPoint(),
+ pA->GetSize(),
+ pA->GetBitmapEx(),
+ aTarget);
+ break;
+ }
+
+ case MetaActionType::BMPSCALE :
+ {
+ const MetaBmpScaleAction* pA = static_cast< const MetaBmpScaleAction* >(pAction);
+
+ bDone = handleBitmapContent(
+ aClips.back(),
+ pA->GetPoint(),
+ pA->GetSize(),
+ BitmapEx(pA->GetBitmap()),
+ aTarget);
+ break;
+ }
+
+ case MetaActionType::BMPEXSCALEPART :
+ {
+ const MetaBmpExScalePartAction* pA = static_cast< const MetaBmpExScalePartAction* >(pAction);
+ const BitmapEx& rBitmapEx = pA->GetBitmapEx();
+
+ if(rBitmapEx.IsEmpty())
+ {
+ // empty content
+ bDone = true;
+ }
+ else
+ {
+ BitmapEx aCroppedBitmapEx(rBitmapEx);
+ const tools::Rectangle aCropRectangle(pA->GetSrcPoint(), pA->GetSrcSize());
+
+ if(aCropRectangle.IsEmpty())
+ {
+ // empty content
+ bDone = true;
+ }
+ else
+ {
+ aCroppedBitmapEx.Crop(aCropRectangle);
+ bDone = handleBitmapContent(
+ aClips.back(),
+ pA->GetDestPoint(),
+ pA->GetDestSize(),
+ aCroppedBitmapEx,
+ aTarget);
+ }
+ }
+ break;
+ }
+
+ case MetaActionType::BMPSCALEPART :
+ {
+ const MetaBmpScalePartAction* pA = static_cast< const MetaBmpScalePartAction* >(pAction);
+ const Bitmap& rBitmap = pA->GetBitmap();
+
+ if(rBitmap.IsEmpty())
+ {
+ // empty content
+ bDone = true;
+ }
+ else
+ {
+ Bitmap aCroppedBitmap(rBitmap);
+ const tools::Rectangle aCropRectangle(pA->GetSrcPoint(), pA->GetSrcSize());
+
+ if(aCropRectangle.IsEmpty())
+ {
+ // empty content
+ bDone = true;
+ }
+ else
+ {
+ aCroppedBitmap.Crop(aCropRectangle);
+ bDone = handleBitmapContent(
+ aClips.back(),
+ pA->GetDestPoint(),
+ pA->GetDestSize(),
+ BitmapEx(aCroppedBitmap),
+ aTarget);
+ }
+ }
+ break;
+ }
+
+ // need to handle all those 'hacks' which hide data in comments
+
+ case MetaActionType::COMMENT :
+ {
+ const MetaCommentAction* pA = static_cast< const MetaCommentAction* >(pAction);
+ const OString& rComment = pA->GetComment();
+
+ if(rComment.equalsIgnoreAsciiCase("XGRAD_SEQ_BEGIN"))
+ {
+ // nothing to do; this just means that between here and XGRAD_SEQ_END
+ // exists a MetaActionType::GRADIENTEX mixed with Xor-tricked painting
+ // commands. This comment is used to scan over these and filter for
+ // the gradient action. It is needed to support MetaActionType::GRADIENTEX
+ // in this processor to solve usages.
+ }
+ else if(rComment.equalsIgnoreAsciiCase("XPATHFILL_SEQ_BEGIN"))
+ {
+ SvtGraphicFill aFilling;
+ tools::PolyPolygon aPath;
+
+ { // read SvtGraphicFill
+ SvMemoryStream aMemStm(const_cast<sal_uInt8 *>(pA->GetData()), pA->GetDataSize(),StreamMode::READ);
+ ReadSvtGraphicFill( aMemStm, aFilling );
+ }
+
+ aFilling.getPath(aPath);
+
+ if(aPath.Count())
+ {
+ const basegfx::B2DPolyPolygon aSource(aPath.getB2DPolyPolygon());
+ const basegfx::B2DPolyPolygon aResult(
+ basegfx::utils::clipPolyPolygonOnPolyPolygon(
+ aSource,
+ aClips.back(),
+ true, // inside
+ false)); // stroke
+
+ if(aResult.count())
+ {
+ if(aResult != aSource)
+ {
+ // add clipped geometry
+ aFilling.setPath(tools::PolyPolygon(aResult));
+ addSvtGraphicFill(aFilling, aTarget);
+ bDone = true;
+ }
+ }
+ else
+ {
+ // exchange with empty polygon
+ aFilling.setPath(tools::PolyPolygon());
+ addSvtGraphicFill(aFilling, aTarget);
+ bDone = true;
+ }
+ }
+ }
+ else if(rComment.equalsIgnoreAsciiCase("XPATHSTROKE_SEQ_BEGIN"))
+ {
+ SvtGraphicStroke aStroke;
+ tools::Polygon aPath;
+
+ { // read SvtGraphicFill
+ SvMemoryStream aMemStm(const_cast<sal_uInt8 *>(pA->GetData()), pA->GetDataSize(),StreamMode::READ);
+ ReadSvtGraphicStroke( aMemStm, aStroke );
+ }
+
+ aStroke.getPath(aPath);
+
+ if(aPath.GetSize())
+ {
+ const basegfx::B2DPolygon aSource(aPath.getB2DPolygon());
+ const basegfx::B2DPolyPolygon aResult(
+ basegfx::utils::clipPolygonOnPolyPolygon(
+ aSource,
+ aClips.back(),
+ true, // inside
+ true)); // stroke
+
+ if(aResult.count())
+ {
+ if(aResult.count() > 1 || aResult.getB2DPolygon(0) != aSource)
+ {
+ // add clipped geometry
+ for(auto const& rB2DPolygon : aResult)
+ {
+ aStroke.setPath(tools::Polygon(rB2DPolygon));
+ addSvtGraphicStroke(aStroke, aTarget);
+ }
+
+ bDone = true;
+ }
+ }
+ else
+ {
+ // exchange with empty polygon
+ aStroke.setPath(tools::Polygon());
+ addSvtGraphicStroke(aStroke, aTarget);
+ bDone = true;
+ }
+
+ }
+ }
+ break;
+ }
+
+ // need to handle gradient fills (hopefully only unrotated ones)
+
+ case MetaActionType::GRADIENT :
+ {
+ const MetaGradientAction* pA = static_cast< const MetaGradientAction* >(pAction);
+ const tools::Rectangle& rRect = pA->GetRect();
+
+ if(rRect.IsEmpty())
+ {
+ bDone = true;
+ }
+ else
+ {
+ bDone = handleGradientContent(
+ aClips.back(),
+ basegfx::B2DPolyPolygon(
+ basegfx::utils::createPolygonFromRect(
+ vcl::unotools::b2DRectangleFromRectangle(rRect))),
+ pA->GetGradient(),
+ aTarget);
+ }
+
+ break;
+ }
+
+ case MetaActionType::GRADIENTEX :
+ {
+ const MetaGradientExAction* pA = static_cast< const MetaGradientExAction* >(pAction);
+ const tools::PolyPolygon& rPolyPoly = pA->GetPolyPolygon();
+
+ bDone = handleGradientContent(
+ aClips.back(),
+ rPolyPoly.getB2DPolyPolygon(),
+ pA->GetGradient(),
+ aTarget);
+ break;
+ }
+
+ // not (yet) supported actions
+
+ // MetaActionType::NONE
+ // MetaActionType::TEXT
+ // MetaActionType::TEXTARRAY
+ // MetaActionType::STRETCHTEXT
+ // MetaActionType::TEXTRECT
+ // MetaActionType::MASK
+ // MetaActionType::MASKSCALE
+ // MetaActionType::MASKSCALEPART
+ // MetaActionType::HATCH
+ // MetaActionType::WALLPAPER
+ // MetaActionType::FILLCOLOR
+ // MetaActionType::TEXTCOLOR
+ // MetaActionType::TEXTFILLCOLOR
+ // MetaActionType::TEXTALIGN
+ // MetaActionType::MAPMODE
+ // MetaActionType::FONT
+ // MetaActionType::Transparent
+ // MetaActionType::EPS
+ // MetaActionType::REFPOINT
+ // MetaActionType::TEXTLINECOLOR
+ // MetaActionType::TEXTLINE
+ // MetaActionType::FLOATTRANSPARENT
+ // MetaActionType::LAYOUTMODE
+ // MetaActionType::TEXTLANGUAGE
+ // MetaActionType::OVERLINECOLOR
+
+ // if an action is not handled at all, it will simply get copied to the
+ // target (see below). This is the default for all non-implemented actions
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ if(bDone)
+ {
+ bChanged = true;
+ }
+ else
+ {
+ aTarget.AddAction(const_cast< MetaAction* >(pAction));
+ }
+ }
+
+ if(bChanged)
+ {
+ // when changed, copy back and do not forget to set MapMode
+ // and PrefSize
+ aTarget.SetPrefMapMode(rSource.GetPrefMapMode());
+ aTarget.SetPrefSize(rSource.GetPrefSize());
+ rSource = aTarget;
+ }
+}
+
+bool usesClipActions(const GDIMetaFile& rSource)
+{
+ const sal_uLong nObjCount(rSource.GetActionSize());
+
+ for(sal_uLong i(0); i < nObjCount; ++i)
+ {
+ const MetaAction* pAction(rSource.GetAction(i));
+ const MetaActionType nType(pAction->GetType());
+
+ switch(nType)
+ {
+ case MetaActionType::CLIPREGION :
+ case MetaActionType::ISECTRECTCLIPREGION :
+ case MetaActionType::ISECTREGIONCLIPREGION :
+ case MetaActionType::MOVECLIPREGION :
+ {
+ return true;
+ }
+
+ default: break;
+ }
+ }
+
+ return false;
+}
+
+MetafileAccessor::~MetafileAccessor()
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/gdimtf.cxx b/vcl/source/gdi/gdimtf.cxx
new file mode 100644
index 000000000..46a145750
--- /dev/null
+++ b/vcl/source/gdi/gdimtf.cxx
@@ -0,0 +1,2866 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <cstdlib>
+#include <memory>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+#include <tools/diagnose_ex.h>
+#include <tools/helpers.hxx>
+#include <tools/stream.hxx>
+#include <tools/vcompat.hxx>
+#include <tools/fract.hxx>
+#include <vcl/BitmapPalette.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/window.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/graphictools.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <vcl/canvastools.hxx>
+#include <vcl/mtfxmldump.hxx>
+
+#include <svmconverter.hxx>
+#include <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;
+
+#define GAMMA( _def_cVal, _def_InvGamma ) (static_cast<sal_uInt8>(MinMax(FRound(pow( _def_cVal/255.0,_def_InvGamma)*255.0),0,255)))
+
+namespace {
+
+struct ImplColAdjustParam
+{
+ std::unique_ptr<sal_uInt8[]> pMapR;
+ std::unique_ptr<sal_uInt8[]> pMapG;
+ std::unique_ptr<sal_uInt8[]> pMapB;
+};
+
+struct ImplBmpAdjustParam
+{
+ short nLuminancePercent;
+ short nContrastPercent;
+ short nChannelRPercent;
+ short nChannelGPercent;
+ short nChannelBPercent;
+ double fGamma;
+ bool bInvert;
+};
+
+struct ImplColConvertParam
+{
+ MtfConversion eConversion;
+};
+
+struct ImplBmpConvertParam
+{
+ BmpConversion eConversion;
+};
+
+struct ImplColMonoParam
+{
+ Color aColor;
+};
+
+struct ImplBmpMonoParam
+{
+ Color aColor;
+};
+
+struct ImplColReplaceParam
+{
+ std::unique_ptr<sal_uLong[]> pMinR;
+ std::unique_ptr<sal_uLong[]> pMaxR;
+ std::unique_ptr<sal_uLong[]> pMinG;
+ std::unique_ptr<sal_uLong[]> pMaxG;
+ std::unique_ptr<sal_uLong[]> pMinB;
+ std::unique_ptr<sal_uLong[]> pMaxB;
+ const Color * pDstCols;
+ sal_uLong nCount;
+};
+
+struct ImplBmpReplaceParam
+{
+ const Color* pSrcCols;
+ const Color* pDstCols;
+ sal_uLong nCount;
+};
+
+}
+
+GDIMetaFile::GDIMetaFile() :
+ m_nCurrentActionElement( 0 ),
+ m_aPrefSize ( 1, 1 ),
+ m_pPrev ( nullptr ),
+ m_pNext ( nullptr ),
+ m_pOutDev ( nullptr ),
+ m_bPause ( false ),
+ m_bRecord ( false ),
+ m_bUseCanvas ( false )
+{
+}
+
+GDIMetaFile::GDIMetaFile( const GDIMetaFile& rMtf ) :
+ m_nCurrentActionElement( rMtf.m_nCurrentActionElement ),
+ m_aPrefMapMode ( rMtf.m_aPrefMapMode ),
+ m_aPrefSize ( rMtf.m_aPrefSize ),
+ m_pPrev ( rMtf.m_pPrev ),
+ m_pNext ( rMtf.m_pNext ),
+ m_pOutDev ( nullptr ),
+ m_bPause ( false ),
+ m_bRecord ( false ),
+ m_bUseCanvas ( rMtf.m_bUseCanvas )
+{
+ for( size_t i = 0, n = rMtf.GetActionSize(); i < n; ++i )
+ {
+ m_aList.push_back( rMtf.GetAction( i ) );
+ }
+
+ if( rMtf.m_bRecord )
+ {
+ Record( rMtf.m_pOutDev );
+
+ if ( rMtf.m_bPause )
+ Pause( true );
+ }
+}
+
+GDIMetaFile::~GDIMetaFile()
+{
+ Clear();
+}
+
+bool GDIMetaFile::HasTransparentActions() const
+{
+ MetaAction* pCurrAct;
+
+ // watch for transparent drawing actions
+ for(pCurrAct = const_cast<GDIMetaFile*>(this)->FirstAction();
+ pCurrAct;
+ pCurrAct = const_cast<GDIMetaFile*>(this)->NextAction())
+ {
+ // #i10613# determine if the action is transparency capable
+
+ // #107169# Also examine metafiles with masked bitmaps in
+ // detail. Further down, this is optimized in such a way
+ // that there's no unnecessary painting of masked bitmaps
+ // (which are _always_ subdivided into rectangular regions
+ // of uniform opacity): if a masked bitmap is printed over
+ // empty background, we convert to a plain bitmap with
+ // white background.
+ if (pCurrAct->IsTransparent())
+ return true;
+ }
+
+ return false;
+}
+
+size_t GDIMetaFile::GetActionSize() const
+{
+ return m_aList.size();
+}
+
+MetaAction* GDIMetaFile::GetAction( size_t nAction ) const
+{
+ return (nAction < m_aList.size()) ? m_aList[ nAction ].get() : nullptr;
+}
+
+MetaAction* GDIMetaFile::FirstAction()
+{
+ m_nCurrentActionElement = 0;
+ return m_aList.empty() ? nullptr : m_aList[ 0 ].get();
+}
+
+MetaAction* GDIMetaFile::NextAction()
+{
+ return ( m_nCurrentActionElement + 1 < m_aList.size() ) ? m_aList[ ++m_nCurrentActionElement ].get() : nullptr;
+}
+
+void GDIMetaFile::ReplaceAction( rtl::Reference<MetaAction> pAction, size_t nAction )
+{
+ if ( nAction >= m_aList.size() )
+ {
+ return;
+ }
+ //fdo#39995 This doesn't increment the incoming action ref-count nor does it
+ //decrement the outgoing action ref-count
+ std::swap(pAction, m_aList[nAction]);
+}
+
+GDIMetaFile& GDIMetaFile::operator=( const GDIMetaFile& rMtf )
+{
+ if( this != &rMtf )
+ {
+ Clear();
+
+ // Increment RefCount of MetaActions
+ for( size_t i = 0, n = rMtf.GetActionSize(); i < n; ++i )
+ {
+ m_aList.push_back( rMtf.GetAction( i ) );
+ }
+
+ m_aPrefMapMode = rMtf.m_aPrefMapMode;
+ m_aPrefSize = rMtf.m_aPrefSize;
+ m_pPrev = rMtf.m_pPrev;
+ m_pNext = rMtf.m_pNext;
+ m_pOutDev = nullptr;
+ m_bPause = false;
+ m_bRecord = false;
+ m_bUseCanvas = rMtf.m_bUseCanvas;
+
+ if( rMtf.m_bRecord )
+ {
+ Record( rMtf.m_pOutDev );
+
+ if( rMtf.m_bPause )
+ Pause( true );
+ }
+ }
+
+ return *this;
+}
+
+bool GDIMetaFile::operator==( const GDIMetaFile& rMtf ) const
+{
+ const size_t nObjCount = m_aList.size();
+ bool bRet = false;
+
+ if( this == &rMtf )
+ bRet = true;
+ else if( rMtf.GetActionSize() == nObjCount &&
+ rMtf.GetPrefSize() == m_aPrefSize &&
+ rMtf.GetPrefMapMode() == m_aPrefMapMode )
+ {
+ bRet = true;
+
+ for( size_t n = 0; n < nObjCount; n++ )
+ {
+ if( m_aList[ n ] != rMtf.GetAction( n ) )
+ {
+ bRet = false;
+ break;
+ }
+ }
+ }
+
+ return bRet;
+}
+
+void GDIMetaFile::Clear()
+{
+ if( m_bRecord )
+ Stop();
+
+ m_aList.clear();
+}
+
+void GDIMetaFile::Linker( OutputDevice* pOut, bool bLink )
+{
+ if( bLink )
+ {
+ m_pNext = nullptr;
+ m_pPrev = pOut->GetConnectMetaFile();
+ pOut->SetConnectMetaFile( this );
+
+ if( m_pPrev )
+ m_pPrev->m_pNext = this;
+ }
+ else
+ {
+ if( m_pNext )
+ {
+ m_pNext->m_pPrev = m_pPrev;
+
+ if( m_pPrev )
+ m_pPrev->m_pNext = m_pNext;
+ }
+ else
+ {
+ if( m_pPrev )
+ m_pPrev->m_pNext = nullptr;
+
+ pOut->SetConnectMetaFile( m_pPrev );
+ }
+
+ m_pPrev = nullptr;
+ m_pNext = nullptr;
+ }
+}
+
+void GDIMetaFile::Record( OutputDevice* pOut )
+{
+ if( m_bRecord )
+ Stop();
+
+ m_nCurrentActionElement = m_aList.empty() ? 0 : (m_aList.size() - 1);
+ m_pOutDev = pOut;
+ m_bRecord = true;
+ Linker( pOut, true );
+}
+
+void GDIMetaFile::Play( GDIMetaFile& rMtf )
+{
+ if ( !m_bRecord && !rMtf.m_bRecord )
+ {
+ MetaAction* pAction = GetCurAction();
+ const size_t nObjCount = m_aList.size();
+
+ rMtf.UseCanvas( rMtf.GetUseCanvas() || m_bUseCanvas );
+
+ for( size_t nCurPos = m_nCurrentActionElement; nCurPos < nObjCount; nCurPos++ )
+ {
+ if( pAction )
+ {
+ rMtf.AddAction( pAction );
+ }
+
+ pAction = NextAction();
+ }
+ }
+}
+
+void GDIMetaFile::Play( OutputDevice* pOut, size_t nPos )
+{
+ if( !m_bRecord )
+ {
+ MetaAction* pAction = GetCurAction();
+ const size_t nObjCount = m_aList.size();
+ size_t nSyncCount = ( pOut->GetOutDevType() == OUTDEV_WINDOW ) ? 0x000000ff : 0xffffffff;
+
+ 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.
+ pOut->Push( PushFlags::TEXTLAYOUTMODE|PushFlags::TEXTLANGUAGE );
+ pOut->SetLayoutMode( ComplexTextLayoutFlags::Default );
+ pOut->SetDigitLanguage( LANGUAGE_SYSTEM );
+
+ SAL_INFO( "vcl.gdi", "GDIMetaFile::Play on device of size: " << pOut->GetOutputSizePixel().Width() << " " << pOut->GetOutputSizePixel().Height());
+
+ if( !ImplPlayWithRenderer( pOut, Point(0,0), pOut->GetOutputSize() ) ) {
+ size_t i = 0;
+ for( size_t nCurPos = m_nCurrentActionElement; nCurPos < nPos; nCurPos++ )
+ {
+ if( pAction )
+ {
+ pAction->Execute( pOut );
+
+ // flush output from time to time
+ if( i++ > nSyncCount )
+ {
+ static_cast<vcl::Window*>( pOut )->Flush();
+ i = 0;
+ }
+ }
+
+ pAction = NextAction();
+ }
+ }
+ pOut->Pop();
+ }
+}
+
+bool GDIMetaFile::ImplPlayWithRenderer( OutputDevice* pOut, const Point& rPos, Size rLogicDestSize )
+{
+ if (!m_bUseCanvas)
+ return false;
+
+ Size rDestSize( pOut->LogicToPixel( rLogicDestSize ) );
+
+ const vcl::Window* win = dynamic_cast <vcl::Window*> ( pOut );
+
+ if (!win)
+ win = Application::GetActiveTopWindow();
+ if (!win)
+ win = Application::GetFirstTopLevelWindow();
+
+ if (!win)
+ return false;
+
+ try
+ {
+ uno::Reference<rendering::XCanvas> xCanvas = win->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 (pOut->GetMapMode().GetMapUnit() == MapUnit::MapPixel)
+ pOut->DrawBitmapEx( rPos, aBitmapEx );
+ else
+ pOut->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* pOut, const Point& rPos,
+ const Size& rSize )
+{
+ MapMode aDrawMap( GetPrefMapMode() );
+ Size aDestSize( pOut->LogicToPixel( rSize ) );
+
+ if( !aDestSize.Width() || !aDestSize.Height() )
+ return;
+
+ GDIMetaFile* pMtf = pOut->GetConnectMetaFile();
+
+ if( ImplPlayWithRenderer( pOut, rPos, rSize ) )
+ return;
+
+ Size aTmpPrefSize( pOut->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(); aDrawMap.SetScaleX( aScaleX );
+ aScaleY *= aDrawMap.GetScaleY(); 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 pOut, 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( pOut->GetPixelOffset() );
+ const Size aEmptySize;
+ pOut->SetPixelOffset( aEmptySize );
+ aDrawMap.SetOrigin( pOut->PixelToLogic( pOut->LogicToPixel( rPos ), aDrawMap ) );
+ pOut->SetPixelOffset( rOldOffset );
+
+ pOut->Push();
+
+ if ( pMtf && pMtf->IsRecord() && ( pOut->GetOutDevType() != OUTDEV_PRINTER ) )
+ pOut->SetRelativeMapMode( aDrawMap );
+ else
+ pOut->SetMapMode( aDrawMap );
+
+ // #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.
+ pOut->SetLayoutMode( ComplexTextLayoutFlags::Default );
+ pOut->SetDigitLanguage( LANGUAGE_SYSTEM );
+
+ Play( pOut );
+
+ pOut->Pop();
+
+}
+
+void GDIMetaFile::Pause( bool _bPause )
+{
+ if( m_bRecord )
+ {
+ 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() );
+ 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( long nX, 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( long nX, long nY, long nDPIX, 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<long>(aOffset.Width() * static_cast<double>(aMap.GetScaleX())) );
+ aOffset.setHeight( static_cast<long>(aOffset.Height() * static_cast<double>(aMap.GetScaleY())) );
+ }
+ else
+ aOffset = OutputDevice::LogicToLogic( aBaseOffset, GetPrefMapMode(), aMapVDev->GetMapMode() );
+ }
+
+ pModAct->Move( aOffset.Width(), aOffset.Height() );
+ }
+}
+
+void GDIMetaFile::Scale( double fScaleX, double fScaleY )
+{
+ for( MetaAction* pAct = FirstAction(); pAct; pAct = NextAction() )
+ {
+ MetaAction* pModAct;
+
+ if( pAct->GetRefCount() > 1 )
+ {
+ m_aList[ m_nCurrentActionElement ] = pAct->Clone();
+ pModAct = m_aList[ m_nCurrentActionElement ].get();
+ }
+ else
+ pModAct = pAct;
+
+ pModAct->Scale( fScaleX, fScaleY );
+ }
+
+ m_aPrefSize.setWidth( FRound( m_aPrefSize.Width() * fScaleX ) );
+ m_aPrefSize.setHeight( FRound( m_aPrefSize.Height() * fScaleY ) );
+}
+
+void GDIMetaFile::Scale( const Fraction& rScaleX, const Fraction& rScaleY )
+{
+ Scale( static_cast<double>(rScaleX), static_cast<double>(rScaleY) );
+}
+
+void GDIMetaFile::Clip( const tools::Rectangle& i_rClipRect )
+{
+ tools::Rectangle aCurRect( i_rClipRect );
+ ScopedVclPtrInstance< VirtualDevice > aMapVDev;
+
+ aMapVDev->EnableOutput( false );
+ aMapVDev->SetMapMode( GetPrefMapMode() );
+
+ for( MetaAction* pAct = FirstAction(); pAct; pAct = NextAction() )
+ {
+ const MetaActionType nType = pAct->GetType();
+
+ if( ( MetaActionType::MAPMODE == nType ) ||
+ ( MetaActionType::PUSH == nType ) ||
+ ( MetaActionType::POP == nType ) )
+ {
+ pAct->Execute( aMapVDev.get() );
+ aCurRect = OutputDevice::LogicToLogic( i_rClipRect, GetPrefMapMode(), aMapVDev->GetMapMode() );
+ }
+ else if( nType == MetaActionType::CLIPREGION )
+ {
+ MetaClipRegionAction* pOldAct = static_cast<MetaClipRegionAction*>(pAct);
+ vcl::Region aNewReg( aCurRect );
+ if( pOldAct->IsClipping() )
+ aNewReg.Intersect( pOldAct->GetRegion() );
+ MetaClipRegionAction* pNewAct = new MetaClipRegionAction( aNewReg, true );
+ m_aList[ m_nCurrentActionElement ] = pNewAct;
+ }
+ }
+}
+
+Point GDIMetaFile::ImplGetRotatedPoint( const Point& rPt, const Point& rRotatePt,
+ const Size& rOffset, double fSin, double fCos )
+{
+ const long nX = rPt.X() - rRotatePt.X();
+ const long nY = rPt.Y() - rRotatePt.Y();
+
+ return Point( FRound( fCos * nX + fSin * nY ) + rRotatePt.X() + rOffset.Width(),
+ -FRound( fSin * nX - fCos * nY ) + rRotatePt.Y() + rOffset.Height() );
+}
+
+tools::Polygon GDIMetaFile::ImplGetRotatedPolygon( const tools::Polygon& rPoly, const Point& rRotatePt,
+ const Size& rOffset, double fSin, double fCos )
+{
+ tools::Polygon aRet( rPoly );
+
+ aRet.Rotate( rRotatePt, fSin, fCos );
+ aRet.Move( rOffset.Width(), rOffset.Height() );
+
+ return aRet;
+}
+
+tools::PolyPolygon GDIMetaFile::ImplGetRotatedPolyPolygon( const tools::PolyPolygon& rPolyPoly, const Point& rRotatePt,
+ const Size& rOffset, double fSin, double fCos )
+{
+ tools::PolyPolygon aRet( rPolyPoly );
+
+ aRet.Rotate( rRotatePt, fSin, fCos );
+ aRet.Move( rOffset.Width(), rOffset.Height() );
+
+ return aRet;
+}
+
+void GDIMetaFile::ImplAddGradientEx( GDIMetaFile& rMtf,
+ const OutputDevice& rMapDev,
+ const tools::PolyPolygon& rPolyPoly,
+ const Gradient& rGrad )
+{
+ // Generate comment, GradientEx and Gradient actions (within DrawGradient)
+ ScopedVclPtrInstance< VirtualDevice > aVDev(rMapDev, DeviceFormat::DEFAULT);
+ aVDev->EnableOutput( false );
+ GDIMetaFile aGradMtf;
+
+ aGradMtf.Record( aVDev.get() );
+ aVDev->DrawGradient( rPolyPoly, rGrad );
+ aGradMtf.Stop();
+
+ size_t i, nAct( aGradMtf.GetActionSize() );
+ for( i=0; i < nAct; ++i )
+ {
+ MetaAction* pMetaAct = aGradMtf.GetAction( i );
+ rMtf.AddAction( pMetaAct );
+ }
+}
+
+void GDIMetaFile::Rotate( long nAngle10 )
+{
+ nAngle10 %= 3600;
+ nAngle10 = ( nAngle10 < 0 ) ? ( 3599 + nAngle10 ) : nAngle10;
+
+ if( !nAngle10 )
+ return;
+
+ GDIMetaFile aMtf;
+ ScopedVclPtrInstance< VirtualDevice > aMapVDev;
+ const double fAngle = F_PI1800 * nAngle10;
+ const double fSin = sin( fAngle );
+ const double fCos = cos( fAngle );
+ tools::Rectangle aRect( Point(), GetPrefSize() );
+ tools::Polygon aPoly( aRect );
+
+ aPoly.Rotate( Point(), fSin, fCos );
+
+ aMapVDev->EnableOutput( false );
+ aMapVDev->SetMapMode( GetPrefMapMode() );
+
+ const tools::Rectangle aNewBound( aPoly.GetBoundRect() );
+
+ const Point aOrigin( GetPrefMapMode().GetOrigin().X(), GetPrefMapMode().GetOrigin().Y() );
+ const Size aOffset( -aNewBound.Left(), -aNewBound.Top() );
+
+ Point aRotAnchor( aOrigin );
+ Size aRotOffset( aOffset );
+
+ for( MetaAction* pAction = FirstAction(); pAction; pAction = NextAction() )
+ {
+ const MetaActionType nActionType = pAction->GetType();
+
+ switch( nActionType )
+ {
+ case MetaActionType::PIXEL:
+ {
+ MetaPixelAction* pAct = static_cast<MetaPixelAction*>(pAction);
+ aMtf.AddAction( new MetaPixelAction( ImplGetRotatedPoint( pAct->GetPoint(), aRotAnchor, aRotOffset, fSin, fCos ),
+ pAct->GetColor() ) );
+ }
+ break;
+
+ case MetaActionType::POINT:
+ {
+ MetaPointAction* pAct = static_cast<MetaPointAction*>(pAction);
+ aMtf.AddAction( new MetaPointAction( ImplGetRotatedPoint( pAct->GetPoint(), aRotAnchor, aRotOffset, fSin, fCos ) ) );
+ }
+ break;
+
+ case MetaActionType::LINE:
+ {
+ MetaLineAction* pAct = static_cast<MetaLineAction*>(pAction);
+ aMtf.AddAction( new MetaLineAction( ImplGetRotatedPoint( pAct->GetStartPoint(), aRotAnchor, aRotOffset, fSin, fCos ),
+ ImplGetRotatedPoint( pAct->GetEndPoint(), aRotAnchor, aRotOffset, fSin, fCos ),
+ pAct->GetLineInfo() ) );
+ }
+ break;
+
+ case MetaActionType::RECT:
+ {
+ MetaRectAction* pAct = static_cast<MetaRectAction*>(pAction);
+ aMtf.AddAction( new MetaPolygonAction( ImplGetRotatedPolygon( pAct->GetRect(), aRotAnchor, aRotOffset, fSin, fCos ) ) );
+ }
+ break;
+
+ case MetaActionType::ROUNDRECT:
+ {
+ MetaRoundRectAction* pAct = static_cast<MetaRoundRectAction*>(pAction);
+ const tools::Polygon aRoundRectPoly( pAct->GetRect(), pAct->GetHorzRound(), pAct->GetVertRound() );
+
+ aMtf.AddAction( new MetaPolygonAction( ImplGetRotatedPolygon( aRoundRectPoly, aRotAnchor, aRotOffset, fSin, fCos ) ) );
+ }
+ break;
+
+ case MetaActionType::ELLIPSE:
+ {
+ MetaEllipseAction* pAct = static_cast<MetaEllipseAction*>(pAction);
+ const tools::Polygon aEllipsePoly( pAct->GetRect().Center(), pAct->GetRect().GetWidth() >> 1, pAct->GetRect().GetHeight() >> 1 );
+
+ aMtf.AddAction( new MetaPolygonAction( ImplGetRotatedPolygon( aEllipsePoly, aRotAnchor, aRotOffset, fSin, fCos ) ) );
+ }
+ break;
+
+ case MetaActionType::ARC:
+ {
+ MetaArcAction* pAct = static_cast<MetaArcAction*>(pAction);
+ const tools::Polygon aArcPoly( pAct->GetRect(), pAct->GetStartPoint(), pAct->GetEndPoint(), PolyStyle::Arc );
+
+ aMtf.AddAction( new MetaPolygonAction( ImplGetRotatedPolygon( aArcPoly, aRotAnchor, aRotOffset, fSin, fCos ) ) );
+ }
+ break;
+
+ case MetaActionType::PIE:
+ {
+ MetaPieAction* pAct = static_cast<MetaPieAction*>(pAction);
+ const tools::Polygon aPiePoly( pAct->GetRect(), pAct->GetStartPoint(), pAct->GetEndPoint(), PolyStyle::Pie );
+
+ aMtf.AddAction( new MetaPolygonAction( ImplGetRotatedPolygon( aPiePoly, aRotAnchor, aRotOffset, fSin, fCos ) ) );
+ }
+ break;
+
+ case MetaActionType::CHORD:
+ {
+ MetaChordAction* pAct = static_cast<MetaChordAction*>(pAction);
+ const tools::Polygon aChordPoly( pAct->GetRect(), pAct->GetStartPoint(), pAct->GetEndPoint(), PolyStyle::Chord );
+
+ aMtf.AddAction( new MetaPolygonAction( ImplGetRotatedPolygon( aChordPoly, aRotAnchor, aRotOffset, fSin, fCos ) ) );
+ }
+ break;
+
+ case MetaActionType::POLYLINE:
+ {
+ MetaPolyLineAction* pAct = static_cast<MetaPolyLineAction*>(pAction);
+ aMtf.AddAction( new MetaPolyLineAction( ImplGetRotatedPolygon( pAct->GetPolygon(), aRotAnchor, aRotOffset, fSin, fCos ), pAct->GetLineInfo() ) );
+ }
+ break;
+
+ case MetaActionType::POLYGON:
+ {
+ MetaPolygonAction* pAct = static_cast<MetaPolygonAction*>(pAction);
+ aMtf.AddAction( new MetaPolygonAction( ImplGetRotatedPolygon( pAct->GetPolygon(), aRotAnchor, aRotOffset, fSin, fCos ) ) );
+ }
+ break;
+
+ case MetaActionType::POLYPOLYGON:
+ {
+ MetaPolyPolygonAction* pAct = static_cast<MetaPolyPolygonAction*>(pAction);
+ aMtf.AddAction( new MetaPolyPolygonAction( ImplGetRotatedPolyPolygon( pAct->GetPolyPolygon(), aRotAnchor, aRotOffset, fSin, fCos ) ) );
+ }
+ break;
+
+ case MetaActionType::TEXT:
+ {
+ MetaTextAction* pAct = static_cast<MetaTextAction*>(pAction);
+ aMtf.AddAction( new MetaTextAction( ImplGetRotatedPoint( pAct->GetPoint(), aRotAnchor, aRotOffset, fSin, fCos ),
+ pAct->GetText(), pAct->GetIndex(), pAct->GetLen() ) );
+ }
+ break;
+
+ case MetaActionType::TEXTARRAY:
+ {
+ MetaTextArrayAction* pAct = static_cast<MetaTextArrayAction*>(pAction);
+ aMtf.AddAction( new MetaTextArrayAction( ImplGetRotatedPoint( pAct->GetPoint(), aRotAnchor, aRotOffset, fSin, fCos ),
+ pAct->GetText(), pAct->GetDXArray(), pAct->GetIndex(), pAct->GetLen() ) );
+ }
+ break;
+
+ case MetaActionType::STRETCHTEXT:
+ {
+ MetaStretchTextAction* pAct = static_cast<MetaStretchTextAction*>(pAction);
+ aMtf.AddAction( new MetaStretchTextAction( ImplGetRotatedPoint( pAct->GetPoint(), aRotAnchor, aRotOffset, fSin, fCos ),
+ pAct->GetWidth(), pAct->GetText(), pAct->GetIndex(), pAct->GetLen() ) );
+ }
+ break;
+
+ case MetaActionType::TEXTLINE:
+ {
+ MetaTextLineAction* pAct = static_cast<MetaTextLineAction*>(pAction);
+ aMtf.AddAction( new MetaTextLineAction( ImplGetRotatedPoint( pAct->GetStartPoint(), aRotAnchor, aRotOffset, fSin, fCos ),
+ pAct->GetWidth(), pAct->GetStrikeout(), pAct->GetUnderline(), pAct->GetOverline() ) );
+ }
+ break;
+
+ case MetaActionType::BMPSCALE:
+ {
+ MetaBmpScaleAction* pAct = static_cast<MetaBmpScaleAction*>(pAction);
+ tools::Polygon aBmpPoly( ImplGetRotatedPolygon( tools::Rectangle( pAct->GetPoint(), pAct->GetSize() ), aRotAnchor, aRotOffset, fSin, fCos ) );
+ tools::Rectangle aBmpRect( aBmpPoly.GetBoundRect() );
+ BitmapEx aBmpEx( pAct->GetBitmap() );
+
+ aBmpEx.Rotate( nAngle10, COL_TRANSPARENT );
+ aMtf.AddAction( new MetaBmpExScaleAction( aBmpRect.TopLeft(), aBmpRect.GetSize(),
+ aBmpEx ) );
+ }
+ break;
+
+ case MetaActionType::BMPSCALEPART:
+ {
+ MetaBmpScalePartAction* pAct = static_cast<MetaBmpScalePartAction*>(pAction);
+ tools::Polygon aBmpPoly( ImplGetRotatedPolygon( tools::Rectangle( pAct->GetDestPoint(), pAct->GetDestSize() ), aRotAnchor, aRotOffset, fSin, fCos ) );
+ tools::Rectangle aBmpRect( aBmpPoly.GetBoundRect() );
+ BitmapEx aBmpEx( pAct->GetBitmap() );
+
+ aBmpEx.Crop( tools::Rectangle( pAct->GetSrcPoint(), pAct->GetSrcSize() ) );
+ aBmpEx.Rotate( nAngle10, COL_TRANSPARENT );
+
+ aMtf.AddAction( new MetaBmpExScaleAction( aBmpRect.TopLeft(), aBmpRect.GetSize(), aBmpEx ) );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ {
+ MetaBmpExScaleAction* pAct = static_cast<MetaBmpExScaleAction*>(pAction);
+ tools::Polygon aBmpPoly( ImplGetRotatedPolygon( tools::Rectangle( pAct->GetPoint(), pAct->GetSize() ), aRotAnchor, aRotOffset, fSin, fCos ) );
+ tools::Rectangle aBmpRect( aBmpPoly.GetBoundRect() );
+ BitmapEx aBmpEx( pAct->GetBitmapEx() );
+
+ aBmpEx.Rotate( nAngle10, COL_TRANSPARENT );
+
+ aMtf.AddAction( new MetaBmpExScaleAction( aBmpRect.TopLeft(), aBmpRect.GetSize(), aBmpEx ) );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ {
+ MetaBmpExScalePartAction* pAct = static_cast<MetaBmpExScalePartAction*>(pAction);
+ tools::Polygon aBmpPoly( ImplGetRotatedPolygon( tools::Rectangle( pAct->GetDestPoint(), pAct->GetDestSize() ), aRotAnchor, aRotOffset, fSin, fCos ) );
+ tools::Rectangle aBmpRect( aBmpPoly.GetBoundRect() );
+ BitmapEx aBmpEx( pAct->GetBitmapEx() );
+
+ aBmpEx.Crop( tools::Rectangle( pAct->GetSrcPoint(), pAct->GetSrcSize() ) );
+ aBmpEx.Rotate( nAngle10, COL_TRANSPARENT );
+
+ aMtf.AddAction( new MetaBmpExScaleAction( aBmpRect.TopLeft(), aBmpRect.GetSize(), aBmpEx ) );
+ }
+ break;
+
+ case MetaActionType::GRADIENT:
+ {
+ MetaGradientAction* pAct = static_cast<MetaGradientAction*>(pAction);
+
+ ImplAddGradientEx( aMtf, *aMapVDev,
+ ImplGetRotatedPolygon( pAct->GetRect(), aRotAnchor, aRotOffset, fSin, fCos ),
+ pAct->GetGradient() );
+ }
+ break;
+
+ case MetaActionType::GRADIENTEX:
+ {
+ MetaGradientExAction* pAct = static_cast<MetaGradientExAction*>(pAction);
+ aMtf.AddAction( new MetaGradientExAction( ImplGetRotatedPolyPolygon( pAct->GetPolyPolygon(), aRotAnchor, aRotOffset, fSin, fCos ),
+ pAct->GetGradient() ) );
+ }
+ break;
+
+ // Handle gradientex comment block correctly
+ case MetaActionType::COMMENT:
+ {
+ MetaCommentAction* pCommentAct = static_cast<MetaCommentAction*>(pAction);
+ if( pCommentAct->GetComment() == "XGRAD_SEQ_BEGIN" )
+ {
+ int nBeginComments( 1 );
+ pAction = NextAction();
+
+ // skip everything, except gradientex action
+ while( pAction )
+ {
+ const MetaActionType nType = pAction->GetType();
+
+ if( MetaActionType::GRADIENTEX == nType )
+ {
+ // Add rotated gradientex
+ MetaGradientExAction* pAct = static_cast<MetaGradientExAction*>(pAction);
+ ImplAddGradientEx( aMtf, *aMapVDev,
+ ImplGetRotatedPolyPolygon( pAct->GetPolyPolygon(), aRotAnchor, aRotOffset, fSin, fCos ),
+ pAct->GetGradient() );
+ }
+ else if( MetaActionType::COMMENT == nType)
+ {
+ MetaCommentAction* pAct = static_cast<MetaCommentAction*>(pAction);
+ if( pAct->GetComment() == "XGRAD_SEQ_END" )
+ {
+ // handle nested blocks
+ --nBeginComments;
+
+ // gradientex comment block: end reached, done.
+ if( !nBeginComments )
+ break;
+ }
+ else if( pAct->GetComment() == "XGRAD_SEQ_BEGIN" )
+ {
+ // handle nested blocks
+ ++nBeginComments;
+ }
+
+ }
+
+ pAction =NextAction();
+ }
+ }
+ else
+ {
+ bool bPathStroke = (pCommentAct->GetComment() == "XPATHSTROKE_SEQ_BEGIN");
+ if ( bPathStroke || pCommentAct->GetComment() == "XPATHFILL_SEQ_BEGIN" )
+ {
+ if ( pCommentAct->GetDataSize() )
+ {
+ SvMemoryStream aMemStm( const_cast<sal_uInt8 *>(pCommentAct->GetData()), pCommentAct->GetDataSize(), StreamMode::READ );
+ SvMemoryStream aDest;
+ if ( bPathStroke )
+ {
+ SvtGraphicStroke aStroke;
+ ReadSvtGraphicStroke( aMemStm, aStroke );
+ tools::Polygon aPath;
+ aStroke.getPath( aPath );
+ aStroke.setPath( ImplGetRotatedPolygon( aPath, aRotAnchor, aRotOffset, fSin, fCos ) );
+ WriteSvtGraphicStroke( aDest, aStroke );
+ aMtf.AddAction( new MetaCommentAction( "XPATHSTROKE_SEQ_BEGIN", 0,
+ static_cast<const sal_uInt8*>( aDest.GetData()), aDest.Tell() ) );
+ }
+ else
+ {
+ SvtGraphicFill aFill;
+ ReadSvtGraphicFill( aMemStm, aFill );
+ tools::PolyPolygon aPath;
+ aFill.getPath( aPath );
+ aFill.setPath( ImplGetRotatedPolyPolygon( aPath, aRotAnchor, aRotOffset, fSin, fCos ) );
+ WriteSvtGraphicFill( aDest, aFill );
+ aMtf.AddAction( new MetaCommentAction( "XPATHFILL_SEQ_BEGIN", 0,
+ static_cast<const sal_uInt8*>( aDest.GetData()), aDest.Tell() ) );
+ }
+ }
+ }
+ else if ( pCommentAct->GetComment() == "XPATHSTROKE_SEQ_END"
+ || pCommentAct->GetComment() == "XPATHFILL_SEQ_END" )
+ {
+ pAction->Execute( aMapVDev.get() );
+ aMtf.AddAction( pAction );
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::HATCH:
+ {
+ MetaHatchAction* pAct = static_cast<MetaHatchAction*>(pAction);
+ Hatch aHatch( pAct->GetHatch() );
+
+ aHatch.SetAngle( aHatch.GetAngle() + static_cast<sal_uInt16>(nAngle10) );
+ aMtf.AddAction( new MetaHatchAction( ImplGetRotatedPolyPolygon( pAct->GetPolyPolygon(), aRotAnchor, aRotOffset, fSin, fCos ),
+ aHatch ) );
+ }
+ break;
+
+ case MetaActionType::Transparent:
+ {
+ MetaTransparentAction* pAct = static_cast<MetaTransparentAction*>(pAction);
+ aMtf.AddAction( new MetaTransparentAction( ImplGetRotatedPolyPolygon( pAct->GetPolyPolygon(), aRotAnchor, aRotOffset, fSin, fCos ),
+ pAct->GetTransparence() ) );
+ }
+ break;
+
+ case MetaActionType::FLOATTRANSPARENT:
+ {
+ MetaFloatTransparentAction* pAct = static_cast<MetaFloatTransparentAction*>(pAction);
+ GDIMetaFile aTransMtf( pAct->GetGDIMetaFile() );
+ tools::Polygon aMtfPoly( ImplGetRotatedPolygon( tools::Rectangle( pAct->GetPoint(), pAct->GetSize() ), aRotAnchor, aRotOffset, fSin, fCos ) );
+ tools::Rectangle aMtfRect( aMtfPoly.GetBoundRect() );
+
+ aTransMtf.Rotate( nAngle10 );
+ aMtf.AddAction( new MetaFloatTransparentAction( aTransMtf, aMtfRect.TopLeft(), aMtfRect.GetSize(),
+ pAct->GetGradient() ) );
+ }
+ break;
+
+ case MetaActionType::EPS:
+ {
+ MetaEPSAction* pAct = static_cast<MetaEPSAction*>(pAction);
+ GDIMetaFile aEPSMtf( pAct->GetSubstitute() );
+ tools::Polygon aEPSPoly( ImplGetRotatedPolygon( tools::Rectangle( pAct->GetPoint(), pAct->GetSize() ), aRotAnchor, aRotOffset, fSin, fCos ) );
+ tools::Rectangle aEPSRect( aEPSPoly.GetBoundRect() );
+
+ aEPSMtf.Rotate( nAngle10 );
+ aMtf.AddAction( new MetaEPSAction( aEPSRect.TopLeft(), aEPSRect.GetSize(),
+ pAct->GetLink(), aEPSMtf ) );
+ }
+ break;
+
+ case MetaActionType::CLIPREGION:
+ {
+ MetaClipRegionAction* pAct = static_cast<MetaClipRegionAction*>(pAction);
+
+ if( pAct->IsClipping() && pAct->GetRegion().HasPolyPolygonOrB2DPolyPolygon() )
+ aMtf.AddAction( new MetaClipRegionAction( vcl::Region( ImplGetRotatedPolyPolygon( pAct->GetRegion().GetAsPolyPolygon(), aRotAnchor, aRotOffset, fSin, fCos ) ), true ) );
+ else
+ {
+ aMtf.AddAction( pAction );
+ }
+ }
+ break;
+
+ case MetaActionType::ISECTRECTCLIPREGION:
+ {
+ MetaISectRectClipRegionAction* pAct = static_cast<MetaISectRectClipRegionAction*>(pAction);
+ aMtf.AddAction( new MetaISectRegionClipRegionAction(vcl::Region(
+ ImplGetRotatedPolygon( pAct->GetRect(), aRotAnchor,
+ aRotOffset, fSin, fCos )) ) );
+ }
+ break;
+
+ case MetaActionType::ISECTREGIONCLIPREGION:
+ {
+ MetaISectRegionClipRegionAction* pAct = static_cast<MetaISectRegionClipRegionAction*>(pAction);
+ const vcl::Region& rRegion = pAct->GetRegion();
+
+ if( rRegion.HasPolyPolygonOrB2DPolyPolygon() )
+ aMtf.AddAction( new MetaISectRegionClipRegionAction( vcl::Region( ImplGetRotatedPolyPolygon( rRegion.GetAsPolyPolygon(), aRotAnchor, aRotOffset, fSin, fCos ) ) ) );
+ else
+ {
+ aMtf.AddAction( pAction );
+ }
+ }
+ break;
+
+ case MetaActionType::REFPOINT:
+ {
+ MetaRefPointAction* pAct = static_cast<MetaRefPointAction*>(pAction);
+ aMtf.AddAction( new MetaRefPointAction( ImplGetRotatedPoint( pAct->GetRefPoint(), aRotAnchor, aRotOffset, fSin, fCos ), pAct->IsSetting() ) );
+ }
+ break;
+
+ case MetaActionType::FONT:
+ {
+ MetaFontAction* pAct = static_cast<MetaFontAction*>(pAction);
+ vcl::Font aFont( pAct->GetFont() );
+
+ aFont.SetOrientation( aFont.GetOrientation() + static_cast<sal_uInt16>(nAngle10) );
+ aMtf.AddAction( new MetaFontAction( aFont ) );
+ }
+ break;
+
+ case MetaActionType::BMP:
+ case MetaActionType::BMPEX:
+ case MetaActionType::MASK:
+ case MetaActionType::MASKSCALE:
+ case MetaActionType::MASKSCALEPART:
+ case MetaActionType::WALLPAPER:
+ case MetaActionType::TEXTRECT:
+ case MetaActionType::MOVECLIPREGION:
+ {
+ OSL_FAIL( "GDIMetaFile::Rotate(): unsupported action" );
+ }
+ break;
+
+ default:
+ {
+ pAction->Execute( aMapVDev.get() );
+ aMtf.AddAction( pAction );
+
+ // update rotation point and offset, if necessary
+ if( ( MetaActionType::MAPMODE == nActionType ) ||
+ ( MetaActionType::PUSH == nActionType ) ||
+ ( MetaActionType::POP == nActionType ) )
+ {
+ aRotAnchor = OutputDevice::LogicToLogic( aOrigin, m_aPrefMapMode, aMapVDev->GetMapMode() );
+ aRotOffset = OutputDevice::LogicToLogic( aOffset, m_aPrefMapMode, aMapVDev->GetMapMode() );
+ }
+ }
+ break;
+ }
+ }
+
+ aMtf.m_aPrefMapMode = m_aPrefMapMode;
+ aMtf.m_aPrefSize = aNewBound.GetSize();
+
+ *this = aMtf;
+
+}
+
+static void ImplActionBounds( tools::Rectangle& o_rOutBounds,
+ const tools::Rectangle& i_rInBounds,
+ const std::vector<tools::Rectangle>& i_rClipStack,
+ tools::Rectangle* o_pHairline )
+{
+ tools::Rectangle aBounds( i_rInBounds );
+ if( ! i_rInBounds.IsEmpty() && ! i_rClipStack.empty() && ! i_rClipStack.back().IsEmpty() )
+ aBounds.Intersection( i_rClipStack.back() );
+ if( ! aBounds.IsEmpty() )
+ {
+ if( ! o_rOutBounds.IsEmpty() )
+ o_rOutBounds.Union( aBounds );
+ else
+ o_rOutBounds = aBounds;
+
+ if(o_pHairline)
+ {
+ if( ! o_pHairline->IsEmpty() )
+ o_pHairline->Union( aBounds );
+ else
+ *o_pHairline = aBounds;
+ }
+ }
+}
+
+tools::Rectangle GDIMetaFile::GetBoundRect( OutputDevice& i_rReference, tools::Rectangle* pHairline ) const
+{
+ ScopedVclPtrInstance< VirtualDevice > aMapVDev( i_rReference );
+
+ aMapVDev->EnableOutput( false );
+ aMapVDev->SetMapMode( GetPrefMapMode() );
+
+ std::vector<tools::Rectangle> aClipStack( 1, tools::Rectangle() );
+ std::vector<PushFlags> aPushFlagStack;
+
+ tools::Rectangle aBound;
+
+ if(pHairline)
+ *pHairline = tools::Rectangle();
+
+ const sal_uLong nCount(GetActionSize());
+
+ for(sal_uLong a(0); a < nCount; a++)
+ {
+ MetaAction* pAction = GetAction(a);
+ const MetaActionType nActionType = pAction->GetType();
+ tools::Rectangle* pUseHairline = (pHairline && aMapVDev->IsLineColor()) ? pHairline : nullptr;
+
+ 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, pUseHairline );
+ }
+ 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, pUseHairline );
+ }
+ break;
+
+ case MetaActionType::LINE:
+ {
+ MetaLineAction* pAct = static_cast<MetaLineAction*>(pAction);
+ Point aP1( pAct->GetStartPoint() ), aP2( pAct->GetEndPoint() );
+ tools::Rectangle aRect( aP1, aP2 );
+ aRect.Justify();
+
+ if(pUseHairline)
+ {
+ const LineInfo& rLineInfo = pAct->GetLineInfo();
+
+ if(0 != rLineInfo.GetWidth())
+ pUseHairline = nullptr;
+ }
+
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack, pUseHairline );
+ }
+ break;
+
+ case MetaActionType::RECT:
+ {
+ MetaRectAction* pAct = static_cast<MetaRectAction*>(pAction);
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( pAct->GetRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack, pUseHairline );
+ }
+ break;
+
+ case MetaActionType::ROUNDRECT:
+ {
+ MetaRoundRectAction* pAct = static_cast<MetaRoundRectAction*>(pAction);
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( pAct->GetRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack, pUseHairline );
+ }
+ break;
+
+ case MetaActionType::ELLIPSE:
+ {
+ MetaEllipseAction* pAct = static_cast<MetaEllipseAction*>(pAction);
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( pAct->GetRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack, pUseHairline );
+ }
+ 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, pUseHairline );
+ }
+ 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, pUseHairline );
+ }
+ 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, pUseHairline );
+ }
+ break;
+
+ case MetaActionType::POLYLINE:
+ {
+ MetaPolyLineAction* pAct = static_cast<MetaPolyLineAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetPolygon().GetBoundRect() );
+
+ if(pUseHairline)
+ {
+ const LineInfo& rLineInfo = pAct->GetLineInfo();
+
+ if(0 != rLineInfo.GetWidth())
+ pUseHairline = nullptr;
+ }
+
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack, pUseHairline );
+ }
+ 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, pUseHairline );
+ }
+ 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, pUseHairline );
+ }
+ 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, nullptr );
+ }
+ break;
+
+ case MetaActionType::TEXTARRAY:
+ {
+ MetaTextArrayAction* pAct = static_cast<MetaTextArrayAction*>(pAction);
+ tools::Rectangle aRect;
+ // hdu said base = index
+ aMapVDev->GetTextBoundRect( aRect, pAct->GetText(), pAct->GetIndex(), pAct->GetIndex(), pAct->GetLen(),
+ 0, pAct->GetDXArray() );
+ Point aPt( pAct->GetPoint() );
+ aRect.Move( aPt.X(), aPt.Y() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack, nullptr );
+ }
+ 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, nullptr );
+ }
+ break;
+
+ case MetaActionType::TEXTLINE:
+ {
+ MetaTextLineAction* pAct = static_cast<MetaTextLineAction*>(pAction);
+ // measure a test string to get ascend and descent right
+ static const sal_Unicode pStr[] = { 0xc4, 0x67, 0 };
+ 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, nullptr );
+ }
+ 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, nullptr );
+ }
+ 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, nullptr );
+ }
+ 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, nullptr );
+ }
+ 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, nullptr );
+ }
+ break;
+
+ case MetaActionType::GRADIENT:
+ {
+ MetaGradientAction* pAct = static_cast<MetaGradientAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetRect() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack, nullptr );
+ }
+ 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, nullptr );
+ }
+ 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, nullptr );
+ }
+ 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, nullptr );
+ }
+ 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, nullptr );
+ }
+ 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, nullptr );
+ }
+ 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, nullptr );
+ }
+ 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, nullptr );
+ }
+ 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, nullptr );
+ }
+ 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, nullptr );
+ }
+ 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, nullptr );
+ }
+ break;
+
+ case MetaActionType::WALLPAPER:
+ {
+ MetaWallpaperAction* pAct = static_cast<MetaWallpaperAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetRect() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack, nullptr );
+ }
+ break;
+
+ case MetaActionType::TEXTRECT:
+ {
+ MetaTextRectAction* pAct = static_cast<MetaTextRectAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetRect() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack, nullptr );
+ }
+ 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() & PushFlags::CLIPREGION )
+ {
+ tools::Rectangle aRect( aClipStack.back() );
+ aClipStack.push_back( aRect );
+ }
+ }
+ else if( nActionType == MetaActionType::POP )
+ {
+ // sanity check
+ if( ! aPushFlagStack.empty() )
+ {
+ if( aPushFlagStack.back() & 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( rColor.GetTransparency(),
+ 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( rColor.GetTransparency(), 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(), 4, &aPal );
+ aBmp.Erase( static_cast<const ImplBmpMonoParam*>(pBmpParam)->aColor );
+
+ if( rBmpEx.IsAlpha() )
+ return BitmapEx( aBmp, rBmpEx.GetAlpha() );
+ else if( rBmpEx.IsTransparent() )
+ return BitmapEx( aBmp, rBmpEx.GetMask() );
+ else
+ return BitmapEx( aBmp );
+}
+
+Color GDIMetaFile::ImplColReplaceFnc( const Color& rColor, const void* pColParam )
+{
+ const sal_uLong nR = rColor.GetRed(), nG = rColor.GetGreen(), nB = rColor.GetBlue();
+
+ for( sal_uLong i = 0; i < static_cast<const ImplColReplaceParam*>(pColParam)->nCount; i++ )
+ {
+ if( ( static_cast<const ImplColReplaceParam*>(pColParam)->pMinR[ i ] <= nR ) &&
+ ( static_cast<const ImplColReplaceParam*>(pColParam)->pMaxR[ i ] >= nR ) &&
+ ( static_cast<const ImplColReplaceParam*>(pColParam)->pMinG[ i ] <= nG ) &&
+ ( static_cast<const ImplColReplaceParam*>(pColParam)->pMaxG[ i ] >= nG ) &&
+ ( static_cast<const ImplColReplaceParam*>(pColParam)->pMinB[ i ] <= nB ) &&
+ ( static_cast<const ImplColReplaceParam*>(pColParam)->pMaxB[ i ] >= nB ) )
+ {
+ return static_cast<const ImplColReplaceParam*>(pColParam)->pDstCols[ i ];
+ }
+ }
+
+ return rColor;
+}
+
+BitmapEx GDIMetaFile::ImplBmpReplaceFnc( const BitmapEx& rBmpEx, const void* pBmpParam )
+{
+ const ImplBmpReplaceParam* p = static_cast<const ImplBmpReplaceParam*>(pBmpParam);
+ BitmapEx aRet( rBmpEx );
+
+ aRet.Replace( p->pSrcCols, p->pDstCols, p->nCount );
+
+ return aRet;
+}
+
+void GDIMetaFile::ImplExchangeColors( ColorExchangeFnc pFncCol, const void* pColParam,
+ BmpExchangeFnc pFncBmp, const void* pBmpParam )
+{
+ GDIMetaFile aMtf;
+
+ aMtf.m_aPrefSize = m_aPrefSize;
+ aMtf.m_aPrefMapMode = m_aPrefMapMode;
+ aMtf.m_bUseCanvas = m_bUseCanvas;
+
+ for( MetaAction* pAction = FirstAction(); pAction; pAction = NextAction() )
+ {
+ const MetaActionType nType = pAction->GetType();
+
+ switch( nType )
+ {
+ case MetaActionType::PIXEL:
+ {
+ MetaPixelAction* pAct = static_cast<MetaPixelAction*>(pAction);
+ aMtf.push_back( new MetaPixelAction( pAct->GetPoint(), pFncCol( pAct->GetColor(), pColParam ) ) );
+ }
+ break;
+
+ case MetaActionType::LINECOLOR:
+ {
+ MetaLineColorAction* pAct = static_cast<MetaLineColorAction*>(pAction);
+
+ if( pAct->IsSetting() )
+ pAct = new MetaLineColorAction( pFncCol( pAct->GetColor(), pColParam ), true );
+
+ aMtf.push_back( pAct );
+ }
+ break;
+
+ case MetaActionType::FILLCOLOR:
+ {
+ MetaFillColorAction* pAct = static_cast<MetaFillColorAction*>(pAction);
+
+ if( pAct->IsSetting() )
+ pAct = new MetaFillColorAction( pFncCol( pAct->GetColor(), pColParam ), true );
+
+ aMtf.push_back( pAct );
+ }
+ break;
+
+ case MetaActionType::TEXTCOLOR:
+ {
+ MetaTextColorAction* pAct = static_cast<MetaTextColorAction*>(pAction);
+ aMtf.push_back( new MetaTextColorAction( pFncCol( pAct->GetColor(), pColParam ) ) );
+ }
+ break;
+
+ case MetaActionType::TEXTFILLCOLOR:
+ {
+ MetaTextFillColorAction* pAct = static_cast<MetaTextFillColorAction*>(pAction);
+
+ if( pAct->IsSetting() )
+ pAct = new MetaTextFillColorAction( pFncCol( pAct->GetColor(), pColParam ), true );
+
+ aMtf.push_back( pAct );
+ }
+ break;
+
+ case MetaActionType::TEXTLINECOLOR:
+ {
+ MetaTextLineColorAction* pAct = static_cast<MetaTextLineColorAction*>(pAction);
+
+ if( pAct->IsSetting() )
+ pAct = new MetaTextLineColorAction( pFncCol( pAct->GetColor(), pColParam ), true );
+
+ aMtf.push_back( pAct );
+ }
+ break;
+
+ case MetaActionType::OVERLINECOLOR:
+ {
+ MetaOverlineColorAction* pAct = static_cast<MetaOverlineColorAction*>(pAction);
+
+ if( pAct->IsSetting() )
+ pAct = new MetaOverlineColorAction( pFncCol( pAct->GetColor(), pColParam ), true );
+
+ aMtf.push_back( pAct );
+ }
+ break;
+
+ case MetaActionType::FONT:
+ {
+ MetaFontAction* pAct = static_cast<MetaFontAction*>(pAction);
+ vcl::Font aFont( pAct->GetFont() );
+
+ aFont.SetColor( pFncCol( aFont.GetColor(), pColParam ) );
+ aFont.SetFillColor( pFncCol( aFont.GetFillColor(), pColParam ) );
+ aMtf.push_back( new MetaFontAction( aFont ) );
+ }
+ break;
+
+ case MetaActionType::WALLPAPER:
+ {
+ MetaWallpaperAction* pAct = static_cast<MetaWallpaperAction*>(pAction);
+ Wallpaper aWall( pAct->GetWallpaper() );
+ const tools::Rectangle& rRect = pAct->GetRect();
+
+ aWall.SetColor( pFncCol( aWall.GetColor(), pColParam ) );
+
+ if( aWall.IsBitmap() )
+ aWall.SetBitmap( pFncBmp( aWall.GetBitmap(), pBmpParam ) );
+
+ if( aWall.IsGradient() )
+ {
+ Gradient aGradient( aWall.GetGradient() );
+
+ aGradient.SetStartColor( pFncCol( aGradient.GetStartColor(), pColParam ) );
+ aGradient.SetEndColor( pFncCol( aGradient.GetEndColor(), pColParam ) );
+ aWall.SetGradient( aGradient );
+ }
+
+ aMtf.push_back( new MetaWallpaperAction( rRect, aWall ) );
+ }
+ break;
+
+ case MetaActionType::BMP:
+ case MetaActionType::BMPEX:
+ case MetaActionType::MASK:
+ {
+ OSL_FAIL( "Don't use bitmap actions of this type in metafiles!" );
+ }
+ break;
+
+ case MetaActionType::BMPSCALE:
+ {
+ MetaBmpScaleAction* pAct = static_cast<MetaBmpScaleAction*>(pAction);
+ aMtf.push_back( new MetaBmpScaleAction( pAct->GetPoint(), pAct->GetSize(),
+ pFncBmp( BitmapEx(pAct->GetBitmap()), pBmpParam ).GetBitmap() ) );
+ }
+ break;
+
+ case MetaActionType::BMPSCALEPART:
+ {
+ MetaBmpScalePartAction* pAct = static_cast<MetaBmpScalePartAction*>(pAction);
+ aMtf.push_back( new MetaBmpScalePartAction( pAct->GetDestPoint(), pAct->GetDestSize(),
+ pAct->GetSrcPoint(), pAct->GetSrcSize(),
+ pFncBmp( BitmapEx(pAct->GetBitmap()), pBmpParam ).GetBitmap() )
+ );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ {
+ MetaBmpExScaleAction* pAct = static_cast<MetaBmpExScaleAction*>(pAction);
+ aMtf.push_back( new MetaBmpExScaleAction( pAct->GetPoint(), pAct->GetSize(),
+ pFncBmp( pAct->GetBitmapEx(), pBmpParam ) )
+ );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ {
+ MetaBmpExScalePartAction* pAct = static_cast<MetaBmpExScalePartAction*>(pAction);
+ aMtf.push_back( new MetaBmpExScalePartAction( pAct->GetDestPoint(), pAct->GetDestSize(),
+ pAct->GetSrcPoint(), pAct->GetSrcSize(),
+ pFncBmp( pAct->GetBitmapEx(), pBmpParam ) )
+ );
+ }
+ break;
+
+ case MetaActionType::MASKSCALE:
+ {
+ MetaMaskScaleAction* pAct = static_cast<MetaMaskScaleAction*>(pAction);
+ aMtf.push_back( new MetaMaskScaleAction( pAct->GetPoint(), pAct->GetSize(),
+ pAct->GetBitmap(),
+ pFncCol( pAct->GetColor(), pColParam ) )
+ );
+ }
+ break;
+
+ case MetaActionType::MASKSCALEPART:
+ {
+ MetaMaskScalePartAction* pAct = static_cast<MetaMaskScalePartAction*>(pAction);
+ aMtf.push_back( new MetaMaskScalePartAction( pAct->GetDestPoint(), pAct->GetDestSize(),
+ pAct->GetSrcPoint(), pAct->GetSrcSize(),
+ pAct->GetBitmap(),
+ pFncCol( pAct->GetColor(), pColParam ) )
+ );
+ }
+ break;
+
+ case MetaActionType::GRADIENT:
+ {
+ MetaGradientAction* pAct = static_cast<MetaGradientAction*>(pAction);
+ Gradient aGradient( pAct->GetGradient() );
+
+ aGradient.SetStartColor( pFncCol( aGradient.GetStartColor(), pColParam ) );
+ aGradient.SetEndColor( pFncCol( aGradient.GetEndColor(), pColParam ) );
+ aMtf.push_back( new MetaGradientAction( pAct->GetRect(), aGradient ) );
+ }
+ break;
+
+ case MetaActionType::GRADIENTEX:
+ {
+ MetaGradientExAction* pAct = static_cast<MetaGradientExAction*>(pAction);
+ Gradient aGradient( pAct->GetGradient() );
+
+ aGradient.SetStartColor( pFncCol( aGradient.GetStartColor(), pColParam ) );
+ aGradient.SetEndColor( pFncCol( aGradient.GetEndColor(), pColParam ) );
+ aMtf.push_back( new MetaGradientExAction( pAct->GetPolyPolygon(), aGradient ) );
+ }
+ break;
+
+ case MetaActionType::HATCH:
+ {
+ MetaHatchAction* pAct = static_cast<MetaHatchAction*>(pAction);
+ Hatch aHatch( pAct->GetHatch() );
+
+ aHatch.SetColor( pFncCol( aHatch.GetColor(), pColParam ) );
+ aMtf.push_back( new MetaHatchAction( pAct->GetPolyPolygon(), aHatch ) );
+ }
+ break;
+
+ case MetaActionType::FLOATTRANSPARENT:
+ {
+ MetaFloatTransparentAction* pAct = static_cast<MetaFloatTransparentAction*>(pAction);
+ GDIMetaFile aTransMtf( pAct->GetGDIMetaFile() );
+
+ aTransMtf.ImplExchangeColors( pFncCol, pColParam, pFncBmp, pBmpParam );
+ aMtf.push_back( new MetaFloatTransparentAction( aTransMtf,
+ pAct->GetPoint(), pAct->GetSize(),
+ pAct->GetGradient() )
+ );
+ }
+ break;
+
+ case MetaActionType::EPS:
+ {
+ MetaEPSAction* pAct = static_cast<MetaEPSAction*>(pAction);
+ GDIMetaFile aSubst( pAct->GetSubstitute() );
+
+ aSubst.ImplExchangeColors( pFncCol, pColParam, pFncBmp, pBmpParam );
+ aMtf.push_back( new MetaEPSAction( pAct->GetPoint(), pAct->GetSize(),
+ pAct->GetLink(), aSubst )
+ );
+ }
+ break;
+
+ default:
+ {
+ aMtf.push_back( pAction );
+ }
+ break;
+ }
+ }
+
+ *this = aMtf;
+}
+
+void GDIMetaFile::Adjust( short nLuminancePercent, short nContrastPercent,
+ short nChannelRPercent, short nChannelGPercent,
+ short nChannelBPercent, double fGamma, bool bInvert, bool msoBrightness )
+{
+ // nothing to do? => return quickly
+ if( !(nLuminancePercent || nContrastPercent ||
+ nChannelRPercent || nChannelGPercent || nChannelBPercent ||
+ ( fGamma != 1.0 ) || bInvert) )
+ return;
+
+ double fM, fROff, fGOff, fBOff, fOff;
+ ImplColAdjustParam aColParam;
+ ImplBmpAdjustParam aBmpParam;
+
+ aColParam.pMapR.reset(new sal_uInt8[ 256 ]);
+ aColParam.pMapG.reset(new sal_uInt8[ 256 ]);
+ aColParam.pMapB.reset(new sal_uInt8[ 256 ]);
+
+ // calculate slope
+ if( nContrastPercent >= 0 )
+ fM = 128.0 / ( 128.0 - 1.27 * MinMax( nContrastPercent, 0, 100 ) );
+ else
+ fM = ( 128.0 + 1.27 * MinMax( nContrastPercent, -100, 0 ) ) / 128.0;
+
+ if(!msoBrightness)
+ // total offset = luminance offset + contrast offset
+ fOff = MinMax( nLuminancePercent, -100, 100 ) * 2.55 + 128.0 - fM * 128.0;
+ else
+ fOff = MinMax( nLuminancePercent, -100, 100 ) * 2.55;
+
+ // channel offset = channel offset + total offset
+ fROff = nChannelRPercent * 2.55 + fOff;
+ fGOff = nChannelGPercent * 2.55 + fOff;
+ fBOff = nChannelBPercent * 2.55 + fOff;
+
+ // calculate gamma value
+ fGamma = ( fGamma <= 0.0 || fGamma > 10.0 ) ? 1.0 : ( 1.0 / fGamma );
+ const bool bGamma = ( fGamma != 1.0 );
+
+ // create mapping table
+ for( long nX = 0; nX < 256; nX++ )
+ {
+ if(!msoBrightness)
+ {
+ aColParam.pMapR[ nX ] = static_cast<sal_uInt8>(MinMax( FRound( nX * fM + fROff ), 0, 255 ));
+ aColParam.pMapG[ nX ] = static_cast<sal_uInt8>(MinMax( FRound( nX * fM + fGOff ), 0, 255 ));
+ aColParam.pMapB[ nX ] = static_cast<sal_uInt8>(MinMax( FRound( nX * fM + fBOff ), 0, 255 ));
+ }
+ else
+ {
+ aColParam.pMapR[ nX ] = static_cast<sal_uInt8>(MinMax( FRound( (nX+fROff/2-128) * fM + 128 + fROff/2 ), 0, 255 ));
+ aColParam.pMapG[ nX ] = static_cast<sal_uInt8>(MinMax( FRound( (nX+fGOff/2-128) * fM + 128 + fGOff/2 ), 0, 255 ));
+ aColParam.pMapB[ nX ] = static_cast<sal_uInt8>(MinMax( FRound( (nX+fBOff/2-128) * fM + 128 + fBOff/2 ), 0, 255 ));
+ }
+ if( bGamma )
+ {
+ aColParam.pMapR[ nX ] = GAMMA( aColParam.pMapR[ nX ], fGamma );
+ aColParam.pMapG[ nX ] = GAMMA( aColParam.pMapG[ nX ], fGamma );
+ aColParam.pMapB[ nX ] = GAMMA( aColParam.pMapB[ nX ], fGamma );
+ }
+
+ if( bInvert )
+ {
+ aColParam.pMapR[ nX ] = ~aColParam.pMapR[ nX ];
+ aColParam.pMapG[ nX ] = ~aColParam.pMapG[ nX ];
+ aColParam.pMapB[ nX ] = ~aColParam.pMapB[ nX ];
+ }
+ }
+
+ aBmpParam.nLuminancePercent = nLuminancePercent;
+ aBmpParam.nContrastPercent = nContrastPercent;
+ aBmpParam.nChannelRPercent = nChannelRPercent;
+ aBmpParam.nChannelGPercent = nChannelGPercent;
+ aBmpParam.nChannelBPercent = nChannelBPercent;
+ aBmpParam.fGamma = fGamma;
+ aBmpParam.bInvert = bInvert;
+
+ // do color adjustment
+ ImplExchangeColors( ImplColAdjustFnc, &aColParam, ImplBmpAdjustFnc, &aBmpParam );
+}
+
+void GDIMetaFile::Convert( MtfConversion eConversion )
+{
+ ImplColConvertParam aColParam;
+ ImplBmpConvertParam aBmpParam;
+
+ aColParam.eConversion = eConversion;
+ aBmpParam.eConversion = ( MtfConversion::N1BitThreshold == eConversion ) ? BmpConversion::N1BitThreshold : BmpConversion::N8BitGreys;
+
+ ImplExchangeColors( ImplColConvertFnc, &aColParam, ImplBmpConvertFnc, &aBmpParam );
+}
+
+void GDIMetaFile::ReplaceColors( const Color* pSearchColors, const Color* pReplaceColors, sal_uLong nColorCount )
+{
+ ImplColReplaceParam aColParam;
+ ImplBmpReplaceParam aBmpParam;
+
+ aColParam.pMinR.reset(new sal_uLong[ nColorCount ]);
+ aColParam.pMaxR.reset(new sal_uLong[ nColorCount ]);
+ aColParam.pMinG.reset(new sal_uLong[ nColorCount ]);
+ aColParam.pMaxG.reset(new sal_uLong[ nColorCount ]);
+ aColParam.pMinB.reset(new sal_uLong[ nColorCount ]);
+ aColParam.pMaxB.reset(new sal_uLong[ nColorCount ]);
+
+ for( sal_uLong i = 0; i < nColorCount; i++ )
+ {
+ long nVal;
+
+ nVal = pSearchColors[ i ].GetRed();
+ aColParam.pMinR[ i ] = static_cast<sal_uLong>(std::max( nVal, 0L ));
+ aColParam.pMaxR[ i ] = static_cast<sal_uLong>(std::min( nVal, 255L ));
+
+ nVal = pSearchColors[ i ].GetGreen();
+ aColParam.pMinG[ i ] = static_cast<sal_uLong>(std::max( nVal, 0L ));
+ aColParam.pMaxG[ i ] = static_cast<sal_uLong>(std::min( nVal, 255L ));
+
+ nVal = pSearchColors[ i ].GetBlue();
+ aColParam.pMinB[ i ] = static_cast<sal_uLong>(std::max( nVal, 0L ));
+ aColParam.pMaxB[ i ] = static_cast<sal_uLong>(std::min( nVal, 255L ));
+ }
+
+ 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;
+}
+
+BitmapChecksum GDIMetaFile::GetChecksum() const
+{
+ 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 = GetActionSize(); i < nObjCount; i++ )
+ {
+ MetaAction* pAction = GetAction( i );
+
+ switch( pAction->GetType() )
+ {
+ case MetaActionType::BMP:
+ {
+ MetaBmpAction* pAct = static_cast<MetaBmpAction*>(pAction);
+
+ ShortToSVBT16( static_cast<sal_uInt16>(pAct->GetType()), aBT16 );
+ nCrc = vcl_get_checksum( nCrc, aBT16, 2 );
+
+ BCToBCOA( pAct->GetBitmap().GetChecksum(), aBCOA );
+ nCrc = vcl_get_checksum( nCrc, aBCOA, BITMAP_CHECKSUM_SIZE );
+
+ Int32ToSVBT32( pAct->GetPoint().X(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetPoint().Y(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+ }
+ break;
+
+ case MetaActionType::BMPSCALE:
+ {
+ MetaBmpScaleAction* pAct = static_cast<MetaBmpScaleAction*>(pAction);
+
+ ShortToSVBT16( static_cast<sal_uInt16>(pAct->GetType()), aBT16 );
+ nCrc = vcl_get_checksum( nCrc, aBT16, 2 );
+
+ BCToBCOA( pAct->GetBitmap().GetChecksum(), aBCOA );
+ nCrc = vcl_get_checksum( nCrc, aBCOA, BITMAP_CHECKSUM_SIZE );
+
+ Int32ToSVBT32( pAct->GetPoint().X(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetPoint().Y(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetSize().Width(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetSize().Height(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+ }
+ break;
+
+ case MetaActionType::BMPSCALEPART:
+ {
+ MetaBmpScalePartAction* pAct = static_cast<MetaBmpScalePartAction*>(pAction);
+
+ ShortToSVBT16( static_cast<sal_uInt16>(pAct->GetType()), aBT16 );
+ nCrc = vcl_get_checksum( nCrc, aBT16, 2 );
+
+ BCToBCOA( pAct->GetBitmap().GetChecksum(), aBCOA );
+ nCrc = vcl_get_checksum( nCrc, aBCOA, BITMAP_CHECKSUM_SIZE );
+
+ Int32ToSVBT32( pAct->GetDestPoint().X(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetDestPoint().Y(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetDestSize().Width(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetDestSize().Height(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetSrcPoint().X(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetSrcPoint().Y(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetSrcSize().Width(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetSrcSize().Height(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+ }
+ break;
+
+ case MetaActionType::BMPEX:
+ {
+ MetaBmpExAction* pAct = static_cast<MetaBmpExAction*>(pAction);
+
+ ShortToSVBT16( static_cast<sal_uInt16>(pAct->GetType()), aBT16 );
+ nCrc = vcl_get_checksum( nCrc, aBT16, 2 );
+
+ BCToBCOA( pAct->GetBitmapEx().GetChecksum(), aBCOA );
+ nCrc = vcl_get_checksum( nCrc, aBCOA, BITMAP_CHECKSUM_SIZE );
+
+ Int32ToSVBT32( pAct->GetPoint().X(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetPoint().Y(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ {
+ MetaBmpExScaleAction* pAct = static_cast<MetaBmpExScaleAction*>(pAction);
+
+ ShortToSVBT16( static_cast<sal_uInt16>(pAct->GetType()), aBT16 );
+ nCrc = vcl_get_checksum( nCrc, aBT16, 2 );
+
+ BCToBCOA( pAct->GetBitmapEx().GetChecksum(), aBCOA );
+ nCrc = vcl_get_checksum( nCrc, aBCOA, BITMAP_CHECKSUM_SIZE );
+
+ Int32ToSVBT32( pAct->GetPoint().X(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetPoint().Y(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetSize().Width(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetSize().Height(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ {
+ MetaBmpExScalePartAction* pAct = static_cast<MetaBmpExScalePartAction*>(pAction);
+
+ ShortToSVBT16( static_cast<sal_uInt16>(pAct->GetType()), aBT16 );
+ nCrc = vcl_get_checksum( nCrc, aBT16, 2 );
+
+ BCToBCOA( pAct->GetBitmapEx().GetChecksum(), aBCOA );
+ nCrc = vcl_get_checksum( nCrc, aBCOA, BITMAP_CHECKSUM_SIZE );
+
+ Int32ToSVBT32( pAct->GetDestPoint().X(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetDestPoint().Y(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetDestSize().Width(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetDestSize().Height(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetSrcPoint().X(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetSrcPoint().Y(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetSrcSize().Width(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetSrcSize().Height(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+ }
+ break;
+
+ case MetaActionType::MASK:
+ {
+ MetaMaskAction* pAct = static_cast<MetaMaskAction*>(pAction);
+
+ ShortToSVBT16( static_cast<sal_uInt16>(pAct->GetType()), aBT16 );
+ nCrc = vcl_get_checksum( nCrc, aBT16, 2 );
+
+ BCToBCOA( pAct->GetBitmap().GetChecksum(), aBCOA );
+ nCrc = vcl_get_checksum( nCrc, aBCOA, BITMAP_CHECKSUM_SIZE );
+
+ UInt32ToSVBT32( sal_uInt32(pAct->GetColor()), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetPoint().X(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetPoint().Y(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+ }
+ break;
+
+ case MetaActionType::MASKSCALE:
+ {
+ MetaMaskScaleAction* pAct = static_cast<MetaMaskScaleAction*>(pAction);
+
+ ShortToSVBT16( static_cast<sal_uInt16>(pAct->GetType()), aBT16 );
+ nCrc = vcl_get_checksum( nCrc, aBT16, 2 );
+
+ BCToBCOA( pAct->GetBitmap().GetChecksum(), aBCOA );
+ nCrc = vcl_get_checksum( nCrc, aBCOA, BITMAP_CHECKSUM_SIZE );
+
+ UInt32ToSVBT32( sal_uInt32(pAct->GetColor()), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetPoint().X(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetPoint().Y(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetSize().Width(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetSize().Height(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+ }
+ break;
+
+ case MetaActionType::MASKSCALEPART:
+ {
+ MetaMaskScalePartAction* pAct = static_cast<MetaMaskScalePartAction*>(pAction);
+
+ ShortToSVBT16( static_cast<sal_uInt16>(pAct->GetType()), aBT16 );
+ nCrc = vcl_get_checksum( nCrc, aBT16, 2 );
+
+ BCToBCOA( pAct->GetBitmap().GetChecksum(), aBCOA );
+ nCrc = vcl_get_checksum( nCrc, aBCOA, BITMAP_CHECKSUM_SIZE );
+
+ UInt32ToSVBT32( sal_uInt32(pAct->GetColor()), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetDestPoint().X(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetDestPoint().Y(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetDestSize().Width(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetDestSize().Height(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetSrcPoint().X(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetSrcPoint().Y(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetSrcSize().Width(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetSrcSize().Height(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+ }
+ break;
+
+ case MetaActionType::EPS :
+ {
+ MetaEPSAction* pAct = static_cast<MetaEPSAction*>(pAction);
+ nCrc = vcl_get_checksum( nCrc, pAct->GetLink().GetData(), pAct->GetLink().GetDataSize() );
+ }
+ break;
+
+ case MetaActionType::CLIPREGION :
+ {
+ MetaClipRegionAction& rAct = static_cast<MetaClipRegionAction&>(*pAction);
+ const vcl::Region& rRegion = rAct.GetRegion();
+
+ if(rRegion.HasPolyPolygonOrB2DPolyPolygon())
+ {
+ // It has shown that this is a possible bottleneck for checksum calculation.
+ // In worst case a very expensive RegionHandle representation gets created.
+ // In this case it's cheaper to use the PolyPolygon
+ const basegfx::B2DPolyPolygon aPolyPolygon(rRegion.GetAsB2DPolyPolygon());
+ SVBT64 aSVBT64;
+
+ for(auto const& rPolygon : aPolyPolygon)
+ {
+ const sal_uInt32 nPointCount(rPolygon.count());
+ const bool bControl(rPolygon.areControlPointsUsed());
+
+ for(sal_uInt32 b(0); b < nPointCount; b++)
+ {
+ const basegfx::B2DPoint aPoint(rPolygon.getB2DPoint(b));
+
+ DoubleToSVBT64(aPoint.getX(), aSVBT64);
+ nCrc = vcl_get_checksum(nCrc, aSVBT64, 8);
+ DoubleToSVBT64(aPoint.getY(), aSVBT64);
+ nCrc = vcl_get_checksum(nCrc, aSVBT64, 8);
+
+ if(bControl)
+ {
+ if(rPolygon.isPrevControlPointUsed(b))
+ {
+ const basegfx::B2DPoint aCtrl(rPolygon.getPrevControlPoint(b));
+
+ DoubleToSVBT64(aCtrl.getX(), aSVBT64);
+ nCrc = vcl_get_checksum(nCrc, aSVBT64, 8);
+ DoubleToSVBT64(aCtrl.getY(), aSVBT64);
+ nCrc = vcl_get_checksum(nCrc, aSVBT64, 8);
+ }
+
+ if(rPolygon.isNextControlPointUsed(b))
+ {
+ const basegfx::B2DPoint aCtrl(rPolygon.getNextControlPoint(b));
+
+ DoubleToSVBT64(aCtrl.getX(), aSVBT64);
+ nCrc = vcl_get_checksum(nCrc, aSVBT64, 8);
+ DoubleToSVBT64(aCtrl.getY(), aSVBT64);
+ nCrc = vcl_get_checksum(nCrc, aSVBT64, 8);
+ }
+ }
+ }
+ }
+
+ sal_uInt8 tmp = static_cast<sal_uInt8>(rAct.IsClipping());
+ nCrc = vcl_get_checksum(nCrc, &tmp, 1);
+ }
+ else
+ {
+ pAction->Write( aMemStm, &aWriteData );
+ nCrc = vcl_get_checksum( nCrc, aMemStm.GetData(), aMemStm.Tell() );
+ aMemStm.Seek( 0 );
+ }
+ }
+ break;
+
+ default:
+ {
+ pAction->Write( aMemStm, &aWriteData );
+ nCrc = vcl_get_checksum( nCrc, aMemStm.GetData(), aMemStm.Tell() );
+ aMemStm.Seek( 0 );
+ }
+ break;
+ }
+ }
+
+ return nCrc;
+}
+
+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() )
+ nSizeBytes += ( pTextArrayAction->GetLen() << 2 );
+ }
+ break;
+ default: break;
+ }
+ }
+
+ return nSizeBytes;
+}
+
+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;
+ }
+ };
+}
+
+SvStream& ReadGDIMetaFile(SvStream& rIStm, GDIMetaFile& rGDIMetaFile, ImplMetaReadData* pData)
+{
+ if (rIStm.GetError())
+ {
+ SAL_WARN("vcl.gdi", "Stream error: " << rIStm.GetError());
+ return rIStm;
+ }
+
+ sal_uLong nStmPos = rIStm.Tell();
+ SvStreamEndian nOldFormat = rIStm.GetEndian();
+
+ rIStm.SetEndian( SvStreamEndian::LITTLE );
+
+ try
+ {
+ char aId[7];
+ aId[0] = 0;
+ aId[6] = 0;
+ rIStm.ReadBytes( aId, 6 );
+
+ if ( !strcmp( aId, "VCLMTF" ) )
+ {
+ // new format
+ sal_uInt32 nStmCompressMode = 0;
+ sal_uInt32 nCount = 0;
+ std::unique_ptr<VersionCompat> pCompat(new VersionCompat( rIStm, StreamMode::READ ));
+
+ rIStm.ReadUInt32( nStmCompressMode );
+ ReadMapMode( rIStm, rGDIMetaFile.m_aPrefMapMode );
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readSize(rGDIMetaFile.m_aPrefSize);
+ rIStm.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, rIStm);
+
+ if (aDepthGuard.TooDeep())
+ throw std::runtime_error("too much recursion");
+
+ for( sal_uInt32 nAction = 0; ( nAction < nCount ) && !rIStm.eof(); nAction++ )
+ {
+ MetaAction* pAction = MetaAction::ReadMetaAction(rIStm, pData);
+ if( pAction )
+ {
+ if (pAction->GetType() == MetaActionType::COMMENT)
+ {
+ MetaCommentAction* pCommentAct = static_cast<MetaCommentAction*>(pAction);
+ if ( pCommentAct->GetComment() == "EMF_PLUS" )
+ rGDIMetaFile.UseCanvas( true );
+ }
+ rGDIMetaFile.AddAction( pAction );
+ }
+ }
+ }
+ else
+ {
+ rIStm.Seek( nStmPos );
+ SVMConverter( rIStm, rGDIMetaFile );
+ }
+ }
+ catch (...)
+ {
+ SAL_WARN("vcl", "GDIMetaFile exception during load");
+ rIStm.SetError(SVSTREAM_FILEFORMAT_ERROR);
+ };
+
+ // check for errors
+ if( rIStm.GetError() )
+ {
+ rGDIMetaFile.Clear();
+ rIStm.Seek( nStmPos );
+ }
+
+ rIStm.SetEndian( nOldFormat );
+ return rIStm;
+}
+
+SvStream& WriteGDIMetaFile( SvStream& rOStm, const GDIMetaFile& rGDIMetaFile )
+{
+ if( !rOStm.GetError() )
+ {
+ const_cast< GDIMetaFile& >( rGDIMetaFile ).Write( rOStm );
+ }
+ return rOStm;
+}
+
+SvStream& GDIMetaFile::Read( SvStream& rIStm )
+{
+ Clear();
+ ReadGDIMetaFile( rIStm, *this );
+
+ return rIStm;
+}
+
+SvStream& GDIMetaFile::Write( SvStream& rOStm )
+{
+ VersionCompat* pCompat;
+ const SvStreamCompressFlags nStmCompressMode = rOStm.GetCompressMode();
+ SvStreamEndian nOldFormat = rOStm.GetEndian();
+
+ rOStm.SetEndian( SvStreamEndian::LITTLE );
+ rOStm.WriteBytes( "VCLMTF", 6 );
+
+ pCompat = new VersionCompat( rOStm, StreamMode::WRITE, 1 );
+
+ rOStm.WriteUInt32( static_cast<sal_uInt32>(nStmCompressMode) );
+ WriteMapMode( rOStm, m_aPrefMapMode );
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writeSize(m_aPrefSize);
+ rOStm.WriteUInt32( GetActionSize() );
+
+ delete pCompat;
+
+ ImplMetaWriteData aWriteData;
+
+ aWriteData.meActualCharSet = rOStm.GetStreamCharSet();
+
+ MetaAction* pAct = FirstAction();
+ while ( pAct )
+ {
+ pAct->Write( rOStm, &aWriteData );
+ pAct = NextAction();
+ }
+
+ rOStm.SetEndian( nOldFormat );
+
+ return rOStm;
+}
+
+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;
+ 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( labs( aBRPix.X() - aTLPix.X() ) + 1, labs( aBRPix.Y() - aTLPix.Y() ) + 1 );
+ sal_uInt32 nMaximumExtent = 256;
+
+ 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< unsigned long >(aSizePix.Width()) >
+ nMaximumExtent ||
+ sal::static_int_cast< unsigned long >(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.get(), Point(), aAntialias);
+
+ // get paint bitmap
+ BitmapEx aBitmap( aVDev->GetBitmapEx( aNullPt, aVDev->GetOutputSizePixel() ) );
+
+ // scale down the image to the desired size - use the input scaler for the scaling operation
+ aBitmap.Scale(aDrawSize, nScaleFlag);
+
+ // convert to desired bitmap color format
+ Size aSize(aBitmap.GetSizePixel());
+ if (aSize.Width() && aSize.Height())
+ aBitmap.Convert(eColorConversion);
+
+ rBitmapEx = aBitmap;
+ }
+
+ return !rBitmapEx.IsEmpty();
+}
+
+void GDIMetaFile::UseCanvas( bool _bUseCanvas )
+{
+ m_bUseCanvas = _bUseCanvas;
+}
+
+void GDIMetaFile::dumpAsXml(const char* pFileName) const
+{
+ SvFileStream aStream(pFileName ? OUString::fromUtf8(pFileName) : OUString("file:///tmp/metafile.xml"),
+ StreamMode::STD_READWRITE | StreamMode::TRUNC);
+ assert(aStream.good());
+ MetafileXmlDump aDumper;
+ aDumper.dump(*this, aStream);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/genVerticalOrientationData.pl b/vcl/source/gdi/genVerticalOrientationData.pl
new file mode 100755
index 000000000..328727b26
--- /dev/null
+++ b/vcl/source/gdi/genVerticalOrientationData.pl
@@ -0,0 +1,206 @@
+#!/usr/bin/env perl
+
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# This tool is used to prepare lookup tables of Unicode character properties.
+# The properties are read from the Unicode Character Database and compiled into
+# multi-level arrays for efficient lookup.
+#
+# To regenerate the tables in VerticalOrientationData.cxx:
+#
+# (1) Download the current Unicode data files from
+#
+# We require the latest data file for UTR50, currently revision-17:
+# http://www.unicode.org/Public/vertical/revision-17/VerticalOrientation-17.txt
+#
+#
+# (2) Run this tool using a command line of the form
+#
+# perl genVerticalOrientationData.pl \
+# /path/to/VerticalOrientation-17.txt
+#
+# This will generate (or overwrite!) the files
+#
+# VerticalOrientationData.cxx
+#
+# in the current directory.
+
+use strict;
+use List::Util qw(first);
+
+my $DATA_FILE = $ARGV[0];
+
+my %verticalOrientationCode = (
+ 'U' => 0, # U - Upright, the same orientation as in the code charts
+ 'R' => 1, # R - Rotated 90 degrees clockwise compared to the code charts
+ 'Tu' => 2, # Tu - Transformed typographically, with fallback to Upright
+ 'Tr' => 3 # Tr - Transformed typographically, with fallback to Rotated
+);
+
+my @verticalOrientation;
+for (my $i = 0; $i < 0x110000; ++$i) {
+ $verticalOrientation[$i] = 1; # default for unlisted codepoints is 'R'
+}
+
+# read VerticalOrientation-17.txt
+my @versionInfo;
+open FH, "< $DATA_FILE" or die "can't open UTR50 data file VerticalOrientation-17.txt\n";
+push @versionInfo, "";
+while (<FH>) {
+ chomp;
+ push @versionInfo, $_;
+ last if /Date:/;
+}
+while (<FH>) {
+ chomp;
+ s/#.*//;
+ if (m/([0-9A-F]{4,6})(?:\.\.([0-9A-F]{4,6}))*\s*;\s*([^ ]+)/) {
+ my $vo = $3;
+ warn "unknown Vertical_Orientation code $vo"
+ unless exists $verticalOrientationCode{$vo};
+ $vo = $verticalOrientationCode{$vo};
+ my $start = hex "0x$1";
+ my $end = (defined $2) ? hex "0x$2" : $start;
+ for (my $i = $start; $i <= $end; ++$i) {
+ $verticalOrientation[$i] = $vo;
+ }
+ }
+}
+close FH;
+
+my $timestamp = gmtime();
+
+open DATA_TABLES, "> VerticalOrientationData.cxx" or die "unable to open VerticalOrientationData.cxx for output";
+
+my $licenseBlock = q[
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/*
+ * Derived from the Unicode Character Database by genVerticalOrientationData.pl
+ *
+ * For Unicode terms of use, see http://www.unicode.org/terms_of_use.html
+ */
+];
+
+my $versionInfo = join("\n", @versionInfo);
+
+print DATA_TABLES <<__END;
+$licenseBlock
+/*
+ * Created on $timestamp from UCD data files with version info:
+ *
+
+$versionInfo
+
+ *
+ * * * * * This file contains MACHINE-GENERATED DATA, do not edit! * * * * *
+ */
+
+__END
+
+our $totalData = 0;
+
+sub sprintVerticalOrientation
+{
+ my $usv = shift;
+ return sprintf("%d,",
+ $verticalOrientation[$usv]);
+}
+
+&genTables("VerticalOrientation", "uint8_t", 9, 7, \&sprintVerticalOrientation, 16, 1, 1);
+
+sub genTables
+{
+ my ($prefix, $type, $indexBits, $charBits, $func, $maxPlane, $bytesPerEntry, $charsPerEntry) = @_;
+
+ print DATA_TABLES "#define k${prefix}MaxPlane $maxPlane\n";
+ print DATA_TABLES "#define k${prefix}IndexBits $indexBits\n";
+ print DATA_TABLES "#define k${prefix}CharBits $charBits\n";
+
+ my $indexLen = 1 << $indexBits;
+ my $charsPerPage = 1 << $charBits;
+ my %charIndex = ();
+ my %pageMapIndex = ();
+ my @pageMap = ();
+ my @char = ();
+
+ my $planeMap = "\x00" x $maxPlane;
+ foreach my $plane (0 .. $maxPlane) {
+ my $pageMap = "\x00" x $indexLen * 2;
+ foreach my $page (0 .. $indexLen - 1) {
+ my $charValues = "";
+ for (my $ch = 0; $ch < $charsPerPage; $ch += $charsPerEntry) {
+ my $usv = $plane * 0x10000 + $page * $charsPerPage + $ch;
+ $charValues .= &$func($usv);
+ }
+ chop $charValues;
+
+ unless (exists $charIndex{$charValues}) {
+ $charIndex{$charValues} = scalar keys %charIndex;
+ $char[$charIndex{$charValues}] = $charValues;
+ }
+ substr($pageMap, $page * 2, 2) = pack('S', $charIndex{$charValues});
+ }
+
+ unless (exists $pageMapIndex{$pageMap}) {
+ $pageMapIndex{$pageMap} = scalar keys %pageMapIndex;
+ $pageMap[$pageMapIndex{$pageMap}] = $pageMap;
+ }
+ if ($plane > 0) {
+ substr($planeMap, $plane - 1, 1) = pack('C', $pageMapIndex{$pageMap});
+ }
+ }
+
+ if ($maxPlane) {
+ print DATA_TABLES "static const uint8_t s${prefix}Planes[$maxPlane] = {";
+ print DATA_TABLES join(',', map { sprintf("%d", $_) } unpack('C*', $planeMap));
+ print DATA_TABLES "};\n\n";
+ }
+
+ my $chCount = scalar @char;
+ my $pmBits = $chCount > 255 ? 16 : 8;
+ my $pmCount = scalar @pageMap;
+ if ($maxPlane == 0) {
+ die "there should only be one pageMap entry!" if $pmCount > 1;
+ print DATA_TABLES "static const uint${pmBits}_t s${prefix}Pages[$indexLen] = {\n";
+ } else {
+ print DATA_TABLES "static const uint${pmBits}_t s${prefix}Pages[$pmCount][$indexLen] = {\n";
+ }
+ for (my $i = 0; $i < scalar @pageMap; ++$i) {
+ print DATA_TABLES $maxPlane > 0 ? " {" : " ";
+ print DATA_TABLES join(',', map { sprintf("%d", $_) } unpack('S*', $pageMap[$i]));
+ print DATA_TABLES $maxPlane > 0 ? ($i < $#pageMap ? "},\n" : "}\n") : "\n";
+ }
+ print DATA_TABLES "};\n\n";
+
+ my $pageLen = $charsPerPage / $charsPerEntry;
+ print DATA_TABLES "static const $type s${prefix}Values[$chCount][$pageLen] = {\n";
+ for (my $i = 0; $i < scalar @char; ++$i) {
+ print DATA_TABLES " {";
+ print DATA_TABLES $char[$i];
+ print DATA_TABLES $i < $#char ? "},\n" : "}\n";
+ }
+ print DATA_TABLES "};\n";
+
+ my $dataSize = $pmCount * $indexLen * $pmBits/8 +
+ $chCount * $pageLen * $bytesPerEntry +
+ $maxPlane;
+ $totalData += $dataSize;
+
+ print STDERR "Data for $prefix = $dataSize\n";
+}
+print DATA_TABLES <<__END;
+/*
+ * * * * * This file contains MACHINE-GENERATED DATA, do not edit! * * * * *
+ */
+__END
+
+close DATA_TABLES;
diff --git a/vcl/source/gdi/gfxlink.cxx b/vcl/source/gdi/gfxlink.cxx
new file mode 100644
index 000000000..83936c277
--- /dev/null
+++ b/vcl/source/gdi/gfxlink.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/log.hxx>
+#include <tools/stream.hxx>
+#include <tools/vcompat.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/gfxlink.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <memory>
+#include <boost/functional/hash.hpp>
+
+GfxLink::GfxLink()
+ : meType(GfxLinkType::NONE)
+ , mnUserId(0)
+ , maHash(0)
+ , mnSwapInDataSize(0)
+ , mbPrefMapModeValid(false)
+ , mbPrefSizeValid(false)
+{
+}
+
+
+
+GfxLink::GfxLink(std::unique_ptr<sal_uInt8[]> pBuf, sal_uInt32 nSize, GfxLinkType nType)
+ : meType(nType)
+ , mnUserId(0)
+ , mpSwapInData(std::shared_ptr<sal_uInt8>(pBuf.release(), pBuf.get_deleter())) // std::move(pBuf) does not compile on Jenkins MacOSX (24 May 2016)
+ , maHash(0)
+ , mnSwapInDataSize(nSize)
+ , mbPrefMapModeValid(false)
+ , mbPrefSizeValid(false)
+{
+ SAL_WARN_IF(mpSwapInData == nullptr || mnSwapInDataSize <= 0, "vcl",
+ "GfxLink::GfxLink(): empty/NULL buffer given");
+}
+
+size_t GfxLink::GetHash() const
+{
+ if (!maHash)
+ {
+ std::size_t seed = 0;
+ boost::hash_combine(seed, mnSwapInDataSize);
+ boost::hash_combine(seed, meType);
+ const sal_uInt8* pData = GetData();
+ if (pData)
+ seed += boost::hash_range(pData, pData + GetDataSize());
+ maHash = seed;
+
+ }
+ return maHash;
+}
+
+bool GfxLink::operator==( const GfxLink& rGfxLink ) const
+{
+ if (GetHash() != rGfxLink.GetHash())
+ return false;
+
+ if ( mnSwapInDataSize != rGfxLink.mnSwapInDataSize ||
+ meType != rGfxLink.meType )
+ return false;
+
+ const sal_uInt8* pSource = GetData();
+ const sal_uInt8* pDest = rGfxLink.GetData();
+ if ( pSource == pDest )
+ return true;
+ sal_uInt32 nSourceSize = GetDataSize();
+ sal_uInt32 nDestSize = rGfxLink.GetDataSize();
+ if ( pSource && pDest && ( nSourceSize == nDestSize ) )
+ return (memcmp( pSource, pDest, nSourceSize ) == 0);
+ return false;
+}
+
+bool GfxLink::IsNative() const
+{
+ return meType >= GfxLinkType::NativeFirst && meType <= GfxLinkType::NativeLast;
+}
+
+
+const sal_uInt8* GfxLink::GetData() const
+{
+ return mpSwapInData.get();
+}
+
+
+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 )
+{
+ bool bRet = false;
+
+ if( IsNative() && mnSwapInDataSize )
+ {
+ const sal_uInt8* pData = GetData();
+ if (pData)
+ {
+ SvMemoryStream aMemoryStream(const_cast<sal_uInt8*>(pData), mnSwapInDataSize, 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;
+ default: break;
+ }
+ if (!aShortName.isEmpty())
+ {
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ sal_uInt16 nFormat = rFilter.GetImportFormatNumberForShortName(aShortName);
+ ErrCode nResult = rFilter.ImportGraphic(rGraphic, OUString(), aMemoryStream, nFormat);
+ if (nResult == ERRCODE_NONE)
+ bRet = true;
+ }
+ }
+ }
+
+ return bRet;
+}
+
+bool GfxLink::ExportNative( SvStream& rOStream ) const
+{
+ if( GetDataSize() )
+ {
+ auto pData = GetSwapInData();
+ if (pData)
+ rOStream.WriteBytes( pData.get(), mnSwapInDataSize );
+ }
+
+ return ( rOStream.GetError() == ERRCODE_NONE );
+}
+
+std::shared_ptr<sal_uInt8> GfxLink::GetSwapInData() const
+{
+ return mpSwapInData;
+}
+
+bool GfxLink::IsEMF() const
+{
+ const sal_uInt8* pGraphicAry = GetData();
+ if ((GetType() == GfxLinkType::NativeWmf) && pGraphicAry && (GetDataSize() > 0x2c))
+ {
+ // check the magic number
+ if ((pGraphicAry[0x28] == 0x20) && (pGraphicAry[0x29] == 0x45)
+ && (pGraphicAry[0x2a] == 0x4d) && (pGraphicAry[0x2b] == 0x46))
+ {
+ //emf detected
+ return true;
+ }
+ }
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/gradient.cxx b/vcl/source/gdi/gradient.cxx
new file mode 100644
index 000000000..2973f2d82
--- /dev/null
+++ b/vcl/source/gdi/gradient.cxx
@@ -0,0 +1,285 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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>
+
+class Gradient::Impl
+{
+public:
+ GradientStyle meStyle;
+ Color maStartColor;
+ Color maEndColor;
+ sal_uInt16 mnAngle;
+ sal_uInt16 mnBorder;
+ sal_uInt16 mnOfsX;
+ sal_uInt16 mnOfsY;
+ sal_uInt16 mnIntensityStart;
+ sal_uInt16 mnIntensityEnd;
+ sal_uInt16 mnStepCount;
+
+ Impl()
+ : meStyle (GradientStyle::Linear)
+ , maStartColor(COL_BLACK)
+ , maEndColor(COL_WHITE)
+ , mnAngle(0)
+ , mnBorder(0)
+ , mnOfsX(50)
+ , mnOfsY(50)
+ , mnIntensityStart(100)
+ , mnIntensityEnd(100)
+ , mnStepCount(0)
+ {
+ }
+
+ Impl(const Impl& rImplGradient)
+ : meStyle (rImplGradient.meStyle)
+ , maStartColor(rImplGradient.maStartColor)
+ , maEndColor(rImplGradient.maEndColor)
+ , mnAngle(rImplGradient.mnAngle)
+ , mnBorder(rImplGradient.mnBorder)
+ , mnOfsX(rImplGradient.mnOfsX)
+ , mnOfsY(rImplGradient.mnOfsY)
+ , mnIntensityStart(rImplGradient.mnIntensityStart)
+ , mnIntensityEnd(rImplGradient.mnIntensityEnd)
+ , mnStepCount(rImplGradient.mnStepCount)
+ {
+ }
+
+ bool operator==(const Impl& rImpl_Gradient) const
+ {
+ return (meStyle == rImpl_Gradient.meStyle)
+ && (mnAngle == rImpl_Gradient.mnAngle)
+ && (mnBorder == rImpl_Gradient.mnBorder)
+ && (mnOfsX == rImpl_Gradient.mnOfsX)
+ && (mnOfsY == rImpl_Gradient.mnOfsY)
+ && (mnStepCount == rImpl_Gradient.mnStepCount)
+ && (mnIntensityStart == rImpl_Gradient.mnIntensityStart)
+ && (mnIntensityEnd == rImpl_Gradient.mnIntensityEnd)
+ && (maStartColor == rImpl_Gradient.maStartColor)
+ && (maEndColor == rImpl_Gradient.maEndColor);
+ }
+};
+
+Gradient::Gradient() = default;
+
+Gradient::Gradient( const Gradient& ) = default;
+
+Gradient::Gradient( Gradient&& ) = default;
+
+Gradient::Gradient( GradientStyle eStyle,
+ const Color& rStartColor, const Color& rEndColor ) :
+ mpImplGradient()
+{
+ mpImplGradient->meStyle = eStyle;
+ mpImplGradient->maStartColor = rStartColor;
+ mpImplGradient->maEndColor = rEndColor;
+}
+
+Gradient::~Gradient() = default;
+
+
+GradientStyle Gradient::GetStyle() const
+{
+ return mpImplGradient->meStyle;
+}
+
+void Gradient::SetStyle( GradientStyle eStyle )
+{
+ mpImplGradient->meStyle = eStyle;
+}
+
+const Color& Gradient::GetStartColor() const
+{
+ return mpImplGradient->maStartColor;
+}
+
+void Gradient::SetStartColor( const Color& rColor )
+{
+ mpImplGradient->maStartColor = rColor;
+}
+
+const Color& Gradient::GetEndColor() const
+{
+ return mpImplGradient->maEndColor;
+}
+
+void Gradient::SetEndColor( const Color& rColor )
+{
+ mpImplGradient->maEndColor = rColor;
+}
+
+sal_uInt16 Gradient::GetAngle() const
+{
+ return mpImplGradient->mnAngle;
+}
+
+void Gradient::SetAngle( sal_uInt16 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 );
+ sal_uInt16 nAngle = GetAngle() % 3600;
+
+ if( GetStyle() == GradientStyle::Linear || GetStyle() == GradientStyle::Axial )
+ {
+ const double fAngle = nAngle * F_PI1800;
+ 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<long>(fDX) );
+ aRect.AdjustRight(static_cast<long>(fDX) );
+ aRect.AdjustTop( -static_cast<long>(fDY) );
+ aRect.AdjustBottom(static_cast<long>(fDY) );
+
+ rBoundRect = aRect;
+ rCenter = rRect.Center();
+ }
+ else
+ {
+ if( GetStyle() == GradientStyle::Square || GetStyle() == GradientStyle::Rect )
+ {
+ const double fAngle = nAngle * F_PI1800;
+ 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<long>(fDX) );
+ aRect.AdjustRight(static_cast<long>(fDX) );
+ aRect.AdjustTop( -static_cast<long>(fDY) );
+ aRect.AdjustBottom(static_cast<long>(fDY) );
+ }
+
+ Size aSize( aRect.GetSize() );
+
+ if( GetStyle() == GradientStyle::Radial )
+ {
+ // Calculation of radii for circle
+ aSize.setWidth( static_cast<long>(0.5 + sqrt(static_cast<double>(aSize.Width())*static_cast<double>(aSize.Width()) + static_cast<double>(aSize.Height())*static_cast<double>(aSize.Height()))) );
+ aSize.setHeight( aSize.Width() );
+ }
+ else if( GetStyle() == GradientStyle::Elliptical )
+ {
+ // Calculation of radii for ellipse
+ aSize.setWidth( static_cast<long>( 0.5 + static_cast<double>(aSize.Width()) * 1.4142 ) );
+ aSize.setHeight( static_cast<long>( 0.5 + static_cast<double>(aSize.Height()) * 1.4142 ) );
+ }
+
+ // Calculate new centers
+ long nZWidth = aRect.GetWidth() * static_cast<long>(GetOfsX()) / 100;
+ long nZHeight = aRect.GetHeight() * static_cast<long>(GetOfsY()) / 100;
+ long nBorderX = static_cast<long>(GetBorder()) * aSize.Width() / 100;
+ long nBorderY = static_cast<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;
+ }
+}
+
+Gradient& Gradient::operator=( const Gradient& ) = default;
+
+Gradient& Gradient::operator=( Gradient&& ) = default;
+
+bool Gradient::operator==( const Gradient& rGradient ) const
+{
+ return mpImplGradient == rGradient.mpImplGradient;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/graph.cxx b/vcl/source/gdi/graph.cxx
new file mode 100644
index 000000000..4c635b454
--- /dev/null
+++ b/vcl/source/gdi/graph.cxx
@@ -0,0 +1,601 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <cppuhelper/typeprovider.hxx>
+#include <graphic/UnoGraphic.hxx>
+#include <vcl/GraphicExternalLink.hxx>
+
+using namespace ::com::sun::star;
+
+namespace
+{
+
+void ImplDrawDefault( OutputDevice* pOutDev, const OUString* pText,
+ vcl::Font* pFont, const BitmapEx* pBitmapEx,
+ const Point& rDestPt, const Size& rDestSize )
+{
+ sal_uInt16 nPixel = static_cast<sal_uInt16>(pOutDev->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 );
+
+ pOutDev->Push();
+
+ pOutDev->SetFillColor();
+
+ // On the printer a black rectangle and on the screen one with 3D effect
+ if ( pOutDev->GetOutDevType() == OUTDEV_PRINTER )
+ pOutDev->SetLineColor( COL_BLACK );
+ else
+ {
+ aBorderRect.AdjustLeft(nPixel );
+ aBorderRect.AdjustTop(nPixel );
+
+ pOutDev->SetLineColor( COL_LIGHTGRAY );
+ pOutDev->DrawRect( aBorderRect );
+
+ aBorderRect.AdjustLeft( -nPixel );
+ aBorderRect.AdjustTop( -nPixel );
+ aBorderRect.AdjustRight( -nPixel );
+ aBorderRect.AdjustBottom( -nPixel );
+ pOutDev->SetLineColor( COL_GRAY );
+ }
+
+ pOutDev->DrawRect( 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 )
+ {
+ Size aBitmapSize( pOutDev->PixelToLogic( pBitmapEx->GetSizePixel() ) );
+
+ if( aSize.Height() > aBitmapSize.Height() && aSize.Width() > aBitmapSize.Width() )
+ {
+ pOutDev->DrawBitmapEx( aPoint, *pBitmapEx );
+ aPoint.AdjustX(aBitmapSize.Width() + 2*nPixel );
+ aSize.AdjustWidth( -(aBitmapSize.Width() + 2*nPixel) );
+ }
+ }
+
+ if ( !aSize.IsEmpty() && pFont && pText && pText->getLength() && pOutDev->IsOutputEnabled() )
+ {
+ MapMode aMapMode( MapUnit::MapPoint );
+ Size aSz = pOutDev->LogicToLogic( Size( 0, 12 ), &aMapMode, nullptr );
+ long nThreshold = aSz.Height() / 2;
+ long nStep = nThreshold / 3;
+
+ if ( !nStep )
+ nStep = aSz.Height() - nThreshold;
+
+ for(;; aSz.AdjustHeight( -nStep ) )
+ {
+ pFont->SetFontSize( aSz );
+ pOutDev->SetFont( *pFont );
+
+ long nTextHeight = pOutDev->GetTextHeight();
+ long nTextWidth = pOutDev->GetTextWidth( *pText );
+ if ( nTextHeight )
+ {
+ // The approximation does not respect imprecisions caused
+ // by word wraps
+ long nLines = aSize.Height() / nTextHeight;
+ 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 = pOutDev->GetTextWidth( *pText, nStart, nNext );
+ if ( nTextWidth > aSize.Width() )
+ break;
+ nLen = nNext;
+ }
+ while ( nStart+nNext < pText->getLength() );
+
+ sal_Int32 n = nLen;
+ nTextWidth = pOutDev->GetTextWidth( *pText, nStart, n );
+ while( nTextWidth > aSize.Width() )
+ nTextWidth = pOutDev->GetTextWidth( *pText, nStart, --n );
+ pOutDev->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 );
+
+ pOutDev->SetLineColor( COL_LIGHTRED );
+ pOutDev->DrawLine( aBorderRect.TopLeft(), aBorderRect.BottomRight() );
+ pOutDev->DrawLine( aBorderRect.TopRight(), aBorderRect.BottomLeft() );
+ }
+
+ pOutDev->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(GraphicExternalLink const & rGraphicExternalLink)
+ : mxImpGraphic(vcl::graphic::Manager::get().newInstance(rGraphicExternalLink))
+{
+}
+
+Graphic::Graphic(const Bitmap& rBmp)
+ : mxImpGraphic(vcl::graphic::Manager::get().newInstance(rBmp))
+{
+}
+
+Graphic::Graphic(const BitmapEx& rBmpEx)
+ : mxImpGraphic(vcl::graphic::Manager::get().newInstance(rBmpEx))
+{
+}
+
+// We use XGraphic for passing toolbar images across app UNO aps
+// and we need to be able to see and preserve 'stock' images too.
+Graphic::Graphic(const Image& rImage)
+ // FIXME: should really defer the BitmapEx load.
+ : mxImpGraphic(std::make_shared<ImpGraphic>(rImage.GetBitmapEx()))
+{
+ OUString aStock = rImage.GetStock();
+ if (aStock.getLength())
+ mxImpGraphic->setOriginURL("private:graphicrepository/" + aStock);
+}
+
+Graphic::Graphic(const std::shared_ptr<VectorGraphicData>& rVectorGraphicDataPtr)
+ : mxImpGraphic(vcl::graphic::Manager::get().newInstance(rVectorGraphicDataPtr))
+{
+}
+
+Graphic::Graphic(const Animation& rAnimation)
+ : mxImpGraphic(vcl::graphic::Manager::get().newInstance(rAnimation))
+{
+}
+
+Graphic::Graphic(const GDIMetaFile& rMtf)
+ : mxImpGraphic(vcl::graphic::Manager::get().newInstance(rMtf))
+{
+}
+
+Graphic::Graphic( const css::uno::Reference< css::graphic::XGraphic >& rxGraphic )
+{
+ const ::Graphic* pGraphic = comphelper::getUnoTunnelImplementation<::Graphic>(rxGraphic);
+
+ if( pGraphic )
+ {
+ if (pGraphic->IsAnimated())
+ mxImpGraphic = vcl::graphic::Manager::get().copy(pGraphic->mxImpGraphic);
+ else
+ mxImpGraphic = pGraphic->mxImpGraphic;
+ }
+ else
+ mxImpGraphic = vcl::graphic::Manager::get().newInstance();
+}
+
+void Graphic::ImplTestRefCount()
+{
+ if (mxImpGraphic.use_count() > 1)
+ {
+ mxImpGraphic = vcl::graphic::Manager::get().copy(mxImpGraphic);
+ }
+}
+
+bool Graphic::isAvailable() const
+{
+ return mxImpGraphic->isAvailable();
+}
+
+bool Graphic::makeAvailable()
+{
+ return mxImpGraphic->makeAvailable();
+}
+
+Graphic& Graphic::operator=( const Graphic& rGraphic )
+{
+ if( &rGraphic != this )
+ {
+ if( rGraphic.IsAnimated() )
+ mxImpGraphic = vcl::graphic::Manager::get().copy(rGraphic.mxImpGraphic);
+ else
+ mxImpGraphic = rGraphic.mxImpGraphic;
+ }
+
+ return *this;
+}
+
+Graphic& Graphic::operator=(Graphic&& rGraphic) noexcept
+{
+ mxImpGraphic = std::move(rGraphic.mxImpGraphic);
+ return *this;
+}
+
+bool Graphic::operator==( const Graphic& rGraphic ) const
+{
+ return (*mxImpGraphic == *rGraphic.mxImpGraphic);
+}
+
+bool Graphic::operator!=( const Graphic& rGraphic ) const
+{
+ return (*mxImpGraphic != *rGraphic.mxImpGraphic);
+}
+
+bool Graphic::IsNone() const
+{
+ return GraphicType::NONE == mxImpGraphic->ImplGetType();
+}
+
+void Graphic::Clear()
+{
+ ImplTestRefCount();
+ mxImpGraphic->ImplClear();
+}
+
+GraphicType Graphic::GetType() const
+{
+ return mxImpGraphic->ImplGetType();
+}
+
+void Graphic::SetDefaultType()
+{
+ ImplTestRefCount();
+ mxImpGraphic->ImplSetDefaultType();
+}
+
+bool Graphic::IsSupportedGraphic() const
+{
+ return mxImpGraphic->ImplIsSupportedGraphic();
+}
+
+bool Graphic::IsTransparent() const
+{
+ return mxImpGraphic->ImplIsTransparent();
+}
+
+bool Graphic::IsAlpha() const
+{
+ return mxImpGraphic->ImplIsAlpha();
+}
+
+bool Graphic::IsAnimated() const
+{
+ return mxImpGraphic->ImplIsAnimated();
+}
+
+bool Graphic::IsEPS() const
+{
+ return mxImpGraphic->ImplIsEPS();
+}
+
+BitmapEx Graphic::GetBitmapEx(const GraphicConversionParameters& rParameters) const
+{
+ return mxImpGraphic->ImplGetBitmapEx(rParameters);
+}
+
+Animation Graphic::GetAnimation() const
+{
+ return mxImpGraphic->ImplGetAnimation();
+}
+
+const GDIMetaFile& Graphic::GetGDIMetaFile() const
+{
+ return mxImpGraphic->ImplGetGDIMetaFile();
+}
+
+const BitmapEx& Graphic::GetBitmapExRef() const
+{
+ return mxImpGraphic->ImplGetBitmapExRef();
+}
+
+uno::Reference<graphic::XGraphic> Graphic::GetXGraphic() const
+{
+ uno::Reference<graphic::XGraphic> xGraphic;
+
+ if (GetType() != GraphicType::NONE)
+ {
+ unographic::Graphic* pUnoGraphic = new unographic::Graphic;
+ pUnoGraphic->init(*this);
+ xGraphic = pUnoGraphic;
+ }
+
+ return xGraphic;
+}
+
+Size Graphic::GetPrefSize() const
+{
+ return mxImpGraphic->ImplGetPrefSize();
+}
+
+void Graphic::SetPrefSize( const Size& rPrefSize )
+{
+ ImplTestRefCount();
+ mxImpGraphic->ImplSetPrefSize( rPrefSize );
+}
+
+MapMode Graphic::GetPrefMapMode() const
+{
+ return mxImpGraphic->ImplGetPrefMapMode();
+}
+
+void Graphic::SetPrefMapMode( const MapMode& rPrefMapMode )
+{
+ ImplTestRefCount();
+ mxImpGraphic->ImplSetPrefMapMode( 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 = 1000.0 * aGrfPixelSize.Width() / aGrf1000thInchSize.Width();
+ nGrfDPIy = 1000.0 * aGrfPixelSize.Height() / aGrf1000thInchSize.Height();
+ }
+
+ return basegfx::B2DSize(nGrfDPIx, nGrfDPIy);
+}
+
+Size Graphic::GetSizePixel( const OutputDevice* pRefDevice ) const
+{
+ Size aRet;
+
+ if( GraphicType::Bitmap == mxImpGraphic->ImplGetType() )
+ aRet = mxImpGraphic->ImplGetSizePixel();
+ else
+ aRet = ( pRefDevice ? pRefDevice : Application::GetDefaultDevice() )->LogicToPixel( GetPrefSize(), GetPrefMapMode() );
+
+ return aRet;
+}
+
+sal_uLong Graphic::GetSizeBytes() const
+{
+ return mxImpGraphic->ImplGetSizeBytes();
+}
+
+void Graphic::Draw( OutputDevice* pOutDev, const Point& rDestPt ) const
+{
+ mxImpGraphic->ImplDraw( pOutDev, rDestPt );
+}
+
+void Graphic::Draw( OutputDevice* pOutDev,
+ const Point& rDestPt, const Size& rDestSz ) const
+{
+ if( GraphicType::Default == mxImpGraphic->ImplGetType() )
+ ImplDrawDefault( pOutDev, nullptr, nullptr, nullptr, rDestPt, rDestSz );
+ else
+ mxImpGraphic->ImplDraw( pOutDev, rDestPt, rDestSz );
+}
+
+void Graphic::DrawEx( OutputDevice* pOutDev, const OUString& rText,
+ vcl::Font& rFont, const BitmapEx& rBitmap,
+ const Point& rDestPt, const Size& rDestSz )
+{
+ ImplDrawDefault( pOutDev, &rText, &rFont, &rBitmap, rDestPt, rDestSz );
+}
+
+void Graphic::StartAnimation( OutputDevice* pOutDev, const Point& rDestPt,
+ const Size& rDestSz, long nExtraData,
+ OutputDevice* pFirstFrameOutDev )
+{
+ ImplTestRefCount();
+ mxImpGraphic->ImplStartAnimation( pOutDev, rDestPt, rDestSz, nExtraData, pFirstFrameOutDev );
+}
+
+void Graphic::StopAnimation( OutputDevice* pOutDev, long nExtraData )
+{
+ ImplTestRefCount();
+ mxImpGraphic->ImplStopAnimation( pOutDev, nExtraData );
+}
+
+void Graphic::SetAnimationNotifyHdl( const Link<Animation*,void>& rLink )
+{
+ mxImpGraphic->ImplSetAnimationNotifyHdl( rLink );
+}
+
+Link<Animation*,void> Graphic::GetAnimationNotifyHdl() const
+{
+ return mxImpGraphic->ImplGetAnimationNotifyHdl();
+}
+
+sal_uInt32 Graphic::GetAnimationLoopCount() const
+{
+ return mxImpGraphic->ImplGetAnimationLoopCount();
+}
+
+std::shared_ptr<GraphicReader>& Graphic::GetReaderContext()
+{
+ return mxImpGraphic->ImplGetContext();
+}
+
+void Graphic::SetReaderContext( const std::shared_ptr<GraphicReader> &pReader )
+{
+ mxImpGraphic->ImplSetContext( pReader );
+}
+
+void Graphic::SetDummyContext( bool value )
+{
+ mxImpGraphic->ImplSetDummyContext( value );
+}
+
+bool Graphic::IsDummyContext() const
+{
+ return mxImpGraphic->ImplIsDummyContext();
+}
+
+void Graphic::SetGfxLink( const std::shared_ptr<GfxLink>& rGfxLink )
+{
+ ImplTestRefCount();
+ mxImpGraphic->ImplSetLink( rGfxLink );
+}
+
+std::shared_ptr<GfxLink> Graphic::GetSharedGfxLink() const
+{
+ return mxImpGraphic->ImplGetSharedGfxLink();
+}
+
+GfxLink Graphic::GetGfxLink() const
+{
+ return mxImpGraphic->ImplGetLink();
+}
+
+bool Graphic::IsGfxLink() const
+{
+ return mxImpGraphic->ImplIsLink();
+}
+
+BitmapChecksum Graphic::GetChecksum() const
+{
+ return mxImpGraphic->ImplGetChecksum();
+}
+
+bool Graphic::ExportNative( SvStream& rOStream ) const
+{
+ return mxImpGraphic->ImplExportNative( rOStream );
+}
+
+void ReadGraphic(SvStream& rIStream, Graphic& rGraphic)
+{
+ rGraphic.ImplTestRefCount();
+ ReadImpGraphic(rIStream, *rGraphic.mxImpGraphic);
+}
+
+void WriteGraphic( SvStream& rOStream, const Graphic& rGraphic )
+{
+ WriteImpGraphic(rOStream, *rGraphic.mxImpGraphic);
+}
+
+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;
+}
+
+namespace {
+
+struct Id: public rtl::Static<cppu::OImplementationId, Id> {};
+
+}
+
+css::uno::Sequence<sal_Int8> Graphic::getUnoTunnelId() {
+ return Id::get().getImplementationId();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/graphictools.cxx b/vcl/source/gdi/graphictools.cxx
new file mode 100644
index 000000000..4be1b43fa
--- /dev/null
+++ b/vcl/source/gdi/graphictools.cxx
@@ -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 <tools/stream.hxx>
+#include <tools/vcompat.hxx>
+#include <tools/GenericTypeSerializer.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() :
+ maPath(),
+ maStartArrow(),
+ maEndArrow(),
+ mfTransparency(),
+ mfStrokeWidth(),
+ maCapType(),
+ maJoinType(),
+ mfMiterLimit( 3.0 ),
+ maDashArray()
+{
+}
+
+SvtGraphicStroke::SvtGraphicStroke( const tools::Polygon& rPath,
+ const tools::PolyPolygon& rStartArrow,
+ const tools::PolyPolygon& rEndArrow,
+ double fTransparency,
+ double fStrokeWidth,
+ CapType aCap,
+ JoinType aJoin,
+ double fMiterLimit,
+ const DashArray& rDashArray ) :
+ maPath( rPath ),
+ maStartArrow( rStartArrow ),
+ maEndArrow( rEndArrow ),
+ mfTransparency( fTransparency ),
+ mfStrokeWidth( fStrokeWidth ),
+ maCapType( aCap ),
+ maJoinType( aJoin ),
+ mfMiterLimit( fMiterLimit ),
+ maDashArray( 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 )
+{
+ VersionCompat aCompat( rOStm, StreamMode::WRITE, 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 )
+{
+ VersionCompat aCompat( rIStm, StreamMode::READ );
+
+ 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() :
+ maPath(),
+ maFillColor( COL_BLACK ),
+ mfTransparency(),
+ maFillRule(),
+ maFillType(),
+ maFillTransform(),
+ mbTiling( false ),
+ maHatchType(),
+ maHatchColor( COL_BLACK ),
+ maGradientType(),
+ maGradient1stColor( COL_BLACK ),
+ maGradient2ndColor( COL_BLACK ),
+ maGradientStepCount( gradientStepsInfinite ),
+ maFillGraphic()
+{
+}
+
+SvtGraphicFill::SvtGraphicFill( const tools::PolyPolygon& rPath,
+ Color aFillColor,
+ double fTransparency,
+ FillRule aFillRule,
+ FillType aFillType,
+ const Transform& aFillTransform,
+ bool bTiling,
+ HatchType aHatchType,
+ Color aHatchColor,
+ GradientType aGradientType,
+ Color aGradient1stColor,
+ Color aGradient2ndColor,
+ sal_Int32 aGradientStepCount,
+ const Graphic& aFillGraphic ) :
+ maPath( rPath ),
+ maFillColor( aFillColor ),
+ mfTransparency( fTransparency ),
+ maFillRule( aFillRule ),
+ maFillType( aFillType ),
+ maFillTransform( aFillTransform ),
+ mbTiling( bTiling ),
+ maHatchType( aHatchType ),
+ maHatchColor( aHatchColor ),
+ maGradientType( aGradientType ),
+ maGradient1stColor( aGradient1stColor ),
+ maGradient2ndColor( aGradient2ndColor ),
+ maGradientStepCount( aGradientStepCount ),
+ maFillGraphic( aFillGraphic )
+{
+}
+
+void SvtGraphicFill::getPath( tools::PolyPolygon& rPath ) const
+{
+ rPath = maPath;
+}
+
+
+void SvtGraphicFill::getTransform( Transform& rTrans ) const
+{
+ rTrans = maFillTransform;
+}
+
+
+void SvtGraphicFill::getGraphic( Graphic& rGraphic ) const
+{
+ rGraphic = maFillGraphic;
+}
+
+void SvtGraphicFill::setPath( const tools::PolyPolygon& rPath )
+{
+ maPath = rPath;
+}
+
+SvStream& WriteSvtGraphicFill( SvStream& rOStm, const SvtGraphicFill& rClass )
+{
+ VersionCompat aCompat( rOStm, StreamMode::WRITE, 1 );
+
+ rClass.maPath.Write( rOStm );
+ tools::GenericTypeSerializer 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 );
+ WriteGraphic( rOStm, rClass.maFillGraphic );
+
+ return rOStm;
+}
+
+SvStream& ReadSvtGraphicFill( SvStream& rIStm, SvtGraphicFill& rClass )
+{
+ VersionCompat aCompat( rIStm, StreamMode::READ );
+
+ rClass.maPath.Read( rIStm );
+
+ tools::GenericTypeSerializer 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 );
+ ReadGraphic( rIStm, rClass.maFillGraphic );
+
+ return rIStm;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/hatch.cxx b/vcl/source/gdi/hatch.cxx
new file mode 100644
index 000000000..06b15f1bb
--- /dev/null
+++ b/vcl/source/gdi/hatch.cxx
@@ -0,0 +1,110 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <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,
+ long nDistance, sal_uInt16 nAngle10 ) : mpImplHatch()
+{
+ 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( long nDistance )
+{
+ mpImplHatch->mnDistance = nDistance;
+}
+
+void Hatch::SetAngle( sal_uInt16 nAngle10 )
+{
+ mpImplHatch->mnAngle = nAngle10;
+}
+
+SvStream& ReadHatch( SvStream& rIStm, Hatch& rHatch )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ sal_uInt16 nTmp16;
+ sal_Int32 nTmp32(0);
+
+ rIStm.ReadUInt16(nTmp16);
+ rHatch.mpImplHatch->meStyle = static_cast<HatchStyle>(nTmp16);
+
+ tools::GenericTypeSerializer aSerializer(rIStm);
+ aSerializer.readColor(rHatch.mpImplHatch->maColor);
+ rIStm.ReadInt32(nTmp32);
+ rIStm.ReadUInt16(rHatch.mpImplHatch->mnAngle);
+ rHatch.mpImplHatch->mnDistance = nTmp32;
+
+ return rIStm;
+}
+
+SvStream& WriteHatch( SvStream& rOStm, const Hatch& rHatch )
+{
+ VersionCompat aCompat( rOStm, StreamMode::WRITE, 1 );
+
+ rOStm.WriteUInt16( static_cast<sal_uInt16>(rHatch.mpImplHatch->meStyle) );
+
+ tools::GenericTypeSerializer aSerializer(rOStm);
+ aSerializer.writeColor(rHatch.mpImplHatch->maColor);
+ rOStm.WriteInt32( rHatch.mpImplHatch->mnDistance ).WriteUInt16( rHatch.mpImplHatch->mnAngle );
+
+ return rOStm;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/impanmvw.cxx b/vcl/source/gdi/impanmvw.cxx
new file mode 100644
index 000000000..abdc376b2
--- /dev/null
+++ b/vcl/source/gdi/impanmvw.cxx
@@ -0,0 +1,321 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <memory>
+#include <impanmvw.hxx>
+
+#include <vcl/virdev.hxx>
+#include <vcl/window.hxx>
+#include <tools/helpers.hxx>
+
+#include <window.h>
+
+ImplAnimView::ImplAnimView( Animation* pParent, OutputDevice* pOut,
+ const Point& rPt, const Size& rSz,
+ sal_uLong nExtraData,
+ OutputDevice* pFirstFrameOutDev ) :
+ mpParent ( pParent ),
+ mpRenderContext ( pFirstFrameOutDev ? pFirstFrameOutDev : pOut ),
+ mnExtraData ( nExtraData ),
+ maPt ( rPt ),
+ maSz ( rSz ),
+ maSzPix ( mpRenderContext->LogicToPixel( maSz ) ),
+ maClip ( mpRenderContext->GetClipRegion() ),
+ mpBackground ( VclPtr<VirtualDevice>::Create() ),
+ mpRestore ( VclPtr<VirtualDevice>::Create() ),
+ meLastDisposal ( Disposal::Back ),
+ mbIsPaused ( false ),
+ mbIsMarked ( false ),
+ mbIsMirroredHorizontally ( maSz.Width() < 0 ),
+ mbIsMirroredVertically ( maSz.Height() < 0 )
+{
+ Animation::ImplIncAnimCount();
+
+ // Mirrored horizontally?
+ if( mbIsMirroredHorizontally )
+ {
+ maDispPt.setX( maPt.X() + maSz.Width() + 1 );
+ maDispSz.setWidth( -maSz.Width() );
+ maSzPix.setWidth( -maSzPix.Width() );
+ }
+ else
+ {
+ maDispPt.setX( maPt.X() );
+ maDispSz.setWidth( maSz.Width() );
+ }
+
+ // Mirrored vertically?
+ if( mbIsMirroredVertically )
+ {
+ maDispPt.setY( maPt.Y() + maSz.Height() + 1 );
+ maDispSz.setHeight( -maSz.Height() );
+ maSzPix.setHeight( -maSzPix.Height() );
+ }
+ else
+ {
+ maDispPt.setY( maPt.Y() );
+ maDispSz.setHeight( maSz.Height() );
+ }
+
+ // save background
+ mpBackground->SetOutputSizePixel( maSzPix );
+ mpRenderContext->SaveBackground(*mpBackground, maDispPt, maDispSz, maSzPix);
+
+ // Initialize drawing to actual position
+ drawToPos( mpParent->ImplGetCurPos() );
+
+ // If first frame OutputDevice is set, update variables now for real OutputDevice
+ if( pFirstFrameOutDev )
+ {
+ mpRenderContext = pOut;
+ maClip = mpRenderContext->GetClipRegion();
+ }
+}
+
+ImplAnimView::~ImplAnimView()
+{
+ mpBackground.disposeAndClear();
+ mpRestore.disposeAndClear();
+
+ Animation::ImplDecAnimCount();
+}
+
+bool ImplAnimView::matches(const OutputDevice* pOut, long nExtraData) const
+{
+ return (!pOut || pOut == mpRenderContext) && (nExtraData == 0 || nExtraData == mnExtraData);
+}
+
+void ImplAnimView::getPosSize( const AnimationBitmap& rAnimationBitmap, Point& rPosPix, Size& rSizePix )
+{
+ const Size& rAnmSize = mpParent->GetDisplaySizePixel();
+ Point aPt2( rAnimationBitmap.maPositionPixel.X() + rAnimationBitmap.maSizePixel.Width() - 1,
+ rAnimationBitmap.maPositionPixel.Y() + rAnimationBitmap.maSizePixel.Height() - 1 );
+ double fFactX, fFactY;
+
+ // calculate x scaling
+ if( rAnmSize.Width() > 1 )
+ fFactX = static_cast<double>( maSzPix.Width() - 1 ) / ( rAnmSize.Width() - 1 );
+ else
+ fFactX = 1.0;
+
+ // calculate y scaling
+ if( rAnmSize.Height() > 1 )
+ fFactY = static_cast<double>( maSzPix.Height() - 1 ) / ( rAnmSize.Height() - 1 );
+ else
+ fFactY = 1.0;
+
+ rPosPix.setX( FRound( rAnimationBitmap.maPositionPixel.X() * fFactX ) );
+ rPosPix.setY( FRound( rAnimationBitmap.maPositionPixel.Y() * fFactY ) );
+
+ aPt2.setX( FRound( aPt2.X() * fFactX ) );
+ aPt2.setY( FRound( aPt2.Y() * fFactY ) );
+
+ rSizePix.setWidth( aPt2.X() - rPosPix.X() + 1 );
+ rSizePix.setHeight( aPt2.Y() - rPosPix.Y() + 1 );
+
+ // Mirrored horizontally?
+ if( mbIsMirroredHorizontally )
+ rPosPix.setX( maSzPix.Width() - 1 - aPt2.X() );
+
+ // Mirrored vertically?
+ if( mbIsMirroredVertically )
+ rPosPix.setY( maSzPix.Height() - 1 - aPt2.Y() );
+}
+
+void ImplAnimView::drawToPos( sal_uLong nPos )
+{
+ VclPtr<vcl::RenderContext> pRenderContext = mpRenderContext;
+
+ std::unique_ptr<vcl::PaintBufferGuard> pGuard;
+ if (mpRenderContext->GetOutDevType() == OUTDEV_WINDOW)
+ {
+ vcl::Window* pWindow = static_cast<vcl::Window*>(mpRenderContext.get());
+ pGuard.reset(new vcl::PaintBufferGuard(pWindow->ImplGetWindowImpl()->mpFrameData, pWindow));
+ pRenderContext = pGuard->GetRenderContext();
+ }
+
+ ScopedVclPtrInstance<VirtualDevice> aVDev;
+ std::unique_ptr<vcl::Region> xOldClip(!maClip.IsNull() ? new vcl::Region( pRenderContext->GetClipRegion() ) : nullptr);
+
+ aVDev->SetOutputSizePixel( maSzPix, false );
+ nPos = std::min( nPos, static_cast<sal_uLong>(mpParent->Count()) - 1 );
+
+ for( sal_uLong i = 0; i <= nPos; i++ )
+ draw( i, aVDev.get() );
+
+ if (xOldClip)
+ pRenderContext->SetClipRegion( maClip );
+
+ pRenderContext->DrawOutDev( maDispPt, maDispSz, Point(), maSzPix, *aVDev );
+ if (pGuard)
+ pGuard->SetPaintRect(tools::Rectangle(maDispPt, maDispSz));
+
+ if (xOldClip)
+ pRenderContext->SetClipRegion(*xOldClip);
+}
+
+void ImplAnimView::draw( sal_uLong nPos, VirtualDevice* pVDev )
+{
+ VclPtr<vcl::RenderContext> pRenderContext = mpRenderContext;
+
+ std::unique_ptr<vcl::PaintBufferGuard> pGuard;
+ if (!pVDev && mpRenderContext->GetOutDevType() == OUTDEV_WINDOW)
+ {
+ vcl::Window* pWindow = static_cast<vcl::Window*>(mpRenderContext.get());
+ pGuard.reset(new vcl::PaintBufferGuard(pWindow->ImplGetWindowImpl()->mpFrameData, pWindow));
+ pRenderContext = pGuard->GetRenderContext();
+ }
+
+ tools::Rectangle aOutRect( pRenderContext->PixelToLogic( Point() ), pRenderContext->GetOutputSize() );
+
+ // check, if output lies out of display
+ if( aOutRect.Intersection( tools::Rectangle( maDispPt, maDispSz ) ).IsEmpty() )
+ setMarked( true );
+ else if( !mbIsPaused )
+ {
+ VclPtr<VirtualDevice> pDev;
+ Point aPosPix;
+ Point aBmpPosPix;
+ Size aSizePix;
+ Size aBmpSizePix;
+ const sal_uLong nLastPos = mpParent->Count() - 1;
+ mnActPos = std::min( nPos, nLastPos );
+ const AnimationBitmap& rAnimationBitmap = mpParent->Get( static_cast<sal_uInt16>( mnActPos ) );
+
+ getPosSize( rAnimationBitmap, aPosPix, aSizePix );
+
+ // Mirrored horizontally?
+ if( mbIsMirroredHorizontally )
+ {
+ aBmpPosPix.setX( aPosPix.X() + aSizePix.Width() - 1 );
+ aBmpSizePix.setWidth( -aSizePix.Width() );
+ }
+ else
+ {
+ aBmpPosPix.setX( aPosPix.X() );
+ aBmpSizePix.setWidth( aSizePix.Width() );
+ }
+
+ // Mirrored vertically?
+ if( mbIsMirroredVertically )
+ {
+ aBmpPosPix.setY( aPosPix.Y() + aSizePix.Height() - 1 );
+ aBmpSizePix.setHeight( -aSizePix.Height() );
+ }
+ else
+ {
+ aBmpPosPix.setY( aPosPix.Y() );
+ aBmpSizePix.setHeight( aSizePix.Height() );
+ }
+
+ // get output device
+ if( !pVDev )
+ {
+ pDev = VclPtr<VirtualDevice>::Create();
+ pDev->SetOutputSizePixel( maSzPix, false );
+ pDev->DrawOutDev( Point(), maSzPix, maDispPt, maDispSz, *pRenderContext );
+ }
+ else
+ pDev = pVDev;
+
+ // restore background after each run
+ if( !nPos )
+ {
+ meLastDisposal = Disposal::Back;
+ maRestPt = Point();
+ maRestSz = maSzPix;
+ }
+
+ // restore
+ if( ( Disposal::Not != meLastDisposal ) && maRestSz.Width() && maRestSz.Height() )
+ {
+ if( Disposal::Back == meLastDisposal )
+ pDev->DrawOutDev( maRestPt, maRestSz, maRestPt, maRestSz, *mpBackground );
+ else
+ pDev->DrawOutDev( maRestPt, maRestSz, Point(), maRestSz, *mpRestore );
+ }
+
+ meLastDisposal = rAnimationBitmap.meDisposal;
+ maRestPt = aPosPix;
+ maRestSz = aSizePix;
+
+ // What do we need to restore the next time?
+ // Put it into a bitmap if needed, else delete
+ // SaveBitmap to conserve memory
+ if( ( meLastDisposal == Disposal::Back ) || ( meLastDisposal == Disposal::Not ) )
+ mpRestore->SetOutputSizePixel( Size( 1, 1 ), false );
+ else
+ {
+ mpRestore->SetOutputSizePixel( maRestSz, false );
+ mpRestore->DrawOutDev( Point(), maRestSz, aPosPix, aSizePix, *pDev );
+ }
+
+ pDev->DrawBitmapEx( aBmpPosPix, aBmpSizePix, rAnimationBitmap.maBitmapEx );
+
+ if( !pVDev )
+ {
+ std::unique_ptr<vcl::Region> xOldClip(!maClip.IsNull() ? new vcl::Region( pRenderContext->GetClipRegion() ) : nullptr);
+
+ if (xOldClip)
+ pRenderContext->SetClipRegion( maClip );
+
+ pRenderContext->DrawOutDev( maDispPt, maDispSz, Point(), maSzPix, *pDev );
+ if (pGuard)
+ pGuard->SetPaintRect(tools::Rectangle(maDispPt, maDispSz));
+
+ if( xOldClip)
+ {
+ pRenderContext->SetClipRegion(*xOldClip);
+ xOldClip.reset();
+ }
+
+ pDev.disposeAndClear();
+
+ if( pRenderContext->GetOutDevType() == OUTDEV_WINDOW )
+ static_cast<vcl::Window*>( pRenderContext.get() )->Flush();
+ }
+ }
+}
+
+void ImplAnimView::repaint()
+{
+ const bool bOldPause = mbIsPaused;
+
+ mpRenderContext->SaveBackground(*mpBackground, maDispPt, maDispSz, maSzPix);
+
+ mbIsPaused = false;
+ drawToPos( mnActPos );
+ mbIsPaused = bOldPause;
+}
+
+AInfo* ImplAnimView::createAInfo() const
+{
+ AInfo* pAInfo = new AInfo;
+
+ pAInfo->aStartOrg = maPt;
+ pAInfo->aStartSize = maSz;
+ pAInfo->pOutDev = mpRenderContext;
+ pAInfo->pViewData = const_cast<ImplAnimView *>(this);
+ pAInfo->nExtraData = mnExtraData;
+ pAInfo->bPause = mbIsPaused;
+
+ return pAInfo;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/impglyphitem.cxx b/vcl/source/gdi/impglyphitem.cxx
new file mode 100644
index 000000000..4bb53d4a4
--- /dev/null
+++ b/vcl/source/gdi/impglyphitem.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 <impglyphitem.hxx>
+
+#if (defined UNX && !defined MACOSX && !defined IOS)
+#include <unx/freetype_glyphcache.hxx>
+#endif
+
+SalLayoutGlyphs::SalLayoutGlyphs()
+ : m_pImpl(nullptr)
+{
+}
+
+SalLayoutGlyphs::~SalLayoutGlyphs() { delete m_pImpl; }
+
+SalLayoutGlyphs::SalLayoutGlyphs(const SalLayoutGlyphs& rOther)
+{
+ m_pImpl = rOther.m_pImpl ? rOther.m_pImpl->clone(*this) : nullptr;
+}
+
+SalLayoutGlyphs& SalLayoutGlyphs::operator=(const SalLayoutGlyphs& rOther)
+{
+ if (this != &rOther)
+ {
+ delete m_pImpl;
+ m_pImpl = rOther.m_pImpl ? rOther.m_pImpl->clone(*this) : nullptr;
+ }
+ return *this;
+}
+
+bool SalLayoutGlyphs::IsValid() const { return m_pImpl && m_pImpl->IsValid(); }
+
+void SalLayoutGlyphs::Invalidate()
+{
+ if (m_pImpl)
+ m_pImpl->Invalidate();
+}
+
+SalLayoutGlyphsImpl* SalLayoutGlyphsImpl::clone(SalLayoutGlyphs& rGlyphs) const
+{
+ SalLayoutGlyphsImpl* pNew = new SalLayoutGlyphsImpl(rGlyphs, *m_rFontInstance);
+ *pNew = *this;
+ return pNew;
+}
+
+bool SalLayoutGlyphsImpl::IsValid() const
+{
+ if (!m_rFontInstance.is())
+ return false;
+ if (empty())
+ return false;
+ return true;
+}
+
+void SalLayoutGlyphsImpl::Invalidate()
+{
+ m_rFontInstance.clear();
+ clear();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/impgraph.cxx b/vcl/source/gdi/impgraph.cxx
new file mode 100644
index 000000000..e06663706
--- /dev/null
+++ b/vcl/source/gdi/impgraph.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 <sal/log.hxx>
+
+#include <comphelper/fileformat.h>
+#include <o3tl/deleter.hxx>
+#include <tools/fract.hxx>
+#include <tools/vcompat.hxx>
+#include <tools/urlobj.hxx>
+#include <tools/stream.hxx>
+#include <unotools/ucbhelper.hxx>
+#include <unotools/ucbstreamhelper.hxx>
+#include <unotools/tempfile.hxx>
+#include <vcl/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 <vcl/dibtools.hxx>
+#include <map>
+#include <memory>
+#include <vcl/gdimetafiletools.hxx>
+#include <TypeSerializer.hxx>
+#include <vcl/pdfread.hxx>
+
+#define GRAPHIC_MTFTOBMP_MAXEXT 2048
+#define GRAPHIC_STREAMBUFSIZE 8192UL
+
+#define SYS_WINMETAFILE 0x00000003L
+#define SYS_WNTMETAFILE 0x00000004L
+#define SYS_OS2METAFILE 0x00000005L
+#define SYS_MACMETAFILE 0x00000006L
+
+#define GRAPHIC_FORMAT_50 COMPAT_FORMAT( 'G', 'R', 'F', '5' )
+#define NATIVE_FORMAT_50 COMPAT_FORMAT( 'N', 'A', 'T', '5' )
+
+namespace {
+
+constexpr sal_uInt32 constSvgMagic((sal_uInt32('s') << 24) | (sal_uInt32('v') << 16) | (sal_uInt32('g') << 8) | sal_uInt32('0'));
+constexpr sal_uInt32 constWmfMagic((sal_uInt32('w') << 24) | (sal_uInt32('m') << 16) | (sal_uInt32('f') << 8) | sal_uInt32('0'));
+constexpr sal_uInt32 constEmfMagic((sal_uInt32('e') << 24) | (sal_uInt32('m') << 16) | (sal_uInt32('f') << 8) | sal_uInt32('0'));
+constexpr sal_uInt32 constPdfMagic((sal_uInt32('s') << 24) | (sal_uInt32('v') << 16) | (sal_uInt32('g') << 8) | sal_uInt32('0'));
+
+}
+
+using namespace com::sun::star;
+
+class ImpSwapFile
+{
+private:
+ INetURLObject maSwapURL;
+ OUString maOriginURL;
+
+public:
+ ImpSwapFile(INetURLObject const & rSwapURL, OUString const & rOriginURL)
+ : maSwapURL(rSwapURL)
+ , maOriginURL(rOriginURL)
+ {
+ }
+
+ ~ImpSwapFile() COVERITY_NOEXCEPT_FALSE
+ {
+ utl::UCBContentHelper::Kill(maSwapURL.GetMainURL(INetURLObject::DecodeMechanism::NONE));
+ }
+
+ INetURLObject getSwapURL()
+ {
+ return maSwapURL;
+ }
+
+ OUString getSwapURLString()
+ {
+ return maSwapURL.GetMainURL(INetURLObject::DecodeMechanism::NONE);
+ }
+
+ OUString const & getOriginURL() { return maOriginURL; }
+
+ std::unique_ptr<SvStream> openOutputStream()
+ {
+ OUString sSwapURL = getSwapURLString();
+ if (!sSwapURL.isEmpty())
+ {
+ try
+ {
+ return utl::UcbStreamHelper::CreateStream(sSwapURL, StreamMode::READWRITE | StreamMode::SHARE_DENYWRITE);
+ }
+ catch (const css::uno::Exception&)
+ {
+ }
+ }
+ return std::unique_ptr<SvStream>();
+ }
+};
+
+OUString ImpGraphic::getSwapFileURL()
+{
+ if (mpSwapFile)
+ return mpSwapFile->getSwapURL().GetMainURL(INetURLObject::DecodeMechanism::NONE);
+ return OUString();
+}
+
+ImpGraphic::ImpGraphic() :
+ meType ( GraphicType::NONE ),
+ mnSizeBytes ( 0 ),
+ mbSwapOut ( false ),
+ mbDummyContext ( false ),
+ maLastUsed (std::chrono::high_resolution_clock::now()),
+ mbPrepared ( false )
+{
+}
+
+ImpGraphic::ImpGraphic(const ImpGraphic& rImpGraphic)
+ : maMetaFile(rImpGraphic.maMetaFile)
+ , maBitmapEx(rImpGraphic.maBitmapEx)
+ , maSwapInfo(rImpGraphic.maSwapInfo)
+ , mpContext(rImpGraphic.mpContext)
+ , mpSwapFile(rImpGraphic.mpSwapFile)
+ , mpGfxLink(rImpGraphic.mpGfxLink)
+ , meType(rImpGraphic.meType)
+ , mnSizeBytes(rImpGraphic.mnSizeBytes)
+ , mbSwapOut(rImpGraphic.mbSwapOut)
+ , mbDummyContext(rImpGraphic.mbDummyContext)
+ , maVectorGraphicData(rImpGraphic.maVectorGraphicData)
+ , maGraphicExternalLink(rImpGraphic.maGraphicExternalLink)
+ , maLastUsed (std::chrono::high_resolution_clock::now())
+ , mbPrepared (rImpGraphic.mbPrepared)
+{
+ if( rImpGraphic.mpAnimation )
+ {
+ mpAnimation = std::make_unique<Animation>( *rImpGraphic.mpAnimation );
+ maBitmapEx = mpAnimation->GetBitmapEx();
+ }
+}
+
+ImpGraphic::ImpGraphic(ImpGraphic&& rImpGraphic) noexcept
+ : maMetaFile(std::move(rImpGraphic.maMetaFile))
+ , maBitmapEx(std::move(rImpGraphic.maBitmapEx))
+ , maSwapInfo(std::move(rImpGraphic.maSwapInfo))
+ , mpAnimation(std::move(rImpGraphic.mpAnimation))
+ , mpContext(std::move(rImpGraphic.mpContext))
+ , mpSwapFile(std::move(rImpGraphic.mpSwapFile))
+ , mpGfxLink(std::move(rImpGraphic.mpGfxLink))
+ , meType(rImpGraphic.meType)
+ , mnSizeBytes(rImpGraphic.mnSizeBytes)
+ , mbSwapOut(rImpGraphic.mbSwapOut)
+ , mbDummyContext(rImpGraphic.mbDummyContext)
+ , maVectorGraphicData(std::move(rImpGraphic.maVectorGraphicData))
+ , maGraphicExternalLink(rImpGraphic.maGraphicExternalLink)
+ , maLastUsed (std::chrono::high_resolution_clock::now())
+ , mbPrepared (rImpGraphic.mbPrepared)
+{
+ rImpGraphic.ImplClear();
+ rImpGraphic.mbDummyContext = false;
+}
+
+ImpGraphic::ImpGraphic(GraphicExternalLink const & rGraphicExternalLink) :
+ meType ( GraphicType::Default ),
+ mnSizeBytes ( 0 ),
+ mbSwapOut ( false ),
+ mbDummyContext ( false ),
+ maGraphicExternalLink(rGraphicExternalLink),
+ maLastUsed (std::chrono::high_resolution_clock::now()),
+ mbPrepared (false)
+{
+}
+
+ImpGraphic::ImpGraphic( const Bitmap& rBitmap ) :
+ maBitmapEx ( rBitmap ),
+ meType ( !rBitmap.IsEmpty() ? GraphicType::Bitmap : GraphicType::NONE ),
+ mnSizeBytes ( 0 ),
+ mbSwapOut ( false ),
+ mbDummyContext ( false ),
+ 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.ImplClear();
+ rImpGraphic.mbDummyContext = false;
+ maLastUsed = std::chrono::high_resolution_clock::now();
+
+ vcl::graphic::Manager::get().changeExisting(this, aOldSizeBytes);
+
+ return *this;
+}
+
+bool ImpGraphic::operator==( const ImpGraphic& rImpGraphic ) const
+{
+ bool bRet = false;
+
+ if( this == &rImpGraphic )
+ bRet = true;
+ else if (mbPrepared && rImpGraphic.mbPrepared)
+ {
+ bRet = (*mpGfxLink == *rImpGraphic.mpGfxLink);
+ }
+ else if (isAvailable() && rImpGraphic.isAvailable())
+ {
+ switch( meType )
+ {
+ case GraphicType::NONE:
+ bRet = true;
+ break;
+
+ case GraphicType::GdiMetafile:
+ {
+ if( rImpGraphic.maMetaFile == maMetaFile )
+ bRet = true;
+ }
+ break;
+
+ case GraphicType::Bitmap:
+ {
+ if(maVectorGraphicData)
+ {
+ if(maVectorGraphicData == rImpGraphic.maVectorGraphicData)
+ {
+ // equal instances
+ bRet = true;
+ }
+ else if(rImpGraphic.maVectorGraphicData)
+ {
+ // equal content
+ bRet = (*maVectorGraphicData) == (*rImpGraphic.maVectorGraphicData);
+ }
+ }
+ else if( mpAnimation )
+ {
+ if( rImpGraphic.mpAnimation && ( *rImpGraphic.mpAnimation == *mpAnimation ) )
+ bRet = true;
+ }
+ else if( !rImpGraphic.mpAnimation && ( rImpGraphic.maBitmapEx == maBitmapEx ) )
+ {
+ bRet = true;
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return bRet;
+}
+
+const std::shared_ptr<VectorGraphicData>& ImpGraphic::getVectorGraphicData() const
+{
+ ensureAvailable();
+
+ return maVectorGraphicData;
+}
+
+void ImpGraphic::createSwapInfo()
+{
+ if (isSwappedOut())
+ return;
+
+ maSwapInfo.maPrefMapMode = ImplGetPrefMapMode();
+ maSwapInfo.maPrefSize = ImplGetPrefSize();
+ maSwapInfo.mbIsAnimated = ImplIsAnimated();
+ maSwapInfo.mbIsEPS = ImplIsEPS();
+ maSwapInfo.mbIsTransparent = ImplIsTransparent();
+ maSwapInfo.mbIsAlpha = ImplIsAlpha();
+ maSwapInfo.mnAnimationLoopCount = ImplGetAnimationLoopCount();
+}
+
+void ImpGraphic::ImplClearGraphics()
+{
+ maBitmapEx.Clear();
+ maMetaFile.Clear();
+ mpAnimation.reset();
+ mpGfxLink.reset();
+ maVectorGraphicData.reset();
+}
+
+void ImpGraphic::ImplSetPrepared(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 (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;
+}
+
+void ImpGraphic::ImplClear()
+{
+ mpSwapFile.reset();
+ mbSwapOut = false;
+ mbPrepared = false;
+
+ // cleanup
+ ImplClearGraphics();
+ meType = GraphicType::NONE;
+ sal_Int64 nOldSize = mnSizeBytes;
+ mnSizeBytes = 0;
+ vcl::graphic::Manager::get().changeExisting(this, nOldSize);
+ maGraphicExternalLink.msURL.clear();
+}
+
+void ImpGraphic::ImplSetDefaultType()
+{
+ ImplClear();
+ meType = GraphicType::Default;
+}
+
+bool ImpGraphic::ImplIsSupportedGraphic() const
+{
+ return( meType != GraphicType::NONE );
+}
+
+bool ImpGraphic::ImplIsTransparent() const
+{
+ bool bRet(true);
+
+ if (mbSwapOut)
+ {
+ bRet = maSwapInfo.mbIsTransparent;
+ }
+ else if (meType == GraphicType::Bitmap && !maVectorGraphicData)
+ {
+ bRet = mpAnimation ? mpAnimation->IsTransparent() : maBitmapEx.IsTransparent();
+ }
+
+ return bRet;
+}
+
+bool ImpGraphic::ImplIsAlpha() 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::ImplIsAnimated() const
+{
+ return mbSwapOut ? maSwapInfo.mbIsAnimated : mpAnimation != nullptr;
+}
+
+bool ImpGraphic::ImplIsEPS() 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::ImplGetBitmap(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 ) && ImplIsSupportedGraphic() )
+ {
+ 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 == ImplGetType())
+ {
+ // get hairline and full bound rect
+ tools::Rectangle aHairlineRect;
+ const tools::Rectangle aRect(maMetaFile.GetBoundRect(*aVDev, &aHairlineRect));
+
+ if(!aRect.IsEmpty() && !aHairlineRect.IsEmpty())
+ {
+ // expand if needed to allow bottom and right hairlines to be added
+ if(aRect.Right() == aHairlineRect.Right())
+ {
+ aPixelSize.setWidth(aPixelSize.getWidth() + 1);
+ }
+
+ if(aRect.Bottom() == aHairlineRect.Bottom())
+ {
+ aPixelSize.setHeight(aPixelSize.getHeight() + 1);
+ }
+ }
+ }
+
+ if(aVDev->SetOutputSizePixel(aPixelSize))
+ {
+ if(rParameters.getAntiAliase())
+ {
+ aVDev->SetAntialiasing(aVDev->GetAntialiasing() | AntialiasingFlags::EnableB2dDraw);
+ }
+
+ if(rParameters.getSnapHorVerLines())
+ {
+ aVDev->SetAntialiasing(aVDev->GetAntialiasing() | AntialiasingFlags::PixelSnapHairline);
+ }
+
+ ImplDraw( aVDev.get(), 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 )
+ {
+ aRetBmp.SetPrefMapMode( ImplGetPrefMapMode() );
+ aRetBmp.SetPrefSize( ImplGetPrefSize() );
+ }
+
+ return aRetBmp;
+}
+
+BitmapEx ImpGraphic::ImplGetBitmapEx(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 ) && ImplIsSupportedGraphic() )
+ {
+ if(maBitmapEx.IsEmpty())
+ {
+ const ImpGraphic aMonoMask( maMetaFile.GetMonochromeMtf( COL_BLACK ) );
+
+ // use maBitmapEx as local buffer for rendered metafile
+ const_cast< ImpGraphic* >(this)->maBitmapEx = BitmapEx(ImplGetBitmap(rParameters), aMonoMask.ImplGetBitmap(rParameters));
+ }
+
+ aRetBmpEx = maBitmapEx;
+ }
+
+ return aRetBmpEx;
+}
+
+Animation ImpGraphic::ImplGetAnimation() const
+{
+ Animation aAnimation;
+
+ ensureAvailable();
+ if( mpAnimation )
+ aAnimation = *mpAnimation;
+
+ return aAnimation;
+}
+
+const BitmapEx& ImpGraphic::ImplGetBitmapExRef() const
+{
+ ensureAvailable();
+ return maBitmapEx;
+}
+
+const GDIMetaFile& ImpGraphic::ImplGetGDIMetaFile() const
+{
+ ensureAvailable();
+ if (!maMetaFile.GetActionSize()
+ && maVectorGraphicData
+ && (VectorGraphicDataType::Emf == maVectorGraphicData->getVectorGraphicDataType()
+ || VectorGraphicDataType::Wmf == maVectorGraphicData->getVectorGraphicDataType()))
+ {
+ // 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]);
+ const MetafileAccessor* pMetafileAccessor = dynamic_cast< const MetafileAccessor* >(xReference.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)
+ {
+ // 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.IsTransparent())
+ {
+ 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::ImplGetSizePixel() const
+{
+ Size aSize;
+
+ if (isSwappedOut())
+ aSize = maSwapInfo.maSizePixel;
+ else
+ aSize = ImplGetBitmapEx(GraphicConversionParameters()).GetSizePixel();
+
+ return aSize;
+}
+
+Size ImpGraphic::ImplGetPrefSize() const
+{
+ Size aSize;
+
+ if (isSwappedOut())
+ {
+ aSize = maSwapInfo.maPrefSize;
+ }
+ else
+ {
+ switch( meType )
+ {
+ case GraphicType::NONE:
+ case GraphicType::Default:
+ break;
+
+ case GraphicType::Bitmap:
+ {
+ if(maVectorGraphicData && maBitmapEx.IsEmpty())
+ {
+ if (!maExPrefSize.getWidth() || !maExPrefSize.getHeight())
+ {
+ // svg not yet buffered in maBitmapEx, return size derived from range
+ const basegfx::B2DRange& rRange = maVectorGraphicData->getRange();
+
+ aSize = Size(basegfx::fround(rRange.getWidth()), basegfx::fround(rRange.getHeight()));
+ }
+ else
+ {
+ aSize = maExPrefSize;
+ }
+ }
+ else
+ {
+ aSize = maBitmapEx.GetPrefSize();
+
+ if( !aSize.Width() || !aSize.Height() )
+ {
+ aSize = maBitmapEx.GetSizePixel();
+ }
+ }
+ }
+ break;
+
+ default:
+ {
+ if( ImplIsSupportedGraphic() )
+ aSize = maMetaFile.GetPrefSize();
+ }
+ break;
+ }
+ }
+
+ return aSize;
+}
+
+void ImpGraphic::ImplSetPrefSize( const Size& rPrefSize )
+{
+ ensureAvailable();
+
+ switch( meType )
+ {
+ case GraphicType::NONE:
+ case GraphicType::Default:
+ break;
+
+ 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( ImplIsAnimated() )
+ {
+ const_cast< BitmapEx& >(mpAnimation->GetBitmapEx()).SetPrefSize( rPrefSize );
+ }
+
+ if (!maExPrefSize.getWidth() || !maExPrefSize.getHeight())
+ {
+ maBitmapEx.SetPrefSize( rPrefSize );
+ }
+ }
+ break;
+
+ default:
+ {
+ if( ImplIsSupportedGraphic() )
+ maMetaFile.SetPrefSize( rPrefSize );
+ }
+ break;
+ }
+}
+
+MapMode ImpGraphic::ImplGetPrefMapMode() const
+{
+ MapMode aMapMode;
+
+ if (isSwappedOut())
+ {
+ aMapMode = maSwapInfo.maPrefMapMode;
+ }
+ else
+ {
+ switch( meType )
+ {
+ case GraphicType::NONE:
+ case GraphicType::Default:
+ break;
+
+ 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;
+
+ default:
+ {
+ if( ImplIsSupportedGraphic() )
+ return maMetaFile.GetPrefMapMode();
+ }
+ break;
+ }
+ }
+
+ return aMapMode;
+}
+
+void ImpGraphic::ImplSetPrefMapMode( const MapMode& rPrefMapMode )
+{
+ ensureAvailable();
+
+ switch( meType )
+ {
+ case GraphicType::NONE:
+ case GraphicType::Default:
+ break;
+
+ 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( ImplIsAnimated() )
+ {
+ const_cast< BitmapEx& >(mpAnimation->GetBitmapEx()).SetPrefMapMode( rPrefMapMode );
+ }
+
+ maBitmapEx.SetPrefMapMode( rPrefMapMode );
+ }
+ }
+ break;
+
+ default:
+ {
+ if( ImplIsSupportedGraphic() )
+ maMetaFile.SetPrefMapMode( rPrefMapMode );
+ }
+ break;
+ }
+}
+
+sal_uLong ImpGraphic::ImplGetSizeBytes() const
+{
+ if( mnSizeBytes )
+ return mnSizeBytes;
+
+ if (mbPrepared)
+ ensureAvailable();
+
+ if( meType == GraphicType::Bitmap )
+ {
+ if(maVectorGraphicData)
+ {
+ std::pair<VectorGraphicData::State, size_t> tmp(maVectorGraphicData->getSizeBytes());
+ if (VectorGraphicData::State::UNPARSED == tmp.first)
+ {
+ return tmp.second; // don't cache it until Vector Graphic Data is parsed
+ }
+ mnSizeBytes = tmp.second;
+ }
+ else
+ {
+ mnSizeBytes = mpAnimation ? mpAnimation->GetSizeBytes() : maBitmapEx.GetSizeBytes();
+ }
+ }
+ else if( meType == GraphicType::GdiMetafile )
+ {
+ mnSizeBytes = maMetaFile.GetSizeBytes();
+ }
+
+ return mnSizeBytes;
+}
+
+void ImpGraphic::ImplDraw( OutputDevice* pOutDev, const Point& rDestPt ) const
+{
+ ensureAvailable();
+ if( !ImplIsSupportedGraphic() || isSwappedOut() )
+ return;
+
+ switch( meType )
+ {
+ case GraphicType::Default:
+ break;
+
+ case GraphicType::Bitmap:
+ {
+ if(maVectorGraphicData && !maBitmapEx)
+ {
+ // use maEx as local buffer for rendered svg
+ const_cast< ImpGraphic* >(this)->maBitmapEx = getVectorGraphicReplacement();
+ }
+
+ if ( mpAnimation )
+ {
+ mpAnimation->Draw( pOutDev, rDestPt );
+ }
+ else
+ {
+ maBitmapEx.Draw( pOutDev, rDestPt );
+ }
+ }
+ break;
+
+ default:
+ ImplDraw( pOutDev, rDestPt, maMetaFile.GetPrefSize() );
+ break;
+ }
+}
+
+void ImpGraphic::ImplDraw( OutputDevice* pOutDev,
+ const Point& rDestPt, const Size& rDestSize ) const
+{
+ ensureAvailable();
+ if( !ImplIsSupportedGraphic() || isSwappedOut() )
+ return;
+
+ switch( meType )
+ {
+ case GraphicType::Default:
+ break;
+
+ case GraphicType::Bitmap:
+ {
+ if(maVectorGraphicData && maBitmapEx.IsEmpty())
+ {
+ // use maEx as local buffer for rendered svg
+ const_cast< ImpGraphic* >(this)->maBitmapEx = getVectorGraphicReplacement();
+ }
+
+ if( mpAnimation )
+ {
+ mpAnimation->Draw( pOutDev, rDestPt, rDestSize );
+ }
+ else
+ {
+ maBitmapEx.Draw( pOutDev, rDestPt, rDestSize );
+ }
+ }
+ break;
+
+ default:
+ {
+ const_cast<ImpGraphic*>(this)->maMetaFile.WindStart();
+ const_cast<ImpGraphic*>(this)->maMetaFile.Play( pOutDev, rDestPt, rDestSize );
+ const_cast<ImpGraphic*>(this)->maMetaFile.WindStart();
+ }
+ break;
+ }
+}
+
+void ImpGraphic::ImplStartAnimation( OutputDevice* pOutDev, const Point& rDestPt,
+ const Size& rDestSize, long nExtraData,
+ OutputDevice* pFirstFrameOutDev )
+{
+ ensureAvailable();
+
+ if( ImplIsSupportedGraphic() && !isSwappedOut() && mpAnimation )
+ mpAnimation->Start( pOutDev, rDestPt, rDestSize, nExtraData, pFirstFrameOutDev );
+}
+
+void ImpGraphic::ImplStopAnimation( OutputDevice* pOutDev, long nExtraData )
+{
+ ensureAvailable();
+
+ if( ImplIsSupportedGraphic() && !isSwappedOut() && mpAnimation )
+ mpAnimation->Stop( pOutDev, nExtraData );
+}
+
+void ImpGraphic::ImplSetAnimationNotifyHdl( const Link<Animation*,void>& rLink )
+{
+ ensureAvailable();
+
+ if( mpAnimation )
+ mpAnimation->SetNotifyHdl( rLink );
+}
+
+Link<Animation*,void> ImpGraphic::ImplGetAnimationNotifyHdl() const
+{
+ Link<Animation*,void> aLink;
+
+ ensureAvailable();
+
+ if( mpAnimation )
+ aLink = mpAnimation->GetNotifyHdl();
+
+ return aLink;
+}
+
+sal_uInt32 ImpGraphic::ImplGetAnimationLoopCount() const
+{
+ if (mbSwapOut)
+ return maSwapInfo.mnAnimationLoopCount;
+
+ return mpAnimation ? mpAnimation->GetLoopCount() : 0;
+}
+
+void ImpGraphic::ImplSetContext( const std::shared_ptr<GraphicReader>& pReader )
+{
+ mpContext = pReader;
+ mbDummyContext = false;
+}
+
+bool ImpGraphic::ImplReadEmbedded( SvStream& rIStm )
+{
+ ensureAvailable();
+
+ MapMode aMapMode;
+ Size aSize;
+ sal_uInt32 nId;
+ sal_Int32 nType;
+ const SvStreamEndian nOldFormat = rIStm.GetEndian();
+ bool bRet = false;
+
+ rIStm.SetEndian( SvStreamEndian::LITTLE );
+ rIStm.ReadUInt32( nId );
+
+ // check version
+ if( GRAPHIC_FORMAT_50 == nId )
+ {
+ // read new style header
+ VersionCompat aCompat( rIStm, StreamMode::READ );
+
+ rIStm.ReadInt32( nType );
+ sal_Int32 nLen;
+ rIStm.ReadInt32( nLen );
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readSize(aSize);
+ ReadMapMode( rIStm, aMapMode );
+ }
+ else
+ {
+ // read old style header
+ sal_Int32 nWidth, nHeight;
+ sal_Int32 nMapMode, nScaleNumX, nScaleDenomX;
+ sal_Int32 nScaleNumY, nScaleDenomY, nOffsX, nOffsY;
+
+ rIStm.SeekRel( -4 );
+
+ sal_Int32 nLen;
+ rIStm.ReadInt32( nType ).ReadInt32( nLen ).ReadInt32( nWidth ).ReadInt32( nHeight );
+ rIStm.ReadInt32( nMapMode ).ReadInt32( nScaleNumX ).ReadInt32( nScaleDenomX ).ReadInt32( nScaleNumY );
+ rIStm.ReadInt32( nScaleDenomY ).ReadInt32( nOffsX ).ReadInt32( nOffsY );
+
+ // swapped
+ if( nType > 100 )
+ {
+ nType = OSL_SWAPDWORD( nType );
+ nWidth = OSL_SWAPDWORD( nWidth );
+ nHeight = OSL_SWAPDWORD( nHeight );
+ nMapMode = OSL_SWAPDWORD( nMapMode );
+ nScaleNumX = OSL_SWAPDWORD( nScaleNumX );
+ nScaleDenomX = OSL_SWAPDWORD( nScaleDenomX );
+ nScaleNumY = OSL_SWAPDWORD( nScaleNumY );
+ nScaleDenomY = OSL_SWAPDWORD( nScaleDenomY );
+ nOffsX = OSL_SWAPDWORD( nOffsX );
+ nOffsY = OSL_SWAPDWORD( nOffsY );
+ }
+
+ aSize = Size( nWidth, nHeight );
+ aMapMode = MapMode( static_cast<MapUnit>(nMapMode), Point( nOffsX, nOffsY ),
+ Fraction( nScaleNumX, nScaleDenomX ),
+ Fraction( nScaleNumY, nScaleDenomY ) );
+ }
+
+ meType = static_cast<GraphicType>(nType);
+
+ if( meType != GraphicType::NONE )
+ {
+ if( meType == GraphicType::Bitmap )
+ {
+ if(maVectorGraphicData && maBitmapEx.IsEmpty())
+ {
+ // use maBitmapEx as local buffer for rendered svg
+ maBitmapEx = getVectorGraphicReplacement();
+ }
+
+ maBitmapEx.SetSizePixel(aSize);
+
+ if( aMapMode != MapMode() )
+ {
+ maBitmapEx.SetPrefMapMode( aMapMode );
+ maBitmapEx.SetPrefSize( aSize );
+ }
+ }
+ else
+ {
+ maMetaFile.SetPrefMapMode( aMapMode );
+ maMetaFile.SetPrefSize( aSize );
+ }
+
+ if( meType == GraphicType::Bitmap || meType == GraphicType::GdiMetafile )
+ {
+ ReadImpGraphic( rIStm, *this );
+ bRet = rIStm.GetError() == ERRCODE_NONE;
+ }
+ else if( sal::static_int_cast<sal_uLong>(meType) >= SYS_WINMETAFILE
+ && sal::static_int_cast<sal_uLong>(meType) <= SYS_MACMETAFILE )
+ {
+ Graphic aSysGraphic;
+ ConvertDataFormat nCvtType;
+
+ switch( sal::static_int_cast<sal_uLong>(meType) )
+ {
+ case SYS_WINMETAFILE:
+ case SYS_WNTMETAFILE: nCvtType = ConvertDataFormat::WMF; break;
+ case SYS_OS2METAFILE: nCvtType = ConvertDataFormat::MET; break;
+ case SYS_MACMETAFILE: nCvtType = ConvertDataFormat::PCT; break;
+
+ default:
+ nCvtType = ConvertDataFormat::Unknown;
+ break;
+ }
+
+ if( nType && GraphicConverter::Import( rIStm, aSysGraphic, nCvtType ) == ERRCODE_NONE )
+ {
+ *this = ImpGraphic( aSysGraphic.GetGDIMetaFile() );
+ bRet = rIStm.GetError() == ERRCODE_NONE;
+ }
+ else
+ meType = GraphicType::Default;
+ }
+
+ if( bRet )
+ {
+ ImplSetPrefMapMode( aMapMode );
+ ImplSetPrefSize( aSize );
+ }
+ }
+ else
+ bRet = true;
+
+ rIStm.SetEndian( nOldFormat );
+
+ return bRet;
+}
+
+bool ImpGraphic::ImplWriteEmbedded( SvStream& rOStm )
+{
+ ensureAvailable();
+
+ if( ( meType == GraphicType::NONE ) || ( meType == GraphicType::Default ) || isSwappedOut() )
+ return false;
+
+ const MapMode aMapMode( ImplGetPrefMapMode() );
+ const Size aSize( ImplGetPrefSize() );
+ const SvStreamEndian nOldFormat = rOStm.GetEndian();
+ sal_uLong nDataFieldPos;
+
+ rOStm.SetEndian( SvStreamEndian::LITTLE );
+
+ // write correct version ( old style/new style header )
+ if( rOStm.GetVersion() >= SOFFICE_FILEFORMAT_50 )
+ {
+ // write ID for new format (5.0)
+ rOStm.WriteUInt32( GRAPHIC_FORMAT_50 );
+
+ // write new style header
+ VersionCompat aCompat( rOStm, StreamMode::WRITE, 1 );
+
+ rOStm.WriteInt32( static_cast<sal_Int32>(meType) );
+
+ // data size is updated later
+ nDataFieldPos = rOStm.Tell();
+ rOStm.WriteInt32( 0 );
+
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writeSize(aSize);
+
+ WriteMapMode( rOStm, aMapMode );
+ }
+ else
+ {
+ // write old style (<=4.0) header
+ rOStm.WriteInt32( static_cast<sal_Int32>(meType) );
+
+ // data size is updated later
+ nDataFieldPos = rOStm.Tell();
+ rOStm.WriteInt32( 0 );
+ rOStm.WriteInt32( aSize.Width() );
+ rOStm.WriteInt32( aSize.Height() );
+ rOStm.WriteInt32( static_cast<sal_uInt16>(aMapMode.GetMapUnit()) );
+ rOStm.WriteInt32( aMapMode.GetScaleX().GetNumerator() );
+ rOStm.WriteInt32( aMapMode.GetScaleX().GetDenominator() );
+ rOStm.WriteInt32( aMapMode.GetScaleY().GetNumerator() );
+ rOStm.WriteInt32( aMapMode.GetScaleY().GetDenominator() );
+ rOStm.WriteInt32( aMapMode.GetOrigin().X() );
+ rOStm.WriteInt32( aMapMode.GetOrigin().Y() );
+ }
+
+ bool bRet = false;
+ // write data block
+ if( !rOStm.GetError() )
+ {
+ const sal_uLong nDataStart = rOStm.Tell();
+
+ if( ImplIsSupportedGraphic() )
+ WriteImpGraphic( rOStm, *this );
+
+ if( !rOStm.GetError() )
+ {
+ const sal_uLong nStmPos2 = rOStm.Tell();
+ rOStm.Seek( nDataFieldPos );
+ rOStm.WriteInt32( nStmPos2 - nDataStart );
+ rOStm.Seek( nStmPos2 );
+ bRet = true;
+ }
+ }
+
+ rOStm.SetEndian( nOldFormat );
+
+ return bRet;
+}
+
+bool ImpGraphic::swapOut()
+{
+ if (isSwappedOut())
+ return false;
+
+ // Create a temp filename for the swap file
+ utl::TempFile aTempFile;
+ const INetURLObject aTempFileURL(aTempFile.GetURL());
+
+ // Create a swap file
+ std::shared_ptr<ImpSwapFile> pSwapFile(new ImpSwapFile(aTempFileURL, getOriginURL()), o3tl::default_delete<ImpSwapFile>());
+
+ bool bResult = false;
+
+ // Open a stream to write the swap file to
+ {
+ std::unique_ptr<SvStream> xOutputStream = pSwapFile->openOutputStream();
+
+ if (!xOutputStream)
+ return false;
+
+ // Write to stream
+ xOutputStream->SetVersion(SOFFICE_FILEFORMAT_50);
+ xOutputStream->SetCompressMode(SvStreamCompressFlags::NATIVE);
+ xOutputStream->SetBufferSize(GRAPHIC_STREAMBUFSIZE);
+
+ if (!xOutputStream->GetError() && ImplWriteEmbedded(*xOutputStream))
+ {
+ xOutputStream->Flush();
+ bResult = !xOutputStream->GetError();
+ }
+ }
+
+ // Check if writing was successful
+ if (bResult)
+ {
+ // We have swapped out, so can clean memory and prepare swap info
+ createSwapInfo();
+ ImplClearGraphics();
+
+ mpSwapFile = std::move(pSwapFile);
+ mbSwapOut = true;
+
+ // Signal to manager that we have swapped out
+ vcl::graphic::Manager::get().swappedOut(this);
+ }
+
+ return bResult;
+}
+
+bool ImpGraphic::ensureAvailable() const
+{
+ auto pThis = const_cast<ImpGraphic*>(this);
+
+ if (isSwappedOut())
+ return pThis->swapIn();
+
+ pThis->maLastUsed = std::chrono::high_resolution_clock::now();
+ return true;
+}
+
+bool ImpGraphic::loadPrepared()
+{
+ Graphic aGraphic;
+ if (!mpGfxLink->LoadNative(aGraphic))
+ return false;
+
+ GraphicExternalLink aLink = maGraphicExternalLink;
+
+ Size aPrefSize = maSwapInfo.maPrefSize;
+ MapMode aPrefMapMode = maSwapInfo.maPrefMapMode;
+ *this = *aGraphic.ImplGetImpGraphic();
+ if (aPrefSize.getWidth() && aPrefSize.getHeight() && aPrefMapMode == ImplGetPrefMapMode())
+ {
+ // 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.
+ ImplSetPrefSize(aPrefSize);
+ }
+
+ maGraphicExternalLink = aLink;
+
+ return true;
+}
+
+bool ImpGraphic::swapIn()
+{
+ bool bRet = false;
+
+ if (!isSwappedOut())
+ return bRet;
+
+ if (mbPrepared)
+ {
+ bRet = loadPrepared();
+ }
+ else
+ {
+ OUString aSwapURL;
+
+ if( mpSwapFile )
+ aSwapURL = mpSwapFile->getSwapURL().GetMainURL( INetURLObject::DecodeMechanism::NONE );
+
+ if( !aSwapURL.isEmpty() )
+ {
+ std::unique_ptr<SvStream> xIStm;
+ try
+ {
+ xIStm = ::utl::UcbStreamHelper::CreateStream( aSwapURL, StreamMode::READWRITE | StreamMode::SHARE_DENYWRITE );
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+
+ if( xIStm )
+ {
+ xIStm->SetVersion( SOFFICE_FILEFORMAT_50 );
+ xIStm->SetCompressMode( SvStreamCompressFlags::NATIVE );
+
+ bRet = swapInFromStream(xIStm.get());
+ xIStm.reset();
+ if (mpSwapFile)
+ setOriginURL(mpSwapFile->getOriginURL());
+ mpSwapFile.reset();
+ }
+ }
+ }
+
+ if (bRet)
+ vcl::graphic::Manager::get().swappedIn(this);
+
+ return bRet;
+}
+
+bool ImpGraphic::swapInFromStream(SvStream* xIStm)
+{
+ bool bRet = false;
+
+ if( !xIStm )
+ return false;
+
+ xIStm->SetBufferSize( GRAPHIC_STREAMBUFSIZE );
+
+ if( xIStm->GetError() )
+ return false;
+
+ //keep the swap file alive, because its quite possibly the backing storage
+ //for xIStm
+ std::shared_ptr<ImpSwapFile> xSwapFile(std::move(mpSwapFile));
+ assert(!mpSwapFile);
+
+ std::shared_ptr<GraphicReader> xContext(std::move(mpContext));
+ assert(!mpContext);
+
+ bool bDummyContext = mbDummyContext;
+ mbDummyContext = false;
+
+ bRet = ImplReadEmbedded( *xIStm );
+
+ //restore ownership of the swap file and context
+ mpSwapFile = std::move(xSwapFile);
+ mpContext = std::move(xContext);
+ mbDummyContext = bDummyContext;
+
+ if (!bRet)
+ {
+ //throw away swapfile, etc.
+ ImplClear();
+ }
+
+ mbSwapOut = false;
+
+ return bRet;
+}
+
+void ImpGraphic::ImplSetLink(const std::shared_ptr<GfxLink>& rGfxLink)
+{
+ ensureAvailable();
+
+ mpGfxLink = rGfxLink;
+}
+
+std::shared_ptr<GfxLink> ImpGraphic::ImplGetSharedGfxLink() const
+{
+ return mpGfxLink;
+}
+
+GfxLink ImpGraphic::ImplGetLink()
+{
+ ensureAvailable();
+
+ return( mpGfxLink ? *mpGfxLink : GfxLink() );
+}
+
+bool ImpGraphic::ImplIsLink() const
+{
+ return ( bool(mpGfxLink) );
+}
+
+BitmapChecksum ImpGraphic::ImplGetChecksum() const
+{
+ if (mnChecksum != 0)
+ return mnChecksum;
+
+ BitmapChecksum nRet = 0;
+
+ ensureAvailable();
+
+ if( ImplIsSupportedGraphic() && !isSwappedOut() )
+ {
+ switch( meType )
+ {
+ case GraphicType::Default:
+ break;
+
+ case GraphicType::Bitmap:
+ {
+ if(maVectorGraphicData)
+ nRet = maVectorGraphicData->GetChecksum();
+ else if( mpAnimation )
+ nRet = mpAnimation->GetChecksum();
+ else
+ nRet = maBitmapEx.GetChecksum();
+ }
+ break;
+
+ default:
+ nRet = maMetaFile.GetChecksum();
+ break;
+ }
+ }
+
+ mnChecksum = nRet;
+ return nRet;
+}
+
+bool ImpGraphic::ImplExportNative( SvStream& rOStm ) const
+{
+ ensureAvailable();
+
+ if( rOStm.GetError() )
+ return false;
+
+ bool bResult = false;
+
+ if( !isSwappedOut() )
+ {
+ if( mpGfxLink && mpGfxLink->IsNative() )
+ bResult = mpGfxLink->ExportNative( rOStm );
+ else
+ {
+ WriteImpGraphic( rOStm, *this );
+ bResult = ( rOStm.GetError() == ERRCODE_NONE );
+ }
+ }
+ else
+ rOStm.SetError( SVSTREAM_GENERALERROR );
+
+ return bResult;
+}
+
+sal_Int32 ImpGraphic::getPageNumber() const
+{
+ if (maVectorGraphicData)
+ return maVectorGraphicData->getPageIndex();
+ return -1;
+}
+
+void ReadImpGraphic( SvStream& rIStm, ImpGraphic& rImpGraphic )
+{
+ if (rIStm.GetError())
+ return;
+
+ const sal_uLong nStmPos1 = rIStm.Tell();
+ sal_uInt32 nTmp;
+
+ rImpGraphic.ImplClear();
+
+ // read Id
+ rIStm.ReadUInt32( nTmp );
+
+ // if there is no more data, avoid further expensive
+ // reading which will create VDevs and other stuff, just to
+ // read nothing. CAUTION: Eof is only true AFTER reading another
+ // byte, a speciality of SvMemoryStream (!)
+ if (!rIStm.good())
+ return;
+
+ if (NATIVE_FORMAT_50 == nTmp)
+ {
+ Graphic aGraphic;
+ GfxLink aLink;
+
+ // read compat info, destructor writes stuff into the header
+ {
+ VersionCompat aCompat( rIStm, StreamMode::READ );
+ }
+
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readGfxLink(aLink);
+
+ // set dummy link to avoid creation of additional link after filtering;
+ // we set a default link to avoid unnecessary swapping of native data
+ aGraphic.SetGfxLink(std::make_shared<GfxLink>());
+
+ if( !rIStm.GetError() && aLink.LoadNative( aGraphic ) )
+ {
+ // set link only, if no other link was set
+ const bool bSetLink = !rImpGraphic.mpGfxLink;
+
+ // assign graphic
+ rImpGraphic = *aGraphic.ImplGetImpGraphic();
+
+ if( aLink.IsPrefMapModeValid() )
+ rImpGraphic.ImplSetPrefMapMode( aLink.GetPrefMapMode() );
+
+ if( aLink.IsPrefSizeValid() )
+ rImpGraphic.ImplSetPrefSize( aLink.GetPrefSize() );
+
+ if( bSetLink )
+ rImpGraphic.ImplSetLink(std::make_shared<GfxLink>(aLink));
+ }
+ else
+ {
+ rIStm.Seek( nStmPos1 );
+ rIStm.SetError( ERRCODE_IO_WRONGFORMAT );
+ }
+ return;
+ }
+
+ BitmapEx aBmpEx;
+ const SvStreamEndian nOldFormat = rIStm.GetEndian();
+
+ rIStm.SeekRel( -4 );
+ rIStm.SetEndian( SvStreamEndian::LITTLE );
+ ReadDIBBitmapEx(aBmpEx, rIStm);
+
+ if( !rIStm.GetError() )
+ {
+ sal_uInt32 nMagic1(0), nMagic2(0);
+ sal_uLong nActPos = rIStm.Tell();
+
+ rIStm.ReadUInt32( nMagic1 ).ReadUInt32( nMagic2 );
+ rIStm.Seek( nActPos );
+
+ rImpGraphic = ImpGraphic( aBmpEx );
+
+ if( !rIStm.GetError() && ( 0x5344414e == nMagic1 ) && ( 0x494d4931 == nMagic2 ) )
+ {
+ rImpGraphic.mpAnimation = std::make_unique<Animation>();
+ ReadAnimation( rIStm, *rImpGraphic.mpAnimation );
+
+ // #108077# manually set loaded BmpEx to Animation
+ // (which skips loading its BmpEx if already done)
+ rImpGraphic.mpAnimation->SetBitmapEx(aBmpEx);
+ }
+ else
+ rIStm.ResetError();
+ }
+ else
+ {
+ GDIMetaFile aMtf;
+
+ rIStm.Seek( nStmPos1 );
+ rIStm.ResetError();
+ ReadGDIMetaFile( rIStm, aMtf );
+
+ if( !rIStm.GetError() )
+ {
+ rImpGraphic = aMtf;
+ }
+ else
+ {
+ ErrCode nOrigError = rIStm.GetErrorCode();
+ // try to stream in Svg defining data (length, byte array and evtl. path)
+ // See below (operator<<) for more information
+ sal_uInt32 nMagic;
+ rIStm.Seek(nStmPos1);
+ rIStm.ResetError();
+ rIStm.ReadUInt32( nMagic );
+
+ if (constSvgMagic == nMagic || constWmfMagic == nMagic || constEmfMagic == nMagic || constPdfMagic == nMagic)
+ {
+ sal_uInt32 nVectorGraphicDataArrayLength(0);
+ rIStm.ReadUInt32(nVectorGraphicDataArrayLength);
+
+ if (nVectorGraphicDataArrayLength)
+ {
+ VectorGraphicDataArray aNewData(nVectorGraphicDataArrayLength);
+
+ rIStm.ReadBytes(aNewData.getArray(), nVectorGraphicDataArrayLength);
+ OUString aPath = rIStm.ReadUniOrByteString(rIStm.GetStreamCharSet());
+
+ if (!rIStm.GetError())
+ {
+ VectorGraphicDataType aDataType(VectorGraphicDataType::Svg);
+
+ if (constWmfMagic == nMagic)
+ {
+ aDataType = VectorGraphicDataType::Wmf;
+ }
+ else if (constEmfMagic == nMagic)
+ {
+ aDataType = VectorGraphicDataType::Emf;
+ }
+ else if (constPdfMagic == nMagic)
+ {
+ aDataType = VectorGraphicDataType::Pdf;
+ }
+
+ auto aVectorGraphicDataPtr = std::make_shared<VectorGraphicData>(aNewData, aPath, aDataType);
+ rImpGraphic = aVectorGraphicDataPtr;
+ }
+ }
+ }
+ else
+ {
+ rIStm.SetError(nOrigError);
+ }
+
+ rIStm.Seek(nStmPos1);
+ }
+ }
+
+ rIStm.SetEndian( nOldFormat );
+}
+
+void WriteImpGraphic(SvStream& rOStm, const ImpGraphic& rImpGraphic)
+{
+ if (rOStm.GetError())
+ return;
+
+ rImpGraphic.ensureAvailable();
+
+ if (rImpGraphic.isSwappedOut())
+ {
+ rOStm.SetError( SVSTREAM_GENERALERROR );
+ return;
+ }
+
+ if( ( rOStm.GetVersion() >= SOFFICE_FILEFORMAT_50 ) &&
+ ( rOStm.GetCompressMode() & SvStreamCompressFlags::NATIVE ) &&
+ rImpGraphic.mpGfxLink && rImpGraphic.mpGfxLink->IsNative())
+ {
+ // native format
+ rOStm.WriteUInt32( NATIVE_FORMAT_50 );
+
+ // write compat info, destructor writes stuff into the header
+ {
+ VersionCompat aCompat( rOStm, StreamMode::WRITE, 1 );
+ }
+ rImpGraphic.mpGfxLink->SetPrefMapMode( rImpGraphic.ImplGetPrefMapMode() );
+ rImpGraphic.mpGfxLink->SetPrefSize( rImpGraphic.ImplGetPrefSize() );
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writeGfxLink(*rImpGraphic.mpGfxLink);
+ }
+ else
+ {
+ // own format
+ const SvStreamEndian nOldFormat = rOStm.GetEndian();
+ rOStm.SetEndian( SvStreamEndian::LITTLE );
+
+ switch( rImpGraphic.ImplGetType() )
+ {
+ case GraphicType::NONE:
+ case GraphicType::Default:
+ break;
+
+ case GraphicType::Bitmap:
+ {
+ if(rImpGraphic.getVectorGraphicData())
+ {
+ // 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 (rImpGraphic.getVectorGraphicData()->getVectorGraphicDataType())
+ {
+ case VectorGraphicDataType::Wmf:
+ {
+ rOStm.WriteUInt32(constWmfMagic);
+ break;
+ }
+ case VectorGraphicDataType::Emf:
+ {
+ rOStm.WriteUInt32(constEmfMagic);
+ break;
+ }
+ case VectorGraphicDataType::Svg:
+ {
+ rOStm.WriteUInt32(constSvgMagic);
+ break;
+ }
+ case VectorGraphicDataType::Pdf:
+ {
+ rOStm.WriteUInt32(constPdfMagic);
+ break;
+ }
+ }
+
+ rOStm.WriteUInt32( rImpGraphic.getVectorGraphicData()->getVectorGraphicDataArrayLength() );
+ rOStm.WriteBytes(rImpGraphic.getVectorGraphicData()->getVectorGraphicDataArray().getConstArray(),
+ rImpGraphic.getVectorGraphicData()->getVectorGraphicDataArrayLength());
+ rOStm.WriteUniOrByteString(rImpGraphic.getVectorGraphicData()->getPath(),
+ rOStm.GetStreamCharSet());
+ }
+ else if( rImpGraphic.ImplIsAnimated())
+ {
+ WriteAnimation( rOStm, *rImpGraphic.mpAnimation );
+ }
+ else
+ {
+ WriteDIBBitmapEx(rImpGraphic.maBitmapEx, rOStm);
+ }
+ }
+ break;
+
+ default:
+ {
+ if( rImpGraphic.ImplIsSupportedGraphic() )
+ WriteGDIMetaFile( rOStm, rImpGraphic.maMetaFile );
+ }
+ break;
+ }
+
+ rOStm.SetEndian( nOldFormat );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/impvect.cxx b/vcl/source/gdi/impvect.cxx
new file mode 100644
index 000000000..60027e19c
--- /dev/null
+++ b/vcl/source/gdi/impvect.cxx
@@ -0,0 +1,1000 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/bitmapaccess.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<long []> & pMapIn, const std::unique_ptr<long []>& pMapOut, long nVal )
+{
+ pMapIn[nVal] = (nVal * 4) + 1;
+ pMapOut[nVal] = pMapIn[nVal] + 5;
+}
+static constexpr long BACK_MAP( long _def_nVal )
+{
+ return ((_def_nVal + 2) >> 2) - 1;
+}
+static void VECT_PROGRESS( const Link<long, void>* pProgress, long _def_nVal )
+{
+ if(pProgress)
+ pProgress->Call(_def_nVal);
+}
+
+namespace {
+
+class ImplVectMap;
+class ImplChain;
+
+}
+
+namespace ImplVectorizer
+{
+ static ImplVectMap* ImplExpand( BitmapReadAccess* pRAcc, const Color& rColor );
+ static void ImplCalculate( ImplVectMap* pMap, tools::PolyPolygon& rPolyPoly, sal_uInt8 cReduce );
+ static bool ImplGetChain( ImplVectMap* pMap, const Point& rStartPt, ImplChain& rChain );
+ static bool ImplIsUp( ImplVectMap const * pMap, long nY, long nX );
+ static void ImplLimitPolyPoly( tools::PolyPolygon& rPolyPoly );
+}
+
+namespace {
+
+struct ChainMove { long nDX; long nDY; };
+
+}
+
+static const ChainMove aImplMove[ 8 ] = {
+ { 1, 0 },
+ { 0, -1 },
+ { -1, 0 },
+ { 0, 1 },
+ { 1, -1 },
+ { -1, -1 },
+ { -1, 1 },
+ { 1, 1 }
+ };
+
+static const ChainMove aImplMoveInner[ 8 ] = {
+ { 0, 1 },
+ { 1, 0 },
+ { 0, -1 },
+ { -1, 0 },
+ { 0, 1 },
+ { 1, 0 },
+ { 0, -1 },
+ { -1, 0 }
+ };
+
+static 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;
+ long mnWidth;
+ long mnHeight;
+
+public:
+
+ ImplVectMap( long nWidth, long nHeight );
+ ~ImplVectMap();
+
+ long Width() const { return mnWidth; }
+ long Height() const { return mnHeight; }
+
+ inline void Set( long nY, long nX, sal_uInt8 cVal );
+ inline sal_uInt8 Get( long nY, long nX ) const;
+
+ inline bool IsFree( long nY, long nX ) const;
+ inline bool IsCont( long nY, long nX ) const;
+ inline bool IsDone( long nY, long nX ) const;
+
+};
+
+}
+
+ImplVectMap::ImplVectMap( long nWidth, long nHeight ) :
+ mpBuf ( static_cast<Scanline>(rtl_allocateZeroMemory(nWidth * nHeight)) ),
+ mpScan ( static_cast<Scanline*>(std::malloc(nHeight * sizeof(Scanline))) ),
+ mnWidth ( nWidth ),
+ mnHeight( nHeight )
+{
+ const long nWidthAl = ( nWidth >> 2 ) + 1;
+ Scanline pTmp = mpBuf;
+
+ for( long nY = 0; nY < nHeight; pTmp += nWidthAl )
+ mpScan[ nY++ ] = pTmp;
+}
+
+ImplVectMap::~ImplVectMap()
+{
+ std::free( mpBuf );
+ std::free( mpScan );
+}
+
+inline void ImplVectMap::Set( long nY, 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( long nY, long nX ) const
+{
+ return sal::static_int_cast<sal_uInt8>( ( ( mpScan[ nY ][ nX >> 2 ] ) >> ( 6 - ( ( nX & 3 ) << 1 ) ) ) & 3 );
+}
+
+inline bool ImplVectMap::IsFree( long nY, long nX ) const
+{
+ return( VECT_FREE_INDEX == Get( nY, nX ) );
+}
+
+inline bool ImplVectMap::IsCont( long nY, long nX ) const
+{
+ return( VECT_CONT_INDEX == Get( nY, nX ) );
+}
+
+inline bool ImplVectMap::IsDone( long nY, 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 )
+ {
+ long nFirstX, nFirstY;
+ 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 )
+ {
+ long nFirstX, nFirstY;
+ 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
+ {
+ 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 long nX = BACK_MAP( rPt.X() );
+ const 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<long,void>* pProgress )
+{
+ bool bRet = false;
+
+ VECT_PROGRESS( pProgress, 0 );
+
+ std::unique_ptr<Bitmap> xBmp(new Bitmap( rColorBmp ));
+ Bitmap::ScopedReadAccess pRAcc(*xBmp);
+
+ if( pRAcc )
+ {
+ tools::PolyPolygon aPolyPoly;
+ double fPercent = 0.0;
+ double fPercentStep_2 = 0.0;
+ const long nWidth = pRAcc->Width();
+ const 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( long nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pScanlineRead = pRAcc->GetScanline( nY );
+ for( 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::unique_ptr<ImplVectMap> xMap(ImplExpand( pRAcc.get(), aFindColor ));
+
+ fPercent += fPercentStep_2;
+ VECT_PROGRESS( pProgress, FRound( fPercent ) );
+
+ if( xMap )
+ {
+ aPolyPoly.Clear();
+ ImplCalculate( xMap.get(), aPolyPoly, cReduce );
+ xMap.reset();
+
+ if( aPolyPoly.Count() )
+ {
+ ImplLimitPolyPoly( aPolyPoly );
+
+ aPolyPoly.Optimize( PolyOptimizeFlags::EDGES );
+
+ if( aPolyPoly.Count() )
+ {
+ rMtf.AddAction( new MetaLineColorAction( aFindColor, true ) );
+ rMtf.AddAction( new MetaFillColorAction( aFindColor, true ) );
+ rMtf.AddAction( new MetaPolyPolygonAction( aPolyPoly ) );
+ }
+ }
+ }
+
+ fPercent += fPercentStep_2;
+ VECT_PROGRESS( pProgress, FRound( fPercent ) );
+ }
+
+ if( rMtf.GetActionSize() )
+ {
+ MapMode aMap( MapUnit::Map100thMM );
+ ScopedVclPtrInstance< VirtualDevice > aVDev;
+ const Size aLogSize1( aVDev->PixelToLogic( Size( 1, 1 ), aMap ) );
+
+ rMtf.SetPrefMapMode( aMap );
+ rMtf.SetPrefSize( Size( nWidth + 2, nHeight + 2 ) );
+ rMtf.Move( 1, 1 );
+ rMtf.Scale( aLogSize1.Width(), aLogSize1.Height() );
+ bRet = true;
+ }
+ }
+
+ pRAcc.reset();
+ xBmp.reset();
+ VECT_PROGRESS( pProgress, 100 );
+
+ return bRet;
+}
+
+void ImplLimitPolyPoly( tools::PolyPolygon& rPolyPoly )
+{
+ if( rPolyPoly.Count() > VECT_POLY_MAX )
+ {
+ tools::PolyPolygon aNewPolyPoly;
+ 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;
+ }
+}
+
+ImplVectMap* ImplExpand( BitmapReadAccess* pRAcc, const Color& rColor )
+{
+ ImplVectMap* pMap = nullptr;
+
+ if( pRAcc && pRAcc->Width() && pRAcc->Height() )
+ {
+ const long nOldWidth = pRAcc->Width();
+ const long nOldHeight = pRAcc->Height();
+ const long nNewWidth = ( nOldWidth << 2 ) + 4;
+ const long nNewHeight = ( nOldHeight << 2 ) + 4;
+ const BitmapColor aTest( pRAcc->GetBestMatchingColor( rColor ) );
+ std::unique_ptr<long[]> pMapIn(new long[ std::max( nOldWidth, nOldHeight ) ]);
+ std::unique_ptr<long[]> pMapOut(new long[ std::max( nOldWidth, nOldHeight ) ]);
+ long nX, nY, nTmpX, nTmpY;
+
+ pMap = new ImplVectMap( 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;
+
+ pMap->Set( nTmpY++, nTmpX, VECT_CONT_INDEX );
+ pMap->Set( nTmpY++, nTmpX, VECT_CONT_INDEX );
+ pMap->Set( nTmpY++, nTmpX, VECT_CONT_INDEX );
+ pMap->Set( nTmpY, nTmpX, VECT_CONT_INDEX );
+
+ while( nX < nOldWidth && pRAcc->GetPixelFromData( pScanlineRead, nX ) == aTest )
+ nX++;
+
+ nTmpX = pMapOut[ nX - 1 ];
+ nTmpY -= 3;
+
+ pMap->Set( nTmpY++, nTmpX, VECT_CONT_INDEX );
+ pMap->Set( nTmpY++, nTmpX, VECT_CONT_INDEX );
+ pMap->Set( nTmpY++, nTmpX, VECT_CONT_INDEX );
+ pMap->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++ ];
+
+ pMap->Set( nTmpY, nTmpX++, VECT_CONT_INDEX );
+ pMap->Set( nTmpY, nTmpX++, VECT_CONT_INDEX );
+ pMap->Set( nTmpY, nTmpX++, VECT_CONT_INDEX );
+ pMap->Set( nTmpY, nTmpX, VECT_CONT_INDEX );
+
+ while( nY < nOldHeight && pRAcc->GetPixel( nY, nX ) == aTest )
+ nY++;
+
+ nTmpX -= 3;
+ nTmpY = pMapOut[ nY - 1 ];
+
+ pMap->Set( nTmpY, nTmpX++, VECT_CONT_INDEX );
+ pMap->Set( nTmpY, nTmpX++, VECT_CONT_INDEX );
+ pMap->Set( nTmpY, nTmpX++, VECT_CONT_INDEX );
+ pMap->Set( nTmpY, nTmpX, VECT_CONT_INDEX );
+ }
+ else
+ nY++;
+ }
+ }
+ }
+
+ return pMap;
+}
+
+void ImplCalculate( ImplVectMap* pMap, tools::PolyPolygon& rPolyPoly, sal_uInt8 cReduce )
+{
+ const long nWidth = pMap->Width(), nHeight= pMap->Height();
+
+ for( long nY = 0; nY < nHeight; nY++ )
+ {
+ long nX = 0;
+ bool bInner = true;
+
+ while( nX < nWidth )
+ {
+ // skip free
+ while( ( nX < nWidth ) && pMap->IsFree( nY, nX ) )
+ nX++;
+
+ if( nX == nWidth )
+ break;
+
+ if( pMap->IsCont( nY, nX ) )
+ {
+ // new contour
+ ImplChain aChain;
+ const Point aStartPt( nX++, nY );
+
+ // get chain code
+ aChain.ImplBeginAdd( aStartPt );
+ ImplGetChain( pMap, 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( pMap->IsCont( nY, nX ) )
+ nX++;
+ }
+ else
+ {
+ // process done segment
+ const long nStartSegX = nX++;
+
+ while( pMap->IsDone( nY, nX ) )
+ nX++;
+
+ if( ( ( nX - nStartSegX ) == 1 ) || ( ImplIsUp( pMap, nY, nStartSegX ) != ImplIsUp( pMap, nY, nX - 1 ) ) )
+ bInner = !bInner;
+ }
+ }
+ }
+}
+
+bool ImplGetChain( ImplVectMap* pMap, const Point& rStartPt, ImplChain& rChain )
+{
+ long nActX = rStartPt.X();
+ long nActY = rStartPt.Y();
+ sal_uLong nFound;
+ sal_uLong nLastDir = 0;
+ sal_uLong nDir;
+
+ do
+ {
+ nFound = 0;
+
+ // first try last direction
+ long nTryX = nActX + aImplMove[ nLastDir ].nDX;
+ long nTryY = nActY + aImplMove[ nLastDir ].nDY;
+
+ if( pMap->IsCont( nTryY, nTryX ) )
+ {
+ rChain.ImplAdd( static_cast<sal_uInt8>(nLastDir) );
+ nActY = nTryY;
+ nActX = nTryX;
+ pMap->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( pMap->IsCont( nTryY, nTryX ) )
+ {
+ rChain.ImplAdd( static_cast<sal_uInt8>(nDir) );
+ nActY = nTryY;
+ nActX = nTryX;
+ pMap->Set( nActY, nActX, VECT_DONE_INDEX );
+ nFound = 1;
+ nLastDir = nDir;
+ break;
+ }
+ }
+ }
+ }
+ }
+ while( nFound );
+
+ return true;
+}
+
+bool ImplIsUp( ImplVectMap const * pMap, long nY, long nX )
+{
+ if( pMap->IsDone( nY - 1, nX ) )
+ return true;
+ else if( pMap->IsDone( nY + 1, nX ) )
+ return false;
+ else if( pMap->IsDone( nY - 1, nX - 1 ) || pMap->IsDone( nY - 1, nX + 1 ) )
+ return true;
+ else
+ return false;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/impvect.hxx b/vcl/source/gdi/impvect.hxx
new file mode 100644
index 000000000..017b2dc1a
--- /dev/null
+++ b/vcl/source/gdi/impvect.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 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_GDI_IMPVECT_HXX
+#define INCLUDED_VCL_SOURCE_GDI_IMPVECT_HXX
+
+#include <vcl/gdimtf.hxx>
+
+namespace tools { class PolyPolygon; }
+
+namespace ImplVectorizer
+{
+ bool ImplVectorize( const Bitmap& rColorBmp, GDIMetaFile& rMtf,
+ sal_uInt8 cReduce, const Link<long,void>* pProgress );
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/jobset.cxx b/vcl/source/gdi/jobset.cxx
new file mode 100644
index 000000000..a861169c9
--- /dev/null
+++ b/vcl/source/gdi/jobset.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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>
+#include <rtl/instance.hxx>
+
+#define JOBSET_FILE364_SYSTEM (sal_uInt16(0xFFFF))
+#define JOBSET_FILE605_SYSTEM (sal_uInt16(0xFFFE))
+
+namespace {
+
+struct ImplOldJobSetupData
+{
+ char cPrinterName[64];
+ char cDeviceName[32];
+ char cPortName[32];
+ char cDriverName[32];
+};
+
+struct Impl364JobSetupData
+{
+ SVBT16 nSize;
+ SVBT16 nSystem;
+ SVBT32 nDriverDataLen;
+ SVBT16 nOrientation;
+ SVBT16 nPaperBin;
+ SVBT16 nPaperFormat;
+ SVBT32 nPaperWidth;
+ SVBT32 nPaperHeight;
+};
+
+}
+
+ImplJobSetup::ImplJobSetup()
+{
+ mnSystem = 0;
+ meOrientation = Orientation::Portrait;
+ meDuplexMode = DuplexMode::Unknown;
+ mnPaperBin = 0;
+ mePaperFormat = PAPER_USER;
+ mnPaperWidth = 0;
+ mnPaperHeight = 0;
+ mnDriverDataLen = 0;
+ mpDriverData = nullptr;
+ mbPapersizeFromSetup = false;
+ meSetupMode = PrinterSetupMode::DocumentGlobal;
+}
+
+ImplJobSetup::ImplJobSetup( const ImplJobSetup& rJobSetup ) :
+ mnSystem( rJobSetup.GetSystem() ),
+ maPrinterName( rJobSetup.GetPrinterName() ),
+ maDriver( rJobSetup.GetDriver() ),
+ meOrientation( rJobSetup.GetOrientation() ),
+ meDuplexMode( rJobSetup.GetDuplexMode() ),
+ mnPaperBin( rJobSetup.GetPaperBin() ),
+ mePaperFormat( rJobSetup.GetPaperFormat() ),
+ mnPaperWidth( rJobSetup.GetPaperWidth() ),
+ mnPaperHeight( rJobSetup.GetPaperHeight() ),
+ mnDriverDataLen( rJobSetup.GetDriverDataLen() ),
+ mbPapersizeFromSetup( rJobSetup.GetPapersizeFromSetup() ),
+ meSetupMode( rJobSetup.GetPrinterSetupMode() ),
+ maValueMap( rJobSetup.GetValueMap() )
+ {
+ if ( rJobSetup.GetDriverData() )
+ {
+ mpDriverData = static_cast<sal_uInt8*>(std::malloc( mnDriverDataLen ));
+ memcpy( mpDriverData, rJobSetup.GetDriverData(), mnDriverDataLen );
+ }
+ else
+ mpDriverData = nullptr;
+}
+
+ImplJobSetup::~ImplJobSetup()
+{
+ std::free( mpDriverData );
+}
+
+void ImplJobSetup::SetSystem(sal_uInt16 nSystem)
+{
+ mnSystem = nSystem;
+}
+
+void ImplJobSetup::SetPrinterName(const OUString& rPrinterName)
+{
+ maPrinterName = rPrinterName;
+}
+
+void ImplJobSetup::SetDriver(const OUString& rDriver)
+{
+ maDriver = rDriver;
+}
+
+void ImplJobSetup::SetOrientation(Orientation eOrientation)
+{
+ meOrientation = eOrientation;
+}
+
+void ImplJobSetup::SetDuplexMode(DuplexMode eDuplexMode)
+{
+ meDuplexMode = eDuplexMode;
+}
+
+void ImplJobSetup::SetPaperBin(sal_uInt16 nPaperBin)
+{
+ mnPaperBin = nPaperBin;
+}
+
+void ImplJobSetup::SetPaperFormat(Paper ePaperFormat)
+{
+ mePaperFormat = ePaperFormat;
+}
+
+void ImplJobSetup::SetPaperWidth(long nPaperWidth)
+{
+ mnPaperWidth = nPaperWidth;
+}
+
+void ImplJobSetup::SetPaperHeight(long nPaperHeight)
+{
+ mnPaperHeight = nPaperHeight;
+}
+
+void ImplJobSetup::SetDriverDataLen(sal_uInt32 nDriverDataLen)
+{
+ mnDriverDataLen = nDriverDataLen;
+}
+
+void ImplJobSetup::SetDriverData(sal_uInt8* pDriverData)
+{
+ mpDriverData = pDriverData;
+}
+
+void ImplJobSetup::SetPapersizeFromSetup(bool bPapersizeFromSetup)
+{
+ mbPapersizeFromSetup = bPapersizeFromSetup;
+}
+
+void ImplJobSetup::SetPrinterSetupMode(PrinterSetupMode eMode)
+{
+ meSetupMode = eMode;
+}
+
+void ImplJobSetup::SetValueMap( const OUString& rKey, const OUString& rValue )
+{
+ maValueMap [ rKey ] = rValue;
+}
+
+JobSetup& JobSetup::operator=( const JobSetup& ) = default;
+
+JobSetup& JobSetup::operator=( JobSetup&& ) = default;
+
+bool ImplJobSetup::operator==( const ImplJobSetup& rImplJobSetup ) const
+{
+ return mnSystem == rImplJobSetup.mnSystem &&
+ maPrinterName == rImplJobSetup.maPrinterName &&
+ maDriver == rImplJobSetup.maDriver &&
+ meOrientation == rImplJobSetup.meOrientation &&
+ meDuplexMode == rImplJobSetup.meDuplexMode &&
+ mnPaperBin == rImplJobSetup.mnPaperBin &&
+ mePaperFormat == rImplJobSetup.mePaperFormat &&
+ mnPaperWidth == rImplJobSetup.mnPaperWidth &&
+ mnPaperHeight == rImplJobSetup.mnPaperHeight &&
+ mbPapersizeFromSetup == rImplJobSetup.mbPapersizeFromSetup &&
+ mnDriverDataLen == rImplJobSetup.mnDriverDataLen &&
+ maValueMap == rImplJobSetup.maValueMap &&
+ memcmp( mpDriverData, rImplJobSetup.mpDriverData, mnDriverDataLen ) == 0;
+}
+
+namespace
+{
+ struct theGlobalDefault :
+ public rtl::Static< JobSetup::ImplType, theGlobalDefault > {};
+}
+
+JobSetup::JobSetup() : mpData(theGlobalDefault::get())
+{
+}
+
+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(theGlobalDefault::get());
+}
+
+SvStream& ReadJobSetup( SvStream& rIStream, JobSetup& rJobSetup )
+{
+ {
+ sal_uInt16 nLen = 0;
+ rIStream.ReadUInt16( nLen );
+ if (nLen <= 4)
+ return rIStream;
+
+ sal_uInt16 nSystem = 0;
+ rIStream.ReadUInt16( nSystem );
+ size_t nRead = nLen - sizeof(nLen) - sizeof(nSystem);
+ if (nRead > rIStream.remainingSize())
+ {
+ SAL_WARN("vcl", "Parsing error: " << rIStream.remainingSize() <<
+ " max possible entries, but " << nRead << " claimed, truncating");
+ return rIStream;
+ }
+ sal_uInt64 const nFirstPos = rIStream.Tell();
+ std::unique_ptr<char[]> pTempBuf(new char[nRead]);
+ nRead = rIStream.ReadBytes(pTempBuf.get(), nRead);
+ if (nRead >= sizeof(ImplOldJobSetupData))
+ {
+ ImplOldJobSetupData* pData = reinterpret_cast<ImplOldJobSetupData*>(pTempBuf.get());
+
+ rtl_TextEncoding aStreamEncoding = RTL_TEXTENCODING_UTF8;
+ if( nSystem == JOBSET_FILE364_SYSTEM )
+ aStreamEncoding = rIStream.GetStreamCharSet();
+
+ ImplJobSetup& rJobData = rJobSetup.ImplGetData();
+
+ pData->cPrinterName[SAL_N_ELEMENTS(pData->cPrinterName) - 1] = 0;
+ rJobData.SetPrinterName( OStringToOUString(pData->cPrinterName, aStreamEncoding) );
+ pData->cDriverName[SAL_N_ELEMENTS(pData->cDriverName) - 1] = 0;
+ rJobData.SetDriver( OStringToOUString(pData->cDriverName, aStreamEncoding) );
+
+ // Are these our new JobSetup files?
+ if ( nSystem == JOBSET_FILE364_SYSTEM ||
+ nSystem == JOBSET_FILE605_SYSTEM )
+ {
+ Impl364JobSetupData* pOldJobData = reinterpret_cast<Impl364JobSetupData*>(pTempBuf.get() + sizeof( ImplOldJobSetupData ));
+ sal_uInt16 nOldJobDataSize = SVBT16ToUInt16( pOldJobData->nSize );
+ rJobData.SetSystem( SVBT16ToUInt16( pOldJobData->nSystem ) );
+ rJobData.SetDriverDataLen( SVBT32ToUInt32( pOldJobData->nDriverDataLen ) );
+ rJobData.SetOrientation( static_cast<Orientation>(SVBT16ToUInt16( pOldJobData->nOrientation )) );
+ rJobData.SetDuplexMode( DuplexMode::Unknown );
+ rJobData.SetPaperBin( SVBT16ToUInt16( pOldJobData->nPaperBin ) );
+ rJobData.SetPaperFormat( static_cast<Paper>(SVBT16ToUInt16( pOldJobData->nPaperFormat )) );
+ rJobData.SetPaperWidth( static_cast<long>(SVBT32ToUInt32( pOldJobData->nPaperWidth )) );
+ rJobData.SetPaperHeight( static_cast<long>(SVBT32ToUInt32( pOldJobData->nPaperHeight )) );
+ if ( rJobData.GetDriverDataLen() )
+ {
+ const char* pDriverData = reinterpret_cast<const char*>(pOldJobData) + nOldJobDataSize;
+ const char* pDriverDataEnd = pDriverData + rJobData.GetDriverDataLen();
+ if (pDriverDataEnd > pTempBuf.get() + nRead)
+ {
+ SAL_WARN("vcl", "corrupted job setup");
+ }
+ else
+ {
+ sal_uInt8* pNewDriverData = static_cast<sal_uInt8*>(
+ std::malloc( rJobData.GetDriverDataLen() ));
+ memcpy( pNewDriverData, pDriverData, rJobData.GetDriverDataLen() );
+ rJobData.SetDriverData( pNewDriverData );
+ }
+ }
+ if( nSystem == JOBSET_FILE605_SYSTEM )
+ {
+ rIStream.Seek( nFirstPos + sizeof( ImplOldJobSetupData ) +
+ sizeof( Impl364JobSetupData ) + rJobData.GetDriverDataLen() );
+ while( rIStream.Tell() < nFirstPos + nRead )
+ {
+ OUString aKey = read_uInt16_lenPrefixed_uInt8s_ToOUString(rIStream, RTL_TEXTENCODING_UTF8);
+ OUString aValue = read_uInt16_lenPrefixed_uInt8s_ToOUString(rIStream, RTL_TEXTENCODING_UTF8);
+ if( aKey == "COMPAT_DUPLEX_MODE" )
+ {
+ if( aValue == "DuplexMode::Unknown" )
+ rJobData.SetDuplexMode( DuplexMode::Unknown );
+ else if( aValue == "DuplexMode::Off" )
+ rJobData.SetDuplexMode( DuplexMode::Off );
+ else if( aValue == "DuplexMode::ShortEdge" )
+ rJobData.SetDuplexMode( DuplexMode::ShortEdge );
+ else if( aValue == "DuplexMode::LongEdge" )
+ rJobData.SetDuplexMode( DuplexMode::LongEdge );
+ }
+ else
+ rJobData.SetValueMap(aKey, aValue);
+ }
+ SAL_WARN_IF( rIStream.Tell() != nFirstPos+nRead, "vcl", "corrupted job setup" );
+ // ensure correct stream position
+ rIStream.Seek(nFirstPos + nRead);
+ }
+ }
+ }
+ }
+
+ return rIStream;
+}
+
+SvStream& WriteJobSetup( SvStream& rOStream, const JobSetup& rJobSetup )
+{
+ {
+ sal_uInt16 nLen = 0;
+ if ( rJobSetup.IsDefault() )
+ rOStream.WriteUInt16( nLen );
+ else
+ {
+ const ImplJobSetup& rJobData = rJobSetup.ImplGetConstData();
+ Impl364JobSetupData aOldJobData;
+ sal_uInt16 nOldJobDataSize = sizeof( aOldJobData );
+ ShortToSVBT16( nOldJobDataSize, aOldJobData.nSize );
+ ShortToSVBT16( rJobData.GetSystem(), aOldJobData.nSystem );
+ UInt32ToSVBT32( rJobData.GetDriverDataLen(), aOldJobData.nDriverDataLen );
+ ShortToSVBT16( static_cast<sal_uInt16>(rJobData.GetOrientation()), aOldJobData.nOrientation );
+ ShortToSVBT16( rJobData.GetPaperBin(), aOldJobData.nPaperBin );
+ ShortToSVBT16( static_cast<sal_uInt16>(rJobData.GetPaperFormat()), aOldJobData.nPaperFormat );
+ UInt32ToSVBT32( static_cast<sal_uLong>(rJobData.GetPaperWidth()), aOldJobData.nPaperWidth );
+ UInt32ToSVBT32( static_cast<sal_uLong>(rJobData.GetPaperHeight()), aOldJobData.nPaperHeight );
+
+ ImplOldJobSetupData aOldData = {};
+ OString aPrnByteName(OUStringToOString(rJobData.GetPrinterName(), RTL_TEXTENCODING_UTF8));
+ strncpy(aOldData.cPrinterName, aPrnByteName.getStr(), SAL_N_ELEMENTS(aOldData.cPrinterName) - 1);
+ OString aDriverByteName(OUStringToOString(rJobData.GetDriver(), RTL_TEXTENCODING_UTF8));
+ strncpy(aOldData.cDriverName, aDriverByteName.getStr(), SAL_N_ELEMENTS(aOldData.cDriverName) - 1);
+ int nPos = rOStream.Tell();
+ rOStream.WriteUInt16( 0 );
+ rOStream.WriteUInt16( JOBSET_FILE605_SYSTEM );
+ rOStream.WriteBytes( &aOldData, sizeof( aOldData ) );
+ rOStream.WriteBytes( &aOldJobData, nOldJobDataSize );
+ rOStream.WriteBytes( rJobData.GetDriverData(), rJobData.GetDriverDataLen() );
+
+ const std::unordered_map< OUString, OUString >& rValueMap(
+ rJobData.GetValueMap());
+
+ for (auto const& value : rValueMap)
+ {
+ write_uInt16_lenPrefixed_uInt8s_FromOUString(rOStream, value.first, RTL_TEXTENCODING_UTF8);
+ write_uInt16_lenPrefixed_uInt8s_FromOUString(rOStream, value.second, RTL_TEXTENCODING_UTF8);
+ }
+ write_uInt16_lenPrefixed_uInt8s_FromOString(rOStream, "COMPAT_DUPLEX_MODE");
+ switch( rJobData.GetDuplexMode() )
+ {
+ case DuplexMode::Unknown:
+ write_uInt16_lenPrefixed_uInt8s_FromOString(rOStream, "DuplexMode::Unknown");
+ break;
+ case DuplexMode::Off:
+ write_uInt16_lenPrefixed_uInt8s_FromOString(rOStream, "DuplexMode::Off");
+ break;
+ case DuplexMode::ShortEdge:
+ write_uInt16_lenPrefixed_uInt8s_FromOString(rOStream, "DuplexMode::ShortEdge");
+ break;
+ case DuplexMode::LongEdge:
+ write_uInt16_lenPrefixed_uInt8s_FromOString(rOStream, "DuplexMode::LongEdge");
+ break;
+ }
+ nLen = sal::static_int_cast<sal_uInt16>(rOStream.Tell() - nPos);
+ rOStream.Seek( nPos );
+ rOStream.WriteUInt16( nLen );
+ rOStream.Seek( nPos + nLen );
+ }
+ }
+
+ return rOStream;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/lineinfo.cxx b/vcl/source/gdi/lineinfo.cxx
new file mode 100644
index 000000000..dd460d77e
--- /dev/null
+++ b/vcl/source/gdi/lineinfo.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 <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, sal_Int32 nWidth ) : mpImplLineInfo()
+{
+ 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( sal_Int32 nWidth )
+{
+ mpImplLineInfo->mnWidth = nWidth;
+}
+
+void LineInfo::SetDashCount( sal_uInt16 nDashCount )
+{
+ mpImplLineInfo->mnDashCount = nDashCount;
+}
+
+void LineInfo::SetDashLen( sal_Int32 nDashLen )
+{
+ mpImplLineInfo->mnDashLen = nDashLen;
+}
+
+void LineInfo::SetDotCount( sal_uInt16 nDotCount )
+{
+ mpImplLineInfo->mnDotCount = nDotCount;
+}
+
+void LineInfo::SetDotLen( sal_Int32 nDotLen )
+{
+ mpImplLineInfo->mnDotLen = nDotLen;
+}
+
+void LineInfo::SetDistance( sal_Int32 nDistance )
+{
+ mpImplLineInfo->mnDistance = nDistance;
+}
+
+void LineInfo::SetLineJoin(basegfx::B2DLineJoin eLineJoin)
+{
+
+ if(eLineJoin != mpImplLineInfo->meLineJoin)
+ {
+ mpImplLineInfo->meLineJoin = eLineJoin;
+ }
+}
+
+void LineInfo::SetLineCap(css::drawing::LineCap eLineCap)
+{
+ if(eLineCap != mpImplLineInfo->meLineCap)
+ {
+ mpImplLineInfo->meLineCap = eLineCap;
+ }
+}
+
+bool LineInfo::IsDefault() const
+{
+ return( !mpImplLineInfo->mnWidth
+ && ( LineStyle::Solid == mpImplLineInfo->meStyle )
+ && ( css::drawing::LineCap_BUTT == mpImplLineInfo->meLineCap));
+}
+
+SvStream& ReadLineInfo( SvStream& rIStm, LineInfo& rLineInfo )
+{
+ VersionCompat aCompat( rIStm, StreamMode::READ );
+ 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);
+ }
+
+ return rIStm;
+}
+
+SvStream& WriteLineInfo( SvStream& rOStm, const LineInfo& rLineInfo )
+{
+ VersionCompat aCompat( rOStm, StreamMode::WRITE, 4 );
+
+ // version 1
+ rOStm.WriteUInt16( static_cast<sal_uInt16>(rLineInfo.mpImplLineInfo->meStyle) )
+ .WriteInt32( rLineInfo.mpImplLineInfo->mnWidth );
+
+ // since version2
+ rOStm.WriteUInt16( rLineInfo.mpImplLineInfo->mnDashCount )
+ .WriteInt32( rLineInfo.mpImplLineInfo->mnDashLen );
+ rOStm.WriteUInt16( rLineInfo.mpImplLineInfo->mnDotCount )
+ .WriteInt32( rLineInfo.mpImplLineInfo->mnDotLen );
+ rOStm.WriteInt32( 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) );
+
+ return rOStm;
+}
+
+void LineInfo::applyToB2DPolyPolygon(
+ basegfx::B2DPolyPolygon& io_rLinePolyPolygon,
+ basegfx::B2DPolyPolygon& o_rFillPolyPolygon) const
+{
+ o_rFillPolyPolygon.clear();
+
+ if(io_rLinePolyPolygon.count())
+ {
+ if(LineStyle::Dash == GetStyle())
+ {
+ ::std::vector< double > 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);
+ }
+
+ const double fAccumulated(::std::accumulate(fDotDashArray.begin(), fDotDashArray.end(), 0.0));
+
+ if(fAccumulated > 0.0)
+ {
+ basegfx::B2DPolyPolygon aResult;
+
+ for(auto const& rPolygon : io_rLinePolyPolygon)
+ {
+ basegfx::B2DPolyPolygon aLineTraget;
+ basegfx::utils::applyLineDashing(
+ rPolygon,
+ fDotDashArray,
+ &aLineTraget);
+ aResult.append(aLineTraget);
+ }
+
+ io_rLinePolyPolygon = aResult;
+ }
+ }
+
+ if(GetWidth() > 1 && io_rLinePolyPolygon.count())
+ {
+ const double fHalfLineWidth((GetWidth() * 0.5) + 0.5);
+
+ for(auto const& rPolygon : io_rLinePolyPolygon)
+ {
+ o_rFillPolyPolygon.append(basegfx::utils::createAreaGeometry(
+ rPolygon,
+ fHalfLineWidth,
+ GetLineJoin(),
+ GetLineCap()));
+ }
+
+ io_rLinePolyPolygon.clear();
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/mapmod.cxx b/vcl/source/gdi/mapmod.cxx
new file mode 100644
index 000000000..5103da661
--- /dev/null
+++ b/vcl/source/gdi/mapmod.cxx
@@ -0,0 +1,178 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <tools/gen.hxx>
+#include <tools/fract.hxx>
+#include <tools/stream.hxx>
+#include <tools/vcompat.hxx>
+#include <rtl/instance.hxx>
+#include <TypeSerializer.hxx>
+
+struct MapMode::ImplMapMode
+{
+ MapUnit meUnit;
+ 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;
+ bool mbSimple;
+
+ ImplMapMode();
+ ImplMapMode(const ImplMapMode& rImpMapMode);
+
+ bool operator==( const ImplMapMode& rImpMapMode ) const;
+};
+
+MapMode::ImplMapMode::ImplMapMode() :
+ maOrigin( 0, 0 ),
+ maScaleX( 1, 1 ),
+ maScaleY( 1, 1 )
+{
+ meUnit = MapUnit::MapPixel;
+ mbSimple = true;
+}
+
+MapMode::ImplMapMode::ImplMapMode( const ImplMapMode& ) = default;
+
+bool MapMode::ImplMapMode::operator==( const ImplMapMode& rImpMapMode ) const
+{
+ return meUnit == rImpMapMode.meUnit
+ && maOrigin == rImpMapMode.maOrigin
+ && maScaleX == rImpMapMode.maScaleX
+ && maScaleY == rImpMapMode.maScaleY;
+}
+
+namespace
+{
+ struct theGlobalDefault :
+ public rtl::Static< MapMode::ImplType, theGlobalDefault > {};
+}
+
+MapMode::MapMode() : mpImplMapMode(theGlobalDefault::get())
+{
+}
+
+MapMode::MapMode( const MapMode& ) = default;
+
+MapMode::MapMode( MapUnit eUnit ) : mpImplMapMode()
+{
+ mpImplMapMode->meUnit = eUnit;
+}
+
+MapMode::MapMode( MapUnit eUnit, const Point& rLogicOrg,
+ const Fraction& rScaleX, const Fraction& rScaleY )
+{
+ mpImplMapMode->meUnit = eUnit;
+ mpImplMapMode->maOrigin = rLogicOrg;
+ mpImplMapMode->maScaleX = rScaleX;
+ mpImplMapMode->maScaleY = rScaleY;
+ mpImplMapMode->maScaleX.ReduceInaccurate(32);
+ mpImplMapMode->maScaleY.ReduceInaccurate(32);
+ mpImplMapMode->mbSimple = false;
+}
+
+MapMode::~MapMode() = default;
+
+void MapMode::SetMapUnit( MapUnit eUnit )
+{
+ mpImplMapMode->meUnit = eUnit;
+}
+
+void MapMode::SetOrigin( const Point& rLogicOrg )
+{
+ mpImplMapMode->maOrigin = rLogicOrg;
+ mpImplMapMode->mbSimple = false;
+}
+
+void MapMode::SetScaleX( const Fraction& rScaleX )
+{
+ mpImplMapMode->maScaleX = rScaleX;
+ mpImplMapMode->maScaleX.ReduceInaccurate(32);
+ mpImplMapMode->mbSimple = false;
+}
+
+void MapMode::SetScaleY( const Fraction& rScaleY )
+{
+ mpImplMapMode->maScaleY = rScaleY;
+ mpImplMapMode->maScaleY.ReduceInaccurate(32);
+ mpImplMapMode->mbSimple = false;
+}
+
+MapMode& MapMode::operator=( const MapMode& ) = default;
+
+MapMode& MapMode::operator=( MapMode&& ) = default;
+
+bool MapMode::operator==( const MapMode& rMapMode ) const
+{
+ return mpImplMapMode == rMapMode.mpImplMapMode;
+}
+
+bool MapMode::IsDefault() const
+{
+ return mpImplMapMode.same_object(theGlobalDefault::get());
+}
+
+SvStream& ReadMapMode( SvStream& rIStm, MapMode& rMapMode )
+{
+ VersionCompat aCompat( rIStm, StreamMode::READ );
+ sal_uInt16 nTmp16;
+
+ TypeSerializer aSerializer(rIStm);
+
+ rIStm.ReadUInt16( nTmp16 ); rMapMode.mpImplMapMode->meUnit = static_cast<MapUnit>(nTmp16);
+ aSerializer.readPoint(rMapMode.mpImplMapMode->maOrigin);
+ ReadFraction( rIStm, rMapMode.mpImplMapMode->maScaleX );
+ ReadFraction( rIStm, rMapMode.mpImplMapMode->maScaleY );
+ rIStm.ReadCharAsBool( rMapMode.mpImplMapMode->mbSimple );
+
+ return rIStm;
+}
+
+SvStream& WriteMapMode( SvStream& rOStm, const MapMode& rMapMode )
+{
+ VersionCompat aCompat( rOStm, StreamMode::WRITE, 1 );
+
+ TypeSerializer aSerializer(rOStm);
+
+ rOStm.WriteUInt16( static_cast<sal_uInt16>(rMapMode.mpImplMapMode->meUnit) );
+ aSerializer.writePoint(rMapMode.mpImplMapMode->maOrigin);
+ WriteFraction( rOStm, rMapMode.mpImplMapMode->maScaleX );
+ WriteFraction( rOStm, rMapMode.mpImplMapMode->maScaleY );
+ rOStm.WriteBool( rMapMode.mpImplMapMode->mbSimple );
+
+ return rOStm;
+}
+
+
+MapUnit MapMode::GetMapUnit() const { return mpImplMapMode->meUnit; }
+
+const Point& MapMode::GetOrigin() const { return mpImplMapMode->maOrigin; }
+
+const Fraction& MapMode::GetScaleX() const { return mpImplMapMode->maScaleX; }
+
+const Fraction& MapMode::GetScaleY() const { return mpImplMapMode->maScaleY; }
+
+bool MapMode::IsSimple() const { return mpImplMapMode->mbSimple; }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/metaact.cxx b/vcl/source/gdi/metaact.cxx
new file mode 100644
index 000000000..0f55e2fbe
--- /dev/null
+++ b/vcl/source/gdi/metaact.cxx
@@ -0,0 +1,3412 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <osl/thread.h>
+#include <sal/log.hxx>
+#include <tools/stream.hxx>
+#include <tools/vcompat.hxx>
+#include <tools/helpers.hxx>
+#include <vcl/dibtools.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/graphictools.hxx>
+#include <unotools/fontdefs.hxx>
+#include <TypeSerializer.hxx>
+
+namespace
+{
+
+const char *
+meta_action_name(MetaActionType nMetaAction)
+{
+#ifndef SAL_LOG_INFO
+ (void) nMetaAction;
+ return "";
+#else
+ switch( nMetaAction )
+ {
+ case MetaActionType::NONE: return "NULL";
+ case MetaActionType::PIXEL: return "PIXEL";
+ case MetaActionType::POINT: return "POINT";
+ case MetaActionType::LINE: return "LINE";
+ case MetaActionType::RECT: return "RECT";
+ case MetaActionType::ROUNDRECT: return "ROUNDRECT";
+ case MetaActionType::ELLIPSE: return "ELLIPSE";
+ case MetaActionType::ARC: return "ARC";
+ case MetaActionType::PIE: return "PIE";
+ case MetaActionType::CHORD: return "CHORD";
+ case MetaActionType::POLYLINE: return "POLYLINE";
+ case MetaActionType::POLYGON: return "POLYGON";
+ case MetaActionType::POLYPOLYGON: return "POLYPOLYGON";
+ case MetaActionType::TEXT: return "TEXT";
+ case MetaActionType::TEXTARRAY: return "TEXTARRAY";
+ case MetaActionType::STRETCHTEXT: return "STRETCHTEXT";
+ case MetaActionType::TEXTRECT: return "TEXTRECT";
+ case MetaActionType::BMP: return "BMP";
+ case MetaActionType::BMPSCALE: return "BMPSCALE";
+ case MetaActionType::BMPSCALEPART: return "BMPSCALEPART";
+ case MetaActionType::BMPEX: return "BMPEX";
+ case MetaActionType::BMPEXSCALE: return "BMPEXSCALE";
+ case MetaActionType::BMPEXSCALEPART: return "BMPEXSCALEPART";
+ case MetaActionType::MASK: return "MASK";
+ case MetaActionType::MASKSCALE: return "MASKSCALE";
+ case MetaActionType::MASKSCALEPART: return "MASKSCALEPART";
+ case MetaActionType::GRADIENT: return "GRADIENT";
+ case MetaActionType::HATCH: return "HATCH";
+ case MetaActionType::WALLPAPER: return "WALLPAPER";
+ case MetaActionType::CLIPREGION: return "CLIPREGION";
+ case MetaActionType::ISECTRECTCLIPREGION: return "ISECTRECTCLIPREGION";
+ case MetaActionType::ISECTREGIONCLIPREGION: return "ISECTREGIONCLIPREGION";
+ case MetaActionType::MOVECLIPREGION: return "MOVECLIPREGION";
+ case MetaActionType::LINECOLOR: return "LINECOLOR";
+ case MetaActionType::FILLCOLOR: return "FILLCOLOR";
+ case MetaActionType::TEXTCOLOR: return "TEXTCOLOR";
+ case MetaActionType::TEXTFILLCOLOR: return "TEXTFILLCOLOR";
+ case MetaActionType::TEXTALIGN: return "TEXTALIGN";
+ case MetaActionType::MAPMODE: return "MAPMODE";
+ case MetaActionType::FONT: return "FONT";
+ case MetaActionType::PUSH: return "PUSH";
+ case MetaActionType::POP: return "POP";
+ case MetaActionType::RASTEROP: return "RASTEROP";
+ case MetaActionType::Transparent: return "TRANSPARENT";
+ case MetaActionType::EPS: return "EPS";
+ case MetaActionType::REFPOINT: return "REFPOINT";
+ case MetaActionType::TEXTLINECOLOR: return "TEXTLINECOLOR";
+ case MetaActionType::TEXTLINE: return "TEXTLINE";
+ case MetaActionType::FLOATTRANSPARENT: return "FLOATTRANSPARENT";
+ case MetaActionType::GRADIENTEX: return "GRADIENTEX";
+ case MetaActionType::LAYOUTMODE: return "LAYOUTMODE";
+ case MetaActionType::TEXTLANGUAGE: return "TEXTLANGUAGE";
+ case MetaActionType::OVERLINECOLOR: return "OVERLINECOLOR";
+ case MetaActionType::COMMENT: return "COMMENT";
+ default:
+ // Yes, return a pointer to a static buffer. This is a very
+ // local debugging output function, so no big deal.
+ static char buffer[11];
+ sprintf(buffer, "%u", static_cast<unsigned int>(nMetaAction));
+ return buffer;
+ }
+#endif
+}
+
+void ImplScalePoint( Point& rPt, double fScaleX, double fScaleY )
+{
+ rPt.setX( FRound( fScaleX * rPt.X() ) );
+ rPt.setY( FRound( fScaleY * rPt.Y() ) );
+}
+
+void ImplScaleRect( tools::Rectangle& rRect, double fScaleX, double fScaleY )
+{
+ Point aTL( rRect.TopLeft() );
+ Point aBR( rRect.BottomRight() );
+
+ ImplScalePoint( aTL, fScaleX, fScaleY );
+ ImplScalePoint( aBR, fScaleX, fScaleY );
+
+ rRect = tools::Rectangle( aTL, aBR );
+ rRect.Justify();
+}
+
+void ImplScalePoly( tools::Polygon& rPoly, double fScaleX, double fScaleY )
+{
+ for( sal_uInt16 i = 0, nCount = rPoly.GetSize(); i < nCount; i++ )
+ ImplScalePoint( rPoly[ i ], fScaleX, fScaleY );
+}
+
+void ImplScaleLineInfo( LineInfo& rLineInfo, double fScaleX, double fScaleY )
+{
+ if( !rLineInfo.IsDefault() )
+ {
+ const double fScale = ( fabs(fScaleX) + fabs(fScaleY) ) * 0.5;
+
+ rLineInfo.SetWidth( FRound( fScale * rLineInfo.GetWidth() ) );
+ rLineInfo.SetDashLen( FRound( fScale * rLineInfo.GetDashLen() ) );
+ rLineInfo.SetDotLen( FRound( fScale * rLineInfo.GetDotLen() ) );
+ rLineInfo.SetDistance( FRound( fScale * rLineInfo.GetDistance() ) );
+ }
+}
+
+} //anonymous namespace
+
+MetaAction::MetaAction() :
+ mnType( MetaActionType::NONE )
+{
+}
+
+MetaAction::MetaAction( MetaActionType nType ) :
+ mnType( nType )
+{
+}
+
+MetaAction::MetaAction( MetaAction const & rOther ) :
+ SimpleReferenceObject(), mnType( rOther.mnType )
+{
+}
+
+MetaAction::~MetaAction()
+{
+}
+
+void MetaAction::Execute( OutputDevice* )
+{
+}
+
+rtl::Reference<MetaAction> MetaAction::Clone()
+{
+ return new MetaAction;
+}
+
+void MetaAction::Move( long, long )
+{
+}
+
+void MetaAction::Scale( double, double )
+{
+}
+
+void MetaAction::Write( SvStream& rOStm, ImplMetaWriteData* )
+{
+ rOStm.WriteUInt16( static_cast<sal_uInt16>(mnType) );
+}
+
+void MetaAction::Read( SvStream&, ImplMetaReadData* )
+{
+ // DO NOT read mnType - ReadMetaAction already did that!
+}
+
+MetaAction* MetaAction::ReadMetaAction( SvStream& rIStm, ImplMetaReadData* pData )
+{
+ MetaAction* pAction = nullptr;
+ sal_uInt16 nTmp = 0;
+ rIStm.ReadUInt16( nTmp );
+ MetaActionType nType = static_cast<MetaActionType>(nTmp);
+
+ SAL_INFO("vcl.gdi", "ReadMetaAction " << meta_action_name( nType ));
+
+ switch( nType )
+ {
+ case MetaActionType::NONE: pAction = new MetaAction; break;
+ case MetaActionType::PIXEL: pAction = new MetaPixelAction; break;
+ case MetaActionType::POINT: pAction = new MetaPointAction; break;
+ case MetaActionType::LINE: pAction = new MetaLineAction; break;
+ case MetaActionType::RECT: pAction = new MetaRectAction; break;
+ case MetaActionType::ROUNDRECT: pAction = new MetaRoundRectAction; break;
+ case MetaActionType::ELLIPSE: pAction = new MetaEllipseAction; break;
+ case MetaActionType::ARC: pAction = new MetaArcAction; break;
+ case MetaActionType::PIE: pAction = new MetaPieAction; break;
+ case MetaActionType::CHORD: pAction = new MetaChordAction; break;
+ case MetaActionType::POLYLINE: pAction = new MetaPolyLineAction; break;
+ case MetaActionType::POLYGON: pAction = new MetaPolygonAction; break;
+ case MetaActionType::POLYPOLYGON: pAction = new MetaPolyPolygonAction; break;
+ case MetaActionType::TEXT: pAction = new MetaTextAction; break;
+ case MetaActionType::TEXTARRAY: pAction = new MetaTextArrayAction; break;
+ case MetaActionType::STRETCHTEXT: pAction = new MetaStretchTextAction; break;
+ case MetaActionType::TEXTRECT: pAction = new MetaTextRectAction; break;
+ case MetaActionType::TEXTLINE: pAction = new MetaTextLineAction; break;
+ case MetaActionType::BMP: pAction = new MetaBmpAction; break;
+ case MetaActionType::BMPSCALE: pAction = new MetaBmpScaleAction; break;
+ case MetaActionType::BMPSCALEPART: pAction = new MetaBmpScalePartAction; break;
+ case MetaActionType::BMPEX: pAction = new MetaBmpExAction; break;
+ case MetaActionType::BMPEXSCALE: pAction = new MetaBmpExScaleAction; break;
+ case MetaActionType::BMPEXSCALEPART: pAction = new MetaBmpExScalePartAction; break;
+ case MetaActionType::MASK: pAction = new MetaMaskAction; break;
+ case MetaActionType::MASKSCALE: pAction = new MetaMaskScaleAction; break;
+ case MetaActionType::MASKSCALEPART: pAction = new MetaMaskScalePartAction; break;
+ case MetaActionType::GRADIENT: pAction = new MetaGradientAction; break;
+ case MetaActionType::GRADIENTEX: pAction = new MetaGradientExAction; break;
+ case MetaActionType::HATCH: pAction = new MetaHatchAction; break;
+ case MetaActionType::WALLPAPER: pAction = new MetaWallpaperAction; break;
+ case MetaActionType::CLIPREGION: pAction = new MetaClipRegionAction; break;
+ case MetaActionType::ISECTRECTCLIPREGION: pAction = new MetaISectRectClipRegionAction; break;
+ case MetaActionType::ISECTREGIONCLIPREGION: pAction = new MetaISectRegionClipRegionAction; break;
+ case MetaActionType::MOVECLIPREGION: pAction = new MetaMoveClipRegionAction; break;
+ case MetaActionType::LINECOLOR: pAction = new MetaLineColorAction; break;
+ case MetaActionType::FILLCOLOR: pAction = new MetaFillColorAction; break;
+ case MetaActionType::TEXTCOLOR: pAction = new MetaTextColorAction; break;
+ case MetaActionType::TEXTFILLCOLOR: pAction = new MetaTextFillColorAction; break;
+ case MetaActionType::TEXTLINECOLOR: pAction = new MetaTextLineColorAction; break;
+ case MetaActionType::OVERLINECOLOR: pAction = new MetaOverlineColorAction; break;
+ case MetaActionType::TEXTALIGN: pAction = new MetaTextAlignAction; break;
+ case MetaActionType::MAPMODE: pAction = new MetaMapModeAction; break;
+ case MetaActionType::FONT: pAction = new MetaFontAction; break;
+ case MetaActionType::PUSH: pAction = new MetaPushAction; break;
+ case MetaActionType::POP: pAction = new MetaPopAction; break;
+ case MetaActionType::RASTEROP: pAction = new MetaRasterOpAction; break;
+ case MetaActionType::Transparent: pAction = new MetaTransparentAction; break;
+ case MetaActionType::FLOATTRANSPARENT: pAction = new MetaFloatTransparentAction; break;
+ case MetaActionType::EPS: pAction = new MetaEPSAction; break;
+ case MetaActionType::REFPOINT: pAction = new MetaRefPointAction; break;
+ case MetaActionType::COMMENT: pAction = new MetaCommentAction; break;
+ case MetaActionType::LAYOUTMODE: pAction = new MetaLayoutModeAction; break;
+ case MetaActionType::TEXTLANGUAGE: pAction = new MetaTextLanguageAction; break;
+
+ default:
+ {
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ }
+ break;
+ }
+
+ if( pAction )
+ pAction->Read( rIStm, pData );
+
+ return pAction;
+}
+
+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()
+{
+ return new MetaPixelAction( *this );
+}
+
+void MetaPixelAction::Move( long nHorzMove, long nVertMove )
+{
+ maPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaPixelAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoint( maPt, fScaleX, fScaleY );
+}
+
+void MetaPixelAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(maPt);
+ rOStm.WriteUInt32(maColor.mValue);
+}
+
+void MetaPixelAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(maPt);
+ rIStm.ReadUInt32(maColor.mValue);
+}
+
+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()
+{
+ return new MetaPointAction( *this );
+}
+
+void MetaPointAction::Move( long nHorzMove, long nVertMove )
+{
+ maPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaPointAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoint( maPt, fScaleX, fScaleY );
+}
+
+void MetaPointAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(maPt);
+}
+
+void MetaPointAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(maPt);
+}
+
+MetaLineAction::MetaLineAction() :
+ MetaAction(MetaActionType::LINE)
+{}
+
+MetaLineAction::~MetaLineAction()
+{}
+
+MetaLineAction::MetaLineAction( const Point& rStart, const Point& rEnd ) :
+ MetaAction ( MetaActionType::LINE ),
+ maStartPt ( rStart ),
+ maEndPt ( rEnd )
+{}
+
+MetaLineAction::MetaLineAction( const Point& rStart, const Point& rEnd,
+ const LineInfo& rLineInfo ) :
+ MetaAction ( MetaActionType::LINE ),
+ maLineInfo ( rLineInfo ),
+ maStartPt ( rStart ),
+ maEndPt ( rEnd )
+{}
+
+void MetaLineAction::Execute( OutputDevice* pOut )
+{
+ if( maLineInfo.IsDefault() )
+ pOut->DrawLine( maStartPt, maEndPt );
+ else
+ pOut->DrawLine( maStartPt, maEndPt, maLineInfo );
+}
+
+rtl::Reference<MetaAction> MetaLineAction::Clone()
+{
+ return new MetaLineAction( *this );
+}
+
+void MetaLineAction::Move( long nHorzMove, 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 );
+}
+
+void MetaLineAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 2);
+
+ // Version 1
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(maStartPt);
+ aSerializer.writePoint(maEndPt);
+ // Version 2
+ WriteLineInfo( rOStm, maLineInfo );
+}
+
+void MetaLineAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+
+ // Version 1
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(maStartPt);
+ aSerializer.readPoint(maEndPt);
+
+ // Version 2
+ if( aCompat.GetVersion() >= 2 )
+ {
+ ReadLineInfo( rIStm, maLineInfo );
+ }
+}
+
+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()
+{
+ return new MetaRectAction( *this );
+}
+
+void MetaRectAction::Move( long nHorzMove, long nVertMove )
+{
+ maRect.Move( nHorzMove, nVertMove );
+}
+
+void MetaRectAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScaleRect( maRect, fScaleX, fScaleY );
+}
+
+void MetaRectAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writeRectangle(maRect);
+}
+
+void MetaRectAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readRectangle(maRect);
+}
+
+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()
+{
+ return new MetaRoundRectAction( *this );
+}
+
+void MetaRoundRectAction::Move( long nHorzMove, 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) );
+}
+
+void MetaRoundRectAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writeRectangle(maRect);
+ rOStm.WriteUInt32( mnHorzRound ).WriteUInt32( mnVertRound );
+}
+
+void MetaRoundRectAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readRectangle(maRect);
+ rIStm.ReadUInt32( mnHorzRound ).ReadUInt32( mnVertRound );
+}
+
+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()
+{
+ return new MetaEllipseAction( *this );
+}
+
+void MetaEllipseAction::Move( long nHorzMove, long nVertMove )
+{
+ maRect.Move( nHorzMove, nVertMove );
+}
+
+void MetaEllipseAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScaleRect( maRect, fScaleX, fScaleY );
+}
+
+void MetaEllipseAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writeRectangle(maRect);
+}
+
+void MetaEllipseAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readRectangle(maRect);
+}
+
+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()
+{
+ return new MetaArcAction( *this );
+}
+
+void MetaArcAction::Move( long nHorzMove, 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 );
+}
+
+void MetaArcAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writeRectangle(maRect);
+ aSerializer.writePoint(maStartPt);
+ aSerializer.writePoint(maEndPt);
+}
+
+void MetaArcAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readRectangle(maRect);
+ aSerializer.readPoint(maStartPt);
+ aSerializer.readPoint(maEndPt);
+}
+
+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()
+{
+ return new MetaPieAction( *this );
+}
+
+void MetaPieAction::Move( long nHorzMove, 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 );
+}
+
+void MetaPieAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writeRectangle(maRect);
+ aSerializer.writePoint(maStartPt);
+ aSerializer.writePoint(maEndPt);
+}
+
+void MetaPieAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readRectangle(maRect);
+ aSerializer.readPoint(maStartPt);
+ aSerializer.readPoint(maEndPt);
+}
+
+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()
+{
+ return new MetaChordAction( *this );
+}
+
+void MetaChordAction::Move( long nHorzMove, 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 );
+}
+
+void MetaChordAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writeRectangle(maRect);
+ aSerializer.writePoint(maStartPt);
+ aSerializer.writePoint(maEndPt);
+}
+
+void MetaChordAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readRectangle(maRect);
+ aSerializer.readPoint(maStartPt);
+ aSerializer.readPoint(maEndPt);
+}
+
+MetaPolyLineAction::MetaPolyLineAction() :
+ MetaAction(MetaActionType::POLYLINE)
+{}
+
+MetaPolyLineAction::~MetaPolyLineAction()
+{}
+
+MetaPolyLineAction::MetaPolyLineAction( const tools::Polygon& rPoly ) :
+ MetaAction ( MetaActionType::POLYLINE ),
+ maPoly ( rPoly )
+{}
+
+MetaPolyLineAction::MetaPolyLineAction( const tools::Polygon& rPoly, const LineInfo& rLineInfo ) :
+ MetaAction ( MetaActionType::POLYLINE ),
+ maLineInfo ( rLineInfo ),
+ maPoly ( rPoly )
+{}
+
+void MetaPolyLineAction::Execute( OutputDevice* pOut )
+{
+ if( maLineInfo.IsDefault() )
+ pOut->DrawPolyLine( maPoly );
+ else
+ pOut->DrawPolyLine( maPoly, maLineInfo );
+}
+
+rtl::Reference<MetaAction> MetaPolyLineAction::Clone()
+{
+ return new MetaPolyLineAction( *this );
+}
+
+void MetaPolyLineAction::Move( long nHorzMove, long nVertMove )
+{
+ maPoly.Move( nHorzMove, nVertMove );
+}
+
+void MetaPolyLineAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoly( maPoly, fScaleX, fScaleY );
+ ImplScaleLineInfo( maLineInfo, fScaleX, fScaleY );
+}
+
+void MetaPolyLineAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 3);
+
+ tools::Polygon aSimplePoly;
+ maPoly.AdaptiveSubdivide( aSimplePoly );
+
+ WritePolygon( rOStm, aSimplePoly ); // Version 1
+ WriteLineInfo( rOStm, maLineInfo ); // Version 2
+
+ bool bHasPolyFlags = maPoly.HasFlags(); // Version 3
+ rOStm.WriteBool( bHasPolyFlags );
+ if ( bHasPolyFlags )
+ maPoly.Write( rOStm );
+}
+
+void MetaPolyLineAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+
+ // Version 1
+ ReadPolygon( rIStm, maPoly );
+
+ // Version 2
+ if( aCompat.GetVersion() >= 2 )
+ ReadLineInfo( rIStm, maLineInfo );
+ if ( aCompat.GetVersion() >= 3 )
+ {
+ sal_uInt8 bHasPolyFlags(0);
+ rIStm.ReadUChar( bHasPolyFlags );
+ if ( bHasPolyFlags )
+ maPoly.Read( rIStm );
+ }
+}
+
+MetaPolygonAction::MetaPolygonAction() :
+ MetaAction(MetaActionType::POLYGON)
+{}
+
+MetaPolygonAction::~MetaPolygonAction()
+{}
+
+MetaPolygonAction::MetaPolygonAction( const tools::Polygon& rPoly ) :
+ MetaAction ( MetaActionType::POLYGON ),
+ maPoly ( rPoly )
+{}
+
+void MetaPolygonAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawPolygon( maPoly );
+}
+
+rtl::Reference<MetaAction> MetaPolygonAction::Clone()
+{
+ return new MetaPolygonAction( *this );
+}
+
+void MetaPolygonAction::Move( long nHorzMove, long nVertMove )
+{
+ maPoly.Move( nHorzMove, nVertMove );
+}
+
+void MetaPolygonAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoly( maPoly, fScaleX, fScaleY );
+}
+
+void MetaPolygonAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 2);
+
+ tools::Polygon aSimplePoly; // Version 1
+ maPoly.AdaptiveSubdivide( aSimplePoly );
+ WritePolygon( rOStm, aSimplePoly );
+
+ bool bHasPolyFlags = maPoly.HasFlags(); // Version 2
+ rOStm.WriteBool( bHasPolyFlags );
+ if ( bHasPolyFlags )
+ maPoly.Write( rOStm );
+}
+
+void MetaPolygonAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+
+ ReadPolygon( rIStm, maPoly ); // Version 1
+
+ if( aCompat.GetVersion() >= 2 ) // Version 2
+ {
+ sal_uInt8 bHasPolyFlags(0);
+ rIStm.ReadUChar( bHasPolyFlags );
+ if ( bHasPolyFlags )
+ maPoly.Read( rIStm );
+ }
+}
+
+MetaPolyPolygonAction::MetaPolyPolygonAction() :
+ MetaAction(MetaActionType::POLYPOLYGON)
+{}
+
+MetaPolyPolygonAction::~MetaPolyPolygonAction()
+{}
+
+MetaPolyPolygonAction::MetaPolyPolygonAction( const tools::PolyPolygon& rPolyPoly ) :
+ MetaAction ( MetaActionType::POLYPOLYGON ),
+ maPolyPoly ( rPolyPoly )
+{}
+
+void MetaPolyPolygonAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawPolyPolygon( maPolyPoly );
+}
+
+rtl::Reference<MetaAction> MetaPolyPolygonAction::Clone()
+{
+ return new MetaPolyPolygonAction( *this );
+}
+
+void MetaPolyPolygonAction::Move( long nHorzMove, 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 );
+}
+
+void MetaPolyPolygonAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 2);
+
+ sal_uInt16 nNumberOfComplexPolygons = 0;
+ sal_uInt16 i, nPolyCount = maPolyPoly.Count();
+
+ tools::Polygon aSimplePoly; // Version 1
+ rOStm.WriteUInt16( nPolyCount );
+ for ( i = 0; i < nPolyCount; i++ )
+ {
+ const tools::Polygon& rPoly = maPolyPoly.GetObject( i );
+ if ( rPoly.HasFlags() )
+ nNumberOfComplexPolygons++;
+ rPoly.AdaptiveSubdivide( aSimplePoly );
+ WritePolygon( rOStm, aSimplePoly );
+ }
+
+ rOStm.WriteUInt16( nNumberOfComplexPolygons ); // Version 2
+ for ( i = 0; nNumberOfComplexPolygons && ( i < nPolyCount ); i++ )
+ {
+ const tools::Polygon& rPoly = maPolyPoly.GetObject( i );
+ if ( rPoly.HasFlags() )
+ {
+ rOStm.WriteUInt16( i );
+ rPoly.Write( rOStm );
+
+ nNumberOfComplexPolygons--;
+ }
+ }
+}
+
+void MetaPolyPolygonAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadPolyPolygon( rIStm, maPolyPoly ); // Version 1
+
+ if ( aCompat.GetVersion() >= 2 ) // Version 2
+ {
+ sal_uInt16 nNumberOfComplexPolygons(0);
+ rIStm.ReadUInt16( nNumberOfComplexPolygons );
+ const size_t nMinRecordSize = sizeof(sal_uInt16);
+ const size_t nMaxRecords = rIStm.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);
+ rIStm.ReadUInt16( nIndex );
+ tools::Polygon aPoly;
+ aPoly.Read( rIStm );
+ if (nIndex >= maPolyPoly.Count())
+ {
+ SAL_WARN("vcl.gdi", "svm contains polygon index " << nIndex
+ << " outside possible range " << maPolyPoly.Count());
+ continue;
+ }
+ maPolyPoly.Replace( aPoly, nIndex );
+ }
+ }
+}
+
+MetaTextAction::MetaTextAction() :
+ MetaAction ( MetaActionType::TEXT ),
+ mnIndex ( 0 ),
+ mnLen ( 0 )
+{}
+
+MetaTextAction::~MetaTextAction()
+{}
+
+MetaTextAction::MetaTextAction( const Point& rPt, const OUString& rStr,
+ sal_Int32 nIndex, sal_Int32 nLen ) :
+ MetaAction ( MetaActionType::TEXT ),
+ maPt ( rPt ),
+ maStr ( rStr ),
+ mnIndex ( nIndex ),
+ mnLen ( nLen )
+{}
+
+void MetaTextAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawText( maPt, maStr, mnIndex, mnLen );
+}
+
+rtl::Reference<MetaAction> MetaTextAction::Clone()
+{
+ return new MetaTextAction( *this );
+}
+
+void MetaTextAction::Move( long nHorzMove, long nVertMove )
+{
+ maPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaTextAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoint( maPt, fScaleX, fScaleY );
+}
+
+void MetaTextAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 2);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(maPt);
+ rOStm.WriteUniOrByteString( maStr, pData->meActualCharSet );
+ rOStm.WriteUInt16(mnIndex);
+ rOStm.WriteUInt16(mnLen);
+
+ write_uInt16_lenPrefixed_uInt16s_FromOUString(rOStm, maStr); // version 2
+}
+
+void MetaTextAction::Read( SvStream& rIStm, ImplMetaReadData* pData )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(maPt);
+ maStr = rIStm.ReadUniOrByteString(pData->meActualCharSet);
+ sal_uInt16 nTmpIndex(0);
+ rIStm.ReadUInt16(nTmpIndex);
+ mnIndex = nTmpIndex;
+ sal_uInt16 nTmpLen(0);
+ rIStm.ReadUInt16(nTmpLen);
+ mnLen = nTmpLen;
+
+ if ( aCompat.GetVersion() >= 2 ) // Version 2
+ maStr = read_uInt16_lenPrefixed_uInt16s_ToOUString(rIStm);
+}
+
+MetaTextArrayAction::MetaTextArrayAction() :
+ MetaAction ( MetaActionType::TEXTARRAY ),
+ mnIndex ( 0 ),
+ mnLen ( 0 )
+{}
+
+MetaTextArrayAction::MetaTextArrayAction( const MetaTextArrayAction& rAction ) :
+ MetaAction ( MetaActionType::TEXTARRAY ),
+ maStartPt ( rAction.maStartPt ),
+ maStr ( rAction.maStr ),
+ mnIndex ( rAction.mnIndex ),
+ mnLen ( rAction.mnLen )
+{
+ if( rAction.mpDXAry )
+ {
+ mpDXAry.reset( new long[ mnLen ] );
+ memcpy( mpDXAry.get(), rAction.mpDXAry.get(), mnLen * sizeof( long ) );
+ }
+}
+
+MetaTextArrayAction::MetaTextArrayAction( const Point& rStartPt,
+ const OUString& rStr,
+ const long* pDXAry,
+ sal_Int32 nIndex,
+ sal_Int32 nLen ) :
+ MetaAction ( MetaActionType::TEXTARRAY ),
+ maStartPt ( rStartPt ),
+ maStr ( rStr ),
+ mnIndex ( nIndex ),
+ mnLen ( nLen )
+{
+ const sal_Int32 nAryLen = pDXAry ? mnLen : 0;
+
+ if (nAryLen > 0)
+ {
+ mpDXAry.reset( new long[ nAryLen ] );
+ memcpy( mpDXAry.get(), pDXAry, nAryLen * sizeof(long) );
+ }
+}
+
+MetaTextArrayAction::~MetaTextArrayAction()
+{
+}
+
+void MetaTextArrayAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawTextArray( maStartPt, maStr, mpDXAry.get(), mnIndex, mnLen );
+}
+
+rtl::Reference<MetaAction> MetaTextArrayAction::Clone()
+{
+ return new MetaTextArrayAction( *this );
+}
+
+void MetaTextArrayAction::Move( long nHorzMove, long nVertMove )
+{
+ maStartPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaTextArrayAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoint( maStartPt, fScaleX, fScaleY );
+
+ if ( mpDXAry && mnLen )
+ {
+ for ( sal_uInt16 i = 0, nCount = mnLen; i < nCount; i++ )
+ mpDXAry[ i ] = FRound( mpDXAry[ i ] * fabs(fScaleX) );
+ }
+}
+
+void MetaTextArrayAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ const sal_Int32 nAryLen = mpDXAry ? mnLen : 0;
+
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 2);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(maStartPt);
+ rOStm.WriteUniOrByteString( maStr, pData->meActualCharSet );
+ rOStm.WriteUInt16(mnIndex);
+ rOStm.WriteUInt16(mnLen);
+ rOStm.WriteInt32(nAryLen);
+
+ for (sal_Int32 i = 0; i < nAryLen; ++i)
+ rOStm.WriteInt32( mpDXAry[ i ] );
+
+ write_uInt16_lenPrefixed_uInt16s_FromOUString(rOStm, maStr); // version 2
+}
+
+void MetaTextArrayAction::Read( SvStream& rIStm, ImplMetaReadData* pData )
+{
+ mpDXAry.reset();
+
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(maStartPt);
+ maStr = rIStm.ReadUniOrByteString(pData->meActualCharSet);
+ sal_uInt16 nTmpIndex(0);
+ rIStm.ReadUInt16(nTmpIndex);
+ mnIndex = nTmpIndex;
+ sal_uInt16 nTmpLen(0);
+ rIStm.ReadUInt16(nTmpLen);
+ mnLen = nTmpLen;
+ sal_Int32 nAryLen(0);
+ rIStm.ReadInt32(nAryLen);
+
+ if (mnLen > maStr.getLength() - mnIndex)
+ {
+ mnIndex = 0;
+ mpDXAry = nullptr;
+ return;
+ }
+
+ if( nAryLen )
+ {
+ // #i9762#, #106172# Ensure that DX array is at least mnLen entries long
+ if ( mnLen >= nAryLen )
+ {
+ mpDXAry.reset( new (std::nothrow)long[ mnLen ] );
+ if ( mpDXAry )
+ {
+ sal_Int32 i;
+ sal_Int32 val;
+ for( i = 0; i < nAryLen; i++ )
+ {
+ rIStm.ReadInt32( val);
+ mpDXAry[ i ] = val;
+ }
+ // #106172# setup remainder
+ for( ; i < mnLen; i++ )
+ mpDXAry[ i ] = 0;
+ }
+ }
+ else
+ {
+ mpDXAry = nullptr;
+ return;
+ }
+ }
+ else
+ mpDXAry = nullptr;
+
+ if ( aCompat.GetVersion() >= 2 ) // Version 2
+ {
+ maStr = read_uInt16_lenPrefixed_uInt16s_ToOUString(rIStm);
+
+ if ( mnIndex + mnLen > maStr.getLength() )
+ {
+ mnIndex = 0;
+ mpDXAry.reset();
+ }
+ }
+}
+
+MetaStretchTextAction::MetaStretchTextAction() :
+ MetaAction ( MetaActionType::STRETCHTEXT ),
+ mnWidth ( 0 ),
+ mnIndex ( 0 ),
+ mnLen ( 0 )
+{}
+
+MetaStretchTextAction::~MetaStretchTextAction()
+{}
+
+MetaStretchTextAction::MetaStretchTextAction( const Point& rPt, sal_uInt32 nWidth,
+ const OUString& rStr,
+ sal_Int32 nIndex, sal_Int32 nLen ) :
+ MetaAction ( MetaActionType::STRETCHTEXT ),
+ maPt ( rPt ),
+ maStr ( rStr ),
+ mnWidth ( nWidth ),
+ mnIndex ( nIndex ),
+ mnLen ( nLen )
+{}
+
+void MetaStretchTextAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawStretchText( maPt, mnWidth, maStr, mnIndex, mnLen );
+}
+
+rtl::Reference<MetaAction> MetaStretchTextAction::Clone()
+{
+ return new MetaStretchTextAction( *this );
+}
+
+void MetaStretchTextAction::Move( long nHorzMove, 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) ));
+}
+
+void MetaStretchTextAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 2);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(maPt);
+ rOStm.WriteUniOrByteString( maStr, pData->meActualCharSet );
+ rOStm.WriteUInt32( mnWidth );
+ rOStm.WriteUInt16( mnIndex );
+ rOStm.WriteUInt16( mnLen );
+
+ write_uInt16_lenPrefixed_uInt16s_FromOUString(rOStm, maStr); // version 2
+}
+
+void MetaStretchTextAction::Read( SvStream& rIStm, ImplMetaReadData* pData )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(maPt);
+ maStr = rIStm.ReadUniOrByteString(pData->meActualCharSet);
+ rIStm.ReadUInt32( mnWidth );
+ sal_uInt16 nTmpIndex(0);
+ rIStm.ReadUInt16(nTmpIndex);
+ mnIndex = nTmpIndex;
+ sal_uInt16 nTmpLen(0);
+ rIStm.ReadUInt16(nTmpLen);
+ mnLen = nTmpLen;
+
+ if ( aCompat.GetVersion() >= 2 ) // Version 2
+ maStr = read_uInt16_lenPrefixed_uInt16s_ToOUString(rIStm);
+}
+
+MetaTextRectAction::MetaTextRectAction() :
+ MetaAction ( MetaActionType::TEXTRECT ),
+ mnStyle ( DrawTextFlags::NONE )
+{}
+
+MetaTextRectAction::~MetaTextRectAction()
+{}
+
+MetaTextRectAction::MetaTextRectAction( const tools::Rectangle& rRect,
+ const OUString& rStr, DrawTextFlags nStyle ) :
+ MetaAction ( MetaActionType::TEXTRECT ),
+ maRect ( rRect ),
+ maStr ( rStr ),
+ mnStyle ( nStyle )
+{}
+
+void MetaTextRectAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawText( maRect, maStr, mnStyle );
+}
+
+rtl::Reference<MetaAction> MetaTextRectAction::Clone()
+{
+ return new MetaTextRectAction( *this );
+}
+
+void MetaTextRectAction::Move( long nHorzMove, long nVertMove )
+{
+ maRect.Move( nHorzMove, nVertMove );
+}
+
+void MetaTextRectAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScaleRect( maRect, fScaleX, fScaleY );
+}
+
+void MetaTextRectAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 2);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writeRectangle(maRect);
+ rOStm.WriteUniOrByteString( maStr, pData->meActualCharSet );
+ rOStm.WriteUInt16( static_cast<sal_uInt16>(mnStyle) );
+
+ write_uInt16_lenPrefixed_uInt16s_FromOUString(rOStm, maStr); // version 2
+}
+
+void MetaTextRectAction::Read( SvStream& rIStm, ImplMetaReadData* pData )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readRectangle(maRect);
+ maStr = rIStm.ReadUniOrByteString(pData->meActualCharSet);
+ sal_uInt16 nTmp;
+ rIStm .ReadUInt16( nTmp );
+ mnStyle = static_cast<DrawTextFlags>(nTmp);
+
+ if ( aCompat.GetVersion() >= 2 ) // Version 2
+ maStr = read_uInt16_lenPrefixed_uInt16s_ToOUString(rIStm);
+}
+
+MetaTextLineAction::MetaTextLineAction() :
+ MetaAction ( MetaActionType::TEXTLINE ),
+ mnWidth ( 0 ),
+ meStrikeout ( STRIKEOUT_NONE ),
+ meUnderline ( LINESTYLE_NONE ),
+ meOverline ( LINESTYLE_NONE )
+{}
+
+MetaTextLineAction::~MetaTextLineAction()
+{}
+
+MetaTextLineAction::MetaTextLineAction( const Point& rPos, long nWidth,
+ FontStrikeout eStrikeout,
+ FontLineStyle eUnderline,
+ FontLineStyle eOverline ) :
+ MetaAction ( MetaActionType::TEXTLINE ),
+ maPos ( rPos ),
+ mnWidth ( nWidth ),
+ meStrikeout ( eStrikeout ),
+ meUnderline ( eUnderline ),
+ meOverline ( eOverline )
+{}
+
+void MetaTextLineAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawTextLine( maPos, mnWidth, meStrikeout, meUnderline, meOverline );
+}
+
+rtl::Reference<MetaAction> MetaTextLineAction::Clone()
+{
+ return new MetaTextLineAction( *this );
+}
+
+void MetaTextLineAction::Move( long nHorzMove, long nVertMove )
+{
+ maPos.Move( nHorzMove, nVertMove );
+}
+
+void MetaTextLineAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoint( maPos, fScaleX, fScaleY );
+ mnWidth = FRound( mnWidth * fabs(fScaleX) );
+}
+
+void MetaTextLineAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 2);
+
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(maPos);
+
+ rOStm.WriteInt32( mnWidth );
+ rOStm.WriteUInt32( meStrikeout );
+ rOStm.WriteUInt32( meUnderline );
+ // new in version 2
+ rOStm.WriteUInt32( meOverline );
+}
+
+void MetaTextLineAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+
+ sal_Int32 nTempWidth(0);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(maPos);
+ rIStm.ReadInt32(nTempWidth);
+ mnWidth = nTempWidth;
+
+ sal_uInt32 nTempStrikeout(0);
+ rIStm.ReadUInt32( nTempStrikeout );
+ meStrikeout = static_cast<FontStrikeout>(nTempStrikeout);
+
+ sal_uInt32 nTempUnderline(0);
+ rIStm.ReadUInt32( nTempUnderline );
+ meUnderline = static_cast<FontLineStyle>(nTempUnderline);
+
+ if (aCompat.GetVersion() >= 2)
+ {
+ sal_uInt32 nTempOverline(0);
+ rIStm.ReadUInt32(nTempOverline);
+ meOverline = static_cast<FontLineStyle>(nTempOverline);
+ }
+}
+
+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()
+{
+ return new MetaBmpAction( *this );
+}
+
+void MetaBmpAction::Move( long nHorzMove, long nVertMove )
+{
+ maPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaBmpAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoint( maPt, fScaleX, fScaleY );
+}
+
+void MetaBmpAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ if( !!maBmp )
+ {
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ WriteDIB(maBmp, rOStm, false, true);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(maPt);
+ }
+}
+
+void MetaBmpAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadDIB(maBmp, rIStm, true);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(maPt);
+}
+
+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 )
+{}
+
+void MetaBmpScaleAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawBitmap( maPt, maSz, maBmp );
+}
+
+rtl::Reference<MetaAction> MetaBmpScaleAction::Clone()
+{
+ return new MetaBmpScaleAction( *this );
+}
+
+void MetaBmpScaleAction::Move( long nHorzMove, 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();
+}
+
+void MetaBmpScaleAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ if( !!maBmp )
+ {
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ WriteDIB(maBmp, rOStm, false, true);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(maPt);
+ aSerializer.writeSize(maSz);
+
+ }
+}
+
+void MetaBmpScaleAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadDIB(maBmp, rIStm, true);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(maPt);
+ aSerializer.readSize(maSz);
+}
+
+MetaBmpScalePartAction::MetaBmpScalePartAction() :
+ MetaAction(MetaActionType::BMPSCALEPART)
+{}
+
+MetaBmpScalePartAction::~MetaBmpScalePartAction()
+{}
+
+MetaBmpScalePartAction::MetaBmpScalePartAction( const Point& rDstPt, const Size& rDstSz,
+ const Point& rSrcPt, const Size& rSrcSz,
+ const Bitmap& rBmp ) :
+ MetaAction ( MetaActionType::BMPSCALEPART ),
+ maBmp ( rBmp ),
+ maDstPt ( rDstPt ),
+ maDstSz ( rDstSz ),
+ maSrcPt ( rSrcPt ),
+ maSrcSz ( rSrcSz )
+{}
+
+void MetaBmpScalePartAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawBitmap( maDstPt, maDstSz, maSrcPt, maSrcSz, maBmp );
+}
+
+rtl::Reference<MetaAction> MetaBmpScalePartAction::Clone()
+{
+ return new MetaBmpScalePartAction( *this );
+}
+
+void MetaBmpScalePartAction::Move( long nHorzMove, 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();
+}
+
+void MetaBmpScalePartAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ if( !!maBmp )
+ {
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ WriteDIB(maBmp, rOStm, false, true);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(maDstPt);
+ aSerializer.writeSize(maDstSz);
+ aSerializer.writePoint(maSrcPt);
+ aSerializer.writeSize(maSrcSz);
+
+ }
+}
+
+void MetaBmpScalePartAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadDIB(maBmp, rIStm, true);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(maDstPt);
+ aSerializer.readSize(maDstSz);
+ aSerializer.readPoint(maSrcPt);
+ aSerializer.readSize(maSrcSz);
+}
+
+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()
+{
+ return new MetaBmpExAction( *this );
+}
+
+void MetaBmpExAction::Move( long nHorzMove, long nVertMove )
+{
+ maPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaBmpExAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoint( maPt, fScaleX, fScaleY );
+}
+
+void MetaBmpExAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ if( !!maBmpEx.GetBitmap() )
+ {
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ WriteDIBBitmapEx(maBmpEx, rOStm);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(maPt);
+ }
+}
+
+void MetaBmpExAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadDIBBitmapEx(maBmpEx, rIStm);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(maPt);
+}
+
+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 )
+{
+ pOut->DrawBitmapEx( maPt, maSz, maBmpEx );
+}
+
+rtl::Reference<MetaAction> MetaBmpExScaleAction::Clone()
+{
+ return new MetaBmpExScaleAction( *this );
+}
+
+void MetaBmpExScaleAction::Move( long nHorzMove, 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();
+}
+
+void MetaBmpExScaleAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ if( !!maBmpEx.GetBitmap() )
+ {
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ WriteDIBBitmapEx(maBmpEx, rOStm);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(maPt);
+ aSerializer.writeSize(maSz);
+ }
+}
+
+void MetaBmpExScaleAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadDIBBitmapEx(maBmpEx, rIStm);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(maPt);
+ aSerializer.readSize(maSz);
+}
+
+MetaBmpExScalePartAction::MetaBmpExScalePartAction() :
+ MetaAction(MetaActionType::BMPEXSCALEPART)
+{}
+
+MetaBmpExScalePartAction::~MetaBmpExScalePartAction()
+{}
+
+MetaBmpExScalePartAction::MetaBmpExScalePartAction( const Point& rDstPt, const Size& rDstSz,
+ const Point& rSrcPt, const Size& rSrcSz,
+ const BitmapEx& rBmpEx ) :
+ MetaAction ( MetaActionType::BMPEXSCALEPART ),
+ maBmpEx ( rBmpEx ),
+ maDstPt ( rDstPt ),
+ maDstSz ( rDstSz ),
+ maSrcPt ( rSrcPt ),
+ maSrcSz ( rSrcSz )
+{}
+
+void MetaBmpExScalePartAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawBitmapEx( maDstPt, maDstSz, maSrcPt, maSrcSz, maBmpEx );
+}
+
+rtl::Reference<MetaAction> MetaBmpExScalePartAction::Clone()
+{
+ return new MetaBmpExScalePartAction( *this );
+}
+
+void MetaBmpExScalePartAction::Move( long nHorzMove, 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();
+}
+
+void MetaBmpExScalePartAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ if( !!maBmpEx.GetBitmap() )
+ {
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ WriteDIBBitmapEx(maBmpEx, rOStm);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(maDstPt);
+ aSerializer.writeSize(maDstSz);
+ aSerializer.writePoint(maSrcPt);
+ aSerializer.writeSize(maSrcSz);
+ }
+}
+
+void MetaBmpExScalePartAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadDIBBitmapEx(maBmpEx, rIStm);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(maDstPt);
+ aSerializer.readSize(maDstSz);
+ aSerializer.readPoint(maSrcPt);
+ aSerializer.readSize(maSrcSz);
+}
+
+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()
+{
+ return new MetaMaskAction( *this );
+}
+
+void MetaMaskAction::Move( long nHorzMove, long nVertMove )
+{
+ maPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaMaskAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoint( maPt, fScaleX, fScaleY );
+}
+
+void MetaMaskAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ if( !!maBmp )
+ {
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ WriteDIB(maBmp, rOStm, false, true);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(maPt);
+ }
+}
+
+void MetaMaskAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadDIB(maBmp, rIStm, true);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(maPt);
+}
+
+MetaMaskScaleAction::MetaMaskScaleAction() :
+ MetaAction(MetaActionType::MASKSCALE)
+{}
+
+MetaMaskScaleAction::~MetaMaskScaleAction()
+{}
+
+MetaMaskScaleAction::MetaMaskScaleAction( const Point& rPt, const Size& rSz,
+ const Bitmap& rBmp,
+ const Color& rColor ) :
+ MetaAction ( MetaActionType::MASKSCALE ),
+ maBmp ( rBmp ),
+ maColor ( rColor ),
+ maPt ( rPt ),
+ maSz ( rSz )
+{}
+
+void MetaMaskScaleAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawMask( maPt, maSz, maBmp, maColor );
+}
+
+rtl::Reference<MetaAction> MetaMaskScaleAction::Clone()
+{
+ return new MetaMaskScaleAction( *this );
+}
+
+void MetaMaskScaleAction::Move( long nHorzMove, 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();
+}
+
+void MetaMaskScaleAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ if( !!maBmp )
+ {
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ WriteDIB(maBmp, rOStm, false, true);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(maPt);
+ aSerializer.writeSize(maSz);
+ }
+}
+
+void MetaMaskScaleAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadDIB(maBmp, rIStm, true);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(maPt);
+ aSerializer.readSize(maSz);
+}
+
+MetaMaskScalePartAction::MetaMaskScalePartAction() :
+ MetaAction(MetaActionType::MASKSCALEPART)
+{}
+
+MetaMaskScalePartAction::~MetaMaskScalePartAction()
+{}
+
+MetaMaskScalePartAction::MetaMaskScalePartAction( const Point& rDstPt, const Size& rDstSz,
+ const Point& rSrcPt, const Size& rSrcSz,
+ const Bitmap& rBmp,
+ const Color& rColor ) :
+ MetaAction ( MetaActionType::MASKSCALEPART ),
+ maBmp ( rBmp ),
+ maColor ( rColor ),
+ maDstPt ( rDstPt ),
+ maDstSz ( rDstSz ),
+ maSrcPt ( rSrcPt ),
+ maSrcSz ( rSrcSz )
+{}
+
+void MetaMaskScalePartAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawMask( maDstPt, maDstSz, maSrcPt, maSrcSz, maBmp, maColor, MetaActionType::MASKSCALE );
+}
+
+rtl::Reference<MetaAction> MetaMaskScalePartAction::Clone()
+{
+ return new MetaMaskScalePartAction( *this );
+}
+
+void MetaMaskScalePartAction::Move( long nHorzMove, 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();
+}
+
+void MetaMaskScalePartAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ if( !!maBmp )
+ {
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ WriteDIB(maBmp, rOStm, false, true);
+ rOStm.WriteUInt32(maColor.mValue);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(maDstPt);
+ aSerializer.writeSize(maDstSz);
+ aSerializer.writePoint(maSrcPt);
+ aSerializer.writeSize(maSrcSz);
+ }
+}
+
+void MetaMaskScalePartAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadDIB(maBmp, rIStm, true);
+ rIStm.ReadUInt32(maColor.mValue);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(maDstPt);
+ aSerializer.readSize(maDstSz);
+ aSerializer.readPoint(maSrcPt);
+ aSerializer.readSize(maSrcSz);
+}
+
+MetaGradientAction::MetaGradientAction() :
+ MetaAction(MetaActionType::GRADIENT)
+{}
+
+MetaGradientAction::~MetaGradientAction()
+{}
+
+MetaGradientAction::MetaGradientAction( const tools::Rectangle& rRect, const Gradient& rGradient ) :
+ MetaAction ( MetaActionType::GRADIENT ),
+ maRect ( rRect ),
+ maGradient ( rGradient )
+{}
+
+void MetaGradientAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawGradient( maRect, maGradient );
+}
+
+rtl::Reference<MetaAction> MetaGradientAction::Clone()
+{
+ return new MetaGradientAction( *this );
+}
+
+void MetaGradientAction::Move( long nHorzMove, long nVertMove )
+{
+ maRect.Move( nHorzMove, nVertMove );
+}
+
+void MetaGradientAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScaleRect( maRect, fScaleX, fScaleY );
+}
+
+void MetaGradientAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writeRectangle(maRect);
+ aSerializer.writeGradient(maGradient);
+}
+
+void MetaGradientAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readRectangle(maRect);
+ aSerializer.readGradient(maGradient);
+}
+
+MetaGradientExAction::MetaGradientExAction() :
+ MetaAction ( MetaActionType::GRADIENTEX )
+{}
+
+MetaGradientExAction::MetaGradientExAction( const tools::PolyPolygon& rPolyPoly, const Gradient& rGradient ) :
+ MetaAction ( MetaActionType::GRADIENTEX ),
+ maPolyPoly ( rPolyPoly ),
+ maGradient ( rGradient )
+{}
+
+MetaGradientExAction::~MetaGradientExAction()
+{}
+
+void MetaGradientExAction::Execute( OutputDevice* pOut )
+{
+ if( pOut->GetConnectMetaFile() )
+ {
+ pOut->GetConnectMetaFile()->AddAction( this );
+ }
+}
+
+rtl::Reference<MetaAction> MetaGradientExAction::Clone()
+{
+ return new MetaGradientExAction( *this );
+}
+
+void MetaGradientExAction::Move( long nHorzMove, 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 );
+}
+
+void MetaGradientExAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+
+ // #i105373# see comment at MetaTransparentAction::Write
+ tools::PolyPolygon aNoCurvePolyPolygon;
+ maPolyPoly.AdaptiveSubdivide(aNoCurvePolyPolygon);
+
+ WritePolyPolygon( rOStm, aNoCurvePolyPolygon );
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writeGradient(maGradient);
+}
+
+void MetaGradientExAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadPolyPolygon( rIStm, maPolyPoly );
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readGradient(maGradient);
+}
+
+MetaHatchAction::MetaHatchAction() :
+ MetaAction(MetaActionType::HATCH)
+{}
+
+MetaHatchAction::~MetaHatchAction()
+{}
+
+MetaHatchAction::MetaHatchAction( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch ) :
+ MetaAction ( MetaActionType::HATCH ),
+ maPolyPoly ( rPolyPoly ),
+ maHatch ( rHatch )
+{}
+
+void MetaHatchAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawHatch( maPolyPoly, maHatch );
+}
+
+rtl::Reference<MetaAction> MetaHatchAction::Clone()
+{
+ return new MetaHatchAction( *this );
+}
+
+void MetaHatchAction::Move( long nHorzMove, 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 );
+}
+
+void MetaHatchAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+
+ // #i105373# see comment at MetaTransparentAction::Write
+ tools::PolyPolygon aNoCurvePolyPolygon;
+ maPolyPoly.AdaptiveSubdivide(aNoCurvePolyPolygon);
+
+ WritePolyPolygon( rOStm, aNoCurvePolyPolygon );
+ WriteHatch( rOStm, maHatch );
+}
+
+void MetaHatchAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadPolyPolygon( rIStm, maPolyPoly );
+ ReadHatch( rIStm, maHatch );
+}
+
+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()
+{
+ return new MetaWallpaperAction( *this );
+}
+
+void MetaWallpaperAction::Move( long nHorzMove, long nVertMove )
+{
+ maRect.Move( nHorzMove, nVertMove );
+}
+
+void MetaWallpaperAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScaleRect( maRect, fScaleX, fScaleY );
+}
+
+void MetaWallpaperAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+
+ WriteWallpaper( rOStm, maWallpaper );
+}
+
+void MetaWallpaperAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadWallpaper( rIStm, maWallpaper );
+}
+
+MetaClipRegionAction::MetaClipRegionAction() :
+ MetaAction ( MetaActionType::CLIPREGION ),
+ mbClip ( false )
+{}
+
+MetaClipRegionAction::~MetaClipRegionAction()
+{}
+
+MetaClipRegionAction::MetaClipRegionAction( const vcl::Region& rRegion, bool bClip ) :
+ MetaAction ( MetaActionType::CLIPREGION ),
+ maRegion ( rRegion ),
+ mbClip ( bClip )
+{}
+
+void MetaClipRegionAction::Execute( OutputDevice* pOut )
+{
+ if( mbClip )
+ pOut->SetClipRegion( maRegion );
+ else
+ pOut->SetClipRegion();
+}
+
+rtl::Reference<MetaAction> MetaClipRegionAction::Clone()
+{
+ return new MetaClipRegionAction( *this );
+}
+
+void MetaClipRegionAction::Move( long nHorzMove, long nVertMove )
+{
+ maRegion.Move( nHorzMove, nVertMove );
+}
+
+void MetaClipRegionAction::Scale( double fScaleX, double fScaleY )
+{
+ maRegion.Scale( fScaleX, fScaleY );
+}
+
+void MetaClipRegionAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+
+ WriteRegion( rOStm, maRegion );
+ rOStm.WriteBool( mbClip );
+}
+
+void MetaClipRegionAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadRegion( rIStm, maRegion );
+ rIStm.ReadCharAsBool( mbClip );
+}
+
+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()
+{
+ return new MetaISectRectClipRegionAction( *this );
+}
+
+void MetaISectRectClipRegionAction::Move( long nHorzMove, long nVertMove )
+{
+ maRect.Move( nHorzMove, nVertMove );
+}
+
+void MetaISectRectClipRegionAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScaleRect( maRect, fScaleX, fScaleY );
+}
+
+void MetaISectRectClipRegionAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writeRectangle(maRect);
+}
+
+void MetaISectRectClipRegionAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readRectangle(maRect);
+}
+
+MetaISectRegionClipRegionAction::MetaISectRegionClipRegionAction() :
+ MetaAction(MetaActionType::ISECTREGIONCLIPREGION)
+{}
+
+MetaISectRegionClipRegionAction::~MetaISectRegionClipRegionAction()
+{}
+
+MetaISectRegionClipRegionAction::MetaISectRegionClipRegionAction( const vcl::Region& rRegion ) :
+ MetaAction ( MetaActionType::ISECTREGIONCLIPREGION ),
+ maRegion ( rRegion )
+{
+}
+
+void MetaISectRegionClipRegionAction::Execute( OutputDevice* pOut )
+{
+ pOut->IntersectClipRegion( maRegion );
+}
+
+rtl::Reference<MetaAction> MetaISectRegionClipRegionAction::Clone()
+{
+ return new MetaISectRegionClipRegionAction( *this );
+}
+
+void MetaISectRegionClipRegionAction::Move( long nHorzMove, long nVertMove )
+{
+ maRegion.Move( nHorzMove, nVertMove );
+}
+
+void MetaISectRegionClipRegionAction::Scale( double fScaleX, double fScaleY )
+{
+ maRegion.Scale( fScaleX, fScaleY );
+}
+
+void MetaISectRegionClipRegionAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ WriteRegion( rOStm, maRegion );
+}
+
+void MetaISectRegionClipRegionAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadRegion( rIStm, maRegion );
+}
+
+MetaMoveClipRegionAction::MetaMoveClipRegionAction() :
+ MetaAction ( MetaActionType::MOVECLIPREGION ),
+ mnHorzMove ( 0 ),
+ mnVertMove ( 0 )
+{}
+
+MetaMoveClipRegionAction::~MetaMoveClipRegionAction()
+{}
+
+MetaMoveClipRegionAction::MetaMoveClipRegionAction( long nHorzMove, long nVertMove ) :
+ MetaAction ( MetaActionType::MOVECLIPREGION ),
+ mnHorzMove ( nHorzMove ),
+ mnVertMove ( nVertMove )
+{}
+
+void MetaMoveClipRegionAction::Execute( OutputDevice* pOut )
+{
+ pOut->MoveClipRegion( mnHorzMove, mnVertMove );
+}
+
+rtl::Reference<MetaAction> MetaMoveClipRegionAction::Clone()
+{
+ return new MetaMoveClipRegionAction( *this );
+}
+
+void MetaMoveClipRegionAction::Scale( double fScaleX, double fScaleY )
+{
+ mnHorzMove = FRound( mnHorzMove * fScaleX );
+ mnVertMove = FRound( mnVertMove * fScaleY );
+}
+
+void MetaMoveClipRegionAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ rOStm.WriteInt32( mnHorzMove ).WriteInt32( mnVertMove );
+}
+
+void MetaMoveClipRegionAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ sal_Int32 nTmpHM(0), nTmpVM(0);
+ rIStm.ReadInt32( nTmpHM ).ReadInt32( nTmpVM );
+ mnHorzMove = nTmpHM;
+ mnVertMove = nTmpVM;
+}
+
+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()
+{
+ return new MetaLineColorAction( *this );
+}
+
+void MetaLineColorAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ rOStm.WriteUInt32(maColor.mValue);
+ rOStm.WriteBool( mbSet );
+}
+
+void MetaLineColorAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ rIStm.ReadUInt32(maColor.mValue);
+ rIStm.ReadCharAsBool( mbSet );
+}
+
+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()
+{
+ return new MetaFillColorAction( *this );
+}
+
+void MetaFillColorAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ rOStm.WriteUInt32(maColor.mValue);
+ rOStm.WriteBool( mbSet );
+}
+
+void MetaFillColorAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ rIStm.ReadUInt32(maColor.mValue);
+ rIStm.ReadCharAsBool( mbSet );
+}
+
+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()
+{
+ return new MetaTextColorAction( *this );
+}
+
+void MetaTextColorAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ rOStm.WriteUInt32(maColor.mValue);
+}
+
+void MetaTextColorAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ rIStm.ReadUInt32(maColor.mValue);
+}
+
+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()
+{
+ return new MetaTextFillColorAction( *this );
+}
+
+void MetaTextFillColorAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ rOStm.WriteUInt32(maColor.mValue);
+ rOStm.WriteBool( mbSet );
+}
+
+void MetaTextFillColorAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ rIStm.ReadUInt32(maColor.mValue);
+ rIStm.ReadCharAsBool( mbSet );
+}
+
+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()
+{
+ return new MetaTextLineColorAction( *this );
+}
+
+void MetaTextLineColorAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ rOStm.WriteUInt32(maColor.mValue);
+ rOStm.WriteBool( mbSet );
+}
+
+void MetaTextLineColorAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ rIStm.ReadUInt32(maColor.mValue);
+ rIStm.ReadCharAsBool( mbSet );
+}
+
+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()
+{
+ return new MetaOverlineColorAction( *this );
+}
+
+void MetaOverlineColorAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ rOStm.WriteUInt32(maColor.mValue);
+ rOStm.WriteBool( mbSet );
+}
+
+void MetaOverlineColorAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ rIStm.ReadUInt32(maColor.mValue);
+ rIStm.ReadCharAsBool( mbSet );
+}
+
+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()
+{
+ return new MetaTextAlignAction( *this );
+}
+
+void MetaTextAlignAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ rOStm.WriteUInt16( maAlign );
+}
+
+void MetaTextAlignAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ sal_uInt16 nTmp16(0);
+
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ rIStm.ReadUInt16( nTmp16 ); maAlign = static_cast<TextAlign>(nTmp16);
+}
+
+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()
+{
+ return new MetaMapModeAction( *this );
+}
+
+void MetaMapModeAction::Scale( double fScaleX, double fScaleY )
+{
+ Point aPoint( maMapMode.GetOrigin() );
+
+ ImplScalePoint( aPoint, fScaleX, fScaleY );
+ maMapMode.SetOrigin( aPoint );
+}
+
+void MetaMapModeAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ WriteMapMode( rOStm, maMapMode );
+}
+
+void MetaMapModeAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadMapMode( rIStm, maMapMode );
+}
+
+MetaFontAction::MetaFontAction() :
+ MetaAction(MetaActionType::FONT)
+{}
+
+MetaFontAction::~MetaFontAction()
+{}
+
+MetaFontAction::MetaFontAction( const vcl::Font& rFont ) :
+ MetaAction ( MetaActionType::FONT ),
+ maFont ( rFont )
+{
+ // #96876: because RTL_TEXTENCODING_SYMBOL is often set at the StarSymbol font,
+ // we change the textencoding to RTL_TEXTENCODING_UNICODE here, which seems
+ // to be the right way; changing the textencoding at other sources
+ // is too dangerous at the moment
+ if ( IsStarSymbol( maFont.GetFamilyName() )
+ && ( maFont.GetCharSet() != RTL_TEXTENCODING_UNICODE ) )
+ {
+ maFont.SetCharSet( RTL_TEXTENCODING_UNICODE );
+ }
+}
+
+void MetaFontAction::Execute( OutputDevice* pOut )
+{
+ pOut->SetFont( maFont );
+}
+
+rtl::Reference<MetaAction> MetaFontAction::Clone()
+{
+ 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 );
+}
+
+void MetaFontAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ WriteFont( rOStm, maFont );
+ pData->meActualCharSet = maFont.GetCharSet();
+ if ( pData->meActualCharSet == RTL_TEXTENCODING_DONTKNOW )
+ pData->meActualCharSet = osl_getThreadTextEncoding();
+}
+
+void MetaFontAction::Read( SvStream& rIStm, ImplMetaReadData* pData )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadFont( rIStm, maFont );
+ pData->meActualCharSet = maFont.GetCharSet();
+ if ( pData->meActualCharSet == RTL_TEXTENCODING_DONTKNOW )
+ pData->meActualCharSet = osl_getThreadTextEncoding();
+}
+
+MetaPushAction::MetaPushAction() :
+ MetaAction ( MetaActionType::PUSH ),
+ mnFlags ( PushFlags::NONE )
+{}
+
+MetaPushAction::~MetaPushAction()
+{}
+
+MetaPushAction::MetaPushAction( PushFlags nFlags ) :
+ MetaAction ( MetaActionType::PUSH ),
+ mnFlags ( nFlags )
+{}
+
+void MetaPushAction::Execute( OutputDevice* pOut )
+{
+ pOut->Push( mnFlags );
+}
+
+rtl::Reference<MetaAction> MetaPushAction::Clone()
+{
+ return new MetaPushAction( *this );
+}
+
+void MetaPushAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ rOStm.WriteUInt16( static_cast<sal_uInt16>(mnFlags) );
+}
+
+void MetaPushAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ sal_uInt16 tmp;
+ rIStm.ReadUInt16( tmp );
+ mnFlags = static_cast<PushFlags>(tmp);
+}
+
+MetaPopAction::MetaPopAction() :
+ MetaAction(MetaActionType::POP)
+{}
+
+MetaPopAction::~MetaPopAction()
+{}
+
+void MetaPopAction::Execute( OutputDevice* pOut )
+{
+ pOut->Pop();
+}
+
+rtl::Reference<MetaAction> MetaPopAction::Clone()
+{
+ return new MetaPopAction( *this );
+}
+
+void MetaPopAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+}
+
+void MetaPopAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+}
+
+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()
+{
+ return new MetaRasterOpAction( *this );
+}
+
+void MetaRasterOpAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ rOStm.WriteUInt16( static_cast<sal_uInt16>(meRasterOp) );
+}
+
+void MetaRasterOpAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ sal_uInt16 nTmp16(0);
+
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ rIStm.ReadUInt16( nTmp16 ); meRasterOp = static_cast<RasterOp>(nTmp16);
+}
+
+MetaTransparentAction::MetaTransparentAction() :
+ MetaAction ( MetaActionType::Transparent ),
+ mnTransPercent ( 0 )
+{}
+
+MetaTransparentAction::~MetaTransparentAction()
+{}
+
+MetaTransparentAction::MetaTransparentAction( const tools::PolyPolygon& rPolyPoly, sal_uInt16 nTransPercent ) :
+ MetaAction ( MetaActionType::Transparent ),
+ maPolyPoly ( rPolyPoly ),
+ mnTransPercent ( nTransPercent )
+{}
+
+void MetaTransparentAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawTransparent( maPolyPoly, mnTransPercent );
+}
+
+rtl::Reference<MetaAction> MetaTransparentAction::Clone()
+{
+ return new MetaTransparentAction( *this );
+}
+
+void MetaTransparentAction::Move( long nHorzMove, 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 );
+}
+
+void MetaTransparentAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 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;
+ maPolyPoly.AdaptiveSubdivide(aNoCurvePolyPolygon);
+
+ WritePolyPolygon( rOStm, aNoCurvePolyPolygon );
+ rOStm.WriteUInt16( mnTransPercent );
+}
+
+void MetaTransparentAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadPolyPolygon( rIStm, maPolyPoly );
+ rIStm.ReadUInt16( mnTransPercent );
+}
+
+MetaFloatTransparentAction::MetaFloatTransparentAction() :
+ MetaAction(MetaActionType::FLOATTRANSPARENT)
+{}
+
+MetaFloatTransparentAction::~MetaFloatTransparentAction()
+{}
+
+MetaFloatTransparentAction::MetaFloatTransparentAction( const GDIMetaFile& rMtf, const Point& rPos,
+ const Size& rSize, const Gradient& rGradient ) :
+ MetaAction ( MetaActionType::FLOATTRANSPARENT ),
+ maMtf ( rMtf ),
+ maPoint ( rPos ),
+ maSize ( rSize ),
+ maGradient ( rGradient )
+{}
+
+void MetaFloatTransparentAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawTransparent( maMtf, maPoint, maSize, maGradient );
+}
+
+rtl::Reference<MetaAction> MetaFloatTransparentAction::Clone()
+{
+ return new MetaFloatTransparentAction( *this );
+}
+
+void MetaFloatTransparentAction::Move( long nHorzMove, 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::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+
+ maMtf.Write( rOStm );
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(maPoint);
+ aSerializer.writeSize(maSize);
+ aSerializer.writeGradient(maGradient);
+}
+
+void MetaFloatTransparentAction::Read(SvStream& rIStm, ImplMetaReadData* pData)
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadGDIMetaFile(rIStm, maMtf, pData);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(maPoint);
+ aSerializer.readSize(maSize);
+ aSerializer.readGradient(maGradient);
+}
+
+MetaEPSAction::MetaEPSAction() :
+ MetaAction(MetaActionType::EPS)
+{}
+
+MetaEPSAction::~MetaEPSAction()
+{}
+
+MetaEPSAction::MetaEPSAction( const Point& rPoint, const Size& rSize,
+ const GfxLink& rGfxLink, const GDIMetaFile& rSubst ) :
+ MetaAction ( MetaActionType::EPS ),
+ maGfxLink ( rGfxLink ),
+ maSubst ( rSubst ),
+ maPoint ( rPoint ),
+ maSize ( rSize )
+{}
+
+void MetaEPSAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawEPS( maPoint, maSize, maGfxLink, &maSubst );
+}
+
+rtl::Reference<MetaAction> MetaEPSAction::Clone()
+{
+ return new MetaEPSAction( *this );
+}
+
+void MetaEPSAction::Move( long nHorzMove, 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();
+}
+
+void MetaEPSAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writeGfxLink(maGfxLink);
+ aSerializer.writePoint(maPoint);
+ aSerializer.writeSize(maSize);
+ maSubst.Write( rOStm );
+}
+
+void MetaEPSAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readGfxLink(maGfxLink);
+ aSerializer.readPoint(maPoint);
+ aSerializer.readSize(maSize);
+ ReadGDIMetaFile( rIStm, maSubst );
+}
+
+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()
+{
+ return new MetaRefPointAction( *this );
+}
+
+void MetaRefPointAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(maRefPoint);
+ rOStm.WriteBool( mbSet );
+}
+
+void MetaRefPointAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(maRefPoint);
+ rIStm.ReadCharAsBool( mbSet );
+}
+
+MetaCommentAction::MetaCommentAction() :
+ MetaAction ( MetaActionType::COMMENT ),
+ mnValue ( 0 )
+{
+ ImplInitDynamicData( nullptr, 0UL );
+}
+
+MetaCommentAction::MetaCommentAction( const MetaCommentAction& rAct ) :
+ MetaAction ( MetaActionType::COMMENT ),
+ maComment ( rAct.maComment ),
+ mnValue ( rAct.mnValue )
+{
+ ImplInitDynamicData( rAct.mpData.get(), rAct.mnDataSize );
+}
+
+MetaCommentAction::MetaCommentAction( const OString& rComment, sal_Int32 nValue, const sal_uInt8* pData, sal_uInt32 nDataSize ) :
+ MetaAction ( MetaActionType::COMMENT ),
+ maComment ( rComment ),
+ mnValue ( nValue )
+{
+ ImplInitDynamicData( pData, nDataSize );
+}
+
+MetaCommentAction::~MetaCommentAction()
+{
+}
+
+void MetaCommentAction::ImplInitDynamicData( const sal_uInt8* pData, sal_uInt32 nDataSize )
+{
+ if ( nDataSize && pData )
+ {
+ mnDataSize = nDataSize;
+ mpData.reset( new sal_uInt8[ mnDataSize ] );
+ memcpy( mpData.get(), pData, mnDataSize );
+ }
+ else
+ {
+ mnDataSize = 0;
+ mpData = nullptr;
+ }
+}
+
+void MetaCommentAction::Execute( OutputDevice* pOut )
+{
+ if ( pOut->GetConnectMetaFile() )
+ {
+ pOut->GetConnectMetaFile()->AddAction( this );
+ }
+}
+
+rtl::Reference<MetaAction> MetaCommentAction::Clone()
+{
+ return new MetaCommentAction( *this );
+}
+
+void MetaCommentAction::Move( long nXMove, long nYMove )
+{
+ if ( nXMove || nYMove )
+ {
+ if ( mnDataSize && mpData )
+ {
+ 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 );
+
+ 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 ) )
+ {
+ if ( mnDataSize && mpData )
+ {
+ 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() );
+ }
+ }
+ }
+}
+
+void MetaCommentAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ write_uInt16_lenPrefixed_uInt8s_FromOString(rOStm, maComment);
+ rOStm.WriteInt32( mnValue ).WriteUInt32( mnDataSize );
+
+ if ( mnDataSize )
+ rOStm.WriteBytes( mpData.get(), mnDataSize );
+}
+
+void MetaCommentAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ maComment = read_uInt16_lenPrefixed_uInt8s_ToOString(rIStm);
+ rIStm.ReadInt32( mnValue ).ReadUInt32( mnDataSize );
+
+ if (mnDataSize > rIStm.remainingSize())
+ {
+ SAL_WARN("vcl.gdi", "Parsing error: " << rIStm.remainingSize() <<
+ " available data, but " << mnDataSize << " claimed, truncating");
+ mnDataSize = rIStm.remainingSize();
+ }
+
+ SAL_INFO("vcl.gdi", "MetaCommentAction::Read " << maComment);
+
+ mpData.reset();
+
+ if( mnDataSize )
+ {
+ mpData.reset(new sal_uInt8[ mnDataSize ]);
+ rIStm.ReadBytes(mpData.get(), mnDataSize);
+ }
+}
+
+MetaLayoutModeAction::MetaLayoutModeAction() :
+ MetaAction ( MetaActionType::LAYOUTMODE ),
+ mnLayoutMode( ComplexTextLayoutFlags::Default )
+{}
+
+MetaLayoutModeAction::~MetaLayoutModeAction()
+{}
+
+MetaLayoutModeAction::MetaLayoutModeAction( ComplexTextLayoutFlags nLayoutMode ) :
+ MetaAction ( MetaActionType::LAYOUTMODE ),
+ mnLayoutMode( nLayoutMode )
+{}
+
+void MetaLayoutModeAction::Execute( OutputDevice* pOut )
+{
+ pOut->SetLayoutMode( mnLayoutMode );
+}
+
+rtl::Reference<MetaAction> MetaLayoutModeAction::Clone()
+{
+ return new MetaLayoutModeAction( *this );
+}
+
+void MetaLayoutModeAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ rOStm.WriteUInt32( static_cast<sal_uInt32>(mnLayoutMode) );
+}
+
+void MetaLayoutModeAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ sal_uInt32 tmp;
+ rIStm.ReadUInt32( tmp );
+ mnLayoutMode = static_cast<ComplexTextLayoutFlags>(tmp);
+}
+
+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()
+{
+ return new MetaTextLanguageAction( *this );
+}
+
+void MetaTextLanguageAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ rOStm.WriteUInt16( static_cast<sal_uInt16>(meTextLanguage) );
+}
+
+void MetaTextLanguageAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ sal_uInt16 nTmp = 0;
+ rIStm.ReadUInt16( nTmp );
+ meTextLanguage = static_cast<LanguageType>(nTmp);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/mtfxmldump.cxx b/vcl/source/gdi/mtfxmldump.cxx
new file mode 100644
index 000000000..461321c1c
--- /dev/null
+++ b/vcl/source/gdi/mtfxmldump.cxx
@@ -0,0 +1,1274 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <vcl/mtfxmldump.hxx>
+#include <tools/XmlWriter.hxx>
+#include <tools/fract.hxx>
+
+#include <vcl/metaact.hxx>
+#include <vcl/outdev.hxx>
+#include <rtl/string.hxx>
+#include <rtl/ustrbuf.hxx>
+
+#include <sstream>
+
+namespace
+{
+
+OUString collectPushFlags(PushFlags nFlags)
+{
+ if ((nFlags & PushFlags::ALL) == PushFlags::ALL)
+ return "PushAll";
+ else if ((nFlags & PUSH_ALLFONT) == PUSH_ALLFONT)
+ return "PushAllFont";
+ else if ((nFlags & PUSH_ALLTEXT) == PUSH_ALLTEXT)
+ return "PushAllText";
+
+ std::vector<OUString> aStrings;
+
+ if (nFlags & PushFlags::LINECOLOR)
+ aStrings.emplace_back("PushLineColor");
+ if (nFlags & PushFlags::FILLCOLOR)
+ aStrings.emplace_back("PushFillColor");
+ if (nFlags & PushFlags::FONT)
+ aStrings.emplace_back("PushFont");
+ if (nFlags & PushFlags::TEXTCOLOR)
+ aStrings.emplace_back("PushTextColor");
+ if (nFlags & PushFlags::MAPMODE)
+ aStrings.emplace_back("PushMapMode");
+ if (nFlags & PushFlags::CLIPREGION)
+ aStrings.emplace_back("PushClipRegion");
+ if (nFlags & PushFlags::RASTEROP)
+ aStrings.emplace_back("PushRasterOp");
+ if (nFlags & PushFlags::TEXTFILLCOLOR)
+ aStrings.emplace_back("PushTextFillColor");
+ if (nFlags & PushFlags::TEXTALIGN)
+ aStrings.emplace_back("PushTextAlign");
+ if (nFlags & PushFlags::REFPOINT)
+ aStrings.emplace_back("PushRefPoint");
+ if (nFlags & PushFlags::TEXTLINECOLOR)
+ aStrings.emplace_back("PushTextLineColor");
+ if (nFlags & PushFlags::TEXTLAYOUTMODE)
+ aStrings.emplace_back("PushTextLayoutMode");
+ if (nFlags & PushFlags::TEXTLANGUAGE)
+ aStrings.emplace_back("PushTextLanguage");
+ if (nFlags & PushFlags::OVERLINECOLOR)
+ aStrings.emplace_back("PushOverlineColor");
+
+ OUString aString;
+
+ if (aStrings.empty())
+ return aString;
+
+ aString = aStrings[0];
+ for (size_t i = 1; i < aStrings.size(); ++i)
+ {
+ aString += ", " + aStrings[i];
+ }
+ return aString;
+}
+
+OUString convertDrawTextFlagsToString(DrawTextFlags eDrawTextFlags)
+{
+ std::vector<OUString> aStrings;
+ if (eDrawTextFlags & DrawTextFlags::Disable)
+ aStrings.emplace_back("Disable");
+ if (eDrawTextFlags & DrawTextFlags::Mnemonic)
+ aStrings.emplace_back("Mnemonic");
+ if (eDrawTextFlags & DrawTextFlags::Mono)
+ aStrings.emplace_back("Mono");
+ if (eDrawTextFlags & DrawTextFlags::Clip)
+ aStrings.emplace_back("Clip");
+ if (eDrawTextFlags & DrawTextFlags::Left)
+ aStrings.emplace_back("Left");
+ if (eDrawTextFlags & DrawTextFlags::Center)
+ aStrings.emplace_back("Center");
+ if (eDrawTextFlags & DrawTextFlags::Right)
+ aStrings.emplace_back("Right");
+ if (eDrawTextFlags & DrawTextFlags::Top)
+ aStrings.emplace_back("Top");
+ if (eDrawTextFlags & DrawTextFlags::VCenter)
+ aStrings.emplace_back("VCenter");
+ if (eDrawTextFlags & DrawTextFlags::Bottom)
+ aStrings.emplace_back("Bottom");
+ if (eDrawTextFlags & DrawTextFlags::EndEllipsis)
+ aStrings.emplace_back("EndEllipsis");
+ if (eDrawTextFlags & DrawTextFlags::PathEllipsis)
+ aStrings.emplace_back("PathEllipsis");
+ if (eDrawTextFlags & DrawTextFlags::MultiLine)
+ aStrings.emplace_back("MultiLine");
+ if (eDrawTextFlags & DrawTextFlags::WordBreak)
+ aStrings.emplace_back("WordBreak");
+ if (eDrawTextFlags & DrawTextFlags::NewsEllipsis)
+ aStrings.emplace_back("NewsEllipsis");
+ if (eDrawTextFlags & DrawTextFlags::WordBreakHyphenation)
+ aStrings.emplace_back("WordBreakHyphenation");
+ if (eDrawTextFlags & DrawTextFlags::CenterEllipsis)
+ aStrings.emplace_back("CenterEllipsis");
+ if (eDrawTextFlags & DrawTextFlags::HideMnemonic)
+ aStrings.emplace_back("HideMnemonic");
+
+ OUString aString;
+
+ if (aStrings.empty())
+ return "None";
+
+ aString = aStrings[0];
+ for (size_t i = 1; i < aStrings.size(); ++i)
+ {
+ aString += " " + aStrings[i];
+ }
+ return aString;
+};
+
+OUString convertRopToString(RasterOp eRop)
+{
+ switch (eRop)
+ {
+ case RasterOp::OverPaint: return "overpaint";
+ case RasterOp::Xor: return "xor";
+ case RasterOp::N0: return "0";
+ case RasterOp::N1: return "1";
+ case RasterOp::Invert: return "invert";
+ }
+ return OUString();
+}
+
+OUString convertTextAlignToString(TextAlign eAlign)
+{
+ switch (eAlign)
+ {
+ case ALIGN_BASELINE: return "baseline";
+ case ALIGN_BOTTOM: return "bottom";
+ case ALIGN_TOP: return "top";
+ case TextAlign_FORCE_EQUAL_SIZE: return "equalsize";
+ }
+ return OUString();
+}
+
+OUString convertColorToString(Color aColor)
+{
+ OUString aRGBString = aColor.AsRGBHexString();
+ return "#" + aRGBString;
+}
+
+OUString convertLineStyleToString(LineStyle eAlign)
+{
+ switch (eAlign)
+ {
+ case LineStyle::NONE: return "none";
+ case LineStyle::Solid: return "solid";
+ case LineStyle::Dash: return "dash";
+ default: break;
+ }
+ return OUString();
+}
+
+OUString convertLineJoinToString(basegfx::B2DLineJoin eJoin)
+{
+ switch (eJoin)
+ {
+ default:
+ case basegfx::B2DLineJoin::NONE: return "none";
+ case basegfx::B2DLineJoin::Bevel: return "bevel";
+ case basegfx::B2DLineJoin::Miter: return "miter";
+ case basegfx::B2DLineJoin::Round: return "round";
+ }
+}
+
+OUString convertLineCapToString(css::drawing::LineCap eCap)
+{
+ switch (eCap)
+ {
+ default:
+ case css::drawing::LineCap_BUTT: return "butt";
+ case css::drawing::LineCap_ROUND: return "round";
+ case css::drawing::LineCap_SQUARE: return "square";
+ }
+}
+
+OUString convertPolygonFlags(PolyFlags eFlags)
+{
+ switch (eFlags)
+ {
+ default:
+ case PolyFlags::Normal: return "normal";
+ case PolyFlags::Control: return "control";
+ case PolyFlags::Smooth: return "smooth";
+ case PolyFlags::Symmetric: return "symmetric";
+ }
+}
+
+OUString convertFontWeigthToString(FontWeight eFontWeight)
+{
+ switch (eFontWeight)
+ {
+ case WEIGHT_DONTKNOW: return "unknown";
+ case WEIGHT_THIN: return "thin";
+ case WEIGHT_ULTRALIGHT: return "ultralight";
+ case WEIGHT_LIGHT: return "light";
+ case WEIGHT_SEMILIGHT: return "semilight";
+ case WEIGHT_NORMAL: return "normal";
+ case WEIGHT_MEDIUM: return "medium";
+ case WEIGHT_SEMIBOLD: return "semibold";
+ case WEIGHT_BOLD: return "bold";
+ case WEIGHT_ULTRABOLD: return "ultrabold";
+ case WEIGHT_BLACK: return "black";
+ case FontWeight_FORCE_EQUAL_SIZE: return "equalsize";
+ }
+ return OUString();
+}
+
+OUString convertFontStrikeoutToString(FontStrikeout eFontStrikeout)
+{
+ switch (eFontStrikeout)
+ {
+ case STRIKEOUT_NONE: return "none";
+ case STRIKEOUT_SINGLE: return "single";
+ case STRIKEOUT_DOUBLE: return "double";
+ case STRIKEOUT_DONTKNOW: return "dontknow";
+ case STRIKEOUT_BOLD: return "bold";
+ case STRIKEOUT_SLASH: return "slash";
+ case STRIKEOUT_X: return "x";
+ case FontStrikeout_FORCE_EQUAL_SIZE: return "equalsize";
+ }
+ return OUString();
+}
+
+OUString convertFontLineStyleToString(FontLineStyle eFontLineStyle)
+{
+ switch (eFontLineStyle)
+ {
+ case LINESTYLE_NONE: return "none";
+ case LINESTYLE_SINGLE: return "single";
+ case LINESTYLE_DOUBLE: return "double";
+ case LINESTYLE_DOTTED: return "dotted";
+ case LINESTYLE_DONTKNOW: return "dontknow";
+ case LINESTYLE_DASH: return "dash";
+ case LINESTYLE_LONGDASH: return "longdash";
+ case LINESTYLE_DASHDOT: return "dashdot";
+ case LINESTYLE_DASHDOTDOT: return "dashdotdot";
+ case LINESTYLE_SMALLWAVE: return "smallwave";
+ case LINESTYLE_WAVE: return "wave";
+ case LINESTYLE_DOUBLEWAVE: return "doublewave";
+ case LINESTYLE_BOLD: return "bold";
+ case LINESTYLE_BOLDDOTTED: return "bolddotted";
+ case LINESTYLE_BOLDDASH: return "bolddash";
+ case LINESTYLE_BOLDLONGDASH: return "boldlongdash";
+ case LINESTYLE_BOLDDASHDOT: return "bolddashdot";
+ case LINESTYLE_BOLDDASHDOTDOT: return "bolddashdotdot";
+ case LINESTYLE_BOLDWAVE: return "boldwave";
+ case FontLineStyle_FORCE_EQUAL_SIZE: return "equalsize";
+ }
+ return OUString();
+}
+
+OString convertLineStyleToString(const MetaActionType nActionType)
+{
+ switch (nActionType)
+ {
+ case MetaActionType::NONE: return "null";
+ case MetaActionType::PIXEL: return "pixel";
+ case MetaActionType::POINT: return "point";
+ case MetaActionType::LINE: return "line";
+ case MetaActionType::RECT: return "rect";
+ case MetaActionType::ROUNDRECT: return "roundrect";
+ case MetaActionType::ELLIPSE: return "ellipse";
+ case MetaActionType::ARC: return "arc";
+ case MetaActionType::PIE: return "pie";
+ case MetaActionType::CHORD: return "chord";
+ case MetaActionType::POLYLINE: return "polyline";
+ case MetaActionType::POLYGON: return "polygon";
+ case MetaActionType::POLYPOLYGON: return "polypolygon";
+ case MetaActionType::TEXT: return "text";
+ case MetaActionType::TEXTARRAY: return "textarray";
+ case MetaActionType::STRETCHTEXT: return "stretchtext";
+ case MetaActionType::TEXTRECT: return "textrect";
+ case MetaActionType::TEXTLINE: return "textline";
+ case MetaActionType::BMP: return "bmp";
+ case MetaActionType::BMPSCALE: return "bmpscale";
+ case MetaActionType::BMPSCALEPART: return "bmpscalepart";
+ case MetaActionType::BMPEX: return "bmpex";
+ case MetaActionType::BMPEXSCALE: return "bmpexscale";
+ case MetaActionType::BMPEXSCALEPART: return "bmpexscalepart";
+ case MetaActionType::MASK: return "mask";
+ case MetaActionType::MASKSCALE: return "maskscale";
+ case MetaActionType::MASKSCALEPART: return "maskscalepart";
+ case MetaActionType::GRADIENT: return "gradient";
+ case MetaActionType::GRADIENTEX: return "gradientex";
+ case MetaActionType::HATCH: return "hatch";
+ case MetaActionType::WALLPAPER: return "wallpaper";
+ case MetaActionType::CLIPREGION: return "clipregion";
+ case MetaActionType::ISECTRECTCLIPREGION: return "sectrectclipregion";
+ case MetaActionType::ISECTREGIONCLIPREGION: return "sectregionclipregion";
+ case MetaActionType::MOVECLIPREGION: return "moveclipregion";
+ case MetaActionType::LINECOLOR: return "linecolor";
+ case MetaActionType::FILLCOLOR: return "fillcolor";
+ case MetaActionType::TEXTCOLOR: return "textcolor";
+ case MetaActionType::TEXTFILLCOLOR: return "textfillcolor";
+ case MetaActionType::TEXTLINECOLOR: return "textlinecolor";
+ case MetaActionType::OVERLINECOLOR: return "overlinecolor";
+ case MetaActionType::TEXTALIGN: return "textalign";
+ case MetaActionType::MAPMODE: return "mapmode";
+ case MetaActionType::FONT: return "font";
+ case MetaActionType::PUSH: return "push";
+ case MetaActionType::POP: return "pop";
+ case MetaActionType::RASTEROP: return "rasterop";
+ case MetaActionType::Transparent: return "transparent";
+ case MetaActionType::FLOATTRANSPARENT: return "floattransparent";
+ case MetaActionType::EPS: return "eps";
+ case MetaActionType::REFPOINT: return "refpoint";
+ case MetaActionType::COMMENT: return "comment";
+ case MetaActionType::LAYOUTMODE: return "layoutmode";
+ case MetaActionType::TEXTLANGUAGE: return "textlanguage";
+ }
+ return "";
+}
+
+OUString convertBitmapExTransparentType(TransparentType eType)
+{
+ switch (eType)
+ {
+ default:
+ case TransparentType::NONE: return "none";
+ case TransparentType::Bitmap: return "bitmap";
+ case TransparentType::Color: return "color";
+ }
+}
+
+OUString convertMapUnitToString(MapUnit eUnit)
+{
+ switch (eUnit)
+ {
+ default:
+ case MapUnit::LASTENUMDUMMY: return "LASTENUMDUMMY";
+ case MapUnit::Map1000thInch: return "Map1000thInch";
+ case MapUnit::Map100thInch: return "Map100thInch";
+ case MapUnit::Map100thMM: return "Map100thMM";
+ case MapUnit::Map10thInch: return "Map10thInch";
+ case MapUnit::Map10thMM: return "Map10thMM";
+ case MapUnit::MapAppFont: return "MapAppFont";
+ case MapUnit::MapCM: return "MapCM";
+ case MapUnit::MapInch: return "MapInch";
+ case MapUnit::MapMM: return "MapMM";
+ case MapUnit::MapPixel: return "MapPixel";
+ case MapUnit::MapPoint: return "MapPoint";
+ case MapUnit::MapRelative: return "MapRelative";
+ case MapUnit::MapSysFont: return "MapSysFont";
+ case MapUnit::MapTwip: return "MapTwip";
+ }
+}
+
+OUString convertFractionToString(const Fraction& aFraction)
+{
+ std::stringstream ss;
+
+ ss << aFraction;
+
+ return OUString::createFromAscii(ss.str().c_str());
+}
+
+OUString convertGradientStyle(GradientStyle eStyle)
+{
+ switch (eStyle)
+ {
+ case GradientStyle::Linear: return "Linear";
+ case GradientStyle::Axial: return "Axial";
+ case GradientStyle::Radial: return "Radial";
+ case GradientStyle::Elliptical: return "Elliptical";
+ case GradientStyle::Square: return "Square";
+ case GradientStyle::Rect: return "Rect";
+ case GradientStyle::FORCE_EQUAL_SIZE: return "ForceEqualSize";
+ }
+ return OUString();
+}
+
+OUString convertHatchStyle(HatchStyle eStyle)
+{
+ switch (eStyle)
+ {
+ case HatchStyle::Single: return "Single";
+ case HatchStyle::Double: return "Double";
+ case HatchStyle::Triple: return "Triple";
+ case HatchStyle::FORCE_EQUAL_SIZE: return "ForceEqualSize";
+ }
+ return OUString();
+}
+
+OUString 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 hex32(sal_uInt32 nNumber)
+{
+ std::stringstream ss;
+ ss << std::hex << std::setfill('0') << std::setw(8) << nNumber;
+ return OUString::createFromAscii(ss.str().c_str());
+}
+
+void writePoint(tools::XmlWriter& rWriter, Point const& rPoint)
+{
+ rWriter.attribute("x", rPoint.X());
+ rWriter.attribute("y", rPoint.Y());
+}
+
+void writeStartPoint(tools::XmlWriter& rWriter, Point const& rPoint)
+{
+ rWriter.attribute("startx", rPoint.X());
+ rWriter.attribute("starty", rPoint.Y());
+}
+
+void writeEndPoint(tools::XmlWriter& rWriter, Point const& rPoint)
+{
+ rWriter.attribute("endx", rPoint.X());
+ rWriter.attribute("endy", rPoint.Y());
+}
+
+void writeSize(tools::XmlWriter& rWriter, Size const& rSize)
+{
+ rWriter.attribute("width", rSize.Width());
+ rWriter.attribute("height", rSize.Height());
+}
+
+void writeRectangle(tools::XmlWriter& rWriter, tools::Rectangle const& rRectangle)
+{
+ rWriter.attribute("left", rRectangle.Left());
+ rWriter.attribute("top", rRectangle.Top());
+ if (rRectangle.IsWidthEmpty())
+ rWriter.attribute("right", OString("empty"));
+ else
+ rWriter.attribute("right", rRectangle.Right());
+ if (rRectangle.IsHeightEmpty())
+ rWriter.attribute("bottom", OString("empty"));
+ else
+ rWriter.attribute("bottom", rRectangle.Bottom());
+}
+
+void writeLineInfo(tools::XmlWriter& rWriter, LineInfo const& rLineInfo)
+{
+ rWriter.attribute("style", convertLineStyleToString(rLineInfo.GetStyle()));
+ rWriter.attribute("width", rLineInfo.GetWidth());
+ rWriter.attribute("dashlen", rLineInfo.GetDashLen());
+ rWriter.attribute("dashcount", rLineInfo.GetDashCount());
+ rWriter.attribute("dotlen", rLineInfo.GetDotLen());
+ rWriter.attribute("dotcount", rLineInfo.GetDotCount());
+ rWriter.attribute("distance", rLineInfo.GetDistance());
+ rWriter.attribute("join", convertLineJoinToString(rLineInfo.GetLineJoin()));
+ rWriter.attribute("cap", convertLineCapToString(rLineInfo.GetLineCap()));
+}
+
+void writeGradient(tools::XmlWriter& rWriter, Gradient const& rGradient)
+{
+ rWriter.attribute("style", convertGradientStyle(rGradient.GetStyle()));
+ rWriter.attribute("startcolor", convertColorToString(rGradient.GetStartColor()));
+ rWriter.attribute("endcolor", convertColorToString(rGradient.GetEndColor()));
+ rWriter.attribute("angle", rGradient.GetAngle());
+ rWriter.attribute("border", rGradient.GetBorder());
+ rWriter.attribute("offsetx", rGradient.GetOfsX());
+ rWriter.attribute("offsety", rGradient.GetOfsY());
+ rWriter.attribute("startintensity", rGradient.GetStartIntensity());
+ rWriter.attribute("endintensity", rGradient.GetEndIntensity());
+ rWriter.attribute("steps", rGradient.GetSteps());
+}
+
+} // anonymous namespace
+
+MetafileXmlDump::MetafileXmlDump()
+{
+ maFilter.fill(false);
+}
+
+MetafileXmlDump::~MetafileXmlDump()
+{}
+
+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)
+{
+ 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())
+ {
+ rWriter.startElement("dxarray");
+ OUStringBuffer sDxLengthString;
+ for (sal_Int32 i = 0; i < aLength - aIndex; ++i)
+ {
+ sDxLengthString.append(OUString::number(pMetaTextArrayAction->GetDXArray()[aIndex + i]));
+ sDxLengthString.append(" ");
+ }
+ rWriter.content(sDxLengthString.makeStringAndClear());
+ rWriter.endElement();
+ }
+
+ rWriter.startElement("text");
+ rWriter.content(pMetaTextArrayAction->GetText());
+ rWriter.endElement();
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::STRETCHTEXT:
+ {
+ auto* pMeta = static_cast<MetaStretchTextAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ writePoint(rWriter, pMeta->GetPoint());
+ rWriter.attribute("index", pMeta->GetIndex());
+ rWriter.attribute("length", pMeta->GetLen());
+ rWriter.attribute("width", pMeta->GetWidth());
+
+ rWriter.startElement("textcontent");
+ rWriter.content(pMeta->GetText());
+ rWriter.endElement();
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::TEXTRECT:
+ {
+ auto* pMeta = static_cast<MetaTextRectAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writeRectangle(rWriter, pMeta->GetRect());
+ rWriter.startElement("textcontent");
+ rWriter.content(pMeta->GetText());
+ rWriter.endElement();
+
+ rWriter.startElement("style");
+ rWriter.content(convertDrawTextFlagsToString(pMeta->GetStyle()));
+ rWriter.endElement();
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::BMP:
+ {
+ auto pMeta = static_cast<MetaBmpAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writePoint(rWriter, pMeta->GetPoint());
+ rWriter.attribute("crc", hex32(pMeta->GetBitmap().GetChecksum()));
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::BMPSCALE:
+ {
+ auto pMeta = static_cast<MetaBmpScaleAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writePoint(rWriter, pMeta->GetPoint());
+ writeSize(rWriter, pMeta->GetSize());
+ rWriter.attribute("crc", hex32(pMeta->GetBitmap().GetChecksum()));
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::BMPSCALEPART:
+ {
+ auto pMeta = static_cast<MetaBmpScalePartAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ rWriter.attribute("destx", pMeta->GetDestPoint().X());
+ rWriter.attribute("desty", pMeta->GetDestPoint().Y());
+ rWriter.attribute("destwidth", pMeta->GetDestSize().Width());
+ rWriter.attribute("destheight", pMeta->GetDestSize().Height());
+ rWriter.attribute("srcx", pMeta->GetSrcPoint().X());
+ rWriter.attribute("srcy", pMeta->GetSrcPoint().Y());
+ rWriter.attribute("srcwidth", pMeta->GetSrcSize().Width());
+ rWriter.attribute("srcheight", pMeta->GetSrcSize().Height());
+ rWriter.attribute("crc", hex32(pMeta->GetBitmap().GetChecksum()));
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::BMPEX:
+ {
+ auto pMeta = static_cast<MetaBmpExAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writePoint(rWriter, pMeta->GetPoint());
+ rWriter.attribute("crc", hex32(pMeta->GetBitmapEx().GetBitmap().GetChecksum()));
+ rWriter.attribute("transparenttype", convertBitmapExTransparentType(pMeta->GetBitmapEx().GetTransparentType()));
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ {
+ auto pMeta = static_cast<MetaBmpExScaleAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writePoint(rWriter, pMeta->GetPoint());
+ writeSize(rWriter, pMeta->GetSize());
+ rWriter.attribute("crc", hex32(pMeta->GetBitmapEx().GetBitmap().GetChecksum()));
+ rWriter.attribute("transparenttype", convertBitmapExTransparentType(pMeta->GetBitmapEx().GetTransparentType()));
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ {
+ auto pMeta = static_cast<MetaBmpExScalePartAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ rWriter.attribute("destx", pMeta->GetDestPoint().X());
+ rWriter.attribute("desty", pMeta->GetDestPoint().Y());
+ rWriter.attribute("destwidth", pMeta->GetDestSize().Width());
+ rWriter.attribute("destheight", pMeta->GetDestSize().Height());
+ rWriter.attribute("srcx", pMeta->GetSrcPoint().X());
+ rWriter.attribute("srcy", pMeta->GetSrcPoint().Y());
+ rWriter.attribute("srcwidth", pMeta->GetSrcSize().Width());
+ rWriter.attribute("srcheight", pMeta->GetSrcSize().Height());
+ rWriter.attribute("crc", hex32(pMeta->GetBitmapEx().GetBitmap().GetChecksum()));
+ rWriter.attribute("transparenttype", convertBitmapExTransparentType(pMeta->GetBitmapEx().GetTransparentType()));
+ 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()));
+ 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.GetTransparentType()));
+ rWriter.attribute("bitcount", hex32(rBitmapEx.GetBitmap().GetBitCount()));
+ 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);
+
+ // 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 = pMetaClipRegionAction->GetRegion().GetBoundRect();
+ writeRectangle(rWriter, aRectangle);
+ 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:
+
+ 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", OUString("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());
+ rWriter.attribute("weight", convertFontWeigthToString(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:
+ //case MetaActionType::REFPOINT:
+
+ case MetaActionType::TEXTLINECOLOR:
+ {
+ MetaTextLineColorAction* pMetaTextLineColorAction = static_cast<MetaTextLineColorAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ rWriter.attribute("color", convertColorToString(pMetaTextLineColorAction->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:
+ //case MetaActionType::GRADIENTEX:
+ //case MetaActionType::LAYOUTMODE:
+ //case MetaActionType::TEXTLANGUAGE:
+
+ 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());
+ }
+ if (!pMetaCommentAction->GetComment().isEmpty())
+ {
+ rWriter.startElement("comment");
+ rWriter.content(pMetaCommentAction->GetComment());
+ rWriter.endElement();
+ }
+
+ rWriter.endElement();
+ }
+ break;
+
+ default:
+ {
+ rWriter.startElement(sCurrentElementTag);
+ rWriter.attribute("note", OString("not implemented in xml dump"));
+ rWriter.endElement();
+ }
+ break;
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/oldprintadaptor.cxx b/vcl/source/gdi/oldprintadaptor.cxx
new file mode 100644
index 000000000..05a6f9bbe
--- /dev/null
+++ b/vcl/source/gdi/oldprintadaptor.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 <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
+{
+ Sequence< PropertyValue > aRet( 1 );
+ aRet[0].Name = "PageSize";
+ if( i_nPage < int(mpData->maPages.size() ) )
+ aRet[0].Value <<= mpData->maPages[i_nPage].maPageSize;
+ else
+ {
+ awt::Size aEmpty( 0, 0 );
+ aRet[0].Value <<= aEmpty;
+ }
+ return aRet;
+}
+
+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().get() );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/pdfbuildin_fonts.cxx b/vcl/source/gdi/pdfbuildin_fonts.cxx
new file mode 100644
index 000000000..41c208b93
--- /dev/null
+++ b/vcl/source/gdi/pdfbuildin_fonts.cxx
@@ -0,0 +1,740 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "pdfbuildin_fonts.hxx"
+
+#include <rtl/strbuf.hxx>
+
+using namespace vcl;
+
+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();
+}
+
+FontAttributes BuildinFont::GetFontAttributes() const
+{
+ FontAttributes aDFA;
+ aDFA.SetFamilyName(OUString::createFromAscii(m_pName));
+ aDFA.SetStyleName(OUString::createFromAscii(m_pStyleName));
+ aDFA.SetFamilyType(m_eFamily);
+ aDFA.SetSymbolFlag(m_eCharSet != RTL_TEXTENCODING_MS_1252);
+ aDFA.SetPitch(m_ePitch);
+ aDFA.SetWeight(m_eWeight);
+ aDFA.SetItalic(m_eItalic);
+ aDFA.SetWidthType(m_eWidthType);
+ aDFA.SetQuality(50000);
+ return aDFA;
+}
+
+const BuildinFont BuildinFontFace::m_aBuildinFonts[14]
+ = { { "Courier", // family name
+ "Normal", // style
+ "Courier", // PSName
+ 629,
+ -157, // ascend, descend
+ FAMILY_MODERN, // family style
+ RTL_TEXTENCODING_MS_1252, // charset
+ PITCH_FIXED, // pitch
+ WIDTH_NORMAL, // width type
+ WEIGHT_NORMAL, // weight type
+ ITALIC_NONE, // italic type
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15
+ 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23
+ 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31
+ 600, 600, 600, 600, 600, 600, 600, 600, // 32 - 39
+ 600, 600, 600, 600, 600, 600, 600, 600, // 40 - 47
+ 600, 600, 600, 600, 600, 600, 600, 600, // 48 - 55
+ 600, 600, 600, 600, 600, 600, 600, 600, // 56 - 63
+ 600, 600, 600, 600, 600, 600, 600, 600, // 64 - 71
+ 600, 600, 600, 600, 600, 600, 600, 600, // 72 - 79
+ 600, 600, 600, 600, 600, 600, 600, 600, // 80 - 87
+ 600, 600, 600, 600, 600, 600, 600, 600, // 88 - 95
+ 600, 600, 600, 600, 600, 600, 600, 600, // 96 - 103
+ 600, 600, 600, 600, 600, 600, 600, 600, // 104 - 111
+ 600, 600, 600, 600, 600, 600, 600, 600, // 112 - 119
+ 600, 600, 600, 600, 600, 600, 600, 0, // 120 - 127
+ 600, 0, 600, 600, 600, 600, 600, 600, // 128 - 135
+ 600, 600, 600, 600, 600, 0, 600, 0, // 136 - 143
+ 0, 600, 600, 600, 600, 600, 600, 600, // 144 - 151
+ 600, 600, 600, 600, 600, 0, 600, 600, // 152 - 159
+ 600, 600, 600, 600, 600, 600, 600, 600, // 160 - 167
+ 600, 600, 600, 600, 600, 600, 600, 600, // 168 - 175
+ 600, 600, 600, 600, 600, 600, 600, 600, // 176 - 183
+ 600, 600, 600, 600, 600, 600, 600, 600, // 184 - 191
+ 600, 600, 600, 600, 600, 600, 600, 600, // 192 - 199
+ 600, 600, 600, 600, 600, 600, 600, 600, // 200 - 207
+ 600, 600, 600, 600, 600, 600, 600, 600, // 208 - 215
+ 600, 600, 600, 600, 600, 600, 600, 600, // 216 - 223
+ 600, 600, 600, 600, 600, 600, 600, 600, // 224 - 231
+ 600, 600, 600, 600, 600, 600, 600, 600, // 232 - 239
+ 600, 600, 600, 600, 600, 600, 600, 600, // 240 - 247
+ 600, 600, 600, 600, 600, 600, 600, 600 // 248 - 255
+ } },
+
+ { "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
+ } },
+
+ { "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
+ } },
+
+ { "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
+ } },
+
+ { "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
+ } },
+
+ { "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
+ } },
+
+ { "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
+ } },
+
+ { "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
+ } },
+
+ { "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
+ } },
+
+ { "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
+ } },
+
+ { "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
+ } },
+
+ { "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
+ } },
+
+ // 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
+ } },
+
+ { "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
+ } }
+
+ };
+
+BuildinFontInstance::BuildinFontInstance(const PhysicalFontFace& rFontFace,
+ const FontSelectPattern& rFSP)
+ : LogicalFontInstance(rFontFace, rFSP)
+{
+}
+
+bool BuildinFontInstance::ImplGetGlyphBoundRect(sal_GlyphId, tools::Rectangle&, bool) const
+{
+ return false;
+}
+
+bool BuildinFontInstance::GetGlyphOutline(sal_GlyphId, basegfx::B2DPolyPolygon&, bool) const
+{
+ return false;
+}
+
+BuildinFontFace::BuildinFontFace(int nId)
+ : PhysicalFontFace(m_aBuildinFonts[nId].GetFontAttributes())
+ , mrBuildin(m_aBuildinFonts[nId])
+{
+}
+
+rtl::Reference<LogicalFontInstance>
+BuildinFontFace::CreateFontInstance(const FontSelectPattern& rFSP) const
+{
+ return new BuildinFontInstance(*this, rFSP);
+}
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/pdfbuildin_fonts.hxx b/vcl/source/gdi/pdfbuildin_fonts.hxx
new file mode 100644
index 000000000..69bdee5dc
--- /dev/null
+++ b/vcl/source/gdi/pdfbuildin_fonts.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_SOURCE_PDF_BUILDIN_FONTS_HXX
+#define INCLUDED_VCL_SOURCE_PDF_BUILDIN_FONTS_HXX
+
+#include <PhysicalFontFace.hxx>
+#include <fontinstance.hxx>
+
+namespace vcl
+{
+namespace 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];
+
+ OString getNameObject() const;
+ FontAttributes GetFontAttributes() const;
+};
+
+class BuildinFontInstance final : public LogicalFontInstance
+{
+ bool ImplGetGlyphBoundRect(sal_GlyphId nID, tools::Rectangle& rRect, bool) const override;
+
+public:
+ BuildinFontInstance(const PhysicalFontFace&, const FontSelectPattern&);
+
+ bool GetGlyphOutline(sal_GlyphId nId, basegfx::B2DPolyPolygon& rPoly, bool) const override;
+};
+
+class BuildinFontFace final : public PhysicalFontFace
+{
+private:
+ static const BuildinFont m_aBuildinFonts[14];
+ const BuildinFont& mrBuildin;
+
+ rtl::Reference<LogicalFontInstance>
+ CreateFontInstance(const 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); }
+
+ static const BuildinFont& Get(int nId) { return m_aBuildinFonts[nId]; }
+};
+
+} // namespace pdf
+} // namespace vcl
+
+#endif // INCLUDED_VCL_SOURCE_PDF_BUILDIN_FONTS_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/pdfextoutdevdata.cxx b/vcl/source/gdi/pdfextoutdevdata.cxx
new file mode 100644
index 000000000..da7e78dcb
--- /dev/null
+++ b/vcl/source/gdi/pdfextoutdevdata.cxx
@@ -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 .
+ */
+
+#include <vcl/canvastools.hxx>
+#include <vcl/pdfextoutdevdata.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/gfxlink.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <sal/log.hxx>
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+#include <tools/stream.hxx>
+
+#include <memory>
+#include <map>
+
+namespace vcl
+{
+namespace {
+
+struct PDFExtOutDevDataSync
+{
+ enum Action{ CreateNamedDest,
+ CreateDest,
+ CreateLink,
+ CreateScreen,
+ SetLinkDest,
+ SetLinkURL,
+ SetScreenURL,
+ SetScreenStream,
+ RegisterDest,
+ CreateOutlineItem,
+ CreateNote,
+ SetPageTransition,
+
+ BeginStructureElement,
+ EndStructureElement,
+ SetCurrentStructureElement,
+ SetStructureAttribute,
+ SetStructureAttributeNumerical,
+ SetStructureBoundingBox,
+ SetActualText,
+ SetAlternateText,
+ CreateControl,
+ BeginGroup,
+ EndGroupGfxLink
+ };
+
+ sal_uInt32 nIdx;
+ Action eAct;
+};
+
+struct PDFLinkDestination
+{
+ tools::Rectangle mRect;
+ MapMode mMapMode;
+ sal_Int32 mPageNr;
+ PDFWriter::DestAreaType mAreaType;
+};
+
+}
+
+struct GlobalSyncData
+{
+ std::deque< PDFExtOutDevDataSync::Action > mActions;
+ std::deque< MapMode > mParaMapModes;
+ std::deque< tools::Rectangle > mParaRects;
+ std::deque< sal_Int32 > mParaInts;
+ std::deque< sal_uInt32 > mParauInts;
+ std::deque< OUString > mParaOUStrings;
+ std::deque< PDFWriter::DestAreaType > mParaDestAreaTypes;
+ std::deque< PDFNote > mParaPDFNotes;
+ std::deque< PDFWriter::PageTransition > mParaPageTransitions;
+ ::std::map< sal_Int32, PDFLinkDestination > mFutureDestinations;
+
+ sal_Int32 GetMappedId();
+ sal_Int32 GetMappedStructId( sal_Int32 );
+
+ sal_Int32 mCurId;
+ std::vector< sal_Int32 > mParaIds;
+ std::vector< sal_Int32 > mStructIdMap;
+
+ sal_Int32 mCurrentStructElement;
+ std::vector< sal_Int32 > mStructParents;
+ GlobalSyncData() :
+ mCurId ( 0 ),
+ mCurrentStructElement( 0 )
+ {
+ mStructParents.push_back( 0 );
+ mStructIdMap.push_back( 0 );
+ }
+ void PlayGlobalActions( PDFWriter& rWriter );
+};
+
+sal_Int32 GlobalSyncData::GetMappedId()
+{
+ sal_Int32 nLinkId = mParaInts.front();
+ mParaInts.pop_front();
+
+ /* negative values are intentionally passed as invalid IDs
+ * e.g. to create a new top level outline item
+ */
+ if( nLinkId >= 0 )
+ {
+ if ( o3tl::make_unsigned(nLinkId) < mParaIds.size() )
+ nLinkId = mParaIds[ nLinkId ];
+ else
+ nLinkId = -1;
+
+ SAL_WARN_IF( nLinkId < 0, "vcl", "unmapped id in GlobalSyncData" );
+ }
+
+ return nLinkId;
+}
+
+sal_Int32 GlobalSyncData::GetMappedStructId( sal_Int32 nStructId )
+{
+ if ( o3tl::make_unsigned(nStructId) < mStructIdMap.size() )
+ nStructId = mStructIdMap[ nStructId ];
+ else
+ nStructId = -1;
+
+ SAL_WARN_IF( nStructId < 0, "vcl", "unmapped structure id in GlobalSyncData" );
+
+ return nStructId;
+}
+
+void GlobalSyncData::PlayGlobalActions( PDFWriter& rWriter )
+{
+ for (auto const& action : mActions)
+ {
+ switch (action)
+ {
+ case PDFExtOutDevDataSync::CreateNamedDest : //i56629
+ {
+ rWriter.Push( PushFlags::MAPMODE );
+ rWriter.SetMapMode( mParaMapModes.front() );
+ mParaMapModes.pop_front();
+ mParaIds.push_back( rWriter.CreateNamedDest( mParaOUStrings.front(), mParaRects.front(), mParaInts.front(), mParaDestAreaTypes.front() ) );
+ mParaOUStrings.pop_front();
+ mParaRects.pop_front();
+ mParaInts.pop_front();
+ mParaDestAreaTypes.pop_front();
+ rWriter.Pop();
+ }
+ break;
+ case PDFExtOutDevDataSync::CreateDest :
+ {
+ rWriter.Push( PushFlags::MAPMODE );
+ rWriter.SetMapMode( mParaMapModes.front() );
+ mParaMapModes.pop_front();
+ mParaIds.push_back( rWriter.CreateDest( mParaRects.front(), mParaInts.front(), mParaDestAreaTypes.front() ) );
+ mParaRects.pop_front();
+ mParaInts.pop_front();
+ mParaDestAreaTypes.pop_front();
+ rWriter.Pop();
+ }
+ break;
+ case PDFExtOutDevDataSync::CreateLink :
+ {
+ rWriter.Push( PushFlags::MAPMODE );
+ rWriter.SetMapMode( mParaMapModes.front() );
+ mParaMapModes.pop_front();
+ mParaIds.push_back( rWriter.CreateLink( mParaRects.front(), mParaInts.front() ) );
+ // resolve LinkAnnotation structural attribute
+ rWriter.SetLinkPropertyID( mParaIds.back(), sal_Int32(mParaIds.size()-1) );
+ mParaRects.pop_front();
+ mParaInts.pop_front();
+ rWriter.Pop();
+ }
+ break;
+ case PDFExtOutDevDataSync::CreateScreen:
+ {
+ rWriter.Push(PushFlags::MAPMODE);
+ rWriter.SetMapMode(mParaMapModes.front());
+ mParaMapModes.pop_front();
+ mParaIds.push_back(rWriter.CreateScreen(mParaRects.front(), mParaInts.front()));
+ mParaRects.pop_front();
+ mParaInts.pop_front();
+ rWriter.Pop();
+ }
+ break;
+ case PDFExtOutDevDataSync::SetLinkDest :
+ {
+ sal_Int32 nLinkId = GetMappedId();
+ sal_Int32 nDestId = GetMappedId();
+ rWriter.SetLinkDest( nLinkId, nDestId );
+ }
+ break;
+ case PDFExtOutDevDataSync::SetLinkURL :
+ {
+ sal_Int32 nLinkId = GetMappedId();
+ rWriter.SetLinkURL( nLinkId, mParaOUStrings.front() );
+ mParaOUStrings.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::SetScreenURL:
+ {
+ sal_Int32 nScreenId = GetMappedId();
+ rWriter.SetScreenURL(nScreenId, mParaOUStrings.front());
+ mParaOUStrings.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::SetScreenStream:
+ {
+ sal_Int32 nScreenId = GetMappedId();
+ rWriter.SetScreenStream(nScreenId, mParaOUStrings.front());
+ mParaOUStrings.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::RegisterDest :
+ {
+ const sal_Int32 nDestId = mParaInts.front();
+ mParaInts.pop_front();
+ OSL_ENSURE( mFutureDestinations.find( nDestId ) != mFutureDestinations.end(),
+ "GlobalSyncData::PlayGlobalActions: DescribeRegisteredRequest has not been called for that destination!" );
+
+ PDFLinkDestination& rDest = mFutureDestinations[ nDestId ];
+
+ rWriter.Push( PushFlags::MAPMODE );
+ rWriter.SetMapMode( rDest.mMapMode );
+ mParaIds.push_back( rWriter.RegisterDestReference( nDestId, rDest.mRect, rDest.mPageNr, rDest.mAreaType ) );
+ rWriter.Pop();
+ }
+ break;
+ case PDFExtOutDevDataSync::CreateOutlineItem :
+ {
+ sal_Int32 nParent = GetMappedId();
+ sal_Int32 nLinkId = GetMappedId();
+ mParaIds.push_back( rWriter.CreateOutlineItem( nParent, mParaOUStrings.front(), nLinkId ) );
+ mParaOUStrings.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::CreateNote :
+ {
+ rWriter.Push( PushFlags::MAPMODE );
+ rWriter.SetMapMode( mParaMapModes.front() );
+ rWriter.CreateNote( mParaRects.front(), mParaPDFNotes.front(), mParaInts.front() );
+ mParaMapModes.pop_front();
+ mParaRects.pop_front();
+ mParaPDFNotes.pop_front();
+ mParaInts.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::SetPageTransition :
+ {
+ rWriter.SetPageTransition( mParaPageTransitions.front(), mParauInts.front(), mParaInts.front() );
+ mParaPageTransitions.pop_front();
+ mParauInts.pop_front();
+ mParaInts.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::BeginStructureElement:
+ case PDFExtOutDevDataSync::EndStructureElement:
+ case PDFExtOutDevDataSync::SetCurrentStructureElement:
+ case PDFExtOutDevDataSync::SetStructureAttribute:
+ case PDFExtOutDevDataSync::SetStructureAttributeNumerical:
+ case PDFExtOutDevDataSync::SetStructureBoundingBox:
+ case PDFExtOutDevDataSync::SetActualText:
+ case PDFExtOutDevDataSync::SetAlternateText:
+ case PDFExtOutDevDataSync::CreateControl:
+ case PDFExtOutDevDataSync::BeginGroup:
+ case PDFExtOutDevDataSync::EndGroupGfxLink:
+ break;
+ }
+ }
+}
+
+struct PageSyncData
+{
+ std::deque< PDFExtOutDevDataSync > mActions;
+ std::deque< tools::Rectangle > mParaRects;
+ std::deque< sal_Int32 > mParaInts;
+ std::deque< OUString > mParaOUStrings;
+ std::deque< PDFWriter::StructElement > mParaStructElements;
+ std::deque< PDFWriter::StructAttribute > mParaStructAttributes;
+ std::deque< PDFWriter::StructAttributeValue > mParaStructAttributeValues;
+ std::deque< Graphic > mGraphics;
+ Graphic mCurrentGraphic;
+ std::deque< std::shared_ptr< PDFWriter::AnyWidget > >
+ mControls;
+ GlobalSyncData* mpGlobalData;
+
+ bool mbGroupIgnoreGDIMtfActions;
+
+
+ explicit PageSyncData( GlobalSyncData* pGlobal )
+ : mbGroupIgnoreGDIMtfActions ( false )
+ { mpGlobalData = pGlobal; }
+
+ void PushAction( const OutputDevice& rOutDev, const PDFExtOutDevDataSync::Action eAct );
+ bool PlaySyncPageAct( PDFWriter& rWriter, sal_uInt32& rCurGDIMtfAction, const GDIMetaFile& rMtf, const PDFExtOutDevData& rOutDevData );
+};
+
+void PageSyncData::PushAction( const OutputDevice& rOutDev, const PDFExtOutDevDataSync::Action eAct )
+{
+ GDIMetaFile* pMtf = rOutDev.GetConnectMetaFile();
+ SAL_WARN_IF( !pMtf, "vcl", "PageSyncData::PushAction -> no ConnectMetaFile !!!" );
+
+ PDFExtOutDevDataSync aSync;
+ aSync.eAct = eAct;
+ if ( pMtf )
+ aSync.nIdx = pMtf->GetActionSize();
+ else
+ aSync.nIdx = 0x7fffffff; // sync not possible
+ mActions.push_back( aSync );
+}
+bool PageSyncData::PlaySyncPageAct( PDFWriter& rWriter, sal_uInt32& rCurGDIMtfAction, const GDIMetaFile& rMtf, const PDFExtOutDevData& rOutDevData )
+{
+ bool bRet = false;
+ if ( !mActions.empty() && ( mActions.front().nIdx == rCurGDIMtfAction ) )
+ {
+ bRet = true;
+ PDFExtOutDevDataSync aDataSync = mActions.front();
+ mActions.pop_front();
+ switch( aDataSync.eAct )
+ {
+ case PDFExtOutDevDataSync::BeginStructureElement :
+ {
+ sal_Int32 nNewEl = rWriter.BeginStructureElement( mParaStructElements.front(), mParaOUStrings.front() ) ;
+ mParaStructElements.pop_front();
+ mParaOUStrings.pop_front();
+ mpGlobalData->mStructIdMap.push_back( nNewEl );
+ }
+ break;
+ case PDFExtOutDevDataSync::EndStructureElement :
+ {
+ rWriter.EndStructureElement();
+ }
+ break;
+ case PDFExtOutDevDataSync::SetCurrentStructureElement:
+ {
+ rWriter.SetCurrentStructureElement( mpGlobalData->GetMappedStructId( mParaInts.front() ) );
+ mParaInts.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::SetStructureAttribute :
+ {
+ rWriter.SetStructureAttribute( mParaStructAttributes.front(), mParaStructAttributeValues.front() );
+ mParaStructAttributeValues.pop_front();
+ mParaStructAttributes.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::SetStructureAttributeNumerical :
+ {
+ rWriter.SetStructureAttributeNumerical( mParaStructAttributes.front(), mParaInts.front() );
+ mParaStructAttributes.pop_front();
+ mParaInts.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::SetStructureBoundingBox :
+ {
+ rWriter.SetStructureBoundingBox( mParaRects.front() );
+ mParaRects.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::SetActualText :
+ {
+ rWriter.SetActualText( mParaOUStrings.front() );
+ mParaOUStrings.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::SetAlternateText :
+ {
+ rWriter.SetAlternateText( mParaOUStrings.front() );
+ mParaOUStrings.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::CreateControl:
+ {
+ std::shared_ptr< PDFWriter::AnyWidget > pControl( mControls.front() );
+ SAL_WARN_IF( !pControl, "vcl", "PageSyncData::PlaySyncPageAct: invalid widget!" );
+ if ( pControl )
+ rWriter.CreateControl( *pControl );
+ mControls.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::BeginGroup :
+ {
+ /* first determining if this BeginGroup is starting a GfxLink,
+ by searching for an EndGroup or an EndGroupGfxLink */
+ mbGroupIgnoreGDIMtfActions = false;
+ auto isStartingGfxLink = std::any_of(mActions.begin(), mActions.end(),
+ [](const PDFExtOutDevDataSync& rAction) { return rAction.eAct == PDFExtOutDevDataSync::EndGroupGfxLink; });
+ if ( isStartingGfxLink )
+ {
+ Graphic& rGraphic = mGraphics.front();
+ if ( rGraphic.IsGfxLink() && mParaRects.size() >= 2 )
+ {
+ GfxLinkType eType = rGraphic.GetGfxLink().GetType();
+ if ( eType == GfxLinkType::NativeJpg )
+ {
+ mbGroupIgnoreGDIMtfActions = rOutDevData.HasAdequateCompression(rGraphic, mParaRects[0], mParaRects[1]);
+ if ( !mbGroupIgnoreGDIMtfActions )
+ mCurrentGraphic = rGraphic;
+ }
+ else if ( eType == GfxLinkType::NativePng || eType == GfxLinkType::NativePdf )
+ {
+ if ( eType == GfxLinkType::NativePdf || rOutDevData.HasAdequateCompression(rGraphic, mParaRects[0], mParaRects[1]) )
+ mCurrentGraphic = rGraphic;
+ }
+ }
+ }
+ }
+ break;
+ case PDFExtOutDevDataSync::EndGroupGfxLink :
+ {
+ tools::Rectangle aOutputRect, aVisibleOutputRect;
+ Graphic aGraphic( mGraphics.front() );
+
+ mGraphics.pop_front();
+ sal_Int32 nTransparency = mParaInts.front();
+ mParaInts.pop_front();
+ aOutputRect = mParaRects.front();
+ mParaRects.pop_front();
+ aVisibleOutputRect = mParaRects.front();
+ mParaRects.pop_front();
+
+ if ( mbGroupIgnoreGDIMtfActions )
+ {
+ bool bClippingNeeded = ( aOutputRect != aVisibleOutputRect ) && !aVisibleOutputRect.IsEmpty();
+
+ GfxLink aGfxLink( aGraphic.GetGfxLink() );
+ if ( aGfxLink.GetType() == GfxLinkType::NativeJpg )
+ {
+ if ( bClippingNeeded )
+ {
+ rWriter.Push();
+ basegfx::B2DPolyPolygon aRect( basegfx::utils::createPolygonFromRect(
+ vcl::unotools::b2DRectangleFromRectangle(aVisibleOutputRect) ) );
+ rWriter.SetClipRegion( aRect);
+ }
+
+ Bitmap aMask;
+ if (nTransparency)
+ {
+ AlphaMask aAlphaMask(aGraphic.GetSizePixel());
+ aAlphaMask.Erase(nTransparency);
+ aMask = aAlphaMask.GetBitmap();
+ }
+
+ 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());
+ }
+ }
+
+ rWriter.DrawJPGBitmap( aTmp, aGraphic.GetBitmapEx().GetBitCount() > 8, aGraphic.GetSizePixel(), aOutputRect, aMask, aGraphic );
+ }
+
+ if ( bClippingNeeded )
+ rWriter.Pop();
+ }
+ mbGroupIgnoreGDIMtfActions = false;
+ }
+ mCurrentGraphic.Clear();
+ }
+ break;
+ case PDFExtOutDevDataSync::CreateNamedDest:
+ case PDFExtOutDevDataSync::CreateDest:
+ case PDFExtOutDevDataSync::CreateLink:
+ case PDFExtOutDevDataSync::CreateScreen:
+ case PDFExtOutDevDataSync::SetLinkDest:
+ case PDFExtOutDevDataSync::SetLinkURL:
+ case PDFExtOutDevDataSync::SetScreenURL:
+ case PDFExtOutDevDataSync::SetScreenStream:
+ case PDFExtOutDevDataSync::RegisterDest:
+ case PDFExtOutDevDataSync::CreateOutlineItem:
+ case PDFExtOutDevDataSync::CreateNote:
+ case PDFExtOutDevDataSync::SetPageTransition:
+ break;
+ }
+ }
+ else if ( mbGroupIgnoreGDIMtfActions )
+ {
+ rCurGDIMtfAction++;
+ bRet = true;
+ }
+ return bRet;
+}
+
+PDFExtOutDevData::PDFExtOutDevData( const OutputDevice& rOutDev ) :
+ mrOutDev ( rOutDev ),
+ mbTaggedPDF ( false ),
+ mbExportNotes ( true ),
+ mbExportNotesPages ( false ),
+ mbTransitionEffects ( true ),
+ mbUseLosslessCompression( true ),
+ mbReduceImageResolution ( false ),
+ mbExportFormFields ( false ),
+ mbExportBookmarks ( false ),
+ mbExportHiddenSlides ( false ),
+ mbSinglePageSheets ( false ),
+ mbExportNDests ( false ),
+ mnPage ( -1 ),
+ mnCompressionQuality ( 90 ),
+ mpGlobalSyncData ( new GlobalSyncData() )
+{
+ mpPageSyncData.reset( new PageSyncData( mpGlobalSyncData.get() ) );
+}
+
+PDFExtOutDevData::~PDFExtOutDevData()
+{
+ mpPageSyncData.reset();
+ mpGlobalSyncData.reset();
+}
+
+const Graphic& PDFExtOutDevData::GetCurrentGraphic() const
+{
+ return mpPageSyncData->mCurrentGraphic;
+}
+
+void PDFExtOutDevData::SetDocumentLocale( const css::lang::Locale& rLoc )
+{
+ maDocLocale = rLoc;
+}
+void PDFExtOutDevData::SetCurrentPageNumber( const sal_Int32 nPage )
+{
+ mnPage = nPage;
+}
+void PDFExtOutDevData::SetIsLosslessCompression( const bool bUseLosslessCompression )
+{
+ mbUseLosslessCompression = bUseLosslessCompression;
+}
+void PDFExtOutDevData::SetCompressionQuality( const sal_Int32 nQuality )
+{
+ mnCompressionQuality = nQuality;
+}
+void PDFExtOutDevData::SetIsReduceImageResolution( const bool bReduceImageResolution )
+{
+ mbReduceImageResolution = bReduceImageResolution;
+}
+void PDFExtOutDevData::SetIsExportNotes( const bool bExportNotes )
+{
+ mbExportNotes = bExportNotes;
+}
+void PDFExtOutDevData::SetIsExportNotesPages( const bool bExportNotesPages )
+{
+ mbExportNotesPages = bExportNotesPages;
+}
+void PDFExtOutDevData::SetIsExportTaggedPDF( const bool bTaggedPDF )
+{
+ mbTaggedPDF = bTaggedPDF;
+}
+void PDFExtOutDevData::SetIsExportTransitionEffects( const bool bTransitionEffects )
+{
+ mbTransitionEffects = bTransitionEffects;
+}
+void PDFExtOutDevData::SetIsExportFormFields( const bool bExportFomtFields )
+{
+ mbExportFormFields = bExportFomtFields;
+}
+void PDFExtOutDevData::SetIsExportBookmarks( const bool bExportBookmarks )
+{
+ mbExportBookmarks = bExportBookmarks;
+}
+void PDFExtOutDevData::SetIsExportHiddenSlides( const bool bExportHiddenSlides )
+{
+ mbExportHiddenSlides = bExportHiddenSlides;
+}
+void PDFExtOutDevData::SetIsSinglePageSheets( const bool bSinglePageSheets )
+{
+ mbSinglePageSheets = bSinglePageSheets;
+}
+void PDFExtOutDevData::SetIsExportNamedDestinations( const bool bExportNDests )
+{
+ mbExportNDests = bExportNDests;
+}
+void PDFExtOutDevData::ResetSyncData()
+{
+ *mpPageSyncData = PageSyncData( mpGlobalSyncData.get() );
+}
+bool PDFExtOutDevData::PlaySyncPageAct( PDFWriter& rWriter, sal_uInt32& rIdx, const GDIMetaFile& rMtf )
+{
+ return mpPageSyncData->PlaySyncPageAct( rWriter, rIdx, rMtf, *this );
+}
+void PDFExtOutDevData::PlayGlobalActions( PDFWriter& rWriter )
+{
+ mpGlobalSyncData->PlayGlobalActions( rWriter );
+}
+
+/* global actions, synchronisation to the recorded metafile isn't needed,
+ all actions will be played after the last page was recorded
+*/
+//--->i56629
+sal_Int32 PDFExtOutDevData::CreateNamedDest(const OUString& sDestName, const tools::Rectangle& rRect, sal_Int32 nPageNr )
+{
+ mpGlobalSyncData->mActions.push_back( PDFExtOutDevDataSync::CreateNamedDest );
+ mpGlobalSyncData->mParaOUStrings.push_back( sDestName );
+ mpGlobalSyncData->mParaRects.push_back( rRect );
+ mpGlobalSyncData->mParaMapModes.push_back( mrOutDev.GetMapMode() );
+ mpGlobalSyncData->mParaInts.push_back( nPageNr == -1 ? mnPage : nPageNr );
+ mpGlobalSyncData->mParaDestAreaTypes.push_back( PDFWriter::DestAreaType::XYZ );
+
+ return mpGlobalSyncData->mCurId++;
+}
+//<---i56629
+sal_Int32 PDFExtOutDevData::RegisterDest()
+{
+ const sal_Int32 nLinkDestID = mpGlobalSyncData->mCurId++;
+ mpGlobalSyncData->mActions.push_back( PDFExtOutDevDataSync::RegisterDest );
+ mpGlobalSyncData->mParaInts.push_back( nLinkDestID );
+
+ return nLinkDestID;
+}
+void PDFExtOutDevData::DescribeRegisteredDest( sal_Int32 nDestId, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
+{
+ OSL_PRECOND( nDestId != -1, "PDFExtOutDevData::DescribeRegisteredDest: invalid destination Id!" );
+ PDFLinkDestination aLinkDestination;
+ aLinkDestination.mRect = rRect;
+ aLinkDestination.mMapMode = mrOutDev.GetMapMode();
+ aLinkDestination.mPageNr = nPageNr == -1 ? mnPage : nPageNr;
+ aLinkDestination.mAreaType = eType;
+ mpGlobalSyncData->mFutureDestinations[ nDestId ] = aLinkDestination;
+}
+sal_Int32 PDFExtOutDevData::CreateDest( const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
+{
+ mpGlobalSyncData->mActions.push_back( PDFExtOutDevDataSync::CreateDest );
+ mpGlobalSyncData->mParaRects.push_back( rRect );
+ mpGlobalSyncData->mParaMapModes.push_back( mrOutDev.GetMapMode() );
+ mpGlobalSyncData->mParaInts.push_back( nPageNr == -1 ? mnPage : nPageNr );
+ mpGlobalSyncData->mParaDestAreaTypes.push_back( eType );
+ return mpGlobalSyncData->mCurId++;
+}
+sal_Int32 PDFExtOutDevData::CreateLink( const tools::Rectangle& rRect, sal_Int32 nPageNr )
+{
+ mpGlobalSyncData->mActions.push_back( PDFExtOutDevDataSync::CreateLink );
+ mpGlobalSyncData->mParaRects.push_back( rRect );
+ mpGlobalSyncData->mParaMapModes.push_back( mrOutDev.GetMapMode() );
+ mpGlobalSyncData->mParaInts.push_back( nPageNr == -1 ? mnPage : nPageNr );
+ return mpGlobalSyncData->mCurId++;
+}
+
+sal_Int32 PDFExtOutDevData::CreateScreen(const tools::Rectangle& rRect, sal_Int32 nPageNr)
+{
+ mpGlobalSyncData->mActions.push_back(PDFExtOutDevDataSync::CreateScreen);
+ mpGlobalSyncData->mParaRects.push_back(rRect);
+ mpGlobalSyncData->mParaMapModes.push_back(mrOutDev.GetMapMode());
+ mpGlobalSyncData->mParaInts.push_back(nPageNr);
+ return mpGlobalSyncData->mCurId++;
+}
+
+void PDFExtOutDevData::SetLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId )
+{
+ mpGlobalSyncData->mActions.push_back( PDFExtOutDevDataSync::SetLinkDest );
+ mpGlobalSyncData->mParaInts.push_back( nLinkId );
+ mpGlobalSyncData->mParaInts.push_back( nDestId );
+}
+void PDFExtOutDevData::SetLinkURL( sal_Int32 nLinkId, const OUString& rURL )
+{
+ mpGlobalSyncData->mActions.push_back( PDFExtOutDevDataSync::SetLinkURL );
+ mpGlobalSyncData->mParaInts.push_back( nLinkId );
+ mpGlobalSyncData->mParaOUStrings.push_back( rURL );
+}
+
+void PDFExtOutDevData::SetScreenURL(sal_Int32 nScreenId, const OUString& rURL)
+{
+ mpGlobalSyncData->mActions.push_back(PDFExtOutDevDataSync::SetScreenURL);
+ mpGlobalSyncData->mParaInts.push_back(nScreenId);
+ mpGlobalSyncData->mParaOUStrings.push_back(rURL);
+}
+
+void PDFExtOutDevData::SetScreenStream(sal_Int32 nScreenId, const OUString& rURL)
+{
+ mpGlobalSyncData->mActions.push_back(PDFExtOutDevDataSync::SetScreenStream);
+ mpGlobalSyncData->mParaInts.push_back(nScreenId);
+ mpGlobalSyncData->mParaOUStrings.push_back(rURL);
+}
+
+sal_Int32 PDFExtOutDevData::CreateOutlineItem( sal_Int32 nParent, const OUString& rText, sal_Int32 nDestID )
+{
+ if (nParent == -1)
+ // Has no parent, it's a chapter / heading 1.
+ maChapterNames.push_back(rText);
+
+ mpGlobalSyncData->mActions.push_back( PDFExtOutDevDataSync::CreateOutlineItem );
+ mpGlobalSyncData->mParaInts.push_back( nParent );
+ mpGlobalSyncData->mParaOUStrings.push_back( rText );
+ mpGlobalSyncData->mParaInts.push_back( nDestID );
+ return mpGlobalSyncData->mCurId++;
+}
+void PDFExtOutDevData::CreateNote( const tools::Rectangle& rRect, const PDFNote& rNote, sal_Int32 nPageNr )
+{
+ mpGlobalSyncData->mActions.push_back( PDFExtOutDevDataSync::CreateNote );
+ mpGlobalSyncData->mParaRects.push_back( rRect );
+ mpGlobalSyncData->mParaMapModes.push_back( mrOutDev.GetMapMode() );
+ mpGlobalSyncData->mParaPDFNotes.push_back( rNote );
+ mpGlobalSyncData->mParaInts.push_back( nPageNr == -1 ? mnPage : nPageNr );
+}
+void PDFExtOutDevData::SetPageTransition( PDFWriter::PageTransition eType, sal_uInt32 nMilliSec )
+{
+ mpGlobalSyncData->mActions.push_back( PDFExtOutDevDataSync::SetPageTransition );
+ mpGlobalSyncData->mParaPageTransitions.push_back( eType );
+ mpGlobalSyncData->mParauInts.push_back( nMilliSec );
+ mpGlobalSyncData->mParaInts.push_back( mnPage );
+}
+
+/* local (page), actions have to be played synchronously to the actions of
+ of the recorded metafile (created by each xRenderable->render()) */
+ sal_Int32 PDFExtOutDevData::BeginStructureElement( PDFWriter::StructElement eType, const OUString& rAlias )
+{
+ mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::BeginStructureElement );
+ mpPageSyncData->mParaStructElements.push_back( eType );
+ mpPageSyncData->mParaOUStrings.push_back( rAlias );
+ // need a global id
+ sal_Int32 nNewId = mpGlobalSyncData->mStructParents.size();
+ mpGlobalSyncData->mStructParents.push_back( mpGlobalSyncData->mCurrentStructElement );
+ mpGlobalSyncData->mCurrentStructElement = nNewId;
+ return nNewId;
+}
+void PDFExtOutDevData::EndStructureElement()
+{
+ mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::EndStructureElement );
+ mpGlobalSyncData->mCurrentStructElement = mpGlobalSyncData->mStructParents[ mpGlobalSyncData->mCurrentStructElement ];
+}
+bool PDFExtOutDevData::SetCurrentStructureElement( sal_Int32 nStructId )
+{
+ bool bSuccess = false;
+ if( o3tl::make_unsigned(nStructId) < mpGlobalSyncData->mStructParents.size() )
+ {
+ mpGlobalSyncData->mCurrentStructElement = nStructId;
+ mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::SetCurrentStructureElement );
+ mpPageSyncData->mParaInts.push_back( nStructId );
+ bSuccess = true;
+ }
+ return bSuccess;
+}
+sal_Int32 PDFExtOutDevData::GetCurrentStructureElement() const
+{
+ return mpGlobalSyncData->mCurrentStructElement;
+}
+void PDFExtOutDevData::SetStructureAttribute( PDFWriter::StructAttribute eAttr, PDFWriter::StructAttributeValue eVal )
+{
+ mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::SetStructureAttribute );
+ mpPageSyncData->mParaStructAttributes.push_back( eAttr );
+ mpPageSyncData->mParaStructAttributeValues.push_back( eVal );
+}
+void PDFExtOutDevData::SetStructureAttributeNumerical( PDFWriter::StructAttribute eAttr, sal_Int32 nValue )
+{
+ mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::SetStructureAttributeNumerical );
+ mpPageSyncData->mParaStructAttributes.push_back( eAttr );
+ mpPageSyncData->mParaInts.push_back( nValue );
+}
+void PDFExtOutDevData::SetStructureBoundingBox( const tools::Rectangle& rRect )
+{
+ mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::SetStructureBoundingBox );
+ mpPageSyncData->mParaRects.push_back( rRect );
+}
+void PDFExtOutDevData::SetActualText( const OUString& rText )
+{
+ mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::SetActualText );
+ mpPageSyncData->mParaOUStrings.push_back( rText );
+}
+void PDFExtOutDevData::SetAlternateText( const OUString& rText )
+{
+ mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::SetAlternateText );
+ mpPageSyncData->mParaOUStrings.push_back( rText );
+}
+
+void PDFExtOutDevData::CreateControl( const PDFWriter::AnyWidget& rControlType )
+{
+ mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::CreateControl );
+
+ std::shared_ptr< PDFWriter::AnyWidget > pClone( rControlType.Clone() );
+ mpPageSyncData->mControls.push_back( pClone );
+}
+
+void PDFExtOutDevData::BeginGroup()
+{
+ mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::BeginGroup );
+}
+
+void PDFExtOutDevData::EndGroup( const Graphic& rGraphic,
+ sal_uInt8 nTransparency,
+ const tools::Rectangle& rOutputRect,
+ const tools::Rectangle& rVisibleOutputRect )
+{
+ mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::EndGroupGfxLink );
+ mpPageSyncData->mGraphics.push_back( rGraphic );
+ mpPageSyncData->mParaInts.push_back( nTransparency );
+ mpPageSyncData->mParaRects.push_back( rOutputRect );
+ mpPageSyncData->mParaRects.push_back( rVisibleOutputRect );
+}
+
+// Avoids expensive de-compression and re-compression of large images.
+bool PDFExtOutDevData::HasAdequateCompression( const Graphic &rGraphic,
+ const tools::Rectangle & rOutputRect,
+ const tools::Rectangle & rVisibleOutputRect ) const
+{
+ assert(rGraphic.IsGfxLink() &&
+ (rGraphic.GetGfxLink().GetType() == GfxLinkType::NativeJpg ||
+ rGraphic.GetGfxLink().GetType() == GfxLinkType::NativePng ||
+ rGraphic.GetGfxLink().GetType() == GfxLinkType::NativePdf));
+
+ if (rOutputRect != rVisibleOutputRect)
+ // rOutputRect is the crop rectangle, re-compress cropped image.
+ return false;
+
+ if (mbReduceImageResolution)
+ // Reducing resolution was requested, implies that re-compressing is
+ // wanted.
+ return false;
+
+ if (rGraphic.GetGfxLink().GetDataSize() == 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) /
+ rGraphic.GetGfxLink().GetDataSize();
+
+ static const struct {
+ sal_Int32 mnQuality;
+ sal_Int32 mnRatio;
+ } aRatios[] = { // minimum tolerable compression ratios
+ { 100, 400 }, { 95, 700 }, { 90, 1000 }, { 85, 1200 },
+ { 80, 1500 }, { 75, 1700 }
+ };
+ sal_Int32 nTargetRatio = 10000;
+ bool bIsTargetRatioReached = false;
+ for (auto & rRatio : aRatios)
+ {
+ if ( mnCompressionQuality > rRatio.mnQuality )
+ {
+ bIsTargetRatioReached = true;
+ break;
+ }
+ nTargetRatio = rRatio.mnRatio;
+ }
+
+ return ((nCurrentRatio > nTargetRatio) && bIsTargetRatioReached);
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/pdffontcache.cxx b/vcl/source/gdi/pdffontcache.cxx
new file mode 100644
index 000000000..b79753c0f
--- /dev/null
+++ b/vcl/source/gdi/pdffontcache.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <typeinfo>
+
+#include <sal/types.h>
+
+#include <PhysicalFontFace.hxx>
+#include <salgdi.hxx>
+
+#include "pdffontcache.hxx"
+
+using namespace vcl;
+
+PDFFontCache::FontIdentifier::FontIdentifier( const PhysicalFontFace* pFont, bool bVertical ) :
+ m_nFontId( pFont->GetFontId() ),
+ m_bVertical( bVertical ),
+ m_typeFontFace( const_cast<std::type_info*>(&typeid(pFont)) )
+{
+}
+
+PDFFontCache::FontData& PDFFontCache::getFont( const PhysicalFontFace* pFont, bool bVertical )
+{
+ FontIdentifier aId( pFont, bVertical );
+ FontToIndexMap::iterator it = m_aFontToIndex.find( aId );
+ if( it != m_aFontToIndex.end() )
+ return m_aFonts[ it->second ];
+ m_aFontToIndex[ aId ] = sal_uInt32(m_aFonts.size());
+ m_aFonts.emplace_back( );
+ return m_aFonts.back();
+}
+
+sal_Int32 PDFFontCache::getGlyphWidth( const PhysicalFontFace* pFont, sal_GlyphId nGlyph, bool bVertical, SalGraphics* pGraphics )
+{
+ sal_Int32 nWidth = 0;
+ FontData& rFontData( getFont( pFont, bVertical ) );
+ if( rFontData.m_nWidths.empty() )
+ {
+ pGraphics->GetGlyphWidths( pFont, bVertical, rFontData.m_nWidths, rFontData.m_aGlyphIdToIndex );
+ }
+ if( ! rFontData.m_nWidths.empty() )
+ {
+ if (nGlyph < rFontData.m_nWidths.size())
+ nWidth = rFontData.m_nWidths[nGlyph];
+ }
+ return nWidth;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/pdffontcache.hxx b/vcl/source/gdi/pdffontcache.hxx
new file mode 100644
index 000000000..6eb8e7823
--- /dev/null
+++ b/vcl/source/gdi/pdffontcache.hxx
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_GDI_PDFFONTCACHE_HXX
+#define INCLUDED_VCL_SOURCE_GDI_PDFFONTCACHE_HXX
+
+#include <typeinfo>
+
+#include <sal/types.h>
+
+#include <salgdi.hxx>
+
+namespace vcl
+{
+ class PDFFontCache
+ {
+ struct FontIdentifier
+ {
+ sal_IntPtr m_nFontId;
+ bool m_bVertical;
+ std::type_info* m_typeFontFace;
+
+ FontIdentifier( const PhysicalFontFace*, bool bVertical );
+
+ // Less than needed for std::set and std::map
+ bool operator<( const FontIdentifier& rRight ) const
+ {
+ std::type_info *pType = rRight.m_typeFontFace;
+
+ return m_nFontId < rRight.m_nFontId ||
+ ( m_nFontId == rRight.m_nFontId &&
+ ( m_typeFontFace->before( *pType ) ||
+ ( *m_typeFontFace == *pType && m_bVertical < rRight.m_bVertical ) ) );
+ }
+ };
+ struct FontData
+ {
+ std::vector< sal_Int32 > m_nWidths;
+ Ucs2UIntMap m_aGlyphIdToIndex;
+ };
+ typedef std::map< FontIdentifier, sal_uInt32 > FontToIndexMap;
+
+ std::vector< FontData > m_aFonts;
+ FontToIndexMap m_aFontToIndex;
+
+ FontData& getFont( const PhysicalFontFace*, bool bVertical );
+ public:
+ PDFFontCache() {}
+
+ sal_Int32 getGlyphWidth( const PhysicalFontFace*, sal_GlyphId, bool bVertical, SalGraphics* );
+ };
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/pdfwriter.cxx b/vcl/source/gdi/pdfwriter.cxx
new file mode 100644
index 000000000..9a418917c
--- /dev/null
+++ b/vcl/source/gdi/pdfwriter.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 "pdfwriter_impl.hxx"
+#include <vcl/bitmapex.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,
+ 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,
+ const long* pDXAry,
+ sal_Int32 nIndex,
+ sal_Int32 nLen )
+{
+ xImplementation->drawTextArray( rStartPt, rStr, pDXAry, nIndex, nLen );
+}
+
+void PDFWriter::DrawStretchText(
+ const Point& rStartPt,
+ sal_uLong nWidth,
+ const OUString& rStr,
+ sal_Int32 nIndex,
+ sal_Int32 nLen )
+{
+ xImplementation->drawStretchText( rStartPt, nWidth, rStr, nIndex, nLen );
+}
+
+void PDFWriter::DrawText(
+ const tools::Rectangle& rRect,
+ const OUString& rStr,
+ DrawTextFlags nStyle )
+{
+ xImplementation->drawText( rRect, rStr, nStyle );
+}
+
+void PDFWriter::DrawLine( const Point& rStart, const Point& rStop )
+{
+ xImplementation->drawLine( rStart, rStop );
+}
+
+void PDFWriter::DrawLine( const Point& rStart, const Point& rStop, const LineInfo& rInfo )
+{
+ xImplementation->drawLine( rStart, rStop, rInfo );
+}
+
+void PDFWriter::DrawPolygon( const tools::Polygon& rPoly )
+{
+ xImplementation->drawPolygon( rPoly );
+}
+
+void PDFWriter::DrawPolyLine( const tools::Polygon& rPoly )
+{
+ xImplementation->drawPolyLine( rPoly );
+}
+
+void PDFWriter::DrawRect( const tools::Rectangle& rRect )
+{
+ xImplementation->drawRectangle( rRect );
+}
+
+void PDFWriter::DrawRect( const tools::Rectangle& rRect, sal_uLong nHorzRound, sal_uLong nVertRound )
+{
+ xImplementation->drawRectangle( rRect, nHorzRound, nVertRound );
+}
+
+void PDFWriter::DrawEllipse( const tools::Rectangle& rRect )
+{
+ xImplementation->drawEllipse( rRect );
+}
+
+void PDFWriter::DrawArc( const tools::Rectangle& rRect, const Point& rStart, const Point& rStop )
+{
+ xImplementation->drawArc( rRect, rStart, rStop, false, false );
+}
+
+void PDFWriter::DrawPie( const tools::Rectangle& rRect, const Point& rStart, const Point& rStop )
+{
+ xImplementation->drawArc( rRect, rStart, rStop, true, false );
+}
+
+void PDFWriter::DrawChord( const tools::Rectangle& rRect, const Point& rStart, const Point& rStop )
+{
+ xImplementation->drawArc( rRect, rStart, rStop, false, true );
+}
+
+void PDFWriter::DrawPolyLine( const tools::Polygon& rPoly, const LineInfo& rInfo )
+{
+ xImplementation->drawPolyLine( rPoly, rInfo );
+}
+
+void PDFWriter::DrawPolyLine( const tools::Polygon& rPoly, const ExtLineInfo& rInfo )
+{
+ xImplementation->drawPolyLine( rPoly, rInfo );
+}
+
+void PDFWriter::DrawPolyPolygon( const tools::PolyPolygon& rPolyPoly )
+{
+ xImplementation->drawPolyPolygon( rPolyPoly );
+}
+
+void PDFWriter::DrawPixel( const Point& rPos, const Color& rColor )
+{
+ xImplementation->drawPixel( rPos, rColor );
+}
+
+void PDFWriter::DrawBitmap( const Point& rDestPt, const Size& rDestSize, const Bitmap& rBitmap, const Graphic& rGraphic )
+{
+ xImplementation->drawBitmap( rDestPt, rDestSize, rBitmap, rGraphic );
+}
+
+void PDFWriter::DrawBitmapEx( const Point& rDestPt, const Size& rDestSize, const BitmapEx& rBitmap )
+{
+ xImplementation->drawBitmap( rDestPt, rDestSize, rBitmap );
+}
+
+void PDFWriter::DrawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch )
+{
+ xImplementation->drawHatch( rPolyPoly, rHatch );
+}
+
+void PDFWriter::DrawGradient( const tools::Rectangle& rRect, const Gradient& rGradient )
+{
+ xImplementation->drawGradient( rRect, rGradient );
+}
+
+void PDFWriter::DrawGradient( const tools::PolyPolygon& rPolyPoly, const Gradient& rGradient )
+{
+ xImplementation->push(PushFlags::CLIPREGION);
+ xImplementation->setClipRegion( rPolyPoly.getB2DPolyPolygon() );
+ xImplementation->drawGradient( rPolyPoly.GetBoundRect(), rGradient );
+ xImplementation->pop();
+}
+
+void PDFWriter::DrawWallpaper( const tools::Rectangle& rRect, const Wallpaper& rWallpaper )
+{
+ xImplementation->drawWallpaper( rRect, rWallpaper );
+}
+
+void PDFWriter::DrawTransparent( const tools::PolyPolygon& rPolyPoly, sal_uInt16 nTransparencePercent )
+{
+ xImplementation->drawTransparent( rPolyPoly, nTransparencePercent );
+}
+
+void PDFWriter::BeginTransparencyGroup()
+{
+ xImplementation->beginTransparencyGroup();
+}
+
+void PDFWriter::EndTransparencyGroup( const tools::Rectangle& rRect, sal_uInt16 nTransparentPercent )
+{
+ xImplementation->endTransparencyGroup( rRect, nTransparentPercent );
+}
+
+void PDFWriter::Push( PushFlags nFlags )
+{
+ xImplementation->push( nFlags );
+}
+
+void PDFWriter::Pop()
+{
+ xImplementation->pop();
+}
+
+void PDFWriter::SetMapMode( const MapMode& rMapMode )
+{
+ xImplementation->setMapMode( rMapMode );
+}
+
+void PDFWriter::SetLineColor( const Color& rColor )
+{
+ xImplementation->setLineColor( rColor );
+}
+
+void PDFWriter::SetFillColor( const Color& rColor )
+{
+ xImplementation->setFillColor( rColor );
+}
+
+void PDFWriter::SetClipRegion()
+{
+ xImplementation->clearClipRegion();
+}
+
+void PDFWriter::SetClipRegion( const basegfx::B2DPolyPolygon& rRegion )
+{
+ xImplementation->setClipRegion( rRegion );
+}
+
+void PDFWriter::MoveClipRegion( long nHorzMove, 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( 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 Bitmap& rMask, const Graphic& rGraphic )
+{
+ xImplementation->drawJPGBitmap( rStreamData, bIsTrueColor, rSrcSizePixel, rTargetArea, rMask, rGraphic );
+}
+
+sal_Int32 PDFWriter::CreateLink( const tools::Rectangle& rRect, sal_Int32 nPageNr )
+{
+ return xImplementation->createLink( rRect, nPageNr );
+}
+
+sal_Int32 PDFWriter::CreateScreen(const tools::Rectangle& rRect, sal_Int32 nPageNr)
+{
+ return xImplementation->createScreen(rRect, nPageNr);
+}
+
+sal_Int32 PDFWriter::RegisterDestReference( sal_Int32 nDestId, const tools::Rectangle& rRect, sal_Int32 nPageNr, DestAreaType eType )
+{
+ return xImplementation->registerDestReference( nDestId, rRect, nPageNr, eType );
+}
+//--->i56629
+sal_Int32 PDFWriter::CreateNamedDest( const OUString& sDestName, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
+{
+ return xImplementation->createNamedDest( sDestName, rRect, nPageNr, eType );
+}
+sal_Int32 PDFWriter::CreateDest( const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
+{
+ return xImplementation->createDest( rRect, nPageNr, eType );
+}
+
+void PDFWriter::SetLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId )
+{
+ xImplementation->setLinkDest( nLinkId, nDestId );
+}
+
+void PDFWriter::SetLinkURL( sal_Int32 nLinkId, const OUString& rURL )
+{
+ xImplementation->setLinkURL( nLinkId, rURL );
+}
+
+void PDFWriter::SetScreenURL(sal_Int32 nScreenId, const OUString& rURL)
+{
+ xImplementation->setScreenURL(nScreenId, rURL);
+}
+
+void PDFWriter::SetScreenStream(sal_Int32 nScreenId, const OUString& rURL)
+{
+ xImplementation->setScreenStream(nScreenId, rURL);
+}
+
+void PDFWriter::SetLinkPropertyID( sal_Int32 nLinkId, sal_Int32 nPropertyId )
+{
+ xImplementation->setLinkPropertyId( nLinkId, nPropertyId );
+}
+
+sal_Int32 PDFWriter::CreateOutlineItem( sal_Int32 nParent, const OUString& rText, sal_Int32 nDestID )
+{
+ return xImplementation->createOutlineItem( nParent, rText, nDestID );
+}
+
+void PDFWriter::CreateNote( const tools::Rectangle& rRect, const PDFNote& rNote, sal_Int32 nPageNr )
+{
+ xImplementation->createNote( rRect, rNote, nPageNr );
+}
+
+sal_Int32 PDFWriter::BeginStructureElement( PDFWriter::StructElement eType, const OUString& rAlias )
+{
+ return xImplementation->beginStructureElement( eType, rAlias );
+}
+
+void PDFWriter::EndStructureElement()
+{
+ xImplementation->endStructureElement();
+}
+
+void PDFWriter::SetCurrentStructureElement( sal_Int32 nID )
+{
+ xImplementation->setCurrentStructureElement( nID );
+}
+
+void PDFWriter::SetStructureAttribute( enum StructAttribute eAttr, enum StructAttributeValue eVal )
+{
+ xImplementation->setStructureAttribute( eAttr, eVal );
+}
+
+void PDFWriter::SetStructureAttributeNumerical( enum StructAttribute eAttr, sal_Int32 nValue )
+{
+ xImplementation->setStructureAttributeNumerical( eAttr, nValue );
+}
+
+void PDFWriter::SetStructureBoundingBox( const tools::Rectangle& rRect )
+{
+ xImplementation->setStructureBoundingBox( rRect );
+}
+
+void PDFWriter::SetActualText( const OUString& rText )
+{
+ xImplementation->setActualText( rText );
+}
+
+void PDFWriter::SetAlternateText( const OUString& rText )
+{
+ xImplementation->setAlternateText( rText );
+}
+
+void PDFWriter::SetPageTransition( PDFWriter::PageTransition eType, sal_uInt32 nMilliSec, sal_Int32 nPageNr )
+{
+ xImplementation->setPageTransition( eType, nMilliSec, nPageNr );
+}
+
+sal_Int32 PDFWriter::CreateControl( const PDFWriter::AnyWidget& rControl )
+{
+ return xImplementation->createControl( rControl );
+}
+
+PDFOutputStream::~PDFOutputStream()
+{
+}
+
+void PDFWriter::AddStream( const OUString& rMimeType, PDFOutputStream* pStream )
+{
+ xImplementation->addStream( rMimeType, pStream );
+}
+
+std::set< PDFWriter::ErrorCode > const & PDFWriter::GetErrors() const
+{
+ return xImplementation->getErrors();
+}
+
+css::uno::Reference< css::beans::XMaterialHolder >
+PDFWriter::InitEncryption( const OUString& i_rOwnerPassword,
+ const OUString& i_rUserPassword
+ )
+{
+ return PDFWriterImpl::initEncryption( i_rOwnerPassword, i_rUserPassword );
+}
+
+void PDFWriter::PlayMetafile( const GDIMetaFile& i_rMTF, const vcl::PDFWriter::PlayMetafileContext& i_rPlayContext, PDFExtOutDevData* i_pData )
+{
+ xImplementation->playMetafile( i_rMTF, i_pData, i_rPlayContext );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx
new file mode 100644
index 000000000..7e02762d6
--- /dev/null
+++ b/vcl/source/gdi/pdfwriter_impl.cxx
@@ -0,0 +1,11212 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/types.h>
+
+#include <math.h>
+#include <algorithm>
+
+#include <lcms2.h>
+
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <memory>
+#include <com/sun/star/io/XOutputStream.hpp>
+#include <com/sun/star/util/URL.hpp>
+#include <com/sun/star/util/URLTransformer.hpp>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/string.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <o3tl/numeric.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <osl/file.hxx>
+#include <osl/thread.h>
+#include <rtl/digest.h>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+#include <svl/urihelper.hxx>
+#include <tools/fract.hxx>
+#include <tools/helpers.hxx>
+#include <tools/stream.hxx>
+#include <tools/urlobj.hxx>
+#include <tools/zcodec.hxx>
+#include <svl/cryptosign.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/bitmapaccess.hxx>
+#include <vcl/canvastools.hxx>
+#include <vcl/cvtgrf.hxx>
+#include <vcl/fontcharmap.hxx>
+#include <vcl/lineinfo.hxx>
+#include <vcl/metric.hxx>
+#include <vcl/settings.hxx>
+#include <strhelper.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/filter/pdfdocument.hxx>
+#include <comphelper/hash.hxx>
+
+#include <fontsubset.hxx>
+#include <PhysicalFontFace.hxx>
+#include <salgdi.hxx>
+#include <textlayout.hxx>
+#include <textlineinfo.hxx>
+#include <bitmapwriteaccess.hxx>
+#include <impglyphitem.hxx>
+#include <pdf/XmpMetadata.hxx>
+
+#include "pdfwriter_impl.hxx"
+
+#ifdef _WIN32
+// WinCrypt headers for PDF signing
+// Note: this uses Windows 7 APIs and requires the relevant data types
+#include <prewin.h>
+#include <wincrypt.h>
+#include <postwin.h>
+#endif
+
+#include <config_eot.h>
+
+#if ENABLE_EOT
+#include <libeot/libeot.h>
+#endif
+
+using namespace vcl;
+using namespace::com::sun::star;
+
+static bool g_bDebugDisableCompression = getenv("VCL_DEBUG_DISABLE_PDFCOMPRESSION");
+
+#if HAVE_FEATURE_NSS
+// Is this length truly the maximum possible, or just a number that
+// seemed large enough when the author tested this (with some type of
+// certificates)? I suspect the latter.
+
+// Used to be 0x4000 = 16384, but a sample signed PDF (produced by
+// some other software) provided by the customer has a signature
+// content that is 30000 bytes. The SampleSignedPDFDocument.pdf from
+// Adobe has one that is 21942 bytes. So let's be careful. Pity this
+// can't be dynamic, at least not without restructuring the code. Also
+// note that the checks in the code for this being too small
+// apparently are broken, if this overflows you end up with an invalid
+// PDF. Need to fix that.
+
+#define MAX_SIGNATURE_CONTENT_LENGTH 50000
+#endif
+
+static const sal_Int32 nLog10Divisor = 3;
+static const double fDivisor = 1000.0;
+
+static double pixelToPoint( double px ) { return px/fDivisor; }
+static sal_Int32 pointToPixel( double pt ) { return sal_Int32(pt*fDivisor); }
+
+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
+};
+
+static 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 ] );
+}
+
+static void appendName( const OUString& 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 );
+ }
+ }
+}
+
+static 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
+static 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.
+*/
+static 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 );
+ }
+ }
+}
+
+void PDFWriter::AppendUnicodeTextString(const OUString& rString, OStringBuffer& rBuffer)
+{
+ rBuffer.append( "FEFF" );
+ const sal_Unicode* pStr = rString.getStr();
+ sal_Int32 nLen = rString.getLength();
+ for( int i = 0; i < nLen; i++ )
+ {
+ sal_Unicode aChar = pStr[i];
+ appendHex( static_cast<sal_Int8>(aChar >> 8), rBuffer );
+ appendHex( static_cast<sal_Int8>(aChar & 255 ), rBuffer );
+ }
+}
+
+void PDFWriterImpl::createWidgetFieldName( sal_Int32 i_nWidgetIndex, const PDFWriter::AnyWidget& i_rControl )
+{
+ /* #i80258# previously we use appendName here
+ however we need a slightly different coding scheme than the normal
+ name encoding for field names
+ */
+ const OUString& rName = (m_aContext.Version > PDFWriter::PDFVersion::PDF_1_2) ? i_rControl.Name : i_rControl.Text;
+ OString aStr( OUStringToOString( rName, RTL_TEXTENCODING_UTF8 ) );
+ int nLen = aStr.getLength();
+
+ OStringBuffer aBuffer( rName.getLength()+64 );
+ for( int i = 0; i < nLen; i++ )
+ {
+ /* #i16920# PDF recommendation: output UTF8, any byte
+ * outside the interval [32(=ASCII' ');126(=ASCII'~')]
+ * should be escaped hexadecimal
+ */
+ if( aStr[i] >= 32 && aStr[i] <= 126 )
+ aBuffer.append( aStr[i] );
+ else
+ {
+ aBuffer.append( '#' );
+ appendHex( static_cast<sal_Int8>(aStr[i]), aBuffer );
+ }
+ }
+
+ OString aFullName( aBuffer.makeStringAndClear() );
+
+ /* #i82785# create hierarchical fields down to the for each dot in i_rName */
+ sal_Int32 nTokenIndex = 0, nLastTokenIndex = 0;
+ OString aPartialName;
+ OString aDomain;
+ do
+ {
+ nLastTokenIndex = nTokenIndex;
+ aPartialName = aFullName.getToken( 0, '.', nTokenIndex );
+ if( nTokenIndex != -1 )
+ {
+ // find or create a hierarchical field
+ // first find the fully qualified name up to this field
+ aDomain = aFullName.copy( 0, nTokenIndex-1 );
+ std::unordered_map< OString, sal_Int32 >::const_iterator it = m_aFieldNameMap.find( aDomain );
+ if( it == m_aFieldNameMap.end() )
+ {
+ // create new hierarchy field
+ sal_Int32 nNewWidget = m_aWidgets.size();
+ m_aWidgets.emplace_back( );
+ m_aWidgets[nNewWidget].m_nObject = createObject();
+ m_aWidgets[nNewWidget].m_eType = PDFWriter::Hierarchy;
+ m_aWidgets[nNewWidget].m_aName = aPartialName;
+ m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject;
+ m_aFieldNameMap[aDomain] = nNewWidget;
+ m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject;
+ if( nLastTokenIndex > 0 )
+ {
+ // this field is not a root field and
+ // needs to be inserted to its parent
+ OString aParentDomain( aDomain.copy( 0, nLastTokenIndex-1 ) );
+ it = m_aFieldNameMap.find( aParentDomain );
+ OSL_ENSURE( it != m_aFieldNameMap.end(), "field name not found" );
+ if( it != m_aFieldNameMap.end() )
+ {
+ OSL_ENSURE( it->second < sal_Int32(m_aWidgets.size()), "invalid field number entry" );
+ if( it->second < sal_Int32(m_aWidgets.size()) )
+ {
+ PDFWidget& rParentField( m_aWidgets[it->second] );
+ rParentField.m_aKids.push_back( m_aWidgets[nNewWidget].m_nObject );
+ rParentField.m_aKidsIndex.push_back( nNewWidget );
+ m_aWidgets[nNewWidget].m_nParent = rParentField.m_nObject;
+ }
+ }
+ }
+ }
+ else if( m_aWidgets[it->second].m_eType != PDFWriter::Hierarchy )
+ {
+ // this is invalid, someone tries to have a terminal field as parent
+ // example: a button with the name foo.bar exists and
+ // another button is named foo.bar.no
+ // workaround: put the second terminal field as much up in the hierarchy as
+ // necessary to have a non-terminal field as parent (or none at all)
+ // since it->second already is terminal, we just need to use its parent
+ aDomain.clear();
+ aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 );
+ if( nLastTokenIndex > 0 )
+ {
+ aDomain = aFullName.copy( 0, nLastTokenIndex-1 );
+ aFullName = aDomain + "." + aPartialName;
+ }
+ else
+ aFullName = aPartialName;
+ break;
+ }
+ }
+ } while( nTokenIndex != -1 );
+
+ // insert widget into its hierarchy field
+ if( !aDomain.isEmpty() )
+ {
+ std::unordered_map< OString, sal_Int32 >::const_iterator it = m_aFieldNameMap.find( aDomain );
+ if( it != m_aFieldNameMap.end() )
+ {
+ OSL_ENSURE( it->second >= 0 && it->second < sal_Int32( m_aWidgets.size() ), "invalid field index" );
+ if( it->second >= 0 && it->second < sal_Int32(m_aWidgets.size()) )
+ {
+ m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[it->second].m_nObject;
+ m_aWidgets[it->second].m_aKids.push_back( m_aWidgets[i_nWidgetIndex].m_nObject);
+ m_aWidgets[it->second].m_aKidsIndex.push_back( i_nWidgetIndex );
+ }
+ }
+ }
+
+ if( aPartialName.isEmpty() )
+ {
+ // how funny, an empty field name
+ if( i_rControl.getType() == PDFWriter::RadioButton )
+ {
+ aPartialName = "RadioGroup" +
+ OString::number( static_cast<const PDFWriter::RadioButtonWidget&>(i_rControl).RadioGroup );
+ }
+ else
+ aPartialName = OString( "Widget" );
+ }
+
+ if( ! m_aContext.AllowDuplicateFieldNames )
+ {
+ std::unordered_map<OString, sal_Int32>::iterator it = m_aFieldNameMap.find( aFullName );
+
+ if( it != m_aFieldNameMap.end() ) // not unique
+ {
+ std::unordered_map< OString, sal_Int32 >::const_iterator check_it;
+ OString aTry;
+ sal_Int32 nTry = 2;
+ do
+ {
+ OStringBuffer aUnique( aFullName.getLength() + 16 );
+ aUnique.append( aFullName );
+ aUnique.append( '_' );
+ aUnique.append( nTry++ );
+ aTry = aUnique.makeStringAndClear();
+ check_it = m_aFieldNameMap.find( aTry );
+ } while( check_it != m_aFieldNameMap.end() );
+ aFullName = aTry;
+ m_aFieldNameMap[ aFullName ] = i_nWidgetIndex;
+ aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 );
+ }
+ else
+ m_aFieldNameMap[ aFullName ] = i_nWidgetIndex;
+ }
+
+ // finally
+ m_aWidgets[i_nWidgetIndex].m_aName = aPartialName;
+}
+
+static 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
+static void appendDouble( double fValue, OStringBuffer& rBuffer, sal_Int32 nPrecision = 5 )
+{
+ 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 )
+ {
+ 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;
+ }
+ }
+}
+
+static void appendColor( const Color& rColor, OStringBuffer& rBuffer, bool bConvertToGrey )
+{
+
+ if( rColor != COL_TRANSPARENT )
+ {
+ 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 );
+ }
+ }
+}
+
+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" );
+ }
+}
+
+PDFPage::PDFPage( PDFWriterImpl* pWriter, double nPageWidth, double nPageHeight, PDFWriter::Orientation eOrientation )
+ :
+ m_pWriter( pWriter ),
+ m_nPageWidth( nPageWidth ),
+ m_nPageHeight( nPageHeight ),
+ m_nUserUnit( 1 ),
+ m_eOrientation( eOrientation ),
+ m_nPageObject( 0 ), // invalid object number
+ m_nStreamLengthObject( 0 ),
+ m_nBeginStreamPos( 0 ),
+ m_eTransition( PDFWriter::PageTransition::Regular ),
+ m_nTransTime( 0 )
+{
+ // object ref must be only ever updated in emit()
+ m_nPageObject = m_pWriter->createObject();
+
+ switch (m_pWriter->m_aContext.Version)
+ {
+ case PDFWriter::PDFVersion::PDF_1_6:
+ m_nUserUnit = std::ceil(std::max(nPageWidth, nPageHeight) / 14400.0);
+ break;
+ default:
+ // 1.2 -> 1.5
+ break;
+ }
+}
+
+void PDFPage::beginStream()
+{
+ if (g_bDebugDisableCompression)
+ {
+ m_pWriter->emitComment("PDFWriterImpl::PDFPage::beginStream, +");
+ }
+ m_aStreamObjects.push_back(m_pWriter->createObject());
+ if( ! m_pWriter->updateObject( m_aStreamObjects.back() ) )
+ return;
+
+ m_nStreamLengthObject = m_pWriter->createObject();
+ // write content stream header
+ OStringBuffer aLine;
+ aLine.append( m_aStreamObjects.back() );
+ aLine.append( " 0 obj\n<</Length " );
+ aLine.append( m_nStreamLengthObject );
+ aLine.append( " 0 R" );
+ if (!g_bDebugDisableCompression)
+ aLine.append( "/Filter/FlateDecode" );
+ aLine.append( ">>\nstream\n" );
+ if( ! m_pWriter->writeBuffer( aLine.getStr(), aLine.getLength() ) )
+ return;
+ if (osl::File::E_None != m_pWriter->m_aFile.getPos(m_nBeginStreamPos))
+ {
+ m_pWriter->m_aFile.close();
+ m_pWriter->m_bOpen = false;
+ }
+ if (!g_bDebugDisableCompression)
+ m_pWriter->beginCompression();
+ m_pWriter->checkAndEnableStreamEncryption( m_aStreamObjects.back() );
+}
+
+void PDFPage::endStream()
+{
+ if (!g_bDebugDisableCompression)
+ m_pWriter->endCompression();
+ sal_uInt64 nEndStreamPos;
+ if (osl::File::E_None != m_pWriter->m_aFile.getPos(nEndStreamPos))
+ {
+ m_pWriter->m_aFile.close();
+ m_pWriter->m_bOpen = false;
+ return;
+ }
+ m_pWriter->disableStreamEncryption();
+ if( ! m_pWriter->writeBuffer( "\nendstream\nendobj\n\n", 19 ) )
+ return;
+ // emit stream length object
+ if( ! m_pWriter->updateObject( m_nStreamLengthObject ) )
+ return;
+ OString aLine =
+ OString::number( m_nStreamLengthObject ) +
+ " 0 obj\n" +
+ OString::number( static_cast<sal_Int64>(nEndStreamPos-m_nBeginStreamPos) ) +
+ "\nendobj\n\n";
+ m_pWriter->writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+bool PDFPage::emit(sal_Int32 nParentObject )
+{
+ m_pWriter->MARK("PDFPage::emit");
+ // emit page object
+ if( ! m_pWriter->updateObject( m_nPageObject ) )
+ return false;
+ OStringBuffer aLine;
+
+ aLine.append( m_nPageObject );
+ aLine.append( " 0 obj\n"
+ "<</Type/Page/Parent " );
+ aLine.append( nParentObject );
+ aLine.append( " 0 R" );
+ aLine.append( "/Resources " );
+ aLine.append( m_pWriter->getResourceDictObj() );
+ aLine.append( " 0 R" );
+ if( m_nPageWidth && m_nPageHeight )
+ {
+ aLine.append( "/MediaBox[0 0 " );
+ aLine.append(m_nPageWidth / m_nUserUnit);
+ aLine.append( ' ' );
+ aLine.append(m_nPageHeight / m_nUserUnit);
+ aLine.append( "]" );
+ if (m_nUserUnit > 1)
+ {
+ aLine.append("\n/UserUnit ");
+ aLine.append(m_nUserUnit);
+ }
+ }
+ switch( m_eOrientation )
+ {
+ case PDFWriter::Orientation::Portrait: aLine.append( "/Rotate 0\n" );break;
+ case PDFWriter::Orientation::Inherit: break;
+ }
+ int nAnnots = m_aAnnotations.size();
+ if( nAnnots > 0 )
+ {
+ aLine.append( "/Annots[\n" );
+ for( int i = 0; i < nAnnots; i++ )
+ {
+ aLine.append( m_aAnnotations[i] );
+ aLine.append( " 0 R" );
+ aLine.append( ((i+1)%15) ? " " : "\n" );
+ }
+ aLine.append( "]\n" );
+ }
+ if( !m_aMCIDParents.empty() )
+ {
+ OStringBuffer aStructParents( 1024 );
+ aStructParents.append( "[ " );
+ int nParents = m_aMCIDParents.size();
+ for( int i = 0; i < nParents; i++ )
+ {
+ aStructParents.append( m_aMCIDParents[i] );
+ aStructParents.append( " 0 R" );
+ aStructParents.append( ((i%10) == 9) ? "\n" : " " );
+ }
+ aStructParents.append( "]" );
+ m_pWriter->m_aStructParentTree.push_back( aStructParents.makeStringAndClear() );
+
+ aLine.append( "/StructParents " );
+ aLine.append( sal_Int32(m_pWriter->m_aStructParentTree.size()-1) );
+ aLine.append( "\n" );
+ }
+ if( m_eTransition != PDFWriter::PageTransition::Regular && m_nTransTime > 0 )
+ {
+ // transition duration
+ aLine.append( "/Trans<</D " );
+ appendDouble( static_cast<double>(m_nTransTime)/1000.0, aLine, 3 );
+ aLine.append( "\n" );
+ const char *pStyle = nullptr, *pDm = nullptr, *pM = nullptr, *pDi = nullptr;
+ switch( m_eTransition )
+ {
+ case PDFWriter::PageTransition::SplitHorizontalInward:
+ pStyle = "Split"; pDm = "H"; pM = "I"; break;
+ case PDFWriter::PageTransition::SplitHorizontalOutward:
+ pStyle = "Split"; pDm = "H"; pM = "O"; break;
+ case PDFWriter::PageTransition::SplitVerticalInward:
+ pStyle = "Split"; pDm = "V"; pM = "I"; break;
+ case PDFWriter::PageTransition::SplitVerticalOutward:
+ pStyle = "Split"; pDm = "V"; pM = "O"; break;
+ case PDFWriter::PageTransition::BlindsHorizontal:
+ pStyle = "Blinds"; pDm = "H"; break;
+ case PDFWriter::PageTransition::BlindsVertical:
+ pStyle = "Blinds"; pDm = "V"; break;
+ case PDFWriter::PageTransition::BoxInward:
+ pStyle = "Box"; pM = "I"; break;
+ case PDFWriter::PageTransition::BoxOutward:
+ pStyle = "Box"; pM = "O"; break;
+ case PDFWriter::PageTransition::WipeLeftToRight:
+ pStyle = "Wipe"; pDi = "0"; break;
+ case PDFWriter::PageTransition::WipeBottomToTop:
+ pStyle = "Wipe"; pDi = "90"; break;
+ case PDFWriter::PageTransition::WipeRightToLeft:
+ pStyle = "Wipe"; pDi = "180"; break;
+ case PDFWriter::PageTransition::WipeTopToBottom:
+ pStyle = "Wipe"; pDi = "270"; break;
+ case PDFWriter::PageTransition::Dissolve:
+ pStyle = "Dissolve"; break;
+ case PDFWriter::PageTransition::Regular:
+ break;
+ }
+ // transition style
+ if( pStyle )
+ {
+ aLine.append( "/S/" );
+ aLine.append( pStyle );
+ aLine.append( "\n" );
+ }
+ if( pDm )
+ {
+ aLine.append( "/Dm/" );
+ aLine.append( pDm );
+ aLine.append( "\n" );
+ }
+ if( pM )
+ {
+ aLine.append( "/M/" );
+ aLine.append( pM );
+ aLine.append( "\n" );
+ }
+ if( pDi )
+ {
+ aLine.append( "/Di " );
+ aLine.append( pDi );
+ aLine.append( "\n" );
+ }
+ aLine.append( ">>\n" );
+ }
+ if( m_pWriter->getVersion() > PDFWriter::PDFVersion::PDF_1_3 && ! m_pWriter->m_bIsPDF_A1 )
+ {
+ aLine.append( "/Group<</S/Transparency/CS/DeviceRGB/I true>>" );
+ }
+ aLine.append( "/Contents" );
+ unsigned int nStreamObjects = m_aStreamObjects.size();
+ if( nStreamObjects > 1 )
+ aLine.append( '[' );
+ for(sal_Int32 i : m_aStreamObjects)
+ {
+ aLine.append( ' ' );
+ aLine.append( i );
+ aLine.append( " 0 R" );
+ }
+ if( nStreamObjects > 1 )
+ aLine.append( ']' );
+ aLine.append( ">>\nendobj\n\n" );
+ return m_pWriter->writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+namespace vcl
+{
+template < class GEOMETRY >
+static 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 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 )
+ {
+ 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 )
+ {
+ 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(long(rMatrix.get(4)), long(rMatrix.get(5))), rBuffer);
+}
+
+double PDFPage::getHeight() const
+{
+ double fRet = m_nPageHeight ? m_nPageHeight : vcl::pdf::g_nInheritedPageHeight;
+
+ if (m_nUserUnit > 1)
+ {
+ fRet /= m_nUserUnit;
+ }
+
+ return fRet;
+}
+
+PDFWriterImpl::PDFWriterImpl( const PDFWriter::PDFWriterContext& rContext,
+ const css::uno::Reference< css::beans::XMaterialHolder >& xEnc,
+ PDFWriter& i_rOuterFace)
+ : VirtualDevice(Application::GetDefaultDevice(), DeviceFormat::DEFAULT, DeviceFormat::NONE, OUTDEV_PDF),
+ m_aMapMode( MapUnit::MapPoint, Point(), Fraction( 1, pointToPixel(1) ), Fraction( 1, pointToPixel(1) ) ),
+ m_nCurrentStructElement( 0 ),
+ m_bEmitStructure( true ),
+ m_nNextFID( 1 ),
+ m_aPDFBmpCache(
+ officecfg::Office::Common::VCL::PDFExportImageCacheSize::get() ),
+ m_nCurrentPage( -1 ),
+ m_nCatalogObject(0),
+ m_nSignatureObject( -1 ),
+ m_nSignatureContentOffset( 0 ),
+ m_nSignatureLastByteRangeNoOffset( 0 ),
+ m_nResourceDict( -1 ),
+ m_nFontDictObject( -1 ),
+ m_aContext(rContext),
+ m_aFile(m_aContext.URL),
+ m_bOpen(false),
+ m_DocDigest(::comphelper::HashType::MD5),
+ m_aCipher( nullptr ),
+ m_nKeyLength(0),
+ m_nRC4KeyLength(0),
+ m_bEncryptThisStream( false ),
+ m_nAccessPermissions(0),
+ m_bIsPDF_A1( false ),
+ m_bIsPDF_A2( false ),
+ m_bIsPDF_UA( false ),
+ m_bIsPDF_A3( false ),
+ m_rOuterFace( i_rOuterFace )
+{
+ m_aStructure.emplace_back( );
+ m_aStructure[0].m_nOwnElement = 0;
+ m_aStructure[0].m_nParentElement = 0;
+
+ Font aFont;
+ aFont.SetFamilyName( "Times" );
+ aFont.SetFontSize( Size( 0, 12 ) );
+
+ GraphicsState aState;
+ aState.m_aMapMode = m_aMapMode;
+ aState.m_aFont = aFont;
+ m_aGraphicsStack.push_front( aState );
+
+ osl::File::RC aError = m_aFile.open(osl_File_OpenFlag_Write | osl_File_OpenFlag_Create);
+ if (aError != osl::File::E_None)
+ {
+ if (aError == osl::File::E_EXIST)
+ {
+ aError = m_aFile.open(osl_File_OpenFlag_Write);
+ if (aError == osl::File::E_None)
+ aError = m_aFile.setSize(0);
+ }
+ }
+ if (aError != osl::File::E_None)
+ return;
+
+ m_bOpen = true;
+
+ // setup DocInfo
+ setupDocInfo();
+
+ /* prepare the cypher engine, can be done in CTOR, free in DTOR */
+ m_aCipher = rtl_cipher_createARCFOUR( rtl_Cipher_ModeStream );
+
+ if( xEnc.is() )
+ prepareEncryption( xEnc );
+
+ if( m_aContext.Encryption.Encrypt() )
+ {
+ // sanity check
+ if( m_aContext.Encryption.OValue.size() != ENCRYPTED_PWD_SIZE ||
+ m_aContext.Encryption.UValue.size() != ENCRYPTED_PWD_SIZE ||
+ m_aContext.Encryption.EncryptionKey.size() != MAXIMUM_RC4_KEY_LENGTH
+ )
+ {
+ // the field lengths are invalid ? This was not setup by initEncryption.
+ // do not encrypt after all
+ m_aContext.Encryption.OValue.clear();
+ m_aContext.Encryption.UValue.clear();
+ OSL_ENSURE( false, "encryption data failed sanity check, encryption disabled" );
+ }
+ else // setup key lengths
+ m_nAccessPermissions = computeAccessPermissions( m_aContext.Encryption, m_nKeyLength, m_nRC4KeyLength );
+ }
+
+ // write header
+ OStringBuffer aBuffer( 20 );
+ aBuffer.append( "%PDF-" );
+ switch( m_aContext.Version )
+ {
+ case PDFWriter::PDFVersion::PDF_1_2: aBuffer.append( "1.2" );break;
+ case PDFWriter::PDFVersion::PDF_1_3: aBuffer.append( "1.3" );break;
+ case PDFWriter::PDFVersion::PDF_A_1:
+ case PDFWriter::PDFVersion::PDF_1_4: aBuffer.append( "1.4" );break;
+ case PDFWriter::PDFVersion::PDF_1_5: aBuffer.append( "1.5" );break;
+ default:
+ case PDFWriter::PDFVersion::PDF_1_6: aBuffer.append( "1.6" );break;
+ }
+ // append something binary as comment (suggested in PDF Reference)
+ aBuffer.append( "\n%\303\244\303\274\303\266\303\237\n" );
+ if( !writeBuffer( aBuffer.getStr(), aBuffer.getLength() ) )
+ {
+ m_aFile.close();
+ m_bOpen = false;
+ return;
+ }
+
+ // insert outline root
+ m_aOutline.emplace_back( );
+
+ m_bIsPDF_A1 = (m_aContext.Version == PDFWriter::PDFVersion::PDF_A_1);
+ if( m_bIsPDF_A1 )
+ m_aContext.Version = PDFWriter::PDFVersion::PDF_1_4; //meaning we need PDF 1.4, PDF/A flavour
+
+ m_bIsPDF_A2 = (m_aContext.Version == PDFWriter::PDFVersion::PDF_A_2);
+ if( m_bIsPDF_A2 )
+ m_aContext.Version = PDFWriter::PDFVersion::PDF_1_6; //we could even use 1.7 features
+
+ m_bIsPDF_A3 = (m_aContext.Version == PDFWriter::PDFVersion::PDF_A_3);
+ if( m_bIsPDF_A3 )
+ m_aContext.Version = PDFWriter::PDFVersion::PDF_1_6; //we could even use 1.7 features
+
+ if (m_aContext.UniversalAccessibilityCompliance)
+ {
+ m_bIsPDF_UA = true;
+ m_aContext.Tagged = true;
+ }
+
+ if( m_aContext.DPIx == 0 || m_aContext.DPIy == 0 )
+ SetReferenceDevice( VirtualDevice::RefDevMode::PDF1 );
+ else
+ SetReferenceDevice( m_aContext.DPIx, m_aContext.DPIy );
+
+ SetOutputSizePixel( Size( 640, 480 ) );
+ SetMapMode(MapMode(MapUnit::MapMM));
+}
+
+PDFWriterImpl::~PDFWriterImpl()
+{
+ disposeOnce();
+}
+
+void PDFWriterImpl::dispose()
+{
+ if( m_aCipher )
+ rtl_cipher_destroyARCFOUR( m_aCipher );
+ m_aPages.clear();
+ VirtualDevice::dispose();
+}
+
+void PDFWriterImpl::setupDocInfo()
+{
+ std::vector< sal_uInt8 > aId;
+ m_aCreationDateString = PDFWriter::GetDateTime();
+ computeDocumentIdentifier( aId, m_aContext.DocumentInfo, m_aCreationDateString, m_aCreationMetaDateString );
+ if( m_aContext.Encryption.DocumentIdentifier.empty() )
+ m_aContext.Encryption.DocumentIdentifier = aId;
+}
+
+OString PDFWriter::GetDateTime()
+{
+ OStringBuffer aRet;
+
+ TimeValue aTVal, aGMT;
+ oslDateTime aDT;
+ osl_getSystemTime(&aGMT);
+ osl_getLocalTimeFromSystemTime(&aGMT, &aTVal);
+ osl_getDateTimeFromTimeValue(&aTVal, &aDT);
+ aRet.append("D:");
+ aRet.append(static_cast<char>('0' + ((aDT.Year / 1000) % 10)));
+ aRet.append(static_cast<char>('0' + ((aDT.Year / 100) % 10)));
+ aRet.append(static_cast<char>('0' + ((aDT.Year / 10) % 10)));
+ aRet.append(static_cast<char>('0' + (aDT.Year % 10)));
+ aRet.append(static_cast<char>('0' + ((aDT.Month / 10) % 10)));
+ aRet.append(static_cast<char>('0' + (aDT.Month % 10)));
+ aRet.append(static_cast<char>('0' + ((aDT.Day / 10) % 10)));
+ aRet.append(static_cast<char>('0' + (aDT.Day % 10)));
+ aRet.append(static_cast<char>('0' + ((aDT.Hours / 10) % 10)));
+ aRet.append(static_cast<char>('0' + (aDT.Hours % 10)));
+ aRet.append(static_cast<char>('0' + ((aDT.Minutes / 10) % 10)));
+ aRet.append(static_cast<char>('0' + (aDT.Minutes % 10)));
+ aRet.append(static_cast<char>('0' + ((aDT.Seconds / 10) % 10)));
+ aRet.append(static_cast<char>('0' + (aDT.Seconds % 10)));
+
+ sal_uInt32 nDelta = 0;
+ if (aGMT.Seconds > aTVal.Seconds)
+ {
+ aRet.append("-");
+ nDelta = aGMT.Seconds-aTVal.Seconds;
+ }
+ else if (aGMT.Seconds < aTVal.Seconds)
+ {
+ aRet.append("+");
+ nDelta = aTVal.Seconds-aGMT.Seconds;
+ }
+ else
+ aRet.append("Z");
+
+ if (nDelta)
+ {
+ aRet.append(static_cast<char>('0' + ((nDelta / 36000) % 10)));
+ aRet.append(static_cast<char>('0' + ((nDelta / 3600) % 10)));
+ aRet.append("'");
+ aRet.append(static_cast<char>('0' + ((nDelta / 600) % 6)));
+ aRet.append(static_cast<char>('0' + ((nDelta / 60) % 10)));
+ }
+ aRet.append( "'" );
+
+ return aRet.makeStringAndClear();
+}
+
+void PDFWriterImpl::computeDocumentIdentifier( std::vector< sal_uInt8 >& o_rIdentifier,
+ const vcl::PDFWriter::PDFDocInfo& i_rDocInfo,
+ const OString& i_rCString1,
+ OString& o_rCString2
+ )
+{
+ o_rIdentifier.clear();
+
+ //build the document id
+ OString aInfoValuesOut;
+ OStringBuffer aID( 1024 );
+ if( !i_rDocInfo.Title.isEmpty() )
+ PDFWriter::AppendUnicodeTextString(i_rDocInfo.Title, aID);
+ if( !i_rDocInfo.Author.isEmpty() )
+ PDFWriter::AppendUnicodeTextString(i_rDocInfo.Author, aID);
+ if( !i_rDocInfo.Subject.isEmpty() )
+ PDFWriter::AppendUnicodeTextString(i_rDocInfo.Subject, aID);
+ if( !i_rDocInfo.Keywords.isEmpty() )
+ PDFWriter::AppendUnicodeTextString(i_rDocInfo.Keywords, aID);
+ if( !i_rDocInfo.Creator.isEmpty() )
+ PDFWriter::AppendUnicodeTextString(i_rDocInfo.Creator, aID);
+ if( !i_rDocInfo.Producer.isEmpty() )
+ PDFWriter::AppendUnicodeTextString(i_rDocInfo.Producer, aID);
+
+ TimeValue aTVal, aGMT;
+ oslDateTime aDT;
+ osl_getSystemTime( &aGMT );
+ osl_getLocalTimeFromSystemTime( &aGMT, &aTVal );
+ osl_getDateTimeFromTimeValue( &aTVal, &aDT );
+ OStringBuffer aCreationMetaDateString(64);
+
+ // i59651: we fill the Metadata date string as well, if PDF/A is requested
+ // according to ISO 19005-1:2005 6.7.3 the date is corrected for
+ // local time zone offset UTC only, whereas Acrobat 8 seems
+ // to use the localtime notation only
+ // according to a recommendation in XMP Specification (Jan 2004, page 75)
+ // the Acrobat way seems the right approach
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Year/1000)%10)) );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Year/100)%10)) );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Year/10)%10)) );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Year)%10)) );
+ aCreationMetaDateString.append( "-" );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Month/10)%10)) );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Month)%10)) );
+ aCreationMetaDateString.append( "-" );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Day/10)%10)) );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Day)%10)) );
+ aCreationMetaDateString.append( "T" );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Hours/10)%10)) );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Hours)%10)) );
+ aCreationMetaDateString.append( ":" );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Minutes/10)%10)) );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Minutes)%10)) );
+ aCreationMetaDateString.append( ":" );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Seconds/10)%10)) );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Seconds)%10)) );
+
+ sal_uInt32 nDelta = 0;
+ if( aGMT.Seconds > aTVal.Seconds )
+ {
+ nDelta = aGMT.Seconds-aTVal.Seconds;
+ aCreationMetaDateString.append( "-" );
+ }
+ else if( aGMT.Seconds < aTVal.Seconds )
+ {
+ nDelta = aTVal.Seconds-aGMT.Seconds;
+ aCreationMetaDateString.append( "+" );
+ }
+ else
+ {
+ aCreationMetaDateString.append( "Z" );
+
+ }
+ if( nDelta )
+ {
+ aCreationMetaDateString.append( static_cast<char>('0' + ((nDelta/36000)%10)) );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((nDelta/3600)%10)) );
+ aCreationMetaDateString.append( ":" );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((nDelta/600)%6)) );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((nDelta/60)%10)) );
+ }
+ aID.append( i_rCString1.getStr(), i_rCString1.getLength() );
+
+ aInfoValuesOut = aID.makeStringAndClear();
+ o_rCString2 = aCreationMetaDateString.makeStringAndClear();
+
+ ::comphelper::Hash aDigest(::comphelper::HashType::MD5);
+ aDigest.update(reinterpret_cast<unsigned char const*>(&aGMT), sizeof(aGMT));
+ aDigest.update(reinterpret_cast<unsigned char const*>(aInfoValuesOut.getStr()), aInfoValuesOut.getLength());
+ //the binary form of the doc id is needed for encryption stuff
+ o_rIdentifier = aDigest.finalize();
+}
+
+/* i12626 methods */
+/*
+check if the Unicode string must be encrypted or not, perform the requested task,
+append the string as unicode hex, encrypted if needed
+ */
+inline void PDFWriterImpl::appendUnicodeTextStringEncrypt( const OUString& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer )
+{
+ rOutBuffer.append( "<" );
+ if( m_aContext.Encryption.Encrypt() )
+ {
+ const sal_Unicode* pStr = rInString.getStr();
+ sal_Int32 nLen = rInString.getLength();
+ //prepare a unicode string, encrypt it
+ enableStringEncryption( nInObjectNumber );
+ sal_uInt8 *pCopy = m_vEncryptionBuffer.data();
+ sal_Int32 nChars = 2 + (nLen * 2);
+ m_vEncryptionBuffer.resize(nChars);
+ *pCopy++ = 0xFE;
+ *pCopy++ = 0xFF;
+ // we need to prepare a byte stream from the unicode string buffer
+ for( int i = 0; i < nLen; i++ )
+ {
+ sal_Unicode aUnChar = pStr[i];
+ *pCopy++ = static_cast<sal_uInt8>( aUnChar >> 8 );
+ *pCopy++ = static_cast<sal_uInt8>( aUnChar & 255 );
+ }
+ //encrypt in place
+ rtl_cipher_encodeARCFOUR( m_aCipher, m_vEncryptionBuffer.data(), nChars, m_vEncryptionBuffer.data(), nChars );
+ //now append, hexadecimal (appendHex), the encrypted result
+ for(int i = 0; i < nChars; i++)
+ appendHex( m_vEncryptionBuffer[i], rOutBuffer );
+ }
+ else
+ PDFWriter::AppendUnicodeTextString(rInString, rOutBuffer);
+ rOutBuffer.append( ">" );
+}
+
+inline void PDFWriterImpl::appendLiteralStringEncrypt( OStringBuffer const & rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer )
+{
+ rOutBuffer.append( "(" );
+ sal_Int32 nChars = rInString.getLength();
+ //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.getStr(), nChars, m_vEncryptionBuffer.data(), nChars );
+ appendLiteralString( reinterpret_cast<char*>(m_vEncryptionBuffer.data()), nChars, rOutBuffer );
+ }
+ else
+ appendLiteralString( rInString.getStr(), nChars , rOutBuffer );
+ rOutBuffer.append( ")" );
+}
+
+inline void PDFWriterImpl::appendLiteralStringEncrypt( const OString& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer )
+{
+ OStringBuffer aBufferString( rInString );
+ appendLiteralStringEncrypt( aBufferString, nInObjectNumber, rOutBuffer);
+}
+
+void PDFWriterImpl::appendLiteralStringEncrypt( const OUString& 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 = "% " + rtl::OStringView(pComment) + "\n";
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+bool PDFWriterImpl::compressStream( SvMemoryStream* pStream )
+{
+ if (!g_bDebugDisableCompression)
+ {
+ sal_uLong nEndPos = pStream->TellEnd();
+ pStream->Seek( STREAM_SEEK_TO_BEGIN );
+ ZCodec aCodec( 0x4000, 0x4000 );
+ SvMemoryStream aStream;
+ aCodec.BeginCompression();
+ aCodec.Write( aStream, static_cast<const sal_uInt8*>(pStream->GetData()), nEndPos );
+ aCodec.EndCompression();
+ nEndPos = aStream.Tell();
+ pStream->Seek( STREAM_SEEK_TO_BEGIN );
+ aStream.Seek( STREAM_SEEK_TO_BEGIN );
+ pStream->SetStreamSize( nEndPos );
+ pStream->WriteBytes( aStream.GetData(), nEndPos );
+ return true;
+ }
+ else
+ return false;
+}
+
+void PDFWriterImpl::beginCompression()
+{
+ if (!g_bDebugDisableCompression)
+ {
+ m_pCodec = std::make_unique<ZCodec>( 0x4000, 0x4000 );
+ m_pMemStream = std::make_unique<SvMemoryStream>();
+ m_pCodec->BeginCompression();
+ }
+}
+
+void PDFWriterImpl::endCompression()
+{
+ if (!g_bDebugDisableCompression && m_pCodec)
+ {
+ m_pCodec->EndCompression();
+ m_pCodec.reset();
+ sal_uInt64 nLen = m_pMemStream->Tell();
+ m_pMemStream->Seek( 0 );
+ writeBuffer( m_pMemStream->GetData(), nLen );
+ m_pMemStream.reset();
+ }
+}
+
+bool PDFWriterImpl::writeBuffer( const void* pBuffer, sal_uInt64 nBytes )
+{
+ if( ! m_bOpen ) // we are already down the drain
+ return false;
+
+ if( ! nBytes ) // huh ?
+ return true;
+
+ if( !m_aOutputStreams.empty() )
+ {
+ m_aOutputStreams.front().m_pStream->Seek( STREAM_SEEK_TO_END );
+ m_aOutputStreams.front().m_pStream->WriteBytes(
+ pBuffer, sal::static_int_cast<std::size_t>(nBytes));
+ return true;
+ }
+
+ sal_uInt64 nWritten;
+ if( m_pCodec )
+ {
+ m_pCodec->Write( *m_pMemStream, static_cast<const sal_uInt8*>(pBuffer), static_cast<sal_uLong>(nBytes) );
+ nWritten = nBytes;
+ }
+ else
+ {
+ bool buffOK = true;
+ if( m_bEncryptThisStream )
+ {
+ /* implement the encryption part of the PDF spec encryption algorithm 3.1 */
+ m_vEncryptionBuffer.resize(nBytes);
+ if( buffOK )
+ rtl_cipher_encodeARCFOUR( m_aCipher,
+ pBuffer, static_cast<sal_Size>(nBytes),
+ m_vEncryptionBuffer.data(), static_cast<sal_Size>(nBytes) );
+ }
+
+ const void* pWriteBuffer = ( m_bEncryptThisStream && buffOK ) ? m_vEncryptionBuffer.data() : pBuffer;
+ m_DocDigest.update(static_cast<unsigned char const*>(pWriteBuffer), static_cast<sal_uInt32>(nBytes));
+
+ if (m_aFile.write(pWriteBuffer, nBytes, nWritten) != osl::File::E_None)
+ nWritten = 0;
+
+ if( nWritten != nBytes )
+ {
+ m_aFile.close();
+ m_bOpen = false;
+ }
+ }
+
+ return nWritten == nBytes;
+}
+
+void PDFWriterImpl::newPage( double nPageWidth, double nPageHeight, PDFWriter::Orientation eOrientation )
+{
+ endPage();
+ m_nCurrentPage = m_aPages.size();
+ m_aPages.emplace_back(this, nPageWidth, nPageHeight, eOrientation );
+
+ sal_Int32 nUserUnit = m_aPages.back().m_nUserUnit;
+ if (nUserUnit > 1)
+ {
+ m_aMapMode = MapMode(MapUnit::MapPoint, Point(), Fraction(nUserUnit, pointToPixel(1)),
+ Fraction(nUserUnit, pointToPixel(1)));
+ }
+
+ m_aPages.back().beginStream();
+
+ // setup global graphics state
+ // linewidth is "1 pixel" by default
+ OStringBuffer aBuf( 16 );
+ appendDouble( 72.0/double(GetDPIX()), aBuf );
+ aBuf.append( " w\n" );
+ writeBuffer( aBuf.getStr(), aBuf.getLength() );
+}
+
+void PDFWriterImpl::endPage()
+{
+ if( m_aPages.empty() )
+ return;
+
+ // close eventual MC sequence
+ endStructureElementMCSeq();
+
+ // sanity check
+ if( !m_aOutputStreams.empty() )
+ {
+ OSL_FAIL( "redirection across pages !!!" );
+ m_aOutputStreams.clear(); // leak !
+ m_aMapMode.SetOrigin( Point() );
+ }
+
+ m_aGraphicsStack.clear();
+ m_aGraphicsStack.emplace_back( );
+
+ // this should pop the PDF graphics stack if necessary
+ updateGraphicsState();
+
+ m_aPages.back().endStream();
+
+ // reset the default font
+ Font aFont;
+ aFont.SetFamilyName( "Times" );
+ aFont.SetFontSize( Size( 0, 12 ) );
+
+ m_aCurrentPDFState = m_aGraphicsStack.front();
+ m_aGraphicsStack.front().m_aFont = aFont;
+
+ for (auto & bitmap : m_aBitmaps)
+ {
+ if( ! bitmap.m_aBitmap.IsEmpty() )
+ {
+ writeBitmapObject(bitmap);
+ bitmap.m_aBitmap = BitmapEx();
+ }
+ }
+ for (auto & jpeg : m_aJPGs)
+ {
+ if( jpeg.m_pStream )
+ {
+ writeJPG( jpeg );
+ jpeg.m_pStream.reset();
+ jpeg.m_aMask = Bitmap();
+ }
+ }
+ for (auto & item : m_aTransparentObjects)
+ {
+ if( item.m_pContentStream )
+ {
+ writeTransparentObject(item);
+ item.m_pContentStream.reset();
+ }
+ }
+
+}
+
+sal_Int32 PDFWriterImpl::createObject()
+{
+ m_aObjects.push_back( ~0U );
+ return m_aObjects.size();
+}
+
+bool PDFWriterImpl::updateObject( sal_Int32 n )
+{
+ if( ! m_bOpen )
+ return false;
+
+ sal_uInt64 nOffset = ~0U;
+ osl::File::RC aError = m_aFile.getPos(nOffset);
+ SAL_WARN_IF( aError != osl::File::E_None, "vcl.pdfwriter", "could not register object" );
+ if (aError != osl::File::E_None)
+ {
+ m_aFile.close();
+ m_bOpen = false;
+ }
+ m_aObjects[ n-1 ] = nOffset;
+ return aError == osl::File::E_None;
+}
+
+#define CHECK_RETURN( x ) if( !(x) ) return 0
+#define CHECK_RETURN2( x ) if( !(x) ) return
+
+sal_Int32 PDFWriterImpl::emitStructParentTree( sal_Int32 nObject )
+{
+ if( nObject > 0 )
+ {
+ OStringBuffer aLine( 1024 );
+
+ aLine.append( nObject );
+ aLine.append( " 0 obj\n"
+ "<</Nums[\n" );
+ sal_Int32 nTreeItems = m_aStructParentTree.size();
+ for( sal_Int32 n = 0; n < nTreeItems; n++ )
+ {
+ aLine.append( n );
+ aLine.append( ' ' );
+ aLine.append( m_aStructParentTree[n] );
+ aLine.append( "\n" );
+ }
+ aLine.append( "]>>\nendobj\n\n" );
+ CHECK_RETURN( updateObject( nObject ) );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+ }
+ return nObject;
+}
+
+const char* PDFWriterImpl::getAttributeTag( PDFWriter::StructAttribute eAttr )
+{
+ static std::map< PDFWriter::StructAttribute, const char* > aAttributeStrings;
+ // fill maps once
+ if( aAttributeStrings.empty() )
+ {
+ aAttributeStrings[ PDFWriter::Placement ] = "Placement";
+ aAttributeStrings[ PDFWriter::WritingMode ] = "WritingMode";
+ aAttributeStrings[ PDFWriter::SpaceBefore ] = "SpaceBefore";
+ aAttributeStrings[ PDFWriter::SpaceAfter ] = "SpaceAfter";
+ aAttributeStrings[ PDFWriter::StartIndent ] = "StartIndent";
+ aAttributeStrings[ PDFWriter::EndIndent ] = "EndIndent";
+ aAttributeStrings[ PDFWriter::TextIndent ] = "TextIndent";
+ aAttributeStrings[ PDFWriter::TextAlign ] = "TextAlign";
+ aAttributeStrings[ PDFWriter::Width ] = "Width";
+ aAttributeStrings[ PDFWriter::Height ] = "Height";
+ aAttributeStrings[ PDFWriter::BlockAlign ] = "BlockAlign";
+ aAttributeStrings[ PDFWriter::InlineAlign ] = "InlineAlign";
+ aAttributeStrings[ PDFWriter::LineHeight ] = "LineHeight";
+ aAttributeStrings[ PDFWriter::BaselineShift ] = "BaselineShift";
+ aAttributeStrings[ PDFWriter::TextDecorationType ] = "TextDecorationType";
+ aAttributeStrings[ PDFWriter::ListNumbering ] = "ListNumbering";
+ aAttributeStrings[ PDFWriter::RowSpan ] = "RowSpan";
+ aAttributeStrings[ PDFWriter::ColSpan ] = "ColSpan";
+ aAttributeStrings[ PDFWriter::LinkAnnotation ] = "LinkAnnotation";
+ }
+
+ std::map< PDFWriter::StructAttribute, const char* >::const_iterator it =
+ aAttributeStrings.find( eAttr );
+
+ if( it == aAttributeStrings.end() )
+ SAL_INFO("vcl.pdfwriter", "invalid PDFWriter::StructAttribute " << eAttr);
+
+ return it != aAttributeStrings.end() ? it->second : "";
+}
+
+const char* PDFWriterImpl::getAttributeValueTag( PDFWriter::StructAttributeValue eVal )
+{
+ static std::map< PDFWriter::StructAttributeValue, const char* > aValueStrings;
+
+ if( aValueStrings.empty() )
+ {
+ aValueStrings[ PDFWriter::NONE ] = "None";
+ aValueStrings[ PDFWriter::Block ] = "Block";
+ aValueStrings[ PDFWriter::Inline ] = "Inline";
+ aValueStrings[ PDFWriter::Before ] = "Before";
+ aValueStrings[ PDFWriter::After ] = "After";
+ aValueStrings[ PDFWriter::Start ] = "Start";
+ aValueStrings[ PDFWriter::End ] = "End";
+ aValueStrings[ PDFWriter::LrTb ] = "LrTb";
+ aValueStrings[ PDFWriter::RlTb ] = "RlTb";
+ aValueStrings[ PDFWriter::TbRl ] = "TbRl";
+ aValueStrings[ PDFWriter::Center ] = "Center";
+ aValueStrings[ PDFWriter::Justify ] = "Justify";
+ aValueStrings[ PDFWriter::Auto ] = "Auto";
+ aValueStrings[ PDFWriter::Middle ] = "Middle";
+ aValueStrings[ PDFWriter::Normal ] = "Normal";
+ aValueStrings[ PDFWriter::Underline ] = "Underline";
+ aValueStrings[ PDFWriter::Overline ] = "Overline";
+ aValueStrings[ PDFWriter::LineThrough ] = "LineThrough";
+ aValueStrings[ PDFWriter::Disc ] = "Disc";
+ aValueStrings[ PDFWriter::Circle ] = "Circle";
+ aValueStrings[ PDFWriter::Square ] = "Square";
+ aValueStrings[ PDFWriter::Decimal ] = "Decimal";
+ aValueStrings[ PDFWriter::UpperRoman ] = "UpperRoman";
+ aValueStrings[ PDFWriter::LowerRoman ] = "LowerRoman";
+ aValueStrings[ PDFWriter::UpperAlpha ] = "UpperAlpha";
+ aValueStrings[ PDFWriter::LowerAlpha ] = "LowerAlpha";
+ }
+
+ std::map< PDFWriter::StructAttributeValue, const char* >::const_iterator it =
+ aValueStrings.find( eVal );
+
+ if( it == aValueStrings.end() )
+ SAL_INFO("vcl.pdfwriter", "invalid PDFWriter::StructAttributeValue " << eVal);
+
+ return it != aValueStrings.end() ? it->second : "";
+}
+
+static void appendStructureAttributeLine( PDFWriter::StructAttribute i_eAttr, const PDFStructureAttribute& i_rVal, OStringBuffer& o_rLine, bool i_bIsFixedInt )
+{
+ o_rLine.append( "/" );
+ o_rLine.append( PDFWriterImpl::getAttributeTag( i_eAttr ) );
+
+ if( i_rVal.eValue != PDFWriter::Invalid )
+ {
+ o_rLine.append( "/" );
+ o_rLine.append( PDFWriterImpl::getAttributeValueTag( i_rVal.eValue ) );
+ }
+ else
+ {
+ // numerical value
+ o_rLine.append( " " );
+ if( i_bIsFixedInt )
+ appendFixedInt( i_rVal.nValue, o_rLine );
+ else
+ o_rLine.append( i_rVal.nValue );
+ }
+ o_rLine.append( "\n" );
+}
+
+OString PDFWriterImpl::emitStructureAttributes( PDFStructureElement& i_rEle )
+{
+ // create layout, list and table attribute sets
+ OStringBuffer aLayout(256), aList(64), aTable(64);
+ for (auto const& attribute : i_rEle.m_aAttributes)
+ {
+ if( attribute.first == PDFWriter::ListNumbering )
+ appendStructureAttributeLine( attribute.first, attribute.second, aList, true );
+ else if( attribute.first == PDFWriter::RowSpan ||
+ attribute.first == PDFWriter::ColSpan )
+ 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 && nLink < static_cast<sal_Int32>(m_aLinks.size()) )
+ {
+ // update struct parent of link
+ OString aStructParentEntry =
+ OString::number( i_rEle.m_nObject ) +
+ " 0 R";
+ m_aStructParentTree.push_back( aStructParentEntry );
+ m_aLinks[ nLink ].m_nStructParent = m_aStructParentTree.size()-1;
+
+ sal_Int32 nRefObject = createObject();
+ if (updateObject(nRefObject))
+ {
+ OString aRef =
+ OString::number( nRefObject ) +
+ " 0 obj\n"
+ "<</Type/OBJR/Obj " +
+ OString::number( m_aLinks[ nLink ].m_nObject ) +
+ " 0 R>>\n"
+ "endobj\n\n";
+ writeBuffer( aRef.getStr(), aRef.getLength() );
+ }
+
+ i_rEle.m_aKids.emplace_back( nRefObject );
+ }
+ else
+ {
+ OSL_FAIL( "unresolved link id for Link structure" );
+ SAL_INFO("vcl.pdfwriter", "unresolved link id " << nLink << " for Link structure");
+ if (g_bDebugDisableCompression)
+ {
+ OString aLine = "unresolved link id " +
+ OString::number( nLink ) +
+ " for Link structure";
+ emitComment( aLine.getStr() );
+ }
+ }
+ }
+ else
+ appendStructureAttributeLine( attribute.first, attribute.second, aLayout, true );
+ }
+ if( ! i_rEle.m_aBBox.IsEmpty() )
+ {
+ aLayout.append( "/BBox[" );
+ appendFixedInt( i_rEle.m_aBBox.Left(), aLayout );
+ aLayout.append( " " );
+ appendFixedInt( i_rEle.m_aBBox.Top(), aLayout );
+ aLayout.append( " " );
+ appendFixedInt( i_rEle.m_aBBox.Right(), aLayout );
+ aLayout.append( " " );
+ appendFixedInt( i_rEle.m_aBBox.Bottom(), aLayout );
+ aLayout.append( "]\n" );
+ }
+
+ std::vector< sal_Int32 > aAttribObjects;
+ if( !aLayout.isEmpty() )
+ {
+ aAttribObjects.push_back( createObject() );
+ if (updateObject( aAttribObjects.back() ))
+ {
+ OStringBuffer aObj( 64 );
+ aObj.append( aAttribObjects.back() );
+ aObj.append( " 0 obj\n"
+ "<</O/Layout\n" );
+ aLayout.append( ">>\nendobj\n\n" );
+ writeBuffer( aObj.getStr(), aObj.getLength() );
+ writeBuffer( aLayout.getStr(), aLayout.getLength() );
+ }
+ }
+ if( !aList.isEmpty() )
+ {
+ aAttribObjects.push_back( createObject() );
+ if (updateObject( aAttribObjects.back() ))
+ {
+ OStringBuffer aObj( 64 );
+ aObj.append( aAttribObjects.back() );
+ aObj.append( " 0 obj\n"
+ "<</O/List\n" );
+ aList.append( ">>\nendobj\n\n" );
+ writeBuffer( aObj.getStr(), aObj.getLength() );
+ writeBuffer( aList.getStr(), aList.getLength() );
+ }
+ }
+ if( !aTable.isEmpty() )
+ {
+ aAttribObjects.push_back( createObject() );
+ if (updateObject( aAttribObjects.back() ))
+ {
+ OStringBuffer aObj( 64 );
+ aObj.append( aAttribObjects.back() );
+ aObj.append( " 0 obj\n"
+ "<</O/Table\n" );
+ aTable.append( ">>\nendobj\n\n" );
+ writeBuffer( aObj.getStr(), aObj.getLength() );
+ writeBuffer( aTable.getStr(), aTable.getLength() );
+ }
+ }
+
+ OStringBuffer aRet( 64 );
+ if( aAttribObjects.size() > 1 )
+ aRet.append( " [" );
+ for (auto const& attrib : aAttribObjects)
+ {
+ aRet.append( " " );
+ aRet.append( attrib );
+ aRet.append( " 0 R" );
+ }
+ if( aAttribObjects.size() > 1 )
+ aRet.append( " ]" );
+ return aRet.makeStringAndClear();
+}
+
+sal_Int32 PDFWriterImpl::emitStructure( PDFStructureElement& rEle )
+{
+ if(
+ // do not emit NonStruct and its children
+ rEle.m_eType == PDFWriter::NonStructElement &&
+ rEle.m_nOwnElement != rEle.m_nParentElement // but of course emit the struct tree root
+ )
+ return 0;
+
+ for (auto const& child : rEle.m_aChildren)
+ {
+ if( child > 0 && child < sal_Int32(m_aStructure.size()) )
+ {
+ PDFStructureElement& rChild = m_aStructure[ child ];
+ if( rChild.m_eType != PDFWriter::NonStructElement )
+ {
+ if( rChild.m_nParentElement == rEle.m_nOwnElement )
+ emitStructure( rChild );
+ else
+ {
+ OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure element" );
+ SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::emitStructure: invalid child structure element with id " << child);
+ }
+ }
+ }
+ else
+ {
+ OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure id" );
+ SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::emitStructure: invalid child structure id " << child);
+ }
+ }
+
+ OStringBuffer aLine( 512 );
+ aLine.append( rEle.m_nObject );
+ aLine.append( " 0 obj\n"
+ "<</Type" );
+ sal_Int32 nParentTree = -1;
+ if( rEle.m_nOwnElement == rEle.m_nParentElement )
+ {
+ nParentTree = createObject();
+ CHECK_RETURN( nParentTree );
+ aLine.append( "/StructTreeRoot\n" );
+ aLine.append( "/ParentTree " );
+ aLine.append( nParentTree );
+ aLine.append( " 0 R\n" );
+ if( ! m_aRoleMap.empty() )
+ {
+ aLine.append( "/RoleMap<<" );
+ for (auto const& role : m_aRoleMap)
+ {
+ aLine.append( '/' );
+ aLine.append(role.first);
+ aLine.append( '/' );
+ aLine.append( role.second );
+ aLine.append( '\n' );
+ }
+ aLine.append( ">>\n" );
+ }
+ }
+ else
+ {
+ aLine.append( "/StructElem\n"
+ "/S/" );
+ if( !rEle.m_aAlias.isEmpty() )
+ aLine.append( rEle.m_aAlias );
+ else
+ aLine.append( getStructureTag( rEle.m_eType ) );
+ aLine.append( "\n"
+ "/P " );
+ aLine.append( m_aStructure[ rEle.m_nParentElement ].m_nObject );
+ aLine.append( " 0 R\n"
+ "/Pg " );
+ aLine.append( rEle.m_nFirstPageObject );
+ aLine.append( " 0 R\n" );
+ if( !rEle.m_aActualText.isEmpty() )
+ {
+ aLine.append( "/ActualText" );
+ appendUnicodeTextStringEncrypt( rEle.m_aActualText, rEle.m_nObject, aLine );
+ aLine.append( "\n" );
+ }
+ if( !rEle.m_aAltText.isEmpty() )
+ {
+ aLine.append( "/Alt" );
+ appendUnicodeTextStringEncrypt( rEle.m_aAltText, rEle.m_nObject, aLine );
+ aLine.append( "\n" );
+ }
+ }
+ if( (! rEle.m_aBBox.IsEmpty()) || (! rEle.m_aAttributes.empty()) )
+ {
+ OString aAttribs = emitStructureAttributes( rEle );
+ if( !aAttribs.isEmpty() )
+ {
+ aLine.append( "/A" );
+ aLine.append( aAttribs );
+ aLine.append( "\n" );
+ }
+ }
+ if( !rEle.m_aLocale.Language.isEmpty() )
+ {
+ /* PDF allows only RFC 3066, which is only partly BCP 47 and does not
+ * include script tags and others.
+ * http://pdf.editme.com/pdfua-naturalLanguageSpecification
+ * http://partners.adobe.com/public/developer/en/pdf/PDFReference16.pdf#page=886
+ * https://www.adobe.com/content/dam/Adobe/en/devnet/pdf/pdfs/PDF32000_2008.pdf#M13.9.19332.1Heading.97.Natural.Language.Specification
+ * */
+ LanguageTag aLanguageTag( rEle.m_aLocale);
+ OUString aLanguage, aScript, aCountry;
+ aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, aCountry);
+ if (!aLanguage.isEmpty())
+ {
+ OUStringBuffer aLocBuf( 16 );
+ aLocBuf.append( aLanguage );
+ if( !aCountry.isEmpty() )
+ {
+ aLocBuf.append( '-' );
+ aLocBuf.append( aCountry );
+ }
+ aLine.append( "/Lang" );
+ appendLiteralStringEncrypt( aLocBuf.makeStringAndClear(), rEle.m_nObject, aLine );
+ aLine.append( "\n" );
+ }
+ }
+ if( ! rEle.m_aKids.empty() )
+ {
+ unsigned int i = 0;
+ aLine.append( "/K[" );
+ for (auto const& kid : rEle.m_aKids)
+ {
+ if( kid.nMCID == -1 )
+ {
+ aLine.append( kid.nObject );
+ aLine.append( " 0 R" );
+ aLine.append( ( (i & 15) == 15 ) ? "\n" : " " );
+ }
+ else
+ {
+ if( kid.nObject == rEle.m_nFirstPageObject )
+ {
+ aLine.append( kid.nMCID );
+ aLine.append( " " );
+ }
+ else
+ {
+ aLine.append( "<</Type/MCR/Pg " );
+ aLine.append( kid.nObject );
+ aLine.append( " 0 R /MCID " );
+ aLine.append( kid.nMCID );
+ aLine.append( ">>\n" );
+ }
+ }
+ ++i;
+ }
+ aLine.append( "]\n" );
+ }
+ aLine.append( ">>\nendobj\n\n" );
+
+ CHECK_RETURN( updateObject( rEle.m_nObject ) );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+
+ CHECK_RETURN( emitStructParentTree( nParentTree ) );
+
+ return rEle.m_nObject;
+}
+
+bool PDFWriterImpl::emitGradients()
+{
+ for (auto const& gradient : m_aGradients)
+ {
+ if ( !writeGradientFunction( gradient ) ) return false;
+ }
+ return true;
+}
+
+bool PDFWriterImpl::emitTilings()
+{
+ OStringBuffer aTilingObj( 1024 );
+
+ for (auto & tiling : m_aTilings)
+ {
+ SAL_WARN_IF( !tiling.m_pTilingStream, "vcl.pdfwriter", "tiling without stream" );
+ if( ! tiling.m_pTilingStream )
+ continue;
+
+ aTilingObj.setLength( 0 );
+
+ if (g_bDebugDisableCompression)
+ {
+ emitComment( "PDFWriterImpl::emitTilings" );
+ }
+
+ sal_Int32 nX = static_cast<sal_Int32>(tiling.m_aRectangle.Left());
+ sal_Int32 nY = static_cast<sal_Int32>(tiling.m_aRectangle.Top());
+ sal_Int32 nW = static_cast<sal_Int32>(tiling.m_aRectangle.GetWidth());
+ sal_Int32 nH = static_cast<sal_Int32>(tiling.m_aRectangle.GetHeight());
+ if( tiling.m_aCellSize.Width() == 0 )
+ tiling.m_aCellSize.setWidth( nW );
+ if( tiling.m_aCellSize.Height() == 0 )
+ tiling.m_aCellSize.setHeight( nH );
+
+ bool bDeflate = compressStream( tiling.m_pTilingStream.get() );
+ sal_uInt64 const nTilingStreamSize = tiling.m_pTilingStream->TellEnd();
+ tiling.m_pTilingStream->Seek( STREAM_SEEK_TO_BEGIN );
+
+ // write pattern object
+ aTilingObj.append( tiling.m_nObject );
+ aTilingObj.append( " 0 obj\n" );
+ aTilingObj.append( "<</Type/Pattern/PatternType 1\n"
+ "/PaintType 1\n"
+ "/TilingType 2\n"
+ "/BBox[" );
+ appendFixedInt( nX, aTilingObj );
+ aTilingObj.append( ' ' );
+ appendFixedInt( nY, aTilingObj );
+ aTilingObj.append( ' ' );
+ appendFixedInt( nX+nW, aTilingObj );
+ aTilingObj.append( ' ' );
+ appendFixedInt( nY+nH, aTilingObj );
+ aTilingObj.append( "]\n"
+ "/XStep " );
+ appendFixedInt( tiling.m_aCellSize.Width(), aTilingObj );
+ aTilingObj.append( "\n"
+ "/YStep " );
+ appendFixedInt( tiling.m_aCellSize.Height(), aTilingObj );
+ aTilingObj.append( "\n" );
+ if( tiling.m_aTransform.matrix[0] != 1.0 ||
+ tiling.m_aTransform.matrix[1] != 0.0 ||
+ tiling.m_aTransform.matrix[3] != 0.0 ||
+ tiling.m_aTransform.matrix[4] != 1.0 ||
+ tiling.m_aTransform.matrix[2] != 0.0 ||
+ tiling.m_aTransform.matrix[5] != 0.0 )
+ {
+ aTilingObj.append( "/Matrix [" );
+ // TODO: scaling, mirroring on y, etc
+ appendDouble( tiling.m_aTransform.matrix[0], aTilingObj );
+ aTilingObj.append( ' ' );
+ appendDouble( tiling.m_aTransform.matrix[1], aTilingObj );
+ aTilingObj.append( ' ' );
+ appendDouble( tiling.m_aTransform.matrix[3], aTilingObj );
+ aTilingObj.append( ' ' );
+ appendDouble( tiling.m_aTransform.matrix[4], aTilingObj );
+ aTilingObj.append( ' ' );
+ appendDouble( tiling.m_aTransform.matrix[2], aTilingObj );
+ aTilingObj.append( ' ' );
+ appendDouble( tiling.m_aTransform.matrix[5], aTilingObj );
+ aTilingObj.append( "]\n" );
+ }
+ aTilingObj.append( "/Resources" );
+ tiling.m_aResources.append( aTilingObj, getFontDictObject() );
+ if( bDeflate )
+ aTilingObj.append( "/Filter/FlateDecode" );
+ aTilingObj.append( "/Length " );
+ aTilingObj.append( static_cast<sal_Int32>(nTilingStreamSize) );
+ aTilingObj.append( ">>\nstream\n" );
+ if ( !updateObject( tiling.m_nObject ) ) return false;
+ if ( !writeBuffer( aTilingObj.getStr(), aTilingObj.getLength() ) ) return false;
+ checkAndEnableStreamEncryption( tiling.m_nObject );
+ bool written = writeBuffer( tiling.m_pTilingStream->GetData(), nTilingStreamSize );
+ tiling.m_pTilingStream.reset();
+ if( !written )
+ return false;
+ disableStreamEncryption();
+ aTilingObj.setLength( 0 );
+ aTilingObj.append( "\nendstream\nendobj\n\n" );
+ if ( !writeBuffer( aTilingObj.getStr(), aTilingObj.getLength() ) ) return false;
+ }
+ return true;
+}
+
+sal_Int32 PDFWriterImpl::emitBuildinFont(const pdf::BuildinFontFace* pFD, sal_Int32 nFontObject)
+{
+ if( !pFD )
+ return 0;
+ const pdf::BuildinFont& rBuildinFont = pFD->GetBuildinFont();
+
+ OStringBuffer aLine( 1024 );
+
+ if( nFontObject <= 0 )
+ nFontObject = createObject();
+ CHECK_RETURN( updateObject( nFontObject ) );
+ aLine.append( nFontObject );
+ aLine.append( " 0 obj\n"
+ "<</Type/Font/Subtype/Type1/BaseFont/" );
+ appendName( rBuildinFont.m_pPSName, aLine );
+ aLine.append( "\n" );
+ if( rBuildinFont.m_eCharSet == RTL_TEXTENCODING_MS_1252 )
+ aLine.append( "/Encoding/WinAnsiEncoding\n" );
+ aLine.append( ">>\nendobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+ return nFontObject;
+}
+
+std::map< sal_Int32, sal_Int32 > PDFWriterImpl::emitSystemFont( const PhysicalFontFace* pFont, EmbedFont const & rEmbed )
+{
+ std::map< sal_Int32, sal_Int32 > aRet;
+
+ sal_Int32 nFontDescriptor = 0;
+ OString aSubType( "/Type1" );
+ FontSubsetInfo aInfo;
+ // fill in dummy values
+ aInfo.m_nAscent = 1000;
+ aInfo.m_nDescent = 200;
+ aInfo.m_nCapHeight = 1000;
+ aInfo.m_aFontBBox = tools::Rectangle( Point( -200, -200 ), Size( 1700, 1700 ) );
+ aInfo.m_aPSName = pFont->GetFamilyName();
+ sal_Int32 pWidths[256] = {};
+
+ SalGraphics *pGraphics = GetGraphics();
+
+ assert(pGraphics);
+
+ aSubType = OString( "/TrueType" );
+ std::vector< sal_Int32 > aGlyphWidths;
+ Ucs2UIntMap aUnicodeMap;
+ pGraphics->GetGlyphWidths( pFont, false, aGlyphWidths, aUnicodeMap );
+
+ OUString aTmpName;
+ osl_createTempFile( nullptr, nullptr, &aTmpName.pData );
+ sal_GlyphId aGlyphIds[ 256 ] = {};
+ sal_uInt8 pEncoding[ 256 ] = {};
+ sal_Int32 pDuWidths[ 256 ] = {};
+
+ for( sal_Ucs c = 32; c < 256; c++ )
+ {
+ pEncoding[c] = c;
+ aGlyphIds[c] = 0;
+ if( aUnicodeMap.find( c ) != aUnicodeMap.end() )
+ pWidths[ c ] = aGlyphWidths[ aUnicodeMap[ c ] ];
+ }
+ //TODO: surely this is utterly broken because aGlyphIds is just all zeros, if we
+ //had the right glyphids here then I imagine we could replace pDuWidths with
+ //pWidths and remove pWidths assignment above. i.e. start with the glyph ids
+ //and map those to unicode rather than try and reverse map them ?
+ pGraphics->CreateFontSubset( aTmpName, pFont, aGlyphIds, pEncoding, pDuWidths, 256, aInfo );
+ osl_removeFile( aTmpName.pData );
+
+ // write font descriptor
+ nFontDescriptor = emitFontDescriptor( pFont, aInfo, 0, 0 );
+ if( nFontDescriptor )
+ {
+ // write font object
+ sal_Int32 nObject = createObject();
+ if( updateObject( nObject ) )
+ {
+ OStringBuffer aLine( 1024 );
+ aLine.append( nObject );
+ aLine.append( " 0 obj\n"
+ "<</Type/Font/Subtype" );
+ aLine.append( aSubType );
+ aLine.append( "/BaseFont/" );
+ appendName( aInfo.m_aPSName, aLine );
+ aLine.append( "\n" );
+ if( !pFont->IsSymbolFont() )
+ aLine.append( "/Encoding/WinAnsiEncoding\n" );
+ aLine.append( "/FirstChar 32 /LastChar 255\n"
+ "/Widths[" );
+ for( int i = 32; i < 256; i++ )
+ {
+ aLine.append( pWidths[i] );
+ aLine.append( ((i&15) == 15) ? "\n" : " " );
+ }
+ aLine.append( "]\n"
+ "/FontDescriptor " );
+ aLine.append( nFontDescriptor );
+ aLine.append( " 0 R>>\n"
+ "endobj\n\n" );
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+
+ aRet[ rEmbed.m_nNormalFontID ] = nObject;
+ }
+ }
+
+ return aRet;
+}
+
+typedef int ThreeInts[3];
+static bool getPfbSegmentLengths( const unsigned char* pFontBytes, int nByteLen,
+ ThreeInts& rSegmentLengths )
+{
+ if( !pFontBytes || (nByteLen < 0) )
+ return false;
+ const unsigned char* pPtr = pFontBytes;
+ const unsigned char* pEnd = pFontBytes + nByteLen;
+
+ for(int & rSegmentLength : rSegmentLengths) {
+ // read segment1 header
+ if( pPtr+6 >= pEnd )
+ return false;
+ if( (pPtr[0] != 0x80) || (pPtr[1] >= 0x03) )
+ return false;
+ const int nLen = (pPtr[5]<<24) + (pPtr[4]<<16) + (pPtr[3]<<8) + pPtr[2];
+ if( nLen <= 0)
+ return false;
+ rSegmentLength = nLen;
+ pPtr += nLen + 6;
+ }
+
+ // read segment-end header
+ if( pPtr+2 >= pEnd )
+ return false;
+ if( (pPtr[0] != 0x80) || (pPtr[1] != 0x03) )
+ return false;
+
+ return true;
+}
+
+static void appendSubsetName( int nSubsetID, const OUString& rPSName, OStringBuffer& rBuffer )
+{
+ if( nSubsetID )
+ {
+ for( int i = 0; i < 6; i++ )
+ {
+ int nOffset = nSubsetID % 26;
+ nSubsetID /= 26;
+ rBuffer.append( static_cast<char>('A'+nOffset) );
+ }
+ rBuffer.append( '+' );
+ }
+ appendName( rPSName, rBuffer );
+}
+
+sal_Int32 PDFWriterImpl::createToUnicodeCMap( sal_uInt8 const * pEncoding,
+ const sal_Ucs* pCodeUnits,
+ const sal_Int32* pCodeUnitsPerGlyph,
+ const sal_Int32* pEncToUnicodeIndex,
+ int nGlyphs )
+{
+ int nMapped = 0;
+ for (int n = 0; n < nGlyphs; ++n)
+ if( pCodeUnits[pEncToUnicodeIndex[n]] && pCodeUnitsPerGlyph[n] )
+ nMapped++;
+
+ if( nMapped == 0 )
+ return 0;
+
+ sal_Int32 nStream = createObject();
+ CHECK_RETURN( updateObject( nStream ) );
+
+ OStringBuffer aContents( 1024 );
+ aContents.append(
+ "/CIDInit/ProcSet findresource begin\n"
+ "12 dict begin\n"
+ "begincmap\n"
+ "/CIDSystemInfo<<\n"
+ "/Registry (Adobe)\n"
+ "/Ordering (UCS)\n"
+ "/Supplement 0\n"
+ ">> def\n"
+ "/CMapName/Adobe-Identity-UCS def\n"
+ "/CMapType 2 def\n"
+ "1 begincodespacerange\n"
+ "<00> <FF>\n"
+ "endcodespacerange\n"
+ );
+ int nCount = 0;
+ for (int n = 0; n < nGlyphs; ++n)
+ {
+ if( pCodeUnits[pEncToUnicodeIndex[n]] && pCodeUnitsPerGlyph[n] )
+ {
+ if( (nCount % 100) == 0 )
+ {
+ if( nCount )
+ aContents.append( "endbfchar\n" );
+ aContents.append( static_cast<sal_Int32>(std::min(nMapped-nCount, 100)) );
+ aContents.append( " beginbfchar\n" );
+ }
+ aContents.append( '<' );
+ appendHex( static_cast<sal_Int8>(pEncoding[n]), aContents );
+ aContents.append( "> <" );
+ // TODO: handle code points>U+FFFF
+ sal_Int32 nIndex = pEncToUnicodeIndex[n];
+ for( sal_Int32 j = 0; j < pCodeUnitsPerGlyph[n]; j++ )
+ {
+ appendHex( static_cast<sal_Int8>(pCodeUnits[nIndex + j] / 256), aContents );
+ appendHex( static_cast<sal_Int8>(pCodeUnits[nIndex + j] & 255), aContents );
+ }
+ aContents.append( ">\n" );
+ nCount++;
+ }
+ }
+ aContents.append( "endbfchar\n"
+ "endcmap\n"
+ "CMapName currentdict /CMap defineresource pop\n"
+ "end\n"
+ "end\n" );
+ SvMemoryStream aStream;
+ if (!g_bDebugDisableCompression)
+ {
+ ZCodec aCodec( 0x4000, 0x4000 );
+ aCodec.BeginCompression();
+ aCodec.Write( aStream, reinterpret_cast<const sal_uInt8*>(aContents.getStr()), aContents.getLength() );
+ aCodec.EndCompression();
+ }
+
+ if (g_bDebugDisableCompression)
+ {
+ emitComment( "PDFWriterImpl::createToUnicodeCMap" );
+ }
+ OStringBuffer aLine( 40 );
+
+ aLine.append( nStream );
+ aLine.append( " 0 obj\n<</Length " );
+ sal_Int32 nLen = 0;
+ if (!g_bDebugDisableCompression)
+ {
+ nLen = static_cast<sal_Int32>(aStream.Tell());
+ aStream.Seek( 0 );
+ aLine.append( nLen );
+ aLine.append( "/Filter/FlateDecode" );
+ }
+ else
+ aLine.append( aContents.getLength() );
+ aLine.append( ">>\nstream\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+ checkAndEnableStreamEncryption( nStream );
+ if (!g_bDebugDisableCompression)
+ {
+ CHECK_RETURN( writeBuffer( aStream.GetData(), nLen ) );
+ }
+ else
+ {
+ CHECK_RETURN( writeBuffer( aContents.getStr(), aContents.getLength() ) );
+ }
+ disableStreamEncryption();
+ aLine.setLength( 0 );
+ aLine.append( "\nendstream\n"
+ "endobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+ return nStream;
+}
+
+sal_Int32 PDFWriterImpl::emitFontDescriptor( const PhysicalFontFace* pFont, FontSubsetInfo const & rInfo, sal_Int32 nSubsetID, sal_Int32 nFontStream )
+{
+ OStringBuffer aLine( 1024 );
+ // get font flags, see PDF reference 1.4 p. 358
+ // possibly characters outside Adobe standard encoding
+ // so set Symbolic flag
+ sal_Int32 nFontFlags = (1<<2);
+ if( pFont->GetItalic() == ITALIC_NORMAL || pFont->GetItalic() == ITALIC_OBLIQUE )
+ nFontFlags |= (1 << 6);
+ if( pFont->GetPitch() == PITCH_FIXED )
+ nFontFlags |= 1;
+ if( pFont->GetFamilyType() == FAMILY_SCRIPT )
+ nFontFlags |= (1 << 3);
+ else if( pFont->GetFamilyType() == FAMILY_ROMAN )
+ nFontFlags |= (1 << 1);
+
+ sal_Int32 nFontDescriptor = createObject();
+ CHECK_RETURN( updateObject( nFontDescriptor ) );
+ aLine.setLength( 0 );
+ aLine.append( nFontDescriptor );
+ aLine.append( " 0 obj\n"
+ "<</Type/FontDescriptor/FontName/" );
+ appendSubsetName( nSubsetID, rInfo.m_aPSName, aLine );
+ aLine.append( "\n"
+ "/Flags " );
+ aLine.append( nFontFlags );
+ aLine.append( "\n"
+ "/FontBBox[" );
+ // note: Top and Bottom are reversed in VCL and PDF rectangles
+ aLine.append( static_cast<sal_Int32>(rInfo.m_aFontBBox.TopLeft().X()) );
+ aLine.append( ' ' );
+ aLine.append( static_cast<sal_Int32>(rInfo.m_aFontBBox.TopLeft().Y()) );
+ aLine.append( ' ' );
+ aLine.append( static_cast<sal_Int32>(rInfo.m_aFontBBox.BottomRight().X()) );
+ aLine.append( ' ' );
+ aLine.append( static_cast<sal_Int32>(rInfo.m_aFontBBox.BottomRight().Y()+1) );
+ aLine.append( "]/ItalicAngle " );
+ if( pFont->GetItalic() == ITALIC_OBLIQUE || pFont->GetItalic() == ITALIC_NORMAL )
+ aLine.append( "-30" );
+ else
+ aLine.append( "0" );
+ aLine.append( "\n"
+ "/Ascent " );
+ aLine.append( static_cast<sal_Int32>(rInfo.m_nAscent) );
+ aLine.append( "\n"
+ "/Descent " );
+ aLine.append( static_cast<sal_Int32>(-rInfo.m_nDescent) );
+ aLine.append( "\n"
+ "/CapHeight " );
+ aLine.append( static_cast<sal_Int32>(rInfo.m_nCapHeight) );
+ // According to PDF reference 1.4 StemV is required
+ // seems a tad strange to me, but well ...
+ aLine.append( "\n"
+ "/StemV 80\n" );
+ if( nFontStream )
+ {
+ aLine.append( "/FontFile" );
+ switch( rInfo.m_nFontType )
+ {
+ case FontType::SFNT_TTF:
+ aLine.append( '2' );
+ break;
+ case FontType::TYPE1_PFA:
+ case FontType::TYPE1_PFB:
+ case FontType::ANY_TYPE1:
+ break;
+ default:
+ OSL_FAIL( "unknown fonttype in PDF font descriptor" );
+ return 0;
+ }
+ aLine.append( ' ' );
+ aLine.append( nFontStream );
+ aLine.append( " 0 R\n" );
+ }
+ aLine.append( ">>\n"
+ "endobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+
+ return nFontDescriptor;
+}
+
+void PDFWriterImpl::appendBuildinFontsToDict( OStringBuffer& rDict ) const
+{
+ for (auto const& item : m_aBuildinFontToObjectMap)
+ {
+ rDict.append( pdf::BuildinFontFace::Get(item.first).getNameObject() );
+ rDict.append( ' ' );
+ rDict.append( item.second );
+ rDict.append( " 0 R" );
+ }
+}
+
+bool PDFWriterImpl::emitFonts()
+{
+ SalGraphics *pGraphics = GetGraphics();
+
+ if (!pGraphics)
+ return false;
+
+ OStringBuffer aLine( 1024 );
+
+ std::map< sal_Int32, sal_Int32 > aFontIDToObject;
+
+ OUString aTmpName;
+ osl_createTempFile( nullptr, nullptr, &aTmpName.pData );
+ for (const auto & subset : m_aSubsets)
+ {
+ for (auto & s_subset :subset.second.m_aSubsets)
+ {
+ sal_GlyphId aGlyphIds[ 256 ] = {};
+ sal_Int32 pWidths[ 256 ];
+ sal_uInt8 pEncoding[ 256 ] = {};
+ sal_Int32 pEncToUnicodeIndex[ 256 ] = {};
+ sal_Int32 pCodeUnitsPerGlyph[ 256 ] = {};
+ std::vector<sal_Ucs> aCodeUnits;
+ aCodeUnits.reserve( 256 );
+ int nGlyphs = 1;
+ // fill arrays and prepare encoding index map
+ sal_Int32 nToUnicodeStream = 0;
+
+ for (auto const& item : s_subset.m_aMapping)
+ {
+ sal_uInt8 nEnc = item.second.getGlyphId();
+
+ SAL_WARN_IF( aGlyphIds[nEnc] != 0 || pEncoding[nEnc] != 0, "vcl.pdfwriter", "duplicate glyph" );
+ SAL_WARN_IF( nEnc > s_subset.m_aMapping.size(), "vcl.pdfwriter", "invalid glyph encoding" );
+
+ aGlyphIds[ nEnc ] = item.first;
+ pEncoding[ nEnc ] = nEnc;
+ pEncToUnicodeIndex[ nEnc ] = static_cast<sal_Int32>(aCodeUnits.size());
+ pCodeUnitsPerGlyph[ nEnc ] = item.second.countCodes();
+ for( sal_Int32 n = 0; n < pCodeUnitsPerGlyph[ nEnc ]; n++ )
+ aCodeUnits.push_back( item.second.getCode( n ) );
+ if( item.second.getCode(0) )
+ nToUnicodeStream = 1;
+ if( nGlyphs < 256 )
+ nGlyphs++;
+ else
+ {
+ OSL_FAIL( "too many glyphs for subset" );
+ }
+ }
+ FontSubsetInfo aSubsetInfo;
+ if( pGraphics->CreateFontSubset( aTmpName, subset.first, aGlyphIds, pEncoding, pWidths, nGlyphs, aSubsetInfo ) )
+ {
+ // create font stream
+ osl::File aFontFile(aTmpName);
+ if (osl::File::E_None != aFontFile.open(osl_File_OpenFlag_Read)) return false;
+ // get file size
+ sal_uInt64 nLength1;
+ if ( osl::File::E_None != aFontFile.setPos(osl_Pos_End, 0) ) return false;
+ if ( osl::File::E_None != aFontFile.getPos(nLength1) ) return false;
+ if ( osl::File::E_None != aFontFile.setPos(osl_Pos_Absolut, 0) ) return false;
+
+ if (g_bDebugDisableCompression)
+ {
+ emitComment( "PDFWriterImpl::emitFonts" );
+ }
+ sal_Int32 nFontStream = createObject();
+ sal_Int32 nStreamLengthObject = createObject();
+ if ( !updateObject( nFontStream ) ) return false;
+ aLine.setLength( 0 );
+ aLine.append( nFontStream );
+ aLine.append( " 0 obj\n"
+ "<</Length " );
+ aLine.append( nStreamLengthObject );
+ if (!g_bDebugDisableCompression)
+ aLine.append( " 0 R"
+ "/Filter/FlateDecode"
+ "/Length1 " );
+ else
+ aLine.append( " 0 R"
+ "/Length1 " );
+
+ sal_uInt64 nStartPos = 0;
+ if( aSubsetInfo.m_nFontType == FontType::SFNT_TTF )
+ {
+ aLine.append( static_cast<sal_Int32>(nLength1) );
+
+ aLine.append( ">>\n"
+ "stream\n" );
+ if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
+ if ( osl::File::E_None != m_aFile.getPos(nStartPos) ) return false;
+
+ // copy font file
+ beginCompression();
+ checkAndEnableStreamEncryption( nFontStream );
+ sal_Bool bEOF = false;
+ do
+ {
+ char buf[8192];
+ sal_uInt64 nRead;
+ if ( osl::File::E_None != aFontFile.read(buf, sizeof(buf), nRead) ) return false;
+ if ( !writeBuffer( buf, nRead ) ) return false;
+ if ( osl::File::E_None != aFontFile.isEndOfFile(&bEOF) ) return false;
+ } while( ! bEOF );
+ }
+ else if( aSubsetInfo.m_nFontType & FontType::CFF_FONT)
+ {
+ // TODO: implement
+ OSL_FAIL( "PDFWriterImpl does not support CFF-font subsets yet!" );
+ }
+ else if( aSubsetInfo.m_nFontType & FontType::TYPE1_PFB) // TODO: also support PFA?
+ {
+ std::unique_ptr<unsigned char[]> xBuffer(new unsigned char[nLength1]);
+
+ sal_uInt64 nBytesRead = 0;
+ if ( osl::File::E_None != aFontFile.read(xBuffer.get(), nLength1, nBytesRead) ) return false;
+ SAL_WARN_IF( nBytesRead!=nLength1, "vcl.pdfwriter", "PDF-FontSubset read incomplete!" );
+ if ( osl::File::E_None != aFontFile.setPos(osl_Pos_Absolut, 0) ) return false;
+ // get the PFB-segment lengths
+ ThreeInts aSegmentLengths = {0,0,0};
+ getPfbSegmentLengths(xBuffer.get(), static_cast<int>(nBytesRead), aSegmentLengths);
+ // the lengths below are mandatory for PDF-exported Type1 fonts
+ // because the PFB segment headers get stripped! WhyOhWhy.
+ aLine.append( static_cast<sal_Int32>(aSegmentLengths[0]) );
+ aLine.append( "/Length2 " );
+ aLine.append( static_cast<sal_Int32>(aSegmentLengths[1]) );
+ aLine.append( "/Length3 " );
+ aLine.append( static_cast<sal_Int32>(aSegmentLengths[2]) );
+
+ aLine.append( ">>\n"
+ "stream\n" );
+ if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
+ if ( osl::File::E_None != m_aFile.getPos(nStartPos) ) return false;
+
+ // emit PFB-sections without section headers
+ beginCompression();
+ checkAndEnableStreamEncryption( nFontStream );
+ if ( !writeBuffer( &xBuffer[6], aSegmentLengths[0] ) ) return false;
+ if ( !writeBuffer( &xBuffer[12] + aSegmentLengths[0], aSegmentLengths[1] ) ) return false;
+ if ( !writeBuffer( &xBuffer[18] + aSegmentLengths[0] + aSegmentLengths[1], aSegmentLengths[2] ) ) return false;
+ }
+ else
+ {
+ SAL_INFO("vcl.pdfwriter", "PDF: CreateFontSubset result in not yet supported format=" << static_cast<int>(aSubsetInfo.m_nFontType));
+ aLine.append( "0 >>\nstream\n" );
+ }
+
+ endCompression();
+ disableStreamEncryption();
+ // close the file
+ aFontFile.close();
+
+ sal_uInt64 nEndPos = 0;
+ if ( osl::File::E_None != m_aFile.getPos(nEndPos) ) return false;
+ // end the stream
+ aLine.setLength( 0 );
+ aLine.append( "\nendstream\nendobj\n\n" );
+ if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
+
+ // emit stream length object
+ if ( !updateObject( nStreamLengthObject ) ) return false;
+ aLine.setLength( 0 );
+ aLine.append( nStreamLengthObject );
+ aLine.append( " 0 obj\n" );
+ aLine.append( static_cast<sal_Int64>(nEndPos-nStartPos) );
+ aLine.append( "\nendobj\n\n" );
+ if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
+
+ // write font descriptor
+ sal_Int32 nFontDescriptor = emitFontDescriptor( subset.first, aSubsetInfo, s_subset.m_nFontID, nFontStream );
+
+ if( nToUnicodeStream )
+ nToUnicodeStream = createToUnicodeCMap( pEncoding, aCodeUnits.data(), pCodeUnitsPerGlyph, pEncToUnicodeIndex, nGlyphs );
+
+ sal_Int32 nFontObject = createObject();
+ if ( !updateObject( nFontObject ) ) return false;
+ aLine.setLength( 0 );
+ aLine.append( nFontObject );
+
+ aLine.append( " 0 obj\n" );
+ aLine.append( (aSubsetInfo.m_nFontType & FontType::ANY_TYPE1) ?
+ "<</Type/Font/Subtype/Type1/BaseFont/" :
+ "<</Type/Font/Subtype/TrueType/BaseFont/" );
+ appendSubsetName( s_subset.m_nFontID, aSubsetInfo.m_aPSName, aLine );
+ aLine.append( "\n"
+ "/FirstChar 0\n"
+ "/LastChar " );
+ aLine.append( static_cast<sal_Int32>(nGlyphs-1) );
+ aLine.append( "\n"
+ "/Widths[" );
+ for( int i = 0; i < nGlyphs; i++ )
+ {
+ aLine.append( pWidths[ i ] );
+ aLine.append( ((i & 15) == 15) ? "\n" : " " );
+ }
+ aLine.append( "]\n"
+ "/FontDescriptor " );
+ aLine.append( nFontDescriptor );
+ aLine.append( " 0 R\n" );
+ if( nToUnicodeStream )
+ {
+ aLine.append( "/ToUnicode " );
+ aLine.append( nToUnicodeStream );
+ aLine.append( " 0 R\n" );
+ }
+ aLine.append( ">>\n"
+ "endobj\n\n" );
+ if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
+
+ aFontIDToObject[ s_subset.m_nFontID ] = nFontObject;
+ }
+ else
+ {
+ const PhysicalFontFace* pFont = subset.first;
+ OStringBuffer aErrorComment( 256 );
+ aErrorComment.append( "CreateFontSubset failed for font \"" );
+ aErrorComment.append( OUStringToOString( pFont->GetFamilyName(), RTL_TEXTENCODING_UTF8 ) );
+ aErrorComment.append( '\"' );
+ if( pFont->GetItalic() == ITALIC_NORMAL )
+ aErrorComment.append( " italic" );
+ else if( pFont->GetItalic() == ITALIC_OBLIQUE )
+ aErrorComment.append( " oblique" );
+ aErrorComment.append( " weight=" );
+ aErrorComment.append( sal_Int32(pFont->GetWeight()) );
+ emitComment( aErrorComment.getStr() );
+ }
+ }
+ }
+ osl_removeFile( aTmpName.pData );
+
+ // emit system fonts
+ for (auto const& systemFont : m_aSystemFonts)
+ {
+ std::map< sal_Int32, sal_Int32 > aObjects = emitSystemFont( systemFont.first, systemFont.second );
+ for (auto const& item : aObjects)
+ {
+ if ( !item.second ) return false;
+ aFontIDToObject[ item.first ] = item.second;
+ }
+ }
+
+ OStringBuffer aFontDict( 1024 );
+ aFontDict.append( getFontDictObject() );
+ aFontDict.append( " 0 obj\n"
+ "<<" );
+ int ni = 0;
+ for (auto const& itemMap : aFontIDToObject)
+ {
+ aFontDict.append( "/F" );
+ aFontDict.append( itemMap.first );
+ aFontDict.append( ' ' );
+ aFontDict.append( itemMap.second );
+ aFontDict.append( " 0 R" );
+ if( ((++ni) & 7) == 0 )
+ aFontDict.append( '\n' );
+ }
+ // emit builtin font for widget appearances / variable text
+ for (auto & item : m_aBuildinFontToObjectMap)
+ {
+ rtl::Reference<pdf::BuildinFontFace> aData(new pdf::BuildinFontFace(item.first));
+ item.second = emitBuildinFont( aData.get(), item.second );
+ }
+
+ appendBuildinFontsToDict(aFontDict);
+ aFontDict.append( "\n>>\nendobj\n\n" );
+
+ if ( !updateObject( getFontDictObject() ) ) return false;
+ if ( !writeBuffer( aFontDict.getStr(), aFontDict.getLength() ) ) return false;
+ return true;
+}
+
+sal_Int32 PDFWriterImpl::emitResources()
+{
+ // emit shadings
+ if( ! m_aGradients.empty() )
+ CHECK_RETURN( emitGradients() );
+ // emit tilings
+ if( ! m_aTilings.empty() )
+ CHECK_RETURN( emitTilings() );
+
+ // emit font dict
+ CHECK_RETURN( emitFonts() );
+
+ // emit Resource dict
+ OStringBuffer aLine( 512 );
+ sal_Int32 nResourceDict = getResourceDictObj();
+ CHECK_RETURN( updateObject( nResourceDict ) );
+ aLine.setLength( 0 );
+ aLine.append( nResourceDict );
+ aLine.append( " 0 obj\n" );
+ m_aGlobalResourceDict.append( aLine, getFontDictObject() );
+ aLine.append( "endobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+ return nResourceDict;
+}
+
+sal_Int32 PDFWriterImpl::updateOutlineItemCount( std::vector< sal_Int32 >& rCounts,
+ sal_Int32 nItemLevel,
+ sal_Int32 nCurrentItemId )
+{
+ /* The /Count number of an item is
+ positive: the number of visible subitems
+ negative: the negative number of subitems that will become visible if
+ the item gets opened
+ see PDF ref 1.4 p 478
+ */
+
+ sal_Int32 nCount = 0;
+
+ if( m_aContext.OpenBookmarkLevels < 0 || // all levels are visible
+ m_aContext.OpenBookmarkLevels >= nItemLevel // this level is visible
+ )
+ {
+ PDFOutlineEntry& rItem = m_aOutline[ nCurrentItemId ];
+ sal_Int32 nChildren = rItem.m_aChildren.size();
+ for( sal_Int32 i = 0; i < nChildren; i++ )
+ nCount += updateOutlineItemCount( rCounts, nItemLevel+1, rItem.m_aChildren[i] );
+ rCounts[nCurrentItemId] = nCount;
+ // return 1 (this item) + visible sub items
+ if( nCount < 0 )
+ nCount = 0;
+ nCount++;
+ }
+ else
+ {
+ // this bookmark level is invisible
+ PDFOutlineEntry& rItem = m_aOutline[ nCurrentItemId ];
+ sal_Int32 nChildren = rItem.m_aChildren.size();
+ rCounts[ nCurrentItemId ] = -sal_Int32(rItem.m_aChildren.size());
+ for( sal_Int32 i = 0; i < nChildren; i++ )
+ updateOutlineItemCount( rCounts, nItemLevel+1, rItem.m_aChildren[i] );
+ nCount = -1;
+ }
+
+ return nCount;
+}
+
+sal_Int32 PDFWriterImpl::emitOutline()
+{
+ int i, nItems = m_aOutline.size();
+
+ // do we have an outline at all ?
+ if( nItems < 2 )
+ return 0;
+
+ // reserve object numbers for all outline items
+ for( i = 0; i < nItems; ++i )
+ m_aOutline[i].m_nObject = createObject();
+
+ // update all parent, next and prev object ids
+ for( i = 0; i < nItems; ++i )
+ {
+ PDFOutlineEntry& rItem = m_aOutline[i];
+ int nChildren = rItem.m_aChildren.size();
+
+ if( nChildren )
+ {
+ for( int n = 0; n < nChildren; ++n )
+ {
+ PDFOutlineEntry& rChild = m_aOutline[ rItem.m_aChildren[n] ];
+
+ rChild.m_nParentObject = rItem.m_nObject;
+ rChild.m_nPrevObject = (n > 0) ? m_aOutline[ rItem.m_aChildren[n-1] ].m_nObject : 0;
+ rChild.m_nNextObject = (n < nChildren-1) ? m_aOutline[ rItem.m_aChildren[n+1] ].m_nObject : 0;
+ }
+
+ }
+ }
+
+ // calculate Count entries for all items
+ std::vector< sal_Int32 > aCounts( nItems );
+ updateOutlineItemCount( aCounts, 0, 0 );
+
+ // emit hierarchy
+ for( i = 0; i < nItems; ++i )
+ {
+ PDFOutlineEntry& rItem = m_aOutline[i];
+ OStringBuffer aLine( 1024 );
+
+ CHECK_RETURN( updateObject( rItem.m_nObject ) );
+ aLine.append( rItem.m_nObject );
+ aLine.append( " 0 obj\n" );
+ aLine.append( "<<" );
+ // number of visible children (all levels)
+ if( i > 0 || aCounts[0] > 0 )
+ {
+ aLine.append( "/Count " );
+ aLine.append( aCounts[i] );
+ }
+ if( ! rItem.m_aChildren.empty() )
+ {
+ // children list: First, Last
+ aLine.append( "/First " );
+ aLine.append( m_aOutline[rItem.m_aChildren.front()].m_nObject );
+ aLine.append( " 0 R/Last " );
+ aLine.append( m_aOutline[rItem.m_aChildren.back()].m_nObject );
+ aLine.append( " 0 R\n" );
+ }
+ if( i > 0 )
+ {
+ // Title, Dest, Parent, Prev, Next
+ aLine.append( "/Title" );
+ appendUnicodeTextStringEncrypt( rItem.m_aTitle, rItem.m_nObject, aLine );
+ aLine.append( "\n" );
+ // Dest is not required
+ if( rItem.m_nDestID >= 0 && rItem.m_nDestID < static_cast<sal_Int32>(m_aDests.size()) )
+ {
+ aLine.append( "/Dest" );
+ appendDest( rItem.m_nDestID, aLine );
+ }
+ aLine.append( "/Parent " );
+ aLine.append( rItem.m_nParentObject );
+ aLine.append( " 0 R" );
+ if( rItem.m_nPrevObject )
+ {
+ aLine.append( "/Prev " );
+ aLine.append( rItem.m_nPrevObject );
+ aLine.append( " 0 R" );
+ }
+ if( rItem.m_nNextObject )
+ {
+ aLine.append( "/Next " );
+ aLine.append( rItem.m_nNextObject );
+ aLine.append( " 0 R" );
+ }
+ }
+ aLine.append( ">>\nendobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+ }
+
+ return m_aOutline[0].m_nObject;
+}
+
+#undef CHECK_RETURN
+#define CHECK_RETURN( x ) if( !x ) return false
+
+bool PDFWriterImpl::appendDest( sal_Int32 nDestID, OStringBuffer& rBuffer )
+{
+ if( nDestID < 0 || nDestID >= static_cast<sal_Int32>(m_aDests.size()) )
+ {
+ SAL_INFO("vcl.pdfwriter", "ERROR: invalid dest " << static_cast<int>(nDestID) << " requested");
+ return false;
+ }
+
+ const PDFDest& rDest = m_aDests[ nDestID ];
+ const PDFPage& rDestPage = m_aPages[ rDest.m_nPage ];
+
+ rBuffer.append( '[' );
+ rBuffer.append( rDestPage.m_nPageObject );
+ rBuffer.append( " 0 R" );
+
+ switch( rDest.m_eType )
+ {
+ case PDFWriter::DestAreaType::XYZ:
+ default:
+ rBuffer.append( "/XYZ " );
+ appendFixedInt( rDest.m_aRect.Left(), rBuffer );
+ rBuffer.append( ' ' );
+ appendFixedInt( rDest.m_aRect.Bottom(), rBuffer );
+ rBuffer.append( " 0" );
+ break;
+ case PDFWriter::DestAreaType::FitRectangle:
+ rBuffer.append( "/FitR " );
+ appendFixedInt( rDest.m_aRect.Left(), rBuffer );
+ rBuffer.append( ' ' );
+ appendFixedInt( rDest.m_aRect.Top(), rBuffer );
+ rBuffer.append( ' ' );
+ appendFixedInt( rDest.m_aRect.Right(), rBuffer );
+ rBuffer.append( ' ' );
+ appendFixedInt( rDest.m_aRect.Bottom(), rBuffer );
+ break;
+ }
+ rBuffer.append( ']' );
+
+ return true;
+}
+
+bool PDFWriterImpl::emitScreenAnnotations()
+{
+ int nAnnots = m_aScreens.size();
+ for (int i = 0; i < nAnnots; i++)
+ {
+ const PDFScreen& rScreen = m_aScreens[i];
+
+ OStringBuffer aLine;
+ bool bEmbed = false;
+ if (!rScreen.m_aTempFileURL.isEmpty())
+ {
+ bEmbed = true;
+ if (!updateObject(rScreen.m_nTempFileObject))
+ continue;
+
+ SvFileStream aFileStream(rScreen.m_aTempFileURL, StreamMode::READ);
+ SvMemoryStream aMemoryStream;
+ aMemoryStream.WriteStream(aFileStream);
+
+ aLine.append(rScreen.m_nTempFileObject);
+ aLine.append(" 0 obj\n");
+ aLine.append("<< /Type /EmbeddedFile /Length ");
+ aLine.append(static_cast<sal_Int64>(aMemoryStream.GetSize()));
+ aLine.append(" >>\nstream\n");
+ CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
+ aLine.setLength(0);
+
+ CHECK_RETURN(writeBuffer(aMemoryStream.GetData(), aMemoryStream.GetSize()));
+
+ aLine.append("\nendstream\nendobj\n\n");
+ CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
+ aLine.setLength(0);
+ }
+
+ if (!updateObject(rScreen.m_nObject))
+ continue;
+
+ // Annot dictionary.
+ aLine.append(rScreen.m_nObject);
+ aLine.append(" 0 obj\n");
+ aLine.append("<</Type/Annot");
+ aLine.append("/Subtype/Screen/Rect[");
+ appendFixedInt(rScreen.m_aRect.Left(), aLine);
+ aLine.append(' ');
+ appendFixedInt(rScreen.m_aRect.Top(), aLine);
+ aLine.append(' ');
+ appendFixedInt(rScreen.m_aRect.Right(), aLine);
+ aLine.append(' ');
+ appendFixedInt(rScreen.m_aRect.Bottom(), aLine);
+ aLine.append("]");
+
+ // Action dictionary.
+ aLine.append("/A<</Type/Action /S/Rendition /AN ");
+ aLine.append(rScreen.m_nObject);
+ aLine.append(" 0 R ");
+
+ // Rendition dictionary.
+ aLine.append("/R<</Type/Rendition /S/MR ");
+
+ // MediaClip dictionary.
+ aLine.append("/C<</Type/MediaClip /S/MCD ");
+ if (bEmbed)
+ {
+ aLine.append("/D << /Type /Filespec /F (<embedded file>) /EF << /F ");
+ aLine.append(rScreen.m_nTempFileObject);
+ aLine.append(" 0 R >> >>");
+ }
+ else
+ {
+ // Linked.
+ aLine.append("/D << /Type /Filespec /FS /URL /F ");
+ appendLiteralStringEncrypt(rScreen.m_aURL, rScreen.m_nObject, aLine, osl_getThreadTextEncoding());
+ aLine.append(" >>");
+ }
+ // Allow playing the video via a tempfile.
+ aLine.append("/P <</TF (TEMPACCESS)>>");
+ // Until the real MIME type (instead of application/vnd.sun.star.media) is available here.
+ aLine.append("/CT (video/mp4)");
+ aLine.append(">>");
+
+ // End Rendition dictionary by requesting play/pause/stop controls.
+ aLine.append("/P<</BE<</C true >>>>");
+ aLine.append(">>");
+
+ // End Action dictionary.
+ aLine.append("/OP 0 >>");
+
+ // End Annot dictionary.
+ aLine.append("/P ");
+ aLine.append(m_aPages[rScreen.m_nPage].m_nPageObject);
+ aLine.append(" 0 R\n>>\nendobj\n\n");
+ CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
+ }
+
+ return true;
+}
+
+bool PDFWriterImpl::emitLinkAnnotations()
+{
+ MARK("PDFWriterImpl::emitLinkAnnotations");
+ int nAnnots = m_aLinks.size();
+ for( int i = 0; i < nAnnots; i++ )
+ {
+ const PDFLink& rLink = m_aLinks[i];
+ if( ! updateObject( rLink.m_nObject ) )
+ continue;
+
+ OStringBuffer aLine( 1024 );
+ aLine.append( rLink.m_nObject );
+ aLine.append( " 0 obj\n" );
+// i59651: key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should'
+// see PDF 8.4.2 and ISO 19005-1:2005 6.5.3
+ aLine.append( "<</Type/Annot" );
+ if( m_bIsPDF_A1 || m_bIsPDF_A2 || m_bIsPDF_A3)
+ aLine.append( "/F 4" );
+ aLine.append( "/Subtype/Link/Border[0 0 0]/Rect[" );
+
+ appendFixedInt( rLink.m_aRect.Left()-7, aLine );//the +7 to have a better shape of the border rectangle
+ aLine.append( ' ' );
+ appendFixedInt( rLink.m_aRect.Top(), aLine );
+ aLine.append( ' ' );
+ appendFixedInt( rLink.m_aRect.Right()+7, aLine );//the +7 to have a better shape of the border rectangle
+ aLine.append( ' ' );
+ appendFixedInt( rLink.m_aRect.Bottom(), aLine );
+ aLine.append( "]" );
+ 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;
+
+ // 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 aNewBase( aDocumentURL );//duplicate document URL
+ aNewBase.removeSegment(); //remove last segment from it, obtaining the base URL of the
+ //target document
+ aNewBase.insertName( url );
+ aTargetURL = aNewBase;//reassign the new target URL
+ //recompute the target protocol, with the new URL
+ //normal URL processing resumes
+ eTargetProtocol = aTargetURL.GetProtocol();
+ }
+ }
+
+ 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("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.makeStringAndClear(), RTL_TEXTENCODING_ASCII_US) );
+ }
+ OUString aURL = aTargetURL.GetMainURL( bFileSpec ? INetURLObject::DecodeMechanism::WithCharset : INetURLObject::DecodeMechanism::NONE );
+ appendLiteralStringEncrypt(bSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURL,
+ INetURLObject::EncodeMechanism::WasEncoded,
+ bFileSpec ? INetURLObject::DecodeMechanism::WithCharset : INetURLObject::DecodeMechanism::NONE
+ ) :
+ aURL , rLink.m_nObject, aLine, osl_getThreadTextEncoding() );
+ }
+ }
+ aLine.append( ">>\n" );
+ }
+ if( rLink.m_nStructParent > 0 )
+ {
+ aLine.append( "/StructParent " );
+ aLine.append( rLink.m_nStructParent );
+ }
+ aLine.append( ">>\nendobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+ }
+
+ return true;
+}
+
+bool PDFWriterImpl::emitNoteAnnotations()
+{
+ // emit note annotations
+ int nAnnots = m_aNotes.size();
+ for( int i = 0; i < nAnnots; i++ )
+ {
+ const PDFNoteEntry& rNote = m_aNotes[i];
+ if( ! updateObject( rNote.m_nObject ) )
+ return false;
+
+ OStringBuffer aLine( 1024 );
+ aLine.append( rNote.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/Text/Rect[" );
+
+ appendFixedInt( rNote.m_aRect.Left(), aLine );
+ aLine.append( ' ' );
+ appendFixedInt( rNote.m_aRect.Top(), aLine );
+ aLine.append( ' ' );
+ appendFixedInt( rNote.m_aRect.Right(), aLine );
+ aLine.append( ' ' );
+ appendFixedInt( rNote.m_aRect.Bottom(), aLine );
+ aLine.append( "]" );
+
+ // contents of the note (type text string)
+ aLine.append( "/Contents\n" );
+ 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( ">>\nendobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+ }
+ 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
+ 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 = Application::GetSettings().GetStyleSettings();
+
+ // save graphics state
+ push( PushFlags::ALL );
+
+ // transform relative to control's coordinates since an
+ // appearance stream is a form XObject
+ // this relies on the m_aRect member of rButton NOT already being transformed
+ // to default user space
+ if( rWidget.Background || rWidget.Border )
+ {
+ setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetLightColor() ) : COL_TRANSPARENT );
+ setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetDialogColor() ) : COL_TRANSPARENT );
+ drawRectangle( rWidget.Location );
+ }
+ // prepare font to use
+ Font aFont = replaceFont( rWidget.TextFont, rSettings.GetPushButtonFont() );
+ setFont( aFont );
+ setTextColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ) );
+
+ drawText( rButton.m_aRect, rButton.m_aText, rButton.m_nTextStyle );
+
+ // create DA string while local mapmode is still in place
+ // (that is before endRedirect())
+ OStringBuffer aDA( 256 );
+ appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ), aDA );
+ Font aDummyFont( "Helvetica", aFont.GetFontSize() );
+ sal_Int32 nDummyBuildin = getBestBuildinFont( aDummyFont );
+ aDA.append( ' ' );
+ aDA.append(pdf::BuildinFontFace::Get(nDummyBuildin).getNameObject());
+ aDA.append( ' ' );
+ m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
+ aDA.append( " Tf" );
+ rButton.m_aDAString = aDA.makeStringAndClear();
+
+ pop();
+
+ rButton.m_aAppearances[ "N" ][ "Standard" ] = new SvMemoryStream();
+
+ /* seems like a bad hack but at least works in both AR5 and 6:
+ we draw the button ourselves and tell AR
+ the button would be totally transparent with no text
+
+ One would expect that simply setting a normal appearance
+ should suffice, but no, as soon as the user actually presses
+ the button and an action is tied to it (gasp! a button that
+ does something) the appearance gets replaced by some crap that AR
+ creates on the fly even if no DA or MK is given. On AR6 at least
+ the DA and MK work as expected, but on AR5 this creates a region
+ filled with the background color but nor text. Urgh.
+ */
+ rButton.m_aMKDict = "/BC [] /BG [] /CA";
+ rButton.m_aMKDictCAString = "";
+}
+
+Font PDFWriterImpl::drawFieldBorder( PDFWidget& rIntern,
+ const PDFWriter::AnyWidget& rWidget,
+ const StyleSettings& rSettings )
+{
+ Font aFont = replaceFont( rWidget.TextFont, rSettings.GetFieldFont() );
+
+ if( rWidget.Background || rWidget.Border )
+ {
+ if( rWidget.Border && rWidget.BorderColor == COL_TRANSPARENT )
+ {
+ sal_Int32 nDelta = GetDPIX() / 500;
+ if( nDelta < 1 )
+ nDelta = 1;
+ setLineColor( COL_TRANSPARENT );
+ tools::Rectangle aRect = rIntern.m_aRect;
+ setFillColor( rSettings.GetLightBorderColor() );
+ drawRectangle( aRect );
+ aRect.AdjustLeft(nDelta ); aRect.AdjustTop(nDelta );
+ aRect.AdjustRight( -nDelta ); aRect.AdjustBottom( -nDelta );
+ setFillColor( rSettings.GetFieldColor() );
+ drawRectangle( aRect );
+ setFillColor( rSettings.GetLightColor() );
+ drawRectangle( tools::Rectangle( Point( aRect.Left(), aRect.Bottom()-nDelta ), aRect.BottomRight() ) );
+ drawRectangle( tools::Rectangle( Point( aRect.Right()-nDelta, aRect.Top() ), aRect.BottomRight() ) );
+ setFillColor( rSettings.GetDarkShadowColor() );
+ drawRectangle( tools::Rectangle( aRect.TopLeft(), Point( aRect.Left()+nDelta, aRect.Bottom() ) ) );
+ drawRectangle( tools::Rectangle( aRect.TopLeft(), Point( aRect.Right(), aRect.Top()+nDelta ) ) );
+ }
+ else
+ {
+ setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetShadowColor() ) : COL_TRANSPARENT );
+ setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT );
+ drawRectangle( rIntern.m_aRect );
+ }
+
+ if( rWidget.Border )
+ {
+ // adjust edit area accounting for border
+ sal_Int32 nDelta = aFont.GetFontHeight()/4;
+ if( nDelta < 1 )
+ nDelta = 1;
+ rIntern.m_aRect.AdjustLeft(nDelta );
+ rIntern.m_aRect.AdjustTop(nDelta );
+ rIntern.m_aRect.AdjustRight( -nDelta );
+ rIntern.m_aRect.AdjustBottom( -nDelta );
+ }
+ }
+ return aFont;
+}
+
+void PDFWriterImpl::createDefaultEditAppearance( PDFWidget& rEdit, const PDFWriter::EditWidget& rWidget )
+{
+ const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
+ SvMemoryStream* pEditStream = new SvMemoryStream( 1024, 1024 );
+
+ push( PushFlags::ALL );
+
+ // prepare font to use, draw field border
+ Font aFont = drawFieldBorder( rEdit, rWidget, rSettings );
+ sal_Int32 nBest = getSystemFont( aFont );
+
+ // prepare DA string
+ OStringBuffer aDA( 32 );
+ 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" );
+ rEdit.m_aDRDict = aDR.makeStringAndClear();
+ aDA.append( ' ' );
+ m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
+ aDA.append( " Tf" );
+
+ /* create an empty appearance stream, let the viewer create
+ the appearance at runtime. This is because AR5 seems to
+ paint the widget appearance always, and a dynamically created
+ appearance on top of it. AR6 is well behaved in that regard, so
+ that behaviour seems to be a bug. Anyway this empty appearance
+ relies on /NeedAppearances in the AcroForm dictionary set to "true"
+ */
+ beginRedirect( pEditStream, rEdit.m_aRect );
+ OString aAppearance = "/Tx BMC\nEMC\n";
+ writeBuffer( aAppearance.getStr(), aAppearance.getLength() );
+
+ endRedirect();
+ pop();
+
+ rEdit.m_aAppearances[ "N" ][ "Standard" ] = pEditStream;
+
+ rEdit.m_aDAString = aDA.makeStringAndClear();
+}
+
+void PDFWriterImpl::createDefaultListBoxAppearance( PDFWidget& rBox, const PDFWriter::ListBoxWidget& rWidget )
+{
+ const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
+ SvMemoryStream* pListBoxStream = new SvMemoryStream( 1024, 1024 );
+
+ push( PushFlags::ALL );
+
+ // prepare font to use, draw field border
+ Font aFont = drawFieldBorder( rBox, rWidget, rSettings );
+ sal_Int32 nBest = getSystemFont( aFont );
+
+ beginRedirect( pListBoxStream, rBox.m_aRect );
+
+ setLineColor( COL_TRANSPARENT );
+ setFillColor( replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) );
+ drawRectangle( rBox.m_aRect );
+
+ // empty appearance, see createDefaultEditAppearance for reference
+ OString aAppearance = "/Tx BMC\nEMC\n";
+ writeBuffer( aAppearance.getStr(), aAppearance.getLength() );
+
+ endRedirect();
+ pop();
+
+ rBox.m_aAppearances[ "N" ][ "Standard" ] = pListBoxStream;
+
+ // prepare DA string
+ OStringBuffer aDA( 256 );
+ // prepare DA string
+ appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA );
+ aDA.append( ' ' );
+ aDA.append( "/F" );
+ aDA.append( nBest );
+
+ OStringBuffer aDR( 32 );
+ aDR.append( "/Font " );
+ aDR.append( getFontDictObject() );
+ aDR.append( " 0 R" );
+ rBox.m_aDRDict = aDR.makeStringAndClear();
+ aDA.append( ' ' );
+ m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
+ aDA.append( " Tf" );
+ rBox.m_aDAString = aDA.makeStringAndClear();
+}
+
+void PDFWriterImpl::createDefaultCheckBoxAppearance( PDFWidget& rBox, const PDFWriter::CheckBoxWidget& rWidget )
+{
+ const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
+
+ // save graphics state
+ push( PushFlags::ALL );
+
+ if( rWidget.Background || rWidget.Border )
+ {
+ setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : COL_TRANSPARENT );
+ setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT );
+ drawRectangle( rBox.m_aRect );
+ }
+
+ Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() );
+ setFont( aFont );
+ Size aFontSize = aFont.GetFontSize();
+ if( aFontSize.Height() > rBox.m_aRect.GetHeight() )
+ aFontSize.setHeight( rBox.m_aRect.GetHeight() );
+ sal_Int32 nDelta = aFontSize.Height()/10;
+ if( nDelta < 1 )
+ nDelta = 1;
+
+ tools::Rectangle aCheckRect, aTextRect;
+ {
+ aCheckRect.SetLeft( rBox.m_aRect.Left() + nDelta );
+ aCheckRect.SetTop( rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2 );
+ aCheckRect.SetRight( aCheckRect.Left() + aFontSize.Height() );
+ aCheckRect.SetBottom( aCheckRect.Top() + aFontSize.Height() );
+
+ // #i74206# handle small controls without text area
+ while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta )
+ {
+ aCheckRect.AdjustRight( -nDelta );
+ aCheckRect.AdjustTop(nDelta/2 );
+ aCheckRect.AdjustBottom( -(nDelta - (nDelta/2)) );
+ }
+
+ aTextRect.SetLeft( rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta );
+ aTextRect.SetTop( rBox.m_aRect.Top() );
+ aTextRect.SetRight( aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta );
+ aTextRect.SetBottom( rBox.m_aRect.Bottom() );
+ }
+ setLineColor( COL_BLACK );
+ setFillColor( COL_TRANSPARENT );
+ OStringBuffer aLW( 32 );
+ aLW.append( "q " );
+ m_aPages[m_nCurrentPage].appendMappedLength( nDelta, aLW );
+ aLW.append( " w " );
+ writeBuffer( aLW.getStr(), aLW.getLength() );
+ drawRectangle( aCheckRect );
+ writeBuffer( " Q\n", 3 );
+ setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
+ drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle );
+
+ pop();
+
+ OStringBuffer aDA( 256 );
+
+ // tdf#93853 don't rely on Zapf (or any other 'standard' font)
+ // being present, but our own OpenSymbol - N.B. PDF/A for good
+ // reasons require even the standard PS fonts to be embedded!
+ Push();
+ SetFont( Font( OUString( "OpenSymbol" ), aFont.GetFontSize() ) );
+ FontCharMapRef pMap;
+ GetFontCharMap(pMap);
+ const LogicalFontInstance* pFontInstance = GetFontInstance();
+ const PhysicalFontFace* pDevFont = pFontInstance->GetFontFace();
+ Pop();
+
+ // make sure OpenSymbol is embedded, and includes our checkmark
+ const sal_Unicode cMark=0x2713;
+ const GlyphItem aItem(0, 0, pMap->GetGlyphIndex(cMark),
+ Point(), GlyphItemFlags::NONE, 0, 0,
+ const_cast<LogicalFontInstance*>(pFontInstance));
+ const std::vector<sal_Ucs> aCodeUnits={ cMark };
+ sal_uInt8 nMappedGlyph;
+ sal_Int32 nMappedFontObject;
+ registerGlyph(&aItem, pDevFont, aCodeUnits, nMappedGlyph, nMappedFontObject);
+
+ appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
+ aDA.append( ' ' );
+ aDA.append( "/F" );
+ aDA.append( nMappedFontObject );
+ aDA.append( " 0 Tf" );
+
+ OStringBuffer aDR( 32 );
+ aDR.append( "/Font " );
+ aDR.append( getFontDictObject() );
+ aDR.append( " 0 R" );
+ rBox.m_aDRDict = aDR.makeStringAndClear();
+ rBox.m_aDAString = aDA.makeStringAndClear();
+ rBox.m_aMKDict = "/CA";
+ rBox.m_aMKDictCAString = "8";
+ rBox.m_aRect = aCheckRect;
+
+ // create appearance streams
+ sal_Int32 nCharXOffset = 1000 - 787; // metrics from OpenSymbol
+ nCharXOffset *= aCheckRect.GetHeight();
+ nCharXOffset /= 2000;
+ sal_Int32 nCharYOffset = 1000 - (820-143); // metrics from Zapf
+ nCharYOffset *= aCheckRect.GetHeight();
+ nCharYOffset /= 2000;
+
+ // write 'checked' appearance stream
+ SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 );
+ beginRedirect( pCheckStream, aCheckRect );
+ aDA.append( "/Tx BMC\nq BT\n" );
+ appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
+ aDA.append( ' ' );
+ aDA.append( "/F" );
+ aDA.append( nMappedFontObject );
+ aDA.append( ' ' );
+ m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA );
+ aDA.append( " Tf\n" );
+ m_aPages[ m_nCurrentPage ].appendMappedLength( nCharXOffset, aDA );
+ aDA.append( " " );
+ m_aPages[ m_nCurrentPage ].appendMappedLength( nCharYOffset, aDA );
+ aDA.append( " Td <" );
+ appendHex( nMappedGlyph, aDA );
+ aDA.append( "> Tj\nET\nQ\nEMC\n" );
+ writeBuffer( aDA.getStr(), aDA.getLength() );
+ endRedirect();
+ rBox.m_aAppearances[ "N" ][ "Yes" ] = pCheckStream;
+
+ // write 'unchecked' appearance stream
+ SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 );
+ beginRedirect( pUncheckStream, aCheckRect );
+ writeBuffer( "/Tx BMC\nEMC\n", 12 );
+ endRedirect();
+ rBox.m_aAppearances[ "N" ][ "Off" ] = pUncheckStream;
+}
+
+void PDFWriterImpl::createDefaultRadioButtonAppearance( PDFWidget& rBox, const PDFWriter::RadioButtonWidget& rWidget )
+{
+ const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
+
+ // save graphics state
+ push( PushFlags::ALL );
+
+ if( rWidget.Background || rWidget.Border )
+ {
+ setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : COL_TRANSPARENT );
+ setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT );
+ drawRectangle( rBox.m_aRect );
+ }
+
+ Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() );
+ setFont( aFont );
+ Size aFontSize = aFont.GetFontSize();
+ if( aFontSize.Height() > rBox.m_aRect.GetHeight() )
+ aFontSize.setHeight( rBox.m_aRect.GetHeight() );
+ sal_Int32 nDelta = aFontSize.Height()/10;
+ if( nDelta < 1 )
+ nDelta = 1;
+
+ tools::Rectangle aCheckRect, aTextRect;
+ {
+ aCheckRect.SetLeft( rBox.m_aRect.Left() + nDelta );
+ aCheckRect.SetTop( rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2 );
+ aCheckRect.SetRight( aCheckRect.Left() + aFontSize.Height() );
+ aCheckRect.SetBottom( aCheckRect.Top() + aFontSize.Height() );
+
+ // #i74206# handle small controls without text area
+ while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta )
+ {
+ aCheckRect.AdjustRight( -nDelta );
+ aCheckRect.AdjustTop(nDelta/2 );
+ aCheckRect.AdjustBottom( -(nDelta - (nDelta/2)) );
+ }
+
+ aTextRect.SetLeft( rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta );
+ aTextRect.SetTop( rBox.m_aRect.Top() );
+ aTextRect.SetRight( aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta );
+ aTextRect.SetBottom( rBox.m_aRect.Bottom() );
+ }
+ setLineColor( COL_BLACK );
+ setFillColor( COL_TRANSPARENT );
+ OStringBuffer aLW( 32 );
+ aLW.append( "q " );
+ m_aPages[ m_nCurrentPage ].appendMappedLength( nDelta, aLW );
+ aLW.append( " w " );
+ writeBuffer( aLW.getStr(), aLW.getLength() );
+ drawEllipse( aCheckRect );
+ writeBuffer( " Q\n", 3 );
+ setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
+ drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle );
+
+ pop();
+
+ OStringBuffer aDA( 256 );
+ appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
+ rBox.m_aDAString = aDA.makeStringAndClear();
+ //to encrypt this (el)
+ rBox.m_aMKDict = "/CA";
+ //after this assignment, to m_aMKDic cannot be added anything
+ rBox.m_aMKDictCAString = "l";
+
+ rBox.m_aRect = aCheckRect;
+
+ // create appearance streams
+ push( PushFlags::ALL);
+ SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 );
+
+ beginRedirect( pCheckStream, aCheckRect );
+ aDA.append( "/Tx BMC\nq BT\n" );
+ appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
+ aDA.append( ' ' );
+ m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA );
+ aDA.append( " 0 0 Td\nET\nQ\n" );
+ writeBuffer( aDA.getStr(), aDA.getLength() );
+ setFillColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
+ setLineColor( COL_TRANSPARENT );
+ aCheckRect.AdjustLeft(3*nDelta );
+ aCheckRect.AdjustTop(3*nDelta );
+ aCheckRect.AdjustBottom( -(3*nDelta) );
+ aCheckRect.AdjustRight( -(3*nDelta) );
+ drawEllipse( aCheckRect );
+ writeBuffer( "\nEMC\n", 5 );
+ endRedirect();
+
+ pop();
+ rBox.m_aAppearances[ "N" ][ "Yes" ] = pCheckStream;
+
+ SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 );
+ beginRedirect( pUncheckStream, aCheckRect );
+ writeBuffer( "/Tx BMC\nEMC\n", 12 );
+ endRedirect();
+ rBox.m_aAppearances[ "N" ][ "Off" ] = pUncheckStream;
+}
+
+bool PDFWriterImpl::emitAppearances( PDFWidget& rWidget, OStringBuffer& rAnnotDict )
+{
+ // TODO: check and insert default streams
+ OString aStandardAppearance;
+ switch( rWidget.m_eType )
+ {
+ case PDFWriter::CheckBox:
+ aStandardAppearance = OUStringToOString( rWidget.m_aValue, RTL_TEXTENCODING_ASCII_US );
+ break;
+ default:
+ break;
+ }
+
+ if( !rWidget.m_aAppearances.empty() )
+ {
+ rAnnotDict.append( "/AP<<\n" );
+ for (auto & dict_item : rWidget.m_aAppearances)
+ {
+ rAnnotDict.append( "/" );
+ rAnnotDict.append( dict_item.first );
+ bool bUseSubDict = (dict_item.second.size() > 1);
+
+ // PDF/A requires sub-dicts for /FT/Btn objects (clause
+ // 6.3.3)
+ if( m_bIsPDF_A1 || m_bIsPDF_A2 || m_bIsPDF_A3)
+ {
+ if( rWidget.m_eType == PDFWriter::RadioButton ||
+ rWidget.m_eType == PDFWriter::CheckBox ||
+ rWidget.m_eType == PDFWriter::PushButton )
+ {
+ bUseSubDict = true;
+ }
+ }
+
+ rAnnotDict.append( bUseSubDict ? "<<" : " " );
+
+ for (auto const& stream_item : dict_item.second)
+ {
+ SvMemoryStream* pApppearanceStream = stream_item.second;
+ dict_item.second[ stream_item.first ] = nullptr;
+
+ bool bDeflate = compressStream( pApppearanceStream );
+
+ sal_Int64 nStreamLen = pApppearanceStream->TellEnd();
+ pApppearanceStream->Seek( STREAM_SEEK_TO_BEGIN );
+ sal_Int32 nObject = createObject();
+ CHECK_RETURN( updateObject( nObject ) );
+ if (g_bDebugDisableCompression)
+ {
+ emitComment( "PDFWriterImpl::emitAppearances" );
+ }
+ OStringBuffer aLine;
+ aLine.append( nObject );
+
+ aLine.append( " 0 obj\n"
+ "<</Type/XObject\n"
+ "/Subtype/Form\n"
+ "/BBox[0 0 " );
+ appendFixedInt( rWidget.m_aRect.GetWidth()-1, aLine );
+ aLine.append( " " );
+ appendFixedInt( rWidget.m_aRect.GetHeight()-1, aLine );
+ aLine.append( "]\n"
+ "/Resources " );
+ aLine.append( getResourceDictObj() );
+ aLine.append( " 0 R\n"
+ "/Length " );
+ aLine.append( nStreamLen );
+ aLine.append( "\n" );
+ if( bDeflate )
+ aLine.append( "/Filter/FlateDecode\n" );
+ aLine.append( ">>\nstream\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+ checkAndEnableStreamEncryption( nObject );
+ CHECK_RETURN( writeBuffer( pApppearanceStream->GetData(), nStreamLen ) );
+ disableStreamEncryption();
+ CHECK_RETURN( writeBuffer( "\nendstream\nendobj\n\n", 19 ) );
+
+ if( bUseSubDict )
+ {
+ rAnnotDict.append( " /" );
+ rAnnotDict.append( stream_item.first );
+ rAnnotDict.append( " " );
+ }
+ rAnnotDict.append( nObject );
+ rAnnotDict.append( " 0 R" );
+
+ delete pApppearanceStream;
+ }
+
+ 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];
+
+ OStringBuffer aLine( 1024 );
+ OStringBuffer aValue( 256 );
+ aLine.append( rWidget.m_nObject );
+ aLine.append( " 0 obj\n"
+ "<<" );
+ if( rWidget.m_eType != PDFWriter::Hierarchy )
+ {
+ // emit widget annotation only for terminal fields
+ if( rWidget.m_aKids.empty() )
+ {
+ int iRectMargin;
+
+ aLine.append( "/Type/Annot/Subtype/Widget/F " );
+
+ if (rWidget.m_eType == PDFWriter::Signature)
+ {
+ aLine.append( "132\n" ); // Print & Locked
+ iRectMargin = 0;
+ }
+ else
+ {
+ aLine.append( "4\n" );
+ iRectMargin = 1;
+ }
+
+ aLine.append("/Rect[" );
+ appendFixedInt( rWidget.m_aRect.Left()-iRectMargin, aLine );
+ aLine.append( ' ' );
+ appendFixedInt( rWidget.m_aRect.Top()+iRectMargin, aLine );
+ aLine.append( ' ' );
+ appendFixedInt( rWidget.m_aRect.Right()+iRectMargin, aLine );
+ aLine.append( ' ' );
+ appendFixedInt( rWidget.m_aRect.Bottom()-iRectMargin, aLine );
+ aLine.append( "]\n" );
+ }
+ aLine.append( "/FT/" );
+ switch( rWidget.m_eType )
+ {
+ case PDFWriter::RadioButton:
+ case PDFWriter::CheckBox:
+ // for radio buttons only the RadioButton field, not the
+ // CheckBox children should have a value, else acrobat reader
+ // does not always check the right button
+ // of course real check boxes (not belonging to a radio group)
+ // need their values, too
+ if( rWidget.m_eType == PDFWriter::RadioButton || rWidget.m_nRadioGroup < 0 )
+ {
+ aValue.append( "/" );
+ // check for radio group with all buttons unpressed
+ if( rWidget.m_aValue.isEmpty() )
+ aValue.append( "Off" );
+ else
+ appendName( rWidget.m_aValue, aValue );
+ }
+ [[fallthrough]];
+ case PDFWriter::PushButton:
+ aLine.append( "Btn" );
+ break;
+ case PDFWriter::ListBox:
+ if( rWidget.m_nFlags & 0x200000 ) // multiselect
+ {
+ aValue.append( "[" );
+ for( size_t i = 0; i < rWidget.m_aSelectedEntries.size(); i++ )
+ {
+ sal_Int32 nEntry = rWidget.m_aSelectedEntries[i];
+ if( nEntry >= 0 && nEntry < sal_Int32(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 &&
+ rWidget.m_aSelectedEntries[0] < sal_Int32(rWidget.m_aListEntries.size()) )
+ {
+ appendUnicodeTextStringEncrypt( rWidget.m_aListEntries[ rWidget.m_aSelectedEntries[0] ], rWidget.m_nObject, aValue );
+ }
+ else
+ appendUnicodeTextStringEncrypt( OUString(), rWidget.m_nObject, aValue );
+ aLine.append( "Ch" );
+ break;
+ case PDFWriter::ComboBox:
+ appendUnicodeTextStringEncrypt( rWidget.m_aValue, rWidget.m_nObject, aValue );
+ aLine.append( "Ch" );
+ break;
+ case PDFWriter::Edit:
+ aLine.append( "Tx" );
+ appendUnicodeTextStringEncrypt( rWidget.m_aValue, rWidget.m_nObject, aValue );
+ break;
+ case PDFWriter::Signature:
+ aLine.append( "Sig" );
+ aValue.append(OUStringToOString(rWidget.m_aValue, RTL_TEXTENCODING_ASCII_US));
+ break;
+ case PDFWriter::Hierarchy: // make the compiler happy
+ break;
+ }
+ aLine.append( "\n" );
+ aLine.append( "/P " );
+ aLine.append( m_aPages[ rWidget.m_nPage ].m_nPageObject );
+ aLine.append( " 0 R\n" );
+ }
+ if( rWidget.m_nParent )
+ {
+ aLine.append( "/Parent " );
+ aLine.append( rWidget.m_nParent );
+ aLine.append( " 0 R\n" );
+ }
+ if( !rWidget.m_aKids.empty() )
+ {
+ aLine.append( "/Kids[" );
+ for( size_t i = 0; i < rWidget.m_aKids.size(); i++ )
+ {
+ aLine.append( rWidget.m_aKids[i] );
+ aLine.append( " 0 R" );
+ aLine.append( ( (i&15) == 15 ) ? "\n" : " " );
+ }
+ aLine.append( "]\n" );
+ }
+ if( !rWidget.m_aName.isEmpty() )
+ {
+ aLine.append( "/T" );
+ appendLiteralStringEncrypt( rWidget.m_aName, rWidget.m_nObject, aLine );
+ aLine.append( "\n" );
+ }
+ if( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_2 && !rWidget.m_aDescription.isEmpty() )
+ {
+ // the alternate field name should be unicode able since it is
+ // supposed to be used in UI
+ aLine.append( "/TU" );
+ appendUnicodeTextStringEncrypt( rWidget.m_aDescription, rWidget.m_nObject, aLine );
+ aLine.append( "\n" );
+ }
+
+ if( rWidget.m_nFlags )
+ {
+ aLine.append( "/Ff " );
+ aLine.append( rWidget.m_nFlags );
+ aLine.append( "\n" );
+ }
+ if( !aValue.isEmpty() )
+ {
+ OString aVal = aValue.makeStringAndClear();
+ aLine.append( "/V " );
+ aLine.append( aVal );
+ aLine.append( "\n"
+ "/DV " );
+ aLine.append( aVal );
+ aLine.append( "\n" );
+ }
+ if( rWidget.m_eType == PDFWriter::ListBox || rWidget.m_eType == PDFWriter::ComboBox )
+ {
+ sal_Int32 nTI = -1;
+ aLine.append( "/Opt[\n" );
+ sal_Int32 i = 0;
+ for (auto const& entry : rWidget.m_aListEntries)
+ {
+ appendUnicodeTextStringEncrypt( entry, rWidget.m_nObject, aLine );
+ aLine.append( "\n" );
+ if( entry == rWidget.m_aValue )
+ nTI = i;
+ ++i;
+ }
+ aLine.append( "]\n" );
+ if( nTI > 0 )
+ {
+ aLine.append( "/TI " );
+ aLine.append( nTI );
+ aLine.append( "\n" );
+ if( rWidget.m_nFlags & 0x200000 ) // Multiselect
+ {
+ aLine.append( "/I [" );
+ aLine.append( nTI );
+ aLine.append( "]\n" );
+ }
+ }
+ }
+ if( rWidget.m_eType == PDFWriter::Edit && rWidget.m_nMaxLen > 0 )
+ {
+ aLine.append( "/MaxLen " );
+ aLine.append( rWidget.m_nMaxLen );
+ 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.makeStringAndClear() );
+ aLine.append( ">>>>\n" );
+ }
+ else if( rWidget.m_aListEntries.empty() )
+ {
+ if( !m_bIsPDF_A2 && !m_bIsPDF_A3 )
+ {
+ // create a reset form action
+ aLine.append( "/AA<</D<</Type/Action/S/ResetForm>>>>\n" );
+ }
+ }
+ else if( rWidget.m_bSubmit )
+ {
+ // create a submit form action
+ aLine.append( "/AA<</D<</Type/Action/S/SubmitForm/F" );
+ appendLiteralStringEncrypt( rWidget.m_aListEntries.front(), rWidget.m_nObject, aLine, osl_getThreadTextEncoding() );
+ aLine.append( "/Flags " );
+
+ sal_Int32 nFlags = 0;
+ switch( m_aContext.SubmitFormat )
+ {
+ case PDFWriter::HTML:
+ nFlags |= 4;
+ break;
+ case PDFWriter::XML:
+ if( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
+ nFlags |= 32;
+ break;
+ case PDFWriter::PDF:
+ if( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
+ nFlags |= 256;
+ break;
+ case PDFWriter::FDF:
+ default:
+ break;
+ }
+ if( rWidget.m_bSubmitGet )
+ nFlags |= 8;
+ aLine.append( nFlags );
+ aLine.append( ">>>>\n" );
+ }
+ else
+ {
+ // create a URI action
+ aLine.append( "/AA<</D<</Type/Action/S/URI/URI(" );
+ aLine.append( OUStringToOString( rWidget.m_aListEntries.front(), RTL_TEXTENCODING_ASCII_US ) );
+ aLine.append( ")>>>>\n" );
+ }
+ }
+ else
+ m_aErrors.insert( PDFWriter::Warning_FormAction_Omitted_PDFA );
+ }
+ if( !rWidget.m_aDAString.isEmpty() )
+ {
+ if( !rWidget.m_aDRDict.isEmpty() )
+ {
+ aLine.append( "/DR<<" );
+ aLine.append( rWidget.m_aDRDict );
+ aLine.append( ">>\n" );
+ }
+ else
+ {
+ aLine.append( "/DR<</Font<<" );
+ appendBuildinFontsToDict( aLine );
+ aLine.append( ">>>>\n" );
+ }
+ aLine.append( "/DA" );
+ appendLiteralStringEncrypt( rWidget.m_aDAString, rWidget.m_nObject, aLine );
+ aLine.append( "\n" );
+ if( rWidget.m_nTextStyle & DrawTextFlags::Center )
+ aLine.append( "/Q 1\n" );
+ else if( rWidget.m_nTextStyle & DrawTextFlags::Right )
+ aLine.append( "/Q 2\n" );
+ }
+ // appearance characteristics for terminal fields
+ // which are supposed to have an appearance constructed
+ // by the viewer application
+ if( !rWidget.m_aMKDict.isEmpty() )
+ {
+ aLine.append( "/MK<<" );
+ aLine.append( rWidget.m_aMKDict );
+ //add the CA string, encrypting it
+ appendLiteralStringEncrypt(rWidget.m_aMKDictCAString, rWidget.m_nObject, aLine);
+ aLine.append( ">>\n" );
+ }
+
+ CHECK_RETURN( emitAppearances( rWidget, aLine ) );
+
+ aLine.append( ">>\n"
+ "endobj\n\n" );
+ CHECK_RETURN( updateObject( rWidget.m_nObject ) );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+ }
+ return true;
+}
+
+bool PDFWriterImpl::emitAnnotations()
+{
+ if( m_aPages.empty() )
+ return false;
+
+ CHECK_RETURN( emitLinkAnnotations() );
+ CHECK_RETURN(emitScreenAnnotations());
+ CHECK_RETURN( emitNoteAnnotations() );
+ CHECK_RETURN( emitWidgetAnnotations() );
+
+ return true;
+}
+
+bool PDFWriterImpl::emitEmbeddedFiles()
+{
+ for (auto& rEmbeddedFile : m_aEmbeddedFiles)
+ {
+ if (!updateObject(rEmbeddedFile.m_nObject))
+ continue;
+
+ OStringBuffer aLine;
+ aLine.append(rEmbeddedFile.m_nObject);
+ aLine.append(" 0 obj\n");
+ aLine.append("<< /Type /EmbeddedFile /Length ");
+ aLine.append(static_cast<sal_Int64>(rEmbeddedFile.m_pData->size()));
+ aLine.append(" >>\nstream\n");
+ CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
+ aLine.setLength(0);
+
+ CHECK_RETURN(writeBuffer(rEmbeddedFile.m_pData->data(), rEmbeddedFile.m_pData->size()));
+
+ aLine.append("\nendstream\nendobj\n\n");
+ CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
+ }
+ return true;
+}
+
+#undef CHECK_RETURN
+#define CHECK_RETURN( x ) if( !x ) return false
+
+bool PDFWriterImpl::emitCatalog()
+{
+ // build page tree
+ // currently there is only one node that contains all leaves
+
+ // first create a page tree node id
+ sal_Int32 nTreeNode = createObject();
+
+ // emit global resource dictionary (page emit needs it)
+ CHECK_RETURN( emitResources() );
+
+ // emit all pages
+ for (auto & page : m_aPages)
+ if( ! page.emit( nTreeNode ) )
+ return false;
+
+ sal_Int32 nNamedDestinationsDictionary = emitNamedDestinations();
+
+ sal_Int32 nOutlineDict = emitOutline();
+
+ // emit Output intent
+ sal_Int32 nOutputIntentObject = emitOutputIntent();
+
+ // emit metadata
+ sal_Int32 nMetadataObject = emitDocumentMetadata();
+
+ sal_Int32 nStructureDict = 0;
+ if(m_aStructure.size() > 1)
+ {
+ // check if dummy structure containers are needed
+ addInternalStructureContainer(m_aStructure[0]);
+ nStructureDict = m_aStructure[0].m_nObject = createObject();
+ emitStructure( m_aStructure[ 0 ] );
+ }
+
+ // adjust tree node file offset
+ if( ! updateObject( nTreeNode ) )
+ return false;
+
+ // emit tree node
+ OStringBuffer aLine( 2048 );
+ aLine.append( nTreeNode );
+ aLine.append( " 0 obj\n" );
+ aLine.append( "<</Type/Pages\n" );
+ aLine.append( "/Resources " );
+ aLine.append( getResourceDictObj() );
+ aLine.append( " 0 R\n" );
+
+ sal_Int32 nMediaBoxWidth = 0;
+ sal_Int32 nMediaBoxHeight = 0;
+ sal_Int32 nUserUnit = 1;
+ if( m_aPages.empty() ) // sanity check, this should not happen
+ {
+ nMediaBoxWidth = g_nInheritedPageWidth;
+ nMediaBoxHeight = g_nInheritedPageHeight;
+ }
+ else
+ {
+ for (auto const& page : m_aPages)
+ {
+ if( page.m_nPageWidth > nMediaBoxWidth )
+ {
+ nMediaBoxWidth = page.m_nPageWidth;
+ nUserUnit = page.m_nUserUnit;
+ }
+ if( page.m_nPageHeight > nMediaBoxHeight )
+ {
+ nMediaBoxHeight = page.m_nPageHeight;
+ nUserUnit = page.m_nUserUnit;
+ }
+ }
+ }
+ aLine.append( "/MediaBox[ 0 0 " );
+ aLine.append(nMediaBoxWidth / nUserUnit);
+ aLine.append( ' ' );
+ aLine.append(nMediaBoxHeight / nUserUnit);
+ aLine.append(" ]\n");
+ if (nUserUnit > 1)
+ {
+ aLine.append("/UserUnit ");
+ aLine.append(nUserUnit);
+ aLine.append("\n");
+ }
+ aLine.append("/Kids[ ");
+ unsigned int i = 0;
+ for (const auto & page : m_aPages)
+ {
+ aLine.append( page.m_nPageObject );
+ aLine.append( " 0 R" );
+ aLine.append( ( (i&15) == 15 ) ? "\n" : " " );
+ ++i;
+ }
+ aLine.append( "]\n"
+ "/Count " );
+ aLine.append( static_cast<sal_Int32>(m_aPages.size()) );
+ aLine.append( ">>\n"
+ "endobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+
+ // emit annotation objects
+ CHECK_RETURN( emitAnnotations() );
+ CHECK_RETURN( emitEmbeddedFiles() );
+
+ // emit Catalog
+ m_nCatalogObject = createObject();
+ if( ! updateObject( m_nCatalogObject ) )
+ return false;
+ aLine.setLength( 0 );
+ aLine.append( m_nCatalogObject );
+ aLine.append( " 0 obj\n"
+ "<</Type/Catalog/Pages " );
+ aLine.append( nTreeNode );
+ aLine.append( " 0 R\n" );
+
+ // check if there are named destinations to emit (root must be inside the catalog)
+ if( nNamedDestinationsDictionary )
+ {
+ aLine.append("/Dests ");
+ aLine.append( nNamedDestinationsDictionary );
+ aLine.append( " 0 R\n" );
+ }
+
+ if( m_aContext.PageLayout != PDFWriter::DefaultLayout )
+ switch( m_aContext.PageLayout )
+ {
+ default :
+ case PDFWriter::SinglePage :
+ aLine.append( "/PageLayout/SinglePage\n" );
+ break;
+ case PDFWriter::Continuous :
+ aLine.append( "/PageLayout/OneColumn\n" );
+ break;
+ case PDFWriter::ContinuousFacing :
+ // the flag m_aContext.FirstPageLeft below is used to set the page on the left side
+ aLine.append( "/PageLayout/TwoColumnRight\n" );//odd page on the right side
+ break;
+ }
+ if( m_aContext.PDFDocumentMode != PDFWriter::ModeDefault && !m_aContext.OpenInFullScreenMode )
+ switch( m_aContext.PDFDocumentMode )
+ {
+ default :
+ aLine.append( "/PageMode/UseNone\n" );
+ break;
+ case PDFWriter::UseOutlines :
+ aLine.append( "/PageMode/UseOutlines\n" ); //document is opened with outline pane open
+ break;
+ case PDFWriter::UseThumbs :
+ aLine.append( "/PageMode/UseThumbs\n" ); //document is opened with thumbnails pane open
+ break;
+ }
+ else if( m_aContext.OpenInFullScreenMode )
+ aLine.append( "/PageMode/FullScreen\n" ); //document is opened full screen
+
+ OStringBuffer aInitPageRef;
+ if( m_aContext.InitialPage >= 0 && m_aContext.InitialPage < static_cast<sal_Int32>(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.makeStringAndClear() );
+ aLine.append( " /XYZ null null 0]\n" );
+ }
+ break;
+ case PDFWriter::FitInWindow :
+ aLine.append( "/OpenAction[" );
+ aLine.append( aInitPageRef.makeStringAndClear() );
+ aLine.append( " /Fit]\n" ); //Open fit page
+ break;
+ case PDFWriter::FitWidth :
+ aLine.append( "/OpenAction[" );
+ aLine.append( aInitPageRef.makeStringAndClear() );
+ aLine.append( " /FitH " );
+ aLine.append( g_nInheritedPageHeight );//Open fit width
+ aLine.append( "]\n" );
+ break;
+ case PDFWriter::FitVisible :
+ aLine.append( "/OpenAction[" );
+ aLine.append( aInitPageRef.makeStringAndClear() );
+ aLine.append( " /FitBH " );
+ aLine.append( g_nInheritedPageHeight );//Open fit visible
+ aLine.append( "]\n" );
+ break;
+ case PDFWriter::ActionZoom :
+ aLine.append( "/OpenAction[" );
+ aLine.append( aInitPageRef.makeStringAndClear() );
+ aLine.append( " /XYZ null null " );
+ if( m_aContext.Zoom >= 50 && m_aContext.Zoom <= 1600 )
+ aLine.append( static_cast<double>(m_aContext.Zoom)/100.0 );
+ else
+ aLine.append( "0" );
+ aLine.append( "]\n" );
+ break;
+ }
+
+ // viewer preferences, if we had some, then emit
+ if( m_aContext.HideViewerToolbar ||
+ ( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 && !m_aContext.DocumentInfo.Title.isEmpty() && m_aContext.DisplayPDFDocumentTitle ) ||
+ m_aContext.HideViewerMenubar ||
+ m_aContext.HideViewerWindowControls || m_aContext.FitWindow ||
+ m_aContext.CenterWindow || (m_aContext.FirstPageLeft && m_aContext.PageLayout == PDFWriter::ContinuousFacing ) ||
+ m_aContext.OpenInFullScreenMode )
+ {
+ aLine.append( "/ViewerPreferences<<" );
+ if( m_aContext.HideViewerToolbar )
+ aLine.append( "/HideToolbar true\n" );
+ if( m_aContext.HideViewerMenubar )
+ aLine.append( "/HideMenubar true\n" );
+ if( m_aContext.HideViewerWindowControls )
+ aLine.append( "/HideWindowUI true\n" );
+ if( m_aContext.FitWindow )
+ aLine.append( "/FitWindow true\n" );
+ if( m_aContext.CenterWindow )
+ aLine.append( "/CenterWindow true\n" );
+ if( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 && !m_aContext.DocumentInfo.Title.isEmpty() && m_aContext.DisplayPDFDocumentTitle )
+ aLine.append( "/DisplayDocTitle true\n" );
+ if( m_aContext.FirstPageLeft && m_aContext.PageLayout == PDFWriter::ContinuousFacing )
+ aLine.append( "/Direction/R2L\n" );
+ if( m_aContext.OpenInFullScreenMode )
+ switch( m_aContext.PDFDocumentMode )
+ {
+ default :
+ case PDFWriter::ModeDefault :
+ aLine.append( "/NonFullScreenPageMode/UseNone\n" );
+ break;
+ case PDFWriter::UseOutlines :
+ aLine.append( "/NonFullScreenPageMode/UseOutlines\n" );
+ break;
+ case PDFWriter::UseThumbs :
+ aLine.append( "/NonFullScreenPageMode/UseThumbs\n" );
+ break;
+ }
+ aLine.append( ">>\n" );
+ }
+
+ if( nOutlineDict )
+ {
+ aLine.append( "/Outlines " );
+ aLine.append( nOutlineDict );
+ aLine.append( " 0 R\n" );
+ }
+ if( nStructureDict )
+ {
+ aLine.append( "/StructTreeRoot " );
+ aLine.append( nStructureDict );
+ aLine.append( " 0 R\n" );
+ }
+ if( !m_aContext.DocumentLocale.Language.isEmpty() )
+ {
+ /* PDF allows only RFC 3066, see above in emitStructure(). */
+ LanguageTag aLanguageTag( m_aContext.DocumentLocale);
+ OUString aLanguage, aScript, aCountry;
+ aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, aCountry);
+ if (!aLanguage.isEmpty())
+ {
+ OUStringBuffer aLocBuf( 16 );
+ aLocBuf.append( aLanguage );
+ if( !aCountry.isEmpty() )
+ {
+ aLocBuf.append( '-' );
+ aLocBuf.append( aCountry );
+ }
+ aLine.append( "/Lang" );
+ appendLiteralStringEncrypt( aLocBuf.makeStringAndClear(), m_nCatalogObject, aLine );
+ aLine.append( "\n" );
+ }
+ }
+ if( m_aContext.Tagged && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
+ {
+ aLine.append( "/MarkInfo<</Marked true>>\n" );
+ }
+ if( !m_aWidgets.empty() )
+ {
+ aLine.append( "/AcroForm<</Fields[\n" );
+ int nWidgets = m_aWidgets.size();
+ int nOut = 0;
+ for( int j = 0; j < nWidgets; j++ )
+ {
+ // output only root fields
+ if( m_aWidgets[j].m_nParent < 1 )
+ {
+ aLine.append( m_aWidgets[j].m_nObject );
+ aLine.append( (nOut++ % 5)==4 ? " 0 R\n" : " 0 R " );
+ }
+ }
+ aLine.append( "\n]" );
+
+#if HAVE_FEATURE_NSS
+ if (m_nSignatureObject != -1)
+ aLine.append( "/SigFlags 3");
+#endif
+
+ aLine.append( "/DR " );
+ aLine.append( getResourceDictObj() );
+ aLine.append( " 0 R" );
+ // NeedAppearances must not be used if PDF is signed
+ if( m_bIsPDF_A1 || m_bIsPDF_A2 || m_bIsPDF_A3
+#if HAVE_FEATURE_NSS
+ || ( m_nSignatureObject != -1 )
+#endif
+ )
+ aLine.append( ">>\n" );
+ else
+ aLine.append( "/NeedAppearances true>>\n" );
+ }
+
+ //check if there is a Metadata object
+ if( nOutputIntentObject )
+ {
+ aLine.append("/OutputIntents[");
+ aLine.append( nOutputIntentObject );
+ aLine.append( " 0 R]" );
+ }
+
+ if( nMetadataObject )
+ {
+ aLine.append("/Metadata ");
+ aLine.append( nMetadataObject );
+ aLine.append( " 0 R" );
+ }
+
+ aLine.append( ">>\n"
+ "endobj\n\n" );
+ return writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+#if HAVE_FEATURE_NSS
+
+bool PDFWriterImpl::emitSignature()
+{
+ if( !updateObject( m_nSignatureObject ) )
+ return false;
+
+ OStringBuffer aLine( 0x5000 );
+ aLine.append( m_nSignatureObject );
+ aLine.append( " 0 obj\n" );
+ aLine.append("<</Contents <" );
+
+ sal_uInt64 nOffset = ~0U;
+ CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nOffset) ) );
+
+ m_nSignatureContentOffset = nOffset + aLine.getLength();
+
+ // reserve some space for the PKCS#7 object
+ OStringBuffer aContentFiller( MAX_SIGNATURE_CONTENT_LENGTH );
+ comphelper::string::padToLength(aContentFiller, MAX_SIGNATURE_CONTENT_LENGTH, '0');
+ aLine.append( aContentFiller.makeStringAndClear() );
+ 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.makeStringAndClear() );
+ aLine.append(" /Filter/Adobe.PPKMS");
+
+ //emit reason, location and contactinfo
+ if ( !m_aContext.SignReason.isEmpty() )
+ {
+ aLine.append("/Reason");
+ appendUnicodeTextStringEncrypt( m_aContext.SignReason, m_nSignatureObject, aLine );
+ }
+
+ if ( !m_aContext.SignLocation.isEmpty() )
+ {
+ aLine.append("/Location");
+ appendUnicodeTextStringEncrypt( m_aContext.SignLocation, m_nSignatureObject, aLine );
+ }
+
+ if ( !m_aContext.SignContact.isEmpty() )
+ {
+ aLine.append("/ContactInfo");
+ appendUnicodeTextStringEncrypt( m_aContext.SignContact, m_nSignatureObject, aLine );
+ }
+
+ aLine.append(" >>\nendobj\n\n" );
+
+ return writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+bool PDFWriterImpl::finalizeSignature()
+{
+ if (!m_aContext.SignCertificate.is())
+ return false;
+
+ // 1- calculate last ByteRange value
+ sal_uInt64 nOffset = ~0U;
+ CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nOffset) ) );
+
+ sal_Int64 nLastByteRangeNo = nOffset - (m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1);
+
+ // 2- overwrite the value to the m_nSignatureLastByteRangeNoOffset position
+ sal_uInt64 nWritten = 0;
+ CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, m_nSignatureLastByteRangeNoOffset) ) );
+ OString aByteRangeNo = OString::number( nLastByteRangeNo ) + " ]";
+
+ if (m_aFile.write(aByteRangeNo.getStr(), aByteRangeNo.getLength(), nWritten) != osl::File::E_None)
+ {
+ CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, nOffset)) );
+ return false;
+ }
+
+ // 3- create the PKCS#7 object using NSS
+
+ // Prepare buffer and calculate PDF file digest
+ CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, 0)) );
+
+ std::unique_ptr<char[]> buffer1(new char[m_nSignatureContentOffset + 1]);
+ sal_uInt64 bytesRead1;
+
+ //FIXME: Check if hash is calculated from the correct byterange
+ if (osl::File::E_None != m_aFile.read(buffer1.get(), m_nSignatureContentOffset - 1 , bytesRead1) ||
+ bytesRead1 != static_cast<sal_uInt64>(m_nSignatureContentOffset) - 1)
+ {
+ SAL_WARN("vcl.pdfwriter", "First buffer read failed");
+ return false;
+ }
+
+ std::unique_ptr<char[]> buffer2(new char[nLastByteRangeNo + 1]);
+ sal_uInt64 bytesRead2;
+
+ if (osl::File::E_None != m_aFile.setPos(osl_Pos_Absolut, m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1) ||
+ osl::File::E_None != m_aFile.read(buffer2.get(), nLastByteRangeNo, bytesRead2) ||
+ bytesRead2 != static_cast<sal_uInt64>(nLastByteRangeNo))
+ {
+ SAL_WARN("vcl.pdfwriter", "Second buffer read failed");
+ return false;
+ }
+
+ OStringBuffer aCMSHexBuffer;
+ svl::crypto::Signing aSigning(m_aContext.SignCertificate);
+ aSigning.AddDataRange(buffer1.get(), bytesRead1);
+ aSigning.AddDataRange(buffer2.get(), bytesRead2);
+ aSigning.SetSignTSA(m_aContext.SignTSA);
+ aSigning.SetSignPassword(m_aContext.SignPassword);
+ if (!aSigning.Sign(aCMSHexBuffer))
+ {
+ SAL_WARN("vcl.pdfwriter", "PDFWriter::Sign() failed");
+ return false;
+ }
+
+ assert(aCMSHexBuffer.getLength() <= MAX_SIGNATURE_CONTENT_LENGTH);
+
+ // Set file pointer to the m_nSignatureContentOffset, we're ready to overwrite PKCS7 object
+ nWritten = 0;
+ CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, m_nSignatureContentOffset)) );
+ m_aFile.write(aCMSHexBuffer.getStr(), aCMSHexBuffer.getLength(), nWritten);
+
+ return osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, nOffset);
+}
+
+#endif //HAVE_FEATURE_NSS
+
+sal_Int32 PDFWriterImpl::emitInfoDict( )
+{
+ sal_Int32 nObject = createObject();
+
+ if( updateObject( nObject ) )
+ {
+ OStringBuffer aLine( 1024 );
+ aLine.append( nObject );
+ aLine.append( " 0 obj\n"
+ "<<" );
+ if( !m_aContext.DocumentInfo.Title.isEmpty() )
+ {
+ aLine.append( "/Title" );
+ appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Title, nObject, aLine );
+ aLine.append( "\n" );
+ }
+ if( !m_aContext.DocumentInfo.Author.isEmpty() )
+ {
+ aLine.append( "/Author" );
+ appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Author, nObject, aLine );
+ aLine.append( "\n" );
+ }
+ if( !m_aContext.DocumentInfo.Subject.isEmpty() )
+ {
+ aLine.append( "/Subject" );
+ appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Subject, nObject, aLine );
+ aLine.append( "\n" );
+ }
+ if( !m_aContext.DocumentInfo.Keywords.isEmpty() )
+ {
+ aLine.append( "/Keywords" );
+ appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Keywords, nObject, aLine );
+ aLine.append( "\n" );
+ }
+ if( !m_aContext.DocumentInfo.Creator.isEmpty() )
+ {
+ aLine.append( "/Creator" );
+ appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Creator, nObject, aLine );
+ aLine.append( "\n" );
+ }
+ if( !m_aContext.DocumentInfo.Producer.isEmpty() )
+ {
+ aLine.append( "/Producer" );
+ appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Producer, nObject, aLine );
+ aLine.append( "\n" );
+ }
+
+ aLine.append( "/CreationDate" );
+ appendLiteralStringEncrypt( m_aCreationDateString, nObject, aLine );
+ aLine.append( ">>\nendobj\n\n" );
+ if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) )
+ nObject = 0;
+ }
+ else
+ nObject = 0;
+
+ return nObject;
+}
+
+// Part of this function may be shared with method appendDest.
+sal_Int32 PDFWriterImpl::emitNamedDestinations()
+{
+ sal_Int32 nCount = m_aNamedDests.size();
+ if( nCount <= 0 )
+ return 0;//define internal error
+
+ //get the object number for all the destinations
+ sal_Int32 nObject = createObject();
+
+ if( updateObject( nObject ) )
+ {
+ //emit the dictionary
+ OStringBuffer aLine( 1024 );
+ aLine.append( nObject );
+ aLine.append( " 0 obj\n"
+ "<<" );
+
+ sal_Int32 nDestID;
+ for( nDestID = 0; nDestID < nCount; nDestID++ )
+ {
+ const PDFNamedDest& rDest = m_aNamedDests[ nDestID ];
+ // In order to correctly function both under an Internet browser and
+ // directly with a reader (provided the reader has the feature) we
+ // need to set the name of the destination the same way it will be encoded
+ // in an Internet link
+ INetURLObject aLocalURL( "http://ahost.ax" ); //dummy location, won't be used
+ aLocalURL.SetMark( rDest.m_aDestName );
+
+ const OUString aName = aLocalURL.GetMark( INetURLObject::DecodeMechanism::NONE ); //same coding as
+ // in link creation ( see PDFWriterImpl::emitLinkAnnotations )
+ const PDFPage& rDestPage = m_aPages[ rDest.m_nPage ];
+
+ aLine.append( '/' );
+ appendDestinationName( aName, aLine ); // this conversion must be done when forming the link to target ( see in emitCatalog )
+ aLine.append( '[' ); // the '[' can be emitted immediately, because the appendDestinationName function
+ //maps the preceding character properly
+ aLine.append( rDestPage.m_nPageObject );
+ aLine.append( " 0 R" );
+
+ switch( rDest.m_eType )
+ {
+ case PDFWriter::DestAreaType::XYZ:
+ default:
+ aLine.append( "/XYZ " );
+ appendFixedInt( rDest.m_aRect.Left(), aLine );
+ aLine.append( ' ' );
+ appendFixedInt( rDest.m_aRect.Bottom(), aLine );
+ aLine.append( " 0" );
+ break;
+ case PDFWriter::DestAreaType::FitRectangle:
+ aLine.append( "/FitR " );
+ appendFixedInt( rDest.m_aRect.Left(), aLine );
+ aLine.append( ' ' );
+ appendFixedInt( rDest.m_aRect.Top(), aLine );
+ aLine.append( ' ' );
+ appendFixedInt( rDest.m_aRect.Right(), aLine );
+ aLine.append( ' ' );
+ appendFixedInt( rDest.m_aRect.Bottom(), aLine );
+ break;
+ }
+ aLine.append( "]\n" );
+ }
+
+ //close
+ aLine.append( ">>\nendobj\n\n" );
+ if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) )
+ nObject = 0;
+ }
+ else
+ nObject = 0;
+
+ return nObject;
+}
+
+// emits the output intent dictionary
+sal_Int32 PDFWriterImpl::emitOutputIntent()
+{
+ if( !m_bIsPDF_A1 && !m_bIsPDF_A2 && !m_bIsPDF_A3 )
+ return 0;
+
+ //emit the sRGB standard profile, in ICC format, in a stream, per IEC61966-2.1
+
+ OStringBuffer aLine( 1024 );
+ sal_Int32 nICCObject = createObject();
+ sal_Int32 nStreamLengthObject = createObject();
+
+ aLine.append( nICCObject );
+// sRGB has 3 colors, hence /N 3 below (PDF 1.4 table 4.16)
+ aLine.append( " 0 obj\n<</N 3/Length " );
+ aLine.append( nStreamLengthObject );
+ aLine.append( " 0 R" );
+ if (!g_bDebugDisableCompression)
+ aLine.append( "/Filter/FlateDecode" );
+ aLine.append( ">>\nstream\n" );
+ if ( !updateObject( nICCObject ) ) return 0;
+ if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return 0;
+ //get file position
+ sal_uInt64 nBeginStreamPos = 0;
+ if (osl::File::E_None != m_aFile.getPos(nBeginStreamPos))
+ return 0;
+ beginCompression();
+ checkAndEnableStreamEncryption( nICCObject );
+ cmsHPROFILE hProfile = cmsCreate_sRGBProfile();
+ //force ICC profile version 2.1
+ cmsSetProfileVersion(hProfile, 2.1);
+ cmsUInt32Number nBytesNeeded = 0;
+ cmsSaveProfileToMem(hProfile, nullptr, &nBytesNeeded);
+ if (!nBytesNeeded)
+ return 0;
+ std::vector<unsigned char> aBuffer(nBytesNeeded);
+ cmsSaveProfileToMem(hProfile, aBuffer.data(), &nBytesNeeded);
+ cmsCloseProfile(hProfile);
+ bool written = writeBuffer( aBuffer.data(), static_cast<sal_Int32>(aBuffer.size()) );
+ disableStreamEncryption();
+ endCompression();
+
+ sal_uInt64 nEndStreamPos = 0;
+ if (m_aFile.getPos(nEndStreamPos) != osl::File::E_None)
+ return 0;
+
+ if( !written )
+ return 0;
+ if( ! writeBuffer( "\nendstream\nendobj\n\n", 19 ) )
+ return 0 ;
+ aLine.setLength( 0 );
+
+ //emit the stream length object
+ if ( !updateObject( nStreamLengthObject ) ) return 0;
+ aLine.setLength( 0 );
+ aLine.append( nStreamLengthObject );
+ aLine.append( " 0 obj\n" );
+ aLine.append( static_cast<sal_Int64>(nEndStreamPos-nBeginStreamPos) );
+ aLine.append( "\nendobj\n\n" );
+ if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return 0;
+ aLine.setLength( 0 );
+
+ //emit the OutputIntent dictionary
+ sal_Int32 nOIObject = createObject();
+ if ( !updateObject( nOIObject ) ) return 0;
+ aLine.append( nOIObject );
+ aLine.append( " 0 obj\n"
+ "<</Type/OutputIntent/S/GTS_PDFA1/OutputConditionIdentifier");
+
+ OUString const aComment( "sRGB IEC61966-2.1" );
+ appendLiteralStringEncrypt( aComment ,nOIObject, aLine );
+ aLine.append("/DestOutputProfile ");
+ aLine.append( nICCObject );
+ aLine.append( " 0 R>>\nendobj\n\n" );
+ if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return 0;
+
+ return nOIObject;
+}
+
+// formats the string for the XML stream
+static void escapeStringXML( const OUString& rStr, OUString &rValue)
+{
+ const sal_Unicode* pUni = rStr.getStr();
+ int nLen = rStr.getLength();
+ for( ; nLen; nLen--, pUni++ )
+ {
+ switch( *pUni )
+ {
+ case u'&':
+ rValue += "&amp;";
+ break;
+ case u'<':
+ rValue += "&lt;";
+ break;
+ case u'>':
+ rValue += "&gt;";
+ break;
+ case u'\'':
+ rValue += "&apos;";
+ break;
+ case u'"':
+ rValue += "&quot;";
+ break;
+ default:
+ rValue += OUStringChar( *pUni );
+ break;
+ }
+ }
+}
+
+static void lcl_assignMeta(const OUString& aValue, OString& aMeta)
+{
+ if (!aValue.isEmpty())
+ {
+ OUString aTempString;
+ escapeStringXML(aValue, aTempString);
+ aMeta = OUStringToOString(aTempString, RTL_TEXTENCODING_UTF8);
+ }
+}
+
+// emits the document metadata
+sal_Int32 PDFWriterImpl::emitDocumentMetadata()
+{
+ if( !m_bIsPDF_A1 && !m_bIsPDF_A2 && !m_bIsPDF_A3 )
+ return 0;
+
+ //get the object number for all the destinations
+ sal_Int32 nObject = createObject();
+
+ if( updateObject( nObject ) )
+ {
+ pdf::XmpMetadata aMetadata;
+
+ if (m_bIsPDF_A1)
+ aMetadata.mnPDF_A = 1;
+ else if (m_bIsPDF_A2)
+ aMetadata.mnPDF_A = 2;
+ else if (m_bIsPDF_A3)
+ aMetadata.mnPDF_A = 3;
+
+ aMetadata.mbPDF_UA = m_bIsPDF_UA;
+
+ lcl_assignMeta(m_aContext.DocumentInfo.Title, aMetadata.msTitle);
+ lcl_assignMeta(m_aContext.DocumentInfo.Author, aMetadata.msAuthor);
+ lcl_assignMeta(m_aContext.DocumentInfo.Subject, aMetadata.msSubject);
+ lcl_assignMeta(m_aContext.DocumentInfo.Producer, aMetadata.msProducer);
+ lcl_assignMeta(m_aContext.DocumentInfo.Keywords, aMetadata.msKeywords);
+ lcl_assignMeta(m_aContext.DocumentInfo.Creator, aMetadata.m_sCreatorTool);
+ aMetadata.m_sCreateDate = m_aCreationMetaDateString;
+
+ OStringBuffer aMetadataObj( 1024 );
+
+ aMetadataObj.append( nObject );
+ aMetadataObj.append( " 0 obj\n" );
+
+ aMetadataObj.append( "<</Type/Metadata/Subtype/XML/Length " );
+
+ aMetadataObj.append( sal_Int32(aMetadata.getSize()) );
+ aMetadataObj.append( ">>\nstream\n" );
+ if ( !writeBuffer( aMetadataObj.getStr(), aMetadataObj.getLength() ) )
+ return 0;
+ //emit the stream
+ if ( !writeBuffer( aMetadata.getData(), aMetadata.getSize() ) )
+ return 0;
+
+ aMetadataObj.setLength( 0 );
+ aMetadataObj.append( "\nendstream\nendobj\n\n" );
+ if( ! writeBuffer( aMetadataObj.getStr(), aMetadataObj.getLength() ) )
+ nObject = 0;
+ }
+ else
+ nObject = 0;
+
+ return nObject;
+}
+
+bool PDFWriterImpl::emitTrailer()
+{
+ // emit doc info
+ sal_Int32 nDocInfoObject = emitInfoDict( );
+
+ sal_Int32 nSecObject = 0;
+
+ if( m_aContext.Encryption.Encrypt() )
+ {
+ //emit the security information
+ //must be emitted as indirect dictionary object, since
+ //Acrobat Reader 5 works only with this kind of implementation
+ nSecObject = createObject();
+
+ if( updateObject( nSecObject ) )
+ {
+ OStringBuffer aLineS( 1024 );
+ aLineS.append( nSecObject );
+ aLineS.append( " 0 obj\n"
+ "<</Filter/Standard/V " );
+ // check the version
+ aLineS.append( "2/Length 128/R 3" );
+
+ // emit the owner password, must not be encrypted
+ aLineS.append( "/O(" );
+ appendLiteralString( reinterpret_cast<char*>(m_aContext.Encryption.OValue.data()), sal_Int32(m_aContext.Encryption.OValue.size()), aLineS );
+ aLineS.append( ")/U(" );
+ appendLiteralString( reinterpret_cast<char*>(m_aContext.Encryption.UValue.data()), sal_Int32(m_aContext.Encryption.UValue.size()), aLineS );
+ aLineS.append( ")/P " );// the permission set
+ aLineS.append( m_nAccessPermissions );
+ aLineS.append( ">>\nendobj\n\n" );
+ if( !writeBuffer( aLineS.getStr(), aLineS.getLength() ) )
+ nSecObject = 0;
+ }
+ else
+ nSecObject = 0;
+ }
+ // emit xref table
+ // remember start
+ sal_uInt64 nXRefOffset = 0;
+ CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nXRefOffset )) );
+ CHECK_RETURN( writeBuffer( "xref\n", 5 ) );
+
+ sal_Int32 nObjects = m_aObjects.size();
+ OStringBuffer aLine;
+ aLine.append( "0 " );
+ aLine.append( static_cast<sal_Int32>(nObjects+1) );
+ aLine.append( "\n" );
+ aLine.append( "0000000000 65535 f \n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+
+ for( sal_Int32 i = 0; i < nObjects; i++ )
+ {
+ aLine.setLength( 0 );
+ OString aOffset = OString::number( m_aObjects[i] );
+ for( sal_Int32 j = 0; j < (10-aOffset.getLength()); j++ )
+ aLine.append( '0' );
+ aLine.append( aOffset );
+ aLine.append( " 00000 n \n" );
+ SAL_WARN_IF( aLine.getLength() != 20, "vcl.pdfwriter", "invalid xref entry" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+ }
+
+ // prepare document checksum
+ OStringBuffer aDocChecksum( 2*RTL_DIGEST_LENGTH_MD5+1 );
+ ::std::vector<unsigned char> const nMD5Sum(m_DocDigest.finalize());
+ for (sal_uInt8 i : nMD5Sum)
+ appendHex( i, aDocChecksum );
+ // document id set in setDocInfo method
+ // emit trailer
+ aLine.setLength( 0 );
+ aLine.append( "trailer\n"
+ "<</Size " );
+ aLine.append( static_cast<sal_Int32>(nObjects+1) );
+ aLine.append( "/Root " );
+ aLine.append( m_nCatalogObject );
+ aLine.append( " 0 R\n" );
+ if( nSecObject )
+ {
+ aLine.append( "/Encrypt ");
+ aLine.append( nSecObject );
+ aLine.append( " 0 R\n" );
+ }
+ if( nDocInfoObject )
+ {
+ aLine.append( "/Info " );
+ aLine.append( nDocInfoObject );
+ aLine.append( " 0 R\n" );
+ }
+ if( ! m_aContext.Encryption.DocumentIdentifier.empty() )
+ {
+ aLine.append( "/ID [ <" );
+ for (auto const& item : m_aContext.Encryption.DocumentIdentifier)
+ {
+ appendHex( sal_Int8(item), aLine );
+ }
+ aLine.append( ">\n"
+ "<" );
+ for (auto const& item : m_aContext.Encryption.DocumentIdentifier)
+ {
+ appendHex( sal_Int8(item), aLine );
+ }
+ aLine.append( "> ]\n" );
+ }
+ if( !aDocChecksum.isEmpty() )
+ {
+ aLine.append( "/DocChecksum /" );
+ aLine.append( aDocChecksum.makeStringAndClear() );
+ aLine.append( "\n" );
+ }
+ if( !m_aAdditionalStreams.empty() )
+ {
+ aLine.append( "/AdditionalStreams [" );
+ for(const PDFAddStream & rAdditionalStream : m_aAdditionalStreams)
+ {
+ aLine.append( "/" );
+ appendName( rAdditionalStream.m_aMimeType, aLine );
+ aLine.append( " " );
+ aLine.append( rAdditionalStream.m_nStreamObject );
+ aLine.append( " 0 R\n" );
+ }
+ aLine.append( "]\n" );
+ }
+ aLine.append( ">>\n"
+ "startxref\n" );
+ aLine.append( static_cast<sal_Int64>(nXRefOffset) );
+ aLine.append( "\n"
+ "%%EOF\n" );
+ return writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+namespace {
+
+struct AnnotationSortEntry
+{
+ sal_Int32 nTabOrder;
+ sal_Int32 nObject;
+ sal_Int32 nWidgetIndex;
+
+ AnnotationSortEntry( sal_Int32 nTab, sal_Int32 nObj, sal_Int32 nI ) :
+ nTabOrder( nTab ),
+ nObject( nObj ),
+ nWidgetIndex( nI )
+ {}
+};
+
+struct AnnotSortContainer
+{
+ std::set< 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<long int>(item.second.aSortedAnnots.size()) << " sorted and " <<
+ static_cast<long int>(nAnnots) << " unsorted");
+ }
+ }
+
+ // FIXME: implement tab order in structure tree for PDF 1.5
+}
+
+namespace vcl {
+class PDFStreamIf :
+ public cppu::WeakImplHelper< css::io::XOutputStream >
+{
+ VclPtr<PDFWriterImpl> m_pWriter;
+ bool m_bWrite;
+ public:
+ explicit PDFStreamIf( PDFWriterImpl* pWriter ) : m_pWriter( pWriter ), m_bWrite( true ) {}
+
+ virtual void SAL_CALL writeBytes( const css::uno::Sequence< sal_Int8 >& aData ) override;
+ virtual void SAL_CALL flush() override;
+ virtual void SAL_CALL closeOutput() override;
+};
+}
+
+void SAL_CALL PDFStreamIf::writeBytes( const css::uno::Sequence< sal_Int8 >& aData )
+{
+ if( m_bWrite && aData.hasElements() )
+ {
+ sal_Int32 nBytes = aData.getLength();
+ m_pWriter->writeBuffer( aData.getConstArray(), nBytes );
+ }
+}
+
+void SAL_CALL PDFStreamIf::flush()
+{
+}
+
+void SAL_CALL PDFStreamIf::closeOutput()
+{
+ m_bWrite = false;
+}
+
+bool PDFWriterImpl::emitAdditionalStreams()
+{
+ unsigned int nStreams = m_aAdditionalStreams.size();
+ for( unsigned int i = 0; i < nStreams; i++ )
+ {
+ PDFAddStream& rStream = m_aAdditionalStreams[i];
+ rStream.m_nStreamObject = createObject();
+ sal_Int32 nSizeObject = createObject();
+
+ if( ! updateObject( rStream.m_nStreamObject ) )
+ return false;
+
+ OStringBuffer aLine;
+ aLine.append( rStream.m_nStreamObject );
+ aLine.append( " 0 obj\n<</Length " );
+ aLine.append( nSizeObject );
+ aLine.append( " 0 R" );
+ if( rStream.m_bCompress )
+ aLine.append( "/Filter/FlateDecode" );
+ aLine.append( ">>\nstream\n" );
+ if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) )
+ return false;
+ sal_uInt64 nBeginStreamPos = 0, nEndStreamPos = 0;
+ if( osl::File::E_None != m_aFile.getPos(nBeginStreamPos) )
+ {
+ m_aFile.close();
+ m_bOpen = false;
+ }
+ if( rStream.m_bCompress )
+ beginCompression();
+
+ checkAndEnableStreamEncryption( rStream.m_nStreamObject );
+ css::uno::Reference< css::io::XOutputStream > xStream( new PDFStreamIf( this ) );
+ assert(rStream.m_pStream);
+ if (!rStream.m_pStream)
+ return false;
+ rStream.m_pStream->write( xStream );
+ xStream.clear();
+ delete rStream.m_pStream;
+ rStream.m_pStream = nullptr;
+ disableStreamEncryption();
+
+ if( rStream.m_bCompress )
+ endCompression();
+
+ if (osl::File::E_None != m_aFile.getPos(nEndStreamPos))
+ {
+ m_aFile.close();
+ m_bOpen = false;
+ return false;
+ }
+ if( ! writeBuffer( "\nendstream\nendobj\n\n", 19 ) )
+ return false ;
+ // emit stream length object
+ if( ! updateObject( nSizeObject ) )
+ return false;
+ aLine.setLength( 0 );
+ aLine.append( nSizeObject );
+ aLine.append( " 0 obj\n" );
+ aLine.append( static_cast<sal_Int64>(nEndStreamPos-nBeginStreamPos) );
+ aLine.append( "\nendobj\n\n" );
+ if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) )
+ return false;
+ }
+ return true;
+}
+
+bool PDFWriterImpl::emit()
+{
+ endPage();
+
+ // resort structure tree and annotations if necessary
+ // needed for widget tab order
+ sortWidgets();
+
+#if HAVE_FEATURE_NSS
+ if( m_aContext.SignPDF )
+ {
+ // sign the document
+ PDFWriter::SignatureWidget aSignature;
+ aSignature.Name = "Signature1";
+ createControl( aSignature, 0 );
+ }
+#endif
+
+ // emit additional streams
+ CHECK_RETURN( emitAdditionalStreams() );
+
+ // emit catalog
+ CHECK_RETURN( emitCatalog() );
+
+#if HAVE_FEATURE_NSS
+ if (m_nSignatureObject != -1) // if document is signed, emit sigdict
+ {
+ if( !emitSignature() )
+ {
+ m_aErrors.insert( PDFWriter::Error_Signature_Failed );
+ return false;
+ }
+ }
+#endif
+
+ // emit trailer
+ CHECK_RETURN( emitTrailer() );
+
+#if HAVE_FEATURE_NSS
+ if (m_nSignatureObject != -1) // finalize the signature
+ {
+ if( !finalizeSignature() )
+ {
+ m_aErrors.insert( PDFWriter::Error_Signature_Failed );
+ return false;
+ }
+ }
+#endif
+
+ m_aFile.close();
+ m_bOpen = false;
+
+ return true;
+}
+
+
+sal_Int32 PDFWriterImpl::getSystemFont( const vcl::Font& i_rFont )
+{
+ Push();
+
+ SetFont( i_rFont );
+
+ const PhysicalFontFace* pDevFont = GetFontInstance()->GetFontFace();
+ sal_Int32 nFontID = 0;
+ auto it = m_aSystemFonts.find( pDevFont );
+ if( it != m_aSystemFonts.end() )
+ nFontID = it->second.m_nNormalFontID;
+ else
+ {
+ nFontID = m_nNextFID++;
+ m_aSystemFonts[ pDevFont ] = EmbedFont();
+ m_aSystemFonts[ pDevFont ].m_nNormalFontID = nFontID;
+ }
+
+ Pop();
+ return nFontID;
+}
+
+void PDFWriterImpl::registerGlyph(const GlyphItem* pGlyph,
+ const PhysicalFontFace* pFont,
+ const std::vector<sal_Ucs>& rCodeUnits,
+ sal_uInt8& nMappedGlyph,
+ sal_Int32& nMappedFontObject)
+{
+ const int nFontGlyphId = pGlyph->glyphId();
+ FontSubset& rSubset = m_aSubsets[ pFont ];
+ // search for font specific glyphID
+ auto it = rSubset.m_aMapping.find( nFontGlyphId );
+ if( it != rSubset.m_aMapping.end() )
+ {
+ nMappedFontObject = it->second.m_nFontID;
+ nMappedGlyph = it->second.m_nSubsetGlyphID;
+ }
+ else
+ {
+ // create new subset if necessary
+ if( rSubset.m_aSubsets.empty()
+ || (rSubset.m_aSubsets.back().m_aMapping.size() > 254) )
+ {
+ rSubset.m_aSubsets.emplace_back( m_nNextFID++ );
+ }
+
+ // copy font id
+ nMappedFontObject = rSubset.m_aSubsets.back().m_nFontID;
+ // create new glyph in subset
+ sal_uInt8 nNewId = sal::static_int_cast<sal_uInt8>(rSubset.m_aSubsets.back().m_aMapping.size()+1);
+ nMappedGlyph = nNewId;
+
+ // add new glyph to emitted font subset
+ GlyphEmit& rNewGlyphEmit = rSubset.m_aSubsets.back().m_aMapping[ nFontGlyphId ];
+ rNewGlyphEmit.setGlyphId( nNewId );
+ for (const auto nCode : rCodeUnits)
+ rNewGlyphEmit.addCode(nCode);
+
+ // add new glyph to font mapping
+ Glyph& rNewGlyph = rSubset.m_aMapping[ nFontGlyphId ];
+ rNewGlyph.m_nFontID = nMappedFontObject;
+ rNewGlyph.m_nSubsetGlyphID = nNewId;
+ }
+}
+
+void PDFWriterImpl::drawRelief( SalLayout& rLayout, const OUString& rText, bool bTextLines )
+{
+ push( PushFlags::ALL );
+
+ FontRelief eRelief = m_aCurrentPDFState.m_aFont.GetRelief();
+
+ Color aTextColor = m_aCurrentPDFState.m_aFont.GetColor();
+ Color aTextLineColor = m_aCurrentPDFState.m_aTextLineColor;
+ Color aOverlineColor = m_aCurrentPDFState.m_aOverlineColor;
+ Color aReliefColor( COL_LIGHTGRAY );
+ if( aTextColor == COL_BLACK )
+ aTextColor = COL_WHITE;
+ if( aTextLineColor == COL_BLACK )
+ aTextLineColor = COL_WHITE;
+ if( aOverlineColor == COL_BLACK )
+ aOverlineColor = COL_WHITE;
+ // coverity[copy_paste_error : FALSE] - aReliefColor depending on aTextColor is correct
+ if( aTextColor == COL_WHITE )
+ aReliefColor = COL_BLACK;
+
+ Font aSetFont = m_aCurrentPDFState.m_aFont;
+ aSetFont.SetRelief( FontRelief::NONE );
+ aSetFont.SetShadow( false );
+
+ aSetFont.SetColor( aReliefColor );
+ setTextLineColor( aReliefColor );
+ setOverlineColor( aReliefColor );
+ setFont( aSetFont );
+ 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();
+
+ long nOff = 1 + ((GetFontInstance()->mnLineHeight-24)/24);
+ if( rFont.IsOutline() )
+ nOff++;
+ rLayout.DrawBase() += Point( nOff, nOff );
+ drawLayout( rLayout, rText, bTextLines );
+ rLayout.DrawBase() -= Point( nOff, nOff );
+
+ setFont( aSaveFont );
+ setTextLineColor( aSaveTextLineColor );
+ setOverlineColor( aSaveOverlineColor );
+ updateGraphicsState();
+}
+
+void PDFWriterImpl::drawVerticalGlyphs(
+ const std::vector<PDFGlyph>& rGlyphs,
+ OStringBuffer& rLine,
+ const Point& rAlignOffset,
+ const Matrix3& rRotScale,
+ double fAngle,
+ double fXScale,
+ double fSkew,
+ sal_Int32 nFontHeight )
+{
+ long nXOffset = 0;
+ Point aCurPos( rGlyphs[0].m_aPos );
+ aCurPos = PixelToLogic( aCurPos );
+ aCurPos += rAlignOffset;
+ for( size_t i = 0; i < rGlyphs.size(); i++ )
+ {
+ // have to emit each glyph on its own
+ double fDeltaAngle = 0.0;
+ double fYScale = 1.0;
+ double fTempXScale = fXScale;
+ double fSkewB = fSkew;
+ double fSkewA = 0.0;
+
+ Point aDeltaPos;
+ if (rGlyphs[i].m_pGlyph->IsVertical())
+ {
+ fDeltaAngle = M_PI/2.0;
+ aDeltaPos.setX( GetFontMetric().GetAscent() );
+ aDeltaPos.setY( static_cast<int>(static_cast<double>(GetFontMetric().GetDescent()) * fXScale) );
+ fYScale = fXScale;
+ fTempXScale = 1.0;
+ fSkewA = -fSkewB;
+ fSkewB = 0.0;
+ }
+ aDeltaPos += PixelToLogic( Point( static_cast<int>(static_cast<double>(nXOffset)/fXScale), 0 ) ) - PixelToLogic( Point() );
+ if( i < rGlyphs.size()-1 )
+ // #i120627# the text on the Y axis is reversed when export ppt file to PDF format
+ {
+ long nOffsetX = rGlyphs[i+1].m_aPos.X() - rGlyphs[i].m_aPos.X();
+ long nOffsetY = rGlyphs[i+1].m_aPos.Y() - rGlyphs[i].m_aPos.Y();
+ nXOffset += static_cast<int>(sqrt(double(nOffsetX*nOffsetX + nOffsetY*nOffsetY)));
+ }
+ if (!rGlyphs[i].m_pGlyph->glyphId())
+ continue;
+
+ aDeltaPos = rRotScale.transform( aDeltaPos );
+
+ Matrix3 aMat;
+ if( fSkewB != 0.0 || fSkewA != 0.0 )
+ aMat.skew( fSkewA, fSkewB );
+ aMat.scale( fTempXScale, fYScale );
+ aMat.rotate( fAngle+fDeltaAngle );
+ aMat.translate( aCurPos.X()+aDeltaPos.X(), aCurPos.Y()+aDeltaPos.Y() );
+ m_aPages.back().appendMatrix3(aMat, rLine);
+ rLine.append( " Tm" );
+ if( i == 0 || rGlyphs[i-1].m_nMappedFontId != rGlyphs[i].m_nMappedFontId )
+ {
+ rLine.append( " /F" );
+ rLine.append( rGlyphs[i].m_nMappedFontId );
+ rLine.append( ' ' );
+ m_aPages.back().appendMappedLength( nFontHeight, rLine );
+ rLine.append( " Tf" );
+ }
+ rLine.append( "<" );
+ appendHex( rGlyphs[i].m_nMappedGlyphId, rLine );
+ rLine.append( ">Tj\n" );
+ }
+}
+
+void PDFWriterImpl::drawHorizontalGlyphs(
+ const std::vector<PDFGlyph>& rGlyphs,
+ OStringBuffer& rLine,
+ const Point& rAlignOffset,
+ bool bFirst,
+ double fAngle,
+ double fXScale,
+ double fSkew,
+ sal_Int32 nFontHeight,
+ sal_Int32 nPixelFontHeight
+ )
+{
+ // horizontal (= normal) case
+
+ // fill in run end indices
+ // end is marked by index of the first glyph of the next run
+ // a run is marked by same mapped font id and same Y position
+ std::vector< sal_uInt32 > aRunEnds;
+ aRunEnds.reserve( rGlyphs.size() );
+ for( size_t i = 1; i < rGlyphs.size(); i++ )
+ {
+ if( rGlyphs[i].m_nMappedFontId != rGlyphs[i-1].m_nMappedFontId ||
+ rGlyphs[i].m_aPos.Y() != rGlyphs[i-1].m_aPos.Y() )
+ {
+ aRunEnds.push_back(i);
+ }
+ }
+ // last run ends at last glyph
+ aRunEnds.push_back( rGlyphs.size() );
+
+ // loop over runs of the same font
+ sal_uInt32 nBeginRun = 0;
+ for( size_t nRun = 0; nRun < aRunEnds.size(); nRun++ )
+ {
+ // setup text matrix
+ Point aCurPos = rGlyphs[nBeginRun].m_aPos;
+ // back transformation to current coordinate system
+ aCurPos = PixelToLogic( aCurPos );
+ aCurPos += rAlignOffset;
+ // the first run can be set with "Td" operator
+ // subsequent use of that operator would move
+ // the textline matrix relative to what was set before
+ // making use of that would drive us into rounding issues
+ Matrix3 aMat;
+ if( bFirst && nRun == 0 && fAngle == 0.0 && fXScale == 1.0 && fSkew == 0.0 )
+ {
+ m_aPages.back().appendPoint( aCurPos, rLine );
+ rLine.append( " Td " );
+ }
+ else
+ {
+ if( fSkew != 0.0 )
+ aMat.skew( 0.0, fSkew );
+ aMat.scale( fXScale, 1.0 );
+ aMat.rotate( fAngle );
+ aMat.translate( aCurPos.X(), aCurPos.Y() );
+ m_aPages.back().appendMatrix3(aMat, rLine);
+ rLine.append( " Tm\n" );
+ }
+ // set up correct font
+ rLine.append( "/F" );
+ rLine.append( rGlyphs[nBeginRun].m_nMappedFontId );
+ rLine.append( ' ' );
+ m_aPages.back().appendMappedLength( nFontHeight, rLine );
+ rLine.append( " Tf" );
+
+ // output glyphs using Tj or TJ
+ OStringBuffer aKernedLine( 256 ), aUnkernedLine( 256 );
+ aKernedLine.append( "[<" );
+ aUnkernedLine.append( '<' );
+ appendHex( rGlyphs[nBeginRun].m_nMappedGlyphId, aKernedLine );
+ appendHex( rGlyphs[nBeginRun].m_nMappedGlyphId, aUnkernedLine );
+
+ aMat.invert();
+ bool bNeedKern = false;
+ for( sal_uInt32 nPos = nBeginRun+1; nPos < aRunEnds[nRun]; nPos++ )
+ {
+ appendHex( rGlyphs[nPos].m_nMappedGlyphId, aUnkernedLine );
+ // check if default glyph positioning is sufficient
+ const Point aThisPos = aMat.transform( rGlyphs[nPos].m_aPos );
+ const Point aPrevPos = aMat.transform( rGlyphs[nPos-1].m_aPos );
+ double fAdvance = aThisPos.X() - aPrevPos.X();
+ fAdvance *= 1000.0 / nPixelFontHeight;
+ const double fAdjustment = rGlyphs[nPos-1].m_nNativeWidth - fAdvance + 0.5;
+ SAL_WARN_IF(
+ fAdjustment < SAL_MIN_INT32 || fAdjustment > SAL_MAX_INT32, "vcl.pdfwriter",
+ "adjustment " << fAdjustment << " outside 32-bit int");
+ const sal_Int32 nAdjustment = static_cast<sal_Int32>(
+ std::clamp(fAdjustment, double(SAL_MIN_INT32), double(SAL_MAX_INT32)));
+ if( nAdjustment != 0 )
+ {
+ // apply individual glyph positioning
+ bNeedKern = true;
+ aKernedLine.append( ">" );
+ aKernedLine.append( nAdjustment );
+ aKernedLine.append( "<" );
+ }
+ appendHex( rGlyphs[nPos].m_nMappedGlyphId, aKernedLine );
+ }
+ aKernedLine.append( ">]TJ\n" );
+ aUnkernedLine.append( ">Tj\n" );
+ rLine.append(
+ (bNeedKern ? aKernedLine : aUnkernedLine).makeStringAndClear() );
+
+ // set beginning of next run
+ nBeginRun = aRunEnds[nRun];
+ }
+}
+
+void PDFWriterImpl::drawLayout( SalLayout& rLayout, const OUString& rText, bool bTextLines )
+{
+ // relief takes precedence over shadow (see outdev3.cxx)
+ if( m_aCurrentPDFState.m_aFont.GetRelief() != FontRelief::NONE )
+ {
+ drawRelief( rLayout, rText, bTextLines );
+ return;
+ }
+ else if( m_aCurrentPDFState.m_aFont.IsShadow() )
+ drawShadow( rLayout, rText, bTextLines );
+
+ OStringBuffer aLine( 512 );
+
+ const int nMaxGlyphs = 256;
+
+ std::vector<sal_Ucs> aCodeUnits;
+ bool bVertical = m_aCurrentPDFState.m_aFont.IsVertical();
+ int nIndex = 0;
+ double fXScale = 1.0;
+ double fSkew = 0.0;
+ sal_Int32 nPixelFontHeight = GetFontInstance()->GetFontSelectPattern().mnHeight;
+ TextAlign eAlign = m_aCurrentPDFState.m_aFont.GetAlignment();
+
+ // transform font height back to current units
+ // note: the layout calculates in outdevs device pixel !!
+ sal_Int32 nFontHeight = ImplDevicePixelToLogicHeight( nPixelFontHeight );
+ if( m_aCurrentPDFState.m_aFont.GetAverageFontWidth() )
+ {
+ Font aFont( m_aCurrentPDFState.m_aFont );
+ aFont.SetAverageFontWidth( 0 );
+ FontMetric aMetric = GetFontMetric( aFont );
+ if( aMetric.GetAverageFontWidth() != m_aCurrentPDFState.m_aFont.GetAverageFontWidth() )
+ {
+ fXScale =
+ static_cast<double>(m_aCurrentPDFState.m_aFont.GetAverageFontWidth()) /
+ static_cast<double>(aMetric.GetAverageFontWidth());
+ }
+ }
+
+ // perform artificial italics if necessary
+ if( ( m_aCurrentPDFState.m_aFont.GetItalic() == ITALIC_NORMAL ||
+ m_aCurrentPDFState.m_aFont.GetItalic() == ITALIC_OBLIQUE ) &&
+ !( GetFontInstance()->GetFontFace()->GetItalic() == ITALIC_NORMAL ||
+ GetFontInstance()->GetFontFace()->GetItalic() == ITALIC_OBLIQUE )
+ )
+ {
+ fSkew = M_PI/12.0;
+ }
+
+ // if the mapmode is distorted we need to adjust for that also
+ if( m_aCurrentPDFState.m_aMapMode.GetScaleX() != m_aCurrentPDFState.m_aMapMode.GetScaleY() )
+ {
+ fXScale *= double(m_aCurrentPDFState.m_aMapMode.GetScaleX()) / double(m_aCurrentPDFState.m_aMapMode.GetScaleY());
+ }
+
+ int nAngle = m_aCurrentPDFState.m_aFont.GetOrientation();
+ // normalize angles
+ while( nAngle < 0 )
+ nAngle += 3600;
+ nAngle = nAngle % 3600;
+ double fAngle = static_cast<double>(nAngle) * M_PI / 1800.0;
+
+ Matrix3 aRotScale;
+ aRotScale.scale( fXScale, 1.0 );
+ if( fAngle != 0.0 )
+ aRotScale.rotate( -fAngle );
+
+ bool bPop = false;
+ bool bABold = false;
+ // artificial bold necessary ?
+ if( GetFontInstance()->GetFontFace()->GetWeight() <= WEIGHT_MEDIUM &&
+ GetFontInstance()->GetFontSelectPattern().GetWeight() > WEIGHT_MEDIUM )
+ {
+ aLine.append("q ");
+ bPop = true;
+ bABold = true;
+ }
+ // setup text colors (if necessary)
+ Color aStrokeColor( COL_TRANSPARENT );
+ Color aNonStrokeColor( COL_TRANSPARENT );
+
+ if( m_aCurrentPDFState.m_aFont.IsOutline() )
+ {
+ aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
+ aNonStrokeColor = COL_WHITE;
+ }
+ else
+ aNonStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
+ if( bABold )
+ aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
+
+ if( aStrokeColor != COL_TRANSPARENT && aStrokeColor != m_aCurrentPDFState.m_aLineColor )
+ {
+ if( ! bPop )
+ aLine.append( "q " );
+ bPop = true;
+ appendStrokingColor( aStrokeColor, aLine );
+ aLine.append( "\n" );
+ }
+ if( aNonStrokeColor != COL_TRANSPARENT && aNonStrokeColor != m_aCurrentPDFState.m_aFillColor )
+ {
+ if( ! bPop )
+ aLine.append( "q " );
+ bPop = true;
+ appendNonStrokingColor( aNonStrokeColor, aLine );
+ aLine.append( "\n" );
+ }
+
+ // begin text object
+ aLine.append( "BT\n" );
+ // outline attribute ?
+ if( m_aCurrentPDFState.m_aFont.IsOutline() || bABold )
+ {
+ // set correct text mode, set stroke width
+ aLine.append( "2 Tr " ); // fill, then stroke
+
+ if( m_aCurrentPDFState.m_aFont.IsOutline() )
+ {
+ // unclear what to do in case of outline and artificial bold
+ // for the time being outline wins
+ aLine.append( "0.25 w \n" );
+ }
+ else
+ {
+ double fW = static_cast<double>(m_aCurrentPDFState.m_aFont.GetFontHeight()) / 30.0;
+ m_aPages.back().appendMappedLength( fW, aLine );
+ aLine.append ( " w\n" );
+ }
+ }
+
+ FontMetric aRefDevFontMetric = GetFontMetric();
+ const PhysicalFontFace* pDevFont = GetFontInstance()->GetFontFace();
+ const GlyphItem* pGlyph = nullptr;
+ const PhysicalFontFace* pFallbackFont = nullptr;
+
+ // collect the glyphs into a single array
+ std::vector< PDFGlyph > aGlyphs;
+ aGlyphs.reserve( nMaxGlyphs );
+ // first get all the glyphs and register them; coordinates still in Pixel
+ Point aPos;
+ while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex, &pFallbackFont))
+ {
+ const auto* pFont = pFallbackFont ? pFallbackFont : pDevFont;
+
+ aCodeUnits.clear();
+
+ // tdf#66597, tdf#115117
+ //
+ // Here is how we embed textual content in PDF files, to allow for
+ // better text extraction for complex and typography-rich text.
+ //
+ // * If there is many to one or many to many mapping, use an
+ // ActualText span embedding the original string, since ToUnicode
+ // can’t handle these.
+ // * If the one glyph is used for several Unicode code points, also
+ // use ActualText since ToUnicode can map each glyph in the font
+ // only once.
+ // * Limit ActualText to single cluster at a time, since using it
+ // for whole words or sentences breaks text selection and
+ // highlighting in PDF viewers (there will be no way to tell
+ // which glyphs belong to which characters).
+ // * Keep generating (now) redundant ToUnicode entries for
+ // compatibility with old tools not supporting ActualText.
+
+ assert(pGlyph->charCount() >= 0);
+ for (int n = 0; n < pGlyph->charCount(); n++)
+ aCodeUnits.push_back(rText[pGlyph->charPos() + n]);
+
+ bool bUseActualText = false;
+
+ // If this is a start of complex cluster, use ActualText.
+ if (pGlyph->IsClusterStart())
+ bUseActualText = true;
+
+ // Or part of a complex cluster, will be handled by the ActualText
+ // of its cluster start.
+ if (pGlyph->IsInCluster())
+ assert(aCodeUnits.empty());
+
+ // A glyph can’t have more than one ToUnicode entry, use ActualText
+ // instead.
+ if (!aCodeUnits.empty() && !bUseActualText)
+ {
+ for (const auto& rSubset : m_aSubsets[pFont].m_aSubsets)
+ {
+ const auto& it = rSubset.m_aMapping.find(pGlyph->glyphId());
+ if (it != rSubset.m_aMapping.cend() && it->second.codes() != aCodeUnits)
+ {
+ bUseActualText = true;
+ aCodeUnits.clear();
+ }
+ }
+ }
+
+ assert(!aCodeUnits.empty() || bUseActualText || pGlyph->IsInCluster());
+
+ sal_uInt8 nMappedGlyph;
+ sal_Int32 nMappedFontObject;
+ registerGlyph(pGlyph, pFont, aCodeUnits, nMappedGlyph, nMappedFontObject);
+
+ sal_Int32 nGlyphWidth = 0;
+ SalGraphics *pGraphics = GetGraphics();
+ if (pGraphics)
+ nGlyphWidth = m_aFontCache.getGlyphWidth(pFont,
+ pGlyph->glyphId(),
+ pGlyph->IsVertical(),
+ pGraphics);
+
+ int nCharPos = -1;
+ if (bUseActualText || pGlyph->IsInCluster())
+ nCharPos = pGlyph->charPos();
+
+ aGlyphs.emplace_back(aPos,
+ pGlyph,
+ nGlyphWidth,
+ nMappedFontObject,
+ nMappedGlyph,
+ nCharPos);
+ }
+
+ // Avoid fill color when map mode is in pixels, the below code assumes
+ // logic map mode.
+ bool bPixel = m_aCurrentPDFState.m_aMapMode.GetMapUnit() == MapUnit::MapPixel;
+ if (m_aCurrentPDFState.m_aFont.GetFillColor() != COL_TRANSPARENT && !bPixel)
+ {
+ // PDF doesn't have a text fill color, so draw a rectangle before
+ // drawing the actual text.
+ push(PushFlags::FILLCOLOR | PushFlags::LINECOLOR);
+ setFillColor(m_aCurrentPDFState.m_aFont.GetFillColor());
+ // Avoid border around the rectangle for Writer shape text.
+ setLineColor(COL_TRANSPARENT);
+
+ // The rectangle is the bounding box of the text, but also includes
+ // ascent / descent to match the on-screen rendering.
+ tools::Rectangle aRectangle;
+ // This is the top left of the text without ascent / descent.
+ aRectangle.SetPos(PixelToLogic(rLayout.GetDrawPosition()));
+ aRectangle.setY(aRectangle.getY() - aRefDevFontMetric.GetAscent());
+ aRectangle.SetSize(PixelToLogic(Size(rLayout.GetTextWidth(), 0)));
+ // This includes ascent / descent.
+ aRectangle.setHeight(aRefDevFontMetric.GetLineHeight());
+
+ const LogicalFontInstance* pFontInstance = GetFontInstance();
+ if (pFontInstance->mnOrientation)
+ {
+ // Adapt rectangle for rotated text.
+ tools::Polygon aPolygon(aRectangle);
+ aPolygon.Rotate(PixelToLogic(rLayout.GetDrawPosition()), pFontInstance->mnOrientation);
+ drawPolygon(aPolygon);
+ }
+ else
+ drawRectangle(aRectangle);
+
+ pop();
+ }
+
+ Point aAlignOffset;
+ if ( eAlign == ALIGN_BOTTOM )
+ aAlignOffset.AdjustY( -(aRefDevFontMetric.GetDescent()) );
+ else if ( eAlign == ALIGN_TOP )
+ aAlignOffset.AdjustY(aRefDevFontMetric.GetAscent() );
+ if( aAlignOffset.X() || aAlignOffset.Y() )
+ aAlignOffset = aRotScale.transform( aAlignOffset );
+
+ /* #159153# do not emit an empty glyph vector; this can happen if e.g. the original
+ string contained only one of the UTF16 BOMs
+ */
+ if( ! aGlyphs.empty() )
+ {
+ size_t nStart = 0;
+ size_t nEnd = 0;
+ while (nStart < aGlyphs.size())
+ {
+ while (nEnd < aGlyphs.size() && aGlyphs[nEnd].m_nCharPos == aGlyphs[nStart].m_nCharPos)
+ nEnd++;
+
+ std::vector<PDFGlyph> aRun(aGlyphs.begin() + nStart, aGlyphs.begin() + nEnd);
+
+ int nCharPos, nCharCount;
+ if (!aRun.front().m_pGlyph->IsRTLGlyph())
+ {
+ nCharPos = aRun.front().m_nCharPos;
+ nCharCount = aRun.front().m_pGlyph->charCount();
+ }
+ else
+ {
+ nCharPos = aRun.back().m_nCharPos;
+ nCharCount = aRun.back().m_pGlyph->charCount();
+ }
+
+ if (nCharPos >= 0 && nCharCount)
+ {
+ aLine.append("/Span<</ActualText<FEFF");
+ for (int i = 0; i < nCharCount; i++)
+ {
+ sal_Unicode aChar = rText[nCharPos + i];
+ appendHex(static_cast<sal_Int8>(aChar >> 8), aLine);
+ appendHex(static_cast<sal_Int8>(aChar & 255), aLine);
+ }
+ aLine.append( ">>>\nBDC\n" );
+ }
+
+ if (bVertical)
+ drawVerticalGlyphs(aRun, aLine, aAlignOffset, aRotScale, fAngle, fXScale, fSkew, nFontHeight);
+ else
+ drawHorizontalGlyphs(aRun, aLine, aAlignOffset, nStart == 0, fAngle, fXScale, fSkew, nFontHeight, nPixelFontHeight);
+
+ if (nCharPos >= 0 && nCharCount)
+ aLine.append( "EMC\n" );
+
+ nStart = nEnd;
+ }
+ }
+
+ // end textobject
+ aLine.append( "ET\n" );
+ if( bPop )
+ aLine.append( "Q\n" );
+
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+
+ // draw eventual textlines
+ FontStrikeout eStrikeout = m_aCurrentPDFState.m_aFont.GetStrikeout();
+ FontLineStyle eUnderline = m_aCurrentPDFState.m_aFont.GetUnderline();
+ FontLineStyle eOverline = m_aCurrentPDFState.m_aFont.GetOverline();
+ if( bTextLines &&
+ (
+ ( eUnderline != LINESTYLE_NONE && eUnderline != LINESTYLE_DONTKNOW ) ||
+ ( eOverline != LINESTYLE_NONE && eOverline != LINESTYLE_DONTKNOW ) ||
+ ( eStrikeout != STRIKEOUT_NONE && eStrikeout != STRIKEOUT_DONTKNOW )
+ )
+ )
+ {
+ bool bUnderlineAbove = m_aCurrentPDFState.m_aFont.IsUnderlineAbove();
+ if( m_aCurrentPDFState.m_aFont.IsWordLineMode() )
+ {
+ Point aStartPt;
+ sal_Int32 nWidth = 0;
+ nIndex = 0;
+ while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex))
+ {
+ if (!pGlyph->IsSpacing())
+ {
+ if( !nWidth )
+ aStartPt = aPos;
+
+ nWidth += pGlyph->m_nNewWidth;
+ }
+ else if( nWidth > 0 )
+ {
+ drawTextLine( PixelToLogic( aStartPt ),
+ ImplDevicePixelToLogicWidth( nWidth ),
+ eStrikeout, eUnderline, eOverline, bUnderlineAbove );
+ nWidth = 0;
+ }
+ }
+
+ if( nWidth > 0 )
+ {
+ drawTextLine( PixelToLogic( aStartPt ),
+ ImplDevicePixelToLogicWidth( nWidth ),
+ eStrikeout, eUnderline, eOverline, bUnderlineAbove );
+ }
+ }
+ else
+ {
+ Point aStartPt = rLayout.GetDrawPosition();
+ int nWidth = rLayout.GetTextWidth() / rLayout.GetUnitsPerPixel();
+ drawTextLine( PixelToLogic( aStartPt ),
+ ImplDevicePixelToLogicWidth( nWidth ),
+ eStrikeout, eUnderline, eOverline, bUnderlineAbove );
+ }
+ }
+
+ // write eventual emphasis marks
+ if( !(m_aCurrentPDFState.m_aFont.GetEmphasisMark() & FontEmphasisMark::Style) )
+ return;
+
+ tools::PolyPolygon aEmphPoly;
+ tools::Rectangle aEmphRect1;
+ tools::Rectangle aEmphRect2;
+ long nEmphYOff;
+ long nEmphWidth;
+ long nEmphHeight;
+ bool bEmphPolyLine;
+ FontEmphasisMark nEmphMark;
+
+ push( PushFlags::ALL );
+
+ aLine.setLength( 0 );
+ aLine.append( "q\n" );
+
+ nEmphMark = OutputDevice::ImplGetEmphasisMarkStyle( m_aCurrentPDFState.m_aFont );
+ if ( nEmphMark & FontEmphasisMark::PosBelow )
+ nEmphHeight = GetEmphasisDescent();
+ else
+ nEmphHeight = GetEmphasisAscent();
+ ImplGetEmphasisMark( aEmphPoly,
+ bEmphPolyLine,
+ aEmphRect1,
+ aEmphRect2,
+ nEmphYOff,
+ nEmphWidth,
+ nEmphMark,
+ ImplDevicePixelToLogicWidth(nEmphHeight) );
+ if ( bEmphPolyLine )
+ {
+ setLineColor( m_aCurrentPDFState.m_aFont.GetColor() );
+ setFillColor( COL_TRANSPARENT );
+ }
+ else
+ {
+ setFillColor( m_aCurrentPDFState.m_aFont.GetColor() );
+ setLineColor( COL_TRANSPARENT );
+ }
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+
+ Point aOffset(0,0);
+
+ if ( nEmphMark & FontEmphasisMark::PosBelow )
+ aOffset.AdjustY(GetFontInstance()->mxFontMetric->GetDescent() + nEmphYOff );
+ else
+ aOffset.AdjustY( -(GetFontInstance()->mxFontMetric->GetAscent() + nEmphYOff) );
+
+ long nEmphWidth2 = nEmphWidth / 2;
+ long nEmphHeight2 = nEmphHeight / 2;
+ aOffset += Point( nEmphWidth2, nEmphHeight2 );
+
+ if ( eAlign == ALIGN_BOTTOM )
+ aOffset.AdjustY( -(GetFontInstance()->mxFontMetric->GetDescent()) );
+ else if ( eAlign == ALIGN_TOP )
+ aOffset.AdjustY(GetFontInstance()->mxFontMetric->GetAscent() );
+
+ nIndex = 0;
+ while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex))
+ {
+ if (pGlyph->IsSpacing())
+ {
+ Point aAdjOffset = aOffset;
+ aAdjOffset.AdjustX((pGlyph->m_nNewWidth - nEmphWidth) / 2 );
+ aAdjOffset = aRotScale.transform( aAdjOffset );
+
+ aAdjOffset -= Point( nEmphWidth2, nEmphHeight2 );
+
+ aPos += aAdjOffset;
+ aPos = PixelToLogic( aPos );
+ drawEmphasisMark( aPos.X(), aPos.Y(),
+ aEmphPoly, bEmphPolyLine,
+ aEmphRect1, aEmphRect2 );
+ }
+ }
+
+ writeBuffer( "Q\n", 2 );
+ pop();
+
+}
+
+void PDFWriterImpl::drawEmphasisMark( long nX, 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
+ std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos );
+ if( pLayout )
+ {
+ drawLayout( *pLayout, rText, bTextLines );
+ }
+}
+
+void PDFWriterImpl::drawTextArray( const Point& rPos, const OUString& rText, const long* pDXArray, sal_Int32 nIndex, sal_Int32 nLen )
+{
+ MARK( "drawText with array" );
+
+ updateGraphicsState();
+
+ // get a layout from the OutputDevice's SalGraphics
+ // this also enforces font substitution and sets the font on SalGraphics
+ std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos, 0, pDXArray );
+ if( pLayout )
+ {
+ drawLayout( *pLayout, rText, true );
+ }
+}
+
+void PDFWriterImpl::drawStretchText( const Point& rPos, sal_uLong nWidth, const OUString& rText, sal_Int32 nIndex, sal_Int32 nLen )
+{
+ MARK( "drawStretchText" );
+
+ updateGraphicsState();
+
+ // get a layout from the OutputDevice's SalGraphics
+ // this also enforces font substitution and sets the font on SalGraphics
+ std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos, nWidth );
+ if( pLayout )
+ {
+ drawLayout( *pLayout, rText, true );
+ }
+}
+
+void PDFWriterImpl::drawText( const tools::Rectangle& rRect, const OUString& rOrigStr, DrawTextFlags nStyle )
+{
+ long nWidth = rRect.GetWidth();
+ long nHeight = rRect.GetHeight();
+
+ if ( nWidth <= 0 || nHeight <= 0 )
+ return;
+
+ MARK( "drawText with rectangle" );
+
+ updateGraphicsState();
+
+ // clip with rectangle
+ OStringBuffer aLine;
+ aLine.append( "q " );
+ m_aPages.back().appendRect( rRect, aLine );
+ aLine.append( " W* n\n" );
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+
+ // if disabled text is needed, put in here
+
+ Point aPos = rRect.TopLeft();
+
+ long nTextHeight = GetTextHeight();
+ sal_Int32 nMnemonicPos = -1;
+
+ OUString aStr = rOrigStr;
+ if ( nStyle & DrawTextFlags::Mnemonic )
+ aStr = OutputDevice::GetNonMnemonicString( aStr, nMnemonicPos );
+
+ // multiline text
+ if ( nStyle & DrawTextFlags::MultiLine )
+ {
+ OUString aLastLine;
+ ImplMultiTextLineInfo aMultiLineInfo;
+ ImplTextLineInfo* pLineInfo;
+ sal_Int32 i;
+ sal_Int32 nLines;
+ sal_Int32 nFormatLines;
+
+ if ( nTextHeight )
+ {
+ vcl::DefaultTextLayout aLayout( *this );
+ OutputDevice::ImplGetTextLines( aMultiLineInfo, nWidth, aStr, nStyle, aLayout );
+ nLines = nHeight/nTextHeight;
+ nFormatLines = aMultiLineInfo.Count();
+ if ( !nLines )
+ nLines = 1;
+ if ( nFormatLines > nLines )
+ {
+ if ( nStyle & DrawTextFlags::EndEllipsis )
+ {
+ // handle last line
+ nFormatLines = nLines-1;
+
+ pLineInfo = aMultiLineInfo.GetLine( nFormatLines );
+ aLastLine = convertLineEnd(aStr.copy(pLineInfo->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++ )
+ {
+ pLineInfo = aMultiLineInfo.GetLine( i );
+ if ( nStyle & DrawTextFlags::Right )
+ aPos.AdjustX(nWidth-pLineInfo->GetWidth() );
+ else if ( nStyle & DrawTextFlags::Center )
+ aPos.AdjustX((nWidth-pLineInfo->GetWidth())/2 );
+ sal_Int32 nIndex = pLineInfo->GetIndex();
+ sal_Int32 nLineLen = pLineInfo->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
+ {
+ long nTextWidth = GetTextWidth( aStr );
+
+ // Evt. Text kuerzen
+ if ( nTextWidth > nWidth )
+ {
+ if ( nStyle & (DrawTextFlags::EndEllipsis | DrawTextFlags::PathEllipsis | DrawTextFlags::NewsEllipsis) )
+ {
+ aStr = GetEllipsisString( aStr, nWidth, nStyle );
+ nStyle &= ~DrawTextFlags(DrawTextFlags::Center | DrawTextFlags::Right);
+ nStyle |= DrawTextFlags::Left;
+ nTextWidth = GetTextWidth( aStr );
+ }
+ }
+
+ // vertical alignment
+ if ( nStyle & DrawTextFlags::Right )
+ aPos.AdjustX(nWidth-nTextWidth );
+ else if ( nStyle & DrawTextFlags::Center )
+ aPos.AdjustX((nWidth-nTextWidth)/2 );
+
+ if ( nStyle & DrawTextFlags::Bottom )
+ aPos.AdjustY(nHeight-nTextHeight );
+ else if ( nStyle & DrawTextFlags::VCenter )
+ aPos.AdjustY((nHeight-nTextHeight)/2 );
+
+ // mnemonics should be inserted here if the need arises
+
+ // draw the actual text
+ drawText( aPos, aStr, 0, aStr.getLength() );
+ }
+
+ // reset clip region to original value
+ aLine.setLength( 0 );
+ aLine.append( "Q\n" );
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop )
+{
+ MARK( "drawLine" );
+
+ updateGraphicsState();
+
+ if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
+ return;
+
+ OStringBuffer aLine;
+ m_aPages.back().appendPoint( rStart, aLine );
+ aLine.append( " m " );
+ m_aPages.back().appendPoint( rStop, aLine );
+ aLine.append( " l S\n" );
+
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop, const LineInfo& rInfo )
+{
+ MARK( "drawLine with LineInfo" );
+ updateGraphicsState();
+
+ if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
+ return;
+
+ if( rInfo.GetStyle() == LineStyle::Solid && rInfo.GetWidth() < 2 )
+ {
+ drawLine( rStart, rStop );
+ return;
+ }
+
+ OStringBuffer aLine;
+
+ aLine.append( "q " );
+ if( m_aPages.back().appendLineInfo( rInfo, aLine ) )
+ {
+ m_aPages.back().appendPoint( rStart, aLine );
+ aLine.append( " m " );
+ m_aPages.back().appendPoint( rStop, aLine );
+ aLine.append( " l S Q\n" );
+
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+ }
+ else
+ {
+ PDFWriter::ExtLineInfo aInfo;
+ convertLineInfoToExtLineInfo( rInfo, aInfo );
+ Point aPolyPoints[2] = { rStart, rStop };
+ tools::Polygon aPoly( 2, aPolyPoints );
+ drawPolyLine( aPoly, aInfo );
+ }
+}
+
+#define HCONV( x ) ImplDevicePixelToLogicHeight( x )
+
+void PDFWriterImpl::drawWaveTextLine( OStringBuffer& aLine, long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove )
+{
+ // note: units in pFontInstance are ref device pixel
+ const LogicalFontInstance* pFontInstance = GetFontInstance();
+ long nLineHeight = 0;
+ 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;
+
+ 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 )
+ {
+ long nOrgLineHeight = nLineHeight;
+ nLineHeight /= 3;
+ if ( nLineHeight < 2 )
+ {
+ if ( nOrgLineHeight > 1 )
+ nLineHeight = 2;
+ else
+ nLineHeight = 1;
+ }
+ long nLineDY = nOrgLineHeight-(nLineHeight*2);
+ if ( nLineDY < nLineWidth )
+ nLineDY = nLineWidth;
+ 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, long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove )
+{
+ // note: units in pFontInstance are ref device pixel
+ const LogicalFontInstance* pFontInstance = GetFontInstance();
+ long nLineHeight = 0;
+ long nLinePos = 0;
+ long nLinePos2 = 0;
+
+ if ( eTextLine > LINESTYLE_BOLDWAVE )
+ eTextLine = LINESTYLE_SINGLE;
+
+ switch ( eTextLine )
+ {
+ case LINESTYLE_SINGLE:
+ case LINESTYLE_DOTTED:
+ case LINESTYLE_DASH:
+ case LINESTYLE_LONGDASH:
+ case LINESTYLE_DASHDOT:
+ case LINESTYLE_DASHDOTDOT:
+ if ( bIsAbove )
+ {
+ if ( !pFontInstance->mxFontMetric->GetAboveUnderlineSize() )
+ ImplInitAboveTextLineSize();
+ nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveUnderlineSize() );
+ nLinePos = HCONV( pFontInstance->mxFontMetric->GetAboveUnderlineOffset() );
+ }
+ else
+ {
+ if ( !pFontInstance->mxFontMetric->GetUnderlineSize() )
+ ImplInitTextLineSize();
+ nLineHeight = HCONV( pFontInstance->mxFontMetric->GetUnderlineSize() );
+ nLinePos = HCONV( pFontInstance->mxFontMetric->GetUnderlineOffset() );
+ }
+ break;
+ case LINESTYLE_BOLD:
+ case LINESTYLE_BOLDDOTTED:
+ case LINESTYLE_BOLDDASH:
+ case LINESTYLE_BOLDLONGDASH:
+ case LINESTYLE_BOLDDASHDOT:
+ case LINESTYLE_BOLDDASHDOTDOT:
+ if ( bIsAbove )
+ {
+ if ( !pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize() )
+ ImplInitAboveTextLineSize();
+ nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize() );
+ nLinePos = HCONV( pFontInstance->mxFontMetric->GetAboveBoldUnderlineOffset() );
+ }
+ else
+ {
+ if ( !pFontInstance->mxFontMetric->GetBoldUnderlineSize() )
+ ImplInitTextLineSize();
+ nLineHeight = HCONV( pFontInstance->mxFontMetric->GetBoldUnderlineSize() );
+ nLinePos = HCONV( pFontInstance->mxFontMetric->GetBoldUnderlineOffset() );
+ nLinePos += nLineHeight/2;
+ }
+ break;
+ case LINESTYLE_DOUBLE:
+ if ( bIsAbove )
+ {
+ if ( !pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize() )
+ ImplInitAboveTextLineSize();
+ nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize() );
+ nLinePos = HCONV( pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset1() );
+ nLinePos2 = HCONV( pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset2() );
+ }
+ else
+ {
+ if ( !pFontInstance->mxFontMetric->GetDoubleUnderlineSize() )
+ ImplInitTextLineSize();
+ nLineHeight = HCONV( pFontInstance->mxFontMetric->GetDoubleUnderlineSize() );
+ nLinePos = HCONV( pFontInstance->mxFontMetric->GetDoubleUnderlineOffset1() );
+ nLinePos2 = HCONV( pFontInstance->mxFontMetric->GetDoubleUnderlineOffset2() );
+ }
+ break;
+ default:
+ break;
+ }
+
+ if ( !nLineHeight )
+ return;
+
+ // outline attribute ?
+ if (m_aCurrentPDFState.m_aFont.IsOutline() && eTextLine == LINESTYLE_SINGLE)
+ {
+ appendStrokingColor(aColor, aLine); // stroke with text color
+ aLine.append( " " );
+ appendNonStrokingColor(COL_WHITE, aLine); // fill with white
+ aLine.append( "\n" );
+ aLine.append( "0.25 w \n" ); // same line thickness as in drawLayout
+
+ // draw rectangle instead
+ aLine.append( "0 " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos * 1.5), aLine );
+ aLine.append( " " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine );
+ aLine.append( " re h B\n" );
+ return;
+ }
+
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine );
+ aLine.append( " w " );
+ appendStrokingColor( aColor, aLine );
+ aLine.append( "\n" );
+
+ switch ( eTextLine )
+ {
+ case LINESTYLE_DOTTED:
+ case LINESTYLE_BOLDDOTTED:
+ aLine.append( "[ " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
+ aLine.append( " ] 0 d\n" );
+ break;
+ case LINESTYLE_DASH:
+ case LINESTYLE_LONGDASH:
+ case LINESTYLE_BOLDDASH:
+ case LINESTYLE_BOLDLONGDASH:
+ {
+ sal_Int32 nDashLength = 4*nLineHeight;
+ sal_Int32 nVoidLength = 2*nLineHeight;
+ if ( ( eTextLine == LINESTYLE_LONGDASH ) || ( eTextLine == LINESTYLE_BOLDLONGDASH ) )
+ nDashLength = 8*nLineHeight;
+
+ aLine.append( "[ " );
+ m_aPages.back().appendMappedLength( nDashLength, aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
+ aLine.append( " ] 0 d\n" );
+ }
+ break;
+ case LINESTYLE_DASHDOT:
+ case LINESTYLE_BOLDDASHDOT:
+ {
+ sal_Int32 nDashLength = 4*nLineHeight;
+ sal_Int32 nVoidLength = 2*nLineHeight;
+ aLine.append( "[ " );
+ m_aPages.back().appendMappedLength( nDashLength, aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
+ aLine.append( " ] 0 d\n" );
+ }
+ break;
+ case LINESTYLE_DASHDOTDOT:
+ case LINESTYLE_BOLDDASHDOTDOT:
+ {
+ sal_Int32 nDashLength = 4*nLineHeight;
+ sal_Int32 nVoidLength = 2*nLineHeight;
+ aLine.append( "[ " );
+ m_aPages.back().appendMappedLength( nDashLength, aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
+ aLine.append( " ] 0 d\n" );
+ }
+ break;
+ default:
+ break;
+ }
+
+ aLine.append( "0 " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
+ aLine.append( " m " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
+ aLine.append( " l S\n" );
+ if ( eTextLine == LINESTYLE_DOUBLE )
+ {
+ aLine.append( "0 " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
+ aLine.append( " m " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
+ aLine.append( " l S\n" );
+ }
+
+}
+
+void PDFWriterImpl::drawStrikeoutLine( OStringBuffer& aLine, long nWidth, FontStrikeout eStrikeout, Color aColor )
+{
+ // note: units in pFontInstance are ref device pixel
+ const LogicalFontInstance* pFontInstance = GetFontInstance();
+ long nLineHeight = 0;
+ long nLinePos = 0;
+ long nLinePos2 = 0;
+
+ if ( eStrikeout > STRIKEOUT_X )
+ eStrikeout = STRIKEOUT_SINGLE;
+
+ switch ( eStrikeout )
+ {
+ case STRIKEOUT_SINGLE:
+ if ( !pFontInstance->mxFontMetric->GetStrikeoutSize() )
+ ImplInitTextLineSize();
+ nLineHeight = HCONV( pFontInstance->mxFontMetric->GetStrikeoutSize() );
+ nLinePos = HCONV( pFontInstance->mxFontMetric->GetStrikeoutOffset() );
+ break;
+ case STRIKEOUT_BOLD:
+ if ( !pFontInstance->mxFontMetric->GetBoldStrikeoutSize() )
+ ImplInitTextLineSize();
+ nLineHeight = HCONV( pFontInstance->mxFontMetric->GetBoldStrikeoutSize() );
+ nLinePos = HCONV( pFontInstance->mxFontMetric->GetBoldStrikeoutOffset() );
+ break;
+ case STRIKEOUT_DOUBLE:
+ if ( !pFontInstance->mxFontMetric->GetDoubleStrikeoutSize() )
+ ImplInitTextLineSize();
+ nLineHeight = HCONV( pFontInstance->mxFontMetric->GetDoubleStrikeoutSize() );
+ nLinePos = HCONV( pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset1() );
+ nLinePos2 = HCONV( pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset2() );
+ break;
+ default:
+ break;
+ }
+
+ if ( !nLineHeight )
+ return;
+
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine );
+ aLine.append( " w " );
+ appendStrokingColor( aColor, aLine );
+ aLine.append( "\n" );
+
+ aLine.append( "0 " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
+ aLine.append( " m " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
+ aLine.append( " l S\n" );
+
+ if ( eStrikeout == STRIKEOUT_DOUBLE )
+ {
+ aLine.append( "0 " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
+ aLine.append( " m " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
+ aLine.append( " l S\n" );
+ }
+
+}
+
+void PDFWriterImpl::drawStrikeoutChar( const Point& rPos, long nWidth, FontStrikeout eStrikeout )
+{
+ //See qadevOOo/testdocs/StrikeThrough.odt for examples if you need
+ //to tweak this
+
+ OUString aStrikeoutChar = eStrikeout == STRIKEOUT_SLASH ? OUString( "/" ) : OUString( "X" );
+ OUString aStrikeout = aStrikeoutChar;
+ while( GetTextWidth( aStrikeout ) < nWidth )
+ aStrikeout += aStrikeout;
+
+ // do not get broader than nWidth modulo 1 character
+ while( GetTextWidth( aStrikeout ) >= nWidth )
+ aStrikeout = aStrikeout.replaceAt( 0, 1, "" );
+ 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
+ ComplexTextLayoutFlags nOrigTLM = GetLayoutMode();
+ SetLayoutMode(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, 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 = static_cast<double>(m_aCurrentPDFState.m_aFont.GetOrientation()) * M_PI / 1800.0;
+ Matrix3 aMat;
+ aMat.rotate( fAngle );
+ aMat.translate( aPos.X(), aPos.Y() );
+ m_aPages.back().appendMatrix3(aMat, aLine);
+ aLine.append( " cm\n" );
+
+ if ( aUnderlineColor.GetTransparency() != 0 )
+ aUnderlineColor = aStrikeoutColor;
+
+ if ( (eUnderline == LINESTYLE_SMALLWAVE) ||
+ (eUnderline == LINESTYLE_WAVE) ||
+ (eUnderline == LINESTYLE_DOUBLEWAVE) ||
+ (eUnderline == LINESTYLE_BOLDWAVE) )
+ {
+ drawWaveTextLine( aLine, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove );
+ bUnderlineDone = true;
+ }
+
+ if ( (eOverline == LINESTYLE_SMALLWAVE) ||
+ (eOverline == LINESTYLE_WAVE) ||
+ (eOverline == LINESTYLE_DOUBLEWAVE) ||
+ (eOverline == LINESTYLE_BOLDWAVE) )
+ {
+ drawWaveTextLine( aLine, nWidth, eOverline, aOverlineColor, true );
+ bOverlineDone = true;
+ }
+
+ if ( !bUnderlineDone )
+ {
+ drawStraightTextLine( aLine, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove );
+ }
+
+ if ( !bOverlineDone )
+ {
+ drawStraightTextLine( aLine, nWidth, eOverline, aOverlineColor, true );
+ }
+
+ if ( !bStrikeoutDone )
+ {
+ drawStrikeoutLine( aLine, nWidth, eStrikeout, aStrikeoutColor );
+ }
+
+ aLine.append( "Q\n" );
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+void PDFWriterImpl::drawPolygon( const tools::Polygon& rPoly )
+{
+ MARK( "drawPolygon" );
+
+ updateGraphicsState();
+
+ if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
+ return;
+
+ int nPoints = rPoly.GetSize();
+ OStringBuffer aLine( 20 * nPoints );
+ m_aPages.back().appendPolygon( rPoly, aLine );
+ if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
+ aLine.append( "B*\n" );
+ else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
+ aLine.append( "S\n" );
+ else
+ aLine.append( "f*\n" );
+
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+void PDFWriterImpl::drawPolyPolygon( const tools::PolyPolygon& rPolyPoly )
+{
+ MARK( "drawPolyPolygon" );
+
+ updateGraphicsState();
+
+ if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
+ return;
+
+ int nPolygons = rPolyPoly.Count();
+
+ OStringBuffer aLine( 40 * nPolygons );
+ m_aPages.back().appendPolyPolygon( rPolyPoly, aLine );
+ if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
+ aLine.append( "B*\n" );
+ else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
+ aLine.append( "S\n" );
+ else
+ aLine.append( "f*\n" );
+
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+void PDFWriterImpl::drawTransparent( const tools::PolyPolygon& rPolyPoly, sal_uInt32 nTransparentPercent )
+{
+ SAL_WARN_IF( nTransparentPercent > 100, "vcl.pdfwriter", "invalid alpha value" );
+ nTransparentPercent = nTransparentPercent % 100;
+
+ MARK( "drawTransparent" );
+
+ updateGraphicsState();
+
+ if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
+ return;
+
+ if( m_bIsPDF_A1 || m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
+ {
+ m_aErrors.insert( m_bIsPDF_A1 ?
+ PDFWriter::Warning_Transparency_Omitted_PDFA :
+ PDFWriter::Warning_Transparency_Omitted_PDF13 );
+
+ drawPolyPolygon( rPolyPoly );
+ return;
+ }
+
+ // create XObject
+ m_aTransparentObjects.emplace_back( );
+ // FIXME: polygons with beziers may yield incorrect bound rect
+ m_aTransparentObjects.back().m_aBoundRect = rPolyPoly.GetBoundRect();
+ // convert rectangle to default user space
+ m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect );
+ m_aTransparentObjects.back().m_nObject = createObject();
+ m_aTransparentObjects.back().m_nExtGStateObject = createObject();
+ m_aTransparentObjects.back().m_fAlpha = static_cast<double>(100-nTransparentPercent) / 100.0;
+ m_aTransparentObjects.back().m_pContentStream.reset(new SvMemoryStream( 256, 256 ));
+ // create XObject's content stream
+ OStringBuffer aContent( 256 );
+ m_aPages.back().appendPolyPolygon( rPolyPoly, aContent );
+ if( m_aCurrentPDFState.m_aLineColor != COL_TRANSPARENT &&
+ m_aCurrentPDFState.m_aFillColor != COL_TRANSPARENT )
+ aContent.append( " B*\n" );
+ else if( m_aCurrentPDFState.m_aLineColor != COL_TRANSPARENT )
+ aContent.append( " S\n" );
+ else
+ aContent.append( " f*\n" );
+ m_aTransparentObjects.back().m_pContentStream->WriteBytes(
+ aContent.getStr(), aContent.getLength() );
+
+ OStringBuffer aObjName( 16 );
+ aObjName.append( "Tr" );
+ aObjName.append( m_aTransparentObjects.back().m_nObject );
+ OString aTrName( aObjName.makeStringAndClear() );
+ aObjName.append( "EGS" );
+ aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject );
+ OString aExtName( aObjName.makeStringAndClear() );
+
+ OString aLine =
+ // insert XObject
+ "q /" +
+ aExtName +
+ " gs /" +
+ aTrName +
+ " Do Q\n";
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+
+ pushResource( ResourceKind::XObject, aTrName, m_aTransparentObjects.back().m_nObject );
+ pushResource( ResourceKind::ExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject );
+}
+
+void PDFWriterImpl::pushResource( ResourceKind eKind, const OString& rResource, sal_Int32 nObject )
+{
+ if( nObject >= 0 )
+ {
+ switch( eKind )
+ {
+ case ResourceKind::XObject:
+ m_aGlobalResourceDict.m_aXObjects[ rResource ] = nObject;
+ if( ! m_aOutputStreams.empty() )
+ m_aOutputStreams.front().m_aResourceDict.m_aXObjects[ rResource ] = nObject;
+ break;
+ case ResourceKind::ExtGState:
+ m_aGlobalResourceDict.m_aExtGStates[ rResource ] = nObject;
+ if( ! m_aOutputStreams.empty() )
+ m_aOutputStreams.front().m_aResourceDict.m_aExtGStates[ rResource ] = nObject;
+ break;
+ case ResourceKind::Shading:
+ m_aGlobalResourceDict.m_aShadings[ rResource ] = nObject;
+ if( ! m_aOutputStreams.empty() )
+ m_aOutputStreams.front().m_aResourceDict.m_aShadings[ rResource ] = nObject;
+ break;
+ case ResourceKind::Pattern:
+ m_aGlobalResourceDict.m_aPatterns[ rResource ] = nObject;
+ if( ! m_aOutputStreams.empty() )
+ m_aOutputStreams.front().m_aResourceDict.m_aPatterns[ rResource ] = nObject;
+ break;
+ }
+ }
+}
+
+void PDFWriterImpl::beginRedirect( SvStream* pStream, const tools::Rectangle& rTargetRect )
+{
+ push( PushFlags::ALL );
+
+ // force reemitting clip region inside the new stream, and
+ // prevent emitting an unbalanced "Q" at the start
+ clearClipRegion();
+ // this is needed to point m_aCurrentPDFState at the pushed state
+ // ... but it's pointless to actually write into the "outer" stream here!
+ updateGraphicsState(Mode::NOWRITE);
+
+ m_aOutputStreams.push_front( StreamRedirect() );
+ m_aOutputStreams.front().m_pStream = pStream;
+ m_aOutputStreams.front().m_aMapMode = m_aMapMode;
+
+ if( !rTargetRect.IsEmpty() )
+ {
+ m_aOutputStreams.front().m_aTargetRect =
+ lcl_convert( m_aGraphicsStack.front().m_aMapMode,
+ m_aMapMode,
+ this,
+ rTargetRect );
+ Point aDelta = m_aOutputStreams.front().m_aTargetRect.BottomLeft();
+ long nPageHeight = pointToPixel(m_aPages[m_nCurrentPage].getHeight());
+ aDelta.setY( -(nPageHeight - m_aOutputStreams.front().m_aTargetRect.Bottom()) );
+ m_aMapMode.SetOrigin( m_aMapMode.GetOrigin() + aDelta );
+ }
+
+ // setup graphics state for independent object stream
+
+ // force reemitting colors
+ m_aCurrentPDFState.m_aLineColor = COL_TRANSPARENT;
+ m_aCurrentPDFState.m_aFillColor = COL_TRANSPARENT;
+}
+
+SvStream* PDFWriterImpl::endRedirect()
+{
+ SvStream* pStream = nullptr;
+ if( ! m_aOutputStreams.empty() )
+ {
+ pStream = m_aOutputStreams.front().m_pStream;
+ m_aMapMode = m_aOutputStreams.front().m_aMapMode;
+ m_aOutputStreams.pop_front();
+ }
+
+ pop();
+
+ m_aCurrentPDFState.m_aLineColor = COL_TRANSPARENT;
+ m_aCurrentPDFState.m_aFillColor = COL_TRANSPARENT;
+
+ // needed after pop() to set m_aCurrentPDFState
+ updateGraphicsState(Mode::NOWRITE);
+
+ return pStream;
+}
+
+void PDFWriterImpl::beginTransparencyGroup()
+{
+ updateGraphicsState();
+ if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
+ beginRedirect( new SvMemoryStream( 1024, 1024 ), tools::Rectangle() );
+}
+
+void PDFWriterImpl::endTransparencyGroup( const tools::Rectangle& rBoundingBox, sal_uInt32 nTransparentPercent )
+{
+ SAL_WARN_IF( nTransparentPercent > 100, "vcl.pdfwriter", "invalid alpha value" );
+ nTransparentPercent = nTransparentPercent % 100;
+
+ if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
+ return;
+
+ // create XObject
+ m_aTransparentObjects.emplace_back( );
+ m_aTransparentObjects.back().m_aBoundRect = rBoundingBox;
+ // convert rectangle to default user space
+ m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect );
+ m_aTransparentObjects.back().m_nObject = createObject();
+ m_aTransparentObjects.back().m_fAlpha = static_cast<double>(100-nTransparentPercent) / 100.0;
+ // get XObject's content stream
+ m_aTransparentObjects.back().m_pContentStream.reset( static_cast<SvMemoryStream*>(endRedirect()) );
+ m_aTransparentObjects.back().m_nExtGStateObject = createObject();
+
+ OStringBuffer aObjName( 16 );
+ aObjName.append( "Tr" );
+ aObjName.append( m_aTransparentObjects.back().m_nObject );
+ OString aTrName( aObjName.makeStringAndClear() );
+ aObjName.append( "EGS" );
+ aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject );
+ OString aExtName( aObjName.makeStringAndClear() );
+
+ OString aLine =
+ // insert XObject
+ "q /" +
+ aExtName +
+ " gs /" +
+ aTrName +
+ " Do Q\n";
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+
+ pushResource( ResourceKind::XObject, aTrName, m_aTransparentObjects.back().m_nObject );
+ pushResource( ResourceKind::ExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject );
+
+}
+
+void PDFWriterImpl::drawRectangle( const tools::Rectangle& rRect )
+{
+ MARK( "drawRectangle" );
+
+ updateGraphicsState();
+
+ if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
+ return;
+
+ OStringBuffer aLine( 40 );
+ m_aPages.back().appendRect( rRect, aLine );
+
+ if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
+ aLine.append( " B*\n" );
+ else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
+ aLine.append( " S\n" );
+ else
+ aLine.append( " f*\n" );
+
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+void PDFWriterImpl::drawRectangle( const tools::Rectangle& rRect, sal_uInt32 nHorzRound, sal_uInt32 nVertRound )
+{
+ MARK( "drawRectangle with rounded edges" );
+
+ if( !nHorzRound && !nVertRound )
+ drawRectangle( rRect );
+
+ updateGraphicsState();
+
+ if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
+ return;
+
+ if( nHorzRound > static_cast<sal_uInt32>(rRect.GetWidth())/2 )
+ nHorzRound = rRect.GetWidth()/2;
+ if( nVertRound > static_cast<sal_uInt32>(rRect.GetHeight())/2 )
+ nVertRound = rRect.GetHeight()/2;
+
+ Point aPoints[16];
+ const double kappa = 0.5522847498;
+ const sal_uInt32 kx = static_cast<sal_uInt32>((kappa*static_cast<double>(nHorzRound))+0.5);
+ const sal_uInt32 ky = static_cast<sal_uInt32>((kappa*static_cast<double>(nVertRound))+0.5);
+
+ aPoints[1] = Point( rRect.TopLeft().X() + nHorzRound, rRect.TopLeft().Y() );
+ aPoints[0] = Point( aPoints[1].X() - kx, aPoints[1].Y() );
+ aPoints[2] = Point( rRect.TopRight().X()+1 - nHorzRound, aPoints[1].Y() );
+ aPoints[3] = Point( aPoints[2].X()+kx, aPoints[2].Y() );
+
+ aPoints[5] = Point( rRect.TopRight().X()+1, rRect.TopRight().Y()+nVertRound );
+ aPoints[4] = Point( aPoints[5].X(), aPoints[5].Y()-ky );
+ aPoints[6] = Point( aPoints[5].X(), rRect.BottomRight().Y()+1 - nVertRound );
+ aPoints[7] = Point( aPoints[6].X(), aPoints[6].Y()+ky );
+
+ aPoints[9] = Point( rRect.BottomRight().X()+1-nHorzRound, rRect.BottomRight().Y()+1 );
+ aPoints[8] = Point( aPoints[9].X()+kx, aPoints[9].Y() );
+ aPoints[10] = Point( rRect.BottomLeft().X() + nHorzRound, aPoints[9].Y() );
+ aPoints[11] = Point( aPoints[10].X()-kx, aPoints[10].Y() );
+
+ aPoints[13] = Point( rRect.BottomLeft().X(), rRect.BottomLeft().Y()+1-nVertRound );
+ aPoints[12] = Point( aPoints[13].X(), aPoints[13].Y()+ky );
+ aPoints[14] = Point( rRect.TopLeft().X(), rRect.TopLeft().Y()+nVertRound );
+ aPoints[15] = Point( aPoints[14].X(), aPoints[14].Y()-ky );
+
+ OStringBuffer aLine( 80 );
+ m_aPages.back().appendPoint( aPoints[1], aLine );
+ aLine.append( " m " );
+ m_aPages.back().appendPoint( aPoints[2], aLine );
+ aLine.append( " l " );
+ m_aPages.back().appendPoint( aPoints[3], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[4], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[5], aLine );
+ aLine.append( " c\n" );
+ m_aPages.back().appendPoint( aPoints[6], aLine );
+ aLine.append( " l " );
+ m_aPages.back().appendPoint( aPoints[7], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[8], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[9], aLine );
+ aLine.append( " c\n" );
+ m_aPages.back().appendPoint( aPoints[10], aLine );
+ aLine.append( " l " );
+ m_aPages.back().appendPoint( aPoints[11], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[12], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[13], aLine );
+ aLine.append( " c\n" );
+ m_aPages.back().appendPoint( aPoints[14], aLine );
+ aLine.append( " l " );
+ m_aPages.back().appendPoint( aPoints[15], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[0], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[1], aLine );
+ aLine.append( " c " );
+
+ if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
+ aLine.append( "b*\n" );
+ else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
+ aLine.append( "s\n" );
+ else
+ aLine.append( "f*\n" );
+
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+void PDFWriterImpl::drawEllipse( const tools::Rectangle& rRect )
+{
+ MARK( "drawEllipse" );
+
+ updateGraphicsState();
+
+ if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
+ return;
+
+ Point aPoints[12];
+ const double kappa = 0.5522847498;
+ const sal_uInt32 kx = static_cast<sal_uInt32>((kappa*static_cast<double>(rRect.GetWidth())/2.0)+0.5);
+ const sal_uInt32 ky = static_cast<sal_uInt32>((kappa*static_cast<double>(rRect.GetHeight())/2.0)+0.5);
+
+ aPoints[1] = Point( rRect.TopLeft().X() + rRect.GetWidth()/2, rRect.TopLeft().Y() );
+ aPoints[0] = Point( aPoints[1].X() - kx, aPoints[1].Y() );
+ aPoints[2] = Point( aPoints[1].X() + kx, aPoints[1].Y() );
+
+ aPoints[4] = Point( rRect.TopRight().X()+1, rRect.TopRight().Y() + 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.BottomLeft().X() + rRect.GetWidth()/2, rRect.BottomLeft().Y()+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.TopLeft().X(), rRect.TopLeft().Y() + rRect.GetHeight()/2 );
+ aPoints[9] = Point( aPoints[10].X(), aPoints[10].Y() + ky );
+ aPoints[11] = Point( aPoints[10].X(), aPoints[10].Y() - ky );
+
+ OStringBuffer aLine( 80 );
+ m_aPages.back().appendPoint( aPoints[1], aLine );
+ aLine.append( " m " );
+ m_aPages.back().appendPoint( aPoints[2], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[3], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[4], aLine );
+ aLine.append( " c\n" );
+ m_aPages.back().appendPoint( aPoints[5], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[6], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[7], aLine );
+ aLine.append( " c\n" );
+ m_aPages.back().appendPoint( aPoints[8], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[9], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[10], aLine );
+ aLine.append( " c\n" );
+ m_aPages.back().appendPoint( aPoints[11], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[0], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[1], aLine );
+ aLine.append( " c " );
+
+ if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
+ aLine.append( "b*\n" );
+ else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
+ aLine.append( "s\n" );
+ else
+ aLine.append( "f*\n" );
+
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+static double calcAngle( const tools::Rectangle& rRect, const Point& rPoint )
+{
+ Point aOrigin((rRect.Left()+rRect.Right()+1)/2,
+ (rRect.Top()+rRect.Bottom()+1)/2);
+ Point aPoint = rPoint - aOrigin;
+
+ double fX = static_cast<double>(aPoint.X());
+ double fY = static_cast<double>(-aPoint.Y());
+
+ if ((rRect.GetHeight() == 0) || (rRect.GetWidth() == 0))
+ throw o3tl::divide_by_zero();
+
+ if( rRect.GetWidth() > rRect.GetHeight() )
+ fY = fY*(static_cast<double>(rRect.GetWidth())/static_cast<double>(rRect.GetHeight()));
+ else if( rRect.GetHeight() > rRect.GetWidth() )
+ fX = fX*(static_cast<double>(rRect.GetHeight())/static_cast<double>(rRect.GetWidth()));
+ return atan2( fY, fX );
+}
+
+void PDFWriterImpl::drawArc( const tools::Rectangle& rRect, const Point& rStart, const Point& rStop, bool bWithPie, bool bWithChord )
+{
+ MARK( "drawArc" );
+
+ updateGraphicsState();
+
+ if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
+ return;
+
+ // calculate start and stop angles
+ const double fStartAngle = calcAngle( rRect, rStart );
+ double fStopAngle = calcAngle( rRect, rStop );
+ while( fStopAngle < fStartAngle )
+ fStopAngle += 2.0*M_PI;
+ const int nFragments = static_cast<int>((fStopAngle-fStartAngle)/(M_PI/2.0))+1;
+ const double fFragmentDelta = (fStopAngle-fStartAngle)/static_cast<double>(nFragments);
+ const double kappa = fabs( 4.0 * (1.0-cos(fFragmentDelta/2.0))/sin(fFragmentDelta/2.0) / 3.0);
+ const double halfWidth = static_cast<double>(rRect.GetWidth())/2.0;
+ const double halfHeight = static_cast<double>(rRect.GetHeight())/2.0;
+
+ const Point aCenter( (rRect.Left()+rRect.Right()+1)/2,
+ (rRect.Top()+rRect.Bottom()+1)/2 );
+
+ OStringBuffer aLine( 30*nFragments );
+ Point aPoint( static_cast<int>(halfWidth * cos(fStartAngle) ),
+ -static_cast<int>(halfHeight * sin(fStartAngle) ) );
+ aPoint += aCenter;
+ m_aPages.back().appendPoint( aPoint, aLine );
+ aLine.append( " m " );
+ if( !basegfx::fTools::equal(fStartAngle, fStopAngle) )
+ {
+ for( int i = 0; i < nFragments; i++ )
+ {
+ const double fStartFragment = fStartAngle + static_cast<double>(i)*fFragmentDelta;
+ const double fStopFragment = fStartFragment + fFragmentDelta;
+ aPoint = Point( static_cast<int>(halfWidth * (cos(fStartFragment) - kappa*sin(fStartFragment) ) ),
+ -static_cast<int>(halfHeight * (sin(fStartFragment) + kappa*cos(fStartFragment) ) ) );
+ aPoint += aCenter;
+ m_aPages.back().appendPoint( aPoint, aLine );
+ aLine.append( ' ' );
+
+ aPoint = Point( static_cast<int>(halfWidth * (cos(fStopFragment) + kappa*sin(fStopFragment) ) ),
+ -static_cast<int>(halfHeight * (sin(fStopFragment) - kappa*cos(fStopFragment) ) ) );
+ aPoint += aCenter;
+ m_aPages.back().appendPoint( aPoint, aLine );
+ aLine.append( ' ' );
+
+ aPoint = Point( static_cast<int>(halfWidth * cos(fStopFragment) ),
+ -static_cast<int>(halfHeight * sin(fStopFragment) ) );
+ aPoint += aCenter;
+ m_aPages.back().appendPoint( aPoint, aLine );
+ aLine.append( " c\n" );
+ }
+ }
+ if( bWithChord || bWithPie )
+ {
+ if( bWithPie )
+ {
+ m_aPages.back().appendPoint( aCenter, aLine );
+ aLine.append( " l " );
+ }
+ aLine.append( "h " );
+ }
+ if( ! bWithChord && ! bWithPie )
+ aLine.append( "S\n" );
+ else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
+ aLine.append( "B*\n" );
+ else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
+ aLine.append( "S\n" );
+ else
+ aLine.append( "f*\n" );
+
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly )
+{
+ MARK( "drawPolyLine" );
+
+ sal_uInt16 nPoints = rPoly.GetSize();
+ if( nPoints < 2 )
+ return;
+
+ updateGraphicsState();
+
+ if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
+ return;
+
+ OStringBuffer aLine( 20 * nPoints );
+ m_aPages.back().appendPolygon( rPoly, aLine, rPoly[0] == rPoly[nPoints-1] );
+ aLine.append( "S\n" );
+
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly, const LineInfo& rInfo )
+{
+ MARK( "drawPolyLine with LineInfo" );
+
+ updateGraphicsState();
+
+ if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
+ return;
+
+ OStringBuffer aLine;
+ aLine.append( "q " );
+ if( m_aPages.back().appendLineInfo( rInfo, aLine ) )
+ {
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+ drawPolyLine( rPoly );
+ writeBuffer( "Q\n", 2 );
+ }
+ else
+ {
+ PDFWriter::ExtLineInfo aInfo;
+ convertLineInfoToExtLineInfo( rInfo, aInfo );
+ drawPolyLine( rPoly, aInfo );
+ }
+}
+
+void PDFWriterImpl::convertLineInfoToExtLineInfo( const LineInfo& rIn, PDFWriter::ExtLineInfo& rOut )
+{
+ SAL_WARN_IF( rIn.GetStyle() != LineStyle::Dash, "vcl.pdfwriter", "invalid conversion" );
+ rOut.m_fLineWidth = rIn.GetWidth();
+ rOut.m_fTransparency = 0.0;
+ rOut.m_eCap = PDFWriter::capButt;
+ rOut.m_eJoin = PDFWriter::joinMiter;
+ rOut.m_fMiterLimit = 10;
+ rOut.m_aDashArray.clear();
+
+ // add DashDot to DashArray
+ const int nDashes = rIn.GetDashCount();
+ const int nDashLen = rIn.GetDashLen();
+ const int nDistance = rIn.GetDistance();
+
+ for( int n = 0; n < nDashes; n++ )
+ {
+ rOut.m_aDashArray.push_back( nDashLen );
+ rOut.m_aDashArray.push_back( nDistance );
+ }
+ const int nDots = rIn.GetDotCount();
+ const int nDotLen = rIn.GetDotLen();
+
+ for( int n = 0; n < nDots; n++ )
+ {
+ rOut.m_aDashArray.push_back( nDotLen );
+ rOut.m_aDashArray.push_back( nDistance );
+ }
+
+ // add LineJoin
+ switch(rIn.GetLineJoin())
+ {
+ case basegfx::B2DLineJoin::Bevel :
+ {
+ rOut.m_eJoin = PDFWriter::joinBevel;
+ break;
+ }
+ // Pdf has no 'none' lineJoin, default is miter
+ case basegfx::B2DLineJoin::NONE :
+ case basegfx::B2DLineJoin::Miter :
+ {
+ rOut.m_eJoin = PDFWriter::joinMiter;
+ break;
+ }
+ case basegfx::B2DLineJoin::Round :
+ {
+ rOut.m_eJoin = PDFWriter::joinRound;
+ break;
+ }
+ }
+
+ // add LineCap
+ switch(rIn.GetLineCap())
+ {
+ default: /* css::drawing::LineCap_BUTT */
+ {
+ rOut.m_eCap = PDFWriter::capButt;
+ break;
+ }
+ case css::drawing::LineCap_ROUND:
+ {
+ rOut.m_eCap = PDFWriter::capRound;
+ break;
+ }
+ case css::drawing::LineCap_SQUARE:
+ {
+ rOut.m_eCap = PDFWriter::capSquare;
+ break;
+ }
+ }
+}
+
+void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly, const PDFWriter::ExtLineInfo& rInfo )
+{
+ MARK( "drawPolyLine with ExtLineInfo" );
+
+ updateGraphicsState();
+
+ if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
+ return;
+
+ if( rInfo.m_fTransparency >= 1.0 )
+ return;
+
+ if( rInfo.m_fTransparency != 0.0 )
+ beginTransparencyGroup();
+
+ OStringBuffer aLine;
+ aLine.append( "q " );
+ m_aPages.back().appendMappedLength( rInfo.m_fLineWidth, aLine );
+ aLine.append( " w" );
+ if( rInfo.m_aDashArray.size() < 10 ) // implementation limit of acrobat reader
+ {
+ switch( rInfo.m_eCap )
+ {
+ default:
+ case PDFWriter::capButt: aLine.append( " 0 J" );break;
+ case PDFWriter::capRound: aLine.append( " 1 J" );break;
+ case PDFWriter::capSquare: aLine.append( " 2 J" );break;
+ }
+ switch( rInfo.m_eJoin )
+ {
+ default:
+ case PDFWriter::joinMiter:
+ {
+ double fLimit = rInfo.m_fMiterLimit;
+ if( rInfo.m_fLineWidth < rInfo.m_fMiterLimit )
+ fLimit = fLimit / rInfo.m_fLineWidth;
+ if( fLimit < 1.0 )
+ fLimit = 1.0;
+ aLine.append( " 0 j " );
+ appendDouble( fLimit, aLine );
+ aLine.append( " M" );
+ }
+ break;
+ case PDFWriter::joinRound: aLine.append( " 1 j" );break;
+ case PDFWriter::joinBevel: aLine.append( " 2 j" );break;
+ }
+ if( !rInfo.m_aDashArray.empty() )
+ {
+ aLine.append( " [ " );
+ for (auto const& dash : rInfo.m_aDashArray)
+ {
+ m_aPages.back().appendMappedLength( dash, aLine );
+ aLine.append( ' ' );
+ }
+ aLine.append( "] 0 d" );
+ }
+ aLine.append( "\n" );
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+ drawPolyLine( rPoly );
+ }
+ else
+ {
+ basegfx::B2DPolygon aPoly(rPoly.getB2DPolygon());
+ basegfx::B2DPolyPolygon aPolyPoly;
+
+ basegfx::utils::applyLineDashing(aPoly, rInfo.m_aDashArray, &aPolyPoly);
+
+ // Old applyLineDashing subdivided the polygon. New one will create bezier curve segments.
+ // To mimic old behaviour, apply subdivide here. If beziers shall be written (better quality)
+ // this line needs to be removed and the loop below adapted accordingly
+ aPolyPoly = basegfx::utils::adaptiveSubdivideByAngle(aPolyPoly);
+
+ const sal_uInt32 nPolygonCount(aPolyPoly.count());
+
+ for( sal_uInt32 nPoly = 0; nPoly < nPolygonCount; nPoly++ )
+ {
+ aLine.append( (nPoly != 0 && (nPoly & 7) == 0) ? "\n" : " " );
+ aPoly = aPolyPoly.getB2DPolygon( nPoly );
+ const sal_uInt32 nPointCount(aPoly.count());
+
+ if(nPointCount)
+ {
+ const sal_uInt32 nEdgeCount(aPoly.isClosed() ? nPointCount : nPointCount - 1);
+ basegfx::B2DPoint aCurrent(aPoly.getB2DPoint(0));
+
+ for(sal_uInt32 a(0); a < nEdgeCount; a++)
+ {
+ if( a > 0 )
+ aLine.append( " " );
+ const sal_uInt32 nNextIndex((a + 1) % nPointCount);
+ const basegfx::B2DPoint aNext(aPoly.getB2DPoint(nNextIndex));
+
+ m_aPages.back().appendPoint( Point( FRound(aCurrent.getX()),
+ FRound(aCurrent.getY()) ),
+ aLine );
+ aLine.append( " m " );
+ m_aPages.back().appendPoint( Point( FRound(aNext.getX()),
+ FRound(aNext.getY()) ),
+ aLine );
+ aLine.append( " l" );
+
+ // prepare next edge
+ aCurrent = aNext;
+ }
+ }
+ }
+ aLine.append( " S " );
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+ }
+ writeBuffer( "Q\n", 2 );
+
+ if( rInfo.m_fTransparency != 0.0 )
+ {
+ // FIXME: actually this may be incorrect with bezier polygons
+ tools::Rectangle aBoundRect( rPoly.GetBoundRect() );
+ // avoid clipping with thick lines
+ if( rInfo.m_fLineWidth > 0.0 )
+ {
+ sal_Int32 nLW = sal_Int32(rInfo.m_fLineWidth);
+ aBoundRect.AdjustTop( -nLW );
+ aBoundRect.AdjustLeft( -nLW );
+ aBoundRect.AdjustRight(nLW );
+ aBoundRect.AdjustBottom(nLW );
+ }
+ endTransparencyGroup( aBoundRect, static_cast<sal_uInt16>(100.0*rInfo.m_fTransparency) );
+ }
+}
+
+void PDFWriterImpl::drawPixel( const Point& rPoint, const Color& rColor )
+{
+ MARK( "drawPixel" );
+
+ Color aColor = ( rColor == COL_TRANSPARENT ? m_aGraphicsStack.front().m_aLineColor : rColor );
+
+ if( aColor == COL_TRANSPARENT )
+ return;
+
+ // pixels are drawn in line color, so have to set
+ // the nonstroking color to line color
+ Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor;
+ setFillColor( aColor );
+
+ updateGraphicsState();
+
+ OStringBuffer aLine( 20 );
+ m_aPages.back().appendPoint( rPoint, aLine );
+ aLine.append( ' ' );
+ appendDouble( 1.0/double(GetDPIX()), aLine );
+ aLine.append( ' ' );
+ appendDouble( 1.0/double(GetDPIY()), aLine );
+ aLine.append( " re f\n" );
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+
+ setFillColor( aOldFillColor );
+}
+
+void PDFWriterImpl::writeTransparentObject( TransparencyEmit& rObject )
+{
+ CHECK_RETURN2( updateObject( rObject.m_nObject ) );
+
+ bool bFlateFilter = compressStream( rObject.m_pContentStream.get() );
+ sal_uLong 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( ! rObject.m_pSoftMaskStream )
+ {
+ if( ! m_bIsPDF_A1 )
+ {
+ // 7.8.3 Resource dicts are required for content streams
+ aLine.append( "/Resources " );
+ aLine.append( getResourceDictObj() );
+ aLine.append( " 0 R\n" );
+
+ aLine.append( "/Group<</S/Transparency/CS/DeviceRGB/K true>>\n" );
+ }
+ }
+
+ aLine.append( "/Length " );
+ aLine.append( static_cast<sal_Int32>(nSize) );
+ aLine.append( "\n" );
+ if( bFlateFilter )
+ aLine.append( "/Filter/FlateDecode\n" );
+ aLine.append( ">>\n"
+ "stream\n" );
+ CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+ checkAndEnableStreamEncryption( rObject.m_nObject );
+ CHECK_RETURN2( writeBuffer( rObject.m_pContentStream->GetData(), nSize ) );
+ disableStreamEncryption();
+ aLine.setLength( 0 );
+ aLine.append( "\n"
+ "endstream\n"
+ "endobj\n\n" );
+ CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+
+ // write ExtGState dict for this XObject
+ aLine.setLength( 0 );
+ aLine.append( rObject.m_nExtGStateObject );
+ aLine.append( " 0 obj\n"
+ "<<" );
+ if( ! rObject.m_pSoftMaskStream )
+ {
+ 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" );
+ }
+ else
+ {
+ if( m_bIsPDF_A1 )
+ {
+ aLine.append( "/SMask/None" );
+ m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
+ }
+ else
+ {
+ sal_Int32 nMaskSize = static_cast<sal_Int32>(rObject.m_pSoftMaskStream->TellEnd());
+ rObject.m_pSoftMaskStream->Seek( STREAM_SEEK_TO_BEGIN );
+ sal_Int32 nMaskObject = createObject();
+ aLine.append( "/SMask<</Type/Mask/S/Luminosity/G " );
+ aLine.append( nMaskObject );
+ aLine.append( " 0 R>>\n" );
+
+ OStringBuffer aMask;
+ aMask.append( nMaskObject );
+ aMask.append( " 0 obj\n"
+ "<</Type/XObject\n"
+ "/Subtype/Form\n"
+ "/BBox[" );
+ appendFixedInt( rObject.m_aBoundRect.Left(), aMask );
+ aMask.append( ' ' );
+ appendFixedInt( rObject.m_aBoundRect.Top(), aMask );
+ aMask.append( ' ' );
+ appendFixedInt( rObject.m_aBoundRect.Right(), aMask );
+ aMask.append( ' ' );
+ appendFixedInt( rObject.m_aBoundRect.Bottom()+1, aMask );
+ aMask.append( "]\n" );
+
+ // 7.8.3 Resource dicts are required for content streams
+ aMask.append( "/Resources " );
+ aMask.append( getResourceDictObj() );
+ aMask.append( " 0 R\n" );
+
+ aMask.append( "/Group<</S/Transparency/CS/DeviceRGB>>\n" );
+ aMask.append( "/Length " );
+ aMask.append( nMaskSize );
+ aMask.append( ">>\n"
+ "stream\n" );
+ CHECK_RETURN2( updateObject( nMaskObject ) );
+ checkAndEnableStreamEncryption( nMaskObject );
+ CHECK_RETURN2( writeBuffer( aMask.getStr(), aMask.getLength() ) );
+ CHECK_RETURN2( writeBuffer( rObject.m_pSoftMaskStream->GetData(), nMaskSize ) );
+ disableStreamEncryption();
+ aMask.setLength( 0 );
+ aMask.append( "\nendstream\n"
+ "endobj\n\n" );
+ CHECK_RETURN2( writeBuffer( aMask.getStr(), aMask.getLength() ) );
+ }
+ }
+ aLine.append( ">>\n"
+ "endobj\n\n" );
+ CHECK_RETURN2( updateObject( rObject.m_nExtGStateObject ) );
+ CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+}
+
+bool PDFWriterImpl::writeGradientFunction( GradientEmit const & rObject )
+{
+ // LO internal gradient -> PDF shading type:
+ // * GradientStyle::Linear: axial shading, using sampled-function with 2 samples
+ // [t=0:colorStart, t=1:colorEnd]
+ // * GradientStyle::Axial: axial shading, using sampled-function with 3 samples
+ // [t=0:colorEnd, t=0.5:colorStart, t=1:colorEnd]
+ // * other styles: function shading with aSize.Width() * aSize.Height() samples
+ sal_Int32 nFunctionObject = createObject();
+ CHECK_RETURN( updateObject( nFunctionObject ) );
+
+ ScopedVclPtrInstance< VirtualDevice > aDev;
+ aDev->SetOutputSizePixel( rObject.m_aSize );
+ aDev->SetMapMode( MapMode( MapUnit::MapPixel ) );
+ if( m_aContext.ColorMode == PDFWriter::DrawGreyscale )
+ aDev->SetDrawMode( aDev->GetDrawMode() |
+ ( DrawModeFlags::GrayLine | DrawModeFlags::GrayFill | DrawModeFlags::GrayText |
+ DrawModeFlags::GrayBitmap | DrawModeFlags::GrayGradient ) );
+ aDev->DrawGradient( tools::Rectangle( Point( 0, 0 ), rObject.m_aSize ), rObject.m_aGradient );
+
+ Bitmap aSample = aDev->GetBitmap( Point( 0, 0 ), rObject.m_aSize );
+ Bitmap::ScopedReadAccess pAccess(aSample);
+
+ Size aSize = aSample.GetSizePixel();
+
+ sal_Int32 nStreamLengthObject = createObject();
+ if (g_bDebugDisableCompression)
+ {
+ emitComment( "PDFWriterImpl::writeGradientFunction" );
+ }
+ OStringBuffer aLine( 120 );
+ aLine.append( nFunctionObject );
+ aLine.append( " 0 obj\n"
+ "<</FunctionType 0\n");
+ switch (rObject.m_aGradient.GetStyle())
+ {
+ case GradientStyle::Linear:
+ case GradientStyle::Axial:
+ aLine.append("/Domain[ 0 1]\n");
+ break;
+ default:
+ aLine.append("/Domain[ 0 1 0 1]\n");
+ }
+ aLine.append("/Size[ " );
+ switch (rObject.m_aGradient.GetStyle())
+ {
+ case GradientStyle::Linear:
+ aLine.append('2');
+ break;
+ case GradientStyle::Axial:
+ aLine.append('3');
+ break;
+ default:
+ aLine.append( static_cast<sal_Int32>(aSize.Width()) );
+ aLine.append( ' ' );
+ aLine.append( static_cast<sal_Int32>(aSize.Height()) );
+ }
+ aLine.append( " ]\n"
+ "/BitsPerSample 8\n"
+ "/Range[ 0 1 0 1 0 1 ]\n"
+ "/Order 3\n"
+ "/Length " );
+ aLine.append( nStreamLengthObject );
+ if (!g_bDebugDisableCompression)
+ aLine.append( " 0 R\n"
+ "/Filter/FlateDecode"
+ ">>\n"
+ "stream\n" );
+ else
+ aLine.append( " 0 R\n"
+ ">>\n"
+ "stream\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+
+ sal_uInt64 nStartStreamPos = 0;
+ CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nStartStreamPos)) );
+
+ checkAndEnableStreamEncryption( nFunctionObject );
+ beginCompression();
+ sal_uInt8 aCol[3];
+ switch (rObject.m_aGradient.GetStyle())
+ {
+ case GradientStyle::Axial:
+ aCol[0] = rObject.m_aGradient.GetEndColor().GetRed();
+ aCol[1] = rObject.m_aGradient.GetEndColor().GetGreen();
+ aCol[2] = rObject.m_aGradient.GetEndColor().GetBlue();
+ CHECK_RETURN( writeBuffer( aCol, 3 ) );
+ [[fallthrough]];
+ case GradientStyle::Linear:
+ {
+ aCol[0] = rObject.m_aGradient.GetStartColor().GetRed();
+ aCol[1] = rObject.m_aGradient.GetStartColor().GetGreen();
+ aCol[2] = rObject.m_aGradient.GetStartColor().GetBlue();
+ CHECK_RETURN( writeBuffer( aCol, 3 ) );
+
+ aCol[0] = rObject.m_aGradient.GetEndColor().GetRed();
+ aCol[1] = rObject.m_aGradient.GetEndColor().GetGreen();
+ aCol[2] = rObject.m_aGradient.GetEndColor().GetBlue();
+ CHECK_RETURN( writeBuffer( aCol, 3 ) );
+ break;
+ }
+ default:
+ for( int y = aSize.Height()-1; y >= 0; y-- )
+ {
+ for( long x = 0; x < aSize.Width(); x++ )
+ {
+ BitmapColor aColor = pAccess->GetColor( y, x );
+ aCol[0] = aColor.GetRed();
+ aCol[1] = aColor.GetGreen();
+ aCol[2] = aColor.GetBlue();
+ CHECK_RETURN( writeBuffer( aCol, 3 ) );
+ }
+ }
+ }
+ endCompression();
+ disableStreamEncryption();
+
+ sal_uInt64 nEndStreamPos = 0;
+ CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nEndStreamPos)) );
+
+ aLine.setLength( 0 );
+ aLine.append( "\nendstream\nendobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+
+ // write stream length
+ CHECK_RETURN( updateObject( nStreamLengthObject ) );
+ aLine.setLength( 0 );
+ aLine.append( nStreamLengthObject );
+ aLine.append( " 0 obj\n" );
+ aLine.append( static_cast<sal_Int64>(nEndStreamPos-nStartStreamPos) );
+ aLine.append( "\nendobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+
+ CHECK_RETURN( updateObject( rObject.m_nObject ) );
+ aLine.setLength( 0 );
+ aLine.append( rObject.m_nObject );
+ aLine.append( " 0 obj\n");
+ switch (rObject.m_aGradient.GetStyle())
+ {
+ case GradientStyle::Linear:
+ case GradientStyle::Axial:
+ aLine.append("<</ShadingType 2\n");
+ break;
+ default:
+ aLine.append("<</ShadingType 1\n");
+ }
+ aLine.append("/ColorSpace/DeviceRGB\n"
+ "/AntiAlias true\n");
+
+ // Determination of shading axis
+ // See: OutputDevice::ImplDrawLinearGradient for reference
+ tools::Rectangle aRect;
+ aRect.SetLeft(0);
+ aRect.SetTop(0);
+ aRect.SetRight( aSize.Width() );
+ aRect.SetBottom( aSize.Height() );
+
+ tools::Rectangle aBoundRect;
+ Point aCenter;
+ sal_uInt16 nAngle = rObject.m_aGradient.GetAngle() % 3600;
+ rObject.m_aGradient.GetBoundRect( aRect, aBoundRect, aCenter );
+
+ const bool bLinear = (rObject.m_aGradient.GetStyle() == GradientStyle::Linear);
+ double fBorder = aBoundRect.GetHeight() * rObject.m_aGradient.GetBorder() / 100.0;
+ if ( !bLinear )
+ {
+ fBorder /= 2.0;
+ }
+
+ aBoundRect.AdjustBottom( -fBorder );
+ if (!bLinear)
+ {
+ aBoundRect.AdjustTop(fBorder );
+ }
+
+ switch (rObject.m_aGradient.GetStyle())
+ {
+ case GradientStyle::Linear:
+ case GradientStyle::Axial:
+ {
+ aLine.append("/Domain[ 0 1 ]\n"
+ "/Coords[ " );
+ tools::Polygon aPoly( 2 );
+ aPoly[0] = aBoundRect.BottomCenter();
+ aPoly[1] = aBoundRect.TopCenter();
+ aPoly.Rotate( aCenter, 3600 - nAngle );
+
+ aLine.append( static_cast<sal_Int32>(aPoly[0].X()) );
+ aLine.append( " " );
+ aLine.append( static_cast<sal_Int32>(aPoly[0].Y()) );
+ aLine.append( " " );
+ aLine.append( static_cast<sal_Int32>(aPoly[1].X()));
+ aLine.append( " ");
+ aLine.append( static_cast<sal_Int32>(aPoly[1].Y()));
+ aLine.append( " ]\n");
+ aLine.append("/Extend [true true]\n");
+ break;
+ }
+ default:
+ aLine.append("/Domain[ 0 1 0 1 ]\n"
+ "/Matrix[ " );
+ aLine.append( static_cast<sal_Int32>(aSize.Width()) );
+ aLine.append( " 0 0 " );
+ aLine.append( static_cast<sal_Int32>(aSize.Height()) );
+ aLine.append( " 0 0 ]\n");
+ }
+ aLine.append("/Function " );
+ aLine.append( nFunctionObject );
+ aLine.append( " 0 R\n"
+ ">>\n"
+ "endobj\n\n" );
+ return writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+void PDFWriterImpl::writeJPG( JPGEmit& rObject )
+{
+ if (!rObject.m_aReferenceXObject.m_aPDFData.empty() && !m_aContext.UseReferenceXObject)
+ {
+ writeReferenceXObject(rObject.m_aReferenceXObject);
+ return;
+ }
+
+ CHECK_RETURN2( rObject.m_pStream );
+ CHECK_RETURN2( updateObject( rObject.m_nObject ) );
+
+ sal_Int32 nLength = rObject.m_pStream->TellEnd();
+ rObject.m_pStream->Seek( STREAM_SEEK_TO_BEGIN );
+
+ sal_Int32 nMaskObject = 0;
+ if( !!rObject.m_aMask )
+ {
+ if( rObject.m_aMask.GetBitCount() == 1 ||
+ ( rObject.m_aMask.GetBitCount() == 8 && m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 && !m_bIsPDF_A1 )
+ )
+ {
+ nMaskObject = createObject();
+ }
+ else if( m_bIsPDF_A1 )
+ m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
+ else if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
+ m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDF13 );
+
+ }
+ if (g_bDebugDisableCompression)
+ {
+ emitComment( "PDFWriterImpl::writeJPG" );
+ }
+
+ OStringBuffer aLine(200);
+ aLine.append( rObject.m_nObject );
+ aLine.append( " 0 obj\n"
+ "<</Type/XObject/Subtype/Image/Width " );
+ aLine.append( static_cast<sal_Int32>(rObject.m_aID.m_aPixelSize.Width()) );
+ aLine.append( " /Height " );
+ aLine.append( static_cast<sal_Int32>(rObject.m_aID.m_aPixelSize.Height()) );
+ aLine.append( " /BitsPerComponent 8 " );
+ if( rObject.m_bTrueColor )
+ aLine.append( "/ColorSpace/DeviceRGB" );
+ else
+ aLine.append( "/ColorSpace/DeviceGray" );
+ aLine.append( "/Filter/DCTDecode/Length " );
+ aLine.append( nLength );
+ if( nMaskObject )
+ {
+ aLine.append( rObject.m_aMask.GetBitCount() == 1 ? " /Mask " : " /SMask " );
+ aLine.append( nMaskObject );
+ aLine.append( " 0 R " );
+ }
+ aLine.append( ">>\nstream\n" );
+ CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+
+ checkAndEnableStreamEncryption( rObject.m_nObject );
+ CHECK_RETURN2( writeBuffer( rObject.m_pStream->GetData(), nLength ) );
+ disableStreamEncryption();
+
+ aLine.setLength( 0 );
+ aLine.append( "\nendstream\nendobj\n\n" );
+ CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+
+ if( nMaskObject )
+ {
+ BitmapEmit aEmit;
+ aEmit.m_nObject = nMaskObject;
+ if( rObject.m_aMask.GetBitCount() == 1 )
+ aEmit.m_aBitmap = BitmapEx( rObject.m_aMask, rObject.m_aMask );
+ else if( rObject.m_aMask.GetBitCount() == 8 )
+ aEmit.m_aBitmap = BitmapEx( rObject.m_aMask, AlphaMask( rObject.m_aMask ) );
+ writeBitmapObject( aEmit, true );
+ }
+
+ writeReferenceXObject(rObject.m_aReferenceXObject);
+}
+
+sal_Int32 PDFWriterImpl::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 = createObject();
+ // Remember what is the ID of this object in our output.
+ rCopiedResources[rObject.GetObjectValue()] = nObject;
+ SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::copyExternalResource: " << rObject.GetObjectValue() << " -> " << nObject);
+
+ OStringBuffer aLine;
+ aLine.append(nObject);
+ aLine.append(" 0 obj\n");
+ if (rObject.GetDictionary())
+ {
+ aLine.append("<<");
+
+ // Complex case: can't copy the dictionary byte array as is, as it may contain references.
+ bool bDone = false;
+ sal_uInt64 nCopyStart = 0;
+ for (auto pReference : rObject.GetDictionaryReferences())
+ {
+ if (pReference)
+ {
+ filter::PDFObjectElement* pReferenced = pReference->LookupObject();
+ if (pReferenced)
+ {
+ // Copy the referenced object.
+ sal_Int32 nRef = copyExternalResource(rDocBuffer, *pReferenced, rCopiedResources);
+
+ sal_uInt64 nReferenceStart = pReference->GetObjectElement().GetLocation();
+ sal_uInt64 nReferenceEnd = pReference->GetOffset();
+ sal_uInt64 nOffset = 0;
+ if (nCopyStart == 0)
+ // Dict start -> reference start.
+ nOffset = rObject.GetDictionaryOffset();
+ else
+ // Previous reference end -> reference start.
+ nOffset = nCopyStart;
+ aLine.append(static_cast<const char*>(rDocBuffer.GetData()) + nOffset, nReferenceStart - nOffset);
+ // Write the updated reference.
+ aLine.append(" ");
+ aLine.append(nRef);
+ aLine.append(" 0 R");
+ // Start copying here next time.
+ nCopyStart = nReferenceEnd;
+
+ bDone = true;
+ }
+ }
+ }
+
+ if (bDone)
+ {
+ // Copy the last part here, in the complex case.
+ sal_uInt64 nDictEnd = rObject.GetDictionaryOffset() + rObject.GetDictionaryLength();
+ const sal_Int32 nLen = nDictEnd - nCopyStart;
+ if (nLen < 0)
+ SAL_WARN("vcl.pdfwriter", "copyExternalResource() failed");
+ else
+ aLine.append(static_cast<const char*>(rDocBuffer.GetData()) + nCopyStart, nLen);
+ }
+ else
+ // Can copy it as-is.
+ aLine.append(static_cast<const char*>(rDocBuffer.GetData()) + rObject.GetDictionaryOffset(), rObject.GetDictionaryLength());
+
+ aLine.append(">>\n");
+ }
+
+ if (filter::PDFStreamElement* pStream = rObject.GetStream())
+ {
+ aLine.append("stream\n");
+ SvMemoryStream& rStream = pStream->GetMemory();
+ aLine.append(static_cast<const char*>(rStream.GetData()), rStream.GetSize());
+ aLine.append("\nendstream\n");
+ }
+
+ if (filter::PDFArrayElement* pArray = rObject.GetArray())
+ {
+ aLine.append("[");
+
+ const std::vector<filter::PDFElement*>& rElements = pArray->GetElements();
+ bool bDone = false;
+ // Complex case: can't copy the array byte array as is, as it may contain references.
+ sal_uInt64 nCopyStart = 0;
+ for (const auto pElement : rElements)
+ {
+ auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement);
+ if (pReference)
+ {
+ filter::PDFObjectElement* pReferenced = pReference->LookupObject();
+ if (pReferenced)
+ {
+ // Copy the referenced object.
+ sal_Int32 nRef = copyExternalResource(rDocBuffer, *pReferenced, rCopiedResources);
+
+ sal_uInt64 nReferenceStart = pReference->GetObjectElement().GetLocation();
+ sal_uInt64 nReferenceEnd = pReference->GetOffset();
+ sal_uInt64 nOffset = 0;
+ if (nCopyStart == 0)
+ // Array start -> reference start.
+ nOffset = rObject.GetArrayOffset();
+ else
+ // Previous reference end -> reference start.
+ nOffset = nCopyStart;
+ aLine.append(static_cast<const char*>(rDocBuffer.GetData()) + nOffset, nReferenceStart - nOffset);
+
+ // Write the updated reference.
+ aLine.append(" ");
+ aLine.append(nRef);
+ aLine.append(" 0 R");
+ // Start copying here next time.
+ nCopyStart = nReferenceEnd;
+
+ bDone = true;
+ }
+ }
+ }
+
+ if (bDone)
+ {
+ // Copy the last part here, in the complex case.
+ sal_uInt64 nArrEnd = rObject.GetArrayOffset() + rObject.GetArrayLength();
+ const sal_Int32 nLen = nArrEnd - nCopyStart;
+ if (nLen < 0)
+ SAL_WARN("vcl.pdfwriter", "copyExternalResource() failed");
+ else
+ aLine.append(static_cast<const char*>(rDocBuffer.GetData()) + nCopyStart, nLen);
+ }
+ else
+ // Can copy it as-is.
+ aLine.append(static_cast<const char*>(rDocBuffer.GetData()) + rObject.GetArrayOffset(), rObject.GetArrayLength());
+
+ aLine.append("]\n");
+ }
+
+ // If the object has a number element outside a dictionary or array, copy that.
+ if (filter::PDFNumberElement* pNumber = rObject.GetNumberElement())
+ {
+ aLine.append(static_cast<const char*>(rDocBuffer.GetData()) + pNumber->GetLocation(), pNumber->GetLength());
+ aLine.append("\n");
+ }
+
+
+ aLine.append("endobj\n\n");
+
+ // We have the whole object, now write it to the output.
+ if (!updateObject(nObject))
+ return -1;
+ if (!writeBuffer(aLine.getStr(), aLine.getLength()))
+ return -1;
+
+ return nObject;
+}
+
+OString PDFWriterImpl::copyExternalResources(filter::PDFObjectElement& rPage, const OString& rKind, std::map<sal_Int32, sal_Int32>& rCopiedResources)
+{
+ // A name - object ID map, IDs as they appear in our output, not the
+ // original ones.
+ std::map<OString, sal_Int32> aRet;
+
+ // Get the rKind subset of the resource dictionary.
+ std::map<OString, filter::PDFElement*> aItems;
+ if (auto pResources = dynamic_cast<filter::PDFDictionaryElement*>(rPage.Lookup("Resources")))
+ {
+ // Resources is a direct dictionary.
+ filter::PDFElement* pLookup = pResources->LookupElement(rKind);
+ if (auto pDictionary = dynamic_cast<filter::PDFDictionaryElement*>(pLookup))
+ {
+ // rKind is an inline dictionary.
+ aItems = pDictionary->GetItems();
+ }
+ else if (auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pLookup))
+ {
+ // rKind refers to a dictionary.
+ filter::PDFObjectElement* pReferenced = pReference->LookupObject();
+ if (!pReferenced)
+ {
+ return OString();
+ }
+
+ aItems = pReferenced->GetDictionaryItems();
+ }
+ }
+ else if (filter::PDFObjectElement* pPageResources = rPage.LookupObject("Resources"))
+ {
+ // Resources is an indirect object.
+ filter::PDFElement* pValue = pPageResources->Lookup(rKind);
+ if (auto pDictionary = dynamic_cast<filter::PDFDictionaryElement*>(pValue))
+ // Kind is a direct dictionary.
+ aItems = pDictionary->GetItems();
+ else if (filter::PDFObjectElement* pObject = pPageResources->LookupObject(rKind))
+ // Kind is an indirect object.
+ aItems = pObject->GetDictionaryItems();
+ }
+ if (aItems.empty())
+ return OString();
+
+ SvMemoryStream& rDocBuffer = rPage.GetDocument().GetEditBuffer();
+
+ for (const auto& rItem : aItems)
+ {
+ // For each item copy it over to our output then insert it into aRet.
+ auto pReference = dynamic_cast<filter::PDFReferenceElement*>(rItem.second);
+ if (!pReference)
+ continue;
+
+ filter::PDFObjectElement* pValue = pReference->LookupObject();
+ if (!pValue)
+ continue;
+
+ // Then copying over an object copy its dictionary and its stream.
+ sal_Int32 nObject = copyExternalResource(rDocBuffer, *pValue, rCopiedResources);
+ aRet[rItem.first] = nObject;
+ }
+
+ // Build the dictionary entry string.
+ OStringBuffer sRet("/" + rKind + "<<");
+ for (const auto& rPair : aRet)
+ {
+ sRet.append("/").append(rPair.first).append(" ").append(OString::number(rPair.second)).append(" 0 R");
+ }
+ sRet.append(">>");
+
+ return sRet.makeStringAndClear();
+}
+
+void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit)
+{
+ if (rEmit.m_nFormObject <= 0)
+ return;
+
+ // Count /Matrix and /BBox.
+ // vcl::ImportPDF() works with 96 DPI so use the same values here, too.
+ sal_Int32 nOldDPIX = GetDPIX();
+ SetDPIX(96);
+ sal_Int32 nOldDPIY = GetDPIY();
+ SetDPIY(96);
+ Size aSize = PixelToLogic(rEmit.m_aPixelSize, MapMode(m_aMapMode.GetMapUnit()));
+ SetDPIX(nOldDPIX);
+ SetDPIY(nOldDPIY);
+ double fScaleX = 1.0 / aSize.Width();
+ double fScaleY = 1.0 / aSize.Height();
+
+ sal_Int32 nWrappedFormObject = 0;
+ if (!m_aContext.UseReferenceXObject)
+ {
+ // Parse the PDF data, we need that to write the PDF dictionary of our
+ // object.
+ SvMemoryStream aPDFStream;
+ aPDFStream.WriteBytes(rEmit.m_aPDFData.data(), rEmit.m_aPDFData.size());
+ aPDFStream.Seek(0);
+ filter::PDFDocument aPDFDocument;
+ if (!aPDFDocument.Read(aPDFStream))
+ {
+ SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: reading the PDF document failed");
+ return;
+ }
+ std::vector<filter::PDFObjectElement*> aPages = aPDFDocument.GetPages();
+ if (aPages.empty())
+ {
+ SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no pages");
+ return;
+ }
+
+ size_t nPageIndex = rEmit.m_nPDFPageIndex >= 0 ? rEmit.m_nPDFPageIndex : 0;
+
+ filter::PDFObjectElement* pPage = aPages[nPageIndex];
+ if (!pPage)
+ {
+ SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no page");
+ return;
+ }
+
+ std::vector<filter::PDFObjectElement*> aContentStreams;
+ if (filter::PDFObjectElement* pContentStream = pPage->LookupObject("Contents"))
+ aContentStreams.push_back(pContentStream);
+ else if (auto pArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("Contents")))
+ {
+ for (const auto pElement : pArray->GetElements())
+ {
+ auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement);
+ if (!pReference)
+ continue;
+
+ filter::PDFObjectElement* pObject = pReference->LookupObject();
+ if (!pObject)
+ continue;
+
+ aContentStreams.push_back(pObject);
+ }
+ }
+
+ if (aContentStreams.empty())
+ {
+ SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no content stream");
+ return;
+ }
+
+ // Maps from source object id (PDF image) to target object id (export result).
+ std::map<sal_Int32, sal_Int32> aCopiedResources;
+
+ 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");
+
+ long nWidth = aSize.Width();
+ long nHeight = aSize.Height();
+ if (auto pRotate = dynamic_cast<filter::PDFNumberElement*>(pPage->Lookup("Rotate")))
+ {
+ // The original page was rotated, then construct a transformation matrix which does the
+ // same with our form object.
+ if (rtl::math::approxEqual(pRotate->GetValue(), 90))
+ {
+ std::swap(nWidth, nHeight);
+ basegfx::B2DHomMatrix aMat;
+ aMat.rotate(basegfx::deg2rad(pRotate->GetValue()));
+ // Rotate around the origo (bottom left corner) counter-clockwise, then translate
+ // horizontally to effectively keep the bottom left corner unchanged.
+ aLine.append(" /Matrix [ ");
+ aLine.append(aMat.get(0, 0));
+ aLine.append(" ");
+ aLine.append(aMat.get(0, 1));
+ aLine.append(" ");
+ aLine.append(aMat.get(1, 0));
+ aLine.append(" ");
+ aLine.append(aMat.get(1, 1));
+ aLine.append(" 0 ");
+ aLine.append(nWidth);
+ aLine.append(" ] ");
+ }
+ }
+
+ aLine.append(" /Resources <<");
+ static const std::initializer_list<OString> aKeys =
+ {
+ "ColorSpace",
+ "ExtGState",
+ "Font",
+ "XObject",
+ "Shading"
+ };
+ for (const auto& rKey : aKeys)
+ aLine.append(copyExternalResources(*pPage, rKey, aCopiedResources));
+ aLine.append(">>");
+ aLine.append(" /BBox [ 0 0 ");
+ aLine.append(nWidth);
+ aLine.append(" ");
+ aLine.append(nHeight);
+ aLine.append(" ]");
+
+ if (!g_bDebugDisableCompression)
+ aLine.append(" /Filter/FlateDecode");
+ aLine.append(" /Length ");
+
+ SvMemoryStream aStream;
+ for (auto pContent : aContentStreams)
+ {
+ filter::PDFStreamElement* pPageStream = pContent->GetStream();
+ if (!pPageStream)
+ {
+ SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: contents has no stream");
+ continue;
+ }
+
+ SvMemoryStream& rPageStream = pPageStream->GetMemory();
+
+ auto pFilter = dynamic_cast<filter::PDFNameElement*>(pContent->Lookup("Filter"));
+ if (pFilter)
+ {
+ if (pFilter->GetValue() != "FlateDecode")
+ continue;
+
+ SvMemoryStream aMemoryStream;
+ ZCodec aZCodec;
+ rPageStream.Seek(0);
+ aZCodec.BeginCompression();
+ aZCodec.Decompress(rPageStream, aMemoryStream);
+ if (!aZCodec.EndCompression())
+ {
+ SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: decompression failed");
+ continue;
+ }
+
+ aStream.WriteBytes(aMemoryStream.GetData(), aMemoryStream.GetSize());
+ }
+ else
+ aStream.WriteBytes(rPageStream.GetData(), rPageStream.GetSize());
+ }
+
+ compressStream(&aStream);
+ sal_Int32 nLength = aStream.Tell();
+ aLine.append(nLength);
+
+ aLine.append(">>\nstream\n");
+ // Copy the original page streams to the form XObject stream.
+ aLine.append(static_cast<const char*>(aStream.GetData()), aStream.GetSize());
+ aLine.append("\nendstream\nendobj\n\n");
+ if (!updateObject(nWrappedFormObject))
+ return;
+ if (!writeBuffer(aLine.getStr(), aLine.getLength()))
+ return;
+ }
+
+ OStringBuffer aLine;
+ if (!updateObject(rEmit.m_nFormObject))
+ return;
+
+ // Now have all the info to write the form XObject.
+ aLine.append(rEmit.m_nFormObject);
+ aLine.append(" 0 obj\n");
+ aLine.append("<< /Type /XObject");
+ aLine.append(" /Subtype /Form");
+ aLine.append(" /Resources << /XObject<<");
+
+ sal_Int32 nObject = m_aContext.UseReferenceXObject ? rEmit.m_nBitmapObject : nWrappedFormObject;
+ aLine.append(" /Im");
+ aLine.append(nObject);
+ aLine.append(" ");
+ aLine.append(nObject);
+ aLine.append(" 0 R");
+
+ aLine.append(">> >>");
+ aLine.append(" /Matrix [ ");
+ appendDouble(fScaleX, aLine);
+ aLine.append(" 0 0 ");
+ appendDouble(fScaleY, aLine);
+ aLine.append(" 0 0 ]");
+ aLine.append(" /BBox [ 0 0 ");
+ aLine.append(aSize.Width());
+ aLine.append(" ");
+ aLine.append(aSize.Height());
+ aLine.append(" ]\n");
+
+ if (m_aContext.UseReferenceXObject && rEmit.m_nEmbeddedObject > 0)
+ {
+ // Write the reference dictionary.
+ aLine.append("/Ref<< /F << /Type /Filespec /F (<embedded file>) /EF << /F ");
+ aLine.append(rEmit.m_nEmbeddedObject);
+ aLine.append(" 0 R >> >> /Page 0 >>\n");
+ }
+
+ aLine.append("/Length ");
+
+ OStringBuffer aStream;
+ aStream.append("q ");
+ if (m_aContext.UseReferenceXObject)
+ {
+ // Reference XObject markup is used, just refer to the fallback bitmap
+ // here.
+ aStream.append(aSize.Width());
+ aStream.append(" 0 0 ");
+ aStream.append(aSize.Height());
+ aStream.append(" 0 0 cm\n");
+ aStream.append("/Im");
+ aStream.append(rEmit.m_nBitmapObject);
+ aStream.append(" Do\n");
+ }
+ else
+ {
+ // Reset line width to the default.
+ aStream.append(" 1 w\n");
+
+ // vcl::RenderPDFBitmaps() effectively renders a white background for transparent input, be
+ // consistent with that.
+ aStream.append("1 1 1 rg\n");
+ aStream.append("0 0 ");
+ aStream.append(aSize.Width());
+ aStream.append(" ");
+ aStream.append(aSize.Height());
+ aStream.append(" re\n");
+ aStream.append("f*\n");
+
+ // No reference XObject, draw the form XObject containing the original
+ // page streams.
+ aStream.append("/Im");
+ aStream.append(nWrappedFormObject);
+ aStream.append(" Do\n");
+ }
+ aStream.append("Q");
+ aLine.append(aStream.getLength());
+
+ aLine.append(">>\nstream\n");
+ aLine.append(aStream.getStr());
+ aLine.append("\nendstream\nendobj\n\n");
+ CHECK_RETURN2(writeBuffer(aLine.getStr(), aLine.getLength()));
+}
+
+namespace
+{
+ unsigned char reverseByte(unsigned char b)
+ {
+ b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
+ b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
+ b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
+ return b;
+ }
+
+ //tdf#103051 convert any N1BitLsbPal to N1BitMsbPal
+ Bitmap getExportBitmap(const Bitmap &rBitmap)
+ {
+ Bitmap::ScopedReadAccess pAccess(const_cast<Bitmap&>(rBitmap));
+ const ScanlineFormat eFormat = pAccess->GetScanlineFormat();
+ if (eFormat != ScanlineFormat::N1BitLsbPal)
+ return rBitmap;
+ Bitmap aNewBmp(rBitmap);
+ BitmapScopedWriteAccess xWriteAcc(aNewBmp);
+ const int nScanLineBytes = (pAccess->Width() + 7U) / 8U;
+ for (long nY = 0L; nY < xWriteAcc->Height(); ++nY)
+ {
+ Scanline pBitSwap = xWriteAcc->GetScanline(nY);
+ for (int x = 0; x < nScanLineBytes; ++x)
+ pBitSwap[x] = reverseByte(pBitSwap[x]);
+ }
+ return aNewBmp;
+ }
+}
+
+bool PDFWriterImpl::writeBitmapObject( BitmapEmit& rObject, bool bMask )
+{
+ if (!rObject.m_aReferenceXObject.m_aPDFData.empty() && !m_aContext.UseReferenceXObject)
+ {
+ writeReferenceXObject(rObject.m_aReferenceXObject);
+ return true;
+ }
+
+ CHECK_RETURN( updateObject( rObject.m_nObject ) );
+
+ Bitmap aBitmap;
+ Color aTransparentColor( COL_TRANSPARENT );
+ bool bWriteMask = false;
+ if( ! bMask )
+ {
+ aBitmap = getExportBitmap(rObject.m_aBitmap.GetBitmap());
+ if( rObject.m_aBitmap.IsAlpha() )
+ {
+ if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
+ bWriteMask = true;
+ // else draw without alpha channel
+ }
+ else
+ {
+ switch( rObject.m_aBitmap.GetTransparentType() )
+ {
+ case TransparentType::NONE:
+ break;
+ case TransparentType::Color:
+ aTransparentColor = rObject.m_aBitmap.GetTransparentColor();
+ break;
+ case TransparentType::Bitmap:
+ bWriteMask = true;
+ break;
+ }
+ }
+ }
+ else
+ {
+ if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 || ! rObject.m_aBitmap.IsAlpha() )
+ {
+ aBitmap = getExportBitmap(rObject.m_aBitmap.GetMask());
+ aBitmap.Convert( BmpConversion::N1BitThreshold );
+ SAL_WARN_IF( aBitmap.GetBitCount() != 1, "vcl.pdfwriter", "mask conversion failed" );
+ }
+ else if( aBitmap.GetBitCount() != 8 )
+ {
+ aBitmap = getExportBitmap(rObject.m_aBitmap.GetAlpha().GetBitmap());
+ aBitmap.Convert( BmpConversion::N8BitGreys );
+ SAL_WARN_IF( aBitmap.GetBitCount() != 8, "vcl.pdfwriter", "alpha mask conversion failed" );
+ }
+ }
+
+ Bitmap::ScopedReadAccess pAccess(aBitmap);
+
+ bool bTrueColor;
+ sal_Int32 nBitsPerComponent;
+ switch( aBitmap.GetBitCount() )
+ {
+ case 1:
+ case 2:
+ case 4:
+ case 8:
+ bTrueColor = false;
+ nBitsPerComponent = aBitmap.GetBitCount();
+ break;
+ default:
+ bTrueColor = true;
+ nBitsPerComponent = 8;
+ break;
+ }
+
+ 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.GetBitCount() == 1 )
+ {
+ // #i47395# 1 bit bitmaps occasionally have an inverted grey palette
+ sal_uInt16 nBlackIndex = pAccess->GetBestPaletteIndex( BitmapColor( COL_BLACK ) );
+ assert( nBlackIndex == 0 || nBlackIndex == 1);
+ sal_uInt16 nWhiteIndex = pAccess->GetBestPaletteIndex( BitmapColor( COL_WHITE ) );
+ if( pAccess->GetPalette()[nBlackIndex] == BitmapColor( COL_BLACK ) &&
+ pAccess->GetPalette()[nWhiteIndex] == BitmapColor( COL_WHITE ) )
+ {
+ // It is black and white
+ if( nBlackIndex == 1 )
+ aLine.append( "/Decode[1 0]\n" );
+ }
+ else
+ {
+ // It is two levels of grey
+ aLine.append( "/Decode[" );
+ assert( pAccess->GetPalette()[0].GetRed() == pAccess->GetPalette()[0].GetGreen() &&
+ pAccess->GetPalette()[0].GetRed() == pAccess->GetPalette()[0].GetBlue() &&
+ pAccess->GetPalette()[1].GetRed() == pAccess->GetPalette()[1].GetGreen() &&
+ pAccess->GetPalette()[1].GetRed() == pAccess->GetPalette()[1].GetBlue() );
+ aLine.append( pAccess->GetPalette()[0].GetRed() / 255.0 );
+ aLine.append( " " );
+ aLine.append( pAccess->GetPalette()[1].GetRed() / 255.0 );
+ aLine.append( "]\n" );
+ }
+ }
+ }
+ else
+ {
+ aLine.append( "[ /Indexed/DeviceRGB " );
+ aLine.append( static_cast<sal_Int32>(pAccess->GetPaletteEntryCount()-1) );
+ aLine.append( "\n<" );
+ if( m_aContext.Encryption.Encrypt() )
+ {
+ enableStringEncryption( rObject.m_nObject );
+ //check encryption buffer size
+ m_vEncryptionBuffer.resize(pAccess->GetPaletteEntryCount()*3);
+ int nChar = 0;
+ //fill the encryption buffer
+ for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
+ {
+ const BitmapColor& rColor = pAccess->GetPaletteColor( i );
+ m_vEncryptionBuffer[nChar++] = rColor.GetRed();
+ m_vEncryptionBuffer[nChar++] = rColor.GetGreen();
+ m_vEncryptionBuffer[nChar++] = rColor.GetBlue();
+ }
+ //encrypt the colorspace lookup table
+ rtl_cipher_encodeARCFOUR( m_aCipher, m_vEncryptionBuffer.data(), nChar, m_vEncryptionBuffer.data(), nChar );
+ //now queue the data for output
+ nChar = 0;
+ for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
+ {
+ appendHex(m_vEncryptionBuffer[nChar++], aLine );
+ appendHex(m_vEncryptionBuffer[nChar++], aLine );
+ appendHex(m_vEncryptionBuffer[nChar++], aLine );
+ }
+ }
+ else //no encryption requested (PDF/A-1a program flow drops here)
+ {
+ for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
+ {
+ const BitmapColor& rColor = pAccess->GetPaletteColor( i );
+ appendHex( rColor.GetRed(), aLine );
+ appendHex( rColor.GetGreen(), aLine );
+ appendHex( rColor.GetBlue(), aLine );
+ }
+ }
+ aLine.append( ">\n]\n" );
+ }
+ }
+ else
+ {
+ if( aBitmap.GetBitCount() == 1 )
+ {
+ aLine.append( "/ImageMask true\n" );
+ sal_Int32 nBlackIndex = pAccess->GetBestPaletteIndex( BitmapColor( COL_BLACK ) );
+ SAL_WARN_IF( nBlackIndex != 0 && nBlackIndex != 1, "vcl.pdfwriter", "wrong black index" );
+ if( nBlackIndex )
+ aLine.append( "/Decode[ 1 0 ]\n" );
+ else
+ aLine.append( "/Decode[ 0 1 ]\n" );
+ }
+ else if( aBitmap.GetBitCount() == 8 )
+ {
+ aLine.append( "/ColorSpace/DeviceGray\n"
+ "/Decode [ 1 0 ]\n" );
+ }
+ }
+
+ if( ! bMask && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_2 && !m_bIsPDF_A1 )
+ {
+ if( bWriteMask )
+ {
+ nMaskObject = createObject();
+ if( rObject.m_aBitmap.IsAlpha() && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
+ aLine.append( "/SMask " );
+ else
+ aLine.append( "/Mask " );
+ aLine.append( nMaskObject );
+ aLine.append( " 0 R\n" );
+ }
+ else if( aTransparentColor != COL_TRANSPARENT )
+ {
+ aLine.append( "/Mask[ " );
+ if( bTrueColor )
+ {
+ aLine.append( static_cast<sal_Int32>(aTransparentColor.GetRed()) );
+ aLine.append( ' ' );
+ aLine.append( static_cast<sal_Int32>(aTransparentColor.GetRed()) );
+ aLine.append( ' ' );
+ aLine.append( static_cast<sal_Int32>(aTransparentColor.GetGreen()) );
+ aLine.append( ' ' );
+ aLine.append( static_cast<sal_Int32>(aTransparentColor.GetGreen()) );
+ aLine.append( ' ' );
+ aLine.append( static_cast<sal_Int32>(aTransparentColor.GetBlue()) );
+ aLine.append( ' ' );
+ aLine.append( static_cast<sal_Int32>(aTransparentColor.GetBlue()) );
+ }
+ else
+ {
+ sal_Int32 nIndex = pAccess->GetBestPaletteIndex( BitmapColor( aTransparentColor ) );
+ aLine.append( nIndex );
+ }
+ aLine.append( " ]\n" );
+ }
+ }
+ else if( m_bIsPDF_A1 && (bWriteMask || aTransparentColor != COL_TRANSPARENT) )
+ m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
+
+ aLine.append( ">>\n"
+ "stream\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+ sal_uInt64 nStartPos = 0;
+ CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nStartPos)) );
+
+ checkAndEnableStreamEncryption( rObject.m_nObject );
+ if (!g_bDebugDisableCompression && nBitsPerComponent == 1)
+ {
+ writeG4Stream(pAccess.get());
+ }
+ else
+ {
+ beginCompression();
+ if( ! bTrueColor || pAccess->GetScanlineFormat() == ScanlineFormat::N24BitTcRgb )
+ {
+ //With PDF bitmaps, each row is padded to a BYTE boundary (multiple of 8 bits).
+ const int nScanLineBytes = ((pAccess->GetBitCount() * pAccess->Width()) + 7U) / 8U;
+
+ for( long i = 0; i < pAccess->Height(); i++ )
+ {
+ CHECK_RETURN( writeBuffer( pAccess->GetScanline( i ), nScanLineBytes ) );
+ }
+ }
+ else
+ {
+ const int nScanLineBytes = pAccess->Width()*3;
+ std::unique_ptr<sal_uInt8[]> xCol(new sal_uInt8[nScanLineBytes]);
+ for( long y = 0; y < pAccess->Height(); y++ )
+ {
+ for( long x = 0; x < pAccess->Width(); x++ )
+ {
+ BitmapColor aColor = pAccess->GetColor( y, x );
+ xCol[3*x+0] = aColor.GetRed();
+ xCol[3*x+1] = aColor.GetGreen();
+ xCol[3*x+2] = aColor.GetBlue();
+ }
+ CHECK_RETURN(writeBuffer(xCol.get(), nScanLineBytes));
+ }
+ }
+ endCompression();
+ }
+ disableStreamEncryption();
+
+ sal_uInt64 nEndPos = 0;
+ CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nEndPos)) );
+ aLine.setLength( 0 );
+ aLine.append( "\nendstream\nendobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+ CHECK_RETURN( updateObject( nStreamLengthObject ) );
+ aLine.setLength( 0 );
+ aLine.append( nStreamLengthObject );
+ aLine.append( " 0 obj\n" );
+ aLine.append( static_cast<sal_Int64>(nEndPos-nStartPos) );
+ aLine.append( "\nendobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+
+ if( nMaskObject )
+ {
+ BitmapEmit aEmit;
+ aEmit.m_nObject = nMaskObject;
+ aEmit.m_aBitmap = rObject.m_aBitmap;
+ return writeBitmapObject( aEmit, true );
+ }
+
+ writeReferenceXObject(rObject.m_aReferenceXObject);
+
+ return true;
+}
+
+void PDFWriterImpl::createEmbeddedFile(const Graphic& rGraphic, ReferenceXObjectEmit& rEmit, sal_Int32 nBitmapObject)
+{
+ // The bitmap object is always a valid identifier, even if the graphic has
+ // no pdf data.
+ rEmit.m_nBitmapObject = nBitmapObject;
+
+ if (!rGraphic.getVectorGraphicData() || rGraphic.getVectorGraphicData()->getVectorGraphicDataType() != VectorGraphicDataType::Pdf)
+ return;
+
+ sal_uInt32 nLength = rGraphic.getVectorGraphicData()->getVectorGraphicDataArrayLength();
+ auto const & rArray = rGraphic.getVectorGraphicData()->getVectorGraphicDataArray();
+
+ auto pPDFData = std::make_shared<std::vector<sal_Int8>>(rArray.getConstArray(), rArray.getConstArray() + nLength);
+
+ if (m_aContext.UseReferenceXObject)
+ {
+ // Store the original PDF data as an embedded file.
+ m_aEmbeddedFiles.emplace_back();
+ m_aEmbeddedFiles.back().m_nObject = createObject();
+ m_aEmbeddedFiles.back().m_pData = pPDFData;
+ rEmit.m_nEmbeddedObject = m_aEmbeddedFiles.back().m_nObject;
+ }
+ else
+ {
+ rEmit.m_nPDFPageIndex = rGraphic.getVectorGraphicData()->getPageIndex();
+ rEmit.m_aPDFData = *pPDFData;
+ }
+
+ rEmit.m_nFormObject = createObject();
+ rEmit.m_aPixelSize = rGraphic.GetPrefSize();
+}
+
+void PDFWriterImpl::drawJPGBitmap( SvStream& rDCTData, bool bIsTrueColor, const Size& rSizePixel, const tools::Rectangle& rTargetArea, const Bitmap& rMask, 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( !!rMask && rMask.GetSizePixel() == aGraphic.GetSizePixel() )
+ {
+ Bitmap aBmp( aGraphic.GetBitmapEx().GetBitmap() );
+ BitmapEx aBmpEx( aBmp, rMask );
+ drawBitmap( rTargetArea.TopLeft(), rTargetArea.GetSize(), aBmpEx );
+ }
+ else
+ drawBitmap( rTargetArea.TopLeft(), rTargetArea.GetSize(), aGraphic.GetBitmapEx() );
+ return;
+ }
+
+ std::unique_ptr<SvMemoryStream> pStream(new SvMemoryStream);
+ pStream->WriteStream( rDCTData );
+ pStream->Seek( STREAM_SEEK_TO_END );
+
+ BitmapID aID;
+ aID.m_aPixelSize = rSizePixel;
+ aID.m_nSize = pStream->Tell();
+ pStream->Seek( STREAM_SEEK_TO_BEGIN );
+ aID.m_nChecksum = vcl_get_checksum( 0, pStream->GetData(), aID.m_nSize );
+ if( ! rMask.IsEmpty() )
+ aID.m_nMaskChecksum = rMask.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()->getVectorGraphicDataType() != VectorGraphicDataType::Pdf || m_aContext.UseReferenceXObject)
+ rEmit.m_nObject = createObject();
+ rEmit.m_aID = aID;
+ rEmit.m_pStream = std::move( pStream );
+ rEmit.m_bTrueColor = bIsTrueColor;
+ if( !! rMask && rMask.GetSizePixel() == rSizePixel )
+ rEmit.m_aMask = rMask;
+ createEmbeddedFile(rGraphic, rEmit.m_aReferenceXObject, rEmit.m_nObject);
+
+ it = m_aJPGs.begin();
+ }
+
+ aLine.append( "q " );
+ sal_Int32 nCheckWidth = 0;
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rTargetArea.GetWidth()), aLine, false, &nCheckWidth );
+ aLine.append( " 0 0 " );
+ sal_Int32 nCheckHeight = 0;
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rTargetArea.GetHeight()), aLine, true, &nCheckHeight );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( rTargetArea.BottomLeft(), aLine );
+ aLine.append( " cm\n/Im" );
+ sal_Int32 nObject = it->m_aReferenceXObject.getObject();
+ aLine.append(nObject);
+ aLine.append( " Do Q\n" );
+ if( nCheckWidth == 0 || nCheckHeight == 0 )
+ {
+ // #i97512# avoid invalid current matrix
+ aLine.setLength( 0 );
+ aLine.append( "\n%jpeg image /Im" );
+ aLine.append( it->m_nObject );
+ aLine.append( " scaled to zero size, omitted\n" );
+ }
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+
+ OString aObjName = "Im" + OString::number(nObject);
+ pushResource( ResourceKind::XObject, aObjName, nObject );
+
+}
+
+void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const BitmapEmit& rBitmap, const Color& rFillColor )
+{
+ OStringBuffer aLine( 80 );
+ updateGraphicsState();
+
+ aLine.append( "q " );
+ if( rFillColor != COL_TRANSPARENT )
+ {
+ appendNonStrokingColor( rFillColor, aLine );
+ aLine.append( ' ' );
+ }
+ sal_Int32 nCheckWidth = 0;
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rDestSize.Width()), aLine, false, &nCheckWidth );
+ aLine.append( " 0 0 " );
+ sal_Int32 nCheckHeight = 0;
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rDestSize.Height()), aLine, true, &nCheckHeight );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( rDestPoint + Point( 0, rDestSize.Height()-1 ), aLine );
+ aLine.append( " cm\n/Im" );
+ sal_Int32 nObject = rBitmap.m_aReferenceXObject.getObject();
+ aLine.append(nObject);
+ aLine.append( " Do Q\n" );
+ if( nCheckWidth == 0 || nCheckHeight == 0 )
+ {
+ // #i97512# avoid invalid current matrix
+ aLine.setLength( 0 );
+ aLine.append( "\n%bitmap image /Im" );
+ aLine.append( rBitmap.m_nObject );
+ aLine.append( " scaled to zero size, omitted\n" );
+ }
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+const BitmapEmit& PDFWriterImpl::createBitmapEmit( const BitmapEx& i_rBitmap, const Graphic& rGraphic )
+{
+ BitmapEx aBitmap( i_rBitmap );
+ if( m_aContext.ColorMode == PDFWriter::DrawGreyscale )
+ {
+ BmpConversion eConv = BmpConversion::N8BitGreys;
+ int nDepth = aBitmap.GetBitmap().GetBitCount();
+ if( nDepth <= 4 )
+ eConv = BmpConversion::N4BitGreys;
+ if( nDepth > 1 )
+ aBitmap.Convert( eConv );
+ }
+ BitmapID aID;
+ aID.m_aPixelSize = aBitmap.GetSizePixel();
+ aID.m_nSize = aBitmap.GetBitCount();
+ aID.m_nChecksum = aBitmap.GetBitmap().GetChecksum();
+ aID.m_nMaskChecksum = 0;
+ if( aBitmap.IsAlpha() )
+ aID.m_nMaskChecksum = aBitmap.GetAlpha().GetChecksum();
+ else
+ {
+ Bitmap aMask = aBitmap.GetMask();
+ if( ! aMask.IsEmpty() )
+ aID.m_nMaskChecksum = aMask.GetChecksum();
+ }
+ std::list< BitmapEmit >::const_iterator it = std::find_if(m_aBitmaps.begin(), m_aBitmaps.end(),
+ [&](const BitmapEmit& arg) { return aID == arg.m_aID; });
+ if( it == m_aBitmaps.end() )
+ {
+ m_aBitmaps.push_front( BitmapEmit() );
+ m_aBitmaps.front().m_aID = aID;
+ m_aBitmaps.front().m_aBitmap = aBitmap;
+ if (!rGraphic.getVectorGraphicData() || rGraphic.getVectorGraphicData()->getVectorGraphicDataType() != VectorGraphicDataType::Pdf || m_aContext.UseReferenceXObject)
+ m_aBitmaps.front().m_nObject = createObject();
+ createEmbeddedFile(rGraphic, m_aBitmaps.front().m_aReferenceXObject, m_aBitmaps.front().m_nObject);
+ it = m_aBitmaps.begin();
+ }
+
+ sal_Int32 nObject = it->m_aReferenceXObject.getObject();
+ OString aObjName = "Im" + OString::number(nObject);
+ pushResource( ResourceKind::XObject, aObjName, nObject );
+
+ return *it;
+}
+
+void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const Bitmap& rBitmap, const Graphic& rGraphic )
+{
+ MARK( "drawBitmap (Bitmap)" );
+
+ // #i40055# sanity check
+ if( ! (rDestSize.Width() && rDestSize.Height()) )
+ return;
+
+ const BitmapEmit& rEmit = createBitmapEmit( BitmapEx( rBitmap ), rGraphic );
+ drawBitmap( rDestPoint, rDestSize, rEmit, COL_TRANSPARENT );
+}
+
+void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const BitmapEx& rBitmap )
+{
+ MARK( "drawBitmap (BitmapEx)" );
+
+ // #i40055# sanity check
+ if( ! (rDestSize.Width() && rDestSize.Height()) )
+ return;
+
+ const BitmapEmit& rEmit = createBitmapEmit( rBitmap, Graphic() );
+ drawBitmap( rDestPoint, rDestSize, rEmit, COL_TRANSPARENT );
+}
+
+sal_Int32 PDFWriterImpl::createGradient( const Gradient& rGradient, const Size& rSize )
+{
+ Size aPtSize( lcl_convert( m_aGraphicsStack.front().m_aMapMode,
+ MapMode( MapUnit::MapPoint ),
+ this,
+ rSize ) );
+ // check if we already have this gradient
+ // rounding to point will generally lose some pixels
+ // round up to point boundary
+ aPtSize.AdjustWidth( 1 );
+ aPtSize.AdjustHeight( 1 );
+ std::list< GradientEmit >::const_iterator it = std::find_if(m_aGradients.begin(), m_aGradients.end(),
+ [&](const GradientEmit& arg) { return ((rGradient == arg.m_aGradient) && (aPtSize == arg.m_aSize) ); });
+
+ if( it == m_aGradients.end() )
+ {
+ m_aGradients.push_front( GradientEmit() );
+ m_aGradients.front().m_aGradient = rGradient;
+ m_aGradients.front().m_nObject = createObject();
+ m_aGradients.front().m_aSize = aPtSize;
+ it = m_aGradients.begin();
+ }
+
+ OStringBuffer aObjName( 16 );
+ aObjName.append( 'P' );
+ aObjName.append( it->m_nObject );
+ pushResource( ResourceKind::Shading, aObjName.makeStringAndClear(), it->m_nObject );
+
+ return it->m_nObject;
+}
+
+void PDFWriterImpl::drawGradient( const tools::Rectangle& rRect, const Gradient& rGradient )
+{
+ MARK( "drawGradient (Rectangle)" );
+
+ if( m_aContext.Version == PDFWriter::PDFVersion::PDF_1_2 )
+ {
+ drawRectangle( rRect );
+ return;
+ }
+
+ sal_Int32 nGradient = createGradient( rGradient, rRect.GetSize() );
+
+ Point aTranslate( rRect.BottomLeft() );
+ aTranslate += Point( 0, 1 );
+
+ updateGraphicsState();
+
+ OStringBuffer aLine( 80 );
+ aLine.append( "q 1 0 0 1 " );
+ m_aPages.back().appendPoint( aTranslate, aLine );
+ aLine.append( " cm " );
+ // if a stroke is appended reset the clip region before stroke
+ if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
+ aLine.append( "q " );
+ aLine.append( "0 0 " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), aLine );
+ aLine.append( " re W n\n" );
+
+ aLine.append( "/P" );
+ aLine.append( nGradient );
+ aLine.append( " sh " );
+ if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
+ {
+ aLine.append( "Q 0 0 " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), aLine );
+ aLine.append( " re S " );
+ }
+ aLine.append( "Q\n" );
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+void PDFWriterImpl::drawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch )
+{
+ MARK( "drawHatch" );
+
+ updateGraphicsState();
+
+ if( rPolyPoly.Count() )
+ {
+ tools::PolyPolygon aPolyPoly( rPolyPoly );
+
+ aPolyPoly.Optimize( PolyOptimizeFlags::NO_SAME );
+ push( PushFlags::LINECOLOR );
+ setLineColor( rHatch.GetColor() );
+ DrawHatch( aPolyPoly, rHatch, false );
+ pop();
+ }
+}
+
+void PDFWriterImpl::drawWallpaper( const tools::Rectangle& rRect, const Wallpaper& rWall )
+{
+ MARK( "drawWallpaper" );
+
+ bool bDrawColor = false;
+ bool bDrawGradient = false;
+ bool bDrawBitmap = false;
+
+ BitmapEx aBitmap;
+ Point aBmpPos = rRect.TopLeft();
+ Size aBmpSize;
+ if( rWall.IsBitmap() )
+ {
+ aBitmap = rWall.GetBitmap();
+ aBmpSize = lcl_convert( aBitmap.GetPrefMapMode(),
+ getMapMode(),
+ this,
+ aBitmap.GetPrefSize() );
+ tools::Rectangle aRect( rRect );
+ if( rWall.IsRect() )
+ {
+ aRect = rWall.GetRect();
+ aBmpPos = aRect.TopLeft();
+ aBmpSize = aRect.GetSize();
+ }
+ if( rWall.GetStyle() != WallpaperStyle::Scale )
+ {
+ if( rWall.GetStyle() != WallpaperStyle::Tile )
+ {
+ bDrawBitmap = true;
+ if( rWall.IsGradient() )
+ bDrawGradient = true;
+ else
+ bDrawColor = true;
+ switch( rWall.GetStyle() )
+ {
+ case WallpaperStyle::TopLeft:
+ break;
+ case WallpaperStyle::Top:
+ aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 );
+ break;
+ case WallpaperStyle::Left:
+ aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 );
+ break;
+ case WallpaperStyle::TopRight:
+ aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() );
+ break;
+ case WallpaperStyle::Center:
+ aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 );
+ aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 );
+ break;
+ case WallpaperStyle::Right:
+ aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() );
+ aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 );
+ break;
+ case WallpaperStyle::BottomLeft:
+ aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() );
+ break;
+ case WallpaperStyle::Bottom:
+ aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 );
+ aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() );
+ break;
+ case WallpaperStyle::BottomRight:
+ aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() );
+ aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() );
+ break;
+ default: ;
+ }
+ }
+ else
+ {
+ // push the bitmap
+ const BitmapEmit& rEmit = createBitmapEmit( aBitmap, Graphic() );
+
+ // convert to page coordinates; this needs to be done here
+ // since the emit does not know the page anymore
+ tools::Rectangle aConvertRect( aBmpPos, aBmpSize );
+ m_aPages.back().convertRect( aConvertRect );
+
+ OString aImageName = "Im" + OString::number( rEmit.m_nObject );
+
+ // push the pattern
+ OStringBuffer aTilingStream( 32 );
+ appendFixedInt( aConvertRect.GetWidth(), aTilingStream );
+ aTilingStream.append( " 0 0 " );
+ appendFixedInt( aConvertRect.GetHeight(), aTilingStream );
+ aTilingStream.append( " 0 0 cm\n/" );
+ aTilingStream.append( aImageName );
+ aTilingStream.append( " Do\n" );
+
+ m_aTilings.emplace_back( );
+ m_aTilings.back().m_nObject = createObject();
+ m_aTilings.back().m_aRectangle = tools::Rectangle( Point( 0, 0 ), aConvertRect.GetSize() );
+ m_aTilings.back().m_pTilingStream.reset(new SvMemoryStream());
+ m_aTilings.back().m_pTilingStream->WriteBytes(
+ aTilingStream.getStr(), aTilingStream.getLength() );
+ // phase the tiling so wallpaper begins on upper left
+ if ((aConvertRect.GetWidth() == 0) || (aConvertRect.GetHeight() == 0))
+ throw o3tl::divide_by_zero();
+ m_aTilings.back().m_aTransform.matrix[2] = double(aConvertRect.Left() % aConvertRect.GetWidth()) / fDivisor;
+ m_aTilings.back().m_aTransform.matrix[5] = double(aConvertRect.Top() % aConvertRect.GetHeight()) / fDivisor;
+ m_aTilings.back().m_aResources.m_aXObjects[aImageName] = rEmit.m_nObject;
+
+ updateGraphicsState();
+
+ OStringBuffer aObjName( 16 );
+ aObjName.append( 'P' );
+ aObjName.append( m_aTilings.back().m_nObject );
+ OString aPatternName( aObjName.makeStringAndClear() );
+ pushResource( ResourceKind::Pattern, aPatternName, m_aTilings.back().m_nObject );
+
+ // fill a rRect with the pattern
+ OStringBuffer aLine( 100 );
+ aLine.append( "q /Pattern cs /" );
+ aLine.append( aPatternName );
+ aLine.append( " scn " );
+ m_aPages.back().appendRect( rRect, aLine );
+ aLine.append( " f Q\n" );
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+ }
+ }
+ else
+ {
+ aBmpPos = aRect.TopLeft();
+ aBmpSize = aRect.GetSize();
+ bDrawBitmap = true;
+ }
+
+ if( aBitmap.IsTransparent() )
+ {
+ if( rWall.IsGradient() )
+ bDrawGradient = true;
+ else
+ bDrawColor = true;
+ }
+ }
+ else if( rWall.IsGradient() )
+ bDrawGradient = true;
+ else
+ bDrawColor = true;
+
+ if( bDrawGradient )
+ {
+ drawGradient( rRect, rWall.GetGradient() );
+ }
+ if( bDrawColor )
+ {
+ Color aOldLineColor = m_aGraphicsStack.front().m_aLineColor;
+ Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor;
+ setLineColor( COL_TRANSPARENT );
+ setFillColor( rWall.GetColor() );
+ drawRectangle( rRect );
+ setLineColor( aOldLineColor );
+ setFillColor( aOldFillColor );
+ }
+ if( bDrawBitmap )
+ {
+ // set temporary clip region since aBmpPos and aBmpSize
+ // may be outside rRect
+ OStringBuffer aLine( 20 );
+ aLine.append( "q " );
+ m_aPages.back().appendRect( rRect, aLine );
+ aLine.append( " W n\n" );
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+ drawBitmap( aBmpPos, aBmpSize, aBitmap );
+ writeBuffer( "Q\n", 2 );
+ }
+}
+
+void PDFWriterImpl::updateGraphicsState(Mode const mode)
+{
+ OStringBuffer aLine( 256 );
+ GraphicsState& rNewState = m_aGraphicsStack.front();
+ // first set clip region since it might invalidate everything else
+
+ if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::ClipRegion )
+ {
+ rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::ClipRegion;
+
+ if( m_aCurrentPDFState.m_bClipRegion != rNewState.m_bClipRegion ||
+ ( rNewState.m_bClipRegion && m_aCurrentPDFState.m_aClipRegion != rNewState.m_aClipRegion ) )
+ {
+ if( m_aCurrentPDFState.m_bClipRegion )
+ {
+ aLine.append( "Q " );
+ // invalidate everything but the clip region
+ m_aCurrentPDFState = GraphicsState();
+ rNewState.m_nUpdateFlags = ~GraphicsStateUpdateFlags::ClipRegion;
+ }
+ if( rNewState.m_bClipRegion )
+ {
+ // clip region is always stored in private PDF mapmode
+ MapMode aNewMapMode = rNewState.m_aMapMode;
+ rNewState.m_aMapMode = m_aMapMode;
+ SetMapMode( rNewState.m_aMapMode );
+ m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode;
+
+ aLine.append("q ");
+ if ( rNewState.m_aClipRegion.count() )
+ {
+ m_aPages.back().appendPolyPolygon( rNewState.m_aClipRegion, aLine );
+ }
+ else
+ {
+ // tdf#130150 Need to revert tdf#99680, that breaks the
+ // rule that an set but empty clip-region clips everything
+ // aka draws nothing -> nothing is in an empty clip-region
+ aLine.append( "0 0 m h " ); // NULL clip, i.e. nothing visible
+ }
+ aLine.append( "W* n\n" );
+
+ rNewState.m_aMapMode = aNewMapMode;
+ SetMapMode( rNewState.m_aMapMode );
+ m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode;
+ }
+ }
+ }
+
+ if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::MapMode )
+ {
+ rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::MapMode;
+ SetMapMode( rNewState.m_aMapMode );
+ }
+
+ if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::Font )
+ {
+ rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::Font;
+ SetFont( rNewState.m_aFont );
+ }
+
+ if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::LayoutMode )
+ {
+ rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::LayoutMode;
+ SetLayoutMode( rNewState.m_nLayoutMode );
+ }
+
+ if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::DigitLanguage )
+ {
+ rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::DigitLanguage;
+ SetDigitLanguage( rNewState.m_aDigitLanguage );
+ }
+
+ if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::LineColor )
+ {
+ rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::LineColor;
+ if( m_aCurrentPDFState.m_aLineColor != rNewState.m_aLineColor &&
+ rNewState.m_aLineColor != COL_TRANSPARENT )
+ {
+ appendStrokingColor( rNewState.m_aLineColor, aLine );
+ aLine.append( "\n" );
+ }
+ }
+
+ if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::FillColor )
+ {
+ rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::FillColor;
+ if( m_aCurrentPDFState.m_aFillColor != rNewState.m_aFillColor &&
+ rNewState.m_aFillColor != COL_TRANSPARENT )
+ {
+ appendNonStrokingColor( rNewState.m_aFillColor, aLine );
+ aLine.append( "\n" );
+ }
+ }
+
+ if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::TransparentPercent )
+ {
+ rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::TransparentPercent;
+ if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
+ {
+ // TODO: switch extended graphicsstate
+ }
+ }
+
+ // everything is up to date now
+ m_aCurrentPDFState = m_aGraphicsStack.front();
+ if ((mode != Mode::NOWRITE) && !aLine.isEmpty())
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+/* #i47544# imitate OutputDevice behaviour:
+* if a font with a nontransparent color is set, it overwrites the current
+* text color. OTOH setting the text color will overwrite the color of the font.
+*/
+void PDFWriterImpl::setFont( const vcl::Font& rFont )
+{
+ Color aColor = rFont.GetColor();
+ if( aColor == COL_TRANSPARENT )
+ aColor = m_aGraphicsStack.front().m_aFont.GetColor();
+ m_aGraphicsStack.front().m_aFont = rFont;
+ m_aGraphicsStack.front().m_aFont.SetColor( aColor );
+ m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::Font;
+}
+
+void PDFWriterImpl::push( PushFlags nFlags )
+{
+ OSL_ENSURE( !m_aGraphicsStack.empty(), "invalid graphics stack" );
+ m_aGraphicsStack.push_front( m_aGraphicsStack.front() );
+ m_aGraphicsStack.front().m_nFlags = nFlags;
+}
+
+void PDFWriterImpl::pop()
+{
+ OSL_ENSURE( m_aGraphicsStack.size() > 1, "pop without push" );
+ if( m_aGraphicsStack.size() < 2 )
+ return;
+
+ GraphicsState aState = m_aGraphicsStack.front();
+ m_aGraphicsStack.pop_front();
+ GraphicsState& rOld = m_aGraphicsStack.front();
+
+ // move those parameters back that were not pushed
+ // in the first place
+ if( ! (aState.m_nFlags & PushFlags::LINECOLOR) )
+ setLineColor( aState.m_aLineColor );
+ if( ! (aState.m_nFlags & PushFlags::FILLCOLOR) )
+ setFillColor( aState.m_aFillColor );
+ if( ! (aState.m_nFlags & PushFlags::FONT) )
+ setFont( aState.m_aFont );
+ if( ! (aState.m_nFlags & PushFlags::TEXTCOLOR) )
+ setTextColor( aState.m_aFont.GetColor() );
+ if( ! (aState.m_nFlags & PushFlags::MAPMODE) )
+ setMapMode( aState.m_aMapMode );
+ if( ! (aState.m_nFlags & PushFlags::CLIPREGION) )
+ {
+ // do not use setClipRegion here
+ // it would convert again assuming the current mapmode
+ rOld.m_aClipRegion = aState.m_aClipRegion;
+ rOld.m_bClipRegion = aState.m_bClipRegion;
+ }
+ if( ! (aState.m_nFlags & PushFlags::TEXTLINECOLOR ) )
+ setTextLineColor( aState.m_aTextLineColor );
+ if( ! (aState.m_nFlags & PushFlags::OVERLINECOLOR ) )
+ setOverlineColor( aState.m_aOverlineColor );
+ if( ! (aState.m_nFlags & PushFlags::TEXTALIGN ) )
+ setTextAlign( aState.m_aFont.GetAlignment() );
+ if( ! (aState.m_nFlags & PushFlags::TEXTFILLCOLOR) )
+ setTextFillColor( aState.m_aFont.GetFillColor() );
+ if( ! (aState.m_nFlags & PushFlags::REFPOINT) )
+ {
+ // what ?
+ }
+ // invalidate graphics state
+ m_aGraphicsStack.front().m_nUpdateFlags = GraphicsStateUpdateFlags::All;
+}
+
+void PDFWriterImpl::setMapMode( const MapMode& rMapMode )
+{
+ m_aGraphicsStack.front().m_aMapMode = rMapMode;
+ SetMapMode( rMapMode );
+ m_aCurrentPDFState.m_aMapMode = rMapMode;
+}
+
+void PDFWriterImpl::setClipRegion( const basegfx::B2DPolyPolygon& rRegion )
+{
+ // tdf#130150 improve coordinate manipulations to double precision transformations
+ const basegfx::B2DHomMatrix aCurrentTransform(
+ GetInverseViewTransformation(m_aMapMode) * GetViewTransformation(m_aGraphicsStack.front().m_aMapMode));
+ basegfx::B2DPolyPolygon aRegion(rRegion);
+
+ aRegion.transform(aCurrentTransform);
+ m_aGraphicsStack.front().m_aClipRegion = aRegion;
+ m_aGraphicsStack.front().m_bClipRegion = true;
+ m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
+}
+
+void PDFWriterImpl::moveClipRegion( sal_Int32 nX, sal_Int32 nY )
+{
+ if( m_aGraphicsStack.front().m_bClipRegion && m_aGraphicsStack.front().m_aClipRegion.count() )
+ {
+ // 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 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()) )
+ return;
+
+ m_aNotes.emplace_back( );
+ m_aNotes.back().m_nObject = createObject();
+ m_aNotes.back().m_aContents = rNote;
+ m_aNotes.back().m_aRect = rRect;
+ // convert to default user space now, since the mapmode may change
+ m_aPages[nPageNr].convertRect( m_aNotes.back().m_aRect );
+
+ // insert note to page's annotation list
+ m_aPages[ nPageNr ].m_aAnnotations.push_back( m_aNotes.back().m_nObject );
+}
+
+sal_Int32 PDFWriterImpl::createLink( const tools::Rectangle& rRect, sal_Int32 nPageNr )
+{
+ if( nPageNr < 0 )
+ nPageNr = m_nCurrentPage;
+
+ if( nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()) )
+ return -1;
+
+ sal_Int32 nRet = m_aLinks.size();
+
+ m_aLinks.emplace_back( );
+ m_aLinks.back().m_nObject = createObject();
+ m_aLinks.back().m_nPage = nPageNr;
+ m_aLinks.back().m_aRect = rRect;
+ // convert to default user space now, since the mapmode may change
+ m_aPages[nPageNr].convertRect( m_aLinks.back().m_aRect );
+
+ // insert link to page's annotation list
+ m_aPages[ nPageNr ].m_aAnnotations.push_back( m_aLinks.back().m_nObject );
+
+ return nRet;
+}
+
+sal_Int32 PDFWriterImpl::createScreen(const tools::Rectangle& rRect, sal_Int32 nPageNr)
+{
+ if (nPageNr < 0)
+ nPageNr = m_nCurrentPage;
+
+ if (nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()))
+ return -1;
+
+ sal_Int32 nRet = m_aScreens.size();
+
+ m_aScreens.emplace_back();
+ m_aScreens.back().m_nObject = createObject();
+ m_aScreens.back().m_nPage = nPageNr;
+ m_aScreens.back().m_aRect = rRect;
+ // Convert to default user space now, since the mapmode may change.
+ m_aPages[nPageNr].convertRect(m_aScreens.back().m_aRect);
+
+ // Insert link to page's annotation list.
+ m_aPages[nPageNr].m_aAnnotations.push_back(m_aScreens.back().m_nObject);
+
+ return nRet;
+}
+
+sal_Int32 PDFWriterImpl::createNamedDest( const OUString& sDestName, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
+{
+ if( nPageNr < 0 )
+ nPageNr = m_nCurrentPage;
+
+ if( nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(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 || nPageNr >= static_cast<sal_Int32>(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 || nLinkId >= static_cast<sal_Int32>(m_aLinks.size()) )
+ return;
+ if( nDestId < 0 || nDestId >= static_cast<sal_Int32>(m_aDests.size()) )
+ return;
+
+ m_aLinks[ nLinkId ].m_nDest = nDestId;
+}
+
+void PDFWriterImpl::setLinkURL( sal_Int32 nLinkId, const OUString& rURL )
+{
+ if( nLinkId < 0 || nLinkId >= static_cast<sal_Int32>(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 || nScreenId >= static_cast<sal_Int32>(m_aScreens.size()))
+ return;
+
+ m_aScreens[nScreenId].m_aURL = rURL;
+}
+
+void PDFWriterImpl::setScreenStream(sal_Int32 nScreenId, const OUString& rURL)
+{
+ if (nScreenId < 0 || nScreenId >= static_cast<sal_Int32>(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, const OUString& 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 || nItem >= static_cast<sal_Int32>(m_aOutline.size()) )
+ return;
+
+ if( nNewParent < 0 || nNewParent >= static_cast<sal_Int32>(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, const OUString& rText )
+{
+ if( nItem < 1 || nItem >= static_cast<sal_Int32>(m_aOutline.size()) )
+ return;
+
+ m_aOutline[ nItem ].m_aTitle = psp::WhitespaceToSpace( rText );
+}
+
+void PDFWriterImpl::setOutlineItemDest( sal_Int32 nItem, sal_Int32 nDestID )
+{
+ if( nItem < 1 || nItem >= static_cast<sal_Int32>(m_aOutline.size()) ) // item does not exist
+ return;
+ if( nDestID < 0 || nDestID >= static_cast<sal_Int32>(m_aDests.size()) ) // dest does not exist
+ return;
+ m_aOutline[nItem].m_nDestID = nDestID;
+}
+
+const char* PDFWriterImpl::getStructureTag( PDFWriter::StructElement eType )
+{
+ static std::map< PDFWriter::StructElement, const char* > aTagStrings;
+ if( aTagStrings.empty() )
+ {
+ aTagStrings[ PDFWriter::NonStructElement] = "NonStruct";
+ aTagStrings[ PDFWriter::Document ] = "Document";
+ aTagStrings[ PDFWriter::Part ] = "Part";
+ aTagStrings[ PDFWriter::Article ] = "Art";
+ aTagStrings[ PDFWriter::Section ] = "Sect";
+ aTagStrings[ PDFWriter::Division ] = "Div";
+ aTagStrings[ PDFWriter::BlockQuote ] = "BlockQuote";
+ aTagStrings[ PDFWriter::Caption ] = "Caption";
+ aTagStrings[ PDFWriter::TOC ] = "TOC";
+ aTagStrings[ PDFWriter::TOCI ] = "TOCI";
+ aTagStrings[ PDFWriter::Index ] = "Index";
+ aTagStrings[ PDFWriter::Paragraph ] = "P";
+ aTagStrings[ PDFWriter::Heading ] = "H";
+ aTagStrings[ PDFWriter::H1 ] = "H1";
+ aTagStrings[ PDFWriter::H2 ] = "H2";
+ aTagStrings[ PDFWriter::H3 ] = "H3";
+ aTagStrings[ PDFWriter::H4 ] = "H4";
+ aTagStrings[ PDFWriter::H5 ] = "H5";
+ aTagStrings[ PDFWriter::H6 ] = "H6";
+ aTagStrings[ PDFWriter::List ] = "L";
+ aTagStrings[ PDFWriter::ListItem ] = "LI";
+ aTagStrings[ PDFWriter::LILabel ] = "Lbl";
+ aTagStrings[ PDFWriter::LIBody ] = "LBody";
+ aTagStrings[ PDFWriter::Table ] = "Table";
+ aTagStrings[ PDFWriter::TableRow ] = "TR";
+ aTagStrings[ PDFWriter::TableHeader ] = "TH";
+ aTagStrings[ PDFWriter::TableData ] = "TD";
+ aTagStrings[ PDFWriter::Span ] = "Span";
+ aTagStrings[ PDFWriter::Quote ] = "Quote";
+ aTagStrings[ PDFWriter::Note ] = "Note";
+ aTagStrings[ PDFWriter::Reference ] = "Reference";
+ aTagStrings[ PDFWriter::BibEntry ] = "BibEntry";
+ aTagStrings[ PDFWriter::Code ] = "Code";
+ aTagStrings[ PDFWriter::Link ] = "Link";
+ aTagStrings[ PDFWriter::Figure ] = "Figure";
+ aTagStrings[ PDFWriter::Formula ] = "Formula";
+ aTagStrings[ PDFWriter::Form ] = "Form";
+ }
+
+ std::map< PDFWriter::StructElement, const char* >::const_iterator it = aTagStrings.find( eType );
+
+ return it != aTagStrings.end() ? it->second : "Div";
+}
+
+void PDFWriterImpl::addRoleMap(OString aAlias, PDFWriter::StructElement eType)
+{
+ OString aTag = getStructureTag(eType);
+ // For PDF/UA it's not allowed to map an alias with the same name.
+ // Not sure if this allowed, necessary or recommended otherwise, so
+ // only enable filtering when PDF/UA is enabled.
+ if (!m_bIsPDF_UA || aAlias != aTag)
+ m_aRoleMap[aAlias] = aTag;
+}
+
+void PDFWriterImpl::beginStructureElementMCSeq()
+{
+ if( m_bEmitStructure &&
+ m_nCurrentStructElement > 0 && // StructTreeRoot
+ ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence
+ )
+ {
+ PDFStructureElement& rEle = m_aStructure[ m_nCurrentStructElement ];
+ OStringBuffer aLine( 128 );
+ sal_Int32 nMCID = m_aPages[ m_nCurrentPage ].m_aMCIDParents.size();
+ aLine.append( "/" );
+ if( !rEle.m_aAlias.isEmpty() )
+ aLine.append( rEle.m_aAlias );
+ else
+ aLine.append( getStructureTag( rEle.m_eType ) );
+ aLine.append( "<</MCID " );
+ aLine.append( nMCID );
+ aLine.append( ">>BDC\n" );
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+
+ // update the element's content list
+ SAL_INFO("vcl.pdfwriter", "beginning marked content id " << nMCID << " on page object "
+ << m_aPages[ m_nCurrentPage ].m_nPageObject << ", structure first page = "
+ << rEle.m_nFirstPageObject);
+ rEle.m_aKids.emplace_back( nMCID, m_aPages[m_nCurrentPage].m_nPageObject );
+ // update the page's mcid parent list
+ m_aPages[ m_nCurrentPage ].m_aMCIDParents.push_back( rEle.m_nObject );
+ // mark element MC sequence as open
+ rEle.m_bOpenMCSeq = true;
+ }
+ // handle artifacts
+ else if( ! m_bEmitStructure && m_aContext.Tagged &&
+ m_nCurrentStructElement > 0 &&
+ m_aStructure[ m_nCurrentStructElement ].m_eType == PDFWriter::NonStructElement &&
+ ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence
+ )
+ {
+ OString aLine = "/Artifact BMC\n";
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+ // mark element MC sequence as open
+ m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = true;
+ }
+}
+
+void PDFWriterImpl::endStructureElementMCSeq()
+{
+ if( m_nCurrentStructElement > 0 && // StructTreeRoot
+ ( m_bEmitStructure || m_aStructure[ m_nCurrentStructElement ].m_eType == PDFWriter::NonStructElement ) &&
+ m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // must have an opened MC sequence
+ )
+ {
+ writeBuffer( "EMC\n", 4 );
+ m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = false;
+ }
+}
+
+bool PDFWriterImpl::checkEmitStructure()
+{
+ bool bEmit = false;
+ if( m_aContext.Tagged )
+ {
+ bEmit = true;
+ sal_Int32 nEle = m_nCurrentStructElement;
+ while( nEle > 0 && nEle < sal_Int32(m_aStructure.size()) )
+ {
+ if( m_aStructure[ nEle ].m_eType == PDFWriter::NonStructElement )
+ {
+ bEmit = false;
+ break;
+ }
+ nEle = m_aStructure[ nEle ].m_nParentElement;
+ }
+ }
+ return bEmit;
+}
+
+sal_Int32 PDFWriterImpl::beginStructureElement( PDFWriter::StructElement eType, const OUString& rAlias )
+{
+ if( m_nCurrentPage < 0 )
+ return -1;
+
+ if( ! m_aContext.Tagged )
+ return -1;
+
+ // close eventual current MC sequence
+ endStructureElementMCSeq();
+
+ if( m_nCurrentStructElement == 0 &&
+ eType != PDFWriter::Document && eType != PDFWriter::NonStructElement )
+ {
+ // struct tree root hit, but not beginning document
+ // this might happen with setCurrentStructureElement
+ // silently insert structure into document again if one properly exists
+ if( ! m_aStructure[ 0 ].m_aChildren.empty() )
+ {
+ const std::list< sal_Int32 >& rRootChildren = m_aStructure[0].m_aChildren;
+ auto it = std::find_if(rRootChildren.begin(), rRootChildren.end(),
+ [&](sal_Int32 nElement) { return m_aStructure[ nElement ].m_eType == PDFWriter::Document; });
+ if( it != rRootChildren.end() )
+ {
+ m_nCurrentStructElement = *it;
+ SAL_WARN( "vcl.pdfwriter", "Structure element inserted to StructTreeRoot that is not a document" );
+ }
+ else {
+ OSL_FAIL( "document structure in disorder !" );
+ }
+ }
+ else {
+ OSL_FAIL( "PDF document structure MUST be contained in a Document element" );
+ }
+ }
+
+ sal_Int32 nNewId = sal_Int32(m_aStructure.size());
+ m_aStructure.emplace_back( );
+ PDFStructureElement& rEle = m_aStructure.back();
+ rEle.m_eType = eType;
+ rEle.m_nOwnElement = nNewId;
+ rEle.m_nParentElement = m_nCurrentStructElement;
+ rEle.m_nFirstPageObject = m_aPages[ m_nCurrentPage ].m_nPageObject;
+ m_aStructure[ m_nCurrentStructElement ].m_aChildren.push_back( nNewId );
+ m_nCurrentStructElement = nNewId;
+
+ // handle alias names
+ if( !rAlias.isEmpty() && eType != PDFWriter::NonStructElement )
+ {
+ OStringBuffer aNameBuf( rAlias.getLength() );
+ appendName( rAlias, aNameBuf );
+ OString aAliasName( aNameBuf.makeStringAndClear() );
+ rEle.m_aAlias = aAliasName;
+ addRoleMap(aAliasName, eType);
+ }
+
+ if (g_bDebugDisableCompression)
+ {
+ OStringBuffer aLine( "beginStructureElement " );
+ aLine.append( m_nCurrentStructElement );
+ aLine.append( ": " );
+ aLine.append( getStructureTag( eType ) );
+ if( !rEle.m_aAlias.isEmpty() )
+ {
+ aLine.append( " aliased as \"" );
+ aLine.append( rEle.m_aAlias );
+ aLine.append( '\"' );
+ }
+ emitComment( aLine.getStr() );
+ }
+
+ // check whether to emit structure henceforth
+ m_bEmitStructure = checkEmitStructure();
+
+ if( m_bEmitStructure ) // don't create nonexistent objects
+ {
+ rEle.m_nObject = createObject();
+ // update parent's kids list
+ m_aStructure[ rEle.m_nParentElement ].m_aKids.emplace_back(rEle.m_nObject);
+ }
+ return nNewId;
+}
+
+void PDFWriterImpl::endStructureElement()
+{
+ if( m_nCurrentPage < 0 )
+ return;
+
+ if( ! m_aContext.Tagged )
+ return;
+
+ if( m_nCurrentStructElement == 0 )
+ {
+ // hit the struct tree root, that means there is an endStructureElement
+ // without corresponding beginStructureElement
+ return;
+ }
+
+ // end the marked content sequence
+ endStructureElementMCSeq();
+
+ OStringBuffer aLine;
+ if (g_bDebugDisableCompression)
+ {
+ aLine.append( "endStructureElement " );
+ aLine.append( m_nCurrentStructElement );
+ aLine.append( ": " );
+ aLine.append( getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType ) );
+ if( !m_aStructure[ m_nCurrentStructElement ].m_aAlias.isEmpty() )
+ {
+ aLine.append( " aliased as \"" );
+ aLine.append( m_aStructure[ m_nCurrentStructElement ].m_aAlias );
+ aLine.append( '\"' );
+ }
+ }
+
+ // "end" the structure element, the parent becomes current element
+ m_nCurrentStructElement = m_aStructure[ m_nCurrentStructElement ].m_nParentElement;
+
+ // check whether to emit structure henceforth
+ m_bEmitStructure = checkEmitStructure();
+
+ if (g_bDebugDisableCompression && m_bEmitStructure)
+ {
+ emitComment( aLine.getStr() );
+ }
+}
+
+/*
+ * This function adds an internal structure list container to overcome the 8191 elements array limitation
+ * in kids element emission.
+ * Recursive function
+ *
+ */
+void PDFWriterImpl::addInternalStructureContainer( PDFStructureElement& rEle )
+{
+ if( rEle.m_eType == PDFWriter::NonStructElement &&
+ rEle.m_nOwnElement != rEle.m_nParentElement )
+ return;
+
+ for (auto const& child : rEle.m_aChildren)
+ {
+ if( child > 0 && child < sal_Int32(m_aStructure.size()) )
+ {
+ PDFStructureElement& rChild = m_aStructure[ child ];
+ if( rChild.m_eType != PDFWriter::NonStructElement )
+ {
+ //triggered when a child of the rEle element is found
+ if( rChild.m_nParentElement == rEle.m_nOwnElement )
+ addInternalStructureContainer( rChild );//examine the child
+ else
+ {
+ OSL_FAIL( "PDFWriterImpl::addInternalStructureContainer: invalid child structure element" );
+ SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::addInternalStructureContainer: invalid child structure element with id " << child );
+ }
+ }
+ }
+ else
+ {
+ OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure id" );
+ SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::addInternalStructureContainer: invalid child structure id " << child );
+ }
+ }
+
+ if( rEle.m_nOwnElement != rEle.m_nParentElement )
+ {
+ if( !rEle.m_aKids.empty() )
+ {
+ if( rEle.m_aKids.size() > ncMaxPDFArraySize ) {
+ //then we need to add the containers for the kids elements
+ // a list to be used for the new kid element
+ std::list< PDFStructureElementKid > aNewKids;
+ std::list< sal_Int32 > aNewChildren;
+
+ // add Div in RoleMap, in case no one else did (TODO: is it needed? Is it dangerous?)
+ OString aAliasName("Div");
+ addRoleMap(aAliasName, PDFWriter::Division);
+
+ while( rEle.m_aKids.size() > ncMaxPDFArraySize )
+ {
+ sal_Int32 nCurrentStructElement = rEle.m_nOwnElement;
+ sal_Int32 nNewId = sal_Int32(m_aStructure.size());
+ m_aStructure.emplace_back( );
+ PDFStructureElement& rEleNew = m_aStructure.back();
+ rEleNew.m_aAlias = aAliasName;
+ rEleNew.m_eType = PDFWriter::Division; // a new Div type container
+ rEleNew.m_nOwnElement = nNewId;
+ rEleNew.m_nParentElement = nCurrentStructElement;
+ //inherit the same page as the first child to be reparented
+ rEleNew.m_nFirstPageObject = m_aStructure[ rEle.m_aChildren.front() ].m_nFirstPageObject;
+ rEleNew.m_nObject = createObject();//assign a PDF object number
+ //add the object to the kid list of the parent
+ aNewKids.emplace_back( rEleNew.m_nObject );
+ aNewChildren.push_back( nNewId );
+
+ std::list< sal_Int32 >::iterator aChildEndIt( rEle.m_aChildren.begin() );
+ std::list< PDFStructureElementKid >::iterator aKidEndIt( rEle.m_aKids.begin() );
+ advance( aChildEndIt, ncMaxPDFArraySize );
+ advance( aKidEndIt, ncMaxPDFArraySize );
+
+ rEleNew.m_aKids.splice( rEleNew.m_aKids.begin(),
+ rEle.m_aKids,
+ rEle.m_aKids.begin(),
+ aKidEndIt );
+ rEleNew.m_aChildren.splice( rEleNew.m_aChildren.begin(),
+ rEle.m_aChildren,
+ rEle.m_aChildren.begin(),
+ aChildEndIt );
+ // set the kid's new parent
+ for (auto const& child : rEleNew.m_aChildren)
+ {
+ m_aStructure[ child ].m_nParentElement = nNewId;
+ }
+ }
+ //finally add the new kids resulting from the container added
+ rEle.m_aKids.insert( rEle.m_aKids.begin(), aNewKids.begin(), aNewKids.end() );
+ rEle.m_aChildren.insert( rEle.m_aChildren.begin(), aNewChildren.begin(), aNewChildren.end() );
+ }
+ }
+ }
+}
+
+bool PDFWriterImpl::setCurrentStructureElement( sal_Int32 nEle )
+{
+ bool bSuccess = false;
+
+ if( m_aContext.Tagged && nEle >= 0 && nEle < sal_Int32(m_aStructure.size()) )
+ {
+ // end eventual previous marked content sequence
+ endStructureElementMCSeq();
+
+ m_nCurrentStructElement = nEle;
+ m_bEmitStructure = checkEmitStructure();
+ if (g_bDebugDisableCompression)
+ {
+ OStringBuffer aLine( "setCurrentStructureElement " );
+ aLine.append( m_nCurrentStructElement );
+ aLine.append( ": " );
+ aLine.append( getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType ) );
+ if( !m_aStructure[ m_nCurrentStructElement ].m_aAlias.isEmpty() )
+ {
+ aLine.append( " aliased as \"" );
+ aLine.append( m_aStructure[ m_nCurrentStructElement ].m_aAlias );
+ aLine.append( '\"' );
+ }
+ if( ! m_bEmitStructure )
+ aLine.append( " (inside NonStruct)" );
+ emitComment( aLine.getStr() );
+ }
+ bSuccess = true;
+ }
+
+ return bSuccess;
+}
+
+bool PDFWriterImpl::setStructureAttribute( enum PDFWriter::StructAttribute eAttr, enum PDFWriter::StructAttributeValue eVal )
+{
+ if( !m_aContext.Tagged )
+ return false;
+
+ bool bInsert = false;
+ if( m_nCurrentStructElement > 0 && m_bEmitStructure )
+ {
+ PDFWriter::StructElement eType = m_aStructure[ m_nCurrentStructElement ].m_eType;
+ switch( eAttr )
+ {
+ case PDFWriter::Placement:
+ if( eVal == PDFWriter::Block ||
+ eVal == PDFWriter::Inline ||
+ eVal == PDFWriter::Before ||
+ eVal == PDFWriter::Start ||
+ eVal == PDFWriter::End )
+ bInsert = true;
+ break;
+ case PDFWriter::WritingMode:
+ if( eVal == PDFWriter::LrTb ||
+ eVal == PDFWriter::RlTb ||
+ eVal == PDFWriter::TbRl )
+ {
+ bInsert = true;
+ }
+ break;
+ case PDFWriter::TextAlign:
+ if( eVal == PDFWriter::Start ||
+ eVal == PDFWriter::Center ||
+ eVal == PDFWriter::End ||
+ eVal == PDFWriter::Justify )
+ {
+ if( eType == PDFWriter::Paragraph ||
+ eType == PDFWriter::Heading ||
+ eType == PDFWriter::H1 ||
+ eType == PDFWriter::H2 ||
+ eType == PDFWriter::H3 ||
+ eType == PDFWriter::H4 ||
+ eType == PDFWriter::H5 ||
+ eType == PDFWriter::H6 ||
+ eType == PDFWriter::List ||
+ eType == PDFWriter::ListItem ||
+ eType == PDFWriter::LILabel ||
+ eType == PDFWriter::LIBody ||
+ eType == PDFWriter::Table ||
+ eType == PDFWriter::TableRow ||
+ eType == PDFWriter::TableHeader ||
+ eType == PDFWriter::TableData )
+ {
+ bInsert = true;
+ }
+ }
+ break;
+ case PDFWriter::Width:
+ case PDFWriter::Height:
+ if( eVal == PDFWriter::Auto )
+ {
+ if( eType == PDFWriter::Figure ||
+ eType == PDFWriter::Formula ||
+ eType == PDFWriter::Form ||
+ eType == PDFWriter::Table ||
+ eType == PDFWriter::TableHeader ||
+ eType == PDFWriter::TableData )
+ {
+ bInsert = true;
+ }
+ }
+ break;
+ case PDFWriter::BlockAlign:
+ if( eVal == PDFWriter::Before ||
+ eVal == PDFWriter::Middle ||
+ eVal == PDFWriter::After ||
+ eVal == PDFWriter::Justify )
+ {
+ if( eType == PDFWriter::TableHeader ||
+ eType == PDFWriter::TableData )
+ {
+ bInsert = true;
+ }
+ }
+ break;
+ case PDFWriter::InlineAlign:
+ if( eVal == PDFWriter::Start ||
+ eVal == PDFWriter::Center ||
+ eVal == PDFWriter::End )
+ {
+ if( eType == PDFWriter::TableHeader ||
+ eType == PDFWriter::TableData )
+ {
+ bInsert = true;
+ }
+ }
+ break;
+ case PDFWriter::LineHeight:
+ if( eVal == PDFWriter::Normal ||
+ eVal == PDFWriter::Auto )
+ {
+ // only for ILSE and BLSE
+ if( eType == PDFWriter::Paragraph ||
+ eType == PDFWriter::Heading ||
+ eType == PDFWriter::H1 ||
+ eType == PDFWriter::H2 ||
+ eType == PDFWriter::H3 ||
+ eType == PDFWriter::H4 ||
+ eType == PDFWriter::H5 ||
+ eType == PDFWriter::H6 ||
+ eType == PDFWriter::List ||
+ eType == PDFWriter::ListItem ||
+ eType == PDFWriter::LILabel ||
+ eType == PDFWriter::LIBody ||
+ eType == PDFWriter::Table ||
+ eType == PDFWriter::TableRow ||
+ eType == PDFWriter::TableHeader ||
+ eType == PDFWriter::TableData ||
+ eType == PDFWriter::Span ||
+ eType == PDFWriter::Quote ||
+ eType == PDFWriter::Note ||
+ eType == PDFWriter::Reference ||
+ eType == PDFWriter::BibEntry ||
+ eType == PDFWriter::Code ||
+ eType == PDFWriter::Link )
+ {
+ bInsert = true;
+ }
+ }
+ break;
+ case PDFWriter::TextDecorationType:
+ if( eVal == PDFWriter::NONE ||
+ eVal == PDFWriter::Underline ||
+ eVal == PDFWriter::Overline ||
+ eVal == PDFWriter::LineThrough )
+ {
+ // only for ILSE and BLSE
+ if( eType == PDFWriter::Paragraph ||
+ eType == PDFWriter::Heading ||
+ eType == PDFWriter::H1 ||
+ eType == PDFWriter::H2 ||
+ eType == PDFWriter::H3 ||
+ eType == PDFWriter::H4 ||
+ eType == PDFWriter::H5 ||
+ eType == PDFWriter::H6 ||
+ eType == PDFWriter::List ||
+ eType == PDFWriter::ListItem ||
+ eType == PDFWriter::LILabel ||
+ eType == PDFWriter::LIBody ||
+ eType == PDFWriter::Table ||
+ eType == PDFWriter::TableRow ||
+ eType == PDFWriter::TableHeader ||
+ eType == PDFWriter::TableData ||
+ eType == PDFWriter::Span ||
+ eType == PDFWriter::Quote ||
+ eType == PDFWriter::Note ||
+ eType == PDFWriter::Reference ||
+ eType == PDFWriter::BibEntry ||
+ eType == PDFWriter::Code ||
+ eType == PDFWriter::Link )
+ {
+ bInsert = true;
+ }
+ }
+ break;
+ case PDFWriter::ListNumbering:
+ if( eVal == PDFWriter::NONE ||
+ eVal == PDFWriter::Disc ||
+ eVal == PDFWriter::Circle ||
+ eVal == PDFWriter::Square ||
+ eVal == PDFWriter::Decimal ||
+ eVal == PDFWriter::UpperRoman ||
+ eVal == PDFWriter::LowerRoman ||
+ eVal == PDFWriter::UpperAlpha ||
+ eVal == PDFWriter::LowerAlpha )
+ {
+ if( eType == PDFWriter::List )
+ bInsert = true;
+ }
+ break;
+ default: break;
+ }
+ }
+
+ if( bInsert )
+ m_aStructure[ m_nCurrentStructElement ].m_aAttributes[ eAttr ] = PDFStructureAttribute( eVal );
+ else if( m_nCurrentStructElement > 0 && m_bEmitStructure )
+ SAL_INFO("vcl.pdfwriter",
+ "rejecting setStructureAttribute( " << getAttributeTag( eAttr )
+ << ", " << getAttributeValueTag( eVal )
+ << " ) on " << getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType )
+ << " (" << m_aStructure[ m_nCurrentStructElement ].m_aAlias
+ << ") element");
+
+ return bInsert;
+}
+
+bool PDFWriterImpl::setStructureAttributeNumerical( enum PDFWriter::StructAttribute eAttr, sal_Int32 nValue )
+{
+ if( ! m_aContext.Tagged )
+ return false;
+
+ bool bInsert = false;
+ if( m_nCurrentStructElement > 0 && m_bEmitStructure )
+ {
+ if( eAttr == PDFWriter::Language )
+ {
+ m_aStructure[ m_nCurrentStructElement ].m_aLocale = LanguageTag( LanguageType(nValue) ).getLocale();
+ return true;
+ }
+
+ PDFWriter::StructElement eType = m_aStructure[ m_nCurrentStructElement ].m_eType;
+ switch( eAttr )
+ {
+ case PDFWriter::SpaceBefore:
+ case PDFWriter::SpaceAfter:
+ case PDFWriter::StartIndent:
+ case PDFWriter::EndIndent:
+ // just for BLSE
+ if( eType == PDFWriter::Paragraph ||
+ eType == PDFWriter::Heading ||
+ eType == PDFWriter::H1 ||
+ eType == PDFWriter::H2 ||
+ eType == PDFWriter::H3 ||
+ eType == PDFWriter::H4 ||
+ eType == PDFWriter::H5 ||
+ eType == PDFWriter::H6 ||
+ eType == PDFWriter::List ||
+ eType == PDFWriter::ListItem ||
+ eType == PDFWriter::LILabel ||
+ eType == PDFWriter::LIBody ||
+ eType == PDFWriter::Table ||
+ eType == PDFWriter::TableRow ||
+ eType == PDFWriter::TableHeader ||
+ eType == PDFWriter::TableData )
+ {
+ bInsert = true;
+ }
+ break;
+ case PDFWriter::TextIndent:
+ // paragraph like BLSE and additional elements
+ if( eType == PDFWriter::Paragraph ||
+ eType == PDFWriter::Heading ||
+ eType == PDFWriter::H1 ||
+ eType == PDFWriter::H2 ||
+ eType == PDFWriter::H3 ||
+ eType == PDFWriter::H4 ||
+ eType == PDFWriter::H5 ||
+ eType == PDFWriter::H6 ||
+ eType == PDFWriter::LILabel ||
+ eType == PDFWriter::LIBody ||
+ eType == PDFWriter::TableHeader ||
+ eType == PDFWriter::TableData )
+ {
+ bInsert = true;
+ }
+ break;
+ case PDFWriter::Width:
+ case PDFWriter::Height:
+ if( eType == PDFWriter::Figure ||
+ eType == PDFWriter::Formula ||
+ eType == PDFWriter::Form ||
+ eType == PDFWriter::Table ||
+ eType == PDFWriter::TableHeader ||
+ eType == PDFWriter::TableData )
+ {
+ bInsert = true;
+ }
+ break;
+ case PDFWriter::LineHeight:
+ case PDFWriter::BaselineShift:
+ // only for ILSE and BLSE
+ if( eType == PDFWriter::Paragraph ||
+ eType == PDFWriter::Heading ||
+ eType == PDFWriter::H1 ||
+ eType == PDFWriter::H2 ||
+ eType == PDFWriter::H3 ||
+ eType == PDFWriter::H4 ||
+ eType == PDFWriter::H5 ||
+ eType == PDFWriter::H6 ||
+ eType == PDFWriter::List ||
+ eType == PDFWriter::ListItem ||
+ eType == PDFWriter::LILabel ||
+ eType == PDFWriter::LIBody ||
+ eType == PDFWriter::Table ||
+ eType == PDFWriter::TableRow ||
+ eType == PDFWriter::TableHeader ||
+ eType == PDFWriter::TableData ||
+ eType == PDFWriter::Span ||
+ eType == PDFWriter::Quote ||
+ eType == PDFWriter::Note ||
+ eType == PDFWriter::Reference ||
+ eType == PDFWriter::BibEntry ||
+ eType == PDFWriter::Code ||
+ eType == PDFWriter::Link )
+ {
+ bInsert = true;
+ }
+ break;
+ case PDFWriter::RowSpan:
+ case PDFWriter::ColSpan:
+ // only for table cells
+ if( eType == PDFWriter::TableHeader ||
+ eType == PDFWriter::TableData )
+ {
+ bInsert = true;
+ }
+ break;
+ case PDFWriter::LinkAnnotation:
+ if( eType == PDFWriter::Link )
+ bInsert = true;
+ break;
+ default: break;
+ }
+ }
+
+ if( bInsert )
+ m_aStructure[ m_nCurrentStructElement ].m_aAttributes[ eAttr ] = PDFStructureAttribute( nValue );
+ else if( m_nCurrentStructElement > 0 && m_bEmitStructure )
+ SAL_INFO("vcl.pdfwriter",
+ "rejecting setStructureAttributeNumerical( " << getAttributeTag( eAttr )
+ << ", " << static_cast<int>(nValue)
+ << " ) on " << getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType )
+ << " (" << m_aStructure[ m_nCurrentStructElement ].m_aAlias
+ << ") element");
+
+ return bInsert;
+}
+
+void PDFWriterImpl::setStructureBoundingBox( const tools::Rectangle& rRect )
+{
+ sal_Int32 nPageNr = m_nCurrentPage;
+ if( nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()) || !m_aContext.Tagged )
+ return;
+
+ if( m_nCurrentStructElement > 0 && m_bEmitStructure )
+ {
+ PDFWriter::StructElement eType = m_aStructure[ m_nCurrentStructElement ].m_eType;
+ if( eType == PDFWriter::Figure ||
+ eType == PDFWriter::Formula ||
+ eType == PDFWriter::Form ||
+ eType == PDFWriter::Table )
+ {
+ m_aStructure[ m_nCurrentStructElement ].m_aBBox = rRect;
+ // convert to default user space now, since the mapmode may change
+ m_aPages[nPageNr].convertRect( m_aStructure[ m_nCurrentStructElement ].m_aBBox );
+ }
+ }
+}
+
+void PDFWriterImpl::setActualText( const OUString& rText )
+{
+ if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure )
+ {
+ m_aStructure[ m_nCurrentStructElement ].m_aActualText = rText;
+ }
+}
+
+void PDFWriterImpl::setAlternateText( const OUString& rText )
+{
+ if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure )
+ {
+ m_aStructure[ m_nCurrentStructElement ].m_aAltText = rText;
+ }
+}
+
+void PDFWriterImpl::setPageTransition( PDFWriter::PageTransition eType, sal_uInt32 nMilliSec, sal_Int32 nPageNr )
+{
+ if( nPageNr < 0 )
+ nPageNr = m_nCurrentPage;
+
+ if( nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(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];
+ auto app_it = rKid.m_aAppearances.find( "N" );
+ if( app_it != rKid.m_aAppearances.end() )
+ {
+ auto stream_it = app_it->second.find( "Yes" );
+ if( stream_it != app_it->second.end() )
+ {
+ SvMemoryStream* pStream = stream_it->second;
+ app_it->second.erase( stream_it );
+ OStringBuffer aBuf( rKid.m_aOnValue.getLength()*2 );
+ appendName( rKid.m_aOnValue, aBuf );
+ (app_it->second)[ aBuf.makeStringAndClear() ] = pStream;
+ }
+ else
+ SAL_INFO("vcl.pdfwriter", "error: RadioButton without \"Yes\" stream" );
+ }
+ // 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 || nPageNr >= static_cast<sal_Int32>(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 || nRadioGroupWidget >= static_cast<sal_Int32>(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;
+ 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 ? OUStringLiteral("Yes") : OUStringLiteral("Off" );
+ // create default appearance before m_aRect gets transformed
+ createDefaultCheckBoxAppearance( rNewWidget, rBox );
+ }
+ else if( rControl.getType() == PDFWriter::ListBox )
+ {
+ if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
+ rNewWidget.m_nTextStyle = DrawTextFlags::VCenter;
+
+ const PDFWriter::ListBoxWidget& rLstBox = static_cast<const PDFWriter::ListBoxWidget&>(rControl);
+ rNewWidget.m_aListEntries = rLstBox.Entries;
+ rNewWidget.m_aSelectedEntries = rLstBox.SelectedEntries;
+ rNewWidget.m_aValue = rLstBox.Text;
+ if( rLstBox.DropDown )
+ rNewWidget.m_nFlags |= 0x00020000;
+ if( rLstBox.MultiSelect && !rLstBox.DropDown && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
+ rNewWidget.m_nFlags |= 0x00200000;
+
+ createDefaultListBoxAppearance( rNewWidget, rLstBox );
+ }
+ else if( rControl.getType() == PDFWriter::ComboBox )
+ {
+ if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
+ rNewWidget.m_nTextStyle = DrawTextFlags::VCenter;
+
+ const PDFWriter::ComboBoxWidget& rBox = static_cast<const PDFWriter::ComboBoxWidget&>(rControl);
+ rNewWidget.m_aValue = rBox.Text;
+ rNewWidget.m_aListEntries = rBox.Entries;
+ rNewWidget.m_nFlags |= 0x00060000; // combo and edit flag
+
+ PDFWriter::ListBoxWidget aLBox;
+ aLBox.Name = rBox.Name;
+ aLBox.Description = rBox.Description;
+ aLBox.Text = rBox.Text;
+ aLBox.TextStyle = rBox.TextStyle;
+ aLBox.ReadOnly = rBox.ReadOnly;
+ aLBox.Border = rBox.Border;
+ aLBox.BorderColor = rBox.BorderColor;
+ aLBox.Background = rBox.Background;
+ aLBox.BackgroundColor = rBox.BackgroundColor;
+ aLBox.TextFont = rBox.TextFont;
+ aLBox.TextColor = rBox.TextColor;
+ aLBox.DropDown = true;
+ aLBox.MultiSelect = false;
+ aLBox.Entries = rBox.Entries;
+
+ createDefaultListBoxAppearance( rNewWidget, aLBox );
+ }
+ else if( rControl.getType() == PDFWriter::Edit )
+ {
+ if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
+ rNewWidget.m_nTextStyle = DrawTextFlags::Left | DrawTextFlags::VCenter;
+
+ const PDFWriter::EditWidget& rEdit = static_cast<const PDFWriter::EditWidget&>(rControl);
+ if( rEdit.MultiLine )
+ {
+ rNewWidget.m_nFlags |= 0x00001000;
+ rNewWidget.m_nTextStyle |= DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
+ }
+ if( rEdit.Password )
+ rNewWidget.m_nFlags |= 0x00002000;
+ if( rEdit.FileSelect && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
+ rNewWidget.m_nFlags |= 0x00100000;
+ rNewWidget.m_nMaxLen = rEdit.MaxLen;
+ rNewWidget.m_aValue = rEdit.Text;
+
+ createDefaultEditAppearance( rNewWidget, rEdit );
+ }
+#if HAVE_FEATURE_NSS
+ else if( rControl.getType() == PDFWriter::Signature)
+ {
+ sigHidden = true;
+
+ rNewWidget.m_aRect = tools::Rectangle(0, 0, 0, 0);
+
+ m_nSignatureObject = createObject();
+ rNewWidget.m_aValue = OUString::number( m_nSignatureObject );
+ rNewWidget.m_aValue += " 0 R";
+ // let's add a fake appearance
+ rNewWidget.m_aAppearances[ "N" ][ "Standard" ] = new SvMemoryStream();
+ }
+#endif
+
+ // if control is a hidden signature, do not convert coordinates since we
+ // need /Rect [ 0 0 0 0 ]
+ if ( ! ( ( rControl.getType() == PDFWriter::Signature ) && sigHidden ) )
+ {
+ // convert to default user space now, since the mapmode may change
+ // note: create default appearances before m_aRect gets transformed
+ m_aPages[ nPageNr ].convertRect( rNewWidget.m_aRect );
+ }
+
+ // insert widget to page's annotation list
+ m_aPages[ nPageNr ].m_aAnnotations.push_back( rNewWidget.m_nObject );
+
+ return nNewWidget;
+}
+
+void PDFWriterImpl::addStream( const OUString& rMimeType, PDFOutputStream* pStream )
+{
+ if( pStream )
+ {
+ m_aAdditionalStreams.emplace_back( );
+ PDFAddStream& rStream = m_aAdditionalStreams.back();
+ rStream.m_aMimeType = !rMimeType.isEmpty()
+ ? rMimeType
+ : OUString( "application/octet-stream" );
+ rStream.m_pStream = pStream;
+ rStream.m_bCompress = false;
+ }
+}
+
+void PDFWriterImpl::MARK( const char* pString )
+{
+ beginStructureElementMCSeq();
+ if (g_bDebugDisableCompression)
+ emitComment( pString );
+}
+
+sal_Int32 ReferenceXObjectEmit::getObject() const
+{
+ if (m_nFormObject > 0)
+ return m_nFormObject;
+ else
+ return m_nBitmapObject;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/pdfwriter_impl.hxx b/vcl/source/gdi/pdfwriter_impl.hxx
new file mode 100644
index 000000000..6261a391b
--- /dev/null
+++ b/vcl/source/gdi/pdfwriter_impl.hxx
@@ -0,0 +1,1265 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_GDI_PDFWRITER_IMPL_HXX
+#define INCLUDED_VCL_SOURCE_GDI_PDFWRITER_IMPL_HXX
+
+#include <map>
+#include <list>
+#include <unordered_map>
+#include <memory>
+#include <vector>
+
+#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 <com/sun/star/uno/Sequence.h>
+#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 <outdata.hxx>
+#include "pdffontcache.hxx"
+#include "pdfbuildin_fonts.hxx"
+
+class StyleSettings;
+class FontSelectPattern;
+class FontSubsetInfo;
+class ZCodec;
+class EncHashTransporter;
+struct BitStreamState;
+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
+{
+constexpr sal_Int32 g_nInheritedPageWidth = 595; // default A4 in inch/72
+constexpr sal_Int32 g_nInheritedPageHeight = 842; // default A4 in inch/72
+
+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.
+ std::vector<sal_Int8> m_aPDFData;
+ sal_Int32 m_nPDFPageIndex;
+
+ ReferenceXObjectEmit()
+ : m_nFormObject(0),
+ m_nEmbeddedObject(0),
+ m_nBitmapObject(0),
+ m_nPDFPageIndex(-1)
+ {
+ }
+
+ /// Returns the ID one should use when referring to this bitmap.
+ sal_Int32 getObject() const;
+};
+
+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;
+ Bitmap m_aMask;
+ 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;
+ std::unique_ptr<SvMemoryStream> m_pSoftMaskStream;
+
+ TransparencyEmit()
+ : m_nObject( 0 ),
+ m_nExtGStateObject( -1 ),
+ m_fAlpha( 0.0 )
+ {}
+};
+
+// font subsets
+class GlyphEmit
+{
+ // performance: actually this should probably a vector;
+ std::vector<sal_Ucs> m_CodeUnits;
+ sal_uInt8 m_nSubsetGlyphID;
+
+public:
+ GlyphEmit() : m_nSubsetGlyphID(0)
+ {
+ }
+
+ void setGlyphId( sal_uInt8 i_nId ) { m_nSubsetGlyphID = i_nId; }
+ sal_uInt8 getGlyphId() const { return m_nSubsetGlyphID; }
+
+ 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;
+
+ EmbedFont() : m_nNormalFontID( 0 ) {}
+};
+
+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
+
+ PDFLink()
+ : m_nDest( -1 ),
+ m_nStructParent( -1 )
+ {}
+};
+
+/// A PDF embedded file.
+struct PDFEmbeddedFile
+{
+ /// ID of the file.
+ sal_Int32 m_nObject;
+ /// Contents of the file.
+ std::shared_ptr<std::vector<sal_Int8>> m_pData;
+
+ PDFEmbeddedFile()
+ : m_nObject(0)
+ {
+ }
+};
+
+struct PDFNoteEntry : public PDFAnnotation
+{
+ PDFNote m_aContents;
+
+ 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;
+
+ PDFScreen()
+ : m_nTempFileObject(0)
+ {
+ }
+};
+
+struct PDFWidget : public PDFAnnotation
+{
+ typedef std::unordered_map<OString, SvMemoryStream*> PDFAppearanceStreams;
+
+ 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;
+ sal_Int32 m_nTabOrder; // lowest number gets first in tab order
+ sal_Int32 m_nRadioGroup;
+ sal_Int32 m_nMaxLen;
+ bool m_bSubmit;
+ bool m_bSubmitGet;
+ sal_Int32 m_nDest;
+ std::vector<OUString> m_aListEntries;
+ std::vector<sal_Int32> m_aSelectedEntries;
+ std::unordered_map<OString, PDFAppearanceStreams> m_aAppearances;
+ 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_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 PDFStructureElementKid // for Kids entries
+{
+ sal_Int32 const nObject; // an object number if nMCID is -1,
+ // else the page object relevant to MCID
+ sal_Int32 const nMCID; // an MCID if >= 0
+
+ explicit PDFStructureElementKid( sal_Int32 nObj ) : nObject( nObj ), nMCID( -1 ) {}
+ PDFStructureElementKid( sal_Int32 MCID, sal_Int32 nPage ) : nObject( nPage ), nMCID( MCID ) {}
+};
+
+struct PDFStructureElement
+{
+ sal_Int32 m_nObject;
+ PDFWriter::StructElement m_eType;
+ 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::list< sal_Int32 > m_aChildren; // indexes into structure vector
+ std::list< PDFStructureElementKid > m_aKids;
+ std::map<PDFWriter::StructAttribute, PDFStructureAttribute >
+ m_aAttributes;
+ 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_eType( PDFWriter::NonStructElement ),
+ m_nOwnElement( -1 ),
+ m_nParentElement( -1 ),
+ m_nFirstPageObject( 0 ),
+ m_bOpenMCSeq( false )
+ {
+ }
+
+};
+
+struct PDFAddStream
+{
+ OUString m_aMimeType;
+ PDFOutputStream* m_pStream;
+ sal_Int32 m_nStreamObject;
+ bool m_bCompress;
+
+ PDFAddStream() : m_pStream( nullptr ), m_nStreamObject( 0 ), m_bCompress( true ) {}
+};
+
+// helper structure for drawLayout and friends
+struct PDFGlyph
+{
+ Point const m_aPos;
+ const GlyphItem* m_pGlyph;
+ sal_Int32 const m_nNativeWidth;
+ sal_Int32 const m_nMappedFontId;
+ sal_uInt8 const m_nMappedGlyphId;
+ int const m_nCharPos;
+
+ PDFGlyph( const Point& rPos,
+ const GlyphItem* pGlyph,
+ sal_Int32 nNativeWidth,
+ sal_Int32 nFontId,
+ sal_uInt8 nMappedGlyphId,
+ int nCharPos )
+ : m_aPos( rPos ), m_pGlyph(pGlyph), 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;
+ 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( ComplexTextLayoutFlags::Default ),
+ m_aDigitLanguage( 0 ),
+ m_nFlags( PushFlags::ALL ),
+ m_nUpdateFlags( GraphicsStateUpdateFlags::All )
+ {}
+};
+
+enum class Mode { DEFAULT, NOWRITE };
+
+}
+
+class PDFWriterImpl : public VirtualDevice
+{
+ friend class PDFStreamIf;
+
+public:
+ friend struct vcl::pdf::PDFPage;
+
+ static 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 );
+
+protected:
+ 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;
+
+private:
+ MapMode m_aMapMode; // PDFWriterImpl scaled units
+ 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;
+ /* 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;
+ /* 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;
+
+ /* 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::list< TransparencyEmit > m_aTransparentObjects;
+ /* contains all font subsets in use */
+ std::map<const PhysicalFontFace*, FontSubset> m_aSubsets;
+ std::map<const PhysicalFontFace*, EmbedFont> m_aSystemFonts;
+ sal_Int32 m_nNextFID;
+ PDFFontCache m_aFontCache;
+
+ /// 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;
+
+ /* 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::vector< PDFAddStream > m_aAdditionalStreams;
+ std::set< PDFWriter::ErrorCode > m_aErrors;
+
+ ::comphelper::Hash m_DocDigest;
+
+/*
+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 );
+
+ void disableStreamEncryption() { 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( const OUString& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer, rtl_TextEncoding nEnc = RTL_TEXTENCODING_ASCII_US );
+ void appendLiteralStringEncrypt( const OString& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer );
+ void appendLiteralStringEncrypt( OStringBuffer const & rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer );
+
+ /* creates fonts and subsets that will be emitted later */
+ void registerGlyph(const GlyphItem* pGlyph, const PhysicalFontFace* pFont, const std::vector<sal_Ucs>& rCodeUnits, sal_uInt8& nMappedGlyph, sal_Int32& nMappedFontObject);
+
+ /* 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, double fSkew, sal_Int32 nFontHeight );
+ void drawHorizontalGlyphs( const std::vector<PDFGlyph>& rGlyphs, OStringBuffer& rLine, const Point& rAlignOffset, bool bFirst, double fAngle, double fXScale, double fSkew, sal_Int32 nFontHeight, sal_Int32 nPixelFontHeight );
+ 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( BitmapEmit& rObject, bool bMask = false );
+
+ void writeJPG( JPGEmit& rEmit );
+ /// Writes the form XObject proxy for the image.
+ void writeReferenceXObject(ReferenceXObjectEmit& rEmit);
+ /// 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);
+
+ /* 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 );
+
+ /* 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 PhysicalFontFace*, EmbedFont const & );
+ /* writes a font descriptor and returns its object id (or 0) */
+ sal_Int32 emitFontDescriptor( const 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 sal_Ucs* pCodeUnits, const sal_Int32* pCodeUnitsPerGlyph,
+ const sal_Int32* pEncToUnicodeIndex, int 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 */
+ 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();
+ // 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();
+ // 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 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();
+ // emit additional streams collected; also create there object numbers
+ bool emitAdditionalStreams();
+ // 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 );
+ /* adds an entry to m_aObjects and returns its index+1,
+ * sets the offset to ~0
+ */
+ sal_Int32 createObject();
+ /* sets the offset of object n to the current position of output file+1
+ */
+ bool updateObject( sal_Int32 n );
+
+ bool writeBuffer( const void* pBuffer, sal_uInt64 nBytes );
+ void beginCompression();
+ void endCompression();
+ void beginRedirect( SvStream* pStream, const tools::Rectangle& );
+ SvStream* endRedirect();
+
+ void endPage();
+
+ void beginStructureElementMCSeq();
+ void endStructureElementMCSeq();
+ /** 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( long nX, 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( const OUString& 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,
+ 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( 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 && m_nCurrentPage < static_cast<sal_Int32>(m_aPages.size()) )
+ aSize = Size( m_aPages[ m_nCurrentPage ].m_nPageWidth, m_aPages[ m_nCurrentPage ].m_nPageHeight );
+ return aSize;
+ }
+
+ PDFWriter::PDFVersion getVersion() const { return m_aContext.Version; }
+
+ 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 = ImplIsColorTransparent(rColor) ? COL_TRANSPARENT : rColor;
+ m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::LineColor;
+ }
+
+ void setFillColor( const Color& rColor )
+ {
+ m_aGraphicsStack.front().m_aFillColor = ImplIsColorTransparent(rColor) ? 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( ImplIsColorTransparent( rColor ) );
+ 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( 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, const long* pDXArray, sal_Int32 nIndex, sal_Int32 nLen );
+ void drawStretchText( const Point& rPos, sal_uLong 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, long nWidth, FontStrikeout eStrikeout, FontLineStyle eUnderline, FontLineStyle eOverline, bool bUnderlineAbove );
+ void drawWaveTextLine( OStringBuffer& aLine, long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove );
+ void drawStraightTextLine( OStringBuffer& aLine, long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove );
+ void drawStrikeoutLine( OStringBuffer& aLine, long nWidth, FontStrikeout eStrikeout, Color aColor );
+ void drawStrikeoutChar( const Point& rPos, 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 Bitmap& rMask, 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 );
+ 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);
+ void setScreenURL(sal_Int32 nScreenId, const OUString& rURL);
+ void setScreenStream(sal_Int32 nScreenId, const OUString& rURL);
+
+ // outline
+ sal_Int32 createOutlineItem( sal_Int32 nParent, const OUString& rText, sal_Int32 nDestID );
+ void setOutlineItemParent( sal_Int32 nItem, sal_Int32 nNewParent );
+ void setOutlineItemText( sal_Int32 nItem, const OUString& 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 beginStructureElement( PDFWriter::StructElement eType, const OUString& rAlias );
+ 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 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 );
+
+ // additional streams
+ void addStream( const OUString& rMimeType, PDFOutputStream* pStream );
+
+ // helper: eventually begin marked content sequence and
+ // emit a comment in debug case
+ void MARK( const char* pString );
+};
+
+} // namespace vcl
+
+#endif //_VCL_PDFEXPORT_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/pdfwriter_impl2.cxx b/vcl/source/gdi/pdfwriter_impl2.cxx
new file mode 100644
index 000000000..443e51e3a
--- /dev/null
+++ b/vcl/source/gdi/pdfwriter_impl2.cxx
@@ -0,0 +1,1989 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "pdfwriter_impl.hxx"
+
+#include <vcl/pdfextoutdevdata.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/bitmapaccess.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 <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 <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;
+
+ i_pDummyVDev->AddGradientActions( i_rPolyPoly.GetBoundRect(), i_rGradient, 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 = aDstSizeTwip.Width() * i_rContext.m_nMaxImageResolution / 1440.0;
+ const double fMaxPixelY = aDstSizeTwip.Height() * i_rContext.m_nMaxImageResolution / 1440.0;
+
+ // 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() )
+ {
+ if( m_aContext.ColorMode == PDFWriter::DrawGreyscale )
+ {
+ BmpConversion eConv = BmpConversion::N8BitGreys;
+ int nDepth = aBitmapEx.GetBitmap().GetBitCount();
+ if( nDepth <= 4 )
+ eConv = BmpConversion::N4BitGreys;
+ if( nDepth > 1 )
+ aBitmapEx.Convert( eConv );
+ }
+ bool bUseJPGCompression = !i_rContext.m_bOnlyLosslessCompression;
+ if ( bIsPng || ( aSizePixel.Width() < 32 ) || ( aSizePixel.Height() < 32 ) )
+ bUseJPGCompression = false;
+
+ auto pStrm=std::make_shared<SvMemoryStream>();
+ Bitmap aMask;
+
+ bool bTrueColorJPG = true;
+ if ( bUseJPGCompression )
+ {
+ // TODO this checks could be done much earlier, saving us
+ // from trying conversion & stores before...
+ if ( !aBitmapEx.IsTransparent() )
+ {
+ const auto& rCacheEntry=m_aPDFBmpCache.find(
+ aBitmapEx.GetChecksum());
+ if ( rCacheEntry != m_aPDFBmpCache.end() )
+ {
+ m_rOuterFace.DrawJPGBitmap( *rCacheEntry->second, true, aSizePixel,
+ tools::Rectangle( aPoint, aSize ), aMask, 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.IsTransparent() )
+ {
+ if ( aBitmapEx.IsAlpha() )
+ aMask = aBitmapEx.GetAlpha().GetBitmap();
+ else
+ aMask = aBitmapEx.GetMask();
+ }
+ Graphic aGraphic( aBitmapEx.GetBitmap() );
+
+ Sequence< PropertyValue > aFilterData( 2 );
+ aFilterData[ 0 ].Name = "Quality";
+ aFilterData[ 0 ].Value <<= sal_Int32(i_rContext.m_nJPEGQuality);
+ aFilterData[ 1 ].Name = "ColorMode";
+ aFilterData[ 1 ].Value <<= 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( 3 );
+ aOutMediaProperties[0].Name = "OutputStream";
+ aOutMediaProperties[0].Value <<= xOut;
+ aOutMediaProperties[1].Name = "MimeType";
+ aOutMediaProperties[1].Value <<= OUString("image/jpeg");
+ aOutMediaProperties[2].Name = "FilterData";
+ aOutMediaProperties[2].Value <<= 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( 1 );
+ aArgs[ 0 ].Name = "InputStream";
+ aArgs[ 0 ].Value <<= 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 ), aMask, i_Graphic );
+ if (!aBitmapEx.IsTransparent() && bTrueColorJPG)
+ {
+ // Cache last jpeg export
+ m_aPDFBmpCache.insert(
+ {aBitmapEx.GetChecksum(), pStrm});
+ }
+ }
+ else if ( aBitmapEx.IsTransparent() )
+ 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();
+ 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 = static_cast<sal_Int32>(static_cast<double>(aDstSizeTwip.Width()) * static_cast<double>(nMaxBmpDPI) / 1440.0);
+ const sal_Int32 nPixelY = static_cast<sal_Int32>(static_cast<double>(aDstSizeTwip.Height()) * static_cast<double>(nMaxBmpDPI) / 1440.0);
+ if ( nPixelX && nPixelY )
+ {
+ Size aDstSizePixel( nPixelX, nPixelY );
+ ScopedVclPtrInstance<VirtualDevice> xVDev;
+ if( xVDev->SetOutputSizePixel( aDstSizePixel ) )
+ {
+ Bitmap aPaint, aMask;
+ AlphaMask aAlpha;
+ Point aPoint;
+
+ MapMode aMapMode( pDummyVDev->GetMapMode() );
+ aMapMode.SetOrigin( aPoint );
+ xVDev->SetMapMode( aMapMode );
+ Size aDstSize( xVDev->PixelToLogic( aDstSizePixel ) );
+
+ Point aMtfOrigin( aTmpMtf.GetPrefMapMode().GetOrigin() );
+ if ( aMtfOrigin.X() || aMtfOrigin.Y() )
+ aTmpMtf.Move( -aMtfOrigin.X(), -aMtfOrigin.Y() );
+ double fScaleX = static_cast<double>(aDstSize.Width()) / static_cast<double>(aTmpMtf.GetPrefSize().Width());
+ double fScaleY = static_cast<double>(aDstSize.Height()) / static_cast<double>(aTmpMtf.GetPrefSize().Height());
+ if( fScaleX != 1.0 || fScaleY != 1.0 )
+ aTmpMtf.Scale( fScaleX, fScaleY );
+ aTmpMtf.SetPrefMapMode( aMapMode );
+
+ // create paint bitmap
+ aTmpMtf.WindStart();
+ aTmpMtf.Play( xVDev.get(), aPoint, aDstSize );
+ aTmpMtf.WindStart();
+
+ xVDev->EnableMapMode( false );
+ aPaint = xVDev->GetBitmap( aPoint, aDstSizePixel );
+ xVDev->EnableMapMode();
+
+ // create mask bitmap
+ xVDev->SetLineColor( COL_BLACK );
+ xVDev->SetFillColor( COL_BLACK );
+ xVDev->DrawRect( tools::Rectangle( aPoint, aDstSize ) );
+ xVDev->SetDrawMode( DrawModeFlags::WhiteLine | DrawModeFlags::WhiteFill | DrawModeFlags::WhiteText |
+ DrawModeFlags::WhiteBitmap | DrawModeFlags::WhiteGradient );
+ aTmpMtf.WindStart();
+ aTmpMtf.Play( xVDev.get(), aPoint, aDstSize );
+ aTmpMtf.WindStart();
+ xVDev->EnableMapMode( false );
+ aMask = xVDev->GetBitmap( aPoint, aDstSizePixel );
+ xVDev->EnableMapMode();
+
+ // create alpha mask from gradient
+ xVDev->SetDrawMode( DrawModeFlags::GrayGradient );
+ xVDev->DrawGradient( tools::Rectangle( aPoint, aDstSize ), rTransparenceGradient );
+ xVDev->SetDrawMode( DrawModeFlags::Default );
+ xVDev->EnableMapMode( false );
+ xVDev->DrawMask( aPoint, aDstSizePixel, aMask, COL_WHITE );
+ aAlpha = xVDev->GetBitmap( aPoint, aDstSizePixel );
+
+ Graphic aGraphic = i_pOutDevData ? i_pOutDevData->GetCurrentGraphic() : Graphic();
+ implWriteBitmapEx( rPos, rSize, BitmapEx( aPaint, aAlpha ), aGraphic, pDummyVDev, i_rContext );
+ }
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::EPS:
+ {
+ const MetaEPSAction* pA = static_cast<const MetaEPSAction*>(pAction);
+ const GDIMetaFile& aSubstitute( pA->GetSubstitute() );
+
+ m_rOuterFace.Push();
+ pDummyVDev->Push();
+
+ MapMode aMapMode( aSubstitute.GetPrefMapMode() );
+ Size aOutSize( OutputDevice::LogicToLogic( pA->GetSize(), pDummyVDev->GetMapMode(), aMapMode ) );
+ aMapMode.SetScaleX( Fraction( aOutSize.Width(), aSubstitute.GetPrefSize().Width() ) );
+ aMapMode.SetScaleY( Fraction( aOutSize.Height(), aSubstitute.GetPrefSize().Height() ) );
+ aMapMode.SetOrigin( OutputDevice::LogicToLogic( pA->GetPoint(), pDummyVDev->GetMapMode(), aMapMode ) );
+
+ m_rOuterFace.SetMapMode( aMapMode );
+ pDummyVDev->SetMapMode( aMapMode );
+ playMetafile( aSubstitute, nullptr, i_rContext, pDummyVDev );
+ pDummyVDev->Pop();
+ m_rOuterFace.Pop();
+ }
+ break;
+
+ case MetaActionType::COMMENT:
+ if( ! i_rContext.m_bTransparenciesWereRemoved )
+ {
+ const MetaCommentAction* pA = static_cast<const MetaCommentAction*>(pAction);
+
+ if( pA->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_BEGIN"))
+ {
+ const MetaGradientExAction* pGradAction = nullptr;
+ bool bDone = false;
+
+ while( !bDone && ( ++i < nCount ) )
+ {
+ pAction = aMtf.GetAction( i );
+
+ if( pAction->GetType() == MetaActionType::GRADIENTEX )
+ pGradAction = static_cast<const MetaGradientExAction*>(pAction);
+ else if( ( pAction->GetType() == MetaActionType::COMMENT ) &&
+ ( static_cast<const MetaCommentAction*>(pAction)->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_END")) )
+ {
+ bDone = true;
+ }
+ }
+
+ if( pGradAction )
+ {
+ if (lcl_canUsePDFAxialShading(pGradAction->GetGradient()))
+ {
+ m_rOuterFace.DrawGradient( pGradAction->GetPolyPolygon(), pGradAction->GetGradient() );
+ }
+ else
+ {
+ implWriteGradient( pGradAction->GetPolyPolygon(), pGradAction->GetGradient(), pDummyVDev, i_rContext );
+ }
+ }
+ }
+ else
+ {
+ const sal_uInt8* pData = pA->GetData();
+ if ( pData )
+ {
+ SvMemoryStream aMemStm( const_cast<sal_uInt8 *>(pData), pA->GetDataSize(), StreamMode::READ );
+ bool bSkipSequence = false;
+ OString sSeqEnd;
+
+ if( pA->GetComment() == "XPATHSTROKE_SEQ_BEGIN" )
+ {
+ sSeqEnd = OString("XPATHSTROKE_SEQ_END");
+ SvtGraphicStroke aStroke;
+ ReadSvtGraphicStroke( aMemStm, aStroke );
+
+ tools::Polygon aPath;
+ aStroke.getPath( aPath );
+
+ tools::PolyPolygon aStartArrow;
+ tools::PolyPolygon aEndArrow;
+ double fTransparency( aStroke.getTransparency() );
+ double fStrokeWidth( aStroke.getStrokeWidth() );
+ SvtGraphicStroke::DashArray aDashArray;
+
+ aStroke.getStartArrow( aStartArrow );
+ aStroke.getEndArrow( aEndArrow );
+ aStroke.getDashArray( aDashArray );
+
+ bSkipSequence = true;
+ if ( aStartArrow.Count() || aEndArrow.Count() )
+ bSkipSequence = false;
+ if ( !aDashArray.empty() && ( fStrokeWidth != 0.0 ) && ( fTransparency == 0.0 ) )
+ bSkipSequence = false;
+ if ( bSkipSequence )
+ {
+ PDFWriter::ExtLineInfo aInfo;
+ aInfo.m_fLineWidth = fStrokeWidth;
+ aInfo.m_fTransparency = fTransparency;
+ aInfo.m_fMiterLimit = aStroke.getMiterLimit();
+ switch( aStroke.getCapType() )
+ {
+ default:
+ case SvtGraphicStroke::capButt: aInfo.m_eCap = PDFWriter::capButt;break;
+ case SvtGraphicStroke::capRound: aInfo.m_eCap = PDFWriter::capRound;break;
+ case SvtGraphicStroke::capSquare: aInfo.m_eCap = PDFWriter::capSquare;break;
+ }
+ switch( aStroke.getJoinType() )
+ {
+ default:
+ case SvtGraphicStroke::joinMiter: aInfo.m_eJoin = PDFWriter::joinMiter;break;
+ case SvtGraphicStroke::joinRound: aInfo.m_eJoin = PDFWriter::joinRound;break;
+ case SvtGraphicStroke::joinBevel: aInfo.m_eJoin = PDFWriter::joinBevel;break;
+ case SvtGraphicStroke::joinNone:
+ aInfo.m_eJoin = PDFWriter::joinMiter;
+ aInfo.m_fMiterLimit = 0.0;
+ break;
+ }
+ aInfo.m_aDashArray = aDashArray;
+
+ if(SvtGraphicStroke::joinNone == aStroke.getJoinType()
+ && fStrokeWidth > 0.0)
+ {
+ // emulate no edge rounding by handling single edges
+ const sal_uInt16 nPoints(aPath.GetSize());
+ const bool bCurve(aPath.HasFlags());
+
+ for(sal_uInt16 a(0); a + 1 < nPoints; a++)
+ {
+ if(bCurve
+ && PolyFlags::Normal != aPath.GetFlags(a + 1)
+ && a + 2 < nPoints
+ && PolyFlags::Normal != aPath.GetFlags(a + 2)
+ && a + 3 < nPoints)
+ {
+ const tools::Polygon aSnippet(4,
+ aPath.GetConstPointAry() + a,
+ aPath.GetConstFlagAry() + a);
+ m_rOuterFace.DrawPolyLine( aSnippet, aInfo );
+ a += 2;
+ }
+ else
+ {
+ const tools::Polygon aSnippet(2,
+ aPath.GetConstPointAry() + a);
+ m_rOuterFace.DrawPolyLine( aSnippet, aInfo );
+ }
+ }
+ }
+ else
+ {
+ m_rOuterFace.DrawPolyLine( aPath, aInfo );
+ }
+ }
+ }
+ else if ( pA->GetComment() == "XPATHFILL_SEQ_BEGIN" )
+ {
+ sSeqEnd = OString("XPATHFILL_SEQ_END");
+ SvtGraphicFill aFill;
+ ReadSvtGraphicFill( aMemStm, aFill );
+
+ if ( ( aFill.getFillType() == SvtGraphicFill::fillSolid ) && ( aFill.getFillRule() == SvtGraphicFill::fillEvenOdd ) )
+ {
+ double fTransparency = aFill.getTransparency();
+ if ( fTransparency == 0.0 )
+ {
+ tools::PolyPolygon aPath;
+ aFill.getPath( aPath );
+
+ bSkipSequence = true;
+ m_rOuterFace.DrawPolyPolygon( aPath );
+ }
+ else if ( fTransparency == 1.0 )
+ bSkipSequence = true;
+ }
+ }
+ if ( bSkipSequence )
+ {
+ while( ++i < nCount )
+ {
+ pAction = aMtf.GetAction( i );
+ if ( pAction->GetType() == MetaActionType::COMMENT )
+ {
+ OString sComment( static_cast<const MetaCommentAction*>(pAction)->GetComment() );
+ if (sComment == sSeqEnd)
+ break;
+ }
+ // #i44496#
+ // the replacement action for stroke is a filled rectangle
+ // the set fillcolor of the replacement is part of the graphics
+ // state and must not be skipped
+ else if( pAction->GetType() == MetaActionType::FILLCOLOR )
+ {
+ const MetaFillColorAction* pMA = static_cast<const MetaFillColorAction*>(pAction);
+ if( pMA->IsSetting() )
+ m_rOuterFace.SetFillColor( pMA->GetColor() );
+ else
+ m_rOuterFace.SetFillColor();
+ }
+ }
+ }
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::BMP:
+ {
+ const MetaBmpAction* pA = static_cast<const MetaBmpAction*>(pAction);
+ BitmapEx aBitmapEx( pA->GetBitmap() );
+ Size aSize( OutputDevice::LogicToLogic( aBitmapEx.GetPrefSize(),
+ aBitmapEx.GetPrefMapMode(), pDummyVDev->GetMapMode() ) );
+ if( ! ( aSize.Width() && aSize.Height() ) )
+ aSize = pDummyVDev->PixelToLogic( aBitmapEx.GetSizePixel() );
+
+ Graphic aGraphic = i_pOutDevData ? i_pOutDevData->GetCurrentGraphic() : Graphic();
+ implWriteBitmapEx( pA->GetPoint(), aSize, aBitmapEx, aGraphic, pDummyVDev, i_rContext );
+ }
+ break;
+
+ case MetaActionType::BMPSCALE:
+ {
+ const MetaBmpScaleAction* pA = static_cast<const MetaBmpScaleAction*>(pAction);
+ Graphic aGraphic = i_pOutDevData ? i_pOutDevData->GetCurrentGraphic() : Graphic();
+ implWriteBitmapEx( pA->GetPoint(), pA->GetSize(), BitmapEx( pA->GetBitmap() ), aGraphic, pDummyVDev, i_rContext );
+ }
+ break;
+
+ case MetaActionType::BMPSCALEPART:
+ {
+ const MetaBmpScalePartAction* pA = static_cast<const MetaBmpScalePartAction*>(pAction);
+ BitmapEx aBitmapEx( pA->GetBitmap() );
+ aBitmapEx.Crop( tools::Rectangle( pA->GetSrcPoint(), pA->GetSrcSize() ) );
+ Graphic aGraphic = i_pOutDevData ? i_pOutDevData->GetCurrentGraphic() : Graphic();
+ implWriteBitmapEx( pA->GetDestPoint(), pA->GetDestSize(), aBitmapEx, aGraphic, pDummyVDev, i_rContext );
+ }
+ break;
+
+ case MetaActionType::BMPEX:
+ {
+ const MetaBmpExAction* pA = static_cast<const MetaBmpExAction*>(pAction);
+ const BitmapEx& aBitmapEx( pA->GetBitmapEx() );
+ Size aSize( OutputDevice::LogicToLogic( aBitmapEx.GetPrefSize(),
+ aBitmapEx.GetPrefMapMode(), pDummyVDev->GetMapMode() ) );
+ Graphic aGraphic = i_pOutDevData ? i_pOutDevData->GetCurrentGraphic() : Graphic();
+ implWriteBitmapEx( pA->GetPoint(), aSize, aBitmapEx, aGraphic, pDummyVDev, i_rContext );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ {
+ const MetaBmpExScaleAction* pA = static_cast<const MetaBmpExScaleAction*>(pAction);
+ Graphic aGraphic = i_pOutDevData ? i_pOutDevData->GetCurrentGraphic() : Graphic();
+ implWriteBitmapEx( pA->GetPoint(), pA->GetSize(), pA->GetBitmapEx(), aGraphic, pDummyVDev, i_rContext );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ {
+ const MetaBmpExScalePartAction* pA = static_cast<const MetaBmpExScalePartAction*>(pAction);
+ BitmapEx aBitmapEx( pA->GetBitmapEx() );
+ aBitmapEx.Crop( tools::Rectangle( pA->GetSrcPoint(), pA->GetSrcSize() ) );
+ Graphic aGraphic = i_pOutDevData ? i_pOutDevData->GetCurrentGraphic() : Graphic();
+ implWriteBitmapEx( pA->GetDestPoint(), pA->GetDestSize(), aBitmapEx, aGraphic, pDummyVDev, i_rContext );
+ }
+ break;
+
+ case MetaActionType::MASK:
+ case MetaActionType::MASKSCALE:
+ case MetaActionType::MASKSCALEPART:
+ {
+ SAL_WARN( "vcl", "MetaMask...Action not supported yet" );
+ }
+ break;
+
+ case MetaActionType::TEXT:
+ {
+ const MetaTextAction* pA = static_cast<const MetaTextAction*>(pAction);
+ m_rOuterFace.DrawText( pA->GetPoint(), pA->GetText().copy( pA->GetIndex(), std::min<sal_Int32>(pA->GetText().getLength() - pA->GetIndex(), pA->GetLen()) ) );
+ }
+ break;
+
+ case MetaActionType::TEXTRECT:
+ {
+ const MetaTextRectAction* pA = static_cast<const MetaTextRectAction*>(pAction);
+ m_rOuterFace.DrawText( pA->GetRect(), pA->GetText(), pA->GetStyle() );
+ }
+ break;
+
+ case MetaActionType::TEXTARRAY:
+ {
+ const MetaTextArrayAction* pA = static_cast<const MetaTextArrayAction*>(pAction);
+ m_rOuterFace.DrawTextArray( pA->GetPoint(), pA->GetText(), pA->GetDXArray(), pA->GetIndex(), pA->GetLen() );
+ }
+ break;
+
+ case MetaActionType::STRETCHTEXT:
+ {
+ const MetaStretchTextAction* pA = static_cast<const MetaStretchTextAction*>(pAction);
+ m_rOuterFace.DrawStretchText( pA->GetPoint(), pA->GetWidth(), pA->GetText(), pA->GetIndex(), pA->GetLen() );
+ }
+ break;
+
+ case MetaActionType::TEXTLINE:
+ {
+ const MetaTextLineAction* pA = static_cast<const MetaTextLineAction*>(pAction);
+ m_rOuterFace.DrawTextLine( pA->GetStartPoint(), pA->GetWidth(), pA->GetStrikeout(), pA->GetUnderline(), pA->GetOverline() );
+
+ }
+ break;
+
+ case MetaActionType::CLIPREGION:
+ {
+ const MetaClipRegionAction* pA = static_cast<const MetaClipRegionAction*>(pAction);
+
+ if( pA->IsClipping() )
+ {
+ if( pA->GetRegion().IsEmpty() )
+ m_rOuterFace.SetClipRegion( basegfx::B2DPolyPolygon() );
+ else
+ {
+ const vcl::Region& aReg( pA->GetRegion() );
+ m_rOuterFace.SetClipRegion( aReg.GetAsB2DPolyPolygon() );
+ }
+ }
+ else
+ m_rOuterFace.SetClipRegion();
+ }
+ break;
+
+ case MetaActionType::ISECTRECTCLIPREGION:
+ {
+ const MetaISectRectClipRegionAction* pA = static_cast<const MetaISectRectClipRegionAction*>(pAction);
+ m_rOuterFace.IntersectClipRegion( pA->GetRect() );
+ }
+ break;
+
+ case MetaActionType::ISECTREGIONCLIPREGION:
+ {
+ const MetaISectRegionClipRegionAction* pA = static_cast<const MetaISectRegionClipRegionAction*>(pAction);
+ const vcl::Region& aReg( pA->GetRegion() );
+ m_rOuterFace.IntersectClipRegion( aReg.GetAsB2DPolyPolygon() );
+ }
+ break;
+
+ case MetaActionType::MOVECLIPREGION:
+ {
+ const MetaMoveClipRegionAction* pA = static_cast<const MetaMoveClipRegionAction*>(pAction);
+ m_rOuterFace.MoveClipRegion( pA->GetHorzMove(), pA->GetVertMove() );
+ }
+ break;
+
+ case MetaActionType::MAPMODE:
+ {
+ const_cast< MetaAction* >( pAction )->Execute( pDummyVDev );
+ m_rOuterFace.SetMapMode( pDummyVDev->GetMapMode() );
+ }
+ break;
+
+ case MetaActionType::LINECOLOR:
+ {
+ const MetaLineColorAction* pA = static_cast<const MetaLineColorAction*>(pAction);
+
+ if( pA->IsSetting() )
+ m_rOuterFace.SetLineColor( pA->GetColor() );
+ else
+ m_rOuterFace.SetLineColor();
+ }
+ break;
+
+ case MetaActionType::FILLCOLOR:
+ {
+ const MetaFillColorAction* pA = static_cast<const MetaFillColorAction*>(pAction);
+
+ if( pA->IsSetting() )
+ m_rOuterFace.SetFillColor( pA->GetColor() );
+ else
+ m_rOuterFace.SetFillColor();
+ }
+ break;
+
+ case MetaActionType::TEXTLINECOLOR:
+ {
+ const MetaTextLineColorAction* pA = static_cast<const MetaTextLineColorAction*>(pAction);
+
+ if( pA->IsSetting() )
+ m_rOuterFace.SetTextLineColor( pA->GetColor() );
+ else
+ m_rOuterFace.SetTextLineColor();
+ }
+ break;
+
+ case MetaActionType::OVERLINECOLOR:
+ {
+ const MetaOverlineColorAction* pA = static_cast<const MetaOverlineColorAction*>(pAction);
+
+ if( pA->IsSetting() )
+ m_rOuterFace.SetOverlineColor( pA->GetColor() );
+ else
+ m_rOuterFace.SetOverlineColor();
+ }
+ break;
+
+ case MetaActionType::TEXTFILLCOLOR:
+ {
+ const MetaTextFillColorAction* pA = static_cast<const MetaTextFillColorAction*>(pAction);
+
+ if( pA->IsSetting() )
+ m_rOuterFace.SetTextFillColor( pA->GetColor() );
+ else
+ m_rOuterFace.SetTextFillColor();
+ }
+ break;
+
+ case MetaActionType::TEXTCOLOR:
+ {
+ const MetaTextColorAction* pA = static_cast<const MetaTextColorAction*>(pAction);
+ m_rOuterFace.SetTextColor( pA->GetColor() );
+ }
+ break;
+
+ case MetaActionType::TEXTALIGN:
+ {
+ const MetaTextAlignAction* pA = static_cast<const MetaTextAlignAction*>(pAction);
+ m_rOuterFace.SetTextAlign( pA->GetTextAlign() );
+ }
+ break;
+
+ case MetaActionType::FONT:
+ {
+ const MetaFontAction* pA = static_cast<const MetaFontAction*>(pAction);
+ m_rOuterFace.SetFont( pA->GetFont() );
+ }
+ break;
+
+ case MetaActionType::PUSH:
+ {
+ const MetaPushAction* pA = static_cast<const MetaPushAction*>(pAction);
+
+ pDummyVDev->Push( pA->GetFlags() );
+ m_rOuterFace.Push( pA->GetFlags() );
+ }
+ break;
+
+ case MetaActionType::POP:
+ {
+ pDummyVDev->Pop();
+ m_rOuterFace.Pop();
+ }
+ break;
+
+ case MetaActionType::LAYOUTMODE:
+ {
+ const MetaLayoutModeAction* pA = static_cast<const MetaLayoutModeAction*>(pAction);
+ m_rOuterFace.SetLayoutMode( pA->GetLayoutMode() );
+ }
+ break;
+
+ case MetaActionType::TEXTLANGUAGE:
+ {
+ const MetaTextLanguageAction* pA = static_cast<const MetaTextLanguageAction*>(pAction);
+ m_rOuterFace.SetDigitLanguage( pA->GetTextLanguage() );
+ }
+ break;
+
+ case MetaActionType::WALLPAPER:
+ {
+ const MetaWallpaperAction* pA = static_cast<const MetaWallpaperAction*>(pAction);
+ m_rOuterFace.DrawWallpaper( pA->GetRect(), pA->GetWallpaper() );
+ }
+ break;
+
+ case MetaActionType::RASTEROP:
+ {
+ // !!! >>> we don't want to support this actions
+ }
+ break;
+
+ case MetaActionType::REFPOINT:
+ {
+ // !!! >>> we don't want to support this actions
+ }
+ break;
+
+ default:
+ // #i24604# Made assertion fire only once per
+ // metafile. The asserted actions here are all
+ // deprecated
+ if( !bAssertionFired )
+ {
+ bAssertionFired = true;
+ SAL_WARN( "vcl", "PDFExport::ImplWriteActions: deprecated and unsupported MetaAction encountered " << static_cast<int>(nType) );
+ }
+ break;
+ }
+ i++;
+ }
+ }
+}
+
+// Encryption methods
+
+/* a crutch to transport a ::comphelper::Hash safely though UNO API
+ this is needed for the PDF export dialog, which otherwise would have to pass
+ clear text passwords down till they can be used in PDFWriter. Unfortunately
+ the MD5 sum of the password (which is needed to create the PDF encryption key)
+ is not sufficient, since an MD5 digest cannot be created in an arbitrary state
+ which would be needed in PDFWriterImpl::computeEncryptionKey.
+*/
+class EncHashTransporter : public cppu::WeakImplHelper < css::beans::XMaterialHolder >
+{
+ ::std::unique_ptr<::comphelper::Hash> m_pDigest;
+ sal_IntPtr maID;
+ std::vector< sal_uInt8 > maOValue;
+
+ static std::map< sal_IntPtr, EncHashTransporter* > sTransporters;
+public:
+ EncHashTransporter()
+ : m_pDigest(new ::comphelper::Hash(::comphelper::HashType::MD5))
+ {
+ maID = reinterpret_cast< sal_IntPtr >(this);
+ while( sTransporters.find( maID ) != sTransporters.end() ) // paranoia mode
+ maID++;
+ sTransporters[ maID ] = this;
+ }
+
+ virtual ~EncHashTransporter() override
+ {
+ sTransporters.erase( maID );
+ SAL_INFO( "vcl", "EncHashTransporter freed" );
+ }
+
+ ::comphelper::Hash* getUDigest() { return m_pDigest.get(); };
+ std::vector< sal_uInt8 >& getOValue() { return maOValue; }
+ void invalidate()
+ {
+ m_pDigest.reset();
+ }
+
+ // XMaterialHolder
+ virtual uno::Any SAL_CALL getMaterial() override
+ {
+ return uno::makeAny( 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() )
+ {
+ 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() )
+ {
+ 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() )
+ {
+ 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( const OUString& 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 */
+
+static const 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 */
+};
+
+static const 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, long i_nIndex )
+{
+ return (i_pLine[ i_nIndex/8 ] & (0x80 >> (i_nIndex&7))) != 0;
+}
+
+static long findBitRunImpl( const Scanline i_pLine, long i_nStartIndex, long i_nW, bool i_bSet )
+{
+ 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
+ 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 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 long findBitRun(const Scanline i_pLine, long i_nStartIndex, long i_nW, bool i_bSet)
+{
+ if (i_nStartIndex < 0)
+ return i_nW;
+
+ return findBitRunImpl(i_pLine, i_nStartIndex, i_nW, i_bSet);
+}
+
+static long findBitRun(const Scanline i_pLine, long i_nStartIndex, long i_nW)
+{
+ if (i_nStartIndex < 0)
+ return i_nW;
+
+ const bool bSet = i_nStartIndex < i_nW && isSet(i_pLine, i_nStartIndex);
+
+ return findBitRunImpl(i_pLine, i_nStartIndex, i_nW, bSet);
+}
+
+struct BitStreamState
+{
+ sal_uInt8 mnBuffer;
+ sal_uInt32 mnNextBitPos;
+
+ BitStreamState()
+ : mnBuffer( 0 )
+ , mnNextBitPos( 8 )
+ {
+ }
+
+ const sal_uInt8& getByte() const { return mnBuffer; }
+ void flush() { mnNextBitPos = 8; mnBuffer = 0; }
+};
+
+void PDFWriterImpl::putG4Bits( sal_uInt32 i_nLength, sal_uInt32 i_nCode, BitStreamState& io_rState )
+{
+ while( i_nLength > io_rState.mnNextBitPos )
+ {
+ io_rState.mnBuffer |= static_cast<sal_uInt8>( i_nCode >> (i_nLength - io_rState.mnNextBitPos) );
+ i_nLength -= io_rState.mnNextBitPos;
+ writeBuffer( &io_rState.getByte(), 1 );
+ io_rState.flush();
+ }
+ assert(i_nLength < 9);
+ static const unsigned int msbmask[9] = { 0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff };
+ io_rState.mnBuffer |= static_cast<sal_uInt8>( (i_nCode & msbmask[i_nLength]) << (io_rState.mnNextBitPos - i_nLength) );
+ io_rState.mnNextBitPos -= i_nLength;
+ if( io_rState.mnNextBitPos == 0 )
+ {
+ writeBuffer( &io_rState.getByte(), 1 );
+ io_rState.flush();
+ }
+}
+
+namespace {
+
+struct PixelCode
+{
+ sal_uInt32 mnEncodedPixels;
+ sal_uInt32 mnCodeBits;
+ sal_uInt32 mnCode;
+};
+
+}
+
+static 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
+};
+
+static 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( 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 )
+{
+ long nW = i_pBitmap->Width();
+ 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( long nY = 0; nY < nH; nY++ )
+ {
+ const Scanline pCurLine = i_pBitmap->GetScanline( nY );
+ long nLineIndex = 0;
+ bool bRunSet = (*pCurLine & 0x80) != 0;
+ bool bRefSet = (*pRefLine & 0x80) != 0;
+ long nRunIndex1 = bRunSet ? 0 : findBitRun( pCurLine, 0, nW, bRunSet );
+ long nRefIndex1 = bRefSet ? 0 : findBitRun( pRefLine, 0, nW, bRefSet );
+ for( ; nLineIndex < nW; )
+ {
+ long nRefIndex2 = findBitRun( pRefLine, nRefIndex1, nW );
+ if( nRefIndex2 >= nRunIndex1 )
+ {
+ 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 );
+ long nRunIndex2 = findBitRun( pCurLine, nRunIndex1, nW );
+ bool bWhiteFirst = ( nLineIndex + nRunIndex1 == 0 || ! isSet( pCurLine, nLineIndex ) );
+ putG4Span( nRunIndex1 - nLineIndex, bWhiteFirst, aBitState );
+ putG4Span( nRunIndex2 - nRunIndex1, ! bWhiteFirst, aBitState );
+ nLineIndex = nRunIndex2;
+ }
+ }
+ else
+ { // emit pass code 0001
+ putG4Bits( 4, 0x1, aBitState );
+ nLineIndex = nRefIndex2;
+ }
+ if( nLineIndex < nW )
+ {
+ bool bSet = isSet( pCurLine, nLineIndex );
+ nRunIndex1 = findBitRun( pCurLine, nLineIndex, nW, bSet );
+ nRefIndex1 = findBitRun( pRefLine, nLineIndex, nW, ! bSet );
+ nRefIndex1 = findBitRun( pRefLine, nRefIndex1, nW, bSet );
+ }
+ }
+
+ // the current line is the reference for the next line
+ pRefLine = pCurLine;
+ }
+ // terminate strip with EOFB
+ putG4Bits( 12, 1, aBitState );
+ putG4Bits( 12, 1, aBitState );
+ if( aBitState.mnNextBitPos != 8 )
+ {
+ writeBuffer( &aBitState.getByte(), 1 );
+ aBitState.flush();
+ }
+}
+
+void PDFWriterImpl::DrawHatchLine_DrawLine(const Point& rStartPoint, const Point& rEndPoint)
+{
+ drawLine(rStartPoint, rEndPoint);
+}
+
+static bool lcl_canUsePDFAxialShading(const Gradient& rGradient) {
+ switch (rGradient.GetStyle())
+ {
+ case GradientStyle::Linear:
+ case GradientStyle::Axial:
+ break;
+ default:
+ return false;
+ }
+
+ // TODO: handle step count
+ return rGradient.GetSteps() <= 0;
+}
+
+void PDFWriterImpl::ImplClearFontData(bool bNewFontLists)
+{
+ VirtualDevice::ImplClearFontData(bNewFontLists);
+ if (bNewFontLists && AcquireGraphics())
+ {
+ ReleaseFontCollection();
+ ReleaseFontCache();
+ }
+}
+
+void PDFWriterImpl::ImplRefreshFontData(bool bNewFontLists)
+{
+ if (bNewFontLists && AcquireGraphics())
+ {
+ SetFontCollectionFromSVData();
+ ResetNewFontCache();
+ }
+}
+
+vcl::Region PDFWriterImpl::ClipToDeviceBounds(vcl::Region aRegion) const
+{
+ return aRegion;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/print.cxx b/vcl/source/gdi/print.cxx
new file mode 100644
index 000000000..e6386ef17
--- /dev/null
+++ b/vcl/source/gdi/print.cxx
@@ -0,0 +1,1639 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <tools/helpers.hxx>
+#include <tools/debug.hxx>
+
+#include <vcl/event.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/print.hxx>
+
+#include <comphelper/processfactory.hxx>
+
+#include <salinst.hxx>
+#include <salvd.hxx>
+#include <salgdi.hxx>
+#include <salptype.hxx>
+#include <salprn.hxx>
+#include <svdata.hxx>
+#include <print.hrc>
+#include <jobset.h>
+#include <outdev.h>
+#include <PhysicalFontCollection.hxx>
+#include <print.h>
+
+#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( long nWidth100thMM, 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);
+ }
+}
+
+// PrinterOptions
+PrinterOptions::PrinterOptions() :
+ mbReduceTransparency( false ),
+ meReducedTransparencyMode( PrinterTransparencyMode::Auto ),
+ mbReduceGradients( false ),
+ meReducedGradientsMode( PrinterGradientMode::Stripes ),
+ mnReducedGradientStepCount( 64 ),
+ mbReduceBitmaps( false ),
+ meReducedBitmapMode( PrinterBitmapMode::Normal ),
+ mnReducedBitmapResolution( 200 ),
+ mbReducedBitmapsIncludeTransparency( true ),
+ mbConvertToGreyscales( false ),
+ mbPDFAsStandardPrintJobFormat( false )
+{
+}
+
+void PrinterOptions::ReadFromConfig( bool i_bFile )
+{
+ bool bSuccess = false;
+ // save old state in case something goes wrong
+ PrinterOptions 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::uno::Sequence< css::uno::Any > aArgs(1);
+ 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" );
+ aArgs.getArray()[0] <<= aVal;
+ xConfigAccess.set(
+ xConfigProvider->createInstanceWithArguments(
+ "com.sun.star.configuration.ConfigurationAccess", aArgs ),
+ 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<PrinterTransparencyMode>(nValue) );
+ if( xSet->getPropertyValue("ReduceGradients") >>= bValue )
+ SetReduceGradients( bValue );
+ if( xSet->getPropertyValue("ReducedGradientMode") >>= nValue )
+ SetReducedGradientMode( static_cast<PrinterGradientMode>(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<PrinterBitmapMode>(nValue) );
+ if( xSet->getPropertyValue("ReducedBitmapResolution") >>= nValue )
+ SetReducedBitmapResolution( static_cast<sal_uInt16>(nValue) );
+ if( xSet->getPropertyValue("ReducedBitmapIncludesTransparency") >>= bValue )
+ SetReducedBitmapIncludesTransparency( bValue );
+ if( xSet->getPropertyValue("ConvertToGreyscales") >>= bValue )
+ SetConvertToGreyscales( bValue );
+ if( xSet->getPropertyValue("PDFAsStandardPrintJobFormat") >>= bValue )
+ SetPDFAsStandardPrintJobFormat( bValue );
+
+ bSuccess = true;
+ }
+ }
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+ }
+ catch( const css::lang::WrappedTargetException& )
+ {
+ }
+
+ if( ! bSuccess )
+ *this = aOldValues;
+}
+
+bool Printer::DrawTransformBitmapExDirect(
+ const basegfx::B2DHomMatrix& /*aFullTransform*/,
+ const BitmapEx& /*rBitmapEx*/)
+{
+ // 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::DrawDeviceBitmap( const Point& rDestPt, const Size& rDestSize,
+ const Point& rSrcPtPixel, const Size& rSrcSizePixel,
+ BitmapEx& rBmpEx )
+{
+ if( rBmpEx.IsAlpha() )
+ {
+ // #107169# For true alpha bitmaps, no longer masking the
+ // bitmap, but perform a full alpha blend against a white
+ // background here.
+ Bitmap aBmp( rBmpEx.GetBitmap() );
+ aBmp.Blend( rBmpEx.GetAlpha(), COL_WHITE );
+ DrawBitmap( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, aBmp );
+ }
+ else
+ {
+ Bitmap aBmp( rBmpEx.GetBitmap() ), aMask( rBmpEx.GetMask() );
+ aBmp.Replace( aMask, COL_WHITE );
+ ImplPrintTransparent( aBmp, aMask, 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 long nBaseExtent = std::max( FRound( aDPISize.Width() / 300. ), 1L );
+ 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( PushFlags::CLIPREGION | 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 PrinterOptions& i_rOptions )
+{
+ *mpPrinterOptions = i_rOptions;
+}
+
+bool Printer::HasMirroredGraphics() const
+{
+ // due to a "hotfix" for AOO bug i55719, this needs to return false
+ return false;
+}
+
+// QueueInfo
+QueueInfo::QueueInfo()
+{
+ mnStatus = PrintQueueFlags::NONE;
+ mnJobs = 0;
+}
+
+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();
+ 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;
+ mpInfoPrinter = nullptr;
+ mpPrinter = nullptr;
+ mpDisplayDev = nullptr;
+ mpPrinterOptions.reset(new PrinterOptions);
+
+ // 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->setAntiAliasB2DDraw(bool(mnAntialiasing & AntialiasingFlags::EnableB2dDraw));
+ }
+
+ return mpGraphics != nullptr;
+}
+
+void Printer::ImplReleaseFonts()
+{
+#ifdef UNX
+ // HACK to fix an urgent P1 printing issue fast
+ // WinSalPrinter does not respect GetGraphics/ReleaseGraphics conventions
+ // so Printer::mpGraphics often points to a dead WinSalGraphics
+ // TODO: fix WinSalPrinter's GetGraphics/ReleaseGraphics handling
+ mpGraphics->ReleaseFonts();
+#endif
+ mbNewFont = true;
+ mbInitFont = true;
+
+ mpFontInstance.clear();
+ mpDeviceFontList.reset();
+ mpDeviceFontSizeList.reset();
+}
+
+void Printer::ReleaseGraphics( 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 = mpNextGraphics;
+ if ( mpNextGraphics )
+ mpNextGraphics->mpPrevGraphics = mpPrevGraphics;
+ else
+ pSVData->maGDIData.mpLastPrnGraphics = mpPrevGraphics;
+ }
+ }
+
+ mpGraphics = nullptr;
+ mpPrevGraphics = nullptr;
+ mpNextGraphics = nullptr;
+}
+
+void Printer::ImplInit( SalPrinterQueueInfo* pInfo )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ // #i74084# update info for this specific SalPrinterQueueInfo
+ pSVData->mpDefInst->GetPrinterQueueState( pInfo );
+
+ // Test whether the driver actually matches the JobSetup
+ ImplJobSetup& rData = maJobSetup.ImplGetData();
+ if ( rData.GetDriverData() )
+ {
+ if ( rData.GetPrinterName() != pInfo->maPrinterName ||
+ rData.GetDriver() != pInfo->maDriver )
+ {
+ std::free( const_cast<sal_uInt8*>(rData.GetDriverData()) );
+ rData.SetDriverData(nullptr);
+ rData.SetDriverDataLen(0);
+ }
+ }
+
+ // Remember printer name
+ maPrinterName = pInfo->maPrinterName;
+ maDriver = pInfo->maDriver;
+
+ // Add printer name to JobSetup
+ rData.SetPrinterName( maPrinterName );
+ rData.SetDriver( maDriver );
+
+ mpInfoPrinter = pSVData->mpDefInst->CreateInfoPrinter( pInfo, &rData );
+ mpPrinter = nullptr;
+ mpJobGraphics = nullptr;
+ ImplUpdateJobSetupPaper( maJobSetup );
+
+ if ( !mpInfoPrinter )
+ {
+ ImplInitDisplay();
+ return;
+ }
+
+ // we need a graphics
+ if ( !AcquireGraphics() )
+ {
+ ImplInitDisplay();
+ return;
+ }
+
+ // Init data
+ ImplUpdatePageData();
+ mxFontCollection = std::make_shared<PhysicalFontCollection>();
+ mxFontCache = std::make_shared<ImplFontCache>();
+ mpGraphics->GetDevFontList(mxFontCollection.get());
+}
+
+void Printer::ImplInitDisplay()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ mpInfoPrinter = nullptr;
+ mpPrinter = nullptr;
+ mpJobGraphics = nullptr;
+
+ mpDisplayDev = VclPtr<VirtualDevice>::Create();
+ mxFontCollection = pSVData->maGDIData.mxScreenFontList;
+ mxFontCache = pSVData->maGDIData.mxScreenFontCache;
+ mnDPIX = mpDisplayDev->mnDPIX;
+ mnDPIY = mpDisplayDev->mnDPIY;
+}
+
+void Printer::DrawDeviceMask( const Bitmap& rMask, const Color& rMaskColor,
+ const Point& rDestPt, const Size& rDestSize,
+ const Point& rSrcPtPixel, const Size& rSrcSizePixel )
+{
+ Point aDestPt( LogicToPixel( rDestPt ) );
+ Size aDestSz( LogicToPixel( rDestSize ) );
+ tools::Rectangle aSrcRect( rSrcPtPixel, rSrcSizePixel );
+
+ aSrcRect.Justify();
+
+ if( !(!rMask.IsEmpty() && aSrcRect.GetWidth() && aSrcRect.GetHeight() && aDestSz.Width() && aDestSz.Height()) )
+ return;
+
+ Bitmap aMask( rMask );
+ BmpMirrorFlags nMirrFlags = BmpMirrorFlags::NONE;
+
+ if( aMask.GetBitCount() > 1 )
+ aMask.Convert( BmpConversion::N1BitThreshold );
+
+ // mirrored horizontically
+ 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 long nSrcWidth = aSrcRect.GetWidth(), nSrcHeight = aSrcRect.GetHeight();
+ long nX, nY; //, nWorkX, nWorkY, nWorkWidth, nWorkHeight;
+ std::unique_ptr<long[]> pMapX( new long[ nSrcWidth + 1 ] );
+ std::unique_ptr<long[]> pMapY( new long[ nSrcHeight + 1 ] );
+ GDIMetaFile* pOldMetaFile = mpMetaFile;
+ const bool bOldMap = mbMap;
+
+ mpMetaFile = nullptr;
+ mbMap = false;
+ Push( PushFlags::FILLCOLOR | 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();
+}
+
+long Printer::GetGradientStepCount( long nMinRect )
+{
+ // use display-equivalent step size calculation
+ 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();
+
+ ReleaseGraphics();
+ 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();
+ mpDeviceFontList.reset();
+ mpDeviceFontSizeList.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();
+}
+
+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();
+ mpDeviceFontList.reset();
+ mpDeviceFontSizeList.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();
+ mpDeviceFontList.reset();
+ mpDeviceFontSizeList.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 ))
+ {
+ const long nRotatedWidth = rData.GetPaperHeight();
+ const 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 )
+ {
+ JobSetup aJobSetup = maJobSetup;
+ ImplJobSetup& rData = aJobSetup.ImplGetData();
+
+ rData.SetPaperFormat( ePaper );
+ if ( ePaper != PAPER_USER )
+ {
+ PaperInfo aInfo(ePaper);
+ rData.SetPaperWidth( aInfo.getWidth() );
+ rData.SetPaperHeight( aInfo.getHeight() );
+ }
+
+ if ( IsDisplayPrinter() )
+ {
+ mbNewJobSetup = true;
+ maJobSetup = aJobSetup;
+ return;
+ }
+
+ ReleaseGraphics();
+ if ( ePaper == PAPER_USER )
+ ImplFindPaperFormatForUserSize( aJobSetup );
+ if ( mpInfoPrinter->SetData( JobSetFlags::PAPERSIZE | JobSetFlags::ORIENTATION, &rData ))
+ {
+ ImplUpdateJobSetupPaper( aJobSetup );
+ mbNewJobSetup = true;
+ maJobSetup = aJobSetup;
+ ImplUpdatePageData();
+ ImplUpdateFontList();
+ }
+ }
+}
+
+bool Printer::SetPaperSizeUser( const Size& rSize )
+{
+ if ( mbInPrintPage )
+ return false;
+
+ const Size aPixSize = LogicToPixel( rSize );
+ const Size aPageSize = PixelToLogic(aPixSize, MapMode(MapUnit::Map100thMM));
+ bool bNeedToChange(maJobSetup.ImplGetConstData().GetPaperWidth() != aPageSize.Width() ||
+ maJobSetup.ImplGetConstData().GetPaperHeight() != aPageSize.Height());
+
+ if(!bNeedToChange)
+ {
+ // #i122984# only need to change when Paper is different from PAPER_USER and
+ // the mapped Paper which will created below in the call to ImplFindPaperFormatForUserSize
+ // and will replace maJobSetup.ImplGetConstData()->GetPaperFormat(). This leads to
+ // unnecessary JobSetups, e.g. when printing a multi-page fax, but also with
+ // normal print
+ const Paper aPaper = ImplGetPaperFormat(aPageSize.Width(), aPageSize.Height());
+
+ bNeedToChange = maJobSetup.ImplGetConstData().GetPaperFormat() != PAPER_USER &&
+ maJobSetup.ImplGetConstData().GetPaperFormat() != aPaper;
+ }
+
+ if(bNeedToChange)
+ {
+ JobSetup aJobSetup = maJobSetup;
+ ImplJobSetup& rData = aJobSetup.ImplGetData();
+ rData.SetPaperFormat( PAPER_USER );
+ rData.SetPaperWidth( aPageSize.Width() );
+ rData.SetPaperHeight( aPageSize.Height() );
+
+ if ( IsDisplayPrinter() )
+ {
+ mbNewJobSetup = true;
+ maJobSetup = aJobSetup;
+ return true;
+ }
+
+ ReleaseGraphics();
+ ImplFindPaperFormatForUserSize( aJobSetup );
+
+ // Changing the paper size can also change the orientation!
+ if ( mpInfoPrinter->SetData( JobSetFlags::PAPERSIZE | JobSetFlags::ORIENTATION, &rData ))
+ {
+ ImplUpdateJobSetupPaper( aJobSetup );
+ mbNewJobSetup = true;
+ maJobSetup = aJobSetup;
+ ImplUpdatePageData();
+ ImplUpdateFontList();
+ return true;
+ }
+ else
+ return false;
+ }
+
+ return true;
+}
+
+int Printer::GetPaperInfoCount() const
+{
+ if( ! mpInfoPrinter )
+ return 0;
+ if( ! mpInfoPrinter->m_bPapersInit )
+ mpInfoPrinter->InitPaperFormats( &maJobSetup.ImplGetConstData() );
+ return mpInfoPrinter->m_aPaperFormats.size();
+}
+
+OUString Printer::GetPaperName( Paper ePaper )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if( pSVData->maPaperNames.empty() )
+ {
+ 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
+ };
+ 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 || nPaper >= int(mpInfoPrinter->m_aPaperFormats.size()) )
+ return ImplGetEmptyPaper();
+ return mpInfoPrinter->m_aPaperFormats[nPaper];
+}
+
+Size Printer::GetPaperSize( int nPaper )
+{
+ PaperInfo aInfo = GetPaperInfo( nPaper );
+ return PixelToLogic( Size( aInfo.getWidth(), aInfo.getHeight() ) );
+}
+
+void Printer::SetDuplexMode( DuplexMode eDuplex )
+{
+ if ( mbInPrintPage )
+ return;
+
+ if ( maJobSetup.ImplGetConstData().GetDuplexMode() != eDuplex )
+ {
+ 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();
+}
+
+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 )
+ {
+ 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 )
+ {
+ mpPrinter->EndPage();
+ ReleaseGraphics();
+ mbDevOutput = false;
+
+ mpJobGraphics = nullptr;
+ mbNewJobSetup = false;
+ }
+}
+
+void Printer::updatePrinters()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ ImplPrnQueueList* pPrnList = pSVData->maGDIData.mpPrinterQueueList.get();
+
+ if ( pPrnList )
+ {
+ 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 )
+ {
+ 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( 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 );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/print2.cxx b/vcl/source/gdi/print2.cxx
new file mode 100644
index 000000000..89fec06ff
--- /dev/null
+++ b/vcl/source/gdi/print2.cxx
@@ -0,0 +1,1314 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <list>
+#include <vector>
+
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <sal/log.hxx>
+#include <officecfg/Office/Common.hxx>
+
+#include <vcl/virdev.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/print.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/bitmapaccess.hxx>
+
+#include "pdfwriter_impl.hxx"
+
+#define MAX_TILE_WIDTH 1024
+#define MAX_TILE_HEIGHT 1024
+
+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).IsInside(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(PushFlags::LINECOLOR|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 mask
+ Bitmap::ScopedReadAccess pRA(aBmp);
+
+ if (!pRA)
+ return; // what else should I do?
+
+ Color aActualColor(aBgColor);
+
+ if (pRA->HasPalette())
+ aActualColor = pRA->GetBestPaletteColor(aBgColor);
+
+ pRA.reset();
+
+ // did we get true white?
+ if (aActualColor.GetColorError(aBgColor))
+ {
+ // no, create truecolor bitmap, then
+ aBmp.Convert(BmpConversion::N24Bit);
+
+ // fill masked out areas white
+ aBmp.Replace(aBmpEx.GetMask(), aBgColor);
+ }
+ else
+ {
+ // fill masked out areas white
+ aBmp.Replace(aBmpEx.GetMask(), aActualColor);
+ }
+ }
+ else
+ {
+ // blend with alpha channel
+ aBmp.Convert(BmpConversion::N24Bit);
+ aBmp.Blend(aBmpEx.GetAlpha(), aBgColor);
+ }
+
+ // add corresponding action
+ switch (rAct.GetType())
+ {
+ case MetaActionType::BMPEX:
+ o_rMtf.AddAction(new MetaBmpAction(
+ static_cast<const MetaBmpExAction&>(rAct).GetPoint(),
+ aBmp));
+ break;
+ case MetaActionType::BMPEXSCALE:
+ o_rMtf.AddAction(new MetaBmpScaleAction(
+ static_cast<const MetaBmpExScaleAction&>(rAct).GetPoint(),
+ static_cast<const MetaBmpExScaleAction&>(rAct).GetSize(),
+ aBmp));
+ break;
+ case MetaActionType::BMPEXSCALEPART:
+ o_rMtf.AddAction(new MetaBmpScalePartAction(
+ static_cast<const MetaBmpExScalePartAction&>(rAct).GetDestPoint(),
+ static_cast<const MetaBmpExScalePartAction&>(rAct).GetDestSize(),
+ static_cast<const MetaBmpExScalePartAction&>(rAct).GetSrcPoint(),
+ static_cast<const MetaBmpExScalePartAction&>(rAct).GetSrcSize(),
+ aBmp));
+ break;
+ default:
+ OSL_FAIL("Unexpected case");
+ break;
+ }
+ }
+}
+
+// #i10613# Extracted from ImplCheckRect::ImplCreate
+// Returns true, if given action creates visible (i.e. non-transparent) output
+bool ImplIsNotTransparent( const MetaAction& rAct, const OutputDevice& rOut )
+{
+ const bool bLineTransparency( !rOut.IsLineColor() || rOut.GetLineColor().GetTransparency() == 255 );
+ const bool bFillTransparency( !rOut.IsFillColor() || rOut.GetFillColor().GetTransparency() == 255 );
+ bool bRet( false );
+
+ switch( rAct.GetType() )
+ {
+ case MetaActionType::POINT:
+ if( !bLineTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::LINE:
+ if( !bLineTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::RECT:
+ if( !bLineTransparency || !bFillTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::ROUNDRECT:
+ if( !bLineTransparency || !bFillTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::ELLIPSE:
+ if( !bLineTransparency || !bFillTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::ARC:
+ if( !bLineTransparency || !bFillTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::PIE:
+ if( !bLineTransparency || !bFillTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::CHORD:
+ if( !bLineTransparency || !bFillTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::POLYLINE:
+ if( !bLineTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::POLYGON:
+ if( !bLineTransparency || !bFillTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::POLYPOLYGON:
+ if( !bLineTransparency || !bFillTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::TEXT:
+ {
+ const MetaTextAction& rTextAct = static_cast<const MetaTextAction&>(rAct);
+ const OUString aString( rTextAct.GetText().copy(rTextAct.GetIndex(), rTextAct.GetLen()) );
+ if (!aString.isEmpty())
+ bRet = true;
+ }
+ break;
+
+ case MetaActionType::TEXTARRAY:
+ {
+ const MetaTextArrayAction& rTextAct = static_cast<const MetaTextArrayAction&>(rAct);
+ const OUString aString( rTextAct.GetText().copy(rTextAct.GetIndex(), rTextAct.GetLen()) );
+ if (!aString.isEmpty())
+ bRet = true;
+ }
+ break;
+
+ case MetaActionType::PIXEL:
+ case MetaActionType::BMP:
+ case MetaActionType::BMPSCALE:
+ case MetaActionType::BMPSCALEPART:
+ case MetaActionType::BMPEX:
+ case MetaActionType::BMPEXSCALE:
+ case MetaActionType::BMPEXSCALEPART:
+ case MetaActionType::MASK:
+ case MetaActionType::MASKSCALE:
+ case MetaActionType::MASKSCALEPART:
+ case MetaActionType::GRADIENT:
+ case MetaActionType::GRADIENTEX:
+ case MetaActionType::HATCH:
+ case MetaActionType::WALLPAPER:
+ case MetaActionType::Transparent:
+ case MetaActionType::FLOATTRANSPARENT:
+ case MetaActionType::EPS:
+ case MetaActionType::TEXTRECT:
+ case MetaActionType::STRETCHTEXT:
+ case MetaActionType::TEXTLINE:
+ // all other actions: generate non-transparent output
+ bRet = true;
+ break;
+
+ default:
+ break;
+ }
+
+ return bRet;
+}
+
+// #i10613# Extracted from ImplCheckRect::ImplCreate
+tools::Rectangle ImplCalcActionBounds( const MetaAction& rAct, const OutputDevice& rOut )
+{
+ tools::Rectangle aActionBounds;
+
+ switch( rAct.GetType() )
+ {
+ case MetaActionType::PIXEL:
+ aActionBounds = tools::Rectangle( static_cast<const MetaPixelAction&>(rAct).GetPoint(), Size( 1, 1 ) );
+ break;
+
+ case MetaActionType::POINT:
+ aActionBounds = tools::Rectangle( static_cast<const MetaPointAction&>(rAct).GetPoint(), Size( 1, 1 ) );
+ break;
+
+ case MetaActionType::LINE:
+ {
+ const MetaLineAction& rMetaLineAction = static_cast<const MetaLineAction&>(rAct);
+ aActionBounds = tools::Rectangle( rMetaLineAction.GetStartPoint(), rMetaLineAction.GetEndPoint() );
+ aActionBounds.Justify();
+ const long nLineWidth(rMetaLineAction.GetLineInfo().GetWidth());
+ if(nLineWidth)
+ {
+ const 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 long nLineWidth(rMetaPolyLineAction.GetLineInfo().GetWidth());
+ if(nLineWidth)
+ {
+ const long nHalfLineWidth((nLineWidth + 1) / 2);
+ aActionBounds.AdjustLeft( -nHalfLineWidth );
+ aActionBounds.AdjustTop( -nHalfLineWidth );
+ aActionBounds.AdjustRight(nHalfLineWidth );
+ aActionBounds.AdjustBottom(nHalfLineWidth );
+ }
+ break;
+ }
+
+ case MetaActionType::POLYGON:
+ aActionBounds = static_cast<const MetaPolygonAction&>(rAct).GetPolygon().GetBoundRect();
+ break;
+
+ case MetaActionType::POLYPOLYGON:
+ aActionBounds = static_cast<const MetaPolyPolygonAction&>(rAct).GetPolyPolygon().GetBoundRect();
+ break;
+
+ case MetaActionType::BMP:
+ aActionBounds = tools::Rectangle( static_cast<const MetaBmpAction&>(rAct).GetPoint(),
+ rOut.PixelToLogic( static_cast<const MetaBmpAction&>(rAct).GetBitmap().GetSizePixel() ) );
+ break;
+
+ case MetaActionType::BMPSCALE:
+ aActionBounds = tools::Rectangle( static_cast<const MetaBmpScaleAction&>(rAct).GetPoint(),
+ static_cast<const MetaBmpScaleAction&>(rAct).GetSize() );
+ break;
+
+ case MetaActionType::BMPSCALEPART:
+ aActionBounds = tools::Rectangle( static_cast<const MetaBmpScalePartAction&>(rAct).GetDestPoint(),
+ static_cast<const MetaBmpScalePartAction&>(rAct).GetDestSize() );
+ break;
+
+ case MetaActionType::BMPEX:
+ aActionBounds = tools::Rectangle( static_cast<const MetaBmpExAction&>(rAct).GetPoint(),
+ rOut.PixelToLogic( static_cast<const MetaBmpExAction&>(rAct).GetBitmapEx().GetSizePixel() ) );
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ aActionBounds = tools::Rectangle( static_cast<const MetaBmpExScaleAction&>(rAct).GetPoint(),
+ static_cast<const MetaBmpExScaleAction&>(rAct).GetSize() );
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ aActionBounds = tools::Rectangle( static_cast<const MetaBmpExScalePartAction&>(rAct).GetDestPoint(),
+ static_cast<const MetaBmpExScalePartAction&>(rAct).GetDestSize() );
+ break;
+
+ case MetaActionType::MASK:
+ aActionBounds = tools::Rectangle( static_cast<const MetaMaskAction&>(rAct).GetPoint(),
+ rOut.PixelToLogic( static_cast<const MetaMaskAction&>(rAct).GetBitmap().GetSizePixel() ) );
+ break;
+
+ case MetaActionType::MASKSCALE:
+ aActionBounds = tools::Rectangle( static_cast<const MetaMaskScaleAction&>(rAct).GetPoint(),
+ static_cast<const MetaMaskScaleAction&>(rAct).GetSize() );
+ break;
+
+ case MetaActionType::MASKSCALEPART:
+ aActionBounds = tools::Rectangle( static_cast<const MetaMaskScalePartAction&>(rAct).GetDestPoint(),
+ static_cast<const MetaMaskScalePartAction&>(rAct).GetDestSize() );
+ break;
+
+ case MetaActionType::GRADIENT:
+ aActionBounds = static_cast<const MetaGradientAction&>(rAct).GetRect();
+ break;
+
+ case MetaActionType::GRADIENTEX:
+ aActionBounds = static_cast<const MetaGradientExAction&>(rAct).GetPolyPolygon().GetBoundRect();
+ break;
+
+ case MetaActionType::HATCH:
+ aActionBounds = static_cast<const MetaHatchAction&>(rAct).GetPolyPolygon().GetBoundRect();
+ break;
+
+ case MetaActionType::WALLPAPER:
+ aActionBounds = static_cast<const MetaWallpaperAction&>(rAct).GetRect();
+ break;
+
+ case MetaActionType::Transparent:
+ aActionBounds = static_cast<const MetaTransparentAction&>(rAct).GetPolyPolygon().GetBoundRect();
+ break;
+
+ case MetaActionType::FLOATTRANSPARENT:
+ aActionBounds = tools::Rectangle( static_cast<const MetaFloatTransparentAction&>(rAct).GetPoint(),
+ static_cast<const MetaFloatTransparentAction&>(rAct).GetSize() );
+ break;
+
+ case MetaActionType::EPS:
+ aActionBounds = tools::Rectangle( static_cast<const MetaEPSAction&>(rAct).GetPoint(),
+ static_cast<const MetaEPSAction&>(rAct).GetSize() );
+ break;
+
+ case MetaActionType::TEXT:
+ {
+ const MetaTextAction& rTextAct = static_cast<const MetaTextAction&>(rAct);
+ const OUString aString( rTextAct.GetText().copy(rTextAct.GetIndex(), rTextAct.GetLen()) );
+
+ if (!aString.isEmpty())
+ {
+ const Point aPtLog( rTextAct.GetPoint() );
+
+ // #105987# Use API method instead of Impl* methods
+ // #107490# Set base parameter equal to index parameter
+ rOut.GetTextBoundRect( aActionBounds, rTextAct.GetText(), rTextAct.GetIndex(),
+ rTextAct.GetIndex(), rTextAct.GetLen() );
+ aActionBounds.Move( aPtLog.X(), aPtLog.Y() );
+ }
+ }
+ break;
+
+ case MetaActionType::TEXTARRAY:
+ {
+ const MetaTextArrayAction& rTextAct = static_cast<const MetaTextArrayAction&>(rAct);
+ const OUString aString( rTextAct.GetText().copy(rTextAct.GetIndex(), rTextAct.GetLen()) );
+
+ if( !aString.isEmpty() )
+ {
+ // #105987# ImplLayout takes everything in logical coordinates
+ std::unique_ptr<SalLayout> pSalLayout = rOut.ImplLayout( rTextAct.GetText(), rTextAct.GetIndex(),
+ rTextAct.GetLen(), rTextAct.GetPoint(),
+ 0, rTextAct.GetDXArray() );
+ if( pSalLayout )
+ {
+ tools::Rectangle aBoundRect( const_cast<OutputDevice&>(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( const_cast<OutputDevice&>(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
+
+bool OutputDevice::RemoveTransparenciesFromMetaFile( const GDIMetaFile& rInMtf, GDIMetaFile& rOutMtf,
+ long nMaxBmpDPIX, 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);
+
+ int nLastBgAction, nActionNum;
+
+ // 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
+ 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;
+ }
+
+ 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.IsInside(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.IsOver( 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( 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 = GetDownsampledBitmap( aDstSzPix,
+ Point(), aBandBmp.GetSizePixel(),
+ aBandBmp, nMaxBmpDPIX, nMaxBmpDPIY );
+ }
+
+ rOutMtf.AddAction( new MetaCommentAction( "PRNSPOOL_TRANSPARENTBITMAP_BEGIN" ) );
+ rOutMtf.AddAction( new MetaBmpScaleAction( aDstPtPix, aDstSzPix, aBandBmp ) );
+ rOutMtf.AddAction( new MetaCommentAction( "PRNSPOOL_TRANSPARENTBITMAP_END" ) );
+
+ aPaintVDev->mbMap = true;
+ mbMap = bOldMap;
+ aMapVDev->Pop();
+ aPaintVDev->Pop();
+ }
+
+ // overlapping bands to avoid missing lines (e.g. PostScript)
+ aDstPtPix.AdjustX(aDstSzPix.Width() );
+ }
+
+ // overlapping bands to avoid missing lines (e.g. PostScript)
+ aDstPtPix.AdjustY(aDstSzPix.Height() );
+ }
+
+ rOutMtf.AddAction( new MetaPopAction() );
+ }
+ }
+ }
+ }
+
+ aMapModeVDev->ClearStack(); // clean up aMapModeVDev
+
+ // STAGE 4: Copy actions to output metafile
+
+ // iterate over all actions and duplicate the ones not in a
+ // special aCCList member into rOutMtf
+ for( pCurrAct=const_cast<GDIMetaFile&>(rInMtf).FirstAction(), nActionNum=0;
+ pCurrAct;
+ pCurrAct=const_cast<GDIMetaFile&>(rInMtf).NextAction(), ++nActionNum )
+ {
+ const ConnectedComponents* pCurrAssociatedComponent = aCCList_MemberMap[nActionNum];
+
+ // NOTE: This relies on the fact that map-mode or draw
+ // mode changing actions are solitary aCCList elements and
+ // have empty bounding boxes, see comment on stage 2.1
+ // above
+ if( pCurrAssociatedComponent &&
+ (pCurrAssociatedComponent->aBounds.IsEmpty() ||
+ !pCurrAssociatedComponent->bIsSpecial) )
+ {
+ // #107169# Treat transparent bitmaps special, if they
+ // are the first (or sole) action in their bounds
+ // list. Note that we previously ensured that no
+ // fully-transparent objects are before us here.
+ if( DoesActionHandleTransparency( *pCurrAct ) &&
+ pCurrAssociatedComponent->aComponentList.begin()->first == pCurrAct )
+ {
+ // convert actions, where masked-out parts are of
+ // given background color
+ ImplConvertTransparentAction(rOutMtf,
+ *pCurrAct,
+ *aMapModeVDev,
+ aBackgroundComponent.aBgColor);
+ }
+ else
+ {
+ // simply add this action
+ rOutMtf.AddAction( pCurrAct );
+ }
+
+ pCurrAct->Execute(aMapModeVDev.get());
+ }
+ }
+
+ rOutMtf.SetPrefMapMode( rInMtf.GetPrefMapMode() );
+ rOutMtf.SetPrefSize( rInMtf.GetPrefSize() );
+
+#if OSL_DEBUG_LEVEL > 1
+ // iterate over all aCCList members and generate rectangles for the bounding boxes
+ rOutMtf.AddAction( new MetaFillColorAction( COL_WHITE, false ) );
+ for(auto const& aCurr:aCCList)
+ {
+ if( aCurr.bIsSpecial )
+ rOutMtf.AddAction( new MetaLineColorAction( COL_RED, true) );
+ else
+ rOutMtf.AddAction( new MetaLineColorAction( COL_BLUE, true) );
+
+ rOutMtf.AddAction( new MetaRectAction( aMapModeVDev->PixelToLogic( aCurr.aBounds ) ) );
+ }
+#endif
+ }
+ return bTransparent;
+}
+
+void Printer::DrawGradientEx( OutputDevice* pOut, const tools::Rectangle& rRect, const Gradient& rGradient )
+{
+ const PrinterOptions& rPrinterOptions = GetPrinterOptions();
+
+ if( rPrinterOptions.IsReduceGradients() )
+ {
+ if( PrinterGradientMode::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 long nR = ( ( static_cast<long>(rStartColor.GetRed()) * rGradient.GetStartIntensity() ) / 100 +
+ ( static_cast<long>(rEndColor.GetRed()) * rGradient.GetEndIntensity() ) / 100 ) >> 1;
+ const long nG = ( ( static_cast<long>(rStartColor.GetGreen()) * rGradient.GetStartIntensity() ) / 100 +
+ ( static_cast<long>(rEndColor.GetGreen()) * rGradient.GetEndIntensity() ) / 100 ) >> 1;
+ const long nB = ( ( static_cast<long>(rStartColor.GetBlue()) * rGradient.GetStartIntensity() ) / 100 +
+ ( static_cast<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( PushFlags::LINECOLOR | PushFlags::FILLCOLOR );
+ pOut->SetLineColor( aColor );
+ pOut->SetFillColor( aColor );
+ pOut->DrawRect( rRect );
+ pOut->Pop();
+ }
+ }
+ else
+ pOut->DrawGradient( rRect, rGradient );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/print3.cxx b/vcl/source/gdi/print3.cxx
new file mode 100644
index 000000000..cbf486713
--- /dev/null
+++ b/vcl/source/gdi/print3.cxx
@@ -0,0 +1,2118 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/weld.hxx>
+#include <vcl/print.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/metaact.hxx>
+#include <configsettings.hxx>
+#include <tools/urlobj.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/sequence.hxx>
+#include <sal/types.h>
+#include <sal/log.hxx>
+#include <tools/debug.hxx>
+
+#include <printdlg.hxx>
+#include <svdata.hxx>
+#include <salinst.hxx>
+#include <salprn.hxx>
+#include <strings.hrc>
+
+#include <com/sun/star/ui/dialogs/FilePicker.hpp>
+#include <com/sun/star/ui/dialogs/TemplateDescription.hpp>
+#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp>
+#include <com/sun/star/view/DuplexMode.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/awt/Size.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 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 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 ),
+ mbPrinterModified( false ),
+ meJobState( css::view::PrintableState_JOB_STARTED ),
+ mnDefaultPaperBin( -1 ),
+ mnFixedPaperBin( -1 )
+ {}
+
+ ~ImplPrinterControllerData()
+ {
+ if (mxProgress)
+ {
+ mxProgress->response(RET_CANCEL);
+ mxProgress.reset();
+ }
+ }
+
+ const Size& getRealPaperSize( const Size& i_rPageSize, bool bNoNUP ) const
+ {
+ if ( mbPapersizeFromUser )
+ return maUserPageSize;
+ if( mbPapersizeFromSetup )
+ return maDefaultPageSize;
+ if( maMultiPage.nRows * maMultiPage.nColumns > 1 && ! bNoNUP )
+ return maMultiPage.aPaperSize;
+ return i_rPageSize;
+ }
+ 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&)
+ {
+ SAL_WARN( "vcl.gdi", "caught IllegalArgumentException when registering filter" );
+ }
+
+ if( xFilePicker->execute() == css::ui::dialogs::ExecutableDialogResults::OK )
+ {
+ css::uno::Sequence< OUString > aPathSeq( xFilePicker->getSelectedFiles() );
+ INetURLObject aObj( aPathSeq[0] );
+ aResult = aObj.PathToFileName();
+ }
+ return aResult;
+}
+
+namespace {
+
+struct PrintJobAsync
+{
+ std::shared_ptr<PrinterController> mxController;
+ JobSetup maInitSetup;
+
+ PrintJobAsync(const std::shared_ptr<PrinterController>& i_xController,
+ const JobSetup& i_rInitSetup)
+ : mxController( i_xController ), maInitSetup( i_rInitSetup )
+ {}
+
+ DECL_LINK( ExecJob, void*, void );
+};
+
+}
+
+IMPL_LINK_NOARG(PrintJobAsync, ExecJob, void*, void)
+{
+ Printer::ImplPrintJob(mxController, maInitSetup);
+
+ // clean up, do not access members after this
+ delete this;
+}
+
+void Printer::PrintJob(const std::shared_ptr<PrinterController>& i_xController,
+ const JobSetup& i_rInitSetup)
+{
+ bool bSynchronous = false;
+ css::beans::PropertyValue* pVal = i_xController->getValue( "Wait" );
+ if( pVal )
+ pVal->Value >>= bSynchronous;
+
+ if( bSynchronous )
+ ImplPrintJob(i_xController, i_rInitSetup);
+ else
+ {
+ PrintJobAsync* pAsync = new PrintJobAsync(i_xController, i_rInitSetup);
+ Application::PostUserEvent( LINK( pAsync, PrintJobAsync, ExecJob ) );
+ }
+}
+
+bool Printer::PreparePrintJob(std::shared_ptr<PrinterController> xController,
+ const JobSetup& i_rInitSetup)
+{
+ // check if there is a default printer; if not, show an error box (if appropriate)
+ if( GetDefaultPrinterName().isEmpty() )
+ {
+ if (xController->isShowDialogs())
+ {
+ std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(xController->getWindow(), "vcl/ui/errornoprinterdialog.ui"));
+ std::unique_ptr<weld::MessageDialog> xBox(xBuilder->weld_message_dialog("ErrorNoPrinterDialog"));
+ xBox->run();
+ }
+ xController->setValue( "IsDirect",
+ css::uno::makeAny( false ) );
+ }
+
+ // setup printer
+
+ // #i114306# changed behavior back from persistence
+ // if no specific printer is already set, create the default printer
+ if (!xController->getPrinter())
+ {
+ OUString aPrinterName( i_rInitSetup.GetPrinterName() );
+ VclPtrInstance<Printer> xPrinter( aPrinterName );
+ xPrinter->SetJobSetup(i_rInitSetup);
+ xController->setPrinter(xPrinter);
+ xController->setPapersizeFromSetup(xPrinter->GetPrinterSettingsPreferred());
+ }
+
+ // reset last page property
+ xController->setLastPage(false);
+
+ // update "PageRange" property inferring from other properties:
+ // case 1: "Pages" set from UNO API ->
+ // setup "Print Selection" and insert "PageRange" attribute
+ // case 2: "All pages" is selected
+ // update "Page range" attribute to have a sensible default,
+ // but leave "All" as selected
+
+ // "Pages" attribute from API is now equivalent to "PageRange"
+ // AND "PrintContent" = 1 except calc where it is "PrintRange" = 1
+ // Argh ! That sure needs cleaning up
+ css::beans::PropertyValue* pContentVal = xController->getValue("PrintRange");
+ if( ! pContentVal )
+ pContentVal = xController->getValue("PrintContent");
+
+ // case 1: UNO API has set "Pages"
+ css::beans::PropertyValue* pPagesVal = xController->getValue("Pages");
+ if( pPagesVal )
+ {
+ OUString aPagesVal;
+ pPagesVal->Value >>= aPagesVal;
+ if( !aPagesVal.isEmpty() )
+ {
+ // "Pages" attribute from API is now equivalent to "PageRange"
+ // AND "PrintContent" = 1 except calc where it is "PrintRange" = 1
+ // Argh ! That sure needs cleaning up
+ if( pContentVal )
+ {
+ pContentVal->Value <<= sal_Int32( 1 );
+ xController->setValue("PageRange", pPagesVal->Value);
+ }
+ }
+ }
+ // case 2: is "All" selected ?
+ else if( pContentVal )
+ {
+ sal_Int32 nContent = -1;
+ if( pContentVal->Value >>= nContent )
+ {
+ if( nContent == 0 )
+ {
+ // do not overwrite PageRange if it is already set
+ css::beans::PropertyValue* pRangeVal = xController->getValue("PageRange");
+ OUString aRange;
+ if( pRangeVal )
+ pRangeVal->Value >>= aRange;
+ if( aRange.isEmpty() )
+ {
+ sal_Int32 nPages = xController->getPageCount();
+ if( nPages > 0 )
+ {
+ OUStringBuffer aBuf( 32 );
+ aBuf.append( "1" );
+ if( nPages > 1 )
+ {
+ aBuf.append( "-" );
+ aBuf.append( nPages );
+ }
+ xController->setValue("PageRange", css::uno::makeAny(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::makeAny( aFile ) );
+ }
+ else if (aDlg.isSingleJobs())
+ {
+ xController->setValue( "PrintCollateAsSingleJobs",
+ css::uno::makeAny( true ) );
+ }
+ }
+ catch (const std::bad_alloc&)
+ {
+ }
+ }
+
+ xController->pushPropertiesToPrinter();
+ return true;
+}
+
+bool Printer::ExecutePrintJob(const std::shared_ptr<PrinterController>& xController)
+{
+ OUString aJobName;
+ css::beans::PropertyValue* pJobNameVal = xController->getValue( "JobName" );
+ if( pJobNameVal )
+ pJobNameVal->Value >>= aJobName;
+
+ return xController->getPrinter()->StartJob( aJobName, xController );
+}
+
+void Printer::FinishPrintJob(const std::shared_ptr<PrinterController>& xController)
+{
+ xController->resetPaperToLastConfigured();
+ xController->jobFinished( xController->getJobState() );
+}
+
+void Printer::ImplPrintJob(const std::shared_ptr<PrinterController>& xController,
+ const JobSetup& i_rInitSetup)
+{
+ if (PreparePrintJob(xController, i_rInitSetup))
+ {
+ ExecutePrintJob(xController);
+ }
+ FinishPrintJob(xController);
+}
+
+bool Printer::StartJob( const OUString& i_rJobName, std::shared_ptr<vcl::PrinterController> const & i_xController)
+{
+ mnError = ERRCODE_NONE;
+
+ if ( IsDisplayPrinter() )
+ return false;
+
+ if ( IsJobActive() || IsPrinting() )
+ return false;
+
+ sal_uInt32 nCopies = mnCopyCount;
+ bool bCollateCopy = mbCollateCopy;
+ bool bUserCopy = false;
+
+ if ( nCopies > 1 )
+ {
+ const sal_uInt32 nDevCopy = GetCapabilities( bCollateCopy
+ ? PrinterCapType::CollateCopies
+ : PrinterCapType::Copies );
+
+ // need to do copies by hand ?
+ if ( nCopies > nDevCopy )
+ {
+ bUserCopy = true;
+ nCopies = 1;
+ bCollateCopy = false;
+ }
+ }
+ else
+ bCollateCopy = false;
+
+ ImplSVData* pSVData = ImplGetSVData();
+ mpPrinter = pSVData->mpDefInst->CreatePrinter( mpInfoPrinter );
+
+ if (!mpPrinter)
+ return false;
+
+ bool bSinglePrintJobs = false;
+ css::beans::PropertyValue* pSingleValue = i_xController->getValue("PrintCollateAsSingleJobs");
+ if( pSingleValue )
+ {
+ pSingleValue->Value >>= bSinglePrintJobs;
+ }
+
+ 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::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::makeAny( 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->mxPrinter->Pop();
+ mpImplData->mnFixedPaperBin = -1;
+}
+
+void PrinterController::resetPrinterOptions( bool i_bFileOutput )
+{
+ PrinterOptions 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 )
+ {
+ 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 && nBin < static_cast<sal_Int32>(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 )
+ {
+ // 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();
+ long nDX = (aPaperSize.Width() - aPageSize.aSize.Width()) / 2;
+ 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
+ long nAdvX = aMPArea.Width() / rMPS.nColumns;
+ 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() )
+ {
+ 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"
+ long nOffX = (aSubPageSize.Width() - long(double(aPageSize.aSize.Width()) * fScale)) / 2;
+ long nOffY = (aSubPageSize.Height() - long(double(aPageSize.aSize.Height()) * fScale)) / 2;
+ long nX = rMPS.nLeftMargin + nOffX + nAdvX * nCellX;
+ 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( long(double(aPageSize.aSize.Width())*fScale),
+ 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 PrinterOptions& 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( PrinterBitmapMode::Optimal == rPrinterOptions.GetReducedBitmapMode() )
+ {
+ nMaxBmpDPIX = std::min( sal_Int32(OPTIMAL_BMP_RESOLUTION), nMaxBmpDPIX );
+ nMaxBmpDPIY = std::min( sal_Int32(OPTIMAL_BMP_RESOLUTION), nMaxBmpDPIY );
+ }
+ else if( PrinterBitmapMode::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() && ( PrinterTransparencyMode::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() == PrinterTransparencyMode::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.get() );
+ 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 = !i_bPapersizeFromSetup;
+}
+
+bool PrinterController::getPapersizeFromSetup() const
+{
+ return mpImplData->mbPapersizeFromSetup;
+}
+
+Size& PrinterController::getPaperSizeSetup() const
+{
+ return mpImplData->maDefaultPageSize;
+}
+
+void PrinterController::setPaperSizeFromUser( Size i_aUserSize )
+{
+ mpImplData->mbPapersizeFromUser = true;
+ mpImplData->mbPapersizeFromSetup = false;
+ mpImplData->mxPrinter->SetPrinterSettingsPreferred( false );
+
+ mpImplData->maUserPageSize = i_aUserSize;
+}
+
+Size& PrinterController::getPaperSizeFromUser() const
+{
+ return mpImplData->maUserPageSize;
+}
+
+bool PrinterController::isPaperSizeFromUser() const
+{
+ return mpImplData->mbPapersizeFromUser;
+}
+
+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 );
+ std::copy(i_rMergeList.begin(), i_rMergeList.end(), aResult.begin());
+ int nCur = i_rMergeList.getLength();
+ for(const css::beans::PropertyValue & rPropVal : mpImplData->maUIProperties)
+ {
+ if( aMergeSet.find( rPropVal.Name ) == aMergeSet.end() )
+ aResult[nCur++] = rPropVal;
+ }
+ // append IsFirstPage
+ if( aMergeSet.find( "IsFirstPage" ) == aMergeSet.end() )
+ {
+ css::beans::PropertyValue aVal;
+ aVal.Name = "IsFirstPage";
+ aVal.Value <<= mpImplData->mbFirstPage;
+ aResult[nCur++] = aVal;
+ }
+ // append IsLastPage
+ if( aMergeSet.find( "IsLastPage" ) == aMergeSet.end() )
+ {
+ css::beans::PropertyValue aVal;
+ aVal.Name = "IsLastPage";
+ aVal.Value <<= mpImplData->mbLastPage;
+ aResult[nCur++] = aVal;
+ }
+ // append IsPrinter
+ if( aMergeSet.find( "IsPrinter" ) == aMergeSet.end() )
+ {
+ css::beans::PropertyValue aVal;
+ aVal.Name = "IsPrinter";
+ aVal.Value <<= true;
+ aResult[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::makeAny( sal_Int32( it->second.mnDependsOnEntry ) ) );
+ }
+ }
+ else if( pVal->Value >>= bDepVal )
+ {
+ setValue( aDependency, css::uno::makeAny( 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 );
+
+ // 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 );
+ css::beans::PropertyValue aVal;
+ aVal.Name = "ExtraPrintUIOptions";
+ aVal.Value <<= comphelper::containerToSequence(m_aUIProperties);
+ io_rProps[ nIndex ] = aVal;
+ }
+}
+
+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 );
+ sal_Int32 nUsed = 0;
+ if( !i_rTitle.isEmpty() )
+ {
+ aCtrl[nUsed ].Name = "Text";
+ aCtrl[nUsed++].Value <<= i_rTitle;
+ }
+ if( i_rHelpIds.hasElements() )
+ {
+ aCtrl[nUsed ].Name = "HelpId";
+ aCtrl[nUsed++].Value <<= i_rHelpIds;
+ }
+ aCtrl[nUsed ].Name = "ControlType";
+ aCtrl[nUsed++].Value <<= i_rType;
+ aCtrl[nUsed ].Name = "ID";
+ aCtrl[nUsed++].Value <<= i_rIDs;
+ if( i_pVal )
+ {
+ aCtrl[nUsed ].Name = "Property";
+ aCtrl[nUsed++].Value <<= *i_pVal;
+ }
+ if( !i_rControlOptions.maDependsOnName.isEmpty() )
+ {
+ aCtrl[nUsed ].Name = "DependsOnName";
+ aCtrl[nUsed++].Value <<= i_rControlOptions.maDependsOnName;
+ if( i_rControlOptions.mnDependsOnEntry != -1 )
+ {
+ aCtrl[nUsed ].Name = "DependsOnEntry";
+ aCtrl[nUsed++].Value <<= i_rControlOptions.mnDependsOnEntry;
+ }
+ if( i_rControlOptions.mbAttachToDependency )
+ {
+ aCtrl[nUsed ].Name = "AttachToDependency";
+ aCtrl[nUsed++].Value <<= i_rControlOptions.mbAttachToDependency;
+ }
+ }
+ if( !i_rControlOptions.maGroupHint.isEmpty() )
+ {
+ aCtrl[nUsed ].Name = "GroupingHint";
+ aCtrl[nUsed++].Value <<= i_rControlOptions.maGroupHint;
+ }
+ if( i_rControlOptions.mbInternalOnly )
+ {
+ aCtrl[nUsed ].Name = "InternalUIOnly";
+ aCtrl[nUsed++].Value <<= true;
+ }
+ if( ! i_rControlOptions.mbEnabled )
+ {
+ aCtrl[nUsed ].Name = "Enabled";
+ aCtrl[nUsed++].Value <<= false;
+ }
+
+ sal_Int32 nAddProps = i_rControlOptions.maAddProps.size();
+ for( sal_Int32 i = 0; i < nAddProps; i++ )
+ aCtrl[ nUsed++ ] = i_rControlOptions.maAddProps[i];
+
+ SAL_WARN_IF( nUsed != nElements, "vcl.gdi", "nUsed != nElements, probable heap corruption" );
+
+ return css::uno::makeAny( aCtrl );
+}
+
+css::uno::Any PrinterOptionsHelper::setGroupControlOpt(const OUString& i_rID,
+ const OUString& i_rTitle,
+ const OUString& i_rHelpId)
+{
+ css::uno::Sequence< OUString > aHelpId;
+ if( !i_rHelpId.isEmpty() )
+ {
+ aHelpId.realloc( 1 );
+ *aHelpId.getArray() = i_rHelpId;
+ }
+ css::uno::Sequence< OUString > aIds { i_rID };
+ return setUIControlOpt(aIds, i_rTitle, aHelpId, "Group");
+}
+
+css::uno::Any PrinterOptionsHelper::setSubgroupControlOpt(const OUString& i_rID,
+ const OUString& i_rTitle,
+ const OUString& i_rHelpId,
+ const PrinterOptionsHelper::UIControlOptions& i_rControlOptions)
+{
+ css::uno::Sequence< OUString > aHelpId;
+ if( !i_rHelpId.isEmpty() )
+ {
+ aHelpId.realloc( 1 );
+ *aHelpId.getArray() = i_rHelpId;
+ }
+ css::uno::Sequence< OUString > aIds { i_rID };
+ return setUIControlOpt(aIds, i_rTitle, aHelpId, "Subgroup", nullptr, i_rControlOptions);
+}
+
+css::uno::Any PrinterOptionsHelper::setBoolControlOpt(const OUString& i_rID,
+ const OUString& i_rTitle,
+ const OUString& i_rHelpId,
+ const OUString& i_rProperty,
+ bool i_bValue,
+ const PrinterOptionsHelper::UIControlOptions& i_rControlOptions)
+{
+ css::uno::Sequence< OUString > aHelpId;
+ if( !i_rHelpId.isEmpty() )
+ {
+ aHelpId.realloc( 1 );
+ *aHelpId.getArray() = i_rHelpId;
+ }
+ css::beans::PropertyValue aVal;
+ aVal.Name = i_rProperty;
+ aVal.Value <<= i_bValue;
+ css::uno::Sequence< OUString > aIds { i_rID };
+ return setUIControlOpt(aIds, i_rTitle, aHelpId, "Bool", &aVal, i_rControlOptions);
+}
+
+css::uno::Any PrinterOptionsHelper::setChoiceRadiosControlOpt(const css::uno::Sequence< OUString >& i_rIDs,
+ const OUString& i_rTitle,
+ const css::uno::Sequence< OUString >& i_rHelpId,
+ const OUString& i_rProperty,
+ const css::uno::Sequence< OUString >& i_rChoices,
+ sal_Int32 i_nValue,
+ const css::uno::Sequence< sal_Bool >& i_rDisabledChoices,
+ const PrinterOptionsHelper::UIControlOptions& i_rControlOptions)
+{
+ UIControlOptions aOpt( i_rControlOptions );
+ sal_Int32 nUsed = aOpt.maAddProps.size();
+ aOpt.maAddProps.resize( nUsed + 1 + (i_rDisabledChoices.hasElements() ? 1 : 0) );
+ aOpt.maAddProps[nUsed].Name = "Choices";
+ aOpt.maAddProps[nUsed].Value <<= i_rChoices;
+ if( i_rDisabledChoices.hasElements() )
+ {
+ aOpt.maAddProps[nUsed+1].Name = "ChoicesDisabled";
+ aOpt.maAddProps[nUsed+1].Value <<= i_rDisabledChoices;
+ }
+
+ css::beans::PropertyValue aVal;
+ aVal.Name = i_rProperty;
+ aVal.Value <<= i_nValue;
+ return setUIControlOpt(i_rIDs, i_rTitle, i_rHelpId, "Radio", &aVal, aOpt);
+}
+
+css::uno::Any PrinterOptionsHelper::setChoiceListControlOpt(const OUString& i_rID,
+ const OUString& i_rTitle,
+ const css::uno::Sequence< OUString >& i_rHelpId,
+ const OUString& i_rProperty,
+ const css::uno::Sequence< OUString >& i_rChoices,
+ sal_Int32 i_nValue,
+ const css::uno::Sequence< sal_Bool >& i_rDisabledChoices,
+ const PrinterOptionsHelper::UIControlOptions& i_rControlOptions)
+{
+ UIControlOptions aOpt( i_rControlOptions );
+ sal_Int32 nUsed = aOpt.maAddProps.size();
+ aOpt.maAddProps.resize( nUsed + 1 + (i_rDisabledChoices.hasElements() ? 1 : 0) );
+ aOpt.maAddProps[nUsed].Name = "Choices";
+ aOpt.maAddProps[nUsed].Value <<= i_rChoices;
+ if( i_rDisabledChoices.hasElements() )
+ {
+ aOpt.maAddProps[nUsed+1].Name = "ChoicesDisabled";
+ aOpt.maAddProps[nUsed+1].Value <<= i_rDisabledChoices;
+ }
+
+ css::beans::PropertyValue aVal;
+ aVal.Name = i_rProperty;
+ aVal.Value <<= i_nValue;
+ css::uno::Sequence< OUString > aIds { i_rID };
+ return setUIControlOpt(aIds, i_rTitle, i_rHelpId, "List", &aVal, aOpt);
+}
+
+css::uno::Any PrinterOptionsHelper::setRangeControlOpt(const OUString& i_rID,
+ const OUString& i_rTitle,
+ const OUString& i_rHelpId,
+ const OUString& i_rProperty,
+ sal_Int32 i_nValue,
+ sal_Int32 i_nMinValue,
+ sal_Int32 i_nMaxValue,
+ const PrinterOptionsHelper::UIControlOptions& i_rControlOptions)
+{
+ UIControlOptions aOpt( i_rControlOptions );
+ if( i_nMaxValue >= i_nMinValue )
+ {
+ sal_Int32 nUsed = aOpt.maAddProps.size();
+ aOpt.maAddProps.resize( nUsed + 2 );
+ aOpt.maAddProps[nUsed ].Name = "MinValue";
+ aOpt.maAddProps[nUsed++].Value <<= i_nMinValue;
+ aOpt.maAddProps[nUsed ].Name = "MaxValue";
+ aOpt.maAddProps[nUsed++].Value <<= i_nMaxValue;
+ }
+
+ css::uno::Sequence< OUString > aHelpId;
+ if( !i_rHelpId.isEmpty() )
+ {
+ aHelpId.realloc( 1 );
+ *aHelpId.getArray() = i_rHelpId;
+ }
+ css::beans::PropertyValue aVal;
+ aVal.Name = i_rProperty;
+ aVal.Value <<= i_nValue;
+ css::uno::Sequence< OUString > aIds { i_rID };
+ return setUIControlOpt(aIds, i_rTitle, aHelpId, "Range", &aVal, aOpt);
+}
+
+css::uno::Any PrinterOptionsHelper::setEditControlOpt(const OUString& i_rID,
+ const OUString& i_rTitle,
+ const OUString& i_rHelpId,
+ const OUString& i_rProperty,
+ const OUString& i_rValue,
+ const PrinterOptionsHelper::UIControlOptions& i_rControlOptions)
+{
+ css::uno::Sequence< OUString > aHelpId;
+ if( !i_rHelpId.isEmpty() )
+ {
+ aHelpId.realloc( 1 );
+ *aHelpId.getArray() = i_rHelpId;
+ }
+ css::beans::PropertyValue aVal;
+ aVal.Name = i_rProperty;
+ aVal.Value <<= i_rValue;
+ css::uno::Sequence< OUString > aIds { i_rID };
+ return setUIControlOpt(aIds, i_rTitle, aHelpId, "Edit", &aVal, i_rControlOptions);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/regband.cxx b/vcl/source/gdi/regband.cxx
new file mode 100644
index 000000000..c47721107
--- /dev/null
+++ b/vcl/source/gdi/regband.cxx
@@ -0,0 +1,885 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <tools/helpers.hxx>
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+
+#include <regband.hxx>
+
+// ImplRegionBand
+
+// Each band contains all rectangles between upper and lower border.
+// For Union, Intersect, Xor and Exclude operations rectangles of
+// equal height are evaluated. The borders of the bands should always
+// be chosen such that this is possible.
+
+// If possible, rectangles within the bands are condensed.
+
+// When converting polygons all points of the polygon are registered
+// in the individual bands (for each band they are stored as
+// points in a list). After registration of these points they are
+// converted to rectangles and the points in the list are deleted.
+
+ImplRegionBand::ImplRegionBand( long nTop, 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)
+ {
+ // 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( long nX, 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( 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( long nXLeft, 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( long nXLeft, 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( long nXLeft, 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( long nXLeft, 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 )
+ {
+ long nOldLeft( pSep->mnXLeft );
+ 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::IsInside( long nX )
+{
+ ImplRegionBandSep* pSep = mpFirstSep;
+ while ( pSep )
+ {
+ if ( (pSep->mnXLeft <= nX) && (pSep->mnXRight >= nX) )
+ return true;
+
+ pSep = pSep->mpNextSep;
+ }
+
+ return false;
+}
+
+long ImplRegionBand::GetXLeftBoundary() const
+{
+ assert(mpFirstSep && "ImplRegionBand::XLeftBoundary -> no separation in band!");
+
+ return mpFirstSep->mnXLeft;
+}
+
+long ImplRegionBand::GetXRightBoundary() const
+{
+ SAL_WARN_IF( mpFirstSep == nullptr, "vcl", "ImplRegionBand::XRightBoundary -> no separation in band!" );
+
+ // search last separation
+ ImplRegionBandSep* pSep = mpFirstSep;
+ while ( pSep->mpNextSep )
+ pSep = pSep->mpNextSep;
+ return pSep->mnXRight;
+}
+
+bool ImplRegionBand::operator==( const ImplRegionBand& rRegionBand ) const
+{
+ ImplRegionBandSep* pOwnRectBandSep = mpFirstSep;
+ ImplRegionBandSep* pSecondRectBandSep = rRegionBand.mpFirstSep;
+ while ( pOwnRectBandSep && pSecondRectBandSep )
+ {
+ // get boundaries of current rectangle
+ long nOwnXLeft = pOwnRectBandSep->mnXLeft;
+ long nSecondXLeft = pSecondRectBandSep->mnXLeft;
+ if ( nOwnXLeft != nSecondXLeft )
+ return false;
+
+ long nOwnXRight = pOwnRectBandSep->mnXRight;
+ long nSecondXRight = pSecondRectBandSep->mnXRight;
+ if ( nOwnXRight != nSecondXRight )
+ return false;
+
+ // get next separation from current band
+ pOwnRectBandSep = pOwnRectBandSep->mpNextSep;
+
+ // get next separation from current band
+ pSecondRectBandSep = pSecondRectBandSep->mpNextSep;
+ }
+
+ // different number of separations?
+ return !(pOwnRectBandSep || pSecondRectBandSep);
+}
+
+ImplRegionBand* ImplRegionBand::SplitBand (const sal_Int32 nY)
+{
+ OSL_ASSERT(nY>mnYTop);
+ OSL_ASSERT(nY<=mnYBottom);
+
+ // Create a copy of the given band (we tell the constructor to copy the points together
+ // with the seps.)
+ ImplRegionBand* pLowerBand = new ImplRegionBand(*this, false);
+
+ // Adapt vertical coordinates.
+ mnYBottom = nY-1;
+ pLowerBand->mnYTop = nY;
+
+ // Insert new band into list of bands.
+ pLowerBand->mpNextBand = mpNextBand;
+ mpNextBand = pLowerBand;
+ pLowerBand->mpPrevBand = this;
+ if (pLowerBand->mpNextBand != nullptr)
+ pLowerBand->mpNextBand->mpPrevBand = pLowerBand;
+
+ return pLowerBand;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/region.cxx b/vcl/source/gdi/region.cxx
new file mode 100644
index 000000000..4ebabaa5b
--- /dev/null
+++ b/vcl/source/gdi/region.cxx
@@ -0,0 +1,1778 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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>
+
+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>() );
+ 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 long nTop (::std::min(aStart.Y(), aEnd.Y()));
+ const 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)
+ {
+ 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)
+: mpB2DPolyPolygon(),
+ mpPolyPolygon(),
+ mpRegionBand(),
+ mbIsNull(bIsNull)
+{
+}
+
+Region::Region(const tools::Rectangle& rRect)
+: mpB2DPolyPolygon(),
+ mpPolyPolygon(),
+ mpRegionBand(),
+ mbIsNull(false)
+{
+ mpRegionBand.reset(rRect.IsEmpty() ? nullptr : new RegionBand(rRect));
+}
+
+Region::Region(const tools::Polygon& rPolygon)
+: mpB2DPolyPolygon(),
+ mpPolyPolygon(),
+ mpRegionBand(),
+ mbIsNull(false)
+{
+
+ if(rPolygon.GetSize())
+ {
+ ImplCreatePolyPolyRegion(rPolygon);
+ }
+}
+
+Region::Region(const tools::PolyPolygon& rPolyPoly)
+: mpB2DPolyPolygon(),
+ mpPolyPolygon(),
+ mpRegionBand(),
+ mbIsNull(false)
+{
+
+ if(rPolyPoly.Count())
+ {
+ ImplCreatePolyPolyRegion(rPolyPoly);
+ }
+}
+
+Region::Region(const basegfx::B2DPolyPolygon& rPolyPoly)
+: mpB2DPolyPolygon(),
+ mpPolyPolygon(),
+ mpRegionBand(),
+ 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)
+ {
+ // polypolygon empty? -> empty region
+ const tools::Rectangle aRect(rPolyPoly.GetBoundRect());
+
+ if(!aRect.IsEmpty())
+ {
+ // width OR height == 1 ? => Rectangular region
+ if((1 == aRect.GetWidth()) || (1 == aRect.GetHeight()) || rPolyPoly.IsRect())
+ {
+ mpRegionBand = std::make_shared<RegionBand>(aRect);
+ }
+ else
+ {
+ mpPolyPolygon = std::make_shared<tools::PolyPolygon>(rPolyPoly);
+ }
+
+ mbIsNull = false;
+ }
+ }
+}
+
+void vcl::Region::ImplCreatePolyPolyRegion( const basegfx::B2DPolyPolygon& rPolyPoly )
+{
+ if(rPolyPoly.count() && !rPolyPoly.getB2DRange().isEmpty())
+ {
+ mpB2DPolyPolygon = std::make_shared<basegfx::B2DPolyPolygon>(rPolyPoly);
+ mbIsNull = false;
+ }
+}
+
+void vcl::Region::Move( long nHorzMove, 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));
+ mpB2DPolyPolygon.reset(aPoly.count() ? new basegfx::B2DPolyPolygon(aPoly) : nullptr);
+ mpPolyPolygon.reset();
+ mpRegionBand.reset();
+ }
+ else if(getPolyPolygon())
+ {
+ tools::PolyPolygon aPoly(*getPolyPolygon());
+
+ aPoly.Move(nHorzMove, nVertMove);
+ mpB2DPolyPolygon.reset();
+ mpPolyPolygon.reset(aPoly.Count() ? new tools::PolyPolygon(aPoly) : nullptr);
+ mpRegionBand.reset();
+ }
+ else if(getRegionBand())
+ {
+ RegionBand* pNew = new RegionBand(*getRegionBand());
+
+ pNew->Move(nHorzMove, nVertMove);
+ mpB2DPolyPolygon.reset();
+ mpPolyPolygon.reset();
+ mpRegionBand.reset(pNew);
+ }
+ else
+ {
+ OSL_ENSURE(false, "Region::Move error: impossible combination (!)");
+ }
+}
+
+void vcl::Region::Scale( double fScaleX, double fScaleY )
+{
+ if(IsNull() || IsEmpty())
+ {
+ // empty or null need no scale
+ return;
+ }
+
+ if(basegfx::fTools::equalZero(fScaleX) && basegfx::fTools::equalZero(fScaleY))
+ {
+ // no scale defined
+ return;
+ }
+
+ if(getB2DPolyPolygon())
+ {
+ basegfx::B2DPolyPolygon aPoly(*getB2DPolyPolygon());
+
+ aPoly.transform(basegfx::utils::createScaleB2DHomMatrix(fScaleX, fScaleY));
+ mpB2DPolyPolygon.reset(aPoly.count() ? new basegfx::B2DPolyPolygon(aPoly) : nullptr);
+ mpPolyPolygon.reset();
+ mpRegionBand.reset();
+ }
+ else if(getPolyPolygon())
+ {
+ tools::PolyPolygon aPoly(*getPolyPolygon());
+
+ aPoly.Scale(fScaleX, fScaleY);
+ mpB2DPolyPolygon.reset();
+ mpPolyPolygon.reset(aPoly.Count() ? new tools::PolyPolygon(aPoly) : nullptr);
+ mpRegionBand.reset();
+ }
+ else if(getRegionBand())
+ {
+ RegionBand* pNew = new RegionBand(*getRegionBand());
+
+ pNew->Scale(fScaleX, fScaleY);
+ mpB2DPolyPolygon.reset();
+ mpPolyPolygon.reset();
+ mpRegionBand.reset(pNew);
+ }
+ else
+ {
+ OSL_ENSURE(false, "Region::Scale error: impossible combination (!)");
+ }
+}
+
+void vcl::Region::Union( const tools::Rectangle& rRect )
+{
+ if(rRect.IsEmpty())
+ {
+ // empty rectangle will not expand the existing union, nothing to do
+ return;
+ }
+
+ if(IsEmpty())
+ {
+ // no local data, the union will be equal to source. Create using rectangle
+ *this = rRect;
+ return;
+ }
+
+ if(HasPolyPolygonOrB2DPolyPolygon())
+ {
+ // get this B2DPolyPolygon, solve on polygon base
+ basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon());
+
+ aThisPolyPoly = basegfx::utils::prepareForPolygonOperation(aThisPolyPoly);
+
+ if(!aThisPolyPoly.count())
+ {
+ // no local polygon, use the rectangle as new region
+ *this = rRect;
+ }
+ else
+ {
+ // get the other B2DPolyPolygon and use logical Or-Operation
+ const basegfx::B2DPolygon aRectPoly(
+ basegfx::utils::createPolygonFromRect(
+ vcl::unotools::b2DRectangleFromRectangle(rRect)));
+ const basegfx::B2DPolyPolygon aClip(
+ basegfx::utils::solvePolygonOperationOr(
+ aThisPolyPoly,
+ basegfx::B2DPolyPolygon(aRectPoly)));
+ *this = vcl::Region(aClip);
+ }
+
+ return;
+ }
+
+ // only region band mode possibility left here or null/empty
+ const RegionBand* pCurrent = getRegionBand();
+
+ if(!pCurrent)
+ {
+ // no region band, create using the rectangle
+ *this = rRect;
+ return;
+ }
+
+ std::shared_ptr<RegionBand> pNew = std::make_shared<RegionBand>(*pCurrent);
+
+ // get justified rectangle
+ const long nLeft(std::min(rRect.Left(), rRect.Right()));
+ const long nTop(std::min(rRect.Top(), rRect.Bottom()));
+ const long nRight(std::max(rRect.Left(), rRect.Right()));
+ const 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));
+
+ mpB2DPolyPolygon.reset(aPoly.count() ? new basegfx::B2DPolyPolygon(aPoly) : nullptr);
+ 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();
+ mpPolyPolygon.reset(aPoly.Count() ? new tools::PolyPolygon(aPoly) : nullptr);
+ 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 long nLeft(std::min(rRect.Left(), rRect.Right()));
+ const long nTop(std::min(rRect.Top(), rRect.Bottom()));
+ const long nRight(std::max(rRect.Left(), rRect.Right()));
+ const long nBottom(std::max(rRect.Top(), rRect.Bottom()));
+
+ // insert bands if the boundaries are not already in the list
+ pNew->InsertBands(nTop, nBottom);
+
+ // process intersect
+ pNew->Intersect(nLeft, nTop, nRight, nBottom);
+
+ // cleanup
+ if(!pNew->OptimizeBandList())
+ {
+ pNew.reset();
+ }
+
+ mpRegionBand = std::move(pNew);
+}
+
+void vcl::Region::Exclude( const tools::Rectangle& rRect )
+{
+ if ( rRect.IsEmpty() )
+ {
+ // excluding nothing will do no change
+ return;
+ }
+
+ if(IsEmpty())
+ {
+ // cannot exclude from empty, done
+ return;
+ }
+
+ if(IsNull())
+ {
+ // error; cannot exclude from null region since this is not representable
+ // in the data
+ OSL_ENSURE(false, "Region::Exclude error: Cannot exclude from null region (!)");
+ return;
+ }
+
+ if( HasPolyPolygonOrB2DPolyPolygon() )
+ {
+ // get this B2DPolyPolygon
+ basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon());
+
+ aThisPolyPoly = basegfx::utils::prepareForPolygonOperation(aThisPolyPoly);
+
+ if(!aThisPolyPoly.count())
+ {
+ // when local polygon is empty, nothing can be excluded
+ return;
+ }
+
+ // get the other B2DPolyPolygon
+ const basegfx::B2DPolygon aRectPoly(
+ basegfx::utils::createPolygonFromRect(
+ vcl::unotools::b2DRectangleFromRectangle(rRect)));
+ const basegfx::B2DPolyPolygon aOtherPolyPoly(aRectPoly);
+ const basegfx::B2DPolyPolygon aClip = basegfx::utils::solvePolygonOperationDiff(aThisPolyPoly, aOtherPolyPoly);
+
+ *this = vcl::Region(aClip);
+
+ return;
+ }
+
+ // only region band mode possibility left here or null/empty
+ const RegionBand* pCurrent = getRegionBand();
+
+ if(!pCurrent)
+ {
+ // empty? -> done!
+ return;
+ }
+
+ std::shared_ptr<RegionBand> pNew( std::make_shared<RegionBand>(*pCurrent));
+
+ // get justified rectangle
+ const long nLeft(std::min(rRect.Left(), rRect.Right()));
+ const long nTop(std::min(rRect.Top(), rRect.Bottom()));
+ const long nRight(std::max(rRect.Left(), rRect.Right()));
+ const long nBottom(std::max(rRect.Top(), rRect.Bottom()));
+
+ // insert bands if the boundaries are not already in the list
+ pNew->InsertBands(nTop, nBottom);
+
+ // process exclude
+ pNew->Exclude(nLeft, nTop, nRight, nBottom);
+
+ // cleanup
+ if(!pNew->OptimizeBandList())
+ {
+ pNew.reset();
+ }
+
+ mpRegionBand = std::move(pNew);
+}
+
+void vcl::Region::XOr( const tools::Rectangle& rRect )
+{
+ if ( rRect.IsEmpty() )
+ {
+ // empty rectangle will not change local content
+ return;
+ }
+
+ if(IsEmpty())
+ {
+ // rRect will be the xored-form (local off, rect on)
+ *this = rRect;
+ return;
+ }
+
+ if(IsNull())
+ {
+ // error; cannot exclude from null region since this is not representable
+ // in the data
+ OSL_ENSURE(false, "Region::XOr error: Cannot XOr with null region (!)");
+ return;
+ }
+
+ if( HasPolyPolygonOrB2DPolyPolygon() )
+ {
+ // get this B2DPolyPolygon
+ basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon());
+
+ aThisPolyPoly = basegfx::utils::prepareForPolygonOperation( aThisPolyPoly );
+
+ if(!aThisPolyPoly.count())
+ {
+ // no local content, XOr will be equal to rectangle
+ *this = rRect;
+ return;
+ }
+
+ // get the other B2DPolyPolygon
+ const basegfx::B2DPolygon aRectPoly(
+ basegfx::utils::createPolygonFromRect(
+ vcl::unotools::b2DRectangleFromRectangle(rRect)));
+ const basegfx::B2DPolyPolygon aOtherPolyPoly(aRectPoly);
+ const basegfx::B2DPolyPolygon aClip = basegfx::utils::solvePolygonOperationXor(aThisPolyPoly, aOtherPolyPoly);
+
+ *this = vcl::Region(aClip);
+
+ return;
+ }
+
+ // only region band mode possibility left here or null/empty
+ const RegionBand* pCurrent = getRegionBand();
+
+ if(!pCurrent)
+ {
+ // rRect will be the xored-form (local off, rect on)
+ *this = rRect;
+ return;
+ }
+
+ // only region band mode possibility left here or null/empty
+ std::shared_ptr<RegionBand> pNew( std::make_shared<RegionBand>(*getRegionBand()));
+
+ // get justified rectangle
+ const long nLeft(std::min(rRect.Left(), rRect.Right()));
+ const long nTop(std::min(rRect.Top(), rRect.Bottom()));
+ const long nRight(std::max(rRect.Left(), rRect.Right()));
+ const 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;
+ }
+
+ const basegfx::B2DPolyPolygon aClip(
+ basegfx::utils::clipPolyPolygonOnPolyPolygon(
+ aOtherPolyPoly,
+ aThisPolyPoly,
+ true,
+ false));
+ *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 = std::make_shared<tools::PolyPolygon>(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 = std::make_shared<tools::PolyPolygon>(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 = std::make_shared<basegfx::B2DPolyPolygon>(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 = std::make_shared<basegfx::B2DPolyPolygon>(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::IsInside( 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()->IsInside( rPoint );
+ //}
+
+ // ensure RegionBand existence
+ const RegionBand* pRegionBand = GetAsRegionBand();
+
+ if(pRegionBand)
+ {
+ return pRegionBand->IsInside(rPoint);
+ }
+
+ return false;
+}
+
+bool vcl::Region::IsOver( const tools::Rectangle& rRect ) const
+{
+ if(IsEmpty())
+ {
+ // nothing can be over something empty
+ return false;
+ }
+
+ if(IsNull())
+ {
+ // everything is over null region
+ return true;
+ }
+
+ // Can we optimize this ??? - is used in StarDraw for brushes pointers
+ // Why we have no IsOver for Regions ???
+ // create region from rectangle and intersect own region
+ vcl::Region aRegion(rRect);
+ aRegion.Intersect( *this );
+
+ // rectangle is over if include is not empty
+ return !aRegion.IsEmpty();
+}
+
+bool vcl::Region::IsRectangle() const
+{
+ if( IsEmpty() || IsNull() )
+ return false;
+
+ if( getB2DPolyPolygon() )
+ return basegfx::utils::isRectangle( *getB2DPolyPolygon() );
+
+ if( getPolyPolygon() )
+ return getPolyPolygon()->IsRect();
+
+ if( getRegionBand() )
+ return (getRegionBand()->getRectangleCount() == 1);
+
+ return false;
+}
+
+void vcl::Region::SetNull()
+{
+ // reset all content
+ mpB2DPolyPolygon.reset();
+ mpPolyPolygon.reset();
+ mpRegionBand.reset();
+ mbIsNull = true;
+}
+
+void vcl::Region::SetEmpty()
+{
+ // reset all content
+ mpB2DPolyPolygon.reset();
+ mpPolyPolygon.reset();
+ mpRegionBand.reset();
+ mbIsNull = false;
+}
+
+Region& vcl::Region::operator=( const vcl::Region& ) = default;
+
+Region& vcl::Region::operator=( vcl::Region&& rRegion ) noexcept
+{
+ mpB2DPolyPolygon = std::move(rRegion.mpB2DPolyPolygon);
+ mpPolyPolygon = std::move(rRegion.mpPolyPolygon);
+ mpRegionBand = std::move(rRegion.mpRegionBand);
+ mbIsNull = rRegion.mbIsNull;
+ rRegion.mbIsNull = true;
+
+ return *this;
+}
+
+Region& vcl::Region::operator=( const tools::Rectangle& rRect )
+{
+ mpB2DPolyPolygon.reset();
+ mpPolyPolygon.reset();
+ mpRegionBand.reset(rRect.IsEmpty() ? nullptr : new RegionBand(rRect));
+ mbIsNull = false;
+
+ return *this;
+}
+
+bool vcl::Region::operator==( const vcl::Region& rRegion ) const
+{
+ if(IsNull() && rRegion.IsNull())
+ {
+ // both are null region
+ return true;
+ }
+
+ if(IsEmpty() && rRegion.IsEmpty())
+ {
+ // both are empty
+ return true;
+ }
+
+ if(getB2DPolyPolygon() && getB2DPolyPolygon() == rRegion.getB2DPolyPolygon())
+ {
+ // same instance data? -> equal
+ return true;
+ }
+
+ if(getPolyPolygon() && getPolyPolygon() == rRegion.getPolyPolygon())
+ {
+ // same instance data? -> equal
+ return true;
+ }
+
+ if(getRegionBand() && getRegionBand() == rRegion.getRegionBand())
+ {
+ // same instance data? -> equal
+ return true;
+ }
+
+ if(IsNull() || IsEmpty())
+ {
+ return false;
+ }
+
+ if(rRegion.IsNull() || rRegion.IsEmpty())
+ {
+ return false;
+ }
+
+ if(rRegion.getB2DPolyPolygon() || getB2DPolyPolygon())
+ {
+ // one of both has a B2DPolyPolygon based region, ensure both have it
+ // by evtl. conversion
+ GetAsB2DPolyPolygon();
+ rRegion.GetAsB2DPolyPolygon();
+
+ return *rRegion.getB2DPolyPolygon() == *getB2DPolyPolygon();
+ }
+
+ if(rRegion.getPolyPolygon() || getPolyPolygon())
+ {
+ // one of both has a B2DPolyPolygon based region, ensure both have it
+ // by evtl. conversion
+ GetAsPolyPolygon();
+ rRegion.GetAsPolyPolygon();
+
+ return *rRegion.getPolyPolygon() == *getPolyPolygon();
+ }
+
+ // both are not empty or null (see above) and if content supported polygon
+ // data the comparison is already done. Only both on RegionBand base can be left,
+ // but better check
+ if(rRegion.getRegionBand() && getRegionBand())
+ {
+ return *rRegion.getRegionBand() == *getRegionBand();
+ }
+
+ // should not happen, but better deny equality
+ return false;
+}
+
+SvStream& ReadRegion(SvStream& rIStrm, vcl::Region& rRegion)
+{
+ VersionCompat aCompat(rIStrm, StreamMode::READ);
+ 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 };
+ RegionType meStreamedType = static_cast<RegionType>(nTmp16);
+
+ switch(meStreamedType)
+ {
+ case REGION_NULL:
+ {
+ rRegion.SetNull();
+ break;
+ }
+
+ case REGION_EMPTY:
+ {
+ rRegion.SetEmpty();
+ break;
+ }
+
+ default:
+ {
+ RegionBand* pNewRegionBand = new RegionBand();
+ bool bSuccess = pNewRegionBand->load(rIStrm);
+ rRegion.mpRegionBand.reset(pNewRegionBand);
+
+ bool bHasPolyPolygon(false);
+ if (aCompat.GetVersion() >= 2)
+ {
+ rIStrm.ReadCharAsBool( bHasPolyPolygon );
+
+ if (bHasPolyPolygon)
+ {
+ tools::PolyPolygon* pNewPoly = new tools::PolyPolygon();
+ ReadPolyPolygon( rIStrm, *pNewPoly );
+ rRegion.mpPolyPolygon.reset(pNewPoly);
+ }
+ }
+
+ 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);
+ VersionCompat aCompat(rOStrm, StreamMode::WRITE, 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]) )
+ {
+ 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 )
+ {
+ long nSwap;
+
+ if( nX2 < nX1 )
+ {
+ nSwap = nX2;
+ nX2 = nX1;
+ nX1 = nSwap;
+ }
+
+ if( nY2 < nY1 )
+ {
+ nSwap = nY2;
+ nY2 = nY1;
+ nY1 = nSwap;
+ }
+
+ if( nX2 != nX1 )
+ {
+ nX2--;
+ }
+
+ if( nY2 != nY1 )
+ {
+ nY2--;
+ }
+
+ pRectOut->SetLeft( nX1 );
+ pRectOut->SetRight( nX2 );
+ pRectOut->SetTop( nY1 );
+ pRectOut->SetBottom( nY2 );
+ }
+ }
+ }
+
+ return bIsRect;
+}
+
+vcl::Region vcl::Region::GetRegionFromPolyPolygon( const tools::PolyPolygon& rPolyPoly )
+{
+ //return vcl::Region( rPolyPoly );
+
+ // check if it's worth extracting the XOr'ing the Rectangles
+ // empiricism shows that break even between XOr'ing rectangles separately
+ // and ImplCreateRegionBandFromPolyPolygon is at half rectangles/half polygons
+ int nPolygonRects = 0, nPolygonPolygons = 0;
+ int nPolygons = rPolyPoly.Count();
+
+ for( int i = 0; i < nPolygons; i++ )
+ {
+ const tools::Polygon& rPoly = rPolyPoly[i];
+
+ if( ImplPolygonRectTest( rPoly ) )
+ {
+ nPolygonRects++;
+ }
+ else
+ {
+ nPolygonPolygons++;
+ }
+ }
+
+ if( nPolygonPolygons > nPolygonRects )
+ {
+ return vcl::Region( rPolyPoly );
+ }
+
+ vcl::Region aResult;
+ tools::Rectangle aRect;
+
+ for( int i = 0; i < nPolygons; i++ )
+ {
+ const tools::Polygon& rPoly = rPolyPoly[i];
+
+ if( ImplPolygonRectTest( rPoly, &aRect ) )
+ {
+ aResult.XOr( aRect );
+ }
+ else
+ {
+ aResult.XOr( vcl::Region(rPoly) );
+ }
+ }
+
+ return aResult;
+}
+
+} /* namespace vcl */
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/regionband.cxx b/vcl/source/gdi/regionband.cxx
new file mode 100644
index 000000000..8478ebb1f
--- /dev/null
+++ b/vcl/source/gdi/regionband.cxx
@@ -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 .
+ */
+
+#include <tools/stream.hxx>
+#include <regionband.hxx>
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+
+RegionBand::RegionBand()
+: mpFirstBand(nullptr),
+ mpLastCheckedBand(nullptr)
+{
+}
+
+RegionBand::RegionBand(const RegionBand& rRef)
+: mpFirstBand(nullptr),
+ mpLastCheckedBand(nullptr)
+{
+ *this = rRef;
+}
+
+RegionBand& RegionBand::operator=(const RegionBand& rRef)
+{
+ if (this != &rRef)
+ {
+ ImplRegionBand* pPrevBand = nullptr;
+ ImplRegionBand* pBand = rRef.mpFirstBand;
+
+ while(pBand)
+ {
+ ImplRegionBand* pNewBand = new ImplRegionBand(*pBand);
+
+ // first element? -> set as first into the list
+ if(pBand == rRef.mpFirstBand)
+ {
+ mpFirstBand = pNewBand;
+ }
+ else
+ {
+ pPrevBand->mpNextBand = pNewBand;
+ }
+
+ pPrevBand = pNewBand;
+ pBand = pBand->mpNextBand;
+ }
+ }
+ return *this;
+}
+
+RegionBand::RegionBand(const tools::Rectangle& rRect)
+: mpFirstBand(nullptr),
+ mpLastCheckedBand(nullptr)
+{
+ const long nTop(std::min(rRect.Top(), rRect.Bottom()));
+ const long nBottom(std::max(rRect.Top(), rRect.Bottom()));
+ const long nLeft(std::min(rRect.Left(), rRect.Right()));
+ const 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
+ long nOwnXLeft = pOwnRectBandSep->mnXLeft;
+ long nSecondXLeft = pSecondRectBandSep->mnXLeft;
+
+ if ( nOwnXLeft != nSecondXLeft )
+ {
+ return false;
+ }
+
+ long nOwnYTop = pOwnRectBand->mnYTop;
+ long nSecondYTop = pSecondRectBand->mnYTop;
+
+ if ( nOwnYTop != nSecondYTop )
+ {
+ return false;
+ }
+
+ long nOwnXRight = pOwnRectBandSep->mnXRight;
+ long nSecondXRight = pSecondRectBandSep->mnXRight;
+
+ if ( nOwnXRight != nSecondXRight )
+ {
+ return false;
+ }
+
+ long nOwnYBottom = pOwnRectBand->mnYBottom;
+ 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 long nTop, const long nBottom)
+{
+ // Iterate over already existing bands and add missing bands atop the
+ // first and between two bands.
+ ImplRegionBand* pPreviousBand = nullptr;
+ ImplRegionBand* pBand = ImplGetFirstRegionBand();
+ 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(long nYTop, 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 ( 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, long nLineId)
+{
+ 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 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 long nDX = labs( rEndPt.X() - rStartPt.X() );
+ const long nDY = labs( rEndPt.Y() - rStartPt.Y() );
+ const long nStartX = rStartPt.X();
+ const long nStartY = rStartPt.Y();
+ const long nEndX = rEndPt.X();
+ const long nEndY = rEndPt.Y();
+ const long nXInc = ( nStartX < nEndX ) ? 1 : -1;
+ const long nYInc = ( nStartY < nEndY ) ? 1 : -1;
+
+ if ( nDX >= nDY )
+ {
+ const long nDYX = ( nDY - nDX ) * 2;
+ const long nDY2 = nDY << 1;
+ 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 long nDYX = ( nDX - nDY ) * 2;
+ const long nDY2 = nDX << 1;
+ 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, 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(long nHorzMove, long nVertMove)
+{
+ ImplRegionBand* pBand = mpFirstBand;
+
+ while(pBand)
+ {
+ // process the vertical move
+ if(nVertMove)
+ {
+ pBand->mnYTop = pBand->mnYTop + nVertMove;
+ pBand->mnYBottom = pBand->mnYBottom + nVertMove;
+ }
+
+ // process the horizontal move
+ if(nHorzMove)
+ {
+ pBand->MoveX(nHorzMove);
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+
+}
+
+void RegionBand::Scale(double fScaleX, double fScaleY)
+{
+ ImplRegionBand* pBand = mpFirstBand;
+
+ while(pBand)
+ {
+ // process the vertical move
+ if(0.0 != fScaleY)
+ {
+ pBand->mnYTop = basegfx::fround(pBand->mnYTop * fScaleY);
+ pBand->mnYBottom = basegfx::fround(pBand->mnYBottom * fScaleY);
+ }
+
+ // process the horizontal move
+ if(0.0 != fScaleX)
+ {
+ pBand->ScaleX(fScaleX);
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+
+}
+
+void RegionBand::InsertBands(long nTop, 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, 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(long nLeft, long nTop, long nRight, 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
+ 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(long nLeft, long nTop, long nRight, 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(long nLeft, long nTop, long nRight, 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
+ 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(long nLeft, long nTop, long nRight, 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
+ long nCurY = pBand->mnYBottom;
+ pBand = pBand->mpNextBand;
+
+ while(pBand)
+ {
+ if((pBand->mnYTop < nCurY) || (pBand->mnYBottom < nCurY))
+ {
+ OSL_ENSURE(false, "RegionBand::XOr() - Bands not sorted!" );
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+#endif
+ break;
+ }
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+
+}
+
+void RegionBand::Intersect(const RegionBand& rSource)
+{
+ // mark all bands as untouched
+ ImplRegionBand* pBand = mpFirstBand;
+
+ while ( pBand )
+ {
+ pBand->mbTouched = false;
+ pBand = pBand->mpNextBand;
+ }
+
+ pBand = rSource.mpFirstBand;
+
+ while ( pBand )
+ {
+ // insert bands if the boundaries are not already in the list
+ InsertBands( pBand->mnYTop, pBand->mnYBottom );
+
+ // process all elements of the list
+ ImplRegionBandSep* pSep = pBand->mpFirstSep;
+
+ while ( pSep )
+ {
+ // left boundary?
+ if ( pSep == pBand->mpFirstSep )
+ {
+ // process intersection and do not remove untouched bands
+ Exclude( LONG_MIN+1, pBand->mnYTop, pSep->mnXLeft-1, pBand->mnYBottom );
+ }
+
+ // right boundary?
+ if ( pSep->mpNextSep == nullptr )
+ {
+ // process intersection and do not remove untouched bands
+ Exclude( pSep->mnXRight+1, pBand->mnYTop, LONG_MAX-1, pBand->mnYBottom );
+ }
+ else
+ {
+ // process intersection and do not remove untouched bands
+ Exclude( pSep->mnXRight+1, pBand->mnYTop, pSep->mpNextSep->mnXLeft-1, pBand->mnYBottom );
+ }
+
+ pSep = pSep->mpNextSep;
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+
+ // remove all untouched bands if bands already left
+ ImplRegionBand* pPrevBand = nullptr;
+ pBand = mpFirstBand;
+
+ while ( pBand )
+ {
+ if ( !pBand->mbTouched )
+ {
+ // save pointer
+ ImplRegionBand* pOldBand = pBand;
+
+ // previous element of the list
+ if ( pBand == mpFirstBand )
+ {
+ mpFirstBand = pBand->mpNextBand;
+ }
+ else
+ {
+ pPrevBand->mpNextBand = pBand->mpNextBand;
+ }
+
+ pBand = pBand->mpNextBand;
+ delete pOldBand;
+ }
+ else
+ {
+ pPrevBand = pBand;
+ pBand = pBand->mpNextBand;
+ }
+ }
+
+}
+
+bool RegionBand::Exclude(const RegionBand& rSource)
+{
+ // apply all rectangles to the region passed to this region
+ ImplRegionBand* pBand = rSource.mpFirstBand;
+
+ while ( pBand )
+ {
+ // insert bands if the boundaries are not already in the list
+ InsertBands( pBand->mnYTop, pBand->mnYBottom );
+
+ // process all elements of the list
+ ImplRegionBandSep* pSep = pBand->mpFirstSep;
+
+ while ( pSep )
+ {
+ Exclude( pSep->mnXLeft, pBand->mnYTop, pSep->mnXRight, pBand->mnYBottom );
+ pSep = pSep->mpNextSep;
+ }
+
+ // to test less bands, already check in the loop
+ if ( !OptimizeBandList() )
+ {
+ return false;
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+
+ return true;
+}
+
+bool RegionBand::CheckConsistency() const
+{
+ if (!mpFirstBand)
+ return true;
+ // look in the band list (don't test first band again!)
+ const ImplRegionBand* pBand = mpFirstBand->mpNextBand;
+ while (pBand)
+ {
+ if (!pBand->mpFirstSep)
+ return false;
+ pBand = pBand->mpNextBand;
+ }
+ return true;
+}
+
+tools::Rectangle RegionBand::GetBoundRect() const
+{
+
+ // get the boundaries of the first band
+ long nYTop(mpFirstBand->mnYTop);
+ long nYBottom(mpFirstBand->mnYBottom);
+ long nXLeft(mpFirstBand->GetXLeftBoundary());
+ 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::IsInside(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->IsInside(rPoint.X());
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+
+ return false;
+}
+
+void RegionBand::GetRegionRectangles(RectangleVector& rTarget) const
+{
+ // clear result vector
+ rTarget.clear();
+ ImplRegionBand* pCurrRectBand = mpFirstBand;
+ tools::Rectangle aRectangle;
+
+ while(pCurrRectBand)
+ {
+ ImplRegionBandSep* pCurrRectBandSep = pCurrRectBand->mpFirstSep;
+
+ aRectangle.SetTop( pCurrRectBand->mnYTop );
+ aRectangle.SetBottom( pCurrRectBand->mnYBottom );
+
+ while(pCurrRectBandSep)
+ {
+ aRectangle.SetLeft( pCurrRectBandSep->mnXLeft );
+ aRectangle.SetRight( pCurrRectBandSep->mnXRight );
+ rTarget.push_back(aRectangle);
+ pCurrRectBandSep = pCurrRectBandSep->mpNextSep;
+ }
+
+ pCurrRectBand = pCurrRectBand->mpNextBand;
+ }
+}
+
+sal_uInt32 RegionBand::getRectangleCount() const
+{
+ sal_uInt32 nCount = 0;
+ const ImplRegionBand* pBand = mpFirstBand;
+
+ while(pBand)
+ {
+ ImplRegionBandSep* pSep = pBand->mpFirstSep;
+
+ while(pSep)
+ {
+ nCount++;
+ pSep = pSep->mpNextSep;
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+
+ return nCount;
+}
+
+#ifdef DBG_UTIL
+const char* ImplDbgTestRegionBand(const void* pObj)
+{
+ const RegionBand* pRegionBand = static_cast< const RegionBand* >(pObj);
+
+ if(pRegionBand)
+ {
+ const ImplRegionBand* pBand = pRegionBand->ImplGetFirstRegionBand();
+
+ while(pBand)
+ {
+ if(pBand->mnYBottom < pBand->mnYTop)
+ {
+ return "YBottom < YTop";
+ }
+
+ if(pBand->mpNextBand)
+ {
+ if(pBand->mnYBottom >= pBand->mpNextBand->mnYTop)
+ {
+ return "overlapping bands in region";
+ }
+ }
+
+ if(pBand->mbTouched)
+ {
+ return "Band-mbTouched overwrite";
+ }
+
+ ImplRegionBandSep* pSep = pBand->mpFirstSep;
+
+ while(pSep)
+ {
+ if(pSep->mnXRight < pSep->mnXLeft)
+ {
+ return "XLeft < XRight";
+ }
+
+ if(pSep->mpNextSep)
+ {
+ if(pSep->mnXRight >= pSep->mpNextSep->mnXLeft)
+ {
+ return "overlapping separations in region";
+ }
+ }
+
+ if ( pSep->mbRemoved )
+ {
+ return "Sep-mbRemoved overwrite";
+ }
+
+ pSep = pSep->mpNextSep;
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+ }
+
+ return nullptr;
+}
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/salgdiimpl.cxx b/vcl/source/gdi/salgdiimpl.cxx
new file mode 100644
index 000000000..654ae90ed
--- /dev/null
+++ b/vcl/source/gdi/salgdiimpl.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <salgdiimpl.hxx>
+
+SalGraphicsImpl::~SalGraphicsImpl()
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/salgdilayout.cxx b/vcl/source/gdi/salgdilayout.cxx
new file mode 100644
index 000000000..e6581d45c
--- /dev/null
+++ b/vcl/source/gdi/salgdilayout.cxx
@@ -0,0 +1,897 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <config_features.h>
+#include <sal/log.hxx>
+#if HAVE_FEATURE_OPENGL
+#include <opengl/gdiimpl.hxx>
+#include <opengl/zone.hxx>
+#include <desktop/exithelper.h>
+#ifdef _WIN32
+#include <svsys.h>
+#endif
+#endif
+#include <salgdi.hxx>
+#include <salframe.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <FileDefinitionWidgetDraw.hxx>
+
+// The only common SalFrame method
+
+SalFrameGeometry SalFrame::GetGeometry() const
+{
+ // mirror frame coordinates at parent
+ SalFrame *pParent = GetParent();
+ if( pParent && AllSettings::GetLayoutRTL() )
+ {
+ SalFrameGeometry aGeom = maGeometry;
+ int parent_x = aGeom.nX - pParent->maGeometry.nX;
+ aGeom.nX = pParent->maGeometry.nX + pParent->maGeometry.nWidth - maGeometry.nWidth - parent_x;
+ return aGeom;
+ }
+ else
+ return maGeometry;
+}
+
+SalGraphics::SalGraphics()
+: m_nLayout( SalLayoutFlags::NONE ),
+ m_aLastMirror(),
+ m_aLastMirrorW(0),
+ m_bAntiAliasB2DDraw(false)
+{
+ // read global RTL settings
+ if( AllSettings::GetLayoutRTL() )
+ m_nLayout = SalLayoutFlags::BiDiRtl;
+}
+
+bool SalGraphics::initWidgetDrawBackends(bool bForce)
+{
+ bool bFileDefinitionsWidgetDraw = !!getenv("VCL_DRAW_WIDGETS_FROM_FILE");
+
+ if (bFileDefinitionsWidgetDraw || bForce)
+ {
+ m_pWidgetDraw.reset(new vcl::FileDefinitionWidgetDraw(*this));
+ auto pFileDefinitionWidgetDraw = static_cast<vcl::FileDefinitionWidgetDraw*>(m_pWidgetDraw.get());
+ if (!pFileDefinitionWidgetDraw->isActive())
+ {
+ m_pWidgetDraw.reset();
+ return false;
+ }
+ return true;
+ }
+ return false;
+}
+
+SalGraphics::~SalGraphics() COVERITY_NOEXCEPT_FALSE
+{
+ // can't call ReleaseFonts here, as the destructor just calls this classes SetFont (pure virtual)!
+}
+
+#if HAVE_FEATURE_OPENGL
+
+namespace
+{
+ void disableOpenGLAndTerminateForRestart()
+ {
+ OpenGLZone::hardDisable();
+#ifdef _WIN32
+ TerminateProcess(GetCurrentProcess(), EXITHELPER_NORMAL_RESTART);
+#endif
+ }
+}
+
+rtl::Reference<OpenGLContext> SalGraphics::GetOpenGLContext() const
+{
+ OpenGLSalGraphicsImpl *pImpl = dynamic_cast<OpenGLSalGraphicsImpl*>(GetImpl());
+ if (pImpl)
+ {
+ // If we notice that OpenGL is broken the first time being called, it is not too late to call
+ // disableOpenGLAndTerminateForRestart(). The first time this will be called is from displaying
+ // the splash screen, so if OpenGL is broken, it is "early enough" for us to be able to disable
+ // OpenGL and terminate bluntly with EXITHELPER_NORMAL_RESTART, thus causing the wrapper process
+ // to restart us, then without using OpenGL.
+ static bool bFirstCall = true;
+ rtl::Reference<OpenGLContext> xRet(pImpl->GetOpenGLContext());
+ if (!xRet.is() && bFirstCall)
+ disableOpenGLAndTerminateForRestart();
+ bFirstCall = false;
+ return xRet;
+ }
+ return nullptr;
+}
+
+#endif
+
+bool SalGraphics::drawTransformedBitmap(
+ const basegfx::B2DPoint& /* rNull */,
+ const basegfx::B2DPoint& /* rX */,
+ const basegfx::B2DPoint& /* rY */,
+ const SalBitmap& /* rSourceBitmap */,
+ const SalBitmap* /* pAlphaBitmap */)
+{
+ // here direct support for transformed bitmaps can be implemented
+ return false;
+}
+
+long SalGraphics::mirror2( long x, const OutputDevice *pOutDev ) const
+{
+ mirror(x, pOutDev);
+ return x;
+}
+
+inline long SalGraphics::GetDeviceWidth(const OutputDevice* pOutDev) const
+{
+ return (pOutDev && pOutDev->IsVirtual())
+ ? pOutDev->GetOutputWidthPixel() : GetGraphicsWidth();
+}
+
+void SalGraphics::mirror( long& x, const OutputDevice *pOutDev ) const
+{
+ const long w = GetDeviceWidth(pOutDev);
+ if( w )
+ {
+ if( pOutDev && pOutDev->ImplIsAntiparallel() )
+ {
+ OutputDevice *pOutDevRef = const_cast<OutputDevice*>(pOutDev);
+ // mirror this window back
+ if( m_nLayout & SalLayoutFlags::BiDiRtl )
+ {
+ long devX = w-pOutDevRef->GetOutputWidthPixel()-pOutDevRef->GetOutOffXPixel(); // re-mirrored mnOutOffX
+ x = devX + (x - pOutDevRef->GetOutOffXPixel());
+ }
+ else
+ {
+ long devX = pOutDevRef->GetOutOffXPixel(); // re-mirrored mnOutOffX
+ x = pOutDevRef->GetOutputWidthPixel() - (x - devX) + pOutDevRef->GetOutOffXPixel() - 1;
+ }
+ }
+ else if( m_nLayout & SalLayoutFlags::BiDiRtl )
+ x = w-1-x;
+ }
+}
+
+void SalGraphics::mirror( long& x, long nWidth, const OutputDevice *pOutDev, bool bBack ) const
+{
+ const long w = GetDeviceWidth(pOutDev);
+ if( w )
+ {
+ if( pOutDev && pOutDev->ImplIsAntiparallel() )
+ {
+ OutputDevice *pOutDevRef = const_cast<OutputDevice*>(pOutDev);
+ // mirror this window back
+ if( m_nLayout & SalLayoutFlags::BiDiRtl )
+ {
+ long devX = w-pOutDevRef->GetOutputWidthPixel()-pOutDevRef->GetOutOffXPixel(); // re-mirrored mnOutOffX
+ if( bBack )
+ x = x - devX + pOutDevRef->GetOutOffXPixel();
+ else
+ x = devX + (x - pOutDevRef->GetOutOffXPixel());
+ }
+ else
+ {
+ long devX = pOutDevRef->GetOutOffXPixel(); // re-mirrored mnOutOffX
+ if( bBack )
+ x = devX + (pOutDevRef->GetOutputWidthPixel() + devX) - (x + nWidth);
+ else
+ x = pOutDevRef->GetOutputWidthPixel() - (x - devX) + pOutDevRef->GetOutOffXPixel() - nWidth;
+ }
+ }
+ else if( m_nLayout & SalLayoutFlags::BiDiRtl )
+ x = w-nWidth-x;
+ }
+}
+
+bool SalGraphics::mirror( sal_uInt32 nPoints, const SalPoint *pPtAry, SalPoint *pPtAry2, const OutputDevice *pOutDev ) const
+{
+ const long w = GetDeviceWidth(pOutDev);
+ if( w )
+ {
+ sal_uInt32 i, j;
+
+ if( pOutDev && pOutDev->ImplIsAntiparallel() )
+ {
+ OutputDevice *pOutDevRef = const_cast<OutputDevice*>(pOutDev);
+ // mirror this window back
+ if( m_nLayout & SalLayoutFlags::BiDiRtl )
+ {
+ long devX = w-pOutDevRef->GetOutputWidthPixel()-pOutDevRef->GetOutOffXPixel(); // re-mirrored mnOutOffX
+ for( i=0, j=nPoints-1; i<nPoints; i++,j-- )
+ {
+ pPtAry2[j].mnX = devX + (pPtAry[i].mnX - pOutDevRef->GetOutOffXPixel());
+ pPtAry2[j].mnY = pPtAry[i].mnY;
+ }
+ }
+ else
+ {
+ long devX = pOutDevRef->GetOutOffXPixel(); // re-mirrored mnOutOffX
+ for( i=0, j=nPoints-1; i<nPoints; i++,j-- )
+ {
+ pPtAry2[j].mnX = pOutDevRef->GetOutputWidthPixel() - (pPtAry[i].mnX - devX) + pOutDevRef->GetOutOffXPixel() - 1;
+ pPtAry2[j].mnY = pPtAry[i].mnY;
+ }
+ }
+ }
+ else if( m_nLayout & SalLayoutFlags::BiDiRtl )
+ {
+ for( i=0, j=nPoints-1; i<nPoints; i++,j-- )
+ {
+ pPtAry2[j].mnX = w-1-pPtAry[i].mnX;
+ pPtAry2[j].mnY = pPtAry[i].mnY;
+ }
+ }
+ return true;
+ }
+ else
+ return false;
+}
+
+void SalGraphics::mirror( vcl::Region& rRgn, const OutputDevice *pOutDev ) const
+{
+ if( rRgn.HasPolyPolygonOrB2DPolyPolygon() )
+ {
+ const basegfx::B2DPolyPolygon aPolyPoly(mirror(rRgn.GetAsB2DPolyPolygon(), pOutDev));
+
+ rRgn = vcl::Region(aPolyPoly);
+ }
+ else
+ {
+ RectangleVector aRectangles;
+ rRgn.GetRegionRectangles(aRectangles);
+ rRgn.SetEmpty();
+
+ for (auto & rectangle : aRectangles)
+ {
+ mirror(rectangle, pOutDev);
+ 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, pOutDev, bBack );
+ // aMirroredRegion.Union( aRect );
+ // bRegionRect = rRgn.ImplGetNextRect( aInfo, nX, nY, nWidth, nHeight );
+ //}
+ //rRgn = aMirroredRegion;
+ }
+}
+
+void SalGraphics::mirror( tools::Rectangle& rRect, const OutputDevice *pOutDev, bool bBack ) const
+{
+ long nWidth = rRect.GetWidth();
+ long x = rRect.Left();
+ long x_org = x;
+
+ mirror( x, nWidth, pOutDev, bBack );
+ rRect.Move( x - x_org, 0 );
+}
+
+basegfx::B2DPolyPolygon SalGraphics::mirror( const basegfx::B2DPolyPolygon& i_rPoly, const OutputDevice* i_pOutDev ) const
+{
+ const basegfx::B2DHomMatrix& rMirror(getMirror(i_pOutDev));
+
+ if(rMirror.isIdentity())
+ {
+ return i_rPoly;
+ }
+ else
+ {
+ basegfx::B2DPolyPolygon aRet(i_rPoly);
+ aRet.transform(rMirror);
+ aRet.flip();
+ return aRet;
+ }
+}
+
+const basegfx::B2DHomMatrix& SalGraphics::getMirror( const OutputDevice* i_pOutDev ) const
+{
+ // get mirroring transformation
+ const long w = GetDeviceWidth(i_pOutDev);
+ SAL_WARN_IF( !w, "vcl", "missing graphics width" );
+
+ if(w != m_aLastMirrorW)
+ {
+ const_cast<SalGraphics*>(this)->m_aLastMirrorW = w;
+
+ if(w)
+ {
+ if(nullptr != i_pOutDev && !i_pOutDev->IsRTLEnabled())
+ {
+ // Original code was (removed here already pOutDevRef->i_pOutDev):
+ // // mirror this window back
+ // double devX = w-i_pOutDev->GetOutputWidthPixel()-i_pOutDev->GetOutOffXPixel(); // re-mirrored mnOutOffX
+ // aRet.setX( devX + (i_rPoint.getX() - i_pOutDev->GetOutOffXPixel()) );
+ // I do not really understand the comment 'mirror this window back', so cannot guarantee
+ // that this works as before, but I have reduced this (by re-placing and re-formatting) to
+ // a simple translation:
+ const_cast<SalGraphics*>(this)->m_aLastMirror = basegfx::utils::createTranslateB2DHomMatrix(
+ w - i_pOutDev->GetOutputWidthPixel() - (2 * i_pOutDev->GetOutOffXPixel()),
+ 0.0);
+ }
+ else
+ {
+ // 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,
+ w-1,
+ 0.0);
+ }
+ }
+ else
+ {
+ const_cast<SalGraphics*>(this)->m_aLastMirror.identity();
+ }
+ }
+
+ return m_aLastMirror;
+}
+
+bool SalGraphics::SetClipRegion( const vcl::Region& i_rClip, const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ vcl::Region aMirror( i_rClip );
+ mirror( aMirror, pOutDev );
+ return setClipRegion( aMirror );
+ }
+ return setClipRegion( i_rClip );
+}
+
+void SalGraphics::DrawPixel( long nX, long nY, const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ mirror( nX, pOutDev );
+ drawPixel( nX, nY );
+}
+
+void SalGraphics::DrawPixel( long nX, long nY, Color nColor, const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ mirror( nX, pOutDev );
+ drawPixel( nX, nY, nColor );
+}
+
+void SalGraphics::DrawLine( long nX1, long nY1, long nX2, long nY2, const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ mirror( nX1, pOutDev );
+ mirror( nX2, pOutDev );
+ }
+ drawLine( nX1, nY1, nX2, nY2 );
+}
+
+void SalGraphics::DrawRect( long nX, long nY, long nWidth, long nHeight, const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ mirror( nX, nWidth, pOutDev );
+ drawRect( nX, nY, nWidth, nHeight );
+}
+
+void SalGraphics::DrawPolyLine( sal_uInt32 nPoints, SalPoint const * pPtAry, const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ std::unique_ptr<SalPoint[]> pPtAry2(new SalPoint[nPoints]);
+ bool bCopied = mirror( nPoints, pPtAry, pPtAry2.get(), pOutDev );
+ drawPolyLine( nPoints, bCopied ? pPtAry2.get() : pPtAry );
+ }
+ else
+ drawPolyLine( nPoints, pPtAry );
+}
+
+void SalGraphics::DrawPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry, const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ std::unique_ptr<SalPoint[]> pPtAry2(new SalPoint[nPoints]);
+ bool bCopied = mirror( nPoints, pPtAry, pPtAry2.get(), pOutDev );
+ drawPolygon( nPoints, bCopied ? pPtAry2.get() : pPtAry );
+ }
+ else
+ drawPolygon( nPoints, pPtAry );
+}
+
+void SalGraphics::DrawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints, PCONSTSALPOINT* pPtAry, const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ // TODO: optimize, reduce new/delete calls
+ std::unique_ptr<SalPoint*[]> pPtAry2( new SalPoint*[nPoly] );
+ sal_uLong i;
+ for(i=0; i<nPoly; i++)
+ {
+ sal_uLong nPoints = pPoints[i];
+ pPtAry2[i] = new SalPoint[ nPoints ];
+ mirror( nPoints, pPtAry[i], pPtAry2[i], pOutDev );
+ }
+
+ drawPolyPolygon( nPoly, pPoints, const_cast<PCONSTSALPOINT*>(pPtAry2.get()) );
+
+ for(i=0; i<nPoly; i++)
+ delete [] pPtAry2[i];
+ }
+ else
+ drawPolyPolygon( nPoly, pPoints, pPtAry );
+}
+
+namespace
+{
+ basegfx::B2DHomMatrix createTranslateToMirroredBounds(const basegfx::B2DRange &rBoundingBox, const basegfx::B2DHomMatrix& rMirror)
+ {
+ basegfx::B2DRange aRTLBoundingBox(rBoundingBox);
+ aRTLBoundingBox *= rMirror;
+ return basegfx::utils::createTranslateB2DHomMatrix(aRTLBoundingBox.getMinX() - rBoundingBox.getMinX(), 0);
+ }
+}
+
+bool SalGraphics::DrawPolyPolygon(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon& i_rPolyPolygon,
+ double i_fTransparency,
+ const OutputDevice* i_pOutDev)
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (i_pOutDev && i_pOutDev->IsRTLEnabled()) )
+ {
+ // mirroring set
+ const basegfx::B2DHomMatrix& rMirror(getMirror(i_pOutDev));
+ if(!rMirror.isIdentity())
+ {
+ basegfx::B2DRange aBoundingBox(i_rPolyPolygon.getB2DRange());
+ aBoundingBox *= rObjectToDevice;
+ auto aTranslateToMirroredBounds = createTranslateToMirroredBounds(aBoundingBox, rMirror);
+
+ return drawPolyPolygon(
+ aTranslateToMirroredBounds * rObjectToDevice,
+ i_rPolyPolygon,
+ i_fTransparency);
+ }
+ }
+
+ return drawPolyPolygon(
+ rObjectToDevice,
+ i_rPolyPolygon,
+ i_fTransparency);
+}
+
+bool SalGraphics::DrawPolyLineBezier( sal_uInt32 nPoints, const SalPoint* pPtAry, const PolyFlags* pFlgAry, const OutputDevice* pOutDev )
+{
+ bool bResult = false;
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ std::unique_ptr<SalPoint[]> pPtAry2(new SalPoint[nPoints]);
+ bool bCopied = mirror( nPoints, pPtAry, pPtAry2.get(), pOutDev );
+ bResult = drawPolyLineBezier( nPoints, bCopied ? pPtAry2.get() : pPtAry, pFlgAry );
+ }
+ else
+ bResult = drawPolyLineBezier( nPoints, pPtAry, pFlgAry );
+ return bResult;
+}
+
+bool SalGraphics::DrawPolygonBezier( sal_uInt32 nPoints, const SalPoint* pPtAry, const PolyFlags* pFlgAry, const OutputDevice* pOutDev )
+{
+ bool bResult = false;
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ std::unique_ptr<SalPoint[]> pPtAry2(new SalPoint[nPoints]);
+ bool bCopied = mirror( nPoints, pPtAry, pPtAry2.get(), pOutDev );
+ 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 SalPoint* const* i_pPtAry, const PolyFlags* const* i_pFlgAry, const OutputDevice* i_pOutDev )
+{
+ bool bRet = false;
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (i_pOutDev && i_pOutDev->IsRTLEnabled()) )
+ {
+ // TODO: optimize, reduce new/delete calls
+ std::unique_ptr<SalPoint*[]> pPtAry2( new SalPoint*[i_nPoly] );
+ sal_uLong i;
+ for(i=0; i<i_nPoly; i++)
+ {
+ sal_uLong nPoints = i_pPoints[i];
+ pPtAry2[i] = new SalPoint[ nPoints ];
+ mirror( nPoints, i_pPtAry[i], pPtAry2[i], i_pOutDev );
+ }
+
+ bRet = drawPolyPolygonBezier( i_nPoly, i_pPoints, const_cast<PCONSTSALPOINT 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_pOutDev)
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (i_pOutDev && i_pOutDev->IsRTLEnabled()) )
+ {
+ // mirroring set
+ const basegfx::B2DHomMatrix& rMirror(getMirror(i_pOutDev));
+ if(!rMirror.isIdentity())
+ {
+ basegfx::B2DRange aBoundingBox(i_rPolygon.getB2DRange());
+ aBoundingBox *= rObjectToDevice;
+ auto aTranslateToMirroredBounds = createTranslateToMirroredBounds(aBoundingBox, rMirror);
+
+ return drawPolyLine(
+ aTranslateToMirroredBounds * 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 )
+{
+ return drawGradient( rPolyPoly, rGradient );
+}
+
+bool SalGraphics::DrawGradient(basegfx::B2DPolyPolygon const & rPolyPolygon, SalGradient const & rSalGradient)
+{
+ return implDrawGradient(rPolyPolygon, rSalGradient);
+}
+
+void SalGraphics::CopyArea( long nDestX, long nDestY,
+ long nSrcX, long nSrcY,
+ long nSrcWidth, long nSrcHeight,
+ const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ mirror( nDestX, nSrcWidth, pOutDev );
+ mirror( nSrcX, nSrcWidth, pOutDev );
+ }
+ copyArea( nDestX, nDestY, nSrcX, nSrcY, nSrcWidth, nSrcHeight, true/*bWindowInvalidate*/ );
+}
+
+void SalGraphics::CopyBits( const SalTwoRect& rPosAry,
+ SalGraphics* pSrcGraphics, const OutputDevice *pOutDev, const OutputDevice *pSrcOutDev )
+{
+ if( ( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) ) ||
+ (pSrcGraphics && ( (pSrcGraphics->GetLayout() & SalLayoutFlags::BiDiRtl) || (pSrcOutDev && pSrcOutDev->IsRTLEnabled()) ) ) )
+ {
+ SalTwoRect aPosAry2 = rPosAry;
+ if( (pSrcGraphics && (pSrcGraphics->GetLayout() & SalLayoutFlags::BiDiRtl)) || (pSrcOutDev && pSrcOutDev->IsRTLEnabled()) )
+ mirror( aPosAry2.mnSrcX, aPosAry2.mnSrcWidth, pSrcOutDev );
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, pOutDev );
+ copyBits( aPosAry2, pSrcGraphics );
+ }
+ else
+ copyBits( rPosAry, pSrcGraphics );
+}
+
+void SalGraphics::DrawBitmap( const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap, const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ SalTwoRect aPosAry2 = rPosAry;
+ mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, pOutDev );
+ drawBitmap( aPosAry2, rSalBitmap );
+ }
+ else
+ drawBitmap( rPosAry, rSalBitmap );
+}
+
+void SalGraphics::DrawBitmap( const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap,
+ const SalBitmap& rTransparentBitmap, const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ SalTwoRect aPosAry2 = rPosAry;
+ mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, pOutDev );
+ drawBitmap( aPosAry2, rSalBitmap, rTransparentBitmap );
+ }
+ else
+ drawBitmap( rPosAry, rSalBitmap, rTransparentBitmap );
+}
+
+void SalGraphics::DrawMask( const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap,
+ Color nMaskColor, const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ SalTwoRect aPosAry2 = rPosAry;
+ mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, pOutDev );
+ drawMask( aPosAry2, rSalBitmap, nMaskColor );
+ }
+ else
+ drawMask( rPosAry, rSalBitmap, nMaskColor );
+}
+
+std::shared_ptr<SalBitmap> SalGraphics::GetBitmap( long nX, long nY, long nWidth, long nHeight, const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ mirror( nX, nWidth, pOutDev );
+ return getBitmap( nX, nY, nWidth, nHeight );
+}
+
+Color SalGraphics::GetPixel( long nX, long nY, const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ mirror( nX, pOutDev );
+ return getPixel( nX, nY );
+}
+
+void SalGraphics::Invert( long nX, long nY, long nWidth, long nHeight, SalInvert nFlags, const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ mirror( nX, nWidth, pOutDev );
+ invert( nX, nY, nWidth, nHeight, nFlags );
+}
+
+void SalGraphics::Invert( sal_uInt32 nPoints, const SalPoint* pPtAry, SalInvert nFlags, const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ std::unique_ptr<SalPoint[]> pPtAry2(new SalPoint[nPoints]);
+ bool bCopied = mirror( nPoints, pPtAry, pPtAry2.get(), pOutDev );
+ invert( nPoints, bCopied ? pPtAry2.get() : pPtAry, nFlags );
+ }
+ else
+ invert( nPoints, pPtAry, nFlags );
+}
+
+bool SalGraphics::DrawEPS( long nX, long nY, long nWidth, long nHeight, void* pPtr, sal_uInt32 nSize, const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ mirror( nX, nWidth, pOutDev );
+ return drawEPS( nX, nY, nWidth, nHeight, pPtr, nSize );
+}
+
+bool SalGraphics::HitTestNativeScrollbar( ControlPart nPart, const tools::Rectangle& rControlRegion,
+ const Point& aPos, bool& rIsInside, const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ Point pt( aPos );
+ tools::Rectangle rgn( rControlRegion );
+ pt.setX( mirror2( pt.X(), pOutDev ) );
+ mirror( rgn, pOutDev );
+ 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* pOutDev ) const
+{
+ switch( rVal.getType() )
+ {
+ case ControlType::Slider:
+ {
+ SliderValue* pSlVal = static_cast<SliderValue*>(&rVal);
+ mirror(pSlVal->maThumbRect,pOutDev);
+ }
+ break;
+ case ControlType::Scrollbar:
+ {
+ ScrollbarValue* pScVal = static_cast<ScrollbarValue*>(&rVal);
+ mirror(pScVal->maThumbRect,pOutDev);
+ mirror(pScVal->maButton1Rect,pOutDev);
+ mirror(pScVal->maButton2Rect,pOutDev);
+ }
+ break;
+ case ControlType::Spinbox:
+ case ControlType::SpinButtons:
+ {
+ SpinbuttonValue* pSpVal = static_cast<SpinbuttonValue*>(&rVal);
+ mirror(pSpVal->maUpperRect,pOutDev);
+ mirror(pSpVal->maLowerRect,pOutDev);
+ }
+ break;
+ case ControlType::Toolbar:
+ {
+ ToolbarValue* pTVal = static_cast<ToolbarValue*>(&rVal);
+ mirror(pTVal->maGripRect,pOutDev);
+ }
+ break;
+ default: break;
+ }
+}
+
+bool SalGraphics::DrawNativeControl( ControlType nType, ControlPart nPart, const tools::Rectangle& rControlRegion,
+ ControlState nState, const ImplControlValue& aValue,
+ const OUString& aCaption, const OutputDevice *pOutDev,
+ const Color& rBackgroundColor)
+{
+ bool bRet = false;
+ tools::Rectangle aControlRegion(rControlRegion);
+ if (aControlRegion.IsEmpty() || aControlRegion.GetWidth() <= 0 || aControlRegion.GetHeight() <= 0)
+ return bRet;
+
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ mirror(aControlRegion, pOutDev);
+ std::unique_ptr< ImplControlValue > mirrorValue( aValue.clone());
+ mirror( *mirrorValue, pOutDev );
+ 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 *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ tools::Rectangle rgn( rControlRegion );
+ mirror( rgn, pOutDev );
+ std::unique_ptr< ImplControlValue > mirrorValue( aValue.clone());
+ mirror( *mirrorValue, pOutDev );
+ if (forWidget()->getNativeControlRegion(nType, nPart, rgn, nState, *mirrorValue, OUString(), rNativeBoundingRegion, rNativeContentRegion))
+ {
+ mirror( rNativeBoundingRegion, pOutDev, true );
+ mirror( rNativeContentRegion, pOutDev, 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 *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ SalTwoRect aPosAry2 = rPosAry;
+ mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, pOutDev );
+ 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 *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ SalTwoRect aPosAry2 = rPosAry;
+ mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, pOutDev );
+ 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 *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ SalTwoRect aPosAry2 = rPosAry;
+ mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, pOutDev );
+ 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,
+ const OutputDevice* pOutDev)
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ // mirroring set
+ const basegfx::B2DHomMatrix& rMirror(getMirror(pOutDev));
+ 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);
+ }
+ }
+
+ return drawTransformedBitmap(rNull, rX, rY, rSourceBitmap, pAlphaBitmap);
+}
+
+bool SalGraphics::DrawAlphaRect( long nX, long nY, long nWidth, long nHeight,
+ sal_uInt8 nTransparency, const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ mirror( nX, nWidth, pOutDev );
+
+ return drawAlphaRect( nX, nY, nWidth, nHeight, nTransparency );
+}
+
+OUString SalGraphics::getRenderBackendName() const
+{
+ if (GetImpl())
+ return GetImpl()->getRenderBackendName();
+ return OUString();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/sallayout.cxx b/vcl/source/gdi/sallayout.cxx
new file mode 100644
index 000000000..eaa016ed1
--- /dev/null
+++ b/vcl/source/gdi/sallayout.cxx
@@ -0,0 +1,1585 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <iostream>
+#include <iomanip>
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+
+#include <cstdio>
+
+#include <math.h>
+
+#include <salgdi.hxx>
+#include <sallayout.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+
+#include <i18nlangtag/lang.h>
+
+#include <vcl/svapp.hxx>
+
+#include <unicode/ubidi.h>
+#include <unicode/uchar.h>
+
+#include <algorithm>
+#include <memory>
+
+#include <impglyphitem.hxx>
+
+// Glyph Flags
+#define GF_FONTMASK 0xF0000000
+#define GF_FONTSHIFT 28
+
+
+std::ostream &operator <<(std::ostream& s, ImplLayoutArgs const &rArgs)
+{
+#ifndef SAL_LOG_INFO
+ (void) rArgs;
+#else
+ s << "ImplLayoutArgs{";
+
+ s << "Flags=";
+ if (rArgs.mnFlags == SalLayoutFlags::NONE)
+ s << 0;
+ else {
+ bool need_or = false;
+ s << "{";
+#define TEST(x) if (rArgs.mnFlags & SalLayoutFlags::x) { if (need_or) s << "|"; s << #x; need_or = true; }
+ TEST(BiDiRtl);
+ TEST(BiDiStrong);
+ TEST(RightAlign);
+ TEST(DisableKerning);
+ TEST(KerningAsian);
+ TEST(Vertical);
+ TEST(KashidaJustification);
+ TEST(ForFallback);
+#undef TEST
+ s << "}";
+ }
+
+ const int nLength = rArgs.mrStr.getLength();
+
+ s << ",Length=" << nLength;
+ s << ",MinCharPos=" << rArgs.mnMinCharPos;
+ s << ",EndCharPos=" << rArgs.mnEndCharPos;
+
+ s << ",Str=\"";
+ int lim = nLength;
+ if (lim > 10)
+ lim = 7;
+ for (int i = 0; i < lim; i++) {
+ if (rArgs.mrStr[i] == '\n')
+ s << "\\n";
+ else if (rArgs.mrStr[i] < ' ' || (rArgs.mrStr[i] >= 0x7F && rArgs.mrStr[i] <= 0xFF))
+ s << "\\0x" << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(rArgs.mrStr[i]) << std::setfill(' ') << std::setw(1) << std::dec;
+ else if (rArgs.mrStr[i] < 0x7F)
+ s << static_cast<char>(rArgs.mrStr[i]);
+ else
+ s << "\\u" << std::hex << std::setw(4) << std::setfill('0') << static_cast<int>(rArgs.mrStr[i]) << std::setfill(' ') << std::setw(1) << std::dec;
+ }
+ if (nLength > lim)
+ s << "...";
+ s << "\"";
+
+ s << ",DXArray=";
+ if (rArgs.mpDXArray) {
+ 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 << ",LayoutWidth=" << rArgs.mnLayoutWidth;
+
+ s << "}";
+
+#endif
+ return s;
+}
+
+sal_UCS4 GetMirroredChar( sal_UCS4 nChar )
+{
+ nChar = u_charMirror( nChar );
+ return nChar;
+}
+
+sal_UCS4 GetLocalizedChar( sal_UCS4 nChar, LanguageType eLang )
+{
+ // currently only conversion from ASCII digits is interesting
+ if( (nChar < '0') || ('9' < nChar) )
+ return nChar;
+
+ int nOffset;
+ // eLang & LANGUAGE_MASK_PRIMARY catches language independent of region.
+ // CAVEAT! To some like Mongolian MS assigned the same primary language
+ // although the script type is different!
+ LanguageType pri = primary(eLang);
+ if( pri == primary(LANGUAGE_ARABIC_SAUDI_ARABIA) )
+ nOffset = 0x0660 - '0'; // arabic-indic digits
+ else if ( pri.anyOf(
+ primary(LANGUAGE_FARSI),
+ primary(LANGUAGE_URDU_PAKISTAN),
+ primary(LANGUAGE_PUNJABI), //???
+ primary(LANGUAGE_SINDHI)))
+ nOffset = 0x06F0 - '0'; // eastern arabic-indic digits
+ else if ( pri == primary(LANGUAGE_BENGALI) )
+ nOffset = 0x09E6 - '0'; // bengali
+ else if ( pri == primary(LANGUAGE_HINDI) )
+ nOffset = 0x0966 - '0'; // devanagari
+ else if ( pri.anyOf(
+ primary(LANGUAGE_AMHARIC_ETHIOPIA),
+ primary(LANGUAGE_TIGRIGNA_ETHIOPIA)))
+ // TODO case:
+ nOffset = 0x1369 - '0'; // ethiopic
+ else if ( pri == primary(LANGUAGE_GUJARATI) )
+ nOffset = 0x0AE6 - '0'; // gujarati
+#ifdef LANGUAGE_GURMUKHI // TODO case:
+ else if ( pri == primary(LANGUAGE_GURMUKHI) )
+ nOffset = 0x0A66 - '0'; // gurmukhi
+#endif
+ else if ( pri == primary(LANGUAGE_KANNADA) )
+ nOffset = 0x0CE6 - '0'; // kannada
+ else if ( pri == primary(LANGUAGE_KHMER))
+ nOffset = 0x17E0 - '0'; // khmer
+ else if ( pri == primary(LANGUAGE_LAO) )
+ nOffset = 0x0ED0 - '0'; // lao
+ else if ( pri == primary(LANGUAGE_MALAYALAM) )
+ nOffset = 0x0D66 - '0'; // malayalam
+ else if ( pri == primary(LANGUAGE_MONGOLIAN_MONGOLIAN_LSO))
+ {
+ if (eLang.anyOf(
+ LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA,
+ LANGUAGE_MONGOLIAN_MONGOLIAN_CHINA,
+ LANGUAGE_MONGOLIAN_MONGOLIAN_LSO))
+ nOffset = 0x1810 - '0'; // mongolian
+ else
+ nOffset = 0; // mongolian cyrillic
+ }
+ else if ( pri == primary(LANGUAGE_BURMESE) )
+ nOffset = 0x1040 - '0'; // myanmar
+ else if ( pri == primary(LANGUAGE_ODIA) )
+ nOffset = 0x0B66 - '0'; // odia
+ else if ( pri == primary(LANGUAGE_TAMIL) )
+ nOffset = 0x0BE7 - '0'; // tamil
+ else if ( pri == primary(LANGUAGE_TELUGU) )
+ nOffset = 0x0C66 - '0'; // telugu
+ else if ( pri == primary(LANGUAGE_THAI) )
+ nOffset = 0x0E50 - '0'; // thai
+ else if ( pri == primary(LANGUAGE_TIBETAN) )
+ nOffset = 0x0F20 - '0'; // tibetan
+ else
+ {
+ nOffset = 0;
+ }
+
+ nChar += nOffset;
+ return nChar;
+}
+
+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;
+ return false;
+}
+
+void ImplLayoutRuns::AddPos( int nCharPos, bool bRTL )
+{
+ // check if charpos could extend current run
+ int nIndex = maRuns.size();
+ if( nIndex >= 2 )
+ {
+ int nRunPos0 = maRuns[ nIndex-2 ];
+ int nRunPos1 = maRuns[ nIndex-1 ];
+ if( ((nCharPos + int(bRTL)) == nRunPos1) && ((nRunPos0 > nRunPos1) == bRTL) )
+ {
+ // extend current run by new charpos
+ maRuns[ nIndex-1 ] = nCharPos + int(!bRTL);
+ return;
+ }
+ // ignore new charpos when it is in current run
+ if( (nRunPos0 <= nCharPos) && (nCharPos < nRunPos1) )
+ return;
+ if( (nRunPos1 <= nCharPos) && (nCharPos < nRunPos0) )
+ return;
+ }
+
+ // else append a new run consisting of the new charpos
+ maRuns.push_back( nCharPos + (bRTL ? 1 : 0) );
+ maRuns.push_back( nCharPos + (bRTL ? 0 : 1) );
+}
+
+void ImplLayoutRuns::AddRun( int nCharPos0, int nCharPos1, bool bRTL )
+{
+ if( nCharPos0 == nCharPos1 )
+ return;
+
+ // swap if needed
+ if( bRTL == (nCharPos0 < nCharPos1) )
+ {
+ int nTemp = nCharPos0;
+ nCharPos0 = nCharPos1;
+ nCharPos1 = nTemp;
+ }
+
+ if (maRuns.size() >= 2 && nCharPos0 == maRuns[maRuns.size() - 2] && nCharPos1 == maRuns[maRuns.size() - 1])
+ {
+ //this run is the same as the last
+ return;
+ }
+
+ // append new run
+ maRuns.push_back( nCharPos0 );
+ maRuns.push_back( nCharPos1 );
+}
+
+bool ImplLayoutRuns::PosIsInRun( int nCharPos ) const
+{
+ if( mnRunIndex >= static_cast<int>(maRuns.size()) )
+ return false;
+
+ int nMinCharPos = maRuns[ mnRunIndex+0 ];
+ int nEndCharPos = maRuns[ mnRunIndex+1 ];
+ if( nMinCharPos > nEndCharPos ) // reversed in RTL case
+ {
+ int nTemp = nMinCharPos;
+ nMinCharPos = nEndCharPos;
+ nEndCharPos = nTemp;
+ }
+
+ if( nCharPos < nMinCharPos )
+ return false;
+ if( nCharPos >= nEndCharPos )
+ return false;
+ return true;
+}
+
+bool ImplLayoutRuns::PosIsInAnyRun( int nCharPos ) const
+{
+ bool bRet = false;
+ int nRunIndex = mnRunIndex;
+
+ ImplLayoutRuns *pThis = const_cast<ImplLayoutRuns*>(this);
+
+ pThis->ResetPos();
+
+ for (size_t i = 0; i < maRuns.size(); i+=2)
+ {
+ bRet = PosIsInRun( nCharPos );
+ if( bRet )
+ break;
+ pThis->NextRun();
+ }
+
+ pThis->mnRunIndex = nRunIndex;
+ return bRet;
+}
+
+bool ImplLayoutRuns::GetNextPos( int* nCharPos, bool* bRightToLeft )
+{
+ // negative nCharPos => reset to first run
+ if( *nCharPos < 0 )
+ mnRunIndex = 0;
+
+ // return false when all runs completed
+ if( mnRunIndex >= static_cast<int>(maRuns.size()) )
+ return false;
+
+ int nRunPos0 = maRuns[ mnRunIndex+0 ];
+ int nRunPos1 = maRuns[ mnRunIndex+1 ];
+ *bRightToLeft = (nRunPos0 > nRunPos1);
+
+ if( *nCharPos < 0 )
+ {
+ // get first valid nCharPos in run
+ *nCharPos = nRunPos0;
+ }
+ else
+ {
+ // advance to next nCharPos for LTR case
+ if( !*bRightToLeft )
+ ++(*nCharPos);
+
+ // advance to next run if current run is completed
+ if( *nCharPos == nRunPos1 )
+ {
+ if( (mnRunIndex += 2) >= static_cast<int>(maRuns.size()) )
+ return false;
+ nRunPos0 = maRuns[ mnRunIndex+0 ];
+ nRunPos1 = maRuns[ mnRunIndex+1 ];
+ *bRightToLeft = (nRunPos0 > nRunPos1);
+ *nCharPos = nRunPos0;
+ }
+ }
+
+ // advance to next nCharPos for RTL case
+ if( *bRightToLeft )
+ --(*nCharPos);
+
+ return true;
+}
+
+bool ImplLayoutRuns::GetRun( int* nMinRunPos, int* nEndRunPos, bool* bRightToLeft ) const
+{
+ if( mnRunIndex >= static_cast<int>(maRuns.size()) )
+ return false;
+
+ int nRunPos0 = maRuns[ mnRunIndex+0 ];
+ int nRunPos1 = maRuns[ mnRunIndex+1 ];
+ *bRightToLeft = (nRunPos1 < nRunPos0) ;
+ if( !*bRightToLeft )
+ {
+ *nMinRunPos = nRunPos0;
+ *nEndRunPos = nRunPos1;
+ }
+ else
+ {
+ *nMinRunPos = nRunPos1;
+ *nEndRunPos = nRunPos0;
+ }
+ return true;
+}
+
+ImplLayoutArgs::ImplLayoutArgs(const OUString& rStr,
+ int nMinCharPos, int nEndCharPos, SalLayoutFlags nFlags, const LanguageTag& rLanguageTag,
+ vcl::TextLayoutCache const*const pLayoutCache)
+:
+ maLanguageTag( rLanguageTag ),
+ mnFlags( nFlags ),
+ mrStr( rStr ),
+ mnMinCharPos( nMinCharPos ),
+ mnEndCharPos( nEndCharPos ),
+ m_pTextLayoutCache(pLayoutCache),
+ mpDXArray( 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 = mrStr.getLength();
+ UBiDi* pParaBidi = ubidi_openSized(nLength, 0, &rcI18n);
+ if( !pParaBidi )
+ return;
+ ubidi_setPara(pParaBidi, reinterpret_cast<const UChar *>(mrStr.getStr()), nLength, nLevel, nullptr, &rcI18n);
+
+ UBiDi* pLineBidi = pParaBidi;
+ int nSubLength = mnEndCharPos - mnMinCharPos;
+ if (nSubLength != nLength)
+ {
+ pLineBidi = ubidi_openSized( nSubLength, 0, &rcI18n );
+ ubidi_setLine( pParaBidi, mnMinCharPos, mnEndCharPos, pLineBidi, &rcI18n );
+ }
+
+ // run BiDi algorithm
+ const int nRunCount = ubidi_countRuns( pLineBidi, &rcI18n );
+ //maRuns.resize( 2 * nRunCount );
+ for( int i = 0; i < nRunCount; ++i )
+ {
+ int32_t nMinPos, nRunLength;
+ const UBiDiDirection nDir = ubidi_getVisualRun( pLineBidi, 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
+ if( pLineBidi != pParaBidi )
+ ubidi_close( pLineBidi );
+ ubidi_close( pParaBidi );
+ }
+
+ // prepare calls to GetNextPos/GetNextRun
+ maRuns.ResetPos();
+}
+
+// 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()
+{
+ // 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;
+}
+
+SalLayout::SalLayout()
+: mnMinCharPos( -1 ),
+ mnEndCharPos( -1 ),
+ mnUnitsPerPixel( 1 ),
+ mnOrientation( 0 ),
+ maDrawOffset( 0, 0 )
+{}
+
+SalLayout::~SalLayout()
+{}
+
+void SalLayout::AdjustLayout( ImplLayoutArgs& rArgs )
+{
+ mnMinCharPos = rArgs.mnMinCharPos;
+ mnEndCharPos = rArgs.mnEndCharPos;
+ mnOrientation = rArgs.mnOrientation;
+}
+
+Point SalLayout::GetDrawPosition( const Point& rRelative ) const
+{
+ Point aPos = maDrawBase;
+ Point aOfs = rRelative + maDrawOffset;
+
+ if( mnOrientation == 0 )
+ aPos += aOfs;
+ else
+ {
+ // cache trigonometric results
+ static int nOldOrientation = 0;
+ static double fCos = 1.0, fSin = 0.0;
+ if( nOldOrientation != mnOrientation )
+ {
+ nOldOrientation = mnOrientation;
+ double fRad = mnOrientation * (M_PI / 1800.0);
+ fCos = cos( fRad );
+ fSin = sin( fRad );
+ }
+
+ double fX = aOfs.X();
+ double fY = aOfs.Y();
+ long nX = static_cast<long>( +fCos * fX + fSin * fY );
+ long nY = static_cast<long>( +fCos * fY - fSin * fX );
+ aPos += Point( nX, nY );
+ }
+
+ return aPos;
+}
+
+bool SalLayout::GetOutline(basegfx::B2DPolyPolygonVector& rVector) const
+{
+ bool bAllOk = true;
+ bool bOneOk = false;
+
+ basegfx::B2DPolyPolygon aGlyphOutline;
+
+ Point aPos;
+ const GlyphItem* pGlyph;
+ int nStart = 0;
+ while (GetNextGlyph(&pGlyph, aPos, nStart))
+ {
+ // get outline of individual glyph, ignoring "empty" glyphs
+ bool bSuccess = pGlyph->GetGlyphOutline(aGlyphOutline);
+ bAllOk &= bSuccess;
+ bOneOk |= bSuccess;
+ // only add non-empty outlines
+ if( bSuccess && (aGlyphOutline.count() > 0) )
+ {
+ if( aPos.X() || aPos.Y() )
+ {
+ aGlyphOutline.transform(basegfx::utils::createTranslateB2DHomMatrix(aPos.X(), aPos.Y()));
+ }
+
+ // 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;
+
+ Point aPos;
+ const GlyphItem* pGlyph;
+ int nStart = 0;
+ while (GetNextGlyph(&pGlyph, aPos, nStart))
+ {
+ // get bounding rectangle of individual glyph
+ if (pGlyph->GetGlyphBoundRect(aRectangle))
+ {
+ // merge rectangle
+ aRectangle += aPos;
+ if (rRect.IsEmpty())
+ rRect = aRectangle;
+ else
+ rRect.Union(aRectangle);
+ bRet = true;
+ }
+ }
+
+ return bRet;
+}
+
+DeviceCoordinate GenericSalLayout::FillDXArray( DeviceCoordinate* pCharWidths ) const
+{
+ if (pCharWidths)
+ GetCharWidths(pCharWidths);
+
+ return GetTextWidth();
+}
+
+// the text width is the maximum logical extent of all glyphs
+DeviceCoordinate GenericSalLayout::GetTextWidth() const
+{
+ if (!m_GlyphItems.IsValid())
+ return 0;
+
+ // initialize the extent
+ DeviceCoordinate nMinPos = 0;
+ DeviceCoordinate nMaxPos = 0;
+
+ for (auto const& aGlyphItem : *m_GlyphItems.Impl())
+ {
+ // update the text extent with the glyph extent
+ DeviceCoordinate nXPos = aGlyphItem.m_aLinearPos.getX();
+ if( nMinPos > nXPos )
+ nMinPos = nXPos;
+ nXPos += aGlyphItem.m_nNewWidth - aGlyphItem.xOffset();
+ if( nMaxPos < nXPos )
+ nMaxPos = nXPos;
+ }
+
+ DeviceCoordinate nWidth = nMaxPos - nMinPos;
+ return nWidth;
+}
+
+void GenericSalLayout::Justify( DeviceCoordinate nNewWidth )
+{
+ nNewWidth *= mnUnitsPerPixel;
+ DeviceCoordinate nOldWidth = GetTextWidth();
+ if( !nOldWidth || nNewWidth==nOldWidth )
+ return;
+
+ if (!m_GlyphItems.IsValid())
+ {
+ return;
+ }
+ // find rightmost glyph, it won't get stretched
+ std::vector<GlyphItem>::iterator pGlyphIterRight = m_GlyphItems.Impl()->begin();
+ pGlyphIterRight += m_GlyphItems.Impl()->size() - 1;
+ std::vector<GlyphItem>::iterator pGlyphIter;
+ // count stretchable glyphs
+ int nStretchable = 0;
+ int nMaxGlyphWidth = 0;
+ for(pGlyphIter = m_GlyphItems.Impl()->begin(); pGlyphIter != pGlyphIterRight; ++pGlyphIter)
+ {
+ if( !pGlyphIter->IsDiacritic() )
+ ++nStretchable;
+ if (nMaxGlyphWidth < pGlyphIter->origWidth())
+ nMaxGlyphWidth = pGlyphIter->origWidth();
+ }
+
+ // move rightmost glyph to requested position
+ nOldWidth -= pGlyphIterRight->origWidth();
+ if( nOldWidth <= 0 )
+ return;
+ if( nNewWidth < nMaxGlyphWidth)
+ nNewWidth = nMaxGlyphWidth;
+ nNewWidth -= pGlyphIterRight->origWidth();
+ pGlyphIterRight->m_aLinearPos.setX( nNewWidth );
+
+ // justify glyph widths and positions
+ int nDiffWidth = nNewWidth - nOldWidth;
+ if( nDiffWidth >= 0) // expanded case
+ {
+ // expand width by distributing space between glyphs evenly
+ int nDeltaSum = 0;
+ for( pGlyphIter = m_GlyphItems.Impl()->begin(); pGlyphIter != pGlyphIterRight; ++pGlyphIter )
+ {
+ // move glyph to justified position
+ pGlyphIter->m_aLinearPos.AdjustX(nDeltaSum );
+
+ // do not stretch non-stretchable glyphs
+ if( pGlyphIter->IsDiacritic() || (nStretchable <= 0) )
+ continue;
+
+ // distribute extra space equally to stretchable glyphs
+ int nDeltaWidth = nDiffWidth / nStretchable--;
+ nDiffWidth -= nDeltaWidth;
+ pGlyphIter->m_nNewWidth += nDeltaWidth;
+ nDeltaSum += nDeltaWidth;
+ }
+ }
+ else // condensed case
+ {
+ // squeeze width by moving glyphs proportionally
+ double fSqueeze = static_cast<double>(nNewWidth) / nOldWidth;
+ if(m_GlyphItems.Impl()->size() > 1)
+ {
+ for( pGlyphIter = m_GlyphItems.Impl()->begin(); ++pGlyphIter != pGlyphIterRight;)
+ {
+ int nX = pGlyphIter->m_aLinearPos.getX();
+ nX = static_cast<int>(nX * fSqueeze);
+ pGlyphIter->m_aLinearPos.setX( nX );
+ }
+ }
+ // adjust glyph widths to new positions
+ for( pGlyphIter = m_GlyphItems.Impl()->begin(); pGlyphIter != pGlyphIterRight; ++pGlyphIter )
+ pGlyphIter->m_nNewWidth = pGlyphIter[1].m_aLinearPos.getX() - pGlyphIter[0].m_aLinearPos.getX();
+ }
+}
+
+// returns asian kerning values in quarter of character width units
+// to enable automatic halfwidth substitution for fullwidth punctuation
+// return value is negative for l, positive for r, zero for neutral
+// TODO: handle vertical layout as proposed in commit 43bf2ad49c2b3989bbbe893e4fee2e032a3920f5?
+static int lcl_CalcAsianKerning(sal_UCS4 c, bool bLeft)
+{
+ // http://www.asahi-net.or.jp/~sd5a-ucd/freetexts/jis/x4051/1995/appendix.html
+ static const signed char nTable[0x30] =
+ {
+ 0, -2, -2, 0, 0, 0, 0, 0, +2, -2, +2, -2, +2, -2, +2, -2,
+ +2, -2, 0, 0, +2, -2, +2, -2, 0, 0, 0, 0, 0, +2, -2, -2,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2, -2, +2, +2, -2, -2
+ };
+
+ int nResult = 0;
+ if( (c >= 0x3000) && (c < 0x3030) )
+ nResult = nTable[ c - 0x3000 ];
+ else switch( c )
+ {
+ case 0x30FB:
+ nResult = bLeft ? -1 : +1; // 25% left/right/top/bottom
+ break;
+ case 0x2019: case 0x201D:
+ case 0xFF01: case 0xFF09: case 0xFF0C:
+ case 0xFF1A: case 0xFF1B:
+ nResult = -2;
+ break;
+ case 0x2018: case 0x201C:
+ case 0xFF08:
+ nResult = +2;
+ break;
+ default:
+ break;
+ }
+
+ return nResult;
+}
+
+static bool lcl_CanApplyAsianKerning(sal_Unicode cp)
+{
+ return (0x3000 == (cp & 0xFF00)) || (0xFF00 == (cp & 0xFF00)) || (0x2010 == (cp & 0xFFF0));
+}
+
+void GenericSalLayout::ApplyAsianKerning(const OUString& rStr)
+{
+ const int nLength = rStr.getLength();
+ long nOffset = 0;
+
+ for (std::vector<GlyphItem>::iterator pGlyphIter = m_GlyphItems.Impl()->begin(),
+ pGlyphIterEnd = m_GlyphItems.Impl()->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
+ int nDelta = (nKernCurrent < nKernNext) ? nKernCurrent : nKernNext;
+ if (nDelta < 0)
+ {
+ nDelta = (nDelta * pGlyphIter->origWidth() + 2) / 4;
+ if( pGlyphIter+1 == pGlyphIterEnd )
+ pGlyphIter->m_nNewWidth += nDelta;
+ nOffset += nDelta;
+ }
+ }
+
+ // adjust the glyph positions to the new glyph widths
+ if( pGlyphIter+1 != pGlyphIterEnd )
+ pGlyphIter->m_aLinearPos.AdjustX(nOffset);
+ }
+}
+
+void GenericSalLayout::GetCaretPositions( int nMaxIndex, long* pCaretXArray ) const
+{
+ // initialize result array
+ for (int i = 0; i < nMaxIndex; ++i)
+ pCaretXArray[i] = -1;
+
+ // calculate caret positions using glyph array
+ for (auto const& aGlyphItem : *m_GlyphItems.Impl())
+ {
+ long nXPos = aGlyphItem.m_aLinearPos.getX();
+ long nXRight = nXPos + aGlyphItem.origWidth();
+ int n = aGlyphItem.charPos();
+ int nCurrIdx = 2 * (n - mnMinCharPos);
+ // tdf#86399 if this is not the start of a cluster, don't overwrite the caret bounds of the cluster start
+ if (aGlyphItem.IsInCluster() && pCaretXArray[nCurrIdx] != -1)
+ continue;
+ if (!aGlyphItem.IsRTLGlyph() )
+ {
+ // normal positions for LTR case
+ pCaretXArray[ nCurrIdx ] = nXPos;
+ pCaretXArray[ nCurrIdx+1 ] = nXRight;
+ }
+ else
+ {
+ // reverse positions for RTL case
+ pCaretXArray[ nCurrIdx ] = nXRight;
+ pCaretXArray[ nCurrIdx+1 ] = nXPos;
+ }
+ }
+}
+
+sal_Int32 GenericSalLayout::GetTextBreak( DeviceCoordinate nMaxWidth, DeviceCoordinate nCharExtra, int nFactor ) const
+{
+ int nCharCapacity = mnEndCharPos - mnMinCharPos;
+ std::unique_ptr<DeviceCoordinate[]> const pCharWidths(new DeviceCoordinate[nCharCapacity]);
+ GetCharWidths(pCharWidths.get());
+
+ DeviceCoordinate nWidth = 0;
+ for( int i = mnMinCharPos; i < mnEndCharPos; ++i )
+ {
+ nWidth += pCharWidths[ i - mnMinCharPos ] * nFactor;
+ if( nWidth > nMaxWidth )
+ return i;
+ nWidth += nCharExtra;
+ }
+
+ return -1;
+}
+
+bool GenericSalLayout::GetNextGlyph(const GlyphItem** pGlyph,
+ Point& rPos, int& nStart,
+ const PhysicalFontFace**, int* const pFallbackLevel) const
+{
+ std::vector<GlyphItem>::const_iterator pGlyphIter = m_GlyphItems.Impl()->begin();
+ std::vector<GlyphItem>::const_iterator pGlyphIterEnd = m_GlyphItems.Impl()->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.Impl()->size()) )
+ return false;
+
+ if( pGlyphIter == pGlyphIterEnd )
+ return false;
+
+ // update return data with glyph info
+ *pGlyph = &(*pGlyphIter);
+ if (pFallbackLevel)
+ *pFallbackLevel = 0;
+ ++nStart;
+
+ // calculate absolute position in pixel units
+ Point aRelativePos = pGlyphIter->m_aLinearPos;
+
+ aRelativePos.setX( aRelativePos.X() / mnUnitsPerPixel );
+ aRelativePos.setY( aRelativePos.Y() / mnUnitsPerPixel );
+ rPos = GetDrawPosition( aRelativePos );
+
+ return true;
+}
+
+void GenericSalLayout::MoveGlyph( int nStart, long nNewXPos )
+{
+ if( nStart >= static_cast<int>(m_GlyphItems.Impl()->size()) )
+ return;
+
+ std::vector<GlyphItem>::iterator pGlyphIter = m_GlyphItems.Impl()->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->m_nNewWidth - pGlyphIter->origWidth();
+ // calculate the x-offset to the old position
+ long nXDelta = nNewXPos - pGlyphIter->m_aLinearPos.getX();
+ // adjust all following glyph positions if needed
+ if( nXDelta != 0 )
+ {
+ for( std::vector<GlyphItem>::iterator pGlyphIterEnd = m_GlyphItems.Impl()->end(); pGlyphIter != pGlyphIterEnd; ++pGlyphIter )
+ {
+ pGlyphIter->m_aLinearPos.AdjustX(nXDelta );
+ }
+ }
+}
+
+void GenericSalLayout::DropGlyph( int nStart )
+{
+ if( nStart >= static_cast<int>(m_GlyphItems.Impl()->size()))
+ return;
+
+ std::vector<GlyphItem>::iterator pGlyphIter = m_GlyphItems.Impl()->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.Impl()->size(); i++ )
+ {
+ if (bIsBase && (*m_GlyphItems.Impl())[i].IsDropped())
+ continue;
+ if (!bIsBase && (*m_GlyphItems.Impl())[i].glyphId() == 0)
+ continue;
+
+ if( i != j )
+ {
+ (*m_GlyphItems.Impl())[j] = (*m_GlyphItems.Impl())[i];
+ }
+ j += 1;
+ }
+ m_GlyphItems.Impl()->erase(m_GlyphItems.Impl()->begin() + j, m_GlyphItems.Impl()->end());
+}
+
+MultiSalLayout::MultiSalLayout( std::unique_ptr<SalLayout> pBaseLayout )
+: SalLayout()
+, mnLevel( 1 )
+, mbIncomplete( false )
+{
+ assert(dynamic_cast<GenericSalLayout*>(pBaseLayout.get()));
+
+ mpLayouts[ 0 ].reset(static_cast<GenericSalLayout*>(pBaseLayout.release()));
+ mnUnitsPerPixel = mpLayouts[ 0 ]->GetUnitsPerPixel();
+}
+
+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( ImplLayoutArgs& rArgs, const SalLayoutGlyphs* )
+{
+ if( mnLevel <= 1 )
+ return false;
+ if (!mbIncomplete)
+ maFallbackRuns[ mnLevel-1 ] = rArgs.maRuns;
+ return true;
+}
+
+void MultiSalLayout::AdjustLayout( ImplLayoutArgs& rArgs )
+{
+ SalLayout::AdjustLayout( rArgs );
+ ImplLayoutArgs aMultiArgs = rArgs;
+ std::unique_ptr<DeviceCoordinate[]> pJustificationArray;
+
+ if( !rArgs.mpDXArray && rArgs.mnLayoutWidth )
+ {
+ // for stretched text in a MultiSalLayout the target width needs to be
+ // distributed by individually adjusting its virtual character widths
+ DeviceCoordinate nTargetWidth = aMultiArgs.mnLayoutWidth;
+ nTargetWidth *= mnUnitsPerPixel; // convert target width to base font units
+ 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;
+ pJustificationArray.reset(new DeviceCoordinate[nCharCount]);
+ FillDXArray( pJustificationArray.get() );
+ // #i17359# multilayout is not simplified yet, so calculating the
+ // unjustified width needs handholding; also count the number of
+ // stretchable virtual char widths
+ DeviceCoordinate nOrigWidth = 0;
+ int nStretchable = 0;
+ for( int i = 0; i < nCharCount; ++i )
+ {
+ // convert array from widths to sum of widths
+ nOrigWidth += pJustificationArray[i];
+ if( pJustificationArray[i] > 0 )
+ ++nStretchable;
+ }
+
+ // now we are able to distribute the extra width over the virtual char widths
+ if( nOrigWidth && (nTargetWidth != nOrigWidth) )
+ {
+ DeviceCoordinate nDiffWidth = nTargetWidth - nOrigWidth;
+ DeviceCoordinate nWidthSum = 0;
+ for( int i = 0; i < nCharCount; ++i )
+ {
+ DeviceCoordinate nJustWidth = pJustificationArray[i];
+ if( (nJustWidth > 0) && (nStretchable > 0) )
+ {
+ DeviceCoordinate nDeltaWidth = nDiffWidth / nStretchable;
+ nJustWidth += nDeltaWidth;
+ nDiffWidth -= nDeltaWidth;
+ --nStretchable;
+ }
+ nWidthSum += nJustWidth;
+ pJustificationArray[i] = nWidthSum;
+ }
+ if( nWidthSum != nTargetWidth )
+ pJustificationArray[ nCharCount-1 ] = nTargetWidth;
+
+ // the justification array is still in base level units
+ // => convert it to pixel units
+ if( mnUnitsPerPixel > 1 )
+ {
+ for( int i = 0; i < nCharCount; ++i )
+ {
+ DeviceCoordinate nVal = pJustificationArray[ i ];
+ nVal += (mnUnitsPerPixel + 1) / 2;
+ pJustificationArray[ i ] = nVal / mnUnitsPerPixel;
+ }
+ }
+
+ // change the mpDXArray temporarily (just for the justification)
+ aMultiArgs.mpDXArray = pJustificationArray.get();
+ }
+ }
+
+ // 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 };
+
+ Point aPos;
+ int nLevel = 0, n;
+ for( n = 0; n < mnLevel; ++n )
+ {
+ // now adjust the individual components
+ if( n > 0 )
+ {
+ aMultiArgs.maRuns = maFallbackRuns[ n-1 ];
+ aMultiArgs.mnFlags |= SalLayoutFlags::ForFallback;
+ }
+ mpLayouts[n]->AdjustLayout( aMultiArgs );
+
+ // 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
+ long nXPos = 0;
+ double fUnitMul = 1.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
+ fUnitMul = mnUnitsPerPixel;
+ fUnitMul /= mpLayouts[n]->GetUnitsPerPixel();
+ long nNewPos = static_cast<long>(nXPos/fUnitMul + 0.5);
+ mpLayouts[n]->MoveGlyph( nStartOld[n], nNewPos );
+ }
+ else
+ {
+ n = 0; // keep NotDef in base level
+ fUnitMul = 1.0;
+ }
+
+ if( n > 0 )
+ {
+ // drop the NotDef glyphs in the base layout run if a fallback run exists
+ while (
+ (maFallbackRuns[n-1].PosIsInRun(pGlyphs[nFirstValid]->charPos())) &&
+ (!maFallbackRuns[n].PosIsInAnyRun(pGlyphs[nFirstValid]->charPos()))
+ )
+ {
+ mpLayouts[0]->DropGlyph( nStartOld[0] );
+ nStartOld[0] = nStartNew[0];
+ bValid[nFirstValid] = mpLayouts[0]->GetNextGlyph(&pGlyphs[nFirstValid], aPos, nStartNew[0]);
+
+ if( !bValid[nFirstValid] )
+ break;
+ }
+ }
+
+ // skip to end of layout run and calculate its advance width
+ DeviceCoordinate nRunAdvance = 0;
+ bool bKeepNotDef = (nFBLevel >= nLevel);
+ for(;;)
+ {
+ nRunAdvance += pGlyphs[n]->m_nNewWidth;
+
+ // 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 (aMultiArgs.mpDXArray &&
+ nRunVisibleEndChar < mnEndCharPos &&
+ nRunVisibleEndChar >= mnMinCharPos &&
+ pGlyphs[n]->charPos() < mnEndCharPos &&
+ pGlyphs[n]->charPos() >= mnMinCharPos)
+ {
+ if (vRtl[nActiveCharPos - mnMinCharPos])
+ {
+ if (aMultiArgs.mpDXArray[nRunVisibleEndChar-mnMinCharPos]
+ >= aMultiArgs.mpDXArray[pGlyphs[n]->charPos() - mnMinCharPos])
+ {
+ nRunVisibleEndChar = pGlyphs[n]->charPos();
+ }
+ }
+ else if (aMultiArgs.mpDXArray[nRunVisibleEndChar-mnMinCharPos]
+ <= aMultiArgs.mpDXArray[pGlyphs[n]->charPos() - mnMinCharPos])
+ {
+ nRunVisibleEndChar = pGlyphs[n]->charPos();
+ }
+ }
+ }
+
+ // if a justification array is available
+ // => use it directly to calculate the corresponding run width
+ if( aMultiArgs.mpDXArray )
+ {
+ // 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 -= aMultiArgs.mpDXArray[nRunVisibleEndChar - 1 - mnMinCharPos];
+ if (nLastRunEndChar > mnMinCharPos && nLastRunEndChar <= mnEndCharPos)
+ nRunAdvance += aMultiArgs.mpDXArray[nLastRunEndChar - 1 - mnMinCharPos];
+ }
+ else
+ {
+ if (nRunVisibleEndChar >= mnMinCharPos)
+ nRunAdvance += aMultiArgs.mpDXArray[nRunVisibleEndChar - mnMinCharPos];
+ if (nLastRunEndChar >= mnMinCharPos)
+ nRunAdvance -= aMultiArgs.mpDXArray[nLastRunEndChar - mnMinCharPos];
+ }
+ nLastRunEndChar = nRunVisibleEndChar;
+ nRunVisibleEndChar = pGlyphs[nFirstValid]->charPos();
+ // the requested width is still in pixel units
+ // => convert it to base level font units
+ nRunAdvance *= mnUnitsPerPixel;
+ }
+ else
+ {
+ // the measured width is still in fallback font units
+ // => convert it to base level font units
+ if( n > 0 ) // optimization: because (fUnitMul==1.0) for (n==0)
+ nRunAdvance = static_cast<long>(nRunAdvance*fUnitMul + 0.5);
+ }
+
+ // calculate new x position (in base level units)
+ nXPos += nRunAdvance;
+
+ // prepare for next fallback run
+ nActiveCharPos = pGlyphs[nFirstValid]->charPos();
+ // it essential that the runs don't get ahead of themselves and in the
+ // if( bKeepNotDef && !bNeedFallback ) statement above, the next run may
+ // have already been reached on the base level
+ for( int i = nFBLevel; --i >= 0;)
+ {
+ if (maFallbackRuns[i].GetRun(&nRunStart, &nRunEnd, &bRtl))
+ {
+ if (bRtl)
+ {
+ if (nRunStart > nActiveCharPos)
+ maFallbackRuns[i].NextRun();
+ }
+ else
+ {
+ if (nRunEnd <= nActiveCharPos)
+ maFallbackRuns[i].NextRun();
+ }
+ }
+ }
+ }
+
+ mpLayouts[0]->Simplify( true );
+}
+
+void MultiSalLayout::InitFont() const
+{
+ if( mnLevel > 0 )
+ mpLayouts[0]->InitFont();
+}
+
+void MultiSalLayout::DrawText( SalGraphics& rGraphics ) const
+{
+ for( int i = mnLevel; --i >= 0; )
+ {
+ SalLayout& rLayout = *mpLayouts[ i ];
+ rLayout.DrawBase() += maDrawBase;
+ rLayout.DrawOffset() += maDrawOffset;
+ rLayout.InitFont();
+ rLayout.DrawText( rGraphics );
+ rLayout.DrawOffset() -= maDrawOffset;
+ rLayout.DrawBase() -= maDrawBase;
+ }
+ // NOTE: now the baselevel font is active again
+}
+
+sal_Int32 MultiSalLayout::GetTextBreak( DeviceCoordinate nMaxWidth, DeviceCoordinate nCharExtra, int nFactor ) const
+{
+ if( mnLevel <= 0 )
+ return -1;
+ if( mnLevel == 1 )
+ return mpLayouts[0]->GetTextBreak( nMaxWidth, nCharExtra, nFactor );
+
+ int nCharCount = mnEndCharPos - mnMinCharPos;
+ std::unique_ptr<DeviceCoordinate[]> const pCharWidths(new DeviceCoordinate[nCharCount]);
+ std::unique_ptr<DeviceCoordinate[]> const pFallbackCharWidths(new DeviceCoordinate[nCharCount]);
+ mpLayouts[0]->FillDXArray( pCharWidths.get() );
+
+ for( int n = 1; n < mnLevel; ++n )
+ {
+ SalLayout& rLayout = *mpLayouts[ n ];
+ rLayout.FillDXArray( pFallbackCharWidths.get() );
+ double fUnitMul = mnUnitsPerPixel;
+ fUnitMul /= rLayout.GetUnitsPerPixel();
+ for( int i = 0; i < nCharCount; ++i )
+ {
+ if( pCharWidths[ i ] == 0 )
+ {
+ DeviceCoordinate w = pFallbackCharWidths[i];
+ w = static_cast<DeviceCoordinate>(w * fUnitMul + 0.5);
+ pCharWidths[ i ] = w;
+ }
+ }
+ }
+
+ DeviceCoordinate nWidth = 0;
+ for( int i = 0; i < nCharCount; ++i )
+ {
+ nWidth += pCharWidths[ i ] * nFactor;
+ if( nWidth > nMaxWidth )
+ return (i + mnMinCharPos);
+ nWidth += nCharExtra;
+ }
+
+ return -1;
+}
+
+DeviceCoordinate MultiSalLayout::FillDXArray( DeviceCoordinate* pCharWidths ) const
+{
+ DeviceCoordinate nMaxWidth = 0;
+
+ // prepare merging of fallback levels
+ std::unique_ptr<DeviceCoordinate[]> pTempWidths;
+ const int nCharCount = mnEndCharPos - mnMinCharPos;
+ if( pCharWidths )
+ {
+ for( int i = 0; i < nCharCount; ++i )
+ pCharWidths[i] = 0;
+ pTempWidths.reset(new DeviceCoordinate[nCharCount]);
+ }
+
+ for( int n = mnLevel; --n >= 0; )
+ {
+ // query every fallback level
+ DeviceCoordinate nTextWidth = mpLayouts[n]->FillDXArray( pTempWidths.get() );
+ if( !nTextWidth )
+ continue;
+ // merge results from current level
+ double fUnitMul = mnUnitsPerPixel;
+ fUnitMul /= mpLayouts[n]->GetUnitsPerPixel();
+ nTextWidth = static_cast<DeviceCoordinate>(nTextWidth * fUnitMul + 0.5);
+ if( nMaxWidth < nTextWidth )
+ nMaxWidth = nTextWidth;
+ if( !pCharWidths )
+ continue;
+ // calculate virtual char widths using most probable fallback layout
+ for( int i = 0; i < nCharCount; ++i )
+ {
+ // #i17359# restriction:
+ // one char cannot be resolved from different fallbacks
+ if( pCharWidths[i] != 0 )
+ continue;
+ DeviceCoordinate nCharWidth = pTempWidths[i];
+ if( !nCharWidth )
+ continue;
+ nCharWidth = static_cast<DeviceCoordinate>(nCharWidth * fUnitMul + 0.5);
+ pCharWidths[i] = nCharWidth;
+ }
+ }
+
+ return nMaxWidth;
+}
+
+void MultiSalLayout::GetCaretPositions( int nMaxIndex, long* pCaretXArray ) const
+{
+ SalLayout& rLayout = *mpLayouts[ 0 ];
+ rLayout.GetCaretPositions( nMaxIndex, pCaretXArray );
+
+ if( mnLevel > 1 )
+ {
+ std::unique_ptr<long[]> const pTempPos(new long[nMaxIndex]);
+ for( int n = 1; n < mnLevel; ++n )
+ {
+ mpLayouts[ n ]->GetCaretPositions( nMaxIndex, pTempPos.get() );
+ double fUnitMul = mnUnitsPerPixel;
+ fUnitMul /= mpLayouts[n]->GetUnitsPerPixel();
+ for( int i = 0; i < nMaxIndex; ++i )
+ if( pTempPos[i] >= 0 )
+ {
+ long w = pTempPos[i];
+ w = static_cast<long>(w*fUnitMul + 0.5);
+ pCaretXArray[i] = w;
+ }
+ }
+ }
+}
+
+bool MultiSalLayout::GetNextGlyph(const GlyphItem** pGlyph,
+ Point& rPos, int& nStart,
+ const PhysicalFontFace** pFallbackFont,
+ int* const pFallbackLevel) const
+{
+ // NOTE: nStart is tagged with current font index
+ int nLevel = static_cast<unsigned>(nStart) >> GF_FONTSHIFT;
+ nStart &= ~GF_FONTMASK;
+ for(; nLevel < mnLevel; ++nLevel, nStart=0 )
+ {
+ GenericSalLayout& rLayout = *mpLayouts[ nLevel ];
+ rLayout.InitFont();
+ const PhysicalFontFace* pFontFace = rLayout.GetFont().GetFontFace();
+ if (rLayout.GetNextGlyph(pGlyph, rPos, nStart))
+ {
+ int nFontTag = nLevel << GF_FONTSHIFT;
+ nStart |= nFontTag;
+ if (pFallbackFont)
+ *pFallbackFont = pFontFace;
+ if (pFallbackLevel)
+ *pFallbackLevel = nLevel;
+ rPos += maDrawBase;
+ rPos += maDrawOffset;
+ return true;
+ }
+ }
+
+ // #111016# reset to base level font when done
+ mpLayouts[0]->InitFont();
+ return false;
+}
+
+bool MultiSalLayout::GetOutline(basegfx::B2DPolyPolygonVector& rPPV) const
+{
+ bool bRet = false;
+
+ for( int i = mnLevel; --i >= 0; )
+ {
+ SalLayout& rLayout = *mpLayouts[ i ];
+ rLayout.DrawBase() = maDrawBase;
+ rLayout.DrawOffset() += maDrawOffset;
+ rLayout.InitFont();
+ bRet |= rLayout.GetOutline(rPPV);
+ rLayout.DrawOffset() -= maDrawOffset;
+ }
+
+ return bRet;
+}
+
+bool MultiSalLayout::IsKashidaPosValid(int nCharPos) const
+{
+ // Check the base layout
+ bool bValid = mpLayouts[0]->IsKashidaPosValid(nCharPos);
+
+ // If base layout returned false, it might be because the character was not
+ // supported there, so we check fallback layouts.
+ if (!bValid)
+ {
+ for (int i = 1; i < mnLevel; ++i)
+ {
+ // - 1 because there is no fallback run for the base layout, IIUC.
+ if (maFallbackRuns[i - 1].PosIsInAnyRun(nCharPos))
+ {
+ bValid = mpLayouts[i]->IsKashidaPosValid(nCharPos);
+ break;
+ }
+ }
+ }
+
+ return bValid;
+}
+
+const SalLayoutGlyphs* SalLayout::GetGlyphs() const
+{
+ // No access to the glyphs by default.
+ return nullptr;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/salmisc.cxx b/vcl/source/gdi/salmisc.cxx
new file mode 100644
index 000000000..a6a5e3627
--- /dev/null
+++ b/vcl/source/gdi/salmisc.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/bitmapaccess.hxx>
+#include <vcl/salgtype.hxx>
+#include <bmpfast.hxx>
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+#include <tools/helpers.hxx>
+#include <memory>
+
+#define IMPL_CASE_GET_FORMAT( Format ) \
+case( ScanlineFormat::Format ): \
+ pFncGetPixel = BitmapReadAccess::GetPixelFor##Format; \
+break
+
+#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++; \
+}
+
+#define TC_TO_PAL_COLORS 4096
+
+static long ImplIndexFromColor( const BitmapColor& rCol )
+{
+#if TC_TO_PAL_COLORS == 4096
+
+ return( ( ( static_cast<long>(rCol.GetBlue()) >> 4) << 8 ) |
+ ( ( static_cast<long>(rCol.GetGreen()) >> 4 ) << 4 ) |
+ ( static_cast<long>(rCol.GetRed()) >> 4 ) );
+
+#elif TC_TO_PAL_COLORS == 32768
+
+ return( ( ( (long) rCol.GetBlue() >> 3) << 10 ) |
+ ( ( (long) rCol.GetGreen() >> 3 ) << 5 ) |
+ ( (long) rCol.GetRed() >> 3 ) );
+
+#endif
+}
+
+static void ImplPALToPAL( const BitmapBuffer& rSrcBuffer, BitmapBuffer& rDstBuffer,
+ FncGetPixel pFncGetPixel, FncSetPixel pFncSetPixel,
+ Scanline* pSrcScanMap, Scanline* pDstScanMap, long const * pMapX, const long* pMapY )
+{
+ const 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 (long nActY = 0; nActY < rDstBuffer.mnHeight; ++nActY)
+ {
+ long nMapY = pMapY[nActY];
+ Scanline pSrcScan(pSrcScanMap[nMapY]), pDstScan(pDstScanMap[nActY]);
+
+ for (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, long const * pMapX, const long* pMapY )
+{
+ const 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 ] );
+ long nMapX;
+
+ for (long nActY = 0; nActY < rDstBuffer.mnHeight; ++nActY)
+ {
+ long nMapY = pMapY[nActY];
+ Scanline pSrcScan(pSrcScanMap[nMapY]), pDstScan(pDstScanMap[nActY]);
+
+ for (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::N4BitMsnPal )
+ {
+ long nMapX;
+
+ for (long nActY = 0; nActY < rDstBuffer.mnHeight; ++nActY)
+ {
+ long nMapY = pMapY[nActY];
+ Scanline pSrcScan(pSrcScanMap[nMapY]), pDstScan(pDstScanMap[nActY]);
+
+ for (long nX = 0; nX < rDstBuffer.mnWidth;)
+ {
+ nMapX = pMapX[ nX ];
+ pFncSetPixel( pDstScan, nX++,
+ pColBuf[ ( pSrcScan[ nMapX >> 1 ] >> ( nMapX & 1 ? 0 : 4 ) ) & 0x0f ],
+ rDstMask );
+ }
+
+ DOUBLE_SCANLINES();
+ }
+ }
+ else if( RemoveScanline( rSrcBuffer.mnFormat ) == ScanlineFormat::N8BitPal )
+ {
+ for (long nActY = 0; nActY < rDstBuffer.mnHeight; ++nActY)
+ {
+ long nMapY = pMapY[nActY];
+ Scanline pSrcScan(pSrcScanMap[nMapY]), pDstScan(pDstScanMap[nActY]);
+
+ for (long nX = 0; nX < rDstBuffer.mnWidth; ++nX)
+ pFncSetPixel( pDstScan, nX, pColBuf[ pSrcScan[ pMapX[ nX ] ] ], rDstMask );
+
+ DOUBLE_SCANLINES();
+ }
+ }
+ else
+ {
+ for (long nActY = 0; nActY < rDstBuffer.mnHeight; ++nActY)
+ {
+ long nMapY = pMapY[nActY];
+ Scanline pSrcScan(pSrcScanMap[nMapY]), pDstScan(pDstScanMap[nActY]);
+
+ for (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, long const * pMapX, const long* pMapY )
+{
+ const 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 (long nActY = 0; nActY < rDstBuffer.mnHeight; ++nActY)
+ {
+ long nMapY = pMapY[nActY];
+ Scanline pSrcScan(pSrcScanMap[nMapY]), pDstScan(pDstScanMap[nActY]);
+
+ for (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 (long nActY = 0; nActY < rDstBuffer.mnHeight; ++nActY)
+ {
+ long nMapY = pMapY[nActY];
+ Scanline pSrcScan(pSrcScanMap[nMapY]), pDstScan(pDstScanMap[nActY]);
+
+ for (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, long const * pMapX, const long* pMapY )
+{
+ const 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( long nR = 0; nR < 16; nR++ )
+ {
+ for( long nG = 0; nG < 16; nG++ )
+ {
+ for( 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 (long nActY = 0; nActY < rDstBuffer.mnHeight; ++nActY)
+ {
+ long nMapY = pMapY[nActY];
+ Scanline pSrcScan(pSrcScanMap[nMapY]), pDstScan(pDstScanMap[nActY]);
+
+ for (long nX = 0; nX < rDstBuffer.mnWidth; ++nX)
+ {
+ aIndex.SetIndex( pColToPalMap[ ImplIndexFromColor( pFncGetPixel( pSrcScan, pMapX[ nX ], rSrcMask ) ) ] );
+ pFncSetPixel( pDstScan, nX, aIndex, rDstMask );
+ }
+
+ DOUBLE_SCANLINES();
+ }
+}
+
+std::unique_ptr<BitmapBuffer> StretchAndConvert(
+ const BitmapBuffer& rSrcBuffer, const SalTwoRect& rTwoRect,
+ ScanlineFormat nDstBitmapFormat, const BitmapPalette* pDstPal, const ColorMask* pDstMask )
+{
+ FncGetPixel pFncGetPixel;
+ FncSetPixel pFncSetPixel;
+ std::unique_ptr<BitmapBuffer> pDstBuffer(new BitmapBuffer);
+
+ // set function for getting pixels
+ switch( RemoveScanline( rSrcBuffer.mnFormat ) )
+ {
+ IMPL_CASE_GET_FORMAT( N1BitMsbPal );
+ IMPL_CASE_GET_FORMAT( N1BitLsbPal );
+ IMPL_CASE_GET_FORMAT( N4BitMsnPal );
+ IMPL_CASE_GET_FORMAT( N4BitLsnPal );
+ IMPL_CASE_GET_FORMAT( N8BitPal );
+ IMPL_CASE_GET_FORMAT( N8BitTcMask );
+ IMPL_CASE_GET_FORMAT( N24BitTcBgr );
+ IMPL_CASE_GET_FORMAT( N24BitTcRgb );
+ IMPL_CASE_GET_FORMAT( N32BitTcAbgr );
+ IMPL_CASE_GET_FORMAT( N32BitTcArgb );
+ IMPL_CASE_GET_FORMAT( N32BitTcBgra );
+ IMPL_CASE_GET_FORMAT( N32BitTcRgba );
+ IMPL_CASE_GET_FORMAT( N32BitTcMask );
+
+ default:
+ // should never come here
+ // initialize pFncGetPixel to something valid that is
+ // least likely to crash
+ pFncGetPixel = BitmapReadAccess::GetPixelForN1BitMsbPal;
+ OSL_FAIL( "unknown read format" );
+ break;
+ }
+
+ // set function for setting pixels
+ const ScanlineFormat nDstScanlineFormat = RemoveScanline( nDstBitmapFormat );
+ switch( nDstScanlineFormat )
+ {
+ IMPL_CASE_SET_FORMAT( N1BitMsbPal, 1 );
+ IMPL_CASE_SET_FORMAT( N1BitLsbPal, 1 );
+ IMPL_CASE_SET_FORMAT( N4BitMsnPal, 1 );
+ IMPL_CASE_SET_FORMAT( N4BitLsnPal, 4 );
+ IMPL_CASE_SET_FORMAT( N8BitPal, 8 );
+ IMPL_CASE_SET_FORMAT( N8BitTcMask, 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;
+ long nScanlineBase;
+ bool bFail = o3tl::checked_multiply<long>(pDstBuffer->mnBitCount, pDstBuffer->mnWidth, nScanlineBase);
+ if (bFail)
+ {
+ SAL_WARN("vcl.gdi", "checked multiply failed");
+ pDstBuffer->mpBits = nullptr;
+ return nullptr;
+ }
+ pDstBuffer->mnScanlineSize = AlignedWidth4Bytes(nScanlineBase);
+ if (pDstBuffer->mnScanlineSize < nScanlineBase/8)
+ {
+ SAL_WARN("vcl.gdi", "scanline calculation wraparound");
+ pDstBuffer->mpBits = nullptr;
+ return nullptr;
+ }
+ try
+ {
+ pDstBuffer->mpBits = new sal_uInt8[ pDstBuffer->mnScanlineSize * pDstBuffer->mnHeight ];
+ }
+ catch( const std::bad_alloc& )
+ {
+ // memory exception, clean up
+ pDstBuffer->mpBits = nullptr;
+ return nullptr;
+ }
+
+ // do we need a destination palette or color mask?
+ if( ( nDstScanlineFormat == ScanlineFormat::N1BitMsbPal ) ||
+ ( nDstScanlineFormat == ScanlineFormat::N1BitLsbPal ) ||
+ ( nDstScanlineFormat == ScanlineFormat::N4BitMsnPal ) ||
+ ( nDstScanlineFormat == ScanlineFormat::N4BitLsnPal ) ||
+ ( nDstScanlineFormat == ScanlineFormat::N8BitPal ) )
+ {
+ assert(pDstPal && "destination buffer requires palette");
+ if (!pDstPal)
+ {
+ return nullptr;
+ }
+ pDstBuffer->maPalette = *pDstPal;
+ }
+ else if( ( nDstScanlineFormat == ScanlineFormat::N8BitTcMask ) ||
+ ( nDstScanlineFormat == ScanlineFormat::N32BitTcMask ) )
+ {
+ assert(pDstMask && "destination buffer requires color mask");
+ if (!pDstMask)
+ {
+ return nullptr;
+ }
+ pDstBuffer->maColorMask = *pDstMask;
+ }
+
+ // short circuit the most important conversions
+ bool bFastConvert = ImplFastBitmapConversion( *pDstBuffer, rSrcBuffer, rTwoRect );
+ if( bFastConvert )
+ return pDstBuffer;
+
+ std::unique_ptr<Scanline[]> pSrcScan;
+ std::unique_ptr<Scanline[]> pDstScan;
+ std::unique_ptr<long[]> pMapX;
+ std::unique_ptr<long[]> pMapY;
+
+ try
+ {
+ pSrcScan.reset(new Scanline[rSrcBuffer.mnHeight]);
+ pDstScan.reset(new Scanline[pDstBuffer->mnHeight]);
+ pMapX.reset(new long[pDstBuffer->mnWidth]);
+ pMapY.reset(new long[pDstBuffer->mnHeight]);
+ }
+ catch( const std::bad_alloc& )
+ {
+ // memory exception, clean up
+ // remark: the buffer ptr causing the exception
+ // is still NULL here
+ return nullptr;
+ }
+
+ // horizontal mapping table
+ if( (pDstBuffer->mnWidth != rTwoRect.mnSrcWidth) && (pDstBuffer->mnWidth != 0) )
+ {
+ const double fFactorX = static_cast<double>(rTwoRect.mnSrcWidth) / pDstBuffer->mnWidth;
+
+ for (long i = 0; i < pDstBuffer->mnWidth; ++i)
+ pMapX[ i ] = rTwoRect.mnSrcX + static_cast<int>( i * fFactorX );
+ }
+ else
+ {
+ for (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 (long i = 0; i < pDstBuffer->mnHeight; ++i)
+ pMapY[ i ] = rTwoRect.mnSrcY + static_cast<int>( i * fFactorY );
+ }
+ else
+ {
+ for (long i = 0, nTmp = rTwoRect.mnSrcY; i < pDstBuffer->mnHeight; ++i)
+ pMapY[ i ] = nTmp++;
+ }
+
+ // source scanline buffer
+ Scanline pTmpScan;
+ 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 (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 (long i = 0; i < pDstBuffer->mnHeight; i++, pTmpScan += nOffset)
+ pDstScan[ i ] = pTmpScan;
+
+ // do buffer scaling and conversion
+ if( rSrcBuffer.mnBitCount <= 8 && pDstBuffer->mnBitCount <= 8 )
+ {
+ ImplPALToPAL( rSrcBuffer, *pDstBuffer, pFncGetPixel, pFncSetPixel,
+ pSrcScan.get(), pDstScan.get(), pMapX.get(), pMapY.get() );
+ }
+ else if( rSrcBuffer.mnBitCount <= 8 && pDstBuffer->mnBitCount > 8 )
+ {
+ ImplPALToTC( rSrcBuffer, *pDstBuffer, pFncGetPixel, pFncSetPixel,
+ pSrcScan.get(), pDstScan.get(), pMapX.get(), pMapY.get() );
+ }
+ else if( rSrcBuffer.mnBitCount > 8 && pDstBuffer->mnBitCount > 8 )
+ {
+ ImplTCToTC( rSrcBuffer, *pDstBuffer, pFncGetPixel, pFncSetPixel,
+ pSrcScan.get(), pDstScan.get(), pMapX.get(), pMapY.get() );
+ }
+ else
+ {
+ ImplTCToPAL( rSrcBuffer, *pDstBuffer, pFncGetPixel, pFncSetPixel,
+ pSrcScan.get(), pDstScan.get(), pMapX.get(), pMapY.get() );
+ }
+
+ return pDstBuffer;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/scrptrun.cxx b/vcl/source/gdi/scrptrun.cxx
new file mode 100644
index 000000000..b18816cac
--- /dev/null
+++ b/vcl/source/gdi/scrptrun.cxx
@@ -0,0 +1,247 @@
+/*
+ *******************************************************************************
+ *
+ * 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 <unicode/utypes.h>
+#include <unicode/uscript.h>
+
+#include <scrptrun.h>
+#include <algorithm>
+
+namespace {
+
+struct PairIndices
+{
+ int8_t ma00[0xff];
+ int8_t ma20[0x7f];
+ int8_t ma30[0x7f];
+
+ PairIndices()
+ {
+ std::fill_n(ma00, 0xff, -1);
+ std::fill_n(ma20, 0x7f, -1);
+ std::fill_n(ma30, 0x7f, -1);
+
+ // characters in the range 0x0000 - 0x007e (inclusive)
+ // ascii paired punctuation
+ ma00[0x28] = 0;
+ ma00[0x29] = 1;
+ ma00[0x3c] = 2;
+ ma00[0x3e] = 3;
+ ma00[0x5b] = 4;
+ ma00[0x5d] = 5;
+ ma00[0x7b] = 6;
+ ma00[0x7d] = 7;
+ // guillemets
+ ma00[0xab] = 8;
+ ma00[0xbb] = 9;
+
+ // characters in the range 0x2000 - 0x207e (inclusive)
+ // general punctuation
+ ma20[0x18] = 10;
+ ma20[0x19] = 11;
+ ma20[0x1c] = 12;
+ ma20[0x1d] = 13;
+ ma20[0x39] = 14;
+ ma20[0x3a] = 15;
+
+ // characters in the range 0x3000 - 0x307e (inclusive)
+ // chinese paired punctuation
+ ma30[0x08] = 16;
+ ma30[0x09] = 17;
+ ma30[0x0a] = 18;
+ ma30[0x0b] = 19;
+ ma30[0x0c] = 20;
+ ma30[0x0d] = 21;
+ ma30[0x0e] = 22;
+ ma30[0x0f] = 23;
+ ma30[0x10] = 24;
+ ma30[0x11] = 25;
+ ma30[0x14] = 26;
+ ma30[0x15] = 27;
+ ma30[0x16] = 28;
+ ma30[0x17] = 29;
+ ma30[0x18] = 30;
+ ma30[0x19] = 31;
+ ma30[0x1a] = 32;
+ ma30[0x1b] = 33;
+ }
+
+ int32_t getPairIndex(UChar32 ch) const
+ {
+ if (ch < 0xff)
+ return ma00[ch];
+ if (ch >= 0x2000 && ch < 0x207f)
+ return ma20[ch - 0x2000];
+ if (ch >= 0x3000 && ch < 0x307f)
+ return ma30[ch - 0x3000];
+ return -1;
+ }
+
+};
+
+// There are three Unicode script codes for Japanese text, but only one
+// OpenType script tag, so we want to keep them in one run as splitting is
+// pointless for the purpose of OpenType shaping.
+UScriptCode getScript(UChar32 ch, UErrorCode* status)
+{
+ UScriptCode script = uscript_getScript(ch, status);
+ if (U_FAILURE(*status))
+ return script;
+ if (script == USCRIPT_KATAKANA || script == USCRIPT_KATAKANA_OR_HIRAGANA)
+ return USCRIPT_HIRAGANA;
+ return script;
+}
+
+}
+
+static 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 (high >= 0xD800 && high <= 0xDBFF && scriptEnd < charLimit - 1)
+ {
+ UChar low = charArray[scriptEnd + 1];
+
+ // if it is followed by a low surrogate,
+ // consume it and form the full character
+ if (low >= 0xDC00 && low <= 0xDFFF) {
+ ch = (high - 0xD800) * 0x0400 + low - 0xDC00 + 0x10000;
+ 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/svmconverter.cxx b/vcl/source/gdi/svmconverter.cxx
new file mode 100644
index 000000000..d65b6c52f
--- /dev/null
+++ b/vcl/source/gdi/svmconverter.cxx
@@ -0,0 +1,1245 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <string.h>
+
+#include <o3tl/safeint.hxx>
+#include <osl/thread.h>
+#include <tools/fract.hxx>
+#include <tools/stream.hxx>
+#include <vcl/dibtools.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/lineinfo.hxx>
+#include <vcl/metaact.hxx>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+#include <TypeSerializer.hxx>
+#include <svmconverter.hxx>
+#include <memory>
+#include <stack>
+
+// 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_uInt32 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);
+ }
+}
+
+#define LF_FACESIZE 32
+
+void static lcl_error( SvStream& rIStm, const SvStreamEndian& nOldFormat, sal_uLong nPos)
+{
+ rIStm.SetError(SVSTREAM_FILEFORMAT_ERROR);
+ rIStm.SetEndian(nOldFormat);
+ rIStm.Seek(nPos);
+ return;
+}
+void SVMConverter::ImplConvertFromSVM1( SvStream& rIStm, GDIMetaFile& rMtf )
+{
+ const sal_uLong 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()
+ || ( memcmp( aCode, "SVGDI", sizeof( aCode ) ) != 0 )
+ || ( nVersion != 200 ) )
+ {
+ 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<std::unique_ptr<LineInfo>> aLIStack;
+ ScopedVclPtrInstance< VirtualDevice > aFontVDev;
+ rtl_TextEncoding eActualCharSet = osl_getThreadTextEncoding();
+ bool bFatLine = false;
+
+ tools::Polygon aActionPoly;
+ tools::Rectangle aRect;
+ Point aPt, aPt1;
+ Size aSz;
+ Color aActionColor;
+
+ sal_uInt32 nUnicodeCommentStreamPos = 0;
+ sal_Int32 nUnicodeCommentActionNumber = 0;
+
+ rMtf.SetPrefSize(aPrefSz);
+
+ MapMode aMapMode;
+ if (ImplReadMapMode(rIStm, aMapMode)) // MapMode
+ rMtf.SetPrefMapMode(aMapMode);
+
+ sal_Int32 nActions(0);
+ rIStm.ReadInt32(nActions); // Action count
+ if (nActions < 0)
+ {
+ SAL_WARN("vcl.gdi", "svm claims negative action count (" << nActions << ")");
+ nActions = 0;
+ }
+
+ const size_t nMinActionSize = sizeof(sal_uInt16) + sizeof(sal_Int32);
+ const size_t nMaxPossibleActions = rIStm.remainingSize() / nMinActionSize;
+ if (o3tl::make_unsigned(nActions) > nMaxPossibleActions)
+ {
+ SAL_WARN("vcl.gdi", "svm claims more actions (" << nActions << ") than stream could provide, truncating");
+ nActions = nMaxPossibleActions;
+ }
+
+ size_t nLastPolygonAction(0);
+
+ TypeSerializer aSerializer(rIStm);
+
+ for (sal_Int32 i = 0; i < nActions && rIStm.good(); ++i)
+ {
+ sal_Int16 nType(0);
+ rIStm.ReadInt16(nType);
+ sal_Int32 nActBegin = rIStm.Tell();
+ sal_Int32 nActionSize(0);
+ rIStm.ReadInt32(nActionSize);
+
+ SAL_WARN_IF( ( nType > 33 ) && ( nType < 1024 ), "vcl.gdi", "Unknown GDIMetaAction while converting!" );
+
+ switch( nType )
+ {
+ case GDI_PIXEL_ACTION:
+ {
+ aSerializer.readPoint(aPt);
+ ImplReadColor( rIStm, aActionColor );
+ rMtf.AddAction( new MetaPixelAction( aPt, aActionColor ) );
+ }
+ break;
+
+ case GDI_POINT_ACTION:
+ {
+ aSerializer.readPoint(aPt);
+ rMtf.AddAction( new MetaPointAction( aPt ) );
+ }
+ break;
+
+ case GDI_LINE_ACTION:
+ {
+ aSerializer.readPoint(aPt);
+ aSerializer.readPoint(aPt1);
+ rMtf.AddAction( new MetaLineAction( aPt, aPt1, aLineInfo ) );
+ }
+ break;
+
+ case GDI_LINEJOIN_ACTION :
+ {
+ sal_Int16 nLineJoin(0);
+ rIStm.ReadInt16( nLineJoin );
+ aLineInfo.SetLineJoin(static_cast<basegfx::B2DLineJoin>(nLineJoin));
+ }
+ break;
+
+ case GDI_LINECAP_ACTION :
+ {
+ sal_Int16 nLineCap(0);
+ rIStm.ReadInt16( nLineCap );
+ aLineInfo.SetLineCap(static_cast<css::drawing::LineCap>(nLineCap));
+ }
+ break;
+
+ case GDI_LINEDASHDOT_ACTION :
+ {
+ sal_Int16 a(0);
+ sal_Int32 b(0);
+
+ rIStm.ReadInt16( a ); aLineInfo.SetDashCount(a);
+ rIStm.ReadInt32( b ); aLineInfo.SetDashLen(b);
+ rIStm.ReadInt16( a ); aLineInfo.SetDotCount(a);
+ rIStm.ReadInt32( b ); aLineInfo.SetDotLen(b);
+ rIStm.ReadInt32( b ); aLineInfo.SetDistance(b);
+
+ if(((aLineInfo.GetDashCount() && aLineInfo.GetDashLen())
+ || (aLineInfo.GetDotCount() && aLineInfo.GetDotLen()))
+ && aLineInfo.GetDistance())
+ {
+ aLineInfo.SetStyle(LineStyle::Dash);
+ }
+ }
+ break;
+
+ case GDI_EXTENDEDPOLYGON_ACTION :
+ {
+ // read the tools::PolyPolygon in every case
+ tools::PolyPolygon aInputPolyPolygon;
+ ImplReadExtendedPolyPolygonAction(rIStm, aInputPolyPolygon);
+
+ // now check if it can be set somewhere
+ if(nLastPolygonAction < rMtf.GetActionSize())
+ {
+ MetaPolyLineAction* pPolyLineAction = dynamic_cast< MetaPolyLineAction* >(rMtf.GetAction(nLastPolygonAction));
+
+ if(pPolyLineAction)
+ {
+ // replace MetaPolyLineAction when we have a single polygon. Do not rely on the
+ // same point count; the originally written GDI_POLYLINE_ACTION may have been
+ // Subdivided for better quality for older usages
+ if(1 == aInputPolyPolygon.Count())
+ {
+ rMtf.ReplaceAction(
+ new MetaPolyLineAction(
+ aInputPolyPolygon.GetObject(0),
+ pPolyLineAction->GetLineInfo()),
+ nLastPolygonAction);
+ }
+ }
+ else
+ {
+ MetaPolyPolygonAction* pPolyPolygonAction = dynamic_cast< MetaPolyPolygonAction* >(rMtf.GetAction(nLastPolygonAction));
+
+ if(pPolyPolygonAction)
+ {
+ // replace MetaPolyPolygonAction when we have a curved polygon. Do rely on the
+ // same sub-polygon count
+ if(pPolyPolygonAction->GetPolyPolygon().Count() == aInputPolyPolygon.Count())
+ {
+ rMtf.ReplaceAction(
+ new MetaPolyPolygonAction(
+ aInputPolyPolygon),
+ nLastPolygonAction);
+ }
+ }
+ else
+ {
+ MetaPolygonAction* pPolygonAction = dynamic_cast< MetaPolygonAction* >(rMtf.GetAction(nLastPolygonAction));
+
+ if(pPolygonAction)
+ {
+ // replace MetaPolygonAction
+ if(1 == aInputPolyPolygon.Count())
+ {
+ rMtf.ReplaceAction(
+ new MetaPolygonAction(
+ aInputPolyPolygon.GetObject(0)),
+ nLastPolygonAction);
+ }
+ }
+ }
+ }
+ }
+ }
+ break;
+
+ case GDI_RECT_ACTION:
+ {
+ ImplReadRect( rIStm, aRect );
+ sal_Int32 nTmp(0), nTmp1(0);
+ rIStm.ReadInt32( nTmp ).ReadInt32( nTmp1 );
+
+ if( nTmp || nTmp1 )
+ rMtf.AddAction( new MetaRoundRectAction( aRect, nTmp, nTmp1 ) );
+ else
+ {
+ rMtf.AddAction( new MetaRectAction( aRect ) );
+
+ if( bFatLine )
+ rMtf.AddAction( new MetaPolyLineAction( aRect, aLineInfo ) );
+ }
+ }
+ break;
+
+ case GDI_ELLIPSE_ACTION:
+ {
+ ImplReadRect( rIStm, aRect );
+
+ if( bFatLine )
+ {
+ const tools::Polygon aPoly( aRect.Center(), aRect.GetWidth() >> 1, aRect.GetHeight() >> 1 );
+
+ rMtf.AddAction( new MetaPushAction( PushFlags::LINECOLOR ) );
+ rMtf.AddAction( new MetaLineColorAction( COL_TRANSPARENT, false ) );
+ rMtf.AddAction( new MetaPolygonAction( aPoly ) );
+ rMtf.AddAction( new MetaPopAction() );
+ rMtf.AddAction( new MetaPolyLineAction( aPoly, aLineInfo ) );
+ }
+ else
+ rMtf.AddAction( new MetaEllipseAction( aRect ) );
+ }
+ break;
+
+ case GDI_ARC_ACTION:
+ {
+ ImplReadRect( rIStm, aRect );
+ aSerializer.readPoint(aPt);
+ aSerializer.readPoint(aPt1);
+
+ if( bFatLine )
+ {
+ const tools::Polygon aPoly( aRect, aPt, aPt1, PolyStyle::Arc );
+
+ rMtf.AddAction( new MetaPushAction( 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( 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( 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( 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( 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) );
+ aFont.SetPitch( static_cast<FontPitch>(nPitch) );
+ aFont.SetAlignment( static_cast<FontAlign>(nAlign) );
+ aFont.SetWeight( ( nWeight == 1 ) ? WEIGHT_LIGHT : ( nWeight == 2 ) ? WEIGHT_NORMAL :
+ ( nWeight == 3 ) ? WEIGHT_BOLD : WEIGHT_DONTKNOW );
+ aFont.SetUnderline( static_cast<FontLineStyle>(nUnderline) );
+ aFont.SetStrikeout( static_cast<FontStrikeout>(nStrikeout) );
+ aFont.SetItalic( bItalic ? ITALIC_NORMAL : ITALIC_NONE );
+ aFont.SetOutline( bOutline );
+ aFont.SetShadow( bShadow );
+ aFont.SetOrientation( 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 );
+ rMtf.AddAction( new MetaTextAction( aPt, aStr, nIndex, nLen ) );
+ }
+
+ if (nActionSize < 24)
+ rIStm.SetError(SVSTREAM_FILEFORMAT_ERROR);
+ else
+ rIStm.Seek(nActBegin + nActionSize);
+ }
+ break;
+
+ case GDI_TEXTARRAY_ACTION:
+ {
+ sal_Int32 nIndex(0), nLen(0), nAryLen(0), nTmp(0);
+ aSerializer.readPoint(aPt);
+ rIStm.ReadInt32( nIndex ).ReadInt32( nLen ).ReadInt32( nTmp ).ReadInt32( nAryLen );
+ if (nTmp > 0)
+ {
+ OString aByteStr = read_uInt8s_ToOString(rIStm, nTmp);
+ sal_uInt8 nTerminator = 0;
+ rIStm.ReadUChar( nTerminator );
+ SAL_WARN_IF( nTerminator != 0, "vcl.gdi", "expected string to be NULL terminated" );
+
+ OUString aStr(OStringToOUString(aByteStr, eActualCharSet));
+
+ std::unique_ptr<long[]> pDXAry;
+ sal_Int32 nDXAryLen = 0;
+ 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() );
+
+ 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
+ {
+ pDXAry.reset(new long[nDXAryLen]);
+
+ for (sal_Int32 j = 0; j < nAryLen; ++j)
+ {
+ rIStm.ReadInt32( nTmp );
+ pDXAry[ j ] = nTmp;
+ }
+
+ // #106172# Add last DX array elem, if missing
+ if( nAryLen != nStrLen )
+ {
+ if (nAryLen+1 == nStrLen && nIndex >= 0)
+ {
+ std::unique_ptr<long[]> pTmpAry(new long[nStrLen]);
+
+ aFontVDev->GetTextArray( aStr, pTmpAry.get(), nIndex, nLen );
+
+ // 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 )
+ pDXAry[ nStrLen-1 ] = pDXAry[ nStrLen-2 ] + pTmpAry[ nStrLen-1 ] - pTmpAry[ nStrLen-2 ];
+ else
+ pDXAry[ nStrLen-1 ] = pTmpAry[ 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 );
+ rMtf.AddAction( new MetaTextArrayAction( aPt, aStr, pDXAry.get(), 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 );
+ 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:
+ {
+ sal_Int32 nPenWidth;
+ sal_Int16 nPenStyle;
+
+ ImplReadColor( rIStm, aActionColor );
+ 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:
+ {
+ sal_Int16 nBrushStyle;
+
+ ImplReadColor( rIStm, aActionColor );
+ rIStm.SeekRel( 6 );
+ 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;
+ sal_Int16 nRegType;
+ sal_Int16 bIntersect;
+ bool bClip = false;
+
+ rIStm.ReadInt16( nRegType ).ReadInt16( bIntersect );
+ ImplReadRect( rIStm, aRect );
+
+ switch( nRegType )
+ {
+ case 0:
+ break;
+
+ case 1:
+ {
+ tools::Rectangle aRegRect;
+
+ ImplReadRect( rIStm, aRegRect );
+ aRegion = vcl::Region( aRegRect );
+ bClip = true;
+ }
+ break;
+
+ case 2:
+ {
+ if (ImplReadPoly(rIStm, aActionPoly))
+ {
+ aRegion = vcl::Region( aActionPoly );
+ bClip = true;
+ }
+ }
+ break;
+
+ case 3:
+ {
+ bool bSuccess = true;
+ tools::PolyPolygon aPolyPoly;
+ sal_Int32 nPolyCount32(0);
+ rIStm.ReadInt32(nPolyCount32);
+ sal_uInt16 nPolyCount(nPolyCount32);
+
+ for (sal_uInt16 j = 0; j < nPolyCount && rIStm.good(); ++j)
+ {
+ if (!ImplReadPoly(rIStm, aActionPoly))
+ {
+ bSuccess = false;
+ break;
+ }
+ aPolyPoly.Insert(aActionPoly);
+ }
+
+ if (bSuccess)
+ {
+ aRegion = vcl::Region( aPolyPoly );
+ bClip = true;
+ }
+ }
+ break;
+ }
+
+ if( bIntersect )
+ aRegion.Intersect( aRect );
+
+ rMtf.AddAction( new MetaClipRegionAction( aRegion, bClip ) );
+ }
+ break;
+
+ case GDI_MOVECLIPREGION_ACTION:
+ {
+ sal_Int32 nTmp(0), nTmp1(0);
+ rIStm.ReadInt32( nTmp ).ReadInt32( nTmp1 );
+ rMtf.AddAction( new MetaMoveClipRegionAction( nTmp, nTmp1 ) );
+ }
+ break;
+
+ case GDI_ISECTCLIPREGION_ACTION:
+ {
+ ImplReadRect( rIStm, aRect );
+ rMtf.AddAction( new MetaISectRectClipRegionAction( aRect ) );
+ }
+ break;
+
+ case GDI_RASTEROP_ACTION:
+ {
+ RasterOp eRasterOp;
+ sal_Int16 nRasterOp;
+
+ 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(std::make_unique<LineInfo>(aLineInfo));
+ rMtf.AddAction( new MetaPushAction( PushFlags::ALL ) );
+
+ // #106172# Track font relevant data in shadow VDev
+ aFontVDev->Push();
+ }
+ break;
+
+ case GDI_POP_ACTION:
+ {
+
+ std::unique_ptr<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:
+ {
+ Color aStartCol;
+ Color aEndCol;
+ sal_Int16 nStyle;
+ sal_Int16 nAngle;
+ sal_Int16 nBorder;
+ sal_Int16 nOfsX;
+ sal_Int16 nOfsY;
+ sal_Int16 nIntensityStart;
+ sal_Int16 nIntensityEnd;
+
+ ImplReadRect( rIStm, aRect );
+ rIStm.ReadInt16( nStyle );
+ ImplReadColor( rIStm, aStartCol );
+ ImplReadColor( rIStm, aEndCol );
+ rIStm.ReadInt16( nAngle ).ReadInt16( nBorder ).ReadInt16( nOfsX ).ReadInt16( nOfsY ).ReadInt16( nIntensityStart ).ReadInt16( nIntensityEnd );
+
+ Gradient aGrad( static_cast<GradientStyle>(nStyle), aStartCol, aEndCol );
+
+ aGrad.SetAngle( nAngle );
+ aGrad.SetBorder( nBorder );
+ aGrad.SetOfsX( nOfsX );
+ aGrad.SetOfsY( nOfsY );
+ aGrad.SetStartIntensity( nIntensityStart );
+ aGrad.SetEndIntensity( nIntensityEnd );
+ rMtf.AddAction( new MetaGradientAction( aRect, aGrad ) );
+ }
+ break;
+
+ case GDI_TRANSPARENT_COMMENT:
+ {
+ tools::PolyPolygon aPolyPoly;
+ sal_Int32 nFollowingActionCount(0);
+ sal_Int16 nTrans(0);
+
+ ReadPolyPolygon( rIStm, aPolyPoly );
+ rIStm.ReadInt16( nTrans ).ReadInt32( nFollowingActionCount );
+ ImplSkipActions( rIStm, nFollowingActionCount );
+ rMtf.AddAction( new MetaTransparentAction( aPolyPoly, nTrans ) );
+
+ i = SkipActions(i, nFollowingActionCount, nActions);
+ }
+ break;
+
+ case GDI_FLOATTRANSPARENT_COMMENT:
+ {
+ GDIMetaFile aMtf;
+ Point aPos;
+ Size aSize;
+ Gradient aGradient;
+ sal_Int32 nFollowingActionCount(0);
+
+ ReadGDIMetaFile( rIStm, aMtf );
+ aSerializer.readPoint(aPos);
+ aSerializer.readSize(aSize);
+ aSerializer.readGradient(aGradient);
+ rIStm.ReadInt32( nFollowingActionCount );
+ ImplSkipActions( rIStm, nFollowingActionCount );
+ rMtf.AddAction( new MetaFloatTransparentAction( aMtf, aPos, aSize, aGradient ) );
+
+ i = SkipActions(i, nFollowingActionCount, nActions);
+ }
+ break;
+
+ case GDI_HATCH_COMMENT:
+ {
+ tools::PolyPolygon aPolyPoly;
+ Hatch aHatch;
+ sal_Int32 nFollowingActionCount(0);
+
+ ReadPolyPolygon( rIStm, aPolyPoly );
+ ReadHatch( rIStm, aHatch );
+ rIStm.ReadInt32( nFollowingActionCount );
+ ImplSkipActions( rIStm, nFollowingActionCount );
+ rMtf.AddAction( new MetaHatchAction( aPolyPoly, aHatch ) );
+
+ i = SkipActions(i, nFollowingActionCount, nActions);
+ }
+ break;
+
+ case GDI_REFPOINT_COMMENT:
+ {
+ Point aRefPoint;
+ bool bSet;
+ 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;
+ 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),
+ static_cast<FontLineStyle>(nUnderline),
+ LINESTYLE_NONE ) );
+
+ i = SkipActions(i, nFollowingActionCount, nActions);
+ }
+ break;
+
+ case GDI_GRADIENTEX_COMMENT:
+ {
+ tools::PolyPolygon aPolyPoly;
+ Gradient aGradient;
+ sal_Int32 nFollowingActionCount(0);
+
+ ReadPolyPolygon( rIStm, aPolyPoly );
+ aSerializer.readGradient(aGradient);
+ rIStm.ReadInt32( nFollowingActionCount );
+ ImplSkipActions( rIStm, nFollowingActionCount );
+ rMtf.AddAction( new MetaGradientExAction( aPolyPoly, aGradient ) );
+
+ i = SkipActions(i, nFollowingActionCount, nActions);
+ }
+ break;
+
+ case GDI_COMMENT_COMMENT:
+ {
+ std::vector<sal_uInt8> aData;
+
+ OString aComment = read_uInt16_lenPrefixed_uInt8s_ToOString(rIStm);
+ sal_Int32 nValue(0);
+ sal_uInt32 nDataSize(0);
+ rIStm.ReadInt32(nValue).ReadUInt32(nDataSize);
+
+ if (nDataSize)
+ {
+ const size_t nMaxPossibleData = rIStm.remainingSize();
+ if (nDataSize > nMaxPossibleActions)
+ {
+ SAL_WARN("vcl.gdi", "svm record claims to have: " << nDataSize << " data, but only " << nMaxPossibleData << " possible");
+ nDataSize = nMaxPossibleActions;
+ }
+ aData.resize(nDataSize);
+ nDataSize = rIStm.ReadBytes(aData.data(), nDataSize);
+ }
+
+ sal_Int32 nFollowingActionCount(0);
+ rIStm.ReadInt32(nFollowingActionCount);
+ ImplSkipActions( rIStm, nFollowingActionCount );
+ rMtf.AddAction(new MetaCommentAction(aComment, nValue, aData.data(), nDataSize));
+
+ i = SkipActions(i, nFollowingActionCount, nActions);
+ }
+ break;
+
+ case GDI_UNICODE_COMMENT:
+ {
+ nUnicodeCommentActionNumber = i + 1;
+ nUnicodeCommentStreamPos = rIStm.Tell() - 6;
+ if (nActionSize < 4)
+ rIStm.SetError(SVSTREAM_FILEFORMAT_ERROR);
+ else
+ rIStm.SeekRel(nActionSize - 4);
+ }
+ break;
+
+ default:
+ if (nActionSize < 4)
+ rIStm.SetError(SVSTREAM_FILEFORMAT_ERROR);
+ else
+ rIStm.SeekRel(nActionSize - 4);
+ break;
+ }
+ }
+
+ rIStm.SetEndian( nOldFormat );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/textlayout.cxx b/vcl/source/gdi/textlayout.cxx
new file mode 100644
index 000000000..1efe1e617
--- /dev/null
+++ b/vcl/source/gdi/textlayout.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 <vcl/ctrl.hxx>
+#include <vcl/outdev.hxx>
+
+#include <textlayout.hxx>
+
+#include <osl/diagnose.h>
+#include <tools/fract.hxx>
+#include <sal/log.hxx>
+
+#if OSL_DEBUG_LEVEL > 1
+#include <rtl/strbuf.hxx>
+#endif
+
+#include <memory>
+#include <iterator>
+
+namespace vcl
+{
+
+ DefaultTextLayout::~DefaultTextLayout()
+ {
+ }
+
+ 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, MetricVector* _pVector, OUString* _pDisplayText )
+ {
+ m_rTargetDevice.DrawText( _rStartPoint, _rText, _nStartIndex, _nLength, _pVector, _pDisplayText );
+ }
+
+ void DefaultTextLayout::GetCaretPositions( const OUString& _rText, long* _pCaretXArray,
+ sal_Int32 _nStartIndex, sal_Int32 _nLength ) const
+ {
+ m_rTargetDevice.GetCaretPositions( _rText, _pCaretXArray, _nStartIndex, _nLength );
+ }
+
+ sal_Int32 DefaultTextLayout::GetTextBreak( const OUString& _rText, long _nMaxTextWidth, sal_Int32 _nStartIndex, sal_Int32 _nLength ) const
+ {
+ return m_rTargetDevice.GetTextBreak( _rText, _nMaxTextWidth, _nStartIndex, _nLength );
+ }
+
+ bool DefaultTextLayout::DecomposeTextRectAction() const
+ {
+ return false;
+ }
+
+ class ReferenceDeviceTextLayout : public ITextLayout
+ {
+ public:
+ ReferenceDeviceTextLayout( const Control& _rControl, OutputDevice& _rTargetDevice, OutputDevice& _rReferenceDevice );
+ virtual ~ReferenceDeviceTextLayout();
+
+ // ITextLayout
+ virtual 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, MetricVector* _pVector, OUString* _pDisplayText ) override;
+ virtual void GetCaretPositions( const OUString& _rText, long* _pCaretXArray, sal_Int32 _nStartIndex, sal_Int32 _nLength ) const override;
+ virtual sal_Int32 GetTextBreak(const OUString& _rText, 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, MetricVector* _pVector, OUString* _pDisplayText, const Size* i_pDeviceSize );
+ tools::Rectangle GetTextRect( const tools::Rectangle& _rRect, const OUString& _rText, DrawTextFlags _nStyle, Size* o_pDeviceSize );
+
+ private:
+ long GetTextArray( const OUString& _rText, long* _pDXAry, sal_Int32 _nStartIndex, sal_Int32 _nLength ) const;
+
+ OutputDevice& m_rTargetDevice;
+ OutputDevice& m_rReferenceDevice;
+ const bool m_bRTLEnabled;
+
+ tools::Rectangle m_aCompleteTextRect;
+ };
+
+ ReferenceDeviceTextLayout::ReferenceDeviceTextLayout( const Control& _rControl, OutputDevice& _rTargetDevice,
+ OutputDevice& _rReferenceDevice )
+ :m_rTargetDevice( _rTargetDevice )
+ ,m_rReferenceDevice( _rReferenceDevice )
+ ,m_bRTLEnabled( _rControl.IsRTLEnabled() )
+ {
+ Font const aUnzoomedPointFont( _rControl.GetUnzoomedControlPointFont() );
+ const Fraction& aZoom( _rControl.GetZoom() );
+ m_rTargetDevice.Push( PushFlags::MAPMODE | PushFlags::FONT | PushFlags::TEXTLAYOUTMODE );
+
+ MapMode aTargetMapMode( m_rTargetDevice.GetMapMode() );
+ OSL_ENSURE( aTargetMapMode.GetOrigin() == Point(), "ReferenceDeviceTextLayout::ReferenceDeviceTextLayout: uhm, the code below won't work here ..." );
+
+ // normally, controls simulate "zoom" by "zooming" the font. This is responsible for (part of) the discrepancies
+ // between text in Writer and text in controls in Writer, though both have the same font.
+ // So, if we have a zoom set at the control, then we do not scale the font, but instead modify the map mode
+ // to accommodate for the zoom.
+ aTargetMapMode.SetScaleX( aZoom ); // TODO: shouldn't this be "current_scale * zoom"?
+ aTargetMapMode.SetScaleY( aZoom );
+
+ // also, use a higher-resolution map unit than "pixels", which should save us some rounding errors when
+ // translating coordinates between the reference device and the target device.
+ OSL_ENSURE( aTargetMapMode.GetMapUnit() == MapUnit::MapPixel,
+ "ReferenceDeviceTextLayout::ReferenceDeviceTextLayout: this class is not expected to work with such target devices!" );
+ // we *could* adjust all the code in this class to handle this case, but at the moment, it's not necessary
+ const MapUnit eTargetMapUnit = m_rReferenceDevice.GetMapMode().GetMapUnit();
+ aTargetMapMode.SetMapUnit( eTargetMapUnit );
+ OSL_ENSURE( aTargetMapMode.GetMapUnit() != MapUnit::MapPixel,
+ "ReferenceDeviceTextLayout::ReferenceDeviceTextLayout: a reference device which has map mode PIXEL?!" );
+
+ m_rTargetDevice.SetMapMode( aTargetMapMode );
+
+ // now that the Zoom is part of the map mode, reset the target device's font to the "unzoomed" version
+ Font aDrawFont( aUnzoomedPointFont );
+ aDrawFont.SetFontSize( OutputDevice::LogicToLogic(aDrawFont.GetFontSize(), MapMode(MapUnit::MapPoint), MapMode(eTargetMapUnit)) );
+ _rTargetDevice.SetFont( aDrawFont );
+
+ // transfer font to the reference device
+ m_rReferenceDevice.Push( PushFlags::FONT | PushFlags::TEXTLAYOUTMODE );
+ Font aRefFont( aUnzoomedPointFont );
+ aRefFont.SetFontSize( OutputDevice::LogicToLogic(
+ aRefFont.GetFontSize(), MapMode(MapUnit::MapPoint), m_rReferenceDevice.GetMapMode()) );
+ m_rReferenceDevice.SetFont( aRefFont );
+ }
+
+ ReferenceDeviceTextLayout::~ReferenceDeviceTextLayout()
+ {
+ m_rReferenceDevice.Pop();
+ m_rTargetDevice.Pop();
+ }
+
+ namespace
+ {
+ bool lcl_normalizeLength( const OUString& _rText, const sal_Int32 _nStartIndex, sal_Int32& _io_nLength )
+ {
+ sal_Int32 nTextLength = _rText.getLength();
+ if ( _nStartIndex > nTextLength )
+ return false;
+ if ( _nStartIndex + _io_nLength > nTextLength )
+ _io_nLength = nTextLength - _nStartIndex;
+ return true;
+ }
+ }
+
+ long ReferenceDeviceTextLayout::GetTextArray( const OUString& _rText, long* _pDXAry, sal_Int32 _nStartIndex, sal_Int32 _nLength ) const
+ {
+ if ( !lcl_normalizeLength( _rText, _nStartIndex, _nLength ) )
+ return 0;
+
+ // retrieve the character widths from the reference device
+ long nTextWidth = m_rReferenceDevice.GetTextArray( _rText, _pDXAry, _nStartIndex, _nLength );
+#if OSL_DEBUG_LEVEL > 1
+ if ( _pDXAry )
+ {
+ OStringBuffer aTrace;
+ aTrace.append( "ReferenceDeviceTextLayout::GetTextArray( " );
+ aTrace.append( OUStringToOString( _rText, RTL_TEXTENCODING_UTF8 ) );
+ aTrace.append( " ): " );
+ aTrace.append( nTextWidth );
+ aTrace.append( " = ( " );
+ for ( sal_Int32 i=0; i<_nLength; )
+ {
+ aTrace.append( _pDXAry[i] );
+ if ( ++i < _nLength )
+ aTrace.append( ", " );
+ }
+ aTrace.append( ")" );
+ SAL_INFO( "vcl", aTrace.makeStringAndClear() );
+ }
+#endif
+ return nTextWidth;
+ }
+
+ 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, MetricVector* _pVector, OUString* _pDisplayText )
+ {
+ if ( !lcl_normalizeLength( _rText, _nStartIndex, _nLength ) )
+ return;
+
+ if ( _pVector && _pDisplayText )
+ {
+ MetricVector aGlyphBounds;
+ m_rReferenceDevice.GetGlyphBoundRects( _rStartPoint, _rText, _nStartIndex, _nLength, aGlyphBounds );
+ ::std::copy(
+ aGlyphBounds.begin(), aGlyphBounds.end(),
+ ::std::insert_iterator< MetricVector > ( *_pVector, _pVector->end() ) );
+ *_pDisplayText += _rText.copy( _nStartIndex, _nLength );
+ return;
+ }
+
+ std::unique_ptr<long[]> pCharWidths(new long[ _nLength ]);
+ long nTextWidth = GetTextArray( _rText, pCharWidths.get(), _nStartIndex, _nLength );
+ m_rTargetDevice.DrawTextArray( _rStartPoint, _rText, pCharWidths.get(), _nStartIndex, _nLength );
+ pCharWidths.reset();
+
+ m_aCompleteTextRect.Union( tools::Rectangle( _rStartPoint, Size( nTextWidth, m_rTargetDevice.GetTextHeight() ) ) );
+ }
+
+ void ReferenceDeviceTextLayout::GetCaretPositions( const OUString& _rText, long* _pCaretXArray,
+ sal_Int32 _nStartIndex, sal_Int32 _nLength ) const
+ {
+ if ( !lcl_normalizeLength( _rText, _nStartIndex, _nLength ) )
+ return;
+
+ // retrieve the caret positions from the reference device
+ m_rReferenceDevice.GetCaretPositions( _rText, _pCaretXArray, _nStartIndex, _nLength );
+ }
+
+ sal_Int32 ReferenceDeviceTextLayout::GetTextBreak( const OUString& _rText, 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,
+ MetricVector* _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
+ ComplexTextLayoutFlags nTextLayoutMode = m_bRTLEnabled ? ComplexTextLayoutFlags::BiDiRtl : ComplexTextLayoutFlags::Default;
+ m_rReferenceDevice.SetLayoutMode( nTextLayoutMode );
+ m_rTargetDevice.SetLayoutMode( nTextLayoutMode | 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
+ ComplexTextLayoutFlags nTextLayoutMode = m_bRTLEnabled ? ComplexTextLayoutFlags::BiDiRtl : ComplexTextLayoutFlags::Default;
+ m_rReferenceDevice.SetLayoutMode( nTextLayoutMode );
+ m_rTargetDevice.SetLayoutMode( nTextLayoutMode | 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,
+ MetricVector* _pVector, OUString* _pDisplayText, const Size* i_pDeviceSize )
+ {
+ return m_pImpl->DrawText( _rRect, _rText, _nStyle, _pVector, _pDisplayText, i_pDeviceSize );
+ }
+
+ tools::Rectangle ControlTextRenderer::GetTextRect( const tools::Rectangle& _rRect, const OUString& _rText, DrawTextFlags _nStyle, Size* o_pDeviceSize = nullptr )
+ {
+ return m_pImpl->GetTextRect( _rRect, _rText, _nStyle, o_pDeviceSize );
+ }
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/vectorgraphicdata.cxx b/vcl/source/gdi/vectorgraphicdata.cxx
new file mode 100644
index 000000000..80195dc26
--- /dev/null
+++ b/vcl/source/gdi/vectorgraphicdata.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 <tools/diagnose_ex.h>
+#include <tools/stream.hxx>
+#include <sal/log.hxx>
+#include <vcl/vectorgraphicdata.hxx>
+#include <comphelper/processfactory.hxx>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/graphic/PdfTools.hpp>
+#include <com/sun/star/graphic/SvgTools.hpp>
+#include <com/sun/star/graphic/EmfTools.hpp>
+#include <com/sun/star/graphic/Primitive2DTools.hpp>
+#include <com/sun/star/rendering/XIntegerReadOnlyBitmap.hpp>
+#include <com/sun/star/util/XAccounting.hpp>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <vcl/canvastools.hxx>
+#include <comphelper/seqstream.hxx>
+#include <comphelper/sequence.hxx>
+#include <comphelper/propertysequence.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/wmfexternal.hxx>
+#include <vcl/pdfread.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)
+{
+ 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;
+ geometry::RealRectangle2D aRealRect;
+
+ aRealRect.X1 = rTargetRange.getMinX();
+ aRealRect.Y1 = rTargetRange.getMinY();
+ aRealRect.X2 = rTargetRange.getMaxX();
+ aRealRect.Y2 = rTargetRange.getMaxY();
+
+ // get system DPI
+ const Size aDPI(Application::GetDefaultDevice()->LogicToPixel(Size(1, 1), MapMode(MapUnit::MapInch)));
+
+ 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 (getVectorGraphicDataType() == rCandidate.getVectorGraphicDataType())
+ {
+ if (getVectorGraphicDataArrayLength() == rCandidate.getVectorGraphicDataArrayLength())
+ {
+ if (0 == memcmp(
+ getVectorGraphicDataArray().getConstArray(),
+ rCandidate.getVectorGraphicDataArray().getConstArray(),
+ getVectorGraphicDataArrayLength()))
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+void VectorGraphicData::setWmfExternalHeader(const WmfExternal& aExtHeader)
+{
+ if (!mpExternalHeader)
+ {
+ mpExternalHeader.reset( new WmfExternal );
+ }
+
+ *mpExternalHeader = aExtHeader;
+}
+
+void VectorGraphicData::ensurePdfReplacement()
+{
+ assert(getVectorGraphicDataType() == VectorGraphicDataType::Pdf);
+
+ if (!maReplacement.IsEmpty())
+ return; // nothing to do
+
+ // use PDFium directly
+ std::vector<Bitmap> aBitmaps;
+ sal_Int32 nUsePageIndex = 0;
+ if (mnPageIndex >= 0)
+ nUsePageIndex = mnPageIndex;
+ vcl::RenderPDFBitmaps(maVectorGraphicDataArray.getConstArray(),
+ maVectorGraphicDataArray.getLength(), 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 (getVectorGraphicDataType() == VectorGraphicDataType::Pdf)
+ {
+ ensurePdfReplacement();
+ return;
+ }
+
+ ensureSequenceAndRange();
+
+ if (!maSequence.empty())
+ {
+ maReplacement = convertPrimitive2DSequenceToBitmapEx(maSequence, getRange());
+ }
+}
+
+void VectorGraphicData::ensureSequenceAndRange()
+{
+ if (!mbSequenceCreated && maVectorGraphicDataArray.hasElements())
+ {
+ // import SVG to maSequence, also set maRange
+ maRange.reset();
+
+ // create Vector Graphic Data interpreter
+ uno::Reference<uno::XComponentContext> xContext(::comphelper::getProcessComponentContext());
+
+ switch (getVectorGraphicDataType())
+ {
+ case VectorGraphicDataType::Svg:
+ {
+ const uno::Reference< graphic::XSvgParser > xSvgParser = graphic::SvgTools::create(xContext);
+ const uno::Reference< io::XInputStream > myInputStream(new comphelper::SequenceInputStream(maVectorGraphicDataArray));
+
+ if (myInputStream.is())
+ maSequence = comphelper::sequenceToContainer<std::deque<css::uno::Reference< css::graphic::XPrimitive2D >>>(xSvgParser->getDecomposition(myInputStream, maPath));
+
+ break;
+ }
+ case VectorGraphicDataType::Emf:
+ case VectorGraphicDataType::Wmf:
+ {
+ const uno::Reference< graphic::XEmfParser > xEmfParser = graphic::EmfTools::create(xContext);
+ const uno::Reference< io::XInputStream > myInputStream(new comphelper::SequenceInputStream(maVectorGraphicDataArray));
+ uno::Sequence< ::beans::PropertyValue > aSequence;
+
+ if (mpExternalHeader)
+ {
+ aSequence = mpExternalHeader->getSequence();
+ }
+
+ if (myInputStream.is())
+ {
+ // 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);
+
+ maSequence = comphelper::sequenceToContainer<std::deque<css::uno::Reference< css::graphic::XPrimitive2D >>>(xEmfParser->getDecomposition(myInputStream, maPath, aSequence));
+ }
+
+ break;
+ }
+ case VectorGraphicDataType::Pdf:
+ {
+ const uno::Reference<graphic::XPdfDecomposer> xPdfDecomposer = graphic::PdfTools::create(xContext);
+ uno::Sequence<beans::PropertyValue> aDecompositionParameters = comphelper::InitPropertySequence({
+ {"PageIndex", uno::makeAny<sal_Int32>(mnPageIndex)},
+ });
+ auto xPrimitive2D = xPdfDecomposer->getDecomposition(maVectorGraphicDataArray, 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;
+ }
+}
+
+auto VectorGraphicData::getSizeBytes() const -> std::pair<State, size_t>
+{
+ if (maSequence.empty() && maVectorGraphicDataArray.hasElements())
+ {
+ return std::make_pair(State::UNPARSED, maVectorGraphicDataArray.getLength());
+ }
+ else
+ {
+ return std::make_pair(State::PARSED, maVectorGraphicDataArray.getLength() + mNestedBitmapSize);
+ }
+}
+
+VectorGraphicData::VectorGraphicData(
+ const VectorGraphicDataArray& rVectorGraphicDataArray,
+ const OUString& rPath,
+ VectorGraphicDataType eVectorDataType,
+ sal_Int32 nPageIndex)
+: maVectorGraphicDataArray(rVectorGraphicDataArray),
+ maPath(rPath),
+ mbSequenceCreated(false),
+ maRange(),
+ maSequence(),
+ maReplacement(),
+ mNestedBitmapSize(0),
+ meVectorGraphicDataType(eVectorDataType),
+ mnPageIndex(nPageIndex)
+{
+}
+
+VectorGraphicData::VectorGraphicData(
+ const OUString& rPath,
+ VectorGraphicDataType eVectorDataType)
+: maVectorGraphicDataArray(),
+ maPath(rPath),
+ mbSequenceCreated(false),
+ maRange(),
+ maSequence(),
+ maReplacement(),
+ mNestedBitmapSize(0),
+ meVectorGraphicDataType(eVectorDataType),
+ mnPageIndex(-1)
+{
+ SvFileStream rIStm(rPath, StreamMode::STD_READ);
+ if(rIStm.GetError())
+ return;
+ const sal_uInt32 nStmLen(rIStm.remainingSize());
+ if (nStmLen)
+ {
+ maVectorGraphicDataArray.realloc(nStmLen);
+ rIStm.ReadBytes(maVectorGraphicDataArray.begin(), nStmLen);
+
+ if (rIStm.GetError())
+ {
+ maVectorGraphicDataArray = VectorGraphicDataArray();
+ }
+ }
+}
+
+VectorGraphicData::~VectorGraphicData()
+{
+}
+
+const basegfx::B2DRange& VectorGraphicData::getRange() const
+{
+ const_cast< VectorGraphicData* >(this)->ensureSequenceAndRange();
+
+ return maRange;
+}
+
+const std::deque< css::uno::Reference< css::graphic::XPrimitive2D > >& VectorGraphicData::getPrimitive2DSequence() const
+{
+ const_cast< VectorGraphicData* >(this)->ensureSequenceAndRange();
+
+ return maSequence;
+}
+
+const BitmapEx& VectorGraphicData::getReplacement() const
+{
+ const_cast< VectorGraphicData* >(this)->ensureReplacement();
+
+ return maReplacement;
+}
+
+BitmapChecksum VectorGraphicData::GetChecksum() const
+{
+ return vcl_get_checksum(0, maVectorGraphicDataArray.getConstArray(), maVectorGraphicDataArray.getLength());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/virdev.cxx b/vcl/source/gdi/virdev.cxx
new file mode 100644
index 000000000..1bb163cda
--- /dev/null
+++ b/vcl/source/gdi/virdev.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 <salinst.hxx>
+#include <salgdi.hxx>
+#include <salvd.hxx>
+#include <outdev.h>
+#include <PhysicalFontCollection.hxx>
+#include <svdata.hxx>
+#include <vcl/virdev.hxx>
+#include <sal/log.hxx>
+#include <tools/debug.hxx>
+
+using namespace ::com::sun::star::uno;
+
+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->setAntiAliasB2DDraw(bool(mnAntialiasing & AntialiasingFlags::EnableB2dDraw));
+ }
+
+ 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,
+ long nDX, long nDY, const SystemGraphicsData *pData )
+{
+ SAL_INFO( "vcl.virdev", "ImplInitVirDev(" << nDX << "," << nDY << ")" );
+
+ meRefDevMode = RefDevMode::NONE;
+ mbForceZeroExtleadBug = false;
+
+ bool bErase = nDX > 0 && nDY > 0;
+
+ if ( nDX < 1 )
+ nDX = 1;
+
+ if ( nDY < 1 )
+ nDY = 1;
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ if ( !pOutDev )
+ pOutDev = ImplGetDefaultWindow();
+ if( !pOutDev )
+ return;
+
+ SalGraphics* pGraphics;
+ if ( !pOutDev->mpGraphics )
+ (void)pOutDev->AcquireGraphics();
+ pGraphics = pOutDev->mpGraphics;
+ if ( pGraphics )
+ mpVirDev = pSVData->mpDefInst->CreateVirtualDevice(pGraphics, nDX, nDY, meFormat, pData);
+ else
+ mpVirDev = nullptr;
+ if ( !mpVirDev )
+ {
+ // do not abort but throw an exception, may be the current thread terminates anyway (plugin-scenario)
+ throw css::uno::RuntimeException(
+ "Could not create system bitmap!",
+ css::uno::Reference< css::uno::XInterface >() );
+ }
+
+ switch (meFormat)
+ {
+ case DeviceFormat::BITMASK:
+ mnBitCount = 1;
+ break;
+ default:
+ mnBitCount = pOutDev->GetBitCount();
+ break;
+ }
+ mnOutWidth = nDX;
+ mnOutHeight = nDY;
+
+ if (meFormat == DeviceFormat::BITMASK)
+ SetAntialiasing( AntialiasingFlags::DisableText );
+
+ mbScreenComp = pOutDev->IsScreenComp();
+
+ mbDevOutput = true;
+ mxFontCollection = pSVData->maGDIData.mxScreenFontList;
+ mxFontCache = pSVData->maGDIData.mxScreenFontCache;
+ mnDPIX = pOutDev->mnDPIX;
+ mnDPIY = pOutDev->mnDPIY;
+ mnDPIScalePercentage = pOutDev->mnDPIScalePercentage;
+ maFont = pOutDev->maFont;
+
+ if( maTextColor != pOutDev->maTextColor )
+ {
+ maTextColor = pOutDev->maTextColor;
+ mbInitTextColor = true;
+ }
+
+ // virtual devices have white background by default
+ SetBackground( Wallpaper( COL_WHITE ) );
+
+ // #i59283# don't erase user-provided surface
+ if( !pData && bErase)
+ Erase();
+
+ // register VirDev in the list
+ mpNext = pSVData->maGDIData.mpFirstVirDev;
+ mpPrev = nullptr;
+ if ( mpNext )
+ mpNext->mpPrev = this;
+ pSVData->maGDIData.mpFirstVirDev = this;
+}
+
+VirtualDevice::VirtualDevice(const OutputDevice* pCompDev, DeviceFormat eFormat,
+ DeviceFormat eAlphaFormat, OutDevType eOutDevType)
+ : OutputDevice(eOutDevType)
+ , meFormat(eFormat)
+ , meAlphaFormat(eAlphaFormat)
+{
+ SAL_INFO( "vcl.virdev", "VirtualDevice::VirtualDevice( " << static_cast<int>(eFormat)
+ << ", " << static_cast<int>(eAlphaFormat)
+ << ", " << static_cast<int>(eOutDevType) << " )" );
+
+ ImplInitVirDev(pCompDev ? pCompDev : Application::GetDefaultDevice(), 0, 0);
+}
+
+VirtualDevice::VirtualDevice(const SystemGraphicsData& rData, const Size &rSize,
+ DeviceFormat eFormat)
+ : OutputDevice(OUTDEV_VIRDEV)
+ , meFormat(eFormat)
+ , meAlphaFormat(DeviceFormat::NONE)
+{
+ SAL_INFO( "vcl.virdev", "VirtualDevice::VirtualDevice( " << static_cast<int>(eFormat) << " )" );
+
+ ImplInitVirDev(Application::GetDefaultDevice(), rSize.Width(), rSize.Height(), &rData);
+}
+
+VirtualDevice::~VirtualDevice()
+{
+ SAL_INFO( "vcl.virdev", "VirtualDevice::~VirtualDevice()" );
+ disposeOnce();
+}
+
+void VirtualDevice::dispose()
+{
+ SAL_INFO( "vcl.virdev", "VirtualDevice::dispose()" );
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ ReleaseGraphics();
+
+ mpVirDev.reset();
+
+ // remove this VirtualDevice from the double-linked global list
+ if( mpPrev )
+ mpPrev->mpNext = mpNext;
+ else
+ pSVData->maGDIData.mpFirstVirDev = mpNext;
+
+ if( mpNext )
+ mpNext->mpPrev = mpPrev;
+
+ OutputDevice::dispose();
+}
+
+bool VirtualDevice::InnerImplSetOutputSizePixel( const Size& rNewSize, bool bErase,
+ sal_uInt8 *const pBuffer)
+{
+ SAL_INFO( "vcl.virdev",
+ "VirtualDevice::InnerImplSetOutputSizePixel( " << rNewSize.Width() << ", "
+ << rNewSize.Height() << ", " << int(bErase) << " )" );
+
+ if ( !mpVirDev )
+ return false;
+ else if ( rNewSize == GetOutputSizePixel() )
+ {
+ if ( bErase )
+ Erase();
+ SAL_INFO( "vcl.virdev", "Trying to re-use a VirtualDevice but this time using a pre-allocated buffer");
+ return true;
+ }
+
+ bool bRet;
+ 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;
+
+ pNewVirDev = pSVData->mpDefInst->CreateVirtualDevice(mpGraphics, nNewWidth, nNewHeight, meFormat);
+ if ( pNewVirDev )
+ {
+ SalGraphics* pGraphics = pNewVirDev->AcquireGraphics();
+ if ( pGraphics )
+ {
+ long nWidth;
+ long nHeight;
+ if ( mnOutWidth < nNewWidth )
+ nWidth = mnOutWidth;
+ else
+ nWidth = nNewWidth;
+ if ( mnOutHeight < nNewHeight )
+ nHeight = mnOutHeight;
+ else
+ nHeight = nNewHeight;
+ SalTwoRect aPosAry(0, 0, nWidth, nHeight, 0, 0, nWidth, nHeight);
+ pGraphics->CopyBits( aPosAry, mpGraphics, this, this );
+ pNewVirDev->ReleaseGraphics( pGraphics );
+ ReleaseGraphics();
+ mpVirDev = std::move(pNewVirDev);
+ mnOutWidth = rNewSize.Width();
+ mnOutHeight = rNewSize.Height();
+ bRet = true;
+ }
+ else
+ {
+ bRet = false;
+ }
+ }
+ else
+ bRet = false;
+ }
+
+ return bRet;
+}
+
+// #i32109#: Fill opaque areas correctly (without relying on
+// fill/linecolor state)
+void VirtualDevice::ImplFillOpaqueRectangle( const tools::Rectangle& rRect )
+{
+ // Set line and fill color to black (->opaque),
+ // fill rect with that (linecolor, too, because of
+ // those pesky missing pixel problems)
+ Push( PushFlags::LINECOLOR | PushFlags::FILLCOLOR );
+ SetLineColor( COL_BLACK );
+ SetFillColor( COL_BLACK );
+ DrawRect( rRect );
+ Pop();
+}
+
+bool VirtualDevice::ImplSetOutputSizePixel( const Size& rNewSize, bool bErase,
+ sal_uInt8 *const pBuffer)
+{
+ if( InnerImplSetOutputSizePixel(rNewSize, bErase, pBuffer) )
+ {
+ if (meAlphaFormat != DeviceFormat::NONE)
+ {
+ // #110958# Setup alpha bitmap
+ if(mpAlphaVDev && mpAlphaVDev->GetOutputSizePixel() != rNewSize)
+ {
+ mpAlphaVDev.disposeAndClear();
+ }
+
+ if( !mpAlphaVDev )
+ {
+ mpAlphaVDev = VclPtr<VirtualDevice>::Create(*this, meAlphaFormat);
+ mpAlphaVDev->InnerImplSetOutputSizePixel(rNewSize, bErase, nullptr);
+ }
+
+ // TODO: copy full outdev state to new one, here. Also needed in outdev2.cxx:DrawOutDev
+ if( GetLineColor() != COL_TRANSPARENT )
+ mpAlphaVDev->SetLineColor( COL_BLACK );
+
+ if( GetFillColor() != COL_TRANSPARENT )
+ mpAlphaVDev->SetFillColor( COL_BLACK );
+
+ mpAlphaVDev->SetMapMode( GetMapMode() );
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+void VirtualDevice::EnableRTL( bool bEnable )
+{
+ // virdevs default to not mirroring, they will only be set to mirroring
+ // under rare circumstances in the UI, eg the valueset control
+ // because each virdev has its own SalGraphics we can safely switch the SalGraphics here
+ // ...hopefully
+ if( AcquireGraphics() )
+ mpGraphics->SetLayout( bEnable ? SalLayoutFlags::BiDiRtl : SalLayoutFlags::NONE );
+
+ OutputDevice::EnableRTL(bEnable);
+}
+
+bool VirtualDevice::SetOutputSizePixel( const Size& rNewSize, bool bErase )
+{
+ return ImplSetOutputSizePixel(rNewSize, bErase, nullptr);
+}
+
+bool VirtualDevice::SetOutputSizePixelScaleOffsetAndBuffer(
+ const Size& rNewSize, const Fraction& rScale, const Point& rNewOffset,
+ sal_uInt8 *const pBuffer)
+{
+ if (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();
+ mpDeviceFontList.reset();
+ mpDeviceFontSizeList.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;
+}
+
+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: */
diff --git a/vcl/source/gdi/wall.cxx b/vcl/source/gdi/wall.cxx
new file mode 100644
index 000000000..5170134cc
--- /dev/null
+++ b/vcl/source/gdi/wall.cxx
@@ -0,0 +1,366 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <wall2.hxx>
+#include <vcl/dibtools.hxx>
+#include <vcl/settings.hxx>
+
+#include <TypeSerializer.hxx>
+
+ImplWallpaper::ImplWallpaper() :
+ maColor( COL_TRANSPARENT ), meStyle( WallpaperStyle::NONE )
+{
+}
+
+ImplWallpaper::ImplWallpaper( const ImplWallpaper& rImplWallpaper ) :
+ maColor( rImplWallpaper.maColor ), meStyle(rImplWallpaper.meStyle)
+{
+ if ( rImplWallpaper.mpBitmap )
+ mpBitmap = std::make_unique<BitmapEx>( *rImplWallpaper.mpBitmap );
+
+ if( rImplWallpaper.mpCache )
+ mpCache = std::make_unique<BitmapEx>( *rImplWallpaper.mpCache );
+
+ if ( rImplWallpaper.mpGradient )
+ mpGradient = std::make_unique<Gradient>( *rImplWallpaper.mpGradient );
+
+ if ( rImplWallpaper.mpRect )
+ mpRect = *rImplWallpaper.mpRect;
+}
+
+ImplWallpaper::~ImplWallpaper()
+{
+}
+
+SvStream& ReadImplWallpaper( SvStream& rIStm, ImplWallpaper& rImplWallpaper )
+{
+ VersionCompat aCompat( rIStm, StreamMode::READ );
+
+ rImplWallpaper.mpRect.reset();
+ rImplWallpaper.mpGradient.reset();
+ rImplWallpaper.mpBitmap.reset();
+
+ // 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.mpRect = tools::Rectangle();
+ aSerializer.readRectangle(*rImplWallpaper.mpRect);
+ }
+
+ if( bGrad )
+ {
+ rImplWallpaper.mpGradient = std::make_unique<Gradient>();
+ aSerializer.readGradient(*rImplWallpaper.mpGradient);
+ }
+
+ if( bBmp )
+ {
+ rImplWallpaper.mpBitmap = std::make_unique<BitmapEx>();
+ ReadDIBBitmapEx(*rImplWallpaper.mpBitmap, rIStm);
+ }
+
+ // version 3 (new color format)
+ if( aCompat.GetVersion() >= 3 )
+ {
+ rIStm.ReadUInt32(rImplWallpaper.maColor.mValue);
+ }
+ }
+
+ return rIStm;
+}
+
+SvStream& WriteImplWallpaper( SvStream& rOStm, const ImplWallpaper& rImplWallpaper )
+{
+ VersionCompat aCompat( rOStm, StreamMode::WRITE, 3 );
+ bool bRect = bool(rImplWallpaper.mpRect);
+ bool bGrad = bool(rImplWallpaper.mpGradient);
+ bool bBmp = bool(rImplWallpaper.mpBitmap);
+ 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.mpRect);
+ }
+
+ if (bGrad)
+ {
+ aSerializer.writeGradient(*rImplWallpaper.mpGradient);
+ }
+
+ if( bBmp )
+ WriteDIBBitmapEx(*rImplWallpaper.mpBitmap, rOStm);
+
+ // version 3 (new color format)
+ rOStm.WriteUInt32(rImplWallpaper.maColor.mValue);
+
+ return rOStm;
+}
+
+namespace
+{
+ struct theGlobalDefault :
+ public rtl::Static< Wallpaper::ImplType, theGlobalDefault > {};
+}
+
+Wallpaper::Wallpaper() : mpImplWallpaper(theGlobalDefault::get())
+{
+}
+
+Wallpaper::Wallpaper( const Wallpaper& ) = default;
+
+Wallpaper::Wallpaper( Wallpaper&& ) = default;
+
+Wallpaper::Wallpaper( const Color& rColor ) : mpImplWallpaper()
+{
+ mpImplWallpaper->maColor = rColor;
+ mpImplWallpaper->meStyle = WallpaperStyle::Tile;
+}
+
+Wallpaper::Wallpaper( const BitmapEx& rBmpEx ) : mpImplWallpaper()
+{
+ mpImplWallpaper->mpBitmap = std::make_unique<BitmapEx>( rBmpEx );
+ mpImplWallpaper->meStyle = WallpaperStyle::Tile;
+}
+
+Wallpaper::Wallpaper( const Gradient& rGradient ) : mpImplWallpaper()
+{
+ mpImplWallpaper->mpGradient = std::make_unique<Gradient>( rGradient );
+ mpImplWallpaper->meStyle = WallpaperStyle::Tile;
+}
+
+Wallpaper::~Wallpaper() = default;
+
+void Wallpaper::ImplSetCachedBitmap( BitmapEx& rBmp ) const
+{
+ if( !mpImplWallpaper->mpCache )
+ const_cast< ImplWallpaper* >(mpImplWallpaper.get())->mpCache = std::make_unique<BitmapEx>( rBmp );
+ else
+ *const_cast< ImplWallpaper* >(mpImplWallpaper.get())->mpCache = rBmp;
+}
+
+const BitmapEx* Wallpaper::ImplGetCachedBitmap() const
+{
+ return mpImplWallpaper->mpCache.get();
+}
+
+void Wallpaper::ImplReleaseCachedBitmap() const
+{
+ const_cast< ImplWallpaper* >(mpImplWallpaper.get())->mpCache.reset();
+}
+
+void Wallpaper::SetColor( const Color& rColor )
+{
+ ImplReleaseCachedBitmap();
+ mpImplWallpaper->maColor = rColor;
+
+ if( WallpaperStyle::NONE == mpImplWallpaper->meStyle || WallpaperStyle::ApplicationGradient == mpImplWallpaper->meStyle )
+ mpImplWallpaper->meStyle = WallpaperStyle::Tile;
+}
+
+const Color& Wallpaper::GetColor() const
+{
+ return mpImplWallpaper->maColor;
+}
+
+void Wallpaper::SetStyle( WallpaperStyle eStyle )
+{
+ if( eStyle == WallpaperStyle::ApplicationGradient )
+ // set a dummy gradient, the correct gradient
+ // will be created dynamically in GetGradient()
+ SetGradient( ImplGetApplicationGradient() );
+
+ mpImplWallpaper->meStyle = eStyle;
+}
+
+WallpaperStyle Wallpaper::GetStyle() const
+{
+ return mpImplWallpaper->meStyle;
+}
+
+void Wallpaper::SetBitmap( const BitmapEx& rBitmap )
+{
+ if ( !rBitmap )
+ {
+ if ( mpImplWallpaper->mpBitmap )
+ {
+ ImplReleaseCachedBitmap();
+ mpImplWallpaper->mpBitmap.reset();
+ }
+ }
+ else
+ {
+ ImplReleaseCachedBitmap();
+ if ( mpImplWallpaper->mpBitmap )
+ *(mpImplWallpaper->mpBitmap) = rBitmap;
+ else
+ mpImplWallpaper->mpBitmap = std::make_unique<BitmapEx>( rBitmap );
+ }
+
+ if( WallpaperStyle::NONE == mpImplWallpaper->meStyle || WallpaperStyle::ApplicationGradient == mpImplWallpaper->meStyle)
+ mpImplWallpaper->meStyle = WallpaperStyle::Tile;
+}
+
+BitmapEx Wallpaper::GetBitmap() const
+{
+ if ( mpImplWallpaper->mpBitmap )
+ return *(mpImplWallpaper->mpBitmap);
+ else
+ return BitmapEx();
+}
+
+bool Wallpaper::IsBitmap() const
+{
+ return bool(mpImplWallpaper->mpBitmap);
+}
+
+void Wallpaper::SetGradient( const Gradient& rGradient )
+{
+ ImplReleaseCachedBitmap();
+
+ if ( mpImplWallpaper->mpGradient )
+ *(mpImplWallpaper->mpGradient) = rGradient;
+ else
+ mpImplWallpaper->mpGradient = std::make_unique<Gradient>( rGradient );
+
+ if( WallpaperStyle::NONE == mpImplWallpaper->meStyle || WallpaperStyle::ApplicationGradient == mpImplWallpaper->meStyle )
+ mpImplWallpaper->meStyle = WallpaperStyle::Tile;
+}
+
+Gradient Wallpaper::GetGradient() const
+{
+ if( WallpaperStyle::ApplicationGradient == mpImplWallpaper->meStyle )
+ return ImplGetApplicationGradient();
+ else if ( mpImplWallpaper->mpGradient )
+ return *(mpImplWallpaper->mpGradient);
+ else
+ return Gradient();
+}
+
+bool Wallpaper::IsGradient() const
+{
+ return bool(mpImplWallpaper->mpGradient);
+}
+
+Gradient Wallpaper::ImplGetApplicationGradient()
+{
+ Gradient g;
+ g.SetAngle( 900 );
+ g.SetStyle( GradientStyle::Linear );
+ g.SetStartColor( Application::GetSettings().GetStyleSettings().GetFaceColor() );
+ // no 'extreme' gradient when high contrast
+ if( Application::GetSettings().GetStyleSettings().GetHighContrastMode() )
+ g.SetEndColor( Application::GetSettings().GetStyleSettings().GetFaceColor() );
+ else
+ g.SetEndColor( Application::GetSettings().GetStyleSettings().GetFaceGradientColor() );
+ return g;
+}
+
+void Wallpaper::SetRect( const tools::Rectangle& rRect )
+{
+ if ( rRect.IsEmpty() )
+ {
+ mpImplWallpaper->mpRect.reset();
+ }
+ else
+ {
+ mpImplWallpaper->mpRect = rRect;
+ }
+}
+
+tools::Rectangle Wallpaper::GetRect() const
+{
+ if ( mpImplWallpaper->mpRect )
+ return *mpImplWallpaper->mpRect;
+ else
+ return tools::Rectangle();
+}
+
+bool Wallpaper::IsRect() const
+{
+ return bool(mpImplWallpaper->mpRect);
+}
+
+bool Wallpaper::IsFixed() const
+{
+ if ( mpImplWallpaper->meStyle == WallpaperStyle::NONE )
+ return false;
+ else
+ return (!mpImplWallpaper->mpBitmap && !mpImplWallpaper->mpGradient);
+}
+
+bool Wallpaper::IsScrollable() const
+{
+ if ( mpImplWallpaper->meStyle == WallpaperStyle::NONE )
+ return false;
+ else if ( !mpImplWallpaper->mpBitmap && !mpImplWallpaper->mpGradient )
+ return true;
+ else if ( mpImplWallpaper->mpBitmap )
+ return (mpImplWallpaper->meStyle == WallpaperStyle::Tile);
+ else
+ return false;
+}
+
+Wallpaper& Wallpaper::operator=( const Wallpaper& ) = default;
+
+Wallpaper& Wallpaper::operator=( Wallpaper&& ) = default;
+
+bool Wallpaper::operator==( const Wallpaper& rWallpaper ) const
+{
+ return mpImplWallpaper.same_object(rWallpaper.mpImplWallpaper);
+}
+
+SvStream& ReadWallpaper( SvStream& rIStm, Wallpaper& rWallpaper )
+{
+ return ReadImplWallpaper( rIStm, *rWallpaper.mpImplWallpaper );
+}
+
+SvStream& WriteWallpaper( SvStream& rOStm, const Wallpaper& rWallpaper )
+{
+ return WriteImplWallpaper( rOStm, *rWallpaper.mpImplWallpaper );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/graphic/GraphicID.cxx b/vcl/source/graphic/GraphicID.cxx
new file mode 100644
index 000000000..1cad21724
--- /dev/null
+++ b/vcl/source/graphic/GraphicID.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 <graphic/GraphicID.hxx>
+
+#include <impgraph.hxx>
+#include <rtl/strbuf.hxx>
+
+GraphicID::GraphicID(ImpGraphic const& rGraphic)
+{
+ rGraphic.ensureAvailable();
+
+ mnID1 = static_cast<sal_uLong>(rGraphic.ImplGetType()) << 28;
+ mnID2 = mnID3 = mnID4 = 0;
+
+ if (rGraphic.ImplGetType() == GraphicType::Bitmap)
+ {
+ auto const& rVectorGraphicDataPtr = rGraphic.getVectorGraphicData();
+ if (rVectorGraphicDataPtr)
+ {
+ const basegfx::B2DRange& rRange = rVectorGraphicDataPtr->getRange();
+
+ mnID1 |= rVectorGraphicDataPtr->getVectorGraphicDataArrayLength();
+ mnID2 = basegfx::fround(rRange.getWidth());
+ mnID3 = basegfx::fround(rRange.getHeight());
+ mnID4 = vcl_get_checksum(
+ 0, rVectorGraphicDataPtr->getVectorGraphicDataArray().getConstArray(),
+ rVectorGraphicDataPtr->getVectorGraphicDataArrayLength());
+ }
+ else if (rGraphic.ImplIsAnimated())
+ {
+ const Animation aAnimation(rGraphic.ImplGetAnimation());
+
+ mnID1 |= (aAnimation.Count() & 0x0fffffff);
+ mnID2 = aAnimation.GetDisplaySizePixel().Width();
+ mnID3 = aAnimation.GetDisplaySizePixel().Height();
+ mnID4 = rGraphic.ImplGetChecksum();
+ }
+ else
+ {
+ const BitmapEx aBmpEx(rGraphic.ImplGetBitmapEx(GraphicConversionParameters()));
+
+ mnID1 |= (((static_cast<sal_uLong>(aBmpEx.GetTransparentType()) << 8)
+ | (aBmpEx.IsAlpha() ? 1 : 0))
+ & 0x0fffffff);
+ mnID2 = aBmpEx.GetSizePixel().Width();
+ mnID3 = aBmpEx.GetSizePixel().Height();
+ mnID4 = rGraphic.ImplGetChecksum();
+ }
+ }
+ else if (rGraphic.ImplGetType() == GraphicType::GdiMetafile)
+ {
+ const GDIMetaFile& rMtf = rGraphic.ImplGetGDIMetaFile();
+
+ mnID1 |= (rMtf.GetActionSize() & 0x0fffffff);
+ mnID2 = rMtf.GetPrefSize().Width();
+ mnID3 = rMtf.GetPrefSize().Height();
+ mnID4 = rGraphic.ImplGetChecksum();
+ }
+}
+
+OString GraphicID::getIDString() const
+{
+ static const char aHexData[]
+ = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+
+ sal_Int32 nShift, nIndex = 0;
+ sal_Int32 nLen = 24 + (2 * BITMAP_CHECKSUM_SIZE);
+ OStringBuffer aHexStr(nLen);
+ aHexStr.setLength(nLen);
+
+ for (nShift = 28; nShift >= 0; nShift -= 4)
+ aHexStr[nIndex++] = aHexData[(mnID1 >> static_cast<sal_uInt32>(nShift)) & 0xf];
+
+ for (nShift = 28; nShift >= 0; nShift -= 4)
+ aHexStr[nIndex++] = aHexData[(mnID2 >> static_cast<sal_uInt32>(nShift)) & 0xf];
+
+ for (nShift = 28; nShift >= 0; nShift -= 4)
+ aHexStr[nIndex++] = aHexData[(mnID3 >> static_cast<sal_uInt32>(nShift)) & 0xf];
+
+ for (nShift = (8 * BITMAP_CHECKSUM_SIZE) - 4; nShift >= 0; nShift -= 4)
+ aHexStr[nIndex++] = aHexData[(mnID4 >> static_cast<sal_uInt32>(nShift)) & 0xf];
+
+ return aHexStr.makeStringAndClear();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/graphic/GraphicLoader.cxx b/vcl/source/graphic/GraphicLoader.cxx
new file mode 100644
index 000000000..c26b2b9d0
--- /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 <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,
+ /*pExtHeader*/ static_cast<WmfExternal const*>(nullptr));
+ if (nError != ERRCODE_NONE || aGraphic.GetType() == GraphicType::NONE)
+ return Graphic();
+ }
+
+ return aGraphic;
+}
+} // end vcl::graphic
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/graphic/GraphicObject.cxx b/vcl/source/graphic/GraphicObject.cxx
new file mode 100644
index 000000000..377945ef2
--- /dev/null
+++ b/vcl/source/graphic/GraphicObject.cxx
@@ -0,0 +1,942 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <osl/diagnose.h>
+#include <tools/fract.hxx>
+#include <tools/helpers.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/GraphicObject.hxx>
+#include <vcl/GraphicLoader.hxx>
+#include <vcl/outdev.hxx>
+
+#include <com/sun/star/container/XNameContainer.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/graphic/XGraphic.hpp>
+#include <memory>
+
+
+using namespace css;
+using com::sun::star::uno::Reference;
+using com::sun::star::uno::XInterface;
+using com::sun::star::uno::UNO_QUERY;
+using com::sun::star::uno::Sequence;
+using com::sun::star::container::XNameContainer;
+using com::sun::star::beans::XPropertySet;
+
+#define WATERMARK_LUM_OFFSET 50
+#define WATERMARK_CON_OFFSET -70
+
+namespace vcl::graphic
+{
+
+void SearchForGraphics(uno::Reference<uno::XInterface> const & xInterface,
+ std::vector<uno::Reference<css::graphic::XGraphic>> & raGraphicList)
+{
+ uno::Reference<beans::XPropertySet> xPropertySet(xInterface, UNO_QUERY);
+ if (xPropertySet.is())
+ {
+ if (xPropertySet->getPropertySetInfo()->hasPropertyByName("ImageURL"))
+ {
+ OUString sURL;
+ xPropertySet->getPropertyValue("ImageURL") >>= sURL;
+ if (!sURL.isEmpty() && !GraphicObject::isGraphicObjectUniqueIdURL(sURL))
+ {
+ Graphic aGraphic = vcl::graphic::loadFromURL(sURL);
+ if (!aGraphic.IsNone())
+ {
+ raGraphicList.push_back(aGraphic.GetXGraphic());
+ }
+ }
+ } else if (xPropertySet->getPropertySetInfo()->hasPropertyByName("Graphic"))
+ {
+ uno::Reference<css::graphic::XGraphic> xGraphic;
+ xPropertySet->getPropertyValue("Graphic") >>= xGraphic;
+ if (xGraphic.is())
+ {
+ raGraphicList.push_back(xGraphic);
+ }
+ }
+ }
+ Reference<XNameContainer> xContainer(xInterface, UNO_QUERY);
+ if (xContainer.is())
+ {
+ const css::uno::Sequence<OUString> aElementNames = xContainer->getElementNames();
+ for (OUString const & rName : aElementNames)
+ {
+ uno::Reference<XInterface> xInnerInterface;
+ xContainer->getByName(rName) >>= xInnerInterface;
+ SearchForGraphics(xInnerInterface, raGraphicList);
+ }
+ }
+}
+
+} // end namespace vcl::graphic
+
+namespace
+{
+
+bool lclDrawObj( OutputDevice* pOut, 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 sal_uInt16 nRot10 = rAttr.GetRotation() % 3600;
+
+ 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( pOut, 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(aAttr.GetTransparency());
+ }
+}
+
+void lclImplAdjust( GDIMetaFile& rMtf, const GraphicAttr& rAttr, GraphicAdjustmentFlags nAdjustmentFlags )
+{
+ GraphicAttr aAttr( rAttr );
+
+ if( ( nAdjustmentFlags & GraphicAdjustmentFlags::DRAWMODE ) && aAttr.IsSpecialDrawMode() )
+ {
+ switch( aAttr.GetDrawMode() )
+ {
+ case GraphicDrawMode::Mono:
+ rMtf.Convert( MtfConversion::N1BitThreshold );
+ break;
+
+ case GraphicDrawMode::Greys:
+ rMtf.Convert( MtfConversion::N8BitGreys );
+ break;
+
+ case GraphicDrawMode::Watermark:
+ {
+ aAttr.SetLuminance( aAttr.GetLuminance() + WATERMARK_LUM_OFFSET );
+ aAttr.SetContrast( aAttr.GetContrast() + WATERMARK_CON_OFFSET );
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if( ( nAdjustmentFlags & GraphicAdjustmentFlags::COLORS ) && aAttr.IsAdjusted() )
+ {
+ rMtf.Adjust( aAttr.GetLuminance(), aAttr.GetContrast(),
+ aAttr.GetChannelR(), aAttr.GetChannelG(), aAttr.GetChannelB(),
+ aAttr.GetGamma(), aAttr.IsInvert() );
+ }
+
+ if( ( nAdjustmentFlags & GraphicAdjustmentFlags::MIRROR ) && aAttr.IsMirrored() )
+ {
+ rMtf.Mirror( aAttr.GetMirrorFlags() );
+ }
+
+ if( ( nAdjustmentFlags & GraphicAdjustmentFlags::ROTATE ) && aAttr.IsRotated() )
+ {
+ rMtf.Rotate( aAttr.GetRotation() );
+ }
+
+ if( ( nAdjustmentFlags & GraphicAdjustmentFlags::TRANSPARENCY ) && aAttr.IsTransparent() )
+ {
+ OSL_FAIL( "Missing implementation: Mtf-Transparency" );
+ }
+}
+
+void lclImplAdjust( Animation& rAnimation, const GraphicAttr& rAttr, GraphicAdjustmentFlags nAdjustmentFlags )
+{
+ GraphicAttr aAttr( rAttr );
+
+ if( ( nAdjustmentFlags & GraphicAdjustmentFlags::DRAWMODE ) && aAttr.IsSpecialDrawMode() )
+ {
+ switch( aAttr.GetDrawMode() )
+ {
+ case GraphicDrawMode::Mono:
+ rAnimation.Convert( BmpConversion::N1BitThreshold );
+ break;
+
+ case GraphicDrawMode::Greys:
+ rAnimation.Convert( BmpConversion::N8BitGreys );
+ break;
+
+ case GraphicDrawMode::Watermark:
+ {
+ aAttr.SetLuminance( aAttr.GetLuminance() + WATERMARK_LUM_OFFSET );
+ aAttr.SetContrast( aAttr.GetContrast() + WATERMARK_CON_OFFSET );
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if( ( nAdjustmentFlags & GraphicAdjustmentFlags::COLORS ) && aAttr.IsAdjusted() )
+ {
+ rAnimation.Adjust( aAttr.GetLuminance(), aAttr.GetContrast(),
+ aAttr.GetChannelR(), aAttr.GetChannelG(), aAttr.GetChannelB(),
+ aAttr.GetGamma(), aAttr.IsInvert() );
+ }
+
+ if( ( nAdjustmentFlags & GraphicAdjustmentFlags::MIRROR ) && aAttr.IsMirrored() )
+ {
+ rAnimation.Mirror( aAttr.GetMirrorFlags() );
+ }
+
+ if( ( nAdjustmentFlags & GraphicAdjustmentFlags::ROTATE ) && aAttr.IsRotated() )
+ {
+ OSL_FAIL( "Missing implementation: Animation-Rotation" );
+ }
+
+ if( ( nAdjustmentFlags & GraphicAdjustmentFlags::TRANSPARENCY ) && aAttr.IsTransparent() )
+ {
+ OSL_FAIL( "Missing implementation: Animation-Transparency" );
+ }
+}
+
+} // end anonymous namespace
+
+struct GrfSimpleCacheObj
+{
+ Graphic maGraphic;
+ GraphicAttr maAttr;
+
+ GrfSimpleCacheObj( const Graphic& rGraphic, const GraphicAttr& rAttr ) :
+ maGraphic( rGraphic ), maAttr( rAttr ) {}
+};
+
+GraphicObject::GraphicObject()
+{
+}
+
+GraphicObject::GraphicObject(const Graphic& rGraphic)
+ : maGraphic(rGraphic)
+{
+}
+
+GraphicObject::GraphicObject(const GraphicObject& rGraphicObj)
+ : maGraphic(rGraphicObj.GetGraphic())
+ , maAttr(rGraphicObj.maAttr)
+ , maUserData(rGraphicObj.maUserData)
+{
+}
+
+GraphicObject::~GraphicObject()
+{
+}
+
+GraphicType GraphicObject::GetType() const
+{
+ return maGraphic.GetType();
+}
+
+Size GraphicObject::GetPrefSize() const
+{
+ return maGraphic.GetPrefSize();
+}
+
+MapMode GraphicObject::GetPrefMapMode() const
+{
+ return maGraphic.GetPrefMapMode();
+}
+
+bool GraphicObject::IsTransparent() const
+{
+ return maGraphic.IsTransparent();
+}
+
+bool GraphicObject::IsAnimated() const
+{
+ return maGraphic.IsAnimated();
+}
+
+bool GraphicObject::IsEPS() const
+{
+ return maGraphic.IsEPS();
+}
+
+bool GraphicObject::ImplGetCropParams( OutputDevice const * pOut, 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 sal_uInt16 nRot10 = pAttr->GetRotation() % 3600;
+ const Point aOldOrigin( rPt );
+ const MapMode aMap100( MapUnit::Map100thMM );
+ Size aSize100;
+ long nTotalWidth, nTotalHeight;
+
+ if( nRot10 )
+ {
+ aClipPoly.Rotate( rPt, nRot10 );
+ bRectClipRegion = false;
+ }
+ else
+ bRectClipRegion = true;
+
+ rClipPolyPoly = aClipPoly;
+
+ if (maGraphic.GetPrefMapMode().GetMapUnit() == MapUnit::MapPixel)
+ aSize100 = Application::GetDefaultDevice()->PixelToLogic( maGraphic.GetPrefSize(), aMap100 );
+ else
+ {
+ MapMode m(maGraphic.GetPrefMapMode());
+ aSize100 = pOut->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 long nNewLeft = -FRound( ( ( pAttr->GetMirrorFlags() & BmpMirrorFlags::Horizontal ) ? pAttr->GetRightCrop() : pAttr->GetLeftCrop() ) * fScale );
+ const 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 long nNewTop = -FRound( ( ( pAttr->GetMirrorFlags() & BmpMirrorFlags::Vertical ) ? pAttr->GetBottomCrop() : pAttr->GetTopCrop() ) * fScale );
+ const 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* pOut, const Point& rPt, const Size& rSz,
+ const GraphicAttr* pAttr )
+{
+ GraphicAttr aAttr( pAttr ? *pAttr : GetAttr() );
+ Point aPt( rPt );
+ Size aSz( rSz );
+ const DrawModeFlags nOldDrawMode = pOut->GetDrawMode();
+ bool bCropped = aAttr.IsCropped();
+ bool bRet;
+
+ // #i29534# Provide output rects for PDF writer
+ tools::Rectangle aCropRect;
+
+ pOut->SetDrawMode( nOldDrawMode & ~DrawModeFlags( DrawModeFlags::SettingsLine | DrawModeFlags::SettingsFill | DrawModeFlags::SettingsText | DrawModeFlags::SettingsGradient ) );
+
+ // mirrored horizontically
+ 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( pOut, aPt, aSz, &aAttr, aClipPolyPoly, bRectClip );
+
+ pOut->Push( PushFlags::CLIPREGION );
+
+ if( bCrop )
+ {
+ if( bRectClip )
+ {
+ // #i29534# Store crop rect for later forwarding to
+ // PDF writer
+ aCropRect = aClipPolyPoly.GetBoundRect();
+ pOut->IntersectClipRegion( aCropRect );
+ }
+ else
+ {
+ pOut->IntersectClipRegion(vcl::Region(aClipPolyPoly));
+ }
+ }
+ }
+
+ bRet = lclDrawObj(pOut, aPt, aSz, *this, aAttr);
+
+ if( bCropped )
+ pOut->Pop();
+
+ pOut->SetDrawMode( nOldDrawMode );
+
+ return bRet;
+}
+
+void GraphicObject::DrawTiled( OutputDevice* pOut, const tools::Rectangle& rArea, const Size& rSize,
+ const Size& rOffset, int nTileCacheSize1D )
+{
+ if( pOut == nullptr || rSize.IsEmpty() )
+ return;
+
+ const MapMode aOutMapMode( pOut->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( 1L, pOut->LogicToPixel( rSize, aOutMapMode ).Width() ),
+ ::std::max( 1L, pOut->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( pOut, rArea, aOutTileSize, rOffset, nullptr, nTileCacheSize1D );
+}
+
+bool GraphicObject::StartAnimation( OutputDevice* pOut, const Point& rPt, const Size& rSz,
+ long nExtraData,
+ OutputDevice* pFirstFrameOutDev )
+{
+ bool bRet = false;
+
+ GetGraphic();
+
+ const GraphicAttr aAttr( GetAttr() );
+
+ if (IsAnimated())
+ {
+ Point aPt( rPt );
+ Size aSz( rSz );
+ bool bCropped = aAttr.IsCropped();
+
+ if( bCropped )
+ {
+ tools::PolyPolygon aClipPolyPoly;
+ bool bRectClip;
+ const bool bCrop = ImplGetCropParams( pOut, aPt, aSz, &aAttr, aClipPolyPoly, bRectClip );
+
+ pOut->Push( PushFlags::CLIPREGION );
+
+ if( bCrop )
+ {
+ if( bRectClip )
+ pOut->IntersectClipRegion( aClipPolyPoly.GetBoundRect() );
+ else
+ pOut->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(pOut, aPt, aSz, nExtraData, pFirstFrameOutDev);
+
+ if( bCropped )
+ pOut->Pop();
+
+ bRet = true;
+ }
+ else
+ bRet = Draw( pOut, rPt, rSz, &aAttr );
+
+ return bRet;
+}
+
+void GraphicObject::StopAnimation( OutputDevice* pOut, long nExtraData )
+{
+ if (mxSimpleCache)
+ mxSimpleCache->maGraphic.StopAnimation(pOut, nExtraData);
+}
+
+const Graphic& GraphicObject::GetGraphic() const
+{
+ return maGraphic;
+}
+
+void GraphicObject::SetGraphic( const Graphic& rGraphic, const GraphicObject* /*pCopyObj*/)
+{
+ maGraphic = rGraphic;
+}
+
+void GraphicObject::SetGraphic( const Graphic& rGraphic, const OUString& /*rLink*/ )
+{
+ SetGraphic( 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<long>(static_cast<double>(rDestSize.Width()) * (1.0 + (aCropLeftTop.Width() + aCropRightBottom.Width()) / aSrcSize.Width()) + .5),
+ static_cast<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<long>(static_cast<double>(aMtfMapMode.GetOrigin().X()) + rDestSize.Width() * aCropLeftTop.Width() / (aSrcSize.Width() - aCropLeftTop.Width() - aCropRightBottom.Width()) + .5),
+ static_cast<long>(static_cast<double>(aMtfMapMode.GetOrigin().Y()) + rDestSize.Height() * aCropLeftTop.Height() / (aSrcSize.Height() - aCropLeftTop.Height() - aCropRightBottom.Height()) + .5) );
+ MapMode aNewMap( rDestMap );
+ aNewMap.SetOrigin( OutputDevice::LogicToLogic(aNewOrigin, aMtfMapMode, rDestMap) );
+ aMtf.SetPrefMapMode( aNewMap );
+ }
+ else
+ {
+ aMtf.Scale( Fraction( rDestSize.Width(), aSrcSize.Width() ), Fraction( rDestSize.Height(), aSrcSize.Height() ) );
+ aMtf.SetPrefMapMode( rDestMap );
+ }
+
+ aTransGraphic = aMtf;
+ }
+ else if( GraphicType::Bitmap == eType )
+ {
+ BitmapEx aBitmapEx( aTransGraphic.GetBitmapEx() );
+ tools::Rectangle aCropRect;
+
+ // convert crops to pixel
+ if(rAttr.IsCropped())
+ {
+ if (aMapGraph.GetMapUnit() == MapUnit::MapPixel)
+ {
+ // crops are in 1/100th mm -> to MapUnit::MapPixel
+ aCropLeftTop = Application::GetDefaultDevice()->LogicToPixel(
+ Size(rAttr.GetLeftCrop(), rAttr.GetTopCrop()),
+ aMap100);
+ aCropRightBottom = Application::GetDefaultDevice()->LogicToPixel(
+ Size(rAttr.GetRightCrop(), rAttr.GetBottomCrop()),
+ aMap100);
+ }
+ else
+ {
+ // crops are in GraphicObject units -> to MapUnit::MapPixel
+ aCropLeftTop = Application::GetDefaultDevice()->LogicToPixel(
+ Size(rAttr.GetLeftCrop(), rAttr.GetTopCrop()),
+ aMapGraph);
+ aCropRightBottom = Application::GetDefaultDevice()->LogicToPixel(
+ Size(rAttr.GetRightCrop(), rAttr.GetBottomCrop()),
+ aMapGraph);
+ }
+
+ // convert from prefmapmode to pixel
+ Size aSrcSizePixel(
+ Application::GetDefaultDevice()->LogicToPixel(
+ aSrcSize,
+ aMapGraph));
+
+ if(rAttr.IsCropped()
+ && (aSrcSizePixel.Width() != aBitmapEx.GetSizePixel().Width() || aSrcSizePixel.Height() != aBitmapEx.GetSizePixel().Height())
+ && aSrcSizePixel.Width())
+ {
+ // the size in pixels calculated from Graphic's internal MapMode (aTransGraphic.GetPrefMapMode())
+ // and its internal size (aTransGraphic.GetPrefSize()) is different from its real pixel size.
+ // This can be interpreted as this values to be set wrong, but needs to be corrected since e.g.
+ // existing cropping is calculated based on this logic values already.
+ // aBitmapEx.Scale(aSrcSizePixel);
+
+ // another possibility is to adapt the values created so far with a factor; this
+ // will keep the original Bitmap untouched and thus quality will not change
+ // caution: convert to double first, else pretty big errors may occur
+ const double fFactorX(static_cast<double>(aBitmapEx.GetSizePixel().Width()) / aSrcSizePixel.Width());
+ const double fFactorY(static_cast<double>(aBitmapEx.GetSizePixel().Height()) / aSrcSizePixel.Height());
+
+ aCropLeftTop.setWidth( basegfx::fround(aCropLeftTop.Width() * fFactorX) );
+ aCropLeftTop.setHeight( basegfx::fround(aCropLeftTop.Height() * fFactorY) );
+ aCropRightBottom.setWidth( basegfx::fround(aCropRightBottom.Width() * fFactorX) );
+ aCropRightBottom.setHeight( basegfx::fround(aCropRightBottom.Height() * fFactorY) );
+
+ aSrcSizePixel = aBitmapEx.GetSizePixel();
+ }
+
+ // setup crop rectangle in pixel
+ aCropRect = tools::Rectangle( aCropLeftTop.Width(), aCropLeftTop.Height(),
+ aSrcSizePixel.Width() - aCropRightBottom.Width(),
+ aSrcSizePixel.Height() - aCropRightBottom.Height() );
+ }
+
+ // #105641# Also crop animations
+ if( aTransGraphic.IsAnimated() )
+ {
+ Animation aAnim( aTransGraphic.GetAnimation() );
+
+ for( size_t nFrame=0; nFrame<aAnim.Count(); ++nFrame )
+ {
+ AnimationBitmap aAnimationBitmap( aAnim.Get( nFrame ) );
+
+ if( !aCropRect.IsInside( tools::Rectangle(aAnimationBitmap.maPositionPixel, aAnimationBitmap.maSizePixel) ) )
+ {
+ // setup actual cropping (relative to frame position)
+ tools::Rectangle aCropRectRel( aCropRect );
+ aCropRectRel.Move( -aAnimationBitmap.maPositionPixel.X(),
+ -aAnimationBitmap.maPositionPixel.Y() );
+
+ // cropping affects this frame, apply it then
+ // do _not_ apply enlargement, this is done below
+ ImplTransformBitmap( aAnimationBitmap.maBitmapEx, rAttr, Size(), Size(),
+ aCropRectRel, rDestSize, false );
+
+ aAnim.Replace( aAnimationBitmap, nFrame );
+ }
+ // else: bitmap completely within crop area,
+ // i.e. nothing is cropped away
+ }
+
+ // now, apply enlargement (if any) through global animation size
+ if( aCropLeftTop.Width() < 0 ||
+ aCropLeftTop.Height() < 0 ||
+ aCropRightBottom.Width() < 0 ||
+ aCropRightBottom.Height() < 0 )
+ {
+ Size aNewSize( aAnim.GetDisplaySizePixel() );
+ aNewSize.AdjustWidth(aCropRightBottom.Width() < 0 ? -aCropRightBottom.Width() : 0 );
+ aNewSize.AdjustWidth(aCropLeftTop.Width() < 0 ? -aCropLeftTop.Width() : 0 );
+ aNewSize.AdjustHeight(aCropRightBottom.Height() < 0 ? -aCropRightBottom.Height() : 0 );
+ aNewSize.AdjustHeight(aCropLeftTop.Height() < 0 ? -aCropLeftTop.Height() : 0 );
+ aAnim.SetDisplaySizePixel( aNewSize );
+ }
+
+ // if topleft has changed, we must move all frames to the
+ // right and bottom, resp.
+ if( aCropLeftTop.Width() < 0 ||
+ aCropLeftTop.Height() < 0 )
+ {
+ Point aPosOffset( aCropLeftTop.Width() < 0 ? -aCropLeftTop.Width() : 0,
+ aCropLeftTop.Height() < 0 ? -aCropLeftTop.Height() : 0 );
+
+ for( size_t nFrame=0; nFrame<aAnim.Count(); ++nFrame )
+ {
+ AnimationBitmap aAnimationBitmap( aAnim.Get( nFrame ) );
+
+ aAnimationBitmap.maPositionPixel += aPosOffset;
+
+ aAnim.Replace( aAnimationBitmap, nFrame );
+ }
+ }
+
+ aTransGraphic = aAnim;
+ }
+ else
+ {
+ ImplTransformBitmap( aBitmapEx, rAttr, aCropLeftTop, aCropRightBottom,
+ aCropRect, rDestSize, true );
+
+ aTransGraphic = aBitmapEx;
+ }
+
+ aTransGraphic.SetPrefSize( rDestSize );
+ aTransGraphic.SetPrefMapMode( rDestMap );
+ }
+
+ GraphicObject aGrfObj( aTransGraphic );
+ aTransGraphic = aGrfObj.GetTransformedGraphic( &rAttr );
+
+ return aTransGraphic;
+}
+
+Graphic GraphicObject::GetTransformedGraphic( const GraphicAttr* pAttr ) const
+{
+ GetGraphic();
+
+ Graphic aGraphic;
+ GraphicAttr aAttr( pAttr ? *pAttr : GetAttr() );
+
+ if (maGraphic.IsSupportedGraphic())
+ {
+ if( aAttr.IsSpecialDrawMode() || aAttr.IsAdjusted() || aAttr.IsMirrored() || aAttr.IsRotated() || aAttr.IsTransparent() )
+ {
+ if( GetType() == GraphicType::Bitmap )
+ {
+ if( IsAnimated() )
+ {
+ Animation aAnimation( maGraphic.GetAnimation() );
+ lclImplAdjust( aAnimation, aAttr, GraphicAdjustmentFlags::ALL );
+ aAnimation.SetLoopCount(maGraphic.GetAnimationLoopCount());
+ aGraphic = aAnimation;
+ }
+ else
+ {
+ BitmapEx aBmpEx( maGraphic.GetBitmapEx() );
+ lclImplAdjust( aBmpEx, aAttr, GraphicAdjustmentFlags::ALL );
+ aGraphic = aBmpEx;
+ }
+ }
+ else
+ {
+ GDIMetaFile aMtf( maGraphic.GetGDIMetaFile() );
+ lclImplAdjust( aMtf, aAttr, GraphicAdjustmentFlags::ALL );
+ aGraphic = aMtf;
+ }
+ }
+ else
+ {
+ if( ( GetType() == GraphicType::Bitmap ) && IsAnimated() )
+ {
+ Animation aAnimation( maGraphic.GetAnimation() );
+ aAnimation.SetLoopCount(maGraphic.GetAnimationLoopCount());
+ aGraphic = aAnimation;
+ }
+ else
+ aGraphic = maGraphic;
+ }
+ }
+
+ return aGraphic;
+}
+
+bool GraphicObject::isGraphicObjectUniqueIdURL(OUString const & rURL)
+{
+ const OUString aPrefix("vnd.sun.star.GraphicObject:");
+ return rURL.startsWith(aPrefix);
+}
+
+// calculate scalings between real image size and logic object size. This
+// is necessary since the crop values are relative to original bitmap size
+basegfx::B2DVector GraphicObject::calculateCropScaling(
+ double fWidth,
+ double fHeight,
+ double fLeftCrop,
+ double fTopCrop,
+ double fRightCrop,
+ double fBottomCrop) const
+{
+ const MapMode aMapMode100thmm(MapUnit::Map100thMM);
+ Size aBitmapSize(GetPrefSize());
+ double fFactorX(1.0);
+ double fFactorY(1.0);
+
+ if(MapUnit::MapPixel == GetPrefMapMode().GetMapUnit())
+ {
+ aBitmapSize = Application::GetDefaultDevice()->PixelToLogic(aBitmapSize, aMapMode100thmm);
+ }
+ else
+ {
+ aBitmapSize = OutputDevice::LogicToLogic(aBitmapSize, GetPrefMapMode(), aMapMode100thmm);
+ }
+
+ const double fDivX(aBitmapSize.Width() - fLeftCrop - fRightCrop);
+ const double fDivY(aBitmapSize.Height() - fTopCrop - fBottomCrop);
+
+ if(!basegfx::fTools::equalZero(fDivX))
+ {
+ fFactorX = fabs(fWidth) / fDivX;
+ }
+
+ if(!basegfx::fTools::equalZero(fDivY))
+ {
+ fFactorY = fabs(fHeight) / fDivY;
+ }
+
+ return basegfx::B2DVector(fFactorX,fFactorY);
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/graphic/GraphicObject2.cxx b/vcl/source/graphic/GraphicObject2.cxx
new file mode 100644
index 000000000..dc60db55d
--- /dev/null
+++ b/vcl/source/graphic/GraphicObject2.cxx
@@ -0,0 +1,506 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <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() : aTileTopLeft(), aNextTileTopLeft(), aTileSizePixel(), nTilesEmptyX(0), nTilesEmptyY(0) {}
+
+ Point aTileTopLeft; // top, left position of the rendered tile
+ Point aNextTileTopLeft; // top, left position for next recursion
+ // level's tile
+ Size aTileSizePixel; // size of the generated tile (might
+ // differ from
+ // aNextTileTopLeft-aTileTopLeft, because
+ // this is nExponent*prevTileSize. The
+ // generated tile is always nExponent
+ // times the previous tile, such that it
+ // can be used in the next stage. The
+ // required area coverage is often
+ // less. The extraneous area covered is
+ // later overwritten by the next stage)
+ int nTilesEmptyX; // number of original tiles empty right of
+ // this tile. This counts from
+ // aNextTileTopLeft, i.e. the additional
+ // area covered by aTileSizePixel is not
+ // considered here. This is for
+ // unification purposes, as the iterative
+ // calculation of the next level's empty
+ // tiles has to be based on this value.
+ int nTilesEmptyY; // as above, for Y
+};
+
+
+bool GraphicObject::ImplRenderTempTile( VirtualDevice& rVDev,
+ int nNumTilesX, int nNumTilesY,
+ const Size& rTileSizePixel,
+ const GraphicAttr* pAttr )
+{
+ // how many tiles to generate per recursion step
+ const int nExponent = 2;
+
+ // determine MSB factor
+ int nMSBFactor( 1 );
+ while( nNumTilesX / nMSBFactor != 0 ||
+ nNumTilesY / nMSBFactor != 0 )
+ {
+ nMSBFactor *= nExponent;
+ }
+
+ // one less
+ if(nMSBFactor > 1)
+ {
+ nMSBFactor /= nExponent;
+ }
+ ImplTileInfo aTileInfo;
+
+ // #105229# Switch off mapping (converting to logic and back to
+ // pixel might cause roundoff errors)
+ bool bOldMap( rVDev.IsMapModeEnabled() );
+ rVDev.EnableMapMode( false );
+
+ bool bRet( ImplRenderTileRecursive( rVDev, nExponent, nMSBFactor, nNumTilesX, nNumTilesY,
+ nNumTilesX, nNumTilesY, rTileSizePixel, pAttr, aTileInfo ) );
+
+ rVDev.EnableMapMode( bOldMap );
+
+ return bRet;
+}
+
+// define for debug drawings
+//#define DBG_TEST
+
+// see header comment. this works similar to base conversion of a
+// number, i.e. if the exponent is 10, then the number for every tile
+// size is given by the decimal place of the corresponding decimal
+// representation.
+bool GraphicObject::ImplRenderTileRecursive( VirtualDevice& rVDev, int nExponent, int nMSBFactor,
+ int nNumOrigTilesX, int nNumOrigTilesY,
+ int nRemainderTilesX, int nRemainderTilesY,
+ const Size& rTileSizePixel, const GraphicAttr* pAttr,
+ ImplTileInfo& rTileInfo )
+{
+ // gets loaded with our tile bitmap
+ std::unique_ptr<GraphicObject> xTmpGraphic;
+ GraphicObject* pTileGraphic;
+
+ // stores a flag that renders the zero'th tile position
+ // (i.e. (0,0)+rCurrPos) only if we're at the bottom of the
+ // recursion stack. All other position already have that tile
+ // rendered, because the lower levels painted their generated tile
+ // there.
+ bool bNoFirstTileDraw( false );
+
+ // what's left when we're done with our tile size
+ const int nNewRemainderX( nRemainderTilesX % nMSBFactor );
+ const int nNewRemainderY( nRemainderTilesY % nMSBFactor );
+
+ // gets filled out from the recursive call with info of what's
+ // been generated
+ ImplTileInfo aTileInfo;
+
+ // check for recursion's end condition: LSB place reached?
+ if( nMSBFactor == 1 )
+ {
+ pTileGraphic = this;
+
+ // set initial tile size -> orig size
+ aTileInfo.aTileSizePixel = rTileSizePixel;
+ aTileInfo.nTilesEmptyX = nNumOrigTilesX;
+ aTileInfo.nTilesEmptyY = nNumOrigTilesY;
+ }
+ else if( ImplRenderTileRecursive( rVDev, nExponent, nMSBFactor/nExponent,
+ nNumOrigTilesX, nNumOrigTilesY,
+ nNewRemainderX, nNewRemainderY,
+ rTileSizePixel, pAttr, aTileInfo ) )
+ {
+ // extract generated tile -> see comment on the first loop below
+ BitmapEx aTileBitmap( rVDev.GetBitmap( aTileInfo.aTileTopLeft, aTileInfo.aTileSizePixel ) );
+
+ xTmpGraphic.reset(new GraphicObject(aTileBitmap));
+ pTileGraphic = xTmpGraphic.get();
+
+ // fill stripes left over from upstream levels:
+
+ // x0000
+ // 0
+ // 0
+ // 0
+ // 0
+
+ // where x denotes the place filled by our recursive predecessors
+
+ // check whether we have to fill stripes here. Although not
+ // obvious, there is one case where we can skip this step: if
+ // the previous recursion level (the one who filled our
+ // aTileInfo) had zero area to fill, then there are no white
+ // stripes left, naturally. This happens if the digit
+ // associated to that level has a zero, and can be checked via
+ // aTileTopLeft==aNextTileTopLeft.
+ if( aTileInfo.aTileTopLeft != aTileInfo.aNextTileTopLeft )
+ {
+ // now fill one row from aTileInfo.aNextTileTopLeft.X() all
+ // the way to the right
+ // current output position while drawing
+ Point aCurrPos(aTileInfo.aNextTileTopLeft.X(), aTileInfo.aTileTopLeft.Y());
+ for (int nX=0; nX < aTileInfo.nTilesEmptyX; nX += nMSBFactor)
+ {
+ if (!pTileGraphic->Draw(&rVDev, aCurrPos, aTileInfo.aTileSizePixel, pAttr))
+ return false;
+
+ aCurrPos.AdjustX(aTileInfo.aTileSizePixel.Width() );
+ }
+
+#ifdef DBG_TEST
+// rVDev.SetFillCOL_WHITE );
+ rVDev.SetFillColor();
+ rVDev.SetLineColor( Color( 255 * nExponent / nMSBFactor, 255 - 255 * nExponent / nMSBFactor, 128 - 255 * nExponent / nMSBFactor ) );
+ rVDev.DrawEllipse( tools::Rectangle(aTileInfo.aNextTileTopLeft.X(), aTileInfo.aTileTopLeft.Y(),
+ aTileInfo.aNextTileTopLeft.X() - 1 + (aTileInfo.nTilesEmptyX/nMSBFactor)*aTileInfo.aTileSizePixel.Width(),
+ aTileInfo.aTileTopLeft.Y() + aTileInfo.aTileSizePixel.Height() - 1) );
+#endif
+
+ // now fill one column from aTileInfo.aNextTileTopLeft.Y() all
+ // the way to the bottom
+ aCurrPos.setX( aTileInfo.aTileTopLeft.X() );
+ aCurrPos.setY( aTileInfo.aNextTileTopLeft.Y() );
+ for (int nY=0; nY < aTileInfo.nTilesEmptyY; nY += nMSBFactor)
+ {
+ if (!pTileGraphic->Draw(&rVDev, aCurrPos, aTileInfo.aTileSizePixel, pAttr))
+ return false;
+
+ aCurrPos.AdjustY(aTileInfo.aTileSizePixel.Height() );
+ }
+
+#ifdef DBG_TEST
+ rVDev.DrawEllipse( tools::Rectangle(aTileInfo.aTileTopLeft.X(), aTileInfo.aNextTileTopLeft.Y(),
+ aTileInfo.aTileTopLeft.X() + aTileInfo.aTileSizePixel.Width() - 1,
+ aTileInfo.aNextTileTopLeft.Y() - 1 + (aTileInfo.nTilesEmptyY/nMSBFactor)*aTileInfo.aTileSizePixel.Height()) );
+#endif
+ }
+ else
+ {
+ // Thought that aTileInfo.aNextTileTopLeft tile has always
+ // been drawn already, but that's wrong: typically,
+ // _parts_ of that tile have been drawn, since the
+ // previous level generated the tile there. But when
+ // aTileInfo.aNextTileTopLeft!=aTileInfo.aTileTopLeft, the
+ // difference between these two values is missing in the
+ // lower right corner of this first tile. So, can do that
+ // only here.
+ bNoFirstTileDraw = true;
+ }
+ }
+ else
+ {
+ return false;
+ }
+
+ // calc number of original tiles in our drawing area without
+ // remainder
+ nRemainderTilesX -= nNewRemainderX;
+ nRemainderTilesY -= nNewRemainderY;
+
+ // fill tile info for calling method
+ rTileInfo.aTileTopLeft = aTileInfo.aNextTileTopLeft;
+ rTileInfo.aNextTileTopLeft = Point( rTileInfo.aTileTopLeft.X() + rTileSizePixel.Width()*nRemainderTilesX,
+ rTileInfo.aTileTopLeft.Y() + rTileSizePixel.Height()*nRemainderTilesY );
+ rTileInfo.aTileSizePixel = Size( rTileSizePixel.Width()*nMSBFactor*nExponent,
+ rTileSizePixel.Height()*nMSBFactor*nExponent );
+ rTileInfo.nTilesEmptyX = aTileInfo.nTilesEmptyX - nRemainderTilesX;
+ rTileInfo.nTilesEmptyY = aTileInfo.nTilesEmptyY - nRemainderTilesY;
+
+ // init output position
+ Point aCurrPos = aTileInfo.aNextTileTopLeft;
+
+ // fill our drawing area. Fill possibly more, to create the next
+ // bigger tile size -> see bitmap extraction above. This does no
+ // harm, since everything right or below our actual area is
+ // overdrawn by our caller. Just in case we're in the last level,
+ // we don't draw beyond the right or bottom border.
+ for (int nY=0; nY < aTileInfo.nTilesEmptyY && nY < nExponent*nMSBFactor; nY += nMSBFactor)
+ {
+ aCurrPos.setX( aTileInfo.aNextTileTopLeft.X() );
+
+ for (int nX=0; nX < aTileInfo.nTilesEmptyX && nX < nExponent*nMSBFactor; nX += nMSBFactor)
+ {
+ if( bNoFirstTileDraw )
+ bNoFirstTileDraw = false; // don't draw first tile position
+ else if (!pTileGraphic->Draw(&rVDev, aCurrPos, aTileInfo.aTileSizePixel, pAttr))
+ return false;
+
+ aCurrPos.AdjustX(aTileInfo.aTileSizePixel.Width() );
+ }
+
+ aCurrPos.AdjustY(aTileInfo.aTileSizePixel.Height() );
+ }
+
+#ifdef DBG_TEST
+// rVDev.SetFillCOL_WHITE );
+ rVDev.SetFillColor();
+ rVDev.SetLineColor( Color( 255 * nExponent / nMSBFactor, 255 - 255 * nExponent / nMSBFactor, 128 - 255 * nExponent / nMSBFactor ) );
+ rVDev.DrawRect( tools::Rectangle((rTileInfo.aTileTopLeft.X())*rTileSizePixel.Width(),
+ (rTileInfo.aTileTopLeft.Y())*rTileSizePixel.Height(),
+ (rTileInfo.aNextTileTopLeft.X())*rTileSizePixel.Width()-1,
+ (rTileInfo.aNextTileTopLeft.Y())*rTileSizePixel.Height()-1) );
+#endif
+
+ return true;
+}
+
+bool GraphicObject::ImplDrawTiled( OutputDevice* pOut, const tools::Rectangle& rArea, const Size& rSizePixel,
+ const Size& rOffset, const GraphicAttr* pAttr, int nTileCacheSize1D )
+{
+ const MapMode aOutMapMode( pOut->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( GetGraphic().GetBitmapEx().GetAlpha().GetBitmap() );
+ else
+ aAlphaGraphic.SetGraphic( GetGraphic().GetBitmapEx().GetMask() );
+
+ if( aAlphaGraphic.ImplRenderTempTile( *aVDev, nNumTilesInCacheX,
+ nNumTilesInCacheY, rSizePixel, pAttr ) )
+ {
+ // Combine bitmap and alpha/mask
+ if( GetGraphic().IsAlpha() )
+ aTileBitmap = BitmapEx( aTileBitmap.GetBitmap(),
+ AlphaMask( aVDev->GetBitmap( Point(0,0), aVDev->GetOutputSize() ) ) );
+ else
+ aTileBitmap = BitmapEx( aTileBitmap.GetBitmap(),
+ aVDev->GetBitmap( Point(0,0), aVDev->GetOutputSize() ).CreateMask( COL_WHITE ) );
+ }
+ }
+
+ // paint generated tile
+ GraphicObject aTmpGraphic( aTileBitmap );
+ bRet = aTmpGraphic.ImplDrawTiled( pOut, rArea,
+ aTileBitmap.GetSizePixel(),
+ rOffset, pAttr, nTileCacheSize1D );
+ }
+ }
+ else
+ {
+ const Size aOutOffset( pOut->LogicToPixel( rOffset, aOutMapMode ) );
+ const tools::Rectangle aOutArea( pOut->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( pOut->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() );
+
+ pOut->Push( PushFlags::CLIPREGION );
+ pOut->IntersectClipRegion( rArea );
+
+ // Paint all tiles
+
+
+ bRet = ImplDrawTiled( *pOut, 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 );
+
+ pOut->Pop();
+ }
+
+ return bRet;
+}
+
+bool GraphicObject::ImplDrawTiled( OutputDevice& rOut, const Point& rPosPixel,
+ int nNumTilesX, int nNumTilesY,
+ const Size& rTileSizePixel, const GraphicAttr* pAttr )
+{
+ 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.IsTransparent() )
+ {
+ if( rBmpEx.IsAlpha() )
+ aBmpEx2 = BitmapEx( rBmpEx.GetBitmap(), rBmpEx.GetAlpha() );
+ else
+ aBmpEx2 = BitmapEx( rBmpEx.GetBitmap(), rBmpEx.GetMask() );
+ }
+ else
+ {
+ // #104115# Generate mask bitmap and init to zero
+ Bitmap aMask( aBmpSize, 1 );
+ aMask.Erase( Color(0,0,0) );
+
+ // #104115# Always generate transparent bitmap, we need the border transparent
+ aBmpEx2 = BitmapEx( rBmpEx.GetBitmap(), aMask );
+
+ // #104115# Add opaque mask to source bitmap, otherwise the destination remains transparent
+ rBmpEx = aBmpEx2;
+ }
+
+ aBmpEx2.Scale(Size(nPadTotalWidth, nPadTotalHeight));
+ aBmpEx2.Erase( Color(0xFF,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 || IsAnimated() )
+ return;
+
+ if( !(aSizePixel.Width() && aSizePixel.Height() && rDstSize.Width() && rDstSize.Height()) )
+ return;
+
+ double fSrcWH = static_cast<double>(aSizePixel.Width()) / aSizePixel.Height();
+ double fDstWH = static_cast<double>(rDstSize.Width()) / rDstSize.Height();
+ double fScaleX = 1.0, fScaleY = 1.0;
+
+ // always choose scaling to shrink bitmap
+ if( fSrcWH < fDstWH )
+ fScaleY = aSizePixel.Width() / ( fDstWH * aSizePixel.Height() );
+ else
+ fScaleX = fDstWH * aSizePixel.Height() / aSizePixel.Width();
+
+ rBmpEx.Scale( fScaleX, fScaleY );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/graphic/GraphicReader.cxx b/vcl/source/graphic/GraphicReader.cxx
new file mode 100644
index 000000000..9137ebd8a
--- /dev/null
+++ b/vcl/source/graphic/GraphicReader.cxx
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <graphic/GraphicReader.hxx>
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+
+GraphicReader::GraphicReader() {}
+
+GraphicReader::~GraphicReader() {}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/graphic/Manager.cxx b/vcl/source/graphic/Manager.cxx
new file mode 100644
index 000000000..65e81fc1e
--- /dev/null
+++ b/vcl/source/graphic/Manager.cxx
@@ -0,0 +1,247 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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)
+ , mnMemoryLimit(300000000)
+ , mnUsedSize(0)
+ , maSwapOutTimer("graphic::Manager maSwapOutTimer")
+{
+ setupConfigurationValuesIfPossible(mnMemoryLimit, mnAllowedIdleTime, mbSwapEnabled);
+
+ if (mbSwapEnabled)
+ {
+ maSwapOutTimer.SetInvokeHandler(LINK(this, Manager, SwapOutTimerHandler));
+ maSwapOutTimer.SetTimeout(10000);
+ maSwapOutTimer.SetDebugName("graphic::Manager maSwapOutTimer");
+ maSwapOutTimer.Start();
+ }
+}
+
+void Manager::reduceGraphicMemory()
+{
+ if (!mbSwapEnabled)
+ return;
+
+ std::scoped_lock<std::recursive_mutex> aGuard(maMutex);
+
+ // 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 < mnMemoryLimit * 0.7)
+ return;
+
+ sal_Int64 nCurrentGraphicSize = getGraphicSizeBytes(pEachImpGraphic);
+ if (!pEachImpGraphic->isSwappedOut() && nCurrentGraphicSize > 1000000)
+ {
+ 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)
+ pEachImpGraphic->swapOut();
+ }
+ }
+ }
+}
+
+sal_Int64 Manager::getGraphicSizeBytes(const ImpGraphic* pImpGraphic)
+{
+ if (!pImpGraphic->isAvailable())
+ return 0;
+ return pImpGraphic->ImplGetSizeBytes();
+}
+
+IMPL_LINK(Manager, SwapOutTimerHandler, Timer*, pTimer, void)
+{
+ std::scoped_lock<std::recursive_mutex> aGuard(maMutex);
+
+ pTimer->Stop();
+ reduceGraphicMemory();
+ pTimer->Start();
+}
+
+void Manager::registerGraphic(const std::shared_ptr<ImpGraphic>& pImpGraphic,
+ OUString const& /*rsContext*/)
+{
+ std::scoped_lock<std::recursive_mutex> aGuard(maMutex);
+
+ // make some space first
+ if (mnUsedSize > mnMemoryLimit)
+ reduceGraphicMemory();
+
+ // Insert and update the used size (bytes)
+ 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<std::recursive_mutex> 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, "Copy");
+ return pReturn;
+}
+
+std::shared_ptr<ImpGraphic> Manager::newInstance()
+{
+ auto pReturn = std::make_shared<ImpGraphic>();
+ registerGraphic(pReturn, "Empty");
+ return pReturn;
+}
+
+std::shared_ptr<ImpGraphic> Manager::newInstance(const Bitmap& rBitmap)
+{
+ auto pReturn = std::make_shared<ImpGraphic>(rBitmap);
+ registerGraphic(pReturn, "Bitmap");
+ return pReturn;
+}
+
+std::shared_ptr<ImpGraphic> Manager::newInstance(const BitmapEx& rBitmapEx)
+{
+ auto pReturn = std::make_shared<ImpGraphic>(rBitmapEx);
+ registerGraphic(pReturn, "BitmapEx");
+ return pReturn;
+}
+
+std::shared_ptr<ImpGraphic> Manager::newInstance(const Animation& rAnimation)
+{
+ auto pReturn = std::make_shared<ImpGraphic>(rAnimation);
+ registerGraphic(pReturn, "Animation");
+ return pReturn;
+}
+
+std::shared_ptr<ImpGraphic>
+Manager::newInstance(const std::shared_ptr<VectorGraphicData>& rVectorGraphicDataPtr)
+{
+ auto pReturn = std::make_shared<ImpGraphic>(rVectorGraphicDataPtr);
+ registerGraphic(pReturn, "VectorGraphic");
+ return pReturn;
+}
+
+std::shared_ptr<ImpGraphic> Manager::newInstance(const GDIMetaFile& rMetaFile)
+{
+ auto pReturn = std::make_shared<ImpGraphic>(rMetaFile);
+ registerGraphic(pReturn, "Metafile");
+ return pReturn;
+}
+
+std::shared_ptr<ImpGraphic> Manager::newInstance(const GraphicExternalLink& rGraphicLink)
+{
+ auto pReturn = std::make_shared<ImpGraphic>(rGraphicLink);
+ registerGraphic(pReturn, "GraphicExternalLink");
+ return pReturn;
+}
+
+void Manager::swappedIn(const ImpGraphic* pImpGraphic)
+{
+ std::scoped_lock<std::recursive_mutex> aGuard(maMutex);
+
+ mnUsedSize += getGraphicSizeBytes(pImpGraphic);
+}
+
+void Manager::swappedOut(const ImpGraphic* pImpGraphic)
+{
+ std::scoped_lock<std::recursive_mutex> aGuard(maMutex);
+
+ mnUsedSize -= getGraphicSizeBytes(pImpGraphic);
+}
+
+void Manager::changeExisting(const ImpGraphic* pImpGraphic, sal_Int64 nOldSizeBytes)
+{
+ std::scoped_lock<std::recursive_mutex> aGuard(maMutex);
+
+ mnUsedSize -= nOldSizeBytes;
+ mnUsedSize += getGraphicSizeBytes(pImpGraphic);
+}
+} // end vcl::graphic
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/graphic/UnoGraphic.cxx b/vcl/source/graphic/UnoGraphic.cxx
new file mode 100644
index 000000000..4fddaebd6
--- /dev/null
+++ b/vcl/source/graphic/UnoGraphic.cxx
@@ -0,0 +1,192 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <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 <comphelper/servicehelper.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <cppuhelper/queryinterface.hxx>
+#include <cppuhelper/typeprovider.hxx>
+
+using namespace com::sun::star;
+
+namespace unographic {
+
+Graphic::Graphic() :
+ maGraphic()
+{
+}
+
+Graphic::~Graphic() throw()
+{
+}
+
+void Graphic::init( const ::Graphic& rGraphic )
+ throw()
+{
+ maGraphic = rGraphic;
+ unographic::GraphicDescriptor::init(maGraphic);
+}
+
+uno::Any SAL_CALL Graphic::queryAggregation( const uno::Type& rType )
+{
+ uno::Any aAny;
+ if( rType == cppu::UnoType<graphic::XGraphic>::get())
+ aAny <<= uno::Reference< graphic::XGraphic >( this );
+ else if( rType == cppu::UnoType<awt::XBitmap>::get())
+ aAny <<= uno::Reference< awt::XBitmap >( this );
+ else if( rType == cppu::UnoType<lang::XUnoTunnel>::get())
+ aAny <<= uno::Reference< lang::XUnoTunnel >(this);
+ else
+ aAny = ::unographic::GraphicDescriptor::queryAggregation( rType );
+
+ return aAny;
+}
+
+uno::Any SAL_CALL Graphic::queryInterface( const uno::Type & rType )
+{
+ css::uno::Any aReturn = ::unographic::GraphicDescriptor::queryInterface( rType );
+ if ( !aReturn.hasValue() )
+ aReturn = ::cppu::queryInterface ( rType, static_cast< graphic::XGraphicTransformer*>( this ) );
+ return aReturn;
+}
+
+void SAL_CALL Graphic::acquire()
+ throw()
+{
+ unographic::GraphicDescriptor::acquire();
+}
+
+void SAL_CALL Graphic::release() throw()
+{
+ 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() );
+ 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.begin(), 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().GetMask(), aMemoryStream, false, true);
+ return css::uno::Sequence<sal_Int8>( static_cast<sal_Int8 const *>(aMemoryStream.GetData()), aMemoryStream.Tell() );
+ }
+ else
+ {
+ return uno::Sequence<sal_Int8>();
+ }
+}
+
+sal_Int64 SAL_CALL Graphic::getSomething( const uno::Sequence< sal_Int8 >& rId )
+{
+ return( ( isUnoTunnelId<::Graphic>(rId) ) ?
+ reinterpret_cast<sal_Int64>(&maGraphic) : 0 );
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/graphic/UnoGraphicDescriptor.cxx b/vcl/source/graphic/UnoGraphicDescriptor.cxx
new file mode 100644
index 000000000..6f65828fe
--- /dev/null
+++ b/vcl/source/graphic/UnoGraphicDescriptor.cxx
@@ -0,0 +1,424 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <graphic/UnoGraphicDescriptor.hxx>
+
+#include <unotools/ucbstreamhelper.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <cppuhelper/supportsservice.hxx>
+
+#include <com/sun/star/beans/PropertyAttribute.hpp>
+#include <com/sun/star/awt/Size.hpp>
+#include <com/sun/star/graphic/GraphicType.hpp>
+#include <com/sun/star/io/XInputStream.hpp>
+
+#include <vcl/outdev.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/svapp.hxx>
+#include <memory>
+
+namespace {
+
+enum class UnoGraphicProperty
+{
+ GraphicType = 1
+ , MimeType = 2
+ , SizePixel = 3
+ , Size100thMM = 4
+ , BitsPerPixel = 5
+ , Transparent = 6
+ , Alpha = 7
+ , Animated = 8
+ , Linked = 9
+ , OriginURL = 10
+};
+
+}
+
+using namespace ::com::sun::star;
+
+namespace unographic {
+
+
+GraphicDescriptor::GraphicDescriptor() :
+ ::comphelper::PropertySetHelper( createPropertySetInfo() ),
+ mpGraphic( nullptr ),
+ meType( GraphicType::NONE ),
+ mnBitsPerPixel ( 0 ),
+ mbTransparent ( false )
+{
+}
+
+GraphicDescriptor::~GraphicDescriptor()
+ throw()
+{
+}
+
+void GraphicDescriptor::init( const ::Graphic& rGraphic )
+{
+ mpGraphic = &rGraphic;
+}
+
+void GraphicDescriptor::init( const OUString& rURL )
+{
+ std::unique_ptr<SvStream> pIStm(::utl::UcbStreamHelper::CreateStream( rURL, StreamMode::READ ));
+
+ if( pIStm )
+ implCreate( *pIStm, &rURL );
+}
+
+void GraphicDescriptor::init( const uno::Reference< io::XInputStream >& rxIStm, const OUString& rURL )
+{
+ std::unique_ptr<SvStream> pIStm(::utl::UcbStreamHelper::CreateStream( rxIStm ));
+
+ if( pIStm )
+ implCreate( *pIStm, &rURL );
+}
+
+void GraphicDescriptor::implCreate( SvStream& rIStm, const OUString* pURL )
+{
+ OUString aURL;
+ if( pURL )
+ aURL = *pURL;
+ ::GraphicDescriptor aDescriptor( rIStm, &aURL );
+
+ mpGraphic = nullptr;
+ maMimeType.clear();
+ meType = GraphicType::NONE;
+ mnBitsPerPixel = 0;
+ mbTransparent = false;
+
+ if( !(aDescriptor.Detect( true ) && aDescriptor.GetFileFormat() != GraphicFileFormat::NOT) )
+ return;
+
+ const char* pMimeType = nullptr;
+ sal_uInt8 cType = graphic::GraphicType::EMPTY;
+
+ switch( aDescriptor.GetFileFormat() )
+ {
+ case GraphicFileFormat::BMP: pMimeType = MIMETYPE_BMP; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::GIF: pMimeType = MIMETYPE_GIF; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::JPG: pMimeType = MIMETYPE_JPG; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::PCD: pMimeType = MIMETYPE_PCD; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::PCX: pMimeType = MIMETYPE_PCX; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::PNG: pMimeType = MIMETYPE_PNG; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::TIF: pMimeType = MIMETYPE_TIF; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::XBM: pMimeType = MIMETYPE_XBM; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::XPM: pMimeType = MIMETYPE_XPM; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::PBM: pMimeType = MIMETYPE_PBM; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::PGM: pMimeType = MIMETYPE_PGM; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::PPM: pMimeType = MIMETYPE_PPM; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::RAS: pMimeType = MIMETYPE_RAS; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::TGA: pMimeType = MIMETYPE_TGA; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::PSD: pMimeType = MIMETYPE_PSD; cType = graphic::GraphicType::PIXEL; break;
+
+ case GraphicFileFormat::EPS: pMimeType = MIMETYPE_EPS; cType = graphic::GraphicType::VECTOR; break;
+ case GraphicFileFormat::DXF: pMimeType = MIMETYPE_DXF; cType = graphic::GraphicType::VECTOR; break;
+ case GraphicFileFormat::MET: pMimeType = MIMETYPE_MET; cType = graphic::GraphicType::VECTOR; break;
+ case GraphicFileFormat::PCT: pMimeType = MIMETYPE_PCT; cType = graphic::GraphicType::VECTOR; break;
+ case GraphicFileFormat::SVM: pMimeType = MIMETYPE_SVM; cType = graphic::GraphicType::VECTOR; break;
+ case GraphicFileFormat::WMF: pMimeType = MIMETYPE_WMF; cType = graphic::GraphicType::VECTOR; break;
+ case GraphicFileFormat::EMF: pMimeType = MIMETYPE_EMF; cType = graphic::GraphicType::VECTOR; break;
+ case GraphicFileFormat::SVG: pMimeType = MIMETYPE_SVG; cType = graphic::GraphicType::VECTOR; break;
+
+ default:
+ break;
+ }
+
+ if( graphic::GraphicType::EMPTY != cType )
+ {
+ meType = ( ( graphic::GraphicType::PIXEL == cType ) ? GraphicType::Bitmap : GraphicType::GdiMetafile );
+ maMimeType = OUString( pMimeType, strlen(pMimeType), RTL_TEXTENCODING_ASCII_US );
+ maSizePixel = aDescriptor.GetSizePixel();
+ maSize100thMM = aDescriptor.GetSize_100TH_MM();
+ mnBitsPerPixel = aDescriptor.GetBitsPerPixel();
+ mbTransparent = ( graphic::GraphicType::VECTOR == cType );
+ }
+}
+
+
+uno::Any SAL_CALL GraphicDescriptor::queryAggregation( const uno::Type & rType )
+{
+ uno::Any aAny;
+
+ if( rType == cppu::UnoType<lang::XServiceInfo>::get())
+ aAny <<= uno::Reference< lang::XServiceInfo >(this);
+ else if( rType == cppu::UnoType<lang::XTypeProvider>::get())
+ aAny <<= uno::Reference< lang::XTypeProvider >(this);
+ else if( rType == cppu::UnoType<beans::XPropertySet>::get())
+ aAny <<= uno::Reference< beans::XPropertySet >(this);
+ else if( rType == cppu::UnoType<beans::XPropertyState>::get())
+ aAny <<= uno::Reference< beans::XPropertyState >(this);
+ else if( rType == cppu::UnoType<beans::XMultiPropertySet>::get())
+ aAny <<= uno::Reference< beans::XMultiPropertySet >(this);
+ else
+ aAny = OWeakAggObject::queryAggregation( rType );
+
+ return aAny;
+}
+
+
+uno::Any SAL_CALL GraphicDescriptor::queryInterface( const uno::Type & rType )
+{
+ return OWeakAggObject::queryInterface( rType );
+}
+
+
+void SAL_CALL GraphicDescriptor::acquire()
+ throw()
+{
+ OWeakAggObject::acquire();
+}
+
+
+void SAL_CALL GraphicDescriptor::release()
+ throw()
+{
+ OWeakAggObject::release();
+}
+
+
+OUString SAL_CALL GraphicDescriptor::getImplementationName()
+{
+ return "com.sun.star.comp.graphic.GraphicDescriptor";
+}
+
+sal_Bool SAL_CALL GraphicDescriptor::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+
+uno::Sequence< OUString > SAL_CALL GraphicDescriptor::getSupportedServiceNames()
+{
+ return { "com.sun.star.graphic.GraphicDescriptor" };
+}
+
+
+uno::Sequence< uno::Type > SAL_CALL GraphicDescriptor::getTypes()
+{
+ static const uno::Sequence< uno::Type > aTypes {
+ cppu::UnoType<uno::XAggregation>::get(),
+ cppu::UnoType<lang::XServiceInfo>::get(),
+ cppu::UnoType<lang::XTypeProvider>::get(),
+ cppu::UnoType<beans::XPropertySet>::get(),
+ cppu::UnoType<beans::XPropertyState>::get(),
+ cppu::UnoType<beans::XMultiPropertySet>::get() };
+ return aTypes;
+}
+
+uno::Sequence< sal_Int8 > SAL_CALL GraphicDescriptor::getImplementationId()
+{
+ return css::uno::Sequence<sal_Int8>();
+}
+
+
+rtl::Reference<::comphelper::PropertySetInfo> GraphicDescriptor::createPropertySetInfo()
+{
+ static ::comphelper::PropertyMapEntry const aEntries[] =
+ {
+ { OUString( "GraphicType" ), static_cast< sal_Int32 >( UnoGraphicProperty::GraphicType ), cppu::UnoType< sal_Int8 >::get(), beans::PropertyAttribute::READONLY, 0 },
+ { OUString( "MimeType" ), static_cast< sal_Int32 >( UnoGraphicProperty::MimeType ), cppu::UnoType< OUString >::get(), beans::PropertyAttribute::READONLY, 0 },
+ { OUString( "SizePixel" ), static_cast< sal_Int32 >( UnoGraphicProperty::SizePixel ), cppu::UnoType< awt::Size >::get(), beans::PropertyAttribute::READONLY, 0 },
+ { OUString( "Size100thMM" ), static_cast< sal_Int32 >( UnoGraphicProperty::Size100thMM ), cppu::UnoType< awt::Size >::get(), beans::PropertyAttribute::READONLY, 0 },
+ { OUString( "BitsPerPixel" ), static_cast< sal_Int32 >( UnoGraphicProperty::BitsPerPixel ), cppu::UnoType< sal_uInt8 >::get(), beans::PropertyAttribute::READONLY, 0 },
+ { OUString( "Transparent" ), static_cast< sal_Int32 >( UnoGraphicProperty::Transparent ), cppu::UnoType< sal_Bool >::get(), beans::PropertyAttribute::READONLY, 0 },
+ { OUString( "Alpha" ), static_cast< sal_Int32 >( UnoGraphicProperty::Alpha ), cppu::UnoType< sal_Bool >::get(), beans::PropertyAttribute::READONLY, 0 },
+ { OUString( "Animated" ), static_cast< sal_Int32 >( UnoGraphicProperty::Animated ), cppu::UnoType< sal_Bool >::get(), beans::PropertyAttribute::READONLY, 0 },
+ { OUString("Linked"), sal_Int32(UnoGraphicProperty::Linked), cppu::UnoType<sal_Bool>::get(), beans::PropertyAttribute::READONLY, 0 },
+ { OUString("OriginURL"), sal_Int32(UnoGraphicProperty::OriginURL), cppu::UnoType<OUString>::get(), beans::PropertyAttribute::READONLY, 0 },
+
+ { OUString(), 0, css::uno::Type(), 0, 0 }
+ };
+
+ return rtl::Reference<::comphelper::PropertySetInfo>( new ::comphelper::PropertySetInfo(aEntries) );
+}
+
+
+void GraphicDescriptor::_setPropertyValues( const comphelper::PropertyMapEntry** /*ppEntries*/, const uno::Any* /*pValues*/ )
+{
+ // we only have readonly attributes
+}
+
+
+void GraphicDescriptor::_getPropertyValues( const comphelper::PropertyMapEntry** ppEntries, uno::Any* pValues )
+{
+ SolarMutexGuard aGuard;
+
+ while( *ppEntries )
+ {
+ UnoGraphicProperty theProperty = static_cast< UnoGraphicProperty >( (*ppEntries)->mnHandle );
+ switch( theProperty )
+ {
+ case UnoGraphicProperty::GraphicType:
+ {
+ const GraphicType eType( mpGraphic ? mpGraphic->GetType() : meType );
+
+ *pValues <<= ( eType == GraphicType::Bitmap ? graphic::GraphicType::PIXEL :
+ ( eType == GraphicType::GdiMetafile ? graphic::GraphicType::VECTOR :
+ graphic::GraphicType::EMPTY ) );
+ }
+ break;
+
+ case UnoGraphicProperty::MimeType:
+ {
+ OUString aMimeType;
+
+ if( mpGraphic )
+ {
+ if( mpGraphic->IsGfxLink() )
+ {
+ const char* pMimeType;
+
+ switch( mpGraphic->GetGfxLink().GetType() )
+ {
+ case GfxLinkType::NativeGif: pMimeType = MIMETYPE_GIF; break;
+
+ // #i15508# added BMP type for better exports (checked, works)
+ case GfxLinkType::NativeBmp: pMimeType = MIMETYPE_BMP; break;
+
+ case GfxLinkType::NativeJpg: pMimeType = MIMETYPE_JPG; break;
+ case GfxLinkType::NativePng: pMimeType = MIMETYPE_PNG; break;
+ case GfxLinkType::NativeWmf: pMimeType = MIMETYPE_WMF; break;
+ case GfxLinkType::NativeMet: pMimeType = MIMETYPE_MET; break;
+ case GfxLinkType::NativePct: pMimeType = MIMETYPE_PCT; break;
+
+ // added Svg mimetype support
+ case GfxLinkType::NativeSvg: pMimeType = MIMETYPE_SVG; break;
+ case GfxLinkType::NativePdf: pMimeType = MIMETYPE_PDF; break;
+
+ default:
+ pMimeType = nullptr;
+ break;
+ }
+
+ if( pMimeType )
+ aMimeType = OUString::createFromAscii( pMimeType );
+ }
+
+ if( aMimeType.isEmpty() && ( mpGraphic->GetType() != GraphicType::NONE ) )
+ aMimeType = MIMETYPE_VCLGRAPHIC;
+ }
+ else
+ aMimeType = maMimeType;
+
+ *pValues <<= aMimeType;
+ }
+ break;
+
+ case UnoGraphicProperty::SizePixel:
+ {
+ awt::Size aAWTSize( 0, 0 );
+
+ if( mpGraphic )
+ {
+ if( mpGraphic->GetType() == GraphicType::Bitmap )
+ {
+ const Size aSizePix( mpGraphic->GetSizePixel() );
+ aAWTSize = awt::Size( aSizePix.Width(), aSizePix.Height() );
+ }
+ }
+ else
+ aAWTSize = awt::Size( maSizePixel.Width(), maSizePixel.Height() );
+
+ *pValues <<= aAWTSize;
+ }
+ break;
+
+ case UnoGraphicProperty::Size100thMM:
+ {
+ awt::Size aAWTSize( 0, 0 );
+
+ if( mpGraphic )
+ {
+ if( mpGraphic->GetPrefMapMode().GetMapUnit() != MapUnit::MapPixel )
+ {
+ const Size aSizeLog( OutputDevice::LogicToLogic(
+ mpGraphic->GetPrefSize(),
+ mpGraphic->GetPrefMapMode(),
+ MapMode(MapUnit::Map100thMM)) );
+ aAWTSize = awt::Size( aSizeLog.Width(), aSizeLog.Height() );
+ }
+ }
+ else
+ aAWTSize = awt::Size( maSize100thMM.Width(), maSize100thMM.Height() );
+
+ *pValues <<= aAWTSize;
+ }
+ break;
+
+ case UnoGraphicProperty::BitsPerPixel:
+ {
+ sal_uInt16 nBitsPerPixel = 0;
+
+ if( mpGraphic )
+ {
+ if( mpGraphic->GetType() == GraphicType::Bitmap )
+ nBitsPerPixel = mpGraphic->GetBitmapEx().GetBitmap().GetBitCount();
+ }
+ 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/UnoGraphicObject.cxx b/vcl/source/graphic/UnoGraphicObject.cxx
new file mode 100644
index 000000000..bab6bffe5
--- /dev/null
+++ b/vcl/source/graphic/UnoGraphicObject.cxx
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <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>
+
+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
+{
+ osl::Mutex m_aMutex;
+ std::unique_ptr<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 uno::Sequence<OUString> { "com.sun.star.graphic.GraphicObject" };
+ }
+};
+
+GraphicObjectImpl::GraphicObjectImpl(const uno::Sequence<uno::Any>& /*rArgs*/)
+{
+ mpGraphicObject.reset(new GraphicObject());
+}
+
+uno::Reference<graphic::XGraphic> SAL_CALL GraphicObjectImpl::getGraphic()
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ if (!mpGraphicObject)
+ throw uno::RuntimeException();
+ return mpGraphicObject->GetGraphic().GetXGraphic();
+}
+
+void SAL_CALL GraphicObjectImpl::setGraphic(uno::Reference<graphic::XGraphic> const & rxGraphic)
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ if (!mpGraphicObject)
+ throw uno::RuntimeException();
+ Graphic aGraphic(rxGraphic);
+ mpGraphicObject->SetGraphic(aGraphic);
+}
+
+} // end anonymous namespace
+
+extern "C" SAL_DLLPUBLIC_EXPORT
+css::uno::XInterface* com_sun_star_graphic_GraphicObject_get_implementation(
+ SAL_UNUSED_PARAMETER uno::XComponentContext*,
+ uno::Sequence<uno::Any> const & rArguments)
+{
+ return cppu::acquire(new GraphicObjectImpl(rArguments));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/graphic/UnoGraphicProvider.cxx b/vcl/source/graphic/UnoGraphicProvider.cxx
new file mode 100644
index 000000000..d8c6e774d
--- /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 <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>
+
+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( const OUString& rResourceURL );
+ static css::uno::Reference< css::graphic::XGraphic > implLoadRepositoryImage( const OUString& 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( const OUString& 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()
+{
+ uno::Sequence<OUString> aSeq { "com.sun.star.graphic.GraphicProvider" };
+ return aSeq;
+}
+
+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( const OUString& rResourceURL )
+{
+ uno::Reference< ::graphic::XGraphic > xRet;
+ sal_Int32 nIndex = 0;
+
+ if( rResourceURL.getToken( 0, '/', nIndex ) == "private:memorygraphic" )
+ {
+ sal_Int64 nGraphicAddress = rResourceURL.getToken( 0, '/', nIndex ).toInt64();
+
+ if( nGraphicAddress )
+ {
+ ::unographic::Graphic* pUnoGraphic = new ::unographic::Graphic;
+
+ pUnoGraphic->init( *reinterpret_cast< ::Graphic* >( nGraphicAddress ) );
+ xRet = pUnoGraphic;
+ }
+ }
+
+ return xRet;
+}
+
+
+uno::Reference< ::graphic::XGraphic > GraphicProvider::implLoadRepositoryImage( const OUString& rResourceURL )
+{
+ uno::Reference< ::graphic::XGraphic > xRet;
+
+ OUString sPathName;
+ if( rResourceURL.startsWith("private:graphicrepository/", &sPathName) )
+ {
+ BitmapEx aBitmap;
+ if ( vcl::ImageRepository::loadImage( sPathName, aBitmap ) )
+ {
+ xRet = Graphic(aBitmap).GetXGraphic();
+ }
+ }
+ return xRet;
+}
+
+
+uno::Reference< ::graphic::XGraphic > GraphicProvider::implLoadStandardImage( const OUString& rResourceURL )
+{
+ uno::Reference< ::graphic::XGraphic > xRet;
+
+ OUString sImageName;
+ if( rResourceURL.startsWith("private:standardimage/", &sImageName) )
+ {
+ if ( sImageName == "info" )
+ {
+ xRet = Graphic(GetStandardInfoBoxImage().GetBitmapEx()).GetXGraphic();
+ }
+ else if ( sImageName == "warning" )
+ {
+ xRet = Graphic(GetStandardWarningBoxImage().GetBitmapEx()).GetXGraphic();
+ }
+ else if ( sImageName == "error" )
+ {
+ xRet = Graphic(GetStandardErrorBoxImage().GetBitmapEx()).GetXGraphic();
+ }
+ else if ( sImageName == "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() )
+ {
+ ::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() )
+ {
+ 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
+ {
+ 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;
+ WmfExternal *pExtHeader = nullptr;
+ if ( nExtMapMode > 0 )
+ {
+ pExtHeader = &aExtHeader;
+ 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, pExtHeader);
+
+ if( (error == ERRCODE_NONE ) &&
+ ( aVCLGraphic.GetType() != GraphicType::NONE ) )
+ {
+ if (!aPath.isEmpty() && bLoadAsLink)
+ aVCLGraphic.setOriginURL(aPath);
+
+ ::unographic::Graphic* pUnoGraphic = new ::unographic::Graphic;
+
+ pUnoGraphic->init( aVCLGraphic );
+ xRet = pUnoGraphic;
+ }
+ else{
+ SAL_WARN("svtools", "Could not create graphic: " << error);
+ }
+ }
+ }
+
+ return xRet;
+}
+
+uno::Sequence< uno::Reference<graphic::XGraphic> > SAL_CALL GraphicProvider::queryGraphics(const uno::Sequence< uno::Sequence<beans::PropertyValue> >& rMediaPropertiesSeq)
+{
+ SolarMutexGuard aGuard;
+
+ // 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)
+ {
+ auto 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 )
+{
+ SolarMutexGuard g;
+
+ 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;
+ const char* pFilterShortName = nullptr;
+
+ 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)
+ pFilterShortName = "bmp";
+ else if (aMimeType == MIMETYPE_EPS)
+ pFilterShortName = "eps";
+ else if (aMimeType == MIMETYPE_GIF)
+ pFilterShortName = "gif";
+ else if (aMimeType == MIMETYPE_JPG)
+ pFilterShortName = "jpg";
+ else if (aMimeType == MIMETYPE_MET)
+ pFilterShortName = "met";
+ else if (aMimeType == MIMETYPE_PNG)
+ pFilterShortName = "png";
+ else if (aMimeType == MIMETYPE_PCT)
+ pFilterShortName = "pct";
+ else if (aMimeType == MIMETYPE_PBM)
+ pFilterShortName = "pbm";
+ else if (aMimeType == MIMETYPE_PGM)
+ pFilterShortName = "pgm";
+ else if (aMimeType == MIMETYPE_PPM)
+ pFilterShortName = "ppm";
+ else if (aMimeType == MIMETYPE_RAS)
+ pFilterShortName = "ras";
+ else if (aMimeType == MIMETYPE_SVM)
+ pFilterShortName = "svm";
+ else if (aMimeType == MIMETYPE_TIF)
+ pFilterShortName = "tif";
+ else if (aMimeType == MIMETYPE_EMF)
+ pFilterShortName = "emf";
+ else if (aMimeType == MIMETYPE_WMF)
+ pFilterShortName = "wmf";
+ else if (aMimeType == MIMETYPE_XPM)
+ pFilterShortName = "xpm";
+ else if (aMimeType == MIMETYPE_SVG)
+ pFilterShortName = "svg";
+ else if (aMimeType == MIMETYPE_VCLGRAPHIC)
+ pFilterShortName = MIMETYPE_VCLGRAPHIC;
+ }
+ }
+
+ if( !pFilterShortName )
+ return;
+
+ ::GraphicFilter& rFilter = ::GraphicFilter::GetGraphicFilter();
+
+ {
+ const uno::Reference< XInterface > xIFace( rxGraphic, uno::UNO_QUERY );
+ const ::Graphic* pGraphic = comphelper::getUnoTunnelImplementation<::Graphic>( xIFace );
+
+ if( pGraphic && ( pGraphic->GetType() != GraphicType::NONE ) )
+ {
+ ::Graphic aGraphic( *pGraphic );
+ ImplApplyFilterData( aGraphic, aFilterDataSeq );
+
+ /* sj: using a temporary memory stream, because some graphic filters are seeking behind
+ stream end (which leads to an invalid argument exception then). */
+ SvMemoryStream aMemStrm;
+ aMemStrm.SetVersion( SOFFICE_FILEFORMAT_CURRENT );
+ if( 0 == strcmp( pFilterShortName, MIMETYPE_VCLGRAPHIC ) )
+ WriteGraphic( aMemStrm, aGraphic );
+ else
+ {
+ rFilter.ExportGraphic( aGraphic, aPath, aMemStrm,
+ rFilter.GetExportFormatNumberForShortName( OUString::createFromAscii( pFilterShortName ) ),
+ ( 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/UnoGraphicTransformer.cxx b/vcl/source/graphic/UnoGraphicTransformer.cxx
new file mode 100644
index 000000000..100e37b29
--- /dev/null
+++ b/vcl/source/graphic/UnoGraphicTransformer.cxx
@@ -0,0 +1,140 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/UnoGraphicTransformer.hxx>
+
+#include <vcl/graph.hxx>
+#include <vcl/BitmapColor.hxx>
+#include <vcl/BitmapDuoToneFilter.hxx>
+
+using namespace com::sun::star;
+
+namespace unographic {
+
+
+GraphicTransformer::GraphicTransformer()
+{
+}
+
+
+GraphicTransformer::~GraphicTransformer()
+{
+}
+
+
+// XGraphicTransformer
+uno::Reference< graphic::XGraphic > SAL_CALL GraphicTransformer::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(static_cast< sal_uInt8 >(nColorFrom), static_cast< sal_uInt8 >(nColorFrom >> 8), static_cast< sal_uInt8 >(nColorFrom >> 16));
+ BitmapColor aBmpColorTo( static_cast< sal_uInt8 >(nColorTo), static_cast< sal_uInt8 >(nColorTo >> 8), static_cast< sal_uInt8 >(nColorTo >> 16));
+
+ 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());
+ Bitmap aBitmap(aBitmapEx.GetBitmap());
+
+ if (aBitmapEx.IsAlpha())
+ {
+ aBitmapEx.setAlphaFrom( cIndexFrom, 0xff - nAlphaTo );
+ aBitmapEx.Replace(aColorFrom, aColorTo, nTolerance);
+ aReturnGraphic = ::Graphic(aBitmapEx);
+ }
+ else if (aBitmapEx.IsTransparent())
+ {
+ if (nAlphaTo == sal::static_int_cast< sal_Int8 >(0xff))
+ {
+ Bitmap aMask(aBitmapEx.GetMask());
+ Bitmap aMask2(aBitmap.CreateMask(aColorFrom, nTolerance));
+ aMask.CombineSimple(aMask2, BmpCombine::Or);
+ aBitmap.Replace(aColorFrom, aColorTo, nTolerance);
+ aReturnGraphic = ::Graphic(BitmapEx(aBitmap, aMask));
+ }
+ else
+ {
+ aBitmapEx.setAlphaFrom(cIndexFrom, 0xff - nAlphaTo);
+ aBitmapEx.Replace(aColorFrom, aColorTo, nTolerance);
+ aReturnGraphic = ::Graphic(aBitmapEx);
+ }
+ }
+ else
+ {
+ if ((nAlphaTo == 0) || (nAlphaTo == sal::static_int_cast< sal_Int8 >(0xff)))
+ {
+ Bitmap aMask(aBitmap.CreateMask(aColorFrom, nTolerance));
+ aBitmap.Replace(aColorFrom, aColorTo, nTolerance);
+ aReturnGraphic = ::Graphic(BitmapEx(aBitmap, aMask));
+ }
+ else
+ {
+ aBitmapEx.setAlphaFrom(cIndexFrom, nAlphaTo);
+ aBitmapEx.Replace(aColorFrom, aColorTo, nTolerance);
+ aReturnGraphic = ::Graphic(aBitmapEx);
+ }
+ }
+ }
+
+ aReturnGraphic.setOriginURL(aGraphic.getOriginURL());
+ return aReturnGraphic.GetXGraphic();
+}
+
+uno::Reference< graphic::XGraphic > SAL_CALL GraphicTransformer::applyDuotone(
+ const uno::Reference< graphic::XGraphic >& rxGraphic, sal_Int32 nColorOne, sal_Int32 nColorTwo )
+{
+ ::Graphic aGraphic(rxGraphic);
+ ::Graphic aReturnGraphic;
+
+ BitmapEx aBitmapEx( aGraphic.GetBitmapEx() );
+ AlphaMask aMask( aBitmapEx.GetAlpha() );
+
+ BitmapEx aTmpBmpEx(aBitmapEx.GetBitmap());
+ BitmapFilter::Filter(aTmpBmpEx, BitmapDuoToneFilter(static_cast<sal_uLong>(nColorOne), static_cast<sal_uLong>(nColorTwo)));
+
+ aReturnGraphic = ::Graphic( BitmapEx( aTmpBmpEx.GetBitmap(), aMask ) );
+ aReturnGraphic.setOriginURL(aGraphic.getOriginURL());
+ return aReturnGraphic.GetXGraphic();
+}
+
+uno::Reference< graphic::XGraphic > SAL_CALL GraphicTransformer::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/grfattr.cxx b/vcl/source/graphic/grfattr.cxx
new file mode 100644
index 000000000..88d0852af
--- /dev/null
+++ b/vcl/source/graphic/grfattr.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/GraphicObject.hxx>
+
+
+GraphicAttr::GraphicAttr() :
+ mfGamma ( 1.0 ),
+ mnMirrFlags ( BmpMirrorFlags::NONE ),
+ mnLeftCrop ( 0 ),
+ mnTopCrop ( 0 ),
+ mnRightCrop ( 0 ),
+ mnBottomCrop ( 0 ),
+ mnRotate10 ( 0 ),
+ mnContPercent ( 0 ),
+ mnLumPercent ( 0 ),
+ mnRPercent ( 0 ),
+ mnGPercent ( 0 ),
+ mnBPercent ( 0 ),
+ mbInvert ( false ),
+ mcTransparency ( 0 ),
+ meDrawMode ( GraphicDrawMode::Standard )
+{
+}
+
+bool GraphicAttr::operator==( const GraphicAttr& rAttr ) const
+{
+ return( ( mfGamma == rAttr.mfGamma ) &&
+ ( mnMirrFlags == rAttr.mnMirrFlags ) &&
+ ( mnLeftCrop == rAttr.mnLeftCrop ) &&
+ ( mnTopCrop == rAttr.mnTopCrop ) &&
+ ( mnRightCrop == rAttr.mnRightCrop ) &&
+ ( mnBottomCrop == rAttr.mnBottomCrop ) &&
+ ( mnRotate10 == rAttr.mnRotate10 ) &&
+ ( mnContPercent == rAttr.mnContPercent ) &&
+ ( mnLumPercent == rAttr.mnLumPercent ) &&
+ ( mnRPercent == rAttr.mnRPercent ) &&
+ ( mnGPercent == rAttr.mnGPercent ) &&
+ ( mnBPercent == rAttr.mnBPercent ) &&
+ ( mbInvert == rAttr.mbInvert ) &&
+ ( mcTransparency == rAttr.mcTransparency ) &&
+ ( meDrawMode == rAttr.meDrawMode ) );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/helper/canvasbitmap.cxx b/vcl/source/helper/canvasbitmap.cxx
new file mode 100644
index 000000000..b720cdf3b
--- /dev/null
+++ b/vcl/source/helper/canvasbitmap.cxx
@@ -0,0 +1,1407 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <rtl/instance.hxx>
+
+#include <tools/diagnose_ex.h>
+#include <canvasbitmap.hxx>
+#include <vcl/canvastools.hxx>
+#include <vcl/bitmapaccess.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);
+}
+
+VclCanvasBitmap::VclCanvasBitmap( const BitmapEx& rBitmap ) :
+ m_aBmpEx( rBitmap ),
+ m_aBitmap( rBitmap.GetBitmap() ),
+ m_aAlpha(),
+ m_pBmpAcc( m_aBitmap ),
+ m_aComponentTags(),
+ m_aComponentBitCounts(),
+ m_aLayout(),
+ m_nBitsPerInputPixel(0),
+ m_nBitsPerOutputPixel(0),
+ m_nRedIndex(-1),
+ m_nGreenIndex(-1),
+ m_nBlueIndex(-1),
+ m_nAlphaIndex(-1),
+ m_nIndexIndex(-1),
+ m_nEndianness(0),
+ m_bPalette(false)
+{
+ if( m_aBmpEx.IsTransparent() )
+ {
+ m_aAlpha = m_aBmpEx.IsAlpha() ? m_aBmpEx.GetAlpha().GetBitmap() : m_aBmpEx.GetMask();
+ m_pAlphaAcc = Bitmap::ScopedReadAccess(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 )
+ {
+ 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_nEndianness = util::Endianness::LITTLE; // doesn't matter
+ m_aLayout.IsMsbFirst = true;
+ break;
+
+ case ScanlineFormat::N1BitLsbPal:
+ m_bPalette = true;
+ m_nBitsPerInputPixel = 1;
+ m_nEndianness = util::Endianness::LITTLE; // doesn't matter
+ m_aLayout.IsMsbFirst = false;
+ break;
+
+ case ScanlineFormat::N4BitMsnPal:
+ m_bPalette = true;
+ m_nBitsPerInputPixel = 4;
+ m_nEndianness = util::Endianness::LITTLE; // doesn't matter
+ m_aLayout.IsMsbFirst = true;
+ break;
+
+ case ScanlineFormat::N4BitLsnPal:
+ m_bPalette = true;
+ m_nBitsPerInputPixel = 4;
+ m_nEndianness = util::Endianness::LITTLE; // doesn't matter
+ m_aLayout.IsMsbFirst = false;
+ break;
+
+ case ScanlineFormat::N8BitPal:
+ m_bPalette = true;
+ m_nBitsPerInputPixel = 8;
+ m_nEndianness = util::Endianness::LITTLE; // doesn't matter
+ m_aLayout.IsMsbFirst = false; // doesn't matter
+ break;
+
+ case ScanlineFormat::N8BitTcMask:
+ m_bPalette = false;
+ m_nBitsPerInputPixel = 8;
+ m_nEndianness = util::Endianness::LITTLE; // doesn't matter
+ m_aLayout.IsMsbFirst = false; // doesn't matter
+ setComponentInfo( m_pBmpAcc->GetColorMask().GetRedMask(),
+ m_pBmpAcc->GetColorMask().GetGreenMask(),
+ m_pBmpAcc->GetColorMask().GetBlueMask() );
+ break;
+
+ case ScanlineFormat::N24BitTcBgr:
+ m_bPalette = false;
+ m_nBitsPerInputPixel = 24;
+ m_nEndianness = util::Endianness::LITTLE;
+ 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_nEndianness = util::Endianness::LITTLE;
+ 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_nEndianness = util::Endianness::LITTLE;
+ m_aLayout.IsMsbFirst = false; // doesn't matter
+
+ m_aComponentTags.realloc(4);
+ sal_Int8* pTags = m_aComponentTags.getArray();
+ pTags[0] = rendering::ColorComponentTag::ALPHA;
+ pTags[1] = rendering::ColorComponentTag::RGB_BLUE;
+ pTags[2] = rendering::ColorComponentTag::RGB_GREEN;
+ pTags[3] = rendering::ColorComponentTag::RGB_RED;
+
+ m_aComponentBitCounts.realloc(4);
+ sal_Int32* pCounts = m_aComponentBitCounts.getArray();
+ pCounts[0] = 8;
+ pCounts[1] = 8;
+ pCounts[2] = 8;
+ pCounts[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_nEndianness = util::Endianness::LITTLE;
+ m_aLayout.IsMsbFirst = false; // doesn't matter
+
+ m_aComponentTags.realloc(4);
+ sal_Int8* pTags = m_aComponentTags.getArray();
+ pTags[0] = rendering::ColorComponentTag::ALPHA;
+ pTags[1] = rendering::ColorComponentTag::RGB_RED;
+ pTags[2] = rendering::ColorComponentTag::RGB_GREEN;
+ pTags[3] = rendering::ColorComponentTag::RGB_BLUE;
+
+ m_aComponentBitCounts.realloc(4);
+ sal_Int32* pCounts = m_aComponentBitCounts.getArray();
+ pCounts[0] = 8;
+ pCounts[1] = 8;
+ pCounts[2] = 8;
+ pCounts[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_nEndianness = util::Endianness::LITTLE;
+ m_aLayout.IsMsbFirst = false; // doesn't matter
+
+ m_aComponentTags.realloc(4);
+ sal_Int8* pTags = m_aComponentTags.getArray();
+ pTags[0] = rendering::ColorComponentTag::RGB_BLUE;
+ pTags[1] = rendering::ColorComponentTag::RGB_GREEN;
+ pTags[2] = rendering::ColorComponentTag::RGB_RED;
+ pTags[3] = rendering::ColorComponentTag::ALPHA;
+
+ m_aComponentBitCounts.realloc(4);
+ sal_Int32* pCounts = m_aComponentBitCounts.getArray();
+ pCounts[0] = 8;
+ pCounts[1] = 8;
+ pCounts[2] = 8;
+ pCounts[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_nEndianness = util::Endianness::LITTLE;
+ m_aLayout.IsMsbFirst = false; // doesn't matter
+
+ m_aComponentTags.realloc(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;
+
+ m_aComponentBitCounts.realloc(4);
+ sal_Int32* pCounts = m_aComponentBitCounts.getArray();
+ pCounts[0] = 8;
+ pCounts[1] = 8;
+ pCounts[2] = 8;
+ pCounts[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_nEndianness = util::Endianness::LITTLE;
+ 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.realloc(1);
+ m_aComponentTags[0] = rendering::ColorComponentTag::INDEX;
+
+ m_aComponentBitCounts.realloc(1);
+ m_aComponentBitCounts[0] = m_nBitsPerInputPixel;
+
+ m_nIndexIndex = 0;
+ }
+
+ m_nBitsPerOutputPixel = m_nBitsPerInputPixel;
+ if( m_aBmpEx.IsTransparent() )
+ {
+ // 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[m_aComponentTags.getLength()-1] = rendering::ColorComponentTag::ALPHA;
+
+ m_aComponentBitCounts.realloc(m_aComponentBitCounts.getLength()+1);
+ m_aComponentBitCounts[m_aComponentBitCounts.getLength()-1] = m_aBmpEx.IsAlpha() ? 8 : 1;
+
+ if( m_nEndianness == util::Endianness::BIG )
+ {
+ // put alpha in front of all the color channels
+ sal_Int8* pTags =m_aComponentTags.getArray();
+ sal_Int32* pCounts=m_aComponentBitCounts.getArray();
+ std::rotate(pTags,
+ pTags+m_aComponentTags.getLength()-1,
+ pTags+m_aComponentTags.getLength());
+ std::rotate(pCounts,
+ pCounts+m_aComponentBitCounts.getLength()-1,
+ pCounts+m_aComponentBitCounts.getLength());
+ ++m_nRedIndex;
+ ++m_nGreenIndex;
+ ++m_nBlueIndex;
+ ++m_nIndexIndex;
+ m_nAlphaIndex=0;
+ }
+
+ // 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.IsTransparent();
+}
+
+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.IsTransparent() && !m_pAlphaAcc )
+ throw lang::IndexOutOfBoundsException();
+
+ if( aRequestedArea.Left() < 0 || aRequestedArea.Top() < 0 ||
+ aRequestedArea.Right() > m_pBmpAcc->Width() ||
+ aRequestedArea.Bottom() > m_pBmpAcc->Height() )
+ {
+ throw lang::IndexOutOfBoundsException();
+ }
+
+ uno::Sequence< sal_Int8 > aRet;
+ tools::Rectangle aRequestedBytes( aRequestedArea );
+
+ // adapt to byte boundaries
+ aRequestedBytes.SetLeft( aRequestedArea.Left()*m_nBitsPerOutputPixel/8 );
+ aRequestedBytes.SetRight( (aRequestedArea.Right()*m_nBitsPerOutputPixel + 7)/8 );
+
+ // copy stuff to output sequence
+ aRet.realloc(aRequestedBytes.getWidth()*aRequestedBytes.getHeight());
+ sal_Int8* pOutBuf = aRet.getArray();
+
+ bitmapLayout.ScanLines = aRequestedBytes.getHeight();
+ bitmapLayout.ScanLineBytes =
+ bitmapLayout.ScanLineStride= aRequestedBytes.getWidth();
+
+ sal_Int32 nScanlineStride=bitmapLayout.ScanLineStride;
+ if( !(m_pBmpAcc->GetScanlineFormat() & ScanlineFormat::TopDown) )
+ {
+ pOutBuf += bitmapLayout.ScanLineStride*(aRequestedBytes.getHeight()-1);
+ nScanlineStride *= -1;
+ }
+
+ if( !m_aBmpEx.IsTransparent() )
+ {
+ OSL_ENSURE(m_pBmpAcc,"Invalid bmp read access");
+
+ // can return bitmap data as-is
+ for( long y=aRequestedBytes.Top(); y<aRequestedBytes.Bottom(); ++y )
+ {
+ Scanline pScan = m_pBmpAcc->GetScanline(y);
+ memcpy(pOutBuf, pScan+aRequestedBytes.Left(), aRequestedBytes.getWidth());
+ pOutBuf += nScanlineStride;
+ }
+ }
+ else
+ {
+ OSL_ENSURE(m_pBmpAcc,"Invalid bmp read access");
+ OSL_ENSURE(m_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( 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( long x=aRequestedArea.Left(); x<aRequestedArea.Right(); ++x )
+ {
+ *pOutScan++ = m_pBmpAcc->GetPixelIndex(y,x);
+ *pOutScan++ = m_pAlphaAcc->GetPixelIndex(y,x);
+ }
+ }
+ else
+ {
+ const long nNonAlphaBytes( m_nBitsPerInputPixel/8 );
+ const long nScanlineOffsetLeft(aRequestedArea.Left()*nNonAlphaBytes);
+ Scanline pScan = m_pBmpAcc->GetScanline(y) + nScanlineOffsetLeft;
+ Scanline pScanlineAlpha = m_pAlphaAcc->GetScanline( y );
+
+ // input integer multiple of byte - copy directly
+ for( long x=aRequestedArea.Left(); x<aRequestedArea.Right(); ++x )
+ {
+ for( long i=0; i<nNonAlphaBytes; ++i )
+ *pOutScan++ = *pScan++;
+ *pOutScan++ = m_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.IsTransparent() && !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 long nScanlineLeftOffset( pos.X*m_nBitsPerInputPixel/8 );
+ if( !m_aBmpEx.IsTransparent() )
+ {
+ assert(m_pBmpAcc && "Invalid bmp read access");
+
+ // can return bitmap data as-is
+ Scanline pScan = m_pBmpAcc->GetScanline(pos.Y);
+ memcpy(pOutBuf, pScan+nScanlineLeftOffset, aRet.getLength() );
+ }
+ else
+ {
+ assert(m_pBmpAcc && "Invalid bmp read access");
+ assert(m_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++ = m_pBmpAcc->GetPixelIndex(pos.Y,pos.X);
+ *pOutBuf = m_pAlphaAcc->GetPixelIndex(pos.Y,pos.X);
+ }
+ else
+ {
+ const long nNonAlphaBytes( m_nBitsPerInputPixel/8 );
+ Scanline pScan = m_pBmpAcc->GetScanline(pos.Y);
+
+ // input integer multiple of byte - copy directly
+ memcpy(pOutBuf, pScan+nScanlineLeftOffset, nNonAlphaBytes );
+ pOutBuf += nNonAlphaBytes;
+ *pOutBuf++ = m_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
+}
+
+namespace
+{
+ struct PaletteColorSpaceHolder: public rtl::StaticWithInit<uno::Reference<rendering::XColorSpace>,
+ PaletteColorSpaceHolder>
+ {
+ uno::Reference<rendering::XColorSpace> operator()()
+ {
+ return vcl::unotools::createStandardColorSpace();
+ }
+ };
+}
+
+uno::Reference< rendering::XColorSpace > SAL_CALL VclCanvasBitmap::getColorSpace( )
+{
+ // this is the method from XBitmapPalette. Return palette color
+ // space here
+ return PaletteColorSpaceHolder::get();
+}
+
+sal_Int8 SAL_CALL VclCanvasBitmap::getType( )
+{
+ return rendering::ColorSpaceType::RGB;
+}
+
+uno::Sequence< ::sal_Int8 > SAL_CALL VclCanvasBitmap::getComponentTags( )
+{
+ SolarMutexGuard aGuard;
+ return m_aComponentTags;
+}
+
+sal_Int8 SAL_CALL VclCanvasBitmap::getRenderingIntent( )
+{
+ return rendering::RenderingIntent::PERCEPTUAL;
+}
+
+uno::Sequence< ::beans::PropertyValue > SAL_CALL VclCanvasBitmap::getProperties( )
+{
+ return uno::Sequence< ::beans::PropertyValue >();
+}
+
+uno::Sequence< double > SAL_CALL VclCanvasBitmap::convertColorSpace( const uno::Sequence< double >& deviceColor,
+ const uno::Reference< ::rendering::XColorSpace >& targetColorSpace )
+{
+ // TODO(P3): if we know anything about target
+ // colorspace, this can be greatly sped up
+ uno::Sequence<rendering::ARGBColor> aIntermediate(
+ convertToARGB(deviceColor));
+ return targetColorSpace->convertFromARGB(aIntermediate);
+}
+
+uno::Sequence<rendering::RGBColor> SAL_CALL VclCanvasBitmap::convertToRGB( const uno::Sequence< double >& deviceColor )
+{
+ SolarMutexGuard aGuard;
+
+ const std::size_t nLen( deviceColor.getLength() );
+ const sal_Int32 nComponentsPerPixel(m_aComponentTags.getLength());
+ ENSURE_ARG_OR_THROW2(nLen%nComponentsPerPixel==0,
+ "number of channels no multiple of pixel element count",
+ static_cast<rendering::XBitmapPalette*>(this), 01);
+
+ uno::Sequence< rendering::RGBColor > aRes(nLen/nComponentsPerPixel);
+ rendering::RGBColor* pOut( aRes.getArray() );
+
+ if( m_bPalette )
+ {
+ OSL_ENSURE(m_nIndexIndex != -1,
+ "Invalid color channel indices");
+ ENSURE_OR_THROW(m_pBmpAcc,
+ "Unable to get BitmapAccess");
+
+ for( std::size_t i=0; i<nLen; i+=nComponentsPerPixel )
+ {
+ const BitmapColor aCol = m_pBmpAcc->GetPaletteColor(
+ sal::static_int_cast<sal_uInt16>(deviceColor[i+m_nIndexIndex]));
+
+ // TODO(F3): Convert result to sRGB color space
+ *pOut++ = rendering::RGBColor(toDoubleColor(aCol.GetRed()),
+ toDoubleColor(aCol.GetGreen()),
+ toDoubleColor(aCol.GetBlue()));
+ }
+ }
+ else
+ {
+ OSL_ENSURE(m_nRedIndex != -1 && m_nGreenIndex != -1 && m_nBlueIndex != -1,
+ "Invalid color channel indices");
+
+ for( std::size_t i=0; i<nLen; i+=nComponentsPerPixel )
+ {
+ // TODO(F3): Convert result to sRGB color space
+ *pOut++ = rendering::RGBColor(
+ deviceColor[i+m_nRedIndex],
+ deviceColor[i+m_nGreenIndex],
+ deviceColor[i+m_nBlueIndex]);
+ }
+ }
+
+ return aRes;
+}
+
+uno::Sequence<rendering::ARGBColor> SAL_CALL VclCanvasBitmap::convertToARGB( const uno::Sequence< double >& deviceColor )
+{
+ SolarMutexGuard aGuard;
+
+ const std::size_t nLen( deviceColor.getLength() );
+ const sal_Int32 nComponentsPerPixel(m_aComponentTags.getLength());
+ ENSURE_ARG_OR_THROW2(nLen%nComponentsPerPixel==0,
+ "number of channels no multiple of pixel element count",
+ static_cast<rendering::XBitmapPalette*>(this), 01);
+
+ uno::Sequence< rendering::ARGBColor > aRes(nLen/nComponentsPerPixel);
+ rendering::ARGBColor* pOut( aRes.getArray() );
+
+ if( m_bPalette )
+ {
+ OSL_ENSURE(m_nIndexIndex != -1,
+ "Invalid color channel indices");
+ ENSURE_OR_THROW(m_pBmpAcc,
+ "Unable to get BitmapAccess");
+
+ for( std::size_t i=0; i<nLen; i+=nComponentsPerPixel )
+ {
+ const BitmapColor aCol = m_pBmpAcc->GetPaletteColor(
+ sal::static_int_cast<sal_uInt16>(deviceColor[i+m_nIndexIndex]));
+
+ // TODO(F3): Convert result to sRGB color space
+ const double nAlpha( m_nAlphaIndex != -1 ? 1.0 - deviceColor[i+m_nAlphaIndex] : 1.0 );
+ *pOut++ = rendering::ARGBColor(nAlpha,
+ toDoubleColor(aCol.GetRed()),
+ toDoubleColor(aCol.GetGreen()),
+ toDoubleColor(aCol.GetBlue()));
+ }
+ }
+ else
+ {
+ OSL_ENSURE(m_nRedIndex != -1 && m_nGreenIndex != -1 && m_nBlueIndex != -1,
+ "Invalid color channel indices");
+
+ for( std::size_t i=0; i<nLen; i+=nComponentsPerPixel )
+ {
+ // TODO(F3): Convert result to sRGB color space
+ const double nAlpha( m_nAlphaIndex != -1 ? 1.0 - deviceColor[i+m_nAlphaIndex] : 1.0 );
+ *pOut++ = rendering::ARGBColor(
+ nAlpha,
+ deviceColor[i+m_nRedIndex],
+ deviceColor[i+m_nGreenIndex],
+ deviceColor[i+m_nBlueIndex]);
+ }
+ }
+
+ return aRes;
+}
+
+uno::Sequence<rendering::ARGBColor> SAL_CALL VclCanvasBitmap::convertToPARGB( const uno::Sequence< double >& deviceColor )
+{
+ SolarMutexGuard aGuard;
+
+ const std::size_t nLen( deviceColor.getLength() );
+ const sal_Int32 nComponentsPerPixel(m_aComponentTags.getLength());
+ ENSURE_ARG_OR_THROW2(nLen%nComponentsPerPixel==0,
+ "number of channels no multiple of pixel element count",
+ static_cast<rendering::XBitmapPalette*>(this), 01);
+
+ uno::Sequence< rendering::ARGBColor > aRes(nLen/nComponentsPerPixel);
+ rendering::ARGBColor* pOut( aRes.getArray() );
+
+ if( m_bPalette )
+ {
+ OSL_ENSURE(m_nIndexIndex != -1,
+ "Invalid color channel indices");
+ ENSURE_OR_THROW(m_pBmpAcc,
+ "Unable to get BitmapAccess");
+
+ for( std::size_t i=0; i<nLen; i+=nComponentsPerPixel )
+ {
+ const BitmapColor aCol = m_pBmpAcc->GetPaletteColor(
+ sal::static_int_cast<sal_uInt16>(deviceColor[i+m_nIndexIndex]));
+
+ // TODO(F3): Convert result to sRGB color space
+ const double nAlpha( m_nAlphaIndex != -1 ? 1.0 - deviceColor[i+m_nAlphaIndex] : 1.0 );
+ *pOut++ = rendering::ARGBColor(nAlpha,
+ nAlpha*toDoubleColor(aCol.GetRed()),
+ nAlpha*toDoubleColor(aCol.GetGreen()),
+ nAlpha*toDoubleColor(aCol.GetBlue()));
+ }
+ }
+ else
+ {
+ OSL_ENSURE(m_nRedIndex != -1 && m_nGreenIndex != -1 && m_nBlueIndex != -1,
+ "Invalid color channel indices");
+
+ for( std::size_t i=0; i<nLen; i+=nComponentsPerPixel )
+ {
+ // TODO(F3): Convert result to sRGB color space
+ const double nAlpha( m_nAlphaIndex != -1 ? 1.0 - deviceColor[i+m_nAlphaIndex] : 1.0 );
+ *pOut++ = rendering::ARGBColor(
+ nAlpha,
+ nAlpha*deviceColor[i+m_nRedIndex],
+ nAlpha*deviceColor[i+m_nGreenIndex],
+ nAlpha*deviceColor[i+m_nBlueIndex]);
+ }
+ }
+
+ return aRes;
+}
+
+uno::Sequence< double > SAL_CALL VclCanvasBitmap::convertFromRGB( const uno::Sequence<rendering::RGBColor>& rgbColor )
+{
+ SolarMutexGuard aGuard;
+
+ const std::size_t nLen( rgbColor.getLength() );
+ const sal_Int32 nComponentsPerPixel(m_aComponentTags.getLength());
+
+ uno::Sequence< double > aRes(nLen*nComponentsPerPixel);
+ double* pColors=aRes.getArray();
+
+ if( m_bPalette )
+ {
+ for( const auto& rIn : rgbColor )
+ {
+ pColors[m_nIndexIndex] = m_pBmpAcc->GetBestPaletteIndex(
+ BitmapColor(toByteColor(rIn.Red),
+ toByteColor(rIn.Green),
+ toByteColor(rIn.Blue)));
+ if( m_nAlphaIndex != -1 )
+ pColors[m_nAlphaIndex] = 1.0;
+
+ pColors += nComponentsPerPixel;
+ }
+ }
+ else
+ {
+ for( const auto& rIn : rgbColor )
+ {
+ pColors[m_nRedIndex] = rIn.Red;
+ pColors[m_nGreenIndex] = rIn.Green;
+ pColors[m_nBlueIndex] = rIn.Blue;
+ if( m_nAlphaIndex != -1 )
+ pColors[m_nAlphaIndex] = 1.0;
+
+ pColors += nComponentsPerPixel;
+ }
+ }
+ return aRes;
+}
+
+uno::Sequence< double > SAL_CALL VclCanvasBitmap::convertFromARGB( const uno::Sequence<rendering::ARGBColor>& rgbColor )
+{
+ SolarMutexGuard aGuard;
+
+ const std::size_t nLen( rgbColor.getLength() );
+ const sal_Int32 nComponentsPerPixel(m_aComponentTags.getLength());
+
+ uno::Sequence< double > aRes(nLen*nComponentsPerPixel);
+ double* pColors=aRes.getArray();
+
+ if( m_bPalette )
+ {
+ for( const auto& rIn : rgbColor )
+ {
+ pColors[m_nIndexIndex] = m_pBmpAcc->GetBestPaletteIndex(
+ BitmapColor(toByteColor(rIn.Red),
+ toByteColor(rIn.Green),
+ toByteColor(rIn.Blue)));
+ if( m_nAlphaIndex != -1 )
+ pColors[m_nAlphaIndex] = rIn.Alpha;
+
+ pColors += nComponentsPerPixel;
+ }
+ }
+ else
+ {
+ for( const auto& rIn : rgbColor )
+ {
+ pColors[m_nRedIndex] = rIn.Red;
+ pColors[m_nGreenIndex] = rIn.Green;
+ pColors[m_nBlueIndex] = rIn.Blue;
+ if( m_nAlphaIndex != -1 )
+ pColors[m_nAlphaIndex] = rIn.Alpha;
+
+ pColors += nComponentsPerPixel;
+ }
+ }
+ return aRes;
+}
+
+uno::Sequence< double > SAL_CALL VclCanvasBitmap::convertFromPARGB( const uno::Sequence<rendering::ARGBColor>& rgbColor )
+{
+ SolarMutexGuard aGuard;
+
+ const std::size_t nLen( rgbColor.getLength() );
+ const sal_Int32 nComponentsPerPixel(m_aComponentTags.getLength());
+
+ uno::Sequence< double > aRes(nLen*nComponentsPerPixel);
+ double* pColors=aRes.getArray();
+
+ if( m_bPalette )
+ {
+ for( const auto& rIn : rgbColor )
+ {
+ const double nAlpha( rIn.Alpha );
+ pColors[m_nIndexIndex] = m_pBmpAcc->GetBestPaletteIndex(
+ BitmapColor(toByteColor(rIn.Red / nAlpha),
+ toByteColor(rIn.Green / nAlpha),
+ toByteColor(rIn.Blue / nAlpha)));
+ if( m_nAlphaIndex != -1 )
+ pColors[m_nAlphaIndex] = nAlpha;
+
+ pColors += nComponentsPerPixel;
+ }
+ }
+ else
+ {
+ for( const auto& rIn : rgbColor )
+ {
+ const double nAlpha( rIn.Alpha );
+ pColors[m_nRedIndex] = rIn.Red / nAlpha;
+ pColors[m_nGreenIndex] = rIn.Green / nAlpha;
+ pColors[m_nBlueIndex] = rIn.Blue / nAlpha;
+ if( m_nAlphaIndex != -1 )
+ pColors[m_nAlphaIndex] = nAlpha;
+
+ pColors += nComponentsPerPixel;
+ }
+ }
+ return aRes;
+}
+
+sal_Int32 SAL_CALL VclCanvasBitmap::getBitsPerPixel( )
+{
+ SolarMutexGuard aGuard;
+ return m_nBitsPerOutputPixel;
+}
+
+uno::Sequence< ::sal_Int32 > SAL_CALL VclCanvasBitmap::getComponentBitCounts( )
+{
+ SolarMutexGuard aGuard;
+ return m_aComponentBitCounts;
+}
+
+sal_Int8 SAL_CALL VclCanvasBitmap::getEndianness( )
+{
+ SolarMutexGuard aGuard;
+ return m_nEndianness;
+}
+
+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() );
+
+ ENSURE_OR_THROW(m_pBmpAcc,
+ "Unable to get BitmapAccess");
+
+ if( m_aBmpEx.IsTransparent() )
+ {
+ 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 ?
+ m_pBmpAcc->GetPaletteColor(*pIn) :
+ m_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 ?
+ m_pBmpAcc->GetPaletteColor( m_pBmpAcc->GetPixelFromData( pIn, i ).GetIndex()) :
+ m_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() );
+
+ ENSURE_OR_THROW(m_pBmpAcc,
+ "Unable to get BitmapAccess");
+
+ if( m_aBmpEx.IsTransparent() )
+ {
+ const long nNonAlphaBytes( (m_nBitsPerInputPixel+7)/8 );
+ const sal_Int32 nBytesPerPixel((m_nBitsPerOutputPixel+7)/8);
+ const sal_uInt8 nAlphaFactor( m_aBmpEx.IsAlpha() ? 1 : 255 );
+ for( std::size_t i=0; i<nLen; i+=nBytesPerPixel )
+ {
+ // if palette, index is guaranteed to be 8 bit
+ const BitmapColor aCol =
+ m_bPalette ?
+ m_pBmpAcc->GetPaletteColor(*pIn) :
+ m_pBmpAcc->GetPixelFromData(pIn,0);
+
+ // TODO(F3): Convert result to sRGB color space
+ *pOut++ = rendering::ARGBColor(1.0 - toDoubleColor(nAlphaFactor*pIn[nNonAlphaBytes]),
+ toDoubleColor(aCol.GetRed()),
+ toDoubleColor(aCol.GetGreen()),
+ toDoubleColor(aCol.GetBlue()));
+ pIn += nBytesPerPixel;
+ }
+ }
+ else
+ {
+ for( sal_Int32 i=0; i<nNumColors; ++i )
+ {
+ const BitmapColor aCol =
+ m_bPalette ?
+ m_pBmpAcc->GetPaletteColor( m_pBmpAcc->GetPixelFromData( pIn, i ).GetIndex() ) :
+ m_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() );
+
+ ENSURE_OR_THROW(m_pBmpAcc,
+ "Unable to get BitmapAccess");
+
+ if( m_aBmpEx.IsTransparent() )
+ {
+ const long nNonAlphaBytes( (m_nBitsPerInputPixel+7)/8 );
+ const sal_Int32 nBytesPerPixel((m_nBitsPerOutputPixel+7)/8);
+ const sal_uInt8 nAlphaFactor( m_aBmpEx.IsAlpha() ? 1 : 255 );
+ for( std::size_t i=0; i<nLen; i+=nBytesPerPixel )
+ {
+ // if palette, index is guaranteed to be 8 bit
+ const BitmapColor aCol =
+ m_bPalette ?
+ m_pBmpAcc->GetPaletteColor(*pIn) :
+ m_pBmpAcc->GetPixelFromData(pIn,0);
+
+ // TODO(F3): Convert result to sRGB color space
+ const double nAlpha( 1.0 - toDoubleColor(nAlphaFactor*pIn[nNonAlphaBytes]) );
+ *pOut++ = rendering::ARGBColor(nAlpha,
+ nAlpha*toDoubleColor(aCol.GetRed()),
+ nAlpha*toDoubleColor(aCol.GetGreen()),
+ nAlpha*toDoubleColor(aCol.GetBlue()));
+ pIn += nBytesPerPixel;
+ }
+ }
+ else
+ {
+ for( sal_Int32 i=0; i<nNumColors; ++i )
+ {
+ const BitmapColor aCol =
+ m_bPalette ?
+ m_pBmpAcc->GetPaletteColor( m_pBmpAcc->GetPixelFromData( pIn, i ).GetIndex() ) :
+ m_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());
+
+ if( m_aBmpEx.IsTransparent() )
+ {
+ const 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>(m_pBmpAcc->GetBestPaletteIndex( aCol ))) :
+ aCol;
+
+ m_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>(m_pBmpAcc->GetBestPaletteIndex( aCol ))) :
+ aCol;
+
+ m_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());
+
+ if( m_aBmpEx.IsTransparent() )
+ {
+ const 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>(m_pBmpAcc->GetBestPaletteIndex( aCol ))) :
+ aCol;
+
+ m_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>(m_pBmpAcc->GetBestPaletteIndex( aCol ))) :
+ aCol;
+
+ m_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());
+
+ if( m_aBmpEx.IsTransparent() )
+ {
+ const 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>(m_pBmpAcc->GetBestPaletteIndex( aCol ))) :
+ aCol;
+
+ m_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>(m_pBmpAcc->GetBestPaletteIndex( aCol ))) :
+ aCol;
+
+ m_pBmpAcc->SetPixelOnData(pColors,i,aCol2);
+ }
+ }
+
+ return aRes;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/helper/canvastools.cxx b/vcl/source/helper/canvastools.cxx
new file mode 100644
index 000000000..18f923d50
--- /dev/null
+++ b/vcl/source/helper/canvastools.cxx
@@ -0,0 +1,623 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <com/sun/star/geometry/RealSize2D.hpp>
+#include <com/sun/star/geometry/IntegerSize2D.hpp>
+#include <com/sun/star/geometry/IntegerPoint2D.hpp>
+#include <com/sun/star/geometry/IntegerRectangle2D.hpp>
+
+#include <com/sun/star/rendering/ColorSpaceType.hpp>
+#include <com/sun/star/rendering/RenderingIntent.hpp>
+#include <com/sun/star/rendering/VolatileContentDestroyedException.hpp>
+#include <com/sun/star/rendering/XBitmap.hpp>
+#include <com/sun/star/rendering/IntegerBitmapLayout.hpp>
+#include <com/sun/star/rendering/ColorComponentTag.hpp>
+
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/range/b2drectangle.hxx>
+#include <basegfx/point/b2ipoint.hxx>
+#include <basegfx/range/b2irectangle.hxx>
+
+#include <sal/log.hxx>
+#include <tools/helpers.hxx>
+#include <tools/diagnose_ex.h>
+
+#include <vcl/bitmapex.hxx>
+
+#include <canvasbitmap.hxx>
+#include <vcl/canvastools.hxx>
+#include <bitmapwriteaccess.hxx>
+
+using namespace ::com::sun::star;
+
+namespace vcl::unotools
+{
+ uno::Reference< rendering::XBitmap > xBitmapFromBitmapEx(const ::BitmapEx& inputBitmap )
+ {
+ SAL_INFO( "vcl.helper", "vcl::unotools::xBitmapFromBitmapEx()" );
+
+ return new vcl::unotools::VclCanvasBitmap( inputBitmap );
+ }
+
+ namespace
+ {
+ bool equalsLayout( const rendering::IntegerBitmapLayout& rLHS,
+ const rendering::IntegerBitmapLayout& rRHS )
+ {
+ return
+ rLHS.ScanLineBytes == rRHS.ScanLineBytes &&
+ rLHS.ScanLineStride == rRHS.ScanLineStride &&
+ rLHS.PlaneStride == rRHS.PlaneStride &&
+ rLHS.ColorSpace == rRHS.ColorSpace &&
+ rLHS.Palette == rRHS.Palette &&
+ rLHS.IsMsbFirst == rRHS.IsMsbFirst;
+ }
+ bool readBmp( sal_Int32 nWidth,
+ sal_Int32 nHeight,
+ const rendering::IntegerBitmapLayout& rLayout,
+ const uno::Reference< rendering::XIntegerReadOnlyBitmap >& xInputBitmap,
+ BitmapScopedWriteAccess& rWriteAcc,
+ BitmapScopedWriteAccess& rAlphaAcc )
+ {
+ rendering::IntegerBitmapLayout aCurrLayout;
+ geometry::IntegerRectangle2D aRect;
+ uno::Sequence<sal_Int8> aPixelData;
+ uno::Sequence<rendering::RGBColor> aRGBColors;
+ uno::Sequence<rendering::ARGBColor> aARGBColors;
+
+ for( aRect.Y1=0; aRect.Y1<nHeight; ++aRect.Y1 )
+ {
+ aRect.X1 = 0; aRect.X2 = nWidth; aRect.Y2 = aRect.Y1+1;
+ try
+ {
+ aPixelData = xInputBitmap->getData(aCurrLayout,aRect);
+ }
+ catch( rendering::VolatileContentDestroyedException& )
+ {
+ // re-read bmp from the start
+ return false;
+ }
+ if( !equalsLayout(aCurrLayout, rLayout) )
+ return false; // re-read bmp from the start
+
+ Scanline pScanline = rWriteAcc->GetScanline( aRect.Y1 );
+ if( rAlphaAcc.get() )
+ {
+ Scanline pScanlineAlpha = rAlphaAcc->GetScanline( aRect.Y1 );
+ // read ARGB color
+ aARGBColors = rLayout.ColorSpace->convertIntegerToARGB(aPixelData);
+
+ if( rWriteAcc->HasPalette() )
+ {
+ for( sal_Int32 x=0; x<nWidth; ++x )
+ {
+ const rendering::ARGBColor& rColor=aARGBColors[x];
+ rWriteAcc->SetPixelOnData( pScanline, x,
+ BitmapColor(static_cast<sal_uInt8>(rWriteAcc->GetBestPaletteIndex(
+ BitmapColor( toByteColor(rColor.Red),
+ toByteColor(rColor.Green),
+ toByteColor(rColor.Blue))))) );
+ rAlphaAcc->SetPixelOnData( pScanlineAlpha, x,
+ BitmapColor( 255 - toByteColor(rColor.Alpha) ));
+ }
+ }
+ else
+ {
+ for( sal_Int32 x=0; x<nWidth; ++x )
+ {
+ const rendering::ARGBColor& rColor=aARGBColors[x];
+ rWriteAcc->SetPixelOnData( pScanline, x,
+ BitmapColor( toByteColor(rColor.Red),
+ toByteColor(rColor.Green),
+ toByteColor(rColor.Blue) ));
+ rAlphaAcc->SetPixelOnData( pScanlineAlpha, x,
+ BitmapColor( 255 - toByteColor(rColor.Alpha) ));
+ }
+ }
+ }
+ else
+ {
+ // read RGB color
+ aRGBColors = rLayout.ColorSpace->convertIntegerToRGB(aPixelData);
+ if( rWriteAcc->HasPalette() )
+ {
+ for( sal_Int32 x=0; x<nWidth; ++x )
+ {
+ const rendering::RGBColor& rColor=aRGBColors[x];
+ rWriteAcc->SetPixelOnData( pScanline, x,
+ BitmapColor(static_cast<sal_uInt8>(rWriteAcc->GetBestPaletteIndex(
+ BitmapColor( toByteColor(rColor.Red),
+ toByteColor(rColor.Green),
+ toByteColor(rColor.Blue))))) );
+ }
+ }
+ else
+ {
+ for( sal_Int32 x=0; x<nWidth; ++x )
+ {
+ const rendering::RGBColor& rColor=aRGBColors[x];
+ rWriteAcc->SetPixelOnData( pScanline, x,
+ BitmapColor( toByteColor(rColor.Red),
+ toByteColor(rColor.Green),
+ toByteColor(rColor.Blue) ));
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+ }
+
+ ::BitmapEx bitmapExFromXBitmap( const uno::Reference< rendering::XIntegerReadOnlyBitmap >& xInputBitmap )
+ {
+ SAL_INFO( "vcl.helper", "vcl::unotools::bitmapExFromXBitmap()" );
+
+ if( !xInputBitmap.is() )
+ return ::BitmapEx();
+
+ // tunnel directly for known implementation
+ VclCanvasBitmap* pImplBitmap = dynamic_cast<VclCanvasBitmap*>(xInputBitmap.get());
+ if( pImplBitmap )
+ return pImplBitmap->getBitmapEx();
+
+ // retrieve data via UNO interface
+
+ // volatile bitmaps are a bit more complicated to read
+ // from...
+
+ // loop a few times, until successfully read (for XVolatileBitmap)
+ for( int i=0; i<10; ++i )
+ {
+ sal_Int32 nDepth=0;
+ sal_Int32 nAlphaDepth=0;
+ const rendering::IntegerBitmapLayout aLayout(
+ xInputBitmap->getMemoryLayout());
+
+ OSL_ENSURE(aLayout.ColorSpace.is(),
+ "Cannot convert image without color space!");
+ if( !aLayout.ColorSpace.is() )
+ return ::BitmapEx();
+
+ nDepth = aLayout.ColorSpace->getBitsPerPixel();
+
+ if( xInputBitmap->hasAlpha() )
+ {
+ // determine alpha channel depth
+ const uno::Sequence<sal_Int8> aTags(
+ aLayout.ColorSpace->getComponentTags() );
+ const sal_Int8* pStart(aTags.getConstArray());
+ const std::size_t nLen(aTags.getLength());
+ const sal_Int8* pEnd(pStart+nLen);
+
+ const std::ptrdiff_t nAlphaIndex =
+ std::find(pStart,pEnd,
+ rendering::ColorComponentTag::ALPHA) - pStart;
+
+ if( nAlphaIndex < sal::static_int_cast<std::ptrdiff_t>(nLen) )
+ {
+ nAlphaDepth = aLayout.ColorSpace->getComponentBitCounts()[nAlphaIndex] > 1 ? 8 : 1;
+ nDepth -= nAlphaDepth;
+ }
+ }
+
+ BitmapPalette aPalette;
+ if( aLayout.Palette.is() )
+ {
+ uno::Reference< rendering::XColorSpace > xPaletteColorSpace(
+ aLayout.Palette->getColorSpace());
+ ENSURE_OR_THROW(xPaletteColorSpace.is(),
+ "Palette without color space");
+
+ const sal_Int32 nEntryCount( aLayout.Palette->getNumberOfEntries() );
+ if( nEntryCount <= 256 )
+ {
+ if( nEntryCount <= 2 )
+ nDepth = 1;
+ else
+ nDepth = 8;
+
+ const sal_uInt16 nPaletteEntries(
+ sal::static_int_cast<sal_uInt16>(
+ std::min(sal_Int32(255), nEntryCount)));
+
+ // copy palette entries
+ aPalette.SetEntryCount(nPaletteEntries);
+ uno::Reference<rendering::XBitmapPalette> xPalette( aLayout.Palette );
+ uno::Reference<rendering::XColorSpace> xPalColorSpace( xPalette->getColorSpace() );
+
+ uno::Sequence<double> aPaletteEntry;
+ for( sal_uInt16 j=0; j<nPaletteEntries; ++j )
+ {
+ if( !xPalette->getIndex(aPaletteEntry,j) &&
+ nAlphaDepth == 0 )
+ {
+ nAlphaDepth = 1;
+ }
+ uno::Sequence<rendering::RGBColor> aColors=xPalColorSpace->convertToRGB(aPaletteEntry);
+ ENSURE_OR_THROW(aColors.getLength() == 1,
+ "Palette returned more or less than one entry");
+ const rendering::RGBColor& rColor=aColors[0];
+ aPalette[j] = BitmapColor(toByteColor(rColor.Red),
+ toByteColor(rColor.Green),
+ toByteColor(rColor.Blue));
+ }
+ }
+ }
+
+ const ::Size aPixelSize(
+ sizeFromIntegerSize2D(xInputBitmap->getSize()));
+
+ // normalize bitcount
+ nDepth =
+ ( nDepth <= 1 ) ? 1 :
+ ( nDepth <= 4 ) ? 4 :
+ ( nDepth <= 8 ) ? 8 : 24;
+
+ ::Bitmap aBitmap( aPixelSize,
+ sal::static_int_cast<sal_uInt16>(nDepth),
+ aLayout.Palette.is() ? &aPalette : nullptr );
+ ::Bitmap aAlpha;
+ if( nAlphaDepth )
+ aAlpha = ::Bitmap( aPixelSize,
+ sal::static_int_cast<sal_uInt16>(nAlphaDepth),
+ &::Bitmap::GetGreyPalette(
+ sal::static_int_cast<sal_uInt16>(1 << nAlphaDepth)) );
+
+ { // limit scoped access
+ BitmapScopedWriteAccess pWriteAccess( aBitmap );
+ BitmapScopedWriteAccess pAlphaWriteAccess( nAlphaDepth ? aAlpha.AcquireWriteAccess() : nullptr,
+ aAlpha );
+
+ ENSURE_OR_THROW(pWriteAccess.get() != nullptr,
+ "Cannot get write access to bitmap");
+
+ const sal_Int32 nWidth(aPixelSize.Width());
+ const sal_Int32 nHeight(aPixelSize.Height());
+
+ if( !readBmp(nWidth,nHeight,aLayout,xInputBitmap,
+ pWriteAccess,pAlphaWriteAccess) )
+ continue;
+ } // limit scoped access
+
+ if( nAlphaDepth )
+ return ::BitmapEx( aBitmap,
+ AlphaMask( aAlpha ) );
+ else
+ return ::BitmapEx( aBitmap );
+ }
+
+ // failed to read data 10 times - bail out
+ return ::BitmapEx();
+ }
+
+ geometry::RealSize2D size2DFromSize( const Size& rSize )
+ {
+ return geometry::RealSize2D( rSize.Width(),
+ rSize.Height() );
+ }
+
+ Size sizeFromRealSize2D( const geometry::RealSize2D& rSize )
+ {
+ return Size( static_cast<long>(rSize.Width + .5),
+ static_cast<long>(rSize.Height + .5) );
+ }
+
+ ::Size sizeFromB2DSize( const basegfx::B2DVector& rVec )
+ {
+ return ::Size( FRound( rVec.getX() ),
+ FRound( rVec.getY() ) );
+ }
+
+ ::Point pointFromB2DPoint( const basegfx::B2DPoint& rPoint )
+ {
+ return pointFromB2IPoint(basegfx::fround(rPoint));
+ }
+
+ ::tools::Rectangle rectangleFromB2DRectangle( const basegfx::B2DRange& rRect )
+ {
+ return rectangleFromB2IRectangle(basegfx::fround(rRect));
+ }
+
+ Point pointFromB2IPoint( const basegfx::B2IPoint& rPoint )
+ {
+ return ::Point( rPoint.getX(),
+ rPoint.getY() );
+ }
+
+ basegfx::B2IPoint b2IPointFromPoint(Point const& rPoint)
+ {
+ return basegfx::B2IPoint(rPoint.X(), rPoint.Y());
+ }
+
+ tools::Rectangle rectangleFromB2IRectangle( const basegfx::B2IRange& rRect )
+ {
+ return ::tools::Rectangle( rRect.getMinX(),
+ rRect.getMinY(),
+ rRect.getMaxX(),
+ rRect.getMaxY() );
+ }
+
+ basegfx::B2IRectangle b2IRectangleFromRectangle(tools::Rectangle const& rRect)
+ {
+ // although B2IRange internally has separate height/width emptiness, it doesn't
+ // expose any API to let us set them separately, so just do the best we can.
+ if (rRect.IsWidthEmpty() && rRect.IsHeightEmpty())
+ return basegfx::B2IRange( basegfx::B2ITuple( rRect.Left(), rRect.Top() ) );
+ return basegfx::B2IRange( rRect.Left(),
+ rRect.Top(),
+ rRect.IsWidthEmpty() ? rRect.Left() : rRect.Right(),
+ rRect.IsHeightEmpty() ? rRect.Top() : rRect.Bottom() );
+ }
+
+ basegfx::B2DVector b2DSizeFromSize( const ::Size& rSize )
+ {
+ return basegfx::B2DVector( rSize.Width(),
+ rSize.Height() );
+ }
+
+ basegfx::B2DPoint b2DPointFromPoint( const ::Point& rPoint )
+ {
+ return basegfx::B2DPoint( rPoint.X(),
+ rPoint.Y() );
+ }
+
+ basegfx::B2DRange b2DRectangleFromRectangle( const ::tools::Rectangle& rRect )
+ {
+ // although B2DRange internally has separate height/width emptiness, it doesn't
+ // expose any API to let us set them separately, so just do the best we can.
+ if (rRect.IsWidthEmpty() && rRect.IsHeightEmpty())
+ return basegfx::B2DRange( basegfx::B2DTuple( rRect.Left(), rRect.Top() ) );
+ return basegfx::B2DRectangle( rRect.Left(),
+ rRect.Top(),
+ rRect.IsWidthEmpty() ? rRect.Left() : rRect.Right(),
+ rRect.IsHeightEmpty() ? rRect.Top() : rRect.Bottom() );
+ }
+
+ geometry::IntegerSize2D integerSize2DFromSize( const Size& rSize )
+ {
+ return geometry::IntegerSize2D( rSize.Width(),
+ rSize.Height() );
+ }
+
+ Size sizeFromIntegerSize2D( const geometry::IntegerSize2D& rSize )
+ {
+ return Size( rSize.Width,
+ rSize.Height );
+ }
+
+ Point pointFromIntegerPoint2D( const geometry::IntegerPoint2D& rPoint )
+ {
+ return Point( rPoint.X,
+ rPoint.Y );
+ }
+
+ tools::Rectangle rectangleFromIntegerRectangle2D( const geometry::IntegerRectangle2D& rRectangle )
+ {
+ return tools::Rectangle( rRectangle.X1, rRectangle.Y1,
+ rRectangle.X2, rRectangle.Y2 );
+ }
+
+ namespace
+ {
+ class StandardColorSpace : public cppu::WeakImplHelper< css::rendering::XColorSpace >
+ {
+ private:
+ uno::Sequence< sal_Int8 > m_aComponentTags;
+
+ virtual ::sal_Int8 SAL_CALL getType( ) override
+ {
+ return rendering::ColorSpaceType::RGB;
+ }
+ virtual uno::Sequence< ::sal_Int8 > SAL_CALL getComponentTags( ) override
+ {
+ return m_aComponentTags;
+ }
+ virtual ::sal_Int8 SAL_CALL getRenderingIntent( ) override
+ {
+ return rendering::RenderingIntent::PERCEPTUAL;
+ }
+ virtual uno::Sequence< beans::PropertyValue > SAL_CALL getProperties( ) override
+ {
+ return uno::Sequence< beans::PropertyValue >();
+ }
+ virtual uno::Sequence< double > SAL_CALL convertColorSpace( const uno::Sequence< double >& deviceColor,
+ const uno::Reference< rendering::XColorSpace >& targetColorSpace ) override
+ {
+ // TODO(P3): if we know anything about target
+ // colorspace, this can be greatly sped up
+ uno::Sequence<rendering::ARGBColor> aIntermediate(
+ convertToARGB(deviceColor));
+ return targetColorSpace->convertFromARGB(aIntermediate);
+ }
+ virtual uno::Sequence< rendering::RGBColor > SAL_CALL convertToRGB( const uno::Sequence< double >& deviceColor ) override
+ {
+ const double* pIn( deviceColor.getConstArray() );
+ const std::size_t nLen( deviceColor.getLength() );
+ ENSURE_ARG_OR_THROW2(nLen%4==0,
+ "number of channels no multiple of 4",
+ static_cast<rendering::XColorSpace*>(this), 0);
+
+ uno::Sequence< rendering::RGBColor > aRes(nLen/4);
+ rendering::RGBColor* pOut( aRes.getArray() );
+ for( std::size_t i=0; i<nLen; i+=4 )
+ {
+ *pOut++ = rendering::RGBColor(pIn[0],pIn[1],pIn[2]);
+ pIn += 4;
+ }
+ return aRes;
+ }
+ virtual uno::Sequence< rendering::ARGBColor > SAL_CALL convertToARGB( const uno::Sequence< double >& deviceColor ) override
+ {
+ const double* pIn( deviceColor.getConstArray() );
+ const std::size_t nLen( deviceColor.getLength() );
+ ENSURE_ARG_OR_THROW2(nLen%4==0,
+ "number of channels no multiple of 4",
+ static_cast<rendering::XColorSpace*>(this), 0);
+
+ uno::Sequence< rendering::ARGBColor > aRes(nLen/4);
+ rendering::ARGBColor* pOut( aRes.getArray() );
+ for( std::size_t i=0; i<nLen; i+=4 )
+ {
+ *pOut++ = rendering::ARGBColor(pIn[3],pIn[0],pIn[1],pIn[2]);
+ pIn += 4;
+ }
+ return aRes;
+ }
+ virtual uno::Sequence< rendering::ARGBColor > SAL_CALL convertToPARGB( const uno::Sequence< double >& deviceColor ) override
+ {
+ const double* pIn( deviceColor.getConstArray() );
+ const std::size_t nLen( deviceColor.getLength() );
+ ENSURE_ARG_OR_THROW2(nLen%4==0,
+ "number of channels no multiple of 4",
+ static_cast<rendering::XColorSpace*>(this), 0);
+
+ uno::Sequence< rendering::ARGBColor > aRes(nLen/4);
+ rendering::ARGBColor* pOut( aRes.getArray() );
+ for( std::size_t i=0; i<nLen; i+=4 )
+ {
+ *pOut++ = rendering::ARGBColor(pIn[3],pIn[3]*pIn[0],pIn[3]*pIn[1],pIn[3]*pIn[2]);
+ pIn += 4;
+ }
+ return aRes;
+ }
+ virtual uno::Sequence< double > SAL_CALL convertFromRGB( const uno::Sequence< rendering::RGBColor >& rgbColor ) override
+ {
+ const std::size_t nLen( rgbColor.getLength() );
+
+ uno::Sequence< double > aRes(nLen*4);
+ double* pColors=aRes.getArray();
+ for( const auto& rIn : rgbColor )
+ {
+ *pColors++ = rIn.Red;
+ *pColors++ = rIn.Green;
+ *pColors++ = rIn.Blue;
+ *pColors++ = 1.0;
+ }
+ return aRes;
+ }
+ virtual uno::Sequence< double > SAL_CALL convertFromARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) override
+ {
+ const std::size_t nLen( rgbColor.getLength() );
+
+ uno::Sequence< double > aRes(nLen*4);
+ double* pColors=aRes.getArray();
+ for( const auto& rIn : rgbColor )
+ {
+ *pColors++ = rIn.Red;
+ *pColors++ = rIn.Green;
+ *pColors++ = rIn.Blue;
+ *pColors++ = rIn.Alpha;
+ }
+ return aRes;
+ }
+ virtual uno::Sequence< double > SAL_CALL convertFromPARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) override
+ {
+ const std::size_t nLen( rgbColor.getLength() );
+
+ uno::Sequence< double > aRes(nLen*4);
+ double* pColors=aRes.getArray();
+ for( const auto& rIn : rgbColor )
+ {
+ *pColors++ = rIn.Red/rIn.Alpha;
+ *pColors++ = rIn.Green/rIn.Alpha;
+ *pColors++ = rIn.Blue/rIn.Alpha;
+ *pColors++ = rIn.Alpha;
+ }
+ return aRes;
+ }
+
+ public:
+ StandardColorSpace() : m_aComponentTags(4)
+ {
+ sal_Int8* pTags = m_aComponentTags.getArray();
+ pTags[0] = rendering::ColorComponentTag::RGB_RED;
+ pTags[1] = rendering::ColorComponentTag::RGB_GREEN;
+ pTags[2] = rendering::ColorComponentTag::RGB_BLUE;
+ pTags[3] = rendering::ColorComponentTag::ALPHA;
+ }
+ };
+ }
+
+ uno::Reference<rendering::XColorSpace> createStandardColorSpace()
+ {
+ return new StandardColorSpace();
+ }
+
+ uno::Sequence< double > colorToStdColorSpaceSequence( const Color& rColor )
+ {
+ uno::Sequence< double > aRet(4);
+ double* pRet = aRet.getArray();
+
+ pRet[0] = toDoubleColor(rColor.GetRed());
+ pRet[1] = toDoubleColor(rColor.GetGreen());
+ pRet[2] = toDoubleColor(rColor.GetBlue());
+
+ // VCL's notion of alpha is different from the rest of the world's
+ pRet[3] = 1.0 - toDoubleColor(rColor.GetTransparency());
+
+ return aRet;
+ }
+
+ 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]) );
+ // VCL's notion of alpha is different from the rest of the world's
+ aColor.SetTransparency( 255 - toByteColor(rColor[3]) );
+
+ return aColor;
+ }
+
+ uno::Sequence< double > colorToDoubleSequence(
+ const Color& rColor,
+ const uno::Reference< rendering::XColorSpace >& xColorSpace )
+ {
+ uno::Sequence<rendering::ARGBColor> aSeq(1);
+ aSeq[0] = rendering::ARGBColor(
+ 1.0-toDoubleColor(rColor.GetTransparency()),
+ 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( 255-toByteColor(aARGBColor.Alpha),
+ toByteColor(aARGBColor.Red),
+ toByteColor(aARGBColor.Green),
+ toByteColor(aARGBColor.Blue) );
+ }
+
+} // namespace canvas
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/helper/commandinfoprovider.cxx b/vcl/source/helper/commandinfoprovider.cxx
new file mode 100644
index 000000000..805aa7ad6
--- /dev/null
+++ b/vcl/source/helper/commandinfoprovider.cxx
@@ -0,0 +1,480 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <vcl/commandinfoprovider.hxx>
+#include <vcl/keycod.hxx>
+#include <vcl/mnemonic.hxx>
+#include <comphelper/string.hxx>
+#include <comphelper/sequence.hxx>
+#include <comphelper/processfactory.hxx>
+#include <cppuhelper/weakref.hxx>
+
+#include <com/sun/star/frame/XFrame.hpp>
+#include <com/sun/star/frame/ModuleManager.hpp>
+#include <com/sun/star/frame/theUICommandDescription.hpp>
+#include <com/sun/star/ui/GlobalAcceleratorConfiguration.hpp>
+#include <com/sun/star/ui/XUIConfigurationManagerSupplier.hpp>
+#include <com/sun/star/ui/theModuleUIConfigurationManagerSupplier.hpp>
+#include <com/sun/star/ui/ImageType.hpp>
+#include <com/sun/star/ui/XImageManager.hpp>
+#include <com/sun/star/awt/KeyModifier.hpp>
+
+using namespace css;
+using namespace css::uno;
+
+namespace vcl::CommandInfoProvider {
+
+static Reference<container::XNameAccess> GetCommandDescription()
+{
+ static WeakReference<container::XNameAccess> xWeakRef;
+ css::uno::Reference<container::XNameAccess> xRef(xWeakRef);
+
+ if (!xRef.is())
+ {
+ xRef = frame::theUICommandDescription::get(comphelper::getProcessComponentContext());
+ xWeakRef = xRef;
+ }
+
+ return xRef;
+}
+
+static Reference<ui::XModuleUIConfigurationManagerSupplier> GetModuleConfigurationSupplier()
+{
+ static WeakReference<ui::XModuleUIConfigurationManagerSupplier> xWeakRef;
+ css::uno::Reference<ui::XModuleUIConfigurationManagerSupplier> xRef(xWeakRef);
+
+ if (!xRef.is())
+ {
+ xRef = ui::theModuleUIConfigurationManagerSupplier::get(comphelper::getProcessComponentContext());
+ xWeakRef = xRef;
+ }
+
+ return xRef;
+}
+
+static Reference<ui::XAcceleratorConfiguration> GetGlobalAcceleratorConfiguration()
+{
+ static WeakReference<ui::XAcceleratorConfiguration> xWeakRef;
+ css::uno::Reference<ui::XAcceleratorConfiguration> xRef(xWeakRef);
+
+ if (!xRef.is())
+ {
+ xRef = ui::GlobalAcceleratorConfiguration::create(comphelper::getProcessComponentContext());
+ xWeakRef = xRef;
+ }
+
+ return xRef;
+}
+
+static Reference<ui::XAcceleratorConfiguration> GetDocumentAcceleratorConfiguration(const Reference<frame::XFrame>& rxFrame)
+{
+ Reference<frame::XController> xController = rxFrame->getController();
+ if (xController.is())
+ {
+ Reference<ui::XUIConfigurationManagerSupplier> xSupplier(xController->getModel(), UNO_QUERY);
+ if (xSupplier.is())
+ {
+ Reference<ui::XUIConfigurationManager> xConfigurationManager(
+ xSupplier->getUIConfigurationManager());
+ if (xConfigurationManager.is())
+ {
+ return xConfigurationManager->getShortCutManager();
+ }
+ }
+ }
+ return nullptr;
+}
+
+static Reference<ui::XAcceleratorConfiguration> GetModuleAcceleratorConfiguration(const Reference<frame::XFrame>& rxFrame)
+{
+ css::uno::Reference<css::ui::XAcceleratorConfiguration> curModuleAcceleratorConfiguration;
+ try
+ {
+ Reference<ui::XModuleUIConfigurationManagerSupplier> xSupplier(GetModuleConfigurationSupplier());
+ Reference<ui::XUIConfigurationManager> xManager (
+ xSupplier->getUIConfigurationManager(GetModuleIdentifier(rxFrame)));
+ if (xManager.is())
+ {
+ curModuleAcceleratorConfiguration = xManager->getShortCutManager();
+ }
+ }
+ catch (Exception&)
+ {
+ }
+ return curModuleAcceleratorConfiguration;
+}
+
+static vcl::KeyCode AWTKey2VCLKey(const awt::KeyEvent& aAWTKey)
+{
+ bool bShift = ((aAWTKey.Modifiers & awt::KeyModifier::SHIFT) == awt::KeyModifier::SHIFT );
+ bool bMod1 = ((aAWTKey.Modifiers & awt::KeyModifier::MOD1 ) == awt::KeyModifier::MOD1 );
+ bool bMod2 = ((aAWTKey.Modifiers & awt::KeyModifier::MOD2 ) == awt::KeyModifier::MOD2 );
+ bool bMod3 = ((aAWTKey.Modifiers & awt::KeyModifier::MOD3 ) == awt::KeyModifier::MOD3 );
+ sal_uInt16 nKey = static_cast<sal_uInt16>(aAWTKey.KeyCode);
+
+ return vcl::KeyCode(nKey, bShift, bMod1, bMod2, bMod3);
+}
+
+static OUString RetrieveShortcutsFromConfiguration(
+ const Reference<ui::XAcceleratorConfiguration>& rxConfiguration,
+ const OUString& rsCommandName)
+{
+ if (rxConfiguration.is())
+ {
+ try
+ {
+ Sequence<OUString> aCommands { rsCommandName };
+
+ Sequence<Any> aKeyCodes (rxConfiguration->getPreferredKeyEventsForCommandList(aCommands));
+ if (aCommands.getLength() == 1)
+ {
+ awt::KeyEvent aKeyEvent;
+ if (aKeyCodes[0] >>= aKeyEvent)
+ {
+ return AWTKey2VCLKey(aKeyEvent).GetName();
+ }
+ }
+ }
+ catch (css::lang::IllegalArgumentException&)
+ {
+ }
+ }
+ return OUString();
+}
+
+static vcl::KeyCode RetrieveKeyCodeShortcutsFromConfiguration(
+ const Reference<ui::XAcceleratorConfiguration>& rxConfiguration,
+ const OUString& rsCommandName)
+{
+ if (rxConfiguration.is())
+ {
+ try
+ {
+ Sequence<OUString> aCommands { rsCommandName };
+
+ Sequence<Any> aKeyCodes (rxConfiguration->getPreferredKeyEventsForCommandList(aCommands));
+ if (aCommands.getLength() == 1)
+ {
+ awt::KeyEvent aKeyEvent;
+ if (aKeyCodes[0] >>= aKeyEvent)
+ {
+ return AWTKey2VCLKey(aKeyEvent);
+ }
+ }
+ }
+ catch (css::lang::IllegalArgumentException&)
+ {
+ }
+ }
+ return vcl::KeyCode();
+}
+
+static bool ResourceHasKey(const OUString& rsResourceName, const OUString& rsCommandName, const OUString& rsModuleName)
+{
+ Sequence< OUString > aSequence;
+ try
+ {
+ if (!rsModuleName.isEmpty())
+ {
+ Reference<container::XNameAccess> xNameAccess(GetCommandDescription());
+ Reference<container::XNameAccess> xUICommandLabels;
+ if (xNameAccess->getByName(rsModuleName) >>= xUICommandLabels)
+ {
+ xUICommandLabels->getByName(rsResourceName) >>= aSequence;
+ if (comphelper::findValue(aSequence, rsCommandName) != -1)
+ return true;
+ }
+ }
+ }
+ catch (Exception&)
+ {
+ }
+ return false;
+}
+
+Sequence<beans::PropertyValue> GetCommandProperties(const OUString& rsCommandName, const OUString& rsModuleName)
+{
+ Sequence<beans::PropertyValue> aProperties;
+
+ try
+ {
+ if (!rsModuleName.isEmpty())
+ {
+ Reference<container::XNameAccess> xNameAccess(GetCommandDescription());
+ Reference<container::XNameAccess> xUICommandLabels;
+ if ((xNameAccess->getByName(rsModuleName) >>= xUICommandLabels) && xUICommandLabels->hasByName(rsCommandName))
+ xUICommandLabels->getByName(rsCommandName) >>= aProperties;
+ }
+ }
+ catch (Exception&)
+ {
+ }
+
+ return aProperties;
+}
+
+static OUString GetCommandProperty(const OUString& rsProperty, const Sequence<beans::PropertyValue> &rProperties)
+{
+ auto pProp = std::find_if(rProperties.begin(), rProperties.end(),
+ [&rsProperty](const beans::PropertyValue& rProp) { return rProp.Name == rsProperty; });
+ if (pProp != rProperties.end())
+ {
+ OUString sLabel;
+ pProp->Value >>= sLabel;
+ return sLabel;
+ }
+ return OUString();
+}
+
+OUString GetLabelForCommand(const css::uno::Sequence<css::beans::PropertyValue>& rProperties)
+{
+ return GetCommandProperty("Name", rProperties);
+}
+
+OUString GetMenuLabelForCommand(const css::uno::Sequence<css::beans::PropertyValue>& rProperties)
+{
+ // Here we want to use "Label", not "Name". "Name" is a stripped-down version of "Label" without accelerators
+ // and ellipsis. In the menu, we want to have those accelerators and ellipsis.
+ return GetCommandProperty("Label", rProperties);
+}
+
+OUString GetPopupLabelForCommand(const css::uno::Sequence<css::beans::PropertyValue>& rProperties)
+{
+ OUString sPopupLabel(GetCommandProperty("PopupLabel", rProperties));
+ if (!sPopupLabel.isEmpty())
+ return sPopupLabel;
+ return GetCommandProperty("Label", rProperties);
+}
+
+OUString GetTooltipLabelForCommand(const css::uno::Sequence<css::beans::PropertyValue>& rProperties)
+{
+ OUString sLabel(GetCommandProperty("TooltipLabel", rProperties));
+ if (!sLabel.isEmpty())
+ return sLabel;
+ return GetCommandProperty("Label", rProperties);
+}
+
+OUString GetTooltipForCommand(
+ const OUString& rsCommandName,
+ const css::uno::Sequence<css::beans::PropertyValue>& rProperties,
+ const Reference<frame::XFrame>& rxFrame)
+{
+ OUString sLabel(GetCommandProperty("TooltipLabel", rProperties));
+ if (sLabel.isEmpty()) {
+ sLabel = GetPopupLabelForCommand(rProperties);
+ // Remove '...' at the end and mnemonics (we don't want those in tooltips)
+ sLabel = comphelper::string::stripEnd(sLabel, '.');
+ sLabel = MnemonicGenerator::EraseAllMnemonicChars(sLabel);
+ }
+
+ // Command can be just an alias to another command,
+ // so need to get the shortcut of the "real" command.
+ const OUString sRealCommand(GetRealCommandForCommand(rProperties));
+ const OUString sShortCut(GetCommandShortcut(!sRealCommand.isEmpty() ? sRealCommand : rsCommandName, rxFrame));
+ if (!sShortCut.isEmpty())
+ return sLabel + " (" + sShortCut + ")";
+ return sLabel;
+}
+
+OUString GetCommandShortcut (const OUString& rsCommandName,
+ const Reference<frame::XFrame>& rxFrame)
+{
+
+ OUString sShortcut;
+
+ sShortcut = RetrieveShortcutsFromConfiguration(GetDocumentAcceleratorConfiguration(rxFrame), rsCommandName);
+ if (sShortcut.getLength() > 0)
+ return sShortcut;
+
+ sShortcut = RetrieveShortcutsFromConfiguration(GetModuleAcceleratorConfiguration(rxFrame), rsCommandName);
+ if (sShortcut.getLength() > 0)
+ return sShortcut;
+
+ sShortcut = RetrieveShortcutsFromConfiguration(GetGlobalAcceleratorConfiguration(), rsCommandName);
+ if (sShortcut.getLength() > 0)
+ return sShortcut;
+
+ return OUString();
+}
+
+vcl::KeyCode GetCommandKeyCodeShortcut (const OUString& rsCommandName, const Reference<frame::XFrame>& rxFrame)
+{
+ vcl::KeyCode aKeyCodeShortcut;
+
+ aKeyCodeShortcut = RetrieveKeyCodeShortcutsFromConfiguration(GetDocumentAcceleratorConfiguration(rxFrame), rsCommandName);
+ if (aKeyCodeShortcut.GetCode())
+ return aKeyCodeShortcut;
+
+ aKeyCodeShortcut = RetrieveKeyCodeShortcutsFromConfiguration(GetModuleAcceleratorConfiguration(rxFrame), rsCommandName);
+ if (aKeyCodeShortcut.GetCode())
+ return aKeyCodeShortcut;
+
+ aKeyCodeShortcut = RetrieveKeyCodeShortcutsFromConfiguration(GetGlobalAcceleratorConfiguration(), rsCommandName);
+ if (aKeyCodeShortcut.GetCode())
+ return aKeyCodeShortcut;
+
+ return vcl::KeyCode();
+}
+
+OUString GetRealCommandForCommand(const css::uno::Sequence<css::beans::PropertyValue>& rProperties)
+{
+ return GetCommandProperty("TargetURL", rProperties);
+}
+
+Reference<graphic::XGraphic> GetXGraphicForCommand(const OUString& rsCommandName,
+ const Reference<frame::XFrame>& rxFrame,
+ vcl::ImageType eImageType)
+{
+ if (rsCommandName.isEmpty())
+ return nullptr;
+
+ sal_Int16 nImageType(ui::ImageType::COLOR_NORMAL | ui::ImageType::SIZE_DEFAULT);
+
+ if (eImageType == vcl::ImageType::Size26)
+ nImageType |= ui::ImageType::SIZE_LARGE;
+ else if (eImageType == vcl::ImageType::Size32)
+ nImageType |= ui::ImageType::SIZE_32;
+
+ try
+ {
+ Reference<frame::XController> xController(rxFrame->getController(), UNO_SET_THROW);
+ Reference<ui::XUIConfigurationManagerSupplier> xSupplier(xController->getModel(), UNO_QUERY);
+ if (xSupplier.is())
+ {
+ Reference<ui::XUIConfigurationManager> xDocUICfgMgr(xSupplier->getUIConfigurationManager());
+ Reference<ui::XImageManager> xDocImgMgr(xDocUICfgMgr->getImageManager(), UNO_QUERY);
+
+ Sequence< Reference<graphic::XGraphic> > aGraphicSeq;
+ Sequence<OUString> aImageCmdSeq { rsCommandName };
+
+ aGraphicSeq = xDocImgMgr->getImages( nImageType, aImageCmdSeq );
+ Reference<graphic::XGraphic> xGraphic = aGraphicSeq[0];
+ if (xGraphic.is())
+ return xGraphic;
+ }
+ }
+ catch (Exception&)
+ {
+ }
+
+ try {
+ Reference<ui::XModuleUIConfigurationManagerSupplier> xModuleCfgMgrSupplier(GetModuleConfigurationSupplier());
+ Reference<ui::XUIConfigurationManager> xUICfgMgr(xModuleCfgMgrSupplier->getUIConfigurationManager(GetModuleIdentifier(rxFrame)));
+
+ Sequence< Reference<graphic::XGraphic> > aGraphicSeq;
+ Reference<ui::XImageManager> xModuleImageManager(xUICfgMgr->getImageManager(), UNO_QUERY);
+
+ Sequence<OUString> aImageCmdSeq { rsCommandName };
+
+ aGraphicSeq = xModuleImageManager->getImages(nImageType, aImageCmdSeq);
+
+ Reference<graphic::XGraphic> xGraphic(aGraphicSeq[0]);
+
+ return xGraphic;
+ }
+ catch (Exception&)
+ {
+ }
+
+ return nullptr;
+}
+
+Image GetImageForCommand(const OUString& rsCommandName,
+ const Reference<frame::XFrame>& rxFrame,
+ vcl::ImageType eImageType)
+{
+ return Image(GetXGraphicForCommand(rsCommandName, rxFrame, eImageType));
+}
+
+sal_Int32 GetPropertiesForCommand (
+ const OUString& rsCommandName,
+ const OUString& rsModuleName)
+{
+ sal_Int32 nValue = 0;
+ const Sequence<beans::PropertyValue> aProperties (GetCommandProperties(rsCommandName, rsModuleName));
+
+ auto pProp = std::find_if(aProperties.begin(), aProperties.end(),
+ [](const beans::PropertyValue& rProp) { return rProp.Name == "Properties"; });
+ if (pProp != aProperties.end())
+ pProp->Value >>= nValue;
+
+ return nValue;
+}
+
+bool IsRotated(const OUString& rsCommandName, const OUString& rsModuleName)
+{
+ return ResourceHasKey("private:resource/image/commandrotateimagelist", rsCommandName, rsModuleName);
+}
+
+bool IsMirrored(const OUString& rsCommandName, const OUString& rsModuleName)
+{
+ return ResourceHasKey("private:resource/image/commandmirrorimagelist", rsCommandName, rsModuleName);
+}
+
+bool IsExperimental(const OUString& rsCommandName, const OUString& rModuleName)
+{
+ Sequence<beans::PropertyValue> aProperties;
+ try
+ {
+ if( rModuleName.getLength() > 0)
+ {
+ Reference<container::XNameAccess> xNameAccess(GetCommandDescription());
+ Reference<container::XNameAccess> xUICommandLabels;
+ if (xNameAccess->getByName( rModuleName ) >>= xUICommandLabels )
+ xUICommandLabels->getByName(rsCommandName) >>= aProperties;
+
+ auto pProp = std::find_if(aProperties.begin(), aProperties.end(),
+ [](const beans::PropertyValue& rProp) { return rProp.Name == "IsExperimental"; });
+ if (pProp != aProperties.end())
+ {
+ bool bValue;
+ return (pProp->Value >>= bValue) && bValue;
+ }
+ }
+ }
+ catch (Exception&)
+ {
+ }
+ return false;
+}
+
+OUString GetModuleIdentifier(const Reference<frame::XFrame>& rxFrame)
+{
+ static WeakReference<frame::XModuleManager2> xWeakRef;
+ css::uno::Reference<frame::XModuleManager2> xRef(xWeakRef);
+
+ if (!xRef.is())
+ {
+ xRef = frame::ModuleManager::create(comphelper::getProcessComponentContext());
+ xWeakRef = xRef;
+ }
+
+ try
+ {
+ return xRef->identify(rxFrame);
+ }
+ catch (const Exception&)
+ {}
+
+ return OUString();
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/helper/displayconnectiondispatch.cxx b/vcl/source/helper/displayconnectiondispatch.cxx
new file mode 100644
index 000000000..85c4e8cde
--- /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;
+
+ MutexGuard 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*/ )
+{
+ MutexGuard aGuard( m_aMutex );
+
+ m_aHandlers.push_back( handler );
+}
+
+void SAL_CALL DisplayConnectionDispatch::removeEventHandler( const Any& /*window*/, const css::uno::Reference< XEventHandler >& handler )
+{
+ MutexGuard aGuard( m_aMutex );
+
+ m_aHandlers.erase( std::remove(m_aHandlers.begin(), m_aHandlers.end(), handler), m_aHandlers.end() );
+}
+
+void SAL_CALL DisplayConnectionDispatch::addErrorHandler( const css::uno::Reference< XEventHandler >& )
+{
+}
+
+void SAL_CALL DisplayConnectionDispatch::removeErrorHandler( const css::uno::Reference< XEventHandler >& )
+{
+}
+
+Any SAL_CALL DisplayConnectionDispatch::getIdentifier()
+{
+ return Any(m_ConnectionIdentifier);
+}
+
+bool DisplayConnectionDispatch::dispatchEvent( void const * pData, int nBytes )
+{
+ SolarMutexReleaser aRel;
+
+ Sequence< sal_Int8 > aSeq( static_cast<const sal_Int8*>(pData), nBytes );
+ Any aEvent;
+ aEvent <<= aSeq;
+ ::std::vector< css::uno::Reference< XEventHandler > > handlers;
+ {
+ MutexGuard aGuard( m_aMutex );
+ handlers = m_aHandlers;
+ }
+ for (auto const& handle : handlers)
+ if( handle->handleEvent( aEvent ) )
+ return true;
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/helper/driverblocklist.cxx b/vcl/source/helper/driverblocklist.cxx
new file mode 100644
index 000000000..30777bb9b
--- /dev/null
+++ b/vcl/source/helper/driverblocklist.cxx
@@ -0,0 +1,762 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 <sal/log.hxx>
+
+#ifdef _WIN32
+#if !defined WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+#endif
+
+namespace DriverBlocklist
+{
+static OperatingSystem getOperatingSystem(const OString& 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(const OString& 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(const OString& 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;
+ }
+}
+
+OUStringLiteral GetVendorNameFromId(uint32_t id)
+{
+ switch (id)
+ {
+ case 0x8086:
+ return "Intel";
+ case 0x10de:
+ return "Nvidia";
+ case 0x1002:
+ return "AMD";
+ case 0x1414:
+ return "Microsoft";
+ default:
+ return "?";
+ }
+}
+
+Parser::Parser(const OUString& rURL, std::vector<DriverInfo>& rDriverList, VersionType versionType)
+ : meBlockType(BlockType::UNKNOWN)
+ , mrDriverList(rDriverList)
+ , maURL(rURL)
+ , mVersionType(versionType)
+{
+}
+
+bool Parser::parse()
+{
+ try
+ {
+ xmlreader::XmlReader aReader(maURL);
+ handleContent(aReader);
+ }
+ catch (...)
+ {
+ mrDriverList.clear();
+ return false;
+ }
+ return true;
+}
+
+// This allows us to pad driver version 'substrings' with 0s, this
+// effectively allows us to treat the version numbers as 'decimals'. This is
+// a little strange but this method seems to do the right thing for all
+// different vendor's driver strings. i.e. .98 will become 9800, which is
+// larger than .978 which would become 9780.
+static void PadDriverDecimal(char* aString)
+{
+ for (int i = 0; i < 4; i++)
+ {
+ if (!aString[i])
+ {
+ for (int c = i; c < 4; c++)
+ {
+ aString[c] = '0';
+ }
+ break;
+ }
+ }
+ aString[4] = 0;
+}
+
+// All destination string storage needs to have at least 5 bytes available.
+static bool SplitDriverVersion(const char* aSource, char* aAStr, char* aBStr, char* aCStr,
+ char* aDStr, VersionType versionType)
+{
+ // sscanf doesn't do what we want here to we parse this manually.
+ int len = strlen(aSource);
+ char* dest[4] = { aAStr, aBStr, aCStr, aDStr };
+ unsigned destIdx = 0;
+ unsigned destPos = 0;
+
+ for (int i = 0; i < len; i++)
+ {
+ if (destIdx >= SAL_N_ELEMENTS(dest))
+ {
+ // Invalid format found. Ensure we don't access dest beyond bounds.
+ return false;
+ }
+
+ if (aSource[i] == '.')
+ {
+ dest[destIdx++][destPos] = 0;
+ destPos = 0;
+ continue;
+ }
+
+ if (destPos > 3)
+ {
+ // Ignore more than 4 chars. Ensure we never access dest[destIdx]
+ // beyond its bounds.
+ continue;
+ }
+
+ dest[destIdx][destPos++] = aSource[i];
+ }
+
+ // Add last terminator.
+ dest[destIdx][destPos] = 0;
+
+ // Vulkan version numbers have only 3 fields.
+ if (versionType == VersionType::Vulkan && destIdx == SAL_N_ELEMENTS(dest) - 2)
+ dest[++destIdx][0] = '\0';
+ if (destIdx != SAL_N_ELEMENTS(dest) - 1)
+ {
+ return false;
+ }
+ return true;
+}
+
+static bool ParseDriverVersion(const OUString& 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(const OString& 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::WHITELIST)
+ {
+ rDriver.mbWhitelisted = true;
+ }
+ else if (meBlockType == BlockType::BLACKLIST)
+ {
+ rDriver.mbWhitelisted = 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 == "whitelist")
+ {
+ meBlockType = BlockType::WHITELIST;
+ handleList(rReader);
+ }
+ else if (name == "blacklist")
+ {
+ meBlockType = BlockType::BLACKLIST;
+ handleList(rReader);
+ }
+ else if (name == "root")
+ {
+ }
+ else
+ {
+ throw InvalidFileException();
+ }
+ }
+ else if (res == xmlreader::XmlReader::Result::End)
+ {
+ if (name == "whitelist" || name == "blacklist")
+ {
+ meBlockType = BlockType::UNKNOWN;
+ }
+ }
+ else if (res == xmlreader::XmlReader::Result::Done)
+ {
+ break;
+ }
+ }
+}
+
+static OperatingSystem getOperatingSystem()
+{
+#ifdef _WIN32
+ // OS version in 16.16 major/minor form
+ // based on http://msdn.microsoft.com/en-us/library/ms724834(VS.85).aspx
+ switch (DriverBlocklist::GetWindowsVersion())
+ {
+ case 0x00060001:
+ return DRIVER_OS_WINDOWS_7;
+ case 0x00060002:
+ return DRIVER_OS_WINDOWS_8;
+ case 0x00060003:
+ return DRIVER_OS_WINDOWS_8_1;
+ case 0x000A0000: // Major 10 Minor 0
+ return DRIVER_OS_WINDOWS_10;
+ default:
+ return DRIVER_OS_UNKNOWN;
+ }
+#elif defined LINUX
+ return DRIVER_OS_LINUX;
+#else
+ return DRIVER_OS_UNKNOWN;
+#endif
+}
+
+namespace
+{
+struct compareIgnoreAsciiCase
+{
+ explicit compareIgnoreAsciiCase(const OUString& rString)
+ : maString(rString)
+ {
+ }
+
+ bool operator()(const OUString& rCompare) { return maString.equalsIgnoreAsciiCase(rCompare); }
+
+private:
+ OUString maString;
+};
+}
+
+const uint64_t allDriverVersions = ~(uint64_t(0));
+
+DriverInfo::DriverInfo()
+ : meOperatingSystem(DRIVER_OS_UNKNOWN)
+ , maAdapterVendor(GetVendorId(VendorAll))
+ , mbWhitelisted(false)
+ , meComparisonOp(DRIVER_COMPARISON_IGNORED)
+ , mnDriverVersion(0)
+ , mnDriverVersionMax(0)
+{
+}
+
+DriverInfo::DriverInfo(OperatingSystem os, const OUString& vendor, VersionComparisonOp op,
+ uint64_t driverVersion, bool bWhitelisted,
+ const char* suggestedVersion /* = nullptr */)
+ : meOperatingSystem(os)
+ , maAdapterVendor(vendor)
+ , mbWhitelisted(bWhitelisted)
+ , meComparisonOp(op)
+ , mnDriverVersion(driverVersion)
+ , mnDriverVersionMax(0)
+{
+ if (suggestedVersion)
+ maSuggestedVersion = OStringToOUString(OString(suggestedVersion), RTL_TEXTENCODING_UTF8);
+}
+
+bool FindBlocklistedDeviceInList(std::vector<DriverInfo>& aDeviceInfos, VersionType versionType,
+ OUString const& sDriverVersion, OUString const& 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].mbWhitelisted)
+ {
+ SAL_INFO("vcl.driver", "whitelisted driver");
+ return false;
+ }
+
+ match = true;
+ if (!aDeviceInfos[i].maSuggestedVersion.isEmpty())
+ {
+ SAL_WARN("vcl.driver", "use : " << aDeviceInfos[i].maSuggestedVersion);
+ }
+ break;
+ }
+ }
+
+ SAL_INFO("vcl.driver", (match ? "blacklisted" : "not blacklisted") << " in " << blocklistURL);
+ return match;
+}
+
+bool IsDeviceBlocked(const OUString& blocklistURL, VersionType versionType,
+ const OUString& driverVersion, const OUString& vendorId,
+ const OUString& deviceId)
+{
+ std::vector<DriverInfo> driverList;
+ Parser parser(blocklistURL, driverList, versionType);
+ if (!parser.parse())
+ {
+ SAL_WARN("vcl.driver", "error parsing blacklist " << blocklistURL);
+ return false;
+ }
+ return FindBlocklistedDeviceInList(driverList, versionType, driverVersion, vendorId, deviceId,
+ getOperatingSystem(), blocklistURL);
+}
+
+#ifdef _WIN32
+int32_t GetWindowsVersion()
+{
+ static int32_t winVersion = [&]() {
+ // GetVersion(Ex) and VersionHelpers (based on VerifyVersionInfo) API are
+ // subject to manifest-based behavior since Windows 8.1, so give wrong results.
+ // Another approach would be to use NetWkstaGetInfo, but that has some small
+ // reported delays (some milliseconds), and might get slower in domains with
+ // poor network connections.
+ // So go with a solution described at https://msdn.microsoft.com/en-us/library/ms724429
+ HINSTANCE hLibrary = LoadLibraryW(L"kernel32.dll");
+ if (hLibrary != nullptr)
+ {
+ wchar_t szPath[MAX_PATH];
+ DWORD dwCount = GetModuleFileNameW(hLibrary, szPath, SAL_N_ELEMENTS(szPath));
+ FreeLibrary(hLibrary);
+ if (dwCount != 0 && dwCount < SAL_N_ELEMENTS(szPath))
+ {
+ dwCount = GetFileVersionInfoSizeW(szPath, nullptr);
+ if (dwCount != 0)
+ {
+ std::unique_ptr<char[]> ver(new char[dwCount]);
+ if (GetFileVersionInfoW(szPath, 0, dwCount, ver.get()) != FALSE)
+ {
+ void* pBlock = nullptr;
+ UINT dwBlockSz = 0;
+ if (VerQueryValueW(ver.get(), L"\\", &pBlock, &dwBlockSz) != FALSE
+ && dwBlockSz >= sizeof(VS_FIXEDFILEINFO))
+ {
+ VS_FIXEDFILEINFO* vinfo = static_cast<VS_FIXEDFILEINFO*>(pBlock);
+ return int32_t(vinfo->dwProductVersionMS);
+ }
+ }
+ }
+ }
+ }
+ return 0;
+ }();
+
+ return winVersion;
+}
+#endif
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/helper/errcode.cxx b/vcl/source/helper/errcode.cxx
new file mode 100644
index 000000000..d50bf522c
--- /dev/null
+++ b/vcl/source/helper/errcode.cxx
@@ -0,0 +1,147 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <vcl/errcode.hxx>
+
+VCL_DLLPUBLIC std::ostream& operator<<(std::ostream& os, const ErrCode& err)
+{
+ os << err.toHexString() << "(" << (err.IsWarning() ? "Warning" : "Error");
+ if (err.IsDynamic())
+ os << " Dynamic";
+ else
+ {
+ os << " Area:";
+ switch (err.GetArea())
+ {
+ case ErrCodeArea::Io:
+ os << "Io";
+ break;
+ case ErrCodeArea::Sfx:
+ os << "Sfx";
+ break;
+ case ErrCodeArea::Inet:
+ os << "Inet";
+ break;
+ case ErrCodeArea::Vcl:
+ os << "Vcl";
+ break;
+ case ErrCodeArea::Svx:
+ os << "Svx";
+ break;
+ case ErrCodeArea::So:
+ os << "So";
+ break;
+ case ErrCodeArea::Sbx:
+ os << "Sbx";
+ break;
+ case ErrCodeArea::Uui:
+ os << "Uui";
+ break;
+ case ErrCodeArea::Sc:
+ os << "Sc";
+ break;
+ case ErrCodeArea::Sd:
+ os << "Sd";
+ break;
+ case ErrCodeArea::Sw:
+ os << "Sw";
+ break;
+ default:
+ os << "Unknown";
+ }
+ os << " Class:";
+ switch (err.GetClass())
+ {
+ case ErrCodeClass::NONE:
+ os << "NONE";
+ break;
+ case ErrCodeClass::Abort:
+ os << "Abort";
+ break;
+ case ErrCodeClass::General:
+ os << "General";
+ break;
+ case ErrCodeClass::NotExists:
+ os << "NotExists";
+ break;
+ case ErrCodeClass::AlreadyExists:
+ os << "AlreadyExists";
+ break;
+ case ErrCodeClass::Access:
+ os << "Access";
+ break;
+ case ErrCodeClass::Path:
+ os << "Path";
+ break;
+ case ErrCodeClass::Locking:
+ os << "Locking";
+ break;
+ case ErrCodeClass::Parameter:
+ os << "Parameter";
+ break;
+ case ErrCodeClass::Space:
+ os << "Space";
+ break;
+ case ErrCodeClass::NotSupported:
+ os << "NotSupported";
+ break;
+ case ErrCodeClass::Read:
+ os << "Read";
+ break;
+ case ErrCodeClass::Write:
+ os << "Write";
+ break;
+ case ErrCodeClass::Unknown:
+ os << "Unknown";
+ break;
+ case ErrCodeClass::Version:
+ os << "Version";
+ break;
+ case ErrCodeClass::Format:
+ os << "Format";
+ break;
+ case ErrCodeClass::Create:
+ os << "Create";
+ break;
+ case ErrCodeClass::Import:
+ os << "Import";
+ break;
+ case ErrCodeClass::Export:
+ os << "Export";
+ break;
+ case ErrCodeClass::So:
+ os << "So";
+ break;
+ case ErrCodeClass::Sbx:
+ os << "Sbx";
+ break;
+ case ErrCodeClass::Runtime:
+ os << "Runtime";
+ break;
+ case ErrCodeClass::Compiler:
+ os << "Compiler";
+ break;
+ }
+ os << " Code:" << OUString::number(err.GetCode());
+ }
+ os << ")";
+ return os;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/helper/evntpost.cxx b/vcl/source/helper/evntpost.cxx
new file mode 100644
index 000000000..710ac0ce1
--- /dev/null
+++ b/vcl/source/helper/evntpost.cxx
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <tools/debug.hxx>
+#include <vcl/evntpost.hxx>
+#include <vcl/svapp.hxx>
+
+namespace vcl
+{
+
+EventPoster::EventPoster( const Link<LinkParamNone*,void>& rLink )
+ : m_aLink(rLink)
+{
+ m_nId = nullptr;
+}
+
+EventPoster::~EventPoster()
+{
+ DBG_TESTSOLARMUTEX();
+ if ( m_nId )
+ Application::RemoveUserEvent( m_nId );
+}
+
+void EventPoster::Post()
+{
+ DBG_TESTSOLARMUTEX();
+ m_nId = Application::PostUserEvent( ( LINK( this, EventPoster, DoEvent_Impl ) ) );
+}
+
+IMPL_LINK( EventPoster, DoEvent_Impl, void*, /*p*/, void )
+{
+ DBG_TESTSOLARMUTEX();
+ m_nId = nullptr;
+ m_aLink.Call( nullptr );
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/helper/lazydelete.cxx b/vcl/source/helper/lazydelete.cxx
new file mode 100644
index 000000000..6cd1e1b46
--- /dev/null
+++ b/vcl/source/helper/lazydelete.cxx
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <vcl/lazydelete.hxx>
+#include <svdata.hxx>
+#include <sal/log.hxx>
+
+namespace vcl {
+
+DeleteOnDeinitBase::~DeleteOnDeinitBase()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if( !pSVData )
+ return;
+ auto & rList = pSVData->maDeinitDeleteList;
+ rList.erase(std::remove(rList.begin(), rList.end(), this), rList.end());
+}
+
+void DeleteOnDeinitBase::addDeinitContainer( DeleteOnDeinitBase* i_pContainer )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ SAL_WARN_IF( pSVData->mbDeInit, "vcl", "DeleteOnDeinit added after DeiInitVCL !" );
+ if( pSVData->mbDeInit )
+ return;
+
+ pSVData->maDeinitDeleteList.push_back( i_pContainer );
+}
+
+void DeleteOnDeinitBase::ImplDeleteOnDeInit()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ for (auto const& deinitDelete : pSVData->maDeinitDeleteList)
+ {
+ deinitDelete->doCleanup();
+ }
+ pSVData->maDeinitDeleteList.clear();
+}
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/helper/strhelper.cxx b/vcl/source/helper/strhelper.cxx
new file mode 100644
index 000000000..96e10b486
--- /dev/null
+++ b/vcl/source/helper/strhelper.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 <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++;
+ }
+ 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( const OUString& rLine, bool bProtect )
+{
+ sal_Int32 nLen = rLine.getLength();
+ if( ! nLen )
+ return OUString();
+
+ sal_Unicode *pBuffer = static_cast<sal_Unicode*>(alloca( sizeof(sal_Unicode)*(nLen + 1) ));
+ const sal_Unicode *pRun = rLine.getStr();
+ sal_Unicode *pLeap = pBuffer;
+
+ while( *pRun )
+ {
+ if( *pRun && isSpace( *pRun ) )
+ {
+ *pLeap = ' ';
+ pLeap++;
+ pRun++;
+ }
+ while( *pRun && isSpace( *pRun ) )
+ pRun++;
+ while( *pRun && ! isSpace( *pRun ) )
+ {
+ if( *pRun == '\\' )
+ {
+ // escapement
+ pRun++;
+ *pLeap = *pRun;
+ pLeap++;
+ if( *pRun )
+ 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(const OString& rLine)
+{
+ sal_Int32 nLen = rLine.getLength();
+ if (!nLen)
+ return rLine;
+
+ char *pBuffer = static_cast<char*>(alloca( nLen + 1 ));
+ const char *pRun = rLine.getStr();
+ char *pLeap = pBuffer;
+
+ while( *pRun )
+ {
+ if( *pRun && isSpace( *pRun ) )
+ {
+ *pLeap = ' ';
+ pLeap++;
+ pRun++;
+ }
+ while( *pRun && isSpace( *pRun ) )
+ pRun++;
+ while( *pRun && ! isSpace( *pRun ) )
+ {
+ if( *pRun == '\\' )
+ {
+ // escapement
+ pRun++;
+ *pLeap = *pRun;
+ pLeap++;
+ if( *pRun )
+ 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
+ pLeap--;
+ if( *pLeap == ' ' )
+ *pLeap = 0;
+
+ return *pBuffer == ' ' ? pBuffer+1 : pBuffer;
+}
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/helper/svtaccessiblefactory.cxx b/vcl/source/helper/svtaccessiblefactory.cxx
new file mode 100644
index 000000000..5b966d486
--- /dev/null
+++ b/vcl/source/helper/svtaccessiblefactory.cxx
@@ -0,0 +1,273 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <vcl/svtaccessiblefactory.hxx>
+#include <vcl/accessiblefactory.hxx>
+
+#include <tools/svlibrary.h>
+#include <tools/debug.hxx>
+
+#include <osl/module.h>
+#include <osl/diagnose.h>
+#include <rtl/ref.hxx>
+
+namespace vcl
+{
+ using namespace ::com::sun::star::uno;
+ using namespace ::com::sun::star::awt;
+ using namespace ::com::sun::star::accessibility;
+
+ namespace
+ {
+#ifndef DISABLE_DYNLOADING
+ static oslModule s_hAccessibleImplementationModule = nullptr;
+#endif
+#if HAVE_FEATURE_DESKTOP
+ static GetSvtAccessibilityComponentFactory s_pAccessibleFactoryFunc = nullptr;
+#endif
+ static ::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 vcl::IAccessibleBrowseBox*
+ createAccessibleBrowseBox(
+ const css::uno::Reference< css::accessibility::XAccessible >& /*_rxParent*/,
+ vcl::IAccessibleTableProvider& /*_rBrowseBox*/
+ ) const override
+ {
+ return nullptr;
+ }
+
+ virtual table::IAccessibleTableControl*
+ createAccessibleTableControl(
+ const css::uno::Reference< css::accessibility::XAccessible >& /*_rxParent*/,
+ table::IAccessibleTable& /*_rTable*/
+ ) const override
+ {
+ return nullptr;
+ }
+
+ virtual css::uno::Reference< css::accessibility::XAccessible >
+ createAccessibleIconChoiceCtrl(
+ SvtIconChoiceCtrl& /*_rIconCtrl*/,
+ const css::uno::Reference< css::accessibility::XAccessible >& /*_xParent*/
+ ) const override
+ {
+ return nullptr;
+ }
+
+ virtual css::uno::Reference< css::accessibility::XAccessible >
+ createAccessibleTabBar(
+ TabBar& /*_rTabBar*/
+ ) const override
+ {
+ return nullptr;
+ }
+
+ virtual css::uno::Reference< css::accessibility::XAccessibleContext >
+ createAccessibleTextWindowContext(
+ VCLXWindow* /*pVclXWindow*/, TextEngine& /*rEngine*/, TextView& /*rView*/
+ ) const override
+ {
+ return nullptr;
+ }
+
+ virtual css::uno::Reference< css::accessibility::XAccessible >
+ createAccessibleBrowseBoxHeaderBar(
+ const css::uno::Reference< css::accessibility::XAccessible >& /*rxParent*/,
+ vcl::IAccessibleTableProvider& /*_rOwningTable*/,
+ vcl::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*/,
+ vcl::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 HAVE_FEATURE_DESKTOP
+#ifndef DISABLE_DYNLOADING
+ extern "C" { static void thisModule() {} }
+#else
+ extern "C" void* getSvtAccessibilityComponentFactory();
+#endif
+#endif // HAVE_FEATURE_DESKTOP
+
+ void AccessibleFactoryAccess::ensureInitialized()
+ {
+ if ( m_bInitialized )
+ return;
+
+ ::osl::MutexGuard aGuard( ::osl::Mutex::getGlobalMutex() );
+
+#if HAVE_FEATURE_DESKTOP
+ // load the library implementing the factory
+ if (!s_pFactory)
+ {
+#ifndef DISABLE_DYNLOADING
+ const OUString sModuleName( SVLIBRARY( "acc" ));
+ s_hAccessibleImplementationModule = osl_loadModuleRelative( &thisModule, sModuleName.pData, 0 );
+ if ( s_hAccessibleImplementationModule != nullptr )
+ {
+ const OUString sFactoryCreationFunc( "getSvtAccessibilityComponentFactory" );
+ s_pAccessibleFactoryFunc = reinterpret_cast<GetSvtAccessibilityComponentFactory>(
+ osl_getFunctionSymbol( s_hAccessibleImplementationModule, sFactoryCreationFunc.pData ));
+
+ }
+ OSL_ENSURE( s_pAccessibleFactoryFunc, "ac_registerClient: could not load the library, or not retrieve the needed symbol!" );
+#else
+ s_pAccessibleFactoryFunc = getSvtAccessibilityComponentFactory;
+#endif // DISABLE_DYNLOADING
+
+ // get a factory instance
+ if ( s_pAccessibleFactoryFunc )
+ {
+ IAccessibleFactory* pFactory = static_cast< IAccessibleFactory* >( (*s_pAccessibleFactoryFunc)() );
+ if ( pFactory )
+ {
+ s_pFactory = pFactory;
+ pFactory->release();
+ }
+ }
+ }
+#endif // HAVE_FEATURE_DESKTOP
+
+ if (!s_pFactory)
+ // the attempt to load the lib, or to create the factory, failed
+ // -> fall back to a dummy factory
+ s_pFactory = new AccessibleDummyFactory;
+
+ m_bInitialized = true;
+ }
+
+ IAccessibleFactory& AccessibleFactoryAccess::getFactory()
+ {
+ ensureInitialized();
+ DBG_ASSERT( s_pFactory.is(), "AccessibleFactoryAccess::getFactory: at least a dummy factory should have been created!" );
+ return *s_pFactory;
+ }
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/helper/threadex.cxx b/vcl/source/helper/threadex.cxx
new file mode 100644
index 000000000..16249bf44
--- /dev/null
+++ b/vcl/source/helper/threadex.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 <vcl/threadex.hxx>
+#include <vcl/svapp.hxx>
+
+using namespace vcl;
+
+SolarThreadExecutor::SolarThreadExecutor()
+ :m_aStart()
+ ,m_aFinish()
+ ,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();
+ SolarMutexReleaser aReleaser;
+ ImplSVEvent * nEvent = Application::PostUserEvent( LINK( this, SolarThreadExecutor, worker ) );
+ if (m_aStart.wait() == osl::Condition::result_timeout)
+ {
+ m_bTimeout = true;
+ Application::RemoveUserEvent( nEvent );
+ }
+ else
+ {
+ m_aFinish.wait();
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/image/Image.cxx b/vcl/source/image/Image.cxx
new file mode 100644
index 000000000..0cb7c5d44
--- /dev/null
+++ b/vcl/source/image/Image.cxx
@@ -0,0 +1,163 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <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 <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, Size());
+ else
+ ImplInit(aGraphic.GetBitmapEx());
+ }
+}
+
+Image::Image(const OUString & rFileUrl)
+{
+ OUString sImageName;
+ if (rFileUrl.startsWith("private:graphicrepository/", &sImageName))
+ mpImplData = std::make_shared<ImplImage>(sImageName, Size());
+ else
+ {
+ Graphic aGraphic;
+ if (ERRCODE_NONE == GraphicFilter::LoadGraphic(rFileUrl, IMP_PNG, aGraphic))
+ ImplInit(aGraphic.GetBitmapEx());
+ }
+}
+
+Image::Image(StockImage, const OUString & rFileUrl, Size aSpecificSize)
+ : mpImplData(std::make_shared<ImplImage>(rFileUrl, aSpecificSize))
+{
+}
+
+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));
+
+ 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.IsTransparent())
+ {
+ Bitmap aAlphaBmp(aTempBitmapEx.GetAlpha().GetBitmap());
+ aAlphaBmp.Adjust(50);
+ aTempBitmapEx = BitmapEx(aTempBitmapEx.GetBitmap(), AlphaMask(aAlphaBmp));
+ }
+ else
+ {
+ sal_uInt8 cErase = 128;
+ aTempBitmapEx = BitmapEx(aTempBitmapEx.GetBitmap(), AlphaMask(aTempBitmapEx.GetSizePixel(), &cErase));
+ }
+ }
+ aRenderBmp = aTempBitmapEx;
+ }
+
+ pOutDev->DrawBitmapEx(rPos, aOutSize, aRenderBmp);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/image/ImageRepository.cxx b/vcl/source/image/ImageRepository.cxx
new file mode 100644
index 000000000..6bf57e699
--- /dev/null
+++ b/vcl/source/image/ImageRepository.cxx
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <vcl/bitmapex.hxx>
+#include <imagerepository.hxx>
+#include <vcl/ImageTree.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+
+namespace vcl
+{
+ bool ImageRepository::loadImage( const OUString& _rName, BitmapEx& _out_rImage )
+ {
+ OUString sIconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme();
+
+ return ImageTree::get().loadImage( _rName, sIconTheme, _out_rImage, false/*_bSearchLanguageDependent*/ );
+ }
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/image/ImageTree.cxx b/vcl/source/image/ImageTree.cxx
new file mode 100644
index 000000000..83e2962cb
--- /dev/null
+++ b/vcl/source/image/ImageTree.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/.
+ *
+ */
+
+#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);
+}
+
+bool ImageTree::loadImage(OUString const & rName, OUString const & rStyle,
+ BitmapEx & rBitmap, bool bLocalized,
+ sal_Int32 nScalePercentage,
+ const ImageLoadFlags eFlags)
+{
+ return mpImplImageTree->loadImage(rName, rStyle, rBitmap, bLocalized, eFlags, nScalePercentage);
+}
+
+bool ImageTree::loadImage(OUString const & rName, OUString const & rStyle,
+ BitmapEx & rBitmap, bool bLocalized,
+ const ImageLoadFlags eFlags)
+{
+ return loadImage(rName, rStyle, rBitmap, bLocalized, -1, eFlags);
+}
+
+css::uno::Reference<css::container::XNameAccess> const & ImageTree::getNameAccess()
+{
+ return mpImplImageTree->getNameAccess();
+}
+
+void ImageTree::shutdown()
+{
+ mpImplImageTree->shutdown();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/image/ImplImage.cxx b/vcl/source/image/ImplImage.cxx
new file mode 100644
index 000000000..965b0e360
--- /dev/null
+++ b/vcl/source/image/ImplImage.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/log.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/BitmapFilter.hxx>
+#include <vcl/ImageTree.hxx>
+#include <BitmapDisabledImageFilter.hxx>
+#include <comphelper/lok.hxx>
+
+#include <image.h>
+
+ImplImage::ImplImage(const BitmapEx &rBitmapEx)
+ : maBitmapChecksum(0)
+ , maSizePixel(rBitmapEx.GetSizePixel())
+ , maPreferedSizePixel()
+ , maBitmapEx(rBitmapEx)
+{
+}
+
+ImplImage::ImplImage(const OUString &aStockName, Size const & rPreferedSize)
+ : maBitmapChecksum(0)
+ , maSizePixel() // defer size lookup
+ , maPreferedSizePixel(rPreferedSize)
+ , maStockName(aStockName)
+{
+}
+
+bool ImplImage::loadStockAtScale(double fScale, BitmapEx &rBitmapEx)
+{
+ BitmapEx aBitmapEx;
+
+ ImageLoadFlags eScalingFlags = ImageLoadFlags::NONE;
+ sal_Int32 nScalePercentage = -1;
+
+ if (comphelper::LibreOfficeKit::isActive()) // 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, "");
+ 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;
+ }
+ }
+ }
+ if (maPreferedSizePixel != Size())
+ {
+ Size aScaleSize(maPreferedSizePixel.Width() * fScale, maPreferedSizePixel.Height() * fScale);
+ aBitmapEx.Scale(aScaleSize);
+ }
+ rBitmapEx = aBitmapEx;
+ return true;
+}
+
+Size ImplImage::getSizePixel()
+{
+ Size aRet;
+ if (!isSizeEmpty())
+ aRet = maSizePixel;
+ else if (isStock())
+ {
+ if (loadStockAtScale(1.0, maBitmapEx))
+ {
+ assert(!maDisabledBitmapEx);
+ 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)
+{
+ if (isStock())
+ { // check we have the right bitmap cached.
+ // FIXME: DPI scaling should be tied to the outdev really ...
+ double fScale = comphelper::LibreOfficeKit::getDPIScale();
+ Size aTarget(maSizePixel.Width()*fScale,
+ maSizePixel.Height()*fScale);
+ if (maBitmapEx.GetSizePixel() != aTarget)
+ loadStockAtScale(fScale, maBitmapEx);
+ }
+ return getBitmapEx(bDisabled);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/image/ImplImageTree.cxx b/vcl/source/image/ImplImageTree.cxx
new file mode 100644
index 000000000..f08f1973c
--- /dev/null
+++ b/vcl/source/image/ImplImageTree.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 <config_folders.h>
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+
+#include <deque>
+
+#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 <tools/diagnose_ex.h>
+#include <tools/stream.hxx>
+#include <tools/urlobj.hxx>
+#include <implimagetree.hxx>
+
+#include <vcl/bitmapex.hxx>
+#include <vcl/dibtools.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/BitmapTools.hxx>
+#include <IconThemeScanner.hxx>
+#include <vcl/filter/PngImageReader.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/pngwrite.hxx>
+
+#include <BitmapLightenFilter.hxx>
+
+using namespace css;
+
+bool ImageRequestParameters::convertToDarkTheme()
+{
+ static bool bIconsForDarkTheme = !!getenv("VCL_ICONS_FOR_DARK_THEME");
+
+ bool bConvertToDarkTheme = false;
+ if (!(meFlags & ImageLoadFlags::IgnoreDarkTheme))
+ bConvertToDarkTheme = bIconsForDarkTheme;
+
+ return bConvertToDarkTheme;
+}
+
+sal_Int32 ImageRequestParameters::scalePercentage()
+{
+ sal_Int32 aScalePercentage = 100;
+ if (!(meFlags & ImageLoadFlags::IgnoreScalingFactor))
+ aScalePercentage = Application::GetDefaultDevice()->GetDPIScalePercentage();
+ else if (mnScalePercentage > 0)
+ aScalePercentage = mnScalePercentage;
+ return aScalePercentage;
+}
+
+namespace
+{
+
+OUString convertLcTo32Path(OUString const & rPath)
+{
+ OUString aResult;
+ if (rPath.lastIndexOf('/') != -1)
+ {
+ sal_Int32 nCopyFrom = rPath.lastIndexOf('/') + 1;
+ OUString sFile = rPath.copy(nCopyFrom);
+ OUString sDir = rPath.copy(0, rPath.lastIndexOf('/'));
+ if (!sFile.isEmpty() && sFile.startsWith("lc_"))
+ {
+ aResult = sDir + "/32/" + sFile.copy(3);
+ }
+ }
+ return aResult;
+}
+
+OUString createPath(OUString const & name, sal_Int32 pos, OUString const & locale)
+{
+ return name.copy(0, pos + 1) + locale + name.copy(pos);
+}
+
+OUString getIconCacheUrl(OUString const & sVariant, ImageRequestParameters const & rParameters)
+{
+ OUString sUrl = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/cache/"
+ + rParameters.msStyle + "/" + sVariant + "/" + rParameters.msName;
+ rtl::Bootstrap::expandMacros(sUrl);
+ return sUrl;
+}
+
+OUString createIconCacheUrl(OUString const & 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(OUString const & sName)
+{
+ sal_Int32 nDotPosition = sName.lastIndexOf('.');
+ return sName.copy(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();
+}
+
+std::shared_ptr<SvMemoryStream> ImplImageTree::getImageStream(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 wrapStream(aStream);
+ }
+ }
+ }
+ }
+ catch (const uno::Exception &)
+ {
+ TOOLS_INFO_EXCEPTION("vcl", "");
+ }
+
+ aStyle = fallbackStyle(aStyle);
+ }
+ return std::shared_ptr<SvMemoryStream>();
+}
+
+OUString ImplImageTree::fallbackStyle(const OUString& rsStyle)
+{
+ OUString sResult;
+
+ if (rsStyle == "colibre" || rsStyle == "helpimg")
+ sResult = "";
+ else if (rsStyle == "sifr" || rsStyle == "breeze_dark")
+ sResult = "breeze";
+ else if (rsStyle == "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(OUString const & 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(OUString const & sVariant, ImageRequestParameters const & rParameters)
+{
+ OUString sUrl(createIconCacheUrl(sVariant, rParameters));
+ vcl::PNGWriter aWriter(rParameters.mrBitmap);
+ try
+ {
+ SvFileStream aStream(sUrl, StreamMode::WRITE);
+ aWriter.Write(aStream);
+ aStream.Close();
+ }
+ catch (...)
+ {}
+}
+
+} // end anonymous namespace
+
+bool ImplImageTree::doLoadImage(ImageRequestParameters& rParameters)
+{
+ setStyle(rParameters.msStyle);
+
+ if (iconCacheLookup(rParameters))
+ return true;
+
+ OUString aVariant = createVariant(rParameters);
+ if (loadDiskCachedVersion(aVariant, rParameters))
+ return true;
+
+ if (!rParameters.mrBitmap.IsEmpty())
+ rParameters.mrBitmap.SetEmpty();
+
+ LanguageTag aLanguageTag = Application::GetSettings().GetUILanguageTag();
+
+ std::vector<OUString> aPaths = getPaths(rParameters.msName, aLanguageTag);
+
+ bool bFound = false;
+
+ try
+ {
+ bFound = findImage(aPaths, rParameters);
+ }
+ catch (uno::RuntimeException&)
+ {
+ throw;
+ }
+ catch (const uno::Exception&)
+ {
+ TOOLS_INFO_EXCEPTION("vcl", "ImplImageTree::doLoadImage");
+ }
+
+ if (bFound)
+ {
+ if (rParameters.mbWriteImageToCache)
+ {
+ cacheBitmapToDisk(aVariant, rParameters);
+ }
+ getIconCache(rParameters)[rParameters.msName] = std::make_pair(rParameters.mbLocalized, rParameters.mrBitmap);
+ }
+
+ return bFound;
+}
+
+void ImplImageTree::shutdown()
+{
+ maCurrentStyle.clear();
+ maIconSets.clear();
+}
+
+void ImplImageTree::setStyle(OUString const & style)
+{
+ assert(!style.isEmpty());
+ if (style != maCurrentStyle)
+ {
+ maCurrentStyle = style;
+ createStyle();
+ }
+}
+
+/**
+ * The vcldemo app doesn't set up all the config stuff that the main app does, so we need another
+ * way of finding the cursor images.
+ */
+static bool isVclDemo()
+{
+ static const bool bVclDemoOverride = std::getenv("LIBO_VCL_DEMO") != nullptr;
+ return bVclDemoOverride;
+}
+
+void ImplImageTree::createStyle()
+{
+ if (maIconSets.find(maCurrentStyle) != maIconSets.end())
+ return;
+
+ OUString sThemeUrl;
+
+ if (isVclDemo())
+ {
+ if (maCurrentStyle == "default")
+ sThemeUrl = "file://" SRC_ROOT "/icon-themes/colibre-svg";
+ else
+ sThemeUrl = "file://" SRC_ROOT "/icon-themes/" + maCurrentStyle;
+ }
+ else if (maCurrentStyle != "default")
+ {
+ OUString paths = vcl::IconThemeScanner::GetStandardIconThemePath();
+ std::deque<OUString> aPaths;
+ sal_Int32 nIndex = 0;
+ do
+ {
+ aPaths.push_front(paths.getToken(0, ';', nIndex));
+ }
+ while (nIndex >= 0);
+
+ for (const auto& path : aPaths)
+ {
+ INetURLObject aUrl(path);
+ OSL_ASSERT(!aUrl.HasError());
+
+ bool ok = aUrl.Append("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[rParameters.mnScalePercentage] = std::make_unique<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()
+{
+ const OUString aLinkFilename("links.txt");
+
+ if (!checkPathAccess())
+ return;
+
+ const uno::Reference<container::XNameAccess> &rNameAccess = getCurrentIconSet().maNameAccess;
+
+ if (rNameAccess->hasByName(aLinkFilename))
+ {
+ uno::Reference<io::XInputStream> xStream;
+ bool ok = rNameAccess->getByName(aLinkFilename) >>= xStream;
+ assert(ok);
+ (void)ok; // prevent unused warning in release build
+
+ parseLinkFile(wrapStream(xStream));
+ return;
+ }
+}
+
+void ImplImageTree::parseLinkFile(std::shared_ptr<SvStream> const & xStream)
+{
+ OString aLine;
+ OUString aLink, aOriginal;
+ int nLineNo = 0;
+ while (xStream->ReadLine(aLine))
+ {
+ ++nLineNo;
+ if (aLine.isEmpty())
+ continue;
+
+ sal_Int32 nIndex = 0;
+ aLink = OStringToOUString(aLine.getToken(0, ' ', nIndex), RTL_TEXTENCODING_UTF8);
+ aOriginal = OStringToOUString(aLine.getToken(0, ' ', nIndex), RTL_TEXTENCODING_UTF8);
+
+ // skip comments, or incomplete entries
+ if (aLink.isEmpty() || aLink[0] == '#' || aOriginal.isEmpty())
+ {
+ if (aLink.isEmpty() || aOriginal.isEmpty())
+ SAL_WARN("vcl", "ImplImageTree::parseLinkFile: icon links.txt parse error, incomplete link at line " << nLineNo);
+ continue;
+ }
+
+ getCurrentIconSet().maLinkHash[aLink] = aOriginal;
+
+ OUString aOriginal32 = convertLcTo32Path(aOriginal);
+ OUString aLink32 = convertLcTo32Path(aLink);
+
+ if (!aOriginal32.isEmpty() && !aLink32.isEmpty())
+ getCurrentIconSet().maLinkHash[aLink32] = aOriginal32;
+ }
+}
+
+OUString const & ImplImageTree::getRealImageName(OUString const & rIconName)
+{
+ IconLinkHash & rLinkHash = maIconSets[maCurrentStyle].maLinkHash;
+
+ OUString sNameWithNoExtension = getNameNoExtension(rIconName);
+
+ // PNG is priority
+ auto it = rLinkHash.find(sNameWithNoExtension + ".png");
+ if (it != rLinkHash.end())
+ return it->second;
+
+ // also check SVG name
+ it = rLinkHash.find(sNameWithNoExtension + ".svg");
+ if (it != rLinkHash.end())
+ return it->second;
+
+ // neither was found so just return the original name
+ return rIconName;
+}
+
+namespace {
+
+class FolderFileAccess : public ::cppu::WeakImplHelper<css::container::XNameAccess>
+{
+public:
+ uno::Reference< uno::XComponentContext > mxContext;
+ OUString maURL;
+ FolderFileAccess(uno::Reference< uno::XComponentContext > const & context, OUString const & url)
+ : mxContext(context), maURL(url) {}
+ // XElementAccess
+ virtual css::uno::Type SAL_CALL getElementType() override { return cppu::UnoType<io::XInputStream>::get(); }
+ virtual sal_Bool SAL_CALL hasElements() override { return true; }
+ // XNameAccess
+ virtual css::uno::Any SAL_CALL getByName( const OUString& aName ) override
+ {
+ uno::Reference< io::XInputStream > xInputStream = ucb::SimpleFileAccess::create(mxContext)->openFileRead( maURL + "/" + aName );
+ return css::uno::Any(xInputStream);
+ }
+ virtual css::uno::Sequence< OUString > SAL_CALL getElementNames() override
+ {
+ return {};
+ }
+ virtual sal_Bool SAL_CALL hasByName( const OUString& aName ) override
+ {
+ osl::File aBaseFile(maURL + "/" + aName);
+ return osl::File::E_None == aBaseFile.open(osl_File_OpenFlag_Read);
+ }
+};
+
+}
+
+bool ImplImageTree::checkPathAccess()
+{
+ IconSet& rIconSet = getCurrentIconSet();
+ uno::Reference<container::XNameAccess> & rNameAccess = rIconSet.maNameAccess;
+ if (rNameAccess.is())
+ return true;
+
+ try
+ {
+ if (isVclDemo())
+ rNameAccess = new FolderFileAccess(comphelper::getProcessComponentContext(), rIconSet.maURL);
+ else
+ rNameAccess = packages::zip::ZipFileAccess::createWithURL(comphelper::getProcessComponentContext(), rIconSet.maURL);
+ }
+ catch (const uno::RuntimeException &)
+ {
+ throw;
+ }
+ catch (const uno::Exception &)
+ {
+ TOOLS_INFO_EXCEPTION("vcl", "ImplImageTree::zip file location " << rIconSet.maURL);
+ return false;
+ }
+ return rNameAccess.is();
+}
+
+uno::Reference<container::XNameAccess> const & ImplImageTree::getNameAccess()
+{
+ (void)checkPathAccess();
+ return getCurrentIconSet().maNameAccess;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/opengl/GLMHelper.hxx b/vcl/source/opengl/GLMHelper.hxx
new file mode 100644
index 000000000..9f4cd20f9
--- /dev/null
+++ b/vcl/source/opengl/GLMHelper.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_GLM_GLMHELPER_HXX
+#define INCLUDED_VCL_GLM_GLMHELPER_HXX
+
+#include <glm/glm.hpp>
+#include <vcl/dllapi.h>
+
+#include <ostream>
+
+std::ostream& operator<<(std::ostream& rStrm, const glm::mat4& rMatrix);
+std::ostream& operator<<(std::ostream& rStrm, const glm::vec4& rPos);
+std::ostream& operator<<(std::ostream& rStrm, const glm::vec3& rPos);
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/opengl/OpenGLContext.cxx b/vcl/source/opengl/OpenGLContext.cxx
new file mode 100644
index 000000000..c959dac4d
--- /dev/null
+++ b/vcl/source/opengl/OpenGLContext.cxx
@@ -0,0 +1,809 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <chrono>
+
+#include <vcl/opengl/OpenGLContext.hxx>
+#include <vcl/opengl/OpenGLHelper.hxx>
+#include <vcl/opengl/OpenGLWrapper.hxx>
+#include <vcl/syschild.hxx>
+#include <vcl/sysdata.hxx>
+
+#include <osl/thread.hxx>
+#include <sal/log.hxx>
+
+#include <svdata.hxx>
+#include <salgdi.hxx>
+#include <salinst.hxx>
+
+#include <opengl/framebuffer.hxx>
+#include <opengl/program.hxx>
+#include <opengl/texture.hxx>
+#include <opengl/zone.hxx>
+
+#include <opengl/RenderState.hxx>
+
+#include <config_features.h>
+
+using namespace com::sun::star;
+
+#define MAX_FRAMEBUFFER_COUNT 30
+
+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),
+ mbVCLOnly(false),
+ mnFramebufferCount(0),
+ mpCurrentFramebuffer(nullptr),
+ mpFirstFramebuffer(nullptr),
+ mpLastFramebuffer(nullptr),
+ mpCurrentProgram(nullptr),
+ mpRenderState(new RenderState),
+ 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;
+
+ // reset the clip region
+ maClipRegion.SetEmpty();
+ mpRenderState.reset(new RenderState);
+
+ // destroy all framebuffers
+ if( mpLastFramebuffer )
+ {
+ OpenGLFramebuffer* pFramebuffer = mpLastFramebuffer;
+
+ makeCurrent();
+ while( pFramebuffer )
+ {
+ OpenGLFramebuffer* pPrevFramebuffer = pFramebuffer->mpPrevFramebuffer;
+ delete pFramebuffer;
+ pFramebuffer = pPrevFramebuffer;
+ }
+ mnFramebufferCount = 0;
+ mpFirstFramebuffer = nullptr;
+ mpLastFramebuffer = nullptr;
+ }
+
+ // destroy all programs
+ if( !maPrograms.empty() )
+ {
+ makeCurrent();
+ maPrograms.clear();
+ }
+
+ 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()
+{
+ 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() && pCurrentCtx->isCurrent() )
+ pCurrentCtx->ReleaseFramebuffers();
+}
+
+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());
+}
+
+rtl::Reference<OpenGLContext> OpenGLContext::getVCLContext(bool bMakeIfNecessary)
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ OpenGLContext *pContext = pSVData->maGDIData.mpLastContext;
+ while( pContext )
+ {
+ // check if this context is usable
+ if( pContext->isInitialized() && pContext->isVCLOnly() )
+ break;
+ pContext = pContext->mpPrevContext;
+ }
+ rtl::Reference<OpenGLContext> xContext;
+ vcl::Window* pDefWindow = !pContext && bMakeIfNecessary ? ImplGetDefaultWindow() : nullptr;
+ if (pDefWindow)
+ {
+ // create our magic fallback window context.
+#if HAVE_FEATURE_OPENGL
+ xContext = pDefWindow->GetGraphics()->GetOpenGLContext();
+ assert(xContext.is());
+#endif
+ }
+ else
+ xContext = pContext;
+
+ if( xContext.is() )
+ xContext->makeCurrent();
+
+ return xContext;
+}
+
+/*
+ * We don't care what context we have, but we want one that is live,
+ * ie. not reset underneath us, and is setup for VCL usage - ideally
+ * not swapping context at all.
+ */
+void OpenGLContext::makeVCLCurrent()
+{
+ getVCLContext();
+}
+
+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;
+ }
+
+ // sync the render state with the current context
+ mpRenderState->sync();
+}
+
+void OpenGLContext::resetCurrent()
+{
+ clearCurrent();
+ // by default nothing else to do
+}
+
+void OpenGLContext::swapBuffers()
+{
+ // by default nothing else to do
+ BuffersSwapped();
+}
+
+void OpenGLContext::BuffersSwapped()
+{
+ nBufferSwapCounter++;
+
+ static bool bSleep = getenv("SAL_GL_SLEEP_ON_SWAP");
+ if (bSleep)
+ {
+ // half a second.
+ osl::Thread::wait( std::chrono::milliseconds(500) );
+ }
+}
+
+
+sal_Int64 OpenGLWrapper::getBufferSwapCounter()
+{
+ return nBufferSwapCounter;
+}
+
+void OpenGLContext::sync()
+{
+ // default is nothing
+ (void) this; // loplugin:staticmethods
+}
+
+void OpenGLContext::show()
+{
+ if (m_pChildWindow)
+ m_pChildWindow->Show();
+ else if (m_xWindow)
+ m_xWindow->Show();
+}
+
+SystemChildWindow* OpenGLContext::getChildWindow()
+{
+ return m_pChildWindow;
+}
+
+const SystemChildWindow* OpenGLContext::getChildWindow() const
+{
+ return m_pChildWindow;
+}
+
+void OpenGLContext::BindFramebuffer( OpenGLFramebuffer* pFramebuffer )
+{
+ OpenGLZone aZone;
+
+ if( pFramebuffer != mpCurrentFramebuffer )
+ {
+ if( pFramebuffer )
+ pFramebuffer->Bind();
+ else
+ OpenGLFramebuffer::Unbind();
+ mpCurrentFramebuffer = pFramebuffer;
+ }
+}
+
+void OpenGLContext::AcquireDefaultFramebuffer()
+{
+ BindFramebuffer( nullptr );
+}
+
+OpenGLFramebuffer* OpenGLContext::AcquireFramebuffer( const OpenGLTexture& rTexture )
+{
+ OpenGLZone aZone;
+
+ OpenGLFramebuffer* pFramebuffer = nullptr;
+ OpenGLFramebuffer* pFreeFbo = nullptr;
+ OpenGLFramebuffer* pSameSizeFbo = nullptr;
+
+ // check if there is already a framebuffer attached to that texture
+ pFramebuffer = mpLastFramebuffer;
+ while( pFramebuffer )
+ {
+ if( pFramebuffer->IsAttached( rTexture ) )
+ break;
+ if( !pFreeFbo && pFramebuffer->IsFree() )
+ pFreeFbo = pFramebuffer;
+ if( !pSameSizeFbo &&
+ pFramebuffer->GetWidth() == rTexture.GetWidth() &&
+ pFramebuffer->GetHeight() == rTexture.GetHeight() )
+ pSameSizeFbo = pFramebuffer;
+ pFramebuffer = pFramebuffer->mpPrevFramebuffer;
+ }
+
+ // else use any framebuffer having the same size
+ if( !pFramebuffer && pSameSizeFbo )
+ pFramebuffer = pSameSizeFbo;
+
+ // else use the first free framebuffer
+ if( !pFramebuffer && pFreeFbo )
+ pFramebuffer = pFreeFbo;
+
+ // if there isn't any free one, create a new one if the limit isn't reached
+ if( !pFramebuffer && mnFramebufferCount < MAX_FRAMEBUFFER_COUNT )
+ {
+ mnFramebufferCount++;
+ pFramebuffer = new OpenGLFramebuffer();
+ if( mpLastFramebuffer )
+ {
+ pFramebuffer->mpPrevFramebuffer = mpLastFramebuffer;
+ mpLastFramebuffer = pFramebuffer;
+ }
+ else
+ {
+ mpFirstFramebuffer = pFramebuffer;
+ mpLastFramebuffer = pFramebuffer;
+ }
+ }
+
+ // last try, use any framebuffer
+ // TODO order the list of framebuffers as a LRU
+ if( !pFramebuffer )
+ pFramebuffer = mpFirstFramebuffer;
+
+ assert( pFramebuffer );
+ BindFramebuffer( pFramebuffer );
+ pFramebuffer->AttachTexture( rTexture );
+
+ state().viewport(tools::Rectangle(Point(), Size(rTexture.GetWidth(), rTexture.GetHeight())));
+
+ return pFramebuffer;
+}
+
+// FIXME: this method is rather grim from a perf. perspective.
+// We should instead (eventually) use pointers to associate the
+// framebuffer and texture cleanly.
+void OpenGLContext::UnbindTextureFromFramebuffers( GLuint nTexture )
+{
+ OpenGLFramebuffer* pFramebuffer;
+
+ // see if there is a framebuffer attached to that texture
+ pFramebuffer = mpLastFramebuffer;
+ while( pFramebuffer )
+ {
+ if (pFramebuffer->IsAttached(nTexture))
+ {
+ BindFramebuffer(pFramebuffer);
+ pFramebuffer->DetachTexture();
+ }
+ pFramebuffer = pFramebuffer->mpPrevFramebuffer;
+ }
+
+ // Lets just check that no other context has a framebuffer
+ // with this texture - that would be bad ...
+ assert( !IsTextureAttachedAnywhere( nTexture ) );
+}
+
+/// Method for debugging; check texture is not already attached.
+bool OpenGLContext::IsTextureAttachedAnywhere( GLuint nTexture )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ for( auto *pCheck = pSVData->maGDIData.mpLastContext; pCheck;
+ pCheck = pCheck->mpPrevContext )
+ {
+ for( auto pBuffer = pCheck->mpLastFramebuffer; pBuffer;
+ pBuffer = pBuffer->mpPrevFramebuffer )
+ {
+ if( pBuffer->IsAttached( nTexture ) )
+ return true;
+ }
+ }
+ return false;
+}
+
+void OpenGLContext::ReleaseFramebuffer( OpenGLFramebuffer* pFramebuffer )
+{
+ if( pFramebuffer )
+ pFramebuffer->DetachTexture();
+}
+
+void OpenGLContext::ReleaseFramebuffer( const OpenGLTexture& rTexture )
+{
+ OpenGLZone aZone;
+
+ if (!rTexture) // no texture to release.
+ return;
+
+ OpenGLFramebuffer* pFramebuffer = mpLastFramebuffer;
+
+ while( pFramebuffer )
+ {
+ if( pFramebuffer->IsAttached( rTexture ) )
+ {
+ BindFramebuffer( pFramebuffer );
+ pFramebuffer->DetachTexture();
+ if (mpCurrentFramebuffer == pFramebuffer)
+ BindFramebuffer( nullptr );
+ }
+ pFramebuffer = pFramebuffer->mpPrevFramebuffer;
+ }
+}
+
+void OpenGLContext::ReleaseFramebuffers()
+{
+ OpenGLZone aZone;
+
+ OpenGLFramebuffer* pFramebuffer = mpLastFramebuffer;
+ while( pFramebuffer )
+ {
+ if (!pFramebuffer->IsFree())
+ {
+ BindFramebuffer( pFramebuffer );
+ pFramebuffer->DetachTexture();
+ }
+ pFramebuffer = pFramebuffer->mpPrevFramebuffer;
+ }
+ BindFramebuffer( nullptr );
+}
+
+OpenGLProgram* OpenGLContext::GetProgram( const OUString& rVertexShader, const OUString& rFragmentShader, const OString& preamble )
+{
+ OpenGLZone aZone;
+
+ // We cache the shader programs in a per-process run-time cache
+ // based on only the names and the preamble. We don't expect
+ // shader source files to change during the lifetime of a
+ // LibreOffice process.
+ OString aNameBasedKey = OUStringToOString(rVertexShader + "+" + rFragmentShader, RTL_TEXTENCODING_UTF8) + "+" + preamble;
+ if( !aNameBasedKey.isEmpty() )
+ {
+ ProgramCollection::iterator it = maPrograms.find( aNameBasedKey );
+ if( it != maPrograms.end() )
+ return it->second.get();
+ }
+
+ // Binary shader programs are cached persistently (between
+ // LibreOffice process instances) based on a hash of their source
+ // code, as the source code can and will change between
+ // LibreOffice versions even if the shader names don't change.
+ OString aPersistentKey = OpenGLHelper::GetDigest( rVertexShader, rFragmentShader, preamble );
+ std::shared_ptr<OpenGLProgram> pProgram = std::make_shared<OpenGLProgram>();
+ if( !pProgram->Load( rVertexShader, rFragmentShader, preamble, aPersistentKey ) )
+ return nullptr;
+
+ maPrograms.insert(std::make_pair(aNameBasedKey, pProgram));
+ return pProgram.get();
+}
+
+OpenGLProgram* OpenGLContext::UseProgram( const OUString& rVertexShader, const OUString& rFragmentShader, const OString& preamble )
+{
+ OpenGLZone aZone;
+
+ OpenGLProgram* pProgram = GetProgram( rVertexShader, rFragmentShader, preamble );
+
+ if (pProgram && pProgram == mpCurrentProgram)
+ {
+ VCL_GL_INFO("Context::UseProgram: Reusing existing program " << pProgram->Id());
+ pProgram->Reuse();
+ return pProgram;
+ }
+
+ mpCurrentProgram = pProgram;
+
+ if (!mpCurrentProgram)
+ {
+ SAL_WARN("vcl.opengl", "OpenGLContext::UseProgram: mpCurrentProgram is 0");
+ return nullptr;
+ }
+
+ mpCurrentProgram->Use();
+
+ return mpCurrentProgram;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/opengl/OpenGLHelper.cxx b/vcl/source/opengl/OpenGLHelper.cxx
new file mode 100644
index 000000000..1ea130942
--- /dev/null
+++ b/vcl/source/opengl/OpenGLHelper.cxx
@@ -0,0 +1,1055 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 <osl/file.hxx>
+#include <rtl/bootstrap.hxx>
+#include <rtl/digest.h>
+#include <rtl/strbuf.hxx>
+#include <rtl/ustring.hxx>
+#include <sal/log.hxx>
+#include <tools/stream.hxx>
+#include <config_folders.h>
+#include <memory>
+#include <vcl/pngwrite.hxx>
+#include <vcl/svapp.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <com/sun/star/util/XFlushable.hpp>
+#include <com/sun/star/configuration/theDefaultProvider.hpp>
+
+#include <stdarg.h>
+#include <vector>
+#include <unordered_map>
+
+#include <opengl/zone.hxx>
+#include <vcl/opengl/OpenGLWrapper.hxx>
+#include <vcl/opengl/OpenGLContext.hxx>
+#include <desktop/crashreport.hxx>
+#include <bitmapwriteaccess.hxx>
+#include <watchdog.hxx>
+#include <vcl/skia/SkiaHelper.hxx>
+#include <vcl/glxtestprocess.hxx>
+
+#if defined UNX && !defined MACOSX && !defined IOS && !defined ANDROID && !defined HAIKU
+#include <opengl/x11/X11DeviceInfo.hxx>
+#elif defined (_WIN32)
+#include <opengl/win/WinDeviceInfo.hxx>
+#endif
+
+#include "GLMHelper.hxx"
+
+static bool volatile gbInShaderCompile = false;
+
+namespace {
+
+using namespace rtl;
+
+OUString getShaderFolder()
+{
+ OUString aUrl("$BRAND_BASE_DIR/" LIBO_ETC_FOLDER);
+ rtl::Bootstrap::expandMacros(aUrl);
+
+ return aUrl + "/opengl/";
+}
+
+OString loadShader(const OUString& 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, const OString& rPreamble)
+{
+ if (rPreamble.isEmpty())
+ return;
+
+ int nVersionStrStartPos = rShaderSource.indexOf("#version");
+
+ if (nVersionStrStartPos == -1)
+ {
+ rShaderSource = 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
+{
+ static const sal_uInt32 GLenumSize = sizeof(GLenum);
+
+ OString getHexString(const sal_uInt8* pData, sal_uInt32 nLength)
+ {
+ static const char* const pHexData = "0123456789ABCDEF";
+
+ bool bIsZero = true;
+ OStringBuffer aHexStr;
+ for(size_t i = 0; i < nLength; ++i)
+ {
+ sal_uInt8 val = pData[i];
+ if( val != 0 )
+ bIsZero = false;
+ aHexStr.append( pHexData[ val & 0xf ] );
+ aHexStr.append( pHexData[ val >> 4 ] );
+ }
+ if( bIsZero )
+ return OString();
+ else
+ return aHexStr.makeStringAndClear();
+ }
+
+ OString generateMD5(const void* pData, size_t length)
+ {
+ sal_uInt8 pBuffer[RTL_DIGEST_LENGTH_MD5];
+ rtlDigestError aError = rtl_digest_MD5(pData, length,
+ pBuffer, RTL_DIGEST_LENGTH_MD5);
+ SAL_WARN_IF(aError != rtl_Digest_E_None, "vcl.opengl", "md5 generation failed");
+
+ return getHexString(pBuffer, RTL_DIGEST_LENGTH_MD5);
+ }
+
+ OString getDeviceInfoString()
+ {
+#if defined( SAL_UNX ) && !defined( MACOSX ) && !defined( IOS )&& !defined( ANDROID ) && !defined( HAIKU )
+ const X11OpenGLDeviceInfo aInfo;
+ return aInfo.GetOS() +
+ aInfo.GetOSRelease() +
+ aInfo.GetRenderer() +
+ aInfo.GetVendor() +
+ aInfo.GetVersion();
+#elif defined( _WIN32 )
+ 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());
+#else
+ return rtl::OStringView(reinterpret_cast<const char*>(glGetString(GL_VENDOR))) +
+ reinterpret_cast<const char*>(glGetString(GL_RENDERER)) +
+ reinterpret_cast<const char*>(glGetString(GL_VERSION));
+#endif
+ }
+
+ OString getStringDigest( const OUString& rVertexShaderName,
+ const OUString& rFragmentShaderName,
+ const OString& 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());
+ }
+
+ 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( const OUString& rVertexShaderName,
+ const OUString& rFragmentShaderName,
+ const OUString& rGeometryShaderName,
+ const OString& rDigest )
+ {
+ OString aFileName = getCacheFolder() +
+ OUStringToOString( rVertexShaderName, RTL_TEXTENCODING_UTF8 ) + "-" +
+ OUStringToOString( rFragmentShaderName, RTL_TEXTENCODING_UTF8 ) + "-";
+ if (!rGeometryShaderName.isEmpty())
+ aFileName += OUStringToOString( rGeometryShaderName, RTL_TEXTENCODING_UTF8 ) + "-";
+ aFileName += 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");
+ }
+}
+
+OString OpenGLHelper::GetDigest( const OUString& rVertexShaderName,
+ const OUString& rFragmentShaderName,
+ const OString& rPreamble )
+{
+ return getStringDigest(rVertexShaderName, rFragmentShaderName, rPreamble);
+}
+
+GLint OpenGLHelper::LoadShaders(const OUString& rVertexShaderName,
+ const OUString& rFragmentShaderName,
+ const OUString& rGeometryShaderName,
+ const OString& preamble,
+ const OString& 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.isEmpty())
+ {
+ 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.isEmpty())
+ 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.isEmpty())
+ 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.isEmpty())
+ 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.isEmpty())
+ {
+ 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,
+ const OString& preamble,
+ const OString& 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, OString(), OString());
+}
+
+GLint OpenGLHelper::LoadShaders(const OUString& rVertexShaderName,
+ const OUString& rFragmentShaderName)
+{
+ return LoadShaders(rVertexShaderName, rFragmentShaderName, OUString(), "", "");
+}
+
+void OpenGLHelper::renderToFile(long nWidth, long nHeight, const OUString& rFileName)
+{
+ OpenGLZone aZone;
+
+ std::unique_ptr<sal_uInt8[]> pBuffer(new sal_uInt8[nWidth*nHeight*4]);
+ glReadPixels(0, 0, nWidth, nHeight, OptimalBufferFormat(), GL_UNSIGNED_BYTE, pBuffer.get());
+ BitmapEx aBitmap = ConvertBufferToBitmapEx(pBuffer.get(), nWidth, nHeight);
+ try {
+ vcl::PNGWriter aWriter( aBitmap );
+ SvFileStream sOutput( rFileName, StreamMode::WRITE );
+ aWriter.Write( sOutput );
+ sOutput.Close();
+ } catch (...) {
+ SAL_WARN("vcl.opengl", "Error writing png to " << rFileName);
+ }
+
+ CHECK_GL_ERROR();
+}
+
+GLenum OpenGLHelper::OptimalBufferFormat()
+{
+#ifdef _WIN32
+ return GL_BGRA; // OpenGLSalBitmap is internally ScanlineFormat::N24BitTcBgr
+#else
+ return GL_RGBA; // OpenGLSalBitmap is internally ScanlineFormat::N24BitTcRgb
+#endif
+}
+
+BitmapEx OpenGLHelper::ConvertBufferToBitmapEx(const sal_uInt8* const pBuffer, long nWidth, long nHeight)
+{
+ assert(pBuffer);
+ Bitmap aBitmap( Size(nWidth, nHeight), 24 );
+ AlphaMask aAlpha( Size(nWidth, nHeight) );
+
+ {
+ BitmapScopedWriteAccess pWriteAccess( aBitmap );
+ AlphaScopedWriteAccess pAlphaWriteAccess( aAlpha );
+#ifdef _WIN32
+ assert(pWriteAccess->GetScanlineFormat() == ScanlineFormat::N24BitTcBgr);
+ assert(pWriteAccess->IsTopDown());
+ assert(pAlphaWriteAccess->IsTopDown());
+#else
+ assert(pWriteAccess->GetScanlineFormat() == ScanlineFormat::N24BitTcRgb);
+ assert(!pWriteAccess->IsTopDown());
+ assert(!pAlphaWriteAccess->IsTopDown());
+#endif
+ assert(pAlphaWriteAccess->GetScanlineFormat() == ScanlineFormat::N8BitPal);
+
+ size_t nCurPos = 0;
+ for( 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( long x = 0; x < nWidth; ++x )
+ {
+ *pScan++ = pBuffer[nCurPos];
+ *pScan++ = pBuffer[nCurPos+1];
+ *pScan++ = pBuffer[nCurPos+2];
+
+ nCurPos += 3;
+ *pAlphaScan++ = static_cast<sal_uInt8>( 255 - pBuffer[nCurPos++] );
+ }
+ }
+ }
+ return BitmapEx(aBitmap, aAlpha);
+}
+
+const char* OpenGLHelper::GLErrorString(GLenum errorCode)
+{
+ static const struct {
+ GLenum code;
+ const char *string;
+ } errors[]=
+ {
+ /* GL */
+ {GL_NO_ERROR, "no error"},
+ {GL_INVALID_ENUM, "invalid enumerant"},
+ {GL_INVALID_VALUE, "invalid value"},
+ {GL_INVALID_OPERATION, "invalid operation"},
+ {GL_STACK_OVERFLOW, "stack overflow"},
+ {GL_STACK_UNDERFLOW, "stack underflow"},
+ {GL_OUT_OF_MEMORY, "out of memory"},
+ {GL_INVALID_FRAMEBUFFER_OPERATION, "invalid framebuffer operation"},
+
+ {0, nullptr }
+ };
+
+ int i;
+
+ for (i=0; errors[i].string; i++)
+ {
+ if (errors[i].code == errorCode)
+ {
+ return errors[i].string;
+ }
+ }
+
+ return nullptr;
+}
+
+std::ostream& operator<<(std::ostream& rStrm, const glm::vec4& rPos)
+{
+ rStrm << "( " << rPos[0] << ", " << rPos[1] << ", " << rPos[2] << ", " << rPos[3] << ")";
+ return rStrm;
+}
+
+std::ostream& operator<<(std::ostream& rStrm, const glm::vec3& rPos)
+{
+ rStrm << "( " << rPos[0] << ", " << rPos[1] << ", " << rPos[2] << ")";
+ return rStrm;
+}
+
+std::ostream& operator<<(std::ostream& rStrm, const glm::mat4& rMatrix)
+{
+ for(int i = 0; i < 4; ++i)
+ {
+ rStrm << "\n( ";
+ for(int j = 0; j < 4; ++j)
+ {
+ rStrm << rMatrix[j][i];
+ rStrm << " ";
+ }
+ rStrm << ")\n";
+ }
+ return rStrm;
+}
+
+void OpenGLHelper::createFramebuffer(long nWidth, 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::isDeviceBlacklisted()
+{
+ static bool bSet = false;
+ static bool bBlacklisted = true; // assume the worst
+ if (!bSet)
+ {
+ OpenGLZone aZone;
+
+#if defined UNX && !defined MACOSX && !defined IOS && !defined ANDROID && !defined HAIKU
+ X11OpenGLDeviceInfo aInfo;
+ bBlacklisted = aInfo.isDeviceBlocked();
+ SAL_INFO("vcl.opengl", "blacklisted: " << bBlacklisted);
+#elif defined( _WIN32 )
+ WinOpenGLDeviceInfo aInfo;
+ bBlacklisted = 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
+ bBlacklisted = false;
+#endif
+ bSet = true;
+ }
+
+ return bBlacklisted;
+}
+
+bool OpenGLHelper::supportsVCLOpenGL()
+{
+ static bool bDisableGL = !!getenv("SAL_DISABLEGL");
+ bool bBlacklisted = isDeviceBlacklisted();
+
+ return !bDisableGL && !bBlacklisted;
+}
+
+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];
+ }
+};
+
+static 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)
+ {
+ bDisabled = true;
+
+ // Disable the OpenGL support
+ std::shared_ptr<comphelper::ConfigurationChanges> xChanges(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Office::Common::VCL::UseOpenGL::set(false, 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);
+}
+
+OpenGLVCLContextZone::OpenGLVCLContextZone()
+{
+ OpenGLContext::makeVCLCurrent();
+}
+
+namespace
+{
+ bool bTempOpenGLDisabled = false;
+}
+
+PreDefaultWinNoOpenGLZone::PreDefaultWinNoOpenGLZone()
+{
+ bTempOpenGLDisabled = true;
+}
+
+PreDefaultWinNoOpenGLZone::~PreDefaultWinNoOpenGLZone()
+{
+ bTempOpenGLDisabled = false;
+}
+
+static void reapGlxTest()
+{
+ // Reap the glxtest child, or it'll stay around as a zombie,
+ // as X11OpenGLDeviceInfo::GetData() will not get called.
+ static bool bTestReaped = false;
+ if(!bTestReaped)
+ {
+ reap_glxtest_process();
+ bTestReaped = true;
+ }
+}
+
+bool OpenGLHelper::isVCLOpenGLEnabled()
+{
+ // Skia always takes precedence if enabled
+ if( SkiaHelper::isVCLSkiaEnabled())
+ {
+ reapGlxTest();
+ return false;
+ }
+
+ /**
+ * The !bSet part should only be called once! Changing the results in the same
+ * run will mix OpenGL and normal rendering.
+ */
+
+ static bool bSet = false;
+ static bool bEnable = false;
+ static bool bForceOpenGL = false;
+
+ // No hardware rendering, so no OpenGL
+ if (Application::IsBitmapRendering())
+ return false;
+
+ //tdf#106155, disable GL while loading certain bitmaps needed for the initial toplevel windows
+ //under raw X (kde) vclplug
+ if (bTempOpenGLDisabled)
+ return false;
+
+ if (bSet)
+ {
+ return bForceOpenGL || bEnable;
+ }
+ /*
+ * There are a number of cases that these environment variables cover:
+ * * SAL_FORCEGL forces OpenGL independent of any other option
+ * * SAL_DISABLEGL or a blacklisted driver avoid the use of OpenGL if SAL_FORCEGL is not set
+ */
+
+ bSet = true;
+ bForceOpenGL = !!getenv("SAL_FORCEGL") || officecfg::Office::Common::VCL::ForceOpenGL::get();
+
+ bool bRet = false;
+ bool bSupportsVCLOpenGL = supportsVCLOpenGL();
+ // always call supportsVCLOpenGL to de-zombie the glxtest child process on X11
+ if (bForceOpenGL)
+ {
+ bRet = true;
+ }
+ else if (bSupportsVCLOpenGL)
+ {
+ static bool bEnableGLEnv = !!getenv("SAL_ENABLEGL");
+
+ bEnable = bEnableGLEnv;
+
+ if (officecfg::Office::Common::VCL::UseOpenGL::get())
+ bEnable = true;
+
+ // Force disable in safe mode
+ if (Application::IsSafeModeEnabled())
+ bEnable = false;
+
+ bRet = bEnable;
+ }
+
+ if (bRet)
+ WatchdogThread::start();
+ else
+ reapGlxTest();
+
+ CrashReporter::addKeyValue("UseOpenGL", OUString::boolean(bRet), CrashReporter::Write);
+
+ return bRet;
+}
+
+bool OpenGLWrapper::isVCLOpenGLEnabled()
+{
+ return OpenGLHelper::isVCLOpenGLEnabled();
+}
+
+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/outdev/bitmap.cxx b/vcl/source/outdev/bitmap.cxx
new file mode 100644
index 000000000..dcb9a6b1d
--- /dev/null
+++ b/vcl/source/outdev/bitmap.cxx
@@ -0,0 +1,1788 @@
+/* -*- 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 <cassert>
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapFilterStackBlur.hxx>
+#include <vcl/bitmapaccess.hxx>
+#include <vcl/canvastools.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <config_features.h>
+#if HAVE_FEATURE_OPENGL
+#include <vcl/opengl/OpenGLHelper.hxx>
+#endif
+#include <vcl/skia/SkiaHelper.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/image.hxx>
+#include <vcl/BitmapMonochromeFilter.hxx>
+
+#include <bmpfast.hxx>
+#include <salgdi.hxx>
+#include <salbmp.hxx>
+
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <memory>
+#include <comphelper/lok.hxx>
+#include <bitmapwriteaccess.hxx>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+#include <tools/helpers.hxx>
+#include <tools/debug.hxx>
+#include <rtl/math.hxx>
+
+#include <vcl/dibtools.hxx>
+#include <tools/stream.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, 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( PushFlags::LINECOLOR | PushFlags::FILLCOLOR );
+ SetLineColor( aCol );
+ SetFillColor( aCol );
+ DrawRect( tools::Rectangle( rDestPt, rDestSize ) );
+ Pop();
+ return;
+ }
+ else if( !!aBmp )
+ {
+ 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;
+
+ 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 )
+ ScaleBitmap (aBmp, aPosAry);
+
+ mpGraphics->DrawBitmap( aPosAry, *aBmp.ImplGetSalBitmap(), this );
+ }
+ }
+ }
+
+ if( mpAlphaVDev )
+ {
+ // #i32109#: Make bitmap area opaque
+ mpAlphaVDev->ImplFillOpaqueRectangle( tools::Rectangle(rDestPt, rDestSize) );
+ }
+}
+
+Bitmap OutputDevice::GetDownsampledBitmap( const Size& rDstSz,
+ const Point& rSrcPt, const Size& rSrcSz,
+ const Bitmap& rBmp, long nMaxBmpDPIX, 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
+ Size aDstSizeTwip( PixelToLogic(LogicToPixel(rDstSz), MapMode(MapUnit::MapTwip)) );
+
+ // #103209# Normalize size (mirroring has to happen outside of this method)
+ aDstSizeTwip = Size( labs(aDstSizeTwip.Width()), labs(aDstSizeTwip.Height()) );
+
+ const Size aBmpSize( aBmp.GetSizePixel() );
+ const double fBmpPixelX = aBmpSize.Width();
+ const double fBmpPixelY = aBmpSize.Height();
+ const double fMaxPixelX = aDstSizeTwip.Width() * nMaxBmpDPIX / 1440.0;
+ const double fMaxPixelY = aDstSizeTwip.Height() * nMaxBmpDPIY / 1440.0;
+
+ // 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;
+}
+
+void OutputDevice::DrawBitmapEx( const Point& rDestPt,
+ const BitmapEx& rBitmapEx )
+{
+ assert(!is_double_buffered_window());
+
+ if( ImplIsRecordLayout() )
+ return;
+
+ if( TransparentType::NONE == rBitmapEx.GetTransparentType() )
+ {
+ 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 ( TransparentType::NONE == rBitmapEx.GetTransparentType() )
+ {
+ 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, const MetaActionType nAction )
+{
+ assert(!is_double_buffered_window());
+
+ if( ImplIsRecordLayout() )
+ return;
+
+ if( TransparentType::NONE == rBitmapEx.GetTransparentType() )
+ {
+ DrawBitmap( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, rBitmapEx.GetBitmap() );
+ }
+ else
+ {
+ if ( RasterOp::Invert == meRasterOp )
+ {
+ DrawRect( tools::Rectangle( rDestPt, rDestSize ) );
+ return;
+ }
+
+ BitmapEx aBmpEx( rBitmapEx );
+
+ if ( mnDrawMode & ( DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap |
+ DrawModeFlags::GrayBitmap ) )
+ {
+ if ( mnDrawMode & ( DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap ) )
+ {
+ Bitmap aColorBmp( aBmpEx.GetSizePixel(), 1 );
+ sal_uInt8 cCmpVal;
+
+ if ( mnDrawMode & DrawModeFlags::BlackBitmap )
+ cCmpVal = 0;
+ else
+ cCmpVal = 255;
+
+ aColorBmp.Erase( Color( cCmpVal, cCmpVal, cCmpVal ) );
+
+ if( aBmpEx.IsAlpha() )
+ {
+ // Create one-bit mask out of alpha channel, by
+ // thresholding it at alpha=0.5. As
+ // DRAWMODE_BLACK/WHITEBITMAP requires monochrome
+ // output, having alpha-induced grey levels is not
+ // acceptable.
+ BitmapEx aMaskEx(aBmpEx.GetAlpha().GetBitmap());
+ BitmapFilter::Filter(aMaskEx, BitmapMonochromeFilter(129));
+ aBmpEx = BitmapEx(aColorBmp, aMaskEx.GetBitmap());
+ }
+ else
+ {
+ aBmpEx = BitmapEx( aColorBmp, aBmpEx.GetMask() );
+ }
+ }
+ else if( !!aBmpEx )
+ {
+ if ( mnDrawMode & DrawModeFlags::GrayBitmap )
+ aBmpEx.Convert( BmpConversion::N8BitGreys );
+ }
+ }
+
+ 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;
+
+ DrawDeviceBitmap( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, aBmpEx );
+ }
+}
+
+Bitmap OutputDevice::GetBitmap( const Point& rSrcPt, const Size& rSize ) const
+{
+ Bitmap aBmp;
+ long nX = ImplLogicXToDevicePixel( rSrcPt.X() );
+ long nY = ImplLogicYToDevicePixel( rSrcPt.Y() );
+ long nWidth = ImplLogicWidthToDevicePixel( rSize.Width() );
+ long nHeight = ImplLogicHeightToDevicePixel( rSize.Height() );
+
+ if ( mpGraphics || AcquireGraphics() )
+ {
+ if ( nWidth > 0 && nHeight > 0 && nX <= (mnOutWidth + mnOutOffX) && nY <= (mnOutHeight + mnOutOffY))
+ {
+ tools::Rectangle aRect( Point( nX, nY ), Size( nWidth, nHeight ) );
+ bool bClipped = false;
+
+ // X-Coordinate outside of draw area?
+ if ( nX < mnOutOffX )
+ {
+ nWidth -= ( mnOutOffX - nX );
+ nX = mnOutOffX;
+ bClipped = true;
+ }
+
+ // Y-Coordinate outside of draw area?
+ if ( nY < mnOutOffY )
+ {
+ nHeight -= ( mnOutOffY - nY );
+ nY = mnOutOffY;
+ bClipped = true;
+ }
+
+ // Width outside of draw area?
+ if ( (nWidth + nX) > (mnOutWidth + mnOutOffX) )
+ {
+ nWidth = mnOutOffX + mnOutWidth - nX;
+ bClipped = true;
+ }
+
+ // Height outside of draw area?
+ if ( (nHeight + nY) > (mnOutHeight + mnOutOffY) )
+ {
+ nHeight = mnOutOffY + mnOutHeight - nY;
+ bClipped = true;
+ }
+
+ if ( bClipped )
+ {
+ // If the visible part has been clipped, we have to create a
+ // Bitmap with the correct size in which we copy the clipped
+ // Bitmap to the correct position.
+ ScopedVclPtrInstance< VirtualDevice > aVDev( *this );
+
+ if ( aVDev->SetOutputSizePixel( aRect.GetSize() ) )
+ {
+ if ( aVDev->mpGraphics || aVDev->AcquireGraphics() )
+ {
+ if ( (nWidth > 0) && (nHeight > 0) )
+ {
+ SalTwoRect aPosAry(nX, nY, nWidth, nHeight,
+ (aRect.Left() < mnOutOffX) ? (mnOutOffX - aRect.Left()) : 0L,
+ (aRect.Top() < mnOutOffY) ? (mnOutOffY - aRect.Top()) : 0L,
+ nWidth, nHeight);
+ aVDev->mpGraphics->CopyBits( aPosAry, mpGraphics, this, this );
+ }
+ else
+ {
+ OSL_ENSURE(false, "CopyBits with zero or negative width or height");
+ }
+
+ aBmp = aVDev->GetBitmap( Point(), aVDev->GetOutputSizePixel() );
+ }
+ else
+ bClipped = false;
+ }
+ else
+ bClipped = false;
+ }
+
+ if ( !bClipped )
+ {
+ std::shared_ptr<SalBitmap> pSalBmp = mpGraphics->GetBitmap( nX, nY, nWidth, nHeight, this );
+
+ if( pSalBmp )
+ {
+ aBmp.ImplSetSalBitmap(pSalBmp);
+ }
+ }
+ }
+ }
+
+ return aBmp;
+}
+
+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.GetBitCount() > 8 )
+ aAlphaBitmap.Convert( BmpConversion::N8BitNoConversion );
+
+ return BitmapEx(GetBitmap( rSrcPt, rSize ), AlphaMask( aAlphaBitmap ) );
+ }
+ else
+ return BitmapEx(GetBitmap( rSrcPt, rSize ));
+}
+
+void OutputDevice::DrawDeviceBitmap( const Point& rDestPt, const Size& rDestSize,
+ const Point& rSrcPtPixel, const Size& rSrcSizePixel,
+ BitmapEx& rBitmapEx )
+{
+ assert(!is_double_buffered_window());
+
+ if (rBitmapEx.IsAlpha())
+ {
+ DrawDeviceAlphaBitmap(rBitmapEx.GetBitmap(), rBitmapEx.GetAlpha(), rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel);
+ }
+ else if (!!rBitmapEx)
+ {
+ 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.ImplGetMaskSalBitmap();
+
+ 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.GetMask(),
+ rBitmapEx.GetMask()));
+ }
+ else
+ {
+ mpGraphics->DrawBitmap(aPosAry, *pSalSrcBmp, this);
+
+ if (mpAlphaVDev)
+ {
+ // #i32109#: Make bitmap area opaque
+ mpAlphaVDev->ImplFillOpaqueRectangle( tools::Rectangle(rDestPt, rDestSize) );
+ }
+ }
+ }
+ }
+}
+
+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);
+
+ if (bHMirr)
+ {
+ aOutSz.setWidth( -aOutSz.Width() );
+ aOutPt.AdjustX( -(aOutSz.Width() - 1) );
+ }
+
+ if (bVMirr)
+ {
+ aOutSz.setHeight( -aOutSz.Height() );
+ aOutPt.AdjustY( -(aOutSz.Height() - 1) );
+ }
+
+ if (!aDstRect.Intersection(tools::Rectangle(aOutPt, aOutSz)).IsEmpty())
+ {
+ static const char* pDisableNative = getenv( "SAL_DISABLE_NATIVE_ALPHA");
+ bool bTryDirectPaint(!pDisableNative && !bHMirr && !bVMirr);
+
+ if (bTryDirectPaint)
+ {
+ Point aRelPt = aOutPt + Point(mnOutOffX, mnOutOffY);
+ SalTwoRect aTR(
+ rSrcPtPixel.X(), rSrcPtPixel.Y(),
+ rSrcSizePixel.Width(), rSrcSizePixel.Height(),
+ aRelPt.X(), aRelPt.Y(),
+ aOutSz.Width(), aOutSz.Height());
+
+ SalBitmap* pSalSrcBmp = rBmp.ImplGetSalBitmap().get();
+ SalBitmap* pSalAlphaBmp = rAlpha.ImplGetSalBitmap().get();
+
+ // #i83087# Naturally, system alpha blending (SalGraphics::DrawAlphaBitmap) cannot work
+ // with separate alpha VDev
+
+ // try to blend the alpha bitmap with the alpha virtual device
+ if (mpAlphaVDev)
+ {
+ Bitmap aAlphaBitmap( mpAlphaVDev->GetBitmap( aRelPt, aOutSz ) );
+ if (SalBitmap* pSalAlphaBmp2 = aAlphaBitmap.ImplGetSalBitmap().get())
+ {
+ if (mpGraphics->BlendAlphaBitmap(aTR, *pSalSrcBmp, *pSalAlphaBmp, *pSalAlphaBmp2, this))
+ {
+ mpAlphaVDev->BlendBitmap(aTR, rAlpha);
+ return;
+ }
+ }
+ }
+ else
+ {
+ if (mpGraphics->DrawAlphaBitmap(aTR, *pSalSrcBmp, *pSalAlphaBmp, this))
+ return;
+ }
+ }
+
+ // we need to make sure OpenGL never reaches this slow code path
+
+ assert(!SkiaHelper::isVCLSkiaEnabled());
+#if HAVE_FEATURE_OPENGL
+ assert(!OpenGLHelper::isVCLOpenGLEnabled());
+#endif
+ tools::Rectangle aBmpRect(Point(), rBmp.GetSizePixel());
+ if (!aBmpRect.Intersection(tools::Rectangle(rSrcPtPixel, rSrcSizePixel)).IsEmpty())
+ {
+ Point auxOutPt(LogicToPixel(rDestPt));
+ Size auxOutSz(LogicToPixel(rDestSize));
+
+ DrawDeviceAlphaBitmapSlowPath(rBmp, rAlpha, aDstRect, aBmpRect, auxOutSz, auxOutPt);
+ }
+ }
+}
+
+namespace
+{
+
+struct LinearScaleContext
+{
+ std::unique_ptr<long[]> mpMapX;
+ std::unique_ptr<long[]> mpMapY;
+
+ std::unique_ptr<long[]> mpMapXOffset;
+ std::unique_ptr<long[]> mpMapYOffset;
+
+ LinearScaleContext(tools::Rectangle const & aDstRect, tools::Rectangle const & aBitmapRect,
+ Size const & aOutSize, long nOffX, long nOffY)
+
+ : mpMapX(new long[aDstRect.GetWidth()])
+ , mpMapY(new long[aDstRect.GetHeight()])
+ , mpMapXOffset(new long[aDstRect.GetWidth()])
+ , mpMapYOffset(new long[aDstRect.GetHeight()])
+ {
+ const long nSrcWidth = aBitmapRect.GetWidth();
+ const 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(long nSrcDimension, long nDstDimension, long nDstLocation,
+ long nOutDimention, long nOffset, long* pMap, long* pMapOffset)
+ {
+
+ const double fReverseScale = (std::abs(nOutDimention) > 1) ? (nSrcDimension - 1) / double(std::abs(nOutDimention) - 1) : 0.0;
+
+ long nSampleRange = std::max(0L, nSrcDimension - 2);
+
+ for (long i = 0; i < nDstDimension; i++)
+ {
+ double fTemp = std::abs((nOffset + i) * fReverseScale);
+
+ pMap[i] = MinMax(nDstLocation + long(fTemp), 0, nSampleRange);
+ pMapOffset[i] = static_cast<long>((fTemp - pMap[i]) * 128.0);
+ }
+ }
+
+public:
+ bool blendBitmap(
+ const BitmapWriteAccess* pDestination,
+ const BitmapReadAccess* pSource,
+ const BitmapReadAccess* pSourceAlpha,
+ const long nDstWidth,
+ const long nDstHeight)
+ {
+ if (pSource && pSourceAlpha && pDestination)
+ {
+ ScanlineFormat nSourceFormat = pSource->GetScanlineFormat();
+ ScanlineFormat nDestinationFormat = pDestination->GetScanlineFormat();
+
+ switch (nSourceFormat)
+ {
+ case ScanlineFormat::N24BitTcRgb:
+ case ScanlineFormat::N24BitTcBgr:
+ {
+ if ( (nSourceFormat == ScanlineFormat::N24BitTcBgr && nDestinationFormat == ScanlineFormat::N32BitTcBgra)
+ || (nSourceFormat == ScanlineFormat::N24BitTcRgb && nDestinationFormat == ScanlineFormat::N32BitTcRgba))
+ {
+ blendBitmap24(pDestination, pSource, pSourceAlpha, nDstWidth, nDstHeight);
+ return true;
+ }
+ }
+ break;
+ default: break;
+ }
+ }
+ return false;
+ }
+
+ void blendBitmap24(
+ const BitmapWriteAccess* pDestination,
+ const BitmapReadAccess* pSource,
+ const BitmapReadAccess* pSourceAlpha,
+ const long nDstWidth,
+ const long nDstHeight)
+ {
+ Scanline pLine0, pLine1;
+ Scanline pLineAlpha0, pLineAlpha1;
+ Scanline pColorSample1, pColorSample2;
+ Scanline pDestScanline;
+
+ long nColor1Line1, nColor2Line1, nColor3Line1;
+ long nColor1Line2, nColor2Line2, nColor3Line2;
+ long nAlphaLine1, nAlphaLine2;
+
+ sal_uInt8 nColor1, nColor2, nColor3, nAlpha;
+
+ for (long nY = 0; nY < nDstHeight; nY++)
+ {
+ const long nMapY = mpMapY[nY];
+ const 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 (long nX = 0; nX < nDstWidth; nX++)
+ {
+ const long nMapX = mpMapX[nX];
+ const long nMapFX = mpMapXOffset[nX];
+
+ pColorSample1 = pLine0 + 3 * nMapX;
+ pColorSample2 = (nMapX + 1 < pSource->Width()) ? pColorSample1 + 3 : pColorSample1;
+ nColor1Line1 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1);
+
+ pColorSample1++;
+ pColorSample2++;
+ nColor2Line1 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1);
+
+ pColorSample1++;
+ pColorSample2++;
+ nColor3Line1 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1);
+
+ pColorSample1 = pLine1 + 3 * nMapX;
+ pColorSample2 = (nMapX + 1 < pSource->Width()) ? pColorSample1 + 3 : pColorSample1;
+ nColor1Line2 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1);
+
+ pColorSample1++;
+ pColorSample2++;
+ nColor2Line2 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1);
+
+ pColorSample1++;
+ pColorSample2++;
+ nColor3Line2 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1);
+
+ pColorSample1 = pLineAlpha0 + nMapX;
+ pColorSample2 = (nMapX + 1 < pSourceAlpha->Width()) ? pColorSample1 + 1 : pColorSample1;
+ nAlphaLine1 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1);
+
+ pColorSample1 = pLineAlpha1 + nMapX;
+ pColorSample2 = (nMapX + 1 < pSourceAlpha->Width()) ? pColorSample1 + 1 : pColorSample1;
+ nAlphaLine2 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<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 = ColorChannelMerge(*pDestScanline, nColor1, nAlpha);
+ pDestScanline++;
+ *pDestScanline = ColorChannelMerge(*pDestScanline, nColor2, nAlpha);
+ pDestScanline++;
+ *pDestScanline = ColorChannelMerge(*pDestScanline, nColor3, nAlpha);
+ pDestScanline++;
+ pDestScanline++;
+ }
+ }
+ }
+};
+
+struct TradScaleContext
+{
+ std::unique_ptr<long[]> mpMapX;
+ std::unique_ptr<long[]> mpMapY;
+
+ TradScaleContext(tools::Rectangle const & aDstRect, tools::Rectangle const & aBitmapRect,
+ Size const & aOutSize, long nOffX, long nOffY)
+
+ : mpMapX(new long[aDstRect.GetWidth()])
+ , mpMapY(new long[aDstRect.GetHeight()])
+ {
+ const long nSrcWidth = aBitmapRect.GetWidth();
+ const 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(long nSrcDimension, long nDstDimension, long nDstLocation,
+ long nOutDimention, long nOffset, bool bMirror, long* pMap)
+ {
+ long nMirrorOffset = 0;
+
+ if (bMirror)
+ nMirrorOffset = (nDstLocation << 1) + nSrcDimension - 1;
+
+ for (long i = 0; i < nDstDimension; ++i, ++nOffset)
+ {
+ pMap[i] = nDstLocation + nOffset * nSrcDimension / nOutDimention;
+ 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 long nDstWidth = aDstRect.GetWidth();
+ const 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 long nOffX = IsRTLEnabled()
+ ? aOutSize.Width() - aDstRect.GetWidth() - (aDstRect.Left() - aOutPoint.X())
+ : aDstRect.Left() - aOutPoint.X();
+
+ const long nOffY = aDstRect.Top() - aOutPoint.Y();
+
+ TradScaleContext aTradContext(aDstRect, aBmpRect, aOutSize, nOffX, nOffY);
+
+ Bitmap::ScopedReadAccess pBitmapReadAccess(const_cast<Bitmap&>(rBitmap));
+ AlphaMask::ScopedReadAccess pAlphaReadAccess(const_cast<AlphaMask&>(rAlpha));
+
+ DBG_ASSERT( pAlphaReadAccess->GetScanlineFormat() == ScanlineFormat::N8BitPal ||
+ pAlphaReadAccess->GetScanlineFormat() == ScanlineFormat::N8BitTcMask,
+ "OutputDevice::ImplDrawAlpha(): non-8bit alpha no longer supported!" );
+
+ // #i38887# reading from screen may sometimes fail
+ if (aBmp.ImplGetSalBitmap())
+ {
+ Bitmap aNewBitmap;
+
+ if (mpAlphaVDev)
+ {
+ aNewBitmap = BlendBitmapWithAlpha(
+ aBmp, pBitmapReadAccess.get(), pAlphaReadAccess.get(),
+ aDstRect,
+ nOffY, nDstHeight,
+ nOffX, nDstWidth,
+ aTradContext.mpMapX.get(), aTradContext.mpMapY.get() );
+ }
+ else
+ {
+ LinearScaleContext aLinearContext(aDstRect, aBmpRect, aOutSize, nOffX, nOffY);
+
+ if (aLinearContext.blendBitmap( BitmapScopedWriteAccess(aBmp).get(), pBitmapReadAccess.get(), pAlphaReadAccess.get(),
+ nDstWidth, nDstHeight))
+ {
+ aNewBitmap = aBmp;
+ }
+ else
+ {
+ aNewBitmap = BlendBitmap(
+ aBmp, pBitmapReadAccess.get(), pAlphaReadAccess.get(),
+ nOffY, nDstHeight,
+ nOffX, nDstWidth,
+ aBmpRect, aOutSize,
+ bHMirr, bVMirr,
+ aTradContext.mpMapX.get(), aTradContext.mpMapY.get() );
+ }
+ }
+
+ // #110958# Disable alpha VDev, we're doing the necessary
+ // stuff explicitly further below
+ if (mpAlphaVDev)
+ mpAlphaVDev = nullptr;
+
+ DrawBitmap(aDstRect.TopLeft(), aNewBitmap);
+
+ // #110958# Enable alpha VDev again
+ mpAlphaVDev = pOldVDev;
+ }
+
+ mbMap = bOldMap;
+ mpMetaFile = pOldMetaFile;
+}
+
+void OutputDevice::ScaleBitmap (Bitmap &rBmp, SalTwoRect &rPosAry)
+{
+ const double nScaleX = rPosAry.mnDestWidth / static_cast<double>( rPosAry.mnSrcWidth );
+ const double nScaleY = rPosAry.mnDestHeight / static_cast<double>( rPosAry.mnSrcHeight );
+
+ // If subsampling, use Bitmap::Scale for subsampling for better quality.
+ if ( nScaleX < 1.0 || nScaleY < 1.0 )
+ {
+ rBmp.Scale ( nScaleX, nScaleY );
+ rPosAry.mnSrcWidth = rPosAry.mnDestWidth;
+ rPosAry.mnSrcHeight = rPosAry.mnDestHeight;
+ }
+}
+
+bool OutputDevice::DrawTransformBitmapExDirect(
+ const basegfx::B2DHomMatrix& aFullTransform,
+ const BitmapEx& rBitmapEx)
+{
+ assert(!is_double_buffered_window());
+
+ bool bDone = false;
+
+ // try to paint directly
+ const basegfx::B2DPoint aNull(aFullTransform * basegfx::B2DPoint(0.0, 0.0));
+ const basegfx::B2DPoint aTopX(aFullTransform * basegfx::B2DPoint(1.0, 0.0));
+ const basegfx::B2DPoint aTopY(aFullTransform * basegfx::B2DPoint(0.0, 1.0));
+ SalBitmap* pSalSrcBmp = rBitmapEx.GetBitmap().ImplGetSalBitmap().get();
+ Bitmap aAlphaBitmap;
+
+ if(rBitmapEx.IsTransparent())
+ {
+ if(rBitmapEx.IsAlpha())
+ {
+ aAlphaBitmap = rBitmapEx.GetAlpha();
+ }
+ else
+ {
+ aAlphaBitmap = rBitmapEx.GetMask();
+ }
+ }
+ else if (mpAlphaVDev)
+ {
+ aAlphaBitmap = Bitmap(rBitmapEx.GetSizePixel(), 1);
+ aAlphaBitmap.Erase(COL_BLACK);
+ }
+
+ SalBitmap* pSalAlphaBmp = aAlphaBitmap.ImplGetSalBitmap().get();
+
+ bDone = mpGraphics->DrawTransformedBitmap(
+ aNull,
+ aTopX,
+ aTopY,
+ *pSalSrcBmp,
+ pSalAlphaBmp,
+ this);
+
+ if (mpAlphaVDev)
+ {
+ // Merge bitmap alpha to alpha device
+ Bitmap aBlack(rBitmapEx.GetSizePixel(), 1);
+ aBlack.Erase(COL_BLACK);
+ mpAlphaVDev->DrawTransformBitmapExDirect(aFullTransform, BitmapEx(aBlack, 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)
+{
+ assert(!is_double_buffered_window());
+
+ if( ImplIsRecordLayout() )
+ return;
+
+ if(rBitmapEx.IsEmpty())
+ 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 the in
+ record-to-metafile case 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
+
+ // MM02 reorganize order: Prefer DrawTransformBitmapExDirect due
+ // to this having evolved and is improved on quite some systems.
+ // Check for exclusion parameters that may prevent using it
+ static bool bAllowPreferDirectPaint(true);
+ const bool bInvert(RasterOp::Invert == meRasterOp);
+ const bool bBitmapChangedColor(mnDrawMode & (DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap | DrawModeFlags::GrayBitmap ));
+ const bool bTryDirectPaint(!bInvert && !bBitmapChangedColor && !bMetafile);
+
+ if(bAllowPreferDirectPaint && bTryDirectPaint)
+ {
+ // 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
+ const basegfx::B2DHomMatrix aFullTransform(ImplGetDeviceTransformation() * rTransformation);
+
+ if(DrawTransformBitmapExDirect(aFullTransform, rBitmapEx))
+ {
+ // we are done
+ 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, rBitmapEx);
+ if (!bMetafile && comphelper::LibreOfficeKit::isActive() && GetMapMode().GetMapUnit() != MapUnit::MapPixel)
+ {
+ EnableMapMode();
+ aDestPt.Move(-aOrigin.getX(), -aOrigin.getY());
+ }
+ return;
+ }
+
+ // MM02 bAllowPreferDirectPaint may have been false to allow
+ // to specify order of executions, so give bTryDirectPaint a call
+ if(bTryDirectPaint)
+ {
+ // 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
+ const basegfx::B2DHomMatrix aFullTransform(ImplGetDeviceTransformation() * rTransformation);
+
+ if(DrawTransformBitmapExDirect(aFullTransform, rBitmapEx))
+ {
+ // we are done
+ 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, rBitmapEx);
+ 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(rBitmapEx.GetSizePixel());
+ const double fOrigArea(rOriginalSizePixel.Width() * rOriginalSizePixel.Height() * 0.5);
+ const double fOrigAreaScaled(fOrigArea * 1.44);
+ double fMaximumArea(std::min(4500000.0, std::max(1000000.0, fOrigAreaScaled)));
+ // 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);
+
+ if(!bMetafile)
+ {
+ if ( !TransformAndReduceBitmapExToTargetRange( aFullTransform, aVisibleRange, fMaximumArea ) )
+ return;
+ }
+
+ if(!aVisibleRange.isEmpty())
+ {
+ BitmapEx aTransformed(rBitmapEx);
+
+ // #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.IsTransparent() && (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);
+ }
+
+ // Remove scaling from aFulltransform: we transform due to shearing or rotation, scaling
+ // will happen according to aDestSize.
+ basegfx::B2DVector aFullScale, aFullTranslate;
+ double fFullRotate, fFullShearX;
+ aFullTransform.decompose(aFullScale, aFullTranslate, fFullRotate, fFullShearX);
+ // Require positive scaling, negative scaling would loose horizontal or vertical flip.
+ if (aFullScale.getX() > 0 && aFullScale.getY() > 0)
+ {
+ basegfx::B2DHomMatrix aTransform = basegfx::utils::createScaleB2DHomMatrix(
+ rOriginalSizePixel.getWidth() / aFullScale.getX(),
+ rOriginalSizePixel.getHeight() / aFullScale.getY());
+ aFullTransform *= aTransform;
+ }
+
+ 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, F_2PI);
+ if (fFullRotate < 0)
+ {
+ fFullRotate += F_2PI;
+ }
+ long nAngle10 = basegfx::fround(basegfx::rad2deg(fFullRotate) * 10);
+ 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);
+ }
+}
+
+void OutputDevice::DrawShadowBitmapEx(
+ const BitmapEx& rBitmapEx,
+ ::Color aShadowColor)
+{
+ Bitmap::ScopedReadAccess pReadAccess(const_cast<Bitmap&>(rBitmapEx.maBitmap));
+
+ if(!pReadAccess)
+ return;
+
+ for(long y(0); y < pReadAccess->Height(); y++)
+ {
+ for(long x(0); x < pReadAccess->Width(); x++)
+ {
+ const BitmapColor aColor = pReadAccess->GetColor(y, x);
+ sal_uInt16 nLuminance(static_cast<sal_uInt16>(aColor.GetLuminance()) + 1);
+ const Color aDestColor(
+ static_cast<sal_uInt8>((nLuminance * static_cast<sal_uInt16>(aShadowColor.GetRed())) >> 8),
+ static_cast<sal_uInt8>((nLuminance * static_cast<sal_uInt16>(aShadowColor.GetGreen())) >> 8),
+ static_cast<sal_uInt8>((nLuminance * static_cast<sal_uInt16>(aShadowColor.GetBlue())) >> 8));
+ DrawPixel(Point(x,y), aDestColor);
+ }
+ }
+}
+
+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 long nMapX,
+ const long nMapY,
+ BitmapReadAccess const * pP,
+ BitmapReadAccess const * pA,
+ BitmapReadAccess const * pB,
+ BitmapWriteAccess const * pAlphaW,
+ sal_uInt8& nResAlpha )
+ {
+ BitmapColor aDstCol,aSrcCol;
+ aSrcCol = pP->GetColor( nMapY, nMapX );
+ aDstCol = pB->GetColor( nY, nX );
+
+ // vcl stores transparency, not alpha - invert it
+ const sal_uInt8 nSrcAlpha = 255 - pA->GetPixelIndex( nMapY, nMapX );
+ const sal_uInt8 nDstAlpha = 255 - pAlphaW->GetPixelIndex( nY, nX );
+
+ // Perform porter-duff compositing 'over' operation
+
+ // Co = Cs + Cd*(1-As)
+ // Ad = As + Ad*(1-As)
+ nResAlpha = static_cast<int>(nSrcAlpha) + static_cast<int>(nDstAlpha) - static_cast<int>(nDstAlpha)*nSrcAlpha/255;
+
+ aDstCol.SetRed( CalcColor( aSrcCol.GetRed(), nSrcAlpha, nDstAlpha, nResAlpha, aDstCol.GetRed() ) );
+ aDstCol.SetBlue( CalcColor( aSrcCol.GetBlue(), nSrcAlpha, nDstAlpha, nResAlpha, aDstCol.GetBlue() ) );
+ aDstCol.SetGreen( CalcColor( aSrcCol.GetGreen(), nSrcAlpha, nDstAlpha, nResAlpha, aDstCol.GetGreen() ) );
+
+ return aDstCol;
+ }
+}
+
+void OutputDevice::BlendBitmap(
+ const SalTwoRect& rPosAry,
+ const Bitmap& rBmp )
+{
+ mpGraphics->BlendBitmap( rPosAry, *rBmp.ImplGetSalBitmap(), this );
+}
+
+Bitmap OutputDevice::BlendBitmapWithAlpha(
+ Bitmap& aBmp,
+ BitmapReadAccess const * pP,
+ BitmapReadAccess const * pA,
+ const tools::Rectangle& aDstRect,
+ const sal_Int32 nOffY,
+ const sal_Int32 nDstHeight,
+ const sal_Int32 nOffX,
+ const sal_Int32 nDstWidth,
+ const long* pMapX,
+ const long* 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(), 8 );
+ BitmapColor aIndex( 0 );
+ Bitmap::ScopedReadAccess pB(aBmp);
+ BitmapScopedWriteAccess pW(aDither);
+
+ if (pB && pP && pA && pW && pAlphaW)
+ {
+ int nOutY;
+
+ for( nY = 0, nOutY = nOffY; nY < nDstHeight; nY++, nOutY++ )
+ {
+ const long nMapY = pMapY[ nY ];
+ const 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 long nMapX = pMapX[ nX ];
+ const sal_uLong nD = nVCLDitherLut[ nModY | ( nOutX & 0x0FL ) ];
+
+ aDstCol = AlphaBlend( nX, nY, nMapX, nMapY, pP, pA, pB.get(), pAlphaW.get(), nResAlpha );
+
+ aIndex.SetIndex( static_cast<sal_uInt8>( nVCLRLut[ ( nVCLLut[ aDstCol.GetRed() ] + nD ) >> 16 ] +
+ nVCLGLut[ ( nVCLLut[ aDstCol.GetGreen() ] + nD ) >> 16 ] +
+ nVCLBLut[ ( nVCLLut[ aDstCol.GetBlue() ] + nD ) >> 16 ] ) );
+ pW->SetPixelOnData( pScanline, nX, aIndex );
+
+ aIndex.SetIndex( static_cast<sal_uInt8>( nVCLRLut[ ( nVCLLut[ 255-nResAlpha ] + nD ) >> 16 ] +
+ nVCLGLut[ ( nVCLLut[ 255-nResAlpha ] + nD ) >> 16 ] +
+ nVCLBLut[ ( nVCLLut[ 255-nResAlpha ] + nD ) >> 16 ] ) );
+ pAlphaW->SetPixelOnData( pScanlineAlpha, nX, aIndex );
+ }
+ }
+ }
+ pB.reset();
+ pW.reset();
+ res = aDither;
+ }
+ else
+ {
+ BitmapScopedWriteAccess pB(aBmp);
+ if (pB && pP && pA && pAlphaW)
+ {
+ for( nY = 0; nY < nDstHeight; nY++ )
+ {
+ const long nMapY = pMapY[ nY ];
+ Scanline pScanlineB = pB->GetScanline(nY);
+ Scanline pScanlineAlpha = pAlphaW->GetScanline(nY);
+
+ for( nX = 0; nX < nDstWidth; nX++ )
+ {
+ const long nMapX = pMapX[ nX ];
+ aDstCol = AlphaBlend( nX, nY, nMapX, nMapY, pP, pA, pB.get(), pAlphaW.get(), nResAlpha );
+
+ pB->SetPixelOnData(pScanlineB, nX, pB->GetBestMatchingColor(aDstCol));
+ pAlphaW->SetPixelOnData(pScanlineAlpha, nX, pB->GetBestMatchingColor(Color(255L-nResAlpha, 255L-nResAlpha, 255L-nResAlpha)));
+ }
+ }
+ }
+ pB.reset();
+ res = aBmp;
+ }
+
+ pAlphaW.reset();
+ mpAlphaVDev->DrawBitmap( aDstRect.TopLeft(), aAlphaBitmap );
+ mpAlphaVDev->EnableMapMode( bOldMapMode );
+
+ return res;
+}
+
+Bitmap OutputDevice::BlendBitmap(
+ Bitmap& aBmp,
+ BitmapReadAccess const * pP,
+ BitmapReadAccess const * pA,
+ const sal_Int32 nOffY,
+ const sal_Int32 nDstHeight,
+ const sal_Int32 nOffX,
+ const sal_Int32 nDstWidth,
+ const tools::Rectangle& aBmpRect,
+ const Size& aOutSz,
+ const bool bHMirr,
+ const bool bVMirr,
+ const long* pMapX,
+ const long* pMapY )
+{
+ BitmapColor aDstCol;
+ Bitmap res;
+ int nX, nY;
+
+ if( GetBitCount() <= 8 )
+ {
+ Bitmap aDither( aBmp.GetSizePixel(), 8 );
+ BitmapColor aIndex( 0 );
+ Bitmap::ScopedReadAccess pB(aBmp);
+ BitmapScopedWriteAccess pW(aDither);
+
+ if( pB && pP && pA && pW )
+ {
+ int nOutY;
+
+ for( nY = 0, nOutY = nOffY; nY < nDstHeight; nY++, nOutY++ )
+ {
+ long nMapY = pMapY[ nY ];
+ if (bVMirr)
+ {
+ nMapY = aBmpRect.Bottom() - nMapY;
+ }
+ const long nModY = ( nOutY & 0x0FL ) << 4;
+ int nOutX;
+
+ Scanline pScanline = pW->GetScanline(nY);
+ Scanline pScanlineAlpha = pA->GetScanline(nMapY);
+ for( nX = 0, nOutX = nOffX; nX < nDstWidth; nX++, nOutX++ )
+ {
+ long nMapX = pMapX[ nX ];
+ if (bHMirr)
+ {
+ nMapX = aBmpRect.Right() - nMapX;
+ }
+ const sal_uLong nD = nVCLDitherLut[ nModY | ( nOutX & 0x0FL ) ];
+
+ aDstCol = pB->GetColor( nY, nX );
+ aDstCol.Merge( pP->GetColor( nMapY, nMapX ), pA->GetIndexFromData( pScanlineAlpha, nMapX ) );
+ aIndex.SetIndex( static_cast<sal_uInt8>( nVCLRLut[ ( nVCLLut[ aDstCol.GetRed() ] + nD ) >> 16 ] +
+ nVCLGLut[ ( nVCLLut[ aDstCol.GetGreen() ] + nD ) >> 16 ] +
+ nVCLBLut[ ( nVCLLut[ aDstCol.GetBlue() ] + nD ) >> 16 ] ) );
+ pW->SetPixelOnData( pScanline, nX, aIndex );
+ }
+ }
+ }
+
+ pB.reset();
+ pW.reset();
+ res = aDither;
+ }
+ else
+ {
+ BitmapScopedWriteAccess pB(aBmp);
+
+ bool bFastBlend = false;
+ if( pP && pA && pB && !bHMirr && !bVMirr )
+ {
+ SalTwoRect aTR(aBmpRect.Left(), aBmpRect.Top(), aBmpRect.GetWidth(), aBmpRect.GetHeight(),
+ nOffX, nOffY, aOutSz.Width(), aOutSz.Height());
+
+ bFastBlend = ImplFastBitmapBlending( *pB,*pP,*pA, aTR );
+ }
+
+ if( pP && pA && pB && !bFastBlend )
+ {
+ switch( pP->GetScanlineFormat() )
+ {
+ case ScanlineFormat::N8BitPal:
+ {
+ for( nY = 0; nY < nDstHeight; nY++ )
+ {
+ long nMapY = pMapY[ nY ];
+ if ( bVMirr )
+ {
+ nMapY = aBmpRect.Bottom() - nMapY;
+ }
+ Scanline pPScan = pP->GetScanline( nMapY );
+ Scanline pAScan = pA->GetScanline( nMapY );
+ Scanline pBScan = pB->GetScanline( nY );
+
+ for( nX = 0; nX < nDstWidth; nX++ )
+ {
+ long nMapX = pMapX[ nX ];
+
+ if ( bHMirr )
+ {
+ nMapX = aBmpRect.Right() - nMapX;
+ }
+ aDstCol = pB->GetPixelFromData( pBScan, nX );
+ aDstCol.Merge( pP->GetPaletteColor( pPScan[ nMapX ] ), pAScan[ nMapX ] );
+ pB->SetPixelOnData( pBScan, nX, aDstCol );
+ }
+ }
+ }
+ break;
+
+ default:
+ {
+
+ for( nY = 0; nY < nDstHeight; nY++ )
+ {
+ long nMapY = pMapY[ nY ];
+
+ if ( bVMirr )
+ {
+ nMapY = aBmpRect.Bottom() - nMapY;
+ }
+ Scanline pAScan = pA->GetScanline( nMapY );
+ Scanline pBScan = pB->GetScanline(nY);
+ for( nX = 0; nX < nDstWidth; nX++ )
+ {
+ long nMapX = pMapX[ nX ];
+
+ if ( bHMirr )
+ {
+ nMapX = aBmpRect.Right() - nMapX;
+ }
+ aDstCol = pB->GetPixelFromData( pBScan, nX );
+ aDstCol.Merge( pP->GetColor( nMapY, nMapX ), pAScan[ nMapX ] );
+ pB->SetPixelOnData( pBScan, nX, aDstCol );
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ pB.reset();
+ res = aBmp;
+ }
+
+ return res;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/clipping.cxx b/vcl/source/outdev/clipping.cxx
new file mode 100644
index 000000000..1c6e4551c
--- /dev/null
+++ b/vcl/source/outdev/clipping.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 <sal/config.h>
+#include <osl/diagnose.h>
+
+#include <tools/debug.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/outdev.hxx>
+
+#include <salgdi.hxx>
+
+void OutputDevice::SaveBackground(VirtualDevice& rSaveDevice,
+ const Point& rPos, const Size& rSize, const Size& rBackgroundSize) const
+{
+ rSaveDevice.DrawOutDev(Point(), rBackgroundSize, rPos, rSize, *this);
+}
+
+vcl::Region OutputDevice::GetClipRegion() const
+{
+
+ return PixelToLogic( maRegion );
+}
+
+void OutputDevice::SetClipRegion()
+{
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaClipRegionAction( vcl::Region(), false ) );
+
+ SetDeviceClipRegion( nullptr );
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetClipRegion();
+}
+
+void OutputDevice::SetClipRegion( const vcl::Region& rRegion )
+{
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaClipRegionAction( rRegion, true ) );
+
+ if ( rRegion.IsNull() )
+ {
+ SetDeviceClipRegion( nullptr );
+ }
+ else
+ {
+ vcl::Region aRegion = LogicToPixel( rRegion );
+ SetDeviceClipRegion( &aRegion );
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetClipRegion( rRegion );
+}
+
+bool OutputDevice::SelectClipRegion( const vcl::Region& rRegion, SalGraphics* pGraphics )
+{
+ DBG_TESTSOLARMUTEX();
+
+ if( !pGraphics )
+ {
+ if( !mpGraphics && !AcquireGraphics() )
+ return false;
+ pGraphics = mpGraphics;
+ }
+
+ bool bClipRegion = pGraphics->SetClipRegion( rRegion, this );
+ OSL_ENSURE( bClipRegion, "OutputDevice::SelectClipRegion() - can't create region" );
+ return bClipRegion;
+}
+
+void OutputDevice::MoveClipRegion( long nHorzMove, long nVertMove )
+{
+
+ if ( mbClipRegion )
+ {
+ if( mpMetaFile )
+ mpMetaFile->AddAction( new MetaMoveClipRegionAction( nHorzMove, nVertMove ) );
+
+ maRegion.Move( ImplLogicWidthToDevicePixel( nHorzMove ),
+ ImplLogicHeightToDevicePixel( nVertMove ) );
+ mbInitClipRegion = true;
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->MoveClipRegion( nHorzMove, nVertMove );
+}
+
+void OutputDevice::IntersectClipRegion( const tools::Rectangle& rRect )
+{
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaISectRectClipRegionAction( rRect ) );
+
+ tools::Rectangle aRect = LogicToPixel( rRect );
+ maRegion.Intersect( aRect );
+ mbClipRegion = true;
+ mbInitClipRegion = true;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->IntersectClipRegion( rRect );
+}
+
+void OutputDevice::IntersectClipRegion( const vcl::Region& rRegion )
+{
+
+ if(!rRegion.IsNull())
+ {
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaISectRegionClipRegionAction( rRegion ) );
+
+ vcl::Region aRegion = LogicToPixel( rRegion );
+ maRegion.Intersect( aRegion );
+ mbClipRegion = true;
+ mbInitClipRegion = true;
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->IntersectClipRegion( rRegion );
+}
+
+void OutputDevice::InitClipRegion()
+{
+ DBG_TESTSOLARMUTEX();
+
+ if ( mbClipRegion )
+ {
+ if ( maRegion.IsEmpty() )
+ mbOutputClipped = true;
+ else
+ {
+ mbOutputClipped = false;
+
+ // #102532# Respect output offset also for clip region
+ vcl::Region aRegion = ClipToDeviceBounds(ImplPixelToDevicePixel(maRegion));
+
+ if ( aRegion.IsEmpty() )
+ {
+ mbOutputClipped = true;
+ }
+ else
+ {
+ mbOutputClipped = false;
+ SelectClipRegion( aRegion );
+ }
+ }
+
+ mbClipRegionSet = true;
+ }
+ else
+ {
+ if ( mbClipRegionSet )
+ {
+ if (mpGraphics)
+ mpGraphics->ResetClipRegion();
+ mbClipRegionSet = false;
+ }
+
+ mbOutputClipped = false;
+ }
+
+ mbInitClipRegion = false;
+}
+
+vcl::Region OutputDevice::ClipToDeviceBounds(vcl::Region aRegion) const
+{
+ aRegion.Intersect(tools::Rectangle{mnOutOffX,
+ mnOutOffY,
+ mnOutOffX + GetOutputWidthPixel() - 1,
+ mnOutOffY + GetOutputHeightPixel() - 1
+ });
+ return aRegion;
+}
+
+vcl::Region OutputDevice::GetActiveClipRegion() const
+{
+ return GetClipRegion();
+}
+
+void OutputDevice::ClipToPaintRegion(tools::Rectangle& /*rDstRect*/)
+{
+ // this is only used in Window, but we still need it as it's called
+ // on in other clipping functions
+}
+
+void OutputDevice::SetDeviceClipRegion( const vcl::Region* pRegion )
+{
+ DBG_TESTSOLARMUTEX();
+
+ if ( !pRegion )
+ {
+ if ( mbClipRegion )
+ {
+ maRegion = vcl::Region(true);
+ mbClipRegion = false;
+ mbInitClipRegion = true;
+ }
+ }
+ else
+ {
+ maRegion = *pRegion;
+ mbClipRegion = true;
+ mbInitClipRegion = true;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/curvedshapes.cxx b/vcl/source/outdev/curvedshapes.cxx
new file mode 100644
index 000000000..d25d69ad1
--- /dev/null
+++ b/vcl/source/outdev/curvedshapes.cxx
@@ -0,0 +1,210 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <cassert>
+
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/virdev.hxx>
+
+#include <salgdi.hxx>
+
+void OutputDevice::DrawEllipse( const tools::Rectangle& rRect )
+{
+ assert(!is_double_buffered_window());
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaEllipseAction( rRect ) );
+
+ if ( !IsDeviceOutputNecessary() || (!mbLineColor && !mbFillColor) || ImplIsRecordLayout() )
+ return;
+
+ tools::Rectangle aRect( ImplLogicToDevicePixel( rRect ) );
+ if ( aRect.IsEmpty() )
+ return;
+
+ // we need a graphics
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+ if ( mbOutputClipped )
+ return;
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ tools::Polygon aRectPoly( aRect.Center(), aRect.GetWidth() >> 1, aRect.GetHeight() >> 1 );
+ if ( aRectPoly.GetSize() >= 2 )
+ {
+ SalPoint* pPtAry = reinterpret_cast<SalPoint*>(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;
+
+ 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 )
+ {
+ SalPoint* pPtAry = reinterpret_cast<SalPoint*>(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;
+
+ 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 )
+ {
+ SalPoint* pPtAry = reinterpret_cast<SalPoint*>(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;
+
+ 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 )
+ {
+ SalPoint* pPtAry = reinterpret_cast<SalPoint*>(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/font.cxx b/vcl/source/outdev/font.cxx
new file mode 100644
index 000000000..cf50830dc
--- /dev/null
+++ b/vcl/source/outdev/font.cxx
@@ -0,0 +1,1435 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/mslangid.hxx>
+#include <i18nlangtag/lang.h>
+
+#include <unotools/configmgr.hxx>
+#include <vcl/metric.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/print.hxx>
+#include <vcl/sysdata.hxx>
+#include <vcl/fontcharmap.hxx>
+#include <vcl/event.hxx>
+#include <font/FeatureCollector.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+#include <tools/debug.hxx>
+
+#include <sallayout.hxx>
+#include <salgdi.hxx>
+#include <svdata.hxx>
+#include <impglyphitem.hxx>
+
+#include <outdev.h>
+#include <window.h>
+
+#include <PhysicalFontCollection.hxx>
+
+#include <strings.hrc>
+
+FontMetric OutputDevice::GetDevFont( int nDevFontIndex ) const
+{
+ FontMetric aFontMetric;
+
+ ImplInitFontList();
+
+ int nCount = GetDevFontCount();
+ if( nDevFontIndex < nCount )
+ {
+ const PhysicalFontFace& rData = *mpDeviceFontList->Get( nDevFontIndex );
+ aFontMetric.SetFamilyName( rData.GetFamilyName() );
+ aFontMetric.SetStyleName( rData.GetStyleName() );
+ aFontMetric.SetCharSet( rData.GetCharSet() );
+ aFontMetric.SetFamily( rData.GetFamilyType() );
+ aFontMetric.SetPitch( rData.GetPitch() );
+ aFontMetric.SetWeight( rData.GetWeight() );
+ aFontMetric.SetItalic( rData.GetItalic() );
+ aFontMetric.SetAlignment( TextAlign::ALIGN_TOP );
+ aFontMetric.SetWidthType( rData.GetWidthType() );
+ aFontMetric.SetQuality( rData.GetQuality() );
+ }
+
+ return aFontMetric;
+}
+
+int OutputDevice::GetDevFontCount() const
+{
+ if( !mpDeviceFontList )
+ {
+ if (!mxFontCollection)
+ {
+ return 0;
+ }
+
+ mpDeviceFontList = mxFontCollection->GetDeviceFontList();
+
+ if (!mpDeviceFontList->Count())
+ {
+ mpDeviceFontList.reset();
+ return 0;
+ }
+ }
+ return mpDeviceFontList->Count();
+}
+
+bool OutputDevice::IsFontAvailable( const OUString& rFontName ) const
+{
+ ImplInitFontList();
+ PhysicalFontFamily* pFound = mxFontCollection->FindFontFamily( rFontName );
+ return (pFound != nullptr);
+}
+
+int OutputDevice::GetDevFontSizeCount( const vcl::Font& rFont ) const
+{
+ mpDeviceFontSizeList.reset();
+
+ ImplInitFontList();
+ mpDeviceFontSizeList = mxFontCollection->GetDeviceFontSizeList( rFont.GetFamilyName() );
+ return mpDeviceFontSizeList->Count();
+}
+
+Size OutputDevice::GetDevFontSize( const vcl::Font& rFont, int nSizeIndex ) const
+{
+ // check range
+ int nCount = GetDevFontSizeCount( rFont );
+ if ( nSizeIndex >= nCount )
+ return Size();
+
+ // when mapping is enabled round to .5 points
+ Size aSize( 0, mpDeviceFontSizeList->Get( nSizeIndex ) );
+ if ( mbMap )
+ {
+ aSize.setHeight( aSize.Height() * 10 );
+ MapMode aMap( MapUnit::Map10thInch, Point(), Fraction( 1, 72 ), Fraction( 1, 72 ) );
+ aSize = PixelToLogic( aSize, aMap );
+ aSize.AdjustHeight(5 );
+ aSize.setHeight( aSize.Height() / 10 );
+ long nRound = aSize.Height() % 5;
+ if ( nRound >= 3 )
+ aSize.AdjustHeight(5-nRound);
+ else
+ aSize.AdjustHeight( -nRound );
+ aSize.setHeight( aSize.Height() * 10 );
+ aSize = LogicToPixel( aSize, aMap );
+ aSize = PixelToLogic( aSize );
+ aSize.AdjustHeight(5 );
+ aSize.setHeight( aSize.Height() / 10 );
+ }
+ return aSize;
+}
+
+bool OutputDevice::AddTempDevFont( const OUString& rFileURL, const OUString& rFontName )
+{
+ ImplInitFontList();
+
+ if( !mpGraphics && !AcquireGraphics() )
+ return false;
+
+ bool bRC = mpGraphics->AddTempDevFont( mxFontCollection.get(), rFileURL, rFontName );
+ if( !bRC )
+ return false;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->AddTempDevFont( rFileURL, rFontName );
+
+ return true;
+}
+
+bool OutputDevice::GetFontFeatures(std::vector<vcl::font::Feature>& rFontFeatures) const
+{
+ if (!ImplNewFont())
+ return false;
+
+ LogicalFontInstance* pFontInstance = mpFontInstance.get();
+ if (!pFontInstance)
+ return false;
+
+ hb_font_t* pHbFont = pFontInstance->GetHbFont();
+ if (!pHbFont)
+ return false;
+
+ hb_face_t* pHbFace = hb_font_get_face(pHbFont);
+ if (!pHbFace)
+ return false;
+
+ const LanguageType eOfficeLanguage = Application::GetSettings().GetLanguageTag().getLanguageType();
+
+ vcl::font::FeatureCollector aFeatureCollector(pHbFace, rFontFeatures, eOfficeLanguage);
+ aFeatureCollector.collect();
+
+ return true;
+}
+
+FontMetric OutputDevice::GetFontMetric() const
+{
+ FontMetric aMetric;
+ if (!ImplNewFont())
+ return aMetric;
+
+ LogicalFontInstance* pFontInstance = mpFontInstance.get();
+ ImplFontMetricDataRef xFontMetric = pFontInstance->mxFontMetric;
+
+ // prepare metric
+ aMetric = maFont;
+
+ // set aMetric with info from font
+ aMetric.SetFamilyName( maFont.GetFamilyName() );
+ aMetric.SetStyleName( xFontMetric->GetStyleName() );
+ aMetric.SetFontSize( PixelToLogic( Size( xFontMetric->GetWidth(), xFontMetric->GetAscent() + xFontMetric->GetDescent() - xFontMetric->GetInternalLeading() ) ) );
+ aMetric.SetCharSet( xFontMetric->IsSymbolFont() ? RTL_TEXTENCODING_SYMBOL : RTL_TEXTENCODING_UNICODE );
+ aMetric.SetFamily( xFontMetric->GetFamilyType() );
+ aMetric.SetPitch( xFontMetric->GetPitch() );
+ aMetric.SetWeight( xFontMetric->GetWeight() );
+ aMetric.SetItalic( xFontMetric->GetItalic() );
+ aMetric.SetAlignment( TextAlign::ALIGN_TOP );
+ aMetric.SetWidthType( xFontMetric->GetWidthType() );
+ if ( pFontInstance->mnOwnOrientation )
+ aMetric.SetOrientation( pFontInstance->mnOwnOrientation );
+ else
+ aMetric.SetOrientation( xFontMetric->GetOrientation() );
+
+ // set remaining metric fields
+ aMetric.SetFullstopCenteredFlag( xFontMetric->IsFullstopCentered() );
+ aMetric.SetBulletOffset( xFontMetric->GetBulletOffset() );
+ aMetric.SetAscent( ImplDevicePixelToLogicHeight( xFontMetric->GetAscent() + mnEmphasisAscent ) );
+ aMetric.SetDescent( ImplDevicePixelToLogicHeight( xFontMetric->GetDescent() + mnEmphasisDescent ) );
+ aMetric.SetInternalLeading( ImplDevicePixelToLogicHeight( xFontMetric->GetInternalLeading() + mnEmphasisAscent ) );
+ // OutputDevice has its own external leading function due to #i60945#
+ aMetric.SetExternalLeading( ImplDevicePixelToLogicHeight( GetFontExtLeading() ) );
+ aMetric.SetLineHeight( ImplDevicePixelToLogicHeight( xFontMetric->GetAscent() + xFontMetric->GetDescent() + mnEmphasisAscent + mnEmphasisDescent ) );
+ aMetric.SetSlant( ImplDevicePixelToLogicHeight( xFontMetric->GetSlant() ) );
+
+ // 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);
+}
+
+#if ENABLE_CAIRO_CANVAS
+
+SystemFontData OutputDevice::GetSysFontData(int nFallbacklevel) const
+{
+ SystemFontData aSysFontData;
+
+ if (!mpGraphics)
+ (void) AcquireGraphics();
+
+ if (mpGraphics)
+ aSysFontData = mpGraphics->GetSysFontData(nFallbacklevel);
+
+ return aSysFontData;
+}
+
+#endif // ENABLE_CAIRO_CANVAS
+
+void OutputDevice::ImplGetEmphasisMark( tools::PolyPolygon& rPolyPoly, bool& rPolyLine,
+ tools::Rectangle& rRect1, tools::Rectangle& rRect2,
+ long& rYOff, long& rWidth,
+ FontEmphasisMark eEmphasis,
+ long nHeight )
+{
+ static const PolyFlags aAccentPolyFlags[24] =
+ {
+ PolyFlags::Normal, PolyFlags::Control, PolyFlags::Control,
+ PolyFlags::Normal, PolyFlags::Control, PolyFlags::Control,
+ PolyFlags::Normal, PolyFlags::Control, PolyFlags::Control,
+ PolyFlags::Normal, PolyFlags::Control, PolyFlags::Control,
+ PolyFlags::Normal, PolyFlags::Control, PolyFlags::Control,
+ PolyFlags::Normal, PolyFlags::Control, PolyFlags::Control,
+ PolyFlags::Normal, PolyFlags::Normal, PolyFlags::Control,
+ PolyFlags::Normal, PolyFlags::Control, PolyFlags::Control
+ };
+
+ static const long aAccentPos[48] =
+ {
+ 78, 0,
+ 348, 79,
+ 599, 235,
+ 843, 469,
+ 938, 574,
+ 990, 669,
+ 990, 773,
+ 990, 843,
+ 964, 895,
+ 921, 947,
+ 886, 982,
+ 860, 999,
+ 825, 999,
+ 764, 999,
+ 721, 964,
+ 686, 895,
+ 625, 791,
+ 556, 660,
+ 469, 504,
+ 400, 400,
+ 261, 252,
+ 61, 61,
+ 0, 27,
+ 9, 0
+ };
+
+ rWidth = 0;
+ rYOff = 0;
+ rPolyLine = false;
+
+ if ( !nHeight )
+ return;
+
+ FontEmphasisMark nEmphasisStyle = eEmphasis & FontEmphasisMark::Style;
+ long nDotSize = 0;
+ switch ( nEmphasisStyle )
+ {
+ case FontEmphasisMark::Dot:
+ // Dot has 55% of the height
+ nDotSize = (nHeight*550)/1000;
+ if ( !nDotSize )
+ nDotSize = 1;
+ if ( nDotSize <= 2 )
+ rRect1 = tools::Rectangle( Point(), Size( nDotSize, nDotSize ) );
+ else
+ {
+ long nRad = nDotSize/2;
+ tools::Polygon aPoly( Point( nRad, nRad ), nRad, nRad );
+ rPolyPoly.Insert( aPoly );
+ }
+ rYOff = ((nHeight*250)/1000)/2; // Center to the another EmphasisMarks
+ rWidth = nDotSize;
+ break;
+
+ case FontEmphasisMark::Circle:
+ // Dot has 80% of the height
+ nDotSize = (nHeight*800)/1000;
+ if ( !nDotSize )
+ nDotSize = 1;
+ if ( nDotSize <= 2 )
+ rRect1 = tools::Rectangle( Point(), Size( nDotSize, nDotSize ) );
+ else
+ {
+ long nRad = nDotSize/2;
+ tools::Polygon aPoly( Point( nRad, nRad ), nRad, nRad );
+ rPolyPoly.Insert( aPoly );
+ // BorderWidth is 15%
+ long nBorder = (nDotSize*150)/1000;
+ if ( nBorder <= 1 )
+ rPolyLine = true;
+ else
+ {
+ tools::Polygon aPoly2( Point( nRad, nRad ),
+ nRad-nBorder, nRad-nBorder );
+ rPolyPoly.Insert( aPoly2 );
+ }
+ }
+ rWidth = nDotSize;
+ break;
+
+ case FontEmphasisMark::Disc:
+ // Dot has 80% of the height
+ nDotSize = (nHeight*800)/1000;
+ if ( !nDotSize )
+ nDotSize = 1;
+ if ( nDotSize <= 2 )
+ rRect1 = tools::Rectangle( Point(), Size( nDotSize, nDotSize ) );
+ else
+ {
+ long nRad = nDotSize/2;
+ tools::Polygon aPoly( Point( nRad, nRad ), nRad, nRad );
+ rPolyPoly.Insert( aPoly );
+ }
+ rWidth = nDotSize;
+ break;
+
+ case FontEmphasisMark::Accent:
+ // Dot has 80% of the height
+ nDotSize = (nHeight*800)/1000;
+ if ( !nDotSize )
+ nDotSize = 1;
+ if ( nDotSize <= 2 )
+ {
+ if ( nDotSize == 1 )
+ {
+ rRect1 = tools::Rectangle( Point(), Size( nDotSize, nDotSize ) );
+ rWidth = nDotSize;
+ }
+ else
+ {
+ rRect1 = tools::Rectangle( Point(), Size( 1, 1 ) );
+ rRect2 = tools::Rectangle( Point( 1, 1 ), Size( 1, 1 ) );
+ }
+ }
+ else
+ {
+ tools::Polygon aPoly( SAL_N_ELEMENTS( aAccentPos ) / 2,
+ reinterpret_cast<const Point*>(aAccentPos),
+ aAccentPolyFlags );
+ double dScale = static_cast<double>(nDotSize)/1000.0;
+ aPoly.Scale( dScale, dScale );
+ tools::Polygon aTemp;
+ aPoly.AdaptiveSubdivide( aTemp );
+ tools::Rectangle aBoundRect = aTemp.GetBoundRect();
+ rWidth = aBoundRect.GetWidth();
+ nDotSize = aBoundRect.GetHeight();
+ rPolyPoly.Insert( aTemp );
+ }
+ break;
+ default: break;
+ }
+
+ // calculate position
+ long nOffY = 1+(mnDPIY/300); // one visible pixel space
+ long nSpaceY = nHeight-nDotSize;
+ if ( nSpaceY >= nOffY*2 )
+ rYOff += nOffY;
+ if ( !(eEmphasis & FontEmphasisMark::PosBelow) )
+ rYOff += nDotSize;
+}
+
+FontEmphasisMark OutputDevice::ImplGetEmphasisMarkStyle( const vcl::Font& rFont )
+{
+ FontEmphasisMark nEmphasisMark = rFont.GetEmphasisMark();
+
+ // If no Position is set, then calculate the default position, which
+ // depends on the language
+ if ( !(nEmphasisMark & (FontEmphasisMark::PosAbove | FontEmphasisMark::PosBelow)) )
+ {
+ LanguageType eLang = rFont.GetLanguage();
+ // In Chinese Simplified the EmphasisMarks are below/left
+ if (MsLangId::isSimplifiedChinese(eLang))
+ nEmphasisMark |= FontEmphasisMark::PosBelow;
+ else
+ {
+ eLang = rFont.GetCJKContextLanguage();
+ // In Chinese Simplified the EmphasisMarks are below/left
+ if (MsLangId::isSimplifiedChinese(eLang))
+ nEmphasisMark |= FontEmphasisMark::PosBelow;
+ else
+ nEmphasisMark |= FontEmphasisMark::PosAbove;
+ }
+ }
+
+ return nEmphasisMark;
+}
+
+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 )
+ {
+ mpDeviceFontList.reset();
+ mpDeviceFontSizeList.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 )
+ {
+ pSVData->maGDIData.mxScreenFontList->Clear();
+ vcl::Window * pFrame = pSVData->maFrameData.mpFirstFrame;
+ if ( pFrame )
+ {
+ if ( pFrame->AcquireGraphics() )
+ {
+ OutputDevice *pDevice = pFrame;
+ 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->*pHdl )( bNewFontLists );
+
+ vcl::Window* pSysWin = pFrame->mpWindowImpl->mpFrameData->mpFirstOverlap;
+ while ( pSysWin )
+ {
+ ( pSysWin->*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 )
+{
+ ImplDirectFontSubstitution*& rpSubst = ImplGetSVData()->maGDIData.mpDirectFontSubst;
+ if( !rpSubst )
+ rpSubst = new ImplDirectFontSubstitution;
+ rpSubst->AddFontSubstitute( rFontName, rReplaceFontName, nFlags );
+ ImplGetSVData()->maGDIData.mbFontSubChanged = true;
+}
+
+void ImplDirectFontSubstitution::AddFontSubstitute( const OUString& rFontName,
+ const OUString& rSubstFontName, AddFontSubstituteFlags nFlags )
+{
+ maFontSubstList.emplace_back( rFontName, rSubstFontName, nFlags );
+}
+
+ImplFontSubstEntry::ImplFontSubstEntry( const OUString& rFontName,
+ const OUString& rSubstFontName, AddFontSubstituteFlags nSubstFlags )
+: mnFlags( nSubstFlags )
+{
+ maSearchName = GetEnglishSearchFontName( rFontName );
+ maSearchReplaceName = GetEnglishSearchFontName( rSubstFontName );
+}
+
+void OutputDevice::RemoveFontsSubstitute()
+{
+ ImplDirectFontSubstitution* pSubst = ImplGetSVData()->maGDIData.mpDirectFontSubst;
+ if( pSubst )
+ pSubst->RemoveFontsSubstitute();
+}
+
+void ImplDirectFontSubstitution::RemoveFontsSubstitute()
+{
+ maFontSubstList.clear();
+}
+
+bool ImplDirectFontSubstitution::FindFontSubstitute( OUString& rSubstName,
+ const OUString& rSearchName ) const
+{
+ // TODO: get rid of O(N) searches
+ std::vector<ImplFontSubstEntry>::const_iterator it = std::find_if (
+ maFontSubstList.begin(), maFontSubstList.end(),
+ [&] (const ImplFontSubstEntry& 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 ImplDirectFontSubstitution* pSubst = ImplGetSVData()->maGDIData.mpDirectFontSubst;
+ if( pSubst && pSubst->FindFontSubstitute( aSubstFontName, rFontName ) )
+ {
+ rFontName = aSubstFontName;
+ return;
+ }
+}
+
+//hidpi TODO: This routine has hard-coded font-sizes that break places such as DialControl
+vcl::Font OutputDevice::GetDefaultFont( DefaultFontType nType, LanguageType eLang,
+ GetDefaultFontFlags nFlags, const OutputDevice* pOutDev )
+{
+ if (!pOutDev && !utl::ConfigManager::IsFuzzing()) // default is NULL
+ pOutDev = Application::GetDefaultDevice();
+
+ OUString aSearch;
+ if (!utl::ConfigManager::IsFuzzing())
+ {
+ LanguageTag aLanguageTag(
+ ( eLang == LANGUAGE_NONE || eLang == LANGUAGE_SYSTEM || eLang == LANGUAGE_DONTKNOW ) ?
+ Application::GetSettings().GetUILanguageTag() :
+ LanguageTag( eLang ));
+
+ utl::DefaultFontConfiguration& rDefaults = utl::DefaultFontConfiguration::get();
+ OUString aDefault = rDefaults.getDefaultFont( aLanguageTag, nType );
+
+ if( !aDefault.isEmpty() )
+ aSearch = aDefault;
+ else
+ aSearch = rDefaults.getUserInterfaceFont( aLanguageTag ); // use the UI font as a fallback
+ }
+ else
+ aSearch = "Liberation Serif";
+
+ vcl::Font aFont;
+ aFont.SetPitch( PITCH_VARIABLE );
+
+ switch ( nType )
+ {
+ case DefaultFontType::SANS_UNICODE:
+ case DefaultFontType::UI_SANS:
+ aFont.SetFamily( FAMILY_SWISS );
+ break;
+
+ case DefaultFontType::SANS:
+ case DefaultFontType::LATIN_HEADING:
+ case DefaultFontType::LATIN_SPREADSHEET:
+ case DefaultFontType::LATIN_DISPLAY:
+ aFont.SetFamily( FAMILY_SWISS );
+ break;
+
+ case DefaultFontType::SERIF:
+ case DefaultFontType::LATIN_TEXT:
+ case DefaultFontType::LATIN_PRESENTATION:
+ aFont.SetFamily( FAMILY_ROMAN );
+ break;
+
+ case DefaultFontType::FIXED:
+ case DefaultFontType::LATIN_FIXED:
+ case DefaultFontType::UI_FIXED:
+ aFont.SetPitch( PITCH_FIXED );
+ aFont.SetFamily( FAMILY_MODERN );
+ break;
+
+ case DefaultFontType::SYMBOL:
+ aFont.SetCharSet( RTL_TEXTENCODING_SYMBOL );
+ break;
+
+ case DefaultFontType::CJK_TEXT:
+ case DefaultFontType::CJK_PRESENTATION:
+ case DefaultFontType::CJK_SPREADSHEET:
+ case DefaultFontType::CJK_HEADING:
+ case DefaultFontType::CJK_DISPLAY:
+ aFont.SetFamily( FAMILY_SYSTEM ); // don't care, but don't use font subst config later...
+ break;
+
+ case DefaultFontType::CTL_TEXT:
+ case DefaultFontType::CTL_PRESENTATION:
+ case DefaultFontType::CTL_SPREADSHEET:
+ case DefaultFontType::CTL_HEADING:
+ case DefaultFontType::CTL_DISPLAY:
+ aFont.SetFamily( FAMILY_SYSTEM ); // don't care, but don't use font subst config later...
+ break;
+ }
+
+ if ( !aSearch.isEmpty() )
+ {
+ aFont.SetFontHeight( 12 ); // corresponds to nDefaultHeight
+ aFont.SetWeight( WEIGHT_NORMAL );
+ aFont.SetLanguage( eLang );
+
+ if ( aFont.GetCharSet() == RTL_TEXTENCODING_DONTKNOW )
+ aFont.SetCharSet( osl_getThreadTextEncoding() );
+
+ // Should we only return available fonts on the given device
+ if ( pOutDev )
+ {
+ pOutDev->ImplInitFontList();
+
+ // Search Font in the FontList
+ OUString aName;
+ sal_Int32 nIndex = 0;
+ do
+ {
+ 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() )
+ {
+ if( mpGraphics || AcquireGraphics() )
+ {
+ 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;
+
+ 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();
+
+ // get correct font list on the PDF writer if necessary
+ if (GetOutDevType() == OUTDEV_PDF)
+ {
+ const ImplSVData* pSVData = ImplGetSVData();
+ if( mxFontCollection == pSVData->maGDIData.mxScreenFontList
+ || mxFontCache == pSVData->maGDIData.mxScreenFontCache )
+ const_cast<OutputDevice&>(*this).ImplUpdateFontData();
+ }
+
+ if ( !mbNewFont )
+ return true;
+
+ // we need a graphics
+ if ( !mpGraphics && !AcquireGraphics() )
+ {
+ SAL_WARN("vcl.gdi", "OutputDevice::ImplNewFont(): no Graphics, no Font");
+ return false;
+ }
+
+ ImplInitFontList();
+
+ // convert to pixel height
+ // TODO: replace integer based aSize completely with subpixel accurate type
+ float fExactHeight = ImplFloatLogicHeightToDevicePixel( static_cast<float>(maFont.GetFontHeight()) );
+ Size aSize = ImplLogicToDevicePixel( maFont.GetFontSize() );
+ if ( !aSize.Height() )
+ {
+ // use default pixel height only when logical height is zero
+ if ( maFont.GetFontSize().Height() )
+ aSize.setHeight( 1 );
+ else
+ aSize.setHeight( (12*mnDPIY)/72 );
+ fExactHeight = static_cast<float>(aSize.Height());
+ }
+
+ // select the default width only when logical width is zero
+ if( (0 == aSize.Width()) && (0 != maFont.GetFontSize().Width()) )
+ aSize.setWidth( 1 );
+
+ // decide if antialiasing is appropriate
+ bool bNonAntialiased(GetAntialiasing() & AntialiasingFlags::DisableText);
+ if (!utl::ConfigManager::IsFuzzing())
+ {
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ bNonAntialiased |= bool(rStyleSettings.GetDisplayOptions() & DisplayOptions::AADisable);
+ bNonAntialiased |= (int(rStyleSettings.GetAntialiasingMinPixelHeight()) > maFont.GetFontSize().Height());
+ }
+
+ // get font entry
+ rtl::Reference<LogicalFontInstance> pOldFontInstance = mpFontInstance;
+ mpFontInstance = mxFontCache->GetFontInstance(mxFontCollection.get(), maFont, aSize, fExactHeight, bNonAntialiased);
+ const bool bNewFontInstance = pOldFontInstance.get() != mpFontInstance.get();
+ pOldFontInstance.clear();
+
+ LogicalFontInstance* pFontInstance = mpFontInstance.get();
+
+ if (!pFontInstance)
+ {
+ SAL_WARN("vcl.gdi", "OutputDevice::ImplNewFont(): no LogicalFontInstance, no Font");
+ return false;
+ }
+
+ // mark when lower layers need to get involved
+ mbNewFont = false;
+ if( bNewFontInstance )
+ mbInitFont = true;
+
+ // select font when it has not been initialized yet
+ if (!pFontInstance->mbInit && InitFont())
+ {
+ // get metric data from device layers
+ pFontInstance->mbInit = true;
+
+ pFontInstance->mxFontMetric->SetOrientation( sal::static_int_cast<short>(mpFontInstance->GetFontSelectPattern().mnOrientation) );
+ mpGraphics->GetFontMetric( pFontInstance->mxFontMetric, 0 );
+
+ pFontInstance->mxFontMetric->ImplInitTextLineSize( this );
+ pFontInstance->mxFontMetric->ImplInitAboveTextLineSize();
+ pFontInstance->mxFontMetric->ImplInitFlags( this );
+
+ pFontInstance->mnLineHeight = pFontInstance->mxFontMetric->GetAscent() + pFontInstance->mxFontMetric->GetDescent();
+
+ SetFontOrientation( pFontInstance );
+ }
+
+ // calculate EmphasisArea
+ mnEmphasisAscent = 0;
+ mnEmphasisDescent = 0;
+ if ( maFont.GetEmphasisMark() & FontEmphasisMark::Style )
+ {
+ FontEmphasisMark nEmphasisMark = ImplGetEmphasisMarkStyle( maFont );
+ 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() )
+ {
+ int nOrigWidth = pFontInstance->mxFontMetric->GetWidth();
+ float fStretch = static_cast<float>(maMapRes.mnMapScNumX) * maMapRes.mnMapScDenomY;
+ fStretch /= static_cast<float>(maMapRes.mnMapScNumY) * maMapRes.mnMapScDenomX;
+ int nNewWidth = static_cast<int>(nOrigWidth * fStretch + 0.5);
+ if( (nNewWidth != nOrigWidth) && (nNewWidth != 0) )
+ {
+ Size aOrigSize = maFont.GetFontSize();
+ const_cast<vcl::Font&>(maFont).SetFontSize( Size( nNewWidth, aSize.Height() ) );
+ mbMap = false;
+ mbNewFont = true;
+ bRet = ImplNewFont(); // recurse once using stretched width
+ mbMap = true;
+ const_cast<vcl::Font&>(maFont).SetFontSize( aOrigSize );
+ }
+ }
+
+ return bRet;
+}
+
+void OutputDevice::SetFontOrientation( LogicalFontInstance* const pFontInstance ) const
+{
+ if( pFontInstance->GetFontSelectPattern().mnOrientation && !pFontInstance->mxFontMetric->GetOrientation() )
+ {
+ pFontInstance->mnOwnOrientation = sal::static_int_cast<short>(pFontInstance->GetFontSelectPattern().mnOrientation);
+ pFontInstance->mnOrientation = pFontInstance->mnOwnOrientation;
+ }
+ else
+ {
+ pFontInstance->mnOrientation = pFontInstance->mxFontMetric->GetOrientation();
+ }
+}
+
+void OutputDevice::ImplDrawEmphasisMark( long nBaseX, long nX, long nY,
+ const tools::PolyPolygon& rPolyPoly, bool bPolyLine,
+ const tools::Rectangle& rRect1, const tools::Rectangle& rRect2 )
+{
+ if( IsRTLEnabled() )
+ nX = nBaseX - (nX - nBaseX - 1);
+
+ nX -= mnOutOffX;
+ nY -= mnOutOffY;
+
+ if ( rPolyPoly.Count() )
+ {
+ if ( bPolyLine )
+ {
+ tools::Polygon aPoly = rPolyPoly.GetObject( 0 );
+ aPoly.Move( nX, nY );
+ DrawPolyLine( aPoly );
+ }
+ else
+ {
+ tools::PolyPolygon aPolyPoly = rPolyPoly;
+ aPolyPoly.Move( nX, nY );
+ DrawPolyPolygon( aPolyPoly );
+ }
+ }
+
+ if ( !rRect1.IsEmpty() )
+ {
+ tools::Rectangle aRect( Point( nX+rRect1.Left(),
+ nY+rRect1.Top() ), rRect1.GetSize() );
+ DrawRect( aRect );
+ }
+
+ if ( !rRect2.IsEmpty() )
+ {
+ tools::Rectangle aRect( Point( nX+rRect2.Left(),
+ nY+rRect2.Top() ), rRect2.GetSize() );
+
+ DrawRect( aRect );
+ }
+}
+
+void OutputDevice::ImplDrawEmphasisMarks( SalLayout& rSalLayout )
+{
+ Color aOldLineColor = GetLineColor();
+ Color aOldFillColor = GetFillColor();
+ bool bOldMap = mbMap;
+ GDIMetaFile* pOldMetaFile = mpMetaFile;
+ mpMetaFile = nullptr;
+ EnableMapMode( false );
+
+ FontEmphasisMark nEmphasisMark = ImplGetEmphasisMarkStyle( maFont );
+ tools::PolyPolygon aPolyPoly;
+ tools::Rectangle aRect1;
+ tools::Rectangle aRect2;
+ long nEmphasisYOff;
+ long nEmphasisWidth;
+ long nEmphasisHeight;
+ bool bPolyLine;
+
+ if ( nEmphasisMark & FontEmphasisMark::PosBelow )
+ nEmphasisHeight = mnEmphasisDescent;
+ else
+ nEmphasisHeight = mnEmphasisAscent;
+
+ ImplGetEmphasisMark( aPolyPoly, bPolyLine,
+ aRect1, aRect2,
+ nEmphasisYOff, nEmphasisWidth,
+ nEmphasisMark,
+ nEmphasisHeight );
+
+ if ( bPolyLine )
+ {
+ SetLineColor( GetTextColor() );
+ SetFillColor();
+ }
+ else
+ {
+ SetLineColor();
+ SetFillColor( GetTextColor() );
+ }
+
+ Point aOffset(0,0);
+
+ if ( nEmphasisMark & FontEmphasisMark::PosBelow )
+ aOffset.AdjustY(mpFontInstance->mxFontMetric->GetDescent() + nEmphasisYOff );
+ else
+ aOffset.AdjustY( -(mpFontInstance->mxFontMetric->GetAscent() + nEmphasisYOff) );
+
+ long nEmphasisWidth2 = nEmphasisWidth / 2;
+ long nEmphasisHeight2 = nEmphasisHeight / 2;
+ aOffset += Point( nEmphasisWidth2, nEmphasisHeight2 );
+
+ Point aOutPoint;
+ tools::Rectangle aRectangle;
+ const GlyphItem* pGlyph;
+ int nStart = 0;
+ while (rSalLayout.GetNextGlyph(&pGlyph, aOutPoint, nStart))
+ {
+ if (!pGlyph->GetGlyphBoundRect(aRectangle))
+ continue;
+
+ if (!pGlyph->IsSpacing())
+ {
+ Point aAdjPoint = aOffset;
+ aAdjPoint.AdjustX(aRectangle.Left() + (aRectangle.GetWidth() - nEmphasisWidth) / 2 );
+ if ( mpFontInstance->mnOrientation )
+ {
+ Point aOriginPt(0, 0);
+ aOriginPt.RotateAround( aAdjPoint, mpFontInstance->mnOrientation );
+ }
+ aOutPoint += aAdjPoint;
+ aOutPoint -= Point( nEmphasisWidth2, nEmphasisHeight2 );
+ ImplDrawEmphasisMark( rSalLayout.DrawBase().X(),
+ aOutPoint.X(), aOutPoint.Y(),
+ aPolyPoly, bPolyLine, aRect1, aRect2 );
+ }
+ }
+
+ SetLineColor( aOldLineColor );
+ SetFillColor( aOldFillColor );
+ EnableMapMode( bOldMap );
+ mpMetaFile = pOldMetaFile;
+}
+
+std::unique_ptr<SalLayout> OutputDevice::getFallbackLayout(
+ LogicalFontInstance* pLogicalFont, int nFallbackLevel,
+ ImplLayoutArgs& rLayoutArgs) 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, nullptr))
+ {
+ // there is no need for a font that couldn't resolve anything
+ return nullptr;
+ }
+
+ return pFallback;
+}
+
+std::unique_ptr<SalLayout> OutputDevice::ImplGlyphFallbackLayout( std::unique_ptr<SalLayout> pSalLayout, ImplLayoutArgs& rLayoutArgs ) 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();
+ rLayoutArgs.mnFlags |= SalLayoutFlags::ForFallback;
+
+ // get list of code units that need glyph fallback
+ int nCharPos = -1;
+ bool bRTL = false;
+ OUStringBuffer aMissingCodeBuf(512);
+ while (rLayoutArgs.GetNextPos( &nCharPos, &bRTL))
+ aMissingCodeBuf.append(rLayoutArgs.mrStr[nCharPos]);
+ rLayoutArgs.ResetPos();
+ OUString aMissingCodes = aMissingCodeBuf.makeStringAndClear();
+
+ FontSelectPattern aFontSelData(mpFontInstance->GetFontSelectPattern());
+
+ // try if fallback fonts support the missing code units
+ for( int nFallbackLevel = 1; nFallbackLevel < MAX_FALLBACK; ++nFallbackLevel )
+ {
+ // find a font family suited for glyph fallback
+ // GetGlyphFallbackFont() needs a valid FontInstance
+ // if the system-specific glyph fallback is active
+ rtl::Reference<LogicalFontInstance> pFallbackFont = mxFontCache->GetGlyphFallbackFont( mxFontCollection.get(),
+ aFontSelData, mpFontInstance.get(), nFallbackLevel, aMissingCodes );
+ if( !pFallbackFont )
+ break;
+
+ if( nFallbackLevel < MAX_FALLBACK-1)
+ {
+ // ignore fallback font if it is the same as the original font
+ // unless we are looking for a substitution for 0x202F, in which
+ // case we'll just use a normal space
+ if( mpFontInstance->GetFontFace() == pFallbackFont->GetFontFace() &&
+ aMissingCodes.indexOf(0x202F) == -1 )
+ {
+ continue;
+ }
+ }
+
+ // create and add glyph fallback layout to multilayout
+ std::unique_ptr<SalLayout> pFallback = getFallbackLayout(pFallbackFont.get(),
+ nFallbackLevel, rLayoutArgs);
+ 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);
+ }
+
+ // break when this fallback was sufficient
+ if( !rLayoutArgs.PrepareFallback() )
+ break;
+ }
+
+ if( pMultiSalLayout && pMultiSalLayout->LayoutText( rLayoutArgs, nullptr ) )
+ pSalLayout = std::move(pMultiSalLayout);
+
+ // restore orig font settings
+ pSalLayout->InitFont();
+ rLayoutArgs.maRuns = aLayoutRuns;
+
+ return pSalLayout;
+}
+
+long OutputDevice::GetMinKashida() const
+{
+ if (!ImplNewFont())
+ return 0;
+
+ return ImplDevicePixelToLogicWidth( mpFontInstance->mxFontMetric->GetMinKashida() );
+}
+
+sal_Int32 OutputDevice::ValidateKashidas ( const OUString& rTxt,
+ sal_Int32 nIdx, sal_Int32 nLen,
+ sal_Int32 nKashCount,
+ const sal_Int32* pKashidaPos,
+ sal_Int32* pKashidaPosDropped ) const
+{
+ // do layout
+ std::unique_ptr<SalLayout> pSalLayout = ImplLayout( rTxt, nIdx, nLen );
+ if( !pSalLayout )
+ return 0;
+ sal_Int32 nDropped = 0;
+ for( int i = 0; i < nKashCount; ++i )
+ {
+ if( !pSalLayout->IsKashidaPosValid( pKashidaPos[ i ] ))
+ {
+ pKashidaPosDropped[ nDropped ] = pKashidaPos [ i ];
+ ++nDropped;
+ }
+ }
+ return nDropped;
+}
+
+bool OutputDevice::GetGlyphBoundRects( const Point& rOrigin, const OUString& rStr,
+ int nIndex, int nLen, MetricVector& rVector )
+{
+ rVector.clear();
+
+ if( nIndex >= rStr.getLength() )
+ return false;
+
+ if( nLen < 0 || nIndex + nLen >= rStr.getLength() )
+ {
+ nLen = rStr.getLength() - nIndex;
+ }
+
+ tools::Rectangle aRect;
+ for( int i = 0; i < nLen; i++ )
+ {
+ if( !GetTextBoundRect( aRect, rStr, nIndex, nIndex + i, 1 ) )
+ break;
+ aRect.Move( rOrigin.X(), rOrigin.Y() );
+ rVector.push_back( aRect );
+ }
+
+ return (nLen == static_cast<int>(rVector.size()));
+}
+
+sal_Int32 OutputDevice::HasGlyphs( const vcl::Font& rTempFont, const OUString& rStr,
+ sal_Int32 nIndex, sal_Int32 nLen ) const
+{
+ if( nIndex >= rStr.getLength() )
+ return nIndex;
+ sal_Int32 nEnd;
+ if( nLen == -1 )
+ nEnd = rStr.getLength();
+ else
+ nEnd = std::min( rStr.getLength(), nIndex + nLen );
+
+ SAL_WARN_IF( nIndex >= nEnd, "vcl.gdi", "StartPos >= EndPos?" );
+ SAL_WARN_IF( nEnd > rStr.getLength(), "vcl.gdi", "String too short" );
+
+ // to get the map temporarily set font
+ const vcl::Font aOrigFont = GetFont();
+ const_cast<OutputDevice&>(*this).SetFont( rTempFont );
+ FontCharMapRef xFontCharMap;
+ bool bRet = GetFontCharMap( xFontCharMap );
+ const_cast<OutputDevice&>(*this).SetFont( aOrigFont );
+
+ // if fontmap is unknown assume it doesn't have the glyphs
+ if( !bRet )
+ return nIndex;
+
+ for( sal_Int32 i = nIndex; nIndex < nEnd; ++i, ++nIndex )
+ if( ! xFontCharMap->HasChar( rStr[i] ) )
+ return nIndex;
+
+ return -1;
+}
+
+void OutputDevice::ReleaseFontCache() { mxFontCache.reset(); }
+
+void OutputDevice::ReleaseFontCollection() { mxFontCollection.reset(); }
+
+void OutputDevice::SetFontCollectionFromSVData()
+{
+ mxFontCollection = ImplGetSVData()->maGDIData.mxScreenFontList->Clone();
+}
+
+void OutputDevice::ResetNewFontCache()
+{
+ mxFontCache = std::make_shared<ImplFontCache>();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/gradient.cxx b/vcl/source/outdev/gradient.cxx
new file mode 100644
index 000000000..2e8406714
--- /dev/null
+++ b/vcl/source/outdev/gradient.cxx
@@ -0,0 +1,1045 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <memory>
+#include <cassert>
+
+#include <tools/poly.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/gradient.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/window.hxx>
+
+#include <salgdi.hxx>
+
+#define GRADIENT_DEFAULT_STEPCOUNT 0
+
+void OutputDevice::DrawGradient( const tools::Rectangle& rRect,
+ const Gradient& rGradient )
+{
+ assert(!is_double_buffered_window());
+
+ // Convert rectangle to a tools::PolyPolygon by first converting to a Polygon
+ tools::Polygon aPolygon ( rRect );
+ tools::PolyPolygon aPolyPoly ( aPolygon );
+
+ DrawGradient ( aPolyPoly, rGradient );
+}
+
+void OutputDevice::DrawGradient( const tools::PolyPolygon& rPolyPoly,
+ const Gradient& rGradient )
+{
+ assert(!is_double_buffered_window());
+
+ if (mbInitClipRegion)
+ InitClipRegion();
+ // don't return on mbOutputClipped here, as we may need to draw the clipped metafile, even if the output is clipped
+
+ if ( rPolyPoly.Count() && rPolyPoly[ 0 ].GetSize() )
+ {
+ if ( mnDrawMode & ( DrawModeFlags::BlackGradient | DrawModeFlags::WhiteGradient | DrawModeFlags::SettingsGradient) )
+ {
+ Color aColor = GetSingleColorGradientFill();
+
+ Push( PushFlags::LINECOLOR | PushFlags::FILLCOLOR );
+ SetLineColor( aColor );
+ SetFillColor( aColor );
+ DrawPolyPolygon( rPolyPoly );
+ Pop();
+ return;
+ }
+
+ Gradient aGradient( rGradient );
+
+ if ( mnDrawMode & DrawModeFlags::GrayGradient )
+ {
+ SetGrayscaleColors( aGradient );
+ }
+
+ DrawGradientToMetafile( rPolyPoly, rGradient );
+
+ if( !IsDeviceOutputNecessary() || ImplIsRecordLayout() )
+ return;
+
+ // Clip and then draw the gradient
+ if( !tools::Rectangle( PixelToLogic( Point() ), GetOutputSize() ).IsEmpty() )
+ {
+ const tools::Rectangle aBoundRect( rPolyPoly.GetBoundRect() );
+
+ // convert rectangle to pixels
+ tools::Rectangle aRect( ImplLogicToDevicePixel( aBoundRect ) );
+ aRect.Justify();
+
+ // do nothing if the rectangle is empty
+ if ( !aRect.IsEmpty() )
+ {
+ tools::PolyPolygon aClixPolyPoly( ImplLogicToDevicePixel( rPolyPoly ) );
+ bool bDrawn = false;
+
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+
+ // secure clip region
+ Push( PushFlags::CLIPREGION );
+ IntersectClipRegion( aBoundRect );
+
+ if (mbInitClipRegion)
+ InitClipRegion();
+
+ // try to draw gradient natively
+ bDrawn = mpGraphics->DrawGradient( aClixPolyPoly, aGradient );
+
+ if (!bDrawn && !mbOutputClipped)
+ {
+ // draw gradients without border
+ if( mbLineColor || mbInitLineColor )
+ {
+ mpGraphics->SetLineColor();
+ mbInitLineColor = true;
+ }
+
+ mbInitFillColor = true;
+
+ // calculate step count if necessary
+ if ( !aGradient.GetSteps() )
+ aGradient.SetSteps( GRADIENT_DEFAULT_STEPCOUNT );
+
+ if ( rPolyPoly.IsRect() )
+ {
+ // because we draw with no border line, we have to expand gradient
+ // rect to avoid missing lines on the right and bottom edge
+ aRect.AdjustLeft( -1 );
+ aRect.AdjustTop( -1 );
+ aRect.AdjustRight( 1 );
+ aRect.AdjustBottom( 1 );
+ }
+
+ // if the clipping polypolygon is a rectangle, then it's the same size as the bounding of the
+ // polypolygon, so pass in a NULL for the clipping parameter
+ if( aGradient.GetStyle() == GradientStyle::Linear || rGradient.GetStyle() == GradientStyle::Axial )
+ DrawLinearGradient( aRect, aGradient, aClixPolyPoly.IsRect() ? nullptr : &aClixPolyPoly );
+ else
+ DrawComplexGradient( aRect, aGradient, aClixPolyPoly.IsRect() ? nullptr : &aClixPolyPoly );
+ }
+
+ Pop();
+ }
+ }
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawPolyPolygon( rPolyPoly );
+}
+
+void OutputDevice::ClipAndDrawGradientMetafile ( const Gradient &rGradient, const tools::PolyPolygon &rPolyPoly )
+{
+ const tools::Rectangle aBoundRect( rPolyPoly.GetBoundRect() );
+ const bool bOldOutput = IsOutputEnabled();
+
+ EnableOutput( false );
+ Push( PushFlags::RASTEROP );
+ SetRasterOp( RasterOp::Xor );
+ DrawGradient( aBoundRect, rGradient );
+ SetFillColor( COL_BLACK );
+ SetRasterOp( RasterOp::N0 );
+ DrawPolyPolygon( rPolyPoly );
+ SetRasterOp( RasterOp::Xor );
+ DrawGradient( aBoundRect, rGradient );
+ Pop();
+ EnableOutput( bOldOutput );
+}
+
+void OutputDevice::DrawGradientToMetafile ( const tools::PolyPolygon& rPolyPoly,
+ const Gradient& rGradient )
+{
+ assert(!is_double_buffered_window());
+
+ if ( !mpMetaFile )
+ return;
+
+ if ( rPolyPoly.Count() && rPolyPoly[ 0 ].GetSize() )
+ {
+ Gradient aGradient( rGradient );
+
+ if ( mnDrawMode & DrawModeFlags::GrayGradient )
+ {
+ SetGrayscaleColors( aGradient );
+ }
+
+ const tools::Rectangle aBoundRect( rPolyPoly.GetBoundRect() );
+
+ if ( rPolyPoly.IsRect() )
+ {
+ mpMetaFile->AddAction( new MetaGradientAction( aBoundRect, aGradient ) );
+ }
+ else
+ {
+ mpMetaFile->AddAction( new MetaCommentAction( "XGRAD_SEQ_BEGIN" ) );
+ mpMetaFile->AddAction( new MetaGradientExAction( rPolyPoly, rGradient ) );
+
+ ClipAndDrawGradientMetafile ( rGradient, rPolyPoly );
+
+ mpMetaFile->AddAction( new MetaCommentAction( "XGRAD_SEQ_END" ) );
+ }
+
+ if( !IsDeviceOutputNecessary() || ImplIsRecordLayout() )
+ return;
+
+ // Clip and then draw the gradient
+ if( !tools::Rectangle( PixelToLogic( Point() ), GetOutputSize() ).IsEmpty() )
+ {
+ // convert rectangle to pixels
+ tools::Rectangle aRect( ImplLogicToDevicePixel( aBoundRect ) );
+ aRect.Justify();
+
+ // do nothing if the rectangle is empty
+ if ( !aRect.IsEmpty() )
+ {
+ if( !mbOutputClipped )
+ {
+ // calculate step count if necessary
+ if ( !aGradient.GetSteps() )
+ aGradient.SetSteps( GRADIENT_DEFAULT_STEPCOUNT );
+
+ if ( rPolyPoly.IsRect() )
+ {
+ // because we draw with no border line, we have to expand gradient
+ // rect to avoid missing lines on the right and bottom edge
+ aRect.AdjustLeft( -1 );
+ aRect.AdjustTop( -1 );
+ aRect.AdjustRight( 1 );
+ aRect.AdjustBottom( 1 );
+ }
+
+ // if the clipping polypolygon is a rectangle, then it's the same size as the bounding of the
+ // polypolygon, so pass in a NULL for the clipping parameter
+ if( aGradient.GetStyle() == GradientStyle::Linear || rGradient.GetStyle() == GradientStyle::Axial )
+ DrawLinearGradientToMetafile( aRect, aGradient );
+ else
+ DrawComplexGradientToMetafile( aRect, aGradient );
+ }
+ }
+ }
+ }
+}
+
+namespace
+{
+ sal_uInt8 GetGradientColorValue( 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;
+ sal_uInt16 nAngle = rGradient.GetAngle() % 3600;
+
+ rGradient.GetBoundRect( rRect, aRect, aCenter );
+
+ bool bLinear = (rGradient.GetStyle() == GradientStyle::Linear);
+ double fBorder = rGradient.GetBorder() * aRect.GetHeight() / 100.0;
+ if ( !bLinear )
+ {
+ fBorder /= 2.0;
+ }
+ tools::Rectangle aMirrorRect = aRect; // used in style axial
+ aMirrorRect.SetTop( ( aRect.Top() + aRect.Bottom() ) / 2 );
+ if ( !bLinear )
+ {
+ aRect.SetBottom( aMirrorRect.Top() );
+ }
+
+ // colour-intensities of start- and finish; change if needed
+ long nFactor;
+ Color aStartCol = rGradient.GetStartColor();
+ Color aEndCol = rGradient.GetEndColor();
+ long nStartRed = aStartCol.GetRed();
+ long nStartGreen = aStartCol.GetGreen();
+ long nStartBlue = aStartCol.GetBlue();
+ long nEndRed = aEndCol.GetRed();
+ long nEndGreen = aEndCol.GetGreen();
+ 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)
+ {
+ long nTempColor = nStartRed;
+ nStartRed = nEndRed;
+ nEndRed = nTempColor;
+ nTempColor = nStartGreen;
+ nStartGreen = nEndGreen;
+ nEndGreen = nTempColor;
+ nTempColor = nStartBlue;
+ nStartBlue = nEndBlue;
+ nEndBlue = nTempColor;
+ }
+
+ sal_uInt8 nRed;
+ sal_uInt8 nGreen;
+ sal_uInt8 nBlue;
+
+ // Create border
+ tools::Rectangle aBorderRect = aRect;
+ tools::Polygon aPoly( 4 );
+ if (fBorder > 0.0)
+ {
+ nRed = static_cast<sal_uInt8>(nStartRed);
+ nGreen = static_cast<sal_uInt8>(nStartGreen);
+ nBlue = static_cast<sal_uInt8>(nStartBlue);
+
+ mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) );
+
+ aBorderRect.SetBottom( static_cast<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<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
+ long nStepCount = GetGradientSteps( rGradient, aRect, false/*bMtf*/ );
+
+ // minimal three steps and maximal as max color steps
+ long nAbsRedSteps = std::abs( nEndRed - nStartRed );
+ long nAbsGreenSteps = std::abs( nEndGreen - nStartGreen );
+ long nAbsBlueSteps = std::abs( nEndBlue - nStartBlue );
+ long nMaxColorSteps = std::max( nAbsRedSteps , nAbsGreenSteps );
+ nMaxColorSteps = std::max( nMaxColorSteps, nAbsBlueSteps );
+ 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 ( 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<long>(fTempColor));
+ fTempColor = static_cast<double>(nStartGreen) * (1.0-fAlpha) + static_cast<double>(nEndGreen) * fAlpha;
+ nGreen = GetGradientColorValue(static_cast<long>(fTempColor));
+ fTempColor = static_cast<double>(nStartBlue) * (1.0-fAlpha) + static_cast<double>(nEndBlue) * fAlpha;
+ nBlue = GetGradientColorValue(static_cast<long>(fTempColor));
+
+ mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) );
+
+ // Polygon for this color step
+ aRect.SetTop( static_cast<long>( fGradientLine + static_cast<double>(i) * fScanInc ) );
+ aRect.SetBottom( static_cast<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<long>( fMirrorGradientLine - static_cast<double>(i) * fScanInc ) );
+ aMirrorRect.SetTop( static_cast<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<long>( fGradientLine + static_cast<double>(nSteps) * fScanInc ) );
+ aRect.SetBottom( static_cast<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
+{
+ const vcl::Window *pWindow = dynamic_cast<const vcl::Window*>(this);
+ return pWindow && pWindow->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::unique_ptr<tools::PolyPolygon> xPolyPoly;
+ tools::Rectangle aRect;
+ Point aCenter;
+ Color aStartCol( rGradient.GetStartColor() );
+ Color aEndCol( rGradient.GetEndColor() );
+ long nStartRed = ( static_cast<long>(aStartCol.GetRed()) * rGradient.GetStartIntensity() ) / 100;
+ long nStartGreen = ( static_cast<long>(aStartCol.GetGreen()) * rGradient.GetStartIntensity() ) / 100;
+ long nStartBlue = ( static_cast<long>(aStartCol.GetBlue()) * rGradient.GetStartIntensity() ) / 100;
+ long nEndRed = ( static_cast<long>(aEndCol.GetRed()) * rGradient.GetEndIntensity() ) / 100;
+ long nEndGreen = ( static_cast<long>(aEndCol.GetGreen()) * rGradient.GetEndIntensity() ) / 100;
+ long nEndBlue = ( static_cast<long>(aEndCol.GetBlue()) * rGradient.GetEndIntensity() ) / 100;
+ long nRedSteps = nEndRed - nStartRed;
+ long nGreenSteps = nEndGreen - nStartGreen;
+ long nBlueSteps = nEndBlue - nStartBlue;
+ sal_uInt16 nAngle = rGradient.GetAngle() % 3600;
+
+ rGradient.GetBoundRect( rRect, aRect, aCenter );
+
+ if ( UsePolyPolygonForComplexGradient() )
+ xPolyPoly.reset(new tools::PolyPolygon( 2 ));
+
+ long nStepCount = GetGradientSteps( rGradient, rRect, false/*bMtf*/, true/*bComplex*/ );
+
+ // at least three steps and at most the number of colour differences
+ long nSteps = std::max( nStepCount, 2L );
+ long nCalcSteps = std::abs( nRedSteps );
+ long nTempSteps = std::abs( nGreenSteps );
+ if ( nTempSteps > nCalcSteps )
+ nCalcSteps = nTempSteps;
+ nTempSteps = std::abs( nBlueSteps );
+ if ( nTempSteps > nCalcSteps )
+ nCalcSteps = nTempSteps;
+ if ( nCalcSteps < nSteps )
+ nSteps = nCalcSteps;
+ if ( !nSteps )
+ nSteps = 1;
+
+ // determine output limits and stepsizes for all directions
+ tools::Polygon aPoly;
+ double fScanLeft = aRect.Left();
+ double fScanTop = aRect.Top();
+ double fScanRight = aRect.Right();
+ double fScanBottom = aRect.Bottom();
+ double fScanIncX = static_cast<double>(aRect.GetWidth()) / static_cast<double>(nSteps) * 0.5;
+ double fScanIncY = static_cast<double>(aRect.GetHeight()) / static_cast<double>(nSteps) * 0.5;
+
+ // all gradients are rendered as nested rectangles which shrink
+ // equally in each dimension - except for 'square' gradients
+ // which shrink to a central vertex but are not per-se square.
+ if( rGradient.GetStyle() != GradientStyle::Square )
+ {
+ fScanIncY = std::min( fScanIncY, fScanIncX );
+ fScanIncX = fScanIncY;
+ }
+ sal_uInt8 nRed = static_cast<sal_uInt8>(nStartRed), nGreen = static_cast<sal_uInt8>(nStartGreen), nBlue = static_cast<sal_uInt8>(nStartBlue);
+ bool bPaintLastPolygon( false ); // #107349# Paint last polygon only if loop has generated any output
+
+ mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) );
+
+ if( xPolyPoly )
+ {
+ aPoly = rRect;
+ xPolyPoly->Insert( aPoly );
+ xPolyPoly->Insert( aPoly );
+ }
+ else
+ {
+ // extend rect, to avoid missing bounding line
+ tools::Rectangle aExtRect( rRect );
+
+ aExtRect.AdjustLeft( -1 );
+ aExtRect.AdjustTop( -1 );
+ aExtRect.AdjustRight(1 );
+ aExtRect.AdjustBottom(1 );
+
+ aPoly = aExtRect;
+ ImplDrawPolygon( aPoly, pClixPolyPoly );
+ }
+
+ // loop to output Polygon/PolyPolygon sequentially
+ for( long i = 1; i < nSteps; i++ )
+ {
+ // calculate new Polygon
+ fScanLeft += fScanIncX;
+ aRect.SetLeft( static_cast<long>( fScanLeft ) );
+ fScanTop += fScanIncY;
+ aRect.SetTop( static_cast<long>( fScanTop ) );
+ fScanRight -= fScanIncX;
+ aRect.SetRight( static_cast<long>( fScanRight ) );
+ fScanBottom -= fScanIncY;
+ aRect.SetBottom( static_cast<long>( fScanBottom ) );
+
+ if( ( aRect.GetWidth() < 2 ) || ( aRect.GetHeight() < 2 ) )
+ break;
+
+ if( rGradient.GetStyle() == GradientStyle::Radial || rGradient.GetStyle() == GradientStyle::Elliptical )
+ aPoly = tools::Polygon( aRect.Center(), aRect.GetWidth() >> 1, aRect.GetHeight() >> 1 );
+ else
+ aPoly = tools::Polygon( aRect );
+
+ aPoly.Rotate( aCenter, nAngle );
+
+ // adapt colour accordingly
+ const 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 )
+ {
+ const tools::Polygon& rPoly = xPolyPoly->GetObject( 1 );
+
+ if( !rPoly.GetBoundRect().IsEmpty() )
+ {
+ // #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 );
+ }
+ }
+}
+
+void OutputDevice::DrawLinearGradientToMetafile( const tools::Rectangle& rRect,
+ const Gradient& rGradient )
+{
+ assert(!is_double_buffered_window());
+
+ // get BoundRect of rotated rectangle
+ tools::Rectangle aRect;
+ Point aCenter;
+ sal_uInt16 nAngle = rGradient.GetAngle() % 3600;
+
+ rGradient.GetBoundRect( rRect, aRect, aCenter );
+
+ bool bLinear = (rGradient.GetStyle() == GradientStyle::Linear);
+ double fBorder = rGradient.GetBorder() * aRect.GetHeight() / 100.0;
+ if ( !bLinear )
+ {
+ fBorder /= 2.0;
+ }
+ tools::Rectangle aMirrorRect = aRect; // used in style axial
+ aMirrorRect.SetTop( ( aRect.Top() + aRect.Bottom() ) / 2 );
+ if ( !bLinear )
+ {
+ aRect.SetBottom( aMirrorRect.Top() );
+ }
+
+ // colour-intensities of start- and finish; change if needed
+ long nFactor;
+ Color aStartCol = rGradient.GetStartColor();
+ Color aEndCol = rGradient.GetEndColor();
+ long nStartRed = aStartCol.GetRed();
+ long nStartGreen = aStartCol.GetGreen();
+ long nStartBlue = aStartCol.GetBlue();
+ long nEndRed = aEndCol.GetRed();
+ long nEndGreen = aEndCol.GetGreen();
+ 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)
+ {
+ long nTempColor = nStartRed;
+ nStartRed = nEndRed;
+ nEndRed = nTempColor;
+ nTempColor = nStartGreen;
+ nStartGreen = nEndGreen;
+ nEndGreen = nTempColor;
+ nTempColor = nStartBlue;
+ nStartBlue = nEndBlue;
+ nEndBlue = nTempColor;
+ }
+
+ sal_uInt8 nRed;
+ sal_uInt8 nGreen;
+ sal_uInt8 nBlue;
+
+ // Create border
+ tools::Rectangle aBorderRect = aRect;
+ tools::Polygon aPoly( 4 );
+ if (fBorder > 0.0)
+ {
+ nRed = static_cast<sal_uInt8>(nStartRed);
+ nGreen = static_cast<sal_uInt8>(nStartGreen);
+ nBlue = static_cast<sal_uInt8>(nStartBlue);
+
+ mpMetaFile->AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) );
+
+ aBorderRect.SetBottom( static_cast<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 );
+
+ mpMetaFile->AddAction( new MetaPolygonAction( aPoly ) );
+
+ if ( !bLinear)
+ {
+ aBorderRect = aMirrorRect;
+ aBorderRect.SetTop( static_cast<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 );
+
+ mpMetaFile->AddAction( new MetaPolygonAction( aPoly ) );
+ }
+ }
+
+ long nStepCount = GetGradientSteps( rGradient, aRect, true/*bMtf*/ );
+
+ // minimal three steps and maximal as max color steps
+ long nAbsRedSteps = std::abs( nEndRed - nStartRed );
+ long nAbsGreenSteps = std::abs( nEndGreen - nStartGreen );
+ long nAbsBlueSteps = std::abs( nEndBlue - nStartBlue );
+ long nMaxColorSteps = std::max( nAbsRedSteps , nAbsGreenSteps );
+ nMaxColorSteps = std::max( nMaxColorSteps, nAbsBlueSteps );
+ 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 ( 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<long>(fTempColor));
+ fTempColor = static_cast<double>(nStartGreen) * (1.0-fAlpha) + static_cast<double>(nEndGreen) * fAlpha;
+ nGreen = GetGradientColorValue(static_cast<long>(fTempColor));
+ fTempColor = static_cast<double>(nStartBlue) * (1.0-fAlpha) + static_cast<double>(nEndBlue) * fAlpha;
+ nBlue = GetGradientColorValue(static_cast<long>(fTempColor));
+
+ mpMetaFile->AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) );
+
+ // Polygon for this color step
+ aRect.SetTop( static_cast<long>( fGradientLine + static_cast<double>(i) * fScanInc ) );
+ aRect.SetBottom( static_cast<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 );
+
+ mpMetaFile->AddAction( new MetaPolygonAction( aPoly ) );
+
+ if ( !bLinear )
+ {
+ aMirrorRect.SetBottom( static_cast<long>( fMirrorGradientLine - static_cast<double>(i) * fScanInc ) );
+ aMirrorRect.SetTop( static_cast<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 );
+
+ mpMetaFile->AddAction( new MetaPolygonAction( aPoly ) );
+ }
+ }
+ if ( bLinear)
+ return;
+
+ // draw middle polygon with end color
+ nRed = GetGradientColorValue(nEndRed);
+ nGreen = GetGradientColorValue(nEndGreen);
+ nBlue = GetGradientColorValue(nEndBlue);
+
+ mpMetaFile->AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) );
+
+ aRect.SetTop( static_cast<long>( fGradientLine + static_cast<double>(nSteps) * fScanInc ) );
+ aRect.SetBottom( static_cast<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 );
+
+ mpMetaFile->AddAction( new MetaPolygonAction( aPoly ) );
+
+}
+
+void OutputDevice::DrawComplexGradientToMetafile( const tools::Rectangle& rRect,
+ const Gradient& rGradient )
+{
+ 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::unique_ptr<tools::PolyPolygon> xPolyPoly;
+ tools::Rectangle aRect;
+ Point aCenter;
+ Color aStartCol( rGradient.GetStartColor() );
+ Color aEndCol( rGradient.GetEndColor() );
+ long nStartRed = ( static_cast<long>(aStartCol.GetRed()) * rGradient.GetStartIntensity() ) / 100;
+ long nStartGreen = ( static_cast<long>(aStartCol.GetGreen()) * rGradient.GetStartIntensity() ) / 100;
+ long nStartBlue = ( static_cast<long>(aStartCol.GetBlue()) * rGradient.GetStartIntensity() ) / 100;
+ long nEndRed = ( static_cast<long>(aEndCol.GetRed()) * rGradient.GetEndIntensity() ) / 100;
+ long nEndGreen = ( static_cast<long>(aEndCol.GetGreen()) * rGradient.GetEndIntensity() ) / 100;
+ long nEndBlue = ( static_cast<long>(aEndCol.GetBlue()) * rGradient.GetEndIntensity() ) / 100;
+ long nRedSteps = nEndRed - nStartRed;
+ long nGreenSteps = nEndGreen - nStartGreen;
+ long nBlueSteps = nEndBlue - nStartBlue;
+ sal_uInt16 nAngle = rGradient.GetAngle() % 3600;
+
+ rGradient.GetBoundRect( rRect, aRect, aCenter );
+
+ xPolyPoly.reset(new tools::PolyPolygon( 2 ));
+
+ // last parameter - true if complex gradient, false if linear
+ long nStepCount = GetGradientSteps( rGradient, rRect, true, true );
+
+ // at least three steps and at most the number of colour differences
+ long nSteps = std::max( nStepCount, 2L );
+ long nCalcSteps = std::abs( nRedSteps );
+ long nTempSteps = std::abs( nGreenSteps );
+ if ( nTempSteps > nCalcSteps )
+ nCalcSteps = nTempSteps;
+ nTempSteps = std::abs( nBlueSteps );
+ if ( nTempSteps > nCalcSteps )
+ nCalcSteps = nTempSteps;
+ if ( nCalcSteps < nSteps )
+ nSteps = nCalcSteps;
+ if ( !nSteps )
+ nSteps = 1;
+
+ // determine output limits and stepsizes for all directions
+ tools::Polygon aPoly;
+ double fScanLeft = aRect.Left();
+ double fScanTop = aRect.Top();
+ double fScanRight = aRect.Right();
+ double fScanBottom = aRect.Bottom();
+ double fScanIncX = static_cast<double>(aRect.GetWidth()) / static_cast<double>(nSteps) * 0.5;
+ double fScanIncY = static_cast<double>(aRect.GetHeight()) / static_cast<double>(nSteps) * 0.5;
+
+ // all gradients are rendered as nested rectangles which shrink
+ // equally in each dimension - except for 'square' gradients
+ // which shrink to a central vertex but are not per-se square.
+ if( rGradient.GetStyle() != GradientStyle::Square )
+ {
+ fScanIncY = std::min( fScanIncY, fScanIncX );
+ fScanIncX = fScanIncY;
+ }
+ sal_uInt8 nRed = static_cast<sal_uInt8>(nStartRed), nGreen = static_cast<sal_uInt8>(nStartGreen), nBlue = static_cast<sal_uInt8>(nStartBlue);
+ bool bPaintLastPolygon( false ); // #107349# Paint last polygon only if loop has generated any output
+
+ mpMetaFile->AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) );
+
+ aPoly = rRect;
+ xPolyPoly->Insert( aPoly );
+ xPolyPoly->Insert( aPoly );
+
+ // loop to output Polygon/PolyPolygon sequentially
+ for( long i = 1; i < nSteps; i++ )
+ {
+ // calculate new Polygon
+ fScanLeft += fScanIncX;
+ aRect.SetLeft( static_cast<long>( fScanLeft ) );
+ fScanTop += fScanIncY;
+ aRect.SetTop( static_cast<long>( fScanTop ) );
+ fScanRight -= fScanIncX;
+ aRect.SetRight( static_cast<long>( fScanRight ) );
+ fScanBottom -= fScanIncY;
+ aRect.SetBottom( static_cast<long>( fScanBottom ) );
+
+ if( ( aRect.GetWidth() < 2 ) || ( aRect.GetHeight() < 2 ) )
+ break;
+
+ if( rGradient.GetStyle() == GradientStyle::Radial || rGradient.GetStyle() == GradientStyle::Elliptical )
+ aPoly = tools::Polygon( aRect.Center(), aRect.GetWidth() >> 1, aRect.GetHeight() >> 1 );
+ else
+ aPoly = tools::Polygon( aRect );
+
+ aPoly.Rotate( aCenter, nAngle );
+
+ // adapt colour accordingly
+ const 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 );
+
+ mpMetaFile->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.
+ mpMetaFile->AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) );
+ }
+
+ const tools::Polygon& rPoly = xPolyPoly->GetObject( 1 );
+
+ if( !rPoly.GetBoundRect().IsEmpty() )
+ {
+ // #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 );
+ }
+
+ mpMetaFile->AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) );
+ mpMetaFile->AddAction( new MetaPolygonAction( rPoly ) );
+ }
+}
+
+long OutputDevice::GetGradientStepCount( long nMinRect )
+{
+ long nInc = (nMinRect < 50) ? 2 : 4;
+
+ return nInc;
+}
+
+long OutputDevice::GetGradientSteps( const Gradient& rGradient, const tools::Rectangle& rRect, bool bMtf, bool bComplex )
+{
+ // calculate step count
+ long nStepCount = rGradient.GetSteps();
+ long nMinRect;
+
+ // generate nStepCount, if not passed
+ if (bComplex)
+ nMinRect = std::min( rRect.GetWidth(), rRect.GetHeight() );
+ else
+ nMinRect = rRect.GetHeight();
+
+ if ( !nStepCount )
+ {
+ long nInc;
+
+ nInc = GetGradientStepCount (nMinRect);
+ if ( !nInc || bMtf )
+ nInc = 1;
+ nStepCount = nMinRect / nInc;
+ }
+
+ return nStepCount;
+}
+
+Color OutputDevice::GetSingleColorGradientFill()
+{
+ Color aColor;
+
+ // we should never call on this function if any of these aren't set!
+ assert( mnDrawMode & ( DrawModeFlags::BlackGradient | DrawModeFlags::WhiteGradient | DrawModeFlags::SettingsGradient) );
+
+ if ( mnDrawMode & DrawModeFlags::BlackGradient )
+ aColor = COL_BLACK;
+ else if ( mnDrawMode & DrawModeFlags::WhiteGradient )
+ aColor = COL_WHITE;
+ else if ( mnDrawMode & DrawModeFlags::SettingsGradient )
+ aColor = GetSettings().GetStyleSettings().GetWindowColor();
+
+ return aColor;
+}
+
+void OutputDevice::SetGrayscaleColors( Gradient &rGradient )
+{
+ // this should only be called with the drawing mode is for grayscale gradients
+ assert ( mnDrawMode & DrawModeFlags::GrayGradient );
+
+ Color aStartCol( rGradient.GetStartColor() );
+ Color aEndCol( rGradient.GetEndColor() );
+
+ if ( mnDrawMode & DrawModeFlags::GrayGradient )
+ {
+ sal_uInt8 cStartLum = aStartCol.GetLuminance(), cEndLum = aEndCol.GetLuminance();
+ aStartCol = Color( cStartLum, cStartLum, cStartLum );
+ aEndCol = Color( cEndLum, cEndLum, cEndLum );
+ }
+
+ rGradient.SetStartColor( aStartCol );
+ rGradient.SetEndColor( aEndCol );
+}
+
+void OutputDevice::AddGradientActions( const tools::Rectangle& rRect, const Gradient& rGradient,
+ GDIMetaFile& rMtf )
+{
+
+ tools::Rectangle aRect( rRect );
+
+ aRect.Justify();
+
+ // do nothing if the rectangle is empty
+ if ( aRect.IsEmpty() )
+ return;
+
+ Gradient aGradient( rGradient );
+ GDIMetaFile* pOldMtf = mpMetaFile;
+
+ mpMetaFile = &rMtf;
+ mpMetaFile->AddAction( new MetaPushAction( PushFlags::ALL ) );
+ mpMetaFile->AddAction( new MetaISectRectClipRegionAction( aRect ) );
+ mpMetaFile->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 ( !aGradient.GetSteps() )
+ aGradient.SetSteps( GRADIENT_DEFAULT_STEPCOUNT );
+
+ if( aGradient.GetStyle() == GradientStyle::Linear || aGradient.GetStyle() == GradientStyle::Axial )
+ DrawLinearGradientToMetafile( aRect, aGradient );
+ else
+ DrawComplexGradientToMetafile( aRect, aGradient );
+
+ mpMetaFile->AddAction( new MetaPopAction() );
+ mpMetaFile = pOldMtf;
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/hatch.cxx b/vcl/source/outdev/hatch.cxx
new file mode 100644
index 000000000..c5cba1277
--- /dev/null
+++ b/vcl/source/outdev/hatch.cxx
@@ -0,0 +1,409 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <cassert>
+
+#include <osl/diagnose.h>
+#include <tools/line.hxx>
+#include <tools/helpers.hxx>
+
+#include <vcl/hatch.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/virdev.hxx>
+
+#include <salgdi.hxx>
+
+#include <memory>
+
+#define HATCH_MAXPOINTS 1024
+
+extern "C" {
+
+static int HatchCmpFnc( const void* p1, const void* p2 )
+{
+ const long nX1 = static_cast<Point const *>(p1)->X();
+ const long nX2 = static_cast<Point const *>(p2)->X();
+ const long nY1 = static_cast<Point const *>(p1)->Y();
+ const 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 );
+
+ if ( mnDrawMode & ( DrawModeFlags::BlackLine | DrawModeFlags::WhiteLine |
+ DrawModeFlags::GrayLine |
+ DrawModeFlags::SettingsLine ) )
+ {
+ Color aColor( rHatch.GetColor() );
+
+ if ( mnDrawMode & DrawModeFlags::BlackLine )
+ aColor = COL_BLACK;
+ else if ( mnDrawMode & DrawModeFlags::WhiteLine )
+ aColor = COL_WHITE;
+ else if ( mnDrawMode & DrawModeFlags::GrayLine )
+ {
+ const sal_uInt8 cLum = aColor.GetLuminance();
+ aColor = Color( cLum, cLum, cLum );
+ }
+ else if( mnDrawMode & DrawModeFlags::SettingsLine )
+ {
+ aColor = GetSettings().GetStyleSettings().GetFontColor();
+ }
+
+ aHatch.SetColor( aColor );
+ }
+
+ if( mpMetaFile )
+ mpMetaFile->AddAction( new MetaHatchAction( rPolyPoly, aHatch ) );
+
+ if( !IsDeviceOutputNecessary() || ImplIsRecordLayout() )
+ return;
+
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+
+ 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( 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( PushFlags::ALL ) );
+ mpMetaFile->AddAction( new MetaLineColorAction( rHatch.GetColor(), true ) );
+ DrawHatch( aPolyPoly, rHatch, true );
+ mpMetaFile->AddAction( new MetaPopAction() );
+ mpMetaFile = pOldMtf;
+ }
+}
+
+void OutputDevice::DrawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch, bool bMtf )
+{
+ assert(!is_double_buffered_window());
+
+ if(rPolyPoly.Count())
+ {
+ // #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 long nLogPixelWidth = ImplDevicePixelToLogicWidth( 1 );
+ const long nWidth = ImplDevicePixelToLogicWidth( std::max( ImplLogicWidthToDevicePixel( rHatch.GetDistance() ), 3L ) );
+ 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 );
+ 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, aPt1, aPt2, aInc, aEndPt1 );
+ 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, aPt1, aPt2, aInc, aEndPt1 );
+ 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, long nDist, sal_uInt16 nAngle10,
+ Point& rPt1, Point& rPt2, Size& rInc, Point& rEndPt1 )
+{
+ Point aRef;
+ long nAngle = nAngle10 % 1800;
+ long nOffset = 0;
+
+ if( nAngle > 900 )
+ nAngle -= 1800;
+
+ aRef = ( !IsRefPoint() ? rRect.TopLeft() : GetRefPoint() );
+
+ if( 0 == 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 == 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 >= -450 && nAngle <= 450 )
+ {
+ const double fAngle = F_PI1800 * labs( nAngle );
+ const double fTan = tan( fAngle );
+ const long nYOff = FRound( ( rRect.Right() - rRect.Left() ) * fTan );
+ long nPY;
+
+ nDist = FRound( nDist / cos( fAngle ) );
+ rInc = Size( 0, nDist );
+
+ if( nAngle > 0 )
+ {
+ 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 = F_PI1800 * labs( nAngle );
+ const double fTan = tan( fAngle );
+ const long nXOff = FRound( ( rRect.Bottom() - rRect.Top() ) / fTan );
+ long nPX;
+
+ nDist = FRound( nDist / sin( fAngle ) );
+ rInc = Size( nDist, 0 );
+
+ if( nAngle > 0 )
+ {
+ rPt1 = rRect.TopLeft();
+ rPt2 = Point( rRect.Left() - nXOff, rRect.Bottom() );
+ rEndPt1 = Point( rRect.Right() + nXOff, rRect.Top() );
+ nPX = FRound( aRef.X() - ( ( rPt1.Y() - aRef.Y() ) / fTan ) );
+ }
+ else
+ {
+ rPt1 = rRect.BottomLeft();
+ rPt2 = Point( rRect.Left() - nXOff, rRect.Top() );
+ rEndPt1 = Point( rRect.Right() + nXOff, rRect.Bottom() );
+ nPX = FRound( aRef.X() + ( ( rPt1.Y() - aRef.Y() ) / fTan ) );
+ }
+
+ if( nPX <= rPt1.X() )
+ nOffset = ( rPt1.X() - nPX ) % nDist;
+ else
+ nOffset = nDist - ( ( nPX - rPt1.X() ) % nDist );
+
+ rPt1.AdjustX( -nOffset );
+ rPt2.AdjustX( -nOffset );
+ }
+}
+
+void OutputDevice::DrawHatchLine( const tools::Line& rLine, const tools::PolyPolygon& rPolyPoly,
+ Point* pPtBuffer, bool bMtf )
+{
+ assert(!is_double_buffered_window());
+
+ double fX, fY;
+ long nAdd, nPCounter = 0;
+
+ for( 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( 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 )
+ pPtBuffer[ nPCounter++ ] = Point( FRound( fX ), FRound( fY ) );
+ }
+
+ aCurSegment.SetStart( aCurSegment.GetEnd() );
+ }
+ }
+ }
+
+ if( nPCounter > 1 )
+ {
+ qsort( pPtBuffer, nPCounter, sizeof( Point ), HatchCmpFnc );
+
+ if( nPCounter & 1 )
+ nPCounter--;
+
+ if( bMtf )
+ {
+ for( long i = 0; i < nPCounter; i += 2 )
+ mpMetaFile->AddAction( new MetaLineAction( pPtBuffer[ i ], pPtBuffer[ i + 1 ] ) );
+ }
+ else
+ {
+ for( long i = 0; i < nPCounter; i += 2 )
+ DrawHatchLine_DrawLine(pPtBuffer[i], pPtBuffer[i+1]);
+ }
+ }
+}
+
+void OutputDevice::DrawHatchLine_DrawLine(const Point& rStartPoint, const Point& rEndPoint)
+{
+ Point aPt1{ImplLogicToDevicePixel(rStartPoint)}, aPt2{ImplLogicToDevicePixel(rEndPoint)};
+ mpGraphics->DrawLine(aPt1.X(), aPt1.Y(), aPt2.X(), aPt2.Y(), this);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/line.cxx b/vcl/source/outdev/line.cxx
new file mode 100644
index 000000000..f451b6e1f
--- /dev/null
+++ b/vcl/source/outdev/line.cxx
@@ -0,0 +1,304 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <cassert>
+#include <numeric>
+
+#include <vcl/gdimtf.hxx>
+#include <vcl/lineinfo.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/virdev.hxx>
+
+#include <salgdi.hxx>
+
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/polygon/b2dlinegeometry.hxx>
+
+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;
+
+ 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;
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ // #i101598# support AA and snap for lines, too
+ if((mnAntialiasing & AntialiasingFlags::EnableB2dDraw)
+ && mpGraphics->supportsOperation(OutDevSupportType::B2DDraw)
+ && RasterOp::OverPaint == GetRasterOp()
+ && IsLineColor())
+ {
+ // at least transform with double precision to device coordinates; this will
+ // avoid pixel snap of single, appended lines
+ const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation());
+ basegfx::B2DPolygon aB2DPolyLine;
+
+ aB2DPolyLine.append(basegfx::B2DPoint(rStartPt.X(), rStartPt.Y()));
+ aB2DPolyLine.append(basegfx::B2DPoint(rEndPt.X(), rEndPt.Y()));
+ aB2DPolyLine.transform( aTransform );
+
+ const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
+
+ if( 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))
+ {
+ return;
+ }
+ }
+
+ const Point aStartPt(ImplLogicToDevicePixel(rStartPt));
+ const Point aEndPt(ImplLogicToDevicePixel(rEndPt));
+
+ mpGraphics->DrawLine( aStartPt.X(), aStartPt.Y(), aEndPt.X(), aEndPt.Y(), this );
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawLine( rStartPt, rEndPt );
+}
+
+void OutputDevice::drawLine( basegfx::B2DPolyPolygon aLinePolyPolygon, const LineInfo& rInfo )
+{
+ const bool bTryAA((mnAntialiasing & AntialiasingFlags::EnableB2dDraw)
+ && mpGraphics->supportsOperation(OutDevSupportType::B2DDraw)
+ && RasterOp::OverPaint == GetRasterOp()
+ && IsLineColor());
+ basegfx::B2DPolyPolygon aFillPolyPolygon;
+ const bool bDashUsed(LineStyle::Dash == rInfo.GetStyle());
+ const bool bLineWidthUsed(rInfo.GetWidth() > 1);
+
+ if(bDashUsed && aLinePolyPolygon.count())
+ {
+ ::std::vector< double > fDotDashArray;
+ const double fDashLen(rInfo.GetDashLen());
+ const double fDotLen(rInfo.GetDotLen());
+ const double fDistance(rInfo.GetDistance());
+
+ for(sal_uInt16 a(0); a < rInfo.GetDashCount(); a++)
+ {
+ fDotDashArray.push_back(fDashLen);
+ fDotDashArray.push_back(fDistance);
+ }
+
+ for(sal_uInt16 b(0); b < rInfo.GetDotCount(); b++)
+ {
+ fDotDashArray.push_back(fDotLen);
+ fDotDashArray.push_back(fDistance);
+ }
+
+ const double fAccumulated(::std::accumulate(fDotDashArray.begin(), fDotDashArray.end(), 0.0));
+
+ if(fAccumulated > 0.0)
+ {
+ basegfx::B2DPolyPolygon aResult;
+
+ for(auto const& rPolygon : aLinePolyPolygon)
+ {
+ basegfx::B2DPolyPolygon aLineTarget;
+ basegfx::utils::applyLineDashing(
+ rPolygon,
+ fDotDashArray,
+ &aLineTarget);
+ aResult.append(aLineTarget);
+ }
+
+ aLinePolyPolygon = aResult;
+ }
+ }
+
+ if(bLineWidthUsed && aLinePolyPolygon.count())
+ {
+ const double fHalfLineWidth((rInfo.GetWidth() * 0.5) + 0.5);
+
+ if(aLinePolyPolygon.areControlPointsUsed())
+ {
+ // #i110768# When area geometry has to be created, do not
+ // use the fallback bezier decomposition inside createAreaGeometry,
+ // but one that is at least as good as ImplSubdivideBezier was.
+ // There, Polygon::AdaptiveSubdivide was used with default parameter
+ // 1.0 as quality index.
+ aLinePolyPolygon = basegfx::utils::adaptiveSubdivideByDistance(aLinePolyPolygon, 1.0);
+ }
+
+ for(auto const& rPolygon : 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 : aLinePolyPolygon)
+ {
+ const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
+ bool bDone(false);
+
+ if(bTryAA)
+ {
+ 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(),
+ reinterpret_cast<SalPoint*>(aPolygon.GetPointAry()),
+ this);
+ }
+ }
+ }
+
+ if(aFillPolyPolygon.count())
+ {
+ const Color aOldLineColor( maLineColor );
+ const Color aOldFillColor( maFillColor );
+
+ SetLineColor();
+ InitLineColor();
+ SetFillColor( aOldLineColor );
+ InitFillColor();
+
+ bool bDone(false);
+
+ if(bTryAA)
+ {
+ bDone = mpGraphics->DrawPolyPolygon(
+ basegfx::B2DHomMatrix(),
+ aFillPolyPolygon,
+ 0.0,
+ this);
+ }
+
+ if(!bDone)
+ {
+ for(auto const& rB2DPolygon : aFillPolyPolygon)
+ {
+ tools::Polygon aPolygon(rB2DPolygon);
+
+ // need to subdivide, mpGraphics->DrawPolygon ignores curves
+ aPolygon.AdaptiveSubdivide(aPolygon);
+ mpGraphics->DrawPolygon(aPolygon.GetSize(), reinterpret_cast<const SalPoint*>(aPolygon.GetConstPointAry()), this);
+ }
+ }
+
+ SetFillColor( aOldFillColor );
+ SetLineColor( aOldLineColor );
+ }
+
+ mpMetaFile = pOldMetaFile;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/map.cxx b/vcl/source/outdev/map.cxx
new file mode 100644
index 000000000..92d9e6322
--- /dev/null
+++ b/vcl/source/outdev/map.cxx
@@ -0,0 +1,1936 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/bigint.hxx>
+#include <tools/debug.hxx>
+#include <vcl/cursor.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/lineinfo.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/wrkwin.hxx>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+#include <svdata.hxx>
+#include <window.h>
+#include <outdev.h>
+
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <o3tl/enumarray.hxx>
+
+// we don't actually handle units beyond, hence the zeros in the arrays
+static const MapUnit s_MaxValidUnit = MapUnit::MapPixel;
+static const o3tl::enumarray<MapUnit,long> aImplNumeratorAry =
+ { 1, 1, 5, 50, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 };
+static const o3tl::enumarray<MapUnit,long> aImplDenominatorAry =
+ { 2540, 254, 127, 127, 1000, 100, 10, 1, 72, 1440, 1, 0, 0, 0 };
+
+/*
+Reduces accuracy until it is a fraction (should become
+ctor fraction once); we could also do this with BigInts
+*/
+
+static Fraction ImplMakeFraction( long nN1, long nN2, long nD1, long nD2 )
+{
+ if( nD1 == 0 || nD2 == 0 ) //under these bad circumstances the following while loop will be endless
+ {
+ SAL_WARN("vcl.gdi", "Invalid parameter for ImplMakeFraction");
+ return Fraction( 1, 1 );
+ }
+
+ long i = 1;
+
+ if ( nN1 < 0 ) { i = -i; nN1 = -nN1; }
+ if ( nN2 < 0 ) { i = -i; nN2 = -nN2; }
+ if ( nD1 < 0 ) { i = -i; nD1 = -nD1; }
+ if ( nD2 < 0 ) { i = -i; nD2 = -nD2; }
+ // all positive; i sign
+
+ Fraction aF = Fraction( i*nN1, nD1 ) * Fraction( nN2, nD2 );
+
+ while ( !aF.IsValid() ) {
+ if ( nN1 > nN2 )
+ nN1 = (nN1 + 1) / 2;
+ else
+ nN2 = (nN2 + 1) / 2;
+ if ( nD1 > nD2 )
+ nD1 = (nD1 + 1) / 2;
+ else
+ nD2 = (nD2 + 1) / 2;
+
+ aF = Fraction( i*nN1, nD1 ) * Fraction( nN2, nD2 );
+ }
+
+ aF.ReduceInaccurate(32);
+ return aF;
+}
+
+// Fraction.GetNumerator()
+// Fraction.GetDenominator() > 0
+// rOutRes.nPixPerInch? > 0
+// rMapRes.nMapScNum?
+// rMapRes.nMapScDenom? > 0
+
+static void ImplCalcBigIntThreshold( long nDPIX, long nDPIY,
+ const ImplMapRes& rMapRes,
+ ImplThresholdRes& rThresRes )
+{
+ if ( nDPIX && (LONG_MAX / nDPIX < std::abs( rMapRes.mnMapScNumX ) ) ) // #111139# avoid div by zero
+ {
+ rThresRes.mnThresLogToPixX = 0;
+ rThresRes.mnThresPixToLogX = 0;
+ }
+ else
+ {
+ // calculate thresholds for BigInt arithmetic
+ long nDenomHalfX = rMapRes.mnMapScDenomX / 2;
+ sal_uLong nDenomX = rMapRes.mnMapScDenomX;
+ long nProductX = nDPIX * rMapRes.mnMapScNumX;
+
+ if ( !nProductX )
+ rThresRes.mnThresLogToPixX = LONG_MAX;
+ else
+ rThresRes.mnThresLogToPixX = std::abs( (LONG_MAX - nDenomHalfX) / nProductX );
+
+ if ( !nDenomX )
+ rThresRes.mnThresPixToLogX = LONG_MAX;
+ else if ( nProductX >= 0 )
+ rThresRes.mnThresPixToLogX = static_cast<long>((sal_uLong(LONG_MAX) - static_cast<sal_uLong>( nProductX/2)) / nDenomX);
+ else
+ rThresRes.mnThresPixToLogX = static_cast<long>((sal_uLong(LONG_MAX) + static_cast<sal_uLong>(-nProductX/2)) / nDenomX);
+ }
+
+ if ( nDPIY && (LONG_MAX / nDPIY < std::abs( rMapRes.mnMapScNumY ) ) ) // #111139# avoid div by zero
+ {
+ rThresRes.mnThresLogToPixY = 0;
+ rThresRes.mnThresPixToLogY = 0;
+ }
+ else
+ {
+ // calculate thresholds for BigInt arithmetic
+ long nDenomHalfY = rMapRes.mnMapScDenomY / 2;
+ sal_uLong nDenomY = rMapRes.mnMapScDenomY;
+ long nProductY = nDPIY * rMapRes.mnMapScNumY;
+
+ if ( !nProductY )
+ rThresRes.mnThresLogToPixY = LONG_MAX;
+ else
+ rThresRes.mnThresLogToPixY = std::abs( (LONG_MAX - nDenomHalfY) / nProductY );
+
+ if ( !nDenomY )
+ rThresRes.mnThresPixToLogY = LONG_MAX;
+ else if ( nProductY >= 0 )
+ rThresRes.mnThresPixToLogY = static_cast<long>((sal_uLong(LONG_MAX) - static_cast<sal_uLong>( nProductY/2)) / nDenomY);
+ else
+ rThresRes.mnThresPixToLogY = static_cast<long>((sal_uLong(LONG_MAX) + static_cast<sal_uLong>(-nProductY/2)) / nDenomY);
+ }
+
+ rThresRes.mnThresLogToPixX /= 2;
+ rThresRes.mnThresLogToPixY /= 2;
+ rThresRes.mnThresPixToLogX /= 2;
+ rThresRes.mnThresPixToLogY /= 2;
+}
+
+static void ImplCalcMapResolution( const MapMode& rMapMode,
+ long nDPIX, long nDPIY, ImplMapRes& rMapRes )
+{
+ switch ( rMapMode.GetMapUnit() )
+ {
+ case MapUnit::MapRelative:
+ break;
+ case MapUnit::Map100thMM:
+ rMapRes.mnMapScNumX = 1;
+ rMapRes.mnMapScDenomX = 2540;
+ rMapRes.mnMapScNumY = 1;
+ rMapRes.mnMapScDenomY = 2540;
+ break;
+ case MapUnit::Map10thMM:
+ rMapRes.mnMapScNumX = 1;
+ rMapRes.mnMapScDenomX = 254;
+ rMapRes.mnMapScNumY = 1;
+ rMapRes.mnMapScDenomY = 254;
+ break;
+ case MapUnit::MapMM:
+ rMapRes.mnMapScNumX = 5; // 10
+ rMapRes.mnMapScDenomX = 127; // 254
+ rMapRes.mnMapScNumY = 5; // 10
+ rMapRes.mnMapScDenomY = 127; // 254
+ break;
+ case MapUnit::MapCM:
+ rMapRes.mnMapScNumX = 50; // 100
+ rMapRes.mnMapScDenomX = 127; // 254
+ rMapRes.mnMapScNumY = 50; // 100
+ rMapRes.mnMapScDenomY = 127; // 254
+ break;
+ case MapUnit::Map1000thInch:
+ rMapRes.mnMapScNumX = 1;
+ rMapRes.mnMapScDenomX = 1000;
+ rMapRes.mnMapScNumY = 1;
+ rMapRes.mnMapScDenomY = 1000;
+ break;
+ case MapUnit::Map100thInch:
+ rMapRes.mnMapScNumX = 1;
+ rMapRes.mnMapScDenomX = 100;
+ rMapRes.mnMapScNumY = 1;
+ rMapRes.mnMapScDenomY = 100;
+ break;
+ case MapUnit::Map10thInch:
+ rMapRes.mnMapScNumX = 1;
+ rMapRes.mnMapScDenomX = 10;
+ rMapRes.mnMapScNumY = 1;
+ rMapRes.mnMapScDenomY = 10;
+ break;
+ case MapUnit::MapInch:
+ rMapRes.mnMapScNumX = 1;
+ rMapRes.mnMapScDenomX = 1;
+ rMapRes.mnMapScNumY = 1;
+ rMapRes.mnMapScDenomY = 1;
+ break;
+ case MapUnit::MapPoint:
+ rMapRes.mnMapScNumX = 1;
+ rMapRes.mnMapScDenomX = 72;
+ rMapRes.mnMapScNumY = 1;
+ rMapRes.mnMapScDenomY = 72;
+ break;
+ case MapUnit::MapTwip:
+ rMapRes.mnMapScNumX = 1;
+ rMapRes.mnMapScDenomX = 1440;
+ rMapRes.mnMapScNumY = 1;
+ rMapRes.mnMapScDenomY = 1440;
+ break;
+ case MapUnit::MapPixel:
+ rMapRes.mnMapScNumX = 1;
+ rMapRes.mnMapScDenomX = nDPIX;
+ rMapRes.mnMapScNumY = 1;
+ rMapRes.mnMapScDenomY = nDPIY;
+ break;
+ case MapUnit::MapSysFont:
+ case MapUnit::MapAppFont:
+ {
+ ImplSVData* pSVData = ImplGetSVData();
+ if ( !pSVData->maGDIData.mnAppFontX )
+ {
+ if (pSVData->maFrameData.mpFirstFrame)
+ vcl::Window::ImplInitAppFontData(pSVData->maFrameData.mpFirstFrame);
+ else
+ {
+ ScopedVclPtrInstance<WorkWindow> pWin( nullptr, 0 );
+ vcl::Window::ImplInitAppFontData( pWin );
+ }
+ }
+ rMapRes.mnMapScNumX = pSVData->maGDIData.mnAppFontX;
+ rMapRes.mnMapScDenomX = nDPIX * 40;
+ rMapRes.mnMapScNumY = pSVData->maGDIData.mnAppFontY;
+ rMapRes.mnMapScDenomY = nDPIY * 80;
+ }
+ break;
+ default:
+ OSL_FAIL( "unhandled MapUnit" );
+ break;
+ }
+
+ const Fraction& aScaleX = rMapMode.GetScaleX();
+ const Fraction& aScaleY = rMapMode.GetScaleY();
+
+ // set offset according to MapMode
+ Point aOrigin = rMapMode.GetOrigin();
+ if ( rMapMode.GetMapUnit() != MapUnit::MapRelative )
+ {
+ rMapRes.mnMapOfsX = aOrigin.X();
+ rMapRes.mnMapOfsY = aOrigin.Y();
+ }
+ else
+ {
+ auto nXNumerator = aScaleX.GetNumerator();
+ auto nYNumerator = aScaleY.GetNumerator();
+ assert(nXNumerator != 0 && nYNumerator != 0);
+
+ BigInt aX( rMapRes.mnMapOfsX );
+ aX *= BigInt( aScaleX.GetDenominator() );
+ if ( rMapRes.mnMapOfsX >= 0 )
+ {
+ if (nXNumerator >= 0)
+ aX += BigInt(nXNumerator / 2);
+ else
+ aX -= BigInt((nXNumerator + 1) / 2);
+ }
+ else
+ {
+ if (nXNumerator >= 0 )
+ aX -= BigInt((nXNumerator - 1) / 2);
+ else
+ aX += BigInt(nXNumerator / 2);
+ }
+ aX /= BigInt(nXNumerator);
+ rMapRes.mnMapOfsX = static_cast<long>(aX) + aOrigin.X();
+ BigInt aY( rMapRes.mnMapOfsY );
+ aY *= BigInt( aScaleY.GetDenominator() );
+ if( rMapRes.mnMapOfsY >= 0 )
+ {
+ if (nYNumerator >= 0)
+ aY += BigInt(nYNumerator / 2);
+ else
+ aY -= BigInt((nYNumerator + 1) / 2);
+ }
+ else
+ {
+ if (nYNumerator >= 0)
+ aY -= BigInt((nYNumerator - 1) / 2);
+ else
+ aY += BigInt(nYNumerator / 2);
+ }
+ aY /= BigInt(nYNumerator);
+ rMapRes.mnMapOfsY = static_cast<long>(aY) + aOrigin.Y();
+ }
+
+ // calculate scaling factor according to MapMode
+ // aTemp? = rMapRes.mnMapSc? * aScale?
+ Fraction aTempX = ImplMakeFraction( rMapRes.mnMapScNumX,
+ aScaleX.GetNumerator(),
+ rMapRes.mnMapScDenomX,
+ aScaleX.GetDenominator() );
+ Fraction aTempY = ImplMakeFraction( rMapRes.mnMapScNumY,
+ aScaleY.GetNumerator(),
+ rMapRes.mnMapScDenomY,
+ aScaleY.GetDenominator() );
+ rMapRes.mnMapScNumX = aTempX.GetNumerator();
+ rMapRes.mnMapScDenomX = aTempX.GetDenominator();
+ rMapRes.mnMapScNumY = aTempY.GetNumerator();
+ rMapRes.mnMapScDenomY = aTempY.GetDenominator();
+}
+
+static void ImplCalcMapResolution( const MapMode& rMapMode,
+ long nDPIX, long nDPIY,
+ ImplMapRes& rMapRes,
+ ImplThresholdRes& rThresRes )
+{
+ ImplCalcMapResolution( rMapMode, nDPIX, nDPIY, rMapRes );
+ ImplCalcBigIntThreshold( nDPIX, nDPIY, rMapRes, rThresRes );
+}
+
+// #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 long ImplLogicToPixel( long n, long nDPI, long nMapNum, long nMapDenom,
+ long nThres )
+{
+ assert(nDPI > 0);
+#if (SAL_TYPES_SIZEOFLONG < 8)
+ if( (+n < nThres) && (-n < nThres) )
+ {
+ n *= nMapNum * nDPI;
+ if( nMapDenom != 1 )
+ {
+ n = (2 * n) / nMapDenom;
+ if( n < 0 ) --n; else ++n;
+ n /= 2;
+ }
+ }
+ else
+#else
+ (void) nThres;
+ assert(nMapNum >= 0);
+ assert(nMapNum == 0 || std::abs(n) < std::numeric_limits<long>::max() / nMapNum / nDPI); //detect overflows
+#endif
+ {
+ sal_Int64 n64 = n;
+ n64 *= nMapNum;
+ n64 *= nDPI;
+ if( nMapDenom == 1 )
+ n = static_cast<long>(n64);
+ else
+ {
+ n = static_cast<long>(2 * n64 / nMapDenom);
+ if( n < 0 ) --n; else ++n;
+ n /= 2;
+ }
+ }
+ return n;
+}
+
+static long ImplPixelToLogic( long n, long nDPI, long nMapNum, long nMapDenom,
+ long nThres )
+{
+ assert(nDPI > 0);
+ long nDenom = nDPI * nMapNum;
+ if (nDenom == 0)
+ {
+ return 0;
+ }
+
+#if (SAL_TYPES_SIZEOFLONG < 8)
+ if( (+n < nThres) && (-n < nThres) )
+ n = (2 * n * nMapDenom) / nDenom;
+ else
+#else
+ (void) nThres;
+#endif
+ {
+ sal_Int64 n64 = n;
+ n64 *= nMapDenom;
+ n = static_cast<long>(2 * n64 / nDenom);
+ }
+ if( n < 0 ) --n; else ++n;
+ return (n / 2);
+}
+
+long OutputDevice::ImplLogicXToDevicePixel( long nX ) const
+{
+ if ( !mbMap )
+ return nX+mnOutOffX;
+
+ return ImplLogicToPixel( nX + maMapRes.mnMapOfsX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX,
+ maThresRes.mnThresLogToPixX )+mnOutOffX+mnOutOffOrigX;
+}
+
+long OutputDevice::ImplLogicYToDevicePixel( long nY ) const
+{
+ if ( !mbMap )
+ return nY+mnOutOffY;
+
+ return ImplLogicToPixel( nY + maMapRes.mnMapOfsY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresLogToPixY )+mnOutOffY+mnOutOffOrigY;
+}
+
+long OutputDevice::ImplLogicWidthToDevicePixel( long nWidth ) const
+{
+ if ( !mbMap )
+ return nWidth;
+
+ return ImplLogicToPixel( nWidth, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX,
+ maThresRes.mnThresLogToPixX );
+}
+
+long OutputDevice::ImplLogicHeightToDevicePixel( long nHeight ) const
+{
+ if ( !mbMap )
+ return nHeight;
+
+ return ImplLogicToPixel( nHeight, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresLogToPixY );
+}
+
+float OutputDevice::ImplFloatLogicHeightToDevicePixel( float fLogicHeight) const
+{
+ if( !mbMap)
+ return fLogicHeight;
+ float fPixelHeight = (fLogicHeight * mnDPIY * maMapRes.mnMapScNumY) / maMapRes.mnMapScDenomY;
+ return fPixelHeight;
+}
+
+long OutputDevice::ImplDevicePixelToLogicWidth( long nWidth ) const
+{
+ if ( !mbMap )
+ return nWidth;
+
+ return ImplPixelToLogic( nWidth, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX,
+ maThresRes.mnThresPixToLogX );
+}
+
+long OutputDevice::ImplDevicePixelToLogicHeight( long nHeight ) const
+{
+ if ( !mbMap )
+ return nHeight;
+
+ return ImplPixelToLogic( nHeight, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresPixToLogY );
+}
+
+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,
+ maThresRes.mnThresLogToPixX )+mnOutOffX+mnOutOffOrigX,
+ ImplLogicToPixel( rLogicPt.Y() + maMapRes.mnMapOfsY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresLogToPixY )+mnOutOffY+mnOutOffOrigY );
+}
+
+Size OutputDevice::ImplLogicToDevicePixel( const Size& rLogicSize ) const
+{
+ if ( !mbMap )
+ return rLogicSize;
+
+ return Size( ImplLogicToPixel( rLogicSize.Width(), mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX,
+ maThresRes.mnThresLogToPixX ),
+ ImplLogicToPixel( rLogicSize.Height(), mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresLogToPixY ) );
+}
+
+tools::Rectangle OutputDevice::ImplLogicToDevicePixel( const tools::Rectangle& rLogicRect ) const
+{
+ if ( rLogicRect.IsEmpty() )
+ return rLogicRect;
+
+ if ( !mbMap )
+ {
+ return tools::Rectangle( rLogicRect.Left()+mnOutOffX, rLogicRect.Top()+mnOutOffY,
+ rLogicRect.Right()+mnOutOffX, rLogicRect.Bottom()+mnOutOffY );
+ }
+
+ return tools::Rectangle( ImplLogicToPixel( rLogicRect.Left()+maMapRes.mnMapOfsX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX,
+ maThresRes.mnThresLogToPixX )+mnOutOffX+mnOutOffOrigX,
+ ImplLogicToPixel( rLogicRect.Top()+maMapRes.mnMapOfsY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresLogToPixY )+mnOutOffY+mnOutOffOrigY,
+ ImplLogicToPixel( rLogicRect.Right()+maMapRes.mnMapOfsX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX,
+ maThresRes.mnThresLogToPixX )+mnOutOffX+mnOutOffOrigX,
+ ImplLogicToPixel( rLogicRect.Bottom()+maMapRes.mnMapOfsY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresLogToPixY )+mnOutOffY+mnOutOffOrigY );
+}
+
+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* pPt = &(pPointAry[i]);
+ Point aPt;
+ aPt.setX( ImplLogicToPixel( pPt->X()+maMapRes.mnMapOfsX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX,
+ maThresRes.mnThresLogToPixX )+mnOutOffX+mnOutOffOrigX );
+ aPt.setY( ImplLogicToPixel( pPt->Y()+maMapRes.mnMapOfsY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresLogToPixY )+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;
+}
+
+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() ), 1L ) );
+ else
+ aInfo.SetDotCount( 0 );
+
+ if( aInfo.GetDashCount() && aInfo.GetDashLen() )
+ aInfo.SetDashLen( std::max( ImplLogicWidthToDevicePixel( aInfo.GetDashLen() ), 1L ) );
+ 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
+{
+ if ( rPixelRect.IsEmpty() )
+ return rPixelRect;
+
+ if ( !mbMap )
+ {
+ return tools::Rectangle( rPixelRect.Left()-mnOutOffX, rPixelRect.Top()-mnOutOffY,
+ rPixelRect.Right()-mnOutOffX, rPixelRect.Bottom()-mnOutOffY );
+ }
+
+ return tools::Rectangle( ImplPixelToLogic( rPixelRect.Left()-mnOutOffX-mnOutOffOrigX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX,
+ maThresRes.mnThresPixToLogX )-maMapRes.mnMapOfsX,
+ ImplPixelToLogic( rPixelRect.Top()-mnOutOffY-mnOutOffOrigY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresPixToLogY )-maMapRes.mnMapOfsY,
+ ImplPixelToLogic( rPixelRect.Right()-mnOutOffX-mnOutOffOrigX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX,
+ maThresRes.mnThresPixToLogX )-maMapRes.mnMapOfsX,
+ ImplPixelToLogic( rPixelRect.Bottom()-mnOutOffY-mnOutOffOrigY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresPixToLogY )-maMapRes.mnMapOfsY );
+}
+
+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, maThresRes );
+ }
+
+ // set new MapMode
+ if ( bRelMap )
+ {
+ Point aOrigin( maMapRes.mnMapOfsX, maMapRes.mnMapOfsY );
+ // aScale? = maMapMode.GetScale?() * rNewMapMode.GetScale?()
+ Fraction aScaleX = ImplMakeFraction( maMapMode.GetScaleX().GetNumerator(),
+ rNewMapMode.GetScaleX().GetNumerator(),
+ maMapMode.GetScaleX().GetDenominator(),
+ rNewMapMode.GetScaleX().GetDenominator() );
+ Fraction aScaleY = ImplMakeFraction( maMapMode.GetScaleY().GetNumerator(),
+ rNewMapMode.GetScaleY().GetNumerator(),
+ maMapMode.GetScaleY().GetDenominator(),
+ rNewMapMode.GetScaleY().GetDenominator() );
+ maMapMode.SetOrigin( aOrigin );
+ maMapMode.SetScaleX( aScaleX );
+ maMapMode.SetScaleY( aScaleY );
+ }
+ else
+ maMapMode = rNewMapMode;
+
+ // create new objects (clip region are not re-scaled)
+ mbNewFont = true;
+ mbInitFont = true;
+ ImplInitMapModeObjects();
+
+ // #106426# Adapt logical offset when changing mapmode
+ mnOutOffLogicX = ImplPixelToLogic( mnOutOffOrigX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX,
+ maThresRes.mnThresPixToLogX );
+ mnOutOffLogicY = ImplPixelToLogic( mnOutOffOrigY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresPixToLogY );
+
+ // #i75163#
+ ImplInvalidateViewTransform();
+}
+
+void OutputDevice::ImplInitMapModeObjects() {}
+
+void OutputDevice::SetRelativeMapMode( const MapMode& rNewMapMode )
+{
+ // do nothing if MapMode did not change
+ if ( maMapMode == rNewMapMode )
+ return;
+
+ MapUnit eOld = maMapMode.GetMapUnit();
+ MapUnit eNew = rNewMapMode.GetMapUnit();
+
+ // a?F = rNewMapMode.GetScale?() / maMapMode.GetScale?()
+ Fraction aXF = ImplMakeFraction( rNewMapMode.GetScaleX().GetNumerator(),
+ maMapMode.GetScaleX().GetDenominator(),
+ rNewMapMode.GetScaleX().GetDenominator(),
+ maMapMode.GetScaleX().GetNumerator() );
+ Fraction aYF = ImplMakeFraction( rNewMapMode.GetScaleY().GetNumerator(),
+ maMapMode.GetScaleY().GetDenominator(),
+ rNewMapMode.GetScaleY().GetDenominator(),
+ maMapMode.GetScaleY().GetNumerator() );
+
+ Point aPt( LogicToLogic( Point(), nullptr, &rNewMapMode ) );
+ if ( eNew != eOld )
+ {
+ if ( eOld > MapUnit::MapPixel )
+ {
+ SAL_WARN( "vcl.gdi", "Not implemented MapUnit" );
+ }
+ else if ( eNew > MapUnit::MapPixel )
+ {
+ SAL_WARN( "vcl.gdi", "Not implemented MapUnit" );
+ }
+ else
+ {
+ Fraction aF( aImplNumeratorAry[eNew] * aImplDenominatorAry[eOld],
+ aImplNumeratorAry[eOld] * aImplDenominatorAry[eNew] );
+
+ // a?F = a?F * aF
+ aXF = ImplMakeFraction( aXF.GetNumerator(), aF.GetNumerator(),
+ aXF.GetDenominator(), aF.GetDenominator() );
+ aYF = ImplMakeFraction( aYF.GetNumerator(), aF.GetNumerator(),
+ aYF.GetDenominator(), aF.GetDenominator() );
+ if ( eOld == MapUnit::MapPixel )
+ {
+ aXF *= Fraction( mnDPIX, 1 );
+ aYF *= Fraction( mnDPIY, 1 );
+ }
+ else if ( eNew == MapUnit::MapPixel )
+ {
+ aXF *= Fraction( 1, mnDPIX );
+ aYF *= Fraction( 1, mnDPIY );
+ }
+ }
+ }
+
+ MapMode aNewMapMode( MapUnit::MapRelative, Point( -aPt.X(), -aPt.Y() ), aXF, aYF );
+ SetMapMode( aNewMapMode );
+
+ if ( eNew != eOld )
+ maMapMode = rNewMapMode;
+
+ // #106426# Adapt logical offset when changing MapMode
+ mnOutOffLogicX = ImplPixelToLogic( mnOutOffOrigX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX,
+ maThresRes.mnThresPixToLogX );
+ mnOutOffLogicY = ImplPixelToLogic( mnOutOffOrigY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresPixToLogY );
+
+ 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;
+ ImplThresholdRes aThresRes;
+ ImplCalcMapResolution( rMapMode, mnDPIX, mnDPIY, aMapRes, aThresRes );
+
+ 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,
+ maThresRes.mnThresLogToPixX )+mnOutOffOrigX,
+ ImplLogicToPixel( rLogicPt.Y() + maMapRes.mnMapOfsY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresLogToPixY )+mnOutOffOrigY );
+}
+
+Size OutputDevice::LogicToPixel( const Size& rLogicSize ) const
+{
+
+ if ( !mbMap )
+ return rLogicSize;
+
+ return Size( ImplLogicToPixel( rLogicSize.Width(), mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX,
+ maThresRes.mnThresLogToPixX ),
+ ImplLogicToPixel( rLogicSize.Height(), mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresLogToPixY ) );
+}
+
+tools::Rectangle OutputDevice::LogicToPixel( const tools::Rectangle& rLogicRect ) const
+{
+
+ if ( !mbMap || rLogicRect.IsEmpty() )
+ return rLogicRect;
+
+ return tools::Rectangle( ImplLogicToPixel( rLogicRect.Left() + maMapRes.mnMapOfsX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX,
+ maThresRes.mnThresLogToPixX )+mnOutOffOrigX,
+ ImplLogicToPixel( rLogicRect.Top() + maMapRes.mnMapOfsY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresLogToPixY )+mnOutOffOrigY,
+ ImplLogicToPixel( rLogicRect.Right() + maMapRes.mnMapOfsX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX,
+ maThresRes.mnThresLogToPixX )+mnOutOffOrigX,
+ ImplLogicToPixel( rLogicRect.Bottom() + maMapRes.mnMapOfsY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresLogToPixY )+mnOutOffOrigY );
+}
+
+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,
+ maThresRes.mnThresLogToPixX )+mnOutOffOrigX );
+ aPt.setY( ImplLogicToPixel( pPt->Y() + maMapRes.mnMapOfsY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresLogToPixY )+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;
+ ImplThresholdRes aThresRes;
+ ImplCalcMapResolution( rMapMode, mnDPIX, mnDPIY, aMapRes, aThresRes );
+
+ return Point( ImplLogicToPixel( rLogicPt.X() + aMapRes.mnMapOfsX, mnDPIX,
+ aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX,
+ aThresRes.mnThresLogToPixX )+mnOutOffOrigX,
+ ImplLogicToPixel( rLogicPt.Y() + aMapRes.mnMapOfsY, mnDPIY,
+ aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY,
+ aThresRes.mnThresLogToPixY )+mnOutOffOrigY );
+}
+
+Size OutputDevice::LogicToPixel( const Size& rLogicSize,
+ const MapMode& rMapMode ) const
+{
+
+ if ( rMapMode.IsDefault() )
+ return rLogicSize;
+
+ // convert MapMode resolution and convert
+ ImplMapRes aMapRes;
+ ImplThresholdRes aThresRes;
+ ImplCalcMapResolution( rMapMode, mnDPIX, mnDPIY, aMapRes, aThresRes );
+
+ return Size( ImplLogicToPixel( rLogicSize.Width(), mnDPIX,
+ aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX,
+ aThresRes.mnThresLogToPixX ),
+ ImplLogicToPixel( rLogicSize.Height(), mnDPIY,
+ aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY,
+ aThresRes.mnThresLogToPixY ) );
+}
+
+tools::Rectangle OutputDevice::LogicToPixel( const tools::Rectangle& rLogicRect,
+ const MapMode& rMapMode ) const
+{
+
+ if ( rMapMode.IsDefault() || rLogicRect.IsEmpty() )
+ return rLogicRect;
+
+ // convert MapMode resolution and convert
+ ImplMapRes aMapRes;
+ ImplThresholdRes aThresRes;
+ ImplCalcMapResolution( rMapMode, mnDPIX, mnDPIY, aMapRes, aThresRes );
+
+ return tools::Rectangle( ImplLogicToPixel( rLogicRect.Left() + aMapRes.mnMapOfsX, mnDPIX,
+ aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX,
+ aThresRes.mnThresLogToPixX )+mnOutOffOrigX,
+ ImplLogicToPixel( rLogicRect.Top() + aMapRes.mnMapOfsY, mnDPIY,
+ aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY,
+ aThresRes.mnThresLogToPixY )+mnOutOffOrigY,
+ ImplLogicToPixel( rLogicRect.Right() + aMapRes.mnMapOfsX, mnDPIX,
+ aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX,
+ aThresRes.mnThresLogToPixX )+mnOutOffOrigX,
+ ImplLogicToPixel( rLogicRect.Bottom() + aMapRes.mnMapOfsY, mnDPIY,
+ aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY,
+ aThresRes.mnThresLogToPixY )+mnOutOffOrigY );
+}
+
+tools::Polygon OutputDevice::LogicToPixel( const tools::Polygon& rLogicPoly,
+ const MapMode& rMapMode ) const
+{
+
+ if ( rMapMode.IsDefault() )
+ return rLogicPoly;
+
+ // convert MapMode resolution and convert
+ ImplMapRes aMapRes;
+ ImplThresholdRes aThresRes;
+ ImplCalcMapResolution( rMapMode, mnDPIX, mnDPIY, aMapRes, aThresRes );
+
+ 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,
+ aThresRes.mnThresLogToPixX )+mnOutOffOrigX );
+ aPt.setY( ImplLogicToPixel( pPt->Y() + aMapRes.mnMapOfsY, mnDPIY,
+ aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY,
+ aThresRes.mnThresLogToPixY )+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,
+ maThresRes.mnThresPixToLogX ) - maMapRes.mnMapOfsX - mnOutOffLogicX,
+ ImplPixelToLogic( rDevicePt.Y(), mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresPixToLogY ) - maMapRes.mnMapOfsY - mnOutOffLogicY );
+}
+
+Size OutputDevice::PixelToLogic( const Size& rDeviceSize ) const
+{
+
+ if ( !mbMap )
+ return rDeviceSize;
+
+ return Size( ImplPixelToLogic( rDeviceSize.Width(), mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX,
+ maThresRes.mnThresPixToLogX ),
+ ImplPixelToLogic( rDeviceSize.Height(), mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresPixToLogY ) );
+}
+
+tools::Rectangle OutputDevice::PixelToLogic( const tools::Rectangle& rDeviceRect ) const
+{
+
+ if ( !mbMap || rDeviceRect.IsEmpty() )
+ return rDeviceRect;
+
+ return tools::Rectangle( ImplPixelToLogic( rDeviceRect.Left(), mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX,
+ maThresRes.mnThresPixToLogX ) - maMapRes.mnMapOfsX - mnOutOffLogicX,
+ ImplPixelToLogic( rDeviceRect.Top(), mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresPixToLogY ) - maMapRes.mnMapOfsY - mnOutOffLogicY,
+ ImplPixelToLogic( rDeviceRect.Right(), mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX,
+ maThresRes.mnThresPixToLogX ) - maMapRes.mnMapOfsX - mnOutOffLogicX,
+ ImplPixelToLogic( rDeviceRect.Bottom(), mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresPixToLogY ) - maMapRes.mnMapOfsY - mnOutOffLogicY );
+}
+
+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,
+ maThresRes.mnThresPixToLogX ) - maMapRes.mnMapOfsX - mnOutOffLogicX );
+ aPt.setY( ImplPixelToLogic( pPt->Y(), mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresPixToLogY ) - 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;
+ ImplThresholdRes aThresRes;
+ ImplCalcMapResolution( rMapMode, mnDPIX, mnDPIY, aMapRes, aThresRes );
+
+ return Point( ImplPixelToLogic( rDevicePt.X(), mnDPIX,
+ aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX,
+ aThresRes.mnThresPixToLogX ) - aMapRes.mnMapOfsX - mnOutOffLogicX,
+ ImplPixelToLogic( rDevicePt.Y(), mnDPIY,
+ aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY,
+ aThresRes.mnThresPixToLogY ) - 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;
+ ImplThresholdRes aThresRes;
+ ImplCalcMapResolution( rMapMode, mnDPIX, mnDPIY, aMapRes, aThresRes );
+
+ return Size( ImplPixelToLogic( rDeviceSize.Width(), mnDPIX,
+ aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX,
+ aThresRes.mnThresPixToLogX ),
+ ImplPixelToLogic( rDeviceSize.Height(), mnDPIY,
+ aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY,
+ aThresRes.mnThresPixToLogY ) );
+}
+
+tools::Rectangle OutputDevice::PixelToLogic( const tools::Rectangle& rDeviceRect,
+ const MapMode& rMapMode ) const
+{
+
+ // calculate nothing if default-MapMode
+ if ( rMapMode.IsDefault() || rDeviceRect.IsEmpty() )
+ return rDeviceRect;
+
+ // calculate MapMode-resolution and convert
+ ImplMapRes aMapRes;
+ ImplThresholdRes aThresRes;
+ ImplCalcMapResolution( rMapMode, mnDPIX, mnDPIY, aMapRes, aThresRes );
+
+ return tools::Rectangle( ImplPixelToLogic( rDeviceRect.Left(), mnDPIX,
+ aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX,
+ aThresRes.mnThresPixToLogX ) - aMapRes.mnMapOfsX - mnOutOffLogicX,
+ ImplPixelToLogic( rDeviceRect.Top(), mnDPIY,
+ aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY,
+ aThresRes.mnThresPixToLogY ) - aMapRes.mnMapOfsY - mnOutOffLogicY,
+ ImplPixelToLogic( rDeviceRect.Right(), mnDPIX,
+ aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX,
+ aThresRes.mnThresPixToLogX ) - aMapRes.mnMapOfsX - mnOutOffLogicX,
+ ImplPixelToLogic( rDeviceRect.Bottom(), mnDPIY,
+ aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY,
+ aThresRes.mnThresPixToLogY ) - aMapRes.mnMapOfsY - mnOutOffLogicY );
+}
+
+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;
+ ImplThresholdRes aThresRes;
+ ImplCalcMapResolution( rMapMode, mnDPIX, mnDPIY, aMapRes, aThresRes );
+
+ 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,
+ aThresRes.mnThresPixToLogX ) - aMapRes.mnMapOfsX - mnOutOffLogicX );
+ aPt.setY( ImplPixelToLogic( pPt->Y(), mnDPIY,
+ aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY,
+ aThresRes.mnThresPixToLogY ) - 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; \
+ aMapResSource.mnMapOfsX = 0; \
+ aMapResSource.mnMapOfsY = 0; \
+ aMapResSource.mnMapScNumX = 1; \
+ aMapResSource.mnMapScNumY = 1; \
+ aMapResSource.mnMapScDenomX = 1; \
+ aMapResSource.mnMapScDenomY = 1; \
+ ImplMapRes aMapResDest(aMapResSource); \
+ \
+ 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" );
+}
+
+#define ENTER3( eUnitSource, eUnitDest ) \
+ long nNumerator = 1; \
+ long nDenominator = 1; \
+ SAL_WARN_IF( eUnitSource > s_MaxValidUnit, "vcl.gdi", "Invalid source map unit"); \
+ SAL_WARN_IF( eUnitDest > s_MaxValidUnit, "vcl.gdi", "Invalid destination map unit"); \
+ if( (eUnitSource <= s_MaxValidUnit) && (eUnitDest <= s_MaxValidUnit) ) \
+ { \
+ nNumerator = aImplNumeratorAry[eUnitSource] * \
+ aImplDenominatorAry[eUnitDest]; \
+ nDenominator = aImplNumeratorAry[eUnitDest] * \
+ aImplDenominatorAry[eUnitSource]; \
+ } \
+ if ( eUnitSource == MapUnit::MapPixel ) \
+ nDenominator *= 72; \
+ else if( eUnitDest == MapUnit::MapPixel ) \
+ nNumerator *= 72
+
+#define ENTER4( rMapModeSource, rMapModeDest ) \
+ ImplMapRes aMapResSource; \
+ aMapResSource.mnMapOfsX = 0; \
+ aMapResSource.mnMapOfsY = 0; \
+ aMapResSource.mnMapScNumX = 1; \
+ aMapResSource.mnMapScNumY = 1; \
+ aMapResSource.mnMapScDenomX = 1; \
+ aMapResSource.mnMapScDenomY = 1; \
+ ImplMapRes aMapResDest(aMapResSource); \
+ \
+ ImplCalcMapResolution( rMapModeSource, 72, 72, aMapResSource ); \
+ ImplCalcMapResolution( rMapModeDest, 72, 72, aMapResDest )
+
+// return (n1 * n2 * n3) / (n4 * n5)
+static long fn5( const long n1,
+ const long n2,
+ const long n3,
+ const long n4,
+ const long n5 )
+{
+ if ( n1 == 0 || n2 == 0 || n3 == 0 || n4 == 0 || n5 == 0 )
+ return 0;
+ if ( LONG_MAX / std::abs(n2) < std::abs(n3) )
+ {
+ // a6 is skipped
+ BigInt a7 = n2;
+ a7 *= n3;
+ a7 *= n1;
+
+ if ( 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
+ {
+ long n8 = n4 * n5;
+
+ if ( a7.IsNeg() )
+ a7 -= n8 / 2;
+ else
+ a7 += n8 / 2;
+
+ a7 /= n8;
+ } // of else
+ return static_cast<long>(a7);
+ } // of if
+ else
+ {
+ long n6 = n2 * n3;
+
+ if ( LONG_MAX / std::abs(n1) < std::abs(n6) )
+ {
+ BigInt a7 = n1;
+ a7 *= n6;
+
+ if ( 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
+ {
+ long n8 = n4 * n5;
+
+ if ( a7.IsNeg() )
+ a7 -= n8 / 2;
+ else
+ a7 += n8 / 2;
+
+ a7 /= n8;
+ } // of else
+ return static_cast<long>(a7);
+ } // of if
+ else
+ {
+ long n7 = n1 * n6;
+
+ if ( 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<long>(a7);
+ } // of if
+ else
+ {
+ const long n8 = n4 * n5;
+ const long n8_2 = n8 / 2;
+
+ if( n7 < 0 )
+ {
+ if( ( n7 - LONG_MIN ) >= n8_2 )
+ n7 -= n8_2;
+ }
+ else if( ( LONG_MAX - n7 ) >= n8_2 )
+ n7 += n8_2;
+
+ return n7 / n8;
+ } // of else
+ } // of else
+ } // of else
+}
+
+// return (n1 * n2) / n3
+static long fn3( const long n1, const long n2, const long n3 )
+{
+ if ( n1 == 0 || n2 == 0 || n3 == 0 )
+ return 0;
+ if ( LONG_MAX / std::abs(n1) < std::abs(n2) )
+ {
+ BigInt a4 = n1;
+ a4 *= n2;
+
+ if ( a4.IsNeg() )
+ a4 -= n3 / 2;
+ else
+ a4 += n3 / 2;
+
+ a4 /= n3;
+ return static_cast<long>(a4);
+ } // of if
+ else
+ {
+ long n4 = n1 * n2;
+ const long n3_2 = n3 / 2;
+
+ if( n4 < 0 )
+ {
+ if( ( n4 - LONG_MIN ) >= n3_2 )
+ n4 -= n3_2;
+ }
+ else if( ( LONG_MAX - n4 ) >= n3_2 )
+ n4 += n3_2;
+
+ return n4 / n3;
+ } // of else
+}
+
+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())
+ {
+ ENTER3( eUnitSource, eUnitDest );
+
+ return Point( fn3( rPtSource.X(), nNumerator, nDenominator ),
+ fn3( rPtSource.Y(), nNumerator, nDenominator ) );
+ }
+ else
+ {
+ ENTER4( rMapModeSource, rMapModeDest );
+
+ return Point( fn5( rPtSource.X() + aMapResSource.mnMapOfsX,
+ aMapResSource.mnMapScNumX, aMapResDest.mnMapScDenomX,
+ aMapResSource.mnMapScDenomX, aMapResDest.mnMapScNumX ) -
+ aMapResDest.mnMapOfsX,
+ fn5( rPtSource.Y() + aMapResSource.mnMapOfsY,
+ aMapResSource.mnMapScNumY, aMapResDest.mnMapScDenomY,
+ aMapResSource.mnMapScDenomY, aMapResDest.mnMapScNumY ) -
+ aMapResDest.mnMapOfsY );
+ }
+}
+
+Size OutputDevice::LogicToLogic( const Size& rSzSource,
+ const MapMode& rMapModeSource,
+ const MapMode& rMapModeDest )
+{
+ if ( rMapModeSource == rMapModeDest )
+ return rSzSource;
+
+ MapUnit eUnitSource = rMapModeSource.GetMapUnit();
+ MapUnit eUnitDest = rMapModeDest.GetMapUnit();
+ verifyUnitSourceDest( eUnitSource, eUnitDest );
+
+ if (rMapModeSource.IsSimple() && rMapModeDest.IsSimple())
+ {
+ ENTER3( eUnitSource, eUnitDest );
+
+ return Size( fn3( rSzSource.Width(), nNumerator, nDenominator ),
+ fn3( rSzSource.Height(), nNumerator, nDenominator ) );
+ }
+ else
+ {
+ ENTER4( rMapModeSource, rMapModeDest );
+
+ return Size( fn5( rSzSource.Width(),
+ aMapResSource.mnMapScNumX, aMapResDest.mnMapScDenomX,
+ aMapResSource.mnMapScDenomX, aMapResDest.mnMapScNumX ),
+ fn5( rSzSource.Height(),
+ aMapResSource.mnMapScNumY, aMapResDest.mnMapScDenomY,
+ aMapResSource.mnMapScDenomY, aMapResDest.mnMapScNumY ) );
+ }
+}
+
+basegfx::B2DPolygon OutputDevice::LogicToLogic( const basegfx::B2DPolygon& rPolySource,
+ const MapMode& rMapModeSource,
+ const MapMode& rMapModeDest )
+{
+ if(rMapModeSource == rMapModeDest)
+ {
+ return rPolySource;
+ }
+
+ const basegfx::B2DHomMatrix aTransform(LogicToLogic(rMapModeSource, rMapModeDest));
+ basegfx::B2DPolygon aPoly(rPolySource);
+
+ aPoly.transform(aTransform);
+ return aPoly;
+}
+
+basegfx::B2DHomMatrix OutputDevice::LogicToLogic(const MapMode& rMapModeSource, const MapMode& rMapModeDest)
+{
+ basegfx::B2DHomMatrix aTransform;
+
+ if(rMapModeSource == rMapModeDest)
+ {
+ return aTransform;
+ }
+
+ MapUnit eUnitSource = rMapModeSource.GetMapUnit();
+ MapUnit eUnitDest = rMapModeDest.GetMapUnit();
+ verifyUnitSourceDest(eUnitSource, eUnitDest);
+
+ if (rMapModeSource.IsSimple() && rMapModeDest.IsSimple())
+ {
+ ENTER3(eUnitSource, eUnitDest);
+
+ const double fScaleFactor(static_cast<double>(nNumerator) / static_cast<double>(nDenominator));
+ aTransform.set(0, 0, fScaleFactor);
+ aTransform.set(1, 1, fScaleFactor);
+ }
+ else
+ {
+ ENTER4(rMapModeSource, rMapModeDest);
+
+ const double fScaleFactorX((double(aMapResSource.mnMapScNumX) * double(aMapResDest.mnMapScDenomX)) / (double(aMapResSource.mnMapScDenomX) * double(aMapResDest.mnMapScNumX)));
+ const double fScaleFactorY((double(aMapResSource.mnMapScNumY) * double(aMapResDest.mnMapScDenomY)) / (double(aMapResSource.mnMapScDenomY) * double(aMapResDest.mnMapScNumY)));
+ const double fZeroPointX(double(aMapResSource.mnMapOfsX) * fScaleFactorX - double(aMapResDest.mnMapOfsX));
+ const double fZeroPointY(double(aMapResSource.mnMapOfsY) * fScaleFactorY - double(aMapResDest.mnMapOfsY));
+
+ aTransform.set(0, 0, fScaleFactorX);
+ aTransform.set(1, 1, fScaleFactorY);
+ aTransform.set(0, 2, fZeroPointX);
+ aTransform.set(1, 2, fZeroPointY);
+ }
+
+ return aTransform;
+}
+
+tools::Rectangle OutputDevice::LogicToLogic( const tools::Rectangle& rRectSource,
+ const MapMode& rMapModeSource,
+ const MapMode& rMapModeDest )
+{
+ if ( rMapModeSource == rMapModeDest )
+ return rRectSource;
+
+ MapUnit eUnitSource = rMapModeSource.GetMapUnit();
+ MapUnit eUnitDest = rMapModeDest.GetMapUnit();
+ verifyUnitSourceDest( eUnitSource, eUnitDest );
+
+ if (rMapModeSource.IsSimple() && rMapModeDest.IsSimple())
+ {
+ ENTER3( eUnitSource, eUnitDest );
+
+ auto left = fn3( rRectSource.Left(), nNumerator, nDenominator );
+ auto top = fn3( rRectSource.Top(), nNumerator, nDenominator );
+ if (rRectSource.IsEmpty())
+ return tools::Rectangle( left, top );
+
+ auto right = fn3( rRectSource.Right(), nNumerator, nDenominator );
+ auto bottom = fn3( rRectSource.Bottom(), nNumerator, nDenominator );
+ return tools::Rectangle(left, top, right, bottom);
+ }
+ else
+ {
+ ENTER4( rMapModeSource, rMapModeDest );
+
+ auto left = fn5( rRectSource.Left() + aMapResSource.mnMapOfsX,
+ aMapResSource.mnMapScNumX, aMapResDest.mnMapScDenomX,
+ aMapResSource.mnMapScDenomX, aMapResDest.mnMapScNumX ) -
+ aMapResDest.mnMapOfsX;
+ auto top = fn5( rRectSource.Top() + aMapResSource.mnMapOfsY,
+ aMapResSource.mnMapScNumY, aMapResDest.mnMapScDenomY,
+ aMapResSource.mnMapScDenomY, aMapResDest.mnMapScNumY ) -
+ aMapResDest.mnMapOfsY;
+ if (rRectSource.IsEmpty())
+ return tools::Rectangle(left, top);
+
+ auto right = fn5( rRectSource.Right() + aMapResSource.mnMapOfsX,
+ aMapResSource.mnMapScNumX, aMapResDest.mnMapScDenomX,
+ aMapResSource.mnMapScDenomX, aMapResDest.mnMapScNumX ) -
+ aMapResDest.mnMapOfsX;
+ auto bottom = fn5( rRectSource.Bottom() + aMapResSource.mnMapOfsY,
+ aMapResSource.mnMapScNumY, aMapResDest.mnMapScDenomY,
+ aMapResSource.mnMapScDenomY, aMapResDest.mnMapScNumY ) -
+ aMapResDest.mnMapOfsY;
+ return tools::Rectangle(left, top, right, bottom);
+ }
+}
+
+long OutputDevice::LogicToLogic( long nLongSource,
+ MapUnit eUnitSource, MapUnit eUnitDest )
+{
+ if ( eUnitSource == eUnitDest )
+ return nLongSource;
+
+ verifyUnitSourceDest( eUnitSource, eUnitDest );
+ ENTER3( eUnitSource, eUnitDest );
+
+ return fn3( nLongSource, nNumerator, nDenominator );
+}
+
+void OutputDevice::SetPixelOffset( const Size& rOffset )
+{
+ mnOutOffOrigX = rOffset.Width();
+ mnOutOffOrigY = rOffset.Height();
+
+ mnOutOffLogicX = ImplPixelToLogic( mnOutOffOrigX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX,
+ maThresRes.mnThresPixToLogX );
+ mnOutOffLogicY = ImplPixelToLogic( mnOutOffOrigY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresPixToLogY );
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetPixelOffset( rOffset );
+}
+
+
+DeviceCoordinate OutputDevice::LogicWidthToDeviceCoordinate( long nWidth ) const
+{
+ if ( !mbMap )
+ return static_cast<DeviceCoordinate>(nWidth);
+
+#if VCL_FLOAT_DEVICE_PIXEL
+ return (double)nWidth * maMapRes.mfScaleX * mnDPIX;
+#else
+
+ return ImplLogicToPixel( nWidth, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX,
+ maThresRes.mnThresLogToPixX );
+#endif
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/mask.cxx b/vcl/source/outdev/mask.cxx
new file mode 100644
index 000000000..209389cfb
--- /dev/null
+++ b/vcl/source/outdev/mask.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 <cassert>
+
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/virdev.hxx>
+
+#include <salgdi.hxx>
+#include <salbmp.hxx>
+
+void OutputDevice::DrawMask( const Point& rDestPt,
+ const Bitmap& rBitmap, const Color& rMaskColor )
+{
+ assert(!is_double_buffered_window());
+
+ const Size aSizePix( rBitmap.GetSizePixel() );
+ DrawMask( rDestPt, PixelToLogic( aSizePix ), Point(), aSizePix, rBitmap, rMaskColor, MetaActionType::MASK );
+}
+
+void OutputDevice::DrawMask( const Point& rDestPt, const Size& rDestSize,
+ const Bitmap& rBitmap, const Color& rMaskColor )
+{
+ assert(!is_double_buffered_window());
+
+ DrawMask( rDestPt, rDestSize, Point(), rBitmap.GetSizePixel(), rBitmap, rMaskColor, MetaActionType::MASKSCALE );
+}
+
+void OutputDevice::DrawMask( const Point& rDestPt, const Size& rDestSize,
+ const Point& rSrcPtPixel, const Size& rSrcSizePixel,
+ const Bitmap& rBitmap, const Color& rMaskColor,
+ 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 )
+ {
+ const Bitmap& rAlphaMask( rMask.CreateMask( rMaskColor ) );
+
+ // #i25167# Restrict mask painting to _opaque_ areas
+ // of the mask, otherwise we spoil areas where no
+ // bitmap content was ever visible. Interestingly
+ // enough, this can be achieved by taking the mask as
+ // the transparency mask of itself
+ mpAlphaVDev->DrawBitmapEx( rDestPt,
+ rDestSize,
+ rSrcPtPixel,
+ rSrcSizePixel,
+ BitmapEx( rAlphaMask, rMask ) );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/nativecontrols.cxx b/vcl/source/outdev/nativecontrols.cxx
new file mode 100644
index 000000000..14ad647db
--- /dev/null
+++ b/vcl/source/outdev/nativecontrols.cxx
@@ -0,0 +1,356 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <cassert>
+
+#include <vcl/outdev.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/window.hxx>
+#include <sal/log.hxx>
+
+#include <vcl/salnativewidgets.hxx>
+#include <vcl/pdfextoutdevdata.hxx>
+
+#include <salgdi.hxx>
+
+static bool EnableNativeWidget( const OutputDevice& i_rDevice )
+{
+ const OutDevType eType( i_rDevice.GetOutDevType() );
+ switch ( eType )
+ {
+
+ case OUTDEV_WINDOW:
+ {
+ const vcl::Window* pWindow = dynamic_cast< const vcl::Window* >( &i_rDevice );
+ if (pWindow)
+ {
+ return pWindow->IsNativeWidgetEnabled();
+ }
+ else
+ {
+ SAL_WARN ("vcl.gdi", "Could not cast i_rDevice to Window");
+ assert (pWindow);
+ return false;
+ }
+ }
+
+ case OUTDEV_PDF:
+ [[fallthrough]];
+ case OUTDEV_VIRDEV:
+ {
+ const vcl::ExtOutDevData* pOutDevData( i_rDevice.GetExtOutDevData() );
+ const vcl::PDFExtOutDevData* pPDFData( dynamic_cast< const vcl::PDFExtOutDevData* >( pOutDevData ) );
+ return pPDFData == nullptr;
+ }
+
+ default:
+ return false;
+ }
+}
+
+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( !EnableNativeWidget( *this ) )
+ return false;
+
+ if ( !mpGraphics && !AcquireGraphics() )
+ return false;
+
+ return mpGraphics->IsNativeControlSupported(nType, nPart);
+}
+
+bool OutputDevice::HitTestNativeScrollbar(
+ ControlPart nPart,
+ const tools::Rectangle& rControlRegion,
+ const Point& aPos,
+ bool& rIsInside ) const
+{
+ if( !EnableNativeWidget( *this ) )
+ return false;
+
+ if ( !mpGraphics && !AcquireGraphics() )
+ return false;
+
+ 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::shared_ptr< ImplControlValue > TransformControlValue( const ImplControlValue& rVal, const OutputDevice& rDev )
+{
+ std::shared_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_shared<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( !EnableNativeWidget( *this ) )
+ return false;
+
+ // make sure the current clip region is initialized correctly
+ if ( !mpGraphics && !AcquireGraphics() )
+ return false;
+
+ 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::shared_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( !EnableNativeWidget( *this ) )
+ return false;
+
+ if ( !mpGraphics && !AcquireGraphics() )
+ return false;
+
+ // Convert the coordinates from relative to Window-absolute, so we draw
+ // in the correct place in platform code
+ std::shared_ptr< ImplControlValue > aScreenCtrlValue( TransformControlValue( aValue, *this ) );
+ tools::Rectangle screenRegion( ImplLogicToDevicePixel( rControlRegion ) );
+
+ bool bRet = mpGraphics->GetNativeControlRegion(nType, nPart, screenRegion, nState, *aScreenCtrlValue,
+ rNativeBoundingRegion,
+ rNativeContentRegion, this );
+ if( bRet )
+ {
+ // transform back native regions
+ rNativeBoundingRegion = ImplDevicePixelToLogic( rNativeBoundingRegion );
+ rNativeContentRegion = ImplDevicePixelToLogic( rNativeContentRegion );
+ }
+
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/outdev.cxx b/vcl/source/outdev/outdev.cxx
new file mode 100644
index 000000000..020a57a6a
--- /dev/null
+++ b/vcl/source/outdev/outdev.cxx
@@ -0,0 +1,707 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+
+#include <tools/debug.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/toolkit/unowrap.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/sysdata.hxx>
+
+#include <salgdi.hxx>
+#include <window.h>
+#include <outdev.h>
+
+#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
+
+// Begin initializer and accessor public functions
+
+OutputDevice::OutputDevice(OutDevType eOutDevType) :
+ meOutDevType(eOutDevType),
+ maRegion(true),
+ maFillColor( COL_WHITE ),
+ maTextLineColor( COL_TRANSPARENT ),
+ mxSettings( new AllSettings(Application::GetSettings()) )
+{
+ mpGraphics = nullptr;
+ mpUnoGraphicsList = nullptr;
+ mpPrevGraphics = nullptr;
+ mpNextGraphics = nullptr;
+ mpMetaFile = nullptr;
+ mpFontInstance = nullptr;
+ mpDeviceFontList = nullptr;
+ mpDeviceFontSizeList = 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 = ComplexTextLayoutFlags::Default;
+
+ if( AllSettings::GetLayoutRTL() ) //#i84553# tip BiDi preference to RTL
+ mnTextLayoutMode = ComplexTextLayoutFlags::BiDiRtl | 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 ImplThresholdRes
+ maThresRes.mnThresLogToPixX = 0;
+ maThresRes.mnThresLogToPixY = 0;
+ maThresRes.mnThresPixToLogX = 0;
+ maThresRes.mnThresPixToLogY = 0;
+
+ // struct ImplOutDevData- see #i82615#
+ mpOutDevData.reset(new ImplOutDevData);
+ mpOutDevData->mpRotateDev = nullptr;
+ mpOutDevData->mpRecordLayout = nullptr;
+
+ // #i75163#
+ mpOutDevData->mpViewTransform = nullptr;
+ mpOutDevData->mpInverseViewTransform = nullptr;
+}
+
+OutputDevice::~OutputDevice()
+{
+ disposeOnce();
+}
+
+void OutputDevice::dispose()
+{
+ if ( GetUnoGraphicsList() )
+ {
+ UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper( false );
+ if ( pWrapper )
+ pWrapper->ReleaseAllGraphics( this );
+ delete mpUnoGraphicsList;
+ mpUnoGraphicsList = nullptr;
+ }
+
+ mpOutDevData->mpRotateDev.disposeAndClear();
+
+ // #i75163#
+ ImplInvalidateViewTransform();
+
+ mpOutDevData.reset();
+
+ // for some reason, we haven't removed state from the stack properly
+ if ( !maOutDevStateStack.empty() )
+ SAL_WARN( "vcl.gdi", "OutputDevice::~OutputDevice(): OutputDevice::Push() calls != OutputDevice::Pop() calls" );
+ maOutDevStateStack.clear();
+
+ // release the active font instance
+ mpFontInstance.clear();
+
+ // remove cached results of GetDevFontList/GetDevSizeList
+ mpDeviceFontList.reset();
+ mpDeviceFontSizeList.reset();
+
+ // release ImplFontCache specific to this OutputDevice
+ mxFontCache.reset();
+
+ // release ImplFontList specific to this OutputDevice
+ mxFontCollection.reset();
+
+ mpAlphaVDev.disposeAndClear();
+ mpPrevGraphics.clear();
+ mpNextGraphics.clear();
+ VclReferenceBase::dispose();
+}
+
+bool OutputDevice::IsVirtual() const
+{
+ return false;
+}
+
+SalGraphics* OutputDevice::GetGraphics()
+{
+ DBG_TESTSOLARMUTEX();
+
+ if (!mpGraphics && !AcquireGraphics())
+ SAL_WARN("vcl.gdi", "No mpGraphics set");
+
+ return mpGraphics;
+}
+
+SalGraphics const *OutputDevice::GetGraphics() const
+{
+ DBG_TESTSOLARMUTEX();
+
+ if (!mpGraphics && !AcquireGraphics())
+ SAL_WARN("vcl.gdi", "No mpGraphics set");
+
+ return mpGraphics;
+}
+
+void OutputDevice::SetConnectMetaFile( GDIMetaFile* pMtf )
+{
+ mpMetaFile = pMtf;
+}
+
+void OutputDevice::SetSettings( const AllSettings& rSettings )
+{
+ *mxSettings = rSettings;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetSettings( rSettings );
+}
+
+SystemGraphicsData OutputDevice::GetSystemGfxData() const
+{
+ if (!mpGraphics && !AcquireGraphics())
+ return SystemGraphicsData();
+
+ return mpGraphics->GetGraphicsData();
+}
+
+#if ENABLE_CAIRO_CANVAS
+
+bool OutputDevice::SupportsCairo() const
+{
+ if (!mpGraphics && !AcquireGraphics())
+ return false;
+
+ return mpGraphics->SupportsCairo();
+}
+
+cairo::SurfaceSharedPtr OutputDevice::CreateSurface(const cairo::CairoSurfaceSharedPtr& rSurface) const
+{
+ if (!mpGraphics && !AcquireGraphics())
+ return cairo::SurfaceSharedPtr();
+ return mpGraphics->CreateSurface(rSurface);
+}
+
+cairo::SurfaceSharedPtr OutputDevice::CreateSurface(int x, int y, int width, int height) const
+{
+ if (!mpGraphics && !AcquireGraphics())
+ return cairo::SurfaceSharedPtr();
+ 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();
+ 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();
+ 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::makeAny(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 );
+}
+
+sal_uInt16 OutputDevice::GetBitCount() const
+{
+ // we need a graphics instance
+ if ( !mpGraphics && !AcquireGraphics() )
+ return 0;
+
+ return mpGraphics->GetBitCount();
+}
+
+void OutputDevice::SetOutOffXPixel(long nOutOffX)
+{
+ mnOutOffX = nOutOffX;
+}
+
+void OutputDevice::SetOutOffYPixel(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;
+ 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;
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ long nSrcWidth = ImplLogicWidthToDevicePixel( rSrcSize.Width() );
+ long nSrcHeight = ImplLogicHeightToDevicePixel( rSrcSize.Height() );
+ long nDestWidth = ImplLogicWidthToDevicePixel( rDestSize.Width() );
+ 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);
+
+ const tools::Rectangle aSrcOutRect( Point( mnOutOffX, mnOutOffY ),
+ Size( mnOutWidth, mnOutHeight ) );
+
+ AdjustTwoRect( aPosAry, aSrcOutRect );
+
+ if ( aPosAry.mnSrcWidth && aPosAry.mnSrcHeight && aPosAry.mnDestWidth && aPosAry.mnDestHeight )
+ mpGraphics->CopyBits( aPosAry, nullptr, this, nullptr );
+ }
+
+ 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;
+
+ 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;
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ long nSrcWidth = ImplLogicWidthToDevicePixel( rSrcSize.Width() );
+ 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);
+
+ const tools::Rectangle aSrcOutRect( Point( mnOutOffX, mnOutOffY ),
+ Size( mnOutWidth, mnOutHeight ) );
+
+ AdjustTwoRect( aPosAry, aSrcOutRect );
+
+ 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, nullptr, this, nullptr);
+}
+
+// Direct OutputDevice drawing private function
+
+void OutputDevice::drawOutDevDirect( const OutputDevice* pSrcDev, SalTwoRect& rPosAry )
+{
+ SalGraphics* pSrcGraphics;
+ if (const OutputDevice* pCheckedSrc = DrawOutDevDirectCheck(pSrcDev))
+ {
+ if (!pCheckedSrc->mpGraphics && !pCheckedSrc->AcquireGraphics())
+ return;
+ pSrcGraphics = pCheckedSrc->mpGraphics;
+ }
+ else
+ pSrcGraphics = nullptr;
+
+ if (!mpGraphics && !AcquireGraphics())
+ return;
+
+ // #102532# Offset only has to be pseudo window offset
+ const tools::Rectangle aSrcOutRect( Point( pSrcDev->mnOutOffX, pSrcDev->mnOutOffY ),
+ Size( pSrcDev->mnOutWidth, pSrcDev->mnOutHeight ) );
+
+ AdjustTwoRect( rPosAry, aSrcOutRect );
+
+ if ( rPosAry.mnSrcWidth && rPosAry.mnSrcHeight && rPosAry.mnDestWidth && rPosAry.mnDestHeight )
+ {
+ // if this is no window, but pSrcDev is a window
+ // mirroring may be required
+ // because only windows have a SalGraphicsLayout
+ // mirroring is performed here
+ DrawOutDevDirectProcess( pSrcDev, rPosAry, pSrcGraphics);
+ }
+}
+
+const OutputDevice* OutputDevice::DrawOutDevDirectCheck(const OutputDevice* pSrcDev) const
+{
+ return this == pSrcDev ? nullptr : pSrcDev;
+}
+
+void OutputDevice::DrawOutDevDirectProcess( const OutputDevice* pSrcDev, SalTwoRect& rPosAry, SalGraphics* pSrcGraphics )
+{
+ if( pSrcGraphics && (pSrcGraphics->GetLayout() & SalLayoutFlags::BiDiRtl) )
+ {
+ SalTwoRect aPosAry2 = rPosAry;
+ pSrcGraphics->mirror( aPosAry2.mnSrcX, aPosAry2.mnSrcWidth, pSrcDev );
+ mpGraphics->CopyBits( aPosAry2, pSrcGraphics, this, pSrcDev );
+ }
+ else
+ mpGraphics->CopyBits( rPosAry, pSrcGraphics, this, pSrcDev );
+}
+
+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
+{
+ 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;
+}
+
+// EPS public function
+
+bool OutputDevice::DrawEPS( const Point& rPoint, const Size& rSize,
+ const GfxLink& rGfxLink, GDIMetaFile* pSubst )
+{
+ bool bDrawn(true);
+
+ if ( mpMetaFile )
+ {
+ GDIMetaFile aSubst;
+
+ if( pSubst )
+ aSubst = *pSubst;
+
+ mpMetaFile->AddAction( new MetaEPSAction( rPoint, rSize, rGfxLink, aSubst ) );
+ }
+
+ if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() )
+ return bDrawn;
+
+ if( mbOutputClipped )
+ return bDrawn;
+
+ tools::Rectangle aRect( ImplLogicToDevicePixel( tools::Rectangle( rPoint, rSize ) ) );
+
+ if( !aRect.IsEmpty() )
+ {
+ // draw the real EPS graphics
+ if( rGfxLink.GetData() && rGfxLink.GetDataSize() )
+ {
+ if( !mpGraphics && !AcquireGraphics() )
+ return bDrawn;
+
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ aRect.Justify();
+ bDrawn = mpGraphics->DrawEPS( aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight(),
+ const_cast<sal_uInt8*>(rGfxLink.GetData()), rGfxLink.GetDataSize(), this );
+ }
+
+ // else draw the substitution graphics
+ if( !bDrawn && pSubst )
+ {
+ GDIMetaFile* pOldMetaFile = mpMetaFile;
+
+ mpMetaFile = nullptr;
+ Graphic( *pSubst ).Draw( this, rPoint, rSize );
+ mpMetaFile = pOldMetaFile;
+ }
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawEPS( rPoint, rSize, rGfxLink, pSubst );
+
+ return bDrawn;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/outdevstate.cxx b/vcl/source/outdev/outdevstate.cxx
new file mode 100644
index 000000000..a9ca52d94
--- /dev/null
+++ b/vcl/source/outdev/outdevstate.cxx
@@ -0,0 +1,609 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+
+#include <tools/debug.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/outdevstate.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/settings.hxx>
+
+#include <outdev.h>
+#include <outdata.hxx>
+#include <salgdi.hxx>
+
+OutDevState::OutDevState()
+ : mbMapActive(false)
+ , meTextAlign(ALIGN_TOP)
+ , meRasterOp(RasterOp::OverPaint)
+ , mnTextLayoutMode(ComplexTextLayoutFlags::Default)
+ , meTextLanguage(0)
+ , mnFlags(PushFlags::NONE)
+{
+}
+
+OutDevState::OutDevState(OutDevState&&) = default;
+
+OutDevState::~OutDevState()
+{
+ mpLineColor.reset();
+ mpFillColor.reset();
+ mpFont.reset();
+ mpTextColor.reset();
+ mpTextFillColor.reset();
+ mpTextLineColor.reset();
+ mpOverlineColor.reset();
+ mpMapMode.reset();
+ mpClipRegion.reset();
+ mpRefPoint.reset();
+}
+
+void OutputDevice::Push( PushFlags nFlags )
+{
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaPushAction( nFlags ) );
+
+ maOutDevStateStack.emplace_back();
+ OutDevState& rState = maOutDevStateStack.back();
+
+ rState.mnFlags = nFlags;
+
+ if (nFlags & PushFlags::LINECOLOR && mbLineColor)
+ {
+ rState.mpLineColor = maLineColor;
+ }
+ if (nFlags & PushFlags::FILLCOLOR && mbFillColor)
+ {
+ rState.mpFillColor = maFillColor;
+ }
+ if ( nFlags & PushFlags::FONT )
+ rState.mpFont.reset( new vcl::Font( maFont ) );
+ if ( nFlags & PushFlags::TEXTCOLOR )
+ rState.mpTextColor = GetTextColor();
+ if (nFlags & PushFlags::TEXTFILLCOLOR && IsTextFillColor())
+ {
+ rState.mpTextFillColor = GetTextFillColor();
+ }
+ if (nFlags & PushFlags::TEXTLINECOLOR && IsTextLineColor())
+ {
+ rState.mpTextLineColor = GetTextLineColor();
+ }
+ if (nFlags & PushFlags::OVERLINECOLOR && IsOverlineColor())
+ {
+ rState.mpOverlineColor = GetOverlineColor();
+ }
+ if ( nFlags & PushFlags::TEXTALIGN )
+ rState.meTextAlign = GetTextAlign();
+ if( nFlags & PushFlags::TEXTLAYOUTMODE )
+ rState.mnTextLayoutMode = GetLayoutMode();
+ if( nFlags & PushFlags::TEXTLANGUAGE )
+ rState.meTextLanguage = GetDigitLanguage();
+ if ( nFlags & PushFlags::RASTEROP )
+ rState.meRasterOp = GetRasterOp();
+ if ( nFlags & PushFlags::MAPMODE )
+ {
+ rState.mpMapMode = maMapMode;
+ rState.mbMapActive = mbMap;
+ }
+ if (nFlags & PushFlags::CLIPREGION && mbClipRegion)
+ {
+ rState.mpClipRegion.reset( new vcl::Region( maRegion ) );
+ }
+ if (nFlags & PushFlags::REFPOINT && mbRefPoint)
+ {
+ rState.mpRefPoint = maRefPoint;
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->Push();
+}
+
+void OutputDevice::Pop()
+{
+
+ if( mpMetaFile )
+ mpMetaFile->AddAction( new MetaPopAction() );
+
+ GDIMetaFile* pOldMetaFile = mpMetaFile;
+ mpMetaFile = nullptr;
+
+ if ( maOutDevStateStack.empty() )
+ {
+ SAL_WARN( "vcl.gdi", "OutputDevice::Pop() without OutputDevice::Push()" );
+ return;
+ }
+ const OutDevState& rState = maOutDevStateStack.back();
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->Pop();
+
+ if ( rState.mnFlags & PushFlags::LINECOLOR )
+ {
+ if ( rState.mpLineColor )
+ SetLineColor( *rState.mpLineColor );
+ else
+ SetLineColor();
+ }
+
+ if ( rState.mnFlags & PushFlags::FILLCOLOR )
+ {
+ if ( rState.mpFillColor )
+ SetFillColor( *rState.mpFillColor );
+ else
+ SetFillColor();
+ }
+
+ if ( rState.mnFlags & PushFlags::FONT )
+ SetFont( *rState.mpFont );
+
+ if ( rState.mnFlags & PushFlags::TEXTCOLOR )
+ SetTextColor( *rState.mpTextColor );
+
+ if ( rState.mnFlags & PushFlags::TEXTFILLCOLOR )
+ {
+ if ( rState.mpTextFillColor )
+ SetTextFillColor( *rState.mpTextFillColor );
+ else
+ SetTextFillColor();
+ }
+
+ if ( rState.mnFlags & PushFlags::TEXTLINECOLOR )
+ {
+ if ( rState.mpTextLineColor )
+ SetTextLineColor( *rState.mpTextLineColor );
+ else
+ SetTextLineColor();
+ }
+
+ if ( rState.mnFlags & PushFlags::OVERLINECOLOR )
+ {
+ if ( rState.mpOverlineColor )
+ SetOverlineColor( *rState.mpOverlineColor );
+ else
+ SetOverlineColor();
+ }
+
+ if ( rState.mnFlags & PushFlags::TEXTALIGN )
+ SetTextAlign( rState.meTextAlign );
+
+ if( rState.mnFlags & PushFlags::TEXTLAYOUTMODE )
+ SetLayoutMode( rState.mnTextLayoutMode );
+
+ if( rState.mnFlags & PushFlags::TEXTLANGUAGE )
+ SetDigitLanguage( rState.meTextLanguage );
+
+ if ( rState.mnFlags & PushFlags::RASTEROP )
+ SetRasterOp( rState.meRasterOp );
+
+ if ( rState.mnFlags & PushFlags::MAPMODE )
+ {
+ if ( rState.mpMapMode )
+ SetMapMode( *rState.mpMapMode );
+ else
+ SetMapMode();
+ mbMap = rState.mbMapActive;
+ }
+
+ if ( rState.mnFlags & PushFlags::CLIPREGION )
+ SetDeviceClipRegion( rState.mpClipRegion.get() );
+
+ if ( rState.mnFlags & PushFlags::REFPOINT )
+ {
+ if ( rState.mpRefPoint )
+ SetRefPoint( *rState.mpRefPoint );
+ else
+ SetRefPoint();
+ }
+
+ maOutDevStateStack.pop_back();
+
+ mpMetaFile = pOldMetaFile;
+}
+
+sal_uInt32 OutputDevice::GetGCStackDepth() const
+{
+ return maOutDevStateStack.size();
+}
+
+void OutputDevice::ClearStack()
+{
+ sal_uInt32 nCount = GetGCStackDepth();
+ while( nCount-- )
+ Pop();
+}
+
+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->setAntiAliasB2DDraw(bool(mnAntialiasing & AntialiasingFlags::EnableB2dDraw));
+ }
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetAntialiasing( nMode );
+}
+
+void OutputDevice::SetDrawMode( DrawModeFlags nDrawMode )
+{
+
+ mnDrawMode = nDrawMode;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetDrawMode( nDrawMode );
+}
+
+void OutputDevice::SetLayoutMode( 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::SetRasterOp( RasterOp eRasterOp )
+{
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaRasterOpAction( eRasterOp ) );
+
+ if ( meRasterOp != eRasterOp )
+ {
+ meRasterOp = eRasterOp;
+ mbInitLineColor = mbInitFillColor = true;
+
+ if( mpGraphics || AcquireGraphics() )
+ mpGraphics->SetXORMode( (RasterOp::Invert == meRasterOp) || (RasterOp::Xor == meRasterOp), RasterOp::Invert == meRasterOp );
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetRasterOp( eRasterOp );
+}
+
+
+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( rColor );
+
+ if( mnDrawMode & ( DrawModeFlags::BlackFill | DrawModeFlags::WhiteFill |
+ DrawModeFlags::GrayFill | DrawModeFlags::NoFill |
+ DrawModeFlags::SettingsFill ) )
+ {
+ if( !ImplIsColorTransparent( aColor ) )
+ {
+ if( mnDrawMode & DrawModeFlags::BlackFill )
+ {
+ aColor = COL_BLACK;
+ }
+ else if( mnDrawMode & DrawModeFlags::WhiteFill )
+ {
+ aColor = COL_WHITE;
+ }
+ else if( mnDrawMode & DrawModeFlags::GrayFill )
+ {
+ const sal_uInt8 cLum = aColor.GetLuminance();
+ aColor = Color( cLum, cLum, cLum );
+ }
+ else if( mnDrawMode & DrawModeFlags::NoFill )
+ {
+ aColor = COL_TRANSPARENT;
+ }
+ else if( mnDrawMode & DrawModeFlags::SettingsFill )
+ {
+ aColor = GetSettings().GetStyleSettings().GetWindowColor();
+ }
+ }
+ }
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaFillColorAction( aColor, true ) );
+
+ if ( ImplIsColorTransparent( aColor ) )
+ {
+ if ( mbFillColor )
+ {
+ mbInitFillColor = true;
+ mbFillColor = false;
+ maFillColor = COL_TRANSPARENT;
+ }
+ }
+ else
+ {
+ if ( maFillColor != aColor )
+ {
+ mbInitFillColor = true;
+ mbFillColor = true;
+ maFillColor = aColor;
+ }
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetFillColor( COL_BLACK );
+}
+
+void OutputDevice::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 = ImplDrawModeToColor( rColor );
+
+ if( mpMetaFile )
+ mpMetaFile->AddAction( new MetaLineColorAction( aColor, true ) );
+
+ if( ImplIsColorTransparent( aColor ) )
+ {
+ if ( mbLineColor )
+ {
+ mbInitLineColor = true;
+ mbLineColor = false;
+ maLineColor = COL_TRANSPARENT;
+ }
+ }
+ else
+ {
+ if( maLineColor != aColor )
+ {
+ mbInitLineColor = true;
+ mbLineColor = true;
+ maLineColor = aColor;
+ }
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetLineColor( COL_BLACK );
+}
+
+void OutputDevice::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 )
+ mpAlphaVDev->SetBackground( rBackground );
+}
+
+void OutputDevice::SetFont( const vcl::Font& rNewFont )
+{
+
+ vcl::Font aFont( rNewFont );
+ if ( mnDrawMode & (DrawModeFlags::BlackText | DrawModeFlags::WhiteText | DrawModeFlags::GrayText | DrawModeFlags::SettingsText |
+ DrawModeFlags::BlackFill | DrawModeFlags::WhiteFill | DrawModeFlags::GrayFill | DrawModeFlags::NoFill |
+ DrawModeFlags::SettingsFill ) )
+ {
+ Color aTextColor( aFont.GetColor() );
+
+ if ( mnDrawMode & DrawModeFlags::BlackText )
+ aTextColor = COL_BLACK;
+ else if ( mnDrawMode & DrawModeFlags::WhiteText )
+ aTextColor = COL_WHITE;
+ else if ( mnDrawMode & DrawModeFlags::GrayText )
+ {
+ const sal_uInt8 cLum = aTextColor.GetLuminance();
+ aTextColor = Color( cLum, cLum, cLum );
+ }
+ else if ( mnDrawMode & DrawModeFlags::SettingsText )
+ aTextColor = GetSettings().GetStyleSettings().GetFontColor();
+
+ aFont.SetColor( aTextColor );
+
+ bool bTransFill = aFont.IsTransparent();
+ if ( !bTransFill )
+ {
+ Color aTextFillColor( aFont.GetFillColor() );
+
+ if ( mnDrawMode & DrawModeFlags::BlackFill )
+ aTextFillColor = COL_BLACK;
+ else if ( mnDrawMode & DrawModeFlags::WhiteFill )
+ aTextFillColor = COL_WHITE;
+ else if ( mnDrawMode & DrawModeFlags::GrayFill )
+ {
+ const sal_uInt8 cLum = aTextFillColor.GetLuminance();
+ aTextFillColor = Color( cLum, cLum, cLum );
+ }
+ else if( mnDrawMode & DrawModeFlags::SettingsFill )
+ aTextFillColor = GetSettings().GetStyleSettings().GetWindowColor();
+ else if ( mnDrawMode & DrawModeFlags::NoFill )
+ {
+ aTextFillColor = COL_TRANSPARENT;
+ }
+
+ aFont.SetFillColor( aTextFillColor );
+ }
+ }
+
+ 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 ) )
+ {
+ // 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 )
+ {
+ // #i30463#
+ // Since SetFont might change the text color, apply that only
+ // selectively to alpha vdev (which normally paints opaque text
+ // with COL_BLACK)
+ if( aFont.GetColor() != COL_TRANSPARENT )
+ {
+ mpAlphaVDev->SetTextColor( COL_BLACK );
+ aFont.SetColor( COL_TRANSPARENT );
+ }
+
+ mpAlphaVDev->SetFont( aFont );
+ }
+ }
+}
+
+
+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::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;
+}
+
+void OutputDevice::ImplReleaseFonts()
+{
+ mpGraphics->ReleaseFonts();
+
+ mbNewFont = true;
+ mbInitFont = true;
+
+ mpFontInstance.clear();
+ mpDeviceFontList.reset();
+ mpDeviceFontSizeList.reset();
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/pixel.cxx b/vcl/source/outdev/pixel.cxx
new file mode 100644
index 000000000..0a80a8f1e
--- /dev/null
+++ b/vcl/source/outdev/pixel.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 <cassert>
+
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/virdev.hxx>
+
+#include <salgdi.hxx>
+
+Color OutputDevice::GetPixel(const Point& rPoint) const
+{
+ Color aColor;
+
+ if (mpGraphics || AcquireGraphics())
+ {
+ if (mbInitClipRegion)
+ const_cast<OutputDevice*>(this)->InitClipRegion();
+
+ if (!mbOutputClipped)
+ {
+ const long nX = ImplLogicXToDevicePixel(rPoint.X());
+ const long nY = ImplLogicYToDevicePixel(rPoint.Y());
+ aColor = mpGraphics->GetPixel(nX, nY, this);
+
+ if (mpAlphaVDev)
+ {
+ Color aAlphaColor = mpAlphaVDev->GetPixel(rPoint);
+ aColor.SetTransparency(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;
+
+ 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 = ImplDrawModeToColor( rColor );
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaPixelAction( rPt, aColor ) );
+
+ if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() )
+ return;
+
+ Point aPt = ImplLogicToDevicePixel( rPt );
+
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ mpGraphics->DrawPixel( aPt.X(), aPt.Y(), aColor, this );
+
+ if (mpAlphaVDev)
+ {
+ Color aAlphaColor(rColor.GetTransparency(), rColor.GetTransparency(), rColor.GetTransparency());
+ mpAlphaVDev->DrawPixel(rPt, aAlphaColor);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/polygon.cxx b/vcl/source/outdev/polygon.cxx
new file mode 100644
index 000000000..e031fb059
--- /dev/null
+++ b/vcl/source/outdev/polygon.cxx
@@ -0,0 +1,518 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <cassert>
+
+#include <sal/types.h>
+
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <memory>
+#include <tools/poly.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/virdev.hxx>
+
+#include <salgdi.hxx>
+
+#define OUTDEV_POLYPOLY_STACKBUF 32
+
+void OutputDevice::DrawPolyPolygon( const tools::PolyPolygon& rPolyPoly )
+{
+ assert(!is_double_buffered_window());
+
+ if( mpMetaFile )
+ mpMetaFile->AddAction( new MetaPolyPolygonAction( rPolyPoly ) );
+
+ sal_uInt16 nPoly = rPolyPoly.Count();
+
+ if ( !IsDeviceOutputNecessary() || (!mbLineColor && !mbFillColor) || !nPoly || ImplIsRecordLayout() )
+ return;
+
+ // we need a graphics
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ if ( mbInitFillColor )
+ InitFillColor();
+
+ // use b2dpolygon drawing if possible
+ if((mnAntialiasing & AntialiasingFlags::EnableB2dDraw) &&
+ mpGraphics->supportsOperation(OutDevSupportType::B2DDraw) &&
+ RasterOp::OverPaint == GetRasterOp() &&
+ (IsLineColor() || IsFillColor()))
+ {
+ const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation());
+ basegfx::B2DPolyPolygon aB2DPolyPolygon(rPolyPoly.getB2DPolyPolygon());
+ bool bSuccess(true);
+
+ // ensure closed - may be asserted, will prevent buffering
+ if(!aB2DPolyPolygon.isClosed())
+ {
+ aB2DPolyPolygon.setClosed(true);
+ }
+
+ if(IsFillColor())
+ {
+ bSuccess = mpGraphics->DrawPolyPolygon(
+ aTransform,
+ aB2DPolyPolygon,
+ 0.0,
+ this);
+ }
+
+ if(bSuccess && IsLineColor())
+ {
+ const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
+
+ for(auto const& rPolygon : 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;
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ if ( mbInitFillColor )
+ InitFillColor();
+
+ // use b2dpolygon drawing if possible
+ if((mnAntialiasing & AntialiasingFlags::EnableB2dDraw) &&
+ mpGraphics->supportsOperation(OutDevSupportType::B2DDraw) &&
+ RasterOp::OverPaint == GetRasterOp() &&
+ (IsLineColor() || IsFillColor()))
+ {
+ const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation());
+ basegfx::B2DPolygon aB2DPolygon(rPoly.getB2DPolygon());
+ bool bSuccess(true);
+
+ // ensure closed - maybe assert, hinders buffering
+ if(!aB2DPolygon.isClosed())
+ {
+ aB2DPolygon.setClosed(true);
+ }
+
+ if(IsFillColor())
+ {
+ bSuccess = mpGraphics->DrawPolyPolygon(
+ aTransform,
+ basegfx::B2DPolyPolygon(aB2DPolygon),
+ 0.0,
+ this);
+ }
+
+ if(bSuccess && IsLineColor())
+ {
+ const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
+
+ bSuccess = mpGraphics->DrawPolyLine(
+ aTransform,
+ aB2DPolygon,
+ 0.0,
+ 0.0, // tdf#124848 hairline
+ nullptr, // MM01
+ basegfx::B2DLineJoin::NONE,
+ css::drawing::LineCap_BUTT,
+ basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
+ bPixelSnapHairline,
+ this);
+ }
+
+ if(bSuccess)
+ {
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawPolygon( rPoly );
+ return;
+ }
+ }
+
+ tools::Polygon aPoly = ImplLogicToDevicePixel( rPoly );
+ const SalPoint* pPtAry = reinterpret_cast<const SalPoint*>(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 = reinterpret_cast<const SalPoint*>(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;
+
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ if( mbOutputClipped )
+ return;
+
+ if( mbInitLineColor )
+ InitLineColor();
+
+ if( mbInitFillColor )
+ InitFillColor();
+
+ bool bSuccess(false);
+
+ if((mnAntialiasing & AntialiasingFlags::EnableB2dDraw) &&
+ mpGraphics->supportsOperation(OutDevSupportType::B2DDraw) &&
+ RasterOp::OverPaint == GetRasterOp() &&
+ (IsLineColor() || IsFillColor()))
+ {
+ const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation());
+ basegfx::B2DPolyPolygon aB2DPolyPolygon(rB2DPolyPoly);
+ bSuccess = true;
+
+ // ensure closed - maybe assert, hinders buffering
+ if(!aB2DPolyPolygon.isClosed())
+ {
+ aB2DPolyPolygon.setClosed(true);
+ }
+
+ if(IsFillColor())
+ {
+ bSuccess = mpGraphics->DrawPolyPolygon(
+ aTransform,
+ aB2DPolyPolygon,
+ 0.0,
+ this);
+ }
+
+ if(bSuccess && IsLineColor())
+ {
+ const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
+
+ for(auto const& rPolygon : 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];
+ PCONSTSALPOINT aStackAry2[OUTDEV_POLYPOLY_STACKBUF];
+ PolyFlags* aStackAry3[OUTDEV_POLYPOLY_STACKBUF];
+ sal_uInt32* pPointAry;
+ PCONSTSALPOINT* 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 PCONSTSALPOINT[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] = reinterpret_cast<PCONSTSALPOINT>(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(), reinterpret_cast<const SalPoint*>(aPoly.GetConstPointAry()), this );
+ }
+ }
+ else
+ {
+ mpGraphics->DrawPolygon( *pPointAry, *pPointAryAry, this );
+ }
+ }
+ else
+ {
+ // #100127# Forward beziers to sal, if any
+ if( bHaveBezier )
+ {
+ if( !mpGraphics->DrawPolyPolygonBezier( j, pPointAry, pPointAryAry, pFlagAryAry, this ) )
+ {
+ tools::PolyPolygon aPolyPoly = tools::PolyPolygon::SubdivideBezier( rPolyPoly );
+ ImplDrawPolyPolygon( aPolyPoly.Count(), aPolyPoly );
+ }
+ }
+ else
+ {
+ mpGraphics->DrawPolyPolygon( j, pPointAry, pPointAryAry, this );
+ }
+ }
+
+ if ( pPointAry != aStackAry1 )
+ {
+ delete[] pPointAry;
+ delete[] pPointAryAry;
+ delete[] pFlagAryAry;
+ }
+}
+
+void OutputDevice::ImplDrawPolygon( const tools::Polygon& rPoly, const tools::PolyPolygon* pClipPolyPoly )
+{
+ if( pClipPolyPoly )
+ {
+ ImplDrawPolyPolygon( rPoly, pClipPolyPoly );
+ }
+ else
+ {
+ sal_uInt16 nPoints = rPoly.GetSize();
+
+ if ( nPoints < 2 )
+ return;
+
+ const SalPoint* pPtAry = reinterpret_cast<const SalPoint*>(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 SalPoint* pPtAry = reinterpret_cast<const SalPoint*>(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<PCONSTSALPOINT[]> pPointAryAry(new PCONSTSALPOINT[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] = reinterpret_cast<PCONSTSALPOINT>(rPoly.GetConstPointAry());
+ i++;
+ }
+ else
+ nCount--;
+ }
+ while( i < nCount );
+
+ if( nCount == 1 )
+ mpGraphics->DrawPolygon( pPointAry[0], pPointAryAry[0], this );
+ else
+ mpGraphics->DrawPolyPolygon( nCount, pPointAry.get(), pPointAryAry.get(), this );
+ }
+
+ if( pClipPolyPoly )
+ delete pPolyPoly;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/polyline.cxx b/vcl/source/outdev/polyline.cxx
new file mode 100644
index 000000000..672854a9e
--- /dev/null
+++ b/vcl/source/outdev/polyline.cxx
@@ -0,0 +1,386 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <cassert>
+
+#include <sal/types.h>
+
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/polygon/b2dlinegeometry.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/virdev.hxx>
+
+#include <salgdi.hxx>
+
+void OutputDevice::DrawPolyLine( const tools::Polygon& rPoly )
+{
+ assert(!is_double_buffered_window());
+
+ if( mpMetaFile )
+ mpMetaFile->AddAction( new MetaPolyLineAction( rPoly ) );
+
+ sal_uInt16 nPoints = rPoly.GetSize();
+
+ if ( !IsDeviceOutputNecessary() || !mbLineColor || (nPoints < 2) || ImplIsRecordLayout() )
+ return;
+
+ // we need a graphics
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ // use b2dpolygon drawing if possible
+ if(DrawPolyLineDirect(
+ basegfx::B2DHomMatrix(),
+ rPoly.getB2DPolygon()))
+ {
+ return;
+ }
+
+ const basegfx::B2DPolygon aB2DPolyLine(rPoly.getB2DPolygon());
+ const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation());
+ const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
+
+ if(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))
+ {
+ return;
+ }
+
+ tools::Polygon aPoly = ImplLogicToDevicePixel( rPoly );
+ SalPoint* pPtAry = reinterpret_cast<SalPoint*>(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 = reinterpret_cast<SalPoint*>(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;
+ }
+
+ // #i101491#
+ // Try direct Fallback to B2D-Version of DrawPolyLine
+ if((mnAntialiasing & AntialiasingFlags::EnableB2dDraw) &&
+ LineStyle::Solid == rLineInfo.GetStyle())
+ {
+ DrawPolyLine(
+ rPoly.getB2DPolygon(),
+ static_cast< double >(rLineInfo.GetWidth()),
+ rLineInfo.GetLineJoin(),
+ rLineInfo.GetLineCap(),
+ basegfx::deg2rad(15.0) /* default fMiterMinimumAngle, value not available in LineInfo */);
+ 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( static_cast<long>(fLineWidth+0.5) );
+
+ const tools::Polygon aToolsPolygon( rB2DPolygon );
+ mpMetaFile->AddAction( new MetaPolyLineAction( aToolsPolygon, aLineInfo ) );
+ }
+
+ // Do not paint empty PolyPolygons
+ if(!rB2DPolygon.count() || !IsDeviceOutputNecessary())
+ return;
+
+ // we need a graphics
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ if( mbOutputClipped )
+ return;
+
+ if( mbInitLineColor )
+ InitLineColor();
+
+ // use b2dpolygon drawing if possible
+ if(DrawPolyLineDirect(
+ 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();
+
+ const bool bTryAA((mnAntialiasing & AntialiasingFlags::EnableB2dDraw) &&
+ mpGraphics->supportsOperation(OutDevSupportType::B2DDraw) &&
+ RasterOp::OverPaint == GetRasterOp() &&
+ IsLineColor());
+
+ // when AA it is necessary to also paint the filled polygon's outline
+ // to avoid optical gaps
+ for(auto const& rPolygon : aAreaPolyPolygon)
+ {
+ (void)DrawPolyLineDirect(
+ basegfx::B2DHomMatrix(),
+ rPolygon,
+ 0.0,
+ 0.0,
+ nullptr, // MM01
+ basegfx::B2DLineJoin::NONE,
+ css::drawing::LineCap_BUTT,
+ basegfx::deg2rad(15.0) /*default, not used*/,
+ bTryAA);
+ }
+ }
+ else
+ {
+ // fallback to old polygon drawing if needed
+ const tools::Polygon aToolsPolygon( rB2DPolygon );
+ LineInfo aLineInfo;
+ if( fLineWidth != 0.0 )
+ aLineInfo.SetWidth( static_cast<long>(fLineWidth+0.5) );
+
+ 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;
+
+ tools::Polygon aPoly = ImplLogicToDevicePixel( rPoly );
+
+ // we need a graphics
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+
+ 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)
+ {
+ drawLine ( basegfx::B2DPolyPolygon(aPoly.getB2DPolygon()), aInfo );
+ }
+ else
+ {
+ // #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, reinterpret_cast<SalPoint*>(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,
+ bool bBypassAACheck)
+{
+ 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;
+
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ if( mbOutputClipped )
+ return true;
+
+ if( mbInitLineColor )
+ InitLineColor();
+
+ const bool bTryAA( bBypassAACheck ||
+ ((mnAntialiasing & AntialiasingFlags::EnableB2dDraw) &&
+ mpGraphics->supportsOperation(OutDevSupportType::B2DDraw) &&
+ RasterOp::OverPaint == GetRasterOp() &&
+ IsLineColor()));
+
+ if(bTryAA)
+ {
+ // 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 )
+ {
+ // worked, add metafile action (if recorded) and return true
+ if( mpMetaFile )
+ {
+ LineInfo aLineInfo;
+ if( fLineWidth != 0.0 )
+ aLineInfo.SetWidth( static_cast<long>(fLineWidth+0.5) );
+ // Transport known information, might be needed
+ aLineInfo.SetLineJoin(eLineJoin);
+ aLineInfo.SetLineCap(eLineCap);
+ // MiterMinimumAngle does not exist yet in LineInfo
+ const tools::Polygon aToolsPolygon( rB2DPolygon );
+ mpMetaFile->AddAction( new MetaPolyLineAction( aToolsPolygon, aLineInfo ) );
+ }
+
+ if (mpAlphaVDev)
+ mpAlphaVDev->DrawPolyLineDirect(rObjectTransform, rB2DPolygon, fLineWidth,
+ fTransparency, pStroke, eLineJoin, eLineCap,
+ fMiterMinimumAngle, bBypassAACheck);
+
+ return true;
+ }
+ }
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/rect.cxx b/vcl/source/outdev/rect.cxx
new file mode 100644
index 000000000..ecbeb12fb
--- /dev/null
+++ b/vcl/source/outdev/rect.cxx
@@ -0,0 +1,415 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <cassert>
+
+#include <sal/types.h>
+
+#include <tools/poly.hxx>
+#include <tools/helpers.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/virdev.hxx>
+
+#include <salgdi.hxx>
+
+void OutputDevice::DrawRect( const tools::Rectangle& rRect )
+{
+ assert(!is_double_buffered_window());
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaRectAction( rRect ) );
+
+ if ( !IsDeviceOutputNecessary() || (!mbLineColor && !mbFillColor) || ImplIsRecordLayout() )
+ return;
+
+ tools::Rectangle aRect( ImplLogicToDevicePixel( rRect ) );
+
+ if ( aRect.IsEmpty() )
+ return;
+
+ aRect.Justify();
+
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+
+ 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;
+
+ 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 )
+ {
+ SalPoint* pPtAry = reinterpret_cast<SalPoint*>(aRoundRectPoly.GetPointAry());
+
+ if ( !mbFillColor )
+ mpGraphics->DrawPolyLine( aRoundRectPoly.GetSize(), pPtAry, this );
+ else
+ mpGraphics->DrawPolygon( aRoundRectPoly.GetSize(), pPtAry, this );
+ }
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawRect( rRect, nHorzRound, nVertRound );
+}
+
+void OutputDevice::Invert( const tools::Rectangle& rRect, InvertFlags nFlags )
+{
+ assert(!is_double_buffered_window());
+ if ( !IsDeviceOutputNecessary() )
+ return;
+
+ tools::Rectangle aRect( ImplLogicToDevicePixel( rRect ) );
+
+ if ( aRect.IsEmpty() )
+ return;
+ aRect.Justify();
+
+ // we need a graphics
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+
+ 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;
+
+ 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 SalPoint* pPtAry = reinterpret_cast<const SalPoint*>(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(PushFlags::LINECOLOR|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;
+
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ if( mbOutputClipped )
+ return;
+
+ const long nDistX = std::max( rDist.Width(), 1L );
+ const long nDistY = std::max( rDist.Height(), 1L );
+ long nX = ( rRect.Left() >= aDstRect.Left() ) ? rRect.Left() : ( rRect.Left() + ( ( aDstRect.Left() - rRect.Left() ) / nDistX ) * nDistX );
+ long nY = ( rRect.Top() >= aDstRect.Top() ) ? rRect.Top() : ( rRect.Top() + ( ( aDstRect.Top() - rRect.Top() ) / nDistY ) * nDistY );
+ const long nRight = aDstRect.Right();
+ const long nBottom = aDstRect.Bottom();
+ const long nStartX = ImplLogicXToDevicePixel( nX );
+ const long nEndX = ImplLogicXToDevicePixel( nRight );
+ const long nStartY = ImplLogicYToDevicePixel( nY );
+ const long nEndY = ImplLogicYToDevicePixel( nBottom );
+ long nHorzCount = 0;
+ 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( long i = 0; i < nVertCount; i++ )
+ {
+ for( long j = 0, Y = aVertBuf[ i ]; j < nHorzCount; j++ )
+ {
+ mpGraphics->DrawPixel( aHorzBuf[ j ], Y, this );
+ }
+ }
+ }
+ else
+ {
+ if( nFlags & DrawGridFlags::HorzLines )
+ {
+ for( long i = 0; i < nVertCount; i++ )
+ {
+ nY = aVertBuf[ i ];
+ mpGraphics->DrawLine( nStartX, nY, nEndX, nY, this );
+ }
+ }
+
+ if( nFlags & DrawGridFlags::VertLines )
+ {
+ for( 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 long nDstX1 = rTwoRect.mnDestX + FRound( fFactorX * ( aCropRect.Left() - rTwoRect.mnSrcX ) );
+ const long nDstY1 = rTwoRect.mnDestY + FRound( fFactorY * ( aCropRect.Top() - rTwoRect.mnSrcY ) );
+ const long nDstX2 = rTwoRect.mnDestX + FRound( fFactorX * ( aCropRect.Right() - rTwoRect.mnSrcX ) );
+ const 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() ) )
+ {
+ 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 long nDstX1 = rTwoRect.mnDestX + FRound( fFactorX * ( aCropRect.Left() - rTwoRect.mnSrcX ) );
+ const long nDstY1 = rTwoRect.mnDestY + FRound( fFactorY * ( aCropRect.Top() - rTwoRect.mnSrcY ) );
+ const long nDstX2 = rTwoRect.mnDestX + FRound( fFactorX * ( aCropRect.Right() - rTwoRect.mnSrcX ) );
+ const 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/text.cxx b/vcl/source/outdev/text.cxx
new file mode 100644
index 000000000..34db8e629
--- /dev/null
+++ b/vcl/source/outdev/text.cxx
@@ -0,0 +1,2510 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <basegfx/matrix/b2dhommatrix.hxx>
+
+#include <com/sun/star/i18n/WordType.hpp>
+#include <com/sun/star/i18n/XBreakIterator.hpp>
+#include <com/sun/star/linguistic2/LinguServiceManager.hpp>
+
+#include <comphelper/processfactory.hxx>
+#include <osl/file.h>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+#include <tools/lineend.hxx>
+#include <tools/debug.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/metric.hxx>
+#include <vcl/textrectinfo.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/sysdata.hxx>
+#include <vcl/unohelp.hxx>
+#include <vcl/toolkit/controllayout.hxx>
+#ifdef MACOSX
+# include <vcl/opengl/OpenGLHelper.hxx>
+#endif
+
+#include <outdata.hxx>
+#include <outdev.h>
+#include <salgdi.hxx>
+#include <svdata.hxx>
+#include <textlayout.hxx>
+#include <textlineinfo.hxx>
+#include <impglyphitem.hxx>
+#include <optional>
+
+#define TEXT_DRAW_ELLIPSIS (DrawTextFlags::EndEllipsis | DrawTextFlags::PathEllipsis | DrawTextFlags::NewsEllipsis)
+
+ImplMultiTextLineInfo::ImplMultiTextLineInfo()
+{
+}
+
+ImplMultiTextLineInfo::~ImplMultiTextLineInfo()
+{
+}
+
+void ImplMultiTextLineInfo::AddLine( ImplTextLineInfo* pLine )
+{
+ mvLines.push_back(std::unique_ptr<ImplTextLineInfo>(pLine));
+}
+
+void ImplMultiTextLineInfo::Clear()
+{
+ mvLines.clear();
+}
+
+void OutputDevice::ImplInitTextColor()
+{
+ DBG_TESTSOLARMUTEX();
+
+ if ( mbInitTextColor )
+ {
+ mpGraphics->SetTextColor( GetTextColor() );
+ mbInitTextColor = false;
+ }
+}
+
+void OutputDevice::ImplDrawTextRect( long nBaseX, long nBaseY,
+ long nDistX, long nDistY, long nWidth, long nHeight )
+{
+ long nX = nDistX;
+ long nY = nDistY;
+
+ short nOrientation = mpFontInstance->mnOrientation;
+ if ( nOrientation )
+ {
+ // Rotate rect without rounding problems for 90 degree rotations
+ if ( !(nOrientation % 900) )
+ {
+ if ( nOrientation == 900 )
+ {
+ long nTemp = nX;
+ nX = nY;
+ nY = -nTemp;
+ nTemp = nWidth;
+ nWidth = nHeight;
+ nHeight = nTemp;
+ nY -= nHeight;
+ }
+ else if ( nOrientation == 1800 )
+ {
+ nX = -nX;
+ nY = -nY;
+ nX -= nWidth;
+ nY -= nHeight;
+ }
+ else /* ( nOrientation == 2700 ) */
+ {
+ 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 long nWidth = rSalLayout.GetTextWidth() / rSalLayout.GetUnitsPerPixel();
+ const Point aBase = rSalLayout.DrawBase();
+ const long nX = aBase.X();
+ const long nY = aBase.Y();
+
+ 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 )
+{
+ Point aPoint = rSalLayout.GetDrawPosition();
+ long nX = aPoint.X();
+ long nY = aPoint.Y();
+
+ long nWidth = rSalLayout.GetTextWidth();
+ long nHeight = mpFontInstance->mnLineHeight + mnEmphasisAscent + mnEmphasisDescent;
+
+ nY -= mpFontInstance->mxFontMetric->GetAscent() + mnEmphasisAscent;
+
+ if ( mpFontInstance->mnOrientation )
+ {
+ long nBaseX = nX, nBaseY = nY;
+ if ( !(mpFontInstance->mnOrientation % 900) )
+ {
+ long nX2 = nX+nWidth;
+ 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 )
+{
+ long nX = rSalLayout.DrawBase().X();
+ long nY = rSalLayout.DrawBase().Y();
+
+ tools::Rectangle aBoundRect;
+ rSalLayout.DrawBase() = Point( 0, 0 );
+ rSalLayout.DrawOffset() = Point( 0, 0 );
+ if (!rSalLayout.GetBoundRect(aBoundRect))
+ {
+ // guess vertical text extents if GetBoundRect failed
+ long nRight = rSalLayout.GetTextWidth();
+ long nTop = mpFontInstance->mxFontMetric->GetAscent() + mnEmphasisAscent;
+ 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, DeviceFormat::BITMASK);
+ VirtualDevice* pVDev = mpOutDevData->mpRotateDev;
+
+ // size it accordingly
+ if( !pVDev->SetOutputSizePixel( aBoundRect.GetSize() ) )
+ return false;
+
+ const FontSelectPattern& rPattern = mpFontInstance->GetFontSelectPattern();
+ vcl::Font aFont( GetFont() );
+ aFont.SetOrientation( 0 );
+ 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() -= aBoundRect.TopLeft();
+ rSalLayout.DrawText( *pVDev->mpGraphics );
+
+ Bitmap aBmp = pVDev->GetBitmap( Point(), aBoundRect.GetSize() );
+ if ( !aBmp || !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;
+ long nOldOffX = mnOutOffX;
+ 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;
+
+ long nOldX = rSalLayout.DrawBase().X();
+ if( HasMirroredGraphics() )
+ {
+ long w = IsVirtual() ? mnOutWidth : mpGraphics->GetGraphicsWidth();
+ long x = rSalLayout.DrawBase().X();
+ rSalLayout.DrawBase().setX( w - 1 - x );
+ if( !IsRTLEnabled() )
+ {
+ OutputDevice *pOutDevRef = this;
+ // mirror this window back
+ long devX = w-pOutDevRef->mnOutWidth-pOutDevRef->mnOutOffX; // re-mirrored mnOutOffX
+ rSalLayout.DrawBase().setX( devX + ( pOutDevRef->mnOutWidth - 1 - (rSalLayout.DrawBase().X() - devX) ) ) ;
+ }
+ }
+ else if( IsRTLEnabled() )
+ {
+ OutputDevice *pOutDevRef = this;
+
+ // mirror this window back
+ long devX = pOutDevRef->mnOutOffX; // re-mirrored mnOutOffX
+ rSalLayout.DrawBase().setX( pOutDevRef->mnOutWidth - 1 - (rSalLayout.DrawBase().X() - 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();
+
+ Point 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
+ 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() )
+ {
+ 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() += Point( nOff, nOff );
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() -= Point( nOff, nOff );
+ SetTextColor( aOldColor );
+ SetTextLineColor( aOldTextLineColor );
+ SetOverlineColor( aOldOverlineColor );
+ ImplInitTextColor();
+
+ if ( !maFont.IsOutline() )
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ }
+
+ if ( maFont.IsOutline() )
+ {
+ rSalLayout.DrawBase() = aOrigPos + Point(-1,-1);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() = aOrigPos + Point(+1,+1);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() = aOrigPos + Point(-1,+0);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() = aOrigPos + Point(-1,+1);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() = aOrigPos + Point(+0,+1);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() = aOrigPos + Point(+0,-1);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() = aOrigPos + Point(+1,-1);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() = aOrigPos + Point(+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() += Point( mnTextOffX, mnTextOffY );
+
+ if( IsTextFillColor() )
+ ImplDrawTextBackground( rSalLayout );
+
+ if( mbTextSpecial )
+ ImplDrawSpecialText( rSalLayout );
+ else
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+}
+
+long OutputDevice::ImplGetTextLines( ImplMultiTextLineInfo& rLineInfo,
+ long nWidth, const OUString& rStr,
+ DrawTextFlags nStyle, const vcl::ITextLayout& _rLayout )
+{
+ SAL_WARN_IF( nWidth <= 0, "vcl", "ImplGetTextLines: nWidth <= 0!" );
+
+ if ( nWidth <= 0 )
+ nWidth = 1;
+
+ long nMaxLineWidth = 0;
+ rLineInfo.Clear();
+ if (!rStr.isEmpty())
+ {
+ 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();
+ while ( nPos < nLen )
+ {
+ sal_Int32 nBreakPos = nPos;
+
+ while ( ( nBreakPos < nLen ) && ( rStr[ nBreakPos ] != '\r' ) && ( rStr[ nBreakPos ] != '\n' ) )
+ nBreakPos++;
+
+ long nLineWidth = _rLayout.GetTextWidth( rStr, nPos, nBreakPos-nPos );
+ if ( ( nLineWidth > nWidth ) && ( nStyle & DrawTextFlags::WordBreak ) )
+ {
+ if ( !xBI.is() )
+ xBI = vcl::unohelper::CreateBreakIterator();
+
+ if ( xBI.is() )
+ {
+ const css::lang::Locale& rDefLocale(Application::GetSettings().GetUILanguageTag().getLocale());
+ sal_Int32 nSoftBreak = _rLayout.GetTextBreak( rStr, nWidth, nPos, nBreakPos - nPos );
+ if (nSoftBreak == -1)
+ {
+ nSoftBreak = nPos;
+ }
+ SAL_WARN_IF( nSoftBreak >= nBreakPos, "vcl", "Break?!" );
+ css::i18n::LineBreakHyphenationOptions aHyphOptions( xHyph, css::uno::Sequence <css::beans::PropertyValue>(), 1 );
+ css::i18n::LineBreakUserOptions aUserOptions;
+ css::i18n::LineBreakResults aLBR = xBI->getLineBreak( rStr, nSoftBreak, rDefLocale, nPos, aHyphOptions, aUserOptions );
+ nBreakPos = aLBR.breakIndex;
+ if ( nBreakPos <= nPos )
+ nBreakPos = nSoftBreak;
+ if ( bHyphenate )
+ {
+ // 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() )
+ {
+ sal_Unicode cAlternateReplChar = 0;
+ css::i18n::Boundary aBoundary = xBI->getWordBoundary( rStr, nBreakPos, rDefLocale, css::i18n::WordType::DICTIONARY_WORD, true );
+ sal_Int32 nWordStart = nPos;
+ sal_Int32 nWordEnd = aBoundary.endPos;
+ SAL_WARN_IF( nWordEnd <= nWordStart, "vcl", "ImpBreakLine: Start >= End?" );
+
+ sal_Int32 nWordLen = nWordEnd - nWordStart;
+ if ( ( nWordEnd >= nSoftBreak ) && ( nWordLen > 3 ) )
+ {
+ // #104415# May happen, because getLineBreak may differ from getWordBoudary 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())
+ {
+ bool bAlternate = xHyphWord->isAlternativeSpelling();
+ sal_Int32 _nWordLen = 1 + xHyphWord->getHyphenPos();
+
+ if ( ( _nWordLen >= 2 ) && ( (nWordStart+_nWordLen) >= 2 ) )
+ {
+ if ( !bAlternate )
+ {
+ nBreakPos = nWordStart + _nWordLen;
+ }
+ else
+ {
+ OUString aAlt( xHyphWord->getHyphenatedWord() );
+
+ // We can have two cases:
+ // 1) "packen" turns into "pak-ken"
+ // 2) "Schiffahrt" turns into "Schiff-fahrt"
+
+ // In case 1 we need to replace a char
+ // In case 2 we add a char
+
+ // Correct recognition is made harder by words such as
+ // "Schiffahrtsbrennesseln", as the Hyphenator splits all
+ // positions of the word and comes up with "Schifffahrtsbrennnesseln"
+ // Thus, we cannot infer the aWord from the AlternativeWord's
+ // index.
+ // TODO: The whole junk will be made easier by a function in
+ // the Hyphenator, as soon as AMA adds it.
+ sal_Int32 nAltStart = _nWordLen - 1;
+ sal_Int32 nTxtStart = nAltStart - (aAlt.getLength() - aWord.getLength());
+ sal_Int32 nTxtEnd = nTxtStart;
+ sal_Int32 nAltEnd = nAltStart;
+
+ // The area between nStart and nEnd is the difference
+ // between AlternativeString and OriginalString
+ while( nTxtEnd < aWord.getLength() && nAltEnd < aAlt.getLength() &&
+ aWord[nTxtEnd] != aAlt[nAltEnd] )
+ {
+ ++nTxtEnd;
+ ++nAltEnd;
+ }
+
+ // If a char was added, we notice it now:
+ if( nAltEnd > nTxtEnd && nAltStart == nAltEnd &&
+ aWord[ nTxtEnd ] == aAlt[nAltEnd] )
+ {
+ ++nAltEnd;
+ ++nTxtStart;
+ ++nTxtEnd;
+ }
+
+ SAL_WARN_IF( ( nAltEnd - nAltStart ) != 1, "vcl", "Alternate: Wrong assumption!" );
+
+ if ( nTxtEnd > nTxtStart )
+ cAlternateReplChar = aAlt[ nAltStart ];
+
+ nBreakPos = nWordStart + nTxtStart;
+ if ( cAlternateReplChar )
+ nBreakPos++;
+ }
+ }
+ }
+ }
+ }
+ }
+ nLineWidth = _rLayout.GetTextWidth( rStr, nPos, nBreakPos-nPos );
+ }
+ else
+ {
+ // fallback to something really simple
+ sal_Int32 nSpacePos = rStr.getLength();
+ long nW = 0;
+ do
+ {
+ nSpacePos = rStr.lastIndexOf( ' ', nSpacePos );
+ if( nSpacePos != -1 )
+ {
+ if( nSpacePos > nPos )
+ nSpacePos--;
+ nW = _rLayout.GetTextWidth( rStr, nPos, nSpacePos-nPos );
+ }
+ } while( nW > nWidth );
+
+ if( nSpacePos != -1 )
+ {
+ nBreakPos = nSpacePos;
+ nLineWidth = _rLayout.GetTextWidth( rStr, nPos, nBreakPos-nPos );
+ if( nBreakPos < rStr.getLength()-1 )
+ nBreakPos++;
+ }
+ }
+ }
+
+ if ( nLineWidth > nMaxLineWidth )
+ nMaxLineWidth = nLineWidth;
+
+ rLineInfo.AddLine( new 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++;
+ }
+ }
+ }
+#ifdef DBG_UTIL
+ for ( sal_Int32 nL = 0; nL < rLineInfo.Count(); nL++ )
+ {
+ ImplTextLineInfo* pLine = rLineInfo.GetLine( nL );
+ OUString aLine = rStr.copy( pLine->GetIndex(), pLine->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;
+}
+
+void OutputDevice::SetTextColor( const Color& rColor )
+{
+
+ Color aColor( rColor );
+
+ if ( mnDrawMode & ( DrawModeFlags::BlackText | DrawModeFlags::WhiteText |
+ DrawModeFlags::GrayText |
+ DrawModeFlags::SettingsText ) )
+ {
+ if ( mnDrawMode & DrawModeFlags::BlackText )
+ aColor = COL_BLACK;
+ else if ( mnDrawMode & DrawModeFlags::WhiteText )
+ aColor = COL_WHITE;
+ else if ( mnDrawMode & DrawModeFlags::GrayText )
+ {
+ const sal_uInt8 cLum = aColor.GetLuminance();
+ aColor = Color( cLum, cLum, cLum );
+ }
+ else if ( mnDrawMode & DrawModeFlags::SettingsText )
+ aColor = GetSettings().GetStyleSettings().GetFontColor();
+ }
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaTextColorAction( aColor ) );
+
+ if ( maTextColor != aColor )
+ {
+ maTextColor = aColor;
+ mbInitTextColor = true;
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetTextColor( COL_BLACK );
+}
+
+void OutputDevice::SetTextFillColor()
+{
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaTextFillColorAction( Color(), false ) );
+
+ if ( maFont.GetColor() != COL_TRANSPARENT ) {
+ maFont.SetFillColor( COL_TRANSPARENT );
+ }
+ if ( !maFont.IsTransparent() )
+ maFont.SetTransparent( true );
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetTextFillColor();
+}
+
+void OutputDevice::SetTextFillColor( const Color& rColor )
+{
+ Color aColor( rColor );
+ bool bTransFill = ImplIsColorTransparent( aColor );
+
+ if ( !bTransFill )
+ {
+ if ( mnDrawMode & ( DrawModeFlags::BlackFill | DrawModeFlags::WhiteFill |
+ DrawModeFlags::GrayFill | DrawModeFlags::NoFill |
+ DrawModeFlags::SettingsFill ) )
+ {
+ if ( mnDrawMode & DrawModeFlags::BlackFill )
+ aColor = COL_BLACK;
+ else if ( mnDrawMode & DrawModeFlags::WhiteFill )
+ aColor = COL_WHITE;
+ else if ( mnDrawMode & DrawModeFlags::GrayFill )
+ {
+ const sal_uInt8 cLum = aColor.GetLuminance();
+ aColor = Color( cLum, cLum, cLum );
+ }
+ else if( mnDrawMode & DrawModeFlags::SettingsFill )
+ aColor = GetSettings().GetStyleSettings().GetWindowColor();
+ else if ( mnDrawMode & DrawModeFlags::NoFill )
+ {
+ aColor = COL_TRANSPARENT;
+ bTransFill = true;
+ }
+ }
+ }
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaTextFillColorAction( aColor, true ) );
+
+ if ( maFont.GetFillColor() != aColor )
+ maFont.SetFillColor( aColor );
+ if ( maFont.IsTransparent() != bTransFill )
+ maFont.SetTransparent( bTransFill );
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetTextFillColor( COL_BLACK );
+}
+
+Color OutputDevice::GetTextFillColor() const
+{
+ if ( maFont.IsTransparent() )
+ return COL_TRANSPARENT;
+ else
+ return maFont.GetFillColor();
+}
+
+void OutputDevice::SetTextAlign( TextAlign eAlign )
+{
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaTextAlignAction( eAlign ) );
+
+ if ( maFont.GetAlignment() != eAlign )
+ {
+ maFont.SetAlignment( eAlign );
+ mbNewFont = true;
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetTextAlign( eAlign );
+}
+
+vcl::Region OutputDevice::GetOutputBoundsClipRegion() const
+{
+ return GetClipRegion();
+}
+
+void OutputDevice::DrawText( const Point& rStartPt, const OUString& rStr,
+ sal_Int32 nIndex, sal_Int32 nLen,
+ MetricVector* 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() )
+ {
+ MetricVector aTmp;
+ GetGlyphBoundRects( rStartPt, rStr, nIndex, nLen, aTmp );
+
+ bool bInserted = false;
+ for( MetricVector::const_iterator it = aTmp.begin(); it != aTmp.end(); ++it, nIndex++ )
+ {
+ bool bAppend = false;
+
+ if( aClip.IsOver( *it ) )
+ bAppend = true;
+ else if( rStr[ nIndex ] == ' ' && bInserted )
+ {
+ MetricVector::const_iterator next = it;
+ ++next;
+ if( next != aTmp.end() && aClip.IsOver( *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.copy( nIndex, nLen );
+ }
+ }
+
+ if ( !IsDeviceOutputNecessary() || pVector )
+ return;
+
+ if(mpFontInstance)
+ // do not use cache with modified string
+ if(mpFontInstance->mpConversion)
+ pLayoutCache = nullptr;
+
+#ifdef MACOSX
+ // FIXME: tdf#112990
+ // Cache text layout crashes on mac with OpenGL enabled
+ // Force it to not use the cache
+ if(OpenGLHelper::isVCLOpenGLEnabled())
+ pLayoutCache = nullptr;
+#endif
+
+ std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, rStartPt, 0, nullptr, SalLayoutFlags::NONE, nullptr, pLayoutCache);
+ if(pSalLayout)
+ {
+ ImplDrawText( *pSalLayout );
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawText( rStartPt, rStr, nIndex, nLen, pVector, pDisplayText );
+}
+
+long OutputDevice::GetTextWidth( const OUString& rStr, sal_Int32 nIndex, sal_Int32 nLen,
+ vcl::TextLayoutCache const*const pLayoutCache,
+ SalLayoutGlyphs const*const pSalLayoutCache) const
+{
+
+ long nWidth = GetTextArray( rStr, nullptr, nIndex,
+ nLen, pLayoutCache, pSalLayoutCache );
+
+ return nWidth;
+}
+
+long OutputDevice::GetTextHeight() const
+{
+ if (!InitFont())
+ return 0;
+
+ 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,
+ const long* pDXAry,
+ sal_Int32 nIndex, sal_Int32 nLen, SalLayoutFlags flags,
+ const SalLayoutGlyphs* pSalLayoutCache )
+{
+ assert(!is_double_buffered_window());
+
+ if( nLen < 0 || nIndex + nLen >= rStr.getLength() )
+ {
+ nLen = rStr.getLength() - nIndex;
+ }
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaTextArrayAction( rStartPt, rStr, pDXAry, nIndex, nLen ) );
+
+ if ( !IsDeviceOutputNecessary() )
+ return;
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+ if( mbInitClipRegion )
+ InitClipRegion();
+ if( mbOutputClipped )
+ return;
+
+ std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, rStartPt, 0, pDXAry, flags, nullptr, pSalLayoutCache);
+ if( pSalLayout )
+ {
+ ImplDrawText( *pSalLayout );
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawTextArray( rStartPt, rStr, pDXAry, nIndex, nLen, flags );
+}
+
+long OutputDevice::GetTextArray( const OUString& rStr, long* pDXAry,
+ sal_Int32 nIndex, sal_Int32 nLen,
+ vcl::TextLayoutCache const*const pLayoutCache,
+ SalLayoutGlyphs const*const pSalLayoutCache) const
+{
+ if( nIndex >= rStr.getLength() )
+ return 0; // TODO: this looks like a buggy caller?
+
+ if( nLen < 0 || nIndex + nLen >= rStr.getLength() )
+ {
+ nLen = rStr.getLength() - nIndex;
+ }
+
+ // do layout
+ std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen,
+ Point(0,0), 0, nullptr, SalLayoutFlags::NONE, 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)
+ {
+ memset(pDXAry, 0, nLen * sizeof(*pDXAry));
+ }
+ return 0;
+ }
+
+#if VCL_FLOAT_DEVICE_PIXEL
+ std::unique_ptr<DeviceCoordinate[]> pDXPixelArray;
+ if(pDXAry)
+ {
+ pDXPixelArray.reset(new DeviceCoordinate[nLen]);
+ }
+ DeviceCoordinate nWidth = pSalLayout->FillDXArray( pDXPixelArray.get() );
+ int nWidthFactor = pSalLayout->GetUnitsPerPixel();
+
+ // convert virtual char widths to virtual absolute positions
+ if( pDXPixelArray )
+ {
+ for( int i = 1; i < nLen; ++i )
+ {
+ pDXPixelArray[ i ] += pDXPixelArray[ i-1 ];
+ }
+ }
+ if( mbMap )
+ {
+ if( pDXPixelArray )
+ {
+ for( int i = 0; i < nLen; ++i )
+ {
+ pDXPixelArray[i] = ImplDevicePixelToLogicWidth( pDXPixelArray[i] );
+ }
+ }
+ nWidth = ImplDevicePixelToLogicWidth( nWidth );
+ }
+ if( nWidthFactor > 1 )
+ {
+ if( pDXPixelArray )
+ {
+ for( int i = 0; i < nLen; ++i )
+ {
+ pDXPixelArray[i] /= nWidthFactor;
+ }
+ }
+ nWidth /= nWidthFactor;
+ }
+ if(pDXAry)
+ {
+ for( int i = 0; i < nLen; ++i )
+ {
+ pDXAry[i] = basegfx::fround(pDXPixelArray[i]);
+ }
+ }
+ return basegfx::fround(nWidth);
+
+#else /* ! VCL_FLOAT_DEVICE_PIXEL */
+
+ long nWidth = pSalLayout->FillDXArray( pDXAry );
+ int nWidthFactor = pSalLayout->GetUnitsPerPixel();
+
+ // convert virtual char widths to virtual absolute positions
+ if( pDXAry )
+ for( int i = 1; i < nLen; ++i )
+ pDXAry[ i ] += pDXAry[ i-1 ];
+
+ // convert from font units to logical units
+ if( mbMap )
+ {
+ if( pDXAry )
+ for( int i = 0; i < nLen; ++i )
+ pDXAry[i] = ImplDevicePixelToLogicWidth( pDXAry[i] );
+ nWidth = ImplDevicePixelToLogicWidth( nWidth );
+ }
+
+ if( nWidthFactor > 1 )
+ {
+ if( pDXAry )
+ for( int i = 0; i < nLen; ++i )
+ pDXAry[i] /= nWidthFactor;
+ nWidth /= nWidthFactor;
+ }
+ return nWidth;
+#endif /* VCL_FLOAT_DEVICE_PIXEL */
+}
+
+void OutputDevice::GetCaretPositions( const OUString& rStr, long* pCaretXArray,
+ sal_Int32 nIndex, sal_Int32 nLen,
+ const SalLayoutGlyphs* pGlyphs ) const
+{
+
+ if( nIndex >= rStr.getLength() )
+ return;
+ if( nIndex+nLen >= rStr.getLength() )
+ nLen = rStr.getLength() - nIndex;
+
+ // layout complex text
+ std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, Point(0, 0), 0, nullptr,
+ SalLayoutFlags::NONE, nullptr, pGlyphs);
+ if( !pSalLayout )
+ return;
+
+ int nWidthFactor = pSalLayout->GetUnitsPerPixel();
+ pSalLayout->GetCaretPositions( 2*nLen, pCaretXArray );
+ long nWidth = pSalLayout->GetTextWidth();
+
+ // fixup unknown caret positions
+ int i;
+ for( i = 0; i < 2 * nLen; ++i )
+ if( pCaretXArray[ i ] >= 0 )
+ break;
+ long nXPos = pCaretXArray[ i ];
+ for( i = 0; i < 2 * nLen; ++i )
+ {
+ if( pCaretXArray[ i ] >= 0 )
+ nXPos = pCaretXArray[ i ];
+ else
+ pCaretXArray[ i ] = nXPos;
+ }
+
+ // handle window mirroring
+ if( IsRTLEnabled() )
+ {
+ for( i = 0; i < 2 * nLen; ++i )
+ pCaretXArray[i] = nWidth - pCaretXArray[i] - 1;
+ }
+
+ // convert from font units to logical units
+ if( mbMap )
+ {
+ for( i = 0; i < 2*nLen; ++i )
+ pCaretXArray[i] = ImplDevicePixelToLogicWidth( pCaretXArray[i] );
+ }
+
+ if( nWidthFactor != 1 )
+ {
+ for( i = 0; i < 2*nLen; ++i )
+ pCaretXArray[i] /= nWidthFactor;
+ }
+}
+
+void OutputDevice::DrawStretchText( const Point& rStartPt, sal_uLong nWidth,
+ const OUString& rStr,
+ sal_Int32 nIndex, sal_Int32 nLen)
+{
+ assert(!is_double_buffered_window());
+
+ if( (nLen < 0) || (nIndex + nLen >= rStr.getLength()))
+ {
+ nLen = rStr.getLength() - nIndex;
+ }
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaStretchTextAction( rStartPt, nWidth, rStr, nIndex, nLen ) );
+
+ if ( !IsDeviceOutputNecessary() )
+ return;
+
+ std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, rStartPt, nWidth);
+ if( pSalLayout )
+ {
+ ImplDrawText( *pSalLayout );
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawStretchText( rStartPt, nWidth, rStr, nIndex, nLen );
+}
+
+ImplLayoutArgs OutputDevice::ImplPrepareLayoutArgs( OUString& rStr,
+ const sal_Int32 nMinIndex, const sal_Int32 nLen,
+ DeviceCoordinate nPixelWidth, const DeviceCoordinate* pDXArray,
+ SalLayoutFlags nLayoutFlags,
+ vcl::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;
+
+ if( mnTextLayoutMode & ComplexTextLayoutFlags::BiDiRtl )
+ nLayoutFlags |= SalLayoutFlags::BiDiRtl;
+ if( mnTextLayoutMode & ComplexTextLayoutFlags::BiDiStrong )
+ nLayoutFlags |= SalLayoutFlags::BiDiStrong;
+ else if( !(mnTextLayoutMode & 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;
+ }
+
+ if( !maFont.IsKerning() )
+ nLayoutFlags |= SalLayoutFlags::DisableKerning;
+ if( maFont.GetKerning() & FontKerning::Asian )
+ nLayoutFlags |= SalLayoutFlags::KerningAsian;
+ if( maFont.IsVertical() )
+ nLayoutFlags |= SalLayoutFlags::Vertical;
+
+ if( meTextLanguage ) //TODO: (mnTextLayoutMode & 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 & ComplexTextLayoutFlags::BiDiRtl);
+ if( mnTextLayoutMode & ComplexTextLayoutFlags::TextOriginLeft )
+ bRightAlign = false;
+ else if ( mnTextLayoutMode & 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
+ ImplLayoutArgs aLayoutArgs(rStr, nMinIndex, nEndIndex, nLayoutFlags, maFont.GetLanguageTag(), pLayoutCache);
+
+ int nOrientation = mpFontInstance ? mpFontInstance->mnOrientation : 0;
+ aLayoutArgs.SetOrientation( nOrientation );
+
+ aLayoutArgs.SetLayoutWidth( nPixelWidth );
+ aLayoutArgs.SetDXArray( pDXArray );
+
+ return aLayoutArgs;
+}
+
+std::unique_ptr<SalLayout> OutputDevice::ImplLayout(const OUString& rOrigStr,
+ sal_Int32 nMinIndex, sal_Int32 nLen,
+ const Point& rLogicalPos, long nLogicalWidth,
+ const long* pDXArray, SalLayoutFlags flags,
+ vcl::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;
+ }
+
+ if (!InitFont())
+ return nullptr;
+
+ // check string index and length
+ if( -1 == nLen || nMinIndex + nLen > rOrigStr.getLength() )
+ {
+ const sal_Int32 nNewLen = rOrigStr.getLength() - nMinIndex;
+ if( nNewLen <= 0 )
+ return nullptr;
+ nLen = nNewLen;
+ }
+
+ OUString aStr = rOrigStr;
+
+ // convert from logical units to physical units
+ // recode string if needed
+ if( mpFontInstance->mpConversion ) {
+ mpFontInstance->mpConversion->RecodeString( aStr, 0, aStr.getLength() );
+ pLayoutCache = nullptr; // don't use cache with modified string!
+ pGlyphs = nullptr;
+ }
+
+ DeviceCoordinate nPixelWidth = static_cast<DeviceCoordinate>(nLogicalWidth);
+ if( nLogicalWidth && mbMap )
+ {
+ nPixelWidth = LogicWidthToDeviceCoordinate( nLogicalWidth );
+ }
+
+ std::unique_ptr<DeviceCoordinate[]> xDXPixelArray;
+ DeviceCoordinate* pDXPixelArray(nullptr);
+ if( pDXArray)
+ {
+ if(mbMap)
+ {
+ // convert from logical units to font units using a temporary array
+ xDXPixelArray.reset(new DeviceCoordinate[nLen]);
+ pDXPixelArray = xDXPixelArray.get();
+ // using base position for better rounding a.k.a. "dancing characters"
+ DeviceCoordinate nPixelXOfs = LogicWidthToDeviceCoordinate( rLogicalPos.X() );
+ for( int i = 0; i < nLen; ++i )
+ {
+ pDXPixelArray[i] = LogicWidthToDeviceCoordinate( rLogicalPos.X() + pDXArray[i] ) - nPixelXOfs;
+ }
+ }
+ else
+ {
+#if VCL_FLOAT_DEVICE_PIXEL
+ xDXPixelArray.reset(new DeviceCoordinate[nLen]);
+ pDXPixelArray = xDXPixelArray.get();
+ for( int i = 0; i < nLen; ++i )
+ {
+ pDXPixelArray[i] = pDXArray[i];
+ }
+#else /* !VCL_FLOAT_DEVICE_PIXEL */
+ pDXPixelArray = const_cast<DeviceCoordinate*>(pDXArray);
+#endif /* !VCL_FLOAT_DEVICE_PIXEL */
+ }
+ }
+
+ ImplLayoutArgs aLayoutArgs = ImplPrepareLayoutArgs( aStr, nMinIndex, nLen,
+ nPixelWidth, pDXPixelArray, flags, pLayoutCache);
+
+ // get matching layout object for base font
+ std::unique_ptr<SalLayout> pSalLayout = mpGraphics->GetTextLayout(0);
+
+ // layout text
+ if( pSalLayout && !pSalLayout->LayoutText( aLayoutArgs, pGlyphs ) )
+ {
+ pSalLayout.reset();
+ }
+
+ if( !pSalLayout )
+ return nullptr;
+
+ // do glyph fallback if needed
+ // #105768# avoid fallback for very small font sizes
+ if (aLayoutArgs.NeedFallback() && mpFontInstance->GetFontSelectPattern().mnHeight >= 3)
+ pSalLayout = ImplGlyphFallbackLayout(std::move(pSalLayout), aLayoutArgs);
+
+ 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 );
+ pSalLayout->DrawBase() = ImplLogicToDevicePixel( rLogicalPos );
+ // adjust to right alignment if necessary
+ if( aLayoutArgs.mnFlags & SalLayoutFlags::RightAlign )
+ {
+ DeviceCoordinate nRTLOffset;
+ if( pDXPixelArray )
+ nRTLOffset = pDXPixelArray[ nLen - 1 ];
+ else if( nPixelWidth )
+ nRTLOffset = nPixelWidth;
+ else
+ nRTLOffset = pSalLayout->GetTextWidth() / pSalLayout->GetUnitsPerPixel();
+ pSalLayout->DrawOffset().setX( 1 - nRTLOffset );
+ }
+
+ return pSalLayout;
+}
+
+std::shared_ptr<vcl::TextLayoutCache> OutputDevice::CreateTextLayoutCache(
+ OUString const& rString)
+{
+ return GenericSalLayout::CreateTextLayoutCache(rString);
+}
+
+bool OutputDevice::GetTextIsRTL( const OUString& rString, sal_Int32 nIndex, sal_Int32 nLen ) const
+{
+ OUString aStr( rString );
+ ImplLayoutArgs aArgs = ImplPrepareLayoutArgs( aStr, nIndex, nLen, 0, nullptr );
+ bool bRTL = false;
+ int nCharPos = -1;
+ if (!aArgs.GetNextPos(&nCharPos, &bRTL))
+ return false;
+ return (nCharPos != nIndex);
+}
+
+sal_Int32 OutputDevice::GetTextBreak( const OUString& rStr, long nTextWidth,
+ sal_Int32 nIndex, sal_Int32 nLen,
+ long nCharExtra,
+ vcl::TextLayoutCache const*const pLayoutCache,
+ const SalLayoutGlyphs* pGlyphs) const
+{
+ std::unique_ptr<SalLayout> pSalLayout = ImplLayout( rStr, nIndex, nLen,
+ Point(0,0), 0, nullptr, SalLayoutFlags::NONE, 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
+ long nWidthFactor = pSalLayout->GetUnitsPerPixel();
+ long nSubPixelFactor = (nWidthFactor < 64 ) ? 64 : 1;
+ nTextWidth *= nWidthFactor * nSubPixelFactor;
+ DeviceCoordinate nTextPixelWidth = LogicWidthToDeviceCoordinate( nTextWidth );
+ DeviceCoordinate nExtraPixelWidth = 0;
+ if( nCharExtra != 0 )
+ {
+ nCharExtra *= nWidthFactor * nSubPixelFactor;
+ nExtraPixelWidth = LogicWidthToDeviceCoordinate( nCharExtra );
+ }
+ nRetVal = pSalLayout->GetTextBreak( nTextPixelWidth, nExtraPixelWidth, nSubPixelFactor );
+ }
+
+ return nRetVal;
+}
+
+sal_Int32 OutputDevice::GetTextBreak( const OUString& rStr, long nTextWidth,
+ sal_Unicode nHyphenChar, sal_Int32& rHyphenPos,
+ sal_Int32 nIndex, sal_Int32 nLen,
+ long nCharExtra,
+ vcl::TextLayoutCache const*const pLayoutCache) const
+{
+ rHyphenPos = -1;
+
+ std::unique_ptr<SalLayout> pSalLayout = ImplLayout( rStr, nIndex, nLen,
+ Point(0,0), 0, nullptr, SalLayoutFlags::NONE, pLayoutCache);
+ 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
+ long nWidthFactor = pSalLayout->GetUnitsPerPixel();
+ long nSubPixelFactor = (nWidthFactor < 64 ) ? 64 : 1;
+
+ nTextWidth *= nWidthFactor * nSubPixelFactor;
+ DeviceCoordinate nTextPixelWidth = LogicWidthToDeviceCoordinate( nTextWidth );
+ DeviceCoordinate nExtraPixelWidth = 0;
+ if( nCharExtra != 0 )
+ {
+ nCharExtra *= nWidthFactor * nSubPixelFactor;
+ nExtraPixelWidth = LogicWidthToDeviceCoordinate( nCharExtra );
+ }
+
+ // calculate un-hyphenated break position
+ nRetVal = pSalLayout->GetTextBreak( nTextPixelWidth, nExtraPixelWidth, nSubPixelFactor );
+
+ // calculate hyphenated break position
+ OUString aHyphenStr(nHyphenChar);
+ std::unique_ptr<SalLayout> pHyphenLayout = ImplLayout( aHyphenStr, 0, 1 );
+ if( pHyphenLayout )
+ {
+ // calculate subpixel width of hyphenation character
+ long nHyphenPixelWidth = pHyphenLayout->GetTextWidth() * nSubPixelFactor;
+
+ // calculate hyphenated break position
+ nTextPixelWidth -= nHyphenPixelWidth;
+ if( nExtraPixelWidth > 0 )
+ nTextPixelWidth -= nExtraPixelWidth;
+
+ rHyphenPos = pSalLayout->GetTextBreak(nTextPixelWidth, nExtraPixelWidth, nSubPixelFactor);
+
+ if( rHyphenPos > nRetVal )
+ rHyphenPos = nRetVal;
+ }
+ }
+
+ return nRetVal;
+}
+
+void OutputDevice::ImplDrawText( OutputDevice& rTargetDevice, const tools::Rectangle& rRect,
+ const OUString& rOrigStr, DrawTextFlags nStyle,
+ MetricVector* pVector, OUString* pDisplayText,
+ vcl::ITextLayout& _rLayout )
+{
+
+ Color aOldTextColor;
+ Color aOldTextFillColor;
+ bool bRestoreFillColor = false;
+ if ( (nStyle & DrawTextFlags::Disable) && ! pVector )
+ {
+ bool bHighContrastBlack = false;
+ bool bHighContrastWhite = false;
+ const StyleSettings& rStyleSettings( rTargetDevice.GetSettings().GetStyleSettings() );
+ if( rStyleSettings.GetHighContrastMode() )
+ {
+ Color aCol;
+ if( rTargetDevice.IsBackground() )
+ aCol = rTargetDevice.GetBackground().GetColor();
+ else
+ // best guess is the face color here
+ // but it may be totally wrong. the background color
+ // was typically already reset
+ aCol = rStyleSettings.GetFaceColor();
+
+ bHighContrastBlack = aCol.IsDark();
+ bHighContrastWhite = aCol.IsBright();
+ }
+
+ aOldTextColor = rTargetDevice.GetTextColor();
+ if ( rTargetDevice.IsTextFillColor() )
+ {
+ bRestoreFillColor = true;
+ aOldTextFillColor = rTargetDevice.GetTextFillColor();
+ }
+ if( bHighContrastBlack )
+ rTargetDevice.SetTextColor( COL_GREEN );
+ else if( bHighContrastWhite )
+ rTargetDevice.SetTextColor( COL_LIGHTGREEN );
+ else
+ {
+ // draw disabled text always without shadow
+ // as it fits better with native look
+ rTargetDevice.SetTextColor( rTargetDevice.GetSettings().GetStyleSettings().GetDisableColor() );
+ }
+ }
+
+ long nWidth = rRect.GetWidth();
+ long nHeight = rRect.GetHeight();
+
+ if ( ((nWidth <= 0) || (nHeight <= 0)) && (nStyle & DrawTextFlags::Clip) )
+ return;
+
+ Point aPos = rRect.TopLeft();
+
+ long nTextHeight = rTargetDevice.GetTextHeight();
+ TextAlign eAlign = rTargetDevice.GetTextAlign();
+ sal_Int32 nMnemonicPos = -1;
+
+ OUString aStr = rOrigStr;
+ if ( nStyle & DrawTextFlags::Mnemonic )
+ aStr = GetNonMnemonicString( aStr, nMnemonicPos );
+
+ const bool bDrawMnemonics = !(rTargetDevice.GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::NoMnemonics) && !pVector;
+
+ // We treat multiline text differently
+ if ( nStyle & DrawTextFlags::MultiLine )
+ {
+
+ OUString aLastLine;
+ ImplMultiTextLineInfo aMultiLineInfo;
+ ImplTextLineInfo* pLineInfo;
+ sal_Int32 i;
+ sal_Int32 nLines;
+ sal_Int32 nFormatLines;
+
+ if ( nTextHeight )
+ {
+ long nMaxTextWidth = ImplGetTextLines( aMultiLineInfo, nWidth, aStr, nStyle, _rLayout );
+ nLines = static_cast<sal_Int32>(nHeight/nTextHeight);
+ nFormatLines = aMultiLineInfo.Count();
+ if (nLines <= 0)
+ nLines = 1;
+ if ( nFormatLines > nLines )
+ {
+ if ( nStyle & DrawTextFlags::EndEllipsis )
+ {
+ // Create last line and shorten it
+ nFormatLines = nLines-1;
+
+ pLineInfo = aMultiLineInfo.GetLine( nFormatLines );
+ aLastLine = convertLineEnd(aStr.copy(pLineInfo->GetIndex()), LINEEND_LF);
+ // Replace all LineFeeds with Spaces
+ OUStringBuffer aLastLineBuffer(aLastLine);
+ sal_Int32 nLastLineLen = aLastLineBuffer.getLength();
+ for ( i = 0; i < nLastLineLen; i++ )
+ {
+ if ( aLastLineBuffer[ i ] == '\n' )
+ aLastLineBuffer[ i ] = ' ';
+ }
+ aLastLine = aLastLineBuffer.makeStringAndClear();
+ aLastLine = ImplGetEllipsisString( rTargetDevice, aLastLine, nWidth, nStyle, _rLayout );
+ nStyle &= ~DrawTextFlags(DrawTextFlags::VCenter | DrawTextFlags::Bottom);
+ nStyle |= DrawTextFlags::Top;
+ }
+ }
+ else
+ {
+ if ( nMaxTextWidth <= nWidth )
+ nStyle &= ~DrawTextFlags::Clip;
+ }
+
+ // Do we need to clip the height?
+ if ( nFormatLines*nTextHeight > nHeight )
+ nStyle |= DrawTextFlags::Clip;
+
+ // Set clipping
+ if ( nStyle & DrawTextFlags::Clip )
+ {
+ rTargetDevice.Push( 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++ )
+ {
+ pLineInfo = aMultiLineInfo.GetLine( i );
+ if ( nStyle & DrawTextFlags::Right )
+ aPos.AdjustX(nWidth-pLineInfo->GetWidth() );
+ else if ( nStyle & DrawTextFlags::Center )
+ aPos.AdjustX((nWidth-pLineInfo->GetWidth())/2 );
+ sal_Int32 nIndex = pLineInfo->GetIndex();
+ sal_Int32 nLineLen = pLineInfo->GetLen();
+ _rLayout.DrawText( aPos, aStr, nIndex, nLineLen, pVector, pDisplayText );
+ if ( bDrawMnemonics )
+ {
+ if ( (nMnemonicPos >= nIndex) && (nMnemonicPos < nIndex+nLineLen) )
+ {
+ long nMnemonicX;
+ long nMnemonicY;
+ DeviceCoordinate nMnemonicWidth;
+
+ std::unique_ptr<long[]> const pCaretXArray(new long[2 * nLineLen]);
+ /*sal_Bool bRet =*/ _rLayout.GetCaretPositions( aStr, pCaretXArray.get(),
+ nIndex, nLineLen );
+ long lc_x1 = pCaretXArray[2*(nMnemonicPos - nIndex)];
+ long lc_x2 = pCaretXArray[2*(nMnemonicPos - nIndex)+1];
+ nMnemonicWidth = rTargetDevice.LogicWidthToDeviceCoordinate( std::abs(lc_x1 - lc_x2) );
+
+ Point aTempPos = rTargetDevice.LogicToPixel( aPos );
+ nMnemonicX = rTargetDevice.GetOutOffXPixel() + aTempPos.X() + rTargetDevice.ImplLogicWidthToDevicePixel( std::min( lc_x1, lc_x2 ) );
+ nMnemonicY = rTargetDevice.GetOutOffYPixel() + aTempPos.Y() + rTargetDevice.ImplLogicWidthToDevicePixel( rTargetDevice.GetFontMetric().GetAscent() );
+ rTargetDevice.ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth );
+ }
+ }
+ aPos.AdjustY(nTextHeight );
+ aPos.setX( rRect.Left() );
+ }
+
+ // If there still is a last line, we output it left-aligned as the line would be clipped
+ if ( !aLastLine.isEmpty() )
+ _rLayout.DrawText( aPos, aLastLine, 0, aLastLine.getLength(), pVector, pDisplayText );
+
+ // Reset clipping
+ if ( nStyle & DrawTextFlags::Clip )
+ rTargetDevice.Pop();
+ }
+ }
+ else
+ {
+ long nTextWidth = _rLayout.GetTextWidth( aStr, 0, -1 );
+
+ // Clip text if needed
+ if ( nTextWidth > nWidth )
+ {
+ if ( nStyle & TEXT_DRAW_ELLIPSIS )
+ {
+ aStr = ImplGetEllipsisString( rTargetDevice, aStr, nWidth, nStyle, _rLayout );
+ nStyle &= ~DrawTextFlags(DrawTextFlags::Center | DrawTextFlags::Right);
+ nStyle |= DrawTextFlags::Left;
+ nTextWidth = _rLayout.GetTextWidth( aStr, 0, aStr.getLength() );
+ }
+ }
+ else
+ {
+ if ( nTextHeight <= nHeight )
+ nStyle &= ~DrawTextFlags::Clip;
+ }
+
+ // horizontal text alignment
+ if ( nStyle & DrawTextFlags::Right )
+ aPos.AdjustX(nWidth-nTextWidth );
+ else if ( nStyle & DrawTextFlags::Center )
+ aPos.AdjustX((nWidth-nTextWidth)/2 );
+
+ // vertical font alignment
+ if ( eAlign == ALIGN_BOTTOM )
+ aPos.AdjustY(nTextHeight );
+ else if ( eAlign == ALIGN_BASELINE )
+ aPos.AdjustY(rTargetDevice.GetFontMetric().GetAscent() );
+
+ if ( nStyle & DrawTextFlags::Bottom )
+ aPos.AdjustY(nHeight-nTextHeight );
+ else if ( nStyle & DrawTextFlags::VCenter )
+ aPos.AdjustY((nHeight-nTextHeight)/2 );
+
+ long nMnemonicX = 0;
+ long nMnemonicY = 0;
+ DeviceCoordinate nMnemonicWidth = 0;
+ if ( nMnemonicPos != -1 )
+ {
+ std::unique_ptr<long[]> const pCaretXArray(new long[2 * aStr.getLength()]);
+ /*sal_Bool bRet =*/ _rLayout.GetCaretPositions( aStr, pCaretXArray.get(), 0, aStr.getLength() );
+ long lc_x1 = pCaretXArray[2*nMnemonicPos];
+ long lc_x2 = pCaretXArray[2*nMnemonicPos+1];
+ nMnemonicWidth = rTargetDevice.LogicWidthToDeviceCoordinate( std::abs(lc_x1 - lc_x2) );
+
+ Point aTempPos = rTargetDevice.LogicToPixel( aPos );
+ nMnemonicX = rTargetDevice.GetOutOffXPixel() + aTempPos.X() + rTargetDevice.ImplLogicWidthToDevicePixel( std::min(lc_x1, lc_x2) );
+ nMnemonicY = rTargetDevice.GetOutOffYPixel() + aTempPos.Y() + rTargetDevice.ImplLogicWidthToDevicePixel( rTargetDevice.GetFontMetric().GetAscent() );
+ }
+
+ if ( nStyle & DrawTextFlags::Clip )
+ {
+ rTargetDevice.Push( 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;
+ 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,
+ MetricVector* pVector, OUString* pDisplayText,
+ vcl::ITextLayout* _pTextLayout )
+{
+ assert(!is_double_buffered_window());
+
+ if (mpOutDevData->mpRecordLayout)
+ {
+ pVector = &mpOutDevData->mpRecordLayout->m_aUnicodeBoundRects;
+ pDisplayText = &mpOutDevData->mpRecordLayout->m_aDisplayText;
+ }
+
+ bool bDecomposeTextRectAction = ( _pTextLayout != nullptr ) && _pTextLayout->DecomposeTextRectAction();
+ if ( mpMetaFile && !bDecomposeTextRectAction )
+ mpMetaFile->AddAction( new MetaTextRectAction( rRect, rOrigStr, nStyle ) );
+
+ if ( ( !IsDeviceOutputNecessary() && !pVector && !bDecomposeTextRectAction ) || rOrigStr.isEmpty() || rRect.IsEmpty() )
+ return;
+
+ // we need a graphics
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+ if( mbInitClipRegion )
+ InitClipRegion();
+ if( mbOutputClipped && !bDecomposeTextRectAction )
+ return;
+
+ // temporarily disable mtf action generation (ImplDrawText _does_
+ // create MetaActionType::TEXTs otherwise)
+ GDIMetaFile* pMtf = mpMetaFile;
+ if ( !bDecomposeTextRectAction )
+ mpMetaFile = nullptr;
+
+ // #i47157# Factored out to ImplDrawText(), to be used also
+ // from AddTextRectActions()
+ vcl::DefaultTextLayout aDefaultLayout( *this );
+ ImplDrawText( *this, rRect, rOrigStr, nStyle, pVector, pDisplayText, _pTextLayout ? *_pTextLayout : aDefaultLayout );
+
+ // and enable again
+ mpMetaFile = pMtf;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawText( rRect, rOrigStr, nStyle, pVector, pDisplayText );
+}
+
+tools::Rectangle OutputDevice::GetTextRect( const tools::Rectangle& rRect,
+ const OUString& rStr, DrawTextFlags nStyle,
+ TextRectInfo* pInfo,
+ const vcl::ITextLayout* _pTextLayout ) const
+{
+
+ tools::Rectangle aRect = rRect;
+ sal_Int32 nLines;
+ long nWidth = rRect.GetWidth();
+ long nMaxWidth;
+ long nTextHeight = GetTextHeight();
+
+ OUString aStr = rStr;
+ if ( nStyle & DrawTextFlags::Mnemonic )
+ aStr = GetNonMnemonicString( aStr );
+
+ if ( nStyle & DrawTextFlags::MultiLine )
+ {
+ ImplMultiTextLineInfo aMultiLineInfo;
+ ImplTextLineInfo* pLineInfo;
+ sal_Int32 nFormatLines;
+ sal_Int32 i;
+
+ nMaxWidth = 0;
+ vcl::DefaultTextLayout aDefaultLayout( *const_cast< OutputDevice* >( this ) );
+ ImplGetTextLines( aMultiLineInfo, nWidth, aStr, nStyle, _pTextLayout ? *_pTextLayout : aDefaultLayout );
+ nFormatLines = aMultiLineInfo.Count();
+ if ( !nTextHeight )
+ nTextHeight = 1;
+ nLines = static_cast<sal_uInt16>(aRect.GetHeight()/nTextHeight);
+ if ( pInfo )
+ pInfo->mnLineCount = nFormatLines;
+ if ( !nLines )
+ nLines = 1;
+ if ( nFormatLines <= nLines )
+ nLines = nFormatLines;
+ else
+ {
+ if ( !(nStyle & DrawTextFlags::EndEllipsis) )
+ nLines = nFormatLines;
+ else
+ {
+ if ( pInfo )
+ pInfo->mbEllipsis = true;
+ nMaxWidth = nWidth;
+ }
+ }
+ if ( pInfo )
+ {
+ bool bMaxWidth = nMaxWidth == 0;
+ pInfo->mnMaxWidth = 0;
+ for ( i = 0; i < nLines; i++ )
+ {
+ pLineInfo = aMultiLineInfo.GetLine( i );
+ if ( bMaxWidth && (pLineInfo->GetWidth() > nMaxWidth) )
+ nMaxWidth = pLineInfo->GetWidth();
+ if ( pLineInfo->GetWidth() > pInfo->mnMaxWidth )
+ pInfo->mnMaxWidth = pLineInfo->GetWidth();
+ }
+ }
+ else if ( !nMaxWidth )
+ {
+ for ( i = 0; i < nLines; i++ )
+ {
+ pLineInfo = aMultiLineInfo.GetLine( i );
+ if ( pLineInfo->GetWidth() > nMaxWidth )
+ nMaxWidth = pLineInfo->GetWidth();
+ }
+ }
+ }
+ else
+ {
+ nLines = 1;
+ nMaxWidth = _pTextLayout ? _pTextLayout->GetTextWidth( aStr, 0, aStr.getLength() ) : GetTextWidth( aStr );
+
+ if ( pInfo )
+ {
+ pInfo->mnLineCount = 1;
+ pInfo->mnMaxWidth = nMaxWidth;
+ }
+
+ if ( (nMaxWidth > nWidth) && (nStyle & TEXT_DRAW_ELLIPSIS) )
+ {
+ if ( pInfo )
+ pInfo->mbEllipsis = true;
+ nMaxWidth = nWidth;
+ }
+ }
+
+ if ( nStyle & DrawTextFlags::Right )
+ aRect.SetLeft( aRect.Right()-nMaxWidth+1 );
+ else if ( nStyle & DrawTextFlags::Center )
+ {
+ aRect.AdjustLeft((nWidth-nMaxWidth)/2 );
+ aRect.SetRight( aRect.Left()+nMaxWidth-1 );
+ }
+ else
+ aRect.SetRight( aRect.Left()+nMaxWidth-1 );
+
+ if ( nStyle & DrawTextFlags::Bottom )
+ aRect.SetTop( aRect.Bottom()-(nTextHeight*nLines)+1 );
+ else if ( nStyle & DrawTextFlags::VCenter )
+ {
+ aRect.AdjustTop((aRect.GetHeight()-(nTextHeight*nLines))/2 );
+ aRect.SetBottom( aRect.Top()+(nTextHeight*nLines)-1 );
+ }
+ else
+ aRect.SetBottom( aRect.Top()+(nTextHeight*nLines)-1 );
+
+ // #99188# get rid of rounding problems when using this rect later
+ if (nStyle & DrawTextFlags::Right)
+ aRect.AdjustLeft( -1 );
+ else
+ aRect.AdjustRight( 1 );
+ return aRect;
+}
+
+static bool ImplIsCharIn( sal_Unicode c, const char* pStr )
+{
+ while ( *pStr )
+ {
+ if ( *pStr == c )
+ return true;
+ pStr++;
+ }
+
+ return false;
+}
+
+OUString OutputDevice::GetEllipsisString( const OUString& rOrigStr, long nMaxWidth,
+ DrawTextFlags nStyle ) const
+{
+ vcl::DefaultTextLayout aTextLayout( *const_cast< OutputDevice* >( this ) );
+ return ImplGetEllipsisString( *this, rOrigStr, nMaxWidth, nStyle, aTextLayout );
+}
+
+OUString OutputDevice::ImplGetEllipsisString( const OutputDevice& rTargetDevice, const OUString& rOrigStr, long nMaxWidth,
+ DrawTextFlags nStyle, const vcl::ITextLayout& _rLayout )
+{
+ OUString aStr = rOrigStr;
+ sal_Int32 nIndex = _rLayout.GetTextBreak( aStr, nMaxWidth, 0, aStr.getLength() );
+
+ if ( nIndex != -1 )
+ {
+ if( (nStyle & DrawTextFlags::CenterEllipsis) == DrawTextFlags::CenterEllipsis )
+ {
+ OUStringBuffer aTmpStr( aStr );
+ // speed it up by removing all but 1.33x as many as the break pos.
+ sal_Int32 nEraseChars = std::max<sal_Int32>(4, aStr.getLength() - (nIndex*4)/3);
+ while( nEraseChars < aStr.getLength() && _rLayout.GetTextWidth( aTmpStr.toString(), 0, aTmpStr.getLength() ) > nMaxWidth )
+ {
+ aTmpStr = aStr;
+ sal_Int32 i = (aTmpStr.getLength() - nEraseChars)/2;
+ aTmpStr.remove(i, nEraseChars++);
+ aTmpStr.insert(i, "...");
+ }
+ aStr = aTmpStr.makeStringAndClear();
+ }
+ else if ( nStyle & DrawTextFlags::EndEllipsis )
+ {
+ aStr = aStr.copy(0, nIndex);
+ if ( nIndex > 1 )
+ {
+ aStr += "...";
+ while ( !aStr.isEmpty() && (_rLayout.GetTextWidth( aStr, 0, aStr.getLength() ) > nMaxWidth) )
+ {
+ if ( (nIndex > 1) || (nIndex == aStr.getLength()) )
+ nIndex--;
+ aStr = aStr.replaceAt( nIndex, 1, "");
+ }
+ }
+
+ if ( aStr.isEmpty() && (nStyle & DrawTextFlags::Clip) )
+ aStr += OUStringChar(rOrigStr[ 0 ]);
+ }
+ else if ( nStyle & DrawTextFlags::PathEllipsis )
+ {
+ OUString aPath( rOrigStr );
+ OUString aAbbreviatedPath;
+ osl_abbreviateSystemPath( aPath.pData, &aAbbreviatedPath.pData, nIndex, nullptr );
+ aStr = aAbbreviatedPath;
+ }
+ else if ( nStyle & DrawTextFlags::NewsEllipsis )
+ {
+ static char const pSepChars[] = ".";
+ // Determine last section
+ sal_Int32 nLastContent = aStr.getLength();
+ while ( nLastContent )
+ {
+ nLastContent--;
+ if ( ImplIsCharIn( aStr[ nLastContent ], pSepChars ) )
+ break;
+ }
+ while ( nLastContent &&
+ ImplIsCharIn( aStr[ nLastContent-1 ], pSepChars ) )
+ nLastContent--;
+
+ OUString aLastStr = aStr.copy(nLastContent);
+ OUString aTempLastStr1 = "..." + aLastStr;
+ if ( _rLayout.GetTextWidth( aTempLastStr1, 0, aTempLastStr1.getLength() ) > nMaxWidth )
+ aStr = OutputDevice::ImplGetEllipsisString( rTargetDevice, aStr, nMaxWidth, nStyle | DrawTextFlags::EndEllipsis, _rLayout );
+ else
+ {
+ sal_Int32 nFirstContent = 0;
+ while ( nFirstContent < nLastContent )
+ {
+ nFirstContent++;
+ if ( ImplIsCharIn( aStr[ nFirstContent ], pSepChars ) )
+ break;
+ }
+ while ( (nFirstContent < nLastContent) &&
+ ImplIsCharIn( aStr[ nFirstContent ], pSepChars ) )
+ nFirstContent++;
+ // MEM continue here
+ if ( nFirstContent >= nLastContent )
+ aStr = OutputDevice::ImplGetEllipsisString( rTargetDevice, aStr, nMaxWidth, nStyle | DrawTextFlags::EndEllipsis, _rLayout );
+ else
+ {
+ if ( nFirstContent > 4 )
+ nFirstContent = 4;
+ OUString aFirstStr = aStr.copy( 0, nFirstContent ) + "...";
+ OUString aTempStr = aFirstStr + aLastStr;
+ if ( _rLayout.GetTextWidth( aTempStr, 0, aTempStr.getLength() ) > nMaxWidth )
+ aStr = OutputDevice::ImplGetEllipsisString( rTargetDevice, aStr, nMaxWidth, nStyle | DrawTextFlags::EndEllipsis, _rLayout );
+ else
+ {
+ do
+ {
+ aStr = aTempStr;
+ if( nLastContent > aStr.getLength() )
+ nLastContent = aStr.getLength();
+ while ( nFirstContent < nLastContent )
+ {
+ nLastContent--;
+ if ( ImplIsCharIn( aStr[ nLastContent ], pSepChars ) )
+ break;
+
+ }
+ while ( (nFirstContent < nLastContent) &&
+ ImplIsCharIn( aStr[ nLastContent-1 ], pSepChars ) )
+ nLastContent--;
+
+ if ( nFirstContent < nLastContent )
+ {
+ OUString aTempLastStr = aStr.copy( nLastContent );
+ aTempStr = aFirstStr + aTempLastStr;
+
+ if ( _rLayout.GetTextWidth( aTempStr, 0, aTempStr.getLength() ) > nMaxWidth )
+ break;
+ }
+ }
+ while ( nFirstContent < nLastContent );
+ }
+ }
+ }
+ }
+ }
+
+ return aStr;
+}
+
+void OutputDevice::DrawCtrlText( const Point& rPos, const OUString& rStr,
+ sal_Int32 nIndex, sal_Int32 nLen,
+ DrawTextFlags nStyle, MetricVector* 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;
+ 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;
+
+ long nMnemonicX = 0;
+ long nMnemonicY = 0;
+ long nMnemonicWidth = 0;
+ if ( (nStyle & DrawTextFlags::Mnemonic) && nLen > 1 )
+ {
+ aStr = GetNonMnemonicString( aStr, nMnemonicPos );
+ if ( nMnemonicPos != -1 )
+ {
+ if( nMnemonicPos < nIndex )
+ {
+ --nIndex;
+ }
+ else
+ {
+ if( nMnemonicPos < (nIndex+nLen) )
+ --nLen;
+ SAL_WARN_IF( nMnemonicPos >= (nIndex+nLen), "vcl", "Mnemonic underline marker after last character" );
+ }
+ bool bInvalidPos = false;
+
+ if( nMnemonicPos >= nLen )
+ {
+ // may occur in BiDi-Strings: the '~' is sometimes found behind the last char
+ // due to some strange BiDi text editors
+ // -> place the underline behind the string to indicate a failure
+ bInvalidPos = true;
+ nMnemonicPos = nLen-1;
+ }
+
+ std::unique_ptr<long[]> const pCaretXArray(new long[2 * nLen]);
+ /*sal_Bool bRet =*/ GetCaretPositions( aStr, pCaretXArray.get(), nIndex, nLen, pGlyphs );
+ long lc_x1 = pCaretXArray[ 2*(nMnemonicPos - nIndex) ];
+ long lc_x2 = pCaretXArray[ 2*(nMnemonicPos - nIndex)+1 ];
+ nMnemonicWidth = ::abs(static_cast<int>(lc_x1 - lc_x2));
+
+ Point aTempPos( std::min(lc_x1,lc_x2), GetFontMetric().GetAscent() );
+ if( bInvalidPos ) // #106952#, place behind the (last) character
+ aTempPos = Point( std::max(lc_x1,lc_x2), GetFontMetric().GetAscent() );
+
+ aTempPos += rPos;
+ aTempPos = LogicToPixel( aTempPos );
+ nMnemonicX = mnOutOffX + aTempPos.X();
+ nMnemonicY = mnOutOffY + aTempPos.Y();
+ }
+ }
+
+ bool autoacc = ImplGetSVData()->maNWFData.mbAutoAccel;
+
+ if ( nStyle & DrawTextFlags::Disable && ! pVector )
+ {
+ Color aOldTextColor;
+ Color aOldTextFillColor;
+ bool bRestoreFillColor;
+ bool bHighContrastBlack = false;
+ bool bHighContrastWhite = false;
+ const StyleSettings& rStyleSettings( GetSettings().GetStyleSettings() );
+ if( rStyleSettings.GetHighContrastMode() )
+ {
+ if( IsBackground() )
+ {
+ Wallpaper aWall = GetBackground();
+ Color aCol = aWall.GetColor();
+ bHighContrastBlack = aCol.IsDark();
+ bHighContrastWhite = aCol.IsBright();
+ }
+ }
+
+ aOldTextColor = GetTextColor();
+ if ( IsTextFillColor() )
+ {
+ bRestoreFillColor = true;
+ aOldTextFillColor = GetTextFillColor();
+ }
+ else
+ bRestoreFillColor = false;
+
+ if( bHighContrastBlack )
+ SetTextColor( COL_GREEN );
+ else if( bHighContrastWhite )
+ SetTextColor( COL_LIGHTGREEN );
+ else
+ SetTextColor( GetSettings().GetStyleSettings().GetDisableColor() );
+
+ DrawText( rPos, aStr, nIndex, nLen, pVector, pDisplayText );
+ if (!(GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::NoMnemonics)
+ && (!autoacc || !(nStyle & DrawTextFlags::HideMnemonic)) )
+ {
+ if ( nMnemonicPos != -1 )
+ ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth );
+ }
+ SetTextColor( aOldTextColor );
+ if ( bRestoreFillColor )
+ SetTextFillColor( aOldTextFillColor );
+ }
+ else
+ {
+ DrawText( rPos, aStr, nIndex, nLen, pVector, pDisplayText, pGlyphs );
+ if ( !(GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::NoMnemonics) && !pVector
+ && (!autoacc || !(nStyle & DrawTextFlags::HideMnemonic)) )
+ {
+ if ( nMnemonicPos != -1 )
+ ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth );
+ }
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawCtrlText( rPos, rStr, nIndex, nLen, nStyle, pVector, pDisplayText );
+}
+
+long OutputDevice::GetCtrlTextWidth( const OUString& rStr, const SalLayoutGlyphs* pGlyphs ) const
+{
+ sal_Int32 nLen = rStr.getLength();
+ sal_Int32 nIndex = 0;
+
+ sal_Int32 nMnemonicPos;
+ OUString aStr = GetNonMnemonicString( rStr, nMnemonicPos );
+ if ( nMnemonicPos != -1 )
+ {
+ if ( nMnemonicPos < nIndex )
+ nIndex--;
+ else if (static_cast<sal_uLong>(nMnemonicPos) < static_cast<sal_uLong>(nIndex+nLen))
+ nLen--;
+ }
+ return GetTextWidth( aStr, nIndex, nLen, nullptr, pGlyphs );
+}
+
+OUString OutputDevice::GetNonMnemonicString( const OUString& rStr, sal_Int32& rMnemonicPos )
+{
+ OUString aStr = rStr;
+ sal_Int32 nLen = aStr.getLength();
+ sal_Int32 i = 0;
+
+ rMnemonicPos = -1;
+ while ( i < nLen )
+ {
+ if ( aStr[ i ] == '~' )
+ {
+ if ( nLen <= i+1 )
+ break;
+
+ if ( aStr[ i+1 ] != '~' )
+ {
+ if ( rMnemonicPos == -1 )
+ rMnemonicPos = i;
+ aStr = aStr.replaceAt( i, 1, "" );
+ nLen--;
+ }
+ else
+ {
+ aStr = aStr.replaceAt( i, 1, "" );
+ nLen--;
+ i++;
+ }
+ }
+ else
+ i++;
+ }
+
+ return aStr;
+}
+
+/** OutputDevice::GetSysTextLayoutData
+ *
+ * @param rStartPt Start point of the text
+ * @param rStr Text string that will be transformed into layout of glyphs
+ * @param nIndex Position in the string from where layout will be done
+ * @param nLen Length of the string
+ * @param pDXAry Custom layout adjustment data
+ *
+ * Export finalized glyph layout data as platform independent SystemTextLayoutData
+ * (see vcl/inc/vcl/sysdata.hxx)
+ *
+ * Only parameters rStartPt and rStr are mandatory, the rest is optional
+ * (default values will be used)
+ *
+ * @return SystemTextLayoutData
+ **/
+SystemTextLayoutData OutputDevice::GetSysTextLayoutData(const Point& rStartPt, const OUString& rStr, sal_Int32 nIndex, sal_Int32 nLen,
+ const long* pDXAry) const
+{
+ if( (nLen < 0) || (nIndex + nLen >= rStr.getLength()))
+ {
+ nLen = rStr.getLength() - nIndex;
+ }
+
+ SystemTextLayoutData aSysLayoutData;
+ aSysLayoutData.rGlyphData.reserve( 256 );
+ aSysLayoutData.orientation = 0;
+
+ if ( mpMetaFile )
+ {
+ if (pDXAry)
+ mpMetaFile->AddAction( new MetaTextArrayAction( rStartPt, rStr, pDXAry, nIndex, nLen ) );
+ else
+ mpMetaFile->AddAction( new MetaTextAction( rStartPt, rStr, nIndex, nLen ) );
+ }
+
+ if ( !IsDeviceOutputNecessary() ) return aSysLayoutData;
+
+ std::unique_ptr<SalLayout> pLayout = ImplLayout(rStr, nIndex, nLen, rStartPt, 0, pDXAry);
+
+ if ( !pLayout ) return aSysLayoutData;
+
+ // setup glyphs
+ Point aPos;
+ const GlyphItem* pGlyph;
+ int nStart = 0;
+ SystemGlyphData aSystemGlyph;
+ while (pLayout->GetNextGlyph(&pGlyph, aPos, nStart, nullptr, &aSystemGlyph.fallbacklevel))
+ {
+ aSystemGlyph.index = pGlyph->glyphId();
+ aSystemGlyph.x = aPos.X();
+ aSystemGlyph.y = aPos.Y();
+ aSysLayoutData.rGlyphData.push_back(aSystemGlyph);
+ }
+
+ // Get font data
+ aSysLayoutData.orientation = pLayout->GetOrientation();
+
+ return aSysLayoutData;
+}
+
+bool OutputDevice::GetTextBoundRect( tools::Rectangle& rRect,
+ const OUString& rStr, sal_Int32 nBase,
+ sal_Int32 nIndex, sal_Int32 nLen,
+ sal_uLong nLayoutWidth, const long* pDXAry,
+ const SalLayoutGlyphs* pGlyphs ) const
+{
+ bool bRet = false;
+ rRect.SetEmpty();
+
+ std::unique_ptr<SalLayout> pSalLayout;
+ const Point aPoint;
+ // calculate offset when nBase!=nIndex
+ long nXOffset = 0;
+ if( nBase != nIndex )
+ {
+ sal_Int32 nStart = std::min( nBase, nIndex );
+ sal_Int32 nOfsLen = std::max( nBase, nIndex ) - nStart;
+ pSalLayout = ImplLayout( rStr, nStart, nOfsLen, aPoint, nLayoutWidth, pDXAry );
+ if( pSalLayout )
+ {
+ nXOffset = pSalLayout->GetTextWidth();
+ nXOffset /= pSalLayout->GetUnitsPerPixel();
+ // TODO: fix offset calculation for Bidi case
+ if( nBase < nIndex)
+ nXOffset = -nXOffset;
+ }
+ }
+
+ pSalLayout = ImplLayout(rStr, nIndex, nLen, aPoint, nLayoutWidth, pDXAry, SalLayoutFlags::NONE,
+ nullptr, pGlyphs);
+ tools::Rectangle aPixelRect;
+ if( pSalLayout )
+ {
+ bRet = pSalLayout->GetBoundRect(aPixelRect);
+
+ if( bRet )
+ {
+ int nWidthFactor = pSalLayout->GetUnitsPerPixel();
+
+ if( nWidthFactor > 1 )
+ {
+ double fFactor = 1.0 / nWidthFactor;
+ aPixelRect.SetLeft(
+ static_cast< long >(aPixelRect.Left() * fFactor) );
+ aPixelRect.SetRight(
+ static_cast< long >(aPixelRect.Right() * fFactor) );
+ aPixelRect.SetTop(
+ static_cast< long >(aPixelRect.Top() * fFactor) );
+ aPixelRect.SetBottom(
+ static_cast< long >(aPixelRect.Bottom() * fFactor) );
+ }
+
+ Point aRotatedOfs( mnTextOffX, mnTextOffY );
+ aRotatedOfs -= pSalLayout->GetDrawPosition( Point( nXOffset, 0 ) );
+ 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, const long* pDXArray ) const
+{
+ if (!InitFont())
+ return false;
+
+ bool bRet = false;
+ rVector.clear();
+ if( nLen < 0 )
+ {
+ nLen = rStr.getLength() - nIndex;
+ }
+ rVector.reserve( nLen );
+
+ // we want to get the Rectangle in logical units, so to
+ // avoid rounding errors we just size the font in logical units
+ bool bOldMap = mbMap;
+ if( bOldMap )
+ {
+ const_cast<OutputDevice&>(*this).mbMap = false;
+ const_cast<OutputDevice&>(*this).mbNewFont = true;
+ }
+
+ std::unique_ptr<SalLayout> pSalLayout;
+
+ // calculate offset when nBase!=nIndex
+ long nXOffset = 0;
+ if( nBase != nIndex )
+ {
+ sal_Int32 nStart = std::min( nBase, nIndex );
+ sal_Int32 nOfsLen = std::max( nBase, nIndex ) - nStart;
+ pSalLayout = ImplLayout( rStr, nStart, nOfsLen, Point(0,0), nLayoutWidth, pDXArray );
+ if( pSalLayout )
+ {
+ nXOffset = pSalLayout->GetTextWidth();
+ pSalLayout.reset();
+ // TODO: fix offset calculation for Bidi case
+ if( nBase > nIndex)
+ nXOffset = -nXOffset;
+ }
+ }
+
+ pSalLayout = ImplLayout( rStr, nIndex, nLen, Point(0,0), nLayoutWidth, pDXArray );
+ if( pSalLayout )
+ {
+ bRet = pSalLayout->GetOutline(rVector);
+ if( bRet )
+ {
+ // transform polygon to pixel units
+ basegfx::B2DHomMatrix aMatrix;
+
+ int nWidthFactor = pSalLayout->GetUnitsPerPixel();
+ if( nXOffset | mnTextOffX | mnTextOffY )
+ {
+ Point aRotatedOfs( mnTextOffX*nWidthFactor, mnTextOffY*nWidthFactor );
+ aRotatedOfs -= pSalLayout->GetDrawPosition( Point( nXOffset, 0 ) );
+ aMatrix.translate( aRotatedOfs.X(), aRotatedOfs.Y() );
+ }
+
+ if( nWidthFactor > 1 )
+ {
+ double fFactor = 1.0 / nWidthFactor;
+ aMatrix.scale( fFactor, fFactor );
+ }
+
+ 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 nTWidth, const long* pDXArray ) const
+{
+ rResultVector.clear();
+
+ // get the basegfx polypolygon vector
+ basegfx::B2DPolyPolygonVector aB2DPolyPolyVector;
+ if( !GetTextOutlines( aB2DPolyPolyVector, rStr, nBase, nIndex, nLen,
+ nTWidth, pDXArray ) )
+ return false;
+
+ // convert to a tool polypolygon vector
+ rResultVector.reserve( aB2DPolyPolyVector.size() );
+ for (auto const& elem : aB2DPolyPolyVector)
+ rResultVector.emplace_back(elem); // #i76339#
+
+ return true;
+}
+
+bool OutputDevice::GetTextOutline( tools::PolyPolygon& rPolyPoly, const OUString& rStr,
+ sal_Int32 nLen,
+ sal_uLong nTWidth, const long* pDXArray ) const
+{
+ rPolyPoly.Clear();
+
+ // get the basegfx polypolygon vector
+ basegfx::B2DPolyPolygonVector aB2DPolyPolyVector;
+ if( !GetTextOutlines( aB2DPolyPolyVector, rStr, 0/*nBase*/, 0/*nIndex*/, nLen,
+ nTWidth, 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;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/textline.cxx b/vcl/source/outdev/textline.cxx
new file mode 100644
index 000000000..6ade6113f
--- /dev/null
+++ b/vcl/source/outdev/textline.cxx
@@ -0,0 +1,1026 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <cassert>
+
+#include <sal/types.h>
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/virdev.hxx>
+
+#include <tools/helpers.hxx>
+
+#include <salgdi.hxx>
+#include <impglyphitem.hxx>
+
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/polygon/WaveLine.hxx>
+
+#define UNDERLINE_LAST LINESTYLE_BOLDWAVE
+#define STRIKEOUT_LAST STRIKEOUT_X
+
+void OutputDevice::ImplInitTextLineSize()
+{
+ mpFontInstance->mxFontMetric->ImplInitTextLineSize( this );
+}
+
+void OutputDevice::ImplInitAboveTextLineSize()
+{
+ mpFontInstance->mxFontMetric->ImplInitAboveTextLineSize();
+}
+
+void OutputDevice::ImplDrawWavePixel( long nOriginX, long nOriginY,
+ long nCurX, long nCurY,
+ short nOrientation,
+ SalGraphics* pGraphics,
+ OutputDevice const * pOutDev,
+ bool bDrawPixAsRect,
+ long nPixWidth, long nPixHeight )
+{
+ if ( nOrientation )
+ {
+ Point aPoint( nOriginX, nOriginY );
+ aPoint.RotateAround( nCurX, nCurY, nOrientation );
+ }
+
+ if ( bDrawPixAsRect )
+ {
+
+ pGraphics->DrawRect( nCurX, nCurY, nPixWidth, nPixHeight, pOutDev );
+ }
+ else
+ {
+ pGraphics->DrawPixel( nCurX, nCurY, pOutDev );
+ }
+}
+
+void OutputDevice::ImplDrawWaveLine( long nBaseX, long nBaseY,
+ long nDistX, long nDistY,
+ long nWidth, long nHeight,
+ long nLineWidth, short nOrientation,
+ const Color& rColor )
+{
+ if ( !nHeight )
+ return;
+
+ long nStartX = nBaseX + nDistX;
+ 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;
+
+ long nEndX = nStartX+nWidth;
+ 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
+ {
+ long nCurX = nStartX;
+ long nCurY = nStartY;
+ long nDiffX = 2;
+ long nDiffY = nHeight-1;
+ long nCount = nWidth;
+ long nOffY = -1;
+ long nPixWidth;
+ long nPixHeight;
+ bool bDrawPixAsRect;
+ // On printers that output pixel via DrawRect()
+ if ( (GetOutDevType() == OUTDEV_PRINTER) || (nLineWidth > 1) )
+ {
+ if ( mbLineColor || mbInitLineColor )
+ {
+ mpGraphics->SetLineColor();
+ mbInitLineColor = true;
+ }
+ mpGraphics->SetFillColor( rColor );
+ mbInitFillColor = true;
+ bDrawPixAsRect = true;
+ nPixWidth = nLineWidth;
+ nPixHeight = ((nLineWidth*mnDPIX)+(mnDPIY/2))/mnDPIY;
+ }
+ else
+ {
+ mpGraphics->SetLineColor( rColor );
+ mbInitLineColor = true;
+ nPixWidth = 1;
+ nPixHeight = 1;
+ bDrawPixAsRect = false;
+ }
+
+ if ( !nDiffY )
+ {
+ while ( nWidth )
+ {
+ ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nOrientation,
+ mpGraphics, this,
+ bDrawPixAsRect, nPixWidth, nPixHeight );
+ nCurX++;
+ nWidth--;
+ }
+ }
+ else
+ {
+ nCurY += nDiffY;
+ long nFreq = nCount / (nDiffX+nDiffY);
+ while ( nFreq-- )
+ {
+ for( long i = nDiffY; i; --i )
+ {
+ ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nOrientation,
+ mpGraphics, this,
+ bDrawPixAsRect, nPixWidth, nPixHeight );
+ nCurX++;
+ nCurY += nOffY;
+ }
+ for( long i = nDiffX; i; --i )
+ {
+ ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nOrientation,
+ mpGraphics, this,
+ bDrawPixAsRect, nPixWidth, nPixHeight );
+ nCurX++;
+ }
+ nOffY = -nOffY;
+ }
+ nFreq = nCount % (nDiffX+nDiffY);
+ if ( nFreq )
+ {
+ for( long i = nDiffY; i && nFreq; --i, --nFreq )
+ {
+ ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nOrientation,
+ mpGraphics, this,
+ bDrawPixAsRect, nPixWidth, nPixHeight );
+ nCurX++;
+ nCurY += nOffY;
+
+ }
+ for( long i = nDiffX; i && nFreq; --i, --nFreq )
+ {
+ ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nOrientation,
+ mpGraphics, this,
+ bDrawPixAsRect, nPixWidth, nPixHeight );
+ nCurX++;
+ }
+ }
+ }
+ }
+}
+
+void OutputDevice::ImplDrawWaveTextLine( long nBaseX, long nBaseY,
+ long nDistX, long nDistY, long nWidth,
+ FontLineStyle eTextLine,
+ Color aColor,
+ bool bIsAbove )
+{
+ LogicalFontInstance* pFontInstance = mpFontInstance.get();
+ long nLineHeight;
+ 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;
+
+ long nLineWidth = mnDPIX / 300;
+ if ( !nLineWidth )
+ nLineWidth = 1;
+
+ if ( eTextLine == LINESTYLE_BOLDWAVE )
+ nLineWidth *= 2;
+
+ nLinePos += nDistY - (nLineHeight / 2);
+
+ long nLineWidthHeight = ((nLineWidth * mnDPIX) + (mnDPIY / 2)) / mnDPIY;
+ if ( eTextLine == LINESTYLE_DOUBLEWAVE )
+ {
+ long nOrgLineHeight = nLineHeight;
+ nLineHeight /= 3;
+ if ( nLineHeight < 2 )
+ {
+ if ( nOrgLineHeight > 1 )
+ nLineHeight = 2;
+ else
+ nLineHeight = 1;
+ }
+
+ long nLineDY = nOrgLineHeight-(nLineHeight*2);
+ if ( nLineDY < nLineWidthHeight )
+ nLineDY = nLineWidthHeight;
+
+ 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( long nBaseX, long nBaseY,
+ long nDistX, long nDistY, long nWidth,
+ FontLineStyle eTextLine,
+ Color aColor,
+ bool bIsAbove )
+{
+ LogicalFontInstance* pFontInstance = mpFontInstance.get();
+ long nLineHeight = 0;
+ long nLinePos = 0;
+ long nLinePos2 = 0;
+
+ const 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 )
+ {
+ if ( mbLineColor || mbInitLineColor )
+ {
+ mpGraphics->SetLineColor();
+ mbInitLineColor = true;
+ }
+ mpGraphics->SetFillColor( aColor );
+ mbInitFillColor = true;
+
+ 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:
+ {
+ long nDotWidth = nLineHeight*mnDPIY;
+ nDotWidth += mnDPIY/2;
+ nDotWidth /= mnDPIY;
+
+ long nTempWidth = nDotWidth;
+ 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:
+ {
+ long nDotWidth = nLineHeight*mnDPIY;
+ nDotWidth += mnDPIY/2;
+ nDotWidth /= mnDPIY;
+
+ long nMinDashWidth;
+ long nMinSpaceWidth;
+ long nSpaceWidth;
+ 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;
+
+ long nTempWidth = nDashWidth;
+ 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:
+ {
+ long nDotWidth = nLineHeight*mnDPIY;
+ nDotWidth += mnDPIY/2;
+ nDotWidth /= mnDPIY;
+
+ long nDashWidth = ((100*mnDPIX)+1270)/2540;
+ 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;
+
+ long nTempDotWidth = nDotWidth;
+ long nTempDashWidth = nDashWidth;
+ 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:
+ {
+ long nDotWidth = nLineHeight*mnDPIY;
+ nDotWidth += mnDPIY/2;
+ nDotWidth /= mnDPIY;
+
+ long nDashWidth = ((100*mnDPIX)+1270)/2540;
+ 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;
+
+ long nTempDotWidth = nDotWidth;
+ long nTempDashWidth = nDashWidth;
+ 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( long nBaseX, long nBaseY,
+ long nDistX, long nDistY, long nWidth,
+ FontStrikeout eStrikeout,
+ Color aColor )
+{
+ LogicalFontInstance* pFontInstance = mpFontInstance.get();
+ long nLineHeight = 0;
+ long nLinePos = 0;
+ long nLinePos2 = 0;
+
+ 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 )
+ {
+ if ( mbLineColor || mbInitLineColor )
+ {
+ mpGraphics->SetLineColor();
+ mbInitLineColor = true;
+ }
+ mpGraphics->SetFillColor( aColor );
+ mbInitFillColor = true;
+
+ const 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( long nBaseX, long nBaseY,
+ long nDistX, long nDistY, 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
+ long nStrikeoutWidth = 0;
+ std::unique_ptr<SalLayout> pLayout = ImplLayout( aStrikeoutTest, 0, nTestStrLen );
+ if( pLayout )
+ {
+ nStrikeoutWidth = pLayout->GetTextWidth() / (nTestStrLen * pLayout->GetUnitsPerPixel());
+ }
+ if( nStrikeoutWidth <= 0 ) // sanity check
+ return;
+
+ int nStrikeStrLen = (nWidth+(nStrikeoutWidth-1)) / nStrikeoutWidth;
+ if( nStrikeStrLen > nMaxStrikeStrLen )
+ nStrikeStrLen = nMaxStrikeStrLen;
+
+ // 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
+ ComplexTextLayoutFlags nOrigTLM = mnTextLayoutMode;
+ mnTextLayoutMode = 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() = Point( 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( PushFlags::CLIPREGION );
+ IntersectClipRegion( PixelToLogic(aPixelRect) );
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ pLayout->DrawText( *mpGraphics );
+
+ Pop();
+
+ SetTextColor( aOldColor );
+ ImplInitTextColor();
+}
+
+void OutputDevice::ImplDrawTextLine( long nX, long nY,
+ long nDistX, DeviceCoordinate nWidth,
+ FontStrikeout eStrikeout,
+ FontLineStyle eUnderline,
+ FontLineStyle eOverline,
+ bool bUnderlineAbove )
+{
+ if ( !nWidth )
+ return;
+
+ Color aStrikeoutColor = GetTextColor();
+ Color aUnderlineColor = GetTextLineColor();
+ Color aOverlineColor = GetOverlineColor();
+ bool bStrikeoutDone = false;
+ bool bUnderlineDone = false;
+ bool bOverlineDone = false;
+
+ if ( IsRTLEnabled() )
+ {
+ long nXAdd = nWidth - nDistX;
+ if( mpFontInstance->mnOrientation )
+ nXAdd = FRound( nXAdd * cos( mpFontInstance->mnOrientation * F_PI1800 ) );
+
+ 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 Point aStartPt = rSalLayout.DrawBase();
+
+ // calculate distance of each word from the base point
+ Point aPos;
+ DeviceCoordinate nDist = 0;
+ DeviceCoordinate nWidth = 0;
+ const GlyphItem* pGlyph;
+ int nStart = 0;
+ while (rSalLayout.GetNextGlyph(&pGlyph, aPos, nStart))
+ {
+ // calculate the boundaries of each word
+ if (!pGlyph->IsSpacing())
+ {
+ if( !nWidth )
+ {
+ // get the distance to the base point (as projected to baseline)
+ nDist = aPos.X() - aStartPt.X();
+ if( mpFontInstance->mnOrientation )
+ {
+ const long nDY = aPos.Y() - aStartPt.Y();
+ const double fRad = mpFontInstance->mnOrientation * F_PI1800;
+ nDist = FRound( nDist*cos(fRad) - nDY*sin(fRad) );
+ }
+ }
+
+ // update the length of the textline
+ nWidth += pGlyph->m_nNewWidth;
+ }
+ else if( nWidth > 0 )
+ {
+ // draw the textline for each word
+ ImplDrawTextLine( aStartPt.X(), aStartPt.Y(), nDist, nWidth,
+ eStrikeout, eUnderline, eOverline, bUnderlineAbove );
+ nWidth = 0;
+ }
+ }
+
+ // draw textline for the last word
+ if( nWidth > 0 )
+ {
+ ImplDrawTextLine( aStartPt.X(), aStartPt.Y(), nDist, nWidth,
+ eStrikeout, eUnderline, eOverline, bUnderlineAbove );
+ }
+ }
+ else
+ {
+ Point aStartPt = rSalLayout.GetDrawPosition();
+ ImplDrawTextLine( aStartPt.X(), aStartPt.Y(), 0,
+ rSalLayout.GetTextWidth() / rSalLayout.GetUnitsPerPixel(),
+ eStrikeout, eUnderline, eOverline, bUnderlineAbove );
+ }
+}
+
+void OutputDevice::ImplDrawMnemonicLine( long nX, long nY, long nWidth )
+{
+ long nBaseX = nX;
+ if( /*HasMirroredGraphics() &&*/ IsRTLEnabled() )
+ {
+ // add some strange offset
+ nX += 2;
+ // revert the hack that will be done later in ImplDrawTextLine
+ nX = nBaseX - nWidth - (nX - nBaseX - 1);
+ }
+
+ ImplDrawTextLine( nX, nY, 0, nWidth, STRIKEOUT_NONE, LINESTYLE_SINGLE, LINESTYLE_NONE, false );
+}
+
+void OutputDevice::SetTextLineColor()
+{
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaTextLineColorAction( Color(), false ) );
+
+ maTextLineColor = COL_TRANSPARENT;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetTextLineColor();
+}
+
+void OutputDevice::SetTextLineColor( const Color& rColor )
+{
+
+ Color aColor( rColor );
+
+ if ( mnDrawMode & ( DrawModeFlags::BlackText | DrawModeFlags::WhiteText |
+ DrawModeFlags::GrayText |
+ DrawModeFlags::SettingsText ) )
+ {
+ if ( mnDrawMode & DrawModeFlags::BlackText )
+ {
+ aColor = COL_BLACK;
+ }
+ else if ( mnDrawMode & DrawModeFlags::WhiteText )
+ {
+ aColor = COL_WHITE;
+ }
+ else if ( mnDrawMode & DrawModeFlags::GrayText )
+ {
+ const sal_uInt8 cLum = aColor.GetLuminance();
+ aColor = Color( cLum, cLum, cLum );
+ }
+ else if ( mnDrawMode & DrawModeFlags::SettingsText )
+ {
+ aColor = GetSettings().GetStyleSettings().GetFontColor();
+ }
+ }
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaTextLineColorAction( aColor, true ) );
+
+ maTextLineColor = aColor;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetTextLineColor( COL_BLACK );
+}
+
+void OutputDevice::SetOverlineColor()
+{
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaOverlineColorAction( Color(), false ) );
+
+ maOverlineColor = COL_TRANSPARENT;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetOverlineColor();
+}
+
+void OutputDevice::SetOverlineColor( const Color& rColor )
+{
+
+ Color aColor( rColor );
+
+ if ( mnDrawMode & ( DrawModeFlags::BlackText | DrawModeFlags::WhiteText |
+ DrawModeFlags::GrayText |
+ DrawModeFlags::SettingsText ) )
+ {
+ if ( mnDrawMode & DrawModeFlags::BlackText )
+ {
+ aColor = COL_BLACK;
+ }
+ else if ( mnDrawMode & DrawModeFlags::WhiteText )
+ {
+ aColor = COL_WHITE;
+ }
+ else if ( mnDrawMode & DrawModeFlags::GrayText )
+ {
+ const sal_uInt8 cLum = aColor.GetLuminance();
+ aColor = Color( cLum, cLum, cLum );
+ }
+ else if ( mnDrawMode & DrawModeFlags::SettingsText )
+ {
+ aColor = GetSettings().GetStyleSettings().GetFontColor();
+ }
+ }
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaOverlineColorAction( aColor, true ) );
+
+ maOverlineColor = aColor;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetOverlineColor( COL_BLACK );
+}
+
+void OutputDevice::DrawTextLine( const Point& rPos, long nWidth,
+ FontStrikeout eStrikeout,
+ FontLineStyle eUnderline,
+ FontLineStyle eOverline,
+ bool bUnderlineAbove )
+{
+ assert(!is_double_buffered_window());
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaTextLineAction( rPos, nWidth, eStrikeout, eUnderline, eOverline ) );
+
+ if ( ((eUnderline == LINESTYLE_NONE) || (eUnderline == LINESTYLE_DONTKNOW)) &&
+ ((eOverline == LINESTYLE_NONE) || (eOverline == LINESTYLE_DONTKNOW)) &&
+ ((eStrikeout == STRIKEOUT_NONE) || (eStrikeout == STRIKEOUT_DONTKNOW)) )
+ {
+ return;
+ }
+ if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() )
+ return;
+
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ if( mbOutputClipped )
+ return;
+
+ // initialize font if needed to get text offsets
+ // TODO: only needed for mnTextOff!=(0,0)
+ if (!InitFont())
+ return;
+
+ Point aPos = ImplLogicToDevicePixel( rPos );
+ DeviceCoordinate fWidth;
+ fWidth = LogicWidthToDeviceCoordinate( nWidth );
+ aPos += Point( mnTextOffX, mnTextOffY );
+ ImplDrawTextLine( aPos.X(), aPos.X(), 0, fWidth, eStrikeout, eUnderline, eOverline, bUnderlineAbove );
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawTextLine( rPos, nWidth, eStrikeout, eUnderline, eOverline, bUnderlineAbove );
+}
+
+void OutputDevice::DrawWaveLine(const Point& rStartPos, const Point& rEndPos, long nLineWidth)
+{
+ assert(!is_double_buffered_window());
+
+ if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() )
+ return;
+
+ // we need a graphics
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ if (!InitFont())
+ return;
+
+ Point aStartPt = ImplLogicToDevicePixel(rStartPos);
+ Point aEndPt = ImplLogicToDevicePixel(rEndPos);
+
+ long nStartX = aStartPt.X();
+ long nStartY = aStartPt.Y();
+ long nEndX = aEndPt.X();
+ long nEndY = aEndPt.Y();
+ double fOrientation = 0.0;
+
+ // handle rotation
+ if (nStartY != nEndY || nStartX > nEndX)
+ {
+ long nLengthX = nEndX - nStartX;
+ fOrientation = std::atan2(nStartY - nEndY, (nLengthX == 0 ? 0.000000001 : nLengthX));
+ fOrientation /= F_PI180;
+ // un-rotate the end point
+ aStartPt.RotateAround(nEndX, nEndY, -fOrientation * 10.0);
+ }
+
+ long nWaveHeight = 3;
+
+ // 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;
+ }
+
+ 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->DrawWaveLine( rStartPos, rEndPos, nLineWidth );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/transparent.cxx b/vcl/source/outdev/transparent.cxx
new file mode 100644
index 000000000..56e4f2d03
--- /dev/null
+++ b/vcl/source/outdev/transparent.cxx
@@ -0,0 +1,859 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <cassert>
+
+#include <sal/types.h>
+#include <tools/helpers.hxx>
+#include <rtl/math.hxx>
+
+#include <memory>
+
+#include <vcl/bitmapaccess.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/virdev.hxx>
+
+#include <outdata.hxx>
+#include <salgdi.hxx>
+#include <bitmapwriteaccess.hxx>
+
+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;
+ }
+}
+
+Color OutputDevice::ImplDrawModeToColor( const Color& rColor ) const
+{
+ Color aColor( rColor );
+ DrawModeFlags nDrawMode = GetDrawMode();
+
+ if( nDrawMode & ( DrawModeFlags::BlackLine | DrawModeFlags::WhiteLine |
+ DrawModeFlags::GrayLine |
+ DrawModeFlags::SettingsLine ) )
+ {
+ if( !ImplIsColorTransparent( aColor ) )
+ {
+ if( nDrawMode & DrawModeFlags::BlackLine )
+ {
+ aColor = COL_BLACK;
+ }
+ else if( nDrawMode & DrawModeFlags::WhiteLine )
+ {
+ aColor = COL_WHITE;
+ }
+ else if( nDrawMode & DrawModeFlags::GrayLine )
+ {
+ const sal_uInt8 cLum = aColor.GetLuminance();
+ aColor = Color( cLum, cLum, cLum );
+ }
+ else if( nDrawMode & DrawModeFlags::SettingsLine )
+ {
+ aColor = GetSettings().GetStyleSettings().GetFontColor();
+ }
+ }
+ }
+ return aColor;
+}
+
+void OutputDevice::ImplPrintTransparent( const Bitmap& rBmp, const Bitmap& rMask,
+ const Point& rDestPt, const Size& rDestSize,
+ const Point& rSrcPtPixel, const Size& rSrcSizePixel )
+{
+ Point aDestPt( LogicToPixel( rDestPt ) );
+ Size aDestSz( LogicToPixel( rDestSize ) );
+ tools::Rectangle aSrcRect( rSrcPtPixel, rSrcSizePixel );
+
+ aSrcRect.Justify();
+
+ if( rBmp.IsEmpty() || !aSrcRect.GetWidth() || !aSrcRect.GetHeight() || !aDestSz.Width() || !aDestSz.Height() )
+ return;
+
+ Bitmap aPaint( rBmp ), aMask( rMask );
+ BmpMirrorFlags nMirrFlags = BmpMirrorFlags::NONE;
+
+ if( aMask.GetBitCount() > 1 )
+ aMask.Convert( BmpConversion::N1BitThreshold );
+
+ // mirrored horizontically
+ if( aDestSz.Width() < 0 )
+ {
+ aDestSz.setWidth( -aDestSz.Width() );
+ aDestPt.AdjustX( -( aDestSz.Width() - 1 ) );
+ nMirrFlags |= BmpMirrorFlags::Horizontal;
+ }
+
+ // mirrored vertically
+ if( aDestSz.Height() < 0 )
+ {
+ aDestSz.setHeight( -aDestSz.Height() );
+ aDestPt.AdjustY( -( aDestSz.Height() - 1 ) );
+ nMirrFlags |= BmpMirrorFlags::Vertical;
+ }
+
+ // source cropped?
+ if( aSrcRect != tools::Rectangle( Point(), aPaint.GetSizePixel() ) )
+ {
+ aPaint.Crop( aSrcRect );
+ aMask.Crop( aSrcRect );
+ }
+
+ // destination mirrored
+ if( nMirrFlags != BmpMirrorFlags::NONE )
+ {
+ aPaint.Mirror( nMirrFlags );
+ aMask.Mirror( nMirrFlags );
+ }
+
+ // we always want to have a mask
+ if( aMask.IsEmpty() )
+ {
+ aMask = Bitmap( aSrcRect.GetSize(), 1 );
+ aMask.Erase( COL_BLACK );
+ }
+
+ // do painting
+ const long nSrcWidth = aSrcRect.GetWidth(), nSrcHeight = aSrcRect.GetHeight();
+ long nX, nY; // , nWorkX, nWorkY, nWorkWidth, nWorkHeight;
+ std::unique_ptr<long[]> pMapX(new long[ nSrcWidth + 1 ]);
+ std::unique_ptr<long[]> pMapY(new long[ nSrcHeight + 1 ]);
+ const bool bOldMap = mbMap;
+
+ mbMap = false;
+
+ // create forward mapping tables
+ for( nX = 0; nX <= nSrcWidth; nX++ )
+ pMapX[ nX ] = aDestPt.X() + FRound( static_cast<double>(aDestSz.Width()) * nX / nSrcWidth );
+
+ for( nY = 0; nY <= nSrcHeight; nY++ )
+ pMapY[ nY ] = aDestPt.Y() + FRound( static_cast<double>(aDestSz.Height()) * nY / nSrcHeight );
+
+ // walk through all rectangles of mask
+ const vcl::Region aWorkRgn(aMask.CreateRegion(COL_BLACK, tools::Rectangle(Point(), aMask.GetSizePixel())));
+ RectangleVector aRectangles;
+ aWorkRgn.GetRegionRectangles(aRectangles);
+
+ for (auto const& rectangle : aRectangles)
+ {
+ const Point aMapPt(pMapX[rectangle.Left()], pMapY[rectangle.Top()]);
+ const Size aMapSz( pMapX[rectangle.Right() + 1] - aMapPt.X(), // pMapX[L + W] -> L + ((R - L) + 1) -> R + 1
+ pMapY[rectangle.Bottom() + 1] - aMapPt.Y()); // same for Y
+ Bitmap aBandBmp(aPaint);
+
+ aBandBmp.Crop(rectangle);
+ DrawBitmap(aMapPt, aMapSz, Point(), aBandBmp.GetSizePixel(), aBandBmp);
+ }
+
+ mbMap = bOldMap;
+
+}
+
+// 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;
+
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ if( mbOutputClipped )
+ return;
+
+ if( mbInitLineColor )
+ InitLineColor();
+
+ if( mbInitFillColor )
+ InitFillColor();
+
+ if((mnAntialiasing & AntialiasingFlags::EnableB2dDraw) &&
+ mpGraphics->supportsOperation(OutDevSupportType::B2DDraw) &&
+ (RasterOp::OverPaint == GetRasterOp()) )
+ {
+ // b2dpolygon support not implemented yet on non-UNX platforms
+ basegfx::B2DPolyPolygon aB2DPolyPolygon(rB2DPolyPoly);
+
+ // ensure it is closed
+ if(!aB2DPolyPolygon.isClosed())
+ {
+ // maybe assert, prevents buffering due to making a copy
+ aB2DPolyPolygon.setClosed( true );
+ }
+
+ // create ObjectToDevice transformation
+ const basegfx::B2DHomMatrix aFullTransform(ImplGetDeviceTransformation() * rObjectTransform);
+ // TODO: this must not drop transparency for mpAlphaVDev case, but instead use premultiplied
+ // aplha... but that requires using premultiplied alpha also for already drawn data
+ const double fAdjustedTransparency = mpAlphaVDev ? 0 : fTransparency;
+ bool bDrawnOk(true);
+
+ if( IsFillColor() )
+ {
+ bDrawnOk = mpGraphics->DrawPolyPolygon(
+ aFullTransform,
+ aB2DPolyPolygon,
+ fAdjustedTransparency,
+ this);
+ }
+
+ if( bDrawnOk && IsLineColor() )
+ {
+ const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
+
+ for(auto const& rPolygon : aB2DPolyPolygon)
+ {
+ mpGraphics->DrawPolyLine(
+ aFullTransform,
+ rPolygon,
+ fAdjustedTransparency,
+ 0.0, // tdf#124848 hairline
+ nullptr, // MM01
+ basegfx::B2DLineJoin::NONE,
+ css::drawing::LineCap_BUTT,
+ basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
+ bPixelSnapHairline,
+ this );
+ }
+ }
+
+ if( bDrawnOk )
+ {
+ if( mpMetaFile )
+ {
+ // tdf#119843 need transformed Polygon here
+ basegfx::B2DPolyPolygon aB2DPolyPoly(rB2DPolyPoly);
+ aB2DPolyPoly.transform(rObjectTransform);
+ mpMetaFile->AddAction(
+ new MetaTransparentAction(
+ tools::PolyPolygon(aB2DPolyPoly),
+ static_cast< sal_uInt16 >(fTransparency * 100.0)));
+ }
+
+ if (mpAlphaVDev)
+ mpAlphaVDev->DrawTransparent(rObjectTransform, rB2DPolyPoly, fTransparency);
+
+ return;
+ }
+ }
+
+ // fallback to old polygon drawing if needed
+ // tdf#119843 need transformed Polygon here
+ basegfx::B2DPolyPolygon aB2DPolyPoly(rB2DPolyPoly);
+ aB2DPolyPoly.transform(rObjectTransform);
+ DrawTransparent(
+ toPolyPolygon(aB2DPolyPoly),
+ static_cast<sal_uInt16>(fTransparency * 100.0));
+}
+
+void OutputDevice::DrawInvisiblePolygon( const tools::PolyPolygon& rPolyPoly )
+{
+ assert(!is_double_buffered_window());
+
+ // short circuit if the polygon border is invisible too
+ if( !mbLineColor )
+ return;
+
+ // we assume that the border is NOT to be drawn transparently???
+ Push( PushFlags::FILLCOLOR );
+ SetFillColor();
+ DrawPolyPolygon( rPolyPoly );
+ Pop();
+}
+
+bool OutputDevice::DrawTransparentNatively ( const tools::PolyPolygon& rPolyPoly,
+ sal_uInt16 nTransparencePercent )
+{
+ assert(!is_double_buffered_window());
+
+ bool bDrawn = false;
+
+ // debug helper:
+ static const char* pDisableNative = getenv( "SAL_DISABLE_NATIVE_ALPHA");
+
+ if( !pDisableNative &&
+ mpGraphics->supportsOperation( OutDevSupportType::B2DDraw )
+#if defined UNX && ! defined MACOSX && ! defined IOS
+ && GetBitCount() > 8
+#endif
+#ifdef _WIN32
+ // workaround bad dithering on remote displaying when using GDI+ with toolbar button highlighting
+ && !rPolyPoly.IsRect()
+#endif
+ )
+ {
+ // prepare the graphics device
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ if( mbOutputClipped )
+ return false;
+
+ if( mbInitLineColor )
+ InitLineColor();
+
+ if( mbInitFillColor )
+ InitFillColor();
+
+ // get the polygon in device coordinates
+ basegfx::B2DPolyPolygon aB2DPolyPolygon(rPolyPoly.getB2DPolyPolygon());
+ const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation());
+
+ const double fTransparency = 0.01 * nTransparencePercent;
+ if( mbFillColor )
+ {
+ // #i121591#
+ // CAUTION: Only non printing (pixel-renderer) VCL commands from OutputDevices
+ // should be used when printing. Normally this is avoided by the printer being
+ // non-AAed and thus e.g. on WIN GdiPlus calls are not used. It may be necessary
+ // to figure out a way of moving this code to its own function that is
+ // overridden by the Print class, which will mean we deliberately override the
+ // functionality and we use the fallback some lines below (which is not very good,
+ // though. For now, WinSalGraphics::drawPolyPolygon will detect printer usage and
+ // correct the wrong mapping (see there for details)
+ bDrawn = mpGraphics->DrawPolyPolygon(
+ aTransform,
+ aB2DPolyPolygon,
+ fTransparency,
+ this);
+ }
+
+ if( mbLineColor )
+ {
+ // disable the fill color for now
+ mpGraphics->SetFillColor();
+
+ // draw the border line
+ const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
+
+ for(auto const& rPolygon : 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;
+
+ // debug helper:
+ static const char* pDisableNative = getenv( "SAL_DISABLE_NATIVE_ALPHA" );
+
+ // #i66849# Added fast path for exactly rectangular
+ // polygons
+ // #i83087# Naturally, system alpha blending cannot
+ // work with separate alpha VDev
+ if( !mpAlphaVDev && !pDisableNative && aPolyPoly.IsRect() )
+ {
+ // setup Graphics only here (other cases delegate
+ // to basic OutDev methods)
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ if ( mbInitFillColor )
+ InitFillColor();
+
+ tools::Rectangle aLogicPolyRect( rPolyPoly.GetBoundRect() );
+ tools::Rectangle aPixelRect( ImplLogicToDevicePixel( aLogicPolyRect ) );
+
+ if( !mbOutputClipped )
+ {
+ bDrawn = mpGraphics->DrawAlphaRect( aPixelRect.Left(), aPixelRect.Top(),
+ // #i98405# use methods with small g, else one pixel too much will be painted.
+ // This is because the source is a polygon which when painted would not paint
+ // the rightmost and lowest pixel line(s), so use one pixel less for the
+ // rectangle, too.
+ aPixelRect.getWidth(), aPixelRect.getHeight(),
+ sal::static_int_cast<sal_uInt8>(nTransparencePercent),
+ this );
+ }
+ else
+ {
+ bDrawn = true;
+ }
+ }
+
+ if( !bDrawn )
+ {
+ ScopedVclPtrInstance< VirtualDevice > aVDev(*this, DeviceFormat::BITMASK);
+ const Size aDstSz( aDstRect.GetSize() );
+ const sal_uInt8 cTrans = static_cast<sal_uInt8>(MinMax( FRound( nTransparencePercent * 2.55 ), 0, 255 ));
+
+ if( aDstRect.Left() || aDstRect.Top() )
+ aPolyPoly.Move( -aDstRect.Left(), -aDstRect.Top() );
+
+ if( aVDev->SetOutputSizePixel( aDstSz ) )
+ {
+ const bool bOldMap = mbMap;
+
+ EnableMapMode( false );
+
+ aVDev->SetLineColor( COL_BLACK );
+ aVDev->SetFillColor( COL_BLACK );
+ aVDev->DrawPolyPolygon( aPolyPoly );
+
+ Bitmap aPaint( GetBitmap( aDstRect.TopLeft(), aDstSz ) );
+ Bitmap aPolyMask( aVDev->GetBitmap( Point(), aDstSz ) );
+
+ // #107766# check for non-empty bitmaps before accessing them
+ if( !!aPaint && !!aPolyMask )
+ {
+ BitmapScopedWriteAccess pW(aPaint);
+ Bitmap::ScopedReadAccess pR(aPolyMask);
+
+ if( pW && pR )
+ {
+ BitmapColor aPixCol;
+ const BitmapColor aFillCol( GetFillColor() );
+ const BitmapColor aBlack( pR->GetBestMatchingColor( COL_BLACK ) );
+ const long nWidth = pW->Width();
+ const long nHeight = pW->Height();
+ const long nR = aFillCol.GetRed();
+ const long nG = aFillCol.GetGreen();
+ const long nB = aFillCol.GetBlue();
+ long nX, nY;
+
+ if( aPaint.GetBitCount() <= 8 )
+ {
+ const BitmapPalette& rPal = pW->GetPalette();
+ const sal_uInt16 nCount = rPal.GetEntryCount();
+ BitmapColor* pMap = reinterpret_cast<BitmapColor*>(new sal_uInt8[ nCount * sizeof( BitmapColor ) ]);
+
+ 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 ) ] );
+ }
+ }
+ }
+ }
+ delete[] reinterpret_cast<sal_uInt8*>(pMap);
+ }
+ 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 ] = ColorChannelMerge( pWScan[ 0 ], nB, cTrans );
+ pWScan[ 1 ] = ColorChannelMerge( pWScan[ 1 ], nG, cTrans );
+ pWScan[ 2 ] = 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( 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 || (nTransparencePercent >= 100) )
+ {
+ DrawInvisiblePolygon( rPolyPoly );
+ 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;
+
+ // try hard to draw it directly, because the emulation layers are slower
+ bDrawn = DrawTransparentNatively( rPolyPoly, nTransparencePercent );
+
+ if (!bDrawn)
+ EmulateDrawTransparent( rPolyPoly, nTransparencePercent );
+
+ // #110958# Apply alpha value also to VDev alpha channel
+ if( mpAlphaVDev )
+ {
+ const Color aFillCol( mpAlphaVDev->GetFillColor() );
+ mpAlphaVDev->SetFillColor( Color(sal::static_int_cast<sal_uInt8>(255*nTransparencePercent/100),
+ sal::static_int_cast<sal_uInt8>(255*nTransparencePercent/100),
+ sal::static_int_cast<sal_uInt8>(255*nTransparencePercent/100)) );
+
+ mpAlphaVDev->DrawTransparent( rPolyPoly, nTransparencePercent );
+
+ mpAlphaVDev->SetFillColor( aFillCol );
+ }
+}
+
+void OutputDevice::DrawTransparent( const GDIMetaFile& rMtf, const Point& rPos,
+ const Size& rSize, const Gradient& rTransparenceGradient )
+{
+ assert(!is_double_buffered_window());
+
+ const Color aBlack( COL_BLACK );
+
+ if( mpMetaFile )
+ {
+ // missing here is to map the data using the DeviceTransformation
+ mpMetaFile->AddAction( new MetaFloatTransparentAction( rMtf, rPos, rSize, rTransparenceGradient ) );
+ }
+
+ if ( !IsDeviceOutputNecessary() )
+ return;
+
+ if( ( rTransparenceGradient.GetStartColor() == aBlack && rTransparenceGradient.GetEndColor() == aBlack ) ||
+ ( mnDrawMode & DrawModeFlags::NoTransparency ) )
+ {
+ const_cast<GDIMetaFile&>(rMtf).WindStart();
+ const_cast<GDIMetaFile&>(rMtf).Play( this, rPos, rSize );
+ const_cast<GDIMetaFile&>(rMtf).WindStart();
+ }
+ else
+ {
+ GDIMetaFile* pOldMetaFile = mpMetaFile;
+ tools::Rectangle aOutRect( LogicToPixel( rPos ), LogicToPixel( rSize ) );
+ Point aPoint;
+ tools::Rectangle aDstRect( aPoint, GetOutputSizePixel() );
+
+ mpMetaFile = nullptr;
+ aDstRect.Intersection( aOutRect );
+
+ ClipToPaintRegion( aDstRect );
+
+ if( !aDstRect.IsEmpty() )
+ {
+ ScopedVclPtrInstance< VirtualDevice > xVDev;
+
+ xVDev->mnDPIX = mnDPIX;
+ xVDev->mnDPIY = mnDPIY;
+
+ if( xVDev->SetOutputSizePixel( aDstRect.GetSize() ) )
+ {
+ if(GetAntialiasing() != AntialiasingFlags::NONE)
+ {
+ // #i102109#
+ // For MetaFile replay (see task) it may now be necessary to take
+ // into account that the content is AntiAlialiased and needs to be masked
+ // like that. Instead of masking, i will use a copy-modify-paste cycle
+ // here (as i already use in the VclPrimiziveRenderer with success)
+ xVDev->SetAntialiasing(GetAntialiasing());
+
+ // create MapMode for buffer (offset needed) and set
+ MapMode aMap(GetMapMode());
+ const Point aOutPos(PixelToLogic(aDstRect.TopLeft()));
+ aMap.SetOrigin(Point(-aOutPos.X(), -aOutPos.Y()));
+ xVDev->SetMapMode(aMap);
+
+ // copy MapMode state and disable for target
+ const bool bOrigMapModeEnabled(IsMapModeEnabled());
+ EnableMapMode(false);
+
+ // copy MapMode state and disable for buffer
+ const bool bBufferMapModeEnabled(xVDev->IsMapModeEnabled());
+ xVDev->EnableMapMode(false);
+
+ // copy content from original to buffer
+ xVDev->DrawOutDev( aPoint, xVDev->GetOutputSizePixel(), // dest
+ aDstRect.TopLeft(), xVDev->GetOutputSizePixel(), // source
+ *this);
+
+ // draw MetaFile to buffer
+ xVDev->EnableMapMode(bBufferMapModeEnabled);
+ const_cast<GDIMetaFile&>(rMtf).WindStart();
+ const_cast<GDIMetaFile&>(rMtf).Play(xVDev.get(), rPos, rSize);
+ const_cast<GDIMetaFile&>(rMtf).WindStart();
+
+ // get content bitmap from buffer
+ xVDev->EnableMapMode(false);
+
+ const Bitmap aPaint(xVDev->GetBitmap(aPoint, xVDev->GetOutputSizePixel()));
+
+ // create alpha mask from gradient and get as Bitmap
+ xVDev->EnableMapMode(bBufferMapModeEnabled);
+ xVDev->SetDrawMode(DrawModeFlags::GrayGradient);
+ xVDev->DrawGradient(tools::Rectangle(rPos, rSize), rTransparenceGradient);
+ xVDev->SetDrawMode(DrawModeFlags::Default);
+ xVDev->EnableMapMode(false);
+
+ const AlphaMask aAlpha(xVDev->GetBitmap(aPoint, xVDev->GetOutputSizePixel()));
+
+ xVDev.disposeAndClear();
+
+ // draw masked content to target and restore MapMode
+ DrawBitmapEx(aDstRect.TopLeft(), BitmapEx(aPaint, aAlpha));
+ EnableMapMode(bOrigMapModeEnabled);
+ }
+ else
+ {
+ Bitmap aPaint, aMask;
+ AlphaMask aAlpha;
+ 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.get(), rPos, rSize );
+ const_cast<GDIMetaFile&>(rMtf).WindStart();
+ xVDev->EnableMapMode( false );
+ aPaint = xVDev->GetBitmap( Point(), xVDev->GetOutputSizePixel() );
+ xVDev->EnableMapMode( bVDevOldMap ); // #i35331#: MUST NOT use EnableMapMode( sal_True ) here!
+
+ // create mask bitmap
+ xVDev->SetLineColor( COL_BLACK );
+ xVDev->SetFillColor( COL_BLACK );
+ xVDev->DrawRect( tools::Rectangle( xVDev->PixelToLogic( Point() ), xVDev->GetOutputSize() ) );
+ xVDev->SetDrawMode( DrawModeFlags::WhiteLine | DrawModeFlags::WhiteFill | DrawModeFlags::WhiteText |
+ DrawModeFlags::WhiteBitmap | DrawModeFlags::WhiteGradient );
+ const_cast<GDIMetaFile&>(rMtf).WindStart();
+ const_cast<GDIMetaFile&>(rMtf).Play( xVDev.get(), rPos, rSize );
+ const_cast<GDIMetaFile&>(rMtf).WindStart();
+ xVDev->EnableMapMode( false );
+ aMask = xVDev->GetBitmap( Point(), xVDev->GetOutputSizePixel() );
+ xVDev->EnableMapMode( bVDevOldMap ); // #i35331#: MUST NOT use EnableMapMode( sal_True ) here!
+
+ // create alpha mask from gradient
+ xVDev->SetDrawMode( DrawModeFlags::GrayGradient );
+ xVDev->DrawGradient( tools::Rectangle( rPos, rSize ), rTransparenceGradient );
+ xVDev->SetDrawMode( DrawModeFlags::Default );
+ xVDev->EnableMapMode( false );
+ xVDev->DrawMask( Point(), xVDev->GetOutputSizePixel(), aMask, COL_WHITE );
+
+ aAlpha = xVDev->GetBitmap( Point(), xVDev->GetOutputSizePixel() );
+
+ xVDev.disposeAndClear();
+
+ EnableMapMode( false );
+ DrawBitmapEx( aDstRect.TopLeft(), BitmapEx( aPaint, aAlpha ) );
+ EnableMapMode( bOldMap );
+ }
+ }
+ }
+
+ mpMetaFile = pOldMetaFile;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/vclreferencebase.cxx b/vcl/source/outdev/vclreferencebase.cxx
new file mode 100644
index 000000000..62dd0e67f
--- /dev/null
+++ b/vcl/source/outdev/vclreferencebase.cxx
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <vcl/vclreferencebase.hxx>
+
+VclReferenceBase::VclReferenceBase() :
+ mnRefCnt(1), // cf. VclPtrInstance and README.lifecycle
+ mbDisposed(false)
+{
+}
+
+VclReferenceBase::~VclReferenceBase()
+{
+ disposeOnce();
+}
+
+void VclReferenceBase::disposeOnce()
+{
+ if ( mbDisposed )
+ return;
+ mbDisposed = true;
+ dispose();
+}
+
+void VclReferenceBase::dispose()
+{
+}
+
diff --git a/vcl/source/outdev/wallpaper.cxx b/vcl/source/outdev/wallpaper.cxx
new file mode 100644
index 000000000..594fdbe05
--- /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 <cassert>
+
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/virdev.hxx>
+
+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;
+}
+
+Color OutputDevice::GetBackgroundColor() const
+{
+ return GetBackground().GetColor();
+}
+
+void OutputDevice::DrawWallpaper( const tools::Rectangle& rRect,
+ const Wallpaper& rWallpaper )
+{
+ assert(!is_double_buffered_window());
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaWallpaperAction( rRect, rWallpaper ) );
+
+ if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() )
+ return;
+
+ if ( rWallpaper.GetStyle() != WallpaperStyle::NONE )
+ {
+ tools::Rectangle aRect = LogicToPixel( rRect );
+ aRect.Justify();
+
+ if ( !aRect.IsEmpty() )
+ {
+ DrawWallpaper( aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight(),
+ rWallpaper );
+ }
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawWallpaper( rRect, rWallpaper );
+}
+
+void OutputDevice::DrawWallpaper( long nX, long nY,
+ long nWidth, 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( long nX, long nY,
+ long nWidth, 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( long nX, long nY,
+ long nWidth, long nHeight,
+ const Wallpaper& rWallpaper )
+{
+ assert(!is_double_buffered_window());
+
+ BitmapEx aBmpEx;
+ const BitmapEx* pCached = rWallpaper.ImplGetCachedBitmap();
+ Point aPos;
+ Size aSize;
+ GDIMetaFile* pOldMetaFile = mpMetaFile;
+ const WallpaperStyle eStyle = rWallpaper.GetStyle();
+ const bool bOldMap = mbMap;
+ bool bDrawn = false;
+ bool bDrawGradientBackground = false;
+ bool bDrawColorBackground = false;
+
+ if( pCached )
+ aBmpEx = *pCached;
+ else
+ aBmpEx = rWallpaper.GetBitmap();
+
+ const long nBmpWidth = aBmpEx.GetSizePixel().Width();
+ const long nBmpHeight = aBmpEx.GetSizePixel().Height();
+ const bool bTransparent = aBmpEx.IsTransparent();
+
+ // draw background
+ if( bTransparent )
+ {
+ if( rWallpaper.IsGradient() )
+ bDrawGradientBackground = true;
+ else
+ {
+ if( !pCached && !rWallpaper.GetColor().GetTransparency() )
+ {
+ 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;
+ }
+
+ // 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( PushFlags::CLIPREGION );
+ IntersectClipRegion( tools::Rectangle( Point( nX, nY ), Size( nWidth, nHeight ) ) );
+
+ 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.GetMask() );
+ }
+ 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 long nRight = nX + nWidth - 1;
+ const long nBottom = nY + nHeight - 1;
+ long nFirstX;
+ 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 long nOffX = ( nFirstX - nX ) % nBmpWidth;
+ const long nOffY = ( nFirstY - nY ) % nBmpHeight;
+ long nStartX = nX + nOffX;
+ long nStartY = nY + nOffY;
+
+ if( nOffX > 0 )
+ nStartX -= nBmpWidth;
+
+ if( nOffY > 0 )
+ nStartY -= nBmpHeight;
+
+ for( long nBmpY = nStartY; nBmpY <= nBottom; nBmpY += nBmpHeight )
+ {
+ for( long nBmpX = nStartX; nBmpX <= nRight; nBmpX += nBmpWidth )
+ {
+ DrawBitmapEx( Point( nBmpX, nBmpY ), aBmpEx );
+ }
+ }
+ bDrawn = true;
+ }
+ break;
+ }
+
+ if( !bDrawn )
+ {
+ // optimized for non-transparent bitmaps
+ if( bDrawColorBackground )
+ {
+ const Size aBmpSize( aBmpEx.GetSizePixel() );
+ const Point aTmpPoint;
+ const tools::Rectangle aOutRect( aTmpPoint, GetOutputSizePixel() );
+ const tools::Rectangle aColRect( Point( nX, nY ), Size( nWidth, nHeight ) );
+
+ tools::Rectangle aWorkRect( 0, 0, aOutRect.Right(), aPos.Y() - 1 );
+ aWorkRect.Justify();
+ aWorkRect.Intersection( aColRect );
+ if( !aWorkRect.IsEmpty() )
+ {
+ DrawColorWallpaper( aWorkRect.Left(), aWorkRect.Top(),
+ aWorkRect.GetWidth(), aWorkRect.GetHeight(),
+ rWallpaper );
+ }
+
+ aWorkRect = tools::Rectangle( 0, aPos.Y(), aPos.X() - 1, aPos.Y() + aBmpSize.Height() - 1 );
+ aWorkRect.Justify();
+ aWorkRect.Intersection( aColRect );
+ if( !aWorkRect.IsEmpty() )
+ {
+ DrawColorWallpaper( aWorkRect.Left(), aWorkRect.Top(),
+ aWorkRect.GetWidth(), aWorkRect.GetHeight(),
+ rWallpaper );
+ }
+
+ aWorkRect = tools::Rectangle( aPos.X() + aBmpSize.Width(), aPos.Y(),
+ aOutRect.Right(), aPos.Y() + aBmpSize.Height() - 1 );
+ aWorkRect.Justify();
+ aWorkRect.Intersection( aColRect );
+ if( !aWorkRect.IsEmpty() )
+ {
+ DrawColorWallpaper( aWorkRect.Left(), aWorkRect.Top(),
+ aWorkRect.GetWidth(), aWorkRect.GetHeight(),
+ rWallpaper );
+ }
+
+ aWorkRect = tools::Rectangle( 0, aPos.Y() + aBmpSize.Height(),
+ aOutRect.Right(), aOutRect.Bottom() );
+ aWorkRect.Justify();
+ aWorkRect.Intersection( aColRect );
+ if( !aWorkRect.IsEmpty() )
+ {
+ DrawColorWallpaper( aWorkRect.Left(), aWorkRect.Top(),
+ aWorkRect.GetWidth(), aWorkRect.GetHeight(),
+ rWallpaper );
+ }
+ }
+
+ DrawBitmapEx( aPos, aBmpEx );
+ }
+
+ rWallpaper.ImplSetCachedBitmap( aBmpEx );
+
+ Pop();
+ EnableMapMode( bOldMap );
+ mpMetaFile = pOldMetaFile;
+}
+
+void OutputDevice::DrawGradientWallpaper( long nX, long nY,
+ long nWidth, 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( 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/Matrix3.cxx b/vcl/source/pdf/Matrix3.cxx
new file mode 100644
index 000000000..22e5c4d73
--- /dev/null
+++ b/vcl/source/pdf/Matrix3.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 <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(static_cast<int>(x * f[0] + y * f[2] + f[4]),
+ static_cast<int>(x * f[1] + y * f[3] + f[5]));
+}
+
+void Matrix3::skew(double alpha, double beta)
+{
+ double fn[6];
+ double tb = tan(beta);
+ fn[0] = f[0] + f[2] * tb;
+ fn[1] = f[1];
+ fn[2] = f[2] + f[3] * tb;
+ fn[3] = f[3];
+ fn[4] = f[4] + f[5] * tb;
+ fn[5] = f[5];
+ if (alpha != 0.0)
+ {
+ double ta = tan(alpha);
+ fn[1] += f[0] * ta;
+ fn[3] += f[2] * ta;
+ fn[5] += f[4] * ta;
+ }
+ set(fn);
+}
+
+void Matrix3::scale(double sx, double sy)
+{
+ double fn[6];
+ fn[0] = sx * f[0];
+ fn[1] = sy * f[1];
+ fn[2] = sx * f[2];
+ fn[3] = sy * f[3];
+ fn[4] = sx * f[4];
+ fn[5] = sy * f[5];
+ set(fn);
+}
+
+void Matrix3::rotate(double angle)
+{
+ double fn[6];
+ double fSin = sin(angle);
+ double fCos = cos(angle);
+ fn[0] = f[0] * fCos - f[1] * fSin;
+ fn[1] = f[0] * fSin + f[1] * fCos;
+ fn[2] = f[2] * fCos - f[3] * fSin;
+ fn[3] = f[2] * fSin + f[3] * fCos;
+ fn[4] = f[4] * fCos - f[5] * fSin;
+ fn[5] = f[4] * fSin + f[5] * fCos;
+ set(fn);
+}
+
+void Matrix3::translate(double tx, double ty)
+{
+ f[4] += tx;
+ f[5] += ty;
+}
+
+void Matrix3::invert()
+{
+ // short circuit trivial cases
+ if (f[1] == f[2] && f[1] == 0.0 && f[0] == f[3] && f[0] == 1.0)
+ {
+ f[4] = -f[4];
+ f[5] = -f[5];
+ return;
+ }
+
+ // check determinant
+ const double fDet = f[0] * f[3] - f[1] * f[2];
+ if (fDet == 0.0)
+ return;
+
+ // invert the matrix
+ double fn[6];
+ fn[0] = +f[3] / fDet;
+ fn[1] = -f[1] / fDet;
+ fn[2] = -f[2] / fDet;
+ fn[3] = +f[0] / fDet;
+
+ // apply inversion to translation
+ fn[4] = -(f[4] * fn[0] + f[5] * fn[2]);
+ fn[5] = -(f[4] * fn[1] + f[5] * fn[3]);
+
+ set(fn);
+}
+
+} // end vcl::pdf
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/pdf/PDFiumLibrary.cxx b/vcl/source/pdf/PDFiumLibrary.cxx
new file mode 100644
index 000000000..f481078ab
--- /dev/null
+++ b/vcl/source/pdf/PDFiumLibrary.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 <config_features.h>
+
+#if HAVE_FEATURE_PDFIUM
+
+#include <vcl/filter/PDFiumLibrary.hxx>
+#include <fpdf_doc.h>
+
+#include <vcl/bitmap.hxx>
+
+#include <bitmapwriteaccess.hxx>
+
+namespace vcl::pdf
+{
+PDFium::PDFium()
+{
+ FPDF_LIBRARY_CONFIG aConfig;
+ aConfig.version = 2;
+ aConfig.m_pUserFontPaths = nullptr;
+ aConfig.m_pIsolate = nullptr;
+ aConfig.m_v8EmbedderSlot = 0;
+ FPDF_InitLibraryWithConfig(&aConfig);
+}
+
+PDFium::~PDFium() { FPDF_DestroyLibrary(); }
+
+PDFiumDocument::PDFiumDocument(FPDF_DOCUMENT pPdfDocument)
+ : mpPdfDocument(pPdfDocument)
+{
+}
+
+PDFiumDocument::~PDFiumDocument()
+{
+ if (mpPdfDocument)
+ FPDF_CloseDocument(mpPdfDocument);
+}
+
+std::unique_ptr<PDFiumPage> PDFiumDocument::openPage(int nIndex)
+{
+ std::unique_ptr<PDFiumPage> pPDFiumPage;
+ FPDF_PAGE pPage = FPDF_LoadPage(mpPdfDocument, nIndex);
+ if (pPage)
+ {
+ pPDFiumPage = std::make_unique<PDFiumPage>(pPage);
+ }
+ return pPDFiumPage;
+}
+
+int PDFiumDocument::getPageCount() { return FPDF_GetPageCount(mpPdfDocument); }
+
+BitmapChecksum PDFiumPage::getChecksum(int nMDPPerm)
+{
+ size_t nPageWidth = FPDF_GetPageWidth(mpPage);
+ size_t nPageHeight = FPDF_GetPageHeight(mpPage);
+ FPDF_BITMAP pPdfBitmap = FPDFBitmap_Create(nPageWidth, nPageHeight, /*alpha=*/1);
+ if (!pPdfBitmap)
+ {
+ return 0;
+ }
+
+ int nFlags = 0;
+ if (nMDPPerm != 3)
+ {
+ // Annotations/commenting should affect the checksum, signature verification wants this.
+ nFlags = FPDF_ANNOT;
+ }
+ FPDF_RenderPageBitmap(pPdfBitmap, mpPage, /*start_x=*/0, /*start_y=*/0, nPageWidth, nPageHeight,
+ /*rotate=*/0, nFlags);
+ Bitmap aBitmap(Size(nPageWidth, nPageHeight), 24);
+ {
+ BitmapScopedWriteAccess pWriteAccess(aBitmap);
+ const auto pPdfBuffer = static_cast<ConstScanline>(FPDFBitmap_GetBuffer(pPdfBitmap));
+ const int nStride = FPDFBitmap_GetStride(pPdfBitmap);
+ for (size_t nRow = 0; nRow < nPageHeight; ++nRow)
+ {
+ ConstScanline pPdfLine = pPdfBuffer + (nStride * nRow);
+ pWriteAccess->CopyScanline(nRow, pPdfLine, ScanlineFormat::N32BitTcBgra, nStride);
+ }
+ }
+ return aBitmap.GetChecksum();
+}
+
+} // end vcl::pdf
+
+#endif // HAVE_FEATURE_PDFIUM
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/pdf/ResourceDict.cxx b/vcl/source/pdf/ResourceDict.cxx
new file mode 100644
index 000000000..3490da30b
--- /dev/null
+++ b/vcl/source/pdf/ResourceDict.cxx
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <pdf/ResourceDict.hxx>
+
+namespace vcl::pdf
+{
+namespace
+{
+void appendResourceMap(OStringBuffer& rBuf, const char* pPrefix,
+ std::map<OString, sal_Int32> const& rList)
+{
+ if (rList.empty())
+ return;
+ rBuf.append('/');
+ rBuf.append(pPrefix);
+ rBuf.append("<<");
+ int ni = 0;
+ for (auto const& item : rList)
+ {
+ if (!item.first.isEmpty() && item.second > 0)
+ {
+ rBuf.append('/');
+ rBuf.append(item.first);
+ rBuf.append(' ');
+ rBuf.append(item.second);
+ rBuf.append(" 0 R");
+ if (((++ni) & 7) == 0)
+ rBuf.append('\n');
+ }
+ }
+ rBuf.append(">>\n");
+}
+}
+
+void ResourceDict::append(OStringBuffer& rBuf, sal_Int32 nFontDictObject)
+{
+ rBuf.append("<</Font ");
+ rBuf.append(nFontDictObject);
+ rBuf.append(" 0 R\n");
+ appendResourceMap(rBuf, "XObject", m_aXObjects);
+ appendResourceMap(rBuf, "ExtGState", m_aExtGStates);
+ appendResourceMap(rBuf, "Shading", m_aShadings);
+ appendResourceMap(rBuf, "Pattern", m_aPatterns);
+ rBuf.append("/ProcSet[/PDF/Text");
+ if (!m_aXObjects.empty())
+ rBuf.append("/ImageC/ImageI/ImageB");
+ rBuf.append("]\n>>\n");
+}
+
+} // end vcl::pdf
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/pdf/XmpMetadata.cxx b/vcl/source/pdf/XmpMetadata.cxx
new file mode 100644
index 000000000..70588dab3
--- /dev/null
+++ b/vcl/source/pdf/XmpMetadata.cxx
@@ -0,0 +1,190 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <pdf/XmpMetadata.hxx>
+#include <tools/XmlWriter.hxx>
+
+namespace vcl::pdf
+{
+namespace
+{
+constexpr const char* constPadding = " "
+ " "
+ " "
+ " "
+ " "
+ "\n";
+}
+
+XmpMetadata::XmpMetadata()
+ : mbWritten(false)
+ , mnPDF_A(0)
+ , mbPDF_UA(false)
+{
+}
+
+void XmpMetadata::write()
+{
+ mpMemoryStream = std::make_unique<SvMemoryStream>(4096 /*Initial*/, 64 /*Resize*/);
+
+ // Header
+ mpMemoryStream->WriteOString("<?xpacket begin=\"");
+ mpMemoryStream->WriteOString(OUStringToOString(OUString(u'\xFEFF'), RTL_TEXTENCODING_UTF8));
+ mpMemoryStream->WriteOString("\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>\n");
+
+ {
+ tools::XmlWriter aXmlWriter(mpMemoryStream.get());
+ aXmlWriter.startDocument(2, false);
+ aXmlWriter.startElement("x", "xmpmeta", "adobe:ns:meta/");
+ aXmlWriter.startElement("rdf", "RDF", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
+
+ // PDF/A part ( ISO 19005-1:2005 - 6.7.11 )
+ if (mnPDF_A > 0)
+ {
+ OString sPdfVersion = OString::number(mnPDF_A);
+ OString sPdfConformance = (mnPDF_A == 1) ? "A" : "B";
+
+ aXmlWriter.startElement("rdf:Description");
+ aXmlWriter.attribute("rdf:about", OString(""));
+ aXmlWriter.attribute("xmlns:pdfaid", OString("http://www.aiim.org/pdfa/ns/id/"));
+
+ aXmlWriter.startElement("pdfaid:part");
+ aXmlWriter.content(sPdfVersion);
+ aXmlWriter.endElement();
+
+ aXmlWriter.startElement("pdfaid:conformance");
+ aXmlWriter.content(sPdfConformance);
+ aXmlWriter.endElement();
+
+ aXmlWriter.endElement();
+ }
+
+ // Dublin Core properties
+ if (!msTitle.isEmpty() || !msAuthor.isEmpty() || !msSubject.isEmpty())
+ {
+ aXmlWriter.startElement("rdf:Description");
+ aXmlWriter.attribute("rdf:about", OString(""));
+ aXmlWriter.attribute("xmlns:dc", OString("http://purl.org/dc/elements/1.1/"));
+ if (!msTitle.isEmpty())
+ {
+ // this is according to PDF/A-1, technical corrigendum 1 (2007-04-01)
+ aXmlWriter.startElement("dc:title");
+ aXmlWriter.startElement("rdf:Alt");
+ aXmlWriter.startElement("rdf:li");
+ aXmlWriter.attribute("xml:lang", OString("x-default"));
+ aXmlWriter.content(msTitle);
+ aXmlWriter.endElement();
+ aXmlWriter.endElement();
+ aXmlWriter.endElement();
+ }
+ if (!msAuthor.isEmpty())
+ {
+ aXmlWriter.startElement("dc:creator");
+ aXmlWriter.startElement("rdf:Seq");
+ aXmlWriter.startElement("rdf:li");
+ aXmlWriter.content(msAuthor);
+ aXmlWriter.endElement();
+ aXmlWriter.endElement();
+ aXmlWriter.endElement();
+ }
+ if (!msSubject.isEmpty())
+ {
+ aXmlWriter.startElement("dc:description");
+ aXmlWriter.startElement("rdf:Alt");
+ aXmlWriter.startElement("rdf:li");
+ aXmlWriter.attribute("xml:lang", OString("x-default"));
+ aXmlWriter.content(msSubject);
+ aXmlWriter.endElement();
+ aXmlWriter.endElement();
+ aXmlWriter.endElement();
+ }
+ aXmlWriter.endElement();
+ }
+
+ // PDF/UA
+ if (mbPDF_UA)
+ {
+ OString sPdfUaVersion = OString::number(1);
+ aXmlWriter.startElement("rdf:Description");
+ aXmlWriter.attribute("rdf:about", OString(""));
+ aXmlWriter.attribute("xmlns:pdfuaid", OString("http://www.aiim.org/pdfua/ns/id/"));
+
+ aXmlWriter.startElement("pdfuaid:part");
+ aXmlWriter.content(sPdfUaVersion);
+ aXmlWriter.endElement();
+
+ aXmlWriter.endElement();
+ }
+
+ // PDF properties
+ if (!msProducer.isEmpty() || !msKeywords.isEmpty())
+ {
+ aXmlWriter.startElement("rdf:Description");
+ aXmlWriter.attribute("rdf:about", OString(""));
+ aXmlWriter.attribute("xmlns:pdf", OString("http://ns.adobe.com/pdf/1.3/"));
+ if (!msProducer.isEmpty())
+ {
+ aXmlWriter.startElement("pdf:Producer");
+ aXmlWriter.content(msProducer);
+ aXmlWriter.endElement();
+ }
+ if (!msKeywords.isEmpty())
+ {
+ aXmlWriter.startElement("pdf:Keywords");
+ aXmlWriter.content(msKeywords);
+ aXmlWriter.endElement();
+ }
+ aXmlWriter.endElement();
+ }
+
+ aXmlWriter.startElement("rdf:Description");
+ aXmlWriter.attribute("rdf:about", OString(""));
+ aXmlWriter.attribute("xmlns:xmp", OString("http://ns.adobe.com/xap/1.0/"));
+ if (!m_sCreatorTool.isEmpty())
+ {
+ aXmlWriter.startElement("xmp:CreatorTool");
+ aXmlWriter.content(m_sCreatorTool);
+ aXmlWriter.endElement();
+ }
+ aXmlWriter.startElement("xmp:CreateDate");
+ aXmlWriter.content(m_sCreateDate);
+ aXmlWriter.endElement();
+ aXmlWriter.endElement();
+
+ aXmlWriter.endElement();
+ aXmlWriter.endElement();
+ aXmlWriter.endDocument();
+ }
+
+ // add padding (needed so the metadata can be changed in-place"
+ for (sal_Int32 nSpaces = 1; nSpaces <= 21; nSpaces++)
+ mpMemoryStream->WriteOString(constPadding);
+
+ mpMemoryStream->WriteOString("<?xpacket end=\"w\"?>\n");
+ mbWritten = true;
+}
+
+sal_uInt64 XmpMetadata::getSize()
+{
+ if (!mbWritten)
+ write();
+ return mpMemoryStream->GetSize();
+}
+
+const void* XmpMetadata::getData()
+{
+ if (!mbWritten)
+ write();
+ return mpMemoryStream->GetData();
+}
+
+} // end vcl::pdf
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/salmain/salmain.cxx b/vcl/source/salmain/salmain.cxx
new file mode 100644
index 000000000..d03f6b2cd
--- /dev/null
+++ b/vcl/source/salmain/salmain.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/glxtestprocess.hxx>
+#include <vcl/vclmain.hxx>
+#include <vcl/svmain.hxx>
+
+SAL_IMPLEMENT_MAIN() {
+ fire_glxtest_process();
+ tools::extendApplicationEnvironment();
+ vclmain::createApplication();
+ return SVMain();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/toolkit/README b/vcl/source/toolkit/README
new file mode 100644
index 000000000..78863390f
--- /dev/null
+++ b/vcl/source/toolkit/README
@@ -0,0 +1,2 @@
+These are controls which are now only used by the toolkit module, which exposes
+them via uno. Don't use these in any new code.
diff --git a/vcl/source/toolkit/group.cxx b/vcl/source/toolkit/group.cxx
new file mode 100644
index 000000000..ef7b28794
--- /dev/null
+++ b/vcl/source/toolkit/group.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/event.hxx>
+#include <vcl/toolkit/group.hxx>
+#include <vcl/settings.hxx>
+
+#include <controldata.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 )
+ {
+ 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, DrawFlags nDrawFlags,
+ const Point& rPos, const Size& rSize, bool bLayout )
+{
+ long nTop;
+ 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 ( (nDrawFlags & DrawFlags::Mono) ||
+ (rStyleSettings.GetOptions() & StyleSettingsOptions::Mono) )
+ {
+ nTextStyle |= DrawTextFlags::Mono;
+ nDrawFlags |= DrawFlags::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 ( nDrawFlags & DrawFlags::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 && !(nDrawFlags & DrawFlags::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 ) );
+ }
+ }
+
+ MetricVector* pVector = bLayout ? &mpControlData->mpLayoutData->m_aUnicodeBoundRects : nullptr;
+ OUString* pDisplayText = bLayout ? &mpControlData->mpLayoutData->m_aDisplayText : nullptr;
+ DrawControlText( *pDev, aRect, aText, nTextStyle, pVector, pDisplayText );
+}
+
+void GroupBox::FillLayoutData() const
+{
+ mpControlData->mpLayoutData.reset( new vcl::ControlLayoutData );
+ const_cast<GroupBox*>(this)->ImplDraw( const_cast<GroupBox*>(this), DrawFlags::NONE, Point(), GetOutputSizePixel(), true );
+}
+
+void GroupBox::Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& )
+{
+ ImplDraw(&rRenderContext, DrawFlags::NONE, Point(), GetOutputSizePixel());
+}
+
+void GroupBox::Draw( OutputDevice* pDev, const Point& rPos,
+ DrawFlags nFlags )
+{
+ Point aPos = pDev->LogicToPixel( rPos );
+ Size aSize = GetSizePixel();
+ vcl::Font aFont = GetDrawPixelFont( pDev );
+
+ pDev->Push();
+ pDev->SetMapMode();
+ pDev->SetFont( aFont );
+ if ( nFlags & DrawFlags::Mono )
+ pDev->SetTextColor( COL_BLACK );
+ else
+ pDev->SetTextColor( GetTextColor() );
+ pDev->SetTextFillColor();
+
+ ImplDraw( pDev, nFlags, aPos, aSize );
+ pDev->Pop();
+}
+
+void GroupBox::Resize()
+{
+ Control::Resize();
+ Invalidate();
+}
+
+void GroupBox::StateChanged( StateChangedType nType )
+{
+ Control::StateChanged( nType );
+
+ if ( (nType == StateChangedType::Enable) ||
+ (nType == StateChangedType::Text) ||
+ (nType == StateChangedType::UpdateMode) )
+ {
+ if ( IsUpdateMode() )
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::Style )
+ {
+ SetStyle( ImplInitStyle( GetStyle() ) );
+ if ( (GetPrevStyle() & GROUP_VIEW_STYLE) !=
+ (GetStyle() & GROUP_VIEW_STYLE) )
+ Invalidate();
+ }
+ else if ( (nType == StateChangedType::Zoom) ||
+ (nType == StateChangedType::ControlFont) )
+ {
+ ImplInitSettings( false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ ImplInitSettings( false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ImplInitSettings( true );
+ Invalidate();
+ }
+}
+
+void GroupBox::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Control::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
+ {
+ ImplInitSettings( true );
+ Invalidate();
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/toolkit/morebtn.cxx b/vcl/source/toolkit/morebtn.cxx
new file mode 100644
index 000000000..1a3ca8eaf
--- /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() );
+ long nDeltaPixel = LogicToPixel(Size(0, 0), MapMode(MapUnit::MapPixel)).Height();
+
+ // Change status
+ mbState = !mbState;
+ ShowState();
+
+ // Update the windows according to the status
+ if ( mbState )
+ {
+ // Adapt dialogbox
+ Point aPos( pParent->GetPosPixel() );
+ tools::Rectangle aDeskRect( pParent->ImplGetFrameWindow()->GetDesktopRectPixel() );
+
+ aSize.AdjustHeight(nDeltaPixel );
+ if ( (aPos.Y()+aSize.Height()) > aDeskRect.Bottom() )
+ {
+ aPos.setY( aDeskRect.Bottom()-aSize.Height() );
+
+ if ( aPos.Y() < aDeskRect.Top() )
+ aPos.setY( aDeskRect.Top() );
+
+ pParent->SetPosSizePixel( aPos, aSize );
+ }
+ else
+ pParent->SetSizePixel( aSize );
+ }
+ else
+ {
+ // Adapt Dialogbox
+ aSize.AdjustHeight( -nDeltaPixel );
+ pParent->SetSizePixel( aSize );
+ }
+ // Call Click handler here, so that we can initialize the Controls
+ PushButton::Click();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/treelist/headbar.cxx b/vcl/source/treelist/headbar.cxx
new file mode 100644
index 000000000..7020c616b
--- /dev/null
+++ b/vcl/source/treelist/headbar.cxx
@@ -0,0 +1,1302 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <vcl/headbar.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <tools/debug.hxx>
+
+#include <vcl/svapp.hxx>
+#include <vcl/help.hxx>
+#include <vcl/image.hxx>
+#include <vcl/salnativewidgets.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/event.hxx>
+#include <vcl/ptrstyle.hxx>
+
+#include <com/sun/star/accessibility/XAccessible.hpp>
+
+class ImplHeadItem
+{
+public:
+ sal_uInt16 mnId;
+ HeaderBarItemBits mnBits;
+ 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(*this, rStyleSettings.GetToolFont());
+
+ if (bForeground || bFont)
+ {
+ ApplyControlForeground(*this, rStyleSettings.GetButtonTextColor());
+ SetTextFillColor();
+ }
+
+ if (bBackground)
+ ApplyControlBackground(*this, rStyleSettings.GetFaceColor());
+}
+
+long HeaderBar::ImplGetItemPos( sal_uInt16 nPos ) const
+{
+ 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::ImplHitTest( const Point& rPos,
+ long& nMouseOff, sal_uInt16& nPos ) const
+{
+ size_t nCount = static_cast<sal_uInt16>(mvItemList.size());
+ bool bLastFixed = true;
+ 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 );
+ }
+
+ SetRasterOp( RasterOp::Invert );
+ DrawRect( aStartRect );
+ DrawLine( aStartPos, aEndPos );
+ if ( nEndPos > nStartPos )
+ {
+ DrawLine( Point( aEndPos.X()+1, aEndPos.Y()-3 ),
+ Point( aEndPos.X()+1, aEndPos.Y()+3 ) );
+ DrawLine( Point( aEndPos.X()+2, aEndPos.Y()-2 ),
+ Point( aEndPos.X()+2, aEndPos.Y()+2 ) );
+ DrawLine( Point( aEndPos.X()+3, aEndPos.Y()-1 ),
+ Point( aEndPos.X()+3, aEndPos.Y()+1 ) );
+ DrawPixel( Point( aEndPos.X()+4, aEndPos.Y() ) );
+ }
+ else
+ {
+ DrawLine( Point( aEndPos.X()-1, aEndPos.Y()-3 ),
+ Point( aEndPos.X()-1, aEndPos.Y()+3 ) );
+ DrawLine( Point( aEndPos.X()-2, aEndPos.Y()-2 ),
+ Point( aEndPos.X()-2, aEndPos.Y()+2 ) );
+ DrawLine( Point( aEndPos.X()-3, aEndPos.Y()-1 ),
+ Point( aEndPos.X()-3, aEndPos.Y()+1 ) );
+ DrawPixel( Point( aEndPos.X()-4, aEndPos.Y() ) );
+ }
+ 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() );
+ 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
+ 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;
+ long nMaxTxtWidth = aRect.GetWidth() - (HEADERBAR_TEXTOFF * 2) - nArrowWidth;
+ if (nBits & (HeaderBarItemBits::LEFTIMAGE | HeaderBarItemBits::RIGHTIMAGE))
+ nMaxTxtWidth -= aImageSize.Width();
+ long nTxtWidth = aTxtSize.Width();
+ if (nTxtWidth > nMaxTxtWidth)
+ {
+ bLeftText = true;
+ OUStringBuffer aBuf(pItem->maOutText);
+ aBuf.append("...");
+ do
+ {
+ aBuf.remove(aBuf.getLength() - 3 - 1, 1);
+ nTxtWidth = rRenderContext.GetTextWidth(aBuf.toString());
+ }
+ while ((nTxtWidth > nMaxTxtWidth) && (aBuf.getLength() > 3));
+ pItem->maOutText = aBuf.makeStringAndClear();
+ if (pItem->maOutText.getLength() == 3)
+ {
+ nTxtWidth = 0;
+ pItem->maOutText.clear();
+ }
+ }
+
+ // calculate text/imageposition
+ 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))
+ {
+ 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
+ long nTxtPosY = 0;
+ if (!pItem->maOutText.isEmpty() || (nArrowWidth && aTxtSize.Height()))
+ {
+ 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(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
+ long nImagePosY = 0;
+ if (aImageSize.Width() && aImageSize.Height())
+ {
+ 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;
+ }
+
+ 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;
+
+ 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
+ {
+ 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 = ImplHitTest( 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.IsInside( 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
+ {
+ 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 )
+ {
+ long nTemp;
+ sal_uInt16 nPos;
+ sal_uInt16 nHitTest = ImplHitTest( 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 )
+{
+ long nTemp1;
+ sal_uInt16 nTemp2;
+ PointerStyle eStyle = PointerStyle::Arrow;
+ sal_uInt16 nHitTest = ImplHitTest( 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,
+ DrawFlags 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 & DrawFlags::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,
+ 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( 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 );
+ 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 ).IsInside( 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, 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 );
+ }
+ }
+}
+
+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
+{
+ long nMaxImageSize = 0;
+ Size aSize( 0, GetTextHeight() );
+
+ for (auto& pItem : mvItemList)
+ {
+ // take image size into account
+ long nImageHeight = pItem->maImage.GetSizePixel().Height();
+ if ( !(pItem->mnBits & (HeaderBarItemBits::LEFTIMAGE | HeaderBarItemBits::RIGHTIMAGE)) && !pItem->maText.isEmpty() )
+ nImageHeight += aSize.Height();
+ if ( nImageHeight > nMaxImageSize )
+ nMaxImageSize = nImageHeight;
+
+ // add width
+ aSize.AdjustWidth(pItem->mnSize );
+ }
+
+ if ( nMaxImageSize > aSize.Height() )
+ aSize.setHeight( nMaxImageSize );
+
+ // add border
+ if ( mbButtonStyle )
+ aSize.AdjustHeight(4 );
+ else
+ aSize.AdjustHeight(2 );
+ aSize.AdjustHeight(mnBorderOff1+mnBorderOff2 );
+
+ return aSize;
+}
+
+css::uno::Reference< css::accessibility::XAccessible > HeaderBar::CreateAccessible()
+{
+ if ( !mxAccessible.is() )
+ {
+ maCreateAccessibleHdl.Call( this );
+
+ if ( !mxAccessible.is() )
+ mxAccessible = Window::CreateAccessible();
+ }
+
+ return mxAccessible;
+}
+
+void HeaderBar::SetAccessible( const css::uno::Reference< css::accessibility::XAccessible >& _xAccessible )
+{
+ mxAccessible = _xAccessible;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/treelist/iconview.cxx b/vcl/source/treelist/iconview.cxx
new file mode 100644
index 000000000..162096bff
--- /dev/null
+++ b/vcl/source/treelist/iconview.cxx
@@ -0,0 +1,221 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/treelistentry.hxx>
+#include <vcl/viewdataentry.hxx>
+#include <iconview.hxx>
+#include "iconviewimpl.hxx"
+
+IconView::IconView( vcl::Window* pParent, WinBits nBits )
+ : SvTreeListBox( pParent, nBits )
+{
+ nColumns = 1;
+ mbCenterAndClipText = true;
+ SetEntryHeight( 100 );
+ SetEntryWidth( 100 );
+
+ pImpl.reset( new IconViewImpl( this, GetModel(), GetStyle() ) );
+}
+
+void IconView::Resize()
+{
+ Size aBoxSize = Control::GetParent()->GetOutputSizePixel();
+
+ if ( !aBoxSize.Width() )
+ return;
+
+ SetSizePixel( aBoxSize );
+
+ nColumns = aBoxSize.Width() / nEntryWidth;
+
+ SvTreeListBox::Resize();
+}
+
+tools::Rectangle IconView::GetFocusRect(const SvTreeListEntry*, long nEntryPos)
+{
+ Size aSize;
+ aSize.setHeight( nEntryHeight );
+ aSize.setWidth( nEntryWidth );
+
+ Point aPos;
+ aPos.setX( 0 );
+ aPos.setY( 0 );
+
+ tools::Rectangle aRect;
+
+ short nCols = GetColumnsCount();
+
+ if(nCols)
+ {
+ aPos.setY( ( nEntryPos / nCols ) * nEntryHeight );
+ aPos.setX( ( nEntryPos % nCols ) * nEntryWidth );
+ }
+
+ aRect.SetPos( aPos );
+ aRect.SetSize( aSize );
+
+ return aRect;
+}
+
+void IconView::PaintEntry(SvTreeListEntry& rEntry, long nX, long nY,
+ vcl::RenderContext& rRenderContext)
+{
+
+ tools::Rectangle aRect; // multi purpose
+
+ PreparePaint(rRenderContext, rEntry);
+
+ pImpl->UpdateContextBmpWidthMax(&rEntry);
+
+ short nTempEntryHeight = GetEntryHeight();
+ short nTempEntryWidth = GetEntryWidth();
+
+ Point aEntryPos;
+
+ Color aBackupTextColor(rRenderContext.GetTextColor());
+ vcl::Font aBackupFont(rRenderContext.GetFont());
+ Color aBackupColor = rRenderContext.GetFillColor();
+
+ bool bCurFontIsSel = false;
+ 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(nTempEntryWidth, nTempEntryHeight);
+
+ SvViewDataEntry* pViewDataEntry = GetViewDataEntry( &rEntry );
+
+ sal_uInt16 nItemCount = rEntry.ItemCount();
+ sal_uInt16 nCurItem = 0;
+ sal_uInt16 nIconItem = nItemCount;
+
+ while (nCurItem < nItemCount)
+ {
+ SvLBoxItem* pItem = nCurItem < nItemCount ? &rEntry.GetItem(nCurItem) : nullptr;
+ SvLBoxItemType nItemType = pItem->GetType();
+
+ if (nItemType == SvLBoxItemType::ContextBmp)
+ {
+ nIconItem = nCurItem;
+ nCurItem++;
+ continue;
+ }
+
+ auto nItemHeight = SvLBoxItem::GetHeight(pViewDataEntry, nCurItem);
+
+ aEntryPos.setX( nX );
+ aEntryPos.setY( nY );
+
+ // 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
+ 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)
+ {
+ bCurFontIsSel = false;
+ rRenderContext.SetTextColor(aBackupTextColor);
+ rRenderContext.SetFont(aBackupFont);
+ }
+ else
+ {
+ aWallpaper.SetColor(rEntry.GetBackColor());
+ }
+ }
+
+ // draw background
+ if (!(nTreeFlags & SvTreeFlags::USESEL))
+ {
+ aRect.SetPos(aEntryPos);
+ aRect.SetSize(aRectSize);
+
+ 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);
+ }
+ }
+
+ // center vertically
+ aEntryPos.AdjustY((nTempEntryHeight - nItemHeight) / 2 );
+
+ aEntryPos.AdjustY(15 );
+
+ pItem->Paint(aEntryPos, *this, rRenderContext, pViewDataEntry, rEntry);
+
+ rRenderContext.SetFillColor(aBackupColor);
+
+ nCurItem++;
+ }
+
+ // draw icon
+ if(nIconItem != nItemCount && nIconItem < nItemCount)
+ {
+ SvLBoxItem* pItem = &rEntry.GetItem(nIconItem);
+ auto nItemWidth = pItem->GetWidth(this, pViewDataEntry, nIconItem);
+ auto nItemHeight = SvLBoxItem::GetHeight(pViewDataEntry, nIconItem);
+
+ aEntryPos.setX( nX );
+ aEntryPos.setY( nY );
+
+ // center horizontally
+ aEntryPos.AdjustX((nTempEntryWidth - nItemWidth) / 2 );
+ // center vertically
+ aEntryPos.AdjustY((nTempEntryHeight - nItemHeight) / 2 );
+
+ aEntryPos.AdjustY( -10 );
+
+ pItem->Paint(aEntryPos, *this, rRenderContext, pViewDataEntry, rEntry);
+ }
+
+ if (bCurFontIsSel)
+ {
+ rRenderContext.SetTextColor(aBackupTextColor);
+ rRenderContext.SetFont(aBackupFont);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/treelist/iconviewimpl.cxx b/vcl/source/treelist/iconviewimpl.cxx
new file mode 100644
index 000000000..c2640dfc0
--- /dev/null
+++ b/vcl/source/treelist/iconviewimpl.cxx
@@ -0,0 +1,656 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <iconview.hxx>
+#include "iconviewimpl.hxx"
+
+IconViewImpl::IconViewImpl( SvTreeListBox* pTreeListBox, SvTreeList* pTreeList, WinBits nWinStyle )
+: SvImpLBox( pTreeListBox, pTreeList, nWinStyle )
+{
+}
+
+void IconViewImpl::CursorUp()
+{
+ if (!m_pStartEntry)
+ return;
+
+ SvTreeListEntry* pPrevFirstToDraw = m_pStartEntry;
+
+ for(short i = 0; i < m_pView->GetColumnsCount() && pPrevFirstToDraw; i++)
+ pPrevFirstToDraw = m_pView->PrevVisible(pPrevFirstToDraw);
+
+ if( !pPrevFirstToDraw )
+ return;
+
+ m_nFlags &= ~LBoxFlags::Filling;
+ long nEntryHeight = m_pView->GetEntryHeight();
+ ShowCursor( false );
+ m_pView->PaintImmediately();
+ m_pStartEntry = pPrevFirstToDraw;
+ tools::Rectangle aArea( GetVisibleArea() );
+ aArea.AdjustBottom( -nEntryHeight );
+ m_pView->Scroll( 0, nEntryHeight, aArea, ScrollFlags::NoChildren );
+ m_pView->PaintImmediately();
+ ShowCursor( true );
+ m_pView->NotifyScrolled();
+}
+
+void IconViewImpl::CursorDown()
+{
+ if (!m_pStartEntry)
+ return;
+
+ SvTreeListEntry* pNextFirstToDraw = m_pStartEntry;
+
+ for(short i = 0; i < m_pView->GetColumnsCount(); i++)
+ pNextFirstToDraw = m_pView->NextVisible(pNextFirstToDraw);
+
+ 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 IconViewImpl::PageDown( sal_uInt16 nDelta )
+{
+ sal_uInt16 nRealDelta = nDelta * m_pView->GetColumnsCount();
+
+ 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() );
+ }
+ else
+ {
+ tools::Rectangle aArea( GetVisibleArea() );
+ long nScroll = m_pView->GetEntryHeight() * static_cast<long>(nRealDelta);
+ nScroll = -nScroll;
+ m_pView->PaintImmediately();
+ m_pView->Scroll( 0, nScroll, aArea, ScrollFlags::NoChildren );
+ m_pView->PaintImmediately();
+ m_pView->NotifyScrolled();
+ }
+
+ ShowCursor( true );
+}
+
+void IconViewImpl::PageUp( sal_uInt16 nDelta )
+{
+ sal_uInt16 nRealDelta = nDelta * m_pView->GetColumnsCount();
+ 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() );
+ }
+ else
+ {
+ long nEntryHeight = m_pView->GetEntryHeight();
+ tools::Rectangle aArea( GetVisibleArea() );
+ m_pView->PaintImmediately();
+ m_pView->Scroll( 0, nEntryHeight*nRealDelta, aArea, ScrollFlags::NoChildren );
+ m_pView->PaintImmediately();
+ m_pView->NotifyScrolled();
+ }
+
+ ShowCursor( true );
+}
+
+void IconViewImpl::KeyDown( bool bPageDown )
+{
+ if( !m_aVerSBar->IsVisible() )
+ return;
+
+ long nDelta;
+ if( bPageDown )
+ nDelta = m_aVerSBar->GetPageSize();
+ else
+ nDelta = 1;
+
+ long nThumbPos = m_aVerSBar->GetThumbPos();
+
+ if( nDelta <= 0 )
+ return;
+
+ m_nFlags &= ~LBoxFlags::Filling;
+ BeginScroll();
+
+ m_aVerSBar->SetThumbPos( nThumbPos+nDelta );
+ if( bPageDown )
+ PageDown( static_cast<short>(nDelta) );
+ else
+ CursorDown();
+
+ EndScroll();
+}
+
+void IconViewImpl::KeyUp( bool bPageUp )
+{
+ if( !m_aVerSBar->IsVisible() )
+ return;
+
+ long nDelta;
+ if( bPageUp )
+ nDelta = m_aVerSBar->GetPageSize();
+ else
+ nDelta = 1;
+
+ long nThumbPos = m_aVerSBar->GetThumbPos();
+
+ if( nThumbPos < nDelta )
+ nDelta = nThumbPos;
+
+ if( nDelta < 0 )
+ return;
+
+ m_nFlags &= ~LBoxFlags::Filling;
+ BeginScroll();
+
+ m_aVerSBar->SetThumbPos( nThumbPos - nDelta );
+ if( bPageUp )
+ PageUp( static_cast<short>(nDelta) );
+ else
+ CursorUp();
+
+ EndScroll();
+}
+
+long IconViewImpl::GetEntryLine(const SvTreeListEntry* pEntry) const
+{
+ if(!m_pStartEntry )
+ return -1; // invisible position
+
+ long nFirstVisPos = m_pView->GetVisiblePos( m_pStartEntry );
+ long nEntryVisPos = m_pView->GetVisiblePos( pEntry );
+ nFirstVisPos = nEntryVisPos - nFirstVisPos;
+
+ return nFirstVisPos;
+}
+
+Point IconViewImpl::GetEntryPosition(const SvTreeListEntry* pEntry) const
+{
+ const int pos = m_pView->GetAbsPos( pEntry );
+
+ return Point( ( pos % m_pView->GetColumnsCount() ) * m_pView->GetEntryWidth(),
+ ( pos / m_pView->GetColumnsCount() ) * m_pView->GetEntryHeight() );
+}
+
+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;
+
+ sal_uInt16 nY = static_cast<sal_uInt16>(rPoint.Y() / m_pView->GetEntryHeight() );
+ sal_uInt16 nX = static_cast<sal_uInt16>(rPoint.X() / m_pView->GetEntryWidth() );
+ sal_uInt16 nTemp = nY * m_pView->GetColumnsCount() + nX;
+
+ SvTreeListEntry* pEntry = m_pView->NextVisible(m_pStartEntry, nTemp);
+
+ return pEntry;
+}
+
+bool IconViewImpl::IsEntryInView( SvTreeListEntry* pEntry ) const
+{
+ // parent collapsed
+ if( !m_pView->IsEntryVisible(pEntry) )
+ return false;
+
+ long nY = GetEntryLine( pEntry ) / m_pView->GetColumnsCount() * m_pView->GetEntryHeight();
+ if( nY < 0 )
+ return false;
+
+ long nMax = m_nVisibleCount / m_pView->GetColumnsCount() * m_pView->GetEntryHeight();
+ if( nY >= nMax )
+ return false;
+
+ long nStart = GetEntryLine( pEntry ) - GetEntryLine( m_pStartEntry );
+ return nStart >= 0;
+}
+
+void IconViewImpl::AdjustScrollBars( Size& rSize )
+{
+ 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 that are not collapsed
+ sal_uLong nTotalCount = m_pView->GetVisibleCount();
+
+ // number of entries visible within the view
+ m_nVisibleCount = aOSize.Height() / nEntryHeight * m_pView->GetColumnsCount();
+
+ long nRows = ( nTotalCount / m_pView->GetColumnsCount() ) + 1;
+
+ // do we need a vertical scrollbar?
+ if( bVerSBar || nTotalCount > m_nVisibleCount )
+ {
+ nResult = 1;
+ }
+
+ 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->SetPageSize( nTotalCount );
+ m_aVerSBar->SetVisibleSize( nTotalCount - nRows );
+ }
+ else
+ {
+ m_nFlags |= LBoxFlags::EndScrollSetVisSize;
+ }
+
+ if( nResult & 0x0001 )
+ m_aVerSBar->Show();
+ else
+ m_aVerSBar->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;
+
+ sal_uInt16 nClickedEntry = static_cast<sal_uInt16>(rPoint.Y() / m_pView->GetEntryHeight() * m_pView->GetColumnsCount() + rPoint.X() / m_pView->GetEntryWidth() );
+ sal_uInt16 nTemp = nClickedEntry;
+ SvTreeListEntry* pEntry = m_pView->NextVisible(m_pStartEntry, nTemp);
+ if( nTemp != nClickedEntry )
+ pEntry = nullptr;
+ return pEntry;
+}
+
+void IconViewImpl::SyncVerThumb()
+{
+ if( m_pStartEntry )
+ {
+ long nEntryPos = m_pView->GetVisiblePos( m_pStartEntry );
+ m_aVerSBar->SetThumbPos( nEntryPos );
+ }
+ else
+ m_aVerSBar->SetThumbPos( 0 );
+}
+
+void IconViewImpl::UpdateAll( bool bInvalidateCompleteView )
+{
+ FindMostRight();
+ m_aVerSBar->SetRange( Range( 0, m_pView->GetVisibleCount() ) );
+ SyncVerThumb();
+ FillView();
+ ShowVerSBar();
+ if( m_bSimpleTravel && m_pCursor && m_pView->HasFocus() )
+ m_pView->Select( m_pCursor );
+ ShowCursor( true );
+ if( bInvalidateCompleteView )
+ m_pView->Invalidate();
+ else
+ m_pView->Invalidate( GetVisibleArea() );
+}
+
+void IconViewImpl::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ if (!m_pView->GetVisibleCount())
+ return;
+
+ m_nFlags |= LBoxFlags::InPaint;
+
+ if (m_nFlags & LBoxFlags::Filling)
+ {
+ SvTreeListEntry* pFirst = m_pView->First();
+ if (pFirst != m_pStartEntry)
+ {
+ ShowCursor(false);
+ m_pStartEntry = m_pView->First();
+ m_aVerSBar->SetThumbPos( 0 );
+ StopUserEvent();
+ ShowCursor(true);
+ m_nCurUserEvent = Application::PostUserEvent(LINK(this, SvImpLBox, MyUserEvent),
+ reinterpret_cast<void*>(1));
+ return;
+ }
+ }
+
+ if (!m_pStartEntry)
+ {
+ m_pStartEntry = m_pView->First();
+ }
+
+ long nRectHeight = rRect.GetHeight();
+ long nRectWidth = rRect.GetWidth();
+ long nEntryHeight = m_pView->GetEntryHeight();
+ long nEntryWidth = m_pView->GetEntryWidth();
+
+ // calculate area for the entries we want to draw
+ sal_uInt16 nStartId = static_cast<sal_uInt16>(rRect.Top() / nEntryHeight * m_pView->GetColumnsCount() + (rRect.Left() / nEntryWidth));
+ sal_uInt16 nCount = static_cast<sal_uInt16>(( nRectHeight / nEntryHeight + 1 ) * nRectWidth / nEntryWidth);
+ nCount += 2; // don't miss an entry
+
+ long nY = nStartId / m_pView->GetColumnsCount() * nEntryHeight;
+ long nX = 0;
+ SvTreeListEntry* pEntry = m_pStartEntry;
+ while (nStartId && pEntry)
+ {
+ pEntry = m_pView->NextVisible(pEntry);
+ nStartId--;
+ }
+
+ 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++)
+ {
+ static_cast<IconView*>(m_pView.get())->PaintEntry(*pEntry, nX, nY, rRenderContext);
+ nX += nEntryWidth;
+
+ if(nX + m_pView->GetEntryWidth() > nEntryWidth * m_pView->GetColumnsCount())
+ {
+ nY += nEntryHeight;
+ nX = 0;
+ }
+ pEntry = m_pView->NextVisible(pEntry);
+ }
+
+ m_nFlags &= ~LBoxFlags::DeselectAll;
+ rRenderContext.SetClipRegion();
+ m_nFlags &= ~LBoxFlags::InPaint;
+}
+
+void IconViewImpl::InvalidateEntry( long nId ) const
+{
+ if( m_nFlags & LBoxFlags::InPaint )
+ return;
+
+ tools::Rectangle aRect( GetVisibleArea() );
+ long nMaxBottom = aRect.Bottom();
+ aRect.SetTop( nId / m_pView->GetColumnsCount() * m_pView->GetEntryHeight() );
+ aRect.SetBottom( aRect.Top() ); aRect.AdjustBottom(m_pView->GetEntryHeight() );
+
+ if( aRect.Top() > nMaxBottom )
+ return;
+ if( aRect.Bottom() > nMaxBottom )
+ aRect.SetBottom( nMaxBottom );
+ 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;
+
+ long i;
+ long nColumns = m_pView->GetColumnsCount();
+
+ 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:
+ {
+ if( !IsEntryInView( m_pCursor ) )
+ MakeVisible( m_pCursor );
+
+ pNewCursor = m_pCursor;
+ for( i = 0; i < nColumns && pNewCursor; i++)
+ {
+ 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 && m_pCursor )
+ pNewCursor = m_pCursor;
+
+ if( pNewCursor )
+ {
+ 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;
+ for( i = 0; i < nColumns && pNewCursor; i++)
+ {
+ 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_RETURN:
+ {
+ m_pView->aDoubleClickHdl.Call( m_pView );
+ bHandled = true;
+
+ break;
+ }
+
+ case KEY_END:
+ {
+ pNewCursor = m_pView->GetModel()->Last();
+
+ while( pNewCursor && !IsSelectable(pNewCursor) )
+ {
+ pNewCursor = m_pView->PrevVisible(pNewCursor);
+ }
+
+ m_pStartEntry = pNewCursor;
+
+ while( m_pStartEntry && m_pView->GetAbsPos( m_pStartEntry ) % m_pView->GetColumnsCount() != 0 )
+ {
+ m_pStartEntry = m_pView->PrevVisible(m_pStartEntry);
+ }
+
+ if( pNewCursor && pNewCursor != m_pCursor)
+ {
+// SelAllDestrAnch( false );
+ m_aSelEng.CursorPosChanging( bShift, bMod1 );
+ SetCursor( pNewCursor );
+ SyncVerThumb();
+ }
+
+ bHandled = true;
+
+ break;
+ }
+
+ default:
+ {
+ bHandled = false;
+ break;
+ }
+ }
+
+ if(!bHandled)
+ return SvImpLBox::KeyInput( rKEvt );
+
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/treelist/iconviewimpl.hxx b/vcl/source/treelist/iconviewimpl.hxx
new file mode 100644
index 000000000..efc587950
--- /dev/null
+++ b/vcl/source/treelist/iconviewimpl.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_SOURCE_INC_ICONVIEWIMPL_HXX
+#define INCLUDED_VCL_SOURCE_INC_ICONVIEWIMPL_HXX
+
+#include <svimpbox.hxx>
+
+class SvTreeListBox;
+class Point;
+
+class IconViewImpl : public SvImpLBox
+{
+public:
+ IconViewImpl( SvTreeListBox* pTreeListBox, SvTreeList* pTreeList, WinBits nWinStyle );
+
+ void KeyDown( bool bPageDown ) override;
+
+ void KeyUp( bool bPageUp ) override;
+
+ Point GetEntryPosition(const SvTreeListEntry* pEntry) const override;
+
+ SvTreeListEntry* GetClickedEntry( const Point& rPoint ) const override;
+
+ bool IsEntryInView( SvTreeListEntry* pEntry ) const override;
+
+ void Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect ) override;
+
+ // returns 0 if position is just past the last entry
+ SvTreeListEntry* GetEntry( const Point& rPoint ) const override;
+
+ void UpdateAll( bool bInvalidateCompleteView ) override;
+
+ bool KeyInput( const KeyEvent& ) override;
+
+ void InvalidateEntry( long nId ) const override;
+
+protected:
+ 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;
+};
+
+#endif // INCLUDED_VCL_SOURCE_INC_ICONVIEWIMPL_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/treelist/imap.cxx b/vcl/source/treelist/imap.cxx
new file mode 100644
index 000000000..3c08c220d
--- /dev/null
+++ b/vcl/source/treelist/imap.cxx
@@ -0,0 +1,994 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <tools/urlobj.hxx>
+#include <tools/fract.hxx>
+#include <tools/GenericTypeSerializer.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/mapmod.hxx>
+#include <o3tl/numeric.hxx>
+#include <svl/urihelper.hxx>
+#include <vcl/imap.hxx>
+#include <vcl/imapobj.hxx>
+#include <vcl/imapcirc.hxx>
+#include <vcl/imaprect.hxx>
+#include <vcl/imappoly.hxx>
+
+#include <string.h>
+#include <math.h>
+#include <memory>
+#include <sal/log.hxx>
+
+
+#define SCALEPOINT(aPT,aFracX,aFracY) (aPT).setX(long((aPT).X()*aFracX)); \
+ (aPT).setY(long((aPT).Y()*aFracY));
+
+
+/******************************************************************************/
+
+
+IMapObject::IMapObject()
+ : bActive( false )
+ , nReadVersion( 0 )
+{
+}
+
+IMapObject::IMapObject( const OUString& rURL, const OUString& rAltText, const OUString& rDesc,
+ const OUString& rTarget, const OUString& rName, bool bURLActive )
+: aURL( rURL )
+, aAltText( rAltText )
+, aDesc( rDesc )
+, aTarget( rTarget )
+, aName( rName )
+, bActive( bURLActive )
+, nReadVersion( 0 )
+{
+}
+
+
+void IMapObject::Write( SvStream& rOStm ) const
+{
+ const rtl_TextEncoding eEncoding = osl_getThreadTextEncoding();
+
+ rOStm.WriteUInt16( 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(""), 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 )
+{
+ 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
+|*
+\******************************************************************************/
+
+sal_uInt16 IMapRectangleObject::GetType() const
+{
+ return IMAP_OBJ_RECTANGLE;
+}
+
+
+/******************************************************************************
+|*
+|* Hit test
+|*
+\******************************************************************************/
+
+bool IMapRectangleObject::IsHit( const Point& rPoint ) const
+{
+ return aRect.IsInside( 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 )
+{
+ return ( IMapObject::IsEqual( rEqObj ) && ( aRect == rEqObj.aRect ) );
+}
+
+IMapCircleObject::IMapCircleObject( const Point& rCenter, sal_uLong 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_uLong 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
+|*
+\******************************************************************************/
+
+sal_uInt16 IMapCircleObject::GetType() const
+{
+ return IMAP_OBJ_CIRCLE;
+}
+
+
+/******************************************************************************
+|*
+|* Hit-Test
+|*
+\******************************************************************************/
+
+bool IMapCircleObject::IsHit( const Point& rPoint ) const
+{
+ const Point aPoint( aCenter - rPoint );
+ bool bRet = false;
+
+ if ( static_cast<sal_Int32>(sqrt( static_cast<double>(aPoint.X()) * aPoint.X() +
+ aPoint.Y() * aPoint.Y() )) <= nRadius )
+ {
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+Point IMapCircleObject::GetCenter( bool bPixelCoords ) const
+{
+ Point aNewPoint;
+
+ if ( bPixelCoords )
+ aNewPoint = Application::GetDefaultDevice()->LogicToPixel( aCenter, MapMode( MapUnit::Map100thMM ) );
+ else
+ aNewPoint = aCenter;
+
+ return aNewPoint;
+}
+
+sal_uLong IMapCircleObject::GetRadius( bool bPixelCoords ) const
+{
+ sal_uLong 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 )
+{
+ 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
+|*
+\******************************************************************************/
+
+sal_uInt16 IMapPolygonObject::GetType() const
+{
+ return IMAP_OBJ_POLYGON;
+}
+
+
+/******************************************************************************
+|*
+|* hit test
+|*
+\******************************************************************************/
+
+bool IMapPolygonObject::IsHit( const Point& rPoint ) const
+{
+ return aPoly.IsInside( rPoint );
+}
+
+tools::Polygon IMapPolygonObject::GetPolygon( bool bPixelCoords ) const
+{
+ tools::Polygon aNewPoly;
+
+ if ( bPixelCoords )
+ aNewPoly = Application::GetDefaultDevice()->LogicToPixel( aPoly, MapMode( MapUnit::Map100thMM ) );
+ else
+ aNewPoly = aPoly;
+
+ return aNewPoly;
+}
+
+void IMapPolygonObject::SetExtraEllipse( const tools::Rectangle& rEllipse )
+{
+ if ( aPoly.GetSize() )
+ {
+ bEllipse = true;
+ aEllipse = rEllipse;
+ }
+}
+
+void IMapPolygonObject::Scale( const Fraction& rFracX, const Fraction& rFracY )
+{
+ sal_uInt16 nCount = aPoly.GetSize();
+
+ for ( sal_uInt16 i = 0; i < nCount; i++ )
+ {
+ Point aScaledPt( aPoly[ i ] );
+
+ if ( rFracX.GetDenominator() && rFracY.GetDenominator() )
+ {
+ SCALEPOINT( aScaledPt, rFracX, rFracY );
+ }
+
+ aPoly[ i ] = aScaledPt;
+ }
+
+ if ( !bEllipse )
+ return;
+
+ Point aTL( aEllipse.TopLeft() );
+ Point aBR( aEllipse.BottomRight() );
+
+ if ( rFracX.GetDenominator() && rFracY.GetDenominator() )
+ {
+ SCALEPOINT( aTL, rFracX, rFracY );
+ SCALEPOINT( aBR, rFracX, rFracY );
+ }
+
+ aEllipse = tools::Rectangle( aTL, aBR );
+}
+
+bool IMapPolygonObject::IsEqual( const IMapPolygonObject& rEqObj )
+{
+ bool bRet = false;
+
+ if ( IMapObject::IsEqual( rEqObj ) )
+ {
+ const tools::Polygon& rEqPoly = rEqObj.aPoly;
+ const sal_uInt16 nCount = aPoly.GetSize();
+ const sal_uInt16 nEqCount = rEqPoly.GetSize();
+
+ if ( nCount == nEqCount )
+ {
+ bool bDifferent = false;
+
+ for ( sal_uInt16 i = 0; i < nCount; i++ )
+ {
+ if ( aPoly[ i ] != rEqPoly[ i ] )
+ {
+ bDifferent = true;
+ break;
+ }
+ }
+
+ if ( !bDifferent )
+ bRet = true;
+ }
+ }
+
+ return bRet;
+}
+
+/******************************************************************************
+|*
+|* Ctor
+|*
+\******************************************************************************/
+
+ImageMap::ImageMap( const OUString& rName )
+: aName( rName )
+{
+}
+
+
+/******************************************************************************
+|*
+|* Copy-Ctor
+|*
+\******************************************************************************/
+
+ImageMap::ImageMap( const ImageMap& rImageMap )
+{
+
+ size_t nCount = rImageMap.GetIMapObjectCount();
+
+ for ( size_t i = 0; i < nCount; i++ )
+ {
+ IMapObject* pCopyObj = rImageMap.GetIMapObject( i );
+
+ switch( pCopyObj->GetType() )
+ {
+ case IMAP_OBJ_RECTANGLE:
+ maList.emplace_back( new IMapRectangleObject( *static_cast<IMapRectangleObject*>( pCopyObj ) ) );
+ break;
+
+ case IMAP_OBJ_CIRCLE:
+ maList.emplace_back( new IMapCircleObject( *static_cast<IMapCircleObject*>( pCopyObj ) ) );
+ break;
+
+ case IMAP_OBJ_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 IMAP_OBJ_RECTANGLE:
+ maList.emplace_back( new IMapRectangleObject( *static_cast<IMapRectangleObject*>(pCopyObj) ) );
+ break;
+
+ case IMAP_OBJ_CIRCLE:
+ maList.emplace_back( new IMapCircleObject( *static_cast<IMapCircleObject*>(pCopyObj) ) );
+ break;
+
+ case IMAP_OBJ_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 IMAP_OBJ_RECTANGLE:
+ {
+ if ( ! static_cast<IMapRectangleObject*>(pObj)->IsEqual( *static_cast<IMapRectangleObject*>(pEqObj) ) )
+ bDifferent = true;
+ }
+ break;
+
+ case IMAP_OBJ_CIRCLE:
+ {
+ if ( ! static_cast<IMapCircleObject*>(pObj)->IsEqual( *static_cast<IMapCircleObject*>(pEqObj) ) )
+ bDifferent = true;
+ }
+ break;
+
+ case IMAP_OBJ_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 IMAP_OBJ_RECTANGLE:
+ maList.emplace_back( new IMapRectangleObject( static_cast<const IMapRectangleObject&>( rIMapObject ) ) );
+ break;
+
+ case IMAP_OBJ_CIRCLE:
+ maList.emplace_back( new IMapCircleObject( static_cast<const IMapCircleObject&>( rIMapObject ) ) );
+ break;
+
+ case IMAP_OBJ_POLYGON:
+ maList.emplace_back( new IMapPolygonObject( static_cast<const IMapPolygonObject&>( rIMapObject ) ) );
+ break;
+
+ default:
+ break;
+ }
+}
+
+void ImageMap::InsertIMapObject( std::unique_ptr<IMapObject> pNewObject )
+{
+ maList.emplace_back( std::move(pNewObject) );
+}
+
+/******************************************************************************
+|*
+|* hit test
+|*
+\******************************************************************************/
+
+IMapObject* ImageMap::GetHitIMapObject( const Size& rTotalSize,
+ const Size& rDisplaySize,
+ const Point& rRelHitPoint,
+ sal_uLong nFlags )
+{
+ Point aRelPoint( rTotalSize.Width() * rRelHitPoint.X() / rDisplaySize.Width(),
+ rTotalSize.Height() * rRelHitPoint.Y() / rDisplaySize.Height() );
+
+ // transform point to check before checking if flags to mirror etc. are set,
+ if ( nFlags )
+ {
+ if ( nFlags & IMAP_MIRROR_HORZ )
+ aRelPoint.setX( rTotalSize.Width() - aRelPoint.X() );
+
+ if ( nFlags & IMAP_MIRROR_VERT )
+ aRelPoint.setY( rTotalSize.Height() - aRelPoint.Y() );
+ }
+
+ // walk over all objects and execute HitTest
+ IMapObject* pObj = nullptr;
+ for(const auto& i : maList) {
+ if ( i->IsHit( aRelPoint ) ) {
+ pObj = i.get();
+ break;
+ }
+ }
+
+ return( pObj ? ( pObj->IsActive() ? pObj : nullptr ) : nullptr );
+}
+
+void ImageMap::Scale( const Fraction& rFracX, const Fraction& rFracY )
+{
+ size_t nCount = maList.size();
+
+ for ( size_t i = 0; i < nCount; i++ )
+ {
+ IMapObject* pObj = maList[ i ].get();
+
+ switch( pObj->GetType() )
+ {
+ case IMAP_OBJ_RECTANGLE:
+ static_cast<IMapRectangleObject*>( pObj )->Scale( rFracX, rFracY );
+ break;
+
+ case IMAP_OBJ_CIRCLE:
+ static_cast<IMapCircleObject*>( pObj )->Scale( rFracX, rFracY );
+ break;
+
+ case IMAP_OBJ_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( nType )
+ {
+ case IMAP_OBJ_RECTANGLE:
+ {
+ IMapRectangleObject* pObj = new IMapRectangleObject;
+ pObj->Read( rIStm );
+ maList.emplace_back( pObj );
+ }
+ break;
+
+ case IMAP_OBJ_CIRCLE:
+ {
+ IMapCircleObject* pObj = new IMapCircleObject;
+ pObj->Read( rIStm );
+ maList.emplace_back( pObj );
+ }
+ break;
+
+ case IMAP_OBJ_POLYGON:
+ {
+ IMapPolygonObject* pObj = new IMapPolygonObject;
+ pObj->Read( rIStm );
+ maList.emplace_back( pObj );
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+
+/******************************************************************************
+|*
+|* store binary
+|*
+\******************************************************************************/
+
+void ImageMap::Write( SvStream& rOStm ) const
+{
+ IMapCompat* pCompat;
+ OUString aImageName( GetName() );
+ SvStreamEndian nOldFormat = rOStm.GetEndian();
+ sal_uInt16 nCount = static_cast<sal_uInt16>(GetIMapObjectCount());
+ const rtl_TextEncoding eEncoding = osl_getThreadTextEncoding(); //vomit!
+
+ rOStm.SetEndian( SvStreamEndian::LITTLE );
+
+ // write MagicCode
+ rOStm.WriteCharPtr( IMAPMAGIC );
+ rOStm.WriteUInt16( IMAGE_MAP_VERSION );
+ write_uInt16_lenPrefixed_uInt8s_FromOUString(rOStm, aImageName, eEncoding);
+ write_uInt16_lenPrefixed_uInt8s_FromOString(rOStm, OString()); //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();
+ sal_uInt16 nCount;
+
+ rIStm.SetEndian( SvStreamEndian::LITTLE );
+ rIStm.ReadBytes(cMagic, sizeof(cMagic));
+
+ if ( !memcmp( cMagic, IMAPMAGIC, sizeof( cMagic ) ) )
+ {
+ IMapCompat* pCompat;
+
+ // delete old content
+ ClearImageMap();
+
+ // read on version
+ rIStm.SeekRel( 2 );
+
+ aName = read_uInt16_lenPrefixed_uInt8s_ToOUString(rIStm, osl_getThreadTextEncoding());
+ read_uInt16_lenPrefixed_uInt8s_ToOString(rIStm); // Dummy
+ rIStm.ReadUInt16( nCount );
+ read_uInt16_lenPrefixed_uInt8s_ToOString(rIStm); // Dummy
+
+ pCompat = new IMapCompat( rIStm, StreamMode::READ );
+
+ // here one can read in newer versions
+
+ delete pCompat;
+ ImpReadImageMap( rIStm, nCount );
+
+ }
+ else
+ rIStm.SetError( SVSTREAM_GENERALERROR );
+
+ rIStm.SetEndian( nOldFormat );
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/treelist/imap2.cxx b/vcl/source/treelist/imap2.cxx
new file mode 100644
index 000000000..382a76c04
--- /dev/null
+++ b/vcl/source/treelist/imap2.cxx
@@ -0,0 +1,531 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <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.makeStringAndClear());
+}
+
+void IMapRectangleObject::WriteNCSA( SvStream& rOStm ) const
+{
+ OStringBuffer aStrBuf("rect ");
+
+ AppendNCSAURL(aStrBuf);
+ AppendNCSACoords(aStrBuf, aRect.TopLeft());
+ AppendNCSACoords(aStrBuf, aRect.BottomRight());
+
+ rOStm.WriteLine(aStrBuf.makeStringAndClear());
+}
+
+void IMapCircleObject::WriteCERN( SvStream& rOStm ) const
+{
+ OStringBuffer aStrBuf("circle ");
+
+ AppendCERNCoords(aStrBuf, aCenter);
+ aStrBuf.append(nRadius);
+ aStrBuf.append(' ');
+ AppendCERNURL(aStrBuf);
+
+ rOStm.WriteLine(aStrBuf.makeStringAndClear());
+}
+
+void IMapCircleObject::WriteNCSA( SvStream& rOStm ) const
+{
+ OStringBuffer aStrBuf("circle ");
+
+ AppendNCSAURL(aStrBuf);
+ AppendNCSACoords(aStrBuf, aCenter);
+ AppendNCSACoords(aStrBuf, aCenter + Point(nRadius, 0));
+
+ rOStm.WriteLine(aStrBuf.makeStringAndClear());
+}
+
+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.makeStringAndClear());
+}
+
+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.makeStringAndClear());
+}
+
+void ImageMap::Write( SvStream& rOStm, sal_uLong nFormat ) const
+{
+ switch( nFormat )
+ {
+ case IMAP_FORMAT_BIN : Write( rOStm ); break;
+ case IMAP_FORMAT_CERN : ImpWriteCERN( rOStm ); break;
+ case IMAP_FORMAT_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 IMAP_OBJ_RECTANGLE:
+ static_cast<IMapRectangleObject*>( pObj )->WriteCERN( rOStm );
+ break;
+
+ case IMAP_OBJ_CIRCLE:
+ static_cast<IMapCircleObject*>( pObj )->WriteCERN( rOStm );
+ break;
+
+ case IMAP_OBJ_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 IMAP_OBJ_RECTANGLE:
+ static_cast<IMapRectangleObject*>( pObj )->WriteNCSA( rOStm );
+ break;
+
+ case IMAP_OBJ_CIRCLE:
+ static_cast<IMapCircleObject*>( pObj )->WriteNCSA( rOStm );
+ break;
+
+ case IMAP_OBJ_POLYGON:
+ static_cast<IMapPolygonObject*>( pObj )->WriteNCSA( rOStm );
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+sal_uLong ImageMap::Read( SvStream& rIStm, sal_uLong nFormat )
+{
+ sal_uLong nRet = IMAP_ERR_FORMAT;
+
+ if ( nFormat == IMAP_FORMAT_DETECT )
+ nFormat = ImpDetectFormat( rIStm );
+
+ switch ( nFormat )
+ {
+ case IMAP_FORMAT_BIN : Read( rIStm ); break;
+ case IMAP_FORMAT_CERN : ImpReadCERN( rIStm ); break;
+ case IMAP_FORMAT_NCSA : ImpReadNCSA( rIStm ); break;
+
+ default:
+ break;
+ }
+
+ if ( !rIStm.GetError() )
+ nRet = IMAP_ERR_OK;
+
+ return nRet;
+}
+
+void ImageMap::ImpReadCERN( SvStream& rIStm )
+{
+ // delete old content
+ ClearImageMap();
+
+ OString aStr;
+ while ( rIStm.ReadLine( aStr ) )
+ ImpReadCERNLine( aStr );
+}
+
+void ImageMap::ImpReadCERNLine( const OString& rLine )
+{
+ OString aStr = comphelper::string::stripStart(rLine, ' ');
+ aStr = comphelper::string::stripStart(aStr, '\t');
+ aStr = aStr.replaceAll(";", "");
+ aStr = aStr.toAsciiLowerCase();
+
+ const char* pStr = aStr.getStr();
+ char cChar = *pStr++;
+
+ // find instruction
+ OStringBuffer aBuf;
+ while ((cChar >= 'a') && (cChar <= 'z'))
+ {
+ aBuf.append(cChar);
+ cChar = *pStr++;
+ }
+ OString aToken = aBuf.makeStringAndClear();
+
+ if ( !(NOTEOL( cChar )) )
+ return;
+
+ if ( ( aToken == "rectangle" ) || ( aToken == "rect" ) )
+ {
+ const Point aTopLeft( ImpReadCERNCoords( &pStr ) );
+ const Point aBottomRight( ImpReadCERNCoords( &pStr ) );
+ const OUString aURL( ImpReadCERNURL( &pStr ) );
+ const tools::Rectangle aRect( aTopLeft, aBottomRight );
+
+ maList.emplace_back( new IMapRectangleObject( aRect, aURL, OUString(), OUString(), OUString(), OUString() ) );
+ }
+ else if ( ( aToken == "circle" ) || ( aToken == "circ" ) )
+ {
+ const Point aCenter( ImpReadCERNCoords( &pStr ) );
+ const 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( aStrX.makeStringAndClear().toInt32(), aStrY.makeStringAndClear().toInt32() );
+ }
+ }
+
+ return aPt;
+}
+
+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 aStr.makeStringAndClear().toInt32();
+}
+
+OUString ImageMap::ImpReadCERNURL( const char** ppStr )
+{
+ OUString aStr(OUString::createFromAscii(*ppStr));
+
+ aStr = comphelper::string::stripStart(aStr, ' ');
+ aStr = comphelper::string::stripStart(aStr, '\t');
+ aStr = comphelper::string::stripEnd(aStr, ' ');
+ aStr = comphelper::string::stripEnd(aStr, '\t');
+
+ return INetURLObject::GetAbsURL( "", aStr );
+}
+
+void ImageMap::ImpReadNCSA( SvStream& rIStm )
+{
+ // delete old content
+ ClearImageMap();
+
+ OString aStr;
+ while ( rIStm.ReadLine( aStr ) )
+ ImpReadNCSALine( aStr );
+}
+
+void ImageMap::ImpReadNCSALine( const OString& rLine )
+{
+ OString aStr = comphelper::string::stripStart(rLine, ' ');
+ aStr = comphelper::string::stripStart(aStr, '\t');
+ aStr = aStr.replaceAll(";", "");
+ aStr = aStr.toAsciiLowerCase();
+
+ const char* pStr = aStr.getStr();
+ char cChar = *pStr++;
+
+ // find instruction
+ OStringBuffer aBuf;
+ while ((cChar >= 'a') && (cChar <= 'z'))
+ {
+ aBuf.append(cChar);
+ cChar = *pStr++;
+ }
+ OString aToken = aBuf.makeStringAndClear();
+
+ if ( !(NOTEOL( cChar )) )
+ return;
+
+ if ( aToken == "rect" )
+ {
+ const OUString aURL( ImpReadNCSAURL( &pStr ) );
+ const Point aTopLeft( ImpReadNCSACoords( &pStr ) );
+ const Point aBottomRight( ImpReadNCSACoords( &pStr ) );
+ const tools::Rectangle aRect( aTopLeft, aBottomRight );
+
+ maList.emplace_back( new IMapRectangleObject( aRect, aURL, OUString(), OUString(), OUString(), OUString() ) );
+ }
+ else if ( aToken == "circle" )
+ {
+ const OUString aURL( ImpReadNCSAURL( &pStr ) );
+ const Point aCenter( ImpReadNCSACoords( &pStr ) );
+ const Point aDX( aCenter - ImpReadNCSACoords( &pStr ) );
+ long nRadius = static_cast<long>(sqrt( static_cast<double>(aDX.X()) * aDX.X() +
+ static_cast<double>(aDX.Y()) * aDX.Y() ));
+
+ maList.emplace_back( new IMapCircleObject( aCenter, nRadius, aURL, OUString(), OUString(), OUString(), OUString() ) );
+ }
+ else if ( aToken == "poly" )
+ {
+ const sal_uInt16 nCount = comphelper::string::getTokenCount(aStr, ',') - 1;
+ const OUString aURL( ImpReadNCSAURL( &pStr ) );
+ tools::Polygon aPoly( nCount );
+
+ for ( sal_uInt16 i = 0; i < nCount; i++ )
+ aPoly[ i ] = ImpReadNCSACoords( &pStr );
+
+ maList.emplace_back( new IMapPolygonObject( aPoly, aURL, OUString(), OUString(), OUString(), OUString() ) );
+ }
+}
+
+OUString ImageMap::ImpReadNCSAURL( const char** ppStr )
+{
+ OUStringBuffer aStr;
+ char cChar = *(*ppStr)++;
+
+ while( NOTEOL( cChar ) && ( ( cChar == ' ' ) || ( cChar == '\t' ) ) )
+ cChar = *(*ppStr)++;
+
+ if ( NOTEOL( cChar ) )
+ {
+ while( NOTEOL( cChar ) && ( cChar != ' ' ) && ( cChar != '\t' ) )
+ {
+ aStr.append( cChar );
+ cChar = *(*ppStr)++;
+ }
+ }
+
+ return INetURLObject::GetAbsURL( "", 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( aStrX.makeStringAndClear().toInt32(), aStrY.makeStringAndClear().toInt32() );
+ }
+ }
+
+ return aPt;
+}
+
+sal_uLong ImageMap::ImpDetectFormat( SvStream& rIStm )
+{
+ sal_uInt64 nPos = rIStm.Tell();
+ sal_uLong nRet = IMAP_FORMAT_BIN;
+ 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 ) ) )
+ {
+ 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 = IMAP_FORMAT_CERN;
+ }
+ else
+ nRet = IMAP_FORMAT_NCSA;
+
+ break;
+ }
+ }
+ }
+
+ rIStm.Seek( nPos );
+
+ return nRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/treelist/imap3.cxx b/vcl/source/treelist/imap3.cxx
new file mode 100644
index 000000000..32c6523e4
--- /dev/null
+++ b/vcl/source/treelist/imap3.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/imap.hxx>
+
+#include <tools/debug.hxx>
+
+
+/******************************************************************************
+|*
+|* Ctor
+|*
+\******************************************************************************/
+
+IMapCompat::IMapCompat( SvStream& rStm, const StreamMode nStreamMode )
+ : pRWStm(&rStm)
+ , nCompatPos(0)
+ , nTotalSize(0)
+ , nStmMode(nStreamMode)
+{
+ DBG_ASSERT( nStreamMode == StreamMode::READ || nStreamMode == StreamMode::WRITE, "Wrong Mode!" );
+
+ if ( pRWStm->GetError() )
+ return;
+
+ if ( nStmMode == StreamMode::WRITE )
+ {
+ nCompatPos = pRWStm->Tell();
+ pRWStm->SeekRel( 4 );
+ nTotalSize = nCompatPos + 4;
+ }
+ else
+ {
+ sal_uInt32 nTotalSizeTmp;
+ pRWStm->ReadUInt32( nTotalSizeTmp );
+ nTotalSize = nTotalSizeTmp;
+ nCompatPos = pRWStm->Tell();
+ }
+}
+
+
+/******************************************************************************
+|*
+|* Dtor
+|*
+\******************************************************************************/
+
+IMapCompat::~IMapCompat()
+{
+ if ( pRWStm->GetError() )
+ return;
+
+ if ( nStmMode == StreamMode::WRITE )
+ {
+ const sal_uInt64 nEndPos = pRWStm->Tell();
+
+ pRWStm->Seek( nCompatPos );
+ pRWStm->WriteUInt32( nEndPos - nTotalSize );
+ pRWStm->Seek( nEndPos );
+ }
+ else
+ {
+ const sal_uInt64 nReadSize = pRWStm->Tell() - nCompatPos;
+
+ if ( nTotalSize > nReadSize )
+ pRWStm->SeekRel( nTotalSize - nReadSize );
+ }
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/treelist/inetimg.cxx b/vcl/source/treelist/inetimg.cxx
new file mode 100644
index 000000000..c7bc99ebb
--- /dev/null
+++ b/vcl/source/treelist/inetimg.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 <osl/thread.h>
+#include <sot/formats.hxx>
+#include <tools/stream.hxx>
+
+#include <vcl/inetimg.hxx>
+
+static 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( sINetImg.getToken( 0, TOKEN_SEPARATOR,
+ nStart ).toInt32() );
+ aSizePixel.setHeight( sINetImg.getToken( 0, TOKEN_SEPARATOR,
+ nStart ).toInt32() );
+ bRet = !sINetImg.isEmpty();
+ }
+ break;
+
+ case SotClipboardFormatId::NETSCAPE_IMAGE:
+ {
+/*
+ --> structure size MUST - alignment of 4!
+ int iSize; // size of all data, including variable length strings
+ sal_Bool bIsMap; // For server side maps
+ sal_Int32 iWidth; // Fixed size data correspond to fields in LO_ImageDataStruct
+ sal_Int32 iHeight; // and EDT_ImageData
+ sal_Int32 iHSpace;
+ sal_Int32 iVSpace;
+ sal_Int32 iBorder;
+ int iLowResOffset; // Offsets into string_data. If 0, string is NULL (not used)
+ int iAltOffset; // (alternate text?)
+ int iAnchorOffset; // HREF in image
+ int iExtraHTML_Offset; // Extra HTML (stored in CImageElement)
+ char pImageURL[1]; // Append all variable-length strings starting here
+*/
+ rtl_TextEncoding eSysCSet = osl_getThreadTextEncoding();
+ sal_Int32 nVal, nAnchorOffset, nAltOffset;
+ sal_uInt64 nFilePos;
+
+ nFilePos = rIStm.Tell();
+ // skip over iSize (int), bIsMao ( sal_Bool ) alignment of 4 !!!!
+ rIStm.SeekRel( 8 );
+ rIStm.ReadInt32( nVal ); aSizePixel.setWidth( nVal );
+ rIStm.ReadInt32( nVal ); aSizePixel.setHeight( nVal );
+ // skip over iHSpace, iVSpace, iBorder, iLowResOffset
+ rIStm.SeekRel( 3 * sizeof( sal_Int32 ) + sizeof( int ) );
+ rIStm.ReadInt32( nAltOffset );
+ rIStm.ReadInt32( nAnchorOffset );
+ // skip over iExtraHTML_Offset
+ rIStm.SeekRel( sizeof( int ) );
+
+ aImageURL = read_zeroTerminated_uInt8s_ToOUString(rIStm, eSysCSet);
+ if( nAltOffset )
+ {
+ rIStm.Seek( nFilePos + nAltOffset );
+ /*aAlternateText =*/ read_zeroTerminated_uInt8s_ToOUString(rIStm, eSysCSet);
+ }
+
+ if( nAnchorOffset )
+ {
+ rIStm.Seek( nFilePos + nAnchorOffset );
+ aTargetURL = read_zeroTerminated_uInt8s_ToOUString(rIStm, eSysCSet);
+ }
+ else if( !aTargetURL.isEmpty() )
+ aTargetURL.clear();
+
+ bRet = ERRCODE_NONE == rIStm.GetError();
+ }
+ break;
+ default: break;
+ }
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/treelist/svimpbox.cxx b/vcl/source/treelist/svimpbox.cxx
new file mode 100644
index 000000000..fcd1487e7
--- /dev/null
+++ b/vcl/source/treelist/svimpbox.cxx
@@ -0,0 +1,3314 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/treelistbox.hxx>
+#include <vcl/svlbitm.hxx>
+#include <tools/wintypes.hxx>
+#include <bitmaps.hlst>
+#include <svimpbox.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/string.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <tools/debug.hxx>
+
+#include <vcl/treelistentry.hxx>
+#include <vcl/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_aHorSBar(VclPtr<ScrollBar>::Create(pLBView, WB_DRAG | WB_HSCROLL))
+ , m_aScrBarBox(VclPtr<ScrollBarBox>::Create(pLBView))
+ , m_aFctSet(this, pLBView)
+ , mbForceMakeVisible (false)
+ , 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_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 = m_bContextMenuHandling = false;
+}
+
+SvImpLBox::~SvImpLBox()
+{
+ m_aEditIdle.Stop();
+ StopUserEvent();
+
+ if ( osl_atomic_decrement(&s_nImageRefCount) == 0 )
+ {
+ DELETEZ(s_pDefCollapsed);
+ DELETEZ(s_pDefExpanded);
+ }
+ 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_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->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;
+ }
+ EndScroll();
+}
+
+// handler for vertical scrollbar
+
+IMPL_LINK( SvImpLBox, ScrollUpDownHdl, ScrollBar *, pScrollBar, void )
+{
+ DBG_ASSERT(!m_bInVScrollHdl,"Scroll handler out-paces itself!");
+ long nDelta = pScrollBar->GetDelta();
+ if( !nDelta )
+ return;
+
+ m_nFlags &= ~LBoxFlags::Filling;
+
+ m_bInVScrollHdl = true;
+
+ if( m_pView->IsEditingActive() )
+ {
+ m_pView->EndEditing( true ); // Cancel
+ m_pView->PaintImmediately();
+ }
+ BeginScroll();
+
+ if( nDelta > 0 )
+ {
+ if( nDelta == 1 )
+ CursorDown();
+ else
+ PageDown( static_cast<sal_uInt16>(nDelta) );
+ }
+ else
+ {
+ nDelta *= -1;
+ if( nDelta == 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;
+ long nEntryHeight = m_pView->GetEntryHeight();
+ ShowCursor( false );
+ m_pView->PaintImmediately();
+ m_pStartEntry = pPrevFirstToDraw;
+ tools::Rectangle aArea( GetVisibleArea() );
+ 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() );
+ long nScroll = m_pView->GetEntryHeight() * static_cast<long>(nRealDelta);
+ nScroll = -nScroll;
+ m_pView->PaintImmediately();
+ m_pView->Scroll( 0, nScroll, aArea, ScrollFlags::NoChildren );
+ m_pView->PaintImmediately();
+ m_pView->NotifyScrolled();
+ }
+
+ ShowCursor( true );
+}
+
+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
+ {
+ long nEntryHeight = m_pView->GetEntryHeight();
+ tools::Rectangle aArea( GetVisibleArea() );
+ m_pView->PaintImmediately();
+ m_pView->Scroll( 0, nEntryHeight*nRealDelta, aArea, ScrollFlags::NoChildren );
+ m_pView->PaintImmediately();
+ m_pView->NotifyScrolled();
+ }
+
+ ShowCursor( true );
+}
+
+void SvImpLBox::KeyUp( bool bPageUp )
+{
+ if( !m_aVerSBar->IsVisible() )
+ return;
+
+ long nDelta;
+ if( bPageUp )
+ nDelta = m_aVerSBar->GetPageSize();
+ else
+ nDelta = 1;
+
+ long nThumbPos = m_aVerSBar->GetThumbPos();
+
+ if( nThumbPos < nDelta )
+ nDelta = nThumbPos;
+
+ if( nDelta <= 0 )
+ return;
+
+ m_nFlags &= ~LBoxFlags::Filling;
+ BeginScroll();
+
+ m_aVerSBar->SetThumbPos( nThumbPos - nDelta );
+ if( bPageUp )
+ PageUp( static_cast<short>(nDelta) );
+ else
+ CursorUp();
+
+ EndScroll();
+}
+
+
+void SvImpLBox::KeyDown( bool bPageDown )
+{
+ if( !m_aVerSBar->IsVisible() )
+ return;
+
+ long nDelta;
+ if( bPageDown )
+ nDelta = m_aVerSBar->GetPageSize();
+ else
+ nDelta = 1;
+
+ long nThumbPos = m_aVerSBar->GetThumbPos();
+ long nVisibleSize = m_aVerSBar->GetVisibleSize();
+ long nRange = m_aVerSBar->GetRange().Len();
+
+ long nTmp = nThumbPos+nVisibleSize;
+ while( (nDelta > 0) && (nTmp+nDelta) >= nRange )
+ nDelta--;
+
+ if( nDelta <= 0 )
+ return;
+
+ m_nFlags &= ~LBoxFlags::Filling;
+ BeginScroll();
+
+ m_aVerSBar->SetThumbPos( nThumbPos+nDelta );
+ if( bPageDown )
+ PageDown( static_cast<short>(nDelta) );
+ else
+ CursorDown();
+
+ EndScroll();
+}
+
+
+void SvImpLBox::InvalidateEntriesFrom( long nY ) const
+{
+ if( !(m_nFlags & LBoxFlags::InPaint ))
+ {
+ tools::Rectangle aRect( GetVisibleArea() );
+ aRect.SetTop( nY );
+ m_pView->Invalidate( aRect );
+ }
+}
+
+void SvImpLBox::InvalidateEntry( long nY ) const
+{
+ if( !(m_nFlags & LBoxFlags::InPaint ))
+ {
+ tools::Rectangle aRect( GetVisibleArea() );
+ 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() )
+ {
+ 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();
+ long nY = GetEntryLine( m_pCursor );
+ tools::Rectangle aRect = m_pView->GetFocusRect( m_pCursor, nY );
+ vcl::Region aOldClip( m_pView->GetClipRegion());
+ vcl::Region aClipRegion( GetClipRegionRect() );
+ m_pView->SetClipRegion( aClipRegion );
+ m_pView->ShowFocus( aRect );
+ m_pView->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->GetClipRegion());
+ vcl::Region aClipRegion( GetClipRegionRect() );
+ m_pView->SetClipRegion( aClipRegion );
+ m_pView->HideFocus();
+ m_pView->SetClipRegion( aOldClip );
+ }
+ else
+ {
+ long nY = GetEntryLine( m_pCursor );
+ tools::Rectangle aRect = m_pView->GetFocusRect( m_pCursor, nY );
+ vcl::Region aOldClip( m_pView->GetClipRegion());
+ vcl::Region aClipRegion( GetClipRegionRect() );
+ m_pView->SetClipRegion( aClipRegion );
+ m_pView->ShowFocus( aRect );
+ m_pView->SetClipRegion( aOldClip );
+ }
+}
+
+
+void SvImpLBox::UpdateAll( bool bInvalidateCompleteView )
+{
+ FindMostRight();
+ m_aVerSBar->SetRange( Range(0, m_pView->GetVisibleCount()-1 ) );
+ SyncVerThumb();
+ FillView();
+ ShowVerSBar();
+ if( m_bSimpleTravel && m_pCursor && m_pView->HasFocus() )
+ m_pView->Select( m_pCursor );
+ ShowCursor( true );
+ if( bInvalidateCompleteView )
+ m_pView->Invalidate();
+ else
+ m_pView->Invalidate( GetVisibleArea() );
+}
+
+IMPL_LINK( SvImpLBox, ScrollLeftRightHdl, ScrollBar *, pScrollBar, void )
+{
+ 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( long nDelta )
+{
+ if( !(m_nFlags & LBoxFlags::InResize) )
+ m_pView->PaintImmediately();
+ BeginScroll();
+ m_nFlags &= ~LBoxFlags::Filling;
+ ShowCursor( false );
+
+ // calculate new origin
+ 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, 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.IsInside( 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;
+ long nY = rPoint.Y();
+ SvTreeListEntry* pEntry = nullptr;
+ 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();
+
+ long nRectHeight = rRect.GetHeight();
+ 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
+
+ long nY = nStartLine * nEntryHeight;
+ SvTreeListEntry* pEntry = m_pStartEntry;
+ while (nStartLine && pEntry)
+ {
+ pEntry = m_pView->NextVisible(pEntry);
+ nStartLine--;
+ }
+
+ vcl::Region aClipRegion(GetClipRegionRect());
+
+ // first draw the lines, then clip them!
+ rRenderContext.SetClipRegion();
+ if (m_nStyle & (WB_HASLINES | WB_HASLINESATROOT))
+ DrawNet(rRenderContext);
+
+ rRenderContext.SetClipRegion(aClipRegion);
+
+ 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);
+ }
+
+ m_nFlags &= ~LBoxFlags::DeselectAll;
+ rRenderContext.SetClipRegion();
+ 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<long>(m_pView->GetVisiblePos( m_pStartEntry )) );
+ ShowCursor( true );
+ m_pView->Invalidate();
+}
+
+void SvImpLBox::ScrollToAbsPos( long nPos )
+{
+ if( m_pView->GetVisibleCount() == 0 )
+ return;
+ 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;
+ }
+ }
+
+ long nEntryHeight = m_pView->GetEntryHeight();
+ 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));
+ long nY = 0;
+ nY -= (nOffs * nEntryHeight);
+
+ DBG_ASSERT(pFirstDynamicTab,"No Tree!");
+
+ rRenderContext.Push(PushFlags::LINECOLOR);
+
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ Color aCol = rStyleSettings.GetFaceColor();
+
+ if (aCol.IsRGBEqual(rRenderContext.GetBackground().GetColor()))
+ aCol = rStyleSettings.GetShadowColor();
+ rRenderContext.SetLineColor(aCol);
+ Point aPos1, aPos2;
+ sal_uInt16 nDistance;
+ sal_uLong nMax = m_nVisibleCount + nOffs + 1;
+
+ const Image& rExpandedNodeBitmap = GetExpandedNodeBmp();
+
+ for (sal_uLong n=0; n< nMax && pEntry; n++)
+ {
+ if (m_pView->IsExpanded(pEntry))
+ {
+ aPos1.setX( m_pView->GetTabPos(pEntry, pFirstDynamicTab) );
+ // if it is not a context bitmap, go a little to the right below the
+ // first text (node bitmap, too)
+ if (!m_pView->nContextBmpWidthMax)
+ aPos1.AdjustX(rExpandedNodeBitmap.GetSizePixel().Width() / 2 );
+
+ aPos1.setY( nY );
+ aPos1.AdjustY(nEntryHeightDIV2 );
+
+ 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 );
+ rRenderContext.DrawLine(aPos1, aPos2);
+ }
+ // visible in control?
+ if (n >= nOffs && ((m_nStyle & WB_HASLINESATROOT) || !m_pTree->IsAtRootDepth(pEntry)))
+ {
+ // can we recycle aPos1?
+ if (!m_pView->IsExpanded(pEntry))
+ {
+ // nope
+ aPos1.setX( m_pView->GetTabPos(pEntry, pFirstDynamicTab) );
+ // if it is not a context bitmap, go a little to the right below
+ // the first text (node bitmap, too)
+ if (!m_pView->nContextBmpWidthMax)
+ aPos1.AdjustX(rExpandedNodeBitmap.GetSizePixel().Width() / 2 );
+ aPos1.setY( nY );
+ aPos1.AdjustY(nEntryHeightDIV2 );
+ aPos2.setX( aPos1.X() );
+ }
+ aPos2.setY( aPos1.Y() );
+ aPos2.AdjustX( -(m_pView->GetIndent()) );
+ rRenderContext.DrawLine(aPos1, aPos2);
+ }
+ nY += nEntryHeight;
+ pEntry = m_pView->NextVisible(pEntry);
+ }
+ if (m_nStyle & WB_HASLINESATROOT)
+ {
+ pEntry = m_pView->First();
+ aPos1.setX( m_pView->GetTabPos(pEntry, pFirstDynamicTab) );
+ // if it is not a context bitmap, go a little to the right below the
+ // first text (node bitmap, too)
+ if (!m_pView->nContextBmpWidthMax)
+ aPos1.AdjustX(rExpandedNodeBitmap.GetSizePixel().Width() / 2 );
+ aPos1.AdjustX( -(m_pView->GetIndent()) );
+ aPos1.setY( GetEntryLine( pEntry ) );
+ aPos1.AdjustY(nEntryHeightDIV2 );
+ pChild = pEntry->LastSibling();
+ aPos2.setX( aPos1.X() );
+ aPos2.setY( GetEntryLine( pChild ) );
+ aPos2.AdjustY(nEntryHeightDIV2 );
+ rRenderContext.DrawLine(aPos1, aPos2);
+ }
+ rRenderContext.Pop();
+}
+
+void SvImpLBox::PositionScrollBars( Size& rSize, sal_uInt16 nMask )
+{
+ 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 )
+{
+ 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;
+ long nMaxRight = aOSize.Width(); //GetOutputSize().Width();
+ Point aOrigin( m_pView->GetMapMode().GetOrigin() );
+ aOrigin.setX( aOrigin.X() * -1 );
+ nMaxRight += aOrigin.X() - 1;
+ 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 = 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 = (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
+ long nTemp = static_cast<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() );
+ long nNewThumbPos = m_aHorSBar->GetThumbPos();
+ Range aRange( m_aHorSBar->GetRange() );
+ if( aRange.Max() < m_nMostRight+25 )
+ {
+ aRange.Max() = m_nMostRight+25;
+ m_aHorSBar->SetRange( aRange );
+ }
+
+ if( nTemp != nNewThumbPos )
+ {
+ nTemp = nNewThumbPos - nTemp;
+ if( m_pView->IsEditingActive() )
+ {
+ m_pView->EndEditing( true ); // Cancel
+ m_pView->PaintImmediately();
+ }
+ m_pView->nFocusWidth = -1;
+ KeyLeftRight( nTemp );
+ }
+
+ if( nResult & 0x0001 )
+ m_aVerSBar->Show();
+ else
+ m_aVerSBar->Hide();
+
+ if( nResult & 0x0002 )
+ m_aHorSBar->Show();
+ else
+ {
+ m_aHorSBar->Hide();
+ }
+ rSize = aOSize;
+}
+
+void SvImpLBox::InitScrollBarBox()
+{
+ m_aScrBarBox->SetSizePixel( Size(m_nVerSBarWidth, m_nHorSBarHeight) );
+ Size aSize( m_pView->Control::GetOutputSizePixel() );
+ m_aScrBarBox->SetPosPixel( Point(aSize.Width()-m_nVerSBarWidth, aSize.Height()-m_nHorSBarHeight));
+}
+
+void SvImpLBox::Resize()
+{
+ m_aOutputSize = m_pView->Control::GetOutputSizePixel();
+ if( m_aOutputSize.IsEmpty() )
+ return;
+ m_nFlags |= LBoxFlags::InResize;
+ InitScrollBarBox();
+
+ if( m_pView->GetEntryHeight())
+ {
+ AdjustScrollBars( m_aOutputSize );
+ UpdateAll(false);
+ }
+ // HACK, as in floating and docked windows the scrollbars might not be drawn
+ // correctly/not be drawn at all after resizing!
+ if( m_aHorSBar->IsVisible())
+ m_aHorSBar->Invalidate();
+ if( m_aVerSBar->IsVisible())
+ m_aVerSBar->Invalidate();
+ m_nFlags &= ~LBoxFlags::InResize;
+}
+
+void SvImpLBox::FillView()
+{
+ if( !m_pStartEntry )
+ {
+ sal_uLong nVisibleViewCount = m_pView->GetVisibleCount();
+ 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 );
+ }
+ }
+
+ 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 )
+ {
+ 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;
+ long nY = GetEntryLine( pEntry );
+ if( nY < 0 )
+ return false;
+ long nMax = m_nVisibleCount * m_pView->GetEntryHeight();
+ return nY < nMax;
+}
+
+
+long SvImpLBox::GetEntryLine(const SvTreeListEntry* pEntry) const
+{
+ if(!m_pStartEntry )
+ return -1; // invisible position
+
+ long nFirstVisPos = m_pView->GetVisiblePos( m_pStartEntry );
+ 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 );
+ 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 )
+ {
+ long nOldThumbPos = m_aVerSBar->GetThumbPos();
+ sal_uLong nVisList = m_pView->GetVisibleCount();
+ m_aVerSBar->SetRange( Range(0, nVisList-1) );
+ 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?
+ 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) )
+ {
+ 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
+ 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 )
+ {
+ long nMouseX = rMEvt.GetPosPixel().X();
+ if( pEntry == m_pActiveEntry &&
+ m_pView->GetItem(m_pActiveEntry, nMouseX) == m_pActiveButton )
+ {
+ if( !m_pActiveButton->IsStateHilighted() )
+ {
+ m_pActiveButton->SetStateHilighted(true );
+ InvalidateEntry(m_pActiveEntry);
+ }
+ }
+ else
+ {
+ if( m_pActiveButton->IsStateHilighted() )
+ {
+ m_pActiveButton->SetStateHilighted(false );
+ InvalidateEntry(m_pActiveEntry);
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+bool SvImpLBox::ButtonUpCheckCtrl( const MouseEvent& rMEvt )
+{
+ if( m_pActiveButton )
+ {
+ m_pView->ReleaseMouse();
+ SvTreeListEntry* pEntry = GetClickedEntry( rMEvt.GetPosPixel() );
+ m_pActiveButton->SetStateHilighted( false );
+ long nMouseX = rMEvt.GetPosPixel().X();
+ if (pEntry == m_pActiveEntry && m_pView->GetItem(m_pActiveEntry, nMouseX) == m_pActiveButton)
+ m_pActiveButton->ClickHdl(m_pActiveEntry);
+ InvalidateEntry(m_pActiveEntry);
+ if (m_pCursor == m_pActiveEntry)
+ ShowCursor(true);
+ m_pActiveButton = nullptr;
+ m_pActiveEntry = nullptr;
+ m_pActiveTab = nullptr;
+ return true;
+ }
+ return false;
+}
+
+// ******* Control plus/minus button for expanding/collapsing
+
+// false == no expand/collapse button hit
+bool SvImpLBox::IsNodeButton( const Point& rPosPixel, SvTreeListEntry* pEntry ) const
+{
+ if( !pEntry->HasChildren() && !pEntry->HasChildrenOnDemand() )
+ return false;
+
+ SvLBoxTab* pFirstDynamicTab = m_pView->GetFirstDynamicTab();
+ if( !pFirstDynamicTab )
+ return false;
+
+ long nMouseX = rPosPixel.X();
+ // convert to document coordinates
+ Point aOrigin( m_pView->GetMapMode().GetOrigin() );
+ nMouseX -= aOrigin.X();
+
+ 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;
+
+ 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 );
+ EndScroll();
+ if( m_nFlags & LBoxFlags::StartEditTimer )
+ {
+ m_nFlags &= ~LBoxFlags::StartEditTimer;
+ m_aEditClickPos = rMEvt.GetPosPixel();
+ m_aEditIdle.Start();
+ }
+
+ if (m_pView->mbActivateOnSingleClick)
+ m_pView->DoubleClickHdl();
+}
+
+void SvImpLBox::MouseMove( const MouseEvent& rMEvt)
+{
+ SvTreeListEntry* pEntry = GetClickedEntry( rMEvt.GetPosPixel() );
+ if ( !MouseMoveCheckCtrl( rMEvt, pEntry ) && ( m_aSelEng.GetSelectionMode() != SelectionMode::NONE ) )
+ m_aSelEng.SelMouseMove( rMEvt );
+}
+
+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)
+ {
+ 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())
+ {
+ long nThumb = m_aHorSBar->GetThumbPos();
+ nThumb += m_aHorSBar->GetLineSize();
+ long nOldThumb = m_aHorSBar->GetThumbPos();
+ m_aHorSBar->SetThumbPos( nThumb );
+ nThumb = nOldThumb;
+ nThumb -= m_aHorSBar->GetThumbPos();
+ nThumb *= -1;
+ if( nThumb )
+ {
+ KeyLeftRight( nThumb );
+ EndScroll();
+ }
+ }
+ else
+ bKeyUsed = false;
+ break;
+ }
+
+ case KEY_LEFT:
+ {
+ if (m_aHorSBar->IsVisible())
+ {
+ long nThumb = m_aHorSBar->GetThumbPos();
+ nThumb -= m_aHorSBar->GetLineSize();
+ long nOldThumb = m_aHorSBar->GetThumbPos();
+ m_aHorSBar->SetThumbPos( nThumb );
+ nThumb = nOldThumb;
+ nThumb -= m_aHorSBar->GetThumbPos();
+ if( nThumb )
+ {
+ KeyLeftRight( -nThumb );
+ EndScroll();
+ }
+ else if( m_bSubLstOpLR )
+ {
+ if( IsExpandable() && m_pView->IsExpanded( m_pCursor ) )
+ m_pView->Collapse( m_pCursor );
+ else
+ {
+ pNewCursor = m_pView->GetParent( m_pCursor );
+ if( pNewCursor )
+ SetCursor( pNewCursor );
+ }
+ }
+ }
+ else if( m_bSubLstOpLR )
+ {
+ if( IsExpandable() && m_pView->IsExpanded( m_pCursor ) )
+ m_pView->Collapse( m_pCursor );
+ else
+ {
+ pNewCursor = m_pView->GetParent( m_pCursor );
+ if( pNewCursor )
+ SetCursor( pNewCursor );
+ }
+ }
+ else
+ bKeyUsed = false;
+ break;
+ }
+
+ case KEY_PAGEUP:
+ if( !bMod1 )
+ {
+ pNewCursor = m_pView->PrevVisible(m_pCursor, nDelta);
+
+ while( nDelta && pNewCursor && !IsSelectable(pNewCursor) )
+ {
+ pNewCursor = m_pView->NextVisible(pNewCursor);
+ nDelta--;
+ }
+
+ if( nDelta )
+ {
+ DBG_ASSERT(pNewCursor && pNewCursor!=m_pCursor, "Cursor?");
+ m_aSelEng.CursorPosChanging( bShift, bMod1 );
+ if( IsEntryInView( pNewCursor ) )
+ SetCursor( pNewCursor );
+ else
+ {
+ SetCursor( pNewCursor );
+ KeyUp( true );
+ }
+ }
+ }
+ else
+ bKeyUsed = false;
+ break;
+
+ case KEY_PAGEDOWN:
+ if( !bMod1 )
+ {
+ pNewCursor= m_pView->NextVisible(m_pCursor, nDelta);
+
+ while( nDelta && pNewCursor && !IsSelectable(pNewCursor) )
+ {
+ pNewCursor = m_pView->PrevVisible(pNewCursor);
+ nDelta--;
+ }
+
+ if( nDelta && pNewCursor )
+ {
+ DBG_ASSERT(pNewCursor && pNewCursor!=m_pCursor, "Cursor?");
+ m_aSelEng.CursorPosChanging( bShift, bMod1 );
+ if( IsEntryInView( pNewCursor ) )
+ SetCursor( pNewCursor );
+ else
+ {
+ SetCursor( pNewCursor );
+ KeyDown( true );
+ }
+ }
+ else
+ KeyDown( false ); // see also: KEY_DOWN
+ }
+ else
+ bKeyUsed = false;
+ break;
+
+ case KEY_SPACE:
+ if ( m_pView->GetSelectionMode() != SelectionMode::NONE )
+ {
+ if ( bMod1 )
+ {
+ if ( m_pView->GetSelectionMode() == SelectionMode::Multiple && !bShift )
+ // toggle selection
+ m_pView->Select( m_pCursor, !m_pView->IsSelected( m_pCursor ) );
+ }
+ else if ( !bShift /*&& !bMod1*/ )
+ {
+ if ( m_aSelEng.IsAddMode() )
+ {
+ // toggle selection
+ m_pView->Select( m_pCursor, !m_pView->IsSelected( m_pCursor ) );
+ }
+ else if ( !m_pView->IsSelected( m_pCursor ) )
+ {
+ SelAllDestrAnch( false );
+ m_pView->Select( m_pCursor );
+ }
+ else
+ bKeyUsed = false;
+ }
+ else
+ bKeyUsed = false;
+ }
+ else
+ bKeyUsed = false;
+ break;
+
+ case KEY_RETURN:
+ bKeyUsed = !m_pView->DoubleClickHdl();
+ break;
+
+ case KEY_F2:
+ if( !bShift && !bMod1 )
+ {
+ m_aEditClickPos = Point( -1, -1 );
+ EditTimerCall( nullptr );
+ }
+ else
+ bKeyUsed = false;
+ break;
+
+ case KEY_F8:
+ if( bShift && m_pView->GetSelectionMode()==SelectionMode::Multiple &&
+ !(m_nStyle & WB_SIMPLEMODE))
+ {
+ if( m_aSelEng.IsAlwaysAdding() )
+ m_aSelEng.AddAlways( false );
+ else
+ m_aSelEng.AddAlways( true );
+ }
+ else
+ bKeyUsed = false;
+ break;
+
+ case KEY_ADD:
+ if (!m_pView->IsExpanded(m_pCursor))
+ m_pView->Expand(m_pCursor);
+ if (bMod1)
+ ExpandAll();
+ break;
+
+ case KEY_A:
+ if( bMod1 )
+ SelAllDestrAnch( true );
+ else
+ bKeyUsed = false;
+ break;
+
+ case KEY_SUBTRACT:
+ if (m_pView->IsExpanded(m_pCursor))
+ m_pView->Collapse(m_pCursor);
+ if (bMod1)
+ CollapseTo(m_pTree->GetRootLevelParent(m_pCursor));
+ break;
+
+ case KEY_MULTIPLY:
+ if( bMod1 )
+ {
+ // only try to expand/collapse if sublist is expandable,
+ // otherwise ignore the key press
+ if( IsExpandable() )
+ {
+ if (!m_pView->IsAllExpanded(m_pCursor))
+ {
+ m_pView->Expand(m_pCursor);
+ ExpandAll();
+ }
+ else
+ CollapseTo(m_pCursor);
+ }
+ }
+ else
+ bKeyUsed = false;
+ break;
+
+ case KEY_DIVIDE :
+ if( bMod1 )
+ SelAllDestrAnch( true );
+ else
+ bKeyUsed = false;
+ break;
+
+ case KEY_COMMA :
+ if( bMod1 )
+ SelAllDestrAnch( false );
+ else
+ bKeyUsed = false;
+ break;
+
+ case KEY_HOME :
+ pNewCursor = m_pView->GetModel()->First();
+
+ while( pNewCursor && !IsSelectable(pNewCursor) )
+ {
+ pNewCursor = m_pView->NextVisible(pNewCursor);
+ }
+
+ if( pNewCursor && pNewCursor != m_pCursor )
+ {
+// SelAllDestrAnch( false );
+ m_aSelEng.CursorPosChanging( bShift, bMod1 );
+ SetCursor( pNewCursor );
+ if( !IsEntryInView( pNewCursor ) )
+ MakeVisible( pNewCursor );
+ }
+ else
+ bKeyUsed = false;
+ break;
+
+ case KEY_END :
+ pNewCursor = m_pView->GetModel()->Last();
+
+ while( pNewCursor && !IsSelectable(pNewCursor) )
+ {
+ pNewCursor = m_pView->PrevVisible(pNewCursor);
+ }
+
+ if( pNewCursor && pNewCursor != m_pCursor)
+ {
+// SelAllDestrAnch( false );
+ m_aSelEng.CursorPosChanging( bShift, bMod1 );
+ SetCursor( pNewCursor );
+ if( !IsEntryInView( pNewCursor ) )
+ MakeVisible( pNewCursor );
+ }
+ else
+ bKeyUsed = false;
+ break;
+
+ case KEY_ESCAPE:
+ case KEY_TAB:
+ case KEY_DELETE:
+ case KEY_BACKSPACE:
+ // must not be handled because this quits dialogs and does other magic things...
+ // if there are other single keys which should not be handled, they can be added here
+ bKeyUsed = false;
+ break;
+
+ default:
+ // is there any reason why we should eat the events here? The only place where this is called
+ // is from SvTreeListBox::KeyInput. If we set bKeyUsed to true here, then the key input
+ // is just silenced. However, we want SvLBox::KeyInput to get a chance, to do the QuickSelection
+ // handling.
+ // (The old code here which intentionally set bKeyUsed to sal_True said this was because of "quick search"
+ // handling, but actually there was no quick search handling anymore. We just re-implemented it.)
+ // #i31275# / 2009-06-16 / frank.schoenheit@sun.com
+ bKeyUsed = false;
+ break;
+ }
+ return bKeyUsed;
+}
+
+void SvImpLBox::GetFocus()
+{
+ if( m_pCursor )
+ {
+ m_pView->SetEntryFocus( m_pCursor, true );
+ ShowCursor( true );
+// auskommentiert wg. deselectall
+// if( bSimpleTravel && !pView->IsSelected(pCursor) )
+// pView->Select( pCursor, true );
+ }
+ if( m_nStyle & WB_HIDESELECTION )
+ {
+ SvTreeListEntry* pEntry = m_pView->FirstSelected();
+ while( pEntry )
+ {
+ InvalidateEntry( pEntry );
+ pEntry = m_pView->NextSelected( pEntry );
+ }
+ }
+}
+
+void SvImpLBox::LoseFocus()
+{
+ m_aEditIdle.Stop();
+ if( m_pCursor )
+ m_pView->SetEntryFocus( m_pCursor,false );
+ ShowCursor( false );
+
+ if( m_nStyle & WB_HIDESELECTION )
+ {
+ SvTreeListEntry* pEntry = m_pView ? m_pView->FirstSelected() : nullptr;
+ while( pEntry )
+ {
+ InvalidateEntry( pEntry );
+ pEntry = m_pView->NextSelected( pEntry );
+ }
+ }
+}
+
+
+// ********************************************************************
+// SelectionEngine
+// ********************************************************************
+
+void SvImpLBox::SelectEntry( SvTreeListEntry* pEntry, bool bSelect )
+{
+ m_pView->Select( pEntry, bSelect );
+}
+
+ImpLBSelEng::ImpLBSelEng( SvImpLBox* pImpl, SvTreeListBox* pV )
+{
+ pImp = pImpl;
+ pView = pV;
+}
+
+ImpLBSelEng::~ImpLBSelEng()
+{
+}
+
+void ImpLBSelEng::BeginDrag()
+{
+ pImp->BeginDrag();
+}
+
+void ImpLBSelEng::CreateAnchor()
+{
+ pImp->m_pAnchor = pImp->m_pCursor;
+}
+
+void ImpLBSelEng::DestroyAnchor()
+{
+ pImp->m_pAnchor = nullptr;
+}
+
+void ImpLBSelEng::SetCursorAtPoint(const Point& rPoint, bool bDontSelectAtCursor)
+{
+ SvTreeListEntry* pNewCursor = pImp->MakePointVisible( rPoint );
+ if( pNewCursor != pImp->m_pCursor )
+ pImp->BeginScroll();
+
+ 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) )
+ {
+ 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;
+ BeginScroll();
+ m_pView->StartDrag( 0, m_aSelEng.GetMousePosPixel() );
+ EndScroll();
+}
+
+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);
+#else
+ InvalidateEntry(pEntry);
+#endif
+ }
+}
+
+void SvImpLBox::Command( const CommandEvent& rCEvt )
+{
+ CommandEventId nCommand = rCEvt.GetCommand();
+
+ if( nCommand == CommandEventId::ContextMenu )
+ m_aEditIdle.Stop();
+
+ // scroll mouse event?
+ if (nCommand == CommandEventId::Wheel ||
+ nCommand == CommandEventId::StartAutoScroll ||
+ nCommand == CommandEventId::AutoScroll ||
+ nCommand == CommandEventId::Gesture)
+ {
+ if (m_pView->HandleScrollCommand(rCEvt, m_aHorSBar.get(), m_aVerSBar.get()))
+ return;
+ }
+
+ if( m_bContextMenuHandling && nCommand == CommandEventId::ContextMenu )
+ {
+ Point aPopupPos;
+ bool bClickedIsFreePlace = false;
+ std::stack<SvTreeListEntry*> aSelRestore;
+
+ if( rCEvt.IsMouseEvent() )
+ { // change selection, if mouse position doesn't fit to selection
+
+ aPopupPos = rCEvt.GetMousePosPixel();
+
+ SvTreeListEntry* pClickedEntry = GetEntry( aPopupPos );
+ if( pClickedEntry )
+ { // mouse in non empty area
+ bool bClickedIsSelected = false;
+
+ // collect the currently selected entries
+ SvTreeListEntry* pSelected = m_pView->FirstSelected();
+ while( pSelected )
+ {
+ bClickedIsSelected |= ( pClickedEntry == pSelected );
+ pSelected = m_pView->NextSelected( pSelected );
+ }
+
+ // if the entry which the user clicked at is not selected
+ if( !bClickedIsSelected )
+ { // deselect all other and select the clicked one
+ m_pView->SelectAll( false );
+ m_pView->SetCursor( pClickedEntry );
+ }
+ }
+ else if( m_aSelEng.GetSelectionMode() == SelectionMode::Single )
+ {
+ bClickedIsFreePlace = true;
+ sal_Int32 nSelectedEntries = m_pView->GetSelectionCount();
+ SvTreeListEntry* pSelected = m_pView->FirstSelected();
+ for(sal_Int32 nSel = 0; nSel < nSelectedEntries; nSel++ )
+ {
+ aSelRestore.push(pSelected);
+ pSelected = m_pView->NextSelected( pSelected );
+ }
+ m_pView->SelectAll( false );
+ }
+ else
+ { // deselect all
+ m_pView->SelectAll( false );
+ }
+
+
+ }
+ else
+ { // key event (or at least no mouse event)
+ sal_Int32 nSelectionCount = m_pView->GetSelectionCount();
+
+ if( nSelectionCount )
+ { // now always take first visible as base for positioning the menu
+ SvTreeListEntry* pSelected = m_pView->FirstSelected();
+ while( pSelected )
+ {
+ if( IsEntryInView( pSelected ) )
+ break;
+
+ pSelected = m_pView->NextSelected( pSelected );
+ }
+
+ if( !pSelected )
+ {
+ // no one was visible
+ pSelected = m_pView->FirstSelected();
+ m_pView->MakeVisible( pSelected );
+ }
+
+ aPopupPos = m_pView->GetFocusRect( pSelected, m_pView->GetEntryPosition( pSelected ).Y() ).Center();
+ }
+ else
+ aPopupPos = Point( 0, 0 );
+ }
+
+ {
+ VclPtr<PopupMenu> pPopup = m_pView->CreateContextMenu();
+ if (pPopup)
+ {
+ // do action for selected entry in popup menu
+ sal_uInt16 nMenuAction = pPopup->Execute( m_pView, aPopupPos );
+ if ( nMenuAction )
+ m_pView->ExecuteContextMenuAction( nMenuAction );
+ pPopup.disposeAndClear();
+ }
+ }
+
+ if( bClickedIsFreePlace )
+ {
+ while(!aSelRestore.empty())
+ {
+ SvTreeListEntry* pEntry = aSelRestore.top();
+ //#i19717# the entry is maybe already deleted
+ bool bFound = false;
+ for(sal_uLong nEntry = 0; nEntry < m_pView->GetEntryCount(); nEntry++)
+ if(pEntry == m_pView->GetEntry(nEntry))
+ {
+ bFound = true;
+ break;
+ }
+ if(bFound)
+ SetCurEntry( pEntry );
+ aSelRestore.pop();
+ }
+ }
+ }
+ else
+ {
+ const Point& rPos = rCEvt.GetMousePosPixel();
+ if( rPos.X() < m_aOutputSize.Width() && rPos.Y() < m_aOutputSize.Height() )
+ m_aSelEng.Command( rCEvt );
+ }
+}
+
+void SvImpLBox::BeginScroll()
+{
+ if( !(m_nFlags & LBoxFlags::InScrolling))
+ {
+ m_nFlags |= LBoxFlags::InScrolling;
+ }
+}
+
+void SvImpLBox::EndScroll()
+{
+ if( m_nFlags & LBoxFlags::InScrolling)
+ {
+ m_pView->NotifyEndScroll();
+ m_nFlags &= ~LBoxFlags::InScrolling;
+ }
+}
+
+
+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->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().IsInside( 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.IsInside( 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::EndSelection()
+{
+ DestroyAnchor();
+ m_nFlags &= ~LBoxFlags::StartEditTimer;
+}
+
+void SvImpLBox::SetUpdateMode( bool bMode )
+{
+ if( m_bUpdateMode != bMode )
+ {
+ m_bUpdateMode = bMode;
+ if( m_bUpdateMode )
+ UpdateAll( false );
+ }
+}
+
+bool 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 )
+ {
+ if( nLastItem < nLastTab )
+ nLastTab = nLastItem;
+
+ SvLBoxTab* pTab = m_pView->aTabs[ nLastTab ].get();
+ SvLBoxItem& rItem = pEntry->GetItem( nLastTab );
+
+ long nTabPos = m_pView->GetTabPos( pEntry, pTab );
+
+ long nMaxRight = GetOutputSize().Width();
+ Point aPos( m_pView->GetMapMode().GetOrigin() );
+ aPos.setX( aPos.X() * -1 ); // conversion document coordinates
+ nMaxRight = nMaxRight + aPos.X() - 1;
+
+ long nNextTab = nTabPos < nMaxRight ? nMaxRight : nMaxRight + 50;
+ long nTabWidth = nNextTab - nTabPos + 1;
+ auto nItemSize = rItem.GetWidth(m_pView,pEntry);
+ long nOffset = pTab->CalcOffset( nItemSize, nTabWidth );
+
+ long nRight = nTabPos + nOffset + nItemSize;
+ if( nRight > m_nMostRight )
+ {
+ m_nMostRight = nRight;
+ m_pMostRightEntry = pEntry;
+ return true;
+ }
+ }
+ return false;
+}
+
+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::ShowFocusRect( const SvTreeListEntry* pEntry )
+{
+ if( pEntry )
+ {
+ long nY = GetEntryLine(pEntry);
+ tools::Rectangle aRect = m_pView->GetFocusRect(pEntry, nY);
+ vcl::Region aOldClip( m_pView->GetClipRegion());
+ vcl::Region aClipRegion( GetClipRegionRect() );
+ m_pView->SetClipRegion( aClipRegion );
+ m_pView->ShowFocus( aRect );
+ m_pView->SetClipRegion( aOldClip );
+
+ }
+ else
+ {
+ m_pView->HideFocus();
+ }
+}
+
+
+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 )
+{
+ if( pEntry )
+ {
+ SvViewDataEntry* pViewDataNewCur = m_pView->GetViewDataEntry(pEntry);
+ return (pViewDataNewCur == nullptr) || pViewDataNewCur->IsSelectable();
+ }
+ else
+ {
+ return false;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/treelist/svlbitm.cxx b/vcl/source/treelist/svlbitm.cxx
new file mode 100644
index 000000000..da5074283
--- /dev/null
+++ b/vcl/source/treelist/svlbitm.cxx
@@ -0,0 +1,529 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/treelistbox.hxx>
+#include <vcl/svlbitm.hxx>
+#include <vcl/treelistentry.hxx>
+#include <vcl/viewdataentry.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/toolkit/button.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/salnativewidgets.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 )
+ : pImpl( new SvLBoxButtonData_Impl )
+{
+ InitData( false, pControlForSettings );
+}
+
+SvLBoxButtonData::SvLBoxButtonData( const Control* pControlForSettings, bool _bRadioBtn )
+ : pImpl( new SvLBoxButtonData_Impl )
+{
+ InitData( _bRadioBtn, pControlForSettings );
+}
+
+SvLBoxButtonData::~SvLBoxButtonData()
+{
+}
+
+void SvLBoxButtonData::CallLink()
+{
+ aLink.Call( this );
+}
+
+SvBmp SvLBoxButtonData::GetIndex( SvItemStateFlags nItemState )
+{
+ SvBmp nIdx;
+ if (nItemState == SvItemStateFlags::UNCHECKED)
+ nIdx = SvBmp::UNCHECKED;
+ else if (nItemState == SvItemStateFlags::CHECKED)
+ nIdx = SvBmp::CHECKED;
+ else if (nItemState == SvItemStateFlags::TRISTATE)
+ nIdx = SvBmp::TRISTATE;
+ else if (nItemState == (SvItemStateFlags::UNCHECKED | SvItemStateFlags::HILIGHTED))
+ nIdx = SvBmp::HIUNCHECKED;
+ else if (nItemState == (SvItemStateFlags::CHECKED | SvItemStateFlags::HILIGHTED))
+ nIdx = SvBmp::HICHECKED;
+ else if (nItemState == (SvItemStateFlags::TRISTATE | SvItemStateFlags::HILIGHTED))
+ nIdx = SvBmp::HITRISTATE;
+ else
+ nIdx = SvBmp::UNCHECKED;
+ return nIdx;
+}
+
+void SvLBoxButtonData::SetWidthAndHeight()
+{
+ Size aSize = aBmps[int(SvBmp::UNCHECKED)].GetSizePixel();
+ nWidth = aSize.Width();
+ nHeight = aSize.Height();
+ bDataOk = true;
+}
+
+void SvLBoxButtonData::StoreButtonState(SvTreeListEntry* pActEntry, SvLBoxButton* pActBox)
+{
+ pImpl->pEntry = pActEntry;
+ pImpl->pBox = pActBox;
+}
+
+SvButtonState SvLBoxButtonData::ConvertToButtonState( SvItemStateFlags nItemFlags )
+{
+ nItemFlags &= SvItemStateFlags::UNCHECKED |
+ SvItemStateFlags::CHECKED |
+ SvItemStateFlags::TRISTATE;
+ switch( nItemFlags )
+ {
+ case SvItemStateFlags::UNCHECKED:
+ return SvButtonState::Unchecked;
+ case SvItemStateFlags::CHECKED:
+ return SvButtonState::Checked;
+ case SvItemStateFlags::TRISTATE:
+ return SvButtonState::Tristate;
+ default:
+ return SvButtonState::Unchecked;
+ }
+}
+
+SvTreeListEntry* SvLBoxButtonData::GetActEntry() const
+{
+ assert(pImpl && "-SvLBoxButtonData::GetActEntry(): don't use me that way!");
+ return pImpl->pEntry;
+}
+
+SvLBoxButton* SvLBoxButtonData::GetActBox() const
+{
+ assert(pImpl && "-SvLBoxButtonData::GetActBox(): don't use me that way!");
+ return pImpl->pBox;
+}
+
+void SvLBoxButtonData::SetDefaultImages( const Control* pCtrl )
+{
+ const AllSettings& rSettings = pCtrl? pCtrl->GetSettings() : Application::GetSettings();
+
+ if ( pImpl->bShowRadioButton )
+ {
+ SetImage(SvBmp::UNCHECKED, RadioButton::GetRadioImage( rSettings, DrawButtonFlags::Default ) );
+ SetImage(SvBmp::CHECKED, RadioButton::GetRadioImage( rSettings, DrawButtonFlags::Checked ) );
+ SetImage(SvBmp::HICHECKED, RadioButton::GetRadioImage( rSettings, DrawButtonFlags::Checked | DrawButtonFlags::Pressed ) );
+ SetImage(SvBmp::HIUNCHECKED, RadioButton::GetRadioImage( rSettings, DrawButtonFlags::Default | DrawButtonFlags::Pressed ) );
+ SetImage(SvBmp::TRISTATE, RadioButton::GetRadioImage( rSettings, DrawButtonFlags::DontKnow ) );
+ SetImage(SvBmp::HITRISTATE, RadioButton::GetRadioImage( rSettings, DrawButtonFlags::DontKnow | DrawButtonFlags::Pressed ) );
+ }
+ else
+ {
+ SetImage(SvBmp::UNCHECKED, CheckBox::GetCheckImage( rSettings, DrawButtonFlags::Default ) );
+ SetImage(SvBmp::CHECKED, CheckBox::GetCheckImage( rSettings, DrawButtonFlags::Checked ) );
+ SetImage(SvBmp::HICHECKED, CheckBox::GetCheckImage( rSettings, DrawButtonFlags::Checked | DrawButtonFlags::Pressed ) );
+ SetImage(SvBmp::HIUNCHECKED, CheckBox::GetCheckImage( rSettings, DrawButtonFlags::Default | DrawButtonFlags::Pressed ) );
+ SetImage(SvBmp::TRISTATE, CheckBox::GetCheckImage( rSettings, DrawButtonFlags::DontKnow ) );
+ SetImage(SvBmp::HITRISTATE, CheckBox::GetCheckImage( rSettings, DrawButtonFlags::DontKnow | DrawButtonFlags::Pressed ) );
+ }
+}
+
+bool SvLBoxButtonData::HasDefaultImages() const
+{
+ return pImpl->bDefaultImages;
+}
+
+bool SvLBoxButtonData::IsRadio() const {
+ return pImpl->bShowRadioButton;
+}
+
+// ***************************************************************
+// class SvLBoxString
+// ***************************************************************
+
+
+SvLBoxString::SvLBoxString(const OUString& rStr)
+ : mbEmphasized(false)
+ , mbCustom(false)
+ , mfAlign(0.0)
+ , maText(rStr)
+{
+}
+
+SvLBoxString::SvLBoxString()
+ : mbEmphasized(false)
+ , mbCustom(false)
+ , mfAlign(0.0)
+{
+}
+
+SvLBoxString::~SvLBoxString()
+{
+}
+
+SvLBoxItemType SvLBoxString::GetType() const
+{
+ return SvLBoxItemType::String;
+}
+
+void SvLBoxString::Paint(
+ const Point& rPos, SvTreeListBox& rDev, vcl::RenderContext& rRenderContext,
+ const SvViewDataEntry* /*pView*/, const SvTreeListEntry& rEntry)
+{
+ Size aSize;
+ DrawTextFlags nStyle = (rDev.IsEnabled() && !mbDisabled) ? DrawTextFlags::NONE : DrawTextFlags::Disable;
+ if (rDev.IsEntryMnemonicsEnabled())
+ nStyle |= DrawTextFlags::Mnemonic;
+ if (rDev.TextCenterAndClipEnabled())
+ {
+ nStyle |= DrawTextFlags::PathEllipsis | DrawTextFlags::Center;
+ aSize.setWidth( rDev.GetEntryWidth() );
+ }
+ else
+ {
+ if (mfAlign < 0.5 )
+ {
+ nStyle |= DrawTextFlags::Left;
+ aSize.setWidth(GetWidth(&rDev, &rEntry));
+ }
+ else if (mfAlign == 0.5)
+ {
+ nStyle |= DrawTextFlags::Center;
+ aSize.setWidth(rDev.GetBoundingRect(&rEntry).getWidth());
+ }
+ else if (mfAlign > 0.5)
+ {
+ nStyle |= DrawTextFlags::Right;
+ aSize.setWidth(rDev.GetBoundingRect(&rEntry).getWidth());
+ }
+ }
+ aSize.setHeight(GetHeight(&rDev, &rEntry));
+
+ if (mbEmphasized)
+ {
+ rRenderContext.Push();
+ vcl::Font aFont(rRenderContext.GetFont());
+ aFont.SetWeight(WEIGHT_BOLD);
+ rRenderContext.SetFont(aFont);
+ }
+
+ tools::Rectangle aRect(rPos, aSize);
+
+ if (mbCustom)
+ rDev.DrawCustomEntry(rRenderContext, aRect, rEntry);
+ else
+ rRenderContext.DrawText(aRect, maText, nStyle);
+
+ if (mbEmphasized)
+ rRenderContext.Pop();
+}
+
+std::unique_ptr<SvLBoxItem> SvLBoxString::Clone(SvLBoxItem const * pSource) const
+{
+ std::unique_ptr<SvLBoxString> pNew(new SvLBoxString);
+
+ const SvLBoxString* pOther = static_cast<const SvLBoxString*>(pSource);
+ pNew->maText = pOther->maText;
+ pNew->mbEmphasized = pOther->mbEmphasized;
+ pNew->mbCustom = pOther->mbCustom;
+ pNew->mfAlign = pOther->mfAlign;
+
+ return std::unique_ptr<SvLBoxItem>(pNew.release());
+}
+
+void SvLBoxString::InitViewData(
+ SvTreeListBox* pView, SvTreeListEntry* pEntry, SvViewDataItem* pViewData)
+{
+ if( !pViewData )
+ pViewData = pView->GetViewDataItem( pEntry, this );
+
+ if (mbEmphasized)
+ {
+ pView->Push();
+ vcl::Font aFont( pView->GetFont());
+ aFont.SetWeight(WEIGHT_BOLD);
+ pView->Control::SetFont( aFont );
+ }
+
+ if (mbCustom)
+ {
+ Size aSize = pView->MeasureCustomEntry(*pView, *pEntry);
+ pViewData->mnWidth = aSize.Width();
+ pViewData->mnHeight = aSize.Height();
+ }
+ else
+ {
+ pViewData->mnWidth = -1; // calc on demand
+ pViewData->mnHeight = pView->GetTextHeight();
+ }
+
+ if (mbEmphasized)
+ pView->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()
+ : SvLBoxItem()
+ , isVis(false)
+ , pData(nullptr)
+ , nItemFlags(SvItemStateFlags::NONE)
+{
+ SetStateUnchecked();
+}
+
+SvLBoxButton::~SvLBoxButton()
+{
+}
+
+SvLBoxItemType SvLBoxButton::GetType() const
+{
+ return SvLBoxItemType::Button;
+}
+
+void SvLBoxButton::ClickHdl( SvTreeListEntry* pEntry )
+{
+ if ( IsStateChecked() )
+ SetStateUnchecked();
+ else
+ SetStateChecked();
+ pData->StoreButtonState(pEntry, this);
+ pData->CallLink();
+}
+
+void SvLBoxButton::Paint(
+ const Point& rPos, SvTreeListBox& rDev, vcl::RenderContext& rRenderContext,
+ const SvViewDataEntry* /*pView*/, const SvTreeListEntry& /*rEntry*/)
+{
+ SvBmp nIndex = SvLBoxButtonData::GetIndex(nItemFlags);
+ DrawImageFlags nStyle = rDev.IsEnabled() ? DrawImageFlags::NONE : DrawImageFlags::Disable;
+
+ //Native drawing
+ bool bNativeOK = false;
+ ControlType eCtrlType = (pData->IsRadio())? ControlType::Radiobutton : ControlType::Checkbox;
+ if ( rRenderContext.IsNativeControlSupported( eCtrlType, ControlPart::Entire) )
+ {
+ Size aSize(pData->Width(), pData->Height());
+ ImplAdjustBoxSize(aSize, eCtrlType, rRenderContext);
+ ImplControlValue aControlValue;
+ tools::Rectangle aCtrlRegion( rPos, aSize );
+ ControlState nState = ControlState::NONE;
+
+ //states ControlState::DEFAULT, ControlState::PRESSED and ControlState::ROLLOVER are not implemented
+ if (IsStateHilighted())
+ nState |= ControlState::FOCUSED;
+ if (nStyle != DrawImageFlags::Disable)
+ nState |= ControlState::ENABLED;
+ if (IsStateChecked())
+ aControlValue.setTristateVal(ButtonValue::On);
+ else if (IsStateUnchecked())
+ aControlValue.setTristateVal(ButtonValue::Off);
+ else if (IsStateTristate())
+ aControlValue.setTristateVal( ButtonValue::Mixed );
+
+ if (isVis)
+ bNativeOK = rRenderContext.DrawNativeControl(eCtrlType, ControlPart::Entire,
+ aCtrlRegion, nState, aControlValue, OUString());
+ }
+
+ if (!bNativeOK && isVis)
+ rRenderContext.DrawImage(rPos, pData->GetImage(nIndex), nStyle);
+}
+
+std::unique_ptr<SvLBoxItem> SvLBoxButton::Clone(SvLBoxItem const * pSource) const
+{
+ std::unique_ptr<SvLBoxButton> pNew(new SvLBoxButton);
+ pNew->pData = static_cast<SvLBoxButton const *>(pSource)->pData;
+ return std::unique_ptr<SvLBoxItem>(pNew.release());
+}
+
+void SvLBoxButton::ImplAdjustBoxSize(Size& io_rSize, ControlType i_eType, vcl::RenderContext const & rRenderContext)
+{
+ if (!rRenderContext.IsNativeControlSupported( i_eType, ControlPart::Entire) )
+ return;
+
+ ImplControlValue aControlValue;
+ tools::Rectangle aCtrlRegion( Point( 0, 0 ), io_rSize );
+
+ aControlValue.setTristateVal( ButtonValue::On );
+
+ tools::Rectangle aNativeBounds, aNativeContent;
+ bool bNativeOK = rRenderContext.GetNativeControlRegion( i_eType,
+ ControlPart::Entire,
+ aCtrlRegion,
+ ControlState::ENABLED,
+ aControlValue,
+ aNativeBounds,
+ aNativeContent );
+ if( bNativeOK )
+ {
+ Size aContentSize( aNativeContent.GetSize() );
+ // leave a little space around the box image (looks better)
+ if( aContentSize.Height() + 2 > io_rSize.Height() )
+ io_rSize.setHeight( aContentSize.Height() + 2 );
+ if( aContentSize.Width() + 2 > io_rSize.Width() )
+ io_rSize.setWidth( aContentSize.Width() + 2 );
+ }
+}
+
+void SvLBoxButton::InitViewData(SvTreeListBox* pView,SvTreeListEntry* pEntry, SvViewDataItem* pViewData)
+{
+ if( !pViewData )
+ pViewData = pView->GetViewDataItem( pEntry, this );
+ Size aSize( pData->Width(), pData->Height() );
+
+ ControlType eCtrlType = (pData->IsRadio())? ControlType::Radiobutton : ControlType::Checkbox;
+ if ( pView )
+ ImplAdjustBoxSize(aSize, eCtrlType, *pView);
+ 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()
+ :SvLBoxItem( )
+ ,m_pImpl( new SvLBoxContextBmp_Impl )
+{
+ m_pImpl->m_bExpanded = false;
+}
+
+SvLBoxContextBmp::~SvLBoxContextBmp()
+{
+}
+
+SvLBoxItemType SvLBoxContextBmp::GetType() const
+{
+ return SvLBoxItemType::ContextBmp;
+}
+
+void SvLBoxContextBmp::SetModeImages( const Image& _rBitmap1, const Image& _rBitmap2 )
+{
+ m_pImpl->m_aImage1 = _rBitmap1;
+ m_pImpl->m_aImage2 = _rBitmap2;
+}
+
+Image& SvLBoxContextBmp::implGetImageStore( bool _bFirst )
+{
+
+ // OJ: #i27071# wrong mode so we just return the normal images
+ return _bFirst ? m_pImpl->m_aImage1 : m_pImpl->m_aImage2;
+}
+
+void SvLBoxContextBmp::InitViewData( SvTreeListBox* pView,SvTreeListEntry* pEntry,
+ SvViewDataItem* pViewData)
+{
+ if( !pViewData )
+ pViewData = pView->GetViewDataItem( pEntry, this );
+ Size aSize = m_pImpl->m_aImage1.GetSizePixel();
+ pViewData->mnWidth = aSize.Width();
+ pViewData->mnHeight = aSize.Height();
+}
+
+void SvLBoxContextBmp::Paint(
+ const Point& _rPos, SvTreeListBox& _rDev, vcl::RenderContext& rRenderContext,
+ const SvViewDataEntry* pView, const SvTreeListEntry& rEntry)
+{
+
+ // get the image.
+ const Image& rImage = implGetImageStore(pView->IsExpanded() != m_pImpl->m_bExpanded);
+
+ bool _bSemiTransparent = bool( SvTLEntryFlags::SEMITRANSPARENT & rEntry.GetFlags( ) );
+ // draw
+ DrawImageFlags nStyle = _rDev.IsEnabled() ? DrawImageFlags::NONE : DrawImageFlags::Disable;
+ if (_bSemiTransparent)
+ nStyle |= DrawImageFlags::SemiTransparent;
+ rRenderContext.DrawImage(_rPos, rImage, nStyle);
+}
+
+std::unique_ptr<SvLBoxItem> SvLBoxContextBmp::Clone(SvLBoxItem const * pSource) const
+{
+ std::unique_ptr<SvLBoxContextBmp> pNew(new SvLBoxContextBmp);
+ pNew->m_pImpl->m_aImage1 = static_cast< SvLBoxContextBmp const * >( pSource )->m_pImpl->m_aImage1;
+ pNew->m_pImpl->m_aImage2 = static_cast< SvLBoxContextBmp const * >( pSource )->m_pImpl->m_aImage2;
+ pNew->m_pImpl->m_bExpanded = static_cast<SvLBoxContextBmp const *>(pSource)->m_pImpl->m_bExpanded;
+ return std::unique_ptr<SvLBoxItem>(pNew.release());
+}
+
+long SvLBoxButtonData::Width()
+{
+ if ( !bDataOk )
+ SetWidthAndHeight();
+ return nWidth;
+}
+
+long SvLBoxButtonData::Height()
+{
+ if ( !bDataOk )
+ SetWidthAndHeight();
+ return nHeight;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/treelist/svtabbx.cxx b/vcl/source/treelist/svtabbx.cxx
new file mode 100644
index 000000000..7c4b44631
--- /dev/null
+++ b/vcl/source/treelist/svtabbx.cxx
@@ -0,0 +1,1033 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <vcl/svtaccessiblefactory.hxx>
+#include <vcl/accessiblefactory.hxx>
+#include <vcl/toolkit/svtabbx.hxx>
+#include <vcl/headbar.hxx>
+#include <vcl/svlbitm.hxx>
+#include <vcl/treelistentry.hxx>
+#include <unotools/accessiblestatesethelper.hxx>
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+#include <strings.hrc>
+#include <svdata.hxx>
+#include <memory>
+
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::accessibility;
+
+static constexpr SvLBoxTabFlags MYTABMASK =
+ SvLBoxTabFlags::ADJUST_RIGHT | SvLBoxTabFlags::ADJUST_LEFT | SvLBoxTabFlags::ADJUST_CENTER | SvLBoxTabFlags::FORCE;
+
+// SvTreeListBox callback
+
+void SvTabListBox::SetTabs()
+{
+ SvTreeListBox::SetTabs();
+ if( mvTabList.empty() )
+ return;
+
+ DBG_ASSERT(!mvTabList.empty(),"TabList ?");
+
+ // The tree listbox has now inserted its tabs into the list. Now we
+ // fluff up the list with additional tabs and adjust the rightmost tab
+ // of the tree listbox.
+
+ // Picking the rightmost tab.
+ // HACK for the explorer! If ViewParent != 0, the first tab of the tree
+ // listbox is calculated by the tree listbox itself! This behavior is
+ // necessary for ButtonsOnRoot, as the explorer does not know in this
+ // case, which additional offset it needs to add to the tabs in this mode
+ // -- the tree listbox knows that, though!
+ /*
+ if( !pViewParent )
+ {
+ SvLBoxTab* pFirstTab = (SvLBoxTab*)aTabs.GetObject( aTabs.Count()-1 );
+ pFirstTab->SetPos( pTabList[0].GetPos() );
+ pFirstTab->nFlags &= ~MYTABMASK;
+ pFirstTab->nFlags |= pTabList[0].nFlags;
+ }
+ */
+
+ // append all other tabs to the list
+ for( sal_uInt16 nCurTab = 1; nCurTab < sal_uInt16(mvTabList.size()); nCurTab++ )
+ {
+ SvLBoxTab& rTab = mvTabList[nCurTab];
+ AddTab( rTab.GetPos(), rTab.nFlags );
+ }
+}
+
+void SvTabListBox::InitEntry(SvTreeListEntry* pEntry, const OUString& rStr,
+ const Image& rColl, const Image& rExp)
+{
+ SvTreeListBox::InitEntry(pEntry, rStr, rColl, rExp);
+
+ sal_Int32 nIndex = 0;
+ // TODO: verify if nTabCount is always >0 here!
+ const sal_uInt16 nCount = mvTabList.size() - 1;
+ for( sal_uInt16 nToken = 0; nToken < nCount; nToken++ )
+ {
+ const OUString aToken = GetToken(aCurEntry, nIndex);
+ pEntry->AddItem(std::make_unique<SvLBoxString>(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, long const pTabPositions[], MapUnit eMapUnit)
+{
+ mvTabList.resize(nTabs);
+
+ MapMode aMMSource( eMapUnit );
+ MapMode aMMDest( MapUnit::MapPixel );
+
+ for( sal_uInt16 nIdx = 0; nIdx < sal_uInt16(mvTabList.size()); nIdx++, pTabPositions++ )
+ {
+ Size aSize( *pTabPositions, 0 );
+ aSize = LogicToLogic( aSize, &aMMSource, &aMMDest );
+ long nNewTab = aSize.Width();
+ mvTabList[nIdx].SetPos( nNewTab );
+ mvTabList[nIdx].nFlags &= MYTABMASK;
+ }
+ SvTreeListBox::nTreeFlags |= SvTreeFlags::RECALCTABS;
+ if( IsUpdateMode() )
+ Invalidate();
+}
+
+SvTreeListEntry* SvTabListBox::InsertEntry( const OUString& rText, SvTreeListEntry* pParent,
+ bool /*bChildrenOnDemand*/,
+ sal_uLong nPos, void* pUserData )
+{
+ return InsertEntryToColumn( rText, pParent, nPos, 0xffff, pUserData );
+}
+
+SvTreeListEntry* SvTabListBox::InsertEntry( const OUString& rText,
+ const Image& rExpandedEntryBmp,
+ const Image& rCollapsedEntryBmp,
+ SvTreeListEntry* pParent,
+ bool /*bChildrenOnDemand*/,
+ sal_uLong nPos, void* pUserData )
+{
+ return InsertEntryToColumn( rText, rExpandedEntryBmp, rCollapsedEntryBmp,
+ pParent, nPos, 0xffff, pUserData );
+}
+
+SvTreeListEntry* SvTabListBox::InsertEntryToColumn(const OUString& rStr,SvTreeListEntry* pParent,sal_uLong 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 );
+}
+
+SvTreeListEntry* SvTabListBox::InsertEntryToColumn( const OUString& rStr,
+ const Image& rExpandedEntryBmp, const Image& rCollapsedEntryBmp,
+ SvTreeListEntry* pParent,sal_uLong 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,
+ rExpandedEntryBmp, rCollapsedEntryBmp,
+ pParent, false, nPos, pUser );
+}
+
+OUString SvTabListBox::GetEntryText( SvTreeListEntry* pEntry ) const
+{
+ return GetEntryText( pEntry, 0xffff );
+}
+
+OUString SvTabListBox::GetEntryText( 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_uLong nPos, sal_uInt16 nCol ) const
+{
+ SvTreeListEntry* pEntry = GetEntryOnPos( nPos );
+ return GetEntryText( pEntry, nCol );
+}
+
+OUString SvTabListBox::GetCellText( sal_uLong 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_uLong SvTabListBox::GetEntryPos( const SvTreeListEntry* pEntry ) const
+{
+ sal_uLong nPos = 0;
+ SvTreeListEntry* pTmpEntry = First();
+ while( pTmpEntry )
+ {
+ if ( pTmpEntry == pEntry )
+ return nPos;
+ pTmpEntry = Next( pTmpEntry );
+ ++nPos;
+ }
+ return 0xffffffff;
+}
+
+// static
+OUString SvTabListBox::GetToken( const OUString &sStr, sal_Int32& nIndex )
+{
+ return sStr.getToken(0, '\t', nIndex);
+}
+
+OUString SvTabListBox::GetTabEntryText( sal_uLong nPos, sal_uInt16 nCol ) const
+{
+ SvTreeListEntry* pEntry = SvTreeListBox::GetEntry( nPos );
+ DBG_ASSERT( pEntry, "GetTabEntryText(): Invalid entry " );
+ OUStringBuffer aResult;
+ if ( pEntry )
+ {
+ sal_uInt16 nCount = pEntry->ItemCount();
+ sal_uInt16 nCur = 0;
+ while( nCur < nCount )
+ {
+ const SvLBoxItem& rBoxItem = pEntry->GetItem( nCur );
+ if (rBoxItem.GetType() == SvLBoxItemType::String)
+ {
+ if ( nCol == 0xffff )
+ {
+ if (!aResult.isEmpty())
+ aResult.append("\t");
+ aResult.append(static_cast<const SvLBoxString&>(rBoxItem).GetText());
+ }
+ else
+ {
+ if ( nCol == 0 )
+ {
+ OUString sRet = static_cast<const SvLBoxString&>(rBoxItem).GetText();
+ if ( sRet.isEmpty() )
+ sRet = VclResId( STR_SVT_ACC_EMPTY_FIELD );
+ return sRet;
+ }
+ --nCol;
+ }
+ }
+ ++nCur;
+ }
+ }
+ return aResult.makeStringAndClear();
+}
+
+SvTreeListEntry* SvTabListBox::GetEntryOnPos( sal_uLong _nEntryPos ) const
+{
+ SvTreeListEntry* pEntry = nullptr;
+ sal_uLong 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_uLong _nEntryPos, sal_uLong& _rPos ) const
+{
+ sal_uLong 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;
+}
+
+long SvTabListBox::GetLogicTab( sal_uInt16 nTab )
+{
+ if( SvTreeListBox::nTreeFlags & SvTreeFlags::RECALCTABS )
+ SetTabs();
+
+ DBG_ASSERT(nTab<mvTabList.size(),"GetTabPos:Invalid Tab");
+ return aTabs[ nTab ]->GetPos();
+}
+
+namespace vcl
+{
+ struct SvHeaderTabListBoxImpl
+ {
+ VclPtr<HeaderBar> m_pHeaderBar;
+ AccessibleFactoryAccess m_aFactoryAccess;
+
+ SvHeaderTabListBoxImpl() : m_pHeaderBar( nullptr ) { }
+ };
+}
+
+SvHeaderTabListBox::SvHeaderTabListBox( vcl::Window* pParent, WinBits nWinStyle )
+ : SvTabListBox(pParent, nWinStyle)
+ , m_bFirstPaint(true)
+ , m_pImpl(new ::vcl::SvHeaderTabListBoxImpl)
+ , m_pAccessible(nullptr)
+{
+}
+
+SvHeaderTabListBox::~SvHeaderTabListBox()
+{
+ disposeOnce();
+}
+
+void SvHeaderTabListBox::dispose()
+{
+ m_pImpl.reset();
+ SvTabListBox::dispose();
+}
+
+void SvHeaderTabListBox::Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect )
+{
+ if (m_bFirstPaint)
+ {
+ m_bFirstPaint = false;
+ }
+ SvTabListBox::Paint(rRenderContext, rRect);
+}
+
+void SvHeaderTabListBox::InitHeaderBar( HeaderBar* pHeaderBar )
+{
+ DBG_ASSERT( !m_pImpl->m_pHeaderBar, "header bar already initialized" );
+ DBG_ASSERT( pHeaderBar, "invalid header bar initialization" );
+ m_pImpl->m_pHeaderBar = pHeaderBar;
+ SetScrolledHdl( LINK( this, SvHeaderTabListBox, ScrollHdl_Impl ) );
+ m_pImpl->m_pHeaderBar->SetCreateAccessibleHdl( LINK( this, SvHeaderTabListBox, CreateAccessibleHdl_Impl ) );
+}
+
+HeaderBar* SvHeaderTabListBox::GetHeaderBar()
+{
+ return m_pImpl ? m_pImpl->m_pHeaderBar : nullptr;
+}
+
+bool SvHeaderTabListBox::IsItemChecked( SvTreeListEntry* pEntry, sal_uInt16 nCol )
+{
+ SvButtonState eState = SvButtonState::Unchecked;
+ SvLBoxButton& rItem = static_cast<SvLBoxButton&>( pEntry->GetItem( nCol + 1 ) );
+
+ if (rItem.GetType() == SvLBoxItemType::Button)
+ {
+ SvItemStateFlags nButtonFlags = rItem.GetButtonFlags();
+ eState = SvLBoxButtonData::ConvertToButtonState( nButtonFlags );
+ }
+
+ return ( eState == SvButtonState::Checked );
+}
+
+SvTreeListEntry* SvHeaderTabListBox::InsertEntryToColumn(
+ const OUString& rStr, SvTreeListEntry* pParent, sal_uLong nPos, sal_uInt16 nCol, void* pUserData )
+{
+ SvTreeListEntry* pEntry = SvTabListBox::InsertEntryToColumn( rStr, pParent, nPos, nCol, pUserData );
+ RecalculateAccessibleChildren();
+ return pEntry;
+}
+
+SvTreeListEntry* SvHeaderTabListBox::InsertEntryToColumn(
+ const OUString& rStr, const Image& rExpandedEntryBmp, const Image& rCollapsedEntryBmp,
+ SvTreeListEntry* pParent, sal_uLong nPos, sal_uInt16 nCol, void* pUserData )
+{
+ SvTreeListEntry* pEntry = SvTabListBox::InsertEntryToColumn(
+ rStr, rExpandedEntryBmp, rCollapsedEntryBmp, pParent, nPos, nCol, pUserData );
+ RecalculateAccessibleChildren();
+ return pEntry;
+}
+
+sal_uLong SvHeaderTabListBox::Insert(
+ SvTreeListEntry* pEnt, SvTreeListEntry* pPar, sal_uLong nPos )
+{
+ sal_uLong n = SvTabListBox::Insert( pEnt, pPar, nPos );
+ RecalculateAccessibleChildren();
+ return n;
+}
+
+sal_uLong SvHeaderTabListBox::Insert( SvTreeListEntry* pEntry, sal_uLong nRootPos )
+{
+ sal_uLong nPos = SvTabListBox::Insert( pEntry, nRootPos );
+ RecalculateAccessibleChildren();
+ return nPos;
+}
+
+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, ::vcl::BBTYPE_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( long _nRow, sal_uInt16 _nColumn, TriState& _rState )
+{
+ bool bRet = false;
+ SvTreeListEntry* pEntry = GetEntry( _nRow );
+ if ( pEntry )
+ {
+ sal_uInt16 nItemCount = pEntry->ItemCount();
+ if ( nItemCount > ( _nColumn + 1 ) )
+ {
+ SvLBoxItem& rItem = pEntry->GetItem( _nColumn + 1 );
+ if (rItem.GetType() == SvLBoxItemType::Button)
+ {
+ bRet = true;
+ _rState = ( ( static_cast<SvLBoxButton&>(rItem).GetButtonFlags() & SvItemStateFlags::UNCHECKED ) == SvItemStateFlags::NONE )
+ ? TRISTATE_TRUE : TRISTATE_FALSE;
+ }
+ }
+ else
+ {
+ SAL_WARN( "svtools.contnr", "SvHeaderTabListBox::IsCellCheckBox(): column out of range" );
+ }
+ }
+ return bRet;
+}
+long 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_uLong nCount = GetEntryCount();
+ for ( sal_uLong i = 0; i < nCount; ++i )
+ {
+ if ( pEntry == GetEntry(i) )
+ {
+ nRet = i;
+ break;
+ }
+ }
+ }
+
+ return nRet;
+}
+
+sal_uInt16 SvHeaderTabListBox::GetCurrColumn() const
+{
+ return 0;
+}
+
+OUString SvHeaderTabListBox::GetRowDescription( sal_Int32 _nRow ) const
+{
+ return GetEntryText( _nRow );
+}
+
+OUString SvHeaderTabListBox::GetColumnDescription( sal_uInt16 _nColumn ) const
+{
+ return m_pImpl->m_pHeaderBar->GetItemText( m_pImpl->m_pHeaderBar->GetItemId( _nColumn ) );
+}
+
+bool SvHeaderTabListBox::HasRowHeader() const
+{
+ return false;
+}
+
+bool SvHeaderTabListBox::GoToCell( sal_Int32 /*_nRow*/, sal_uInt16 /*_nColumn*/ )
+{
+ return false;
+}
+
+void SvHeaderTabListBox::SetNoSelection()
+{
+ SvTreeListBox::SelectAll(false);
+}
+
+void SvHeaderTabListBox::SelectAll()
+{
+ SvTreeListBox::SelectAll(true);
+}
+
+void SvHeaderTabListBox::SelectRow( long _nRow, bool _bSelect, bool )
+{
+ Select( GetEntry( _nRow ), _bSelect );
+}
+
+void SvHeaderTabListBox::SelectColumn( sal_uInt16, bool )
+{
+}
+
+sal_Int32 SvHeaderTabListBox::GetSelectedRowCount() const
+{
+ return GetSelectionCount();
+}
+
+sal_Int32 SvHeaderTabListBox::GetSelectedColumnCount() const
+{
+ return 0;
+}
+
+bool SvHeaderTabListBox::IsRowSelected( long _nRow ) const
+{
+ SvTreeListEntry* pEntry = GetEntry( _nRow );
+ return ( pEntry && IsSelected( pEntry ) );
+}
+
+bool SvHeaderTabListBox::IsColumnSelected( long ) const
+{
+ return false;
+}
+
+void SvHeaderTabListBox::GetAllSelectedRows( css::uno::Sequence< sal_Int32 >& ) const
+{
+}
+
+void SvHeaderTabListBox::GetAllSelectedColumns( css::uno::Sequence< sal_Int32 >& ) const
+{
+}
+
+bool SvHeaderTabListBox::IsCellVisible( sal_Int32, sal_uInt16 ) const
+{
+ return true;
+}
+
+OUString SvHeaderTabListBox::GetAccessibleCellText( long _nRow, sal_uInt16 _nColumnPos ) const
+{
+ return GetTabEntryText(_nRow, _nColumnPos);
+}
+
+tools::Rectangle SvHeaderTabListBox::calcHeaderRect( bool _bIsColumnBar, bool _bOnScreen )
+{
+ tools::Rectangle aRect;
+ if ( _bIsColumnBar )
+ {
+ vcl::Window* pParent = nullptr;
+ if ( !_bOnScreen )
+ pParent = m_pImpl->m_pHeaderBar->GetAccessibleParentWindow();
+
+ aRect = m_pImpl->m_pHeaderBar->GetWindowExtentsRelative( pParent );
+ }
+ return aRect;
+}
+
+tools::Rectangle SvHeaderTabListBox::calcTableRect( bool _bOnScreen )
+{
+ vcl::Window* pParent = nullptr;
+ if ( !_bOnScreen )
+ pParent = GetAccessibleParentWindow();
+
+ tools::Rectangle aRect( GetWindowExtentsRelative( pParent ) );
+ return aRect;
+}
+
+tools::Rectangle SvHeaderTabListBox::GetFieldRectPixelAbs( sal_Int32 _nRow, sal_uInt16 _nColumn, bool _bIsHeader, bool _bOnScreen )
+{
+ DBG_ASSERT( !_bIsHeader || 0 == _nRow, "invalid parameters" );
+ tools::Rectangle aRect;
+ SvTreeListEntry* pEntry = GetEntry( _nRow );
+ if ( pEntry )
+ {
+ aRect = _bIsHeader ? calcHeaderRect( true, false ) : GetBoundingRect( pEntry );
+ Point aTopLeft = aRect.TopLeft();
+ DBG_ASSERT( m_pImpl->m_pHeaderBar->GetItemCount() > _nColumn, "invalid column" );
+ tools::Rectangle aItemRect = m_pImpl->m_pHeaderBar->GetItemRect( m_pImpl->m_pHeaderBar->GetItemId( _nColumn ) );
+ aTopLeft.setX( aItemRect.Left() );
+ Size aSize = aItemRect.GetSize();
+ aRect = tools::Rectangle( aTopLeft, aSize );
+ vcl::Window* pParent = nullptr;
+ if ( !_bOnScreen )
+ pParent = GetAccessibleParentWindow();
+ aTopLeft = aRect.TopLeft();
+ aTopLeft += GetWindowExtentsRelative( pParent ).TopLeft();
+ aRect = tools::Rectangle( aTopLeft, aRect.GetSize() );
+ }
+
+ return aRect;
+}
+
+Reference< XAccessible > SvHeaderTabListBox::CreateAccessibleCell( sal_Int32 _nRow, sal_uInt16 _nColumnPos )
+{
+ OSL_ENSURE( m_pAccessible, "Invalid call: Accessible is null" );
+
+ Reference< XAccessible > xChild;
+
+ TriState eState = TRISTATE_INDET;
+ bool bIsCheckBox = IsCellCheckBox( _nRow, _nColumnPos, eState );
+ if ( bIsCheckBox )
+ xChild = m_pImpl->m_aFactoryAccess.getFactory().createAccessibleCheckBoxCell(
+ m_pAccessible->getHeaderBar(), *this, nullptr, _nRow, _nColumnPos, eState, false );
+ else
+ xChild = m_pImpl->m_aFactoryAccess.getFactory().createAccessibleBrowseBoxTableCell(
+ m_pAccessible->getHeaderBar(), *this, nullptr, _nRow, _nColumnPos, OFFSET_NONE );
+
+ return xChild;
+}
+
+Reference< XAccessible > SvHeaderTabListBox::CreateAccessibleRowHeader( sal_Int32 )
+{
+ Reference< XAccessible > xHeader;
+ return xHeader;
+}
+
+Reference< XAccessible > SvHeaderTabListBox::CreateAccessibleColumnHeader( sal_uInt16 _nColumn )
+{
+ // first call? -> initial list
+ if ( m_aAccessibleChildren.empty() )
+ {
+ const sal_uInt16 nColumnCount = GetColumnCount();
+ m_aAccessibleChildren.assign( nColumnCount, Reference< XAccessible >() );
+ }
+
+ // get header
+ Reference< XAccessible > xChild = m_aAccessibleChildren[ _nColumn ];
+ // already exists?
+ if ( !xChild.is() && m_pAccessible )
+ {
+ // no -> create new header cell
+ xChild = m_pImpl->m_aFactoryAccess.getFactory().createAccessibleBrowseBoxHeaderCell(
+ _nColumn, m_pAccessible->getHeaderBar(),
+ *this, nullptr, ::vcl::BBTYPE_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( ::vcl::AccessibleBrowseBoxObjType _eType, sal_Int32 _nPos ) const
+{
+ OUString aRetText;
+ switch( _eType )
+ {
+ case ::vcl::BBTYPE_BROWSEBOX:
+ case ::vcl::BBTYPE_TABLE:
+ case ::vcl::BBTYPE_COLUMNHEADERBAR:
+ // should be empty now (see #i63983)
+ aRetText.clear();
+ break;
+
+ case ::vcl::BBTYPE_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 ::vcl::BBTYPE_CHECKBOXCELL:
+ {
+ break; // checkbox cells have no name
+ }
+ case ::vcl::BBTYPE_COLUMNHEADERCELL:
+ {
+ aRetText = m_pImpl->m_pHeaderBar->GetItemText( m_pImpl->m_pHeaderBar->GetItemId( static_cast<sal_uInt16>(_nPos) ) );
+ break;
+ }
+
+ case ::vcl::BBTYPE_ROWHEADERBAR:
+ case ::vcl::BBTYPE_ROWHEADERCELL:
+ aRetText = "error";
+ break;
+
+ default:
+ OSL_FAIL("BrowseBox::GetAccessibleName: invalid enum!");
+ }
+ return aRetText;
+}
+
+OUString SvHeaderTabListBox::GetAccessibleObjectDescription( ::vcl::AccessibleBrowseBoxObjType _eType, sal_Int32 _nPos ) const
+{
+ OUString aRetText;
+
+ if( _eType == ::vcl::BBTYPE_TABLECELL && _nPos != -1 )
+ {
+ const OUString sVar1( "%1" );
+ const OUString sVar2( "%2" );
+
+ 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( sVar1, 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( sVar2, sColHeader );
+ aRetText = aText;
+ }
+ }
+
+ return aRetText;
+}
+
+void SvHeaderTabListBox::FillAccessibleStateSet( ::utl::AccessibleStateSetHelper& _rStateSet, ::vcl::AccessibleBrowseBoxObjType _eType ) const
+{
+ switch( _eType )
+ {
+ case ::vcl::BBTYPE_BROWSEBOX:
+ case ::vcl::BBTYPE_TABLE:
+ {
+ _rStateSet.AddState( AccessibleStateType::FOCUSABLE );
+ if ( HasFocus() )
+ _rStateSet.AddState( AccessibleStateType::FOCUSED );
+ if ( IsActive() )
+ _rStateSet.AddState( AccessibleStateType::ACTIVE );
+ if ( IsEnabled() )
+ {
+ _rStateSet.AddState( AccessibleStateType::ENABLED );
+ _rStateSet.AddState( AccessibleStateType::SENSITIVE );
+ }
+ if ( IsReallyVisible() )
+ _rStateSet.AddState( AccessibleStateType::VISIBLE );
+ if ( _eType == ::vcl::BBTYPE_TABLE )
+ {
+
+ _rStateSet.AddState( AccessibleStateType::MANAGES_DESCENDANTS );
+ _rStateSet.AddState( AccessibleStateType::MULTI_SELECTABLE );
+ }
+ break;
+ }
+
+ case ::vcl::BBTYPE_COLUMNHEADERBAR:
+ {
+ sal_Int32 nCurRow = GetCurrRow();
+ sal_uInt16 nCurColumn = GetCurrColumn();
+ if ( IsCellVisible( nCurRow, nCurColumn ) )
+ _rStateSet.AddState( AccessibleStateType::VISIBLE );
+ if ( IsEnabled() )
+ _rStateSet.AddState( AccessibleStateType::ENABLED );
+ _rStateSet.AddState( AccessibleStateType::TRANSIENT );
+ break;
+ }
+
+ case ::vcl::BBTYPE_ROWHEADERCELL:
+ case ::vcl::BBTYPE_COLUMNHEADERCELL:
+ {
+ _rStateSet.AddState( AccessibleStateType::VISIBLE );
+ _rStateSet.AddState( AccessibleStateType::FOCUSABLE );
+ _rStateSet.AddState( AccessibleStateType::TRANSIENT );
+ if ( IsEnabled() )
+ _rStateSet.AddState( AccessibleStateType::ENABLED );
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void SvHeaderTabListBox::FillAccessibleStateSetForCell( ::utl::AccessibleStateSetHelper& _rStateSet, sal_Int32 _nRow, sal_uInt16 _nColumn ) const
+{
+ _rStateSet.AddState( AccessibleStateType::SELECTABLE );
+ _rStateSet.AddState( AccessibleStateType::TRANSIENT );
+
+ if ( IsCellVisible( _nRow, _nColumn ) )
+ {
+ _rStateSet.AddState( AccessibleStateType::VISIBLE );
+ _rStateSet.AddState( AccessibleStateType::ENABLED );
+ }
+
+ if ( IsRowSelected( _nRow ) )
+ {
+ _rStateSet.AddState( AccessibleStateType::ACTIVE );
+ _rStateSet.AddState( AccessibleStateType::SELECTED );
+ }
+ if ( IsEnabled() )
+ _rStateSet.AddState( AccessibleStateType::ENABLED );
+}
+
+void SvHeaderTabListBox::GrabTableFocus()
+{
+ GrabFocus();
+}
+
+bool SvHeaderTabListBox::GetGlyphBoundRects( const Point& rOrigin, const OUString& rStr, int nIndex, int nLen, MetricVector& rVector )
+{
+ return Control::GetGlyphBoundRects( rOrigin, rStr, nIndex, nLen, rVector );
+}
+
+tools::Rectangle SvHeaderTabListBox::GetWindowExtentsRelative( vcl::Window *pRelativeWindow ) const
+{
+ return Control::GetWindowExtentsRelative( pRelativeWindow );
+}
+
+void SvHeaderTabListBox::GrabFocus()
+{
+ Control::GrabFocus();
+}
+
+Reference< XAccessible > SvHeaderTabListBox::GetAccessible()
+{
+ return Control::GetAccessible();
+}
+
+vcl::Window* SvHeaderTabListBox::GetAccessibleParentWindow() const
+{
+ return Control::GetAccessibleParentWindow();
+}
+
+vcl::Window* SvHeaderTabListBox::GetWindowInstance()
+{
+ return this;
+}
+
+Reference< XAccessible > SvHeaderTabListBox::CreateAccessible()
+{
+ vcl::Window* pParent = GetAccessibleParentWindow();
+ DBG_ASSERT( pParent, "SvHeaderTabListBox::::CreateAccessible - accessible parent not found" );
+
+ Reference< XAccessible > xAccessible;
+ if ( m_pAccessible ) xAccessible = m_pAccessible->getMyself();
+
+ if( pParent && !m_pAccessible )
+ {
+ Reference< XAccessible > xAccParent = pParent->GetAccessible();
+ if ( xAccParent.is() )
+ {
+ m_pAccessible = m_pImpl->m_aFactoryAccess.getFactory().createAccessibleTabListBox( xAccParent, *this );
+ if ( m_pAccessible )
+ xAccessible = m_pAccessible->getMyself();
+ }
+ }
+ return xAccessible;
+}
+
+tools::Rectangle SvHeaderTabListBox::GetFieldCharacterBounds(sal_Int32,sal_Int32,sal_Int32)
+{
+ return tools::Rectangle();
+}
+
+sal_Int32 SvHeaderTabListBox::GetFieldIndexAtPoint(sal_Int32 _nRow,sal_Int32 _nColumnPos,const Point& _rPoint)
+{
+ OUString sText = GetAccessibleCellText( _nRow, static_cast< sal_uInt16 >( _nColumnPos ) );
+ MetricVector aRects;
+ if ( GetGlyphBoundRects(Point(0,0), sText, 0, sText.getLength(), aRects) )
+ {
+ sal_Int32 nPos = 0;
+ for (auto const& rectangle : aRects)
+ {
+ if( rectangle.IsInside(_rPoint) )
+ return nPos;
+ ++nPos;
+ }
+ }
+
+ return -1;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/treelist/transfer.cxx b/vcl/source/treelist/transfer.cxx
new file mode 100644
index 000000000..eefc22e8b
--- /dev/null
+++ b/vcl/source/treelist/transfer.cxx
@@ -0,0 +1,2264 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <osl/mutex.hxx>
+#include <rtl/uri.hxx>
+#include <sal/log.hxx>
+#include <tools/debug.hxx>
+#include <tools/urlobj.hxx>
+#include <unotools/ucbstreamhelper.hxx>
+#include <sot/exchange.hxx>
+#include <sot/storage.hxx>
+#include <vcl/bitmap.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/cvtgrf.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/window.hxx>
+#include <comphelper/fileformat.h>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/servicehelper.hxx>
+#include <comphelper/sequence.hxx>
+#include <sot/filelist.hxx>
+#include <cppuhelper/implbase.hxx>
+
+#include <comphelper/seqstream.hxx>
+#include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardNotifier.hpp>
+#include <com/sun/star/datatransfer/clipboard/XFlushableClipboard.hpp>
+#include <com/sun/star/datatransfer/MimeContentTypeFactory.hpp>
+#include <com/sun/star/datatransfer/XMimeContentType.hpp>
+#include <com/sun/star/datatransfer/XTransferable2.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+
+#include <svl/urlbmk.hxx>
+#include <vcl/inetimg.hxx>
+#include <vcl/wmf.hxx>
+#include <vcl/imap.hxx>
+#include <vcl/transfer.hxx>
+#include <rtl/strbuf.hxx>
+#include <cstdio>
+#include <vcl/dibtools.hxx>
+#include <vcl/pngread.hxx>
+#include <vcl/pngwrite.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <memory>
+#include <utility>
+
+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;
+
+
+#define TOD_SIG1 0x01234567
+#define TOD_SIG2 0x89abcdef
+
+SvStream& WriteTransferableObjectDescriptor( SvStream& rOStm, const TransferableObjectDescriptor& rObjDesc )
+{
+ const sal_uInt32 nFirstPos = rOStm.Tell(), nViewAspect = rObjDesc.mnViewAspect;
+ const sal_uInt32 nSig1 = TOD_SIG1, nSig2 = TOD_SIG2;
+
+ rOStm.SeekRel( 4 );
+ WriteSvGlobalName( rOStm, rObjDesc.maClassName );
+ rOStm.WriteUInt32( nViewAspect );
+ rOStm.WriteInt32( rObjDesc.maSize.Width() );
+ rOStm.WriteInt32( rObjDesc.maSize.Height() );
+ rOStm.WriteInt32( rObjDesc.maDragStartPos.X() );
+ rOStm.WriteInt32( rObjDesc.maDragStartPos.Y() );
+ rOStm.WriteUniOrByteString( rObjDesc.maTypeName, osl_getThreadTextEncoding() );
+ rOStm.WriteUniOrByteString( rObjDesc.maDisplayName, osl_getThreadTextEncoding() );
+ rOStm.WriteUInt32( nSig1 ).WriteUInt32( nSig2 );
+
+ const sal_uInt32 nLastPos = rOStm.Tell();
+
+ rOStm.Seek( nFirstPos );
+ rOStm.WriteUInt32( nLastPos - nFirstPos );
+ rOStm.Seek( nLastPos );
+
+ return rOStm;
+}
+
+
+// the reading of the parameter is done using the special service css::datatransfer::MimeContentType,
+// a similar approach should be implemented for creation of the mimetype string;
+// for now the set of acceptable characters has to be hardcoded, in future it should be part of the service that creates the mimetype
+
+static OUString ImplGetParameterString( const TransferableObjectDescriptor& rObjDesc )
+{
+ const OUString aClassName( rObjDesc.maClassName.GetHexName() );
+ OUString aParams;
+
+ if( !aClassName.isEmpty() )
+ {
+ aParams += ";classname=\"" + aClassName + "\"";
+ }
+
+ if( !rObjDesc.maTypeName.isEmpty() )
+ {
+ aParams += ";typename=\"" + rObjDesc.maTypeName + "\"";
+ }
+
+ if( !rObjDesc.maDisplayName.isEmpty() )
+ {
+ // the display name might contain unacceptable characters, encode all of them
+ // this seems to be the only parameter currently that might contain such characters
+ sal_Bool pToAccept[128];
+ for (sal_Bool & rb : pToAccept)
+ rb = false;
+
+ const char aQuotedParamChars[] =
+ "()<>@,;:/[]?=!#$&'*+-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz{|}~. ";
+
+ for ( sal_Int32 nInd = 0; nInd < RTL_CONSTASCII_LENGTH(aQuotedParamChars); ++nInd )
+ {
+ sal_Unicode nChar = aQuotedParamChars[nInd];
+ if ( nChar < 128 )
+ pToAccept[nChar] = true;
+ }
+
+ aParams += ";displayname=\""
+ + rtl::Uri::encode(
+ rObjDesc.maDisplayName, pToAccept, 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() )
+ {
+ const OUString aClassNameString( "classname" );
+ const OUString aTypeNameString( "typename" );
+ const OUString aDisplayNameString( "displayname" );
+ const OUString aViewAspectString( "viewaspect" );
+ const OUString aWidthString( "width" );
+ const OUString aHeightString( "height" );
+ const OUString aPosXString( "posx" );
+ const OUString aPosYString( "posy" );
+
+ if( xMimeType->hasParameter( aClassNameString ) )
+ {
+ rObjDesc.maClassName.MakeId( xMimeType->getParameterValue( aClassNameString ) );
+ }
+
+ if( xMimeType->hasParameter( aTypeNameString ) )
+ {
+ rObjDesc.maTypeName = xMimeType->getParameterValue( aTypeNameString );
+ }
+
+ if( xMimeType->hasParameter( aDisplayNameString ) )
+ {
+ // the display name might contain unacceptable characters, in this case they should be encoded
+ // this seems to be the only parameter currently that might contain such characters
+ rObjDesc.maDisplayName = ::rtl::Uri::decode( xMimeType->getParameterValue( aDisplayNameString ), rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8 );
+ }
+
+ if( xMimeType->hasParameter( aViewAspectString ) )
+ {
+ rObjDesc.mnViewAspect = static_cast< sal_uInt16 >( xMimeType->getParameterValue( aViewAspectString ).toInt32() );
+ }
+
+ if( xMimeType->hasParameter( aWidthString ) )
+ {
+ rObjDesc.maSize.setWidth( xMimeType->getParameterValue( aWidthString ).toInt32() );
+ }
+
+ if( xMimeType->hasParameter( aHeightString ) )
+ {
+ rObjDesc.maSize.setHeight( xMimeType->getParameterValue( aHeightString ).toInt32() );
+ }
+
+ if( xMimeType->hasParameter( aPosXString ) )
+ {
+ rObjDesc.maDragStartPos.setX( xMimeType->getParameterValue( aPosXString ).toInt32() );
+ }
+
+ if( xMimeType->hasParameter( aPosYString ) )
+ {
+ rObjDesc.maDragStartPos.setY( xMimeType->getParameterValue( aPosYString ).toInt32() );
+ }
+ }
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+}
+
+
+TransferableHelper::TerminateListener::TerminateListener( TransferableHelper& rTransferableHelper ) :
+ mrParent( rTransferableHelper )
+{
+}
+
+
+TransferableHelper::TerminateListener::~TerminateListener()
+{
+}
+
+
+void SAL_CALL TransferableHelper::TerminateListener::disposing( const EventObject& )
+{
+}
+
+
+void SAL_CALL TransferableHelper::TerminateListener::queryTermination( const EventObject& )
+{
+}
+
+
+void SAL_CALL TransferableHelper::TerminateListener::notifyTermination( const EventObject& )
+{
+ mrParent.ImplFlush();
+}
+
+OUString SAL_CALL TransferableHelper::TerminateListener::getImplementationName()
+{
+ return "com.sun.star.comp.svt.TransferableHelperTerminateListener";
+}
+
+sal_Bool SAL_CALL TransferableHelper::TerminateListener::supportsService(const OUString& /*rServiceName*/)
+{
+ return false;
+}
+
+css::uno::Sequence<OUString> TransferableHelper::TerminateListener::getSupportedServiceNames()
+{
+ return css::uno::Sequence<OUString>();
+}
+
+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 )
+ {
+ std::unique_ptr<SvMemoryStream> pSrcStm(new SvMemoryStream( aSeq.getArray(), aSeq.getLength(), StreamMode::WRITE | StreamMode::TRUNC ));
+ GDIMetaFile aMtf;
+
+ ReadGDIMetaFile( *pSrcStm, aMtf );
+ pSrcStm.reset();
+
+ 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 )
+ {
+ std::unique_ptr<SvMemoryStream> pSrcStm(new SvMemoryStream( aSeq.getArray(), aSeq.getLength(), StreamMode::WRITE | StreamMode::TRUNC ));
+ GDIMetaFile aMtf;
+
+ ReadGDIMetaFile( *pSrcStm, aMtf );
+ pSrcStm.reset();
+
+ SvMemoryStream aDstStm( 65535, 65535 );
+
+ // taking wmf without file header
+ if ( ConvertGDIMetaFileToWMF( aMtf, aDstStm, nullptr, false ) )
+ {
+ maAny <<= Sequence< sal_Int8 >( static_cast< const sal_Int8* >( aDstStm.GetData() ),
+ aDstStm.TellEnd() );
+ bDone = true;
+ }
+ }
+ }
+ }
+
+ // reset Any if substitute doesn't work
+ if( !bDone && maAny.hasValue() )
+ maAny = Any();
+
+ // if any is not yet filled, use standard format
+ if( !maAny.hasValue() )
+ GetData(rFlavor, rDestDoc);
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+
+ if( !maAny.hasValue() )
+ throw UnsupportedFlavorException();
+ }
+
+ return maAny;
+}
+
+sal_Bool SAL_CALL TransferableHelper::isComplex()
+{
+ // By default everything is complex, until proven otherwise
+ // in the respective document type transferable handler.
+ return true;
+}
+
+Sequence< DataFlavor > SAL_CALL TransferableHelper::getTransferDataFlavors()
+{
+ const SolarMutexGuard aGuard;
+
+ try
+ {
+ if(maFormats.empty())
+ AddSupportedFormats();
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+
+ return comphelper::containerToSequence<DataFlavor>(maFormats);
+}
+
+
+sal_Bool SAL_CALL TransferableHelper::isDataFlavorSupported( const DataFlavor& rFlavor )
+{
+ const SolarMutexGuard aGuard;
+
+ try
+ {
+ if (maFormats.empty())
+ AddSupportedFormats();
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+
+ for (auto const& format : maFormats)
+ {
+ if( TransferableDataHelper::IsEqual( format, rFlavor ) )
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+void SAL_CALL TransferableHelper::lostOwnership( const Reference< XClipboard >&, const Reference< XTransferable >& )
+{
+ const SolarMutexGuard aGuard;
+
+ try
+ {
+ if( mxTerminateListener.is() )
+ {
+ Reference< XDesktop2 > xDesktop = Desktop::create( ::comphelper::getProcessComponentContext() );
+ xDesktop->removeTerminateListener( mxTerminateListener );
+
+ mxTerminateListener.clear();
+ }
+
+ ObjectReleased();
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+}
+
+
+void SAL_CALL TransferableHelper::disposing( const EventObject& )
+{
+}
+
+
+void SAL_CALL TransferableHelper::dragDropEnd( const DragSourceDropEvent& rDSDE )
+{
+ const SolarMutexGuard aGuard;
+
+ try
+ {
+ DragFinished( rDSDE.DropSuccess ? ( rDSDE.DropAction & ~DNDConstants::ACTION_DEFAULT ) : DNDConstants::ACTION_NONE );
+ ObjectReleased();
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+}
+
+
+void SAL_CALL TransferableHelper::dragEnter( const DragSourceDragEvent& )
+{
+}
+
+
+void SAL_CALL TransferableHelper::dragExit( const DragSourceEvent& )
+{
+}
+
+
+void SAL_CALL TransferableHelper::dragOver( const DragSourceDragEvent& )
+{
+}
+
+
+void SAL_CALL TransferableHelper::dropActionChanged( const DragSourceDragEvent& )
+{
+}
+
+
+sal_Int64 SAL_CALL TransferableHelper::getSomething( const Sequence< sal_Int8 >& rId )
+{
+ sal_Int64 nRet;
+
+ if( isUnoTunnelId<TransferableHelper>(rId) )
+ {
+ nRet = sal::static_int_cast<sal_Int64>(reinterpret_cast<sal_IntPtr>(this));
+ }
+ else
+ nRet = 0;
+
+ return nRet;
+}
+
+
+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, const DataFlavor& rFlavor )
+{
+ DataFlavor aFileFlavor;
+
+ if( !rString.isEmpty() &&
+ SotExchange::GetFormatDataFlavor( SotClipboardFormatId::SIMPLE_FILE, aFileFlavor ) &&
+ TransferableDataHelper::IsEqual( aFileFlavor, rFlavor ) )
+ {
+ const OString aByteStr(OUStringToOString(rString, osl_getThreadTextEncoding()));
+ Sequence< sal_Int8 > aSeq( aByteStr.getLength() + 1 );
+
+ memcpy( aSeq.getArray(), aByteStr.getStr(), aByteStr.getLength() );
+ aSeq[ aByteStr.getLength() ] = 0;
+ maAny <<= aSeq;
+ }
+ else
+ 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[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[aFilterData.getLength() - 1].Value <<= 1;
+#endif
+ vcl::PNGWriter aPNGWriter(rBitmapEx, &aFilterData);
+
+ aPNGWriter.Write(aMemStm);
+ }
+ else
+ {
+ // explicitly use Bitmap::Write with bCompressed = sal_False and bFileHeader = sal_True
+ WriteDIB(rBitmapEx.GetBitmap(), aMemStm, false, true);
+ }
+
+ maAny <<= Sequence< sal_Int8 >( static_cast< const sal_Int8* >( aMemStm.GetData() ), aMemStm.TellEnd() );
+ }
+
+ return maAny.hasValue();
+}
+
+
+bool TransferableHelper::SetGDIMetaFile( const GDIMetaFile& rMtf )
+{
+ if( rMtf.GetActionSize() )
+ {
+ SvMemoryStream aMemStm( 65535, 65535 );
+
+ const_cast<GDIMetaFile&>(rMtf).Write( aMemStm );
+ 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 );
+ WriteGraphic( aMemStm, rGraphic );
+ maAny <<= Sequence< sal_Int8 >( static_cast< const sal_Int8* >( aMemStm.GetData() ), aMemStm.Seek( STREAM_SEEK_TO_END ) );
+ }
+
+ 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.Seek( STREAM_SEEK_TO_END ) );
+
+ return maAny.hasValue();
+}
+
+
+bool TransferableHelper::SetTransferableObjectDescriptor( const TransferableObjectDescriptor& rDesc )
+{
+ PrepareOLE( rDesc );
+
+ SvMemoryStream aMemStm( 1024, 1024 );
+
+ WriteTransferableObjectDescriptor( aMemStm, rDesc );
+ maAny <<= Sequence< sal_Int8 >( static_cast< const sal_Int8* >( aMemStm.GetData() ), aMemStm.Tell() );
+
+ return maAny.hasValue();
+ }
+
+
+bool TransferableHelper::SetINetBookmark( const INetBookmark& rBmk,
+ const css::datatransfer::DataFlavor& rFlavor )
+{
+ rtl_TextEncoding eSysCSet = osl_getThreadTextEncoding();
+
+ switch( SotExchange::GetFormat( rFlavor ) )
+ {
+ case SotClipboardFormatId::SOLK:
+ {
+ OString sURL(OUStringToOString(rBmk.GetURL(), eSysCSet));
+ OString sDesc(OUStringToOString(rBmk.GetDescription(), eSysCSet));
+ OStringBuffer sOut;
+ sOut.append(sURL.getLength());
+ sOut.append('@').append(sURL);
+ sOut.append(sDesc.getLength());
+ sOut.append('@').append(sDesc);
+
+ Sequence< sal_Int8 > aSeq(sOut.getLength());
+ memcpy(aSeq.getArray(), sOut.getStr(), sOut.getLength());
+ maAny <<= aSeq;
+ }
+ break;
+
+ case SotClipboardFormatId::STRING:
+ maAny <<= rBmk.GetURL();
+ break;
+
+ case SotClipboardFormatId::UNIFORMRESOURCELOCATOR:
+ {
+ OString sURL(OUStringToOString(rBmk.GetURL(), eSysCSet));
+ Sequence< sal_Int8 > aSeq( sURL.getLength() );
+ memcpy( aSeq.getArray(), sURL.getStr(), sURL.getLength() );
+ maAny <<= aSeq;
+ }
+ break;
+
+ case SotClipboardFormatId::NETSCAPE_BOOKMARK:
+ {
+ Sequence< sal_Int8 > aSeq( 2048 );
+
+ memset( aSeq.getArray(), 0, 2048 );
+ strcpy( reinterpret_cast< char* >( aSeq.getArray() ), OUStringToOString(rBmk.GetURL(), eSysCSet).getStr() );
+ strcpy( reinterpret_cast< char* >( aSeq.getArray() ) + 1024, OUStringToOString(rBmk.GetDescription(), eSysCSet).getStr() );
+
+ maAny <<= aSeq;
+ }
+ break;
+
+#ifdef _WIN32
+ case SotClipboardFormatId::FILEGRPDESCRIPTOR:
+ {
+ Sequence< sal_Int8 > aSeq( sizeof( FILEGROUPDESCRIPTOR ) );
+ FILEGROUPDESCRIPTOR* pFDesc = reinterpret_cast<FILEGROUPDESCRIPTOR*>(aSeq.getArray());
+ FILEDESCRIPTOR& rFDesc1 = pFDesc->fgd[ 0 ];
+
+ pFDesc->cItems = 1;
+ memset( &rFDesc1, 0, sizeof( FILEDESCRIPTOR ) );
+ rFDesc1.dwFlags = FD_LINKUI;
+
+ OStringBuffer aStr(OUStringToOString(
+ rBmk.GetDescription(), eSysCSet));
+ for( sal_Int32 nChar = 0; nChar < aStr.getLength(); ++nChar )
+ if( strchr( "\\/:*?\"<>|", aStr[nChar] ) )
+ aStr.remove(nChar--, 1);
+
+ aStr.insert(0, "Shortcut to ");
+ aStr.append(".URL");
+ strcpy( rFDesc1.cFileName, aStr.getStr() );
+
+ maAny <<= aSeq;
+ }
+ break;
+
+ case SotClipboardFormatId::FILECONTENT:
+ {
+ OUString aStr( "[InternetShortcut]\x0aURL=" );
+ maAny <<= ( aStr += 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.Seek( STREAM_SEEK_TO_END ) );
+
+ return maAny.hasValue();
+}
+
+
+bool TransferableHelper::SetObject( void* pUserObject, sal_uInt32 nUserObjectId, const DataFlavor& rFlavor )
+{
+ tools::SvRef<SotStorageStream> xStm( new SotStorageStream( 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<SotStorageStream>&, 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::CopyToSelection( vcl::Window *pWindow ) const
+{
+ DBG_ASSERT( pWindow, "Window pointer is NULL" );
+ Reference< XClipboard > xSelection;
+
+ if( pWindow )
+ xSelection = pWindow->GetPrimarySelection();
+
+ CopyToSelection(xSelection);
+}
+
+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::ClearSelection( vcl::Window *pWindow )
+{
+ DBG_ASSERT( pWindow, "Window pointer is NULL" );
+ Reference< XClipboard > xSelection( pWindow->GetPrimarySelection() );
+
+ if( xSelection.is() )
+ xSelection->setContents( nullptr, nullptr );
+}
+
+namespace
+{
+ class theTransferableHelperUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theTransferableHelperUnoTunnelId > {};
+}
+
+const Sequence< sal_Int8 >& TransferableHelper::getUnoTunnelId()
+{
+ return theTransferableHelperUnoTunnelId::get().getSeq();
+}
+
+namespace {
+
+class TransferableClipboardNotifier : public ::cppu::WeakImplHelper< XClipboardListener >
+{
+private:
+ ::osl::Mutex& mrMutex;
+ Reference< XClipboardNotifier > mxNotifier;
+ TransferableDataHelper* mpListener;
+
+protected:
+ // XClipboardListener
+ virtual void SAL_CALL changedContents( const clipboard::ClipboardEvent& event ) override;
+
+ // XEventListener
+ virtual void SAL_CALL disposing( const EventObject& Source ) override;
+
+public:
+ TransferableClipboardNotifier( const Reference< XClipboard >& _rxClipboard, TransferableDataHelper& _rListener, ::osl::Mutex& _rMutex );
+
+ /// determines whether we're currently listening
+ bool isListening() const { return mpListener != nullptr; }
+
+ /// makes the instance non-functional
+ void dispose();
+};
+
+}
+
+TransferableClipboardNotifier::TransferableClipboardNotifier( const Reference< XClipboard >& _rxClipboard, TransferableDataHelper& _rListener, ::osl::Mutex& _rMutex )
+ :mrMutex( _rMutex )
+ ,mxNotifier( _rxClipboard, UNO_QUERY )
+ ,mpListener( &_rListener )
+{
+ osl_atomic_increment( &m_refCount );
+ {
+ if ( mxNotifier.is() )
+ mxNotifier->addClipboardListener( this );
+ else
+ // born dead
+ mpListener = nullptr;
+ }
+ osl_atomic_decrement( &m_refCount );
+}
+
+
+void SAL_CALL TransferableClipboardNotifier::changedContents( const clipboard::ClipboardEvent& event )
+{
+ SolarMutexGuard aSolarGuard;
+ // the SolarMutex here is necessary, since
+ // - we cannot call mpListener without our own mutex locked
+ // - Rebind respectively InitFormats (called by Rebind) will
+ // try to lock the SolarMutex, too
+ ::osl::MutexGuard aGuard( mrMutex );
+ if( mpListener )
+ mpListener->Rebind( event.Contents );
+}
+
+
+void SAL_CALL TransferableClipboardNotifier::disposing( const EventObject& )
+{
+ // clipboard is being disposed. Hmm. Okay, become disfunctional myself.
+ dispose();
+}
+
+
+void TransferableClipboardNotifier::dispose()
+{
+ ::osl::MutexGuard aGuard( mrMutex );
+
+ Reference< XClipboardListener > xKeepMeAlive( this );
+
+ if ( mxNotifier.is() )
+ mxNotifier->removeClipboardListener( this );
+ mxNotifier.clear();
+
+ mpListener = nullptr;
+}
+
+struct TransferableDataHelper_Impl
+{
+ ::osl::Mutex maMutex;
+ rtl::Reference<TransferableClipboardNotifier> mxClipboardListener;
+
+ TransferableDataHelper_Impl()
+ {
+ }
+};
+
+TransferableDataHelper::TransferableDataHelper()
+ : mxObjDesc(new TransferableObjectDescriptor)
+ , mxImpl(new TransferableDataHelper_Impl)
+{
+}
+
+TransferableDataHelper::TransferableDataHelper(const Reference< css::datatransfer::XTransferable >& rxTransferable)
+ : mxTransfer(rxTransferable)
+ , mxObjDesc(new TransferableObjectDescriptor)
+ , mxImpl(new TransferableDataHelper_Impl)
+{
+ InitFormats();
+}
+
+TransferableDataHelper::TransferableDataHelper(const TransferableDataHelper& rDataHelper)
+ : mxTransfer(rDataHelper.mxTransfer)
+ , mxClipboard(rDataHelper.mxClipboard)
+ , maFormats(rDataHelper.maFormats)
+ , mxObjDesc(new TransferableObjectDescriptor(*rDataHelper.mxObjDesc))
+ , mxImpl(new TransferableDataHelper_Impl)
+{
+}
+
+TransferableDataHelper::TransferableDataHelper(TransferableDataHelper&& rDataHelper) noexcept
+ : mxTransfer(std::move(rDataHelper.mxTransfer))
+ , mxClipboard(std::move(rDataHelper.mxClipboard))
+ , maFormats(std::move(rDataHelper.maFormats))
+ , mxObjDesc(std::move(rDataHelper.mxObjDesc))
+ , mxImpl(new TransferableDataHelper_Impl)
+{
+}
+
+TransferableDataHelper& TransferableDataHelper::operator=( const TransferableDataHelper& rDataHelper )
+{
+ if ( this != &rDataHelper )
+ {
+ ::osl::MutexGuard aGuard(mxImpl->maMutex);
+
+ const bool bWasClipboardListening = mxImpl->mxClipboardListener.is();
+
+ if (bWasClipboardListening)
+ StopClipboardListening();
+
+ mxTransfer = rDataHelper.mxTransfer;
+ maFormats = rDataHelper.maFormats;
+ mxObjDesc.reset(new TransferableObjectDescriptor(*rDataHelper.mxObjDesc));
+ mxClipboard = rDataHelper.mxClipboard;
+
+ if (bWasClipboardListening)
+ StartClipboardListening();
+ }
+
+ return *this;
+}
+
+TransferableDataHelper& TransferableDataHelper::operator=(TransferableDataHelper&& rDataHelper)
+{
+ ::osl::MutexGuard aGuard(mxImpl->maMutex);
+
+ const bool bWasClipboardListening = mxImpl->mxClipboardListener.is();
+
+ if (bWasClipboardListening)
+ StopClipboardListening();
+
+ mxTransfer = std::move(rDataHelper.mxTransfer);
+ maFormats = std::move(rDataHelper.maFormats);
+ mxObjDesc = std::move(rDataHelper.mxObjDesc);
+ mxClipboard = std::move(rDataHelper.mxClipboard);
+
+ if (bWasClipboardListening)
+ StartClipboardListening();
+
+ return *this;
+}
+
+TransferableDataHelper::~TransferableDataHelper()
+{
+ StopClipboardListening( );
+ {
+ ::osl::MutexGuard aGuard(mxImpl->maMutex);
+ maFormats.clear();
+ mxObjDesc.reset();
+ }
+}
+
+void TransferableDataHelper::FillDataFlavorExVector( const Sequence< DataFlavor >& rDataFlavorSeq,
+ DataFlavorExVector& rDataFlavorExVector )
+{
+ try
+ {
+ Reference< XComponentContext > xContext( ::comphelper::getProcessComponentContext() );
+ Reference< XMimeContentTypeFactory > xMimeFact = MimeContentTypeFactory::create( xContext );
+ DataFlavorEx aFlavorEx;
+ const OUString aCharsetStr( "charset" );
+
+
+ for (auto const& rFlavor : rDataFlavorSeq)
+ {
+ Reference< XMimeContentType > xMimeType;
+
+ try
+ {
+ if( !rFlavor.MimeType.isEmpty() )
+ xMimeType = xMimeFact->createMimeContentType( rFlavor.MimeType );
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+
+ aFlavorEx.MimeType = rFlavor.MimeType;
+ aFlavorEx.HumanPresentableName = rFlavor.HumanPresentableName;
+ aFlavorEx.DataType = rFlavor.DataType;
+ aFlavorEx.mnSotId = SotExchange::RegisterFormat( rFlavor );
+
+ rDataFlavorExVector.push_back( aFlavorEx );
+
+ // add additional formats for special mime types
+ if(SotClipboardFormatId::BMP == aFlavorEx.mnSotId || SotClipboardFormatId::PNG == aFlavorEx.mnSotId || SotClipboardFormatId::JPEG == aFlavorEx.mnSotId)
+ {
+ if( SotExchange::GetFormatDataFlavor( SotClipboardFormatId::BITMAP, aFlavorEx ) )
+ {
+ aFlavorEx.mnSotId = SotClipboardFormatId::BITMAP;
+ rDataFlavorExVector.push_back( aFlavorEx );
+ }
+ }
+ else if( SotClipboardFormatId::WMF == aFlavorEx.mnSotId || SotClipboardFormatId::EMF == aFlavorEx.mnSotId )
+ {
+ if( SotExchange::GetFormatDataFlavor( SotClipboardFormatId::GDIMETAFILE, aFlavorEx ) )
+ {
+ aFlavorEx.mnSotId = SotClipboardFormatId::GDIMETAFILE;
+ rDataFlavorExVector.push_back( aFlavorEx );
+ }
+ }
+ else if ( SotClipboardFormatId::HTML_SIMPLE == aFlavorEx.mnSotId )
+ {
+ // #104735# HTML_SIMPLE may also be inserted without comments
+ aFlavorEx.mnSotId = SotClipboardFormatId::HTML_NO_COMMENT;
+ rDataFlavorExVector.push_back( aFlavorEx );
+ }
+ else if( xMimeType.is() && xMimeType->getFullMediaType().equalsIgnoreAsciiCase( "text/plain" ) )
+ {
+ // add, if it is a UTF-8 byte buffer
+ if( xMimeType->hasParameter( aCharsetStr ) )
+ {
+ if( xMimeType->getParameterValue( aCharsetStr ).equalsIgnoreAsciiCase( "unicode" ) ||
+ xMimeType->getParameterValue( aCharsetStr ).equalsIgnoreAsciiCase( "utf-16" ) )
+ {
+ rDataFlavorExVector[ rDataFlavorExVector.size() - 1 ].mnSotId = SotClipboardFormatId::STRING;
+
+ }
+ }
+ }
+ else if( xMimeType.is() && xMimeType->getFullMediaType().equalsIgnoreAsciiCase( "text/rtf" ) )
+ {
+ rDataFlavorExVector[ rDataFlavorExVector.size() - 1 ].mnSotId = SotClipboardFormatId::RTF;
+ }
+ else if( xMimeType.is() && xMimeType->getFullMediaType().equalsIgnoreAsciiCase( "text/richtext" ) )
+ {
+ rDataFlavorExVector[ rDataFlavorExVector.size() - 1 ].mnSotId = SotClipboardFormatId::RICHTEXT;
+ }
+ else if( xMimeType.is() && xMimeType->getFullMediaType().equalsIgnoreAsciiCase( "text/html" ) )
+
+ {
+ rDataFlavorExVector[ rDataFlavorExVector.size() - 1 ].mnSotId = SotClipboardFormatId::HTML;
+ }
+ else if( xMimeType.is() && xMimeType->getFullMediaType().equalsIgnoreAsciiCase( "text/uri-list" ) )
+ {
+ rDataFlavorExVector[ rDataFlavorExVector.size() - 1 ].mnSotId = SotClipboardFormatId::FILE_LIST;
+ }
+ else if( xMimeType.is() && xMimeType->getFullMediaType().equalsIgnoreAsciiCase( "application/x-openoffice-objectdescriptor-xml" ) )
+ {
+ rDataFlavorExVector[ rDataFlavorExVector.size() - 1 ].mnSotId = SotClipboardFormatId::OBJECTDESCRIPTOR;
+ }
+ }
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+}
+
+void TransferableDataHelper::InitFormats()
+{
+ SolarMutexGuard aSolarGuard;
+ ::osl::MutexGuard aGuard(mxImpl->maMutex);
+
+ maFormats.clear();
+ mxObjDesc.reset(new TransferableObjectDescriptor);
+
+ if( !mxTransfer.is() )
+ return;
+
+ TransferableDataHelper::FillDataFlavorExVector(mxTransfer->getTransferDataFlavors(), maFormats);
+
+ for (auto const& format : maFormats)
+ {
+ if( SotClipboardFormatId::OBJECTDESCRIPTOR == format.mnSotId )
+ {
+ ImplSetParameterString(*mxObjDesc, format);
+ break;
+ }
+ }
+}
+
+
+bool TransferableDataHelper::HasFormat( SotClipboardFormatId nFormat ) const
+{
+ ::osl::MutexGuard aGuard(mxImpl->maMutex);
+ return std::any_of(maFormats.begin(), maFormats.end(),
+ [&](const DataFlavorEx& data) { return data.mnSotId == nFormat; });
+}
+
+bool TransferableDataHelper::HasFormat( const DataFlavor& rFlavor ) const
+{
+ ::osl::MutexGuard aGuard(mxImpl->maMutex);
+ for (auto const& format : maFormats)
+ {
+ if( TransferableDataHelper::IsEqual( rFlavor, format ) )
+ return true;
+ }
+
+ return false;
+}
+
+sal_uInt32 TransferableDataHelper::GetFormatCount() const
+{
+ ::osl::MutexGuard aGuard(mxImpl->maMutex);
+ return maFormats.size();
+}
+
+SotClipboardFormatId TransferableDataHelper::GetFormat( sal_uInt32 nFormat ) const
+{
+ ::osl::MutexGuard aGuard(mxImpl->maMutex);
+ DBG_ASSERT(nFormat < maFormats.size(), "TransferableDataHelper::GetFormat: invalid format index");
+ return( ( nFormat < maFormats.size() ) ? maFormats[ nFormat ].mnSotId : SotClipboardFormatId::NONE );
+}
+
+DataFlavor TransferableDataHelper::GetFormatDataFlavor( sal_uInt32 nFormat ) const
+{
+ ::osl::MutexGuard aGuard(mxImpl->maMutex);
+ DBG_ASSERT(nFormat < maFormats.size(), "TransferableDataHelper::GetFormat: invalid format index");
+
+ DataFlavor aRet;
+
+ if (nFormat < maFormats.size())
+ aRet = maFormats[nFormat];
+
+ return aRet;
+}
+
+
+Reference< XTransferable > TransferableDataHelper::GetXTransferable() const
+{
+ Reference< XTransferable > xRet;
+
+ if( mxTransfer.is() )
+ {
+ try
+ {
+ xRet = mxTransfer;
+
+ // do a dummy call to check, if this interface is valid (nasty)
+ xRet->getTransferDataFlavors();
+
+ }
+ catch( const css::uno::Exception& )
+ {
+ xRet.clear();
+ }
+ }
+
+ return xRet;
+}
+
+
+Any TransferableDataHelper::GetAny( SotClipboardFormatId nFormat, const OUString& rDestDoc ) const
+{
+ Any aReturn;
+
+ DataFlavor aFlavor;
+ if ( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) )
+ aReturn = GetAny(aFlavor, rDestDoc);
+
+ return aReturn;
+}
+
+Any TransferableDataHelper::GetAny( const DataFlavor& rFlavor, const OUString& rDestDoc ) const
+{
+ ::osl::MutexGuard aGuard(mxImpl->maMutex);
+ Any aRet;
+
+ try
+ {
+ if( mxTransfer.is() )
+ {
+ const SotClipboardFormatId nRequestFormat = SotExchange::GetFormat( rFlavor );
+
+ Reference<css::datatransfer::XTransferable2> xTransfer2(mxTransfer, UNO_QUERY);
+
+ if( nRequestFormat != SotClipboardFormatId::NONE )
+ {
+ // try to get alien format first
+ for (auto const& format : maFormats)
+ {
+ if( ( nRequestFormat == format.mnSotId ) && !rFlavor.MimeType.equalsIgnoreAsciiCase( format.MimeType ) )
+ {
+// tdf#133365: only release solar mutex on Windows
+#ifdef _WIN32
+ // tdf#133527: first, make sure that we actually hold the mutex
+ SolarMutexGuard g;
+ // Our own thread may handle the nested IDataObject::GetData call,
+ // and try to acquire solar mutex
+ SolarMutexReleaser r;
+#endif // _WIN32
+
+ if (xTransfer2.is())
+ aRet = xTransfer2->getTransferData2(format, rDestDoc);
+ else
+ aRet = mxTransfer->getTransferData(format);
+ }
+
+ if( aRet.hasValue() )
+ break;
+ }
+ }
+
+ if( !aRet.hasValue() )
+ {
+// tdf#133365: only release solar mutex on Windows
+#ifdef _WIN32
+ // tdf#133527: first, make sure that we actually hold the mutex
+ SolarMutexGuard g;
+ // Our own thread may handle the nested IDataObject::GetData call,
+ // and try to acquire solar mutex
+ SolarMutexReleaser r;
+#endif // _WIN32
+
+ if (xTransfer2.is())
+ aRet = xTransfer2->getTransferData2(rFlavor, rDestDoc);
+ else
+ aRet = mxTransfer->getTransferData(rFlavor);
+ }
+ }
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+
+ return aRet;
+}
+
+
+bool TransferableDataHelper::GetString( SotClipboardFormatId nFormat, OUString& rStr )
+{
+ DataFlavor aFlavor;
+ return( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) && GetString( aFlavor, rStr ) );
+}
+
+
+bool TransferableDataHelper::GetString( const DataFlavor& rFlavor, OUString& rStr )
+{
+ 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 )
+{
+ 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 )
+{
+ tools::SvRef<SotStorageStream> 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::PNGReader 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, "", *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(OutputDevice::LogicToLogic(aSize, MapMode(MapUnit::Map100thMM), MapMode(MapUnit::MapPixel)));
+ rBmpEx.SetPrefSize(aNewSize);
+ }
+ }
+ }
+ }
+
+ return bRet;
+}
+
+
+bool TransferableDataHelper::GetGDIMetaFile(SotClipboardFormatId nFormat, GDIMetaFile& rMtf, size_t nMaxActions)
+{
+ DataFlavor aFlavor;
+ return SotExchange::GetFormatDataFlavor(nFormat, aFlavor) &&
+ GetGDIMetaFile(aFlavor, rMtf) &&
+ (nMaxActions == 0 || rMtf.GetActionSize() < nMaxActions);
+}
+
+
+bool TransferableDataHelper::GetGDIMetaFile( const DataFlavor& rFlavor, GDIMetaFile& rMtf )
+{
+ tools::SvRef<SotStorageStream> xStm;
+ DataFlavor aSubstFlavor;
+ bool bRet = false;
+
+ if( GetSotStorageStream( rFlavor, xStm ) )
+ {
+ ReadGDIMetaFile( *xStm, 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 )
+{
+ 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 )
+{
+ 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<SotStorageStream> 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<SotStorageStream> xStm;
+
+ if( GetSotStorageStream( rFlavor, xStm ) )
+ {
+ ReadGraphic( *xStm, rGraphic );
+ bRet = ( xStm->GetError() == ERRCODE_NONE );
+ }
+ }
+
+ return bRet;
+}
+
+
+bool TransferableDataHelper::GetImageMap( SotClipboardFormatId nFormat, ImageMap& rIMap )
+{
+ DataFlavor aFlavor;
+ return( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) && GetImageMap( aFlavor, rIMap ) );
+}
+
+
+bool TransferableDataHelper::GetImageMap( const css::datatransfer::DataFlavor& rFlavor, ImageMap& rIMap )
+{
+ tools::SvRef<SotStorageStream> xStm;
+ bool bRet = GetSotStorageStream( rFlavor, xStm );
+
+ if( bRet )
+ {
+ rIMap.Read( *xStm );
+ bRet = ( xStm->GetError() == ERRCODE_NONE );
+ }
+
+ return bRet;
+}
+
+
+bool TransferableDataHelper::GetTransferableObjectDescriptor( SotClipboardFormatId nFormat, TransferableObjectDescriptor& rDesc )
+{
+ DataFlavor aFlavor;
+ return( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) && GetTransferableObjectDescriptor( rDesc ) );
+}
+
+
+bool TransferableDataHelper::GetTransferableObjectDescriptor( TransferableObjectDescriptor& rDesc )
+{
+ rDesc = *mxObjDesc;
+ return true;
+}
+
+
+bool TransferableDataHelper::GetINetBookmark( SotClipboardFormatId nFormat, INetBookmark& rBmk )
+{
+ DataFlavor aFlavor;
+ return( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) && GetINetBookmark( aFlavor, rBmk ) );
+}
+
+
+bool TransferableDataHelper::GetINetBookmark( const css::datatransfer::DataFlavor& rFlavor, INetBookmark& rBmk )
+{
+ bool bRet = false;
+ if( HasFormat( rFlavor ))
+ {
+ 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, "" );
+ 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())
+ {
+ FILEGROUPDESCRIPTOR const * pFDesc = reinterpret_cast<FILEGROUPDESCRIPTOR const *>(aSeq.getConstArray());
+
+ if( pFDesc->cItems )
+ {
+ OString aDesc( pFDesc->fgd[ 0 ].cFileName );
+ rtl_TextEncoding eTextEncoding = osl_getThreadTextEncoding();
+
+ if( ( aDesc.getLength() > 4 ) && aDesc.copy(aDesc.getLength() - 4).equalsIgnoreAsciiCase(".URL") )
+ {
+ std::unique_ptr<SvStream> pStream(::utl::UcbStreamHelper::CreateStream( INetURLObject( OStringToOUString(aDesc, eTextEncoding) ).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 bSttFnd = false;
+
+ while( pStream->ReadLine( aLine ) )
+ {
+ if (aLine.equalsIgnoreAsciiCase("[InternetShortcut]"))
+ bSttFnd = true;
+ else if (bSttFnd && aLine.copy(0, 4).equalsIgnoreAsciiCase("URL="))
+ {
+ rBmk = INetBookmark( OStringToOUString(aLine.copy(4), eTextEncoding),
+ OStringToOUString(aDesc.copy(0, aDesc.getLength() - 4), eTextEncoding) );
+ bRet = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ break;
+#endif
+ default: break;
+ }
+ }
+ return bRet;
+}
+
+
+bool TransferableDataHelper::GetINetImage( SotClipboardFormatId nFormat,
+ INetImage& rINtImg )
+{
+ DataFlavor aFlavor;
+ return( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) && GetINetImage( aFlavor, rINtImg ) );
+}
+
+
+bool TransferableDataHelper::GetINetImage(
+ const css::datatransfer::DataFlavor& rFlavor,
+ INetImage& rINtImg )
+{
+ tools::SvRef<SotStorageStream> xStm;
+ bool bRet = GetSotStorageStream( rFlavor, xStm );
+
+ if( bRet )
+ bRet = rINtImg.Read( *xStm, SotExchange::GetFormat( rFlavor ) );
+ return bRet;
+}
+
+
+bool TransferableDataHelper::GetFileList( SotClipboardFormatId nFormat,
+ FileList& rFileList )
+{
+ DataFlavor aFlavor;
+ return( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) && GetFileList( rFileList ) );
+}
+
+
+bool TransferableDataHelper::GetFileList( FileList& rFileList )
+{
+ tools::SvRef<SotStorageStream> 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 )
+ {
+ OString 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 )
+{
+ 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 Any aAny = GetAny(rFlavor, rDestDoc);
+ Sequence<sal_Int8> aSeq;
+ if (aAny.hasValue())
+ aAny >>= aSeq;
+
+ return aSeq;
+}
+
+
+bool TransferableDataHelper::GetSotStorageStream( SotClipboardFormatId nFormat, tools::SvRef<SotStorageStream>& rxStream )
+{
+ DataFlavor aFlavor;
+ return( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) && GetSotStorageStream( aFlavor, rxStream ) );
+}
+
+
+bool TransferableDataHelper::GetSotStorageStream( const DataFlavor& rFlavor, tools::SvRef<SotStorageStream>& rxStream )
+{
+ Sequence<sal_Int8> aSeq = GetSequence(rFlavor, OUString());
+
+ if (aSeq.hasElements())
+ {
+ rxStream = new SotStorageStream( "" );
+ rxStream->WriteBytes( aSeq.getConstArray(), aSeq.getLength() );
+ rxStream->Seek( 0 );
+ }
+
+ return aSeq.hasElements();
+}
+
+Reference<XInputStream> TransferableDataHelper::GetInputStream( SotClipboardFormatId nFormat, const OUString& rDestDoc )
+{
+ DataFlavor aFlavor;
+ if (!SotExchange::GetFormatDataFlavor(nFormat, aFlavor))
+ return Reference<XInputStream>();
+
+ return GetInputStream(aFlavor, rDestDoc);
+}
+
+Reference<XInputStream> TransferableDataHelper::GetInputStream( const DataFlavor& rFlavor, const OUString& rDestDoc )
+{
+ Sequence<sal_Int8> aSeq = GetSequence(rFlavor, rDestDoc);
+
+ if (!aSeq.hasElements())
+ return Reference<XInputStream>();
+
+ Reference<XInputStream> xStream(new comphelper::SequenceInputStream(aSeq));
+ return xStream;
+}
+
+void TransferableDataHelper::Rebind( const Reference< XTransferable >& _rxNewContent )
+{
+ mxTransfer = _rxNewContent;
+ InitFormats();
+}
+
+bool TransferableDataHelper::StartClipboardListening( )
+{
+ ::osl::MutexGuard aGuard(mxImpl->maMutex);
+
+ StopClipboardListening( );
+
+ mxImpl->mxClipboardListener = new TransferableClipboardNotifier(mxClipboard, *this, mxImpl->maMutex);
+
+ return mxImpl->mxClipboardListener->isListening();
+}
+
+void TransferableDataHelper::StopClipboardListening( )
+{
+ ::osl::MutexGuard aGuard(mxImpl->maMutex);
+
+ if (mxImpl->mxClipboardListener.is())
+ {
+ mxImpl->mxClipboardListener->dispose();
+ mxImpl->mxClipboardListener.clear();
+ }
+}
+
+TransferableDataHelper TransferableDataHelper::CreateFromClipboard(const css::uno::Reference<css::datatransfer::clipboard::XClipboard>& rClipboard)
+{
+ TransferableDataHelper aRet;
+
+ if( rClipboard.is() )
+ {
+ try
+ {
+ Reference< XTransferable > xTransferable( rClipboard->getContents() );
+
+ if( xTransferable.is() )
+ {
+ aRet = TransferableDataHelper( xTransferable );
+ // also copy the clipboard
+ aRet.mxClipboard = rClipboard;
+ }
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+ }
+
+ return aRet;
+}
+
+TransferableDataHelper TransferableDataHelper::CreateFromSystemClipboard( vcl::Window * pWindow )
+{
+ DBG_ASSERT( pWindow, "Window pointer is NULL" );
+
+ Reference< XClipboard > xClipboard;
+
+ if( pWindow )
+ xClipboard = pWindow->GetClipboard();
+
+ return CreateFromClipboard(xClipboard);
+}
+
+
+TransferableDataHelper TransferableDataHelper::CreateFromSelection( vcl::Window* pWindow )
+{
+ DBG_ASSERT( pWindow, "Window pointer is NULL" );
+
+ Reference< XClipboard > xSelection;
+ TransferableDataHelper aRet;
+
+ if( pWindow )
+ xSelection = pWindow->GetPrimarySelection();
+
+ 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
+ const OUString aCharsetString( "charset" );
+
+ if( !xRequestType2->hasParameter( aCharsetString ) ||
+ xRequestType2->getParameterValue( aCharsetString ).equalsIgnoreAsciiCase( "utf-16" ) ||
+ xRequestType2->getParameterValue( aCharsetString ).equalsIgnoreAsciiCase( "unicode" ) )
+ {
+ bRet = true;
+ }
+ }
+ else if( xRequestType1->getFullMediaType().equalsIgnoreAsciiCase( "application/x-openoffice" ) )
+ {
+ // special handling for application/x-openoffice media types
+ const OUString aFormatString( "windows_formatname" );
+
+ if( xRequestType1->hasParameter( aFormatString ) &&
+ xRequestType2->hasParameter( aFormatString ) &&
+ xRequestType1->getParameterValue( aFormatString ).equalsIgnoreAsciiCase( xRequestType2->getParameterValue( aFormatString ) ) )
+ {
+ bRet = true;
+ }
+ }
+ else
+ bRet = true;
+ }
+ }
+ }
+ catch( const css::uno::Exception& )
+ {
+ bRet = rInternalFlavor.MimeType.equalsIgnoreAsciiCase( rRequestFlavor.MimeType );
+ }
+
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/treelist/transfer2.cxx b/vcl/source/treelist/transfer2.cxx
new file mode 100644
index 000000000..cb1cd18cd
--- /dev/null
+++ b/vcl/source/treelist/transfer2.cxx
@@ -0,0 +1,519 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/mutex.hxx>
+#include <sot/exchange.hxx>
+#include <tools/debug.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/window.hxx>
+#include <comphelper/processfactory.hxx>
+#include <com/sun/star/datatransfer/clipboard/SystemClipboard.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTargetDragContext.hpp>
+#include <com/sun/star/datatransfer/dnd/XDragGestureRecognizer.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp>
+#include <com/sun/star/uno/DeploymentException.hpp>
+#include <svl/urlbmk.hxx>
+#include <vcl/transfer.hxx>
+
+#include <svdata.hxx>
+
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::io;
+using namespace ::com::sun::star::datatransfer;
+using namespace ::com::sun::star::datatransfer::clipboard;
+using namespace ::com::sun::star::datatransfer::dnd;
+
+
+DragSourceHelper::DragGestureListener::DragGestureListener( DragSourceHelper& rDragSourceHelper ) :
+ mrParent( rDragSourceHelper )
+{
+}
+
+
+DragSourceHelper::DragGestureListener::~DragGestureListener()
+{
+}
+
+
+void SAL_CALL DragSourceHelper::DragGestureListener::disposing( const EventObject& )
+{
+}
+
+
+void SAL_CALL DragSourceHelper::DragGestureListener::dragGestureRecognized( const DragGestureEvent& rDGE )
+{
+ const SolarMutexGuard aGuard;
+
+ const Point aPtPixel( rDGE.DragOriginX, rDGE.DragOriginY );
+ mrParent.StartDrag( rDGE.DragAction, aPtPixel );
+}
+
+
+DragSourceHelper::DragSourceHelper( vcl::Window* pWindow ) :
+ mxDragGestureRecognizer( pWindow->GetDragGestureRecognizer() )
+{
+ if( mxDragGestureRecognizer.is() )
+ {
+ mxDragGestureListener = new DragSourceHelper::DragGestureListener( *this );
+ mxDragGestureRecognizer->addDragGestureListener( mxDragGestureListener );
+ }
+}
+
+
+void DragSourceHelper::dispose()
+{
+ Reference<XDragGestureRecognizer> xTmp;
+ {
+ osl::MutexGuard aGuard( maMutex );
+ xTmp = mxDragGestureRecognizer;
+ mxDragGestureRecognizer.clear();
+ }
+ 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;
+ {
+ osl::MutexGuard aGuard( maMutex );
+ xTmp = mxDropTarget;
+ mxDropTarget.clear();
+ }
+ 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> aFinshedLnk;
+ std::unique_ptr<INetBookmark> pBookmk;
+
+ TransferDataContainer_Impl()
+ {
+ }
+};
+
+
+TransferDataContainer::TransferDataContainer()
+ : pImpl( new TransferDataContainer_Impl )
+{
+}
+
+
+TransferDataContainer::~TransferDataContainer()
+{
+}
+
+
+void TransferDataContainer::AddSupportedFormats()
+{
+}
+
+
+bool TransferDataContainer::GetData(
+ const css::datatransfer::DataFlavor& rFlavor, const OUString& /*rDestDoc*/ )
+{
+ bool bFnd = false;
+ SotClipboardFormatId nFmtId = SotExchange::GetFormat( rFlavor );
+
+ // test first the list
+ for (auto const& format : pImpl->aFmtList)
+ {
+ if( nFmtId == format.nId )
+ {
+ bFnd = SetAny( format.aAny );
+ break;
+ }
+ }
+
+ // test second the bookmark pointer
+ if( !bFnd )
+ switch( nFmtId )
+ {
+ case SotClipboardFormatId::STRING:
+ case SotClipboardFormatId::SOLK:
+ case SotClipboardFormatId::NETSCAPE_BOOKMARK:
+ case SotClipboardFormatId::FILECONTENT:
+ case SotClipboardFormatId::FILEGRPDESCRIPTOR:
+ case SotClipboardFormatId::UNIFORMRESOURCELOCATOR:
+ if( pImpl->pBookmk )
+ bFnd = SetINetBookmark( *pImpl->pBookmk, rFlavor );
+ break;
+
+ default: break;
+ }
+
+ return bFnd;
+}
+
+
+void TransferDataContainer::CopyINetBookmark( const INetBookmark& rBkmk )
+{
+ if( !pImpl->pBookmk )
+ pImpl->pBookmk.reset( new INetBookmark( rBkmk ) );
+ else
+ *pImpl->pBookmk = rBkmk;
+
+ AddFormat( SotClipboardFormatId::STRING );
+ AddFormat( SotClipboardFormatId::SOLK );
+ AddFormat( SotClipboardFormatId::NETSCAPE_BOOKMARK );
+ AddFormat( SotClipboardFormatId::FILECONTENT );
+ AddFormat( SotClipboardFormatId::FILEGRPDESCRIPTOR );
+ AddFormat( SotClipboardFormatId::UNIFORMRESOURCELOCATOR );
+}
+
+
+void TransferDataContainer::CopyAnyData( SotClipboardFormatId nFormatId,
+ const char* pData, sal_uLong nLen )
+{
+ if( nLen )
+ {
+ TDataCntnrEntry_Impl aEntry;
+ aEntry.nId = nFormatId;
+
+ Sequence< sal_Int8 > aSeq( nLen );
+ memcpy( aSeq.getArray(), pData, nLen );
+ aEntry.aAny <<= aSeq;
+ pImpl->aFmtList.push_back( aEntry );
+ AddFormat( nFormatId );
+ }
+}
+
+
+void TransferDataContainer::CopyByteString( SotClipboardFormatId nFormatId,
+ const OString& rStr )
+{
+ CopyAnyData( nFormatId, rStr.getStr(), rStr.getLength() );
+}
+
+
+void TransferDataContainer::CopyString( SotClipboardFormatId nFmt, const OUString& rStr )
+{
+ if( !rStr.isEmpty() )
+ {
+ TDataCntnrEntry_Impl aEntry;
+ aEntry.nId = nFmt;
+ aEntry.aAny <<= rStr;
+ pImpl->aFmtList.push_back( aEntry );
+ AddFormat( aEntry.nId );
+ }
+}
+
+
+void TransferDataContainer::CopyString( const OUString& rStr )
+{
+ CopyString( SotClipboardFormatId::STRING, rStr );
+}
+
+
+bool TransferDataContainer::HasAnyData() const
+{
+ return !pImpl->aFmtList.empty() ||
+ nullptr != pImpl->pBookmk;
+}
+
+
+void TransferDataContainer::StartDrag(
+ vcl::Window* pWindow, sal_Int8 nDragSourceActions,
+ const Link<sal_Int8,void>& rLnk )
+{
+ pImpl->aFinshedLnk = rLnk;
+ TransferableHelper::StartDrag( pWindow, nDragSourceActions );
+}
+
+
+void TransferDataContainer::DragFinished( sal_Int8 nDropAction )
+{
+ pImpl->aFinshedLnk.Call( nDropAction );
+}
+
+Reference<XClipboard> GetSystemClipboard()
+{
+ // On Windows, the css.datatransfer.clipboard.SystemClipboard UNO service is implemented as a
+ // single-instance service (sysdtrans_component_getFactory,
+ // dtrans/source/win32/clipb/wcbentry.cxx) that needs timely disposing to join a spawned thread
+ // (done in DeInitVCL, vcl/source/app/svmain.cxx), while on other platforms it is implemented as
+ // a multi-instance service (ClipboardFactory, vcl/source/components/dtranscomp.cxx) so we
+ // should not hold on to a single instance here:
+#if defined _WIN32
+ DBG_TESTSOLARMUTEX();
+ auto const data = ImplGetSVData();
+ if (!data->m_xSystemClipboard.is())
+ {
+ try
+ {
+ data->m_xSystemClipboard = css::datatransfer::clipboard::SystemClipboard::create(
+ comphelper::getProcessComponentContext());
+ }
+ catch (DeploymentException const &) {}
+ }
+ return data->m_xSystemClipboard;
+#else
+ Reference<XClipboard> xClipboard;
+ try
+ {
+ xClipboard = css::datatransfer::clipboard::SystemClipboard::create(
+ comphelper::getProcessComponentContext());
+ }
+ catch (DeploymentException const &) {}
+ return xClipboard;
+#endif
+}
+
+Reference<XClipboard> GetSystemPrimarySelection()
+{
+ Reference<XClipboard> xSelection;
+ try
+ {
+ Reference<XComponentContext> xContext(comphelper::getProcessComponentContext());
+#if HAVE_FEATURE_X11
+ // A hack, making the primary selection available as an instance
+ // of the SystemClipboard service on X11:
+ Sequence< Any > args(1);
+ args[0] <<= OUString("PRIMARY");
+ xSelection.set(xContext->getServiceManager()->createInstanceWithArgumentsAndContext(
+ "com.sun.star.datatransfer.clipboard.SystemClipboard", args, xContext), UNO_QUERY_THROW);
+#else
+ static Reference< XClipboard > s_xSelection(
+ xContext->getServiceManager()->createInstanceWithContext(
+ "com.sun.star.datatransfer.clipboard.GenericClipboard", xContext), UNO_QUERY);
+ xSelection = s_xSelection;
+#endif
+ }
+ catch (RuntimeException const &) {}
+ return xSelection;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/treelist/treelist.cxx b/vcl/source/treelist/treelist.cxx
new file mode 100644
index 000000000..4f2cd1dfe
--- /dev/null
+++ b/vcl/source/treelist/treelist.cxx
@@ -0,0 +1,1565 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/treelist.hxx>
+#include <vcl/treelistentry.hxx>
+#include <vcl/viewdataentry.hxx>
+#include <tools/debug.hxx>
+#include <osl/diagnose.h>
+
+#include <memory>
+#include <map>
+
+
+typedef std::map<SvTreeListEntry*, std::unique_ptr<SvViewDataEntry>> SvDataTable;
+
+struct SvListView::Impl
+{
+ SvListView & m_rThis;
+
+ SvDataTable m_DataTable; // Mapping SvTreeListEntry -> ViewData
+
+ sal_uLong m_nVisibleCount;
+ sal_uLong 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 = SortNone;
+}
+
+SvTreeList::~SvTreeList()
+{
+}
+
+void SvTreeList::Broadcast(
+ SvListAction nActionId,
+ SvTreeListEntry* pEntry1,
+ SvTreeListEntry* pEntry2,
+ sal_uLong 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->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_uLong 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_uLong>(~0);
+
+ return static_cast<sal_uLong>(std::distance(rDst.begin(), itPos));
+}
+
+}
+
+sal_uLong SvTreeList::Move(SvTreeListEntry* pSrcEntry,SvTreeListEntry* pTargetParent,sal_uLong 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_uLong nRetVal = findEntryPosition(rDst, pSrcEntry);
+ OSL_ENSURE(nRetVal == pSrcEntry->GetChildListPos(), "ListPos not valid");
+ Broadcast( SvListAction::MOVED,pSrcEntry,pTargetParent,nRetVal);
+ return nRetVal;
+}
+
+sal_uLong SvTreeList::Copy(SvTreeListEntry* pSrcEntry,SvTreeListEntry* pTargetParent,sal_uLong nListPos)
+{
+ // pDest may be 0!
+ DBG_ASSERT(pSrcEntry,"Entry?");
+ if ( !pTargetParent )
+ pTargetParent = pRootItem.get();
+
+ bAbsPositionsValid = false;
+
+ sal_uLong 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_uLong nRetVal = findEntryPosition(rDst, pClonedEntry);
+ return nRetVal;
+}
+
+void SvTreeList::Move( SvTreeListEntry* pSrcEntry, SvTreeListEntry* pDstEntry )
+{
+ SvTreeListEntry* pParent;
+ sal_uLong 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_uLong 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_uLong& 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_uLong& 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_uLong SvTreeList::GetChildCount( const SvTreeListEntry* pParent ) const
+{
+ if ( !pParent )
+ return GetEntryCount();
+
+ if (pParent->m_Children.empty())
+ return 0;
+
+ sal_uLong 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_uLong 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_uLong 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_uLong 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_uLong 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_uLong 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_uLong 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_uLong 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_uLong 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_uLong 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_uLong 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?");
+
+ sal_uInt16 nDepth = 0;
+
+ SvTreeListEntries* pActualList = &pActEntry->pParent->m_Children;
+ sal_uLong nActualPos = pActEntry->GetChildListPos();
+
+ if ( nActualPos > 0 )
+ {
+ pActEntry = (*pActualList)[nActualPos-1].get();
+ while( pView->IsExpanded(pActEntry) )
+ {
+ pActualList = &pActEntry->m_Children;
+ nDepth++;
+ pActEntry = pActualList->back().get();
+ }
+ return pActEntry;
+ }
+
+ if ( pActEntry->pParent == pRootItem.get() )
+ return nullptr;
+
+ pActEntry = pActEntry->pParent;
+ if ( pActEntry )
+ {
+ nDepth--;
+ 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_uLong 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_uLong 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;
+}
+
+SvTreeListEntry* SvTreeList::LastSelected( const SvListView* pView ) const
+{
+ DBG_ASSERT(pView,"LastSel:No View");
+ SvTreeListEntry* pEntry = Last();
+ while( pEntry && !pView->IsSelected(pEntry) )
+ pEntry = Prev( pEntry );
+ return pEntry;
+}
+
+sal_uLong SvTreeList::Insert( SvTreeListEntry* pEntry,SvTreeListEntry* pParent,sal_uLong 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_uLong SvTreeList::GetAbsPos( const SvTreeListEntry* pEntry) const
+{
+ if ( !bAbsPositionsValid )
+ const_cast<SvTreeList*>(this)->SetAbsolutePositions();
+ return pEntry->nAbsPos;
+}
+
+sal_uLong SvTreeList::GetRelPos( const SvTreeListEntry* pChild )
+{
+ return pChild->GetChildListPos();
+}
+
+void SvTreeList::SetAbsolutePositions()
+{
+ sal_uLong 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_uLong 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_uLong nAbsPos ) const
+{
+ SvTreeListEntry* pEntry = First();
+ while ( nAbsPos && pEntry )
+ {
+ pEntry = Next( pEntry );
+ nAbsPos--;
+ }
+ return pEntry;
+}
+
+SvTreeListEntry* SvTreeList::GetEntryAtVisPos( const SvListView* pView, sal_uLong nVisPos ) const
+{
+ DBG_ASSERT(pView,"GetEntryAtVisPos:No View");
+ SvTreeListEntry* pEntry = First();
+ while ( nVisPos && pEntry )
+ {
+ pEntry = NextVisible( pView, pEntry );
+ nVisPos--;
+ }
+ return pEntry;
+}
+
+void SvTreeList::SetListPositions( SvTreeListEntries& rEntries )
+{
+ if (rEntries.empty())
+ return;
+
+ SvTreeListEntry& rFirst = *rEntries.front();
+ if (rFirst.pParent)
+ rFirst.pParent->InvalidateChildrensListPositions();
+}
+
+void SvTreeList::EnableInvalidate( bool bEnable )
+{
+ mbEnableInvalidate = bEnable;
+}
+
+void SvTreeList::InvalidateEntry( SvTreeListEntry* pEntry )
+{
+ if (!mbEnableInvalidate)
+ return;
+
+ Broadcast( SvListAction::INVALIDATE_ENTRY, pEntry );
+}
+
+SvTreeListEntry* SvTreeList::GetRootLevelParent( SvTreeListEntry* pEntry ) const
+{
+ DBG_ASSERT(pEntry,"GetRootLevelParent:No Entry");
+ SvTreeListEntry* pCurParent = nullptr;
+ if ( pEntry )
+ {
+ pCurParent = pEntry->pParent;
+ if ( pCurParent == pRootItem.get() )
+ return pEntry; // is its own parent
+ while( pCurParent && pCurParent->pParent != pRootItem.get() )
+ pCurParent = pCurParent->pParent;
+ }
+ return pCurParent;
+}
+
+std::pair<SvTreeListEntries::iterator, SvTreeListEntries::iterator>
+ SvTreeList::GetChildIterators(SvTreeListEntry* pParent)
+{
+ typedef std::pair<SvTreeListEntries::iterator, SvTreeListEntries::iterator> IteratorPair;
+
+ static SvTreeListEntries dummy; // prevent singular iterator asserts
+ IteratorPair aRet(dummy.begin(), dummy.end());
+
+ if (!pParent)
+ pParent = pRootItem.get();
+
+ if (pParent->m_Children.empty())
+ // This entry has no children.
+ return aRet;
+
+ aRet.first = pParent->m_Children.begin();
+ aRet.second = pParent->m_Children.end();
+
+ return aRet;
+}
+
+
+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_uLong SvListView::GetSelectionCount() const
+{ return m_pImpl->m_nSelectionCount; }
+
+bool SvListView::HasViewData() const
+{ return m_pImpl->m_DataTable.size() > 1; } // There's always a ROOT
+
+
+void SvListView::Impl::InitTable()
+{
+ DBG_ASSERT(m_rThis.pModel,"InitTable:No Model");
+ DBG_ASSERT(!m_nSelectionCount && !m_nVisibleCount && !m_bVisPositionsValid,
+ "InitTable: Not cleared!");
+
+ if (!m_DataTable.empty())
+ {
+ DBG_ASSERT(m_DataTable.size() == 1, "InitTable: TableCount != 1");
+ // Delete the view data allocated to the Clear in the root.
+ // Attention: The model belonging to the root entry (and thus the entry
+ // itself) might already be deleted.
+ m_DataTable.clear();
+ }
+
+ SvTreeListEntry* pEntry;
+
+ // insert root entry
+ pEntry = m_rThis.pModel->pRootItem.get();
+ std::unique_ptr<SvViewDataEntry> pViewData(new SvViewDataEntry);
+ pViewData->SetExpanded(true);
+ m_DataTable.insert(std::make_pair(pEntry, std::move(pViewData)));
+ // now all the other entries
+ pEntry = m_rThis.pModel->First();
+ while( pEntry )
+ {
+ pViewData = std::make_unique<SvViewDataEntry>();
+ m_rThis.InitViewData( pViewData.get(), pEntry );
+ m_DataTable.insert(std::make_pair(pEntry, std::move(pViewData)));
+ pEntry = m_rThis.pModel->Next( pEntry );
+ }
+}
+
+void SvListView::Clear()
+{
+ m_pImpl->m_DataTable.clear();
+ m_pImpl->m_nSelectionCount = 0;
+ m_pImpl->m_nVisibleCount = 0;
+ m_pImpl->m_bVisPositionsValid = false;
+ if( pModel )
+ {
+ // insert root entry
+ SvTreeListEntry* pEntry = pModel->pRootItem.get();
+ std::unique_ptr<SvViewDataEntry> pViewData(new SvViewDataEntry);
+ pViewData->SetExpanded(true);
+ m_pImpl->m_DataTable.insert(std::make_pair(pEntry, std::move(pViewData)));
+ }
+}
+
+void SvListView::ModelHasCleared()
+{
+}
+
+void SvListView::ModelHasInserted( SvTreeListEntry* )
+{
+}
+
+void SvListView::ModelHasInsertedTree( SvTreeListEntry* )
+{
+}
+
+void SvListView::ModelIsMoving( SvTreeListEntry* /* pSource */ )
+{
+}
+
+
+void SvListView::ModelHasMoved( SvTreeListEntry* )
+{
+}
+
+void SvListView::ModelIsRemoving( SvTreeListEntry* )
+{
+}
+
+void SvListView::ModelHasRemoved( SvTreeListEntry* )
+{
+ //WARNING WARNING WARNING
+ //The supplied pointer should have been deleted
+ //before this call. Be careful not to use it!!!
+}
+
+void SvListView::ModelHasEntryInvalidated( SvTreeListEntry*)
+{
+}
+
+void SvListView::Impl::ActionMoving( SvTreeListEntry* pEntry )
+{
+ SvTreeListEntry* pParent = pEntry->pParent;
+ DBG_ASSERT(pParent,"Model not consistent");
+ if (pParent != m_rThis.pModel->pRootItem.get() && pParent->m_Children.size() == 1)
+ {
+ SvViewDataEntry* pViewData = m_DataTable.find( pParent )->second.get();
+ pViewData->SetExpanded(false);
+ }
+ // preliminary
+ m_nVisibleCount = 0;
+ m_bVisPositionsValid = false;
+}
+
+void SvListView::Impl::ActionMoved()
+{
+ m_nVisibleCount = 0;
+ m_bVisPositionsValid = false;
+}
+
+void SvListView::Impl::ActionInserted( SvTreeListEntry* pEntry )
+{
+ DBG_ASSERT(pEntry,"Insert:No Entry");
+ std::unique_ptr<SvViewDataEntry> pData(new SvViewDataEntry());
+ m_rThis.InitViewData( pData.get(), pEntry );
+ std::pair<SvDataTable::iterator, bool> aSuccess =
+ m_DataTable.insert(std::make_pair(pEntry, std::move(pData)));
+ DBG_ASSERT(aSuccess.second,"Entry already in View");
+ if (m_nVisibleCount && m_rThis.pModel->IsEntryVisible(&m_rThis, pEntry))
+ {
+ m_nVisibleCount = 0;
+ m_bVisPositionsValid = false;
+ }
+}
+
+void SvListView::Impl::ActionInsertedTree( SvTreeListEntry* pEntry )
+{
+ if (m_rThis.pModel->IsEntryVisible(&m_rThis, pEntry))
+ {
+ m_nVisibleCount = 0;
+ m_bVisPositionsValid = false;
+ }
+ // iterate over entry and its children
+ SvTreeListEntry* pCurEntry = pEntry;
+ sal_uInt16 nRefDepth = m_rThis.pModel->GetDepth( pCurEntry );
+ while( pCurEntry )
+ {
+ DBG_ASSERT(m_DataTable.find(pCurEntry) != m_DataTable.end(),"Entry already in Table");
+ std::unique_ptr<SvViewDataEntry> pViewData(new SvViewDataEntry());
+ m_rThis.InitViewData( pViewData.get(), pEntry );
+ m_DataTable.insert(std::make_pair(pCurEntry, std::move(pViewData)));
+ pCurEntry = m_rThis.pModel->Next( pCurEntry );
+ if ( pCurEntry && m_rThis.pModel->GetDepth(pCurEntry) <= nRefDepth)
+ pCurEntry = nullptr;
+ }
+}
+
+void SvListView::Impl::RemoveViewData( SvTreeListEntry* pParent )
+{
+ for (auto const& it : pParent->m_Children)
+ {
+ SvTreeListEntry& rEntry = *it;
+ m_DataTable.erase(&rEntry);
+ if (rEntry.HasChildren())
+ RemoveViewData(&rEntry);
+ }
+}
+
+
+void SvListView::Impl::ActionRemoving( SvTreeListEntry* pEntry )
+{
+ DBG_ASSERT(pEntry,"Remove:No Entry");
+
+ SvViewDataEntry* pViewData = m_DataTable.find( pEntry )->second.get();
+ sal_uLong nSelRemoved = 0;
+ if ( pViewData->IsSelected() )
+ nSelRemoved = 1 + m_rThis.pModel->GetChildSelectionCount(&m_rThis, pEntry);
+ m_nSelectionCount -= nSelRemoved;
+ sal_uLong nVisibleRemoved = 0;
+ if (m_rThis.pModel->IsEntryVisible(&m_rThis, pEntry))
+ nVisibleRemoved = 1 + m_rThis.pModel->GetVisibleChildCount(&m_rThis, pEntry);
+ if( m_nVisibleCount )
+ {
+#ifdef DBG_UTIL
+ if (m_nVisibleCount < nVisibleRemoved)
+ {
+ OSL_FAIL("nVisibleRemoved bad");
+ }
+#endif
+ m_nVisibleCount -= nVisibleRemoved;
+ }
+ m_bVisPositionsValid = false;
+
+ m_DataTable.erase(pEntry);
+ RemoveViewData( pEntry );
+
+ SvTreeListEntry* pCurEntry = pEntry->pParent;
+ if (pCurEntry && pCurEntry != m_rThis.pModel->pRootItem.get() && pCurEntry->m_Children.size() == 1)
+ {
+ pViewData = m_DataTable.find(pCurEntry)->second.get();
+ pViewData->SetExpanded(false);
+ }
+}
+
+void SvListView::Impl::ActionClear()
+{
+ m_rThis.Clear();
+}
+
+void SvListView::ModelNotification( SvListAction nActionId, SvTreeListEntry* pEntry1,
+ SvTreeListEntry* /*pEntry2*/, sal_uLong /*nPos*/ )
+{
+ switch( nActionId )
+ {
+ case SvListAction::INSERTED:
+ m_pImpl->ActionInserted( pEntry1 );
+ ModelHasInserted( pEntry1 );
+ break;
+ case SvListAction::INSERTED_TREE:
+ m_pImpl->ActionInsertedTree( pEntry1 );
+ ModelHasInsertedTree( pEntry1 );
+ break;
+ case SvListAction::REMOVING:
+ ModelIsRemoving( pEntry1 );
+ m_pImpl->ActionRemoving( pEntry1 );
+ break;
+ case SvListAction::REMOVED:
+ ModelHasRemoved( pEntry1 );
+ break;
+ case SvListAction::MOVING:
+ ModelIsMoving( pEntry1 );
+ m_pImpl->ActionMoving( pEntry1 );
+ break;
+ case SvListAction::MOVED:
+ m_pImpl->ActionMoved();
+ ModelHasMoved( pEntry1 );
+ break;
+ case SvListAction::CLEARING:
+ m_pImpl->ActionClear();
+ ModelHasCleared(); // sic! for compatibility reasons!
+ break;
+ case SvListAction::CLEARED:
+ break;
+ case SvListAction::INVALIDATE_ENTRY:
+ // no action for the base class
+ ModelHasEntryInvalidated( pEntry1 );
+ break;
+ case SvListAction::RESORTED:
+ m_pImpl->m_bVisPositionsValid = false;
+ break;
+ case SvListAction::RESORTING:
+ break;
+ default:
+ OSL_FAIL("unknown ActionId");
+ }
+}
+
+void SvListView::InitViewData( SvViewDataEntry*, SvTreeListEntry* )
+{
+}
+
+bool SvListView::IsExpanded( SvTreeListEntry* pEntry ) const
+{
+ DBG_ASSERT(pEntry,"IsExpanded:No Entry");
+ SvDataTable::const_iterator itr = m_pImpl->m_DataTable.find(pEntry);
+ DBG_ASSERT(itr != m_pImpl->m_DataTable.end(),"Entry not in Table");
+ if (itr == m_pImpl->m_DataTable.end())
+ return false;
+ return itr->second->IsExpanded();
+}
+
+bool SvListView::IsAllExpanded( SvTreeListEntry* pEntry ) const
+{
+ DBG_ASSERT(pEntry,"IsAllExpanded:No Entry");
+ if (!IsExpanded(pEntry))
+ return false;
+ const SvTreeListEntries& rChildren = pEntry->GetChildEntries();
+ for (auto& rChild : rChildren)
+ {
+ if (rChild->HasChildren() || rChild->HasChildrenOnDemand())
+ {
+ if (!IsAllExpanded(rChild.get()))
+ return false;
+ }
+ }
+ return true;
+}
+
+bool SvListView::IsSelected(const SvTreeListEntry* pEntry) const
+{
+ DBG_ASSERT(pEntry,"IsExpanded:No Entry");
+ SvDataTable::const_iterator itr = m_pImpl->m_DataTable.find(const_cast<SvTreeListEntry*>(pEntry));
+ if (itr == m_pImpl->m_DataTable.end())
+ return false;
+ return itr->second->IsSelected();
+}
+
+void SvListView::SetEntryFocus( SvTreeListEntry* pEntry, bool bFocus )
+{
+ DBG_ASSERT(pEntry,"SetEntryFocus:No Entry");
+ SvDataTable::iterator itr = m_pImpl->m_DataTable.find(pEntry);
+ DBG_ASSERT(itr != m_pImpl->m_DataTable.end(),"Entry not in Table");
+ itr->second->SetFocus(bFocus);
+}
+
+const SvViewDataEntry* SvListView::GetViewData( const SvTreeListEntry* pEntry ) const
+{
+ SvDataTable::const_iterator itr =
+ m_pImpl->m_DataTable.find(const_cast<SvTreeListEntry*>(pEntry));
+ if (itr == m_pImpl->m_DataTable.end())
+ return nullptr;
+ return itr->second.get();
+}
+
+SvViewDataEntry* SvListView::GetViewData( SvTreeListEntry* pEntry )
+{
+ SvDataTable::iterator itr = m_pImpl->m_DataTable.find( pEntry );
+ DBG_ASSERT(itr != m_pImpl->m_DataTable.end(),"Entry not in model or wrong view");
+ return itr->second.get();
+}
+
+sal_Int32 SvTreeList::Compare(const SvTreeListEntry* pLeft, const SvTreeListEntry* pRight) const
+{
+ if( aCompareLink.IsSet())
+ {
+ SvSortData aSortData;
+ aSortData.pLeft = pLeft;
+ aSortData.pRight = pRight;
+ return aCompareLink.Call( aSortData );
+ }
+ return 0;
+}
+
+void SvTreeList::Resort()
+{
+ Broadcast( SvListAction::RESORTING );
+ bAbsPositionsValid = false;
+ ResortChildren( pRootItem.get() );
+ Broadcast( SvListAction::RESORTED );
+}
+
+namespace {
+
+class SortComparator
+{
+ SvTreeList& mrList;
+public:
+
+ explicit SortComparator( SvTreeList& rList ) : mrList(rList) {}
+
+ bool operator() (std::unique_ptr<SvTreeListEntry> const& rpLeft,
+ std::unique_ptr<SvTreeListEntry> const& rpRight) const
+ {
+ int nCompare = mrList.Compare(rpLeft.get(), rpRight.get());
+ if (nCompare != 0 && mrList.GetSortMode() == SortDescending)
+ {
+ 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_uLong& rPos )
+{
+ DBG_ASSERT(pEntry,"No Entry");
+
+ if( eSortMode == SortNone )
+ return;
+
+ rPos = TREELIST_ENTRY_NOTFOUND;
+ const SvTreeListEntries& rChildList = GetChildList(pParent);
+
+ if (rChildList.empty())
+ return;
+
+ long i = 0;
+ long j = rChildList.size()-1;
+ 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 == SortDescending)
+ {
+ 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<long>(rChildList.size()-1)) // not found, end of list
+ rPos = TREELIST_ENTRY_NOTFOUND;
+ else
+ rPos = i; // not found, middle of list
+ }
+ else
+ rPos = k;
+}
+
+bool SvTreeList::HasChildren( const SvTreeListEntry* pEntry ) const
+{
+ if ( !pEntry )
+ pEntry = pRootItem.get();
+
+ return !pEntry->m_Children.empty();
+}
+
+bool SvTreeList::HasParent( const SvTreeListEntry* pEntry ) const
+{
+ return pEntry->pParent != pRootItem.get();
+}
+
+SvTreeListEntry* SvTreeList::GetEntry( SvTreeListEntry* pParent, sal_uLong 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_uLong 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 000000000..bb52e28fe
--- /dev/null
+++ b/vcl/source/treelist/treelistbox.cxx
@@ -0,0 +1,3603 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/treelistbox.hxx>
+#include <vcl/accessiblefactory.hxx>
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+#include <vcl/svapp.hxx>
+#include <vcl/accel.hxx>
+#include <vcl/builder.hxx>
+#include <vcl/edit.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/uitest/uiobject.hxx>
+#include <sot/formats.hxx>
+#include <unotools/accessiblestatesethelper.hxx>
+#include <rtl/instance.hxx>
+#include <comphelper/string.hxx>
+#include <sal/log.hxx>
+#include <tools/debug.hxx>
+
+#include <vcl/svlbitm.hxx>
+#include <vcl/treelistentry.hxx>
+#include <vcl/viewdataentry.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;
+ VclPtr<Edit> pEdit;
+ bool bCanceled;
+ bool bAlreadyInCallBack;
+
+ void CallCallBackHdl_Impl();
+ DECL_LINK( Timeout_Impl, Timer *, void );
+ DECL_LINK( ReturnHdl_Impl, Accelerator&, void );
+ DECL_LINK( EscapeHdl_Impl, Accelerator&, void );
+
+public:
+ SvInplaceEdit2( vcl::Window* pParent, const Point& rPos, const Size& rSize,
+ const OUString& rData, const Link<SvInplaceEdit2&,void>& rNotifyEditEnd,
+ const Selection& );
+ ~SvInplaceEdit2();
+ bool KeyInput( const KeyEvent& rKEvt );
+ void LoseFocus();
+ bool EditingCanceled() const { return bCanceled; }
+ OUString GetText() const;
+ OUString const & GetSavedValue() const;
+ void StopEditing( bool bCancel );
+ void Hide();
+};
+
+// ***************************************************************
+
+namespace {
+
+class MyEdit_Impl : public Edit
+{
+ SvInplaceEdit2* pOwner;
+public:
+ MyEdit_Impl( vcl::Window* pParent, SvInplaceEdit2* pOwner );
+ virtual ~MyEdit_Impl() override { disposeOnce(); }
+ virtual void dispose() override { pOwner = nullptr; Edit::dispose(); }
+ virtual void KeyInput( const KeyEvent& rKEvt ) override;
+ virtual void LoseFocus() override;
+};
+
+}
+
+MyEdit_Impl::MyEdit_Impl( vcl::Window* pParent, SvInplaceEdit2* _pOwner ) :
+
+ Edit( pParent, WB_LEFT ),
+
+ pOwner( _pOwner )
+
+{
+}
+
+void MyEdit_Impl::KeyInput( const KeyEvent& rKEvt )
+{
+ if( !pOwner->KeyInput( rKEvt ))
+ Edit::KeyInput( rKEvt );
+}
+
+void MyEdit_Impl::LoseFocus()
+{
+ if (pOwner)
+ pOwner->LoseFocus();
+}
+
+SvInplaceEdit2::SvInplaceEdit2
+(
+ vcl::Window* pParent, const Point& rPos,
+ const Size& rSize,
+ const OUString& rData,
+ const Link<SvInplaceEdit2&,void>& rNotifyEditEnd,
+ const Selection& rSelection
+) :
+
+ aCallBackHdl ( rNotifyEditEnd ),
+ bCanceled ( false ),
+ bAlreadyInCallBack ( false )
+
+{
+
+ pEdit = VclPtr<MyEdit_Impl>::Create( pParent, this );
+
+ vcl::Font aFont( pParent->GetFont() );
+ aFont.SetTransparent( false );
+ Color aColor( pParent->GetBackground().GetColor() );
+ aFont.SetFillColor(aColor );
+ pEdit->SetFont( aFont );
+ pEdit->SetBackground( pParent->GetBackground() );
+ pEdit->SetPosPixel( rPos );
+ pEdit->SetSizePixel( rSize );
+ pEdit->SetText( rData );
+ pEdit->SetSelection( rSelection );
+ pEdit->SaveValue();
+
+ aAccReturn.InsertItem( SVLBOX_ACC_RETURN, vcl::KeyCode(KEY_RETURN) );
+ aAccEscape.InsertItem( SVLBOX_ACC_ESCAPE, vcl::KeyCode(KEY_ESCAPE) );
+
+ aAccReturn.SetActivateHdl( LINK( this, SvInplaceEdit2, ReturnHdl_Impl) );
+ aAccEscape.SetActivateHdl( LINK( this, SvInplaceEdit2, EscapeHdl_Impl) );
+ Application::InsertAccel( &aAccReturn );
+ Application::InsertAccel( &aAccEscape );
+
+ pEdit->Show();
+ pEdit->GrabFocus();
+}
+
+SvInplaceEdit2::~SvInplaceEdit2()
+{
+ if( !bAlreadyInCallBack )
+ {
+ Application::RemoveAccel( &aAccReturn );
+ Application::RemoveAccel( &aAccEscape );
+ }
+ pEdit.disposeAndClear();
+}
+
+OUString const & SvInplaceEdit2::GetSavedValue() const
+{
+ return pEdit->GetSavedValue();
+}
+
+void SvInplaceEdit2::Hide()
+{
+ pEdit->Hide();
+}
+
+
+IMPL_LINK_NOARG(SvInplaceEdit2, ReturnHdl_Impl, Accelerator&, void)
+{
+ bCanceled = false;
+ CallCallBackHdl_Impl();
+}
+
+IMPL_LINK_NOARG(SvInplaceEdit2, EscapeHdl_Impl, Accelerator&, void)
+{
+ bCanceled = true;
+ CallCallBackHdl_Impl();
+}
+
+bool SvInplaceEdit2::KeyInput( const KeyEvent& rKEvt )
+{
+ vcl::KeyCode aCode = rKEvt.GetKeyCode();
+ sal_uInt16 nCode = aCode.GetCode();
+
+ switch ( nCode )
+ {
+ case KEY_ESCAPE:
+ bCanceled = true;
+ CallCallBackHdl_Impl();
+ return true;
+
+ case KEY_RETURN:
+ bCanceled = false;
+ CallCallBackHdl_Impl();
+ return true;
+ }
+ return false;
+}
+
+void SvInplaceEdit2::StopEditing( bool bCancel )
+{
+ if ( !bAlreadyInCallBack )
+ {
+ bCanceled = bCancel;
+ CallCallBackHdl_Impl();
+ }
+}
+
+void SvInplaceEdit2::LoseFocus()
+{
+ if ( !bAlreadyInCallBack
+ && ((!Application::GetFocusWindow()) || !pEdit->IsChild( Application::GetFocusWindow()) )
+ )
+ {
+ bCanceled = false;
+ aIdle.SetPriority(TaskPriority::REPAINT);
+ aIdle.SetInvokeHandler(LINK(this,SvInplaceEdit2,Timeout_Impl));
+ aIdle.SetDebugName( "svtools::SvInplaceEdit2 aIdle" );
+ 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( long nPosition, SvLBoxTabFlags nTabFlags )
+{
+ nPos = nPosition;
+ nFlags = nTabFlags;
+}
+
+SvLBoxTab::SvLBoxTab( const SvLBoxTab& rTab )
+{
+ nPos = rTab.nPos;
+ nFlags = rTab.nFlags;
+}
+
+SvLBoxTab::~SvLBoxTab()
+{
+}
+
+
+long SvLBoxTab::CalcOffset( long nItemWidth, long nTabWidth )
+{
+ 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 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_bEntryMnemonicsEnabled:1;
+ bool m_bDoingQuickSelection:1;
+
+ vcl::MnemonicEngine m_aMnemonicEngine;
+ vcl::QuickSelectionEngine m_aQuickSelectionEngine;
+
+ explicit SvTreeListBoxImpl(SvTreeListBox& _rBox) :
+ m_bEntryMnemonicsEnabled(false),
+ m_bDoingQuickSelection(false),
+ m_aMnemonicEngine(_rBox),
+ m_aQuickSelectionEngine(_rBox) {}
+};
+
+
+SvTreeListBox::SvTreeListBox(vcl::Window* pParent, WinBits nWinStyle) :
+ Control(pParent, nWinStyle | WB_CLIPCHILDREN),
+ DropTargetHelper(this),
+ DragSourceHelper(this),
+ mpImpl(new SvTreeListBoxImpl(*this)),
+ mbContextBmpExpanded(false),
+ mbAlternatingRowColors(false),
+ mbUpdateAlternatingRows(false),
+ mbQuickSearch(false),
+ mbActivateOnSingleClick(false),
+ 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()
+}
+
+void SvTreeListBox::EnableEntryMnemonics()
+{
+ if ( IsEntryMnemonicsEnabled() )
+ return;
+
+ mpImpl->m_bEntryMnemonicsEnabled = true;
+ Invalidate();
+}
+
+bool SvTreeListBox::IsEntryMnemonicsEnabled() const
+{
+ return mpImpl->m_bEntryMnemonicsEnabled;
+}
+
+IMPL_LINK( SvTreeListBox, CloneHdl_Impl, SvTreeListEntry*, pEntry, SvTreeListEntry* )
+{
+ return CloneEntry(pEntry);
+}
+
+sal_uLong SvTreeListBox::Insert( SvTreeListEntry* pEntry, SvTreeListEntry* pParent, sal_uLong nPos )
+{
+ sal_uLong nInsPos = pModel->Insert( pEntry, pParent, nPos );
+ pEntry->SetBackColor( GetBackground().GetColor() );
+ SetAlternatingRowColors( mbAlternatingRowColors );
+ return nInsPos;
+}
+
+sal_uLong SvTreeListBox::Insert( SvTreeListEntry* pEntry,sal_uLong nRootPos )
+{
+ sal_uLong nInsPos = pModel->Insert( pEntry, nRootPos );
+ pEntry->SetBackColor( GetBackground().GetColor() );
+ SetAlternatingRowColors( mbAlternatingRowColors );
+ return nInsPos;
+}
+
+bool SvTreeListBox::ExpandingHdl()
+{
+ return !aExpandingHdl.IsSet() || aExpandingHdl.Call( this );
+}
+
+void SvTreeListBox::ExpandedHdl()
+{
+ aExpandedHdl.Call( this );
+}
+
+void SvTreeListBox::SelectHdl()
+{
+ aSelectHdl.Call( this );
+}
+
+void SvTreeListBox::DeselectHdl()
+{
+ aDeselectHdl.Call( this );
+}
+
+bool SvTreeListBox::DoubleClickHdl()
+{
+ return !aDoubleClickHdl.IsSet() || aDoubleClickHdl.Call(this);
+}
+
+bool SvTreeListBox::CheckDragAndDropMode( SvTreeListBox const * pSource, sal_Int8 nAction )
+{
+ if ( pSource == this )
+ {
+ if ( !(nDragDropMode & (DragDropMode::CTRL_MOVE | DragDropMode::CTRL_COPY) ) )
+ return false; // D&D locked within list
+ if( DND_ACTION_MOVE == nAction )
+ {
+ if ( !(nDragDropMode & DragDropMode::CTRL_MOVE) )
+ return false; // no local move
+ }
+ else
+ {
+ if ( !(nDragDropMode & DragDropMode::CTRL_COPY))
+ return false; // no local copy
+ }
+ }
+ else
+ {
+ return false; // no drop
+ }
+ 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()
+ SvTreeListEntry* pEntry, // entry that we want to move, from
+ // GetSourceListBox()->GetModel()
+ SvTreeListEntry*& rpNewParent, // new target parent
+ sal_uLong& 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()
+ SvTreeListEntry* pEntry, // entry that we want to move, from
+ // GetSourceListBox()->GetModel()
+ SvTreeListEntry*& rpNewParent, // new target parent
+ sal_uLong& 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_uLong nInsertionPos = TREELIST_APPEND;
+ TriState nOk = NotifyCopying(pTarget,pSourceEntry,pNewParent,nInsertionPos);
+ if ( nOk )
+ {
+ if ( bClone )
+ {
+ sal_uLong nCloneCount = 0;
+ pSourceEntry = pModel->Clone(pSourceEntry, nCloneCount);
+ pModel->InsertTree(pSourceEntry, pNewParent, nInsertionPos);
+ }
+ else
+ {
+ sal_uLong 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_uLong 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_uLong 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_uLong nPos ) const
+{
+ return pModel->GetEntry(pParent, nPos);
+}
+
+SvTreeListEntry* SvTreeListBox::GetEntry( sal_uLong 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_uLong 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);
+}
+
+SvTreeListEntry* SvTreeListBox::GetRootLevelParent( SvTreeListEntry* pEntry ) const
+{
+ return pModel->GetRootLevelParent(pEntry);
+}
+
+sal_uLong SvTreeListBox::GetChildCount( SvTreeListEntry const * pParent ) const
+{
+ return pModel->GetChildCount(pParent);
+}
+
+sal_uLong SvTreeListBox::GetLevelChildCount( SvTreeListEntry* _pParent ) const
+{
+
+ //if _pParent is 0, then pEntry is the first child of the root.
+ SvTreeListEntry* pEntry = FirstChild( _pParent );
+
+ if( !pEntry )//there is only root, root don't have children
+ return 0;
+
+ if( !_pParent )//root and children of root
+ return pEntry->pParent->m_Children.size();
+
+ return _pParent->m_Children.size();
+}
+
+SvViewDataEntry* SvTreeListBox::GetViewDataEntry( SvTreeListEntry const * pEntry ) const
+{
+ return const_cast<SvViewDataEntry*>(SvListView::GetViewData(pEntry));
+}
+
+SvViewDataItem* SvTreeListBox::GetViewDataItem(SvTreeListEntry const * pEntry, SvLBoxItem const * pItem)
+{
+ return const_cast<SvViewDataItem*>(static_cast<const SvTreeListBox*>(this)->GetViewDataItem(pEntry, pItem));
+}
+
+const SvViewDataItem* SvTreeListBox::GetViewDataItem(const SvTreeListEntry* pEntry, const SvLBoxItem* pItem) const
+{
+ const SvViewDataEntry* pEntryData = SvListView::GetViewData(pEntry);
+ assert(pEntryData && "Entry not in View");
+ sal_uInt16 nItemPos = pEntry->GetPos(pItem);
+ return &pEntryData->GetItem(nItemPos);
+}
+
+void SvTreeListBox::InitViewData( SvViewDataEntry* pData, SvTreeListEntry* pEntry )
+{
+ SvTreeListEntry* pInhEntry = pEntry;
+ SvViewDataEntry* pEntryData = pData;
+
+ pEntryData->Init(pInhEntry->ItemCount());
+ sal_uInt16 nCount = pInhEntry->ItemCount();
+ sal_uInt16 nCurPos = 0;
+ while( nCurPos < nCount )
+ {
+ SvLBoxItem& rItem = pInhEntry->GetItem( nCurPos );
+ SvViewDataItem& rItemData = pEntryData->GetItem(nCurPos);
+ rItem.InitViewData( this, pInhEntry, &rItemData );
+ nCurPos++;
+ }
+}
+
+void SvTreeListBox::EnableSelectionAsDropTarget( bool bEnable )
+{
+ sal_uInt16 nRefDepth;
+ SvTreeListEntry* pTemp;
+
+ SvTreeListEntry* pSelEntry = FirstSelected();
+ while( pSelEntry )
+ {
+ if ( !bEnable )
+ {
+ pSelEntry->nEntryFlags |= SvTLEntryFlags::DISABLE_DROP;
+ nRefDepth = pModel->GetDepth( pSelEntry );
+ pTemp = Next( pSelEntry );
+ while( pTemp && pModel->GetDepth( pTemp ) > nRefDepth )
+ {
+ pTemp->nEntryFlags |= SvTLEntryFlags::DISABLE_DROP;
+ pTemp = Next( pTemp );
+ }
+ }
+ else
+ {
+ pSelEntry->nEntryFlags &= ~SvTLEntryFlags::DISABLE_DROP;
+ nRefDepth = pModel->GetDepth( pSelEntry );
+ pTemp = Next( pSelEntry );
+ while( pTemp && pModel->GetDepth( pTemp ) > nRefDepth )
+ {
+ pTemp->nEntryFlags &= ~SvTLEntryFlags::DISABLE_DROP;
+ pTemp = Next( pTemp );
+ }
+ }
+ pSelEntry = NextSelected( pSelEntry );
+ }
+}
+
+// ******************************************************************
+// InplaceEditing
+// ******************************************************************
+
+void SvTreeListBox::EditText( const OUString& rStr, const tools::Rectangle& rRect,
+ const Selection& rSel )
+{
+ pEdCtrl.reset();
+ nImpFlags |= SvTreeListBoxFlags::IN_EDT;
+ nImpFlags &= ~SvTreeListBoxFlags::EDTEND_CALLED;
+ HideFocus();
+ pEdCtrl.reset( new SvInplaceEdit2(
+ this, rRect.TopLeft(), rRect.GetSize(), rStr,
+ LINK( this, SvTreeListBox, TextEditEndedHdl_Impl ),
+ rSel ) );
+}
+
+IMPL_LINK_NOARG(SvTreeListBox, TextEditEndedHdl_Impl, SvInplaceEdit2&, void)
+{
+ if ( nImpFlags & SvTreeListBoxFlags::EDTEND_CALLED ) // avoid nesting
+ return;
+ nImpFlags |= SvTreeListBoxFlags::EDTEND_CALLED;
+ OUString aStr;
+ if ( !pEdCtrl->EditingCanceled() )
+ aStr = pEdCtrl->GetText();
+ else
+ aStr = pEdCtrl->GetSavedValue();
+ EditedText( aStr );
+ // Hide may only be called after the new text was put into the entry, so
+ // that we don't call the selection handler in the GetFocus of the listbox
+ // with the old entry text.
+ pEdCtrl->Hide();
+ nImpFlags &= ~SvTreeListBoxFlags::IN_EDT;
+ GrabFocus();
+}
+
+void SvTreeListBox::CancelTextEditing()
+{
+ if ( pEdCtrl )
+ pEdCtrl->StopEditing( true );
+ nImpFlags &= ~SvTreeListBoxFlags::IN_EDT;
+}
+
+void SvTreeListBox::EndEditing( bool bCancel )
+{
+ if( pEdCtrl )
+ pEdCtrl->StopEditing( bCancel );
+ nImpFlags &= ~SvTreeListBoxFlags::IN_EDT;
+}
+
+
+const void* SvTreeListBox::FirstSearchEntry( OUString& _rEntryText ) const
+{
+ SvTreeListEntry* pEntry = GetCurEntry();
+ if ( pEntry )
+ pEntry = const_cast< SvTreeListEntry* >( static_cast< const SvTreeListEntry* >( NextSearchEntry( pEntry, _rEntryText ) ) );
+ else
+ {
+ pEntry = FirstSelected();
+ if ( !pEntry )
+ pEntry = First();
+ }
+
+ if ( pEntry )
+ _rEntryText = GetEntryText( pEntry );
+
+ return pEntry;
+}
+
+const void* SvTreeListBox::NextSearchEntry( const void* _pCurrentSearchEntry, OUString& _rEntryText ) 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 )
+ _rEntryText = GetEntryText( pEntry );
+
+ return pEntry;
+}
+
+void SvTreeListBox::SelectSearchEntry( const void* _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 );
+}
+
+void SvTreeListBox::ExecuteSearchEntry( const void* /*_pEntry*/ ) const
+{
+ // nothing to do here, we have no "execution"
+}
+
+vcl::StringEntryIdentifier SvTreeListBox::CurrentEntry( OUString& _out_entryText ) const
+{
+ // always accept the current entry if there is one
+ SvTreeListEntry* pCurrentEntry( GetCurEntry() );
+ if ( pCurrentEntry )
+ {
+ _out_entryText = GetEntryText( pCurrentEntry );
+ return pCurrentEntry;
+ }
+ return FirstSearchEntry( _out_entryText );
+}
+
+vcl::StringEntryIdentifier SvTreeListBox::NextEntry( vcl::StringEntryIdentifier _currentEntry, OUString& _out_entryText ) const
+{
+ return NextSearchEntry( _currentEntry, _out_entryText );
+}
+
+void SvTreeListBox::SelectEntry( vcl::StringEntryIdentifier _entry )
+{
+ SelectSearchEntry( _entry );
+}
+
+bool SvTreeListBox::HandleKeyInput( const KeyEvent& _rKEvt )
+{
+ if ( _rKEvt.GetKeyCode().IsMod1() )
+ return false;
+
+ if ( IsEntryMnemonicsEnabled()
+ && mpImpl->m_aMnemonicEngine.HandleKeyEvent( _rKEvt )
+ )
+ return true;
+
+ if (mbQuickSearch)
+ {
+ mpImpl->m_bDoingQuickSelection = true;
+ const bool bHandled = mpImpl->m_aQuickSelectionEngine.HandleKeyEvent( _rKEvt );
+ mpImpl->m_bDoingQuickSelection = false;
+ if ( bHandled )
+ return true;
+ }
+
+ return false;
+}
+
+bool SvTreeListBox::EditingCanceled() const
+{
+ return pEdCtrl && pEdCtrl->EditingCanceled();
+}
+
+
+//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 )
+{
+ Point aEventPos( rPosPixel );
+ MouseEvent aMouseEvt( aEventPos, 1, MouseEventModifiers::SELECT, MOUSE_LEFT );
+ MouseButtonUp( aMouseEvt );
+
+ 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( *xContainer, pEntry );
+ 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(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( TransferDataContainer&, SvTreeListEntry* )
+{
+ 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
+{
+ struct SortLBoxes : public rtl::Static<std::set<sal_uLong>, SortLBoxes> {};
+}
+
+void SvTreeListBox::AddBoxToDDList_Impl( const SvTreeListBox& rB )
+{
+ sal_uLong nVal = reinterpret_cast<sal_uLong>(&rB);
+ SortLBoxes::get().insert( nVal );
+}
+
+void SvTreeListBox::RemoveBoxFromDDList_Impl( const SvTreeListBox& rB )
+{
+ sal_uLong nVal = reinterpret_cast<sal_uLong>(&rB);
+ SortLBoxes::get().erase( nVal );
+}
+
+IMPL_LINK( SvTreeListBox, DragFinishHdl_Impl, sal_Int8, nAction, void )
+{
+ sal_uLong nVal = reinterpret_cast<sal_uLong>(this);
+ std::set<sal_uLong> &rSortLBoxes = SortLBoxes::get();
+ std::set<sal_uLong>::const_iterator it = rSortLBoxes.find(nVal);
+ 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 );
+ 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()).append(",");
+ }
+ nCur++;
+ }
+
+ if (!sRet.isEmpty())
+ sRet = sRet.copy(0, sRet.getLength() - 1);
+ return sRet.makeStringAndClear();
+}
+
+SvTreeListBox::~SvTreeListBox()
+{
+ disposeOnce();
+}
+
+void SvTreeListBox::dispose()
+{
+ 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;
+ long nStartPos = TAB_STARTPOS;
+ 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.
+ long nCheckWidth = 0;
+ if( nTreeFlags & SvTreeFlags::CHKBTN )
+ nCheckWidth = mnCheckboxItemWidth;
+ long nCheckWidthDIV2 = nCheckWidth / 2;
+
+ long nContextWidth = nContextBmpWidthMax;
+ long nContextWidthDIV2 = nContextWidth / 2;
+
+ ClearTabList();
+
+ int nCase = NO_BUTTONS;
+ if( !(nTreeFlags & SvTreeFlags::CHKBTN) )
+ {
+ if( bHasButtons )
+ nCase = NODE_BUTTONS;
+ }
+ else
+ {
+ if( bHasButtons )
+ nCase = NODE_AND_CHECK_BUTTONS;
+ else
+ nCase = CHECK_BUTTONS;
+ }
+
+ switch( nCase )
+ {
+ case NO_BUTTONS :
+ nStartPos += nContextWidthDIV2; // because of centering
+ AddTab( nStartPos, TABFLAGS_CONTEXTBMP );
+ nStartPos += nContextWidthDIV2; // right edge of context bitmap
+ // only set a distance if there are bitmaps
+ if( nContextBmpWidthMax )
+ nStartPos += 5; // distance context bitmap to text
+ AddTab( nStartPos, TABFLAGS_TEXT );
+ break;
+
+ case NODE_BUTTONS :
+ if( bHasButtonsAtRoot )
+ nStartPos += ( nIndent + (nNodeWidthPixel/2) );
+ else
+ nStartPos += nContextWidthDIV2;
+ AddTab( nStartPos, TABFLAGS_CONTEXTBMP );
+ nStartPos += nContextWidthDIV2; // right edge of context bitmap
+ // only set a distance if there are bitmaps
+ if( nContextBmpWidthMax )
+ nStartPos += 5; // distance context bitmap to text
+ AddTab( nStartPos, TABFLAGS_TEXT );
+ break;
+
+ case NODE_AND_CHECK_BUTTONS :
+ if( bHasButtonsAtRoot )
+ nStartPos += ( nIndent + nNodeWidthPixel );
+ else
+ nStartPos += nCheckWidthDIV2;
+ AddTab( nStartPos, TABFLAGS_CHECKBTN );
+ nStartPos += nCheckWidthDIV2; // right edge of CheckButton
+ nStartPos += 3; // distance CheckButton to context bitmap
+ nStartPos += nContextWidthDIV2; // center of context bitmap
+ AddTab( nStartPos, TABFLAGS_CONTEXTBMP );
+ nStartPos += nContextWidthDIV2; // right edge of context bitmap
+ // only set a distance if there are bitmaps
+ if( nContextBmpWidthMax )
+ nStartPos += 5; // distance context bitmap to text
+ AddTab( nStartPos, TABFLAGS_TEXT );
+ break;
+
+ case CHECK_BUTTONS :
+ nStartPos += nCheckWidthDIV2;
+ AddTab( nStartPos, TABFLAGS_CHECKBTN );
+ nStartPos += nCheckWidthDIV2; // right edge of CheckButton
+ nStartPos += 3; // distance CheckButton to context bitmap
+ nStartPos += nContextWidthDIV2; // center of context bitmap
+ AddTab( nStartPos, TABFLAGS_CONTEXTBMP );
+ nStartPos += nContextWidthDIV2; // right edge of context bitmap
+ // only set a distance if there are bitmaps
+ if( nContextBmpWidthMax )
+ nStartPos += 5; // distance context bitmap to text
+ AddTab( nStartPos, TABFLAGS_TEXT );
+ break;
+ }
+ pImpl->NotifyTabsChanged();
+}
+
+void SvTreeListBox::InitEntry(SvTreeListEntry* pEntry,
+ const OUString& aStr, const Image& aCollEntryBmp, const Image& aExpEntryBmp)
+{
+ if( nTreeFlags & SvTreeFlags::CHKBTN )
+ {
+ pEntry->AddItem(std::make_unique<SvLBoxButton>(pCheckButtonData));
+ }
+
+ pEntry->AddItem(std::make_unique<SvLBoxContextBmp>( aCollEntryBmp,aExpEntryBmp, mbContextBmpExpanded));
+
+ pEntry->AddItem(std::make_unique<SvLBoxString>(aStr));
+}
+
+OUString SvTreeListBox::GetEntryText(SvTreeListEntry* pEntry) const
+{
+ assert(pEntry);
+ SvLBoxString* pItem = static_cast<SvLBoxString*>(pEntry->GetFirstItem(SvLBoxItemType::String));
+ assert(pItem);
+ return pItem->GetText();
+}
+
+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_uLong 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;
+}
+
+SvTreeListEntry* SvTreeListBox::InsertEntry( const OUString& rText,
+ const Image& aExpEntryBmp, const Image& aCollEntryBmp,
+ SvTreeListEntry* pParent, bool bChildrenOnDemand, sal_uLong nPos, void* pUser )
+{
+ nTreeFlags |= SvTreeFlags::MANINS;
+
+ aCurInsertedExpBmp = aExpEntryBmp;
+ aCurInsertedColBmp = aCollEntryBmp;
+
+ SvTreeListEntry* pEntry = new SvTreeListEntry;
+ pEntry->SetUserData( pUser );
+ InitEntry( pEntry, rText, aCollEntryBmp, aExpEntryBmp );
+
+ pEntry->EnableChildrenOnDemand( bChildrenOnDemand );
+
+ if( !pParent )
+ Insert( pEntry, nPos );
+ else
+ Insert( pEntry, pParent, nPos );
+
+ aPrevInsertedExpBmp = aExpEntryBmp;
+ aPrevInsertedColBmp = aCollEntryBmp;
+
+ 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 );
+
+ GetModel()->InvalidateEntry( pEntry );
+ SetEntryHeight( 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 );
+
+ GetModel()->InvalidateEntry( pEntry );
+ SetEntryHeight( 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;
+ }
+ }
+ SetEntryHeight( 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()
+{
+ aCheckButtonHdl.Call( this );
+ 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::SetNodeBitmaps( const Image& rCollapsedNodeBmp, const Image& rExpandedNodeBmp )
+{
+ SetExpandedNodeBmp( rExpandedNodeBmp );
+ SetCollapsedNodeBmp( rCollapsedNodeBmp );
+ SetTabs();
+}
+
+bool SvTreeListBox::EditingEntry( SvTreeListEntry*, Selection& )
+{
+ 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();
+}
+
+void SvTreeListBox::ScrollOutputArea( short nDeltaEntries )
+{
+ if( !nDeltaEntries || !pImpl->m_aVerSBar->IsVisible() )
+ return;
+
+ long nThumb = pImpl->m_aVerSBar->GetThumbPos();
+ long nMax = pImpl->m_aVerSBar->GetRange().Max();
+
+ if( nDeltaEntries < 0 )
+ {
+ // move window up
+ nDeltaEntries *= -1;
+ long nVis = pImpl->m_aVerSBar->GetVisibleSize();
+ 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();
+ NotifyEndScroll();
+}
+
+void SvTreeListBox::ScrollToAbsPos( 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::SetEntryHeight( 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()
+{
+ Size aSize( GetTextWidth(OUString('X')), GetTextHeight() );
+ if( aSize.Height() > nEntryHeight )
+ {
+ nEntryHeight = static_cast<short>(aSize.Height()) + nEntryHeightOffs;
+ pImpl->SetEntryHeight();
+ }
+}
+
+bool SvTreeListBox::Expand( SvTreeListEntry* pParent )
+{
+ pHdlEntry = pParent;
+ bool bExpanded = false;
+ SvTLEntryFlags nFlags;
+
+ if( pParent->HasChildrenOnDemand() )
+ RequestingChildren( pParent );
+ bool bExpandAllowed = pParent->HasChildren() && ExpandingHdl();
+ // double check if the expander callback ended up removing all children
+ if (pParent->HasChildren())
+ {
+ if (bExpandAllowed)
+ {
+ bExpanded = true;
+ ExpandListEntry( pParent );
+ pImpl->EntryExpanded( pParent );
+ pHdlEntry = pParent;
+ ExpandedHdl();
+ SetAlternatingRowColors( mbAlternatingRowColors );
+ }
+ nFlags = pParent->GetFlags();
+ nFlags &= ~SvTLEntryFlags::NO_NODEBMP;
+ nFlags |= SvTLEntryFlags::HAD_CHILDREN;
+ pParent->SetFlags( nFlags );
+ }
+ else
+ {
+ nFlags = pParent->GetFlags();
+ nFlags |= SvTLEntryFlags::NO_NODEBMP;
+ pParent->SetFlags( nFlags );
+ GetModel()->InvalidateEntry( pParent ); // repaint
+ }
+
+ // #i92103#
+ if ( bExpanded )
+ {
+ pImpl->CallEventListeners( VclEventId::ItemExpanded, pParent );
+ }
+
+ return bExpanded;
+}
+
+bool SvTreeListBox::Collapse( SvTreeListEntry* pParent )
+{
+ pHdlEntry = pParent;
+ bool bCollapsed = false;
+
+ if( ExpandingHdl() )
+ {
+ bCollapsed = true;
+ pImpl->CollapsingEntry( pParent );
+ CollapseListEntry( pParent );
+ pImpl->EntryCollapsed( pParent );
+ pHdlEntry = pParent;
+ ExpandedHdl();
+ SetAlternatingRowColors( mbAlternatingRowColors );
+ }
+
+ // #i92103#
+ if ( bCollapsed )
+ {
+ pImpl->CallEventListeners( VclEventId::ItemCollapsed, pParent );
+ }
+
+ return bCollapsed;
+}
+
+bool SvTreeListBox::Select( SvTreeListEntry* pEntry, bool bSelect )
+{
+ DBG_ASSERT(pEntry,"Select: Null-Ptr");
+ bool bRetVal = SelectListEntry( pEntry, bSelect );
+ DBG_ASSERT(IsSelected(pEntry)==bSelect,"Select failed");
+ if( bRetVal )
+ {
+ pImpl->EntrySelected( pEntry, bSelect );
+ pHdlEntry = pEntry;
+ if( bSelect )
+ {
+ SelectHdl();
+ CallEventListeners( VclEventId::ListboxTreeSelect, pEntry);
+ }
+ else
+ DeselectHdl();
+ }
+ return bRetVal;
+}
+
+sal_uLong SvTreeListBox::SelectChildren( SvTreeListEntry* pParent, bool bSelect )
+{
+ pImpl->DestroyAnchor();
+ sal_uLong 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())
+ {
+ long nHeight = rRenderContext.GetTextHeight();
+ tools::Rectangle aRect(Point(0, 0), Size(GetSizePixel().Width(), nHeight));
+ ShowFocus(aRect);
+ }
+ else
+ {
+ HideFocus();
+ }
+}
+
+void SvTreeListBox::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ pImpl->MouseButtonDown( rMEvt );
+}
+
+void SvTreeListBox::MouseButtonUp( const MouseEvent& rMEvt )
+{
+ pImpl->MouseButtonUp( rMEvt );
+}
+
+void SvTreeListBox::MouseMove( const MouseEvent& rMEvt )
+{
+ pImpl->MouseMove( rMEvt );
+}
+
+
+void SvTreeListBox::SetUpdateMode( bool bUpdate )
+{
+ pImpl->SetUpdateMode( bUpdate );
+ mbUpdateAlternatingRows = bUpdate;
+ SetAlternatingRowColors( mbAlternatingRowColors );
+}
+
+void SvTreeListBox::SetSpaceBetweenEntries( short nOffsLogic )
+{
+ if( nOffsLogic != nEntryHeightOffs )
+ {
+ nEntryHeight = nEntryHeight - nEntryHeightOffs;
+ nEntryHeightOffs = nOffsLogic;
+ nEntryHeight = nEntryHeight + nOffsLogic;
+ AdjustEntryHeightAndRecalc();
+ pImpl->SetEntryHeight();
+ }
+}
+
+void SvTreeListBox::SetCursor( SvTreeListEntry* pEntry, bool bForceNoSelect )
+{
+ pImpl->SetCursor(pEntry, bForceNoSelect);
+}
+
+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 ) );
+ 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();
+ 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;
+
+ long nClickX = pImpl->m_aEditClickPos.X();
+ bool bIsMouseTriggered = nClickX >= 0;
+
+ SvLBoxString* pItem = nullptr;
+ sal_uInt16 nCount = pEntry->ItemCount();
+ 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;
+ }
+ }
+ }
+
+ Selection aSel( SELECTION_MIN, SELECTION_MAX );
+ if( pItem && EditingEntry( pEntry, aSel ) )
+ {
+ SelectAll( false );
+ MakeVisible( pEntry );
+ EditItemText( pEntry, pItem, aSel );
+ }
+}
+
+void SvTreeListBox::EditedText( const OUString& rStr )
+
+{
+ if(pEdEntry) // we have to check if this entry is null that means that it is removed while editing
+ {
+ if( EditedEntry( pEdEntry, rStr ) )
+ {
+ static_cast<SvLBoxString*>(pEdItem)->SetText( rStr );
+ pModel->InvalidateEntry( pEdEntry );
+ }
+ if( GetSelectionCount() == 0 )
+ Select( pEdEntry );
+ if( GetSelectionMode() == SelectionMode::Multiple && !GetCurEntry() )
+ SetCurEntry( pEdEntry );
+ }
+}
+
+SvTreeListEntry* SvTreeListBox::GetDropTarget( const Point& rPos )
+{
+ // scroll
+ if( rPos.Y() < 12 )
+ {
+ ImplShowTargetEmphasis(pTargetEntry, false);
+ ScrollOutputArea( +1 );
+ }
+ else
+ {
+ Size aSize( pImpl->GetOutputSize() );
+ if( rPos.Y() > aSize.Height() - 12 )
+ {
+ ImplShowTargetEmphasis(pTargetEntry, false);
+ ScrollOutputArea( -1 );
+ }
+ }
+
+ SvTreeListEntry* pTarget = pImpl->GetEntry( rPos );
+ // when dropping in a vacant space, use the last entry
+ if( !pTarget )
+ return LastVisible();
+ else if( (GetDragDropMode() & DragDropMode::ENABLE_TOP) &&
+ pTarget == First() && rPos.Y() < 6 )
+ return nullptr;
+
+ return pTarget;
+}
+
+
+SvTreeListEntry* SvTreeListBox::GetEntry( const Point& rPos, bool bHit ) const
+{
+ SvTreeListEntry* pEntry = pImpl->GetEntry( rPos );
+ if( pEntry && bHit )
+ {
+ 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(SortAscending);
+ GetModel()->SetCompareHdl(LINK(this, SvTreeListBox, DefaultCompare));
+ }
+ else
+ {
+ GetModel()->SetSortMode(SortNone);
+ 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, long nLine, vcl::RenderContext& rRenderContext)
+{
+ tools::Rectangle aRect; // multi purpose
+
+ bool bHorSBar = pImpl->HasHorScrollBar();
+ PreparePaint(rRenderContext, rEntry);
+
+ pImpl->UpdateContextBmpWidthMax(&rEntry);
+
+ if (nTreeFlags & SvTreeFlags::RECALCTABS)
+ SetTabs();
+
+ short nTempEntryHeight = GetEntryHeight();
+ 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
+ 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 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));
+ long nTabPos = GetTabPos(&rEntry, pTab);
+
+ long nNextTabPos;
+ if (pNextTab)
+ nNextTabPos = GetTabPos(&rEntry, pNextTab);
+ else
+ {
+ nNextTabPos = nMaxRight;
+ if (nTabPos > nMaxRight)
+ nNextTabPos += 50;
+ }
+
+ long nX;
+ if( pTab->nFlags & SvLBoxTabFlags::ADJUST_RIGHT )
+ // avoid cutting the right edge off the tab separation
+ nX = nTabPos + pTab->CalcOffset(aSize.Width(), (nNextTabPos - SV_TAB_BORDER - 1) - nTabPos);
+ else
+ nX = nTabPos + pTab->CalcOffset(aSize.Width(), nNextTabPos - nTabPos);
+
+ aEntryPos.setX( nX );
+ aEntryPos.setY( nLine );
+
+ // set background pattern/color
+
+ Wallpaper aWallpaper = rRenderContext.GetBackground();
+
+ bool bSelTab = bool(nFlags & SvLBoxTabFlags::SHOW_SELECTION);
+
+ if (pViewDataEntry->IsHighlighted() && bSelTab)
+ {
+ Color aNewWallColor = rSettings.GetHighlightColor();
+ // if the face color is bright then the deactivate color is also bright
+ // -> so you can't see any deactivate selection
+ if (bHideSelection && !rSettings.GetFaceColor().IsBright()
+ && aWallpaper.GetColor().IsBright() != rSettings.GetDeactiveColor().IsBright())
+ {
+ aNewWallColor = rSettings.GetDeactiveColor();
+ }
+ // set font color to highlight
+ if (!bCurFontIsSel)
+ {
+ rRenderContext.SetTextColor(aHighlightTextColor);
+ rRenderContext.SetFont(aHighlightFont);
+ bCurFontIsSel = true;
+ }
+ aWallpaper.SetColor(aNewWallColor);
+ }
+ else // no selection
+ {
+ if (bCurFontIsSel || rEntry.GetTextColor())
+ {
+ bCurFontIsSel = false;
+ if (const auto & xCustomTextColor = rEntry.GetTextColor())
+ rRenderContext.SetTextColor(*xCustomTextColor);
+ else
+ rRenderContext.SetTextColor(aBackupTextColor);
+ rRenderContext.SetFont(aBackupFont);
+ }
+ else
+ {
+ aWallpaper.SetColor(rEntry.GetBackColor());
+ }
+ }
+
+ // draw background
+ if (!(nTreeFlags & SvTreeFlags::USESEL))
+ {
+ // only draw the area that is used by the item
+ aRectSize.setWidth( aSize.Width() );
+ aRect.SetPos(aEntryPos);
+ aRect.SetSize(aRectSize);
+ }
+ else
+ {
+ // draw from the current to the next tab
+ if (nCurTab != 0)
+ aRect.SetLeft( nTabPos );
+ else
+ // if we're in the 0th tab, always draw from column 0 --
+ // else we get problems with centered tabs
+ aRect.SetLeft( 0 );
+ aRect.SetTop( nLine );
+ aRect.SetBottom( nLine + nTempEntryHeight - 1 );
+ if (pNextTab)
+ {
+ 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
+ if (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());
+ 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);
+ 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;
+
+ if (IsExpanded(&rEntry))
+ pImg = &pImpl->GetExpandedNodeBmp();
+ else
+ {
+ if ((!rEntry.HasChildren()) && rEntry.HasChildrenOnDemand() &&
+ (!(rEntry.GetFlags() & SvTLEntryFlags::HAD_CHILDREN)) &&
+ pImpl->GetDontKnowNodeBmp().GetSizePixel().Width())
+ {
+ pImg = &pImpl->GetDontKnowNodeBmp( );
+ }
+ else
+ {
+ pImg = &pImpl->GetCollapsedNodeBmp( );
+ }
+ }
+ aPos.AdjustY((nTempEntryHeight - pImg->GetSizePixel().Height()) / 2 );
+
+ DrawImageFlags nStyle = DrawImageFlags::NONE;
+ if (!IsEnabled())
+ nStyle |= DrawImageFlags::Disable;
+
+ //native
+ bool bNativeOK = false;
+ 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 (IsExpanded(&rEntry))
+ aControlValue.setTristateVal(ButtonValue::On); //expanded node
+ else
+ {
+ if ((!rEntry.HasChildren()) && rEntry.HasChildrenOnDemand() &&
+ (!(rEntry.GetFlags() & SvTLEntryFlags::HAD_CHILDREN)) &&
+ pImpl->GetDontKnowNodeBmp().GetSizePixel().Width())
+ {
+ 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)
+ {
+ rRenderContext.DrawImage(aPos, *pImg ,nStyle);
+ }
+}
+
+void SvTreeListBox::PreparePaint(vcl::RenderContext& /*rRenderContext*/, SvTreeListEntry& /*rEntry*/)
+{
+}
+
+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)
+{
+ return aCustomMeasureHdl.Call(std::pair<vcl::RenderContext&, const SvTreeListEntry&>(rRenderContext, rEntry));
+}
+
+tools::Rectangle SvTreeListBox::GetFocusRect(const SvTreeListEntry* pEntry, long nLine )
+{
+ pImpl->UpdateContextBmpWidthMax( pEntry );
+
+ Size aSize;
+ tools::Rectangle aRect;
+ aRect.SetTop( nLine );
+ aSize.setHeight( GetEntryHeight() );
+
+ long nRealWidth = pImpl->GetOutputSize().Width();
+ nRealWidth -= GetMapMode().GetOrigin().X();
+
+ sal_uInt16 nCurTab;
+ SvLBoxTab* pTab = GetFirstTab( SvLBoxTabFlags::SHOW_SELECTION, nCurTab );
+ long nTabPos = 0;
+ if( pTab )
+ nTabPos = GetTabPos( pEntry, pTab );
+ 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 );
+ 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, SvLBoxTab* pTab)
+{
+ 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, 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();
+ long nRealWidth = pImpl->GetOutputSize().Width();
+ nRealWidth -= GetMapMode().GetOrigin().X();
+
+ while( true )
+ {
+ SvLBoxTab* pNextTab=nNextItem<nTabCount ? aTabs[nNextItem].get() : nullptr;
+ long nStart = GetTabPos( pEntry, pTab );
+
+ 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 )
+ {
+ 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;
+}
+
+long SvTreeListBox::getPreferredDimensions(std::vector<long> &rWidths) const
+{
+ 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<long> aWidths;
+ Size aRet(0, getPreferredDimensions(aWidths));
+ for (long aWidth : aWidths)
+ aRet.AdjustWidth(aWidth );
+ if (GetStyle() & WB_BORDER)
+ {
+ aRet.AdjustWidth(StyleSettings::GetBorderSize() * 2 );
+ aRet.AdjustHeight(StyleSettings::GetBorderSize() * 2 );
+ }
+ long nMinWidth = nMinWidthInChars * approximate_char_width();
+ aRet.setWidth( std::max(aRet.Width(), nMinWidth) );
+
+ if (GetStyle() & WB_VSCROLL)
+ aRet.AdjustWidth(GetSettings().GetStyleSettings().GetScrollBarSize());
+
+ return aRet;
+}
+
+void SvTreeListBox::SetAlternatingRowColors( bool bEnable )
+{
+ if( !mbUpdateAlternatingRows )
+ {
+ mbAlternatingRowColors = bEnable;
+ return;
+ }
+
+ if( bEnable )
+ {
+ SvTreeListEntry* pEntry = pModel->First();
+ for(size_t i = 0; pEntry; ++i)
+ {
+ pEntry->SetBackColor( i % 2 == 0 ? GetBackground().GetColor() : GetSettings().GetStyleSettings().GetAlternatingRowColor());
+ SvTreeListEntry *pNextEntry = nullptr;
+ if( IsExpanded( pEntry ) )
+ pNextEntry = pModel->FirstChild( pEntry );
+ else
+ pNextEntry = pEntry->NextSibling();
+
+ if( !pNextEntry )
+ pEntry = pModel->Next( pEntry );
+ else
+ pEntry = pNextEntry;
+ }
+ }
+ else if( mbAlternatingRowColors )
+ for(SvTreeListEntry* pEntry = pModel->First(); pEntry; pEntry = pModel->Next(pEntry))
+ pEntry->SetBackColor( GetBackground().GetColor() );
+
+ mbAlternatingRowColors = bEnable;
+ pImpl->UpdateAll(true);
+}
+
+void SvTreeListBox::SetForceMakeVisible( bool bEnable )
+{
+ pImpl->SetForceMakeVisible(bEnable);
+}
+
+SvLBoxItem* SvTreeListBox::GetItem(SvTreeListEntry* pEntry,long nX,SvLBoxTab** ppTab)
+{
+ return GetItem_Impl( pEntry, nX, ppTab );
+}
+
+SvLBoxItem* SvTreeListBox::GetItem(SvTreeListEntry* pEntry,long nX )
+{
+ SvLBoxTab* pDummyTab;
+ return GetItem_Impl( pEntry, nX, &pDummyTab );
+}
+
+void SvTreeListBox::AddTab(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::NotifyEndScroll()
+{
+}
+
+void SvTreeListBox::NotifyScrolled()
+{
+ aScrolledHdl.Call( this );
+}
+
+void SvTreeListBox::Invalidate( InvalidateFlags nInvalidateFlags )
+{
+ if (!pImpl)
+ return;
+ if( nFocusWidth == -1 )
+ // to make sure that the control doesn't show the wrong focus rectangle
+ // after painting
+ pImpl->RecalcFocusRect();
+ Control::Invalidate( nInvalidateFlags );
+ pImpl->Invalidate();
+}
+
+void SvTreeListBox::Invalidate( const tools::Rectangle& rRect, InvalidateFlags nInvalidateFlags )
+{
+ if( nFocusWidth == -1 )
+ // to make sure that the control doesn't show the wrong focus rectangle
+ // after painting
+ pImpl->RecalcFocusRect();
+ Control::Invalidate( rRect, nInvalidateFlags );
+}
+
+
+void SvTreeListBox::SetHighlightRange( sal_uInt16 nStart, sal_uInt16 nEnd)
+{
+
+ sal_uInt16 nTemp;
+ nTreeFlags |= SvTreeFlags::USESEL;
+ if( nStart > nEnd )
+ {
+ nTemp = nStart;
+ nStart = nEnd;
+ nEnd = nTemp;
+ }
+ // select all tabs that lie within the area
+ nTreeFlags |= SvTreeFlags::RECALCTABS;
+ nFirstSelTab = nStart;
+ nLastSelTab = nEnd;
+ pImpl->RecalcFocusRect();
+}
+
+void SvTreeListBox::Command(const CommandEvent& rCEvt)
+{
+ if (!aPopupMenuHdl.Call(rCEvt))
+ pImpl->Command(rCEvt);
+ //pass at least alt press/release to parent impl
+ if (rCEvt.GetCommand() == CommandEventId::ModKeyChange)
+ Control::Command(rCEvt);
+}
+
+SvLBoxTab* SvTreeListBox::GetFirstTab( SvLBoxTabFlags nFlagMask, sal_uInt16& rPos )
+{
+ sal_uInt16 nTabCount = aTabs.size();
+ for( sal_uInt16 nPos = 0; nPos < nTabCount; nPos++ )
+ {
+ SvLBoxTab* pTab = aTabs[ nPos ].get();
+ if( pTab->nFlags & nFlagMask )
+ {
+ rPos = nPos;
+ return pTab;
+ }
+ }
+ rPos = 0xffff;
+ return nullptr;
+}
+
+void SvTreeListBox::GetLastTab( SvLBoxTabFlags nFlagMask, sal_uInt16& rTabPos )
+{
+ sal_uInt16 nPos = static_cast<sal_uInt16>(aTabs.size());
+ while( nPos )
+ {
+ --nPos;
+ SvLBoxTab* pTab = aTabs[ nPos ].get();
+ if( pTab->nFlags & nFlagMask )
+ {
+ rTabPos = nPos;
+ return;
+ }
+ }
+ rTabPos = 0xffff;
+}
+
+void SvTreeListBox::RequestHelp( const HelpEvent& rHEvt )
+{
+ if (aTooltipHdl.IsSet() && aTooltipHdl.Call(rHEvt))
+ return;
+
+ if( !pImpl->RequestHelp( rHEvt ) )
+ Control::RequestHelp( rHEvt );
+}
+
+sal_Int32 SvTreeListBox::DefaultCompare(const SvLBoxString* pLeftText, const SvLBoxString* pRightText)
+{
+ OUString aLeft = pLeftText ? pLeftText->GetText() : OUString();
+ OUString aRight = pRightText ? pRightText->GetText() : OUString();
+ pImpl->UpdateStringSorter();
+ return pImpl->m_pStringSorter->compare(aLeft, aRight);
+}
+
+IMPL_LINK( SvTreeListBox, DefaultCompare, const SvSortData&, rData, sal_Int32 )
+{
+ const SvTreeListEntry* pLeft = rData.pLeft;
+ const SvTreeListEntry* pRight = rData.pRight;
+ const SvLBoxString* pLeftText = static_cast<const SvLBoxString*>(pLeft->GetFirstItem(SvLBoxItemType::String));
+ const SvLBoxString* pRightText = static_cast<const SvLBoxString*>(pRight->GetFirstItem(SvLBoxItemType::String));
+ return DefaultCompare(pLeftText, pRightText);
+}
+
+void SvTreeListBox::ModelNotification( SvListAction nActionId, SvTreeListEntry* pEntry1,
+ SvTreeListEntry* pEntry2, sal_uLong 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;
+ }
+}
+
+void SvTreeListBox::EndSelection()
+{
+ pImpl->EndSelection();
+}
+
+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::ShowFocusRect( const SvTreeListEntry* pEntry )
+{
+ pImpl->ShowFocusRect( pEntry );
+}
+
+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(*this));
+
+ 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();
+ aFont.SetColor(rStyleSettings.GetWindowTextColor());
+ SetPointFont(*this, aFont);
+ AdjustEntryHeightAndRecalc();
+
+ SetTextColor(rStyleSettings.GetFieldTextColor());
+ SetTextFillColor();
+
+ SetBackground(rStyleSettings.GetFieldColor());
+
+ // always try to re-create default-SvLBoxButtonData
+ if( pCheckButtonData && pCheckButtonData->HasDefaultImages() )
+ pCheckButtonData->SetDefaultImages(this);
+}
+
+VclPtr<PopupMenu> SvTreeListBox::CreateContextMenu()
+{
+ return nullptr;
+}
+
+void SvTreeListBox::ExecuteContextMenuAction( sal_uInt16 )
+{
+ SAL_INFO( "svtools.contnr", "SvTreeListBox::ExecuteContextMenuAction(): now there's happening nothing!" );
+}
+
+void SvTreeListBox::EnableContextMenuHandling()
+{
+ assert(pImpl && "-SvTreeListBox::EnableContextMenuHandling(): No implementation!");
+ pImpl->m_bContextMenuHandling = true;
+}
+
+css::uno::Reference< XAccessible > SvTreeListBox::CreateAccessible()
+{
+ vcl::Window* pParent = GetAccessibleParentWindow();
+ DBG_ASSERT( pParent, "SvTreeListBox::CreateAccessible - accessible parent not found" );
+
+ css::uno::Reference< XAccessible > xAccessible;
+ if ( pParent )
+ {
+ css::uno::Reference< XAccessible > xAccParent = pParent->GetAccessible();
+ if ( xAccParent.is() )
+ {
+ // need to be done here to get the vclxwindow later on in the accessible
+ css::uno::Reference< css::awt::XWindowPeer > xHoldAlive(GetComponentInterface());
+ xAccessible = pImpl->m_aFactoryAccess.getFactory().createAccessibleTreeListBox( *this, xAccParent );
+ }
+ }
+ return xAccessible;
+}
+
+void SvTreeListBox::FillAccessibleEntryStateSet( SvTreeListEntry* pEntry, ::utl::AccessibleStateSetHelper& rStateSet ) const
+{
+ assert(pEntry && "SvTreeListBox::FillAccessibleEntryStateSet: invalid entry");
+
+ if ( pEntry->HasChildrenOnDemand() || pEntry->HasChildren() )
+ {
+ rStateSet.AddState( AccessibleStateType::EXPANDABLE );
+ if ( IsExpanded( pEntry ) )
+ rStateSet.AddState( sal_Int16(AccessibleStateType::EXPANDED) );
+ }
+
+ if ( GetCheckButtonState( pEntry ) == SvButtonState::Checked )
+ rStateSet.AddState( AccessibleStateType::CHECKED );
+ if ( IsEntryVisible( pEntry ) )
+ rStateSet.AddState( AccessibleStateType::VISIBLE );
+ if ( IsSelected( pEntry ) )
+ rStateSet.AddState( AccessibleStateType::SELECTED );
+ if ( IsEnabled() )
+ {
+ rStateSet.AddState( AccessibleStateType::ENABLED );
+ rStateSet.AddState( AccessibleStateType::FOCUSABLE );
+ rStateSet.AddState( AccessibleStateType::SELECTABLE );
+ SvViewDataEntry* pViewDataNewCur = GetViewDataEntry(pEntry);
+ if (pViewDataNewCur && pViewDataNewCur->HasFocus())
+ rStateSet.AddState( AccessibleStateType::FOCUSED );
+ }
+}
+
+tools::Rectangle SvTreeListBox::GetBoundingRect(const SvTreeListEntry* pEntry)
+{
+ Point aPos = GetEntryPosition( pEntry );
+ tools::Rectangle aRect = GetFocusRect( pEntry, aPos.Y() );
+ return aRect;
+}
+
+void SvTreeListBox::CallImplEventListeners(VclEventId nEvent, void* pData)
+{
+ CallEventListeners(nEvent, pData);
+}
+
+void SvTreeListBox::set_min_width_in_chars(sal_Int32 nChars)
+{
+ nMinWidthInChars = nChars;
+ queue_resize();
+}
+
+bool SvTreeListBox::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "min-width-chars")
+ {
+ set_min_width_in_chars(rValue.toInt32());
+ }
+ else if (rKey == "enable-tree-lines")
+ {
+ auto nStyle = GetStyle();
+ nStyle &= ~(WB_HASLINES | WB_HASLINESATROOT);
+ if (toBool(rValue))
+ nStyle |= (WB_HASLINES | WB_HASLINESATROOT);
+ SetStyle(nStyle);
+ }
+ else if (rKey == "show-expanders")
+ {
+ auto nStyle = GetStyle();
+ nStyle &= ~(WB_HASBUTTONS | WB_HASBUTTONSATROOT);
+ if (toBool(rValue))
+ nStyle |= (WB_HASBUTTONS | WB_HASBUTTONSATROOT);
+ SetStyle(nStyle);
+ }
+ else if (rKey == "rules-hint")
+ {
+ SetAlternatingRowColors(toBool(rValue));
+ }
+ else if (rKey == "enable-search")
+ {
+ SetQuickSearch(toBool(rValue));
+ }
+ else if (rKey == "activate-on-single-click")
+ {
+ SetActivateOnSingleClick(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;
+}
+
+FactoryFunction SvTreeListBox::GetUITestFactory() const
+{
+ return TreeListUIObject::create;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/treelist/treelistentry.cxx b/vcl/source/treelist/treelistentry.cxx
new file mode 100644
index 000000000..5ffd4428b
--- /dev/null
+++ b/vcl/source/treelist/treelistentry.cxx
@@ -0,0 +1,235 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <memory>
+#include <vcl/treelistentry.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <tools/debug.hxx>
+
+void SvTreeListEntry::ClearChildren()
+{
+ m_Children.clear();
+}
+
+void SvTreeListEntry::SetListPositions()
+{
+ sal_uLong nCur = 0;
+ for (auto const& pEntry : m_Children)
+ {
+ SvTreeListEntry& rEntry = *pEntry;
+ rEntry.nListPos &= 0x80000000;
+ rEntry.nListPos |= nCur;
+ ++nCur;
+ }
+
+ nListPos &= (~0x80000000); // remove the invalid bit.
+}
+
+void SvTreeListEntry::InvalidateChildrensListPositions()
+{
+ nListPos |= 0x80000000;
+}
+
+SvTreeListEntry::SvTreeListEntry()
+ : pParent(nullptr)
+ , nAbsPos(0)
+ , nListPos(0)
+ , mnExtraIndent(0)
+ , pUserData(nullptr)
+ , nEntryFlags(SvTLEntryFlags::NONE)
+ , maBackColor(Application::GetSettings().GetStyleSettings().GetWindowColor())
+{
+}
+
+SvTreeListEntry::~SvTreeListEntry()
+{
+#ifdef DBG_UTIL
+ pParent = nullptr;
+#endif
+
+ m_Children.clear();
+ m_Items.clear();
+}
+
+bool SvTreeListEntry::HasChildren() const
+{
+ return !m_Children.empty();
+}
+
+bool SvTreeListEntry::HasChildListPos() const
+{
+ return pParent && !(pParent->nListPos & 0x80000000);
+}
+
+sal_uLong 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_uLong nPos = GetChildListPos();
+ nPos++;
+ return (nPos < rList.size()) ? rList[nPos].get() : nullptr;
+}
+
+SvTreeListEntry* SvTreeListEntry::PrevSibling() const
+{
+ SvTreeListEntries& rList = pParent->m_Children;
+ sal_uLong nPos = GetChildListPos();
+ if ( nPos == 0 )
+ return nullptr;
+ nPos--;
+ return rList[nPos].get();
+}
+
+
+SvTreeListEntry* SvTreeListEntry::LastSibling() const
+{
+ SvTreeListEntries& rChildren = pParent->m_Children;
+ return (rChildren.empty()) ? nullptr : rChildren.back().get();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/treelist/uiobject.cxx b/vcl/source/treelist/uiobject.cxx
new file mode 100644
index 000000000..a2f18abee
--- /dev/null
+++ b/vcl/source/treelist/uiobject.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/.
+ */
+
+#include <memory>
+#include <vcl/svlbitm.hxx>
+#include <vcl/uitest/uiobject.hxx>
+#include <vcl/treelistbox.hxx>
+#include <vcl/treelistentry.hxx>
+
+TreeListUIObject::TreeListUIObject(const VclPtr<SvTreeListBox>& xTreeList):
+ WindowUIObject(xTreeList),
+ mxTreeList(xTreeList)
+{
+}
+
+namespace {
+
+bool isCheckBoxList(const VclPtr<SvTreeListBox>& xTreeList)
+{
+ return (xTreeList->GetTreeFlags() & SvTreeFlags::CHKBTN) == SvTreeFlags::CHKBTN;
+}
+
+}
+
+StringMap TreeListUIObject::get_state()
+{
+ StringMap aMap = WindowUIObject::get_state();
+
+ aMap["SelectionCount"] = OUString::number(mxTreeList->GetSelectionCount());
+ aMap["VisibleCount"] = OUString::number(mxTreeList->GetVisibleCount());
+ aMap["Children"] = OUString::number(mxTreeList->GetChildCount(nullptr));
+ aMap["LevelChildren"] = OUString::number(mxTreeList->GetLevelChildCount(nullptr));
+ aMap["CheckBoxList"] = OUString::boolean(isCheckBoxList(mxTreeList));
+ SvTreeListEntry* pEntry = mxTreeList->FirstSelected();
+ aMap["SelectEntryText"] = pEntry ? mxTreeList->GetEntryText(pEntry) : OUString();
+
+ return aMap;
+}
+
+void TreeListUIObject::execute(const OUString& rAction,
+ const StringMap& rParameters)
+{
+ if (rAction.isEmpty())
+ {
+ }
+ else
+ WindowUIObject::execute(rAction, rParameters);
+}
+
+std::unique_ptr<UIObject> TreeListUIObject::get_child(const OUString& rID)
+{
+ sal_Int32 nID = rID.toInt32();
+ if (nID >= 0)
+ {
+ SvTreeListEntry* pEntry = mxTreeList->GetEntry(nullptr, nID);
+ if (!pEntry)
+ return nullptr;
+
+ return std::unique_ptr<UIObject>(new TreeListEntryUIObject(mxTreeList, pEntry));
+ }
+
+ return nullptr;
+}
+
+std::set<OUString> TreeListUIObject::get_children() const
+{
+ std::set<OUString> aChildren;
+
+ size_t nChildren = mxTreeList->GetLevelChildCount(nullptr);
+ for (size_t i = 0; i < nChildren; ++i)
+ {
+ aChildren.insert(OUString::number(i));
+ }
+
+ return aChildren;
+}
+
+OUString TreeListUIObject::get_name() const
+{
+ return "TreeListUIObject";
+}
+
+std::unique_ptr<UIObject> TreeListUIObject::create(vcl::Window* pWindow)
+{
+ SvTreeListBox* pTreeList = dynamic_cast<SvTreeListBox*>(pWindow);
+ assert(pTreeList);
+ return std::unique_ptr<UIObject>(new TreeListUIObject(pTreeList));
+}
+
+TreeListEntryUIObject::TreeListEntryUIObject(const VclPtr<SvTreeListBox>& xTreeList, SvTreeListEntry* pEntry):
+ mxTreeList(xTreeList),
+ mpEntry(pEntry)
+{
+}
+
+StringMap TreeListEntryUIObject::get_state()
+{
+ StringMap aMap;
+
+ aMap["Text"] = mxTreeList->GetEntryText(mpEntry);
+ aMap["Children"] = OUString::number(mxTreeList->GetLevelChildCount(mpEntry));
+ aMap["VisibleChildCount"] = OUString::number(mxTreeList->GetVisibleChildCount(mpEntry));
+ aMap["IsSelected"] = OUString::boolean(mxTreeList->IsSelected(mpEntry));
+
+ return aMap;
+}
+
+void TreeListEntryUIObject::execute(const OUString& rAction, const StringMap& /*rParameters*/)
+{
+ if (rAction == "COLLAPSE")
+ {
+ mxTreeList->Collapse(mpEntry);
+ }
+ else if (rAction == "EXPAND")
+ {
+ mxTreeList->Expand(mpEntry);
+ }
+ else if (rAction == "SELECT")
+ {
+ mxTreeList->Select(mpEntry);
+ }
+ else if (rAction == "DESELECT")
+ {
+ mxTreeList->Select(mpEntry, false);
+ }
+ else if (rAction == "CLICK")
+ {
+ SvLBoxButton* pItem = static_cast<SvLBoxButton*>(mpEntry->GetFirstItem(SvLBoxItemType::Button));
+ if (!pItem)
+ return;
+ pItem->ClickHdl(mpEntry);
+ }
+ else if (rAction == "DOUBLECLICK")
+ {
+ mxTreeList->Select(mpEntry);
+ mxTreeList->DoubleClickHdl();
+ }
+}
+
+std::unique_ptr<UIObject> TreeListEntryUIObject::get_child(const OUString& rID)
+{
+ sal_Int32 nID = rID.toInt32();
+ if (nID >= 0)
+ {
+ SvTreeListEntry* pEntry = mxTreeList->GetEntry(mpEntry, nID);
+ if (!pEntry)
+ return nullptr;
+
+ return std::unique_ptr<UIObject>(new TreeListEntryUIObject(mxTreeList, pEntry));
+ }
+
+ return nullptr;
+}
+
+std::set<OUString> TreeListEntryUIObject::get_children() const
+{
+ std::set<OUString> aChildren;
+
+ size_t nChildren = mxTreeList->GetLevelChildCount(mpEntry);
+ for (size_t i = 0; i < nChildren; ++i)
+ {
+ aChildren.insert(OUString::number(i));
+ }
+
+ return aChildren;
+}
+
+OUString TreeListEntryUIObject::get_type() const
+{
+ return "TreeListEntry";
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/treelist/viewdataentry.cxx b/vcl/source/treelist/viewdataentry.cxx
new file mode 100644
index 000000000..f5fbf8e0b
--- /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/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/uipreviewer/previewer.cxx b/vcl/source/uipreviewer/previewer.cxx
new file mode 100644
index 000000000..ab4d5f91d
--- /dev/null
+++ b/vcl/source/uipreviewer/previewer.cxx
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <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 <vcl/builder.hxx>
+#include <vcl/toolkit/dialog.hxx>
+#include <vcl/help.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/vclmain.hxx>
+
+namespace {
+
+class UIPreviewApp : public Application
+{
+public:
+ virtual void Init() override;
+ virtual int Main() override;
+};
+
+}
+
+using namespace com::sun::star;
+
+void UIPreviewApp::Init()
+{
+ 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 UIPreviewApp::Main()
+{
+ std::vector<OUString> uifiles;
+ for (sal_uInt16 i = 0; i < GetCommandLineParamCount(); ++i)
+ {
+ OUString aFileUrl;
+ osl::File::getFileURLFromSystemPath(GetCommandLineParam(i), aFileUrl);
+ uifiles.push_back(aFileUrl);
+ }
+
+ if (uifiles.empty())
+ {
+ fprintf(stderr, "Usage: ui-previewer file.ui\n");
+ return EXIT_FAILURE;
+ }
+
+ // turn on tooltips
+ Help::EnableQuickHelp();
+
+ int nRet = EXIT_SUCCESS;
+
+ try
+ {
+ VclPtrInstance<Dialog> pDialog(nullptr, WB_STDDIALOG | WB_SIZEABLE, Dialog::InitFlag::NoParent);
+ {
+ VclBuilder aBuilder(pDialog, OUString(), uifiles[0]);
+ vcl::Window *pRoot = aBuilder.get_widget_root();
+ Dialog *pRealDialog = dynamic_cast<Dialog*>(pRoot);
+
+ if (!pRealDialog)
+ pRealDialog = pDialog;
+
+ pRealDialog->SetText("LibreOffice ui-previewer");
+ pRealDialog->SetStyle(pDialog->GetStyle()|WB_CLOSEABLE);
+ /*
+ Force a new StateChangedType::InitShow for the edge case where pRoot
+ is not a dialog or contents of a dialog, but instead a visible floating window
+ which may have had initshow already done before it was given children
+ */
+ pRoot->Hide();
+ pRoot->Show();
+ pRealDialog->Execute();
+ }
+
+ pDialog.disposeAndClear();
+ }
+ 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;
+ }
+
+ return nRet;
+}
+
+void vclmain::createApplication()
+{
+ static UIPreviewApp aApp;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/uitest/logger.cxx b/vcl/source/uitest/logger.cxx
new file mode 100644
index 000000000..13cbf5a7a
--- /dev/null
+++ b/vcl/source/uitest/logger.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/.
+ */
+
+#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();
+ // DIALOG to MODALDIALOG
+ if (nType >= WindowType::DIALOG && nType <= WindowType::MODALDIALOG)
+ 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()
+ : maStream()
+ , 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(const OUString& 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("\"").append(rProp.Name).append("\": ");
+ aBuffer.append(OUString::number(nValue)).append(", ");
+ }
+ else if (aTypeName == "unsigned long")
+ {
+ sal_uInt32 nValue = 0;
+ rProp.Value >>= nValue;
+ aBuffer.append("\"").append(rProp.Name).append("\": ");
+ aBuffer.append(OUString::number(nValue)).append(", ");
+ }
+ else if (aTypeName == "boolean")
+ {
+ bool bValue = false;
+ rProp.Value >>= bValue;
+ aBuffer.append("\"").append(rProp.Name).append("\": ");
+ 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::log(const OUString& rString)
+{
+ if (!mbValid)
+ return;
+
+ if (rString.isEmpty())
+ 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->IsTopWindow())
+ {
+ pParent = pParent->GetParent();
+ }
+
+ OUString aParentID = pParent->get_id();
+
+ 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("\"")
+ .append(itr->first)
+ .append("\": \"")
+ .append(itr->second)
+ .append("\"");
+ }
+
+ aParameterString.append("}");
+
+ return aParameterString.makeStringAndClear();
+}
+
+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;
+}
+
+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.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 AutoFilter 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.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.aParent == "element_selector")
+ {
+ aLogLine = "Select element no " + rDescription.aID + " From " + rDescription.aParent;
+ }
+ else
+ {
+ aLogLine = rDescription.aKeyWord + " Action:" + rDescription.aAction + " Id:"
+ + rDescription.aID + " Parent:" + rDescription.aParent + aParameterString;
+ }
+ log(aLogLine);
+}
+
+UITestLogger& UITestLogger::getInstance()
+{
+ ImplSVData* const pSVData = ImplGetSVData();
+ assert(pSVData);
+
+ if (!pSVData->maFrameData.m_pUITestLogger)
+ {
+ pSVData->maFrameData.m_pUITestLogger.reset(new UITestLogger);
+ }
+
+ return *pSVData->maFrameData.m_pUITestLogger;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/uitest/uiobject.cxx b/vcl/source/uitest/uiobject.cxx
new file mode 100644
index 000000000..5fb0a5c01
--- /dev/null
+++ b/vcl/source/uitest/uiobject.cxx
@@ -0,0 +1,1506 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/floatwin.hxx>
+#include <vcl/tabpage.hxx>
+#include <vcl/tabctrl.hxx>
+#include <vcl/lstbox.hxx>
+#include <vcl/toolkit/spin.hxx>
+#include <vcl/fmtfield.hxx>
+#include <vcl/spinfld.hxx>
+#include <vcl/toolkit/button.hxx>
+#include <vcl/toolkit/dialog.hxx>
+#include <vcl/toolkit/field.hxx>
+#include <vcl/edit.hxx>
+#include <vcl/vclmedit.hxx>
+#include <vcl/uitest/logger.hxx>
+#include <uiobject-internal.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();
+ // DIALOG to MODALDIALOG
+ if (nType >= WindowType::DIALOG && nType <= WindowType::MODALDIALOG)
+ return true;
+
+ // MESSBOX, INFOBOX, WARNINGBOX, ERRORBOX, QUERYBOX
+ if (nType >= WindowType::MESSBOX && nType <= WindowType::QUERYBOX)
+ return true;
+
+ if (nType == WindowType::TABDIALOG)
+ return true;
+
+ return false;
+}
+
+bool isTopWindow(vcl::Window const * pWindow)
+{
+ WindowType eType = pWindow->GetType();
+ if (eType == WindowType::FLOATINGWINDOW)
+ {
+ return pWindow->GetStyle() & WB_SYSTEMFLOATWIN;
+ }
+ return false;
+}
+
+vcl::Window* get_top_parent(vcl::Window* pWindow)
+{
+ if (isDialogWindow(pWindow) || isTopWindow(pWindow))
+ return pWindow;
+
+ vcl::Window* pParent = pWindow->GetParent();
+ if (!pParent)
+ return pWindow;
+
+ return get_top_parent(pParent);
+}
+
+std::vector<KeyEvent> generate_key_events_from_text(const OUString& rStr)
+{
+ std::vector<KeyEvent> aEvents;
+ vcl::KeyCode aCode;
+ for (sal_Int32 i = 0, n = rStr.getLength();
+ i != n; ++i)
+ {
+ aEvents.emplace_back(rStr[i], aCode);
+ }
+ return aEvents;
+}
+
+sal_uInt16 get_key(sal_Unicode cChar, bool& bShift)
+{
+ bShift = false;
+ if (cChar >= 'a' && cChar <= 'z')
+ return KEY_A + (cChar - 'a');
+ else if (cChar >= 'A' && cChar <= 'Z')
+ {
+ bShift = true;
+ return KEY_A + (cChar - 'A');
+ }
+ else if (cChar >= '0' && cChar <= '9')
+ return KEY_0 + (cChar - 'A');
+
+ return cChar;
+}
+
+bool isFunctionKey(const OUString& rStr, sal_uInt16& rKeyCode)
+{
+ std::map<OUString, sal_uInt16> aFunctionKeyMap = {
+ {"F1", KEY_F1},
+ {"F2", KEY_F2},
+ {"F3", KEY_F3},
+ {"F4", KEY_F4},
+ {"F5", KEY_F5},
+ {"F6", KEY_F6},
+ {"F7", KEY_F7},
+ {"F8", KEY_F8},
+ {"F9", KEY_F9},
+ {"F10", KEY_F10},
+ {"F11", KEY_F11},
+ {"F12", KEY_F12}
+ };
+
+ rKeyCode = 0;
+ auto itr = aFunctionKeyMap.find(rStr);
+ if (itr == aFunctionKeyMap.end())
+ return false;
+
+ rKeyCode = itr->second;
+ return true;
+}
+
+std::vector<KeyEvent> generate_key_events_from_keycode(const OUString& 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},
+ {"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["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)
+{
+ bool bHandled = true;
+ 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
+ {
+ SAL_WARN("vcl.uitest", "missing parameter TEXT to action TYPE");
+ return;
+ }
+ }
+ else if (rAction == "FOCUS")
+ {
+ mxWindow->GrabFocus();
+ }
+ else
+ {
+ bHandled = false;
+ }
+
+ if (!bHandled)
+ {
+ SAL_WARN("vcl.uitest", "unknown action or parameter for " << get_name() << ". Action: " << rAction);
+ }
+}
+
+OUString WindowUIObject::get_type() const
+{
+ return get_name();
+}
+
+namespace {
+
+vcl::Window* findChild(vcl::Window* pParent, const OUString& rID)
+{
+ if (!pParent)
+ 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);
+ if (pChild && pChild->get_id() == rID)
+ return pChild;
+
+ vcl::Window* pResult = findChild(pChild, rID);
+ if (pResult)
+ return pResult;
+ }
+
+ return nullptr;
+}
+
+void addChildren(vcl::Window const * pParent, std::set<OUString>& rChildren)
+{
+ if (!pParent)
+ return;
+
+ size_t nCount = pParent->GetChildCount();
+ for (size_t i = 0; i < nCount; ++i)
+ {
+ vcl::Window* pChild = pParent->GetChild(i);
+ if (pChild)
+ {
+ OUString aId = pChild->get_id();
+ if (!aId.isEmpty())
+ {
+ auto ret = rChildren.insert(aId);
+ SAL_WARN_IF(!ret.second, "vcl.uitest", "duplicate ids for ui elements. violates locally unique requirement");
+ }
+
+ addChildren(pChild, rChildren);
+ }
+ }
+}
+
+}
+
+std::unique_ptr<UIObject> WindowUIObject::get_child(const OUString& rID)
+{
+ // in a first step try the real children before moving to the top level parent
+ // This makes it easier to handle cases with the same ID as there is a way
+ // to resolve conflicts
+ vcl::Window* pWindow = findChild(mxWindow.get(), rID);
+ if (!pWindow)
+ {
+ vcl::Window* pDialogParent = get_top_parent(mxWindow.get());
+ pWindow = findChild(pDialogParent, rID);
+ }
+
+ if (!pWindow)
+ throw css::uno::RuntimeException("Could not find child with id: " + rID);
+
+ FactoryFunction aFunction = pWindow->GetUITestFactory();
+ return aFunction(pWindow);
+}
+
+std::set<OUString> WindowUIObject::get_children() const
+{
+ vcl::Window* pDialogParent = get_top_parent(mxWindow.get());
+ std::set<OUString> aChildren;
+ 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 "";
+ //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 == "SET")
+ {
+ if (rParameters.find("TEXT") != rParameters.end())
+ {
+ auto it = rParameters.find("TEXT");
+ if (it == rParameters.end())
+ {
+ SAL_WARN("vcl.uitest", "missing parameter TEXT to action SET");
+ return;
+ }
+
+ 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 == "SELECT")
+ {
+ if (rParameters.find("FROM") != rParameters.end() &&
+ rParameters.find("TO") != rParameters.end())
+ {
+ long nMin = rParameters.find("FROM")->second.toInt32();
+ 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["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();
+ long nMin = rSelection.Min();
+ 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())
+ {
+ long nMin = rParameters.find("FROM")->second.toInt32();
+ 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));
+}
+
+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();
+ }
+}
+
+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();
+ }
+}
+
+StringMap RadioButtonUIObject::get_state()
+{
+ StringMap aMap = WindowUIObject::get_state();
+ aMap["Checked"] = OUString::boolean(mxRadioButton->IsChecked());
+
+ return aMap;
+}
+
+OUString RadioButtonUIObject::get_name() const
+{
+ return "RadioButtonUIObject";
+}
+
+OUString RadioButtonUIObject::get_action(VclEventId nEvent) const
+{
+ if (nEvent == VclEventId::RadiobuttonToggle)
+ {
+ if(get_top_parent(mxRadioButton)->get_id().isEmpty()){
+ //This part because if we don't have parent
+ return "Select '" + mxRadioButton->get_id() + "' RadioButton";
+ }
+ return "Select '" + mxRadioButton->get_id() + "' RadioButton from " +
+ get_top_parent(mxRadioButton)->get_id();
+ }
+ else
+ return WindowUIObject::get_action(nEvent);
+}
+
+std::unique_ptr<UIObject> RadioButtonUIObject::create(vcl::Window* pWindow)
+{
+ RadioButton* pRadioButton = dynamic_cast<RadioButton*>(pWindow);
+ assert(pRadioButton);
+ return std::unique_ptr<UIObject>(new RadioButtonUIObject(pRadioButton));
+}
+
+TabPageUIObject::TabPageUIObject(const VclPtr<TabPage>& xTabPage):
+ WindowUIObject(xTabPage),
+ mxTabPage(xTabPage)
+{
+}
+
+TabPageUIObject::~TabPageUIObject()
+{
+}
+
+void TabPageUIObject::execute(const OUString& rAction,
+ const StringMap& /*rParameters*/)
+{
+ if (rAction == "SELECT")
+ {
+ /* code */
+ }
+}
+
+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);
+ }
+ mxComboBox->Select();
+ }
+ else if (rAction == "TYPE")
+ {
+ 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();
+
+ 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();
+ }
+}
+
+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->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));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/uitest/uitest.cxx b/vcl/source/uitest/uitest.cxx
new file mode 100644
index 000000000..9edcba7a4
--- /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/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.begin(), nIndex));
+ }
+ return comphelper::dispatchCommand(rCommand,lNewArgs);
+}
+
+bool UITest::executeDialog(const OUString& rCommand)
+{
+ return comphelper::dispatchCommand(
+ rCommand,
+ {{"SynchronMode", -1, css::uno::Any(false),
+ css::beans::PropertyState_DIRECT_VALUE}});
+}
+
+std::unique_ptr<UIObject> UITest::getFocusTopWindow()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ ImplSVWinData& rWinData = *pSVData->mpWinData;
+
+ if (!rWinData.mpExecuteDialogs.empty())
+ {
+ return rWinData.mpExecuteDialogs.back()->GetUITestFactory()(rWinData.mpExecuteDialogs.back());
+ }
+
+ return pSVData->maFrameData.mpFirstFrame->GetUITestFactory()(pSVData->maFrameData.mpFirstFrame);
+}
+
+std::unique_ptr<UIObject> UITest::getFloatWindow()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ ImplSVWinData& rWinData = *pSVData->mpWinData;
+
+ VclPtr<vcl::Window> pFloatWin = rWinData.mpFirstFloat;
+ if (pFloatWin)
+ return pFloatWin->GetUITestFactory()(pFloatWin);
+
+ return nullptr;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/uitest/uno/uiobject_uno.cxx b/vcl/source/uitest/uno/uiobject_uno.cxx
new file mode 100644
index 000000000..593f4a4c1
--- /dev/null
+++ b/vcl/source/uitest/uno/uiobject_uno.cxx
@@ -0,0 +1,210 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "uiobject_uno.hxx"
+#include <utility>
+#include <cppuhelper/supportsservice.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/idle.hxx>
+
+#include <set>
+
+UIObjectUnoObj::UIObjectUnoObj(std::unique_ptr<UIObject> pObj):
+ UIObjectBase(m_aMutex),
+ mpObj(std::move(pObj)),
+ mReady(true)
+{
+}
+
+UIObjectUnoObj::~UIObjectUnoObj()
+{
+ {
+ std::scoped_lock<std::mutex> lk3(mMutex);
+ }
+ SolarMutexGuard aGuard;
+ mpObj.reset();
+}
+
+css::uno::Reference<css::ui::test::XUIObject> SAL_CALL UIObjectUnoObj::getChild(const OUString& rID)
+{
+ if (!mpObj)
+ throw css::uno::RuntimeException();
+
+ SolarMutexGuard aGuard;
+ std::unique_ptr<UIObject> pObj = mpObj->get_child(rID);
+ return new UIObjectUnoObj(std::move(pObj));
+}
+
+IMPL_LINK_NOARG(UIObjectUnoObj, 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;
+ volatile 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;
+ {
+ mFunc();
+ aIdle.SetDebugName("UI Test Idle Handler2");
+ aIdle.SetPriority(TaskPriority::LOWEST);
+ aIdle.SetInvokeHandler(mHandler);
+ aIdle.Start();
+ }
+
+ while (!mbSignal) {
+ Application::Reschedule();
+ }
+ }
+ delete this;
+}
+
+}
+
+void SAL_CALL UIObjectUnoObj::executeAction(const OUString& rAction, const css::uno::Sequence<css::beans::PropertyValue>& rPropValues)
+{
+ if (!mpObj)
+ throw css::uno::RuntimeException();
+
+ std::unique_lock<std::mutex> lk(mMutex);
+ mAction = rAction;
+ mPropValues = rPropValues;
+ mReady = false;
+ auto aIdle = std::make_unique<Idle>();
+ aIdle->SetDebugName("UI Test Idle Handler");
+ aIdle->SetPriority(TaskPriority::HIGHEST);
+
+ std::function<void()> func = [this](){
+
+ SolarMutexGuard aGuard;
+ StringMap aMap;
+ for (const auto& rPropVal : std::as_const(mPropValues))
+ {
+ OUString aVal;
+ if (!(rPropVal.Value >>= aVal))
+ continue;
+
+ aMap[rPropVal.Name] = aVal;
+ }
+ mpObj->execute(mAction, aMap);
+ };
+
+ ExecuteWrapper* pWrapper = new ExecuteWrapper(func, LINK(this, UIObjectUnoObj, NotifyHdl));
+ aIdle->SetInvokeHandler(LINK(pWrapper, ExecuteWrapper, ExecuteActionHdl));
+ {
+ SolarMutexGuard aGuard;
+ aIdle->Start();
+ }
+
+ cv.wait(lk, [this]{return mReady;});
+ pWrapper->setSignal();
+
+ SolarMutexGuard aGuard;
+ aIdle.reset();
+}
+
+css::uno::Sequence<css::beans::PropertyValue> UIObjectUnoObj::getState()
+{
+ if (!mpObj)
+ throw css::uno::RuntimeException();
+
+ SolarMutexGuard aGuard;
+ StringMap aMap = mpObj->get_state();
+ css::uno::Sequence<css::beans::PropertyValue> aProps(aMap.size());
+ sal_Int32 i = 0;
+ for (auto const& elem : aMap)
+ {
+ aProps[i].Name = elem.first;
+ aProps[i].Value <<= elem.second;
+ ++i;
+ }
+
+ return aProps;
+}
+
+css::uno::Sequence<OUString> UIObjectUnoObj::getChildren()
+{
+ if (!mpObj)
+ throw css::uno::RuntimeException();
+
+ std::set<OUString> aChildren = mpObj->get_children();
+
+ css::uno::Sequence<OUString> aRet(aChildren.size());
+ sal_Int32 i = 0;
+ for (auto const& child : aChildren)
+ {
+ aRet[i] = child;
+ ++i;
+ }
+
+ return aRet;
+}
+
+OUString SAL_CALL UIObjectUnoObj::getType()
+{
+ if (!mpObj)
+ throw css::uno::RuntimeException();
+
+ return mpObj->get_type();
+}
+
+OUString SAL_CALL UIObjectUnoObj::getImplementationName()
+{
+ return "org.libreoffice.uitest.UIObject";
+}
+
+sal_Bool UIObjectUnoObj::supportsService(OUString const & ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence<OUString> UIObjectUnoObj::getSupportedServiceNames()
+{
+ return { "com.sun.star.ui.test.UIObject" };
+}
+
+OUString SAL_CALL UIObjectUnoObj::getHierarchy()
+{
+ if (!mpObj)
+ throw css::uno::RuntimeException();
+
+ SolarMutexGuard aGuard;
+ return mpObj->dumpHierarchy();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/uitest/uno/uiobject_uno.hxx b/vcl/source/uitest/uno/uiobject_uno.hxx
new file mode 100644
index 000000000..2f98ab7c0
--- /dev/null
+++ b/vcl/source/uitest/uno/uiobject_uno.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/.
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_UITEST_UNO_UIOBJECT_UNO_HXX
+#define INCLUDED_VCL_SOURCE_UITEST_UNO_UIOBJECT_UNO_HXX
+
+#include <cppuhelper/compbase.hxx>
+#include <cppuhelper/basemutex.hxx>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/ui/test/XUIObject.hpp>
+
+#include <memory>
+#include <condition_variable>
+#include <mutex>
+
+#include <vcl/uitest/uiobject.hxx>
+
+class Timer;
+
+typedef ::cppu::WeakComponentImplHelper <
+ css::ui::test::XUIObject, css::lang::XServiceInfo
+ > UIObjectBase;
+
+class UIObjectUnoObj : public cppu::BaseMutex,
+ public UIObjectBase
+{
+private:
+ std::unique_ptr<UIObject> mpObj;
+
+public:
+
+ explicit UIObjectUnoObj(std::unique_ptr<UIObject> pObj);
+ virtual ~UIObjectUnoObj() override;
+
+ css::uno::Reference<css::ui::test::XUIObject> SAL_CALL getChild(const OUString& rID) override;
+
+ void SAL_CALL executeAction(const OUString& rAction, const css::uno::Sequence<css::beans::PropertyValue>& xPropValues) override;
+
+ css::uno::Sequence<css::beans::PropertyValue> SAL_CALL getState() override;
+
+ css::uno::Sequence<OUString> SAL_CALL getChildren() override;
+
+ OUString SAL_CALL getType() override;
+
+ OUString SAL_CALL getImplementationName() override;
+
+ sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override;
+
+ css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override;
+
+ OUString SAL_CALL getHierarchy() override;
+
+private:
+
+ DECL_LINK( NotifyHdl, Timer*, void );
+
+ std::condition_variable cv;
+ std::mutex mMutex;
+ bool mReady;
+
+ OUString mAction;
+ css::uno::Sequence<css::beans::PropertyValue> mPropValues;
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/uitest/uno/uitest_uno.cxx b/vcl/source/uitest/uno/uitest_uno.cxx
new file mode 100644
index 000000000..d0d70a816
--- /dev/null
+++ b/vcl/source/uitest/uno/uitest_uno.cxx
@@ -0,0 +1,120 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <cppuhelper/compbase.hxx>
+#include <cppuhelper/basemutex.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/ui/test/XUITest.hpp>
+
+#include <memory>
+
+#include <vcl/uitest/uitest.hxx>
+#include <vcl/svapp.hxx>
+
+#include "uiobject_uno.hxx"
+
+namespace
+{
+ typedef ::cppu::WeakComponentImplHelper <
+ css::ui::test::XUITest, css::lang::XServiceInfo
+ > UITestBase;
+
+class UITestUnoObj : public cppu::BaseMutex,
+ public UITestBase
+{
+private:
+ std::unique_ptr<UITest> mpUITest;
+
+public:
+
+ UITestUnoObj();
+
+ sal_Bool SAL_CALL executeCommand(const OUString& rCommand) override;
+
+ sal_Bool SAL_CALL executeCommandWithParameters(const OUString& rCommand,
+ const css::uno::Sequence< css::beans::PropertyValue >& rArgs) override;
+
+ sal_Bool SAL_CALL executeDialog(const OUString& rCommand) override;
+
+ css::uno::Reference<css::ui::test::XUIObject> SAL_CALL getTopFocusWindow() override;
+
+ css::uno::Reference<css::ui::test::XUIObject> SAL_CALL getFloatWindow() override;
+
+ OUString SAL_CALL getImplementationName() override;
+
+ sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override;
+
+ css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override;
+};
+
+}
+
+UITestUnoObj::UITestUnoObj():
+ UITestBase(m_aMutex),
+ mpUITest(new UITest)
+{
+}
+
+sal_Bool SAL_CALL UITestUnoObj::executeCommand(const OUString& rCommand)
+{
+ SolarMutexGuard aGuard;
+ return UITest::executeCommand(rCommand);
+}
+
+sal_Bool SAL_CALL UITestUnoObj::executeCommandWithParameters(const OUString& rCommand,
+ const css::uno::Sequence< css::beans::PropertyValue >& rArgs)
+{
+ SolarMutexGuard aGuard;
+ return UITest::executeCommandWithParameters(rCommand,rArgs);
+}
+
+sal_Bool SAL_CALL UITestUnoObj::executeDialog(const OUString& rCommand)
+{
+ SolarMutexGuard aGuard;
+ return UITest::executeDialog(rCommand);
+}
+
+css::uno::Reference<css::ui::test::XUIObject> SAL_CALL UITestUnoObj::getTopFocusWindow()
+{
+ SolarMutexGuard aGuard;
+ std::unique_ptr<UIObject> pObj = UITest::getFocusTopWindow();
+ return new UIObjectUnoObj(std::move(pObj));
+}
+
+css::uno::Reference<css::ui::test::XUIObject> SAL_CALL UITestUnoObj::getFloatWindow()
+{
+ SolarMutexGuard aGuard;
+ std::unique_ptr<UIObject> pObj = UITest::getFloatWindow();
+ return new UIObjectUnoObj(std::move(pObj));
+}
+
+OUString SAL_CALL UITestUnoObj::getImplementationName()
+{
+ return "org.libreoffice.uitest.UITest";
+}
+
+sal_Bool UITestUnoObj::supportsService(OUString const & ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence<OUString> UITestUnoObj::getSupportedServiceNames()
+{
+ return { "com.sun.star.ui.test.UITest" };
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+UITest_get_implementation(css::uno::XComponentContext*, css::uno::Sequence<css::uno::Any> const &)
+{
+ return cppu::acquire(new UITestUnoObj());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/EnumContext.cxx b/vcl/source/window/EnumContext.cxx
new file mode 100644
index 000000000..3229301e3
--- /dev/null
+++ b/vcl/source/window/EnumContext.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/EnumContext.hxx>
+
+#include <osl/diagnose.h>
+#include <o3tl/enumarray.hxx>
+
+#include <map>
+
+namespace vcl {
+
+namespace {
+
+typedef ::std::map<OUString,EnumContext::Application> ApplicationMap;
+
+static ApplicationMap maApplicationMap;
+static o3tl::enumarray<EnumContext::Application, OUString> maApplicationVector;
+
+typedef ::std::map<OUString,EnumContext::Context> ContextMap;
+
+static ContextMap maContextMap;
+static o3tl::enumarray<EnumContext::Context, OUString> maContextVector;
+
+}
+
+const sal_Int32 EnumContext::NoMatch = 4;
+const sal_Int32 EnumContext::OptimalMatch = 0; // Neither application nor context name is "any".
+
+EnumContext::EnumContext()
+ : meApplication(Application::NONE),
+ meContext(Context::Unknown)
+{
+}
+
+EnumContext::EnumContext (
+ const Application eApplication,
+ const Context eContext)
+ : meApplication(eApplication),
+ meContext(eContext)
+{
+}
+
+sal_Int32 EnumContext::GetCombinedContext_DI() const
+{
+ return CombinedEnumContext(GetApplication_DI(), meContext);
+}
+
+EnumContext::Application EnumContext::GetApplication_DI() const
+{
+ switch (meApplication)
+ {
+ case Application::Draw:
+ case Application::Impress:
+ return Application::DrawImpress;
+
+ case Application::Writer:
+ case Application::WriterGlobal:
+ case Application::WriterWeb:
+ case Application::WriterXML:
+ case Application::WriterForm:
+ case Application::WriterReport:
+ return Application::WriterVariants;
+
+ default:
+ return meApplication;
+ }
+}
+
+bool EnumContext::operator== (const EnumContext& rOther) const
+{
+ return meApplication==rOther.meApplication
+ && meContext==rOther.meContext;
+}
+
+bool EnumContext::operator!= (const EnumContext& rOther) const
+{
+ return meApplication!=rOther.meApplication
+ || meContext!=rOther.meContext;
+}
+
+void EnumContext::AddEntry (const OUString& rsName, const Application eApplication)
+{
+ maApplicationMap[rsName] = eApplication;
+ OSL_ASSERT(eApplication<=Application::LAST);
+ maApplicationVector[eApplication]=rsName;
+}
+
+void EnumContext::ProvideApplicationContainers()
+{
+ if (!maApplicationMap.empty())
+ return;
+
+ AddEntry("com.sun.star.text.TextDocument", EnumContext::Application::Writer);
+ AddEntry("com.sun.star.text.GlobalDocument", EnumContext::Application::WriterGlobal);
+ AddEntry("com.sun.star.text.WebDocument", EnumContext::Application::WriterWeb);
+ AddEntry("com.sun.star.xforms.XMLFormDocument", EnumContext::Application::WriterXML);
+ AddEntry("com.sun.star.sdb.FormDesign", EnumContext::Application::WriterForm);
+ AddEntry("com.sun.star.sdb.TextReportDesign", EnumContext::Application::WriterReport);
+ AddEntry("com.sun.star.sheet.SpreadsheetDocument", EnumContext::Application::Calc);
+ AddEntry("com.sun.star.chart2.ChartDocument", EnumContext::Application::Chart);
+ AddEntry("com.sun.star.drawing.DrawingDocument", EnumContext::Application::Draw);
+ AddEntry("com.sun.star.presentation.PresentationDocument", EnumContext::Application::Impress);
+ AddEntry("com.sun.star.formula.FormulaProperties", EnumContext::Application::Formula);
+ AddEntry("com.sun.star.sdb.OfficeDatabaseDocument", EnumContext::Application::Base);
+ AddEntry("any", EnumContext::Application::Any);
+ AddEntry("none", EnumContext::Application::NONE);
+
+}
+
+EnumContext::Application EnumContext::GetApplicationEnum (const OUString& rsApplicationName)
+{
+ ProvideApplicationContainers();
+
+ ApplicationMap::const_iterator iApplication(
+ maApplicationMap.find(rsApplicationName));
+ if (iApplication != maApplicationMap.end())
+ return iApplication->second;
+ else
+ return EnumContext::Application::NONE;
+}
+
+const OUString& EnumContext::GetApplicationName (const Application eApplication)
+{
+ ProvideApplicationContainers();
+ return maApplicationVector[eApplication];
+}
+
+void EnumContext::AddEntry (const OUString& rsName, const Context eContext)
+{
+ maContextMap[rsName] = eContext;
+ maContextVector[eContext] = rsName;
+}
+
+void EnumContext::ProvideContextContainers()
+{
+ if (!maContextMap.empty())
+ return;
+
+ AddEntry("3DObject", Context::ThreeDObject);
+ AddEntry("Annotation", Context::Annotation);
+ AddEntry("Auditing", Context::Auditing);
+ AddEntry("Axis", Context::Axis);
+ AddEntry("Cell", Context::Cell);
+ AddEntry("Chart", Context::Chart);
+ AddEntry("ChartElements", Context::ChartElements);
+ AddEntry("Draw", Context::Draw);
+ AddEntry("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("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);
+
+ // other general contexts
+ AddEntry("any", Context::Any);
+ AddEntry("default", Context::Default);
+ AddEntry("empty", Context::Empty);
+}
+
+EnumContext::Context EnumContext::GetContextEnum (const OUString& rsContextName)
+{
+ ProvideContextContainers();
+
+ ContextMap::const_iterator iContext( maContextMap.find(rsContextName) );
+ if (iContext != maContextMap.end())
+ return iContext->second;
+ else
+ return EnumContext::Context::Unknown;
+}
+
+const OUString& EnumContext::GetContextName (const Context eContext)
+{
+ ProvideContextContainers();
+ return maContextVector[eContext];
+}
+
+} // end of namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/NotebookBarAddonsMerger.cxx b/vcl/source/window/NotebookBarAddonsMerger.cxx
new file mode 100644
index 000000000..9d6bf3dd9
--- /dev/null
+++ b/vcl/source/window/NotebookBarAddonsMerger.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 <cstddef>
+
+#include <vcl/NotebookBarAddonsMerger.hxx>
+#include <vcl/commandinfoprovider.hxx>
+#include <vcl/vclenum.hxx>
+#include <vcl/toolbox.hxx>
+#include <vcl/IPrioritable.hxx>
+#include <OptionalBox.hxx>
+
+static const char STYLE_TEXT[] = "Text";
+static const char STYLE_ICON[] = "Icon";
+
+static const char MERGE_NOTEBOOKBAR_URL[] = "URL";
+static const char MERGE_NOTEBOOKBAR_TITLE[] = "Title";
+static const char MERGE_NOTEBOOKBAR_IMAGEID[] = "ImageIdentifier";
+static const char MERGE_NOTEBOOKBAR_CONTEXT[] = "Context";
+static const char MERGE_NOTEBOOKBAR_TARGET[] = "Target";
+static const char MERGE_NOTEBOOKBAR_CONTROLTYPE[] = "ControlType";
+static const char MERGE_NOTEBOOKBAR_WIDTH[] = "Width";
+static 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_IMAGEID)
+ i.Value >>= aAddonNotebookBarItem.sImageIdentifier;
+ 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 unsigned long& nIter)
+{
+ sal_uInt16 nItemId = 0;
+ ToolBox* pToolbox = dynamic_cast<ToolBox*>(pNotebookbarToolBox);
+ if (pToolbox)
+ {
+ pToolbox->InsertSeparator();
+ pToolbox->Show();
+ Size aSize(0, 0);
+ Image sImage;
+ pToolbox->InsertItem(aAddonNotebookBarItem.sCommandURL, m_xFrame, ToolBoxItemBits::NONE,
+ aSize);
+ nItemId = pToolbox->GetItemId(aAddonNotebookBarItem.sCommandURL);
+ pToolbox->SetItemCommand(nItemId, aAddonNotebookBarItem.sCommandURL);
+ pToolbox->SetQuickHelpText(nItemId, aAddonNotebookBarItem.sLabel);
+
+ if (nIter < aImageVec.size())
+ {
+ sImage = aImageVec[nIter];
+ if (!sImage)
+ {
+ sImage = vcl::CommandInfoProvider::GetImageForCommand(
+ aAddonNotebookBarItem.sImageIdentifier, 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();
+ }
+}
+
+NotebookBarAddonsMerger::NotebookBarAddonsMerger() {}
+
+NotebookBarAddonsMerger::~NotebookBarAddonsMerger() {}
+
+void NotebookBarAddonsMerger::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;
+ unsigned long 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 NotebookBarAddonsMerger::MergeNotebookBarMenuAddons(
+ Menu* pPopupMenu, sal_Int16 nItemId, const OString& sItemIdName,
+ NotebookBarAddonsItem& aNotebookBarAddonsItem)
+{
+ std::vector<Image> aImageVec = aNotebookBarAddonsItem.aImageValues;
+ unsigned long nIter = 0;
+ css::uno::Sequence<css::uno::Sequence<css::beans::PropertyValue>> aExtension;
+ for (std::size_t nIdx = 0; nIdx < aNotebookBarAddonsItem.aAddonValues.size(); nIdx++)
+ {
+ aExtension = aNotebookBarAddonsItem.aAddonValues[nIdx];
+
+ for (int nSecIdx = 0; nSecIdx < aExtension.getLength(); nSecIdx++)
+ {
+ AddonNotebookBarItem aAddonNotebookBarItem;
+ Image sImage;
+ MenuItemBits nBits = MenuItemBits::ICON;
+ const css::uno::Sequence<css::beans::PropertyValue> pExtension = aExtension[nSecIdx];
+
+ GetAddonNotebookBarItem(pExtension, aAddonNotebookBarItem);
+
+ pPopupMenu->InsertItem(nItemId, aAddonNotebookBarItem.sLabel, nBits, sItemIdName);
+ pPopupMenu->SetItemCommand(nItemId, aAddonNotebookBarItem.sCommandURL);
+
+ if (nIter < aImageVec.size())
+ {
+ sImage = aImageVec[nIter];
+ nIter++;
+ }
+ pPopupMenu->SetItemImage(nItemId, sImage);
+
+ if (nSecIdx == aExtension.getLength() - 1)
+ pPopupMenu->InsertSeparator();
+
+ ++nItemId;
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/OptionalBox.cxx b/vcl/source/window/OptionalBox.cxx
new file mode 100644
index 000000000..168da0474
--- /dev/null
+++ b/vcl/source/window/OptionalBox.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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)
+ , IPrioritable()
+ , m_bInFullView(true)
+{
+}
+
+OptionalBox::~OptionalBox() { disposeOnce(); }
+
+void OptionalBox::HideContent()
+{
+ if (m_bInFullView)
+ {
+ m_bInFullView = false;
+
+ for (int i = 0; i < GetChildCount(); i++)
+ GetChild(i)->Hide();
+
+ SetOutputSizePixel(Size(10, GetSizePixel().Height()));
+ }
+}
+
+void OptionalBox::ShowContent()
+{
+ if (!m_bInFullView)
+ {
+ m_bInFullView = true;
+
+ for (int i = 0; i < GetChildCount(); i++)
+ GetChild(i)->Show();
+ }
+}
+
+bool OptionalBox::IsHidden() { return !m_bInFullView; }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/abstdlg.cxx b/vcl/source/window/abstdlg.cxx
new file mode 100644
index 000000000..b774b0206
--- /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
+ static ::osl::Module aDialogLibrary;
+ if (aDialogLibrary.loadRelative(&thisModule, CUI_DLL_NAME,
+ SAL_LOADMODULE_GLOBAL | SAL_LOADMODULE_LAZY))
+ {
+ return reinterpret_cast<FuncPtrCreateDialogFactory>(
+ aDialogLibrary.getFunctionSymbol( "CreateDialogFactory" ) );
+ }
+ return nullptr;
+#else
+ return CreateDialogFactory;
+#endif
+ }();
+ if ( fp )
+ return fp();
+ return nullptr;
+}
+
+VclAbstractDialog::~VclAbstractDialog()
+{
+}
+
+bool VclAbstractDialog::StartExecuteAsync(AsyncContext &)
+{
+ assert(false);
+ return false;
+}
+
+std::vector<OString> VclAbstractDialog::getAllPageUIXMLDescriptions() const
+{
+ // default has no pages
+ return std::vector<OString>();
+}
+
+bool VclAbstractDialog::selectPageByUIXMLDescription(const OString& /*rUIXMLDescription*/)
+{
+ // default cannot select a page (which is okay, return true)
+ return true;
+}
+
+BitmapEx VclAbstractDialog::createScreenshot() const
+{
+ // default returns empty bitmap
+ return BitmapEx();
+}
+
+VclAbstractDialogFactory::~VclAbstractDialogFactory()
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/accel.cxx b/vcl/source/window/accel.cxx
new file mode 100644
index 000000000..c580679bd
--- /dev/null
+++ b/vcl/source/window/accel.cxx
@@ -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 <sal/log.hxx>
+#include <osl/diagnose.h>
+#include <tools/solar.h>
+#include <accel.h>
+#include <vcl/accel.hxx>
+#include <map>
+#include <vector>
+
+typedef ::std::map< sal_uLong, ImplAccelEntry* > ImplAccelMap;
+typedef ::std::vector< std::unique_ptr<ImplAccelEntry> > ImplAccelList;
+
+#define ACCELENTRY_NOTFOUND (sal_uInt16(0xFFFF))
+
+class ImplAccelData
+{
+public:
+ ImplAccelMap maKeyMap; // for keycodes, generated with a code
+ ImplAccelList maIdList; // Id-List
+};
+
+static sal_uInt16 ImplAccelEntryGetIndex( 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
+{
+ ImplAccelMap::iterator it = mpData->maKeyMap.find( rKeyCode.GetFullCode() );
+ if( it != mpData->maKeyMap.end() )
+ return it->second;
+ else
+ return nullptr;
+}
+
+void Accelerator::ImplCopyData( ImplAccelData& rAccelData )
+{
+ // copy table
+ for (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;
+
+ mpData->maKeyMap.insert( std::make_pair( pEntry->maKeyCode.GetFullCode(), pEntry.get() ) );
+ mpData->maIdList.push_back( std::move(pEntry) );
+ }
+}
+
+void Accelerator::ImplDeleteData()
+{
+ // delete accelerator-entries using the id-table
+ for (const std::unique_ptr<ImplAccelEntry>& pEntry : mpData->maIdList) {
+ delete pEntry->mpAutoAccel;
+ }
+ mpData->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 ( !mpData->maKeyMap.insert( std::make_pair( nCode, pEntry.get() ) ).second )
+ {
+ SAL_WARN( "vcl", "Accelerator::InsertItem(): KeyCode (Key: " << nCode << ") already exists" );
+ }
+ else
+ ImplAccelEntryInsert( &(mpData->maIdList), std::move(pEntry) );
+}
+
+Accelerator::Accelerator()
+{
+ ImplInit();
+ mpData.reset(new ImplAccelData);
+}
+
+Accelerator::Accelerator(const Accelerator& rAccel)
+{
+ ImplInit();
+ mpData.reset(new ImplAccelData);
+ ImplCopyData(*rAccel.mpData);
+}
+
+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>(mpData->maIdList.size());
+}
+
+sal_uInt16 Accelerator::GetItemId( sal_uInt16 nPos ) const
+{
+
+ ImplAccelEntry* pEntry = ( nPos < mpData->maIdList.size() ) ? mpData->maIdList[ nPos ].get() : nullptr;
+ if ( pEntry )
+ return pEntry->mnId;
+ else
+ return 0;
+}
+
+Accelerator* Accelerator::GetAccel( sal_uInt16 nItemId ) const
+{
+
+ sal_uInt16 nIndex = ImplAccelEntryGetIndex( &(mpData->maIdList), nItemId );
+ if ( nIndex != ACCELENTRY_NOTFOUND )
+ return mpData->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();
+ mpData->maKeyMap.clear();
+ ImplCopyData(*rAccel.mpData);
+
+ return *this;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/accessibility.cxx b/vcl/source/window/accessibility.cxx
new file mode 100644
index 000000000..ddcfd56aa
--- /dev/null
+++ b/vcl/source/window/accessibility.cxx
@@ -0,0 +1,634 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <vcl/layout.hxx>
+#include <vcl/fixed.hxx>
+#include <vcl/window.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/wrkwin.hxx>
+#include <vcl/popupmenuwindow.hxx>
+
+#include <window.h>
+#include <brdwin.hxx>
+
+#include <com/sun/star/accessibility/XAccessible.hpp>
+#include <com/sun/star/accessibility/AccessibleRole.hpp>
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+#include <com/sun/star/accessibility/XAccessibleEditableText.hpp>
+#include <com/sun/star/awt/XWindowPeer.hpp>
+
+#include <sal/log.hxx>
+
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::datatransfer::clipboard;
+using namespace ::com::sun::star::datatransfer::dnd;
+using namespace ::com::sun::star;
+
+
+ImplAccessibleInfos::ImplAccessibleInfos()
+{
+ nAccessibleRole = 0xFFFF;
+ pLabeledByWindow = nullptr;
+ pLabelForWindow = nullptr;
+ pMemberOfWindow = nullptr;
+}
+
+ImplAccessibleInfos::~ImplAccessibleInfos()
+{
+}
+
+namespace vcl {
+
+css::uno::Reference< css::accessibility::XAccessible > Window::GetAccessible( bool bCreate )
+{
+ // do not optimize hierarchy for the top level border win (ie, when there is no parent)
+ /* // do not optimize accessible hierarchy at all to better reflect real VCL hierarchy
+ if ( GetParent() && ( GetType() == WindowType::BORDERWINDOW ) && ( GetChildCount() == 1 ) )
+ //if( !ImplIsAccessibleCandidate() )
+ {
+ vcl::Window* pChild = GetAccessibleChildWindow( 0 );
+ if ( pChild )
+ return pChild->GetAccessible();
+ }
+ */
+ if ( !mpWindowImpl )
+ return css::uno::Reference< css::accessibility::XAccessible >();
+ if ( !mpWindowImpl->mxAccessible.is() && bCreate )
+ mpWindowImpl->mxAccessible = CreateAccessible();
+
+ return mpWindowImpl->mxAccessible;
+}
+
+css::uno::Reference< css::accessibility::XAccessible > Window::CreateAccessible()
+{
+ css::uno::Reference< css::accessibility::XAccessible > xAcc( GetComponentInterface(), css::uno::UNO_QUERY );
+ return xAcc;
+}
+
+void Window::SetAccessible( const css::uno::Reference< css::accessibility::XAccessible >& x )
+{
+ if (!mpWindowImpl)
+ return;
+
+ mpWindowImpl->mxAccessible = x;
+}
+
+// skip all border windows that are not top level frames
+bool Window::ImplIsAccessibleCandidate() const
+{
+ if( !mpWindowImpl->mbBorderWin )
+ return true;
+ else
+ // #101741 do not check for WB_CLOSEABLE because undecorated floaters (like menus!) are closeable
+ if( mpWindowImpl->mbFrame && mpWindowImpl->mnStyle & (WB_MOVEABLE | WB_SIZEABLE) )
+ return true;
+ else
+ return false;
+}
+
+bool Window::ImplIsAccessibleNativeFrame() const
+{
+ if( mpWindowImpl->mbFrame )
+ // #101741 do not check for WB_CLOSEABLE because undecorated floaters (like menus!) are closeable
+ if( mpWindowImpl->mnStyle & (WB_MOVEABLE | WB_SIZEABLE) )
+ return true;
+ else
+ return false;
+ else
+ return false;
+}
+
+vcl::Window* Window::GetAccessibleParentWindow() const
+{
+ if (!mpWindowImpl || ImplIsAccessibleNativeFrame())
+ return nullptr;
+
+ vcl::Window* pParent = mpWindowImpl->mpParent;
+ if( GetType() == WindowType::MENUBARWINDOW )
+ {
+ // report the menubar as a child of THE workwindow
+ vcl::Window *pWorkWin = GetParent()->mpWindowImpl->mpFirstChild;
+ while( pWorkWin && (pWorkWin == this) )
+ pWorkWin = pWorkWin->mpWindowImpl->mpNext;
+ pParent = pWorkWin;
+ }
+ // If this is a floating window which has a native border window, then that border should be reported as
+ // the accessible parent, unless the floating window is a PopupMenuFloatingWindow
+
+ // The logic here has to match that of AccessibleFactory::createAccessibleContext in
+ // accessibility/source/helper/acc_factory.cxx to avoid PopupMenuFloatingWindow
+ // becoming a11y parents of themselves
+ else if( GetType() == WindowType::FLOATINGWINDOW &&
+ mpWindowImpl->mpBorderWindow && mpWindowImpl->mpBorderWindow->mpWindowImpl->mbFrame &&
+ !PopupMenuFloatingWindow::isPopupMenu(this))
+ {
+ 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::MODALDIALOG:
+ 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:
+ case WindowType::SPINBUTTON: 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::CALCINPUTLINE: nRole = 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::NUMERICFIELD:
+ case WindowType::METRICFIELD:
+ case WindowType::CURRENCYFIELD:
+ case WindowType::LONGCURRENCYFIELD:
+ case WindowType::SPINFIELD:
+ case WindowType::FORMATTEDFIELD: nRole = accessibility::AccessibleRole::SPIN_BOX; break;
+
+ case WindowType::TOOLBOX: nRole = accessibility::AccessibleRole::TOOL_BAR; break;
+ case WindowType::STATUSBAR: nRole = accessibility::AccessibleRole::STATUS_BAR; break;
+
+ case WindowType::TABPAGE: nRole = accessibility::AccessibleRole::PANEL; break;
+ case WindowType::TABCONTROL: nRole = accessibility::AccessibleRole::PAGE_TAB_LIST; break;
+
+ case WindowType::DOCKINGWINDOW: nRole = (mpWindowImpl->mbFrame) ? accessibility::AccessibleRole::FRAME :
+ accessibility::AccessibleRole::PANEL; break;
+
+ case WindowType::FLOATINGWINDOW: nRole = ( mpWindowImpl->mbFrame ||
+ (mpWindowImpl->mpBorderWindow && mpWindowImpl->mpBorderWindow->mpWindowImpl->mbFrame) ||
+ (GetStyle() & WB_OWNERDRAWDECORATION) ) ? accessibility::AccessibleRole::FRAME :
+ accessibility::AccessibleRole::WINDOW; break;
+
+ case WindowType::WORKWINDOW: nRole = accessibility::AccessibleRole::ROOT_PANE; break;
+
+ case WindowType::SCROLLBARBOX: nRole = accessibility::AccessibleRole::FILLER; break;
+
+ case WindowType::HELPTEXTWINDOW: nRole = accessibility::AccessibleRole::TOOL_TIP; break;
+
+ case WindowType::RULER: nRole = accessibility::AccessibleRole::RULER; break;
+
+ case WindowType::SCROLLWINDOW: nRole = accessibility::AccessibleRole::SCROLL_PANE; break;
+
+ case WindowType::WINDOW:
+ case WindowType::CONTROL:
+ case WindowType::BORDERWINDOW:
+ case WindowType::SYSTEMCHILDWINDOW:
+ default:
+ if (ImplIsAccessibleNativeFrame() )
+ nRole = accessibility::AccessibleRole::FRAME;
+ else if( IsScrollable() )
+ nRole = accessibility::AccessibleRole::SCROLL_PANE;
+ else if( this->ImplGetWindow()->IsMenuFloatingWindow() )
+ nRole = accessibility::AccessibleRole::WINDOW; // #106002#, contextmenus are windows (i.e. toplevel)
+ else
+ // #104051# WINDOW seems to be a bad default role, use LAYEREDPANE instead
+ // a WINDOW is interpreted as a top-level window, which is typically not the case
+ //nRole = accessibility::AccessibleRole::WINDOW;
+ nRole = accessibility::AccessibleRole::PANEL;
+ }
+ return nRole;
+}
+
+sal_uInt16 Window::GetAccessibleRole() const
+{
+ if (!mpWindowImpl)
+ return 0;
+
+ sal_uInt16 nRole = mpWindowImpl->mpAccessibleInfos ? mpWindowImpl->mpAccessibleInfos->nAccessibleRole : 0xFFFF;
+ if ( nRole == 0xFFFF )
+ nRole = getDefaultAccessibleRole();
+ return nRole;
+}
+
+void Window::SetAccessibleName( const OUString& rName )
+{
+ if ( !mpWindowImpl->mpAccessibleInfos )
+ mpWindowImpl->mpAccessibleInfos.reset( new ImplAccessibleInfos );
+
+ OUString oldName = GetAccessibleName();
+
+ mpWindowImpl->mpAccessibleInfos->pAccessibleName = rName;
+
+ CallEventListeners( VclEventId::WindowFrameTitleChanged, &oldName );
+}
+
+OUString Window::GetAccessibleName() const
+{
+ if (!mpWindowImpl)
+ return OUString();
+
+ if (mpWindowImpl->mpAccessibleInfos && mpWindowImpl->mpAccessibleInfos->pAccessibleName)
+ return *mpWindowImpl->mpAccessibleInfos->pAccessibleName;
+ return getDefaultAccessibleName();
+}
+
+OUString Window::getDefaultAccessibleName() const
+{
+ OUString aAccessibleName;
+ switch ( GetType() )
+ {
+ case WindowType::MULTILINEEDIT:
+ case WindowType::PATTERNFIELD:
+ case WindowType::NUMERICFIELD:
+ case WindowType::METRICFIELD:
+ case WindowType::CURRENCYFIELD:
+ case WindowType::LONGCURRENCYFIELD:
+ case WindowType::CALCINPUTLINE:
+ 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();
+ }
+ 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 GetNonMnemonicString( aAccessibleName );
+}
+
+void Window::SetAccessibleDescription( const OUString& rDescription )
+{
+ if ( ! mpWindowImpl->mpAccessibleInfos )
+ mpWindowImpl->mpAccessibleInfos.reset( new ImplAccessibleInfos );
+
+ SAL_WARN_IF( mpWindowImpl->mpAccessibleInfos->pAccessibleDescription, "vcl", "AccessibleDescription already set!" );
+ mpWindowImpl->mpAccessibleInfos->pAccessibleDescription = 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;
+}
+
+void Window::SetAccessibleRelationMemberOf( vcl::Window* pMemberOfWin )
+{
+ if ( !mpWindowImpl->mpAccessibleInfos )
+ mpWindowImpl->mpAccessibleInfos.reset( new ImplAccessibleInfos );
+ mpWindowImpl->mpAccessibleInfos->pMemberOfWindow = pMemberOfWin;
+}
+
+vcl::Window* Window::GetAccessibleRelationMemberOf() const
+{
+ if (mpWindowImpl->mpAccessibleInfos && mpWindowImpl->mpAccessibleInfos->pMemberOfWindow)
+ return mpWindowImpl->mpAccessibleInfos->pMemberOfWindow;
+
+ if (!isContainerWindow(this) && !isContainerWindow(GetParent()))
+ return getLegacyNonLayoutAccessibleRelationMemberOf();
+
+ return nullptr;
+}
+
+vcl::Window* Window::getAccessibleRelationLabelFor() const
+{
+ if (mpWindowImpl->mpAccessibleInfos && mpWindowImpl->mpAccessibleInfos->pLabelForWindow)
+ return mpWindowImpl->mpAccessibleInfos->pLabelForWindow;
+
+ return nullptr;
+}
+
+vcl::Window* Window::GetAccessibleRelationLabelFor() const
+{
+ vcl::Window* pWindow = getAccessibleRelationLabelFor();
+
+ if (pWindow)
+ return pWindow;
+
+ if (!isContainerWindow(this) && !isContainerWindow(GetParent()))
+ return getLegacyNonLayoutAccessibleRelationLabelFor();
+
+ return nullptr;
+}
+
+vcl::Window* Window::GetAccessibleRelationLabeledBy() const
+{
+ if (mpWindowImpl->mpAccessibleInfos && mpWindowImpl->mpAccessibleInfos->pLabeledByWindow)
+ return mpWindowImpl->mpAccessibleInfos->pLabeledByWindow;
+
+ std::vector<VclPtr<FixedText> > aMnemonicLabels(list_mnemonic_labels());
+ if (!aMnemonicLabels.empty())
+ {
+ //if we have multiple labels, then prefer the first that is visible
+ for (auto const & rCandidate : aMnemonicLabels)
+ {
+ if (rCandidate->IsVisible())
+ return rCandidate;
+ }
+ return aMnemonicLabels[0];
+ }
+
+ if (!isContainerWindow(this) && !isContainerWindow(GetParent()))
+ return getLegacyNonLayoutAccessibleRelationLabeledBy();
+
+ return nullptr;
+}
+
+bool Window::IsAccessibilityEventsSuppressed( bool bTraverseParentPath )
+{
+ if( !bTraverseParentPath )
+ return mpWindowImpl->mbSuppressAccessibilityEvents;
+ else
+ {
+ vcl::Window *pParent = this;
+ while ( pParent && pParent->mpWindowImpl)
+ {
+ if( pParent->mpWindowImpl->mbSuppressAccessibilityEvents )
+ return true;
+ else
+ pParent = pParent->mpWindowImpl->mpParent; // do not use GetParent() to find borderwindows that are frames
+ }
+ return false;
+ }
+}
+
+void Window::SetAccessibilityEventsSuppressed(bool bSuppressed)
+{
+ mpWindowImpl->mbSuppressAccessibilityEvents = bSuppressed;
+}
+
+} /* namespace vcl */
+
+uno::Reference<accessibility::XAccessibleEditableText>
+FindFocusedEditableText(uno::Reference<accessibility::XAccessibleContext> const& xContext)
+{
+ if (!xContext.is())
+ return uno::Reference<accessibility::XAccessibleEditableText>();
+
+ uno::Reference<accessibility::XAccessibleStateSet> xState = xContext->getAccessibleStateSet();
+ if (xState.is())
+ {
+ if (xState->contains(accessibility::AccessibleStateType::FOCUSED))
+ {
+ uno::Reference<accessibility::XAccessibleEditableText> xText(xContext, uno::UNO_QUERY);
+ if (xText.is())
+ return xText;
+ if (xState->contains(accessibility::AccessibleStateType::MANAGES_DESCENDANTS))
+ return uno::Reference<accessibility::XAccessibleEditableText>();
+ }
+ }
+
+ bool bSafeToIterate = true;
+ sal_Int32 nCount = xContext->getAccessibleChildCount();
+ if (nCount < 0 || nCount > SAL_MAX_UINT16 /* slow enough for anyone */)
+ bSafeToIterate = false;
+ if (!bSafeToIterate)
+ return uno::Reference<accessibility::XAccessibleEditableText>();
+
+ for (sal_Int32 i = 0; i < xContext->getAccessibleChildCount(); ++i)
+ {
+ uno::Reference<accessibility::XAccessible> xChild = xContext->getAccessibleChild(i);
+ if (!xChild.is())
+ continue;
+ uno::Reference<accessibility::XAccessibleContext> xChildContext
+ = xChild->getAccessibleContext();
+ if (!xChildContext.is())
+ continue;
+ uno::Reference<accessibility::XAccessibleEditableText> xText
+ = FindFocusedEditableText(xChildContext);
+ if (xText.is())
+ return xText;
+ }
+ return uno::Reference<accessibility::XAccessibleEditableText>();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/accmgr.cxx b/vcl/source/window/accmgr.cxx
new file mode 100644
index 000000000..003e09343
--- /dev/null
+++ b/vcl/source/window/accmgr.cxx
@@ -0,0 +1,229 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <accel.h>
+#include <vcl/accel.hxx>
+#include <accmgr.hxx>
+
+#include <algorithm>
+
+ImplAccelManager::~ImplAccelManager()
+{
+}
+
+bool ImplAccelManager::InsertAccel( Accelerator* pAccel )
+{
+ if ( !mpAccelList ) {
+ mpAccelList.reset( new std::vector< Accelerator* > );
+ } else {
+ for (Accelerator* i : *mpAccelList) {
+ if ( i == pAccel ) {
+ return false;
+ }
+ }
+ }
+
+ mpAccelList->insert( mpAccelList->begin(), pAccel );
+ return true;
+}
+
+void ImplAccelManager::RemoveAccel( Accelerator const * pAccel )
+{
+ // do we have a list ?
+ if ( !mpAccelList )
+ 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 ( mpSequenceList ) {
+ for (sal_uInt16 i = 0; i < pAccel->GetItemCount(); ++i) {
+ Accelerator* pSubAccel = pAccel->GetAccel( pAccel->GetItemId(i) );
+ for (Accelerator* j : *mpSequenceList) {
+ if ( j == pSubAccel ) {
+ EndSequence();
+ i = pAccel->GetItemCount();
+ break;
+ }
+ }
+ }
+ }
+
+ // throw it away
+ auto it = std::find(mpAccelList->begin(), mpAccelList->end(), pAccel);
+ if (it != mpAccelList->end())
+ mpAccelList->erase( it );
+}
+
+void ImplAccelManager::EndSequence()
+{
+ // are we in a list ?
+ if ( !mpSequenceList )
+ return;
+
+ for (Accelerator* pTempAccel : *mpSequenceList)
+ {
+ pTempAccel->mpDel = nullptr;
+ }
+
+ // delete sequence-list
+ mpSequenceList.reset();
+}
+
+bool ImplAccelManager::IsAccelKey( const vcl::KeyCode& rKeyCode )
+{
+ Accelerator* pAccel;
+
+ // do we have accelerators ??
+ if ( !mpAccelList )
+ return false;
+ if ( mpAccelList->empty() )
+ return false;
+
+ // are we in a sequence ?
+ if ( mpSequenceList )
+ {
+ pAccel = mpSequenceList->empty() ? nullptr : (*mpSequenceList)[ 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 )
+ {
+
+ mpSequenceList->insert( mpSequenceList->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 : *mpAccelList)
+ {
+ 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
+ mpSequenceList.reset( new std::vector< Accelerator* > );
+ mpSequenceList->insert( mpSequenceList->begin(), pAccel );
+ mpSequenceList->insert( mpSequenceList->begin(), pNextAccel );
+
+ // call activate-Handler of the new one
+ pNextAccel->Activate();
+
+ return true;
+ }
+ else
+ {
+ // already assigned !
+ if ( pEntry->mbEnabled )
+ {
+ // first call activate/deactivate-Handler
+ pAccel->Activate();
+
+ // define accelerator of the actual item
+ // and call the handler
+ bool bDel = false;
+ pAccel->mnCurId = pEntry->mnId;
+ pAccel->mpDel = &bDel;
+ pAccel->Select();
+
+ // if the accelerator did survive the call
+ if ( !bDel )
+ {
+ pAccel->mnCurId = 0;
+ pAccel->mpDel = nullptr;
+ }
+
+ return true;
+ }
+ else
+ return false;
+ }
+ }
+ }
+
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/brdwin.cxx b/vcl/source/window/brdwin.cxx
new file mode 100644
index 000000000..8be4de5d8
--- /dev/null
+++ b/vcl/source/window/brdwin.cxx
@@ -0,0 +1,2080 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/floatwin.hxx>
+#include <vcl/help.hxx>
+#include <vcl/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
+ long nExtraWidth = ((rRect.GetWidth()*50)+500)/1000;
+ 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 = dynamic_cast< vcl::Window* >(pDev);
+ 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, rStyleSettings.GetFloatTitleFont() );
+ pData->mnTitleHeight = rStyleSettings.GetFloatTitleHeight();
+ }
+ else // pData->mnTitleType == BorderWindowTitleType::Normal
+ {
+ // FIXME RenderContext
+ pBorderWindow->SetPointFont(*pBorderWindow, rStyleSettings.GetTitleFont());
+ pData->mnTitleHeight = rStyleSettings.GetTitleHeight();
+ }
+ 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.IsInside( rPos ) )
+ {
+ if ( pData->maCloseRect.IsInside( rPos ) )
+ return BorderWindowHitTest::Close;
+ else if ( pData->maRollRect.IsInside( rPos ) )
+ return BorderWindowHitTest::Roll;
+ else if ( pData->maMenuRect.IsInside( rPos ) )
+ return BorderWindowHitTest::Menu;
+ else if ( pData->maDockRect.IsInside( rPos ) )
+ return BorderWindowHitTest::Dock;
+ else if ( pData->maHideRect.IsInside( rPos ) )
+ return BorderWindowHitTest::Hide;
+ else if ( pData->maHelpRect.IsInside( rPos ) )
+ return BorderWindowHitTest::Help;
+ else
+ return BorderWindowHitTest::Title;
+ }
+
+ if ( (pBorderWindow->GetStyle() & WB_SIZEABLE) &&
+ !pBorderWindow->mbRollUp )
+ {
+ 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 )
+{
+ const char* pHelpId = nullptr;
+ 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::Roll )
+ {
+ if ( pData->mpBorderWindow->mbRollUp )
+ pHelpId = SV_HELPTEXT_ROLLDOWN;
+ else
+ pHelpId = SV_HELPTEXT_ROLLUP;
+ rHelpRect = pData->maRollRect;
+ }
+ 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;
+}
+
+long ImplBorderWindowView::ImplCalcTitleWidth( const ImplBorderFrameData* pData )
+{
+ // title is not visible therefore no width
+ if ( !pData->mnTitleHeight )
+ return 0;
+
+ ImplBorderWindow* pBorderWindow = pData->mpBorderWindow;
+ long nTitleWidth = pBorderWindow->GetTextWidth( pBorderWindow->GetText() )+6;
+ nTitleWidth += pData->maCloseRect.GetWidth();
+ nTitleWidth += pData->maRollRect.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*, long, 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;
+}
+
+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, long nWidth, long nHeight )
+{
+ mpOutDev = pDev;
+ mnWidth = nWidth;
+ mnHeight = nHeight;
+ mbNWFBorder = false;
+
+ vcl::Window *pWin = nullptr, *pCtrl = nullptr;
+ if (mpOutDev->GetOutDevType() == OUTDEV_WINDOW)
+ pWin = static_cast<vcl::Window*>(mpOutDev.get());
+
+ if (pWin)
+ pCtrl = mpBorderWindow->GetWindow(GetWindowType::Client);
+
+ long nOrigLeftBorder = mnLeftBorder;
+ long nOrigTopBorder = mnTopBorder;
+ long nOrigRightBorder = mnRightBorder;
+ 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;
+ if (pCtrl)
+ {
+ switch( pCtrl->GetType() )
+ {
+ case WindowType::LISTBOX:
+ if( pCtrl->GetStyle() & WB_DROPDOWN )
+ {
+ aCtrlType = ControlType::Listbox;
+ mbNWFBorder = true;
+ }
+ break;
+ case WindowType::COMBOBOX:
+ if( pCtrl->GetStyle() & WB_DROPDOWN )
+ {
+ aCtrlType = ControlType::Combobox;
+ mbNWFBorder = true;
+ }
+ break;
+ case WindowType::MULTILINEEDIT:
+ aCtrlType = ControlType::MultilineEditbox;
+ mbNWFBorder = true;
+ break;
+ case WindowType::EDIT:
+ case WindowType::PATTERNFIELD:
+ case WindowType::METRICFIELD:
+ case WindowType::CURRENCYFIELD:
+ case WindowType::DATEFIELD:
+ case WindowType::TIMEFIELD:
+ case WindowType::LONGCURRENCYFIELD:
+ case WindowType::NUMERICFIELD:
+ case WindowType::SPINFIELD:
+ case WindowType::FORMATTEDFIELD:
+ case WindowType::CALCINPUTLINE:
+ mbNWFBorder = true;
+ if (pCtrl->GetStyle() & WB_SPIN)
+ aCtrlType = ControlType::Spinbox;
+ else
+ aCtrlType = ControlType::Editbox;
+ break;
+ default:
+ break;
+ }
+ }
+ if( mbNWFBorder )
+ {
+ ImplControlValue aControlValue;
+ Size aMinSize( mnWidth - mnLeftBorder - mnRightBorder, mnHeight - mnTopBorder - mnBottomBorder );
+ if( aMinSize.Width() < 10 ) aMinSize.setWidth( 10 );
+ if( aMinSize.Height() < 10 ) aMinSize.setHeight( 10 );
+ tools::Rectangle aCtrlRegion( Point(mnLeftBorder, mnTopBorder), aMinSize );
+ tools::Rectangle aBounds, aContent;
+ if( pWin->GetNativeControlRegion( aCtrlType, ControlPart::Entire, aCtrlRegion,
+ ControlState::ENABLED, aControlValue,
+ aBounds, aContent ) )
+ {
+ 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();
+ pCtrl->SetPaintTransparent( true );
+
+ vcl::Window* pCompoundParent = nullptr;
+ if( pWin->GetParent() && pWin->GetParent()->IsCompoundControl() )
+ pCompoundParent = pWin->GetParent();
+
+ if( pCompoundParent )
+ pCompoundParent->SetPaintTransparent( true );
+
+ if( mnWidth < aBounds.GetWidth() || mnHeight < aBounds.GetHeight() )
+ {
+ if( ! pCompoundParent ) // compound controls have to fix themselves
+ {
+ Point aPos( mpBorderWindow->GetPosPixel() );
+ if( mnWidth < aBounds.GetWidth() )
+ aPos.AdjustX( -((aBounds.GetWidth() - mnWidth) / 2) );
+ if( mnHeight < aBounds.GetHeight() )
+ aPos.AdjustY( -((aBounds.GetHeight() - mnHeight) / 2) );
+ mpBorderWindow->SetPosSizePixel( aPos, aBounds.GetSize() );
+ }
+ }
+ }
+ }
+ else
+ mbNWFBorder = false;
+ }
+ }
+
+ if( ! mbNWFBorder )
+ {
+ DrawFrameStyle nStyle = DrawFrameStyle::NONE;
+ DrawFrameFlags nFlags = DrawFrameFlags::NoDraw;
+ // move border outside if border was converted or if the BorderWindow is a frame window,
+ if ( mpBorderWindow->mbSmallOutBorder )
+ nStyle = DrawFrameStyle::DoubleOut;
+ else if ( nBorderStyle & WindowBorderStyle::NWF )
+ nStyle = DrawFrameStyle::NWF;
+ else
+ nStyle = DrawFrameStyle::DoubleIn;
+ if ( nBorderStyle & WindowBorderStyle::MONO )
+ nFlags |= DrawFrameFlags::Mono;
+
+ DecorationView aDecoView( mpOutDev );
+ tools::Rectangle aRect( 0, 0, 10, 10 );
+ tools::Rectangle aCalcRect = aDecoView.DrawFrame( aRect, nStyle, nFlags );
+ mnLeftBorder = aCalcRect.Left();
+ mnTopBorder = aCalcRect.Top();
+ mnRightBorder = aRect.Right()-aCalcRect.Right();
+ mnBottomBorder = aRect.Bottom()-aCalcRect.Bottom();
+ }
+ }
+
+ if (pCtrl)
+ {
+ //fdo#57090 If the borders have changed, then trigger a queue_resize on
+ //the bordered window, which will resync its borders at that point
+ if (nOrigLeftBorder != mnLeftBorder ||
+ nOrigTopBorder != mnTopBorder ||
+ nOrigRightBorder != mnRightBorder ||
+ nOrigBottomBorder != mnBottomBorder)
+ {
+ pCtrl->queue_resize();
+ }
+ }
+}
+
+void ImplSmallBorderWindowView::GetBorder( sal_Int32& rLeftBorder, sal_Int32& rTopBorder,
+ sal_Int32& rRightBorder, sal_Int32& rBottomBorder ) const
+{
+ rLeftBorder = mnLeftBorder;
+ rTopBorder = mnTopBorder;
+ rRightBorder = mnRightBorder;
+ rBottomBorder = mnBottomBorder;
+}
+
+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::LONGCURRENCYFIELD:
+ case WindowType::NUMERICFIELD:
+ case WindowType::SPINFIELD:
+ case WindowType::FORMATTEDFIELD:
+ case WindowType::CALCINPUTLINE:
+ if (pCtrl->GetStyle() & WB_SPIN)
+ aCtrlType = ControlType::Spinbox;
+ else
+ aCtrlType = ControlType::Editbox;
+ break;
+
+ case WindowType::LISTBOX:
+ case WindowType::MULTILISTBOX:
+ case WindowType::TREELISTBOX:
+ aCtrlType = ControlType::Listbox;
+ if (pCtrl->GetStyle() & WB_DROPDOWN)
+ aCtrlPart = ControlPart::Entire;
+ else
+ aCtrlPart = ControlPart::ListboxWindow;
+ break;
+
+ case WindowType::LISTBOXWINDOW:
+ aCtrlType = ControlType::Listbox;
+ aCtrlPart = ControlPart::ListboxWindow;
+ break;
+
+ case WindowType::COMBOBOX:
+ case WindowType::PATTERNBOX:
+ case WindowType::NUMERICBOX:
+ case WindowType::METRICBOX:
+ case WindowType::CURRENCYBOX:
+ case WindowType::DATEBOX:
+ case WindowType::TIMEBOX:
+ case WindowType::LONGCURRENCYBOX:
+ if (pCtrl->GetStyle() & WB_DROPDOWN)
+ {
+ aCtrlType = ControlType::Combobox;
+ aCtrlPart = ControlPart::Entire;
+ }
+ else
+ {
+ aCtrlType = ControlType::Listbox;
+ aCtrlPart = ControlPart::ListboxWindow;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (aCtrlType != ControlType::Generic && pCtrl->IsNativeControlSupported(aCtrlType, aCtrlPart))
+ {
+ ImplControlValue aControlValue;
+ ControlState nState = ControlState::ENABLED;
+
+ if (!mpBorderWindow->IsEnabled())
+ nState &= ~ControlState::ENABLED;
+ if (mpBorderWindow->HasFocus())
+ nState |= ControlState::FOCUSED;
+ else if(mbNWFBorder)
+ {
+ // FIXME: this is currently only on macOS, see if other platforms can profit
+
+ // FIXME: for macOS focus rings all controls need to support GetNativeControlRegion
+ // for the dropdown style
+ if (pCtrl->HasFocus() || pCtrl->HasChildPathFocus())
+ nState |= ControlState::FOCUSED;
+ }
+
+ bool bMouseOver = false;
+ vcl::Window *pCtrlChild = pCtrl->GetWindow(GetWindowType::FirstChild);
+ while(pCtrlChild)
+ {
+ bMouseOver = pCtrlChild->IsMouseOver();
+ if (bMouseOver)
+ break;
+ pCtrlChild = pCtrlChild->GetWindow(GetWindowType::Next);
+ }
+
+ if (bMouseOver)
+ nState |= ControlState::ROLLOVER;
+
+ Point aPoint;
+ tools::Rectangle aCtrlRegion(aPoint, Size(mnWidth, mnHeight));
+
+ tools::Rectangle aBoundingRgn(aPoint, Size(mnWidth, mnHeight));
+ tools::Rectangle aContentRgn(aCtrlRegion);
+ if (!ImplGetSVData()->maNWFData.mbCanDrawWidgetAnySize &&
+ rRenderContext.GetNativeControlRegion(aCtrlType, aCtrlPart, aCtrlRegion,
+ nState, aControlValue,
+ aBoundingRgn, aContentRgn))
+ {
+ aCtrlRegion=aContentRgn;
+ }
+
+ Color aBackgroundColor = COL_AUTO;
+ if (pCtrl->IsControlBackground())
+ aBackgroundColor = pCtrl->GetBackgroundColor();
+ bNativeOK = rRenderContext.DrawNativeControl(aCtrlType, aCtrlPart, aCtrlRegion, nState, aControlValue, OUString(), aBackgroundColor);
+
+ // if the native theme draws the spinbuttons in one call, make sure the proper settings
+ // are passed, this might force a redraw though... (TODO: improve)
+ if ((aCtrlType == ControlType::Spinbox) && !pCtrl->IsNativeControlSupported(ControlType::Spinbox, ControlPart::ButtonUp))
+ {
+ Edit* pEdit = static_cast<Edit*>(pCtrl)->GetSubEdit();
+ if (pEdit && !pEdit->SupportsDoubleBuffering())
+ pCtrl->Paint(*pCtrl, 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.mnRollState = 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::Roll )
+ {
+ maFrameData.mnRollState |= 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 );
+ }
+ }
+ 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::Roll )
+ {
+ if ( maFrameData.mnRollState & DrawButtonFlags::Pressed )
+ {
+ maFrameData.mnRollState &= ~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());
+ if ( pClientWindow->IsRollUp() )
+ pClientWindow->RollDown();
+ else
+ pClientWindow->RollUp();
+ }
+ }
+ }
+ }
+ 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.IsInside( 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::Roll )
+ {
+ if ( maFrameData.maRollRect.IsInside( aMousePos ) )
+ {
+ if ( !(maFrameData.mnRollState & DrawButtonFlags::Pressed) )
+ {
+ maFrameData.mnRollState |= DrawButtonFlags::Pressed;
+ pBorderWindow->InvalidateBorder();
+ }
+ }
+ else
+ {
+ if ( maFrameData.mnRollState & DrawButtonFlags::Pressed )
+ {
+ maFrameData.mnRollState &= ~DrawButtonFlags::Pressed;
+ pBorderWindow->InvalidateBorder();
+ }
+ }
+ }
+ else if ( maFrameData.mnHitTest & BorderWindowHitTest::Dock )
+ {
+ if ( maFrameData.maDockRect.IsInside( 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.IsInside( 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.IsInside( 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.IsInside( 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 );
+ long nOldWidth = aSize.Width();
+ long nOldHeight = aSize.Height();
+ long nBorderWidth = maFrameData.mnLeftBorder+maFrameData.mnRightBorder;
+ long nBorderHeight = maFrameData.mnTopBorder+maFrameData.mnBottomBorder;
+ long nMinWidth = pBorderWindow->mnMinWidth+nBorderWidth;
+ long nMinHeight = pBorderWindow->mnMinHeight+nBorderHeight;
+ long nMinWidth2 = nBorderWidth;
+ long nMaxWidth = pBorderWindow->mnMaxWidth+nBorderWidth;
+ 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, long nWidth, 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) )
+ {
+ long nRight = pData->maTitleRect.Right() - 3;
+ long const nItemTop = pData->maTitleRect.Top() + 2;
+ long const nItemBottom = pData->maTitleRect.Bottom() - 2;
+
+ auto addSquareOnRight = [&nRight, nItemTop, nItemBottom](
+ tools::Rectangle & rect, 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);
+ }
+
+ if ( pBorderWindow->GetStyle() & WB_ROLLABLE )
+ {
+ addSquareOnRight(pData->maRollRect, 0);
+ }
+ }
+ else
+ {
+ pData->maCloseRect.SetEmpty();
+ pData->maDockRect.SetEmpty();
+ pData->maMenuRect.SetEmpty();
+ pData->maHideRect.SetEmpty();
+ pData->maRollRect.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->maRollRect.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;
+}
+
+long ImplStdBorderWindowView::CalcTitleWidth() const
+{
+ return ImplCalcTitleWidth( &maFrameData );
+}
+
+void ImplStdBorderWindowView::DrawWindow(vcl::RenderContext& rRenderContext, const Point* pOffset)
+{
+ ImplBorderFrameData* pData = &maFrameData;
+ ImplBorderWindow* pBorderWindow = pData->mpBorderWindow;
+ Point aTmpPoint = pOffset ? *pOffset : Point();
+ tools::Rectangle aInRect( aTmpPoint, Size( pData->mnWidth, pData->mnHeight ) );
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ Color aFaceColor(rStyleSettings.GetFaceColor());
+ Color aFrameColor(aFaceColor);
+
+ aFrameColor.DecreaseContrast(sal_uInt8(0.5 * 255));
+
+ // Draw Frame
+ vcl::Region oldClipRgn(rRenderContext.GetClipRegion());
+
+ // for popups, don't draw part of the frame
+ if (!(pData->mnTitleType & (BorderWindowTitleType::Normal | BorderWindowTitleType::Small)))
+ {
+ FloatingWindow* pWin = dynamic_cast<FloatingWindow*>(pData->mpBorderWindow->GetWindow(GetWindowType::Client));
+ if (pWin)
+ {
+ vcl::Region aClipRgn(aInRect);
+ tools::Rectangle aItemClipRect(pWin->ImplGetItemEdgeClipRect());
+ if (!aItemClipRect.IsEmpty())
+ {
+ aItemClipRect.SetPos(pData->mpBorderWindow->AbsoluteScreenToOutputPixel(aItemClipRect.TopLeft()));
+ aClipRgn.Exclude(aItemClipRect);
+ rRenderContext.SetClipRegion(aClipRgn);
+ }
+ }
+ }
+
+ // single line frame
+ rRenderContext.SetLineColor(aFrameColor);
+ rRenderContext.SetFillColor();
+ rRenderContext.DrawRect(aInRect);
+ aInRect.AdjustLeft( 1 );
+ aInRect.AdjustRight( -1 );
+ aInRect.AdjustTop( 1 );
+ aInRect.AdjustBottom( -1 );
+
+ // restore
+ if (!(pData->mnTitleType & (BorderWindowTitleType::Normal | BorderWindowTitleType::Small)))
+ rRenderContext.SetClipRegion(oldClipRgn);
+
+ // Draw Border
+ rRenderContext.SetLineColor();
+ 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->maRollRect.IsEmpty())
+ aInRect.SetRight( pData->maRollRect.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->maRollRect.IsEmpty())
+ {
+ SymbolType eType;
+ if (pBorderWindow->mbRollUp)
+ eType = SymbolType::ROLLDOWN;
+ else
+ eType = SymbolType::ROLLUP;
+ tools::Rectangle aSymbolRect(pData->maRollRect);
+ if (pOffset)
+ aSymbolRect.Move(pOffset->X(), pOffset->Y());
+ ImplDrawBrdWinSymbolButton(&rRenderContext, aSymbolRect, eType, pData->mnRollState);
+ }
+
+ 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_ROLLABLE | WB_CLOSEABLE | WB_STANDALONE | WB_DIALOGCONTROL | WB_NODIALOGCONTROL | WB_SYSTEMFLOATWIN | WB_INTROWIN | WB_DEFAULTWIN | WB_TOOLTIPWIN | WB_NOSHADOW | WB_OWNERDRAWDECORATION | WB_SYSTEMCHILDWINDOW | WB_POPUP);
+ if ( nTypeStyle & BorderWindowStyle::App )
+ nTestStyle |= WB_APP;
+ nStyle &= nTestStyle;
+
+ mpWindowImpl->mbBorderWin = true;
+ mbSmallOutBorder = false;
+ if ( nTypeStyle & BorderWindowStyle::Frame )
+ {
+ if( nStyle & WB_SYSTEMCHILDWINDOW )
+ {
+ mpWindowImpl->mbOverlapWin = true;
+ mpWindowImpl->mbFrame = true;
+ mbFrameBorder = false;
+ }
+ else if( nStyle & (WB_OWNERDRAWDECORATION | WB_POPUP) )
+ {
+ mpWindowImpl->mbOverlapWin = true;
+ mpWindowImpl->mbFrame = true;
+ mbFrameBorder = (nOrgStyle & WB_NOBORDER) == 0;
+ }
+ else
+ {
+ mpWindowImpl->mbOverlapWin = true;
+ mpWindowImpl->mbFrame = true;
+ mbFrameBorder = false;
+ // closeable windows may have a border as well, eg. system floating windows without caption
+ if ( (nOrgStyle & (WB_BORDER | WB_NOBORDER | WB_MOVEABLE | WB_SIZEABLE/* | WB_CLOSEABLE*/)) == WB_BORDER )
+ mbSmallOutBorder = true;
+ }
+ }
+ else if ( nTypeStyle & BorderWindowStyle::Overlap )
+ {
+ mpWindowImpl->mbOverlapWin = true;
+ mbFrameBorder = true;
+ }
+ else
+ mbFrameBorder = false;
+
+ if ( nTypeStyle & BorderWindowStyle::Float )
+ mbFloatWindow = true;
+ else
+ mbFloatWindow = false;
+
+ Window::ImplInit( pParent, nStyle, pSystemParentData );
+ SetBackground();
+ SetTextFillColor();
+
+ mpMenuBarWindow = nullptr;
+ mnMinWidth = 0;
+ mnMinHeight = 0;
+ mnMaxWidth = SHRT_MAX;
+ mnMaxHeight = SHRT_MAX;
+ mnOrgMenuHeight = 0;
+ mbRollUp = false;
+ 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();
+
+ if ( !mbRollUp )
+ {
+ vcl::Window* pClientWindow = ImplGetClientWindow();
+
+ sal_Int32 nLeftBorder;
+ sal_Int32 nTopBorder;
+ sal_Int32 nRightBorder;
+ sal_Int32 nBottomBorder;
+ mpBorderView->GetBorder( nLeftBorder, nTopBorder, nRightBorder, nBottomBorder );
+
+ if (mpMenuBarWindow)
+ {
+ 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)
+ {
+ 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( this, 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( this, 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( this, 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() )
+ {
+ // 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 )
+ {
+ 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::SetRollUp( bool bRollUp, const Size& rSize )
+{
+ mbRollUp = bRollUp;
+ UpdateView( false, rSize );
+}
+
+void ImplBorderWindow::SetCloseButton()
+{
+ SetStyle( GetStyle() | WB_CLOSEABLE );
+ Size aSize = GetOutputSizePixel();
+ mpBorderView->Init( this, aSize.Width(), aSize.Height() );
+ InvalidateBorder();
+}
+
+void ImplBorderWindow::SetDockButton( bool bDockButton )
+{
+ mbDockBtn = bDockButton;
+ Size aSize = GetOutputSizePixel();
+ mpBorderView->Init( this, aSize.Width(), aSize.Height() );
+ InvalidateBorder();
+}
+
+void ImplBorderWindow::SetHideButton( bool bHideButton )
+{
+ mbHideBtn = bHideButton;
+ Size aSize = GetOutputSizePixel();
+ mpBorderView->Init( this, aSize.Width(), aSize.Height() );
+ InvalidateBorder();
+}
+
+void ImplBorderWindow::SetMenuButton( bool bMenuButton )
+{
+ mbMenuBtn = bMenuButton;
+ Size aSize = GetOutputSizePixel();
+ mpBorderView->Init( this, 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();
+}
+
+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/bufferdevice.cxx b/vcl/source/window/bufferdevice.cxx
new file mode 100644
index 000000000..188fbb1ac
--- /dev/null
+++ b/vcl/source/window/bufferdevice.cxx
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "bufferdevice.hxx"
+
+namespace vcl
+{
+BufferDevice::BufferDevice(const VclPtr<vcl::Window>& pWindow, vcl::RenderContext& rRenderContext)
+ : m_pBuffer(VclPtr<VirtualDevice>::Create(rRenderContext))
+ , m_pWindow(pWindow)
+ , m_rRenderContext(rRenderContext)
+{
+ m_pBuffer->SetOutputSizePixel(pWindow->GetOutputSizePixel(), false);
+ m_pBuffer->SetTextColor(rRenderContext.GetTextColor());
+ m_pBuffer->DrawOutDev(Point(0, 0), pWindow->GetOutputSizePixel(), Point(0, 0),
+ pWindow->GetOutputSizePixel(), rRenderContext);
+ m_pBuffer->EnableRTL(rRenderContext.IsRTLEnabled());
+}
+
+void BufferDevice::Dispose()
+{
+ if (m_bDisposed)
+ {
+ return;
+ }
+
+ m_rRenderContext.DrawOutDev(Point(0, 0), m_pWindow->GetOutputSizePixel(), Point(0, 0),
+ m_pWindow->GetOutputSizePixel(), *m_pBuffer);
+ m_bDisposed = true;
+}
+
+BufferDevice::~BufferDevice() { Dispose(); }
+
+vcl::RenderContext* BufferDevice::operator->() { return m_pBuffer.get(); }
+
+vcl::RenderContext& BufferDevice::operator*() { return *m_pBuffer; }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/bufferdevice.hxx b/vcl/source/window/bufferdevice.hxx
new file mode 100644
index 000000000..f785b6bdc
--- /dev/null
+++ b/vcl/source/window/bufferdevice.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/.
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_WINDOW_BUFFERDEVICE_HXX
+#define INCLUDED_VCL_SOURCE_WINDOW_BUFFERDEVICE_HXX
+
+#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*();
+};
+}
+
+#endif // INCLUDED_VCL_SOURCE_WINDOW_BUFFERDEVICE_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/builder.cxx b/vcl/source/window/builder.cxx
new file mode 100644
index 000000000..14d6cdeed
--- /dev/null
+++ b/vcl/source/window/builder.cxx
@@ -0,0 +1,4708 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <config_feature_desktop.h>
+#include <config_options.h>
+
+#include <memory>
+#include <unordered_map>
+#include <com/sun/star/accessibility/AccessibleRole.hpp>
+
+#include <comphelper/lok.hxx>
+#include <i18nutil/unicode.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <osl/module.hxx>
+#include <sal/log.hxx>
+#include <unotools/localedatawrapper.hxx>
+#include <unotools/resmgr.hxx>
+#include <vcl/builder.hxx>
+#include <vcl/toolkit/button.hxx>
+#include <vcl/calendar.hxx>
+#include <vcl/toolkit/dialog.hxx>
+#include <vcl/edit.hxx>
+#include <vcl/toolkit/field.hxx>
+#include <vcl/fieldvalues.hxx>
+#include <vcl/fmtfield.hxx>
+#include <vcl/fixed.hxx>
+#include <vcl/toolkit/fixedhyper.hxx>
+#include <vcl/headbar.hxx>
+#include <vcl/IPrioritable.hxx>
+#include <vcl/ivctrl.hxx>
+#include <vcl/layout.hxx>
+#include <vcl/lstbox.hxx>
+#include <vcl/menubtn.hxx>
+#include <vcl/mnemonic.hxx>
+#include <vcl/toolkit/prgsbar.hxx>
+#include <vcl/scrbar.hxx>
+#include <vcl/split.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/toolkit/svtabbx.hxx>
+#include <vcl/tabctrl.hxx>
+#include <vcl/tabpage.hxx>
+#include <vcl/toolkit/throbber.hxx>
+#include <vcl/toolbox.hxx>
+#include <vcl/treelistentry.hxx>
+#include <vcl/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 <messagedialog.hxx>
+#include <OptionalBox.hxx>
+#include <window.h>
+#include <xmlreader/xmlreader.hxx>
+#include <desktop/crashreport.hxx>
+#include <salinst.hxx>
+#include <strings.hrc>
+#include <treeglue.hxx>
+#include <tools/diagnose_ex.h>
+#include <wizdlg.hxx>
+#include <tools/svlibrary.h>
+#include <jsdialog/jsdialogbuilder.hxx>
+
+#if defined(DISABLE_DYNLOADING) || defined(LINUX)
+#include <dlfcn.h>
+#endif
+
+static bool toBool(const OString &rValue)
+{
+ return (!rValue.isEmpty() && (rValue[0] == 't' || rValue[0] == 'T' || rValue[0] == '1'));
+}
+
+namespace
+{
+ OUString mapStockToImageResource(const OUString& sType)
+ {
+ if (sType == "gtk-index")
+ return SV_RESID_BITMAP_INDEX;
+ else if (sType == "gtk-refresh")
+ return SV_RESID_BITMAP_REFRESH;
+ else if (sType == "gtk-apply")
+ return IMG_APPLY;
+ else if (sType == "gtk-dialog-error")
+ return IMG_ERROR;
+ else if (sType == "gtk-add")
+ return IMG_ADD;
+ else if (sType == "gtk-remove")
+ return IMG_REMOVE;
+ else if (sType == "gtk-copy")
+ return IMG_COPY;
+ else if (sType == "gtk-paste")
+ return IMG_PASTE;
+ return OUString();
+ }
+
+ SymbolType mapStockToSymbol(const OUString& sType)
+ {
+ SymbolType eRet = SymbolType::DONTKNOW;
+ if (sType == "gtk-media-next")
+ eRet = SymbolType::NEXT;
+ else if (sType == "gtk-media-previous")
+ eRet = SymbolType::PREV;
+ else if (sType == "gtk-media-play")
+ eRet = SymbolType::PLAY;
+ else if (sType == "gtk-media-stop")
+ eRet = SymbolType::STOP;
+ else if (sType == "gtk-goto-first")
+ eRet = SymbolType::FIRST;
+ else if (sType == "gtk-goto-last")
+ eRet = SymbolType::LAST;
+ else if (sType == "gtk-go-back")
+ eRet = SymbolType::ARROW_LEFT;
+ else if (sType == "gtk-go-forward")
+ eRet = SymbolType::ARROW_RIGHT;
+ else if (sType == "gtk-go-up")
+ eRet = SymbolType::ARROW_UP;
+ else if (sType == "gtk-go-down")
+ eRet = SymbolType::ARROW_DOWN;
+ else if (sType == "gtk-missing-image")
+ eRet = SymbolType::IMAGE;
+ else if (sType == "gtk-help")
+ eRet = SymbolType::HELP;
+ else if (sType == "gtk-close")
+ eRet = SymbolType::CLOSE;
+ else if (!mapStockToImageResource(sType).isEmpty())
+ eRet = SymbolType::IMAGE;
+ return eRet;
+ }
+
+ void setupFromActionName(Button *pButton, VclBuilder::stringmap &rMap, const css::uno::Reference<css::frame::XFrame>& rFrame);
+}
+
+#if defined SAL_LOG_WARN
+namespace
+{
+ 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
+
+weld::Builder* Application::CreateBuilder(weld::Widget* pParent, const OUString &rUIFile, bool bMobile)
+{
+ bool bUseJSBuilder = false;
+
+ if (bMobile)
+ {
+ if (rUIFile == "modules/swriter/ui/wordcount-mobile.ui")
+ bUseJSBuilder = true;
+ }
+
+ if (bUseJSBuilder)
+ return new JSInstanceBuilder(pParent, VclBuilderContainer::getUIRootDir(), rUIFile);
+ else
+ return ImplGetSVData()->mpDefInst->CreateBuilder(pParent, VclBuilderContainer::getUIRootDir(), rUIFile);
+}
+
+weld::Builder* Application::CreateInterimBuilder(vcl::Window* pParent, const OUString &rUIFile)
+{
+ return ImplGetSVData()->mpDefInst->CreateInterimBuilder(pParent, VclBuilderContainer::getUIRootDir(), rUIFile);
+}
+
+weld::MessageDialog* Application::CreateMessageDialog(weld::Widget* pParent, VclMessageType eMessageType,
+ VclButtonsType eButtonType, const OUString& rPrimaryMessage)
+{
+ 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()
+ {
+ int 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;
+ }
+
+ int SpinButton::denormalize(int nValue) const
+ {
+ const int nFactor = Power10(get_digits());
+
+ if ((nValue < (SAL_MIN_INT32 + nFactor)) || (nValue > (SAL_MAX_INT32 - nFactor)))
+ {
+ return nValue / nFactor;
+ }
+
+ const int nHalf = nFactor / 2;
+
+ if (nValue < 0)
+ return (nValue - nHalf) / nFactor;
+ return (nValue + nHalf) / nFactor;
+ }
+
+ OUString MetricSpinButton::format_number(int nValue) const
+ {
+ OUString aStr;
+
+ const LocaleDataWrapper& rLocaleData = Application::GetSettings().GetLocaleDataWrapper();
+
+ unsigned int nDecimalDigits = m_xSpinButton->get_digits();
+ //pawn percent off to icu to decide whether percent is separated from its number for this locale
+ if (m_eSrcUnit == FieldUnit::PERCENT)
+ {
+ double fValue = nValue;
+ fValue /= SpinButton::Power10(nDecimalDigits);
+ aStr = unicode::formatPercent(fValue, rLocaleData.getLanguageTag());
+ }
+ else
+ {
+ aStr = rLocaleData.getNum(nValue, nDecimalDigits, true, true);
+ OUString aSuffix = MetricToString(m_eSrcUnit);
+ if (m_eSrcUnit != FieldUnit::NONE && m_eSrcUnit != FieldUnit::DEGREE && m_eSrcUnit != FieldUnit::INCH && m_eSrcUnit != FieldUnit::FOOT)
+ aStr += " ";
+ if (m_eSrcUnit == FieldUnit::INCH)
+ {
+ OUString sDoublePrime = u"\u2033";
+ if (aSuffix != "\"" && aSuffix != sDoublePrime)
+ aStr += " ";
+ else
+ aSuffix = sDoublePrime;
+ }
+ else if (m_eSrcUnit == FieldUnit::FOOT)
+ {
+ OUString sPrime = u"\u2032";
+ if (aSuffix != "'" && aSuffix != sPrime)
+ aStr += " ";
+ else
+ aSuffix = sPrime;
+ }
+
+ assert(m_eSrcUnit != FieldUnit::PERCENT);
+ aStr += aSuffix;
+ }
+
+ return aStr;
+ }
+
+ void MetricSpinButton::set_digits(unsigned int digits)
+ {
+ int step, page;
+ get_increments(step, page, m_eSrcUnit);
+ int 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);
+ int 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();
+ }
+ }
+
+ int MetricSpinButton::ConvertValue(int nValue, FieldUnit eInUnit, FieldUnit eOutUnit) const
+ {
+ auto nRet = vcl::ConvertValue(nValue, 0, m_xSpinButton->get_digits(), eInUnit, eOutUnit);
+ if (nRet > SAL_MAX_INT32)
+ nRet = SAL_MAX_INT32;
+ else if (nRet < SAL_MIN_INT32)
+ nRet = SAL_MIN_INT32;
+ return nRet;
+ }
+
+ 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;
+ }
+
+ IMPL_LINK_NOARG(TimeSpinButton, spin_button_cursor_position, Entry&, void)
+ {
+ int nStartPos, nEndPos;
+ m_xSpinButton->get_selection_bounds(nStartPos, nEndPos);
+
+ const LocaleDataWrapper& rLocaleData = Application::GetSettings().GetLocaleDataWrapper();
+ const int nTimeArea = TimeFormatter::GetTimeArea(m_eFormat, m_xSpinButton->get_text(), 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;
+
+ m_xSpinButton->set_increments(nIncrements, nIncrements * 10);
+ }
+
+ IMPL_LINK_NOARG(TimeSpinButton, spin_button_value_changed, SpinButton&, void)
+ {
+ signal_value_changed();
+ }
+
+ IMPL_LINK(TimeSpinButton, spin_button_output, SpinButton&, rSpinButton, void)
+ {
+ int nStartPos, nEndPos;
+ rSpinButton.get_selection_bounds(nStartPos, nEndPos);
+ rSpinButton.set_text(format_number(rSpinButton.get_value()));
+ rSpinButton.set_position(nEndPos);
+ }
+
+ IMPL_LINK(TimeSpinButton, spin_button_input, int*, result, bool)
+ {
+ int nStartPos, nEndPos;
+ m_xSpinButton->get_selection_bounds(nStartPos, nEndPos);
+
+ const LocaleDataWrapper& rLocaleData = Application::GetSettings().GetLocaleDataWrapper();
+ tools::Time aResult(0);
+ bool bRet = TimeFormatter::TextToTime(m_xSpinButton->get_text(), aResult, m_eFormat, true, rLocaleData);
+ if (bRet)
+ *result = ConvertValue(aResult);
+ return bRet;
+ }
+
+ void TimeSpinButton::update_width_chars()
+ {
+ int 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);
+ }
+
+ tools::Time TimeSpinButton::ConvertValue(int nValue)
+ {
+ tools::Time aTime(0);
+ aTime.MakeTimeFromMS(nValue);
+ return aTime;
+ }
+
+ int TimeSpinButton::ConvertValue(const tools::Time& rTime)
+ {
+ return rTime.GetMSFromTime();
+ }
+
+ OUString TimeSpinButton::format_number(int nValue) const
+ {
+ const LocaleDataWrapper& rLocaleData = Application::GetSettings().GetLocaleDataWrapper();
+ return TimeFormatter::FormatTime(ConvertValue(nValue), m_eFormat, TimeFormat::Hour24, true, rLocaleData);
+ }
+
+ EntryTreeView::EntryTreeView(std::unique_ptr<Entry> xEntry, std::unique_ptr<TreeView> xTreeView)
+ : m_xEntry(std::move(xEntry))
+ , m_xTreeView(std::move(xTreeView))
+ {
+ m_xTreeView->connect_changed(LINK(this, EntryTreeView, ClickHdl));
+ m_xEntry->connect_changed(LINK(this, EntryTreeView, ModifyHdl));
+ }
+
+ IMPL_LINK(EntryTreeView, ClickHdl, weld::TreeView&, rView, void)
+ {
+ m_xEntry->set_text(rView.get_selected_text());
+ m_aChangeHdl.Call(*this);
+ }
+
+ IMPL_LINK_NOARG(EntryTreeView, ModifyHdl, weld::Entry&, void)
+ {
+ m_aChangeHdl.Call(*this);
+ }
+
+ void EntryTreeView::set_height_request_by_rows(int nRows)
+ {
+ int nHeight = nRows == -1 ? -1 : m_xTreeView->get_height_rows(nRows);
+ m_xTreeView->set_size_request(m_xTreeView->get_size_request().Width(), nHeight);
+ }
+
+ size_t GetAbsPos(const weld::TreeView& rTreeView, const weld::TreeIter& rIter)
+ {
+ size_t nAbsPos = 0;
+
+ std::unique_ptr<weld::TreeIter> xEntry(rTreeView.make_iterator(&rIter));
+ if (!rTreeView.get_iter_first(*xEntry))
+ xEntry.reset();
+
+ while (xEntry && rTreeView.iter_compare(*xEntry, rIter) != 0)
+ {
+ if (!rTreeView.iter_next(*xEntry))
+ xEntry.reset();
+ nAbsPos++;
+ }
+
+ return nAbsPos;
+ }
+
+ bool IsEntryVisible(const weld::TreeView& rTreeView, const weld::TreeIter& rIter)
+ {
+ // short circuit for the common case
+ if (rTreeView.get_iter_depth(rIter) == 0)
+ return true;
+
+ std::unique_ptr<weld::TreeIter> xEntry(rTreeView.make_iterator(&rIter));
+ bool bRetVal = false;
+ do
+ {
+ if (rTreeView.get_iter_depth(*xEntry) == 0)
+ {
+ bRetVal = true;
+ break;
+ }
+ } while (rTreeView.iter_parent(*xEntry) && rTreeView.get_row_expanded(*xEntry));
+ return bRetVal;
+ }
+}
+
+VclBuilder::VclBuilder(vcl::Window* pParent, const OUString& sUIDir, const OUString& sUIFile,
+ const OString& sID, const css::uno::Reference<css::frame::XFrame>& rFrame,
+ bool bLegacy, const NotebookBarAddonsItem* pNotebookBarAddonsItem)
+ : m_pNotebookBarAddonsItem(pNotebookBarAddonsItem
+ ? new NotebookBarAddonsItem(*pNotebookBarAddonsItem)
+ : new NotebookBarAddonsItem{})
+ , m_sID(sID)
+ , m_sHelpRoot(OUStringToOString(sUIFile, RTL_TEXTENCODING_UTF8))
+ , m_pStringReplace(Translate::GetReadStringHook())
+ , m_pParent(pParent)
+ , m_bToplevelParentFound(false)
+ , m_bLegacy(bLegacy)
+ , m_pParserState(new ParserState)
+ , m_xFrame(rFrame)
+{
+ m_bToplevelHasDeferredInit = pParent &&
+ ((pParent->IsSystemWindow() && static_cast<SystemWindow*>(pParent)->isDeferredInit()) ||
+ (pParent->IsDockingWindow() && static_cast<DockingWindow*>(pParent)->isDeferredInit()));
+ m_bToplevelHasDeferredProperties = m_bToplevelHasDeferredInit;
+
+ sal_Int32 nIdx = m_sHelpRoot.lastIndexOf('.');
+ if (nIdx != -1)
+ m_sHelpRoot = m_sHelpRoot.copy(0, nIdx);
+ m_sHelpRoot += OString('/');
+
+ OUString sUri = sUIDir + sUIFile;
+
+ try
+ {
+ xmlreader::XmlReader reader(sUri);
+
+ handleChild(pParent, reader);
+ }
+ catch (const css::uno::Exception &rExcept)
+ {
+ DBG_UNHANDLED_EXCEPTION("vcl.builder", "Unable to read .ui file");
+ CrashReporter::addKeyValue("VclBuilderException", "Unable to read .ui file: " + rExcept.Message, CrashReporter::Write);
+ throw;
+ }
+
+ //Set Mnemonic widgets when everything has been imported
+ for (auto const& mnemonicWidget : m_pParserState->m_aMnemonicWidgetMaps)
+ {
+ FixedText *pOne = get<FixedText>(mnemonicWidget.m_sID);
+ vcl::Window *pOther = get(mnemonicWidget.m_sValue.toUtf8());
+ SAL_WARN_IF(!pOne || !pOther, "vcl", "missing either source " << mnemonicWidget.m_sID
+ << " or target " << mnemonicWidget.m_sValue << " member of Mnemonic Widget Mapping");
+ if (pOne && pOther)
+ pOne->set_mnemonic_widget(pOther);
+ }
+
+ //Set a11y relations and role when everything has been imported
+ for (auto const& elemAtk : m_pParserState->m_aAtkInfo)
+ {
+ vcl::Window *pSource = elemAtk.first;
+ const stringmap &rMap = elemAtk.second;
+
+ for (auto const& elemMap : rMap)
+ {
+ const OString &rType = elemMap.first;
+ const OUString &rParam = elemMap.second;
+ if (rType == "role")
+ {
+ sal_Int16 role = BuilderUtils::getRoleFromName(rParam.toUtf8());
+ if (role != com::sun::star::accessibility::AccessibleRole::UNKNOWN)
+ pSource->SetAccessibleRole(role);
+ }
+ else
+ {
+ vcl::Window *pTarget = get(rParam.toUtf8());
+ SAL_WARN_IF(!pTarget, "vcl", "missing parameter of a11y relation: " << rParam);
+ if (!pTarget)
+ continue;
+ if (rType == "labelled-by")
+ pSource->SetAccessibleRelationLabeledBy(pTarget);
+ else if (rType == "label-for")
+ pSource->SetAccessibleRelationLabelFor(pTarget);
+ else if (rType == "member-of")
+ pSource->SetAccessibleRelationMemberOf(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));
+ }
+ }
+ }
+
+ //Set ComboBox models when everything has been imported
+ for (auto const& elem : m_pParserState->m_aModelMaps)
+ {
+ vcl::Window* pTarget = get(elem.m_sID);
+ ListBox *pListBoxTarget = dynamic_cast<ListBox*>(pTarget);
+ ComboBox *pComboBoxTarget = dynamic_cast<ComboBox*>(pTarget);
+ SvTabListBox *pTreeBoxTarget = dynamic_cast<SvTabListBox*>(pTarget);
+ // pStore may be empty
+ const ListStore *pStore = get_model_by_name(elem.m_sValue.toUtf8());
+ SAL_WARN_IF(!pListBoxTarget && !pComboBoxTarget && !pTreeBoxTarget, "vcl", "missing elements of combobox");
+ if (pListBoxTarget && pStore)
+ mungeModel(*pListBoxTarget, *pStore, elem.m_nActiveId);
+ else if (pComboBoxTarget && pStore)
+ mungeModel(*pComboBoxTarget, *pStore, elem.m_nActiveId);
+ else if (pTreeBoxTarget && pStore)
+ mungeModel(*pTreeBoxTarget, *pStore, elem.m_nActiveId);
+ }
+
+ //Set TextView buffers when everything has been imported
+ for (auto const& elem : m_pParserState->m_aTextBufferMaps)
+ {
+ VclMultiLineEdit *pTarget = get<VclMultiLineEdit>(elem.m_sID);
+ const TextBuffer *pBuffer = get_buffer_by_name(elem.m_sValue.toUtf8());
+ SAL_WARN_IF(!pTarget || !pBuffer, "vcl", "missing elements of textview/textbuffer");
+ if (pTarget && pBuffer)
+ mungeTextBuffer(*pTarget, *pBuffer);
+ }
+
+ //Set SpinButton adjustments when everything has been imported
+ for (auto const& elem : m_pParserState->m_aNumericFormatterAdjustmentMaps)
+ {
+ NumericFormatter *pTarget = dynamic_cast<NumericFormatter*>(get(elem.m_sID));
+ const Adjustment *pAdjustment = get_adjustment_by_name(elem.m_sValue.toUtf8());
+ SAL_WARN_IF(!pTarget, "vcl", "missing NumericFormatter element of spinbutton/adjustment");
+ SAL_WARN_IF(!pAdjustment, "vcl", "missing Adjustment element of spinbutton/adjustment");
+ if (pTarget && pAdjustment)
+ mungeAdjustment(*pTarget, *pAdjustment);
+ }
+
+ for (auto const& elem : m_pParserState->m_aFormattedFormatterAdjustmentMaps)
+ {
+ FormattedField *pTarget = dynamic_cast<FormattedField*>(get(elem.m_sID));
+ const Adjustment *pAdjustment = get_adjustment_by_name(elem.m_sValue.toUtf8());
+ SAL_WARN_IF(!pTarget, "vcl", "missing FormattedField element of spinbutton/adjustment");
+ SAL_WARN_IF(!pAdjustment, "vcl", "missing Adjustment element of spinbutton/adjustment");
+ if (pTarget && pAdjustment)
+ mungeAdjustment(*pTarget, *pAdjustment);
+ }
+
+ for (auto const& elem : m_pParserState->m_aTimeFormatterAdjustmentMaps)
+ {
+ TimeField *pTarget = dynamic_cast<TimeField*>(get(elem.m_sID));
+ const Adjustment *pAdjustment = get_adjustment_by_name(elem.m_sValue.toUtf8());
+ SAL_WARN_IF(!pTarget || !pAdjustment, "vcl", "missing elements of spinbutton/adjustment");
+ if (pTarget && pAdjustment)
+ mungeAdjustment(*pTarget, *pAdjustment);
+ }
+
+ for (auto const& elem : m_pParserState->m_aDateFormatterAdjustmentMaps)
+ {
+ DateField *pTarget = dynamic_cast<DateField*>(get(elem.m_sID));
+ const Adjustment *pAdjustment = get_adjustment_by_name(elem.m_sValue.toUtf8());
+ SAL_WARN_IF(!pTarget || !pAdjustment, "vcl", "missing elements of spinbutton/adjustment");
+ if (pTarget && pAdjustment)
+ mungeAdjustment(*pTarget, *pAdjustment);
+ }
+
+ //Set ScrollBar adjustments when everything has been imported
+ for (auto const& elem : m_pParserState->m_aScrollAdjustmentMaps)
+ {
+ ScrollBar *pTarget = get<ScrollBar>(elem.m_sID);
+ const Adjustment *pAdjustment = get_adjustment_by_name(elem.m_sValue.toUtf8());
+ SAL_WARN_IF(!pTarget || !pAdjustment, "vcl", "missing elements of scrollbar/adjustment");
+ if (pTarget && pAdjustment)
+ mungeAdjustment(*pTarget, *pAdjustment);
+ }
+
+ //Set Scale(Slider) adjustments
+ for (auto const& elem : m_pParserState->m_aSliderAdjustmentMaps)
+ {
+ Slider* pTarget = dynamic_cast<Slider*>(get(elem.m_sID));
+ const Adjustment* pAdjustment = get_adjustment_by_name(elem.m_sValue.toUtf8());
+ SAL_WARN_IF(!pTarget || !pAdjustment, "vcl", "missing elements of scale(slider)/adjustment");
+ if (pTarget && pAdjustment)
+ {
+ mungeAdjustment(*pTarget, *pAdjustment);
+ }
+ }
+
+ //Set size-groups when all widgets have been imported
+ for (auto const& sizeGroup : m_pParserState->m_aSizeGroups)
+ {
+ std::shared_ptr<VclSizeGroup> xGroup(std::make_shared<VclSizeGroup>());
+
+ for (auto const& elem : sizeGroup.m_aProperties)
+ {
+ const OString &rKey = elem.first;
+ const OUString &rValue = elem.second;
+ xGroup->set_property(rKey, rValue);
+ }
+
+ for (auto const& elem : sizeGroup.m_aWidgets)
+ {
+ vcl::Window* pWindow = get(elem.getStr());
+ pWindow->add_to_size_group(xGroup);
+ }
+ }
+
+ //Set button images when everything has been imported
+ std::set<OUString> aImagesToBeRemoved;
+ for (auto const& elem : m_pParserState->m_aButtonImageWidgetMaps)
+ {
+ PushButton *pTargetButton = nullptr;
+ RadioButton *pTargetRadio = nullptr;
+ Button *pTarget = nullptr;
+
+ if (!elem.m_bRadio)
+ {
+ pTargetButton = get<PushButton>(elem.m_sID);
+ pTarget = pTargetButton;
+ }
+ else
+ {
+ pTargetRadio = get<RadioButton>(elem.m_sID);
+ pTarget = pTargetRadio;
+ }
+
+ FixedImage *pImage = get<FixedImage>(elem.m_sValue.toUtf8());
+ SAL_WARN_IF(!pTarget || !pImage,
+ "vcl", "missing elements of button/image/stock");
+ if (!pTarget || !pImage)
+ continue;
+ aImagesToBeRemoved.insert(elem.m_sValue);
+
+ VclBuilder::StockMap::iterator aFind = m_pParserState->m_aStockMap.find(elem.m_sValue.toUtf8());
+ if (aFind == m_pParserState->m_aStockMap.end())
+ {
+ if (!elem.m_bRadio)
+ {
+ pTargetButton->SetModeImage(pImage->GetImage());
+ if (pImage->GetStyle() & WB_SMALLSTYLE)
+ {
+ pTargetButton->SetStyle(pTargetButton->GetStyle() | WB_SMALLSTYLE);
+ Size aSz(pTargetButton->GetModeImage().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());
+ }
+ else
+ {
+ const stockinfo &rImageInfo = aFind->second;
+ SymbolType eType = mapStockToSymbol(rImageInfo.m_sStock);
+ SAL_WARN_IF(eType == SymbolType::DONTKNOW, "vcl", "missing stock image element for button");
+ if (eType == SymbolType::DONTKNOW)
+ continue;
+ if (!elem.m_bRadio)
+ {
+ pTargetButton->SetSymbol(eType);
+ //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
+ if (eType != SymbolType::IMAGE)
+ pTargetButton->SetStyle(pTargetButton->GetStyle() | WB_SMALLSTYLE);
+ }
+ else
+ SAL_WARN_IF(eType != SymbolType::IMAGE, "vcl.builder", "unimplemented symbol type for radiobuttons");
+ if (eType == SymbolType::IMAGE)
+ {
+ Image const aImage(StockImage::Yes,
+ mapStockToImageResource(rImageInfo.m_sStock));
+ if (!elem.m_bRadio)
+ pTargetButton->SetModeImage(aImage);
+ else
+ pTargetRadio->SetModeRadioImage(aImage);
+ }
+ switch (rImageInfo.m_nSize)
+ {
+ case 1:
+ pTarget->SetSmallSymbol();
+ break;
+ case 3:
+ // 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 " << rImageInfo.m_nSize);
+ break;
+ }
+ }
+ }
+
+ //There may be duplicate use of an Image, so we used a set to collect and
+ //now we can remove them from the tree after their final munge
+ for (auto const& elem : aImagesToBeRemoved)
+ {
+ delete_by_name(elem.toUtf8());
+ }
+
+ //fill in any stock icons in surviving images
+ for (auto const& elem : m_pParserState->m_aStockMap)
+ {
+ FixedImage *pImage = get<FixedImage>(elem.first);
+ SAL_WARN_IF(!pImage, "vcl", "missing elements of image/stock: " << elem.first);
+ if (!pImage)
+ continue;
+
+ const stockinfo &rImageInfo = elem.second;
+ if (rImageInfo.m_sStock == "gtk-missing-image")
+ continue;
+
+ SymbolType eType = mapStockToSymbol(rImageInfo.m_sStock);
+ SAL_WARN_IF(eType != SymbolType::IMAGE, "vcl", "unimplemented symbol type for images");
+ if (eType != SymbolType::IMAGE)
+ continue;
+
+ Image const aImage(StockImage::Yes,
+ mapStockToImageResource(rImageInfo.m_sStock));
+ pImage->SetImage(aImage);
+ }
+
+ //Set button menus when everything has been imported
+ for (auto const& elem : m_pParserState->m_aButtonMenuMaps)
+ {
+ MenuButton *pTarget = get<MenuButton>(elem.m_sID);
+ PopupMenu *pMenu = get_menu(elem.m_sValue.toUtf8());
+ SAL_WARN_IF(!pTarget || !pMenu,
+ "vcl", "missing elements of button/menu");
+ if (!pTarget || !pMenu)
+ continue;
+ pTarget->SetPopupMenu(pMenu);
+ }
+
+ //Remove ScrollWindow parent widgets whose children in vcl implement scrolling
+ //internally.
+ for (auto const& elem : m_pParserState->m_aRedundantParentWidgets)
+ {
+ delete_by_window(elem.first);
+ }
+
+ //fdo#67378 merge the label into the disclosure button
+ for (auto const& elem : m_pParserState->m_aExpanderWidgets)
+ {
+ vcl::Window *pChild = elem->get_child();
+ vcl::Window* pLabel = elem->GetWindow(GetWindowType::LastChild);
+ if (pLabel && pLabel != pChild && pLabel->GetType() == WindowType::FIXEDTEXT)
+ {
+ FixedText *pLabelWidget = static_cast<FixedText*>(pLabel);
+ elem->set_label(pLabelWidget->GetText());
+ 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 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 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;
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("icon-name"));
+ if (aFind != rMap.end())
+ {
+ sIconName = aFind->second;
+ rMap.erase(aFind);
+ }
+ return sIconName;
+ }
+
+ OUString extractStockId(VclBuilder::stringmap &rMap)
+ {
+ OUString sIconName;
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("stock-id"));
+ if (aFind != rMap.end())
+ {
+ sIconName = aFind->second;
+ rMap.erase(aFind);
+ }
+ return sIconName;
+ }
+
+ OUString getStockText(const OUString &rType)
+ {
+ if (rType == "gtk-ok")
+ return VclResId(SV_BUTTONTEXT_OK);
+ else if (rType == "gtk-cancel")
+ return VclResId(SV_BUTTONTEXT_CANCEL);
+ else if (rType == "gtk-help")
+ return VclResId(SV_BUTTONTEXT_HELP);
+ else if (rType == "gtk-close")
+ return VclResId(SV_BUTTONTEXT_CLOSE);
+ else if (rType == "gtk-revert-to-saved")
+ return VclResId(SV_BUTTONTEXT_RESET);
+ else if (rType == "gtk-add")
+ return VclResId(SV_BUTTONTEXT_ADD);
+ else if (rType == "gtk-delete")
+ return VclResId(SV_BUTTONTEXT_DELETE);
+ else if (rType == "gtk-remove")
+ return VclResId(SV_BUTTONTEXT_REMOVE);
+ else if (rType == "gtk-new")
+ return VclResId(SV_BUTTONTEXT_NEW);
+ else if (rType == "gtk-edit")
+ return VclResId(SV_BUTTONTEXT_EDIT);
+ else if (rType == "gtk-apply")
+ return VclResId(SV_BUTTONTEXT_APPLY);
+ else if (rType == "gtk-save")
+ return VclResId(SV_BUTTONTEXT_SAVE);
+ else if (rType == "gtk-open")
+ return VclResId(SV_BUTTONTEXT_OPEN);
+ else if (rType == "gtk-undo")
+ return VclResId(SV_BUTTONTEXT_UNDO);
+ else if (rType == "gtk-paste")
+ return VclResId(SV_BUTTONTEXT_PASTE);
+ else if (rType == "gtk-media-next")
+ return VclResId(SV_BUTTONTEXT_NEXT);
+ else if (rType == "gtk-media-previous")
+ return VclResId(SV_BUTTONTEXT_PREV);
+ else if (rType == "gtk-go-up")
+ return VclResId(SV_BUTTONTEXT_GO_UP);
+ else if (rType == "gtk-go-down")
+ return VclResId(SV_BUTTONTEXT_GO_DOWN);
+ else if (rType == "gtk-clear")
+ return VclResId(SV_BUTTONTEXT_CLEAR);
+ else if (rType == "gtk-media-play")
+ return VclResId(SV_BUTTONTEXT_PLAY);
+ else if (rType == "gtk-find")
+ return VclResId(SV_BUTTONTEXT_FIND);
+ else if (rType == "gtk-stop")
+ return VclResId(SV_BUTTONTEXT_STOP);
+ else if (rType == "gtk-connect")
+ return VclResId(SV_BUTTONTEXT_CONNECT);
+ else if (rType == "gtk-yes")
+ return VclResId(SV_BUTTONTEXT_YES);
+ else if (rType == "gtk-no")
+ return VclResId(SV_BUTTONTEXT_NO);
+ SAL_WARN("vcl.builder", "unknown stock type: " << rType);
+ return OUString();
+ }
+
+ bool extractStock(VclBuilder::stringmap &rMap)
+ {
+ bool bIsStock = false;
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("use-stock"));
+ if (aFind != rMap.end())
+ {
+ bIsStock = toBool(aFind->second);
+ rMap.erase(aFind);
+ }
+ return bIsStock;
+ }
+
+ WinBits extractRelief(VclBuilder::stringmap &rMap)
+ {
+ WinBits nBits = WB_3DLOOK;
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("relief"));
+ if (aFind != rMap.end())
+ {
+ if (aFind->second == "half")
+ nBits = WB_FLATBUTTON | WB_BEVELBUTTON;
+ else if (aFind->second == "none")
+ nBits = WB_FLATBUTTON;
+ rMap.erase(aFind);
+ }
+ return nBits;
+ }
+
+ OUString extractLabel(VclBuilder::stringmap &rMap)
+ {
+ OUString sType;
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("label"));
+ if (aFind != rMap.end())
+ {
+ sType = aFind->second;
+ rMap.erase(aFind);
+ }
+ return sType;
+ }
+
+ OUString extractActionName(VclBuilder::stringmap &rMap)
+ {
+ OUString sActionName;
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("action-name"));
+ if (aFind != rMap.end())
+ {
+ sActionName = aFind->second;
+ rMap.erase(aFind);
+ }
+ return sActionName;
+ }
+
+ bool extractVisible(VclBuilder::stringmap &rMap)
+ {
+ bool bRet = false;
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("visible"));
+ if (aFind != rMap.end())
+ {
+ bRet = toBool(aFind->second);
+ rMap.erase(aFind);
+ }
+ return bRet;
+ }
+
+ Size extractSizeRequest(VclBuilder::stringmap &rMap)
+ {
+ OUString sWidthRequest("0");
+ OUString sHeightRequest("0");
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("width-request"));
+ if (aFind != rMap.end())
+ {
+ sWidthRequest = aFind->second;
+ rMap.erase(aFind);
+ }
+ aFind = rMap.find("height-request");
+ if (aFind != rMap.end())
+ {
+ sHeightRequest = aFind->second;
+ rMap.erase(aFind);
+ }
+ return Size(sWidthRequest.toInt32(), sHeightRequest.toInt32());
+ }
+
+ OUString extractTooltipText(VclBuilder::stringmap &rMap)
+ {
+ OUString sTooltipText;
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("tooltip-text"));
+ if (aFind == rMap.end())
+ aFind = rMap.find(OString("tooltip-markup"));
+ if (aFind != rMap.end())
+ {
+ sTooltipText = aFind->second;
+ rMap.erase(aFind);
+ }
+ return sTooltipText;
+ }
+
+ float extractAlignment(VclBuilder::stringmap &rMap)
+ {
+ float f = 0.0;
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("alignment"));
+ if (aFind != rMap.end())
+ {
+ f = aFind->second.toFloat();
+ rMap.erase(aFind);
+ }
+ return f;
+ }
+
+ OUString extractTitle(VclBuilder::stringmap &rMap)
+ {
+ OUString sTitle;
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("title"));
+ if (aFind != rMap.end())
+ {
+ sTitle = aFind->second;
+ rMap.erase(aFind);
+ }
+ return sTitle;
+ }
+
+ bool extractHeadersVisible(VclBuilder::stringmap &rMap)
+ {
+ bool bHeadersVisible = true;
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("headers-visible"));
+ if (aFind != rMap.end())
+ {
+ bHeadersVisible = toBool(aFind->second);
+ rMap.erase(aFind);
+ }
+ return bHeadersVisible;
+ }
+
+ bool extractSortIndicator(VclBuilder::stringmap &rMap)
+ {
+ bool bSortIndicator = false;
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("sort-indicator"));
+ if (aFind != rMap.end())
+ {
+ bSortIndicator = toBool(aFind->second);
+ rMap.erase(aFind);
+ }
+ return bSortIndicator;
+ }
+
+ bool extractClickable(VclBuilder::stringmap &rMap)
+ {
+ bool bClickable = false;
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("clickable"));
+ if (aFind != rMap.end())
+ {
+ bClickable = toBool(aFind->second);
+ rMap.erase(aFind);
+ }
+ return bClickable;
+ }
+
+ void setupFromActionName(Button *pButton, VclBuilder::stringmap &rMap, const css::uno::Reference<css::frame::XFrame>& rFrame)
+ {
+ if (!rFrame.is())
+ return;
+
+ OUString aCommand(extractActionName(rMap));
+ if (aCommand.isEmpty())
+ return;
+
+ OUString aModuleName(vcl::CommandInfoProvider::GetModuleIdentifier(rFrame));
+ auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(aCommand, aModuleName);
+ OUString aLabel(vcl::CommandInfoProvider::GetLabelForCommand(aProperties));
+ if (!aLabel.isEmpty())
+ pButton->SetText(aLabel);
+
+ OUString aTooltip(vcl::CommandInfoProvider::GetTooltipForCommand(aCommand, aProperties, rFrame));
+ if (!aTooltip.isEmpty())
+ pButton->SetQuickHelpText(aTooltip);
+
+ Image aImage(vcl::CommandInfoProvider::GetImageForCommand(aCommand, rFrame));
+ pButton->SetModeImage(aImage);
+
+ pButton->SetCommandHandler(aCommand);
+ }
+
+ VclPtr<Button> extractStockAndBuildPushButton(vcl::Window *pParent, VclBuilder::stringmap &rMap, bool bToggle, bool bLegacy)
+ {
+ WinBits nBits = WB_CLIPCHILDREN|WB_CENTER|WB_VCENTER;
+ if (bToggle)
+ nBits |= WB_TOGGLE;
+
+ nBits |= extractRelief(rMap);
+
+ VclPtr<Button> xWindow;
+
+ if (extractStock(rMap))
+ {
+ OUString sType = extractLabel(rMap);
+ if (bLegacy)
+ {
+ if (sType == "gtk-ok")
+ xWindow = VclPtr<OKButton>::Create(pParent, nBits);
+ else if (sType == "gtk-cancel")
+ xWindow = VclPtr<CancelButton>::Create(pParent, nBits);
+ else if (sType == "gtk-close")
+ xWindow = VclPtr<CloseButton>::Create(pParent, nBits);
+ else if (sType == "gtk-help")
+ xWindow = VclPtr<HelpButton>::Create(pParent, nBits);
+ }
+ if (!xWindow)
+ {
+ xWindow = VclPtr<PushButton>::Create(pParent, nBits);
+ xWindow->SetText(getStockText(sType));
+ }
+ }
+
+ if (!xWindow)
+ 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);
+
+ if (extractStock(rMap))
+ {
+ xWindow->SetText(getStockText(extractLabel(rMap)));
+ }
+
+ return xWindow;
+ }
+
+ VclPtr<Button> extractStockAndBuildMenuToggleButton(vcl::Window *pParent, VclBuilder::stringmap &rMap)
+ {
+ WinBits nBits = WB_CLIPCHILDREN|WB_CENTER|WB_VCENTER|WB_3DLOOK;
+
+ nBits |= extractRelief(rMap);
+
+ VclPtr<Button> xWindow = VclPtr<MenuToggleButton>::Create(pParent, nBits);
+
+ if (extractStock(rMap))
+ {
+ xWindow->SetText(getStockText(extractLabel(rMap)));
+ }
+
+ return xWindow;
+ }
+
+ OUString extractUnit(const OUString& sPattern)
+ {
+ OUString sUnit(sPattern);
+ for (sal_Int32 i = 0; i < sPattern.getLength(); ++i)
+ {
+ if (sPattern[i] != '.' && sPattern[i] != ',' && sPattern[i] != '0')
+ {
+ sUnit = sPattern.copy(i);
+ break;
+ }
+ }
+ return sUnit;
+ }
+
+ int extractDecimalDigits(const OUString& sPattern)
+ {
+ int nDigits = 0;
+ bool bAfterPoint = false;
+ for (sal_Int32 i = 0; i < sPattern.getLength(); ++i)
+ {
+ if (sPattern[i] == '.' || sPattern[i] == ',')
+ bAfterPoint = true;
+ else if (sPattern[i] == '0')
+ {
+ if (bAfterPoint)
+ ++nDigits;
+ }
+ else
+ break;
+ }
+ return nDigits;
+ }
+
+ FieldUnit detectMetricUnit(const OUString& sUnit)
+ {
+ FieldUnit eUnit = FieldUnit::NONE;
+
+ if (sUnit == "mm")
+ eUnit = FieldUnit::MM;
+ else if (sUnit == "cm")
+ eUnit = FieldUnit::CM;
+ else if (sUnit == "m")
+ eUnit = FieldUnit::M;
+ else if (sUnit == "km")
+ eUnit = FieldUnit::KM;
+ else if ((sUnit == "twips") || (sUnit == "twip"))
+ eUnit = FieldUnit::TWIP;
+ else if (sUnit == "pt")
+ eUnit = FieldUnit::POINT;
+ else if (sUnit == "pc")
+ eUnit = FieldUnit::PICA;
+ else if (sUnit == "\"" || (sUnit == "in") || (sUnit == "inch"))
+ eUnit = FieldUnit::INCH;
+ else if ((sUnit == "'") || (sUnit == "ft") || (sUnit == "foot") || (sUnit == "feet"))
+ eUnit = FieldUnit::FOOT;
+ else if (sUnit == "mile" || (sUnit == "miles"))
+ eUnit = FieldUnit::MILE;
+ else if (sUnit == "ch")
+ eUnit = FieldUnit::CHAR;
+ else if (sUnit == "line")
+ eUnit = FieldUnit::LINE;
+ else if (sUnit == "%")
+ eUnit = FieldUnit::PERCENT;
+ else if ((sUnit == "pixels") || (sUnit == "pixel") || (sUnit == "px"))
+ eUnit = FieldUnit::PIXEL;
+ else if ((sUnit == "degrees") || (sUnit == "degree"))
+ eUnit = FieldUnit::DEGREE;
+ else if ((sUnit == "sec") || (sUnit == "seconds") || (sUnit == "second"))
+ eUnit = FieldUnit::SECOND;
+ else if ((sUnit == "ms") || (sUnit == "milliseconds") || (sUnit == "millisecond"))
+ eUnit = FieldUnit::MILLISECOND;
+ else if (sUnit != "0")
+ eUnit = FieldUnit::CUSTOM;
+
+ return eUnit;
+ }
+
+ WinBits extractDeferredBits(VclBuilder::stringmap &rMap)
+ {
+ WinBits nBits = WB_3DLOOK|WB_HIDE;
+ if (extractResizable(rMap))
+ nBits |= WB_SIZEABLE;
+ if (extractCloseable(rMap))
+ nBits |= WB_CLOSEABLE;
+ OUString sBorder = BuilderUtils::extractCustomProperty(rMap);
+ if (!sBorder.isEmpty())
+ nBits |= WB_BORDER;
+ if (!extractDecorated(rMap))
+ nBits |= WB_OWNERDRAWDECORATION;
+ OUString sType(extractTypeHint(rMap));
+ if (sType == "utility")
+ nBits |= WB_SYSTEMWINDOW | WB_DIALOGCONTROL | WB_MOVEABLE;
+ else if (sType == "popup-menu")
+ nBits |= WB_SYSTEMWINDOW | WB_DIALOGCONTROL | WB_POPUP;
+ else if (sType == "dock")
+ nBits |= WB_DOCKABLE | WB_MOVEABLE;
+ else
+ nBits |= WB_MOVEABLE;
+ return nBits;
+ }
+}
+
+void VclBuilder::extractGroup(const OString &id, stringmap &rMap)
+{
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("group"));
+ if (aFind != rMap.end())
+ {
+ OUString sID = aFind->second;
+ sal_Int32 nDelim = sID.indexOf(':');
+ if (nDelim != -1)
+ sID = sID.copy(0, nDelim);
+ m_pParserState->m_aGroupMaps.emplace_back(id, sID.toUtf8());
+ rMap.erase(aFind);
+ }
+}
+
+void VclBuilder::connectNumericFormatterAdjustment(const OString &id, const OUString &rAdjustment)
+{
+ if (!rAdjustment.isEmpty())
+ m_pParserState->m_aNumericFormatterAdjustmentMaps.emplace_back(id, rAdjustment);
+}
+
+void VclBuilder::connectFormattedFormatterAdjustment(const OString &id, const OUString &rAdjustment)
+{
+ if (!rAdjustment.isEmpty())
+ m_pParserState->m_aFormattedFormatterAdjustmentMaps.emplace_back(id, rAdjustment);
+}
+
+void VclBuilder::connectTimeFormatterAdjustment(const OString &id, const OUString &rAdjustment)
+{
+ if (!rAdjustment.isEmpty())
+ m_pParserState->m_aTimeFormatterAdjustmentMaps.emplace_back(id, rAdjustment);
+}
+
+void VclBuilder::connectDateFormatterAdjustment(const OString &id, const OUString &rAdjustment)
+{
+ if (!rAdjustment.isEmpty())
+ m_pParserState->m_aDateFormatterAdjustmentMaps.emplace_back(id, rAdjustment);
+}
+
+bool VclBuilder::extractAdjustmentToMap(const OString& id, VclBuilder::stringmap& rMap, std::vector<WidgetAdjustmentMap>& rAdjustmentMap)
+{
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("adjustment"));
+ if (aFind != rMap.end())
+ {
+ rAdjustmentMap.emplace_back(id, aFind->second);
+ rMap.erase(aFind);
+ return true;
+ }
+ return false;
+}
+
+namespace
+{
+ sal_Int32 extractActive(VclBuilder::stringmap &rMap)
+ {
+ sal_Int32 nActiveId = 0;
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("active"));
+ if (aFind != rMap.end())
+ {
+ nActiveId = aFind->second.toInt32();
+ rMap.erase(aFind);
+ }
+ return nActiveId;
+ }
+
+ bool extractSelectable(VclBuilder::stringmap &rMap)
+ {
+ bool bSelectable = false;
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("selectable"));
+ if (aFind != rMap.end())
+ {
+ bSelectable = toBool(aFind->second);
+ rMap.erase(aFind);
+ }
+ return bSelectable;
+ }
+
+ OUString extractAdjustment(VclBuilder::stringmap &rMap)
+ {
+ OUString sAdjustment;
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("adjustment"));
+ if (aFind != rMap.end())
+ {
+ sAdjustment= aFind->second;
+ rMap.erase(aFind);
+ return sAdjustment;
+ }
+ return sAdjustment;
+ }
+
+ bool extractDrawIndicator(VclBuilder::stringmap &rMap)
+ {
+ bool bDrawIndicator = false;
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("draw-indicator"));
+ if (aFind != rMap.end())
+ {
+ bDrawIndicator = toBool(aFind->second);
+ rMap.erase(aFind);
+ }
+ return bDrawIndicator;
+ }
+}
+
+void VclBuilder::extractModel(const OString &id, stringmap &rMap)
+{
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("model"));
+ if (aFind != rMap.end())
+ {
+ m_pParserState->m_aModelMaps.emplace_back(id, aFind->second,
+ extractActive(rMap));
+ rMap.erase(aFind);
+ }
+}
+
+void VclBuilder::extractBuffer(const OString &id, stringmap &rMap)
+{
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("buffer"));
+ if (aFind != rMap.end())
+ {
+ m_pParserState->m_aTextBufferMaps.emplace_back(id, aFind->second);
+ rMap.erase(aFind);
+ }
+}
+
+void VclBuilder::extractStock(const OString &id, stringmap &rMap)
+{
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("stock"));
+ if (aFind != rMap.end())
+ {
+ stockinfo aInfo;
+ aInfo.m_sStock = aFind->second;
+ rMap.erase(aFind);
+ aFind = rMap.find(OString("icon-size"));
+ if (aFind != rMap.end())
+ {
+ aInfo.m_nSize = aFind->second.toInt32();
+ rMap.erase(aFind);
+ }
+ m_pParserState->m_aStockMap[id] = aInfo;
+ }
+}
+
+void VclBuilder::extractButtonImage(const OString &id, stringmap &rMap, bool bRadio)
+{
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("image"));
+ if (aFind != rMap.end())
+ {
+ m_pParserState->m_aButtonImageWidgetMaps.emplace_back(id, aFind->second, bRadio);
+ rMap.erase(aFind);
+ }
+}
+
+void VclBuilder::extractMnemonicWidget(const OString &rLabelID, stringmap &rMap)
+{
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("mnemonic-widget"));
+ if (aFind != rMap.end())
+ {
+ OUString sID = aFind->second;
+ sal_Int32 nDelim = sID.indexOf(':');
+ if (nDelim != -1)
+ sID = sID.copy(0, nDelim);
+ m_pParserState->m_aMnemonicWidgetMaps.emplace_back(rLabelID, sID);
+ rMap.erase(aFind);
+ }
+}
+
+vcl::Window* VclBuilder::prepareWidgetOwnScrolling(vcl::Window *pParent, WinBits &rWinStyle)
+{
+ //For Widgets that manage their own scrolling, if one appears as a child of
+ //a scrolling window shoehorn that scrolling settings to this widget and
+ //return the real parent to use
+ if (pParent && pParent->GetType() == WindowType::SCROLLWINDOW)
+ {
+ WinBits nScrollBits = pParent->GetStyle();
+ nScrollBits &= (WB_AUTOHSCROLL|WB_HSCROLL|WB_AUTOVSCROLL|WB_VSCROLL);
+ rWinStyle |= nScrollBits | WB_BORDER;
+ pParent = pParent->GetParent();
+ }
+
+ return pParent;
+}
+
+void VclBuilder::cleanupWidgetOwnScrolling(vcl::Window *pScrollParent, vcl::Window *pWindow, stringmap &rMap)
+{
+ //remove the redundant scrolling parent
+ sal_Int32 nWidthReq = pScrollParent->get_width_request();
+ rMap[OString("width-request")] = OUString::number(nWidthReq);
+ sal_Int32 nHeightReq = pScrollParent->get_height_request();
+ rMap[OString("height-request")] = OUString::number(nHeightReq);
+
+ m_pParserState->m_aRedundantParentWidgets[pScrollParent] = pWindow;
+}
+
+#ifndef DISABLE_DYNLOADING
+
+extern "C" { static void thisModule() {} }
+
+namespace {
+
+// Don't unload the module on destruction
+class NoAutoUnloadModule : public osl::Module
+{
+public:
+ ~NoAutoUnloadModule() { release(); }
+};
+
+}
+
+typedef std::map<OUString, std::shared_ptr<NoAutoUnloadModule>> ModuleMap;
+static ModuleMap g_aModuleMap;
+
+#if ENABLE_MERGELIBS
+static std::shared_ptr<NoAutoUnloadModule> g_pMergedLib = std::make_shared<NoAutoUnloadModule>();
+#endif
+
+#ifndef SAL_DLLPREFIX
+# define SAL_DLLPREFIX ""
+#endif
+
+#endif
+
+void VclBuilder::preload()
+{
+#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", "svxcorelo", "foruilo",
+ "vcllo", "svxlo", "cuilo", "swlo",
+ "swuilo", "sclo", "sdlo", "chartcontrollerlo",
+ "smlo", "scuilo", "basctllo", "sduilo",
+ "scnlo", "xsltdlglo", "pcrlo" // "dbulo"
+ };
+ for (const auto & lib : aWidgetLibs)
+ {
+ std::unique_ptr<NoAutoUnloadModule> pModule(new NoAutoUnloadModule);
+ OUString sModule = SAL_DLLPREFIX + OUString::createFromAscii(lib) + SAL_DLLEXTENSION;
+ if (pModule->loadRelative(&thisModule, sModule))
+ g_aModuleMap.insert(std::make_pair(sModule, std::move(pModule)));
+ }
+#endif // ENABLE_MERGELIBS
+#endif // DISABLE_DYNLOADING
+}
+
+#if defined DISABLE_DYNLOADING && !HAVE_FEATURE_DESKTOP
+extern "C" VclBuilder::customMakeWidget lo_get_custom_widget_func(const char* name);
+#endif
+
+namespace
+{
+// Takes a string like "sfxlo-SidebarToolBox"
+VclBuilder::customMakeWidget GetCustomMakeWidget(const OString& name)
+{
+ VclBuilder::customMakeWidget pFunction = nullptr;
+ if (sal_Int32 nDelim = name.indexOf('-'); nDelim != -1)
+ {
+ const OString aFunction("make" + name.copy(nDelim + 1));
+ const OUString sFunction(OStringToOUString(aFunction, RTL_TEXTENCODING_UTF8));
+
+#ifndef DISABLE_DYNLOADING
+ const OUString sModule = SAL_DLLPREFIX
+ + OStringToOUString(name.copy(0, nDelim), RTL_TEXTENCODING_UTF8)
+ + SAL_DLLEXTENSION;
+ ModuleMap::iterator aI = g_aModuleMap.find(sModule);
+ if (aI == g_aModuleMap.end())
+ {
+ std::shared_ptr<NoAutoUnloadModule> pModule;
+#if ENABLE_MERGELIBS
+ if (!g_pMergedLib->is())
+ g_pMergedLib->loadRelative(&thisModule, SVLIBRARY("merged"));
+ if ((pFunction = reinterpret_cast<VclBuilder::customMakeWidget>(
+ g_pMergedLib->getFunctionSymbol(sFunction))))
+ pModule = g_pMergedLib;
+#endif
+ if (!pFunction)
+ {
+ pModule = std::make_shared<NoAutoUnloadModule>();
+ bool ok = pModule->loadRelative(&thisModule, sModule);
+ if (!ok)
+ {
+#ifdef LINUX
+ // in the case of preloading, we don't have eg. the
+ // libcuilo.so, but still need to dlsym the symbols -
+ // which are already in-process
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ pFunction = reinterpret_cast<VclBuilder::customMakeWidget>(dlsym(RTLD_DEFAULT, aFunction.getStr()));
+ ok = !!pFunction;
+ assert(ok && "couldn't even directly dlsym the sFunction (available via preload)");
+ }
+#endif
+ assert(ok && "bad module name in .ui");
+ }
+ else
+ {
+ pFunction = reinterpret_cast<VclBuilder::customMakeWidget>(
+ pModule->getFunctionSymbol(sFunction));
+ }
+ }
+ g_aModuleMap.insert(std::make_pair(sModule, pModule));
+ }
+ else
+ pFunction = reinterpret_cast<VclBuilder::customMakeWidget>(
+ aI->second->getFunctionSymbol(sFunction));
+#elif !HAVE_FEATURE_DESKTOP
+ pFunction = lo_get_custom_widget_func(sFunction.toUtf8().getStr());
+ SAL_WARN_IF(!pFunction, "vcl.builder", "Could not find " << sFunction);
+ assert(pFunction);
+#else
+ pFunction = reinterpret_cast<VclBuilder::customMakeWidget>(
+ osl_getFunctionSymbol((oslModule)RTLD_DEFAULT, sFunction.pData));
+#endif
+ }
+ return pFunction;
+}
+}
+
+VclPtr<vcl::Window> VclBuilder::makeObject(vcl::Window *pParent, const OString &name, const OString &id,
+ stringmap &rMap)
+{
+ bool bIsPlaceHolder = name.isEmpty();
+ bool bVertical = false;
+
+ if (pParent && (pParent->GetType() == WindowType::TABCONTROL ||
+ pParent->GetType() == WindowType::VERTICALTABCONTROL))
+ {
+ bool bTopLevel(name == "GtkDialog" || name == "GtkMessageDialog" ||
+ name == "GtkWindow" || name == "GtkPopover" || name == "GtkAssistant");
+ if (!bTopLevel)
+ {
+ if (pParent->GetType() == WindowType::TABCONTROL)
+ {
+ //We have to add a page
+ //make default pageid == position
+ TabControl *pTabControl = static_cast<TabControl*>(pParent);
+ sal_uInt16 nNewPageCount = pTabControl->GetPageCount()+1;
+ sal_uInt16 nNewPageId = nNewPageCount;
+ pTabControl->InsertPage(nNewPageId, OUString());
+ pTabControl->SetCurPageId(nNewPageId);
+ SAL_WARN_IF(bIsPlaceHolder, "vcl.builder", "we should have no placeholders for tabpages");
+ if (!bIsPlaceHolder)
+ {
+ VclPtrInstance<TabPage> pPage(pTabControl);
+ pPage->Show();
+
+ //Make up a name for it
+ OString sTabPageId = get_by_window(pParent) +
+ "-page" +
+ OString::number(nNewPageCount);
+ m_aChildren.emplace_back(sTabPageId, pPage, false);
+ pPage->SetHelpId(m_sHelpRoot + sTabPageId);
+
+ pParent = pPage;
+
+ pTabControl->SetTabPage(nNewPageId, pPage);
+ }
+ }
+ else
+ {
+ VerticalTabControl *pTabControl = static_cast<VerticalTabControl*>(pParent);
+ SAL_WARN_IF(bIsPlaceHolder, "vcl.builder", "we should have no placeholders for tabpages");
+ if (!bIsPlaceHolder)
+ pParent = pTabControl->GetPageParent();
+ }
+ }
+ }
+
+ if (bIsPlaceHolder || name == "GtkTreeSelection")
+ return nullptr;
+
+ ToolBox *pToolBox = (pParent && pParent->GetType() == WindowType::TOOLBOX) ? static_cast<ToolBox*>(pParent) : nullptr;
+
+ extractButtonImage(id, rMap, name == "GtkRadioButton");
+
+ VclPtr<vcl::Window> xWindow;
+ if (name == "GtkDialog" || name == "GtkAssistant")
+ {
+ // WB_ALLOWMENUBAR because we don't know in advance if we will encounter
+ // a menubar, and menubars need a BorderWindow in the toplevel, and
+ // such border windows need to be in created during the dialog ctor
+ WinBits nBits = WB_MOVEABLE|WB_3DLOOK|WB_ALLOWMENUBAR;
+ if (extractResizable(rMap))
+ nBits |= WB_SIZEABLE;
+ if (extractCloseable(rMap))
+ nBits |= WB_CLOSEABLE;
+ Dialog::InitFlag eInit = !pParent ? Dialog::InitFlag::NoParent : Dialog::InitFlag::Default;
+ if (name == "GtkAssistant")
+ xWindow = VclPtr<vcl::RoadmapWizard>::Create(pParent, nBits, eInit);
+ else
+ xWindow = VclPtr<Dialog>::Create(pParent, nBits, eInit);
+#if HAVE_FEATURE_DESKTOP
+ if (!m_bLegacy && !extractModal(rMap))
+ xWindow->SetType(WindowType::MODELESSDIALOG);
+#endif
+ }
+ else if (name == "GtkMessageDialog")
+ {
+ WinBits nBits = WB_MOVEABLE|WB_3DLOOK|WB_CLOSEABLE;
+ if (extractResizable(rMap))
+ nBits |= WB_SIZEABLE;
+ VclPtr<MessageDialog> xDialog(VclPtr<MessageDialog>::Create(pParent, nBits));
+ m_pParserState->m_aMessageDialogs.push_back(xDialog);
+ xWindow = xDialog;
+#if defined _WIN32
+ xWindow->set_border_width(3);
+#else
+ xWindow->set_border_width(12);
+#endif
+ }
+ else if (name == "GtkBox" || name == "GtkStatusbar")
+ {
+ bVertical = extractOrientation(rMap);
+ if (bVertical)
+ xWindow = VclPtr<VclVBox>::Create(pParent);
+ else
+ xWindow = VclPtr<VclHBox>::Create(pParent);
+ }
+ else if (name == "GtkPaned")
+ {
+ bVertical = extractOrientation(rMap);
+ if (bVertical)
+ xWindow = VclPtr<VclVPaned>::Create(pParent);
+ else
+ xWindow = VclPtr<VclHPaned>::Create(pParent);
+ }
+ else if (name == "GtkHBox")
+ xWindow = VclPtr<VclHBox>::Create(pParent);
+ else if (name == "GtkVBox")
+ xWindow = VclPtr<VclVBox>::Create(pParent);
+ else if (name == "GtkButtonBox")
+ {
+ bVertical = extractOrientation(rMap);
+ if (bVertical)
+ xWindow = VclPtr<VclVButtonBox>::Create(pParent);
+ else
+ xWindow = VclPtr<VclHButtonBox>::Create(pParent);
+ }
+ else if (name == "GtkHButtonBox")
+ xWindow = VclPtr<VclHButtonBox>::Create(pParent);
+ else if (name == "GtkVButtonBox")
+ xWindow = VclPtr<VclVButtonBox>::Create(pParent);
+ else if (name == "GtkGrid")
+ xWindow = VclPtr<VclGrid>::Create(pParent);
+ else if (name == "GtkFrame")
+ xWindow = VclPtr<VclFrame>::Create(pParent);
+ else if (name == "GtkExpander")
+ {
+ VclPtrInstance<VclExpander> pExpander(pParent);
+ m_pParserState->m_aExpanderWidgets.push_back(pExpander);
+ xWindow = pExpander;
+ }
+ else if (name == "GtkAlignment")
+ xWindow = VclPtr<VclAlignment>::Create(pParent);
+ 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", m_bLegacy);
+ 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 = extractStockAndBuildMenuButton(pParent, rMap);
+ OUString sMenu = extractPopupMenu(rMap);
+ if (!sMenu.isEmpty())
+ m_pParserState->m_aButtonMenuMaps.emplace_back(id, sMenu);
+ xButton->SetImageAlign(ImageAlign::Left); //default to left
+ xButton->SetAccessibleRole(css::accessibility::AccessibleRole::BUTTON_MENU);
+
+ 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_CENTER|WB_VCENTER|WB_3DLOOK;
+ OUString sWrap = BuilderUtils::extractCustomProperty(rMap);
+ if (!sWrap.isEmpty())
+ nBits |= WB_WORDBREAK;
+ VclPtr<RadioButton> xButton = VclPtr<RadioButton>::Create(pParent, nBits);
+ xButton->SetImageAlign(ImageAlign::Left); //default to left
+ xWindow = xButton;
+
+ if (::extractStock(rMap))
+ {
+ xWindow->SetText(getStockText(extractLabel(rMap)));
+ }
+ }
+ else if (name == "GtkCheckButton")
+ {
+ WinBits nBits = WB_CLIPCHILDREN|WB_CENTER|WB_VCENTER|WB_3DLOOK;
+ OUString sWrap = BuilderUtils::extractCustomProperty(rMap);
+ if (!sWrap.isEmpty())
+ nBits |= WB_WORDBREAK;
+ 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;
+
+ if (::extractStock(rMap))
+ {
+ xWindow->SetText(getStockText(extractLabel(rMap)));
+ }
+ }
+ else if (name == "GtkSpinButton")
+ {
+ OUString sAdjustment = extractAdjustment(rMap);
+ OUString sPattern = BuilderUtils::extractCustomProperty(rMap);
+ OUString sUnit = extractUnit(sPattern);
+
+ WinBits nBits = WB_CLIPCHILDREN|WB_LEFT|WB_BORDER|WB_3DLOOK|WB_SPIN|WB_REPEAT;
+
+ if (sPattern.isEmpty())
+ {
+ SAL_INFO("vcl.builder", "making numeric field for " << name << " " << sUnit);
+ if (m_bLegacy)
+ {
+ connectNumericFormatterAdjustment(id, sAdjustment);
+ xWindow = VclPtr<NumericField>::Create(pParent, nBits);
+ }
+ else
+ {
+ connectFormattedFormatterAdjustment(id, sAdjustment);
+ VclPtrInstance<FormattedField> xField(pParent, nBits);
+ xField->SetMinValue(0);
+ xWindow = xField;
+ }
+ }
+ else
+ {
+ if (sPattern == "hh:mm")
+ {
+ connectTimeFormatterAdjustment(id, sAdjustment);
+ SAL_INFO("vcl.builder", "making time field for " << name << " " << sUnit);
+ xWindow = VclPtr<TimeField>::Create(pParent, nBits);
+ }
+ else if (sPattern == "yy:mm:dd")
+ {
+ connectDateFormatterAdjustment(id, sAdjustment);
+ SAL_INFO("vcl.builder", "making date field for " << name << " " << sUnit);
+ xWindow = VclPtr<DateField>::Create(pParent, nBits);
+ }
+ else
+ {
+ connectNumericFormatterAdjustment(id, sAdjustment);
+ FieldUnit eUnit = detectMetricUnit(sUnit);
+ SAL_INFO("vcl.builder", "making metric field for " << name << " " << sUnit);
+ VclPtrInstance<MetricField> xField(pParent, nBits);
+ xField->SetUnit(eUnit);
+ if (eUnit == FieldUnit::CUSTOM)
+ xField->SetCustomUnitText(sUnit);
+ 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")
+ {
+ OUString sPattern = BuilderUtils::extractCustomProperty(rMap);
+ extractModel(id, rMap);
+
+ WinBits nBits = WB_CLIPCHILDREN|WB_LEFT|WB_VCENTER|WB_3DLOOK;
+
+ bool bDropdown = BuilderUtils::extractDropdown(rMap);
+
+ if (bDropdown)
+ nBits |= WB_DROPDOWN;
+
+ if (!sPattern.isEmpty())
+ {
+ OUString sAdjustment = extractAdjustment(rMap);
+ connectNumericFormatterAdjustment(id, sAdjustment);
+ OUString sUnit = extractUnit(sPattern);
+ FieldUnit eUnit = detectMetricUnit(sUnit);
+ SAL_WARN("vcl.builder", "making metric box for type: " << name
+ << " unit: " << sUnit
+ << " name: " << id
+ << " use a GtkSpinButton instead");
+ VclPtrInstance<MetricBox> xBox(pParent, nBits);
+ xBox->EnableAutoSize(true);
+ xBox->SetUnit(eUnit);
+ xBox->SetDecimalDigits(extractDecimalDigits(sPattern));
+ if (eUnit == FieldUnit::CUSTOM)
+ xBox->SetCustomUnitText(sUnit);
+ xWindow = xBox;
+ }
+ else 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 == "GtkIconView")
+ {
+ assert(rMap.find(OString("model")) != rMap.end() && "GtkIconView must have a model");
+
+ //window we want to apply the packing props for this GtkIconView to
+ VclPtr<vcl::Window> xWindowForPackingProps;
+ extractModel(id, rMap);
+ WinBits nWinStyle = WB_CLIPCHILDREN|WB_LEFT|WB_VCENTER|WB_3DLOOK;
+ //IconView manages its own scrolling,
+ vcl::Window *pRealParent = prepareWidgetOwnScrolling(pParent, nWinStyle);
+ if (pRealParent != pParent)
+ nWinStyle |= WB_BORDER;
+
+ VclPtr<IconView> xBox = VclPtr<IconView>::Create(pRealParent, nWinStyle);
+ xWindowForPackingProps = xBox;
+
+ xWindow = xBox;
+ xBox->SetNoAutoCurEntry(true);
+ xBox->SetQuickSearch(true);
+
+ if (pRealParent != pParent)
+ cleanupWidgetOwnScrolling(pParent, xWindowForPackingProps, rMap);
+ }
+ else if (name == "GtkTreeView")
+ {
+ if (!m_bLegacy)
+ {
+ assert(rMap.find(OString("model")) != rMap.end() && "GtkTreeView must have a model");
+ }
+
+ //window we want to apply the packing props for this GtkTreeView to
+ VclPtr<vcl::Window> xWindowForPackingProps;
+ //To-Do
+ //a) make SvHeaderTabListBox/SvTabListBox the default target for GtkTreeView
+ //b) remove the non-drop down mode of ListBox and convert
+ // everything over to SvHeaderTabListBox/SvTabListBox
+ //c) remove the users of makeSvTabListBox and makeSvTreeListBox
+ 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 (pRealParent != pParent)
+ nWinStyle |= WB_BORDER;
+ if (m_bLegacy)
+ {
+ xWindow = VclPtr<ListBox>::Create(pRealParent, nWinStyle | WB_SIMPLEMODE);
+ xWindowForPackingProps = xWindow;
+ }
+ else
+ {
+ VclPtr<SvTabListBox> xBox;
+ bool bHeadersVisible = extractHeadersVisible(rMap);
+ if (bHeadersVisible)
+ {
+ VclPtr<VclVBox> xContainer = VclPtr<VclVBox>::Create(pRealParent);
+ OString containerid(id + "-container");
+ xContainer->SetHelpId(m_sHelpRoot + containerid);
+ m_aChildren.emplace_back(containerid, xContainer, true);
+
+ VclPtrInstance<HeaderBar> xHeader(xContainer, WB_BUTTONSTYLE | WB_BORDER | WB_TABSTOP | WB_3DLOOK);
+ xHeader->set_width_request(0); // let the headerbar width not affect the size request
+ OString headerid(id + "-header");
+ xHeader->SetHelpId(m_sHelpRoot + headerid);
+ m_aChildren.emplace_back(headerid, xHeader, true);
+
+ VclPtr<LclHeaderTabListBox> xHeaderBox = VclPtr<LclHeaderTabListBox>::Create(xContainer, nWinStyle);
+ xHeaderBox->InitHeaderBar(xHeader);
+ xContainer->set_expand(true);
+ xHeader->Show();
+ xContainer->Show();
+ xBox = xHeaderBox;
+ xWindowForPackingProps = xContainer;
+ }
+ else
+ {
+ xBox = VclPtr<LclTabListBox>::Create(pRealParent, nWinStyle);
+ xWindowForPackingProps = xBox;
+ }
+ xWindow = xBox;
+ xBox->SetNoAutoCurEntry(true);
+ xBox->SetQuickSearch(true);
+ xBox->SetSpaceBetweenEntries(3);
+ xBox->SetEntryHeight(16);
+ xBox->SetHighlightRange(); // select over the whole width
+ }
+ if (pRealParent != pParent)
+ cleanupWidgetOwnScrolling(pParent, xWindowForPackingProps, rMap);
+ }
+ else if (name == "GtkTreeViewColumn")
+ {
+ if (!m_bLegacy)
+ {
+ SvHeaderTabListBox* pTreeView = dynamic_cast<SvHeaderTabListBox*>(pParent);
+ if (HeaderBar* pHeaderBar = pTreeView ? pTreeView->GetHeaderBar() : nullptr)
+ {
+ HeaderBarItemBits nBits = HeaderBarItemBits::LEFTIMAGE;
+ if (extractClickable(rMap))
+ nBits |= HeaderBarItemBits::CLICKABLE;
+ if (extractSortIndicator(rMap))
+ nBits |= HeaderBarItemBits::DOWNARROW;
+ float fAlign = extractAlignment(rMap);
+ if (fAlign == 0.0)
+ nBits |= HeaderBarItemBits::LEFT;
+ else if (fAlign == 1.0)
+ nBits |= HeaderBarItemBits::RIGHT;
+ else if (fAlign == 0.5)
+ nBits |= HeaderBarItemBits::CENTER;
+ auto nItemId = pHeaderBar->GetItemCount() + 1;
+ OUString sTitle(extractTitle(rMap));
+ pHeaderBar->InsertItem(nItemId, sTitle, 100, nBits);
+ }
+ }
+ }
+ else if (name == "GtkLabel")
+ {
+ WinBits nWinStyle = WB_CENTER|WB_VCENTER|WB_3DLOOK;
+ OUString sBorder = BuilderUtils::extractCustomProperty(rMap);
+ if (!sBorder.isEmpty())
+ nWinStyle |= WB_BORDER;
+ extractMnemonicWidget(id, rMap);
+ if (extractSelectable(rMap))
+ xWindow = VclPtr<SelectableFixedText>::Create(pParent, nWinStyle);
+ else
+ xWindow = VclPtr<FixedText>::Create(pParent, nWinStyle);
+ }
+ else if (name == "GtkImage")
+ {
+ extractStock(id, rMap);
+ xWindow = VclPtr<FixedImage>::Create(pParent, WB_CENTER|WB_VCENTER|WB_3DLOOK|WB_SCALE);
+ //such parentless GtkImages are temps used to set icons on buttons
+ //default them to hidden to stop e.g. insert->index entry flicking temp
+ //full screen windows
+ if (!pParent)
+ {
+ rMap["visible"] = "false";
+ }
+ }
+ else if (name == "GtkSeparator")
+ {
+ bVertical = extractOrientation(rMap);
+ xWindow = VclPtr<FixedLine>::Create(pParent, bVertical ? WB_VERT : WB_HORZ);
+ }
+ else if (name == "GtkScrollbar")
+ {
+ extractAdjustmentToMap(id, rMap, m_pParserState->m_aScrollAdjustmentMaps);
+ bVertical = extractOrientation(rMap);
+ xWindow = VclPtr<ScrollBar>::Create(pParent, bVertical ? WB_VERT : WB_HORZ);
+ }
+ else if (name == "GtkProgressBar")
+ {
+ extractAdjustmentToMap(id, rMap, m_pParserState->m_aScrollAdjustmentMaps);
+ bVertical = extractOrientation(rMap);
+ xWindow = VclPtr<ProgressBar>::Create(pParent, bVertical ? WB_VERT : WB_HORZ);
+ }
+ else if (name == "GtkScrolledWindow")
+ {
+ xWindow = VclPtr<VclScrolledWindow>::Create(pParent);
+ }
+ else if (name == "GtkViewport")
+ {
+ xWindow = VclPtr<VclViewport>::Create(pParent);
+ }
+ else if (name == "GtkEventBox")
+ {
+ xWindow = VclPtr<VclEventBox>::Create(pParent);
+ }
+ else if (name == "GtkEntry")
+ {
+ xWindow = VclPtr<Edit>::Create(pParent, WB_LEFT|WB_VCENTER|WB_BORDER|WB_3DLOOK);
+ 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")
+ {
+ OUString sBorder = BuilderUtils::extractCustomProperty(rMap);
+ xWindow = VclPtr<VclDrawingArea>::Create(pParent, sBorder.isEmpty() ? WB_TABSTOP : WB_BORDER | 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);
+ if (pRealParent != pParent)
+ nWinStyle |= WB_BORDER;
+ 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));
+
+ sal_uInt16 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 = pToolBox->GetItemCount() + 1;
+ //TODO: ImplToolItems::size_type -> sal_uInt16!
+ pToolBox->InsertItem(nItemId, extractLabel(rMap), nBits);
+ if (aCommand.isEmpty() && !m_bLegacy)
+ aCommand = OUString::fromUtf8(id);
+ pToolBox->SetItemCommand(nItemId, aCommand);
+ }
+
+ pToolBox->SetHelpId(nItemId, m_sHelpRoot + id);
+ OUString sTooltip(extractTooltipText(rMap));
+ if (!sTooltip.isEmpty())
+ pToolBox->SetQuickHelpText(nItemId, sTooltip);
+
+ OUString sIconName(extractIconName(rMap));
+ if (sIconName.isEmpty())
+ sIconName = mapStockToImageResource(extractStockId(rMap));
+ if (!sIconName.isEmpty())
+ pToolBox->SetItemImage(nItemId, FixedImage::loadThemeImage(sIconName));
+
+ if (!extractVisible(rMap))
+ pToolBox->HideItem(nItemId);
+
+ m_pParserState->m_nLastToolbarId = nItemId;
+
+ return nullptr; // no widget to be created
+ }
+ }
+ else if (name == "GtkSeparatorToolItem")
+ {
+ if (pToolBox)
+ {
+ pToolBox->InsertSeparator();
+ return nullptr; // no widget to be created
+ }
+ }
+ else if (name == "GtkWindow")
+ {
+ WinBits nBits = extractDeferredBits(rMap);
+ if (nBits & WB_DOCKABLE)
+ xWindow = VclPtr<DockingWindow>::Create(pParent, nBits|WB_MOVEABLE);
+ else
+ xWindow = VclPtr<FloatingWindow>::Create(pParent, nBits|WB_MOVEABLE);
+ }
+ else if (name == "GtkPopover")
+ {
+ WinBits nBits = extractDeferredBits(rMap);
+ xWindow = VclPtr<DockingWindow>::Create(pParent, nBits|WB_DOCKABLE|WB_MOVEABLE);
+ }
+ else if (name == "GtkCalendar")
+ {
+ WinBits nBits = extractDeferredBits(rMap);
+ xWindow = VclPtr<Calendar>::Create(pParent, nBits);
+ }
+ else
+ {
+ if (customMakeWidget pFunction = GetCustomMakeWidget(name))
+ {
+ pFunction(xWindow, pParent, rMap);
+ if (xWindow->GetType() == WindowType::PUSHBUTTON)
+ setupFromActionName(static_cast<Button*>(xWindow.get()), rMap, m_xFrame);
+ else if (xWindow->GetType() == WindowType::MENUBUTTON)
+ {
+ OUString sMenu = BuilderUtils::extractCustomProperty(rMap);
+ if (!sMenu.isEmpty())
+ m_pParserState->m_aButtonMenuMaps.emplace_back(id, sMenu);
+ setupFromActionName(static_cast<Button*>(xWindow.get()), rMap, m_xFrame);
+ }
+ }
+ }
+
+ SAL_INFO_IF(!xWindow, "vcl.builder", "probably need to implement " << name << " or add a make" << name << " function");
+ if (xWindow)
+ {
+ // child windows of disabled windows are made disabled by vcl by default, we don't want that
+ WindowImpl *pWindowImpl = xWindow->ImplGetWindowImpl();
+ pWindowImpl->mbDisabled = false;
+
+ xWindow->SetHelpId(m_sHelpRoot + id);
+ SAL_INFO("vcl.builder", "for name '" << name << "' and id '" << id <<
+ "', created " << xWindow.get() << " child of " <<
+ pParent << "(" << xWindow->ImplGetWindowImpl()->mpParent.get() << "/" <<
+ xWindow->ImplGetWindowImpl()->mpRealParent.get() << "/" <<
+ xWindow->ImplGetWindowImpl()->mpBorderWindow.get() << ") with helpid " <<
+ xWindow->GetHelpId());
+ m_aChildren.emplace_back(id, xWindow, bVertical);
+
+ // if the parent was a toolbox set it as an itemwindow for the latest itemid
+ if (pToolBox)
+ {
+ Size aSize(xWindow->GetSizePixel());
+ aSize.setHeight(xWindow->get_preferred_size().Height());
+ xWindow->SetSizePixel(aSize);
+ pToolBox->SetItemWindow(m_pParserState->m_nLastToolbarId, xWindow);
+ pToolBox->SetItemExpand(m_pParserState->m_nLastToolbarId, true);
+ }
+ }
+ return xWindow;
+}
+
+namespace
+{
+ //return true for window types which exist in vcl but are not themselves
+ //represented in the .ui format, i.e. only their children exist.
+ bool isConsideredGtkPseudo(vcl::Window const *pWindow)
+ {
+ return pWindow->GetType() == WindowType::TABPAGE;
+ }
+}
+
+//Any properties from .ui load we couldn't set because of potential virtual methods
+//during ctor are applied here
+void VclBuilder::setDeferredProperties()
+{
+ if (!m_bToplevelHasDeferredProperties)
+ return;
+ stringmap aDeferredProperties;
+ aDeferredProperties.swap(m_aDeferredProperties);
+ m_bToplevelHasDeferredProperties = false;
+ BuilderUtils::set_properties(m_pParent, aDeferredProperties);
+}
+
+namespace BuilderUtils
+{
+ void set_properties(vcl::Window *pWindow, const VclBuilder::stringmap &rProps)
+ {
+ for (auto const& prop : rProps)
+ {
+ const OString &rKey = prop.first;
+ const OUString &rValue = prop.second;
+ pWindow->set_property(rKey, rValue);
+ }
+ }
+
+ OUString convertMnemonicMarkup(const OUString &rIn)
+ {
+ OUStringBuffer aRet(rIn);
+ for (sal_Int32 nI = 0; nI < aRet.getLength(); ++nI)
+ {
+ if (aRet[nI] == '_' && nI+1 < aRet.getLength())
+ {
+ if (aRet[nI+1] != '_')
+ aRet[nI] = MNEMONIC_CHAR;
+ else
+ aRet.remove(nI, 1);
+ ++nI;
+ }
+ }
+ return aRet.makeStringAndClear();
+ }
+
+ OUString extractCustomProperty(VclBuilder::stringmap &rMap)
+ {
+ OUString sCustomProperty;
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("customproperty"));
+ if (aFind != rMap.end())
+ {
+ sCustomProperty = aFind->second;
+ rMap.erase(aFind);
+ }
+ return sCustomProperty;
+ }
+
+ void ensureDefaultWidthChars(VclBuilder::stringmap &rMap)
+ {
+ OString sWidthChars("width-chars");
+ VclBuilder::stringmap::iterator aFind = rMap.find(sWidthChars);
+ if (aFind == rMap.end())
+ rMap[sWidthChars] = "25";
+ }
+
+ bool extractDropdown(VclBuilder::stringmap &rMap)
+ {
+ bool bDropdown = true;
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("dropdown"));
+ if (aFind != rMap.end())
+ {
+ bDropdown = toBool(aFind->second);
+ rMap.erase(aFind);
+ }
+ return bDropdown;
+ }
+
+ void reorderWithinParent(vcl::Window &rWindow, sal_uInt16 nNewPosition)
+ {
+ WindowImpl *pWindowImpl = rWindow.ImplGetWindowImpl();
+ if (pWindowImpl->mpParent != pWindowImpl->mpRealParent)
+ {
+ assert(pWindowImpl->mpBorderWindow == pWindowImpl->mpParent);
+ assert(pWindowImpl->mpBorderWindow->ImplGetWindowImpl()->mpParent == pWindowImpl->mpRealParent);
+ reorderWithinParent(*pWindowImpl->mpBorderWindow, nNewPosition);
+ return;
+ }
+ rWindow.reorderWithinParent(nNewPosition);
+ }
+
+ void reorderWithinParent(std::vector<vcl::Window*>& rChilds, bool bIsButtonBox)
+ {
+ for (size_t i = 0; i < rChilds.size(); ++i)
+ {
+ reorderWithinParent(*rChilds[i], i);
+
+ if (!bIsButtonBox)
+ continue;
+
+ //The first member of the group for legacy code needs WB_GROUP set and the
+ //others not
+ WinBits nBits = rChilds[i]->GetStyle();
+ nBits &= ~WB_GROUP;
+ if (i == 0)
+ nBits |= WB_GROUP;
+ rChilds[i]->SetStyle(nBits);
+ }
+ }
+
+ sal_Int16 getRoleFromName(const OString& roleName)
+ {
+ using namespace com::sun::star::accessibility;
+
+ static const std::unordered_map<OString, sal_Int16> aAtkRoleToAccessibleRole = {
+ /* This is in atkobject.h's AtkRole order */
+ { "invalid", AccessibleRole::UNKNOWN },
+ { "accelerator label", AccessibleRole::UNKNOWN },
+ { "alert", AccessibleRole::ALERT },
+ { "animation", AccessibleRole::UNKNOWN },
+ { "arrow", AccessibleRole::UNKNOWN },
+ { "calendar", AccessibleRole::UNKNOWN },
+ { "canvas", AccessibleRole::CANVAS },
+ { "check box", AccessibleRole::CHECK_BOX },
+ { "check menu item", AccessibleRole::CHECK_MENU_ITEM },
+ { "color chooser", AccessibleRole::COLOR_CHOOSER },
+ { "column header", AccessibleRole::COLUMN_HEADER },
+ { "combo box", AccessibleRole::COMBO_BOX },
+ { "date editor", AccessibleRole::DATE_EDITOR },
+ { "desktop icon", AccessibleRole::DESKTOP_ICON },
+ { "desktop frame", AccessibleRole::DESKTOP_PANE }, // ?
+ { "dial", AccessibleRole::UNKNOWN },
+ { "dialog", AccessibleRole::DIALOG },
+ { "directory pane", AccessibleRole::DIRECTORY_PANE },
+ { "drawing area", AccessibleRole::UNKNOWN },
+ { "file chooser", AccessibleRole::FILE_CHOOSER },
+ { "filler", AccessibleRole::FILLER },
+ { "font chooser", AccessibleRole::FONT_CHOOSER },
+ { "frame", AccessibleRole::FRAME },
+ { "glass pane", AccessibleRole::GLASS_PANE },
+ { "html container", AccessibleRole::UNKNOWN },
+ { "icon", AccessibleRole::ICON },
+ { "image", AccessibleRole::GRAPHIC },
+ { "internal frame", AccessibleRole::INTERNAL_FRAME },
+ { "label", AccessibleRole::LABEL },
+ { "layered pane", AccessibleRole::LAYERED_PANE },
+ { "list", AccessibleRole::LIST },
+ { "list item", AccessibleRole::LIST_ITEM },
+ { "menu", AccessibleRole::MENU },
+ { "menu bar", AccessibleRole::MENU_BAR },
+ { "menu item", AccessibleRole::MENU_ITEM },
+ { "option pane", AccessibleRole::OPTION_PANE },
+ { "page tab", AccessibleRole::PAGE_TAB },
+ { "page tab list", AccessibleRole::PAGE_TAB_LIST },
+ { "panel", AccessibleRole::PANEL }, // or SHAPE or TEXT_FRAME ?
+ { "password text", AccessibleRole::PASSWORD_TEXT },
+ { "popup menu", AccessibleRole::POPUP_MENU },
+ { "progress bar", AccessibleRole::PROGRESS_BAR },
+ { "push button", AccessibleRole::PUSH_BUTTON }, // or BUTTON_DROPDOWN or BUTTON_MENU
+ { "radio button", AccessibleRole::RADIO_BUTTON },
+ { "radio menu item", AccessibleRole::RADIO_MENU_ITEM },
+ { "root pane", AccessibleRole::ROOT_PANE },
+ { "row header", AccessibleRole::ROW_HEADER },
+ { "scroll bar", AccessibleRole::SCROLL_BAR },
+ { "scroll pane", AccessibleRole::SCROLL_PANE },
+ { "separator", AccessibleRole::SEPARATOR },
+ { "slider", AccessibleRole::SLIDER },
+ { "split pane", AccessibleRole::SPLIT_PANE },
+ { "spin button", AccessibleRole::SPIN_BOX }, // ?
+ { "statusbar", AccessibleRole::STATUS_BAR },
+ { "table", AccessibleRole::TABLE },
+ { "table cell", AccessibleRole::TABLE_CELL },
+ { "table column header", AccessibleRole::COLUMN_HEADER }, // approximate
+ { "table row header", AccessibleRole::ROW_HEADER }, // approximate
+ { "tear off menu item", AccessibleRole::UNKNOWN },
+ { "terminal", AccessibleRole::UNKNOWN },
+ { "text", AccessibleRole::TEXT },
+ { "toggle button", AccessibleRole::TOGGLE_BUTTON },
+ { "tool bar", AccessibleRole::TOOL_BAR },
+ { "tool tip", AccessibleRole::TOOL_TIP },
+ { "tree", AccessibleRole::TREE },
+ { "tree table", AccessibleRole::TREE_TABLE },
+ { "unknown", AccessibleRole::UNKNOWN },
+ { "viewport", AccessibleRole::VIEW_PORT },
+ { "window", AccessibleRole::WINDOW },
+ { "header", AccessibleRole::HEADER },
+ { "footer", AccessibleRole::FOOTER },
+ { "paragraph", AccessibleRole::PARAGRAPH },
+ { "ruler", AccessibleRole::RULER },
+ { "application", AccessibleRole::UNKNOWN },
+ { "autocomplete", AccessibleRole::UNKNOWN },
+ { "edit bar", AccessibleRole::EDIT_BAR },
+ { "embedded", AccessibleRole::EMBEDDED_OBJECT },
+ { "entry", AccessibleRole::UNKNOWN },
+ { "chart", AccessibleRole::CHART },
+ { "caption", AccessibleRole::CAPTION },
+ { "document frame", AccessibleRole::DOCUMENT },
+ { "heading", AccessibleRole::HEADING },
+ { "page", AccessibleRole::PAGE },
+ { "section", AccessibleRole::SECTION },
+ { "redundant object", AccessibleRole::UNKNOWN },
+ { "form", AccessibleRole::FORM },
+ { "link", AccessibleRole::HYPER_LINK },
+ { "input method window", AccessibleRole::UNKNOWN },
+ { "table row", AccessibleRole::UNKNOWN },
+ { "tree item", AccessibleRole::TREE_ITEM },
+ { "document spreadsheet", AccessibleRole::DOCUMENT_SPREADSHEET },
+ { "document presentation", AccessibleRole::DOCUMENT_PRESENTATION },
+ { "document text", AccessibleRole::DOCUMENT_TEXT },
+ { "document web", AccessibleRole::DOCUMENT }, // approximate
+ { "document email", AccessibleRole::DOCUMENT }, // approximate
+ { "comment", AccessibleRole::COMMENT }, // or NOTE or END_NOTE or FOOTNOTE or SCROLL_PANE
+ { "list box", AccessibleRole::UNKNOWN },
+ { "grouping", AccessibleRole::GROUP_BOX },
+ { "image map", AccessibleRole::IMAGE_MAP },
+ { "notification", AccessibleRole::UNKNOWN },
+ { "info bar", AccessibleRole::UNKNOWN },
+ { "level bar", AccessibleRole::UNKNOWN },
+ { "title bar", AccessibleRole::UNKNOWN },
+ { "block quote", AccessibleRole::UNKNOWN },
+ { "audio", AccessibleRole::UNKNOWN },
+ { "video", AccessibleRole::UNKNOWN },
+ { "definition", AccessibleRole::UNKNOWN },
+ { "article", AccessibleRole::UNKNOWN },
+ { "landmark", AccessibleRole::UNKNOWN },
+ { "log", AccessibleRole::UNKNOWN },
+ { "marquee", AccessibleRole::UNKNOWN },
+ { "math", AccessibleRole::UNKNOWN },
+ { "rating", AccessibleRole::UNKNOWN },
+ { "timer", AccessibleRole::UNKNOWN },
+ { "description list", AccessibleRole::UNKNOWN },
+ { "description term", AccessibleRole::UNKNOWN },
+ { "description value", AccessibleRole::UNKNOWN },
+ { "static", AccessibleRole::STATIC },
+ { "math fraction", AccessibleRole::UNKNOWN },
+ { "math root", AccessibleRole::UNKNOWN },
+ { "subscript", AccessibleRole::UNKNOWN },
+ { "superscript", AccessibleRole::UNKNOWN },
+ { "footnote", AccessibleRole::FOOTNOTE },
+ };
+
+ auto it = aAtkRoleToAccessibleRole.find(roleName);
+ if (it == aAtkRoleToAccessibleRole.end())
+ return AccessibleRole::UNKNOWN;
+ return it->second;
+ }
+}
+
+VclPtr<vcl::Window> VclBuilder::insertObject(vcl::Window *pParent, const OString &rClass,
+ const OString &rID, stringmap &rProps, stringmap &rPango, stringmap &rAtk)
+{
+ VclPtr<vcl::Window> pCurrentChild;
+
+ if (m_pParent && !isConsideredGtkPseudo(m_pParent) && !m_sID.isEmpty() && rID == m_sID)
+ {
+ pCurrentChild = m_pParent;
+
+ //toplevels default to resizable and apparently you can't change them
+ //afterwards, so we need to wait until now before we can truly
+ //initialize the dialog.
+ if (pParent && pParent->IsSystemWindow())
+ {
+ SystemWindow *pSysWin = static_cast<SystemWindow*>(pCurrentChild.get());
+ pSysWin->doDeferredInit(extractDeferredBits(rProps));
+ m_bToplevelHasDeferredInit = false;
+ }
+ else if (pParent && pParent->IsDockingWindow())
+ {
+ DockingWindow *pDockWin = static_cast<DockingWindow*>(pCurrentChild.get());
+ pDockWin->doDeferredInit(extractDeferredBits(rProps));
+ m_bToplevelHasDeferredInit = false;
+ }
+
+ if (pCurrentChild->GetHelpId().isEmpty())
+ {
+ pCurrentChild->SetHelpId(m_sHelpRoot + m_sID);
+ SAL_INFO("vcl.builder", "for toplevel dialog " << this << " " <<
+ rID << ", set helpid " << pCurrentChild->GetHelpId());
+ }
+ m_bToplevelParentFound = true;
+ }
+ else
+ {
+ //if we're being inserting under a toplevel dialog whose init is
+ //deferred due to waiting to encounter it in this .ui, and it hasn't
+ //been seen yet, then make unattached widgets parent-less toplevels
+ if (pParent == m_pParent.get() && m_bToplevelHasDeferredInit)
+ pParent = nullptr;
+ pCurrentChild = makeObject(pParent, rClass, rID, rProps);
+ }
+
+ if (pCurrentChild)
+ {
+ pCurrentChild->set_id(OStringToOUString(rID, RTL_TEXTENCODING_UTF8));
+ if (pCurrentChild == m_pParent.get() && m_bToplevelHasDeferredProperties)
+ m_aDeferredProperties = rProps;
+ else
+ BuilderUtils::set_properties(pCurrentChild, rProps);
+
+ for (auto const& elem : rPango)
+ {
+ const OString &rKey = elem.first;
+ const OUString &rValue = elem.second;
+ pCurrentChild->set_font_attribute(rKey, rValue);
+ }
+
+ m_pParserState->m_aAtkInfo[pCurrentChild] = rAtk;
+ }
+
+ rProps.clear();
+ rPango.clear();
+ rAtk.clear();
+
+ if (!pCurrentChild)
+ {
+ bool bToolbarParent = (pParent && pParent->GetType() == WindowType::TOOLBOX);
+ pCurrentChild = (m_aChildren.empty() || bToolbarParent) ? pParent : m_aChildren.back().m_pWindow.get();
+ }
+ return pCurrentChild;
+}
+
+void VclBuilder::handleTabChild(vcl::Window *pParent, xmlreader::XmlReader &reader)
+{
+ std::vector<OString> sIDs;
+
+ int nLevel = 1;
+ stringmap aProperties;
+ std::vector<vcl::EnumContext::Context> context;
+
+ while(true)
+ {
+ xmlreader::Span name;
+ int nsId;
+
+ xmlreader::XmlReader::Result res = reader.nextItem(
+ xmlreader::XmlReader::Text::NONE, &name, &nsId);
+
+ if (res == xmlreader::XmlReader::Result::Begin)
+ {
+ ++nLevel;
+ if (name == "object")
+ {
+ while (reader.nextAttribute(&nsId, &name))
+ {
+ if (name == "id")
+ {
+ name = reader.getAttributeValue(false);
+ OString sID(name.begin, name.length);
+ sal_Int32 nDelim = sID.indexOf(':');
+ if (nDelim != -1)
+ {
+ OString sPattern = sID.copy(nDelim+1);
+ aProperties[OString("customproperty")] = OUString::fromUtf8(sPattern);
+ sID = sID.copy(0, nDelim);
+ }
+ sIDs.push_back(sID);
+ }
+ }
+ }
+ else if (name == "style")
+ {
+ int nPriority = 0;
+ context = handleStyle(reader, nPriority);
+ --nLevel;
+ }
+ else if (name == "property")
+ collectProperty(reader, aProperties);
+ }
+
+ if (res == xmlreader::XmlReader::Result::End)
+ --nLevel;
+
+ if (!nLevel)
+ break;
+
+ if (res == xmlreader::XmlReader::Result::Done)
+ break;
+ }
+
+ if (!pParent)
+ return;
+
+ TabControl *pTabControl = pParent->GetType() == WindowType::TABCONTROL ?
+ static_cast<TabControl*>(pParent) : nullptr;
+ VerticalTabControl *pVerticalTabControl = pParent->GetType() == WindowType::VERTICALTABCONTROL ?
+ static_cast<VerticalTabControl*>(pParent) : nullptr;
+ assert(pTabControl || pVerticalTabControl);
+ VclBuilder::stringmap::iterator aFind = aProperties.find(OString("label"));
+ if (aFind != aProperties.end())
+ {
+ if (pTabControl)
+ {
+ sal_uInt16 nPageId = pTabControl->GetCurPageId();
+ pTabControl->SetPageText(nPageId, aFind->second);
+ pTabControl->SetPageName(nPageId, sIDs.back());
+ if (!context.empty())
+ {
+ TabPage* pPage = pTabControl->GetTabPage(nPageId);
+ pPage->SetContext(context);
+ }
+ }
+ else
+ {
+ OUString sLabel(aFind->second);
+ OUString sIconName(extractIconName(aProperties));
+ OUString sTooltip(extractTooltipText(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, 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, 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.getStr());
+ }
+ }
+ ++nLevel;
+ }
+ else
+ ++nLevel;
+ }
+
+ if (res == xmlreader::XmlReader::Result::End)
+ --nLevel;
+
+ if (!nLevel)
+ break;
+
+ if (res == xmlreader::XmlReader::Result::Done)
+ break;
+ }
+}
+
+void VclBuilder::collectPangoAttribute(xmlreader::XmlReader &reader, stringmap &rMap)
+{
+ xmlreader::Span span;
+ int nsId;
+
+ OString sProperty;
+ OString sValue;
+
+ while (reader.nextAttribute(&nsId, &span))
+ {
+ if (span == "name")
+ {
+ span = reader.getAttributeValue(false);
+ sProperty = OString(span.begin, span.length);
+ }
+ else if (span == "value")
+ {
+ span = reader.getAttributeValue(false);
+ sValue = OString(span.begin, span.length);
+ }
+ }
+
+ if (!sProperty.isEmpty())
+ rMap[sProperty] = OUString::fromUtf8(sValue);
+}
+
+void VclBuilder::collectAtkRelationAttribute(xmlreader::XmlReader &reader, stringmap &rMap)
+{
+ xmlreader::Span span;
+ int nsId;
+
+ OString sProperty;
+ OString sValue;
+
+ while (reader.nextAttribute(&nsId, &span))
+ {
+ if (span == "type")
+ {
+ span = reader.getAttributeValue(false);
+ sProperty = OString(span.begin, span.length);
+ }
+ else if (span == "target")
+ {
+ span = reader.getAttributeValue(false);
+ sValue = OString(span.begin, span.length);
+ sal_Int32 nDelim = sValue.indexOf(':');
+ if (nDelim != -1)
+ sValue = sValue.copy(0, nDelim);
+ }
+ }
+
+ if (!sProperty.isEmpty())
+ rMap[sProperty] = OUString::fromUtf8(sValue);
+}
+
+void VclBuilder::collectAtkRoleAttribute(xmlreader::XmlReader &reader, stringmap &rMap)
+{
+ xmlreader::Span span;
+ int nsId;
+
+ OString sProperty;
+
+ while (reader.nextAttribute(&nsId, &span))
+ {
+ if (span == "type")
+ {
+ span = reader.getAttributeValue(false);
+ sProperty = OString(span.begin, span.length);
+ }
+ }
+
+ if (!sProperty.isEmpty())
+ rMap["role"] = OUString::fromUtf8(sProperty);
+}
+
+void VclBuilder::handleRow(xmlreader::XmlReader &reader, const OString &rID)
+{
+ int nLevel = 1;
+
+ ListStore::row aRow;
+
+ while(true)
+ {
+ xmlreader::Span name;
+ int nsId;
+
+ xmlreader::XmlReader::Result res = reader.nextItem(
+ xmlreader::XmlReader::Text::NONE, &name, &nsId);
+
+ if (res == xmlreader::XmlReader::Result::Done)
+ break;
+
+ if (res == xmlreader::XmlReader::Result::Begin)
+ {
+ ++nLevel;
+ if (name == "col")
+ {
+ bool bTranslated = false;
+ sal_uInt32 nId = 0;
+ OString sContext;
+
+ while (reader.nextAttribute(&nsId, &name))
+ {
+ if (name == "id")
+ {
+ name = reader.getAttributeValue(false);
+ nId = OString(name.begin, name.length).toInt32();
+ }
+ 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)
+ {
+ if (!sContext.isEmpty())
+ sValue = sContext + "\004" + sValue;
+ sFinalValue = Translate::get(sValue.getStr(), m_pParserState->m_aResLocale);
+ }
+ else
+ sFinalValue = OUString::fromUtf8(sValue);
+
+
+ if (aRow.size() < nId+1)
+ aRow.resize(nId+1);
+ aRow[nId] = sFinalValue;
+ }
+ }
+
+ if (res == xmlreader::XmlReader::Result::End)
+ {
+ --nLevel;
+ }
+
+ if (!nLevel)
+ break;
+ }
+
+ m_pParserState->m_aModels[rID].m_aEntries.push_back(aRow);
+}
+
+void VclBuilder::handleListStore(xmlreader::XmlReader &reader, const OString &rID, const OString &rClass)
+{
+ int nLevel = 1;
+
+ while(true)
+ {
+ xmlreader::Span name;
+ int nsId;
+
+ xmlreader::XmlReader::Result res = reader.nextItem(
+ xmlreader::XmlReader::Text::NONE, &name, &nsId);
+
+ if (res == xmlreader::XmlReader::Result::Done)
+ break;
+
+ if (res == xmlreader::XmlReader::Result::Begin)
+ {
+ if (name == "row")
+ {
+ bool bNotTreeStore = rClass != "GtkTreeStore";
+ if (bNotTreeStore)
+ handleRow(reader, rID);
+ assert(bNotTreeStore && "gtk, as the time of writing, doesn't support data in GtkTreeStore serialization");
+ }
+ else
+ ++nLevel;
+ }
+
+ if (res == xmlreader::XmlReader::Result::End)
+ {
+ --nLevel;
+ }
+
+ if (!nLevel)
+ break;
+ }
+}
+
+void VclBuilder::handleAtkObject(xmlreader::XmlReader &reader, vcl::Window *pWindow)
+{
+ assert(pWindow);
+
+ 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;
+ }
+
+ for (auto const& prop : aProperties)
+ {
+ const OString &rKey = prop.first;
+ const OUString &rValue = prop.second;
+
+ if (pWindow && rKey.match("AtkObject::"))
+ pWindow->set_property(rKey.copy(RTL_CONSTASCII_LENGTH("AtkObject::")), rValue);
+ else
+ SAL_WARN("vcl.builder", "unhandled atk prop: " << rKey);
+ }
+}
+
+std::vector<ComboBoxTextItem> VclBuilder::handleItems(xmlreader::XmlReader &reader) const
+{
+ int nLevel = 1;
+
+ std::vector<ComboBoxTextItem> aItems;
+
+ while(true)
+ {
+ xmlreader::Span name;
+ int nsId;
+
+ xmlreader::XmlReader::Result res = reader.nextItem(
+ xmlreader::XmlReader::Text::NONE, &name, &nsId);
+
+ if (res == xmlreader::XmlReader::Result::Done)
+ break;
+
+ if (res == xmlreader::XmlReader::Result::Begin)
+ {
+ ++nLevel;
+ if (name == "item")
+ {
+ bool bTranslated = false;
+ OString sContext, sId;
+
+ while (reader.nextAttribute(&nsId, &name))
+ {
+ if (name == "translatable" && reader.getAttributeValue(false) == "yes")
+ {
+ bTranslated = true;
+ }
+ else if (name == "context")
+ {
+ name = reader.getAttributeValue(false);
+ sContext = OString(name.begin, name.length);
+ }
+ else if (name == "id")
+ {
+ name = reader.getAttributeValue(false);
+ sId = OString(name.begin, name.length);
+ }
+ }
+
+ (void)reader.nextItem(
+ xmlreader::XmlReader::Text::Raw, &name, &nsId);
+
+ OString sValue(name.begin, name.length);
+ OUString sFinalValue;
+ if (bTranslated)
+ {
+ if (!sContext.isEmpty())
+ sValue = sContext + "\004" + sValue;
+ sFinalValue = Translate::get(sValue.getStr(), m_pParserState->m_aResLocale);
+ }
+ else
+ sFinalValue = OUString::fromUtf8(sValue);
+
+ if (m_pStringReplace)
+ sFinalValue = (*m_pStringReplace)(sFinalValue);
+
+ aItems.emplace_back(sFinalValue, sId);
+ }
+ }
+
+ if (res == xmlreader::XmlReader::Result::End)
+ {
+ --nLevel;
+ }
+
+ if (!nLevel)
+ break;
+ }
+
+ return aItems;
+}
+
+VclPtr<Menu> VclBuilder::handleMenu(xmlreader::XmlReader &reader, const OString &rID, bool bMenuBar)
+{
+ VclPtr<Menu> pCurrentMenu;
+ if (bMenuBar)
+ pCurrentMenu = VclPtr<MenuBar>::Create();
+ else
+ pCurrentMenu = VclPtr<PopupMenu>::Create();
+
+ int nLevel = 1;
+
+ stringmap aProperties;
+
+ while(true)
+ {
+ xmlreader::Span name;
+ int nsId;
+
+ xmlreader::XmlReader::Result res = reader.nextItem(
+ xmlreader::XmlReader::Text::NONE, &name, &nsId);
+
+ if (res == xmlreader::XmlReader::Result::Done)
+ break;
+
+ if (res == xmlreader::XmlReader::Result::Begin)
+ {
+ if (name == "child")
+ {
+ handleMenuChild(pCurrentMenu, reader);
+ }
+ else
+ {
+ ++nLevel;
+ if (name == "property")
+ collectProperty(reader, aProperties);
+ }
+ }
+
+ if (res == xmlreader::XmlReader::Result::End)
+ {
+ --nLevel;
+ }
+
+ if (!nLevel)
+ break;
+ }
+
+ m_aMenus.emplace_back(rID, pCurrentMenu);
+
+ return pCurrentMenu;
+}
+
+void VclBuilder::handleMenuChild(Menu *pParent, xmlreader::XmlReader &reader)
+{
+ xmlreader::Span name;
+ int nsId;
+
+ int nLevel = 1;
+ while(true)
+ {
+ xmlreader::XmlReader::Result res = reader.nextItem(
+ xmlreader::XmlReader::Text::NONE, &name, &nsId);
+
+ if (res == xmlreader::XmlReader::Result::Begin)
+ {
+ if (name == "object" || name == "placeholder")
+ {
+ handleMenuObject(pParent, reader);
+ }
+ else
+ ++nLevel;
+ }
+
+ if (res == xmlreader::XmlReader::Result::End)
+ --nLevel;
+
+ if (!nLevel)
+ break;
+
+ if (res == xmlreader::XmlReader::Result::Done)
+ break;
+ }
+}
+
+void VclBuilder::handleMenuObject(Menu *pParent, xmlreader::XmlReader &reader)
+{
+ OString sClass;
+ OString sID;
+ OUString sCustomProperty;
+ PopupMenu *pSubMenu = nullptr;
+
+ xmlreader::Span name;
+ int nsId;
+
+ while (reader.nextAttribute(&nsId, &name))
+ {
+ if (name == "class")
+ {
+ name = reader.getAttributeValue(false);
+ sClass = OString(name.begin, name.length);
+ }
+ else if (name == "id")
+ {
+ name = reader.getAttributeValue(false);
+ sID = OString(name.begin, name.length);
+ sal_Int32 nDelim = sID.indexOf(':');
+ if (nDelim != -1)
+ {
+ sCustomProperty = OUString::fromUtf8(sID.copy(nDelim+1));
+ sID = sID.copy(0, nDelim);
+ }
+ }
+ }
+
+ int nLevel = 1;
+
+ stringmap aProperties;
+ accelmap aAccelerators;
+
+ if (!sCustomProperty.isEmpty())
+ aProperties[OString("customproperty")] = sCustomProperty;
+
+ while(true)
+ {
+ xmlreader::XmlReader::Result res = reader.nextItem(
+ xmlreader::XmlReader::Text::NONE, &name, &nsId);
+
+ if (res == xmlreader::XmlReader::Result::Done)
+ break;
+
+ if (res == xmlreader::XmlReader::Result::Begin)
+ {
+ if (name == "child")
+ {
+ size_t nChildMenuIdx = m_aMenus.size();
+ handleChild(nullptr, reader);
+ assert(m_aMenus.size() > nChildMenuIdx && "menu not inserted");
+ 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, aAccelerators);
+}
+
+void VclBuilder::handleSizeGroup(xmlreader::XmlReader &reader)
+{
+ m_pParserState->m_aSizeGroups.emplace_back();
+ SizeGroup &rSizeGroup = m_pParserState->m_aSizeGroups.back();
+
+ int nLevel = 1;
+
+ while(true)
+ {
+ xmlreader::Span name;
+ int nsId;
+
+ xmlreader::XmlReader::Result res = reader.nextItem(
+ xmlreader::XmlReader::Text::NONE, &name, &nsId);
+
+ if (res == xmlreader::XmlReader::Result::Done)
+ break;
+
+ if (res == xmlreader::XmlReader::Result::Begin)
+ {
+ ++nLevel;
+ if (name == "widget")
+ {
+ while (reader.nextAttribute(&nsId, &name))
+ {
+ if (name == "name")
+ {
+ name = reader.getAttributeValue(false);
+ OString sWidget(name.begin, name.length);
+ sal_Int32 nDelim = sWidget.indexOf(':');
+ if (nDelim != -1)
+ sWidget = sWidget.copy(0, nDelim);
+ rSizeGroup.m_aWidgets.push_back(sWidget);
+ }
+ }
+ }
+ else
+ {
+ if (name == "property")
+ collectProperty(reader, rSizeGroup.m_aProperties);
+ }
+ }
+
+ if (res == xmlreader::XmlReader::Result::End)
+ {
+ --nLevel;
+ }
+
+ if (!nLevel)
+ break;
+ }
+}
+
+namespace
+{
+ vcl::KeyCode makeKeyCode(const std::pair<OString,OString> &rKey)
+ {
+ bool bShift = rKey.second.indexOf("GDK_SHIFT_MASK") != -1;
+ bool bMod1 = rKey.second.indexOf("GDK_CONTROL_MASK") != -1;
+ bool bMod2 = rKey.second.indexOf("GDK_MOD1_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);
+
+ assert (rKey.first.getLength() == 1);
+ char cChar = rKey.first.toChar();
+
+ if (cChar >= 'a' && cChar <= 'z')
+ return vcl::KeyCode(KEY_A + (cChar - 'a'), bShift, bMod1, bMod2, bMod3);
+ else if (cChar >= 'A' && cChar <= 'Z')
+ return vcl::KeyCode(KEY_A + (cChar - 'A'), bShift, bMod1, bMod2, bMod3);
+ else if (cChar >= '0' && cChar <= '9')
+ return vcl::KeyCode(KEY_0 + (cChar - 'A'), bShift, bMod1, bMod2, bMod3);
+
+ return vcl::KeyCode(cChar, bShift, bMod1, bMod2, bMod3);
+ }
+}
+
+void VclBuilder::insertMenuObject(Menu *pParent, PopupMenu *pSubMenu, const OString &rClass, const OString &rID,
+ stringmap &rProps, accelmap &rAccels)
+{
+ sal_uInt16 nOldCount = pParent->GetItemCount();
+ sal_uInt16 nNewId = ++m_pParserState->m_nLastMenuItemId;
+
+ if(rClass == "NotebookBarAddonsMenuMergePoint")
+ {
+ NotebookBarAddonsMerger::MergeNotebookBarMenuAddons(pParent, nNewId, rID, *m_pNotebookBarAddonsItem);
+ m_pParserState->m_nLastMenuItemId = pParent->GetItemCount();
+ }
+ else if (rClass == "GtkMenuItem")
+ {
+ OUString sLabel(BuilderUtils::convertMnemonicMarkup(extractLabel(rProps)));
+ OUString aCommand(extractActionName(rProps));
+ pParent->InsertItem(nNewId, sLabel, MenuItemBits::NONE , rID);
+ pParent->SetItemCommand(nNewId, aCommand);
+ if (pSubMenu)
+ pParent->SetPopupMenu(nNewId, pSubMenu);
+ }
+ else if (rClass == "GtkCheckMenuItem")
+ {
+ OUString sLabel(BuilderUtils::convertMnemonicMarkup(extractLabel(rProps)));
+ OUString aCommand(extractActionName(rProps));
+ pParent->InsertItem(nNewId, sLabel, MenuItemBits::CHECKABLE, rID);
+ pParent->SetItemCommand(nNewId, aCommand);
+ }
+ else if (rClass == "GtkRadioMenuItem")
+ {
+ OUString sLabel(BuilderUtils::convertMnemonicMarkup(extractLabel(rProps)));
+ OUString aCommand(extractActionName(rProps));
+ pParent->InsertItem(nNewId, sLabel, MenuItemBits::AUTOCHECK | MenuItemBits::RADIOCHECK, rID);
+ pParent->SetItemCommand(nNewId, aCommand);
+ }
+ else if (rClass == "GtkSeparatorMenuItem")
+ {
+ pParent->InsertSeparator(rID);
+ }
+
+ SAL_WARN_IF(nOldCount == pParent->GetItemCount(), "vcl.builder", "probably need to implement " << rClass);
+
+ if (nOldCount != pParent->GetItemCount())
+ {
+ pParent->SetHelpId(nNewId, m_sHelpRoot + rID);
+ if (!extractVisible(rProps))
+ pParent->HideItem(nNewId);
+
+ for (auto const& prop : rProps)
+ {
+ const OString &rKey = prop.first;
+ const OUString &rValue = prop.second;
+
+ if (rKey == "tooltip-markup")
+ pParent->SetTipHelpText(nNewId, rValue);
+ else if (rKey == "tooltip-text")
+ pParent->SetTipHelpText(nNewId, rValue);
+ else
+ SAL_INFO("vcl.builder", "unhandled property: " << rKey);
+ }
+
+ for (auto const& accel : rAccels)
+ {
+ const OString &rSignal = accel.first;
+ const auto &rValue = accel.second;
+
+ if (rSignal == "activate")
+ pParent->SetAccelKey(nNewId, makeKeyCode(rValue));
+ else
+ SAL_INFO("vcl.builder", "unhandled accelerator for: " << rSignal);
+ }
+ }
+
+ rProps.clear();
+}
+
+/// Insert items to a ComboBox or a ListBox.
+/// They have no common ancestor that would have 'InsertEntry()', so use a template.
+template<typename T> static bool insertItems(vcl::Window *pWindow, VclBuilder::stringmap &rMap,
+ std::vector<std::unique_ptr<OUString>>& rUserData,
+ const std::vector<ComboBoxTextItem> &rItems)
+{
+ T *pContainer = dynamic_cast<T*>(pWindow);
+ if (!pContainer)
+ return false;
+
+ sal_uInt16 nActiveId = extractActive(rMap);
+ for (auto const& item : rItems)
+ {
+ sal_Int32 nPos = pContainer->InsertEntry(item.m_sItem);
+ if (!item.m_sId.isEmpty())
+ {
+ rUserData.emplace_back(std::make_unique<OUString>(OUString::fromUtf8(item.m_sId)));
+ pContainer->SetEntryData(nPos, rUserData.back().get());
+ }
+ }
+ if (nActiveId < rItems.size())
+ pContainer->SelectEntryPos(nActiveId);
+
+ return true;
+}
+
+VclPtr<vcl::Window> VclBuilder::handleObject(vcl::Window *pParent, xmlreader::XmlReader &reader)
+{
+ OString sClass;
+ OString sID;
+ OUString sCustomProperty;
+
+ xmlreader::Span name;
+ int nsId;
+
+ while (reader.nextAttribute(&nsId, &name))
+ {
+ if (name == "class")
+ {
+ name = reader.getAttributeValue(false);
+ sClass = OString(name.begin, name.length);
+ }
+ else if (name == "id")
+ {
+ name = reader.getAttributeValue(false);
+ sID = OString(name.begin, name.length);
+ if (m_bLegacy)
+ {
+ sal_Int32 nDelim = sID.indexOf(':');
+ if (nDelim != -1)
+ {
+ sCustomProperty = OUString::fromUtf8(sID.copy(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")
+ {
+ handleAtkObject(reader, pParent);
+ return nullptr;
+ }
+
+ int nLevel = 1;
+
+ stringmap aProperties, aPangoAttributes;
+ stringmap aAtkAttributes;
+ std::vector<ComboBoxTextItem> aItems;
+
+ if (!sCustomProperty.isEmpty())
+ aProperties[OString("customproperty")] = sCustomProperty;
+
+ VclPtr<vcl::Window> pCurrentChild;
+ while(true)
+ {
+ xmlreader::XmlReader::Result res = reader.nextItem(
+ xmlreader::XmlReader::Text::NONE, &name, &nsId);
+
+ if (res == xmlreader::XmlReader::Result::Done)
+ break;
+
+ if (res == xmlreader::XmlReader::Result::Begin)
+ {
+ if (name == "child")
+ {
+ if (!pCurrentChild)
+ {
+ pCurrentChild = insertObject(pParent, sClass, sID,
+ aProperties, aPangoAttributes, aAtkAttributes);
+ }
+ handleChild(pCurrentChild, 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(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("vcl.builder", "unknown packing: " << sKey);
+ }
+ }
+ }
+}
+
+std::vector<vcl::EnumContext::Context> VclBuilder::handleStyle(xmlreader::XmlReader &reader, int &nPriority)
+{
+ std::vector<vcl::EnumContext::Context> aContext;
+
+ xmlreader::Span name;
+ int nsId;
+
+ int nLevel = 1;
+
+ while(true)
+ {
+ xmlreader::XmlReader::Result res = reader.nextItem(
+ xmlreader::XmlReader::Text::NONE, &name, &nsId);
+
+ if (res == xmlreader::XmlReader::Result::Done)
+ break;
+
+ if (res == xmlreader::XmlReader::Result::Begin)
+ {
+ ++nLevel;
+ if (name == "class")
+ {
+ OString classStyle = getStyleClass(reader);
+
+ if (classStyle.startsWith("context-"))
+ {
+ OString sContext = classStyle.copy(classStyle.indexOf('-') + 1);
+ OUString sContext2(sContext.getStr(), sContext.getLength(), RTL_TEXTENCODING_UTF8);
+ aContext.push_back(vcl::EnumContext::GetContextEnum(sContext2));
+ }
+ else if (classStyle.startsWith("priority-"))
+ {
+ OString aPriority = classStyle.copy(classStyle.indexOf('-') + 1);
+ OUString aPriority2(aPriority.getStr(), aPriority.getLength(), RTL_TEXTENCODING_UTF8);
+ nPriority = aPriority2.toInt32();
+ }
+ else
+ {
+ SAL_WARN("vcl.builder", "unknown class: " << classStyle);
+ }
+ }
+ }
+
+ if (res == xmlreader::XmlReader::Result::End)
+ {
+ --nLevel;
+ }
+
+ if (!nLevel)
+ break;
+ }
+
+ return aContext;
+}
+
+OString VclBuilder::getStyleClass(xmlreader::XmlReader &reader)
+{
+ xmlreader::Span name;
+ int nsId;
+ OString aRet;
+
+ while (reader.nextAttribute(&nsId, &name))
+ {
+ if (name == "name")
+ {
+ name = reader.getAttributeValue(false);
+ aRet = OString (name.begin, name.length);
+ }
+ }
+
+ return aRet;
+}
+
+void VclBuilder::collectProperty(xmlreader::XmlReader &reader, stringmap &rMap) const
+{
+ xmlreader::Span name;
+ int nsId;
+
+ OString sProperty, sContext;
+
+ bool bTranslated = false;
+
+ while (reader.nextAttribute(&nsId, &name))
+ {
+ if (name == "name")
+ {
+ name = reader.getAttributeValue(false);
+ sProperty = OString(name.begin, name.length);
+ }
+ else if (name == "context")
+ {
+ name = reader.getAttributeValue(false);
+ sContext = OString(name.begin, name.length);
+ }
+ else if (name == "translatable" && reader.getAttributeValue(false) == "yes")
+ {
+ bTranslated = true;
+ }
+ }
+
+ (void)reader.nextItem(xmlreader::XmlReader::Text::Raw, &name, &nsId);
+ OString sValue(name.begin, name.length);
+ OUString sFinalValue;
+ if (bTranslated)
+ {
+ if (!sContext.isEmpty())
+ sValue = sContext + "\004" + sValue;
+ sFinalValue = Translate::get(sValue.getStr(), m_pParserState->m_aResLocale);
+ }
+ else
+ sFinalValue = OUString::fromUtf8(sValue);
+
+ if (!sProperty.isEmpty())
+ {
+ sProperty = sProperty.replace('_', '-');
+ if (m_pStringReplace)
+ sFinalValue = (*m_pStringReplace)(sFinalValue);
+ rMap[sProperty] = sFinalValue;
+ }
+}
+
+void VclBuilder::handleActionWidget(xmlreader::XmlReader &reader)
+{
+ xmlreader::Span name;
+ int nsId;
+
+ OString sResponse;
+
+ while (reader.nextAttribute(&nsId, &name))
+ {
+ if (name == "response")
+ {
+ name = reader.getAttributeValue(false);
+ sResponse = OString(name.begin, name.length);
+ }
+ }
+
+ (void)reader.nextItem(xmlreader::XmlReader::Text::Raw, &name, &nsId);
+ OString sID(name.begin, name.length);
+ sal_Int32 nDelim = sID.indexOf(':');
+ if (nDelim != -1)
+ sID = sID.copy(0, nDelim);
+ set_response(sID, sResponse.toInt32());
+}
+
+void VclBuilder::collectAccelerator(xmlreader::XmlReader &reader, accelmap &rMap)
+{
+ xmlreader::Span name;
+ int nsId;
+
+ OString sProperty;
+ OString sValue;
+ OString sModifiers;
+
+ while (reader.nextAttribute(&nsId, &name))
+ {
+ if (name == "key")
+ {
+ name = reader.getAttributeValue(false);
+ sValue = OString(name.begin, name.length);
+ }
+ else if (name == "signal")
+ {
+ name = reader.getAttributeValue(false);
+ sProperty = OString(name.begin, name.length);
+ }
+ else if (name == "modifiers")
+ {
+ name = reader.getAttributeValue(false);
+ sModifiers = OString(name.begin, name.length);
+ }
+ }
+
+ if (!sProperty.isEmpty() && !sValue.isEmpty())
+ {
+ rMap[sProperty] = std::make_pair(sValue, sModifiers);
+ }
+}
+
+vcl::Window *VclBuilder::get_widget_root()
+{
+ return m_aChildren.empty() ? nullptr : m_aChildren[0].m_pWindow.get();
+}
+
+vcl::Window *VclBuilder::get_by_name(const OString& sID)
+{
+ for (auto const& child : m_aChildren)
+ {
+ if (child.m_sID == sID)
+ return child.m_pWindow;
+ }
+
+ return nullptr;
+}
+
+PopupMenu *VclBuilder::get_menu(const OString& 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(const OString& sID, short nResponse)
+{
+ switch (nResponse)
+ {
+ case -5:
+ nResponse = RET_OK;
+ break;
+ case -6:
+ nResponse = RET_CANCEL;
+ break;
+ case -7:
+ nResponse = RET_CLOSE;
+ break;
+ case -8:
+ nResponse = RET_YES;
+ break;
+ case -9:
+ nResponse = RET_NO;
+ break;
+ case -11:
+ nResponse = RET_HELP;
+ break;
+ default:
+ assert(nResponse >= 100 && "keep non-canned responses in range 100+ to avoid collision with vcl RET_*");
+ break;
+ }
+
+ for (const auto & child : m_aChildren)
+ {
+ if (child.m_sID == sID)
+ {
+ PushButton* pPushButton = dynamic_cast<PushButton*>(child.m_pWindow.get());
+ assert(pPushButton);
+ Dialog* pDialog = pPushButton->GetParentDialog();
+ assert(pDialog);
+ pDialog->add_button(pPushButton, nResponse, false);
+ return;
+ }
+ }
+
+ assert(false);
+}
+
+void VclBuilder::delete_by_name(const OString& sID)
+{
+ auto aI = std::find_if(m_aChildren.begin(), m_aChildren.end(),
+ [&sID](WinAndId& rItem) { return rItem.m_sID == sID; });
+ if (aI != m_aChildren.end())
+ {
+ aI->m_pWindow.disposeAndClear();
+ m_aChildren.erase(aI);
+ }
+}
+
+void VclBuilder::delete_by_window(vcl::Window *pWindow)
+{
+ drop_ownership(pWindow);
+ pWindow->disposeOnce();
+}
+
+void VclBuilder::drop_ownership(const vcl::Window *pWindow)
+{
+ auto aI = std::find_if(m_aChildren.begin(), m_aChildren.end(),
+ [&pWindow](WinAndId& rItem) { return rItem.m_pWindow == pWindow; });
+ if (aI != m_aChildren.end())
+ m_aChildren.erase(aI);
+}
+
+OString VclBuilder::get_by_window(const vcl::Window *pWindow) const
+{
+ for (auto const& child : m_aChildren)
+ {
+ if (child.m_pWindow == pWindow)
+ return child.m_sID;
+ }
+
+ return OString();
+}
+
+VclBuilder::PackingData VclBuilder::get_window_packing_data(const vcl::Window *pWindow) const
+{
+ //We've stored the return of new Control, some of these get
+ //border windows placed around them which are what you get
+ //from GetChild, so scoot up a level if necessary to get the
+ //window whose position value we have
+ const vcl::Window *pPropHolder = pWindow->ImplGetWindow();
+
+ for (auto const& child : m_aChildren)
+ {
+ if (child.m_pWindow == pPropHolder)
+ return child.m_aPackingData;
+ }
+
+ return PackingData();
+}
+
+void VclBuilder::set_window_packing_position(const vcl::Window *pWindow, sal_Int32 nPosition)
+{
+ for (auto & child : m_aChildren)
+ {
+ if (child.m_pWindow == pWindow)
+ child.m_aPackingData.m_nPosition = nPosition;
+ }
+}
+
+const VclBuilder::ListStore *VclBuilder::get_model_by_name(const OString& sID) const
+{
+ std::map<OString, ListStore>::const_iterator aI = m_pParserState->m_aModels.find(sID);
+ if (aI != m_pParserState->m_aModels.end())
+ return &(aI->second);
+ return nullptr;
+}
+
+const VclBuilder::TextBuffer *VclBuilder::get_buffer_by_name(const OString& sID) const
+{
+ std::map<OString, TextBuffer>::const_iterator aI = m_pParserState->m_aTextBuffers.find(sID);
+ if (aI != m_pParserState->m_aTextBuffers.end())
+ return &(aI->second);
+ return nullptr;
+}
+
+const VclBuilder::Adjustment *VclBuilder::get_adjustment_by_name(const OString& sID) const
+{
+ std::map<OString, Adjustment>::const_iterator aI = m_pParserState->m_aAdjustments.find(sID);
+ if (aI != m_pParserState->m_aAdjustments.end())
+ return &(aI->second);
+ return nullptr;
+}
+
+void VclBuilder::mungeModel(ComboBox &rTarget, const ListStore &rStore, sal_uInt16 nActiveId)
+{
+ for (auto const& entry : rStore.m_aEntries)
+ {
+ const ListStore::row &rRow = entry;
+ sal_uInt16 nEntry = rTarget.InsertEntry(rRow[0]);
+ if (rRow.size() > 1)
+ {
+ if (m_bLegacy)
+ {
+ sal_IntPtr 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_IntPtr 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_IntPtr nValue = rRow[1].toInt32();
+ pEntry->SetUserData(reinterpret_cast<void*>(nValue));
+ }
+ else
+ {
+ if (!rRow[1].isEmpty())
+ {
+ m_aUserData.emplace_back(std::make_unique<OUString>(rRow[1]));
+ pEntry->SetUserData(m_aUserData.back().get());
+ }
+ }
+ }
+ }
+ if (nActiveId < rStore.m_aEntries.size())
+ {
+ SvTreeListEntry* pEntry = rTarget.GetEntry(nullptr, nActiveId);
+ rTarget.Select(pEntry);
+ }
+}
+
+void VclBuilder::mungeAdjustment(NumericFormatter &rTarget, const Adjustment &rAdjustment)
+{
+ int nMul = rtl_math_pow10Exp(1, rTarget.GetDecimalDigits());
+
+ for (auto const& elem : rAdjustment)
+ {
+ const OString &rKey = elem.first;
+ const OUString &rValue = elem.second;
+
+ if (rKey == "upper")
+ {
+ sal_Int64 nUpper = rValue.toDouble() * nMul;
+ rTarget.SetMax(nUpper);
+ rTarget.SetLast(nUpper);
+ }
+ else if (rKey == "lower")
+ {
+ sal_Int64 nLower = rValue.toDouble() * nMul;
+ rTarget.SetMin(nLower);
+ rTarget.SetFirst(nLower);
+ }
+ else if (rKey == "value")
+ {
+ sal_Int64 nValue = rValue.toDouble() * nMul;
+ rTarget.SetValue(nValue);
+ }
+ else if (rKey == "step-increment")
+ {
+ sal_Int64 nSpinSize = rValue.toDouble() * nMul;
+ rTarget.SetSpinSize(nSpinSize);
+ }
+ else
+ {
+ SAL_INFO("vcl.builder", "unhandled property :" << rKey);
+ }
+ }
+}
+
+void VclBuilder::mungeAdjustment(FormattedField &rTarget, const Adjustment &rAdjustment)
+{
+ for (auto const& elem : rAdjustment)
+ {
+ const OString &rKey = elem.first;
+ const OUString &rValue = elem.second;
+
+ if (rKey == "upper")
+ {
+ rTarget.SetMaxValue(rValue.toDouble());
+ }
+ else if (rKey == "lower")
+ {
+ rTarget.SetMinValue(rValue.toDouble());
+ }
+ else if (rKey == "value")
+ {
+ rTarget.SetValue(rValue.toDouble());
+ }
+ else if (rKey == "step-increment")
+ {
+ rTarget.SetSpinSize(rValue.toDouble());
+ }
+ else
+ {
+ SAL_INFO("vcl.builder", "unhandled property :" << rKey);
+ }
+ }
+}
+
+void VclBuilder::mungeAdjustment(TimeField &rTarget, const Adjustment &rAdjustment)
+{
+ for (auto const& elem : rAdjustment)
+ {
+ const OString &rKey = elem.first;
+ const OUString &rValue = elem.second;
+
+ if (rKey == "upper")
+ {
+ tools::Time aUpper(rValue.toInt32());
+ rTarget.SetMax(aUpper);
+ rTarget.SetLast(aUpper);
+ }
+ else if (rKey == "lower")
+ {
+ tools::Time aLower(rValue.toInt32());
+ rTarget.SetMin(aLower);
+ rTarget.SetFirst(aLower);
+ }
+ else if (rKey == "value")
+ {
+ tools::Time aValue(rValue.toInt32());
+ rTarget.SetTime(aValue);
+ }
+ else
+ {
+ SAL_INFO("vcl.builder", "unhandled property :" << rKey);
+ }
+ }
+}
+
+void VclBuilder::mungeAdjustment(DateField &rTarget, const Adjustment &rAdjustment)
+{
+ for (auto const& elem : rAdjustment)
+ {
+ const OString &rKey = elem.first;
+ const OUString &rValue = elem.second;
+
+ if (rKey == "upper")
+ {
+ Date aUpper(rValue.toInt32());
+ rTarget.SetMax(aUpper);
+ rTarget.SetLast(aUpper);
+ }
+ else if (rKey == "lower")
+ {
+ Date aLower(rValue.toInt32());
+ rTarget.SetMin(aLower);
+ rTarget.SetFirst(aLower);
+ }
+ else if (rKey == "value")
+ {
+ Date aValue(rValue.toInt32());
+ rTarget.SetDate(aValue);
+ }
+ else
+ {
+ SAL_INFO("vcl.builder", "unhandled property :" << rKey);
+ }
+ }
+}
+
+void VclBuilder::mungeAdjustment(ScrollBar &rTarget, const Adjustment &rAdjustment)
+{
+ for (auto const& elem : rAdjustment)
+ {
+ const OString &rKey = elem.first;
+ const OUString &rValue = elem.second;
+
+ if (rKey == "upper")
+ rTarget.SetRangeMax(rValue.toInt32());
+ else if (rKey == "lower")
+ rTarget.SetRangeMin(rValue.toInt32());
+ else if (rKey == "value")
+ rTarget.SetThumbPos(rValue.toInt32());
+ else if (rKey == "step-increment")
+ rTarget.SetLineSize(rValue.toInt32());
+ else if (rKey == "page-increment")
+ rTarget.SetPageSize(rValue.toInt32());
+ else
+ {
+ SAL_INFO("vcl.builder", "unhandled property :" << rKey);
+ }
+ }
+}
+
+void VclBuilder::mungeAdjustment(Slider& rTarget, const Adjustment& rAdjustment)
+{
+ for (auto const& elem : rAdjustment)
+ {
+ const OString &rKey = elem.first;
+ const OUString &rValue = elem.second;
+
+ if (rKey == "upper")
+ rTarget.SetRangeMax(rValue.toInt32());
+ else if (rKey == "lower")
+ rTarget.SetRangeMin(rValue.toInt32());
+ else if (rKey == "value")
+ rTarget.SetThumbPos(rValue.toInt32());
+ else if (rKey == "step-increment")
+ rTarget.SetLineSize(rValue.toInt32());
+ else if (rKey == "page-increment")
+ rTarget.SetPageSize(rValue.toInt32());
+ else
+ {
+ SAL_INFO("vcl.builder", "unhandled property :" << rKey);
+ }
+ }
+}
+
+void VclBuilder::mungeTextBuffer(VclMultiLineEdit &rTarget, const TextBuffer &rTextBuffer)
+{
+ for (auto const& elem : rTextBuffer)
+ {
+ const OString &rKey = elem.first;
+ const OUString &rValue = elem.second;
+
+ if (rKey == "text")
+ rTarget.SetText(rValue);
+ else
+ {
+ SAL_INFO("vcl.builder", "unhandled property :" << rKey);
+ }
+ }
+}
+
+VclBuilder::ParserState::ParserState()
+ : m_nLastToolbarId(0)
+ , m_nLastMenuItemId(0)
+{}
+
+VclBuilder::MenuAndId::MenuAndId(const OString &rId, Menu *pMenu)
+ : m_sID(rId)
+ , m_pMenu(pMenu)
+{}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/clipping.cxx b/vcl/source/window/clipping.cxx
new file mode 100644
index 000000000..e2c3a37c8
--- /dev/null
+++ b/vcl/source/window/clipping.cxx
@@ -0,0 +1,719 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 Window::GetOutputBoundsClipRegion() const
+{
+ vcl::Region aClip(GetClipRegion());
+ aClip.Intersect(tools::Rectangle(Point(), GetOutputSize()));
+
+ return aClip;
+}
+
+void Window::InitClipRegion()
+{
+ DBG_TESTSOLARMUTEX();
+
+ vcl::Region aRegion;
+
+ if ( mpWindowImpl->mbInPaint )
+ aRegion = *(mpWindowImpl->mpPaintRegion);
+ else
+ {
+ aRegion = *(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 )
+ {
+ vcl::Region aPixRegion = LogicToPixel( rRegion );
+ vcl::Region aDevPixRegion = ImplPixelToDevicePixel( aPixRegion );
+
+ vcl::Region aWinChildRegion = *ImplGetWinChildClipRegion();
+ // only this region is in frame coordinates, so re-mirror it
+ if( ImplIsAntiparallel() )
+ {
+ const OutputDevice *pOutDev = GetOutDev();
+ pOutDev->ReMirror( aWinChildRegion );
+ }
+
+ aDevPixRegion.Intersect( aWinChildRegion );
+ if( ! aDevPixRegion.IsEmpty() )
+ {
+ mpWindowImpl->mpPaintRegion->Union( aDevPixRegion );
+ mbInitClipRegion = true;
+ }
+ }
+}
+
+vcl::Region Window::GetWindowClipRegionPixel() const
+{
+ vcl::Region aWinClipRegion;
+
+ if ( mpWindowImpl->mbInitWinClipRegion )
+ const_cast<vcl::Window*>(this)->ImplInitWinClipRegion();
+ aWinClipRegion = mpWindowImpl->maWinClipRegion;
+
+ tools::Rectangle aWinRect( Point( mnOutOffX, mnOutOffY ), Size( mnOutWidth, mnOutHeight ) );
+ vcl::Region aWinRegion( aWinRect );
+
+ if ( aWinRegion == aWinClipRegion )
+ aWinClipRegion.SetNull();
+
+ aWinClipRegion.Move( -mnOutOffX, -mnOutOffY );
+
+ return aWinClipRegion;
+}
+
+
+vcl::Region Window::GetActiveClipRegion() const
+{
+ vcl::Region aRegion(true);
+
+ if ( mpWindowImpl->mbInPaint )
+ {
+ aRegion = *(mpWindowImpl->mpPaintRegion);
+ aRegion.Move( -mnOutOffX, -mnOutOffY );
+ }
+
+ if ( mbClipRegion )
+ aRegion.Intersect( maRegion );
+
+ return PixelToLogic( aRegion );
+}
+
+void Window::ClipToPaintRegion(tools::Rectangle& rDstRect)
+{
+ const vcl::Region aPaintRgn(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 ), Size( mpWindowImpl->mpFrameWindow->mnOutWidth, mpWindowImpl->mpFrameWindow->mnOutHeight ) ) );
+
+ 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 = tools::Rectangle( Point( mnOutOffX, mnOutOffY ),
+ Size( mnOutWidth, mnOutHeight ) );
+ if ( mpWindowImpl->mbWinRegion )
+ mpWindowImpl->maWinClipRegion.Intersect( 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.get();
+ else
+ return &mpWindowImpl->maWinClipRegion;
+}
+
+
+bool Window::ImplSysObjClip( const vcl::Region* pOldRegion )
+{
+ bool bUpdate = true;
+
+ if ( mpWindowImpl->mpSysObj )
+ {
+ bool bVisibleState = mpWindowImpl->mbReallyVisible;
+
+ if ( bVisibleState )
+ {
+ vcl::Region* pWinChildClipRegion = ImplGetWinChildClipRegion();
+
+ if ( !pWinChildClipRegion->IsEmpty() )
+ {
+ if ( pOldRegion )
+ {
+ vcl::Region aNewRegion = *pWinChildClipRegion;
+ pWinChildClipRegion->Intersect( *pOldRegion );
+ bUpdate = aNewRegion == *pWinChildClipRegion;
+ }
+
+ vcl::Region aRegion = *pWinChildClipRegion;
+ tools::Rectangle aWinRect( Point( mnOutOffX, mnOutOffY ), Size( mnOutWidth, mnOutHeight ) );
+ vcl::Region aWinRectRegion( aWinRect );
+
+ if ( aRegion == aWinRectRegion )
+ mpWindowImpl->mpSysObj->ResetClipRegion();
+ else
+ {
+ aRegion.Move( -mnOutOffX, -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 ));
+
+ 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() ) )
+ {
+ mbInitClipRegion = true;
+ mpWindowImpl->mbInitWinClipRegion = true;
+ bUpdate = false;
+ }
+ }
+ else
+ {
+ 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->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( tools::Rectangle( Point( mnOutOffX, mnOutOffY ),
+ Size( mnOutWidth, mnOutHeight ) ) );
+ if ( mpWindowImpl->mbWinRegion )
+ rRegion.Intersect( ImplPixelToDevicePixel( mpWindowImpl->maWinRegion ) );
+}
+
+void Window::ImplExcludeWindowRegion( vcl::Region& rRegion )
+{
+ if ( mpWindowImpl->mbWinRegion )
+ {
+ Point aPoint( mnOutOffX, mnOutOffY );
+ vcl::Region aRegion( tools::Rectangle( aPoint,
+ Size( mnOutWidth, mnOutHeight ) ) );
+ aRegion.Intersect( ImplPixelToDevicePixel( mpWindowImpl->maWinRegion ) );
+ rRegion.Exclude( aRegion );
+ }
+ else
+ {
+ Point aPoint( mnOutOffX, mnOutOffY );
+ rRegion.Exclude( tools::Rectangle( aPoint,
+ Size( mnOutWidth, mnOutHeight ) ) );
+ }
+}
+
+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( 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 ), Size( mpWindowImpl->mpFrameWindow->mnOutWidth, mpWindowImpl->mpFrameWindow->mnOutHeight ) ) );
+ 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 )
+ {
+ pWindow = mpWindowImpl->mpFirstChild;
+ while ( pWindow )
+ {
+ if ( pWindow->mpWindowImpl->mbReallyVisible )
+ {
+ aTempRegion = aRegion;
+ pWindow->ImplIntersectWindowRegion( aTempRegion );
+ rRegion.Union( aTempRegion );
+ }
+ pWindow = pWindow->mpWindowImpl->mpNext;
+ }
+ }
+}
+
+void Window::SaveBackground(VirtualDevice& rSaveDevice, const Point& rPos, const Size& rSize, const Size&) const
+{
+ MapMode aTempMap(GetMapMode());
+ aTempMap.SetOrigin(Point());
+ rSaveDevice.SetMapMode(aTempMap);
+
+ if ( mpWindowImpl->mpPaintRegion )
+ {
+ vcl::Region aClip( *mpWindowImpl->mpPaintRegion );
+ const Point aPixPos( LogicToPixel( rPos ) );
+
+ aClip.Move( -mnOutOffX, -mnOutOffY );
+ aClip.Intersect( tools::Rectangle( aPixPos, LogicToPixel( rSize ) ) );
+
+ if ( !aClip.IsEmpty() )
+ {
+ const vcl::Region aOldClip( rSaveDevice.GetClipRegion() );
+ const Point aPixOffset( rSaveDevice.LogicToPixel( Point() ) );
+ const bool bMap = rSaveDevice.IsMapModeEnabled();
+
+ // move clip region to have the same distance to DestOffset
+ aClip.Move( aPixOffset.X() - aPixPos.X(), aPixOffset.Y() - aPixPos.Y() );
+
+ // set pixel clip region
+ rSaveDevice.EnableMapMode( false );
+ rSaveDevice.SetClipRegion( aClip );
+ rSaveDevice.EnableMapMode( bMap );
+ rSaveDevice.DrawOutDev( Point(), rSize, rPos, rSize, *this );
+ rSaveDevice.SetClipRegion( aOldClip );
+ }
+ }
+ else
+ {
+ rSaveDevice.DrawOutDev( Point(), rSize, rPos, rSize, *this );
+ }
+
+ rSaveDevice.SetMapMode(MapMode());
+}
+
+} /* namespace vcl */
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/commandevent.cxx b/vcl/source/window/commandevent.cxx
new file mode 100644
index 000000000..06e974c9f
--- /dev/null
+++ b/vcl/source/window/commandevent.cxx
@@ -0,0 +1,198 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <string.h>
+
+#include <vcl/commandevent.hxx>
+
+CommandExtTextInputData::CommandExtTextInputData( const OUString& rText,
+ const ExtTextInputAttr* pTextAttr, sal_Int32 nCursorPos, sal_uInt16 nCursorFlags,
+ bool bOnlyCursor)
+ : maText(rText)
+{
+ if ( pTextAttr && !maText.isEmpty() )
+ {
+ mpTextAttr.reset( new ExtTextInputAttr[maText.getLength()] );
+ memcpy( mpTextAttr.get(), pTextAttr, maText.getLength()*sizeof(ExtTextInputAttr) );
+ }
+
+ mnCursorPos = nCursorPos;
+ mnCursorFlags = nCursorFlags;
+ mbOnlyCursor = bOnlyCursor;
+}
+
+CommandExtTextInputData::CommandExtTextInputData( const CommandExtTextInputData& rData ) :
+ maText( rData.maText )
+{
+ if ( rData.mpTextAttr && !maText.isEmpty() )
+ {
+ mpTextAttr.reset( new ExtTextInputAttr[maText.getLength()] );
+ memcpy( mpTextAttr.get(), rData.mpTextAttr.get(), maText.getLength()*sizeof(ExtTextInputAttr) );
+ }
+
+ mnCursorPos = rData.mnCursorPos;
+ mnCursorFlags = rData.mnCursorFlags;
+ mbOnlyCursor = rData.mbOnlyCursor;
+}
+
+CommandExtTextInputData::~CommandExtTextInputData()
+{
+}
+
+CommandWheelData::CommandWheelData()
+{
+ mnDelta = 0;
+ mnNotchDelta = 0;
+ mnLines = 0.0;
+ mnWheelMode = CommandWheelMode::NONE;
+ mnCode = 0;
+ mbHorz = false;
+ mbDeltaIsPixel = false;
+}
+
+CommandWheelData::CommandWheelData( long nWheelDelta, 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( long nDeltaX, long nDeltaY )
+{
+ mnDeltaX = nDeltaX;
+ mnDeltaY = nDeltaY;
+}
+
+CommandModKeyData::CommandModKeyData( ModKeyFlags nCode, bool bDown )
+{
+ mbDown = bDown;
+ mnCode = nCode;
+}
+
+CommandSelectionChangeData::CommandSelectionChangeData( sal_uLong nStart, sal_uLong nEnd )
+{
+ mnStart = nStart;
+ mnEnd = nEnd;
+}
+
+CommandEvent::CommandEvent()
+{
+ mpData = nullptr;
+ mnCommand = CommandEventId::NONE;
+ mbMouseEvent = false;
+}
+
+CommandEvent::CommandEvent( const Point& rMousePos,
+ CommandEventId nCmd, bool bMEvt, const void* pCmdData ) :
+ maPos( rMousePos )
+{
+ mpData = const_cast<void*>(pCmdData);
+ mnCommand = nCmd;
+ mbMouseEvent = bMEvt;
+}
+
+const CommandExtTextInputData* CommandEvent::GetExtTextInputData() const
+{
+ if ( mnCommand == CommandEventId::ExtTextInput )
+ return static_cast<const CommandExtTextInputData*>(mpData);
+ else
+ return nullptr;
+}
+
+const CommandWheelData* CommandEvent::GetWheelData() const
+{
+ if ( mnCommand == CommandEventId::Wheel )
+ return static_cast<const CommandWheelData*>(mpData);
+ else
+ return nullptr;
+}
+
+const CommandScrollData* CommandEvent::GetAutoScrollData() const
+{
+ if ( mnCommand == CommandEventId::AutoScroll )
+ return static_cast<const CommandScrollData*>(mpData);
+ else
+ return nullptr;
+}
+
+const CommandModKeyData* CommandEvent::GetModKeyData() const
+{
+ if( mnCommand == CommandEventId::ModKeyChange )
+ return static_cast<const CommandModKeyData*>(mpData);
+ else
+ return nullptr;
+}
+
+const CommandDialogData* CommandEvent::GetDialogData() const
+{
+ if( mnCommand == CommandEventId::ShowDialog )
+ return static_cast<const CommandDialogData*>(mpData);
+ else
+ return nullptr;
+}
+
+CommandMediaData* CommandEvent::GetMediaData() const
+{
+ if( mnCommand == CommandEventId::Media )
+ return static_cast<CommandMediaData*>(mpData);
+ else
+ return nullptr;
+}
+
+const CommandSelectionChangeData* CommandEvent::GetSelectionChangeData() const
+{
+ if( mnCommand == CommandEventId::SelectionChange )
+ return static_cast<const CommandSelectionChangeData*>(mpData);
+ else
+ return nullptr;
+}
+
+const CommandSwipeData* CommandEvent::GetSwipeData() const
+{
+ if( mnCommand == CommandEventId::Swipe )
+ return static_cast<const CommandSwipeData*>(mpData);
+ else
+ return nullptr;
+}
+
+const CommandLongPressData* CommandEvent::GetLongPressData() const
+{
+ if( mnCommand == CommandEventId::LongPress )
+ return static_cast<const CommandLongPressData*>(mpData);
+ else
+ return nullptr;
+}
+
+const CommandGestureData* CommandEvent::GetGestureData() const
+{
+ if (mnCommand == CommandEventId::Gesture)
+ return static_cast<const CommandGestureData*>(mpData);
+ else
+ return nullptr;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/cursor.cxx b/vcl/source/window/cursor.cxx
new file mode 100644
index 000000000..9eba6b2f6
--- /dev/null
+++ b/vcl/source/window/cursor.cxx
@@ -0,0 +1,462 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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; // Timer
+ Point maPixPos; // Pixel-Position
+ Point maPixRotOff; // Pixel-Offset-Position
+ Size maPixSize; // Pixel-Size
+ short 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
+};
+
+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 ( pData->mnDirection != CursorDirection::NONE)
+ {
+ Point pAry[7];
+ int delta = 3*aRect.getWidth()+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[3] = pAry[1];
+ pAry[3].AdjustY(delta );
+ 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 );
+ pAry[5] = pAry[0];
+ pAry[5].AdjustX( -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;
+
+ std::unique_ptr<vcl::PaintBufferGuard> pGuard;
+ const bool bDoubleBuffering = pWindow->SupportsDoubleBuffering();
+ if (bDoubleBuffering)
+ pGuard.reset(new vcl::PaintBufferGuard(pWindow->ImplGetWindowImpl()->mpFrameData, pWindow));
+
+ vcl::RenderContext* pRenderContext = bDoubleBuffering ? pGuard->GetRenderContext() : pWindow;
+
+ 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, *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 )
+ {
+ 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 )
+ {
+ if ( !mpData )
+ {
+ mpData.reset( new ImplCursorData );
+ mpData->mbCurVisible = false;
+ mpData->maTimer.SetInvokeHandler( LINK( this, Cursor, ImplTimerHdl ) );
+ mpData->maTimer.SetDebugName( "vcl ImplCursorData maTimer" );
+ }
+
+ 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 )
+{
+ if (VclPtr<vcl::Window> pParent = pWindow->GetParentWithLOKNotifier())
+ {
+ 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 long nX = pWindow->GetOutOffXPixel() + pWindow->LogicToPixel(GetPos()).X() - pParent->GetOutOffXPixel();
+ const long nY = pWindow->GetOutOffYPixel() + pWindow->LogicToPixel(GetPos()).Y() - pParent->GetOutOffYPixel();
+ Size aSize = pWindow->LogicToPixel(GetSize());
+ if (!aSize.Width())
+ aSize.setWidth( pWindow->GetSettings().GetStyleSettings().GetCursorSize() );
+
+ const tools::Rectangle aRect(Point(nX, nY), 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 )
+ {
+ 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;
+ 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( long nNewWidth )
+{
+ if ( maSize.Width() != nNewWidth )
+ {
+ maSize.setWidth( nNewWidth );
+ ImplNew();
+ }
+}
+
+void vcl::Cursor::SetOrientation( short nNewOrientation )
+{
+ if ( mnOrientation != nNewOrientation )
+ {
+ mnOrientation = nNewOrientation;
+ ImplNew();
+ }
+}
+
+void vcl::Cursor::SetDirection( CursorDirection nNewDirection )
+{
+ if ( mnDirection != nNewDirection )
+ {
+ mnDirection = nNewDirection;
+ ImplNew();
+ }
+}
+
+vcl::Cursor& vcl::Cursor::operator=( const vcl::Cursor& rCursor )
+{
+ maPos = rCursor.maPos;
+ maSize = rCursor.maSize;
+ mnOrientation = rCursor.mnOrientation;
+ mnDirection = rCursor.mnDirection;
+ mbVisible = rCursor.mbVisible;
+ ImplNew();
+
+ return *this;
+}
+
+bool vcl::Cursor::operator==( const vcl::Cursor& rCursor ) const
+{
+ return
+ ((maPos == rCursor.maPos) &&
+ (maSize == rCursor.maSize) &&
+ (mnOrientation == rCursor.mnOrientation) &&
+ (mnDirection == rCursor.mnDirection) &&
+ (mbVisible == rCursor.mbVisible))
+ ;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/debug.cxx b/vcl/source/window/debug.cxx
new file mode 100644
index 000000000..4b3d41137
--- /dev/null
+++ b/vcl/source/window/debug.cxx
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <vcl/window.hxx>
+#include <tools/debug.hxx>
+
+#include <window.h>
+
+#ifdef DBG_UTIL
+const char* ImplDbgCheckWindow( const void* pObj )
+{
+ DBG_TESTSOLARMUTEX();
+
+ const vcl::Window* pWindow = static_cast<vcl::Window const *>(pObj);
+
+ if ( (pWindow->GetType() < WindowType::FIRST) || (pWindow->GetType() > WindowType::LAST) )
+ return "Window data overwrite";
+
+ // check window-chain
+ vcl::Window* pChild = pWindow->mpWindowImpl->mpFirstChild;
+ while ( pChild )
+ {
+ if ( pChild->mpWindowImpl->mpParent != pWindow )
+ return "Child-Window-Parent wrong";
+ pChild = pChild->mpWindowImpl->mpNext;
+ }
+
+ return nullptr;
+}
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/debugevent.cxx b/vcl/source/window/debugevent.cxx
new file mode 100644
index 000000000..c28cd84f8
--- /dev/null
+++ b/vcl/source/window/debugevent.cxx
@@ -0,0 +1,270 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 <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
+ 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 = OString( pEvents ).toUInt32();
+ if (nEvents > 0)
+ return new DebugEventInjector( nEvents );
+ else
+ return nullptr;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/decoview.cxx b/vcl/source/window/decoview.cxx
new file mode 100644
index 000000000..9111f7402
--- /dev/null
+++ b/vcl/source/window/decoview.cxx
@@ -0,0 +1,1090 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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>
+
+static constexpr auto BUTTON_DRAW_FLATTEST = DrawButtonFlags::Flat |
+ DrawButtonFlags::Pressed |
+ DrawButtonFlags::Checked |
+ DrawButtonFlags::Highlight;
+
+using namespace std;
+
+namespace {
+
+long AdjustRectToSquare( tools::Rectangle &rRect )
+{
+ const long nWidth = rRect.GetWidth();
+ const long nHeight = rRect.GetHeight();
+ 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 long nSide = AdjustRectToSquare( nRect );
+
+ if ( !nSide ) return;
+ if ( nSide==1 )
+ {
+ pDev->DrawPixel( Point( nRect.Left(), nRect.Top() ) );
+ return;
+ }
+
+ // Precalculate some values
+ const long n2 = nSide/2;
+ const long n4 = (n2+1)/2;
+ const long n8 = (n4+1)/2;
+ const long n16 = (n8+1)/2;
+ const Point aCenter = nRect.Center();
+
+ switch ( eType )
+ {
+ case SymbolType::ARROW_UP:
+ pDev->DrawPixel( Point( aCenter.X(), nRect.Top() ) );
+ for ( long i=1; i <= n2; ++i )
+ {
+ nRect.AdjustTop( 1 );
+ pDev->DrawLine( Point( aCenter.X()-i, nRect.Top() ),
+ Point( aCenter.X()+i, nRect.Top() ) );
+ pDev->DrawPixel( Point( aCenter.X()-i, nRect.Top() ) );
+ pDev->DrawPixel( Point( aCenter.X()+i, nRect.Top() ) );
+ }
+ pDev->DrawRect( tools::Rectangle( aCenter.X()-n8, nRect.Top()+1,
+ aCenter.X()+n8, nRect.Bottom() ) );
+ break;
+
+ case SymbolType::ARROW_DOWN:
+ pDev->DrawPixel( Point( aCenter.X(), nRect.Bottom() ) );
+ for ( long i=1; i <= n2; ++i )
+ {
+ nRect.AdjustBottom( -1 );
+ pDev->DrawLine( Point( aCenter.X()-i, nRect.Bottom() ),
+ Point( aCenter.X()+i, nRect.Bottom() ) );
+ pDev->DrawPixel( Point( aCenter.X()-i, nRect.Bottom() ) );
+ pDev->DrawPixel( Point( aCenter.X()+i, nRect.Bottom() ) );
+ }
+ pDev->DrawRect( tools::Rectangle( aCenter.X()-n8, nRect.Top(),
+ aCenter.X()+n8, nRect.Bottom()-1 ) );
+ break;
+
+ case SymbolType::ARROW_LEFT:
+ pDev->DrawPixel( Point( nRect.Left(), aCenter.Y() ) );
+ for ( long i=1; i <= n2; ++i )
+ {
+ nRect.AdjustLeft( 1 );
+ pDev->DrawLine( Point( nRect.Left(), aCenter.Y()-i ),
+ Point( nRect.Left(), aCenter.Y()+i ) );
+ pDev->DrawPixel( Point( nRect.Left(), aCenter.Y()-i ) );
+ pDev->DrawPixel( Point( nRect.Left(), aCenter.Y()+i ) );
+ }
+ pDev->DrawRect( tools::Rectangle( nRect.Left()+1, aCenter.Y()-n8,
+ nRect.Right(), aCenter.Y()+n8 ) );
+ break;
+
+ case SymbolType::ARROW_RIGHT:
+ pDev->DrawPixel( Point( nRect.Right(), aCenter.Y() ) );
+ for ( long i=1; i <= n2; ++i )
+ {
+ nRect.AdjustRight( -1 );
+ pDev->DrawLine( Point( nRect.Right(), aCenter.Y()-i ),
+ Point( nRect.Right(), aCenter.Y()+i ) );
+ pDev->DrawPixel( Point( nRect.Right(), aCenter.Y()-i ) );
+ pDev->DrawPixel( Point( nRect.Right(), aCenter.Y()+i ) );
+ }
+ pDev->DrawRect( tools::Rectangle( nRect.Left(), aCenter.Y()-n8,
+ nRect.Right()-1, aCenter.Y()+n8 ) );
+ break;
+
+ case SymbolType::SPIN_UP:
+ nRect.AdjustTop(n4 );
+ pDev->DrawPixel( Point( aCenter.X(), nRect.Top() ) );
+ for ( long i=1; i <= n2; ++i )
+ {
+ nRect.AdjustTop( 1 );
+ pDev->DrawLine( Point( aCenter.X()-i, nRect.Top() ),
+ Point( aCenter.X()+i, nRect.Top() ) );
+ pDev->DrawPixel( Point( aCenter.X()-i, nRect.Top() ) );
+ pDev->DrawPixel( Point( aCenter.X()+i, nRect.Top() ) );
+ }
+ break;
+
+ case SymbolType::SPIN_DOWN:
+ nRect.AdjustBottom( -n4 );
+ pDev->DrawPixel( Point( aCenter.X(), nRect.Bottom() ) );
+ for ( long i=1; i <= n2; ++i )
+ {
+ nRect.AdjustBottom( -1 );
+ pDev->DrawLine( Point( aCenter.X()-i, nRect.Bottom() ),
+ Point( aCenter.X()+i, nRect.Bottom() ) );
+ pDev->DrawPixel( Point( aCenter.X()-i, nRect.Bottom() ) );
+ pDev->DrawPixel( Point( aCenter.X()+i, nRect.Bottom() ) );
+ }
+ 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(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(PushFlags::LINECOLOR);
+ pDev->SetLineColor();
+ pDev->DrawPolygon(aTriangle);
+ pDev->Pop();
+ break;
+ }
+
+ case SymbolType::PAGEUP:
+ pDev->DrawPixel( Point( aCenter.X(), nRect.Top() ) );
+ pDev->DrawPixel( Point( aCenter.X(), nRect.Top()+n2 ) );
+ for ( long i=1; i < n2; ++i )
+ {
+ nRect.AdjustTop( 1 );
+ pDev->DrawLine( Point( aCenter.X()-i, nRect.Top() ),
+ Point( aCenter.X()+i, nRect.Top() ) );
+ pDev->DrawPixel( Point( aCenter.X()-i, nRect.Top() ) );
+ pDev->DrawPixel( Point( aCenter.X()+i, nRect.Top() ) );
+ pDev->DrawLine( Point( aCenter.X()-i, nRect.Top()+n2 ),
+ Point( aCenter.X()+i, nRect.Top()+n2 ) );
+ pDev->DrawPixel( Point( aCenter.X()-i, nRect.Top()+n2 ) );
+ pDev->DrawPixel( Point( aCenter.X()+i, nRect.Top()+n2 ) );
+ }
+ break;
+
+ case SymbolType::PAGEDOWN:
+ pDev->DrawPixel( Point( aCenter.X(), nRect.Bottom() ) );
+ pDev->DrawPixel( Point( aCenter.X(), nRect.Bottom()-n2 ) );
+ for ( long i=1; i < n2; ++i )
+ {
+ nRect.AdjustBottom( -1 );
+ pDev->DrawLine( Point( aCenter.X()-i, nRect.Bottom() ),
+ Point( aCenter.X()+i, nRect.Bottom() ) );
+ pDev->DrawPixel( Point( aCenter.X()-i, nRect.Bottom() ) );
+ pDev->DrawPixel( Point( aCenter.X()+i, nRect.Bottom() ) );
+ pDev->DrawLine( Point( aCenter.X()-i, nRect.Bottom()-n2 ),
+ Point( aCenter.X()+i, nRect.Bottom()-n2 ) );
+ pDev->DrawPixel( Point( aCenter.X()-i, nRect.Bottom()-n2 ) );
+ pDev->DrawPixel( Point( aCenter.X()+i, nRect.Bottom()-n2 ) );
+ }
+ break;
+
+ case SymbolType::RADIOCHECKMARK:
+ {
+ // Midpoint circle algorithm
+ long x = 0;
+ long y = n2;
+ long p = 1 - n2;
+ // Draw central line
+ pDev->DrawLine( Point( aCenter.X(), aCenter.Y()-y ),
+ Point( aCenter.X(), aCenter.Y()+y ) );
+ while ( x<y )
+ {
+ if ( p>=0 )
+ {
+ // Draw vertical lines close to sides
+ pDev->DrawLine( Point( aCenter.X()+y, aCenter.Y()-x ),
+ Point( aCenter.X()+y, aCenter.Y()+x ) );
+ pDev->DrawLine( Point( aCenter.X()-y, aCenter.Y()-x ),
+ Point( aCenter.X()-y, aCenter.Y()+x ) );
+ --y;
+ p -= 2*y;
+ }
+ ++x;
+ p += 2*x+1;
+ // Draw vertical lines close to center
+ pDev->DrawLine( Point( aCenter.X()-x, aCenter.Y()-y ),
+ Point( aCenter.X()-x, aCenter.Y()+y ) );
+ pDev->DrawLine( Point( aCenter.X()+x, aCenter.Y()-y ),
+ Point( aCenter.X()+x, aCenter.Y()+y ) );
+ }
+ }
+ break;
+
+ case SymbolType::STOP:
+ pDev->DrawRect( nRect );
+ break;
+
+ case SymbolType::CLOSE:
+ pDev->DrawLine( Point( nRect.Left(), nRect.Top() ),
+ Point( nRect.Right(), nRect.Bottom() ) );
+ pDev->DrawLine( Point( nRect.Left(), nRect.Bottom() ),
+ Point( nRect.Right(), nRect.Top() ) );
+ for ( long i=1; i<n8; ++i )
+ {
+ pDev->DrawLine( Point( nRect.Left()+i, nRect.Top() ),
+ Point( nRect.Right(), nRect.Bottom()-i ) );
+ pDev->DrawLine( Point( nRect.Left(), nRect.Top()+i ),
+ Point( nRect.Right()-i, nRect.Bottom() ) );
+ pDev->DrawLine( Point( nRect.Left()+i, nRect.Bottom() ),
+ Point( nRect.Right(), nRect.Top()+i ) );
+ pDev->DrawLine( Point( nRect.Left(), nRect.Bottom()-i ),
+ Point( nRect.Right()-i, nRect.Top() ) );
+ }
+ break;
+
+ case SymbolType::ROLLDOWN:
+ pDev->DrawLine( Point( nRect.Left(), nRect.Top() ),
+ Point( nRect.Left(), nRect.Bottom() ) );
+ pDev->DrawLine( Point( nRect.Right(), nRect.Top() ),
+ Point( nRect.Right(), nRect.Bottom() ) );
+ pDev->DrawLine( Point( nRect.Left(), nRect.Bottom() ),
+ Point( nRect.Right(), nRect.Bottom() ) );
+ [[fallthrough]];
+ case SymbolType::ROLLUP:
+ pDev->DrawRect( tools::Rectangle( nRect.Left(), nRect.Top(),
+ nRect.Right(), nRect.Top()+n8 ) );
+ break;
+
+ case SymbolType::CHECKMARK:
+ {
+ long n3 = nSide/3;
+ nRect.AdjustTop( -(n3/2) );
+ nRect.AdjustBottom( -(n3/2) );
+ // #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!)
+ pDev->DrawLine( Point( nRect.Right(), nRect.Bottom()-n3 ),
+ Point( nRect.Right()-n3, nRect.Bottom() ) );
+ pDev->DrawLine( Point( nRect.Right()-n3, nRect.Bottom() ),
+ Point( nRect.Left(), nRect.Top()+n3 ) );
+ nRect.AdjustTop( 1 );
+ nRect.AdjustBottom( 1 );
+ pDev->DrawLine( Point( nRect.Right(), nRect.Bottom()-n3 ),
+ Point( nRect.Right()-n3, nRect.Bottom() ) );
+ pDev->DrawLine( Point( nRect.Right()-n3, nRect.Bottom() ),
+ Point( nRect.Left(), nRect.Top()+n3 ) );
+ }
+ else
+ {
+ pDev->DrawLine( Point( nRect.Left(), nRect.Bottom()-n3 ),
+ Point( nRect.Left()+n3, nRect.Bottom() ) );
+ pDev->DrawLine( Point( nRect.Left()+n3, nRect.Bottom() ),
+ Point( nRect.Right(), nRect.Top()+n3 ) );
+ nRect.AdjustTop( 1 );
+ nRect.AdjustBottom( 1 );
+ pDev->DrawLine( Point( nRect.Left(), nRect.Bottom()-n3 ),
+ Point( nRect.Left()+n3, nRect.Bottom() ) );
+ pDev->DrawLine( Point( nRect.Left()+n3, nRect.Bottom() ),
+ Point( nRect.Right(), nRect.Top()+n3 ) );
+ }
+ }
+ 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 )
+{
+ long nLineWidth = pDev->GetDPIX()/300;
+ 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 long nWidth = rRect.GetWidth();
+ const 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( 1, 1 );
+ if ( pDev->GetOutDevType() == OUTDEV_PRINTER )
+ {
+ aBrdSize = pDev->LogicToPixel( Size( 20, 20 ), MapMode(MapUnit::Map100thMM) );
+ if ( !aBrdSize.Width() )
+ aBrdSize.setWidth( 1 );
+ if ( !aBrdSize.Height() )
+ aBrdSize.setHeight( 1 );
+ }
+
+ 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
+ if ( pDev->GetOutDevType() == OUTDEV_PRINTER )
+ pDev->SetFillColor( COL_LIGHTGRAY );
+ else
+ pDev->SetFillColor( COL_WHITE );
+ pDev->DrawRect( aFillRect );
+ }
+ else
+ {
+ if ( nStyle & DrawButtonFlags::Default )
+ {
+ const Color aDefBtnColor = rStyleSettings.GetDarkShadowColor();
+ ImplDrawDPILineRect( pDev, aFillRect, &aDefBtnColor );
+ }
+
+ if ( nStyle & DrawButtonFlags::NoLeftLightBorder )
+ {
+ pDev->SetLineColor( rStyleSettings.GetLightBorderColor() );
+ pDev->DrawLine( Point( aFillRect.Left(), aFillRect.Top() ),
+ Point( aFillRect.Left(), aFillRect.Bottom() ) );
+ aFillRect.AdjustLeft( 1 );
+ }
+
+ Color aColor1;
+ Color aColor2;
+ if ( nStyle & (DrawButtonFlags::Pressed | DrawButtonFlags::Checked) )
+ {
+ aColor1 = rStyleSettings.GetDarkShadowColor();
+ aColor2 = rStyleSettings.GetLightColor();
+ }
+ else
+ {
+ if ( nStyle & DrawButtonFlags::NoLightBorder )
+ aColor1 = rStyleSettings.GetLightBorderColor();
+ else
+ aColor1 = rStyleSettings.GetLightColor();
+ if ( (nStyle & BUTTON_DRAW_FLATTEST) == DrawButtonFlags::Flat )
+ aColor2 = rStyleSettings.GetShadowColor();
+ else
+ aColor2 = rStyleSettings.GetDarkShadowColor();
+ }
+
+ ImplDraw2ColorFrame( pDev, aFillRect, aColor1, aColor2 );
+
+ if ( (nStyle & BUTTON_DRAW_FLATTEST) != DrawButtonFlags::Flat )
+ {
+ if ( nStyle & (DrawButtonFlags::Pressed | DrawButtonFlags::Checked) )
+ {
+ aColor1 = rStyleSettings.GetShadowColor();
+ aColor2 = rStyleSettings.GetLightBorderColor();
+ }
+ else
+ {
+ if ( nStyle & DrawButtonFlags::NoLightBorder )
+ aColor1 = rStyleSettings.GetLightColor();
+ else
+ aColor1 = rStyleSettings.GetLightBorderColor();
+ aColor2 = rStyleSettings.GetShadowColor();
+ }
+ ImplDraw2ColorFrame( pDev, aFillRect, aColor1, aColor2 );
+ }
+
+ pDev->SetLineColor();
+ if ( nStyle & (DrawButtonFlags::Checked | DrawButtonFlags::DontKnow) )
+ pDev->SetFillColor( rStyleSettings.GetCheckedColor() );
+ else
+ pDev->SetFillColor( rStyleSettings.GetFaceColor() );
+ pDev->DrawRect( aFillRect );
+ }
+}
+
+void ImplDrawFrame( OutputDevice *const pDev, tools::Rectangle& rRect,
+ const StyleSettings& rStyleSettings, DrawFrameStyle nStyle, DrawFrameFlags nFlags )
+{
+ vcl::Window *const pWin = (pDev->GetOutDevType()==OUTDEV_WINDOW) ? static_cast<vcl::Window*>(pDev) : nullptr;
+
+ 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) )
+ {
+ long nControlFlags = static_cast<long>(nStyle);
+ nControlFlags |= static_cast<long>(nFlags);
+ nControlFlags |= static_cast<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->DrawNativeControl( ControlType::Frame, ControlPart::Border, aContent, ControlState::ENABLED,
+ aControlValue, OUString()) )
+ {
+ rRect = aContent;
+ return;
+ }
+ }
+ }
+
+ if ( nFlags & DrawFrameFlags::Mono )
+ {
+ // no round corners for window frame borders
+ const bool bRound = bFlatBorders && !(nFlags & DrawFrameFlags::WindowBorder);
+
+ if ( bNoDraw )
+ {
+ ImplDrawDPILineRect( pDev, rRect, nullptr, bRound );
+ }
+ else
+ {
+ Color aColor = bRound ? rStyleSettings.GetShadowColor()
+ : pDev->GetSettings().GetStyleSettings().GetMonoColor();
+ // when the MonoColor wasn't set, check face color
+ if (
+ (bRound && aColor.IsDark()) ||
+ (
+ (aColor == COL_BLACK) &&
+ pDev->GetSettings().GetStyleSettings().GetFaceColor().IsDark()
+ )
+ )
+ {
+ aColor = COL_WHITE;
+ }
+ ImplDrawDPILineRect( pDev, rRect, &aColor, bRound );
+ }
+ }
+ else
+ {
+ if ( bNoDraw )
+ {
+ switch ( nStyle )
+ {
+ case DrawFrameStyle::In:
+ case DrawFrameStyle::Out:
+ rRect.AdjustLeft( 1 );
+ rRect.AdjustTop( 1 );
+ rRect.AdjustRight( -1 );
+ rRect.AdjustBottom( -1 );
+ break;
+
+ case DrawFrameStyle::Group:
+ case DrawFrameStyle::DoubleIn:
+ case DrawFrameStyle::DoubleOut:
+ rRect.AdjustLeft(2 );
+ rRect.AdjustTop(2 );
+ rRect.AdjustRight( -2 );
+ rRect.AdjustBottom( -2 );
+ break;
+
+ case DrawFrameStyle::NWF:
+ // enough space for the native rendering
+ rRect.AdjustLeft(4 );
+ rRect.AdjustTop(4 );
+ rRect.AdjustRight( -4 );
+ rRect.AdjustBottom( -4 );
+ break;
+ default: break;
+ }
+ }
+ else
+ {
+ switch ( nStyle )
+ {
+ case DrawFrameStyle::Group:
+ pDev->SetFillColor();
+ pDev->SetLineColor( rStyleSettings.GetLightColor() );
+ pDev->DrawRect( tools::Rectangle( rRect.Left()+1, rRect.Top()+1,
+ rRect.Right(), rRect.Bottom() ) );
+ pDev->SetLineColor( rStyleSettings.GetShadowColor() );
+ pDev->DrawRect( tools::Rectangle( rRect.Left(), rRect.Top(),
+ rRect.Right()-1, rRect.Bottom()-1 ) );
+
+ // adjust target rectangle
+ rRect.AdjustLeft(2 );
+ rRect.AdjustTop(2 );
+ rRect.AdjustRight( -2 );
+ rRect.AdjustBottom( -2 );
+ break;
+
+ case DrawFrameStyle::In:
+ ImplDraw2ColorFrame( pDev, rRect,
+ rStyleSettings.GetShadowColor(),
+ rStyleSettings.GetLightColor() );
+ break;
+
+ case DrawFrameStyle::Out:
+ ImplDraw2ColorFrame( pDev, rRect,
+ rStyleSettings.GetLightColor(),
+ rStyleSettings.GetShadowColor() );
+ break;
+
+ case DrawFrameStyle::DoubleIn:
+ if( bFlatBorders )
+ {
+ // no 3d effect
+ ImplDraw2ColorFrame( pDev, rRect,
+ rStyleSettings.GetShadowColor(),
+ rStyleSettings.GetShadowColor() );
+ ImplDraw2ColorFrame( pDev, rRect,
+ rStyleSettings.GetFaceColor(),
+ rStyleSettings.GetFaceColor() );
+ }
+ else
+ {
+ ImplDraw2ColorFrame( pDev, rRect,
+ rStyleSettings.GetShadowColor(),
+ rStyleSettings.GetLightColor() );
+ ImplDraw2ColorFrame( pDev, rRect,
+ rStyleSettings.GetDarkShadowColor(),
+ rStyleSettings.GetLightBorderColor() );
+ }
+ break;
+
+ case DrawFrameStyle::DoubleOut:
+ if( bMenuStyle )
+ {
+ ImplDraw2ColorFrame( pDev, rRect,
+ rStyleSettings.GetMenuBorderColor(),
+ rStyleSettings.GetDarkShadowColor() );
+ if ( !rStyleSettings.GetUseFlatMenus() )
+ {
+ ImplDraw2ColorFrame( pDev, rRect,
+ rStyleSettings.GetLightColor(),
+ rStyleSettings.GetShadowColor() );
+ }
+ }
+ else
+ {
+ ImplDraw2ColorFrame( pDev, rRect,
+ bFlatBorders ? // no 3d effect
+ rStyleSettings.GetDarkShadowColor() :
+ rStyleSettings.GetLightBorderColor(),
+ rStyleSettings.GetDarkShadowColor() );
+ ImplDraw2ColorFrame( pDev, rRect,
+ rStyleSettings.GetLightColor(),
+ rStyleSettings.GetShadowColor() );
+ }
+ break;
+
+ case DrawFrameStyle::NWF:
+ // no rendering, just enough space for the native rendering
+ rRect.AdjustLeft(4 );
+ rRect.AdjustTop(4 );
+ rRect.AdjustRight( -4 );
+ rRect.AdjustBottom( -4 );
+ break;
+ default: break;
+ }
+ }
+ }
+}
+
+} // end anonymous namespace
+
+DecorationView::DecorationView(OutputDevice* pOutDev) :
+ mpOutDev(pOutDev)
+{}
+
+void DecorationView::DrawSymbol( const tools::Rectangle& rRect, SymbolType eType,
+ const Color& rColor, DrawSymbolFlags nStyle )
+{
+ const StyleSettings& rStyleSettings = mpOutDev->GetSettings().GetStyleSettings();
+ const tools::Rectangle aRect = mpOutDev->LogicToPixel( rRect );
+ const Color aOldLineColor = mpOutDev->GetLineColor();
+ const Color aOldFillColor = mpOutDev->GetFillColor();
+ const bool bOldMapMode = mpOutDev->IsMapModeEnabled();
+ Color nColor(rColor);
+ mpOutDev->EnableMapMode( false );
+
+ if ( (rStyleSettings.GetOptions() & StyleSettingsOptions::Mono) ||
+ (mpOutDev->GetOutDevType() == OUTDEV_PRINTER) )
+ nStyle |= DrawSymbolFlags::Mono;
+
+ if ( nStyle & DrawSymbolFlags::Mono )
+ {
+ // Monochrome: set color to black if enabled, to gray if disabled
+ nColor = ( nStyle & DrawSymbolFlags::Disable ) ? COL_GRAY : COL_BLACK;
+ }
+ else
+ {
+ if ( nStyle & DrawSymbolFlags::Disable )
+ {
+ // Draw shifted and brighter symbol for embossed look
+ mpOutDev->SetLineColor( rStyleSettings.GetLightColor() );
+ mpOutDev->SetFillColor( rStyleSettings.GetLightColor() );
+ ImplDrawSymbol( mpOutDev, aRect + Point(1, 1) , eType );
+ nColor = rStyleSettings.GetShadowColor();
+ }
+ }
+
+ // Set selected color and draw the symbol
+ mpOutDev->SetLineColor( nColor );
+ mpOutDev->SetFillColor( nColor );
+ ImplDrawSymbol( mpOutDev, aRect, eType );
+
+ // Restore previous settings
+ mpOutDev->SetLineColor( aOldLineColor );
+ mpOutDev->SetFillColor( aOldFillColor );
+ mpOutDev->EnableMapMode( bOldMapMode );
+}
+
+void DecorationView::DrawFrame( const tools::Rectangle& rRect,
+ const Color& rLeftTopColor,
+ const Color& rRightBottomColor )
+{
+ tools::Rectangle aRect = mpOutDev->LogicToPixel( rRect );
+ const Color aOldLineColor = mpOutDev->GetLineColor();
+ const bool bOldMapMode = mpOutDev->IsMapModeEnabled();
+ mpOutDev->EnableMapMode( false );
+ ImplDraw2ColorFrame( mpOutDev, aRect, rLeftTopColor, rRightBottomColor );
+ mpOutDev->SetLineColor( aOldLineColor );
+ mpOutDev->EnableMapMode( bOldMapMode );
+}
+
+void DecorationView::DrawHighlightFrame( const tools::Rectangle& rRect,
+ DrawHighlightFrameStyle nStyle )
+{
+ const StyleSettings& rStyleSettings = mpOutDev->GetSettings().GetStyleSettings();
+ Color aLightColor = rStyleSettings.GetLightColor();
+ Color aShadowColor = rStyleSettings.GetShadowColor();
+
+ if ( (rStyleSettings.GetOptions() & StyleSettingsOptions::Mono) ||
+ (mpOutDev->GetOutDevType() == OUTDEV_PRINTER) )
+ {
+ aLightColor = COL_BLACK;
+ aShadowColor = COL_BLACK;
+ }
+ else
+ {
+ Wallpaper aBackground = mpOutDev->GetBackground();
+ if ( aBackground.IsBitmap() || aBackground.IsGradient() )
+ {
+ aLightColor = rStyleSettings.GetFaceColor();
+ aShadowColor = COL_BLACK;
+ }
+ else
+ {
+ Color aBackColor = aBackground.GetColor();
+ if ( (aLightColor.GetColorError( aBackColor ) < 96) ||
+ (aShadowColor.GetColorError( aBackColor ) < 96) )
+ {
+ aLightColor = COL_WHITE;
+ aShadowColor = COL_BLACK;
+
+ if ( aLightColor.GetColorError( aBackColor ) < 96 )
+ aLightColor.DecreaseLuminance( 64 );
+ if ( aShadowColor.GetColorError( aBackColor ) < 96 )
+ aShadowColor.IncreaseLuminance( 64 );
+ }
+ }
+ }
+
+ if ( nStyle == DrawHighlightFrameStyle::In )
+ {
+ Color aTempColor = aLightColor;
+ aLightColor = aShadowColor;
+ aShadowColor = aTempColor;
+ }
+
+ DrawFrame( rRect, aLightColor, aShadowColor );
+}
+
+tools::Rectangle DecorationView::DrawFrame( const tools::Rectangle& rRect, DrawFrameStyle nStyle, DrawFrameFlags nFlags )
+{
+ tools::Rectangle aRect = rRect;
+ bool bOldMap = mpOutDev->IsMapModeEnabled();
+ if ( bOldMap )
+ {
+ aRect = mpOutDev->LogicToPixel( aRect );
+ mpOutDev->EnableMapMode( false );
+ }
+
+ if ( !rRect.IsEmpty() )
+ {
+ if ( nFlags & DrawFrameFlags::NoDraw )
+ ImplDrawFrame( mpOutDev, aRect, mpOutDev->GetSettings().GetStyleSettings(), nStyle, nFlags );
+ else
+ {
+ Color aOldLineColor = mpOutDev->GetLineColor();
+ Color aOldFillColor = mpOutDev->GetFillColor();
+ ImplDrawFrame( mpOutDev, aRect, mpOutDev->GetSettings().GetStyleSettings(), nStyle, nFlags );
+ mpOutDev->SetLineColor( aOldLineColor );
+ mpOutDev->SetFillColor( aOldFillColor );
+ }
+ }
+
+ if ( bOldMap )
+ {
+ mpOutDev->EnableMapMode( bOldMap );
+ aRect = mpOutDev->PixelToLogic( aRect );
+ }
+
+ return aRect;
+}
+
+tools::Rectangle DecorationView::DrawButton( const tools::Rectangle& rRect, DrawButtonFlags nStyle )
+{
+ if ( rRect.IsEmpty() )
+ {
+ return rRect;
+ }
+
+ tools::Rectangle aRect = rRect;
+ const bool bOldMap = mpOutDev->IsMapModeEnabled();
+
+ if ( bOldMap )
+ {
+ aRect = mpOutDev->LogicToPixel( aRect );
+ mpOutDev->EnableMapMode( false );
+ }
+
+ const Color aOldLineColor = mpOutDev->GetLineColor();
+ const Color aOldFillColor = mpOutDev->GetFillColor();
+ ImplDrawButton( mpOutDev, aRect, nStyle );
+ mpOutDev->SetLineColor( aOldLineColor );
+ mpOutDev->SetFillColor( aOldFillColor );
+
+ // keep border free, although it is used at default representation
+ aRect.AdjustLeft( 1 );
+ aRect.AdjustTop( 1 );
+ aRect.AdjustRight( -1 );
+ aRect.AdjustBottom( -1 );
+
+ if ( nStyle & DrawButtonFlags::NoLightBorder )
+ {
+ aRect.AdjustLeft( 1 );
+ aRect.AdjustTop( 1 );
+ }
+ else if ( nStyle & DrawButtonFlags::NoLeftLightBorder )
+ {
+ aRect.AdjustLeft( 1 );
+ }
+
+ if ( nStyle & DrawButtonFlags::Pressed )
+ {
+ if ( (aRect.GetHeight() > 10) && (aRect.GetWidth() > 10) )
+ {
+ aRect.AdjustLeft(4 );
+ aRect.AdjustTop(4 );
+ aRect.AdjustRight( -1 );
+ aRect.AdjustBottom( -1 );
+ }
+ else
+ {
+ aRect.AdjustLeft(3 );
+ aRect.AdjustTop(3 );
+ aRect.AdjustRight( -2 );
+ aRect.AdjustBottom( -2 );
+ }
+ }
+ else if ( nStyle & DrawButtonFlags::Checked )
+ {
+ aRect.AdjustLeft(3 );
+ aRect.AdjustTop(3 );
+ aRect.AdjustRight( -2 );
+ aRect.AdjustBottom( -2 );
+ }
+ else
+ {
+ aRect.AdjustLeft(2 );
+ aRect.AdjustTop(2 );
+ aRect.AdjustRight( -3 );
+ aRect.AdjustBottom( -3 );
+ }
+
+ if ( bOldMap )
+ {
+ mpOutDev->EnableMapMode( bOldMap );
+ aRect = mpOutDev->PixelToLogic( aRect );
+ }
+
+ return aRect;
+}
+
+void DecorationView::DrawSeparator( const Point& rStart, const Point& rStop, bool bVertical )
+{
+ Point aStart( rStart ), aStop( rStop );
+ const StyleSettings& rStyleSettings = mpOutDev->GetSettings().GetStyleSettings();
+ vcl::Window *const pWin = (mpOutDev->GetOutDevType()==OUTDEV_WINDOW) ? static_cast<vcl::Window*>(mpOutDev.get()) : nullptr;
+ 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->DrawNativeControl(ControlType::Fixedline,nPart,aRect,ControlState::NONE,aValue,OUString()))
+ return;
+ }
+
+ mpOutDev->Push( PushFlags::LINECOLOR );
+ if ( rStyleSettings.GetOptions() & StyleSettingsOptions::Mono )
+ mpOutDev->SetLineColor( COL_BLACK );
+ else
+ mpOutDev->SetLineColor( rStyleSettings.GetShadowColor() );
+
+ mpOutDev->DrawLine( aStart, aStop );
+ if ( !(rStyleSettings.GetOptions() & StyleSettingsOptions::Mono) )
+ {
+ mpOutDev->SetLineColor( rStyleSettings.GetLightColor() );
+ if( bVertical )
+ {
+ aStart.AdjustX( 1 );
+ aStop.AdjustX( 1 );
+ }
+ else
+ {
+ aStart.AdjustY( 1 );
+ aStop.AdjustY( 1 );
+ }
+ mpOutDev->DrawLine( aStart, aStop );
+ }
+ mpOutDev->Pop();
+}
+
+void DecorationView::DrawHandle(const tools::Rectangle& rRect)
+{
+ const StyleSettings& rStyleSettings = mpOutDev->GetSettings().GetStyleSettings();
+
+ Size aOutputSize = rRect.GetSize();
+
+ mpOutDev->SetLineColor(rStyleSettings.GetDarkShadowColor());
+ mpOutDev->SetFillColor(rStyleSettings.GetDarkShadowColor());
+
+ const sal_Int32 nNumberOfPoints = 3;
+
+ long nHalfWidth = aOutputSize.Width() / 2.0f;
+
+ float fDistance = aOutputSize.Height();
+ fDistance /= (nNumberOfPoints + 1);
+
+ long nRadius = aOutputSize.Width();
+ nRadius /= (nNumberOfPoints + 2);
+
+ for (long i = 1; i <= nNumberOfPoints; i++)
+ {
+ tools::Rectangle aLocation(nHalfWidth - nRadius,
+ round(fDistance * i) - nRadius,
+ nHalfWidth + nRadius,
+ round(fDistance * i) + nRadius);
+ mpOutDev->DrawEllipse(aLocation);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/dialog.cxx b/vcl/source/window/dialog.cxx
new file mode 100644
index 000000000..d6d00cad2
--- /dev/null
+++ b/vcl/source/window/dialog.cxx
@@ -0,0 +1,1617 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <brdwin.hxx>
+
+#include <rtl/bootstrap.hxx>
+#include <rtl/strbuf.hxx>
+#include <sal/log.hxx>
+
+#include <vcl/abstdlg.hxx>
+#include <vcl/accel.hxx>
+#include <vcl/builder.hxx>
+#include <vcl/layout.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/event.hxx>
+#include <vcl/waitobj.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 <iostream>
+#include <utility>
+
+static OString ImplGetDialogText( Dialog* pDialog )
+{
+ OStringBuffer aErrorStr(OUStringToOString(
+ pDialog->GetText(), RTL_TEXTENCODING_UTF8));
+
+ OUString sMessage;
+ if (MessageDialog* pMessDialog = dynamic_cast<MessageDialog*>(pDialog))
+ {
+ sMessage = pMessDialog->get_primary_text();
+ }
+
+ if (!sMessage.isEmpty())
+ {
+ aErrorStr.append(", ");
+ aErrorStr.append(OUStringToOString(
+ sMessage, RTL_TEXTENCODING_UTF8));
+ }
+ return aErrorStr.makeStringAndClear();
+}
+
+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 Accelerator::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;
+ long mnResult;
+ bool mbStartedModal;
+ VclAbstractDialog::AsyncContext maEndCtx;
+ Link<const CommandEvent&, bool> m_aPopupMenuHdl;
+ Link<void*, vcl::ILibreOfficeKitNotifier*> m_aInstallLOKNotifierHdl;
+
+ DialogImpl() : mnResult( -1 ), mbStartedModal( false ) {}
+
+#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);
+}
+
+vcl::Window* Dialog::GetDefaultParent(WinBits nStyle)
+{
+ vcl::Window* pParent = Application::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()->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;
+ nStyle |= WB_ROLLABLE;
+
+ // 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_ROLLABLE | 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 )
+ , mnInitFlag(InitFlag::Default)
+{
+ ImplInitDialogData();
+}
+
+void VclBuilderContainer::disposeBuilder()
+{
+ if (m_pUIBuilder)
+ m_pUIBuilder->disposeBuilder();
+}
+
+OUString VclBuilderContainer::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)
+ , mnInitFlag(InitFlag::Default)
+{
+ ImplLOKNotifier(pParent);
+ ImplInitDialogData();
+ loadUI(pParent, OUStringToOString(rID, RTL_TEXTENCODING_UTF8), rUIXMLDescription);
+}
+
+Dialog::Dialog(vcl::Window* pParent, WinBits nStyle, InitFlag eFlag)
+ : SystemWindow(WindowType::DIALOG)
+ , 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()
+{
+ 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("Close Dialog");
+
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ if(const vcl::ILibreOfficeKitNotifier* pNotifier = GetLOKNotifier())
+ {
+ pNotifier->notifyWindow(GetLOKWindowId(), "close");
+ ReleaseLOKNotifier();
+ }
+ }
+
+ SystemWindow::dispose();
+}
+
+IMPL_LINK_NOARG(Dialog, ImplAsyncCloseHdl, void*, void)
+{
+ Close();
+}
+
+bool Dialog::EventNotify( NotifyEvent& rNEvt )
+{
+ // first call the base class due to Tab control
+ bool bRet = SystemWindow::EventNotify( rNEvt );
+ if ( !bRet )
+ {
+ if ( rNEvt.GetType() == MouseNotifyEvent::KEYINPUT )
+ {
+ const KeyEvent* pKEvt = rNEvt.GetKeyEvent();
+ vcl::KeyCode aKeyCode = pKEvt->GetKeyCode();
+ sal_uInt16 nKeyCode = aKeyCode.GetCode();
+
+ if ( (nKeyCode == KEY_ESCAPE) &&
+ ((GetStyle() & WB_CLOSEABLE) || ImplGetCancelButton( this ) || ImplGetOKButton( this )) )
+ {
+ // #i89505# for the benefit of slightly mentally challenged implementations
+ // like e.g. SfxModelessDialog which destroy themselves inside Close()
+ // post this Close asynchronous so we can leave our key handler before
+ // we get destroyed
+ PostUserEvent( LINK( this, Dialog, ImplAsyncCloseHdl ), nullptr, true);
+ return true;
+ }
+ }
+ else if ( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS )
+ {
+ // make sure the dialog is still modal
+ // changing focus between application frames may
+ // have re-enabled input for our parent
+ if( mbInExecute && mbModalMode )
+ {
+ ImplSetModalInputMode( false );
+ ImplSetModalInputMode( true );
+
+ // #93022# def-button might have changed after show
+ if( !mnMousePositioned )
+ {
+ mnMousePositioned = 1;
+ ImplMouseAutoPos( this );
+ }
+
+ }
+ }
+ }
+
+ return bRet;
+}
+
+//What we really want here is something that gives the available width and
+//height of a users screen, taking away the space taken up the OS
+//taskbar, menus, etc.
+Size bestmaxFrameSizeForScreenSize(const Size &rScreenSize)
+{
+#ifndef IOS
+ long w = rScreenSize.Width();
+ if (w <= 800)
+ w -= 15;
+ else if (w <= 1024)
+ w -= 65;
+ else
+ w -= 115;
+
+ long h = rScreenSize.Height();
+ if (h <= 768)
+ h -= 50;
+ else
+ h -= 100;
+
+ return Size(std::max<long>(w, 640 - 15),
+ std::max<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::StateChanged( StateChangedType nType )
+{
+ if (nType == StateChangedType::InitShow)
+ {
+ DoInitialLayout();
+
+ const bool bKitActive = comphelper::LibreOfficeKit::isActive();
+ if (bKitActive)
+ {
+ std::vector<vcl::LOKPayloadItem> aItems;
+ aItems.emplace_back("type", "dialog");
+ aItems.emplace_back("size", GetSizePixel().toString());
+ if (!GetText().isEmpty())
+ aItems.emplace_back("title", GetText().toUtf8());
+
+ if (const vcl::ILibreOfficeKitNotifier* pNotifier = GetLOKNotifier())
+ {
+ pNotifier->notifyWindow(GetLOKWindowId(), "created", aItems);
+ pNotifier->notifyWindow(GetLOKWindowId(), "created", aItems);
+ }
+ else
+ {
+ vcl::ILibreOfficeKitNotifier* pViewShell = mpDialogImpl->m_aInstallLOKNotifierHdl.Call(nullptr);
+ if (pViewShell)
+ {
+ SetLOKNotifier(pViewShell);
+ pViewShell->notifyWindow(GetLOKWindowId(), "created", aItems);
+ }
+ }
+ }
+
+ if ( !HasChildPathFocus() || HasFocus() )
+ GrabFocusToFirstControl();
+ if ( !(GetStyle() & WB_CLOSEABLE) )
+ {
+ if ( ImplGetCancelButton( this ) || ImplGetOKButton( this ) )
+ {
+ if ( ImplGetBorderWindow() )
+ static_cast<ImplBorderWindow*>(ImplGetBorderWindow())->SetCloseButton();
+ }
+ }
+
+ ImplMouseAutoPos( this );
+ }
+ else if (nType == StateChangedType::Text)
+ {
+ if (const vcl::ILibreOfficeKitNotifier* pNotifier = GetLOKNotifier())
+ {
+ 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)
+ {
+ if (const vcl::ILibreOfficeKitNotifier* pNotifier = GetLOKNotifier())
+ {
+ std::vector<vcl::LOKPayloadItem> aPayload;
+ aPayload.emplace_back("title", GetText().toUtf8());
+ pNotifier->notifyWindow(GetLOKWindowId(), IsVisible()? OUString("show"): OUString("hide"), aPayload);
+ }
+ }
+}
+
+void Dialog::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ SystemWindow::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) )
+ {
+ ImplInitSettings();
+ Invalidate();
+ }
+}
+
+bool Dialog::Close()
+{
+ VclPtr<vcl::Window> xWindow = this;
+ CallEventListeners( VclEventId::WindowClose );
+ if ( xWindow->IsDisposed() )
+ return false;
+
+ if ( mpWindowImpl->mxWindowPeer.is() && IsCreatedWithToolkit() && !IsInExecute() )
+ return false;
+
+ // If there's a cancel button with a custom handler, then always give it a chance to
+ // handle Dialog::Close
+ PushButton* pCustomCancelButton;
+ PushButton* pCancelButton = dynamic_cast<PushButton*>(get_widget_for_response(RET_CANCEL));
+ if (!mbInClose && pCancelButton && pCancelButton->GetClickHdl().IsSet())
+ pCustomCancelButton = pCancelButton;
+ else
+ pCustomCancelButton = nullptr;
+
+ mbInClose = true;
+
+ if (pCustomCancelButton)
+ {
+ pCustomCancelButton->Click();
+ if (xWindow->IsDisposed())
+ return true;
+ mbInClose = false;
+ return false;
+ }
+
+ if ( !(GetStyle() & WB_CLOSEABLE) )
+ {
+ bool bRet = true;
+ PushButton* pButton = ImplGetCancelButton( this );
+ if ( pButton )
+ pButton->Click();
+ else
+ {
+ pButton = ImplGetOKButton( this );
+ if ( pButton )
+ pButton->Click();
+ else
+ bRet = false;
+ }
+ if ( xWindow->IsDisposed() )
+ return true;
+ return bRet;
+ }
+
+ if (IsInExecute() || mpDialogImpl->maEndCtx.isSet())
+ {
+ EndDialog();
+ mbInClose = false;
+ return true;
+ }
+ else
+ {
+ mbInClose = false;
+ return SystemWindow::Close();
+ }
+}
+
+bool Dialog::ImplStartExecute()
+{
+ setDeferredProperties();
+
+ if (IsInExecute() || mpDialogImpl->maEndCtx.isSet())
+ {
+#ifdef DBG_UTIL
+ SAL_WARN( "vcl", "Dialog::StartExecuteModal() is called in Dialog::StartExecuteModal(): "
+ << ImplGetDialogText(this) );
+#endif
+ return false;
+ }
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ const bool bKitActive = comphelper::LibreOfficeKit::isActive();
+
+ const bool bModal = GetType() != WindowType::MODELESSDIALOG;
+
+ if (bModal)
+ {
+ if (bKitActive && !GetLOKNotifier())
+ SetLOKNotifier(mpDialogImpl->m_aInstallLOKNotifierHdl.Call(nullptr));
+
+ switch ( Application::GetDialogCancelMode() )
+ {
+ case DialogCancelMode::Off:
+ break;
+ case DialogCancelMode::Silent:
+ if (bModal && GetLOKNotifier())
+ {
+ // check if there's already some dialog being ::Execute()d
+ const bool bDialogExecuting = std::any_of(pSVData->mpWinData->mpExecuteDialogs.begin(),
+ pSVData->mpWinData->mpExecuteDialogs.end(),
+ [](const Dialog* pDialog) {
+ return pDialog->IsInSyncExecute();
+ });
+ if (!(bDialogExecuting && IsInSyncExecute()))
+ break;
+ else
+ SAL_WARN("lok.dialog", "Dialog \"" << ImplGetDialogText(this) << "\" is being synchronously executed over an existing synchronously executing dialog.");
+ }
+
+ SAL_INFO(
+ "vcl",
+ "Dialog \"" << ImplGetDialogText(this)
+ << "\"cancelled in silent mode");
+ return false;
+ default: // default cannot happen
+ case DialogCancelMode::Fatal:
+ std::abort();
+ }
+
+#ifdef DBG_UTIL
+ vcl::Window* pParent = GetParent();
+ if ( pParent )
+ {
+ pParent = pParent->ImplGetFirstOverlapWindow();
+ 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(xContext));
+ 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("Open Modal " + get_id());
+ else
+ UITestLogger::getInstance().log("Open Modeless " + get_id());
+
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ if (const vcl::ILibreOfficeKitNotifier* pNotifier = GetLOKNotifier())
+ {
+ // Dialog boxes don't get the Resize call and they
+ // can have invalid size at 'created' message above.
+ // If there is no difference, the client should detect it and ignore us,
+ // otherwise, this should make sure that the window has the correct size.
+ std::vector<vcl::LOKPayloadItem> aItems;
+ aItems.emplace_back("size", GetSizePixel().toString());
+ pNotifier->notifyWindow(GetLOKWindowId(), "size_changed", aItems);
+ }
+ }
+
+ return true;
+}
+
+void Dialog::ImplEndExecuteModal()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maAppData.mnModalMode--;
+}
+
+short Dialog::Execute()
+{
+// Once the Android app is based on same idea as the iOS one currently
+// being developed, no conditional should be needed here. Until then,
+// play it safe.
+#if HAVE_FEATURE_DESKTOP || defined IOS
+ 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::Yield();
+
+ ImplEndExecuteModal();
+#ifdef DBG_UTIL
+ assert (!mpDialogParent || !mpDialogParent->IsDisposed());
+#endif
+ if ( !xWindow->IsDisposed() )
+ xWindow.clear();
+ else
+ {
+ OSL_FAIL( "Dialog::Execute() - Dialog destroyed in Execute()" );
+ }
+
+ long nRet = mpDialogImpl->mnResult;
+ mpDialogImpl->mnResult = -1;
+
+ return static_cast<short>(nRet);
+
+#else
+ return RET_OK;
+#endif
+}
+
+// virtual
+bool Dialog::StartExecuteAsync( VclAbstractDialog::AsyncContext &rCtx )
+{
+ const bool bModal = GetType() != WindowType::MODELESSDIALOG;
+ if (!ImplStartExecute())
+ {
+ rCtx.mxOwner.disposeAndClear();
+ rCtx.mxOwnerDialogController.reset();
+ rCtx.mxOwnerSelf.reset();
+ return false;
+ }
+
+ mpDialogImpl->maEndCtx = rCtx;
+ mpDialogImpl->mbStartedModal = bModal;
+
+ return true;
+}
+
+void Dialog::RemoveFromDlgList()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ auto& rExecuteDialogs = pSVData->mpWinData->mpExecuteDialogs;
+
+ // remove dialog from the list of dialogs which are being executed
+ rExecuteDialogs.erase(std::remove_if(rExecuteDialogs.begin(), rExecuteDialogs.end(), [this](VclPtr<Dialog>& dialog){ return dialog.get() == this; }), rExecuteDialogs.end());
+}
+
+void Dialog::EndDialog( long nResult )
+{
+ if (!mbInExecute || IsDisposed())
+ return;
+
+ const bool bModal = GetType() != WindowType::MODELESSDIALOG;
+
+ Hide();
+
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ if(const vcl::ILibreOfficeKitNotifier* pNotifier = GetLOKNotifier())
+ {
+ pNotifier->notifyWindow(GetLOKWindowId(), "close");
+ ReleaseLOKNotifier();
+ }
+ }
+
+ if (bModal)
+ {
+ SetModalInputMode(false);
+
+ RemoveFromDlgList();
+
+ // set focus to previous modal dialog if it is modal for
+ // the same frame parent (or NULL)
+ ImplSVData* pSVData = ImplGetSVData();
+ if (!pSVData->mpWinData->mpExecuteDialogs.empty())
+ {
+ VclPtr<Dialog> pPrevious = pSVData->mpWinData->mpExecuteDialogs.back();
+
+ vcl::Window* pFrameParent = ImplGetFrameWindow()->ImplGetParent();
+ vcl::Window* pPrevFrameParent = pPrevious->ImplGetFrameWindow()? pPrevious->ImplGetFrameWindow()->ImplGetParent(): nullptr;
+ if( ( !pFrameParent && !pPrevFrameParent ) ||
+ ( pFrameParent && pPrevFrameParent && pFrameParent->ImplGetFrame() == pPrevFrameParent->ImplGetFrame() )
+ )
+ {
+ pPrevious->GrabFocus();
+ }
+ }
+ }
+
+ mpDialogImpl->mnResult = nResult;
+
+ if ( mpDialogImpl->mbStartedModal )
+ ImplEndExecuteModal();
+
+ if ( mpDialogImpl && mpDialogImpl->maEndCtx.isSet() )
+ {
+ auto fn = std::move(mpDialogImpl->maEndCtx.maEndDialogFn);
+ // std::move leaves maEndDialogFn in a valid state with unspecified
+ // value. For the SwSyncBtnDlg case gcc and msvc left maEndDialogFn
+ // unset, but clang left maEndDialogFn at its original value, keeping
+ // an extra reference to the DialogController in its lambda giving
+ // an inconsistent lifecycle for the dialog. Force it to be unset.
+ mpDialogImpl->maEndCtx.maEndDialogFn = nullptr;
+ fn(nResult);
+ }
+
+ if ( mpDialogImpl && mpDialogImpl->mbStartedModal )
+ {
+ mpDialogImpl->mbStartedModal = false;
+ mpDialogImpl->mnResult = -1;
+ }
+ mbInExecute = false;
+
+ if ( mpDialogImpl )
+ {
+ // Destroy ourselves (if we have a context with VclPtr owner)
+ std::shared_ptr<weld::DialogController> xOwnerDialogController = std::move(mpDialogImpl->maEndCtx.mxOwnerDialogController);
+ std::shared_ptr<weld::Dialog> xOwnerSelf = std::move(mpDialogImpl->maEndCtx.mxOwnerSelf);
+ mpDialogImpl->maEndCtx.mxOwner.disposeAndClear();
+ xOwnerDialogController.reset();
+ xOwnerSelf.reset();
+ }
+}
+
+namespace vcl
+{
+ void EndAllDialogs( vcl::Window const * pParent )
+ {
+ ImplSVData* pSVData = ImplGetSVData();
+ auto& rExecuteDialogs = pSVData->mpWinData->mpExecuteDialogs;
+
+ for (auto it = rExecuteDialogs.rbegin(); it != rExecuteDialogs.rend(); ++it)
+ {
+ if (!pParent || pParent->IsWindowOrChild(*it, true))
+ {
+ (*it)->EndDialog();
+ (*it)->PostUserEvent(Link<void*, void>());
+ }
+ }
+ }
+
+ void EnableDialogInput(vcl::Window* pWindow)
+ {
+ if (Dialog* pDialog = dynamic_cast<Dialog*>(pWindow))
+ {
+ pDialog->EnableInput();
+ }
+ }
+
+ bool CloseDialog(vcl::Window* pWindow)
+ {
+ if (Dialog* pDialog = dynamic_cast<Dialog*>(pWindow))
+ {
+ pDialog->Close();
+ return true;
+ }
+ return false;
+ }
+}
+
+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);
+ }
+ }
+ }
+}
+
+void Dialog::GrabFocusToFirstControl()
+{
+ vcl::Window* pFocusControl;
+
+ // find focus control, even if the dialog has focus
+ if ( HasFocus() )
+ pFocusControl = nullptr;
+ else
+ {
+ // 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 );
+ }
+ 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, DrawFlags )
+{
+ 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;
+
+ if (const vcl::ILibreOfficeKitNotifier* pNotifier = GetLOKNotifier())
+ {
+ std::vector<vcl::LOKPayloadItem> aItems;
+ aItems.emplace_back("size", GetSizePixel().toString());
+ pNotifier->notifyWindow(GetLOKWindowId(), "size_changed", aItems);
+ }
+}
+
+bool Dialog::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "border-width")
+ set_border_width(rValue.toInt32());
+ else
+ return SystemWindow::set_property(rKey, rValue);
+ return true;
+}
+
+FactoryFunction Dialog::GetUITestFactory() const
+{
+ return DialogUIObject::create;
+}
+
+IMPL_LINK(Dialog, ResponseHdl, Button*, pButton, void)
+{
+ auto aFind = mpDialogImpl->maResponses.find(pButton);
+ if (aFind == mpDialogImpl->maResponses.end())
+ return;
+ short nResponse = aFind->second;
+ if (nResponse == RET_HELP)
+ {
+ vcl::Window* pFocusWin = Application::GetFocusWindow();
+ if (!pFocusWin)
+ 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()
+{
+}
+
+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);
+}
+
+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_aBusyStack.push(aTopLevels);
+}
+
+void TopLevelWindowLocker::decBusy()
+{
+ // unlock locked toplevel windows from being closed now busy is over
+ for (auto& a : m_aBusyStack.top())
+ {
+ if (a->IsDisposed())
+ continue;
+ a->DecModalCount();
+ a->ImplGetFrame()->NotifyModalHierarchy(false);
+ }
+ m_aBusyStack.pop();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/dlgctrl.cxx b/vcl/source/window/dlgctrl.cxx
new file mode 100644
index 000000000..903a73140
--- /dev/null
+++ b/vcl/source/window/dlgctrl.cxx
@@ -0,0 +1,1157 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/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/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 );
+
+ sal_uInt16 nId;
+ for ( ToolBox::ImplToolItems::size_type nPos = 0; nPos < pToolBoxWindow->GetItemCount(); nPos++ )
+ {
+ 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;
+
+ if ( GetType() == WindowType::RADIOBUTTON )
+ {
+ if ( !static_cast<RadioButton*>(this)->IsChecked() )
+ static_cast<RadioButton*>(this)->ImplCallClick( true, nFlags );
+ else
+ ImplGrabFocus( nFlags );
+ }
+ else
+ {
+ ImplGrabFocus( nFlags );
+ if ( nFlags & GetFocusFlags::UniqueMnemonic )
+ {
+ if ( GetType() == WindowType::CHECKBOX )
+ static_cast<CheckBox*>(this)->ImplCheck();
+ else if ( mpWindowImpl->mbPushButton )
+ {
+ static_cast<PushButton*>(this)->SetPressed( true );
+ static_cast<PushButton*>(this)->SetPressed( false );
+ static_cast<PushButton*>(this)->Click();
+ }
+ }
+ }
+ }
+ else
+ {
+ if ( GetType() == WindowType::RADIOBUTTON )
+ {
+ if ( !static_cast<RadioButton*>(this)->IsChecked() )
+ static_cast<RadioButton*>(this)->ImplCallClick( true, nFlags );
+ else
+ ImplGrabFocus( nFlags );
+ }
+ else
+ ImplGrabFocus( nFlags );
+ }
+}
+
+} /* namespace vcl */
+
+namespace
+{
+ bool isSuitableDestination(vcl::Window const *pWindow)
+ {
+ return (pWindow && isVisibleInLayout(pWindow) &&
+ isEnabledInLayout(pWindow) && pWindow->IsInputEnabled() &&
+ //Pure window shouldn't get window after controls such as
+ //buttons.
+ (pWindow->GetType() != WindowType::WINDOW &&
+ pWindow->GetType() != WindowType::WORKWINDOW && pWindow->GetType() != WindowType::CONTROL)
+ );
+ }
+
+ bool focusNextInGroup(const std::vector<VclPtr<RadioButton> >::iterator& aStart, std::vector<VclPtr<RadioButton> > &rGroup)
+ {
+ std::vector<VclPtr<RadioButton> >::iterator aI(aStart);
+
+ if (aStart != rGroup.end())
+ ++aI;
+
+ aI = std::find_if(aI, rGroup.end(), isSuitableDestination);
+ if (aI != rGroup.end())
+ {
+ vcl::Window *pWindow = *aI;
+ pWindow->ImplControlFocus( GetFocusFlags::CURSOR | GetFocusFlags::Forward );
+ return true;
+ }
+ aI = std::find_if(rGroup.begin(), aStart, isSuitableDestination);
+ if (aI != aStart)
+ {
+ vcl::Window *pWindow = *aI;
+ pWindow->ImplControlFocus( GetFocusFlags::CURSOR | GetFocusFlags::Forward );
+ return true;
+ }
+ return false;
+ }
+
+ bool nextInGroup(RadioButton *pSourceWindow, bool bBackward)
+ {
+ std::vector<VclPtr<RadioButton> > aGroup(pSourceWindow->GetRadioButtonGroup());
+
+ if (aGroup.size() < 2) // have to have at last 2 buttons to be a useful group
+ return false;
+
+ if (bBackward)
+ std::reverse(aGroup.begin(), aGroup.end());
+
+ auto aStart(std::find(aGroup.begin(), aGroup.end(), VclPtr<RadioButton>(pSourceWindow)));
+
+ assert(aStart != aGroup.end());
+
+ return focusNextInGroup(aStart, aGroup);
+ }
+}
+
+namespace vcl {
+
+bool Window::ImplDlgCtrl( const KeyEvent& rKEvt, bool bKeyInput )
+{
+ vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
+ sal_uInt16 nKeyCode = aKeyCode.GetCode();
+ vcl::Window* pSWindow;
+ vcl::Window* pTempWindow;
+ vcl::Window* pButtonWindow;
+ sal_uInt16 i;
+ sal_uInt16 iButton;
+ sal_uInt16 iButtonStart;
+ sal_uInt16 iTemp;
+ sal_uInt16 nIndex;
+ sal_uInt16 nFormStart;
+ sal_uInt16 nFormEnd;
+ DialogControlFlags nDlgCtrlFlags;
+
+ // we cannot take over control without Focus-window
+ vcl::Window* pFocusWindow = Application::GetFocusWindow();
+ if ( !pFocusWindow || !ImplIsWindowOrChild( pFocusWindow ) )
+ return false;
+
+ // find Focus-Window in the child list
+ pSWindow = ::ImplFindDlgCtrlWindow( this, pFocusWindow,
+ nIndex, nFormStart, nFormEnd );
+ if ( !pSWindow )
+ return false;
+ i = nIndex;
+
+ nDlgCtrlFlags = DialogControlFlags::NONE;
+ pTempWindow = pSWindow;
+ do
+ {
+ nDlgCtrlFlags |= pTempWindow->GetDialogControlFlags();
+ if ( pTempWindow == this )
+ break;
+ pTempWindow = pTempWindow->ImplGetParent();
+ }
+ while ( pTempWindow );
+
+ pButtonWindow = nullptr;
+
+ if ( nKeyCode == KEY_RETURN )
+ {
+ // search first for a DefPushButton/CancelButton
+ pButtonWindow = ImplGetChildWindow( this, nFormStart, iButton, true );
+ iButtonStart = iButton;
+ while ( pButtonWindow )
+ {
+ if ( (pButtonWindow->GetStyle() & WB_DEFBUTTON) &&
+ pButtonWindow->mpWindowImpl->mbPushButton )
+ break;
+
+ pButtonWindow = ImplGetNextWindow( this, iButton, iButton, true );
+ if ( (iButton <= iButtonStart) || (iButton > nFormEnd) )
+ pButtonWindow = nullptr;
+ }
+
+ if ( bKeyInput && !pButtonWindow && (nDlgCtrlFlags & DialogControlFlags::Return) )
+ {
+ GetDlgWindowType nType;
+ GetFocusFlags nGetFocusFlags = GetFocusFlags::Tab;
+ sal_uInt16 nNewIndex;
+ sal_uInt16 iStart;
+ if ( aKeyCode.IsShift() )
+ {
+ nType = GetDlgWindowType::Prev;
+ nGetFocusFlags |= GetFocusFlags::Backward;
+ }
+ else
+ {
+ nType = GetDlgWindowType::Next;
+ nGetFocusFlags |= GetFocusFlags::Forward;
+ }
+ iStart = i;
+ pTempWindow = ImplGetDlgWindow( i, nType, nFormStart, nFormEnd, &nNewIndex );
+ while ( pTempWindow && (pTempWindow != pSWindow) )
+ {
+ if ( !pTempWindow->mpWindowImpl->mbPushButton )
+ {
+ // get Around-Flag
+ if ( nType == GetDlgWindowType::Prev )
+ {
+ if ( nNewIndex > iStart )
+ nGetFocusFlags |= GetFocusFlags::Around;
+ }
+ else
+ {
+ if ( nNewIndex < iStart )
+ nGetFocusFlags |= GetFocusFlags::Around;
+ }
+ pTempWindow->ImplControlFocus( nGetFocusFlags );
+ return true;
+ }
+ else
+ {
+ i = nNewIndex;
+ pTempWindow = ImplGetDlgWindow( i, nType, nFormStart, nFormEnd, &nNewIndex );
+ }
+ if ( (i <= iStart) || (i > nFormEnd) )
+ pTempWindow = nullptr;
+ }
+ // if this is the same window, simulate a Get/LoseFocus,
+ // in case AROUND is being processed
+ if ( pTempWindow && (pTempWindow == pSWindow) )
+ {
+ NotifyEvent aNEvt1( MouseNotifyEvent::LOSEFOCUS, pSWindow );
+ if ( !ImplCallPreNotify( aNEvt1 ) )
+ pSWindow->CompatLoseFocus();
+ pSWindow->mpWindowImpl->mnGetFocusFlags = nGetFocusFlags | GetFocusFlags::Around;
+ NotifyEvent aNEvt2( MouseNotifyEvent::GETFOCUS, pSWindow );
+ if ( !ImplCallPreNotify( aNEvt2 ) )
+ pSWindow->CompatGetFocus();
+ pSWindow->mpWindowImpl->mnGetFocusFlags = GetFocusFlags::NONE;
+ return true;
+ }
+ }
+ }
+ else if ( nKeyCode == KEY_ESCAPE )
+ {
+ // search first for a DefPushButton/CancelButton
+ pButtonWindow = ImplGetChildWindow( this, nFormStart, iButton, true );
+ iButtonStart = iButton;
+ while ( pButtonWindow )
+ {
+ if ( pButtonWindow->GetType() == WindowType::CANCELBUTTON )
+ break;
+
+ pButtonWindow = ImplGetNextWindow( this, iButton, iButton, true );
+ if ( (iButton <= iButtonStart) || (iButton > nFormEnd) )
+ pButtonWindow = nullptr;
+ }
+
+ if ( bKeyInput && mpWindowImpl->mpDlgCtrlDownWindow )
+ {
+ if ( mpWindowImpl->mpDlgCtrlDownWindow.get() != pButtonWindow )
+ {
+ static_cast<PushButton*>(mpWindowImpl->mpDlgCtrlDownWindow.get())->SetPressed( false );
+ mpWindowImpl->mpDlgCtrlDownWindow = nullptr;
+ return true;
+ }
+ }
+ }
+ else if ( bKeyInput )
+ {
+ if ( nKeyCode == KEY_TAB )
+ {
+ // do not skip Alt key, for MS Windows
+ if ( !aKeyCode.IsMod2() )
+ {
+ GetDlgWindowType nType;
+ GetFocusFlags nGetFocusFlags = GetFocusFlags::Tab;
+ 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))
+ {
+ if ( aKeyCode.IsShift() )
+ {
+ nType = GetDlgWindowType::Prev;
+ nGetFocusFlags |= GetFocusFlags::Backward;
+ }
+ else
+ {
+ nType = GetDlgWindowType::Next;
+ nGetFocusFlags |= GetFocusFlags::Forward;
+ }
+ vcl::Window* pWindow = ImplGetDlgWindow( i, nType, nFormStart, nFormEnd, &nNewIndex );
+ // if this is the same window, simulate a Get/LoseFocus,
+ // in case AROUND is being processed
+ if ( pWindow == pSWindow )
+ {
+ NotifyEvent aNEvt1( MouseNotifyEvent::LOSEFOCUS, pSWindow );
+ if ( !ImplCallPreNotify( aNEvt1 ) )
+ pSWindow->CompatLoseFocus();
+ pSWindow->mpWindowImpl->mnGetFocusFlags = nGetFocusFlags | GetFocusFlags::Around;
+ NotifyEvent aNEvt2( MouseNotifyEvent::GETFOCUS, pSWindow );
+ if ( !ImplCallPreNotify( aNEvt2 ) )
+ pSWindow->CompatGetFocus();
+ pSWindow->mpWindowImpl->mnGetFocusFlags = GetFocusFlags::NONE;
+ return true;
+ }
+ else if ( pWindow )
+ {
+ // get Around-Flag
+ if ( nType == GetDlgWindowType::Prev )
+ {
+ if ( nNewIndex > i )
+ nGetFocusFlags |= GetFocusFlags::Around;
+ }
+ else
+ {
+ if ( nNewIndex < i )
+ nGetFocusFlags |= GetFocusFlags::Around;
+ }
+ pWindow->ImplControlFocus( nGetFocusFlags );
+ return true;
+ }
+ }
+ }
+ }
+ }
+ else if ( (nKeyCode == KEY_LEFT) || (nKeyCode == KEY_UP) )
+ {
+ if (pSWindow->GetType() == WindowType::RADIOBUTTON)
+ return nextInGroup(static_cast<RadioButton*>(pSWindow), true);
+ else
+ {
+ WinBits nStyle = pSWindow->GetStyle();
+ if ( !(nStyle & WB_GROUP) )
+ {
+ vcl::Window* pWindow = prevLogicalChildOfParent(this, pSWindow);
+ while ( pWindow )
+ {
+ pWindow = pWindow->ImplGetWindow();
+
+ nStyle = pWindow->GetStyle();
+
+ if (isSuitableDestination(pWindow))
+ {
+ if ( pWindow != pSWindow )
+ pWindow->ImplControlFocus( GetFocusFlags::CURSOR | GetFocusFlags::Backward );
+ return true;
+ }
+
+ if ( nStyle & WB_GROUP )
+ break;
+
+ pWindow = prevLogicalChildOfParent(this, pWindow);
+ }
+ }
+ }
+ }
+ else if ( (nKeyCode == KEY_RIGHT) || (nKeyCode == KEY_DOWN) )
+ {
+ if (pSWindow->GetType() == WindowType::RADIOBUTTON)
+ return nextInGroup(static_cast<RadioButton*>(pSWindow), false);
+ else
+ {
+ vcl::Window* pWindow = nextLogicalChildOfParent(this, pSWindow);
+ while ( pWindow )
+ {
+ pWindow = pWindow->ImplGetWindow();
+
+ WinBits nStyle = pWindow->GetStyle();
+
+ if ( nStyle & WB_GROUP )
+ break;
+
+ if (isSuitableDestination(pWindow))
+ {
+ pWindow->ImplControlFocus( GetFocusFlags::CURSOR | GetFocusFlags::Backward );
+ return true;
+ }
+
+ pWindow = nextLogicalChildOfParent(this, pWindow);
+ }
+ }
+ }
+ else
+ {
+ sal_Unicode c = rKEvt.GetCharCode();
+ if ( c )
+ {
+ pSWindow = ::ImplFindAccelWindow( this, i, c, nFormStart, nFormEnd );
+ if ( pSWindow )
+ {
+ GetFocusFlags nGetFocusFlags = GetFocusFlags::Mnemonic;
+ if ( pSWindow == ::ImplFindAccelWindow( this, i, c, nFormStart, nFormEnd ) )
+ nGetFocusFlags |= GetFocusFlags::UniqueMnemonic;
+ pSWindow->ImplControlFocus( nGetFocusFlags );
+ return true;
+ }
+ }
+ }
+ }
+
+ if (isSuitableDestination(pButtonWindow))
+ {
+ if ( bKeyInput )
+ {
+ if ( mpWindowImpl->mpDlgCtrlDownWindow && (mpWindowImpl->mpDlgCtrlDownWindow.get() != pButtonWindow) )
+ {
+ static_cast<PushButton*>(mpWindowImpl->mpDlgCtrlDownWindow.get())->SetPressed( false );
+ mpWindowImpl->mpDlgCtrlDownWindow = nullptr;
+ }
+
+ static_cast<PushButton*>(pButtonWindow)->SetPressed( true );
+ mpWindowImpl->mpDlgCtrlDownWindow = pButtonWindow;
+ }
+ else if ( mpWindowImpl->mpDlgCtrlDownWindow.get() == pButtonWindow )
+ {
+ mpWindowImpl->mpDlgCtrlDownWindow = nullptr;
+ static_cast<PushButton*>(pButtonWindow)->SetPressed( false );
+ static_cast<PushButton*>(pButtonWindow)->Click();
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+// checks if this window has dialog control
+bool Window::ImplHasDlgCtrl() const
+{
+ vcl::Window* pDlgCtrlParent;
+
+ // lookup window for dialog control
+ pDlgCtrlParent = ImplGetParent();
+ while ( pDlgCtrlParent &&
+ !pDlgCtrlParent->ImplIsOverlapWindow() &&
+ ((pDlgCtrlParent->GetStyle() & (WB_DIALOGCONTROL | WB_NODIALOGCONTROL)) != WB_DIALOGCONTROL) )
+ pDlgCtrlParent = pDlgCtrlParent->ImplGetParent();
+
+ return pDlgCtrlParent && ((pDlgCtrlParent->GetStyle() & (WB_DIALOGCONTROL | WB_NODIALOGCONTROL)) == WB_DIALOGCONTROL);
+}
+
+void Window::ImplDlgCtrlNextWindow()
+{
+ vcl::Window* pDlgCtrlParent;
+ vcl::Window* pDlgCtrl;
+ vcl::Window* pSWindow;
+ sal_uInt16 nIndex;
+ sal_uInt16 nFormStart;
+ sal_uInt16 nFormEnd;
+
+ // lookup window for dialog control
+ pDlgCtrl = this;
+ pDlgCtrlParent = ImplGetParent();
+ while ( pDlgCtrlParent &&
+ !pDlgCtrlParent->ImplIsOverlapWindow() &&
+ ((pDlgCtrlParent->GetStyle() & (WB_DIALOGCONTROL | WB_NODIALOGCONTROL)) != WB_DIALOGCONTROL) )
+ pDlgCtrlParent = pDlgCtrlParent->ImplGetParent();
+
+ if ( !pDlgCtrlParent || (GetStyle() & WB_NODIALOGCONTROL) || ((pDlgCtrlParent->GetStyle() & (WB_DIALOGCONTROL | WB_NODIALOGCONTROL)) != WB_DIALOGCONTROL) )
+ return;
+
+ // lookup window in child list
+ pSWindow = ::ImplFindDlgCtrlWindow( pDlgCtrlParent, pDlgCtrl,
+ nIndex, nFormStart, nFormEnd );
+ if ( !pSWindow )
+ return;
+
+ vcl::Window* pWindow = pDlgCtrlParent->ImplGetDlgWindow( nIndex, GetDlgWindowType::Next, nFormStart, nFormEnd );
+ if ( pWindow && (pWindow != pSWindow) )
+ pWindow->ImplControlFocus();
+}
+
+static void ImplDlgCtrlUpdateDefButton( vcl::Window* pParent, vcl::Window* pFocusWindow,
+ bool bGetFocus )
+{
+ PushButton* pOldDefButton = nullptr;
+ PushButton* pNewDefButton = nullptr;
+ vcl::Window* pSWindow;
+ sal_uInt16 i;
+ sal_uInt16 nFormStart;
+ sal_uInt16 nFormEnd;
+
+ // find template
+ pSWindow = ::ImplFindDlgCtrlWindow( pParent, pFocusWindow, i, nFormStart, nFormEnd );
+ if ( !pSWindow )
+ {
+ nFormStart = 0;
+ nFormEnd = 0xFFFF;
+ }
+
+ pSWindow = ImplGetChildWindow( pParent, nFormStart, i, false );
+ while ( pSWindow )
+ {
+ if ( pSWindow->ImplIsPushButton() )
+ {
+ PushButton* pPushButton = static_cast<PushButton*>(pSWindow);
+ if ( pPushButton->ImplIsDefButton() )
+ pOldDefButton = pPushButton;
+ if ( pPushButton->HasChildPathFocus() )
+ pNewDefButton = pPushButton;
+ else if ( !pNewDefButton && (pPushButton->GetStyle() & WB_DEFBUTTON) )
+ pNewDefButton = pPushButton;
+ }
+
+ pSWindow = ImplGetNextWindow( pParent, i, i, false );
+ if ( !i || (i > nFormEnd) )
+ pSWindow = nullptr;
+ }
+
+ if ( !bGetFocus )
+ {
+ sal_uInt16 nDummy;
+ vcl::Window* pNewFocusWindow = Application::GetFocusWindow();
+ if ( !pNewFocusWindow || !pParent->ImplIsWindowOrChild( pNewFocusWindow ) )
+ pNewDefButton = nullptr;
+ else if ( !::ImplFindDlgCtrlWindow( pParent, pNewFocusWindow, i, nDummy, nDummy ) ||
+ (i < nFormStart) || (i > nFormEnd) )
+ pNewDefButton = nullptr;
+ }
+
+ if ( pOldDefButton != pNewDefButton )
+ {
+ if ( pOldDefButton )
+ pOldDefButton->ImplSetDefButton( false );
+ if ( pNewDefButton )
+ pNewDefButton->ImplSetDefButton( true );
+ }
+}
+
+void Window::ImplDlgCtrlFocusChanged( vcl::Window* pWindow, bool bGetFocus )
+{
+ if ( mpWindowImpl->mpDlgCtrlDownWindow && !bGetFocus )
+ {
+ static_cast<PushButton*>(mpWindowImpl->mpDlgCtrlDownWindow.get())->SetPressed( false );
+ mpWindowImpl->mpDlgCtrlDownWindow = nullptr;
+ }
+
+ ImplDlgCtrlUpdateDefButton( this, pWindow, bGetFocus );
+}
+
+vcl::Window* Window::ImplFindDlgCtrlWindow( vcl::Window* pWindow )
+{
+ sal_uInt16 nIndex;
+ sal_uInt16 nFormStart;
+ sal_uInt16 nFormEnd;
+
+ // find Focus-Window in the Child-List and return
+ return ::ImplFindDlgCtrlWindow( this, pWindow, nIndex, nFormStart, nFormEnd );
+}
+
+KeyEvent Window::GetActivationKey() const
+{
+ KeyEvent aKeyEvent;
+
+ sal_Unicode nAccel = getAccel( GetText() );
+ if( ! nAccel )
+ {
+ vcl::Window* pWindow = GetAccessibleRelationLabeledBy();
+ if( pWindow )
+ nAccel = getAccel( pWindow->GetText() );
+ }
+ if( nAccel )
+ {
+ sal_uInt16 nCode = 0;
+ if( nAccel >= 'a' && nAccel <= 'z' )
+ nCode = KEY_A + (nAccel-'a');
+ else if( nAccel >= 'A' && nAccel <= 'Z' )
+ nCode = KEY_A + (nAccel-'A');
+ else if( nAccel >= '0' && nAccel <= '9' )
+ nCode = KEY_0 + (nAccel-'0');
+ else if( nAccel == '.' )
+ nCode = KEY_POINT;
+ else if( nAccel == '-' )
+ nCode = KEY_SUBTRACT;
+ vcl::KeyCode aKeyCode( nCode, false, false, true, false );
+ aKeyEvent = KeyEvent( nAccel, aKeyCode );
+ }
+ return aKeyEvent;
+}
+
+} /* namespace vcl */
+
+sal_Unicode getAccel( const OUString& rStr )
+{
+ sal_Unicode nChar = 0;
+ sal_Int32 nPos = 0;
+ do
+ {
+ nPos = rStr.indexOf( '~', nPos );
+ if( nPos != -1 && nPos < rStr.getLength() )
+ nChar = rStr[ ++nPos ];
+ else
+ nChar = 0;
+ } while( nChar == '~' );
+ return nChar;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/dlgctrl.hxx b/vcl/source/window/dlgctrl.hxx
new file mode 100644
index 000000000..d14f8e326
--- /dev/null
+++ b/vcl/source/window/dlgctrl.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_SOURCE_WINDOW_DLGCTRL_HXX
+#define INCLUDED_VCL_SOURCE_WINDOW_DLGCTRL_HXX
+
+#include <vcl/window.hxx>
+
+vcl::Window* ImplGetChildWindow( vcl::Window* pParent, sal_uInt16 n, sal_uInt16& nIndex, bool bTestEnable );
+
+vcl::Window* ImplFindDlgCtrlWindow( vcl::Window* pParent, vcl::Window* pWindow, sal_uInt16& rIndex,
+ sal_uInt16& rFormStart, sal_uInt16& rFormEnd );
+
+vcl::Window* ImplFindAccelWindow( vcl::Window* pParent, sal_uInt16& rIndex, sal_Unicode cCharCode,
+ sal_uInt16 nFormStart, sal_uInt16 nFormEnd, bool bCheckEnable = true );
+
+sal_Unicode getAccel( const OUString& rStr );
+
+#endif // INCLUDED_VCL_SOURCE_WINDOW_DLGCTRL_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/dndeventdispatcher.cxx b/vcl/source/window/dndeventdispatcher.cxx
new file mode 100644
index 000000000..47adfcf87
--- /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->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 )
+{
+ osl::MutexGuard 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 )
+{
+ osl::MutexGuard 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*/ )
+{
+ osl::MutexGuard aImplGuard( m_aMutex );
+
+ fireDragExitEvent( m_pCurrentWindow );
+
+ // reset member values
+ designate_currentwindow(nullptr);
+ m_aDataFlavorList.realloc( 0 );
+}
+
+void SAL_CALL DNDEventDispatcher::dragOver( const DropTargetDragEvent& dtde )
+{
+ osl::MutexGuard 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 )
+{
+ osl::MutexGuard 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 )
+{
+ osl::MutexGuard aImplGuard( m_aMutex );
+
+ Point origin( dge.DragOriginX, dge.DragOriginY );
+
+ vcl::Window* pChildWindow = findTopLevelWindow(origin);
+
+ fireDragGestureEvent( pChildWindow, dge.DragSource, dge.Event, origin, dge.DragAction );
+}
+
+void SAL_CALL DNDEventDispatcher::disposing( const EventObject& )
+{
+}
+
+void SAL_CALL DNDEventDispatcher::acceptDrag( sal_Int8 /*dropAction*/ )
+{
+}
+
+void SAL_CALL DNDEventDispatcher::rejectDrag()
+{
+}
+
+sal_Int32 DNDEventDispatcher::fireDragEnterEvent( vcl::Window *pWindow,
+ const Reference< XDropTargetDragContext >& xContext, const sal_Int8 nDropAction,
+ const Point& rLocation, const sal_Int8 nSourceActions, const Sequence< DataFlavor >& aFlavorList
+)
+{
+ sal_Int32 n = 0;
+
+ if( pWindow && pWindow->IsInputEnabled() && ! pWindow->IsInModalMode() )
+ {
+ SolarMutexClearableGuard aSolarGuard;
+
+ // query DropTarget from window
+ Reference< XDropTarget > xDropTarget = pWindow->GetDropTarget();
+
+ if( xDropTarget.is() )
+ {
+ // retrieve relative mouse position
+ Point relLoc = pWindow->ImplFrameToOutput( rLocation );
+ aSolarGuard.clear();
+
+ n = static_cast < DNDListenerContainer * > ( xDropTarget.get() )->fireDragEnterEvent(
+ xContext, nDropAction, relLoc.X(), relLoc.Y(), nSourceActions, aFlavorList );
+ }
+ }
+
+ return n;
+}
+
+sal_Int32 DNDEventDispatcher::fireDragOverEvent( vcl::Window *pWindow,
+ const Reference< XDropTargetDragContext >& xContext, const sal_Int8 nDropAction,
+ const Point& rLocation, const sal_Int8 nSourceActions
+)
+{
+ sal_Int32 n = 0;
+
+ if( pWindow && pWindow->IsInputEnabled() && ! pWindow->IsInModalMode() )
+ {
+ SolarMutexClearableGuard aSolarGuard;
+
+ // query DropTarget from window
+ Reference< XDropTarget > xDropTarget = pWindow->GetDropTarget();
+
+ if( xDropTarget.is() )
+ {
+ // retrieve relative mouse position
+ Point relLoc = pWindow->ImplFrameToOutput( rLocation );
+ aSolarGuard.clear();
+
+ n = static_cast < DNDListenerContainer * > ( xDropTarget.get() )->fireDragOverEvent(
+ xContext, nDropAction, relLoc.X(), relLoc.Y(), nSourceActions );
+ }
+ }
+
+ return n;
+}
+
+sal_Int32 DNDEventDispatcher::fireDragExitEvent( vcl::Window *pWindow )
+{
+ sal_Int32 n = 0;
+
+ if( pWindow && pWindow->IsInputEnabled() && ! pWindow->IsInModalMode() )
+ {
+ SolarMutexClearableGuard aGuard;
+
+ // query DropTarget from window
+ Reference< XDropTarget > xDropTarget = pWindow->GetDropTarget();
+
+ aGuard.clear();
+
+ if( xDropTarget.is() )
+ n = static_cast < DNDListenerContainer * > ( xDropTarget.get() )->fireDragExitEvent();
+ }
+
+ return n;
+}
+
+sal_Int32 DNDEventDispatcher::fireDropActionChangedEvent( vcl::Window *pWindow,
+ const Reference< XDropTargetDragContext >& xContext, const sal_Int8 nDropAction,
+ const Point& rLocation, const sal_Int8 nSourceActions
+)
+{
+ sal_Int32 n = 0;
+
+ if( pWindow && pWindow->IsInputEnabled() && ! pWindow->IsInModalMode() )
+ {
+ SolarMutexClearableGuard aGuard;
+
+ // query DropTarget from window
+ Reference< XDropTarget > xDropTarget = pWindow->GetDropTarget();
+
+ if( xDropTarget.is() )
+ {
+ // retrieve relative mouse position
+ Point relLoc = pWindow->ImplFrameToOutput( rLocation );
+ aGuard.clear();
+
+ n = static_cast < DNDListenerContainer * > ( xDropTarget.get() )->fireDropActionChangedEvent(
+ xContext, nDropAction, relLoc.X(), relLoc.Y(), nSourceActions );
+ }
+ }
+
+ return n;
+}
+
+sal_Int32 DNDEventDispatcher::fireDropEvent( vcl::Window *pWindow,
+ const Reference< XDropTargetDropContext >& xContext, const sal_Int8 nDropAction, const Point& rLocation,
+ const sal_Int8 nSourceActions, const Reference< XTransferable >& xTransferable
+)
+{
+ sal_Int32 n = 0;
+
+ if( pWindow && pWindow->IsInputEnabled() && ! pWindow->IsInModalMode() )
+ {
+ SolarMutexClearableGuard aGuard;
+
+ // query DropTarget from window
+ Reference< XDropTarget > xDropTarget = pWindow->GetDropTarget();
+
+ // window may be destroyed in drop event handler
+ VclPtr<vcl::Window> xPreventDelete = pWindow;
+
+ if( xDropTarget.is() )
+ {
+ // retrieve relative mouse position
+ Point relLoc = pWindow->ImplFrameToOutput( rLocation );
+ aGuard.clear();
+
+ n = static_cast < DNDListenerContainer * > ( xDropTarget.get() )->fireDropEvent(
+ xContext, nDropAction, relLoc.X(), relLoc.Y(), nSourceActions, xTransferable );
+ }
+ }
+
+ return n;
+}
+
+sal_Int32 DNDEventDispatcher::fireDragGestureEvent( vcl::Window *pWindow,
+ const Reference< XDragSource >& xSource, const Any& event,
+ const Point& rOrigin, const sal_Int8 nDragAction
+)
+{
+ sal_Int32 n = 0;
+
+ if( pWindow && pWindow->IsInputEnabled() && ! pWindow->IsInModalMode() )
+ {
+ SolarMutexClearableGuard aGuard;
+
+ // query DropTarget from window
+ Reference< XDragGestureRecognizer > xDragGestureRecognizer = pWindow->GetDragGestureRecognizer();
+
+ if( xDragGestureRecognizer.is() )
+ {
+ // retrieve relative mouse position
+ Point relLoc = pWindow->ImplFrameToOutput( rOrigin );
+ aGuard.clear();
+
+ n = static_cast < DNDListenerContainer * > ( xDragGestureRecognizer.get() )->fireDragGestureEvent(
+ nDragAction, relLoc.X(), relLoc.Y(), xSource, event );
+ }
+ }
+
+ return n;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/dndlistenercontainer.cxx b/vcl/source/window/dndlistenercontainer.cxx
new file mode 100644
index 000000000..5d430629c
--- /dev/null
+++ b/vcl/source/window/dndlistenercontainer.cxx
@@ -0,0 +1,451 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <dndlistenercontainer.hxx>
+
+using namespace ::cppu;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::datatransfer;
+using namespace ::com::sun::star::datatransfer::dnd;
+
+DNDListenerContainer::DNDListenerContainer( sal_Int8 nDefaultActions )
+ : WeakComponentImplHelper< XDragGestureRecognizer, XDropTargetDragContext, XDropTargetDropContext, XDropTarget >(GetMutex())
+{
+ m_bActive = true;
+ m_nDefaultActions = nDefaultActions;
+}
+
+DNDListenerContainer::~DNDListenerContainer()
+{
+}
+
+void SAL_CALL DNDListenerContainer::addDragGestureListener( const Reference< XDragGestureListener >& dgl )
+{
+ rBHelper.addListener( cppu::UnoType<XDragGestureListener>::get(), dgl );
+}
+
+void SAL_CALL DNDListenerContainer::removeDragGestureListener( const Reference< XDragGestureListener >& dgl )
+{
+ rBHelper.removeListener( cppu::UnoType<XDragGestureListener>::get(), dgl );
+}
+
+void SAL_CALL DNDListenerContainer::resetRecognizer( )
+{
+}
+
+void SAL_CALL DNDListenerContainer::addDropTargetListener( const Reference< XDropTargetListener >& dtl )
+{
+ rBHelper.addListener( cppu::UnoType<XDropTargetListener>::get(), dtl );
+}
+
+void SAL_CALL DNDListenerContainer::removeDropTargetListener( const Reference< XDropTargetListener >& dtl )
+{
+ rBHelper.removeListener( cppu::UnoType<XDropTargetListener>::get(), dtl );
+}
+
+sal_Bool SAL_CALL DNDListenerContainer::isActive( )
+{
+ return m_bActive;
+}
+
+void SAL_CALL DNDListenerContainer::setActive( sal_Bool active )
+{
+ m_bActive = active;
+}
+
+sal_Int8 SAL_CALL DNDListenerContainer::getDefaultActions( )
+{
+ return m_nDefaultActions;
+}
+
+void SAL_CALL DNDListenerContainer::setDefaultActions( sal_Int8 actions )
+{
+ m_nDefaultActions = actions;
+}
+
+sal_uInt32 DNDListenerContainer::fireDropEvent( const Reference< XDropTargetDropContext >& context,
+ sal_Int8 dropAction, sal_Int32 locationX, sal_Int32 locationY, sal_Int8 sourceActions,
+ const Reference< XTransferable >& transferable )
+{
+ sal_uInt32 nRet = 0;
+
+ // fire DropTargetDropEvent on all XDropTargetListeners
+ OInterfaceContainerHelper *pContainer = rBHelper.getContainer( cppu::UnoType<XDropTargetListener>::get());
+
+ if( pContainer && m_bActive )
+ {
+ OInterfaceIteratorHelper aIterator( *pContainer );
+
+ // remember context to use in own context methods
+ m_xDropTargetDropContext = context;
+
+ // do not construct the event before you are sure at least one listener is registered
+ DropTargetDropEvent aEvent( static_cast < XDropTarget * > (this), 0,
+ static_cast < XDropTargetDropContext * > (this), dropAction,
+ locationX, locationY, sourceActions, transferable );
+
+ while (aIterator.hasMoreElements())
+ {
+ // FIXME: this can be simplified as soon as the Iterator has a remove method
+ Reference< XInterface > xElement( aIterator.next() );
+
+ try
+ {
+ // this may result in a runtime exception
+ Reference < XDropTargetListener > xListener( xElement, UNO_QUERY );
+
+ if( xListener.is() )
+ {
+ // fire drop until the first one has accepted
+ if( m_xDropTargetDropContext.is() )
+ xListener->drop( aEvent );
+ else
+ {
+ DropTargetEvent aDTEvent( static_cast < XDropTarget * > (this), 0 );
+ xListener->dragExit( aDTEvent );
+ }
+
+ nRet++;
+ }
+ }
+ catch (const RuntimeException&)
+ {
+ pContainer->removeInterface( xElement );
+ }
+ }
+
+ // if context still valid, then reject drop
+ if( m_xDropTargetDropContext.is() )
+ {
+ m_xDropTargetDropContext.clear();
+
+ try
+ {
+ context->rejectDrop();
+ }
+ catch (const RuntimeException&)
+ {
+ }
+ }
+ }
+
+ return nRet;
+}
+
+sal_uInt32 DNDListenerContainer::fireDragExitEvent()
+{
+ sal_uInt32 nRet = 0;
+
+ // fire DropTargetDropEvent on all XDropTargetListeners
+ OInterfaceContainerHelper *pContainer = rBHelper.getContainer( cppu::UnoType<XDropTargetListener>::get());
+
+ if( pContainer && m_bActive )
+ {
+ OInterfaceIteratorHelper aIterator( *pContainer );
+
+ // do not construct the event before you are sure at least one listener is registered
+ DropTargetEvent aEvent( static_cast < XDropTarget * > (this), 0 );
+
+ while (aIterator.hasMoreElements())
+ {
+ // FIXME: this can be simplified as soon as the Iterator has a remove method
+ Reference< XInterface > xElement( aIterator.next() );
+
+ try
+ {
+ // this may result in a runtime exception
+ Reference < XDropTargetListener > xListener( xElement, UNO_QUERY );
+
+ if( xListener.is() )
+ {
+ xListener->dragExit( aEvent );
+ nRet++;
+ }
+ }
+ catch (const RuntimeException&)
+ {
+ pContainer->removeInterface( xElement );
+ }
+ }
+ }
+
+ return nRet;
+}
+
+sal_uInt32 DNDListenerContainer::fireDragOverEvent( const Reference< XDropTargetDragContext >& context,
+ sal_Int8 dropAction, sal_Int32 locationX, sal_Int32 locationY, sal_Int8 sourceActions )
+{
+ sal_uInt32 nRet = 0;
+
+ // fire DropTargetDropEvent on all XDropTargetListeners
+ OInterfaceContainerHelper *pContainer = rBHelper.getContainer( cppu::UnoType<XDropTargetListener>::get());
+
+ if( pContainer && m_bActive )
+ {
+ OInterfaceIteratorHelper aIterator( *pContainer );
+
+ // remember context to use in own context methods
+ m_xDropTargetDragContext = context;
+
+ // do not construct the event before you are sure at least one listener is registered
+ DropTargetDragEvent aEvent( static_cast < XDropTarget * > (this), 0,
+ static_cast < XDropTargetDragContext * > (this),
+ dropAction, locationX, locationY, sourceActions );
+
+ while (aIterator.hasMoreElements())
+ {
+ // FIXME: this can be simplified as soon as the Iterator has a remove method
+ Reference< XInterface > xElement( aIterator.next() );
+
+ try
+ {
+ // this may result in a runtime exception
+ Reference < XDropTargetListener > xListener( xElement, UNO_QUERY );
+
+ if( xListener.is() )
+ {
+ if( m_xDropTargetDragContext.is() )
+ xListener->dragOver( aEvent );
+ nRet++;
+ }
+ }
+ catch (const RuntimeException&)
+ {
+ pContainer->removeInterface( xElement );
+ }
+ }
+
+ // if context still valid, then reject drag
+ if( m_xDropTargetDragContext.is() )
+ {
+ m_xDropTargetDragContext.clear();
+
+ try
+ {
+ context->rejectDrag();
+ }
+ catch (const RuntimeException&)
+ {
+ }
+ }
+ }
+
+ return nRet;
+}
+
+sal_uInt32 DNDListenerContainer::fireDragEnterEvent( const Reference< XDropTargetDragContext >& context,
+ sal_Int8 dropAction, sal_Int32 locationX, sal_Int32 locationY, sal_Int8 sourceActions,
+ const Sequence< DataFlavor >& dataFlavors )
+{
+ sal_uInt32 nRet = 0;
+
+ // fire DropTargetDropEvent on all XDropTargetListeners
+ OInterfaceContainerHelper *pContainer = rBHelper.getContainer( cppu::UnoType<XDropTargetListener>::get());
+
+ if( pContainer && m_bActive )
+ {
+ OInterfaceIteratorHelper aIterator( *pContainer );
+
+ // remember context to use in own context methods
+ m_xDropTargetDragContext = context;
+
+ // do not construct the event before you are sure at least one listener is registered
+ DropTargetDragEnterEvent aEvent( static_cast < XDropTarget * > (this), 0,
+ static_cast < XDropTargetDragContext * > (this),
+ dropAction, locationX, locationY, sourceActions, dataFlavors );
+
+ while (aIterator.hasMoreElements())
+ {
+ // FIXME: this can be simplified as soon as the Iterator has a remove method
+ Reference< XInterface > xElement( aIterator.next() );
+
+ try
+ {
+ // this may result in a runtime exception
+ Reference < XDropTargetListener > xListener( xElement, UNO_QUERY );
+
+ if( xListener.is() )
+ {
+ if( m_xDropTargetDragContext.is() )
+ xListener->dragEnter( aEvent );
+ nRet++;
+ }
+ }
+ catch (const RuntimeException&)
+ {
+ pContainer->removeInterface( xElement );
+ }
+ }
+
+ // if context still valid, then reject drag
+ if( m_xDropTargetDragContext.is() )
+ {
+ m_xDropTargetDragContext.clear();
+
+ try
+ {
+ context->rejectDrag();
+ }
+ catch (const RuntimeException&)
+ {
+ }
+ }
+ }
+
+ return nRet;
+}
+
+sal_uInt32 DNDListenerContainer::fireDropActionChangedEvent( const Reference< XDropTargetDragContext >& context,
+ sal_Int8 dropAction, sal_Int32 locationX, sal_Int32 locationY, sal_Int8 sourceActions )
+{
+ sal_uInt32 nRet = 0;
+
+ // fire DropTargetDropEvent on all XDropTargetListeners
+ OInterfaceContainerHelper *pContainer = rBHelper.getContainer( cppu::UnoType<XDropTargetListener>::get());
+
+ if( pContainer && m_bActive )
+ {
+ OInterfaceIteratorHelper aIterator( *pContainer );
+
+ // remember context to use in own context methods
+ m_xDropTargetDragContext = context;
+
+ // do not construct the event before you are sure at least one listener is registered
+ DropTargetDragEvent aEvent( static_cast < XDropTarget * > (this), 0,
+ static_cast < XDropTargetDragContext * > (this),
+ dropAction, locationX, locationY, sourceActions );
+
+ while (aIterator.hasMoreElements())
+ {
+ // FIXME: this can be simplified as soon as the Iterator has a remove method
+ Reference< XInterface > xElement( aIterator.next() );
+
+ try
+ {
+ // this may result in a runtime exception
+ Reference < XDropTargetListener > xListener( xElement, UNO_QUERY );
+
+ if( xListener.is() )
+ {
+ if( m_xDropTargetDragContext.is() )
+ xListener->dropActionChanged( aEvent );
+ nRet++;
+ }
+ }
+ catch (const RuntimeException&)
+ {
+ pContainer->removeInterface( xElement );
+ }
+ }
+
+ // if context still valid, then reject drag
+ if( m_xDropTargetDragContext.is() )
+ {
+ m_xDropTargetDragContext.clear();
+
+ try
+ {
+ context->rejectDrag();
+ }
+ catch (const RuntimeException&)
+ {
+ }
+ }
+ }
+
+ return nRet;
+}
+
+sal_uInt32 DNDListenerContainer::fireDragGestureEvent( sal_Int8 dragAction, sal_Int32 dragOriginX,
+ sal_Int32 dragOriginY, const Reference< XDragSource >& dragSource, const Any& triggerEvent )
+{
+ sal_uInt32 nRet = 0;
+
+ // fire DropTargetDropEvent on all XDropTargetListeners
+ OInterfaceContainerHelper *pContainer = rBHelper.getContainer( cppu::UnoType<XDragGestureListener>::get());
+
+ if( pContainer )
+ {
+ OInterfaceIteratorHelper aIterator( *pContainer );
+
+ // do not construct the event before you are sure at least one listener is registered
+ DragGestureEvent aEvent( static_cast < XDragGestureRecognizer * > (this), dragAction,
+ dragOriginX, dragOriginY, dragSource, triggerEvent );
+
+ while( aIterator.hasMoreElements() )
+ {
+ // FIXME: this can be simplified as soon as the Iterator has a remove method
+ Reference< XInterface > xElement( aIterator.next() );
+
+ try
+ {
+ // this may result in a runtime exception
+ Reference < XDragGestureListener > xListener( xElement, UNO_QUERY );
+
+ if( xListener.is() )
+ {
+ xListener->dragGestureRecognized( aEvent );
+ nRet++;
+ }
+ }
+ catch (const RuntimeException&)
+ {
+ pContainer->removeInterface( xElement );
+ }
+ }
+ }
+
+ return nRet;
+}
+
+void SAL_CALL DNDListenerContainer::acceptDrag( sal_Int8 dragOperation )
+{
+ if( m_xDropTargetDragContext.is() )
+ {
+ m_xDropTargetDragContext->acceptDrag( dragOperation );
+ m_xDropTargetDragContext.clear();
+ }
+}
+
+void SAL_CALL DNDListenerContainer::rejectDrag( )
+{
+ // nothing to do here
+}
+
+void SAL_CALL DNDListenerContainer::acceptDrop( sal_Int8 dropOperation )
+{
+ if( m_xDropTargetDropContext.is() )
+ m_xDropTargetDropContext->acceptDrop( dropOperation );
+}
+
+void SAL_CALL DNDListenerContainer::rejectDrop( )
+{
+ // nothing to do here
+}
+
+void SAL_CALL DNDListenerContainer::dropComplete( sal_Bool success )
+{
+ if( m_xDropTargetDropContext.is() )
+ {
+ m_xDropTargetDropContext->dropComplete( success );
+ m_xDropTargetDropContext.clear();
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/dockingarea.cxx b/vcl/source/window/dockingarea.cxx
new file mode 100644
index 000000000..fed3fa5c4
--- /dev/null
+++ b/vcl/source/window/dockingarea.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <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))
+ {
+ ToolbarValue aControlValue;
+
+ if (GetAlign() == WindowAlign::Top && ImplGetSVData()->maNWFData.mbMenuBarDockingAreaCommonBG)
+ {
+ // give NWF a hint that this dockingarea is adjacent to the menubar
+ // useful for special gradient effects that should cover both windows
+ aControlValue.mbIsTopDockingArea = true;
+ }
+
+ ControlState nState = ControlState::ENABLED;
+ const bool isFooter = GetAlign() == WindowAlign::Bottom && !rSetting.GetPersonaFooter().IsEmpty();
+
+ if ((GetAlign() == WindowAlign::Top && !rSetting.GetPersonaHeader().IsEmpty() ) || isFooter)
+ Erase(rRenderContext);
+ else if (!ImplGetSVData()->maNWFData.mbDockingAreaSeparateTB)
+ {
+ // draw a single toolbar background covering the whole docking area
+ tools::Rectangle aCtrlRegion(Point(), GetOutputSizePixel());
+
+ rRenderContext.DrawNativeControl(ControlType::Toolbar, IsHorizontal() ? ControlPart::DrawBackgroundHorz : ControlPart::DrawBackgroundVert,
+ aCtrlRegion, nState, aControlValue, OUString() );
+
+ if (!ImplGetSVData()->maNWFData.mbDockingAreaAvoidTBFrames)
+ {
+ // each toolbar gets a thin border to better recognize its borders on the homogeneous docking area
+ sal_uInt16 nChildren = GetChildCount();
+ for (sal_uInt16 n = 0; n < nChildren; n++)
+ {
+ vcl::Window* pChild = GetChild(n);
+ if (pChild->IsVisible())
+ {
+ Point aPos = pChild->GetPosPixel();
+ Size aSize = pChild->GetSizePixel();
+ tools::Rectangle aRect(aPos, aSize);
+
+ rRenderContext.SetLineColor(rRenderContext.GetSettings().GetStyleSettings().GetLightColor());
+ rRenderContext.DrawLine(aRect.TopLeft(), aRect.TopRight());
+ rRenderContext.DrawLine(aRect.TopLeft(), aRect.BottomLeft());
+
+ rRenderContext.SetLineColor(rRenderContext.GetSettings().GetStyleSettings().GetSeparatorColor());
+ rRenderContext.DrawLine(aRect.BottomLeft(), aRect.BottomRight());
+ rRenderContext.DrawLine(aRect.TopRight(), aRect.BottomRight());
+ }
+ }
+ }
+ }
+ else
+ {
+ // create map to find toolbar lines
+ Size aOutSz(GetOutputSizePixel());
+ std::map<int, int> ranges;
+ sal_uInt16 nChildren = GetChildCount();
+ for (sal_uInt16 n = 0; n < nChildren; n++)
+ {
+ vcl::Window* pChild = GetChild(n);
+ Point aPos = pChild->GetPosPixel();
+ Size aSize = pChild->GetSizePixel();
+ if (IsHorizontal())
+ ranges[aPos.Y()] = aSize.Height();
+ else
+ ranges[aPos.X()] = aSize.Width();
+ }
+
+ // draw multiple toolbar backgrounds, i.e., one for each toolbar line
+ for (auto const& range : ranges)
+ {
+ tools::Rectangle aTBRect;
+ if (IsHorizontal())
+ {
+ aTBRect.SetLeft( 0 );
+ aTBRect.SetRight( aOutSz.Width() - 1 );
+ aTBRect.SetTop( range.first );
+ aTBRect.SetBottom( range.first + range.second - 1 );
+ }
+ else
+ {
+ aTBRect.SetLeft( range.first );
+ aTBRect.SetRight( range.first + range.second - 1 );
+ aTBRect.SetTop( 0 );
+ aTBRect.SetBottom( aOutSz.Height() - 1 );
+ }
+ rRenderContext.DrawNativeControl(ControlType::Toolbar, IsHorizontal() ? ControlPart::DrawBackgroundHorz : ControlPart::DrawBackgroundVert,
+ aTBRect, nState, aControlValue, OUString());
+ }
+ }
+ }
+}
+
+void DockingAreaWindow::Resize()
+{
+ ImplInvalidateMenubar( this );
+ if (IsNativeControlSupported(ControlType::Toolbar, ControlPart::Entire))
+ Invalidate();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/dockmgr.cxx b/vcl/source/window/dockmgr.cxx
new file mode 100644
index 000000000..4cd3482cd
--- /dev/null
+++ b/vcl/source/window/dockmgr.cxx
@@ -0,0 +1,1059 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/floatwin.hxx>
+#include <vcl/dockwin.hxx>
+#include <vcl/toolbox.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/settings.hxx>
+
+#define DOCKWIN_FLOATSTYLES (WB_SIZEABLE | WB_MOVEABLE | WB_CLOSEABLE | WB_STANDALONE | WB_ROLLABLE )
+
+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() ),
+ mbInMove( false ),
+ mnLastUserEvent( nullptr )
+{
+ // copy state of DockingWindow
+ if ( pDockingWin )
+ {
+ 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_aDockTimer.SetDebugName( "vcl::ImplDockFloatWin2 m_aDockTimer" );
+
+ m_aEndDockTimer.SetInvokeHandler( LINK( this, ImplDockFloatWin2, EndDockTimerHdl ) );
+ m_aEndDockTimer.SetPriority( TaskPriority::HIGH_IDLE );
+ m_aEndDockTimer.SetTimeout( 50 );
+ m_aEndDockTimer.SetDebugName( "vcl::ImplDockFloatWin2 m_aEndDockTimer" );
+}
+
+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.IsInside( 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->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() && pWrapper->GetFloatingWindow()->IsInPopupMode() )
+ pWrapper->GetFloatingWindow()->EndPopupMode();
+}
+
+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 )
+ {
+ if( (*it)->mpDockingWindow == pWindow )
+ {
+ mvDockingWindows.erase( it );
+ break;
+ }
+ }
+}
+
+void DockingManager::SetPosSizePixel( vcl::Window const *pWindow, long nX, long nY,
+ long nWidth, 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)
+ , mbRollUp(false)
+ , mbDockBtn(false)
+ , mbHideBtn(false)
+ // must be enabled in Window::Notify to prevent permanent docking during mouse move
+ , mbStartDockingEnabled(false)
+ , mbLocked(false)
+{
+ DockingWindow *pDockWin = dynamic_cast< DockingWindow* > ( mpDockingWindow.get() );
+ if( pDockWin )
+ mnFloatBits = pDockWin->GetFloatStyle();
+}
+
+ImplDockingWindowWrapper::~ImplDockingWindowWrapper()
+{
+ if ( IsFloatingMode() )
+ {
+ GetWindow()->Show( false, ShowFlags::NoFocusChange );
+ SetFloatingMode(false);
+ }
+}
+
+void ImplDockingWindowWrapper::ImplStartDocking( const Point& rPos )
+{
+ if( !mbStartDockingEnabled )
+ return;
+
+ maMouseOff = rPos;
+ mbDocking = true;
+ mbLastFloatMode = IsFloatingMode();
+
+ // calculate FloatingBorder
+ VclPtr<FloatingWindow> pWin;
+ if ( mpFloatWin )
+ pWin = mpFloatWin;
+ else
+ pWin = VclPtr<ImplDockFloatWin2>::Create( mpParent, mnFloatBits, nullptr );
+ pWin->GetBorder( mnDockLeft, mnDockTop, mnDockRight, mnDockBottom );
+ if ( !mpFloatWin )
+ pWin.disposeAndClear();
+
+ Point aPos = GetWindow()->ImplOutputToFrame( Point() );
+ Size aSize = GetWindow()->GetOutputSizePixel();
+ mnTrackX = aPos.X();
+ mnTrackY = aPos.Y();
+ mnTrackWidth = aSize.Width();
+ mnTrackHeight = aSize.Height();
+
+ if ( mbLastFloatMode )
+ {
+ maMouseOff.AdjustX(mnDockLeft );
+ maMouseOff.AdjustY(mnDockTop );
+ mnTrackX -= mnDockLeft;
+ mnTrackY -= mnDockTop;
+ mnTrackWidth += mnDockLeft+mnDockRight;
+ mnTrackHeight += mnDockTop+mnDockBottom;
+ }
+
+ vcl::Window *pDockingArea = GetWindow()->GetParent();
+ vcl::Window::PointerState aState = pDockingArea->GetPointerState();
+
+ // mouse pos in screen pixels
+ Point aMousePos = pDockingArea->OutputToScreenPixel( aState.maPos );
+ Point aDockPos = pDockingArea->AbsoluteScreenToOutputPixel( GetWindow()->OutputToAbsoluteScreenPixel( GetWindow()->GetPosPixel() ) );
+ tools::Rectangle aDockRect( aDockPos, GetWindow()->GetSizePixel() );
+ StartDocking( aMousePos, aDockRect );
+
+ GetWindow()->ImplUpdateAll();
+ GetWindow()->ImplGetFrameWindow()->ImplUpdateAll();
+
+ GetWindow()->StartTracking( StartTrackingFlags::KeyMod );
+}
+
+void ImplDockingWindowWrapper::Tracking( const TrackingEvent& rTEvt )
+{
+ // used during docking of a currently docked window
+ if ( mbDocking )
+ {
+ if ( rTEvt.IsTrackingEnded() )
+ {
+ mbDocking = false;
+ GetWindow()->HideTracking();
+ if ( rTEvt.IsTrackingCanceled() )
+ {
+ mbDockCanceled = true;
+ EndDocking( tools::Rectangle( Point( mnTrackX, mnTrackY ), Size( mnTrackWidth, mnTrackHeight ) ), mbLastFloatMode );
+ mbDockCanceled = false;
+ }
+ else
+ EndDocking( tools::Rectangle( Point( mnTrackX, mnTrackY ), Size( mnTrackWidth, mnTrackHeight ) ), mbLastFloatMode );
+ }
+ // Docking only upon non-synthetic MouseEvents
+ else if ( !rTEvt.GetMouseEvent().IsSynthetic() || rTEvt.GetMouseEvent().IsModifierChanged() )
+ {
+ Point aMousePos = rTEvt.GetMouseEvent().GetPosPixel();
+ Point aFrameMousePos = GetWindow()->ImplOutputToFrame( aMousePos );
+ Size aFrameSize = GetWindow()->ImplGetFrameWindow()->GetOutputSizePixel();
+ if ( aFrameMousePos.X() < 0 )
+ aFrameMousePos.setX( 0 );
+ if ( aFrameMousePos.Y() < 0 )
+ aFrameMousePos.setY( 0 );
+ if ( aFrameMousePos.X() > aFrameSize.Width()-1 )
+ aFrameMousePos.setX( aFrameSize.Width()-1 );
+ if ( aFrameMousePos.Y() > aFrameSize.Height()-1 )
+ aFrameMousePos.setY( aFrameSize.Height()-1 );
+ aMousePos = GetWindow()->ImplFrameToOutput( aFrameMousePos );
+ aMousePos.AdjustX( -(maMouseOff.X()) );
+ aMousePos.AdjustY( -(maMouseOff.Y()) );
+ Point aPos = GetWindow()->ImplOutputToFrame( aMousePos );
+ tools::Rectangle aTrackRect( aPos, Size( mnTrackWidth, mnTrackHeight ) );
+ tools::Rectangle aCompRect = aTrackRect;
+ aPos.AdjustX(maMouseOff.X() );
+ aPos.AdjustY(maMouseOff.Y() );
+
+ bool bFloatMode = Docking( aPos, aTrackRect );
+
+ if ( mbLastFloatMode != bFloatMode )
+ {
+ if ( bFloatMode )
+ {
+ aTrackRect.AdjustLeft( -mnDockLeft );
+ aTrackRect.AdjustTop( -mnDockTop );
+ aTrackRect.AdjustRight(mnDockRight );
+ aTrackRect.AdjustBottom(mnDockBottom );
+ }
+ else
+ {
+ if ( aCompRect == aTrackRect )
+ {
+ aTrackRect.AdjustLeft(mnDockLeft );
+ aTrackRect.AdjustTop(mnDockTop );
+ aTrackRect.AdjustRight( -mnDockRight );
+ aTrackRect.AdjustBottom( -mnDockBottom );
+ }
+ }
+ mbLastFloatMode = bFloatMode;
+ }
+
+ ShowTrackFlags nTrackStyle;
+ if ( bFloatMode )
+ nTrackStyle = ShowTrackFlags::Object;
+ else
+ nTrackStyle = ShowTrackFlags::Big;
+ tools::Rectangle aShowTrackRect = aTrackRect;
+ aShowTrackRect.SetPos( GetWindow()->ImplFrameToOutput( aShowTrackRect.TopLeft() ) );
+
+ GetWindow()->ShowTracking( aShowTrackRect, nTrackStyle );
+
+ // calculate mouse offset again, as the rectangle was changed
+ maMouseOff.setX( aPos.X() - aTrackRect.Left() );
+ maMouseOff.setY( aPos.Y() - aTrackRect.Top() );
+
+ mnTrackX = aTrackRect.Left();
+ mnTrackY = aTrackRect.Top();
+ mnTrackWidth = aTrackRect.GetWidth();
+ mnTrackHeight = aTrackRect.GetHeight();
+ }
+ }
+}
+
+void ImplDockingWindowWrapper::StartDocking( const Point& rPoint, tools::Rectangle const & rRect )
+{
+ DockingData data( rPoint, rRect, IsFloatingMode() );
+
+ GetWindow()->CallEventListeners( VclEventId::WindowStartDocking, &data );
+ mbDocking = true;
+}
+
+bool ImplDockingWindowWrapper::Docking( const Point& rPoint, tools::Rectangle& rRect )
+{
+ DockingData data( rPoint, rRect, IsFloatingMode() );
+
+ GetWindow()->CallEventListeners( VclEventId::WindowDocking, &data );
+ rRect = data.maTrackRect;
+ return data.mbFloating;
+}
+
+void ImplDockingWindowWrapper::EndDocking( const tools::Rectangle& rRect, bool bFloatMode )
+{
+ tools::Rectangle aRect( rRect );
+
+ bool bOrigDockCanceled = mbDockCanceled;
+ if (bFloatMode && !StyleSettings::GetDockingFloatsSupported())
+ mbDockCanceled = true;
+
+ if ( !IsDockingCanceled() )
+ {
+ bool bShow = false;
+ if ( bFloatMode != IsFloatingMode() )
+ {
+ GetWindow()->Show( false, ShowFlags::NoFocusChange );
+ SetFloatingMode( bFloatMode );
+ bShow = true;
+ if ( bFloatMode )
+ {
+ // #i44800# always use outputsize - as in all other places
+ mpFloatWin->SetOutputSizePixel( aRect.GetSize() );
+ mpFloatWin->SetPosPixel( aRect.TopLeft() );
+ }
+ }
+ if ( !bFloatMode )
+ {
+ Point aPos = aRect.TopLeft();
+ aPos = GetWindow()->GetParent()->ScreenToOutputPixel( aPos );
+ GetWindow()->SetPosSizePixel( aPos, aRect.GetSize() );
+ }
+
+ if ( bShow )
+ GetWindow()->Show( true, ShowFlags::NoFocusChange | ShowFlags::NoActivate );
+ }
+
+ EndDockingData data( aRect, IsFloatingMode(), IsDockingCanceled() );
+ GetWindow()->CallEventListeners( VclEventId::WindowEndDocking, &data );
+
+ mbDocking = false;
+
+ // must be enabled in Window::Notify to prevent permanent docking during mouse move
+ mbStartDockingEnabled = false;
+
+ mbDockCanceled = bOrigDockCanceled;
+}
+
+bool ImplDockingWindowWrapper::PrepareToggleFloatingMode()
+{
+ bool bFloating = true;
+ GetWindow()->CallEventListeners( VclEventId::WindowPrepareToggleFloating, &bFloating );
+ return bFloating;
+}
+
+void ImplDockingWindowWrapper::ToggleFloatingMode()
+{
+ // notify dockingwindow/toolbox
+ // note: this must be done *before* notifying the
+ // listeners to have the toolbox in the proper state
+ if( GetWindow()->IsDockingWindow() )
+ static_cast<DockingWindow*>(GetWindow())->ToggleFloatingMode();
+
+ // now notify listeners
+ GetWindow()->CallEventListeners( VclEventId::WindowToggleFloating );
+
+ // must be enabled in Window::Notify to prevent permanent docking during mouse move
+ mbStartDockingEnabled = false;
+}
+
+void ImplDockingWindowWrapper::TitleButtonClick( TitleButton nType )
+{
+ if( nType == TitleButton::Menu )
+ {
+ ToolBox *pToolBox = dynamic_cast< ToolBox* >( GetWindow() );
+ if( pToolBox )
+ {
+ pToolBox->ExecuteCustomMenu();
+ }
+ }
+ if( nType == TitleButton::Docking )
+ {
+ SetFloatingMode( !IsFloatingMode() );
+ }
+}
+
+void ImplDockingWindowWrapper::Resizing( Size& rSize )
+{
+ // TODO: add virtual Resizing() to class Window, so we can get rid of class DockingWindow
+ DockingWindow *pDockingWindow = dynamic_cast< DockingWindow* >( GetWindow() );
+ if( pDockingWindow )
+ pDockingWindow->Resizing( rSize );
+}
+
+void ImplDockingWindowWrapper::ShowTitleButton( TitleButton nButton, bool bVisible )
+{
+ if ( mpFloatWin )
+ mpFloatWin->ShowTitleButton( nButton, bVisible );
+ else
+ {
+ if ( nButton == TitleButton::Docking )
+ mbDockBtn = bVisible;
+ else // if ( nButton == TitleButton::Hide )
+ mbHideBtn = bVisible;
+ }
+}
+
+void ImplDockingWindowWrapper::ImplPreparePopupMode()
+{
+ GetWindow()->Show( false, ShowFlags::NoFocusChange );
+
+ // prepare reparenting
+ vcl::Window* pRealParent = GetWindow()->GetWindow( GetWindowType::Parent );
+ mpOldBorderWin = GetWindow()->GetWindow( GetWindowType::Border );
+ if( mpOldBorderWin.get() == GetWindow() )
+ mpOldBorderWin = nullptr; // no border window found
+
+ // the new parent for popup mode
+ VclPtrInstance<ImplPopupFloatWin> pWin( mpParent, GetWindow()->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( GetWindow()->GetText() );
+ pWin->SetOutputSizePixel( GetWindow()->GetSizePixel() );
+
+ GetWindow()->mpWindowImpl->mpBorderWindow = nullptr;
+ GetWindow()->mpWindowImpl->mnLeftBorder = 0;
+ GetWindow()->mpWindowImpl->mnTopBorder = 0;
+ GetWindow()->mpWindowImpl->mnRightBorder = 0;
+ GetWindow()->mpWindowImpl->mnBottomBorder = 0;
+
+ // reparent borderwindow and window
+ if ( mpOldBorderWin )
+ mpOldBorderWin->SetParent( pWin );
+ GetWindow()->SetParent( pWin );
+
+ // correct border window pointers
+ GetWindow()->mpWindowImpl->mpBorderWindow = pWin;
+ pWin->mpWindowImpl->mpClientWindow = GetWindow();
+ GetWindow()->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() ||
+ ( GetWindow()->GetType() == WindowType::TOOLBOX && ToolBox::AlwaysLocked() ) )
+ nFlags &= ~FloatWinPopupFlags::AllowTearOff;
+
+ // if the subtoolbar was opened via keyboard make sure that key events
+ // will go into subtoolbar
+ if( pParentToolBox->IsKeyEvent() )
+ nFlags |= FloatWinPopupFlags::GrabFocus;
+
+ mpFloatWin->StartPopupMode( pParentToolBox, nFlags );
+ GetWindow()->Show();
+
+ if( pParentToolBox->IsKeyEvent() )
+ {
+ // send HOME key to subtoolbar in order to select first item
+ KeyEvent aEvent( 0, vcl::KeyCode( KEY_HOME ) );
+ GetWindow()->KeyInput(aEvent);
+ }
+}
+
+void ImplDockingWindowWrapper::StartPopupMode( const tools::Rectangle& rRect, FloatWinPopupFlags nFlags )
+{
+ // do nothing if window is floating
+ if( IsFloatingMode() )
+ return;
+
+ ImplPreparePopupMode();
+ mpFloatWin->StartPopupMode( rRect, nFlags );
+ GetWindow()->Show();
+}
+
+IMPL_LINK_NOARG(ImplDockingWindowWrapper, PopupModeEnd, FloatingWindow*, void)
+{
+ GetWindow()->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 = GetWindow()->GetWindow( GetWindowType::Parent );
+ 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();
+
+ // call handler - which will destroy the window and thus the wrapper as well !
+ GetWindow()->CallEventListeners( VclEventId::WindowEndPopupMode, &aData );
+}
+
+bool ImplDockingWindowWrapper::IsInPopupMode() const
+{
+ if( GetFloatingWindow() )
+ return 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 )
+ {
+ if ( PrepareToggleFloatingMode() )
+ {
+ 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 );
+ if ( mbRollUp )
+ pWin->RollUp();
+ else
+ pWin->RollDown();
+ pWin->SetRollUpOutputSizePixel( maRollUpOutSize );
+ 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 );
+ mbRollUp = mpFloatWin->IsRollUp();
+ maRollUpOutSize = mpFloatWin->GetRollUpOutputSizePixel();
+ 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( long nX, long nY,
+ long nWidth, 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 );
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/dockwin.cxx b/vcl/source/window/dockwin.cxx
new file mode 100644
index 000000000..8fc7937d6
--- /dev/null
+++ b/vcl/source/window/dockwin.cxx
@@ -0,0 +1,1061 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/accel.hxx>
+#include <vcl/event.hxx>
+#include <vcl/floatwin.hxx>
+#include <vcl/dockwin.hxx>
+#include <vcl/layout.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/idle.hxx>
+#include <vcl/settings.hxx>
+
+#include <svdata.hxx>
+#include <window.h>
+#include <brdwin.hxx>
+
+#define DOCKWIN_FLOATSTYLES (WB_SIZEABLE | WB_MOVEABLE | WB_CLOSEABLE | WB_STANDALONE | WB_ROLLABLE )
+
+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() ),
+ mbInMove( false ),
+ mnLastUserEvent( nullptr )
+{
+ // copy settings of DockingWindow
+ if ( pDockingWin )
+ {
+ 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 );
+ maDockIdle.SetDebugName( "vcl::ImplDockFloatWin maDockIdle" );
+}
+
+ImplDockFloatWin::~ImplDockFloatWin()
+{
+ disposeOnce();
+}
+
+void ImplDockFloatWin::dispose()
+{
+ if( mnLastUserEvent )
+ Application::RemoveUserEvent( mnLastUserEvent );
+
+ disposeBuilder();
+
+ mpDockWin.clear();
+ FloatingWindow::dispose();
+}
+
+IMPL_LINK_NOARG(ImplDockFloatWin, DockTimerHdl, Timer *, void)
+{
+ SAL_WARN_IF( !mpDockWin->IsFloatingMode(), "vcl", "docktimer called but not floating" );
+
+ maDockIdle.Stop();
+ PointerState aState = GetPointerState();
+
+ if( aState.mnState & KEY_MOD1 )
+ {
+ // i43499 CTRL disables docking now
+ mpDockWin->GetParent()->ImplGetFrameWindow()->HideTracking();
+ mpDockWin->EndDocking( maDockRect, true );
+ if( aState.mnState & ( MOUSE_LEFT | MOUSE_MIDDLE | MOUSE_RIGHT ) )
+ maDockIdle.Start();
+ }
+ else if( ! ( aState.mnState & ( MOUSE_LEFT | MOUSE_MIDDLE | MOUSE_RIGHT ) ) )
+ {
+ mpDockWin->GetParent()->ImplGetFrameWindow()->HideTracking();
+ mpDockWin->EndDocking( maDockRect, false );
+ }
+ else
+ {
+ mpDockWin->GetParent()->ImplGetFrameWindow()->ShowTracking( maDockRect, ShowTrackFlags::Big | ShowTrackFlags::TrackWindow );
+ maDockIdle.Start();
+ }
+}
+
+IMPL_LINK_NOARG(ImplDockFloatWin, DockingHdl, void*, void)
+{
+ PointerState aState = mpDockWin->GetParent()->GetPointerState();
+
+ mnLastUserEvent = nullptr;
+ if( mpDockWin->IsDockable() &&
+ (tools::Time::GetSystemTicks() - mnLastTicks > 500) &&
+ ( aState.mnState & ( MOUSE_LEFT | MOUSE_MIDDLE | MOUSE_RIGHT ) ) &&
+ !(aState.mnState & KEY_MOD1) ) // i43499 CTRL disables docking now
+ {
+ maDockPos = mpDockWin->GetParent()->AbsoluteScreenToOutputPixel( OutputToAbsoluteScreenPixel( Point() ) );
+ maDockPos = mpDockWin->GetParent()->OutputToScreenPixel( maDockPos ); // sfx expects screen coordinates
+
+ if( ! mpDockWin->IsDocking() )
+ mpDockWin->StartDocking();
+ maDockRect = tools::Rectangle( maDockPos, mpDockWin->GetSizePixel() );
+
+ // mouse pos also in screen pixels
+ Point aMousePos = mpDockWin->GetParent()->OutputToScreenPixel( aState.maPos );
+
+ bool bFloatMode = mpDockWin->Docking( aMousePos, maDockRect );
+ if( ! bFloatMode )
+ {
+ mpDockWin->GetParent()->ImplGetFrameWindow()->ShowTracking( maDockRect, ShowTrackFlags::Object | ShowTrackFlags::TrackWindow );
+ DockTimerHdl( nullptr );
+ }
+ else
+ {
+ mpDockWin->GetParent()->ImplGetFrameWindow()->HideTracking();
+ maDockIdle.Stop();
+ mpDockWin->EndDocking( maDockRect, true );
+ }
+ }
+ mbInMove = false;
+}
+
+void ImplDockFloatWin::Move()
+{
+ if( mbInMove )
+ return;
+
+ mbInMove = true;
+ FloatingWindow::Move();
+ mpDockWin->Move();
+
+ /*
+ * note: the window should only dock if
+ * the user releases all mouse buttons. The real problem here
+ * is that we don't get mouse events (at least not on X)
+ * if the mouse is on the decoration. So we have to start an
+ * awkward timer based process that polls the modifier/buttons
+ * to see whether they are in the right condition shortly after the
+ * last Move message.
+ */
+ if( ! mnLastUserEvent )
+ mnLastUserEvent = Application::PostUserEvent( LINK( this, ImplDockFloatWin, DockingHdl ), nullptr, true );
+}
+
+void ImplDockFloatWin::Resize()
+{
+ FloatingWindow::Resize();
+ Size aSize( GetSizePixel() );
+ mpDockWin->ImplPosSizeWindow( 0, 0, aSize.Width(), aSize.Height(), PosSizeFlags::PosSize );
+}
+
+void ImplDockFloatWin::Resizing( Size& rSize )
+{
+ FloatingWindow::Resizing( rSize );
+ mpDockWin->Resizing( rSize );
+}
+
+bool ImplDockFloatWin::Close()
+{
+ return mpDockWin->Close();
+}
+
+void DockingWindow::ImplStartDocking( const Point& rPos )
+{
+ if ( !mbDockable )
+ return;
+
+ maMouseOff = rPos;
+ mbDocking = true;
+ mbLastFloatMode = IsFloatingMode();
+ mbStartFloat = mbLastFloatMode;
+
+ // calculate FloatingBorder
+ VclPtr<FloatingWindow> pWin;
+ if ( mpFloatWin )
+ pWin = mpFloatWin;
+ else
+ pWin = VclPtr<ImplDockFloatWin>::Create( mpImplData->mpParent, mnFloatBits, nullptr );
+ pWin->GetBorder( mnDockLeft, mnDockTop, mnDockRight, mnDockBottom );
+ if ( !mpFloatWin )
+ pWin.disposeAndClear();
+
+ Point aPos = ImplOutputToFrame( Point() );
+ Size aSize = Window::GetOutputSizePixel();
+ mnTrackX = aPos.X();
+ mnTrackY = aPos.Y();
+ mnTrackWidth = aSize.Width();
+ mnTrackHeight = aSize.Height();
+
+ if ( mbLastFloatMode )
+ {
+ maMouseOff.AdjustX(mnDockLeft );
+ maMouseOff.AdjustY(mnDockTop );
+ mnTrackX -= mnDockLeft;
+ mnTrackY -= mnDockTop;
+ mnTrackWidth += mnDockLeft+mnDockRight;
+ mnTrackHeight += mnDockTop+mnDockBottom;
+ }
+
+ if ( GetSettings().GetStyleSettings().GetDragFullOptions() & DragFullOptions::Docking &&
+ !( mnFloatBits & ( WB_MOVEABLE | WB_SIZEABLE | WB_CLOSEABLE ) ) ) // no full drag when migrating to system window
+ mbDragFull = true;
+ else
+ {
+ StartDocking();
+ mbDragFull = false;
+ ImplUpdateAll();
+ ImplGetFrameWindow()->ImplUpdateAll();
+ }
+
+ StartTracking( StartTrackingFlags::KeyMod );
+}
+
+void DockingWindow::ImplInitDockingWindowData()
+{
+ mpWindowImpl->mbDockWin = true;
+ mpFloatWin = nullptr;
+ mpOldBorderWin = nullptr;
+ mpImplData.reset(new ImplData);
+ mnTrackX = 0;
+ mnTrackY = 0;
+ mnTrackWidth = 0;
+ mnTrackHeight = 0;
+ mnDockLeft = 0;
+ mnDockTop = 0;
+ mnDockRight = 0;
+ mnDockBottom = 0;
+ mnFloatBits = 0;
+ mbDockCanceled = false;
+ mbDockable = false;
+ mbDocking = false;
+ mbDragFull = false;
+ mbLastFloatMode = false;
+ mbStartFloat = false;
+ mbRollUp = 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 ) );
+ maLayoutIdle.SetDebugName( "vcl::DockingWindow maLayoutIdle" );
+}
+
+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() )
+ {
+ 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 ) :
+ Window(nType)
+{
+ ImplInitDockingWindowData();
+}
+
+DockingWindow::DockingWindow( vcl::Window* pParent, WinBits nStyle ) :
+ Window( WindowType::DOCKINGWINDOW )
+{
+ ImplInitDockingWindowData();
+ ImplInit( pParent, nStyle );
+}
+
+//Find the real parent stashed in mpDialogParent.
+void DockingWindow::doDeferredInit(WinBits nBits)
+{
+ vcl::Window *pParent = mpDialogParent;
+ mpDialogParent = nullptr;
+ ImplInit(pParent, nBits);
+ mbIsDeferredInit = false;
+}
+
+void DockingWindow::loadUI(vcl::Window* pParent, const OString& rID, const OUString& rUIXMLDescription,
+ const css::uno::Reference<css::frame::XFrame> &rFrame)
+{
+ mbIsDeferredInit = true;
+ mpDialogParent = pParent; //should be unset in doDeferredInit
+ m_pUIBuilder.reset( new VclBuilder(this, getUIRootDir(), rUIXMLDescription, rID, rFrame) );
+}
+
+DockingWindow::DockingWindow(vcl::Window* pParent, const OString& rID,
+ const OUString& rUIXMLDescription, const css::uno::Reference<css::frame::XFrame> &rFrame)
+ : Window(WindowType::DOCKINGWINDOW)
+{
+ 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 )
+ {
+ if ( rTEvt.IsTrackingEnded() )
+ {
+ mbDocking = false;
+ if ( mbDragFull )
+ {
+ // reset old state on Cancel
+ if ( rTEvt.IsTrackingCanceled() )
+ {
+ StartDocking();
+ tools::Rectangle aRect( Point( mnTrackX, mnTrackY ), Size( mnTrackWidth, mnTrackHeight ) );
+ EndDocking( aRect, mbStartFloat );
+ }
+ }
+ else
+ {
+ HideTracking();
+ if ( rTEvt.IsTrackingCanceled() )
+ {
+ mbDockCanceled = true;
+ EndDocking( tools::Rectangle( Point( mnTrackX, mnTrackY ), Size( mnTrackWidth, mnTrackHeight ) ), mbLastFloatMode );
+ mbDockCanceled = false;
+ }
+ else
+ EndDocking( tools::Rectangle( Point( mnTrackX, mnTrackY ), Size( mnTrackWidth, mnTrackHeight ) ), mbLastFloatMode );
+ }
+ }
+ // dock only for non-synthetic MouseEvents
+ else if ( !rTEvt.GetMouseEvent().IsSynthetic() || rTEvt.GetMouseEvent().IsModifierChanged() )
+ {
+ Point aMousePos = rTEvt.GetMouseEvent().GetPosPixel();
+ Point aFrameMousePos = ImplOutputToFrame( aMousePos );
+ Size aFrameSize = mpWindowImpl->mpFrameWindow->GetOutputSizePixel();
+ if ( aFrameMousePos.X() < 0 )
+ aFrameMousePos.setX( 0 );
+ if ( aFrameMousePos.Y() < 0 )
+ aFrameMousePos.setY( 0 );
+ if ( aFrameMousePos.X() > aFrameSize.Width()-1 )
+ aFrameMousePos.setX( aFrameSize.Width()-1 );
+ if ( aFrameMousePos.Y() > aFrameSize.Height()-1 )
+ aFrameMousePos.setY( aFrameSize.Height()-1 );
+ aMousePos = ImplFrameToOutput( aFrameMousePos );
+ aMousePos.AdjustX( -(maMouseOff.X()) );
+ aMousePos.AdjustY( -(maMouseOff.Y()) );
+ Point aFramePos = ImplOutputToFrame( aMousePos );
+ tools::Rectangle aTrackRect( aFramePos, Size( mnTrackWidth, mnTrackHeight ) );
+ tools::Rectangle aCompRect = aTrackRect;
+ aFramePos.AdjustX(maMouseOff.X() );
+ aFramePos.AdjustY(maMouseOff.Y() );
+ if ( mbDragFull )
+ StartDocking();
+ bool bFloatMode = Docking( aFramePos, aTrackRect );
+ if ( mbLastFloatMode != bFloatMode )
+ {
+ if ( bFloatMode )
+ {
+ aTrackRect.AdjustLeft( -mnDockLeft );
+ aTrackRect.AdjustTop( -mnDockTop );
+ aTrackRect.AdjustRight(mnDockRight );
+ aTrackRect.AdjustBottom(mnDockBottom );
+ }
+ else
+ {
+ if ( aCompRect == aTrackRect )
+ {
+ aTrackRect.AdjustLeft(mnDockLeft );
+ aTrackRect.AdjustTop(mnDockTop );
+ aTrackRect.AdjustRight( -mnDockRight );
+ aTrackRect.AdjustBottom( -mnDockBottom );
+ }
+ }
+ mbLastFloatMode = bFloatMode;
+ }
+ if ( mbDragFull )
+ {
+ Point aOldPos = OutputToScreenPixel( Point() );
+ EndDocking( aTrackRect, mbLastFloatMode );
+ // repaint if state or position has changed
+ if ( aOldPos != OutputToScreenPixel( Point() ) )
+ {
+ ImplUpdateAll();
+ ImplGetFrameWindow()->ImplUpdateAll();
+ }
+// EndDocking( aTrackRect, mbLastFloatMode );
+ }
+ else
+ {
+ ShowTrackFlags nTrackStyle;
+ if ( bFloatMode )
+ nTrackStyle = ShowTrackFlags::Big;
+ else
+ nTrackStyle = ShowTrackFlags::Object;
+ tools::Rectangle aShowTrackRect = aTrackRect;
+ aShowTrackRect.SetPos( ImplFrameToOutput( aShowTrackRect.TopLeft() ) );
+ ShowTracking( aShowTrackRect, nTrackStyle );
+
+ // recalculate mouse offset, as the rectangle was changed
+ maMouseOff.setX( aFramePos.X() - aTrackRect.Left() );
+ maMouseOff.setY( aFramePos.Y() - aTrackRect.Top() );
+ }
+
+ mnTrackX = aTrackRect.Left();
+ mnTrackY = aTrackRect.Top();
+ mnTrackWidth = aTrackRect.GetWidth();
+ mnTrackHeight = aTrackRect.GetHeight();
+ }
+ }
+}
+
+bool DockingWindow::EventNotify( NotifyEvent& rNEvt )
+{
+ if( GetDockingManager()->IsDockable( this ) ) // new docking interface
+ return Window::EventNotify( rNEvt );
+
+ if ( mbDockable )
+ {
+ const bool bDockingSupportCrippled = !StyleSettings::GetDockingFloatsSupported();
+
+ if ( rNEvt.GetType() == MouseNotifyEvent::MOUSEBUTTONDOWN )
+ {
+ const MouseEvent* pMEvt = rNEvt.GetMouseEvent();
+ if ( pMEvt->IsLeft() )
+ {
+ if (!bDockingSupportCrippled && pMEvt->IsMod1() && (pMEvt->GetClicks() == 2) )
+ {
+ SetFloatingMode( !IsFloatingMode() );
+ if ( IsFloatingMode() )
+ ToTop( ToTopFlags::GrabFocusOnly );
+ return true;
+ }
+ else if ( pMEvt->GetClicks() == 1 )
+ {
+ // check if window is floating standalone (IsFloating())
+ // or only partially floating and still docked with one border
+ // ( !mpWindowImpl->mbFrame)
+ if( ! IsFloatingMode() || ! mpFloatWin->mpWindowImpl->mbFrame )
+ {
+ Point aPos = pMEvt->GetPosPixel();
+ vcl::Window* pWindow = rNEvt.GetWindow();
+ if ( pWindow != this )
+ {
+ aPos = pWindow->OutputToScreenPixel( aPos );
+ aPos = ScreenToOutputPixel( aPos );
+ }
+ ImplStartDocking( aPos );
+ }
+ return true;
+ }
+ }
+ }
+ else if( rNEvt.GetType() == MouseNotifyEvent::KEYINPUT )
+ {
+ const vcl::KeyCode& rKey = rNEvt.GetKeyEvent()->GetKeyCode();
+ if( rKey.GetCode() == KEY_F10 && rKey.GetModifier() &&
+ rKey.IsShift() && rKey.IsMod1() && !bDockingSupportCrippled )
+ {
+ SetFloatingMode( !IsFloatingMode() );
+ if ( IsFloatingMode() )
+ ToTop( ToTopFlags::GrabFocusOnly );
+ return true;
+ }
+ }
+ }
+
+ return Window::EventNotify( rNEvt );
+}
+
+void DockingWindow::StartDocking()
+{
+ mbDocking = true;
+}
+
+bool DockingWindow::Docking( const Point&, tools::Rectangle& )
+{
+ return IsFloatingMode();
+}
+
+void DockingWindow::EndDocking( const tools::Rectangle& rRect, bool bFloatMode )
+{
+ bool bOrigDockCanceled = mbDockCanceled;
+ if (bFloatMode && !StyleSettings::GetDockingFloatsSupported())
+ mbDockCanceled = true;
+
+ if ( !IsDockingCanceled() )
+ {
+ if ( bFloatMode != IsFloatingMode() )
+ {
+ SetFloatingMode( bFloatMode );
+ if ( IsFloatingMode() )
+ ToTop( ToTopFlags::GrabFocusOnly );
+ if ( bFloatMode && mpFloatWin )
+ mpFloatWin->SetPosSizePixel( rRect.TopLeft(), rRect.GetSize() );
+ }
+ if ( !bFloatMode )
+ {
+ Point aPos = rRect.TopLeft();
+ aPos = GetParent()->ScreenToOutputPixel( aPos );
+ Window::SetPosSizePixel( aPos, rRect.GetSize() );
+ }
+ }
+ mbDocking = false;
+ mbDockCanceled = bOrigDockCanceled;
+}
+
+bool DockingWindow::PrepareToggleFloatingMode()
+{
+ return true;
+}
+
+bool DockingWindow::Close()
+{
+ VclPtr<vcl::Window> xWindow = this;
+ CallEventListeners( VclEventId::WindowClose );
+ if ( xWindow->IsDisposed() )
+ return false;
+
+ if ( mpWindowImpl->mxWindowPeer.is() && IsCreatedWithToolkit() )
+ return false;
+
+ Show( false, ShowFlags::NoFocusChange );
+ return true;
+}
+
+void DockingWindow::ToggleFloatingMode()
+{
+}
+
+void DockingWindow::Resizing( Size& )
+{
+}
+
+void DockingWindow::DoInitialLayout()
+{
+ if (GetSettings().GetStyleSettings().GetAutoMnemonic())
+ Accelerator::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 )
+ {
+ if ( PrepareToggleFloatingMode() ) // changes to floating mode can be vetoed
+ {
+ 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 );
+ if ( mbRollUp )
+ pWin->RollUp();
+ else
+ pWin->RollDown();
+ pWin->SetRollUpOutputSizePixel( maRollUpOutSize );
+ 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 );
+ mbRollUp = mpFloatWin->IsRollUp();
+ maRollUpOutSize = mpFloatWin->GetRollUpOutputSizePixel();
+ 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( long nX, long nY,
+ long nWidth, 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
+ {
+ mpFloatWin->SetOutputSizePixel(Size(nWidth, nHeight));
+ mpFloatWin->SetPosPixel(Point(nX, nY));
+ }
+ }
+
+ if (::isLayoutEnabled(this))
+ setPosSizeOnContainee();
+}
+
+Point DockingWindow::GetPosPixel() const
+{
+ ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this );
+ if( pWrapper )
+ {
+ if ( pWrapper->mpFloatWin )
+ return pWrapper->mpFloatWin->GetPosPixel();
+ else
+ return Window::GetPosPixel();
+ }
+
+ if ( mpFloatWin )
+ return mpFloatWin->GetPosPixel();
+ else
+ return Window::GetPosPixel();
+}
+
+Size DockingWindow::GetSizePixel() const
+{
+ ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this );
+ if( pWrapper )
+ {
+ if ( pWrapper->mpFloatWin )
+ return pWrapper->mpFloatWin->GetSizePixel();
+ else
+ return Window::GetSizePixel();
+ }
+
+ if ( mpFloatWin )
+ return mpFloatWin->GetSizePixel();
+ else
+ return Window::GetSizePixel();
+}
+
+void DockingWindow::SetOutputSizePixel( const Size& rNewSize )
+{
+ ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this );
+ if( pWrapper )
+ {
+ if ( pWrapper->mpFloatWin )
+ pWrapper->mpFloatWin->SetOutputSizePixel( rNewSize );
+ else
+ Window::SetOutputSizePixel( rNewSize );
+ return;
+ }
+
+ if ( mpFloatWin )
+ mpFloatWin->SetOutputSizePixel( rNewSize );
+ else
+ Window::SetOutputSizePixel( rNewSize );
+}
+
+Size DockingWindow::GetOutputSizePixel() const
+{
+ ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this );
+ if( pWrapper )
+ {
+ if ( pWrapper->mpFloatWin )
+ return pWrapper->mpFloatWin->GetOutputSizePixel();
+ else
+ return Window::GetOutputSizePixel();
+ }
+
+ if ( mpFloatWin )
+ return mpFloatWin->GetOutputSizePixel();
+ else
+ return Window::GetOutputSizePixel();
+}
+
+Point DockingWindow::GetFloatingPos() const
+{
+ ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this );
+ if( pWrapper )
+ {
+ if ( pWrapper->mpFloatWin )
+ {
+ WindowStateData aData;
+ aData.SetMask( WindowStateMask::Pos );
+ pWrapper->mpFloatWin->GetWindowStateData( aData );
+ Point aPos( aData.GetX(), aData.GetY() );
+ aPos = pWrapper->mpFloatWin->GetParent()->ImplGetFrameWindow()->AbsoluteScreenToOutputPixel( aPos );
+ return aPos;
+ }
+ else
+ return maFloatPos;
+ }
+
+ if ( mpFloatWin )
+ {
+ WindowStateData aData;
+ aData.SetMask( WindowStateMask::Pos );
+ mpFloatWin->GetWindowStateData( aData );
+ Point aPos( aData.GetX(), aData.GetY() );
+ aPos = mpFloatWin->GetParent()->ImplGetFrameWindow()->AbsoluteScreenToOutputPixel( aPos );
+ return aPos;
+ }
+ else
+ return maFloatPos;
+}
+
+bool DockingWindow::IsFloatingMode() const
+{
+ ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this );
+ if( pWrapper )
+ return pWrapper->IsFloatingMode();
+ else
+ return (mpFloatWin != nullptr);
+}
+
+void DockingWindow::SetMaxOutputSizePixel( const Size& rSize )
+{
+ if ( mpFloatWin )
+ mpFloatWin->SetMaxOutputSizePixel( rSize );
+ mpImplData->maMaxOutSize = rSize;
+}
+
+void DockingWindow::SetText(const OUString& rStr)
+{
+ setDeferredProperties();
+ Window::SetText(rStr);
+}
+
+OUString DockingWindow::GetText() const
+{
+ const_cast<DockingWindow*>(this)->setDeferredProperties();
+ return Window::GetText();
+}
+
+bool DockingWindow::isLayoutEnabled() const
+{
+ //pre dtor called, and single child is a container => we're layout enabled
+ return mpImplData && ::isLayoutEnabled(this);
+}
+
+void DockingWindow::setOptimalLayoutSize()
+{
+ maLayoutIdle.Stop();
+
+ //resize DockingWindow to fit requisition on initial show
+ Size aSize = get_preferred_size();
+
+ Size aMax(bestmaxFrameSizeForScreenSize(GetDesktopRectPixel().GetSize()));
+
+ aSize.setWidth( std::min(aMax.Width(), aSize.Width()) );
+ aSize.setHeight( std::min(aMax.Height(), aSize.Height()) );
+
+ SetMinOutputSizePixel(aSize);
+ setPosSizeOnContainee();
+}
+
+void DockingWindow::setPosSizeOnContainee()
+{
+ Size aSize = GetOutputSizePixel();
+
+ // Don't make the border width accessible via get_border_width(),
+ // otherwise the floating window will handle the border as well.
+ sal_Int32 nBorderWidth = mpWindowImpl->mnBorderWidth;
+
+ aSize.AdjustWidth( -(2 * nBorderWidth) );
+ aSize.AdjustHeight( -(2 * nBorderWidth) );
+
+ Window* pBox = GetWindow(GetWindowType::FirstChild);
+ assert(pBox);
+ VclContainer::setLayoutAllocation(*pBox, Point(nBorderWidth, nBorderWidth), aSize);
+}
+
+Size DockingWindow::GetOptimalSize() const
+{
+ if (!isLayoutEnabled())
+ return Window::GetOptimalSize();
+
+ Size aSize = VclContainer::getLayoutRequisition(*GetWindow(GetWindowType::FirstChild));
+
+ // Don't make the border width accessible via get_border_width(),
+ // otherwise the floating window will handle the border as well.
+ sal_Int32 nBorderWidth = mpWindowImpl->mnBorderWidth;
+
+ aSize.AdjustHeight(2 * nBorderWidth );
+ aSize.AdjustWidth(2 * nBorderWidth );
+
+ return aSize;
+}
+
+void DockingWindow::queue_resize(StateChangedType eReason)
+{
+ bool bTriggerLayout = true;
+ if (maLayoutIdle.IsActive() || mbIsCalculatingInitialLayoutSize)
+ {
+ bTriggerLayout = false;
+ }
+ if (!isLayoutEnabled())
+ {
+ bTriggerLayout = false;
+ }
+ if (bTriggerLayout)
+ {
+ InvalidateSizeCache();
+ maLayoutIdle.Start();
+ }
+ vcl::Window::queue_resize(eReason);
+}
+
+IMPL_LINK_NOARG(DockingWindow, ImplHandleLayoutTimerHdl, Timer*, void)
+{
+ if (!isLayoutEnabled())
+ {
+ SAL_WARN_IF(GetWindow(GetWindowType::FirstChild), "vcl.layout", "DockingWindow has become non-layout because extra children have been added directly to it.");
+ return;
+ }
+ setPosSizeOnContainee();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/errinf.cxx b/vcl/source/window/errinf.cxx
new file mode 100644
index 000000000..8e08dc361
--- /dev/null
+++ b/vcl/source/window/errinf.cxx
@@ -0,0 +1,319 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <rtl/instance.hxx>
+#include <sal/log.hxx>
+
+#include <tools/debug.hxx>
+#include <vcl/errinf.hxx>
+
+#include <algorithm>
+#include <vector>
+
+class ErrorHandler;
+
+namespace {
+
+class TheErrorRegistry: public rtl::Static<ErrorRegistry, TheErrorRegistry> {};
+
+}
+
+class ErrorStringFactory
+{
+public:
+ static bool CreateString(const ErrorInfo*, OUString&);
+};
+
+bool ErrorStringFactory::CreateString(const ErrorInfo* pInfo, OUString& rStr)
+{
+ for(const ErrorHandler *pHdlr : TheErrorRegistry::get().errorHandlers)
+ {
+ if(pHdlr->CreateString(pInfo, rStr))
+ return true;
+ }
+ return false;
+}
+
+ErrorRegistry::ErrorRegistry()
+ : pDsp(nullptr)
+ , bIsWindowDsp(false)
+ , nNextError(0)
+{
+ for(DynamicErrorInfo*& rp : ppDynErrInfo)
+ rp = nullptr;
+}
+
+void ErrorRegistry::RegisterDisplay(BasicDisplayErrorFunc *aDsp)
+{
+ ErrorRegistry &rData = TheErrorRegistry::get();
+ rData.bIsWindowDsp = false;
+ rData.pDsp = reinterpret_cast< DisplayFnPtr >(aDsp);
+}
+
+void ErrorRegistry::RegisterDisplay(WindowDisplayErrorFunc *aDsp)
+{
+ ErrorRegistry &rData = TheErrorRegistry::get();
+ rData.bIsWindowDsp = true;
+ rData.pDsp = reinterpret_cast< DisplayFnPtr >(aDsp);
+}
+
+void ErrorRegistry::Reset()
+{
+ ErrorRegistry &rData = TheErrorRegistry::get();
+ rData = ErrorRegistry();
+}
+
+static void aDspFunc(const OUString &rErr, const OUString &rAction)
+{
+ SAL_WARN("vcl", "Action: " << rAction << " Error: " << rErr);
+}
+
+ErrorHandler::ErrorHandler()
+{
+ ErrorRegistry &rData = TheErrorRegistry::get();
+ rData.errorHandlers.insert(rData.errorHandlers.begin(), this);
+
+ if(!rData.pDsp)
+ ErrorRegistry::RegisterDisplay(&aDspFunc);
+}
+
+ErrorHandler::~ErrorHandler()
+{
+ auto &rErrorHandlers = TheErrorRegistry::get().errorHandlers;
+ rErrorHandlers.erase( ::std::remove(rErrorHandlers.begin(), rErrorHandlers.end(), this),
+ rErrorHandlers.end());
+}
+
+bool ErrorHandler::GetErrorString(ErrCode nErrCodeId, OUString& rErrStr)
+{
+ OUString aErr;
+
+ if(!nErrCodeId || nErrCodeId == ERRCODE_ABORT)
+ return false;
+
+ std::unique_ptr<ErrorInfo> pInfo = ErrorInfo::GetErrorInfo(nErrCodeId);
+
+ if (ErrorStringFactory::CreateString(pInfo.get(),aErr))
+ {
+ rErrStr = aErr;
+ return true;
+ }
+
+ return false;
+}
+
+DialogMask ErrorHandler::HandleError(ErrCode nErrCodeId, weld::Window *pParent, DialogMask nFlags)
+{
+ if (nErrCodeId == ERRCODE_NONE || nErrCodeId == ERRCODE_ABORT)
+ return DialogMask::NONE;
+
+ ErrorRegistry &rData = TheErrorRegistry::get();
+ std::unique_ptr<ErrorInfo> pInfo = ErrorInfo::GetErrorInfo(nErrCodeId);
+ OUString aAction;
+
+ if (!rData.contexts.empty())
+ {
+ rData.contexts.front()->GetString(pInfo->GetErrorCode(), aAction);
+
+ for(ErrorContext *pCtx : rData.contexts)
+ {
+ if(pCtx->GetParent())
+ {
+ pParent = pCtx->GetParent();
+ break;
+ }
+ }
+ }
+
+ bool bWarning = nErrCodeId.IsWarning();
+ DialogMask nErrFlags = DialogMask::ButtonDefaultsOk | DialogMask::ButtonsOk;
+ if (bWarning)
+ nErrFlags |= DialogMask::MessageWarning;
+ else
+ nErrFlags |= DialogMask::MessageError;
+
+ DynamicErrorInfo* pDynPtr = dynamic_cast<DynamicErrorInfo*>(pInfo.get());
+ if(pDynPtr)
+ {
+ DialogMask nDynFlags = pDynPtr->GetDialogMask();
+ if( nDynFlags != DialogMask::NONE )
+ nErrFlags = nDynFlags;
+ }
+
+ OUString aErr;
+ if (ErrorStringFactory::CreateString(pInfo.get(), aErr))
+ {
+ if(!rData.pDsp)
+ {
+ SAL_WARN( "vcl", "Action: " << aAction << "Error: " << aErr);
+ }
+ else
+ {
+ if(!rData.bIsWindowDsp)
+ {
+ (*reinterpret_cast<BasicDisplayErrorFunc*>(rData.pDsp))(aErr,aAction);
+ return DialogMask::NONE;
+ }
+ else
+ {
+ if (nFlags != DialogMask::MAX)
+ nErrFlags = nFlags;
+
+ return (*reinterpret_cast<WindowDisplayErrorFunc*>(rData.pDsp))(
+ pParent, nErrFlags, aErr, aAction);
+ }
+ }
+ }
+
+ SAL_WARN( "vcl", "Error not handled " << pInfo->GetErrorCode());
+ // Error 1 (ERRCODE_ABORT) is classified as a General Error in sfx
+ if (pInfo->GetErrorCode() != ERRCODE_ABORT)
+ HandleError(ERRCODE_ABORT);
+ else
+ OSL_FAIL("ERRCODE_ABORT not handled");
+
+ return DialogMask::NONE;
+}
+
+struct ImplErrorContext
+{
+ weld::Window *pWin;
+};
+
+ErrorContext::ErrorContext(weld::Window *pWinP)
+ : pImpl( new ImplErrorContext )
+{
+ pImpl->pWin = pWinP;
+ TheErrorRegistry::get().contexts.insert(TheErrorRegistry::get().contexts.begin(), this);
+}
+
+ErrorContext::~ErrorContext()
+{
+ auto &rContexts = TheErrorRegistry::get().contexts;
+ rContexts.erase( ::std::remove(rContexts.begin(), rContexts.end(), this), rContexts.end());
+}
+
+ErrorContext *ErrorContext::GetContext()
+{
+ return TheErrorRegistry::get().contexts.empty() ? nullptr : TheErrorRegistry::get().contexts.front();
+}
+
+weld::Window* ErrorContext::GetParent()
+{
+ return pImpl ? pImpl->pWin : nullptr;
+}
+
+class ImplDynamicErrorInfo
+{
+ friend class DynamicErrorInfo;
+ friend class ErrorInfo;
+
+private:
+ explicit ImplDynamicErrorInfo(DialogMask nInMask)
+ : nMask(nInMask)
+ {
+ }
+ void RegisterError(DynamicErrorInfo *);
+ static void UnRegisterError(DynamicErrorInfo const *);
+ static std::unique_ptr<ErrorInfo> GetDynamicErrorInfo(ErrCode nId);
+
+ ErrCode nErrId;
+ DialogMask nMask;
+
+};
+
+void ImplDynamicErrorInfo::RegisterError(DynamicErrorInfo *pDynErrInfo)
+{
+ // Register dynamic identifier
+ ErrorRegistry& rData = TheErrorRegistry::get();
+ nErrId = ErrCode(((sal_uInt32(rData.nNextError) + 1) << ERRCODE_DYNAMIC_SHIFT) +
+ sal_uInt32(pDynErrInfo->GetErrorCode()));
+
+ if(rData.ppDynErrInfo[rData.nNextError])
+ delete rData.ppDynErrInfo[rData.nNextError];
+
+ rData.ppDynErrInfo[rData.nNextError] = pDynErrInfo;
+
+ if(++rData.nNextError>=ERRCODE_DYNAMIC_COUNT)
+ rData.nNextError=0;
+}
+
+void ImplDynamicErrorInfo::UnRegisterError(DynamicErrorInfo const *pDynErrInfo)
+{
+ DynamicErrorInfo **ppDynErrInfo = TheErrorRegistry::get().ppDynErrInfo;
+ sal_uInt32 nIdx = ErrCode(*pDynErrInfo).GetDynamic() - 1;
+ DBG_ASSERT(ppDynErrInfo[nIdx] == pDynErrInfo, "ErrHdl: Error not found");
+
+ if(ppDynErrInfo[nIdx]==pDynErrInfo)
+ ppDynErrInfo[nIdx]=nullptr;
+}
+
+std::unique_ptr<ErrorInfo> ImplDynamicErrorInfo::GetDynamicErrorInfo(ErrCode nId)
+{
+ sal_uInt32 nIdx = nId.GetDynamic() - 1;
+ DynamicErrorInfo* pDynErrInfo = TheErrorRegistry::get().ppDynErrInfo[nIdx];
+
+ if(pDynErrInfo && ErrCode(*pDynErrInfo)==nId)
+ return std::unique_ptr<ErrorInfo>(pDynErrInfo);
+ else
+ return std::make_unique<ErrorInfo>(nId.StripDynamic());
+}
+
+std::unique_ptr<ErrorInfo> ErrorInfo::GetErrorInfo(ErrCode nId)
+{
+ if(nId.IsDynamic())
+ return ImplDynamicErrorInfo::GetDynamicErrorInfo(nId);
+ else
+ return std::make_unique<ErrorInfo>(nId);
+}
+
+ErrorInfo::~ErrorInfo()
+{
+}
+
+DynamicErrorInfo::DynamicErrorInfo(ErrCode nArgUserId, DialogMask nMask)
+: ErrorInfo(nArgUserId),
+ pImpl(new ImplDynamicErrorInfo(nMask))
+{
+ pImpl->RegisterError(this);
+}
+
+DynamicErrorInfo::~DynamicErrorInfo()
+{
+ ImplDynamicErrorInfo::UnRegisterError(this);
+}
+
+DynamicErrorInfo::operator ErrCode() const
+{
+ return pImpl->nErrId;
+}
+
+DialogMask DynamicErrorInfo::GetDialogMask() const
+{
+ return pImpl->nMask;
+}
+
+StringErrorInfo::StringErrorInfo(
+ ErrCode nArgUserId, const OUString& aStringP, DialogMask nMask)
+: DynamicErrorInfo(nArgUserId, nMask), aString(aStringP)
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/event.cxx b/vcl/source/window/event.cxx
new file mode 100644
index 000000000..a31964ecd
--- /dev/null
+++ b/vcl/source/window/event.cxx
@@ -0,0 +1,678 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <vcl/opengl/OpenGLWrapper.hxx>
+#include <sal/log.hxx>
+
+#include <window.h>
+#include <svdata.hxx>
+#include <salframe.hxx>
+#include <config_features.h>
+#include <comphelper/scopeguard.hxx>
+
+namespace vcl {
+
+void Window::DataChanged( const DataChangedEvent& )
+{
+}
+
+void Window::NotifyAllChildren( DataChangedEvent& rDCEvt )
+{
+ CompatDataChanged( rDCEvt );
+
+ vcl::Window* pChild = mpWindowImpl->mpFirstChild;
+ while ( pChild )
+ {
+ pChild->NotifyAllChildren( rDCEvt );
+ pChild = pChild->mpWindowImpl->mpNext;
+ }
+}
+
+bool Window::PreNotify( NotifyEvent& rNEvt )
+{
+ bool bDone = false;
+ if ( mpWindowImpl->mpParent && !ImplIsOverlapWindow() )
+ bDone = mpWindowImpl->mpParent->CompatPreNotify( rNEvt );
+
+ if ( !bDone )
+ {
+ if( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS )
+ {
+ bool bCompoundFocusChanged = false;
+ if ( mpWindowImpl->mbCompoundControl && !mpWindowImpl->mbCompoundControlHasFocus && HasChildPathFocus() )
+ {
+ mpWindowImpl->mbCompoundControlHasFocus = true;
+ bCompoundFocusChanged = true;
+ }
+
+ if ( bCompoundFocusChanged || ( rNEvt.GetWindow() == this ) )
+ CallEventListeners( VclEventId::WindowGetFocus );
+ }
+ else if( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
+ {
+ bool bCompoundFocusChanged = false;
+ if ( mpWindowImpl->mbCompoundControl && mpWindowImpl->mbCompoundControlHasFocus && !HasChildPathFocus() )
+ {
+ mpWindowImpl->mbCompoundControlHasFocus = false ;
+ bCompoundFocusChanged = true;
+ }
+
+ if ( bCompoundFocusChanged || ( rNEvt.GetWindow() == this ) )
+ CallEventListeners( VclEventId::WindowLoseFocus );
+ }
+
+ // #82968# mouse and key events will be notified after processing ( in ImplNotifyKeyMouseCommandEventListeners() )!
+ // see also ImplHandleMouseEvent(), ImplHandleKey()
+
+ }
+
+ return bDone;
+}
+
+namespace
+{
+ bool parentNotDialogControl(Window* pWindow)
+ {
+ vcl::Window* pParent = getNonLayoutParent(pWindow);
+ if (!pParent)
+ return true;
+ return ((pParent->GetStyle() & (WB_DIALOGCONTROL | WB_NODIALOGCONTROL)) != WB_DIALOGCONTROL);
+ }
+}
+
+bool Window::EventNotify( NotifyEvent& rNEvt )
+{
+ bool bRet = false;
+
+ if (IsDisposed())
+ return false;
+
+ // check for docking window
+ // but do nothing if window is docked and locked
+ ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this );
+ if (pWrapper && !( !pWrapper->IsFloatingMode() && pWrapper->IsLocked() ))
+ {
+ const bool bDockingSupportCrippled = !StyleSettings::GetDockingFloatsSupported();
+
+ if ( rNEvt.GetType() == MouseNotifyEvent::MOUSEBUTTONDOWN )
+ {
+ const MouseEvent* pMEvt = rNEvt.GetMouseEvent();
+ bool bHit = pWrapper->GetDragArea().IsInside( pMEvt->GetPosPixel() );
+ if ( pMEvt->IsLeft() )
+ {
+ if (!bDockingSupportCrippled && pMEvt->IsMod1() && (pMEvt->GetClicks() == 2))
+ {
+ // ctrl double click toggles floating mode
+ pWrapper->SetFloatingMode( !pWrapper->IsFloatingMode() );
+ return true;
+ }
+ else if ( pMEvt->GetClicks() == 1 && bHit)
+ {
+ // allow start docking during mouse move
+ pWrapper->ImplEnableStartDocking();
+ return true;
+ }
+ }
+ }
+ else if ( rNEvt.GetType() == MouseNotifyEvent::MOUSEMOVE )
+ {
+ const MouseEvent* pMEvt = rNEvt.GetMouseEvent();
+ bool bHit = pWrapper->GetDragArea().IsInside( pMEvt->GetPosPixel() );
+ if ( pMEvt->IsLeft() )
+ {
+ // check if a single click initiated this sequence ( ImplStartDockingEnabled() )
+ // check if window is docked and
+ if( pWrapper->ImplStartDockingEnabled() && !pWrapper->IsFloatingMode() &&
+ !pWrapper->IsDocking() && bHit )
+ {
+ Point aPos = pMEvt->GetPosPixel();
+ vcl::Window* pWindow = rNEvt.GetWindow();
+ if ( pWindow != this )
+ {
+ aPos = pWindow->OutputToScreenPixel( aPos );
+ aPos = ScreenToOutputPixel( aPos );
+ }
+ pWrapper->ImplStartDocking( aPos );
+ }
+ return true;
+ }
+ }
+ else if( rNEvt.GetType() == MouseNotifyEvent::KEYINPUT )
+ {
+ const vcl::KeyCode& rKey = rNEvt.GetKeyEvent()->GetKeyCode();
+ if (rKey.GetCode() == KEY_F10 && rKey.GetModifier() &&
+ rKey.IsShift() && rKey.IsMod1() && !bDockingSupportCrippled)
+ {
+ pWrapper->SetFloatingMode( !pWrapper->IsFloatingMode() );
+ /* At this point the floating toolbar frame does not have the
+ * input focus since these frames don't get the focus per default
+ * To enable keyboard handling of this toolbar set the input focus
+ * to the frame. This needs to be done with ToTop since GrabFocus
+ * would not notice any change since "this" already has the focus.
+ */
+ if( pWrapper->IsFloatingMode() )
+ ToTop( ToTopFlags::GrabFocusOnly );
+ return true;
+ }
+ }
+ }
+
+ // manage the dialogs
+ if ( (GetStyle() & (WB_DIALOGCONTROL | WB_NODIALOGCONTROL)) == WB_DIALOGCONTROL )
+ {
+ // if the parent also has dialog control activated, the parent takes over control
+ if ( (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) || (rNEvt.GetType() == MouseNotifyEvent::KEYUP) )
+ {
+ if (ImplIsOverlapWindow() || parentNotDialogControl(this))
+ {
+ bRet = ImplDlgCtrl( *rNEvt.GetKeyEvent(), rNEvt.GetType() == MouseNotifyEvent::KEYINPUT );
+ }
+ }
+ else if ( (rNEvt.GetType() == MouseNotifyEvent::GETFOCUS) || (rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS) )
+ {
+ ImplDlgCtrlFocusChanged( rNEvt.GetWindow(), rNEvt.GetType() == MouseNotifyEvent::GETFOCUS );
+ if ( (rNEvt.GetWindow() == this) && (rNEvt.GetType() == MouseNotifyEvent::GETFOCUS) &&
+ !(GetStyle() & WB_TABSTOP) && !(mpWindowImpl->mnDlgCtrlFlags & DialogControlFlags::WantFocus) )
+ {
+ vcl::Window* pFirstChild = ImplGetDlgWindow( 0, GetDlgWindowType::First );
+ if ( pFirstChild )
+ pFirstChild->ImplControlFocus();
+ }
+ }
+ }
+
+ if ( !bRet )
+ {
+ if ( mpWindowImpl->mpParent && !ImplIsOverlapWindow() )
+ bRet = mpWindowImpl->mpParent->CompatNotify( rNEvt );
+ }
+
+ return bRet;
+}
+
+void Window::CallEventListeners( VclEventId nEvent, void* pData )
+{
+ VclWindowEvent aEvent( this, nEvent, pData );
+
+ VclPtr<vcl::Window> xWindow = this;
+
+ Application::ImplCallEventListeners( aEvent );
+
+ if ( 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]()
+ {
+ if (!xWindow->IsDisposed())
+ {
+ rWindowImpl.mnEventListenersIteratingCount--;
+ if (rWindowImpl.mnEventListenersIteratingCount == 0)
+ rWindowImpl.maEventListenersDeleted.clear();
+ }
+ }
+ );
+ for ( const Link<VclWindowEvent&,void>& rLink : aCopy )
+ {
+ if (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 ( 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]()
+ {
+ if (!xWindow->IsDisposed())
+ {
+ rWindowImpl.mnChildEventListenersIteratingCount--;
+ if (rWindowImpl.mnChildEventListenersIteratingCount == 0)
+ rWindowImpl.maChildEventListenersDeleted.clear();
+ }
+ }
+ );
+ for ( const Link<VclWindowEvent&,void>& rLink : aCopy )
+ {
+ if (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 ( xWindow->IsDisposed() )
+ return;
+
+ xWindow = xWindow->GetParent();
+ }
+}
+
+void Window::FireVclEvent( VclSimpleEvent& rEvent )
+{
+ Application::ImplCallEventListeners(rEvent);
+}
+
+void Window::AddEventListener( const Link<VclWindowEvent&,void>& rEventListener )
+{
+ mpWindowImpl->maEventListeners.push_back( rEventListener );
+}
+
+void Window::RemoveEventListener( const Link<VclWindowEvent&,void>& rEventListener )
+{
+ if (mpWindowImpl)
+ {
+ auto& rListeners = mpWindowImpl->maEventListeners;
+ rListeners.erase( std::remove(rListeners.begin(), rListeners.end(), rEventListener ), rListeners.end() );
+ if (mpWindowImpl->mnEventListenersIteratingCount)
+ mpWindowImpl->maEventListenersDeleted.insert(rEventListener);
+ }
+}
+
+void Window::AddChildEventListener( const Link<VclWindowEvent&,void>& rEventListener )
+{
+ mpWindowImpl->maChildEventListeners.push_back( rEventListener );
+}
+
+void Window::RemoveChildEventListener( const Link<VclWindowEvent&,void>& rEventListener )
+{
+ if (mpWindowImpl)
+ {
+ auto& rListeners = mpWindowImpl->maChildEventListeners;
+ rListeners.erase( std::remove(rListeners.begin(), rListeners.end(), rEventListener ), rListeners.end() );
+ if (mpWindowImpl->mnChildEventListenersIteratingCount)
+ mpWindowImpl->maChildEventListenersDeleted.insert(rEventListener);
+ }
+}
+
+ImplSVEvent * Window::PostUserEvent( const Link<void*,void>& rLink, void* pCaller, bool bReferenceLink )
+{
+ std::unique_ptr<ImplSVEvent> pSVEvent(new ImplSVEvent);
+ pSVEvent->mpData = pCaller;
+ pSVEvent->maLink = rLink;
+ pSVEvent->mpWindow = this;
+ pSVEvent->mbCall = true;
+ if (bReferenceLink)
+ {
+ // Double check that this is indeed a vcl::Window instance.
+ assert(dynamic_cast<vcl::Window *>(
+ static_cast<OutputDevice *>(rLink.GetInstance())) ==
+ static_cast<vcl::Window *>(rLink.GetInstance()));
+ pSVEvent->mpInstanceRef = static_cast<vcl::Window *>(rLink.GetInstance());
+ }
+
+ auto pTmpEvent = pSVEvent.get();
+ if (!mpWindowImpl->mpFrame->PostEvent( std::move(pSVEvent) ))
+ return nullptr;
+ return pTmpEvent;
+}
+
+void Window::RemoveUserEvent( ImplSVEvent * nUserEvent )
+{
+ SAL_WARN_IF( nUserEvent->mpWindow.get() != this, "vcl",
+ "Window::RemoveUserEvent(): Event doesn't send to this window or is already removed" );
+ SAL_WARN_IF( !nUserEvent->mbCall, "vcl",
+ "Window::RemoveUserEvent(): Event is already removed" );
+
+ if ( nUserEvent->mpWindow )
+ {
+ nUserEvent->mpWindow = nullptr;
+ }
+
+ nUserEvent->mbCall = false;
+}
+
+
+static MouseEvent ImplTranslateMouseEvent( const MouseEvent& rE, vcl::Window const * pSource, vcl::Window const * pDest )
+{
+ // the mouse event occurred in a different window, we need to translate the coordinates of
+ // the mouse cursor within that (source) window to the coordinates the mouse cursor would
+ // be in the destination window
+ Point aPos = pSource->OutputToScreenPixel( rE.GetPosPixel() );
+ return MouseEvent( pDest->ScreenToOutputPixel( aPos ), rE.GetClicks(), rE.GetMode(), rE.GetButtons(), rE.GetModifier() );
+}
+
+void Window::ImplNotifyKeyMouseCommandEventListeners( NotifyEvent& rNEvt )
+{
+ if( rNEvt.GetType() == MouseNotifyEvent::COMMAND )
+ {
+ const CommandEvent* pCEvt = rNEvt.GetCommandEvent();
+ if ( pCEvt->GetCommand() != CommandEventId::ContextMenu )
+ // non context menu events are not to be notified up the chain
+ // so we return immediately
+ return;
+
+ if ( mpWindowImpl->mbCompoundControl || ( rNEvt.GetWindow() == this ) )
+ {
+ // not interested: The event listeners are already called in ::Command,
+ // and calling them here a second time doesn't make sense
+ if ( rNEvt.GetWindow() != this )
+ {
+ CommandEvent aCommandEvent;
+
+ if ( !pCEvt->IsMouseEvent() )
+ {
+ aCommandEvent = *pCEvt;
+ }
+ else
+ {
+ // the mouse event occurred in a different window, we need to translate the coordinates of
+ // the mouse cursor within that window to the coordinates the mouse cursor would be in the
+ // current window
+ vcl::Window* pSource = rNEvt.GetWindow();
+ Point aPos = pSource->OutputToScreenPixel( pCEvt->GetMousePosPixel() );
+ aCommandEvent = CommandEvent( ScreenToOutputPixel( aPos ), pCEvt->GetCommand(), pCEvt->IsMouseEvent(), pCEvt->GetEventData() );
+ }
+
+ CallEventListeners( VclEventId::WindowCommand, &aCommandEvent );
+ }
+ }
+ }
+
+ // #82968# notify event listeners for mouse and key events separately and
+ // not in PreNotify ( as for focus listeners )
+ // this allows for processing those events internally first and pass it to
+ // the toolkit later
+
+ VclPtr<vcl::Window> xWindow = this;
+
+ if( rNEvt.GetType() == MouseNotifyEvent::MOUSEMOVE )
+ {
+ if ( mpWindowImpl->mbCompoundControl || ( rNEvt.GetWindow() == this ) )
+ {
+ if ( rNEvt.GetWindow() == this )
+ CallEventListeners( VclEventId::WindowMouseMove, const_cast<MouseEvent *>(rNEvt.GetMouseEvent()) );
+ else
+ {
+ MouseEvent aMouseEvent = ImplTranslateMouseEvent( *rNEvt.GetMouseEvent(), rNEvt.GetWindow(), this );
+ CallEventListeners( VclEventId::WindowMouseMove, &aMouseEvent );
+ }
+ }
+ }
+ else if( rNEvt.GetType() == MouseNotifyEvent::MOUSEBUTTONUP )
+ {
+ if ( mpWindowImpl->mbCompoundControl || ( rNEvt.GetWindow() == this ) )
+ {
+ if ( rNEvt.GetWindow() == this )
+ CallEventListeners( VclEventId::WindowMouseButtonUp, const_cast<MouseEvent *>(rNEvt.GetMouseEvent()) );
+ else
+ {
+ MouseEvent aMouseEvent = ImplTranslateMouseEvent( *rNEvt.GetMouseEvent(), rNEvt.GetWindow(), this );
+ CallEventListeners( VclEventId::WindowMouseButtonUp, &aMouseEvent );
+ }
+ }
+ }
+ else if( rNEvt.GetType() == MouseNotifyEvent::MOUSEBUTTONDOWN )
+ {
+ if ( mpWindowImpl->mbCompoundControl || ( rNEvt.GetWindow() == this ) )
+ {
+ if ( rNEvt.GetWindow() == this )
+ CallEventListeners( VclEventId::WindowMouseButtonDown, const_cast<MouseEvent *>(rNEvt.GetMouseEvent()) );
+ else
+ {
+ MouseEvent aMouseEvent = ImplTranslateMouseEvent( *rNEvt.GetMouseEvent(), rNEvt.GetWindow(), this );
+ CallEventListeners( VclEventId::WindowMouseButtonDown, &aMouseEvent );
+ }
+ }
+ }
+ else if( rNEvt.GetType() == MouseNotifyEvent::KEYINPUT )
+ {
+ if ( mpWindowImpl->mbCompoundControl || ( rNEvt.GetWindow() == this ) )
+ CallEventListeners( VclEventId::WindowKeyInput, const_cast<KeyEvent *>(rNEvt.GetKeyEvent()) );
+ }
+ else if( rNEvt.GetType() == MouseNotifyEvent::KEYUP )
+ {
+ if ( mpWindowImpl->mbCompoundControl || ( rNEvt.GetWindow() == this ) )
+ CallEventListeners( VclEventId::WindowKeyUp, const_cast<KeyEvent *>(rNEvt.GetKeyEvent()) );
+ }
+
+ if ( xWindow->IsDisposed() )
+ return;
+
+ // #106721# check if we're part of a compound control and notify
+ vcl::Window *pParent = ImplGetParent();
+ while( pParent )
+ {
+ if( pParent->IsCompoundControl() )
+ {
+ pParent->ImplNotifyKeyMouseCommandEventListeners( rNEvt );
+ break;
+ }
+ pParent = pParent->ImplGetParent();
+ }
+}
+
+void Window::ImplCallInitShow()
+{
+ mpWindowImpl->mbReallyShown = true;
+ mpWindowImpl->mbInInitShow = true;
+ CompatStateChanged( StateChangedType::InitShow );
+ mpWindowImpl->mbInInitShow = false;
+
+ vcl::Window* pWindow = mpWindowImpl->mpFirstOverlap;
+ while ( pWindow )
+ {
+ if ( pWindow->mpWindowImpl->mbVisible )
+ pWindow->ImplCallInitShow();
+ pWindow = pWindow->mpWindowImpl->mpNext;
+ }
+
+ pWindow = mpWindowImpl->mpFirstChild;
+ while ( pWindow )
+ {
+ if ( pWindow->mpWindowImpl->mbVisible )
+ pWindow->ImplCallInitShow();
+ pWindow = pWindow->mpWindowImpl->mpNext;
+ }
+}
+
+
+void Window::ImplCallResize()
+{
+ mpWindowImpl->mbCallResize = false;
+
+ // OpenGL has a charming feature of black clearing the whole window
+ // some legacy code eg. the app-menu has the beautiful feature of
+ // avoiding re-paints when width doesn't change => invalidate all.
+#if HAVE_FEATURE_OPENGL
+ if( OpenGLWrapper::isVCLOpenGLEnabled() )
+ Invalidate();
+
+ // Normally we avoid blanking on re-size unless people might notice:
+ else
+#endif
+ 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->mpFrame != mpWindowImpl->mpFrame )
+ {
+ pParentFrame = pParent->mpWindowImpl->mpFrame;
+ break;
+ }
+ pParent = pParent->GetParent();
+ }
+
+ SalFrameGeometry g = mpWindowImpl->mpFrame->GetGeometry();
+ mpWindowImpl->maPos = Point( g.nX, g.nY );
+ if( pParentFrame )
+ {
+ g = pParentFrame->GetGeometry();
+ mpWindowImpl->maPos -= Point( g.nX, g.nY );
+ }
+ // the client window and all its subclients have the same position as the borderframe
+ // this is important for floating toolbars where the borderwindow is a floating window
+ // which has another borderwindow (ie the system floating window)
+ vcl::Window *pClientWin = mpWindowImpl->mpClientWindow;
+ while( pClientWin )
+ {
+ pClientWin->mpWindowImpl->maPos = mpWindowImpl->maPos;
+ pClientWin = pClientWin->mpWindowImpl->mpClientWindow;
+ }
+ }
+
+ Move();
+
+ CallEventListeners( VclEventId::WindowMove );
+}
+
+void Window::ImplCallFocusChangeActivate( vcl::Window* pNewOverlapWindow,
+ vcl::Window* pOldOverlapWindow )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ vcl::Window* pNewRealWindow;
+ vcl::Window* pOldRealWindow;
+ bool bCallActivate = true;
+ bool bCallDeactivate = true;
+
+ if (!pOldOverlapWindow)
+ {
+ return;
+ }
+
+ pOldRealWindow = pOldOverlapWindow->ImplGetWindow();
+ if (!pNewOverlapWindow)
+ {
+ return;
+ }
+
+ pNewRealWindow = pNewOverlapWindow->ImplGetWindow();
+ if ( (pOldRealWindow->GetType() != WindowType::FLOATINGWINDOW) ||
+ pOldRealWindow->GetActivateMode() != ActivateModeFlags::NONE )
+ {
+ if ( (pNewRealWindow->GetType() == WindowType::FLOATINGWINDOW) &&
+ pNewRealWindow->GetActivateMode() == ActivateModeFlags::NONE)
+ {
+ pSVData->mpWinData->mpLastDeacWin = pOldOverlapWindow;
+ bCallDeactivate = false;
+ }
+ }
+ else if ( (pNewRealWindow->GetType() != WindowType::FLOATINGWINDOW) ||
+ pNewRealWindow->GetActivateMode() != ActivateModeFlags::NONE )
+ {
+ if (pSVData->mpWinData->mpLastDeacWin)
+ {
+ if (pSVData->mpWinData->mpLastDeacWin.get() == pNewOverlapWindow)
+ bCallActivate = false;
+ else
+ {
+ vcl::Window* pLastRealWindow = pSVData->mpWinData->mpLastDeacWin->ImplGetWindow();
+ pSVData->mpWinData->mpLastDeacWin->mpWindowImpl->mbActive = false;
+ pSVData->mpWinData->mpLastDeacWin->Deactivate();
+ if (pLastRealWindow != pSVData->mpWinData->mpLastDeacWin.get())
+ {
+ pLastRealWindow->mpWindowImpl->mbActive = true;
+ pLastRealWindow->Activate();
+ }
+ }
+ pSVData->mpWinData->mpLastDeacWin = nullptr;
+ }
+ }
+
+ if ( bCallDeactivate )
+ {
+ if( pOldOverlapWindow->mpWindowImpl->mbActive )
+ {
+ pOldOverlapWindow->mpWindowImpl->mbActive = false;
+ pOldOverlapWindow->Deactivate();
+ }
+ if ( pOldRealWindow != pOldOverlapWindow )
+ {
+ if( pOldRealWindow->mpWindowImpl->mbActive )
+ {
+ pOldRealWindow->mpWindowImpl->mbActive = false;
+ pOldRealWindow->Deactivate();
+ }
+ }
+ }
+ if ( bCallActivate && ! pNewOverlapWindow->mpWindowImpl->mbActive )
+ {
+ pNewOverlapWindow->mpWindowImpl->mbActive = true;
+ pNewOverlapWindow->Activate();
+
+ if ( pNewRealWindow != pNewOverlapWindow )
+ {
+ if( ! pNewRealWindow->mpWindowImpl->mbActive )
+ {
+ pNewRealWindow->mpWindowImpl->mbActive = true;
+ pNewRealWindow->Activate();
+ }
+ }
+ }
+}
+
+} /* namespace vcl */
+
+
+NotifyEvent::NotifyEvent( MouseNotifyEvent nEventType, vcl::Window* pWindow,
+ const void* pEvent )
+{
+ mpWindow = pWindow;
+ mpData = const_cast<void*>(pEvent);
+ mnEventType = nEventType;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/floatwin.cxx b/vcl/source/window/floatwin.cxx
new file mode 100644
index 000000000..95f39fba5
--- /dev/null
+++ b/vcl/source/window/floatwin.cxx
@@ -0,0 +1,950 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/floatwin.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/IDialogRenderable.hxx>
+
+class FloatingWindow::ImplData
+{
+public:
+ ImplData();
+
+ VclPtr<ToolBox> mpBox;
+ tools::Rectangle maItemEdgeClipRect; // used to clip the common edge between a toolbar item and the border of this window
+ Point maPos; // position of the floating window wrt. parent
+ Point maLOKTwipsPos; ///< absolute position of the floating window in the document - in twips (for toplevel floating windows).
+};
+
+FloatingWindow::ImplData::ImplData()
+{
+ mpBox = nullptr;
+}
+
+tools::Rectangle& FloatingWindow::ImplGetItemEdgeClipRect()
+{
+ return mpImplData->maItemEdgeClipRect;
+}
+
+void FloatingWindow::ImplInitFloating( vcl::Window* pParent, WinBits nStyle )
+{
+ mpImplData.reset(new ImplData);
+
+ mpWindowImpl->mbFloatWin = true;
+ mbInCleanUp = false;
+ mbGrabFocus = false;
+
+ SAL_WARN_IF(!pParent, "vcl", "FloatWindow::FloatingWindow(): - pParent == NULL!");
+
+ if (!pParent)
+ pParent = ImplGetSVData()->maFrameData.mpAppWin;
+
+ SAL_WARN_IF(!pParent, "vcl", "FloatWindow::FloatingWindow(): - pParent == NULL and no AppWindow exists");
+
+ // no Border, then we don't need a border window
+ if (!nStyle)
+ {
+ mpWindowImpl->mbOverlapWin = true;
+ nStyle |= WB_DIALOGCONTROL;
+ ImplInit(pParent, nStyle, nullptr);
+ }
+ else
+ {
+ if (!(nStyle & WB_NODIALOGCONTROL))
+ nStyle |= WB_DIALOGCONTROL;
+
+ if (nStyle & (WB_MOVEABLE | WB_SIZEABLE | WB_ROLLABLE | 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)
+{
+ ImplInitFloating(pParent, nStyle);
+}
+
+FloatingWindow::FloatingWindow(vcl::Window* pParent, const OString& rID, const OUString& rUIXMLDescription, const css::uno::Reference<css::frame::XFrame> &rFrame)
+ : SystemWindow(WindowType::FLOATINGWINDOW)
+ , 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::CalcFloatingPosition( vcl::Window* pWindow, const tools::Rectangle& rRect, FloatWinPopupFlags nFlags, sal_uInt16& rArrangeIndex )
+{
+ return ImplCalcPos( pWindow, rRect, nFlags, rArrangeIndex );
+}
+
+Point FloatingWindow::ImplCalcPos(vcl::Window* pWindow,
+ const tools::Rectangle& rRect, FloatWinPopupFlags nFlags,
+ sal_uInt16& rArrangeIndex, Point* pLOKTwipsPos)
+{
+ // get window position
+ Point aPos;
+ Size aSize = ::isLayoutEnabled(pWindow) ? pWindow->get_preferred_size() : pWindow->GetSizePixel();
+ tools::Rectangle aScreenRect = pWindow->ImplGetFrameWindow()->GetDesktopRectPixel();
+ FloatingWindow *pFloatingWindow = dynamic_cast<FloatingWindow*>( pWindow );
+
+ // convert...
+ vcl::Window* pW = pWindow;
+ if ( pW->mpWindowImpl->mpRealParent )
+ pW = pW->mpWindowImpl->mpRealParent;
+
+ tools::Rectangle normRect( rRect ); // rRect is already relative to top-level window
+ normRect.SetPos( pW->ScreenToOutputPixel( normRect.TopLeft() ) );
+
+ bool bRTL = AllSettings::GetLayoutRTL();
+
+ tools::Rectangle devRect( pW->OutputToAbsoluteScreenPixel( normRect.TopLeft() ),
+ pW->OutputToAbsoluteScreenPixel( normRect.BottomRight() ) );
+
+ tools::Rectangle devRectRTL( devRect );
+ if( bRTL )
+ // create a rect that can be compared to desktop coordinates
+ devRectRTL = pW->ImplOutputToUnmirroredAbsoluteScreenPixel( normRect );
+ if( Application::GetScreenCount() > 1 && Application::IsUnifiedDisplay() )
+ aScreenRect = Application::GetScreenPosSizePixel(
+ Application::GetBestScreen( bRTL ? devRectRTL : devRect ) );
+
+ FloatWinPopupFlags nArrangeAry[5];
+ sal_uInt16 nArrangeAttempts = 5;
+ Point e1,e2; // the common edge between the item rect and the floating window
+
+ if ( nFlags & FloatWinPopupFlags::Left )
+ {
+ nArrangeAry[0] = FloatWinPopupFlags::Left;
+ nArrangeAry[1] = FloatWinPopupFlags::Right;
+ nArrangeAry[2] = FloatWinPopupFlags::Up;
+ nArrangeAry[3] = FloatWinPopupFlags::Down;
+ nArrangeAry[4] = FloatWinPopupFlags::Left;
+ }
+ else if ( nFlags & FloatWinPopupFlags::Right )
+ {
+ nArrangeAry[0] = FloatWinPopupFlags::Right;
+ nArrangeAry[1] = FloatWinPopupFlags::Left;
+ nArrangeAry[2] = FloatWinPopupFlags::Up;
+ nArrangeAry[3] = FloatWinPopupFlags::Down;
+ nArrangeAry[4] = FloatWinPopupFlags::Right;
+ }
+ else if ( nFlags & FloatWinPopupFlags::Up )
+ {
+ nArrangeAry[0] = FloatWinPopupFlags::Up;
+ nArrangeAry[1] = FloatWinPopupFlags::Down;
+ if (nFlags & FloatWinPopupFlags::NoHorzPlacement)
+ {
+ nArrangeAry[2] = FloatWinPopupFlags::Up;
+ nArrangeAttempts = 3;
+ }
+ else
+ {
+ nArrangeAry[2] = FloatWinPopupFlags::Right;
+ nArrangeAry[3] = FloatWinPopupFlags::Left;
+ nArrangeAry[4] = FloatWinPopupFlags::Up;
+ }
+ }
+ else
+ {
+ nArrangeAry[0] = FloatWinPopupFlags::Down;
+ nArrangeAry[1] = FloatWinPopupFlags::Up;
+ if (nFlags & FloatWinPopupFlags::NoHorzPlacement)
+ {
+ nArrangeAry[2] = FloatWinPopupFlags::Down;
+ nArrangeAttempts = 3;
+ }
+ else
+ {
+ nArrangeAry[2] = FloatWinPopupFlags::Right;
+ nArrangeAry[3] = FloatWinPopupFlags::Left;
+ nArrangeAry[4] = FloatWinPopupFlags::Down;
+ }
+ }
+
+ sal_uInt16 nArrangeIndex = 0;
+ const bool bLOKActive = comphelper::LibreOfficeKit::isActive();
+
+ for ( ; nArrangeIndex < nArrangeAttempts; nArrangeIndex++ )
+ {
+ bool bBreak = true;
+ switch ( nArrangeAry[nArrangeIndex] )
+ {
+
+ case FloatWinPopupFlags::Left:
+ aPos.setX( devRect.Left()-aSize.Width()+1 );
+ aPos.setY( devRect.Top() );
+ aPos.AdjustY( -(pWindow->mpWindowImpl->mnTopBorder) );
+ if( bRTL )
+ {
+ if( (devRectRTL.Right()+aSize.Width()) > aScreenRect.Right() )
+ bBreak = false;
+ }
+ else
+ {
+ if ( aPos.X() < aScreenRect.Left() )
+ bBreak = false;
+ }
+ if (bBreak || bLOKActive)
+ {
+ e1 = devRect.TopLeft();
+ e2 = devRect.BottomLeft();
+ // set non-zero width
+ e2.AdjustX( 1 );
+ // don't clip corners
+ e1.AdjustY( 1 );
+ e2.AdjustY( -1 );
+ }
+ break;
+ case FloatWinPopupFlags::Right:
+ aPos = devRect.TopRight();
+ aPos.AdjustY( -(pWindow->mpWindowImpl->mnTopBorder) );
+ if( bRTL )
+ {
+ if( (devRectRTL.Left() - aSize.Width()) < aScreenRect.Left() )
+ bBreak = false;
+ }
+ else
+ {
+ if ( aPos.X()+aSize.Width() > aScreenRect.Right() )
+ bBreak = false;
+ }
+ if (bBreak || bLOKActive)
+ {
+ e1 = devRect.TopRight();
+ e2 = devRect.BottomRight();
+ // set non-zero width
+ e2.AdjustX( 1 );
+ // don't clip corners
+ e1.AdjustY( 1 );
+ e2.AdjustY( -1 );
+ }
+ break;
+ case FloatWinPopupFlags::Up:
+ aPos.setX( devRect.Left() );
+ aPos.setY( devRect.Top()-aSize.Height()+1 );
+ if ( aPos.Y() < aScreenRect.Top() )
+ bBreak = false;
+ if (bBreak || bLOKActive)
+ {
+ e1 = devRect.TopLeft();
+ e2 = devRect.TopRight();
+ // set non-zero height
+ e2.AdjustY( 1 );
+ // don't clip corners
+ e1.AdjustX( 1 );
+ e2.AdjustX( -1 );
+ }
+ break;
+ case FloatWinPopupFlags::Down:
+ aPos = devRect.BottomLeft();
+ if ( aPos.Y()+aSize.Height() > aScreenRect.Bottom() )
+ bBreak = false;
+ if (bBreak || bLOKActive)
+ {
+ e1 = devRect.BottomLeft();
+ e2 = devRect.BottomRight();
+ // set non-zero height
+ e2.AdjustY( 1 );
+ // don't clip corners
+ e1.AdjustX( 1 );
+ e2.AdjustX( -1 );
+ }
+ break;
+ default: break;
+ }
+
+ // no further adjustment for LibreOfficeKit
+ if (bLOKActive)
+ break;
+
+ // adjust if necessary
+ if (bBreak)
+ {
+ if ( (nArrangeAry[nArrangeIndex] == FloatWinPopupFlags::Left) ||
+ (nArrangeAry[nArrangeIndex] == FloatWinPopupFlags::Right) )
+ {
+ if ( aPos.Y()+aSize.Height() > aScreenRect.Bottom() )
+ {
+ aPos.setY( devRect.Bottom()-aSize.Height()+1 );
+ if ( aPos.Y() < aScreenRect.Top() )
+ aPos.setY( aScreenRect.Top() );
+ }
+ }
+ else
+ {
+ if( bRTL )
+ {
+ if( devRectRTL.Right()-aSize.Width()+1 < aScreenRect.Left() )
+ aPos.AdjustX( -(aScreenRect.Left() - devRectRTL.Right() + aSize.Width() - 1) );
+ }
+ else if ( aPos.X()+aSize.Width() > aScreenRect.Right() )
+ {
+ aPos.setX( devRect.Right()-aSize.Width()+1 );
+ if ( aPos.X() < aScreenRect.Left() )
+ aPos.setX( aScreenRect.Left() );
+ }
+ }
+ }
+
+ if ( bBreak )
+ break;
+ }
+ if (nArrangeIndex >= nArrangeAttempts)
+ nArrangeIndex = nArrangeAttempts - 1;
+
+ rArrangeIndex = nArrangeIndex;
+
+ aPos = pW->AbsoluteScreenToOutputPixel( aPos );
+
+ // store a cliprect that can be used to clip the common edge of the itemrect and the floating window
+ if( pFloatingWindow && pFloatingWindow->mpImplData->mpBox )
+ {
+ pFloatingWindow->mpImplData->maItemEdgeClipRect =
+ tools::Rectangle( e1, e2 );
+ }
+
+ if (bLOKActive && pLOKTwipsPos)
+ {
+ if (pW->IsMapModeEnabled() || pW->GetMapMode().GetMapUnit() == MapUnit::MapPixel)
+ {
+ // if we use pW->LogicToLogic(aPos, pW->GetMapMode(), MapMode(MapUnit::MapTwip)),
+ // for pixel conversions when map mode is not enabled, we get
+ // a 20 twips per pixel conversion since LogicToLogic uses
+ // a fixed 72 dpi value, instead of a correctly computed output
+ // device dpi or at least the most commonly used 96 dpi value;
+ // and anyway the following is what we already do in
+ // ScGridWindow::LogicInvalidate when map mode is not enabled.
+
+ *pLOKTwipsPos = pW->PixelToLogic(aPos, MapMode(MapUnit::MapTwip));
+ }
+ else
+ {
+ *pLOKTwipsPos = OutputDevice::LogicToLogic(aPos, pW->GetMapMode(), MapMode(MapUnit::MapTwip));
+ }
+ }
+
+ // caller expects coordinates relative to top-level win
+ return pW->OutputToScreenPixel( aPos );
+}
+
+Point FloatingWindow::ImplConvertToAbsPos(vcl::Window* pReference, const Point& rPos)
+{
+ Point aAbsolute( rPos );
+
+ const OutputDevice *pWindowOutDev = pReference->GetOutDev();
+
+ // compare coordinates in absolute screen coordinates
+ if( pReference->HasMirroredGraphics() )
+ {
+ if(!pReference->IsRTLEnabled() )
+ pWindowOutDev->ReMirror( aAbsolute );
+
+ tools::Rectangle aRect( pReference->ScreenToOutputPixel(aAbsolute), Size(1,1) ) ;
+ aRect = pReference->ImplOutputToUnmirroredAbsoluteScreenPixel( aRect );
+ aAbsolute = aRect.TopLeft();
+ }
+ else
+ aAbsolute = pReference->OutputToAbsoluteScreenPixel(
+ pReference->ScreenToOutputPixel(rPos) );
+
+ return aAbsolute;
+}
+
+tools::Rectangle FloatingWindow::ImplConvertToAbsPos(vcl::Window* pReference, const tools::Rectangle& rRect)
+{
+ tools::Rectangle aFloatRect = rRect;
+
+ const OutputDevice *pParentWinOutDev = pReference->GetOutDev();
+
+ // compare coordinates in absolute screen coordinates
+ // Keep in sync with FloatingWindow::ImplFloatHitTest, e.g. fdo#33509
+ if( pReference->HasMirroredGraphics() )
+ {
+ if(!pReference->IsRTLEnabled() )
+ pParentWinOutDev->ReMirror(aFloatRect);
+
+ aFloatRect.SetPos(pReference->ScreenToOutputPixel(aFloatRect.TopLeft()));
+ aFloatRect = pReference->ImplOutputToUnmirroredAbsoluteScreenPixel(aFloatRect);
+ }
+ else
+ aFloatRect.SetPos(pReference->OutputToAbsoluteScreenPixel(pReference->ScreenToOutputPixel(rRect.TopLeft())));
+ return aFloatRect;
+}
+
+FloatingWindow* FloatingWindow::ImplFloatHitTest( vcl::Window* pReference, const Point& rPos, bool& rbHitTestInsideRect )
+{
+ FloatingWindow* pWin = this;
+ rbHitTestInsideRect = false;
+
+ Point aAbsolute(FloatingWindow::ImplConvertToAbsPos(pReference, rPos));
+
+ do
+ {
+ // compute the floating window's size in absolute screen coordinates
+
+ // use the border window to have the exact position
+ vcl::Window *pBorderWin = pWin->GetWindow( GetWindowType::Border );
+ if (!pBorderWin)
+ break;
+
+ // the top-left corner in output coordinates ie (0,0)
+ tools::Rectangle devRect( pBorderWin->ImplOutputToUnmirroredAbsoluteScreenPixel( tools::Rectangle( Point(), pBorderWin->GetSizePixel()) ) ) ;
+ if ( devRect.IsInside( 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.IsInside( aAbsolute ) )
+ {
+ rbHitTestInsideRect = true;
+ return pWin;
+ }
+
+ pWin = pWin->mpNextFloat;
+ }
+ while ( pWin );
+
+ return nullptr;
+}
+
+FloatingWindow* FloatingWindow::ImplFindLastLevelFloat()
+{
+ FloatingWindow* pWin = this;
+ FloatingWindow* pLastFoundWin = pWin;
+
+ do
+ {
+ if ( pWin->GetPopupModeFlags() & FloatWinPopupFlags::NewLevel )
+ pLastFoundWin = pWin;
+
+ pWin = pWin->mpNextFloat;
+ }
+ while ( pWin );
+
+ return pLastFoundWin;
+}
+
+bool FloatingWindow::ImplIsFloatPopupModeWindow( const vcl::Window* pWindow )
+{
+ FloatingWindow* pWin = this;
+
+ do
+ {
+ if ( pWin->mpFirstPopupModeWin == pWindow )
+ return true;
+
+ pWin = pWin->mpNextFloat;
+ }
+ while ( pWin );
+
+ return false;
+}
+
+IMPL_LINK_NOARG(FloatingWindow, ImplEndPopupModeHdl, void*, void)
+{
+ VclPtr<FloatingWindow> pThis(this);
+ mnPostId = nullptr;
+ mnPopupModeFlags = FloatWinPopupFlags::NONE;
+ mbPopupMode = false;
+ PopupModeEnd();
+}
+
+bool FloatingWindow::EventNotify( NotifyEvent& rNEvt )
+{
+ // call Base Class first for tab control
+ bool bRet = SystemWindow::EventNotify( rNEvt );
+ if ( !bRet )
+ {
+ if ( rNEvt.GetType() == MouseNotifyEvent::KEYINPUT )
+ {
+ const KeyEvent* pKEvt = rNEvt.GetKeyEvent();
+ vcl::KeyCode aKeyCode = pKEvt->GetKeyCode();
+ sal_uInt16 nKeyCode = aKeyCode.GetCode();
+
+ if ( (nKeyCode == KEY_ESCAPE) && (GetStyle() & WB_CLOSEABLE) )
+ {
+ Close();
+ return true;
+ }
+ }
+ }
+
+ return bRet;
+}
+
+void FloatingWindow::PixelInvalidate(const tools::Rectangle* /*pRectangle*/)
+{
+ if (VclPtr<vcl::Window> pParent = GetParentWithLOKNotifier())
+ {
+ std::vector<vcl::LOKPayloadItem> aPayload;
+ const tools::Rectangle aRect(Point(0,0), Size(GetSizePixel().Width()+1, GetSizePixel().Height()+1));
+ aPayload.push_back(std::make_pair(OString("rectangle"), aRect.toString()));
+ const vcl::ILibreOfficeKitNotifier* pNotifier = pParent->GetLOKNotifier();
+ pNotifier->notifyWindow(GetLOKWindowId(), "invalidate", aPayload);
+ }
+}
+
+void FloatingWindow::StateChanged( StateChangedType nType )
+{
+ if (nType == StateChangedType::InitShow)
+ {
+ DoInitialLayout();
+ }
+
+ SystemWindow::StateChanged( nType );
+
+ VclPtr<vcl::Window> pParent = GetParentWithLOKNotifier();
+ if (pParent)
+ {
+ if (nType == StateChangedType::InitShow)
+ {
+ std::vector<vcl::LOKPayloadItem> aItems;
+ if (pParent == this)
+ {
+ // we are a toplevel window, let's so far pretend to be a
+ // dialog - but maybe we'll need a separate type for this
+ // later
+ 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 )
+ {
+ 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 )
+{
+ if ( IsRollUp() )
+ RollDown();
+
+ // 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;
+ if (nFlags & FloatWinPopupFlags::GrabFocus)
+ {
+ // force key input even without focus (useful for menus)
+ mbGrabFocus = true;
+ mxPrevFocusWin = Window::SaveFocus();
+ mpWindowImpl->mpFrameData->mbHasFocus = true;
+ GrabFocus();
+ }
+ Show( true, ShowFlags::NoActivate );
+}
+
+void FloatingWindow::StartPopupMode( ToolBox* pBox, FloatWinPopupFlags nFlags )
+{
+ mpImplData->mpBox = pBox;
+
+ // get selected button
+ sal_uInt16 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 FloatingWindow::UpdatePositionData()
+{
+ auto pWin = ImplGetParent();
+ if (pWin)
+ {
+ // Simulate Move, so the relative position of the floating window will be recalculated
+ pWin->ImplCallMove();
+ return true;
+ }
+
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/globalization.cxx b/vcl/source/window/globalization.cxx
new file mode 100644
index 000000000..1c56638c6
--- /dev/null
+++ b/vcl/source/window/globalization.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 <vcl/window.hxx>
+#include <vcl/outdev.hxx>
+
+namespace vcl {
+
+void Window::EnableRTL ( bool bEnable )
+{
+ CompatStateChanged( StateChangedType::Mirroring );
+ OutputDevice::EnableRTL(bEnable);
+}
+
+bool Window::HasMirroredGraphics() const
+{
+ const OutputDevice* pOutDev = GetOutDev();
+ return pOutDev->OutputDevice::HasMirroredGraphics();
+}
+
+} /* namespace vcl */
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/introwin.cxx b/vcl/source/window/introwin.cxx
new file mode 100644
index 000000000..934754aa4
--- /dev/null
+++ b/vcl/source/window/introwin.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <vcl/wrkwin.hxx>
+#include <vcl/introwin.hxx>
+
+#include <svdata.hxx>
+
+void IntroWindow::ImplInitIntroWindowData()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->mpIntroWindow = this;
+}
+
+IntroWindow::IntroWindow( ) :
+ WorkWindow( WindowType::INTROWINDOW )
+{
+ ImplInitIntroWindowData();
+ WorkWindow::ImplInit( nullptr, WB_INTROWIN );
+}
+
+IntroWindow::~IntroWindow()
+{
+ disposeOnce();
+}
+
+void IntroWindow::dispose()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if ( pSVData->mpIntroWindow.get() == this )
+ pSVData->mpIntroWindow = nullptr;
+
+ WorkWindow::dispose();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/keycod.cxx b/vcl/source/window/keycod.cxx
new file mode 100644
index 000000000..451e859f6
--- /dev/null
+++ b/vcl/source/window/keycod.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 <sal/config.h>
+
+#include <accel.h>
+#include <salframe.hxx>
+#include <svdata.hxx>
+
+#include <vcl/window.hxx>
+#include <vcl/keycod.hxx>
+
+static const sal_uInt16 aImplKeyFuncTab[(static_cast<int>(KeyFuncType::FRONT)+1)*4] =
+{
+ 0, 0, 0, 0, // KeyFuncType::DONTKNOW
+ KEY_N | KEY_MOD1, 0, 0, 0, // KeyFuncType::NEW
+ KEY_O | KEY_MOD1, KEY_OPEN, 0, 0, // KeyFuncType::OPEN
+ KEY_S | KEY_MOD1, 0, 0, 0, // KeyFuncType::SAVE
+ KEY_S | KEY_SHIFT | KEY_MOD1, 0, 0, 0, // KeyFuncType::SAVEAS
+ KEY_P | KEY_MOD1, 0, 0, 0, // KeyFuncType::PRINT
+ KEY_W | KEY_MOD1, KEY_F4 | KEY_MOD1, 0, 0, // KeyFuncType::CLOSE
+ KEY_Q | KEY_MOD1, KEY_F4 | KEY_MOD2, 0, 0, // KeyFuncType::QUIT
+ 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
+ KEY_REPEAT, 0, 0, 0, // KeyFuncType::REPEAT
+ KEY_F | KEY_MOD1, KEY_FIND, 0, 0, // KeyFuncType::FIND
+ KEY_F | KEY_SHIFT | KEY_MOD1, KEY_SHIFT | KEY_FIND, 0, 0, // KeyFuncType::FINDBACKWARD
+ KEY_RETURN | KEY_MOD2, 0, 0, 0, // KeyFuncType::PROPERTIES
+ 0, 0, 0, 0 // KeyFuncType::FRONT
+};
+
+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( vcl::Window* pWindow ) const
+{
+ if ( !pWindow )
+ 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::NEW); i < sal_uInt16(KeyFuncType::FRONT); i++ )
+ {
+ sal_uInt16 nKeyCode1;
+ sal_uInt16 nKeyCode2;
+ sal_uInt16 nKeyCode3;
+ sal_uInt16 nKeyCode4;
+ ImplGetKeyCode( static_cast<KeyFuncType>(i), nKeyCode1, nKeyCode2, nKeyCode3, nKeyCode4 );
+ if ( (nCompCode == nKeyCode1) || (nCompCode == nKeyCode2) || (nCompCode == nKeyCode3) || (nCompCode == nKeyCode4) )
+ return static_cast<KeyFuncType>(i);
+ }
+ }
+
+ return KeyFuncType::DONTKNOW;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/keyevent.cxx b/vcl/source/window/keyevent.cxx
new file mode 100644
index 000000000..eca00d411
--- /dev/null
+++ b/vcl/source/window/keyevent.cxx
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <vcl/event.hxx>
+
+KeyEvent KeyEvent::LogicalTextDirectionality (TextDirectionality eMode) const
+{
+ KeyEvent aClone(*this);
+
+ sal_uInt16 nCode = maKeyCode.GetCode();
+ sal_uInt16 nMod = maKeyCode.GetModifier();
+
+ switch (eMode)
+ {
+ case TextDirectionality::RightToLeft_TopToBottom:
+ switch (nCode)
+ {
+ case KEY_LEFT: aClone.maKeyCode = vcl::KeyCode(KEY_RIGHT, nMod); break;
+ case KEY_RIGHT: aClone.maKeyCode = vcl::KeyCode(KEY_LEFT, nMod); break;
+ }
+ break;
+
+ case TextDirectionality::TopToBottom_RightToLeft:
+ switch (nCode)
+ {
+ case KEY_DOWN: aClone.maKeyCode = vcl::KeyCode(KEY_RIGHT, nMod); break;
+ case KEY_UP: aClone.maKeyCode = vcl::KeyCode(KEY_LEFT, nMod); break;
+ case KEY_LEFT: aClone.maKeyCode = vcl::KeyCode(KEY_DOWN, nMod); break;
+ case KEY_RIGHT: aClone.maKeyCode = vcl::KeyCode(KEY_UP, nMod); break;
+ }
+ break;
+
+ case TextDirectionality::BottomToTop_LeftToRight:
+ switch (nCode)
+ {
+ case KEY_DOWN: aClone.maKeyCode = vcl::KeyCode(KEY_LEFT, nMod); break;
+ case KEY_UP: aClone.maKeyCode = vcl::KeyCode(KEY_RIGHT, nMod); break;
+ case KEY_LEFT: aClone.maKeyCode = vcl::KeyCode(KEY_UP, nMod); break;
+ case KEY_RIGHT: aClone.maKeyCode = vcl::KeyCode(KEY_DOWN, nMod); break;
+ }
+ break;
+
+ case TextDirectionality::LeftToRight_TopToBottom:
+ /* do nothing */
+ break;
+ }
+
+ return aClone;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/layout.cxx b/vcl/source/window/layout.cxx
new file mode 100644
index 000000000..080acd5f9
--- /dev/null
+++ b/vcl/source/window/layout.cxx
@@ -0,0 +1,2882 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 <o3tl/enumarray.hxx>
+#include <o3tl/enumrange.hxx>
+#include <vcl/toolkit/button.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/toolkit/dialog.hxx>
+#include <vcl/layout.hxx>
+#include <vcl/scrbar.hxx>
+#include <vcl/stdtext.hxx>
+#include <vcl/split.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <bitmaps.hlst>
+#include <messagedialog.hxx>
+#include <svdata.hxx>
+#include <window.h>
+#include <boost/multi_array.hpp>
+#include <boost/property_tree/ptree.hpp>
+#include <vcl/vclmedit.hxx>
+#include <sal/log.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_left() + nBorderWidth;
+ sal_Int32 nTop = rWindow.get_margin_top() + nBorderWidth;
+ sal_Int32 nRight = rWindow.get_margin_right() + 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_left() + nBorderWidth;
+ sal_Int32 nTop = rWindow.get_margin_top() + nBorderWidth;
+ sal_Int32 nRight = rWindow.get_margin_right() + 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_left() );
+ 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_left() + get_margin_right()) );
+ 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
+{
+ long nSecondaryChildDimension = getSecondaryDimension(rChildSize);
+ long nSecondaryBoxDimension = getSecondaryDimension(rSize);
+ setSecondaryDimension(rSize, std::max(nSecondaryChildDimension, nSecondaryBoxDimension));
+
+ long nPrimaryChildDimension = getPrimaryDimension(rChildSize);
+ 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);
+
+ 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;
+
+ long nAllocPrimaryDimension = getPrimaryDimension(rAllocation);
+
+ long nHomogeneousDimension = 0, nExtraSpace = 0;
+ if (m_bHomogeneous)
+ {
+ nHomogeneousDimension = (nAllocPrimaryDimension -
+ (nVisibleChildren - 1) * m_nSpacing) / nVisibleChildren;
+ }
+ else if (nExpandChildren)
+ {
+ Size aRequisition = calculateRequisition();
+ nExtraSpace = (getPrimaryDimension(rAllocation) - getPrimaryDimension(aRequisition)) / nExpandChildren;
+ }
+
+ //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)
+ {
+ long nPrimaryCoordinate = getPrimaryCoordinate(aPos);
+ setPrimaryCoordinate(aPos, nPrimaryCoordinate + nAllocPrimaryDimension);
+ }
+
+ for (auto const& window : aWindows[ePackType])
+ {
+ vcl::Window *pChild = window;
+
+ long nPadding = pChild->get_padding();
+
+ Size aBoxSize;
+ if (m_bHomogeneous)
+ setPrimaryDimension(aBoxSize, nHomogeneousDimension);
+ else
+ {
+ aBoxSize = getLayoutRequisition(*pChild);
+ 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);
+ long nPrimaryCoordinate = getPrimaryCoordinate(aPos);
+
+ bool bFill = pChild->get_fill();
+ if (bFill)
+ {
+ setPrimaryDimension(aChildSize, std::max(static_cast<long>(1),
+ getPrimaryDimension(aBoxSize) - nPadding * 2));
+
+ setPrimaryCoordinate(aChildPos, nPrimaryCoordinate + nPadding);
+ }
+ else
+ {
+ setPrimaryDimension(aChildSize,
+ getPrimaryDimension(getLayoutRequisition(*pChild)));
+
+ setPrimaryCoordinate(aChildPos, nPrimaryCoordinate +
+ (getPrimaryDimension(aBoxSize) - getPrimaryDimension(aChildSize)) / 2);
+ }
+
+ long nDiff = getPrimaryDimension(aBoxSize) + m_nSpacing;
+ if (ePackType == VclPackType::Start)
+ setPrimaryCoordinate(aPos, nPrimaryCoordinate + nDiff);
+ else
+ {
+ setPrimaryCoordinate(aPos, nPrimaryCoordinate - nDiff);
+ setPrimaryCoordinate(aChildPos, getPrimaryCoordinate(aChildPos) -
+ getPrimaryDimension(aBoxSize));
+ }
+
+ setLayoutAllocation(*pChild, aChildPos, aChildSize);
+ }
+ }
+}
+
+bool VclBox::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "spacing")
+ set_spacing(rValue.toInt32());
+ else if (rKey == "homogeneous")
+ set_homogeneous(toBool(rValue));
+ else
+ return VclContainer::set_property(rKey, rValue);
+ return true;
+}
+
+boost::property_tree::ptree VclBox::DumpAsPropertyTree()
+{
+ boost::property_tree::ptree aTree(VclContainer::DumpAsPropertyTree());
+ aTree.put("vertical", m_bVerticalContainer);
+ return aTree;
+}
+
+sal_uInt16 VclBox::getDefaultAccessibleRole() const
+{
+#if defined(_WIN32)
+ //fdo#74284 call Boxes Panels, keep then as "Filler" under
+ //at least Linux seeing as that's what Gtk does for GtkBoxes
+ return css::accessibility::AccessibleRole::PANEL;
+#else
+ return css::accessibility::AccessibleRole::FILLER;
+#endif
+}
+
+#define DEFAULT_CHILD_MIN_WIDTH 85
+#define DEFAULT_CHILD_MIN_HEIGHT 27
+
+Size VclBox::finalizeMaxes(const Size &rSize, sal_uInt16 nVisibleChildren) const
+{
+ Size aRet;
+
+ if (nVisibleChildren)
+ {
+ 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;
+
+ long nMainGroupDimension = getPrimaryDimension(rReq.m_aMainGroupSize);
+ 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 long getMaxNonOutlier(const std::vector<long> &rG, long nAvgDimension)
+{
+ long nMaxDimensionNonOutlier = 0;
+ for (auto const& nPrimaryChildDimension : rG)
+ {
+ if (nPrimaryChildDimension < nAvgDimension * 1.5)
+ {
+ nMaxDimensionNonOutlier = std::max(nPrimaryChildDimension,
+ nMaxDimensionNonOutlier);
+ }
+ }
+ return nMaxDimensionNonOutlier;
+}
+
+static std::vector<long> setButtonSizes(const std::vector<long> &rG,
+ const std::vector<bool> &rNonHomogeneous,
+ long nAvgDimension, long nMaxNonOutlier, long nMinWidth)
+{
+ std::vector<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
+
+ long nMinMainGroupPrimary = getPrimaryDimension(aMainGroupSize);
+ long nMinSubGroupPrimary = getPrimaryDimension(aSubGroupSize);
+ long nMainGroupSecondary = getSecondaryDimension(aMainGroupSize);
+ long nSubGroupSecondary = getSecondaryDimension(aSubGroupSize);
+
+ bool bIgnoreSecondaryPacking = (m_eLayoutStyle == VclButtonBoxStyle::Spread || m_eLayoutStyle == VclButtonBoxStyle::Center);
+
+ std::vector<long> aMainGroupSizes;
+ std::vector<bool> aMainGroupNonHomogeneous;
+ std::vector<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)
+ {
+ long nMaxMainDimension = aMainGroupSizes.empty() ? 0 :
+ *std::max_element(aMainGroupSizes.begin(), aMainGroupSizes.end());
+ nMaxMainDimension = std::max(nMaxMainDimension, nMinMainGroupPrimary);
+ long nMaxSubDimension = aSubGroupSizes.empty() ? 0 :
+ *std::max_element(aSubGroupSizes.begin(), aSubGroupSizes.end());
+ nMaxSubDimension = std::max(nMaxSubDimension, nMinSubGroupPrimary);
+ 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
+ long nAccDimension = std::accumulate(aMainGroupSizes.begin(),
+ aMainGroupSizes.end(), 0);
+ nAccDimension = std::accumulate(aSubGroupSizes.begin(),
+ aSubGroupSizes.end(), nAccDimension);
+
+ size_t nTotalSize = aMainGroupSizes.size() + aSubGroupSizes.size();
+
+ long nAvgDimension = nTotalSize ? nAccDimension / nTotalSize : 0;
+
+ long nMaxMainNonOutlier = getMaxNonOutlier(aMainGroupSizes,
+ nAvgDimension);
+ long nMaxSubNonOutlier = getMaxNonOutlier(aSubGroupSizes,
+ nAvgDimension);
+ 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)
+ {
+ long nPrimaryDimension = getPrimaryDimension(rSize);
+ setPrimaryDimension(aRet,
+ nPrimaryDimension + m_nSpacing * (nVisibleChildren-1));
+ setSecondaryDimension(aRet, getSecondaryDimension(rSize));
+ }
+
+ return aRet;
+}
+
+Size VclButtonBox::calculateRequisition() const
+{
+ Requisition aReq(calculatePrimarySecondaryRequisitions());
+ sal_uInt16 nVisibleChildren = aReq.m_aMainGroupDimensions.size() +
+ aReq.m_aSubGroupDimensions.size();
+ return addSpacing(addReqGroups(aReq), nVisibleChildren);
+}
+
+bool VclButtonBox::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "layout-style")
+ {
+ VclButtonBoxStyle eStyle = VclButtonBoxStyle::Default;
+ if (rValue == "spread")
+ eStyle = VclButtonBoxStyle::Spread;
+ else if (rValue == "edge")
+ eStyle = VclButtonBoxStyle::Edge;
+ else if (rValue == "start")
+ eStyle = VclButtonBoxStyle::Start;
+ else if (rValue == "end")
+ eStyle = VclButtonBoxStyle::End;
+ else if (rValue == "center")
+ eStyle = VclButtonBoxStyle::Center;
+ else
+ {
+ SAL_WARN("vcl.layout", "unknown layout style " << rValue);
+ }
+ m_eLayoutStyle = eStyle;
+ }
+ else
+ return VclBox::set_property(rKey, rValue);
+ return true;
+}
+
+void VclButtonBox::setAllocation(const Size &rAllocation)
+{
+ Requisition aReq(calculatePrimarySecondaryRequisitions());
+
+ if (aReq.m_aMainGroupDimensions.empty() && aReq.m_aSubGroupDimensions.empty())
+ return;
+
+ 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())
+ {
+ long nOtherPrimaryDimension = getPrimaryDimension(
+ addSpacing(aReq.m_aSubGroupSize, aReq.m_aSubGroupDimensions.size()));
+ setPrimaryCoordinate(aOtherGroupPos,
+ nAllocPrimaryDimension - nOtherPrimaryDimension);
+ }
+ break;
+ case VclButtonBoxStyle::Spread:
+ if (!aReq.m_aMainGroupDimensions.empty())
+ {
+ long nMainPrimaryDimension = getPrimaryDimension(
+ addSpacing(aReq.m_aMainGroupSize, aReq.m_aMainGroupDimensions.size()));
+ 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())
+ {
+ long nMainPrimaryDimension = getPrimaryDimension(
+ addSpacing(aReq.m_aMainGroupSize, aReq.m_aMainGroupDimensions.size()));
+ 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())
+ {
+ long nMainPrimaryDimension = getPrimaryDimension(
+ addSpacing(aReq.m_aMainGroupSize, aReq.m_aMainGroupDimensions.size()));
+ setPrimaryCoordinate(aMainGroupPos,
+ nAllocPrimaryDimension - nMainPrimaryDimension);
+ }
+ break;
+ }
+
+ Size aChildSize;
+ setSecondaryDimension(aChildSize, getSecondaryDimension(rAllocation));
+
+ std::vector<long>::const_iterator aPrimaryI = aReq.m_aMainGroupDimensions.begin();
+ std::vector<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())
+ {
+ long nMainGroupPrimaryDimension = *aPrimaryI++;
+ setPrimaryDimension(aChildSize, nMainGroupPrimaryDimension);
+ setLayoutAllocation(*pChild, aMainGroupPos, aChildSize);
+ long nPrimaryCoordinate = getPrimaryCoordinate(aMainGroupPos);
+ setPrimaryCoordinate(aMainGroupPos, nPrimaryCoordinate + nMainGroupPrimaryDimension + nSpacing);
+ }
+ else
+ {
+ long nSubGroupPrimaryDimension = *aSecondaryI++;
+ setPrimaryDimension(aChildSize, nSubGroupPrimaryDimension);
+ setLayoutAllocation(*pChild, aOtherGroupPos, aChildSize);
+ long nPrimaryCoordinate = getPrimaryCoordinate(aOtherGroupPos);
+ setPrimaryCoordinate(aOtherGroupPos, nPrimaryCoordinate + nSubGroupPrimaryDimension + nSpacing);
+ }
+ }
+}
+
+namespace {
+
+struct ButtonOrder
+{
+ const char* m_aType;
+ int m_nPriority;
+};
+
+}
+
+static int getButtonPriority(const OString &rType)
+{
+ static const size_t N_TYPES = 6;
+ static const ButtonOrder aDiscardCancelSave[N_TYPES] =
+ {
+ { "/discard", 0 },
+ { "/cancel", 1 },
+ { "/no", 2 },
+ { "/save", 3 },
+ { "/yes", 3 },
+ { "/ok", 3 }
+ };
+
+ static const ButtonOrder aSaveDiscardCancel[N_TYPES] =
+ {
+ { "/save", 0 },
+ { "/yes", 0 },
+ { "/ok", 0 },
+ { "/discard", 1 },
+ { "/no", 1 },
+ { "/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.endsWith(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->GetHelpId()) < getButtonPriority(pB->GetHelpId());
+}
+
+void VclButtonBox::sort_native_button_order()
+{
+ std::vector<vcl::Window*> aChilds;
+ for (vcl::Window* pChild = 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(m_bVerticalContainer));
+ BuilderUtils::reorderWithinParent(aChilds, true);
+}
+
+namespace {
+
+struct GridEntry
+{
+ VclPtr<vcl::Window> pChild;
+ sal_Int32 nSpanWidth;
+ sal_Int32 nSpanHeight;
+ int x;
+ int y;
+ GridEntry()
+ : pChild(nullptr)
+ , nSpanWidth(0)
+ , nSpanHeight(0)
+ , x(-1)
+ , y(-1)
+ {
+ }
+};
+
+}
+
+typedef boost::multi_array<GridEntry, 2> array_type;
+
+static array_type assembleGrid(const VclGrid &rGrid);
+static bool isNullGrid(const array_type& A);
+static void calcMaxs(const array_type &A, std::vector<VclGrid::Value> &rWidths, std::vector<VclGrid::Value> &rHeights);
+
+array_type assembleGrid(const VclGrid &rGrid)
+{
+ array_type A;
+
+ for (vcl::Window* pChild = rGrid.GetWindow(GetWindowType::FirstChild); pChild;
+ pChild = pChild->GetWindow(GetWindowType::Next))
+ {
+ sal_Int32 nLeftAttach = std::max<sal_Int32>(pChild->get_grid_left_attach(), 0);
+ sal_Int32 nWidth = pChild->get_grid_width();
+ sal_Int32 nMaxXPos = nLeftAttach+nWidth-1;
+
+ sal_Int32 nTopAttach = std::max<sal_Int32>(pChild->get_grid_top_attach(), 0);
+ sal_Int32 nHeight = pChild->get_grid_height();
+ sal_Int32 nMaxYPos = nTopAttach+nHeight-1;
+
+ sal_Int32 nCurrentMaxXPos = A.shape()[0]-1;
+ sal_Int32 nCurrentMaxYPos = A.shape()[1]-1;
+ if (nMaxXPos > nCurrentMaxXPos || nMaxYPos > nCurrentMaxYPos)
+ {
+ nCurrentMaxXPos = std::max(nMaxXPos, nCurrentMaxXPos);
+ nCurrentMaxYPos = std::max(nMaxYPos, nCurrentMaxYPos);
+ A.resize(boost::extents[nCurrentMaxXPos+1][nCurrentMaxYPos+1]);
+ }
+
+ GridEntry &rEntry = A[nLeftAttach][nTopAttach];
+ rEntry.pChild = pChild;
+ rEntry.nSpanWidth = nWidth;
+ rEntry.nSpanHeight = nHeight;
+ rEntry.x = nLeftAttach;
+ rEntry.y = nTopAttach;
+
+ for (sal_Int32 nSpanX = 0; nSpanX < nWidth; ++nSpanX)
+ {
+ for (sal_Int32 nSpanY = 0; nSpanY < nHeight; ++nSpanY)
+ {
+ GridEntry &rSpan = A[nLeftAttach+nSpanX][nTopAttach+nSpanY];
+ rSpan.x = nLeftAttach;
+ rSpan.y = nTopAttach;
+ }
+ }
+ }
+
+ //see if we have any empty rows/cols
+ sal_Int32 nMaxX = A.shape()[0];
+ sal_Int32 nMaxY = A.shape()[1];
+
+ std::vector<bool> aNonEmptyCols(nMaxX);
+ std::vector<bool> aNonEmptyRows(nMaxY);
+
+ for (sal_Int32 x = 0; x < nMaxX; ++x)
+ {
+ for (sal_Int32 y = 0; y < nMaxY; ++y)
+ {
+ const GridEntry &rEntry = A[x][y];
+ const vcl::Window *pChild = rEntry.pChild;
+ if (pChild && pChild->IsVisible())
+ {
+ aNonEmptyCols[x] = true;
+ if (rGrid.get_column_homogeneous())
+ {
+ for (sal_Int32 nSpanX = 1; nSpanX < rEntry.nSpanWidth; ++nSpanX)
+ aNonEmptyCols[x+nSpanX] = true;
+ }
+ aNonEmptyRows[y] = true;
+ if (rGrid.get_row_homogeneous())
+ {
+ for (sal_Int32 nSpanY = 1; nSpanY < rEntry.nSpanHeight; ++nSpanY)
+ aNonEmptyRows[y+nSpanY] = true;
+ }
+ }
+ }
+ }
+
+ if (!rGrid.get_column_homogeneous())
+ {
+ //reduce the spans of elements that span empty columns
+ for (sal_Int32 x = 0; x < nMaxX; ++x)
+ {
+ std::set<GridEntry*> candidates;
+ for (sal_Int32 y = 0; y < nMaxY; ++y)
+ {
+ if (aNonEmptyCols[x])
+ continue;
+ GridEntry &rSpan = A[x][y];
+ //cell x/y is spanned by the widget at cell rSpan.x/rSpan.y,
+ //just points back to itself if there's no cell spanning
+ if ((rSpan.x == -1) || (rSpan.y == -1))
+ {
+ //there is no entry for this cell, i.e. this is a cell
+ //with no widget in it, or spanned by any other widget
+ continue;
+ }
+ GridEntry &rEntry = A[rSpan.x][rSpan.y];
+ candidates.insert(&rEntry);
+ }
+ for (auto const& candidate : candidates)
+ {
+ GridEntry *pEntry = candidate;
+ --pEntry->nSpanWidth;
+ }
+ }
+ }
+
+ if (!rGrid.get_row_homogeneous())
+ {
+ //reduce the spans of elements that span empty rows
+ for (sal_Int32 y = 0; y < nMaxY; ++y)
+ {
+ std::set<GridEntry*> candidates;
+ for (sal_Int32 x = 0; x < nMaxX; ++x)
+ {
+ if (aNonEmptyRows[y])
+ continue;
+ GridEntry &rSpan = A[x][y];
+ //cell x/y is spanned by the widget at cell rSpan.x/rSpan.y,
+ //just points back to itself if there's no cell spanning
+ if ((rSpan.x == -1) || (rSpan.y == -1))
+ {
+ //there is no entry for this cell, i.e. this is a cell
+ //with no widget in it, or spanned by any other widget
+ continue;
+ }
+ GridEntry &rEntry = A[rSpan.x][rSpan.y];
+ candidates.insert(&rEntry);
+ }
+ for (auto const& candidate : candidates)
+ {
+ GridEntry *pEntry = candidate;
+ --pEntry->nSpanHeight;
+ }
+ }
+ }
+
+ sal_Int32 nNonEmptyCols = std::count(aNonEmptyCols.begin(), aNonEmptyCols.end(), true);
+ sal_Int32 nNonEmptyRows = std::count(aNonEmptyRows.begin(), aNonEmptyRows.end(), true);
+
+ //make new grid without empty rows and columns
+ array_type B(boost::extents[nNonEmptyCols][nNonEmptyRows]);
+ for (sal_Int32 x = 0, x2 = 0; x < nMaxX; ++x)
+ {
+ if (!aNonEmptyCols[x])
+ continue;
+ for (sal_Int32 y = 0, y2 = 0; y < nMaxY; ++y)
+ {
+ if (!aNonEmptyRows[y])
+ continue;
+ GridEntry &rEntry = A[x][y];
+ B[x2][y2++] = rEntry;
+ }
+ ++x2;
+ }
+
+ return B;
+}
+
+static bool isNullGrid(const array_type &A)
+{
+ sal_Int32 nMaxX = A.shape()[0];
+ sal_Int32 nMaxY = A.shape()[1];
+
+ return !nMaxX || !nMaxY;
+}
+
+static void calcMaxs(const array_type &A, std::vector<VclGrid::Value> &rWidths, std::vector<VclGrid::Value> &rHeights)
+{
+ sal_Int32 nMaxX = A.shape()[0];
+ sal_Int32 nMaxY = A.shape()[1];
+
+ rWidths.resize(nMaxX);
+ rHeights.resize(nMaxY);
+
+ //first use the non spanning entries to set default width/heights
+ for (sal_Int32 x = 0; x < nMaxX; ++x)
+ {
+ for (sal_Int32 y = 0; y < nMaxY; ++y)
+ {
+ const GridEntry &rEntry = A[x][y];
+ const vcl::Window *pChild = rEntry.pChild;
+ if (!pChild || !pChild->IsVisible())
+ continue;
+
+ sal_Int32 nWidth = rEntry.nSpanWidth;
+ sal_Int32 nHeight = rEntry.nSpanHeight;
+
+ for (sal_Int32 nSpanX = 0; nSpanX < nWidth; ++nSpanX)
+ rWidths[x+nSpanX].m_bExpand |= pChild->get_hexpand();
+
+ for (sal_Int32 nSpanY = 0; nSpanY < nHeight; ++nSpanY)
+ rHeights[y+nSpanY].m_bExpand |= pChild->get_vexpand();
+
+ if (nWidth == 1 || nHeight == 1)
+ {
+ Size aChildSize = VclContainer::getLayoutRequisition(*pChild);
+ if (nWidth == 1)
+ rWidths[x].m_nValue = std::max(rWidths[x].m_nValue, aChildSize.Width());
+ if (nHeight == 1)
+ rHeights[y].m_nValue = std::max(rHeights[y].m_nValue, aChildSize.Height());
+ }
+ }
+ }
+
+ //now use the spanning entries and split any extra sizes across expanding rows/cols
+ //where possible
+ for (sal_Int32 x = 0; x < nMaxX; ++x)
+ {
+ for (sal_Int32 y = 0; y < nMaxY; ++y)
+ {
+ const GridEntry &rEntry = A[x][y];
+ const vcl::Window *pChild = rEntry.pChild;
+ if (!pChild || !pChild->IsVisible())
+ continue;
+
+ sal_Int32 nWidth = rEntry.nSpanWidth;
+ sal_Int32 nHeight = rEntry.nSpanHeight;
+
+ if (nWidth == 1 && nHeight == 1)
+ continue;
+
+ Size aChildSize = VclContainer::getLayoutRequisition(*pChild);
+
+ if (nWidth > 1)
+ {
+ sal_Int32 nExistingWidth = 0;
+ for (sal_Int32 nSpanX = 0; nSpanX < nWidth; ++nSpanX)
+ nExistingWidth += rWidths[x+nSpanX].m_nValue;
+
+ sal_Int32 nExtraWidth = aChildSize.Width() - nExistingWidth;
+
+ if (nExtraWidth > 0)
+ {
+ bool bForceExpandAll = false;
+ sal_Int32 nExpandables = 0;
+ for (sal_Int32 nSpanX = 0; nSpanX < nWidth; ++nSpanX)
+ if (rWidths[x+nSpanX].m_bExpand)
+ ++nExpandables;
+ if (nExpandables == 0)
+ {
+ nExpandables = nWidth;
+ bForceExpandAll = true;
+ }
+
+ for (sal_Int32 nSpanX = 0; nSpanX < nWidth; ++nSpanX)
+ {
+ if (rWidths[x+nSpanX].m_bExpand || bForceExpandAll)
+ rWidths[x+nSpanX].m_nValue += nExtraWidth/nExpandables;
+ }
+ }
+ }
+
+ if (nHeight > 1)
+ {
+ sal_Int32 nExistingHeight = 0;
+ for (sal_Int32 nSpanY = 0; nSpanY < nHeight; ++nSpanY)
+ nExistingHeight += rHeights[y+nSpanY].m_nValue;
+
+ sal_Int32 nExtraHeight = aChildSize.Height() - nExistingHeight;
+
+ if (nExtraHeight > 0)
+ {
+ bool bForceExpandAll = false;
+ sal_Int32 nExpandables = 0;
+ for (sal_Int32 nSpanY = 0; nSpanY < nHeight; ++nSpanY)
+ if (rHeights[y+nSpanY].m_bExpand)
+ ++nExpandables;
+ if (nExpandables == 0)
+ {
+ nExpandables = nHeight;
+ bForceExpandAll = true;
+ }
+
+ for (sal_Int32 nSpanY = 0; nSpanY < nHeight; ++nSpanY)
+ {
+ if (rHeights[y+nSpanY].m_bExpand || bForceExpandAll)
+ rHeights[y+nSpanY].m_nValue += nExtraHeight/nExpandables;
+ }
+ }
+ }
+ }
+ }
+}
+
+static bool compareValues(const VclGrid::Value &i, const VclGrid::Value &j)
+{
+ return i.m_nValue < j.m_nValue;
+}
+
+static VclGrid::Value accumulateValues(const VclGrid::Value &i, const VclGrid::Value &j)
+{
+ VclGrid::Value aRet;
+ aRet.m_nValue = i.m_nValue + j.m_nValue;
+ aRet.m_bExpand = i.m_bExpand || j.m_bExpand;
+ return aRet;
+}
+
+Size VclGrid::calculateRequisition() const
+{
+ return calculateRequisitionForSpacings(get_row_spacing(), get_column_spacing());
+}
+
+Size VclGrid::calculateRequisitionForSpacings(sal_Int32 nRowSpacing, sal_Int32 nColSpacing) const
+{
+ array_type A = assembleGrid(*this);
+
+ if (isNullGrid(A))
+ return Size();
+
+ std::vector<Value> aWidths;
+ std::vector<Value> aHeights;
+ calcMaxs(A, aWidths, aHeights);
+
+ 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);
+
+ 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());
+
+ 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;
+ 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
+ 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;
+ }
+ }
+
+ 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;
+ 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
+ long nExtraHeight = (rAllocation.Height() - aRequisition.Height()) / nMaxY;
+
+ for (sal_Int32 y = 0; y < nMaxY; ++y)
+ aHeights[y].m_nValue += nExtraHeight;
+ }
+
+ if (nExtraHeightForExpanders)
+ {
+ for (sal_Int32 y = 0; y < nMaxY; ++y)
+ if (aHeights[y].m_bExpand)
+ aHeights[y].m_nValue += nExtraHeightForExpanders;
+ }
+ }
+
+ Point aAllocPos(0, 0);
+ for (sal_Int32 x = 0; x < nMaxX; ++x)
+ {
+ for (sal_Int32 y = 0; y < nMaxY; ++y)
+ {
+ GridEntry &rEntry = A[x][y];
+ vcl::Window *pChild = rEntry.pChild;
+ if (pChild)
+ {
+ Size aChildAlloc(0, 0);
+
+ sal_Int32 nWidth = rEntry.nSpanWidth;
+ for (sal_Int32 nSpanX = 0; nSpanX < nWidth; ++nSpanX)
+ aChildAlloc.AdjustWidth(aWidths[x+nSpanX].m_nValue );
+ aChildAlloc.AdjustWidth(nColSpacing*(nWidth-1) );
+
+ sal_Int32 nHeight = rEntry.nSpanHeight;
+ for (sal_Int32 nSpanY = 0; nSpanY < nHeight; ++nSpanY)
+ aChildAlloc.AdjustHeight(aHeights[y+nSpanY].m_nValue );
+ aChildAlloc.AdjustHeight(nRowSpacing*(nHeight-1) );
+
+ setLayoutAllocation(*pChild, aAllocPos, aChildAlloc);
+ }
+ aAllocPos.AdjustY(aHeights[y].m_nValue + nRowSpacing );
+ }
+ aAllocPos.AdjustX(aWidths[x].m_nValue + nColSpacing );
+ aAllocPos.setY( 0 );
+ }
+}
+
+boost::property_tree::ptree VclGrid::DumpAsPropertyTree()
+{
+ boost::property_tree::ptree aTree(VclContainer::DumpAsPropertyTree());
+ aTree.put("type", "grid");
+ return aTree;
+}
+
+bool toBool(const OUString &rValue)
+{
+ return (!rValue.isEmpty() && (rValue[0] == 't' || rValue[0] == 'T' || rValue[0] == '1'));
+}
+
+bool VclGrid::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "row-spacing")
+ set_row_spacing(rValue.toInt32());
+ else if (rKey == "column-spacing")
+ set_column_spacing(rValue.toInt32());
+ else if (rKey == "row-homogeneous")
+ m_bRowHomogeneous = toBool(rValue);
+ else if (rKey == "column-homogeneous")
+ m_bColumnHomogeneous = toBool(rValue);
+ else if (rKey == "n-rows")
+ /*nothing to do*/;
+ else
+ return VclContainer::set_property(rKey, rValue);
+ return true;
+}
+
+const vcl::Window *VclBin::get_child() const
+{
+ const WindowImpl* pWindowImpl = ImplGetWindowImpl();
+
+ return pWindowImpl->mpFirstChild;
+}
+
+vcl::Window *VclBin::get_child()
+{
+ return const_cast<vcl::Window*>(const_cast<const VclBin*>(this)->get_child());
+}
+
+Size VclBin::calculateRequisition() const
+{
+ const vcl::Window *pChild = get_child();
+ if (pChild && pChild->IsVisible())
+ return getLayoutRequisition(*pChild);
+ return Size(0, 0);
+}
+
+void VclBin::setAllocation(const Size &rAllocation)
+{
+ vcl::Window *pChild = get_child();
+ if (pChild && pChild->IsVisible())
+ setLayoutAllocation(*pChild, Point(0, 0), rAllocation);
+}
+
+VclFrame::~VclFrame()
+{
+ disposeOnce();
+}
+
+void VclFrame::dispose()
+{
+ m_pLabel.clear();
+ VclBin::dispose();
+}
+
+//To-Do, hook a DecorationView into VclFrame ?
+
+Size VclFrame::calculateRequisition() const
+{
+ Size aRet(0, 0);
+
+ const vcl::Window *pChild = get_child();
+ const vcl::Window *pLabel = get_label_widget();
+
+ if (pChild && pChild->IsVisible())
+ aRet = getLayoutRequisition(*pChild);
+
+ if (pLabel && pLabel->IsVisible())
+ {
+ Size aLabelSize = getLayoutRequisition(*pLabel);
+ aRet.AdjustHeight(aLabelSize.Height() );
+ aRet.setWidth( std::max(aLabelSize.Width(), aRet.Width()) );
+ }
+
+ return aRet;
+}
+
+void VclFrame::setAllocation(const Size &rAllocation)
+{
+ //SetBackground( Color(0xFF, 0x00, 0xFF) );
+
+ Size aAllocation(rAllocation);
+ Point aChildPos;
+
+ vcl::Window *pChild = get_child();
+ vcl::Window *pLabel = get_label_widget();
+
+ if (pLabel && pLabel->IsVisible())
+ {
+ Size aLabelSize = getLayoutRequisition(*pLabel);
+ aLabelSize.setHeight( std::min(aLabelSize.Height(), aAllocation.Height()) );
+ aLabelSize.setWidth( std::min(aLabelSize.Width(), aAllocation.Width()) );
+ setLayoutAllocation(*pLabel, aChildPos, aLabelSize);
+ aAllocation.AdjustHeight( -(aLabelSize.Height()) );
+ aChildPos.AdjustY(aLabelSize.Height() );
+ }
+
+ if (pChild && pChild->IsVisible())
+ setLayoutAllocation(*pChild, aChildPos, aAllocation);
+}
+
+IMPL_LINK(VclFrame, WindowEventListener, VclWindowEvent&, rEvent, void)
+{
+ if (rEvent.GetId() == VclEventId::ObjectDying)
+ designate_label(nullptr);
+}
+
+void VclFrame::designate_label(vcl::Window *pWindow)
+{
+ assert(!pWindow || pWindow->GetParent() == this);
+ if (m_pLabel)
+ m_pLabel->RemoveEventListener(LINK(this, VclFrame, WindowEventListener));
+ m_pLabel = pWindow;
+ if (m_pLabel)
+ m_pLabel->AddEventListener(LINK(this, VclFrame, WindowEventListener));
+}
+
+const vcl::Window *VclFrame::get_label_widget() const
+{
+ assert(GetChildCount() == 2);
+ if (m_pLabel)
+ return m_pLabel;
+ //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();
+}
+
+boost::property_tree::ptree VclFrame::DumpAsPropertyTree()
+{
+ boost::property_tree::ptree aTree(VclBin::DumpAsPropertyTree());
+ aTree.put("type", "frame");
+ return aTree;
+}
+
+Size VclAlignment::calculateRequisition() const
+{
+ Size aRet(m_nLeftPadding + m_nRightPadding,
+ m_nTopPadding + m_nBottomPadding);
+
+ const vcl::Window *pChild = get_child();
+ if (pChild && pChild->IsVisible())
+ {
+ Size aChildSize = getLayoutRequisition(*pChild);
+ aRet.AdjustWidth(aChildSize.Width() );
+ aRet.AdjustHeight(aChildSize.Height() );
+ }
+
+ return aRet;
+}
+
+void VclAlignment::setAllocation(const Size &rAllocation)
+{
+ vcl::Window *pChild = get_child();
+ if (!pChild || !pChild->IsVisible())
+ return;
+
+ Point aChildPos(m_nLeftPadding, m_nTopPadding);
+
+ Size aAllocation;
+ aAllocation.setWidth( rAllocation.Width() - (m_nLeftPadding + m_nRightPadding) );
+ aAllocation.setHeight( rAllocation.Height() - (m_nTopPadding + m_nBottomPadding) );
+
+ setLayoutAllocation(*pChild, aChildPos, aAllocation);
+}
+
+bool VclAlignment::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "bottom-padding")
+ m_nBottomPadding = rValue.toInt32();
+ else if (rKey == "left-padding")
+ m_nLeftPadding = rValue.toInt32();
+ else if (rKey == "right-padding")
+ m_nRightPadding = rValue.toInt32();
+ else if (rKey == "top-padding")
+ m_nTopPadding = rValue.toInt32();
+ else
+ return VclBin::set_property(rKey, rValue);
+ return true;
+}
+
+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().IsInside(GetPointerPosPixel()))
+ nState |= ControlState::ROLLOVER;
+
+ if (rRenderContext.DrawNativeControl(ControlType::ListNode, ControlPart::Entire, aCtrlRegion,
+ nState, aControlValue, OUString()))
+ return;
+
+ ImplSVCtrlData& rCtrlData(ImplGetSVData()->maCtrlData);
+ if (!rCtrlData.mpDisclosurePlus)
+ rCtrlData.mpDisclosurePlus.reset(new Image(StockImage::Yes, SV_DISCLOSURE_PLUS));
+ if (!rCtrlData.mpDisclosureMinus)
+ rCtrlData.mpDisclosureMinus.reset(new Image(StockImage::Yes, SV_DISCLOSURE_MINUS));
+
+ Image* pImg
+ = IsChecked() ? rCtrlData.mpDisclosureMinus.get() : rCtrlData.mpDisclosurePlus.get();
+
+ DrawImageFlags nStyle = DrawImageFlags::NONE;
+ if (!IsEnabled())
+ nStyle |= DrawImageFlags::Disable;
+
+ Size aSize(aStateRect.GetSize());
+ Size aImgSize(pImg->GetSizePixel());
+ Point aOff((aSize.Width() - aImgSize.Width()) / 2,
+ (aSize.Height() - aImgSize.Height()) / 2);
+ aOff += aStateRect.TopLeft();
+ rRenderContext.DrawImage(aOff, *pImg, nStyle);
+ }
+
+public:
+ explicit DisclosureButton(vcl::Window* pParent)
+ : CheckBox(pParent, 0)
+ {
+ }
+
+ virtual void KeyInput( const KeyEvent& rKEvt ) override
+ {
+ vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
+
+ if( !aKeyCode.GetModifier() &&
+ ( ( aKeyCode.GetCode() == KEY_ADD ) ||
+ ( aKeyCode.GetCode() == KEY_SUBTRACT ) )
+ )
+ {
+ Check( aKeyCode.GetCode() == KEY_ADD );
+ }
+ else
+ CheckBox::KeyInput( rKEvt );
+ }
+};
+
+VclExpander::VclExpander(vcl::Window *pParent)
+ : VclBin(pParent)
+ , m_bResizeTopLevel(true)
+ , 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);
+}
+
+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()) );
+
+ 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()) );
+
+ long nExtraLabelHeight = aExpanderSize.Height() - aLabelSize.Height();
+ Point aLabelPos(aChildPos.X() + aButtonSize.Width(), aChildPos.Y() + nExtraLabelHeight/2);
+ setLayoutAllocation(*pLabel, aLabelPos, aLabelSize);
+ }
+
+ aAllocation.AdjustHeight( -(aExpanderSize.Height()) );
+ aChildPos.AdjustY(aExpanderSize.Height() );
+
+ if (pChild && pChild->IsVisible())
+ {
+ if (!m_pDisclosureButton->IsChecked())
+ aAllocation = Size();
+ setLayoutAllocation(*pChild, aChildPos, aAllocation);
+ }
+}
+
+bool VclExpander::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "expanded")
+ set_expanded(toBool(rValue));
+ else if (rKey == "resize-toplevel")
+ m_bResizeTopLevel = toBool(rValue);
+ else
+ return VclBin::set_property(rKey, rValue);
+ return true;
+}
+
+void VclExpander::StateChanged(StateChangedType nType)
+{
+ VclBin::StateChanged( nType );
+
+ if (nType == StateChangedType::InitShow)
+ {
+ vcl::Window *pChild = get_child();
+ if (pChild)
+ pChild->Show(m_pDisclosureButton->IsChecked());
+ }
+}
+
+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();
+ }
+ 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_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);
+
+ Link<ScrollBar*,void> aLink( LINK( this, VclScrolledWindow, ScrollBarHdl ) );
+ m_pVScroll->SetScrollHdl(aLink);
+ m_pHScroll->SetScrollHdl(aLink);
+}
+
+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);
+ aRet.AdjustWidth(2);
+
+ 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);
+
+ long nAvailHeight = rAllocation.Height() - 2;
+ long nAvailWidth = rAllocation.Width() - 2;
+ // vert. ScrollBar
+ if (GetStyle() & WB_AUTOVSCROLL)
+ {
+ m_pVScroll->Show(nAvailHeight < aChildReq.Height());
+ }
+ else if (m_pVScroll->IsVisible() != bool(GetStyle() & WB_VSCROLL))
+ m_pVScroll->Show((GetStyle() & WB_VSCROLL) != 0);
+
+ if (m_pVScroll->IsVisible())
+ nAvailWidth -= getLayoutRequisition(*m_pVScroll).Width();
+
+ // horz. ScrollBar
+ if (GetStyle() & WB_AUTOHSCROLL)
+ {
+ bool bShowHScroll = nAvailWidth < aChildReq.Width();
+ m_pHScroll->Show(bShowHScroll);
+
+ if (bShowHScroll)
+ nAvailHeight -= getLayoutRequisition(*m_pHScroll).Height();
+
+ if (GetStyle() & WB_AUTOVSCROLL)
+ m_pVScroll->Show(nAvailHeight < aChildReq.Height());
+ }
+ else if (m_pHScroll->IsVisible() != bool(GetStyle() & WB_HSCROLL))
+ m_pHScroll->Show((GetStyle() & WB_HSCROLL) != 0);
+
+ Size aInnerSize(rAllocation);
+ aInnerSize.AdjustWidth(-2);
+ aInnerSize.AdjustHeight(-2);
+
+ 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 - 2, 1);
+ Size aScrollSize(nScrollBarWidth, rAllocation.Height() - 2);
+ if (bBothVisible)
+ aScrollSize.AdjustHeight(-nScrollBarHeight);
+ setLayoutAllocation(*m_pVScroll, aScrollPos, aScrollSize);
+ aInnerSize.AdjustWidth( -nScrollBarWidth );
+ }
+
+ if (m_pHScroll->IsVisible())
+ {
+ Point aScrollPos(1, rAllocation.Height() - nScrollBarHeight);
+ Size aScrollSize(rAllocation.Width() - 2, nScrollBarHeight);
+ if (bBothVisible)
+ aScrollSize.AdjustWidth(-nScrollBarWidth);
+ setLayoutAllocation(*m_pHScroll, aScrollPos, aScrollSize);
+ aInnerSize.AdjustHeight( -nScrollBarHeight );
+ }
+
+ if (bBothVisible)
+ {
+ Point aBoxPos(aInnerSize.Width() + 1, aInnerSize.Height() + 1);
+ 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(1, 1), 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);
+ aRet.AdjustWidth(-2);
+ return aRet;
+}
+
+bool VclScrolledWindow::set_property(const OString &rKey, const OUString &rValue)
+{
+ 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;
+ return true;
+ }
+
+ bool bRet = VclBin::set_property(rKey, rValue);
+ m_pVScroll->Show((GetStyle() & WB_VSCROLL) != 0);
+ m_pHScroll->Show((GetStyle() & WB_HSCROLL) != 0);
+ return bRet;
+}
+
+bool VclScrolledWindow::EventNotify(NotifyEvent& rNEvt)
+{
+ bool bDone = false;
+ if ( rNEvt.GetType() == MouseNotifyEvent::COMMAND )
+ {
+ const CommandEvent& rCEvt = *rNEvt.GetCommandEvent();
+ if ( rCEvt.GetCommand() == CommandEventId::Wheel )
+ {
+ const CommandWheelData* pData = rCEvt.GetWheelData();
+ if( !pData->GetModifier() && ( pData->GetMode() == CommandWheelMode::SCROLL ) )
+ {
+ bDone = HandleScrollCommand(rCEvt, m_pHScroll, m_pVScroll);
+ }
+ }
+ }
+
+ return bDone || VclBin::EventNotify( rNEvt );
+}
+
+void VclScrolledWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ VclBin::Paint(rRenderContext, rRect);
+ DecorationView aDecoView(&rRenderContext);
+ aDecoView.DrawFrame(tools::Rectangle(Point(0,0), GetSizePixel()), m_eDrawFrameStyle);
+}
+
+void VclViewport::setAllocation(const Size &rAllocation)
+{
+ vcl::Window *pChild = get_child();
+ if (pChild && pChild->IsVisible())
+ {
+ Size aReq(getLayoutRequisition(*pChild));
+ aReq.setWidth( std::max(aReq.Width(), rAllocation.Width()) );
+ aReq.setHeight( std::max(aReq.Height(), rAllocation.Height()) );
+ Point aKeepPos(pChild->GetPosPixel());
+ if (m_bInitialAllocation)
+ {
+ aKeepPos = Point(0, 0);
+ m_bInitialAllocation = false;
+ }
+ setLayoutAllocation(*pChild, aKeepPos, aReq);
+ }
+}
+
+const vcl::Window *VclEventBox::get_child() const
+{
+ const WindowImpl* pWindowImpl = ImplGetWindowImpl();
+
+ assert(pWindowImpl->mpFirstChild.get() == m_aEventBoxHelper.get());
+
+ return pWindowImpl->mpFirstChild->GetWindow(GetWindowType::Next);
+}
+
+vcl::Window *VclEventBox::get_child()
+{
+ return const_cast<vcl::Window*>(const_cast<const VclEventBox*>(this)->get_child());
+}
+
+void VclEventBox::setAllocation(const Size& rAllocation)
+{
+ Point aChildPos(0, 0);
+ for (vcl::Window *pChild = GetWindow(GetWindowType::FirstChild); pChild; pChild = pChild->GetWindow(GetWindowType::Next))
+ {
+ if (!pChild->IsVisible())
+ continue;
+ setLayoutAllocation(*pChild, aChildPos, rAllocation);
+ }
+}
+
+Size VclEventBox::calculateRequisition() const
+{
+ Size aRet(0, 0);
+
+ for (const vcl::Window* pChild = get_child(); pChild;
+ pChild = pChild->GetWindow(GetWindowType::Next))
+ {
+ if (!pChild->IsVisible())
+ continue;
+ Size aChildSize = getLayoutRequisition(*pChild);
+ aRet.setWidth( std::max(aRet.Width(), aChildSize.Width()) );
+ aRet.setHeight( std::max(aRet.Height(), aChildSize.Height()) );
+ }
+
+ return aRet;
+}
+
+void VclEventBox::Command(const CommandEvent&)
+{
+ //discard events by default to block them reaching children
+}
+
+VclEventBox::~VclEventBox()
+{
+ disposeOnce();
+}
+
+void VclEventBox::dispose()
+{
+ m_aEventBoxHelper.disposeAndClear();
+ VclBin::dispose();
+}
+
+void VclSizeGroup::trigger_queue_resize()
+{
+ //sufficient to trigger one widget to trigger all of them
+ if (!m_aWindows.empty())
+ {
+ (*m_aWindows.begin())->queue_resize();
+ }
+}
+
+void VclSizeGroup::set_ignore_hidden(bool bIgnoreHidden)
+{
+ if (bIgnoreHidden != m_bIgnoreHidden)
+ {
+ m_bIgnoreHidden = bIgnoreHidden;
+ trigger_queue_resize();
+ }
+}
+
+void VclSizeGroup::set_mode(VclSizeGroupMode eMode)
+{
+ if (eMode != m_eMode)
+ {
+ m_eMode = eMode;
+ trigger_queue_resize();
+ }
+
+}
+
+void VclSizeGroup::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "ignore-hidden")
+ set_ignore_hidden(toBool(rValue));
+ else if (rKey == "mode")
+ {
+ VclSizeGroupMode eMode = VclSizeGroupMode::Horizontal;
+ if (rValue == "none")
+ eMode = VclSizeGroupMode::NONE;
+ else if (rValue == "horizontal")
+ eMode = VclSizeGroupMode::Horizontal;
+ else if (rValue == "vertical")
+ eMode = VclSizeGroupMode::Vertical;
+ else if (rValue == "both")
+ eMode = VclSizeGroupMode::Both;
+ else
+ {
+ SAL_WARN("vcl.layout", "unknown size group mode" << rValue);
+ }
+ set_mode(eMode);
+ }
+ else
+ {
+ SAL_INFO("vcl.layout", "unhandled property: " << rKey);
+ }
+}
+
+void MessageDialog::create_message_area()
+{
+ setDeferredProperties();
+
+ if (!m_pGrid)
+ {
+ 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);
+ pButtonBox->sort_native_button_order();
+ m_pMessageBox->Show();
+ m_pGrid->Show();
+ }
+}
+
+void MessageDialog::create_owned_areas()
+{
+#if defined _WIN32
+ set_border_width(3);
+#else
+ set_border_width(12);
+#endif
+ m_pOwnedContentArea.set(VclPtr<VclVBox>::Create(this, false, 24));
+ set_content_area(m_pOwnedContentArea);
+ m_pOwnedContentArea->Show();
+ m_pOwnedActionArea.set( VclPtr<VclHButtonBox>::Create(m_pOwnedContentArea) );
+ set_action_area(m_pOwnedActionArea);
+ m_pOwnedActionArea->Show();
+}
+
+MessageDialog::MessageDialog(vcl::Window* pParent, WinBits nStyle)
+ : Dialog(pParent, nStyle)
+ , m_eButtonsType(VclButtonsType::NONE)
+ , m_eMessageType(VclMessageType::Info)
+ , m_pOwnedContentArea(nullptr)
+ , m_pOwnedActionArea(nullptr)
+ , m_pGrid(nullptr)
+ , m_pMessageBox(nullptr)
+ , m_pImage(nullptr)
+ , m_pPrimaryMessage(nullptr)
+ , m_pSecondaryMessage(nullptr)
+{
+ SetType(WindowType::MESSBOX);
+}
+
+MessageDialog::MessageDialog(vcl::Window* pParent,
+ const OUString &rMessage,
+ VclMessageType eMessageType,
+ VclButtonsType eButtonsType)
+ : Dialog(pParent, WB_MOVEABLE | WB_3DLOOK | WB_CLOSEABLE)
+ , m_eButtonsType(eButtonsType)
+ , m_eMessageType(eMessageType)
+ , m_pGrid(nullptr)
+ , m_pMessageBox(nullptr)
+ , m_pImage(nullptr)
+ , m_pPrimaryMessage(nullptr)
+ , m_pSecondaryMessage(nullptr)
+ , m_sPrimaryString(rMessage)
+{
+ SetType(WindowType::MESSBOX);
+ create_owned_areas();
+ create_message_area();
+
+ switch (m_eMessageType)
+ {
+ case VclMessageType::Info:
+ SetText(GetStandardInfoBoxText());
+ break;
+ case VclMessageType::Warning:
+ SetText(GetStandardWarningBoxText());
+ break;
+ case VclMessageType::Question:
+ SetText(GetStandardQueryBoxText());
+ break;
+ case VclMessageType::Error:
+ SetText(GetStandardErrorBoxText());
+ break;
+ case VclMessageType::Other:
+ SetText(Application::GetDisplayName());
+ break;
+ }
+}
+
+void MessageDialog::dispose()
+{
+ disposeOwnedButtons();
+ m_pPrimaryMessage.disposeAndClear();
+ m_pSecondaryMessage.disposeAndClear();
+ m_pImage.disposeAndClear();
+ m_pMessageBox.disposeAndClear();
+ m_pGrid.disposeAndClear();
+ m_pOwnedActionArea.disposeAndClear();
+ m_pOwnedContentArea.disposeAndClear();
+ Dialog::dispose();
+}
+
+MessageDialog::~MessageDialog()
+{
+ disposeOnce();
+}
+
+void MessageDialog::SetMessagesWidths(vcl::Window const *pParent,
+ VclMultiLineEdit *pPrimaryMessage, VclMultiLineEdit *pSecondaryMessage)
+{
+ if (pSecondaryMessage)
+ {
+ assert(pPrimaryMessage);
+ vcl::Font aFont = pParent->GetSettings().GetStyleSettings().GetLabelFont();
+ aFont.SetFontSize(Size(0, aFont.GetFontSize().Height() * 1.2));
+ aFont.SetWeight(WEIGHT_BOLD);
+ pPrimaryMessage->SetControlFont(aFont);
+ pPrimaryMessage->SetMaxTextWidth(pPrimaryMessage->approximate_char_width() * 44);
+ pSecondaryMessage->SetMaxTextWidth(pSecondaryMessage->approximate_char_width() * 60);
+ }
+ else
+ pPrimaryMessage->SetMaxTextWidth(pPrimaryMessage->approximate_char_width() * 60);
+}
+
+OUString const & MessageDialog::get_primary_text() const
+{
+ const_cast<MessageDialog*>(this)->setDeferredProperties();
+
+ return m_sPrimaryString;
+}
+
+OUString const & MessageDialog::get_secondary_text() const
+{
+ const_cast<MessageDialog*>(this)->setDeferredProperties();
+
+ return m_sSecondaryString;
+}
+
+bool MessageDialog::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "text")
+ set_primary_text(rValue);
+ else if (rKey == "secondary-text")
+ set_secondary_text(rValue);
+ else if (rKey == "message-type")
+ {
+ VclMessageType eMode = VclMessageType::Info;
+ if (rValue == "info")
+ eMode = VclMessageType::Info;
+ else if (rValue == "warning")
+ eMode = VclMessageType::Warning;
+ else if (rValue == "question")
+ eMode = VclMessageType::Question;
+ else if (rValue == "error")
+ eMode = VclMessageType::Error;
+ else if (rValue == "other")
+ eMode = VclMessageType::Other;
+ else
+ {
+ SAL_WARN("vcl.layout", "unknown message type mode" << rValue);
+ }
+ m_eMessageType = eMode;
+ }
+ else if (rKey == "buttons")
+ {
+ VclButtonsType eMode = VclButtonsType::NONE;
+ if (rValue == "none")
+ eMode = VclButtonsType::NONE;
+ else if (rValue == "ok")
+ eMode = VclButtonsType::Ok;
+ else if (rValue == "cancel")
+ eMode = VclButtonsType::Cancel;
+ else if (rValue == "close")
+ eMode = VclButtonsType::Close;
+ else if (rValue == "yes-no")
+ eMode = VclButtonsType::YesNo;
+ else if (rValue == "ok-cancel")
+ eMode = VclButtonsType::OkCancel;
+ else
+ {
+ SAL_WARN("vcl.layout", "unknown buttons type mode" << rValue);
+ }
+ m_eButtonsType = eMode;
+ }
+ else
+ return Dialog::set_property(rKey, rValue);
+ return true;
+}
+
+void MessageDialog::set_primary_text(const OUString &rPrimaryString)
+{
+ m_sPrimaryString = rPrimaryString;
+ if (m_pPrimaryMessage)
+ {
+ m_pPrimaryMessage->SetText(m_sPrimaryString);
+ m_pPrimaryMessage->Show(!m_sPrimaryString.isEmpty());
+ MessageDialog::SetMessagesWidths(this, m_pPrimaryMessage, !m_sSecondaryString.isEmpty() ? m_pSecondaryMessage.get() : nullptr);
+ }
+}
+
+void MessageDialog::set_secondary_text(const OUString &rSecondaryString)
+{
+ m_sSecondaryString = rSecondaryString;
+ if (m_pSecondaryMessage)
+ {
+ m_pSecondaryMessage->SetText("\n" + m_sSecondaryString);
+ m_pSecondaryMessage->Show(!m_sSecondaryString.isEmpty());
+ MessageDialog::SetMessagesWidths(this, m_pPrimaryMessage, !m_sSecondaryString.isEmpty() ? m_pSecondaryMessage.get() : nullptr);
+ }
+}
+
+void MessageDialog::StateChanged(StateChangedType nType)
+{
+ Dialog::StateChanged(nType);
+ if (nType == StateChangedType::InitShow)
+ {
+ // MessageBox should be at least as wide as to see the title
+ auto nTitleWidth = CalcTitleWidth();
+ // Extra-Width for Close button
+ nTitleWidth += mpWindowImpl->mnTopBorder;
+ if (get_preferred_size().Width() < nTitleWidth)
+ {
+ set_width_request(nTitleWidth);
+ DoInitialLayout();
+ }
+ }
+}
+
+VclPaned::VclPaned(vcl::Window *pParent, bool bVertical)
+ : VclContainer(pParent, WB_HIDE | WB_CLIPCHILDREN)
+ , m_pSplitter(VclPtr<Splitter>::Create(this, bVertical ? WB_VSCROLL : WB_HSCROLL))
+ , m_nPosition(-1)
+{
+ m_pSplitter->SetBackground(Wallpaper(Application::GetSettings().GetStyleSettings().GetFaceColor()));
+ m_pSplitter->Show();
+}
+
+void VclPaned::dispose()
+{
+ m_pSplitter.disposeAndClear();
+ VclContainer::dispose();
+}
+
+VclVPaned::VclVPaned(vcl::Window *pParent)
+ : VclPaned(pParent, true)
+{
+ m_pSplitter->SetSplitHdl(LINK(this, VclVPaned, SplitHdl));
+}
+
+IMPL_LINK(VclVPaned, SplitHdl, Splitter*, pSplitter, void)
+{
+ 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, long nFirstHeight, 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(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 long nHeight = rAllocation.Height() - aSplitterSize.Height();
+
+ long nFirstHeight = 0;
+ 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();
+ bFirstCanResize = pChild->get_expand();
+ }
+ else if (nElement == 2)
+ {
+ if (bInitialAllocation)
+ nSecondHeight = getLayoutRequisition(*pChild).Height();
+ else
+ nSecondHeight = pChild->GetSizePixel().Height();
+ bSecondCanResize = pChild->get_expand();
+ }
+ ++nElement;
+ }
+ long nHeightRequest = nFirstHeight + nSecondHeight;
+ 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)
+{
+ 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, long nFirstWidth, 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(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 long nWidth = rAllocation.Width() - aSplitterSize.Width();
+
+ long nFirstWidth = 0;
+ 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();
+ bFirstCanResize = pChild->get_expand();
+ }
+ else if (nElement == 2)
+ {
+ if (bInitialAllocation)
+ nSecondWidth = getLayoutRequisition(*pChild).Width();
+ else
+ nSecondWidth = pChild->GetSizePixel().Width();
+ bSecondCanResize = pChild->get_expand();
+ }
+ ++nElement;
+ }
+ long nWidthRequest = nFirstWidth + nSecondWidth;
+ 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::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
+{
+ OUString sSurroundingText;
+ if (m_aGetSurroundingHdl.Call(sSurroundingText) != -1)
+ return sSurroundingText;
+ return Control::GetSurroundingText();
+}
+
+Selection VclDrawingArea::GetSurroundingTextSelection() const
+{
+ OUString sSurroundingText;
+ int nCursor = m_aGetSurroundingHdl.Call(sSurroundingText);
+ if (nCursor != -1)
+ return Selection(nCursor, nCursor);
+ return Control::GetSurroundingTextSelection();
+}
+
+VclHPaned::~VclHPaned()
+{
+}
+
+VclVPaned::~VclVPaned()
+{
+}
+
+VclPaned::~VclPaned()
+{
+ disposeOnce();
+}
+
+VclScrolledWindow::~VclScrolledWindow()
+{
+ disposeOnce();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/legacyaccessibility.cxx b/vcl/source/window/legacyaccessibility.cxx
new file mode 100644
index 000000000..376d0d08a
--- /dev/null
+++ b/vcl/source/window/legacyaccessibility.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 <window.h>
+
+#include "dlgctrl.hxx"
+
+using namespace ::com::sun::star;
+
+
+static vcl::Window* ImplGetLabelFor( vcl::Window* pFrameWindow, WindowType nMyType, vcl::Window* pLabel, sal_Unicode nAccel )
+{
+ vcl::Window* pWindow = nullptr;
+
+ if( nMyType == WindowType::FIXEDTEXT ||
+ nMyType == WindowType::FIXEDLINE ||
+ nMyType == WindowType::GROUPBOX )
+ {
+ // #i100833# MT 2010/02: Group box and fixed lines can also label a fixed text.
+ // See tools/options/print for example.
+ bool bThisIsAGroupControl = (nMyType == WindowType::GROUPBOX) || (nMyType == WindowType::FIXEDLINE);
+ // get index, form start and form end
+ sal_uInt16 nIndex=0, nFormStart=0, nFormEnd=0;
+ ::ImplFindDlgCtrlWindow( pFrameWindow,
+ pLabel,
+ nIndex,
+ nFormStart,
+ nFormEnd );
+ if( nAccel )
+ {
+ // find the accelerated window
+ pWindow = ::ImplFindAccelWindow( pFrameWindow,
+ nIndex,
+ nAccel,
+ nFormStart,
+ nFormEnd,
+ false );
+ }
+ else
+ {
+ // find the next control; if that is a fixed text
+ // fixed line or group box, then return NULL
+ while( nIndex < nFormEnd )
+ {
+ nIndex++;
+ vcl::Window* pSWindow = ::ImplGetChildWindow( pFrameWindow,
+ nIndex,
+ nIndex,
+ false );
+ if( pSWindow && isVisibleInLayout(pSWindow) && ! (pSWindow->GetStyle() & WB_NOLABEL) )
+ {
+ WindowType nType = pSWindow->GetType();
+ if( nType != WindowType::FIXEDTEXT &&
+ nType != WindowType::FIXEDLINE &&
+ nType != WindowType::GROUPBOX )
+ {
+ pWindow = pSWindow;
+ }
+ else if( bThisIsAGroupControl && ( nType == WindowType::FIXEDTEXT ) )
+ {
+ pWindow = pSWindow;
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ return pWindow;
+}
+
+namespace vcl {
+
+Window* Window::getLegacyNonLayoutAccessibleRelationLabelFor() const
+{
+ Window* pFrameWindow = ImplGetFrameWindow();
+
+ WinBits nFrameStyle = pFrameWindow->GetStyle();
+ if( ! ( nFrameStyle & WB_DIALOGCONTROL )
+ || ( nFrameStyle & WB_NODIALOGCONTROL )
+ )
+ return nullptr;
+
+ sal_Unicode nAccel = getAccel( GetText() );
+
+ Window* pWindow = ImplGetLabelFor( pFrameWindow, GetType(), const_cast<Window*>(this), nAccel );
+ if( ! pWindow && mpWindowImpl->mpRealParent )
+ pWindow = ImplGetLabelFor( mpWindowImpl->mpRealParent, GetType(), const_cast<Window*>(this), nAccel );
+ return pWindow;
+}
+
+static Window* ImplGetLabeledBy( Window* pFrameWindow, WindowType nMyType, Window* pLabeled )
+{
+ Window* pWindow = nullptr;
+ if ( (nMyType != WindowType::GROUPBOX) && (nMyType != WindowType::FIXEDLINE) )
+ {
+ // search for a control that labels this window
+ // a label is considered the last fixed text, fixed line or group box
+ // that comes before this control; with the exception of push buttons
+ // which are labeled only if the fixed text, fixed line or group box
+ // is directly before the control
+
+ // get form start and form end and index of this control
+ sal_uInt16 nIndex, nFormStart, nFormEnd;
+ Window* pSWindow = ::ImplFindDlgCtrlWindow( pFrameWindow,
+ pLabeled,
+ nIndex,
+ nFormStart,
+ nFormEnd );
+ if( pSWindow && nIndex != nFormStart )
+ {
+ if( nMyType == WindowType::PUSHBUTTON ||
+ nMyType == WindowType::HELPBUTTON ||
+ nMyType == WindowType::OKBUTTON ||
+ nMyType == WindowType::CANCELBUTTON )
+ {
+ nFormStart = nIndex-1;
+ }
+ for( sal_uInt16 nSearchIndex = nIndex-1; nSearchIndex >= nFormStart; nSearchIndex-- )
+ {
+ sal_uInt16 nFoundIndex = 0;
+ pSWindow = ::ImplGetChildWindow( pFrameWindow,
+ nSearchIndex,
+ nFoundIndex,
+ false );
+ if( pSWindow && isVisibleInLayout(pSWindow) && !(pSWindow->GetStyle() & WB_NOLABEL) )
+ {
+ WindowType nType = pSWindow->GetType();
+ if ( nType == WindowType::FIXEDTEXT ||
+ nType == WindowType::FIXEDLINE ||
+ nType == WindowType::GROUPBOX )
+ {
+ // a fixed text can't be labelled by a fixed text.
+ if ( ( nMyType != WindowType::FIXEDTEXT ) || ( nType != WindowType::FIXEDTEXT ) )
+ pWindow = pSWindow;
+ break;
+ }
+ }
+ if( nFoundIndex > nSearchIndex || nSearchIndex == 0 )
+ break;
+ }
+ }
+ }
+ return pWindow;
+}
+
+Window* Window::getLegacyNonLayoutAccessibleRelationLabeledBy() const
+{
+ Window* pFrameWindow = ImplGetFrameWindow();
+
+ // #i62723#, #104191# checkboxes and radiobuttons are not supposed to have labels
+ if( GetType() == WindowType::CHECKBOX || GetType() == WindowType::RADIOBUTTON )
+ return nullptr;
+
+// if( ! ( GetType() == WindowType::FIXEDTEXT ||
+// GetType() == WindowType::FIXEDLINE ||
+// GetType() == WindowType::GROUPBOX ) )
+ // #i100833# MT 2010/02: Group box and fixed lines can also label a fixed text.
+ // See tools/options/print for example.
+
+ Window* pWindow = ImplGetLabeledBy( pFrameWindow, GetType(), const_cast<Window*>(this) );
+ if( ! pWindow && mpWindowImpl->mpRealParent )
+ pWindow = ImplGetLabeledBy( mpWindowImpl->mpRealParent, GetType(), const_cast<Window*>(this) );
+
+ return pWindow;
+}
+
+Window* Window::getLegacyNonLayoutAccessibleRelationMemberOf() const
+{
+ Window* pWindow = nullptr;
+ Window* pFrameWindow = GetParent();
+ if ( !pFrameWindow )
+ {
+ pFrameWindow = ImplGetFrameWindow();
+ }
+ // if( ! ( GetType() == WindowType::FIXEDTEXT ||
+ if( !( GetType() == WindowType::FIXEDLINE ||
+ GetType() == WindowType::GROUPBOX ) )
+ {
+ // search for a control that makes member of this window
+ // it is considered the last fixed line or group box
+ // that comes before this control; with the exception of push buttons
+ // which are labeled only if the fixed line or group box
+ // is directly before the control
+ // get form start and form end and index of this control
+ sal_uInt16 nIndex, nFormStart, nFormEnd;
+ Window* pSWindow = ::ImplFindDlgCtrlWindow( pFrameWindow,
+ const_cast<Window*>(this),
+ nIndex,
+ nFormStart,
+ nFormEnd );
+ if( pSWindow && nIndex != nFormStart )
+ {
+ if( GetType() == WindowType::PUSHBUTTON ||
+ GetType() == WindowType::HELPBUTTON ||
+ GetType() == WindowType::OKBUTTON ||
+ GetType() == WindowType::CANCELBUTTON )
+ {
+ nFormStart = nIndex-1;
+ }
+ for( sal_uInt16 nSearchIndex = nIndex-1; nSearchIndex >= nFormStart; nSearchIndex-- )
+ {
+ sal_uInt16 nFoundIndex = 0;
+ pSWindow = ::ImplGetChildWindow( pFrameWindow,
+ nSearchIndex,
+ nFoundIndex,
+ false );
+ if( pSWindow && pSWindow->IsVisible() &&
+ ( pSWindow->GetType() == WindowType::FIXEDLINE ||
+ pSWindow->GetType() == WindowType::GROUPBOX ) )
+ {
+ pWindow = pSWindow;
+ break;
+ }
+ if( nFoundIndex > nSearchIndex || nSearchIndex == 0 )
+ break;
+ }
+ }
+ }
+ return pWindow;
+}
+
+} /* namespace vcl */
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/menu.cxx b/vcl/source/window/menu.cxx
new file mode 100644
index 000000000..bcfbdbb37
--- /dev/null
+++ b/vcl/source/window/menu.cxx
@@ -0,0 +1,3100 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <tools/diagnose_ex.h>
+#include <sal/log.hxx>
+
+#include <comphelper/lok.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/mnemonic.hxx>
+#include <vcl/image.hxx>
+#include <vcl/event.hxx>
+#include <vcl/help.hxx>
+#include <vcl/floatwin.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/taskpanelist.hxx>
+#include <vcl/toolkit/controllayout.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/commandinfoprovider.hxx>
+
+#include <salinst.hxx>
+#include <svdata.hxx>
+#include <strings.hrc>
+#include <window.h>
+#include <salmenu.hxx>
+#include <salframe.hxx>
+
+#include "menubarwindow.hxx"
+#include "menufloatingwindow.hxx"
+#include "menuitemlist.hxx"
+
+#include <com/sun/star/uno/Reference.h>
+#include <com/sun/star/lang/XComponent.hpp>
+#include <com/sun/star/accessibility/XAccessible.hpp>
+#include <vcl/toolkit/unowrap.hxx>
+#include <rtl/ustrbuf.hxx>
+
+#include <configsettings.hxx>
+
+#include <map>
+#include <string_view>
+#include <vector>
+
+namespace vcl
+{
+
+struct MenuLayoutData : public ControlLayoutData
+{
+ std::vector< sal_uInt16 > m_aLineItemIds;
+ std::map< sal_uInt16, tools::Rectangle > m_aVisibleItemBoundRects;
+};
+
+}
+
+using namespace vcl;
+
+#define EXTRAITEMHEIGHT 4
+#define SPACE_AROUND_TITLE 4
+
+static bool ImplAccelDisabled()
+{
+ // display of accelerator strings may be suppressed via configuration
+ static int nAccelDisabled = -1;
+
+ if( nAccelDisabled == -1 )
+ {
+ OUString aStr =
+ vcl::SettingsConfigItem::get()->
+ getValue( "Menu", "SuppressAccelerators" );
+ nAccelDisabled = aStr.equalsIgnoreAsciiCase("true") ? 1 : 0;
+ }
+ return nAccelDisabled == 1;
+}
+
+static void ImplSetMenuItemData( MenuItemData* pData )
+{
+ // convert data
+ if ( !pData->aImage )
+ pData->eType = MenuItemType::STRING;
+ else if ( pData->aText.isEmpty() )
+ pData->eType = MenuItemType::IMAGE;
+ else
+ pData->eType = MenuItemType::STRINGIMAGE;
+}
+
+namespace {
+
+void ImplClosePopupToolBox( const VclPtr<vcl::Window>& pWin )
+{
+ if ( pWin->GetType() == WindowType::TOOLBOX && ImplGetDockingManager()->IsInPopupMode( pWin ) )
+ {
+ ImplDockingWindowWrapper* pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( pWin );
+ if ( pWrapper && pWrapper->GetFloatingWindow() )
+ pWrapper->GetFloatingWindow()->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;
+
+ long width = 8 * fScaleFactor;
+ long height = 5 * fScaleFactor;
+
+ //Keep odd b/c drawing code works better
+ if ( height % 2 == 0 )
+ height--;
+
+ long heightOrig = height;
+
+ long x = rRect.Left() + (rRect.getWidth() - width)/2 + 1;
+ long y = rRect.Top() + (rRect.getHeight() - height)/2 + 1;
+ while( height >= 1)
+ {
+ rRenderContext.DrawRect( tools::Rectangle( x, y, x + linewidth, y ) );
+ x += space;
+ rRenderContext.DrawRect( tools::Rectangle( x, y, x + linewidth, y ) );
+ x -= space;
+ y++;
+ if( height <= heightOrig / 2 + 1) x--;
+ else x++;
+ height--;
+ }
+ rRenderContext.Pop();
+}
+
+} // end anonymous namespace
+
+
+Menu::Menu()
+ : mpFirstDel(nullptr),
+ pItemList(new MenuItemList),
+ pStartedFrom(nullptr),
+ pWindow(nullptr),
+ nTitleHeight(0),
+ nEventId(nullptr),
+ mnHighlightedItemPos(ITEMPOS_INVALID),
+ nMenuFlags(MenuFlags::NONE),
+ nSelectedId(0),
+ nImgOrChkPos(0),
+ nTextPos(0),
+ bCanceled(false),
+ bInCallback(false),
+ bKilled(false)
+{
+}
+
+Menu::~Menu()
+{
+ disposeOnce();
+}
+
+void Menu::dispose()
+{
+ ImplCallEventListeners( VclEventId::ObjectDying, ITEMPOS_INVALID );
+
+ // at the window free the reference to the accessible component
+ // and make sure the MenuFloatingWindow knows about our destruction
+ if ( pWindow )
+ {
+ MenuFloatingWindow* pFloat = static_cast<MenuFloatingWindow*>(pWindow.get());
+ if( pFloat->pMenu.get() == this )
+ pFloat->pMenu.clear();
+ pWindow->SetAccessible( css::uno::Reference< css::accessibility::XAccessible >() );
+ }
+
+ // dispose accessible components
+ if ( mxAccessible.is() )
+ {
+ css::uno::Reference< css::lang::XComponent> xComponent( mxAccessible, css::uno::UNO_QUERY );
+ if ( xComponent.is() )
+ xComponent->dispose();
+ }
+
+ if ( nEventId )
+ Application::RemoveUserEvent( nEventId );
+
+ // Notify deletion of this menu
+ ImplMenuDelData* pDelData = mpFirstDel;
+ while ( pDelData )
+ {
+ pDelData->mpMenu = nullptr;
+ pDelData = pDelData->mpNext;
+ }
+
+ bKilled = true;
+
+ pItemList->Clear();
+ mpLayoutData.reset();
+
+ // Native-support: destroy SalMenu
+ mpSalMenu.reset();
+
+ pStartedFrom.clear();
+ pWindow.clear();
+ VclReferenceBase::dispose();
+}
+
+void Menu::CreateAutoMnemonics()
+{
+ MnemonicGenerator aMnemonicGenerator;
+ size_t n;
+ for ( n = 0; n < pItemList->size(); n++ )
+ {
+ MenuItemData* pData = pItemList->GetDataFromPos( n );
+ if ( ! (pData->nBits & MenuItemBits::NOSELECT ) )
+ aMnemonicGenerator.RegisterMnemonic( pData->aText );
+ }
+ for ( n = 0; n < pItemList->size(); n++ )
+ {
+ MenuItemData* pData = pItemList->GetDataFromPos( n );
+ if ( ! (pData->nBits & MenuItemBits::NOSELECT ) )
+ pData->aText = aMnemonicGenerator.CreateMnemonic( pData->aText );
+ }
+}
+
+void Menu::Activate()
+{
+ bInCallback = true;
+
+ ImplMenuDelData aDelData( this );
+
+ ImplCallEventListeners( VclEventId::MenuActivate, ITEMPOS_INVALID );
+
+ if( !aDelData.isDeleted() )
+ {
+ if ( !aActivateHdl.Call( this ) )
+ {
+ if( !aDelData.isDeleted() )
+ {
+ Menu* pStartMenu = ImplGetStartMenu();
+ if ( pStartMenu && ( pStartMenu != this ) )
+ {
+ pStartMenu->bInCallback = true;
+ // MT 11/01: Call EventListener here? I don't know...
+ pStartMenu->aActivateHdl.Call( this );
+ pStartMenu->bInCallback = false;
+ }
+ }
+ }
+ bInCallback = false;
+ }
+
+ if (!aDelData.isDeleted() && !(nMenuFlags & MenuFlags::NoAutoMnemonics))
+ CreateAutoMnemonics();
+}
+
+void Menu::Deactivate()
+{
+ for ( size_t n = pItemList->size(); n; )
+ {
+ MenuItemData* pData = pItemList->GetDataFromPos( --n );
+ if ( pData->bIsTemporary )
+ {
+ if ( ImplGetSalMenu() )
+ ImplGetSalMenu()->RemoveItem( n );
+
+ pItemList->Remove( n );
+ }
+ }
+
+ bInCallback = true;
+
+ ImplMenuDelData aDelData( this );
+
+ Menu* pStartMenu = ImplGetStartMenu();
+ ImplCallEventListeners( VclEventId::MenuDeactivate, ITEMPOS_INVALID );
+
+ if( !aDelData.isDeleted() )
+ {
+ if ( !aDeactivateHdl.Call( this ) )
+ {
+ if( !aDelData.isDeleted() )
+ {
+ if ( pStartMenu && ( pStartMenu != this ) )
+ {
+ pStartMenu->bInCallback = true;
+ pStartMenu->aDeactivateHdl.Call( this );
+ pStartMenu->bInCallback = false;
+ }
+ }
+ }
+ }
+
+ if( !aDelData.isDeleted() )
+ {
+ bInCallback = false;
+ }
+}
+
+void Menu::ImplSelect()
+{
+ MenuItemData* pData = GetItemList()->GetData( nSelectedId );
+ if ( pData && (pData->nBits & MenuItemBits::AUTOCHECK) )
+ {
+ bool bChecked = IsItemChecked( nSelectedId );
+ if ( pData->nBits & MenuItemBits::RADIOCHECK )
+ {
+ if ( !bChecked )
+ CheckItem( nSelectedId );
+ }
+ else
+ CheckItem( nSelectedId, !bChecked );
+ }
+
+ // call select
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maAppData.mpActivePopupMenu = nullptr; // if new execute in select()
+ nEventId = Application::PostUserEvent( LINK( this, Menu, ImplCallSelect ) );
+}
+
+void Menu::Select()
+{
+ ImplMenuDelData aDelData( this );
+
+ ImplCallEventListeners( VclEventId::MenuSelect, GetItemPos( GetCurItemId() ) );
+ if (aDelData.isDeleted())
+ return;
+ if (aSelectHdl.Call(this))
+ return;
+ if (aDelData.isDeleted())
+ return;
+ Menu* pStartMenu = ImplGetStartMenu();
+ if (!pStartMenu || (pStartMenu == this))
+ return;
+ pStartMenu->nSelectedId = nSelectedId;
+ pStartMenu->sSelectedIdent = sSelectedIdent;
+ pStartMenu->aSelectHdl.Call( this );
+}
+
+#if defined(MACOSX)
+void Menu::ImplSelectWithStart( Menu* pSMenu )
+{
+ auto pOldStartedFrom = pStartedFrom;
+ pStartedFrom = pSMenu;
+ auto pOldStartedStarted = pOldStartedFrom ? pOldStartedFrom->pStartedFrom : VclPtr<Menu>();
+ Select();
+ if( pOldStartedFrom )
+ pOldStartedFrom->pStartedFrom = pOldStartedStarted;
+ pStartedFrom = pOldStartedFrom;
+}
+#endif
+
+void Menu::ImplCallEventListeners( VclEventId nEvent, sal_uInt16 nPos )
+{
+ ImplMenuDelData aDelData( this );
+
+ VclMenuEvent aEvent( this, nEvent, nPos );
+
+ // This is needed by atk accessibility bridge
+ if ( nEvent == VclEventId::MenuHighlight )
+ {
+ Application::ImplCallEventListeners( aEvent );
+ }
+
+ if ( !aDelData.isDeleted() )
+ {
+ // Copy the list, because this can be destroyed when calling a Link...
+ std::list<Link<VclMenuEvent&,void>> aCopy( maEventListeners );
+ for ( const auto& rLink : aCopy )
+ {
+ if( std::find(maEventListeners.begin(), maEventListeners.end(), rLink) != maEventListeners.end() )
+ rLink.Call( aEvent );
+ }
+ }
+}
+
+void Menu::AddEventListener( const Link<VclMenuEvent&,void>& rEventListener )
+{
+ maEventListeners.push_back( rEventListener );
+}
+
+void Menu::RemoveEventListener( const Link<VclMenuEvent&,void>& rEventListener )
+{
+ maEventListeners.remove( rEventListener );
+}
+
+MenuItemData* Menu::NbcInsertItem(sal_uInt16 nId, MenuItemBits nBits,
+ const OUString& rStr, Menu* pMenu,
+ size_t nPos, const OString &rIdent)
+{
+ // put Item in MenuItemList
+ MenuItemData* pData = pItemList->Insert(nId, MenuItemType::STRING,
+ nBits, rStr, pMenu, nPos, rIdent);
+
+ // update native menu
+ if (ImplGetSalMenu() && pData->pSalMenuItem)
+ ImplGetSalMenu()->InsertItem(pData->pSalMenuItem.get(), nPos);
+
+ return pData;
+}
+
+void Menu::InsertItem(sal_uInt16 nItemId, const OUString& rStr, MenuItemBits nItemBits,
+ const OString &rIdent, sal_uInt16 nPos)
+{
+ SAL_WARN_IF( !nItemId, "vcl", "Menu::InsertItem(): ItemId == 0" );
+ SAL_WARN_IF( GetItemPos( nItemId ) != MENU_ITEM_NOTFOUND, "vcl",
+ "Menu::InsertItem(): ItemId already exists" );
+
+ // if Position > ItemCount, append
+ if ( nPos >= pItemList->size() )
+ nPos = MENU_APPEND;
+
+ // put Item in MenuItemList
+ NbcInsertItem(nItemId, nItemBits, rStr, this, nPos, rIdent);
+
+ vcl::Window* pWin = ImplGetWindow();
+ mpLayoutData.reset();
+ if ( pWin )
+ {
+ ImplCalcSize( pWin );
+ if ( pWin->IsVisible() )
+ pWin->Invalidate();
+ }
+ ImplCallEventListeners( VclEventId::MenuInsertItem, nPos );
+}
+
+void Menu::InsertItem(sal_uInt16 nItemId, const Image& rImage,
+ MenuItemBits nItemBits, const OString &rIdent, sal_uInt16 nPos)
+{
+ InsertItem(nItemId, OUString(), nItemBits, rIdent, nPos);
+ SetItemImage( nItemId, rImage );
+}
+
+void Menu::InsertItem(sal_uInt16 nItemId, const OUString& rStr,
+ const Image& rImage, MenuItemBits nItemBits,
+ const OString &rIdent, sal_uInt16 nPos)
+{
+ InsertItem(nItemId, rStr, nItemBits, rIdent, nPos);
+ SetItemImage( nItemId, rImage );
+}
+
+void Menu::InsertItem(const OUString& rCommand, const css::uno::Reference<css::frame::XFrame>& rFrame)
+{
+ sal_uInt16 nItemId = GetItemCount() + 1;
+
+ if (rFrame.is())
+ {
+ OUString aModuleName(vcl::CommandInfoProvider::GetModuleIdentifier(rFrame));
+ auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(rCommand, aModuleName);
+ OUString aLabel(CommandInfoProvider::GetPopupLabelForCommand(aProperties));
+ OUString aTooltip(CommandInfoProvider::GetTooltipForCommand(rCommand, aProperties, rFrame));
+ Image aImage(CommandInfoProvider::GetImageForCommand(rCommand, rFrame));
+
+ InsertItem(nItemId, aLabel, aImage);
+ SetHelpText(nItemId, aTooltip);
+ }
+ else
+ InsertItem(nItemId, OUString());
+
+ SetItemCommand(nItemId, rCommand);
+}
+
+
+void Menu::InsertSeparator(const OString &rIdent, sal_uInt16 nPos)
+{
+ // do nothing if it's a menu bar
+ if (IsMenuBar())
+ return;
+
+ // if position > ItemCount, append
+ if ( nPos >= pItemList->size() )
+ nPos = MENU_APPEND;
+
+ // put separator in item list
+ pItemList->InsertSeparator(rIdent, nPos);
+
+ // update native menu
+ size_t itemPos = ( nPos != MENU_APPEND ) ? nPos : pItemList->size() - 1;
+ MenuItemData *pData = pItemList->GetDataFromPos( itemPos );
+ if( ImplGetSalMenu() && pData && pData->pSalMenuItem )
+ ImplGetSalMenu()->InsertItem( pData->pSalMenuItem.get(), nPos );
+
+ mpLayoutData.reset();
+
+ ImplCallEventListeners( VclEventId::MenuInsertItem, nPos );
+}
+
+void Menu::RemoveItem( sal_uInt16 nPos )
+{
+ bool bRemove = false;
+
+ if ( nPos < GetItemCount() )
+ {
+ // update native menu
+ if( ImplGetSalMenu() )
+ ImplGetSalMenu()->RemoveItem( nPos );
+
+ pItemList->Remove( nPos );
+ bRemove = true;
+ }
+
+ vcl::Window* pWin = ImplGetWindow();
+ if ( pWin )
+ {
+ ImplCalcSize( pWin );
+ if ( pWin->IsVisible() )
+ pWin->Invalidate();
+ }
+ mpLayoutData.reset();
+
+ if ( bRemove )
+ ImplCallEventListeners( VclEventId::MenuRemoveItem, nPos );
+}
+
+static void ImplCopyItem( Menu* pThis, const Menu& rMenu, sal_uInt16 nPos, sal_uInt16 nNewPos )
+{
+ MenuItemType eType = rMenu.GetItemType( nPos );
+
+ if ( eType == MenuItemType::DONTKNOW )
+ return;
+
+ if ( eType == MenuItemType::SEPARATOR )
+ pThis->InsertSeparator( OString(), nNewPos );
+ else
+ {
+ sal_uInt16 nId = rMenu.GetItemId( nPos );
+
+ SAL_WARN_IF( pThis->GetItemPos( nId ) != MENU_ITEM_NOTFOUND, "vcl",
+ "Menu::CopyItem(): ItemId already exists" );
+
+ MenuItemData* pData = rMenu.GetItemList()->GetData( nId );
+
+ if (!pData)
+ return;
+
+ if ( eType == MenuItemType::STRINGIMAGE )
+ pThis->InsertItem( nId, pData->aText, pData->aImage, pData->nBits, pData->sIdent, nNewPos );
+ else if ( eType == MenuItemType::STRING )
+ pThis->InsertItem( nId, pData->aText, pData->nBits, pData->sIdent, nNewPos );
+ else
+ pThis->InsertItem( nId, pData->aImage, pData->nBits, pData->sIdent, nNewPos );
+
+ if ( rMenu.IsItemChecked( nId ) )
+ pThis->CheckItem( nId );
+ if ( !rMenu.IsItemEnabled( nId ) )
+ pThis->EnableItem( nId, false );
+ pThis->SetHelpId( nId, pData->aHelpId );
+ pThis->SetHelpText( nId, pData->aHelpText );
+ pThis->SetAccelKey( nId, pData->aAccelKey );
+ pThis->SetItemCommand( nId, pData->aCommandStr );
+ pThis->SetHelpCommand( nId, pData->aHelpCommandStr );
+
+ PopupMenu* pSubMenu = rMenu.GetPopupMenu( nId );
+ if ( pSubMenu )
+ {
+ // create auto-copy
+ VclPtr<PopupMenu> pNewMenu = VclPtr<PopupMenu>::Create( *pSubMenu );
+ pThis->SetPopupMenu( nId, pNewMenu );
+ }
+ }
+}
+
+void Menu::Clear()
+{
+ for ( sal_uInt16 i = GetItemCount(); i; i-- )
+ RemoveItem( 0 );
+}
+
+sal_uInt16 Menu::GetItemCount() const
+{
+ return static_cast<sal_uInt16>(pItemList->size());
+}
+
+sal_uInt16 Menu::ImplGetVisibleItemCount() const
+{
+ sal_uInt16 nItems = 0;
+ for ( size_t n = pItemList->size(); n; )
+ {
+ if ( ImplIsVisible( --n ) )
+ nItems++;
+ }
+ return nItems;
+}
+
+sal_uInt16 Menu::ImplGetFirstVisible() const
+{
+ for ( size_t n = 0; n < pItemList->size(); n++ )
+ {
+ if ( ImplIsVisible( n ) )
+ return n;
+ }
+ return ITEMPOS_INVALID;
+}
+
+sal_uInt16 Menu::ImplGetPrevVisible( sal_uInt16 nPos ) const
+{
+ for ( size_t n = nPos; n; )
+ {
+ if (ImplIsVisible(--n))
+ return n;
+ }
+ return ITEMPOS_INVALID;
+}
+
+sal_uInt16 Menu::ImplGetNextVisible( sal_uInt16 nPos ) const
+{
+ for ( size_t n = nPos+1; n < pItemList->size(); n++ )
+ {
+ if ( ImplIsVisible( n ) )
+ return n;
+ }
+ return ITEMPOS_INVALID;
+}
+
+sal_uInt16 Menu::GetItemId(sal_uInt16 nPos) const
+{
+ MenuItemData* pData = pItemList->GetDataFromPos( nPos );
+
+ if ( pData )
+ return pData->nId;
+ else
+ return 0;
+}
+
+sal_uInt16 Menu::GetItemId(const OString &rIdent) const
+{
+ for (size_t n = 0; n < pItemList->size(); ++n)
+ {
+ MenuItemData* pData = pItemList->GetDataFromPos(n);
+ if (pData && pData->sIdent == rIdent)
+ return pData->nId;
+ }
+ return MENU_ITEM_NOTFOUND;
+}
+
+sal_uInt16 Menu::GetItemPos( sal_uInt16 nItemId ) const
+{
+ size_t nPos;
+ MenuItemData* pData = pItemList->GetData( nItemId, nPos );
+
+ if ( pData )
+ return static_cast<sal_uInt16>(nPos);
+ else
+ return MENU_ITEM_NOTFOUND;
+}
+
+MenuItemType Menu::GetItemType( sal_uInt16 nPos ) const
+{
+ MenuItemData* pData = pItemList->GetDataFromPos( nPos );
+
+ if ( pData )
+ return pData->eType;
+ else
+ return MenuItemType::DONTKNOW;
+}
+
+OString Menu::GetItemIdent(sal_uInt16 nId) const
+{
+ const MenuItemData* pData = pItemList->GetData(nId);
+ return pData ? pData->sIdent : OString();
+}
+
+void Menu::SetItemBits( sal_uInt16 nItemId, MenuItemBits nBits )
+{
+ size_t nPos;
+ MenuItemData* pData = pItemList->GetData(nItemId, nPos);
+
+ if (pData && (pData->nBits != nBits))
+ {
+ pData->nBits = nBits;
+
+ // update native menu
+ if (ImplGetSalMenu())
+ ImplGetSalMenu()->SetItemBits(nPos, nBits);
+ }
+}
+
+MenuItemBits Menu::GetItemBits( sal_uInt16 nItemId ) const
+{
+ MenuItemBits nBits = MenuItemBits::NONE;
+ MenuItemData* pData = pItemList->GetData( nItemId );
+ if ( pData )
+ nBits = pData->nBits;
+ return nBits;
+}
+
+void Menu::SetUserValue(sal_uInt16 nItemId, void* nUserValue, MenuUserDataReleaseFunction aFunc)
+{
+ MenuItemData* pData = pItemList->GetData(nItemId);
+ if (pData)
+ {
+ if (pData->aUserValueReleaseFunc)
+ pData->aUserValueReleaseFunc(pData->nUserValue);
+ pData->aUserValueReleaseFunc = aFunc;
+ pData->nUserValue = nUserValue;
+ }
+}
+
+void* Menu::GetUserValue( sal_uInt16 nItemId ) const
+{
+ MenuItemData* pData = pItemList->GetData( nItemId );
+ return pData ? pData->nUserValue : nullptr;
+}
+
+void Menu::SetPopupMenu( sal_uInt16 nItemId, PopupMenu* pMenu )
+{
+ size_t nPos;
+ MenuItemData* pData = pItemList->GetData( nItemId, nPos );
+
+ // Item does not exist -> return NULL
+ if ( !pData )
+ return;
+
+ // same menu, nothing to do
+ if ( static_cast<PopupMenu*>(pData->pSubMenu.get()) == pMenu )
+ return;
+
+ // remove old menu
+ auto oldSubMenu = pData->pSubMenu;
+
+ // data exchange
+ pData->pSubMenu = pMenu;
+
+ // #112023# Make sure pStartedFrom does not point to invalid (old) data
+ if ( pData->pSubMenu )
+ pData->pSubMenu->pStartedFrom = nullptr;
+
+ // set native submenu
+ if( ImplGetSalMenu() && pData->pSalMenuItem )
+ {
+ if( pMenu )
+ ImplGetSalMenu()->SetSubMenu( pData->pSalMenuItem.get(), pMenu->ImplGetSalMenu(), nPos );
+ else
+ ImplGetSalMenu()->SetSubMenu( pData->pSalMenuItem.get(), nullptr, nPos );
+ }
+
+ oldSubMenu.disposeAndClear();
+
+ ImplCallEventListeners( VclEventId::MenuSubmenuChanged, nPos );
+}
+
+PopupMenu* Menu::GetPopupMenu( sal_uInt16 nItemId ) const
+{
+ MenuItemData* pData = pItemList->GetData( nItemId );
+
+ if ( pData )
+ return static_cast<PopupMenu*>(pData->pSubMenu.get());
+ else
+ return nullptr;
+}
+
+void Menu::SetAccelKey( sal_uInt16 nItemId, const KeyCode& rKeyCode )
+{
+ size_t nPos;
+ MenuItemData* pData = pItemList->GetData( nItemId, nPos );
+
+ if ( !pData )
+ return;
+
+ if ( pData->aAccelKey == rKeyCode )
+ return;
+
+ pData->aAccelKey = rKeyCode;
+
+ // update native menu
+ if( ImplGetSalMenu() && pData->pSalMenuItem )
+ ImplGetSalMenu()->SetAccelerator( nPos, pData->pSalMenuItem.get(), rKeyCode, rKeyCode.GetName() );
+}
+
+KeyCode Menu::GetAccelKey( sal_uInt16 nItemId ) const
+{
+ MenuItemData* pData = pItemList->GetData( nItemId );
+
+ if ( pData )
+ return pData->aAccelKey;
+ else
+ return KeyCode();
+}
+
+KeyEvent Menu::GetActivationKey( sal_uInt16 nItemId ) const
+{
+ KeyEvent aRet;
+ MenuItemData* pData = pItemList->GetData( nItemId );
+ if( pData )
+ {
+ sal_Int32 nPos = pData->aText.indexOf( '~' );
+ if( nPos != -1 && nPos < pData->aText.getLength()-1 )
+ {
+ sal_uInt16 nCode = 0;
+ sal_Unicode cAccel = pData->aText[nPos+1];
+ if( cAccel >= 'a' && cAccel <= 'z' )
+ nCode = KEY_A + (cAccel-'a');
+ else if( cAccel >= 'A' && cAccel <= 'Z' )
+ nCode = KEY_A + (cAccel-'A');
+ else if( cAccel >= '0' && cAccel <= '9' )
+ nCode = KEY_0 + (cAccel-'0');
+
+ aRet = KeyEvent( cAccel, KeyCode( nCode, KEY_MOD2 ) );
+ }
+
+ }
+ return aRet;
+}
+
+void Menu::CheckItem( sal_uInt16 nItemId, bool bCheck )
+{
+ size_t nPos;
+ MenuItemData* pData = pItemList->GetData( nItemId, nPos );
+
+ if ( !pData || pData->bChecked == bCheck )
+ return;
+
+ // if radio-check, then uncheck previous
+ if ( bCheck && (pData->nBits & MenuItemBits::AUTOCHECK) &&
+ (pData->nBits & MenuItemBits::RADIOCHECK) )
+ {
+ MenuItemData* pGroupData;
+ sal_uInt16 nGroupPos;
+ sal_uInt16 nItemCount = GetItemCount();
+ bool bFound = false;
+
+ nGroupPos = nPos;
+ while ( nGroupPos )
+ {
+ pGroupData = pItemList->GetDataFromPos( nGroupPos-1 );
+ if ( pGroupData->nBits & MenuItemBits::RADIOCHECK )
+ {
+ if ( IsItemChecked( pGroupData->nId ) )
+ {
+ CheckItem( pGroupData->nId, false );
+ bFound = true;
+ break;
+ }
+ }
+ else
+ break;
+ nGroupPos--;
+ }
+
+ if ( !bFound )
+ {
+ nGroupPos = nPos+1;
+ while ( nGroupPos < nItemCount )
+ {
+ pGroupData = pItemList->GetDataFromPos( nGroupPos );
+ if ( pGroupData->nBits & MenuItemBits::RADIOCHECK )
+ {
+ if ( IsItemChecked( pGroupData->nId ) )
+ {
+ CheckItem( pGroupData->nId, false );
+ break;
+ }
+ }
+ else
+ break;
+ nGroupPos++;
+ }
+ }
+ }
+
+ pData->bChecked = bCheck;
+
+ // update native menu
+ if( ImplGetSalMenu() )
+ ImplGetSalMenu()->CheckItem( nPos, bCheck );
+
+ ImplCallEventListeners( bCheck ? VclEventId::MenuItemChecked : VclEventId::MenuItemUnchecked, nPos );
+}
+
+void Menu::CheckItem( const OString &rIdent , bool bCheck )
+{
+ CheckItem( GetItemId( rIdent ), bCheck );
+}
+
+bool Menu::IsItemChecked( sal_uInt16 nItemId ) const
+{
+ size_t nPos;
+ MenuItemData* pData = pItemList->GetData( nItemId, nPos );
+
+ if ( !pData )
+ return false;
+
+ return pData->bChecked;
+}
+
+void Menu::EnableItem( sal_uInt16 nItemId, bool bEnable )
+{
+ size_t nPos;
+ MenuItemData* pItemData = pItemList->GetData( nItemId, nPos );
+
+ if ( pItemData && ( pItemData->bEnabled != bEnable ) )
+ {
+ pItemData->bEnabled = bEnable;
+
+ vcl::Window* pWin = ImplGetWindow();
+ if ( pWin && pWin->IsVisible() )
+ {
+ SAL_WARN_IF(!IsMenuBar(), "vcl", "Menu::EnableItem - Popup visible!" );
+ 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))
+ {
+ 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 )
+ {
+ 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 && pData->aHelpText.isEmpty() &&
+ (( !pData->aHelpId.isEmpty() ) || ( !pData->aCommandStr.isEmpty() )))
+ {
+ Help* pHelp = Application::GetHelp();
+ if ( pHelp )
+ {
+ if (!pData->aCommandStr.isEmpty())
+ pData->aHelpText = pHelp->GetHelpText( pData->aCommandStr, static_cast<weld::Widget*>(nullptr) );
+ if (pData->aHelpText.isEmpty() && !pData->aHelpId.isEmpty())
+ pData->aHelpText = pHelp->GetHelpText( OStringToOUString( pData->aHelpId, RTL_TEXTENCODING_UTF8 ), static_cast<weld::Widget*>(nullptr) );
+ }
+ }
+
+ return OUString();
+}
+
+OUString Menu::GetHelpText( sal_uInt16 nItemId ) const
+{
+ return ImplGetHelpText( nItemId );
+}
+
+void Menu::SetTipHelpText( sal_uInt16 nItemId, const OUString& rStr )
+{
+ MenuItemData* pData = pItemList->GetData( nItemId );
+
+ if ( pData )
+ pData->aTipHelpText = rStr;
+}
+
+OUString Menu::GetTipHelpText( sal_uInt16 nItemId ) const
+{
+ MenuItemData* pData = pItemList->GetData( nItemId );
+
+ if ( pData )
+ return pData->aTipHelpText;
+
+ return OUString();
+}
+
+void Menu::SetHelpId( sal_uInt16 nItemId, const OString& rHelpId )
+{
+ MenuItemData* pData = pItemList->GetData( nItemId );
+
+ if ( pData )
+ pData->aHelpId = rHelpId;
+}
+
+OString Menu::GetHelpId( sal_uInt16 nItemId ) const
+{
+ OString aRet;
+
+ MenuItemData* pData = pItemList->GetData( nItemId );
+
+ if ( pData )
+ {
+ if ( !pData->aHelpId.isEmpty() )
+ aRet = pData->aHelpId;
+ else
+ aRet = OUStringToOString( pData->aCommandStr, RTL_TEXTENCODING_UTF8 );
+ }
+
+ return aRet;
+}
+
+Menu& Menu::operator=( const Menu& rMenu )
+{
+ if(this == &rMenu)
+ return *this;
+
+ // clean up
+ Clear();
+
+ // copy items
+ sal_uInt16 nCount = rMenu.GetItemCount();
+ for ( sal_uInt16 i = 0; i < nCount; i++ )
+ ImplCopyItem( this, rMenu, i, MENU_APPEND );
+
+ aActivateHdl = rMenu.aActivateHdl;
+ aDeactivateHdl = rMenu.aDeactivateHdl;
+ aSelectHdl = rMenu.aSelectHdl;
+ aTitleText = rMenu.aTitleText;
+ nTitleHeight = rMenu.nTitleHeight;
+
+ return *this;
+}
+
+// Returns true if the item is completely hidden on the GUI and shouldn't
+// be possible to interact with
+bool Menu::ImplCurrentlyHiddenOnGUI(sal_uInt16 nPos) const
+{
+ MenuItemData* pData = pItemList->GetDataFromPos(nPos);
+ if (pData)
+ {
+ MenuItemData* pPreviousData = pItemList->GetDataFromPos( nPos - 1 );
+ if (pPreviousData && pPreviousData->bHiddenOnGUI)
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool Menu::ImplIsVisible( sal_uInt16 nPos ) const
+{
+ bool bVisible = true;
+
+ MenuItemData* pData = pItemList->GetDataFromPos( nPos );
+ // check general visibility first
+ if( pData && !pData->bVisible )
+ bVisible = false;
+
+ if ( bVisible && pData && pData->eType == MenuItemType::SEPARATOR )
+ {
+ if( nPos == 0 ) // no separator should be shown at the very beginning
+ bVisible = false;
+ else
+ {
+ // always avoid adjacent separators
+ size_t nCount = pItemList->size();
+ size_t n;
+ MenuItemData* pNextData = nullptr;
+ // search next visible item
+ for( n = nPos + 1; n < nCount; n++ )
+ {
+ pNextData = pItemList->GetDataFromPos( n );
+ if( pNextData && pNextData->bVisible )
+ {
+ if( pNextData->eType == MenuItemType::SEPARATOR || ImplIsVisible(n) )
+ break;
+ }
+ }
+ if( n == nCount ) // no next visible item
+ bVisible = false;
+ // check for separator
+ if( pNextData && pNextData->bVisible && pNextData->eType == MenuItemType::SEPARATOR )
+ bVisible = false;
+
+ if( bVisible )
+ {
+ for( n = nPos; n > 0; n-- )
+ {
+ pNextData = pItemList->GetDataFromPos( n-1 );
+ if( pNextData && pNextData->bVisible )
+ {
+ if( pNextData->eType != MenuItemType::SEPARATOR && ImplIsVisible(n-1) )
+ break;
+ }
+ }
+ if( n == 0 ) // no previous visible item
+ bVisible = false;
+ }
+ }
+ }
+
+ // not allowed for menubar, as I do not know
+ // whether a menu-entry will disappear or will appear
+ if (bVisible && !IsMenuBar() && (nMenuFlags & MenuFlags::HideDisabledEntries) &&
+ !(nMenuFlags & MenuFlags::AlwaysShowDisabledEntries))
+ {
+ if( !pData ) // e.g. nPos == ITEMPOS_INVALID
+ bVisible = false;
+ else if ( pData->eType != MenuItemType::SEPARATOR ) // separators handled above
+ {
+ // tdf#86850 Always display clipboard functions
+ if ( pData->aCommandStr == ".uno:Cut" || pData->aCommandStr == ".uno:Copy" || pData->aCommandStr == ".uno:Paste" )
+ 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 )
+ {
+ 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, long& rCheckHeight, long& rRadioHeight ) const
+{
+ 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();
+ nCheckWidth = aNativeContent.GetWidth();
+ }
+ }
+ if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::MenuItemRadioMark))
+ {
+ if (rRenderContext.GetNativeControlRegion(ControlType::MenuPopup, ControlPart::MenuItemRadioMark,
+ aCtrlRegion, ControlState::ENABLED, aVal,
+ aNativeBounds, aNativeContent))
+ {
+ rRadioHeight = aNativeBounds.GetHeight();
+ nRadioWidth = aNativeContent.GetWidth();
+ }
+ }
+ }
+ return Size(std::max(nCheckWidth, nRadioWidth), std::max(rCheckHeight, rRadioHeight));
+}
+
+bool Menu::ImplGetNativeSubmenuArrowSize(vcl::RenderContext const & rRenderContext, Size& rArrowSize, long& rArrowSpacing)
+{
+ ImplControlValue aVal;
+ tools::Rectangle aNativeBounds;
+ tools::Rectangle aNativeContent;
+ tools::Rectangle aCtrlRegion(tools::Rectangle(Point(), Size(100, 15)));
+ if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::SubmenuArrow))
+ {
+ 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
+ long nFontHeight = pWin->GetTextHeight();
+ long nExtra = nFontHeight/4;
+
+ long nMinMenuItemHeight = nFontHeight;
+ long nCheckHeight = 0, nRadioHeight = 0;
+ Size aMaxSize = ImplGetNativeCheckAndRadioSize(*pWin, nCheckHeight, nRadioHeight); // FIXME
+ if( aMaxSize.Height() > nMinMenuItemHeight )
+ nMinMenuItemHeight = aMaxSize.Height();
+
+ Size aMaxImgSz;
+
+ 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.Height() > aMaxImgSz.Height() )
+ aMaxImgSz.setHeight( aImgSz.Height() );
+ if ( aImgSz.Height() > nMinMenuItemHeight )
+ nMinMenuItemHeight = aImgSz.Height();
+ break;
+ }
+ }
+ }
+
+ Size aSz;
+ long nCheckWidth = 0;
+ 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 ) )
+ {
+ 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)))
+ {
+ Size aImgSz = pData->aImage.GetSizePixel();
+
+ aImgSz.AdjustHeight(4 ); // add a border for native marks
+ aImgSz.AdjustWidth(4 ); // add a border for native marks
+ if ( aImgSz.Width() > aMaxImgSz.Width() )
+ aMaxImgSz.setWidth( aImgSz.Width() );
+ if ( aImgSz.Height() > aMaxImgSz.Height() )
+ aMaxImgSz.setHeight( aImgSz.Height() );
+ if ( aImgSz.Height() > pData->aSz.Height() )
+ pData->aSz.setHeight( aImgSz.Height() );
+ }
+
+ // Check Buttons:
+ if (!IsMenuBar() && pData->HasCheck())
+ {
+ nCheckWidth = aMaxSize.Width();
+ // checks / images take the same place
+ if( ! ( ( pData->eType == MenuItemType::IMAGE ) || ( pData->eType == MenuItemType::STRINGIMAGE ) ) )
+ nWidth += nCheckWidth + nExtra * 2;
+ }
+
+ // Text:
+ if ( (pData->eType == MenuItemType::STRING) || (pData->eType == MenuItemType::STRINGIMAGE) )
+ {
+ const SalLayoutGlyphs* pGlyphs = pData->GetTextGlyphs(pWin);
+ long nTextWidth = pWin->GetCtrlTextWidth(pData->aText, pGlyphs);
+ long nTextHeight = pWin->GetTextHeight();
+
+ 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();
+ 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 ) );
+ }
+
+ pData->aSz.AdjustHeight(EXTRAITEMHEIGHT ); // little bit more distance
+
+ 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->Push(PushFlags::FONT);
+ vcl::Font aFont = pWin->GetFont();
+ aFont.SetWeight(WEIGHT_BOLD);
+ pWin->SetFont(aFont);
+
+ // Compute text bounding box
+ tools::Rectangle aTextBoundRect;
+ pWin->GetTextBoundRect(aTextBoundRect, aTitleText);
+
+ // Vertically, one height of char + extra space for decoration
+ nTitleHeight = aTextBoundRect.GetSize().Height() + 4 * SPACE_AROUND_TITLE ;
+ aSz.AdjustHeight(nTitleHeight );
+
+ long nWidth = aTextBoundRect.GetSize().Width() + 4 * SPACE_AROUND_TITLE;
+ pWin->Pop();
+ if ( nWidth > nMaxWidth )
+ nMaxWidth = nWidth;
+ }
+
+ if (!IsMenuBar())
+ {
+ // popup menus should not be wider than half the screen
+ // except on rather small screens
+ // TODO: move GetScreenNumber from SystemWindow to Window ?
+ // currently we rely on internal privileges
+ unsigned int nDisplayScreen = pWin->ImplGetWindowImpl()->mpFrame->maGeometry.nDisplayScreenNumber;
+ tools::Rectangle aDispRect( Application::GetScreenPosSizePixel( nDisplayScreen ) );
+ 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, 7L )); // #107710# increase space between checkmarks/images/text
+ nImgOrChkPos = static_cast<sal_uInt16>(nExtra);
+ long nImgOrChkWidth = 0;
+ if( aMaxSize.Height() > 0 ) // NWF case
+ nImgOrChkWidth = aMaxSize.Height() + nExtra;
+ else // non NWF case
+ nImgOrChkWidth = nFontHeight/2 + gfxExtra;
+ nImgOrChkWidth = std::max( nImgOrChkWidth, aMaxImgSz.Width() + 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
+ 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);
+
+ bNativeOk = rRenderContext.DrawNativeControl(ControlType::Toolbar, ControlPart::Button,
+ i_rRect,
+ 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, long i_nMaxWidth )
+{
+ sal_Int32 nPos = -1;
+ OUString aNonMnem(OutputDevice::GetNonMnemonicString(i_rLong, nPos));
+ aNonMnem = rRenderContext.GetEllipsisString( aNonMnem, i_nMaxWidth, DrawTextFlags::CenterEllipsis);
+ // re-insert mnemonic
+ if (nPos != -1)
+ {
+ if (nPos < aNonMnem.getLength() && i_rLong[nPos+1] == aNonMnem[nPos])
+ {
+ OUStringBuffer aBuf( i_rLong.getLength() );
+ aBuf.append( std::u16string_view(aNonMnem).substr(0, nPos) );
+ aBuf.append( '~' );
+ aBuf.append( std::u16string_view(aNonMnem).substr(nPos) );
+ aNonMnem = aBuf.makeStringAndClear();
+ }
+ }
+ 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.setX(aBgRect.getX() + SPACE_AROUND_TITLE);
+ aBgRect.setWidth(aBgRect.getWidth() - 2 * SPACE_AROUND_TITLE - 2 * nOuterSpaceX);
+ aBgRect.setY(aBgRect.getY() + SPACE_AROUND_TITLE);
+ aBgRect.setHeight(nTitleHeight - 2 * SPACE_AROUND_TITLE);
+ rRenderContext.DrawRect(aBgRect);
+
+ // Draw the text centered
+ Point aTextTopLeft(aBgRect.TopLeft());
+ tools::Rectangle aTextBoundRect;
+ rRenderContext.GetTextBoundRect( aTextBoundRect, aTitleText );
+ aTextTopLeft.AdjustX((aBgRect.getWidth() - aTextBoundRect.GetSize().Width()) / 2 );
+ aTextTopLeft.AdjustY((aBgRect.GetHeight() - aTextBoundRect.GetSize().Height()) / 2
+ - aTextBoundRect.TopLeft().Y() );
+ 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, long nStartY, MenuItemData const * pThisItemOnly,
+ bool bHighlighted, bool bLayout, bool bRollover) const
+{
+ // for symbols: nFontHeight x nFontHeight
+ long nFontHeight = rRenderContext.GetTextHeight();
+ long nExtra = nFontHeight / 4;
+
+ 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)
+ {
+ 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()));
+ aOuterCheckRect.AdjustLeft(1 );
+ aOuterCheckRect.AdjustRight( -1 );
+ aOuterCheckRect.AdjustTop(1 );
+ aOuterCheckRect.AdjustBottom( -1 );
+
+ // 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;
+
+ 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;
+
+ const Menu *pMenu = this;
+ while (!pMenu->IsMenuBar() && pMenu->pStartedFrom)
+ pMenu = pMenu->pStartedFrom;
+ if (pMenu->IsMenuBar() && static_cast<MenuBarWindow*>(pMenu->pWindow.get())->GetMBWHideAccel())
+ nStyle |= DrawTextFlags::HideMnemonic;
+
+ if (pData->bIsTemporary)
+ nStyle |= DrawTextFlags::Disable;
+ MetricVector* 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?
+ 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);
+ 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 bCheckPopups, 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 ( bCheckPopups && 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())
+ {
+ mpLayoutData.reset(new MenuLayoutData);
+ if (IsMenuBar())
+ {
+ ImplPaint(*pWindow, pWindow->GetOutputSizePixel(), 0, 0, nullptr, false, true); // FIXME
+ }
+ else
+ {
+ MenuFloatingWindow* pFloat = static_cast<MenuFloatingWindow*>(pWindow.get());
+ ImplPaint(*pWindow, pWindow->GetOutputSizePixel(), pFloat->nScrollerHeight, pFloat->ImplGetStartY(),
+ nullptr, false, true); //FIXME
+ }
+ }
+}
+
+tools::Rectangle Menu::GetCharacterBounds( sal_uInt16 nItemID, long nIndex ) const
+{
+ 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();
+}
+
+long Menu::GetIndexForPoint( const Point& rPoint, sal_uInt16& rItemID ) const
+{
+ 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;
+}
+
+OUString Menu::GetAccessibleName( sal_uInt16 nItemId ) const
+{
+ MenuItemData* pData = pItemList->GetData( nItemId );
+
+ if ( pData )
+ return pData->aAccessibleName;
+
+ return OUString();
+}
+
+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 )
+ {
+ 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()
+ : Menu(),
+ mbCloseBtnVisible(false),
+ mbFloatBtnVisible(false),
+ mbHideBtnVisible(false),
+ mbDisplayable(true)
+{
+ mpSalMenu = ImplGetSVData()->mpDefInst->CreateMenu(true, this);
+}
+
+MenuBar::MenuBar( const MenuBar& rMenu )
+ : Menu(),
+ 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);
+ 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;
+}
+
+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;
+}
+
+bool MenuBar::ImplHandleCmdEvent( const CommandEvent& rCEvent )
+{
+ // No keyboard processing when system handles the menu or our menubar is invisible
+ if( !IsDisplayable() ||
+ ( ImplGetSalMenu() && ImplGetSalMenu()->VisibleMenuBar() ) )
+ return false;
+
+ // check for enabled, if this method is called from another window...
+ MenuBarWindow* pWin = static_cast<MenuBarWindow*>(ImplGetWindow());
+ if ( pWin && pWin->IsEnabled() && pWin->IsInputEnabled() && ! pWin->IsInModalMode() )
+ {
+ if (rCEvent.GetCommand() == CommandEventId::ModKeyChange && ImplGetSVData()->maNWFData.mbAutoAccel)
+ {
+ const CommandModKeyData* pCData = rCEvent.GetModKeyData ();
+ if (pWin->m_nHighlightedItem == ITEMPOS_INVALID)
+ {
+ if (pCData && pCData->IsMod2() && pCData->IsDown())
+ pWin->SetMBWHideAccel(false);
+ else
+ pWin->SetMBWHideAccel(true);
+ pWin->Invalidate(InvalidateFlags::Update);
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+void MenuBar::SelectItem(sal_uInt16 nId)
+{
+ if (pWindow)
+ {
+ 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<MenuBar::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<MenuBar::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->GetOutputHeightPixel() : 0;
+ }
+ return nMenubarHeight;
+}
+
+// bool PopupMenu::bAnyPopupInExecute = false;
+
+MenuFloatingWindow * PopupMenu::ImplGetFloatingWindow() const {
+ return static_cast<MenuFloatingWindow *>(Menu::ImplGetWindow());
+}
+
+PopupMenu::PopupMenu()
+ : mpLOKNotifier(nullptr)
+{
+ mpSalMenu = ImplGetSVData()->mpDefInst->CreateMenu(false, this);
+}
+
+PopupMenu::PopupMenu( const PopupMenu& rMenu )
+ : Menu(),
+ mpLOKNotifier(nullptr)
+{
+ 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);
+}
+
+bool PopupMenu::IsInExecute()
+{
+ return 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() )
+ {
+ if( nId != ITEMPOS_INVALID )
+ {
+ size_t nPos = 0;
+ MenuItemData* pData = GetItemList()->GetData( nId, nPos );
+ if (pData && pData->pSubMenu)
+ ImplGetFloatingWindow()->ChangeHighlightItem( nPos, true );
+ else
+ ImplGetFloatingWindow()->EndExecute( nId );
+ }
+ else
+ {
+ MenuFloatingWindow* pFloat = ImplGetFloatingWindow();
+ pFloat->GrabFocus();
+
+ for( size_t nPos = 0; nPos < GetItemList()->size(); nPos++ )
+ {
+ MenuItemData* pData = GetItemList()->GetDataFromPos( nPos );
+ if( pData->pSubMenu )
+ {
+ pFloat->KillActivePopup();
+ }
+ }
+ pFloat->ChangeHighlightItem( ITEMPOS_INVALID, false );
+ }
+ }
+}
+
+void PopupMenu::SetSelectedEntry( sal_uInt16 nId )
+{
+ nSelectedId = nId;
+ sSelectedIdent = GetItemIdent(nId);
+}
+
+sal_uInt16 PopupMenu::Execute( vcl::Window* pExecWindow, const Point& rPopupPos )
+{
+ return Execute( pExecWindow, tools::Rectangle( rPopupPos, rPopupPos ), PopupMenuFlags::ExecuteDown );
+}
+
+sal_uInt16 PopupMenu::Execute( vcl::Window* pExecWindow, const tools::Rectangle& rRect, PopupMenuFlags nFlags )
+{
+ ENSURE_OR_RETURN( pExecWindow, "PopupMenu::Execute: need a non-NULL window!", 0 );
+
+ FloatWinPopupFlags nPopupModeFlags = FloatWinPopupFlags::NONE;
+ if ( nFlags & PopupMenuFlags::ExecuteDown )
+ nPopupModeFlags = FloatWinPopupFlags::Down;
+ else if ( nFlags & PopupMenuFlags::ExecuteUp )
+ nPopupModeFlags = FloatWinPopupFlags::Up;
+ else if ( nFlags & PopupMenuFlags::ExecuteLeft )
+ nPopupModeFlags = FloatWinPopupFlags::Left;
+ 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)
+
+ if (nFlags & PopupMenuFlags::NoHorzPlacement)
+ nPopupModeFlags |= FloatWinPopupFlags::NoHorzPlacement;
+
+ return ImplExecute( pExecWindow, rRect, nPopupModeFlags, nullptr, false );
+}
+
+void PopupMenu::ImplFlushPendingSelect()
+{
+ // is there still Select?
+ Menu* pSelect = ImplFindSelectMenu();
+ if (pSelect)
+ {
+ // Select should be called prior to leaving execute in a popup menu!
+ Application::RemoveUserEvent( pSelect->nEventId );
+ pSelect->nEventId = nullptr;
+ pSelect->Select();
+ }
+}
+
+sal_uInt16 PopupMenu::ImplExecute( const VclPtr<vcl::Window>& pW, const tools::Rectangle& rRect, FloatWinPopupFlags nPopupModeFlags, Menu* pSFrom, bool bPreSelectFirst )
+{
+ if ( !pSFrom && ( PopupMenu::IsInExecute() || !GetItemCount() ) )
+ return 0;
+
+ // set the flag to hide or show accelerators in the menu depending on whether the menu was launched by mouse or keyboard shortcut
+ if( pSFrom && pSFrom->IsMenuBar())
+ {
+ auto pMenuBarWindow = static_cast<MenuBarWindow*>(pSFrom->pWindow.get());
+ pMenuBarWindow->SetMBWHideAccel( !(pMenuBarWindow->GetMBWMenuKey()) );
+ }
+
+ mpLayoutData.reset();
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ pStartedFrom = pSFrom;
+ nSelectedId = 0;
+ sSelectedIdent.clear();
+ bCanceled = false;
+
+ VclPtr<vcl::Window> xFocusId;
+ bool bRealExecute = false;
+ if ( !pStartedFrom )
+ {
+ pSVData->mpWinData->mbNoDeactivate = true;
+ xFocusId = Window::SaveFocus();
+ bRealExecute = true;
+ }
+ else
+ {
+ // assure that only one menu is open at a time
+ if (pStartedFrom->IsMenuBar() && pSVData->mpWinData->mpFirstFloat)
+ pSVData->mpWinData->mpFirstFloat->EndPopupMode(FloatWinPopupEndFlags::Cancel
+ | FloatWinPopupEndFlags::CloseAll);
+ }
+
+ SAL_WARN_IF( ImplGetWindow(), "vcl", "Win?!" );
+ tools::Rectangle aRect( rRect );
+ aRect.SetPos( pW->OutputToScreenPixel( aRect.TopLeft() ) );
+
+ if (bRealExecute)
+ nPopupModeFlags |= FloatWinPopupFlags::NewLevel;
+ nPopupModeFlags |= FloatWinPopupFlags::NoKeyClose | FloatWinPopupFlags::AllMouseButtonClose;
+
+ bInCallback = true; // set it here, if Activate overridden
+ Activate();
+ bInCallback = false;
+
+ if ( pW->IsDisposed() )
+ return 0; // Error
+
+ if ( bCanceled || bKilled )
+ return 0;
+
+ if ( !GetItemCount() )
+ return 0;
+
+ // The flag MenuFlags::HideDisabledEntries is inherited.
+ if ( pSFrom )
+ {
+ if ( pSFrom->nMenuFlags & MenuFlags::HideDisabledEntries )
+ nMenuFlags |= MenuFlags::HideDisabledEntries;
+ else
+ nMenuFlags &= ~MenuFlags::HideDisabledEntries;
+ }
+ else
+ // #102790# context menus shall never show disabled entries
+ nMenuFlags |= MenuFlags::HideDisabledEntries;
+
+ sal_uInt16 nVisibleEntries = ImplGetVisibleItemCount();
+ if ( !nVisibleEntries )
+ {
+ OUString aTmpEntryText(VclResId(SV_RESID_STRING_NOSELECTIONPOSSIBLE));
+
+ MenuItemData* pData = NbcInsertItem(0xFFFF, MenuItemBits::NONE, aTmpEntryText, nullptr, 0xFFFF, OString());
+ size_t nPos = 0;
+ pData = pItemList->GetData( pData->nId, nPos );
+ assert(pData);
+ if (pData)
+ {
+ pData->bIsTemporary = true;
+ }
+ ImplCallEventListeners(VclEventId::MenuSubmenuChanged, nPos);
+ }
+
+ VclPtrInstance<MenuFloatingWindow> pWin( this, pW, WB_BORDER | WB_SYSTEMWINDOW );
+ if (comphelper::LibreOfficeKit::isActive() && mpLOKNotifier)
+ pWin->SetLOKNotifier(mpLOKNotifier);
+
+ if( pSVData->maNWFData.mbFlatMenu )
+ pWin->SetBorderStyle( WindowBorderStyle::NOBORDER );
+ else
+ pWin->SetBorderStyle( pWin->GetBorderStyle() | WindowBorderStyle::MENU );
+ pWindow = pWin;
+
+ Size aSz = ImplCalcSize( pWin );
+
+ tools::Rectangle aDesktopRect(pWin->GetDesktopRectPixel());
+ if( Application::GetScreenCount() > 1 && Application::IsUnifiedDisplay() )
+ {
+ vcl::Window* pDeskW = pWindow->GetWindow( GetWindowType::RealParent );
+ if( ! pDeskW )
+ pDeskW = pWindow;
+ Point aDesktopTL( pDeskW->OutputToAbsoluteScreenPixel( aRect.TopLeft() ) );
+ aDesktopRect = Application::GetScreenPosSizePixel(
+ Application::GetBestScreen( tools::Rectangle( aDesktopTL, aRect.GetSize() ) ));
+ }
+
+ long nMaxHeight = aDesktopRect.GetHeight();
+
+ //rhbz#1021915. If a menu won't fit in the desired location the default
+ //mode is to place it somewhere it will fit. e.g. above, left, right. For
+ //some cases, e.g. menubars, it's desirable to limit the options to
+ //above/below and force the menu to scroll if it won't fit
+ if (nPopupModeFlags & FloatWinPopupFlags::NoHorzPlacement)
+ {
+ vcl::Window* pRef = pWin;
+ if ( pRef->GetParent() )
+ pRef = pRef->GetParent();
+
+ tools::Rectangle devRect( pRef->OutputToAbsoluteScreenPixel( aRect.TopLeft() ),
+ pRef->OutputToAbsoluteScreenPixel( aRect.BottomRight() ) );
+
+ long nHeightAbove = devRect.Top() - aDesktopRect.Top();
+ 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, 768l);
+
+ if (pStartedFrom && pStartedFrom->IsMenuBar())
+ nMaxHeight -= pW->GetSizePixel().Height();
+ sal_Int32 nLeft, nTop, nRight, nBottom;
+ pWindow->GetBorder( nLeft, nTop, nRight, nBottom );
+ nMaxHeight -= nTop+nBottom;
+ if ( aSz.Height() > nMaxHeight )
+ {
+ pWin->EnableScrollMenu( true );
+ sal_uInt16 nStart = ImplGetFirstVisible();
+ sal_uInt16 nEntries = ImplCalcVisEntries( nMaxHeight, nStart );
+ aSz.setHeight( ImplCalcHeight( nEntries ) );
+ }
+
+ // tdf#126054 hold this until after function completes
+ VclPtr<PopupMenu> xThis(this);
+
+ pWin->SetFocusId( xFocusId );
+ pWin->SetOutputSizePixel( aSz );
+ if ( GetItemCount() )
+ {
+ SalMenu* pMenu = ImplGetSalMenu();
+ if( pMenu && bRealExecute && pMenu->ShowNativePopupMenu( pWin, aRect, nPopupModeFlags | FloatWinPopupFlags::GrabFocus ) )
+ {
+ pWin->StopExecute();
+ pWin->doShutdown();
+ pWindow->SetParentToDefaultWindow();
+ pWindow.disposeAndClear();
+ ImplClosePopupToolBox(pW);
+ ImplFlushPendingSelect();
+ return nSelectedId;
+ }
+ else
+ {
+ pWin->StartPopupMode( aRect, nPopupModeFlags | FloatWinPopupFlags::GrabFocus );
+ }
+ if( pSFrom )
+ {
+ sal_uInt16 aPos;
+ if (pSFrom->IsMenuBar())
+ aPos = static_cast<MenuBarWindow *>(pSFrom->pWindow.get())->GetHighlightedItem();
+ else
+ aPos = static_cast<MenuFloatingWindow *>(pSFrom->pWindow.get())->GetHighlightedItem();
+
+ pWin->SetPosInParent( aPos ); // store position to be sent in SUBMENUDEACTIVATE
+ pSFrom->ImplCallEventListeners( VclEventId::MenuSubmenuActivate, aPos );
+ }
+ }
+ if ( bPreSelectFirst )
+ {
+ size_t nCount = pItemList->size();
+ for ( size_t n = 0; n < nCount; n++ )
+ {
+ MenuItemData* pData = pItemList->GetDataFromPos( n );
+ if ( ( pData->bEnabled
+ || !Application::GetSettings().GetStyleSettings().GetSkipDisabledInMenus()
+ )
+ && ( pData->eType != MenuItemType::SEPARATOR )
+ && ImplIsVisible( n )
+ && ImplIsSelectable( n )
+ )
+ {
+ pWin->ChangeHighlightItem( n, false );
+ break;
+ }
+ }
+ }
+ if ( bRealExecute )
+ {
+ pWin->Execute();
+ if (pWin->IsDisposed())
+ return 0;
+
+ xFocusId = pWin->GetFocusId();
+ assert(xFocusId == nullptr && "Focus should already be restored by MenuFloatingWindow::End");
+ pWin->ImplEndPopupMode(FloatWinPopupEndFlags::NONE, xFocusId);
+
+ if ( nSelectedId ) // then clean up .. ( otherwise done by TH )
+ {
+ PopupMenu* pSub = pWin->GetActivePopup();
+ while ( pSub )
+ {
+ pSub->ImplGetFloatingWindow()->EndPopupMode();
+ pSub = pSub->ImplGetFloatingWindow()->GetActivePopup();
+ }
+ }
+ pWin->doShutdown();
+ pWindow->SetParentToDefaultWindow();
+ pWindow.disposeAndClear();
+ ImplClosePopupToolBox(pW);
+ ImplFlushPendingSelect();
+ }
+
+ return bRealExecute ? nSelectedId : 0;
+}
+
+sal_uInt16 PopupMenu::ImplCalcVisEntries( long nMaxHeight, sal_uInt16 nStartEntry, sal_uInt16* pLastVisible ) const
+{
+ nMaxHeight -= 2 * ImplGetFloatingWindow()->GetScrollerHeight();
+
+ 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;
+}
+
+long PopupMenu::ImplCalcHeight( sal_uInt16 nEntries ) const
+{
+ 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;
+}
+
+ImplMenuDelData::ImplMenuDelData( const Menu* pMenu )
+: mpNext( nullptr )
+, mpMenu( nullptr )
+{
+ if( pMenu )
+ const_cast< Menu* >( pMenu )->ImplAddDel( *this );
+}
+
+ImplMenuDelData::~ImplMenuDelData()
+{
+ if( mpMenu )
+ const_cast< Menu* >( mpMenu.get() )->ImplRemoveDel( *this );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/menubarwindow.cxx b/vcl/source/window/menubarwindow.cxx
new file mode 100644
index 000000000..41a51b79c
--- /dev/null
+++ b/vcl/source/window/menubarwindow.cxx
@@ -0,0 +1,1252 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "menubarwindow.hxx"
+#include "menuitemlist.hxx"
+#include "menufloatingwindow.hxx"
+
+#include <vcl/dockingarea.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/taskpanelist.hxx>
+#include <sal/log.hxx>
+
+#include <salframe.hxx>
+#include <salmenu.hxx>
+#include <svdata.hxx>
+#include <strings.hrc>
+#include <bitmaps.hlst>
+#include <window.h>
+#include "bufferdevice.hxx"
+
+// document closing button
+#define IID_DOCUMENTCLOSE 1
+
+DecoToolBox::DecoToolBox( vcl::Window* pParent ) :
+ ToolBox( pParent, 0 ),
+ lastSize(-1)
+{
+ calcMinSize();
+}
+
+void DecoToolBox::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Window::DataChanged( rDCEvt );
+
+ if ( rDCEvt.GetFlags() & AllSettingsFlags::STYLE )
+ {
+ calcMinSize();
+ SetBackground();
+ SetImages( 0, true);
+ }
+}
+
+void DecoToolBox::calcMinSize()
+{
+ ScopedVclPtrInstance<ToolBox> aTbx( GetParent() );
+ if( GetItemCount() == 0 )
+ {
+ aTbx->InsertItem(IID_DOCUMENTCLOSE, Image(StockImage::Yes, SV_RESID_BITMAP_CLOSEDOC));
+ }
+ else
+ {
+ ImplToolItems::size_type nItems = GetItemCount();
+ for( ImplToolItems::size_type i = 0; i < nItems; i++ )
+ {
+ sal_uInt16 nId = GetItemId( i );
+ aTbx->InsertItem( nId, GetItemImage( nId ) );
+ }
+ }
+ aTbx->SetOutStyle( TOOLBOX_STYLE_FLAT );
+ maMinSize = aTbx->CalcWindowSizePixel();
+
+ aTbx.disposeAndClear();
+}
+
+void DecoToolBox::SetImages( long nMaxHeight, bool bForce )
+{
+ 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( 255, 255, 255, 255 );
+ BitmapEx aBmpExDst( maImage.GetBitmapEx() );
+ BitmapEx aBmpExSrc( aBmpExDst );
+
+ aEraseColor.SetTransparency( 255 );
+ 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( 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;
+ SetMBWHideAccel(true);
+ SetMBWMenuKey(false);
+
+ m_aCloseBtn->maImage = Image(StockImage::Yes, SV_RESID_BITMAP_CLOSEDOC);
+
+ m_aCloseBtn->SetOutStyle(TOOLBOX_STYLE_FLAT);
+ m_aCloseBtn->SetBackground();
+ m_aCloseBtn->SetPaintTransparent(true);
+ m_aCloseBtn->SetParentClipMode(ParentClipMode::NoClip);
+
+ m_aCloseBtn->InsertItem(IID_DOCUMENTCLOSE, m_aCloseBtn->maImage);
+ m_aCloseBtn->SetSelectHdl(LINK(this, MenuBarWindow, CloseHdl));
+ m_aCloseBtn->AddEventListener(LINK(this, MenuBarWindow, ToolboxEventHdl));
+ m_aCloseBtn->SetQuickHelpText(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(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() );
+ }
+}
+
+void MenuBarWindow::SetHeight(long nHeight)
+{
+ setPosSizePixel(0, 0, 0, nHeight, PosSizeFlags::Height);
+}
+
+void MenuBarWindow::ShowButtons( bool bClose, bool bFloat, bool bHide )
+{
+ m_aCloseBtn->ShowItem(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()
+{
+ return m_aCloseBtn->getMinSize();
+}
+
+IMPL_LINK_NOARG(MenuBarWindow, CloseHdl, ToolBox *, void)
+{
+ if( ! m_pMenu )
+ return;
+
+ if( m_aCloseBtn->GetCurItemId() == IID_DOCUMENTCLOSE )
+ {
+ // #i106052# call close hdl asynchronously to ease handler implementation
+ // this avoids still being in the handler while the DecoToolBox already
+ // gets destroyed
+ Application::PostUserEvent(static_cast<MenuBar*>(m_pMenu.get())->GetCloseButtonClickHdl());
+ }
+ else
+ {
+ std::map<sal_uInt16,AddButtonEntry>::iterator it = m_aAddButtons.find(m_aCloseBtn->GetCurItemId());
+ if( it != m_aAddButtons.end() )
+ {
+ MenuBar::MenuBarButtonCallbackArg aArg;
+ aArg.nId = it->first;
+ aArg.bHighlight = (m_aCloseBtn->GetHighlightItemId() == it->first);
+ it->second.m_aSelectLink.Call( aArg );
+ }
+ }
+}
+
+IMPL_LINK( MenuBarWindow, ToolboxEventHdl, VclWindowEvent&, rEvent, void )
+{
+ if( ! m_pMenu )
+ return;
+
+ MenuBar::MenuBarButtonCallbackArg aArg;
+ aArg.nId = 0xffff;
+ aArg.bHighlight = (rEvent.GetId() == VclEventId::ToolboxHighlight);
+ if( rEvent.GetId() == VclEventId::ToolboxHighlight )
+ aArg.nId = 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 = 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 )
+ {
+ m_bIgnoreFirstMove = true;
+ if ( m_pActivePopup && ( m_pActivePopup != pItemData->pSubMenu ) )
+ {
+ KillActivePopup();
+ }
+ if ( pItemData->bEnabled && pItemData->pSubMenu && ( m_nHighlightedItem != ITEMPOS_INVALID ) &&
+ ( pItemData->pSubMenu != m_pActivePopup ) )
+ {
+ m_pActivePopup = static_cast<PopupMenu*>(pItemData->pSubMenu.get());
+ 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 )
+ {
+ 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->SetParentToDefaultWindow();
+ 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;
+ SetMBWMenuKey(false);
+ 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 )
+ {
+ // there is a spurious MouseMove generated after a menu is launched from the keyboard, hence this...
+ if (m_nHighlightedItem != ITEMPOS_INVALID)
+ {
+ bool hide = GetMBWHideAccel();
+ SetMBWHideAccel(true);
+ Invalidate(); //HighlightItem( nRolloveredItem, false );
+ SetMBWHideAccel(hide);
+ }
+ else
+ 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;
+
+ // always hide accelerators when updating the menu bar...
+ SetMBWHideAccel(true);
+
+ // #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;
+
+ 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(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 )
+{
+ tools::Rectangle aRect;
+ if( m_pMenu )
+ {
+ 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;
+ }
+ }
+
+ bool autoacc = ImplGetSVData()->maNWFData.mbAutoAccel;
+
+ 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;
+ }
+ }
+ }
+
+ const bool bShowAccels = nCode != KEY_ESCAPE;
+ if (GetMBWMenuKey() != bShowAccels)
+ {
+ SetMBWMenuKey(bShowAccels);
+ SetMBWHideAccel(!bShowAccels);
+ if (autoacc)
+ Invalidate(InvalidateFlags::Update);
+ }
+
+ return bDone;
+}
+
+void MenuBarWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&)
+{
+ if (!m_pMenu)
+ return;
+
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+
+ Size aOutputSize = GetOutputSizePixel();
+
+ // no VCL paint if native menus
+ if (m_pMenu->ImplGetSalMenu() && m_pMenu->ImplGetSalMenu()->VisibleMenuBar())
+ {
+ ImplGetFrame()->DrawMenuBar();
+ return;
+ }
+
+ // Make sure that all actual rendering happens in one go to avoid flicker.
+ vcl::BufferDevice pBuffer(this, rRenderContext);
+
+ if (rRenderContext.IsNativeControlSupported(ControlType::Menubar, ControlPart::Entire))
+ {
+ MenubarValue aMenubarValue;
+ aMenubarValue.maTopDockingAreaHeight = ImplGetTopDockingAreaHeight(this);
+
+ if (!rStyleSettings.GetPersonaHeader().IsEmpty())
+ Erase(*pBuffer);
+ else
+ {
+ tools::Rectangle aCtrlRegion( Point(), aOutputSize );
+
+ pBuffer->DrawNativeControl(ControlType::Menubar, ControlPart::Entire, aCtrlRegion,
+ ControlState::ENABLED, aMenubarValue, OUString());
+ }
+
+ ImplAddNWFSeparator(*pBuffer, aOutputSize, aMenubarValue);
+ }
+
+ // shrink the area of the buttons
+ aOutputSize.AdjustWidth( -(m_aCloseBtn->GetSizePixel().Width()) );
+
+ pBuffer->SetFillColor(rStyleSettings.GetMenuColor());
+ m_pMenu->ImplPaint(*pBuffer, aOutputSize, 0);
+
+ if (m_nHighlightedItem != ITEMPOS_INVALID && m_pMenu && !m_pMenu->GetItemList()->GetDataFromPos(m_nHighlightedItem)->bHiddenOnGUI)
+ HighlightItem(*pBuffer, m_nHighlightedItem);
+ else if (m_nRolloveredItem != ITEMPOS_INVALID)
+ HighlightItem(*pBuffer, m_nRolloveredItem);
+
+ // in high contrast mode draw a separating line on the lower edge
+ if (!rRenderContext.IsNativeControlSupported( ControlType::Menubar, ControlPart::Entire) &&
+ rStyleSettings.GetHighContrastMode())
+ {
+ pBuffer->Push(PushFlags::LINECOLOR | 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();
+ long n = aOutSz.Height()-4;
+ long nX = aOutSz.Width()-3;
+ long nY = 2;
+
+ if ( m_aCloseBtn->IsVisible() )
+ {
+ m_aCloseBtn->Hide();
+ m_aCloseBtn->SetImages(n);
+ Size aTbxSize( m_aCloseBtn->CalcWindowSizePixel() );
+ nX -= aTbxSize.Width();
+ 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 )
+ {
+ 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(*this);
+ Invalidate();
+ }
+ else if (nType == StateChangedType::Enable)
+ {
+ Invalidate();
+ }
+ else if(m_pMenu)
+ {
+ m_pMenu->ImplKillLayoutData();
+ }
+}
+
+void MenuBarWindow::LayoutChanged()
+{
+ if (!m_pMenu)
+ return;
+
+ ApplySettings(*this);
+
+ // if the font was changed.
+ long nHeight = m_pMenu->ImplCalcSize(this).Height();
+
+ // depending on the native implementation or the displayable flag
+ // the menubar windows is suppressed (ie, height=0)
+ if (!static_cast<MenuBar*>(m_pMenu.get())->IsDisplayable() ||
+ (m_pMenu->ImplGetSalMenu() && m_pMenu->ImplGetSalMenu()->VisibleMenuBar()))
+ {
+ nHeight = 0;
+ }
+ setPosSizePixel(0, 0, 0, nHeight, PosSizeFlags::Height);
+ GetParent()->Resize();
+ Invalidate();
+ Resize();
+
+ m_pMenu->ImplKillLayoutData();
+}
+
+void MenuBarWindow::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ Window::ApplySettings(rRenderContext);
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+
+ SetPointFont(rRenderContext, rStyleSettings.GetMenuFont());
+
+ const BitmapEx& rPersonaBitmap = Application::GetSettings().GetStyleSettings().GetPersonaHeader();
+ SalMenu *pNativeMenu = m_pMenu ? m_pMenu->ImplGetSalMenu() : nullptr;
+ if (pNativeMenu)
+ pNativeMenu->ApplyPersona();
+ if (!rPersonaBitmap.IsEmpty())
+ {
+ Wallpaper aWallpaper(rPersonaBitmap);
+ aWallpaper.SetStyle(WallpaperStyle::TopRight);
+ aWallpaper.SetColor(Application::GetSettings().GetStyleSettings().GetWorkspaceColor());
+
+ rRenderContext.SetBackground(aWallpaper);
+ SetPaintTransparent(false);
+ SetParentClipMode();
+ }
+ else if (rRenderContext.IsNativeControlSupported(ControlType::Menubar, ControlPart::Entire))
+ {
+ rRenderContext.SetBackground(); // background will be drawn by NWF
+ }
+ else
+ {
+ Wallpaper aWallpaper;
+ aWallpaper.SetStyle(WallpaperStyle::ApplicationGradient);
+ rRenderContext.SetBackground(aWallpaper);
+ SetPaintTransparent(false);
+ SetParentClipMode();
+ }
+
+ rRenderContext.SetTextColor(rStyleSettings.GetMenuBarTextColor());
+ rRenderContext.SetTextFillColor();
+ rRenderContext.SetLineColor();
+}
+
+void MenuBarWindow::ImplInitStyleSettings()
+{
+ if (IsNativeControlSupported(ControlType::Menubar, ControlPart::MenuItem) &&
+ IsNativeControlSupported(ControlType::Menubar, ControlPart::Entire))
+ {
+ 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);
+ OutputDevice::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(*this);
+ 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<MenuBar::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(nId, i_rImage, ToolBoxItemBits::NONE, 0);
+ m_aCloseBtn->calcMinSize();
+ ShowButtons(m_aCloseBtn->IsItemVisible(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<MenuBar::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(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(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() )
+ {
+ MenuBar::MenuBarButtonCallbackArg aArg;
+ aArg.nId = it->first;
+ aArg.bHighlight = true;
+ return it->second.m_aSelectLink.Call( aArg );
+ }
+ return false;
+}
+
+bool MenuBarWindow::CanGetFocus() const
+{
+ /* #i83908# do not use the menubar if it is native or invisible
+ this relies on MenuBar::ImplCreate setting the height of the menubar
+ to 0 in this case
+ */
+ SalMenu *pNativeMenu = m_pMenu ? m_pMenu->ImplGetSalMenu() : nullptr;
+ if (pNativeMenu && pNativeMenu->VisibleMenuBar())
+ return pNativeMenu->CanGetFocus();
+ return GetSizePixel().Height() > 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/menubarwindow.hxx b/vcl/source/window/menubarwindow.hxx
new file mode 100644
index 000000000..487746c09
--- /dev/null
+++ b/vcl/source/window/menubarwindow.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 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_WINDOW_MENUBARWINDOW_HXX
+#define INCLUDED_VCL_SOURCE_WINDOW_MENUBARWINDOW_HXX
+
+#include "menuwindow.hxx"
+
+#include <vcl/toolkit/button.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/toolbox.hxx>
+#include <vcl/window.hxx>
+
+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
+{
+ long lastSize;
+ Size maMinSize;
+
+public:
+ explicit DecoToolBox(vcl::Window* pParent);
+
+ void DataChanged( const DataChangedEvent& rDCEvt ) override;
+
+ void SetImages( 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<MenuBar::MenuBarButtonCallbackArg&,bool> m_aSelectLink;
+ Link<MenuBar::MenuBarButtonCallbackArg&,bool> m_aHighlightLink;
+ };
+
+ VclPtr<Menu> m_pMenu;
+ VclPtr<PopupMenu> m_pActivePopup;
+ VclPtr<PopupMenu> mpParentPopup;
+ sal_uInt16 m_nHighlightedItem;
+ sal_uInt16 m_nRolloveredItem;
+ VclPtr<vcl::Window> m_xSaveFocusId;
+ bool mbAutoPopup;
+ bool m_bIgnoreFirstMove;
+ bool mbHideAccel;
+ bool mbMenuKey;
+
+ 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 );
+
+ 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(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();
+
+ /// Add an arbitrary button to the menubar that will appear next to the close button.
+ sal_uInt16 AddMenuBarButton(const Image&, const Link<MenuBar::MenuBarButtonCallbackArg&,bool>&, const OUString&);
+ void SetMenuBarButtonHighlightHdl(sal_uInt16 nId, const Link<MenuBar::MenuBarButtonCallbackArg&,bool>&);
+ tools::Rectangle GetMenuBarButtonRectPixel(sal_uInt16 nId);
+ void RemoveMenuBarButton(sal_uInt16 nId);
+ bool HandleMenuButtonEvent(sal_uInt16 i_nButtonId);
+ void SetMBWHideAccel(bool val) { mbHideAccel = val; }
+ bool GetMBWHideAccel() const { return mbHideAccel; }
+ void SetMBWMenuKey(bool val) { mbMenuKey = val; }
+ bool GetMBWMenuKey() const { return mbMenuKey; }
+ bool CanGetFocus() const;
+};
+
+#endif // INCLUDED_VCL_SOURCE_WINDOW_MENUBARWINDOW_HXX
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/menufloatingwindow.cxx b/vcl/source/window/menufloatingwindow.cxx
new file mode 100644
index 000000000..84e19a63e
--- /dev/null
+++ b/vcl/source/window/menufloatingwindow.cxx
@@ -0,0 +1,1334 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 "menubarwindow.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),
+ 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(*this);
+
+ SetPopupModeEndHdl( LINK( this, MenuFloatingWindow, PopupEnd ) );
+
+ aHighlightChangedTimer.SetInvokeHandler( LINK( this, MenuFloatingWindow, HighlightChanged ) );
+ aHighlightChangedTimer.SetTimeout( GetSettings().GetMouseSettings().GetMenuDelay() );
+ aHighlightChangedTimer.SetDebugName( "vcl::MenuFloatingWindow aHighlightChangedTimer" );
+
+ aSubmenuCloseTimer.SetTimeout( GetSettings().GetMouseSettings().GetMenuDelay() );
+ aSubmenuCloseTimer.SetInvokeHandler( LINK( this, MenuFloatingWindow, SubmenuClose ) );
+ aSubmenuCloseTimer.SetDebugName( "vcl::MenuFloatingWindow aSubmenuCloseTimer" );
+
+ aScrollTimer.SetInvokeHandler( LINK( this, MenuFloatingWindow, AutoScroll ) );
+ aScrollTimer.SetDebugName( "vcl::MenuFloatingWindow aScrollTimer" );
+
+ 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(*this); // 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);
+ OutputDevice::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
+long MenuFloatingWindow::ImplGetStartY() const
+{
+ 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;
+
+ long nY = GetInitialItemY();
+ 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 );
+ 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();
+ 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 )
+ {
+ 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 ) )
+ {
+ pActivePopup = static_cast<PopupMenu*>(pItemData->pSubMenu.get());
+ 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(*this);
+}
+
+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::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 ) ) )
+ {
+ 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->SetParentToDefaultWindow();
+ 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 )
+ {
+ 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 ) )
+ {
+ if ( !pData->pSubMenu )
+ {
+ EndExecute();
+ }
+ else if ( ( pData->nBits & MenuItemBits::POPUPSELECT ) && ( nHighlightedItem == _nMBDownPos ) && ( rMEvt.GetClicks() == 2 ) )
+ {
+ // not when clicked over the arrow...
+ Size aSz = GetOutputSizePixel();
+ 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 )
+ {
+ 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 )
+ {
+ long nScrollEntryHeight = pItemData->aSz.Height();
+
+ nFirstEntry = pMenu->ImplGetNextVisible( nFirstEntry );
+ SAL_WARN_IF( nFirstEntry == ITEMPOS_INVALID, "vcl", "Scroll?!" );
+
+ if ( !bScrollUp )
+ {
+ bScrollUp = true;
+ Invalidate();
+ }
+
+ 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();
+
+ long nY = nScrollerHeight;
+ long nMouseY = rMousePos.Y();
+ 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 )
+ {
+ aScrollTimer.Stop(); // if scrolled through MouseMove.
+ 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.
+long MenuFloatingWindow::GetInitialItemY(long *pStartY) const
+{
+ 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;
+
+ long nY = GetInitialItemY();
+ size_t nCount = pMenu->pItemList->size();
+ for (size_t n = 0; n < nCount; n++)
+ {
+ MenuItemData* pData = pMenu->pItemList->GetDataFromPos( n );
+ 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());
+
+ long nX = 0;
+ long nStartY;
+ 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)
+ {
+ long nFontHeight = GetTextHeight();
+ aItemRect.AdjustRight( -(nFontHeight + nFontHeight / 4) );
+ }
+
+ if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Entire))
+ {
+ Size aPxSize(GetOutputSizePixel());
+ rRenderContext.Push(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 )
+{
+ if( ! pMenu )
+ return tools::Rectangle();
+
+ tools::Rectangle aRect;
+ Size aSz = GetOutputSizePixel();
+ long nStartY = ImplGetStartY();
+ 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 )
+ {
+ 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;
+
+ bool autoacc = ImplGetSVData()->maNWFData.mbAutoAccel;
+ 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 );
+ }
+ }
+
+ if (pMenu && pMenu->pStartedFrom && pMenu->pStartedFrom->IsMenuBar())
+ {
+ MenuBar *pMenuBar = static_cast<MenuBar*>(pMenu->pStartedFrom.get());
+ const bool bShowAccels = nCode != KEY_ESCAPE;
+ if (pMenuBar->getMenuBarWindow()->GetMBWMenuKey() != bShowAccels)
+ {
+ pMenuBar->getMenuBarWindow()->SetMBWMenuKey(bShowAccels);
+ pMenuBar->getMenuBarWindow()->SetMBWHideAccel(!bShowAccels);
+ if (autoacc)
+ Invalidate(InvalidateFlags::Update);
+ }
+ }
+
+ // #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(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();
+ 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());
+ long nY = bUp ? 0 : (aOutSz.Height() - nScrollerHeight);
+ 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(*this);
+ 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(*this);
+ Invalidate();
+ }
+}
+
+void MenuFloatingWindow::Command( const CommandEvent& rCEvt )
+{
+ if ( rCEvt.GetCommand() == CommandEventId::Wheel )
+ {
+ const CommandWheelData* pData = rCEvt.GetWheelData();
+ if( !pData->GetModifier() && ( pData->GetMode() == CommandWheelMode::SCROLL ) )
+ {
+ ImplScroll( pData->GetDelta() > 0 );
+ MouseMove( MouseEvent( GetPointerPosPixel(), 0 ) );
+ }
+ }
+}
+
+css::uno::Reference<css::accessibility::XAccessible> MenuFloatingWindow::CreateAccessible()
+{
+ css::uno::Reference<css::accessibility::XAccessible> xAcc;
+
+ if (pMenu && !pMenu->pStartedFrom)
+ xAcc = pMenu->GetAccessible();
+
+ return xAcc;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/menufloatingwindow.hxx b/vcl/source/window/menufloatingwindow.hxx
new file mode 100644
index 000000000..c091b46ad
--- /dev/null
+++ b/vcl/source/window/menufloatingwindow.hxx
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_WINDOW_MENUFLOATINGWINDOW_HXX
+#define INCLUDED_VCL_SOURCE_WINDOW_MENUFLOATINGWINDOW_HXX
+
+#include "menuwindow.hxx"
+
+#include <vcl/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 );
+ long ImplGetStartY() const;
+ tools::Rectangle ImplGetItemRect( sal_uInt16 nPos );
+ void RenderHighlightItem( vcl::RenderContext& rRenderContext, sal_uInt16 nPos );
+ long GetInitialItemY( 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;
+};
+
+#endif // INCLUDED_VCL_SOURCE_WINDOW_MENUFLOATINGWINDOW_HXX
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/menuitemlist.cxx b/vcl/source/window/menuitemlist.cxx
new file mode 100644
index 000000000..6921e6208
--- /dev/null
+++ b/vcl/source/window/menuitemlist.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 "menuitemlist.hxx"
+
+#include <salframe.hxx>
+#include <salinst.hxx>
+#include <salmenu.hxx>
+#include <svdata.hxx>
+#include <vcl/i18nhelp.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/vcllayout.hxx>
+#include <vcl/window.hxx>
+
+using namespace css;
+using namespace vcl;
+
+MenuItemData::~MenuItemData()
+{
+ if (aUserValueReleaseFunc)
+ aUserValueReleaseFunc(nUserValue);
+ pSalMenuItem.reset();
+ pSubMenu.disposeAndClear();
+}
+
+SalLayoutGlyphs* MenuItemData::GetTextGlyphs(const OutputDevice* pOutputDevice)
+{
+ if (aTextGlyphs.IsValid())
+ // Use pre-calculated result.
+ return &aTextGlyphs;
+
+ OUString aNonMnemonicString = OutputDevice::GetNonMnemonicString(aText);
+ std::unique_ptr<SalLayout> pLayout
+ = pOutputDevice->ImplLayout(aNonMnemonicString, 0, aNonMnemonicString.getLength(),
+ Point(0, 0), 0, nullptr, SalLayoutFlags::GlyphItemsOnly);
+ if (!pLayout)
+ return nullptr;
+
+ const SalLayoutGlyphs* pGlyphs = pLayout->GetGlyphs();
+ if (!pGlyphs)
+ return nullptr;
+
+ // Remember the calculation result.
+ aTextGlyphs = *pGlyphs;
+
+ return &aTextGlyphs;
+}
+
+MenuItemList::~MenuItemList()
+{
+}
+
+MenuItemData* MenuItemList::Insert(
+ sal_uInt16 nId,
+ MenuItemType eType,
+ MenuItemBits nBits,
+ const OUString& rStr,
+ Menu* pMenu,
+ size_t nPos,
+ const OString &rIdent
+)
+{
+ MenuItemData* pData = new MenuItemData( rStr );
+ pData->nId = nId;
+ pData->sIdent = rIdent;
+ pData->eType = eType;
+ pData->nBits = nBits;
+ pData->pSubMenu = nullptr;
+ pData->nUserValue = nullptr;
+ pData->bChecked = false;
+ pData->bEnabled = true;
+ pData->bVisible = true;
+ pData->bIsTemporary = false;
+
+ SalItemParams aSalMIData;
+ aSalMIData.nId = nId;
+ aSalMIData.eType = eType;
+ aSalMIData.nBits = nBits;
+ aSalMIData.pMenu = pMenu;
+ aSalMIData.aText = rStr;
+
+ // Native-support: returns NULL if not supported
+ pData->pSalMenuItem = ImplGetSVData()->mpDefInst->CreateMenuItem( aSalMIData );
+
+ if( nPos < maItemList.size() ) {
+ maItemList.insert( maItemList.begin() + nPos, std::unique_ptr<MenuItemData>(pData) );
+ } else {
+ maItemList.emplace_back( pData );
+ }
+ return pData;
+}
+
+void MenuItemList::InsertSeparator(const OString &rIdent, size_t nPos)
+{
+ MenuItemData* pData = new MenuItemData;
+ pData->nId = 0;
+ pData->sIdent = rIdent;
+ pData->eType = MenuItemType::SEPARATOR;
+ pData->nBits = MenuItemBits::NONE;
+ pData->pSubMenu = nullptr;
+ pData->nUserValue = nullptr;
+ pData->bChecked = false;
+ pData->bEnabled = true;
+ pData->bVisible = true;
+ pData->bIsTemporary = false;
+
+ SalItemParams aSalMIData;
+ aSalMIData.nId = 0;
+ aSalMIData.eType = MenuItemType::SEPARATOR;
+ aSalMIData.nBits = MenuItemBits::NONE;
+ aSalMIData.pMenu = nullptr;
+ aSalMIData.aText.clear();
+ aSalMIData.aImage = Image();
+
+ // Native-support: returns NULL if not supported
+ pData->pSalMenuItem = ImplGetSVData()->mpDefInst->CreateMenuItem( aSalMIData );
+
+ if( nPos < maItemList.size() ) {
+ maItemList.insert( maItemList.begin() + nPos, std::unique_ptr<MenuItemData>(pData) );
+ } else {
+ maItemList.emplace_back( pData );
+ }
+}
+
+void MenuItemList::Remove( size_t nPos )
+{
+ if( nPos < maItemList.size() )
+ {
+ maItemList.erase( maItemList.begin() + nPos );
+ }
+}
+
+void MenuItemList::Clear()
+{
+ maItemList.clear();
+}
+
+MenuItemData* MenuItemList::GetData( sal_uInt16 nSVId, size_t& rPos ) const
+{
+ for( size_t i = 0, n = maItemList.size(); i < n; ++i )
+ {
+ if ( maItemList[ i ]->nId == nSVId )
+ {
+ rPos = i;
+ return maItemList[ i ].get();
+ }
+ }
+ return nullptr;
+}
+
+MenuItemData* MenuItemList::SearchItem(
+ sal_Unicode cSelectChar,
+ KeyCode aKeyCode,
+ size_t& rPos,
+ size_t& nDuplicates,
+ size_t nCurrentPos
+) const
+{
+ const vcl::I18nHelper& rI18nHelper = Application::GetSettings().GetUILocaleI18nHelper();
+
+ size_t nListCount = maItemList.size();
+
+ // try character code first
+ nDuplicates = GetItemCount( cSelectChar ); // return number of duplicates
+ if( nDuplicates )
+ {
+ MenuItemData* pFirstMatch = nullptr;
+ size_t nFirstPos(0);
+ for ( rPos = 0; rPos < nListCount; rPos++)
+ {
+ MenuItemData* pData = maItemList[ rPos ].get();
+ if ( pData->bEnabled && rI18nHelper.MatchMnemonic( pData->aText, cSelectChar ) )
+ {
+ if (nDuplicates == 1)
+ return pData;
+ if (rPos > nCurrentPos)
+ return pData; // select next entry with the same mnemonic
+ if (!pFirstMatch) // stash the first match for use if nothing follows nCurrentPos
+ {
+ pFirstMatch = pData;
+ nFirstPos = rPos;
+ }
+ }
+ }
+ if (pFirstMatch)
+ {
+ rPos = nFirstPos;
+ return pFirstMatch;
+ }
+ }
+
+ // nothing found, try keycode instead
+ nDuplicates = GetItemCount( aKeyCode ); // return number of duplicates
+
+ if( nDuplicates )
+ {
+ char ascii = 0;
+ if( aKeyCode.GetCode() >= KEY_A && aKeyCode.GetCode() <= KEY_Z )
+ ascii = sal::static_int_cast<char>('A' + (aKeyCode.GetCode() - KEY_A));
+
+ MenuItemData* pFirstMatch = nullptr;
+ size_t nFirstPos(0);
+ for ( rPos = 0; rPos < nListCount; rPos++)
+ {
+ MenuItemData* pData = maItemList[ rPos ].get();
+ if ( pData->bEnabled )
+ {
+ sal_Int32 n = pData->aText.indexOf('~');
+ if ( n != -1 )
+ {
+ KeyCode nKeyCode;
+ sal_Unicode nUnicode = pData->aText[n+1];
+ vcl::Window* pDefWindow = ImplGetDefaultWindow();
+ if( ( pDefWindow
+ && pDefWindow->ImplGetFrame()->MapUnicodeToKeyCode( nUnicode,
+ Application::GetSettings().GetUILanguageTag().getLanguageType(), nKeyCode )
+ && aKeyCode.GetCode() == nKeyCode.GetCode()
+ )
+ || ( ascii
+ && rI18nHelper.MatchMnemonic( pData->aText, ascii )
+ )
+ )
+ {
+ if (nDuplicates == 1)
+ return pData;
+ if (rPos > nCurrentPos)
+ return pData; // select next entry with the same mnemonic
+ if (!pFirstMatch) // stash the first match for use if nothing follows nCurrentPos
+ {
+ pFirstMatch = pData;
+ nFirstPos = rPos;
+ }
+ }
+ }
+ }
+ }
+ if (pFirstMatch)
+ {
+ rPos = nFirstPos;
+ return pFirstMatch;
+ }
+ }
+
+ return nullptr;
+}
+
+size_t MenuItemList::GetItemCount( sal_Unicode cSelectChar ) const
+{
+ // returns number of entries with same mnemonic
+ const vcl::I18nHelper& rI18nHelper = Application::GetSettings().GetUILocaleI18nHelper();
+
+ size_t nItems = 0;
+ for ( size_t nPos = maItemList.size(); nPos; )
+ {
+ MenuItemData* pData = maItemList[ --nPos ].get();
+ if ( pData->bEnabled && rI18nHelper.MatchMnemonic( pData->aText, cSelectChar ) )
+ nItems++;
+ }
+
+ return nItems;
+}
+
+size_t MenuItemList::GetItemCount( KeyCode aKeyCode ) const
+{
+ // returns number of entries with same mnemonic
+ // uses key codes instead of character codes
+ const vcl::I18nHelper& rI18nHelper = Application::GetSettings().GetUILocaleI18nHelper();
+ char ascii = 0;
+ if( aKeyCode.GetCode() >= KEY_A && aKeyCode.GetCode() <= KEY_Z )
+ ascii = sal::static_int_cast<char>('A' + (aKeyCode.GetCode() - KEY_A));
+
+ size_t nItems = 0;
+ for ( size_t nPos = maItemList.size(); nPos; )
+ {
+ MenuItemData* pData = maItemList[ --nPos ].get();
+ if ( pData->bEnabled )
+ {
+ sal_Int32 n = pData->aText.indexOf('~');
+ if (n != -1)
+ {
+ KeyCode nKeyCode;
+ // if MapUnicodeToKeyCode fails or is unsupported we try the pure ascii mapping of the keycodes
+ // so we have working shortcuts when ascii mnemonics are used
+ vcl::Window* pDefWindow = ImplGetDefaultWindow();
+ if( ( pDefWindow
+ && pDefWindow->ImplGetFrame()->MapUnicodeToKeyCode( pData->aText[n+1],
+ Application::GetSettings().GetUILanguageTag().getLanguageType(), nKeyCode )
+ && aKeyCode.GetCode() == nKeyCode.GetCode()
+ )
+ || ( ascii
+ && rI18nHelper.MatchMnemonic( pData->aText, ascii )
+ )
+ )
+ nItems++;
+ }
+ }
+ }
+
+ return nItems;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/menuitemlist.hxx b/vcl/source/window/menuitemlist.hxx
new file mode 100644
index 000000000..8ca4b4159
--- /dev/null
+++ b/vcl/source/window/menuitemlist.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 .
+ */
+
+#include <vcl/vclenum.hxx>
+#include <vcl/glyphitem.hxx>
+#include <vcl/image.hxx>
+#include <vcl/keycod.hxx>
+#include <vcl/menu.hxx>
+#include <salmenu.hxx>
+
+#include <memory>
+#include <vector>
+
+class SalMenuItem;
+
+struct MenuItemData
+{
+ sal_uInt16 nId; // SV Id
+ MenuItemType eType; // MenuItem-Type
+ MenuItemBits nBits; // MenuItem-Bits
+ VclPtr<Menu> pSubMenu; // Pointer to SubMenu
+ OUString aText; // Menu-Text
+ SalLayoutGlyphs aTextGlyphs; ///< Text layout of aText.
+ OUString aHelpText; // Help-String
+ OUString aTipHelpText; // TipHelp-String (eg, expanded filenames)
+ OUString aCommandStr; // CommandString
+ OUString aHelpCommandStr; // Help command string (to reference external help)
+ OString sIdent;
+ OString aHelpId; // Help-Id
+ void* nUserValue; // User value
+ MenuUserDataReleaseFunction aUserValueReleaseFunc; // called when MenuItemData is destroyed
+ Image aImage; // Image
+ vcl::KeyCode aAccelKey; // Accelerator-Key
+ bool bChecked; // Checked
+ bool bEnabled; // Enabled
+ bool bVisible; // Visible (note: this flag will not override MenuFlags::HideDisabledEntries when true)
+ bool bIsTemporary; // Temporary inserted ('No selection possible')
+ bool bHiddenOnGUI;
+ Size aSz; // only temporarily valid
+ OUString aAccessibleName; // accessible name
+
+ std::unique_ptr<SalMenuItem> pSalMenuItem; // access to native menu
+
+ MenuItemData()
+ : nId(0)
+ , eType(MenuItemType::DONTKNOW)
+ , nBits(MenuItemBits::NONE)
+ , pSubMenu(nullptr)
+ , nUserValue(nullptr)
+ , aUserValueReleaseFunc(nullptr)
+ , bChecked(false)
+ , bEnabled(false)
+ , bVisible(false)
+ , bIsTemporary(false)
+ , bHiddenOnGUI(false)
+ {
+ }
+ MenuItemData( const OUString& rStr )
+ : nId(0)
+ , eType(MenuItemType::DONTKNOW)
+ , nBits(MenuItemBits::NONE)
+ , pSubMenu(nullptr)
+ , aText(rStr)
+ , nUserValue(nullptr)
+ , aUserValueReleaseFunc(nullptr)
+ , aImage()
+ , bChecked(false)
+ , bEnabled(false)
+ , bVisible(false)
+ , bIsTemporary(false)
+ , bHiddenOnGUI(false)
+ {
+ }
+ ~MenuItemData();
+
+ /// Computes aText's text layout (glyphs), cached in aTextGlyphs.
+ SalLayoutGlyphs* GetTextGlyphs(const OutputDevice* pOutputDevice);
+
+ bool HasCheck() const
+ {
+ return bChecked || ( nBits & ( MenuItemBits::RADIOCHECK | MenuItemBits::CHECKABLE | MenuItemBits::AUTOCHECK ) );
+ }
+};
+
+class MenuItemList
+{
+private:
+ ::std::vector< std::unique_ptr<MenuItemData> > maItemList;
+
+public:
+ MenuItemList() {}
+ ~MenuItemList();
+
+ MenuItemData* Insert(
+ sal_uInt16 nId,
+ MenuItemType eType,
+ MenuItemBits nBits,
+ const OUString& rStr,
+ Menu* pMenu,
+ size_t nPos,
+ const OString &rIdent
+ );
+ void InsertSeparator(const OString &rIdent, size_t nPos);
+ void Remove( size_t nPos );
+ void Clear();
+
+ MenuItemData* GetData( sal_uInt16 nSVId, size_t& rPos ) const;
+ MenuItemData* GetData( sal_uInt16 nSVId ) const
+ {
+ size_t nTemp;
+ return GetData( nSVId, nTemp );
+ }
+ MenuItemData* GetDataFromPos( size_t nPos ) const
+ {
+ return ( nPos < maItemList.size() ) ? maItemList[ nPos ].get() : nullptr;
+ }
+
+ MenuItemData* SearchItem(
+ sal_Unicode cSelectChar,
+ vcl::KeyCode aKeyCode,
+ size_t& rPos,
+ size_t& nDuplicates,
+ size_t nCurrentPos
+ ) const;
+ size_t GetItemCount( sal_Unicode cSelectChar ) const;
+ size_t GetItemCount( vcl::KeyCode aKeyCode ) const;
+ size_t size()
+ {
+ return maItemList.size();
+ }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/menuwindow.cxx b/vcl/source/window/menuwindow.cxx
new file mode 100644
index 000000000..2387d485f
--- /dev/null
+++ b/vcl/source/window/menuwindow.cxx
@@ -0,0 +1,111 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "menuwindow.hxx"
+#include "menuitemlist.hxx"
+
+#include <vcl/help.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/window.hxx>
+
+static sal_uLong ImplChangeTipTimeout( sal_uLong nTimeout, vcl::Window *pWindow )
+{
+ AllSettings aAllSettings( pWindow->GetSettings() );
+ HelpSettings aHelpSettings( aAllSettings.GetHelpSettings() );
+ sal_uLong nRet = aHelpSettings.GetTipTimeout();
+ aHelpSettings.SetTipTimeout( nTimeout );
+ aAllSettings.SetHelpSettings( aHelpSettings );
+ pWindow->SetSettings( aAllSettings );
+ return nRet;
+}
+
+bool MenuWindow::ImplHandleHelpEvent(vcl::Window* pMenuWindow, Menu const * pMenu, sal_uInt16 nHighlightedItem,
+ const HelpEvent& rHEvt, const tools::Rectangle &rHighlightRect)
+{
+ if( ! pMenu )
+ return false;
+
+ bool bDone = false;
+ sal_uInt16 nId = 0;
+
+ if ( nHighlightedItem != ITEMPOS_INVALID )
+ {
+ MenuItemData* pItemData = pMenu->GetItemList()->GetDataFromPos( nHighlightedItem );
+ if ( pItemData )
+ nId = pItemData->nId;
+ }
+
+ if ( ( rHEvt.GetMode() & HelpEventMode::BALLOON ) && pMenuWindow )
+ {
+ Point aPos;
+ if( rHEvt.KeyboardActivated() )
+ aPos = rHighlightRect.Center();
+ else
+ aPos = rHEvt.GetMousePosPixel();
+
+ tools::Rectangle aRect( aPos, Size() );
+ if (!pMenu->GetHelpText(nId).isEmpty())
+ Help::ShowBalloon( pMenuWindow, aPos, aRect, pMenu->GetHelpText( nId ) );
+ else
+ {
+ // give user a chance to read the full filename
+ sal_uLong oldTimeout=ImplChangeTipTimeout( 60000, pMenuWindow );
+ // call always, even when strlen==0 to correctly remove tip
+ Help::ShowQuickHelp( pMenuWindow, aRect, pMenu->GetTipHelpText( nId ) );
+ ImplChangeTipTimeout( oldTimeout, pMenuWindow );
+ }
+ bDone = true;
+ }
+ else if ( ( rHEvt.GetMode() &HelpEventMode::QUICK ) && pMenuWindow )
+ {
+ Point aPos = rHEvt.GetMousePosPixel();
+ tools::Rectangle aRect( aPos, Size() );
+ // give user a chance to read the full filename
+ sal_uLong oldTimeout=ImplChangeTipTimeout( 60000, pMenuWindow );
+ // call always, even when strlen==0 to correctly remove tip
+ Help::ShowQuickHelp( pMenuWindow, aRect, pMenu->GetTipHelpText( nId ) );
+ ImplChangeTipTimeout( oldTimeout, pMenuWindow );
+ bDone = true;
+ }
+ else if ( rHEvt.GetMode() & HelpEventMode::CONTEXT )
+ {
+ // is help in the application selected
+ Help* pHelp = Application::GetHelp();
+ if ( pHelp )
+ {
+ // is an id available, then call help with the id, otherwise
+ // use help-index
+ OUString aCommand = pMenu->GetItemCommand( nId );
+ OString aHelpId( pMenu->GetHelpId( nId ) );
+ if( aHelpId.isEmpty() )
+ aHelpId = OOO_HELP_INDEX;
+
+ if ( !aCommand.isEmpty() )
+ pHelp->Start(aCommand, static_cast<vcl::Window*>(nullptr));
+ else
+ pHelp->Start(OStringToOUString(aHelpId, RTL_TEXTENCODING_UTF8), static_cast<vcl::Window*>(nullptr));
+ }
+ bDone = true;
+ }
+ return bDone;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/menuwindow.hxx b/vcl/source/window/menuwindow.hxx
new file mode 100644
index 000000000..dcd81dea2
--- /dev/null
+++ b/vcl/source/window/menuwindow.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_SOURCE_WINDOW_MENUWINDOW_HXX
+#define INCLUDED_VCL_SOURCE_WINDOW_MENUWINDOW_HXX
+
+#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);
+};
+
+#endif // INCLUDED_VCL_SOURCE_WINDOW_MENUWINDOW_HXX
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/mnemonic.cxx b/vcl/source/window/mnemonic.cxx
new file mode 100644
index 000000000..610862191
--- /dev/null
+++ b/vcl/source/window/mnemonic.cxx
@@ -0,0 +1,343 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <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>
+
+using namespace ::com::sun::star;
+
+MnemonicGenerator::MnemonicGenerator(sal_Unicode cMnemonic)
+ : m_cMnemonic(cMnemonic)
+{
+ memset( maMnemonics, 1, sizeof( maMnemonics ) );
+}
+
+sal_uInt16 MnemonicGenerator::ImplGetMnemonicIndex( sal_Unicode c )
+{
+ static sal_uInt16 const aImplMnemonicRangeTab[MNEMONIC_RANGES*2] =
+ {
+ MNEMONIC_RANGE_1_START, MNEMONIC_RANGE_1_END,
+ MNEMONIC_RANGE_2_START, MNEMONIC_RANGE_2_END,
+ MNEMONIC_RANGE_3_START, MNEMONIC_RANGE_3_END,
+ MNEMONIC_RANGE_4_START, MNEMONIC_RANGE_4_END
+ };
+
+ sal_uInt16 nMnemonicIndex = 0;
+ for ( sal_uInt16 i = 0; i < MNEMONIC_RANGES; i++ )
+ {
+ if ( (c >= aImplMnemonicRangeTab[i*2]) &&
+ (c <= aImplMnemonicRangeTab[i*2+1]) )
+ return nMnemonicIndex+c-aImplMnemonicRangeTab[i*2];
+
+ nMnemonicIndex += aImplMnemonicRangeTab[i*2+1]-aImplMnemonicRangeTab[i*2];
+ }
+
+ return MNEMONIC_INDEX_NOTFOUND;
+}
+
+sal_Unicode MnemonicGenerator::ImplFindMnemonic( const OUString& rKey )
+{
+ sal_Int32 nIndex = 0;
+ while ( (nIndex = rKey.indexOf( m_cMnemonic, nIndex )) != -1 )
+ {
+ 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, OUString(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, OUString(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 = OUStringLiteral("(") + 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::toAsciiUpperCase(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, "" );
+ nLen -= 4;
+ i--;
+ continue;
+ }
+ }
+
+ // remove standard mnemonics
+ aStr = aStr.replaceAt( i, 1, "" );
+ nLen--;
+ }
+ else
+ i++;
+ }
+
+ return aStr;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/mnemonicengine.cxx b/vcl/source/window/mnemonicengine.cxx
new file mode 100644
index 000000000..a110bb5bd
--- /dev/null
+++ b/vcl/source/window/mnemonicengine.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 <vcl/mnemonicengine.hxx>
+
+#include <vcl/i18nhelp.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/event.hxx>
+#include <vcl/settings.hxx>
+
+namespace vcl
+{
+ struct MnemonicEngine_Data
+ {
+ IMnemonicEntryList& rEntryList;
+
+ explicit MnemonicEngine_Data( IMnemonicEntryList& _rEntryList )
+ :rEntryList( _rEntryList )
+ {
+ }
+ };
+
+ namespace
+ {
+ const void* lcl_getEntryForMnemonic( IMnemonicEntryList const & _rEntryList, sal_Unicode _cMnemonic, bool& _rbAmbiguous )
+ {
+ _rbAmbiguous = false;
+
+ const vcl::I18nHelper& rI18nHelper = Application::GetSettings().GetUILocaleI18nHelper();
+
+ OUString sEntryText;
+ const void* pSearchEntry = _rEntryList.FirstSearchEntry( sEntryText );
+
+ const void* pFirstFoundEntry = nullptr;
+ bool bCheckingAmbiguity = false;
+ const void* pStartedWith = pSearchEntry;
+ while ( pSearchEntry )
+ {
+ if ( rI18nHelper.MatchMnemonic( sEntryText, _cMnemonic ) )
+ {
+ if ( bCheckingAmbiguity )
+ {
+ // that's the second (at least) entry with this mnemonic
+ _rbAmbiguous = true;
+ return pFirstFoundEntry;
+ }
+
+ pFirstFoundEntry = pSearchEntry;
+ bCheckingAmbiguity = true;
+ }
+
+ pSearchEntry = _rEntryList.NextSearchEntry( pSearchEntry, sEntryText );
+ if ( pSearchEntry == pStartedWith )
+ break;
+ }
+
+ return pFirstFoundEntry;
+ }
+ }
+
+ MnemonicEngine::MnemonicEngine( IMnemonicEntryList& _rEntryList )
+ :m_pData( new MnemonicEngine_Data( _rEntryList ) )
+ {
+ }
+
+ bool MnemonicEngine::HandleKeyEvent( const KeyEvent& _rKEvt )
+ {
+ bool bAccelKey = _rKEvt.GetKeyCode().IsMod2();
+ if ( !bAccelKey )
+ return false;
+
+ sal_Unicode cChar = _rKEvt.GetCharCode();
+ bool bAmbiguous = false;
+ const void* pEntry = lcl_getEntryForMnemonic( m_pData->rEntryList, cChar, bAmbiguous );
+ if ( !pEntry )
+ return false;
+
+ m_pData->rEntryList.SelectSearchEntry( pEntry );
+ if ( !bAmbiguous )
+ m_pData->rEntryList.ExecuteSearchEntry( pEntry );
+
+ // handled
+ return true;
+ }
+
+ MnemonicEngine::~MnemonicEngine()
+ {
+ }
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/mouse.cxx b/vcl/source/window/mouse.cxx
new file mode 100644
index 000000000..5894d88bd
--- /dev/null
+++ b/vcl/source/window/mouse.cxx
@@ -0,0 +1,792 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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_feature_desktop.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 <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( ImplIsAntiparallel() )
+ {
+ const OutputDevice *pOutDev = GetOutDev();
+ pOutDev->ReMirror( aFramePos );
+ }
+ tools::Rectangle aRect( Point( mnOutOffX, mnOutOffY ), Size( mnOutWidth, mnOutHeight ) );
+ if ( !aRect.IsInside( aFramePos ) )
+ return WindowHitTest::NONE;
+ if ( mpWindowImpl->mbWinRegion )
+ {
+ Point aTempPos = aFramePos;
+ aTempPos.AdjustX( -mnOutOffX );
+ aTempPos.AdjustY( -mnOutOffY );
+ if ( !mpWindowImpl->maWinRegion.IsInside( 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.IsInside( 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 )
+ {
+ sal_uInt64 nTime = tools::Time::GetSystemTicks();
+ long nX = mpWindowImpl->mpFrameData->mnLastMouseX;
+ 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->mnOutWidth) ||
+ (nY >= mpWindowImpl->mpFrameWindow->mnOutHeight)) &&
+ !ImplGetSVData()->mpWinData->mpCaptureWin;
+ nMode |= MouseEventModifiers::SYNTHETIC;
+ if ( bModChanged )
+ nMode |= MouseEventModifiers::MODIFIERCHANGED;
+ ImplHandleMouseEvent( mpWindowImpl->mpFrameWindow, MouseNotifyEvent::MOUSEMOVE, bLeave, nX, nY, nTime, nCode, nMode );
+ }
+}
+
+void Window::ImplGenerateMouseMove()
+{
+ if ( mpWindowImpl && mpWindowImpl->mpFrameData &&
+ !mpWindowImpl->mpFrameData->mnMouseMoveId )
+ mpWindowImpl->mpFrameData->mnMouseMoveId = Application::PostUserEvent( LINK( mpWindowImpl->mpFrameWindow, Window, ImplGenerateMouseMoveHdl ), nullptr, true );
+}
+
+IMPL_LINK_NOARG(Window, ImplGenerateMouseMoveHdl, void*, void)
+{
+ mpWindowImpl->mpFrameData->mnMouseMoveId = nullptr;
+ vcl::Window* pCaptureWin = ImplGetSVData()->mpWinData->mpCaptureWin;
+ if( ! pCaptureWin ||
+ (pCaptureWin->mpWindowImpl && pCaptureWin->mpWindowImpl->mpFrame == mpWindowImpl->mpFrame)
+ )
+ {
+ ImplCallMouseMove( mpWindowImpl->mpFrameData->mnMouseCode );
+ }
+}
+
+void Window::ImplInvertFocus( const tools::Rectangle& rRect )
+{
+ InvertTracking( rRect, ShowTrackFlags::Small | ShowTrackFlags::TrackWindow );
+}
+
+static bool IsWindowFocused(const WindowImpl& rWinImpl)
+{
+ if (rWinImpl.mpSysObj)
+ return true;
+
+ if (rWinImpl.mpFrameData->mbHasFocus)
+ return true;
+
+ if (rWinImpl.mbFakeFocusSet)
+ return true;
+
+ return false;
+}
+
+void Window::ImplGrabFocus( GetFocusFlags nFlags )
+{
+ // #143570# no focus for destructing windows
+ if( !mpWindowImpl || mpWindowImpl->mbInDispose )
+ return;
+
+ // some event listeners do really bad stuff
+ // => prepare for the worst
+ VclPtr<vcl::Window> xWindow( this );
+
+ // Currently the client window should always get the focus
+ // Should the border window at some point be focusable
+ // we need to change all GrabFocus() instances in VCL,
+ // e.g. in ToTop()
+
+ if ( mpWindowImpl->mpClientWindow )
+ {
+ // For a lack of design we need a little hack here to
+ // ensure that dialogs on close pass the focus back to
+ // the correct window
+ if ( mpWindowImpl->mpLastFocusWindow && (mpWindowImpl->mpLastFocusWindow.get() != this) &&
+ !(mpWindowImpl->mnDlgCtrlFlags & DialogControlFlags::WantFocus) &&
+ mpWindowImpl->mpLastFocusWindow->IsEnabled() &&
+ mpWindowImpl->mpLastFocusWindow->IsInputEnabled() &&
+ ! mpWindowImpl->mpLastFocusWindow->IsInModalMode()
+ )
+ mpWindowImpl->mpLastFocusWindow->GrabFocus();
+ else
+ mpWindowImpl->mpClientWindow->GrabFocus();
+ return;
+ }
+ else if ( mpWindowImpl->mbFrame )
+ {
+ // For a lack of design we need a little hack here to
+ // ensure that dialogs on close pass the focus back to
+ // the correct window
+ if ( mpWindowImpl->mpLastFocusWindow && (mpWindowImpl->mpLastFocusWindow.get() != this) &&
+ !(mpWindowImpl->mnDlgCtrlFlags & DialogControlFlags::WantFocus) &&
+ mpWindowImpl->mpLastFocusWindow->IsEnabled() &&
+ mpWindowImpl->mpLastFocusWindow->IsInputEnabled() &&
+ ! mpWindowImpl->mpLastFocusWindow->IsInModalMode()
+ )
+ {
+ mpWindowImpl->mpLastFocusWindow->GrabFocus();
+ return;
+ }
+ }
+
+ // If the Window is disabled, then we don't change the focus
+ if ( !IsEnabled() || !IsInputEnabled() || IsInModalMode() )
+ return;
+
+ // we only need to set the focus if it is not already set
+ // note: if some other frame is waiting for an asynchronous focus event
+ // we also have to post an asynchronous focus event for this frame
+ // which is done using ToTop
+ ImplSVData* pSVData = ImplGetSVData();
+
+ bool bAsyncFocusWaiting = false;
+ vcl::Window *pFrame = pSVData->maFrameData.mpFirstFrame;
+ while( pFrame )
+ {
+ 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;
+ }
+ 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();
+ 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();
+ vcl::Window* pNewRealWindow = pNewOverlapWindow->ImplGetWindow();
+ pNewOverlapWindow->mpWindowImpl->mbActive = true;
+ pNewOverlapWindow->Activate();
+ if ( pNewRealWindow != pNewOverlapWindow )
+ {
+ pNewRealWindow->mpWindowImpl->mbActive = true;
+ pNewRealWindow->Activate();
+ }
+ }
+
+ // call Get- and LoseFocus
+ if ( pOldFocusWindow && ! pOldFocusWindow->IsDisposed() )
+ {
+ NotifyEvent aNEvt( MouseNotifyEvent::LOSEFOCUS, pOldFocusWindow );
+ if ( !ImplCallPreNotify( aNEvt ) )
+ pOldFocusWindow->CompatLoseFocus();
+ pOldFocusWindow->ImplCallDeactivateListeners( this );
+ }
+
+ if (pSVData->mpWinData->mpFocusWin.get() == this)
+ {
+ if ( mpWindowImpl->mpSysObj )
+ {
+ mpWindowImpl->mpFrameData->mpFocusWin = this;
+ if ( !mpWindowImpl->mpFrameData->mbInSysObjFocusHdl )
+ mpWindowImpl->mpSysObj->GrabFocus();
+ }
+
+ if (pSVData->mpWinData->mpFocusWin.get() == this)
+ {
+ if ( mpWindowImpl->mpCursor )
+ mpWindowImpl->mpCursor->ImplShow();
+ mpWindowImpl->mbInFocusHdl = true;
+ mpWindowImpl->mnGetFocusFlags = nFlags;
+ // if we're changing focus due to closing a popup floating window
+ // notify the new focus window so it can restore the inner focus
+ // eg, toolboxes can select their recent active item
+ if( pOldFocusWindow &&
+ ! pOldFocusWindow->IsDisposed() &&
+ ( pOldFocusWindow->GetDialogControlFlags() & DialogControlFlags::FloatWinPopupModeEndCancel ) )
+ mpWindowImpl->mnGetFocusFlags |= GetFocusFlags::FloatWinPopupModeEndCancel;
+ NotifyEvent aNEvt( MouseNotifyEvent::GETFOCUS, this );
+ if ( !ImplCallPreNotify( aNEvt ) && !xWindow->IsDisposed() )
+ CompatGetFocus();
+ if( !xWindow->IsDisposed() )
+ ImplCallActivateListeners( (pOldFocusWindow && ! pOldFocusWindow->IsDisposed()) ? pOldFocusWindow : nullptr );
+ if( !xWindow->IsDisposed() )
+ {
+ mpWindowImpl->mnGetFocusFlags = GetFocusFlags::NONE;
+ mpWindowImpl->mbInFocusHdl = false;
+ }
+ }
+ }
+
+ ImplNewInputContext();
+
+}
+
+void Window::ImplGrabFocusToDocument( GetFocusFlags nFlags )
+{
+ vcl::Window *pWin = this;
+ while( pWin )
+ {
+ if( !pWin->GetParent() )
+ {
+ pWin->mpWindowImpl->mpFrame->GrabFocus();
+ pWin->ImplGetFrameWindow()->GetWindow( GetWindowType::Client )->ImplGrabFocus(nFlags);
+ return;
+ }
+ pWin = pWin->GetParent();
+ }
+}
+
+void Window::MouseMove( const MouseEvent& rMEvt )
+{
+ NotifyEvent aNEvt( MouseNotifyEvent::MOUSEMOVE, this, &rMEvt );
+ EventNotify(aNEvt);
+}
+
+void Window::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ NotifyEvent aNEvt( MouseNotifyEvent::MOUSEBUTTONDOWN, this, &rMEvt );
+ if (!EventNotify(aNEvt))
+ mpWindowImpl->mbMouseButtonDown = true;
+}
+
+void Window::MouseButtonUp( const MouseEvent& rMEvt )
+{
+ NotifyEvent aNEvt( MouseNotifyEvent::MOUSEBUTTONUP, this, &rMEvt );
+ if (!EventNotify(aNEvt))
+ mpWindowImpl->mbMouseButtonUp = true;
+}
+
+void Window::SetMouseTransparent( bool bTransparent )
+{
+
+ if ( mpWindowImpl->mpBorderWindow )
+ mpWindowImpl->mpBorderWindow->SetMouseTransparent( bTransparent );
+
+ if( mpWindowImpl->mpSysObj )
+ mpWindowImpl->mpSysObj->SetMouseTransparent( bTransparent );
+
+ mpWindowImpl->mbMouseTransparent = bTransparent;
+}
+
+void Window::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;
+ 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() );
+
+ if (VclPtr<vcl::Window> pWin = GetParentWithLOKNotifier())
+ {
+ PointerStyle aPointer = GetPointer();
+ // We don't map all possible pointers hence we need a default
+ OString aPointerString = "default";
+ auto aIt = vcl::gaLOKPointerMap.find(aPointer);
+ if (aIt != vcl::gaLOKPointerMap.end())
+ {
+ aPointerString = aIt->second;
+ }
+
+ // issue mouse pointer events only for document windows
+ // Doc windows' immediate parent SfxFrameViewWindow_Impl is the one with
+ // parent notifier set during initialization
+ if (GetParent()->ImplGetWindowImpl()->mbLOKParentNotifier &&
+ GetParent()->ImplGetWindowImpl()->mnLOKWindowId == 0)
+ {
+ pWin->GetLOKNotifier()->libreOfficeKitViewCallback(LOK_CALLBACK_MOUSE_POINTER, aPointerString.getStr());
+ }
+ }
+}
+
+void Window::EnableChildPointerOverwrite( bool bOverwrite )
+{
+
+ if ( mpWindowImpl->mbChildPtrOverwrite == bOverwrite )
+ return;
+
+ mpWindowImpl->mbChildPtrOverwrite = bOverwrite;
+
+ // possibly immediately move pointer
+ if ( !mpWindowImpl->mpFrameData->mbInMouseMove && ImplTestMousePointerSet() )
+ mpWindowImpl->mpFrame->SetPointer( ImplGetMousePointer() );
+}
+
+void Window::SetPointerPosPixel( const Point& rPos )
+{
+ Point aPos = ImplOutputToFrame( rPos );
+ const OutputDevice *pOutDev = GetOutDev();
+ if( pOutDev->HasMirroredGraphics() )
+ {
+ if( !IsRTLEnabled() )
+ {
+ pOutDev->ReMirror( aPos );
+ }
+ // mirroring is required here, SetPointerPos bypasses SalGraphics
+ aPos.setX( mpGraphics->mirror2( aPos.X(), this ) );
+ }
+ else if( ImplIsAntiparallel() )
+ {
+ pOutDev->ReMirror( aPos );
+ }
+ mpWindowImpl->mpFrame->SetPointerPos( aPos.X(), aPos.Y() );
+}
+
+void Window::SetLastMousePos(const Point& rPos)
+{
+ // Do this conversion, so when GetPointerPosPixel() calls
+ // ImplFrameToOutput(), we get back the original position.
+ Point aPos = ImplOutputToFrame(rPos);
+ mpWindowImpl->mpFrameData->mnLastMouseX = aPos.X();
+ mpWindowImpl->mpFrameData->mnLastMouseY = aPos.Y();
+}
+
+Point Window::GetPointerPosPixel()
+{
+
+ Point aPos( mpWindowImpl->mpFrameData->mnLastMouseX, mpWindowImpl->mpFrameData->mnLastMouseY );
+ if( ImplIsAntiparallel() )
+ {
+ const OutputDevice *pOutDev = GetOutDev();
+ pOutDev->ReMirror( aPos );
+ }
+ return ImplFrameToOutput( aPos );
+}
+
+Point Window::GetLastPointerPosPixel()
+{
+
+ Point aPos( mpWindowImpl->mpFrameData->mnBeforeLastMouseX, mpWindowImpl->mpFrameData->mnBeforeLastMouseY );
+ if( ImplIsAntiparallel() )
+ {
+ const OutputDevice *pOutDev = GetOutDev();
+ pOutDev->ReMirror( aPos );
+ }
+ return ImplFrameToOutput( aPos );
+}
+
+void Window::ShowPointer( bool bVisible )
+{
+
+ if ( mpWindowImpl->mbNoPtrVisible != !bVisible )
+ {
+ mpWindowImpl->mbNoPtrVisible = !bVisible;
+
+ // possibly immediately move pointer
+ if ( !mpWindowImpl->mpFrameData->mbInMouseMove && ImplTestMousePointerSet() )
+ mpWindowImpl->mpFrame->SetPointer( ImplGetMousePointer() );
+ }
+}
+
+Window::PointerState Window::GetPointerState()
+{
+ PointerState aState;
+ aState.mnState = 0;
+
+ if (mpWindowImpl->mpFrame)
+ {
+ SalFrame::SalPointerState aSalPointerState = mpWindowImpl->mpFrame->GetPointerState();
+ if( ImplIsAntiparallel() )
+ {
+ const OutputDevice *pOutDev = GetOutDev();
+ pOutDev->ReMirror( aSalPointerState.maPos );
+ }
+ aState.maPos = ImplFrameToOutput( aSalPointerState.maPos );
+ aState.mnState = aSalPointerState.mnState;
+ }
+ return aState;
+}
+
+bool Window::IsMouseOver() const
+{
+ return ImplGetWinData()->mbMouseOver;
+}
+
+void Window::EnterWait()
+{
+
+ mpWindowImpl->mnWaitCount++;
+
+ if ( mpWindowImpl->mnWaitCount == 1 )
+ {
+ // possibly immediately move pointer
+ if ( !mpWindowImpl->mpFrameData->mbInMouseMove && ImplTestMousePointerSet() )
+ mpWindowImpl->mpFrame->SetPointer( ImplGetMousePointer() );
+ }
+}
+
+void Window::LeaveWait()
+{
+
+ if ( mpWindowImpl->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
+
+ if( mpWindowImpl->mpFrameData )
+ {
+ if( ! mpWindowImpl->mpFrameData->mxDragSource.is() )
+ {
+ try
+ {
+ Reference< XComponentContext > xContext( comphelper::getProcessComponentContext() );
+ const SystemEnvData * pEnvData = GetSystemData();
+
+ if( pEnvData )
+ {
+ Sequence< Any > aDragSourceAL( 2 ), aDropTargetAL( 2 );
+ OUString aDragSourceSN, aDropTargetSN;
+#if defined(_WIN32)
+ aDragSourceSN = "com.sun.star.datatransfer.dnd.OleDragSource";
+ aDropTargetSN = "com.sun.star.datatransfer.dnd.OleDropTarget";
+ aDragSourceAL[ 1 ] <<= static_cast<sal_uInt64>( reinterpret_cast<sal_IntPtr>(pEnvData->hWnd) );
+ aDropTargetAL[ 0 ] <<= static_cast<sal_uInt64>( reinterpret_cast<sal_IntPtr>(pEnvData->hWnd) );
+#elif defined MACOSX
+ /* FIXME: macOS specific dnd interface does not exist! *
+ * Using Windows based dnd as a temporary solution */
+ aDragSourceSN = "com.sun.star.datatransfer.dnd.OleDragSource";
+ aDropTargetSN = "com.sun.star.datatransfer.dnd.OleDropTarget";
+ aDragSourceAL[ 1 ] <<= static_cast<sal_uInt64>( reinterpret_cast<sal_IntPtr>(pEnvData->mpNSView) );
+ aDropTargetAL[ 0 ] <<= static_cast<sal_uInt64>( reinterpret_cast<sal_IntPtr>(pEnvData->mpNSView) );
+#elif HAVE_FEATURE_X11
+ aDragSourceSN = "com.sun.star.datatransfer.dnd.X11DragSource";
+ aDropTargetSN = "com.sun.star.datatransfer.dnd.X11DropTarget";
+
+ aDragSourceAL[ 0 ] <<= Application::GetDisplayConnection();
+ aDragSourceAL[ 1 ] <<= pEnvData->aShellWindow;
+ aDropTargetAL[ 0 ] <<= Application::GetDisplayConnection();
+ aDropTargetAL[ 1 ] <<= pEnvData->aShellWindow;
+#endif
+ if( !aDragSourceSN.isEmpty() )
+ mpWindowImpl->mpFrameData->mxDragSource.set(
+ xContext->getServiceManager()->createInstanceWithArgumentsAndContext( aDragSourceSN, aDragSourceAL, xContext ),
+ UNO_QUERY );
+
+ if( !aDropTargetSN.isEmpty() )
+ mpWindowImpl->mpFrameData->mxDropTarget.set(
+ xContext->getServiceManager()->createInstanceWithArgumentsAndContext( aDropTargetSN, aDropTargetAL, xContext ),
+ UNO_QUERY );
+ }
+ }
+
+ // createInstance can throw any exception
+ catch (const Exception&)
+ {
+ // release all instances
+ mpWindowImpl->mpFrameData->mxDropTarget.clear();
+ mpWindowImpl->mpFrameData->mxDragSource.clear();
+ }
+ }
+
+ return mpWindowImpl->mpFrameData->mxDragSource;
+ }
+#endif
+ return Reference< css::datatransfer::dnd::XDragSource > ();
+}
+
+Reference< css::datatransfer::dnd::XDragGestureRecognizer > Window::GetDragGestureRecognizer()
+{
+ return Reference< css::datatransfer::dnd::XDragGestureRecognizer > ( GetDropTarget(), UNO_QUERY );
+}
+
+} /* namespace vcl */
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/paint.cxx b/vcl/source/window/paint.cxx
new file mode 100644
index 000000000..4530da8e0
--- /dev/null
+++ b/vcl/source/window/paint.cxx
@@ -0,0 +1,1844 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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");
+
+ PushFlags nFlags = PushFlags::NONE;
+ nFlags |= PushFlags::CLIPREGION;
+ nFlags |= PushFlags::FILLCOLOR;
+ nFlags |= PushFlags::FONT;
+ nFlags |= PushFlags::LINECOLOR;
+ nFlags |= PushFlags::MAPMODE;
+ maSettings = pFrameData->mpBuffer->GetSettings();
+ nFlags |= PushFlags::REFPOINT;
+ nFlags |= PushFlags::TEXTCOLOR;
+ nFlags |= PushFlags::TEXTLINECOLOR;
+ nFlags |= PushFlags::OVERLINECOLOR;
+ nFlags |= PushFlags::TEXTFILLCOLOR;
+ nFlags |= PushFlags::TEXTALIGN;
+ nFlags |= PushFlags::RASTEROP;
+ nFlags |= PushFlags::TEXTLAYOUTMODE;
+ nFlags |= PushFlags::TEXTLANGUAGE;
+ pFrameData->mpBuffer->Push(nFlags);
+ pFrameData->mpBuffer->SetClipRegion(pWindow->GetClipRegion());
+ pFrameData->mpBuffer->SetFillColor(pWindow->GetFillColor());
+ pFrameData->mpBuffer->SetFont(pWindow->GetFont());
+ pFrameData->mpBuffer->SetLineColor(pWindow->GetLineColor());
+ pFrameData->mpBuffer->SetMapMode(pWindow->GetMapMode());
+ pFrameData->mpBuffer->SetRefPoint(pWindow->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(pWindow->GetRasterOp());
+ pFrameData->mpBuffer->SetLayoutMode(pWindow->GetLayoutMode());
+ pFrameData->mpBuffer->SetDigitLanguage(pWindow->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()
+{
+ 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->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;
+}
+}
+
+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* pWinChildClipRegion = m_pWindow->ImplGetWinChildClipRegion();
+ ImplFrameData* pFrameData = m_pWindow->mpWindowImpl->mpFrameData;
+ if (pWindowImpl->mnPaintFlags & ImplPaintFlags::PaintAll || pFrameData->mbInBufferedPaint)
+ {
+ pWindowImpl->maInvalidateRegion = *pWinChildClipRegion;
+ }
+ 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(*pWinChildClipRegion);
+ }
+ pWindowImpl->mnPaintFlags = ImplPaintFlags::NONE;
+ if (!pWindowImpl->maInvalidateRegion.IsEmpty())
+ {
+#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);
+ // Restore bitmap background if it was lost.
+ if (aBackground.IsBitmap() && !m_pWindow->GetBackground().IsBitmap())
+ {
+ m_pWindow->SetBackground(aBackground);
+ }
+ m_pWindow->PushPaintHelper(this, *m_pWindow);
+ m_pWindow->Paint(*m_pWindow, 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, 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(PushFlags::FILLCOLOR | 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());
+
+ 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 (ImplIsAntiparallel())
+ {
+ rRenderContext.ReMirror(aPaintRect);
+ rRenderContext.ReMirror(rPaintRegion);
+ }
+ aPaintRect = 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;
+ 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(*this);
+
+ 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 (mpWindowImpl->mbPaintDisabled)
+ {
+ 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(*this);
+
+ 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(*this);
+}
+
+void Window::ImplCallOverlapPaint()
+{
+ // 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( 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
+ {
+ tools::Rectangle aRect( Point( mnOutOffX, mnOutOffY ), Size( mnOutWidth, mnOutHeight ) );
+ vcl::Region aRegion( aRect );
+ if ( pRegion )
+ {
+ // RTL: remirror region before intersecting it
+ if ( 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,
+ long nHorzScroll, 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,
+ long nHorzScroll, 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() )
+ {
+ 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 )
+ {
+ tools::Rectangle aRect( Point( mnOutOffX, mnOutOffY ), Size( mnOutWidth, mnOutHeight ) );
+ aChildRegion = aRect;
+ }
+ vcl::Window* pChild = mpWindowImpl->mpFirstChild;
+ while ( pChild )
+ {
+ pChild->Invalidate( aChildRegion, InvalidateFlags::Children | InvalidateFlags::NoTransparent );
+ pChild = pChild->mpWindowImpl->mpNext;
+ }
+ }
+ if ( mpWindowImpl->mnPaintFlags & ImplPaintFlags::PaintAll )
+ {
+ tools::Rectangle aRect( Point( mnOutOffX, mnOutOffY ), Size( mnOutWidth, mnOutHeight ) );
+ mpWindowImpl->maInvalidateRegion = aRect;
+ }
+ 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
+ {
+ tools::Rectangle aRect( Point( mnOutOffX, mnOutOffY ), Size( mnOutWidth, mnOutHeight ) );
+ vcl::Region aRegion( aRect );
+ 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->mbReallyVisible )
+ return;
+
+ bool bFlush = false;
+ if ( mpWindowImpl->mpFrameWindow->mpWindowImpl->mbPaintFrame )
+ {
+ Point aPoint( 0, 0 );
+ vcl::Region aRegion( tools::Rectangle( aPoint, Size( mnOutWidth, mnOutHeight ) ) );
+ 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 )
+ 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() )
+ {
+ tools::Rectangle aRect( Point( mnOutOffX, mnOutOffY ), Size( mnOutWidth, mnOutHeight ) );
+ vcl::Region aRegion( aRect );
+ 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();
+
+ //long nX;
+ //long nY;
+ //long nWidth;
+ //long nHeight;
+ //sal_uLong nRectCount;
+ //ImplRegionInfo aInfo;
+ //sal_Bool bRegionRect;
+
+ //nRectCount = mpWindowImpl->maWinRegion.GetRectCount();
+ //mpWindowImpl->mpFrame->BeginSetClipRegion( nRectCount );
+ //bRegionRect = mpWindowImpl->maWinRegion.ImplGetFirstRect( aInfo, nX, nY, nWidth, nHeight );
+ //while ( bRegionRect )
+ //{
+ // mpWindowImpl->mpFrame->UnionClipRegion( nX, nY, nWidth, nHeight );
+ // bRegionRect = mpWindowImpl->maWinRegion.ImplGetNextRect( aInfo, nX, nY, nWidth, nHeight );
+ //}
+ //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() )
+ {
+ tools::Rectangle aRect( Point( mnOutOffX, mnOutOffY ), Size( mnOutWidth, mnOutHeight ) );
+ vcl::Region aRegion( aRect );
+ ImplInvalidateParentFrameRegion( aRegion );
+ }
+ }
+}
+
+vcl::Region Window::GetPaintRegion() const
+{
+
+ if ( mpWindowImpl->mpPaintRegion )
+ {
+ vcl::Region aRegion = *mpWindowImpl->mpPaintRegion;
+ aRegion.Move( -mnOutOffX, -mnOutOffY );
+ return PixelToLogic( aRegion );
+ }
+ else
+ {
+ vcl::Region aPaintRegion(true);
+ return aPaintRegion;
+ }
+}
+
+void Window::Invalidate( InvalidateFlags nFlags )
+{
+ if ( !comphelper::LibreOfficeKit::isActive() && (!IsDeviceOutputNecessary() || !mnOutWidth || !mnOutHeight) )
+ return;
+
+ ImplInvalidate( nullptr, nFlags );
+ LogicInvalidate(nullptr);
+}
+
+void Window::Invalidate( const tools::Rectangle& rRect, InvalidateFlags nFlags )
+{
+ if ( !comphelper::LibreOfficeKit::isActive() && (!IsDeviceOutputNecessary() || !mnOutWidth || !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() && (!IsDeviceOutputNecessary() || !mnOutWidth || !mnOutHeight) )
+ return;
+
+ if ( rRegion.IsNull() )
+ {
+ ImplInvalidate( nullptr, nFlags );
+ LogicInvalidate(nullptr);
+ }
+ else
+ {
+ vcl::Region aRegion = 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;
+ if (pRectangle)
+ aPayload.emplace_back("rectangle", pRectangle->toString());
+ else
+ {
+ const tools::Rectangle aRect(Point(0, 0), aSize);
+ 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() && (!IsDeviceOutputNecessary() || !mnOutWidth || !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->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, Size( mnOutWidth, mnOutHeight ) ) );
+ 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()->mpWindowImpl->mpFirstOverlap;
+ 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 )
+ 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);
+
+ Size aSize(GetOutputSizePixel());
+ pDevice->SetOutputSizePixel(aSize);
+
+ vcl::Font aCopyFont = GetFont();
+ pDevice->SetFont(aCopyFont);
+
+ pDevice->SetTextColor(GetTextColor());
+ if (IsLineColor())
+ pDevice->SetLineColor(GetLineColor());
+ else
+ pDevice->SetLineColor();
+
+ if (IsFillColor())
+ pDevice->SetFillColor(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(GetRasterOp());
+
+ tools::Rectangle aPaintRect(Point(), GetOutputSizePixel());
+
+ vcl::Region aClipRegion(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);
+
+ // 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() )
+ {
+ long nDeltaX = pChild->mnOutOffX - mnOutOffX;
+ long nDeltaY = pChild->mnOutOffY - mnOutOffY;
+
+ Point aPos( i_rPos );
+ aPos += Point(nDeltaX, nDeltaY);
+
+ pChild->ImplPaintToDevice( i_pTargetOutDev, aPos );
+ }
+ }
+ return;
+ }
+
+
+ bool bRVisible = mpWindowImpl->mbReallyVisible;
+ mpWindowImpl->mbReallyVisible = mpWindowImpl->mbVisible;
+ bool bDevOutput = mbDevOutput;
+ mbDevOutput = true;
+
+ const OutputDevice *pOutDev = GetOutDev();
+ long nOldDPIX = pOutDev->GetDPIX();
+ long nOldDPIY = pOutDev->GetDPIY();
+ mnDPIX = i_pTargetOutDev->GetDPIX();
+ mnDPIY = i_pTargetOutDev->GetDPIY();
+ bool bOutput = IsOutputEnabled();
+ EnableOutput();
+
+ double fScaleX = 1;
+ double fScaleY = 1;
+ bool bNeedsScaling = false;
+ if(comphelper::LibreOfficeKit::isActive())
+ {
+ if(GetMapMode().GetMapUnit() != MapUnit::MapPixel &&
+ // Some of the preview windows (SvxPreviewBase) uses different painting (drawinglayer primitives)
+ // For these preview we don't need to scale even though the unit is not pixel.
+ GetMapMode().GetMapUnit() != MapUnit::Map100thMM)
+ {
+ bNeedsScaling = true;
+ // 1000.0 is used to reduce rounding imprecision (Size uses integers)
+ Size aLogicSize = PixelToLogic(Size(1000.0, 1000.0));
+ fScaleX = aLogicSize.Width() / 1000.0;
+ fScaleY = aLogicSize.Height() / 1000.0;
+ }
+ }
+ else
+ { // TODO: Above scaling was added for LOK only, would be good to check how it works in other use cases
+ SAL_WARN_IF( GetMapMode().GetMapUnit() != MapUnit::MapPixel, "vcl.window", "MapMode must be PIXEL based" );
+ if ( GetMapMode().GetMapUnit() != MapUnit::MapPixel )
+ return;
+ }
+
+ // preserve graphicsstate
+ Push();
+ vcl::Region aClipRegion( GetClipRegion() );
+ SetClipRegion();
+
+ GDIMetaFile* pOldMtf = GetConnectMetaFile();
+ GDIMetaFile aMtf;
+ SetConnectMetaFile( &aMtf );
+
+ // put a push action to metafile
+ Push();
+ // copy graphics state to metafile
+ vcl::Font aCopyFont = GetFont();
+ if( nOldDPIX != mnDPIX || nOldDPIY != mnDPIY )
+ {
+ aCopyFont.SetFontHeight( aCopyFont.GetFontHeight() * mnDPIY / nOldDPIY );
+ aCopyFont.SetAverageFontWidth( aCopyFont.GetAverageFontWidth() * mnDPIX / nOldDPIX );
+ }
+ SetFont( aCopyFont );
+ SetTextColor( GetTextColor() );
+ if( IsLineColor() )
+ SetLineColor( GetLineColor() );
+ else
+ SetLineColor();
+ if( IsFillColor() )
+ SetFillColor( GetFillColor() );
+ else
+ SetFillColor();
+ if( IsTextLineColor() )
+ SetTextLineColor( GetTextLineColor() );
+ else
+ SetTextLineColor();
+ if( IsOverlineColor() )
+ SetOverlineColor( GetOverlineColor() );
+ else
+ SetOverlineColor();
+ if( IsTextFillColor() )
+ SetTextFillColor( GetTextFillColor() );
+ else
+ SetTextFillColor();
+ SetTextAlign( GetTextAlign() );
+ SetRasterOp( GetRasterOp() );
+ if( IsRefPoint() )
+ SetRefPoint( GetRefPoint() );
+ else
+ SetRefPoint();
+ SetLayoutMode( GetLayoutMode() );
+ SetDigitLanguage( GetDigitLanguage() );
+
+ tools::Rectangle aPaintRect;
+ if(bNeedsScaling)
+ {
+ aPaintRect = tools::Rectangle( Point( 0, 0 ),
+ Size(GetOutputSizePixel().Width() * fScaleX, GetOutputSizePixel().Height() * fScaleY) );
+ }
+ else
+ {
+ aPaintRect = tools::Rectangle( Point( 0, 0 ), GetOutputSizePixel() );
+ }
+ aClipRegion.Intersect( aPaintRect );
+ SetClipRegion( aClipRegion );
+
+ // do the actual paint
+
+ // background
+ if( ! IsPaintTransparent() && IsBackground() && ! (GetParentClipMode() & ParentClipMode::NoClip ) )
+ {
+ Erase(*this);
+ if(bNeedsScaling)
+ aMtf.Scale(fScaleX, fScaleY);
+ }
+ // foreground
+ Paint(*this, aPaintRect);
+ // put a pop action to metafile
+ Pop();
+
+ SetConnectMetaFile( pOldMtf );
+ EnableOutput( bOutput );
+ mpWindowImpl->mbReallyVisible = bRVisible;
+
+ // paint metafile to VDev
+ VclPtrInstance<VirtualDevice> pMaskedDevice(*i_pTargetOutDev,
+ DeviceFormat::DEFAULT,
+ DeviceFormat::DEFAULT);
+
+ if(bNeedsScaling)
+ pMaskedDevice->SetMapMode( GetMapMode() );
+ 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() )
+ {
+ long nDeltaX = pChild->mnOutOffX - mnOutOffX;
+
+ if( pOutDev->HasMirroredGraphics() )
+ nDeltaX = mnOutWidth - nDeltaX - pChild->mnOutWidth;
+ long nDeltaY = pChild->GetOutOffYPixel() - GetOutOffYPixel();
+ Point aPos( i_rPos );
+ Point aDelta( nDeltaX, nDeltaY );
+ aPos += aDelta;
+ pChild->ImplPaintToDevice( i_pTargetOutDev, aPos );
+ }
+ }
+
+ // restore graphics state
+ Pop();
+
+ EnableOutput( bOutput );
+ mpWindowImpl->mbReallyVisible = bRVisible;
+ mbDevOutput = bDevOutput;
+ mnDPIX = nOldDPIX;
+ mnDPIY = nOldDPIY;
+}
+
+void Window::PaintToDevice(OutputDevice* pDev, const Point& rPos)
+{
+ 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 (!IsDeviceOutputNecessary() || 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 (mbBackground && !bNativeOK)
+ {
+ RasterOp eRasterOp = GetRasterOp();
+ if (eRasterOp != RasterOp::OverPaint)
+ SetRasterOp(RasterOp::OverPaint);
+ rRenderContext.DrawWallpaper(0, 0, mnOutWidth, mnOutHeight, maBackground);
+ if (eRasterOp != RasterOp::OverPaint)
+ rRenderContext.SetRasterOp(eRasterOp);
+ }
+
+ if (mpAlphaVDev)
+ mpAlphaVDev->Erase();
+}
+
+void Window::ImplScroll( const tools::Rectangle& rRect,
+ long nHorzScroll, long nVertScroll, ScrollFlags nFlags )
+{
+ if ( !IsDeviceOutputNecessary() )
+ return;
+
+ nHorzScroll = ImplLogicWidthToDevicePixel( nHorzScroll );
+ nVertScroll = ImplLogicHeightToDevicePixel( nVertScroll );
+
+ if ( !nHorzScroll && !nVertScroll )
+ return;
+
+ 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 = 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 (!SupportsDoubleBuffering())
+ {
+ // There will be no CopyArea() call below, so invalidate the
+ // whole visible area, not only the smaller one that was just
+ // scrolled in.
+ aWinInvalidateRegion.Exclude(aDestRect);
+ }
+
+ aInvalidateRegion.Union(aWinInvalidateRegion);
+
+ Point aPoint( mnOutOffX, mnOutOffY );
+ vcl::Region aRegion( tools::Rectangle( aPoint, Size( mnOutWidth, mnOutHeight ) ) );
+ if ( nFlags & ScrollFlags::Clip )
+ aRegion.Intersect( rRect );
+ if ( mpWindowImpl->mbWinRegion )
+ aRegion.Intersect( ImplPixelToDevicePixel( mpWindowImpl->maWinRegion ) );
+
+ aRegion.Exclude( aInvalidateRegion );
+
+ ImplClipBoundaries( aRegion, false, true );
+ if ( !bScrollChildren )
+ {
+ if ( nOrgFlags & ScrollFlags::NoChildren )
+ ImplClipAllChildren( aRegion );
+ else
+ ImplClipChildren( aRegion );
+ }
+ if ( mbClipRegion && (nFlags & ScrollFlags::UseClipRegion) )
+ aRegion.Intersect( 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 && !SupportsDoubleBuffering())
+ {
+ 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(),
+ this );
+ }
+#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/popupmenuwindow.cxx b/vcl/source/window/popupmenuwindow.cxx
new file mode 100644
index 000000000..f85298e63
--- /dev/null
+++ b/vcl/source/window/popupmenuwindow.cxx
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <vcl/popupmenuwindow.hxx>
+
+#include <limits>
+
+struct PopupMenuFloatingWindow::ImplData
+{
+ sal_uInt16 mnMenuStackLevel; // Store the stack level of a popup menu. 0 = top-level menu.
+
+ ImplData();
+};
+
+PopupMenuFloatingWindow::ImplData::ImplData() :
+ mnMenuStackLevel( ::std::numeric_limits<sal_uInt16>::max() )
+{
+}
+
+PopupMenuFloatingWindow::PopupMenuFloatingWindow( vcl::Window* pParent ) :
+ FloatingWindow(pParent, WB_SYSTEMFLOATWIN | WB_SYSTEMWINDOW | WB_NOBORDER ),
+ mpImplData(new ImplData)
+{
+}
+
+PopupMenuFloatingWindow::~PopupMenuFloatingWindow()
+{
+ disposeOnce();
+}
+
+void PopupMenuFloatingWindow::dispose()
+{
+ mpImplData.reset();
+ FloatingWindow::dispose();
+}
+
+sal_uInt16 PopupMenuFloatingWindow::GetMenuStackLevel() const
+{
+ return mpImplData->mnMenuStackLevel;
+}
+
+void PopupMenuFloatingWindow::SetMenuStackLevel( sal_uInt16 nLevel )
+{
+ mpImplData->mnMenuStackLevel = nLevel;
+}
+
+bool PopupMenuFloatingWindow::IsPopupMenu() const
+{
+ return mpImplData->mnMenuStackLevel != ::std::numeric_limits<sal_uInt16>::max();
+}
+
+bool PopupMenuFloatingWindow::isPopupMenu(const vcl::Window *pWindow)
+{
+ const PopupMenuFloatingWindow* pChild = dynamic_cast<const PopupMenuFloatingWindow*>(pWindow);
+ return pChild && pChild->IsPopupMenu();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/printdlg.cxx b/vcl/source/window/printdlg.cxx
new file mode 100644
index 000000000..6d73b1449
--- /dev/null
+++ b/vcl/source/window/printdlg.cxx
@@ -0,0 +1,2239 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <printdlg.hxx>
+#include <svdata.hxx>
+#include <strings.hrc>
+#include <bitmaps.hlst>
+
+#include <vcl/commandevent.hxx>
+#include <vcl/naturalsort.hxx>
+#include <vcl/print.hxx>
+#include <vcl/wall.hxx>
+#include <vcl/decoview.hxx>
+#include <configsettings.hxx>
+#include <vcl/help.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/virdev.hxx>
+
+#include <unotools/localedatawrapper.hxx>
+
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+#include <rtl/ustrbuf.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;
+ }
+}
+
+MoreOptionsDialog::MoreOptionsDialog(PrintDialog* i_pParent)
+ : GenericDialogController(i_pParent->getDialog(), "vcl/ui/moreoptionsdialog.ui", "MoreOptionsDialog")
+ , mpParent( i_pParent )
+ , mxOKButton(m_xBuilder->weld_button("ok"))
+ , mxCancelButton(m_xBuilder->weld_button("cancel"))
+ , mxSingleJobsBox(m_xBuilder->weld_check_button("singlejobs"))
+{
+ mxSingleJobsBox->set_active( mpParent->isSingleJobs() );
+
+ mxOKButton->connect_clicked( LINK( this, MoreOptionsDialog, ClickHdl ) );
+ mxCancelButton->connect_clicked( LINK( this, MoreOptionsDialog, ClickHdl ) );
+}
+
+MoreOptionsDialog::~MoreOptionsDialog()
+{
+}
+
+IMPL_LINK (MoreOptionsDialog, ClickHdl, weld::Button&, rButton, void)
+{
+ if (&rButton == mxOKButton.get())
+ {
+ mpParent->mbSingleJobs = mxSingleJobsBox->get_active();
+ m_xDialog->response(RET_OK);
+ }
+ else if (&rButton == mxCancelButton.get())
+ {
+ m_xDialog->response(RET_CANCEL);
+ }
+}
+
+PrintDialog::PrintPreviewWindow::PrintPreviewWindow(PrintDialog* pDialog)
+ : mpDialog(pDialog)
+ , maMtf()
+ , maOrigSize( 10, 10 )
+ , maPreviewSize()
+ , mnDPIX(Application::GetDefaultDevice()->GetDPIX())
+ , mnDPIY(Application::GetDefaultDevice()->GetDPIY())
+ , maPreviewBitmap()
+ , maReplacementString()
+ , mbGreyscale( false )
+{
+}
+
+PrintDialog::PrintPreviewWindow::~PrintPreviewWindow()
+{
+}
+
+void PrintDialog::PrintPreviewWindow::Resize()
+{
+ Size aNewSize(GetOutputSizePixel());
+ 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( long(aScaledSize.Width()*fScale) );
+ aScaledSize.setHeight( long(aScaledSize.Height()*fScale) );
+
+ maPreviewSize = aScaledSize;
+
+ // check and evtl. recreate preview bitmap
+ preparePreviewBitmap();
+}
+
+void PrintDialog::PrintPreviewWindow::SetDrawingArea(weld::DrawingArea* pDrawingArea)
+{
+ pDrawingArea->set_size_request(pDrawingArea->get_approximate_digit_width() * 45,
+ pDrawingArea->get_text_height() * 30);
+ CustomWidgetController::SetDrawingArea(pDrawingArea);
+}
+
+void PrintDialog::PrintPreviewWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&)
+{
+ rRenderContext.Push();
+ if (vcl::Window* pDefaultDevice = dynamic_cast<vcl::Window*>(Application::GetDefaultDevice()))
+ {
+ Font aFont(rRenderContext.GetSettings().GetStyleSettings().GetLabelFont());
+ pDefaultDevice->SetPointFont(rRenderContext, aFont);
+ }
+
+ 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);
+ 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,
+ const OUString& 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());
+ MapUnit eUnit = MapUnit::MapMM;
+ int nDigits = 0;
+ if( rLocWrap.getMeasurementSystemEnum() == MeasurementSystem::US )
+ {
+ eUnit = MapUnit::Map100thInch;
+ nDigits = 2;
+ }
+ Size aLogicPaperSize(OutputDevice::LogicToLogic(i_rOrigSize, MapMode(MapUnit::Map100thMM), MapMode(eUnit)));
+ OUString aNumText( rLocWrap.getNum( aLogicPaperSize.Width(), nDigits ) );
+ OUStringBuffer aBuf;
+ aBuf.append( aNumText )
+ .append( u' ' );
+ aBuf.appendAscii( eUnit == MapUnit::MapMM ? "mm" : "in" );
+ if( !i_rPaperName.isEmpty() )
+ {
+ aBuf.append( " (" );
+ aBuf.append( i_rPaperName );
+ aBuf.append( ')' );
+ }
+ maHorzText = aBuf.makeStringAndClear();
+
+ aNumText = rLocWrap.getNum( aLogicPaperSize.Height(), nDigits );
+ aBuf.append( aNumText )
+ .append( u' ' );
+ aBuf.appendAscii( eUnit == MapUnit::MapMM ? "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 and render to it
+ ScopedVclPtrInstance<VirtualDevice> pPrerenderVDev(*Application::GetDefaultDevice());
+ pPrerenderVDev->SetOutputSizePixel(aScaledSize, false);
+ pPrerenderVDev->SetReferenceDevice( mnDPIX, mnDPIY );
+ pPrerenderVDev->EnableOutput();
+ pPrerenderVDev->SetBackground( Wallpaper(COL_WHITE) );
+
+ GDIMetaFile aMtf( maMtf );
+
+ Size aVDevSize( pPrerenderVDev->GetOutputSizePixel() );
+ const Size aLogicSize( pPrerenderVDev->PixelToLogic( aVDevSize, 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());
+
+ pPrerenderVDev->Erase();
+ pPrerenderVDev->Push();
+ pPrerenderVDev->SetMapMode(MapMode(MapUnit::Map100thMM));
+ DrawModeFlags nOldDrawMode = pPrerenderVDev->GetDrawMode();
+ if( mbGreyscale )
+ pPrerenderVDev->SetDrawMode( pPrerenderVDev->GetDrawMode() |
+ ( DrawModeFlags::GrayLine | DrawModeFlags::GrayFill | DrawModeFlags::GrayText |
+ DrawModeFlags::GrayBitmap | DrawModeFlags::GrayGradient ) );
+ aMtf.WindStart();
+ aMtf.Scale( fScale, fScale );
+ aMtf.WindStart();
+
+ const AntialiasingFlags nOriginalAA(pPrerenderVDev->GetAntialiasing());
+ pPrerenderVDev->SetAntialiasing(nOriginalAA | AntialiasingFlags::EnableB2dDraw);
+ aMtf.Play( pPrerenderVDev.get(), Point( 0, 0 ), aLogicSize );
+ pPrerenderVDev->SetAntialiasing(nOriginalAA);
+
+ pPrerenderVDev->Pop();
+
+ pPrerenderVDev->SetMapMode(MapMode(MapUnit::MapPixel));
+
+ maPreviewBitmap = pPrerenderVDev->GetBitmapEx(Point(0, 0), aVDevSize);
+
+ pPrerenderVDev->SetDrawMode( nOldDrawMode );
+}
+
+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;
+ long nFontHeight = long(24.0 * fScale) - 3;
+ if (nFontHeight < 5)
+ nFontHeight = 5;
+ aFont.SetFontSize(Size( 0, nFontHeight));
+ rRenderContext.SetFont(aFont);
+ long nTextHeight = rRenderContext.GetTextHeight();
+ for (int i = 0; i < nPages; i++)
+ {
+ OUString aPageText(OUString::number(i + 1));
+ int nX = 0, nY = 0;
+ switch (mnOrderMode)
+ {
+ case NupOrderType::LRTB:
+ nX = (i % mnColumns);
+ nY = (i / mnColumns);
+ break;
+ case NupOrderType::TBLR:
+ nX = (i / mnRows);
+ nY = (i % mnRows);
+ break;
+ case NupOrderType::RLTB:
+ nX = mnColumns - 1 - (i % mnColumns);
+ nY = (i / mnColumns);
+ break;
+ case NupOrderType::TBRL:
+ nX = mnColumns - 1 - (i / mnRows);
+ nY = (i % mnRows);
+ break;
+ }
+ Size aTextSize(rRenderContext.GetTextWidth(aPageText), nTextHeight);
+ int nDeltaX = (aSubSize.Width() - aTextSize.Width()) / 2;
+ int nDeltaY = (aSubSize.Height() - aTextSize.Height()) / 2;
+ rRenderContext.DrawText(Point(nX * aSubSize.Width() + nDeltaX,
+ nY * aSubSize.Height() + nDeltaY), aPageText);
+ }
+ DecorationView aDecorationView(&rRenderContext);
+ aDecorationView.DrawFrame(tools::Rectangle(Point(0, 0), aOutSize), DrawFrameStyle::Group);
+}
+
+Size const & PrintDialog::getJobPageSize()
+{
+ if( maFirstPageSize.IsEmpty() )
+ {
+ maFirstPageSize = maNupPortraitSize;
+ GDIMetaFile aMtf;
+ if( maPController->getPageCountProtected() > 0 )
+ {
+ PrinterController::PageSize aPageSize = maPController->getPageFile( 0, aMtf, true );
+ maFirstPageSize = aPageSize.aSize;
+ }
+ }
+ return maFirstPageSize;
+}
+
+PrintDialog::PrintDialog(weld::Window* i_pWindow, const std::shared_ptr<PrinterController>& i_rController)
+ : GenericDialogController(i_pWindow, "vcl/ui/printdialog.ui", "PrintDialog")
+ , maPController( i_rController )
+ , mxTabCtrl(m_xBuilder->weld_notebook("tabcontrol"))
+ , mxScrolledWindow(m_xBuilder->weld_scrolled_window("scrolledwindow"))
+ , mxPageLayoutFrame(m_xBuilder->weld_frame("layoutframe"))
+ , mxPrinters(m_xBuilder->weld_combo_box("printersbox"))
+ , mxStatusTxt(m_xBuilder->weld_label("status"))
+ , mxSetupButton(m_xBuilder->weld_button("setup"))
+ , mxCopyCountField(m_xBuilder->weld_spin_button("copycount"))
+ , mxCollateBox(m_xBuilder->weld_check_button("collate"))
+ , mxCollateImage(m_xBuilder->weld_image("collateimage"))
+ , mxPageRangeEdit(m_xBuilder->weld_entry("pagerange"))
+ , mxPageRangesRadioButton(m_xBuilder->weld_radio_button("rbRangePages"))
+ , mxPaperSidesBox(m_xBuilder->weld_combo_box("sidesbox"))
+ , mxReverseOrderBox(m_xBuilder->weld_check_button("reverseorder"))
+ , mxOKButton(m_xBuilder->weld_button("ok"))
+ , mxCancelButton(m_xBuilder->weld_button("cancel"))
+ , mxHelpButton(m_xBuilder->weld_button("help"))
+ , mxMoreOptionsBtn(m_xBuilder->weld_button("moreoptionsbtn"))
+ , mxBackwardBtn(m_xBuilder->weld_button("backward"))
+ , mxForwardBtn(m_xBuilder->weld_button("forward"))
+ , mxFirstBtn(m_xBuilder->weld_button("btnFirst"))
+ , mxLastBtn(m_xBuilder->weld_button("btnLast"))
+ , mxPreviewBox(m_xBuilder->weld_check_button("previewbox"))
+ , mxNumPagesText(m_xBuilder->weld_label("totalnumpages"))
+ , mxPreview(new PrintPreviewWindow(this))
+ , mxPreviewWindow(new weld::CustomWeld(*m_xBuilder, "preview", *mxPreview))
+ , mxPageEdit(m_xBuilder->weld_entry("pageedit"))
+ , mxPagesBtn(m_xBuilder->weld_radio_button("pagespersheetbtn"))
+ , mxBrochureBtn(m_xBuilder->weld_radio_button("brochure"))
+ , mxPagesBoxTitleTxt(m_xBuilder->weld_label("pagespersheettxt"))
+ , mxNupPagesBox(m_xBuilder->weld_combo_box("pagespersheetbox"))
+ , mxNupNumPagesTxt(m_xBuilder->weld_label("pagestxt"))
+ , mxNupColEdt(m_xBuilder->weld_spin_button("pagecols"))
+ , mxNupTimesTxt(m_xBuilder->weld_label("by"))
+ , mxNupRowsEdt(m_xBuilder->weld_spin_button("pagerows"))
+ , mxPageMarginTxt1(m_xBuilder->weld_label("pagemargintxt1"))
+ , mxPageMarginEdt(m_xBuilder->weld_metric_spin_button("pagemarginsb", FieldUnit::MM))
+ , mxPageMarginTxt2(m_xBuilder->weld_label("pagemargintxt2"))
+ , mxSheetMarginTxt1(m_xBuilder->weld_label("sheetmargintxt1"))
+ , mxSheetMarginEdt(m_xBuilder->weld_metric_spin_button("sheetmarginsb", FieldUnit::MM))
+ , mxSheetMarginTxt2(m_xBuilder->weld_label("sheetmargintxt2"))
+ , mxPaperSizeBox(m_xBuilder->weld_combo_box("papersizebox"))
+ , mxOrientationBox(m_xBuilder->weld_combo_box("pageorientationbox"))
+ , mxNupOrderTxt(m_xBuilder->weld_label("labelorder"))
+ , mxNupOrderBox(m_xBuilder->weld_combo_box("orderbox"))
+ , mxNupOrder(new ShowNupOrderWindow)
+ , mxNupOrderWin(new weld::CustomWeld(*m_xBuilder, "orderpreview", *mxNupOrder))
+ , mxBorderCB(m_xBuilder->weld_check_button("bordercb"))
+ , mxRangeExpander(m_xBuilder->weld_expander("exRangeExpander"))
+ , mxLayoutExpander(m_xBuilder->weld_expander("exLayoutExpander"))
+ , mxCustom(m_xBuilder->weld_widget("customcontents"))
+ , maPrintToFileText( VclResId( SV_PRINT_TOFILE_TXT ) )
+ , maDefPrtText( VclResId( SV_PRINT_DEFPRT_TXT ) )
+ , maNoPageStr( VclResId( SV_PRINT_NOPAGES ) )
+ , maNoPreviewStr( VclResId( SV_PRINT_NOPREVIEW ) )
+ , mnCurPage( 0 )
+ , mnCachedPages( 0 )
+ , mbCollateAlwaysOff(false)
+ , mbShowLayoutFrame( true )
+ , mbSingleJobs( false )
+{
+ // 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();
+
+ // set paper sizes listbox
+ setPaperSizes();
+
+ // 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() );
+ }
+
+ initFromMultiPageSetup( maPController->getMultipage() );
+
+ // setup optional UI options set by application
+ setupOptionalUI();
+
+ // hide layout frame if unwanted
+ mxPageLayoutFrame->set_visible(mbShowLayoutFrame);
+
+ // restore settings from last run
+ readFromSettings();
+
+ // setup click hdl
+ mxOKButton->connect_clicked(LINK(this, PrintDialog, ClickHdl));
+ mxCancelButton->connect_clicked(LINK(this, PrintDialog, ClickHdl));
+ mxHelpButton->connect_clicked(LINK(this, PrintDialog, ClickHdl));
+ mxSetupButton->connect_clicked( LINK( this, PrintDialog, ClickHdl ) );
+ mxMoreOptionsBtn->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 ) );
+ mxPreviewBox->connect_clicked( LINK( this, PrintDialog, ClickHdl ) );
+ mxBorderCB->connect_clicked( LINK( this, PrintDialog, ClickHdl ) );
+
+ // setup toggle hdl
+ mxReverseOrderBox->connect_toggled( LINK( this, PrintDialog, ToggleHdl ) );
+ mxCollateBox->connect_toggled( LINK( this, PrintDialog, ToggleHdl ) );
+ mxPagesBtn->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 ) );
+
+ mxRangeExpander->connect_expanded(LINK( this, PrintDialog, ExpandHdl));
+ mxLayoutExpander->connect_expanded(LINK( this, PrintDialog, ExpandHdl));
+
+ updateNupFromPages();
+
+ // lock the dialog height, regardless of later expander state
+ mxScrolledWindow->set_size_request(
+ mxScrolledWindow->get_preferred_size().Width() + mxScrolledWindow->get_vscroll_width(),
+ mxScrolledWindow->get_preferred_size().Height());
+}
+
+IMPL_LINK_NOARG(PrintDialog, ExpandHdl, weld::Expander&, void)
+{
+ m_xDialog->resize_to_request();
+}
+
+PrintDialog::~PrintDialog()
+{
+}
+
+void PrintDialog::setupPaperSidesBox()
+{
+ DuplexMode eDuplex = maPController->getPrinter()->GetDuplexMode();
+
+ if ( eDuplex == DuplexMode::Unknown || isPrintToFile() )
+ {
+ mxPaperSidesBox->set_active( 0 );
+ mxPaperSidesBox->set_sensitive( false );
+ }
+ else
+ {
+ mxPaperSidesBox->set_active( static_cast<sal_Int32>(eDuplex) - 1 );
+ mxPaperSidesBox->set_sensitive( true );
+ }
+}
+
+void PrintDialog::storeToSettings()
+{
+ SettingsConfigItem* pItem = SettingsConfigItem::get();
+
+ pItem->setValue( "PrintDialog",
+ "LastPrinter",
+ isPrintToFile() ? Printer::GetDefaultPrinterName()
+ : mxPrinters->get_active_text() );
+
+ pItem->setValue( "PrintDialog",
+ "LastPage",
+ mxTabCtrl->get_tab_label_text(mxTabCtrl->get_current_page_ident()));
+
+ pItem->setValue( "PrintDialog",
+ "WindowState",
+ OStringToOUString(m_xDialog->get_window_state(WindowStateMask::All), RTL_TEXTENCODING_UTF8) );
+
+ pItem->setValue( "PrintDialog",
+ "CopyCount",
+ mxCopyCountField->get_text() );
+
+ pItem->setValue( "PrintDialog",
+ "Collate",
+ mxCollateBox->get_active() ? OUString("true") :
+ OUString("false") );
+
+ pItem->setValue( "PrintDialog",
+ "CollateSingleJobs",
+ mbSingleJobs ? OUString("true") :
+ OUString("false") );
+
+ pItem->setValue( "PrintDialog",
+ "HasPreview",
+ hasPreview() ? OUString("true") :
+ OUString("false") );
+
+ pItem->Commit();
+}
+
+void PrintDialog::readFromSettings()
+{
+ SettingsConfigItem* pItem = SettingsConfigItem::get();
+
+ // read last selected tab page; if it exists, activate it
+ OUString aValue = pItem->getValue( "PrintDialog",
+ "LastPage" );
+ sal_uInt16 nCount = mxTabCtrl->get_n_pages();
+ for (sal_uInt16 i = 0; i < nCount; ++i)
+ {
+ OString sPageId = mxTabCtrl->get_page_ident(i);
+ if (aValue == mxTabCtrl->get_tab_label_text(sPageId))
+ {
+ mxTabCtrl->set_current_page(sPageId);
+ break;
+ }
+ }
+
+ // persistent window state
+ aValue = pItem->getValue( "PrintDialog",
+ "WindowState" );
+ if (!aValue.isEmpty())
+ m_xDialog->set_window_state(OUStringToOString(aValue, RTL_TEXTENCODING_UTF8));
+
+ // collate
+ aValue = pItem->getValue( "PrintDialog",
+ "CollateBox" );
+ if( aValue.equalsIgnoreAsciiCase("alwaysoff") )
+ {
+ mbCollateAlwaysOff = true;
+ mxCollateBox->set_active( false );
+ mxCollateBox->set_sensitive( false );
+ }
+ else
+ {
+ mbCollateAlwaysOff = false;
+ aValue = pItem->getValue( "PrintDialog",
+ "Collate" );
+ mxCollateBox->set_active( aValue.equalsIgnoreAsciiCase("true") );
+ }
+
+ // collate single jobs
+ aValue = pItem->getValue( "PrintDialog",
+ "CollateSingleJobs" );
+ if ( aValue.equalsIgnoreAsciiCase("true") )
+ mbSingleJobs = true;
+ else
+ mbSingleJobs = false;
+
+ // 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
+ {
+ for (int nPaper = 0; nPaper < aPrt->GetPaperInfoCount(); nPaper++)
+ {
+ PaperInfo aInfo = aPrt->GetPaperInfo( nPaper );
+ aInfo.doSloppyFit();
+ Paper ePaper = aInfo.getPaper();
+
+ const LocaleDataWrapper& rLocWrap(Application::GetSettings().GetLocaleDataWrapper());
+ MapUnit eUnit = MapUnit::MapMM;
+ int nDigits = 0;
+ if( rLocWrap.getMeasurementSystemEnum() == MeasurementSystem::US )
+ {
+ eUnit = MapUnit::Map100thInch;
+ nDigits = 2;
+ }
+ Size aSize = aPrt->GetPaperSize( nPaper );
+ Size aLogicPaperSize( OutputDevice::LogicToLogic( aSize, MapMode( MapUnit::Map100thMM ), MapMode( eUnit ) ) );
+
+ OUString aWidth( rLocWrap.getNum( aLogicPaperSize.Width(), nDigits ) );
+ OUString aHeight( rLocWrap.getNum( aLogicPaperSize.Height(), nDigits ) );
+ OUString aUnit = eUnit == MapUnit::MapMM ? OUString("mm") : OUString("in");
+ OUString aPaperName = Printer::GetPaperName( ePaper ) + " " + aWidth + aUnit + " x " + aHeight + aUnit;
+
+ mxPaperSizeBox->append_text(aPaperName);
+
+ if ( ePaper == mePaper )
+ 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 );
+}
+
+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));
+
+ const MapMode aMapMode( MapUnit::Map100thMM );
+ if( nPages > 0 )
+ {
+ PrinterController::PageSize aPageSize =
+ maPController->getFilteredPageFile( mnCurPage, aMtf, i_bMayUseCache );
+ if( ! aPageSize.bFullPaper )
+ {
+ Point aOff( aPrt->PixelToLogic( aPrt->GetPageOffsetPixel(), aMapMode ) );
+ aMtf.Move( aOff.X(), aOff.Y() );
+ }
+ }
+
+ 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);
+}
+
+// make sure paper size matches paper orientation
+void PrintDialog::checkPaperSize( Size& rPaperSize )
+{
+ Orientation eOrientation = maPController->getPrinter()->GetOrientation();
+ if ( (eOrientation == Orientation::Portrait && rPaperSize.Width() > rPaperSize.Height()) ||
+ (eOrientation == Orientation::Landscape && rPaperSize.Width() < rPaperSize.Height()) )
+ {
+ // coverity[swapped-arguments : FALSE] - this is in the correct order
+ rPaperSize = Size( rPaperSize.Height(), rPaperSize.Width() );
+ }
+}
+
+// Always use this function to set paper orientation to make sure everything behaves well
+void PrintDialog::setPaperOrientation( Orientation eOrientation )
+{
+ VclPtr<Printer> aPrt( maPController->getPrinter() );
+ aPrt->SetOrientation( eOrientation );
+
+ // check if it's necessary to swap width and height of paper
+ if ( maPController->isPaperSizeFromUser() )
+ {
+ Size& aPaperSize = maPController->getPaperSizeFromUser();
+ checkPaperSize( aPaperSize );
+ }
+ else if ( maPController->getPapersizeFromSetup() )
+ {
+ Size& aPaperSize = maPController->getPaperSizeSetup();
+ checkPaperSize( aPaperSize );
+ }
+}
+
+void PrintDialog::checkControlDependencies()
+{
+ if (mxCopyCountField->get_value() > 1)
+ mxCollateBox->set_sensitive( !mbCollateAlwaysOff );
+ else
+ mxCollateBox->set_sensitive( false );
+
+ OUString aImg(mxCollateBox->get_active() ? OUString(SV_PRINT_COLLATE_BMP) : OUString(SV_PRINT_NOCOLLATE_BMP));
+
+ mxCollateImage->set_from_icon_name(aImg);
+
+ // enable setup button only for printers that can be setup
+ bool bHaveSetup = maPController->getPrinter()->HasSupport( PrinterSupport::SetupDialog );
+ mxSetupButton->set_sensitive(bHaveSetup);
+}
+
+void PrintDialog::checkOptionalControlDependencies()
+{
+ for( const auto& rEntry : maControlToPropertyMap )
+ {
+ bool bShouldbeEnabled = maPController->isUIOptionEnabled( rEntry.second );
+
+ if (bShouldbeEnabled && dynamic_cast<weld::RadioButton*>(rEntry.first))
+ {
+ auto r_it = maControlToNumValMap.find( rEntry.first );
+ if( r_it != maControlToNumValMap.end() )
+ {
+ bShouldbeEnabled = maPController->isUIChoiceEnabled( rEntry.second, r_it->second );
+ }
+ }
+
+ bool bIsEnabled = rEntry.first->get_sensitive();
+ // Enable does not do a change check first, so can be less cheap than expected
+ if (bShouldbeEnabled != bIsEnabled)
+ rEntry.first->set_sensitive( bShouldbeEnabled );
+ }
+}
+
+void PrintDialog::initFromMultiPageSetup( const vcl::PrinterController::MultiPageSetup& i_rMPS )
+{
+ mxNupOrderWin->show();
+ mxPagesBtn->set_active(true);
+ mxBrochureBtn->hide();
+
+ // setup field units for metric fields
+ const LocaleDataWrapper& rLocWrap(Application::GetSettings().GetLocaleDataWrapper());
+ FieldUnit eUnit = FieldUnit::MM;
+ sal_uInt16 nDigits = 0;
+ if( rLocWrap.getMeasurementSystemEnum() == MeasurementSystem::US )
+ {
+ eUnit = FieldUnit::INCH;
+ nDigits = 2;
+ }
+ // set units
+ mxPageMarginEdt->set_unit( eUnit );
+ mxSheetMarginEdt->set_unit( eUnit );
+
+ // set precision
+ mxPageMarginEdt->set_digits( nDigits );
+ mxSheetMarginEdt->set_digits( nDigits );
+
+ mxSheetMarginEdt->set_value( mxSheetMarginEdt->normalize( i_rMPS.nLeftMargin ), FieldUnit::MM_100TH );
+ mxPageMarginEdt->set_value( mxPageMarginEdt->normalize( i_rMPS.nHorizontalSpacing ), FieldUnit::MM_100TH );
+ mxBorderCB->set_active( i_rMPS.bDrawBorder );
+ mxNupRowsEdt->set_value( i_rMPS.nRows );
+ mxNupColEdt->set_value( i_rMPS.nColumns );
+ mxNupOrderBox->set_active( static_cast<sal_Int32>(i_rMPS.nOrder) );
+ if( i_rMPS.nRows != 1 || i_rMPS.nColumns != 1 )
+ {
+ mxNupPagesBox->set_active( mxNupPagesBox->get_count()-1 );
+ showAdvancedControls( true );
+ mxNupOrder->setValues( i_rMPS.nOrder, i_rMPS.nColumns, i_rMPS.nRows );
+ }
+}
+
+void PrintDialog::updateNup( bool i_bMayUseCache )
+{
+ int nRows = mxNupRowsEdt->get_value();
+ int nCols = mxNupColEdt->get_value();
+ long nPageMargin = mxPageMarginEdt->denormalize(mxPageMarginEdt->get_value( FieldUnit::MM_100TH ));
+ 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 );
+ }
+ else
+ {
+ aMPS.aPaperSize = maNupPortraitSize;
+ setPaperOrientation( Orientation::Portrait );
+ }
+ }
+
+ maPController->setMultipage( aMPS );
+
+ mxNupOrder->setValues( aMPS.nOrder, nCols, nRows );
+
+ preparePreview( i_bMayUseCache );
+}
+
+void PrintDialog::updateNupFromPages( bool i_bMayUseCache )
+{
+ int nPages = mxNupPagesBox->get_active_id().toInt32();
+ int nRows = mxNupRowsEdt->get_value();
+ int nCols = mxNupColEdt->get_value();
+ long nPageMargin = mxPageMarginEdt->denormalize(mxPageMarginEdt->get_value( FieldUnit::MM_100TH ));
+ 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
+ long nHorzMax = aSize.Width()/2;
+ long nVertMax = aSize.Height()/2;
+ if( nSheetMargin > nHorzMax )
+ nSheetMargin = nHorzMax;
+ if( nSheetMargin > nVertMax )
+ nSheetMargin = nVertMax;
+
+ mxSheetMarginEdt->set_max(
+ mxSheetMarginEdt->normalize(
+ std::min(nHorzMax, nVertMax) ), FieldUnit::MM_100TH );
+
+ // maximum page distance
+ nHorzMax = (aSize.Width() - 2*nSheetMargin);
+ if( nCols > 1 )
+ nHorzMax /= (nCols-1);
+ nVertMax = (aSize.Height() - 2*nSheetMargin);
+ if( nRows > 1 )
+ nHorzMax /= (nRows-1);
+
+ if( nPageMargin > nHorzMax )
+ nPageMargin = nHorzMax;
+ if( nPageMargin > nVertMax )
+ nPageMargin = nVertMax;
+
+ mxPageMarginEdt->set_max(
+ mxSheetMarginEdt->normalize(
+ std::min(nHorzMax, nVertMax ) ), FieldUnit::MM_100TH );
+ }
+
+ mxNupRowsEdt->set_value( nRows );
+ mxNupColEdt->set_value( nCols );
+ mxPageMarginEdt->set_value( mxPageMarginEdt->normalize( nPageMargin ), FieldUnit::MM_100TH );
+ mxSheetMarginEdt->set_value( mxSheetMarginEdt->normalize( nSheetMargin ), FieldUnit::MM_100TH );
+
+ showAdvancedControls( bCustom );
+ updateNup( i_bMayUseCache );
+}
+
+void PrintDialog::enableNupControls( bool bEnable )
+{
+ mxNupPagesBox->set_sensitive( bEnable );
+ mxNupNumPagesTxt->set_sensitive( bEnable );
+ mxNupColEdt->set_sensitive( bEnable );
+ mxNupTimesTxt->set_sensitive( bEnable );
+ mxNupRowsEdt->set_sensitive( bEnable );
+ mxPageMarginTxt1->set_sensitive( bEnable );
+ mxPageMarginEdt->set_sensitive( bEnable );
+ mxPageMarginTxt2->set_sensitive( bEnable );
+ mxSheetMarginTxt1->set_sensitive( bEnable );
+ mxSheetMarginEdt->set_sensitive( bEnable );
+ mxSheetMarginTxt2->set_sensitive( bEnable );
+ mxNupOrderTxt->set_sensitive( bEnable );
+ mxNupOrderBox->set_sensitive( bEnable );
+ mxNupOrderWin->set_sensitive( bEnable );
+ mxBorderCB->set_sensitive( bEnable );
+}
+
+void PrintDialog::showAdvancedControls( bool i_bShow )
+{
+ mxNupNumPagesTxt->set_visible( i_bShow );
+ mxNupColEdt->set_visible( i_bShow );
+ mxNupTimesTxt->set_visible( i_bShow );
+ mxNupRowsEdt->set_visible( i_bShow );
+ mxPageMarginTxt1->set_visible( i_bShow );
+ mxPageMarginEdt->set_visible( i_bShow );
+ mxPageMarginTxt2->set_visible( i_bShow );
+ mxSheetMarginTxt1->set_visible( i_bShow );
+ mxSheetMarginEdt->set_visible( i_bShow );
+ mxSheetMarginTxt2->set_visible( i_bShow );
+}
+
+namespace
+{
+ void setHelpId( weld::Widget* i_pWindow, const Sequence< OUString >& i_rHelpIds, sal_Int32 i_nIndex )
+ {
+ if( i_nIndex >= 0 && i_nIndex < i_rHelpIds.getLength() )
+ i_pWindow->set_help_id( OUStringToOString( i_rHelpIds.getConstArray()[i_nIndex], RTL_TEXTENCODING_UTF8 ) );
+ }
+
+ void setHelpText( weld::Widget* i_pWindow, const Sequence< OUString >& i_rHelpTexts, sal_Int32 i_nIndex )
+ {
+ // without a help text set and the correct smartID,
+ // help texts will be retrieved from the online help system
+ if( i_nIndex >= 0 && i_nIndex < i_rHelpTexts.getLength() )
+ i_pWindow->set_tooltip_text(i_rHelpTexts.getConstArray()[i_nIndex]);
+ }
+}
+
+void PrintDialog::setupOptionalUI()
+{
+ const Sequence< PropertyValue >& rOptions( maPController->getUIOptions() );
+ for( const auto& rOption : rOptions )
+ {
+ if (rOption.Name == "OptionsUIFile")
+ {
+ OUString sOptionsUIFile;
+ rOption.Value >>= sOptionsUIFile;
+ mxCustomOptionsUIBuilder.reset(Application::CreateBuilder(mxCustom.get(), sOptionsUIFile));
+ std::unique_ptr<weld::Container> xWindow = mxCustomOptionsUIBuilder->weld_container("box");
+ xWindow->show();
+ continue;
+ }
+
+ Sequence< beans::PropertyValue > aOptProp;
+ rOption.Value >>= aOptProp;
+
+ // extract ui element
+ OUString aCtrlType;
+ OString aID;
+ OUString aText;
+ OUString aPropertyName;
+ Sequence< OUString > aChoices;
+ Sequence< sal_Bool > aChoicesDisabled;
+ Sequence< OUString > aHelpTexts;
+ Sequence< OUString > aIDs;
+ Sequence< OUString > aHelpIds;
+ sal_Int64 nMinValue = 0, nMaxValue = 0;
+ OUString aGroupingHint;
+
+ for( const beans::PropertyValue& rEntry : std::as_const(aOptProp) )
+ {
+ if ( rEntry.Name == "ID" )
+ {
+ rEntry.Value >>= aIDs;
+ aID = OUStringToOString(aIDs[0], RTL_TEXTENCODING_UTF8);
+ }
+ if ( rEntry.Name == "Text" )
+ {
+ rEntry.Value >>= aText;
+ }
+ else if ( rEntry.Name == "ControlType" )
+ {
+ rEntry.Value >>= aCtrlType;
+ }
+ else if ( rEntry.Name == "Choices" )
+ {
+ rEntry.Value >>= aChoices;
+ }
+ else if ( rEntry.Name == "ChoicesDisabled" )
+ {
+ rEntry.Value >>= aChoicesDisabled;
+ }
+ else if ( rEntry.Name == "Property" )
+ {
+ PropertyValue aVal;
+ rEntry.Value >>= aVal;
+ aPropertyName = aVal.Name;
+ }
+ else if ( rEntry.Name == "Enabled" )
+ {
+ }
+ else if ( rEntry.Name == "GroupingHint" )
+ {
+ rEntry.Value >>= aGroupingHint;
+ }
+ else if ( rEntry.Name == "DependsOnName" )
+ {
+ }
+ else if ( rEntry.Name == "DependsOnEntry" )
+ {
+ }
+ else if ( rEntry.Name == "AttachToDependency" )
+ {
+ }
+ else if ( rEntry.Name == "MinValue" )
+ {
+ rEntry.Value >>= nMinValue;
+ }
+ else if ( rEntry.Name == "MaxValue" )
+ {
+ rEntry.Value >>= nMaxValue;
+ }
+ else if ( rEntry.Name == "HelpText" )
+ {
+ if( ! (rEntry.Value >>= aHelpTexts) )
+ {
+ OUString aHelpText;
+ if( rEntry.Value >>= aHelpText )
+ {
+ aHelpTexts.realloc( 1 );
+ *aHelpTexts.getArray() = aHelpText;
+ }
+ }
+ }
+ else if ( rEntry.Name == "HelpId" )
+ {
+ if( ! (rEntry.Value >>= aHelpIds ) )
+ {
+ OUString aHelpId;
+ if( rEntry.Value >>= aHelpId )
+ {
+ aHelpIds.realloc( 1 );
+ *aHelpIds.getArray() = aHelpId;
+ }
+ }
+ }
+ else if ( rEntry.Name == "HintNoLayoutPage" )
+ {
+ bool bHasLayoutFrame = false;
+ rEntry.Value >>= bHasLayoutFrame;
+ mbShowLayoutFrame = !bHasLayoutFrame;
+ }
+ }
+
+ if (aCtrlType == "Group")
+ {
+ aID = "custom";
+
+ weld::Container* pPage = mxTabCtrl->get_page(aID);
+ if (!pPage)
+ continue;
+
+ mxTabCtrl->set_tab_label_text(aID, aText);
+
+ // set help id
+ if (aHelpIds.hasElements())
+ pPage->set_help_id(OUStringToOString(aHelpIds.getConstArray()[0], RTL_TEXTENCODING_UTF8));
+
+ // set help text
+ if (aHelpTexts.hasElements())
+ pPage->set_tooltip_text(aHelpTexts.getConstArray()[0]);
+
+ pPage->show();
+ }
+ else if (aCtrlType == "Subgroup" && !aID.isEmpty())
+ {
+ std::unique_ptr<weld::Widget> xWidget;
+ // since 'New Print Dialog Design' fromwhich in calc is not a frame anymore
+ if (aID == "fromwhich")
+ {
+ std::unique_ptr<weld::Label> xLabel = m_xBuilder->weld_label(aID);
+ xLabel->set_label(aText);
+ xWidget = std::move(xLabel);
+ }
+ else
+ {
+ std::unique_ptr<weld::Frame> xFrame = m_xBuilder->weld_frame(aID);
+ if (!xFrame && mxCustomOptionsUIBuilder)
+ xFrame = mxCustomOptionsUIBuilder->weld_frame(aID);
+ if (xFrame)
+ {
+ xFrame->set_label(aText);
+ xWidget = std::move(xFrame);
+ }
+ }
+
+ if (!xWidget)
+ continue;
+
+ // set help id
+ setHelpId(xWidget.get(), aHelpIds, 0);
+ // set help text
+ setHelpText(xWidget.get(), aHelpTexts, 0);
+
+ xWidget->show();
+ }
+ // EVIL
+ else if( aCtrlType == "Bool" && aGroupingHint == "LayoutPage" && aPropertyName == "PrintProspect" )
+ {
+ mxBrochureBtn->set_label(aText);
+ mxBrochureBtn->show();
+
+ bool bVal = false;
+ PropertyValue* pVal = maPController->getValue( aPropertyName );
+ if( pVal )
+ pVal->Value >>= bVal;
+ mxBrochureBtn->set_active( bVal );
+ mxBrochureBtn->set_sensitive( maPController->isUIOptionEnabled( aPropertyName ) && pVal != nullptr );
+ mxBrochureBtn->connect_toggled( LINK( this, PrintDialog, ToggleHdl ) );
+
+ maPropertyToWindowMap[aPropertyName].emplace_back(mxBrochureBtn.get());
+ maControlToPropertyMap[mxBrochureBtn.get()] = aPropertyName;
+
+ // set help id
+ setHelpId( mxBrochureBtn.get(), aHelpIds, 0 );
+ // set help text
+ setHelpText( mxBrochureBtn.get(), aHelpTexts, 0 );
+ }
+ else if (aCtrlType == "Bool")
+ {
+ // add a check box
+ std::unique_ptr<weld::CheckButton> xNewBox = m_xBuilder->weld_check_button(aID);
+ if (!xNewBox && mxCustomOptionsUIBuilder)
+ xNewBox = mxCustomOptionsUIBuilder->weld_check_button(aID);
+ if (!xNewBox)
+ continue;
+
+ xNewBox->set_label( aText );
+ xNewBox->show();
+
+ bool bVal = false;
+ PropertyValue* pVal = maPController->getValue( aPropertyName );
+ if( pVal )
+ pVal->Value >>= bVal;
+ xNewBox->set_active( bVal );
+ xNewBox->connect_toggled( LINK( this, PrintDialog, UIOption_CheckHdl ) );
+
+ maExtraControls.emplace_back(std::move(xNewBox));
+
+ weld::Widget* pWidget = maExtraControls.back().get();
+
+ maPropertyToWindowMap[aPropertyName].emplace_back(pWidget);
+ maControlToPropertyMap[pWidget] = aPropertyName;
+
+ // set help id
+ setHelpId(pWidget, aHelpIds, 0);
+ // set help text
+ setHelpText(pWidget, aHelpTexts, 0);
+ }
+ else if (aCtrlType == "Radio")
+ {
+ sal_Int32 nCurHelpText = 0;
+
+ // iterate options
+ sal_Int32 nSelectVal = 0;
+ PropertyValue* pVal = maPController->getValue( aPropertyName );
+ if( pVal && pVal->Value.hasValue() )
+ pVal->Value >>= nSelectVal;
+ for( sal_Int32 m = 0; m < aChoices.getLength(); m++ )
+ {
+ aID = OUStringToOString(aIDs[m], RTL_TEXTENCODING_UTF8);
+ std::unique_ptr<weld::RadioButton> xBtn = m_xBuilder->weld_radio_button(aID);
+ if (!xBtn && mxCustomOptionsUIBuilder)
+ xBtn = mxCustomOptionsUIBuilder->weld_radio_button(aID);
+ if (!xBtn)
+ continue;
+
+ xBtn->set_label( aChoices[m] );
+ xBtn->set_active( m == nSelectVal );
+ xBtn->connect_toggled( LINK( this, PrintDialog, UIOption_RadioHdl ) );
+ if( aChoicesDisabled.getLength() > m && aChoicesDisabled[m] )
+ xBtn->set_sensitive( false );
+ xBtn->show();
+
+ maExtraControls.emplace_back(std::move(xBtn));
+
+ weld::Widget* pWidget = maExtraControls.back().get();
+
+ maPropertyToWindowMap[ aPropertyName ].emplace_back(pWidget);
+ maControlToPropertyMap[pWidget] = aPropertyName;
+ maControlToNumValMap[pWidget] = m;
+
+ // set help id
+ setHelpId( pWidget, aHelpIds, nCurHelpText );
+ // set help text
+ setHelpText( pWidget, aHelpTexts, nCurHelpText );
+ nCurHelpText++;
+ }
+ }
+ else if ( aCtrlType == "List" )
+ {
+ std::unique_ptr<weld::ComboBox> xList = m_xBuilder->weld_combo_box(aID);
+ if (!xList && mxCustomOptionsUIBuilder)
+ xList = mxCustomOptionsUIBuilder->weld_combo_box(aID);
+ if (!xList)
+ continue;
+
+ // iterate options
+ for( const auto& rChoice : std::as_const(aChoices) )
+ xList->append_text(rChoice);
+
+ sal_Int32 nSelectVal = 0;
+ PropertyValue* pVal = maPController->getValue( aPropertyName );
+ if( pVal && pVal->Value.hasValue() )
+ pVal->Value >>= nSelectVal;
+ xList->set_active(nSelectVal);
+ xList->connect_changed( LINK( this, PrintDialog, UIOption_SelectHdl ) );
+ xList->show();
+
+ maExtraControls.emplace_back(std::move(xList));
+
+ weld::Widget* pWidget = maExtraControls.back().get();
+
+ maPropertyToWindowMap[ aPropertyName ].emplace_back(pWidget);
+ maControlToPropertyMap[pWidget] = aPropertyName;
+
+ // set help id
+ setHelpId( pWidget, aHelpIds, 0 );
+ // set help text
+ setHelpText( pWidget, aHelpTexts, 0 );
+ }
+ else if ( aCtrlType == "Range" )
+ {
+ std::unique_ptr<weld::SpinButton> xField = m_xBuilder->weld_spin_button(aID);
+ if (!xField && mxCustomOptionsUIBuilder)
+ xField = mxCustomOptionsUIBuilder->weld_spin_button(aID);
+ if (!xField)
+ continue;
+
+ // set min/max and current value
+ if(nMinValue != nMaxValue)
+ xField->set_range(nMinValue, nMaxValue);
+
+ sal_Int64 nCurVal = 0;
+ PropertyValue* pVal = maPController->getValue( aPropertyName );
+ if( pVal && pVal->Value.hasValue() )
+ pVal->Value >>= nCurVal;
+ xField->set_value( nCurVal );
+ xField->connect_value_changed( LINK( this, PrintDialog, UIOption_SpinModifyHdl ) );
+ xField->show();
+
+ maExtraControls.emplace_back(std::move(xField));
+
+ weld::Widget* pWidget = maExtraControls.back().get();
+
+ maPropertyToWindowMap[ aPropertyName ].emplace_back(pWidget);
+ maControlToPropertyMap[pWidget] = aPropertyName;
+
+ // set help id
+ setHelpId( pWidget, aHelpIds, 0 );
+ // set help text
+ setHelpText( pWidget, aHelpTexts, 0 );
+ }
+ else if (aCtrlType == "Edit")
+ {
+ std::unique_ptr<weld::Entry> xField = m_xBuilder->weld_entry(aID);
+ if (!xField && mxCustomOptionsUIBuilder)
+ xField = mxCustomOptionsUIBuilder->weld_entry(aID);
+ if (!xField)
+ continue;
+
+ OUString aCurVal;
+ PropertyValue* pVal = maPController->getValue( aPropertyName );
+ if( pVal && pVal->Value.hasValue() )
+ pVal->Value >>= aCurVal;
+ xField->set_text( aCurVal );
+ xField->connect_changed( LINK( this, PrintDialog, UIOption_EntryModifyHdl ) );
+ xField->show();
+
+ maExtraControls.emplace_back(std::move(xField));
+
+ weld::Widget* pWidget = maExtraControls.back().get();
+
+ maPropertyToWindowMap[ aPropertyName ].emplace_back(pWidget);
+ maControlToPropertyMap[pWidget] = aPropertyName;
+
+ // set help id
+ setHelpId( pWidget, aHelpIds, 0 );
+ // set help text
+ setHelpText( pWidget, aHelpTexts, 0 );
+ }
+ else
+ {
+ SAL_WARN( "vcl", "Unsupported UI option: \"" << aCtrlType << '"');
+ }
+ }
+
+ // #i106506# if no brochure button, then the singular Pages radio button
+ // makes no sense, so replace it by a FixedText label
+ if (!mxBrochureBtn->get_visible() && mxPagesBtn->get_visible())
+ {
+ mxPagesBoxTitleTxt->set_label(mxPagesBtn->get_label());
+ mxPagesBoxTitleTxt->show();
+ mxPagesBtn->hide();
+
+ mxPagesBoxTitleTxt->set_accessible_relation_label_for(mxNupPagesBox.get());
+ mxNupPagesBox->set_accessible_relation_labeled_by(mxPagesBoxTitleTxt.get());
+ mxPagesBtn->set_accessible_relation_label_for(nullptr);
+ }
+
+ // 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() )
+ {
+ const auto& rWindows( it->second );
+ if( ! rWindows.empty() )
+ {
+ 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 && nVal < sal_Int32(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::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::ToggleButton&, rButton, void)
+{
+ ClickHdl(rButton);
+}
+
+IMPL_LINK(PrintDialog, ClickHdl, weld::Button&, rButton, void)
+{
+ if (&rButton == mxOKButton.get() || &rButton == mxCancelButton.get())
+ {
+ storeToSettings();
+ m_xDialog->response(&rButton == mxOKButton.get() ? RET_OK : RET_CANCEL);
+ }
+ else if( &rButton == mxHelpButton.get() )
+ {
+ // start help system
+ Help* pHelp = Application::GetHelp();
+ if( pHelp )
+ {
+ pHelp->Start("vcl/ui/printdialog/PrintDialog", mxOKButton.get());
+ }
+ }
+ else if ( &rButton == mxPreviewBox.get() )
+ {
+ preparePreview( true );
+ }
+ 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 == mxBrochureBtn.get() )
+ {
+ PropertyValue* pVal = getValueForWindow( &rButton );
+ if( pVal )
+ {
+ bool bVal = mxBrochureBtn->get_active();
+ pVal->Value <<= bVal;
+
+ checkOptionalControlDependencies();
+
+ // update preview and page settings
+ preparePreview(false);
+ }
+ if( mxBrochureBtn->get_active() )
+ {
+ mxOrientationBox->set_sensitive( false );
+ mxOrientationBox->set_active( ORIENTATION_LANDSCAPE );
+ mxNupPagesBox->set_active( 0 );
+ updateNupFromPages();
+ showAdvancedControls( false );
+ enableNupControls( false );
+ }
+ }
+ else if( &rButton == mxPagesBtn.get() )
+ {
+ mxOrientationBox->set_sensitive( true );
+ mxOrientationBox->set_active( ORIENTATION_AUTOMATIC );
+ enableNupControls( true );
+ updateNupFromPages();
+ }
+ else if( &rButton == mxCollateBox.get() )
+ {
+ maPController->setValue( "Collate",
+ makeAny( isCollate() ) );
+ checkControlDependencies();
+ }
+ else if( &rButton == mxReverseOrderBox.get() )
+ {
+ bool bChecked = mxReverseOrderBox->get_active();
+ maPController->setReversePrint( bChecked );
+ maPController->setValue( "PrintReverse",
+ makeAny( bChecked ) );
+ preparePreview( true );
+ }
+ else if( &rButton == mxBorderCB.get() )
+ {
+ updateNup();
+ }
+ else if ( &rButton == mxMoreOptionsBtn.get() )
+ {
+ mxMoreOptionsDlg.reset(new MoreOptionsDialog(this));
+ mxMoreOptionsDlg->run();
+ }
+ 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();
+ 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
+ preparePreview(false);
+ }
+ 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();
+ preparePreview(true);
+ }
+ 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();
+ preparePreview( true );
+ }
+
+ 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 ) );
+
+ 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();
+ mePaper = aInfo.getPaper();
+
+ if ( mePaper == PAPER_USER )
+ aPrt->SetPaperSizeUser( Size( aInfo.getWidth(), aInfo.getHeight() ) );
+ else
+ aPrt->SetPaper( mePaper );
+
+ Size aPaperSize( aInfo.getWidth(), aInfo.getHeight() );
+ checkPaperSize( aPaperSize );
+ maPController->setPaperSizeFromUser( aPaperSize );
+
+ preparePreview(true);
+ }
+}
+
+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;
+ preparePreview(true);
+ }
+ 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",
+ makeAny( sal_Int32(mxCopyCountField->get_value()) ) );
+ maPController->setValue( "Collate",
+ makeAny( isCollate() ) );
+ }
+}
+
+IMPL_LINK( PrintDialog, UIOption_CheckHdl, weld::ToggleButton&, 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
+ preparePreview(false);
+ }
+}
+
+IMPL_LINK( PrintDialog, UIOption_RadioHdl, weld::ToggleButton&, 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() )
+ {
+ PropertyValue* pVal = getValueForWindow( &i_rBtn );
+ auto it = maControlToNumValMap.find( &i_rBtn );
+ if( pVal && it != maControlToNumValMap.end() )
+ {
+ 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
+ preparePreview(false);
+ }
+ }
+}
+
+IMPL_LINK( PrintDialog, UIOption_SelectHdl, weld::ComboBox&, i_rBox, void )
+{
+ PropertyValue* pVal = getValueForWindow( &i_rBox );
+ if( pVal )
+ {
+ 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
+ preparePreview(false);
+ }
+}
+
+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
+ preparePreview(false);
+ }
+}
+
+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
+ preparePreview(false);
+ }
+}
+
+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 vaules
+ mxText->set_label(getNewLabel(maStr, mnCur, mnMax));
+}
+
+PrintProgressDialog::~PrintProgressDialog()
+{
+}
+
+IMPL_LINK_NOARG(PrintProgressDialog, ClickHdl, weld::Button&, void)
+{
+ mbCanceled = true;
+}
+
+void PrintProgressDialog::setProgress( int i_nCurrent )
+{
+ mnCur = i_nCurrent;
+
+ if( mnMax < 1 )
+ mnMax = 1;
+
+ mxText->set_label(getNewLabel(maStr, mnCur, mnMax));
+
+ // here view the dialog, with the right label
+ mxProgress->set_percentage(mnCur*100/mnMax);
+}
+
+void PrintProgressDialog::tick()
+{
+ if( mnCur < mnMax )
+ setProgress( ++mnCur );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/scrwnd.cxx b/vcl/source/window/scrwnd.cxx
new file mode 100644
index 000000000..58345ef62
--- /dev/null
+++ b/vcl/source/window/scrwnd.cxx
@@ -0,0 +1,390 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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(Image(StockImage::Yes, SV_RESID_BITMAP_SCROLLVH));
+ maImgList.emplace_back(Image(StockImage::Yes, SV_RESID_BITMAP_SCROLLV));
+ maImgList.emplace_back(Image(StockImage::Yes, SV_RESID_BITMAP_SCROLLH));
+ maImgList.emplace_back(Image(StockImage::Yes, SV_RESID_BITMAP_WHEELVH));
+ maImgList.emplace_back(Image(StockImage::Yes, SV_RESID_BITMAP_WHEELV));
+ maImgList.emplace_back(Image(StockImage::Yes, SV_RESID_BITMAP_WHEELH));
+}
+
+void ImplWheelWindow::ImplSetWheelMode( WheelMode nWheelMode )
+{
+ if( nWheelMode != mnWheelMode )
+ {
+ 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;
+
+ if( !o3tl::convertsToAtMost(fValX, LONG_MAX) )
+ mnActDeltaX = LONG_MAX;
+ else if( !o3tl::convertsToAtLeast(fValX, LONG_MIN) )
+ mnActDeltaX = LONG_MIN;
+ else
+ mnActDeltaX = static_cast<long>(fValX);
+
+ if( !o3tl::convertsToAtMost(fValY, LONG_MAX) )
+ mnActDeltaY = LONG_MAX;
+ else if( !o3tl::convertsToAtLeast(fValY, LONG_MIN) )
+ mnActDeltaY = LONG_MIN;
+ else
+ mnActDeltaY = static_cast<long>(fValY);
+ }
+ }
+}
+
+PointerStyle ImplWheelWindow::ImplGetMousePointer( long nDistX, long nDistY )
+{
+ 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 long nDistX = aMousePos.X() - maCenter.X();
+ const long nDistY = aMousePos.Y() - maCenter.Y();
+
+ mnActDist = static_cast<sal_uLong>(hypot( static_cast<double>(nDistX), nDistY ));
+
+ const PointerStyle eActStyle = ImplGetMousePointer( nDistX, nDistY );
+ const StartAutoScrollFlags nFlags = ImplGetSVData()->mpWinData->mnAutoScrollFlags;
+ const bool bHorz( nFlags & StartAutoScrollFlags::Horz );
+ const bool bVert( nFlags & StartAutoScrollFlags::Vert );
+ const bool bOuter = mnActDist > WHEEL_RADIUS;
+
+ if( bOuter && ( maLastMousePos != aMousePos ) )
+ {
+ switch( eActStyle )
+ {
+ case PointerStyle::AutoScrollN: mnActDeltaX = +0; mnActDeltaY = +1; break;
+ case PointerStyle::AutoScrollS: mnActDeltaX = +0; mnActDeltaY = -1; break;
+ case PointerStyle::AutoScrollW: mnActDeltaX = +1; mnActDeltaY = +0; break;
+ case PointerStyle::AutoScrollE: mnActDeltaX = -1; mnActDeltaY = +0; break;
+ case PointerStyle::AutoScrollNW: mnActDeltaX = +1; mnActDeltaY = +1; break;
+ case PointerStyle::AutoScrollNE: mnActDeltaX = -1; mnActDeltaY = +1; break;
+ case PointerStyle::AutoScrollSW: mnActDeltaX = +1; mnActDeltaY = -1; break;
+ case PointerStyle::AutoScrollSE: mnActDeltaX = -1; mnActDeltaY = -1; break;
+
+ default:
+ break;
+ }
+ }
+
+ ImplRecalcScrollValues();
+ maLastMousePos = aMousePos;
+ SetPointer( eActStyle );
+
+ if( bHorz && bVert )
+ ImplSetWheelMode( bOuter ? WheelMode::ScrollVH : WheelMode::VH );
+ else if( bHorz )
+ ImplSetWheelMode( bOuter ? WheelMode::ScrollH : WheelMode::H );
+ else
+ ImplSetWheelMode( bOuter ? WheelMode::ScrollV : WheelMode::V );
+}
+
+void ImplWheelWindow::MouseButtonUp( const MouseEvent& rMEvt )
+{
+ if( mnActDist > WHEEL_RADIUS )
+ GetParent()->EndAutoScroll();
+ else
+ FloatingWindow::MouseButtonUp( rMEvt );
+}
+
+IMPL_LINK_NOARG(ImplWheelWindow, ImplScrollHdl, Timer *, void)
+{
+ if ( mnActDeltaX || mnActDeltaY )
+ {
+ vcl::Window* pWindow = GetParent();
+ const Point aMousePos( pWindow->OutputToScreenPixel( pWindow->GetPointerPosPixel() ) );
+ Point aCmdMousePos( pWindow->ImplFrameToOutput( aMousePos ) );
+ CommandScrollData aScrollData( mnActDeltaX, mnActDeltaY );
+ CommandEvent aCEvt( aCmdMousePos, CommandEventId::AutoScroll, true, &aScrollData );
+ NotifyEvent aNCmdEvt( MouseNotifyEvent::COMMAND, pWindow, &aCEvt );
+
+ if ( !ImplCallPreNotify( aNCmdEvt ) )
+ {
+ const sal_uInt64 nTime = tools::Time::GetSystemTicks();
+ VclPtr<ImplWheelWindow> xWin(this);
+ pWindow->Command( aCEvt );
+ if( xWin->IsDisposed() )
+ return;
+ mnRepaintTime = std::max( tools::Time::GetSystemTicks() - nTime, sal_uInt64(1) );
+ ImplRecalcScrollValues();
+ }
+ }
+
+ if ( mnTimeout != mpTimer->GetTimeout() )
+ mpTimer->SetTimeout( mnTimeout );
+ mpTimer->Start();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/seleng.cxx b/vcl/source/window/seleng.cxx
new file mode 100644
index 000000000..41d0d5f89
--- /dev/null
+++ b/vcl/source/window/seleng.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 <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 ),
+ nUpdateInterval( SELENG_AUTOREPEAT_INTERVAL )
+{
+ eSelMode = SelectionMode::Single;
+ pFunctionSet = pFuncSet;
+ nFlags = SelectionEngineFlags::EXPANDONMOVE;
+ nLockedMods = 0;
+
+ aWTimer.SetInvokeHandler( LINK( this, SelectionEngine, ImpWatchDog ) );
+ aWTimer.SetTimeout( nUpdateInterval );
+ aWTimer.SetDebugName( "vcl::SelectionEngine aWTimer" );
+}
+
+SelectionEngine::~SelectionEngine()
+{
+ aWTimer.Stop();
+}
+
+IMPL_LINK_NOARG(SelectionEngine, ImpWatchDog, Timer *, void)
+{
+ if ( !aArea.IsInside( aLastMove.GetPosPixel() ) )
+ SelMouseMove( aLastMove );
+}
+
+void SelectionEngine::SetSelectionMode( SelectionMode eMode )
+{
+ eSelMode = eMode;
+}
+
+void SelectionEngine::CursorPosChanging( bool bShift, bool bMod1 )
+{
+ if ( !pFunctionSet )
+ return;
+
+ if ( bShift && eSelMode != SelectionMode::Single )
+ {
+ if ( IsAddMode() )
+ {
+ if ( !(nFlags & SelectionEngineFlags::HAS_ANCH) )
+ {
+ pFunctionSet->CreateAnchor();
+ nFlags |= SelectionEngineFlags::HAS_ANCH;
+ }
+ }
+ else
+ {
+ if ( !(nFlags & SelectionEngineFlags::HAS_ANCH) )
+ {
+ if( ShouldDeselect( bMod1 ) )
+ pFunctionSet->DeselectAll();
+ pFunctionSet->CreateAnchor();
+ nFlags |= SelectionEngineFlags::HAS_ANCH;
+ }
+ }
+ }
+ else
+ {
+ if ( IsAddMode() )
+ {
+ if ( nFlags & SelectionEngineFlags::HAS_ANCH )
+ {
+ // pFunctionSet->CreateCursor();
+ pFunctionSet->DestroyAnchor();
+ nFlags &= ~SelectionEngineFlags::HAS_ANCH;
+ }
+ }
+ else
+ {
+ if( ShouldDeselect( bMod1 ) )
+ pFunctionSet->DeselectAll();
+ else
+ pFunctionSet->DestroyAnchor();
+ nFlags &= ~SelectionEngineFlags::HAS_ANCH;
+ }
+ }
+}
+
+bool SelectionEngine::SelMouseButtonDown( const MouseEvent& rMEvt )
+{
+ nFlags &= ~SelectionEngineFlags::CMDEVT;
+ if ( !pFunctionSet || rMEvt.GetClicks() > 1 )
+ return false;
+
+ sal_uInt16 nModifier = rMEvt.GetModifier() | nLockedMods;
+ bool nSwap = comphelper::LibreOfficeKit::isActive() && (nModifier & KEY_MOD1) && (nModifier & KEY_MOD2);
+
+ if ( !nSwap && (nModifier & KEY_MOD2) )
+ return false;
+ // in SingleSelection: filter Control-Key,
+ // so that a D&D can be also started with a Ctrl-Click
+ if ( nModifier == KEY_MOD1 && eSelMode == SelectionMode::Single )
+ nModifier = 0;
+
+ Point aPos = rMEvt.GetPosPixel();
+ aLastMove = rMEvt;
+
+ if( !rMEvt.IsRight() )
+ {
+ CaptureMouse();
+ nFlags |= SelectionEngineFlags::IN_SEL;
+ }
+ else
+ {
+ nModifier = 0;
+ }
+
+ if (nSwap)
+ {
+ pFunctionSet->CreateAnchor();
+ pFunctionSet->SetCursorAtPoint( aPos );
+ return true;
+ }
+
+ switch ( nModifier )
+ {
+ case 0: // KEY_NO_KEY
+ {
+ bool bSelAtPoint = pFunctionSet->IsSelectionAtPoint( aPos );
+ nFlags &= ~SelectionEngineFlags::IN_ADD;
+ if ( (nFlags & SelectionEngineFlags::DRG_ENAB) && bSelAtPoint )
+ {
+ nFlags |= SelectionEngineFlags::WAIT_UPEVT;
+ nFlags &= ~SelectionEngineFlags::IN_SEL;
+ ReleaseMouse();
+ return true; // wait for STARTDRAG-Command-Event
+ }
+ if ( eSelMode != SelectionMode::Single )
+ {
+ if( !IsAddMode() )
+ pFunctionSet->DeselectAll();
+ else
+ pFunctionSet->DestroyAnchor();
+ nFlags &= ~SelectionEngineFlags::HAS_ANCH; // bHasAnchor = false;
+ }
+ pFunctionSet->SetCursorAtPoint( aPos );
+ // special case Single-Selection, to enable simple Select+Drag
+ if (eSelMode == SelectionMode::Single && (nFlags & SelectionEngineFlags::DRG_ENAB))
+ nFlags |= SelectionEngineFlags::WAIT_UPEVT;
+ return true;
+ }
+
+ case KEY_SHIFT:
+ if ( eSelMode == SelectionMode::Single )
+ {
+ ReleaseMouse();
+ nFlags &= ~SelectionEngineFlags::IN_SEL;
+ return false;
+ }
+ if ( nFlags & SelectionEngineFlags::ADD_ALW )
+ nFlags |= SelectionEngineFlags::IN_ADD;
+ else
+ nFlags &= ~SelectionEngineFlags::IN_ADD;
+
+ if( !(nFlags & SelectionEngineFlags::HAS_ANCH) )
+ {
+ if ( !(nFlags & SelectionEngineFlags::IN_ADD) )
+ pFunctionSet->DeselectAll();
+ pFunctionSet->CreateAnchor();
+ nFlags |= SelectionEngineFlags::HAS_ANCH;
+ }
+ pFunctionSet->SetCursorAtPoint( aPos );
+ return true;
+
+ case KEY_MOD1:
+ // allow Control only for Multi-Select
+ if ( eSelMode != SelectionMode::Multiple )
+ {
+ nFlags &= ~SelectionEngineFlags::IN_SEL;
+ ReleaseMouse();
+ return true; // skip Mouse-Click
+ }
+ if ( nFlags & SelectionEngineFlags::HAS_ANCH )
+ {
+ // pFunctionSet->CreateCursor();
+ pFunctionSet->DestroyAnchor();
+ nFlags &= ~SelectionEngineFlags::HAS_ANCH;
+ }
+ if ( pFunctionSet->IsSelectionAtPoint( aPos ) )
+ {
+ pFunctionSet->DeselectAtPoint( aPos );
+ pFunctionSet->SetCursorAtPoint( aPos, true );
+ }
+ else
+ {
+ pFunctionSet->SetCursorAtPoint( aPos );
+ }
+ return true;
+
+ case KEY_SHIFT + KEY_MOD1:
+ if ( eSelMode != SelectionMode::Multiple )
+ {
+ ReleaseMouse();
+ nFlags &= ~SelectionEngineFlags::IN_SEL;
+ return false;
+ }
+ nFlags |= SelectionEngineFlags::IN_ADD; //bIsInAddMode = true;
+ if ( !(nFlags & SelectionEngineFlags::HAS_ANCH) )
+ {
+ pFunctionSet->CreateAnchor();
+ nFlags |= SelectionEngineFlags::HAS_ANCH;
+ }
+ pFunctionSet->SetCursorAtPoint( aPos );
+ return true;
+ }
+
+ return false;
+}
+
+bool SelectionEngine::SelMouseButtonUp( const MouseEvent& rMEvt )
+{
+ aWTimer.Stop();
+ if (!pFunctionSet)
+ {
+ const SelectionEngineFlags nMask = SelectionEngineFlags::CMDEVT | SelectionEngineFlags::WAIT_UPEVT | SelectionEngineFlags::IN_SEL;
+ nFlags &= ~nMask;
+ return false;
+ }
+
+ if (!rMEvt.IsRight())
+ ReleaseMouse();
+
+ if( (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
+ pFunctionSet->SetCursorAtPoint( aLastMove.GetPosPixel(), true );
+ }
+ else
+ {
+ pFunctionSet->DeselectAll();
+ nFlags &= ~SelectionEngineFlags::HAS_ANCH; // uncheck anchor
+ 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.IsInside( 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;
+}
+
+void SelectionEngine::Command( const CommandEvent& rCEvt )
+{
+ // Timer aWTimer is active during enlarging a selection
+ if ( !pFunctionSet || aWTimer.IsActive() )
+ return;
+ aWTimer.Stop();
+ if ( rCEvt.GetCommand() == CommandEventId::StartDrag )
+ {
+ 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;
+ }
+}
+
+void SelectionEngine::SetUpdateInterval( sal_uLong nInterval )
+{
+ if (nInterval < SELENG_AUTOREPEAT_INTERVAL_MIN)
+ // Set a lower threshold. On Windows, setting this value too low
+ // would cause selection to get updated indefinitely.
+ nInterval = SELENG_AUTOREPEAT_INTERVAL_MIN;
+
+ if (nUpdateInterval == nInterval)
+ // no update needed.
+ return;
+
+ if (aWTimer.IsActive())
+ {
+ // reset the timer right away on interval change.
+ aWTimer.Stop();
+ aWTimer.SetTimeout(nInterval);
+ aWTimer.Start();
+ }
+ else
+ aWTimer.SetTimeout(nInterval);
+
+ nUpdateInterval = nInterval;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/settings.cxx b/vcl/source/window/settings.cxx
new file mode 100644
index 000000000..d7fee9b1b
--- /dev/null
+++ b/vcl/source/window/settings.cxx
@@ -0,0 +1,266 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <i18nlangtag/languagetag.hxx>
+#include <i18nlangtag/mslangid.hxx>
+
+#include <vcl/event.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/window.hxx>
+#include <vcl/settings.hxx>
+
+#include <unotools/configmgr.hxx>
+#include <unotools/confignode.hxx>
+
+#include <comphelper/processfactory.hxx>
+
+#include <salframe.hxx>
+#include <brdwin.hxx>
+
+#include <window.h>
+
+namespace vcl {
+
+void Window::SetSettings( const AllSettings& rSettings )
+{
+ SetSettings( rSettings, false );
+}
+
+void Window::SetSettings( const AllSettings& rSettings, bool bChild )
+{
+
+ if ( mpWindowImpl->mpBorderWindow )
+ {
+ mpWindowImpl->mpBorderWindow->SetSettings( rSettings, false );
+ if ( (mpWindowImpl->mpBorderWindow->GetType() == WindowType::BORDERWINDOW) &&
+ static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->mpMenuBarWindow )
+ static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->mpMenuBarWindow->SetSettings( rSettings, true );
+ }
+
+ AllSettings aOldSettings(*mxSettings);
+ OutputDevice::SetSettings( rSettings );
+ AllSettingsFlags nChangeFlags = aOldSettings.GetChangeFlags( rSettings );
+
+ // recalculate AppFont-resolution and DPI-resolution
+ ImplInitResolutionSettings();
+
+ if ( bool(nChangeFlags) )
+ {
+ DataChangedEvent aDCEvt( DataChangedEventType::SETTINGS, &aOldSettings, nChangeFlags );
+ DataChanged( aDCEvt );
+ }
+
+ if ( bChild )
+ {
+ vcl::Window* pChild = mpWindowImpl->mpFirstChild;
+ while ( pChild )
+ {
+ pChild->SetSettings( rSettings, bChild );
+ pChild = pChild->mpWindowImpl->mpNext;
+ }
+ }
+}
+
+void Window::UpdateSettings( const AllSettings& rSettings, bool bChild )
+{
+
+ if ( mpWindowImpl->mpBorderWindow )
+ {
+ mpWindowImpl->mpBorderWindow->UpdateSettings( rSettings );
+ if ( (mpWindowImpl->mpBorderWindow->GetType() == WindowType::BORDERWINDOW) &&
+ static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->mpMenuBarWindow )
+ static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->mpMenuBarWindow->UpdateSettings( rSettings, true );
+ }
+
+ AllSettings aOldSettings(*mxSettings);
+ AllSettingsFlags nChangeFlags = mxSettings->Update( AllSettings::GetWindowUpdate(), rSettings );
+
+ // recalculate AppFont-resolution and DPI-resolution
+ ImplInitResolutionSettings();
+
+ /* #i73785#
+ * do not overwrite a WheelBehavior with false
+ * this looks kind of a hack, but WheelBehavior
+ * is always a local change, not a system property,
+ * so we can spare all our users the hassle of reacting on
+ * this in their respective DataChanged.
+ */
+ MouseSettings aSet( mxSettings->GetMouseSettings() );
+ aSet.SetWheelBehavior( aOldSettings.GetMouseSettings().GetWheelBehavior() );
+ mxSettings->SetMouseSettings( aSet );
+
+ if( (nChangeFlags & AllSettingsFlags::STYLE) && IsBackground() )
+ {
+ Wallpaper aWallpaper = GetBackground();
+ if( !aWallpaper.IsBitmap() && !aWallpaper.IsGradient() )
+ {
+ if ( mpWindowImpl->mnStyle & WB_3DLOOK )
+ {
+ if (aOldSettings.GetStyleSettings().GetFaceColor() != rSettings.GetStyleSettings().GetFaceColor())
+ SetBackground( Wallpaper( rSettings.GetStyleSettings().GetFaceColor() ) );
+ }
+ else
+ {
+ if (aOldSettings.GetStyleSettings().GetWindowColor() != rSettings.GetStyleSettings().GetWindowColor())
+ SetBackground( Wallpaper( rSettings.GetStyleSettings().GetWindowColor() ) );
+ }
+ }
+ }
+
+ if ( bool(nChangeFlags) )
+ {
+ DataChangedEvent aDCEvt( DataChangedEventType::SETTINGS, &aOldSettings, nChangeFlags );
+ DataChanged( aDCEvt );
+ // notify data change handler
+ CallEventListeners( VclEventId::WindowDataChanged, &aDCEvt);
+ }
+
+ if ( bChild )
+ {
+ vcl::Window* pChild = mpWindowImpl->mpFirstChild;
+ while ( pChild )
+ {
+ pChild->UpdateSettings( rSettings, bChild );
+ pChild = pChild->mpWindowImpl->mpNext;
+ }
+ }
+}
+
+void Window::ImplUpdateGlobalSettings( AllSettings& rSettings, bool bCallHdl ) const
+{
+ StyleSettings aTmpSt( rSettings.GetStyleSettings() );
+ aTmpSt.SetHighContrastMode( false );
+ rSettings.SetStyleSettings( aTmpSt );
+ ImplGetFrame()->UpdateSettings( rSettings );
+
+ StyleSettings aStyleSettings = rSettings.GetStyleSettings();
+
+ vcl::Font aFont = aStyleSettings.GetMenuFont();
+ int defFontheight = aFont.GetFontHeight();
+
+ // if the UI is korean, chinese or another locale
+ // where the system font size is known to be often too small to
+ // generate readable fonts enforce a minimum font size of 9 points
+ bool bBrokenLangFontHeight = MsLangId::isCJK(Application::GetSettings().GetUILanguageTag().getLanguageType());
+ if (bBrokenLangFontHeight)
+ defFontheight = std::max(9, defFontheight);
+
+ // i22098, toolfont will be scaled differently to avoid bloated rulers and status bars for big fonts
+ int toolfontheight = defFontheight;
+ if( toolfontheight > 9 )
+ toolfontheight = (defFontheight+8) / 2;
+
+ aFont = aStyleSettings.GetAppFont();
+ aFont.SetFontHeight( defFontheight );
+ aStyleSettings.SetAppFont( aFont );
+ aFont = aStyleSettings.GetTitleFont();
+ aFont.SetFontHeight( defFontheight );
+ aStyleSettings.SetTitleFont( aFont );
+ aFont = aStyleSettings.GetFloatTitleFont();
+ aFont.SetFontHeight( defFontheight );
+ aStyleSettings.SetFloatTitleFont( aFont );
+ // keep menu and help font size from system unless in broken locale size
+ if( bBrokenLangFontHeight )
+ {
+ aFont = aStyleSettings.GetMenuFont();
+ if( aFont.GetFontHeight() < defFontheight )
+ {
+ aFont.SetFontHeight( defFontheight );
+ aStyleSettings.SetMenuFont( aFont );
+ }
+ aFont = aStyleSettings.GetHelpFont();
+ if( aFont.GetFontHeight() < defFontheight )
+ {
+ aFont.SetFontHeight( defFontheight );
+ aStyleSettings.SetHelpFont( aFont );
+ }
+ }
+
+ // use different height for toolfont
+ aFont = aStyleSettings.GetToolFont();
+ aFont.SetFontHeight( toolfontheight );
+ aStyleSettings.SetToolFont( aFont );
+
+ aFont = aStyleSettings.GetLabelFont();
+ aFont.SetFontHeight( defFontheight );
+ aStyleSettings.SetLabelFont( aFont );
+ aFont = aStyleSettings.GetRadioCheckFont();
+ aFont.SetFontHeight( defFontheight );
+ aStyleSettings.SetRadioCheckFont( aFont );
+ aFont = aStyleSettings.GetPushButtonFont();
+ aFont.SetFontHeight( defFontheight );
+ aStyleSettings.SetPushButtonFont( aFont );
+ aFont = aStyleSettings.GetFieldFont();
+ aFont.SetFontHeight( defFontheight );
+ aStyleSettings.SetFieldFont( aFont );
+ aFont = aStyleSettings.GetIconFont();
+ aFont.SetFontHeight( defFontheight );
+ aStyleSettings.SetIconFont( aFont );
+ aFont = aStyleSettings.GetTabFont();
+ aFont.SetFontHeight( defFontheight );
+ aStyleSettings.SetTabFont( aFont );
+ aFont = aStyleSettings.GetGroupFont();
+ aFont.SetFontHeight( defFontheight );
+ aStyleSettings.SetGroupFont( aFont );
+
+ rSettings.SetStyleSettings( aStyleSettings );
+
+ bool bForceHCMode = false;
+
+ // auto detect HC mode; if the system already set it to "yes"
+ // (see above) then accept that
+ if (!rSettings.GetStyleSettings().GetHighContrastMode() && !utl::ConfigManager::IsFuzzing())
+ {
+ bool bAutoHCMode = true;
+ utl::OConfigurationNode aNode = utl::OConfigurationTreeRoot::tryCreateWithComponentContext(
+ comphelper::getProcessComponentContext(),
+ "org.openoffice.Office.Common/Accessibility" ); // note: case sensitive !
+ if ( aNode.isValid() )
+ {
+ css::uno::Any aValue = aNode.getNodeValue( "AutoDetectSystemHC" );
+ bool bTmp = false;
+ if( aValue >>= bTmp )
+ bAutoHCMode = bTmp;
+ }
+ if( bAutoHCMode )
+ {
+ if( rSettings.GetStyleSettings().GetFaceColor().IsDark() ||
+ rSettings.GetStyleSettings().GetWindowColor().IsDark() )
+ bForceHCMode = true;
+ }
+ }
+
+ static const char* pEnvHC = getenv( "SAL_FORCE_HC" );
+ if( pEnvHC && *pEnvHC )
+ bForceHCMode = true;
+
+ if( bForceHCMode )
+ {
+ aStyleSettings = rSettings.GetStyleSettings();
+ aStyleSettings.SetHighContrastMode( true );
+ rSettings.SetStyleSettings( aStyleSettings );
+ }
+
+ if ( bCallHdl )
+ GetpApp()->OverrideSystemSettings( rSettings );
+}
+
+} /*namespace vcl*/
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/split.cxx b/vcl/source/window/split.cxx
new file mode 100644
index 000000000..44b44d33e
--- /dev/null
+++ b/vcl/source/window/split.cxx
@@ -0,0 +1,701 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <rtl/instance.hxx>
+
+#include <window.h>
+
+namespace
+{
+ struct ImplBlackWall
+ : public rtl::StaticWithInit<Wallpaper, ImplBlackWall> {
+ Wallpaper operator () () {
+ return Wallpaper(COL_BLACK);
+ }
+ };
+ struct ImplWhiteWall
+ : public rtl::StaticWithInit<Wallpaper, ImplWhiteWall> {
+ Wallpaper operator () () {
+ return Wallpaper(COL_LIGHTGRAY);
+ }
+ };
+}
+
+// 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::get() );
+ else
+ SetBackground( ImplBlackWall::get() );
+
+ 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 );
+
+ SetLineColor();
+ 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( 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();
+ long nA = rSettings.GetScrollBarSize();
+ long nB = StyleSettings::GetSplitSize();
+
+ Size aSize = 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 );
+ 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() )
+ {
+ 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;
+ 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 )
+ {
+ 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->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;
+ 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( 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->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->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 );
+ 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 );
+ 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 )
+ {
+ 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::get() );
+ else
+ SetBackground( ImplBlackWall::get() );
+ }
+ }
+}
+
+void Splitter::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rPaintRect)
+{
+ rRenderContext.DrawRect(rPaintRect);
+
+ tools::Polygon aPoly(rPaintRect);
+ tools::PolyPolygon aPolyPoly(aPoly);
+ rRenderContext.DrawTransparent(aPolyPoly, 85);
+
+ if (mbKbdSplitting)
+ {
+ LineInfo aInfo( LineStyle::Dash );
+ //aInfo.SetDashLen( 2 );
+ //aInfo.SetDashCount( 1 );
+ aInfo.SetDistance( 1 );
+ aInfo.SetDotLen( 2 );
+ aInfo.SetDotCount( 3 );
+
+ rRenderContext.DrawPolyLine( aPoly, aInfo );
+ }
+ else
+ {
+ rRenderContext.DrawRect(rPaintRect);
+ }
+}
+
+Size Splitter::GetOptimalSize() const
+{
+ return LogicToPixel(Size(3, 3), MapMode(MapUnit::MapAppFont));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/splitwin.cxx b/vcl/source/window/splitwin.cxx
new file mode 100644
index 000000000..9acc609c9
--- /dev/null
+++ b/vcl/source/window/splitwin.cxx
@@ -0,0 +1,2757 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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_SPLITSIZE 4
+#define SPLITWIN_SPLITSIZEEX 4
+#define SPLITWIN_SPLITSIZEEXLN 7
+#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();
+
+ long mnSize;
+ long mnPixSize;
+ long mnLeft;
+ long mnTop;
+ long mnWidth;
+ long mnHeight;
+ long mnSplitPos;
+ long mnSplitSize;
+ long mnOldSplitPos;
+ long mnOldSplitSize;
+ long mnOldWidth;
+ 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.
+ long mnMinSize;
+ /// Maximal width or height of the item. -1 means no restriction.
+ long mnMaxSize;
+};
+
+}
+
+class ImplSplitSet
+{
+public:
+ ImplSplitSet();
+
+ std::vector< ImplSplitItem > mvItems;
+ long mnLastSize;
+ 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 {
+ long ValidateSize (const 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,
+ long& rLeft, long& rTop,
+ long& rRight, 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();
+ long nDX = mnDX;
+ long nDY = mnDY;
+
+ switch (meAlign)
+ {
+ case WindowAlign::Bottom:
+ rRenderContext.SetLineColor(rStyleSettings.GetShadowColor());
+ rRenderContext.DrawLine(Point(0, 0), Point(nDX - 1, 0));
+ rRenderContext.DrawLine(Point(0, nDY - 2), Point(nDX - 1, nDY - 2));
+
+ rRenderContext.SetLineColor(rStyleSettings.GetLightColor());
+ rRenderContext.DrawLine(Point(0, 1), Point(nDX - 1, 1));
+ rRenderContext.DrawLine(Point(0, nDY - 1), Point(nDX - 1, nDY - 1));
+ break;
+ case WindowAlign::Top:
+ rRenderContext.SetLineColor(rStyleSettings.GetShadowColor());
+ rRenderContext.DrawLine(Point(0, nDY - 2), Point(nDX - 1, nDY - 2));
+ rRenderContext.DrawLine(Point(0, 0), Point(nDX - 1, 0));
+
+ rRenderContext.SetLineColor(rStyleSettings.GetLightColor());
+ rRenderContext.DrawLine(Point(0, nDY - 1), Point(nDX - 1, nDY - 1));
+ rRenderContext.DrawLine(Point(0, 1), Point(nDX - 1, 1));
+ break;
+ case WindowAlign::Left:
+ rRenderContext.SetLineColor(rStyleSettings.GetShadowColor());
+ rRenderContext.DrawLine(Point(nDX - 2, 0), Point(nDX - 2, nDY - 2));
+ rRenderContext.DrawLine(Point(0, 0), Point(nDX - 1, 0));
+ 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, 1), Point(nDX - 3, 1));
+ rRenderContext.DrawLine(Point(0, nDY - 1), Point(nDX - 2, nDY - 1));
+ break;
+ default:
+ rRenderContext.SetLineColor(rStyleSettings.GetShadowColor());
+ rRenderContext.DrawLine(Point(0, 0), Point( 0, nDY - 2));
+ rRenderContext.DrawLine(Point(0, 0), Point( nDX - 1, 0));
+ 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(1, 1), Point(nDX - 1, 1));
+ rRenderContext.DrawLine(Point(0, nDY - 1), Point(nDX - 1, nDY - 1));
+ }
+}
+
+void SplitWindow::ImplDrawBorderLine(vcl::RenderContext& rRenderContext)
+{
+ if (mbFadeOut)
+ {
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ long nDX = mnDX;
+ 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:
+ rRenderContext.SetLineColor( rStyleSettings.GetShadowColor() );
+ rRenderContext.DrawLine( Point( SPLITWIN_SPLITSIZEEXLN-1, 0 ), Point( SPLITWIN_SPLITSIZEEXLN-1, nDY-2 ) );
+
+ rRenderContext.SetLineColor( rStyleSettings.GetLightColor() );
+ rRenderContext.DrawLine( Point( SPLITWIN_SPLITSIZEEXLN, 1 ), Point( SPLITWIN_SPLITSIZEEXLN, nDY-3 ) );
+ 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.IsInside( 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,
+ long nSetLeft, long nSetTop,
+ long nSetWidth, 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;
+ long nCalcSize;
+ long nPos;
+ 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) )
+ {
+ long nPercentFactor = 10;
+ long nRelCount = 0;
+ long nPercent = 0;
+ long nRelPercent = 0;
+ long nAbsSize = 0;
+ 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 )
+ {
+ 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;
+ 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;
+ 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 )
+ {
+ 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
+ long nErrorSum = nSizeDelta % nCalcItems;
+ long nCurSizeDelta = nSizeDelta / nCalcItems;
+ nMins = 0;
+ for ( auto& rItem : rItems )
+ {
+ if ( rItem.mbSubSize )
+ {
+ long* pSize = &(rItem.mnPixSize);
+ long nTempErr;
+
+ if ( nErrorSum )
+ {
+ if ( nErrorSum < 0 )
+ nTempErr = -1;
+ else
+ nTempErr = 1;
+ }
+ else
+ nTempErr = 0;
+
+ if ( (*pSize+nCurSizeDelta+nTempErr) <= 0 )
+ {
+ 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 adept 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;
+ long nRelSize = 0;
+ 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();
+ long nPos;
+ long nTop;
+ 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;
+
+ long nItemSplitSize = rItems[i].mnSplitSize;
+ 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,
+ long& rMouseOff, ImplSplitSet** ppFoundSet, sal_uInt16& rFoundPos,
+ bool bRows )
+{
+ if ( pSet->mvItems.empty() )
+ return 0;
+
+ sal_uInt16 nSplitTest;
+ size_t nItems = pSet->mvItems.size();
+ long nMPos1;
+ long nMPos2;
+ long nPos;
+ long nTop;
+ 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,
+ long& rMouseOff, ImplSplitSet** ppFoundSet, sal_uInt16& rFoundPos )
+{
+ // Resizable SplitWindow should be treated different
+ if ( pWindow->mnWinStyle & WB_SIZEABLE )
+ {
+ long nTPos;
+ long nPos;
+ 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();
+ }
+ 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 )
+{
+ ImplInit( pParent, nStyle );
+ DockingWindow::SetIdleDebugName( "vcl::SplitWindow maLayoutIdle" );
+}
+
+SplitWindow::~SplitWindow()
+{
+ disposeOnce();
+}
+
+void SplitWindow::dispose()
+{
+ // delete Sets
+ mpMainSet.reset();
+ DockingWindow::dispose();
+}
+
+void SplitWindow::ImplSetWindowSize( 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 );
+ 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 )
+ {
+ 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() )
+ {
+ long nDelta = 0;
+ 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;
+
+ 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 )
+ {
+ 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() )
+ {
+ 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
+ long nL;
+ long nT;
+ long nW;
+ 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
+{
+ long nSplitSize = mpMainSet->mnSplitSize-1;
+ if (mbFadeOut || mbFadeIn)
+ nSplitSize += SPLITWIN_SPLITSIZEEX;
+
+ long nButtonSize = 0;
+ if ( mbFadeIn )
+ nButtonSize += SPLITWIN_SPLITSIZEFADE+1;
+ if ( mbFadeOut )
+ nButtonSize += SPLITWIN_SPLITSIZEFADE+1;
+ long nCenterEx = 0;
+ if ( mbHorz )
+ nCenterEx += ((mnDX-mnLeftBorder-mnRightBorder)-nButtonSize)/2;
+ else
+ nCenterEx += ((mnDY-mnTopBorder-mnBottomBorder)-nButtonSize)/2;
+ 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.IsInside(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::EnableB2dDraw);
+
+ long nWidth = rRect.getWidth();
+ long nWidthHalf = nWidth / 2;
+ long nHeight = rRect.getHeight();
+ long nHeightHalf = nHeight / 2;
+
+ long nLeft = rRect.Left();
+ long nRight = rRect.Right();
+ long nTop = rRect.Top();
+ long nBottom = rRect.Bottom();
+ long nMargin = 1;
+
+ rRenderContext.SetLineColor(aColor);
+ rRenderContext.SetFillColor(aColor);
+
+ tools::Polygon aPoly(3);
+
+ if (bHorizontal)
+ {
+ 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
+ {
+ 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)
+ {
+ 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)
+ {
+ 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;
+ 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 )
+ {
+ 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 = new 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.IsInside( aMousePosPixel ) )
+ {
+ mbFadeOutDown = true;
+ mbFadeOutPressed = true;
+ Invalidate();
+ }
+ else
+ {
+ ImplGetFadeInRect( aTestRect, true );
+ if ( aTestRect.IsInside( 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();
+ 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.IsInside( aPos ) &&
+ !aFadeOutRect.IsInside( 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.IsInside( 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.IsInside( 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;
+ long nDelta = mnMSplitPos-mnMStartPos;
+
+ if ( (mnSplitTest & SPLIT_WINDOW) && mpMainSet->mvItems.empty() )
+ {
+ if ( (mpSplitSet == mpMainSet.get()) && mbBottomRight )
+ nDelta *= -1;
+ ImplSetWindowSize( nDelta );
+ }
+ else
+ {
+ 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() )
+ {
+ delete [] mpLastSizes;
+ mpLastSizes = nullptr;
+ mpSplitSet = nullptr;
+ mnMouseOff = 0;
+ mnMStartPos = 0;
+ mnMSplitPos = 0;
+ mnMouseModifier = 0;
+ mnSplitTest = 0;
+ mnSplitPos = 0;
+ }
+ }
+}
+
+bool SplitWindow::PreNotify( NotifyEvent& rNEvt )
+{
+ if( rNEvt.GetType() == MouseNotifyEvent::MOUSEMOVE )
+ {
+ const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent();
+ if( pMouseEvt && !pMouseEvt->GetButtons() && !pMouseEvt->IsSynthetic() && !pMouseEvt->IsModifierChanged() )
+ {
+ // trigger redraw if mouse over state has changed
+ tools::Rectangle aFadeInRect;
+ tools::Rectangle aFadeOutRect;
+ ImplGetFadeInRect( aFadeInRect );
+ ImplGetFadeOutRect( aFadeOutRect );
+
+ if ( aFadeInRect.IsInside( GetPointerPosPixel() ) != aFadeInRect.IsInside( GetLastPointerPosPixel() ) )
+ Invalidate( aFadeInRect );
+ if ( aFadeOutRect.IsInside( GetPointerPosPixel() ) != aFadeOutRect.IsInside( 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;
+ const char* pHelpResId = nullptr;
+
+ ImplGetFadeInRect( aHelpRect, true );
+ if ( aHelpRect.IsInside( aMousePosPixel ) )
+ pHelpResId = SV_HELPTEXT_FADEIN;
+ else
+ {
+ ImplGetFadeOutRect( aHelpRect );
+ if ( aHelpRect.IsInside( 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, 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, 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, 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;
+ }
+
+ long nDelta = nNewSize-rItems[nPos].mnPixSize;
+ if ( !nDelta )
+ return;
+
+ // calculate area, which could be affected by splitting
+ sal_uInt16 nMin = 0;
+ sal_uInt16 nMax = nItems;
+ for (size_t i = 0; i < nItems; ++i)
+ {
+ if ( rItems[i].mbFixed )
+ {
+ if ( i < nPos )
+ nMin = i+1;
+ else
+ nMax = i;
+ }
+ }
+
+ // treat TopSet different if the window is sizeable
+ bool bSmall = true;
+ bool bGreat = true;
+ if ( (pSet == mpMainSet.get()) && (mnWinStyle & WB_SIZEABLE) )
+ {
+ if ( nPos < pSet->mvItems.size()-1 )
+ {
+ if ( !((bPropSmall && bPropGreat) ||
+ ((nDelta > 0) && bPropSmall) ||
+ ((nDelta < 0) && bPropGreat)) )
+ {
+ if ( nDelta < 0 )
+ bGreat = false;
+ else
+ bSmall = false;
+ }
+ }
+ else
+ {
+ if ( nDelta < 0 )
+ bGreat = false;
+ else
+ bSmall = false;
+ }
+ }
+ else if ( nPos >= nMax )
+ {
+ bSmall = false;
+ bGreat = false;
+ }
+ else if ( nPos && (nPos >= pSet->mvItems.size()-1) )
+ {
+ nPos--;
+ nDelta *= -1;
+ bool bTemp = bPropSmall;
+ bPropSmall = bPropGreat;
+ bPropGreat = bTemp;
+ }
+
+ sal_uInt16 n;
+ // now splitt the windows
+ if ( nDelta < 0 )
+ {
+ if ( bGreat )
+ {
+ if ( bPropGreat )
+ {
+ 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 )
+ {
+ 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, 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();
+ }
+}
+
+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;
+}
+
+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();
+
+ long nRelSize = 0;
+ 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();
+}
+
+long SplitWindow::GetFadeInSize() const
+{
+ long n = 0;
+
+ if ( mbHorz )
+ n = mnTopBorder+mnBottomBorder;
+ else
+ n = mnLeftBorder+mnRightBorder;
+
+ return n+SPLITWIN_SPLITSIZE+SPLITWIN_SPLITSIZEEX-2;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/stacking.cxx b/vcl/source/window/stacking.cxx
new file mode 100644
index 000000000..d5b130031
--- /dev/null
+++ b/vcl/source/window/stacking.cxx
@@ -0,0 +1,1162 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <vcl/syswin.hxx>
+#include <vcl/window.hxx>
+#include <vcl/taskpanelist.hxx>
+#include <sal/log.hxx>
+
+#include <salframe.hxx>
+#include <salobj.hxx>
+#include <svdata.hxx>
+#include <window.h>
+#include <brdwin.hxx>
+
+#include <com/sun/star/awt/XTopWindow.hpp>
+
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::datatransfer::clipboard;
+using namespace ::com::sun::star::datatransfer::dnd;
+using namespace ::com::sun::star;
+
+using ::com::sun::star::awt::XTopWindow;
+
+struct ImplCalcToTopData
+{
+ std::unique_ptr<ImplCalcToTopData> mpNext;
+ VclPtr<vcl::Window> mpWindow;
+ std::unique_ptr<vcl::Region> mpInvalidateRegion;
+};
+
+namespace vcl {
+
+vcl::Window* Window::ImplGetTopmostFrameWindow()
+{
+ vcl::Window *pTopmostParent = this;
+ while( pTopmostParent->ImplGetParent() )
+ pTopmostParent = pTopmostParent->ImplGetParent();
+ return pTopmostParent->mpWindowImpl->mpFrameWindow;
+}
+
+void Window::ImplInsertWindow( vcl::Window* pParent )
+{
+ mpWindowImpl->mpParent = pParent;
+ mpWindowImpl->mpRealParent = pParent;
+
+ if ( pParent && !mpWindowImpl->mbFrame )
+ {
+ // 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) )
+ {
+ // 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 )
+ {
+ if ( IsReallyVisible() )
+ {
+ // calculate region, where the window overlaps with other windows
+ Point aPoint( mnOutOffX, mnOutOffY );
+ vcl::Region aRegion( tools::Rectangle( aPoint,
+ Size( mnOutWidth, mnOutHeight ) ) );
+ 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() )
+ {
+ if ( mpWindowImpl->mbInitWinClipRegion || !mpWindowImpl->maWinClipRegion.IsEmpty() )
+ {
+ 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 )
+ {
+ // Invalidate all windows which are next to each other
+ // Is INCOMPLETE !!!
+ tools::Rectangle aWinRect( Point( mnOutOffX, mnOutOffY ), Size( mnOutWidth, mnOutHeight ) );
+ 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( Point( pWindow->mnOutOffX, pWindow->mnOutOffY ),
+ Size( pWindow->mnOutWidth, pWindow->mnOutHeight ) );
+ if ( aWinRect.IsOver( 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( Point( pWindow->mnOutOffX, pWindow->mnOutOffY ),
+ Size( pWindow->mnOutWidth, pWindow->mnOutHeight ) );
+ if ( aWinRect.IsOver( 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::FindWindow( const Point& rPos ) const
+{
+
+ Point aPos = OutputToScreenPixel( rPos );
+ return const_cast<vcl::Window*>(this)->ImplFindWindow( aPos );
+}
+
+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;
+
+ mbDevOutput = false;
+ mpWindowImpl->mbReallyVisible = false;
+ mpWindowImpl->mbReallyShown = false;
+
+ // the SHOW/HIDE events serve as indicators to send child creation/destroy events to the access bridge.
+ // For this, the data member of the event must not be NULL.
+ // Previously, we did this in Window::Show, but there some events got lost in certain situations.
+ if( bBecameReallyInvisible && ImplIsAccessibleCandidate() )
+ CallEventListeners( VclEventId::WindowHide, this );
+ // TODO. It's kind of a hack that we're re-using the VclEventId::WindowHide. Normally, we should
+ // introduce another event which explicitly triggers the Accessibility implementations.
+
+ vcl::Window* pWindow = mpWindowImpl->mpFirstOverlap;
+ while ( pWindow )
+ {
+ if ( pWindow->mpWindowImpl->mbReallyVisible )
+ pWindow->ImplResetReallyVisible();
+ pWindow = pWindow->mpWindowImpl->mpNext;
+ }
+
+ pWindow = mpWindowImpl->mpFirstChild;
+ while ( pWindow )
+ {
+ if ( pWindow->mpWindowImpl->mbReallyVisible )
+ pWindow->ImplResetReallyVisible();
+ pWindow = pWindow->mpWindowImpl->mpNext;
+ }
+}
+
+void Window::ImplUpdateWindowPtr( vcl::Window* pWindow )
+{
+ if ( mpWindowImpl->mpFrameWindow != pWindow->mpWindowImpl->mpFrameWindow )
+ {
+ // release graphic
+ OutputDevice *pOutDev = GetOutDev();
+ pOutDev->ReleaseGraphics();
+ }
+
+ mpWindowImpl->mpFrameData = pWindow->mpWindowImpl->mpFrameData;
+ if (mpWindowImpl->mpFrame != pWindow->mpWindowImpl->mpFrame)
+ {
+ mpWindowImpl->mpFrame = pWindow->mpWindowImpl->mpFrame;
+ if (mpWindowImpl->mpSysObj)
+ mpWindowImpl->mpSysObj->Reparent(mpWindowImpl->mpFrame);
+ }
+ mpWindowImpl->mpFrameWindow = pWindow->mpWindowImpl->mpFrameWindow;
+ if ( pWindow->ImplIsOverlapWindow() )
+ mpWindowImpl->mpOverlapWindow = pWindow;
+ else
+ mpWindowImpl->mpOverlapWindow = pWindow->mpWindowImpl->mpOverlapWindow;
+
+ vcl::Window* pChild = mpWindowImpl->mpFirstChild;
+ while ( pChild )
+ {
+ pChild->ImplUpdateWindowPtr( pWindow );
+ pChild = pChild->mpWindowImpl->mpNext;
+ }
+}
+
+void Window::ImplUpdateWindowPtr()
+{
+ vcl::Window* pChild = mpWindowImpl->mpFirstChild;
+ while ( pChild )
+ {
+ pChild->ImplUpdateWindowPtr( this );
+ pChild = pChild->mpWindowImpl->mpNext;
+ }
+}
+
+void Window::ImplUpdateOverlapWindowPtr( bool bNewFrame )
+{
+ bool bVisible = IsVisible();
+ Show( false );
+ ImplRemoveWindow( bNewFrame );
+ vcl::Window* pRealParent = mpWindowImpl->mpRealParent;
+ ImplInsertWindow( ImplGetParent() );
+ mpWindowImpl->mpRealParent = pRealParent;
+ ImplUpdateWindowPtr();
+ if ( ImplUpdatePos() )
+ ImplUpdateSysObjPos();
+
+ if ( bNewFrame )
+ {
+ vcl::Window* pOverlapWindow = mpWindowImpl->mpFirstOverlap;
+ while ( pOverlapWindow )
+ {
+ vcl::Window* pNextOverlapWindow = pOverlapWindow->mpWindowImpl->mpNext;
+ pOverlapWindow->ImplUpdateOverlapWindowPtr( bNewFrame );
+ pOverlapWindow = pNextOverlapWindow;
+ }
+ }
+
+ if ( bVisible )
+ Show();
+}
+
+SystemWindow* Window::GetSystemWindow() const
+{
+
+ const vcl::Window* pWin = this;
+ while ( pWin && !pWin->IsSystemWindow() )
+ pWin = pWin->GetParent();
+ return static_cast<SystemWindow*>(const_cast<Window*>(pWin));
+}
+
+static SystemWindow *ImplGetLastSystemWindow( vcl::Window *pWin )
+{
+ // get the most top-level system window, the one that contains the taskpanelist
+ SystemWindow *pSysWin = nullptr;
+ if( !pWin )
+ return pSysWin;
+ vcl::Window *pMyParent = pWin;
+ while ( pMyParent )
+ {
+ if ( pMyParent->IsSystemWindow() )
+ pSysWin = static_cast<SystemWindow*>(pMyParent);
+ pMyParent = pMyParent->GetParent();
+ }
+ return pSysWin;
+}
+
+void Window::SetParent( vcl::Window* pNewParent )
+{
+ SAL_WARN_IF( !pNewParent, "vcl", "Window::SetParent(): pParent == NULL" );
+ SAL_WARN_IF( pNewParent == this, "vcl", "someone tried to reparent a window to itself" );
+
+ if( !pNewParent || pNewParent == this )
+ return;
+
+ // check if the taskpanelist would change and move the window pointer accordingly
+ SystemWindow *pSysWin = ImplGetLastSystemWindow(this);
+ SystemWindow *pNewSysWin = nullptr;
+ bool bChangeTaskPaneList = false;
+ if( pSysWin && pSysWin->ImplIsInTaskPaneList( this ) )
+ {
+ pNewSysWin = ImplGetLastSystemWindow( pNewParent );
+ if( pNewSysWin && pNewSysWin != pSysWin )
+ {
+ bChangeTaskPaneList = true;
+ pSysWin->GetTaskPaneList()->RemoveWindow( this );
+ }
+ }
+ // remove ownerdraw decorated windows from list in the top-most frame window
+ if( (GetStyle() & WB_OWNERDRAWDECORATION) && mpWindowImpl->mbFrame )
+ {
+ ::std::vector< VclPtr<vcl::Window> >& rList = ImplGetOwnerDrawList();
+ auto p = ::std::find( rList.begin(), rList.end(), VclPtr<vcl::Window>(this) );
+ if( p != rList.end() )
+ rList.erase( p );
+ }
+
+ ImplSetFrameParent( pNewParent );
+
+ if ( mpWindowImpl->mpBorderWindow )
+ {
+ mpWindowImpl->mpRealParent = pNewParent;
+ mpWindowImpl->mpBorderWindow->SetParent( pNewParent );
+ return;
+ }
+
+ if ( mpWindowImpl->mpParent.get() == pNewParent )
+ return;
+
+ if ( mpWindowImpl->mbFrame )
+ mpWindowImpl->mpFrame->SetParent( pNewParent->mpWindowImpl->mpFrame );
+
+ bool bVisible = IsVisible();
+ Show( false, ShowFlags::NoFocusChange );
+
+ // check if the overlap window changes
+ vcl::Window* pOldOverlapWindow;
+ vcl::Window* pNewOverlapWindow = nullptr;
+ if ( ImplIsOverlapWindow() )
+ pOldOverlapWindow = nullptr;
+ else
+ {
+ pNewOverlapWindow = pNewParent->ImplGetFirstOverlapWindow();
+ if ( mpWindowImpl->mpOverlapWindow.get() != pNewOverlapWindow )
+ pOldOverlapWindow = mpWindowImpl->mpOverlapWindow;
+ else
+ pOldOverlapWindow = nullptr;
+ }
+
+ // convert windows in the hierarchy
+ bool bFocusOverlapWin = HasChildPathFocus( true );
+ bool bFocusWin = HasChildPathFocus();
+ bool bNewFrame = pNewParent->mpWindowImpl->mpFrameWindow != mpWindowImpl->mpFrameWindow;
+ if ( bNewFrame )
+ {
+ if ( mpWindowImpl->mpFrameData->mpFocusWin )
+ {
+ if ( IsWindowOrChild( mpWindowImpl->mpFrameData->mpFocusWin ) )
+ mpWindowImpl->mpFrameData->mpFocusWin = nullptr;
+ }
+ if ( mpWindowImpl->mpFrameData->mpMouseMoveWin )
+ {
+ if ( IsWindowOrChild( mpWindowImpl->mpFrameData->mpMouseMoveWin ) )
+ mpWindowImpl->mpFrameData->mpMouseMoveWin = nullptr;
+ }
+ if ( mpWindowImpl->mpFrameData->mpMouseDownWin )
+ {
+ if ( IsWindowOrChild( mpWindowImpl->mpFrameData->mpMouseDownWin ) )
+ mpWindowImpl->mpFrameData->mpMouseDownWin = nullptr;
+ }
+ }
+ ImplRemoveWindow( bNewFrame );
+ ImplInsertWindow( pNewParent );
+ if ( mpWindowImpl->mnParentClipMode & ParentClipMode::Clip )
+ pNewParent->mpWindowImpl->mbClipChildren = true;
+ ImplUpdateWindowPtr();
+ if ( ImplUpdatePos() )
+ ImplUpdateSysObjPos();
+
+ // If the Overlap-Window has changed, we need to test whether
+ // OverlapWindows that had the Child window as their parent
+ // need to be put into the window hierarchy.
+ if ( ImplIsOverlapWindow() )
+ {
+ if ( bNewFrame )
+ {
+ vcl::Window* pOverlapWindow = mpWindowImpl->mpFirstOverlap;
+ while ( pOverlapWindow )
+ {
+ vcl::Window* pNextOverlapWindow = pOverlapWindow->mpWindowImpl->mpNext;
+ pOverlapWindow->ImplUpdateOverlapWindowPtr( bNewFrame );
+ pOverlapWindow = pNextOverlapWindow;
+ }
+ }
+ }
+ else if ( pOldOverlapWindow )
+ {
+ // reset Focus-Save
+ if ( bFocusWin ||
+ (pOldOverlapWindow->mpWindowImpl->mpLastFocusWindow &&
+ IsWindowOrChild( pOldOverlapWindow->mpWindowImpl->mpLastFocusWindow )) )
+ pOldOverlapWindow->mpWindowImpl->mpLastFocusWindow = nullptr;
+
+ vcl::Window* pOverlapWindow = pOldOverlapWindow->mpWindowImpl->mpFirstOverlap;
+ while ( pOverlapWindow )
+ {
+ vcl::Window* pNextOverlapWindow = pOverlapWindow->mpWindowImpl->mpNext;
+ if ( ImplIsRealParentPath( pOverlapWindow->ImplGetWindow() ) )
+ pOverlapWindow->ImplUpdateOverlapWindowPtr( bNewFrame );
+ pOverlapWindow = pNextOverlapWindow;
+ }
+
+ // update activate-status at next overlap window
+ if ( HasChildPathFocus( true ) )
+ ImplCallFocusChangeActivate( pNewOverlapWindow, pOldOverlapWindow );
+ }
+
+ // also convert Activate-Status
+ if ( bNewFrame )
+ {
+ if ( (GetType() == WindowType::BORDERWINDOW) &&
+ (ImplGetWindow()->GetType() == WindowType::FLOATINGWINDOW) )
+ static_cast<ImplBorderWindow*>(this)->SetDisplayActive( mpWindowImpl->mpFrameData->mbHasFocus );
+ }
+
+ // when required give focus to new frame if
+ // FocusWindow is changed with SetParent()
+ if ( bFocusOverlapWin )
+ {
+ mpWindowImpl->mpFrameData->mpFocusWin = Application::GetFocusWindow();
+ if ( !mpWindowImpl->mpFrameData->mbHasFocus )
+ {
+ mpWindowImpl->mpFrame->ToTop( SalFrameToTop::NONE );
+ }
+ }
+
+ // Assure DragSource and DropTarget members are created
+ if ( bNewFrame )
+ {
+ GetDropTarget();
+ }
+
+ if( bChangeTaskPaneList )
+ pNewSysWin->GetTaskPaneList()->AddWindow( this );
+
+ if( (GetStyle() & WB_OWNERDRAWDECORATION) && mpWindowImpl->mbFrame )
+ ImplGetOwnerDrawList().emplace_back(this );
+
+ if ( bVisible )
+ Show( true, ShowFlags::NoFocusChange | ShowFlags::NoActivate );
+}
+
+bool Window::IsAncestorOf( const vcl::Window& rWindow ) const
+{
+ return ImplIsRealParentPath(&rWindow);
+}
+
+sal_uInt16 Window::GetChildCount() const
+{
+ if (!mpWindowImpl)
+ return 0;
+
+ sal_uInt16 nChildCount = 0;
+ vcl::Window* pChild = mpWindowImpl->mpFirstChild;
+ while ( pChild )
+ {
+ nChildCount++;
+ pChild = pChild->mpWindowImpl->mpNext;
+ }
+
+ return nChildCount;
+}
+
+vcl::Window* Window::GetChild( sal_uInt16 nChild ) const
+{
+ if (!mpWindowImpl)
+ return nullptr;
+
+ sal_uInt16 nChildCount = 0;
+ vcl::Window* pChild = mpWindowImpl->mpFirstChild;
+ while ( pChild )
+ {
+ if ( nChild == nChildCount )
+ return pChild;
+ pChild = pChild->mpWindowImpl->mpNext;
+ nChildCount++;
+ }
+
+ return nullptr;
+}
+
+vcl::Window* Window::GetWindow( GetWindowType nType ) const
+{
+ if (!mpWindowImpl)
+ return nullptr;
+
+ switch ( nType )
+ {
+ case GetWindowType::Parent:
+ return mpWindowImpl->mpRealParent;
+
+ case GetWindowType::FirstChild:
+ return mpWindowImpl->mpFirstChild;
+
+ case GetWindowType::LastChild:
+ return mpWindowImpl->mpLastChild;
+
+ case GetWindowType::Prev:
+ return mpWindowImpl->mpPrev;
+
+ case GetWindowType::Next:
+ return mpWindowImpl->mpNext;
+
+ case GetWindowType::FirstOverlap:
+ return mpWindowImpl->mpFirstOverlap;
+
+ case GetWindowType::Overlap:
+ if ( ImplIsOverlapWindow() )
+ return const_cast<vcl::Window*>(this);
+ else
+ return mpWindowImpl->mpOverlapWindow;
+
+ case GetWindowType::ParentOverlap:
+ if ( ImplIsOverlapWindow() )
+ return mpWindowImpl->mpOverlapWindow;
+ else
+ return mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpOverlapWindow;
+
+ case GetWindowType::Client:
+ return this->ImplGetWindow();
+
+ case GetWindowType::RealParent:
+ return ImplGetParent();
+
+ case GetWindowType::Frame:
+ return mpWindowImpl->mpFrameWindow;
+
+ case GetWindowType::Border:
+ if ( mpWindowImpl->mpBorderWindow )
+ return mpWindowImpl->mpBorderWindow->GetWindow( GetWindowType::Border );
+ return const_cast<vcl::Window*>(this);
+
+ case GetWindowType::FirstTopWindowChild:
+ return ImplGetWinData()->maTopWindowChildren.empty() ? nullptr : (*ImplGetWinData()->maTopWindowChildren.begin()).get();
+
+ case GetWindowType::NextTopWindowSibling:
+ {
+ if ( !mpWindowImpl->mpRealParent )
+ return nullptr;
+ const ::std::list< VclPtr<vcl::Window> >& rTopWindows( mpWindowImpl->mpRealParent->ImplGetWinData()->maTopWindowChildren );
+ ::std::list< VclPtr<vcl::Window> >::const_iterator myPos =
+ ::std::find( rTopWindows.begin(), rTopWindows.end(), this );
+ if ( ( myPos == rTopWindows.end() ) || ( ++myPos == rTopWindows.end() ) )
+ return nullptr;
+ return *myPos;
+ }
+
+ }
+
+ return nullptr;
+}
+
+bool Window::IsChild( const vcl::Window* pWindow ) const
+{
+ do
+ {
+ if ( pWindow->ImplIsOverlapWindow() )
+ break;
+
+ pWindow = pWindow->ImplGetParent();
+
+ if ( pWindow == this )
+ return true;
+ }
+ while ( pWindow );
+
+ return false;
+}
+
+bool Window::IsWindowOrChild( const vcl::Window* pWindow, bool bSystemWindow ) const
+{
+
+ if ( this == pWindow )
+ return true;
+ return ImplIsChild( pWindow, bSystemWindow );
+}
+
+void Window::ImplSetFrameParent( const vcl::Window* pParent )
+{
+ vcl::Window* pFrameWindow = ImplGetSVData()->maFrameData.mpFirstFrame;
+ while( pFrameWindow )
+ {
+ // search all frames that are children of this window
+ // and reparent them
+ if( ImplIsRealParentPath( pFrameWindow ) )
+ {
+ SAL_WARN_IF( mpWindowImpl->mpFrame == pFrameWindow->mpWindowImpl->mpFrame, "vcl", "SetFrameParent to own" );
+ SAL_WARN_IF( !mpWindowImpl->mpFrame, "vcl", "no frame" );
+ SalFrame* pParentFrame = pParent ? pParent->mpWindowImpl->mpFrame : nullptr;
+ pFrameWindow->mpWindowImpl->mpFrame->SetParent( pParentFrame );
+ }
+ pFrameWindow = pFrameWindow->mpWindowImpl->mpFrameData->mpNextFrame;
+ }
+}
+
+} /* namespace vcl */
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/status.cxx b/vcl/source/window/status.cxx
new file mode 100644
index 000000000..2eeb5ae0f
--- /dev/null
+++ b/vcl/source/window/status.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 <sal/log.hxx>
+#include <comphelper/string.hxx>
+#include <vcl/event.hxx>
+#include <vcl/decoview.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>
+#if HAVE_FEATURE_OPENGL
+#include <vcl/opengl/OpenGLWrapper.hxx>
+#endif
+#include <svdata.hxx>
+#include <window.h>
+
+#define STATUSBAR_OFFSET_X STATUSBAR_OFFSET
+#define STATUSBAR_OFFSET_Y 2
+#define STATUSBAR_OFFSET_TEXTY 3
+
+#define STATUSBAR_PRGS_OFFSET 3
+#define STATUSBAR_PRGS_COUNT 100
+#define STATUSBAR_PRGS_MIN 5
+
+class StatusBar::ImplData
+{
+public:
+ ImplData();
+
+ VclPtr<VirtualDevice> mpVirDev;
+};
+
+StatusBar::ImplData::ImplData()
+{
+ mpVirDev = nullptr;
+}
+
+struct ImplStatusItem
+{
+ sal_uInt16 mnId;
+ StatusBarItemBits mnBits;
+ long mnWidth;
+ long mnOffset;
+ long mnExtraWidth;
+ long mnX;
+ OUString maText;
+ OUString maHelpText;
+ OUString maQuickHelpText;
+ OString maHelpId;
+ void* mpUserData;
+ bool mbVisible;
+ OUString maAccessibleName;
+ OUString maCommand;
+ std::unique_ptr<SalLayout> mxLayoutCache;
+};
+
+static long ImplCalcProgressWidth( sal_uInt16 nMax, long nSize )
+{
+ return ((nMax*(nSize+(nSize/2)))-(nSize/2)+(STATUSBAR_PRGS_OFFSET*2));
+}
+
+static Point ImplGetItemTextPos( const Size& rRectSize, const Size& rTextSize,
+ StatusBarItemBits nStyle )
+{
+ long nX;
+ long nY;
+ 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()
+{
+ 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( *this );
+ 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(*this);
+
+ mpImplData->mpVirDev->SetFont(GetFont());
+ mpImplData->mpVirDev->SetTextColor(GetTextColor());
+ mpImplData->mpVirDev->SetTextAlign(GetTextAlign());
+ mpImplData->mpVirDev->SetTextFillColor();
+ mpImplData->mpVirDev->SetBackground(GetBackground());
+}
+
+void StatusBar::ImplFormat()
+{
+ long nExtraWidth;
+ long nExtraWidth2;
+ long nX;
+ sal_uInt16 nAutoSizeItems;
+ bool bChanged;
+
+ do {
+ // sum up widths
+ nAutoSizeItems = 0;
+ mnItemsWidth = STATUSBAR_OFFSET_X;
+ bChanged = false;
+ 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( 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();
+ 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))
+ {
+ SalLayout* pLayoutCache = pItem->mxLayoutCache.get();
+
+ if(!pLayoutCache)
+ {
+ // update cache
+ pItem->mxLayoutCache = rRenderContext.ImplLayout(pItem->maText, 0, -1);
+ pLayoutCache = pItem->mxLayoutCache.get();
+ }
+
+ const SalLayoutGlyphs* pGlyphs = pLayoutCache ? pLayoutCache->GetGlyphs() : nullptr;
+ 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(this, mpImplData->mpVirDev, tools::Rectangle(Point(), aTextRectSize), pItem->mnId);
+ UserDraw(aODEvt);
+ mpImplData->mpVirDev->EnableRTL(false);
+ mbInUserDraw = false;
+ }
+ else
+ {
+ UserDrawEvent aODEvt(this, &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,
+ long nOffset, long nPrgsWidth, long nPrgsHeight,
+ sal_uInt16 nPercent1, sal_uInt16 nPercent2, sal_uInt16 nPercentCount,
+ const tools::Rectangle& rFramePosSize)
+{
+ if (rRenderContext.IsNativeControlSupported(ControlType::Progress, ControlPart::Entire))
+ {
+ bool bNeedErase = ImplGetSVData()->maNWFData.mbProgressNeedsErase;
+
+ long nFullWidth = (nPrgsWidth + nOffset) * (10000 / nPercentCount);
+ long nPerc = std::min<sal_uInt16>(nPercent2, 10000);
+ ImplControlValue aValue(nFullWidth * nPerc / 10000);
+ tools::Rectangle aDrawRect(rPos, Size(nFullWidth, nPrgsHeight));
+ tools::Rectangle aControlRegion(aDrawRect);
+
+ if(bNeedErase)
+ {
+ vcl::Window* pEraseWindow = pWindow;
+ while (pEraseWindow->IsPaintTransparent() && !pEraseWindow->ImplGetWindowImpl()->mbFrame)
+ {
+ pEraseWindow = pEraseWindow->ImplGetWindowImpl()->mpParent;
+ }
+
+ if (pEraseWindow == pWindow)
+ {
+ // restore background of pWindow
+ rRenderContext.Erase(rFramePosSize);
+ }
+ else
+ {
+ // restore transparent background
+ Point aTL(pWindow->OutputToAbsoluteScreenPixel(rFramePosSize.TopLeft()));
+ aTL = pEraseWindow->AbsoluteScreenToOutputPixel(aTL);
+ tools::Rectangle aRect(aTL, rFramePosSize.GetSize());
+ pEraseWindow->Invalidate(aRect, InvalidateFlags::NoChildren |
+ InvalidateFlags::NoClipChildren |
+ InvalidateFlags::Transparent);
+ pEraseWindow->PaintImmediately();
+ }
+ rRenderContext.Push(PushFlags::CLIPREGION);
+ rRenderContext.IntersectClipRegion(rFramePosSize);
+ }
+
+ bool bNativeOK = rRenderContext.DrawNativeControl(ControlType::Progress, ControlPart::Entire, aControlRegion,
+ ControlState::ENABLED, aValue, OUString());
+ if (bNeedErase)
+ rRenderContext.Pop();
+ if (bNativeOK)
+ return;
+ }
+
+ // precompute values
+ sal_uInt16 nPerc1 = nPercent1 / nPercentCount;
+ sal_uInt16 nPerc2 = nPercent2 / nPercentCount;
+
+ if (nPerc1 > nPerc2)
+ {
+ // support progress that can also decrease
+
+ // compute rectangle
+ long nDX = nPrgsWidth + nOffset;
+ 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
+ long nDX = nPrgsWidth + nOffset;
+ long nLeft = rPos.X() + (nPerc1 * nDX);
+ tools::Rectangle aRect(nLeft, rPos.Y(), nLeft + nPrgsWidth, rPos.Y() + nPrgsHeight);
+
+ do
+ {
+ rRenderContext.DrawRect(aRect);
+ aRect.AdjustLeft(nDX );
+ aRect.AdjustRight(nDX );
+ nPerc1++;
+ }
+ while (nPerc1 < nPerc2);
+
+ // if greater than 100%, set rectangle to blink
+ if (nPercent2 > 10000)
+ {
+ // define on/off status
+ if (((nPercent2 / nPercentCount) & 0x01) == (nPercentCount & 0x01))
+ {
+ aRect.AdjustLeft( -nDX );
+ aRect.AdjustRight( -nDX );
+ rRenderContext.Erase(aRect);
+ }
+ }
+ }
+}
+
+void StatusBar::ImplDrawProgress(vcl::RenderContext& rRenderContext, sal_uInt16 nPercent2)
+{
+ bool bNative = rRenderContext.IsNativeControlSupported(ControlType::Progress, ControlPart::Entire);
+ // bPaint: draw text also, else only update progress
+ rRenderContext.DrawText(maPrgsTxtPos, maPrgsTxt);
+ if (!bNative)
+ {
+ DecorationView aDecoView(&rRenderContext);
+ aDecoView.DrawFrame(maPrgsFrameRect, DrawFrameStyle::In);
+ }
+
+ Point aPos(maPrgsFrameRect.Left() + STATUSBAR_PRGS_OFFSET,
+ maPrgsFrameRect.Top() + STATUSBAR_PRGS_OFFSET);
+ long nPrgsHeight = mnPrgsSize;
+ if (bNative)
+ {
+ aPos = maPrgsFrameRect.TopLeft();
+ nPrgsHeight = maPrgsFrameRect.GetHeight();
+ }
+ DrawProgress(this, rRenderContext, aPos, mnPrgsSize / 2, mnPrgsSize, nPrgsHeight,
+ 0, nPercent2 * 100, mnPercentCount, maPrgsFrameRect);
+}
+
+void StatusBar::ImplCalcProgressRect()
+{
+ // calculate text size
+ Size aPrgsTxtSize( GetTextWidth( maPrgsTxt ), GetTextHeight() );
+ maPrgsTxtPos.setX( STATUSBAR_OFFSET_X+1 );
+
+ // calculate progress frame
+ maPrgsFrameRect.SetLeft( maPrgsTxtPos.X()+aPrgsTxtSize.Width()+STATUSBAR_OFFSET );
+ maPrgsFrameRect.SetTop( STATUSBAR_OFFSET_Y );
+ maPrgsFrameRect.SetBottom( mnCalcHeight - STATUSBAR_OFFSET_Y );
+
+ // calculate size of progress rects
+ mnPrgsSize = maPrgsFrameRect.Bottom()-maPrgsFrameRect.Top()-(STATUSBAR_PRGS_OFFSET*2);
+ sal_uInt16 nMaxPercent = STATUSBAR_PRGS_COUNT;
+
+ 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 ) ) )
+ {
+ long nProgressHeight = aNativeControlRegion.GetHeight();
+ if( nProgressHeight > maPrgsFrameRect.GetHeight() )
+ {
+ 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() )
+ {
+ 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) ).IsInside( 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(PushFlags::FILLCOLOR | 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();
+
+ // tdf#94213 - un-necessary virtual-device in GL mode
+ // causes context switch & hence flicker during sizing.
+#if HAVE_FEATURE_OPENGL
+ if( OpenGLWrapper::isVCLOpenGLEnabled() )
+ bOffscreen = false;
+#endif
+
+ 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->mxLayoutCache.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 )
+ )
+ )
+ {
+ mbFormat = true;
+ ImplInitSettings();
+ long nFudge = GetTextHeight() / 4;
+ for (auto & pItem : mvItemList)
+ {
+ long nWidth = GetTextWidth( pItem->maText ) + nFudge;
+ if( nWidth > pItem->mnWidth + STATUSBAR_OFFSET )
+ pItem->mnWidth = nWidth + STATUSBAR_OFFSET;
+
+ pItem->mxLayoutCache.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,
+ 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();
+ }
+ long nFudge = GetTextHeight()/4;
+ std::unique_ptr<ImplStatusItem> pItem(new ImplStatusItem);
+ pItem->mnId = nItemId;
+ pItem->mnBits = nBits;
+ pItem->mnWidth = static_cast<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 )
+ {
+ 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 )
+ {
+ 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.IsInside( 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 );
+ 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 );
+ 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;
+}
+
+long StatusBar::GetItemOffset( sal_uInt16 nItemId ) const
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+
+ if ( nPos != STATUSBAR_ITEM_NOTFOUND )
+ return mvItemList[ nPos ]->mnOffset;
+
+ return 0;
+}
+
+void StatusBar::SetItemText( sal_uInt16 nItemId, const OUString& rText, int nCharsWidth )
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+
+ if ( nPos != STATUSBAR_ITEM_NOTFOUND )
+ {
+ ImplStatusItem* pItem = mvItemList[ nPos ].get();
+
+ if ( pItem->maText != rText )
+ {
+ pItem->maText = rText;
+
+ // adjust item width - see also DataChanged()
+ long nFudge = GetTextHeight()/4;
+
+ long nWidth;
+ if (nCharsWidth != -1)
+ {
+ std::unique_ptr<SalLayout> pSalLayout = ImplLayout("0",0,-1);
+ const SalLayoutGlyphs* pGlyphs = pSalLayout ? pSalLayout->GetGlyphs() : nullptr;
+ nWidth = GetTextWidth("0",0,-1,nullptr,pGlyphs );
+ nWidth = nWidth * nCharsWidth + nFudge;
+ }
+ else
+ {
+ std::unique_ptr<SalLayout> pSalLayout = ImplLayout(pItem->maText,0,-1);
+ const SalLayoutGlyphs* pGlyphs = pSalLayout ? pSalLayout->GetGlyphs() : nullptr;
+ nWidth = GetTextWidth( pItem->maText,0,-1,nullptr,pGlyphs ) + nFudge;
+ // Store the calculated layout.
+ pItem->mxLayoutCache = std::move(pSalLayout);
+ }
+
+ if( (nWidth > pItem->mnWidth + STATUSBAR_OFFSET) ||
+ ((nWidth < pItem->mnWidth) && (mnDX - STATUSBAR_OFFSET) < mnItemsWidth ))
+ {
+ pItem->mnWidth = nWidth + STATUSBAR_OFFSET;
+ ImplFormat();
+ Invalidate();
+ }
+
+ // re-draw item if StatusBar is visible and UpdateMode active
+ if ( pItem->mbVisible && !mbFormat && ImplIsItemUpdate() )
+ {
+ tools::Rectangle aRect = ImplGetItemRectPos(nPos);
+ Invalidate(aRect);
+ PaintImmediately();
+ }
+ }
+ }
+}
+
+const OUString& StatusBar::GetItemText( sal_uInt16 nItemId ) const
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+
+ assert( nPos != STATUSBAR_ITEM_NOTFOUND );
+
+ return mvItemList[ nPos ]->maText;
+}
+
+void StatusBar::SetItemCommand( sal_uInt16 nItemId, const OUString& rCommand )
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+
+ if ( nPos != STATUSBAR_ITEM_NOTFOUND )
+ {
+ ImplStatusItem* pItem = mvItemList[ nPos ].get();
+
+ if ( pItem->maCommand != rCommand )
+ pItem->maCommand = rCommand;
+ }
+}
+
+OUString StatusBar::GetItemCommand( sal_uInt16 nItemId )
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+
+ if ( nPos != STATUSBAR_ITEM_NOTFOUND )
+ return mvItemList[ nPos ]->maCommand;
+
+ return OUString();
+}
+
+void StatusBar::SetItemData( sal_uInt16 nItemId, void* pNewData )
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+
+ if ( nPos != STATUSBAR_ITEM_NOTFOUND )
+ {
+ ImplStatusItem* pItem = mvItemList[ nPos ].get();
+ // invalidate cache
+ pItem->mxLayoutCache.reset();
+ pItem->mpUserData = pNewData;
+
+ // call Draw-Item if it's a User-Item
+ if ( (pItem->mnBits & StatusBarItemBits::UserDraw) && pItem->mbVisible &&
+ !mbFormat && ImplIsItemUpdate() )
+ {
+ tools::Rectangle aRect = ImplGetItemRectPos(nPos);
+ Invalidate(aRect, InvalidateFlags::NoErase);
+ PaintImmediately();
+ }
+ }
+}
+
+void* StatusBar::GetItemData( sal_uInt16 nItemId ) const
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+
+ if ( nPos != STATUSBAR_ITEM_NOTFOUND )
+ return mvItemList[ nPos ]->mpUserData;
+
+ return nullptr;
+}
+
+void StatusBar::RedrawItem(sal_uInt16 nItemId)
+{
+ if ( mbFormat )
+ return;
+
+ sal_uInt16 nPos = GetItemPos(nItemId);
+ if ( nPos == STATUSBAR_ITEM_NOTFOUND )
+ return;
+
+ ImplStatusItem* pItem = mvItemList[ nPos ].get();
+ if ((pItem->mnBits & StatusBarItemBits::UserDraw) &&
+ pItem->mbVisible && ImplIsItemUpdate())
+ {
+ tools::Rectangle aRect = ImplGetItemRectPos(nPos);
+ Invalidate(aRect);
+ PaintImmediately();
+ }
+}
+
+void StatusBar::SetHelpText( sal_uInt16 nItemId, const OUString& rText )
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+
+ if ( nPos != STATUSBAR_ITEM_NOTFOUND )
+ mvItemList[ nPos ]->maHelpText = rText;
+}
+
+const OUString& StatusBar::GetHelpText( sal_uInt16 nItemId ) const
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+
+ assert ( nPos != STATUSBAR_ITEM_NOTFOUND );
+
+ ImplStatusItem* pItem = mvItemList[ nPos ].get();
+ if ( pItem->maHelpText.isEmpty() && ( !pItem->maHelpId.isEmpty() || !pItem->maCommand.isEmpty() ))
+ {
+ Help* pHelp = Application::GetHelp();
+ if ( pHelp )
+ {
+ if ( !pItem->maCommand.isEmpty() )
+ pItem->maHelpText = pHelp->GetHelpText( pItem->maCommand, this );
+ if ( pItem->maHelpText.isEmpty() && !pItem->maHelpId.isEmpty() )
+ pItem->maHelpText = pHelp->GetHelpText( OStringToOUString( pItem->maHelpId, RTL_TEXTENCODING_UTF8 ), this );
+ }
+ }
+
+ return pItem->maHelpText;
+}
+
+void StatusBar::SetQuickHelpText( sal_uInt16 nItemId, const OUString& rText )
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+
+ if ( nPos != STATUSBAR_ITEM_NOTFOUND )
+ mvItemList[ nPos ]->maQuickHelpText = rText;
+}
+
+const OUString& StatusBar::GetQuickHelpText( sal_uInt16 nItemId ) const
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+
+ assert ( nPos != STATUSBAR_ITEM_NOTFOUND );
+
+ ImplStatusItem* pItem = mvItemList[ nPos ].get();
+ return pItem->maQuickHelpText;
+}
+
+void StatusBar::SetHelpId( sal_uInt16 nItemId, const OString& rHelpId )
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+
+ if ( nPos != STATUSBAR_ITEM_NOTFOUND )
+ mvItemList[ nPos ]->maHelpId = rHelpId;
+}
+
+void StatusBar::StartProgressMode( const OUString& rText )
+{
+ SAL_WARN_IF( mbProgressMode, "vcl", "StatusBar::StartProgressMode(): progress mode is active" );
+
+ mbProgressMode = true;
+ mnPercent = 0;
+ maPrgsTxt = rText;
+
+ // compute size
+ ImplCalcProgressRect();
+
+ // trigger Paint, which draws text and frame
+ if ( IsReallyVisible() )
+ {
+ Invalidate();
+ PaintImmediately();
+ }
+}
+
+void StatusBar::SetProgressValue( sal_uInt16 nNewPercent )
+{
+ SAL_WARN_IF( !mbProgressMode, "vcl", "StatusBar::SetProgressValue(): no progress mode" );
+ SAL_WARN_IF( nNewPercent > 100, "vcl", "StatusBar::SetProgressValue(): nPercent > 100" );
+
+ bool bInvalidate = mbProgressMode && IsReallyVisible() && (!mnPercent || (mnPercent != nNewPercent));
+
+ mnPercent = nNewPercent;
+
+ if (bInvalidate)
+ {
+ // Rate limit how often we paint, otherwise in some loading scenarios we can spend significant
+ // time just painting progress bars.
+ sal_uInt32 nTime_ms = osl_getGlobalTimer();
+ if ((nTime_ms - mnLastProgressPaint_ms) > 100)
+ {
+ Invalidate(maPrgsFrameRect);
+ PaintImmediately();
+ mnLastProgressPaint_ms = nTime_ms;
+ }
+ }
+}
+
+void StatusBar::EndProgressMode()
+{
+ SAL_WARN_IF( !mbProgressMode, "vcl", "StatusBar::EndProgressMode(): no progress mode" );
+
+ mbProgressMode = false;
+ maPrgsTxt.clear();
+
+ if ( IsReallyVisible() )
+ {
+ Invalidate();
+ PaintImmediately();
+ }
+}
+
+void StatusBar::SetText(const OUString& rText)
+{
+ if ((GetStyle() & WB_RIGHT) && !mbProgressMode && IsReallyVisible() && IsUpdateMode())
+ {
+ if (mbFormat)
+ {
+ Invalidate();
+ Window::SetText(rText);
+ }
+ else
+ {
+ Invalidate();
+ Window::SetText(rText);
+ PaintImmediately();
+ }
+ }
+ else if (mbProgressMode)
+ {
+ maPrgsTxt = rText;
+ if (IsReallyVisible())
+ {
+ Invalidate();
+ PaintImmediately();
+ }
+ }
+ else
+ {
+ Window::SetText(rText);
+ }
+}
+
+Size StatusBar::CalcWindowSizePixel() const
+{
+ size_t i = 0;
+ size_t nCount = mvItemList.size();
+ long nOffset = 0;
+ long nCalcWidth = STATUSBAR_OFFSET_X*2;
+ long nCalcHeight;
+
+ while ( i < nCount )
+ {
+ ImplStatusItem* pItem = mvItemList[ i ].get();
+ nCalcWidth += pItem->mnWidth + nOffset;
+ nOffset = pItem->mnOffset;
+ i++;
+ }
+
+ long nMinHeight = GetTextHeight();
+ const long nBarTextOffset = STATUSBAR_OFFSET_TEXTY*2;
+ long nProgressHeight = nMinHeight + nBarTextOffset;
+
+ if( IsNativeControlSupported( ControlType::Progress, ControlPart::Entire ) )
+ {
+ ImplControlValue aValue;
+ tools::Rectangle aControlRegion( Point(), Size( nCalcWidth, nMinHeight ) );
+ tools::Rectangle aNativeControlRegion, aNativeContentRegion;
+ if( GetNativeControlRegion( ControlType::Progress, ControlPart::Entire,
+ aControlRegion, ControlState::ENABLED, aValue,
+ aNativeControlRegion, aNativeContentRegion ) )
+ {
+ nProgressHeight = aNativeControlRegion.GetHeight();
+ }
+ }
+
+ nCalcHeight = nMinHeight+nBarTextOffset;
+ if( nCalcHeight < nProgressHeight+2 )
+ nCalcHeight = nProgressHeight+2;
+
+ return Size( nCalcWidth, nCalcHeight );
+}
+
+void StatusBar::SetAccessibleName( sal_uInt16 nItemId, const OUString& rName )
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+
+ if ( nPos != STATUSBAR_ITEM_NOTFOUND )
+ {
+ ImplStatusItem* pItem = mvItemList[ nPos ].get();
+
+ if ( pItem->maAccessibleName != rName )
+ {
+ pItem->maAccessibleName = rName;
+ CallEventListeners( VclEventId::StatusbarNameChanged, reinterpret_cast<void*>(pItem->mnId) );
+ }
+ }
+}
+
+const OUString& StatusBar::GetAccessibleName( sal_uInt16 nItemId ) const
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+
+ assert ( nPos != STATUSBAR_ITEM_NOTFOUND );
+
+ return mvItemList[ nPos ]->maAccessibleName;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/syschild.cxx b/vcl/source/window/syschild.cxx
new file mode 100644
index 000000000..81cbc43ab
--- /dev/null
+++ b/vcl/source/window/syschild.cxx
@@ -0,0 +1,187 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <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()->aWindow;
+#endif
+
+ return nRet;
+}
+
+void* SystemChildWindow::CreateGStreamerSink()
+{
+ return ImplGetSVData()->mpDefInst->CreateGStreamerSink(this);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/syswin.cxx b/vcl/source/window/syswin.cxx
new file mode 100644
index 000000000..3c154dd22
--- /dev/null
+++ b/vcl/source/window/syswin.cxx
@@ -0,0 +1,1199 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/accel.hxx>
+#include <vcl/layout.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/event.hxx>
+#include <vcl/syswin.hxx>
+#include <vcl/taskpanelist.hxx>
+#include <vcl/tabctrl.hxx>
+#include <vcl/tabpage.hxx>
+#include <vcl/virdev.hxx>
+
+#include <rtl/strbuf.hxx>
+
+#include <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)
+ : Window(nType)
+ , mbRollUp(false)
+ , mbDockBtn(false)
+ , mbHideBtn(false)
+ , mbSysChild(false)
+ , mbIsCalculatingInitialLayoutSize(false)
+ , mbPaintComplete(false)
+ , mnMenuBarMode(MenuBarMode::Normal)
+ , mnIcon(0)
+ , mpImplData(new ImplData)
+ , mbIsDeferredInit(false)
+{
+ mpWindowImpl->mbSysWin = true;
+ mpWindowImpl->mnActivateMode = ActivateModeFlags::GrabFocus;
+
+ //To-Do, reuse maResizeTimer
+ maLayoutIdle.SetPriority(TaskPriority::RESIZE);
+ maLayoutIdle.SetInvokeHandler( LINK( this, SystemWindow, ImplHandleLayoutTimerHdl ) );
+ maLayoutIdle.SetDebugName( "vcl::SystemWindow maLayoutIdle" );
+}
+
+void SystemWindow::loadUI(vcl::Window* pParent, const OString& rID, const OUString& rUIXMLDescription,
+ const css::uno::Reference<css::frame::XFrame> &rFrame)
+{
+ mbIsDeferredInit = true;
+ mpDialogParent = pParent; //should be unset in doDeferredInit
+ m_pUIBuilder.reset( new VclBuilder(this, 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);
+ }
+ }
+}
+
+bool Accelerator::ToggleMnemonicsOnHierarchy(const CommandEvent& rCEvent, const vcl::Window *pWindow)
+{
+ if (rCEvent.GetCommand() == CommandEventId::ModKeyChange && ImplGetSVData()->maNWFData.mbAutoAccel)
+ {
+ const CommandModKeyData *pCData = rCEvent.GetModKeyData();
+ const bool bShowAccel = pCData && pCData->IsMod2() && pCData->IsDown();
+ processChildren(pWindow, bShowAccel);
+ return true;
+ }
+ return false;
+}
+
+bool SystemWindow::EventNotify( NotifyEvent& rNEvt )
+{
+ if (rNEvt.GetType() == MouseNotifyEvent::COMMAND)
+ Accelerator::ToggleMnemonicsOnHierarchy(*rNEvt.GetCommandEvent(), this);
+
+ // capture KeyEvents for menu handling
+ if (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT ||
+ rNEvt.GetType() == MouseNotifyEvent::COMMAND)
+ {
+ MenuBar* pMBar = mpMenuBar;
+ if ( !pMBar && ( GetType() == WindowType::FLOATINGWINDOW ) )
+ {
+ vcl::Window* pWin = ImplGetFrameWindow()->ImplGetWindow();
+ if( pWin && pWin->IsSystemWindow() )
+ pMBar = static_cast<SystemWindow*>(pWin)->GetMenuBar();
+ }
+ bool bDone(false);
+ if (pMBar)
+ {
+ if (rNEvt.GetType() == MouseNotifyEvent::COMMAND)
+ bDone = pMBar->ImplHandleCmdEvent(*rNEvt.GetCommandEvent());
+ else
+ bDone = pMBar->ImplHandleKeyEvent(*rNEvt.GetKeyEvent());
+ }
+ if (bDone)
+ return true;
+ }
+
+ return Window::EventNotify( rNEvt );
+}
+
+bool SystemWindow::PreNotify( NotifyEvent& rNEvt )
+{
+ // capture KeyEvents for taskpane cycling
+ if ( rNEvt.GetType() == MouseNotifyEvent::KEYINPUT )
+ {
+ if( rNEvt.GetKeyEvent()->GetKeyCode().GetCode() == KEY_F6 &&
+ rNEvt.GetKeyEvent()->GetKeyCode().IsMod1() &&
+ !rNEvt.GetKeyEvent()->GetKeyCode().IsShift() )
+ {
+ // Ctrl-F6 goes directly to the document
+ GrabFocusToDocument();
+ return true;
+ }
+ else
+ {
+ TaskPaneList *pTList = mpImplData->mpTaskPaneList.get();
+ if( !pTList && ( GetType() == WindowType::FLOATINGWINDOW ) )
+ {
+ vcl::Window* pWin = ImplGetFrameWindow()->ImplGetWindow();
+ if( pWin && pWin->IsSystemWindow() )
+ pTList = static_cast<SystemWindow*>(pWin)->mpImplData->mpTaskPaneList.get();
+ }
+ if( !pTList )
+ {
+ // search topmost system window which is the one to handle dialog/toolbar cycling
+ SystemWindow *pSysWin = this;
+ vcl::Window *pWin = this;
+ while( pWin )
+ {
+ pWin = pWin->GetParent();
+ if( pWin && pWin->IsSystemWindow() )
+ pSysWin = static_cast<SystemWindow*>(pWin);
+ }
+ pTList = pSysWin->mpImplData->mpTaskPaneList.get();
+ }
+ if( pTList && pTList->HandleKeyEvent( *rNEvt.GetKeyEvent() ) )
+ return true;
+ }
+ }
+ return Window::PreNotify( rNEvt );
+}
+
+TaskPaneList* SystemWindow::GetTaskPaneList()
+{
+ if( !mpImplData )
+ return nullptr;
+ if( mpImplData->mpTaskPaneList )
+ return mpImplData->mpTaskPaneList.get();
+ else
+ {
+ mpImplData->mpTaskPaneList.reset( new TaskPaneList );
+ MenuBar* pMBar = mpMenuBar;
+ if ( !pMBar && ( GetType() == WindowType::FLOATINGWINDOW ) )
+ {
+ vcl::Window* pWin = ImplGetFrameWindow()->ImplGetWindow();
+ if ( pWin && pWin->IsSystemWindow() )
+ pMBar = static_cast<SystemWindow*>(pWin)->GetMenuBar();
+ }
+ if( pMBar )
+ mpImplData->mpTaskPaneList->AddWindow( pMBar->ImplGetWindow() );
+ return mpImplData->mpTaskPaneList.get();
+ }
+}
+
+bool SystemWindow::Close()
+{
+ VclPtr<vcl::Window> xWindow = this;
+ CallEventListeners( VclEventId::WindowClose );
+ if ( xWindow->IsDisposed() )
+ return false;
+
+ if ( mpWindowImpl->mxWindowPeer.is() && IsCreatedWithToolkit() )
+ return false;
+
+ // Is Window not closeable, ignore close
+ vcl::Window* pBorderWin = ImplGetBorderWindow();
+ WinBits nStyle;
+ if ( pBorderWin )
+ nStyle = pBorderWin->GetStyle();
+ else
+ nStyle = GetStyle();
+ if ( !(nStyle & WB_CLOSEABLE) )
+ return false;
+
+ Hide();
+
+ return true;
+}
+
+void SystemWindow::TitleButtonClick( TitleButton )
+{
+}
+
+void SystemWindow::Resizing( Size& )
+{
+}
+
+void SystemWindow::SetRepresentedURL( const OUString& i_rURL )
+{
+ bool bChanged = (i_rURL != mpImplData->maRepresentedURL);
+ mpImplData->maRepresentedURL = i_rURL;
+ if ( !mbSysChild && bChanged )
+ {
+ const vcl::Window* pWindow = this;
+ while ( pWindow->mpWindowImpl->mpBorderWindow )
+ pWindow = pWindow->mpWindowImpl->mpBorderWindow;
+
+ if ( pWindow->mpWindowImpl->mbFrame )
+ pWindow->mpWindowImpl->mpFrame->SetRepresentedURL( i_rURL );
+ }
+}
+
+void SystemWindow::SetIcon( sal_uInt16 nIcon )
+{
+ if ( mnIcon == nIcon )
+ return;
+
+ mnIcon = nIcon;
+
+ if ( !mbSysChild )
+ {
+ const vcl::Window* pWindow = this;
+ while ( pWindow->mpWindowImpl->mpBorderWindow )
+ pWindow = pWindow->mpWindowImpl->mpBorderWindow;
+
+ if ( pWindow->mpWindowImpl->mbFrame )
+ pWindow->mpWindowImpl->mpFrame->SetIcon( nIcon );
+ }
+}
+
+void SystemWindow::ShowTitleButton( TitleButton nButton, bool bVisible )
+{
+ if ( nButton == TitleButton::Docking )
+ {
+ if ( mbDockBtn != bVisible )
+ {
+ mbDockBtn = bVisible;
+ if ( mpWindowImpl->mpBorderWindow )
+ static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->SetDockButton( bVisible );
+ }
+ }
+ else if ( nButton == TitleButton::Hide )
+ {
+ if ( mbHideBtn != bVisible )
+ {
+ mbHideBtn = bVisible;
+ if ( mpWindowImpl->mpBorderWindow )
+ static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->SetHideButton( bVisible );
+ }
+ }
+ else if ( nButton == TitleButton::Menu )
+ {
+ if ( mpWindowImpl->mpBorderWindow )
+ static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->SetMenuButton( bVisible );
+ }
+ else
+ return;
+}
+
+bool SystemWindow::IsTitleButtonVisible( TitleButton nButton ) const
+{
+ if ( nButton == TitleButton::Docking )
+ return mbDockBtn;
+ else /* if ( nButton == TitleButton::Hide ) */
+ return mbHideBtn;
+}
+
+void SystemWindow::RollUp()
+{
+ if ( !mbRollUp )
+ {
+ maOrgSize = GetOutputSizePixel();
+ Size aSize = maRollUpOutSize;
+ if ( !aSize.Width() )
+ aSize.setWidth( GetOutputSizePixel().Width() );
+ mbRollUp = true;
+ if ( mpWindowImpl->mpBorderWindow )
+ static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->SetRollUp( true, aSize );
+ else
+ SetOutputSizePixel( aSize );
+ }
+}
+
+void SystemWindow::RollDown()
+{
+ if ( mbRollUp )
+ {
+ mbRollUp = false;
+ if ( mpWindowImpl->mpBorderWindow )
+ static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->SetRollUp( false, maOrgSize );
+ else
+ SetOutputSizePixel( maOrgSize );
+ }
+}
+
+void SystemWindow::SetMinOutputSizePixel( const Size& rSize )
+{
+ maMinOutSize = rSize;
+ if ( mpWindowImpl->mpBorderWindow )
+ {
+ static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->SetMinOutputSize( rSize.Width(), rSize.Height() );
+ if ( mpWindowImpl->mpBorderWindow->mpWindowImpl->mbFrame )
+ mpWindowImpl->mpBorderWindow->mpWindowImpl->mpFrame->SetMinClientSize( rSize.Width(), rSize.Height() );
+ }
+ else if ( mpWindowImpl->mbFrame )
+ mpWindowImpl->mpFrame->SetMinClientSize( rSize.Width(), rSize.Height() );
+}
+
+void SystemWindow::SetMaxOutputSizePixel( const Size& rSize )
+{
+ Size aSize( rSize );
+ if( aSize.Width() > SHRT_MAX || aSize.Width() <= 0 )
+ aSize.setWidth( SHRT_MAX );
+ if( aSize.Height() > SHRT_MAX || aSize.Height() <= 0 )
+ aSize.setHeight( SHRT_MAX );
+
+ mpImplData->maMaxOutSize = aSize;
+ if ( mpWindowImpl->mpBorderWindow )
+ {
+ static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->SetMaxOutputSize( aSize.Width(), aSize.Height() );
+ if ( mpWindowImpl->mpBorderWindow->mpWindowImpl->mbFrame )
+ mpWindowImpl->mpBorderWindow->mpWindowImpl->mpFrame->SetMaxClientSize( aSize.Width(), aSize.Height() );
+ }
+ else if ( mpWindowImpl->mbFrame )
+ mpWindowImpl->mpFrame->SetMaxClientSize( aSize.Width(), aSize.Height() );
+}
+
+const Size& SystemWindow::GetMaxOutputSizePixel() const
+{
+ return mpImplData->maMaxOutSize;
+}
+
+void ImplWindowStateFromStr(WindowStateData& rData,
+ const OString& rStr)
+{
+ WindowStateMask nValidMask = WindowStateMask::NONE;
+ sal_Int32 nIndex = 0;
+
+ OString aTokenStr = rStr.getToken(0, ',', nIndex);
+ if (!aTokenStr.isEmpty())
+ {
+ rData.SetX(aTokenStr.toInt32());
+ if( rData.GetX() > -16384 && rData.GetX() < 16384 )
+ nValidMask |= WindowStateMask::X;
+ else
+ rData.SetX( 0 );
+ }
+ else
+ rData.SetX( 0 );
+ aTokenStr = rStr.getToken(0, ',', nIndex);
+ if (!aTokenStr.isEmpty())
+ {
+ rData.SetY(aTokenStr.toInt32());
+ if( rData.GetY() > -16384 && rData.GetY() < 16384 )
+ nValidMask |= WindowStateMask::Y;
+ else
+ rData.SetY( 0 );
+ }
+ else
+ rData.SetY( 0 );
+ aTokenStr = rStr.getToken(0, ',', nIndex);
+ if (!aTokenStr.isEmpty())
+ {
+ rData.SetWidth(aTokenStr.toInt32());
+ if( rData.GetWidth() > 0 && rData.GetWidth() < 16384 )
+ nValidMask |= WindowStateMask::Width;
+ else
+ rData.SetWidth( 0 );
+ }
+ else
+ rData.SetWidth( 0 );
+ aTokenStr = rStr.getToken(0, ';', nIndex);
+ if (!aTokenStr.isEmpty())
+ {
+ rData.SetHeight(aTokenStr.toInt32());
+ if( rData.GetHeight() > 0 && rData.GetHeight() < 16384 )
+ nValidMask |= WindowStateMask::Height;
+ else
+ rData.SetHeight( 0 );
+ }
+ else
+ rData.SetHeight( 0 );
+ aTokenStr = rStr.getToken(0, ';', nIndex);
+ if (!aTokenStr.isEmpty())
+ {
+ // #94144# allow Minimize again, should be masked out when read from configuration
+ // 91625 - ignore Minimize
+ WindowStateState nState = static_cast<WindowStateState>(aTokenStr.toInt32());
+ //nState &= ~(WindowStateState::Minimized);
+ rData.SetState( nState );
+ nValidMask |= WindowStateMask::State;
+ }
+ else
+ rData.SetState( WindowStateState::NONE );
+
+ // read maximized pos/size
+ aTokenStr = rStr.getToken(0, ',', nIndex);
+ if (!aTokenStr.isEmpty())
+ {
+ rData.SetMaximizedX(aTokenStr.toInt32());
+ if( rData.GetMaximizedX() > -16384 && rData.GetMaximizedX() < 16384 )
+ nValidMask |= WindowStateMask::MaximizedX;
+ else
+ rData.SetMaximizedX( 0 );
+ }
+ else
+ rData.SetMaximizedX( 0 );
+ aTokenStr = rStr.getToken(0, ',', nIndex);
+ if (!aTokenStr.isEmpty())
+ {
+ rData.SetMaximizedY(aTokenStr.toInt32());
+ if( rData.GetMaximizedY() > -16384 && rData.GetMaximizedY() < 16384 )
+ nValidMask |= WindowStateMask::MaximizedY;
+ else
+ rData.SetMaximizedY( 0 );
+ }
+ else
+ rData.SetMaximizedY( 0 );
+ aTokenStr = rStr.getToken(0, ',', nIndex);
+ if (!aTokenStr.isEmpty())
+ {
+ rData.SetMaximizedWidth(aTokenStr.toInt32());
+ if( rData.GetMaximizedWidth() > 0 && rData.GetMaximizedWidth() < 16384 )
+ nValidMask |= WindowStateMask::MaximizedWidth;
+ else
+ rData.SetMaximizedWidth( 0 );
+ }
+ else
+ rData.SetMaximizedWidth( 0 );
+ aTokenStr = rStr.getToken(0, ';', nIndex);
+ if (!aTokenStr.isEmpty())
+ {
+ rData.SetMaximizedHeight(aTokenStr.toInt32());
+ if( rData.GetMaximizedHeight() > 0 && rData.GetMaximizedHeight() < 16384 )
+ nValidMask |= WindowStateMask::MaximizedHeight;
+ else
+ rData.SetMaximizedHeight( 0 );
+ }
+ else
+ rData.SetMaximizedHeight( 0 );
+
+ // mark valid fields
+ rData.SetMask( nValidMask );
+}
+
+OString WindowStateData::ToStr() const
+{
+ const WindowStateMask nValidMask = GetMask();
+ if ( nValidMask == WindowStateMask::NONE )
+ return OString();
+
+ OStringBuffer rStrBuf(64);
+
+ if ( nValidMask & WindowStateMask::X )
+ rStrBuf.append(static_cast<sal_Int32>(GetX()));
+ rStrBuf.append(',');
+ if ( nValidMask & WindowStateMask::Y )
+ rStrBuf.append(static_cast<sal_Int32>(GetY()));
+ rStrBuf.append(',');
+ if ( nValidMask & WindowStateMask::Width )
+ rStrBuf.append(static_cast<sal_Int32>(GetWidth()));
+ rStrBuf.append(',');
+ if ( nValidMask & WindowStateMask::Height )
+ rStrBuf.append(static_cast<sal_Int32>(GetHeight()));
+ rStrBuf.append( ';' );
+ if ( nValidMask & WindowStateMask::State )
+ {
+ // #94144# allow Minimize again, should be masked out when read from configuration
+ // 91625 - ignore Minimize
+ WindowStateState nState = GetState();
+ rStrBuf.append(static_cast<sal_Int32>(nState));
+ }
+ rStrBuf.append(';');
+ if ( nValidMask & WindowStateMask::MaximizedX )
+ rStrBuf.append(static_cast<sal_Int32>(GetMaximizedX()));
+ rStrBuf.append(',');
+ if ( nValidMask & WindowStateMask::MaximizedY )
+ rStrBuf.append(static_cast<sal_Int32>(GetMaximizedY()));
+ rStrBuf.append( ',' );
+ if ( nValidMask & WindowStateMask::MaximizedWidth )
+ rStrBuf.append(static_cast<sal_Int32>(GetMaximizedWidth()));
+ rStrBuf.append(',');
+ if ( nValidMask & WindowStateMask::MaximizedHeight )
+ rStrBuf.append(static_cast<sal_Int32>(GetMaximizedHeight()));
+ rStrBuf.append(';');
+
+ return rStrBuf.makeStringAndClear();
+}
+
+void SystemWindow::ImplMoveToScreen( long& io_rX, long& io_rY, long i_nWidth, long i_nHeight, vcl::Window const * i_pConfigureWin )
+{
+ tools::Rectangle aScreenRect;
+ if( !Application::IsUnifiedDisplay() )
+ aScreenRect = Application::GetScreenPosSizePixel( GetScreenNumber() );
+ else
+ {
+ aScreenRect = Application::GetScreenPosSizePixel( 0 );
+ for( unsigned int i = 1; i < Application::GetScreenCount(); i++ )
+ aScreenRect.Union( Application::GetScreenPosSizePixel( i ) );
+ }
+ // unfortunately most of the time width and height are not really known
+ if( i_nWidth < 1 )
+ i_nWidth = 50;
+ if( i_nHeight < 1 )
+ i_nHeight = 50;
+
+ // check left border
+ bool bMove = false;
+ if( io_rX + i_nWidth < aScreenRect.Left() )
+ {
+ bMove = true;
+ io_rX = aScreenRect.Left();
+ }
+ // check right border
+ if( io_rX > aScreenRect.Right() - i_nWidth )
+ {
+ bMove = true;
+ io_rX = aScreenRect.Right() - i_nWidth;
+ }
+ // check top border
+ if( io_rY + i_nHeight < aScreenRect.Top() )
+ {
+ bMove = true;
+ io_rY = aScreenRect.Top();
+ }
+ // check bottom border
+ if( io_rY > aScreenRect.Bottom() - i_nHeight )
+ {
+ bMove = true;
+ io_rY = aScreenRect.Bottom() - i_nHeight;
+ }
+ vcl::Window* pParent = i_pConfigureWin->GetParent();
+ if( bMove && pParent )
+ {
+ // calculate absolute screen pos here, since that is what is contained in WindowState
+ Point aParentAbsPos( pParent->OutputToAbsoluteScreenPixel( Point(0,0) ) );
+ Size aParentSizePixel( pParent->GetOutputSizePixel() );
+ Point aPos( (aParentSizePixel.Width() - i_nWidth) / 2,
+ (aParentSizePixel.Height() - i_nHeight) / 2 );
+ io_rX = aParentAbsPos.X() + aPos.X();
+ io_rY = aParentAbsPos.Y() + aPos.Y();
+ }
+}
+
+void SystemWindow::SetWindowStateData( const WindowStateData& rData )
+{
+ const WindowStateMask nValidMask = rData.GetMask();
+ if ( nValidMask == WindowStateMask::NONE )
+ return;
+
+ if ( mbSysChild )
+ return;
+
+ vcl::Window* pWindow = this;
+ while ( pWindow->mpWindowImpl->mpBorderWindow )
+ pWindow = pWindow->mpWindowImpl->mpBorderWindow;
+
+ if ( pWindow->mpWindowImpl->mbFrame )
+ {
+ const WindowStateState nState = rData.GetState();
+ SalFrameState aState;
+ aState.mnMask = rData.GetMask();
+ aState.mnX = rData.GetX();
+ aState.mnY = rData.GetY();
+ aState.mnWidth = rData.GetWidth();
+ aState.mnHeight = rData.GetHeight();
+
+ if( rData.GetMask() & (WindowStateMask::Width|WindowStateMask::Height) )
+ {
+ // #i43799# adjust window state sizes if a minimal output size was set
+ // otherwise the frame and the client might get different sizes
+ if( maMinOutSize.Width() > aState.mnWidth )
+ aState.mnWidth = maMinOutSize.Width();
+ if( maMinOutSize.Height() > aState.mnHeight )
+ aState.mnHeight = maMinOutSize.Height();
+ }
+
+ aState.mnMaximizedX = rData.GetMaximizedX();
+ aState.mnMaximizedY = rData.GetMaximizedY();
+ aState.mnMaximizedWidth = rData.GetMaximizedWidth();
+ aState.mnMaximizedHeight = rData.GetMaximizedHeight();
+ // #94144# allow Minimize again, should be masked out when read from configuration
+ // 91625 - ignore Minimize
+ //nState &= ~(WindowStateState::Minimized);
+ aState.mnState = nState & WindowStateState::SystemMask;
+
+ // normalize window positions onto screen
+ ImplMoveToScreen( aState.mnX, aState.mnY, aState.mnWidth, aState.mnHeight, pWindow );
+ ImplMoveToScreen( aState.mnMaximizedX, aState.mnMaximizedY, aState.mnMaximizedWidth, aState.mnMaximizedHeight, pWindow );
+
+ // #96568# avoid having multiple frames at the same screen location
+ // do the check only if not maximized
+ if( !((rData.GetMask() & WindowStateMask::State) && (nState & WindowStateState::Maximized)) )
+ if( rData.GetMask() & (WindowStateMask::Pos|WindowStateMask::Width|WindowStateMask::Height) )
+ {
+ tools::Rectangle aDesktop = GetDesktopRectPixel();
+ ImplSVData *pSVData = ImplGetSVData();
+ vcl::Window *pWin = pSVData->maFrameData.mpFirstFrame;
+ bool bWrapped = false;
+ while( pWin )
+ {
+ if( !pWin->ImplIsRealParentPath( this ) && ( pWin != this ) &&
+ pWin->ImplGetWindow()->IsTopWindow() && pWin->mpWindowImpl->mbReallyVisible )
+ {
+ SalFrameGeometry g = pWin->mpWindowImpl->mpFrame->GetGeometry();
+ if( std::abs(g.nX-aState.mnX) < 2 && std::abs(g.nY-aState.mnY) < 5 )
+ {
+ long displacement = g.nTopDecoration ? g.nTopDecoration : 20;
+ if( aState.mnX + displacement + aState.mnWidth + g.nRightDecoration > o3tl::make_unsigned(aDesktop.Right()) ||
+ aState.mnY + displacement + aState.mnHeight + g.nBottomDecoration > o3tl::make_unsigned(aDesktop.Bottom()) )
+ {
+ // displacing would leave screen
+ aState.mnX = g.nLeftDecoration ? g.nLeftDecoration : 10; // should result in (0,0)
+ aState.mnY = displacement;
+ if( bWrapped ||
+ aState.mnX + displacement + aState.mnWidth + g.nRightDecoration > o3tl::make_unsigned(aDesktop.Right()) ||
+ aState.mnY + displacement + aState.mnHeight + g.nBottomDecoration > o3tl::make_unsigned(aDesktop.Bottom()) )
+ break; // further displacement not possible -> break
+ // avoid endless testing
+ bWrapped = true;
+ }
+ else
+ {
+ // displace
+ aState.mnX += displacement;
+ aState.mnY += displacement;
+ }
+ pWin = pSVData->maFrameData.mpFirstFrame; // check new pos again
+ }
+ }
+ pWin = pWin->mpWindowImpl->mpFrameData->mpNextFrame;
+ }
+ }
+
+ mpWindowImpl->mpFrame->SetWindowState( &aState );
+
+ // do a synchronous resize for layout reasons
+ // but use rData only when the window is not to be maximized (#i38089#)
+ // otherwise we have no useful size information
+ if( (rData.GetMask() & WindowStateMask::State) && (nState & WindowStateState::Maximized) )
+ {
+ // query maximized size from frame
+ SalFrameGeometry aGeometry = mpWindowImpl->mpFrame->GetGeometry();
+
+ // but use it only if it is different from the restore size (rData)
+ // as currently only on windows the exact size of a maximized window
+ // can be computed without actually showing the window
+ if( aGeometry.nWidth != rData.GetWidth() || aGeometry.nHeight != rData.GetHeight() )
+ ImplHandleResize( pWindow, aGeometry.nWidth, aGeometry.nHeight );
+ }
+ else
+ if( rData.GetMask() & (WindowStateMask::Width|WindowStateMask::Height) )
+ ImplHandleResize( pWindow, aState.mnWidth, aState.mnHeight ); // #i43799# use aState and not rData, see above
+ }
+ else
+ {
+ PosSizeFlags nPosSize = PosSizeFlags::NONE;
+ if ( nValidMask & WindowStateMask::X )
+ nPosSize |= PosSizeFlags::X;
+ if ( nValidMask & WindowStateMask::Y )
+ nPosSize |= PosSizeFlags::Y;
+ if ( nValidMask & WindowStateMask::Width )
+ nPosSize |= PosSizeFlags::Width;
+ if ( nValidMask & WindowStateMask::Height )
+ nPosSize |= PosSizeFlags::Height;
+
+ if( IsRollUp() )
+ RollDown();
+
+ long nX = rData.GetX();
+ long nY = rData.GetY();
+ long nWidth = rData.GetWidth();
+ long nHeight = rData.GetHeight();
+ const SalFrameGeometry& rGeom = pWindow->mpWindowImpl->mpFrame->GetGeometry();
+ if( nX < 0 )
+ nX = 0;
+ if( nX + nWidth > static_cast<long>(rGeom.nWidth) )
+ nX = rGeom.nWidth - nWidth;
+ if( nY < 0 )
+ nY = 0;
+ if( nY + nHeight > static_cast<long>(rGeom.nHeight) )
+ nY = rGeom.nHeight - nHeight;
+ setPosSizePixel( nX, nY, nWidth, nHeight, nPosSize );
+ maOrgSize = Size( nWidth, nHeight );
+
+ // 91625 - ignore Minimize
+ if ( nValidMask & WindowStateMask::State )
+ {
+ const WindowStateState nState = rData.GetState();
+ if ( nState & WindowStateState::Rollup )
+ RollUp();
+ else
+ RollDown();
+ }
+ }
+}
+
+void SystemWindow::GetWindowStateData( WindowStateData& rData ) const
+{
+ WindowStateMask nValidMask = rData.GetMask();
+ if ( nValidMask == WindowStateMask::NONE )
+ return;
+
+ if ( mbSysChild )
+ return;
+
+ const vcl::Window* pWindow = this;
+ while ( pWindow->mpWindowImpl->mpBorderWindow )
+ pWindow = pWindow->mpWindowImpl->mpBorderWindow;
+
+ if ( pWindow->mpWindowImpl->mbFrame )
+ {
+ SalFrameState aState;
+ aState.mnMask = WindowStateMask::All;
+ if ( mpWindowImpl->mpFrame->GetWindowState( &aState ) )
+ {
+ if ( nValidMask & WindowStateMask::X )
+ rData.SetX( aState.mnX );
+ if ( nValidMask & WindowStateMask::Y )
+ rData.SetY( aState.mnY );
+ if ( nValidMask & WindowStateMask::Width )
+ rData.SetWidth( aState.mnWidth );
+ if ( nValidMask & WindowStateMask::Height )
+ rData.SetHeight( aState.mnHeight );
+ if ( aState.mnMask & WindowStateMask::MaximizedX )
+ {
+ rData.SetMaximizedX( aState.mnMaximizedX );
+ nValidMask |= WindowStateMask::MaximizedX;
+ }
+ if ( aState.mnMask & WindowStateMask::MaximizedY )
+ {
+ rData.SetMaximizedY( aState.mnMaximizedY );
+ nValidMask |= WindowStateMask::MaximizedY;
+ }
+ if ( aState.mnMask & WindowStateMask::MaximizedWidth )
+ {
+ rData.SetMaximizedWidth( aState.mnMaximizedWidth );
+ nValidMask |= WindowStateMask::MaximizedWidth;
+ }
+ if ( aState.mnMask & WindowStateMask::MaximizedHeight )
+ {
+ rData.SetMaximizedHeight( aState.mnMaximizedHeight );
+ nValidMask |= WindowStateMask::MaximizedHeight;
+ }
+ if ( nValidMask & WindowStateMask::State )
+ {
+ // #94144# allow Minimize again, should be masked out when read from configuration
+ // 91625 - ignore Minimize
+ if ( !(nValidMask&WindowStateMask::Minimized) )
+ aState.mnState &= ~WindowStateState::Minimized;
+ rData.SetState( aState.mnState );
+ }
+ rData.SetMask( nValidMask );
+ }
+ else
+ rData.SetMask( WindowStateMask::NONE );
+ }
+ else
+ {
+ Point aPos = GetPosPixel();
+ Size aSize = GetSizePixel();
+ WindowStateState nState = WindowStateState::NONE;
+
+ if ( IsRollUp() )
+ {
+ aSize.AdjustHeight(maOrgSize.Height() );
+ nState = WindowStateState::Rollup;
+ }
+
+ if ( nValidMask & WindowStateMask::X )
+ rData.SetX( aPos.X() );
+ if ( nValidMask & WindowStateMask::Y )
+ rData.SetY( aPos.Y() );
+ if ( nValidMask & WindowStateMask::Width )
+ rData.SetWidth( aSize.Width() );
+ if ( nValidMask & WindowStateMask::Height )
+ rData.SetHeight( aSize.Height() );
+ if ( nValidMask & WindowStateMask::State )
+ rData.SetState( nState );
+ }
+}
+
+void SystemWindow::SetWindowState(const OString& rStr)
+{
+ if (rStr.isEmpty())
+ return;
+
+ WindowStateData aData;
+ ImplWindowStateFromStr( aData, rStr );
+ SetWindowStateData( aData );
+}
+
+OString SystemWindow::GetWindowState( WindowStateMask nMask ) const
+{
+ WindowStateData aData;
+ aData.SetMask( nMask );
+ GetWindowStateData( aData );
+
+ return aData.ToStr();
+}
+
+void SystemWindow::SetMenuBar(MenuBar* pMenuBar)
+{
+ if ( mpMenuBar != pMenuBar )
+ {
+ MenuBar* pOldMenuBar = mpMenuBar;
+ vcl::Window* pOldWindow = nullptr;
+ VclPtr<vcl::Window> pNewWindow;
+ mpMenuBar = pMenuBar;
+
+ if ( mpWindowImpl->mpBorderWindow && (mpWindowImpl->mpBorderWindow->GetType() == WindowType::BORDERWINDOW) )
+ {
+ if ( pOldMenuBar )
+ pOldWindow = pOldMenuBar->ImplGetWindow();
+ else
+ pOldWindow = nullptr;
+ if ( pOldWindow )
+ {
+ CallEventListeners( VclEventId::WindowMenubarRemoved, static_cast<void*>(pOldMenuBar) );
+ pOldWindow->SetAccessible( css::uno::Reference< css::accessibility::XAccessible >() );
+ }
+ if ( pMenuBar )
+ {
+ SAL_WARN_IF( pMenuBar->pWindow, "vcl", "SystemWindow::SetMenuBar() - MenuBars can only set in one SystemWindow at time" );
+
+ pNewWindow = MenuBar::ImplCreate(mpWindowImpl->mpBorderWindow, pOldWindow, pMenuBar);
+ static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->SetMenuBarWindow(pNewWindow);
+
+ CallEventListeners( VclEventId::WindowMenubarAdded, static_cast<void*>(pMenuBar) );
+ }
+ else
+ static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->SetMenuBarWindow( nullptr );
+ ImplToBottomChild();
+ if ( pOldMenuBar )
+ {
+ bool bDelete = (pMenuBar == nullptr);
+ if( bDelete && pOldWindow )
+ {
+ if( mpImplData->mpTaskPaneList )
+ mpImplData->mpTaskPaneList->RemoveWindow( pOldWindow );
+ }
+ MenuBar::ImplDestroy( pOldMenuBar, bDelete );
+ if( bDelete )
+ pOldWindow = nullptr; // will be deleted in MenuBar::ImplDestroy,
+ }
+
+ }
+ else
+ {
+ if( pMenuBar )
+ pNewWindow = pMenuBar->ImplGetWindow();
+ if( pOldMenuBar )
+ pOldWindow = pOldMenuBar->ImplGetWindow();
+ }
+
+ // update taskpane list to make menubar accessible
+ if( mpImplData->mpTaskPaneList )
+ {
+ if( pOldWindow )
+ mpImplData->mpTaskPaneList->RemoveWindow( pOldWindow );
+ if( pNewWindow )
+ mpImplData->mpTaskPaneList->AddWindow( pNewWindow );
+ }
+ }
+}
+
+void SystemWindow::SetNotebookBar(const OUString& rUIXMLDescription,
+ const css::uno::Reference<css::frame::XFrame>& rFrame,
+ const NotebookBarAddonsItem& aNotebookBarAddonsItem,
+ bool bReloadNotebookbar)
+{
+ if (rUIXMLDescription != maNotebookBarUIFile || bReloadNotebookbar)
+ {
+ static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())
+ ->SetNotebookBar(rUIXMLDescription, rFrame, aNotebookBarAddonsItem);
+ maNotebookBarUIFile = rUIXMLDescription;
+ if(GetNotebookBar())
+ GetNotebookBar()->SetSystemWindow(this);
+ }
+}
+
+void SystemWindow::CloseNotebookBar()
+{
+ static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->CloseNotebookBar();
+ maNotebookBarUIFile.clear();
+}
+
+VclPtr<NotebookBar> const & SystemWindow::GetNotebookBar() const
+{
+ return static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->GetNotebookBar();
+}
+
+void SystemWindow::SetMenuBarMode( MenuBarMode nMode )
+{
+ if ( mnMenuBarMode != nMode )
+ {
+ mnMenuBarMode = nMode;
+ if ( mpWindowImpl->mpBorderWindow && (mpWindowImpl->mpBorderWindow->GetType() == WindowType::BORDERWINDOW) )
+ {
+ if ( nMode == MenuBarMode::Hide )
+ static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->SetMenuBarMode( true );
+ else
+ static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->SetMenuBarMode( false );
+ }
+ }
+}
+
+bool SystemWindow::ImplIsInTaskPaneList( vcl::Window* pWin )
+{
+ if( mpImplData && mpImplData->mpTaskPaneList )
+ return mpImplData->mpTaskPaneList->IsInList( pWin );
+ return false;
+}
+
+unsigned int SystemWindow::GetScreenNumber() const
+{
+ return mpWindowImpl->mpFrame->maGeometry.nDisplayScreenNumber;
+}
+
+void SystemWindow::SetScreenNumber(unsigned int nDisplayScreen)
+{
+ mpWindowImpl->mpFrame->SetScreenNumber( nDisplayScreen );
+}
+
+void SystemWindow::SetApplicationID(const OUString &rApplicationID)
+{
+ mpWindowImpl->mpFrame->SetApplicationID( rApplicationID );
+}
+
+void SystemWindow::SetCloseHdl(const Link<SystemWindow&,void>& rLink)
+{
+ mpImplData->maCloseHdl = rLink;
+}
+
+const Link<SystemWindow&,void>& SystemWindow::GetCloseHdl() const
+{
+ return mpImplData->maCloseHdl;
+}
+
+void SystemWindow::queue_resize(StateChangedType /*eReason*/)
+{
+ if (!isLayoutEnabled())
+ return;
+ if (isCalculatingInitialLayoutSize())
+ return;
+ InvalidateSizeCache();
+ if (hasPendingLayout())
+ return;
+ maLayoutIdle.Start();
+}
+
+void SystemWindow::Resize()
+{
+ queue_resize();
+}
+
+bool SystemWindow::isLayoutEnabled() const
+{
+ //pre dtor called, and single child is a container => we're layout enabled
+ return mpImplData && ::isLayoutEnabled(this);
+}
+
+Size SystemWindow::GetOptimalSize() const
+{
+ if (!isLayoutEnabled())
+ return Window::GetOptimalSize();
+
+ Size aSize = VclContainer::getLayoutRequisition(*GetWindow(GetWindowType::FirstChild));
+
+ 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 )
+{
+ if (!isLayoutEnabled())
+ {
+ SAL_WARN("vcl.layout", "SystemWindow has become non-layout because extra children have been added directly to it.");
+ return;
+ }
+
+ Window *pBox = GetWindow(GetWindowType::FirstChild);
+ 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()
+{
+ maLayoutIdle.Stop();
+
+ //resize SystemWindow to fit requisition on initial show
+ Window *pBox = GetWindow(GetWindowType::FirstChild);
+
+ settingOptimalLayoutSize(pBox);
+
+ Size aSize = get_preferred_size();
+
+ Size aMax(bestmaxFrameSizeForScreenSize(GetDesktopRectPixel().GetSize()));
+
+ aSize.setWidth( std::min(aMax.Width(), aSize.Width()) );
+ aSize.setHeight( std::min(aMax.Height(), aSize.Height()) );
+
+ SetMinOutputSizePixel(aSize);
+ SetSizePixel(aSize);
+ setPosSizeOnContainee(aSize, *pBox);
+}
+
+void SystemWindow::DoInitialLayout()
+{
+ if (GetSettings().GetStyleSettings().GetAutoMnemonic())
+ Accelerator::GenerateAutoMnemonicsOnHierarchy(this);
+
+ if (isLayoutEnabled())
+ {
+ mbIsCalculatingInitialLayoutSize = true;
+ setDeferredProperties();
+ setOptimalLayoutSize();
+ mbIsCalculatingInitialLayoutSize = false;
+ }
+}
+
+void SystemWindow::doDeferredInit(WinBits /*nBits*/)
+{
+ SAL_WARN("vcl.layout", "SystemWindow in layout without doDeferredInit impl");
+}
+
+void SystemWindow::createScreenshot(VirtualDevice& rOutput)
+{
+ // same prerequisites as in Execute()
+ setDeferredProperties();
+ ImplAdjustNWFSizes();
+ Show();
+ ToTop();
+ ensureRepaint();
+
+ Point aPos;
+ Size aSize(GetOutputSizePixel());
+
+ rOutput.SetOutputSizePixel(aSize);
+ rOutput.DrawOutDev(aPos, aSize, aPos, aSize, *this);
+}
+
+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::Yield();
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/tabdlg.cxx b/vcl/source/window/tabdlg.cxx
new file mode 100644
index 000000000..ab0f026d0
--- /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/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 );
+ long nDownCtrl = 0;
+ 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());
+ long nTxtWidth = aOptimalSize.Width();
+ if ( nTxtWidth > aCtrlSize.Width() )
+ aCtrlSize.setWidth( nTxtWidth );
+ long nTxtHeight = aOptimalSize.Height();
+ if ( nTxtHeight > aCtrlSize.Height() )
+ aCtrlSize.setHeight( nTxtHeight );
+ nDownCtrl++;
+ }
+ else
+ {
+ 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;
+ long nX;
+ long nY = aDlgSize.Height();
+ long nTopX = IMPL_DIALOG_OFFSET;
+
+ // all buttons are right aligned under Windows 95
+ nX = IMPL_DIALOG_OFFSET;
+ long nCtrlBarWidth = ((aCtrlSize.Width()+IMPL_DIALOG_OFFSET)*nDownCtrl)-IMPL_DIALOG_OFFSET;
+ if ( nCtrlBarWidth <= aTabSize.Width() )
+ nX = aTabSize.Width() - nCtrlBarWidth + IMPL_DIALOG_OFFSET;
+
+ vcl::Window* pChild2 = GetWindow( GetWindowType::FirstChild );
+ while ( pChild2 )
+ {
+ if ( pChild2->IsVisible() )
+ {
+ if ( pChild2 == pTabControl )
+ bTabCtrl = true;
+ else if ( bTabCtrl )
+ {
+ if ( !nLines )
+ nLines = 1;
+
+ if ( nX+aCtrlSize.Width()-IMPL_DIALOG_OFFSET > aTabSize.Width() )
+ {
+ nY += aCtrlSize.Height()+IMPL_DIALOG_OFFSET;
+ nX = IMPL_DIALOG_OFFSET;
+ nLines++;
+ }
+
+ pChild2->SetPosSizePixel( Point( nX, nY ), aCtrlSize );
+ nX += aCtrlSize.Width()+IMPL_DIALOG_OFFSET;
+ }
+ else
+ {
+ Size aChildSize = pChild2->GetSizePixel();
+ pChild2->SetPosPixel( Point( nTopX, (nOffY-aChildSize.Height())/2 ) );
+ nTopX += aChildSize.Width()+2;
+ }
+ }
+
+ pChild2 = pChild2->GetWindow( GetWindowType::Next );
+ }
+
+ aDlgSize.AdjustHeight(nLines * (aCtrlSize.Height()+IMPL_DIALOG_OFFSET) );
+ SetOutputSizePixel( aDlgSize );
+ }
+
+ // store offset
+ if ( nOffY )
+ {
+ Size aDlgSize = GetOutputSizePixel();
+ if ( !mpFixedLine )
+ mpFixedLine = VclPtr<FixedLine>::Create( this );
+ mpFixedLine->SetPosSizePixel( Point( 0, nOffY ),
+ Size( aDlgSize.Width(), 2 ) );
+ mpFixedLine->Show();
+ }
+
+ mbPosControls = false;
+}
+
+TabDialog::TabDialog( vcl::Window* pParent, WinBits nStyle ) :
+ Dialog( WindowType::TABDIALOG )
+{
+ ImplInitTabDialogData();
+ ImplInitDialog( pParent, nStyle );
+}
+
+TabDialog::~TabDialog()
+{
+ disposeOnce();
+}
+
+void TabDialog::dispose()
+{
+ mpFixedLine.disposeAndClear();
+ Dialog::dispose();
+}
+
+void TabDialog::StateChanged( StateChangedType nType )
+{
+ if ( nType == StateChangedType::InitShow )
+ {
+ // Calculate the Layout only for the initialized state
+ if ( mbPosControls )
+ ImplPosControls();
+ }
+ Dialog::StateChanged( nType );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/tabpage.cxx b/vcl/source/window/tabpage.cxx
new file mode 100644
index 000000000..e4d09cc89
--- /dev/null
+++ b/vcl/source/window/tabpage.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/accel.hxx>
+#include <vcl/event.hxx>
+#include <vcl/layout.hxx>
+#include <vcl/tabpage.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/settings.hxx>
+
+void TabPage::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ if ( !(nStyle & WB_NODIALOGCONTROL) )
+ nStyle |= WB_DIALOGCONTROL;
+
+ Window::ImplInit( pParent, nStyle, nullptr );
+
+ 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 )
+ , IContext()
+{
+ ImplInit( pParent, nStyle );
+}
+
+TabPage::TabPage(vcl::Window *pParent, const OString& rID, const OUString& rUIXMLDescription)
+ : Window(WindowType::TABPAGE)
+ , IContext()
+{
+ ImplInit(pParent, 0);
+ m_pUIBuilder.reset( new VclBuilder(this, getUIRootDir(), rUIXMLDescription, rID) );
+ set_hexpand(true);
+ set_vexpand(true);
+ set_expand(true);
+}
+
+TabPage::~TabPage()
+{
+ disposeOnce();
+}
+
+void TabPage::dispose()
+{
+ disposeBuilder();
+ vcl::Window::dispose();
+}
+
+void TabPage::StateChanged( StateChangedType nType )
+{
+ Window::StateChanged( nType );
+
+ if ( nType == StateChangedType::InitShow )
+ {
+ if (GetSettings().GetStyleSettings().GetAutoMnemonic())
+ Accelerator::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) )
+ {
+ 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, DrawFlags )
+{
+ Point aPos = pDev->LogicToPixel( rPos );
+ Size aSize = GetSizePixel();
+
+ Wallpaper aWallpaper = GetBackground();
+ if ( !aWallpaper.IsBitmap() )
+ ImplInitSettings();
+
+ pDev->Push();
+ pDev->SetMapMode();
+ pDev->SetLineColor();
+
+ if ( aWallpaper.IsBitmap() )
+ pDev->DrawBitmapEx( aPos, aSize, aWallpaper.GetBitmap() );
+ else
+ {
+ if( aWallpaper.GetColor() == COL_AUTO )
+ pDev->SetFillColor( GetSettings().GetStyleSettings().GetDialogColor() );
+ else
+ pDev->SetFillColor( aWallpaper.GetColor() );
+ pDev->DrawRect( tools::Rectangle( aPos, aSize ) );
+ }
+
+ pDev->Pop();
+}
+
+Size TabPage::GetOptimalSize() const
+{
+ if (isLayoutEnabled(this))
+ return VclContainer::getLayoutRequisition(*GetWindow(GetWindowType::FirstChild));
+ return getLegacyBestSizeForChildren(*this);
+}
+
+void TabPage::SetPosSizePixel(const Point& rAllocPos, const Size& rAllocation)
+{
+ Window::SetPosSizePixel(rAllocPos, rAllocation);
+ if (isLayoutEnabled(this) && rAllocation.Width() && rAllocation.Height())
+ VclContainer::setLayoutAllocation(*GetWindow(GetWindowType::FirstChild), Point(0, 0), rAllocation);
+}
+
+void TabPage::SetSizePixel(const Size& rAllocation)
+{
+ Window::SetSizePixel(rAllocation);
+ if (isLayoutEnabled(this) && rAllocation.Width() && rAllocation.Height())
+ VclContainer::setLayoutAllocation(*GetWindow(GetWindowType::FirstChild), Point(0, 0), rAllocation);
+}
+
+void TabPage::SetPosPixel(const Point& rAllocPos)
+{
+ Window::SetPosPixel(rAllocPos);
+ Size aAllocation(GetOutputSizePixel());
+ if (isLayoutEnabled(this) && aAllocation.Width() && aAllocation.Height())
+ {
+ VclContainer::setLayoutAllocation(*GetWindow(GetWindowType::FirstChild), Point(0, 0), aAllocation);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/taskpanelist.cxx b/vcl/source/window/taskpanelist.cxx
new file mode 100644
index 000000000..6e321f498
--- /dev/null
+++ b/vcl/source/window/taskpanelist.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/dockwin.hxx>
+#include <vcl/taskpanelist.hxx>
+
+#include <svdata.hxx>
+#include "menubarwindow.hxx"
+
+#include <algorithm>
+
+namespace {
+
+Point ImplTaskPaneListGetPos( const vcl::Window *w )
+{
+ Point pos;
+ if( w->IsDockingWindow() )
+ {
+ pos = static_cast<const DockingWindow*>(w)->GetPosPixel();
+ vcl::Window *pF = static_cast<const DockingWindow*>(w)->GetFloatingWindow();
+ if( pF )
+ pos = pF->OutputToAbsoluteScreenPixel( pF->ScreenToOutputPixel( pos ) );
+ else
+ pos = w->OutputToAbsoluteScreenPixel( pos );
+ }
+ else
+ pos = w->OutputToAbsoluteScreenPixel( w->GetPosPixel() );
+
+ return pos;
+}
+
+// compares window pos left-to-right
+struct LTRSort
+{
+ bool operator()( const vcl::Window* w1, const vcl::Window* w2 ) const
+ {
+ Point pos1(ImplTaskPaneListGetPos( w1 ));
+ Point pos2(ImplTaskPaneListGetPos( w2 ));
+
+ if( pos1.X() == pos2.X() )
+ return ( pos1.Y() < pos2.Y() );
+ else
+ return ( pos1.X() < pos2.X() );
+ }
+};
+
+}
+
+static void ImplTaskPaneListGrabFocus( vcl::Window *pWindow, bool bForward )
+{
+ // put focus in child of floating windows which is typically a toolbar
+ // that can deal with the focus
+ if( pWindow->ImplIsFloatingWindow() && pWindow->GetWindow( GetWindowType::FirstChild ) )
+ pWindow = pWindow->GetWindow( GetWindowType::FirstChild );
+ pWindow->ImplGrabFocus( GetFocusFlags::F6 | (bForward ? GetFocusFlags::Forward : GetFocusFlags::Backward));
+}
+
+TaskPaneList::TaskPaneList()
+{
+}
+
+TaskPaneList::~TaskPaneList()
+{
+}
+
+void TaskPaneList::AddWindow( vcl::Window *pWindow )
+{
+ if( pWindow )
+ {
+ auto insertionPos = dynamic_cast<MenuBarWindow*>(pWindow) ? mTaskPanes.begin() : mTaskPanes.end();
+ for ( auto p = mTaskPanes.begin(); p != mTaskPanes.end(); ++p )
+ {
+ if ( *p == pWindow )
+ // avoid duplicates
+ return;
+
+ // If the new window is the child of an existing pane window, or vice versa,
+ // ensure that in our pane list, *first* the child window appears, *then*
+ // the ancestor window.
+ // This is necessary for HandleKeyEvent: There, the list is traveled from the
+ // beginning, until the first window is found which has the ChildPathFocus. Now
+ // if this would be the ancestor window of another pane window, this would fudge
+ // the result
+ if ( pWindow->IsWindowOrChild( *p ) )
+ {
+ insertionPos = p + 1;
+ break;
+ }
+ if ( (*p)->IsWindowOrChild( pWindow ) )
+ {
+ insertionPos = p;
+ break;
+ }
+ }
+
+ mTaskPanes.insert( insertionPos, pWindow );
+ pWindow->ImplIsInTaskPaneList( true );
+ }
+}
+
+void TaskPaneList::RemoveWindow( vcl::Window *pWindow )
+{
+ auto p = ::std::find( mTaskPanes.begin(), mTaskPanes.end(), VclPtr<vcl::Window>(pWindow) );
+ if( p != mTaskPanes.end() )
+ {
+ mTaskPanes.erase( p );
+ pWindow->ImplIsInTaskPaneList( false );
+ }
+}
+
+bool TaskPaneList::IsInList( vcl::Window *pWindow )
+{
+ auto p = ::std::find( mTaskPanes.begin(), mTaskPanes.end(), VclPtr<vcl::Window>(pWindow) );
+ return p != mTaskPanes.end();
+}
+
+bool TaskPaneList::IsCycleKey(const vcl::KeyCode& rKeyCode)
+{
+ return rKeyCode.GetCode() == KEY_F6 && !rKeyCode.IsMod2(); // F6
+}
+
+bool TaskPaneList::HandleKeyEvent(const KeyEvent& rKeyEvent)
+{
+
+ // F6 cycles through everything and works always
+
+ // MAV, #i104204#
+ // The old design was the following one:
+ // < Ctrl-TAB cycles through Menubar, Toolbars and Floatingwindows only and is
+ // < only active if one of those items has the focus
+
+ // Since the design of Ctrl-Tab looks to be inconsistent ( non-modal dialogs are not reachable
+ // and the shortcut conflicts with tab-control shortcut ), it is no more supported
+ vcl::KeyCode aKeyCode = rKeyEvent.GetKeyCode();
+ bool bForward = !aKeyCode.IsShift();
+ if (TaskPaneList::IsCycleKey(aKeyCode))
+ {
+ bool bSplitterOnly = aKeyCode.IsMod1() && aKeyCode.IsShift();
+
+ // is the focus in the list ?
+ auto p = std::find_if(mTaskPanes.begin(), mTaskPanes.end(),
+ [](const VclPtr<vcl::Window>& rWinPtr) { return rWinPtr->HasChildPathFocus( true ); });
+ if( p != mTaskPanes.end() )
+ {
+ vcl::Window *pWin = p->get();
+
+ // Ctrl-F6 goes directly to the document
+ if( !pWin->IsDialog() && aKeyCode.IsMod1() && !aKeyCode.IsShift() )
+ {
+ pWin->ImplGrabFocusToDocument( GetFocusFlags::F6 );
+ return true;
+ }
+
+ // activate next task pane
+ vcl::Window *pNextWin = nullptr;
+
+ if( bSplitterOnly )
+ pNextWin = FindNextSplitter( *p );
+ else
+ pNextWin = FindNextFloat( *p, bForward );
+
+ if( pNextWin != pWin )
+ {
+ ImplGetSVData()->mpWinData->mbNoSaveFocus = true;
+ ImplTaskPaneListGrabFocus( pNextWin, bForward );
+ ImplGetSVData()->mpWinData->mbNoSaveFocus = false;
+ }
+ else
+ {
+ // forward key if no splitter found
+ if( bSplitterOnly )
+ return false;
+
+ // we did not find another taskpane, so
+ // put focus back into document
+ pWin->ImplGrabFocusToDocument( GetFocusFlags::F6 | (bForward ? GetFocusFlags::Forward : GetFocusFlags::Backward));
+ }
+
+ return true;
+ }
+
+ // the focus is not in the list: activate first float if F6 was pressed
+ vcl::Window *pWin;
+ if( bSplitterOnly )
+ pWin = FindNextSplitter( nullptr );
+ else
+ pWin = FindNextFloat( nullptr, bForward );
+ if( pWin )
+ {
+ ImplTaskPaneListGrabFocus( pWin, bForward );
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// returns next splitter
+vcl::Window* TaskPaneList::FindNextSplitter( vcl::Window *pWindow )
+{
+ ::std::stable_sort( mTaskPanes.begin(), mTaskPanes.end(), LTRSort() );
+
+ auto p = mTaskPanes.begin();
+ if( pWindow )
+ p = std::find(mTaskPanes.begin(), mTaskPanes.end(), pWindow);
+
+ if( p != mTaskPanes.end() )
+ {
+ unsigned n = mTaskPanes.size();
+ while( --n )
+ {
+ if( pWindow ) // increment before test
+ ++p;
+ if( p == mTaskPanes.end() )
+ p = mTaskPanes.begin();
+ if( (*p)->ImplIsSplitter() && (*p)->IsReallyVisible() && !(*p)->IsDialog() && (*p)->GetParent()->HasChildPathFocus() )
+ {
+ pWindow = (*p).get();
+ break;
+ }
+ if( !pWindow ) // increment after test, otherwise first element is skipped
+ ++p;
+ }
+ }
+
+ return pWindow;
+}
+
+// returns first valid item (regardless of type) if pWindow==0, otherwise returns next valid float
+vcl::Window* TaskPaneList::FindNextFloat( vcl::Window *pWindow, bool bForward )
+{
+ ::std::stable_sort( mTaskPanes.begin(), mTaskPanes.end(), LTRSort() );
+
+ if ( !bForward )
+ ::std::reverse( mTaskPanes.begin(), mTaskPanes.end() );
+
+ auto p = mTaskPanes.begin();
+ if( pWindow )
+ p = std::find(mTaskPanes.begin(), mTaskPanes.end(), pWindow);
+
+ while( p != mTaskPanes.end() )
+ {
+ if( pWindow ) // increment before test
+ ++p;
+ if( p == mTaskPanes.end() )
+ break; // do not wrap, send focus back to document at end of list
+ /* #i83908# do not use the menubar if it is native and invisible
+ */
+
+ bool bSkip = false; // used to skip infobar when it has no children
+ if( (*p)->GetType() == WindowType::WINDOW && (*p)->GetChildCount() == 0 )
+ bSkip = true;
+
+ if( !bSkip && (*p)->IsReallyVisible() && !(*p)->ImplIsSplitter() &&
+ ( (*p)->GetType() != WindowType::MENUBARWINDOW || static_cast<MenuBarWindow*>(p->get())->CanGetFocus() ) )
+ {
+ pWindow = (*p).get();
+ break;
+ }
+ if( !pWindow ) // increment after test, otherwise first element is skipped
+ ++p;
+ }
+
+ if ( !bForward )
+ ::std::reverse( mTaskPanes.begin(), mTaskPanes.end() );
+
+ return pWindow;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/toolbox.cxx b/vcl/source/window/toolbox.cxx
new file mode 100644
index 000000000..935a7e3ae
--- /dev/null
+++ b/vcl/source/window/toolbox.cxx
@@ -0,0 +1,4902 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <vcl/toolbox.hxx>
+#include <vcl/commandinfoprovider.hxx>
+#include <vcl/event.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/accel.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/help.hxx>
+#include <vcl/mnemonic.hxx>
+#include <vcl/gradient.hxx>
+#include <vcl/layout.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/settings.hxx>
+#include <vclstatuslistener.hxx>
+#include <vcl/ptrstyle.hxx>
+#include <bitmaps.hlst>
+
+#include <tools/poly.hxx>
+#include <svl/imageitm.hxx>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+#include <svdata.hxx>
+#include <window.h>
+#include <toolbox.h>
+#include <spin.hxx>
+#if defined(_WIN32)
+#include <svsys.h>
+#endif
+
+#include <cstdlib>
+#include <vector>
+#include <math.h>
+
+
+#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::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, long& rLeft, long& rTop,
+ long& rRight, 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(mbHorz ? 0 : 900);
+ g.SetStyle(GradientStyle::Linear);
+
+ g.SetStartColor(startCol);
+ g.SetEndColor(endCol);
+
+ bool bLineColor = rRenderContext.IsLineColor();
+ Color aOldCol = rRenderContext.GetLineColor();
+ rRenderContext.SetLineColor(rRenderContext.GetSettings().GetStyleSettings().GetShadowColor());
+
+ Size aFullSz(GetOutputSizePixel());
+ Size aLineSz(aFullSz);
+
+ // use the linesize only when floating
+ // full window height is used when docked (single line)
+ if (ImplIsFloatingMode())
+ {
+ long nLineSize;
+ if (mbHorz)
+ {
+ nLineSize = mnMaxItemHeight;
+ if (mnWinHeight > mnMaxItemHeight)
+ nLineSize = mnWinHeight;
+
+ aLineSz.setHeight( nLineSize );
+ }
+ else
+ {
+ nLineSize = mnMaxItemWidth;
+ aLineSz.setWidth( nLineSize );
+ }
+ }
+
+ 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)
+ {
+ 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
+ {
+ 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)
+{
+ // 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(GetActiveClipRegion());
+
+ rRenderContext.Push(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(PushFlags::LINECOLOR | PushFlags::FILLCOLOR);
+ rRenderContext.SetLineColor();
+ if (bHasOpenPopup)
+ // choose the same color as the popup will use
+ rRenderContext.SetFillColor(rRenderContext.GetSettings().GetStyleSettings().GetFaceGradientColor());
+ else
+ rRenderContext.SetFillColor(COL_WHITE);
+
+ rRenderContext.DrawRect(rRect);
+ rRenderContext.Pop();
+ }
+ else
+ ImplDrawBackground(rRenderContext, rRect);
+ }
+ else
+ ImplDrawBackground(rRenderContext, rRect);
+}
+
+void ToolBox::ImplDrawBorder(vcl::RenderContext& rRenderContext)
+{
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ long nDX = mnDX;
+ 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 )
+{
+ long nMax;
+ long nLeft = 0;
+ long nTop = 0;
+ long nRight = 0;
+ 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
+ long nCalcSize = 0;
+
+ for (auto const& item : mpData->m_aItems)
+ {
+ if ( item.mbVisible )
+ {
+ if ( item.mpWindow )
+ {
+ 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
+ long upperBoundWidth = nCalcSize * mpData->m_aItems.size();
+
+ ImplToolItems::size_type nLines;
+ ImplToolItems::size_type nCalcLines;
+ ImplToolItems::size_type nTempLines;
+ long nMaxLineWidth;
+ nCalcLines = ImplCalcBreaks( nCalcSize, &nMaxLineWidth, true );
+
+ maFloatSizes.reserve( nCalcLines );
+
+ nTempLines = nLines = nCalcLines;
+ while ( nLines )
+ {
+ 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->ShowTitleButton( TitleButton::Menu, bool( GetMenuType() & ToolBoxMenuType::Customize) );
+ }
+ else
+ {
+ // TODO: change SetMinOutputSizePixel to be not inline
+ SetMinOutputSizePixel( aMinSize );
+ SetMaxOutputSizePixel( aMaxSize );
+ }
+}
+
+ToolBox::ImplToolItems::size_type ToolBox::ImplCalcLines( long nToolSize ) const
+{
+ 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
+ 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;
+ long nOneLineSize;
+ long nCurSize;
+ long nMaxSize;
+ 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;
+ mnOutStyle = TOOLBOX_STYLE_FLAT; // force flat buttons since NWF
+ mnHighItemId = 0;
+ mnCurItemId = 0;
+ mnDownItemId = 0;
+ mnCurPos = ITEM_NOTFOUND;
+ mnLines = 1;
+ mnCurLine = 1;
+ mnCurLines = 1;
+ mnVisLines = 1;
+ mnFloatLines = 0;
+ mnDockLines = 0;
+ mnMouseModifier = 0;
+ mbDrag = false;
+ mbUpper = false;
+ mbLower = false;
+ mbIn = false;
+ mbCalc = true;
+ mbFormat = false;
+ mbFullPaint = false;
+ mbHorz = true;
+ mbScroll = false;
+ mbLastFloatMode = false;
+ mbCustomize = false;
+ mbDragging = false;
+ mbIsKeyEvent = false;
+ mbChangingHighlight = false;
+ mbImagesMirrored = false;
+ mbLineSpacing = false;
+ mbIsArranged = false;
+ meButtonType = ButtonType::SYMBOLONLY;
+ meAlign = WindowAlign::Top;
+ meDockAlign = WindowAlign::Top;
+ meLastStyle = PointerStyle::Arrow;
+ mnWinStyle = 0;
+ meLayoutMode = ToolBoxLayoutMode::Normal;
+ meTextPosition = ToolBoxTextPosition::Right;
+ mnLastFocusItemId = 0;
+ mnActivateCount = 0;
+ mnImagesRotationAngle = 0;
+
+ mpStatusListener = new VclStatusListener<ToolBox>(this, ".uno:ImageOrientation");
+ mpStatusListener->startListening();
+
+ mpIdle.reset(new Idle("vcl::ToolBox maIdle update"));
+ mpIdle->SetPriority( TaskPriority::RESIZE );
+ mpIdle->SetInvokeHandler( LINK( this, ToolBox, ImplUpdateHdl ) );
+
+ // set timeout and handler for dropdown items
+ mpData->maDropdownTimer.SetTimeout( 250 );
+ mpData->maDropdownTimer.SetInvokeHandler( LINK( this, ToolBox, ImplDropdownLongClickHdl ) );
+ mpData->maDropdownTimer.SetDebugName( "vcl::ToolBox mpData->maDropdownTimer" );
+}
+
+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(*this, rStyleSettings.GetToolFont());
+ if (bForeground || bFont)
+ ApplyForegroundSettings(*this, rStyleSettings);
+ if (bBackground)
+ {
+ ApplyBackgroundSettings(*this, 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 )
+{
+ ImplInitToolBoxData();
+ ImplInit( pParent, nStyle );
+}
+
+ToolBox::ToolBox(vcl::Window* pParent, const OString& rID,
+ const OUString& rUIXMLDescription, const css::uno::Reference<css::frame::XFrame> &rFrame)
+ : DockingWindow(WindowType::TOOLBOX)
+{
+ ImplInitToolBoxData();
+
+ loadUI(pParent, rID, rUIXMLDescription, rFrame);
+
+ // calculate size of floating windows and switch if the
+ // toolbox is initially in floating mode
+ if ( ImplIsFloatingMode() )
+ mbHorz = true;
+ else
+ Resize();
+
+ if (!(GetStyle() & WB_HIDE))
+ Show();
+}
+
+ToolBox::~ToolBox()
+{
+ disposeOnce();
+}
+
+void ToolBox::dispose()
+{
+ // #103005# make sure our activate/deactivate balance is right
+ while( mnActivateCount > 0 )
+ Deactivate();
+
+ // terminate popupmode if the floating window is
+ // still connected
+ if ( mpFloatWin )
+ mpFloatWin->EndPopupMode( FloatWinPopupEndFlags::Cancel );
+ mpFloatWin = nullptr;
+
+ // delete private data
+ mpData.reset();
+
+ ImplSVData* pSVData = ImplGetSVData();
+ delete pSVData->maCtrlData.mpTBDragMgr;
+ pSVData->maCtrlData.mpTBDragMgr = nullptr;
+
+ if (mpStatusListener.is())
+ mpStatusListener->dispose();
+
+ mpFloatWin.clear();
+
+ mpIdle.reset();
+
+ DockingWindow::dispose();
+}
+
+ImplToolItem* ToolBox::ImplGetItem( sal_uInt16 nItemId ) const
+{
+ if (!mpData)
+ return nullptr;
+
+ for (auto & item : mpData->m_aItems)
+ {
+ if ( item.mnId == nItemId )
+ return &item;
+ }
+
+ return nullptr;
+}
+
+static void ImplAddButtonBorder( long &rWidth, 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;
+
+ ImplDisableFlatButtons();
+
+ OutputDevice *pDefault = Application::GetDefaultDevice();
+ float fScaleFactor = pDefault ? pDefault->GetDPIScaleFactor() : 1.0;
+
+ long nDefWidth;
+ long nDefHeight;
+ long nMaxWidth = 0;
+ long nMaxHeight = 0;
+ long nMinWidth = 6;
+ long nMinHeight = 6;
+ 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( 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( 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 ? 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 )
+ {
+ 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 )
+ {
+ 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
+ long w = item.maItemSize.Width();
+ long h = item.maItemSize.Height();
+ ImplAddButtonBorder( w, h, mpData->mbNativeButtons );
+ item.maItemSize.setWidth(w);
+ item.maItemSize.setHeight(h);
+
+ if( item.meType == ToolBoxItemType::BUTTON )
+ {
+ long nMinW = std::max(nMinWidth, item.maMinimalItemSize.Width());
+ long nMinH = std::max(nMinHeight, item.maMinimalItemSize.Height());
+
+ long nGrowContentWidth = 0;
+ 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
+
+ long nFixedWidth = nDefWidth+nDropDownArrowWidth;
+ 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( long nWidth, long* pMaxLineWidth, bool bCalcHorz ) const
+{
+ sal_uLong nLineStart = 0;
+ sal_uLong nGroupStart = 0;
+ long nLineWidth = 0;
+ long nCurWidth;
+ long nLastGroupLineWidth = 0;
+ long nMaxLineWidth = 0;
+ ImplToolItems::size_type nLines = 1;
+ bool bWindow;
+ bool bBreak = false;
+ long nWidthTotal = nWidth;
+ 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 )
+ {
+ 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
+ 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
+
+ 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 );
+ long 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
+ long dx1 = std::abs( mnDX - aSize1.Width() );
+ long dy1 = std::abs( mnDY - aSize1.Height() );
+
+ long dx2 = std::abs( mnDX - aSize2.Width() );
+ 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;
+ long nLineSize;
+ long nLeft;
+ long nTop;
+ 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 )
+ {
+ 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() )
+ {
+ long nWinHeight = mnDY - nTop - nBottom;
+ if( nWinHeight > nLineSize )
+ nLineSize = nWinHeight;
+ }
+ }
+ else
+ {
+ 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() )
+ {
+ 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
+ long nX = nLeft; // top-left offset
+ 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);
+
+ 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))
+ {
+ 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 = 0;
+ mnDownItemId = 0;
+ mnMouseModifier = 0;
+ mnHighItemId = 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();
+ long x = rRect.Left() + (rRect.getWidth() - aImageSize.Width())/2;
+ long y = rRect.Top() + (rRect.getHeight() - aImageSize.Height())/2;
+ DrawImageFlags nImageStyle = DrawImageFlags::NONE;
+
+ rRenderContext.DrawImage(Point(x,y), pImage, nImageStyle);
+}
+
+static void ImplDrawDropdownArrow(vcl::RenderContext& rRenderContext, const tools::Rectangle& rDropDownRect, bool bSetColor, bool bRotate )
+{
+ bool bLineColor = rRenderContext.IsLineColor();
+ bool bFillColor = rRenderContext.IsFillColor();
+ Color aOldFillColor = rRenderContext.GetFillColor();
+ Color aOldLineColor = rRenderContext.GetLineColor();
+ rRenderContext.SetLineColor();
+
+ if ( bSetColor )
+ {
+ if (rRenderContext.GetSettings().GetStyleSettings().GetFaceColor().IsDark())
+ rRenderContext.SetFillColor(COL_WHITE);
+ else
+ rRenderContext.SetFillColor(COL_BLACK);
+ }
+
+ tools::Polygon aPoly(4);
+
+ // the assumption is, that the width always specifies the size of the expected arrow.
+ const long nMargin = round(2 * rRenderContext.GetDPIScaleFactor());
+ const long nSize = rDropDownRect.getWidth() - 2 * nMargin;
+ const long nHalfSize = (nSize + 1) / 2;
+ const long x = rDropDownRect.Left() + nMargin + (bRotate ? (rDropDownRect.getWidth() - nHalfSize) / 2 : 0);
+ const long y = rDropDownRect.Top() + nMargin + (rDropDownRect.getHeight() - (bRotate ? nSize : nHalfSize)) / 2;
+
+ aPoly.SetPoint(Point(x, y), 0);
+ if (bRotate) // >
+ {
+ aPoly.SetPoint(Point(x, y + nSize), 1);
+ aPoly.SetPoint(Point(x + nHalfSize, y + nHalfSize), 2);
+ }
+ else // v
+ {
+ aPoly.SetPoint(Point(x + nHalfSize, y + nHalfSize), 1);
+ aPoly.SetPoint(Point(x + nSize, y), 2);
+ }
+ aPoly.SetPoint(Point(x, y), 3);
+
+ auto aaflags = rRenderContext.GetAntialiasing();
+ rRenderContext.SetAntialiasing(AntialiasingFlags::EnableB2dDraw);
+ 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())
+ {
+ // #i53937# paint menu button only if necessary
+ if (!ImplHasClippedItems())
+ return;
+
+ // execute pending paint requests
+ ImplCheckUpdate();
+
+ rRenderContext.Push(PushFlags::FILLCOLOR | 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)
+ {
+ 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();
+
+ ImplDisableFlatButtons();
+
+ 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 );
+ long nOffX = SMALLBUTTON_OFF_NORMAL_X;
+ long nOffY = SMALLBUTTON_OFF_NORMAL_Y;
+ long nImageOffX = 0;
+ long nImageOffY = 0;
+ DrawButtonFlags nStyle = DrawButtonFlags::NONE;
+
+ // draw separators in flat style only
+ if ( (mnOutStyle & TOOLBOX_STYLE_FLAT) &&
+ (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;
+ }
+
+ if ( mnOutStyle & TOOLBOX_STYLE_FLAT )
+ {
+ ImplErase(rRenderContext, pItem->maRect, nHighlight != 0, bHasOpenPopup );
+ }
+ else
+ {
+ DecorationView aDecoView(&rRenderContext);
+ aDecoView.DrawButton(aButtonRect, nStyle);
+ }
+
+ 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
+ long nBtnWidth = aBtnSize.Width()-SMALLBUTTON_HSIZE;
+ long nBtnHeight = aBtnSize.Height()-SMALLBUTTON_VSIZE;
+ Size aImageSize;
+
+ const bool bDropDown = (pItem->mnBits & ToolBoxItemBits::DROPDOWN) == ToolBoxItemBits::DROPDOWN;
+ tools::Rectangle aDropDownRect;
+ if (bDropDown)
+ aDropDownRect = pItem->GetDropDownRect(mbHorz);
+
+ if ( bImage )
+ {
+ const Image* pImage = &(pItem->maImage);
+ aImageSize = pImage->GetSizePixel();
+
+ // determine drawing flags
+ DrawImageFlags nImageStyle = DrawImageFlags::NONE;
+
+ if ( !pItem->mbEnabled || !IsEnabled() )
+ nImageStyle |= DrawImageFlags::Disable;
+
+ // #i35563# the dontknow state indicates different states at the same time
+ // which should not be rendered disabled but normal
+
+ // draw the image
+ nImageOffX = nOffX;
+ nImageOffY = nOffY;
+ if ( ( (pItem->mnBits & (ToolBoxItemBits::LEFT|ToolBoxItemBits::DROPDOWN)) || bText )
+ && ( meTextPosition == ToolBoxTextPosition::Right ) )
+ {
+ // left align also to leave space for drop down arrow
+ // and when drawing text+image
+ // just center in y, except for vertical (ie rotated text)
+ if( mbHorz || !bText )
+ nImageOffY += (nBtnHeight-aImageSize.Height())/2;
+ }
+ else
+ {
+ nImageOffX += (nBtnWidth-(bDropDown ? aDropDownRect.getWidth() : 0)+SMALLBUTTON_OFF_NORMAL_X-aImageSize.Width())/2;
+ if ( meTextPosition == ToolBoxTextPosition::Right )
+ nImageOffY += (nBtnHeight-aImageSize.Height())/2;
+ }
+ if ( nHighlight != 0 || (pItem->meState == TRISTATE_TRUE) )
+ {
+ if( bHasOpenPopup )
+ ImplDrawFloatwinBorder(rRenderContext, pItem);
+ else
+ ImplDrawButton(rRenderContext, aButtonRect, nHighlight, pItem->meState == TRISTATE_TRUE,
+ pItem->mbEnabled && IsEnabled(), pItem->mbShowWindow);
+
+ if( nHighlight != 0 )
+ {
+ if( bHighContrastWhite )
+ nImageStyle |= DrawImageFlags::ColorTransform;
+ }
+ }
+ rRenderContext.DrawImage(Point( nImageOffX, nImageOffY ), *pImage, nImageStyle);
+ }
+
+ // draw the text
+ bool bRotate = false;
+ if ( bText )
+ {
+ const Size aTxtSize(GetCtrlTextWidth(pItem->maText), GetTextHeight());
+ long nTextOffX = nOffX;
+ 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 );
+
+ // center horizontally
+ nTextOffX += aTxtSize.Height();
+ nTextOffX += (nBtnWidth-aTxtSize.Height())/2;
+
+ // add in image offset
+ if( bImage )
+ nTextOffY = nImageOffY + aImageSize.Height() + TB_IMAGETEXTOFFSET;
+
+ rRenderContext.SetFont(aRotateFont);
+ }
+ else
+ {
+ if ( meTextPosition == ToolBoxTextPosition::Right )
+ {
+ // center vertically
+ nTextOffY += (nBtnHeight-aTxtSize.Height())/2;
+
+ // add in image offset
+ if( bImage )
+ nTextOffX = nImageOffX + aImageSize.Width() + TB_IMAGETEXTOFFSET;
+ }
+ else
+ {
+ // center horizontally
+ nTextOffX += (nBtnWidth-(bDropDown ? aDropDownRect.getWidth() : 0)+SMALLBUTTON_OFF_NORMAL_X-aTxtSize.Width() - TB_IMAGETEXTOFFSET)/2;
+ // set vertical position
+ nTextOffY += nBtnHeight - aTxtSize.Height();
+ }
+ }
+
+ // draw selection only if not already drawn during image output (see above)
+ if ( !bImage && (nHighlight != 0 || (pItem->meState == TRISTATE_TRUE) ) )
+ {
+ if( bHasOpenPopup )
+ ImplDrawFloatwinBorder(rRenderContext, pItem);
+ else
+ ImplDrawButton(rRenderContext, pItem->maRect, nHighlight, pItem->meState == TRISTATE_TRUE,
+ pItem->mbEnabled && IsEnabled(), pItem->mbShowWindow );
+ }
+
+ DrawTextFlags nTextStyle = DrawTextFlags::NONE;
+ if ( !pItem->mbEnabled )
+ nTextStyle |= DrawTextFlags::Disable;
+ rRenderContext.DrawCtrlText( Point( nTextOffX, nTextOffY ), pItem->maText,
+ 0, pItem->maText.getLength(), nTextStyle );
+ if ( bRotate )
+ SetFont( aOldFont );
+ }
+
+ // paint optional drop down arrow
+ if (bDropDown)
+ {
+ bool bSetColor = true;
+ if ( !pItem->mbEnabled || !IsEnabled() )
+ {
+ bSetColor = false;
+ rRenderContext.SetFillColor(rStyleSettings.GetShadowColor());
+ }
+
+ // dropdown only will be painted without inner border
+ if( (pItem->mnBits & ToolBoxItemBits::DROPDOWNONLY) != ToolBoxItemBits::DROPDOWNONLY )
+ {
+ ImplErase(rRenderContext, aDropDownRect, nHighlight != 0, bHasOpenPopup);
+
+ if( nHighlight != 0 || (pItem->meState == TRISTATE_TRUE) )
+ {
+ if( bHasOpenPopup )
+ ImplDrawFloatwinBorder(rRenderContext, pItem);
+ else
+ ImplDrawButton(rRenderContext, aDropDownRect, nHighlight, pItem->meState == TRISTATE_TRUE,
+ pItem->mbEnabled && IsEnabled(), false);
+ }
+ }
+ ImplDrawDropdownArrow(rRenderContext, aDropDownRect, bSetColor, bRotate);
+ }
+}
+
+void ToolBox::ImplDrawFloatwinBorder(vcl::RenderContext& rRenderContext, ImplToolItem const * pItem)
+{
+ if ( pItem->maRect.IsEmpty() )
+ return;
+
+ tools::Rectangle aRect( mpFloatWin->ImplGetItemEdgeClipRect() );
+ aRect.SetPos( AbsoluteScreenToOutputPixel( aRect.TopLeft() ) );
+ rRenderContext.SetLineColor(rRenderContext.GetSettings().GetStyleSettings().GetShadowColor());
+ Point p1, p2;
+
+ p1 = pItem->maRect.TopLeft();
+ p1.AdjustX( 1 );
+ p2 = pItem->maRect.TopRight();
+ p2.AdjustX( -1 );
+ rRenderContext.DrawLine( p1, p2);
+ p1 = pItem->maRect.BottomLeft();
+ p1.AdjustX( 1 );
+ p2 = pItem->maRect.BottomRight();
+ p2.AdjustX( -1 );
+ rRenderContext.DrawLine( p1, p2);
+
+ p1 = pItem->maRect.TopLeft();
+ p1.AdjustY( 1 );
+ p2 = pItem->maRect.BottomLeft();
+ p2.AdjustY( -1 );
+ rRenderContext.DrawLine( p1, p2);
+ p1 = pItem->maRect.TopRight();
+ p1.AdjustY( 1 );
+ p2 = pItem->maRect.BottomRight();
+ p2.AdjustY( -1 );
+ rRenderContext.DrawLine( p1, p2);
+
+}
+
+void ToolBox::ImplFloatControl( bool bStart, FloatingWindow* pFloatWindow )
+{
+
+ if ( bStart )
+ {
+ mpFloatWin = pFloatWindow;
+
+ // redraw item, to trigger drawing of a special border
+ InvalidateItem(mnCurPos);
+
+ mbDrag = false;
+ EndTracking();
+ if (IsMouseCaptured())
+ ReleaseMouse();
+ }
+ else
+ {
+ mpFloatWin = nullptr;
+
+ // if focus is still in this toolbox, then the floater was opened by keyboard
+ // draw current item with highlight and keep old state
+ bool bWasKeyboardActivate = mpData->mbDropDownByKeyboard;
+
+ if ( mnCurPos != ITEM_NOTFOUND )
+ InvalidateItem(mnCurPos);
+ Deactivate();
+
+ if( !bWasKeyboardActivate )
+ {
+ mnCurPos = ITEM_NOTFOUND;
+ mnCurItemId = 0;
+ mnHighItemId = 0;
+ }
+ mnDownItemId = 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.IsInside( aMousePos ) )
+ {
+ if ( !mnCurItemId )
+ {
+ InvalidateItem(mnCurPos);
+ mnCurItemId = pItem->mnId;
+ Highlight();
+ }
+
+ if ( (pItem->mnBits & ToolBoxItemBits::REPEAT) && bRepeat )
+ Select();
+ }
+ else
+ {
+ if ( mnCurItemId )
+ {
+ InvalidateItem(mnCurPos);
+ mnCurItemId = 0;
+ InvalidateItem(mnCurPos);
+ Highlight();
+ }
+ }
+
+ return true;
+ }
+
+ if ( mbUpper )
+ {
+ bool bNewIn = maUpperRect.IsInside( aMousePos );
+ if ( bNewIn != mbIn )
+ {
+ mbIn = bNewIn;
+ InvalidateSpin(true, false);
+ }
+ return true;
+ }
+
+ if ( mbLower )
+ {
+ bool bNewIn = maLowerRect.IsInside( aMousePos );
+ if ( bNewIn != mbIn )
+ {
+ mbIn = bNewIn;
+ InvalidateSpin(false);
+ }
+ return true;
+ }
+
+ return false;
+}
+
+bool ToolBox::ImplHandleMouseButtonUp( const MouseEvent& rMEvt, bool bCancel )
+{
+ ImplDisableFlatButtons();
+
+ 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.IsInside( 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);
+ Flush();
+ }
+ }
+ }
+ }
+
+ mnCurPos = ITEM_NOTFOUND;
+ mnCurItemId = 0;
+ mnDownItemId = 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;
+
+ ImplDisableFlatButtons();
+
+ 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()->mbToolBox)
+ {
+ bFocusWindowIsAToolBoxChild = true;
+ break;
+ }
+ pWin = pWin->GetParent();
+ }
+ }
+
+ if( bFocusWindowIsAToolBoxChild || (pFocusWin && 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().IsInside( 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 && ( (mnOutStyle & TOOLBOX_STYLE_FLAT) || !mnOutStyle ) )
+ {
+ bool bClearHigh = true;
+ if ( !rMEvt.IsLeaveWindow() && (mnCurPos == ITEM_NOTFOUND) )
+ {
+ ImplToolItems::size_type nTempPos = 0;
+ for (auto const& item : mpData->m_aItems)
+ {
+ if ( item.maRect.IsInside( aMousePos ) )
+ {
+ if ( (item.meType == ToolBoxItemType::BUTTON) && item.mbEnabled )
+ {
+ if ( !mnOutStyle || (mnOutStyle & TOOLBOX_STYLE_FLAT) )
+ {
+ 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.IsInside( 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 = 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.IsInside( 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 ).IsInside( 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 = 0;
+ mnDownItemId = 0;
+ mnMouseModifier = 0;
+ mnHighItemId = 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.IsInside( aMousePos ) && ImplHasClippedItems() )
+ {
+ if ( maMenuButtonHdl.IsSet() )
+ maMenuButtonHdl.Call( this );
+ else
+ ExecuteCustomMenu( mpData->maMenubuttonItem.maRect );
+ return;
+ }
+
+ // check scroll- and next-buttons here
+ if ( maUpperRect.IsInside( aMousePos ) )
+ {
+ if ( mnCurLine > 1 )
+ {
+ StartTracking();
+ mbUpper = true;
+ mbIn = true;
+ InvalidateSpin(true, false);
+ }
+ return;
+ }
+ if ( maLowerRect.IsInside( 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.IsOver( 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;
+
+ long nOldDX = mnDX;
+ 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 )
+ {
+ // 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(const OUString& rName)
+ {
+ return rName.startsWith(".uno") ||
+ rName.startsWith("slot:") ||
+ rName.startsWith("macro:") ||
+ rName.startsWith("vnd.sun.star.script");
+ }
+}
+
+const OUString& ToolBox::ImplGetHelpText( sal_uInt16 nItemId ) const
+{
+ ImplToolItem* pItem = ImplGetItem( nItemId );
+
+ assert( pItem );
+
+ if ( pItem->maHelpText.isEmpty() && ( !pItem->maHelpId.isEmpty() || pItem->maCommandStr.getLength() ))
+ {
+ Help* pHelp = Application::GetHelp();
+ if ( pHelp )
+ {
+ if (DispatchableCommand(pItem->maCommandStr))
+ pItem->maHelpText = pHelp->GetHelpText( pItem->maCommandStr, this );
+ if ( pItem->maHelpText.isEmpty() && !pItem->maHelpId.isEmpty() )
+ pItem->maHelpText = pHelp->GetHelpText( OStringToOUString( pItem->maHelpId, RTL_TEXTENCODING_UTF8 ), this );
+ }
+ }
+
+ return pItem->maHelpText;
+}
+
+void ToolBox::RequestHelp( const HelpEvent& rHEvt )
+{
+ sal_uInt16 nItemId;
+ Point aHelpPos;
+
+ if( !rHEvt.KeyboardActivated() )
+ {
+ nItemId = GetItemId( ScreenToOutputPixel( rHEvt.GetMousePosPixel() ) );
+ aHelpPos = rHEvt.GetMousePosPixel();
+ }
+ else
+ {
+ if( !mnHighItemId )
+ return;
+ else
+ nItemId = mnHighItemId;
+ tools::Rectangle aRect( GetItemRect( nItemId ) );
+ if( aRect.IsEmpty() )
+ return;
+ else
+ aHelpPos = OutputToScreenPixel( aRect.Center() );
+ }
+
+ if ( nItemId )
+ {
+ if ( rHEvt.GetMode() & (HelpEventMode::BALLOON | HelpEventMode::QUICK) )
+ {
+ // get rectangle
+ tools::Rectangle aTempRect = GetItemRect( nItemId );
+ Point aPt = OutputToScreenPixel( aTempRect.TopLeft() );
+ aTempRect.SetLeft( aPt.X() );
+ aTempRect.SetTop( aPt.Y() );
+ aPt = OutputToScreenPixel( aTempRect.BottomRight() );
+ aTempRect.SetRight( aPt.X() );
+ aTempRect.SetBottom( aPt.Y() );
+
+ // get text and display it
+ OUString aStr = GetQuickHelpText( nItemId );
+ if (aStr.isEmpty())
+ aStr = MnemonicGenerator::EraseAllMnemonicChars( GetItemText( nItemId ) );
+ if ( rHEvt.GetMode() & HelpEventMode::BALLOON )
+ {
+ const OUString& rHelpStr = GetHelpText( nItemId );
+ if (!rHelpStr.isEmpty())
+ aStr = rHelpStr;
+ Help::ShowBalloon( this, aHelpPos, aTempRect, aStr );
+ }
+ else
+ Help::ShowQuickHelp( this, aTempRect, aStr, QuickHelpFlags::CtrlText );
+ return;
+ }
+ }
+
+ DockingWindow::RequestHelp( rHEvt );
+}
+
+bool ToolBox::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == MouseNotifyEvent::KEYINPUT )
+ {
+ KeyEvent aKEvt = *rNEvt.GetKeyEvent();
+ vcl::KeyCode aKeyCode = aKEvt.GetKeyCode();
+ sal_uInt16 nKeyCode = aKeyCode.GetCode();
+ switch( nKeyCode )
+ {
+ case KEY_TAB:
+ {
+ // internal TAB cycling only if parent is not a dialog or if we are the only child
+ // otherwise the dialog control will take over
+ vcl::Window *pParent = ImplGetParent();
+ bool bOldSchoolContainer =
+ ((pParent->GetStyle() & (WB_DIALOGCONTROL | WB_NODIALOGCONTROL)) == WB_DIALOGCONTROL &&
+ pParent->GetChildCount() != 1);
+ bool bNoTabCycling = bOldSchoolContainer || isContainerWindow(pParent);
+
+ if( bNoTabCycling )
+ return DockingWindow::EventNotify( rNEvt );
+ else if( ImplChangeHighlightUpDn( aKeyCode.IsShift() , bNoTabCycling ) )
+ return true;
+ else
+ return DockingWindow::EventNotify( rNEvt );
+ }
+ default:
+ break;
+ }
+ }
+ else if( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS )
+ {
+ if( rNEvt.GetWindow() == this )
+ {
+ // the toolbar itself got the focus
+ if( mnLastFocusItemId != 0 || mpData->mbMenubuttonWasLastSelected )
+ {
+ // restore last item
+ if( mpData->mbMenubuttonWasLastSelected )
+ {
+ ImplChangeHighlight( nullptr );
+ mpData->mbMenubuttonSelected = true;
+ InvalidateMenuButton();
+ }
+ else
+ {
+ ImplChangeHighlight( ImplGetItem( mnLastFocusItemId ) );
+ mnLastFocusItemId = 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 = 0;
+
+ return true;
+ }
+ else
+ {
+ // a child window got the focus so update current item to
+ // allow for proper lose focus handling in keyboard navigation
+ for (auto const& item : mpData->m_aItems)
+ {
+ if ( item.mbVisible )
+ {
+ if ( item.mpWindow && item.mpWindow->ImplIsWindowOrChild( rNEvt.GetWindow() ) )
+ {
+ mnHighItemId = item.mnId;
+ break;
+ }
+ }
+ }
+ return DockingWindow::EventNotify( rNEvt );
+ }
+ }
+ else if( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
+ {
+ // deselect
+ ImplHideFocus();
+ mpData->mbMenubuttonWasLastSelected = false;
+ mnHighItemId = 0;
+ mnCurPos = ITEM_NOTFOUND;
+ }
+
+ return DockingWindow::EventNotify( rNEvt );
+}
+
+void ToolBox::Command( const CommandEvent& rCEvt )
+{
+ if ( rCEvt.GetCommand() == CommandEventId::Wheel )
+ {
+ if ( (mnCurLine > 1) || (mnCurLine+mnVisLines-1 < mnCurLines) )
+ {
+ const CommandWheelData* pData = rCEvt.GetWheelData();
+ if ( pData->GetMode() == CommandWheelMode::SCROLL )
+ {
+ if ( (mnCurLine > 1) && (pData->GetDelta() > 0) )
+ ShowLine( false );
+ else if ( (mnCurLine+mnVisLines-1 < mnCurLines) && (pData->GetDelta() < 0) )
+ ShowLine( true );
+ InvalidateSpin();
+ return;
+ }
+ }
+ }
+ else if ( rCEvt.GetCommand() == CommandEventId::ContextMenu )
+ {
+ ExecuteCustomMenu( tools::Rectangle( rCEvt.GetMousePosPixel(), rCEvt.GetMousePosPixel() ) );
+ return;
+ }
+
+ DockingWindow::Command( rCEvt );
+}
+
+void ToolBox::StateChanged( StateChangedType nType )
+{
+ DockingWindow::StateChanged( nType );
+
+ if ( nType == StateChangedType::InitShow )
+ ImplFormat();
+ else if ( nType == StateChangedType::Enable )
+ ImplUpdateItem();
+ else if ( nType == StateChangedType::UpdateMode )
+ {
+ if ( IsUpdateMode() )
+ Invalidate();
+ }
+ else if ( (nType == StateChangedType::Zoom) ||
+ (nType == StateChangedType::ControlFont) )
+ {
+ mbCalc = true;
+ mbFormat = true;
+ ImplInitSettings( true, false, false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ ImplInitSettings( false, true, false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ImplInitSettings( false, false, true ); // font, foreground, background
+ Invalidate();
+ }
+
+ maStateChangedHandler.Call( &nType );
+}
+
+void ToolBox::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ DockingWindow::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::DISPLAY) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
+ {
+ mbCalc = true;
+ mbFormat = true;
+ ImplInitSettings( true, true, true );
+ Invalidate();
+ }
+
+ maDataChangedHandler.Call( &rDCEvt );
+}
+
+void ToolBox::statusChanged( const css::frame::FeatureStateEvent& Event )
+{
+ // Update image mirroring/rotation
+ if ( Event.FeatureURL.Complete == ".uno:ImageOrientation" )
+ {
+ SfxImageItem aItem( 1 );
+ aItem.PutValue( Event.State, 0 );
+
+ mbImagesMirrored = aItem.IsMirrored();
+ mnImagesRotationAngle = aItem.GetRotation();
+
+ // update image orientation
+ OUString aModuleName(vcl::CommandInfoProvider::GetModuleIdentifier(mpStatusListener->getFrame()));
+ for (auto const& item : mpData->m_aItems)
+ {
+ if (vcl::CommandInfoProvider::IsMirrored(item.maCommandStr, aModuleName))
+ SetItemImageMirrorMode(item.mnId, mbImagesMirrored);
+ if (vcl::CommandInfoProvider::IsRotated(item.maCommandStr, aModuleName))
+ SetItemImageAngle(item.mnId, mnImagesRotationAngle);
+ }
+ }
+}
+
+void ToolBox::SetStyle(WinBits nNewStyle)
+{
+ mnWinStyle = nNewStyle;
+ if (!ImplIsFloatingMode())
+ {
+ bool bOldScroll = mbScroll;
+ mbScroll = (mnWinStyle & WB_SCROLL) != 0;
+ if (mbScroll != bOldScroll)
+ {
+ mbFormat = true;
+ ImplFormat();
+ }
+ }
+}
+
+void ToolBox::ToggleFloatingMode()
+{
+ DockingWindow::ToggleFloatingMode();
+
+ if (!mpData)
+ return;
+
+ bool bOldHorz = mbHorz;
+
+ if ( ImplIsFloatingMode() )
+ {
+ mbHorz = true;
+ meAlign = WindowAlign::Top;
+ mbScroll = true;
+
+ if( bOldHorz != mbHorz )
+ mbCalc = true; // orientation was changed !
+
+ ImplSetMinMaxFloatSize();
+ SetOutputSizePixel( ImplCalcFloatSize( mnFloatLines ) );
+ }
+ else
+ {
+ mbScroll = (mnWinStyle & WB_SCROLL) != 0;
+ if ( (meAlign == WindowAlign::Top) || (meAlign == WindowAlign::Bottom) )
+ mbHorz = true;
+ else
+ mbHorz = false;
+
+ // set focus back to document
+ ImplGetFrameWindow()->GetWindow( GetWindowType::Client )->GrabFocus();
+ }
+
+ if( bOldHorz != mbHorz )
+ {
+ // if orientation changes, the toolbox has to be initialized again
+ // to update the direction of the gradient
+ mbCalc = true;
+ ImplInitSettings( true, true, true );
+ }
+
+ mbFormat = true;
+ ImplFormat();
+}
+
+void ToolBox::StartDocking()
+{
+ meDockAlign = meAlign;
+ mnDockLines = mnLines;
+ mbLastFloatMode = ImplIsFloatingMode();
+ DockingWindow::StartDocking();
+}
+
+bool ToolBox::Docking( const Point& rPos, tools::Rectangle& rRect )
+{
+ // do nothing during dragging, it was calculated before
+ if ( mbDragging )
+ return false;
+
+ bool bFloatMode = false;
+
+ DockingWindow::Docking( rPos, rRect );
+
+ // if the mouse is outside the area, it can only become a floating window
+ tools::Rectangle aDockingRect( rRect );
+ if ( !ImplIsFloatingMode() )
+ {
+ // don't use tracking rectangle for alignment check, because it will be too large
+ // to get a floating mode as result - switch to floating size
+ // so the calculation only depends on the position of the rectangle, not the current
+ // docking state of the window
+ ImplToolItems::size_type nTemp = 0;
+ aDockingRect.SetSize( ImplCalcFloatSize( nTemp ) );
+
+ // in this mode docking is never done by keyboard, so it's OK to use the mouse position
+ aDockingRect.SetPos( ImplGetFrameWindow()->GetPointerPosPixel() );
+ }
+
+ bFloatMode = true;
+
+ meDockAlign = meAlign;
+ if ( !mbLastFloatMode )
+ {
+ ImplToolItems::size_type nTemp = 0;
+ aDockingRect.SetSize( ImplCalcFloatSize( nTemp ) );
+ }
+
+ rRect = aDockingRect;
+ mbLastFloatMode = bFloatMode;
+
+ return bFloatMode;
+}
+
+void ToolBox::EndDocking( const tools::Rectangle& rRect, bool bFloatMode )
+{
+ if ( !IsDockingCanceled() )
+ {
+ if ( mnLines != mnDockLines )
+ SetLineCount( mnDockLines );
+ if ( meAlign != meDockAlign )
+ SetAlign( meDockAlign );
+ }
+ if ( bFloatMode || (bFloatMode != ImplIsFloatingMode()) )
+ DockingWindow::EndDocking( rRect, bFloatMode );
+}
+
+void ToolBox::Resizing( Size& rSize )
+{
+ ImplToolItems::size_type nCalcLines;
+ ImplToolItems::size_type nTemp;
+
+ // calculate all floating sizes
+ ImplCalcFloatSizes();
+
+ if ( !mnLastResizeDY )
+ mnLastResizeDY = mnDY;
+
+ // is vertical resizing needed
+ if ( (mnLastResizeDY != rSize.Height()) && (mnDY != rSize.Height()) )
+ {
+ nCalcLines = ImplCalcLines( rSize.Height() );
+ if ( nCalcLines < 1 )
+ nCalcLines = 1;
+ rSize = ImplCalcFloatSize( nCalcLines );
+ }
+ else
+ {
+ nCalcLines = 1;
+ nTemp = nCalcLines;
+ Size aTempSize = ImplCalcFloatSize( nTemp );
+ while ( (aTempSize.Width() > rSize.Width()) &&
+ (nCalcLines <= maFloatSizes[0].mnLines) )
+ {
+ nCalcLines++;
+ nTemp = nCalcLines;
+ aTempSize = ImplCalcFloatSize( nTemp );
+ }
+ rSize = aTempSize;
+ }
+
+ mnLastResizeDY = rSize.Height();
+}
+
+Size ToolBox::GetOptimalSize() const
+{
+ // If we have any expandable entries, then force them to their
+ // optimal sizes, then reset them afterwards
+ std::map<vcl::Window*, Size> aExpandables;
+ for (const ImplToolItem & rItem : mpData->m_aItems)
+ {
+ if (rItem.mbExpand)
+ {
+ vcl::Window *pWindow = rItem.mpWindow;
+ SAL_INFO_IF(!pWindow, "vcl.layout", "only tabitems with window supported at the moment");
+ if (!pWindow)
+ continue;
+ Size aWinSize(pWindow->GetSizePixel());
+ aExpandables[pWindow] = aWinSize;
+ Size aPrefSize(pWindow->get_preferred_size());
+ aWinSize.setWidth( aPrefSize.Width() );
+ pWindow->SetSizePixel(aWinSize);
+ }
+ }
+
+ Size aSize(const_cast<ToolBox *>(this)->ImplCalcSize( mnLines ));
+
+ for (auto const& expandable : aExpandables)
+ {
+ vcl::Window *pWindow = expandable.first;
+ Size aWinSize = expandable.second;
+ 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( sal_uInt16 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 = 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 = 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::ImplDisableFlatButtons()
+{
+#ifdef _WIN32 // Check in the Windows registry if an AT tool wants no flat toolboxes
+ static bool bInit = false, bValue = false;
+ if( ! bInit )
+ {
+ bInit = true;
+ HKEY hkey;
+
+ if( ERROR_SUCCESS == RegOpenKeyW(HKEY_CURRENT_USER, L"Software\\LibreOffice\\Accessibility\\AtToolSupport", &hkey) )
+ {
+ DWORD dwType = 0;
+ wchar_t Data[6]; // possible values: "true", "false", "1", "0", DWORD
+ DWORD cbData = sizeof(Data);
+
+ if( ERROR_SUCCESS == RegQueryValueExW(hkey, L"DisableFlatToolboxButtons",
+ nullptr, &dwType, reinterpret_cast<LPBYTE>(Data), &cbData) )
+ {
+ switch (dwType)
+ {
+ case REG_SZ:
+ bValue = ((0 == wcsicmp(Data, L"1")) || (0 == wcsicmp(Data, L"true")));
+ break;
+ case REG_DWORD:
+ bValue = static_cast<bool>(reinterpret_cast<DWORD *>(Data)[0]);
+ break;
+ }
+ }
+ RegCloseKey(hkey);
+ }
+ }
+ if( bValue )
+ mnOutStyle &= ~TOOLBOX_STYLE_FLAT;
+#else
+ (void) this; // loplugin:staticmethods
+#endif
+}
+
+void ToolBox::SetToolbarLayoutMode( ToolBoxLayoutMode eLayout )
+{
+ if ( meLayoutMode != eLayout )
+ meLayoutMode = eLayout;
+}
+
+void ToolBox::SetToolBoxTextPosition( ToolBoxTextPosition ePosition )
+{
+ meTextPosition = ePosition;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/toolbox2.cxx b/vcl/source/window/toolbox2.cxx
new file mode 100644
index 000000000..3d1bcd7d1
--- /dev/null
+++ b/vcl/source/window/toolbox2.cxx
@@ -0,0 +1,1778 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/lok.hxx>
+#include <comphelper/processfactory.hxx>
+#include <boost/property_tree/ptree.hpp>
+
+#include <vcl/svapp.hxx>
+#include <vcl/idle.hxx>
+#include <vcl/bitmap.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>
+
+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(sal_uInt16 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;
+ mbMirrorMode = false;
+ mbVisibleText = false;
+ mbExpand = false;
+}
+
+ImplToolItem::ImplToolItem()
+{
+ init(0, ToolBoxItemBits::NONE, true);
+}
+
+ImplToolItem::ImplToolItem( sal_uInt16 nItemId, const Image& rImage,
+ ToolBoxItemBits nItemBits ) :
+ maImage( rImage )
+{
+ init(nItemId, nItemBits, false);
+}
+
+ImplToolItem::ImplToolItem( sal_uInt16 nItemId, const OUString& rText,
+ ToolBoxItemBits nItemBits ) :
+ maText( rText )
+{
+ init(nItemId, nItemBits, false);
+}
+
+ImplToolItem::ImplToolItem( sal_uInt16 nItemId, const Image& rImage,
+ const OUString& rText, ToolBoxItemBits nItemBits ) :
+ maImage( rImage ),
+ maText( rText )
+{
+ init(nItemId, nItemBits, false);
+}
+
+Size ImplToolItem::GetSize( bool bHorz, bool bCheckMaxWidth, 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() )
+ {
+ 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 );
+}
+
+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 );
+}
+
+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() && pWrapper->GetFloatingWindow()->IsInPopupMode() )
+ pWrapper->GetFloatingWindow()->EndPopupMode();
+}
+
+void ToolBox::InsertItem( sal_uInt16 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( sal_uInt16 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( sal_uInt16 nItemId, const OUString& rText, ToolBoxItemBits nBits, ImplToolItems::size_type nPos )
+{
+ SAL_WARN_IF( !nItemId, "vcl", "ToolBox::InsertItem(): ItemId == 0" );
+ SAL_WARN_IF( GetItemPos( nItemId ) != ITEM_NOTFOUND, "vcl",
+ "ToolBox::InsertItem(): ItemId already exists" );
+
+ // create item and add to list
+ mpData->m_aItems.insert( (nPos < mpData->m_aItems.size()) ? mpData->m_aItems.begin()+nPos : mpData->m_aItems.end(),
+ ImplToolItem( nItemId, MnemonicGenerator::EraseAllMnemonicChars(rText), nBits ) );
+ mpData->ImplClearLayoutData();
+
+ ImplInvalidate( true );
+
+ // Notify
+ ImplToolItems::size_type nNewPos = ( nPos == APPEND ) ? ( mpData->m_aItems.size() - 1 ) : nPos;
+ CallEventListeners( VclEventId::ToolboxItemAdded, reinterpret_cast< void* >( nNewPos ) );
+}
+
+void ToolBox::InsertItem(const OUString& rCommand, const css::uno::Reference<css::frame::XFrame>& rFrame, ToolBoxItemBits nBits,
+ const Size& rRequestedSize, ImplToolItems::size_type nPos)
+{
+ OUString aModuleName(vcl::CommandInfoProvider::GetModuleIdentifier(rFrame));
+ auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(rCommand, aModuleName);
+ OUString aLabel(vcl::CommandInfoProvider::GetLabelForCommand(aProperties));
+ OUString aTooltip(vcl::CommandInfoProvider::GetTooltipForCommand(rCommand, aProperties, rFrame));
+
+#ifdef ANDROID
+ Image aImage; // Loading redundant icons for sidebars shows in profiles.
+#else
+ Image aImage(CommandInfoProvider::GetImageForCommand(rCommand, rFrame, GetImageSize()));
+#endif
+
+ sal_uInt16 nItemId = GetItemCount() + 1;
+ //TODO: ImplToolItems::size_type -> sal_uInt16!
+ InsertItem(nItemId, aImage, aLabel, nBits, nPos);
+ SetItemCommand(nItemId, rCommand);
+ SetQuickHelpText(nItemId, aTooltip);
+
+ // set the minimal size
+ ImplToolItem* pItem = ImplGetItem( nItemId );
+ if ( pItem )
+ pItem->maMinimalItemSize = rRequestedSize;
+}
+
+void ToolBox::InsertWindow( sal_uInt16 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() )
+ {
+ 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 = 0;
+ if ( mpData->m_aItems[nPos].mnId == mnHighItemId )
+ mnHighItemId = 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, sal_uInt16 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 )
+ {
+ // 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 = 0;
+ mnHighItemId = 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 )
+ {
+ meAlign = eNewAlign;
+
+ if ( !ImplIsFloatingMode() )
+ {
+ // 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( sal_uInt16 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.IsInside( rPos ); });
+
+ if( it != mpData->m_aItems.end() )
+ return std::distance(mpData->m_aItems.begin(), it);
+
+ return ITEM_NOTFOUND;
+}
+
+sal_uInt16 ToolBox::GetItemId( ImplToolItems::size_type nPos ) const
+{
+ return (nPos < mpData->m_aItems.size()) ? mpData->m_aItems[nPos].mnId : 0;
+}
+
+sal_uInt16 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.IsInside( rPos ); });
+
+ if( (it != mpData->m_aItems.end()) && (it->meType == ToolBoxItemType::BUTTON) )
+ return it->mnId;
+
+ return 0;
+}
+
+Size ToolBox::GetItemContentSize( sal_uInt16 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();
+}
+
+sal_uInt16 ToolBox::GetItemId(const OUString &rCommand) const
+{
+ if (!mpData)
+ return 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 0;
+}
+
+Point ToolBox::ImplGetPopupPosition( const tools::Rectangle& rRect ) const
+{
+ Point aPos;
+ if( !rRect.IsEmpty() )
+ {
+ tools::Rectangle aScreen = GetDesktopRectPixel();
+
+ // the popup should be positioned so that it will not cover
+ // the item rect and that it fits the desktop
+ // the preferred direction is always towards the center of
+ // the application window
+
+ Point devPos; // the position in device coordinates for screen comparison
+ switch( meAlign )
+ {
+ case WindowAlign::Top:
+ aPos = rRect.BottomLeft();
+ aPos.AdjustY( 1 );
+ devPos = OutputToAbsoluteScreenPixel( aPos );
+ if( devPos.Y() >= aScreen.Bottom() )
+ aPos.setY( rRect.Top() );
+ break;
+ case WindowAlign::Bottom:
+ aPos = rRect.TopLeft();
+ aPos.AdjustY( -1 );
+ devPos = OutputToAbsoluteScreenPixel( aPos );
+ if( devPos.Y() <= aScreen.Top() )
+ aPos.setY( rRect.Bottom() );
+ break;
+ case WindowAlign::Left:
+ aPos = rRect.TopRight();
+ aPos.AdjustX( 1 );
+ devPos = OutputToAbsoluteScreenPixel( aPos );
+ if( devPos.X() >= aScreen.Right() )
+ aPos.setX( rRect.Left() );
+ break;
+ case WindowAlign::Right:
+ aPos = rRect.TopLeft();
+ aPos.AdjustX( -1 );
+ devPos = OutputToAbsoluteScreenPixel( aPos );
+ if( devPos.X() <= aScreen.Left() )
+ aPos.setX( rRect.Right() );
+ break;
+ default:
+ break;
+ }
+ }
+ return aPos;
+}
+
+tools::Rectangle ToolBox::GetItemRect( sal_uInt16 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()
+{
+ // 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( sal_uInt16 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(sal_uInt16 nItemId, bool bNonInteractive)
+{
+ ImplToolItems::size_type nPos = GetItemPos( nItemId );
+
+ if ( nPos < GetItemCount() )
+ {
+ mpData->m_aItems[nPos].mbNonInteractiveWindow = bNonInteractive;
+ }
+}
+
+ToolBoxItemBits ToolBox::GetItemBits( sal_uInt16 nItemId ) const
+{
+ ImplToolItem* pItem = ImplGetItem( nItemId );
+
+ if ( pItem )
+ return pItem->mnBits;
+ else
+ return ToolBoxItemBits::NONE;
+}
+
+void ToolBox::SetItemExpand( sal_uInt16 nItemId, bool bExpand )
+{
+ ImplToolItem* pItem = ImplGetItem( nItemId );
+ if (!pItem)
+ return;
+
+ if (pItem->mbExpand != bExpand)
+ {
+ pItem->mbExpand = bExpand;
+ ImplInvalidate(true, true);
+ }
+}
+
+void ToolBox::SetItemData( sal_uInt16 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( sal_uInt16 nItemId ) const
+{
+ ImplToolItem* pItem = ImplGetItem( nItemId );
+
+ if ( pItem )
+ return pItem->mpUserData;
+ else
+ return nullptr;
+}
+
+void ToolBox::SetItemImage( sal_uInt16 nItemId, const Image& rImage )
+{
+ ImplToolItems::size_type nPos = GetItemPos( nItemId );
+
+ if ( nPos != ITEM_NOTFOUND )
+ {
+ ImplToolItem* pItem = &mpData->m_aItems[nPos];
+ Size aOldSize = pItem->maImage.GetSizePixel();
+
+ pItem->maImage = rImage;
+
+ // only once all is calculated, do extra work
+ if (!mbCalc)
+ {
+ if (aOldSize != pItem->maImage.GetSizePixel())
+ ImplInvalidate( true );
+ else
+ ImplUpdateItem( nPos );
+ }
+ }
+}
+
+static Image ImplRotImage( const Image& rImage, long nAngle10 )
+{
+ BitmapEx aRotBitmapEx( rImage.GetBitmapEx() );
+
+ aRotBitmapEx.Rotate( nAngle10, COL_WHITE );
+
+ return Image( aRotBitmapEx );
+}
+
+void ToolBox::SetItemImageAngle( sal_uInt16 nItemId, long nAngle10 )
+{
+ ImplToolItems::size_type nPos = GetItemPos( nItemId );
+
+ if ( nPos != ITEM_NOTFOUND )
+ {
+ ImplToolItem* pItem = &mpData->m_aItems[nPos];
+ Size aOldSize = pItem->maImage.GetSizePixel();
+
+ long nDeltaAngle = (nAngle10 - pItem->mnImageAngle) % 3600;
+ while( nDeltaAngle < 0 )
+ nDeltaAngle += 3600;
+
+ pItem->mnImageAngle = nAngle10;
+ if( nDeltaAngle && !!pItem->maImage )
+ {
+ pItem->maImage = ImplRotImage( pItem->maImage, nDeltaAngle );
+ }
+
+ if (!mbCalc)
+ {
+ if (aOldSize != pItem->maImage.GetSizePixel())
+ ImplInvalidate(true);
+ else
+ ImplUpdateItem(nPos);
+ }
+ }
+}
+
+static Image ImplMirrorImage( const Image& rImage )
+{
+ BitmapEx aMirrBitmapEx( rImage.GetBitmapEx() );
+
+ aMirrBitmapEx.Mirror( BmpMirrorFlags::Horizontal );
+
+ return Image( aMirrBitmapEx );
+}
+
+void ToolBox::SetItemImageMirrorMode( sal_uInt16 nItemId, bool bMirror )
+{
+ ImplToolItems::size_type nPos = GetItemPos( nItemId );
+
+ if ( nPos != ITEM_NOTFOUND )
+ {
+ ImplToolItem* pItem = &mpData->m_aItems[nPos];
+
+ if (pItem->mbMirrorMode != bMirror)
+ {
+ pItem->mbMirrorMode = bMirror;
+ if (!!pItem->maImage)
+ {
+ pItem->maImage = ImplMirrorImage(pItem->maImage);
+ }
+
+ if (!mbCalc)
+ ImplUpdateItem(nPos);
+ }
+ }
+}
+
+Image ToolBox::GetItemImage(sal_uInt16 nItemId) const
+{
+ ImplToolItem* pItem = ImplGetItem(nItemId);
+ return pItem ? pItem->maImage : Image();
+}
+
+void ToolBox::SetItemText( sal_uInt16 nItemId, const OUString& rText )
+{
+ ImplToolItems::size_type nPos = GetItemPos( nItemId );
+
+ if ( nPos != ITEM_NOTFOUND )
+ {
+ ImplToolItem* pItem = &mpData->m_aItems[nPos];
+ // only once all is calculated, do extra work
+ if ( !mbCalc &&
+ ((meButtonType != ButtonType::SYMBOLONLY) || !pItem->maImage) )
+ {
+ long nOldWidth = GetCtrlTextWidth( pItem->maText );
+ pItem->maText = MnemonicGenerator::EraseAllMnemonicChars(rText);
+ mpData->ImplClearLayoutData();
+ if ( nOldWidth != 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( sal_uInt16 nItemId ) const
+{
+
+ ImplToolItem* pItem = ImplGetItem( nItemId );
+
+ assert( pItem );
+
+ return pItem->maText;
+}
+
+void ToolBox::SetItemWindow( sal_uInt16 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( sal_uInt16 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 = 0;
+ mnDownItemId = 0;
+ mnMouseModifier = 0;
+}
+
+void ToolBox::SetItemDown( sal_uInt16 nItemId, bool bDown )
+{
+ ImplToolItems::size_type nPos = GetItemPos( nItemId );
+
+ if ( nPos != ITEM_NOTFOUND )
+ {
+ if ( bDown )
+ {
+ if ( nPos != mnCurPos )
+ {
+ mnCurPos = nPos;
+ InvalidateItem(mnCurPos);
+ Flush();
+ }
+ }
+ else
+ {
+ if ( nPos == mnCurPos )
+ {
+ InvalidateItem(mnCurPos);
+ Flush();
+ mnCurPos = ITEM_NOTFOUND;
+ }
+ }
+
+ if ( mbDrag )
+ {
+ mbDrag = false;
+ EndTracking();
+ if (IsMouseCaptured())
+ ReleaseMouse();
+ Deactivate();
+ }
+
+ mnCurItemId = 0;
+ mnDownItemId = 0;
+ mnMouseModifier = 0;
+ }
+}
+
+void ToolBox::SetItemState( sal_uInt16 nItemId, TriState eState )
+{
+ ImplToolItems::size_type nPos = GetItemPos( nItemId );
+
+ if ( nPos != ITEM_NOTFOUND )
+ {
+ ImplToolItem* pItem = &mpData->m_aItems[nPos];
+
+ // the state has changed
+ if ( pItem->meState != eState )
+ {
+ // 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( sal_uInt16 nItemId ) const
+{
+ ImplToolItem* pItem = ImplGetItem( nItemId );
+
+ if ( pItem )
+ return pItem->meState;
+ else
+ return TRISTATE_FALSE;
+}
+
+void ToolBox::EnableItem( sal_uInt16 nItemId, bool bEnable )
+{
+ ImplToolItems::size_type nPos = GetItemPos( nItemId );
+
+ if ( nPos != ITEM_NOTFOUND )
+ {
+ ImplToolItem* pItem = &mpData->m_aItems[nPos];
+ if ( pItem->mbEnabled != bEnable )
+ {
+ 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( sal_uInt16 nItemId ) const
+{
+ ImplToolItem* pItem = ImplGetItem( nItemId );
+
+ if ( pItem )
+ return pItem->mbEnabled;
+ else
+ return false;
+}
+
+void ToolBox::ShowItem( sal_uInt16 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( sal_uInt16 nItemId ) const
+{
+ ImplToolItem* pItem = ImplGetItem( nItemId );
+
+ if ( pItem )
+ return pItem->IsClipped();
+ else
+ return false;
+}
+
+bool ToolBox::IsItemVisible( sal_uInt16 nItemId ) const
+{
+ ImplToolItem* pItem = ImplGetItem( nItemId );
+
+ if ( pItem )
+ return pItem->mbVisible;
+ else
+ return false;
+}
+
+bool ToolBox::IsItemReallyVisible( sal_uInt16 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.IsOver( pItem->maRect ) )
+ {
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+void ToolBox::SetItemCommand(sal_uInt16 nItemId, const OUString& rCommand)
+{
+ ImplToolItem* pItem = ImplGetItem( nItemId );
+
+ if (pItem)
+ pItem->maCommandStr = rCommand;
+}
+
+OUString ToolBox::GetItemCommand( sal_uInt16 nItemId ) const
+{
+ ImplToolItem* pItem = ImplGetItem( nItemId );
+
+ if (pItem)
+ return pItem->maCommandStr;
+
+ return OUString();
+}
+
+void ToolBox::SetQuickHelpText( sal_uInt16 nItemId, const OUString& rText )
+{
+ ImplToolItem* pItem = ImplGetItem( nItemId );
+
+ if ( pItem )
+ pItem->maQuickHelpText = rText;
+}
+
+OUString ToolBox::GetQuickHelpText( sal_uInt16 nItemId ) const
+{
+ ImplToolItem* pItem = ImplGetItem( nItemId );
+
+ if ( pItem )
+ return pItem->maQuickHelpText;
+ else
+ return OUString();
+}
+
+void ToolBox::SetHelpText( sal_uInt16 nItemId, const OUString& rText )
+{
+ ImplToolItem* pItem = ImplGetItem( nItemId );
+
+ if ( pItem )
+ pItem->maHelpText = rText;
+}
+
+const OUString& ToolBox::GetHelpText( sal_uInt16 nItemId ) const
+{
+ return ImplGetHelpText( nItemId );
+}
+
+void ToolBox::SetHelpId( sal_uInt16 nItemId, const OString& rHelpId )
+{
+ ImplToolItem* pItem = ImplGetItem( nItemId );
+
+ if ( pItem )
+ pItem->maHelpId = rHelpId;
+}
+
+void ToolBox::SetOutStyle( sal_uInt16 nNewStyle )
+{
+ // always force flat looking toolbars since NWF
+ nNewStyle |= TOOLBOX_STYLE_FLAT;
+
+ if ( mnOutStyle != nNewStyle )
+ {
+ mnOutStyle = nNewStyle;
+ ImplDisableFlatButtons();
+
+ // so as to redo the ButtonDevice
+ if ( !(mnOutStyle & TOOLBOX_STYLE_FLAT) )
+ {
+ mnMaxItemWidth = 1;
+ mnMaxItemHeight = 1;
+ }
+
+ ImplInvalidate( true, true );
+ }
+}
+
+// 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.reset(new ToolBoxLayoutData);
+
+ 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( sal_uInt16 nItemID, long nIndex )
+{
+ long nItemIndex = -1;
+ if( ! mpData->m_pLayoutData )
+ ImplFillLayoutData();
+ if( mpData->m_pLayoutData )
+ {
+ for( sal_uLong 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();
+}
+
+long ToolBox::GetIndexForPoint( const Point& rPoint, sal_uInt16& rItemID )
+{
+ long nIndex = -1;
+ rItemID = 0;
+ if( ! mpData->m_pLayoutData )
+ ImplFillLayoutData();
+ if( mpData->m_pLayoutData )
+ {
+ nIndex = mpData->m_pLayoutData->GetIndexForPoint( rPoint );
+ for( sal_uLong 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 )
+ {
+ 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->ShowTitleButton( TitleButton::Menu, 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() )
+ {
+ // 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 = 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 = 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( 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() && 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;
+}
+
+boost::property_tree::ptree ToolBox::DumpAsPropertyTree()
+{
+ boost::property_tree::ptree aTree(DockingWindow::DumpAsPropertyTree());
+ boost::property_tree::ptree aChildren;
+
+ boost::property_tree::ptree::const_assoc_iterator found = aTree.find("children");
+ if (found == aTree.not_found())
+ {
+ for (ToolBox::ImplToolItems::size_type i = 0; i < GetItemCount(); ++i)
+ {
+ ToolBoxItemType type = GetItemType(i);
+ if (type == ToolBoxItemType::BUTTON)
+ {
+ boost::property_tree::ptree aEntry;
+ int nId = GetItemId(i);
+ if (!IsItemVisible(nId))
+ continue;
+ aEntry.put("type", "toolitem");
+ aEntry.put("text", GetItemText(nId));
+ aEntry.put("command", GetItemCommand(nId));
+ aChildren.push_back(std::make_pair("", aEntry));
+ }
+ }
+
+ aTree.add_child("children", aChildren);
+ }
+
+ return aTree;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/window.cxx b/vcl/source/window/window.cxx
new file mode 100644
index 000000000..82ce1465b
--- /dev/null
+++ b/vcl/source/window/window.cxx
@@ -0,0 +1,3926 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/strbuf.hxx>
+#include <sal/log.hxx>
+
+#include <sal/types.h>
+#include <vcl/salgtype.hxx>
+#include <vcl/event.hxx>
+#include <vcl/help.hxx>
+#include <vcl/cursor.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/transfer.hxx>
+#include <vcl/vclevent.hxx>
+#include <vcl/window.hxx>
+#include <vcl/syswin.hxx>
+#include <vcl/dockwin.hxx>
+#include <vcl/wall.hxx>
+#include <vcl/fixed.hxx>
+#include <vcl/taskpanelist.hxx>
+#include <vcl/toolkit/unowrap.hxx>
+#include <vcl/lazydelete.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/sysdata.hxx>
+#include <vcl/ptrstyle.hxx>
+#include <vcl/IDialogRenderable.hxx>
+
+#include <vcl/uitest/uiobject.hxx>
+
+#include <salframe.hxx>
+#include <salobj.hxx>
+#include <salinst.hxx>
+#include <salgdi.hxx>
+#include <svdata.hxx>
+#include <window.h>
+#include <toolbox.h>
+#include <outdev.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/awt/XWindowPeer.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboard.hpp>
+#include <com/sun/star/datatransfer/dnd/XDragGestureRecognizer.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp>
+#include <com/sun/star/rendering/CanvasFactory.hpp>
+#include <com/sun/star/rendering/XSpriteCanvas.hpp>
+#include <comphelper/lok.hxx>
+#include <comphelper/processfactory.hxx>
+#include <unotools/configmgr.hxx>
+#include <osl/diagnose.h>
+#include <tools/debug.hxx>
+#include <boost/property_tree/ptree.hpp>
+
+#include <cassert>
+#include <typeinfo>
+
+#ifdef _WIN32 // see #140456#
+#include <win/salframe.h>
+#endif
+
+
+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 )
+ : OutputDevice(OUTDEV_WINDOW)
+ , mpWindowImpl(new WindowImpl( nType ))
+{
+ // true: this outdev will be mirrored if RTL window layout (UI mirroring) is globally active
+ mbEnableRTL = AllSettings::GetLayoutRTL();
+}
+
+Window::Window( vcl::Window* pParent, WinBits nStyle )
+ : OutputDevice(OUTDEV_WINDOW)
+ , mpWindowImpl(new WindowImpl( WindowType::WINDOW ))
+{
+ // true: this outdev will be mirrored if RTL window layout (UI mirroring) is globally active
+ 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;
+ }
+
+ OStringBuffer aErrorString;
+ aErrorString.append(' ');
+ aErrorString.append(typeid( *pWindow ).name());
+ aErrorString.append("(");
+ aErrorString.append(
+ OUStringToOString(
+ pWindow->GetText(),
+ RTL_TEXTENCODING_UTF8
+ )
+ );
+ aErrorString.append(")");
+ return aErrorString.makeStringAndClear();
+ }
+}
+#endif
+
+bool Window::IsDisposed() const
+{
+ return !mpWindowImpl;
+}
+
+void Window::dispose()
+{
+ assert( mpWindowImpl );
+ assert( !mpWindowImpl->mbInDispose ); // should only be called from disposeOnce()
+ assert( (!mpWindowImpl->mpParent ||
+ !mpWindowImpl->mpParent->IsDisposed()) &&
+ "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.
+ Reference< css::rendering::XCanvas > xCanvas( mpWindowImpl->mxCanvas );
+ if( xCanvas.is() )
+ {
+ Reference < XComponent > xCanvasComponent( xCanvas, UNO_QUERY );
+ if( xCanvasComponent.is() )
+ xCanvasComponent->dispose();
+ }
+
+ mpWindowImpl->mbInDispose = true;
+
+ CallEventListeners( VclEventId::ObjectDying );
+
+ // do not send child events for frames that were registered as native frames
+ if( !ImplIsAccessibleNativeFrame() && mpWindowImpl->mbReallyVisible )
+ if ( ImplIsAccessibleCandidate() && GetAccessibleParentWindow() )
+ GetAccessibleParentWindow()->CallEventListeners( VclEventId::WindowChildDestroyed, this );
+
+ // remove associated data structures from dockingmanager
+ ImplGetDockingManager()->RemoveWindow( this );
+
+ // remove ownerdraw decorated windows from list in the top-most frame window
+ if( (GetStyle() & WB_OWNERDRAWDECORATION) && mpWindowImpl->mbFrame )
+ {
+ ::std::vector< VclPtr<vcl::Window> >& rList = ImplGetOwnerDrawList();
+ auto p = ::std::find( rList.begin(), rList.end(), VclPtr<vcl::Window>(this) );
+ if( p != rList.end() )
+ rList.erase( p );
+ }
+
+ // shutdown drag and drop
+ Reference < XComponent > xDnDComponent( mpWindowImpl->mxDNDListenerContainer, UNO_QUERY );
+
+ if( xDnDComponent.is() )
+ xDnDComponent->dispose();
+
+ if( mpWindowImpl->mbFrame && mpWindowImpl->mpFrameData )
+ {
+ try
+ {
+ // deregister drop target listener
+ if( mpWindowImpl->mpFrameData->mxDropTargetListener.is() )
+ {
+ Reference< XDragGestureRecognizer > xDragGestureRecognizer(mpWindowImpl->mpFrameData->mxDragSource, UNO_QUERY);
+ if( xDragGestureRecognizer.is() )
+ {
+ xDragGestureRecognizer->removeDragGestureListener(
+ Reference< XDragGestureListener > (mpWindowImpl->mpFrameData->mxDropTargetListener, UNO_QUERY));
+ }
+
+ mpWindowImpl->mpFrameData->mxDropTarget->removeDropTargetListener( mpWindowImpl->mpFrameData->mxDropTargetListener );
+ mpWindowImpl->mpFrameData->mxDropTargetListener.clear();
+ }
+
+ // shutdown drag and drop for this frame window
+ Reference< XComponent > xComponent( mpWindowImpl->mpFrameData->mxDropTarget, UNO_QUERY );
+
+ // DNDEventDispatcher does not hold a reference of the DropTarget,
+ // so it's ok if it does not support XComponent
+ if( xComponent.is() )
+ xComponent->dispose();
+ }
+ catch (const Exception&)
+ {
+ // can be safely ignored here.
+ }
+ }
+
+ UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper( false );
+ if ( pWrapper )
+ pWrapper->WindowDestroyed( this );
+
+ // MT: Must be called after WindowDestroyed!
+ // Otherwise, if the accessible is a VCLXWindow, it will try to destroy this window again!
+ // But accessibility implementations from applications need this dispose.
+ if ( mpWindowImpl->mxAccessible.is() )
+ {
+ Reference< XComponent> xC( mpWindowImpl->mxAccessible, UNO_QUERY );
+ if ( xC.is() )
+ xC->dispose();
+ }
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ if ( ImplGetSVHelpData().mpHelpWin && (ImplGetSVHelpData().mpHelpWin->GetParent() == this) )
+ ImplDestroyHelpWindow( true );
+
+ SAL_WARN_IF(pSVData->mpWinData->mpTrackWin.get() == this, "vcl.window",
+ "Window::~Window(): Window is in TrackingMode");
+ SAL_WARN_IF(IsMouseCaptured(), "vcl.window",
+ "Window::~Window(): Window has the mouse captured");
+
+ // due to old compatibility
+ if (pSVData->mpWinData->mpTrackWin == this)
+ EndTracking();
+ if (IsMouseCaptured())
+ ReleaseMouse();
+
+#if OSL_DEBUG_LEVEL > 0
+ if ( true ) // always perform these tests in debug builds
+ {
+ OStringBuffer aErrorStr;
+ bool bError = false;
+ vcl::Window* pTempWin;
+
+ if ( mpWindowImpl->mpFirstChild )
+ {
+ OStringBuffer aTempStr("Window (");
+ aTempStr.append(lcl_createWindowInfo(this));
+ aTempStr.append(") 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.makeStringAndClear(), RTL_TEXTENCODING_UTF8)); // abort in debug builds, this must be fixed!
+ }
+
+ 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 )
+ {
+ OStringBuffer aTempStr;
+ aTempStr.append("Window (");
+ aTempStr.append(lcl_createWindowInfo(this));
+ aTempStr.append(") with live SystemWindows destroyed: ");
+ aTempStr.append(aErrorStr.toString());
+ OSL_FAIL(aTempStr.getStr());
+ // abort in debug builds, must be fixed!
+ Application::Abort(OStringToOUString(
+ aTempStr.makeStringAndClear(), 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 )
+ {
+ OStringBuffer aTempStr( "Window (" );
+ aTempStr.append(lcl_createWindowInfo(this));
+ aTempStr.append(") with live SystemWindows destroyed: ");
+ aTempStr.append(aErrorStr.toString());
+ OSL_FAIL( aTempStr.getStr() );
+ Application::Abort(OStringToOUString(aTempStr.makeStringAndClear(), RTL_TEXTENCODING_UTF8)); // abort in debug builds, this must be fixed!
+ }
+
+ if ( mpWindowImpl->mpFirstOverlap )
+ {
+ OStringBuffer aTempStr("Window (");
+ aTempStr.append(lcl_createWindowInfo(this));
+ aTempStr.append(") 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.makeStringAndClear(), RTL_TEXTENCODING_UTF8)); // abort in debug builds, this must be fixed!
+ }
+
+ 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 ) )
+ {
+ OStringBuffer aTempStr("Window (");
+ aTempStr.append(lcl_createWindowInfo(this));
+ aTempStr.append(") still in TaskPanelList!");
+ OSL_FAIL( aTempStr.getStr() );
+ Application::Abort(OStringToOUString(aTempStr.makeStringAndClear(), RTL_TEXTENCODING_UTF8)); // abort in debug builds, this must be fixed!
+ }
+ }
+#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 bHasFocussedChild = 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
+ bHasFocussedChild = 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); // abort in debug build version, this must be fixed!
+#endif
+ }
+
+ // if we get focus pass focus to another window
+ vcl::Window* pOverlapWindow = ImplGetFirstOverlapWindow();
+ if (pSVData->mpWinData->mpFocusWin == this
+ || bHasFocussedChild) // #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
+ 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 );
+ }
+ }
+
+ delete mpWindowImpl->mpWinData;
+ mpWindowImpl->mpWinData = nullptr;
+
+ // 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;
+ }
+
+ // should be the last statements
+ mpWindowImpl.reset();
+
+ OutputDevice::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 this;
+}
+
+::OutputDevice* Window::GetOutDev()
+{
+ return this;
+}
+
+Color Window::GetBackgroundColor() const
+{
+ return GetDisplayBackground().GetColor();
+}
+
+} /* namespace vcl */
+
+WindowImpl::WindowImpl( WindowType nType )
+{
+ maZoom = Fraction( 1, 1 );
+ 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; // neither AlwaysEnableInput nor AlwaysDisableInput 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
+ mbCallHandlersDuringInputDisabled = false; // true: call event handlers even if input is disabled
+ mbHelpTextDynamic = false; // true: append help id in HELP_DEBUG case
+ mbFakeFocusSet = false; // true: pretend as if the window has focus.
+ mbHexpand = false;
+ mbVexpand = false;
+ mbExpand = false;
+ mbFill = true;
+ mbSecondary = false;
+ mbNonHomogeneous = false;
+ static bool bDoubleBuffer = getenv("VCL_DOUBLEBUFFERING_FORCE_ENABLE");
+ mbDoubleBufferingRequested = bDoubleBuffer; // when we are not sure, assume it cannot do double-buffering via RenderContext
+ mpLOKNotifier = nullptr;
+ mnLOKWindowId = 0;
+ mbLOKParentNotifier = false;
+}
+
+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 )
+{
+ 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;
+ 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 ) );
+ maPaintIdle.SetDebugName( "vcl::Window maPaintIdle" );
+ maResizeIdle.SetPriority( TaskPriority::RESIZE );
+ maResizeIdle.SetInvokeHandler( LINK( pWindow, vcl::Window, ImplHandleResizeTimerHdl ) );
+ maResizeIdle.SetDebugName( "vcl::Window maResizeIdle" );
+ mbInternalDragGestureRecognizer = false;
+ mbInBufferedPaint = false;
+ mnDPIX = 96;
+ mnDPIY = 96;
+ mnTouchPanPosition = -1;
+}
+
+namespace vcl {
+
+bool Window::AcquireGraphics() const
+{
+ DBG_TESTSOLARMUTEX();
+
+ if ( mpGraphics )
+ return true;
+
+ mbInitLineColor = true;
+ mbInitFillColor = true;
+ mbInitFont = true;
+ mbInitTextColor = true;
+ mbInitClipRegion = true;
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ mpGraphics = mpWindowImpl->mpFrame->AcquireGraphics();
+ // try harder if no wingraphics was available directly
+ if ( !mpGraphics )
+ {
+ // find another output device in the same frame
+ OutputDevice* pReleaseOutDev = pSVData->maGDIData.mpLastWinGraphics;
+ while ( pReleaseOutDev )
+ {
+ if ( static_cast<vcl::Window*>(pReleaseOutDev)->mpWindowImpl->mpFrame == mpWindowImpl->mpFrame )
+ break;
+ pReleaseOutDev = pReleaseOutDev->mpPrevGraphics;
+ }
+
+ 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 = mpWindowImpl->mpFrame->AcquireGraphics();
+ }
+ }
+ }
+
+ if ( mpGraphics )
+ {
+ // update global LRU list of wingraphics
+ mpNextGraphics = pSVData->maGDIData.mpFirstWinGraphics;
+ pSVData->maGDIData.mpFirstWinGraphics = const_cast<vcl::Window*>(this);
+ if ( mpNextGraphics )
+ mpNextGraphics->mpPrevGraphics = const_cast<vcl::Window*>(this);
+ if ( !pSVData->maGDIData.mpLastWinGraphics )
+ pSVData->maGDIData.mpLastWinGraphics = const_cast<vcl::Window*>(this);
+
+ mpGraphics->SetXORMode( (RasterOp::Invert == meRasterOp) || (RasterOp::Xor == meRasterOp), RasterOp::Invert == meRasterOp );
+ mpGraphics->setAntiAliasB2DDraw(bool(mnAntialiasing & AntialiasingFlags::EnableB2dDraw));
+ }
+
+ return mpGraphics != nullptr;
+}
+
+void Window::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 = this;
+
+ if ( bRelease )
+ pWindow->mpWindowImpl->mpFrame->ReleaseGraphics( mpGraphics );
+ // remove from global LRU list of window graphics
+ if ( mpPrevGraphics )
+ mpPrevGraphics->mpNextGraphics = mpNextGraphics;
+ else
+ pSVData->maGDIData.mpFirstWinGraphics = mpNextGraphics;
+ if ( mpNextGraphics )
+ mpNextGraphics->mpPrevGraphics = mpPrevGraphics;
+ else
+ pSVData->maGDIData.mpLastWinGraphics = mpPrevGraphics;
+
+ 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 )
+ 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::MODALDIALOG:
+ case WindowType::MODELESSDIALOG:
+ case WindowType::MESSBOX:
+ case WindowType::INFOBOX:
+ case WindowType::WARNINGBOX:
+ case WindowType::ERRORBOX:
+ case WindowType::QUERYBOX:
+ nFrameStyle |= SalFrameStyleFlags::DIALOG;
+ break;
+ default:
+ break;
+ }
+
+ 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
+ mxFontCollection = mpWindowImpl->mpFrameData->mxFontCollection;
+ 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() )
+ {
+ 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.mpSettings );
+ OutputDevice::SetSettings( *pSVData->maAppData.mpSettings );
+ 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( mnOutWidth, 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())
+ OutputDevice::SetSettings( pParent->GetSettings() );
+ }
+
+ }
+
+ // setup the scale factor for HiDPI displays
+ mnDPIScalePercentage = CountDPIScaleFactor(mpWindowImpl->mpFrameData->mnDPIY);
+ mnDPIX = mpWindowImpl->mpFrameData->mnDPIX;
+ mnDPIY = mpWindowImpl->mpFrameData->mnDPIY;
+
+ if (!utl::ConfigManager::IsFuzzing())
+ {
+ const StyleSettings& rStyleSettings = mxSettings->GetStyleSettings();
+ maFont = rStyleSettings.GetAppFont();
+
+ if ( nStyle & WB_3DLOOK )
+ {
+ SetTextColor( rStyleSettings.GetButtonTextColor() );
+ SetBackground( Wallpaper( rStyleSettings.GetFaceColor() ) );
+ }
+ else
+ {
+ SetTextColor( rStyleSettings.GetWindowTextColor() );
+ SetBackground( Wallpaper( rStyleSettings.GetWindowColor() ) );
+ }
+ }
+ else
+ {
+ maFont = GetDefaultFont( DefaultFontType::FIXED, LANGUAGE_ENGLISH_US, GetDefaultFontFlags::NONE );
+ }
+
+ ImplPointToLogic(*this, 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();
+ long nTextHeight = pWindow->GetTextHeight();
+ long nTextWidth = pWindow->approximate_char_width() * 8;
+ 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 = new ImplWinData;
+ mpWindowImpl->mpWinData->mbEnableNativeWidget = !(pNoNWF && *pNoNWF); // true: try to draw this control with native theme API
+ }
+
+ return mpWindowImpl->mpWinData;
+}
+
+
+void Window::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));
+
+ 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* Window::DrawOutDevDirectCheck(const OutputDevice* pSrcDev) const
+{
+ const OutputDevice* pSrcDevChecked;
+ if ( this == pSrcDev )
+ pSrcDevChecked = nullptr;
+ else if (GetOutDevType() != pSrcDev->GetOutDevType())
+ pSrcDevChecked = pSrcDev;
+ else if (this->mpWindowImpl->mpFrameWindow == static_cast<const vcl::Window*>(pSrcDev)->mpWindowImpl->mpFrameWindow)
+ pSrcDevChecked = nullptr;
+ else
+ pSrcDevChecked = pSrcDev;
+
+ return pSrcDevChecked;
+}
+
+void Window::DrawOutDevDirectProcess( const OutputDevice* pSrcDev, SalTwoRect& rPosAry, SalGraphics* pSrcGraphics )
+{
+ mpGraphics->CopyBits( rPosAry, pSrcGraphics, this, pSrcDev );
+}
+
+SalGraphics* Window::ImplGetFrameGraphics() const
+{
+ if ( mpWindowImpl->mpFrameWindow->mpGraphics )
+ {
+ mpWindowImpl->mpFrameWindow->mbInitClipRegion = true;
+ }
+ else
+ {
+ OutputDevice* pFrameWinOutDev = mpWindowImpl->mpFrameWindow;
+ if ( ! pFrameWinOutDev->AcquireGraphics() )
+ {
+ return nullptr;
+ }
+ }
+ mpWindowImpl->mpFrameWindow->mpGraphics->ResetClipRegion();
+ return mpWindowImpl->mpFrameWindow->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;
+
+ 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)
+ {
+ mnDPIX = mpWindowImpl->mpFrameData->mnDPIX;
+ mnDPIY = mpWindowImpl->mpFrameData->mnDPIY;
+
+ // setup the scale factor for HiDPI displays
+ mnDPIScalePercentage = CountDPIScaleFactor(mpWindowImpl->mpFrameData->mnDPIY);
+ const StyleSettings& rStyleSettings = mxSettings->GetStyleSettings();
+ SetPointFont(*this, rStyleSettings.GetAppFont());
+ }
+ else if ( mpWindowImpl->mpParent )
+ {
+ mnDPIX = mpWindowImpl->mpParent->mnDPIX;
+ mnDPIY = mpWindowImpl->mpParent->mnDPIY;
+ mnDPIScalePercentage = mpWindowImpl->mpParent->mnDPIScalePercentage;
+ }
+
+ // update the recalculated values for logical units
+ // and also tools belonging to the values
+ if (IsMapModeEnabled())
+ {
+ MapMode aMapMode = GetMapMode();
+ SetMapMode();
+ SetMapMode( aMapMode );
+ }
+}
+
+void Window::ImplPointToLogic(vcl::RenderContext const & rRenderContext, vcl::Font& rFont) const
+{
+ Size aSize = rFont.GetFontSize();
+
+ if (aSize.Width())
+ {
+ aSize.setWidth( aSize.Width() * ( mpWindowImpl->mpFrameData->mnDPIX) );
+ aSize.AdjustWidth(72 / 2 );
+ aSize.setWidth( aSize.Width() / 72 );
+ }
+ aSize.setHeight( aSize.Height() * ( mpWindowImpl->mpFrameData->mnDPIY) );
+ aSize.AdjustHeight(72/2 );
+ aSize.setHeight( aSize.Height() / 72 );
+
+ if (rRenderContext.IsMapModeEnabled())
+ aSize = rRenderContext.PixelToLogic(aSize);
+
+ rFont.SetFontSize(aSize);
+}
+
+void Window::ImplLogicToPoint(vcl::RenderContext const & rRenderContext, vcl::Font& rFont) const
+{
+ Size aSize = rFont.GetFontSize();
+
+ if (rRenderContext.IsMapModeEnabled())
+ aSize = rRenderContext.LogicToPixel(aSize);
+
+ if (aSize.Width())
+ {
+ aSize.setWidth( aSize.Width() * 72 );
+ aSize.AdjustWidth(mpWindowImpl->mpFrameData->mnDPIX / 2 );
+ aSize.setWidth( aSize.Width() / ( mpWindowImpl->mpFrameData->mnDPIX) );
+ }
+ aSize.setHeight( aSize.Height() * 72 );
+ aSize.AdjustHeight(mpWindowImpl->mpFrameData->mnDPIY / 2 );
+ aSize.setHeight( aSize.Height() / ( mpWindowImpl->mpFrameData->mnDPIY) );
+
+ rFont.SetFontSize(aSize);
+}
+
+bool Window::ImplUpdatePos()
+{
+ bool bSysChild = false;
+
+ if ( ImplIsOverlapWindow() )
+ {
+ mnOutOffX = mpWindowImpl->mnX;
+ mnOutOffY = mpWindowImpl->mnY;
+ }
+ else
+ {
+ vcl::Window* pParent = ImplGetParent();
+
+ mnOutOffX = mpWindowImpl->mnX + pParent->mnOutOffX;
+ mnOutOffY = mpWindowImpl->mnY + pParent->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( mnOutOffX, mnOutOffY, mnOutWidth, mnOutHeight );
+
+ VclPtr< vcl::Window > pChild = mpWindowImpl->mpFirstChild;
+ while ( pChild )
+ {
+ pChild->ImplUpdateSysObjPos();
+ pChild = pChild->mpWindowImpl->mpNext;
+ }
+}
+
+void Window::ImplPosSizeWindow( long nX, long nY,
+ long nWidth, long nHeight, PosSizeFlags nFlags )
+{
+ bool bNewPos = false;
+ bool bNewSize = false;
+ bool bCopyBits = false;
+ long nOldOutOffX = mnOutOffX;
+ long nOldOutOffY = mnOutOffY;
+ long nOldOutWidth = mnOutWidth;
+ long nOldOutHeight = 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( ImplPixelToDevicePixel( mpWindowImpl->maWinRegion ) );
+
+ if ( mnOutWidth && 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 != mnOutWidth )
+ {
+ mnOutWidth = nWidth;
+ bNewSize = true;
+ bCopyBits = false;
+ }
+ }
+ if ( nFlags & PosSizeFlags::Height )
+ {
+ if ( nHeight < 0 )
+ nHeight = 0;
+ if ( nHeight != mnOutHeight )
+ {
+ mnOutHeight = nHeight;
+ bNewSize = true;
+ bCopyBits = false;
+ }
+ }
+
+ if ( nFlags & PosSizeFlags::X )
+ {
+ long nOrgX = nX;
+ Point aPtDev( Point( nX+mnOutOffX, 0 ) );
+ OutputDevice *pOutDev = GetOutDev();
+ if( pOutDev->HasMirroredGraphics() )
+ {
+ aPtDev.setX( mpGraphics->mirror2( aPtDev.X(), this ) );
+
+ // #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->ImplIsAntiparallel() )
+ {
+ nX = mpWindowImpl->mpParent->mnOutWidth - 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( ImplIsAntiparallel() )
+ {
+ aPtDev.setX( mpWindowImpl->mnAbsScreenX );
+ nOrgX = mpWindowImpl->maPos.X();
+ }
+ }
+ }
+ else if( !bnXRecycled && mpWindowImpl->mpParent && !mpWindowImpl->mpParent->mpWindowImpl->mbFrame && mpWindowImpl->mpParent->ImplIsAntiparallel() )
+ {
+ // mirrored window in LTR UI
+ nX = mpWindowImpl->mpParent->mnOutWidth - 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( tools::Rectangle( Point( mnOutOffX, mnOutOffY ),
+ Size( mnOutWidth, mnOutHeight ) ),
+ *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( tools::Rectangle( Point( mnOutOffX, mnOutOffY ),
+ Size( mnOutWidth, mnOutHeight ) ),
+ *pOverlapRegion, false, true );
+ }
+ mpWindowImpl->mnY = nY;
+ mpWindowImpl->maPos.setY( nY );
+ bNewPos = true;
+ }
+ }
+
+ if ( bNewPos || bNewSize )
+ {
+ 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,
+ mnOutWidth-mpWindowImpl->mpClientWindow->mpWindowImpl->mnLeftBorder-mpWindowImpl->mpClientWindow->mpWindowImpl->mnRightBorder,
+ 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 || (mnOutWidth > nOldOutWidth) || (mnOutHeight > nOldOutHeight) )
+ {
+ if ( bNewPos )
+ {
+ bool bInvalidate = false;
+ bool bParentPaint = true;
+ if ( !ImplIsOverlapWindow() )
+ bParentPaint = mpWindowImpl->mpParent->IsPaintEnabled();
+ if ( bCopyBits && bParentPaint && !HasPaintEvent() )
+ {
+ Point aPoint( mnOutOffX, mnOutOffY );
+ vcl::Region aRegion( tools::Rectangle( aPoint,
+ Size( mnOutWidth, mnOutHeight ) ) );
+ if ( mpWindowImpl->mbWinRegion )
+ aRegion.Intersect( ImplPixelToDevicePixel( mpWindowImpl->maWinRegion ) );
+ ImplClipBoundaries( aRegion, false, true );
+ if ( !pOverlapRegion->IsEmpty() )
+ {
+ pOverlapRegion->Move( mnOutOffX-nOldOutOffX, mnOutOffY-nOldOutOffY );
+ aRegion.Exclude( *pOverlapRegion );
+ }
+ if ( !aRegion.IsEmpty() )
+ {
+ // adapt Paint areas
+ ImplMoveAllInvalidateRegions( tools::Rectangle( Point( nOldOutOffX, nOldOutOffY ),
+ Size( nOldOutWidth, nOldOutHeight ) ),
+ mnOutOffX-nOldOutOffX, mnOutOffY-nOldOutOffY,
+ true );
+ SalGraphics* pGraphics = ImplGetFrameGraphics();
+ if ( pGraphics )
+ {
+
+ OutputDevice *pOutDev = GetOutDev();
+ const bool bSelectClipRegion = pOutDev->SelectClipRegion( aRegion, pGraphics );
+ if ( bSelectClipRegion )
+ {
+ pGraphics->CopyArea( mnOutOffX, mnOutOffY,
+ nOldOutOffX, nOldOutOffY,
+ nOldOutWidth, nOldOutHeight,
+ this );
+ }
+ 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
+ {
+ Point aPoint( mnOutOffX, mnOutOffY );
+ vcl::Region aRegion( tools::Rectangle( aPoint,
+ Size( mnOutWidth, mnOutHeight ) ) );
+ aRegion.Exclude( *pOldRegion );
+ if ( mpWindowImpl->mbWinRegion )
+ aRegion.Intersect( ImplPixelToDevicePixel( mpWindowImpl->maWinRegion ) );
+ ImplClipBoundaries( aRegion, false, true );
+ if ( !aRegion.IsEmpty() )
+ ImplInvalidateFrameRegion( &aRegion, InvalidateFlags::Children );
+ }
+ }
+
+ // invalidate Parent or Overlaps
+ if ( bNewPos ||
+ (mnOutWidth < nOldOutWidth) || (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( mnOutOffX, mnOutOffY, mnOutWidth, mnOutHeight );
+ }
+}
+
+void Window::ImplNewInputContext()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ vcl::Window* pFocusWin = pSVData->mpWinData->mpFocusWin;
+ if ( !pFocusWin || 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->mnDPIY)/72 );
+ }
+ pFontInstance = pFocusWin->mxFontCache->GetFontInstance( pFocusWin->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<boost::property_tree::ptree&, void>& rLink)
+{
+ if (mpWindowImpl) // may be called after dispose
+ {
+ mpWindowImpl->maDumpAsPropertyTreeHdl = rLink;
+ }
+}
+
+void Window::SetModalHierarchyHdl(const Link<bool, void>& rLink)
+{
+ ImplGetFrame()->SetModalHierarchyHdl(rLink);
+}
+
+void Window::SetParentToDefaultWindow()
+{
+ Show(false);
+ SetParent(ImplGetDefaultWindow());
+}
+
+KeyIndicatorState Window::GetIndicatorState() const
+{
+ return mpWindowImpl->mpFrame->GetIndicatorState();
+}
+
+void Window::SimulateKeyPress( sal_uInt16 nKeyCode ) const
+{
+ mpWindowImpl->mpFrame->SimulateKeyPress(nKeyCode);
+}
+
+void Window::KeyInput( const KeyEvent& rKEvt )
+{
+ KeyCode cod = rKEvt.GetKeyCode ();
+ bool autoacc = ImplGetSVData()->maNWFData.mbAutoAccel;
+
+ // do not respond to accelerators unless Alt or Ctrl is held */
+ if (cod.GetCode () >= 0x200 && cod.GetCode () <= 0x219)
+ {
+ if (autoacc && cod.GetModifier () != KEY_MOD2 && !(cod.GetModifier() & KEY_MOD1))
+ return;
+ }
+
+ NotifyEvent aNEvt( MouseNotifyEvent::KEYINPUT, this, &rKEvt );
+ if ( !CompatNotify( aNEvt ) )
+ mpWindowImpl->mbKeyInput = true;
+}
+
+void Window::KeyUp( const KeyEvent& rKEvt )
+{
+ NotifyEvent aNEvt( MouseNotifyEvent::KEYUP, this, &rKEvt );
+ if ( !CompatNotify( aNEvt ) )
+ mpWindowImpl->mbKeyUp = true;
+}
+
+void Window::Draw( OutputDevice*, const Point&, DrawFlags )
+{
+}
+
+void Window::Move() {}
+
+void Window::Resize() {}
+
+void Window::Activate() {}
+
+void Window::Deactivate() {}
+
+void Window::GetFocus()
+{
+ if ( HasFocus() && mpWindowImpl->mpLastFocusWindow && !(mpWindowImpl->mnDlgCtrlFlags & DialogControlFlags::WantFocus) )
+ {
+ VclPtr<vcl::Window> xWindow(this);
+ mpWindowImpl->mpLastFocusWindow->GrabFocus();
+ if( xWindow->IsDisposed() )
+ return;
+ }
+
+ NotifyEvent aNEvt( MouseNotifyEvent::GETFOCUS, this );
+ CompatNotify( aNEvt );
+}
+
+void Window::LoseFocus()
+{
+ NotifyEvent aNEvt( MouseNotifyEvent::LOSEFOCUS, this );
+ CompatNotify( aNEvt );
+}
+
+void Window::SetHelpHdl(const Link<vcl::Window&, bool>& rLink)
+{
+ if (mpWindowImpl) // may be called after dispose
+ {
+ mpWindowImpl->maHelpRequestHdl = rLink;
+ }
+}
+
+void Window::RequestHelp( const HelpEvent& rHEvt )
+{
+ // if Balloon-Help is requested, show the balloon
+ // with help text set
+ if ( rHEvt.GetMode() & HelpEventMode::BALLOON )
+ {
+ OUString rStr = GetHelpText();
+ if ( rStr.isEmpty() )
+ rStr = GetQuickHelpText();
+ if ( rStr.isEmpty() && ImplGetParent() && !ImplIsOverlapWindow() )
+ ImplGetParent()->RequestHelp( rHEvt );
+ else
+ {
+ Point aPos = GetPosPixel();
+ if ( ImplGetParent() && !ImplIsOverlapWindow() )
+ aPos = OutputToScreenPixel(Point(0, 0));
+ tools::Rectangle aRect( aPos, GetSizePixel() );
+
+ Help::ShowBalloon( this, rHEvt.GetMousePosPixel(), aRect, rStr );
+ }
+ }
+ else if ( rHEvt.GetMode() & HelpEventMode::QUICK )
+ {
+ const OUString& rStr = GetQuickHelpText();
+ if ( rStr.isEmpty() && ImplGetParent() && !ImplIsOverlapWindow() )
+ ImplGetParent()->RequestHelp( rHEvt );
+ else
+ {
+ Point aPos = GetPosPixel();
+ if ( ImplGetParent() && !ImplIsOverlapWindow() )
+ aPos = OutputToScreenPixel(Point(0, 0));
+ tools::Rectangle aRect( aPos, GetSizePixel() );
+ Help::ShowQuickHelp( this, aRect, rStr, QuickHelpFlags::CtrlText );
+ }
+ }
+ else if (!mpWindowImpl->maHelpRequestHdl.IsSet() || mpWindowImpl->maHelpRequestHdl.Call(*this))
+ {
+ OUString aStrHelpId( OStringToOUString( GetHelpId(), RTL_TEXTENCODING_UTF8 ) );
+ if ( aStrHelpId.isEmpty() && ImplGetParent() )
+ ImplGetParent()->RequestHelp( rHEvt );
+ else
+ {
+ Help* pHelp = Application::GetHelp();
+ if ( pHelp )
+ {
+ if( !aStrHelpId.isEmpty() )
+ pHelp->Start( aStrHelpId, this );
+ else
+ pHelp->Start( OOO_HELP_INDEX, this );
+ }
+ }
+ }
+}
+
+void Window::Command( const CommandEvent& rCEvt )
+{
+ CallEventListeners( VclEventId::WindowCommand, const_cast<CommandEvent *>(&rCEvt) );
+
+ NotifyEvent aNEvt( MouseNotifyEvent::COMMAND, this, &rCEvt );
+ if ( !CompatNotify( aNEvt ) )
+ mpWindowImpl->mbCommand = true;
+}
+
+void Window::Tracking( const TrackingEvent& rTEvt )
+{
+
+ ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this );
+ if( pWrapper )
+ pWrapper->Tracking( rTEvt );
+}
+
+void Window::StateChanged(StateChangedType eType)
+{
+ switch (eType)
+ {
+ //stuff that doesn't invalidate the layout
+ case StateChangedType::ControlForeground:
+ case StateChangedType::ControlBackground:
+ case StateChangedType::UpdateMode:
+ case StateChangedType::ReadOnly:
+ case StateChangedType::Enable:
+ case StateChangedType::State:
+ case StateChangedType::Data:
+ case StateChangedType::InitShow:
+ case StateChangedType::ControlFocus:
+ break;
+ //stuff that does invalidate the layout
+ default:
+ queue_resize(eType);
+ break;
+ }
+}
+
+void Window::SetStyle( WinBits nStyle )
+{
+ if ( mpWindowImpl && mpWindowImpl->mnStyle != nStyle )
+ {
+ mpWindowImpl->mnPrevStyle = mpWindowImpl->mnStyle;
+ mpWindowImpl->mnStyle = nStyle;
+ CompatStateChanged( StateChangedType::Style );
+ }
+}
+
+void Window::SetExtendedStyle( WindowExtendedStyle nExtendedStyle )
+{
+
+ if ( mpWindowImpl->mnExtendedStyle != nExtendedStyle )
+ {
+ 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 )
+ {
+ 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;
+}
+
+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<Window*>(this), rStyleSettings.GetTitleFont());
+ 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, 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;
+}
+
+long Window::GetCursorExtTextInputWidth() const
+{
+
+ ImplWinData* pWinData = ImplGetWinData();
+ return pWinData->mnCursorExtWidth;
+}
+
+void Window::SetCompositionCharRect( const tools::Rectangle* pRect, 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 (long i = 0; i < nCompositionLength; ++i)
+ pWinData->mpCompositionCharRects[i] = pRect[i];
+ }
+}
+
+void Window::CollectChildren(::std::vector<vcl::Window *>& rAllChildren )
+{
+ rAllChildren.push_back( this );
+
+ VclPtr< vcl::Window > pChild = mpWindowImpl->mpFirstChild;
+ while ( pChild )
+ {
+ pChild->CollectChildren( rAllChildren );
+ pChild = pChild->mpWindowImpl->mpNext;
+ }
+}
+
+void Window::SetPointFont(vcl::RenderContext& rRenderContext, const vcl::Font& rFont)
+{
+ vcl::Font aFont = rFont;
+ ImplPointToLogic(rRenderContext, aFont);
+ rRenderContext.SetFont(aFont);
+}
+
+vcl::Font Window::GetPointFont(vcl::RenderContext const & rRenderContext) const
+{
+ vcl::Font aFont = rRenderContext.GetFont();
+ ImplLogicToPoint(rRenderContext, aFont);
+ return aFont;
+}
+
+void Window::Show(bool bVisible, ShowFlags nFlags)
+{
+ if ( IsDisposed() || mpWindowImpl->mbVisible == bVisible )
+ return;
+
+ VclPtr<vcl::Window> xWindow(this);
+
+ bool bRealVisibilityChanged = false;
+ mpWindowImpl->mbVisible = bVisible;
+
+ if ( !bVisible )
+ {
+ ImplHideAllOverlaps();
+ if( xWindow->IsDisposed() )
+ 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->IsDisposed() )
+ 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()->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->IsDisposed() )
+ return;
+
+ // Query the correct size of the window, if we are waiting for
+ // a system resize
+ if ( mpWindowImpl->mbWaitSystemResize )
+ {
+ long nOutWidth;
+ 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->IsDisposed() )
+ return;
+
+ ImplShowAllOverlaps();
+ }
+
+ if( xWindow->IsDisposed() )
+ return;
+
+ // the SHOW/HIDE events also serve as indicators to send child creation/destroy events to the access bridge
+ // However, the access bridge only uses this event if the data member is not NULL (it's kind of a hack that
+ // we re-use the SHOW/HIDE events this way, with this particular semantics).
+ // Since #104887#, the notifications for the access bridge are done in Impl(Set|Reset)ReallyVisible. Here, we
+ // now only notify with a NULL data pointer, for all other clients except the access bridge.
+ if ( !bRealVisibilityChanged )
+ CallEventListeners( mpWindowImpl->mbVisible ? VclEventId::WindowShow : VclEventId::WindowHide );
+ if( xWindow->IsDisposed() )
+ return;
+
+}
+
+Size Window::GetSizePixel() const
+{
+ if (!mpWindowImpl)
+ {
+ SAL_WARN("vcl.layout", "WTF no windowimpl");
+ return Size(0,0);
+ }
+
+ // #i43257# trigger pending resize handler to assure correct window sizes
+ if( mpWindowImpl->mpFrameData->maResizeIdle.IsActive() )
+ {
+ VclPtr<vcl::Window> xWindow( const_cast<Window*>(this) );
+ mpWindowImpl->mpFrameData->maResizeIdle.Stop();
+ mpWindowImpl->mpFrameData->maResizeIdle.Invoke( nullptr );
+ if( xWindow->IsDisposed() )
+ return Size(0,0);
+ }
+
+ return Size( mnOutWidth+mpWindowImpl->mnLeftBorder+mpWindowImpl->mnRightBorder,
+ 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::SetCallHandlersOnInputDisabled( bool bCall )
+{
+ mpWindowImpl->mbCallHandlersDuringInputDisabled = bCall;
+
+ VclPtr< vcl::Window > pChild = mpWindowImpl->mpFirstChild;
+ while ( pChild )
+ {
+ pChild->SetCallHandlersOnInputDisabled( bCall );
+ pChild = pChild->mpWindowImpl->mpNext;
+ }
+}
+
+bool Window::IsCallHandlersOnInputDisabled() const
+{
+ return mpWindowImpl->mbCallHandlersDuringInputDisabled;
+}
+
+void Window::EnableInput( bool bEnable, bool bChild )
+{
+ if (!mpWindowImpl)
+ return;
+
+ bool bNotify = (bEnable != mpWindowImpl->mbInputDisabled);
+ 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 && mpWindowImpl->meAlwaysInputMode != AlwaysInputDisabled) )
+ {
+ // 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();
+
+ // #104827# notify parent
+ if ( bNotify && bEnable )
+ {
+ NotifyEvent aNEvt( MouseNotifyEvent::INPUTENABLE, this );
+ CompatNotify( aNEvt );
+ }
+}
+
+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 )
+ {
+ ::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::AlwaysDisableInput( bool bAlways, bool bChild )
+{
+
+ if ( mpWindowImpl->mpBorderWindow )
+ mpWindowImpl->mpBorderWindow->AlwaysDisableInput( bAlways, false );
+
+ if( bAlways && mpWindowImpl->meAlwaysInputMode != AlwaysInputDisabled )
+ {
+ mpWindowImpl->meAlwaysInputMode = AlwaysInputDisabled;
+ EnableInput(false, false);
+ }
+ else if( ! bAlways && mpWindowImpl->meAlwaysInputMode == AlwaysInputDisabled )
+ {
+ mpWindowImpl->meAlwaysInputMode = AlwaysInputNone;
+ }
+
+ if ( bChild )
+ {
+ VclPtr< vcl::Window > pChild = mpWindowImpl->mpFirstChild;
+ while ( pChild )
+ {
+ pChild->AlwaysDisableInput( bAlways, bChild );
+ pChild = pChild->mpWindowImpl->mpNext;
+ }
+ }
+}
+
+void Window::SetActivateMode( ActivateModeFlags nMode )
+{
+
+ if ( mpWindowImpl->mpBorderWindow )
+ mpWindowImpl->mpBorderWindow->SetActivateMode( nMode );
+
+ if ( mpWindowImpl->mnActivateMode != nMode )
+ {
+ 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( long nX, long nY,
+ long nWidth, 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)
+ long nOldWidth = pWindow->mnOutWidth;
+
+ if ( !(nFlags & PosSizeFlags::Width) )
+ nWidth = pWindow->mnOutWidth;
+ if ( !(nFlags & PosSizeFlags::Height) )
+ nHeight = pWindow->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->mnOutOffX;
+ }
+ if( pParent && pParent->ImplIsAntiparallel() )
+ {
+ tools::Rectangle aRect( Point ( nX, nY ), Size( nWidth, nHeight ) );
+ const OutputDevice *pParentOutDev = pParent->GetOutDev();
+ pParentOutDev->ReMirror( aRect );
+ nX = aRect.Left();
+ }
+ }
+ if( !(nFlags & PosSizeFlags::X) && bHasValidSize && pWindow->mpWindowImpl->mpFrame->maGeometry.nWidth )
+ {
+ // RTL: make sure the old right aligned position is not changed
+ // system windows will always grow to the right
+ if ( pWinParent )
+ {
+ OutputDevice *pParentOutDev = pWinParent->GetOutDev();
+ if( pParentOutDev->HasMirroredGraphics() )
+ {
+ const SalFrameGeometry& aSysGeometry = mpWindowImpl->mpFrame->GetUnmirroredGeometry();
+ const SalFrameGeometry& aParentSysGeometry =
+ pWinParent->mpWindowImpl->mpFrame->GetUnmirroredGeometry();
+ long myWidth = nOldWidth;
+ if( !myWidth )
+ myWidth = aSysGeometry.nWidth;
+ if( !myWidth )
+ myWidth = nWidth;
+ nFlags |= PosSizeFlags::X;
+ nSysFlags |= SAL_FRAME_POSSIZE_X;
+ nX = aParentSysGeometry.nX - aSysGeometry.nLeftDecoration + aParentSysGeometry.nWidth
+ - myWidth - 1 - aSysGeometry.nX;
+ }
+ }
+ }
+ if( nFlags & PosSizeFlags::Y )
+ {
+ nSysFlags |= SAL_FRAME_POSSIZE_Y;
+ if( pWinParent && (pWindow->GetStyle() & WB_SYSTEMCHILDWINDOW) )
+ {
+ nY += pWinParent->mnOutOffY;
+ }
+ }
+
+ if( nSysFlags & (SAL_FRAME_POSSIZE_WIDTH|SAL_FRAME_POSSIZE_HEIGHT) )
+ {
+ // check for min/max client size and adjust size accordingly
+ // otherwise it may happen that the resize event is ignored, i.e. the old size remains
+ // unchanged but ImplHandleResize() is called with the wrong size
+ SystemWindow *pSystemWindow = dynamic_cast< SystemWindow* >( pWindow.get() );
+ if( pSystemWindow )
+ {
+ Size aMinSize = pSystemWindow->GetMinOutputSizePixel();
+ Size aMaxSize = pSystemWindow->GetMaxOutputSizePixel();
+ if( nWidth < aMinSize.Width() )
+ nWidth = aMinSize.Width();
+ if( nHeight < aMinSize.Height() )
+ nHeight = aMinSize.Height();
+
+ if( nWidth > aMaxSize.Width() )
+ nWidth = aMaxSize.Width();
+ if( nHeight > aMaxSize.Height() )
+ nHeight = aMaxSize.Height();
+ }
+ }
+
+ pWindow->mpWindowImpl->mpFrame->SetPosSize( nX, nY, nWidth, nHeight, nSysFlags );
+
+ // Adjust resize with the hack of different client size and frame geometries to fix
+ // native menu bars. Eventually this should be replaced by proper mnTopBorder usage.
+ pWindow->mpWindowImpl->mpFrame->GetClientSize(nWidth, nHeight);
+
+ // Resize should be called directly. If we haven't
+ // set the correct size, we get a second resize from
+ // the system with the correct size. This can be happened
+ // if the size is too small or too large.
+ ImplHandleResize( pWindow, nWidth, nHeight );
+ }
+ else
+ {
+ pWindow->ImplPosSizeWindow( nX, nY, nWidth, nHeight, nFlags );
+ if ( IsReallyVisible() )
+ ImplGenerateMouseMove();
+ }
+}
+
+Point Window::GetPosPixel() const
+{
+ return mpWindowImpl->maPos;
+}
+
+tools::Rectangle Window::GetDesktopRectPixel() const
+{
+ tools::Rectangle rRect;
+ mpWindowImpl->mpFrameWindow->mpWindowImpl->mpFrame->GetWorkArea( rRect );
+ return rRect;
+}
+
+Point Window::OutputToScreenPixel( const Point& rPos ) const
+{
+ // relative to top level parent
+ return Point( rPos.X()+mnOutOffX, rPos.Y()+mnOutOffY );
+}
+
+Point Window::ScreenToOutputPixel( const Point& rPos ) const
+{
+ // relative to top level parent
+ return Point( rPos.X()-mnOutOffX, rPos.Y()-mnOutOffY );
+}
+
+long Window::ImplGetUnmirroredOutOffX()
+{
+ // revert mnOutOffX changes that were potentially made in ImplPosSizeWindow
+ long offx = mnOutOffX;
+ OutputDevice *pOutDev = GetOutDev();
+ if( pOutDev->HasMirroredGraphics() )
+ {
+ if( mpWindowImpl->mpParent && !mpWindowImpl->mpParent->mpWindowImpl->mbFrame && mpWindowImpl->mpParent->ImplIsAntiparallel() )
+ {
+ if ( !ImplIsOverlapWindow() )
+ offx -= mpWindowImpl->mpParent->mnOutOffX;
+
+ offx = mpWindowImpl->mpParent->mnOutWidth - mnOutWidth - offx;
+
+ if ( !ImplIsOverlapWindow() )
+ offx += mpWindowImpl->mpParent->mnOutOffX;
+
+ }
+ }
+ return offx;
+}
+
+// normalized screen pixel are independent of mirroring
+Point Window::OutputToNormalizedScreenPixel( const Point& rPos ) const
+{
+ // relative to top level parent
+ long offx = const_cast<vcl::Window*>(this)->ImplGetUnmirroredOutOffX();
+ return Point( rPos.X()+offx, rPos.Y()+mnOutOffY );
+}
+
+Point Window::NormalizedScreenToOutputPixel( const Point& rPos ) const
+{
+ // relative to top level parent
+ long offx = const_cast<vcl::Window*>(this)->ImplGetUnmirroredOutOffX();
+ return Point( rPos.X()-offx, rPos.Y()-mnOutOffY );
+}
+
+Point Window::OutputToAbsoluteScreenPixel( const Point& rPos ) const
+{
+ // relative to the screen
+ Point p = OutputToScreenPixel( rPos );
+ SalFrameGeometry g = mpWindowImpl->mpFrame->GetGeometry();
+ p.AdjustX(g.nX );
+ p.AdjustY(g.nY );
+ return p;
+}
+
+Point Window::AbsoluteScreenToOutputPixel( const Point& rPos ) const
+{
+ // relative to the screen
+ Point p = ScreenToOutputPixel( rPos );
+ SalFrameGeometry g = mpWindowImpl->mpFrame->GetGeometry();
+ p.AdjustX( -(g.nX) );
+ p.AdjustY( -(g.nY) );
+ return p;
+}
+
+tools::Rectangle Window::ImplOutputToUnmirroredAbsoluteScreenPixel( const tools::Rectangle &rRect ) const
+{
+ // this method creates unmirrored screen coordinates to be compared with the desktop
+ // and is used for positioning of RTL popup windows correctly on the screen
+ SalFrameGeometry g = mpWindowImpl->mpFrame->GetUnmirroredGeometry();
+
+ Point p1 = OutputToScreenPixel( rRect.TopRight() );
+ p1.setX( g.nX+g.nWidth-p1.X() );
+ p1.AdjustY(g.nY );
+
+ Point p2 = OutputToScreenPixel( rRect.BottomLeft() );
+ p2.setX( g.nX+g.nWidth-p2.X() );
+ p2.AdjustY(g.nY );
+
+ return tools::Rectangle( p1, p2 );
+}
+
+tools::Rectangle Window::GetWindowExtentsRelative( vcl::Window *pRelativeWindow ) const
+{
+ // with decoration
+ return ImplGetWindowExtentsRelative( pRelativeWindow );
+}
+
+tools::Rectangle Window::ImplGetWindowExtentsRelative( vcl::Window *pRelativeWindow ) const
+{
+ SalFrameGeometry g = mpWindowImpl->mpFrame->GetGeometry();
+ // make sure we use the extent of our border window,
+ // otherwise we miss a few pixels
+ const vcl::Window *pWin = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow : this;
+
+ Point aPos( pWin->OutputToScreenPixel( Point(0,0) ) );
+ aPos.AdjustX(g.nX );
+ aPos.AdjustY(g.nY );
+ Size aSize ( pWin->GetSizePixel() );
+ // #104088# do not add decoration to the workwindow to be compatible to java accessibility api
+ if( mpWindowImpl->mbFrame || (mpWindowImpl->mpBorderWindow && mpWindowImpl->mpBorderWindow->mpWindowImpl->mbFrame && GetType() != WindowType::WORKWINDOW) )
+ {
+ aPos.AdjustX( -sal_Int32(g.nLeftDecoration) );
+ aPos.AdjustY( -sal_Int32(g.nTopDecoration) );
+ aSize.AdjustWidth(g.nLeftDecoration + g.nRightDecoration );
+ aSize.AdjustHeight(g.nTopDecoration + g.nBottomDecoration );
+ }
+ if( pRelativeWindow )
+ {
+ // #106399# express coordinates relative to borderwindow
+ vcl::Window *pRelWin = pRelativeWindow->mpWindowImpl->mpBorderWindow ? pRelativeWindow->mpWindowImpl->mpBorderWindow.get() : pRelativeWindow;
+ aPos = pRelWin->AbsoluteScreenToOutputPixel( aPos );
+ }
+ return tools::Rectangle( aPos, aSize );
+}
+
+void Window::Scroll( long nHorzScroll, long nVertScroll, ScrollFlags nFlags )
+{
+
+ ImplScroll( tools::Rectangle( Point( mnOutOffX, mnOutOffY ),
+ Size( mnOutWidth, mnOutHeight ) ),
+ nHorzScroll, nVertScroll, nFlags & ~ScrollFlags::Clip );
+}
+
+void Window::Scroll( long nHorzScroll, long nVertScroll,
+ const tools::Rectangle& rRect, ScrollFlags nFlags )
+{
+ OutputDevice *pOutDev = GetOutDev();
+ tools::Rectangle aRect = pOutDev->ImplLogicToDevicePixel( rRect );
+ aRect.Intersection( tools::Rectangle( Point( mnOutOffX, mnOutOffY ), Size( mnOutWidth, mnOutHeight ) ) );
+ if ( !aRect.IsEmpty() )
+ ImplScroll( aRect, nHorzScroll, nVertScroll, nFlags );
+}
+
+void Window::Flush()
+{
+ if (mpWindowImpl)
+ {
+ const tools::Rectangle aWinRect( Point( mnOutOffX, mnOutOffY ), Size( mnOutWidth, mnOutHeight ) );
+ mpWindowImpl->mpFrame->Flush( aWinRect );
+ }
+}
+
+void Window::SetUpdateMode( bool bUpdate )
+{
+ if (mpWindowImpl)
+ {
+ mpWindowImpl->mbNoUpdate = !bUpdate;
+ CompatStateChanged( StateChangedType::UpdateMode );
+ }
+}
+
+void Window::GrabFocus()
+{
+ ImplGrabFocus( GetFocusFlags::NONE );
+}
+
+bool Window::HasFocus() const
+{
+ return (this == ImplGetSVData()->mpWinData->mpFocusWin);
+}
+
+void Window::GrabFocusToDocument()
+{
+ ImplGrabFocusToDocument(GetFocusFlags::NONE);
+}
+
+VclPtr<vcl::Window> Window::GetFocusedWindow() const
+{
+ if (mpWindowImpl && mpWindowImpl->mpFrameData)
+ return mpWindowImpl->mpFrameData->mpFocusWin;
+ else
+ return VclPtr<vcl::Window>();
+}
+
+void Window::SetFakeFocus( bool bFocus )
+{
+ ImplGetWindowImpl()->mbFakeFocusSet = bFocus;
+}
+
+bool Window::HasChildPathFocus( bool bSystemWindow ) const
+{
+
+ vcl::Window* pFocusWin = ImplGetSVData()->mpWinData->mpFocusWin;
+ if ( pFocusWin )
+ return ImplIsWindowOrChild( pFocusWin, bSystemWindow );
+ return false;
+}
+
+void Window::SetCursor( vcl::Cursor* pCursor )
+{
+
+ if ( mpWindowImpl->mpCursor != pCursor )
+ {
+ if ( mpWindowImpl->mpCursor )
+ mpWindowImpl->mpCursor->ImplHide();
+ mpWindowImpl->mpCursor = pCursor;
+ if ( pCursor )
+ pCursor->ImplShow();
+ }
+}
+
+void Window::SetText( const OUString& rStr )
+{
+ if (!mpWindowImpl || rStr == mpWindowImpl->maText)
+ return;
+
+ OUString oldTitle( mpWindowImpl->maText );
+ mpWindowImpl->maText = rStr;
+
+ if ( mpWindowImpl->mpBorderWindow )
+ mpWindowImpl->mpBorderWindow->SetText( rStr );
+ else if ( mpWindowImpl->mbFrame )
+ mpWindowImpl->mpFrame->SetTitle( rStr );
+
+ CallEventListeners( VclEventId::WindowFrameTitleChanged, &oldTitle );
+
+ // #107247# needed for accessibility
+ // The VclEventId::WindowFrameTitleChanged is (mis)used to notify accessible name changes.
+ // Therefore a window, which is labeled by this window, must also notify an accessible
+ // name change.
+ if ( IsReallyVisible() )
+ {
+ vcl::Window* pWindow = GetAccessibleRelationLabelFor();
+ if ( pWindow && pWindow != this )
+ pWindow->CallEventListeners( VclEventId::WindowFrameTitleChanged, &oldTitle );
+ }
+
+ CompatStateChanged( StateChangedType::Text );
+}
+
+OUString Window::GetText() const
+{
+
+ return mpWindowImpl->maText;
+}
+
+OUString Window::GetDisplayText() const
+{
+
+ return GetText();
+}
+
+const Wallpaper& Window::GetDisplayBackground() const
+{
+ // FIXME: fix issue 52349, need to fix this really in
+ // all NWF enabled controls
+ const ToolBox* pTB = dynamic_cast<const ToolBox*>(this);
+ if( pTB && IsNativeWidgetEnabled() )
+ return pTB->ImplGetToolBoxPrivateData()->maDisplayBackground;
+
+ if( !IsBackground() )
+ {
+ if( mpWindowImpl->mpParent )
+ return mpWindowImpl->mpParent->GetDisplayBackground();
+ }
+
+ const Wallpaper& rBack = GetBackground();
+ if( ! rBack.IsBitmap() &&
+ ! rBack.IsGradient() &&
+ rBack.GetColor()== COL_TRANSPARENT &&
+ mpWindowImpl->mpParent )
+ return mpWindowImpl->mpParent->GetDisplayBackground();
+ return rBack;
+}
+
+const OUString& Window::GetHelpText() const
+{
+ OUString aStrHelpId( OStringToOUString( GetHelpId(), RTL_TEXTENCODING_UTF8 ) );
+ bool bStrHelpId = !aStrHelpId.isEmpty();
+
+ if ( !mpWindowImpl->maHelpText.getLength() && bStrHelpId )
+ {
+ if ( !IsDialog() && (mpWindowImpl->mnType != WindowType::TABPAGE) && (mpWindowImpl->mnType != WindowType::FLOATINGWINDOW) )
+ {
+ Help* pHelp = Application::GetHelp();
+ if ( pHelp )
+ {
+ mpWindowImpl->maHelpText = pHelp->GetHelpText(aStrHelpId, this);
+ mpWindowImpl->mbHelpTextDynamic = false;
+ }
+ }
+ }
+ else if( mpWindowImpl->mbHelpTextDynamic && bStrHelpId )
+ {
+ static const char* pEnv = getenv( "HELP_DEBUG" );
+ if( pEnv && *pEnv )
+ {
+ OUString aTxt = mpWindowImpl->maHelpText + "\n------------------\n" + aStrHelpId;
+ mpWindowImpl->maHelpText = aTxt;
+ }
+ mpWindowImpl->mbHelpTextDynamic = false;
+ }
+
+ //Fallback to Window::GetAccessibleDescription without reentry to GetHelpText()
+ if (mpWindowImpl->maHelpText.isEmpty() && mpWindowImpl->mpAccessibleInfos && mpWindowImpl->mpAccessibleInfos->pAccessibleDescription)
+ return *mpWindowImpl->mpAccessibleInfos->pAccessibleDescription;
+ return mpWindowImpl->maHelpText;
+}
+
+void Window::SetWindowPeer( Reference< css::awt::XWindowPeer > const & xPeer, VCLXWindow* pVCLXWindow )
+{
+ if (!mpWindowImpl)
+ return;
+
+ // be safe against re-entrance: first clear the old ref, then assign the new one
+ mpWindowImpl->mxWindowPeer.clear();
+ mpWindowImpl->mxWindowPeer = xPeer;
+
+ mpWindowImpl->mpVCLXWindow = pVCLXWindow;
+}
+
+Reference< css::awt::XWindowPeer > Window::GetComponentInterface( bool bCreate )
+{
+ if ( !mpWindowImpl->mxWindowPeer.is() && bCreate )
+ {
+ UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper();
+ if ( pWrapper )
+ mpWindowImpl->mxWindowPeer = pWrapper->GetWindowInterface( this );
+ }
+ return mpWindowImpl->mxWindowPeer;
+}
+
+void Window::SetComponentInterface( Reference< css::awt::XWindowPeer > const & xIFace )
+{
+ UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper();
+ SAL_WARN_IF( !pWrapper, "vcl.window", "SetComponentInterface: No Wrapper!" );
+ if ( pWrapper )
+ pWrapper->SetWindowInterface( this, xIFace );
+}
+
+typedef std::map<vcl::LOKWindowId, VclPtr<vcl::Window>> LOKWindowsMap;
+
+namespace {
+
+LOKWindowsMap& GetLOKWindowsMap()
+{
+ // Map to remember the LOKWindowId <-> Window binding.
+ static LOKWindowsMap s_aLOKWindowsMap;
+
+ return s_aLOKWindowsMap;
+}
+
+}
+
+void Window::SetLOKNotifier(const vcl::ILibreOfficeKitNotifier* pNotifier, bool bParent)
+{
+ // don't allow setting this twice
+ assert(mpWindowImpl->mpLOKNotifier == nullptr);
+ assert(pNotifier);
+ // never use this in the desktop case
+ assert(comphelper::LibreOfficeKit::isActive());
+
+ if (!bParent)
+ {
+ // Counter to be able to have unique id's for each window.
+ static vcl::LOKWindowId sLastLOKWindowId = 1;
+
+ // assign the LOK window id
+ assert(mpWindowImpl->mnLOKWindowId == 0);
+ mpWindowImpl->mnLOKWindowId = sLastLOKWindowId++;
+ GetLOKWindowsMap().emplace(mpWindowImpl->mnLOKWindowId, this);
+ }
+ else
+ mpWindowImpl->mbLOKParentNotifier = true;
+
+ mpWindowImpl->mpLOKNotifier = pNotifier;
+}
+
+VclPtr<Window> Window::FindLOKWindow(vcl::LOKWindowId nWindowId)
+{
+ const auto it = GetLOKWindowsMap().find(nWindowId);
+ if (it != GetLOKWindowsMap().end())
+ return it->second;
+
+ return VclPtr<Window>();
+}
+
+bool Window::IsLOKWindowsEmpty()
+{
+ return GetLOKWindowsMap().empty();
+}
+
+void Window::ReleaseLOKNotifier()
+{
+ // unregister the LOK window binding
+ if (mpWindowImpl->mnLOKWindowId > 0)
+ GetLOKWindowsMap().erase(mpWindowImpl->mnLOKWindowId);
+
+ mpWindowImpl->mpLOKNotifier = nullptr;
+ mpWindowImpl->mnLOKWindowId = 0;
+}
+
+ILibreOfficeKitNotifier::~ILibreOfficeKitNotifier()
+{
+ if (!comphelper::LibreOfficeKit::isActive())
+ {
+ return;
+ }
+
+ for (auto it = GetLOKWindowsMap().begin(); it != GetLOKWindowsMap().end();)
+ {
+ WindowImpl* pWindowImpl = it->second->ImplGetWindowImpl();
+ if (pWindowImpl && pWindowImpl->mpLOKNotifier == this)
+ {
+ pWindowImpl->mpLOKNotifier = nullptr;
+ pWindowImpl->mnLOKWindowId = 0;
+ it = GetLOKWindowsMap().erase(it);
+ continue;
+ }
+
+ ++it;
+ }
+}
+
+const vcl::ILibreOfficeKitNotifier* Window::GetLOKNotifier() const
+{
+ return mpWindowImpl->mpLOKNotifier;
+}
+
+vcl::LOKWindowId Window::GetLOKWindowId() const
+{
+ return mpWindowImpl->mnLOKWindowId;
+}
+
+VclPtr<vcl::Window> Window::GetParentWithLOKNotifier()
+{
+ VclPtr<vcl::Window> pWindow(this);
+
+ while (pWindow && !pWindow->GetLOKNotifier())
+ pWindow = pWindow->GetParent();
+
+ return pWindow;
+}
+
+namespace
+{
+
+const char* windowTypeName(WindowType nWindowType)
+{
+ switch (nWindowType)
+ {
+ case WindowType::NONE: return "none";
+ case WindowType::MESSBOX: return "messagebox";
+ case WindowType::INFOBOX: return "infobox";
+ case WindowType::WARNINGBOX: return "warningbox";
+ case WindowType::ERRORBOX: return "errorbox";
+ case WindowType::QUERYBOX: return "querybox";
+ case WindowType::WINDOW: return "window";
+ case WindowType::WORKWINDOW: return "workwindow";
+ case WindowType::CONTAINER: return "container";
+ case WindowType::FLOATINGWINDOW: return "floatingwindow";
+ case WindowType::DIALOG: return "dialog";
+ case WindowType::MODELESSDIALOG: return "modelessdialog";
+ case WindowType::MODALDIALOG: return "modaldialog";
+ 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::NUMERICFIELD: return "numericfield";
+ 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::LONGCURRENCYFIELD: return "longcurrencyfield";
+ 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::CALCINPUTLINE: return "calcinputline";
+ 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";
+}
+
+}
+
+boost::property_tree::ptree Window::DumpAsPropertyTree()
+{
+ boost::property_tree::ptree aTree;
+ aTree.put("id", get_id()); // TODO could be missing - sort out
+ aTree.put("type", windowTypeName(GetType()));
+ aTree.put("text", GetText());
+ aTree.put("enabled", IsEnabled());
+
+ boost::property_tree::ptree aChildren;
+ if (vcl::Window* pChild = mpWindowImpl->mpFirstChild)
+ {
+ while (pChild)
+ {
+ if (pChild->IsVisible()) {
+ boost::property_tree::ptree aSubTree = pChild->DumpAsPropertyTree();
+ int nLeft = pChild->get_grid_left_attach();
+ int nTop = pChild->get_grid_top_attach();
+ if (nLeft != -1 && nTop != -1)
+ {
+ OUString sLeft = OUString::number(nLeft);
+ OUString sTop = OUString::number(nTop);
+ aSubTree.put("left", sLeft);
+ aSubTree.put("top", sTop);
+ }
+ aChildren.push_back(std::make_pair("", aSubTree));
+ }
+ pChild = pChild->mpWindowImpl->mpNext;
+ }
+ aTree.add_child("children", aChildren);
+ }
+
+ mpWindowImpl->maDumpAsPropertyTreeHdl.Call(aTree);
+
+ return aTree;
+}
+
+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->IsDisposed() )
+ return;
+
+ // #100759#, avoid walking the wrong frame's hierarchy
+ // eg, undocked docking windows (ImplDockFloatWin)
+ if ( ImplGetParent() && 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 ) )
+ {
+ VclPtr<vcl::Window> xWindow(this);
+ CallEventListeners( VclEventId::WindowActivate, pOld );
+ if( xWindow->IsDisposed() )
+ 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;
+}
+
+Reference< XClipboard > Window::GetPrimarySelection()
+{
+ if (!mpWindowImpl->mpFrameData)
+ return static_cast<XClipboard*>(nullptr);
+ if (!mpWindowImpl->mpFrameData->mxSelection.is())
+ mpWindowImpl->mpFrameData->mxSelection = GetSystemPrimarySelection();
+ return mpWindowImpl->mpFrameData->mxSelection;
+}
+
+void Window::RecordLayoutData( vcl::ControlLayoutData* pLayout, const tools::Rectangle& rRect )
+{
+ assert(mpOutDevData);
+ mpOutDevData->mpRecordLayout = pLayout;
+ mpOutDevData->maRecordRect = rRect;
+ Paint(*this, rRect);
+ 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 = GetFillColor();
+ Color oldLineCol = GetLineColor();
+
+ if( bDrawBorder )
+ SetLineColor( bDark ? COL_WHITE : ( bBright ? COL_BLACK : aSelectionBorderCol ) );
+ else
+ 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;
+ 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;
+ 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;
+ SetLineColor( COL_BLACK );
+ if( highlight == 3 )
+ nPercent = 80;
+ else
+ nPercent = 0;
+ }
+ else
+ nPercent = 70; // selected ( dark )
+ }
+ }
+
+ SetFillColor( aSelectionFillCol );
+
+ if( bDark )
+ {
+ DrawRect( aRect );
+ }
+ else
+ {
+ tools::Polygon aPoly( aRect );
+ tools::PolyPolygon aPolyPoly( aPoly );
+ DrawTransparent( aPolyPoly, nPercent );
+ }
+
+ SetFillColor( oldFillCol );
+ SetLineColor( oldLineCol );
+}
+
+bool Window::IsScrollable() const
+{
+ // check for scrollbars
+ VclPtr< vcl::Window > pChild = mpWindowImpl->mpFirstChild;
+ while( pChild )
+ {
+ if( pChild->GetType() == WindowType::SCROLLBAR )
+ return true;
+ else
+ pChild = pChild->mpWindowImpl->mpNext;
+ }
+ return false;
+}
+
+void Window::ImplMirrorFramePos( Point &pt ) const
+{
+ pt.setX( mpWindowImpl->mpFrame->maGeometry.nWidth-1-pt.X() );
+}
+
+// frame based modal counter (dialogs are not modal to the whole application anymore)
+bool Window::IsInModalMode() const
+{
+ return (mpWindowImpl->mpFrameWindow->mpWindowImpl->mpFrameData->mnModalMode != 0);
+}
+
+void Window::IncModalCount()
+{
+ vcl::Window* pFrameWindow = mpWindowImpl->mpFrameWindow;
+ vcl::Window* pParent = pFrameWindow;
+ while( pFrameWindow )
+ {
+ pFrameWindow->mpWindowImpl->mpFrameData->mnModalMode++;
+ while( pParent && pParent->mpWindowImpl->mpFrameWindow == pFrameWindow )
+ {
+ pParent = pParent->GetParent();
+ }
+ pFrameWindow = pParent ? pParent->mpWindowImpl->mpFrameWindow.get() : nullptr;
+ }
+}
+void Window::DecModalCount()
+{
+ vcl::Window* pFrameWindow = mpWindowImpl->mpFrameWindow;
+ vcl::Window* pParent = pFrameWindow;
+ while( pFrameWindow )
+ {
+ pFrameWindow->mpWindowImpl->mpFrameData->mnModalMode--;
+ while( pParent && pParent->mpWindowImpl->mpFrameWindow == pFrameWindow )
+ {
+ pParent = pParent->GetParent();
+ }
+ pFrameWindow = pParent ? pParent->mpWindowImpl->mpFrameWindow.get() : nullptr;
+ }
+}
+
+void Window::ImplIsInTaskPaneList( bool mbIsInTaskList )
+{
+ mpWindowImpl->mbIsInTaskPaneList = mbIsInTaskList;
+}
+
+void Window::ImplNotifyIconifiedState( bool bIconified )
+{
+ mpWindowImpl->mpFrameWindow->CallEventListeners( bIconified ? VclEventId::WindowMinimize : VclEventId::WindowNormalize );
+ // #109206# notify client window as well to have toolkit topwindow listeners notified
+ if( mpWindowImpl->mpFrameWindow->mpWindowImpl->mpClientWindow && mpWindowImpl->mpFrameWindow != mpWindowImpl->mpFrameWindow->mpWindowImpl->mpClientWindow )
+ mpWindowImpl->mpFrameWindow->mpWindowImpl->mpClientWindow->CallEventListeners( bIconified ? VclEventId::WindowMinimize : VclEventId::WindowNormalize );
+}
+
+bool Window::HasActiveChildFrame() const
+{
+ bool bRet = false;
+ vcl::Window *pFrameWin = ImplGetSVData()->maFrameData.mpFirstFrame;
+ while( pFrameWin )
+ {
+ if( pFrameWin != mpWindowImpl->mpFrameWindow )
+ {
+ bool bDecorated = false;
+ VclPtr< vcl::Window > pChildFrame = pFrameWin->ImplGetWindow();
+ // #i15285# unfortunately WB_MOVEABLE is the same as WB_TABSTOP which can
+ // be removed for ToolBoxes to influence the keyboard accessibility
+ // thus WB_MOVEABLE is no indicator for decoration anymore
+ // but FloatingWindows carry this information in their TitleType...
+ // TODO: avoid duplicate WinBits !!!
+ if( pChildFrame && pChildFrame->ImplIsFloatingWindow() )
+ bDecorated = static_cast<FloatingWindow*>(pChildFrame.get())->GetTitleType() != FloatWinTitleType::NONE;
+ if( bDecorated || (pFrameWin->mpWindowImpl->mnStyle & (WB_MOVEABLE | WB_SIZEABLE) ) )
+ if( pChildFrame && pChildFrame->IsVisible() && pChildFrame->IsActive() )
+ {
+ if( ImplIsChild( pChildFrame, true ) )
+ {
+ bRet = true;
+ break;
+ }
+ }
+ }
+ pFrameWin = pFrameWin->mpWindowImpl->mpFrameData->mpNextFrame;
+ }
+ return bRet;
+}
+
+LanguageType Window::GetInputLanguage() const
+{
+ return mpWindowImpl->mpFrame->GetInputLanguage();
+}
+
+void Window::EnableNativeWidget( bool bEnable )
+{
+ static const char* pNoNWF = getenv( "SAL_NO_NWF" );
+ if( pNoNWF && *pNoNWF )
+ bEnable = false;
+
+ if( bEnable != ImplGetWinData()->mbEnableNativeWidget )
+ {
+ ImplGetWinData()->mbEnableNativeWidget = bEnable;
+
+ // send datachanged event to allow for internal changes required for NWF
+ // like clipmode, transparency, etc.
+ DataChangedEvent aDCEvt( DataChangedEventType::SETTINGS, mxSettings.get(), AllSettingsFlags::STYLE );
+ CompatDataChanged( aDCEvt );
+
+ // sometimes the borderwindow is queried, so keep it in sync
+ if( mpWindowImpl->mpBorderWindow )
+ mpWindowImpl->mpBorderWindow->ImplGetWinData()->mbEnableNativeWidget = bEnable;
+ }
+
+ // push down, useful for compound controls
+ VclPtr< vcl::Window > pChild = mpWindowImpl->mpFirstChild;
+ while( pChild )
+ {
+ pChild->EnableNativeWidget( bEnable );
+ pChild = pChild->mpWindowImpl->mpNext;
+ }
+}
+
+bool Window::IsNativeWidgetEnabled() const
+{
+ return ImplGetWinData()->mbEnableNativeWidget;
+}
+
+Reference< css::rendering::XCanvas > Window::ImplGetCanvas( bool bSpriteCanvas ) const
+{
+ // try to retrieve hard reference from weak member
+ Reference< css::rendering::XCanvas > xCanvas( mpWindowImpl->mxCanvas );
+
+ // canvas still valid? Then we're done.
+ if( xCanvas.is() )
+ return xCanvas;
+
+ Sequence< Any > aArg(5);
+
+ // Feed any with operating system's window handle
+
+ // common: first any is VCL pointer to window (for VCL canvas)
+ aArg[ 0 ] <<= reinterpret_cast<sal_Int64>(this);
+ aArg[ 1 ] <<= css::awt::Rectangle( mnOutOffX, mnOutOffY, mnOutWidth, mnOutHeight );
+ aArg[ 2 ] <<= mpWindowImpl->mbAlwaysOnTop;
+ aArg[ 3 ] <<= Reference< css::awt::XWindow >(
+ const_cast<vcl::Window*>(this)->GetComponentInterface(),
+ UNO_QUERY );
+ aArg[ 4 ] = 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());
+
+ 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* >( 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 );
+
+ }
+ mpWindowImpl->mxCanvas = xCanvas;
+ }
+
+ // no factory??? Empty reference, then.
+ return xCanvas;
+}
+
+Reference< css::rendering::XCanvas > Window::GetCanvas() const
+{
+ return ImplGetCanvas( false );
+}
+
+Reference< css::rendering::XSpriteCanvas > Window::GetSpriteCanvas() const
+{
+ Reference< css::rendering::XSpriteCanvas > xSpriteCanvas(
+ ImplGetCanvas( true ), UNO_QUERY );
+ return xSpriteCanvas;
+}
+
+OUString Window::GetSurroundingText() const
+{
+ return OUString();
+}
+
+Selection Window::GetSurroundingTextSelection() const
+{
+ return Selection( 0, 0 );
+}
+
+bool Window::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.
+ */
+#define COMPAT_BODY(method,args) \
+ if (!mpWindowImpl || mpWindowImpl->mbInDispose) \
+ Window::method args; \
+ else \
+ method args;
+
+void Window::CompatGetFocus()
+{
+ COMPAT_BODY(GetFocus,())
+}
+
+void Window::CompatLoseFocus()
+{
+ COMPAT_BODY(LoseFocus,())
+}
+
+void Window::CompatStateChanged( StateChangedType nStateChange )
+{
+ COMPAT_BODY(StateChanged,(nStateChange))
+}
+
+void Window::CompatDataChanged( const DataChangedEvent& rDCEvt )
+{
+ COMPAT_BODY(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
+{
+ return mpWindowImpl->maID;
+}
+
+FactoryFunction Window::GetUITestFactory() const
+{
+ return WindowUIObject::create;
+}
+
+} /* namespace vcl */
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/window2.cxx b/vcl/source/window/window2.cxx
new file mode 100644
index 000000000..6e5bbc372
--- /dev/null
+++ b/vcl/source/window/window2.cxx
@@ -0,0 +1,1984 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <vcl/toolkit/dialog.hxx>
+#include <vcl/event.hxx>
+#include <vcl/fixed.hxx>
+#include <vcl/layout.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/window.hxx>
+#include <vcl/scrbar.hxx>
+#include <vcl/dockwin.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/builder.hxx>
+
+#include <window.h>
+#include <svdata.hxx>
+#include <salgdi.hxx>
+#include <salframe.hxx>
+#include <scrwnd.hxx>
+
+#include <com/sun/star/accessibility/AccessibleRelation.hpp>
+#include <com/sun/star/accessibility/AccessibleRole.hpp>
+
+using namespace com::sun::star;
+
+namespace vcl {
+
+void Window::ShowFocus( const tools::Rectangle& rRect )
+{
+ if( mpWindowImpl->mbInShowFocus )
+ return;
+ mpWindowImpl->mbInShowFocus = true;
+
+ ImplWinData* pWinData = ImplGetWinData();
+
+ // native themeing suggest not to use focus rects
+ if( ! ( mpWindowImpl->mbUseNativeFocus &&
+ IsNativeWidgetEnabled() ) )
+ {
+ if ( !mpWindowImpl->mbInPaint )
+ {
+ if ( mpWindowImpl->mbFocusVisible )
+ {
+ if ( *pWinData->mpFocusRect == rRect )
+ {
+ mpWindowImpl->mbInShowFocus = false;
+ return;
+ }
+
+ ImplInvertFocus( *pWinData->mpFocusRect );
+ }
+
+ ImplInvertFocus( rRect );
+ }
+ pWinData->mpFocusRect = rRect;
+ mpWindowImpl->mbFocusVisible = true;
+ }
+ else
+ {
+ if( ! mpWindowImpl->mbNativeFocusVisible )
+ {
+ mpWindowImpl->mbNativeFocusVisible = true;
+ if ( !mpWindowImpl->mbInPaint )
+ Invalidate();
+ }
+ }
+ mpWindowImpl->mbInShowFocus = false;
+}
+
+void Window::HideFocus()
+{
+
+ if( mpWindowImpl->mbInHideFocus )
+ return;
+ mpWindowImpl->mbInHideFocus = true;
+
+ // native themeing can suggest not to use focus rects
+ if( ! ( mpWindowImpl->mbUseNativeFocus &&
+ IsNativeWidgetEnabled() ) )
+ {
+ if ( !mpWindowImpl->mbFocusVisible )
+ {
+ mpWindowImpl->mbInHideFocus = false;
+ return;
+ }
+
+ if ( !mpWindowImpl->mbInPaint )
+ ImplInvertFocus( *ImplGetWinData()->mpFocusRect );
+ mpWindowImpl->mbFocusVisible = false;
+ }
+ else
+ {
+ if( mpWindowImpl->mbNativeFocusVisible )
+ {
+ mpWindowImpl->mbNativeFocusVisible = false;
+ if ( !mpWindowImpl->mbInPaint )
+ Invalidate();
+ }
+ }
+ mpWindowImpl->mbInHideFocus = false;
+}
+
+void Window::ShowTracking( const tools::Rectangle& rRect, ShowTrackFlags nFlags )
+{
+ ImplWinData* pWinData = ImplGetWinData();
+
+ if ( !mpWindowImpl->mbInPaint || !(nFlags & ShowTrackFlags::TrackWindow) )
+ {
+ if ( mpWindowImpl->mbTrackVisible )
+ {
+ if ( (*pWinData->mpTrackRect == rRect) &&
+ (pWinData->mnTrackFlags == nFlags) )
+ return;
+
+ InvertTracking( *pWinData->mpTrackRect, pWinData->mnTrackFlags );
+ }
+
+ InvertTracking( rRect, nFlags );
+ }
+
+ pWinData->mpTrackRect = rRect;
+ pWinData->mnTrackFlags = nFlags;
+ mpWindowImpl->mbTrackVisible = true;
+}
+
+void Window::HideTracking()
+{
+ if ( mpWindowImpl->mbTrackVisible )
+ {
+ ImplWinData* pWinData = ImplGetWinData();
+ if ( !mpWindowImpl->mbInPaint || !(pWinData->mnTrackFlags & ShowTrackFlags::TrackWindow) )
+ InvertTracking( *pWinData->mpTrackRect, pWinData->mnTrackFlags );
+ mpWindowImpl->mbTrackVisible = false;
+ }
+}
+
+void Window::InvertTracking( const tools::Rectangle& rRect, ShowTrackFlags nFlags )
+{
+ OutputDevice *pOutDev = GetOutDev();
+ tools::Rectangle aRect( pOutDev->ImplLogicToDevicePixel( rRect ) );
+
+ if ( aRect.IsEmpty() )
+ return;
+ aRect.Justify();
+
+ SalGraphics* pGraphics;
+
+ if ( nFlags & ShowTrackFlags::TrackWindow )
+ {
+ if ( !IsDeviceOutputNecessary() )
+ return;
+
+ // we need a graphics
+ if ( !mpGraphics )
+ {
+ if ( !pOutDev->AcquireGraphics() )
+ return;
+ }
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ pGraphics = mpGraphics;
+ }
+ else
+ {
+ pGraphics = ImplGetFrameGraphics();
+
+ if ( nFlags & ShowTrackFlags::Clip )
+ {
+ Point aPoint( mnOutOffX, mnOutOffY );
+ vcl::Region aRegion( tools::Rectangle( aPoint,
+ Size( mnOutWidth, mnOutHeight ) ) );
+ 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, this );
+ else if ( nStyle == ShowTrackFlags::Split )
+ pGraphics->Invert( aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight(), SalInvert::N50, this );
+ else
+ {
+ long nBorder = 1;
+ if ( nStyle == ShowTrackFlags::Big )
+ nBorder = 5;
+ pGraphics->Invert( aRect.Left(), aRect.Top(), aRect.GetWidth(), nBorder, SalInvert::N50, this );
+ pGraphics->Invert( aRect.Left(), aRect.Bottom()-nBorder+1, aRect.GetWidth(), nBorder, SalInvert::N50, this );
+ pGraphics->Invert( aRect.Left(), aRect.Top()+nBorder, nBorder, aRect.GetHeight()-(nBorder*2), SalInvert::N50, this );
+ pGraphics->Invert( aRect.Right()-nBorder+1, aRect.Top()+nBorder, nBorder, aRect.GetHeight()-(nBorder*2), SalInvert::N50, this );
+ }
+}
+
+IMPL_LINK( Window, ImplTrackTimerHdl, Timer*, pTimer, void )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ // if Button-Repeat we have to change the timeout
+ if ( pSVData->mpWinData->mnTrackFlags & StartTrackingFlags::ButtonRepeat )
+ pTimer->SetTimeout( GetSettings().GetMouseSettings().GetButtonRepeat() );
+
+ // create Tracking-Event
+ Point aMousePos( mpWindowImpl->mpFrameData->mnLastMouseX, mpWindowImpl->mpFrameData->mnLastMouseY );
+ if( ImplIsAntiparallel() )
+ {
+ // re-mirror frame pos at pChild
+ const OutputDevice *pOutDev = GetOutDev();
+ pOutDev->ReMirror( aMousePos );
+ }
+ MouseEvent aMEvt( ImplFrameToOutput( aMousePos ),
+ mpWindowImpl->mpFrameData->mnClickCount, MouseEventModifiers::NONE,
+ mpWindowImpl->mpFrameData->mnMouseCode,
+ mpWindowImpl->mpFrameData->mnMouseCode );
+ TrackingEvent aTEvt( aMEvt, TrackingEventFlags::Repeat );
+ Tracking( aTEvt );
+}
+
+void Window::StartTracking( StartTrackingFlags nFlags )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ if ( pSVData->mpWinData->mpTrackWin.get() != this )
+ {
+ if ( pSVData->mpWinData->mpTrackWin )
+ pSVData->mpWinData->mpTrackWin->EndTracking( TrackingEventFlags::Cancel );
+ }
+
+ if ( nFlags & (StartTrackingFlags::ScrollRepeat | StartTrackingFlags::ButtonRepeat) )
+ {
+ pSVData->mpWinData->mpTrackTimer = new AutoTimer;
+
+ 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->SetDebugName( "vcl::Window pSVData->mpWinData->mpTrackTimer" );
+ pSVData->mpWinData->mpTrackTimer->Start();
+ }
+
+ pSVData->mpWinData->mpTrackWin = this;
+ pSVData->mpWinData->mnTrackFlags = nFlags;
+ CaptureMouse();
+}
+
+void Window::EndTracking( TrackingEventFlags nFlags )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ if ( pSVData->mpWinData->mpTrackWin.get() == this )
+ {
+ if ( pSVData->mpWinData->mpTrackTimer )
+ {
+ delete pSVData->mpWinData->mpTrackTimer;
+ pSVData->mpWinData->mpTrackTimer = nullptr;
+ }
+
+ pSVData->mpWinData->mpTrackWin = nullptr;
+ pSVData->mpWinData->mnTrackFlags = StartTrackingFlags::NONE;
+ ReleaseMouse();
+
+ // call EndTracking if required
+ {
+ Point aMousePos( mpWindowImpl->mpFrameData->mnLastMouseX, mpWindowImpl->mpFrameData->mnLastMouseY );
+ if( ImplIsAntiparallel() )
+ {
+ // re-mirror frame pos at pChild
+ const OutputDevice *pOutDev = GetOutDev();
+ pOutDev->ReMirror( aMousePos );
+ }
+
+ MouseEvent aMEvt( ImplFrameToOutput( aMousePos ),
+ mpWindowImpl->mpFrameData->mnClickCount, MouseEventModifiers::NONE,
+ mpWindowImpl->mpFrameData->mnMouseCode,
+ mpWindowImpl->mpFrameData->mnMouseCode );
+ TrackingEvent aTEvt( aMEvt, nFlags | TrackingEventFlags::End );
+ // CompatTracking effectively
+ if (!mpWindowImpl || mpWindowImpl->mbInDispose)
+ return Window::Tracking( aTEvt );
+ else
+ return Tracking( aTEvt );
+ }
+ }
+}
+
+bool Window::IsTracking() const
+{
+ return (ImplGetSVData()->mpWinData->mpTrackWin == this);
+}
+
+void Window::StartAutoScroll( StartAutoScrollFlags nFlags )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ if ( pSVData->mpWinData->mpAutoScrollWin.get() != this )
+ {
+ if ( pSVData->mpWinData->mpAutoScrollWin )
+ pSVData->mpWinData->mpAutoScrollWin->EndAutoScroll();
+ }
+
+ pSVData->mpWinData->mpAutoScrollWin = this;
+ pSVData->mpWinData->mnAutoScrollFlags = nFlags;
+ pSVData->maAppData.mpWheelWindow = VclPtr<ImplWheelWindow>::Create( this );
+}
+
+void Window::EndAutoScroll()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ if ( pSVData->mpWinData->mpAutoScrollWin.get() == this )
+ {
+ pSVData->mpWinData->mpAutoScrollWin = nullptr;
+ pSVData->mpWinData->mnAutoScrollFlags = StartAutoScrollFlags::NONE;
+ pSVData->maAppData.mpWheelWindow->ImplStop();
+ pSVData->maAppData.mpWheelWindow->SetParentToDefaultWindow();
+ 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 );
+ }
+}
+
+static long WinFloatRound( double fVal )
+{
+ return( fVal > 0.0 ? static_cast<long>( fVal + 0.5 ) : -static_cast<long>( -fVal + 0.5 ) );
+}
+
+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( WinFloatRound(double(aSize.Width() * rZoom)) );
+ aSize.setHeight( WinFloatRound(double(aSize.Height() * rZoom)) );
+ aFont.SetFontSize(aSize);
+ SetPointFont(rRenderContext, aFont);
+ }
+ else
+ {
+ SetPointFont(rRenderContext, rFont);
+ }
+}
+
+long Window::CalcZoom( long nCalc ) const
+{
+
+ const Fraction& rZoom = GetZoom();
+ if ( rZoom.GetNumerator() != rZoom.GetDenominator() )
+ {
+ double n = double(nCalc * rZoom);
+ nCalc = WinFloatRound( 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.reset( new vcl::Font(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.GetTransparency())
+ {
+ 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.GetTransparency())
+ {
+ 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(*const_cast<Window*>(this));
+ Size aFontSize = aFont.GetFontSize();
+ MapMode aPtMapMode(MapUnit::MapPoint);
+ aFontSize = pDev->LogicToPixel( aFontSize, aPtMapMode );
+ aFont.SetFontSize( aFontSize );
+ return aFont;
+}
+
+long Window::GetDrawPixel( OutputDevice const * pDev, long nPixels ) const
+{
+ 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;
+}
+
+static void lcl_HandleScrollHelper( ScrollBar* pScrl, double nN, bool isMultiplyByLineSize )
+{
+ if ( pScrl && nN && pScrl->IsEnabled() && pScrl->IsInputEnabled() && ! pScrl->IsInModalMode() )
+ {
+ long nNewPos = pScrl->GetThumbPos();
+
+ 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();
+ }
+
+ const double fVal = nNewPos - nN;
+
+ if ( !o3tl::convertsToAtLeast(fVal, LONG_MIN) )
+ nNewPos = LONG_MIN;
+ else if ( !o3tl::convertsToAtMost(fVal, LONG_MAX) )
+ nNewPos = LONG_MAX;
+ else
+ nNewPos = static_cast<long>(fVal);
+ }
+
+ pScrl->DoScroll( nNewPos );
+ }
+
+}
+
+bool Window::HandleScrollCommand( const CommandEvent& rCmd,
+ ScrollBar* pHScrl, ScrollBar* pVScrl )
+{
+ bool bRet = false;
+
+ if ( pHScrl || pVScrl )
+ {
+ switch( rCmd.GetCommand() )
+ {
+ case CommandEventId::StartAutoScroll:
+ {
+ StartAutoScrollFlags nFlags = StartAutoScrollFlags::NONE;
+ if ( pHScrl )
+ {
+ if ( (pHScrl->GetVisibleSize() < pHScrl->GetRangeMax()) &&
+ pHScrl->IsEnabled() && pHScrl->IsInputEnabled() && ! pHScrl->IsInModalMode() )
+ nFlags |= StartAutoScrollFlags::Horz;
+ }
+ if ( pVScrl )
+ {
+ if ( (pVScrl->GetVisibleSize() < pVScrl->GetRangeMax()) &&
+ pVScrl->IsEnabled() && pVScrl->IsInputEnabled() && ! pVScrl->IsInModalMode() )
+ nFlags |= StartAutoScrollFlags::Vert;
+ }
+
+ if ( nFlags != StartAutoScrollFlags::NONE )
+ {
+ StartAutoScroll( nFlags );
+ bRet = true;
+ }
+ }
+ break;
+
+ case CommandEventId::Wheel:
+ {
+ const CommandWheelData* pData = rCmd.GetWheelData();
+
+ if ( pData && (CommandWheelMode::SCROLL == pData->GetMode()) )
+ {
+ if (!pData->IsDeltaPixel())
+ {
+ double nScrollLines = pData->GetScrollLines();
+ double nLines;
+ if ( nScrollLines == COMMAND_WHEEL_PAGESCROLL )
+ {
+ if ( pData->GetDelta() < 0 )
+ nLines = double(-LONG_MAX);
+ else
+ nLines = double(LONG_MAX);
+ }
+ else
+ nLines = pData->GetNotchDelta() * nScrollLines;
+ if ( nLines )
+ {
+ ImplHandleScroll( nullptr,
+ 0L,
+ pData->IsHorz() ? pHScrl : pVScrl,
+ nLines );
+ 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());
+ long deltaXInLogic = 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
+ 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());
+ long deltaYInLogic = 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
+ long lineSizeY = pVScrl->GetLineSize();
+ if(lineSizeY)
+ {
+ deltaYInLogic /= lineSizeY;
+ }
+ else
+ {
+ deltaYInLogic = 0;
+ }
+#endif
+ if ( deltaYInLogic )
+ {
+#ifndef IOS
+ bool const isMultiplyByLineSize = true;
+#else
+ bool const isMultiplyByLineSize = false;
+#endif
+ lcl_HandleScrollHelper( pVScrl, deltaYInLogic, isMultiplyByLineSize );
+
+ bRet = true;
+ }
+ }
+ }
+ }
+ }
+ break;
+
+ case CommandEventId::Gesture:
+ {
+ if (pVScrl)
+ {
+ const CommandGestureData* pData = rCmd.GetGestureData();
+ if (pData->meEventType == GestureEventType::PanningBegin)
+ {
+ mpWindowImpl->mpFrameData->mnTouchPanPosition = pVScrl->GetThumbPos();
+ }
+ else if(pData->meEventType == GestureEventType::PanningUpdate)
+ {
+ long nOriginalPosition = mpWindowImpl->mpFrameData->mnTouchPanPosition;
+ pVScrl->DoScroll(nOriginalPosition + (pData->mfOffset / pVScrl->GetVisibleSize()));
+ }
+ if (pData->meEventType == GestureEventType::PanningEnd)
+ {
+ mpWindowImpl->mpFrameData->mnTouchPanPosition = -1;
+ }
+ bRet = true;
+ }
+ break;
+ }
+
+ case CommandEventId::AutoScroll:
+ {
+ const CommandScrollData* pData = rCmd.GetAutoScrollData();
+ if ( pData && (pData->GetDeltaX() || pData->GetDeltaY()) )
+ {
+ ImplHandleScroll( pHScrl, pData->GetDeltaX(),
+ pVScrl, pData->GetDeltaY() );
+ bRet = true;
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return bRet;
+}
+
+// Note that when called for CommandEventId::Wheel above, despite its name,
+// pVScrl isn't necessarily the vertical scroll bar. Depending on
+// whether the scroll is horizontal or vertical, it is either the
+// horizontal or vertical scroll bar. nY is correspondingly either
+// the horizontal or vertical scroll amount.
+
+void Window::ImplHandleScroll( ScrollBar* pHScrl, double nX,
+ ScrollBar* pVScrl, double nY )
+{
+ lcl_HandleScrollHelper( pHScrl, nX, true );
+ lcl_HandleScrollHelper( pVScrl, nY, true );
+}
+
+DockingManager* Window::GetDockingManager()
+{
+ return ImplGetDockingManager();
+}
+
+void Window::EnableDocking( bool bEnable )
+{
+ // update list of dockable windows
+ if( bEnable )
+ ImplGetDockingManager()->AddWindow( this );
+ else
+ ImplGetDockingManager()->RemoveWindow( this );
+}
+
+// retrieves the list of owner draw decorated windows for this window hierarchy
+::std::vector<VclPtr<vcl::Window> >& Window::ImplGetOwnerDrawList()
+{
+ return ImplGetTopmostFrameWindow()->mpWindowImpl->mpFrameData->maOwnerDrawList;
+}
+
+void Window::SetHelpId( const OString& rHelpId )
+{
+ mpWindowImpl->maHelpId = rHelpId;
+}
+
+const OString& Window::GetHelpId() const
+{
+ return mpWindowImpl->maHelpId;
+}
+
+// --------- old inline methods ---------------
+
+vcl::Window* Window::ImplGetWindow() const
+{
+ if ( mpWindowImpl->mpClientWindow )
+ return mpWindowImpl->mpClientWindow;
+ else
+ return const_cast<vcl::Window*>(this);
+}
+
+ImplFrameData* Window::ImplGetFrameData()
+{
+ return mpWindowImpl ? mpWindowImpl->mpFrameData : nullptr;
+}
+
+SalFrame* Window::ImplGetFrame() const
+{
+ return mpWindowImpl ? mpWindowImpl->mpFrame : nullptr;
+}
+
+weld::Window* Window::GetFrameWeld() const
+{
+ SalFrame* pFrame = ImplGetFrame();
+ return pFrame ? pFrame->GetFrameWeld() : nullptr;
+}
+
+vcl::Window* Window::GetFrameWindow() const
+{
+ SalFrame* pFrame = ImplGetFrame();
+ return pFrame ? pFrame->GetWindow() : nullptr;
+}
+
+vcl::Window* Window::ImplGetParent() const
+{
+ return mpWindowImpl ? mpWindowImpl->mpParent.get() : nullptr;
+}
+
+vcl::Window* Window::ImplGetClientWindow() const
+{
+ return mpWindowImpl ? mpWindowImpl->mpClientWindow.get() : nullptr;
+}
+
+vcl::Window* Window::ImplGetBorderWindow() const
+{
+ return mpWindowImpl ? mpWindowImpl->mpBorderWindow.get() : nullptr;
+}
+
+vcl::Window* Window::ImplGetFirstOverlapWindow()
+{
+ if (!mpWindowImpl)
+ {
+ return nullptr;
+ }
+
+ if ( mpWindowImpl->mbOverlapWin )
+ return this;
+ else
+ return mpWindowImpl->mpOverlapWindow;
+}
+
+const vcl::Window* Window::ImplGetFirstOverlapWindow() const
+{
+ if ( mpWindowImpl->mbOverlapWin )
+ return this;
+ else
+ return mpWindowImpl->mpOverlapWindow;
+}
+
+vcl::Window* Window::ImplGetFrameWindow() const
+{
+ return mpWindowImpl ? mpWindowImpl->mpFrameWindow.get() : nullptr;
+}
+
+bool Window::IsDockingWindow() const
+{
+ return mpWindowImpl && mpWindowImpl->mbDockWin;
+}
+
+bool Window::ImplIsFloatingWindow() const
+{
+ return mpWindowImpl && mpWindowImpl->mbFloatWin;
+}
+
+bool Window::ImplIsSplitter() const
+{
+ return mpWindowImpl && mpWindowImpl->mbSplitter;
+}
+
+bool Window::ImplIsPushButton() const
+{
+ return mpWindowImpl && mpWindowImpl->mbPushButton;
+}
+
+bool Window::ImplIsOverlapWindow() const
+{
+ return mpWindowImpl && mpWindowImpl->mbOverlapWin;
+}
+
+void Window::ImplSetMouseTransparent( bool bTransparent )
+{
+ if (mpWindowImpl)
+ mpWindowImpl->mbMouseTransparent = bTransparent;
+}
+
+Point Window::ImplOutputToFrame( const Point& rPos )
+{
+ return Point( rPos.X()+mnOutOffX, rPos.Y()+mnOutOffY );
+}
+
+Point Window::ImplFrameToOutput( const Point& rPos )
+{
+ return Point( rPos.X()-mnOutOffX, rPos.Y()-mnOutOffY );
+}
+
+void Window::SetCompoundControl( bool bCompound )
+{
+ if (mpWindowImpl)
+ mpWindowImpl->mbCompoundControl = bCompound;
+}
+
+WinBits Window::GetStyle() const
+{
+ return mpWindowImpl ? mpWindowImpl->mnStyle : 0;
+}
+
+WinBits Window::GetPrevStyle() const
+{
+ return mpWindowImpl ? mpWindowImpl->mnPrevStyle : 0;
+}
+
+WindowExtendedStyle Window::GetExtendedStyle() const
+{
+ return mpWindowImpl ? mpWindowImpl->mnExtendedStyle : WindowExtendedStyle::NONE;
+}
+
+void Window::SetType( WindowType nType )
+{
+ if (mpWindowImpl)
+ mpWindowImpl->mnType = nType;
+}
+
+WindowType Window::GetType() const
+{
+ if (mpWindowImpl)
+ return mpWindowImpl->mnType;
+ else
+ return WindowType::NONE;
+}
+
+Dialog* Window::GetParentDialog() const
+{
+ const vcl::Window *pWindow = this;
+
+ while( pWindow )
+ {
+ if( pWindow->IsDialog() )
+ break;
+
+ pWindow = pWindow->GetParent();
+ }
+
+ return const_cast<Dialog *>(dynamic_cast<const Dialog*>(pWindow));
+}
+
+bool Window::IsSystemWindow() const
+{
+ return mpWindowImpl && mpWindowImpl->mbSysWin;
+}
+
+bool Window::IsDialog() const
+{
+ return mpWindowImpl && mpWindowImpl->mbDialog;
+}
+
+bool Window::IsMenuFloatingWindow() const
+{
+ return mpWindowImpl && mpWindowImpl->mbMenuFloatingWindow;
+}
+
+bool Window::IsToolbarFloatingWindow() const
+{
+ return mpWindowImpl && mpWindowImpl->mbToolbarFloatingWindow;
+}
+
+void Window::EnableAllResize()
+{
+ mpWindowImpl->mbAllResize = true;
+}
+
+void Window::EnableChildTransparentMode( bool bEnable )
+{
+ mpWindowImpl->mbChildTransparent = bEnable;
+}
+
+bool Window::IsChildTransparentModeEnabled() const
+{
+ return mpWindowImpl && mpWindowImpl->mbChildTransparent;
+}
+
+bool Window::IsMouseTransparent() const
+{
+ return mpWindowImpl && mpWindowImpl->mbMouseTransparent;
+}
+
+bool Window::IsPaintTransparent() const
+{
+ return mpWindowImpl && mpWindowImpl->mbPaintTransparent;
+}
+
+void Window::SetDialogControlStart( bool bStart )
+{
+ mpWindowImpl->mbDlgCtrlStart = bStart;
+}
+
+bool Window::IsDialogControlStart() const
+{
+ return mpWindowImpl && mpWindowImpl->mbDlgCtrlStart;
+}
+
+void Window::SetDialogControlFlags( DialogControlFlags nFlags )
+{
+ mpWindowImpl->mnDlgCtrlFlags = nFlags;
+}
+
+DialogControlFlags Window::GetDialogControlFlags() const
+{
+ return mpWindowImpl->mnDlgCtrlFlags;
+}
+
+const InputContext& Window::GetInputContext() const
+{
+ return mpWindowImpl->maInputContext;
+}
+
+bool Window::IsControlFont() const
+{
+ return bool(mpWindowImpl->mpControlFont);
+}
+
+const Color& Window::GetControlForeground() const
+{
+ return mpWindowImpl->maControlForeground;
+}
+
+bool Window::IsControlForeground() const
+{
+ return mpWindowImpl->mbControlForeground;
+}
+
+const Color& Window::GetControlBackground() const
+{
+ return mpWindowImpl->maControlBackground;
+}
+
+bool Window::IsControlBackground() const
+{
+ return mpWindowImpl->mbControlBackground;
+}
+
+bool Window::IsInPaint() const
+{
+ return mpWindowImpl && mpWindowImpl->mbInPaint;
+}
+
+vcl::Window* Window::GetParent() const
+{
+ return mpWindowImpl ? mpWindowImpl->mpRealParent.get() : nullptr;
+}
+
+bool Window::IsVisible() const
+{
+ return mpWindowImpl && mpWindowImpl->mbVisible;
+}
+
+bool Window::IsReallyVisible() const
+{
+ return mpWindowImpl && mpWindowImpl->mbReallyVisible;
+}
+
+bool Window::IsReallyShown() const
+{
+ return mpWindowImpl && mpWindowImpl->mbReallyShown;
+}
+
+bool Window::IsInInitShow() const
+{
+ return mpWindowImpl->mbInInitShow;
+}
+
+bool Window::IsEnabled() const
+{
+ return mpWindowImpl && !mpWindowImpl->mbDisabled;
+}
+
+bool Window::IsInputEnabled() const
+{
+ return mpWindowImpl && !mpWindowImpl->mbInputDisabled;
+}
+
+bool Window::IsAlwaysEnableInput() const
+{
+ return mpWindowImpl->meAlwaysInputMode == AlwaysInputEnabled;
+}
+
+ActivateModeFlags Window::GetActivateMode() const
+{
+ return mpWindowImpl->mnActivateMode;
+
+}
+
+bool Window::IsAlwaysOnTopEnabled() const
+{
+ return mpWindowImpl->mbAlwaysOnTop;
+}
+
+bool Window::IsDefaultPos() const
+{
+ return mpWindowImpl->mbDefPos;
+}
+
+bool Window::IsDefaultSize() const
+{
+ return mpWindowImpl->mbDefSize;
+}
+
+Point Window::GetOffsetPixelFrom(const vcl::Window& rWindow) const
+{
+ return Point(GetOutOffXPixel() - rWindow.GetOutOffXPixel(), GetOutOffYPixel() - rWindow.GetOutOffYPixel());
+}
+
+void Window::EnablePaint( bool bEnable )
+{
+ mpWindowImpl->mbPaintDisabled = !bEnable;
+}
+
+bool Window::IsPaintEnabled() const
+{
+ return !mpWindowImpl->mbPaintDisabled;
+}
+
+bool Window::IsUpdateMode() const
+{
+ return !mpWindowImpl->mbNoUpdate;
+}
+
+void Window::SetParentUpdateMode( bool bUpdate )
+{
+ mpWindowImpl->mbNoParentUpdate = !bUpdate;
+}
+
+bool Window::IsActive() const
+{
+ return mpWindowImpl->mbActive;
+}
+
+GetFocusFlags Window::GetGetFocusFlags() const
+{
+ return mpWindowImpl->mnGetFocusFlags;
+}
+
+bool Window::IsCompoundControl() const
+{
+ return mpWindowImpl->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;
+}
+
+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 && !mpWindowImpl->mbInDispose)
+ {
+ //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() && GetParentDialog() && !pParent->IsInInitShow())
+ LogicInvalidate(nullptr);
+ }
+}
+
+namespace
+{
+ VclAlign toAlign(const OUString &rValue)
+ {
+ VclAlign eRet = VclAlign::Fill;
+
+ if (rValue == "fill")
+ eRet = VclAlign::Fill;
+ else if (rValue == "start")
+ eRet = VclAlign::Start;
+ else if (rValue == "end")
+ eRet = VclAlign::End;
+ else if (rValue == "center")
+ eRet = VclAlign::Center;
+ return eRet;
+ }
+}
+
+bool Window::set_font_attribute(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "weight")
+ {
+ vcl::Font aFont(GetControlFont());
+ if (rValue == "thin")
+ aFont.SetWeight(WEIGHT_THIN);
+ else if (rValue == "ultralight")
+ aFont.SetWeight(WEIGHT_ULTRALIGHT);
+ else if (rValue == "light")
+ aFont.SetWeight(WEIGHT_LIGHT);
+ else if (rValue == "book")
+ aFont.SetWeight(WEIGHT_SEMILIGHT);
+ else if (rValue == "normal")
+ aFont.SetWeight(WEIGHT_NORMAL);
+ else if (rValue == "medium")
+ aFont.SetWeight(WEIGHT_MEDIUM);
+ else if (rValue == "semibold")
+ aFont.SetWeight(WEIGHT_SEMIBOLD);
+ else if (rValue == "bold")
+ aFont.SetWeight(WEIGHT_BOLD);
+ else if (rValue == "ultrabold")
+ aFont.SetWeight(WEIGHT_ULTRABOLD);
+ else
+ aFont.SetWeight(WEIGHT_BLACK);
+ SetControlFont(aFont);
+ }
+ else if (rKey == "style")
+ {
+ vcl::Font aFont(GetControlFont());
+ if (rValue == "normal")
+ aFont.SetItalic(ITALIC_NONE);
+ else if (rValue == "oblique")
+ aFont.SetItalic(ITALIC_OBLIQUE);
+ else if (rValue == "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 == "size")
+ {
+ vcl::Font aFont(GetControlFont());
+ sal_Int32 nHeight = rValue.toInt32() / 1000;
+ aFont.SetFontHeight(nHeight);
+ SetControlFont(aFont);
+ }
+ else
+ {
+ SAL_INFO("vcl.layout", "unhandled font attribute: " << rKey);
+ return false;
+ }
+ return true;
+}
+
+bool Window::set_property(const OString &rKey, const OUString &rValue)
+{
+ if ((rKey == "label") || (rKey == "title") || (rKey == "text") )
+ {
+ SetText(BuilderUtils::convertMnemonicMarkup(rValue));
+ }
+ else if (rKey == "visible")
+ Show(toBool(rValue));
+ else if (rKey == "sensitive")
+ Enable(toBool(rValue));
+ else if (rKey == "resizable")
+ {
+ WinBits nBits = GetStyle();
+ nBits &= ~WB_SIZEABLE;
+ if (toBool(rValue))
+ nBits |= WB_SIZEABLE;
+ SetStyle(nBits);
+ }
+ else if (rKey == "xalign")
+ {
+ WinBits nBits = GetStyle();
+ nBits &= ~(WB_LEFT | WB_CENTER | WB_RIGHT);
+
+ float f = rValue.toFloat();
+ 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();
+ if (f == 0.0)
+ nBits |= WB_TOP;
+ else if (f == 1.0)
+ nBits |= WB_BOTTOM;
+ else if (f == 0.5)
+ nBits |= WB_CENTER;
+
+ 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-left")
+ set_margin_left(rValue.toInt32());
+ else if (rKey == "margin-right")
+ set_margin_right(rValue.toInt32());
+ else if (rKey == "margin-top")
+ set_margin_top(rValue.toInt32());
+ else if (rKey == "margin-bottom")
+ set_margin_bottom(rValue.toInt32());
+ else if (rKey == "hscrollbar-policy")
+ {
+ WinBits nBits = GetStyle();
+ nBits &= ~(WB_AUTOHSCROLL|WB_HSCROLL);
+ if (rValue == "always")
+ nBits |= WB_HSCROLL;
+ else if (rValue == "automatic")
+ nBits |= WB_AUTOHSCROLL;
+ SetStyle(nBits);
+ }
+ else if (rKey == "vscrollbar-policy")
+ {
+ WinBits nBits = GetStyle();
+ nBits &= ~(WB_AUTOVSCROLL|WB_VSCROLL);
+ if (rValue == "always")
+ nBits |= WB_VSCROLL;
+ else if (rValue == "automatic")
+ nBits |= WB_AUTOVSCROLL;
+ SetStyle(nBits);
+ }
+ else if (rKey == "accessible-name")
+ {
+ SetAccessibleName(rValue);
+ }
+ else if (rKey == "accessible-description")
+ {
+ SetAccessibleDescription(rValue);
+ }
+ else if (rKey == "accessible-role")
+ {
+ sal_Int16 role = BuilderUtils::getRoleFromName(rValue.toUtf8());
+ if (role != com::sun::star::accessibility::AccessibleRole::UNKNOWN)
+ SetAccessibleRole(role);
+ }
+ else if (rKey == "use-markup")
+ {
+ //https://live.gnome.org/GnomeGoals/RemoveMarkupInMessages
+ SAL_WARN_IF(toBool(rValue), "vcl.layout", "Use pango attributes instead of mark-up");
+ }
+ else if (rKey == "has-focus")
+ {
+ if (toBool(rValue))
+ GrabFocus();
+ }
+ else if (rKey == "can-focus")
+ {
+ WinBits nBits = GetStyle();
+ nBits &= ~(WB_TABSTOP|WB_NOTABSTOP);
+ if (toBool(rValue))
+ nBits |= WB_TABSTOP;
+ else
+ nBits |= WB_NOTABSTOP;
+ SetStyle(nBits);
+ }
+ else
+ {
+ SAL_INFO("vcl.layout", "unhandled property: " << rKey);
+ return false;
+ }
+ return true;
+}
+
+void Window::set_height_request(sal_Int32 nHeightRequest)
+{
+ if (!mpWindowImpl)
+ return;
+
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+
+ if ( pWindowImpl->mnHeightRequest != nHeightRequest )
+ {
+ pWindowImpl->mnHeightRequest = nHeightRequest;
+ queue_resize();
+ }
+}
+
+void Window::set_width_request(sal_Int32 nWidthRequest)
+{
+ if (!mpWindowImpl)
+ return;
+
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+
+ if ( pWindowImpl->mnWidthRequest != nWidthRequest )
+ {
+ pWindowImpl->mnWidthRequest = nWidthRequest;
+ queue_resize();
+ }
+}
+
+Size Window::get_ungrouped_preferred_size() const
+{
+ Size aRet(get_width_request(), get_height_request());
+ if (aRet.Width() == -1 || aRet.Height() == -1)
+ {
+ //cache gets blown away by queue_resize
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ if (pWindowImpl->mnOptimalWidthCache == -1 || pWindowImpl->mnOptimalHeightCache == -1)
+ {
+ Size aOptimal(GetOptimalSize());
+ pWindowImpl->mnOptimalWidthCache = aOptimal.Width();
+ pWindowImpl->mnOptimalHeightCache = aOptimal.Height();
+ }
+
+ if (aRet.Width() == -1)
+ aRet.setWidth( pWindowImpl->mnOptimalWidthCache );
+ if (aRet.Height() == -1)
+ aRet.setHeight( pWindowImpl->mnOptimalHeightCache );
+ }
+ return aRet;
+}
+
+Size Window::get_preferred_size() const
+{
+ Size aRet(get_ungrouped_preferred_size());
+
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ if (pWindowImpl->m_xSizeGroup)
+ {
+ const VclSizeGroupMode eMode = pWindowImpl->m_xSizeGroup->get_mode();
+ if (eMode != VclSizeGroupMode::NONE)
+ {
+ const bool bIgnoreInHidden = pWindowImpl->m_xSizeGroup->get_ignore_hidden();
+ const std::set<VclPtr<vcl::Window> > &rWindows = pWindowImpl->m_xSizeGroup->get_widgets();
+ for (auto const& window : rWindows)
+ {
+ const vcl::Window *pOther = window;
+ if (pOther == this)
+ continue;
+ if (bIgnoreInHidden && !pOther->IsVisible())
+ continue;
+ Size aOtherSize = pOther->get_ungrouped_preferred_size();
+ if (eMode == VclSizeGroupMode::Both || eMode == VclSizeGroupMode::Horizontal)
+ aRet.setWidth( std::max(aRet.Width(), aOtherSize.Width()) );
+ if (eMode == VclSizeGroupMode::Both || eMode == VclSizeGroupMode::Vertical)
+ aRet.setHeight( std::max(aRet.Height(), aOtherSize.Height()) );
+ }
+ }
+ }
+
+ return aRet;
+}
+
+VclAlign Window::get_halign() const
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ return pWindowImpl->meHalign;
+}
+
+void Window::set_halign(VclAlign eAlign)
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ pWindowImpl->meHalign = eAlign;
+}
+
+VclAlign Window::get_valign() const
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ return pWindowImpl->meValign;
+}
+
+void Window::set_valign(VclAlign eAlign)
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ pWindowImpl->meValign = eAlign;
+}
+
+bool Window::get_hexpand() const
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ return pWindowImpl->mbHexpand;
+}
+
+void Window::set_hexpand(bool bExpand)
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ pWindowImpl->mbHexpand = bExpand;
+}
+
+bool Window::get_vexpand() const
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ return pWindowImpl->mbVexpand;
+}
+
+void Window::set_vexpand(bool bExpand)
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ pWindowImpl->mbVexpand = bExpand;
+}
+
+bool Window::get_expand() const
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ return pWindowImpl->mbExpand;
+}
+
+void Window::set_expand(bool bExpand)
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ pWindowImpl->mbExpand = bExpand;
+}
+
+VclPackType Window::get_pack_type() const
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ return pWindowImpl->mePackType;
+}
+
+void Window::set_pack_type(VclPackType ePackType)
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ pWindowImpl->mePackType = ePackType;
+}
+
+sal_Int32 Window::get_padding() const
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ return pWindowImpl->mnPadding;
+}
+
+void Window::set_padding(sal_Int32 nPadding)
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ pWindowImpl->mnPadding = nPadding;
+}
+
+bool Window::get_fill() const
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ return pWindowImpl->mbFill;
+}
+
+void Window::set_fill(bool bFill)
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ pWindowImpl->mbFill = bFill;
+}
+
+sal_Int32 Window::get_grid_width() const
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ return pWindowImpl->mnGridWidth;
+}
+
+void Window::set_grid_width(sal_Int32 nCols)
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ pWindowImpl->mnGridWidth = nCols;
+}
+
+sal_Int32 Window::get_grid_left_attach() const
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ return pWindowImpl->mnGridLeftAttach;
+}
+
+void Window::set_grid_left_attach(sal_Int32 nAttach)
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ pWindowImpl->mnGridLeftAttach = nAttach;
+}
+
+sal_Int32 Window::get_grid_height() const
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ return pWindowImpl->mnGridHeight;
+}
+
+void Window::set_grid_height(sal_Int32 nRows)
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ pWindowImpl->mnGridHeight = nRows;
+}
+
+sal_Int32 Window::get_grid_top_attach() const
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ return pWindowImpl->mnGridTopAttach;
+}
+
+void Window::set_grid_top_attach(sal_Int32 nAttach)
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ pWindowImpl->mnGridTopAttach = nAttach;
+}
+
+void Window::set_border_width(sal_Int32 nBorderWidth)
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ pWindowImpl->mnBorderWidth = nBorderWidth;
+}
+
+sal_Int32 Window::get_border_width() const
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ return pWindowImpl->mnBorderWidth;
+}
+
+void Window::set_margin_left(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_left() const
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ return pWindowImpl->mnMarginLeft;
+}
+
+void Window::set_margin_right(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_right() 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 DrawFocusRect(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ const int nBorder = 1;
+ rRenderContext.Invert(tools::Rectangle(Point(rRect.Left(), rRect.Top()), Size(rRect.GetWidth(), nBorder)), InvertFlags::N50);
+ rRenderContext.Invert(tools::Rectangle(Point(rRect.Left(), rRect.Bottom()-nBorder+1), Size(rRect.GetWidth(), nBorder)), InvertFlags::N50);
+ rRenderContext.Invert(tools::Rectangle(Point(rRect.Left(), rRect.Top()+nBorder), Size(nBorder, rRect.GetHeight()-(nBorder*2))), InvertFlags::N50);
+ rRenderContext.Invert(tools::Rectangle(Point(rRect.Right()-nBorder+1, rRect.Top()+nBorder), Size(nBorder, rRect.GetHeight()-(nBorder*2))), InvertFlags::N50);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/window3.cxx b/vcl/source/window/window3.cxx
new file mode 100644
index 000000000..c99d085f4
--- /dev/null
+++ b/vcl/source/window/window3.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/window.hxx>
+#include <vcl/waitobj.hxx>
+#include <window.h>
+#include <vcl/cursor.hxx>
+
+WaitObject::~WaitObject()
+{
+ if ( mpWindow )
+ mpWindow->LeaveWait();
+}
+
+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 Window::ImplClearFontData(bool bNewFontLists)
+{
+ OutputDevice::ImplClearFontData(bNewFontLists);
+ for (Window* pChild = mpWindowImpl->mpFirstChild; pChild; pChild = pChild->mpWindowImpl->mpNext)
+ pChild->ImplClearFontData(bNewFontLists);
+}
+
+void Window::ImplRefreshFontData(bool bNewFontLists)
+{
+ OutputDevice::ImplRefreshFontData(bNewFontLists);
+ for (Window* pChild = mpWindowImpl->mpFirstChild; pChild; pChild = pChild->mpWindowImpl->mpNext)
+ pChild->ImplRefreshFontData(bNewFontLists);
+}
+
+void Window::ImplInitMapModeObjects()
+{
+ OutputDevice::ImplInitMapModeObjects();
+ if (mpWindowImpl->mpCursor)
+ mpWindowImpl->mpCursor->ImplNew();
+}
+
+} /* namespace vcl */
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/winproc.cxx b/vcl/source/window/winproc.cxx
new file mode 100644
index 000000000..2c77f7069
--- /dev/null
+++ b/vcl/source/window/winproc.cxx
@@ -0,0 +1,2634 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <comphelper/lok.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/event.hxx>
+#include <vcl/GestureEvent.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/cursor.hxx>
+#include <vcl/wrkwin.hxx>
+#include <vcl/floatwin.hxx>
+#include <vcl/toolkit/dialog.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/XDragSource.hpp>
+#include <com/sun/star/awt/MouseEvent.hpp>
+
+#define IMPL_MIN_NEEDSYSWIN 49
+
+bool ImplCallPreNotify( NotifyEvent& rEvt )
+{
+ return rEvt.GetWindow()->CompatPreNotify( rEvt );
+}
+
+static bool ImplHandleMouseFloatMode( vcl::Window* pChild, const Point& rMousePos,
+ sal_uInt16 nCode, MouseNotifyEvent nSVEvent,
+ bool bMouseLeave )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ if (pSVData->mpWinData->mpFirstFloat && !pSVData->mpWinData->mpCaptureWin
+ && !pSVData->mpWinData->mpFirstFloat->ImplIsFloatPopupModeWindow(pChild))
+ {
+ /*
+ * #93895# since floats are system windows, coordinates have
+ * to be converted to float relative for the hittest
+ */
+ bool bHitTestInsideRect = false;
+ FloatingWindow* pFloat = pSVData->mpWinData->mpFirstFloat->ImplFloatHitTest( pChild, rMousePos, bHitTestInsideRect );
+ FloatingWindow* pLastLevelFloat;
+ FloatWinPopupFlags nPopupFlags;
+ if ( nSVEvent == MouseNotifyEvent::MOUSEMOVE )
+ {
+ if ( bMouseLeave )
+ return true;
+
+ if ( !pFloat || bHitTestInsideRect )
+ {
+ if ( ImplGetSVHelpData().mpHelpWin && !ImplGetSVHelpData().mbKeyboardHelp )
+ ImplDestroyHelpWindow( true );
+ pChild->ImplGetFrame()->SetPointer( PointerStyle::Arrow );
+ return true;
+ }
+ }
+ else
+ {
+ if ( nCode & MOUSE_LEFT )
+ {
+ if ( nSVEvent == MouseNotifyEvent::MOUSEBUTTONDOWN )
+ {
+ if ( !pFloat )
+ {
+ 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
+ {
+ pLastLevelFloat = pSVData->mpWinData->mpFirstFloat->ImplFindLastLevelFloat();
+ nPopupFlags = pLastLevelFloat->GetPopupModeFlags();
+ if ( !(nPopupFlags & FloatWinPopupFlags::NoMouseUpClose) )
+ {
+ pLastLevelFloat->EndPopupMode( FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll );
+ return true;
+ }
+ }
+ }
+ }
+ else
+ {
+ if ( !pFloat )
+ {
+ pLastLevelFloat = pSVData->mpWinData->mpFirstFloat->ImplFindLastLevelFloat();
+ nPopupFlags = pLastLevelFloat->GetPopupModeFlags();
+ if ( nPopupFlags & FloatWinPopupFlags::AllMouseButtonClose )
+ {
+ if ( (nPopupFlags & FloatWinPopupFlags::NoMouseUpClose) &&
+ (nSVEvent == MouseNotifyEvent::MOUSEBUTTONUP) )
+ return true;
+ pLastLevelFloat->EndPopupMode( FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll );
+ return true;
+ }
+ else
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+static void ImplHandleMouseHelpRequest( vcl::Window* pChild, const Point& rMousePos )
+{
+ ImplSVHelpData& aHelpData = ImplGetSVHelpData();
+ if ( !aHelpData.mpHelpWin ||
+ !( aHelpData.mpHelpWin->IsWindowOrChild( pChild ) ||
+ pChild->IsWindowOrChild( aHelpData.mpHelpWin ) ) )
+ {
+ HelpEventMode nHelpMode = HelpEventMode::NONE;
+ if ( aHelpData.mbQuickHelp )
+ nHelpMode = HelpEventMode::QUICK;
+ if ( aHelpData.mbBalloonHelp )
+ nHelpMode |= HelpEventMode::BALLOON;
+ if ( bool(nHelpMode) )
+ {
+ if ( pChild->IsInputEnabled() && !pChild->IsInModalMode() )
+ {
+ HelpEvent aHelpEvent( rMousePos, nHelpMode );
+ aHelpData.mbRequestingHelp = true;
+ pChild->RequestHelp( aHelpEvent );
+ aHelpData.mbRequestingHelp = false;
+ }
+ // #104172# do not kill keyboard activated tooltips
+ else if ( aHelpData.mpHelpWin && !aHelpData.mbKeyboardHelp)
+ {
+ ImplDestroyHelpWindow( true );
+ }
+ }
+ }
+}
+
+static void ImplSetMousePointer( vcl::Window const * pChild )
+{
+ if ( ImplGetSVHelpData().mbExtHelpMode )
+ pChild->ImplGetFrame()->SetPointer( PointerStyle::Help );
+ else
+ pChild->ImplGetFrame()->SetPointer( pChild->ImplGetMousePointer() );
+}
+
+static bool ImplCallCommand( const VclPtr<vcl::Window>& pChild, CommandEventId nEvt, void const * pData = nullptr,
+ bool bMouse = false, Point const * pPos = nullptr )
+{
+ Point aPos;
+ if ( pPos )
+ aPos = *pPos;
+ else
+ {
+ if( bMouse )
+ aPos = pChild->GetPointerPosPixel();
+ else
+ {
+ // simulate mouseposition at center of window
+ Size aSize( pChild->GetOutputSizePixel() );
+ aPos = Point( aSize.getWidth()/2, aSize.getHeight()/2 );
+ }
+ }
+
+ CommandEvent aCEvt( aPos, nEvt, bMouse, pData );
+ NotifyEvent aNCmdEvt( MouseNotifyEvent::COMMAND, pChild, &aCEvt );
+ bool bPreNotify = ImplCallPreNotify( aNCmdEvt );
+ if ( pChild->IsDisposed() )
+ return false;
+ if ( !bPreNotify )
+ {
+ pChild->ImplGetWindowImpl()->mbCommand = false;
+ pChild->Command( aCEvt );
+
+ if( pChild->IsDisposed() )
+ return false;
+ pChild->ImplNotifyKeyMouseCommandEventListeners( aNCmdEvt );
+ if ( pChild->IsDisposed() )
+ return false;
+ if ( pChild->ImplGetWindowImpl()->mbCommand )
+ return true;
+ }
+
+ return false;
+}
+
+/* #i34277# delayed context menu activation;
+* necessary if there already was a popup menu running.
+*/
+
+namespace {
+
+struct ContextMenuEvent
+{
+ VclPtr<vcl::Window> pWindow;
+ Point aChildPos;
+};
+
+}
+
+static void ContextMenuEventLink( void* pCEvent, void* )
+{
+ ContextMenuEvent* pEv = static_cast<ContextMenuEvent*>(pCEvent);
+
+ if( ! pEv->pWindow->IsDisposed() )
+ {
+ ImplCallCommand( pEv->pWindow, CommandEventId::ContextMenu, nullptr, true, &pEv->aChildPos );
+ }
+ delete pEv;
+}
+
+bool ImplHandleMouseEvent( const VclPtr<vcl::Window>& xWindow, MouseNotifyEvent nSVEvent, bool bMouseLeave,
+ long nX, long nY, sal_uInt64 nMsgTime,
+ sal_uInt16 nCode, MouseEventModifiers 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;
+
+ // we need a mousemove event, before we get a mousebuttondown or a
+ // mousebuttonup event
+ if ( (nSVEvent == MouseNotifyEvent::MOUSEBUTTONDOWN) || (nSVEvent == MouseNotifyEvent::MOUSEBUTTONUP) )
+ {
+ if ( (nSVEvent == MouseNotifyEvent::MOUSEBUTTONUP) && aHelpData.mbExtHelpMode )
+ Help::EndExtHelp();
+ if ( aHelpData.mpHelpWin )
+ {
+ if( xWindow->ImplGetWindow() == aHelpData.mpHelpWin )
+ {
+ ImplDestroyHelpWindow( false );
+ return true; // xWindow is dead now - avoid crash!
+ }
+ else
+ ImplDestroyHelpWindow( true );
+ }
+
+ if ( (pWinFrameData->mnLastMouseX != nX) ||
+ (pWinFrameData->mnLastMouseY != nY) )
+ {
+ sal_uInt16 nMoveCode = nCode & ~(MOUSE_LEFT | MOUSE_RIGHT | MOUSE_MIDDLE);
+ ImplHandleMouseEvent(xWindow, MouseNotifyEvent::MOUSEMOVE, false, nX, nY, nMsgTime, nMoveCode, nMode);
+ }
+ }
+
+ // update frame data
+ pWinFrameData->mnBeforeLastMouseX = pWinFrameData->mnLastMouseX;
+ pWinFrameData->mnBeforeLastMouseY = pWinFrameData->mnLastMouseY;
+ pWinFrameData->mnLastMouseX = nX;
+ pWinFrameData->mnLastMouseY = nY;
+ pWinFrameData->mnMouseCode = nCode;
+ MouseEventModifiers const nTmpMask = MouseEventModifiers::SYNTHETIC | MouseEventModifiers::MODIFIERCHANGED;
+ pWinFrameData->mnMouseMode = nMode & ~nTmpMask;
+ if ( bMouseLeave )
+ {
+ pWinFrameData->mbMouseIn = false;
+ if ( ImplGetSVHelpData().mpHelpWin && !ImplGetSVHelpData().mbKeyboardHelp )
+ {
+ ImplDestroyHelpWindow( true );
+
+ if ( xWindow->IsDisposed() )
+ return true; // xWindow is dead now - avoid crash! (#122045#)
+ }
+ }
+ else
+ pWinFrameData->mbMouseIn = true;
+
+ DBG_ASSERT(!pSVData->mpWinData->mpTrackWin
+ || (pSVData->mpWinData->mpTrackWin == pSVData->mpWinData->mpCaptureWin),
+ "ImplHandleMouseEvent: TrackWin != CaptureWin");
+
+ // AutoScrollMode
+ if (pSVData->mpWinData->mpAutoScrollWin && (nSVEvent == MouseNotifyEvent::MOUSEBUTTONDOWN))
+ {
+ pSVData->mpWinData->mpAutoScrollWin->EndAutoScroll();
+ return true;
+ }
+
+ // find mouse window
+ if (pSVData->mpWinData->mpCaptureWin)
+ {
+ pChild = pSVData->mpWinData->mpCaptureWin;
+
+ SAL_WARN_IF( xWindow != pChild->ImplGetFrameWindow(), "vcl",
+ "ImplHandleMouseEvent: mouse event is not sent to capture window" );
+
+ // java client cannot capture mouse correctly
+ if ( xWindow != pChild->ImplGetFrameWindow() )
+ return false;
+
+ if ( bMouseLeave )
+ return false;
+ }
+ else
+ {
+ if ( bMouseLeave )
+ pChild = nullptr;
+ else
+ pChild = xWindow->ImplFindWindow( aMousePos );
+ }
+
+ // test this because mouse events are buffered in the remote version
+ // and size may not be in sync
+ if ( !pChild && !bMouseLeave )
+ return false;
+
+ // execute a few tests and catch the message or implement the status
+ if ( pChild )
+ {
+ if( pChild->ImplIsAntiparallel() )
+ {
+ // re-mirror frame pos at pChild
+ const OutputDevice *pChildWinOutDev = pChild->GetOutDev();
+ pChildWinOutDev->ReMirror( aMousePos );
+ }
+
+ // no mouse messages to disabled windows
+ // #106845# if the window was disabled during capturing we have to pass the mouse events to release capturing
+ if (pSVData->mpWinData->mpCaptureWin.get() != pChild
+ && (!pChild->IsEnabled() || !pChild->IsInputEnabled() || pChild->IsInModalMode()))
+ {
+ ImplHandleMouseFloatMode( pChild, aMousePos, nCode, nSVEvent, bMouseLeave );
+ if ( nSVEvent == MouseNotifyEvent::MOUSEMOVE )
+ {
+ ImplHandleMouseHelpRequest( pChild, aMousePos );
+ if( pWinFrameData->mpMouseMoveWin.get() != pChild )
+ nMode |= MouseEventModifiers::ENTERWINDOW;
+ }
+
+ // Call the hook also, if Window is disabled
+ Point aChildPos = pChild->ImplFrameToOutput( aMousePos );
+ MouseEvent aMEvt( aChildPos, pWinFrameData->mnClickCount, nMode, nCode, nCode );
+ NotifyEvent aNEvt( nSVEvent, pChild, &aMEvt );
+
+ if( pChild->IsCallHandlersOnInputDisabled() )
+ {
+ pWinFrameData->mpMouseMoveWin = pChild;
+ pChild->ImplNotifyKeyMouseCommandEventListeners( aNEvt );
+ }
+
+ if ( nSVEvent == MouseNotifyEvent::MOUSEBUTTONDOWN )
+ return true;
+ else
+ {
+ // Set normal MousePointer for disabled windows
+ if ( nSVEvent == MouseNotifyEvent::MOUSEMOVE )
+ ImplSetMousePointer( pChild );
+
+ return false;
+ }
+ }
+
+ // End ExtTextInput-Mode, if the user click in the same TopLevel Window
+ if (pSVData->mpWinData->mpExtTextInputWin
+ && ((nSVEvent == MouseNotifyEvent::MOUSEBUTTONDOWN)
+ || (nSVEvent == MouseNotifyEvent::MOUSEBUTTONUP)))
+ pSVData->mpWinData->mpExtTextInputWin->EndExtTextInput();
+ }
+
+ // determine mouse event data
+ if ( nSVEvent == MouseNotifyEvent::MOUSEMOVE )
+ {
+ // check if MouseMove belongs to same window and if the
+ // status did not change
+ if ( pChild )
+ {
+ Point aChildMousePos = pChild->ImplFrameToOutput( aMousePos );
+ if ( !bMouseLeave &&
+ (pChild == pWinFrameData->mpMouseMoveWin) &&
+ (aChildMousePos.X() == pWinFrameData->mnLastMouseWinX) &&
+ (aChildMousePos.Y() == pWinFrameData->mnLastMouseWinY) &&
+ (nOldCode == pWinFrameData->mnMouseCode) )
+ {
+ // set mouse pointer anew, as it could have changed
+ // due to the mode switch
+ ImplSetMousePointer( pChild );
+ return false;
+ }
+
+ pWinFrameData->mnLastMouseWinX = aChildMousePos.X();
+ pWinFrameData->mnLastMouseWinY = aChildMousePos.Y();
+ }
+
+ // mouse click
+ nClicks = pWinFrameData->mnClickCount;
+
+ // call Start-Drag handler if required
+ // Warning: should be called before Move, as otherwise during
+ // fast mouse movements the applications move to the selection state
+ vcl::Window* pMouseDownWin = pWinFrameData->mpMouseDownWin;
+ if ( pMouseDownWin )
+ {
+ // check for matching StartDrag mode. We only compare
+ // the status of the mouse buttons, such that e. g. Mod1 can
+ // change immediately to the copy mode
+ const MouseSettings& rMSettings = pMouseDownWin->GetSettings().GetMouseSettings();
+ if ( (nCode & (MOUSE_LEFT | MOUSE_RIGHT | MOUSE_MIDDLE)) ==
+ (MouseSettings::GetStartDragCode() & (MOUSE_LEFT | MOUSE_RIGHT | MOUSE_MIDDLE)) )
+ {
+ if ( !pMouseDownWin->ImplGetFrameData()->mbStartDragCalled )
+ {
+ long nDragW = rMSettings.GetStartDragWidth();
+ long nDragH = rMSettings.GetStartDragHeight();
+ //long nMouseX = nX;
+ //long nMouseY = nY;
+ long nMouseX = aMousePos.X(); // #106074# use the possibly re-mirrored coordinates (RTL) ! nX,nY are unmodified !
+ long nMouseY = aMousePos.Y();
+ if ( !(((nMouseX-nDragW) <= pMouseDownWin->ImplGetFrameData()->mnFirstMouseX) &&
+ ((nMouseX+nDragW) >= pMouseDownWin->ImplGetFrameData()->mnFirstMouseX)) ||
+ !(((nMouseY-nDragH) <= pMouseDownWin->ImplGetFrameData()->mnFirstMouseY) &&
+ ((nMouseY+nDragH) >= pMouseDownWin->ImplGetFrameData()->mnFirstMouseY)) )
+ {
+ pMouseDownWin->ImplGetFrameData()->mbStartDragCalled = true;
+
+ // Check if drag source provides its own recognizer
+ if( pMouseDownWin->ImplGetFrameData()->mbInternalDragGestureRecognizer )
+ {
+ // query DropTarget from child window
+ css::uno::Reference< css::datatransfer::dnd::XDragGestureRecognizer > xDragGestureRecognizer(
+ pMouseDownWin->ImplGetWindowImpl()->mxDNDListenerContainer,
+ css::uno::UNO_QUERY );
+
+ if( xDragGestureRecognizer.is() )
+ {
+ // retrieve mouse position relative to mouse down window
+ Point relLoc = pMouseDownWin->ImplFrameToOutput( Point(
+ pMouseDownWin->ImplGetFrameData()->mnFirstMouseX,
+ pMouseDownWin->ImplGetFrameData()->mnFirstMouseY ) );
+
+ // create a UNO mouse event out of the available data
+ css::awt::MouseEvent aMouseEvent( static_cast < css::uno::XInterface * > ( nullptr ),
+#ifdef MACOSX
+ nCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_MOD3),
+#else
+ nCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2),
+#endif
+ nCode & (MOUSE_LEFT | MOUSE_RIGHT | MOUSE_MIDDLE),
+ nMouseX,
+ nMouseY,
+ nClicks,
+ false );
+
+ SolarMutexReleaser aReleaser;
+
+ // FIXME: where do I get Action from ?
+ css::uno::Reference< css::datatransfer::dnd::XDragSource > xDragSource = pMouseDownWin->GetDragSource();
+
+ if( xDragSource.is() )
+ {
+ static_cast < DNDListenerContainer * > ( xDragGestureRecognizer.get() )->fireDragGestureEvent( 0,
+ relLoc.X(), relLoc.Y(), xDragSource, css::uno::makeAny( aMouseEvent ) );
+ }
+ }
+ }
+ }
+ }
+ }
+ else
+ pMouseDownWin->ImplGetFrameData()->mbStartDragCalled = true;
+ }
+
+ // test for mouseleave and mouseenter
+ VclPtr<vcl::Window> pMouseMoveWin = pWinFrameData->mpMouseMoveWin;
+ if ( pChild != pMouseMoveWin )
+ {
+ if ( pMouseMoveWin )
+ {
+ Point aLeaveMousePos = pMouseMoveWin->ImplFrameToOutput( aMousePos );
+ MouseEvent aMLeaveEvt( aLeaveMousePos, nClicks, nMode | MouseEventModifiers::LEAVEWINDOW, nCode, nCode );
+ NotifyEvent aNLeaveEvt( MouseNotifyEvent::MOUSEMOVE, pMouseMoveWin, &aMLeaveEvt );
+ pWinFrameData->mbInMouseMove = true;
+ pMouseMoveWin->ImplGetWinData()->mbMouseOver = false;
+
+ // A MouseLeave can destroy this window
+ if ( !ImplCallPreNotify( aNLeaveEvt ) )
+ {
+ pMouseMoveWin->MouseMove( aMLeaveEvt );
+ if( !pMouseMoveWin->IsDisposed() )
+ aNLeaveEvt.GetWindow()->ImplNotifyKeyMouseCommandEventListeners( aNLeaveEvt );
+ }
+
+ pWinFrameData->mpMouseMoveWin = nullptr;
+ pWinFrameData->mbInMouseMove = false;
+
+ if ( pChild && pChild->IsDisposed() )
+ pChild = nullptr;
+ if ( pMouseMoveWin->IsDisposed() )
+ return true;
+ }
+
+ nMode |= MouseEventModifiers::ENTERWINDOW;
+ }
+ pWinFrameData->mpMouseMoveWin = pChild;
+ if( pChild )
+ pChild->ImplGetWinData()->mbMouseOver = true;
+
+ // MouseLeave
+ if ( !pChild )
+ return false;
+ }
+ else
+ {
+ if (pChild)
+ {
+ // mouse click
+ if ( nSVEvent == MouseNotifyEvent::MOUSEBUTTONDOWN )
+ {
+ const MouseSettings& rMSettings = pChild->GetSettings().GetMouseSettings();
+ sal_uInt64 nDblClkTime = rMSettings.GetDoubleClickTime();
+ long nDblClkW = rMSettings.GetDoubleClickWidth();
+ long nDblClkH = rMSettings.GetDoubleClickHeight();
+ //long nMouseX = nX;
+ //long nMouseY = nY;
+ long nMouseX = aMousePos.X(); // #106074# use the possibly re-mirrored coordinates (RTL) ! nX,nY are unmodified !
+ long nMouseY = aMousePos.Y();
+
+ if ( (pChild == pChild->ImplGetFrameData()->mpMouseDownWin) &&
+ (nCode == pChild->ImplGetFrameData()->mnFirstMouseCode) &&
+ ((nMsgTime-pChild->ImplGetFrameData()->mnMouseDownTime) < nDblClkTime) &&
+ ((nMouseX-nDblClkW) <= pChild->ImplGetFrameData()->mnFirstMouseX) &&
+ ((nMouseX+nDblClkW) >= pChild->ImplGetFrameData()->mnFirstMouseX) &&
+ ((nMouseY-nDblClkH) <= pChild->ImplGetFrameData()->mnFirstMouseY) &&
+ ((nMouseY+nDblClkH) >= pChild->ImplGetFrameData()->mnFirstMouseY) )
+ {
+ pChild->ImplGetFrameData()->mnClickCount++;
+ pChild->ImplGetFrameData()->mbStartDragCalled = true;
+ }
+ else
+ {
+ pChild->ImplGetFrameData()->mpMouseDownWin = pChild;
+ pChild->ImplGetFrameData()->mnClickCount = 1;
+ pChild->ImplGetFrameData()->mnFirstMouseX = nMouseX;
+ pChild->ImplGetFrameData()->mnFirstMouseY = nMouseY;
+ pChild->ImplGetFrameData()->mnFirstMouseCode = nCode;
+ pChild->ImplGetFrameData()->mbStartDragCalled = (nCode & (MOUSE_LEFT | MOUSE_RIGHT | MOUSE_MIDDLE)) !=
+ (MouseSettings::GetStartDragCode() & (MOUSE_LEFT | MOUSE_RIGHT | MOUSE_MIDDLE));
+ }
+ pChild->ImplGetFrameData()->mnMouseDownTime = nMsgTime;
+ }
+ nClicks = pChild->ImplGetFrameData()->mnClickCount;
+ }
+
+ pSVData->maAppData.mnLastInputTime = tools::Time::GetSystemTicks();
+ }
+
+ SAL_WARN_IF( !pChild, "vcl", "ImplHandleMouseEvent: pChild == NULL" );
+
+ if (!pChild)
+ return false;
+
+ // create mouse event
+ Point aChildPos = pChild->ImplFrameToOutput( aMousePos );
+ MouseEvent aMEvt( aChildPos, nClicks, nMode, nCode, nCode );
+
+
+ // tracking window gets the mouse events
+ if (pSVData->mpWinData->mpTrackWin)
+ pChild = pSVData->mpWinData->mpTrackWin;
+
+ // handle FloatingMode
+ if (!pSVData->mpWinData->mpTrackWin && pSVData->mpWinData->mpFirstFloat)
+ {
+ if ( ImplHandleMouseFloatMode( pChild, aMousePos, nCode, nSVEvent, bMouseLeave ) )
+ {
+ if ( !pChild->IsDisposed() )
+ {
+ pChild->ImplGetFrameData()->mbStartDragCalled = true;
+ }
+ return true;
+ }
+ }
+
+ // call handler
+ bool bCallHelpRequest = true;
+ SAL_WARN_IF( !pChild, "vcl", "ImplHandleMouseEvent: pChild is NULL" );
+
+ if (!pChild)
+ return false;
+
+ NotifyEvent aNEvt( nSVEvent, pChild, &aMEvt );
+ if ( nSVEvent == MouseNotifyEvent::MOUSEMOVE )
+ pChild->ImplGetFrameData()->mbInMouseMove = true;
+
+ // bring window into foreground on mouseclick
+ if ( nSVEvent == MouseNotifyEvent::MOUSEBUTTONDOWN )
+ {
+ if (!pSVData->mpWinData->mpFirstFloat
+ && // totop for floating windows in popup would change the focus and would close them immediately
+ !(pChild->ImplGetFrameWindow()->GetStyle()
+ & WB_OWNERDRAWDECORATION)) // ownerdrawdecorated windows must never grab focus
+ pChild->ToTop();
+ if ( pChild->IsDisposed() )
+ return true;
+ }
+
+ if ( ImplCallPreNotify( aNEvt ) || pChild->IsDisposed() )
+ bRet = true;
+ else
+ {
+ bRet = false;
+ if ( nSVEvent == MouseNotifyEvent::MOUSEMOVE )
+ {
+ if (pSVData->mpWinData->mpTrackWin)
+ {
+ TrackingEvent aTEvt( aMEvt );
+ pChild->Tracking( aTEvt );
+ if ( !pChild->IsDisposed() )
+ {
+ // When ScrollRepeat, we restart the timer
+ if (pSVData->mpWinData->mpTrackTimer
+ && (pSVData->mpWinData->mnTrackFlags & StartTrackingFlags::ScrollRepeat))
+ pSVData->mpWinData->mpTrackTimer->Start();
+ }
+ bCallHelpRequest = false;
+ bRet = true;
+ }
+ else
+ {
+ // Auto-ToTop
+ if (!pSVData->mpWinData->mpCaptureWin
+ && (pChild->GetSettings().GetMouseSettings().GetOptions()
+ & MouseSettingsOptions::AutoFocus))
+ pChild->ToTop( ToTopFlags::NoGrabFocus );
+
+ if( pChild->IsDisposed() )
+ bCallHelpRequest = false;
+ else
+ {
+ // if the MouseMove handler changes the help window's visibility
+ // the HelpRequest handler should not be called anymore
+ vcl::Window* pOldHelpTextWin = ImplGetSVHelpData().mpHelpWin;
+ pChild->MouseMove( aMEvt );
+ if ( pOldHelpTextWin != ImplGetSVHelpData().mpHelpWin )
+ bCallHelpRequest = false;
+ }
+ }
+ }
+ else if ( nSVEvent == MouseNotifyEvent::MOUSEBUTTONDOWN )
+ {
+ if ( pSVData->mpWinData->mpTrackWin )
+ bRet = true;
+ else
+ {
+ pChild->ImplGetWindowImpl()->mbMouseButtonDown = false;
+ pChild->MouseButtonDown( aMEvt );
+ }
+ }
+ else
+ {
+ if (pSVData->mpWinData->mpTrackWin)
+ {
+ pChild->EndTracking();
+ bRet = true;
+ }
+ else
+ {
+ pChild->ImplGetWindowImpl()->mbMouseButtonUp = false;
+ pChild->MouseButtonUp( aMEvt );
+ }
+ }
+
+ assert(aNEvt.GetWindow() == pChild);
+
+ if (!pChild->IsDisposed())
+ pChild->ImplNotifyKeyMouseCommandEventListeners( aNEvt );
+ }
+
+ if (pChild->IsDisposed())
+ return true;
+
+ if ( nSVEvent == MouseNotifyEvent::MOUSEMOVE )
+ pChild->ImplGetWindowImpl()->mpFrameData->mbInMouseMove = false;
+
+ if ( nSVEvent == MouseNotifyEvent::MOUSEMOVE )
+ {
+ if ( bCallHelpRequest && !ImplGetSVHelpData().mbKeyboardHelp )
+ ImplHandleMouseHelpRequest( pChild, pChild->OutputToScreenPixel( aMEvt.GetPosPixel() ) );
+ bRet = true;
+ }
+ else if ( !bRet )
+ {
+ if ( nSVEvent == MouseNotifyEvent::MOUSEBUTTONDOWN )
+ {
+ if ( !pChild->ImplGetWindowImpl()->mbMouseButtonDown )
+ bRet = true;
+ }
+ else
+ {
+ if ( !pChild->ImplGetWindowImpl()->mbMouseButtonUp )
+ bRet = true;
+ }
+ }
+
+ if ( nSVEvent == MouseNotifyEvent::MOUSEMOVE )
+ {
+ // set new mouse pointer
+ if ( !bMouseLeave )
+ ImplSetMousePointer( pChild );
+ }
+ else if ( (nSVEvent == MouseNotifyEvent::MOUSEBUTTONDOWN) || (nSVEvent == MouseNotifyEvent::MOUSEBUTTONUP) )
+ {
+ // Command-Events
+ if ( /*!bRet &&*/ (nClicks == 1) && (nSVEvent == MouseNotifyEvent::MOUSEBUTTONDOWN) &&
+ (nCode == MOUSE_MIDDLE) )
+ {
+ MouseMiddleButtonAction nMiddleAction = pChild->GetSettings().GetMouseSettings().GetMiddleButtonAction();
+ if ( nMiddleAction == MouseMiddleButtonAction::AutoScroll )
+ bRet = !ImplCallCommand( pChild, CommandEventId::StartAutoScroll, nullptr, true, &aChildPos );
+ else if ( nMiddleAction == MouseMiddleButtonAction::PasteSelection )
+ bRet = !ImplCallCommand( pChild, CommandEventId::PasteSelection, nullptr, true, &aChildPos );
+ }
+ else
+ {
+ // ContextMenu
+ if ( (nCode == MouseSettings::GetContextMenuCode()) &&
+ (nClicks == MouseSettings::GetContextMenuClicks()) )
+ {
+ bool bContextMenu = (nSVEvent == MouseNotifyEvent::MOUSEBUTTONDOWN);
+ if ( bContextMenu )
+ {
+ if( pSVData->maAppData.mpActivePopupMenu )
+ {
+ /* #i34277# there already is a context menu open
+ * that was probably just closed with EndPopupMode.
+ * We need to give the eventual corresponding
+ * PopupMenu::Execute a chance to end properly.
+ * Therefore delay context menu command and
+ * issue only after popping one frame of the
+ * Yield stack.
+ */
+ ContextMenuEvent* pEv = new ContextMenuEvent;
+ pEv->pWindow = pChild;
+ pEv->aChildPos = aChildPos;
+ Application::PostUserEvent( Link<void*,void>( pEv, ContextMenuEventLink ) );
+ }
+ else
+ bRet = ! ImplCallCommand( pChild, CommandEventId::ContextMenu, nullptr, true, &aChildPos );
+ }
+ }
+ }
+ }
+
+ return bRet;
+}
+
+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()->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()->mpFrameData->mpFocusWin;
+
+ // 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
+ SAL_WARN_IF(pChild != pSVData->mpWinData->mpFocusWin, "vcl",
+ "ImplHandleKey: Keyboard-Input is sent to a frame without focus");
+
+ // no keyinput to disabled windows
+ if ( !pChild->IsEnabled() || !pChild->IsInputEnabled() || pChild->IsInModalMode() )
+ return nullptr;
+
+ return pChild;
+}
+
+static bool ImplHandleKey( vcl::Window* pWindow, MouseNotifyEvent nSVEvent,
+ sal_uInt16 nKeyCode, sal_uInt16 nCharCode, sal_uInt16 nRepeat, bool bForward )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ vcl::KeyCode aKeyCode( nKeyCode, nKeyCode );
+ sal_uInt16 nEvCode = aKeyCode.GetCode();
+
+ // allow application key listeners to remove the key event
+ // but make sure we're not forwarding external KeyEvents, (ie where bForward is false)
+ // because those are coming back from the listener itself and MUST be processed
+ if( bForward )
+ {
+ VclEventId nVCLEvent;
+ switch( nSVEvent )
+ {
+ case MouseNotifyEvent::KEYINPUT:
+ nVCLEvent = VclEventId::WindowKeyInput;
+ break;
+ case MouseNotifyEvent::KEYUP:
+ nVCLEvent = VclEventId::WindowKeyUp;
+ break;
+ default:
+ nVCLEvent = VclEventId::NONE;
+ break;
+ }
+ KeyEvent aKeyEvent(static_cast<sal_Unicode>(nCharCode), aKeyCode, nRepeat);
+ if (nVCLEvent != VclEventId::NONE && Application::HandleKey(nVCLEvent, pWindow, &aKeyEvent))
+ return true;
+ }
+
+ // #i1820# use locale specific decimal separator
+ if( nEvCode == KEY_DECIMAL )
+ {
+ if( Application::GetSettings().GetMiscSettings().GetEnableLocalizedDecimalSep() )
+ {
+ OUString aSep( pWindow->GetSettings().GetLocaleDataWrapper().getNumDecimalSep() );
+ nCharCode = static_cast<sal_uInt16>(aSep[0]);
+ }
+ }
+
+ bool bCtrlF6 = (aKeyCode.GetCode() == KEY_F6) && aKeyCode.IsMod1();
+
+ // determine last input time
+ pSVData->maAppData.mnLastInputTime = tools::Time::GetSystemTicks();
+
+ // handle tracking window
+ if ( nSVEvent == MouseNotifyEvent::KEYINPUT )
+ {
+ if ( ImplGetSVHelpData().mbExtHelpMode )
+ {
+ Help::EndExtHelp();
+ if ( nEvCode == KEY_ESCAPE )
+ return true;
+ }
+ if ( ImplGetSVHelpData().mpHelpWin )
+ ImplDestroyHelpWindow( false );
+
+ // AutoScrollMode
+ if (pSVData->mpWinData->mpAutoScrollWin)
+ {
+ pSVData->mpWinData->mpAutoScrollWin->EndAutoScroll();
+ if ( nEvCode == KEY_ESCAPE )
+ return true;
+ }
+
+ if (pSVData->mpWinData->mpTrackWin)
+ {
+ sal_uInt16 nOrigCode = aKeyCode.GetCode();
+
+ if ( nOrigCode == KEY_ESCAPE )
+ {
+ pSVData->mpWinData->mpTrackWin->EndTracking( TrackingEventFlags::Cancel | TrackingEventFlags::Key );
+ if (pSVData->mpWinData->mpFirstFloat)
+ {
+ FloatingWindow* pLastLevelFloat = pSVData->mpWinData->mpFirstFloat->ImplFindLastLevelFloat();
+ if ( !(pLastLevelFloat->GetPopupModeFlags() & FloatWinPopupFlags::NoKeyClose) )
+ {
+ sal_uInt16 nEscCode = aKeyCode.GetCode();
+
+ if ( nEscCode == KEY_ESCAPE )
+ pLastLevelFloat->EndPopupMode( FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll );
+ }
+ }
+ return true;
+ }
+ else if ( nOrigCode == KEY_RETURN )
+ {
+ pSVData->mpWinData->mpTrackWin->EndTracking( TrackingEventFlags::Key );
+ return true;
+ }
+ else
+ return true;
+ }
+
+ // handle FloatingMode
+ if (pSVData->mpWinData->mpFirstFloat)
+ {
+ FloatingWindow* pLastLevelFloat = pSVData->mpWinData->mpFirstFloat->ImplFindLastLevelFloat();
+ if ( !(pLastLevelFloat->GetPopupModeFlags() & FloatWinPopupFlags::NoKeyClose) )
+ {
+ sal_uInt16 nCode = aKeyCode.GetCode();
+
+ if ( (nCode == KEY_ESCAPE) || bCtrlF6)
+ {
+ pLastLevelFloat->EndPopupMode( FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll );
+ if( !bCtrlF6 )
+ return true;
+ }
+ }
+ }
+
+ // test for accel
+ if ( pSVData->maAppData.mpAccelMgr )
+ {
+ if ( pSVData->maAppData.mpAccelMgr->IsAccelKey( aKeyCode ) )
+ return true;
+ }
+ }
+
+ // find window
+ VclPtr<vcl::Window> pChild = ImplGetKeyInputWindow( pWindow );
+ if ( !pChild )
+ return false;
+
+ // RTL: mirror cursor keys
+ const OutputDevice *pChildOutDev = pChild->GetOutDev();
+ if( (aKeyCode.GetCode() == KEY_LEFT || aKeyCode.GetCode() == KEY_RIGHT) &&
+ pChildOutDev->HasMirroredGraphics() && pChild->IsRTLEnabled() )
+ aKeyCode = vcl::KeyCode( aKeyCode.GetCode() == KEY_LEFT ? KEY_RIGHT : KEY_LEFT, aKeyCode.GetModifier() );
+
+ KeyEvent aKeyEvt( static_cast<sal_Unicode>(nCharCode), aKeyCode, nRepeat );
+ NotifyEvent aNotifyEvt( nSVEvent, pChild, &aKeyEvt );
+ bool bKeyPreNotify = ImplCallPreNotify( aNotifyEvt );
+ bool bRet = true;
+
+ if ( !bKeyPreNotify && !pChild->IsDisposed() )
+ {
+ if ( nSVEvent == MouseNotifyEvent::KEYINPUT )
+ {
+ UITestLogger::getInstance().logKeyInput(pChild, aKeyEvt);
+ pChild->ImplGetWindowImpl()->mbKeyInput = false;
+ pChild->KeyInput( aKeyEvt );
+ }
+ else
+ {
+ pChild->ImplGetWindowImpl()->mbKeyUp = false;
+ pChild->KeyUp( aKeyEvt );
+ }
+ if( !pChild->IsDisposed() )
+ aNotifyEvt.GetWindow()->ImplNotifyKeyMouseCommandEventListeners( aNotifyEvt );
+ }
+
+ if ( pChild->IsDisposed() )
+ return true;
+
+ if ( nSVEvent == MouseNotifyEvent::KEYINPUT )
+ {
+ if ( !bKeyPreNotify && pChild->ImplGetWindowImpl()->mbKeyInput )
+ {
+ sal_uInt16 nCode = aKeyCode.GetCode();
+
+ // #101999# is focus in or below toolbox
+ bool bToolboxFocus=false;
+ if( (nCode == KEY_F1) && aKeyCode.IsShift() )
+ {
+ vcl::Window *pWin = pWindow->ImplGetWindowImpl()->mpFrameData->mpFocusWin;
+ while( pWin )
+ {
+ if( pWin->ImplGetWindowImpl()->mbToolBox )
+ {
+ bToolboxFocus = true;
+ break;
+ }
+ else
+ pWin = pWin->GetParent();
+ }
+ }
+
+ // ContextMenu
+ if ( (nCode == KEY_CONTEXTMENU) || ((nCode == KEY_F10) && aKeyCode.IsShift() && !aKeyCode.IsMod1() && !aKeyCode.IsMod2() ) )
+ bRet = !ImplCallCommand( pChild, CommandEventId::ContextMenu );
+ else if ( ( (nCode == KEY_F2) && aKeyCode.IsShift() ) || ( (nCode == KEY_F1) && aKeyCode.IsMod1() ) ||
+ // #101999# no active help when focus in toolbox, simulate BalloonHelp instead
+ ( (nCode == KEY_F1) && aKeyCode.IsShift() && bToolboxFocus ) )
+ {
+ // TipHelp via Keyboard (Shift-F2 or Ctrl-F1)
+ // simulate mouseposition at center of window
+
+ Size aSize = pChild->GetOutputSize();
+ Point aPos( aSize.getWidth()/2, aSize.getHeight()/2 );
+ aPos = pChild->OutputToScreenPixel( aPos );
+
+ HelpEvent aHelpEvent( aPos, HelpEventMode::BALLOON );
+ aHelpEvent.SetKeyboardActivated( true );
+ ImplGetSVHelpData().mbSetKeyboardHelp = true;
+ pChild->RequestHelp( aHelpEvent );
+ ImplGetSVHelpData().mbSetKeyboardHelp = false;
+ }
+ else if ( (nCode == KEY_F1) || (nCode == KEY_HELP) )
+ {
+ if ( !aKeyCode.GetModifier() )
+ {
+ if ( ImplGetSVHelpData().mbContextHelp )
+ {
+ Point aMousePos = pChild->OutputToScreenPixel( pChild->GetPointerPosPixel() );
+ HelpEvent aHelpEvent( aMousePos, HelpEventMode::CONTEXT );
+ pChild->RequestHelp( aHelpEvent );
+ }
+ else
+ bRet = false;
+ }
+ else if ( aKeyCode.IsShift() )
+ {
+ if ( ImplGetSVHelpData().mbExtHelp )
+ Help::StartExtHelp();
+ else
+ bRet = false;
+ }
+ }
+ else
+ bRet = false;
+ }
+ }
+ else
+ {
+ if ( !bKeyPreNotify && pChild->ImplGetWindowImpl()->mbKeyUp )
+ bRet = false;
+ }
+
+ // #105591# send keyinput to parent if we are a floating window and the key was not processed yet
+ if( !bRet && pWindow->ImplGetWindowImpl() && pWindow->ImplGetWindowImpl()->mbFloatWin && pWindow->GetParent() && (pWindow->ImplGetWindowImpl()->mpFrame != pWindow->GetParent()->ImplGetWindowImpl()->mpFrame) )
+ {
+ pChild = pWindow->GetParent();
+
+ // call handler
+ NotifyEvent aNEvt( nSVEvent, pChild, &aKeyEvt );
+ bool bPreNotify = ImplCallPreNotify( aNEvt );
+ if ( pChild->IsDisposed() )
+ return true;
+
+ if ( !bPreNotify )
+ {
+ if ( nSVEvent == MouseNotifyEvent::KEYINPUT )
+ {
+ pChild->ImplGetWindowImpl()->mbKeyInput = false;
+ pChild->KeyInput( aKeyEvt );
+ }
+ else
+ {
+ pChild->ImplGetWindowImpl()->mbKeyUp = false;
+ pChild->KeyUp( aKeyEvt );
+ }
+
+ if( !pChild->IsDisposed() )
+ aNEvt.GetWindow()->ImplNotifyKeyMouseCommandEventListeners( aNEvt );
+ if ( pChild->IsDisposed() )
+ return true;
+ }
+
+ if( bPreNotify || !pChild->ImplGetWindowImpl()->mbKeyInput )
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+static bool ImplHandleExtTextInput( vcl::Window* pWindow,
+ const OUString& rText,
+ const ExtTextInputAttr* pTextAttr,
+ sal_Int32 nCursorPos, sal_uInt16 nCursorFlags )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ vcl::Window* pChild = nullptr;
+
+ int nTries = 200;
+ while( nTries-- )
+ {
+ pChild = pSVData->mpWinData->mpExtTextInputWin;
+ if ( !pChild )
+ {
+ pChild = ImplGetKeyInputWindow( pWindow );
+ if ( !pChild )
+ return false;
+ }
+ if( !pChild->ImplGetWindowImpl()->mpFrameData->mnFocusId )
+ break;
+
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ SAL_WARN("vcl", "Failed to get ext text input context");
+ break;
+ }
+ Application::Yield();
+ }
+
+ // If it is the first ExtTextInput call, we inform the information
+ // and allocate the data, which we must store in this mode
+ ImplWinData* pWinData = pChild->ImplGetWinData();
+ if ( !pChild->ImplGetWindowImpl()->mbExtTextInput )
+ {
+ pChild->ImplGetWindowImpl()->mbExtTextInput = true;
+ pWinData->mpExtOldText = OUString();
+ pWinData->mpExtOldAttrAry.reset();
+ pSVData->mpWinData->mpExtTextInputWin = pChild;
+ ImplCallCommand( pChild, CommandEventId::StartExtTextInput );
+ }
+
+ // be aware of being recursively called in StartExtTextInput
+ if ( !pChild->ImplGetWindowImpl()->mbExtTextInput )
+ return false;
+
+ // Test for changes
+ bool bOnlyCursor = false;
+ sal_Int32 nMinLen = std::min( pWinData->mpExtOldText->getLength(), rText.getLength() );
+ sal_Int32 nDeltaStart = 0;
+ while ( nDeltaStart < nMinLen )
+ {
+ if ( (*pWinData->mpExtOldText)[nDeltaStart] != rText[nDeltaStart] )
+ break;
+ nDeltaStart++;
+ }
+ if ( pWinData->mpExtOldAttrAry || pTextAttr )
+ {
+ if ( !pWinData->mpExtOldAttrAry || !pTextAttr )
+ nDeltaStart = 0;
+ else
+ {
+ sal_Int32 i = 0;
+ while ( i < nDeltaStart )
+ {
+ if ( pWinData->mpExtOldAttrAry[i] != pTextAttr[i] )
+ {
+ nDeltaStart = i;
+ break;
+ }
+ i++;
+ }
+ }
+ }
+ if ( (nDeltaStart >= nMinLen) &&
+ (pWinData->mpExtOldText->getLength() == rText.getLength()) )
+ bOnlyCursor = true;
+
+ // Call Event and store the information
+ CommandExtTextInputData aData( rText, pTextAttr,
+ nCursorPos, nCursorFlags,
+ bOnlyCursor );
+ *pWinData->mpExtOldText = rText;
+ pWinData->mpExtOldAttrAry.reset();
+ if ( pTextAttr )
+ {
+ pWinData->mpExtOldAttrAry.reset( new ExtTextInputAttr[rText.getLength()] );
+ memcpy( pWinData->mpExtOldAttrAry.get(), pTextAttr, rText.getLength()*sizeof( ExtTextInputAttr ) );
+ }
+ return !ImplCallCommand( pChild, CommandEventId::ExtTextInput, &aData );
+}
+
+static bool ImplHandleEndExtTextInput()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ vcl::Window* pChild = pSVData->mpWinData->mpExtTextInputWin;
+ bool bRet = false;
+
+ if ( pChild )
+ {
+ pChild->ImplGetWindowImpl()->mbExtTextInput = false;
+ pSVData->mpWinData->mpExtTextInputWin = nullptr;
+ ImplWinData* pWinData = pChild->ImplGetWinData();
+ pWinData->mpExtOldText.reset();
+ pWinData->mpExtOldAttrAry.reset();
+ bRet = !ImplCallCommand( pChild, CommandEventId::EndExtTextInput );
+ }
+
+ return bRet;
+}
+
+static void ImplHandleExtTextInputPos( vcl::Window* pWindow,
+ tools::Rectangle& rRect, 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->ImplLogicWidthToDevicePixel( pChild->GetCursorExtTextInputWidth() );
+ if ( !rInputWidth )
+ rInputWidth = rRect.GetWidth();
+ }
+ if (pVertical != nullptr)
+ *pVertical
+ = pChild != nullptr && pChild->GetInputContext().GetFont().IsVertical();
+}
+
+static bool ImplHandleInputContextChange( vcl::Window* pWindow )
+{
+ vcl::Window* pChild = ImplGetKeyInputWindow( pWindow );
+ CommandInputContextData aData;
+ return !ImplCallCommand( pChild, CommandEventId::InputContextChange, &aData );
+}
+
+static bool ImplCallWheelCommand( const VclPtr<vcl::Window>& pWindow, const Point& rPos,
+ const CommandWheelData* pWheelData )
+{
+ Point aCmdMousePos = pWindow->ImplFrameToOutput( rPos );
+ CommandEvent aCEvt( aCmdMousePos, CommandEventId::Wheel, true, pWheelData );
+ NotifyEvent aNCmdEvt( MouseNotifyEvent::COMMAND, pWindow, &aCEvt );
+ bool bPreNotify = ImplCallPreNotify( aNCmdEvt );
+ if ( pWindow->IsDisposed() )
+ return false;
+ if ( !bPreNotify )
+ {
+ pWindow->ImplGetWindowImpl()->mbCommand = false;
+ pWindow->Command( aCEvt );
+ if ( pWindow->IsDisposed() )
+ return false;
+ if ( pWindow->ImplGetWindowImpl()->mbCommand )
+ return true;
+ }
+ return false;
+}
+
+static bool acceptableWheelScrollTarget(const vcl::Window *pMouseWindow)
+{
+ return (pMouseWindow && !pMouseWindow->isDisposed() && pMouseWindow->IsInputEnabled() && !pMouseWindow->IsInModalMode());
+}
+
+//If the last event at the same absolute screen position was handled by a
+//different window then reuse that window if the event occurs within 1/2 a
+//second, i.e. so scrolling down something like the calc sidebar that contains
+//widgets that respond to wheel events will continue to send the event to the
+//scrolling widget in favour of the widget that happens to end up under the
+//mouse.
+static bool shouldReusePreviousMouseWindow(const SalWheelMouseEvent& rPrevEvt, const SalWheelMouseEvent& rEvt)
+{
+ return (rEvt.mnX == rPrevEvt.mnX && rEvt.mnY == rPrevEvt.mnY && rEvt.mnTime-rPrevEvt.mnTime < 500/*ms*/);
+}
+
+namespace {
+
+class HandleGestureEventBase
+{
+protected:
+ ImplSVData* m_pSVData;
+ VclPtr<vcl::Window> m_pWindow;
+ Point m_aMousePos;
+
+public:
+ HandleGestureEventBase(vcl::Window *pWindow, const Point &rMousePos)
+ : m_pSVData(ImplGetSVData())
+ , m_pWindow(pWindow)
+ , m_aMousePos(rMousePos)
+ {
+ }
+ bool Setup();
+ vcl::Window* FindTarget();
+ vcl::Window* Dispatch(vcl::Window* pTarget);
+ virtual bool CallCommand(vcl::Window *pWindow, const Point &rMousePos) = 0;
+ virtual ~HandleGestureEventBase() {}
+};
+
+}
+
+bool HandleGestureEventBase::Setup()
+{
+
+ if (m_pSVData->mpWinData->mpAutoScrollWin)
+ m_pSVData->mpWinData->mpAutoScrollWin->EndAutoScroll();
+ if (ImplGetSVHelpData().mpHelpWin)
+ ImplDestroyHelpWindow( true );
+ return !m_pWindow->IsDisposed();
+}
+
+vcl::Window* HandleGestureEventBase::FindTarget()
+{
+ // first check any floating window ( eg. drop down listboxes)
+ vcl::Window *pMouseWindow = nullptr;
+
+ if (m_pSVData->mpWinData->mpFirstFloat && !m_pSVData->mpWinData->mpCaptureWin &&
+ !m_pSVData->mpWinData->mpFirstFloat->ImplIsFloatPopupModeWindow( m_pWindow ) )
+ {
+ bool bHitTestInsideRect = false;
+ pMouseWindow = m_pSVData->mpWinData->mpFirstFloat->ImplFloatHitTest( m_pWindow, m_aMousePos, bHitTestInsideRect );
+ if (!pMouseWindow)
+ pMouseWindow = m_pSVData->mpWinData->mpFirstFloat;
+ }
+ // then try the window directly beneath the mouse
+ if( !pMouseWindow )
+ {
+ pMouseWindow = m_pWindow->ImplFindWindow( m_aMousePos );
+ }
+ else
+ {
+ // transform coordinates to float window frame coordinates
+ pMouseWindow = pMouseWindow->ImplFindWindow(
+ pMouseWindow->OutputToScreenPixel(
+ pMouseWindow->AbsoluteScreenToOutputPixel(
+ m_pWindow->OutputToAbsoluteScreenPixel(
+ m_pWindow->ScreenToOutputPixel( m_aMousePos ) ) ) ) );
+ }
+
+ while (acceptableWheelScrollTarget(pMouseWindow))
+ {
+ if (pMouseWindow->IsEnabled())
+ break;
+ //try the parent if this one is disabled
+ pMouseWindow = pMouseWindow->GetParent();
+ }
+
+ return pMouseWindow;
+}
+
+vcl::Window *HandleGestureEventBase::Dispatch(vcl::Window* pMouseWindow)
+{
+ vcl::Window *pDispatchedTo = nullptr;
+
+ if (acceptableWheelScrollTarget(pMouseWindow) && pMouseWindow->IsEnabled())
+ {
+ // transform coordinates to float window frame coordinates
+ Point aRelMousePos( pMouseWindow->OutputToScreenPixel(
+ pMouseWindow->AbsoluteScreenToOutputPixel(
+ m_pWindow->OutputToAbsoluteScreenPixel(
+ m_pWindow->ScreenToOutputPixel( m_aMousePos ) ) ) ) );
+ bool bPropogate = CallCommand(pMouseWindow, aRelMousePos);
+ if (!bPropogate)
+ 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 bPropogate = CallCommand(pFocusWindow, aRelMousePos);
+ if (!bPropogate)
+ pDispatchedTo = pMouseWindow;
+ }
+ }
+ }
+ return pDispatchedTo;
+}
+
+namespace {
+
+class HandleWheelEvent : public HandleGestureEventBase
+{
+private:
+ CommandWheelData m_aWheelData;
+public:
+ HandleWheelEvent(vcl::Window *pWindow, const SalWheelMouseEvent& rEvt)
+ : HandleGestureEventBase(pWindow, Point(rEvt.mnX, rEvt.mnY))
+ {
+ CommandWheelMode nMode;
+ sal_uInt16 nCode = rEvt.mnCode;
+ bool bHorz = rEvt.mbHorz;
+ bool bPixel = rEvt.mbDeltaIsPixel;
+ if ( nCode & KEY_MOD1 )
+ nMode = CommandWheelMode::ZOOM;
+ else if ( nCode & KEY_MOD2 )
+ nMode = CommandWheelMode::DATAZOOM;
+ else
+ {
+ nMode = CommandWheelMode::SCROLL;
+ // #i85450# interpret shift-wheel as horizontal wheel action
+ if( (nCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_MOD3)) == KEY_SHIFT )
+ bHorz = true;
+ }
+
+ m_aWheelData = CommandWheelData(rEvt.mnDelta, rEvt.mnNotchDelta, rEvt.mnScrollLines, nMode, nCode, bHorz, bPixel);
+
+ }
+ virtual bool CallCommand(vcl::Window *pWindow, const Point &rMousePos) override
+ {
+ return ImplCallWheelCommand(pWindow, rMousePos, &m_aWheelData);
+ }
+ bool HandleEvent(const SalWheelMouseEvent& rEvt);
+};
+
+}
+
+bool HandleWheelEvent::HandleEvent(const SalWheelMouseEvent& rEvt)
+{
+ if (!Setup())
+ return false;
+
+ VclPtr<vcl::Window> xMouseWindow = FindTarget();
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ // avoid the problem that scrolling via wheel to this point brings a widget
+ // under the mouse that also accepts wheel commands, so stick with the old
+ // widget if the time gap is very small
+ if (shouldReusePreviousMouseWindow(pSVData->mpWinData->maLastWheelEvent, rEvt) &&
+ acceptableWheelScrollTarget(pSVData->mpWinData->mpLastWheelWindow))
+ {
+ xMouseWindow = pSVData->mpWinData->mpLastWheelWindow;
+ }
+
+ pSVData->mpWinData->maLastWheelEvent = rEvt;
+
+ pSVData->mpWinData->mpLastWheelWindow = Dispatch(xMouseWindow);
+
+ return pSVData->mpWinData->mpLastWheelWindow;
+}
+
+namespace {
+
+class HandleGestureEvent : public HandleGestureEventBase
+{
+public:
+ HandleGestureEvent(vcl::Window *pWindow, const Point &rMousePos)
+ : HandleGestureEventBase(pWindow, rMousePos)
+ {
+ }
+ bool HandleEvent();
+};
+
+}
+
+bool HandleGestureEvent::HandleEvent()
+{
+ if (!Setup())
+ return false;
+
+ vcl::Window *pTarget = FindTarget();
+
+ bool bHandled = Dispatch(pTarget) != nullptr;
+ return bHandled;
+}
+
+static bool ImplHandleWheelEvent(vcl::Window* pWindow, const SalWheelMouseEvent& rEvt)
+{
+ HandleWheelEvent aHandler(pWindow, rEvt);
+ return aHandler.HandleEvent(rEvt);
+}
+
+namespace {
+
+class HandleSwipeEvent : public HandleGestureEvent
+{
+private:
+ CommandSwipeData m_aSwipeData;
+public:
+ HandleSwipeEvent(vcl::Window *pWindow, const SalSwipeEvent& rEvt)
+ : HandleGestureEvent(pWindow, Point(rEvt.mnX, rEvt.mnY)),
+ m_aSwipeData(rEvt.mnVelocityX)
+ {
+ }
+ virtual bool CallCommand(vcl::Window *pWindow, const Point &/*rMousePos*/) override
+ {
+ return ImplCallCommand(pWindow, CommandEventId::Swipe, &m_aSwipeData);
+ }
+};
+
+}
+
+static bool ImplHandleSwipe(vcl::Window *pWindow, const SalSwipeEvent& rEvt)
+{
+ HandleSwipeEvent aHandler(pWindow, rEvt);
+ return aHandler.HandleEvent();
+}
+
+namespace {
+
+class HandleLongPressEvent : public HandleGestureEvent
+{
+private:
+ CommandLongPressData m_aLongPressData;
+public:
+ HandleLongPressEvent(vcl::Window *pWindow, const SalLongPressEvent& rEvt)
+ : HandleGestureEvent(pWindow, Point(rEvt.mnX, rEvt.mnY)),
+ m_aLongPressData(rEvt.mnX, rEvt.mnY)
+ {
+ }
+ virtual bool CallCommand(vcl::Window *pWindow, const Point &/*rMousePos*/) override
+ {
+ return ImplCallCommand(pWindow, CommandEventId::LongPress, &m_aLongPressData);
+ }
+};
+
+}
+
+static bool ImplHandleLongPress(vcl::Window *pWindow, const SalLongPressEvent& rEvt)
+{
+ HandleLongPressEvent aHandler(pWindow, rEvt);
+ return aHandler.HandleEvent();
+}
+
+namespace {
+
+class HandleGeneralGestureEvent : public HandleGestureEvent
+{
+private:
+ CommandGestureData m_aGestureData;
+
+public:
+ HandleGeneralGestureEvent(vcl::Window* pWindow, const SalGestureEvent& rEvent)
+ : HandleGestureEvent(pWindow, Point(rEvent.mnX, rEvent.mnY))
+ , m_aGestureData(rEvent.mnX, rEvent.mnY, rEvent.meEventType, rEvent.mfOffset, rEvent.meOrientation)
+ {
+ }
+
+ virtual bool CallCommand(vcl::Window* pWindow, const Point& /*rMousePos*/) override
+ {
+ return ImplCallCommand(pWindow, CommandEventId::Gesture, &m_aGestureData);
+ }
+};
+
+}
+
+static bool ImplHandleGestureEvent(vcl::Window* pWindow, const SalGestureEvent& rEvent)
+{
+ HandleGeneralGestureEvent aHandler(pWindow, rEvent);
+ return aHandler.HandleEvent();
+}
+
+static void ImplHandlePaint( vcl::Window* pWindow, const tools::Rectangle& rBoundRect, bool bImmediateUpdate )
+{
+ // system paint events must be checked for re-mirroring
+ pWindow->ImplGetWindowImpl()->mnPaintFlags |= ImplPaintFlags::CheckRtl;
+
+ // trigger paint for all windows that live in the new paint region
+ vcl::Region aRegion( rBoundRect );
+ pWindow->ImplInvalidateOverlapFrameRegion( aRegion );
+ if( bImmediateUpdate )
+ {
+ // #i87663# trigger possible pending resize notifications
+ // (GetSizePixel does that for us)
+ pWindow->GetSizePixel();
+ // force drawing immediately
+ pWindow->PaintImmediately();
+ }
+}
+
+static void KillOwnPopups( vcl::Window const * pWindow )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ vcl::Window *pParent = pWindow->ImplGetWindowImpl()->mpFrameWindow;
+ vcl::Window *pChild = pSVData->mpWinData->mpFirstFloat;
+ if ( pChild && pParent->ImplIsWindowOrChild( pChild, true ) )
+ {
+ if (!(pSVData->mpWinData->mpFirstFloat->GetPopupModeFlags()
+ & FloatWinPopupFlags::NoAppFocusClose))
+ pSVData->mpWinData->mpFirstFloat->EndPopupMode(FloatWinPopupEndFlags::Cancel
+ | FloatWinPopupEndFlags::CloseAll);
+ }
+}
+
+void ImplHandleResize( vcl::Window* pWindow, long nNewWidth, long nNewHeight )
+{
+ const bool bChanged = (nNewWidth != pWindow->GetOutputWidthPixel()) || (nNewHeight != pWindow->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->mnOutWidth = nNewWidth;
+ pWindow->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, long nNewWidth, 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)
+{
+ 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();
+ pOverlapWindow->ImplGetWindowImpl()->mpLastFocusWindow = pFocusWin;
+ pSVData->mpWinData->mpFocusWin = nullptr;
+
+ if ( pFocusWin->ImplGetWindowImpl()->mpCursor )
+ pFocusWin->ImplGetWindowImpl()->mpCursor->ImplHide();
+
+ // call the Deactivate
+ vcl::Window* pOldOverlapWindow = pFocusWin->ImplGetFirstOverlapWindow();
+ vcl::Window* pOldRealWindow = pOldOverlapWindow->ImplGetWindow();
+
+ pOldOverlapWindow->ImplGetWindowImpl()->mbActive = false;
+ pOldOverlapWindow->Deactivate();
+ if ( pOldRealWindow != pOldOverlapWindow )
+ {
+ pOldRealWindow->ImplGetWindowImpl()->mbActive = false;
+ pOldRealWindow->Deactivate();
+ }
+
+ // TrackingMode is ended in ImplHandleLoseFocus
+#ifdef _WIN32
+ // To avoid problems with the Unix IME
+ pFocusWin->EndExtTextInput();
+#endif
+
+ NotifyEvent aNEvt(MouseNotifyEvent::LOSEFOCUS, pFocusWin);
+ if (!ImplCallPreNotify(aNEvt))
+ pFocusWin->CompatLoseFocus();
+ pFocusWin->ImplCallDeactivateListeners(nullptr);
+ }
+ }
+
+ // Redraw all floating window inactive
+ if ( ImplGetWindowImpl()->mpFrameData->mbStartFocusState != bHasFocus )
+ ImplActivateFloatingWindows( this, bHasFocus );
+ }
+}
+
+static void ImplHandleGetFocus( vcl::Window* pWindow )
+{
+ 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 )
+{
+ 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()->mpFrameWindow == pWindow)
+ pSVData->mpWinData->mpTrackWin->EndTracking(TrackingEventFlags::Cancel);
+ }
+
+ 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 && !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;
+ }
+}
+
+static 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;
+}
+
+static MouseEventModifiers ImplGetMouseButtonMode( SalMouseEvent const * pEvent )
+{
+ MouseEventModifiers nMode = MouseEventModifiers::NONE;
+ if ( pEvent->mnButton == MOUSE_LEFT )
+ nMode |= MouseEventModifiers::SIMPLECLICK;
+ if ( (pEvent->mnButton == MOUSE_LEFT) && !(pEvent->mnCode & (MOUSE_MIDDLE | MOUSE_RIGHT)) )
+ nMode |= MouseEventModifiers::SELECT;
+ if ( (pEvent->mnButton == MOUSE_LEFT) && (pEvent->mnCode & KEY_MOD1) &&
+ !(pEvent->mnCode & (MOUSE_MIDDLE | MOUSE_RIGHT | KEY_SHIFT)) )
+ nMode |= MouseEventModifiers::MULTISELECT;
+ if ( (pEvent->mnButton == MOUSE_LEFT) && (pEvent->mnCode & KEY_SHIFT) &&
+ !(pEvent->mnCode & (MOUSE_MIDDLE | MOUSE_RIGHT | KEY_MOD1)) )
+ nMode |= MouseEventModifiers::RANGESELECT;
+ return nMode;
+}
+
+static bool ImplHandleSalMouseLeave( vcl::Window* pWindow, SalMouseEvent const * pEvent )
+{
+ return ImplHandleMouseEvent( pWindow, MouseNotifyEvent::MOUSEMOVE, true,
+ pEvent->mnX, pEvent->mnY,
+ pEvent->mnTime, pEvent->mnCode,
+ ImplGetMouseMoveMode( pEvent ) );
+}
+
+static bool ImplHandleSalMouseMove( vcl::Window* pWindow, SalMouseEvent const * pEvent )
+{
+ return ImplHandleMouseEvent( pWindow, MouseNotifyEvent::MOUSEMOVE, false,
+ pEvent->mnX, pEvent->mnY,
+ pEvent->mnTime, pEvent->mnCode,
+ ImplGetMouseMoveMode( pEvent ) );
+}
+
+static bool ImplHandleSalMouseButtonDown( vcl::Window* pWindow, SalMouseEvent const * pEvent )
+{
+ return ImplHandleMouseEvent( pWindow, MouseNotifyEvent::MOUSEBUTTONDOWN, false,
+ pEvent->mnX, pEvent->mnY,
+ pEvent->mnTime,
+#ifdef MACOSX
+ pEvent->mnButton | (pEvent->mnCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_MOD3)),
+#else
+ pEvent->mnButton | (pEvent->mnCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2)),
+#endif
+ ImplGetMouseButtonMode( pEvent ) );
+}
+
+static bool ImplHandleSalMouseButtonUp( vcl::Window* pWindow, SalMouseEvent const * pEvent )
+{
+ return ImplHandleMouseEvent( pWindow, MouseNotifyEvent::MOUSEBUTTONUP, false,
+ pEvent->mnX, pEvent->mnY,
+ pEvent->mnTime,
+#ifdef MACOSX
+ pEvent->mnButton | (pEvent->mnCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_MOD3)),
+#else
+ pEvent->mnButton | (pEvent->mnCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2)),
+#endif
+ ImplGetMouseButtonMode( pEvent ) );
+}
+
+static bool ImplHandleMenuEvent( vcl::Window const * pWindow, SalMenuEvent* pEvent, SalEvent nEvent )
+{
+ // Find SystemWindow and its Menubar and let it dispatch the command
+ bool bRet = false;
+ vcl::Window *pWin = pWindow->ImplGetWindowImpl()->mpFirstChild;
+ while ( pWin )
+ {
+ if ( pWin->ImplGetWindowImpl()->mbSysWin )
+ break;
+ pWin = pWin->ImplGetWindowImpl()->mpNext;
+ }
+ if( pWin )
+ {
+ MenuBar *pMenuBar = static_cast<SystemWindow*>(pWin)->GetMenuBar();
+ if( pMenuBar )
+ {
+ switch( nEvent )
+ {
+ case SalEvent::MenuActivate:
+ pMenuBar->HandleMenuActivateEvent( static_cast<Menu*>(pEvent->mpMenu) );
+ bRet = true;
+ break;
+ case SalEvent::MenuDeactivate:
+ pMenuBar->HandleMenuDeActivateEvent( static_cast<Menu*>(pEvent->mpMenu) );
+ bRet = true;
+ break;
+ case SalEvent::MenuHighlight:
+ bRet = pMenuBar->HandleMenuHighlightEvent( static_cast<Menu*>(pEvent->mpMenu), pEvent->mnId );
+ break;
+ case SalEvent::MenuButtonCommand:
+ bRet = pMenuBar->HandleMenuButtonEvent( pEvent->mnId );
+ break;
+ case SalEvent::MenuCommand:
+ bRet = pMenuBar->HandleMenuCommandEvent( static_cast<Menu*>(pEvent->mpMenu), pEvent->mnId );
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ return bRet;
+}
+
+static void ImplHandleSalKeyMod( vcl::Window* pWindow, SalKeyModEvent const * pEvent )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ vcl::Window* pTrackWin = pSVData->mpWinData->mpTrackWin;
+ if ( pTrackWin )
+ pWindow = pTrackWin;
+#ifdef MACOSX
+ sal_uInt16 nOldCode = pWindow->ImplGetWindowImpl()->mpFrameData->mnMouseCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_MOD3);
+#else
+ sal_uInt16 nOldCode = pWindow->ImplGetWindowImpl()->mpFrameData->mnMouseCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2);
+#endif
+ sal_uInt16 nNewCode = pEvent->mnCode;
+ if ( nOldCode != nNewCode )
+ {
+#ifdef MACOSX
+ nNewCode |= pWindow->ImplGetWindowImpl()->mpFrameData->mnMouseCode & ~(KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_MOD3);
+#else
+ nNewCode |= pWindow->ImplGetWindowImpl()->mpFrameData->mnMouseCode & ~(KEY_SHIFT | KEY_MOD1 | KEY_MOD2);
+#endif
+ pWindow->ImplGetWindowImpl()->mpFrameWindow->ImplCallMouseMove( nNewCode, true );
+ }
+
+ // #105224# send commandevent to allow special treatment of Ctrl-LeftShift/Ctrl-RightShift etc.
+ // + auto-accelerator feature, tdf#92630
+
+ // try to find a key input window...
+ vcl::Window* pChild = ImplGetKeyInputWindow( pWindow );
+ //...otherwise fail safe...
+ if (!pChild)
+ pChild = pWindow;
+
+ CommandModKeyData data( pEvent->mnModKeyCode, pEvent->mbDown );
+ ImplCallCommand( pChild, CommandEventId::ModKeyChange, &data );
+}
+
+static void ImplHandleInputLanguageChange( vcl::Window* pWindow )
+{
+ // find window
+ vcl::Window* pChild = ImplGetKeyInputWindow( pWindow );
+ if ( !pChild )
+ return;
+
+ ImplCallCommand( pChild, CommandEventId::InputLanguageChange );
+}
+
+static void ImplHandleSalSettings( SalEvent nEvent )
+{
+ Application* pApp = GetpApp();
+ if ( !pApp )
+ return;
+
+ if ( nEvent == SalEvent::SettingsChanged )
+ {
+ AllSettings aSettings = Application::GetSettings();
+ Application::MergeSystemSettings( aSettings );
+ pApp->OverrideSystemSettings( aSettings );
+ Application::SetSettings( aSettings );
+ }
+ else
+ {
+ DataChangedEventType nType;
+ switch ( nEvent )
+ {
+ case SalEvent::PrinterChanged:
+ ImplDeletePrnQueueList();
+ nType = DataChangedEventType::PRINTER;
+ break;
+ case SalEvent::DisplayChanged:
+ nType = DataChangedEventType::DISPLAY;
+ break;
+ case SalEvent::FontChanged:
+ OutputDevice::ImplUpdateAllFontData( true );
+ nType = DataChangedEventType::FONTS;
+ break;
+ default:
+ nType = DataChangedEventType::NONE;
+ break;
+ }
+
+ if ( nType != DataChangedEventType::NONE )
+ {
+ DataChangedEvent aDCEvt( nType );
+ Application::ImplCallEventListenersApplicationDataChanged(&aDCEvt);
+ Application::NotifyAllWindows( aDCEvt );
+ }
+ }
+}
+
+static void ImplHandleSalExtTextInputPos( vcl::Window* pWindow, SalExtTextInputPosEvent* pEvt )
+{
+ tools::Rectangle aCursorRect;
+ ImplHandleExtTextInputPos( pWindow, aCursorRect, pEvt->mnExtWidth, &pEvt->mbVertical );
+ if ( aCursorRect.IsEmpty() )
+ {
+ pEvt->mnX = -1;
+ pEvt->mnY = -1;
+ pEvt->mnWidth = -1;
+ pEvt->mnHeight = -1;
+ }
+ else
+ {
+ pEvt->mnX = aCursorRect.Left();
+ pEvt->mnY = aCursorRect.Top();
+ pEvt->mnWidth = aCursorRect.GetWidth();
+ pEvt->mnHeight = aCursorRect.GetHeight();
+ }
+}
+
+static bool ImplHandleShowDialog( vcl::Window* pWindow, ShowDialogId nDialogId )
+{
+ if( ! pWindow )
+ return false;
+
+ if( pWindow->GetType() == WindowType::BORDERWINDOW )
+ {
+ vcl::Window* pWrkWin = pWindow->GetWindow( GetWindowType::Client );
+ if( pWrkWin )
+ pWindow = pWrkWin;
+ }
+ CommandDialogData aCmdData( nDialogId );
+ return ImplCallCommand( pWindow, CommandEventId::ShowDialog, &aCmdData );
+}
+
+static void ImplHandleSurroundingTextRequest( vcl::Window *pWindow,
+ OUString& rText,
+ Selection &rSelRange )
+{
+ vcl::Window* pChild = ImplGetKeyInputWindow( pWindow );
+
+ if ( !pChild )
+ {
+ rText.clear();
+ rSelRange.setMin( 0 );
+ rSelRange.setMax( 0 );
+ }
+ else
+ {
+ rText = pChild->GetSurroundingText();
+ Selection aSel = pChild->GetSurroundingTextSelection();
+ rSelRange.setMin( aSel.Min() );
+ rSelRange.setMax( aSel.Max() );
+ }
+}
+
+static void ImplHandleSalSurroundingTextRequest( vcl::Window *pWindow,
+ SalSurroundingTextRequestEvent *pEvt )
+{
+ Selection aSelRange;
+ ImplHandleSurroundingTextRequest( pWindow, pEvt->maText, aSelRange );
+
+ aSelRange.Justify();
+
+ if( aSelRange.Min() < 0 )
+ pEvt->mnStart = 0;
+ else if( aSelRange.Min() > pEvt->maText.getLength() )
+ pEvt->mnStart = pEvt->maText.getLength();
+ else
+ pEvt->mnStart = aSelRange.Min();
+
+ if( aSelRange.Max() < 0 )
+ pEvt->mnStart = 0;
+ else if( aSelRange.Max() > pEvt->maText.getLength() )
+ pEvt->mnEnd = pEvt->maText.getLength();
+ else
+ pEvt->mnEnd = aSelRange.Max();
+}
+
+static void ImplHandleSurroundingTextSelectionChange( vcl::Window *pWindow,
+ sal_uLong nStart,
+ sal_uLong nEnd )
+{
+ vcl::Window* pChild = ImplGetKeyInputWindow( pWindow );
+ if( pChild )
+ {
+ CommandSelectionChangeData data( nStart, nEnd );
+ ImplCallCommand( pChild, CommandEventId::SelectionChange, &data );
+ }
+}
+
+static void ImplHandleStartReconversion( vcl::Window *pWindow )
+{
+ vcl::Window* pChild = ImplGetKeyInputWindow( pWindow );
+ if( pChild )
+ ImplCallCommand( pChild, CommandEventId::PrepareReconversion );
+}
+
+static void ImplHandleSalQueryCharPosition( vcl::Window *pWindow,
+ SalQueryCharPositionEvent *pEvt )
+{
+ pEvt->mbValid = false;
+ pEvt->mbVertical = false;
+ pEvt->mnCursorBoundX = 0;
+ pEvt->mnCursorBoundY = 0;
+ pEvt->mnCursorBoundWidth = 0;
+ pEvt->mnCursorBoundHeight = 0;
+
+ ImplSVData* pSVData = ImplGetSVData();
+ vcl::Window* pChild = pSVData->mpWinData->mpExtTextInputWin;
+
+ if ( !pChild )
+ pChild = ImplGetKeyInputWindow( pWindow );
+ else
+ {
+ // Test, if the Window is related to the frame
+ if ( !pWindow->ImplIsWindowOrChild( pChild ) )
+ pChild = ImplGetKeyInputWindow( pWindow );
+ }
+
+ if( pChild )
+ {
+ ImplCallCommand( pChild, CommandEventId::QueryCharPosition );
+
+ ImplWinData* pWinData = pChild->ImplGetWinData();
+ if ( pWinData->mpCompositionCharRects && pEvt->mnCharPos < o3tl::make_unsigned( pWinData->mnCompositionCharRects ) )
+ {
+ const OutputDevice *pChildOutDev = pChild->GetOutDev();
+ const tools::Rectangle& aRect = pWinData->mpCompositionCharRects[ pEvt->mnCharPos ];
+ tools::Rectangle aDeviceRect = pChildOutDev->ImplLogicToDevicePixel( aRect );
+ Point aAbsScreenPos = pChild->OutputToAbsoluteScreenPixel( pChild->ScreenToOutputPixel(aDeviceRect.TopLeft()) );
+ pEvt->mnCursorBoundX = aAbsScreenPos.X();
+ pEvt->mnCursorBoundY = aAbsScreenPos.Y();
+ pEvt->mnCursorBoundWidth = aDeviceRect.GetWidth();
+ pEvt->mnCursorBoundHeight = aDeviceRect.GetHeight();
+ pEvt->mbVertical = pWinData->mbVertical;
+ pEvt->mbValid = true;
+ }
+ }
+}
+
+bool ImplWindowFrameProc( vcl::Window* _pWindow, SalEvent nEvent, const void* pEvent )
+{
+ DBG_TESTSOLARMUTEX();
+
+ // Ensure the window survives during this method.
+ VclPtr<vcl::Window> pWindow( _pWindow );
+
+ bool bRet = false;
+
+ // #119709# for some unknown reason it is possible to receive events (in this case key events)
+ // although the corresponding VCL window must have been destroyed already
+ // at least ImplGetWindowImpl() was NULL in these cases, so check this here
+ if( pWindow->ImplGetWindowImpl() == nullptr )
+ return false;
+
+ switch ( nEvent )
+ {
+ case SalEvent::MouseMove:
+ bRet = ImplHandleSalMouseMove( pWindow, static_cast<SalMouseEvent const *>(pEvent) );
+ break;
+ case SalEvent::ExternalMouseMove:
+ {
+ MouseEvent const * pMouseEvt = static_cast<MouseEvent const *>(pEvent);
+ SalMouseEvent aSalMouseEvent;
+
+ aSalMouseEvent.mnTime = tools::Time::GetSystemTicks();
+ aSalMouseEvent.mnX = pMouseEvt->GetPosPixel().X();
+ aSalMouseEvent.mnY = pMouseEvt->GetPosPixel().Y();
+ aSalMouseEvent.mnButton = 0;
+ aSalMouseEvent.mnCode = pMouseEvt->GetButtons() | pMouseEvt->GetModifier();
+
+ bRet = ImplHandleSalMouseMove( pWindow, &aSalMouseEvent );
+ }
+ break;
+ case SalEvent::MouseLeave:
+ bRet = ImplHandleSalMouseLeave( pWindow, static_cast<SalMouseEvent const *>(pEvent) );
+ break;
+ case SalEvent::MouseButtonDown:
+ bRet = ImplHandleSalMouseButtonDown( pWindow, static_cast<SalMouseEvent const *>(pEvent) );
+ break;
+ case SalEvent::ExternalMouseButtonDown:
+ {
+ MouseEvent const * pMouseEvt = static_cast<MouseEvent const *>(pEvent);
+ SalMouseEvent aSalMouseEvent;
+
+ aSalMouseEvent.mnTime = tools::Time::GetSystemTicks();
+ aSalMouseEvent.mnX = pMouseEvt->GetPosPixel().X();
+ aSalMouseEvent.mnY = pMouseEvt->GetPosPixel().Y();
+ aSalMouseEvent.mnButton = pMouseEvt->GetButtons();
+ aSalMouseEvent.mnCode = pMouseEvt->GetButtons() | pMouseEvt->GetModifier();
+
+ bRet = ImplHandleSalMouseButtonDown( pWindow, &aSalMouseEvent );
+ }
+ break;
+ case SalEvent::MouseButtonUp:
+ bRet = ImplHandleSalMouseButtonUp( pWindow, static_cast<SalMouseEvent const *>(pEvent) );
+ break;
+ case SalEvent::ExternalMouseButtonUp:
+ {
+ MouseEvent const * pMouseEvt = static_cast<MouseEvent const *>(pEvent);
+ SalMouseEvent aSalMouseEvent;
+
+ aSalMouseEvent.mnTime = tools::Time::GetSystemTicks();
+ aSalMouseEvent.mnX = pMouseEvt->GetPosPixel().X();
+ aSalMouseEvent.mnY = pMouseEvt->GetPosPixel().Y();
+ aSalMouseEvent.mnButton = pMouseEvt->GetButtons();
+ aSalMouseEvent.mnCode = pMouseEvt->GetButtons() | pMouseEvt->GetModifier();
+
+ bRet = ImplHandleSalMouseButtonUp( pWindow, &aSalMouseEvent );
+ }
+ break;
+ case SalEvent::MouseActivate:
+ bRet = false;
+ break;
+ case SalEvent::KeyInput:
+ {
+ SalKeyEvent const * pKeyEvt = static_cast<SalKeyEvent const *>(pEvent);
+ bRet = ImplHandleKey( pWindow, MouseNotifyEvent::KEYINPUT,
+ pKeyEvt->mnCode, pKeyEvt->mnCharCode, pKeyEvt->mnRepeat, true );
+ }
+ break;
+ case SalEvent::ExternalKeyInput:
+ {
+ KeyEvent const * pKeyEvt = static_cast<KeyEvent const *>(pEvent);
+ bRet = ImplHandleKey( pWindow, MouseNotifyEvent::KEYINPUT,
+ pKeyEvt->GetKeyCode().GetFullCode(), pKeyEvt->GetCharCode(), pKeyEvt->GetRepeat(), false );
+ }
+ break;
+ case SalEvent::KeyUp:
+ {
+ SalKeyEvent const * pKeyEvt = static_cast<SalKeyEvent const *>(pEvent);
+ bRet = ImplHandleKey( pWindow, MouseNotifyEvent::KEYUP,
+ pKeyEvt->mnCode, pKeyEvt->mnCharCode, pKeyEvt->mnRepeat, true );
+ }
+ break;
+ case SalEvent::ExternalKeyUp:
+ {
+ KeyEvent const * pKeyEvt = static_cast<KeyEvent const *>(pEvent);
+ bRet = ImplHandleKey( pWindow, MouseNotifyEvent::KEYUP,
+ pKeyEvt->GetKeyCode().GetFullCode(), pKeyEvt->GetCharCode(), pKeyEvt->GetRepeat(), false );
+ }
+ break;
+ case SalEvent::KeyModChange:
+ ImplHandleSalKeyMod( pWindow, static_cast<SalKeyModEvent const *>(pEvent) );
+ break;
+
+ case SalEvent::InputLanguageChange:
+ ImplHandleInputLanguageChange( pWindow );
+ break;
+
+ case SalEvent::MenuActivate:
+ case SalEvent::MenuDeactivate:
+ case SalEvent::MenuHighlight:
+ case SalEvent::MenuCommand:
+ case SalEvent::MenuButtonCommand:
+ bRet = ImplHandleMenuEvent( pWindow, const_cast<SalMenuEvent *>(static_cast<SalMenuEvent const *>(pEvent)), nEvent );
+ break;
+
+ case SalEvent::WheelMouse:
+ bRet = ImplHandleWheelEvent( pWindow, *static_cast<const SalWheelMouseEvent*>(pEvent));
+ break;
+
+ case SalEvent::Paint:
+ {
+ SalPaintEvent const * pPaintEvt = static_cast<SalPaintEvent const *>(pEvent);
+
+ if( AllSettings::GetLayoutRTL() )
+ {
+ SalFrame* pSalFrame = pWindow->ImplGetWindowImpl()->mpFrame;
+ const_cast<SalPaintEvent *>(pPaintEvt)->mnBoundX = pSalFrame->maGeometry.nWidth-pPaintEvt->mnBoundWidth-pPaintEvt->mnBoundX;
+ }
+
+ tools::Rectangle aBoundRect( Point( pPaintEvt->mnBoundX, pPaintEvt->mnBoundY ),
+ Size( pPaintEvt->mnBoundWidth, pPaintEvt->mnBoundHeight ) );
+ ImplHandlePaint( pWindow, aBoundRect, pPaintEvt->mbImmediateUpdate );
+ }
+ break;
+
+ case SalEvent::Move:
+ ImplHandleMove( pWindow );
+ break;
+
+ case SalEvent::Resize:
+ {
+ long nNewWidth;
+ long nNewHeight;
+ pWindow->ImplGetWindowImpl()->mpFrame->GetClientSize( nNewWidth, nNewHeight );
+ ImplHandleResize( pWindow, nNewWidth, nNewHeight );
+ }
+ break;
+
+ case SalEvent::MoveResize:
+ {
+ SalFrameGeometry g = pWindow->ImplGetWindowImpl()->mpFrame->GetGeometry();
+ ImplHandleMoveResize( pWindow, g.nWidth, g.nHeight );
+ }
+ break;
+
+ case SalEvent::ClosePopups:
+ {
+ KillOwnPopups( pWindow );
+ }
+ break;
+
+ case SalEvent::GetFocus:
+ ImplHandleGetFocus( pWindow );
+ break;
+ case SalEvent::LoseFocus:
+ ImplHandleLoseFocus( pWindow );
+ break;
+
+ case SalEvent::Close:
+ ImplHandleClose( pWindow );
+ break;
+
+ case SalEvent::Shutdown:
+ {
+ static bool bInQueryExit = false;
+ if( !bInQueryExit )
+ {
+ bInQueryExit = true;
+ if ( GetpApp()->QueryExit() )
+ {
+ // end the message loop
+ Application::Quit();
+ return false;
+ }
+ else
+ {
+ bInQueryExit = false;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ case SalEvent::SettingsChanged:
+ case SalEvent::PrinterChanged:
+ case SalEvent::DisplayChanged:
+ case SalEvent::FontChanged:
+ ImplHandleSalSettings( nEvent );
+ break;
+
+ case SalEvent::UserEvent:
+ ImplHandleUserEvent( const_cast<ImplSVEvent *>(static_cast<ImplSVEvent const *>(pEvent)) );
+ break;
+
+ case SalEvent::ExtTextInput:
+ {
+ SalExtTextInputEvent const * pEvt = static_cast<SalExtTextInputEvent const *>(pEvent);
+ bRet = ImplHandleExtTextInput( pWindow,
+ pEvt->maText, pEvt->mpTextAttr,
+ pEvt->mnCursorPos, pEvt->mnCursorFlags );
+ }
+ break;
+ case SalEvent::EndExtTextInput:
+ bRet = ImplHandleEndExtTextInput();
+ break;
+ case SalEvent::ExtTextInputPos:
+ ImplHandleSalExtTextInputPos( pWindow, const_cast<SalExtTextInputPosEvent *>(static_cast<SalExtTextInputPosEvent const *>(pEvent)) );
+ break;
+ case SalEvent::InputContextChange:
+ bRet = ImplHandleInputContextChange( pWindow );
+ break;
+ case SalEvent::ShowDialog:
+ {
+ ShowDialogId nLOKWindowId = static_cast<ShowDialogId>(reinterpret_cast<sal_IntPtr>(pEvent));
+ bRet = ImplHandleShowDialog( pWindow, nLOKWindowId );
+ }
+ break;
+ case SalEvent::SurroundingTextRequest:
+ ImplHandleSalSurroundingTextRequest( pWindow, const_cast<SalSurroundingTextRequestEvent *>(static_cast<SalSurroundingTextRequestEvent const *>(pEvent)) );
+ break;
+ case SalEvent::SurroundingTextSelectionChange:
+ {
+ SalSurroundingTextSelectionChangeEvent const * pEvt
+ = static_cast<SalSurroundingTextSelectionChangeEvent const *>(pEvent);
+ ImplHandleSurroundingTextSelectionChange( pWindow,
+ pEvt->mnStart,
+ pEvt->mnEnd );
+ [[fallthrough]]; // TODO: Fallthrough really intended?
+ }
+ case SalEvent::StartReconversion:
+ ImplHandleStartReconversion( pWindow );
+ break;
+
+ case SalEvent::QueryCharPosition:
+ ImplHandleSalQueryCharPosition( pWindow, const_cast<SalQueryCharPositionEvent *>(static_cast<SalQueryCharPositionEvent const *>(pEvent)) );
+ break;
+
+ case SalEvent::Swipe:
+ bRet = ImplHandleSwipe(pWindow, *static_cast<const SalSwipeEvent*>(pEvent));
+ break;
+
+ case SalEvent::LongPress:
+ bRet = ImplHandleLongPress(pWindow, *static_cast<const SalLongPressEvent*>(pEvent));
+ break;
+
+ case SalEvent::ExternalGesture:
+ {
+ auto const * pGestureEvent = static_cast<GestureEvent const *>(pEvent);
+
+ SalGestureEvent aSalGestureEvent;
+ aSalGestureEvent.mfOffset = pGestureEvent->mnOffset;
+ aSalGestureEvent.mnX = pGestureEvent->mnX;
+ aSalGestureEvent.mnY = pGestureEvent->mnY;
+ aSalGestureEvent.meEventType = pGestureEvent->meEventType;
+ aSalGestureEvent.meOrientation = pGestureEvent->meOrientation;
+
+ bRet = ImplHandleGestureEvent(pWindow, aSalGestureEvent);
+ }
+ break;
+ case SalEvent::Gesture:
+ {
+ auto const * aSalGestureEvent = static_cast<SalGestureEvent const *>(pEvent);
+ bRet = ImplHandleGestureEvent(pWindow, *aSalGestureEvent);
+ }
+ break;
+ default:
+ SAL_WARN( "vcl.layout", "ImplWindowFrameProc(): unknown event (" << static_cast<int>(nEvent) << ")" );
+ break;
+ }
+
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/wrkwin.cxx b/vcl/source/window/wrkwin.cxx
new file mode 100644
index 000000000..58d476d15
--- /dev/null
+++ b/vcl/source/window/wrkwin.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 <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;
+
+ maLayoutIdle.SetDebugName( "vcl::WorkWindow maLayoutIdle" );
+}
+
+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 )
+{
+ ImplInitWorkWindowData();
+}
+
+WorkWindow::WorkWindow( vcl::Window* pParent, WinBits nStyle ) :
+ SystemWindow( WindowType::WORKWINDOW )
+{
+ ImplInitWorkWindowData();
+ ImplInit( pParent, nStyle );
+}
+
+WorkWindow::WorkWindow( vcl::Window* pParent, const css::uno::Any& aSystemWorkWindowToken, WinBits nStyle ) :
+ SystemWindow( WindowType::WORKWINDOW )
+{
+ ImplInitWorkWindowData();
+ mbSysChild = true;
+ ImplInit( pParent, nStyle, aSystemWorkWindowToken );
+}
+
+WorkWindow::WorkWindow( SystemParentData* pParent ) :
+ SystemWindow( WindowType::WORKWINDOW )
+{
+ 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 )
+ {
+ // Dispose of the canvas implementation, which might rely on
+ // screen-specific system data.
+ css::uno::Reference< css::rendering::XCanvas > xCanvas( mpWindowImpl->mxCanvas );
+ if( xCanvas.is() )
+ {
+ css::uno::Reference< css::lang::XComponent > xCanvasComponent( xCanvas, css::uno::UNO_QUERY );
+ if( xCanvasComponent.is() )
+ xCanvasComponent->dispose();
+ }
+
+ mpWindowImpl->mpFrameWindow->mpWindowImpl->mbWaitSystemResize = true;
+ ImplGetFrame()->ShowFullScreen( bFullScreenMode, nDisplayScreen );
+ }
+}
+
+void WorkWindow::StartPresentationMode( PresentationFlags nFlags )
+{
+ return StartPresentationMode( false/*bPresentation*/, nFlags, GetScreenNumber());
+}
+
+void WorkWindow::StartPresentationMode( bool bPresentation, PresentationFlags nFlags, sal_Int32 nDisplayScreen )
+{
+ if ( !bPresentation == !mbPresentationMode )
+ return;
+
+ if ( bPresentation )
+ {
+ mbPresentationMode = true;
+ mbPresentationVisible = IsVisible();
+ mbPresentationFull = mbFullScreenMode;
+ mnPresentationFlags = nFlags;
+
+ ShowFullScreenMode( true, nDisplayScreen );
+ if ( !mbSysChild )
+ {
+ if ( mnPresentationFlags & PresentationFlags::HideAllApps )
+ mpWindowImpl->mpFrame->SetAlwaysOnTop( true );
+ ToTop();
+ mpWindowImpl->mpFrame->StartPresentation( true );
+ }
+
+ Show();
+ }
+ else
+ {
+ Show( mbPresentationVisible );
+ if ( !mbSysChild )
+ {
+ mpWindowImpl->mpFrame->StartPresentation( false );
+ if ( mnPresentationFlags & PresentationFlags::HideAllApps )
+ mpWindowImpl->mpFrame->SetAlwaysOnTop( false );
+ }
+ ShowFullScreenMode( mbPresentationFull, nDisplayScreen );
+
+ mbPresentationMode = false;
+ mbPresentationVisible = false;
+ mbPresentationFull = false;
+ mnPresentationFlags = PresentationFlags::NONE;
+ }
+}
+
+bool WorkWindow::IsMinimized() const
+{
+ //return mpWindowImpl->mpFrameData->mbMinimized;
+ SalFrameState aState;
+ if (mpWindowImpl->mpFrame->GetWindowState(&aState))
+ return bool(aState.mnState & WindowStateState::Minimized);
+ else
+ return false;
+}
+
+void WorkWindow::SetPluginParent( SystemParentData* pParent )
+{
+ SAL_WARN_IF( mbPresentationMode || mbFullScreenMode, "vcl", "SetPluginParent in fullscreen or presentation mode !" );
+
+ bool bWasDnd = Window::ImplStopDnd();
+
+ bool bShown = IsVisible();
+ Show( false );
+ mpWindowImpl->mpFrame->SetPluginParent( pParent );
+ Show( bShown );
+
+ if( bWasDnd )
+ Window::ImplStartDnd();
+}
+
+void WorkWindow::ImplSetFrameState( WindowStateState aFrameState )
+{
+ SalFrameState aState;
+ aState.mnMask = WindowStateMask::State;
+ aState.mnState = aFrameState;
+ mpWindowImpl->mpFrame->SetWindowState( &aState );
+}
+
+void WorkWindow::Minimize()
+{
+ ImplSetFrameState( WindowStateState::Minimized );
+}
+
+void WorkWindow::Restore()
+{
+ ImplSetFrameState( WindowStateState::Normal );
+}
+
+bool WorkWindow::Close()
+{
+ bool bCanClose = SystemWindow::Close();
+
+ // if it's the application window then close the application
+ if (bCanClose && (ImplGetSVData()->maFrameData.mpAppWin == this))
+ Application::Quit();
+
+ return bCanClose;
+}
+
+void WorkWindow::Maximize( bool bMaximize )
+{
+ ImplSetFrameState( bMaximize ? WindowStateState::Maximized : WindowStateState::Normal );
+}
+
+bool WorkWindow::IsMaximized() const
+{
+ bool bRet = false;
+
+ SalFrameState aState;
+ if( mpWindowImpl->mpFrame->GetWindowState( &aState ) )
+ {
+ if( aState.mnState & (WindowStateState::Maximized |
+ WindowStateState::MaximizedHorz |
+ WindowStateState::MaximizedVert ) )
+ bRet = true;
+ }
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
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 000000000..032df3df6
--- /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 000000000..524906f31
--- /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 000000000..ccb892d77
--- /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 000000000..b4a1627f3
--- /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 000000000..e4c1f6359
--- /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 000000000..24a3b12c2
--- /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 000000000..ee3300ef2
--- /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 000000000..e64144459
--- /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 000000000..ca27ee855
--- /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 000000000..667b0960f
--- /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 000000000..958c85600
--- /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 000000000..667b0960f
--- /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 000000000..ee3300ef2
--- /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 000000000..dad0069ef
--- /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 000000000..4bdcd409d
--- /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 000000000..b4a885d91
--- /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 000000000..3c3b9fc56
--- /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 000000000..66772c752
--- /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 000000000..01cac0c3e
--- /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 000000000..df8c90ccc
--- /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 000000000..a300f6f3a
--- /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 000000000..5eb82b2f8
--- /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 000000000..41482a4bc
--- /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 000000000..6e6676052
--- /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 000000000..a24166a76
--- /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 000000000..f1f898096
--- /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 000000000..80fda60b9
--- /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 000000000..931417d62
--- /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 000000000..b6f7169ba
--- /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 000000000..3c13bd451
--- /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 000000000..bb4d98cf1
--- /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 000000000..17997e4a5
--- /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 000000000..6027d35a4
--- /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 000000000..398e1cb2b
--- /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 000000000..b1877520e
--- /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 000000000..309d850eb
--- /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 000000000..909397fb5
--- /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 000000000..eeb5475b1
--- /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 000000000..16d2a5109
--- /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 000000000..0db53e3d4
--- /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 000000000..cf02114dd
--- /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 000000000..f812bcba7
--- /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 000000000..9bc5b2973
--- /dev/null
+++ b/vcl/uiconfig/ui/aboutbox.ui
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface domain="vcl">
+ <requires lib="gtk+" version="3.18"/>
+ <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="stock">gtk-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>
+ <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>
+ <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_left">12</property>
+ <property name="margin_right">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 000000000..24b07878f
--- /dev/null
+++ b/vcl/uiconfig/ui/combobox.ui
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.2 -->
+<interface domain="vcl">
+ <requires lib="gtk+" version="3.18"/>
+ <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="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>
+ <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 000000000..0bae75795
--- /dev/null
+++ b/vcl/uiconfig/ui/cupspassworddialog.ui
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface domain="vcl">
+ <requires lib="gtk+" version="3.18"/>
+ <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">gtk-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_stock">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">gtk-cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">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="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="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="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="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/editmenu.ui b/vcl/uiconfig/ui/editmenu.ui
new file mode 100644
index 000000000..522fd2e0e
--- /dev/null
+++ b/vcl/uiconfig/ui/editmenu.ui
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.20.0 -->
+<interface domain="vcl">
+ <requires lib="gtk+" version="3.18"/>
+ <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="label" translatable="yes" context="editmenu|specialchar">_Special Character...</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 000000000..92c5e39ee
--- /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.18"/>
+ <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 000000000..5f5627460
--- /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.18"/>
+ <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/moreoptionsdialog.ui b/vcl/uiconfig/ui/moreoptionsdialog.ui
new file mode 100644
index 000000000..4247fae4a
--- /dev/null
+++ b/vcl/uiconfig/ui/moreoptionsdialog.ui
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface domain="vcl">
+ <requires lib="gtk+" version="3.18"/>
+ <object class="GtkDialog" id="MoreOptionsDialog">
+ <property name="can_focus">False</property>
+ <property name="border_width">6</property>
+ <property name="title" translatable="yes" context="moreoptionsdialog|moreprintingoptions">More Printing Options</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="box">
+ <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="buttonbox">
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="ok">
+ <property name="label" context="moreoptionsdialog|ok">gtk-ok</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">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="cancel">
+ <property name="label" context="moreoptionsdialog|cancel">gtk-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_stock">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="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">6</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkCheckButton" id="singlejobs">
+ <property name="label" translatable="yes" context="moreoptionsdialog|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>
+ </object>
+ <packing>
+ <property name="expand">False</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>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/vcl/uiconfig/ui/printdialog.ui b/vcl/uiconfig/ui/printdialog.ui
new file mode 100644
index 000000000..e78d88d4e
--- /dev/null
+++ b/vcl/uiconfig/ui/printdialog.ui
@@ -0,0 +1,1295 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface domain="vcl">
+ <requires lib="gtk+" version="3.18"/>
+ <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="stock">gtk-go-back</property>
+ </object>
+ <object class="GtkImage" id="imgFirst">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="stock">gtk-goto-first</property>
+ </object>
+ <object class="GtkImage" id="imgForward">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="stock">gtk-go-forward</property>
+ </object>
+ <object class="GtkImage" id="imgLast">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="stock">gtk-goto-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 type="titlebar">
+ <placeholder/>
+ </child>
+ <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">gtk-help</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">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="moreoptionsbtn">
+ <property name="label" translatable="yes" context="printdialog|moreoptions">More Options...</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="margin_right">10</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>
+ <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>
+ </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">gtk-cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">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_right">6</property>
+ <property name="hexpand">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="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_right">6</property>
+ </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>
+ <child>
+ <object class="GtkBox" id="box14">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">6</property>
+ <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>
+ </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="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>
+ </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>
+ <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">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">2</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>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">3</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>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <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>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">end</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">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box10">
+ <property name="visible">True</property>
+ <property name="can_focus">False</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="halign">center</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="pack_type">end</property>
+ <property name="position">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="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_left">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="GtkAlignment" id="alignment">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">6</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkBox" id="box5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</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>
+ </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>
+ </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>
+ </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>
+ </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>
+ <object class="GtkAlignment" id="alPrintRange">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">6</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkGrid" id="rangegrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">2</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>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="width">2</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>
+ </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="placeholder_text" translatable="yes" context="printdialog|pagerange">e.g.: 1, 3-5, 7, 9</property>
+ </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_top">2</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">rbAllPages</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ <property name="width">2</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="mnemonic_widget">evenoddbox</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">3</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>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">3</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>
+ <object class="GtkGrid" id="gdCopiesExtra">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">2</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>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</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">0</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>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</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>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</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="mnemonic_widget">copycount</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="valign">center</property>
+ <property name="hexpand">True</property>
+ <property name="activates_default">True</property>
+ <property name="text">1</property>
+ <property name="adjustment">adjustment2</property>
+ <property name="value">1</property>
+ </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">reverseorder</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">4</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>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">4</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>
+ </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="stock">gtk-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>
+ <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">_more</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>
+ </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>
+ <object class="GtkAlignment" id="alignment3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">6</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <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>
+ </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>
+ </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>
+ </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>
+ </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>
+ <object class="GtkGrid" id="expandpagegrid">
+ <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="GtkBox" id="box12">
+ <property name="visible">True</property>
+ <property name="can_focus">False</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="use_underline">True</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ <accessibility>
+ <relation type="label-for" target="pagespersheetbox"/>
+ </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="pagespersheettxt">
+ <property name="visible">True</property>
+ <property name="can_focus">False</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="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>
+ </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="halign">end</property>
+ <property name="label" translatable="yes" context="printdialog|pagespersheettxt">Pages:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">pagerows</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="adjustment">adjustment3</property>
+ <property name="value">1</property>
+ </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="adjustment">adjustment4</property>
+ <property name="value">1</property>
+ </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="halign">end</property>
+ <property name="label" translatable="yes" context="printdialog|pagemargintxt1">Margin:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">pagemarginsb</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="adjustment">adjustment5</property>
+ </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="halign">end</property>
+ <property name="label" translatable="yes" context="printdialog|sheetmargintxt1">Distance:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">sheetmarginsb</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="adjustment">adjustment6</property>
+ </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="halign">end</property>
+ <property name="label" translatable="yes" context="printdialog|labelorder">Order:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">orderbox</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="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>
+ </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>
+ </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>
+ </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>
+ </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">m_ore</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>
+ </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>
+ </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>
+</interface>
diff --git a/vcl/uiconfig/ui/printerdevicepage.ui b/vcl/uiconfig/ui/printerdevicepage.ui
new file mode 100644
index 000000000..48ea728fb
--- /dev/null
+++ b/vcl/uiconfig/ui/printerdevicepage.ui
@@ -0,0 +1,264 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.20.4 -->
+<interface domain="vcl">
+ <requires lib="gtk+" version="3.18"/>
+ <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>
+ <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>
+ <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>
+ </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>
+ <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">2</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">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label9">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" context="printerdevicepage|label9">Printer language _type:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">level</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="level">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <items>
+ <item id="0" translatable="yes" context="printerdevicepage|liststore1">Automatic : %s</item>
+ <item id="1" translatable="yes" context="printerdevicepage|liststore1">PostScript (Level from driver)</item>
+ <item id="2" translatable="yes" context="printerdevicepage|liststore1">PostScript Level 1</item>
+ <item id="3" translatable="yes" context="printerdevicepage|liststore1">PostScript Level 2</item>
+ <item id="4" translatable="yes" context="printerdevicepage|liststore1">PostScript Level 3</item>
+ <item id="10" translatable="yes" context="printerdevicepage|liststore1">PDF</item>
+ </items>
+ </object>
+ <packing>
+ <property name="left_attach">1</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">1</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">2</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 000000000..836c09a28
--- /dev/null
+++ b/vcl/uiconfig/ui/printerpaperpage.ui
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.20.4 -->
+<interface domain="vcl">
+ <requires lib="gtk+" version="3.18"/>
+ <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 000000000..32b3a98d3
--- /dev/null
+++ b/vcl/uiconfig/ui/printerpropertiesdialog.ui
@@ -0,0 +1,176 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.20.4 -->
+<interface domain="vcl">
+ <requires lib="gtk+" version="3.18"/>
+ <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">gtk-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_stock">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">gtk-cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">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>
+ <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>
+ <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 000000000..efccafd91
--- /dev/null
+++ b/vcl/uiconfig/ui/printprogressdialog.ui
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.18.3 -->
+<interface domain="vcl">
+ <requires lib="gtk+" version="3.18"/>
+ <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">gtk-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_stock">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="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>
+ </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 000000000..cc3278e9f
--- /dev/null
+++ b/vcl/uiconfig/ui/querydialog.ui
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.20.4 -->
+<interface domain="vcl">
+ <requires lib="gtk+" version="3.18"/>
+ <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">gtk-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_stock">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">gtk-cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">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="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="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 000000000..722a1e810
--- /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.18"/>
+ <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">gtk-close</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">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 000000000..76f92607f
--- /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.18"/>
+ <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 000000000..500d3b133
--- /dev/null
+++ b/vcl/unx/generic/app/gendata.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 <unx/gendata.hxx>
+
+#include <unx/fontmanager.hxx>
+#include <unx/glyphcache.hxx>
+
+GenericUnixSalData::GenericUnixSalData(GenericUnixSalDataType const t, SalInstance* const pInstance)
+ : m_eType(t)
+ , m_pDisplay(nullptr)
+{
+ m_pInstance = pInstance;
+ SetSalData(this);
+}
+
+GenericUnixSalData::~GenericUnixSalData() {}
+
+void GenericUnixSalData::InitFreetypeManager() { m_pFreetypeManager.reset(new FreetypeManager); }
+
+void GenericUnixSalData::InitPrintFontManager()
+{
+ GetFreetypeManager();
+ m_pPrintFontManager.reset(new psp::PrintFontManager);
+ m_pPrintFontManager->initialize();
+}
+
+/* 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 000000000..b1dbef3f5
--- /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 000000000..b6611631b
--- /dev/null
+++ b/vcl/unx/generic/app/geninst.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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
+
+#include <config_features.h>
+#if HAVE_FEATURE_OPENGL
+#include <vcl/opengl/OpenGLContext.hxx>
+#endif
+#include <unx/geninst.h>
+
+// SalYieldMutex
+
+SalYieldMutex::SalYieldMutex()
+{
+#if HAVE_FEATURE_OPENGL
+ SetBeforeReleaseHandler( &OpenGLContext::prepareForYield );
+#endif
+}
+
+SalYieldMutex::~SalYieldMutex()
+{
+}
+
+SalGenericInstance::~SalGenericInstance()
+{
+}
+
+OUString SalGenericInstance::getOSVersion()
+{
+ OUString aKernelVer = "unknown";
+
+// not so generic, but at least shared between all unix backend
+#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 ..."
+ OUString aVers = aKernelVer.getToken( 2, ' ' );
+ // "3.16.7-29-desktop ..."
+ sal_Int32 nTooDetailed = aVers.indexOf( '.', 2);
+ if (nTooDetailed < 1 || nTooDetailed > 8)
+ aKernelVer = "Linux (misparsed version)";
+ else // "3.16.7-29-desktop ..."
+ aKernelVer = "Linux " + aVers.copy(0, nTooDetailed);
+ }
+ fclose( pVersion );
+ }
+#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 000000000..98371c548
--- /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 000000000..4e5f9952c
--- /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, 1024 /* 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 000000000..ad42c079c
--- /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;
+}
+
+static 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->aWindow;
+
+ // 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 )
+ {
+ if( pFrame )
+ {
+ 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) )
+ {
+ maClientData.pFrame = pFocusFrame;
+
+ const SystemEnvData* pEnv = pFocusFrame->GetSystemData();
+ ::Window aClientWindow = pEnv->aShellWindow;
+ ::Window aFocusWindow = pEnv->aWindow;
+
+ 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 )
+ {
+ 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 000000000..a9e9fb42f
--- /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 )
+ {
+ 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 000000000..a77632a3e
--- /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 000000000..0fc4d7933
--- /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 000000000..742ebef20
--- /dev/null
+++ b/vcl/unx/generic/app/keysymnames.cxx
@@ -0,0 +1,507 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/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 !
+
+ static 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"}
+ };
+
+ static 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" }
+ };
+
+ static 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" }
+ };
+
+ static 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" },
+ };
+
+ static 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" },
+ };
+
+ static 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" },
+ };
+
+ static 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" },
+ };
+
+ static 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" },
+ };
+
+ static 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" },
+ };
+
+ static 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" },
+ };
+
+ static 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" },
+ };
+
+ static 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" },
+ };
+
+ static 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" },
+ };
+
+ static 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" },
+ };
+
+ static 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" },
+ };
+
+ static 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" },
+ };
+
+ static const struct KeyboardReplacements aKeyboards[] =
+ {
+ { "ca", aImplReplacements_Catalan, SAL_N_ELEMENTS(aImplReplacements_Catalan) },
+ { "de", aImplReplacements_German, SAL_N_ELEMENTS(aImplReplacements_German) },
+ { "sl", aImplReplacements_Slovenian, SAL_N_ELEMENTS(aImplReplacements_Slovenian) },
+ { "es", aImplReplacements_Spanish, SAL_N_ELEMENTS(aImplReplacements_Spanish) },
+ { "et", aImplReplacements_Estonian, SAL_N_ELEMENTS(aImplReplacements_Estonian) },
+ { "fr", aImplReplacements_French, SAL_N_ELEMENTS(aImplReplacements_French) },
+ { "hu", aImplReplacements_Hungarian, SAL_N_ELEMENTS(aImplReplacements_Hungarian) },
+ { "it", aImplReplacements_Italian, SAL_N_ELEMENTS(aImplReplacements_Italian) },
+ { "lt", aImplReplacements_Lithuanian, SAL_N_ELEMENTS(aImplReplacements_Lithuanian) },
+ { "nl", aImplReplacements_Dutch, SAL_N_ELEMENTS(aImplReplacements_Dutch) },
+ { "no", aImplReplacements_Norwegian, SAL_N_ELEMENTS(aImplReplacements_Norwegian) },
+ { "pt", aImplReplacements_Portuguese, SAL_N_ELEMENTS(aImplReplacements_Portuguese) },
+ { "ru", aImplReplacements_Russian, SAL_N_ELEMENTS(aImplReplacements_Russian) },
+ { "sv", aImplReplacements_Swedish, SAL_N_ELEMENTS(aImplReplacements_Swedish) },
+ { "tr", aImplReplacements_Turkish, SAL_N_ELEMENTS(aImplReplacements_Turkish) },
+ };
+
+ // translate keycodes, used within the displayed menu shortcuts
+ OUString getKeysymReplacementName( const OUString& pLang, KeySym nSymbol )
+ {
+ for(const auto & rKeyboard : aKeyboards)
+ {
+ if( pLang.equalsAscii( 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 000000000..cb3698365
--- /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;
+}
+
+static 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 )
+ {
+ int nRet = pWrapper->XRRUpdateConfiguration( pEvent );
+ if( nRet == 1 && pEvent->type != ConfigureNotify) // this should then be a XRRScreenChangeNotifyEvent
+ {
+ // 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 = Size( 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 000000000..f2fe146f5
--- /dev/null
+++ b/vcl/unx/generic/app/saldata.cxx
@@ -0,0 +1,782 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 AIX
+#include <strings.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()
+{
+ SalData * p1 = ImplGetSVData()->mpSalData;
+ OSL_ASSERT(p1 != nullptr);
+ X11SalData * p2 = dynamic_cast< X11SalData * >(p1);
+ OSL_ASSERT(p2 != nullptr);
+ return p2;
+}
+
+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;
+}
+
+}
+
+static const struct timeval noyield_ = { 0, 0 };
+static 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( GenericUnixSalDataType t, SalInstance *pInstance )
+ : GenericUnixSalData( t, pInstance )
+{
+ 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)
+ {
+ // 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 000000000..679014cd5
--- /dev/null
+++ b/vcl/unx/generic/app/saldisp.cxx
@@ -0,0 +1,2889 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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) || defined(AIX)
+#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>
+
+#ifdef USE_XINERAMA_XORG
+#include <X11/extensions/Xinerama.h>
+#endif
+
+#include <opengl/zone.hxx>
+
+#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 <unx/salbmp.h>
+#include <osl/diagnose.h>
+#include <unx/salobj.h>
+#include <unx/sm.hxx>
+#include <unx/wmadaptor.hxx>
+#include <unx/x11/xrender_peer.hxx>
+#include <unx/glyphcache.hxx>
+
+#include <vcl/opengl/OpenGLHelper.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 ) ); }
+
+static double Hypothenuse( long w, long h )
+{ return sqrt( static_cast<double>((w*w)+(h*h)) ); }
+#endif
+
+static int ColorDiff( int r, int g, int b )
+{ return (r*r)+(g*g)+(b*b); }
+
+static int ColorDiff( Color c1, int r, int g, int b )
+{ return ColorDiff( static_cast<int>(c1.GetRed())-r,
+ static_cast<int>(c1.GetGreen())-g,
+ static_cast<int>(c1.GetBlue())-b ); }
+
+static int sal_Shift( Pixel nMask )
+{
+ int i = 24;
+ if( nMask < 0x00010000 ) { nMask <<= 16; i -= 16; }
+ if( nMask < 0x01000000 ) { nMask <<= 8; i -= 8; }
+ if( nMask < 0x10000000 ) { nMask <<= 4; i -= 4; }
+ if( nMask < 0x40000000 ) { nMask <<= 2; i -= 2; }
+ if( nMask < 0x80000000 ) { i -= 1; }
+ return i;
+}
+
+static int sal_significantBits( Pixel nMask )
+{
+ int nRotate = sizeof(Pixel)*4;
+ int nBits = 0;
+ while( nRotate-- )
+ {
+ if( nMask & 1 )
+ nBits++;
+ nMask >>= 1;
+ }
+ return nBits;
+}
+
+// check if the resolution is sane
+static bool sal_ValidDPI(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::BestOpenGLVisual(Display* pDisplay, int nScreen, XVisualInfo& rVI)
+{
+ OpenGLZone aZone;
+
+ XVisualInfo* pVI;
+ int aAttrib[] = { GLX_RGBA,
+ GLX_RED_SIZE, 8,
+ GLX_GREEN_SIZE, 8,
+ GLX_BLUE_SIZE, 8,
+ GLX_DEPTH_SIZE, 24,
+ GLX_STENCIL_SIZE, 8,
+ None };
+
+ pVI = glXChooseVisual( pDisplay, nScreen, aAttrib );
+ if( !pVI )
+ return false;
+
+ rVI = *pVI;
+ XFree( pVI );
+
+ CHECK_GL_ERROR();
+ return true;
+}
+
+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;
+
+// TODO SKIA
+ bool bUseOpenGL = OpenGLHelper::isVCLOpenGLEnabled();
+ if (bUseOpenGL && BestOpenGLVisual(pDisplay, nScreen, 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();
+ X11SalBitmap::ImplDestroyCache();
+
+ if (ImplGetSVData())
+ {
+ SalDisplay* pSalDisp = vcl_sal::getSalDisplay(pData);
+ Display* const pX11Disp = pSalDisp->GetDisplay();
+ int nMaxScreens = pSalDisp->GetXScreenCount();
+ XRenderPeer& rRenderPeer = XRenderPeer::GetInstance();
+
+ for (int i = 0; i < nMaxScreens; i++)
+ {
+ SalDisplay::RenderEntryMap& rMap = pSalDisp->GetRenderEntries(SalX11Screen(i));
+ for (auto const& elem : rMap)
+ {
+ if (elem.second.m_aPixmap)
+ ::XFreePixmap(pX11Disp, elem.second.m_aPixmap);
+ if (elem.second.m_aPicture)
+ rRenderPeer.FreePicture(elem.second.m_aPicture);
+ }
+ rMap.clear();
+ }
+ }
+ 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 = Size( 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 long nDPI = static_cast<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
+ */
+ long xDPI = 96;
+ long yDPI = 96;
+ if (m_aScreens.size() == 1) {
+ xDPI = static_cast<long>(round(DisplayWidth(pDisp_, 0)*25.4/DisplayWidthMM(pDisp_, 0)));
+ yDPI = static_cast<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_);
+ X11SalBitmap::ImplCreateCache();
+
+ // - - - - - - - - - - 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)
+ {
+ 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;
+ 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;
+ // - - - - - - - - - - - - - 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;
+
+ 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->aWindow),
+ 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( GetSalData()->m_pInstance->GetYieldMutex()->IsCurrentThread(),
+ "will crash soon since solar mutex not locked in SalDisplay::Yield" );
+
+ XNextEvent( pDisp_, &aEvent );
+
+ // FIXME: under-convinced by Dispatch boolean return value vs. salframe.
+ Dispatch( &aEvent );
+
+#ifdef DBG_UTIL
+ if( GetX11SalData()->HasXErrorOccurred() )
+ {
+ XFlush( pDisp_ );
+ DbgPrintDisplayEvent("SalDisplay::Yield (WasXError)", &aEvent);
+ }
+#endif
+ GetX11SalData()->ResetXErrorOccurred();
+}
+
+bool 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 false;
+ }
+
+ SalInstance* pInstance = GetSalData()->m_pInstance;
+ 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 false;
+ }
+ }
+ }
+ 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 true;
+ }
+ 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
+ )
+ {
+ return pFrame->Dispatch( pEvent );
+ }
+ if( pEvent->type == ConfigureNotify && pEvent->xconfigure.window == pFrame->GetStackingWindow() )
+ {
+ return pFrame->Dispatch( pEvent );
+ }
+ }
+
+ // dispatch to salobjects
+ X11SalObject::Dispatch( pEvent );
+
+ // is this perhaps a root window that changed size ?
+ processRandREvent( pEvent );
+
+ return false;
+}
+
+#ifdef DBG_UTIL
+void SalDisplay::DbgPrintDisplayEvent(const char *pComment, 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()
+ << " " << (Hypothenuse( 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, long i_nX, long i_nY, long i_nWidth, 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( Size( i_nWidth, i_nHeight ) );
+ }
+ return;
+ }
+ }
+ m_aXineramaScreenIndexMap[i] = m_aXineramaScreens.size();
+ m_aXineramaScreens.emplace_back( Point( i_nX, i_nY ), Size( i_nWidth, i_nHeight ) );
+}
+
+void SalDisplay::InitXinerama()
+{
+ if( m_aScreens.size() > 1 )
+ {
+ m_bXinerama = false;
+ return; // multiple screens mean no xinerama
+ }
+#if defined(USE_XINERAMA_XORG)
+ if( XineramaIsActive( pDisp_ ) )
+ {
+ int nFramebuffers = 1;
+ XineramaScreenInfo* pScreens = XineramaQueryScreens( pDisp_, &nFramebuffers );
+ if( pScreens )
+ {
+ if( nFramebuffers > 1 )
+ {
+ m_aXineramaScreens = std::vector<tools::Rectangle>();
+ 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 );
+ }
+ }
+#endif
+#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;
+}
+
+bool SalDisplay::XIfEventWithTimeout( XEvent* o_pEvent, XPointer i_pPredicateData,
+ X_if_predicate i_pPredicate ) const
+{
+ /* #i99360# ugly workaround an X11 library bug
+ this replaces the following call:
+ XIfEvent( GetDisplay(), o_pEvent, i_pPredicate, i_pPredicateData );
+ */
+ bool bRet = true;
+
+ if( ! XCheckIfEvent( GetDisplay(), o_pEvent, i_pPredicate, i_pPredicateData ) )
+ {
+ // wait for some event to arrive
+ struct pollfd aFD;
+ aFD.fd = ConnectionNumber(GetDisplay());
+ aFD.events = POLLIN;
+ aFD.revents = 0;
+ long nTimeout = 1000;
+ (void)poll(&aFD, 1, nTimeout);
+ if( ! XCheckIfEvent( GetDisplay(), o_pEvent, i_pPredicate, i_pPredicateData ) )
+ {
+ (void)poll(&aFD, 1, nTimeout); // try once more for a packet of events from the Xserver
+ if( ! XCheckIfEvent( GetDisplay(), o_pEvent, i_pPredicate, i_pPredicateData ) )
+ {
+ bRet = false;
+ }
+ }
+ }
+ return bRet;
+}
+
+SalVisual::SalVisual():
+ eRGBMode_(SalRGB::RGB), nRedShift_(0), nGreenShift_(0), nBlueShift_(0), nRedBits_(0), nGreenBits_(0),
+ nBlueBits_(0)
+{
+ visual = nullptr;
+}
+
+SalVisual::SalVisual( const XVisualInfo* pXVI )
+{
+ *static_cast<XVisualInfo*>(this) = *pXVI;
+ if( GetClass() == TrueColor )
+ {
+ nRedShift_ = sal_Shift( red_mask );
+ nGreenShift_ = sal_Shift( green_mask );
+ nBlueShift_ = sal_Shift( blue_mask );
+
+ nRedBits_ = sal_significantBits( red_mask );
+ nGreenBits_ = sal_significantBits( green_mask );
+ nBlueBits_ = sal_significantBits( blue_mask );
+
+ if( GetDepth() == 24 )
+ if( red_mask == 0xFF0000 )
+ if( green_mask == 0xFF00 )
+ if( blue_mask == 0xFF )
+ eRGBMode_ = SalRGB::RGB;
+ else
+ eRGBMode_ = SalRGB::otherSalRGB;
+ else if( blue_mask == 0xFF00 )
+ if( green_mask == 0xFF )
+ eRGBMode_ = SalRGB::RBG;
+ else
+ eRGBMode_ = SalRGB::otherSalRGB;
+ else
+ eRGBMode_ = SalRGB::otherSalRGB;
+ else if( green_mask == 0xFF0000 )
+ if( red_mask == 0xFF00 )
+ if( blue_mask == 0xFF )
+ eRGBMode_ = SalRGB::GRB;
+ else
+ eRGBMode_ = SalRGB::otherSalRGB;
+ else if( blue_mask == 0xFF00 )
+ if( red_mask == 0xFF )
+ eRGBMode_ = SalRGB::GBR;
+ else
+ eRGBMode_ = SalRGB::otherSalRGB;
+ else
+ eRGBMode_ = SalRGB::otherSalRGB;
+ else if( blue_mask == 0xFF0000 )
+ if( red_mask == 0xFF00 )
+ if( green_mask == 0xFF )
+ eRGBMode_ = SalRGB::BRG;
+ else
+ eRGBMode_ = SalRGB::otherSalRGB;
+ else if( green_mask == 0xFF00 )
+ if( red_mask == 0xFF )
+ eRGBMode_ = SalRGB::BGR;
+ else
+ eRGBMode_ = SalRGB::otherSalRGB;
+ else
+ eRGBMode_ = SalRGB::otherSalRGB;
+ else
+ eRGBMode_ = SalRGB::otherSalRGB;
+ else
+ eRGBMode_ = SalRGB::otherSalRGB;
+ }
+}
+
+// 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
+
+#define SALCOLOR SalRGB::RGB
+#define SALCOLORREVERSE SalRGB::BGR
+
+Color SalVisual::GetTCColor( Pixel nPixel ) const
+{
+ if( SALCOLOR == eRGBMode_ )
+ return static_cast<Color>(nPixel);
+
+ if( SALCOLORREVERSE == eRGBMode_ )
+ return Color( (nPixel & 0x0000FF),
+ (nPixel & 0x00FF00) >> 8,
+ (nPixel & 0xFF0000) >> 16);
+
+ Pixel r = nPixel & red_mask;
+ Pixel g = nPixel & green_mask;
+ Pixel b = nPixel & blue_mask;
+
+ if( SalRGB::otherSalRGB != eRGBMode_ ) // 8+8+8=24
+ return Color( r >> nRedShift_,
+ g >> nGreenShift_,
+ b >> nBlueShift_ );
+
+ if( nRedShift_ > 0 ) r >>= nRedShift_; else r <<= -nRedShift_;
+ if( nGreenShift_ > 0 ) g >>= nGreenShift_; else g <<= -nGreenShift_;
+ if( nBlueShift_ > 0 ) b >>= nBlueShift_; else b <<= -nBlueShift_;
+
+ if( nRedBits_ != 8 )
+ r |= (r & 0xff) >> (8-nRedBits_);
+ if( nGreenBits_ != 8 )
+ g |= (g & 0xff) >> (8-nGreenBits_);
+ if( nBlueBits_ != 8 )
+ b |= (b & 0xff) >> (8-nBlueBits_);
+
+ return Color( r, g, b );
+}
+
+Pixel SalVisual::GetTCPixel( Color nColor ) const
+{
+ if( SALCOLOR == eRGBMode_ )
+ return static_cast<Pixel>(sal_uInt32(nColor));
+
+ Pixel r = static_cast<Pixel>( nColor.GetRed() );
+ Pixel g = static_cast<Pixel>( nColor.GetGreen() );
+ Pixel b = static_cast<Pixel>( nColor.GetBlue() );
+
+ if( SALCOLORREVERSE == eRGBMode_ )
+ return (b << 16) | (g << 8) | r;
+
+ if( SalRGB::otherSalRGB != eRGBMode_ ) // 8+8+8=24
+ return (r << nRedShift_) | (g << nGreenShift_) | (b << nBlueShift_);
+
+ if( nRedShift_ > 0 ) r <<= nRedShift_; else r >>= -nRedShift_;
+ if( nGreenShift_ > 0 ) g <<= nGreenShift_; else g >>= -nGreenShift_;
+ if( nBlueShift_ > 0 ) b <<= nBlueShift_; else b >>= -nBlueShift_;
+
+ return (r&red_mask) | (g&green_mask) | (b&blue_mask);
+}
+
+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;
+ }
+}
+
+void SalColormap::GetPalette()
+{
+ Pixel i;
+ m_aPalette = std::vector<Color>(m_nUsed);
+
+ std::unique_ptr<XColor[]> aColor(new XColor[m_nUsed]);
+
+ for( i = 0; i < m_nUsed; i++ )
+ {
+ aColor[i].red = aColor[i].green = aColor[i].blue = 0;
+ aColor[i].pixel = i;
+ }
+
+ XQueryColors( m_pDisplay->GetDisplay(), m_hColormap, aColor.get(), m_nUsed );
+
+ for( i = 0; i < m_nUsed; i++ )
+ {
+ m_aPalette[i] = Color( aColor[i].red >> 8,
+ aColor[i].green >> 8,
+ aColor[i].blue >> 8 );
+ }
+}
+
+static sal_uInt16 sal_Lookup( const std::vector<Color>& rPalette,
+ int r, int g, int b,
+ Pixel nUsed )
+{
+ sal_uInt16 nPixel = 0;
+ int nBest = ColorDiff( rPalette[0], r, g, b );
+
+ for( Pixel i = 1; i < nUsed; i++ )
+ {
+ int n = ColorDiff( rPalette[i], r, g, b );
+
+ if( n < nBest )
+ {
+ if( !n )
+ return i;
+
+ nPixel = i;
+ nBest = n;
+ }
+ }
+ return nPixel;
+}
+
+void SalColormap::GetLookupTable()
+{
+ m_aLookupTable = std::vector<sal_uInt16>(16*16*16);
+
+ int i = 0;
+ for( int r = 0; r < 256; r += 17 )
+ for( int g = 0; g < 256; g += 17 )
+ for( int b = 0; b < 256; b += 17 )
+ m_aLookupTable[i++] = sal_Lookup( m_aPalette, r, g, b, m_nUsed );
+}
+
+Color SalColormap::GetColor( Pixel nPixel ) const
+{
+ if( m_nBlackPixel == nPixel ) return COL_BLACK;
+ if( m_nWhitePixel == nPixel ) return COL_WHITE;
+
+ if( m_aVisual.GetVisual() )
+ {
+ if( m_aVisual.GetClass() == TrueColor )
+ return m_aVisual.GetTCColor( nPixel );
+
+ if( m_aPalette.empty()
+ && m_hColormap
+ && m_aVisual.GetDepth() <= 12
+ && m_aVisual.GetClass() == PseudoColor )
+ const_cast<SalColormap*>(this)->GetPalette();
+ }
+
+ if( !m_aPalette.empty() && nPixel < m_nUsed )
+ return m_aPalette[nPixel];
+
+ if( !m_hColormap )
+ {
+ SAL_WARN("vcl", "SalColormap::GetColor() !m_hColormap");
+ return nPixel;
+ }
+
+ // DirectColor, StaticColor, StaticGray, GrayScale
+ XColor aColor;
+
+ aColor.pixel = nPixel;
+
+ XQueryColor( m_pDisplay->GetDisplay(), m_hColormap, &aColor );
+
+ return Color( aColor.red>>8, aColor.green>>8, aColor.blue>>8 );
+}
+
+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 );
+}
+
+Pixel SalColormap::GetPixel( Color nColor ) const
+{
+ if( SALCOLOR_NONE == nColor ) return 0;
+ if( COL_BLACK == nColor ) return m_nBlackPixel;
+ if( COL_WHITE == nColor ) return m_nWhitePixel;
+
+ if( m_aVisual.GetClass() == TrueColor )
+ return m_aVisual.GetTCPixel( nColor );
+
+ if( m_aLookupTable.empty() )
+ {
+ if( m_aPalette.empty()
+ && m_hColormap
+ && m_aVisual.GetDepth() <= 12
+ && m_aVisual.GetClass() == PseudoColor ) // what else ???
+ const_cast<SalColormap*>(this)->GetPalette();
+
+ if( !m_aPalette.empty() )
+ for( Pixel i = 0; i < m_nUsed; i++ )
+ if( m_aPalette[i] == nColor )
+ return i;
+
+ if( m_hColormap )
+ {
+ // DirectColor, StaticColor, StaticGray, GrayScale (PseudoColor)
+ XColor aColor;
+
+ if( GetXPixel( aColor,
+ nColor.GetRed(),
+ nColor.GetGreen(),
+ nColor.GetBlue() ) )
+ {
+ if( !m_aPalette.empty() && m_aPalette[aColor.pixel] == Color(0) )
+ {
+ const_cast<SalColormap*>(this)->m_aPalette[aColor.pixel] = nColor;
+
+ if( !(aColor.pixel & 1) && m_aPalette[aColor.pixel+1] == Color(0) )
+ {
+ XColor aInversColor;
+
+ Color nInversColor = sal_uInt32(nColor) ^ 0xFFFFFF;
+
+ GetXPixel( aInversColor,
+ nInversColor.GetRed(),
+ nInversColor.GetGreen(),
+ nInversColor.GetBlue() );
+
+ if( m_aPalette[aInversColor.pixel] == Color(0) )
+ const_cast<SalColormap*>(this)->m_aPalette[aInversColor.pixel] = nInversColor;
+#ifdef DBG_UTIL
+ else
+ SAL_INFO("vcl.app", "SalColormap::GetPixel() "
+ << std::showbase << std::setfill('0')
+ << std::setw(6) << std::hex
+ << static_cast< unsigned long >(
+ sal_uInt32(nColor))
+ << "="
+ << std::dec
+ << aColor.pixel << " "
+ << std::showbase << std::setfill('0')
+ << std::setw(6) << std::hex
+ << static_cast< unsigned long >(
+ sal_uInt32(nInversColor))
+ << "="
+ << std::dec
+ << aInversColor.pixel);
+#endif
+ }
+ }
+
+ return aColor.pixel;
+ }
+
+#ifdef DBG_UTIL
+ SAL_INFO("vcl.app", "SalColormap::GetPixel() !XAllocColor "
+ << std::hex
+ << static_cast< unsigned long >(sal_uInt32(nColor)));
+#endif
+ }
+
+ if( m_aPalette.empty() )
+ {
+#ifdef DBG_UTIL
+ SAL_INFO("vcl.app", "SalColormap::GetPixel() Palette empty "
+ << std::hex
+ << static_cast< unsigned long >(sal_uInt32(nColor)));
+#endif
+ return sal_uInt32(nColor);
+ }
+
+ const_cast<SalColormap*>(this)->GetLookupTable();
+ }
+
+ // color matching via palette
+ sal_uInt16 r = nColor.GetRed();
+ sal_uInt16 g = nColor.GetGreen();
+ sal_uInt16 b = nColor.GetBlue();
+ return m_aLookupTable[ (((r+8)/17) << 8)
+ + (((g+8)/17) << 4)
+ + ((b+8)/17) ];
+}
+
+/* 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 000000000..21d878cfa
--- /dev/null
+++ b/vcl/unx/generic/app/salinst.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <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>
+
+#include <config_features.h>
+#include <vcl/skia/SkiaHelper.hxx>
+#include <config_skia.h>
+#if HAVE_FEATURE_SKIA
+#include <skia/x11/gdiimpl.hxx>
+#endif
+
+// 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( SAL_DATA_UNX, pInstance );
+
+ 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");
+#if HAVE_FEATURE_SKIA
+ X11SkiaSalGraphicsImpl::prepareSkia();
+#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<vcl::BackendCapabilities> X11SalInstance::GetBackendCapabilities()
+{
+ auto pBackendCapabilities = SalInstance::GetBackendCapabilities();
+#if HAVE_FEATURE_SKIA
+#if SKIA_USE_BITMAP32
+ if( SkiaHelper::isVCLSkiaEnabled())
+ pBackendCapabilities->mbSupportsBitmap32 = true;
+#endif
+#endif
+ return pBackendCapabilities;
+}
+
+/* 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 000000000..dc7a61dfe
--- /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 000000000..30ff437a2
--- /dev/null
+++ b/vcl/unx/generic/app/sm.cxx
@@ -0,0 +1,858 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <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 = "";
+OString SessionManagerClient::m_aTimeID = "";
+OString SessionManagerClient::m_aClientTimeID = "";
+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() );
+ OStringBuffer aRestartOption;
+ aRestartOption.append("--session=");
+ aRestartOption.append(SessionManagerClient::getSessionID());
+ pSmProps[ eRestartCommand ].vals[1].length = aRestartOption.getLength()+1;
+ pSmProps[ eRestartCommand ].vals[1].value = strdup(aRestartOption.getStr());
+ OString aRestartOptionNoLogo("--nologo");
+ 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 )
+ {
+ 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 )
+ {
+ 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.copy(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 000000000..39aaf0073
--- /dev/null
+++ b/vcl/unx/generic/app/wmadaptor.cxx
@@ -0,0 +1,2308 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <i18nlangtag/languagetag.hxx>
+#include <rtl/locale.h>
+
+#include <osl/thread.h>
+#include <osl/process.h>
+#include <sal/macros.h>
+#include <sal/log.hxx>
+#include <configsettings.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 shade( X11SalFrame* pFrame, bool bToShaded ) 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, 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 shade( X11SalFrame* pFrame, bool bToShaded ) 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
+ */
+static 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_SHADED", WMAdaptor::NET_WM_STATE_SHADED },
+ { "_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
+ */
+
+static 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< tools::Rectangle >
+ ( 1, tools::Rectangle( Point(), 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() )
+ {
+ 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 )
+ {
+ 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< tools::Rectangle > ( m_nDesktops );
+ long* pValues = reinterpret_cast<long*>(pProperty);
+ for( int i = 0; i < m_nDesktops; i++ )
+ {
+ Point aPoint( pValues[4*i],
+ pValues[4*i+1] );
+ Size aSize( pValues[4*i+2],
+ pValues[4*i+3] );
+ tools::Rectangle 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 ] )
+ {
+ ::Window aWMChild = None;
+ 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
+ )
+ {
+ aWMChild = *reinterpret_cast< ::Window* >(pProperty);
+ XFree( pProperty );
+ pProperty = nullptr;
+ ::Window aCheckWindow = None;
+ 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();
+
+ 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 ] )
+ {
+ ::Window aWMChild = None;
+ 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
+ )
+ {
+ aWMChild = *reinterpret_cast< ::Window* >(pProperty);
+ XFree( pProperty );
+ pProperty = nullptr;
+ ::Window aCheckWindow = None;
+ 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();
+ 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 = aMetaVersion.getToken(0, '.', nIdx).toInt32();
+ nVersionMinor = aMetaVersion.getToken(0, '.', nIdx).toInt32();
+ }
+ 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( OUString()));
+ 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 ] )
+ {
+ 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->mbShaded && m_aWMAtoms[ NET_WM_STATE_SHADED ] )
+ aStateAtoms[ nStateAtoms++ ] = m_aWMAtoms[ NET_WM_STATE_SHADED ];
+ 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 ) )
+ {
+ /*
+ * for maximizing use NorthWestGravity (including decoration)
+ */
+ XSizeHints hints;
+ 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;
+ }
+ tools::Rectangle aPosSize = m_aWMWorkAreas[nCurrent];
+ const SalFrameGeometry& rGeom( pFrame->GetUnmirroredGeometry() );
+ aPosSize = tools::Rectangle( Point( aPosSize.Left() + rGeom.nLeftDecoration,
+ aPosSize.Top() + rGeom.nTopDecoration ),
+ Size( aPosSize.GetWidth()
+ - rGeom.nLeftDecoration
+ - rGeom.nRightDecoration,
+ aPosSize.GetHeight()
+ - rGeom.nTopDecoration
+ - rGeom.nBottomDecoration )
+ );
+ pFrame->SetPosSize( aPosSize );
+
+ /*
+ * reset gravity hint to static gravity
+ * (this should not move window according to ICCCM)
+ */
+ if( bHint && pFrame->nShowState_ != SHOWSTATE_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 ] )
+ {
+ sal_uInt32 nWinWMState = 0;
+
+ if( pFrame->mbMaximizedVert )
+ nWinWMState |= 1 << 2;
+ if( pFrame->mbMaximizedHorz )
+ nWinWMState |= 1 << 3;
+ if( pFrame->mbShaded )
+ nWinWMState |= 1 << 5;
+
+ 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 ) )
+ {
+ /*
+ * for maximizing use NorthWestGravity (including decoration)
+ */
+ XSizeHints hints;
+ 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;
+ }
+ tools::Rectangle aPosSize = m_aWMWorkAreas[nCurrent];
+ const SalFrameGeometry& rGeom( pFrame->GetUnmirroredGeometry() );
+ aPosSize = tools::Rectangle( Point( aPosSize.Left() + rGeom.nLeftDecoration,
+ aPosSize.Top() + rGeom.nTopDecoration ),
+ Size( aPosSize.GetWidth()
+ - rGeom.nLeftDecoration
+ - rGeom.nRightDecoration,
+ aPosSize.GetHeight()
+ - rGeom.nTopDecoration
+ - rGeom.nBottomDecoration )
+ );
+ pFrame->SetPosSize( aPosSize );
+
+ /*
+ * reset gravity hint to static gravity
+ * (this should not move window according to ICCCM)
+ */
+ if( bHint && pFrame->nShowState_ != SHOWSTATE_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;
+ 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 )
+ {
+ Size aScreenSize( m_pSalDisplay->GetScreenSize( pFrame->GetScreenNumber() ) );
+ Point aTL( rGeom.nLeftDecoration, rGeom.nTopDecoration );
+ if( m_pSalDisplay->IsXinerama() )
+ {
+ Point aMed( aTL.X() + rGeom.nWidth/2, aTL.Y() + rGeom.nHeight/2 );
+ const std::vector< tools::Rectangle >& rScreens = m_pSalDisplay->GetXineramaScreens();
+ for(const auto & rScreen : rScreens)
+ if( rScreen.IsInside( aMed ) )
+ {
+ aTL += rScreen.TopLeft();
+ aScreenSize = rScreen.GetSize();
+ break;
+ }
+ }
+ tools::Rectangle aTarget( aTL,
+ Size( aScreenSize.Width() - rGeom.nLeftDecoration - rGeom.nTopDecoration,
+ aScreenSize.Height() - rGeom.nTopDecoration - rGeom.nBottomDecoration )
+ );
+ if( ! bHorizontal )
+ {
+ aTarget.SetSize(
+ Size(
+ pFrame->maRestorePosSize.IsEmpty() ?
+ rGeom.nWidth : pFrame->maRestorePosSize.GetWidth(),
+ aTarget.GetHeight()
+ )
+ );
+ aTarget.SetLeft(
+ pFrame->maRestorePosSize.IsEmpty() ?
+ rGeom.nX : pFrame->maRestorePosSize.Left() );
+ }
+ else if( ! bVertical )
+ {
+ aTarget.SetSize(
+ Size(
+ aTarget.GetWidth(),
+ pFrame->maRestorePosSize.IsEmpty() ?
+ rGeom.nHeight : pFrame->maRestorePosSize.GetHeight()
+ )
+ );
+ aTarget.SetTop(
+ pFrame->maRestorePosSize.IsEmpty() ?
+ rGeom.nY : pFrame->maRestorePosSize.Top() );
+ }
+
+ tools::Rectangle aRestore( Point( rGeom.nX, rGeom.nY ), Size( rGeom.nWidth, rGeom.nHeight ) );
+ 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 = tools::Rectangle();
+ pFrame->nWidth_ = rGeom.nWidth;
+ pFrame->nHeight_ = rGeom.nHeight;
+ }
+}
+
+/*
+ * 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 = tools::Rectangle();
+ else if( pFrame->maRestorePosSize.IsEmpty() )
+ {
+ const SalFrameGeometry& rGeom( pFrame->GetUnmirroredGeometry() );
+ pFrame->maRestorePosSize =
+ tools::Rectangle( Point( rGeom.nX, rGeom.nY ), Size( rGeom.nWidth, rGeom.nHeight ) );
+ }
+ }
+ 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 = tools::Rectangle();
+ else if( pFrame->maRestorePosSize.IsEmpty() )
+ {
+ const SalFrameGeometry& rGeom( pFrame->GetUnmirroredGeometry() );
+ pFrame->maRestorePosSize =
+ tools::Rectangle( Point( rGeom.nX, rGeom.nY ), Size( rGeom.nWidth, rGeom.nHeight ) );
+ }
+ }
+ 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 ] )
+ {
+ 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 ] )
+ {
+ 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()
+ )
+ {
+ ::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;
+ pFrame->mbShaded = false;
+
+ if( pEvent->state == PropertyNewValue )
+ {
+ Atom nType, *pStates;
+ int nFormat;
+ unsigned long nItems, nBytesLeft;
+ unsigned char* pData = nullptr;
+ 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;
+ else if( pStates[i] == m_aWMAtoms[ NET_WM_STATE_SHADED ] && m_aWMAtoms[ NET_WM_STATE_SHADED ] )
+ pFrame->mbShaded = true;
+ }
+ }
+ XFree( pData );
+ pData = nullptr;
+ nOffset += nItems * nFormat / 32;
+ }
+ else
+ break;
+ } while( nBytesLeft > 0 );
+ }
+
+ if( ! (pFrame->mbMaximizedHorz || pFrame->mbMaximizedVert ) )
+ pFrame->maRestorePosSize = tools::Rectangle();
+ else
+ {
+ const SalFrameGeometry& rGeom = pFrame->GetUnmirroredGeometry();
+ // the current geometry may already be changed by the corresponding
+ // ConfigureNotify, but this cannot be helped
+ pFrame->maRestorePosSize =
+ tools::Rectangle( Point( rGeom.nX, rGeom.nY ),
+ Size( rGeom.nWidth, rGeom.nHeight ) );
+ }
+ }
+ 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;
+ pFrame->mbShaded = 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;
+ if( nWinState & (1<<5) )
+ pFrame->mbShaded = true;
+ }
+ XFree( pData );
+ }
+ }
+
+ if( ! (pFrame->mbMaximizedHorz || pFrame->mbMaximizedVert ) )
+ pFrame->maRestorePosSize = tools::Rectangle();
+ else
+ {
+ const SalFrameGeometry& rGeom = pFrame->GetUnmirroredGeometry();
+ // the current geometry may already be changed by the corresponding
+ // ConfigureNotify, but this cannot be helped
+ pFrame->maRestorePosSize =
+ tools::Rectangle( Point( rGeom.nX, rGeom.nY ),
+ Size( rGeom.nWidth, rGeom.nHeight ) );
+ }
+ }
+ else if( pEvent->atom == m_aWMAtoms[ NET_WM_DESKTOP ] )
+ {
+ pFrame->m_nWorkArea = getWindowWorkArea( pFrame->GetShellWindow() );
+ }
+ else
+ nHandled = 0;
+
+ return nHandled;
+}
+
+/*
+ * WMAdaptor::shade
+ */
+void WMAdaptor::shade( X11SalFrame*, bool /*bToShaded*/ ) const
+{
+}
+
+/*
+ * NetWMAdaptor::shade
+ */
+void NetWMAdaptor::shade( X11SalFrame* pFrame, bool bToShaded ) const
+{
+ if( m_aWMAtoms[ NET_WM_STATE ]
+ && m_aWMAtoms[ NET_WM_STATE_SHADED ]
+ && ( pFrame->nStyle_ & ~SalFrameStyleFlags::DEFAULT )
+ )
+ {
+ pFrame->mbShaded = bToShaded;
+ 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] = bToShaded ? 1 : 0;
+ aEvent.xclient.data.l[1] = m_aWMAtoms[ NET_WM_STATE_SHADED ];
+ 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 );
+ }
+ }
+}
+
+/*
+ * GnomeWMAdaptor::shade
+ */
+void GnomeWMAdaptor::shade( X11SalFrame* pFrame, bool bToShaded ) const
+{
+ if( m_aWMAtoms[ WIN_STATE ] )
+ {
+ pFrame->mbShaded = bToShaded;
+ 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<<5);
+ aEvent.xclient.data.l[1] = bToShaded ? (1<<5) : 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
+ setGnomeWMState( pFrame );
+ }
+}
+
+/*
+ * 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< tools::Rectangle >& rScreens = m_pSalDisplay->GetXineramaScreens();
+ Point aMousePoint( root_x, root_y );
+ for(const auto & rScreen : rScreens)
+ {
+ if( rScreen.IsInside( aMousePoint ) )
+ {
+ pFrame->maGeometry.nX = rScreen.Left();
+ pFrame->maGeometry.nY = rScreen.Top();
+ pFrame->maGeometry.nWidth = rScreen.GetWidth();
+ pFrame->maGeometry.nHeight = rScreen.GetHeight();
+ break;
+ }
+ }
+ }
+ else
+ {
+ Size aSize = m_pSalDisplay->GetScreenSize( pFrame->GetScreenNumber() );
+ pFrame->maGeometry.nX = 0;
+ pFrame->maGeometry.nY = 0;
+ pFrame->maGeometry.nWidth = aSize.Width();
+ pFrame->maGeometry.nHeight = aSize.Height();
+ }
+ 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*, long ) const
+{
+}
+
+/*
+ * NetWMAdaptor::setUserTime
+ */
+void NetWMAdaptor::setUserTime( X11SalFrame* i_pFrame, 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] )
+ {
+ long nPID = static_cast<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 ] )
+ {
+ 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 000000000..bad134fbd
--- /dev/null
+++ b/vcl/unx/generic/desktopdetect/desktopdetector.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 <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"));
+}
+
+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( "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;
+
+ // 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 000000000..17104ca75
--- /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.get();
+}
+
+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( static_cast<OWeakObject*>(this), 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() );
+ m_aListeners.erase( std::remove(m_aListeners.begin(), m_aListeners.end(), listener), m_aListeners.end() );
+}
+
+Reference< XTransferable > X11Clipboard::getTransferable()
+{
+ return getContents();
+}
+
+void X11Clipboard::clearTransferable()
+{
+ clearContents();
+}
+
+void X11Clipboard::fireContentsChanged()
+{
+ fireChangedContentsEvent();
+}
+
+Reference< XInterface > X11Clipboard::getReference() throw()
+{
+ return Reference< XInterface >( static_cast< OWeakObject* >(this) );
+}
+
+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 000000000..4092d3f67
--- /dev/null
+++ b/vcl/unx/generic/dtrans/X11_clipboard.hxx
@@ -0,0 +1,114 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_VCL_UNX_GENERIC_DTRANS_X11_CLIPBOARD_HXX
+#define INCLUDED_VCL_UNX_GENERIC_DTRANS_X11_CLIPBOARD_HXX
+
+#include "X11_selection.hxx"
+
+#include <com/sun/star/datatransfer/clipboard/XSystemClipboard.hpp>
+#include <cppuhelper/compbase.hxx>
+
+#define X11_CLIPBOARD_IMPLEMENTATION_NAME "com.sun.star.datatransfer.X11ClipboardSupport"
+
+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() throw() 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);
+
+} // namepspace
+
+#endif
+
+/* 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 000000000..638c47387
--- /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 000000000..18c33666b
--- /dev/null
+++ b/vcl/unx/generic/dtrans/X11_dndcontext.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_UNX_GENERIC_DTRANS_X11_DNDCONTEXT_HXX
+#define INCLUDED_VCL_UNX_GENERIC_DTRANS_X11_DNDCONTEXT_HXX
+
+#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
+
+#endif // INCLUDED_VCL_UNX_GENERIC_DTRANS_X11_DNDCONTEXT_HXX
+
+/* 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 000000000..5e1320fe3
--- /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 )
+ {
+ 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 );
+
+ m_aListeners.erase( std::remove(m_aListeners.begin(), m_aListeners.end(), xListener), m_aListeners.end() );
+}
+
+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 ) throw()
+{
+ 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 ) throw()
+{
+ 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 ) throw()
+{
+ 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 ) throw()
+{
+ 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 XDND_DROPTARGET_IMPLEMENTATION_NAME;
+}
+
+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 000000000..3e15173ba
--- /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 <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>
+
+#if defined(NETBSD) || defined (FREEBSD) || defined(OPENBSD)
+#include <sys/poll.h>
+#else
+#include <poll.h>
+#endif
+
+#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>
+
+// 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>
+
+static 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 );
+ }
+}
+
+static const 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( aMimeType.getToken( 0, ';', nIndex ) == "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( aToken.getToken( 0, '=', nPos ) == "charset" )
+ {
+ OString aEncToken = OUStringToOString( aToken.getToken( 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 )
+ {
+ 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 )
+ {
+#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 )
+ {
+ // 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 )
+ {
+ 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( aFlavor.MimeType.getToken( 0, ';', nIndex ) == "text/plain" )
+ {
+ if( aFlavor.MimeType.getToken( 0, ';', nIndex ) == "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() )
+ aDisplayName = OStringToOUString( getenv( "DISPLAY" ), 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();
+ osl::Thread::wait(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( pFlavors->MimeType.getToken( 0, ';', nIndex ) == "text/plain" )
+ {
+ OUString aToken(pFlavors->MimeType.getToken( 0, ';', nIndex ));
+ // omit text/plain;charset=unicode since it is not well defined
+ if( aToken == "charset=unicode" )
+ {
+ pAtoms++;
+ continue;
+ }
+ bHaveText = true;
+ if( aToken == "charset=utf-16" )
+ {
+ bHaveUTF16 = true;
+ pFlavors->DataType = cppu::UnoType<OUString>::get();
+ }
+ else if( aToken == "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
+ 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 )
+ {
+ long nTimeStamp = static_cast<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 = static_cast< OWeakObject* >(this);
+ 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;
+ int 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 = static_cast< OWeakObject* >(this);
+ 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 = static_cast< OWeakObject* >(this);
+ 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 = static_cast< OWeakObject* >(this);
+ 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 = static_cast< OWeakObject* >(it->second.m_pTarget );
+ 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 = static_cast< OWeakObject* >(this);
+ 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 = static_cast< OWeakObject* >(this);
+ 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 = static_cast< OWeakObject* >( it->second.m_pTarget );
+ 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 = static_cast< OWeakObject* >(this);
+ 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 = static_cast< OWeakObject* >(it->second.m_pTarget );
+ 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 = static_cast< OWeakObject* >(this);
+ 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 )
+ {
+#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 )
+ {
+#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 = static_cast< OWeakObject* >(this);
+ 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 = static_cast< OWeakObject* >(this);
+ 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 = static_cast< OWeakObject* >( it->second.m_pTarget );
+ 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 = static_cast< OWeakObject* >( it->second.m_pTarget );
+ 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 = static_cast< OWeakObject* >(this);
+ 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
+ osl::Thread::wait(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 = static_cast< OWeakObject* >(this);
+ 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() throw()
+{
+#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::ClearableMutexGuard 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 = static_cast< OWeakObject* >( it->second.m_pTarget );
+ aGuard.clear();
+ it->second.m_pTarget->dragExit( dte );
+ }
+ 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 = static_cast< OWeakObject* >(this);
+ 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() throw()
+{
+ return m_xDragSourceTransferable;
+}
+
+void SelectionManager::clearTransferable() throw()
+{
+ m_xDragSourceTransferable.clear();
+}
+
+void SelectionManager::fireContentsChanged() throw()
+{
+}
+
+css::uno::Reference< XInterface > SelectionManager::getReference() throw()
+{
+ return css::uno::Reference< XInterface >( static_cast<OWeakObject*>(this) );
+}
+
+/*
+ * 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 XDND_IMPLEMENTATION_NAME;
+}
+
+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 000000000..5bcabe9bd
--- /dev/null
+++ b/vcl/unx/generic/dtrans/X11_selection.hxx
@@ -0,0 +1,501 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_UNX_GENERIC_DTRANS_X11_SELECTION_HXX
+#define INCLUDED_VCL_UNX_GENERIC_DTRANS_X11_SELECTION_HXX
+
+#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>
+
+#define XDND_IMPLEMENTATION_NAME "com.sun.star.datatransfer.dnd.XdndSupport"
+#define XDND_DROPTARGET_IMPLEMENTATION_NAME "com.sun.star.datatransfer.dnd.XdndDropTarget"
+
+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 ) throw();
+ void dragExit( const css::datatransfer::dnd::DropTargetEvent& dte ) throw();
+ void dragOver( const css::datatransfer::dnd::DropTargetDragEvent& dtde ) throw();
+ void drop( const css::datatransfer::dnd::DropTargetDropEvent& dtde ) throw();
+
+ // 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;
+ int 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
+ int 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() throw();
+
+ // 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() throw() override;
+ virtual void clearTransferable() throw() override;
+ virtual void fireContentsChanged() throw() override;
+ virtual css::uno::Reference< css::uno::XInterface > getReference() throw() 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);
+
+}
+
+#endif
+
+/* 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 000000000..2b9c1299b
--- /dev/null
+++ b/vcl/unx/generic/dtrans/X11_service.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 <unx/salinst.h>
+
+#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 )
+{
+ SelectionManager& rManager = SelectionManager::get();
+ css::uno::Sequence<css::uno::Any> mgrArgs(1);
+ mgrArgs[0] <<= 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::CreateDragSource()
+{
+ return css::uno::Reference < XInterface >( static_cast<OWeakObject *>(new SelectionManagerHolder()) );
+}
+
+css::uno::Reference< XInterface > X11SalInstance::CreateDropTarget()
+{
+ return css::uno::Reference < XInterface >( static_cast<OWeakObject *>(new DropTarget()) );
+}
+
+/* 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 000000000..1310f70bc
--- /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;
+ }
+
+ 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 000000000..0de101f6d
--- /dev/null
+++ b/vcl/unx/generic/dtrans/X11_transferable.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_UNX_GENERIC_DTRANS_X11_TRANSFERABLE_HXX
+#define INCLUDED_VCL_UNX_GENERIC_DTRANS_X11_TRANSFERABLE_HXX
+
+#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
+
+#endif
+
+/* 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 000000000..7f0111a61
--- /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 )
+ {
+ 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 )
+{
+ 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.GetBitCount() == 24 && depth <= 8) {
+ bm.Dither();
+ }
+ if (bm.GetBitCount() != 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 000000000..8d24ff41d
--- /dev/null
+++ b/vcl/unx/generic/dtrans/bmp.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 INCLUDED_VCL_UNX_GENERIC_DTRANS_BMP_HXX
+#define INCLUDED_VCL_UNX_GENERIC_DTRANS_BMP_HXX
+
+#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;
+ unsigned long 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 );
+
+ unsigned long 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 );
+
+ 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);
+
+}
+
+#endif
+
+/* 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 000000000..6b40fc16c
--- /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"
+
+#define SETTINGS_CONFIGNODE "VCL/Settings/Transfer"
+#define SELECTION_PROPERTY "SelectionTimeout"
+
+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 000000000..4cc36ebde
--- /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 000000000..a3538c952
--- /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 000000000..8a4e6db38
--- /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 000000000..a1875a8e0
--- /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 000000000..b253ce70c
--- /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 000000000..d317b1556
--- /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 000000000..958257518
--- /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 000000000..662a30064
--- /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 000000000..a87c2c210
--- /dev/null
+++ b/vcl/unx/generic/fontmanager/fontconfig.cxx
@@ -0,0 +1,1198 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <unx/fontmanager.hxx>
+#include <unx/helper.hxx>
+#include <comphelper/sequence.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/vclenum.hxx>
+#include <fontselect.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <i18nutil/unicode.hxx>
+#include <rtl/strbuf.hxx>
+#include <sal/log.hxx>
+#include <tools/diagnose_ex.h>
+#include <unicode/uchar.h>
+#include <unicode/uscript.h>
+#include <officecfg/Office/Common.hxx>
+#include <org/freedesktop/PackageKit/SyncDbusSessionHelper.hpp>
+
+using namespace psp;
+
+#include <fontconfig/fontconfig.h>
+
+#include <cstdio>
+
+#include <unotools/configmgr.hxx>
+
+#include <osl/process.h>
+
+#include <utility>
+#include <algorithm>
+
+using namespace osl;
+
+namespace
+{
+ typedef std::pair<FcChar8*, FcChar8*> lang_and_element;
+
+class FontCfgWrapper
+{
+ FcFontSet* m_pFontSet;
+
+ void addFontSet( FcSetName );
+
+ FontCfgWrapper();
+ ~FontCfgWrapper();
+
+public:
+ static FontCfgWrapper& get();
+ static void release();
+
+ FcFontSet* getFontSet();
+
+ 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;
+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();
+}
+
+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;
+
+ 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();
+ addFontSet( FcSetSystem );
+ addFontSet( FcSetApplication );
+
+ std::stable_sort(m_pFontSet->fonts,m_pFontSet->fonts+m_pFontSet->nfont,SortFont());
+ }
+
+ return m_pFontSet;
+}
+
+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;
+ }
+
+ //possible to-do, sort by UILocale instead of process locale
+ if (!m_pLanguageTag)
+ {
+ rtl_Locale* pLoc = nullptr;
+ osl_getProcessLocale(&pLoc);
+ m_pLanguageTag.reset( new LanguageTag(*pLoc) );
+ }
+ *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;
+ }
+}
+
+//FontConfig doesn't come with a way to remove an element from a FontSet as far
+//as I can see
+static void lcl_FcFontSetRemove(FcFontSet* pFSet, int i)
+{
+ FcPatternDestroy(pFSet->fonts[i]);
+
+ int nTail = pFSet->nfont - (i + 1);
+ --pFSet->nfont;
+ if (!nTail)
+ return;
+ memmove(pFSet->fonts + i, pFSet->fonts + i + 1, nTail*sizeof(FcPattern*));
+}
+
+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( std::unordered_map<OString, int>& o_rVisitedPaths )
+{
+ 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");
+ 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 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 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 )
+ 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>"));
+
+// OSL_ASSERT(eScalableRes != FcResultMatch || scalable);
+
+ // only scalable fonts are usable to psprint anyway
+ if( eScalableRes == FcResultMatch && ! scalable )
+ continue;
+
+ if (isPreviouslyDuplicateOrObsoleted(pFSet, i))
+ {
+ SAL_INFO("vcl.fonts.detail", "Ditching " << file << " as duplicate/obsolete");
+ continue;
+ }
+
+ // see if this font is already cached
+ // update attributes
+ OString aDir, aBase, aOrgPath( reinterpret_cast<char*>(file) );
+ splitPath( aOrgPath, aDir, aBase );
+
+ o_rVisitedPaths[aDir] = 1;
+
+ int nDirID = getDirectoryAtom( aDir );
+ SAL_INFO("vcl.fonts.detail", "file " << aBase << " not cached");
+ // not known, analyze font file to get attributes
+ // not described by fontconfig (e.g. alias names, PSName)
+ if (eFormatRes != FcResultMatch)
+ format = nullptr;
+ std::vector<std::unique_ptr<PrintFont>> aFonts = analyzeFontFile( nDirID, aBase, reinterpret_cast<char*>(format) );
+ if(aFonts.empty())
+ {
+ SAL_INFO(
+ "vcl.fonts", "Warning: file \"" << aOrgPath << "\" is unusable to psprint");
+ //remove font, reuse index
+ //we want to remove unusable fonts here, in case there is a usable font
+ //which duplicates the properties of the unusable one
+
+ //not removing the unusable font will risk the usable font being rejected
+ //as a duplicate by isPreviouslyDuplicateOrObsoleted
+ lcl_FcFontSetRemove(pFSet, i--);
+ continue;
+ }
+
+ std::unique_ptr<PrintFont> xUpdate;
+
+ if (aFonts.size() == 1) // one font
+ xUpdate = std::move(aFonts.front());
+ else // more than one font
+ {
+ // a collection entry, get the correct index
+ if( eIndexRes == FcResultMatch && nEntryId != -1 )
+ {
+ int nCollectionEntry = GetCollectionIndex(nEntryId);
+ for (auto & font : aFonts)
+ {
+ if( font->m_nCollectionEntry == nCollectionEntry )
+ {
+ xUpdate = std::move(font);
+ break;
+ }
+ }
+ }
+
+ if (xUpdate)
+ {
+ // update collection entry
+ // additional entries will be created in the cache
+ // if this is a new index (that is if the loop above
+ // ran to the end of the list)
+ xUpdate->m_nCollectionEntry = GetCollectionIndex(nEntryId);
+ }
+ else
+ {
+ SAL_INFO(
+ "vcl.fonts",
+ "multiple fonts for file, but no index in fontconfig pattern ! (index res ="
+ << eIndexRes << " collection entry = " << nEntryId
+ << "; file will not be used");
+ // we have found more than one font in this file
+ // but fontconfig will not tell us which index is meant
+ // -> something is in disorder, do not use this font
+ }
+ }
+
+ if (xUpdate)
+ {
+ // set family name
+ if( eWeightRes == FcResultMatch )
+ xUpdate->m_eWeight = convertWeight(weight);
+ if( eWidthRes == FcResultMatch )
+ xUpdate->m_eWidth = convertWidth(width);
+ if( eSpacRes == FcResultMatch )
+ xUpdate->m_ePitch = convertSpacing(spacing);
+ if( eSlantRes == FcResultMatch )
+ xUpdate->m_eItalic = convertSlant(slant);
+ if( eStyleRes == FcResultMatch )
+ xUpdate->m_aStyleName = OStringToOUString( OString( reinterpret_cast<char*>(style) ), RTL_TEXTENCODING_UTF8 );
+ if( eIndexRes == FcResultMatch )
+ xUpdate->m_nVariationEntry = GetVariationIndex(nEntryId);
+
+ // sort into known fonts
+ fontID aFont = m_nNextFontID++;
+ m_aFonts[ aFont ] = std::move(xUpdate);
+ m_aFontFileToFontID[ aBase ].insert( aFont );
+ nFonts++;
+ SAL_INFO("vcl.fonts.detail", "inserted font " << family << " as fontID " << aFont);
+ }
+ }
+ }
+
+ // 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);
+ }
+}
+
+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 )
+ {
+ 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 fdo#19869
+ //In the meantime try something that will fit to workaround fdo#35118
+ OString mapToFontConfigLangTag(const LanguageTag &rLangTag)
+ {
+#if defined(FC_VERSION) && (FC_VERSION >= 20492)
+ 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();
+#else
+ OString sLangAttrib = OUStringToOString(rLangTag.getLanguageAndScript(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase();
+ if (sLangAttrib.equalsIgnoreAsciiCase("pa-in"))
+ sLangAttrib = "pa";
+ return sLangAttrib;
+#endif
+ }
+
+ bool isEmoji(sal_uInt32 nCurrentChar)
+ {
+#if U_ICU_VERSION_MAJOR_NUM >= 57
+ return u_hasBinaryProperty(nCurrentChar, UCHAR_EMOJI);
+#else
+ return false;
+#endif
+ }
+
+ //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('-').append(pScriptCode);
+ return OStringToOUString(aBuf.makeStringAndClear(), 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(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);
+
+ // Add required Unicode characters, if any
+ if ( !rMissingCodes.isEmpty() )
+ {
+ FcCharSet *codePoints = FcCharSetCreate();
+ 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 aFont = findFontFileID(nDirID, aBase, GetCollectionIndex(nEntryId), GetVariationIndex(nEntryId));
+ if( aFont > 0 )
+ {
+ FastPrintFontInfo aInfo;
+ bRet = getFontFastInfo( aFont, aInfo );
+ rPattern.maSearchName = aInfo.m_aFamilyName;
+ }
+ }
+
+ 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 = OStringLiteral(":lang=") + sTag;
+ m_aCurrentRequests.push_back(OUString::fromUtf8(sReq));
+ m_aPreviousLangSupportRequests.insert(sTag);
+ }
+ }
+ }
+ if (!m_aCurrentRequests.empty())
+ {
+ m_aFontInstallerTimer.Stop();
+ m_aFontInstallerTimer.Start();
+ }
+ }
+ rMissingCodes = sStillMissing;
+ }
+ }
+
+ FcFontSetDestroy( pSet );
+ }
+
+ SAL_INFO("vcl.fonts", "PrintFontManager::Substitute: replacing missing font: '"
+ << rPattern.maTargetName << "' with '" << rPattern.maSearchName
+ << "'");
+}
+
+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 FastPrintFontInfo& rInfo, int nSize)
+{
+ FontCfgWrapper& rWrapper = FontCfgWrapper::get();
+
+ std::unique_ptr<FontConfigFontOptions> pOptions;
+ FcConfig* pConfig = FcConfigGetCurrent();
+ FcPattern* pPattern = FcPatternCreate();
+
+ OString sFamily = OUStringToOString( rInfo.m_aFamilyName, 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, rInfo.m_eItalic, rInfo.m_eWeight, rInfo.m_eWidth, rInfo.m_ePitch);
+ FcPatternAddDouble(pPattern, FC_PIXEL_SIZE, nSize);
+
+ int hintstyle = FC_HINT_FULL;
+
+ FcConfigSubstitute(pConfig, pPattern, FcMatchPattern);
+ FontConfigFontOptions::cairo_font_options_substitute(pPattern);
+ FcDefaultSubstitute(pPattern);
+
+ FcResult eResult = FcResultNoMatch;
+ FcFontSet* pFontSet = rWrapper.getFontSet();
+ FcPattern* pResult = FcFontSetMatch( pConfig, &pFontSet, 1, pPattern, &eResult );
+ if( pResult )
+ {
+ (void) FcPatternGetInteger(pResult,
+ FC_HINT_STYLE, 0, &hintstyle);
+
+ pOptions.reset(new FontConfigFontOptions(pResult));
+ }
+
+ // cleanup
+ FcPatternDestroy( pPattern );
+
+ return pOptions;
+}
+
+void PrintFontManager::matchFont( FastPrintFontInfo& rInfo, const css::lang::Locale& rLocale )
+{
+ 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( rInfo.m_aFamilyName, RTL_TEXTENCODING_UTF8 );
+ if( !aFamily.isEmpty() )
+ FcPatternAddString(pPattern, FC_FAMILY, reinterpret_cast<FcChar8 const *>(aFamily.getStr()));
+
+ addtopattern(pPattern, rInfo.m_eItalic, rInfo.m_eWeight, rInfo.m_eWidth, rInfo.m_ePitch);
+
+ 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 aFont = findFontFileID(nDirID, aBase,
+ GetCollectionIndex(nEntryId),
+ GetVariationIndex(nEntryId));
+ if( aFont > 0 )
+ getFontFastInfo( aFont, rInfo );
+ }
+ }
+ // info: destroying the pSet destroys pResult implicitly
+ // since pResult was "added" to pSet
+ FcFontSetDestroy( pSet );
+ }
+
+ // cleanup
+ FcPatternDestroy( pPattern );
+}
+
+/* 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 000000000..d1601335a
--- /dev/null
+++ b/vcl/unx/generic/fontmanager/fontmanager.cxx
@@ -0,0 +1,1176 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <fontsubset.hxx>
+#include <impfontcharmap.hxx>
+#include <unx/gendata.hxx>
+#include <unx/helper.hxx>
+#include <vcl/fontcharmap.hxx>
+
+#include <tools/urlobj.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_eFamilyStyle(FAMILY_DONTKNOW)
+, m_eItalic(ITALIC_DONTKNOW)
+, m_eWidth(WIDTH_DONTKNOW)
+, m_eWeight(WEIGHT_DONTKNOW)
+, m_ePitch(PITCH_DONTKNOW)
+, m_aEncoding(RTL_TEXTENCODING_DONTKNOW)
+, m_nAscend(0)
+, m_nDescend(0)
+, m_nLeading(0)
+, m_nXMin(0)
+, m_nYMin(0)
+, m_nXMax(0)
+, m_nYMax(0)
+, 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.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( const OUString& 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() )
+ {
+ std::vector<std::unique_ptr<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<std::unique_ptr<PrintFontManager::PrintFont>> PrintFontManager::analyzeFontFile( int nDirID, const OString& rFontFile, const char *pFormat ) const
+{
+ std::vector<std::unique_ptr<PrintFontManager::PrintFont>> aNewFonts;
+
+ OString aDir( getDirectory( nDirID ) );
+
+ OString aFullPath = aDir + "/" + rFontFile;
+
+ // #i1872# reject unreadable files
+ if( access( aFullPath.getStr(), R_OK ) )
+ return aNewFonts;
+
+ bool 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");
+
+ sal_uInt64 fileSize = 0;
+
+ OUString aURL;
+ if (osl::File::getFileURLFromSystemPath(OStringToOUString(aFullPath, osl_getThreadTextEncoding()),
+ aURL) == osl::File::E_None)
+ {
+ osl::File aFile(aURL);
+ if (aFile.open(osl_File_OpenFlag_Read | osl_File_OpenFlag_NoLock) == osl::File::E_None)
+ {
+ osl::DirectoryItem aItem;
+ if (osl::DirectoryItem::get(aURL, aItem) == osl::File::E_None)
+ {
+ osl::FileStatus aFileStatus( osl_FileStatus_Mask_FileSize );
+ if (aItem.getFileStatus(aFileStatus) == osl::File::E_None)
+ fileSize = aFileStatus.getFileSize();
+ }
+ }
+ }
+
+ //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 (nLength > nMaxFontsPossible)
+ nLength = nMaxFontsPossible;
+
+ for( int i = 0; i < nLength; i++ )
+ {
+ std::unique_ptr<PrintFont> xFont(new PrintFont);
+ xFont->m_nDirectory = nDirID;
+ xFont->m_aFontFile = rFontFile;
+ xFont->m_nCollectionEntry = i;
+ if (analyzeSfntFile(xFont.get()))
+ aNewFonts.push_back(std::move(xFont));
+ }
+ }
+ else
+ {
+ std::unique_ptr<PrintFont> xFont(new PrintFont);
+ xFont->m_nDirectory = nDirID;
+ xFont->m_aFontFile = rFontFile;
+ xFont->m_nCollectionEntry = 0;
+
+ // need to read the font anyway to get aliases inside the font file
+ if (analyzeSfntFile(xFont.get()))
+ aNewFonts.push_back(std::move(xFont));
+ }
+ }
+ return aNewFonts;
+}
+
+fontID PrintFontManager::findFontFileID(int nDirID, const OString& rFontFile, int nFaceIndex, int nVariationIndex) const
+{
+ fontID nID = 0;
+
+ std::unordered_map< OString, ::std::set< fontID > >::const_iterator set_it = m_aFontFileToFontID.find( rFontFile );
+ if( set_it == m_aFontFileToFontID.end() )
+ return nID;
+
+ for (auto const& elem : set_it->second)
+ {
+ auto it = m_aFonts.find(elem);
+ if( it == m_aFonts.end() )
+ continue;
+ PrintFont* const pFont = (*it).second.get();
+ if (pFont->m_nDirectory == nDirID &&
+ pFont->m_aFontFile == rFontFile &&
+ pFont->m_nCollectionEntry == nFaceIndex &&
+ pFont->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;
+
+ std::unordered_map< OString, ::std::set< fontID > >::const_iterator 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;
+ PrintFont* const pFont = (*it).second.get();
+ if (pFont->m_nDirectory == nDirID &&
+ pFont->m_aFontFile == rFontFile)
+ aIds.push_back(it->first);
+ }
+
+ return aIds;
+}
+
+OUString PrintFontManager::convertSfntName( void* pRecord )
+{
+ NameRecord* pNameRecord = static_cast<NameRecord*>(pRecord);
+ OUString aValue;
+ if(
+ ( pNameRecord->platformID == 3 && ( pNameRecord->encodingID == 0 || pNameRecord->encodingID == 1 ) ) // MS, Unicode
+ ||
+ ( pNameRecord->platformID == 0 ) // Apple, Unicode
+ )
+ {
+ OUStringBuffer aName( pNameRecord->slen/2 );
+ const sal_uInt8* pNameBuffer = pNameRecord->sptr;
+ for(int n = 0; n < pNameRecord->slen/2; n++ )
+ aName.append( static_cast<sal_Unicode>(getUInt16BE( pNameBuffer )) );
+ aValue = aName.makeStringAndClear();
+ }
+ else if( pNameRecord->platformID == 3 )
+ {
+ if( pNameRecord->encodingID >= 2 && pNameRecord->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 = pNameRecord->sptr;
+ for(int n = 0; n < pNameRecord->slen/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( pNameRecord->encodingID )
+ {
+ case 2:
+ aValue = OStringToOUString( aName.makeStringAndClear(), RTL_TEXTENCODING_MS_932 );
+ break;
+ case 3:
+ aValue = OStringToOUString( aName.makeStringAndClear(), RTL_TEXTENCODING_MS_936 );
+ break;
+ case 4:
+ aValue = OStringToOUString( aName.makeStringAndClear(), RTL_TEXTENCODING_MS_950 );
+ break;
+ case 5:
+ aValue = OStringToOUString( aName.makeStringAndClear(), RTL_TEXTENCODING_MS_949 );
+ break;
+ case 6:
+ aValue = OStringToOUString( aName.makeStringAndClear(), RTL_TEXTENCODING_MS_1361 );
+ break;
+ }
+ }
+ }
+ else if( pNameRecord->platformID == 1 )
+ {
+ OString aName(reinterpret_cast<char*>(pNameRecord->sptr), pNameRecord->slen);
+ rtl_TextEncoding eEncoding = RTL_TEXTENCODING_DONTKNOW;
+ switch (pNameRecord->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 (aName.startsWith("Khmer OS"))
+ eEncoding = RTL_TEXTENCODING_UTF8;
+ SAL_WARN_IF(eEncoding == RTL_TEXTENCODING_DONTKNOW, "vcl.fonts", "Unimplemented mac encoding " << pNameRecord->encodingID << " to unicode conversion for fontname " << aName);
+ break;
+ }
+ if (eEncoding != RTL_TEXTENCODING_DONTKNOW)
+ aValue = OStringToOUString(aName, eEncoding);
+ }
+
+ return aValue;
+}
+
+//fdo#33349.There exists an archaic Berling Antiqua font which has a "Times New
+//Roman" name field in it. We don't want the "Times New Roman" name to take
+//precedence in this case. We take Berling Antiqua as a higher priority name,
+//and erase the "Times New Roman" name
+namespace
+{
+ bool isBadTNR(const OUString &rName, ::std::set< OUString >& rSet)
+ {
+ bool bRet = false;
+ if ( rName == "Berling Antiqua" )
+ {
+ ::std::set< OUString >::iterator aEnd = rSet.end();
+ ::std::set< OUString >::iterator aI = rSet.find("Times New Roman");
+ if (aI != aEnd)
+ {
+ bRet = true;
+ rSet.erase(aI);
+ }
+ }
+ return bRet;
+ }
+}
+
+void PrintFontManager::analyzeSfntFamilyName( void const * pTTFont, ::std::vector< OUString >& rNames )
+{
+ OUString aFamily;
+
+ rNames.clear();
+ ::std::set< OUString > aSet;
+
+ NameRecord* pNameRecords = nullptr;
+ int nNameRecords = GetTTNameRecords( static_cast<TrueTypeFont const *>(pTTFont), &pNameRecords );
+ if( nNameRecords && pNameRecords )
+ {
+ LanguageTag aSystem("");
+ LanguageType eLang = aSystem.getLanguageType();
+ int nLastMatch = -1;
+ for( int i = 0; i < nNameRecords; i++ )
+ {
+ if( pNameRecords[i].nameID != 1 || pNameRecords[i].sptr == nullptr )
+ continue;
+ int nMatch = -1;
+ if( pNameRecords[i].platformID == 0 ) // Unicode
+ nMatch = 4000;
+ else if( pNameRecords[i].platformID == 3 )
+ {
+ // this bases on the LanguageType actually being a Win LCID
+ if (pNameRecords[i].languageID == eLang)
+ nMatch = 8000;
+ else if( pNameRecords[i].languageID == LANGUAGE_ENGLISH_US )
+ nMatch = 2000;
+ else if( pNameRecords[i].languageID == LANGUAGE_ENGLISH ||
+ pNameRecords[i].languageID == LANGUAGE_ENGLISH_UK )
+ nMatch = 1500;
+ else
+ nMatch = 1000;
+ }
+ else if (pNameRecords[i].platformID == 1)
+ {
+ AppleLanguageId aAppleId = static_cast<AppleLanguageId>(static_cast<sal_uInt16>(pNameRecords[i].languageID));
+ LanguageTag aApple(makeLanguageTagFromAppleLanguageId(aAppleId));
+ if (aApple == aSystem)
+ nMatch = 8000;
+ else if (aAppleId == AppleLanguageId::ENGLISH)
+ nMatch = 2000;
+ else
+ nMatch = 1000;
+ }
+ OUString aName = convertSfntName( pNameRecords + i );
+ aSet.insert( aName );
+ if (aName.isEmpty())
+ continue;
+ if( nMatch > nLastMatch || isBadTNR(aName, aSet) )
+ {
+ nLastMatch = nMatch;
+ aFamily = aName;
+ }
+ }
+ }
+ DisposeNameRecords( pNameRecords, nNameRecords );
+ if( !aFamily.isEmpty() )
+ {
+ rNames.push_back( aFamily );
+ for (auto const& elem : aSet)
+ if( elem != aFamily )
+ rNames.push_back(elem);
+ }
+}
+
+bool PrintFontManager::analyzeSfntFile( PrintFont* pFont ) const
+{
+ bool bSuccess = false;
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ OString aFile = getFontFile( pFont );
+ TrueTypeFont* pTTFont = nullptr;
+
+ auto const e = OpenTTFontFile( aFile.getStr(), pFont->m_nCollectionEntry, &pTTFont );
+ if( e == SFErrCodes::Ok )
+ {
+ TTGlobalFontInfo aInfo;
+ GetTTGlobalFontInfo( pTTFont, & aInfo );
+
+ ::std::vector< OUString > aNames;
+ analyzeSfntFamilyName( pTTFont, aNames );
+
+ // set family name from XLFD if possible
+ if (pFont->m_aFamilyName.isEmpty())
+ {
+ if( !aNames.empty() )
+ {
+ pFont->m_aFamilyName = aNames.front();
+ aNames.erase(aNames.begin());
+ }
+ else
+ {
+ sal_Int32 dotIndex;
+
+ // poor font does not have a family name
+ // name it to file name minus the extension
+ dotIndex = pFont->m_aFontFile.lastIndexOf( '.' );
+ if ( dotIndex == -1 )
+ dotIndex = pFont->m_aFontFile.getLength();
+
+ pFont->m_aFamilyName = OStringToOUString(pFont->m_aFontFile.copy(0, dotIndex), aEncoding);
+ }
+ }
+ for (auto const& aAlias : aNames)
+ {
+ if (!aAlias.isEmpty())
+ {
+ if (pFont->m_aFamilyName != aAlias)
+ {
+ auto al_it = std::find(pFont->m_aAliases.begin(), pFont->m_aAliases.end(), aAlias);
+ if( al_it == pFont->m_aAliases.end() )
+ pFont->m_aAliases.push_back(aAlias);
+ }
+ }
+ }
+
+ if( aInfo.usubfamily )
+ pFont->m_aStyleName = OUString( aInfo.usubfamily );
+
+ SAL_WARN_IF( !aInfo.psname, "vcl.fonts", "No PostScript name in font:" << aFile );
+
+ pFont->m_aPSName = aInfo.psname ?
+ OUString(aInfo.psname, rtl_str_getLength(aInfo.psname), aEncoding) :
+ pFont->m_aFamilyName; // poor font does not have a postscript name
+
+ pFont->m_eFamilyStyle = matchFamilyName(pFont->m_aFamilyName);
+
+ switch( aInfo.weight )
+ {
+ case FW_THIN: pFont->m_eWeight = WEIGHT_THIN; break;
+ case FW_EXTRALIGHT: pFont->m_eWeight = WEIGHT_ULTRALIGHT; break;
+ case FW_LIGHT: pFont->m_eWeight = WEIGHT_LIGHT; break;
+ case FW_MEDIUM: pFont->m_eWeight = WEIGHT_MEDIUM; break;
+ case FW_SEMIBOLD: pFont->m_eWeight = WEIGHT_SEMIBOLD; break;
+ case FW_BOLD: pFont->m_eWeight = WEIGHT_BOLD; break;
+ case FW_EXTRABOLD: pFont->m_eWeight = WEIGHT_ULTRABOLD; break;
+ case FW_BLACK: pFont->m_eWeight = WEIGHT_BLACK; break;
+
+ case FW_NORMAL:
+ default: pFont->m_eWeight = WEIGHT_NORMAL; break;
+ }
+
+ switch( aInfo.width )
+ {
+ case FWIDTH_ULTRA_CONDENSED: pFont->m_eWidth = WIDTH_ULTRA_CONDENSED; break;
+ case FWIDTH_EXTRA_CONDENSED: pFont->m_eWidth = WIDTH_EXTRA_CONDENSED; break;
+ case FWIDTH_CONDENSED: pFont->m_eWidth = WIDTH_CONDENSED; break;
+ case FWIDTH_SEMI_CONDENSED: pFont->m_eWidth = WIDTH_SEMI_CONDENSED; break;
+ case FWIDTH_SEMI_EXPANDED: pFont->m_eWidth = WIDTH_SEMI_EXPANDED; break;
+ case FWIDTH_EXPANDED: pFont->m_eWidth = WIDTH_EXPANDED; break;
+ case FWIDTH_EXTRA_EXPANDED: pFont->m_eWidth = WIDTH_EXTRA_EXPANDED; break;
+ case FWIDTH_ULTRA_EXPANDED: pFont->m_eWidth = WIDTH_ULTRA_EXPANDED; break;
+
+ case FWIDTH_NORMAL:
+ default: pFont->m_eWidth = WIDTH_NORMAL; break;
+ }
+
+ pFont->m_ePitch = aInfo.pitch ? PITCH_FIXED : PITCH_VARIABLE;
+ pFont->m_eItalic = 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) )
+ pFont->m_eItalic = ITALIC_NORMAL;
+
+ pFont->m_aEncoding = aInfo.symbolEncoded ? RTL_TEXTENCODING_SYMBOL : RTL_TEXTENCODING_UCS2;
+
+ if( aInfo.ascender && aInfo.descender )
+ {
+ pFont->m_nLeading = aInfo.linegap;
+ pFont->m_nAscend = aInfo.ascender;
+ pFont->m_nDescend = -aInfo.descender;
+ }
+ else if( aInfo.typoAscender && aInfo.typoDescender )
+ {
+ pFont->m_nLeading = aInfo.typoLineGap;
+ pFont->m_nAscend = aInfo.typoAscender;
+ pFont->m_nDescend = -aInfo.typoDescender;
+ }
+ else if( aInfo.winAscent && aInfo.winDescent )
+ {
+ pFont->m_nAscend = aInfo.winAscent;
+ pFont->m_nDescend = aInfo.winDescent;
+ pFont->m_nLeading = pFont->m_nAscend + pFont->m_nDescend - 1000;
+ }
+
+ // last try: font bounding box
+ if( pFont->m_nAscend == 0 )
+ pFont->m_nAscend = aInfo.yMax;
+ if( pFont->m_nDescend == 0 )
+ pFont->m_nDescend = -aInfo.yMin;
+ if( pFont->m_nLeading == 0 )
+ pFont->m_nLeading = 15 * (pFont->m_nAscend+pFont->m_nDescend) / 100;
+
+ // get bounding box
+ pFont->m_nXMin = aInfo.xMin;
+ pFont->m_nYMin = aInfo.yMin;
+ pFont->m_nXMax = aInfo.xMax;
+ pFont->m_nYMax = aInfo.yMax;
+
+ 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 );
+ }
+
+ // protect against duplicate paths
+ std::unordered_map< OString, int > visited_dirs;
+
+ // Don't search directories that fontconfig already did
+ countFontconfigFonts( visited_dirs );
+
+#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);
+}
+
+void PrintFontManager::fillPrintFontInfo(PrintFont* pFont, FastPrintFontInfo& rInfo)
+{
+ rInfo.m_aFamilyName = pFont->m_aFamilyName;
+ rInfo.m_aStyleName = pFont->m_aStyleName;
+ rInfo.m_eFamilyStyle = pFont->m_eFamilyStyle;
+ rInfo.m_eItalic = pFont->m_eItalic;
+ rInfo.m_eWidth = pFont->m_eWidth;
+ rInfo.m_eWeight = pFont->m_eWeight;
+ rInfo.m_ePitch = pFont->m_ePitch;
+ rInfo.m_aEncoding = pFont->m_aEncoding;
+
+ rInfo.m_aAliases.clear();
+ for (auto const& aAlias : pFont->m_aAliases)
+ rInfo.m_aAliases.push_back(aAlias);
+}
+
+void PrintFontManager::fillPrintFontInfo( PrintFont* pFont, PrintFontInfo& rInfo ) const
+{
+ if (pFont->m_nAscend == 0 && pFont->m_nDescend == 0)
+ {
+ analyzeSfntFile(pFont);
+ }
+
+ fillPrintFontInfo( pFont, static_cast< FastPrintFontInfo& >( rInfo ) );
+
+ rInfo.m_nAscend = pFont->m_nAscend;
+ rInfo.m_nDescend = pFont->m_nDescend;
+}
+
+bool PrintFontManager::getFontInfo( fontID nFontID, PrintFontInfo& rInfo ) const
+{
+ PrintFont* pFont = getFont( nFontID );
+ if( pFont )
+ {
+ rInfo.m_nID = nFontID;
+ fillPrintFontInfo( pFont, rInfo );
+ }
+ return pFont != nullptr;
+}
+
+bool PrintFontManager::getFontFastInfo( fontID nFontID, FastPrintFontInfo& rInfo ) const
+{
+ PrintFont* pFont = getFont( nFontID );
+ if( pFont )
+ {
+ rInfo.m_nID = nFontID;
+ fillPrintFontInfo( pFont, rInfo );
+ }
+ return pFont != nullptr;
+}
+
+void PrintFontManager::getFontBoundingBox( fontID nFontID, int& xMin, int& yMin, int& xMax, int& yMax )
+{
+ PrintFont* pFont = getFont( nFontID );
+ if( pFont )
+ {
+ if( pFont->m_nXMin == 0 && pFont->m_nYMin == 0 && pFont->m_nXMax == 0 && pFont->m_nYMax == 0 )
+ {
+ analyzeSfntFile(pFont);
+ }
+ xMin = pFont->m_nXMin;
+ yMin = pFont->m_nYMin;
+ xMax = pFont->m_nXMax;
+ yMax = pFont->m_nYMax;
+ }
+}
+
+int PrintFontManager::getFontFaceNumber( fontID nFontID ) const
+{
+ int nRet = 0;
+ 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;
+ PrintFont* pFont = getFont( nFontID );
+ if (pFont)
+ {
+ nRet = pFont->m_nVariationEntry;
+ if (nRet < 0)
+ nRet = 0;
+ }
+ return nRet;
+}
+
+FontFamily PrintFontManager::matchFamilyName( const OUString& 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* pFont) const
+{
+ OString aPath;
+
+ if (pFont)
+ {
+ std::unordered_map< int, OString >::const_iterator it = m_aAtomToDir.find(pFont->m_nDirectory);
+ aPath = it->second + "/" + pFont->m_aFontFile;
+ }
+ return aPath;
+}
+
+OUString PrintFontManager::getPSName( fontID nFontID ) const
+{
+ PrintFont* pFont = getFont( nFontID );
+ if (pFont && pFont->m_aPSName.isEmpty())
+ {
+ analyzeSfntFile(pFont);
+ }
+
+ return pFont ? pFont->m_aPSName : OUString();
+}
+
+int PrintFontManager::getFontAscend( fontID nFontID ) const
+{
+ PrintFont* pFont = getFont( nFontID );
+ if (pFont && pFont->m_nAscend == 0 && pFont->m_nDescend == 0)
+ {
+ analyzeSfntFile(pFont);
+ }
+ return pFont ? pFont->m_nAscend : 0;
+}
+
+int PrintFontManager::getFontDescend( fontID nFontID ) const
+{
+ PrintFont* pFont = getFont( nFontID );
+ if (pFont && pFont->m_nAscend == 0 && pFont->m_nDescend == 0)
+ {
+ analyzeSfntFile(pFont);
+ }
+ return pFont ? pFont->m_nDescend : 0;
+}
+
+// TODO: move most of this stuff into the central font-subsetting code
+bool PrintFontManager::createFontSubset(
+ FontSubsetInfo& rInfo,
+ fontID nFont,
+ const OUString& rOutFile,
+ const sal_GlyphId* pGlyphIds,
+ const sal_uInt8* pNewEncoding,
+ sal_Int32* pWidths,
+ int nGlyphs
+ )
+{
+ PrintFont* pFont = getFont( nFont );
+ if( !pFont )
+ return false;
+
+ rInfo.m_nFontType = FontType::SFNT_TTF;
+
+ // reshuffle array of requested glyphs to make sure glyph0==notdef
+ sal_uInt8 pEnc[256];
+ sal_uInt16 pGID[256];
+ sal_uInt8 pOldIndex[256];
+ memset( pEnc, 0, sizeof( pEnc ) );
+ memset( pGID, 0, sizeof( pGID ) );
+ memset( pOldIndex, 0, sizeof( pOldIndex ) );
+ if( nGlyphs > 256 )
+ return false;
+ int nChar = 1;
+ for( int i = 0; i < nGlyphs; i++ )
+ {
+ if( pNewEncoding[i] == 0 )
+ {
+ pOldIndex[ 0 ] = i;
+ }
+ else
+ {
+ SAL_WARN_IF( (pGlyphIds[i] & 0x007f0000), "vcl.fonts", "overlong glyph id" );
+ SAL_WARN_IF( static_cast<int>(pNewEncoding[i]) >= nGlyphs, "vcl.fonts", "encoding wrong" );
+ SAL_WARN_IF( pEnc[pNewEncoding[i]] != 0 || pGID[pNewEncoding[i]] != 0, "vcl.fonts", "duplicate encoded glyph" );
+ pEnc[ pNewEncoding[i] ] = pNewEncoding[i];
+ pGID[ pNewEncoding[i] ] = static_cast<sal_uInt16>(pGlyphIds[ i ]);
+ pOldIndex[ pNewEncoding[i] ] = i;
+ nChar++;
+ }
+ }
+ nGlyphs = nChar; // either input value or increased by one
+
+ // prepare system name for read access for subset source file
+ // TODO: since this file is usually already mmapped there is no need to open it again
+ const OString aFromFile = getFontFile( pFont );
+
+ TrueTypeFont* pTTFont = nullptr; // TODO: rename to SfntFont
+ if( OpenTTFontFile( aFromFile.getStr(), pFont->m_nCollectionEntry, &pTTFont ) != SFErrCodes::Ok )
+ return false;
+
+ // prepare system name for write access for subset file target
+ OUString aSysPath;
+ if( osl_File_E_None != osl_getSystemPathFromFileURL( rOutFile.pData, &aSysPath.pData ) )
+ return false;
+ const rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ const OString aToFile( OUStringToOString( aSysPath, aEncoding ) );
+
+ // do CFF subsetting if possible
+ int nCffLength = 0;
+ const sal_uInt8* pCffBytes = nullptr;
+ if( GetSfntTable( pTTFont, O_CFF, &pCffBytes, &nCffLength ) )
+ {
+ rInfo.LoadFont( FontType::CFF_FONT, pCffBytes, nCffLength );
+#if 1 // TODO: remove 16bit->long conversion when related methods handle non-16bit glyphids
+ sal_GlyphId aRequestedGlyphIds[256];
+ for( int i = 0; i < nGlyphs; ++i )
+ aRequestedGlyphIds[i] = pGID[i];
+#endif
+ // create subset file at requested path
+ FILE* pOutFile = fopen( aToFile.getStr(), "wb" );
+ if (!pOutFile)
+ {
+ CloseTTFont( pTTFont );
+ return false;
+ }
+ // create font subset
+ const char* const pGlyphSetName = nullptr; // TODO: better name?
+ const bool bOK = rInfo.CreateFontSubset(
+ FontType::TYPE1_PFB,
+ pOutFile, pGlyphSetName,
+ aRequestedGlyphIds, pEnc, nGlyphs, pWidths );
+ fclose( pOutFile );
+ // For OTC, values from hhea or OS2 are better
+ psp::PrintFontInfo aFontInfo;
+ if( getFontInfo( nFont, aFontInfo ) )
+ {
+ rInfo.m_nAscent = aFontInfo.m_nAscend;
+ rInfo.m_nDescent = -aFontInfo.m_nDescend;
+ }
+ // cleanup before early return
+ CloseTTFont( pTTFont );
+ return bOK;
+ }
+
+ // do TTF->Type42 or Type3 subsetting
+ // fill in font info
+ psp::PrintFontInfo aFontInfo;
+ if( ! getFontInfo( nFont, aFontInfo ) )
+ return false;
+
+ rInfo.m_nAscent = aFontInfo.m_nAscend;
+ rInfo.m_nDescent = aFontInfo.m_nDescend;
+ rInfo.m_aPSName = getPSName( nFont );
+
+ int xMin, yMin, xMax, yMax;
+ getFontBoundingBox( nFont, xMin, yMin, xMax, yMax );
+ rInfo.m_aFontBBox = tools::Rectangle( Point( xMin, yMin ), Size( xMax-xMin, yMax-yMin ) );
+ rInfo.m_nCapHeight = yMax; // Well ...
+
+ // fill in glyph advance widths
+ std::unique_ptr<sal_uInt16[]> pMetrics = GetTTSimpleGlyphMetrics( pTTFont,
+ pGID,
+ nGlyphs,
+ false/*bVertical*/ );
+ if( pMetrics )
+ {
+ for( int i = 0; i < nGlyphs; i++ )
+ pWidths[pOldIndex[i]] = pMetrics[i];
+ pMetrics.reset();
+ }
+ else
+ {
+ CloseTTFont( pTTFont );
+ return false;
+ }
+
+ bool bSuccess = ( SFErrCodes::Ok == CreateTTFromTTGlyphs( pTTFont,
+ aToFile.getStr(),
+ pGID,
+ pEnc,
+ nGlyphs ) );
+ CloseTTFont( pTTFont );
+
+ return bSuccess;
+}
+
+void PrintFontManager::getGlyphWidths( fontID nFont,
+ bool bVertical,
+ std::vector< sal_Int32 >& rWidths,
+ std::map< sal_Unicode, sal_uInt32 >& rUnicodeEnc )
+{
+ PrintFont* pFont = getFont( nFont );
+ if (!pFont)
+ return;
+ TrueTypeFont* pTTFont = nullptr;
+ OString aFromFile = getFontFile( pFont );
+ if( OpenTTFontFile( aFromFile.getStr(), pFont->m_nCollectionEntry, &pTTFont ) != SFErrCodes::Ok )
+ return;
+ int nGlyphs = GetTTGlyphCount(pTTFont);
+ if (nGlyphs > 0)
+ {
+ rWidths.resize(nGlyphs);
+ std::vector<sal_uInt16> aGlyphIds(nGlyphs);
+ for (int i = 0; i < nGlyphs; i++)
+ aGlyphIds[i] = sal_uInt16(i);
+ std::unique_ptr<sal_uInt16[]> pMetrics = GetTTSimpleGlyphMetrics(pTTFont,
+ aGlyphIds.data(),
+ nGlyphs,
+ bVertical);
+ if (pMetrics)
+ {
+ for (int i = 0; i< nGlyphs; i++)
+ rWidths[i] = pMetrics[i];
+ pMetrics.reset();
+ rUnicodeEnc.clear();
+ }
+
+ // fill the unicode map
+ // TODO: isn't this map already available elsewhere in the fontmanager?
+ const sal_uInt8* pCmapData = nullptr;
+ int nCmapSize = 0;
+ if (GetSfntTable(pTTFont, O_cmap, &pCmapData, &nCmapSize))
+ {
+ CmapResult aCmapResult;
+ if (ParseCMAP(pCmapData, nCmapSize, aCmapResult))
+ {
+ FontCharMapRef xFontCharMap(new FontCharMap(aCmapResult));
+ for (sal_uInt32 cOld = 0;;)
+ {
+ // get next unicode covered by font
+ const sal_uInt32 c = xFontCharMap->GetNextChar(cOld);
+ if (c == cOld)
+ break;
+ cOld = c;
+#if 1 // TODO: remove when sal_Unicode covers all of unicode
+ if (c > sal_Unicode(~0))
+ break;
+#endif
+ // get the matching glyph index
+ const sal_GlyphId aGlyphId = xFontCharMap->GetGlyphIndex(c);
+ // update the requested map
+ rUnicodeEnc[static_cast<sal_Unicode>(c)] = aGlyphId;
+ }
+ }
+ }
+ }
+ CloseTTFont(pTTFont);
+}
+
+/// used by online unit tests via dlopen.
+extern "C" {
+SAL_DLLPUBLIC_EXPORT const char * unit_online_get_fonts(void)
+{
+ std::vector< fontID > aFontIDs;
+ PrintFontManager &rMgr = PrintFontManager::get();
+ rMgr.getFontList(aFontIDs);
+ OStringBuffer aBuf;
+ aBuf.append( static_cast<sal_Int32>(aFontIDs.size()) );
+ aBuf.append( " PS fonts.\n" );
+ for( auto nId : aFontIDs )
+ {
+ const OUString& rName = rMgr.getPSName( nId );
+ aBuf.append( OUStringToOString( rName, RTL_TEXTENCODING_UTF8 ) );
+ aBuf.append( "\n" );
+ }
+ static OString aResult = aBuf.makeStringAndClear();
+ return aResult.getStr();
+}
+}
+
+/* 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 000000000..7e71a96c3
--- /dev/null
+++ b/vcl/unx/generic/fontmanager/fontsubst.cxx
@@ -0,0 +1,221 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/geninst.h>
+#include <outdev.h>
+#include <unx/fontmanager.hxx>
+#include <PhysicalFontCollection.hxx>
+
+// platform specific font substitution hooks
+
+namespace {
+
+class FcPreMatchSubstitution
+: public ImplPreMatchFontSubstitution
+{
+public:
+ bool FindFontSubstitute( FontSelectPattern& ) const override;
+ typedef ::std::pair<FontSelectPattern, FontSelectPattern> value_type;
+private:
+ typedef ::std::list<value_type> CachedFontMapType;
+ mutable CachedFontMapType maCachedFontMap;
+};
+
+class FcGlyphFallbackSubstitution
+: public ImplGlyphFallbackFontSubstitution
+{
+ // TODO: add a cache
+public:
+ bool FindFontSubstitute(FontSelectPattern&, LogicalFontInstance* pLogicalFont, OUString& rMissingCodes) const override;
+};
+
+}
+
+void SalGenericInstance::RegisterFontSubstitutors( PhysicalFontCollection* pFontCollection )
+{
+ // register font fallback substitutions
+ static FcPreMatchSubstitution aSubstPreMatch;
+ pFontCollection->SetPreMatchHook( &aSubstPreMatch );
+
+ // register glyph fallback substitutions
+ static FcGlyphFallbackSubstitution aSubstFallback;
+ pFontCollection->SetFallbackHook( &aSubstFallback );
+}
+
+static FontSelectPattern GetFcSubstitute(const FontSelectPattern &rFontSelData, OUString& rMissingCodes)
+{
+ FontSelectPattern aSubstituted(rFontSelData);
+ psp::PrintFontManager& rMgr = psp::PrintFontManager::get();
+ rMgr.Substitute(aSubstituted, rMissingCodes);
+ return aSubstituted;
+}
+
+namespace
+{
+ bool uselessmatch(const FontSelectPattern &rOrig, const 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 FontSelectPattern& mrAttributes;
+ public:
+ explicit equal(const FontSelectPattern& rAttributes)
+ : mrAttributes(rAttributes)
+ {
+ }
+ bool operator()(const FcPreMatchSubstitution::value_type& rOther) const
+ { return rOther.first == mrAttributes; }
+ };
+}
+
+bool FcPreMatchSubstitution::FindFontSubstitute(FontSelectPattern &rFontSelData) const
+{
+ // We don't actually want to talk to Fontconfig at all for symbol fonts
+ if( rFontSelData.IsSymbolFont() )
+ return false;
+ // StarSymbol is a unicode font, but it still deserves the symbol flag
+ if ( IsStarSymbol(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
+ 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 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(FontSelectPattern& rFontSelData,
+ LogicalFontInstance* /*pLogicalFont*/,
+ OUString& rMissingCodes ) const
+{
+ // We don't actually want to talk to Fontconfig at all for symbol fonts
+ if( rFontSelData.IsSymbolFont() )
+ return false;
+ // StarSymbol is a unicode font, but it still deserves the symbol flag
+ if ( IsStarSymbol(rFontSelData.maSearchName) )
+ return false;
+
+ const 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 000000000..c28753e33
--- /dev/null
+++ b/vcl/unx/generic/fontmanager/helper.cxx
@@ -0,0 +1,262 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 += OStringLiteral("/") + 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() )
+ {
+ // 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 );
+ aPathBuffer.append( "/" LIBO_SHARE_FOLDER "/fonts/truetype;");
+ }
+ if( !aUserPath.isEmpty() )
+ {
+ aPathBuffer.append( aUserPath );
+ aPathBuffer.append( "/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("//", "/");
+
+ 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/cairo_xlib_cairo.cxx b/vcl/unx/generic/gdi/cairo_xlib_cairo.cxx
new file mode 100644
index 000000000..3a8250521
--- /dev/null
+++ b/vcl/unx/generic/gdi/cairo_xlib_cairo.cxx
@@ -0,0 +1,314 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <X11/Xlib.h>
+#include <X11/extensions/Xrender.h>
+
+#include "cairo_xlib_cairo.hxx"
+
+#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),
+ pRenderFormat(nullptr)
+ {}
+
+ X11SysData::X11SysData( const SystemGraphicsData& pSysDat ) :
+ pDisplay(pSysDat.pDisplay),
+ hDrawable(pSysDat.hDrawable),
+ pVisual(pSysDat.pVisual),
+ nScreen(pSysDat.nScreen),
+ pRenderFormat(pSysDat.pXRenderFormat)
+ {}
+
+ X11SysData::X11SysData( const SystemEnvData& pSysDat ) :
+ pDisplay(pSysDat.pDisplay),
+ hDrawable(pSysDat.aWindow),
+ pVisual(pSysDat.pVisual),
+ nScreen(pSysDat.nScreen),
+ pRenderFormat(nullptr)
+ {}
+
+ X11Pixmap::~X11Pixmap()
+ {
+ if( mpDisplay && mhDrawable )
+ XFreePixmap( static_cast<Display*>(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,
+ const X11PixmapSharedPtr& rPixmap,
+ const CairoSurfaceSharedPtr& pSurface ) :
+ maSysData(rSysData),
+ mpPixmap(rPixmap),
+ mpSurface(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( const CairoSurfaceSharedPtr& pSurface ) :
+ maSysData(),
+ mpPixmap(),
+ mpSurface(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),
+ mpPixmap(),
+ mpSurface(
+ cairo_xlib_surface_create( static_cast<Display*>(rSysData.pDisplay),
+ rSysData.hDrawable,
+ static_cast<Visual*>(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 ),
+ mpPixmap(),
+ mpSurface(
+ cairo_xlib_surface_create( static_cast<Display*>(rSysData.pDisplay),
+ reinterpret_cast<Drawable>(rData.aPixmap),
+ static_cast<Visual*>(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
+ {
+ Pixmap hPixmap;
+
+ 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( static_cast<Display*>(maSysData.pDisplay), nFormat );
+ hPixmap = limitXCreatePixmap( static_cast<Display*>(maSysData.pDisplay), maSysData.hDrawable,
+ width > 0 ? width : 1, height > 0 ? height : 1,
+ pFormat->depth );
+
+ X11SysData aSysData(maSysData);
+ aSysData.pRenderFormat = pFormat;
+ return SurfaceSharedPtr(
+ new X11Surface( aSysData,
+ std::make_shared<X11Pixmap>(hPixmap, maSysData.pDisplay),
+ CairoSurfaceSharedPtr(
+ cairo_xlib_surface_create_with_xrender_format(
+ static_cast<Display*>(maSysData.pDisplay),
+ hPixmap,
+ ScreenOfDisplay(static_cast<Display *>(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;
+
+ aSystemGraphicsData.nSize = sizeof(SystemGraphicsData);
+ aSystemGraphicsData.hDrawable = mpPixmap ? mpPixmap->mhDrawable : maSysData.hDrawable;
+ aSystemGraphicsData.pXRenderFormat = maSysData.pRenderFormat;
+
+ int width = cairo_xlib_surface_get_width(mpSurface.get());
+ int height = cairo_xlib_surface_get_height(mpSurface.get());
+
+ return VclPtr<VirtualDevice>::Create(aSystemGraphicsData,
+ Size(width, height),
+ getFormat());
+ }
+
+ /**
+ * 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( static_cast<Display*>(maSysData.pDisplay), false );
+ }
+
+ /**
+ * Surface::getDepth: Get the color depth of the Canvas surface.
+ *
+ * @return color depth
+ **/
+ int X11Surface::getDepth() const
+ {
+ if (maSysData.pRenderFormat)
+ return static_cast<XRenderPictFormat*>(maSysData.pRenderFormat)->depth;
+ return -1;
+ }
+
+ /**
+ * Surface::getFormat: Get the device format of the Canvas surface.
+ *
+ * @return color format
+ **/
+ DeviceFormat X11Surface::getFormat() const
+ {
+ if (!maSysData.pRenderFormat)
+ return DeviceFormat::DEFAULT;
+ switch (static_cast<XRenderPictFormat*>(maSysData.pRenderFormat)->depth)
+ {
+ case 1:
+ return DeviceFormat::BITMASK;
+ default:
+ return DeviceFormat::DEFAULT;
+ }
+ }
+}
+
+/* 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 000000000..8b67c5951
--- /dev/null
+++ b/vcl/unx/generic/gdi/cairo_xlib_cairo.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 .
+ */
+
+#ifndef INCLUDED_CANVAS_SOURCE_CAIRO_CAIRO_XLIB_CAIRO_HXX
+#define INCLUDED_CANVAS_SOURCE_CAIRO_CAIRO_XLIB_CAIRO_HXX
+
+#include <sal/config.h>
+#include <vcl/cairo.hxx>
+#include <vcl/salgtype.hxx>
+
+struct BitmapSystemData;
+struct SystemEnvData;
+struct SystemGraphicsData;
+
+namespace cairo {
+
+ /// Holds all X11-output relevant data
+ struct X11SysData
+ {
+ X11SysData();
+ explicit X11SysData( const SystemGraphicsData& );
+ explicit X11SysData( const SystemEnvData& );
+
+ void* pDisplay; // the relevant display connection
+ long hDrawable; // a drawable
+ void* pVisual; // the visual in use
+ int nScreen; // the current screen of the drawable
+ void* pRenderFormat; // render format for drawable
+ };
+
+ /// RAII wrapper for a pixmap
+ struct X11Pixmap
+ {
+ void* mpDisplay; // the relevant display connection
+ long mhDrawable; // a drawable
+
+ X11Pixmap( long hDrawable, void* 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, const X11PixmapSharedPtr& rPixmap, const CairoSurfaceSharedPtr& pSurface );
+
+ public:
+ /// takes over ownership of passed cairo_surface
+ explicit X11Surface( const 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;
+
+ int getDepth() const;
+ DeviceFormat getFormat() const;
+ const X11PixmapSharedPtr& getPixmap() const { return mpPixmap; }
+ };
+}
+
+#endif
+
+/* 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 000000000..e4da35f35
--- /dev/null
+++ b/vcl/unx/generic/gdi/cairotextrender.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 <unx/cairotextrender.hxx>
+
+#include <unx/fc_fontoptions.hxx>
+#include <unx/freetype_glyphcache.hxx>
+#include <vcl/svapp.hxx>
+#include <sallayout.hxx>
+
+#include <cairo.h>
+#include <cairo-ft.h>
+
+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<void *, CacheId> > LRUFonts;
+ static LRUFonts maLRUFonts;
+public:
+ CairoFontsCache() = delete;
+
+ static void CacheFont(void *pFont, const CacheId &rId);
+ static void* FindCachedFont(const CacheId &rId);
+};
+
+CairoFontsCache::LRUFonts CairoFontsCache::maLRUFonts;
+
+void CairoFontsCache::CacheFont(void *pFont, const CairoFontsCache::CacheId &rId)
+{
+ maLRUFonts.push_front( std::pair<void*, CairoFontsCache::CacheId>(pFont, rId) );
+ if (maLRUFonts.size() > 8)
+ {
+ cairo_font_face_destroy(static_cast<cairo_font_face_t*>(maLRUFonts.back().first));
+ maLRUFonts.pop_back();
+ }
+}
+
+void* 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(int nDegree10th)
+ {
+ return (3600 - nDegree10th) * M_PI / 1800.0;
+ }
+
+ 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;
+ }
+}
+
+void CairoTextRender::DrawTextLayout(const GenericSalLayout& rLayout, const SalGraphics& rGraphics)
+{
+ const FreetypeFontInstance& rInstance = static_cast<FreetypeFontInstance&>(rLayout.GetFont());
+ const FreetypeFont& rFont = rInstance.GetFreetypeFont();
+
+ std::vector<cairo_glyph_t> cairo_glyphs;
+ std::vector<int> glyph_extrarotation;
+ cairo_glyphs.reserve( 256 );
+
+ Point aPos;
+ const GlyphItem* pGlyph;
+ int nStart = 0;
+ while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart))
+ {
+ cairo_glyph_t aGlyph;
+ aGlyph.index = pGlyph->glyphId();
+ aGlyph.x = aPos.X();
+ aGlyph.y = aPos.Y();
+ cairo_glyphs.push_back(aGlyph);
+
+ if (pGlyph->IsVertical())
+ glyph_extrarotation.push_back(1);
+ else
+ glyph_extrarotation.push_back(0);
+ }
+
+ if (cairo_glyphs.empty())
+ return;
+
+ const FontSelectPattern& rFSD = rInstance.GetFontSelectPattern();
+ int nHeight = rFSD.mnHeight;
+ int nWidth = rFSD.mnWidth ? rFSD.mnWidth : nHeight;
+ if (nWidth == 0 || nHeight == 0)
+ return;
+
+ int nRatio = nWidth * 10 / nHeight;
+ if (FreetypeFont::AlmostHorizontalDrainsRenderingPool(nRatio, rFSD))
+ return;
+
+ /*
+ * 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;
+ }
+
+ ImplSVData* pSVData = ImplGetSVData();
+ if (const cairo_font_options_t* pFontOptions = pSVData->mpDefInst->GetCairoFontOptions())
+ {
+ const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
+ if (!rStyleSettings.GetUseFontAAFromSystem() && !rGraphics.getAntiAliasB2DDraw())
+ {
+ // 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 = cairo_font_options_copy(pFontOptions);
+ cairo_font_options_set_antialias(pOptions, CAIRO_ANTIALIAS_NONE);
+ cairo_set_font_options(cr, pOptions);
+ cairo_font_options_destroy(pOptions);
+ }
+ else
+ cairo_set_font_options(cr, pFontOptions);
+ }
+
+ double nDX, nDY;
+ getSurfaceOffset(nDX, nDY);
+ cairo_translate(cr, nDX, nDY);
+
+ clipRegion(cr);
+
+ cairo_set_source_rgb(cr,
+ mnTextColor.GetRed()/255.0,
+ mnTextColor.GetGreen()/255.0,
+ mnTextColor.GetBlue()/255.0);
+
+ FT_Face aFace = rFont.GetFtFace();
+ CairoFontsCache::CacheId aId;
+ aId.maFace = aFace;
+ aId.mpOptions = rFont.GetFontOptions();
+ aId.mbEmbolden = rFont.NeedsArtificialBold();
+
+ cairo_matrix_t m;
+
+ 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;
+ cairo_font_face_t* font_face = static_cast<cairo_font_face_t*>(CairoFontsCache::FindCachedFont(aId));
+ if (!font_face)
+ {
+ const FontConfigFontOptions *pOptions = aId.mpOptions;
+ FcPattern *pPattern = pOptions->GetPattern();
+ font_face = cairo_ft_font_face_create_for_pattern(pPattern);
+ CairoFontsCache::CacheFont(font_face, aId);
+ }
+ cairo_set_font_face(cr, font_face);
+
+ cairo_set_font_size(cr, nHeight);
+
+ 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(nGlyphRotation*900));
+
+ cairo_matrix_t em_square;
+ cairo_matrix_init_identity(&em_square);
+ cairo_get_matrix(cr, &em_square);
+
+ cairo_matrix_scale(&em_square, aFace->units_per_EM,
+ aFace->units_per_EM);
+ cairo_set_matrix(cr, &em_square);
+
+ cairo_font_extents_t font_extents;
+ cairo_font_extents(cr, &font_extents);
+
+ cairo_matrix_init_identity(&em_square);
+ cairo_set_matrix(cr, &em_square);
+
+ //gives the same positions as pre-cairo conversion, but I don't
+ //like them
+ double xdiff = 0.0;
+ double ydiff = 0.0;
+
+ // The y is the origin point position, but Cairo will draw
+ // the glyph *above* that point, we need to move it down to
+ // the glyph’s baseline.
+ cairo_text_extents_t aExt;
+ cairo_glyph_extents(cr, &cairo_glyphs[nStartIndex], nLen, &aExt);
+ double nDescender = std::fmax(aExt.height + aExt.y_bearing, 0);
+ ydiff = (aExt.x_advance - nDescender) / nHeight;
+ xdiff = -font_extents.descent/nHeight;
+
+ cairo_matrix_translate(&m, xdiff, ydiff);
+ }
+
+ if (rFont.NeedsArtificialItalic())
+ {
+ cairo_matrix_t shear;
+ cairo_matrix_init_identity(&shear);
+ shear.xy = -shear.xx * 0x6000L / 0x10000L;
+ cairo_matrix_multiply(&m, &shear, &m);
+ }
+
+ cairo_set_font_matrix(cr, &m);
+ 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;
+ }
+
+ releaseCairoContext(cr);
+}
+
+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);
+}
+
+/* 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 000000000..ede4d953c
--- /dev/null
+++ b/vcl/unx/generic/gdi/font.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/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);
+ nTextPixel_ = GetPixel( nColor );
+}
+
+bool X11SalGraphics::AddTempDevFont( PhysicalFontCollection* pFontCollection,
+ const OUString& rFileURL,
+ const OUString& rFontName )
+{
+ return mxTextRenderImpl->AddTempDevFont(pFontCollection, rFileURL, rFontName);
+}
+
+void X11SalGraphics::ClearDevFontCache()
+{
+ mxTextRenderImpl->ClearDevFontCache();
+}
+
+void X11SalGraphics::GetDevFontList( PhysicalFontCollection* pFontCollection )
+{
+ mxTextRenderImpl->GetDevFontList(pFontCollection);
+}
+
+void
+X11SalGraphics::GetFontMetric( ImplFontMetricDataRef &rxFontMetric, int nFallbackLevel )
+{
+ mxTextRenderImpl->GetFontMetric(rxFontMetric, nFallbackLevel);
+}
+
+std::unique_ptr<GenericSalLayout> X11SalGraphics::GetTextLayout(int nFallbackLevel)
+{
+ return mxTextRenderImpl->GetTextLayout(nFallbackLevel);
+}
+
+#if ENABLE_CAIRO_CANVAS
+
+SystemFontData X11SalGraphics::GetSysFontData( int nFallbackLevel ) const
+{
+ return mxTextRenderImpl->GetSysFontData(nFallbackLevel);
+}
+
+#endif
+
+bool X11SalGraphics::CreateFontSubset(
+ const OUString& rToFile,
+ const PhysicalFontFace* pFont,
+ const sal_GlyphId* pGlyphIds,
+ const sal_uInt8* pEncoding,
+ sal_Int32* pWidths,
+ int nGlyphCount,
+ FontSubsetInfo& rInfo
+ )
+{
+ return mxTextRenderImpl->CreateFontSubset(rToFile, pFont,
+ pGlyphIds, pEncoding, pWidths, nGlyphCount, rInfo);
+}
+
+const void* X11SalGraphics::GetEmbedFontData(const PhysicalFontFace* pFont, long* pDataLen)
+{
+ return mxTextRenderImpl->GetEmbedFontData(pFont, pDataLen);
+}
+
+void X11SalGraphics::FreeEmbedFontData( const void* pData, long nLen )
+{
+ mxTextRenderImpl->FreeEmbedFontData(pData, nLen);
+}
+
+void X11SalGraphics::GetGlyphWidths( const PhysicalFontFace* pFont,
+ bool bVertical,
+ std::vector< sal_Int32 >& rWidths,
+ Ucs2UIntMap& rUnicodeEnc )
+{
+ mxTextRenderImpl->GetGlyphWidths(pFont, bVertical, rWidths, rUnicodeEnc);
+}
+
+/* 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 000000000..3d94da304
--- /dev/null
+++ b/vcl/unx/generic/gdi/freetypetextrender.cxx
@@ -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 .
+ */
+
+#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/genpspgraphics.h>
+#include <unx/geninst.h>
+#include <unx/glyphcache.hxx>
+#include <unx/fc_fontoptions.hxx>
+#include <unx/freetype_glyphcache.hxx>
+#include <PhysicalFontFace.hxx>
+#include <impfontmetricdata.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]->GetFreetypeFont().GetFontCharMap();
+}
+
+bool FreeTypeTextRenderImpl::GetFontCapabilities(vcl::FontCapabilities &rGetImplFontCapabilities) const
+{
+ if (!mpFreetypeFont[0])
+ return false;
+ return mpFreetypeFont[0]->GetFreetypeFont().GetFontCapabilities(rGetImplFontCapabilities);
+}
+
+// SalGraphics
+void
+FreeTypeTextRenderImpl::SetTextColor( Color nColor )
+{
+ if( mnTextColor != nColor )
+ {
+ mnTextColor = nColor;
+ }
+}
+
+bool FreeTypeTextRenderImpl::AddTempDevFont( PhysicalFontCollection* pFontCollection,
+ const OUString& rFileURL,
+ const OUString& rFontName )
+{
+ return GenPspGraphics::AddTempDevFontHelper(pFontCollection, rFileURL, rFontName);
+}
+
+void FreeTypeTextRenderImpl::ClearDevFontCache()
+{
+ FreetypeManager::get().ClearFontCache();
+}
+
+void FreeTypeTextRenderImpl::GetDevFontList( 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;
+ psp::FastPrintFontInfo aInfo;
+ rMgr.getFontList( aList );
+ for (auto const& elem : aList)
+ {
+ if( !rMgr.getFontFastInfo( elem, aInfo ) )
+ continue;
+
+ // normalize face number to the FreetypeManager
+ int nFaceNum = rMgr.getFontFaceNumber( aInfo.m_nID );
+ int nVariantNum = rMgr.getFontFaceVariation( aInfo.m_nID );
+
+ // inform FreetypeManager about this font provided by the PsPrint subsystem
+ FontAttributes aDFA = GenPspGraphics::Info2FontAttributes( aInfo );
+ aDFA.IncreaseQualityBy( 4096 );
+ const OString& rFileName = rMgr.getFontFileSysPath( aInfo.m_nID );
+ rFreetypeManager.AddFontFile(rFileName, nFaceNum, nVariantNum, aInfo.m_nID, aDFA);
+ }
+
+ // announce glyphcache fonts
+ rFreetypeManager.AnnounceFonts(pFontCollection);
+
+ // register platform specific font substitutions if available
+ if (!utl::ConfigManager::IsFuzzing())
+ SalGenericInstance::RegisterFontSubstitutors( pFontCollection );
+}
+
+void FreeTypeTextRenderImpl::GetFontMetric( ImplFontMetricDataRef& 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]);
+}
+
+#if ENABLE_CAIRO_CANVAS
+SystemFontData FreeTypeTextRenderImpl::GetSysFontData( int nFallbackLevel ) const
+{
+ SystemFontData aSysFontData;
+
+ if (nFallbackLevel >= MAX_FALLBACK) nFallbackLevel = MAX_FALLBACK - 1;
+ if (nFallbackLevel < 0 ) nFallbackLevel = 0;
+
+ if (mpFreetypeFont[nFallbackLevel])
+ {
+ FreetypeFont& rFreetypeFont = mpFreetypeFont[nFallbackLevel]->GetFreetypeFont();
+ aSysFontData.nFontId = rFreetypeFont.GetFtFace();
+ aSysFontData.nFontFlags = rFreetypeFont.GetLoadFlags();
+ aSysFontData.bFakeBold = rFreetypeFont.NeedsArtificialBold();
+ aSysFontData.bFakeItalic = rFreetypeFont.NeedsArtificialItalic();
+ aSysFontData.bAntialias = rFreetypeFont.GetAntialiasAdvice();
+ aSysFontData.bVerticalCharacterType = mpFreetypeFont[nFallbackLevel]->GetFontSelectPattern().mbVertical;
+ }
+
+ return aSysFontData;
+}
+#endif
+
+bool FreeTypeTextRenderImpl::CreateFontSubset(
+ const OUString& rToFile,
+ const PhysicalFontFace* pFont,
+ const sal_GlyphId* pGlyphIds,
+ const sal_uInt8* pEncoding,
+ sal_Int32* pWidths,
+ int nGlyphCount,
+ FontSubsetInfo& rInfo
+ )
+{
+ // in this context the pFont->GetFontId() is a valid PSP
+ // font since they are the only ones left after the PDF
+ // export has filtered its list of subsettable fonts (for
+ // which this method was created). The correct way would
+ // be to have the FreetypeManager search for the PhysicalFontFace pFont
+ psp::fontID aFont = pFont->GetFontId();
+
+ psp::PrintFontManager& rMgr = psp::PrintFontManager::get();
+ bool bSuccess = rMgr.createFontSubset( rInfo,
+ aFont,
+ rToFile,
+ pGlyphIds,
+ pEncoding,
+ pWidths,
+ nGlyphCount );
+ return bSuccess;
+}
+
+const void* FreeTypeTextRenderImpl::GetEmbedFontData(const PhysicalFontFace* pFont, long* pDataLen)
+{
+ // in this context the pFont->GetFontId() is a valid PSP
+ // font since they are the only ones left after the PDF
+ // export has filtered its list of subsettable fonts (for
+ // which this method was created). The correct way would
+ // be to have the FreetypeManager search for the PhysicalFontFace pFont
+ psp::fontID aFont = pFont->GetFontId();
+ return GenPspGraphics::DoGetEmbedFontData(aFont, pDataLen);
+}
+
+void FreeTypeTextRenderImpl::FreeEmbedFontData( const void* pData, long nLen )
+{
+ GenPspGraphics::DoFreeEmbedFontData( pData, nLen );
+}
+
+void FreeTypeTextRenderImpl::GetGlyphWidths( const PhysicalFontFace* pFont,
+ bool bVertical,
+ std::vector< sal_Int32 >& rWidths,
+ Ucs2UIntMap& rUnicodeEnc )
+{
+ // in this context the pFont->GetFontId() is a valid PSP
+ // font since they are the only ones left after the PDF
+ // export has filtered its list of subsettable fonts (for
+ // which this method was created). The correct way would
+ // be to have the FreetypeManager search for the PhysicalFontFace pFont
+ psp::fontID aFont = pFont->GetFontId();
+ GenPspGraphics::DoGetGlyphWidths( aFont, bVertical, rWidths, rUnicodeEnc );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/gdi/gdiimpl.cxx b/vcl/unx/generic/gdi/gdiimpl.cxx
new file mode 100644
index 000000000..ee45815a7
--- /dev/null
+++ b/vcl/unx/generic/gdi/gdiimpl.cxx
@@ -0,0 +1,1993 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <numeric>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/extensions/Xrender.h>
+#include <X11/Xproto.h>
+
+#include "gdiimpl.hxx"
+
+#include <vcl/gradient.hxx>
+#include <sal/log.hxx>
+
+#include <unx/saldisp.hxx>
+#include <unx/salbmp.h>
+#include <unx/salgdi.h>
+#include <unx/salvd.h>
+#include <unx/x11/xlimits.hxx>
+#include <salframe.hxx>
+#include <unx/x11/xrender_peer.hxx>
+
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolygonclipper.hxx>
+#include <basegfx/polygon/b2dlinegeometry.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/polygon/b2dtrapezoid.hxx>
+#include <basegfx/utils/systemdependentdata.hxx>
+
+#undef SALGDI2_TESTTRANS
+
+#if (OSL_DEBUG_LEVEL > 1) && defined SALGDI2_TESTTRANS
+#define DBG_TESTTRANS( _def_drawable ) \
+{ \
+ XCopyArea( pXDisp, _def_drawable, aDrawable, GetCopyGC(), \
+ 0, 0, \
+ rPosAry.mnDestWidth, rPosAry.mnDestHeight, \
+ 0, 0 ); \
+}
+#else // (OSL_DEBUG_LEVEL > 1) && defined SALGDI2_TESTTRANS
+#define DBG_TESTTRANS( _def_drawable )
+#endif // (OSL_DEBUG_LEVEL > 1) && defined SALGDI2_TESTTRANS
+
+/* From <X11/Intrinsic.h> */
+typedef unsigned long Pixel;
+
+class SalPolyLine
+{
+ std::vector<XPoint> Points_;
+public:
+ SalPolyLine(sal_uLong nPoints, const SalPoint *p)
+ : Points_(nPoints+1)
+ {
+ for (sal_uLong i = 0; i < nPoints; ++i)
+ {
+ Points_[i].x = static_cast<short>(p[i].mnX);
+ Points_[i].y = static_cast<short>(p[i].mnY);
+ }
+ Points_[nPoints] = Points_[0]; // close polyline
+ }
+
+ const XPoint &operator[](sal_uLong n) const
+ {
+ return Points_[n];
+ }
+
+ XPoint &operator[](sal_uLong n)
+ {
+ return Points_[n];
+ }
+};
+
+namespace
+{
+ void setForeBack(XGCValues& rValues, const SalColormap& rColMap, const SalBitmap& rSalBitmap)
+ {
+ rValues.foreground = rColMap.GetWhitePixel();
+ rValues.background = rColMap.GetBlackPixel();
+
+ //fdo#33455 and fdo#80160 handle 1 bit depth pngs with palette entries
+ //to set fore/back colors
+ SalBitmap& rBitmap = const_cast<SalBitmap&>(rSalBitmap);
+ if (BitmapBuffer* pBitmapBuffer = rBitmap.AcquireBuffer(BitmapAccessMode::Read))
+ {
+ const BitmapPalette& rPalette = pBitmapBuffer->maPalette;
+ if (rPalette.GetEntryCount() == 2)
+ {
+ const BitmapColor aWhite(rPalette[rPalette.GetBestIndex(COL_WHITE)]);
+ rValues.foreground = rColMap.GetPixel(aWhite);
+
+ const BitmapColor aBlack(rPalette[rPalette.GetBestIndex(COL_BLACK)]);
+ rValues.background = rColMap.GetPixel(aBlack);
+ }
+ rBitmap.ReleaseBuffer(pBitmapBuffer, BitmapAccessMode::Read);
+ }
+ }
+}
+
+X11SalGraphicsImpl::X11SalGraphicsImpl(X11SalGraphics& rParent):
+ mrParent(rParent),
+ mnBrushColor( 0xFF, 0xFF, 0XFF ),
+ mpBrushGC(nullptr),
+ mnBrushPixel(0),
+ mbPenGC(false),
+ mbBrushGC(false),
+ mbCopyGC(false),
+ mbInvertGC(false),
+ mbInvert50GC(false),
+ mbStippleGC(false),
+ mbTrackingGC(false),
+ mbDitherBrush(false),
+ mbXORMode(false),
+ mpPenGC(nullptr),
+ mnPenColor( 0x00, 0x00, 0x00 ),
+ mnPenPixel(0),
+ mpMonoGC(nullptr),
+ mpCopyGC(nullptr),
+ mpMaskGC(nullptr),
+ mpInvertGC(nullptr),
+ mpInvert50GC(nullptr),
+ mpStippleGC(nullptr),
+ mpTrackingGC(nullptr)
+{
+}
+
+X11SalGraphicsImpl::~X11SalGraphicsImpl()
+{
+}
+
+void X11SalGraphicsImpl::Init()
+{
+ mnPenPixel = mrParent.GetPixel( mnPenColor );
+ mnBrushPixel = mrParent.GetPixel( mnBrushColor );
+}
+
+XID X11SalGraphicsImpl::GetXRenderPicture()
+{
+ XRenderPeer& rRenderPeer = XRenderPeer::GetInstance();
+
+ if( !mrParent.m_aXRenderPicture )
+ {
+ // check xrender support for matching visual
+ XRenderPictFormat* pXRenderFormat = mrParent.GetXRenderFormat();
+ if( !pXRenderFormat )
+ return 0;
+ // get the matching xrender target for drawable
+ mrParent.m_aXRenderPicture = rRenderPeer.CreatePicture( mrParent.hDrawable_, pXRenderFormat, 0, nullptr );
+ }
+
+ {
+ // reset clip region
+ // TODO: avoid clip reset if already done
+ XRenderPictureAttributes aAttr;
+ aAttr.clip_mask = None;
+ rRenderPeer.ChangePicture( mrParent.m_aXRenderPicture, CPClipMask, &aAttr );
+ }
+
+ return mrParent.m_aXRenderPicture;
+}
+
+static void freeGC(Display *pDisplay, GC& rGC)
+{
+ if( rGC )
+ {
+ XFreeGC( pDisplay, rGC );
+ rGC = None;
+ }
+}
+
+void X11SalGraphicsImpl::freeResources()
+{
+ Display *pDisplay = mrParent.GetXDisplay();
+
+ freeGC( pDisplay, mpPenGC );
+ freeGC( pDisplay, mpBrushGC );
+ freeGC( pDisplay, mpMonoGC );
+ freeGC( pDisplay, mpTrackingGC );
+ freeGC( pDisplay, mpCopyGC );
+ freeGC( pDisplay, mpMaskGC );
+ freeGC( pDisplay, mpInvertGC );
+ freeGC( pDisplay, mpInvert50GC );
+ freeGC( pDisplay, mpStippleGC );
+ mbTrackingGC = mbPenGC = mbBrushGC = mbCopyGC = mbInvertGC = mbInvert50GC = mbStippleGC = false;
+}
+
+GC X11SalGraphicsImpl::CreateGC( Drawable hDrawable, unsigned long nMask )
+{
+ XGCValues values;
+
+ values.graphics_exposures = False;
+ values.foreground = mrParent.m_pColormap->GetBlackPixel()
+ ^ mrParent.m_pColormap->GetWhitePixel();
+ values.function = GXxor;
+ values.line_width = 1;
+ values.fill_style = FillStippled;
+ values.stipple = mrParent.GetDisplay()->GetInvert50( mrParent.m_nXScreen );
+ values.subwindow_mode = ClipByChildren;
+
+ return XCreateGC( mrParent.GetXDisplay(), hDrawable, nMask | GCSubwindowMode, &values );
+}
+
+inline GC X11SalGraphicsImpl::GetCopyGC()
+{
+ if( mbXORMode ) return GetInvertGC();
+
+ if( !mpCopyGC )
+ mpCopyGC = CreateGC( mrParent.GetDrawable() );
+
+ if( !mbCopyGC )
+ {
+ mrParent.SetClipRegion( mpCopyGC );
+ mbCopyGC = true;
+ }
+ return mpCopyGC;
+}
+
+GC X11SalGraphicsImpl::GetTrackingGC()
+{
+ const char dash_list[2] = {2, 2};
+
+ if( !mpTrackingGC )
+ {
+ XGCValues values;
+
+ values.graphics_exposures = False;
+ values.foreground = mrParent.m_pColormap->GetBlackPixel()
+ ^ mrParent.m_pColormap->GetWhitePixel();
+ values.function = GXxor;
+ values.line_width = 1;
+ values.line_style = LineOnOffDash;
+
+ mpTrackingGC = XCreateGC( mrParent.GetXDisplay(), mrParent.GetDrawable(),
+ GCGraphicsExposures | GCForeground | GCFunction
+ | GCLineWidth | GCLineStyle,
+ &values );
+ XSetDashes( mrParent.GetXDisplay(), mpTrackingGC, 0, dash_list, 2 );
+ }
+
+ if( !mbTrackingGC )
+ {
+ mrParent.SetClipRegion( mpTrackingGC );
+ mbTrackingGC = true;
+ }
+
+ return mpTrackingGC;
+}
+
+GC X11SalGraphicsImpl::GetInvertGC()
+{
+ if( !mpInvertGC )
+ mpInvertGC = CreateGC( mrParent.GetDrawable(),
+ GCGraphicsExposures
+ | GCForeground
+ | GCFunction
+ | GCLineWidth );
+
+ if( !mbInvertGC )
+ {
+ mrParent.SetClipRegion( mpInvertGC );
+ mbInvertGC = true;
+ }
+ return mpInvertGC;
+}
+
+GC X11SalGraphicsImpl::GetInvert50GC()
+{
+ if( !mpInvert50GC )
+ {
+ XGCValues values;
+
+ values.graphics_exposures = False;
+ values.foreground = mrParent.m_pColormap->GetWhitePixel();
+ values.background = mrParent.m_pColormap->GetBlackPixel();
+ values.function = GXinvert;
+ values.line_width = 1;
+ values.line_style = LineSolid;
+ unsigned long const nValueMask =
+ GCGraphicsExposures
+ | GCForeground
+ | GCBackground
+ | GCFunction
+ | GCLineWidth
+ | GCLineStyle
+ | GCFillStyle
+ | GCStipple;
+
+ values.fill_style = FillStippled;
+ values.stipple = mrParent.GetDisplay()->GetInvert50( mrParent.m_nXScreen );
+
+ mpInvert50GC = XCreateGC( mrParent.GetXDisplay(), mrParent.GetDrawable(),
+ nValueMask,
+ &values );
+ }
+
+ if( !mbInvert50GC )
+ {
+ mrParent.SetClipRegion( mpInvert50GC );
+ mbInvert50GC = true;
+ }
+ return mpInvert50GC;
+}
+
+inline GC X11SalGraphicsImpl::GetStippleGC()
+{
+ if( !mpStippleGC )
+ mpStippleGC = CreateGC( mrParent.GetDrawable(),
+ GCGraphicsExposures
+ | GCFillStyle
+ | GCLineWidth );
+
+ if( !mbStippleGC )
+ {
+ XSetFunction( mrParent.GetXDisplay(), mpStippleGC, mbXORMode ? GXxor : GXcopy );
+ mrParent.SetClipRegion( mpStippleGC );
+ mbStippleGC = true;
+ }
+
+ return mpStippleGC;
+}
+
+GC X11SalGraphicsImpl::SelectBrush()
+{
+ Display *pDisplay = mrParent.GetXDisplay();
+
+ SAL_WARN_IF( mnBrushColor == SALCOLOR_NONE, "vcl", "Brush Transparent" );
+
+ if( !mpBrushGC )
+ {
+ XGCValues values;
+ values.subwindow_mode = ClipByChildren;
+ values.fill_rule = EvenOddRule; // Pict import/ Gradient
+ values.graphics_exposures = False;
+
+ mpBrushGC = XCreateGC( pDisplay, mrParent.hDrawable_,
+ GCSubwindowMode | GCFillRule | GCGraphicsExposures,
+ &values );
+ }
+
+ if( !mbBrushGC )
+ {
+ if( !mbDitherBrush )
+ {
+ XSetFillStyle ( pDisplay, mpBrushGC, FillSolid );
+ XSetForeground( pDisplay, mpBrushGC, mnBrushPixel );
+ }
+ else
+ {
+ XSetFillStyle ( pDisplay, mpBrushGC, FillTiled );
+ XSetTile ( pDisplay, mpBrushGC, mrParent.hBrush_ );
+ }
+ XSetFunction ( pDisplay, mpBrushGC, mbXORMode ? GXxor : GXcopy );
+ mrParent.SetClipRegion( mpBrushGC );
+
+ mbBrushGC = true;
+ }
+
+ return mpBrushGC;
+}
+
+GC X11SalGraphicsImpl::SelectPen()
+{
+ Display *pDisplay = mrParent.GetXDisplay();
+
+ if( !mpPenGC )
+ {
+ XGCValues values;
+ values.subwindow_mode = ClipByChildren;
+ values.fill_rule = EvenOddRule; // Pict import/ Gradient
+ values.graphics_exposures = False;
+
+ mpPenGC = XCreateGC( pDisplay, mrParent.hDrawable_,
+ GCSubwindowMode | GCFillRule | GCGraphicsExposures,
+ &values );
+ }
+
+ if( !mbPenGC )
+ {
+ if( mnPenColor != SALCOLOR_NONE )
+ XSetForeground( pDisplay, mpPenGC, mnPenPixel );
+ XSetFunction ( pDisplay, mpPenGC, mbXORMode ? GXxor : GXcopy );
+ mrParent.SetClipRegion( mpPenGC );
+ mbPenGC = true;
+ }
+
+ return mpPenGC;
+}
+
+void X11SalGraphicsImpl::DrawLines(sal_uInt32 nPoints,
+ const SalPolyLine &rPoints,
+ GC pGC,
+ bool bClose)
+{
+ // calculate how many lines XWindow can draw in one go
+ sal_uLong nMaxLines = (mrParent.GetDisplay()->GetMaxRequestSize() - sizeof(xPolyPointReq))
+ / sizeof(xPoint);
+ if( nMaxLines > nPoints ) nMaxLines = nPoints;
+
+ // print all lines that XWindows can draw
+ sal_uLong n;
+ for( n = 0; nPoints - n > nMaxLines; n += nMaxLines - 1 )
+ XDrawLines( mrParent.GetXDisplay(),
+ mrParent.GetDrawable(),
+ pGC,
+ const_cast<XPoint*>(&rPoints[n]),
+ nMaxLines,
+ CoordModeOrigin );
+
+ if( n < nPoints )
+ XDrawLines( mrParent.GetXDisplay(),
+ mrParent.GetDrawable(),
+ pGC,
+ const_cast<XPoint*>(&rPoints[n]),
+ nPoints - n,
+ CoordModeOrigin );
+ if( bClose )
+ {
+ if( rPoints[nPoints-1].x != rPoints[0].x || rPoints[nPoints-1].y != rPoints[0].y )
+ drawLine( rPoints[nPoints-1].x, rPoints[nPoints-1].y, rPoints[0].x, rPoints[0].y );
+ }
+}
+
+void X11SalGraphicsImpl::copyBits( const SalTwoRect& rPosAry,
+ SalGraphics *pSSrcGraphics )
+{
+ X11SalGraphics* pSrcGraphics = pSSrcGraphics
+ ? static_cast<X11SalGraphics*>(pSSrcGraphics)
+ : &mrParent;
+
+ if( rPosAry.mnSrcWidth <= 0
+ || rPosAry.mnSrcHeight <= 0
+ || rPosAry.mnDestWidth <= 0
+ || rPosAry.mnDestHeight <= 0 )
+ {
+ return;
+ }
+
+ int n;
+ if( pSrcGraphics == &mrParent )
+ {
+ n = 2;
+ }
+ else if( pSrcGraphics->bWindow_ )
+ {
+ // window or compatible virtual device
+ if( pSrcGraphics->GetDisplay() == mrParent.GetDisplay() &&
+ pSrcGraphics->m_nXScreen == mrParent.m_nXScreen &&
+ pSrcGraphics->GetVisual().GetDepth() == mrParent.GetVisual().GetDepth()
+ )
+ n = 2; // same Display
+ else
+ n = 1; // printer or other display
+ }
+ else if( pSrcGraphics->bVirDev_ )
+ {
+ n = 1; // window or compatible virtual device
+ }
+ else
+ n = 0;
+
+ if( n == 2
+ && rPosAry.mnSrcWidth == rPosAry.mnDestWidth
+ && rPosAry.mnSrcHeight == rPosAry.mnDestHeight
+ )
+ {
+ // #i60699# Need to generate graphics exposures (to repaint
+ // obscured areas beneath overlapping windows), src and dest
+ // are the same window.
+ const bool bNeedGraphicsExposures( pSrcGraphics == &mrParent &&
+ !mrParent.bVirDev_ &&
+ pSrcGraphics->bWindow_ );
+
+ GC pCopyGC = GetCopyGC();
+
+ if( bNeedGraphicsExposures )
+ XSetGraphicsExposures( mrParent.GetXDisplay(),
+ pCopyGC,
+ True );
+
+ XCopyArea( mrParent.GetXDisplay(),
+ pSrcGraphics->GetDrawable(), // source
+ mrParent.GetDrawable(), // destination
+ pCopyGC, // destination clipping
+ rPosAry.mnSrcX, rPosAry.mnSrcY,
+ rPosAry.mnSrcWidth, rPosAry.mnSrcHeight,
+ rPosAry.mnDestX, rPosAry.mnDestY );
+
+ if( bNeedGraphicsExposures )
+ {
+ mrParent.YieldGraphicsExpose();
+
+ if( pCopyGC )
+ XSetGraphicsExposures( mrParent.GetXDisplay(),
+ pCopyGC,
+ False );
+ }
+ }
+ else if( n )
+ {
+ // #i60699# No chance to handle graphics exposures - we copy
+ // to a temp bitmap first, into which no repaints are
+ // technically possible.
+ std::shared_ptr<SalBitmap> xDDB(pSrcGraphics->getBitmap( rPosAry.mnSrcX,
+ rPosAry.mnSrcY,
+ rPosAry.mnSrcWidth,
+ rPosAry.mnSrcHeight ));
+
+ if( !xDDB )
+ {
+ SAL_WARN( "vcl", "SalGraphics::CopyBits !pSrcGraphics->GetBitmap()" );
+ return;
+ }
+
+ SalTwoRect aPosAry( rPosAry );
+
+ aPosAry.mnSrcX = 0;
+ aPosAry.mnSrcY = 0;
+ drawBitmap( aPosAry, *xDDB );
+ }
+ else {
+ SAL_WARN( "vcl", "X11SalGraphicsImpl::CopyBits from Printer not yet implemented" );
+ }
+}
+
+void X11SalGraphicsImpl::copyArea ( long nDestX, long nDestY,
+ long nSrcX, long nSrcY,
+ long nSrcWidth, long nSrcHeight,
+ bool /*bWindowInvalidate*/)
+{
+ SalTwoRect aPosAry(nSrcX, nSrcY, nSrcWidth, nSrcHeight, nDestX, nDestY, nSrcWidth, nSrcHeight);
+ copyBits(aPosAry, nullptr);
+}
+
+void X11SalGraphicsImpl::drawBitmap( const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap )
+{
+ const SalDisplay* pSalDisp = mrParent.GetDisplay();
+ Display* pXDisp = pSalDisp->GetDisplay();
+ const Drawable aDrawable( mrParent.GetDrawable() );
+ const SalColormap& rColMap = pSalDisp->GetColormap( mrParent.m_nXScreen );
+ const long nDepth = mrParent.GetDisplay()->GetVisual( mrParent.m_nXScreen ).GetDepth();
+ GC aGC( GetCopyGC() );
+ XGCValues aOldVal, aNewVal;
+ int nValues = GCForeground | GCBackground;
+
+ if( rSalBitmap.GetBitCount() == 1 )
+ {
+ // set foreground/background values for 1Bit bitmaps
+ XGetGCValues( pXDisp, aGC, nValues, &aOldVal );
+ setForeBack(aNewVal, rColMap, rSalBitmap);
+ XChangeGC( pXDisp, aGC, nValues, &aNewVal );
+ }
+
+ static_cast<const X11SalBitmap&>(rSalBitmap).ImplDraw( aDrawable, mrParent.m_nXScreen, nDepth, rPosAry, aGC );
+
+ if( rSalBitmap.GetBitCount() == 1 )
+ XChangeGC( pXDisp, aGC, nValues, &aOldVal );
+ XFlush( pXDisp );
+}
+
+void X11SalGraphicsImpl::drawBitmap( const SalTwoRect& rPosAry,
+ const SalBitmap& rSrcBitmap,
+ const SalBitmap& rMaskBitmap )
+{
+ // decide if alpha masking or transparency masking is needed
+ BitmapBuffer* pAlphaBuffer = const_cast<SalBitmap&>(rMaskBitmap).AcquireBuffer( BitmapAccessMode::Read );
+ if( pAlphaBuffer != nullptr )
+ {
+ ScanlineFormat nMaskFormat = pAlphaBuffer->mnFormat;
+ const_cast<SalBitmap&>(rMaskBitmap).ReleaseBuffer( pAlphaBuffer, BitmapAccessMode::Read );
+ if( nMaskFormat == ScanlineFormat::N8BitPal )
+ drawAlphaBitmap( rPosAry, rSrcBitmap, rMaskBitmap );
+ }
+
+ drawMaskedBitmap( rPosAry, rSrcBitmap, rMaskBitmap );
+}
+
+void X11SalGraphicsImpl::drawMaskedBitmap( const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap,
+ const SalBitmap& rTransBitmap )
+{
+ const SalDisplay* pSalDisp = mrParent.GetDisplay();
+ Display* pXDisp = pSalDisp->GetDisplay();
+ Drawable aDrawable( mrParent.GetDrawable() );
+
+ // figure work mode depth. If this is a VDev Drawable, use its
+ // bitdepth to create pixmaps for, otherwise, XCopyArea will
+ // refuse to work.
+ const sal_uInt16 nDepth( mrParent.m_pVDev ?
+ static_cast< X11SalVirtualDevice* >(mrParent.m_pVDev)->GetDepth() :
+ pSalDisp->GetVisual( mrParent.m_nXScreen ).GetDepth() );
+ Pixmap aFG( limitXCreatePixmap( pXDisp, aDrawable, rPosAry.mnDestWidth,
+ rPosAry.mnDestHeight, nDepth ) );
+ Pixmap aBG( limitXCreatePixmap( pXDisp, aDrawable, rPosAry.mnDestWidth,
+ rPosAry.mnDestHeight, nDepth ) );
+
+ if( aFG && aBG )
+ {
+ GC aTmpGC;
+ XGCValues aValues;
+ setForeBack(aValues, pSalDisp->GetColormap(mrParent.m_nXScreen), rSalBitmap);
+ const int nValues = GCFunction | GCForeground | GCBackground;
+ SalTwoRect aTmpRect( rPosAry ); aTmpRect.mnDestX = aTmpRect.mnDestY = 0;
+
+ // draw paint bitmap in pixmap #1
+ aValues.function = GXcopy;
+ aTmpGC = XCreateGC( pXDisp, aFG, nValues, &aValues );
+ static_cast<const X11SalBitmap&>(rSalBitmap).ImplDraw( aFG, mrParent.m_nXScreen, nDepth, aTmpRect, aTmpGC );
+ DBG_TESTTRANS( aFG );
+
+ // draw background in pixmap #2
+ XCopyArea( pXDisp, aDrawable, aBG, aTmpGC,
+ rPosAry.mnDestX, rPosAry.mnDestY,
+ rPosAry.mnDestWidth, rPosAry.mnDestHeight,
+ 0, 0 );
+
+ DBG_TESTTRANS( aBG );
+
+ // mask out paint bitmap in pixmap #1 (transparent areas 0)
+ aValues.function = GXand;
+ aValues.foreground = 0x00000000;
+ aValues.background = 0xffffffff;
+ XChangeGC( pXDisp, aTmpGC, nValues, &aValues );
+ static_cast<const X11SalBitmap&>(rTransBitmap).ImplDraw( aFG, mrParent.m_nXScreen, 1, aTmpRect, aTmpGC );
+
+ DBG_TESTTRANS( aFG );
+
+ // #105055# For XOR mode, keep background behind bitmap intact
+ if( !mbXORMode )
+ {
+ // mask out background in pixmap #2 (nontransparent areas 0)
+ aValues.function = GXand;
+ aValues.foreground = 0xffffffff;
+ aValues.background = 0x00000000;
+ XChangeGC( pXDisp, aTmpGC, nValues, &aValues );
+ static_cast<const X11SalBitmap&>(rTransBitmap).ImplDraw( aBG, mrParent.m_nXScreen, 1, aTmpRect, aTmpGC );
+
+ DBG_TESTTRANS( aBG );
+ }
+
+ // merge pixmap #1 and pixmap #2 in pixmap #2
+ aValues.function = GXxor;
+ aValues.foreground = 0xffffffff;
+ aValues.background = 0x00000000;
+ XChangeGC( pXDisp, aTmpGC, nValues, &aValues );
+ XCopyArea( pXDisp, aFG, aBG, aTmpGC,
+ 0, 0,
+ rPosAry.mnDestWidth, rPosAry.mnDestHeight,
+ 0, 0 );
+ DBG_TESTTRANS( aBG );
+
+ // #105055# Disable XOR temporarily
+ bool bOldXORMode( mbXORMode );
+ mbXORMode = false;
+
+ // copy pixmap #2 (result) to background
+ XCopyArea( pXDisp, aBG, aDrawable, GetCopyGC(),
+ 0, 0,
+ rPosAry.mnDestWidth, rPosAry.mnDestHeight,
+ rPosAry.mnDestX, rPosAry.mnDestY );
+
+ DBG_TESTTRANS( aBG );
+
+ mbXORMode = bOldXORMode;
+
+ XFreeGC( pXDisp, aTmpGC );
+ XFlush( pXDisp );
+ }
+ else
+ drawBitmap( rPosAry, rSalBitmap );
+
+ if( aFG )
+ XFreePixmap( pXDisp, aFG );
+
+ if( aBG )
+ XFreePixmap( pXDisp, aBG );
+}
+
+bool X11SalGraphicsImpl::blendBitmap( const SalTwoRect&,
+ const SalBitmap& )
+{
+ return false;
+}
+
+bool X11SalGraphicsImpl::blendAlphaBitmap( const SalTwoRect&,
+ const SalBitmap&, const SalBitmap&, const SalBitmap& )
+{
+ return false;
+}
+
+bool X11SalGraphicsImpl::drawAlphaBitmap( const SalTwoRect& rTR,
+ const SalBitmap& rSrcBitmap, const SalBitmap& rAlphaBmp )
+{
+ // non 8-bit alpha not implemented yet
+ if( rAlphaBmp.GetBitCount() != 8 )
+ return false;
+
+ // horizontal mirroring not implemented yet
+ if( rTR.mnDestWidth < 0 )
+ return false;
+
+ // stretched conversion is not implemented yet
+ if( rTR.mnDestWidth != rTR.mnSrcWidth )
+ return false;
+ if( rTR.mnDestHeight!= rTR.mnSrcHeight )
+ return false;
+
+ // create destination picture
+ Picture aDstPic = GetXRenderPicture();
+ if( !aDstPic )
+ return false;
+
+ const SalDisplay* pSalDisp = mrParent.GetDisplay();
+ const SalVisual& rSalVis = pSalDisp->GetVisual( mrParent.m_nXScreen );
+ Display* pXDisplay = pSalDisp->GetDisplay();
+
+ // create source Picture
+ int nDepth = mrParent.m_pVDev ? static_cast< X11SalVirtualDevice* >(mrParent.m_pVDev)->GetDepth() : rSalVis.GetDepth();
+ const X11SalBitmap& rSrcX11Bmp = static_cast<const X11SalBitmap&>( rSrcBitmap );
+ ImplSalDDB* pSrcDDB = rSrcX11Bmp.ImplGetDDB( mrParent.hDrawable_, mrParent.m_nXScreen, nDepth, rTR );
+ if( !pSrcDDB )
+ return false;
+
+ //#i75249# workaround for ImplGetDDB() giving us back a different depth than
+ // we requested. E.g. mask pixmaps are always compatible with the drawable
+ // TODO: find an appropriate picture format for these cases
+ // then remove the workaround below and the one for #i75531#
+ if( nDepth != pSrcDDB->ImplGetDepth() )
+ return false;
+
+ Pixmap aSrcPM = pSrcDDB->ImplGetPixmap();
+ if( !aSrcPM )
+ return false;
+
+ // create source picture
+ // TODO: use scoped picture
+ Visual* pSrcXVisual = rSalVis.GetVisual();
+ XRenderPeer& rPeer = XRenderPeer::GetInstance();
+ XRenderPictFormat* pSrcVisFmt = rPeer.FindVisualFormat( pSrcXVisual );
+ if( !pSrcVisFmt )
+ return false;
+ Picture aSrcPic = rPeer.CreatePicture( aSrcPM, pSrcVisFmt, 0, nullptr );
+ if( !aSrcPic )
+ return false;
+
+ // create alpha Picture
+
+ // TODO: use SalX11Bitmap functionality and caching for the Alpha Pixmap
+ // problem is that they don't provide an 8bit Pixmap on a non-8bit display
+ BitmapBuffer* pAlphaBuffer = const_cast<SalBitmap&>(rAlphaBmp).AcquireBuffer( BitmapAccessMode::Read );
+
+ // an XImage needs its data top_down
+ // TODO: avoid wrongly oriented images in upper layers!
+ const int nImageSize = pAlphaBuffer->mnHeight * pAlphaBuffer->mnScanlineSize;
+ const char* pSrcBits = reinterpret_cast<char*>(pAlphaBuffer->mpBits);
+ char* pAlphaBits = new char[ nImageSize ];
+ if( pAlphaBuffer->mnFormat & ScanlineFormat::TopDown )
+ memcpy( pAlphaBits, pSrcBits, nImageSize );
+ else
+ {
+ char* pDstBits = pAlphaBits + nImageSize;
+ const int nLineSize = pAlphaBuffer->mnScanlineSize;
+ for(; (pDstBits -= nLineSize) >= pAlphaBits; pSrcBits += nLineSize )
+ memcpy( pDstBits, pSrcBits, nLineSize );
+ }
+
+ // the alpha values need to be inverted for XRender
+ // TODO: make upper layers use standard alpha
+ long* pLDst = reinterpret_cast<long*>(pAlphaBits);
+ for( int i = nImageSize/sizeof(long); --i >= 0; ++pLDst )
+ *pLDst = ~*pLDst;
+
+ char* pCDst = reinterpret_cast<char*>(pLDst);
+ for( int i = nImageSize & (sizeof(long)-1); --i >= 0; ++pCDst )
+ *pCDst = ~*pCDst;
+
+ const XRenderPictFormat* pAlphaFormat = rPeer.GetStandardFormatA8();
+ XImage* pAlphaImg = XCreateImage( pXDisplay, pSrcXVisual, 8, ZPixmap, 0,
+ pAlphaBits, pAlphaBuffer->mnWidth, pAlphaBuffer->mnHeight,
+ pAlphaFormat->depth, pAlphaBuffer->mnScanlineSize );
+
+ Pixmap aAlphaPM = limitXCreatePixmap( pXDisplay, mrParent.hDrawable_,
+ rTR.mnDestWidth, rTR.mnDestHeight, 8 );
+
+ XGCValues aAlphaGCV;
+ aAlphaGCV.function = GXcopy;
+ GC aAlphaGC = XCreateGC( pXDisplay, aAlphaPM, GCFunction, &aAlphaGCV );
+ XPutImage( pXDisplay, aAlphaPM, aAlphaGC, pAlphaImg,
+ rTR.mnSrcX, rTR.mnSrcY, 0, 0, rTR.mnDestWidth, rTR.mnDestHeight );
+ XFreeGC( pXDisplay, aAlphaGC );
+ XFree( pAlphaImg );
+ if( pAlphaBits != reinterpret_cast<char*>(pAlphaBuffer->mpBits) )
+ delete[] pAlphaBits;
+
+ const_cast<SalBitmap&>(rAlphaBmp).ReleaseBuffer( pAlphaBuffer, BitmapAccessMode::Read );
+
+ XRenderPictureAttributes aAttr;
+ aAttr.repeat = int(true);
+ Picture aAlphaPic = rPeer.CreatePicture( aAlphaPM, pAlphaFormat, CPRepeat, &aAttr );
+ if( !aAlphaPic )
+ return false;
+
+ // set clipping
+ if( mrParent.mpClipRegion && !XEmptyRegion( mrParent.mpClipRegion ) )
+ rPeer.SetPictureClipRegion( aDstPic, mrParent.mpClipRegion );
+
+ // paint source * mask over destination picture
+ rPeer.CompositePicture( PictOpOver, aSrcPic, aAlphaPic, aDstPic,
+ rTR.mnSrcX, rTR.mnSrcY,
+ rTR.mnDestX, rTR.mnDestY, rTR.mnDestWidth, rTR.mnDestHeight );
+
+ rPeer.FreePicture( aAlphaPic );
+ XFreePixmap(pXDisplay, aAlphaPM);
+ rPeer.FreePicture( aSrcPic );
+ return true;
+}
+
+bool X11SalGraphicsImpl::drawTransformedBitmap(
+ const basegfx::B2DPoint&,
+ const basegfx::B2DPoint&,
+ const basegfx::B2DPoint&,
+ const SalBitmap&,
+ const SalBitmap*)
+{
+ // here direct support for transformed bitmaps can be implemented
+ return false;
+}
+
+bool X11SalGraphicsImpl::drawAlphaRect( long nX, long nY, long nWidth,
+ long nHeight, sal_uInt8 nTransparency )
+{
+ if( ! mrParent.m_pFrame && ! mrParent.m_pVDev )
+ return false;
+
+ if( mbPenGC || !mbBrushGC || mbXORMode )
+ return false; // can only perform solid fills without XOR.
+
+ if( mrParent.m_pVDev && static_cast< X11SalVirtualDevice* >(mrParent.m_pVDev)->GetDepth() < 8 )
+ return false;
+
+ Picture aDstPic = GetXRenderPicture();
+ if( !aDstPic )
+ return false;
+
+ const double fTransparency = (100 - nTransparency) * (1.0/100);
+ const XRenderColor aRenderColor = GetXRenderColor( mnBrushColor , fTransparency);
+
+ XRenderPeer& rPeer = XRenderPeer::GetInstance();
+ rPeer.FillRectangle( PictOpOver,
+ aDstPic,
+ &aRenderColor,
+ nX, nY,
+ nWidth, nHeight );
+
+ return true;
+}
+
+void X11SalGraphicsImpl::drawMask( const SalTwoRect& rPosAry,
+ const SalBitmap &rSalBitmap,
+ Color nMaskColor )
+{
+ const SalDisplay* pSalDisp = mrParent.GetDisplay();
+ Display* pXDisp = pSalDisp->GetDisplay();
+ Drawable aDrawable( mrParent.GetDrawable() );
+ Pixmap aStipple( limitXCreatePixmap( pXDisp, aDrawable,
+ rPosAry.mnDestWidth,
+ rPosAry.mnDestHeight, 1 ) );
+
+ if( aStipple )
+ {
+ SalTwoRect aTwoRect( rPosAry ); aTwoRect.mnDestX = aTwoRect.mnDestY = 0;
+ GC aTmpGC;
+ XGCValues aValues;
+
+ // create a stipple bitmap first (set bits are changed to unset bits and vice versa)
+ aValues.function = GXcopyInverted;
+ aValues.foreground = 1;
+ aValues.background = 0;
+ aTmpGC = XCreateGC( pXDisp, aStipple, GCFunction | GCForeground | GCBackground, &aValues );
+ static_cast<const X11SalBitmap&>(rSalBitmap).ImplDraw( aStipple, mrParent.m_nXScreen, 1, aTwoRect, aTmpGC );
+
+ XFreeGC( pXDisp, aTmpGC );
+
+ // Set stipple and draw rectangle
+ GC aStippleGC( GetStippleGC() );
+ int nX = rPosAry.mnDestX, nY = rPosAry.mnDestY;
+
+ XSetStipple( pXDisp, aStippleGC, aStipple );
+ XSetTSOrigin( pXDisp, aStippleGC, nX, nY );
+ XSetForeground( pXDisp, aStippleGC, mrParent.GetPixel( nMaskColor ) );
+ XFillRectangle( pXDisp, aDrawable, aStippleGC,
+ nX, nY,
+ rPosAry.mnDestWidth, rPosAry.mnDestHeight );
+ XFreePixmap( pXDisp, aStipple );
+ XFlush( pXDisp );
+ }
+ else
+ drawBitmap( rPosAry, rSalBitmap );
+}
+
+void X11SalGraphicsImpl::ResetClipRegion()
+{
+ if( mrParent.mpClipRegion )
+ {
+ mbPenGC = false;
+ mbBrushGC = false;
+ mbCopyGC = false;
+ mbInvertGC = false;
+ mbInvert50GC = false;
+ mbStippleGC = false;
+ mbTrackingGC = false;
+
+ XDestroyRegion( mrParent.mpClipRegion );
+ mrParent.mpClipRegion = nullptr;
+ }
+}
+
+bool X11SalGraphicsImpl::setClipRegion( const vcl::Region& i_rClip )
+{
+ if( mrParent.mpClipRegion )
+ XDestroyRegion( mrParent.mpClipRegion );
+ mrParent.mpClipRegion = XCreateRegion();
+
+ RectangleVector aRectangles;
+ i_rClip.GetRegionRectangles(aRectangles);
+
+ for (auto const& rectangle : aRectangles)
+ {
+ const long nW(rectangle.GetWidth());
+
+ if(nW)
+ {
+ const long nH(rectangle.GetHeight());
+
+ if(nH)
+ {
+ XRectangle aRect;
+
+ aRect.x = static_cast<short>(rectangle.Left());
+ aRect.y = static_cast<short>(rectangle.Top());
+ aRect.width = static_cast<unsigned short>(nW);
+ aRect.height = static_cast<unsigned short>(nH);
+ XUnionRectWithRegion(&aRect, mrParent.mpClipRegion, mrParent.mpClipRegion);
+ }
+ }
+ }
+
+ //ImplRegionInfo aInfo;
+ //long nX, nY, nW, nH;
+ //bool bRegionRect = i_rClip.ImplGetFirstRect(aInfo, nX, nY, nW, nH );
+ //while( bRegionRect )
+ //{
+ // if ( nW && nH )
+ // {
+ // XRectangle aRect;
+ // aRect.x = (short)nX;
+ // aRect.y = (short)nY;
+ // aRect.width = (unsigned short)nW;
+ // aRect.height = (unsigned short)nH;
+
+ // XUnionRectWithRegion( &aRect, mrParent.mpClipRegion, mrParent.mpClipRegion );
+ // }
+ // bRegionRect = i_rClip.ImplGetNextRect( aInfo, nX, nY, nW, nH );
+ //}
+
+ // done, invalidate GCs
+ mbPenGC = false;
+ mbBrushGC = false;
+ mbCopyGC = false;
+ mbInvertGC = false;
+ mbInvert50GC = false;
+ mbStippleGC = false;
+ mbTrackingGC = false;
+
+ if( XEmptyRegion( mrParent.mpClipRegion ) )
+ {
+ XDestroyRegion( mrParent.mpClipRegion );
+ mrParent.mpClipRegion= nullptr;
+ }
+ return true;
+}
+
+void X11SalGraphicsImpl::SetLineColor()
+{
+ if( mnPenColor != SALCOLOR_NONE )
+ {
+ mnPenColor = SALCOLOR_NONE;
+ mbPenGC = false;
+ }
+}
+
+void X11SalGraphicsImpl::SetLineColor( Color nColor )
+{
+ if( mnPenColor != nColor )
+ {
+ mnPenColor = nColor;
+ mnPenPixel = mrParent.GetPixel( nColor );
+ mbPenGC = false;
+ }
+}
+
+void X11SalGraphicsImpl::SetFillColor()
+{
+ if( mnBrushColor != SALCOLOR_NONE )
+ {
+ mbDitherBrush = false;
+ mnBrushColor = SALCOLOR_NONE;
+ mbBrushGC = false;
+ }
+}
+
+void X11SalGraphicsImpl::SetFillColor( Color nColor )
+{
+ if( mnBrushColor != nColor )
+ {
+ mbDitherBrush = false;
+ mnBrushColor = nColor;
+ mnBrushPixel = mrParent.GetPixel( nColor );
+ if( TrueColor != mrParent.GetColormap().GetVisual().GetClass()
+ && mrParent.GetColormap().GetColor( mnBrushPixel ) != mnBrushColor
+ && nColor != Color( 0x00, 0x00, 0x00 ) // black
+ && nColor != Color( 0x00, 0x00, 0x80 ) // blue
+ && nColor != Color( 0x00, 0x80, 0x00 ) // green
+ && nColor != Color( 0x00, 0x80, 0x80 ) // cyan
+ && nColor != Color( 0x80, 0x00, 0x00 ) // red
+ && nColor != Color( 0x80, 0x00, 0x80 ) // magenta
+ && nColor != Color( 0x80, 0x80, 0x00 ) // brown
+ && nColor != Color( 0x80, 0x80, 0x80 ) // gray
+ && nColor != Color( 0xC0, 0xC0, 0xC0 ) // light gray
+ && nColor != Color( 0x00, 0x00, 0xFF ) // light blue
+ && nColor != Color( 0x00, 0xFF, 0x00 ) // light green
+ && nColor != Color( 0x00, 0xFF, 0xFF ) // light cyan
+ && nColor != Color( 0xFF, 0x00, 0x00 ) // light red
+ && nColor != Color( 0xFF, 0x00, 0xFF ) // light magenta
+ && nColor != Color( 0xFF, 0xFF, 0x00 ) // light brown
+ && nColor != Color( 0xFF, 0xFF, 0xFF ) )
+ mbDitherBrush = mrParent.GetDitherPixmap(nColor);
+ mbBrushGC = false;
+ }
+}
+
+void X11SalGraphicsImpl::SetROPLineColor( SalROPColor nROPColor )
+{
+ switch( nROPColor )
+ {
+ case SalROPColor::N0 : // 0
+ mnPenPixel = Pixel(0);
+ break;
+ case SalROPColor::N1 : // 1
+ mnPenPixel = static_cast<Pixel>(1 << mrParent.GetVisual().GetDepth()) - 1;
+ break;
+ case SalROPColor::Invert : // 2
+ mnPenPixel = static_cast<Pixel>(1 << mrParent.GetVisual().GetDepth()) - 1;
+ break;
+ }
+ mnPenColor = mrParent.GetColormap().GetColor( mnPenPixel );
+ mbPenGC = false;
+}
+
+void X11SalGraphicsImpl::SetROPFillColor( SalROPColor nROPColor )
+{
+ switch( nROPColor )
+ {
+ case SalROPColor::N0 : // 0
+ mnBrushPixel = Pixel(0);
+ break;
+ case SalROPColor::N1 : // 1
+ mnBrushPixel = static_cast<Pixel>(1 << mrParent.GetVisual().GetDepth()) - 1;
+ break;
+ case SalROPColor::Invert : // 2
+ mnBrushPixel = static_cast<Pixel>(1 << mrParent.GetVisual().GetDepth()) - 1;
+ break;
+ }
+ mbDitherBrush = false;
+ mnBrushColor = mrParent.GetColormap().GetColor( mnBrushPixel );
+ mbBrushGC = false;
+}
+
+void X11SalGraphicsImpl::SetXORMode( bool bSet, bool )
+{
+ if (mbXORMode != bSet)
+ {
+ mbXORMode = bSet;
+ mbPenGC = false;
+ mbBrushGC = false;
+ mbCopyGC = false;
+ mbInvertGC = false;
+ mbInvert50GC = false;
+ mbStippleGC = false;
+ mbTrackingGC = false;
+ }
+}
+
+void X11SalGraphicsImpl::drawPixel( long nX, long nY )
+{
+ if( mnPenColor != SALCOLOR_NONE )
+ XDrawPoint( mrParent.GetXDisplay(), mrParent.GetDrawable(), SelectPen(), nX, nY );
+}
+
+void X11SalGraphicsImpl::drawPixel( long nX, long nY, Color nColor )
+{
+ if( nColor != SALCOLOR_NONE )
+ {
+ Display *pDisplay = mrParent.GetXDisplay();
+
+ if( (mnPenColor == SALCOLOR_NONE) && !mbPenGC )
+ {
+ SetLineColor( nColor );
+ XDrawPoint( pDisplay, mrParent.GetDrawable(), SelectPen(), nX, nY );
+ mnPenColor = SALCOLOR_NONE;
+ mbPenGC = False;
+ }
+ else
+ {
+ GC pGC = SelectPen();
+
+ if( nColor != mnPenColor )
+ XSetForeground( pDisplay, pGC, mrParent.GetPixel( nColor ) );
+
+ XDrawPoint( pDisplay, mrParent.GetDrawable(), pGC, nX, nY );
+
+ if( nColor != mnPenColor )
+ XSetForeground( pDisplay, pGC, mnPenPixel );
+ }
+ }
+}
+
+void X11SalGraphicsImpl::drawLine( long nX1, long nY1, long nX2, long nY2 )
+{
+ if( mnPenColor != SALCOLOR_NONE )
+ {
+ XDrawLine( mrParent.GetXDisplay(), mrParent.GetDrawable(),SelectPen(),
+ nX1, nY1, nX2, nY2 );
+ }
+}
+
+void X11SalGraphicsImpl::drawRect( long nX, long nY, long nDX, long nDY )
+{
+ if( mnBrushColor != SALCOLOR_NONE )
+ {
+ XFillRectangle( mrParent.GetXDisplay(),
+ mrParent.GetDrawable(),
+ SelectBrush(),
+ nX, nY, nDX, nDY );
+ }
+ // description DrawRect is wrong; thus -1
+ if( mnPenColor != SALCOLOR_NONE )
+ XDrawRectangle( mrParent.GetXDisplay(),
+ mrParent.GetDrawable(),
+ SelectPen(),
+ nX, nY, nDX-1, nDY-1 );
+}
+
+void X11SalGraphicsImpl::drawPolyLine( sal_uInt32 nPoints, const SalPoint *pPtAry )
+{
+ drawPolyLine( nPoints, pPtAry, false );
+}
+
+void X11SalGraphicsImpl::drawPolyLine( sal_uInt32 nPoints, const SalPoint *pPtAry, bool bClose )
+{
+ if( mnPenColor != SALCOLOR_NONE )
+ {
+ SalPolyLine Points( nPoints, pPtAry );
+
+ DrawLines( nPoints, Points, SelectPen(), bClose );
+ }
+}
+
+void X11SalGraphicsImpl::drawPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry )
+{
+ if( nPoints == 0 )
+ return;
+
+ if( nPoints < 3 )
+ {
+ if( !mbXORMode )
+ {
+ if( 1 == nPoints )
+ drawPixel( pPtAry[0].mnX, pPtAry[0].mnY );
+ else
+ drawLine( pPtAry[0].mnX, pPtAry[0].mnY,
+ pPtAry[1].mnX, pPtAry[1].mnY );
+ }
+ return;
+ }
+
+ SalPolyLine Points( nPoints, pPtAry );
+
+ nPoints++;
+
+ /* WORKAROUND: some Xservers (Xorg, VIA chipset in this case)
+ * do not draw the visible part of a polygon
+ * if it overlaps to the left of screen 0,y.
+ * This happens to be the case in the gradient drawn in the
+ * menubar background. workaround for the special case of
+ * of a rectangle overlapping to the left.
+ */
+ if (nPoints == 5 &&
+ Points[ 0 ].x == Points[ 1 ].x &&
+ Points[ 1 ].y == Points[ 2 ].y &&
+ Points[ 2 ].x == Points[ 3 ].x &&
+ Points[ 0 ].x == Points[ 4 ].x && Points[ 0 ].y == Points[ 4 ].y
+ )
+ {
+ bool bLeft = false;
+ bool bRight = false;
+ for(unsigned int i = 0; i < nPoints; i++ )
+ {
+ if( Points[i].x < 0 )
+ bLeft = true;
+ else
+ bRight= true;
+ }
+ if( bLeft && ! bRight )
+ return;
+ if( bLeft && bRight )
+ {
+ for( unsigned int i = 0; i < nPoints; i++ )
+ if( Points[i].x < 0 )
+ Points[i].x = 0;
+ }
+ }
+
+ if( mnBrushColor != SALCOLOR_NONE )
+ XFillPolygon( mrParent.GetXDisplay(),
+ mrParent.GetDrawable(),
+ SelectBrush(),
+ &Points[0], nPoints,
+ Complex, CoordModeOrigin );
+
+ if( mnPenColor != SALCOLOR_NONE )
+ DrawLines( nPoints, Points, SelectPen(), true );
+}
+
+void X11SalGraphicsImpl::drawPolyPolygon( sal_uInt32 nPoly,
+ const sal_uInt32 *pPoints,
+ PCONSTSALPOINT *pPtAry )
+{
+ if( mnBrushColor != SALCOLOR_NONE )
+ {
+ sal_uInt32 i, n;
+ Region pXRegA = nullptr;
+
+ for( i = 0; i < nPoly; i++ ) {
+ n = pPoints[i];
+ SalPolyLine Points( n, pPtAry[i] );
+ if( n > 2 )
+ {
+ Region pXRegB = XPolygonRegion( &Points[0], n+1, WindingRule );
+ if( !pXRegA )
+ pXRegA = pXRegB;
+ else
+ {
+ XXorRegion( pXRegA, pXRegB, pXRegA );
+ XDestroyRegion( pXRegB );
+ }
+ }
+ }
+
+ if( pXRegA )
+ {
+ XRectangle aXRect;
+ XClipBox( pXRegA, &aXRect );
+
+ GC pGC = SelectBrush();
+ mrParent.SetClipRegion( pGC, pXRegA ); // ??? twice
+ XDestroyRegion( pXRegA );
+ mbBrushGC = false;
+
+ XFillRectangle( mrParent.GetXDisplay(),
+ mrParent.GetDrawable(),
+ pGC,
+ aXRect.x, aXRect.y, aXRect.width, aXRect.height );
+ }
+ }
+
+ if( mnPenColor != SALCOLOR_NONE )
+ for( sal_uInt32 i = 0; i < nPoly; i++ )
+ drawPolyLine( pPoints[i], pPtAry[i], true );
+}
+
+bool X11SalGraphicsImpl::drawPolyLineBezier( sal_uInt32, const SalPoint*, const PolyFlags* )
+{
+ return false;
+}
+
+bool X11SalGraphicsImpl::drawPolygonBezier( sal_uInt32, const SalPoint*, const PolyFlags* )
+{
+ return false;
+}
+
+bool X11SalGraphicsImpl::drawPolyPolygonBezier( sal_uInt32, const sal_uInt32*,
+ const SalPoint* const*, const PolyFlags* const* )
+{
+ return false;
+}
+
+void X11SalGraphicsImpl::invert( long nX,
+ long nY,
+ long nDX,
+ long nDY,
+ SalInvert nFlags )
+{
+ GC pGC;
+ if( SalInvert::N50 & nFlags )
+ {
+ pGC = GetInvert50GC();
+ XFillRectangle( mrParent.GetXDisplay(), mrParent.GetDrawable(), pGC, nX, nY, nDX, nDY );
+ }
+ else
+ {
+ if ( SalInvert::TrackFrame & nFlags )
+ {
+ pGC = GetTrackingGC();
+ XDrawRectangle( mrParent.GetXDisplay(), mrParent.GetDrawable(), pGC, nX, nY, nDX, nDY );
+ }
+ else
+ {
+ pGC = GetInvertGC();
+ XFillRectangle( mrParent.GetXDisplay(), mrParent.GetDrawable(), pGC, nX, nY, nDX, nDY );
+ }
+ }
+}
+
+void X11SalGraphicsImpl::invert( sal_uInt32 nPoints,
+ const SalPoint* pPtAry,
+ SalInvert nFlags )
+{
+ SalPolyLine Points ( nPoints, pPtAry );
+
+ GC pGC;
+ if( SalInvert::N50 & nFlags )
+ pGC = GetInvert50GC();
+ else
+ if ( SalInvert::TrackFrame & nFlags )
+ pGC = GetTrackingGC();
+ else
+ pGC = GetInvertGC();
+
+ if( SalInvert::TrackFrame & nFlags )
+ DrawLines ( nPoints, Points, pGC, true );
+ else
+ XFillPolygon( mrParent.GetXDisplay(),
+ mrParent.GetDrawable(),
+ pGC,
+ &Points[0], nPoints,
+ Complex, CoordModeOrigin );
+}
+
+bool X11SalGraphicsImpl::drawEPS( long,long,long,long,void*,sal_uInt32 )
+{
+ return false;
+}
+
+// draw a poly-polygon
+bool X11SalGraphicsImpl::drawPolyPolygon(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon& rPolyPolygon,
+ double fTransparency)
+{
+ // nothing to do for empty polypolygons
+ const int nOrigPolyCount = rPolyPolygon.count();
+ if( nOrigPolyCount <= 0 )
+ return true;
+
+ // nothing to do if everything is transparent
+ if( (mnBrushColor == SALCOLOR_NONE)
+ && (mnPenColor == SALCOLOR_NONE) )
+ return true;
+
+ // cannot handle pencolor!=brushcolor yet
+ if( (mnPenColor != SALCOLOR_NONE)
+ && (mnPenColor != mnBrushColor) )
+ return false;
+
+ // TODO: remove the env-variable when no longer needed
+ static const char* pRenderEnv = getenv( "SAL_DISABLE_RENDER_POLY" );
+ if( pRenderEnv )
+ return false;
+
+ // Fallback: Transform to DeviceCoordinates
+ basegfx::B2DPolyPolygon aPolyPolygon(rPolyPolygon);
+ aPolyPolygon.transform(rObjectToDevice);
+
+ // snap to raster if requested
+ const bool bSnapToRaster = !mrParent.getAntiAliasB2DDraw();
+ if( bSnapToRaster )
+ aPolyPolygon = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges( aPolyPolygon );
+
+ // don't bother with polygons outside of visible area
+ const basegfx::B2DRange aViewRange( 0, 0, GetGraphicsWidth(), GetGraphicsHeight() );
+ aPolyPolygon = basegfx::utils::clipPolyPolygonOnRange( aPolyPolygon, aViewRange, true, false );
+ if( !aPolyPolygon.count() )
+ return true;
+
+ // tessellate the polypolygon into trapezoids
+ basegfx::B2DTrapezoidVector aB2DTrapVector;
+ basegfx::utils::trapezoidSubdivide( aB2DTrapVector, aPolyPolygon );
+ const int nTrapCount = aB2DTrapVector.size();
+ if( !nTrapCount )
+ return true;
+ const bool bDrawn = drawFilledTrapezoids( aB2DTrapVector.data(), nTrapCount, fTransparency );
+ return bDrawn;
+}
+
+long X11SalGraphicsImpl::GetGraphicsHeight() const
+{
+ if( mrParent.m_pFrame )
+ return mrParent.m_pFrame->maGeometry.nHeight;
+ else if( mrParent.m_pVDev )
+ return static_cast< X11SalVirtualDevice* >(mrParent.m_pVDev)->GetHeight();
+ else
+ return 0;
+}
+
+bool X11SalGraphicsImpl::drawFilledTrapezoids( const basegfx::B2DTrapezoid* pB2DTraps, int nTrapCount, double fTransparency )
+{
+ if( nTrapCount <= 0 )
+ return true;
+
+ Picture aDstPic = GetXRenderPicture();
+ // check xrender support for this drawable
+ if( !aDstPic )
+ return false;
+
+ // convert the B2DTrapezoids into XRender-Trapezoids
+ std::vector<XTrapezoid> aTrapVector( nTrapCount );
+ const basegfx::B2DTrapezoid* pB2DTrap = pB2DTraps;
+ for( int i = 0; i < nTrapCount; ++pB2DTrap, ++i )
+ {
+ XTrapezoid& rTrap = aTrapVector[ i ] ;
+
+ // set y-coordinates
+ const double fY1 = pB2DTrap->getTopY();
+ rTrap.left.p1.y = rTrap.right.p1.y = rTrap.top = XDoubleToFixed( fY1 );
+ const double fY2 = pB2DTrap->getBottomY();
+ rTrap.left.p2.y = rTrap.right.p2.y = rTrap.bottom = XDoubleToFixed( fY2 );
+
+ // set x-coordinates
+ const double fXL1 = pB2DTrap->getTopXLeft();
+ rTrap.left.p1.x = XDoubleToFixed( fXL1 );
+ const double fXR1 = pB2DTrap->getTopXRight();
+ rTrap.right.p1.x = XDoubleToFixed( fXR1 );
+ const double fXL2 = pB2DTrap->getBottomXLeft();
+ rTrap.left.p2.x = XDoubleToFixed( fXL2 );
+ const double fXR2 = pB2DTrap->getBottomXRight();
+ rTrap.right.p2.x = XDoubleToFixed( fXR2 );
+ }
+
+ // get xrender Picture for polygon foreground
+ // TODO: cache it like the target picture which uses GetXRenderPicture()
+ XRenderPeer& rRenderPeer = XRenderPeer::GetInstance();
+ SalDisplay::RenderEntry& rEntry = mrParent.GetDisplay()->GetRenderEntries( mrParent.m_nXScreen )[ 32 ];
+ if( !rEntry.m_aPicture )
+ {
+ Display* pXDisplay = mrParent.GetXDisplay();
+
+ rEntry.m_aPixmap = limitXCreatePixmap( pXDisplay, mrParent.hDrawable_, 1, 1, 32 );
+ XRenderPictureAttributes aAttr;
+ aAttr.repeat = int(true);
+
+ XRenderPictFormat* pXRPF = rRenderPeer.FindStandardFormat( PictStandardARGB32 );
+ rEntry.m_aPicture = rRenderPeer.CreatePicture( rEntry.m_aPixmap, pXRPF, CPRepeat, &aAttr );
+ }
+
+ // set polygon foreground color and opacity
+ XRenderColor aRenderColor = GetXRenderColor( mnBrushColor , fTransparency );
+ rRenderPeer.FillRectangle( PictOpSrc, rEntry.m_aPicture, &aRenderColor, 0, 0, 1, 1 );
+
+ // set clipping
+ // TODO: move into GetXRenderPicture?
+ if( mrParent.mpClipRegion && !XEmptyRegion( mrParent.mpClipRegion ) )
+ rRenderPeer.SetPictureClipRegion( aDstPic, mrParent.mpClipRegion );
+
+ // render the trapezoids
+ const XRenderPictFormat* pMaskFormat = rRenderPeer.GetStandardFormatA8();
+ rRenderPeer.CompositeTrapezoids( PictOpOver,
+ rEntry.m_aPicture, aDstPic, pMaskFormat, 0, 0, aTrapVector.data(), aTrapVector.size() );
+
+ return true;
+}
+
+bool X11SalGraphicsImpl::drawFilledTriangles(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::triangulator::B2DTriangleVector& rTriangles,
+ double fTransparency)
+{
+ if(rTriangles.empty())
+ return true;
+
+ Picture aDstPic = GetXRenderPicture();
+ // check xrender support for this drawable
+ if( !aDstPic )
+ {
+ return false;
+ }
+
+ // prepare transformation for ObjectToDevice coordinate system
+ basegfx::B2DHomMatrix aObjectToDevice = basegfx::utils::createTranslateB2DHomMatrix(0.5, 0.5) * rObjectToDevice;
+
+ // convert the Triangles into XRender-Triangles
+ std::vector<XTriangle> aTriVector(rTriangles.size());
+ sal_uInt32 nIndex(0);
+
+ for(const auto& rCandidate : rTriangles)
+ {
+ const basegfx::B2DPoint aP1(aObjectToDevice * rCandidate.getA());
+ const basegfx::B2DPoint aP2(aObjectToDevice * rCandidate.getB());
+ const basegfx::B2DPoint aP3(aObjectToDevice * rCandidate.getC());
+ XTriangle& rTri(aTriVector[nIndex++]);
+
+ rTri.p1.x = XDoubleToFixed(aP1.getX());
+ rTri.p1.y = XDoubleToFixed(aP1.getY());
+
+ rTri.p2.x = XDoubleToFixed(aP2.getX());
+ rTri.p2.y = XDoubleToFixed(aP2.getY());
+
+ rTri.p3.x = XDoubleToFixed(aP3.getX());
+ rTri.p3.y = XDoubleToFixed(aP3.getY());
+ }
+
+ // get xrender Picture for polygon foreground
+ // TODO: cache it like the target picture which uses GetXRenderPicture()
+ XRenderPeer& rRenderPeer = XRenderPeer::GetInstance();
+ SalDisplay::RenderEntry& rEntry = mrParent.GetDisplay()->GetRenderEntries( mrParent.m_nXScreen )[ 32 ];
+ if( !rEntry.m_aPicture )
+ {
+ Display* pXDisplay = mrParent.GetXDisplay();
+
+ rEntry.m_aPixmap = limitXCreatePixmap( pXDisplay, mrParent.hDrawable_, 1, 1, 32 );
+ XRenderPictureAttributes aAttr;
+ aAttr.repeat = int(true);
+
+ XRenderPictFormat* pXRPF = rRenderPeer.FindStandardFormat( PictStandardARGB32 );
+ rEntry.m_aPicture = rRenderPeer.CreatePicture( rEntry.m_aPixmap, pXRPF, CPRepeat, &aAttr );
+ }
+
+ // set polygon foreground color and opacity
+ XRenderColor aRenderColor = GetXRenderColor( mnBrushColor , fTransparency );
+ rRenderPeer.FillRectangle( PictOpSrc, rEntry.m_aPicture, &aRenderColor, 0, 0, 1, 1 );
+
+ // set clipping
+ // TODO: move into GetXRenderPicture?
+ if( mrParent.mpClipRegion && !XEmptyRegion( mrParent.mpClipRegion ) )
+ rRenderPeer.SetPictureClipRegion( aDstPic, mrParent.mpClipRegion );
+
+ // render the trapezoids
+ const XRenderPictFormat* pMaskFormat = rRenderPeer.GetStandardFormatA8();
+ rRenderPeer.CompositeTriangles( PictOpOver,
+ rEntry.m_aPicture, aDstPic, pMaskFormat, 0, 0, aTriVector.data(), aTriVector.size() );
+
+ return true;
+}
+
+namespace {
+
+class SystemDependentData_Triangulation : public basegfx::SystemDependentData
+{
+private:
+ // the triangulation itself
+ basegfx::triangulator::B2DTriangleVector maTriangles;
+
+ // all other values the triangulation is based on and
+ // need to be compared with to check for data validity
+ double mfLineWidth;
+ basegfx::B2DLineJoin meJoin;
+ css::drawing::LineCap meCap;
+ double mfMiterMinimumAngle;
+ std::vector< double > maStroke;
+
+public:
+ SystemDependentData_Triangulation(
+ basegfx::SystemDependentDataManager& rSystemDependentDataManager,
+ const basegfx::triangulator::B2DTriangleVector& rTriangles,
+ double fLineWidth,
+ basegfx::B2DLineJoin eJoin,
+ css::drawing::LineCap eCap,
+ double fMiterMinimumAngle,
+ const std::vector< double >* pStroke); // MM01
+
+ // read access
+ const basegfx::triangulator::B2DTriangleVector& getTriangles() const { return maTriangles; }
+ double getLineWidth() const { return mfLineWidth; }
+ const basegfx::B2DLineJoin& getJoin() const { return meJoin; }
+ const css::drawing::LineCap& getCap() const { return meCap; }
+ double getMiterMinimumAngle() const { return mfMiterMinimumAngle; }
+ const std::vector< double >& getStroke() const { return maStroke; }
+
+ virtual sal_Int64 estimateUsageInBytes() const override;
+};
+
+}
+
+SystemDependentData_Triangulation::SystemDependentData_Triangulation(
+ basegfx::SystemDependentDataManager& rSystemDependentDataManager,
+ const basegfx::triangulator::B2DTriangleVector& rTriangles,
+ double fLineWidth,
+ basegfx::B2DLineJoin eJoin,
+ css::drawing::LineCap eCap,
+ double fMiterMinimumAngle,
+ const std::vector< double >* pStroke)
+: basegfx::SystemDependentData(rSystemDependentDataManager),
+ maTriangles(rTriangles),
+ mfLineWidth(fLineWidth),
+ meJoin(eJoin),
+ meCap(eCap),
+ mfMiterMinimumAngle(fMiterMinimumAngle),
+ maStroke()
+{
+ if(nullptr != pStroke)
+ {
+ maStroke = *pStroke;
+ }
+}
+
+sal_Int64 SystemDependentData_Triangulation::estimateUsageInBytes() const
+{
+ sal_Int64 nRetval(0);
+
+ if(!maTriangles.empty())
+ {
+ nRetval = maTriangles.size() * sizeof(basegfx::triangulator::B2DTriangle);
+ }
+
+ return nRetval;
+}
+
+bool X11SalGraphicsImpl::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)
+{
+ // short circuit if there is nothing to do
+ if(0 == 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());
+ basegfx::B2DHomMatrix aObjectToDeviceInv;
+
+ // tdf#124848 calculate-back logical LineWidth for a hairline.
+ // This implementation does not hand over the transformation to
+ // the graphic sub-system, but the triangulation data is prepared
+ // view-independent based on the logic LineWidth, so we need to
+ // know it
+ if(fLineWidth == 0)
+ {
+ fLineWidth = 1.0;
+
+ if(!bObjectToDeviceIsIdentity)
+ {
+ if(aObjectToDeviceInv.isIdentity())
+ {
+ aObjectToDeviceInv = rObjectToDevice;
+ aObjectToDeviceInv.invert();
+ }
+
+ fLineWidth = (aObjectToDeviceInv * basegfx::B2DVector(fLineWidth, 0)).getLength();
+ }
+ }
+
+ // try to access buffered data
+ std::shared_ptr<SystemDependentData_Triangulation> pSystemDependentData_Triangulation(
+ rPolygon.getSystemDependentData<SystemDependentData_Triangulation>());
+
+ // 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));
+
+ if(pSystemDependentData_Triangulation)
+ {
+ // 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_Triangulation->getStroke().empty());
+
+ if(bStrokeWasUsed != bStrokeUsed
+ || (bStrokeUsed && *pStroke != pSystemDependentData_Triangulation->getStroke()))
+ {
+ // data invalid, forget
+ pSystemDependentData_Triangulation.reset();
+ }
+ }
+
+ if(pSystemDependentData_Triangulation)
+ {
+ // check data validity (I)
+ if(pSystemDependentData_Triangulation->getJoin() != eLineJoin
+ || pSystemDependentData_Triangulation->getCap() != eLineCap
+ || pSystemDependentData_Triangulation->getMiterMinimumAngle() != fMiterMinimumAngle)
+ {
+ // data invalid, forget
+ pSystemDependentData_Triangulation.reset();
+ }
+ }
+
+ if(pSystemDependentData_Triangulation)
+ {
+ // check data validity (II)
+ if(pSystemDependentData_Triangulation->getLineWidth() != fLineWidth)
+ {
+ // sometimes small inconsistencies, use a percentage tolerance
+ const double fFactor(basegfx::fTools::equalZero(fLineWidth)
+ ? 0.0
+ : fabs(1.0 - (pSystemDependentData_Triangulation->getLineWidth() / fLineWidth)));
+ // compare with 5.0% tolerance
+ if(basegfx::fTools::more(fFactor, 0.05))
+ {
+ // data invalid, forget
+ pSystemDependentData_Triangulation.reset();
+ }
+ }
+ }
+
+ if(!pSystemDependentData_Triangulation)
+ {
+ // MM01 need to do line dashing as fallback stuff here now
+ basegfx::B2DPolyPolygon aPolyPolygonLine;
+
+ if(bStrokeUsed)
+ {
+ // apply LineStyle
+ basegfx::utils::applyLineDashing(
+ rPolygon, // source
+ *pStroke, // pattern
+ &aPolyPolygonLine, // target for lines
+ nullptr, // target for gaps
+ fDotDashLength); // full length if available
+ }
+ else
+ {
+ // no line dashing, just copy
+ aPolyPolygonLine.append(rPolygon);
+ }
+
+ // try to create data
+ if(bPixelSnapHairline)
+ {
+ // Do NOT transform, but keep device-independent. To
+ // do so, transform to device for snap, but back again after
+ if(!bObjectToDeviceIsIdentity)
+ {
+ aPolyPolygonLine.transform(rObjectToDevice);
+ }
+
+ aPolyPolygonLine = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyPolygonLine);
+
+ if(!bObjectToDeviceIsIdentity)
+ {
+ if(aObjectToDeviceInv.isIdentity())
+ {
+ aObjectToDeviceInv = rObjectToDevice;
+ aObjectToDeviceInv.invert();
+ }
+
+ aPolyPolygonLine.transform(aObjectToDeviceInv);
+ }
+ }
+
+ basegfx::triangulator::B2DTriangleVector aTriangles;
+
+ // MM01 checked/verified for X11 (linux)
+ for(sal_uInt32 a(0); a < aPolyPolygonLine.count(); a++)
+ {
+ const basegfx::B2DPolygon aPolyLine(aPolyPolygonLine.getB2DPolygon(a));
+ // MM01 upps - commit 51b5b93092d6231615de470c62494c24e54828a1 removed
+ // this *central* geometry-creating lines (!) probably due to aAreaPolyPoly
+ // *not* being used - that's true, but the work is inside of filling
+ // aTriangles data (!)
+ basegfx::utils::createAreaGeometry(
+ aPolyLine,
+ 0.5 * fLineWidth,
+ eLineJoin,
+ eLineCap,
+ basegfx::deg2rad(12.5),
+ 0.4,
+ fMiterMinimumAngle,
+ &aTriangles); // CAUTION! This is *needed* since it creates the data!
+ }
+
+ if(!aTriangles.empty())
+ {
+ // Add to buffering mechanism
+ // Add all values the triangulation is based off, too, to check for
+ // validity (see above)
+ pSystemDependentData_Triangulation = rPolygon.addOrReplaceSystemDependentData<SystemDependentData_Triangulation>(
+ ImplGetSystemDependentDataManager(),
+ aTriangles,
+ fLineWidth,
+ eLineJoin,
+ eLineCap,
+ fMiterMinimumAngle,
+ pStroke);
+ }
+ }
+
+ if(!pSystemDependentData_Triangulation)
+ {
+ return false;
+ }
+
+ // temporarily adjust brush color to pen color
+ // since the line is drawn as an area-polygon
+ const Color aKeepBrushColor = mnBrushColor;
+ mnBrushColor = mnPenColor;
+
+ // create the area-polygon for the line
+ const bool bDrawnOk(
+ drawFilledTriangles(
+ rObjectToDevice,
+ pSystemDependentData_Triangulation->getTriangles(),
+ fTransparency));
+
+ // restore the original brush GC
+ mnBrushColor = aKeepBrushColor;
+ return bDrawnOk;
+}
+
+Color X11SalGraphicsImpl::getPixel( long nX, long nY )
+{
+ if( mrParent.bWindow_ && !mrParent.bVirDev_ )
+ {
+ XWindowAttributes aAttrib;
+
+ XGetWindowAttributes( mrParent.GetXDisplay(), mrParent.GetDrawable(), &aAttrib );
+ if( aAttrib.map_state != IsViewable )
+ {
+ SAL_WARN( "vcl", "X11SalGraphics::GetPixel drawable not viewable" );
+ return 0;
+ }
+ }
+
+ XImage *pXImage = XGetImage( mrParent.GetXDisplay(),
+ mrParent.GetDrawable(),
+ nX, nY,
+ 1, 1,
+ AllPlanes,
+ ZPixmap );
+ if( !pXImage )
+ {
+ SAL_WARN( "vcl", "X11SalGraphics::GetPixel !XGetImage()" );
+ return 0;
+ }
+
+ XColor aXColor;
+
+ aXColor.pixel = XGetPixel( pXImage, 0, 0 );
+ XDestroyImage( pXImage );
+
+ return mrParent.GetColormap().GetColor( aXColor.pixel );
+}
+
+std::shared_ptr<SalBitmap> X11SalGraphicsImpl::getBitmap( long nX, long nY, long nDX, long nDY )
+{
+ bool bFakeWindowBG = false;
+
+ // normalize
+ if( nDX < 0 )
+ {
+ nX += nDX;
+ nDX = -nDX;
+ }
+ if ( nDY < 0 )
+ {
+ nY += nDY;
+ nDY = -nDY;
+ }
+
+ if( mrParent.bWindow_ && !mrParent.bVirDev_ )
+ {
+ XWindowAttributes aAttrib;
+
+ XGetWindowAttributes( mrParent.GetXDisplay(), mrParent.GetDrawable(), &aAttrib );
+ if( aAttrib.map_state != IsViewable )
+ bFakeWindowBG = true;
+ else
+ {
+ long nOrgDX = nDX, nOrgDY = nDY;
+
+ // clip to window size
+ if ( nX < 0 )
+ {
+ nDX += nX;
+ nX = 0;
+ }
+ if ( nY < 0 )
+ {
+ nDY += nY;
+ nY = 0;
+ }
+ if( nX + nDX > aAttrib.width )
+ nDX = aAttrib.width - nX;
+ if( nY + nDY > aAttrib.height )
+ nDY = aAttrib.height - nY;
+
+ // inside ?
+ if( nDX <= 0 || nDY <= 0 )
+ {
+ bFakeWindowBG = true;
+ nDX = nOrgDX;
+ nDY = nOrgDY;
+ }
+ }
+ }
+
+ std::shared_ptr<X11SalBitmap> pSalBitmap = std::make_shared<X11SalBitmap>();
+ sal_uInt16 nBitCount = GetBitCount();
+
+ if( &mrParent.GetDisplay()->GetColormap( mrParent.m_nXScreen ) != &mrParent.GetColormap() )
+ nBitCount = 1;
+
+ if( ! bFakeWindowBG )
+ pSalBitmap->ImplCreateFromDrawable( mrParent.GetDrawable(), mrParent.m_nXScreen, nBitCount, nX, nY, nDX, nDY );
+ else
+ pSalBitmap->Create( Size( nDX, nDY ), (nBitCount > 8) ? 24 : nBitCount, BitmapPalette( nBitCount > 8 ? nBitCount : 0 ) );
+
+ return pSalBitmap;
+}
+
+sal_uInt16 X11SalGraphicsImpl::GetBitCount() const
+{
+ return mrParent.GetVisual().GetDepth();
+}
+
+long X11SalGraphicsImpl::GetGraphicsWidth() const
+{
+ if( mrParent.m_pFrame )
+ return mrParent.m_pFrame->maGeometry.nWidth;
+ else if( mrParent.m_pVDev )
+ return static_cast< X11SalVirtualDevice* >(mrParent.m_pVDev)->GetWidth();
+ else
+ return 0;
+}
+
+bool X11SalGraphicsImpl::drawGradient(const tools::PolyPolygon& /*rPolygon*/, const Gradient& /*rGradient*/)
+{
+ return false;
+}
+
+bool X11SalGraphicsImpl::supportsOperation(OutDevSupportType eType) const
+{
+ bool bRet = false;
+ switch (eType)
+ {
+ case OutDevSupportType::TransparentRect:
+ case OutDevSupportType::B2DDraw:
+ {
+ XRenderPeer& rPeer = XRenderPeer::GetInstance();
+ const SalDisplay* pSalDisp = mrParent.GetDisplay();
+ const SalVisual& rSalVis = pSalDisp->GetVisual(mrParent.GetScreenNumber());
+
+ Visual* pDstXVisual = rSalVis.GetVisual();
+ XRenderPictFormat* pDstVisFmt = rPeer.FindVisualFormat(pDstXVisual);
+ if (pDstVisFmt)
+ bRet = true;
+ }
+ break;
+ default:
+ break;
+ }
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/gdi/gdiimpl.hxx b/vcl/unx/generic/gdi/gdiimpl.hxx
new file mode 100644
index 000000000..061993bb8
--- /dev/null
+++ b/vcl/unx/generic/gdi/gdiimpl.hxx
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_GENERIC_GDI_GDIIMPL_HXX
+#define INCLUDED_VCL_GENERIC_GDI_GDIIMPL_HXX
+
+#include <X11/Xlib.h>
+
+#include <unx/x11/x11gdiimpl.h>
+
+#include <salgdiimpl.hxx>
+
+#include <basegfx/polygon/b2dtrapezoid.hxx>
+#include <basegfx/polygon/b2dpolygontriangulator.hxx>
+#include <ControlCacheKey.hxx>
+
+/* From <X11/Intrinsic.h> */
+typedef unsigned long Pixel;
+
+class SalGraphics;
+class SalBitmap;
+class SalPolyLine;
+class X11SalGraphics;
+class Gradient;
+
+class X11SalGraphicsImpl : public SalGraphicsImpl, public X11GraphicsImpl
+{
+private:
+ X11SalGraphics& mrParent;
+
+ Color mnBrushColor;
+ GC mpBrushGC; // Brush attributes
+ Pixel mnBrushPixel;
+
+ bool mbPenGC : 1; // is Pen GC valid
+ bool mbBrushGC : 1; // is Brush GC valid
+ bool mbCopyGC : 1; // is Copy GC valid
+ bool mbInvertGC : 1; // is Invert GC valid
+ bool mbInvert50GC : 1; // is Invert50 GC valid
+ bool mbStippleGC : 1; // is Stipple GC valid
+ bool mbTrackingGC : 1; // is Tracking GC valid
+ bool mbDitherBrush : 1; // is solid or tile
+
+ bool mbXORMode : 1; // is ROP XOR Mode set
+
+ GC mpPenGC; // Pen attributes
+ Color mnPenColor;
+ Pixel mnPenPixel;
+
+
+ GC mpMonoGC;
+ GC mpCopyGC;
+ GC mpMaskGC;
+ GC mpInvertGC;
+ GC mpInvert50GC;
+ GC mpStippleGC;
+ GC mpTrackingGC;
+
+ GC CreateGC( Drawable hDrawable,
+ unsigned long nMask = GCGraphicsExposures );
+
+ GC SelectBrush();
+ GC SelectPen();
+ inline GC GetCopyGC();
+ inline GC GetStippleGC();
+ GC GetTrackingGC();
+ GC GetInvertGC();
+ GC GetInvert50GC();
+
+ void DrawLines( sal_uInt32 nPoints,
+ const SalPolyLine &rPoints,
+ GC pGC,
+ bool bClose
+ );
+
+ XID GetXRenderPicture();
+ bool drawFilledTrapezoids( const basegfx::B2DTrapezoid*, int nTrapCount, double fTransparency );
+ bool drawFilledTriangles(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::triangulator::B2DTriangleVector& rTriangles,
+ double fTransparency);
+
+ long GetGraphicsHeight() const;
+
+ void drawMaskedBitmap( const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap,
+ const SalBitmap& rTransparentBitmap );
+
+ void drawPolyLine( sal_uInt32 nPoints, const SalPoint* pPtAry, bool bClose );
+
+public:
+
+ explicit X11SalGraphicsImpl(X11SalGraphics& rParent);
+
+ virtual void freeResources() override;
+
+ virtual ~X11SalGraphicsImpl() override;
+
+ virtual OUString getRenderBackendName() const override { return "gen"; }
+
+ virtual bool setClipRegion( const vcl::Region& ) override;
+ //
+ // get the depth of the device
+ virtual sal_uInt16 GetBitCount() const override;
+
+ // get the width of the device
+ virtual 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( long nX, long nY ) override;
+ virtual void drawPixel( long nX, long nY, Color nColor ) override;
+
+ virtual void drawLine( long nX1, long nY1, long nX2, long nY2 ) override;
+
+ virtual void drawRect( long nX, long nY, long nWidth, long nHeight ) override;
+
+ virtual void drawPolyLine( sal_uInt32 nPoints, const SalPoint* pPtAry ) override;
+
+ virtual void drawPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry ) override;
+
+ virtual void drawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints, PCONSTSALPOINT* pPtAry ) override;
+
+ virtual bool 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 SalPoint* pPtAry,
+ const PolyFlags* pFlgAry ) override;
+
+ virtual bool drawPolygonBezier(
+ sal_uInt32 nPoints,
+ const SalPoint* pPtAry,
+ const PolyFlags* pFlgAry ) override;
+
+ virtual bool drawPolyPolygonBezier(
+ sal_uInt32 nPoly,
+ const sal_uInt32* pPoints,
+ const SalPoint* const* pPtAry,
+ const PolyFlags* const* pFlgAry ) override;
+
+ // CopyArea --> No RasterOp, but ClipRegion
+ virtual void copyArea(
+ long nDestX, long nDestY,
+ long nSrcX, long nSrcY,
+ long nSrcWidth, 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( long nX, long nY, long nWidth, long nHeight ) override;
+
+ virtual Color getPixel( long nX, long nY ) override;
+
+ // invert --> ClipRegion (only Windows or VirDevs)
+ virtual void invert(
+ long nX, long nY,
+ long nWidth, long nHeight,
+ SalInvert nFlags) override;
+
+ virtual void invert( sal_uInt32 nPoints, const SalPoint* pPtAry, SalInvert nFlags ) override;
+
+ virtual bool drawEPS(
+ long nX, long nY,
+ long nWidth, long nHeight,
+ void* pPtr,
+ sal_uInt32 nSize ) override;
+
+ /** Blend bitmap with color channels */
+ virtual bool blendBitmap(
+ const SalTwoRect&,
+ const SalBitmap& rBitmap ) override;
+
+ /** Render bitmap by blending using the mask and alpha channel */
+ 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) 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(
+ long nX, long nY,
+ long nWidth, long nHeight,
+ sal_uInt8 nTransparency ) override;
+
+ virtual bool drawGradient(const tools::PolyPolygon& rPolygon, const Gradient& rGradient) override;
+
+ virtual bool supportsOperation(OutDevSupportType eType) const override;
+
+public:
+ void Init() override;
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/gdi/nativewindowhandleprovider.cxx b/vcl/unx/generic/gdi/nativewindowhandleprovider.cxx
new file mode 100644
index 000000000..47986cf20
--- /dev/null
+++ b/vcl/unx/generic/gdi/nativewindowhandleprovider.cxx
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/nativewindowhandleprovider.hxx>
+
+NativeWindowHandleProvider::~NativeWindowHandleProvider()
+{
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/gdi/salbmp.cxx b/vcl/unx/generic/gdi/salbmp.cxx
new file mode 100644
index 000000000..01a5e7637
--- /dev/null
+++ b/vcl/unx/generic/gdi/salbmp.cxx
@@ -0,0 +1,1036 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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>
+
+#ifdef FREEBSD
+#include <sys/types.h>
+#endif
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#include <osl/endian.h>
+#include <sal/log.hxx>
+
+#include <tools/helpers.hxx>
+#include <tools/debug.hxx>
+#include <vcl/bitmap.hxx>
+#include <com/sun/star/beans/XFastPropertySet.hpp>
+
+#include <unx/saldisp.hxx>
+#include <unx/salbmp.h>
+#include <unx/salinst.h>
+#include <unx/x11/xlimits.hxx>
+
+#include <o3tl/safeint.hxx>
+#include <opengl/salbmp.hxx>
+#include <vcl/opengl/OpenGLHelper.hxx>
+
+#include <config_features.h>
+#if HAVE_FEATURE_SKIA
+#include <vcl/skia/SkiaHelper.hxx>
+#include <skia/salbmp.hxx>
+#endif
+
+#if defined HAVE_VALGRIND_HEADERS
+#include <valgrind/valgrind.h>
+#endif
+
+#include <memory>
+
+
+std::shared_ptr<SalBitmap> X11SalInstance::CreateSalBitmap()
+{
+#if HAVE_FEATURE_SKIA
+ if (SkiaHelper::isVCLSkiaEnabled())
+ return std::make_shared<SkiaSalBitmap>();
+ else
+#endif
+ if (OpenGLHelper::isVCLOpenGLEnabled())
+ return std::make_shared<OpenGLSalBitmap>();
+ else
+ return std::make_shared<X11SalBitmap>();
+}
+
+ImplSalBitmapCache* X11SalBitmap::mpCache = nullptr;
+unsigned int X11SalBitmap::mnCacheInstCount = 0;
+
+X11SalBitmap::X11SalBitmap()
+ : mbGrey( false )
+{
+}
+
+X11SalBitmap::~X11SalBitmap()
+{
+ Destroy();
+}
+
+void X11SalBitmap::ImplCreateCache()
+{
+ if( !mnCacheInstCount++ )
+ mpCache = new ImplSalBitmapCache;
+}
+
+void X11SalBitmap::ImplDestroyCache()
+{
+ SAL_WARN_IF( !mnCacheInstCount, "vcl", "X11SalBitmap::ImplDestroyCache(): underflow" );
+
+ if( mnCacheInstCount && !--mnCacheInstCount )
+ {
+ delete mpCache;
+ mpCache = nullptr;
+ }
+}
+
+void X11SalBitmap::ImplRemovedFromCache()
+{
+ mpDDB.reset();
+}
+
+#if defined HAVE_VALGRIND_HEADERS
+namespace
+{
+ void blankExtraSpace(BitmapBuffer* pDIB)
+ {
+ size_t nExtraSpaceInScanLine = pDIB->mnScanlineSize - pDIB->mnWidth * pDIB->mnBitCount / 8;
+ if (nExtraSpaceInScanLine)
+ {
+ for (long i = 0; i < pDIB->mnHeight; ++i)
+ {
+ sal_uInt8 *pRow = pDIB->mpBits + (i * pDIB->mnScanlineSize);
+ memset(pRow + (pDIB->mnScanlineSize - nExtraSpaceInScanLine), 0, nExtraSpaceInScanLine);
+ }
+ }
+ }
+}
+#endif
+
+std::unique_ptr<BitmapBuffer> X11SalBitmap::ImplCreateDIB(
+ const Size& rSize,
+ sal_uInt16 nBitCount,
+ const BitmapPalette& rPal)
+{
+ DBG_ASSERT(
+ nBitCount == 1
+ || nBitCount == 4
+ || nBitCount == 8
+ || nBitCount == 24
+ , "Unsupported BitCount!"
+ );
+
+ std::unique_ptr<BitmapBuffer> pDIB;
+
+ if( !rSize.Width() || !rSize.Height() )
+ return nullptr;
+
+ try
+ {
+ pDIB.reset(new BitmapBuffer);
+ }
+ catch (const std::bad_alloc&)
+ {
+ return nullptr;
+ }
+
+ const sal_uInt16 nColors = ( nBitCount <= 8 ) ? ( 1 << nBitCount ) : 0;
+
+ pDIB->mnFormat = ScanlineFormat::NONE;
+
+ switch( nBitCount )
+ {
+ case 1: pDIB->mnFormat |= ScanlineFormat::N1BitMsbPal; break;
+ case 4: pDIB->mnFormat |= ScanlineFormat::N4BitMsnPal; break;
+ case 8: pDIB->mnFormat |= ScanlineFormat::N8BitPal; break;
+ case 24: pDIB->mnFormat |= ScanlineFormat::N24BitTcBgr; break;
+ default:
+ SAL_WARN("vcl.gdi", "32-bit images not supported, converting to 24-bit");
+ nBitCount = 24;
+ pDIB->mnFormat |= ScanlineFormat::N24BitTcBgr;
+ break;
+ }
+
+ pDIB->mnWidth = rSize.Width();
+ pDIB->mnHeight = rSize.Height();
+ long nScanlineBase;
+ bool bFail = o3tl::checked_multiply<long>(pDIB->mnWidth, nBitCount, nScanlineBase);
+ if (bFail)
+ {
+ SAL_WARN("vcl.gdi", "checked multiply failed");
+ return nullptr;
+ }
+ pDIB->mnScanlineSize = AlignedWidth4Bytes(nScanlineBase);
+ if (pDIB->mnScanlineSize < nScanlineBase/8)
+ {
+ SAL_WARN("vcl.gdi", "scanline calculation wraparound");
+ return nullptr;
+ }
+ pDIB->mnBitCount = nBitCount;
+
+ if( nColors )
+ {
+ pDIB->maPalette = rPal;
+ pDIB->maPalette.SetEntryCount( nColors );
+ }
+
+ try
+ {
+ pDIB->mpBits = new sal_uInt8[ pDIB->mnScanlineSize * pDIB->mnHeight ];
+#if defined HAVE_VALGRIND_HEADERS
+ if (RUNNING_ON_VALGRIND)
+ blankExtraSpace(pDIB.get());
+#endif
+ }
+ catch (const std::bad_alloc&)
+ {
+ return nullptr;
+ }
+
+ return pDIB;
+}
+
+std::unique_ptr<BitmapBuffer> X11SalBitmap::ImplCreateDIB(
+ Drawable aDrawable,
+ SalX11Screen nScreen,
+ long nDrawableDepth,
+ long nX,
+ long nY,
+ long nWidth,
+ long nHeight,
+ bool bGrey
+) {
+ std::unique_ptr<BitmapBuffer> pDIB;
+
+ if( aDrawable && nWidth && nHeight && nDrawableDepth )
+ {
+ SalDisplay* pSalDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData());
+ Display* pXDisp = pSalDisp->GetDisplay();
+
+ // do not die on XError here
+ // alternatively one could check the coordinates for being offscreen
+ // but this call can actually work on servers with backing store
+ // defaults even if the rectangle is offscreen
+ // so better catch the XError
+ GetGenericUnixSalData()->ErrorTrapPush();
+ XImage* pImage = XGetImage( pXDisp, aDrawable, nX, nY, nWidth, nHeight, AllPlanes, ZPixmap );
+ bool bWasError = GetGenericUnixSalData()->ErrorTrapPop( false );
+
+ if( ! bWasError && pImage && pImage->data )
+ {
+ const SalTwoRect aTwoRect = { 0, 0, nWidth, nHeight, 0, 0, nWidth, nHeight };
+ BitmapBuffer aSrcBuf;
+ const BitmapPalette* pDstPal = nullptr;
+
+ aSrcBuf.mnFormat = ScanlineFormat::TopDown;
+ aSrcBuf.mnWidth = nWidth;
+ aSrcBuf.mnHeight = nHeight;
+ aSrcBuf.mnBitCount = pImage->bits_per_pixel;
+ aSrcBuf.mnScanlineSize = pImage->bytes_per_line;
+ aSrcBuf.mpBits = reinterpret_cast<sal_uInt8*>(pImage->data);
+
+ pImage->red_mask = pSalDisp->GetVisual( nScreen ).red_mask;
+ pImage->green_mask = pSalDisp->GetVisual( nScreen ).green_mask;
+ pImage->blue_mask = pSalDisp->GetVisual( nScreen ).blue_mask;
+
+ switch( aSrcBuf.mnBitCount )
+ {
+ case 1:
+ {
+ aSrcBuf.mnFormat |= ( LSBFirst == pImage->bitmap_bit_order
+ ? ScanlineFormat::N1BitLsbPal
+ : ScanlineFormat::N1BitMsbPal
+ );
+ }
+ break;
+
+ case 4:
+ {
+ aSrcBuf.mnFormat |= ( LSBFirst == pImage->bitmap_bit_order
+ ? ScanlineFormat::N4BitLsnPal
+ : ScanlineFormat::N4BitMsnPal
+ );
+ }
+ break;
+
+ case 8:
+ {
+ aSrcBuf.mnFormat |= ScanlineFormat::N8BitPal;
+ }
+ break;
+
+ case 24:
+ {
+ if( ( LSBFirst == pImage->byte_order ) && ( pImage->red_mask == 0xFF ) )
+ aSrcBuf.mnFormat |= ScanlineFormat::N24BitTcRgb;
+ else
+ aSrcBuf.mnFormat |= ScanlineFormat::N24BitTcBgr;
+ }
+ break;
+
+ case 32:
+ {
+ if( LSBFirst == pImage->byte_order )
+ aSrcBuf.mnFormat |= ( pSalDisp->GetVisual(nScreen).red_mask == 0xFF
+ ? ScanlineFormat::N32BitTcRgba
+ : ScanlineFormat::N32BitTcBgra
+ );
+ else
+ aSrcBuf.mnFormat |= ( pSalDisp->GetVisual(nScreen).red_mask == 0xFF
+ ? ScanlineFormat::N32BitTcAbgr
+ : ScanlineFormat::N32BitTcArgb
+ );
+ }
+ break;
+
+ default: assert(false);
+ }
+
+ BitmapPalette& rPal = aSrcBuf.maPalette;
+
+ if( aSrcBuf.mnBitCount == 1 )
+ {
+ rPal.SetEntryCount( 2 );
+ pDstPal = &rPal;
+
+ rPal[ 0 ] = COL_BLACK;
+ rPal[ 1 ] = COL_WHITE;
+ }
+ else if( pImage->depth == 8 && bGrey )
+ {
+ rPal.SetEntryCount( 256 );
+ pDstPal = &rPal;
+
+ for( sal_uInt16 i = 0; i < 256; i++ )
+ {
+ BitmapColor& rBmpCol = rPal[ i ];
+
+ rBmpCol.SetRed( i );
+ rBmpCol.SetGreen( i );
+ rBmpCol.SetBlue( i );
+ }
+
+ }
+ else if( aSrcBuf.mnBitCount <= 8 )
+ {
+ const SalColormap& rColMap = pSalDisp->GetColormap( nScreen );
+ const sal_uInt16 nCols = std::min(static_cast<sal_uLong>(rColMap.GetUsed()),
+ sal_uLong(1) << nDrawableDepth);
+
+ rPal.SetEntryCount( nCols );
+ pDstPal = &rPal;
+
+ for( sal_uInt16 i = 0; i < nCols; i++ )
+ {
+ const Color nColor( rColMap.GetColor( i ) );
+ BitmapColor& rBmpCol = rPal[ i ];
+
+ rBmpCol.SetRed( nColor.GetRed() );
+ rBmpCol.SetGreen( nColor.GetGreen() );
+ rBmpCol.SetBlue( nColor.GetBlue() );
+ }
+ }
+
+ pDIB = StretchAndConvert( aSrcBuf, aTwoRect, aSrcBuf.mnFormat,
+ pDstPal, &aSrcBuf.maColorMask );
+ XDestroyImage( pImage );
+ }
+ }
+
+ return pDIB;
+}
+
+XImage* X11SalBitmap::ImplCreateXImage(
+ SalDisplay const *pSalDisp,
+ SalX11Screen nScreen,
+ long nDepth,
+ const SalTwoRect& rTwoRect
+) const
+{
+ XImage* pImage = nullptr;
+
+ if( !mpDIB && mpDDB )
+ {
+ const_cast<X11SalBitmap*>(this)->mpDIB =
+ ImplCreateDIB( mpDDB->ImplGetPixmap(),
+ mpDDB->ImplGetScreen(),
+ mpDDB->ImplGetDepth(),
+ 0, 0,
+ mpDDB->ImplGetWidth(),
+ mpDDB->ImplGetHeight(),
+ mbGrey );
+ }
+
+ if( mpDIB && mpDIB->mnWidth && mpDIB->mnHeight )
+ {
+ Display* pXDisp = pSalDisp->GetDisplay();
+ long nWidth = rTwoRect.mnDestWidth;
+ long nHeight = rTwoRect.mnDestHeight;
+
+ if( 1 == GetBitCount() )
+ nDepth = 1;
+
+ pImage = XCreateImage( pXDisp, pSalDisp->GetVisual( nScreen ).GetVisual(),
+ nDepth, ( 1 == nDepth ) ? XYBitmap :ZPixmap, 0, nullptr,
+ nWidth, nHeight, 32, 0 );
+
+ if( pImage )
+ {
+ std::unique_ptr<BitmapBuffer> pDstBuf;
+ ScanlineFormat nDstFormat = ScanlineFormat::TopDown;
+ std::unique_ptr<BitmapPalette> xPal;
+ std::unique_ptr<ColorMask> xMask;
+
+ switch( pImage->bits_per_pixel )
+ {
+ case 1:
+ nDstFormat |= ( LSBFirst == pImage->bitmap_bit_order
+ ? ScanlineFormat::N1BitLsbPal
+ : ScanlineFormat::N1BitMsbPal
+ );
+ break;
+
+ case 4:
+ nDstFormat |= ( LSBFirst == pImage->bitmap_bit_order
+ ? ScanlineFormat::N4BitLsnPal
+ : ScanlineFormat::N4BitMsnPal
+ );
+ break;
+
+ case 8:
+ nDstFormat |= ScanlineFormat::N8BitPal;
+ break;
+
+ case 24:
+ {
+ if( ( LSBFirst == pImage->byte_order ) && ( pImage->red_mask == 0xFF ) )
+ nDstFormat |= ScanlineFormat::N24BitTcRgb;
+ else
+ nDstFormat |= ScanlineFormat::N24BitTcBgr;
+ }
+ break;
+
+ case 32:
+ {
+ if( LSBFirst == pImage->byte_order )
+ nDstFormat |= ( pImage->red_mask == 0xFF
+ ? ScanlineFormat::N32BitTcRgba
+ : ScanlineFormat::N32BitTcBgra
+ );
+ else
+ nDstFormat |= ( pImage->red_mask == 0xFF
+ ? ScanlineFormat::N32BitTcAbgr
+ : ScanlineFormat::N32BitTcArgb
+ );
+ }
+ break;
+
+ default: assert(false);
+ }
+
+ if( pImage->depth == 1 )
+ {
+ xPal.reset(new BitmapPalette( 2 ));
+ (*xPal)[ 0 ] = COL_BLACK;
+ (*xPal)[ 1 ] = COL_WHITE;
+ }
+ else if( pImage->depth == 8 && mbGrey )
+ {
+ xPal.reset(new BitmapPalette( 256 ));
+
+ for( sal_uInt16 i = 0; i < 256; i++ )
+ {
+ BitmapColor& rBmpCol = (*xPal)[ i ];
+
+ rBmpCol.SetRed( i );
+ rBmpCol.SetGreen( i );
+ rBmpCol.SetBlue( i );
+ }
+
+ }
+ else if( pImage->depth <= 8 )
+ {
+ const SalColormap& rColMap = pSalDisp->GetColormap( nScreen );
+ const sal_uInt16 nCols = std::min( static_cast<sal_uLong>(rColMap.GetUsed())
+ , static_cast<sal_uLong>(1 << pImage->depth)
+ );
+
+ xPal.reset(new BitmapPalette( nCols ));
+
+ for( sal_uInt16 i = 0; i < nCols; i++ )
+ {
+ const Color nColor( rColMap.GetColor( i ) );
+ BitmapColor& rBmpCol = (*xPal)[ i ];
+
+ rBmpCol.SetRed( nColor.GetRed() );
+ rBmpCol.SetGreen( nColor.GetGreen() );
+ rBmpCol.SetBlue( nColor.GetBlue() );
+ }
+ }
+
+ pDstBuf = StretchAndConvert( *mpDIB, rTwoRect, nDstFormat, xPal.get(), xMask.get() );
+ xPal.reset();
+ xMask.reset();
+
+ if( pDstBuf && pDstBuf->mpBits )
+ {
+#if defined HAVE_VALGRIND_HEADERS
+ if (RUNNING_ON_VALGRIND)
+ blankExtraSpace(pDstBuf.get());
+#endif
+ // set data in buffer as data member in pImage
+ pImage->data = reinterpret_cast<char*>(pDstBuf->mpBits);
+ }
+ else
+ {
+ XDestroyImage( pImage );
+ pImage = nullptr;
+ }
+
+ // note that pDstBuf it deleted here, but that doesn't destroy allocated data in buffer
+ }
+ }
+
+ return pImage;
+}
+
+bool X11SalBitmap::ImplCreateFromDrawable(
+ Drawable aDrawable,
+ SalX11Screen nScreen,
+ long nDrawableDepth,
+ long nX,
+ long nY,
+ long nWidth,
+ long nHeight
+) {
+ Destroy();
+
+ if( aDrawable && nWidth && nHeight && nDrawableDepth )
+ mpDDB.reset(new ImplSalDDB( aDrawable, nScreen, nDrawableDepth, nX, nY, nWidth, nHeight ));
+
+ return( mpDDB != nullptr );
+}
+
+ImplSalDDB* X11SalBitmap::ImplGetDDB(
+ Drawable aDrawable,
+ SalX11Screen nXScreen,
+ long nDrawableDepth,
+ const SalTwoRect& rTwoRect
+) const
+{
+ if( !mpDDB || !mpDDB->ImplMatches( nXScreen, nDrawableDepth, rTwoRect ) )
+ {
+ if( mpDDB )
+ {
+ // do we already have a DIB? if not, create aDIB from current DDB first
+ if( !mpDIB )
+ {
+ const_cast<X11SalBitmap*>(this)->mpDIB = ImplCreateDIB( mpDDB->ImplGetPixmap(),
+ mpDDB->ImplGetScreen(),
+ mpDDB->ImplGetDepth(),
+ 0, 0,
+ mpDDB->ImplGetWidth(),
+ mpDDB->ImplGetHeight(),
+ mbGrey );
+ }
+
+ mpDDB.reset();
+ }
+
+ if( mpCache )
+ mpCache->ImplRemove( this );
+
+ SalTwoRect aTwoRect( rTwoRect );
+ if( aTwoRect.mnSrcX < 0 )
+ {
+ aTwoRect.mnSrcWidth += aTwoRect.mnSrcX;
+ aTwoRect.mnSrcX = 0;
+ }
+ if( aTwoRect.mnSrcY < 0 )
+ {
+ aTwoRect.mnSrcHeight += aTwoRect.mnSrcY;
+ aTwoRect.mnSrcY = 0;
+ }
+
+ // create new DDB from DIB
+ const Size aSize( GetSize() );
+ if( aTwoRect.mnSrcWidth == aTwoRect.mnDestWidth &&
+ aTwoRect.mnSrcHeight == aTwoRect.mnDestHeight )
+ {
+ aTwoRect.mnSrcX = aTwoRect.mnSrcY = aTwoRect.mnDestX = aTwoRect.mnDestY = 0;
+ aTwoRect.mnSrcWidth = aTwoRect.mnDestWidth = aSize.Width();
+ aTwoRect.mnSrcHeight = aTwoRect.mnDestHeight = aSize.Height();
+ }
+ else if( aTwoRect.mnSrcWidth+aTwoRect.mnSrcX > aSize.Width() ||
+ aTwoRect.mnSrcHeight+aTwoRect.mnSrcY > aSize.Height() )
+ {
+ // #i47823# this should not happen at all, but does nonetheless
+ // because BitmapEx allows for mask bitmaps of different size
+ // than image bitmap (broken)
+ if( aTwoRect.mnSrcX >= aSize.Width() ||
+ aTwoRect.mnSrcY >= aSize.Height() )
+ return nullptr; // this would be a really mad case
+
+ if( aTwoRect.mnSrcWidth+aTwoRect.mnSrcX > aSize.Width() )
+ {
+ aTwoRect.mnSrcWidth = aSize.Width()-aTwoRect.mnSrcX;
+ if( aTwoRect.mnSrcWidth < 1 )
+ {
+ aTwoRect.mnSrcX = 0;
+ aTwoRect.mnSrcWidth = aSize.Width();
+ }
+ }
+ if( aTwoRect.mnSrcHeight+aTwoRect.mnSrcY > aSize.Height() )
+ {
+ aTwoRect.mnSrcHeight = aSize.Height() - aTwoRect.mnSrcY;
+ if( aTwoRect.mnSrcHeight < 1 )
+ {
+ aTwoRect.mnSrcY = 0;
+ aTwoRect.mnSrcHeight = aSize.Height();
+ }
+ }
+ }
+
+ XImage* pImage = ImplCreateXImage( vcl_sal::getSalDisplay(GetGenericUnixSalData()), nXScreen,
+ nDrawableDepth, aTwoRect );
+
+ if( pImage )
+ {
+ mpDDB.reset(new ImplSalDDB( pImage, aDrawable, nXScreen, aTwoRect ));
+ delete[] pImage->data;
+ pImage->data = nullptr;
+ XDestroyImage( pImage );
+
+ if( mpCache )
+ mpCache->ImplAdd( const_cast<X11SalBitmap*>(this) );
+ }
+ }
+
+ return mpDDB.get();
+}
+
+void X11SalBitmap::ImplDraw(
+ Drawable aDrawable,
+ SalX11Screen nXScreen,
+ long nDrawableDepth,
+ const SalTwoRect& rTwoRect,
+ const GC& rGC
+) const
+{
+ ImplGetDDB( aDrawable, nXScreen, nDrawableDepth, rTwoRect );
+ if( mpDDB )
+ mpDDB->ImplDraw( aDrawable, rTwoRect, rGC );
+}
+
+bool X11SalBitmap::Create( const Size& rSize, sal_uInt16 nBitCount, const BitmapPalette& rPal )
+{
+ Destroy();
+ mpDIB = ImplCreateDIB( rSize, nBitCount, rPal );
+
+ return( mpDIB != nullptr );
+}
+
+bool X11SalBitmap::Create( const SalBitmap& rSSalBmp )
+{
+ Destroy();
+
+ if( dynamic_cast<const X11SalBitmap*>( &rSSalBmp ) == nullptr )
+ return false;
+
+ const X11SalBitmap& rSalBmp = static_cast<const X11SalBitmap&>( rSSalBmp );
+
+ if( rSalBmp.mpDIB )
+ {
+ // TODO: reference counting...
+ mpDIB.reset(new BitmapBuffer( *rSalBmp.mpDIB ));
+ // TODO: get rid of this when BitmapBuffer gets copy constructor
+ try
+ {
+ mpDIB->mpBits = new sal_uInt8[ mpDIB->mnScanlineSize * mpDIB->mnHeight ];
+#if defined HAVE_VALGRIND_HEADERS
+ if (RUNNING_ON_VALGRIND)
+ blankExtraSpace(mpDIB.get());
+#endif
+ }
+ catch (const std::bad_alloc&)
+ {
+ mpDIB.reset();
+ }
+
+ if( mpDIB )
+ memcpy( mpDIB->mpBits, rSalBmp.mpDIB->mpBits, mpDIB->mnScanlineSize * mpDIB->mnHeight );
+ }
+ else if( rSalBmp.mpDDB )
+ ImplCreateFromDrawable( rSalBmp.mpDDB->ImplGetPixmap(),
+ rSalBmp.mpDDB->ImplGetScreen(),
+ rSalBmp.mpDDB->ImplGetDepth(),
+ 0, 0, rSalBmp.mpDDB->ImplGetWidth(), rSalBmp.mpDDB->ImplGetHeight() );
+
+ return( ( !rSalBmp.mpDIB && !rSalBmp.mpDDB ) ||
+ ( rSalBmp.mpDIB && ( mpDIB != nullptr ) ) ||
+ ( rSalBmp.mpDDB && ( mpDDB != nullptr ) ) );
+}
+
+bool X11SalBitmap::Create( const SalBitmap&, SalGraphics* )
+{
+ return false;
+}
+
+bool X11SalBitmap::Create( const SalBitmap&, sal_uInt16 )
+{
+ return false;
+}
+
+bool X11SalBitmap::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 ) {
+ sal_Int32 depth;
+ css::uno::Sequence< css::uno::Any > args;
+
+ if( xFastPropertySet->getFastPropertyValue(bMask ? 2 : 1) >>= args ) {
+ long pixmapHandle = {}; // spurious -Werror=maybe-uninitialized
+ if( ( args[1] >>= pixmapHandle ) && ( args[2] >>= depth ) ) {
+
+ mbGrey = bMask;
+ bool bSuccess = ImplCreateFromDrawable(
+ pixmapHandle,
+ // FIXME: this seems multi-screen broken to me
+ SalX11Screen( 0 ),
+ depth,
+ 0,
+ 0,
+ rSize.Width(),
+ rSize.Height()
+ );
+ bool bFreePixmap = false;
+ if( bSuccess && (args[0] >>= bFreePixmap) && bFreePixmap )
+ XFreePixmap( vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetDisplay(), pixmapHandle );
+
+ return bSuccess;
+ }
+ }
+ }
+
+ return false;
+}
+
+void X11SalBitmap::Destroy()
+{
+ if( mpDIB )
+ {
+ delete[] mpDIB->mpBits;
+ mpDIB.reset();
+ }
+
+ mpDDB.reset();
+
+ if( mpCache )
+ mpCache->ImplRemove( this );
+}
+
+Size X11SalBitmap::GetSize() const
+{
+ Size aSize;
+
+ if( mpDIB )
+ {
+ aSize.setWidth( mpDIB->mnWidth );
+ aSize.setHeight( mpDIB->mnHeight );
+ }
+ else if( mpDDB )
+ {
+ aSize.setWidth( mpDDB->ImplGetWidth() );
+ aSize.setHeight( mpDDB->ImplGetHeight() );
+ }
+
+ return aSize;
+}
+
+sal_uInt16 X11SalBitmap::GetBitCount() const
+{
+ sal_uInt16 nBitCount;
+
+ if( mpDIB )
+ nBitCount = mpDIB->mnBitCount;
+ else if( mpDDB )
+ nBitCount = mpDDB->ImplGetDepth();
+ else
+ nBitCount = 0;
+
+ return nBitCount;
+}
+
+BitmapBuffer* X11SalBitmap::AcquireBuffer( BitmapAccessMode /*nMode*/ )
+{
+ if( !mpDIB && mpDDB )
+ {
+ mpDIB = ImplCreateDIB(
+ mpDDB->ImplGetPixmap(),
+ mpDDB->ImplGetScreen(),
+ mpDDB->ImplGetDepth(),
+ 0, 0,
+ mpDDB->ImplGetWidth(),
+ mpDDB->ImplGetHeight(),
+ mbGrey
+ );
+ }
+
+ return mpDIB.get();
+}
+
+void X11SalBitmap::ReleaseBuffer( BitmapBuffer*, BitmapAccessMode nMode )
+{
+ if( nMode == BitmapAccessMode::Write )
+ {
+ mpDDB.reset();
+
+ if( mpCache )
+ mpCache->ImplRemove( this );
+ InvalidateChecksum();
+ }
+}
+
+bool X11SalBitmap::GetSystemData( BitmapSystemData& rData )
+{
+ if( mpDDB )
+ {
+ // Rename/retype pDummy to your likings (though X11 Pixmap is
+ // prolly not a good idea, since it's accessed from
+ // non-platform aware code in vcl/bitmap.hxx)
+ rData.aPixmap = reinterpret_cast<void*>(mpDDB->ImplGetPixmap());
+ rData.mnWidth = mpDDB->ImplGetWidth ();
+ rData.mnHeight = mpDDB->ImplGetHeight ();
+ return true;
+ }
+
+ return false;
+}
+
+bool X11SalBitmap::ScalingSupported() const
+{
+ return false;
+}
+
+bool X11SalBitmap::Scale( const double& /*rScaleX*/, const double& /*rScaleY*/, BmpScaleFlag /*nScaleFlag*/ )
+{
+ return false;
+}
+
+bool X11SalBitmap::Replace( const Color& /*rSearchColor*/, const Color& /*rReplaceColor*/, sal_uInt8 /*nTol*/ )
+{
+ return false;
+}
+
+
+ImplSalDDB::ImplSalDDB( XImage* pImage, Drawable aDrawable,
+ SalX11Screen nXScreen, const SalTwoRect& rTwoRect )
+ : maPixmap ( 0 )
+ , maTwoRect ( rTwoRect )
+ , mnDepth ( pImage->depth )
+ , mnXScreen ( nXScreen )
+{
+ SalDisplay* pSalDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData());
+ Display* pXDisp = pSalDisp->GetDisplay();
+
+ if( (maPixmap = limitXCreatePixmap( pXDisp, aDrawable, ImplGetWidth(), ImplGetHeight(), ImplGetDepth() )) )
+ {
+ XGCValues aValues;
+ GC aGC;
+ int nValues = GCFunction;
+
+ aValues.function = GXcopy;
+
+ if( 1 == mnDepth )
+ {
+ nValues |= ( GCForeground | GCBackground );
+ aValues.foreground = 1;
+ aValues.background = 0;
+ }
+
+ aGC = XCreateGC( pXDisp, maPixmap, nValues, &aValues );
+ XPutImage( pXDisp, maPixmap, aGC, pImage, 0, 0, 0, 0, maTwoRect.mnDestWidth, maTwoRect.mnDestHeight );
+ XFreeGC( pXDisp, aGC );
+ }
+}
+
+ImplSalDDB::ImplSalDDB(
+ Drawable aDrawable,
+ SalX11Screen nXScreen,
+ long nDrawableDepth,
+ long nX,
+ long nY,
+ long nWidth,
+ long nHeight
+) : maTwoRect(0, 0, nWidth, nHeight, 0, 0, nWidth, nHeight)
+ , mnDepth( nDrawableDepth )
+ , mnXScreen( nXScreen )
+{
+ SalDisplay* pSalDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData());
+ Display* pXDisp = pSalDisp->GetDisplay();
+
+ if( (maPixmap = limitXCreatePixmap( pXDisp, aDrawable, nWidth, nHeight, nDrawableDepth )) )
+ {
+ XGCValues aValues;
+ GC aGC;
+ int nValues = GCFunction;
+
+ aValues.function = GXcopy;
+
+ if( 1 == mnDepth )
+ {
+ nValues |= ( GCForeground | GCBackground );
+ aValues.foreground = 1;
+ aValues.background = 0;
+ }
+
+ aGC = XCreateGC( pXDisp, maPixmap, nValues, &aValues );
+ ImplDraw( aDrawable, nDrawableDepth, maPixmap,
+ nX, nY, nWidth, nHeight, 0, 0, aGC );
+ XFreeGC( pXDisp, aGC );
+ }
+ else
+ {
+ maTwoRect.mnSrcWidth = maTwoRect.mnDestWidth = 0;
+ maTwoRect.mnSrcHeight = maTwoRect.mnDestHeight = 0;
+ }
+}
+
+ImplSalDDB::~ImplSalDDB()
+{
+ if( maPixmap && ImplGetSVData() )
+ XFreePixmap( vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetDisplay(), maPixmap );
+}
+
+bool ImplSalDDB::ImplMatches( SalX11Screen nXScreen, long nDepth, const SalTwoRect& rTwoRect ) const
+{
+ bool bRet = false;
+
+ if( ( maPixmap != 0 ) && ( ( mnDepth == nDepth ) || ( 1 == mnDepth ) ) && nXScreen == mnXScreen)
+ {
+ if ( rTwoRect.mnSrcX == maTwoRect.mnSrcX
+ && rTwoRect.mnSrcY == maTwoRect.mnSrcY
+ && rTwoRect.mnSrcWidth == maTwoRect.mnSrcWidth
+ && rTwoRect.mnSrcHeight == maTwoRect.mnSrcHeight
+ && rTwoRect.mnDestWidth == maTwoRect.mnDestWidth
+ && rTwoRect.mnDestHeight == maTwoRect.mnDestHeight
+ )
+ {
+ // absolutely identically
+ bRet = true;
+ }
+ else if( rTwoRect.mnSrcWidth == rTwoRect.mnDestWidth
+ && rTwoRect.mnSrcHeight == rTwoRect.mnDestHeight
+ && maTwoRect.mnSrcWidth == maTwoRect.mnDestWidth
+ && maTwoRect.mnSrcHeight == maTwoRect.mnDestHeight
+ && rTwoRect.mnSrcX >= maTwoRect.mnSrcX
+ && rTwoRect.mnSrcY >= maTwoRect.mnSrcY
+ && ( rTwoRect.mnSrcX + rTwoRect.mnSrcWidth ) <= ( maTwoRect.mnSrcX + maTwoRect.mnSrcWidth )
+ && ( rTwoRect.mnSrcY + rTwoRect.mnSrcHeight ) <= ( maTwoRect.mnSrcY + maTwoRect.mnSrcHeight )
+ )
+ {
+ bRet = true;
+ }
+ }
+
+ return bRet;
+}
+
+void ImplSalDDB::ImplDraw(
+ Drawable aDrawable,
+ const SalTwoRect& rTwoRect,
+ const GC& rGC
+) const
+{
+ ImplDraw( maPixmap, mnDepth, aDrawable,
+ rTwoRect.mnSrcX - maTwoRect.mnSrcX, rTwoRect.mnSrcY - maTwoRect.mnSrcY,
+ rTwoRect.mnDestWidth, rTwoRect.mnDestHeight,
+ rTwoRect.mnDestX, rTwoRect.mnDestY, rGC );
+}
+
+void ImplSalDDB::ImplDraw(
+ Drawable aSrcDrawable,
+ long nSrcDrawableDepth,
+ Drawable aDstDrawable,
+ long nSrcX,
+ long nSrcY,
+ long nDestWidth,
+ long nDestHeight,
+ long nDestX,
+ long nDestY,
+ const GC& rGC
+) {
+ SalDisplay* pSalDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData());
+ Display* pXDisp = pSalDisp->GetDisplay();
+
+ if( 1 == nSrcDrawableDepth )
+ {
+ XCopyPlane( pXDisp, aSrcDrawable, aDstDrawable, rGC,
+ nSrcX, nSrcY, nDestWidth, nDestHeight, nDestX, nDestY, 1 );
+ }
+ else
+ {
+ XCopyArea( pXDisp, aSrcDrawable, aDstDrawable, rGC,
+ nSrcX, nSrcY, nDestWidth, nDestHeight, nDestX, nDestY );
+ }
+}
+
+
+ImplSalBitmapCache::ImplSalBitmapCache()
+{
+}
+
+ImplSalBitmapCache::~ImplSalBitmapCache()
+{
+ ImplClear();
+}
+
+void ImplSalBitmapCache::ImplAdd( X11SalBitmap* pBmp )
+{
+ for(auto pObj : maBmpList)
+ {
+ if( pObj == pBmp )
+ return;
+ }
+ maBmpList.push_back( pBmp );
+}
+
+void ImplSalBitmapCache::ImplRemove( X11SalBitmap const * pBmp )
+{
+ auto it = std::find(maBmpList.begin(), maBmpList.end(), pBmp);
+ if( it != maBmpList.end() )
+ {
+ (*it)->ImplRemovedFromCache();
+ maBmpList.erase( it );
+ }
+}
+
+void ImplSalBitmapCache::ImplClear()
+{
+ for(auto pObj : maBmpList)
+ {
+ pObj->ImplRemovedFromCache();
+ }
+ maBmpList.clear();
+}
+
+/* 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 000000000..3ecbe013a
--- /dev/null
+++ b/vcl/unx/generic/gdi/salgdi.cxx
@@ -0,0 +1,799 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/Xutil.h>
+#include <X11/extensions/Xrender.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 "gdiimpl.hxx"
+#include <opengl/x11/gdiimpl.hxx>
+#include <unx/x11/x11cairotextrender.hxx>
+#include <opengl/x11/cairotextrender.hxx>
+
+#include <unx/x11/xrender_peer.hxx>
+#include "cairo_xlib_cairo.hxx"
+#include <cairo-xlib.h>
+
+#include <vcl/opengl/OpenGLHelper.hxx>
+
+#include <config_features.h>
+#include <vcl/skia/SkiaHelper.hxx>
+#if HAVE_FEATURE_SKIA
+#include <skia/x11/gdiimpl.hxx>
+#include <skia/x11/textrender.hxx>
+#endif
+
+X11SalGraphics::X11SalGraphics():
+ m_pFrame(nullptr),
+ m_pVDev(nullptr),
+ m_pColormap(nullptr),
+ hDrawable_(None),
+ m_nXScreen( 0 ),
+ m_pXRenderFormat(nullptr),
+ m_aXRenderPicture(0),
+ mpClipRegion(nullptr),
+#if ENABLE_CAIRO_CANVAS
+ maClipRegion(),
+ mnPenColor(SALCOLOR_NONE),
+ mnFillColor(SALCOLOR_NONE),
+#endif // ENABLE_CAIRO_CANVAS
+ nTextPixel_(0),
+ hBrush_(None),
+ bWindow_(false),
+ bVirDev_(false),
+ m_bOpenGL(OpenGLHelper::isVCLOpenGLEnabled()),
+ m_bSkia(SkiaHelper::isVCLSkiaEnabled())
+{
+#if HAVE_FEATURE_SKIA
+ if (m_bSkia)
+ {
+ mxImpl.reset(new X11SkiaSalGraphicsImpl(*this));
+ mxTextRenderImpl.reset(new SkiaTextRender);
+ }
+ else
+#endif
+ if (m_bOpenGL)
+ {
+ mxImpl.reset(new X11OpenGLSalGraphicsImpl(*this));
+ mxTextRenderImpl.reset(new OpenGLX11CairoTextRender(*this));
+ }
+ else
+ {
+ mxTextRenderImpl.reset(new X11CairoTextRender(*this));
+ mxImpl.reset(new X11SalGraphicsImpl(*this));
+ }
+
+}
+
+X11SalGraphics::~X11SalGraphics() COVERITY_NOEXCEPT_FALSE
+{
+ DeInit();
+ ReleaseFonts();
+ freeResources();
+}
+
+void X11SalGraphics::freeResources()
+{
+ Display *pDisplay = GetXDisplay();
+
+ if( mpClipRegion )
+ {
+ XDestroyRegion( mpClipRegion );
+ mpClipRegion = None;
+ }
+
+ mxImpl->freeResources();
+
+ if( hBrush_ )
+ {
+ XFreePixmap( pDisplay, hBrush_ );
+ hBrush_ = None;
+ }
+ if( m_pDeleteColormap )
+ {
+ m_pDeleteColormap.reset();
+ m_pColormap = nullptr;
+ }
+ if( m_aXRenderPicture )
+ {
+ XRenderPeer::GetInstance().FreePicture( m_aXRenderPicture );
+ m_aXRenderPicture = 0;
+ }
+}
+
+SalGraphicsImpl* X11SalGraphics::GetImpl() const
+{
+ return mxImpl.get();
+}
+
+void X11SalGraphics::SetDrawable( Drawable aDrawable, SalX11Screen nXScreen )
+{
+ // shortcut if nothing changed
+ if( hDrawable_ == aDrawable )
+ return;
+
+ // free screen specific resources if needed
+ if( nXScreen != m_nXScreen )
+ {
+ freeResources();
+ m_pColormap = &vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetColormap( nXScreen );
+ m_nXScreen = nXScreen;
+ }
+
+ hDrawable_ = aDrawable;
+ SetXRenderFormat( nullptr );
+ if( m_aXRenderPicture )
+ {
+ XRenderPeer::GetInstance().FreePicture( m_aXRenderPicture );
+ m_aXRenderPicture = 0;
+ }
+
+ // TODO: moggi: FIXME nTextPixel_ = GetPixel( nTextColor_ );
+}
+
+void X11SalGraphics::Init( SalFrame *pFrame, Drawable aTarget,
+ SalX11Screen nXScreen )
+{
+ m_pColormap = &vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetColormap(nXScreen);
+ m_nXScreen = nXScreen;
+
+ m_pFrame = pFrame;
+ m_pVDev = nullptr;
+
+ bWindow_ = true;
+ bVirDev_ = false;
+
+ SetDrawable( aTarget, nXScreen );
+ mxImpl->Init();
+}
+
+void X11SalGraphics::DeInit()
+{
+ mxImpl->DeInit();
+ SetDrawable( None, m_nXScreen );
+}
+
+void X11SalGraphics::SetClipRegion( GC pGC, Region pXReg ) const
+{
+ Display *pDisplay = GetXDisplay();
+
+ int n = 0;
+ Region Regions[3];
+
+ if( mpClipRegion )
+ Regions[n++] = mpClipRegion;
+
+ if( pXReg && !XEmptyRegion( pXReg ) )
+ Regions[n++] = pXReg;
+
+ if( 0 == n )
+ XSetClipMask( pDisplay, pGC, None );
+ else if( 1 == n )
+ XSetRegion( pDisplay, pGC, Regions[0] );
+ else
+ {
+ Region pTmpRegion = XCreateRegion();
+ XIntersectRegion( Regions[0], Regions[1], pTmpRegion );
+
+ XSetRegion( pDisplay, pGC, pTmpRegion );
+ XDestroyRegion( pTmpRegion );
+ }
+}
+
+// Calculate a dither-pixmap and make a brush of it
+#define P_DELTA 51
+#define DMAP( v, m ) ((v % P_DELTA) > m ? (v / P_DELTA) + 1 : (v / P_DELTA))
+
+bool X11SalGraphics::GetDitherPixmap( Color nColor )
+{
+ static const short nOrdDither8Bit[ 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}
+ };
+
+ // test for correct depth (8bit)
+ if( GetColormap().GetVisual().GetDepth() != 8 )
+ return false;
+
+ char pBits[64];
+ char *pBitsPtr = pBits;
+
+ // Set the palette-entries for the dithering tile
+ sal_uInt8 nColorRed = nColor.GetRed();
+ sal_uInt8 nColorGreen = nColor.GetGreen();
+ sal_uInt8 nColorBlue = nColor.GetBlue();
+
+ for(auto & nY : nOrdDither8Bit)
+ {
+ for( int nX = 0; nX < 8; nX++ )
+ {
+ short nMagic = nY[nX];
+ sal_uInt8 nR = P_DELTA * DMAP( nColorRed, nMagic );
+ sal_uInt8 nG = P_DELTA * DMAP( nColorGreen, nMagic );
+ sal_uInt8 nB = P_DELTA * DMAP( nColorBlue, nMagic );
+
+ *pBitsPtr++ = GetColormap().GetPixel( Color( nR, nG, nB ) );
+ }
+ }
+
+ // create the tile as ximage and an according pixmap -> caching
+ XImage *pImage = XCreateImage( GetXDisplay(),
+ GetColormap().GetXVisual(),
+ 8,
+ ZPixmap,
+ 0, // offset
+ pBits, // data
+ 8, 8, // width & height
+ 8, // bitmap_pad
+ 0 ); // (default) bytes_per_line
+
+ if( !hBrush_ )
+ hBrush_ = limitXCreatePixmap( GetXDisplay(), GetDrawable(), 8, 8, 8 );
+
+ // put the ximage to the pixmap
+ XPutImage( GetXDisplay(),
+ hBrush_,
+ GetDisplay()->GetCopyGC( m_nXScreen ),
+ pImage,
+ 0, 0, // Source
+ 0, 0, // Destination
+ 8, 8 ); // width & height
+
+ // destroy image-frame but not palette-data
+ pImage->data = nullptr;
+ XDestroyImage( pImage );
+
+ return true;
+}
+
+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 )
+ {
+ // 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
+ }
+}
+
+sal_uInt16 X11SalGraphics::GetBitCount() const
+{
+ return mxImpl->GetBitCount();
+}
+
+long X11SalGraphics::GetGraphicsWidth() const
+{
+ return mxImpl->GetGraphicsWidth();
+}
+
+void X11SalGraphics::ResetClipRegion()
+{
+#if ENABLE_CAIRO_CANVAS
+ maClipRegion.SetNull();
+#endif
+ mxImpl->ResetClipRegion();
+}
+
+bool X11SalGraphics::setClipRegion( const vcl::Region& i_rClip )
+{
+#if ENABLE_CAIRO_CANVAS
+ maClipRegion = i_rClip;
+#endif
+ return mxImpl->setClipRegion( i_rClip );
+}
+
+void X11SalGraphics::SetLineColor()
+{
+#if ENABLE_CAIRO_CANVAS
+ mnPenColor = SALCOLOR_NONE;
+#endif // ENABLE_CAIRO_CANVAS
+
+ mxImpl->SetLineColor();
+}
+
+void X11SalGraphics::SetLineColor( Color nColor )
+{
+#if ENABLE_CAIRO_CANVAS
+ mnPenColor = nColor;
+#endif // ENABLE_CAIRO_CANVAS
+
+ mxImpl->SetLineColor( nColor );
+}
+
+void X11SalGraphics::SetFillColor()
+{
+#if ENABLE_CAIRO_CANVAS
+ mnFillColor = SALCOLOR_NONE;
+#endif // ENABLE_CAIRO_CANVAS
+
+ mxImpl->SetFillColor();
+}
+
+void X11SalGraphics::SetFillColor( Color nColor )
+{
+#if ENABLE_CAIRO_CANVAS
+ mnFillColor = nColor;
+#endif // ENABLE_CAIRO_CANVAS
+
+ mxImpl->SetFillColor( nColor );
+}
+
+void X11SalGraphics::SetROPLineColor( SalROPColor nROPColor )
+{
+ mxImpl->SetROPLineColor( nROPColor );
+}
+
+void X11SalGraphics::SetROPFillColor( SalROPColor nROPColor )
+{
+ mxImpl->SetROPFillColor( nROPColor );
+}
+
+void X11SalGraphics::SetXORMode( bool bSet, bool bInvertOnly )
+{
+ mxImpl->SetXORMode( bSet, bInvertOnly );
+}
+
+void X11SalGraphics::drawPixel( long nX, long nY )
+{
+ mxImpl->drawPixel( nX, nY );
+}
+
+void X11SalGraphics::drawPixel( long nX, long nY, Color nColor )
+{
+ mxImpl->drawPixel( nX, nY, nColor );
+}
+
+void X11SalGraphics::drawLine( long nX1, long nY1, long nX2, long nY2 )
+{
+ mxImpl->drawLine( nX1, nY1, nX2, nY2 );
+}
+
+void X11SalGraphics::drawRect( long nX, long nY, long nDX, long nDY )
+{
+ mxImpl->drawRect( nX, nY, nDX, nDY );
+}
+
+void X11SalGraphics::drawPolyLine( sal_uInt32 nPoints, const SalPoint *pPtAry )
+{
+ mxImpl->drawPolyLine( nPoints, pPtAry );
+}
+
+void X11SalGraphics::drawPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry )
+{
+ mxImpl->drawPolygon( nPoints, pPtAry );
+}
+
+void X11SalGraphics::drawPolyPolygon( sal_uInt32 nPoly,
+ const sal_uInt32 *pPoints,
+ PCONSTSALPOINT *pPtAry )
+{
+ mxImpl->drawPolyPolygon( nPoly, pPoints, pPtAry );
+}
+
+bool X11SalGraphics::drawPolyLineBezier( sal_uInt32 nPoints, const SalPoint* pPtAry, const PolyFlags* pFlgAry )
+{
+ return mxImpl->drawPolyLineBezier( nPoints, pPtAry, pFlgAry );
+}
+
+bool X11SalGraphics::drawPolygonBezier( sal_uInt32 nPoints, const SalPoint* pPtAry, const PolyFlags* pFlgAry )
+{
+ return mxImpl->drawPolygonBezier( nPoints, pPtAry, pFlgAry );
+}
+
+bool X11SalGraphics::drawPolyPolygonBezier( sal_uInt32 nPoints, const sal_uInt32* pPoints,
+ const SalPoint* const* pPtAry, const PolyFlags* const* pFlgAry)
+{
+ return mxImpl->drawPolyPolygonBezier( nPoints, pPoints, pPtAry, pFlgAry );
+}
+
+void X11SalGraphics::invert( sal_uInt32 nPoints,
+ const SalPoint* pPtAry,
+ SalInvert nFlags )
+{
+ mxImpl->invert( nPoints, pPtAry, nFlags );
+}
+
+bool X11SalGraphics::drawEPS( long nX, long nY, long nWidth,
+ long nHeight, void* pPtr, sal_uInt32 nSize )
+{
+ return mxImpl->drawEPS( nX, nY, nWidth, nHeight, pPtr, nSize );
+}
+
+XRenderPictFormat* X11SalGraphics::GetXRenderFormat() const
+{
+ if( m_pXRenderFormat == nullptr )
+ m_pXRenderFormat = XRenderPeer::GetInstance().FindVisualFormat( GetVisual().visual );
+ return m_pXRenderFormat;
+}
+
+SystemGraphicsData X11SalGraphics::GetGraphicsData() const
+{
+ SystemGraphicsData aRes;
+
+ aRes.nSize = sizeof(aRes);
+ aRes.pDisplay = GetXDisplay();
+ aRes.hDrawable = hDrawable_;
+ aRes.pVisual = GetVisual().visual;
+ aRes.nScreen = m_nXScreen.getXScreen();
+ aRes.pXRenderFormat = m_pXRenderFormat;
+ return aRes;
+}
+
+#if ENABLE_CAIRO_CANVAS
+
+bool X11SalGraphics::SupportsCairo() const
+{
+ Display *pDisplay = GetXDisplay();
+ int nDummy;
+ return XQueryExtension(pDisplay, "RENDER", &nDummy, &nDummy, &nDummy);
+}
+
+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);
+ }
+
+ 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(static_cast<const vcl::Window&>(rRefDevice)),
+ 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(static_cast<const vcl::Window&>(rRefDevice)), 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( 3 );
+ args[0] <<= false; // do not call XFreePixmap on it
+ args[1] <<= rXlibSurface.getPixmap()->mhDrawable;
+ args[2] <<= sal_Int32( rXlibSurface.getDepth() );
+ return css::uno::Any(args);
+}
+
+#endif // ENABLE_CAIRO_CANVAS
+
+// draw a poly-polygon
+bool X11SalGraphics::drawPolyPolygon(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon& rPolyPolygon,
+ double fTransparency)
+{
+ if(fTransparency >= 1.0)
+ {
+ return true;
+ }
+
+ if(rPolyPolygon.count() == 0)
+ {
+ return true;
+ }
+
+#if ENABLE_CAIRO_CANVAS
+ // Fallback: Transform to DeviceCoordinates
+ basegfx::B2DPolyPolygon aPolyPolygon(rPolyPolygon);
+ aPolyPolygon.transform(rObjectToDevice);
+
+ if(SALCOLOR_NONE == mnFillColor && SALCOLOR_NONE == mnPenColor)
+ {
+ return true;
+ }
+
+ // enable by setting to something
+ static const char* pUseCairoForPolygons(getenv("SAL_ENABLE_USE_CAIRO_FOR_POLYGONS"));
+
+ if (!m_bOpenGL && !m_bSkia && nullptr != pUseCairoForPolygons && SupportsCairo())
+ {
+ // snap to raster if requested
+ const bool bSnapPoints(!getAntiAliasB2DDraw());
+
+ if(bSnapPoints)
+ {
+ aPolyPolygon = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyPolygon);
+ }
+
+ cairo_t* cr = getCairoContext();
+ clipRegion(cr);
+
+ for(auto const& rPolygon : aPolyPolygon)
+ {
+ const sal_uInt32 nPointCount(rPolygon.count());
+
+ if(nPointCount)
+ {
+ const sal_uInt32 nEdgeCount(rPolygon.isClosed() ? nPointCount : nPointCount - 1);
+
+ if(nEdgeCount)
+ {
+ basegfx::B2DCubicBezier aEdge;
+
+ for(sal_uInt32 b = 0; b < nEdgeCount; ++b)
+ {
+ rPolygon.getBezierSegment(b, aEdge);
+
+ if(!b)
+ {
+ const basegfx::B2DPoint aStart(aEdge.getStartPoint());
+ cairo_move_to(cr, aStart.getX(), aStart.getY());
+ }
+
+ const basegfx::B2DPoint aEnd(aEdge.getEndPoint());
+
+ if(aEdge.isBezier())
+ {
+ const basegfx::B2DPoint aCP1(aEdge.getControlPointA());
+ const basegfx::B2DPoint aCP2(aEdge.getControlPointB());
+ cairo_curve_to(cr,
+ aCP1.getX(), aCP1.getY(),
+ aCP2.getX(), aCP2.getY(),
+ aEnd.getX(), aEnd.getY());
+ }
+ else
+ {
+ cairo_line_to(cr, aEnd.getX(), aEnd.getY());
+ }
+ }
+
+ cairo_close_path(cr);
+ }
+ }
+ }
+
+ if(SALCOLOR_NONE != mnFillColor)
+ {
+ cairo_set_source_rgba(cr,
+ mnFillColor.GetRed()/255.0,
+ mnFillColor.GetGreen()/255.0,
+ mnFillColor.GetBlue()/255.0,
+ 1.0 - fTransparency);
+ cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
+ cairo_fill_preserve(cr);
+ }
+
+ if(SALCOLOR_NONE != mnPenColor)
+ {
+ cairo_set_source_rgba(cr,
+ mnPenColor.GetRed()/255.0,
+ mnPenColor.GetGreen()/255.0,
+ mnPenColor.GetBlue()/255.0,
+ 1.0 - fTransparency);
+ cairo_stroke_preserve(cr);
+ }
+
+ releaseCairoContext(cr);
+ return true;
+ }
+#endif // ENABLE_CAIRO_CANVAS
+
+ return mxImpl->drawPolyPolygon(
+ rObjectToDevice,
+ rPolyPolygon,
+ fTransparency);
+}
+
+#if ENABLE_CAIRO_CANVAS
+void X11SalGraphics::clipRegion(cairo_t* cr)
+{
+ SvpSalGraphics::clipRegion(cr, maClipRegion);
+}
+#endif // ENABLE_CAIRO_CANVAS
+
+bool X11SalGraphics::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)
+{
+ if(0 == rPolygon.count())
+ {
+ return true;
+ }
+
+ if(fTransparency >= 1.0)
+ {
+ return true;
+ }
+
+#if ENABLE_CAIRO_CANVAS
+ // disable by setting to something
+ static const char* pUseCairoForFatLines(getenv("SAL_DISABLE_USE_CAIRO_FOR_FATLINES"));
+
+ if (!m_bOpenGL && !m_bSkia && nullptr == pUseCairoForFatLines && SupportsCairo())
+ {
+ cairo_t* cr = getCairoContext();
+ clipRegion(cr);
+
+ // Use the now available static drawPolyLine from the Cairo-Headless-Fallback
+ // that will take care of all needed stuff
+ const bool bRetval(
+ SvpSalGraphics::drawPolyLine(
+ cr,
+ nullptr,
+ mnPenColor,
+ getAntiAliasB2DDraw(),
+ rObjectToDevice,
+ rPolygon,
+ fTransparency,
+ fLineWidth,
+ pStroke, // MM01
+ eLineJoin,
+ eLineCap,
+ fMiterMinimumAngle,
+ bPixelSnapHairline));
+
+ releaseCairoContext(cr);
+
+ if(bRetval)
+ {
+ return true;
+ }
+ }
+#endif // ENABLE_CAIRO_CANVAS
+
+ return mxImpl->drawPolyLine(
+ rObjectToDevice,
+ rPolygon,
+ fTransparency,
+ fLineWidth,
+ pStroke, // MM01
+ eLineJoin,
+ eLineCap,
+ fMiterMinimumAngle,
+ bPixelSnapHairline);
+}
+
+bool X11SalGraphics::drawGradient(const tools::PolyPolygon& rPoly, const Gradient& rGradient)
+{
+ return mxImpl->drawGradient(rPoly, rGradient);
+}
+
+SalGeometryProvider *X11SalGraphics::GetGeometryProvider() const
+{
+ if (m_pFrame)
+ return static_cast< SalGeometryProvider * >(m_pFrame);
+ else
+ return static_cast< SalGeometryProvider * >(m_pVDev);
+}
+
+cairo_t* X11SalGraphics::getCairoContext()
+{
+ cairo_surface_t* surface = cairo_xlib_surface_create(GetXDisplay(), hDrawable_,
+ GetVisual().visual, SAL_MAX_INT16, SAL_MAX_INT16);
+
+ cairo_t *cr = cairo_create(surface);
+ cairo_surface_destroy(surface);
+
+ return cr;
+}
+
+void X11SalGraphics::releaseCairoContext(cairo_t* cr)
+{
+ cairo_destroy(cr);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/gdi/salgdi2.cxx b/vcl/unx/generic/gdi/salgdi2.cxx
new file mode 100644
index 000000000..768562bfb
--- /dev/null
+++ b/vcl/unx/generic/gdi/salgdi2.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 .
+ */
+
+#include <salgdiimpl.hxx>
+
+#include <vcl/sysdata.hxx>
+
+#include <unx/saldisp.hxx>
+#include <unx/salgdi.h>
+#include <unx/x11/xrender_peer.hxx>
+#include <salframe.hxx>
+
+extern "C"
+{
+ static Bool GraphicsExposePredicate( Display*, XEvent* pEvent, const XPointer pFrameWindow )
+ {
+ Bool bRet = False;
+ if( (pEvent->type == GraphicsExpose || pEvent->type == NoExpose) &&
+ pEvent->xnoexpose.drawable == reinterpret_cast<Drawable>(pFrameWindow) )
+ {
+ bRet = True;
+ }
+ return bRet;
+ }
+}
+
+void X11SalGraphics::YieldGraphicsExpose()
+{
+ // get frame if necessary
+ SalFrame* pFrame = m_pFrame;
+ Display* pDisplay = GetXDisplay();
+ ::Window aWindow = GetDrawable();
+ if( ! pFrame )
+ {
+ for (auto pSalFrame : vcl_sal::getSalDisplay(GetGenericUnixSalData())->getFrames() )
+ {
+ const SystemEnvData* pEnvData = pSalFrame->GetSystemData();
+ if( Drawable(pEnvData->aWindow) == aWindow )
+ {
+ pFrame = pSalFrame;
+ break;
+ }
+ }
+ if( ! pFrame )
+ return;
+ }
+
+ XEvent aEvent;
+ while( XCheckTypedWindowEvent( pDisplay, aWindow, Expose, &aEvent ) )
+ {
+ SalPaintEvent aPEvt( aEvent.xexpose.x, aEvent.xexpose.y, aEvent.xexpose.width+1, aEvent.xexpose.height+1 );
+ pFrame->CallCallback( SalEvent::Paint, &aPEvt );
+ }
+
+ do
+ {
+ if( ! GetDisplay()->XIfEventWithTimeout( &aEvent, reinterpret_cast<XPointer>(aWindow), GraphicsExposePredicate ) )
+ // this should not happen at all; still sometimes it happens
+ break;
+
+ if( aEvent.type == NoExpose )
+ break;
+
+ if( pFrame )
+ {
+ SalPaintEvent aPEvt( aEvent.xgraphicsexpose.x, aEvent.xgraphicsexpose.y, aEvent.xgraphicsexpose.width+1, aEvent.xgraphicsexpose.height+1 );
+ pFrame->CallCallback( SalEvent::Paint, &aPEvt );
+ }
+ } while( aEvent.xgraphicsexpose.count != 0 );
+}
+
+void X11SalGraphics::copyBits( const SalTwoRect& rPosAry,
+ SalGraphics *pSSrcGraphics )
+{
+ mxImpl->copyBits( rPosAry, pSSrcGraphics );
+}
+
+void X11SalGraphics::copyArea ( long nDestX, long nDestY,
+ long nSrcX, long nSrcY,
+ long nSrcWidth, long nSrcHeight,
+ bool bWindowInvalidate)
+{
+ mxImpl->copyArea( nDestX, nDestY, nSrcX, nSrcY, nSrcWidth, nSrcHeight, bWindowInvalidate );
+}
+
+bool X11SalGraphics::blendBitmap( const SalTwoRect& rTR,
+ const SalBitmap& rBitmap )
+{
+ return mxImpl->blendBitmap( rTR, rBitmap );
+}
+
+bool X11SalGraphics::blendAlphaBitmap( const SalTwoRect& rTR,
+ const SalBitmap& rSrcBitmap,
+ const SalBitmap& rMaskBitmap,
+ const SalBitmap& rAlphaBitmap )
+{
+ return mxImpl->blendAlphaBitmap( rTR, rSrcBitmap, rMaskBitmap, rAlphaBitmap );
+}
+
+void X11SalGraphics::drawBitmap( const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap )
+{
+ mxImpl->drawBitmap( rPosAry, rSalBitmap );
+}
+
+void X11SalGraphics::drawBitmap( const SalTwoRect& rPosAry,
+ const SalBitmap& rSrcBitmap,
+ const SalBitmap& rMaskBitmap )
+{
+ mxImpl->drawBitmap( rPosAry, rSrcBitmap, rMaskBitmap );
+}
+
+bool X11SalGraphics::drawAlphaBitmap( const SalTwoRect& rTR,
+ const SalBitmap& rSrcBitmap, const SalBitmap& rAlphaBmp )
+{
+ return mxImpl->drawAlphaBitmap( rTR, rSrcBitmap, rAlphaBmp );
+}
+
+bool X11SalGraphics::drawTransformedBitmap(
+ const basegfx::B2DPoint& rNull,
+ const basegfx::B2DPoint& rX,
+ const basegfx::B2DPoint& rY,
+ const SalBitmap& rSourceBitmap,
+ const SalBitmap* pAlphaBitmap)
+{
+ return mxImpl->drawTransformedBitmap( rNull, rX, rY, rSourceBitmap, pAlphaBitmap );
+}
+
+bool X11SalGraphics::drawAlphaRect( long nX, long nY, long nWidth,
+ long nHeight, sal_uInt8 nTransparency )
+{
+ return mxImpl->drawAlphaRect( nX, nY, nWidth, nHeight, nTransparency );
+}
+
+void X11SalGraphics::drawMask( const SalTwoRect& rPosAry,
+ const SalBitmap &rSalBitmap,
+ Color nMaskColor )
+{
+ mxImpl->drawMask( rPosAry, rSalBitmap, nMaskColor );
+}
+
+std::shared_ptr<SalBitmap> X11SalGraphics::getBitmap( long nX, long nY, long nDX, long nDY )
+{
+ return mxImpl->getBitmap( nX, nY, nDX, nDY );
+}
+
+Color X11SalGraphics::getPixel( long nX, long nY )
+{
+ return mxImpl->getPixel( nX, nY );
+}
+
+void X11SalGraphics::invert( long nX,
+ long nY,
+ long nDX,
+ long nDY,
+ SalInvert nFlags )
+{
+ mxImpl->invert( nX, nY, nDX, nDY, nFlags );
+}
+
+bool X11SalGraphics::supportsOperation( OutDevSupportType eType ) const
+{
+ return mxImpl->supportsOperation(eType);
+}
+
+/* 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 000000000..74de1bade
--- /dev/null
+++ b/vcl/unx/generic/gdi/salvd.cxx
@@ -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 .
+ */
+
+#include <vcl/sysdata.hxx>
+
+#include <X11/Xlib.h>
+#include <X11/extensions/Xrender.h>
+
+#include <unx/saldisp.hxx>
+#include <unx/salinst.h>
+#include <unx/salgdi.h>
+#include <unx/salvd.h>
+#include <unx/x11/xlimits.hxx>
+
+#include <vcl/opengl/OpenGLHelper.hxx>
+#include <opengl/x11/salvd.hxx>
+
+#include <config_features.h>
+#include <vcl/skia/SkiaHelper.hxx>
+#if HAVE_FEATURE_SKIA
+#include <skia/x11/salvd.hxx>
+#endif
+
+std::unique_ptr<SalVirtualDevice> X11SalInstance::CreateX11VirtualDevice(SalGraphics const * pGraphics,
+ long &nDX, 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( pGraphics, nDX, nDY, pData, std::move(pNewGraphics) ));
+ else
+#endif
+ if (OpenGLHelper::isVCLOpenGLEnabled())
+ return std::unique_ptr<SalVirtualDevice>(new X11OpenGLSalVirtualDevice( pGraphics, nDX, nDY, pData, std::move(pNewGraphics) ));
+ else
+ return std::unique_ptr<SalVirtualDevice>(new X11SalVirtualDevice(pGraphics, nDX, nDY, eFormat, pData, std::move(pNewGraphics)));
+}
+
+std::unique_ptr<SalVirtualDevice> X11SalInstance::CreateVirtualDevice(SalGraphics* pGraphics,
+ long &nDX, long &nDY, DeviceFormat eFormat, const SystemGraphicsData *pData)
+{
+ return CreateX11VirtualDevice(pGraphics, 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 )
+ {
+ m_pColormap = pColormap;
+ if( bDeleteColormap )
+ m_pDeleteColormap.reset(pColormap);
+ }
+ else if( nDeviceDepth == nVisualDepth )
+ m_pColormap = &pDisplay->GetColormap( m_nXScreen );
+ else if( nDeviceDepth == 1 )
+ {
+ m_pDeleteColormap.reset(new SalColormap());
+ m_pColormap = m_pDeleteColormap.get();
+ }
+
+ m_pVDev = pDevice;
+ m_pFrame = nullptr;
+
+ bWindow_ = pDisplay->IsDisplay();
+ bVirDev_ = true;
+
+ const Drawable aVdevDrawable = pDevice->GetDrawable();
+ SetDrawable( aVdevDrawable, m_nXScreen );
+ mxImpl->Init();
+}
+
+X11SalVirtualDevice::X11SalVirtualDevice(SalGraphics const * pGraphics, long &nDX, 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;
+ switch (eFormat)
+ {
+ case DeviceFormat::BITMASK:
+ nBitCount = 1;
+ break;
+ default:
+ nBitCount = pGraphics->GetBitCount();
+ break;
+
+ }
+
+ 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<long>(w);
+ nDY_ = static_cast<long>(h);
+ nDX = nDX_;
+ nDY = nDY_;
+ m_nXScreen = SalX11Screen( nScreen );
+ hDrawable_ = pData->hDrawable;
+ bExternPixmap_ = true;
+ }
+ else
+ {
+ nDX_ = nDX;
+ nDY_ = nDY;
+ m_nXScreen = pGraphics ? static_cast<X11SalGraphics const *>(pGraphics)->GetScreenNumber() :
+ vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetDefaultXScreen();
+ hDrawable_ = limitXCreatePixmap( GetXDisplay(),
+ pDisplay_->GetDrawable( m_nXScreen ),
+ nDX_, nDY_,
+ GetDepth() );
+ bExternPixmap_ = false;
+ }
+
+ XRenderPictFormat* pXRenderFormat = pData ? static_cast<XRenderPictFormat*>(pData->pXRenderFormat) : nullptr;
+ if( pXRenderFormat )
+ {
+ pGraphics_->SetXRenderFormat( pXRenderFormat );
+ if( pXRenderFormat->colormap )
+ pColormap = new SalColormap( pDisplay_, pXRenderFormat->colormap, m_nXScreen );
+ else
+ pColormap = new SalColormap( nBitCount );
+ bDeleteColormap = true;
+ }
+ else 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()
+ pGraphics_->Init( this, pColormap, bDeleteColormap );
+}
+
+X11SalVirtualDevice::~X11SalVirtualDevice()
+{
+ pGraphics_.reset();
+
+ 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( long nDX, long nDY )
+{
+ if( bExternPixmap_ )
+ return false;
+
+ if( !nDX ) nDX = 1;
+ if( !nDY ) nDY = 1;
+
+ 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;
+ }
+ return false;
+ }
+
+ if( GetDrawable() )
+ XFreePixmap( GetXDisplay(), GetDrawable() );
+ hDrawable_ = h;
+
+ nDX_ = nDX;
+ nDY_ = nDY;
+
+ if( pGraphics_ )
+ pGraphics_->Init( this );
+
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/gdi/x11cairotextrender.cxx b/vcl/unx/generic/gdi/x11cairotextrender.cxx
new file mode 100644
index 000000000..91f82ac84
--- /dev/null
+++ b/vcl/unx/generic/gdi/x11cairotextrender.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 <unx/x11/x11cairotextrender.hxx>
+
+#include <unx/glyphcache.hxx>
+#include <X11/Xregion.h>
+#include <cairo.h>
+#include <salframe.hxx>
+#include <salvd.hxx>
+
+X11CairoTextRender::X11CairoTextRender(X11SalGraphics& rParent)
+ : mrParent(rParent)
+{
+}
+
+cairo_t* X11CairoTextRender::getCairoContext()
+{
+ return mrParent.getCairoContext();
+}
+
+void X11CairoTextRender::getSurfaceOffset( double& nDX, double& nDY )
+{
+ nDX = 0;
+ nDY = 0;
+}
+
+void X11CairoTextRender::clipRegion(cairo_t* cr)
+{
+ Region pClipRegion = mrParent.mpClipRegion;
+ if( pClipRegion && !XEmptyRegion( pClipRegion ) )
+ {
+ for (long i = 0; i < pClipRegion->numRects; ++i)
+ {
+ cairo_rectangle(cr,
+ pClipRegion->rects[i].x1,
+ pClipRegion->rects[i].y1,
+ pClipRegion->rects[i].x2 - pClipRegion->rects[i].x1,
+ pClipRegion->rects[i].y2 - pClipRegion->rects[i].y1);
+ }
+ cairo_clip(cr);
+ }
+}
+
+size_t X11CairoTextRender::GetWidth() const
+{
+ SalGeometryProvider *pProvider = mrParent.m_pFrame;
+ if( !pProvider )
+ pProvider = mrParent.m_pVDev;
+
+ if( pProvider )
+ return pProvider->GetWidth();
+ else
+ return 1;
+}
+
+size_t X11CairoTextRender::GetHeight() const
+{
+ SalGeometryProvider *pProvider = mrParent.m_pFrame;
+ if( !pProvider )
+ pProvider = mrParent.m_pVDev;
+
+ if( pProvider )
+ return pProvider->GetHeight();
+ else
+ return 1;
+}
+
+void X11CairoTextRender::releaseCairoContext(cairo_t* cr)
+{
+ X11SalGraphics::releaseCairoContext(cr);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/gdi/xrender_peer.cxx b/vcl/unx/generic/gdi/xrender_peer.cxx
new file mode 100644
index 000000000..961f4cd3a
--- /dev/null
+++ b/vcl/unx/generic/gdi/xrender_peer.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 <unx/saldisp.hxx>
+
+#include <unx/x11/xrender_peer.hxx>
+
+XRenderPeer::XRenderPeer()
+ : mpDisplay( vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetDisplay() )
+ , mpStandardFormatA8( nullptr )
+{
+ InitRenderLib();
+}
+
+XRenderPeer& XRenderPeer::GetInstance()
+{
+ static XRenderPeer aPeer;
+ return aPeer;
+}
+
+void XRenderPeer::InitRenderLib()
+{
+ int nDummy;
+ // needed to initialize libXrender internals
+ XRenderQueryExtension( mpDisplay, &nDummy, &nDummy );
+
+ // the 8bit alpha mask format must be there
+ XRenderPictFormat aPictFormat={0,0,8,{0,0,0,0,0,0,0,0xFF},0};
+ mpStandardFormatA8 = XRenderFindFormat( mpDisplay, PictFormatAlphaMask|PictFormatDepth, &aPictFormat, 0 );
+}
+
+/* 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 000000000..df59ce1c8
--- /dev/null
+++ b/vcl/unx/generic/glyphs/freetype_glyphcache.cxx
@@ -0,0 +1,951 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/fontcharmap.hxx>
+
+#include <unx/freetype_glyphcache.hxx>
+
+#include <fontinstance.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 <langboost.hxx>
+#include <PhysicalFontCollection.hxx>
+#include <sft.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 <rtl/instance.hxx>
+
+#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( const OString& rNativeFileName )
+: maNativeFileName( rNativeFileName ),
+ 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 = open( pFileName, O_RDONLY );
+ if( nFile < 0 )
+ return false;
+
+ struct stat aStat;
+ int nRet = fstat( nFile, &aStat );
+ if (nRet < 0)
+ {
+ 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 )
+ mpFileMap = nullptr;
+ 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( const FontAttributes& rDevFontAttributes,
+ FreetypeFontFile* const pFontFile, int nFaceNum, int nFaceVariation, sal_IntPtr nFontId)
+:
+ maFaceFT( nullptr ),
+ mpFontFile(pFontFile),
+ mnFaceNum( nFaceNum ),
+ mnFaceVariation( nFaceVariation ),
+ mnRefCount( 0 ),
+ mnFontId( nFontId ),
+ maDevFontAttributes( rDevFontAttributes )
+{
+ // 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)
+ {
+ static auto func = reinterpret_cast<void(*)(FT_Library, FT_MM_Var*)>(dlsym(nullptr, "FT_Done_MM_Var"));
+ if (func)
+ func(library, amaster);
+ else
+ free(amaster);
+ }
+}
+
+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 FreetypeFont::SetFontVariationsOnHBFont(hb_font_t* pHbFace) const
+{
+ sal_uInt32 nFaceVariation = mxFontInfo->GetFontFaceVariation();
+ if (maFaceFT && nFaceVariation)
+ {
+ FT_MM_Var *pFtMMVar;
+ if (FT_Get_MM_Var(maFaceFT, &pFtMMVar) == 0)
+ {
+ if (nFaceVariation <= pFtMMVar->num_namedstyles)
+ {
+ FT_Var_Named_Style *instance = &pFtMMVar->namedstyle[nFaceVariation - 1];
+ std::vector<hb_variation_t> aVariations(pFtMMVar->num_axis);
+ for (FT_UInt i = 0; i < pFtMMVar->num_axis; ++i)
+ {
+ aVariations[i].tag = pFtMMVar->axis[i].tag;
+ aVariations[i].value = instance->coords[i] / 65536.0;
+ }
+ hb_font_set_variations(pHbFace, aVariations.data(), aVariations.size());
+ }
+ dlFT_Done_MM_Var(aLibFT, pFtMMVar);
+ }
+ }
+}
+
+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");
+}
+
+static unsigned GetUInt( const unsigned char* p ) { return((p[0]<<24)+(p[1]<<16)+(p[2]<<8)+p[3]);}
+static unsigned GetUShort( const unsigned char* p ){ return((p[0]<<8)+p[1]);}
+
+static const sal_uInt32 T_true = 0x74727565; /* 'true' */
+static const sal_uInt32 T_ttcf = 0x74746366; /* 'ttcf' */
+static const sal_uInt32 T_otto = 0x4f54544f; /* 'OTTO' */
+
+const unsigned char* FreetypeFontInfo::GetTable( const char* pTag, sal_uLong* pLength ) const
+{
+ const unsigned char* pBuffer = mpFontFile->GetBuffer();
+ int nFileSize = mpFontFile->GetFileSize();
+ if( !pBuffer || nFileSize<1024 )
+ return nullptr;
+
+ // we currently handle TTF, TTC and OTF headers
+ unsigned nFormat = GetUInt( pBuffer );
+
+ const unsigned char* p = pBuffer + 12;
+ if( nFormat == T_ttcf ) // TTC_MAGIC
+ p += GetUInt( p + 4 * mnFaceNum );
+ else if( nFormat != 0x00010000 && nFormat != T_true && nFormat != T_otto) // TTF_MAGIC and Apple TTF Magic and PS-OpenType font
+ return nullptr;
+
+ // walk table directory until match
+ int nTables = GetUShort( p - 8 );
+ if( nTables >= 64 ) // something fishy?
+ return nullptr;
+ for( int i = 0; i < nTables; ++i, p+=16 )
+ {
+ if( p[0]==pTag[0] && p[1]==pTag[1] && p[2]==pTag[2] && p[3]==pTag[3] )
+ {
+ sal_uLong nLength = GetUInt( p + 12 );
+ if( pLength != nullptr )
+ *pLength = nLength;
+ const unsigned char* pTable = pBuffer + GetUInt( p + 8 );
+ if( (pTable + nLength) <= (mpFontFile->GetBuffer() + nFileSize) )
+ return pTable;
+ }
+ }
+
+ return nullptr;
+}
+
+void FreetypeFontInfo::AnnounceFont( 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';
+}
+
+namespace
+{
+ bool DoesAlmostHorizontalDrainRenderingPool()
+ {
+ FT_Int nMajor, nMinor, nPatch;
+ FT_Library_Version(aLibFT, &nMajor, &nMinor, &nPatch);
+ if (nMajor > 2)
+ return false;
+ if (nMajor == 2 && nMinor <= 8)
+ return true;
+ return false;
+ }
+}
+
+bool FreetypeFont::AlmostHorizontalDrainsRenderingPool(int nRatio, const FontSelectPattern& rFSD)
+{
+ static bool bAlmostHorizontalDrainsRenderingPool = DoesAlmostHorizontalDrainRenderingPool();
+ if (nRatio > 100 && rFSD.maTargetName == "OpenSymbol" && bAlmostHorizontalDrainsRenderingPool)
+ {
+ // tdf#127189 FreeType <= 2.8 will fail to render stretched horizontal
+ // brace glyphs in starmath at a fairly low stretch ratio. The failure
+ // will set CAIRO_STATUS_FREETYPE_ERROR on the surface which cannot be
+ // cleared, so all further painting to the surface fails.
+
+ // This appears fixed in >= freetype 2.9
+
+ // Restrict this bodge to a stretch ratio > ~10 of the OpenSymbol font
+ // where it has been seen in practice.
+ SAL_WARN("vcl", "rendering text would fail with stretch ratio of: " << nRatio << ", with FreeType <= 2.8");
+ return true;
+ }
+ return false;
+}
+
+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);
+ if( m_nMaxFontId < nFontId )
+ m_nMaxFontId = nFontId;
+}
+
+void FreetypeManager::AnnounceFonts( 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 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 )
+: PhysicalFontFace( rDFA ),
+ mpFreetypeFontInfo( pFI )
+{
+}
+
+rtl::Reference<LogicalFontInstance> FreetypeFontFace::CreateFontInstance(const FontSelectPattern& rFSD) const
+{
+ return new FreetypeFontInstance(*this, rFSD);
+}
+
+// FreetypeFont
+
+FreetypeFont::FreetypeFont(FreetypeFontInstance& rFontInstance, std::shared_ptr<FreetypeFontInfo>& rFI)
+: mrFontInstance(rFontInstance),
+ mnCos( 0x10000),
+ mnSin( 0 ),
+ mnPrioAntiAlias(nDefaultPrioAntiAlias),
+ mxFontInfo(rFI),
+ mnLoadFlags( 0 ),
+ maFaceFT( nullptr ),
+ maSizeFT( nullptr ),
+ mbFaceOk( false ),
+ mbArtItalic( false ),
+ mbArtBold(false)
+{
+ int nPrioEmbedded = nDefaultPrioEmbedded;
+
+ maFaceFT = mxFontInfo->GetFaceFT();
+
+ const FontSelectPattern& rFSD = rFontInstance.GetFontSelectPattern();
+
+ if( rFSD.mnOrientation != 0 )
+ {
+ const double dRad = rFSD.mnOrientation * ( F_2PI / 3600.0 );
+ mnCos = static_cast<long>( 0x10000 * cos( dRad ) + 0.5 );
+ mnSin = static_cast<long>( 0x10000 * sin( dRad ) + 0.5 );
+ }
+
+ // set the pixel size of the font instance
+ mnWidth = rFSD.mnWidth;
+ if( !mnWidth )
+ mnWidth = rFSD.mnHeight;
+ mfStretch = static_cast<double>(mnWidth) / rFSD.mnHeight;
+ // sanity check (e.g. #i66394#, #i66244#, #i66537#)
+ if( (mnWidth < 0) || (mfStretch > +64.0) || (mfStretch < -64.0) )
+ return;
+
+ 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 );
+
+ FT_Select_Charmap(maFaceFT, FT_ENCODING_UNICODE);
+
+ if( mxFontInfo->IsSymbolFont() )
+ {
+ FT_Encoding eEncoding = FT_ENCODING_MS_SYMBOL;
+ FT_Select_Charmap(maFaceFT, eEncoding);
+ }
+
+ mbFaceOk = true;
+
+ // TODO: query GASP table for load flags
+ mnLoadFlags = FT_LOAD_DEFAULT | FT_LOAD_IGNORE_TRANSFORM;
+
+ mbArtItalic = (rFSD.GetItalic() != ITALIC_NONE && mxFontInfo->GetFontAttributes().GetItalic() == ITALIC_NONE);
+ mbArtBold = (rFSD.GetWeight() > WEIGHT_MEDIUM && mxFontInfo->GetFontAttributes().GetWeight() <= WEIGHT_MEDIUM);
+
+ if( ((mnCos != 0) && (mnSin != 0)) || (nPrioEmbedded <= 0) )
+ mnLoadFlags |= FT_LOAD_NO_BITMAP;
+}
+
+namespace
+{
+ std::unique_ptr<FontConfigFontOptions> GetFCFontOptions( const FontAttributes& rFontAttributes, int nSize)
+ {
+ psp::FastPrintFontInfo aInfo;
+
+ aInfo.m_aFamilyName = rFontAttributes.GetFamilyName();
+ aInfo.m_eItalic = rFontAttributes.GetItalic();
+ aInfo.m_eWeight = rFontAttributes.GetWeight();
+ aInfo.m_eWidth = rFontAttributes.GetWidthType();
+
+ return psp::PrintFontManager::getFontOptions(aInfo, nSize);
+ }
+}
+
+const FontConfigFontOptions* FreetypeFont::GetFontOptions() const
+{
+ if (!mxFontOptions)
+ {
+ mxFontOptions = GetFCFontOptions(mxFontInfo->GetFontAttributes(), mrFontInstance.GetFontSelectPattern().mnHeight);
+ mxFontOptions->SyncPattern(GetFontFileName(), GetFontFaceIndex(), GetFontFaceVariation(), 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(ImplFontMetricDataRef const & rxTo) const
+{
+ rxTo->FontAttributes::operator =(mxFontInfo->GetFontAttributes());
+
+ rxTo->SetOrientation(mrFontInstance.GetFontSelectPattern().mnOrientation);
+
+ //Always consider [star]symbol as symbol fonts
+ if ( IsStarSymbol( rxTo->GetFamilyName() ) )
+ rxTo->SetSymbolFlag( true );
+
+ FT_Activate_Size( maSizeFT );
+
+ rxTo->ImplCalcLineSpacing(&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::GetGlyphBoundRect(sal_GlyphId nID, tools::Rectangle& rRect, bool bVertical) const
+{
+ FT_Activate_Size( maSizeFT );
+
+ FT_Error rc = FT_Load_Glyph(maFaceFT, nID, mnLoadFlags);
+
+ if (rc != FT_Err_Ok)
+ return false;
+
+ if (mbArtBold)
+ FT_GlyphSlot_Embolden(maFaceFT->glyph);
+
+ FT_Glyph pGlyphFT;
+ rc = FT_Get_Glyph(maFaceFT->glyph, &pGlyphFT);
+ if (rc != FT_Err_Ok)
+ return false;
+
+ ApplyGlyphTransform(bVertical, pGlyphFT);
+
+ FT_BBox aBbox;
+ FT_Glyph_Get_CBox( pGlyphFT, FT_GLYPH_BBOX_PIXELS, &aBbox );
+ FT_Done_Glyph( pGlyphFT );
+
+ tools::Rectangle aRect(aBbox.xMin, -aBbox.yMax, aBbox.xMax, -aBbox.yMin);
+ if (mnCos != 0x10000 && mnSin != 0)
+ {
+ const double nCos = mnCos / 65536.0;
+ const double nSin = mnSin / 65536.0;
+ rRect.SetLeft( nCos*aRect.Left() + nSin*aRect.Top() );
+ rRect.SetTop( -nSin*aRect.Left() - nCos*aRect.Top() );
+ rRect.SetRight( nCos*aRect.Right() + nSin*aRect.Bottom() );
+ rRect.SetBottom( -nSin*aRect.Right() - nCos*aRect.Bottom() );
+ }
+ else
+ rRect = aRect;
+ return true;
+}
+
+bool FreetypeFont::GetAntialiasAdvice() const
+{
+ // TODO: also use GASP info
+ return !mrFontInstance.GetFontSelectPattern().mbNonAntialiased && (mnPrioAntiAlias > 0);
+}
+
+// determine unicode ranges in font
+
+FontCharMapRef FreetypeFont::GetFontCharMap() const
+{
+ return mxFontInfo->GetFontCharMap();
+}
+
+const FontCharMapRef& FreetypeFontInfo::GetFontCharMap()
+{
+ // check if the charmap is already cached
+ if( mxFontCharMap.is() )
+ return mxFontCharMap;
+
+ // get the charmap and cache it
+ CmapResult aCmapResult;
+ aCmapResult.mbSymbolic = IsSymbolFont();
+
+ sal_uLong nLength = 0;
+ const unsigned char* pCmap = GetTable("cmap", &nLength);
+ if (pCmap && (nLength > 0) && ParseCMAP(pCmap, nLength, aCmapResult))
+ {
+ FontCharMapRef xFontCharMap( new FontCharMap ( aCmapResult ) );
+ mxFontCharMap = xFontCharMap;
+ }
+ else
+ {
+ FontCharMapRef xFontCharMap( new FontCharMap() );
+ mxFontCharMap = xFontCharMap;
+ }
+ // mxFontCharMap on either branch now has a refcount of 1
+ return mxFontCharMap;
+}
+
+bool FreetypeFont::GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const
+{
+ bool bRet = false;
+
+ sal_uLong nLength = 0;
+
+ // load OS/2 table
+ const FT_Byte* pOS2 = mxFontInfo->GetTable("OS/2", &nLength);
+ if (pOS2)
+ {
+ bRet = vcl::getTTCoverage(
+ rFontCapabilities.oUnicodeRange,
+ rFontCapabilities.oCodePageRange,
+ pOS2, nLength);
+ }
+
+ return bRet;
+}
+
+// outline stuff
+
+namespace {
+
+class PolyArgs
+{
+public:
+ PolyArgs( tools::PolyPolygon& rPolyPoly, sal_uInt16 nMaxPoints );
+
+ void AddPoint( long nX, long nY, PolyFlags);
+ void ClosePolygon();
+
+ long GetPosX() const { return maPosition.x;}
+ 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( long nX, 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 long nX1 = (2 * rA.GetPosX() + 4 * p1->x + 3) / 6;
+ const long nY1 = (2 * rA.GetPosY() + 4 * p1->y + 3) / 6;
+ rA.AddPoint( nX1, nY1, PolyFlags::Control );
+
+ const long nX2 = (2 * p2->x + 4 * p1->x + 3) / 6;
+ const 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 (mbArtBold)
+ 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( mbArtItalic )
+ {
+ FT_Matrix aMatrix;
+ aMatrix.xx = aMatrix.yy = 0x10000L;
+ aMatrix.xy = 0x6000L;
+ 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;
+ }
+
+ 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( +1.0/(1<<6), -1.0/(1<<6) ));
+
+ return true;
+}
+
+const unsigned char* FreetypeFont::GetTable(const char* pName, sal_uLong* pLength) const
+{
+ return mxFontInfo->GetTable( pName, pLength );
+}
+
+/* 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 000000000..f5f6116f4
--- /dev/null
+++ b/vcl/unx/generic/glyphs/glyphcache.cxx
@@ -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 .
+ */
+
+#include <stdlib.h>
+#include <unx/freetype_glyphcache.hxx>
+#include <unx/gendata.hxx>
+
+#include <fontinstance.hxx>
+
+#include <rtl/ustring.hxx>
+#include <sal/log.hxx>
+
+FreetypeManager::FreetypeManager()
+ : m_nMaxFontId(0)
+{
+ 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 PhysicalFontFace& rPFF, const FontSelectPattern& rFSP)
+ : LogicalFontInstance(rPFF, rFSP)
+ , mxFreetypeFont(FreetypeManager::get().CreateFont(this))
+{
+}
+
+FreetypeFontInstance::~FreetypeFontInstance()
+{
+}
+
+static hb_blob_t* getFontTable(hb_face_t* /*face*/, hb_tag_t nTableTag, void* pUserData)
+{
+ char pTagName[5];
+ LogicalFontInstance::DecodeOpenTypeTag( nTableTag, pTagName );
+
+ sal_uLong nLength = 0;
+ FreetypeFontInstance* pFontInstance = static_cast<FreetypeFontInstance*>( pUserData );
+ FreetypeFont& rFont = pFontInstance->GetFreetypeFont();
+ const char* pBuffer = reinterpret_cast<const char*>(
+ rFont.GetTable(pTagName, &nLength) );
+
+ hb_blob_t* pBlob = nullptr;
+ if (pBuffer != nullptr)
+ pBlob = hb_blob_create(pBuffer, nLength, HB_MEMORY_MODE_READONLY, nullptr, nullptr);
+
+ return pBlob;
+}
+
+hb_font_t* FreetypeFontInstance::ImplInitHbFont()
+{
+ hb_font_t* pRet = InitHbFont(hb_face_create_for_tables(getFontTable, this, nullptr));
+ assert(mxFreetypeFont);
+ mxFreetypeFont->SetFontVariationsOnHBFont(pRet);
+ return pRet;
+}
+
+bool FreetypeFontInstance::ImplGetGlyphBoundRect(sal_GlyphId nId, tools::Rectangle& rRect, bool bVertical) const
+{
+ assert(mxFreetypeFont);
+ if (!mxFreetypeFont)
+ return false;
+ return mxFreetypeFont->GetGlyphBoundRect(nId, rRect, bVertical);
+}
+
+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/bitmap_gfx.cxx b/vcl/unx/generic/print/bitmap_gfx.cxx
new file mode 100644
index 000000000..b559b9b13
--- /dev/null
+++ b/vcl/unx/generic/print/bitmap_gfx.cxx
@@ -0,0 +1,696 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <array>
+#include <memory>
+#include "psputil.hxx"
+
+#include <unx/printergfx.hxx>
+
+namespace psp {
+
+const sal_uInt32 nLineLength = 80;
+const sal_uInt32 nBufferSize = 16384;
+
+/*
+ *
+ * Bitmap compression / Hex encoding / Ascii85 Encoding
+ *
+ */
+
+PrinterBmp::~PrinterBmp()
+{
+}
+
+/* virtual base class */
+
+namespace {
+
+class ByteEncoder
+{
+private:
+
+public:
+
+ virtual void EncodeByte (sal_uInt8 nByte) = 0;
+ virtual ~ByteEncoder () = 0;
+};
+
+}
+
+ByteEncoder::~ByteEncoder()
+{
+}
+
+/* HexEncoder */
+
+namespace {
+
+class HexEncoder : public ByteEncoder
+{
+private:
+
+ osl::File* mpFile;
+ sal_uInt32 mnColumn;
+ sal_uInt32 mnOffset;
+ OStringBuffer mpFileBuffer;
+
+public:
+
+ explicit HexEncoder (osl::File* pFile);
+ virtual ~HexEncoder () override;
+ void WriteAscii (sal_uInt8 nByte);
+ virtual void EncodeByte (sal_uInt8 nByte) override;
+ void FlushLine ();
+};
+
+}
+
+HexEncoder::HexEncoder (osl::File* pFile) :
+ mpFile (pFile),
+ mnColumn (0),
+ mnOffset (0)
+{}
+
+HexEncoder::~HexEncoder ()
+{
+ FlushLine ();
+ if (mnColumn > 0)
+ WritePS (mpFile, "\n");
+}
+
+void
+HexEncoder::WriteAscii (sal_uInt8 nByte)
+{
+ sal_uInt32 nOff = psp::getHexValueOf (nByte, mpFileBuffer);
+ mnColumn += nOff;
+ mnOffset += nOff;
+
+ if (mnColumn >= nLineLength)
+ {
+ mnOffset += psp::appendStr ("\n", mpFileBuffer);
+ mnColumn = 0;
+ }
+ if (mnOffset >= nBufferSize)
+ FlushLine ();
+}
+
+void
+HexEncoder::EncodeByte (sal_uInt8 nByte)
+{
+ WriteAscii (nByte);
+}
+
+void
+HexEncoder::FlushLine ()
+{
+ if (mnOffset > 0)
+ {
+ WritePS (mpFile, mpFileBuffer.makeStringAndClear());
+ mnOffset = 0;
+ }
+}
+
+/* Ascii85 encoder, is abi compatible with HexEncoder but writes a ~> to
+ indicate end of data EOD */
+
+namespace {
+
+class Ascii85Encoder : public ByteEncoder
+{
+private:
+
+ osl::File* mpFile;
+ sal_uInt32 mnByte;
+ sal_uInt8 mpByteBuffer[4];
+
+ sal_uInt32 mnColumn;
+ sal_uInt32 mnOffset;
+ OStringBuffer mpFileBuffer;
+
+ inline void PutByte (sal_uInt8 nByte);
+ inline void PutEOD ();
+ void ConvertToAscii85 ();
+ void FlushLine ();
+
+public:
+
+ explicit Ascii85Encoder (osl::File* pFile);
+ virtual ~Ascii85Encoder () override;
+ virtual void EncodeByte (sal_uInt8 nByte) override;
+ void WriteAscii (sal_uInt8 nByte);
+};
+
+}
+
+Ascii85Encoder::Ascii85Encoder (osl::File* pFile) :
+ mpFile (pFile),
+ mnByte (0),
+ mnColumn (0),
+ mnOffset (0)
+{}
+
+inline void
+Ascii85Encoder::PutByte (sal_uInt8 nByte)
+{
+ mpByteBuffer [mnByte++] = nByte;
+}
+
+inline void
+Ascii85Encoder::PutEOD ()
+{
+ WritePS (mpFile, "~>\n");
+}
+
+void
+Ascii85Encoder::ConvertToAscii85 ()
+{
+ // Add (4 - mnByte) zero padding bytes:
+ if (mnByte < 4)
+ std::memset (mpByteBuffer + mnByte, 0, (4 - mnByte) * sizeof(sal_uInt8));
+
+ sal_uInt32 nByteValue = mpByteBuffer[0] * 256 * 256 * 256
+ + mpByteBuffer[1] * 256 * 256
+ + mpByteBuffer[2] * 256
+ + mpByteBuffer[3];
+
+ if (nByteValue == 0 && mnByte == 4)
+ {
+ /* special case of 4 Bytes in row */
+ mpFileBuffer.append('z');
+
+ mnOffset += 1;
+ mnColumn += 1;
+ }
+ else
+ {
+ /* real ascii85 encoding */
+
+ // Of the up to 5 characters to be generated, do not generate the last (4 - mnByte) ones
+ // that correspond to the (4 - mnByte) zero padding bytes added to the input:
+
+ auto const pos = mpFileBuffer.getLength();
+ if (mnByte == 4) {
+ mpFileBuffer.insert(pos, char((nByteValue % 85) + 33));
+ }
+ nByteValue /= 85;
+ if (mnByte >= 3) {
+ mpFileBuffer.insert(pos, char((nByteValue % 85) + 33));
+ }
+ nByteValue /= 85;
+ if (mnByte >= 2) {
+ mpFileBuffer.insert(pos, char((nByteValue % 85) + 33));
+ }
+ nByteValue /= 85;
+ mpFileBuffer.insert(pos, char((nByteValue % 85) + 33));
+ nByteValue /= 85;
+ mpFileBuffer.insert(pos, char((nByteValue % 85) + 33));
+
+ mnColumn += (mnByte + 1);
+ mnOffset += (mnByte + 1);
+
+ /* insert a newline if necessary */
+ if (mnColumn > nLineLength)
+ {
+ sal_uInt32 nEolOff = mnColumn - nLineLength;
+ auto const posNl = pos + (mnByte + 1) - nEolOff;
+
+ mpFileBuffer.insert(posNl, '\n');
+
+ mnOffset++;
+ mnColumn = nEolOff;
+ }
+ }
+
+ mnByte = 0;
+}
+
+void
+Ascii85Encoder::WriteAscii (sal_uInt8 nByte)
+{
+ PutByte (nByte);
+ if (mnByte == 4)
+ ConvertToAscii85 ();
+
+ if (mnColumn >= nLineLength)
+ {
+ mnOffset += psp::appendStr ("\n", mpFileBuffer);
+ mnColumn = 0;
+ }
+ if (mnOffset >= nBufferSize)
+ FlushLine ();
+}
+
+void
+Ascii85Encoder::EncodeByte (sal_uInt8 nByte)
+{
+ WriteAscii (nByte);
+}
+
+void
+Ascii85Encoder::FlushLine ()
+{
+ if (mnOffset > 0)
+ {
+ WritePS (mpFile, mpFileBuffer.makeStringAndClear());
+ mnOffset = 0;
+ }
+}
+
+Ascii85Encoder::~Ascii85Encoder ()
+{
+ if (mnByte > 0)
+ ConvertToAscii85 ();
+ if (mnOffset > 0)
+ FlushLine ();
+ PutEOD ();
+}
+
+/* LZW encoder */
+
+namespace {
+
+class LZWEncoder : public Ascii85Encoder
+{
+private:
+
+ struct LZWCTreeNode
+ {
+ LZWCTreeNode* mpBrother; // next node with same parent
+ LZWCTreeNode* mpFirstChild; // first son
+ sal_uInt16 mnCode; // code for the string
+ sal_uInt16 mnValue; // pixelvalue
+ };
+
+ std::array<LZWCTreeNode, 4096>
+ mpTable; // LZW compression data
+ LZWCTreeNode* mpPrefix; // the compression is as same as the TIFF compression
+ static constexpr sal_uInt16 gnDataSize = 8;
+ static constexpr sal_uInt16 gnClearCode = 1 << gnDataSize;
+ static constexpr sal_uInt16 gnEOICode = gnClearCode + 1;
+ sal_uInt16 mnTableSize;
+ sal_uInt16 mnCodeSize;
+ sal_uInt32 mnOffset;
+ sal_uInt32 mdwShift;
+
+ void WriteBits (sal_uInt16 nCode, sal_uInt16 nCodeLen);
+
+public:
+
+ explicit LZWEncoder (osl::File* pOutputFile);
+ virtual ~LZWEncoder () override;
+
+ virtual void EncodeByte (sal_uInt8 nByte) override;
+};
+
+}
+
+LZWEncoder::LZWEncoder(osl::File* pOutputFile) :
+ Ascii85Encoder (pOutputFile),
+ mpPrefix(nullptr),
+ mnTableSize(gnEOICode + 1),
+ mnCodeSize(gnDataSize + 1),
+ mnOffset(32), // free bits in dwShift
+ mdwShift(0)
+{
+ for (sal_uInt32 i = 0; i < 4096; i++)
+ {
+ mpTable[i].mpBrother = nullptr;
+ mpTable[i].mpFirstChild = nullptr;
+ mpTable[i].mnCode = i;
+ mpTable[i].mnValue = static_cast<sal_uInt8>(mpTable[i].mnCode);
+ }
+
+ WriteBits( gnClearCode, mnCodeSize );
+}
+
+LZWEncoder::~LZWEncoder()
+{
+ if (mpPrefix)
+ WriteBits (mpPrefix->mnCode, mnCodeSize);
+
+ WriteBits (gnEOICode, mnCodeSize);
+}
+
+void
+LZWEncoder::WriteBits (sal_uInt16 nCode, sal_uInt16 nCodeLen)
+{
+ mdwShift |= (nCode << (mnOffset - nCodeLen));
+ mnOffset -= nCodeLen;
+ while (mnOffset < 24)
+ {
+ WriteAscii (static_cast<sal_uInt8>(mdwShift >> 24));
+ mdwShift <<= 8;
+ mnOffset += 8;
+ }
+ if (nCode == 257 && mnOffset != 32)
+ WriteAscii (static_cast<sal_uInt8>(mdwShift >> 24));
+}
+
+void
+LZWEncoder::EncodeByte (sal_uInt8 nByte )
+{
+ LZWCTreeNode* p;
+ sal_uInt16 i;
+ sal_uInt8 nV;
+
+ if (!mpPrefix)
+ {
+ mpPrefix = mpTable.data() + nByte;
+ }
+ else
+ {
+ nV = nByte;
+ for (p = mpPrefix->mpFirstChild; p != nullptr; p = p->mpBrother)
+ {
+ if (p->mnValue == nV)
+ break;
+ }
+
+ if (p != nullptr)
+ {
+ mpPrefix = p;
+ }
+ else
+ {
+ WriteBits (mpPrefix->mnCode, mnCodeSize);
+
+ if (mnTableSize == 409)
+ {
+ WriteBits (gnClearCode, mnCodeSize);
+
+ for (i = 0; i < gnClearCode; i++)
+ mpTable[i].mpFirstChild = nullptr;
+
+ mnCodeSize = gnDataSize + 1;
+ mnTableSize = gnEOICode + 1;
+ }
+ else
+ {
+ if(mnTableSize == static_cast<sal_uInt16>((1 << mnCodeSize) - 1))
+ mnCodeSize++;
+
+ p = mpTable.data() + (mnTableSize++);
+ p->mpBrother = mpPrefix->mpFirstChild;
+ mpPrefix->mpFirstChild = p;
+ p->mnValue = nV;
+ p->mpFirstChild = nullptr;
+ }
+
+ mpPrefix = mpTable.data() + nV;
+ }
+ }
+}
+
+/*
+ *
+ * bitmap handling routines
+ *
+ */
+
+void
+PrinterGfx::DrawBitmap (const tools::Rectangle& rDest, const tools::Rectangle& rSrc,
+ const PrinterBmp& rBitmap)
+{
+ double fScaleX = static_cast<double>(rDest.GetWidth());
+ double fScaleY = static_cast<double>(rDest.GetHeight());
+ if(rSrc.GetWidth() > 0)
+ {
+ fScaleX = static_cast<double>(rDest.GetWidth()) / static_cast<double>(rSrc.GetWidth());
+ }
+ if(rSrc.GetHeight() > 0)
+ {
+ fScaleY = static_cast<double>(rDest.GetHeight()) / static_cast<double>(rSrc.GetHeight());
+ }
+ PSGSave ();
+ PSTranslate (rDest.BottomLeft());
+ PSScale (fScaleX, fScaleY);
+
+ if (mnPSLevel >= 2)
+ {
+ if (rBitmap.GetDepth() == 1)
+ {
+ DrawPS2MonoImage (rBitmap, rSrc);
+ }
+ else
+ if (rBitmap.GetDepth() == 8 && mbColor)
+ {
+ // if the palette is larger than the image itself print it as a truecolor
+ // image to save diskspace. This is important for printing transparent
+ // bitmaps that are disassembled into small pieces
+ sal_Int32 nImageSz = rSrc.GetWidth() * rSrc.GetHeight();
+ sal_Int32 nPaletteSz = rBitmap.GetPaletteEntryCount();
+ if ((nImageSz < nPaletteSz) || (nImageSz < 24) )
+ DrawPS2TrueColorImage (rBitmap, rSrc);
+ else
+ DrawPS2PaletteImage (rBitmap, rSrc);
+ }
+ else
+ if (rBitmap.GetDepth() == 24 && mbColor)
+ {
+ DrawPS2TrueColorImage (rBitmap, rSrc);
+ }
+ else
+ {
+ DrawPS2GrayImage (rBitmap, rSrc);
+ }
+ }
+ else
+ {
+ DrawPS1GrayImage (rBitmap, rSrc);
+ }
+
+ PSGRestore ();
+}
+
+/*
+ *
+ * Implementation: PS Level 1
+ *
+ */
+
+void
+PrinterGfx::DrawPS1GrayImage (const PrinterBmp& rBitmap, const tools::Rectangle& rArea)
+{
+ sal_uInt32 nWidth = rArea.GetWidth();
+ sal_uInt32 nHeight = rArea.GetHeight();
+
+ OStringBuffer pGrayImage;
+
+ // image header
+ psp::getValueOf (nWidth, pGrayImage);
+ psp::appendStr (" ", pGrayImage);
+ psp::getValueOf (nHeight, pGrayImage);
+ psp::appendStr (" 8 ", pGrayImage);
+ psp::appendStr ("[ 1 0 0 1 0 ", pGrayImage);
+ psp::getValueOf (nHeight, pGrayImage);
+ psp::appendStr ("]", pGrayImage);
+ psp::appendStr (" {currentfile ", pGrayImage);
+ psp::getValueOf (nWidth, pGrayImage);
+ psp::appendStr (" string readhexstring pop}\n", pGrayImage);
+ psp::appendStr ("image\n", pGrayImage);
+
+ WritePS (mpPageBody, pGrayImage.makeStringAndClear());
+
+ // image body
+ std::unique_ptr<HexEncoder> xEncoder(new HexEncoder (mpPageBody));
+
+ for (long nRow = rArea.Top(); nRow <= rArea.Bottom(); nRow++)
+ {
+ for (long nColumn = rArea.Left(); nColumn <= rArea.Right(); nColumn++)
+ {
+ unsigned char nByte = rBitmap.GetPixelGray (nRow, nColumn);
+ xEncoder->EncodeByte (nByte);
+ }
+ }
+
+ xEncoder.reset();
+
+ WritePS (mpPageBody, "\n");
+}
+
+/*
+ *
+ * Implementation: PS Level 2
+ *
+ */
+
+void
+PrinterGfx::writePS2ImageHeader (const tools::Rectangle& rArea, psp::ImageType nType)
+{
+ OStringBuffer pImage;
+
+ sal_Int32 nDictType = 0;
+ switch (nType)
+ {
+ case psp::ImageType::TrueColorImage: nDictType = 0; break;
+ case psp::ImageType::PaletteImage: nDictType = 1; break;
+ case psp::ImageType::GrayScaleImage: nDictType = 2; break;
+ case psp::ImageType::MonochromeImage: nDictType = 3; break;
+ default: break;
+ }
+
+ psp::getValueOf (rArea.GetWidth(), pImage);
+ psp::appendStr (" ", pImage);
+ psp::getValueOf (rArea.GetHeight(), pImage);
+ psp::appendStr (" ", pImage);
+ psp::getValueOf (nDictType, pImage);
+ psp::appendStr (" ", pImage);
+ psp::getValueOf (sal_Int32(1), pImage); // nCompressType
+ psp::appendStr (" psp_imagedict image\n", pImage);
+
+ WritePS (mpPageBody, pImage.makeStringAndClear());
+}
+
+void
+PrinterGfx::writePS2Colorspace(const PrinterBmp& rBitmap, psp::ImageType nType)
+{
+ switch (nType)
+ {
+ case psp::ImageType::GrayScaleImage:
+
+ WritePS (mpPageBody, "/DeviceGray setcolorspace\n");
+ break;
+
+ case psp::ImageType::TrueColorImage:
+
+ WritePS (mpPageBody, "/DeviceRGB setcolorspace\n");
+ break;
+
+ case psp::ImageType::MonochromeImage:
+ case psp::ImageType::PaletteImage:
+ {
+
+ OStringBuffer pImage;
+
+ const sal_uInt32 nSize = rBitmap.GetPaletteEntryCount();
+
+ psp::appendStr ("[/Indexed /DeviceRGB ", pImage);
+ psp::getValueOf (nSize - 1, pImage);
+ psp::appendStr ("\npsp_lzwstring\n", pImage);
+ WritePS (mpPageBody, pImage.makeStringAndClear());
+
+ std::unique_ptr<ByteEncoder> xEncoder(new LZWEncoder(mpPageBody));
+ for (sal_uInt32 i = 0; i < nSize; i++)
+ {
+ PrinterColor aColor = rBitmap.GetPaletteColor(i);
+
+ xEncoder->EncodeByte (aColor.GetRed());
+ xEncoder->EncodeByte (aColor.GetGreen());
+ xEncoder->EncodeByte (aColor.GetBlue());
+ }
+ xEncoder.reset();
+
+ WritePS (mpPageBody, "pop ] setcolorspace\n");
+ }
+ break;
+ default: break;
+ }
+}
+
+void
+PrinterGfx::DrawPS2GrayImage (const PrinterBmp& rBitmap, const tools::Rectangle& rArea)
+{
+ writePS2Colorspace(rBitmap, psp::ImageType::GrayScaleImage);
+ writePS2ImageHeader(rArea, psp::ImageType::GrayScaleImage);
+
+ std::unique_ptr<ByteEncoder> xEncoder(new LZWEncoder(mpPageBody));
+
+ for (long nRow = rArea.Top(); nRow <= rArea.Bottom(); nRow++)
+ {
+ for (long nColumn = rArea.Left(); nColumn <= rArea.Right(); nColumn++)
+ {
+ unsigned char nByte = rBitmap.GetPixelGray (nRow, nColumn);
+ xEncoder->EncodeByte (nByte);
+ }
+ }
+}
+
+void
+PrinterGfx::DrawPS2MonoImage (const PrinterBmp& rBitmap, const tools::Rectangle& rArea)
+{
+ writePS2Colorspace(rBitmap, psp::ImageType::MonochromeImage);
+ writePS2ImageHeader(rArea, psp::ImageType::MonochromeImage);
+
+ std::unique_ptr<ByteEncoder> xEncoder(new LZWEncoder(mpPageBody));
+
+ for (long nRow = rArea.Top(); nRow <= rArea.Bottom(); nRow++)
+ {
+ long nBitPos = 0;
+ unsigned char nByte = 0;
+
+ for (long nColumn = rArea.Left(); nColumn <= rArea.Right(); nColumn++)
+ {
+ unsigned char nBit = rBitmap.GetPixelIdx (nRow, nColumn);
+ nByte |= nBit << (7 - nBitPos);
+
+ if (++nBitPos == 8)
+ {
+ xEncoder->EncodeByte (nByte);
+ nBitPos = 0;
+ nByte = 0;
+ }
+ }
+ // keep the row byte aligned
+ if (nBitPos != 0)
+ xEncoder->EncodeByte (nByte);
+ }
+}
+
+void
+PrinterGfx::DrawPS2PaletteImage (const PrinterBmp& rBitmap, const tools::Rectangle& rArea)
+{
+ writePS2Colorspace(rBitmap, psp::ImageType::PaletteImage);
+ writePS2ImageHeader(rArea, psp::ImageType::PaletteImage);
+
+ std::unique_ptr<ByteEncoder> xEncoder(new LZWEncoder(mpPageBody));
+
+ for (long nRow = rArea.Top(); nRow <= rArea.Bottom(); nRow++)
+ {
+ for (long nColumn = rArea.Left(); nColumn <= rArea.Right(); nColumn++)
+ {
+ unsigned char nByte = rBitmap.GetPixelIdx (nRow, nColumn);
+ xEncoder->EncodeByte (nByte);
+ }
+ }
+}
+
+void
+PrinterGfx::DrawPS2TrueColorImage (const PrinterBmp& rBitmap, const tools::Rectangle& rArea)
+{
+ writePS2Colorspace(rBitmap, psp::ImageType::TrueColorImage);
+ writePS2ImageHeader(rArea, psp::ImageType::TrueColorImage);
+
+ std::unique_ptr<ByteEncoder> xEncoder(new LZWEncoder(mpPageBody));
+
+ for (long nRow = rArea.Top(); nRow <= rArea.Bottom(); nRow++)
+ {
+ for (long nColumn = rArea.Left(); nColumn <= rArea.Right(); nColumn++)
+ {
+ PrinterColor aColor = rBitmap.GetPixelRGB (nRow, nColumn);
+ xEncoder->EncodeByte (aColor.GetRed());
+ xEncoder->EncodeByte (aColor.GetGreen());
+ xEncoder->EncodeByte (aColor.GetBlue());
+ }
+ }
+}
+
+} /* namespace psp */
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/print/common_gfx.cxx b/vcl/unx/generic/print/common_gfx.cxx
new file mode 100644
index 000000000..37aa3cbe2
--- /dev/null
+++ b/vcl/unx/generic/print/common_gfx.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 <sal/config.h>
+
+#include "psputil.hxx"
+#include "glyphset.hxx"
+
+#include <unx/printergfx.hxx>
+#include <unx/printerjob.hxx>
+#include <unx/fontmanager.hxx>
+#include <strhelper.hxx>
+#include <printerinfomanager.hxx>
+
+#include <tools/color.hxx>
+#include <tools/poly.hxx>
+#include <tools/stream.hxx>
+
+using namespace psp ;
+
+static const sal_Int32 nMaxTextColumn = 80;
+
+GraphicsStatus::GraphicsStatus() :
+ maEncoding(RTL_TEXTENCODING_DONTKNOW),
+ mbArtItalic( false ),
+ mbArtBold( false ),
+ mnTextHeight( 0 ),
+ mnTextWidth( 0 ),
+ mfLineWidth( -1 )
+{
+}
+
+/*
+ * non graphics routines
+ */
+
+void
+PrinterGfx::Init (PrinterJob &rPrinterJob)
+{
+ mpPageBody = rPrinterJob.GetCurrentPageBody ();
+ mnDepth = rPrinterJob.GetDepth ();
+ mnPSLevel = rPrinterJob.GetPostscriptLevel ();
+ mbColor = rPrinterJob.IsColorPrinter ();
+
+ mnDpi = rPrinterJob.GetResolution();
+ rPrinterJob.GetScale (mfScaleX, mfScaleY);
+ const PrinterInfo& rInfo( PrinterInfoManager::get().getPrinterInfo( rPrinterJob.GetPrinterName() ) );
+ mbUploadPS42Fonts = rInfo.m_pParser && rInfo.m_pParser->isType42Capable();
+}
+
+void
+PrinterGfx::Init (const JobData& rData)
+{
+ mpPageBody = nullptr;
+ mnDepth = rData.m_nColorDepth;
+ mnPSLevel = rData.m_nPSLevel ? rData.m_nPSLevel : (rData.m_pParser ? rData.m_pParser->getLanguageLevel() : 2 );
+ mbColor = rData.m_nColorDevice ? ( rData.m_nColorDevice != -1 ) : ( rData.m_pParser == nullptr || rData.m_pParser->isColorDevice() );
+ int nRes = rData.m_aContext.getRenderResolution();
+ mnDpi = nRes;
+ mfScaleX = 72.0 / static_cast<double>(mnDpi);
+ mfScaleY = 72.0 / static_cast<double>(mnDpi);
+ const PrinterInfo& rInfo( PrinterInfoManager::get().getPrinterInfo( rData.m_aPrinterName ) );
+ mbUploadPS42Fonts = rInfo.m_pParser && rInfo.m_pParser->isType42Capable();
+}
+
+
+PrinterGfx::PrinterGfx()
+ : mfScaleX(0.0)
+ , mfScaleY(0.0)
+ , mnDpi(0)
+ , mnDepth(0)
+ , mnPSLevel(0)
+ , mbColor(false)
+ , mbUploadPS42Fonts(false)
+ , mpPageBody(nullptr)
+ , mnFontID(0)
+ , mnTextAngle(0)
+ , mbTextVertical(false)
+ , mrFontMgr(PrintFontManager::get())
+ , maFillColor(0xff,0,0)
+ , maTextColor(0,0,0)
+ , maLineColor(0, 0xff, 0)
+{
+ maVirtualStatus.mfLineWidth = 1.0;
+ maVirtualStatus.mnTextHeight = 12;
+ maVirtualStatus.mnTextWidth = 0;
+
+ maGraphicsStack.emplace_back( );
+}
+
+PrinterGfx::~PrinterGfx()
+{
+}
+
+void
+PrinterGfx::Clear()
+{
+ mpPageBody = nullptr;
+ mnFontID = 0;
+ maVirtualStatus = GraphicsStatus();
+ maVirtualStatus.mnTextHeight = 12;
+ maVirtualStatus.mnTextWidth = 0;
+ maVirtualStatus.mfLineWidth = 1.0;
+ mbTextVertical = false;
+ maLineColor = PrinterColor();
+ maFillColor = PrinterColor();
+ maTextColor = PrinterColor();
+ mnDpi = 300;
+ mnDepth = 24;
+ mnPSLevel = 2;
+ mbColor = true;
+ mnTextAngle = 0;
+
+ maClipRegion.clear();
+ maGraphicsStack.clear();
+ maGraphicsStack.emplace_back( );
+}
+
+/*
+ * clip region handling
+ */
+
+void
+PrinterGfx::ResetClipRegion()
+{
+ maClipRegion.clear();
+ PSGRestore ();
+ PSGSave (); // get "clean" clippath
+}
+
+void
+PrinterGfx::BeginSetClipRegion()
+{
+ maClipRegion.clear();
+}
+
+void
+PrinterGfx::UnionClipRegion (sal_Int32 nX,sal_Int32 nY,sal_Int32 nDX,sal_Int32 nDY)
+{
+ if( nDX && nDY )
+ maClipRegion.emplace_back(Point(nX,nY ), Size(nDX,nDY));
+}
+
+bool
+PrinterGfx::JoinVerticalClipRectangles( std::list< tools::Rectangle >::iterator& it,
+ Point& rOldPoint, sal_Int32& rColumn )
+{
+ bool bSuccess = false;
+
+ std::list< tools::Rectangle >::iterator tempit, nextit;
+ nextit = it;
+ ++nextit;
+ std::list< Point > leftside, rightside;
+
+ tools::Rectangle aLastRect( *it );
+ leftside.emplace_back( it->Left(), it->Top() );
+ rightside.emplace_back( it->Right()+1, it->Top() );
+ while( nextit != maClipRegion.end() )
+ {
+ tempit = nextit;
+ ++tempit;
+ if( nextit->Top() == aLastRect.Bottom()+1 )
+ {
+ if(
+ ( nextit->Left() >= aLastRect.Left() && nextit->Left() <= aLastRect.Right() ) // left endpoint touches last rectangle
+ ||
+ ( nextit->Right() >= aLastRect.Left() && nextit->Right() <= aLastRect.Right() ) // right endpoint touches last rectangle
+ ||
+ ( nextit->Left() <= aLastRect.Left() && nextit->Right() >= aLastRect.Right() ) // whole line touches last rectangle
+ )
+ {
+ if( aLastRect.GetHeight() > 1 ||
+ std::abs( aLastRect.Left() - nextit->Left() ) > 2 ||
+ std::abs( aLastRect.Right() - nextit->Right() ) > 2
+ )
+ {
+ leftside.emplace_back( aLastRect.Left(), aLastRect.Bottom()+1 );
+ rightside.emplace_back( aLastRect.Right()+1, aLastRect.Bottom()+1 );
+ }
+ aLastRect = *nextit;
+ leftside.push_back( aLastRect.TopLeft() );
+ rightside.push_back( aLastRect.TopRight() );
+ maClipRegion.erase( nextit );
+ }
+ }
+ nextit = tempit;
+ }
+ if( leftside.size() > 1 )
+ {
+ // push the last coordinates
+ leftside.emplace_back( aLastRect.Left(), aLastRect.Bottom()+1 );
+ rightside.emplace_back( aLastRect.Right()+1, aLastRect.Bottom()+1 );
+
+ // cool, we can concatenate rectangles
+ const int nDX = -65536, nDY = 65536;
+ int nNewDX = 0, nNewDY = 0;
+
+ Point aLastPoint = leftside.front();
+ PSBinMoveTo (aLastPoint, rOldPoint, rColumn);
+ leftside.pop_front();
+ while( !leftside.empty() )
+ {
+ Point aPoint (leftside.front());
+ leftside.pop_front();
+ // may have been the last one
+ if( !leftside.empty() )
+ {
+ nNewDX = aPoint.X() - aLastPoint.X();
+ nNewDY = aPoint.Y() - aLastPoint.Y();
+ if( nNewDX != 0 &&
+ static_cast<double>(nNewDY)/static_cast<double>(nNewDX) == double(nDY)/double(nDX) )
+ continue;
+ }
+ PSBinLineTo (aPoint, rOldPoint, rColumn);
+ aLastPoint = aPoint;
+ }
+
+ aLastPoint = rightside.back();
+ PSBinLineTo (aLastPoint, rOldPoint, rColumn);
+ rightside.pop_back();
+ while( !rightside.empty() )
+ {
+ Point aPoint (rightside.back());
+ rightside.pop_back();
+ if( !rightside.empty() )
+ {
+ nNewDX = aPoint.X() - aLastPoint.X();
+ nNewDY = aPoint.Y() - aLastPoint.Y();
+ if( nNewDX != 0 &&
+ static_cast<double>(nNewDY)/static_cast<double>(nNewDX) == double(nDY)/double(nDX) )
+ continue;
+ }
+ PSBinLineTo (aPoint, rOldPoint, rColumn);
+ }
+
+ tempit = it;
+ ++tempit;
+ maClipRegion.erase( it );
+ it = tempit;
+ bSuccess = true;
+ }
+ return bSuccess;
+}
+
+void
+PrinterGfx::EndSetClipRegion()
+{
+ PSGRestore ();
+ PSGSave (); // get "clean" clippath
+
+ PSBinStartPath ();
+ Point aOldPoint (0, 0);
+ sal_Int32 nColumn = 0;
+
+ std::list< tools::Rectangle >::iterator it = maClipRegion.begin();
+ while( it != maClipRegion.end() )
+ {
+ // try to concatenate adjacent rectangles
+ // first try in y direction, then in x direction
+ if( ! JoinVerticalClipRectangles( it, aOldPoint, nColumn ) )
+ {
+ // failed, so it is a single rectangle
+ PSBinMoveTo (Point( it->Left()-1, it->Top()-1), aOldPoint, nColumn );
+ PSBinLineTo (Point( it->Left()-1, it->Bottom()+1 ), aOldPoint, nColumn );
+ PSBinLineTo (Point( it->Right()+1, it->Bottom()+1 ), aOldPoint, nColumn );
+ PSBinLineTo (Point( it->Right()+1, it->Top()-1 ), aOldPoint, nColumn );
+ ++it;
+ }
+ }
+
+ PSBinEndPath ();
+
+ WritePS (mpPageBody, "closepath clip newpath\n");
+ maClipRegion.clear();
+}
+
+/*
+ * draw graphic primitives
+ */
+
+void
+PrinterGfx::DrawRect (const tools::Rectangle& rRectangle )
+{
+ OStringBuffer pRect;
+
+ psp::getValueOf (rRectangle.TopLeft().X(), pRect);
+ psp::appendStr (" ", pRect);
+ psp::getValueOf (rRectangle.TopLeft().Y(), pRect);
+ psp::appendStr (" ", pRect);
+ psp::getValueOf (rRectangle.GetWidth(), pRect);
+ psp::appendStr (" ", pRect);
+ psp::getValueOf (rRectangle.GetHeight(), pRect);
+ psp::appendStr (" ", pRect);
+ auto const rect = pRect.makeStringAndClear();
+
+ if( maFillColor.Is() )
+ {
+ PSSetColor (maFillColor);
+ PSSetColor ();
+ WritePS (mpPageBody, rect);
+ WritePS (mpPageBody, "rectfill\n");
+ }
+ if( maLineColor.Is() )
+ {
+ PSSetColor (maLineColor);
+ PSSetColor ();
+ PSSetLineWidth ();
+ WritePS (mpPageBody, rect);
+ WritePS (mpPageBody, "rectstroke\n");
+ }
+}
+
+void
+PrinterGfx::DrawLine (const Point& rFrom, const Point& rTo)
+{
+ if( maLineColor.Is() )
+ {
+ PSSetColor (maLineColor);
+ PSSetColor ();
+ PSSetLineWidth ();
+
+ PSMoveTo (rFrom);
+ PSLineTo (rTo);
+ WritePS (mpPageBody, "stroke\n" );
+ }
+}
+
+void
+PrinterGfx::DrawPixel (const Point& rPoint, const PrinterColor& rPixelColor)
+{
+ if( rPixelColor.Is() )
+ {
+ PSSetColor (rPixelColor);
+ PSSetColor ();
+
+ PSMoveTo (rPoint);
+ PSLineTo (Point (rPoint.X ()+1, rPoint.Y ()));
+ PSLineTo (Point (rPoint.X ()+1, rPoint.Y ()+1));
+ PSLineTo (Point (rPoint.X (), rPoint.Y ()+1));
+ WritePS (mpPageBody, "fill\n" );
+ }
+}
+
+void
+PrinterGfx::DrawPolyLine (sal_uInt32 nPoints, const Point* pPath)
+{
+ if( maLineColor.Is() && nPoints && pPath )
+ {
+ PSSetColor (maLineColor);
+ PSSetColor ();
+ PSSetLineWidth ();
+
+ PSBinCurrentPath (nPoints, pPath);
+
+ WritePS (mpPageBody, "stroke\n" );
+ }
+}
+
+void
+PrinterGfx::DrawPolygon (sal_uInt32 nPoints, const Point* pPath)
+{
+ // premature end of operation
+ if (nPoints <= 0 || (pPath == nullptr) || !(maFillColor.Is() || maLineColor.Is()))
+ return;
+
+ // setup closed path
+ Point aPoint( 0, 0 );
+ sal_Int32 nColumn( 0 );
+
+ PSBinStartPath();
+ PSBinMoveTo( pPath[0], aPoint, nColumn );
+ for( unsigned int n = 1; n < nPoints; n++ )
+ PSBinLineTo( pPath[n], aPoint, nColumn );
+ if( pPath[0] != pPath[nPoints-1] )
+ PSBinLineTo( pPath[0], aPoint, nColumn );
+ PSBinEndPath();
+
+ // fill the polygon first, then draw the border, note that fill and
+ // stroke reset the currentpath
+
+ // if fill and stroke, save the current path
+ if( maFillColor.Is() && maLineColor.Is())
+ PSGSave();
+
+ if (maFillColor.Is ())
+ {
+ PSSetColor (maFillColor);
+ PSSetColor ();
+ WritePS (mpPageBody, "eofill\n");
+ }
+
+ // restore the current path
+ if( maFillColor.Is() && maLineColor.Is())
+ PSGRestore();
+
+ if (maLineColor.Is ())
+ {
+ PSSetColor (maLineColor);
+ PSSetColor ();
+ PSSetLineWidth ();
+ WritePS (mpPageBody, "stroke\n");
+ }
+}
+
+void
+PrinterGfx::DrawPolyPolygon (sal_uInt32 nPoly, const sal_uInt32* pSizes, const Point** pPaths )
+{
+ // sanity check
+ if ( !nPoly || !pPaths || !(maFillColor.Is() || maLineColor.Is()))
+ return;
+
+ // setup closed path
+ for( unsigned int i = 0; i < nPoly; i++ )
+ {
+ Point aPoint( 0, 0 );
+ sal_Int32 nColumn( 0 );
+
+ PSBinStartPath();
+ PSBinMoveTo( pPaths[i][0], aPoint, nColumn );
+ for( unsigned int n = 1; n < pSizes[i]; n++ )
+ PSBinLineTo( pPaths[i][n], aPoint, nColumn );
+ if( pPaths[i][0] != pPaths[i][pSizes[i]-1] )
+ PSBinLineTo( pPaths[i][0], aPoint, nColumn );
+ PSBinEndPath();
+ }
+
+ // if eofill and stroke, save the current path
+ if( maFillColor.Is() && maLineColor.Is())
+ PSGSave();
+
+ // first draw area
+ if( maFillColor.Is() )
+ {
+ PSSetColor (maFillColor);
+ PSSetColor ();
+ WritePS (mpPageBody, "eofill\n");
+ }
+
+ // restore the current path
+ if( maFillColor.Is() && maLineColor.Is())
+ PSGRestore();
+
+ // now draw outlines
+ if( maLineColor.Is() )
+ {
+ PSSetColor (maLineColor);
+ PSSetColor ();
+ PSSetLineWidth ();
+ WritePS (mpPageBody, "stroke\n");
+ }
+}
+
+/*
+ * Bezier Polygon Drawing methods.
+ */
+
+void
+PrinterGfx::DrawPolyLineBezier (sal_uInt32 nPoints, const Point* pPath, const PolyFlags* pFlgAry)
+{
+ const sal_uInt32 nBezString= 1024;
+ char pString[nBezString];
+
+ if ( nPoints > 1 && maLineColor.Is() && pPath )
+ {
+ PSSetColor (maLineColor);
+ PSSetColor ();
+ PSSetLineWidth ();
+
+ snprintf(pString, nBezString, "%li %li moveto\n", pPath[0].X(), pPath[0].Y());
+ WritePS(mpPageBody, pString);
+
+ // Handle the drawing of mixed lines mixed with curves
+ // - a normal point followed by a normal point is a line
+ // - a normal point followed by 2 control points and a normal point is a curve
+ for (unsigned int i=1; i<nPoints;)
+ {
+ if (pFlgAry[i] != PolyFlags::Control) //If the next point is a PolyFlags::Normal, we're drawing a line
+ {
+ snprintf(pString, nBezString, "%li %li lineto\n", pPath[i].X(), pPath[i].Y());
+ i++;
+ }
+ else //Otherwise we're drawing a spline
+ {
+ if (i+2 >= nPoints)
+ return; //Error: wrong sequence of control/normal points somehow
+ if ((pFlgAry[i] == PolyFlags::Control) && (pFlgAry[i+1] == PolyFlags::Control) &&
+ (pFlgAry[i+2] != PolyFlags::Control))
+ {
+ snprintf(pString, nBezString, "%li %li %li %li %li %li curveto\n",
+ pPath[i].X(), pPath[i].Y(),
+ pPath[i+1].X(), pPath[i+1].Y(),
+ pPath[i+2].X(), pPath[i+2].Y());
+ }
+ else
+ {
+ OSL_FAIL( "PrinterGfx::DrawPolyLineBezier: Strange output" );
+ }
+ i+=3;
+ }
+ WritePS(mpPageBody, pString);
+ }
+
+ // now draw outlines
+ WritePS (mpPageBody, "stroke\n");
+ }
+}
+
+void
+PrinterGfx::DrawPolygonBezier (sal_uInt32 nPoints, const Point* pPath, const PolyFlags* pFlgAry)
+{
+ const sal_uInt32 nBezString = 1024;
+ char pString[nBezString];
+ // premature end of operation
+ if (nPoints <= 0 || (pPath == nullptr) || !(maFillColor.Is() || maLineColor.Is()))
+ return;
+
+ snprintf(pString, nBezString, "%li %li moveto\n", pPath[0].X(), pPath[0].Y());
+ WritePS(mpPageBody, pString); //Move to the starting point for the PolyPolygon
+ for (unsigned int i=1; i < nPoints;)
+ {
+ if (pFlgAry[i] != PolyFlags::Control)
+ {
+ snprintf(pString, nBezString, "%li %li lineto\n", pPath[i].X(), pPath[i].Y());
+ WritePS(mpPageBody, pString);
+ i++;
+ }
+ else
+ {
+ if (i+2 >= nPoints)
+ return; //Error: wrong sequence of control/normal points somehow
+ if ((pFlgAry[i] == PolyFlags::Control) && (pFlgAry[i+1] == PolyFlags::Control) &&
+ (pFlgAry[i+2] != PolyFlags::Control))
+ {
+ snprintf(pString, nBezString, "%li %li %li %li %li %li curveto\n",
+ pPath[i].X(), pPath[i].Y(),
+ pPath[i+1].X(), pPath[i+1].Y(),
+ pPath[i+2].X(), pPath[i+2].Y());
+ WritePS(mpPageBody, pString);
+ }
+ else
+ {
+ OSL_FAIL( "PrinterGfx::DrawPolygonBezier: Strange output" );
+ }
+ i+=3;
+ }
+ }
+
+ // if fill and stroke, save the current path
+ if( maFillColor.Is() && maLineColor.Is())
+ PSGSave();
+
+ if (maFillColor.Is ())
+ {
+ PSSetColor (maFillColor);
+ PSSetColor ();
+ WritePS (mpPageBody, "eofill\n");
+ }
+
+ // restore the current path
+ if( maFillColor.Is() && maLineColor.Is())
+ PSGRestore();
+}
+
+void
+PrinterGfx::DrawPolyPolygonBezier (sal_uInt32 nPoly, const sal_uInt32 * pPoints, const Point* const * pPtAry, const PolyFlags* const* pFlgAry)
+{
+ const sal_uInt32 nBezString = 1024;
+ char pString[nBezString];
+ if ( !nPoly || !pPtAry || !pPoints || !(maFillColor.Is() || maLineColor.Is()))
+ return;
+
+ for (unsigned int i=0; i<nPoly;i++)
+ {
+ sal_uInt32 nPoints = pPoints[i];
+ // sanity check
+ if( nPoints == 0 || pPtAry[i] == nullptr )
+ continue;
+
+ snprintf(pString, nBezString, "%li %li moveto\n", pPtAry[i][0].X(), pPtAry[i][0].Y()); //Move to the starting point
+ WritePS(mpPageBody, pString);
+ for (unsigned int j=1; j < nPoints;)
+ {
+ // if no flag array exists for this polygon, then it must be a regular
+ // polygon without beziers
+ if ( ! pFlgAry[i] || pFlgAry[i][j] != PolyFlags::Control)
+ {
+ snprintf(pString, nBezString, "%li %li lineto\n", pPtAry[i][j].X(), pPtAry[i][j].Y());
+ WritePS(mpPageBody, pString);
+ j++;
+ }
+ else
+ {
+ if (j+2 >= nPoints)
+ break; //Error: wrong sequence of control/normal points somehow
+ if ((pFlgAry[i][j] == PolyFlags::Control) && (pFlgAry[i][j+1] == PolyFlags::Control) && (pFlgAry[i][j+2] != PolyFlags::Control))
+ {
+ snprintf(pString, nBezString, "%li %li %li %li %li %li curveto\n",
+ pPtAry[i][j].X(), pPtAry[i][j].Y(),
+ pPtAry[i][j+1].X(), pPtAry[i][j+1].Y(),
+ pPtAry[i][j+2].X(), pPtAry[i][j+2].Y());
+ WritePS(mpPageBody, pString);
+ }
+ else
+ {
+ OSL_FAIL( "PrinterGfx::DrawPolyPolygonBezier: Strange output" );
+ }
+ j+=3;
+ }
+ }
+ }
+
+ // if fill and stroke, save the current path
+ if( maFillColor.Is() && maLineColor.Is())
+ PSGSave();
+
+ if (maFillColor.Is ())
+ {
+ PSSetColor (maFillColor);
+ PSSetColor ();
+ WritePS (mpPageBody, "eofill\n");
+ }
+
+ // restore the current path
+ if( maFillColor.Is() && maLineColor.Is())
+ PSGRestore();
+}
+
+/*
+ * postscript generating routines
+ */
+void
+PrinterGfx::PSGSave ()
+{
+ WritePS (mpPageBody, "gsave\n" );
+ GraphicsStatus aNewState;
+ if( !maGraphicsStack.empty() )
+ aNewState = maGraphicsStack.front();
+ maGraphicsStack.push_front( aNewState );
+}
+
+void
+PrinterGfx::PSGRestore ()
+{
+ WritePS (mpPageBody, "grestore\n" );
+ if( maGraphicsStack.empty() )
+ WritePS (mpPageBody, "Error: too many grestores\n" );
+ else
+ maGraphicsStack.pop_front();
+}
+
+void
+PrinterGfx::PSSetLineWidth ()
+{
+ if( currentState().mfLineWidth != maVirtualStatus.mfLineWidth )
+ {
+ OStringBuffer pBuffer;
+
+ currentState().mfLineWidth = maVirtualStatus.mfLineWidth;
+ psp::getValueOfDouble (pBuffer, maVirtualStatus.mfLineWidth, 5);
+ psp::appendStr (" setlinewidth\n", pBuffer);
+ WritePS (mpPageBody, pBuffer.makeStringAndClear());
+ }
+}
+
+void
+PrinterGfx::PSSetColor ()
+{
+ PrinterColor& rColor( maVirtualStatus.maColor );
+
+ if( currentState().maColor != rColor )
+ {
+ currentState().maColor = rColor;
+
+ OStringBuffer pBuffer;
+
+ if( mbColor )
+ {
+ psp::getValueOfDouble (pBuffer,
+ static_cast<double>(rColor.GetRed()) / 255.0, 5);
+ psp::appendStr (" ", pBuffer);
+ psp::getValueOfDouble (pBuffer,
+ static_cast<double>(rColor.GetGreen()) / 255.0, 5);
+ psp::appendStr (" ", pBuffer);
+ psp::getValueOfDouble (pBuffer,
+ static_cast<double>(rColor.GetBlue()) / 255.0, 5);
+ psp::appendStr (" setrgbcolor\n", pBuffer );
+ }
+ else
+ {
+ Color aColor( rColor.GetRed(), rColor.GetGreen(), rColor.GetBlue() );
+ sal_uInt8 nCol = aColor.GetLuminance();
+ psp::getValueOfDouble( pBuffer, static_cast<double>(nCol) / 255.0, 5 );
+ psp::appendStr( " setgray\n", pBuffer );
+ }
+
+ WritePS (mpPageBody, pBuffer.makeStringAndClear());
+ }
+}
+
+void
+PrinterGfx::PSSetFont ()
+{
+ GraphicsStatus& rCurrent( currentState() );
+ if( !(maVirtualStatus.maFont != rCurrent.maFont ||
+ maVirtualStatus.mnTextHeight != rCurrent.mnTextHeight ||
+ maVirtualStatus.maEncoding != rCurrent.maEncoding ||
+ maVirtualStatus.mnTextWidth != rCurrent.mnTextWidth ||
+ maVirtualStatus.mbArtBold != rCurrent.mbArtBold ||
+ maVirtualStatus.mbArtItalic != rCurrent.mbArtItalic)
+ )
+ return;
+
+ rCurrent.maFont = maVirtualStatus.maFont;
+ rCurrent.maEncoding = maVirtualStatus.maEncoding;
+ rCurrent.mnTextWidth = maVirtualStatus.mnTextWidth;
+ rCurrent.mnTextHeight = maVirtualStatus.mnTextHeight;
+ rCurrent.mbArtItalic = maVirtualStatus.mbArtItalic;
+ rCurrent.mbArtBold = maVirtualStatus.mbArtBold;
+
+ sal_Int32 nTextHeight = rCurrent.mnTextHeight;
+ sal_Int32 nTextWidth = rCurrent.mnTextWidth ? rCurrent.mnTextWidth
+ : rCurrent.mnTextHeight;
+
+ OStringBuffer pSetFont;
+
+ // postscript based fonts need reencoding
+ if ( ( rCurrent.maEncoding == RTL_TEXTENCODING_MS_1252)
+ || ( rCurrent.maEncoding == RTL_TEXTENCODING_ISO_8859_1)
+ || ( rCurrent.maEncoding >= RTL_TEXTENCODING_USER_START
+ && rCurrent.maEncoding <= RTL_TEXTENCODING_USER_END)
+ )
+ {
+ OString aReencodedFont =
+ psp::GlyphSet::GetReencodedFontName (rCurrent.maEncoding,
+ rCurrent.maFont);
+
+ psp::appendStr ("(", pSetFont);
+ psp::appendStr (aReencodedFont.getStr(),
+ pSetFont);
+ psp::appendStr (") cvn findfont ",
+ pSetFont);
+ }
+ else
+ // tt based fonts mustn't reencode, the encoding is implied by the fontname
+ // same for symbol type1 fonts, don't try to touch them
+ {
+ psp::appendStr ("(", pSetFont);
+ psp::appendStr (rCurrent.maFont.getStr(),
+ pSetFont);
+ psp::appendStr (") cvn findfont ",
+ pSetFont);
+ }
+
+ if( ! rCurrent.mbArtItalic )
+ {
+ psp::getValueOf (nTextWidth, pSetFont);
+ psp::appendStr (" ", pSetFont);
+ psp::getValueOf (-nTextHeight, pSetFont);
+ psp::appendStr (" matrix scale makefont setfont\n", pSetFont);
+ }
+ else // skew 15 degrees to right
+ {
+ psp::appendStr ( " [", pSetFont);
+ psp::getValueOf (nTextWidth, pSetFont);
+ psp::appendStr (" 0 ", pSetFont);
+ psp::getValueOfDouble (pSetFont, 0.27*static_cast<double>(nTextWidth), 3 );
+ psp::appendStr ( " ", pSetFont);
+ psp::getValueOf (-nTextHeight, pSetFont);
+
+ psp::appendStr (" 0 0] makefont setfont\n", pSetFont);
+ }
+
+ WritePS (mpPageBody, pSetFont.makeStringAndClear());
+
+}
+
+void
+PrinterGfx::PSRotate (sal_Int32 nAngle)
+{
+ sal_Int32 nPostScriptAngle = -nAngle;
+ while( nPostScriptAngle < 0 )
+ nPostScriptAngle += 3600;
+
+ if (nPostScriptAngle == 0)
+ return;
+
+ sal_Int32 nFullAngle = nPostScriptAngle / 10;
+ sal_Int32 nTenthAngle = nPostScriptAngle % 10;
+
+ OStringBuffer pRotate;
+
+ psp::getValueOf (nFullAngle, pRotate);
+ psp::appendStr (".", pRotate);
+ psp::getValueOf (nTenthAngle, pRotate);
+ psp::appendStr (" rotate\n", pRotate);
+
+ WritePS (mpPageBody, pRotate.makeStringAndClear());
+}
+
+void
+PrinterGfx::PSPointOp (const Point& rPoint, const char* pOperator)
+{
+ OStringBuffer pPSCommand;
+
+ psp::getValueOf (rPoint.X(), pPSCommand);
+ psp::appendStr (" ", pPSCommand);
+ psp::getValueOf (rPoint.Y(), pPSCommand);
+ psp::appendStr (" ", pPSCommand);
+ psp::appendStr (pOperator, pPSCommand);
+ psp::appendStr ("\n", pPSCommand);
+
+ WritePS (mpPageBody, pPSCommand.makeStringAndClear());
+}
+
+void
+PrinterGfx::PSTranslate (const Point& rPoint)
+{
+ PSPointOp (rPoint, "translate");
+}
+
+void
+PrinterGfx::PSMoveTo (const Point& rPoint)
+{
+ PSPointOp (rPoint, "moveto");
+}
+
+void
+PrinterGfx::PSLineTo (const Point& rPoint)
+{
+ PSPointOp (rPoint, "lineto");
+}
+
+/* get a compressed representation of the path information */
+
+#define DEBUG_BINPATH 0
+
+void
+PrinterGfx::PSBinLineTo (const Point& rCurrent, Point& rOld, sal_Int32& nColumn)
+{
+#if (DEBUG_BINPATH == 1)
+ PSLineTo (rCurrent);
+#else
+ PSBinPath (rCurrent, rOld, lineto, nColumn);
+#endif
+}
+
+void
+PrinterGfx::PSBinMoveTo (const Point& rCurrent, Point& rOld, sal_Int32& nColumn)
+{
+#if (DEBUG_BINPATH == 1)
+ PSMoveTo (rCurrent);
+#else
+ PSBinPath (rCurrent, rOld, moveto, nColumn);
+#endif
+}
+
+void
+PrinterGfx::PSBinStartPath ()
+{
+#if (DEBUG_BINPATH == 1)
+ WritePS (mpPageBody, "% PSBinStartPath\n");
+#else
+ WritePS (mpPageBody, "readpath\n" );
+#endif
+}
+
+void
+PrinterGfx::PSBinEndPath ()
+{
+#if (DEBUG_BINPATH == 1)
+ WritePS (mpPageBody, "% PSBinEndPath\n");
+#else
+ WritePS (mpPageBody, "~\n");
+#endif
+}
+
+void
+PrinterGfx::PSBinCurrentPath (sal_uInt32 nPoints, const Point* pPath)
+{
+ // create the path
+ Point aPoint (0, 0);
+ sal_Int32 nColumn = 0;
+
+ PSBinStartPath ();
+ PSBinMoveTo (*pPath, aPoint, nColumn);
+ for (unsigned int i = 1; i < nPoints; i++)
+ PSBinLineTo (pPath[i], aPoint, nColumn);
+ PSBinEndPath ();
+}
+
+void
+PrinterGfx::PSBinPath (const Point& rCurrent, Point& rOld,
+ pspath_t eType, sal_Int32& nColumn)
+{
+ OStringBuffer pPath;
+ sal_Int32 nChar;
+
+ // create the hex representation of the dx and dy path shift, store the field
+ // width as it is needed for the building the command
+ sal_Int32 nXPrec = getAlignedHexValueOf (rCurrent.X() - rOld.X(), pPath);
+ sal_Int32 nYPrec = getAlignedHexValueOf (rCurrent.Y() - rOld.Y(), pPath);
+
+ // build the command, it is a char with bit representation 000cxxyy
+ // c represents the char, xx and yy repr. the field width of the dx and dy shift,
+ // dx and dy represent the number of bytes to read after the opcode
+ char cCmd = (eType == lineto ? char(0x00) : char(0x10));
+ switch (nYPrec)
+ {
+ case 2: break;
+ case 4: cCmd |= 0x01; break;
+ case 6: cCmd |= 0x02; break;
+ case 8: cCmd |= 0x03; break;
+ default: OSL_FAIL("invalid x precision in binary path");
+ }
+ switch (nXPrec)
+ {
+ case 2: break;
+ case 4: cCmd |= 0x04; break;
+ case 6: cCmd |= 0x08; break;
+ case 8: cCmd |= 0x0c; break;
+ default: OSL_FAIL("invalid y precision in binary path");
+ }
+ cCmd += 'A';
+ pPath.insert(0, cCmd);
+ auto const path = pPath.makeStringAndClear();
+
+ // write the command to file,
+ // line breaking at column nMaxTextColumn (80)
+ nChar = 1 + nXPrec + nYPrec;
+ if ((nColumn + nChar) > nMaxTextColumn)
+ {
+ sal_Int32 nSegment = nMaxTextColumn - nColumn;
+
+ WritePS (mpPageBody, path.copy(0, nSegment));
+ WritePS (mpPageBody, "\n", 1);
+ WritePS (mpPageBody, path.copy(nSegment));
+
+ nColumn = nChar - nSegment;
+ }
+ else
+ {
+ WritePS (mpPageBody, path);
+
+ nColumn += nChar;
+ }
+
+ rOld = rCurrent;
+}
+
+void
+PrinterGfx::PSScale (double fScaleX, double fScaleY)
+{
+ OStringBuffer pScale;
+
+ psp::getValueOfDouble (pScale, fScaleX, 5);
+ psp::appendStr (" ", pScale);
+ psp::getValueOfDouble (pScale, fScaleY, 5);
+ psp::appendStr (" scale\n", pScale);
+
+ WritePS (mpPageBody, pScale.makeStringAndClear());
+}
+
+/* psshowtext helper routines: draw an hex string for show/xshow */
+void
+PrinterGfx::PSHexString (const unsigned char* pString, sal_Int16 nLen)
+{
+ OStringBuffer pHexString;
+ sal_Int32 nChar = psp::appendStr ("<", pHexString);
+ for (int i = 0; i < nLen; i++)
+ {
+ if (nChar >= (nMaxTextColumn - 1))
+ {
+ psp::appendStr ("\n", pHexString);
+ WritePS (mpPageBody, pHexString.makeStringAndClear());
+ nChar = 0;
+ }
+ nChar += psp::getHexValueOf (static_cast<sal_Int32>(pString[i]), pHexString);
+ }
+
+ psp::appendStr (">\n", pHexString);
+ WritePS (mpPageBody, pHexString.makeStringAndClear());
+}
+
+void
+PrinterGfx::PSShowGlyph (const unsigned char nGlyphId)
+{
+ PSSetColor (maTextColor);
+ PSSetColor ();
+ PSSetFont ();
+ // rotate the user coordinate system
+ if (mnTextAngle != 0)
+ {
+ PSGSave ();
+ PSRotate (mnTextAngle);
+ }
+
+ char pBuffer[256];
+ if( maVirtualStatus.mbArtBold )
+ {
+ sal_Int32 nLW = maVirtualStatus.mnTextWidth;
+ if( nLW == 0 )
+ nLW = maVirtualStatus.mnTextHeight;
+ else
+ nLW = std::min(nLW, maVirtualStatus.mnTextHeight);
+ psp::getValueOfDouble( pBuffer, static_cast<double>(nLW) / 30.0 );
+ }
+
+ // dispatch to the drawing method
+ PSHexString (&nGlyphId, 1);
+
+ if( maVirtualStatus.mbArtBold )
+ {
+ WritePS( mpPageBody, pBuffer );
+ WritePS( mpPageBody, " bshow\n" );
+ }
+ else
+ WritePS (mpPageBody, "show\n");
+
+ // restore the user coordinate system
+ if (mnTextAngle != 0)
+ PSGRestore ();
+}
+
+bool
+PrinterGfx::DrawEPS( const tools::Rectangle& rBoundingBox, void* pPtr, sal_uInt32 nSize )
+{
+ if( nSize == 0 )
+ return true;
+ if( ! mpPageBody )
+ return false;
+
+ bool bSuccess = false;
+
+ // first search the BoundingBox of the EPS data
+ SvMemoryStream aStream( pPtr, nSize, StreamMode::READ );
+ aStream.Seek( STREAM_SEEK_TO_BEGIN );
+ OString aLine;
+
+ OString aDocTitle;
+ double fLeft = 0, fRight = 0, fTop = 0, fBottom = 0;
+ bool bEndComments = false;
+ while( ! aStream.eof()
+ && ( ( fLeft == 0 && fRight == 0 && fTop == 0 && fBottom == 0 ) ||
+ ( aDocTitle.isEmpty() && !bEndComments ) )
+ )
+ {
+ aStream.ReadLine( aLine );
+ if( aLine.getLength() > 1 && aLine[0] == '%' )
+ {
+ char cChar = aLine[1];
+ if( cChar == '%' )
+ {
+ if( aLine.matchIgnoreAsciiCase( "%%BoundingBox:" ) )
+ {
+ aLine = WhitespaceToSpace( aLine.getToken(1, ':') );
+ if( !aLine.isEmpty() && aLine.indexOf( "atend" ) == -1 )
+ {
+ fLeft = StringToDouble( GetCommandLineToken( 0, aLine ) );
+ fBottom = StringToDouble( GetCommandLineToken( 1, aLine ) );
+ fRight = StringToDouble( GetCommandLineToken( 2, aLine ) );
+ fTop = StringToDouble( GetCommandLineToken( 3, aLine ) );
+ }
+ }
+ else if( aLine.matchIgnoreAsciiCase( "%%Title:" ) )
+ aDocTitle = WhitespaceToSpace( aLine.copy( 8 ) );
+ else if( aLine.matchIgnoreAsciiCase( "%%EndComments" ) )
+ bEndComments = true;
+ }
+ else if( cChar == ' ' || cChar == '\t' || cChar == '\r' || cChar == '\n' )
+ bEndComments = true;
+ }
+ else
+ bEndComments = true;
+ }
+
+ static sal_uInt16 nEps = 0;
+ if( aDocTitle.isEmpty() )
+ aDocTitle = OString::number(nEps++);
+
+ if( fLeft != fRight && fTop != fBottom )
+ {
+ double fScaleX = static_cast<double>(rBoundingBox.GetWidth())/(fRight-fLeft);
+ double fScaleY = -static_cast<double>(rBoundingBox.GetHeight())/(fTop-fBottom);
+ Point aTranslatePoint( static_cast<int>(rBoundingBox.Left()-fLeft*fScaleX),
+ static_cast<int>(rBoundingBox.Bottom()+1-fBottom*fScaleY) );
+ // prepare EPS
+ WritePS( mpPageBody,
+ "/b4_Inc_state save def\n"
+ "/dict_count countdictstack def\n"
+ "/op_count count 1 sub def\n"
+ "userdict begin\n"
+ "/showpage {} def\n"
+ "0 setgray 0 setlinecap 1 setlinewidth 0 setlinejoin\n"
+ "10 setmiterlimit [] 0 setdash newpath\n"
+ "/languagelevel where\n"
+ "{pop languagelevel\n"
+ "1 ne\n"
+ " {false setstrokeadjust false setoverprint\n"
+ " } if\n"
+ "}if\n" );
+ // set up clip path and scale
+ BeginSetClipRegion();
+ UnionClipRegion( rBoundingBox.Left(), rBoundingBox.Top(), rBoundingBox.GetWidth(), rBoundingBox.GetHeight() );
+ EndSetClipRegion();
+ PSTranslate( aTranslatePoint );
+ PSScale( fScaleX, fScaleY );
+
+ // DSC requires BeginDocument
+ WritePS( mpPageBody, "%%BeginDocument: " );
+ WritePS( mpPageBody, aDocTitle );
+ WritePS( mpPageBody, "\n" );
+
+ // write the EPS data
+ sal_uInt64 nOutLength;
+ mpPageBody->write( pPtr, nSize, nOutLength );
+ bSuccess = nOutLength == nSize;
+
+ // corresponding EndDocument
+ if( static_cast<char*>(pPtr)[ nSize-1 ] != '\n' )
+ WritePS( mpPageBody, "\n" );
+ WritePS( mpPageBody, "%%EndDocument\n" );
+
+ // clean up EPS
+ WritePS( mpPageBody,
+ "count op_count sub {pop} repeat\n"
+ "countdictstack dict_count sub {end} repeat\n"
+ "b4_Inc_state restore\n" );
+ }
+ return bSuccess;
+}
+
+/* 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 000000000..3287e365f
--- /dev/null
+++ b/vcl/unx/generic/print/genprnpsp.cxx
@@ -0,0 +1,1300 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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.
+ */
+
+// 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 <vcl/gdimtf.hxx>
+#include <vcl/idle.hxx>
+#include <vcl/print.hxx>
+#include <vcl/pdfwriter.hxx>
+#include <printerinfomanager.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/weld.hxx>
+#include <strings.hrc>
+#include <saldatabasic.hxx>
+#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)
+ {
+ vcl::Window* pWin = Application::GetDefDialogParent();
+ QueryString aQuery(pWin ? pWin->GetFrameWeld() : nullptr, 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
+ if( pJobSetup->GetDriverData() )
+ std::free( const_cast<sal_uInt8*>(pJobSetup->GetDriverData()) );
+
+ sal_uInt32 nBytes;
+ void* pBuffer = nullptr;
+ if( rData.getStreamBuffer( pBuffer, nBytes ) )
+ {
+ pJobSetup->SetDriverDataLen( nBytes );
+ pJobSetup->SetDriverData( static_cast<sal_uInt8*>(pBuffer) );
+ }
+ else
+ {
+ pJobSetup->SetDriverDataLen( 0 );
+ pJobSetup->SetDriverData( nullptr );
+ }
+ pJobSetup->SetPapersizeFromSetup( rData.m_bPapersizeFromSetup );
+}
+
+// Needs a cleaner abstraction ...
+static bool passFileToCommandLine( const OUString& rFilename, const OUString& rCommandLine )
+{
+ bool bSuccess = false;
+
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ OString aCmdLine(OUStringToOString(rCommandLine, aEncoding));
+ OString aFilename(OUStringToOString(rFilename, aEncoding));
+
+ bool bPipe = aCmdLine.indexOf( "(TMP)" ) == -1;
+
+ // setup command line for exec
+ if( ! bPipe )
+ aCmdLine = aCmdLine.replaceAll("(TMP)", aFilename);
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", (bPipe ? "piping to" : "executing")
+ << " commandline: \"" << aCmdLine << "\".");
+ struct stat aStat;
+ SAL_WARN_IF(stat( aFilename.getStr(), &aStat ),
+ "vcl.unx.print", "stat( " << aFilename << " ) failed.");
+ SAL_INFO("vcl.unx.print", "Tmp file " << aFilename
+ << " has modes: "
+ << std::showbase << std::oct
+ << (long)aStat.st_mode);
+#endif
+ const char* argv[4];
+ if( ! ( argv[ 0 ] = getenv( "SHELL" ) ) )
+ argv[ 0 ] = "/bin/sh";
+ argv[ 1 ] = "-c";
+ argv[ 2 ] = aCmdLine.getStr();
+ argv[ 3 ] = nullptr;
+
+ bool bHavePipes = false;
+ int pid, fd[2];
+
+ if( bPipe )
+ bHavePipes = pipe( fd ) == 0;
+ if( ( pid = fork() ) > 0 )
+ {
+ if( bPipe && bHavePipes )
+ {
+ close( fd[0] );
+ char aBuffer[ 2048 ];
+ FILE* fp = fopen( aFilename.getStr(), "r" );
+ while (fp && !feof(fp))
+ {
+ size_t nBytesRead = fread(aBuffer, 1, sizeof( aBuffer ), fp);
+ if (nBytesRead )
+ {
+ size_t nBytesWritten = write(fd[1], aBuffer, nBytesRead);
+ OSL_ENSURE(nBytesWritten == nBytesRead, "short write");
+ if (nBytesWritten != nBytesRead)
+ break;
+ }
+ }
+ fclose( fp );
+ close( fd[ 1 ] );
+ }
+ int status = 0;
+ if(waitpid( pid, &status, 0 ) != -1)
+ {
+ if( ! status )
+ bSuccess = true;
+ }
+ }
+ else if( ! pid )
+ {
+ if( bPipe && bHavePipes )
+ {
+ close( fd[1] );
+ if( fd[0] != STDIN_FILENO ) // not probable, but who knows :)
+ dup2( fd[0], STDIN_FILENO );
+ }
+ execv( argv[0], const_cast<char**>(argv) );
+ fprintf( stderr, "failed to execute \"%s\"\n", aCmdLine.getStr() );
+ _exit( 1 );
+ }
+ else
+ fprintf( stderr, "failed to fork\n" );
+
+ // clean up the mess
+ unlink( aFilename.getStr() );
+
+ return bSuccess;
+}
+
+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;
+}
+
+static bool createPdf( const OUString& rToFile, const OUString& rFromFile, const OUString& rCommandLine )
+{
+ return passFileToCommandLine( rFromFile, rCommandLine.replaceAll("(OUTFILE)", rToFile) );
+}
+
+/*
+ * SalInstance
+ */
+
+void SalGenericInstance::configurePspInfoPrinter(PspSalInfoPrinter *pPrinter,
+ SalPrinterQueueInfo const * pQueueInfo, ImplJobSetup* pJobSetup)
+{
+ if( pJobSetup )
+ {
+ PrinterInfoManager& rManager( PrinterInfoManager::get() );
+ PrinterInfo aInfo( rManager.getPrinterInfo( pQueueInfo->maPrinterName ) );
+ pPrinter->m_aJobData = aInfo;
+ pPrinter->m_aPrinterGfx.Init( pPrinter->m_aJobData );
+
+ 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 )
+ {
+ 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, &m_aPrinterGfx);
+ 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))
+ {
+ aInfo.resolveDefaultBackend();
+ std::free( const_cast<sal_uInt8*>(pJobSetup->GetDriverData()) );
+ pJobSetup->SetDriverData( nullptr );
+
+ sal_uInt32 nBytes;
+ void* pBuffer = nullptr;
+ aInfo.getStreamBuffer( pBuffer, nBytes );
+ pJobSetup->SetDriverDataLen( nBytes );
+ pJobSetup->SetDriverData( static_cast<sal_uInt8*>(pBuffer) );
+
+ // 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 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() ) );
+ 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() ) );
+ 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 orientation if necessary
+ if( nSetDataFlags & JobSetFlags::ORIENTATION )
+ aData.m_eOrientation = pJobSetup->GetOrientation() == Orientation::Landscape ? orientation::Landscape : orientation::Portrait;
+
+ // 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,
+ long& rOutWidth, 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:
+ if( PrinterInfoManager::get().checkFeatureToken( pJobSetup->GetPrinterName(), "pdf" ) )
+ return 1;
+ else
+ {
+ // see if the PPD contains a value to set PDF device
+ JobData aData = PrinterInfoManager::get().getPrinterInfo( pJobSetup->GetPrinterName() );
+ if( pJobSetup->GetDriverData() )
+ JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aData );
+ return aData.m_nPDFDevice > 0 ? 1 : 0;
+ }
+ case PrinterCapType::ExternalDialog:
+ return PrinterInfoManager::get().checkFeatureToken( pJobSetup->GetPrinterName(), "external_dialog" ) ? 1 : 0;
+ case PrinterCapType::UsePullModel:
+ {
+ // see if the PPD contains a value to set PDF device
+ JobData aData = PrinterInfoManager::get().getPrinterInfo( pJobSetup->GetPrinterName() );
+ if( pJobSetup->GetDriverData() )
+ JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aData );
+ return aData.m_nPDFDevice > 0 ? 1 : 0;
+ }
+ default: break;
+ }
+ return 0;
+}
+
+/*
+ * SalPrinter
+ */
+PspSalPrinter::PspSalPrinter( SalInfoPrinter* pInfoPrinter )
+ : m_pInfoPrinter( pInfoPrinter )
+ , m_nCopies( 1 )
+ , m_bCollate( false )
+ , m_bPdf( false )
+ , m_bIsPDFWriterJob( false )
+{
+}
+
+PspSalPrinter::~PspSalPrinter()
+{
+}
+
+static OUString getTmpName()
+{
+ OUString aTmp, aSys;
+ osl_createTempFile( nullptr, nullptr, &aTmp.pData );
+ osl_getSystemPathFromFileURL( aTmp.pData, &aSys.pData );
+
+ return aSys;
+}
+
+bool PspSalPrinter::StartJob(
+ const OUString* pFileName,
+ const OUString& rJobName,
+ const OUString& rAppName,
+ sal_uInt32 nCopies,
+ bool bCollate,
+ bool bDirect,
+ ImplJobSetup* pJobSetup )
+{
+ SAL_INFO( "vcl.unx.print", "PspSalPrinter::StartJob");
+ GetSalData()->m_pInstance->jobStartedPrinterUpdate();
+ m_bPdf = false;
+ if (pFileName)
+ m_aFileName = *pFileName;
+ else
+ m_aFileName.clear();
+ m_aTmpFile.clear();
+ m_nCopies = nCopies;
+ m_bCollate = bCollate;
+
+ JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), m_aJobData );
+ if( m_nCopies > 1 )
+ {
+ // in case user did not do anything (m_nCopies=1)
+ // take the default from jobsetup
+ m_aJobData.m_nCopies = m_nCopies;
+ m_aJobData.setCollate( bCollate );
+ }
+
+ int nMode = 0;
+ // check whether this printer is configured as fax
+ const PrinterInfo& rInfo( PrinterInfoManager::get().getPrinterInfo( m_aJobData.m_aPrinterName ) );
+ OUString sPdfDir;
+ if (getPdfDir(rInfo, sPdfDir))
+ {
+ m_bPdf = true;
+ m_aTmpFile = getTmpName();
+ nMode = S_IRUSR | S_IWUSR;
+
+ if( m_aFileName.isEmpty() )
+ m_aFileName = sPdfDir + "/" + rJobName + ".pdf";
+ }
+ m_aPrinterGfx.Init( m_aJobData );
+
+ return m_aPrintJob.StartJob( ! m_aTmpFile.isEmpty() ? m_aTmpFile : m_aFileName, nMode, rJobName, rAppName, m_aJobData, &m_aPrinterGfx, bDirect );
+}
+
+bool PspSalPrinter::EndJob()
+{
+ bool bSuccess = false;
+ if( m_bIsPDFWriterJob )
+ bSuccess = true;
+ else
+ {
+ bSuccess = m_aPrintJob.EndJob();
+ SAL_INFO( "vcl.unx.print", "PspSalPrinter::EndJob " << bSuccess);
+
+ if( bSuccess && m_bPdf )
+ {
+ const PrinterInfo& rInfo( PrinterInfoManager::get().getPrinterInfo( m_aJobData.m_aPrinterName ) );
+ bSuccess = createPdf( m_aFileName, m_aTmpFile, rInfo.m_aCommand );
+ }
+ }
+ GetSalData()->m_pInstance->jobEndedPrinterUpdate();
+ return bSuccess;
+}
+
+SalGraphics* PspSalPrinter::StartPage( ImplJobSetup* pJobSetup, bool )
+{
+ SAL_INFO( "vcl.unx.print", "PspSalPrinter::StartPage");
+
+ JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), m_aJobData );
+ m_xGraphics = GetGenericInstance()->CreatePrintGraphics();
+ m_xGraphics->Init(&m_aJobData, &m_aPrinterGfx);
+
+ if( m_nCopies > 1 )
+ {
+ // in case user did not do anything (m_nCopies=1)
+ // take the default from jobsetup
+ m_aJobData.m_nCopies = m_nCopies;
+ m_aJobData.setCollate( m_nCopies > 1 && m_bCollate );
+ }
+
+ m_aPrintJob.StartPage( m_aJobData );
+ m_aPrinterGfx.Init( m_aPrintJob );
+
+ return m_xGraphics.get();
+}
+
+void PspSalPrinter::EndPage()
+{
+ m_aPrintJob.EndPage();
+ m_aPrinterGfx.Clear();
+ SAL_INFO( "vcl.unx.print", "PspSalPrinter::EndPage");
+}
+
+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 long nRotatedWidth = rComp.maPageSize.Height();
+ const 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( const OUString& i_rURL, const PDFNewJobParameters& i_rNewParameters )
+ : maTmpURL( i_rURL )
+ , 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>") );
+ // mark for endjob
+ m_bIsPDFWriterJob = true;
+ // 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 );
+
+ OSL_ASSERT( m_aJobData.m_nPDFDevice > 0 );
+ m_aJobData.m_nPDFDevice = 1;
+
+ // possibly create one job for collated output
+ bool bSinglePrintJobs = false;
+ beans::PropertyValue* pSingleValue = i_rController.getValue( "PrintCollateAsSingleJobs" );
+ if( pSingleValue )
+ {
+ pSingleValue->Value >>= bSinglePrintJobs;
+ }
+
+ int nCopies = i_rController.getPrinter()->GetCopyCount();
+ bool bCollate = i_rController.getPrinter()->IsCollateCopy();
+
+ // 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 jobStarted() { nActiveJobs++; }
+ static void jobEnded();
+};
+
+}
+
+Idle* PrinterUpdate::pPrinterUpdateIdle = nullptr;
+int PrinterUpdate::nActiveJobs = 0;
+
+void PrinterUpdate::doUpdate()
+{
+ ::psp::PrinterInfoManager& rManager( ::psp::PrinterInfoManager::get() );
+ SalGenericInstance *pInst = static_cast<SalGenericInstance *>( GetSalData()->m_pInstance );
+ 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 SalGenericInstance::jobStartedPrinterUpdate()
+{
+ PrinterUpdate::jobStarted();
+}
+
+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 000000000..235f45eb8
--- /dev/null
+++ b/vcl/unx/generic/print/genpspgraphics.cxx
@@ -0,0 +1,1006 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <vcl/bitmapaccess.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 <unx/printergfx.hxx>
+#include <langboost.hxx>
+#include <fontinstance.hxx>
+#include <fontattributes.hxx>
+#include <impfontmetricdata.hxx>
+#include <PhysicalFontCollection.hxx>
+#include <PhysicalFontFace.hxx>
+#include <salbmp.hxx>
+#include <sallayout.hxx>
+
+using namespace psp;
+
+// ----- Implementation of PrinterBmp by means of SalBitmap/BitmapBuffer ---------------
+
+namespace {
+
+class SalPrinterBmp : public psp::PrinterBmp
+{
+private:
+ BitmapBuffer* mpBmpBuffer;
+
+ FncGetPixel mpFncGetPixel;
+ Scanline mpScanAccess;
+ sal_PtrDiff mnScanOffset;
+
+public:
+ explicit SalPrinterBmp (BitmapBuffer* pBitmap);
+
+ virtual sal_uInt32 GetPaletteColor (sal_uInt32 nIdx) const override;
+ virtual sal_uInt32 GetPaletteEntryCount () const override;
+ virtual sal_uInt32 GetPixelRGB (sal_uInt32 nRow, sal_uInt32 nColumn) const override;
+ virtual sal_uInt8 GetPixelGray (sal_uInt32 nRow, sal_uInt32 nColumn) const override;
+ virtual sal_uInt8 GetPixelIdx (sal_uInt32 nRow, sal_uInt32 nColumn) const override;
+ virtual sal_uInt32 GetDepth () const override;
+};
+
+ bool Bitmap32IsPreMultipled()
+ {
+ auto pBackendCapabilities = ImplGetSVData()->mpDefInst->GetBackendCapabilities();
+ return pBackendCapabilities->mbSupportsBitmap32;
+ }
+}
+
+SalPrinterBmp::SalPrinterBmp (BitmapBuffer* pBuffer)
+ : mpBmpBuffer(pBuffer)
+{
+ assert(mpBmpBuffer && "SalPrinterBmp::SalPrinterBmp () can't acquire Bitmap");
+
+ // calibrate scanline buffer
+ if( mpBmpBuffer->mnFormat & ScanlineFormat::TopDown )
+ {
+ mpScanAccess = mpBmpBuffer->mpBits;
+ mnScanOffset = mpBmpBuffer->mnScanlineSize;
+ }
+ else
+ {
+ mpScanAccess = mpBmpBuffer->mpBits
+ + (mpBmpBuffer->mnHeight - 1) * mpBmpBuffer->mnScanlineSize;
+ mnScanOffset = - mpBmpBuffer->mnScanlineSize;
+ }
+
+ // request read access to the pixels
+ switch( RemoveScanline( mpBmpBuffer->mnFormat ) )
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ mpFncGetPixel = BitmapReadAccess::GetPixelForN1BitMsbPal; break;
+ case ScanlineFormat::N1BitLsbPal:
+ mpFncGetPixel = BitmapReadAccess::GetPixelForN1BitLsbPal; break;
+ case ScanlineFormat::N4BitMsnPal:
+ mpFncGetPixel = BitmapReadAccess::GetPixelForN4BitMsnPal; break;
+ case ScanlineFormat::N4BitLsnPal:
+ mpFncGetPixel = BitmapReadAccess::GetPixelForN4BitLsnPal; break;
+ case ScanlineFormat::N8BitPal:
+ mpFncGetPixel = BitmapReadAccess::GetPixelForN8BitPal; break;
+ case ScanlineFormat::N8BitTcMask:
+ mpFncGetPixel = BitmapReadAccess::GetPixelForN8BitTcMask; break;
+ case ScanlineFormat::N24BitTcBgr:
+ mpFncGetPixel = BitmapReadAccess::GetPixelForN24BitTcBgr; break;
+ case ScanlineFormat::N24BitTcRgb:
+ mpFncGetPixel = BitmapReadAccess::GetPixelForN24BitTcRgb; break;
+ case ScanlineFormat::N32BitTcAbgr:
+ if (Bitmap32IsPreMultipled())
+ mpFncGetPixel = BitmapReadAccess::GetPixelForN32BitTcAbgr;
+ else
+ mpFncGetPixel = BitmapReadAccess::GetPixelForN32BitTcXbgr;
+ break;
+ case ScanlineFormat::N32BitTcArgb:
+ if (Bitmap32IsPreMultipled())
+ mpFncGetPixel = BitmapReadAccess::GetPixelForN32BitTcArgb;
+ else
+ mpFncGetPixel = BitmapReadAccess::GetPixelForN32BitTcXrgb;
+ break;
+ case ScanlineFormat::N32BitTcBgra:
+ if (Bitmap32IsPreMultipled())
+ mpFncGetPixel = BitmapReadAccess::GetPixelForN32BitTcBgra;
+ else
+ mpFncGetPixel = BitmapReadAccess::GetPixelForN32BitTcBgrx;
+ break;
+ case ScanlineFormat::N32BitTcRgba:
+ if (Bitmap32IsPreMultipled())
+ mpFncGetPixel = BitmapReadAccess::GetPixelForN32BitTcRgba;
+ else
+ mpFncGetPixel = BitmapReadAccess::GetPixelForN32BitTcRgbx;
+ break;
+ case ScanlineFormat::N32BitTcMask:
+ mpFncGetPixel = BitmapReadAccess::GetPixelForN32BitTcMask; break;
+ default:
+ OSL_FAIL("Error: SalPrinterBmp::SalPrinterBmp() unknown bitmap format");
+ mpFncGetPixel = nullptr;
+ break;
+ }
+}
+
+sal_uInt32
+SalPrinterBmp::GetDepth () const
+{
+ sal_uInt32 nDepth;
+
+ switch (mpBmpBuffer->mnBitCount)
+ {
+ case 1:
+ nDepth = 1;
+ break;
+
+ case 4:
+ case 8:
+ nDepth = 8;
+ break;
+
+ case 24:
+ case 32:
+ nDepth = 24;
+ break;
+
+ default:
+ nDepth = 1;
+ assert(false && "Error: unsupported bitmap depth in SalPrinterBmp::GetDepth()");
+ break;
+ }
+
+ return nDepth;
+}
+
+sal_uInt32
+SalPrinterBmp::GetPaletteEntryCount () const
+{
+ return mpBmpBuffer->maPalette.GetEntryCount ();
+}
+
+sal_uInt32
+SalPrinterBmp::GetPaletteColor(sal_uInt32 nIdx) const
+{
+ BitmapColor aColor(mpBmpBuffer->maPalette[nIdx]);
+
+ return ((aColor.GetBlue()) & 0x000000ff)
+ | ((aColor.GetGreen() << 8) & 0x0000ff00)
+ | ((aColor.GetRed() << 16) & 0x00ff0000);
+}
+
+sal_uInt32
+SalPrinterBmp::GetPixelRGB (sal_uInt32 nRow, sal_uInt32 nColumn) const
+{
+ Scanline pScan = mpScanAccess + nRow * mnScanOffset;
+ BitmapColor aColor = mpFncGetPixel (pScan, nColumn, mpBmpBuffer->maColorMask);
+
+ if (!!mpBmpBuffer->maPalette)
+ GetPaletteColor(aColor.GetIndex());
+
+ return ((aColor.GetBlue()) & 0x000000ff)
+ | ((aColor.GetGreen() << 8) & 0x0000ff00)
+ | ((aColor.GetRed() << 16) & 0x00ff0000);
+}
+
+sal_uInt8
+SalPrinterBmp::GetPixelGray (sal_uInt32 nRow, sal_uInt32 nColumn) const
+{
+ Scanline pScan = mpScanAccess + nRow * mnScanOffset;
+ BitmapColor aColor = mpFncGetPixel (pScan, nColumn, mpBmpBuffer->maColorMask);
+
+ if (!!mpBmpBuffer->maPalette)
+ aColor = mpBmpBuffer->maPalette[aColor.GetIndex()];
+
+ return ( aColor.GetBlue() * 28UL
+ + aColor.GetGreen() * 151UL
+ + aColor.GetRed() * 77UL ) >> 8;
+
+}
+
+sal_uInt8
+SalPrinterBmp::GetPixelIdx (sal_uInt32 nRow, sal_uInt32 nColumn) const
+{
+ Scanline pScan = mpScanAccess + nRow * mnScanOffset;
+ BitmapColor aColor = mpFncGetPixel (pScan, nColumn, mpBmpBuffer->maColorMask);
+
+ if (!!mpBmpBuffer->maPalette)
+ return aColor.GetIndex();
+ else
+ return 0;
+}
+
+/*******************************************************
+ * GenPspGraphics *
+ *******************************************************/
+GenPspGraphics::GenPspGraphics()
+ : m_pJobData( nullptr ),
+ m_pPrinterGfx( nullptr )
+{
+}
+
+void GenPspGraphics::Init(psp::JobData* pJob, psp::PrinterGfx* pGfx)
+{
+ m_pJobData = pJob;
+ m_pPrinterGfx = pGfx;
+ 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;
+ }
+}
+
+sal_uInt16 GenPspGraphics::GetBitCount() const
+{
+ return m_pPrinterGfx->GetBitCount();
+}
+
+long GenPspGraphics::GetGraphicsWidth() const
+{
+ return 0;
+}
+
+void GenPspGraphics::ResetClipRegion()
+{
+ m_pPrinterGfx->ResetClipRegion();
+}
+
+bool GenPspGraphics::setClipRegion( const vcl::Region& i_rClip )
+{
+ // TODO: support polygonal clipregions here
+ RectangleVector aRectangles;
+ i_rClip.GetRegionRectangles(aRectangles);
+ m_pPrinterGfx->BeginSetClipRegion();
+
+ for (auto const& rectangle : aRectangles)
+ {
+ const long nW(rectangle.GetWidth());
+
+ if(nW)
+ {
+ const long nH(rectangle.GetHeight());
+
+ if(nH)
+ {
+ m_pPrinterGfx->UnionClipRegion(
+ rectangle.Left(),
+ rectangle.Top(),
+ nW,
+ nH);
+ }
+ }
+ }
+
+ m_pPrinterGfx->EndSetClipRegion();
+
+ //m_pPrinterGfx->BeginSetClipRegion( i_rClip.GetRectCount() );
+
+ //ImplRegionInfo aInfo;
+ //long nX, nY, nW, nH;
+ //bool bRegionRect = i_rClip.ImplGetFirstRect(aInfo, nX, nY, nW, nH );
+ //while( bRegionRect )
+ //{
+ // if ( nW && nH )
+ // {
+ // m_pPrinterGfx->UnionClipRegion( nX, nY, nW, nH );
+ // }
+ // bRegionRect = i_rClip.ImplGetNextRect( aInfo, nX, nY, nW, nH );
+ //}
+ //m_pPrinterGfx->EndSetClipRegion();
+ return true;
+}
+
+void GenPspGraphics::SetLineColor()
+{
+ m_pPrinterGfx->SetLineColor ();
+}
+
+void GenPspGraphics::SetLineColor( Color nColor )
+{
+ psp::PrinterColor aColor (nColor.GetRed(),
+ nColor.GetGreen(),
+ nColor.GetBlue());
+ m_pPrinterGfx->SetLineColor (aColor);
+}
+
+void GenPspGraphics::SetFillColor()
+{
+ m_pPrinterGfx->SetFillColor ();
+}
+
+void GenPspGraphics::SetFillColor( Color nColor )
+{
+ psp::PrinterColor aColor (nColor.GetRed(),
+ nColor.GetGreen(),
+ nColor.GetBlue());
+ m_pPrinterGfx->SetFillColor (aColor);
+}
+
+void GenPspGraphics::SetROPLineColor( SalROPColor )
+{
+ SAL_WARN( "vcl", "Error: PrinterGfx::SetROPLineColor() not implemented" );
+}
+
+void GenPspGraphics::SetROPFillColor( SalROPColor )
+{
+ SAL_WARN( "vcl", "Error: PrinterGfx::SetROPFillColor() not implemented" );
+}
+
+void GenPspGraphics::SetXORMode( bool bSet, bool )
+{
+ SAL_WARN_IF( bSet, "vcl", "Error: PrinterGfx::SetXORMode() not implemented" );
+}
+
+void GenPspGraphics::drawPixel( long nX, long nY )
+{
+ m_pPrinterGfx->DrawPixel (Point(nX, nY));
+}
+
+void GenPspGraphics::drawPixel( long nX, long nY, Color nColor )
+{
+ psp::PrinterColor aColor (nColor.GetRed(),
+ nColor.GetGreen(),
+ nColor.GetBlue());
+ m_pPrinterGfx->DrawPixel (Point(nX, nY), aColor);
+}
+
+void GenPspGraphics::drawLine( long nX1, long nY1, long nX2, long nY2 )
+{
+ m_pPrinterGfx->DrawLine (Point(nX1, nY1), Point(nX2, nY2));
+}
+
+void GenPspGraphics::drawRect( long nX, long nY, long nDX, long nDY )
+{
+ m_pPrinterGfx->DrawRect (tools::Rectangle(Point(nX, nY), Size(nDX, nDY)));
+}
+
+void GenPspGraphics::drawPolyLine( sal_uInt32 nPoints, const SalPoint *pPtAry )
+{
+ m_pPrinterGfx->DrawPolyLine (nPoints, reinterpret_cast<const Point *>(pPtAry));
+}
+
+void GenPspGraphics::drawPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry )
+{
+ // Point must be equal to SalPoint! see include/vcl/salgtype.hxx
+ m_pPrinterGfx->DrawPolygon (nPoints, reinterpret_cast<const Point *>(pPtAry));
+}
+
+void GenPspGraphics::drawPolyPolygon( sal_uInt32 nPoly,
+ const sal_uInt32 *pPoints,
+ PCONSTSALPOINT *pPtAry )
+{
+ m_pPrinterGfx->DrawPolyPolygon (nPoly, pPoints, reinterpret_cast<const Point**>(pPtAry));
+}
+
+bool GenPspGraphics::drawPolyPolygon(
+ const basegfx::B2DHomMatrix& /*rObjectToDevice*/,
+ const basegfx::B2DPolyPolygon&,
+ double /*fTransparency*/)
+{
+ // TODO: implement and advertise OutDevSupportType::B2DDraw support
+ return false;
+}
+
+bool GenPspGraphics::drawPolyLine(
+ const basegfx::B2DHomMatrix& /* rObjectToDevice */,
+ const basegfx::B2DPolygon&,
+ double /*fTransparency*/,
+ double /*fLineWidth*/,
+ const std::vector< double >* /*pStroke*/, // MM01
+ basegfx::B2DLineJoin /*eJoin*/,
+ css::drawing::LineCap /*eLineCap*/,
+ double /*fMiterMinimumAngle*/,
+ bool /* bPixelSnapHairline */)
+{
+ // TODO: a PS printer can draw B2DPolyLines almost directly
+ return false;
+}
+
+bool GenPspGraphics::drawPolyLineBezier( sal_uInt32 nPoints, const SalPoint* pPtAry, const PolyFlags* pFlgAry )
+{
+ m_pPrinterGfx->DrawPolyLineBezier (nPoints, reinterpret_cast<Point const *>(pPtAry), pFlgAry);
+ return true;
+}
+
+bool GenPspGraphics::drawPolygonBezier( sal_uInt32 nPoints, const SalPoint* pPtAry, const PolyFlags* pFlgAry )
+{
+ m_pPrinterGfx->DrawPolygonBezier (nPoints, reinterpret_cast<Point const *>(pPtAry), pFlgAry);
+ return true;
+}
+
+bool GenPspGraphics::drawPolyPolygonBezier( sal_uInt32 nPoly,
+ const sal_uInt32* pPoints,
+ const SalPoint* const* pPtAry,
+ const PolyFlags* const* pFlgAry )
+{
+ // Point must be equal to SalPoint! see include/vcl/salgtype.hxx
+ m_pPrinterGfx->DrawPolyPolygonBezier (nPoly, pPoints, reinterpret_cast<Point const * const *>(pPtAry), pFlgAry);
+ return true;
+}
+
+void GenPspGraphics::invert( sal_uInt32,
+ const SalPoint*,
+ SalInvert )
+{
+ SAL_WARN( "vcl", "Error: PrinterGfx::Invert() not implemented" );
+}
+
+bool GenPspGraphics::drawEPS( long nX, long nY, long nWidth, long nHeight, void* pPtr, sal_uInt32 nSize )
+{
+ return m_pPrinterGfx->DrawEPS( tools::Rectangle( Point( nX, nY ), Size( nWidth, nHeight ) ), pPtr, nSize );
+}
+
+void GenPspGraphics::copyBits( const SalTwoRect&,
+ SalGraphics* )
+{
+ OSL_FAIL( "Error: PrinterGfx::CopyBits() not implemented" );
+}
+
+void GenPspGraphics::copyArea ( long,long,long,long,long,long,bool )
+{
+ OSL_FAIL( "Error: PrinterGfx::CopyArea() not implemented" );
+}
+
+void GenPspGraphics::drawBitmap( const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap )
+{
+ tools::Rectangle aSrc (Point(rPosAry.mnSrcX, rPosAry.mnSrcY),
+ Size(rPosAry.mnSrcWidth, rPosAry.mnSrcHeight));
+ tools::Rectangle aDst (Point(rPosAry.mnDestX, rPosAry.mnDestY),
+ Size(rPosAry.mnDestWidth, rPosAry.mnDestHeight));
+
+ BitmapBuffer* pBuffer= const_cast<SalBitmap&>(rSalBitmap).AcquireBuffer(BitmapAccessMode::Read);
+
+ SalPrinterBmp aBmp (pBuffer);
+ m_pPrinterGfx->DrawBitmap (aDst, aSrc, aBmp);
+
+ const_cast<SalBitmap&>(rSalBitmap).ReleaseBuffer (pBuffer, BitmapAccessMode::Read);
+}
+
+void GenPspGraphics::drawBitmap( const SalTwoRect&,
+ const SalBitmap&,
+ const SalBitmap& )
+{
+ OSL_FAIL("Error: no PrinterGfx::DrawBitmap() for transparent bitmap");
+}
+
+void GenPspGraphics::drawMask( const SalTwoRect&,
+ const SalBitmap &,
+ Color )
+{
+ OSL_FAIL("Error: PrinterGfx::DrawMask() not implemented");
+}
+
+std::shared_ptr<SalBitmap> GenPspGraphics::getBitmap( long, long, long, long )
+{
+ SAL_INFO("vcl", "Warning: PrinterGfx::GetBitmap() not implemented");
+ return nullptr;
+}
+
+Color GenPspGraphics::getPixel( long, long )
+{
+ OSL_FAIL("Warning: PrinterGfx::GetPixel() not implemented");
+ return 0;
+}
+
+void GenPspGraphics::invert(long,long,long,long,SalInvert)
+{
+ OSL_FAIL("Warning: PrinterGfx::Invert() not implemented");
+}
+
+namespace {
+
+class ImplPspFontData : public FreetypeFontFace
+{
+private:
+ sal_IntPtr mnFontId;
+
+public:
+ explicit ImplPspFontData( const psp::FastPrintFontInfo& );
+ virtual sal_IntPtr GetFontId() const override { return mnFontId; }
+};
+
+}
+
+ImplPspFontData::ImplPspFontData(const psp::FastPrintFontInfo& rInfo)
+: FreetypeFontFace(nullptr, GenPspGraphics::Info2FontAttributes(rInfo)),
+ mnFontId( rInfo.m_nID )
+{}
+
+namespace {
+
+class PspSalLayout : public GenericSalLayout
+{
+public:
+ PspSalLayout(psp::PrinterGfx&, LogicalFontInstance &rFontInstance);
+
+ void InitFont() const final override;
+
+private:
+ ::psp::PrinterGfx& mrPrinterGfx;
+ sal_IntPtr mnFontID;
+ int mnFontHeight;
+ int mnFontWidth;
+ bool mbVertical;
+ bool mbArtItalic;
+ bool mbArtBold;
+};
+
+}
+
+PspSalLayout::PspSalLayout(::psp::PrinterGfx& rGfx, LogicalFontInstance &rFontInstance)
+: GenericSalLayout(rFontInstance)
+, mrPrinterGfx(rGfx)
+{
+ mnFontID = mrPrinterGfx.GetFontID();
+ mnFontHeight = mrPrinterGfx.GetFontHeight();
+ mnFontWidth = mrPrinterGfx.GetFontWidth();
+ mbVertical = mrPrinterGfx.GetFontVertical();
+ mbArtItalic = mrPrinterGfx.GetArtificialItalic();
+ mbArtBold = mrPrinterGfx.GetArtificialBold();
+}
+
+void PspSalLayout::InitFont() const
+{
+ GenericSalLayout::InitFont();
+ mrPrinterGfx.SetFont(mnFontID, mnFontHeight, mnFontWidth,
+ mnOrientation, mbVertical, mbArtItalic, mbArtBold);
+}
+
+void GenPspGraphics::DrawTextLayout(const GenericSalLayout& rLayout)
+{
+ const GlyphItem* pGlyph;
+ Point aPos;
+ int nStart = 0;
+ while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart))
+ m_pPrinterGfx->DrawGlyph(aPos, *pGlyph);
+}
+
+FontCharMapRef GenPspGraphics::GetFontCharMap() const
+{
+ if (!m_pFreetypeFont[0])
+ return nullptr;
+
+ return m_pFreetypeFont[0]->GetFreetypeFont().GetFontCharMap();
+}
+
+bool GenPspGraphics::GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const
+{
+ if (!m_pFreetypeFont[0])
+ return false;
+
+ return m_pFreetypeFont[0]->GetFreetypeFont().GetFontCapabilities(rFontCapabilities);
+}
+
+void GenPspGraphics::SetFont(LogicalFontInstance *pFontInstance, int nFallbackLevel)
+{
+ // release all fonts that are to be overridden
+ for( int i = nFallbackLevel; i < MAX_FALLBACK; ++i )
+ {
+ // old server side font is no longer referenced
+ m_pFreetypeFont[i] = nullptr;
+ }
+
+ // return early if there is no new font
+ if (!pFontInstance)
+ return;
+
+ sal_IntPtr nID = pFontInstance->GetFontFace()->GetFontId();
+
+ const FontSelectPattern& rEntry = pFontInstance->GetFontSelectPattern();
+
+ // determine which font attributes need to be emulated
+ bool bArtItalic = false;
+ bool bArtBold = false;
+ if( rEntry.GetItalic() == ITALIC_OBLIQUE || rEntry.GetItalic() == ITALIC_NORMAL )
+ {
+ FontItalic eItalic = m_pPrinterGfx->GetFontMgr().getFontItalic( nID );
+ if( eItalic != ITALIC_NORMAL && eItalic != ITALIC_OBLIQUE )
+ bArtItalic = true;
+ }
+ int nWeight = static_cast<int>(rEntry.GetWeight());
+ int nRealWeight = static_cast<int>(m_pPrinterGfx->GetFontMgr().getFontWeight( nID ));
+ if( nRealWeight <= int(WEIGHT_MEDIUM) && nWeight > int(WEIGHT_MEDIUM) )
+ {
+ bArtBold = true;
+ }
+
+ // also set the serverside font for layouting
+ // requesting a font provided by builtin rasterizer
+ FreetypeFontInstance* pFreetypeFont = static_cast<FreetypeFontInstance*>(pFontInstance);
+ m_pFreetypeFont[ nFallbackLevel ] = pFreetypeFont;
+
+ // ignore fonts with e.g. corrupted font files
+ if (!m_pFreetypeFont[nFallbackLevel]->GetFreetypeFont().TestFont())
+ m_pFreetypeFont[nFallbackLevel] = nullptr;
+
+ // set the printer font
+ m_pPrinterGfx->SetFont( nID,
+ rEntry.mnHeight,
+ rEntry.mnWidth,
+ rEntry.mnOrientation,
+ rEntry.mbVertical,
+ bArtItalic,
+ bArtBold
+ );
+}
+
+void GenPspGraphics::SetTextColor( Color nColor )
+{
+ psp::PrinterColor aColor (nColor.GetRed(),
+ nColor.GetGreen(),
+ nColor.GetBlue());
+ m_pPrinterGfx->SetTextColor (aColor);
+}
+
+bool GenPspGraphics::AddTempDevFont( PhysicalFontCollection*, const OUString&,const OUString& )
+{
+ return false;
+}
+
+bool GenPspGraphics::AddTempDevFontHelper( 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& elem : aFontIds)
+ {
+ // prepare font data
+ psp::FastPrintFontInfo aInfo;
+ rMgr.getFontFastInfo( elem, aInfo );
+ aInfo.m_aFamilyName = rFontName;
+
+ // inform glyph cache of new font
+ FontAttributes aDFA = GenPspGraphics::Info2FontAttributes( aInfo );
+ aDFA.IncreaseQualityBy( 5800 );
+
+ int nFaceNum = rMgr.getFontFaceNumber( aInfo.m_nID );
+ int nVariantNum = rMgr.getFontFaceVariation( aInfo.m_nID );
+
+ const OString& rFileName = rMgr.getFontFileSysPath( aInfo.m_nID );
+ rFreetypeManager.AddFontFile(rFileName, nFaceNum, nVariantNum, aInfo.m_nID, aDFA);
+ }
+
+ // announce new font to device's font list
+ rFreetypeManager.AnnounceFonts(pFontCollection);
+ return true;
+}
+
+void GenPspGraphics::GetDevFontList( PhysicalFontCollection *pFontCollection )
+{
+ ::std::vector< psp::fontID > aList;
+ psp::PrintFontManager& rMgr = psp::PrintFontManager::get();
+ rMgr.getFontList( aList );
+
+ psp::FastPrintFontInfo aInfo;
+ for (auto const& elem : aList)
+ if (rMgr.getFontFastInfo (elem, aInfo))
+ AnnounceFonts( pFontCollection, aInfo );
+
+ // register platform specific font substitutions if available
+ SalGenericInstance::RegisterFontSubstitutors( pFontCollection );
+}
+
+void GenPspGraphics::ClearDevFontCache()
+{
+ FreetypeManager::get().ClearFontCache();
+}
+
+void GenPspGraphics::GetFontMetric(ImplFontMetricDataRef& rxFontMetric, int nFallbackLevel)
+{
+ if (nFallbackLevel >= MAX_FALLBACK)
+ return;
+
+ if (m_pFreetypeFont[nFallbackLevel])
+ m_pFreetypeFont[nFallbackLevel]->GetFreetypeFont().GetFontMetric(rxFontMetric);
+}
+
+std::unique_ptr<GenericSalLayout> GenPspGraphics::GetTextLayout(int nFallbackLevel)
+{
+ assert(m_pFreetypeFont[nFallbackLevel]);
+ if (!m_pFreetypeFont[nFallbackLevel])
+ return nullptr;
+ return std::make_unique<PspSalLayout>(*m_pPrinterGfx, *m_pFreetypeFont[nFallbackLevel]);
+}
+
+bool GenPspGraphics::CreateFontSubset(
+ const OUString& rToFile,
+ const PhysicalFontFace* pFont,
+ const sal_GlyphId* pGlyphIds,
+ const sal_uInt8* pEncoding,
+ sal_Int32* pWidths,
+ int nGlyphCount,
+ FontSubsetInfo& rInfo
+ )
+{
+ // in this context the pFont->GetFontId() is a valid PSP
+ // font since they are the only ones left after the PDF
+ // export has filtered its list of subsettable fonts (for
+ // which this method was created). The correct way would
+ // be to have the FreetypeManager search for the PhysicalFontFace pFont
+ psp::fontID aFont = pFont->GetFontId();
+
+ psp::PrintFontManager& rMgr = psp::PrintFontManager::get();
+ bool bSuccess = rMgr.createFontSubset( rInfo,
+ aFont,
+ rToFile,
+ pGlyphIds,
+ pEncoding,
+ pWidths,
+ nGlyphCount );
+ return bSuccess;
+}
+
+void GenPspGraphics::GetGlyphWidths( const PhysicalFontFace* pFont,
+ bool bVertical,
+ std::vector< sal_Int32 >& rWidths,
+ Ucs2UIntMap& rUnicodeEnc )
+{
+ // in this context the pFont->GetFontId() is a valid PSP
+ // font since they are the only ones left after the PDF
+ // export has filtered its list of subsettable fonts (for
+ // which this method was created). The correct way would
+ // be to have the FreetypeManager search for the PhysicalFontFace pFont
+ psp::fontID aFont = pFont->GetFontId();
+ GenPspGraphics::DoGetGlyphWidths( aFont, bVertical, rWidths, rUnicodeEnc );
+}
+
+void GenPspGraphics::DoGetGlyphWidths( psp::fontID aFont,
+ bool bVertical,
+ std::vector< sal_Int32 >& rWidths,
+ Ucs2UIntMap& rUnicodeEnc )
+{
+ psp::PrintFontManager& rMgr = psp::PrintFontManager::get();
+ rMgr.getGlyphWidths( aFont, bVertical, rWidths, rUnicodeEnc );
+}
+
+FontAttributes GenPspGraphics::Info2FontAttributes( const psp::FastPrintFontInfo& rInfo )
+{
+ FontAttributes aDFA;
+ aDFA.SetFamilyName( rInfo.m_aFamilyName );
+ aDFA.SetStyleName( rInfo.m_aStyleName );
+ aDFA.SetFamilyType( rInfo.m_eFamilyStyle );
+ aDFA.SetWeight( rInfo.m_eWeight );
+ aDFA.SetItalic( rInfo.m_eItalic );
+ aDFA.SetWidthType( rInfo.m_eWidth );
+ aDFA.SetPitch( rInfo.m_ePitch );
+ aDFA.SetSymbolFlag( rInfo.m_aEncoding == RTL_TEXTENCODING_SYMBOL );
+ aDFA.SetQuality(512);
+
+ // add font family name aliases
+ for (auto const& alias : rInfo.m_aAliases)
+ aDFA.AddMapName(alias);
+
+#if OSL_DEBUG_LEVEL > 2
+ if( aDFA.GetMapNames().getLength() > 0 )
+ {
+ SAL_INFO( "vcl.fonts", "using alias names " << aDFA.GetMapNames() << " for font family " << aDFA.GetFamilyName() );
+ }
+#endif
+
+ return aDFA;
+}
+
+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;
+ }
+}
+
+void GenPspGraphics::AnnounceFonts( PhysicalFontCollection* pFontCollection, const psp::FastPrintFontInfo& aInfo )
+{
+ int nQuality = 0;
+
+ psp::PrintFontManager& rMgr = psp::PrintFontManager::get();
+ OString aFileName( rMgr.getFontFileSysPath( aInfo.m_nID ) );
+ int nPos = aFileName.lastIndexOf( '_' );
+ if( nPos == -1 || aFileName[nPos+1] == '.' )
+ nQuality += 5;
+ else
+ {
+ static const char* pLangBoost = nullptr;
+ static bool bOnce = true;
+ if( bOnce )
+ {
+ bOnce = false;
+ pLangBoost = vcl::getLangBoost();
+ }
+
+ if( pLangBoost )
+ if( aFileName.copy( nPos+1, 3 ).equalsIgnoreAsciiCase( pLangBoost ) )
+ nQuality += 10;
+ }
+
+ rtl::Reference<ImplPspFontData> pFD(new ImplPspFontData( aInfo ));
+ pFD->IncreaseQualityBy( nQuality );
+ pFontCollection->Add( pFD.get() );
+}
+
+bool GenPspGraphics::blendBitmap( const SalTwoRect&, const SalBitmap& )
+{
+ return false;
+}
+
+bool GenPspGraphics::blendAlphaBitmap( const SalTwoRect&, const SalBitmap&, const SalBitmap&, const SalBitmap& )
+{
+ return false;
+}
+
+bool GenPspGraphics::drawAlphaBitmap( const SalTwoRect&,
+ const SalBitmap&,
+ const SalBitmap& )
+{
+ return false;
+}
+
+bool GenPspGraphics::drawTransformedBitmap(
+ const basegfx::B2DPoint&,
+ const basegfx::B2DPoint&,
+ const basegfx::B2DPoint&,
+ const SalBitmap&,
+ const SalBitmap*)
+{
+ // here direct support for transformed bitmaps can be implemented
+ return false;
+}
+
+bool GenPspGraphics::drawAlphaRect( long, long, long, long, sal_uInt8 )
+{
+ return false;
+}
+
+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();
+}
+
+SystemFontData GenPspGraphics::GetSysFontData( int /* nFallbacklevel */ ) const
+{
+ return SystemFontData();
+}
+
+#endif // ENABLE_CAIRO_CANVAS
+
+bool GenPspGraphics::supportsOperation( OutDevSupportType ) const
+{
+ return false;
+}
+
+void GenPspGraphics::DoFreeEmbedFontData( const void* pData, long nLen )
+{
+ if( pData )
+ munmap( const_cast<void *>(pData), nLen );
+}
+
+const void* GenPspGraphics::DoGetEmbedFontData(psp::fontID aFont, long* pDataLen)
+{
+
+ psp::PrintFontManager& rMgr = psp::PrintFontManager::get();
+
+ OString aSysPath = rMgr.getFontFileSysPath( aFont );
+
+ int fd = open( aSysPath.getStr(), O_RDONLY );
+ if( fd < 0 )
+ return nullptr;
+ struct stat aStat;
+ if( fstat( fd, &aStat ) )
+ {
+ close( fd );
+ return nullptr;
+ }
+ void* pFile = mmap( nullptr, aStat.st_size, PROT_READ, MAP_SHARED, fd, 0 );
+ close( fd );
+ if( pFile == MAP_FAILED )
+ return nullptr;
+ *pDataLen = aStat.st_size;
+
+ return pFile;
+}
+
+void GenPspGraphics::FreeEmbedFontData( const void* pData, long nLen )
+{
+ DoFreeEmbedFontData( pData, nLen );
+}
+
+const void* GenPspGraphics::GetEmbedFontData(const PhysicalFontFace* pFont, long* pDataLen)
+{
+ // in this context the pFont->GetFontId() is a valid PSP
+ // font since they are the only ones left after the PDF
+ // export has filtered its list of subsettable fonts (for
+ // which this method was created). The correct way would
+ // be to have the FreetypeManager search for the PhysicalFontFace pFont
+ psp::fontID aFont = pFont->GetFontId();
+ return DoGetEmbedFontData(aFont, pDataLen);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/print/glyphset.cxx b/vcl/unx/generic/print/glyphset.cxx
new file mode 100644
index 000000000..c27348afc
--- /dev/null
+++ b/vcl/unx/generic/print/glyphset.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "glyphset.hxx"
+
+#include <sft.hxx>
+
+#include <unx/printergfx.hxx>
+#include <fontsubset.hxx>
+#include <unx/fontmanager.hxx>
+
+#include <tools/gen.hxx>
+
+#include <osl/thread.h>
+
+#include <rtl/ustring.hxx>
+#include <rtl/strbuf.hxx>
+
+#include <unotools/tempfile.hxx>
+
+#include <algorithm>
+
+using namespace vcl;
+using namespace psp;
+
+GlyphSet::GlyphSet (sal_Int32 nFontID, bool bVertical)
+ : mnFontID (nFontID),
+ mbVertical (bVertical)
+{
+ PrintFontManager &rMgr = PrintFontManager::get();
+ maBaseName = OUStringToOString (rMgr.getPSName(mnFontID),
+ RTL_TEXTENCODING_ASCII_US);
+}
+
+void
+GlyphSet::GetGlyphID (
+ sal_GlyphId nGlyph,
+ unsigned char* nOutGlyphID,
+ sal_Int32* nOutGlyphSetID
+ )
+{
+ if (!LookupGlyphID(nGlyph, nOutGlyphID, nOutGlyphSetID))
+ AddGlyphID(nGlyph, nOutGlyphID, nOutGlyphSetID);
+}
+
+bool
+GlyphSet::LookupGlyphID (
+ sal_GlyphId nGlyph,
+ unsigned char* nOutGlyphID,
+ sal_Int32* nOutGlyphSetID
+ )
+{
+ sal_Int32 nGlyphSetID = 1;
+
+ // loop through all the font subsets
+ for (auto const& glyph : maGlyphList)
+ {
+ // check every subset if it contains the queried unicode char
+ glyph_map_t::const_iterator aGlyph = glyph.find (nGlyph);
+ if (aGlyph != glyph.end())
+ {
+ // success: found the glyph id, return the mapped glyphid and the glyphsetid
+ *nOutGlyphSetID = nGlyphSetID;
+ *nOutGlyphID = aGlyph->second;
+ return true;
+ }
+ ++nGlyphSetID;
+ }
+
+ *nOutGlyphSetID = -1;
+ *nOutGlyphID = 0;
+ return false;
+}
+
+void
+GlyphSet::AddNotdef (glyph_map_t &rGlyphMap)
+{
+ if (rGlyphMap.empty())
+ rGlyphMap[0] = 0;
+}
+
+void
+GlyphSet::AddGlyphID (
+ sal_GlyphId nGlyph,
+ unsigned char* nOutGlyphID,
+ sal_Int32* nOutGlyphSetID
+ )
+{
+ // create an empty glyphmap that is reserved for unencoded symbol glyphs,
+ // and a second map that takes any other
+ if (maGlyphList.empty())
+ {
+ glyph_map_t aMap, aMapp;
+
+ maGlyphList.push_back (aMap);
+ maGlyphList.push_back (aMapp);
+ }
+ // if the last map is full, create a new one
+ if (maGlyphList.back().size() == 255)
+ {
+ glyph_map_t aMap;
+ maGlyphList.push_back (aMap);
+ }
+
+ glyph_map_t& aGlyphSet = maGlyphList.back();
+ AddNotdef (aGlyphSet);
+
+ int nSize = aGlyphSet.size();
+
+ aGlyphSet [nGlyph] = nSize;
+ *nOutGlyphSetID = maGlyphList.size();
+ *nOutGlyphID = aGlyphSet [nGlyph];
+}
+
+OString
+GlyphSet::GetGlyphSetName (sal_Int32 nGlyphSetID)
+{
+ OStringBuffer aSetName( maBaseName.getLength() + 32 );
+ aSetName.append( maBaseName );
+ aSetName.append( "FID" );
+ aSetName.append( mnFontID );
+ aSetName.append( mbVertical ? "VGSet" : "HGSet" );
+ aSetName.append( nGlyphSetID );
+ return aSetName.makeStringAndClear();
+}
+
+OString
+GlyphSet::GetReencodedFontName (rtl_TextEncoding nEnc, const OString &rFontName)
+{
+ if ( nEnc == RTL_TEXTENCODING_MS_1252
+ || nEnc == RTL_TEXTENCODING_ISO_8859_1)
+ {
+ return rFontName + "-iso1252";
+ }
+ else
+ if (nEnc >= RTL_TEXTENCODING_USER_START && nEnc <= RTL_TEXTENCODING_USER_END)
+ {
+ return rFontName
+ + "-enc"
+ + OString::number ((nEnc - RTL_TEXTENCODING_USER_START));
+ }
+ else
+ {
+ return OString();
+ }
+}
+
+void GlyphSet::DrawGlyph(PrinterGfx& rGfx,
+ const Point& rPoint,
+ const sal_GlyphId nGlyphId)
+{
+ unsigned char nGlyphID;
+ sal_Int32 nGlyphSetID;
+
+ // convert to font glyph id and font subset
+ GetGlyphID (nGlyphId, &nGlyphID, &nGlyphSetID);
+
+ OString aGlyphSetName = GetGlyphSetName(nGlyphSetID);
+
+ rGfx.PSSetFont (aGlyphSetName, RTL_TEXTENCODING_DONTKNOW);
+ rGfx.PSMoveTo (rPoint);
+ rGfx.PSShowGlyph(nGlyphID);
+}
+
+namespace {
+
+struct EncEntry
+{
+ unsigned char aEnc;
+ long aGID;
+
+ EncEntry() : aEnc( 0 ), aGID( 0 ) {}
+
+ bool operator<( const EncEntry& rRight ) const
+ { return aEnc < rRight.aEnc; }
+};
+
+}
+
+static void CreatePSUploadableFont( TrueTypeFont* pSrcFont, FILE* pTmpFile,
+ const char* pGlyphSetName, int nGlyphCount,
+ /*const*/ const sal_uInt16* pRequestedGlyphs, /*const*/ const unsigned char* pEncoding,
+ bool bAllowType42 )
+{
+ // match the font-subset to the printer capabilities
+ // TODO: allow CFF for capable printers
+ FontType nTargetMask = FontType::TYPE1_PFA | FontType::TYPE3_FONT;
+ if( bAllowType42 )
+ nTargetMask |= FontType::TYPE42_FONT;
+
+ std::vector< EncEntry > aSorted( nGlyphCount, EncEntry() );
+ for( int i = 0; i < nGlyphCount; i++ )
+ {
+ aSorted[i].aEnc = pEncoding[i];
+ aSorted[i].aGID = pRequestedGlyphs[i];
+ }
+
+ std::stable_sort( aSorted.begin(), aSorted.end() );
+
+ std::vector< unsigned char > aEncoding( nGlyphCount );
+ std::vector< sal_GlyphId > aRequestedGlyphs( nGlyphCount );
+
+ for( int i = 0; i < nGlyphCount; i++ )
+ {
+ aEncoding[i] = aSorted[i].aEnc;
+ aRequestedGlyphs[i] = aSorted[i].aGID;
+ }
+
+ FontSubsetInfo aInfo;
+ aInfo.LoadFont( pSrcFont );
+
+ aInfo.CreateFontSubset( nTargetMask, pTmpFile, pGlyphSetName,
+ aRequestedGlyphs.data(), aEncoding.data(), nGlyphCount );
+}
+
+void
+GlyphSet::PSUploadFont (osl::File& rOutFile, PrinterGfx &rGfx, bool bAllowType42, std::vector< OString >& rSuppliedFonts )
+{
+ TrueTypeFont *pTTFont;
+ OString aTTFileName (rGfx.GetFontMgr().getFontFileSysPath(mnFontID));
+ int nFace = rGfx.GetFontMgr().getFontFaceNumber(mnFontID);
+ SFErrCodes nSuccess = OpenTTFontFile(aTTFileName.getStr(), nFace, &pTTFont);
+ if (nSuccess != SFErrCodes::Ok)
+ return;
+
+ utl::TempFile aTmpFile;
+ aTmpFile.EnableKillingFile();
+ FILE* pTmpFile = fopen(OUStringToOString(aTmpFile.GetFileName(), osl_getThreadTextEncoding()).getStr(), "w+b");
+ if (pTmpFile == nullptr)
+ return;
+
+ // encoding vector maps character encoding to the ordinal number
+ // of the glyph in the output file
+ unsigned char pEncoding[256];
+ sal_uInt16 pTTGlyphMapping[256];
+
+ // loop through all the font glyph subsets
+ sal_Int32 nGlyphSetID = 1;
+ for (auto const& glyph : maGlyphList)
+ {
+ if (glyph.empty())
+ {
+ ++nGlyphSetID;
+ continue;
+ }
+
+ // loop through all the glyphs in the subset
+ sal_Int32 n = 0;
+ for (auto const& elem : glyph)
+ {
+ pTTGlyphMapping [n] = elem.first;
+ pEncoding [n] = elem.second;
+ n++;
+ }
+
+ // create the current subset
+ OString aGlyphSetName = GetGlyphSetName(nGlyphSetID);
+ fprintf( pTmpFile, "%%%%BeginResource: font %s\n", aGlyphSetName.getStr() );
+ CreatePSUploadableFont( pTTFont, pTmpFile, aGlyphSetName.getStr(), glyph.size(),
+ pTTGlyphMapping, pEncoding, bAllowType42 );
+ fprintf( pTmpFile, "%%%%EndResource\n" );
+ rSuppliedFonts.push_back( aGlyphSetName );
+ ++nGlyphSetID;
+ }
+
+ // copy the file into the page header
+ rewind(pTmpFile);
+ fflush(pTmpFile);
+
+ unsigned char pBuffer[0x2000];
+ sal_uInt64 nIn;
+ sal_uInt64 nOut;
+ do
+ {
+ nIn = fread(pBuffer, 1, sizeof(pBuffer), pTmpFile);
+ rOutFile.write (pBuffer, nIn, nOut);
+ }
+ while ((nIn == nOut) && !feof(pTmpFile));
+
+ // cleanup
+ CloseTTFont (pTTFont);
+ fclose (pTmpFile);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/print/glyphset.hxx b/vcl/unx/generic/print/glyphset.hxx
new file mode 100644
index 000000000..33b928d75
--- /dev/null
+++ b/vcl/unx/generic/print/glyphset.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_GENERIC_PRINT_GLYPHSET_HXX
+#define INCLUDED_VCL_GENERIC_PRINT_GLYPHSET_HXX
+
+#include <osl/file.hxx>
+
+#include <rtl/string.hxx>
+#include <vcl/glyphitem.hxx>
+
+#include <vector>
+#include <unordered_map>
+
+class Point;
+
+namespace psp {
+
+class PrinterGfx;
+class PrintFontManager;
+
+class GlyphSet
+{
+private:
+
+ sal_Int32 mnFontID;
+ bool mbVertical;
+ OString maBaseName;
+
+ typedef std::unordered_map< sal_GlyphId, sal_uInt8 > glyph_map_t;
+ std::vector< glyph_map_t > maGlyphList;
+
+ OString GetGlyphSetName (sal_Int32 nGlyphSetID);
+
+ void GetGlyphID (sal_GlyphId nGlyphId,
+ unsigned char* nOutGlyphID, sal_Int32* nOutGlyphSetID);
+ bool LookupGlyphID (sal_GlyphId nGlyphId,
+ unsigned char* nOutGlyphID, sal_Int32* nOutGlyphSetID);
+ void AddGlyphID (sal_GlyphId nGlyphId,
+ unsigned char* nOutGlyphID,
+ sal_Int32* nOutGlyphSetID);
+ static void AddNotdef (glyph_map_t &rGlyphMap);
+
+public:
+
+ GlyphSet (sal_Int32 nFontID, bool bVertical);
+ /* FIXME delete the glyphlist in ~GlyphSet ??? */
+
+ sal_Int32 GetFontID () const { return mnFontID;}
+ static OString
+ GetReencodedFontName (rtl_TextEncoding nEnc,
+ const OString &rFontName);
+
+ bool IsVertical () const { return mbVertical;}
+
+ void DrawGlyph (PrinterGfx& rGfx,
+ const Point& rPoint,
+ const sal_GlyphId nGlyphId);
+ void PSUploadFont (osl::File& rOutFile, PrinterGfx &rGfx, bool bAsType42, std::vector< OString >& rSuppliedFonts );
+};
+
+} /* namespace psp */
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/print/printerjob.cxx b/vcl/unx/generic/print/printerjob.cxx
new file mode 100644
index 000000000..de598f1f6
--- /dev/null
+++ b/vcl/unx/generic/print/printerjob.cxx
@@ -0,0 +1,973 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <stdio.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "psputil.hxx"
+
+#include <unx/printerjob.hxx>
+#include <unx/printergfx.hxx>
+#include <ppdparser.hxx>
+#include <strhelper.hxx>
+#include <printerinfomanager.hxx>
+
+#include <rtl/ustring.hxx>
+#include <rtl/strbuf.hxx>
+#include <rtl/ustrbuf.hxx>
+
+#include <osl/thread.h>
+#include <osl/security.hxx>
+
+#include <algorithm>
+#include <cstddef>
+#include <deque>
+#include <vector>
+
+using namespace psp;
+
+#define nBLOCKSIZE 0x2000
+
+namespace psp
+{
+
+static bool
+AppendPS (FILE* pDst, osl::File* pSrc, unsigned char* pBuffer)
+{
+ assert(pBuffer);
+ if ((pDst == nullptr) || (pSrc == nullptr))
+ return false;
+
+ if (pSrc->setPos(osl_Pos_Absolut, 0) != osl::FileBase::E_None)
+ return false;
+
+ sal_uInt64 nIn = 0;
+ sal_uInt64 nOut = 0;
+ do
+ {
+ pSrc->read (pBuffer, nBLOCKSIZE, nIn);
+ if (nIn > 0)
+ nOut = fwrite (pBuffer, 1, sal::static_int_cast<sal_uInt32>(nIn), pDst);
+ }
+ while ((nIn > 0) && (nIn == nOut));
+
+ return true;
+}
+
+} // namespace psp
+
+/*
+ * private convenience routines for file handling
+ */
+
+std::unique_ptr<osl::File>
+PrinterJob::CreateSpoolFile (const OUString& rName, const OUString& rExtension)
+{
+ OUString aFile = rName + rExtension;
+ OUString aFileURL;
+ osl::File::RC nError = osl::File::getFileURLFromSystemPath( aFile, aFileURL );
+ if (nError != osl::File::E_None)
+ return nullptr;
+ aFileURL = maSpoolDirName + "/" + aFileURL;
+
+ std::unique_ptr<osl::File> pFile( new osl::File (aFileURL) );
+ nError = pFile->open (osl_File_OpenFlag_Read | osl_File_OpenFlag_Write | osl_File_OpenFlag_Create);
+ if (nError != osl::File::E_None)
+ {
+ return nullptr;
+ }
+
+ osl::File::setAttributes (aFileURL,
+ osl_File_Attribute_OwnWrite | osl_File_Attribute_OwnRead);
+ return pFile;
+}
+
+/*
+ * public methods of PrinterJob: for use in PrinterGfx
+ */
+
+void
+PrinterJob::GetScale (double &rXScale, double &rYScale) const
+{
+ rXScale = mfXScale;
+ rYScale = mfYScale;
+}
+
+sal_uInt16
+PrinterJob::GetDepth () const
+{
+ sal_Int32 nLevel = GetPostscriptLevel();
+ bool bColor = IsColorPrinter ();
+
+ return nLevel > 1 && bColor ? 24 : 8;
+}
+
+sal_uInt16
+PrinterJob::GetPostscriptLevel (const JobData *pJobData) const
+{
+ sal_uInt16 nPSLevel = 2;
+
+ if( pJobData == nullptr )
+ pJobData = &m_aLastJobData;
+
+ if( pJobData->m_nPSLevel )
+ nPSLevel = pJobData->m_nPSLevel;
+ else
+ if( pJobData->m_pParser )
+ nPSLevel = pJobData->m_pParser->getLanguageLevel();
+
+ return nPSLevel;
+}
+
+bool
+PrinterJob::IsColorPrinter () const
+{
+ bool bColor = false;
+
+ if( m_aLastJobData.m_nColorDevice )
+ bColor = m_aLastJobData.m_nColorDevice != -1;
+ else if( m_aLastJobData.m_pParser )
+ bColor = m_aLastJobData.m_pParser->isColorDevice();
+
+ return bColor;
+}
+
+osl::File*
+PrinterJob::GetCurrentPageBody ()
+{
+ return maPageVector.back().get();
+}
+
+/*
+ * public methods of PrinterJob: the actual job / spool handling
+ */
+PrinterJob::PrinterJob()
+ : mnFileMode(0)
+ , m_pGraphics(nullptr)
+ , mnResolution(96)
+ , mnWidthPt(0)
+ , mnHeightPt(0)
+ , mnMaxWidthPt(0)
+ , mnMaxHeightPt(0)
+ , mnLandscapes(0)
+ , mnPortraits(0)
+ , mnLMarginPt(0)
+ , mnRMarginPt(0)
+ , mnTMarginPt(0)
+ , mnBMarginPt(0)
+ , mfXScale(1)
+ , mfYScale(1)
+ , m_bQuickJob(false)
+{
+}
+
+/* remove all our temporary files, uses external program "rm", since
+ osl functionality is inadequate */
+static void
+removeSpoolDir (const OUString& rSpoolDir)
+{
+ OUString aSysPath;
+ if( osl::File::E_None != osl::File::getSystemPathFromFileURL( rSpoolDir, aSysPath ) )
+ {
+ // Conversion did not work, as this is quite a dangerous action,
+ // we should abort here...
+ OSL_FAIL( "psprint: couldn't remove spool directory" );
+ return;
+ }
+ OString aSysPathByte =
+ OUStringToOString (aSysPath, osl_getThreadTextEncoding());
+ if (system (OString("rm -rf " + aSysPathByte).getStr()) == -1)
+ OSL_FAIL( "psprint: couldn't remove spool directory" );
+}
+
+/* creates a spool directory with a "pidgin random" value based on
+ current system time */
+static OUString
+createSpoolDir ()
+{
+ TimeValue aCur;
+ osl_getSystemTime( &aCur );
+ sal_Int32 nRand = aCur.Seconds ^ (aCur.Nanosec/1000);
+
+ OUString aTmpDir;
+ osl_getTempDirURL( &aTmpDir.pData );
+
+ do
+ {
+ OUString aDir = aTmpDir + "/psp" + OUString::number(nRand);
+ if( osl::Directory::create( aDir ) == osl::FileBase::E_None )
+ {
+ osl::File::setAttributes( aDir,
+ osl_File_Attribute_OwnWrite
+ | osl_File_Attribute_OwnRead
+ | osl_File_Attribute_OwnExe );
+ return aDir;
+ }
+ nRand++;
+ } while( nRand );
+ return OUString();
+}
+
+PrinterJob::~PrinterJob ()
+{
+ maPageVector.clear();
+ maHeaderVector.clear();
+
+ // mpJobHeader->remove();
+ mpJobHeader.reset();
+ // mpJobTrailer->remove();
+ mpJobTrailer.reset();
+
+ // XXX should really call osl::remove routines
+ if( !maSpoolDirName.isEmpty() )
+ removeSpoolDir (maSpoolDirName);
+
+ // osl::Directory::remove (maSpoolDirName);
+}
+
+static void WriteLocalTimePS( osl::File *rFile )
+{
+ TimeValue aStartTime, tLocal;
+ oslDateTime date_time;
+ if (osl_getSystemTime( &aStartTime ) &&
+ osl_getLocalTimeFromSystemTime( &aStartTime, &tLocal ) &&
+ osl_getDateTimeFromTimeValue( &tLocal, &date_time ))
+ {
+ char ar[ 256 ];
+ snprintf(
+ ar, sizeof (ar),
+ "%04d-%02d-%02d %02d:%02d:%02d ",
+ date_time.Year, date_time.Month, date_time.Day,
+ date_time.Hours, date_time.Minutes, date_time.Seconds );
+ WritePS( rFile, ar );
+ }
+ else
+ WritePS( rFile, "Unknown-Time" );
+}
+
+static bool isAscii( const OUString& rStr )
+{
+ sal_Int32 nLen = rStr.getLength();
+ for( sal_Int32 i = 0; i < nLen; i++ )
+ if( rStr[i] > 127 )
+ return false;
+ return true;
+}
+
+bool
+PrinterJob::StartJob (
+ const OUString& rFileName,
+ int nMode,
+ const OUString& rJobName,
+ const OUString& rAppName,
+ const JobData& rSetupData,
+ PrinterGfx* pGraphics,
+ bool bIsQuickJob
+ )
+{
+ m_bQuickJob = bIsQuickJob;
+ mnMaxWidthPt = mnMaxHeightPt = 0;
+ mnLandscapes = mnPortraits = 0;
+ m_pGraphics = pGraphics;
+ InitPaperSize (rSetupData);
+
+ // create file container for document header and trailer
+ maFileName = rFileName;
+ mnFileMode = nMode;
+ maSpoolDirName = createSpoolDir ();
+ maJobTitle = rJobName;
+
+ OUString aExt(".ps");
+ mpJobHeader = CreateSpoolFile ("psp_head", aExt);
+ mpJobTrailer = CreateSpoolFile ("psp_tail", aExt);
+ if( ! (mpJobHeader && mpJobTrailer) ) // existing files are removed in destructor
+ return false;
+
+ // write document header according to Document Structuring Conventions (DSC)
+ WritePS (mpJobHeader.get(),
+ "%!PS-Adobe-3.0\n"
+ "%%BoundingBox: (atend)\n" );
+
+ // Creator (this application)
+ OUString aFilterWS = WhitespaceToSpace( rAppName, false );
+ WritePS (mpJobHeader.get(), "%%Creator: (");
+ WritePS (mpJobHeader.get(), aFilterWS);
+ WritePS (mpJobHeader.get(), ")\n");
+
+ // For (user name)
+ osl::Security aSecurity;
+ OUString aUserName;
+ if( aSecurity.getUserName( aUserName ) )
+ {
+ WritePS (mpJobHeader.get(), "%%For: (");
+ WritePS (mpJobHeader.get(), aUserName);
+ WritePS (mpJobHeader.get(), ")\n");
+ }
+
+ // Creation Date (locale independent local time)
+ WritePS (mpJobHeader.get(), "%%CreationDate: (");
+ WriteLocalTimePS (mpJobHeader.get());
+ WritePS (mpJobHeader.get(), ")\n");
+
+ // Document Title
+ /* #i74335#
+ * The title should be clean ascii; rJobName however may
+ * contain any Unicode character. So implement the following
+ * algorithm:
+ * use rJobName, if it contains only ascii
+ * use the filename, if it contains only ascii
+ * else omit %%Title
+ */
+ aFilterWS = WhitespaceToSpace( rJobName, false );
+ OUString aTitle( aFilterWS );
+ if( ! isAscii( aTitle ) )
+ {
+ aTitle = WhitespaceToSpace( rFileName.copy(rFileName.lastIndexOf('/')+1), false );
+ if( ! isAscii( aTitle ) )
+ aTitle.clear();
+ }
+
+ maJobTitle = aFilterWS;
+ if( !aTitle.isEmpty() )
+ {
+ WritePS (mpJobHeader.get(), "%%Title: (");
+ WritePS (mpJobHeader.get(), aTitle);
+ WritePS (mpJobHeader.get(), ")\n");
+ }
+
+ // Language Level
+ OStringBuffer pLevel;
+ getValueOf(GetPostscriptLevel(&rSetupData), pLevel);
+ pLevel.append('\n');
+ WritePS (mpJobHeader.get(), "%%LanguageLevel: ");
+ WritePS (mpJobHeader.get(), pLevel.makeStringAndClear());
+
+ // Other
+ WritePS (mpJobHeader.get(), "%%DocumentData: Clean7Bit\n");
+ WritePS (mpJobHeader.get(), "%%Pages: (atend)\n");
+ WritePS (mpJobHeader.get(), "%%Orientation: (atend)\n");
+ WritePS (mpJobHeader.get(), "%%PageOrder: Ascend\n");
+ WritePS (mpJobHeader.get(), "%%EndComments\n");
+
+ // write Prolog
+ writeProlog (mpJobHeader.get(), rSetupData);
+
+ // mark last job setup as not set
+ m_aLastJobData.m_pParser = nullptr;
+ m_aLastJobData.m_aContext.setParser( nullptr );
+
+ return true;
+}
+
+bool
+PrinterJob::EndJob()
+{
+ // no pages ? that really means no print job
+ if( maPageVector.empty() )
+ return false;
+
+ // write document setup (done here because it
+ // includes the accumulated fonts
+ if( mpJobHeader )
+ writeSetup( mpJobHeader.get(), m_aDocumentJobData );
+ m_pGraphics->OnEndJob();
+ if( ! (mpJobHeader && mpJobTrailer) )
+ return false;
+
+ // write document trailer according to Document Structuring Conventions (DSC)
+ OStringBuffer aTrailer(512);
+ aTrailer.append( "%%Trailer\n" );
+ aTrailer.append( "%%BoundingBox: 0 0 " );
+ aTrailer.append( static_cast<sal_Int32>(mnMaxWidthPt) );
+ aTrailer.append( " " );
+ aTrailer.append( static_cast<sal_Int32>(mnMaxHeightPt) );
+ if( mnLandscapes > mnPortraits )
+ aTrailer.append("\n%%Orientation: Landscape");
+ else
+ aTrailer.append("\n%%Orientation: Portrait");
+ aTrailer.append( "\n%%Pages: " );
+ aTrailer.append( static_cast<sal_Int32>(maPageVector.size()) );
+ aTrailer.append( "\n%%EOF\n" );
+ WritePS (mpJobTrailer.get(), aTrailer.getStr());
+
+ /*
+ * spool the set of files to their final destination, this is U**X dependent
+ */
+
+ FILE* pDestFILE = nullptr;
+
+ /* create a destination either as file or as a pipe */
+ bool bSpoolToFile = !maFileName.isEmpty();
+ if (bSpoolToFile)
+ {
+ const OString aFileName = OUStringToOString (maFileName,
+ osl_getThreadTextEncoding());
+ if( mnFileMode )
+ {
+ int nFile = open( aFileName.getStr(), O_CREAT | O_EXCL | O_RDWR, mnFileMode );
+ if( nFile != -1 )
+ {
+ pDestFILE = fdopen( nFile, "w" );
+ if( pDestFILE == nullptr )
+ {
+ close( nFile );
+ unlink( aFileName.getStr() );
+ return false;
+ }
+ }
+ else
+ {
+ (void)chmod( aFileName.getStr(), mnFileMode );
+ }
+ }
+ if (pDestFILE == nullptr)
+ pDestFILE = fopen (aFileName.getStr(), "w");
+
+ if (pDestFILE == nullptr)
+ return false;
+ }
+ else
+ {
+ PrinterInfoManager& rPrinterInfoManager = PrinterInfoManager::get ();
+ pDestFILE = rPrinterInfoManager.startSpool( m_aLastJobData.m_aPrinterName, m_bQuickJob );
+ if (pDestFILE == nullptr)
+ return false;
+ }
+
+ /* spool the document parts to the destination */
+
+ unsigned char pBuffer[ nBLOCKSIZE ];
+
+ AppendPS (pDestFILE, mpJobHeader.get(), pBuffer);
+ mpJobHeader->close();
+
+ bool bSuccess = true;
+ std::vector< std::unique_ptr<osl::File> >::iterator pPageBody;
+ std::vector< std::unique_ptr<osl::File> >::iterator pPageHead;
+ for (pPageBody = maPageVector.begin(), pPageHead = maHeaderVector.begin();
+ pPageBody != maPageVector.end() && pPageHead != maHeaderVector.end();
+ ++pPageBody, ++pPageHead)
+ {
+ if( *pPageHead )
+ {
+ osl::File::RC nError = (*pPageHead)->open(osl_File_OpenFlag_Read);
+ if (nError == osl::File::E_None)
+ {
+ AppendPS (pDestFILE, pPageHead->get(), pBuffer);
+ (*pPageHead)->close();
+ }
+ }
+ else
+ bSuccess = false;
+ if( *pPageBody )
+ {
+ osl::File::RC nError = (*pPageBody)->open(osl_File_OpenFlag_Read);
+ if (nError == osl::File::E_None)
+ {
+ AppendPS (pDestFILE, pPageBody->get(), pBuffer);
+ (*pPageBody)->close();
+ }
+ }
+ else
+ bSuccess = false;
+ }
+
+ AppendPS (pDestFILE, mpJobTrailer.get(), pBuffer);
+ mpJobTrailer->close();
+
+ /* well done */
+
+ if (bSpoolToFile)
+ fclose (pDestFILE);
+ else
+ {
+ PrinterInfoManager& rPrinterInfoManager = PrinterInfoManager::get();
+ if (!rPrinterInfoManager.endSpool( m_aLastJobData.m_aPrinterName,
+ maJobTitle, pDestFILE, m_aDocumentJobData, true, OUString()))
+ {
+ bSuccess = false;
+ }
+ }
+
+ return bSuccess;
+}
+
+void
+PrinterJob::InitPaperSize (const JobData& rJobSetup)
+{
+ int nRes = rJobSetup.m_aContext.getRenderResolution ();
+
+ OUString aPaper;
+ int nWidth, nHeight;
+ rJobSetup.m_aContext.getPageSize (aPaper, nWidth, nHeight);
+
+ int nLeft = 0, nRight = 0, nUpper = 0, nLower = 0;
+ const PPDParser* pParser = rJobSetup.m_aContext.getParser();
+ if (pParser != nullptr)
+ pParser->getMargins (aPaper, nLeft, nRight, nUpper, nLower);
+
+ mnResolution = nRes;
+
+ mnWidthPt = nWidth;
+ mnHeightPt = nHeight;
+
+ if( mnWidthPt > mnMaxWidthPt )
+ mnMaxWidthPt = mnWidthPt;
+ if( mnHeightPt > mnMaxHeightPt )
+ mnMaxHeightPt = mnHeightPt;
+
+ mnLMarginPt = nLeft;
+ mnRMarginPt = nRight;
+ mnTMarginPt = nUpper;
+ mnBMarginPt = nLower;
+
+ mfXScale = 72.0 / static_cast<double>(mnResolution);
+ mfYScale = -1.0 * 72.0 / static_cast<double>(mnResolution);
+}
+
+void
+PrinterJob::StartPage (const JobData& rJobSetup)
+{
+ InitPaperSize (rJobSetup);
+
+ OUString aPageNo = OUString::number (static_cast<sal_Int32>(maPageVector.size())+1); // sequential page number must start with 1
+ OUString aExt = aPageNo + ".ps";
+
+ maHeaderVector.push_back( CreateSpoolFile ( "psp_pghead", aExt) );
+ maPageVector.push_back( CreateSpoolFile ( "psp_pgbody", aExt) );
+
+ osl::File* pPageHeader = maHeaderVector.back().get();
+ osl::File* pPageBody = maPageVector.back().get();
+
+ if( ! (pPageHeader && pPageBody) )
+ return;
+
+ // write page header according to Document Structuring Conventions (DSC)
+ WritePS (pPageHeader, "%%Page: ");
+ WritePS (pPageHeader, aPageNo);
+ WritePS (pPageHeader, " ");
+ WritePS (pPageHeader, aPageNo);
+ WritePS (pPageHeader, "\n");
+
+ if( rJobSetup.m_eOrientation == orientation::Landscape )
+ {
+ WritePS (pPageHeader, "%%PageOrientation: Landscape\n");
+ mnLandscapes++;
+ }
+ else
+ {
+ WritePS (pPageHeader, "%%PageOrientation: Portrait\n");
+ mnPortraits++;
+ }
+
+ OStringBuffer pBBox;
+
+ psp::appendStr ("%%PageBoundingBox: ", pBBox);
+ psp::getValueOf (mnLMarginPt, pBBox);
+ psp::appendStr (" ", pBBox);
+ psp::getValueOf (mnBMarginPt, pBBox);
+ psp::appendStr (" ", pBBox);
+ psp::getValueOf (mnWidthPt - mnRMarginPt, pBBox);
+ psp::appendStr (" ", pBBox);
+ psp::getValueOf (mnHeightPt - mnTMarginPt, pBBox);
+ psp::appendStr ("\n", pBBox);
+
+ WritePS (pPageHeader, pBBox.makeStringAndClear());
+
+ /* #i7262# #i65491# write setup only before first page
+ * (to %%Begin(End)Setup, instead of %%Begin(End)PageSetup)
+ * don't do this in StartJob since the jobsetup there may be
+ * different.
+ */
+ bool bWriteFeatures = true;
+ if( 1 == maPageVector.size() )
+ {
+ m_aDocumentJobData = rJobSetup;
+ bWriteFeatures = false;
+ }
+
+ if ( writePageSetup( pPageHeader, rJobSetup, bWriteFeatures ) )
+ {
+ m_aLastJobData = rJobSetup;
+ }
+}
+
+void
+PrinterJob::EndPage ()
+{
+ osl::File* pPageHeader = maHeaderVector.back().get();
+ osl::File* pPageBody = maPageVector.back().get();
+
+ if( ! (pPageBody && pPageHeader) )
+ return;
+
+ // copy page to paper and write page trailer according to DSC
+
+ OStringBuffer pTrailer;
+ psp::appendStr ("grestore grestore\n", pTrailer);
+ psp::appendStr ("showpage\n", pTrailer);
+ psp::appendStr ("%%PageTrailer\n\n", pTrailer);
+ WritePS (pPageBody, pTrailer.makeStringAndClear());
+
+ // this page is done for now, close it to avoid having too many open fd's
+
+ pPageHeader->close();
+ pPageBody->close();
+}
+
+namespace {
+
+struct less_ppd_key
+{
+ bool operator()(const PPDKey* left, const PPDKey* right)
+ { return left->getOrderDependency() < right->getOrderDependency(); }
+};
+
+}
+
+static bool writeFeature( osl::File* pFile, const PPDKey* pKey, const PPDValue* pValue, bool bUseIncluseFeature )
+{
+ if( ! pKey || ! pValue )
+ return true;
+
+ OStringBuffer aFeature(256);
+ aFeature.append( "[{\n" );
+ if( bUseIncluseFeature )
+ aFeature.append( "%%IncludeFeature:" );
+ else
+ aFeature.append( "%%BeginFeature:" );
+ aFeature.append( " *" );
+ aFeature.append( OUStringToOString( pKey->getKey(), RTL_TEXTENCODING_ASCII_US ) );
+ aFeature.append( ' ' );
+ aFeature.append( OUStringToOString( pValue->m_aOption, RTL_TEXTENCODING_ASCII_US ) );
+ if( !bUseIncluseFeature )
+ {
+ aFeature.append( '\n' );
+ aFeature.append( OUStringToOString( pValue->m_aValue, RTL_TEXTENCODING_ASCII_US ) );
+ aFeature.append( "\n%%EndFeature" );
+ }
+ aFeature.append( "\n} stopped cleartomark\n" );
+ sal_uInt64 nWritten = 0;
+ return !(pFile->write( aFeature.getStr(), aFeature.getLength(), nWritten )
+ || nWritten != static_cast<sal_uInt64>(aFeature.getLength()));
+}
+
+bool PrinterJob::writeFeatureList( osl::File* pFile, const JobData& rJob, bool bDocumentSetup )
+{
+ bool bSuccess = true;
+
+ // 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 &&
+ ( m_aLastJobData.m_pParser == rJob.m_pParser || m_aLastJobData.m_pParser == nullptr )
+ )
+ {
+ 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 && bSuccess; i++ )
+ {
+ const PPDKey* pKey = aKeys[i];
+ bool bEmit = false;
+ if( bDocumentSetup )
+ {
+ if( pKey->getSetupType() == PPDKey::SetupType::DocumentSetup )
+ bEmit = true;
+ }
+ if( pKey->getSetupType() == PPDKey::SetupType::PageSetup ||
+ pKey->getSetupType() == PPDKey::SetupType::AnySetup )
+ bEmit = true;
+ if( bEmit )
+ {
+ const PPDValue* pValue = rJob.m_aContext.getValue( pKey );
+ if( pValue
+ && pValue->m_eType == eInvocation
+ && ( m_aLastJobData.m_pParser == nullptr
+ || m_aLastJobData.m_aContext.getValue( pKey ) != pValue
+ || bDocumentSetup
+ )
+ )
+ {
+ // try to avoid PS level 2 feature commands if level is set to 1
+ if( GetPostscriptLevel( &rJob ) == 1 )
+ {
+ bool bHavePS2 =
+ ( pValue->m_aValue.indexOf( "<<" ) != -1 )
+ ||
+ ( pValue->m_aValue.indexOf( ">>" ) != -1 );
+ if( bHavePS2 )
+ continue;
+ }
+ bSuccess = writeFeature( pFile, pKey, pValue, PrinterInfoManager::get().getUseIncludeFeature() );
+ }
+ }
+ }
+ }
+ else
+ bSuccess = false;
+
+ return bSuccess;
+}
+
+bool PrinterJob::writePageSetup( osl::File* pFile, const JobData& rJob, bool bWriteFeatures )
+{
+ bool bSuccess = true;
+
+ WritePS (pFile, "%%BeginPageSetup\n%\n");
+ if ( bWriteFeatures )
+ bSuccess = writeFeatureList( pFile, rJob, false );
+ WritePS (pFile, "%%EndPageSetup\n");
+
+ OStringBuffer pTranslate;
+
+ if( rJob.m_eOrientation == orientation::Portrait )
+ {
+ psp::appendStr ("gsave\n[", pTranslate);
+ psp::getValueOfDouble ( pTranslate, mfXScale, 5);
+ psp::appendStr (" 0 0 ", pTranslate);
+ psp::getValueOfDouble ( pTranslate, mfYScale, 5);
+ psp::appendStr (" ", pTranslate);
+ psp::getValueOf (mnRMarginPt, pTranslate);
+ psp::appendStr (" ", pTranslate);
+ psp::getValueOf (mnHeightPt-mnTMarginPt,
+ pTranslate);
+ psp::appendStr ("] concat\ngsave\n",
+ pTranslate);
+ }
+ else
+ {
+ psp::appendStr ("gsave\n", pTranslate);
+ psp::appendStr ("[ 0 ", pTranslate);
+ psp::getValueOfDouble ( pTranslate, -mfYScale, 5);
+ psp::appendStr (" ", pTranslate);
+ psp::getValueOfDouble ( pTranslate, mfXScale, 5);
+ psp::appendStr (" 0 ", pTranslate );
+ psp::getValueOfDouble ( pTranslate, mnLMarginPt, 5 );
+ psp::appendStr (" ", pTranslate);
+ psp::getValueOf (mnBMarginPt, pTranslate );
+ psp::appendStr ("] concat\ngsave\n",
+ pTranslate);
+ }
+
+ WritePS (pFile, pTranslate.makeStringAndClear());
+
+ return bSuccess;
+}
+
+void PrinterJob::writeJobPatch( osl::File* pFile, const JobData& rJobData )
+{
+ if( ! PrinterInfoManager::get().getUseJobPatch() )
+ return;
+
+ const PPDKey* pKey = nullptr;
+
+ if( rJobData.m_pParser )
+ pKey = rJobData.m_pParser->getKey( "JobPatchFile" );
+ if( ! pKey )
+ return;
+
+ // order the patch files
+ // according to PPD spec the JobPatchFile options must be int
+ // and should be emitted in order
+ std::deque< sal_Int32 > patch_order;
+ int nValueCount = pKey->countValues();
+ for( int i = 0; i < nValueCount; i++ )
+ {
+ const PPDValue* pVal = pKey->getValue( i );
+ patch_order.push_back( pVal->m_aOption.toInt32() );
+ if( patch_order.back() == 0 && pVal->m_aOption != "0" )
+ {
+ WritePS( pFile, "% Warning: left out JobPatchFile option \"" );
+ OString aOption = OUStringToOString( pVal->m_aOption, RTL_TEXTENCODING_ASCII_US );
+ WritePS( pFile, aOption.getStr() );
+ WritePS( pFile,
+ "\"\n% as it violates the PPD spec;\n"
+ "% JobPatchFile options need to be numbered for ordering.\n" );
+ }
+ }
+
+ std::sort(patch_order.begin(), patch_order.end());
+ patch_order.erase(std::unique(patch_order.begin(), patch_order.end()), patch_order.end());
+
+ for (auto const& elem : patch_order)
+ {
+ // note: this discards patch files not adhering to the "int" scheme
+ // as there won't be a value for them
+ writeFeature( pFile, pKey, pKey->getValue( OUString::number(elem) ), false );
+ }
+}
+
+void PrinterJob::writeProlog (osl::File* pFile, const JobData& rJobData )
+{
+ WritePS( pFile, "%%BeginProlog\n" );
+
+ // JobPatchFile feature needs to be emitted at begin of prolog
+ writeJobPatch( pFile, rJobData );
+
+ static const char pProlog[] = {
+ "%%BeginResource: procset PSPrint-Prolog 1.0 0\n"
+ "/ISO1252Encoding [\n"
+ "/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef\n"
+ "/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef\n"
+ "/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef\n"
+ "/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef\n"
+ "/space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quotesingle\n"
+ "/parenleft /parenright /asterisk /plus /comma /hyphen /period /slash\n"
+ "/zero /one /two /three /four /five /six /seven\n"
+ "/eight /nine /colon /semicolon /less /equal /greater /question\n"
+ "/at /A /B /C /D /E /F /G\n"
+ "/H /I /J /K /L /M /N /O\n"
+ "/P /Q /R /S /T /U /V /W\n"
+ "/X /Y /Z /bracketleft /backslash /bracketright /asciicircum /underscore\n"
+ "/grave /a /b /c /d /e /f /g\n"
+ "/h /i /j /k /l /m /n /o\n"
+ "/p /q /r /s /t /u /v /w\n"
+ "/x /y /z /braceleft /bar /braceright /asciitilde /unused\n"
+ "/Euro /unused /quotesinglbase /florin /quotedblbase /ellipsis /dagger /daggerdbl\n"
+ "/circumflex /perthousand /Scaron /guilsinglleft /OE /unused /Zcaron /unused\n"
+ "/unused /quoteleft /quoteright /quotedblleft /quotedblright /bullet /endash /emdash\n"
+ "/tilde /trademark /scaron /guilsinglright /oe /unused /zcaron /Ydieresis\n"
+ "/space /exclamdown /cent /sterling /currency /yen /brokenbar /section\n"
+ "/dieresis /copyright /ordfeminine /guillemotleft /logicalnot /hyphen /registered /macron\n"
+ "/degree /plusminus /twosuperior /threesuperior /acute /mu /paragraph /periodcentered\n"
+ "/cedilla /onesuperior /ordmasculine /guillemotright /onequarter /onehalf /threequarters /questiondown\n"
+ "/Agrave /Aacute /Acircumflex /Atilde /Adieresis /Aring /AE /Ccedilla\n"
+ "/Egrave /Eacute /Ecircumflex /Edieresis /Igrave /Iacute /Icircumflex /Idieresis\n"
+ "/Eth /Ntilde /Ograve /Oacute /Ocircumflex /Otilde /Odieresis /multiply\n"
+ "/Oslash /Ugrave /Uacute /Ucircumflex /Udieresis /Yacute /Thorn /germandbls\n"
+ "/agrave /aacute /acircumflex /atilde /adieresis /aring /ae /ccedilla\n"
+ "/egrave /eacute /ecircumflex /edieresis /igrave /iacute /icircumflex /idieresis\n"
+ "/eth /ntilde /ograve /oacute /ocircumflex /otilde /odieresis /divide\n"
+ "/oslash /ugrave /uacute /ucircumflex /udieresis /yacute /thorn /ydieresis] def\n"
+ "\n"
+ "/psp_definefont { exch dup findfont dup length dict begin { 1 index /FID ne\n"
+ "{ def } { pop pop } ifelse } forall /Encoding 3 -1 roll def\n"
+ "currentdict end exch pop definefont pop } def\n"
+ "\n"
+ "/pathdict dup 8 dict def load begin\n"
+ "/rcmd { { currentfile 1 string readstring pop 0 get dup 32 gt { exit }\n"
+ "{ pop } ifelse } loop dup 126 eq { pop exit } if 65 sub dup 16#3 and 1\n"
+ "add exch dup 16#C and -2 bitshift 16#3 and 1 add exch 16#10 and 16#10\n"
+ "eq 3 1 roll exch } def\n"
+ "/rhex { dup 1 sub exch currentfile exch string readhexstring pop dup 0\n"
+ "get dup 16#80 and 16#80 eq dup 3 1 roll { 16#7f and } if 2 index 0 3\n"
+ "-1 roll put 3 1 roll 0 0 1 5 -1 roll { 2 index exch get add 256 mul }\n"
+ "for 256 div exch pop exch { neg } if } def\n"
+ "/xcmd { rcmd exch rhex exch rhex exch 5 -1 roll add exch 4 -1 roll add\n"
+ "1 index 1 index 5 -1 roll { moveto } { lineto } ifelse } def end\n"
+ "/readpath { 0 0 pathdict begin { xcmd } loop end pop pop } def\n"
+ "\n"
+ "systemdict /languagelevel known not {\n"
+ "/xshow { exch dup length 0 1 3 -1 roll 1 sub { dup 3 index exch get\n"
+ "exch 2 index exch get 1 string dup 0 4 -1 roll put currentpoint 3 -1\n"
+ "roll show moveto 0 rmoveto } for pop pop } def\n"
+ "/rectangle { 4 -2 roll moveto 1 index 0 rlineto 0 exch rlineto neg 0\n"
+ "rlineto closepath } def\n"
+ "/rectfill { rectangle fill } def\n"
+ "/rectstroke { rectangle stroke } def } if\n"
+ "/bshow { currentlinewidth 3 1 roll currentpoint 3 index show moveto\n"
+ "setlinewidth false charpath stroke setlinewidth } def\n"
+ "/bxshow { currentlinewidth 4 1 roll setlinewidth exch dup length 1 sub\n"
+ "0 1 3 -1 roll { 1 string 2 index 2 index get 1 index exch 0 exch put dup\n"
+ "currentpoint 3 -1 roll show moveto currentpoint 3 -1 roll false charpath\n"
+ "stroke moveto 2 index exch get 0 rmoveto } for pop pop setlinewidth } def\n"
+ "\n"
+ "/psp_lzwfilter { currentfile /ASCII85Decode filter /LZWDecode filter } def\n"
+ "/psp_ascii85filter { currentfile /ASCII85Decode filter } def\n"
+ "/psp_lzwstring { psp_lzwfilter 1024 string readstring } def\n"
+ "/psp_ascii85string { psp_ascii85filter 1024 string readstring } def\n"
+ "/psp_imagedict {\n"
+ "/psp_bitspercomponent { 3 eq { 1 }{ 8 } ifelse } def\n"
+ "/psp_decodearray { [ [0 1 0 1 0 1] [0 255] [0 1] [0 255] ] exch get }\n"
+ "def 7 dict dup\n"
+ "/ImageType 1 put dup\n"
+ "/Width 7 -1 roll put dup\n"
+ "/Height 5 index put dup\n"
+ "/BitsPerComponent 4 index psp_bitspercomponent put dup\n"
+ "/Decode 5 -1 roll psp_decodearray put dup\n"
+ "/ImageMatrix [1 0 0 1 0 0] dup 5 8 -1 roll put put dup\n"
+ "/DataSource 4 -1 roll 1 eq { psp_lzwfilter } { psp_ascii85filter } ifelse put\n"
+ "} def\n"
+ "%%EndResource\n"
+ "%%EndProlog\n"
+ };
+ WritePS (pFile, pProlog);
+}
+
+bool PrinterJob::writeSetup( osl::File* pFile, const JobData& rJob )
+{
+ WritePS (pFile, "%%BeginSetup\n%\n");
+
+ // download fonts
+ std::vector< OString > aFonts;
+ m_pGraphics->writeResources( pFile, aFonts );
+
+ if( !aFonts.empty() )
+ {
+ std::vector< OString >::const_iterator it = aFonts.begin();
+ OStringBuffer aLine( 256 );
+ aLine.append( "%%DocumentSuppliedResources: font " );
+ aLine.append( *it );
+ aLine.append( "\n" );
+ WritePS ( pFile, aLine.getStr() );
+ while( (++it) != aFonts.end() )
+ {
+ aLine.setLength(0);
+ aLine.append( "%%+ font " );
+ aLine.append( *it );
+ aLine.append( "\n" );
+ WritePS ( pFile, aLine.getStr() );
+ }
+ }
+
+ bool bSuccess = true;
+ // in case of external print dialog the number of copies is prepended
+ // to the job, let us not complicate things by emitting our own copy count
+ bool bExternalDialog = PrinterInfoManager::get().checkFeatureToken( GetPrinterName(), "external_dialog" );
+ if( ! bExternalDialog && rJob.m_nCopies > 1 )
+ {
+ // setup code
+ OString aLine = "/#copies " +
+ OString::number(static_cast<sal_Int32>(rJob.m_nCopies)) +
+ " def\n";
+ sal_uInt64 nWritten = 0;
+ bSuccess = !(pFile->write(aLine.getStr(), aLine.getLength(), nWritten)
+ || nWritten != static_cast<sal_uInt64>(aLine.getLength()));
+
+ if( bSuccess && GetPostscriptLevel( &rJob ) >= 2 )
+ WritePS (pFile, "<< /NumCopies null /Policies << /NumCopies 1 >> >> setpagedevice\n" );
+ }
+
+ bool bFeatureSuccess = writeFeatureList( pFile, rJob, true );
+
+ WritePS (pFile, "%%EndSetup\n");
+
+ return bSuccess && bFeatureSuccess;
+}
+
+/* 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 000000000..fae6a1552
--- /dev/null
+++ b/vcl/unx/generic/print/prtsetup.cxx
@@ -0,0 +1,507 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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(OUString::number(reinterpret_cast<sal_Int64>(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(OUString::number(reinterpret_cast<sal_IntPtr>(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 OString&, 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_aJobData.m_nPSLevel = m_xDevicePage->getLevel();
+ m_aJobData.m_nPDFDevice = m_xDevicePage->getPDFDevice();
+ }
+ 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 )
+ {
+ 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 = reinterpret_cast<PPDValue*>(rBox.get_active_id().toInt64());
+ m_pParent->m_aJobData.m_aContext.setValue( pKey, pValue );
+ update();
+ }
+
+ m_pParent->SetDataModified( true );
+}
+
+IMPL_LINK( RTSPaperPage, CheckBoxHdl, weld::ToggleButton&, /*cBox*/, 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_xLevelBox(m_xBuilder->weld_combo_box("level"))
+ , m_xSpaceBox(m_xBuilder->weld_combo_box("colorspace"))
+ , m_xDepthBox(m_xBuilder->weld_combo_box("colordepth"))
+{
+ m_aReselectCustomIdle.SetInvokeHandler(LINK(this, RTSDevicePage, ImplHandleReselectHdl));
+ m_aReselectCustomIdle.SetDebugName("RTSDevicePage m_aReselectCustomIdle");
+
+ 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 ) );
+
+ 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;
+ }
+
+ sal_Int32 nLevelEntryData = 0; //automatic
+ if( m_pParent->m_aJobData.m_nPDFDevice == 2 ) //explicit PDF
+ nLevelEntryData = 10;
+ else if (m_pParent->m_aJobData.m_nPSLevel > 0) //explicit PS Level
+ nLevelEntryData = m_pParent->m_aJobData.m_nPSLevel+1;
+ else if (m_pParent->m_aJobData.m_nPDFDevice == 1) //automatically PDF
+ nLevelEntryData = 0;
+ else if (m_pParent->m_aJobData.m_nPDFDevice == -1) //explicitly PS from driver
+ nLevelEntryData = 1;
+
+ bool bAutoIsPDF = officecfg::Office::Common::Print::Option::Printer::PDFAsStandardPrintJobFormat::get();
+
+ assert(nLevelEntryData != 0
+ || "Generic Printer" == m_pParent->m_aJobData.m_aPrinterName
+ || int(bAutoIsPDF) == m_pParent->m_aJobData.m_nPDFDevice);
+
+ OUString sStr = m_xLevelBox->get_text(0);
+ OUString sId = m_xLevelBox->get_id(0);
+ m_xLevelBox->insert(0, sStr.replaceAll("%s", bAutoIsPDF ? m_xLevelBox->get_text(5) : m_xLevelBox->get_text(1)), &sId, nullptr, nullptr);
+ m_xLevelBox->remove(1);
+
+ for (int i = 0; i < m_xLevelBox->get_count(); ++i)
+ {
+ if (m_xLevelBox->get_id(i).toInt32() == nLevelEntryData)
+ {
+ m_xLevelBox->set_active(i);
+ 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 )
+ {
+ 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(OUString::number(reinterpret_cast<sal_Int64>(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;
+}
+
+sal_uLong RTSDevicePage::getLevel() const
+{
+ auto nLevel = m_xLevelBox->get_active_id().toInt32();
+ if (nLevel == 0)
+ return 0; //automatic
+ return nLevel < 10 ? nLevel-1 : 0;
+}
+
+sal_uLong RTSDevicePage::getPDFDevice() const
+{
+ auto nLevel = m_xLevelBox->get_active_id().toInt32();
+ if (nLevel > 9)
+ return 2; //explicitly PDF
+ else if (nLevel == 0)
+ return 0; //automatic
+ return -1; //explicitly PS
+}
+
+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();
+ }
+}
+
+IMPL_LINK( RTSDevicePage, SelectHdl, weld::TreeView&, rBox, void )
+{
+ if (&rBox == m_xPPDKeyBox.get())
+ {
+ const PPDKey* pKey = reinterpret_cast<PPDKey*>(m_xPPDKeyBox->get_selected_id().toInt64());
+ FillValueBox( pKey );
+ }
+ else if (&rBox == m_xPPDValueBox.get())
+ {
+ const PPDKey* pKey = reinterpret_cast<PPDKey*>(m_xPPDKeyBox->get_selected_id().toInt64());
+ const PPDValue* pValue = reinterpret_cast<PPDValue*>(m_xPPDValueBox->get_selected_id().toInt64());
+ if (pKey && pValue)
+ {
+ m_pParent->m_aJobData.m_aContext.setValue( pKey, pValue );
+ ValueBoxChanged(pKey);
+ }
+ }
+ 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(OUString::number(reinterpret_cast<sal_Int64>(pValue)), aEntry);
+ }
+ }
+ pValue = m_pParent->m_aJobData.m_aContext.getValue( pKey );
+ m_xPPDValueBox->select_id(OUString::number(reinterpret_cast<sal_Int64>(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 000000000..e5d88c7e1
--- /dev/null
+++ b/vcl/unx/generic/print/prtsetup.hxx
@@ -0,0 +1,137 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_GENERIC_PRINT_PRTSETUP_HXX
+#define INCLUDED_VCL_GENERIC_PRINT_PRTSETUP_HXX
+
+#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 OString&, 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::ToggleButton&, 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_xLevelBox;
+ 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(ImplHandleReselectHdl, Timer*, void);
+public:
+ RTSDevicePage(weld::Widget* pPage, RTSDialog* pDialog);
+ ~RTSDevicePage();
+
+ sal_uLong getLevel() const;
+ sal_uLong getPDFDevice() const;
+ sal_uLong getDepth() const;
+ sal_uLong getColorDevice() const;
+};
+
+int SetupPrinterDriver(weld::Window* pParent, ::psp::PrinterInfo& rJobData);
+
+#endif // _PAD_PRTSETUP_HXX
+
+/* 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 000000000..7b667f446
--- /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 it self
+% 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/print/psputil.cxx b/vcl/unx/generic/print/psputil.cxx
new file mode 100644
index 000000000..91e986dbd
--- /dev/null
+++ b/vcl/unx/generic/print/psputil.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 <string.h>
+#include "psputil.hxx"
+
+namespace psp {
+
+/*
+ * string convenience routines
+ */
+
+sal_Int32
+getHexValueOf (sal_Int32 nValue, OStringBuffer& pBuffer)
+{
+ const static char pHex [0x10] = {
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+
+ pBuffer.append(pHex [(nValue & 0xF0) >> 4]);
+ pBuffer.append(pHex [(nValue & 0x0F) ]);
+
+ return 2;
+}
+
+sal_Int32
+getAlignedHexValueOf (sal_Int32 nValue, OStringBuffer& pBuffer)
+{
+ // get sign
+ bool bNegative = nValue < 0;
+ nValue = bNegative ? -nValue : nValue;
+
+ // get required buffer size, must be a multiple of two
+ sal_Int32 nPrecision;
+ if (nValue < 0x80)
+ nPrecision = 2;
+ else
+ if (nValue < 0x8000)
+ nPrecision = 4;
+ else
+ if (nValue < 0x800000)
+ nPrecision = 6;
+ else
+ nPrecision = 8;
+
+ // convert the int into its hex representation, write it into the buffer
+ sal_Int32 nRet = nPrecision;
+ auto const start = pBuffer.getLength();
+ while (nPrecision)
+ {
+ OStringBuffer scratch;
+ nPrecision -= getHexValueOf (nValue % 256, scratch );
+ pBuffer.insert(start, scratch.makeStringAndClear());
+ nValue /= 256;
+ }
+
+ // set sign bit
+ if (bNegative)
+ {
+ switch (pBuffer[start])
+ {
+ case '0' : pBuffer[start] = '8'; break;
+ case '1' : pBuffer[start] = '9'; break;
+ case '2' : pBuffer[start] = 'A'; break;
+ case '3' : pBuffer[start] = 'B'; break;
+ case '4' : pBuffer[start] = 'C'; break;
+ case '5' : pBuffer[start] = 'D'; break;
+ case '6' : pBuffer[start] = 'E'; break;
+ case '7' : pBuffer[start] = 'F'; break;
+ default: OSL_FAIL("Already a signed value");
+ }
+ }
+
+ // report precision
+ return nRet;
+}
+
+sal_Int32
+getValueOf (sal_Int32 nValue, OStringBuffer& pBuffer)
+{
+ sal_Int32 nChar = 0;
+ if (nValue < 0)
+ {
+ pBuffer.append('-');
+ ++nChar;
+ nValue *= -1;
+ }
+ else
+ if (nValue == 0)
+ {
+ pBuffer.append('0');
+ ++nChar;
+ return nChar;
+ }
+
+ char pInvBuffer [32];
+ sal_Int32 nInvChar = 0;
+ while (nValue > 0)
+ {
+ pInvBuffer [nInvChar++] = '0' + nValue % 10;
+ nValue /= 10;
+ }
+ while (nInvChar > 0)
+ {
+ pBuffer.append(pInvBuffer [--nInvChar]);
+ ++nChar;
+ }
+
+ return nChar;
+}
+
+sal_Int32
+appendStr (const char* pSrc, OStringBuffer& pDst)
+{
+ sal_Int32 nBytes = strlen (pSrc);
+ pDst.append(pSrc, nBytes);
+
+ return nBytes;
+}
+
+/*
+ * copy strings to file
+ */
+
+bool
+WritePS (osl::File* pFile, const char* pString)
+{
+ sal_uInt64 nInLength = rtl_str_getLength (pString);
+ sal_uInt64 nOutLength = 0;
+
+ if (nInLength > 0 && pFile)
+ pFile->write (pString, nInLength, nOutLength);
+
+ return nInLength == nOutLength;
+}
+
+bool
+WritePS (osl::File* pFile, const char* pString, sal_uInt64 nInLength)
+{
+ sal_uInt64 nOutLength = 0;
+
+ if (nInLength > 0 && pFile)
+ pFile->write (pString, nInLength, nOutLength);
+
+ return nInLength == nOutLength;
+}
+
+bool
+WritePS (osl::File* pFile, const OString &rString)
+{
+ sal_uInt64 nInLength = rString.getLength();
+ sal_uInt64 nOutLength = 0;
+
+ if (nInLength > 0 && pFile)
+ pFile->write (rString.getStr(), nInLength, nOutLength);
+
+ return nInLength == nOutLength;
+}
+
+bool
+WritePS (osl::File* pFile, const OUString &rString)
+{
+ return WritePS (pFile, OUStringToOString(rString, RTL_TEXTENCODING_ASCII_US));
+}
+
+} /* namespace psp */
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/print/psputil.hxx b/vcl/unx/generic/print/psputil.hxx
new file mode 100644
index 000000000..1a5ec620d
--- /dev/null
+++ b/vcl/unx/generic/print/psputil.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_GENERIC_PRINT_PSPUTIL_HXX
+#define INCLUDED_VCL_GENERIC_PRINT_PSPUTIL_HXX
+
+#include <osl/file.hxx>
+
+#include <rtl/math.hxx>
+#include <rtl/ustring.hxx>
+#include <rtl/strbuf.hxx>
+#include <rtl/string.hxx>
+
+namespace psp {
+
+/*
+ * string convenience routines
+ */
+sal_Int32 getHexValueOf (sal_Int32 nValue, OStringBuffer& pBuffer);
+sal_Int32 getAlignedHexValueOf (sal_Int32 nValue, OStringBuffer& pBuffer);
+sal_Int32 getValueOf (sal_Int32 nValue, OStringBuffer& pBuffer);
+sal_Int32 appendStr (const char* pSrc, OStringBuffer& pDst);
+
+inline void getValueOfDouble( OStringBuffer& pBuffer, double f, int nPrecision = 0)
+{
+ pBuffer.append(rtl::math::doubleToString( f, rtl_math_StringFormat_G, nPrecision, '.', true ));
+}
+
+bool WritePS (osl::File* pFile, const char* pString);
+bool WritePS (osl::File* pFile, const char* pString, sal_uInt64 nInLength);
+bool WritePS (osl::File* pFile, const OString &rString);
+bool WritePS (osl::File* pFile, const OUString &rString);
+
+} /* namespace psp */
+
+#endif // INCLUDED_VCL_GENERIC_PRINT_PSPUTIL_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/print/text_gfx.cxx b/vcl/unx/generic/print/text_gfx.cxx
new file mode 100644
index 000000000..d3153952b
--- /dev/null
+++ b/vcl/unx/generic/print/text_gfx.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 "psputil.hxx"
+#include "glyphset.hxx"
+
+#include <unx/printergfx.hxx>
+#include <unx/fontmanager.hxx>
+
+using namespace psp ;
+
+/*
+ * implement text handling printer routines,
+ */
+
+void PrinterGfx::SetFont(
+ sal_Int32 nFontID,
+ sal_Int32 nHeight,
+ sal_Int32 nWidth,
+ sal_Int32 nAngle,
+ bool bVertical,
+ bool bArtItalic,
+ bool bArtBold
+ )
+{
+ // font and encoding will be set by drawText again immediately
+ // before PSShowText
+ mnFontID = nFontID;
+ maVirtualStatus.maFont.clear();
+ maVirtualStatus.maEncoding = RTL_TEXTENCODING_DONTKNOW;
+ maVirtualStatus.mnTextHeight = nHeight;
+ maVirtualStatus.mnTextWidth = nWidth;
+ maVirtualStatus.mbArtItalic = bArtItalic;
+ maVirtualStatus.mbArtBold = bArtBold;
+ mnTextAngle = nAngle;
+ mbTextVertical = bVertical;
+}
+
+void PrinterGfx::drawGlyph(const Point& rPoint,
+ sal_GlyphId aGlyphId)
+{
+
+ // draw the string
+ // search for a glyph set matching the set font
+ bool bGlyphFound = false;
+ for (auto & elem : maPS3Font)
+ if ( (elem.GetFontID() == mnFontID)
+ && (elem.IsVertical() == mbTextVertical))
+ {
+ elem.DrawGlyph (*this, rPoint, aGlyphId);
+ bGlyphFound = true;
+ break;
+ }
+
+ // not found ? create a new one
+ if (!bGlyphFound)
+ {
+ maPS3Font.emplace_back(mnFontID, mbTextVertical);
+ maPS3Font.back().DrawGlyph (*this, rPoint, aGlyphId);
+ }
+}
+
+void PrinterGfx::DrawGlyph(const Point& rPoint,
+ const GlyphItem& rGlyph)
+{
+ // move and rotate the user coordinate system
+ // avoid the gsave/grestore for the simple cases since it allows
+ // reuse of the current font if it hasn't changed
+ sal_Int32 nCurrentTextAngle = mnTextAngle;
+ Point aPoint( rPoint );
+
+ if (nCurrentTextAngle != 0)
+ {
+ PSGSave ();
+ PSTranslate (rPoint);
+ PSRotate (nCurrentTextAngle);
+ mnTextAngle = 0;
+ aPoint = Point( 0, 0 );
+ }
+
+ if (mbTextVertical && rGlyph.IsVertical())
+ {
+ sal_Int32 nTextHeight = maVirtualStatus.mnTextHeight;
+ sal_Int32 nTextWidth = maVirtualStatus.mnTextWidth ? maVirtualStatus.mnTextWidth : maVirtualStatus.mnTextHeight;
+ sal_Int32 nAscend = mrFontMgr.getFontAscend( mnFontID );
+ sal_Int32 nDescend = mrFontMgr.getFontDescend( mnFontID );
+
+ nDescend = nDescend * nTextHeight / 1000;
+ nAscend = nAscend * nTextHeight / 1000;
+
+ Point aRotPoint( -nDescend*nTextWidth/nTextHeight, nAscend*nTextWidth/nTextHeight );
+
+ // transform matrix to new individual direction
+ PSGSave ();
+ GraphicsStatus aSaveStatus = maVirtualStatus;
+ // switch font aspect
+ maVirtualStatus.mnTextWidth = nTextHeight;
+ maVirtualStatus.mnTextHeight = nTextWidth;
+ if( aPoint.X() || aPoint.Y() )
+ PSTranslate( aPoint );
+ PSRotate (900);
+ // draw the rotated glyph
+ drawGlyph(aRotPoint, rGlyph.glyphId());
+
+ // restore previous state
+ maVirtualStatus = aSaveStatus;
+ PSGRestore();
+ }
+ else
+ drawGlyph(aPoint, rGlyph.glyphId());
+
+ // restore the user coordinate system
+ if (nCurrentTextAngle != 0)
+ {
+ PSGRestore ();
+ mnTextAngle = nCurrentTextAngle;
+ }
+}
+
+/*
+ * spool the converted truetype fonts to the page header after the page body is
+ * complete
+ * for Type1 fonts spool additional reencoding vectors that are necessary to access the
+ * whole font
+ */
+
+void
+PrinterGfx::OnEndJob ()
+{
+ maPS3Font.clear();
+}
+
+void
+PrinterGfx::writeResources( osl::File* pFile, std::vector< OString >& rSuppliedFonts )
+{
+ // write glyphsets and reencodings
+ for (auto & PS3Font : maPS3Font)
+ {
+ PS3Font.PSUploadFont (*pFile, *this, mbUploadPS42Fonts, rSuppliedFonts );
+ }
+}
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/printer/configuration/README b/vcl/unx/generic/printer/configuration/README
new file mode 100644
index 000000000..c39237a53
--- /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 000000000..177e2c4e0
--- /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 [324 648] /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 [324 648] (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 [324 648] /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 324 648"
+*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: "324 648"
+*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 000000000..1b56e084e
--- /dev/null
+++ b/vcl/unx/generic/printer/configuration/psprint.conf
@@ -0,0 +1,99 @@
+;
+; This file is part of the LibreOffice project.
+;
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0. If a copy of the MPL was not distributed with this
+; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;
+; This file incorporates work covered by the following license notice:
+;
+; Licensed to the Apache Software Foundation (ASF) under one or more
+; contributor license agreements. See the NOTICE file distributed
+; with this work for additional information regarding copyright
+; ownership. The ASF licenses this file to you under the Apache
+; License, Version 2.0 (the "License"); you may not use this 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
+
+; PSLevel: the default setting of the PostScript level of the output
+; possible values: 0: driver setting, 1: level 1, 2: level2
+; if key is absent the default is 0
+; PSLevel=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 000000000..bebd56833
--- /dev/null
+++ b/vcl/unx/generic/printer/cpdmgr.cxx
@@ -0,0 +1,754 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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;
+ GDBusNodeInfo *introspection_data;
+
+ // Get Interface for introspection
+ g_file_get_contents (FRONTEND_INTERFACE, &contents, nullptr, nullptr);
+ 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)
+ {
+ GDBusProxy *proxy;
+ // Get Interface for introspection
+ g_file_get_contents (BACKEND_INTERFACE, &contents, nullptr, nullptr);
+ introspection_data = g_dbus_node_info_new_for_xml (contents, nullptr);
+ 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_free(backend.second);
+ 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);
+ }
+}
+
+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;
+ GDBusNodeInfo *introspection_data;
+
+ // Get Interface for introspection
+ g_file_get_contents ("/usr/share/dbus-1/interfaces/org.openprinting.Backend.xml", &contents, nullptr, nullptr);
+ 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(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().c_str(), aEncoding );
+ OUString aUniqueName = OStringToOUString( uniqueName.str().c_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().c_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.setDefaultBackend(true);
+ 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;
+ 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")) {
+ aKey = OString("sides");
+ } else if (aKey.equals("Resolution")) {
+ aKey = OString("printer-resolution");
+ } else if (aKey.equals("PageSize")) {
+ aKey = OString("media");
+ }
+ if (aKey.equals("sides")) {
+ if (aValue.equals("None")) {
+ aValue = OString("one-sided");
+ } else if (aValue.equals("DuplexNoTumble")) {
+ aValue = OString("two-sided-long-edge");
+ } else if (aValue.equals("DuplexTumble")) {
+ aValue = OString("two-sided-short-edge");
+ }
+ }
+ g_variant_builder_add(builder, "(ss)", aKey.getStr(), aValue.getStr());
+ }
+ }
+ }
+ if( rJob.m_nPDFDevice > 0 && 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( pFile );
+ }
+#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 000000000..f5a50345a
--- /dev/null
+++ b/vcl/unx/generic/printer/cupsmgr.cxx
@@ -0,0 +1,965 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <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>
+
+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
+ OString aResult = cupsGetPPD(m_aParameter.getStr());
+#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 )
+ {
+ // if the thread is still running here, then
+ // cupsGetDests is hung; terminate the thread instead of joining
+ osl_terminateThread( m_aDestThread );
+ osl_destroyThread( m_aDestThread );
+ }
+
+ if (m_nDests && m_pDests)
+ cupsFreeDests( m_nDests, static_cast<cups_dest_t*>(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 )
+ {
+ 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
+}
+
+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#)
+ bool bUsePDF = false;
+ cups_dest_t* pDest = static_cast<cups_dest_t*>(m_pDests);
+ const char* pOpt = cupsGetOption( "printer-info",
+ pDest->num_options,
+ pDest->options );
+ if( pOpt )
+ {
+ m_bUseIncludeFeature = true;
+ bUsePDF = officecfg::Office::Common::Print::Option::Printer::PDFAsStandardPrintJobFormat::get();
+ }
+
+ m_aGlobalDefaults.setDefaultBackend(bUsePDF);
+
+ // do not send include JobPatch; CUPS will insert that itself
+ // TODO: currently unknown which versions of CUPS insert JobPatches
+ // so currently it is assumed CUPS = don't insert JobPatch files
+ m_bUseJobPatch = false;
+
+ 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 = static_cast<cups_dest_t*>(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;
+
+ 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);
+ }
+
+ // 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.setDefaultBackend(bUsePDF);
+ aPrinter.m_aInfo.m_aDriverName = "CUPS:" + aPrinterName;
+
+ 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 = static_cast<cups_dest_t*>(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 );
+
+ 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_nPDFDevice > 0 && 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, const OString& rServer, const OString& 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, const OString& rServer, const OString& 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.isEmpty())
+ m_xUserEdit->grab_focus();
+ else
+ {
+ m_xUserEdit->set_text(OStringToOUString(rUserName, osl_getThreadTextEncoding()));
+ m_xPassEdit->grab_focus();
+ }
+ }
+
+ bool AuthenticateQuery(const OString& rServer, OString& rUserName, OString& rPassword)
+ {
+ bool bRet = false;
+
+ vcl::Window* pWin = Application::GetDefDialogParent();
+ RTSPWDialog aDialog(pWin ? pWin->GetFrameWeld() : nullptr, 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
+ {
+ OUString aToken = aInfo.m_aAuthInfoRequired.getToken(0, ',', nIndex);
+ if (aToken == "domain")
+ bDomain = true;
+ else if (aToken == "username")
+ bUser = true;
+ else if (aToken == "password")
+ bPass = true;
+ }
+ while (nIndex >= 0);
+
+ if (bDomain || bUser || bPass)
+ {
+ OString sPrinterName(OUStringToOString(rPrintername, RTL_TEXTENCODING_UTF8));
+ OString sUser = cupsUser();
+ vcl::Window* pWin = Application::GetDefDialogParent();
+ RTSPWDialog aDialog(pWin ? pWin->GetFrameWeld() : nullptr, 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 = static_cast<cups_dest_t*>(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( pFile );
+ 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, static_cast<cups_dest_t*>(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 000000000..362305bea
--- /dev/null
+++ b/vcl/unx/generic/printer/jobdata.cxx
@@ -0,0 +1,306 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <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_nPSLevel = rRight.m_nPSLevel;
+ m_nPDFDevice = rRight.m_nPDFDevice;
+ 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 )
+{
+ if (m_nPDFDevice > 0)
+ {
+ m_bCollate = bCollate;
+ return;
+ }
+ const PPDParser* pParser = m_aContext.getParser();
+ if( pParser )
+ {
+ const PPDKey* pKey = pParser->getKey( "Collate" );
+ if( pKey )
+ {
+ const PPDValue* pVal = nullptr;
+ if( bCollate )
+ pVal = pKey->getValue( "True" );
+ else
+ {
+ pVal = pKey->getValue( "False" );
+ if( ! pVal )
+ pVal = pKey->getValue( "None" );
+ }
+ m_aContext.setValue( pKey, pVal );
+ }
+ }
+}
+
+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( void*& 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;
+
+ aLine.append("printer=");
+ aLine.append(OUStringToOString(m_aPrinterName, RTL_TEXTENCODING_UTF8));
+ aStream.WriteLine(aLine.makeStringAndClear());
+
+ aLine.append("orientation=");
+ if (m_eOrientation == orientation::Landscape)
+ aLine.append("Landscape");
+ else
+ aLine.append("Portrait");
+ aStream.WriteLine(aLine.makeStringAndClear());
+
+ aLine.append("copies=");
+ aLine.append(static_cast<sal_Int32>(m_nCopies));
+ aStream.WriteLine(aLine.makeStringAndClear());
+
+ if (m_nPDFDevice > 0)
+ {
+ aLine.append("collate=");
+ aLine.append(OString::boolean(m_bCollate));
+ aStream.WriteLine(aLine.makeStringAndClear());
+ }
+
+ aLine.append("margindajustment=");
+ aLine.append(static_cast<sal_Int32>(m_nLeftMarginAdjust));
+ aLine.append(',');
+ aLine.append(static_cast<sal_Int32>(m_nRightMarginAdjust));
+ aLine.append(',');
+ aLine.append(static_cast<sal_Int32>(m_nTopMarginAdjust));
+ aLine.append(',');
+ aLine.append(static_cast<sal_Int32>(m_nBottomMarginAdjust));
+ aStream.WriteLine(aLine.makeStringAndClear());
+
+ aLine.append("colordepth=");
+ aLine.append(static_cast<sal_Int32>(m_nColorDepth));
+ aStream.WriteLine(aLine.makeStringAndClear());
+
+ aLine.append("pslevel=");
+ aLine.append(static_cast<sal_Int32>(m_nPSLevel));
+ aStream.WriteLine(aLine.makeStringAndClear());
+
+ aLine.append("pdfdevice=");
+ aLine.append(static_cast<sal_Int32>(m_nPDFDevice));
+ aStream.WriteLine(aLine.makeStringAndClear());
+
+ aLine.append("colordevice=");
+ aLine.append(static_cast<sal_Int32>(m_nColorDevice));
+ aStream.WriteLine(aLine.makeStringAndClear());
+
+ // now append the PPDContext stream buffer
+ aStream.WriteLine( "PPDContexData" );
+ 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::malloc( bytes );
+ memcpy( pData, 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;
+ bool bPSLevel = false;
+ bool bPDFDevice = false;
+
+ const char printerEquals[] = "printer=";
+ const char orientatationEquals[] = "orientation=";
+ const char copiesEquals[] = "copies=";
+ const char collateEquals[] = "collate=";
+ const char margindajustmentEquals[] = "margindajustment=";
+ const char colordepthEquals[] = "colordepth=";
+ const char colordeviceEquals[] = "colordevice=";
+ const char pslevelEquals[] = "pslevel=";
+ const char pdfdeviceEquals[] = "pdfdevice=";
+
+ while( ! aStream.eof() )
+ {
+ aStream.ReadLine( aLine );
+ if (aLine.startsWith("JobData"))
+ bVersion = true;
+ else if (aLine.startsWith(printerEquals))
+ {
+ bPrinter = true;
+ rJobData.m_aPrinterName = OStringToOUString(aLine.copy(RTL_CONSTASCII_LENGTH(printerEquals)), RTL_TEXTENCODING_UTF8);
+ }
+ else if (aLine.startsWith(orientatationEquals))
+ {
+ bOrientation = true;
+ rJobData.m_eOrientation = aLine.copy(RTL_CONSTASCII_LENGTH(orientatationEquals)).equalsIgnoreAsciiCase("landscape") ? orientation::Landscape : orientation::Portrait;
+ }
+ else if (aLine.startsWith(copiesEquals))
+ {
+ bCopies = true;
+ rJobData.m_nCopies = aLine.copy(RTL_CONSTASCII_LENGTH(copiesEquals)).toInt32();
+ }
+ else if (aLine.startsWith(collateEquals))
+ {
+ rJobData.m_bCollate = aLine.copy(RTL_CONSTASCII_LENGTH(collateEquals)).toBoolean();
+ }
+ else if (aLine.startsWith(margindajustmentEquals))
+ {
+ bMargin = true;
+ sal_Int32 nIdx {RTL_CONSTASCII_LENGTH(margindajustmentEquals)};
+ rJobData.m_nLeftMarginAdjust = aLine.getToken(0, ',', nIdx).toInt32();
+ rJobData.m_nRightMarginAdjust = aLine.getToken(0, ',', nIdx).toInt32();
+ rJobData.m_nTopMarginAdjust = aLine.getToken(0, ',', nIdx).toInt32();
+ rJobData.m_nBottomMarginAdjust = aLine.getToken(0, ',', nIdx).toInt32();
+ }
+ else if (aLine.startsWith(colordepthEquals))
+ {
+ bColorDepth = true;
+ rJobData.m_nColorDepth = aLine.copy(RTL_CONSTASCII_LENGTH(colordepthEquals)).toInt32();
+ }
+ else if (aLine.startsWith(colordeviceEquals))
+ {
+ bColorDevice = true;
+ rJobData.m_nColorDevice = aLine.copy(RTL_CONSTASCII_LENGTH(colordeviceEquals)).toInt32();
+ }
+ else if (aLine.startsWith(pslevelEquals))
+ {
+ bPSLevel = true;
+ rJobData.m_nPSLevel = aLine.copy(RTL_CONSTASCII_LENGTH(pslevelEquals)).toInt32();
+ }
+ else if (aLine.startsWith(pdfdeviceEquals))
+ {
+ bPDFDevice = true;
+ rJobData.m_nPDFDevice = aLine.copy(RTL_CONSTASCII_LENGTH(pdfdeviceEquals)).toInt32();
+ }
+ else if (aLine == "PPDContexData" && 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 && bPSLevel && bPDFDevice && bColorDevice && bColorDepth;
+}
+
+void JobData::resolveDefaultBackend()
+{
+ if (m_nPSLevel == 0 && m_nPDFDevice == 0)
+ setDefaultBackend(officecfg::Office::Common::Print::Option::Printer::PDFAsStandardPrintJobFormat::get());
+}
+
+void JobData::setDefaultBackend(bool bUsePDF)
+{
+ if (bUsePDF && m_nPSLevel == 0 && m_nPDFDevice == 0)
+ m_nPDFDevice = 1;
+}
+
+/* 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 000000000..d949441db
--- /dev/null
+++ b/vcl/unx/generic/printer/ppdparser.cxx
@@ -0,0 +1,1987 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <i18nlangtag/languagetag.hxx>
+#include <ppdparser.hxx>
+#include <strhelper.hxx>
+#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/mutex.hxx>
+#include <osl/file.hxx>
+#include <osl/process.h>
+#include <osl/thread.h>
+#include <rtl/strbuf.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <rtl/instance.hxx>
+#include <sal/log.hxx>
+#include <salhelper/linkhelper.hxx>
+
+#include <com/sun/star/lang/Locale.hpp>
+
+#include <unordered_map>
+
+#ifdef ENABLE_CUPS
+#include <cups/cups.h>
+#endif
+
+#include <config_dbus.h>
+#include <config_gio.h>
+#include <boost/functional/hash.hpp>
+
+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;
+ boost::hash_combine(seed, rLocale.Language.hashCode());
+ boost::hash_combine(seed, rLocale.Country.hashCode());
+ boost::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(
+ const OUString& i_rKey,
+ const OUString& i_rOption,
+ const OUString& i_rValue,
+ const OUString& i_rTranslation,
+ const css::lang::Locale& i_rLocale
+ );
+
+ void insertOption( const OUString& i_rKey,
+ const OUString& i_rOption,
+ const OUString& i_rTranslation,
+ const css::lang::Locale& i_rLocale )
+ {
+ insertValue( i_rKey, i_rOption, OUString(), i_rTranslation, i_rLocale );
+ }
+
+ void insertKey( const OUString& i_rKey,
+ const OUString& i_rTranslation,
+ const css::lang::Locale& i_rLocale = css::lang::Locale() )
+ {
+ insertValue( i_rKey, OUString(), OUString(), i_rTranslation, i_rLocale );
+ }
+
+ OUString translateValue(
+ const OUString& i_rKey,
+ const OUString& i_rOption
+ ) const;
+
+ OUString translateOption( const OUString& i_rKey,
+ const OUString& i_rOption ) const
+ {
+ return translateValue( i_rKey, i_rOption );
+ }
+
+ OUString translateKey( const OUString& i_rKey ) const
+ {
+ return translateValue( i_rKey, OUString() );
+ }
+ };
+
+ 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(
+ const OUString& i_rKey,
+ const OUString& i_rOption,
+ const OUString& i_rValue,
+ const OUString& i_rTranslation,
+ const css::lang::Locale& i_rLocale
+ )
+ {
+ OUStringBuffer aKey( i_rKey.getLength() + i_rOption.getLength() + i_rValue.getLength() + 2 );
+ aKey.append( i_rKey );
+ if( !i_rOption.isEmpty() || !i_rValue.isEmpty() )
+ {
+ aKey.append( ':' );
+ aKey.append( i_rOption );
+ }
+ if( !i_rValue.isEmpty() )
+ {
+ aKey.append( ':' );
+ aKey.append( 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(
+ const OUString& i_rKey,
+ const OUString& i_rOption
+ ) const
+ {
+ OUString aResult;
+
+ OUStringBuffer aKey( i_rKey.getLength() + i_rOption.getLength() + 2 );
+ aKey.append( i_rKey );
+ if( !i_rOption.isEmpty() )
+ {
+ aKey.append( ':' );
+ aKey.append( 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::unique_ptr<std::unordered_map< OUString, OUString >> pAllPPDFiles;
+ };
+}
+
+using namespace psp;
+
+namespace
+{
+ struct thePPDCache : public rtl::Static<PPDCache, 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 */ )
+ {
+ // so let's try to decompress the stream
+ mpMemStream.reset( new SvMemoryStream( 4096, 4096 ) );
+ ZCodec aCodec;
+ aCodec.BeginCompression( ZCODEC_DEFAULT_COMPRESSION, /*gzLib*/true );
+ 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 = thePPDCache::get();
+
+ osl::Directory aDir( rDir );
+ if ( aDir.open() == osl::FileBase::E_None )
+ {
+ 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.pAllPPDFiles)[ 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.pAllPPDFiles )
+ return;
+
+ rPPDCache.pAllPPDFiles.reset(new std::unordered_map< OUString, OUString >);
+
+ // 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.pAllPPDFiles->find( OUString( "SGENPRT" ) ) == rPPDCache.pAllPPDFiles->end() )
+ {
+ // 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.pAllPPDFiles->find("SGENPRT") ==
+ rPPDCache.pAllPPDFiles->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 = thePPDCache::get();
+
+ 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.pAllPPDFiles->find( aBase );
+ nLastIndex = aBase.lastIndexOf( '.' );
+ if( nLastIndex > 0 )
+ aBase = aBase.copy( 0, nLastIndex );
+ } while( it == rPPDCache.pAllPPDFiles->end() && nLastIndex > 0 );
+
+ if( it == rPPDCache.pAllPPDFiles->end() && bRetry )
+ {
+ // a new file ? rehash
+ rPPDCache.pAllPPDFiles.reset();
+ bRetry = false;
+ // note this is optimized for office start where
+ // no new files occur and initPPDFiles is called only once
+ }
+ } while( ! rPPDCache.pAllPPDFiles );
+
+ if( it != rPPDCache.pAllPPDFiles->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 )
+{
+ static ::osl::Mutex aMutex;
+ ::osl::Guard< ::osl::Mutex > 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 = thePPDCache::get();
+ 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(const OUString& rFile, const std::vector<PPDKey*>& keys)
+ : m_aFile(rFile)
+ , m_bColorDevice(false)
+ , m_bType42Capable(false)
+ , m_nLanguageLevel(0)
+ , 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( PWG_TO_POINTS(pPWGMedia -> width) );
+ aBuf.append( " " );
+ aBuf.append( 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_WARN( "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);
+
+ auto pFontList = getKey( "Font" );
+ if (pFontList == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no Font in " << m_aFile);
+ }
+
+ // fill in direct values
+ if( (pKey = getKey( "print-color-mode" )) )
+ m_bColorDevice = pKey->countValues() > 1;
+}
+
+PPDParser::PPDParser( const OUString& rFile ) :
+ m_aFile( rFile ),
+ m_bColorDevice( false ),
+ m_bType42Capable( false ),
+ m_nLanguageLevel( 0 ),
+ 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::stripStart(aCurLine, ' ');
+ aCurLine = comphelper::string::stripEnd(aCurLine, ' ');
+ aCurLine = comphelper::string::stripStart(aCurLine, '\t');
+ aCurLine = comphelper::string::stripEnd(aCurLine, '\t');
+ aCurLine = comphelper::string::stripEnd(aCurLine, '\r');
+ aCurLine = comphelper::string::stripEnd(aCurLine, '\n');
+ aCurLine = comphelper::string::stripStart(aCurLine, '"');
+ aCurLine = comphelper::string::stripEnd(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
+
+ // fill in shortcuts
+ const PPDKey* pKey;
+
+ 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_WARN( "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);
+
+ auto pFontList = getKey( "Font" );
+ if (pFontList == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no Font in " << m_aFile);
+ }
+
+ // fill in direct values
+ if ((pKey = getKey("ColorDevice")))
+ {
+ if (const PPDValue* pValue = pKey->getValue(0))
+ m_bColorDevice = pValue->m_aValue.startsWithIgnoreAsciiCase("true");
+ }
+
+ if ((pKey = getKey("LanguageLevel")))
+ {
+ if (const PPDValue* pValue = pKey->getValue(0))
+ m_nLanguageLevel = pValue->m_aValue.toInt32();
+ }
+ if ((pKey = getKey("TTRasterizer")))
+ {
+ if (const PPDValue* pValue = pKey->getValue(0))
+ m_bType42Capable = pValue->m_aValue.equalsIgnoreAsciiCase( "Type42" );
+ }
+}
+
+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.makeStringAndClear(), 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 const OString aDefaultPPDGroupName("General");
+
+ 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.copy( 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');
+ aBuffer.append(*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.copy(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.copy(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.copy(8), RTL_TEXTENCODING_MS_1252));
+ sal_Int32 nPos = aKey.indexOf( ':' );
+ if( nPos != -1 )
+ {
+ aKey = aKey.copy(0, nPos);
+ OUString aOption(OStringToOUString(
+ WhitespaceToSpace(aLine.copy(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, const OString& 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();
+ OString aSetup = GetCommandLineToken( 1, aLine );
+ OUString aKey(OStringToOUString(GetCommandLineToken(2, aLine), RTL_TEXTENCODING_MS_1252));
+ if( aKey[ 0 ] != '*' )
+ return; // invalid order dependency
+ aKey = aKey.replaceAt( 0, 1, "" );
+
+ 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;
+ if( aSetup == "ExitServer" )
+ pKey->m_eSetupType = PPDKey::SetupType::ExitServer;
+ else if( aSetup == "Prolog" )
+ pKey->m_eSetupType = PPDKey::SetupType::Prolog;
+ else if( aSetup == "DocumentSetup" )
+ pKey->m_eSetupType = PPDKey::SetupType::DocumentSetup;
+ else if( aSetup == "PageSetup" )
+ pKey->m_eSetupType = PPDKey::SetupType::PageSetup;
+ else if( aSetup == "JCLSetup" )
+ pKey->m_eSetupType = PPDKey::SetupType::JCLSetup;
+ else
+ pKey->m_eSetupType = PPDKey::SetupType::AnySetup;
+}
+
+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, "");
+ 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, "" );
+ 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(
+ const OUString& 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(
+ const OUString& 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::matchPaper( int nWidth, int nHeight ) 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;
+ }
+ }
+ }
+
+ static bool bDontSwap = false;
+ if( nPDim == -1 && ! bDontSwap )
+ {
+ // swap portrait/landscape and try again
+ bDontSwap = true;
+ OUString rRet = matchPaper( nHeight, nWidth );
+ bDontSwap = false;
+ return rRet;
+ }
+
+ return nPDim != -1 ? m_pPaperDimensions->getValue( nPDim )->m_aOption : OUString();
+}
+
+OUString PPDParser::getDefaultInputSlot() const
+{
+ if( m_pDefaultInputSlot )
+ return m_pDefaultInputSlot->m_aValue;
+ return OUString();
+}
+
+void PPDParser::getResolutionFromString(
+ const OUString& rString,
+ int& rXRes, int& rYRes )
+{
+ rXRes = rYRes = 300;
+
+ const sal_Int32 nDPIPos {rString.indexOf( "dpi" )};
+ if( nDPIPos != -1 )
+ {
+ const sal_Int32 nPos {rString.indexOf( 'x' )};
+ if( nPos >=0 )
+ {
+ rXRes = rString.copy( 0, nPos ).toInt32();
+ rYRes = rString.copy(nPos+1, nDPIPos - nPos - 1).toInt32();
+ }
+ else
+ rXRes = rYRes = rString.copy( 0, nDPIPos ).toInt32();
+ }
+}
+
+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( const OUString& 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( const OUString& rKey ) :
+ m_aKey( rKey ),
+ m_pDefaultValue( nullptr ),
+ m_bQueryValue( false ),
+ m_bUIOption( false ),
+ m_nOrderDependency( 100 ),
+ m_eSetupType( SetupType::AnySetup )
+{
+}
+
+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_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";
+ 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.copy( 0, nPos ), RTL_TEXTENCODING_MS_1252 ) );
+ if( pKey )
+ {
+ const PPDValue* pValue = nullptr;
+ OUString aOption(OStringToOUString(aLine.copy(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 )
+ {
+ const PPDKey* pKey = m_pParser->getKey( "PageSize" );
+ if( pKey )
+ {
+ 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 000000000..ff4b5e691
--- /dev/null
+++ b/vcl/unx/generic/printer/printerinfomanager.cxx
@@ -0,0 +1,900 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/helper.hxx>
+
+#include <saldatabasic.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 <osl/mutex.hxx>
+
+// filename of configuration files
+#define PRINT_FILENAME "psprint.conf"
+// the group of the global defaults
+#define GLOBAL_DEFAULTS_GROUP "__Global_Printer_Defaults__"
+
+#include <cstddef>
+#include <unordered_set>
+
+using namespace psp;
+using namespace osl;
+
+namespace psp
+{
+ class SystemQueueInfo final : public Thread
+ {
+ mutable 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()
+{
+ SalData* pSalData = GetSalData();
+
+ if( ! pSalData->m_pPIManager )
+ {
+ pSalData->m_pPIManager = CPDManager::tryLoadCPD();
+ if( ! pSalData->m_pPIManager )
+ pSalData->m_pPIManager = CUPSManager::tryLoadCUPS();
+ if( ! pSalData->m_pPIManager )
+ pSalData->m_pPIManager = new PrinterInfoManager();
+ pSalData->m_pPIManager->initialize();
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "PrinterInfoManager::get "
+ << "create Manager of type "
+ << ((int) pSalData->m_pPIManager->getType()));
+#endif
+ }
+
+ return *pSalData->m_pPIManager;
+}
+
+void PrinterInfoManager::release()
+{
+ SalData* pSalData = GetSalData();
+ delete pSalData->m_pPIManager;
+ pSalData->m_pPIManager = nullptr;
+}
+
+PrinterInfoManager::PrinterInfoManager( Type eType ) :
+ m_eType( eType ),
+ m_bUseIncludeFeature( false ),
+ m_bUseJobPatch( true ),
+ 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_bUseIncludeFeature = false;
+ 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" ) );
+ if (!aValue.isEmpty())
+ m_aGlobalDefaults.m_nCopies = aValue.toInt32();
+
+ aValue = aConfig.ReadKey( "Orientation" );
+ if (!aValue.isEmpty())
+ m_aGlobalDefaults.m_eOrientation = aValue.equalsIgnoreAsciiCase("Landscape") ? orientation::Landscape : orientation::Portrait;
+
+ aValue = aConfig.ReadKey( "MarginAdjust" );
+ if (!aValue.isEmpty())
+ {
+ sal_Int32 nIdx {0};
+ m_aGlobalDefaults.m_nLeftMarginAdjust = aValue.getToken(0, ',', nIdx).toInt32();
+ m_aGlobalDefaults.m_nRightMarginAdjust = aValue.getToken(0, ',', nIdx).toInt32();
+ m_aGlobalDefaults.m_nTopMarginAdjust = aValue.getToken(0, ',', nIdx).toInt32();
+ m_aGlobalDefaults.m_nBottomMarginAdjust = aValue.getToken(0, ',', nIdx).toInt32();
+ }
+
+ aValue = aConfig.ReadKey( "ColorDepth", "24" );
+ if (!aValue.isEmpty())
+ m_aGlobalDefaults.m_nColorDepth = aValue.toInt32();
+
+ aValue = aConfig.ReadKey( "ColorDevice" );
+ if (!aValue.isEmpty())
+ m_aGlobalDefaults.m_nColorDevice = aValue.toInt32();
+
+ aValue = aConfig.ReadKey( "PSLevel" );
+ if (!aValue.isEmpty())
+ m_aGlobalDefaults.m_nPSLevel = aValue.toInt32();
+
+ aValue = aConfig.ReadKey( "PDFDevice" );
+ if (!aValue.isEmpty())
+ m_aGlobalDefaults.m_nPDFDevice = 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.copy(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" );
+ 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.copy(nNamePos+1),
+ RTL_TEXTENCODING_UTF8);
+ aPrinter.m_aInfo.m_aPrinterName = aPrinterName;
+ aPrinter.m_aInfo.m_aDriverName = OStringToOUString(aValue.copy(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" );
+ // 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";
+ #endif
+ }
+ aPrinter.m_aInfo.m_aCommand = OStringToOUString(aValue, RTL_TEXTENCODING_UTF8);
+ }
+
+ aValue = aConfig.ReadKey( "QuickCommand" );
+ aPrinter.m_aInfo.m_aQuickCommand = OStringToOUString(aValue, RTL_TEXTENCODING_UTF8);
+
+ aValue = aConfig.ReadKey( "Features" );
+ aPrinter.m_aInfo.m_aFeatures = OStringToOUString(aValue, RTL_TEXTENCODING_UTF8);
+
+ // override the settings in m_aGlobalDefaults if keys exist
+ aValue = aConfig.ReadKey( "DefaultPrinter" );
+ if (aValue != "0" && !aValue.equalsIgnoreAsciiCase("false"))
+ aDefaultPrinter = aPrinterName;
+
+ aValue = aConfig.ReadKey( "Location" );
+ aPrinter.m_aInfo.m_aLocation = OStringToOUString(aValue, RTL_TEXTENCODING_UTF8);
+
+ aValue = aConfig.ReadKey( "Comment" );
+ aPrinter.m_aInfo.m_aComment = OStringToOUString(aValue, RTL_TEXTENCODING_UTF8);
+
+ aValue = aConfig.ReadKey( "Copies" );
+ if (!aValue.isEmpty())
+ aPrinter.m_aInfo.m_nCopies = aValue.toInt32();
+
+ aValue = aConfig.ReadKey( "Orientation" );
+ if (!aValue.isEmpty())
+ aPrinter.m_aInfo.m_eOrientation = aValue.equalsIgnoreAsciiCase("Landscape") ? orientation::Landscape : orientation::Portrait;
+
+ aValue = aConfig.ReadKey( "MarginAdjust" );
+ if (!aValue.isEmpty())
+ {
+ sal_Int32 nIdx {0};
+ aPrinter.m_aInfo.m_nLeftMarginAdjust = aValue.getToken(0, ',', nIdx).toInt32();
+ aPrinter.m_aInfo.m_nRightMarginAdjust = aValue.getToken(0, ',', nIdx).toInt32();
+ aPrinter.m_aInfo.m_nTopMarginAdjust = aValue.getToken(0, ',', nIdx).toInt32();
+ aPrinter.m_aInfo.m_nBottomMarginAdjust = aValue.getToken(0, ',', nIdx).toInt32();
+ }
+
+ aValue = aConfig.ReadKey( "ColorDepth" );
+ if (!aValue.isEmpty())
+ aPrinter.m_aInfo.m_nColorDepth = aValue.toInt32();
+
+ aValue = aConfig.ReadKey( "ColorDevice" );
+ if (!aValue.isEmpty())
+ aPrinter.m_aInfo.m_nColorDevice = aValue.toInt32();
+
+ aValue = aConfig.ReadKey( "PSLevel" );
+ if (!aValue.isEmpty())
+ aPrinter.m_aInfo.m_nPSLevel = aValue.toInt32();
+
+ aValue = aConfig.ReadKey( "PDFDevice" );
+ if (!aValue.isEmpty())
+ aPrinter.m_aInfo.m_nPDFDevice = 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.copy(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 );
+
+ // if it's a "Generic Printer", apply defaults from config...
+ aPrinter.m_aInfo.resolveDefaultBackend();
+
+ // 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, const char* 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
+{
+ MutexGuard aGuard( m_aMutex );
+ bool bChanged = m_bChanged;
+ return bChanged;
+}
+
+void SystemQueueInfo::getSystemQueues( std::vector< PrinterInfoManager::SystemPrintQueue >& rQueues )
+{
+ MutexGuard aGuard( m_aMutex );
+ rQueues = m_aQueues;
+ m_bChanged = false;
+}
+
+OUString SystemQueueInfo::getCommand() const
+{
+ MutexGuard aGuard( m_aMutex );
+ OUString aRet = m_aCommand;
+ return aRet;
+}
+
+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, inser 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.copy( 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;
+ }
+ }
+ }
+ }
+}
+
+static 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 + OStringLiteral(" 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 );
+ MutexGuard 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 000000000..a246a975a
--- /dev/null
+++ b/vcl/unx/generic/window/salframe.cxx
@@ -0,0 +1,4107 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <tools/debug.hxx>
+
+#include <vcl/event.hxx>
+#include <vcl/floatwin.hxx>
+#include <vcl/keycodes.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/bitmapaccess.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 <saldatabasic.hxx>
+#include <unx/saldisp.hxx>
+#include <unx/salgdi.h>
+#include <unx/salframe.h>
+#include <unx/wmadaptor.hxx>
+#include <unx/salbmp.h>
+#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 <com/sun/star/uno/Exception.hpp>
+
+#include <svdata.hxx>
+#include <bitmaps.hlst>
+
+#include <optional>
+
+#include <algorithm>
+
+#ifndef Button6
+# define Button6 6
+#endif
+#ifndef Button7
+# define Button7 7
+#endif
+
+using namespace vcl_sal;
+
+static 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 )
+ {
+ 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
+{
+ const OUStringLiteral 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
+ };
+
+ const OUStringLiteral 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
+ };
+
+ const OUStringLiteral 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;
+ switch( aIcon.GetTransparentType())
+ {
+ case TransparentType::NONE:
+ {
+ sal_uInt8 nTrans = 0;
+ mask = AlphaMask( icon.GetSizePixel(), &nTrans );
+ }
+ break;
+ case TransparentType::Color:
+ mask = AlphaMask( icon.CreateMask( aIcon.GetTransparentColor() ) );
+ break;
+ case TransparentType::Bitmap:
+ mask = aIcon.GetAlpha();
+ break;
+ }
+ BitmapReadAccess* iconData = icon.AcquireReadAccess();
+ BitmapReadAccess* maskData = mask.AcquireReadAccess();
+ 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();
+ }
+ Bitmap::ReleaseAccess( iconData );
+ mask.ReleaseAccess( maskData );
+ }
+ netwm_icon.resize( pos );
+}
+
+static bool lcl_SelectAppIconPixmap( SalDisplay const *pDisplay, SalX11Screen nXScreen,
+ sal_uInt16 nIcon, sal_uInt16 iconSize,
+ Pixmap& icon_pixmap, Pixmap& icon_mask, NetWmIconData& netwm_icon)
+{
+ PreDefaultWinNoOpenGLZone aGuard;
+
+ CreateNetWmAppIcon( nIcon, netwm_icon );
+
+ OUString sIcon;
+
+ if( iconSize >= 48 )
+ sIcon = SV_ICON_SIZE48[nIcon];
+ else if( iconSize >= 32 )
+ sIcon = SV_ICON_SIZE32[nIcon];
+ else if( iconSize >= 16 )
+ sIcon = SV_ICON_SIZE16[nIcon];
+ else
+ return false;
+
+ BitmapEx aIcon = vcl::bitmap::loadFromName(sIcon, ImageLoadFlags::IgnoreScalingFactor);
+
+ if( aIcon.IsEmpty() )
+ return false;
+
+ X11SalBitmap *pBitmap = dynamic_cast < X11SalBitmap * >
+ (aIcon.ImplGetBitmapSalBitmap().get());
+ if (!pBitmap) // FIXME: opengl , TODO SKIA
+ return false;
+
+ icon_pixmap = XCreatePixmap( pDisplay->GetDisplay(),
+ pDisplay->GetRootWindow( nXScreen ),
+ iconSize, iconSize,
+ DefaultDepth( pDisplay->GetDisplay(),
+ nXScreen.getXScreen() )
+ );
+
+ SalTwoRect aRect(0, 0, iconSize, iconSize, 0, 0, iconSize, iconSize);
+
+ pBitmap->ImplDraw( icon_pixmap,
+ nXScreen,
+ DefaultDepth( pDisplay->GetDisplay(),
+ nXScreen.getXScreen() ),
+ aRect,
+ DefaultGC( pDisplay->GetDisplay(),
+ nXScreen.getXScreen() ) );
+
+ icon_mask = None;
+
+ if( TransparentType::Bitmap == aIcon.GetTransparentType() )
+ {
+ icon_mask = XCreatePixmap( pDisplay->GetDisplay(),
+ pDisplay->GetRootWindow( pDisplay->GetDefaultXScreen() ),
+ iconSize, iconSize, 1);
+
+ XGCValues aValues;
+ aValues.foreground = 0xffffffff;
+ aValues.background = 0;
+ aValues.function = GXcopy;
+ GC aMonoGC = XCreateGC( pDisplay->GetDisplay(), icon_mask,
+ GCFunction|GCForeground|GCBackground, &aValues );
+
+ Bitmap aMask = aIcon.GetMask();
+ aMask.Invert();
+
+ X11SalBitmap *pMask = static_cast < X11SalBitmap * >
+ (aMask.ImplGetSalBitmap().get());
+
+ pMask->ImplDraw(icon_mask, nXScreen, 1, aRect, aMonoGC);
+ XFreeGC( pDisplay->GetDisplay(), aMonoGC );
+ }
+
+ return true;
+}
+
+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.nX;
+ y = maGeometry.nY;
+ w = maGeometry.nWidth;
+ h = maGeometry.nHeight;
+ }
+
+ if( (nSalFrameStyle & SalFrameStyleFlags::FLOAT) &&
+ ! (nSalFrameStyle & SalFrameStyleFlags::OWNERDRAWDECORATION)
+ )
+ {
+ if( nShowState_ == SHOWSTATE_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().nWidth
+ || ! pFrame->GetUnmirroredGeometry().nHeight
+ )
+ )
+ {
+ 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.nX;
+ y = rGeom.nY;
+ 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< tools::Rectangle >& rScreens = GetDisplay()->GetXineramaScreens();
+ for(const auto & rScreen : rScreens)
+ if( rScreen.IsInside( Point( 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) )
+ {
+ bool bOk=false;
+ try
+ {
+ bOk = lcl_SelectAppIconPixmap( pDisplay_, m_nXScreen,
+ mnIconID != SV_ICON_ID_OFFICE ? mnIconID :
+ (mpParent ? mpParent->mnIconID : SV_ICON_ID_OFFICE), 32,
+ Hints.icon_pixmap, Hints.icon_mask, netwm_icon );
+ }
+ catch( css::uno::Exception& )
+ {
+ // can happen - no ucb during early startup
+ }
+ if( bOk )
+ {
+ Hints.flags |= IconPixmapHint;
+ if( Hints.icon_mask )
+ Hints.flags |= IconMaskHint;
+ }
+ }
+
+ // 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_ = SHOWSTATE_UNKNOWN;
+ bViewable_ = true;
+ bMapped_ = false;
+ nVisibility_ = VisibilityFullyObscured;
+ mhWindow = XCreateWindow( GetXDisplay(),
+ aFrameParent,
+ x, y,
+ w, h,
+ 0,
+ rVis.GetDepth(),
+ InputOutput,
+ rVis.GetVisual(),
+ nAttrMask,
+ &Attributes );
+ // 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.nX = x;
+ maGeometry.nY = y;
+ maGeometry.nWidth = w;
+ maGeometry.nHeight = 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( (nStyle_ & SalFrameStyleFlags::PARTIAL_FULLSCREEN) ||
+ (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( (nStyle_ & SalFrameStyleFlags::PARTIAL_FULLSCREEN)
+ && GetDisplay()->getWMAdaptor()->isLegacyPartialFullscreen() )
+ eType = WMWindowType::Dock;
+
+ GetDisplay()->getWMAdaptor()->
+ setFrameTypeAndDecoration( this,
+ eType,
+ nDecoFlags,
+ hPresentationWindow ? nullptr : mpParent );
+
+ if( (nStyle_ & (SalFrameStyleFlags::DEFAULT |
+ SalFrameStyleFlags::OWNERDRAWDECORATION|
+ SalFrameStyleFlags::FLOAT |
+ SalFrameStyleFlags::INTRO |
+ SalFrameStyleFlags::PARTIAL_FULLSCREEN) )
+ == 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 )
+{
+ GenericUnixSalData *pData = GetGenericUnixSalData();
+
+ mpParent = static_cast< X11SalFrame* >( pParent );
+
+ mbTransientForRoot = false;
+
+ pDisplay_ = vcl_sal::getSalDisplay(pData);
+ // insert frame in framelist
+ pDisplay_->registerFrame( this );
+
+ mhWindow = None;
+ mhShellWindow = None;
+ mhStackingWindow = None;
+ mhForeignParent = None;
+ m_bSetFocusOnMap = false;
+
+ pGraphics_ = nullptr;
+ pFreeGraphics_ = nullptr;
+
+ hCursor_ = None;
+ nCaptured_ = 0;
+
+ mbSendExtKeyModChange = false;
+ mnExtKeyMod = ModKeyFlags::NONE;
+
+ nShowState_ = SHOWSTATE_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 );
+ maAlwaysOnTopRaiseTimer.SetDebugName( "vcl::X11SalFrame maAlwaysOnTopRaiseTimer" );
+
+ meWindowType = WMWindowType::Normal;
+ mbMaximizedVert = false;
+ mbMaximizedHorz = false;
+ mbShaded = false;
+ mbFullScreen = 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;
+ }
+
+ 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.aWindow = 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, m_nXScreen );
+ if( pFreeGraphics_ )
+ pFreeGraphics_->SetDrawable( aDrawable, m_nXScreen );
+}
+
+void X11SalFrame::SetIcon( sal_uInt16 nIcon )
+{
+ if ( IsChildWindow() )
+ return;
+
+ // 0 == default icon -> #1
+ if ( nIcon == 0 )
+ nIcon = 1;
+
+ mnIconID = nIcon;
+
+ XIconSize *pIconSize = nullptr;
+ int nSizes = 0;
+ int iconSize = 32;
+ if ( XGetIconSizes( GetXDisplay(), GetDisplay()->GetRootWindow( m_nXScreen ), &pIconSize, &nSizes ) )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.window", "X11SalFrame::SetIcon(): found "
+ << nSizes
+ << " IconSizes:");
+#endif
+ int i;
+ for( i=0; i<nSizes; i++)
+ {
+ // select largest supported icon
+ if( pIconSize[i].max_width > iconSize )
+ {
+ iconSize = pIconSize[i].max_width;
+ }
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.window", "min: "
+ << pIconSize[i].min_width
+ << ", "
+ << pIconSize[i].min_height);
+ SAL_INFO("vcl.window", "max: "
+ << pIconSize[i].max_width
+ << ", "
+ << pIconSize[i].max_height);
+ SAL_INFO("vcl.window", "inc: "
+ << pIconSize[i].width_inc
+ << ", "
+ << pIconSize[i].height_inc);
+#endif
+ }
+
+ XFree( pIconSize );
+ }
+ else
+ {
+ const OUString& rWM( pDisplay_->getWMAdaptor()->getWindowManagerName() );
+ if( rWM == "KWin" ) // assume KDE is running
+ iconSize = 48;
+ static bool bGnomeIconSize = false;
+ static bool bGnomeChecked = false;
+ if( ! bGnomeChecked )
+ {
+ bGnomeChecked=true;
+ int nCount = 0;
+ Atom* pProps = XListProperties( GetXDisplay(),
+ GetDisplay()->GetRootWindow( m_nXScreen ),
+ &nCount );
+ for( int i = 0; i < nCount && !bGnomeIconSize; i++ )
+ {
+ char* pName = XGetAtomName( GetXDisplay(), pProps[i] );
+ if( pName )
+ {
+ if( !strcmp( pName, "GNOME_PANEL_DESKTOP_AREA" ) )
+ bGnomeIconSize = true;
+ XFree( pName );
+ }
+ }
+ if( pProps )
+ XFree( pProps );
+ }
+ if( bGnomeIconSize )
+ iconSize = 48;
+ }
+
+ XWMHints Hints;
+ Hints.flags = 0;
+ XWMHints *pHints = XGetWMHints( GetXDisplay(), GetShellWindow() );
+ if( pHints )
+ {
+ memcpy(&Hints, pHints, sizeof( XWMHints ));
+ XFree( pHints );
+ }
+ pHints = &Hints;
+
+ NetWmIconData netwm_icon;
+ bool bOk = lcl_SelectAppIconPixmap( GetDisplay(), m_nXScreen,
+ nIcon, iconSize,
+ pHints->icon_pixmap, pHints->icon_mask, netwm_icon );
+ if ( !bOk )
+ {
+ // load default icon (0)
+ bOk = lcl_SelectAppIconPixmap( GetDisplay(), m_nXScreen,
+ 0, iconSize,
+ pHints->icon_pixmap, pHints->icon_mask, netwm_icon );
+ }
+ if( bOk )
+ {
+ pHints->flags |= IconPixmapHint;
+ if( pHints->icon_mask )
+ pHints->flags |= IconMaskHint;
+
+ XSetWMHints( GetXDisplay(), GetShellWindow(), pHints );
+ 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( long nWidth, long nHeight )
+{
+ if( ! IsChildWindow() )
+ {
+ if( GetShellWindow() && (nStyle_ & (SalFrameStyleFlags::FLOAT|SalFrameStyleFlags::OWNERDRAWDECORATION) ) != SalFrameStyleFlags::FLOAT )
+ {
+ XSizeHints* pHints = XAllocSizeHints();
+ 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( long nWidth, long nHeight )
+{
+ if( ! IsChildWindow() )
+ {
+ if( GetShellWindow() && (nStyle_ & (SalFrameStyleFlags::FLOAT|SalFrameStyleFlags::OWNERDRAWDECORATION) ) != SalFrameStyleFlags::FLOAT )
+ {
+ XSizeHints* pHints = XAllocSizeHints();
+ 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->nStyle_ & SalFrameStyleFlags::PARTIAL_FULLSCREEN ) && 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_ == SHOWSTATE_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.nWidth > 0
+ && maGeometry.nHeight > 0
+ && ( nWidth_ != static_cast<int>(maGeometry.nWidth)
+ || nHeight_ != static_cast<int>(maGeometry.nHeight) ) )
+ {
+ nWidth_ = maGeometry.nWidth;
+ nHeight_ = maGeometry.nHeight;
+ }
+
+ 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 SHOWSTATE_UNKNOWN as this indicates first mapping
+ * and is only reset int HandleSizeEvent
+ */
+ if( nShowState_ != SHOWSTATE_UNKNOWN )
+ nShowState_ = SHOWSTATE_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_ = SHOWSTATE_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_ != SHOWSTATE_HIDDEN
+ && nShowState_ != SHOWSTATE_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( tools::Rectangle& rWorkArea )
+{
+ rWorkArea = pDisplay_->getWMAdaptor()->getWorkArea( 0 );
+}
+
+void X11SalFrame::GetClientSize( long &rWidth, long &rHeight )
+{
+ if( ! bViewable_ )
+ {
+ rWidth = rHeight = 0;
+ return;
+ }
+
+ rWidth = maGeometry.nWidth;
+ rHeight = maGeometry.nHeight;
+
+ if( !rWidth || !rHeight )
+ {
+ XWindowAttributes aAttrib;
+
+ XGetWindowAttributes( GetXDisplay(), GetWindow(), &aAttrib );
+
+ maGeometry.nWidth = rWidth = aAttrib.width;
+ maGeometry.nHeight = rHeight = aAttrib.height;
+ }
+}
+
+void X11SalFrame::Center( )
+{
+ int nX, nY, nScreenWidth, nScreenHeight;
+ int nRealScreenWidth, nRealScreenHeight;
+ int nScreenX = 0, nScreenY = 0;
+
+ const Size& aScreenSize = GetDisplay()->getDataForScreen( m_nXScreen ).m_aSize;
+ nScreenWidth = aScreenSize.Width();
+ nScreenHeight = aScreenSize.Height();
+ nRealScreenWidth = nScreenWidth;
+ nRealScreenHeight = nScreenHeight;
+
+ 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.nX + mpParent->maGeometry.nWidth/2;
+ root_y = mpParent->maGeometry.nY + mpParent->maGeometry.nHeight/2;
+ }
+ else
+ XQueryPointer( GetXDisplay(),
+ GetShellWindow(),
+ &aRoot, &aChild,
+ &root_x, &root_y,
+ &x, &y,
+ &mask );
+ const std::vector< tools::Rectangle >& rScreens = GetDisplay()->GetXineramaScreens();
+ for(const auto & rScreen : rScreens)
+ if( rScreen.IsInside( Point( root_x, root_y ) ) )
+ {
+ nScreenX = rScreen.Left();
+ nScreenY = rScreen.Top();
+ nRealScreenWidth = rScreen.GetWidth();
+ nRealScreenHeight = rScreen.GetHeight();
+ break;
+ }
+ }
+
+ if( mpParent )
+ {
+ X11SalFrame* pFrame = mpParent;
+ while( pFrame->mpParent )
+ pFrame = pFrame->mpParent;
+ if( pFrame->maGeometry.nWidth < 1 || pFrame->maGeometry.nHeight < 1 )
+ {
+ tools::Rectangle aRect;
+ pFrame->GetPosSize( aRect );
+ pFrame->maGeometry.nX = aRect.Left();
+ pFrame->maGeometry.nY = aRect.Top();
+ pFrame->maGeometry.nWidth = aRect.GetWidth();
+ pFrame->maGeometry.nHeight = aRect.GetHeight();
+ }
+
+ if( pFrame->nStyle_ & SalFrameStyleFlags::PLUG )
+ {
+ ::Window aRoot;
+ unsigned int bw, depth;
+ XGetGeometry( GetXDisplay(),
+ pFrame->GetShellWindow(),
+ &aRoot,
+ &nScreenX, &nScreenY,
+ reinterpret_cast<unsigned int*>(&nScreenWidth),
+ reinterpret_cast<unsigned int*>(&nScreenHeight),
+ &bw, &depth );
+ }
+ else
+ {
+ nScreenX = pFrame->maGeometry.nX;
+ nScreenY = pFrame->maGeometry.nY;
+ nScreenWidth = pFrame->maGeometry.nWidth;
+ nScreenHeight = pFrame->maGeometry.nHeight;
+ }
+ }
+
+ if( mpParent && mpParent->nShowState_ == SHOWSTATE_NORMAL )
+ {
+ if( maGeometry.nWidth >= mpParent->maGeometry.nWidth &&
+ maGeometry.nHeight >= mpParent->maGeometry.nHeight )
+ {
+ nX = nScreenX + 40;
+ nY = nScreenY + 40;
+ }
+ else
+ {
+ // center the window relative to the top level frame
+ nX = (nScreenWidth - static_cast<int>(maGeometry.nWidth) ) / 2 + nScreenX;
+ nY = (nScreenHeight - static_cast<int>(maGeometry.nHeight)) / 2 + nScreenY;
+ }
+ }
+ else
+ {
+ // center the window relative to screen
+ nX = (nRealScreenWidth - static_cast<int>(maGeometry.nWidth) ) / 2 + nScreenX;
+ nY = (nRealScreenHeight - static_cast<int>(maGeometry.nHeight)) / 2 + nScreenY;
+ }
+ nX = nX < 0 ? 0 : nX;
+ nY = nY < 0 ? 0 : nY;
+
+ bDefaultPosition_ = False;
+ if( mpParent )
+ {
+ nX -= mpParent->maGeometry.nX;
+ nY -= mpParent->maGeometry.nY;
+ }
+
+ Point aPoint(nX, nY);
+ SetPosSize( tools::Rectangle( aPoint, Size( maGeometry.nWidth, maGeometry.nHeight ) ) );
+}
+
+void X11SalFrame::updateScreenNumber()
+{
+ if( GetDisplay()->IsXinerama() && GetDisplay()->GetXineramaScreens().size() > 1 )
+ {
+ Point aPoint( maGeometry.nX, maGeometry.nY );
+ const std::vector<tools::Rectangle>& rScreenRects( GetDisplay()->GetXineramaScreens() );
+ size_t nScreens = rScreenRects.size();
+ for( size_t i = 0; i < nScreens; i++ )
+ {
+ if( rScreenRects[i].IsInside( aPoint ) )
+ {
+ maGeometry.nDisplayScreenNumber = static_cast<unsigned int>(i);
+ break;
+ }
+ }
+ }
+ else
+ maGeometry.nDisplayScreenNumber = m_nXScreen.getXScreen();
+}
+
+void X11SalFrame::SetPosSize( long nX, long nY, long nWidth, long nHeight, sal_uInt16 nFlags )
+{
+ if( nStyle_ & SalFrameStyleFlags::PLUG )
+ return;
+
+ // relative positioning in X11SalFrame::SetPosSize
+ tools::Rectangle aPosSize( Point( maGeometry.nX, maGeometry.nY ), Size( maGeometry.nWidth, maGeometry.nHeight ) );
+ aPosSize.Justify();
+
+ if( ! ( nFlags & SAL_FRAME_POSSIZE_X ) )
+ {
+ nX = aPosSize.Left();
+ if( mpParent )
+ nX -= mpParent->maGeometry.nX;
+ }
+ if( ! ( nFlags & SAL_FRAME_POSSIZE_Y ) )
+ {
+ nY = aPosSize.Top();
+ if( mpParent )
+ nY -= mpParent->maGeometry.nY;
+ }
+ if( ! ( nFlags & SAL_FRAME_POSSIZE_WIDTH ) )
+ nWidth = aPosSize.GetWidth();
+ if( ! ( nFlags & SAL_FRAME_POSSIZE_HEIGHT ) )
+ nHeight = aPosSize.GetHeight();
+
+ aPosSize = tools::Rectangle( Point( nX, nY ), Size( nWidth, nHeight ) );
+
+ if( ! ( nFlags & ( SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y ) ) )
+ {
+ if( bDefaultPosition_ )
+ {
+ maGeometry.nWidth = aPosSize.GetWidth();
+ maGeometry.nHeight = aPosSize.GetHeight();
+ 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 );
+ }
+}
+
+static constexpr auto FRAMESTATE_MASK_GEOMETRY =
+ WindowStateMask::X | WindowStateMask::Y |
+ WindowStateMask::Width | WindowStateMask::Height;
+static constexpr auto FRAMESTATE_MASK_MAXIMIZED_GEOMETRY =
+ WindowStateMask::MaximizedX | WindowStateMask::MaximizedY |
+ WindowStateMask::MaximizedWidth | WindowStateMask::MaximizedHeight;
+
+void X11SalFrame::SetWindowState( const SalFrameState *pState )
+{
+ if (pState == nullptr)
+ return;
+
+ // Request for position or size change
+ if (pState->mnMask & FRAMESTATE_MASK_GEOMETRY)
+ {
+ tools::Rectangle aPosSize;
+
+ /* #i44325#
+ * if maximized, set restore size and guess maximized size from last time
+ * in state change below maximize window
+ */
+ if( ! IsChildWindow() &&
+ (pState->mnMask & WindowStateMask::State) &&
+ (pState->mnState & WindowStateState::Maximized) &&
+ (pState->mnMask & FRAMESTATE_MASK_GEOMETRY) == FRAMESTATE_MASK_GEOMETRY &&
+ (pState->mnMask & FRAMESTATE_MASK_MAXIMIZED_GEOMETRY) == FRAMESTATE_MASK_MAXIMIZED_GEOMETRY
+ )
+ {
+ XSizeHints* pHints = XAllocSizeHints();
+ long nSupplied = 0;
+ XGetWMNormalHints( GetXDisplay(),
+ GetShellWindow(),
+ pHints,
+ &nSupplied );
+ pHints->flags |= PPosition | PWinGravity;
+ pHints->x = pState->mnX;
+ pHints->y = pState->mnY;
+ pHints->win_gravity = pDisplay_->getWMAdaptor()->getPositionWinGravity();
+ XSetWMNormalHints( GetXDisplay(),
+ GetShellWindow(),
+ pHints );
+ XFree( pHints );
+
+ XMoveResizeWindow( GetXDisplay(), GetShellWindow(),
+ pState->mnX, pState->mnY,
+ pState->mnWidth, pState->mnHeight );
+ // guess maximized geometry from last time
+ maGeometry.nX = pState->mnMaximizedX;
+ maGeometry.nY = pState->mnMaximizedY;
+ maGeometry.nWidth = pState->mnMaximizedWidth;
+ maGeometry.nHeight = pState->mnMaximizedHeight;
+ updateScreenNumber();
+ }
+ else
+ {
+ bool bDoAdjust = false;
+ // initialize with current geometry
+ if ((pState->mnMask & FRAMESTATE_MASK_GEOMETRY) != FRAMESTATE_MASK_GEOMETRY)
+ GetPosSize (aPosSize);
+
+ // change requested properties
+ if (pState->mnMask & WindowStateMask::X)
+ {
+ aPosSize.setX (pState->mnX);
+ }
+ if (pState->mnMask & WindowStateMask::Y)
+ {
+ aPosSize.setY (pState->mnY);
+ }
+ if (pState->mnMask & WindowStateMask::Width)
+ {
+ long nWidth = pState->mnWidth > 0 ? pState->mnWidth - 1 : 0;
+ aPosSize.setWidth (nWidth);
+ bDoAdjust = true;
+ }
+ if (pState->mnMask & WindowStateMask::Height)
+ {
+ int nHeight = pState->mnHeight > 0 ? pState->mnHeight - 1 : 0;
+ aPosSize.setHeight (nHeight);
+ bDoAdjust = true;
+ }
+
+ const Size& 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.nLeftDecoration == 0 &&
+ aGeom.nTopDecoration == 0 )
+ {
+ aGeom = mpParent->maGeometry;
+ if( aGeom.nLeftDecoration == 0 &&
+ aGeom.nTopDecoration == 0 )
+ {
+ aGeom.nLeftDecoration = 5;
+ aGeom.nTopDecoration = 20;
+ aGeom.nRightDecoration = 5;
+ aGeom.nBottomDecoration = 5;
+ }
+ }
+
+ // adjust position so that frame fits onto screen
+ if( aPosSize.Right()+static_cast<long>(aGeom.nRightDecoration) > aScreenSize.Width()-1 )
+ aPosSize.Move( aScreenSize.Width() - aPosSize.Right() - static_cast<long>(aGeom.nRightDecoration), 0 );
+ if( aPosSize.Bottom()+static_cast<long>(aGeom.nBottomDecoration) > aScreenSize.Height()-1 )
+ aPosSize.Move( 0, aScreenSize.Height() - aPosSize.Bottom() - static_cast<long>(aGeom.nBottomDecoration) );
+ if( aPosSize.Left() < static_cast<long>(aGeom.nLeftDecoration) )
+ aPosSize.Move( static_cast<long>(aGeom.nLeftDecoration) - aPosSize.Left(), 0 );
+ if( aPosSize.Top() < static_cast<long>(aGeom.nTopDecoration) )
+ aPosSize.Move( 0, static_cast<long>(aGeom.nTopDecoration) - aPosSize.Top() );
+ }
+
+ SetPosSize( 0, 0, aPosSize.GetWidth(), aPosSize.GetHeight(), SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT );
+ }
+ }
+
+ // request for status change
+ if (pState->mnMask & WindowStateMask::State)
+ {
+ if (pState->mnState & WindowStateState::Maximized)
+ {
+ nShowState_ = SHOWSTATE_NORMAL;
+ if( ! (pState->mnState & (WindowStateState::MaximizedHorz|WindowStateState::MaximizedVert) ) )
+ Maximize();
+ else
+ {
+ bool bHorz(pState->mnState & WindowStateState::MaximizedHorz);
+ bool bVert(pState->mnState & WindowStateState::MaximizedVert);
+ GetDisplay()->getWMAdaptor()->maximizeFrame( this, bHorz, bVert );
+ }
+ maRestorePosSize.SetLeft( pState->mnX );
+ maRestorePosSize.SetTop( pState->mnY );
+ maRestorePosSize.SetRight( maRestorePosSize.Left() + pState->mnWidth );
+ maRestorePosSize.SetRight( maRestorePosSize.Left() + pState->mnHeight );
+ }
+ else if( mbMaximizedHorz || mbMaximizedVert )
+ GetDisplay()->getWMAdaptor()->maximizeFrame( this, false, false );
+
+ if (pState->mnState & WindowStateState::Minimized)
+ {
+ if (nShowState_ == SHOWSTATE_UNKNOWN)
+ nShowState_ = SHOWSTATE_NORMAL;
+ Minimize();
+ }
+ if (pState->mnState & WindowStateState::Normal)
+ {
+ if (nShowState_ != SHOWSTATE_NORMAL)
+ Restore();
+ }
+ if (pState->mnState & WindowStateState::Rollup)
+ GetDisplay()->getWMAdaptor()->shade( this, true );
+ }
+}
+
+bool X11SalFrame::GetWindowState( SalFrameState* pState )
+{
+ if( SHOWSTATE_MINIMIZED == nShowState_ )
+ pState->mnState = WindowStateState::Minimized;
+ else
+ pState->mnState = WindowStateState::Normal;
+
+ tools::Rectangle aPosSize;
+ if( maRestorePosSize.IsEmpty() )
+ GetPosSize( aPosSize );
+ else
+ aPosSize = maRestorePosSize;
+
+ if( mbMaximizedHorz )
+ pState->mnState |= WindowStateState::MaximizedHorz;
+ if( mbMaximizedVert )
+ pState->mnState |= WindowStateState::MaximizedVert;
+ if( mbShaded )
+ pState->mnState |= WindowStateState::Rollup;
+
+ pState->mnX = aPosSize.Left();
+ pState->mnY = aPosSize.Top();
+ pState->mnWidth = aPosSize.GetWidth();
+ pState->mnHeight = aPosSize.GetHeight();
+
+ pState->mnMask = FRAMESTATE_MASK_GEOMETRY | WindowStateMask::State;
+
+ if (! maRestorePosSize.IsEmpty() )
+ {
+ GetPosSize( aPosSize );
+ pState->mnState |= WindowStateState::Maximized;
+ pState->mnMaximizedX = aPosSize.Left();
+ pState->mnMaximizedY = aPosSize.Top();
+ pState->mnMaximizedWidth = aPosSize.GetWidth();
+ pState->mnMaximizedHeight = aPosSize.GetHeight();
+ pState->mnMask |= FRAMESTATE_MASK_MAXIMIZED_GEOMETRY;
+ }
+
+ return true;
+}
+
+// native menu implementation - currently empty
+void X11SalFrame::DrawMenuBar()
+{
+}
+
+void X11SalFrame::SetMenu( SalMenu* )
+{
+}
+
+void X11SalFrame::GetPosSize( tools::Rectangle &rPosSize )
+{
+ if( maGeometry.nWidth < 1 || maGeometry.nHeight < 1 )
+ {
+ const Size& aScreenSize = pDisplay_->getDataForScreen( m_nXScreen ).m_aSize;
+ long w = aScreenSize.Width() - maGeometry.nLeftDecoration - maGeometry.nRightDecoration;
+ long h = aScreenSize.Height() - maGeometry.nTopDecoration - maGeometry.nBottomDecoration;
+
+ rPosSize = tools::Rectangle( Point( maGeometry.nX, maGeometry.nY ), Size( w, h ) );
+ }
+ else
+ rPosSize = tools::Rectangle( Point( maGeometry.nX, maGeometry.nY ),
+ Size( maGeometry.nWidth, maGeometry.nHeight ) );
+}
+
+void X11SalFrame::SetSize( const Size &rSize )
+{
+ if( !rSize.IsEmpty() )
+ {
+ if( ! ( nStyle_ & SalFrameStyleFlags::SIZEABLE )
+ && ! IsChildWindow()
+ && ( nStyle_ & (SalFrameStyleFlags::FLOAT|SalFrameStyleFlags::OWNERDRAWDECORATION) ) != SalFrameStyleFlags::FLOAT )
+ {
+ XSizeHints* pHints = XAllocSizeHints();
+ 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() );
+ }
+
+ maGeometry.nWidth = rSize.Width();
+ maGeometry.nHeight = rSize.Height();
+
+ // allow the external status window to reposition
+ if (mbInputFocus && mpInputContext != nullptr)
+ mpInputContext->SetICFocus ( this );
+ }
+}
+
+void X11SalFrame::SetPosSize( const tools::Rectangle &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.nWidth-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.nX || values.y != maGeometry.nY )
+ bMoved = true;
+ if( values.width != static_cast<int>(maGeometry.nWidth) || values.height != static_cast<int>(maGeometry.nHeight) )
+ 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_ == SHOWSTATE_UNKNOWN ||
+ nShowState_ == SHOWSTATE_HIDDEN ||
+ ! ( nStyle_ & SalFrameStyleFlags::SIZEABLE )
+ )
+ )
+ {
+ XSizeHints* pHints = XAllocSizeHints();
+ 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_ == SHOWSTATE_UNKNOWN || nShowState_ == SHOWSTATE_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 );
+ }
+
+ maGeometry.nX = values.x;
+ maGeometry.nY = values.y;
+ maGeometry.nWidth = values.width;
+ maGeometry.nHeight = values.height;
+ if( IsSysChildWindow() && mpParent )
+ {
+ // translate back to root coordinates
+ maGeometry.nX += mpParent->maGeometry.nX;
+ maGeometry.nY += mpParent->maGeometry.nY;
+ }
+
+ 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( SHOWSTATE_UNKNOWN == nShowState_ || SHOWSTATE_HIDDEN == nShowState_ )
+ {
+ SAL_WARN( "vcl", "X11SalFrame::Minimize on withdrawn window" );
+ return;
+ }
+
+ if( XIconifyWindow( GetXDisplay(),
+ GetShellWindow(),
+ pDisplay_->GetDefaultXScreen().getXScreen() ) )
+ nShowState_ = SHOWSTATE_MINIMIZED;
+}
+
+void X11SalFrame::Maximize()
+{
+ if( IsSysChildWindow() )
+ return;
+
+ if( SHOWSTATE_MINIMIZED == nShowState_ )
+ {
+ GetDisplay()->getWMAdaptor()->frameIsMapping( this );
+ XMapWindow( GetXDisplay(), GetShellWindow() );
+ nShowState_ = SHOWSTATE_NORMAL;
+ }
+
+ pDisplay_->getWMAdaptor()->maximizeFrame( this );
+}
+
+void X11SalFrame::Restore()
+{
+ if( IsSysChildWindow() )
+ return;
+
+ if( SHOWSTATE_UNKNOWN == nShowState_ || SHOWSTATE_HIDDEN == nShowState_ )
+ {
+ SAL_WARN( "vcl", "X11SalFrame::Restore on withdrawn window" );
+ return;
+ }
+
+ if( SHOWSTATE_MINIMIZED == nShowState_ )
+ {
+ GetDisplay()->getWMAdaptor()->frameIsMapping( this );
+ XMapWindow( GetXDisplay(), GetShellWindow() );
+ nShowState_ = SHOWSTATE_NORMAL;
+ }
+
+ pDisplay_->getWMAdaptor()->maximizeFrame( this, false, false );
+}
+
+void X11SalFrame::SetScreenNumber( unsigned int nNewScreen )
+{
+ if( nNewScreen == maGeometry.nDisplayScreenNumber )
+ return;
+
+ if( GetDisplay()->IsXinerama() && GetDisplay()->GetXineramaScreens().size() > 1 )
+ {
+ if( nNewScreen >= GetDisplay()->GetXineramaScreens().size() )
+ return;
+
+ tools::Rectangle aOldScreenRect( GetDisplay()->GetXineramaScreens()[maGeometry.nDisplayScreenNumber] );
+ tools::Rectangle aNewScreenRect( GetDisplay()->GetXineramaScreens()[nNewScreen] );
+ bool bVisible = bMapped_;
+ if( bVisible )
+ Show( false );
+ maGeometry.nX = aNewScreenRect.Left() + (maGeometry.nX - aOldScreenRect.Left());
+ maGeometry.nY = aNewScreenRect.Top() + (maGeometry.nY - aOldScreenRect.Top());
+ createNewWindow( None, m_nXScreen );
+ if( bVisible )
+ Show( true );
+ maGeometry.nDisplayScreenNumber = nNewScreen;
+ }
+ else if( nNewScreen < GetDisplay()->GetXScreenCount() )
+ {
+ bool bVisible = bMapped_;
+ if( bVisible )
+ Show( false );
+ createNewWindow( None, SalX11Screen( nNewScreen ) );
+ if( bVisible )
+ Show( true );
+ maGeometry.nDisplayScreenNumber = 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 = tools::Rectangle( Point( maGeometry.nX, maGeometry.nY ),
+ Size( maGeometry.nWidth, maGeometry.nHeight ) );
+ tools::Rectangle aRect;
+ if( nScreen < 0 || nScreen >= static_cast<int>(GetDisplay()->GetXineramaScreens().size()) )
+ aRect = tools::Rectangle( Point(0,0), GetDisplay()->GetScreenSize( m_nXScreen ) );
+ else
+ aRect = GetDisplay()->GetXineramaScreens()[nScreen];
+ nStyle_ |= SalFrameStyleFlags::PARTIAL_FULLSCREEN;
+ bool bVisible = bMapped_;
+ if( bVisible )
+ Show( false );
+ maGeometry.nX = aRect.Left();
+ maGeometry.nY = aRect.Top();
+ maGeometry.nWidth = aRect.GetWidth();
+ maGeometry.nHeight = aRect.GetHeight();
+ 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;
+ nStyle_ &= ~SalFrameStyleFlags::PARTIAL_FULLSCREEN;
+ bool bVisible = bMapped_;
+ tools::Rectangle aRect = maRestorePosSize;
+ maRestorePosSize = tools::Rectangle();
+ 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 || nScreen >= static_cast<int>(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 )
+{
+ maScreenSaverInhibitor.inhibit( bStart,
+ "presentation",
+ true, // isX11
+ 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(long nX, 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.nX + nX;
+ unsigned int nWindowTop = maGeometry.nY + 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()
+{
+ 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() );
+ }
+ 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
+}
+
+bool X11SalFrame::SetPluginParent( SystemParentData* pNewParent )
+{
+ if( pNewParent->nSize >= sizeof(SystemParentData) )
+ m_bXEmbed = pNewParent->aWindow != None && pNewParent->bXEmbedSupport;
+
+ createNewWindow(pNewParent->aWindow);
+
+ return true;
+}
+
+// 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 = {0, 0, 0, 0, 0};
+ 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.nWidth) &&
+ pEvent->xmotion.y >= 0 && pEvent->xmotion.y < static_cast<int>(maGeometry.nHeight) )
+ 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.nX &&
+ pEvent->xbutton.x_root < pFrame->maGeometry.nX + static_cast<int>(pFrame->maGeometry.nWidth) &&
+ pEvent->xbutton.y_root >= pFrame->maGeometry.nY &&
+ pEvent->xbutton.y_root < pFrame->maGeometry.nY + static_cast<int>(pFrame->maGeometry.nHeight) )
+ {
+ 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.nX && root_x < sal::static_int_cast< int >(pFrame->maGeometry.nX+pFrame->maGeometry.nWidth) &&
+ root_y >= pFrame->maGeometry.nY && root_y < sal::static_int_cast< int >(pFrame->maGeometry.nX+pFrame->maGeometry.nHeight) )
+ {
+ 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"
+ OUString aNumbers( rSeq.copy( 1 ) );
+ sal_uInt32 nValue = aNumbers.toUInt32( 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,
+ reinterpret_cast<char*>(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 )
+ {
+ GetSalData()->m_pInstance->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() )
+ {
+ 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() )
+ {
+ ::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 );
+
+ ::Window hDummy;
+ XTranslateCoordinates( GetXDisplay(),
+ GetWindow(),
+ pDisplay_->GetRootWindow( pDisplay_->GetDefaultXScreen() ),
+ 0, 0,
+ &pEvent->x, &pEvent->y,
+ &hDummy );
+
+ if( pEvent->window == GetStackingWindow() )
+ {
+ if( maGeometry.nX != pEvent->x || maGeometry.nY != pEvent->y )
+ {
+ maGeometry.nX = pEvent->x;
+ maGeometry.nY = pEvent->y;
+ CallCallback( SalEvent::Move, nullptr );
+ }
+ return true;
+ }
+
+ // check size hints in first time SalFrame::Show
+ if( SHOWSTATE_UNKNOWN == nShowState_ && bMapped_ )
+ nShowState_ = SHOWSTATE_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.nX || pEvent->y != maGeometry.nY );
+ bool bSized = ( pEvent->width != static_cast<int>(maGeometry.nWidth) || pEvent->height != static_cast<int>(maGeometry.nHeight) );
+
+ maGeometry.nX = pEvent->x;
+ maGeometry.nY = pEvent->y;
+ maGeometry.nWidth = pEvent->width;
+ maGeometry.nHeight = 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.nLeftDecoration = nLeft > 0 ? nLeft-1 : 0;
+ maGeometry.nTopDecoration = 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.nRightDecoration = wp - w - maGeometry.nLeftDecoration;
+ maGeometry.nBottomDecoration = hp - h - maGeometry.nTopDecoration;
+ /*
+ * note: this works because hWM_Parent is direct child of root,
+ * not necessarily parent of GetShellWindow()
+ */
+ maGeometry.nX = xp + nLeft;
+ maGeometry.nY = yp + nTop;
+ bResized = w != maGeometry.nWidth || h != maGeometry.nHeight;
+ maGeometry.nWidth = w;
+ maGeometry.nHeight = 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 )
+ {
+ Size aScreenSize = GetDisplay()->GetScreenSize( m_nXScreen );
+ int nScreenWidth = aScreenSize.Width();
+ int nScreenHeight = aScreenSize.Height();
+ int nFrameWidth = maGeometry.nWidth + maGeometry.nLeftDecoration + maGeometry.nRightDecoration;
+ int nFrameHeight = maGeometry.nHeight + maGeometry.nTopDecoration + maGeometry.nBottomDecoration;
+
+ if ((nFrameWidth > nScreenWidth) || (nFrameHeight > nScreenHeight))
+ {
+ Size aSize(maGeometry.nWidth, maGeometry.nHeight);
+
+ if (nFrameWidth > nScreenWidth)
+ aSize.setWidth( nScreenWidth - maGeometry.nRightDecoration - maGeometry.nLeftDecoration );
+ if (nFrameHeight > nScreenHeight)
+ aSize.setHeight( nScreenHeight - maGeometry.nBottomDecoration - maGeometry.nTopDecoration );
+
+ 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_ = SHOWSTATE_NORMAL;
+ else if( *reinterpret_cast<unsigned long*>(prop) == IconicState )
+ nShowState_ = SHOWSTATE_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_ == SHOWSTATE_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( long nX, long nY, long nWidth, 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 );
+
+}
+
+sal_uIntPtr X11SalFrame::GetNativeWindowHandle()
+{
+ return mhWindow;
+}
+
+/* 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 000000000..029204481
--- /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->aWindow);
+ 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->aWindow = 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( long nX, long nY, long nWidth, 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.aWindow = 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( long nX, long nY, long nWidth, 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( long nX, long nY, long nWidth, 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.aWindow )
+ 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.aWindow,
+ 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() == 3)
+ {
+ rEnterArgs[0] >>= bFreePixmap;
+ long pixmapHandle = None;
+ rEnterArgs[1] >>= pixmapHandle;
+ aPixmap = pixmapHandle;
+ }
+
+ XSetWindowBackgroundPixmap(pDisp, aObjectParent, aPixmap);
+ if (bFreePixmap)
+ XFreePixmap(pDisp, aPixmap);
+
+ bFreePixmap = false;
+ aPixmap = None;
+ if (rLeaveArgs.getLength() == 3)
+ {
+ rLeaveArgs[0] >>= bFreePixmap;
+ long 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/screensaverinhibitor.cxx b/vcl/unx/generic/window/screensaverinhibitor.cxx
new file mode 100644
index 000000000..4cb4e2737
--- /dev/null
+++ b/vcl/unx/generic/window/screensaverinhibitor.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/screensaverinhibitor.hxx>
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+#if !defined(__sun) && !defined(AIX)
+#include <X11/extensions/dpms.h>
+#endif
+
+#include <config_gio.h>
+
+#if ENABLE_GIO
+#include <gio/gio.h>
+
+#define FDO_DBUS_SERVICE "org.freedesktop.ScreenSaver"
+#define FDO_DBUS_PATH "/org/freedesktop/ScreenSaver"
+#define FDO_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 ScreenSaverInhibitor::inhibit( bool bInhibit, const OUString& sReason,
+ bool bIsX11, const std::optional<unsigned int>& xid, std::optional<Display*> pDisplay )
+{
+ const char* appname = SalGenericSystem::getFrameClassName();
+ const OString aReason = OUStringToOString( sReason, RTL_TEXTENCODING_UTF8 );
+
+ inhibitFDO( bInhibit, appname, aReason.getStr() );
+ inhibitFDOPM( bInhibit, appname, aReason.getStr() );
+
+ if ( bIsX11 )
+ {
+ if (pDisplay)
+ {
+ inhibitXScreenSaver( bInhibit, *pDisplay );
+ inhibitXAutoLock( bInhibit, *pDisplay );
+ inhibitDPMS( bInhibit, *pDisplay );
+ }
+
+ if (xid)
+ {
+ inhibitGSM( bInhibit, appname, aReason.getStr(), *xid );
+ inhibitMSM( bInhibit, appname, aReason.getStr(), *xid );
+ }
+ }
+}
+
+#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.screensaverinhibitor", "failed to connect to dbus session bus" );
+
+ if (error != nullptr) {
+ SAL_WARN( "vcl.screensaverinhibitor", "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.screensaverinhibitor", "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.screensaverinhibitor", service << ".Inhibit failed");
+ }
+ }
+ else
+ {
+ res = fUnInhibit( proxy, *rCookie, error );
+ rCookie.reset();
+
+ if (res != nullptr)
+ {
+ g_variant_unref(res);
+ }
+ else
+ {
+ SAL_INFO( "vcl.screensaverinhibitor", service << ".UnInhibit failed" );
+ }
+ }
+
+ if (error != nullptr)
+ {
+ SAL_INFO( "vcl.screensaverinhibitor", "Error: " << error->message );
+ g_error_free( error );
+ }
+
+ g_object_unref( G_OBJECT( proxy ) );
+}
+#endif // ENABLE_GIO
+
+void ScreenSaverInhibitor::inhibitFDO( bool bInhibit, const char* appname, const char* reason )
+{
+#if ENABLE_GIO
+ dbusInhibit( bInhibit,
+ FDO_DBUS_SERVICE, FDO_DBUS_PATH, FDO_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 );
+ },
+ mnFDOCookie );
+#else
+ (void) this;
+ (void) bInhibit;
+ (void) appname;
+ (void) reason;
+#endif // ENABLE_GIO
+}
+
+void ScreenSaverInhibitor::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 ScreenSaverInhibitor::inhibitGSM( bool bInhibit, const char* appname, const char* reason, const unsigned int xid )
+{
+#if ENABLE_GIO
+ dbusInhibit( bInhibit,
+ GSM_DBUS_SERVICE, GSM_DBUS_PATH, GSM_DBUS_INTERFACE,
+ [appname, reason, xid] ( GDBusProxy *proxy, GError*& error ) -> GVariant* {
+ return g_dbus_proxy_call_sync( proxy, "Inhibit",
+ g_variant_new("(susu)",
+ appname,
+ xid,
+ reason,
+ 8 //Inhibit the session being marked as idle
+ ),
+ 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) xid;
+#endif // ENABLE_GIO
+}
+
+void ScreenSaverInhibitor::inhibitMSM( bool bInhibit, const char* appname, const char* reason, const unsigned int xid )
+{
+#if ENABLE_GIO
+ dbusInhibit( bInhibit,
+ MSM_DBUS_SERVICE, MSM_DBUS_PATH, MSM_DBUS_INTERFACE,
+ [appname, reason, xid] ( GDBusProxy *proxy, GError*& error ) -> GVariant* {
+ return g_dbus_proxy_call_sync( proxy, "Inhibit",
+ g_variant_new("(susu)",
+ appname,
+ xid,
+ reason,
+ 8 //Inhibit the session being marked as idle
+ ),
+ 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) xid;
+#endif // ENABLE_GIO
+}
+
+/**
+ * Disable screensavers using the XSetScreenSaver/XGetScreenSaver API.
+ *
+ * Worth noting: xscreensaver explicitly ignores this and does its own
+ * timeout handling.
+ */
+void ScreenSaverInhibitor::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 ScreenSaverInhibitor::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 ScreenSaverInhibitor::inhibitDPMS( bool bInhibit, Display* pDisplay )
+{
+#if !defined(__sun) && !defined(AIX)
+ 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) && !defined(AIX)
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/glxtest.cxx b/vcl/unx/glxtest.cxx
new file mode 100644
index 000000000..b3c6a7521
--- /dev/null
+++ b/vcl/unx/glxtest.cxx
@@ -0,0 +1,294 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=8 et :
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Explanation: See bug 639842. Safely getting GL driver info on X11 is hard, because the only way to do
+// that is to create a GL context and call glGetString(), but with bad drivers,
+// just creating a GL context may crash.
+//
+// This file implements the idea to do that in a separate process.
+//
+// The only non-static function here is fire_glxtest_process(). It creates a pipe, publishes its 'read' end as the
+// mozilla::widget::glxtest_pipe global variable, forks, and runs that GLX probe in the child process,
+// which runs the glxtest() static function. This creates a X connection, a GLX context, calls glGetString, and writes that
+// to the 'write' end of the pipe.
+
+#include <cstdio>
+#include <cstdlib>
+#include <unistd.h>
+#include <dlfcn.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <string.h>
+#include <signal.h>
+
+#include <sys/wait.h>
+
+#include <opengl/x11/glxtest.hxx>
+
+#ifdef __SUNPRO_CC
+#include <stdio.h>
+#endif
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#include <sal/log.hxx>
+#include <vcl/glxtestprocess.hxx>
+
+// stuff from glx.h
+typedef struct __GLXcontextRec *GLXContext;
+typedef XID GLXPixmap;
+typedef XID GLXDrawable;
+/* GLX 1.3 and later */
+typedef struct __GLXFBConfigRec *GLXFBConfig;
+#define GLX_RGBA 4
+#define GLX_RED_SIZE 8
+#define GLX_GREEN_SIZE 9
+#define GLX_BLUE_SIZE 10
+
+// stuff from gl.h
+typedef uint8_t GLubyte;
+typedef uint32_t GLenum;
+#define GL_VENDOR 0x1F00
+#define GL_RENDERER 0x1F01
+#define GL_VERSION 0x1F02
+
+// the write end of the pipe, which we're going to write to
+static int write_end_of_the_pipe = -1;
+
+// C++ standard collides with C standard in that it doesn't allow casting void* to function pointer types.
+// So the work-around is to convert first to size_t.
+// http://www.trilithium.com/johan/2004/12/problem-with-dlsym/
+template<typename func_ptr_type>
+static func_ptr_type cast(void *ptr)
+{
+ return reinterpret_cast<func_ptr_type>(
+ reinterpret_cast<size_t>(ptr)
+ );
+}
+
+static void fatal_error(const char *str)
+{
+ int length = strlen(str);
+ if (write(write_end_of_the_pipe, str, length) != length
+ || write(write_end_of_the_pipe, "\n", 1) != 1)
+ {
+ /* Cannot write to pipe. Fall through to call _exit */
+ }
+ _exit(EXIT_FAILURE);
+}
+
+static int
+x_error_handler(Display *, XErrorEvent *ev)
+{
+ enum { bufsize = 1024 };
+ char buf[bufsize];
+ int length = snprintf(buf, bufsize,
+ "X error occurred in GLX probe, error_code=%d, request_code=%d, minor_code=%d\n",
+ ev->error_code,
+ ev->request_code,
+ ev->minor_code);
+ if (write(write_end_of_the_pipe, buf, length) != length)
+ {
+ /* Cannot write to pipe. Fall through to call _exit */
+ }
+ _exit(EXIT_FAILURE);
+ return 0;
+}
+
+static void glxtest()
+{
+ signal(SIGPIPE, SIG_IGN);
+ // we want to redirect to /dev/null stdout, stderr, and while we're at it,
+ // any PR logging file descriptors. To that effect, we redirect all positive
+ // file descriptors up to what open() returns here. In particular, 1 is stdout and 2 is stderr.
+ int fd = open("/dev/null", O_WRONLY);
+ if (fd == -1)
+ fatal_error("could not redirect stdout+stderr");
+ for (int i = 1; i < fd; i++)
+ dup2(fd, i);
+ close(fd);
+
+ ///// Open libGL and load needed symbols /////
+#ifdef __OpenBSD__
+ #define LIBGL_FILENAME "libGL.so"
+#else
+ #define LIBGL_FILENAME "libGL.so.1"
+#endif
+ void *libgl = dlopen(LIBGL_FILENAME, RTLD_LAZY);
+ if (!libgl)
+ fatal_error("Unable to load " LIBGL_FILENAME);
+
+ typedef void* (* PFNGLXGETPROCADDRESS) (const char *);
+ PFNGLXGETPROCADDRESS glXGetProcAddress = cast<PFNGLXGETPROCADDRESS>(dlsym(libgl, "glXGetProcAddress"));
+
+ if (!glXGetProcAddress)
+ fatal_error("Unable to find glXGetProcAddress in " LIBGL_FILENAME);
+
+ typedef GLXFBConfig* (* PFNGLXQUERYEXTENSION) (Display *, int *, int *);
+ PFNGLXQUERYEXTENSION glXQueryExtension = cast<PFNGLXQUERYEXTENSION>(glXGetProcAddress("glXQueryExtension"));
+
+ typedef GLXFBConfig* (* PFNGLXQUERYVERSION) (Display *, int *, int *);
+ PFNGLXQUERYVERSION glXQueryVersion = cast<PFNGLXQUERYVERSION>(dlsym(libgl, "glXQueryVersion"));
+
+ typedef XVisualInfo* (* PFNGLXCHOOSEVISUAL) (Display *, int, int *);
+ PFNGLXCHOOSEVISUAL glXChooseVisual = cast<PFNGLXCHOOSEVISUAL>(glXGetProcAddress("glXChooseVisual"));
+
+ typedef GLXContext (* PFNGLXCREATECONTEXT) (Display *, XVisualInfo *, GLXContext, Bool);
+ PFNGLXCREATECONTEXT glXCreateContext = cast<PFNGLXCREATECONTEXT>(glXGetProcAddress("glXCreateContext"));
+
+ typedef Bool (* PFNGLXMAKECURRENT) (Display*, GLXDrawable, GLXContext);
+ PFNGLXMAKECURRENT glXMakeCurrent = cast<PFNGLXMAKECURRENT>(glXGetProcAddress("glXMakeCurrent"));
+
+ typedef void (* PFNGLXDESTROYCONTEXT) (Display*, GLXContext);
+ PFNGLXDESTROYCONTEXT glXDestroyContext = cast<PFNGLXDESTROYCONTEXT>(glXGetProcAddress("glXDestroyContext"));
+
+ typedef GLubyte* (* PFNGLGETSTRING) (GLenum);
+ PFNGLGETSTRING glGetString = cast<PFNGLGETSTRING>(glXGetProcAddress("glGetString"));
+
+ if (!glXQueryExtension ||
+ !glXQueryVersion ||
+ !glXChooseVisual ||
+ !glXCreateContext ||
+ !glXMakeCurrent ||
+ !glXDestroyContext ||
+ !glGetString)
+ {
+ fatal_error("glXGetProcAddress couldn't find required functions");
+ }
+ ///// Open a connection to the X server /////
+ Display *dpy = XOpenDisplay(nullptr);
+ if (!dpy)
+ fatal_error("Unable to open a connection to the X server");
+
+ ///// Check that the GLX extension is present /////
+ if (!glXQueryExtension(dpy, nullptr, nullptr))
+ fatal_error("GLX extension missing");
+
+ XSetErrorHandler(x_error_handler);
+
+ ///// Get a visual /////
+ int attribs[] = {
+ GLX_RGBA,
+ GLX_RED_SIZE, 1,
+ GLX_GREEN_SIZE, 1,
+ GLX_BLUE_SIZE, 1,
+ None };
+ XVisualInfo *vInfo = glXChooseVisual(dpy, DefaultScreen(dpy), attribs);
+ if (!vInfo)
+ fatal_error("No visuals found");
+
+ // using a X11 Window instead of a GLXPixmap does not crash
+ // fglrx in indirect rendering. bug 680644
+ Window window;
+ XSetWindowAttributes swa;
+ swa.colormap = XCreateColormap(dpy, RootWindow(dpy, vInfo->screen),
+ vInfo->visual, AllocNone);
+
+ swa.border_pixel = 0;
+ window = XCreateWindow(dpy, RootWindow(dpy, vInfo->screen),
+ 0, 0, 16, 16,
+ 0, vInfo->depth, InputOutput, vInfo->visual,
+ CWBorderPixel | CWColormap, &swa);
+
+ ///// Get a GL context and make it current //////
+ GLXContext context = glXCreateContext(dpy, vInfo, nullptr, True);
+ glXMakeCurrent(dpy, window, context);
+
+ ///// Look for this symbol to determine texture_from_pixmap support /////
+ void* glXBindTexImageEXT = glXGetProcAddress("glXBindTexImageEXT");
+
+ ///// Get GL vendor/renderer/versions strings /////
+ enum { bufsize = 1024 };
+ char buf[bufsize];
+ const GLubyte *vendorString = glGetString(GL_VENDOR);
+ const GLubyte *rendererString = glGetString(GL_RENDERER);
+ const GLubyte *versionString = glGetString(GL_VERSION);
+
+ if (!vendorString || !rendererString || !versionString)
+ fatal_error("glGetString returned null");
+
+ int length = snprintf(buf, bufsize,
+ "VENDOR\n%s\nRENDERER\n%s\nVERSION\n%s\nTFP\n%s\n",
+ vendorString,
+ rendererString,
+ versionString,
+ glXBindTexImageEXT ? "TRUE" : "FALSE");
+ if (length >= bufsize)
+ fatal_error("GL strings length too large for buffer size");
+
+ ///// Clean up. Indeed, the parent process might fail to kill us (e.g. if it doesn't need to check GL info)
+ ///// so we might be staying alive for longer than expected, so it's important to consume as little memory as
+ ///// possible. Also we want to check that we're able to do that too without generating X errors.
+ glXMakeCurrent(dpy, None, nullptr); // must release the GL context before destroying it
+ glXDestroyContext(dpy, context);
+ XDestroyWindow(dpy, window);
+ XFreeColormap(dpy, swa.colormap);
+ XFree(vInfo);
+
+#ifdef NS_FREE_PERMANENT_DATA // conditionally defined in nscore.h, don't forget to #include it above
+ XCloseDisplay(dpy);
+#else
+ // This XSync call wanted to be instead:
+ // XCloseDisplay(dpy);
+ // but this can cause 1-minute stalls on certain setups using Nouveau, see bug 973192
+ XSync(dpy, False);
+#endif
+
+ dlclose(libgl);
+
+ ///// Finally write data to the pipe
+ if (write(write_end_of_the_pipe, buf, length) != length)
+ fatal_error("Could not write to pipe");
+}
+
+/** \returns true in the child glxtest process, false in the parent process */
+bool fire_glxtest_process()
+{
+ int pfd[2];
+ if (pipe(pfd) == -1) {
+ perror("pipe");
+ return false;
+ }
+ pid_t pid = fork();
+ if (pid < 0) {
+ perror("fork");
+ close(pfd[0]);
+ close(pfd[1]);
+ return false;
+ }
+ // The child exits early to avoid running the full shutdown sequence and avoid conflicting with threads
+ // we have already spawned (like the profiler).
+ if (pid == 0) {
+ close(pfd[0]);
+ write_end_of_the_pipe = pfd[1];
+ glxtest();
+ close(pfd[1]);
+ _exit(0);
+ }
+
+ close(pfd[1]);
+ int* glxtest_pipe = getGlxPipe();
+ *glxtest_pipe = pfd[0];
+ pid_t* glxtest_pid = getGlxPid();
+ *glxtest_pid = pid;
+ return true;
+}
+
+void reap_glxtest_process() {
+ pid_t * pid = getGlxPid();
+ if (*pid != 0) {
+ // Use WNOHANG, as it is probably better to have a (rather harmless) zombie child process
+ // hanging around for the duration of the calling process, than to potentially block the
+ // calling process here:
+ pid_t e = waitpid(*pid, nullptr, WNOHANG);
+ SAL_INFO_IF(
+ e <= 0, "vcl.opengl", "waiting for glxtest process " << *pid << " failed with " << e);
+ }
+}
diff --git a/vcl/unx/gtk3/a11y/TODO b/vcl/unx/gtk3/a11y/TODO
new file mode 100644
index 000000000..1048bd96e
--- /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/atkfactory.hxx b/vcl/unx/gtk3/a11y/atkfactory.hxx
new file mode 100644
index 000000000..ac72b5897
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkfactory.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 .
+ */
+
+#ifndef INCLUDED_VCL_UNX_GTK_A11Y_ATKFACTORY_HXX
+#define INCLUDED_VCL_UNX_GTK_A11Y_ATKFACTORY_HXX
+
+#include <atk/atk.h>
+
+extern "C" {
+
+GType wrapper_factory_get_type();
+
+} // extern "C"
+
+#endif
+
+/* 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 000000000..58798d443
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atklistener.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_UNX_GTK_A11Y_ATKLISTENER_HXX
+#define INCLUDED_VCL_UNX_GTK_A11Y_ATKLISTENER_HXX
+
+#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);
+
+ // 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);
+
+ // Process INVALIDATE_ALL_CHILDREN notification
+ void handleInvalidateChildren(
+ const css::uno::Reference< css::accessibility::XAccessibleContext >& rxParent);
+};
+
+#endif // INCLUDED_VCL_UNX_GTK_A11Y_ATKLISTENER_HXX
+
+/* 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 000000000..b69229084
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkregistry.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 .
+ */
+
+#ifndef INCLUDED_VCL_UNX_GTK_A11Y_ATKREGISTRY_HXX
+#define INCLUDED_VCL_UNX_GTK_A11Y_ATKREGISTRY_HXX
+
+#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);
+
+#endif // __ATK_REGISTRY_HXX_
+
+/* 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 000000000..f86c82f4f
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atktextattributes.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_UNX_GTK_A11Y_ATKTEXTATTRIBUTES_HXX
+#define INCLUDED_VCL_UNX_GTK_A11Y_ATKTEXTATTRIBUTES_HXX
+
+#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 );
+
+#endif
+
+/* 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 000000000..3df45c1ca
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkutil.hxx
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_VCL_UNX_GTK_A11Y_ATKUTIL_HXX
+#define INCLUDED_VCL_UNX_GTK_A11Y_ATKUTIL_HXX
+
+#include <atk/atk.h>
+
+void ooo_atk_util_ensure_event_listener();
+
+#endif
+
+/* 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 000000000..d9c651a2e
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkwrapper.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_UNX_GTK_A11Y_ATKWRAPPER_HXX
+#define INCLUDED_VCL_UNX_GTK_A11Y_ATKWRAPPER_HXX
+
+#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 XAccessibleText;
+ class XAccessibleTextMarkup;
+ class XAccessibleTextAttributes;
+ class XAccessibleValue;
+}
+
+struct AtkObjectWrapper
+{
+ AtkObject aParent;
+ AtkObject* mpOrig; //if we're a GtkDrawingArea acting as a custom LibreOffice widget, this is the toolkit default impl
+
+ 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::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);
+
+void atk_object_wrapper_dispose(AtkObjectWrapper* wrapper);
+
+AtkStateType mapAtkState( sal_Int16 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 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(const OUString& rString )
+{
+ OString aUtf8 = OUStringToOString( rString, RTL_TEXTENCODING_UTF8 );
+ return g_strdup( aUtf8.getStr() );
+}
+
+#define OUStringToConstGChar( string ) OUStringToOString( string, RTL_TEXTENCODING_UTF8 ).getStr()
+
+#endif // INCLUDED_VCL_UNX_GTK_A11Y_ATKWRAPPER_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/gtk3atkaction.cxx b/vcl/unx/gtk3/a11y/gtk3atkaction.cxx
new file mode 100644
index 000000000..fa9d09660
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/gtk3atkaction.cxx
@@ -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 .
+ */
+
+#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 "";
+}
+
+#define ACTION_NAME_PAIR( OOoName, AtkName ) \
+ std::pair< const OUString, const gchar * > ( OUString( OOoName ), AtkName )
+
+static const gchar *
+action_wrapper_get_name (AtkAction *action,
+ gint i)
+{
+ static std::map< OUString, const gchar * > aNameMap;
+
+ if( aNameMap.empty() )
+ {
+ aNameMap.insert( ACTION_NAME_PAIR( "click", "click" ) );
+ aNameMap.insert( ACTION_NAME_PAIR( "select", "click" ) );
+ aNameMap.insert( ACTION_NAME_PAIR( "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( OUStringToGChar( OUString( rKeyStroke.KeyChar ) ) );
+ }
+ }
+ }
+}
+
+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/gtk3atkbridge.cxx b/vcl/unx/gtk3/a11y/gtk3atkbridge.cxx
new file mode 100644
index 000000000..ddcc20f20
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/gtk3atkbridge.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 <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/gtk3atkcomponent.cxx b/vcl/unx/gtk3/a11y/gtk3atkcomponent.cxx
new file mode 100644
index 000000000..21301d4da
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/gtk3atkcomponent.cxx
@@ -0,0 +1,387 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <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
+translatePoint( 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();
+ 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( 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( 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
+ aPos = pComponent->getLocation();
+
+ *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/gtk3atkeditabletext.cxx b/vcl/unx/gtk3/a11y/gtk3atkeditabletext.cxx
new file mode 100644
index 000000000..f240c3232
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/gtk3atkeditabletext.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/gtk3atkfactory.cxx b/vcl/unx/gtk3/a11y/gtk3atkfactory.cxx
new file mode 100644
index 000000000..f92f9a667
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/gtk3atkfactory.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, nullptr );
+
+ 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 nullptr;
+}
+
+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/gtk3atkhypertext.cxx b/vcl/unx/gtk3/a11y/gtk3atkhypertext.cxx
new file mode 100644
index 000000000..83f6817fc
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/gtk3atkhypertext.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/gtk3atkimage.cxx b/vcl/unx/gtk3/a11y/gtk3atkimage.cxx
new file mode 100644
index 000000000..acd43f467
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/gtk3atkimage.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/XAccessibleImage.hpp>
+
+using namespace ::com::sun::star;
+
+// FIXME
+static const gchar *
+getAsConst( const OUString& 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 ) )
+ {
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ atk_component_get_position( ATK_COMPONENT( image ), x, y, coord_type );
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ }
+ 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/gtk3atklistener.cxx b/vcl/unx/gtk3/a11y/gtk3atklistener.cxx
new file mode 100644
index 000000000..8606b7bde
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/gtk3atklistener.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 .
+ */
+
+#ifdef AIX
+#define _LINUX_SOURCE_COMPAT
+#include <sys/timer.h>
+#undef _LINUX_SOURCE_COMPAT
+#endif
+
+#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_Int16 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 )
+ {
+ 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();
+
+ uno::Reference< accessibility::XAccessibleStateSet > xStateSet = pContext->getAccessibleStateSet();
+ if( xStateSet.is()
+ && !xStateSet->contains(accessibility::AccessibleStateType::DEFUNC)
+ && !xStateSet->contains(accessibility::AccessibleStateType::MANAGES_DESCENDANTS) )
+ {
+ 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_Int32 nChildren = pContext->getAccessibleChildCount();
+ m_aChildList.resize(nChildren);
+ for(sal_Int32 n = 0; n < nChildren; n++)
+ {
+ try
+ {
+ m_aChildList[n] = pContext->getAccessibleChild(n);
+ }
+ catch (lang::IndexOutOfBoundsException const&)
+ {
+ sal_Int32 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)
+{
+ AtkObject * pChild = rxAccessible.is() ? atk_object_wrapper_ref( rxAccessible ) : nullptr;
+
+ if( pChild )
+ {
+ 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 nIndex = -1;
+
+ // Locate the child in the children list
+ size_t n, nmax = m_aChildList.size();
+ for( 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 )
+ {
+ uno::Reference<accessibility::XAccessibleEventBroadcaster> xBroadcaster(
+ rxChild->getAccessibleContext(), uno::UNO_QUERY);
+
+ if (xBroadcaster.is())
+ {
+ uno::Reference<accessibility::XAccessibleEventListener> xListener(this);
+ xBroadcaster->removeAccessibleEventListener(xListener);
+ }
+
+ updateChildList(rxParent);
+
+ 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_Int16 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);
+
+ if( aEvent.NewValue >>= xChild )
+ handleChildAdded(xParent, xChild);
+ 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:
+
+#ifdef HAS_ATKRECTANGLE
+ 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");
+#endif
+
+ 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:
+ {
+ // TESTME: and remove this comment:
+ // cf. comphelper/source/misc/accessibletexthelper.cxx (implInitTextChangedEvent)
+ accessibility::TextSegment aDeletedText;
+ accessibility::TextSegment aInsertedText;
+
+ // TODO: when GNOME starts to send "update" kind of events, change
+ // we need to re-think this implementation as well
+ if( aEvent.OldValue >>= aDeletedText )
+ {
+ /* Remember the text segment here to be able to return removed text in get_text().
+ * This is clearly a hack to be used until appropriate API exists in atk to pass
+ * the string value directly or we find a compelling reason to start caching the
+ * UTF-8 converted strings in the atk wrapper object.
+ */
+
+ g_object_set_data( G_OBJECT(atk_obj), "ooo::text_changed::delete", &aDeletedText);
+
+ g_signal_emit_by_name( atk_obj, "text_changed::delete",
+ static_cast<gint>(aDeletedText.SegmentStart),
+ static_cast<gint>( aDeletedText.SegmentEnd - aDeletedText.SegmentStart ) );
+
+ g_object_steal_data( G_OBJECT(atk_obj), "ooo::text_changed::delete" );
+ }
+
+ if( aEvent.NewValue >>= aInsertedText )
+ g_signal_emit_by_name( atk_obj, "text_changed::insert",
+ static_cast<gint>(aInsertedText.SegmentStart),
+ static_cast<gint>( aInsertedText.SegmentEnd - aInsertedText.SegmentStart ) );
+ 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;
+
+ static const struct {
+ const char *row;
+ const char *col;
+ } aSignalNames[] =
+ {
+ { nullptr, nullptr }, // dummy
+ { "row_inserted", "column_inserted" }, // INSERT = 1
+ { "row_deleted", "column_deleted" } // DELETE = 2
+ };
+ switch( aChange.Type )
+ {
+ case accessibility::AccessibleTableModelChangeType::INSERT:
+ case accessibility::AccessibleTableModelChangeType::DELETE:
+ if( nRowsChanged > 0 )
+ g_signal_emit_by_name( G_OBJECT( atk_obj ),
+ aSignalNames[aChange.Type].row,
+ aChange.FirstRow, nRowsChanged );
+ if( nColumnsChanged > 0 )
+ g_signal_emit_by_name( G_OBJECT( atk_obj ),
+ aSignalNames[aChange.Type].col,
+ aChange.FirstColumn, nColumnsChanged );
+ 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() );
+ 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/gtk3atkregistry.cxx b/vcl/unx/gtk3/a11y/gtk3atkregistry.cxx
new file mode 100644
index 000000000..ff96378c4
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/gtk3atkregistry.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/gtk3atkselection.cxx b/vcl/unx/gtk3/a11y/gtk3atkselection.cxx
new file mode 100644
index 000000000..91759e8d0
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/gtk3atkselection.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 "atkwrapper.hxx"
+
+#include <com/sun/star/accessibility/XAccessibleSelection.hpp>
+
+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 selectAccessibleChild()" );
+ }
+
+ 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() )
+ return pSelection->getSelectedAccessibleChildCount();
+ }
+ 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 getSelectedAccessibleChildCount()" );
+ }
+
+ return FALSE;
+}
+
+static gboolean
+selection_remove_selection( AtkSelection *selection,
+ gint i )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleSelection> pSelection
+ = getSelection( selection );
+ if( pSelection.is() )
+ {
+ pSelection->deselectAccessibleChild( i );
+ return true;
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getSelectedAccessibleChildCount()" );
+ }
+
+ 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 getSelectedAccessibleChildCount()" );
+ }
+
+ 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/gtk3atktable.cxx b/vcl/unx/gtk3/a11y/gtk3atktable.cxx
new file mode 100644
index 000000000..221e55d2b
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/gtk3atktable.cxx
@@ -0,0 +1,580 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/XAccessibleTable.hpp>
+#include <comphelper/sequence.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( const OUString& 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>();
+}
+
+/*****************************************************************************/
+
+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() )
+ return pTable->getAccessibleIndex( row, column );
+ }
+ 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 *, gint )
+{
+ g_warning( "FIXME: no simple analogue for add_row_selection" );
+ return 0;
+}
+
+/*****************************************************************************/
+
+static gboolean
+table_wrapper_remove_row_selection( AtkTable *, gint )
+{
+ g_warning( "FIXME: no simple analogue for remove_row_selection" );
+ return 0;
+}
+
+/*****************************************************************************/
+
+static gboolean
+table_wrapper_add_column_selection( AtkTable *, gint )
+{
+ g_warning( "FIXME: no simple analogue for add_column_selection" );
+ return 0;
+}
+
+/*****************************************************************************/
+
+static gboolean
+table_wrapper_remove_column_selection( AtkTable *, gint )
+{
+ g_warning( "FIXME: no simple analogue for remove_column_selection" );
+ return 0;
+}
+
+/*****************************************************************************/
+
+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/gtk3atktext.cxx b/vcl/unx/gtk3/a11y/gtk3atktext.cxx
new file mode 100644
index 000000000..713b03325
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/gtk3atktext.cxx
@@ -0,0 +1,897 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <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:
+ 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 );
+
+ /* at-spi expects the delete event to be send before the deletion happened
+ * so we save the deleted string object in the UNO event notification and
+ * fool libatk-bridge.so here ..
+ */
+ void * pData = g_object_get_data( G_OBJECT(text), "ooo::text_changed::delete" );
+ if( pData != nullptr )
+ {
+ accessibility::TextSegment * pTextSegment =
+ static_cast <accessibility::TextSegment *> (pData);
+
+ if( pTextSegment->SegmentStart == start_offset &&
+ pTextSegment->SegmentEnd == end_offset )
+ {
+ OString aUtf8 = OUStringToOString( pTextSegment->SegmentText, RTL_TEXTENCODING_UTF8 );
+ return g_strdup( aUtf8.getStr() );
+ }
+ }
+
+ 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 = 0;
+
+ 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 ) );
+ if ( nTextMarkupCount > 0 )
+ {
+ 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.
+ }
+ } // eof iteration over text markups
+ }
+
+ 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 )
+ {
+ g_return_if_fail( ATK_IS_COMPONENT( text ) );
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ atk_component_get_position( ATK_COMPONENT( text ), &origin_x, &origin_y, coords);
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ }
+
+ *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 )
+ {
+ g_return_val_if_fail( ATK_IS_COMPONENT( text ), -1 );
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ atk_component_get_position( ATK_COMPONENT( text ), &origin_x, &origin_y, coords);
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ }
+
+ 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/gtk3atktextattributes.cxx b/vcl/unx/gtk3/a11y/gtk3atktextattributes.cxx
new file mode 100644
index 000000000..81115fb6a
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/gtk3atktextattributes.cxx
@@ -0,0 +1,1390 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <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 double toPoint(sal_Int16 n)
+{
+ // 100th mm -> pt
+ return static_cast<double>(n * 72) / 2540;
+}
+
+/*****************************************************************************/
+
+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 && n < sal_Int16(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" };
+static 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", toPoint(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( sProperty.getToken( 0, ':', nColonPos ),
+ RTL_TEXTENCODING_UTF8 );
+ OString sPropertyValue = OUStringToOString( sProperty.getToken( 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));
+
+ 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( aAttributeList[nIndex].Value, attribute->value) )
+ return false;
+
+ aAttributeList[nIndex].Name = OUString::createFromAscii( g_TextAttrMap[text_attr].name );
+ aAttributeList[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/gtk3atkutil.cxx b/vcl/unx/gtk3/a11y/gtk3atkutil.cxx
new file mode 100644
index 000000000..1d1337d6e
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/gtk3atkutil.cxx
@@ -0,0 +1,708 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 AIX
+#define _LINUX_SOURCE_COMPAT
+#include <sys/timer.h>
+#undef _LINUX_SOURCE_COMPAT
+#endif
+
+#include <com/sun/star/accessibility/XAccessibleContext.hpp>
+#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
+{
+ struct theNextFocusObject :
+ public rtl::Static< 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::get();
+ 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::get() = xAccessible;
+
+ focus_notify_handler = g_idle_add (atk_wrapper_focus_idle_handler, xAccessible.get());
+}
+
+/*****************************************************************************/
+
+class DocumentFocusListener :
+ public ::cppu::WeakImplHelper< accessibility::XAccessibleEventListener >
+{
+
+ std::set< uno::Reference< uno::XInterface > > m_aRefList;
+
+public:
+ /// @throws lang::IndexOutOfBoundsException
+ /// @throws uno::RuntimeException
+ void attachRecursive(
+ const uno::Reference< accessibility::XAccessible >& xAccessible
+ );
+
+ /// @throws lang::IndexOutOfBoundsException
+ /// @throws uno::RuntimeException
+ void attachRecursive(
+ const uno::Reference< accessibility::XAccessible >& xAccessible,
+ const uno::Reference< accessibility::XAccessibleContext >& xContext
+ );
+
+ /// @throws lang::IndexOutOfBoundsException
+ /// @throws uno::RuntimeException
+ void attachRecursive(
+ const uno::Reference< accessibility::XAccessible >& xAccessible,
+ const uno::Reference< accessibility::XAccessibleContext >& xContext,
+ const uno::Reference< accessibility::XAccessibleStateSet >& xStateSet
+ );
+
+ /// @throws lang::IndexOutOfBoundsException
+ /// @throws uno::RuntimeException
+ void detachRecursive(
+ const uno::Reference< accessibility::XAccessible >& xAccessible
+ );
+
+ /// @throws lang::IndexOutOfBoundsException
+ /// @throws uno::RuntimeException
+ void detachRecursive(
+ const uno::Reference< accessibility::XAccessibleContext >& xContext
+ );
+
+ /// @throws lang::IndexOutOfBoundsException
+ /// @throws uno::RuntimeException
+ void detachRecursive(
+ const uno::Reference< accessibility::XAccessibleContext >& xContext,
+ const uno::Reference< accessibility::XAccessibleStateSet >& xStateSet
+ );
+
+ /// @throws lang::IndexOutOfBoundsException
+ /// @throws uno::RuntimeException
+ static uno::Reference< accessibility::XAccessible > getAccessible(const lang::EventObject& aEvent );
+
+ // XEventListener
+ virtual void SAL_CALL disposing( const lang::EventObject& Source ) override;
+
+ // XAccessibleEventListener
+ virtual void SAL_CALL notifyEvent( const accessibility::AccessibleEventObject& aEvent ) override;
+};
+
+/*****************************************************************************/
+
+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_Int16 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:
+ SAL_INFO("vcl.a11y", "Invalidate all children called");
+ break;
+
+ default:
+ break;
+ }
+ }
+ catch( const lang::IndexOutOfBoundsException& )
+ {
+ g_warning("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
+)
+{
+ uno::Reference< accessibility::XAccessibleStateSet > xStateSet =
+ xContext->getAccessibleStateSet();
+
+ if( xStateSet.is() )
+ attachRecursive(xAccessible, xContext, xStateSet);
+}
+
+/*****************************************************************************/
+
+void DocumentFocusListener::attachRecursive(
+ const uno::Reference< accessibility::XAccessible >& xAccessible,
+ const uno::Reference< accessibility::XAccessibleContext >& xContext,
+ const uno::Reference< accessibility::XAccessibleStateSet >& xStateSet
+)
+{
+ if( xStateSet->contains(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 )
+ {
+ xBroadcaster->addAccessibleEventListener(static_cast< accessibility::XAccessibleEventListener *>(this));
+
+ if( ! xStateSet->contains(accessibility::AccessibleStateType::MANAGES_DESCENDANTS ) )
+ {
+ sal_Int32 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
+)
+{
+ uno::Reference< accessibility::XAccessibleStateSet > xStateSet =
+ xContext->getAccessibleStateSet();
+
+ if( xStateSet.is() )
+ detachRecursive(xContext, xStateSet);
+}
+
+/*****************************************************************************/
+
+void DocumentFocusListener::detachRecursive(
+ const uno::Reference< accessibility::XAccessibleContext >& xContext,
+ const uno::Reference< accessibility::XAccessibleStateSet >& xStateSet
+)
+{
+ uno::Reference< accessibility::XAccessibleEventBroadcaster > xBroadcaster(xContext, uno::UNO_QUERY);
+
+ if( xBroadcaster.is() && 0 < m_aRefList.erase(xBroadcaster) )
+ {
+ xBroadcaster->removeAccessibleEventListener(static_cast< accessibility::XAccessibleEventListener *>(this));
+
+ if( ! xStateSet->contains(accessibility::AccessibleStateType::MANAGES_DESCENDANTS ) )
+ {
+ sal_Int32 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 ) );
+ //TODO: ToolBox::ImplToolItems::size_type -> sal_Int32
+}
+
+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;
+
+}
+
+DocumentFocusListener & GtkSalData::GetDocumentFocusListener()
+{
+ if (!m_pDocumentFocusListener)
+ {
+ m_pDocumentFocusListener = new DocumentFocusListener;
+ m_xDocumentFocusListener.set(m_pDocumentFocusListener);
+ }
+ return *m_pDocumentFocusListener;
+}
+
+static void handle_get_focus(::VclWindowEvent const * pEvent)
+{
+ GtkSalData *const pSalData(GetGtkSalData());
+ assert(pSalData);
+
+ DocumentFocusListener & rDocumentFocusListener(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;
+
+ uno::Reference< accessibility::XAccessibleStateSet > xStateSet =
+ xContext->getAccessibleStateSet();
+
+ if( ! xStateSet.is() )
+ 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( xStateSet->contains(accessibility::AccessibleStateType::FOCUSED) &&
+ ( pWindow->GetType() != WindowType::TREELISTBOX ) )
+ {
+ atk_wrapper_focus_tracker_notify_when_idle( xAccessible );
+ }
+ else
+ {
+ if( g_aWindowList.list.insert(pWindow).second )
+ {
+ try
+ {
+ rDocumentFocusListener.attachRecursive(xAccessible, xContext, xStateSet);
+ }
+ 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);
+ }
+ else if (const VclAccessibleEvent* pAccEvent = dynamic_cast<const VclAccessibleEvent*>(&rEvent))
+ {
+ const uno::Reference< accessibility::XAccessible >& xAccessible = pAccEvent->GetAccessible();
+ if (xAccessible.is())
+ atk_wrapper_focus_tracker_notify_when_idle(xAccessible);
+ }
+ 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("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/gtk3atkvalue.cxx b/vcl/unx/gtk3/a11y/gtk3atkvalue.cxx
new file mode 100644
index 000000000..f5e45d3b2
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/gtk3atkvalue.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <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() )
+ {
+ // FIXME - this needs expanding
+ double aDouble = g_value_get_double( gval );
+ 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/gtk3atkwrapper.cxx b/vcl/unx/gtk3/a11y/gtk3atkwrapper.cxx
new file mode 100644
index 000000000..ab011d9d0
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/gtk3atkwrapper.cxx
@@ -0,0 +1,942 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/XAccessibleStateSet.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 <rtl/strbuf.hxx>
+#include <osl/diagnose.h>
+
+#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_Int16 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( 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 getRoleForName( const gchar * name )
+{
+ AtkRole ret = atk_role_for_name( name );
+ if( ATK_ROLE_INVALID == ret )
+ {
+ // this should only happen in old ATK versions
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ ret = atk_role_register( name );
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ }
+
+ return ret;
+}
+
+static AtkRole mapToAtkRole( sal_Int16 nRole )
+{
+ AtkRole role = ATK_ROLE_UNKNOWN;
+
+ static AtkRole roleMap[] = {
+ ATK_ROLE_UNKNOWN,
+ ATK_ROLE_ALERT,
+ ATK_ROLE_COLUMN_HEADER,
+ ATK_ROLE_CANVAS,
+ ATK_ROLE_CHECK_BOX,
+ ATK_ROLE_CHECK_MENU_ITEM,
+ ATK_ROLE_COLOR_CHOOSER,
+ ATK_ROLE_COMBO_BOX,
+ ATK_ROLE_DATE_EDITOR,
+ ATK_ROLE_DESKTOP_ICON,
+ ATK_ROLE_DESKTOP_FRAME, // ? pane
+ ATK_ROLE_DIRECTORY_PANE,
+ ATK_ROLE_DIALOG,
+ ATK_ROLE_UNKNOWN, // DOCUMENT - registered below
+ ATK_ROLE_UNKNOWN, // EMBEDDED_OBJECT - registered below
+ ATK_ROLE_UNKNOWN, // END_NOTE - registered below
+ ATK_ROLE_FILE_CHOOSER,
+ ATK_ROLE_FILLER,
+ ATK_ROLE_FONT_CHOOSER,
+ ATK_ROLE_FOOTER,
+ ATK_ROLE_UNKNOWN, // FOOTNOTE - registered below
+ ATK_ROLE_FRAME,
+ ATK_ROLE_GLASS_PANE,
+ ATK_ROLE_IMAGE, // GRAPHIC
+ ATK_ROLE_UNKNOWN, // GROUP_BOX - registered below
+ ATK_ROLE_HEADER,
+ ATK_ROLE_HEADING,
+ ATK_ROLE_TEXT, // HYPER_LINK - registered below
+ ATK_ROLE_ICON,
+ ATK_ROLE_INTERNAL_FRAME,
+ ATK_ROLE_LABEL,
+ ATK_ROLE_LAYERED_PANE,
+ ATK_ROLE_LIST,
+ ATK_ROLE_LIST_ITEM,
+ ATK_ROLE_MENU,
+ ATK_ROLE_MENU_BAR,
+ ATK_ROLE_MENU_ITEM,
+ ATK_ROLE_OPTION_PANE,
+ ATK_ROLE_PAGE_TAB,
+ ATK_ROLE_PAGE_TAB_LIST,
+ ATK_ROLE_PANEL,
+ ATK_ROLE_PARAGRAPH,
+ ATK_ROLE_PASSWORD_TEXT,
+ ATK_ROLE_POPUP_MENU,
+ ATK_ROLE_PUSH_BUTTON,
+ ATK_ROLE_PROGRESS_BAR,
+ ATK_ROLE_RADIO_BUTTON,
+ ATK_ROLE_RADIO_MENU_ITEM,
+ ATK_ROLE_ROW_HEADER,
+ ATK_ROLE_ROOT_PANE,
+ ATK_ROLE_SCROLL_BAR,
+ ATK_ROLE_SCROLL_PANE,
+ ATK_ROLE_PANEL, // SHAPE
+ ATK_ROLE_SEPARATOR,
+ ATK_ROLE_SLIDER,
+ ATK_ROLE_SPIN_BUTTON, // SPIN_BOX ?
+ ATK_ROLE_SPLIT_PANE,
+ ATK_ROLE_STATUSBAR,
+ ATK_ROLE_TABLE,
+ ATK_ROLE_TABLE_CELL,
+ ATK_ROLE_TEXT,
+ ATK_ROLE_PANEL, // TEXT_FRAME
+ ATK_ROLE_TOGGLE_BUTTON,
+ ATK_ROLE_TOOL_BAR,
+ ATK_ROLE_TOOL_TIP,
+ ATK_ROLE_TREE,
+ ATK_ROLE_VIEWPORT,
+ ATK_ROLE_WINDOW,
+ ATK_ROLE_PUSH_BUTTON, // BUTTON_DROPDOWN
+ ATK_ROLE_PUSH_BUTTON, // BUTTON_MENU
+ ATK_ROLE_UNKNOWN, // CAPTION - registered below
+ ATK_ROLE_UNKNOWN, // CHART - registered below
+ ATK_ROLE_UNKNOWN, // EDIT_BAR - registered below
+ ATK_ROLE_UNKNOWN, // FORM - registered below
+ ATK_ROLE_UNKNOWN, // IMAGE_MAP - registered below
+ ATK_ROLE_UNKNOWN, // NOTE - registered below
+ ATK_ROLE_UNKNOWN, // PAGE - registered below
+ ATK_ROLE_RULER,
+ ATK_ROLE_UNKNOWN, // SECTION - registered below
+ ATK_ROLE_UNKNOWN, // TREE_ITEM - registered below
+ ATK_ROLE_TREE_TABLE,
+ ATK_ROLE_SCROLL_PANE, // COMMENT - mapped to atk_role_scroll_pane
+ ATK_ROLE_UNKNOWN // COMMENT_END - mapped to atk_role_unknown
+#if defined(ATK_CHECK_VERSION)
+ //older ver that doesn't define ATK_CHECK_VERSION doesn't have the following
+ , ATK_ROLE_DOCUMENT_PRESENTATION
+ , ATK_ROLE_DOCUMENT_SPREADSHEET
+ , ATK_ROLE_DOCUMENT_TEXT
+#if ATK_CHECK_VERSION(2,15,2)
+ , ATK_ROLE_STATIC
+#else
+ , ATK_ROLE_LABEL
+#endif
+#else
+ //older version should fallback to DOCUMENT_FRAME role
+ , ATK_ROLE_DOCUMENT_FRAME
+ , ATK_ROLE_DOCUMENT_FRAME
+ , ATK_ROLE_DOCUMENT_FRAME
+ , ATK_ROLE_LABEL
+#endif
+ };
+
+ static bool initialized = false;
+
+ if( ! initialized )
+ {
+ // the accessible roles below were added to ATK in later versions,
+ // with role_for_name we will know if they exist in runtime.
+ roleMap[accessibility::AccessibleRole::EDIT_BAR] = getRoleForName("edit bar");
+ roleMap[accessibility::AccessibleRole::EMBEDDED_OBJECT] = getRoleForName("embedded");
+ roleMap[accessibility::AccessibleRole::CHART] = getRoleForName("chart");
+ roleMap[accessibility::AccessibleRole::CAPTION] = getRoleForName("caption");
+ roleMap[accessibility::AccessibleRole::DOCUMENT] = getRoleForName("document frame");
+ roleMap[accessibility::AccessibleRole::PAGE] = getRoleForName("page");
+ roleMap[accessibility::AccessibleRole::SECTION] = getRoleForName("section");
+ roleMap[accessibility::AccessibleRole::FORM] = getRoleForName("form");
+ roleMap[accessibility::AccessibleRole::GROUP_BOX] = getRoleForName("grouping");
+ roleMap[accessibility::AccessibleRole::COMMENT] = getRoleForName("comment");
+ roleMap[accessibility::AccessibleRole::IMAGE_MAP] = getRoleForName("image map");
+ roleMap[accessibility::AccessibleRole::TREE_ITEM] = getRoleForName("tree item");
+ roleMap[accessibility::AccessibleRole::HYPER_LINK] = getRoleForName("link");
+ roleMap[accessibility::AccessibleRole::END_NOTE] = getRoleForName("footnote");
+ roleMap[accessibility::AccessibleRole::FOOTNOTE] = getRoleForName("footnote");
+ roleMap[accessibility::AccessibleRole::NOTE] = getRoleForName("comment");
+
+ initialized = true;
+ }
+
+ static const sal_Int32 nMapSize = SAL_N_ELEMENTS(roleMap);
+ if( 0 <= nRole && nMapSize > nRole )
+ role = roleMap[nRole];
+
+ return role;
+}
+
+/*****************************************************************************/
+
+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());
+ }
+ }
+ 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());
+ }
+ 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);
+ gint n = 0;
+
+ if( obj->mpContext.is() )
+ {
+ try {
+ n = obj->mpContext->getAccessibleChildCount();
+ }
+ catch(const uno::Exception&) {
+ OSL_FAIL("Exception in getAccessibleChildCount()" );
+ }
+ }
+
+ return n;
+}
+
+/*****************************************************************************/
+
+static AtkObject *
+wrapper_ref_child( AtkObject *atk_obj,
+ gint i )
+{
+ AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER (atk_obj);
+ 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&) {
+ OSL_FAIL("Exception in getAccessibleChild");
+ }
+ }
+
+ return child;
+}
+
+/*****************************************************************************/
+
+static gint
+wrapper_get_index_in_parent( AtkObject *atk_obj )
+{
+ 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 {
+ i = obj->mpContext->getAccessibleIndexInParent();
+ }
+ 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 {
+ uno::Reference< accessibility::XAccessibleStateSet > xStateSet(
+ obj->mpContext->getAccessibleStateSet());
+
+ if( xStateSet.is() )
+ {
+ uno::Sequence< sal_Int16 > aStates = xStateSet->getStates();
+
+ for( const auto nState : aStates )
+ {
+ // ATK_STATE_LAST_DEFINED is used to check if the state
+ // is unmapped, do not report it to Atk
+ if ( 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 );
+ 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;
+}
+
+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->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 (ATK_TYPE_OBJECT,
+ "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;
+}
+
+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
+ },
+ {
+ "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( 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( rxAccessible.get() != nullptr, 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( rxAccessible.get() != nullptr, nullptr );
+
+ AtkObjectWrapper *pWrap = nullptr;
+
+ try {
+ uno::Reference< accessibility::XAccessibleContext > xContext(rxAccessible->getAccessibleContext());
+
+ g_return_val_if_fail( xContext.get() != nullptr, 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() );
+ 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
+ uno::Reference< accessibility::XAccessibleStateSet > xStateSet( xContext->getAccessibleStateSet() );
+ if( xStateSet.is() && ! xStateSet->contains( 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());
+ }
+ }
+
+ 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)
+{
+ AtkObject *atk_obj = ATK_OBJECT( wrapper );
+ atk_object_set_role( atk_obj, mapToAtkRole( role ) );
+}
+
+/*****************************************************************************/
+
+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->mpText.clear();
+ wrapper->mpTextMarkup.clear();
+ wrapper->mpTextAttributes.clear();
+ wrapper->mpValue.clear();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/cairo_gtk3_cairo.cxx b/vcl/unx/gtk3/cairo_gtk3_cairo.cxx
new file mode 100644
index 000000000..2e882ded6
--- /dev/null
+++ b/vcl/unx/gtk3/cairo_gtk3_cairo.cxx
@@ -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/.
+ */
+
+#include "cairo_gtk3_cairo.hxx"
+
+#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(const CairoSurfaceSharedPtr& pSurface)
+ : mpGraphics(nullptr)
+ , cr(nullptr)
+ , mpSurface(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(false))
+ {
+ 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::DEFAULT);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/cairo_gtk3_cairo.hxx b/vcl/unx/gtk3/cairo_gtk3_cairo.hxx
new file mode 100644
index 000000000..59ed1437f
--- /dev/null
+++ b/vcl/unx/gtk3/cairo_gtk3_cairo.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/.
+ */
+
+#ifndef INCLUDED_CANVAS_SOURCE_CAIRO_CAIRO_GTK3_CAIRO_HXX
+#define INCLUDED_CANVAS_SOURCE_CAIRO_CAIRO_GTK3_CAIRO_HXX
+
+#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(const 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;
+ };
+
+}
+
+#endif
+
+/* 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 000000000..1015fcf67
--- /dev/null
+++ b/vcl/unx/gtk3/fpicker/SalGtkFilePicker.cxx
@@ -0,0 +1,1991 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 AIX
+#define _LINUX_SOURCE_COMPAT
+#include <sys/timer.h>
+#undef _LINUX_SOURCE_COMPAT
+#endif
+
+#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 <vcl/svapp.hxx>
+
+#include <tools/urlobj.hxx>
+
+#include <algorithm>
+#include <set>
+#include <string.h>
+
+#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;
+
+void SalGtkFilePicker::dialog_mapped_cb(GtkWidget *, SalGtkFilePicker *pobjFP)
+{
+ pobjFP->InitialMapping();
+}
+
+void SalGtkFilePicker::InitialMapping()
+{
+ if (!mbPreviewState )
+ {
+ gtk_widget_hide( m_pPreview );
+ gtk_file_chooser_set_preview_widget_active( GTK_FILE_CHOOSER( m_pDialog ), false);
+ }
+ gtk_widget_set_size_request (m_pPreview, -1, -1);
+}
+
+SalGtkFilePicker::SalGtkFilePicker( const uno::Reference< uno::XComponentContext >& xContext ) :
+ SalGtkPicker( xContext ),
+ SalGtkFilePicker_Base( m_rbHelperMtx ),
+ m_pParentWidget ( nullptr ),
+ m_pVBox ( nullptr ),
+ mnHID_FolderChange( 0 ),
+ mnHID_SelectionChange( 0 ),
+ bVersionWidthUnset( false ),
+ mbPreviewState( 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_pAligns[i] = nullptr;
+ m_pLists[i] = nullptr;
+ m_pListLabels[i] = nullptr;
+ mbListVisibility[i] = false;
+ }
+
+ OUString aFilePickerTitle = getResString( FILE_PICKER_TITLE_OPEN );
+
+ m_pDialog = gtk_file_chooser_dialog_new(
+ OUStringToOString( aFilePickerTitle, RTL_TEXTENCODING_UTF8 ).getStr(),
+ nullptr,
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
+ nullptr );
+
+ gtk_dialog_set_default_response( GTK_DIALOG (m_pDialog), GTK_RESPONSE_ACCEPT );
+
+#if ENABLE_GIO
+ gtk_file_chooser_set_local_only( GTK_FILE_CHOOSER( m_pDialog ), false );
+#endif
+ gtk_file_chooser_set_select_multiple( GTK_FILE_CHOOSER( m_pDialog ), false );
+
+ m_pVBox = gtk_vbox_new( false, 0 );
+
+ // We don't want clickable items to have a huge hit-area
+ GtkWidget *pHBox = gtk_hbox_new( false, 0 );
+ GtkWidget *pThinVBox = gtk_vbox_new( false, 0 );
+
+ gtk_box_pack_end (GTK_BOX( m_pVBox ), pHBox, false, false, 0);
+ gtk_box_pack_start (GTK_BOX( pHBox ), pThinVBox, false, false, 0);
+ 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;
+ }
+
+ gtk_box_pack_end( GTK_BOX( pThinVBox ), m_pToggles[i], false, false, 0 );
+ }
+
+ for( i = 0; i < LIST_LAST; i++ )
+ {
+ m_pHBoxs[i] = gtk_hbox_new( false, 0 );
+
+ m_pAligns[i] = gtk_alignment_new(0, 0, 0, 1);
+
+ 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;
+ }
+
+ gtk_container_add( GTK_CONTAINER( m_pAligns[i]), m_pLists[i] );
+ gtk_box_pack_end( GTK_BOX( m_pHBoxs[i] ), m_pAligns[i], false, false, 0 );
+ gtk_box_pack_end( GTK_BOX( m_pHBoxs[i] ), m_pListLabels[i], false, false, 0 );
+ gtk_label_set_mnemonic_widget( GTK_LABEL(m_pListLabels[i]), m_pLists[i] );
+ gtk_box_set_spacing( GTK_BOX( m_pHBoxs[i] ), 12 );
+
+ gtk_box_pack_end( GTK_BOX( m_pVBox ), m_pHBoxs[i], false, false, 0 );
+ }
+
+ aLabel = getResString( FILE_PICKER_FILE_TYPE );
+ m_pFilterExpander = gtk_expander_new_with_mnemonic(
+ OUStringToOString( aLabel, RTL_TEXTENCODING_UTF8 ).getStr());
+
+ gtk_box_pack_end( GTK_BOX( m_pVBox ), m_pFilterExpander, false, true, 0 );
+
+ GtkWidget *scrolled_window = gtk_scrolled_window_new (nullptr, nullptr);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
+ GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window),
+ GTK_SHADOW_IN);
+ gtk_container_add (GTK_CONTAINER (m_pFilterExpander), scrolled_window);
+ 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);
+ gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(m_pFilterView), true);
+
+ 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);
+ }
+
+ gtk_container_add (GTK_CONTAINER (scrolled_window), m_pFilterView);
+ gtk_widget_show (m_pFilterView);
+
+ gtk_file_chooser_set_extra_widget( GTK_FILE_CHOOSER( m_pDialog ), m_pVBox );
+
+ m_pPreview = gtk_image_new();
+ gtk_file_chooser_set_preview_widget( GTK_FILE_CHOOSER( m_pDialog ), m_pPreview );
+
+ 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);
+
+ gtk_file_chooser_set_preview_widget_active( GTK_FILE_CHOOSER( m_pDialog ), true);
+}
+
+// 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 );
+}
+
+struct FilterEntry
+{
+protected:
+ OUString m_sTitle;
+ OUString m_sFilter;
+
+ css::uno::Sequence< css::beans::StringPair > m_aSubFilters;
+
+public:
+ FilterEntry( const OUString& _rTitle, const OUString& _rFilter )
+ :m_sTitle( _rTitle )
+ ,m_sFilter( _rFilter )
+ {
+ }
+
+ 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;
+}
+
+static bool
+isFilterString( const OUString &rFilterString, const char *pMatch )
+{
+ sal_Int32 nIndex = 0;
+ OUString aToken;
+ bool bIsFilter = true;
+
+ OUString aMatch(OUString::createFromAscii(pMatch));
+
+ do
+ {
+ aToken = rFilterString.getToken( 0, ';', nIndex );
+ if( !aToken.match( 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.copy( i + 1, nBracketLen - 1 ), "*." ) )
+ aRealName = aRealName.replaceAt( i, nBracketLen + 1, OUString() );
+ else if (bAllowNoStar)
+ {
+ if( isFilterString( rFilterName.copy( i + 1, nBracketLen - 1 ), ".") )
+ aRealName = aRealName.replaceAt( i, nBracketLen + 1, OUString() );
+ }
+ }
+ }
+
+ return aRealName;
+}
+
+static void
+dialog_remove_buttons(GtkWidget *pActionArea)
+{
+ GtkContainer *pContainer = GTK_CONTAINER( pActionArea );
+
+ g_return_if_fail( pContainer != nullptr );
+
+ GList *pChildren = gtk_container_get_children( pContainer );
+
+ for( GList *p = pChildren; p; p = p->next )
+ {
+ GtkWidget *pWidget = GTK_WIDGET( p->data );
+ if ( GTK_IS_BUTTON( pWidget ) )
+ gtk_widget_destroy( pWidget );
+ }
+
+ g_list_free( pChildren );
+}
+
+static void
+dialog_remove_buttons( GtkDialog *pDialog )
+{
+ g_return_if_fail( GTK_IS_DIALOG( pDialog ) );
+
+ GtkWidget *pHeaderBar = gtk_dialog_get_header_bar(pDialog);
+ if( pHeaderBar != nullptr )
+ dialog_remove_buttons( pHeaderBar );
+ else
+ dialog_remove_buttons(gtk_dialog_get_action_area(pDialog));
+}
+
+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( const OUString& rFilter, const OUString& rExt )
+{
+ const sal_Unicode cSep {';'};
+ sal_Int32 nIdx {0};
+
+ for (;;)
+ {
+ const sal_Int32 nBegin = rFilter.indexOf(rExt, nIdx);
+
+ if (nBegin<0) // 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.getLength();
+
+ // Check if the found occurrence is an exact match: right side
+ if (nIdx<rFilter.getLength() && 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 );
+
+ GSList* pPathList = gtk_file_chooser_get_uris( GTK_FILE_CHOOSER(m_pDialog) );
+
+ int nCount = g_slist_length( pPathList );
+ 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);
+
+ // Convert to OOo
+ for( GSList *pElem = pPathList; pElem; pElem = pElem->next)
+ {
+ gchar *pURI = static_cast<gchar*>(pElem->data);
+ aSelectedFiles[ 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 )
+ {
+ OUString sExtension;
+ nTokenIndex = 0;
+ do
+ sExtension = aSelectedFiles[nIndex].getToken( 0, '.', nTokenIndex );
+ while( nTokenIndex >= 0 );
+
+ if( sExtension.getLength() >= 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(), "*." + 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 = aSelectedFiles[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 );
+ }
+
+ g_slist_free( pPathList );
+
+ return aSelectedFiles;
+}
+
+// XExecutableDialog functions
+
+void SAL_CALL SalGtkFilePicker::setTitle( const OUString& rTitle )
+{
+ SolarMutexGuard g;
+
+ implsetTitle(rTitle);
+}
+
+sal_Int16 SAL_CALL SalGtkFilePicker::execute()
+{
+ SolarMutexGuard g;
+
+ 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);
+
+ mnHID_FolderChange =
+ g_signal_connect( GTK_FILE_CHOOSER( m_pDialog ), "current-folder-changed",
+ G_CALLBACK( folder_changed_cb ), static_cast<gpointer>(this) );
+
+ mnHID_SelectionChange =
+ g_signal_connect( GTK_FILE_CHOOSER( m_pDialog ), "selection-changed",
+ G_CALLBACK( selection_changed_cb ), static_cast<gpointer>(this) );
+
+ 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);
+ RunDialog* pRunDialog = new RunDialog(m_pDialog, xToolkit, xDesktop);
+ uno::Reference < awt::XTopWindowListener > xLifeCycle(pRunDialog);
+ 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 )
+ {
+ OString sFileName = unicodetouri( aPathSeq[0] );
+ gchar *gFileName = g_filename_from_uri ( sFileName.getStr(), nullptr, nullptr );
+ if( g_file_test( gFileName, G_FILE_TEST_IS_REGULAR ) )
+ {
+ INetURLObject aFileObj( OStringToOUString(sFileName, RTL_TEXTENCODING_UTF8) );
+
+ 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$");
+
+ 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()
+ );
+
+ 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$";
+
+ 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));
+ RunDialog* pAnotherDialog = new RunDialog(dlg, xToolkit, xDesktop);
+ uno::Reference < awt::XTopWindowListener > xAnotherLifeCycle(pAnotherDialog);
+ btn = pAnotherDialog->run();
+
+ gtk_widget_destroy( dlg );
+ }
+ g_free (gFileName);
+
+ 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_TOGGLE_BUTTON; //prevent warning by initializing
+ GtkWidget *pWidget = nullptr;
+
+#define MAP_TOGGLE( elem ) \
+ case ExtendedFilePickerElementIds::CHECKBOX_##elem: \
+ pWidget = m_pToggles[elem]; tType = GTK_TYPE_TOGGLE_BUTTON; \
+ break
+#define MAP_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( 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 );
+ 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;
+ gtk_widget_size_request(GTK_WIDGET(pWidget), &requisition);
+ gtk_widget_set_size_request(GTK_WIDGET(pWidget), requisition.width, -1);
+}
+
+static void ComboBoxAppendText(GtkComboBox *pCombo, const OUString &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);
+ for (sal_Int32 i=0; i < nSize; ++i)
+ {
+ gchar *item;
+ gtk_tree_model_get(gtk_combo_box_get_model(pWidget),
+ &iter, 0, &item, -1);
+ aItemList[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_TOGGLE_BUTTON )
+ {
+ bool bChecked = false;
+ rValue >>= bChecked;
+ gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( pWidget ), bChecked );
+ }
+ 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_TOGGLE_BUTTON )
+ aRetval <<= bool( gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( pWidget ) ) );
+ 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 )
+{
+ 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( "vcl.gtk", "Set label on unknown control " << nControlId);
+ return;
+ }
+
+ OString aTxt = OUStringToOString( rLabel.replace('~', '_'), RTL_TEXTENCODING_UTF8 );
+ if (nControlId == ExtendedFilePickerElementIds::PUSHBUTTON_PLAY)
+ {
+#ifdef GTK_STOCK_MEDIA_PLAY
+ if (msPlayLabel.isEmpty())
+ msPlayLabel = rLabel;
+ if (msPlayLabel == rLabel)
+ gtk_button_set_label(GTK_BUTTON(pWidget), GTK_STOCK_MEDIA_PLAY);
+ else
+ gtk_button_set_label(GTK_BUTTON(pWidget), GTK_STOCK_MEDIA_STOP);
+#else
+ gtk_button_set_label(GTK_BUTTON(pWidget), aTxt.getStr());
+#endif
+ }
+ else if( tType == GTK_TYPE_TOGGLE_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_TOGGLE_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 )
+{
+ 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 );
+}
+
+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;
+}
+
+// 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;
+
+ css::uno::Reference<css::awt::XWindow> xParentWindow;
+ if (aArguments.getLength() > 1)
+ {
+ aArguments[1] >>= xParentWindow;
+ }
+
+ if (xParentWindow.is())
+ {
+ if (SalGtkXWindow* pGtkXWindow = dynamic_cast<SalGtkXWindow*>(xParentWindow.get()))
+ m_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()));
+ aAny = xSysDepWin->getWindowHandle(aProcessIdent, css::lang::SystemDependent::SYSTEM_XWINDOW);
+ css::awt::SystemDependentXWindow tmp;
+ aAny >>= tmp;
+ m_pParentWidget = GetGtkSalData()->GetGtkDisplay()->findGtkWidgetForNativeHandle(tmp.WindowHandle);
+ }
+ }
+ }
+
+ GtkFileChooserAction eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ const gchar *first_button_text = GTK_STOCK_OPEN;
+
+ 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 = GTK_STOCK_OPEN;
+ break;
+ case FILESAVE_SIMPLE:
+ eAction = GTK_FILE_CHOOSER_ACTION_SAVE;
+ first_button_text = GTK_STOCK_SAVE;
+ break;
+ case FILESAVE_AUTOEXTENSION_PASSWORD:
+ eAction = GTK_FILE_CHOOSER_ACTION_SAVE;
+ first_button_text = GTK_STOCK_SAVE;
+ mbToggleVisibility[PASSWORD] = true;
+ mbToggleVisibility[GPGENCRYPTION] = true;
+ // TODO
+ break;
+ case FILESAVE_AUTOEXTENSION_PASSWORD_FILTEROPTIONS:
+ eAction = GTK_FILE_CHOOSER_ACTION_SAVE;
+ first_button_text = GTK_STOCK_SAVE;
+ 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 = GTK_STOCK_SAVE;
+ mbToggleVisibility[SELECTION] = true;
+ // TODO
+ break;
+ case FILESAVE_AUTOEXTENSION_TEMPLATE:
+ eAction = GTK_FILE_CHOOSER_ACTION_SAVE;
+ first_button_text = GTK_STOCK_SAVE;
+ mbListVisibility[TEMPLATE] = true;
+ // TODO
+ break;
+ case FILEOPEN_LINK_PREVIEW_IMAGE_TEMPLATE:
+ eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ first_button_text = GTK_STOCK_OPEN;
+ 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 = GTK_STOCK_OPEN;
+ mbToggleVisibility[LINK] = true;
+ mbToggleVisibility[PREVIEW] = true;
+ mbListVisibility[IMAGE_ANCHOR] = true;
+ // TODO
+ break;
+ case FILEOPEN_PLAY:
+ eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ first_button_text = GTK_STOCK_OPEN;
+ mbButtonVisibility[PLAY] = true;
+ // TODO
+ break;
+ case FILEOPEN_LINK_PLAY:
+ eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ first_button_text = GTK_STOCK_OPEN;
+ mbToggleVisibility[LINK] = true;
+ mbButtonVisibility[PLAY] = true;
+ // TODO
+ break;
+ case FILEOPEN_READONLY_VERSION:
+ eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ first_button_text = GTK_STOCK_OPEN;
+ mbToggleVisibility[READONLY] = true;
+ mbListVisibility[VERSION] = true;
+ break;
+ case FILEOPEN_LINK_PREVIEW:
+ eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ first_button_text = GTK_STOCK_OPEN;
+ mbToggleVisibility[LINK] = true;
+ mbToggleVisibility[PREVIEW] = true;
+ // TODO
+ break;
+ case FILESAVE_AUTOEXTENSION:
+ eAction = GTK_FILE_CHOOSER_ACTION_SAVE;
+ first_button_text = GTK_STOCK_SAVE;
+ // TODO
+ break;
+ case FILEOPEN_PREVIEW:
+ eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ first_button_text = GTK_STOCK_OPEN;
+ 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);
+ dialog_remove_buttons( GTK_DIALOG( m_pDialog ) );
+ gtk_dialog_add_button( GTK_DIALOG( m_pDialog ), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL );
+ for( int nTVIndex = 0; nTVIndex < BUTTON_LAST; nTVIndex++ )
+ {
+ if( mbButtonVisibility[nTVIndex] )
+ {
+#ifdef GTK_STOCK_MEDIA_PLAY
+ m_pButtons[ nTVIndex ] = gtk_dialog_add_button( GTK_DIALOG( m_pDialog ), GTK_STOCK_MEDIA_PLAY, 1 );
+#else
+ OString aPlay = OUStringToOString( getResString( PUSHBUTTON_PLAY ), RTL_TEXTENCODING_UTF8 );
+ m_pButtons[ nTVIndex ] = gtk_dialog_add_button( GTK_DIALOG( m_pDialog ), aPlay.getStr(), 1 );
+#endif
+ }
+ }
+ gtk_dialog_add_button( GTK_DIALOG( m_pDialog ), first_button_text, GTK_RESPONSE_ACCEPT );
+
+ 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_pAligns[ nTVIndex ] );
+ gtk_widget_show( m_pHBoxs[ nTVIndex ] );
+ }
+ }
+}
+
+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
+ GSList *filters = gtk_file_chooser_list_filters ( GTK_FILE_CHOOSER( m_pDialog ) );
+ bool bFound = false;
+
+ for( GSList *iter = filters; !bFound && iter; iter = iter->next )
+ {
+ GtkFileFilter* pFilter = static_cast<GtkFileFilter *>( iter->data );
+ 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 );
+ bFound = true;
+ }
+ }
+
+ g_slist_free( filters );
+}
+
+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;
+}
+}
+
+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);
+ gtk_file_filter_add_custom (filter, GTK_FILE_FILTER_URI,
+ case_insensitive_filter,
+ g_strdup( OUStringToOString(aToken, RTL_TEXTENCODING_UTF8).getStr() ),
+ g_free );
+
+ SAL_INFO( "vcl.gtk", "fustering with " << aToken );
+ }
+#if OSL_DEBUG_LEVEL > 0
+ else
+ {
+ g_warning( "Duff filter token '%s'\n",
+ OUStringToOString(
+ rType.getToken( 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.makeStringAndClear(), RTL_TEXTENCODING_UTF8).getStr(),
+ 2, aFilterName.getStr(),
+ 3, OUStringToOString(rType, RTL_TEXTENCODING_UTF8).getStr(),
+ -1);
+ }
+ return filter;
+}
+
+void SalGtkFilePicker::implAddFilterGroup( const OUString& /*_rFilter*/, 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( filter.getTitle(), 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()
+{
+ 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_pAligns[i] ); //m_pAligns[i] owns m_pLists[i]
+ gtk_widget_destroy( m_pHBoxs[i] );
+ }
+
+ m_pFilterVector.reset();
+
+ gtk_widget_destroy( m_pVBox );
+}
+
+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 000000000..39bb6815b
--- /dev/null
+++ b/vcl/unx/gtk3/fpicker/SalGtkFilePicker.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 .
+ */
+
+#ifndef INCLUDED_VCL_UNX_GTK_FPICKER_SALGTKFILEPICKER_HXX
+#define INCLUDED_VCL_UNX_GTK_FPICKER_SALGTKFILEPICKER_HXX
+
+#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 );
+
+ private:
+ css::uno::Reference< css::ui::dialogs::XFilePickerListener >
+ m_xListener;
+ OUString msPlayLabel;
+ std::unique_ptr<std::vector<FilterEntry>> m_pFilterVector;
+ GtkWidget *m_pParentWidget;
+ 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 {
+ PLAY,
+ BUTTON_LAST };
+
+ GtkWidget *m_pButtons[ BUTTON_LAST ];
+
+ enum {
+ VERSION,
+ TEMPLATE,
+ IMAGE_TEMPLATE,
+ IMAGE_ANCHOR,
+ LIST_LAST
+ };
+
+ GtkWidget *m_pHBoxs[ LIST_LAST ];
+ GtkWidget *m_pAligns[ 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;
+ 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 OUString& rFilter,
+ 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;
+
+};
+#endif // INCLUDED_VCL_UNX_GTK_FPICKER_SALGTKFILEPICKER_HXX
+
+/* 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 000000000..bb940cf5f
--- /dev/null
+++ b/vcl/unx/gtk3/fpicker/SalGtkFolderPicker.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 .
+ */
+
+#ifdef AIX
+#define _LINUX_SOURCE_COMPAT
+#include <sys/timer.h>
+#undef _LINUX_SOURCE_COMPAT
+#endif
+
+#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, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, nullptr );
+
+ gtk_dialog_set_default_response( GTK_DIALOG (m_pDialog), GTK_RESPONSE_ACCEPT );
+#if ENABLE_GIO
+ gtk_file_chooser_set_local_only( GTK_FILE_CHOOSER( m_pDialog ), false );
+#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 );
+
+ gtk_file_chooser_set_current_folder_uri( GTK_FILE_CHOOSER( m_pDialog ),
+ aTxt.getStr() );
+}
+
+OUString SAL_CALL SalGtkFolderPicker::getDisplayDirectory()
+{
+ SolarMutexGuard g;
+
+ assert( m_pDialog != nullptr );
+
+ gchar* pCurrentFolder =
+ gtk_file_chooser_get_current_folder_uri( GTK_FILE_CHOOSER( m_pDialog ) );
+ OUString aCurrentFolderName = uritounicode(pCurrentFolder);
+ g_free( pCurrentFolder );
+
+ return aCurrentFolderName;
+}
+
+OUString SAL_CALL SalGtkFolderPicker::getDirectory()
+{
+ SolarMutexGuard g;
+
+ assert( m_pDialog != nullptr );
+
+ gchar* pSelectedFolder =
+ gtk_file_chooser_get_uri( GTK_FILE_CHOOSER( m_pDialog ) );
+ 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 = RunDialog::GetTransientFor();
+ if (pParent)
+ gtk_window_set_transient_for(GTK_WINDOW(m_pDialog), pParent);
+ RunDialog* pRunDialog = new RunDialog(m_pDialog, xToolkit, xDesktop);
+ uno::Reference < awt::XTopWindowListener > xLifeCycle(pRunDialog);
+ 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;
+}
+
+// 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 000000000..229bbe8b8
--- /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 .
+ */
+
+#ifndef INCLUDED_VCL_UNX_GTK_FPICKER_SALGTKFOLDERPICKER_HXX
+#define INCLUDED_VCL_UNX_GTK_FPICKER_SALGTKFOLDERPICKER_HXX
+
+#include <list>
+#include <memory>
+#include <rtl/ustring.hxx>
+#include <cppuhelper/implbase.hxx>
+
+#include "SalGtkPicker.hxx"
+
+class SalGtkFolderPicker :
+ public SalGtkPicker,
+ public cppu::WeakImplHelper< css::ui::dialogs::XFolderPicker2 >
+{
+ 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;
+
+ // XCancellable
+
+ virtual void SAL_CALL cancel( ) override;
+
+ private:
+ SalGtkFolderPicker( const SalGtkFolderPicker& ) = delete;
+ SalGtkFolderPicker& operator=( const SalGtkFolderPicker& ) = delete;
+};
+
+#endif // INCLUDED_VCL_UNX_GTK_FPICKER_SALGTKFOLDERPICKER_HXX
+
+/* 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 000000000..c847774d1
--- /dev/null
+++ b/vcl/unx/gtk3/fpicker/SalGtkPicker.cxx
@@ -0,0 +1,266 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifdef AIX
+#define _LINUX_SOURCE_COMPAT
+#include <sys/timer.h>
+#undef _LINUX_SOURCE_COMPAT
+#endif
+
+#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 <vcl/svapp.hxx>
+#include <tools/urlobj.hxx>
+
+#include <vcl/window.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)
+{
+ 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)
+{
+ // 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()
+{
+ GtkWindow *pParent = nullptr;
+
+ vcl::Window * pWindow = ::Application::GetActiveTopWindow();
+ if( pWindow )
+ {
+ GtkSalFrame *pFrame = dynamic_cast<GtkSalFrame *>( pWindow->ImplGetFrame() );
+ if( pFrame )
+ pParent = GTK_WINDOW(gtk_widget_get_toplevel(pFrame->getWindow()));
+ }
+
+ return pParent;
+}
+
+RunDialog::RunDialog(GtkWidget *pDialog, const uno::Reference<awt::XExtendedToolkit>& rToolkit,
+ const uno::Reference<frame::XDesktop>& rDesktop)
+ : cppu::WeakComponentImplHelper<awt::XTopWindowListener, frame::XTerminateListener>(maLock)
+ , mpDialog(pDialog)
+ , mbTerminateDesktop(false)
+ , mxToolkit(rToolkit)
+ , mxDesktop(rDesktop)
+{
+}
+
+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(const css::uno::Reference<css::frame::XDesktop>& rDesktop)
+ : mxDesktop(rDesktop)
+ {
+ }
+ void terminate()
+ {
+ mxDesktop->terminate();
+ }
+ };
+}
+
+gint RunDialog::run()
+{
+ if (mxToolkit.is())
+ mxToolkit->addTopWindowListener(this);
+
+ mxDesktop->addTerminateListener(this);
+ gint nStatus = gtk_dialog_run(GTK_DIALOG(mpDialog));
+ 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( const uno::Reference<uno::XComponentContext>& xContext )
+ : m_pDialog( nullptr ), m_xContext( xContext )
+{
+}
+
+SalGtkPicker::~SalGtkPicker()
+{
+ SolarMutexGuard g;
+
+ if (m_pDialog)
+ {
+ gtk_widget_destroy(m_pDialog);
+ }
+}
+
+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 );
+
+ gtk_file_chooser_set_current_folder_uri( GTK_FILE_CHOOSER( m_pDialog ),
+ aTxt.getStr() );
+}
+
+OUString SalGtkPicker::implgetDisplayDirectory()
+{
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ gchar* pCurrentFolder =
+ gtk_file_chooser_get_current_folder_uri( GTK_FILE_CHOOSER( m_pDialog ) );
+ OUString aCurrentFolderName = uritounicode(pCurrentFolder);
+ g_free( pCurrentFolder );
+
+ return aCurrentFolderName;
+}
+
+void SalGtkPicker::implsetTitle( const OUString& 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 000000000..46d6d9278
--- /dev/null
+++ b/vcl/unx/gtk3/fpicker/SalGtkPicker.hxx
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_UNX_GTK_FPICKER_SALGTKPICKER_HXX
+#define INCLUDED_VCL_UNX_GTK_FPICKER_SALGTKPICKER_HXX
+
+#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 <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( const css::uno::Reference<css::uno::XComponentContext>& xContext );
+ virtual ~SalGtkPicker();
+ protected:
+ osl::Mutex m_rbHelperMtx;
+ GtkWidget *m_pDialog;
+ protected:
+ /// @throws css::uno::RuntimeException
+ void implsetTitle( const OUString& 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);
+ OString unicodetouri(const OUString &rURL);
+
+ // to instantiate own services
+ css::uno::Reference< css::uno::XComponentContext > m_xContext;
+
+ 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,
+ const css::uno::Reference<css::awt::XExtendedToolkit>& rToolkit,
+ const css::uno::Reference<css::frame::XDesktop>& rDesktop);
+ virtual ~RunDialog() override;
+ gint run();
+ void cancel();
+ static GtkWindow* GetTransientFor();
+};
+
+#endif
+
+/* 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 000000000..88a472e0e
--- /dev/null
+++ b/vcl/unx/gtk3/fpicker/eventnotification.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_UNX_GTK_FPICKER_EVENTNOTIFICATION_HXX
+#define INCLUDED_VCL_UNX_GTK_FPICKER_EVENTNOTIFICATION_HXX
+
+#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;
+};
+
+#endif
+
+/* 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 000000000..5ad801a6e
--- /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 <strings.hrc>
+#include <svdata.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
+
+static const struct
+{
+ sal_Int32 ctrlId;
+ const char *resId;
+} CtrlIdToResIdTable[] = {
+ { CHECKBOX_AUTOEXTENSION, STR_FPICKER_AUTO_EXTENSION },
+ { CHECKBOX_PASSWORD, STR_FPICKER_PASSWORD },
+ { CHECKBOX_GPGENCRYPTION, STR_FPICKER_GPGENCRYPT },
+ { CHECKBOX_FILTEROPTIONS, STR_FPICKER_FILTER_OPTIONS },
+ { CHECKBOX_READONLY, STR_FPICKER_READONLY },
+ { CHECKBOX_LINK, STR_FPICKER_INSERT_AS_LINK },
+ { CHECKBOX_PREVIEW, STR_FPICKER_SHOW_PREVIEW },
+ { PUSHBUTTON_PLAY, STR_FPICKER_PLAY },
+ { LISTBOX_VERSION_LABEL, STR_FPICKER_VERSION },
+ { LISTBOX_TEMPLATE_LABEL, STR_FPICKER_TEMPLATES },
+ { LISTBOX_IMAGE_TEMPLATE_LABEL, STR_FPICKER_IMAGE_TEMPLATE },
+ { LISTBOX_IMAGE_ANCHOR_LABEL, STR_FPICKER_IMAGE_ANCHOR },
+ { CHECKBOX_SELECTION, STR_FPICKER_SELECTION },
+ { FOLDERPICKER_TITLE, STR_FPICKER_FOLDER_DEFAULT_TITLE },
+ { FOLDER_PICKER_DEF_DESCRIPTION, STR_FPICKER_FOLDER_DEFAULT_DESCRIPTION },
+ { FILE_PICKER_OVERWRITE_PRIMARY, STR_FPICKER_ALREADYEXISTOVERWRITE_PRIMARY },
+ { FILE_PICKER_OVERWRITE_SECONDARY, STR_FPICKER_ALREADYEXISTOVERWRITE_SECONDARY },
+ { FILE_PICKER_ALLFORMATS, STR_FPICKER_ALLFORMATS },
+ { FILE_PICKER_TITLE_OPEN, STR_FPICKER_OPEN },
+ { FILE_PICKER_TITLE_SAVE, STR_FPICKER_SAVE },
+ { FILE_PICKER_FILE_TYPE, STR_FPICKER_TYPE }
+};
+
+static const char* CtrlIdToResId( sal_Int32 aControlId )
+{
+ for (auto & i : CtrlIdToResIdTable)
+ {
+ if ( i.ctrlId == aControlId )
+ return i.resId;
+ }
+ return nullptr;
+}
+
+OUString SalGtkPicker::getResString( sal_Int32 aId )
+{
+ OUString aResString;
+ // translate the control id to a resource id
+ const char *pResId = CtrlIdToResId( aId );
+ if (pResId)
+ aResString = VclResId(pResId);
+ return aResString.replace('~', '_');
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtk3gloactiongroup.cxx b/vcl/unx/gtk3/gtk3gloactiongroup.cxx
new file mode 100644
index 000000000..c6d5eaf03
--- /dev/null
+++ b/vcl/unx/gtk3/gtk3gloactiongroup.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)
+ {
+ 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/gtk3glomenu.cxx b/vcl/unx/gtk3/gtk3glomenu.cxx
new file mode 100644
index 000000000..ca6887cb9
--- /dev/null
+++ b/vcl/unx/gtk3/gtk3glomenu.cxx
@@ -0,0 +1,692 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#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 && section < static_cast<gint>(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 && section < static_cast<gint>(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 || position >= static_cast<gint>(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 || position > static_cast<gint>(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 && section < static_cast<gint>(menu->items->len));
+
+ GLOMenu* model = g_lo_menu_get_section (menu, section);
+
+ g_return_if_fail (model != nullptr);
+
+ if (0 <= position && position < static_cast<gint>(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 && section < static_cast<gint>(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 && position < static_cast<gint>(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 && position < static_cast<gint>(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 && section < static_cast<gint>(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/gtk3gtkdata.cxx b/vcl/unx/gtk3/gtk3gtkdata.cxx
new file mode 100644
index 000000000..3182d1551
--- /dev/null
+++ b/vcl/unx/gtk3/gtk3gtkdata.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 .
+ */
+
+#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 <osl/thread.h>
+#include <osl/process.h>
+
+#include <vcl/svapp.hxx>
+#include <sal/log.hxx>
+
+#include <chrono>
+
+
+
+using namespace vcl_sal;
+
+/***************************************************************
+ * class GtkSalDisplay *
+ ***************************************************************/
+extern "C" {
+static GdkFilterReturn call_filterGdkEvent( GdkXEvent* sys_event,
+ GdkEvent* /*event*/,
+ gpointer data )
+{
+ GtkSalDisplay *pDisplay = static_cast<GtkSalDisplay *>(data);
+ return pDisplay->filterGdkEvent( sys_event );
+}
+}
+
+GtkSalDisplay::GtkSalDisplay( GdkDisplay* pDisplay ) :
+ m_pSys( GtkSalSystem::GetSingleton() ),
+ m_pGdkDisplay( pDisplay ),
+ m_bStartupCompleted( false )
+{
+ for(GdkCursor* & rpCsr : m_aCursors)
+ rpCsr = nullptr;
+
+ // FIXME: unify this with SalInst's filter too ?
+ gdk_window_add_filter( nullptr, call_filterGdkEvent, this );
+
+ if ( getenv( "SAL_IGNOREXERRORS" ) )
+ GetGenericUnixSalData()->ErrorTrapPush(); // and leak the trap
+
+ m_bX11Display = DLSYM_GDK_IS_X11_DISPLAY( m_pGdkDisplay );
+
+ gtk_widget_set_default_direction(AllSettings::GetLayoutRTL() ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR);
+}
+
+GtkSalDisplay::~GtkSalDisplay()
+{
+ gdk_window_remove_filter( nullptr, call_filterGdkEvent, this );
+
+ if( !m_bStartupCompleted )
+ gdk_notify_startup_complete();
+
+ for(GdkCursor* & rpCsr : m_aCursors)
+ if( rpCsr )
+ gdk_cursor_unref( rpCsr );
+}
+
+extern "C" {
+
+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 );
+}
+
+}
+
+GdkFilterReturn GtkSalDisplay::filterGdkEvent( GdkXEvent* )
+{
+ (void) this; // loplugin:staticmethods
+ //FIXME: implement filterGdkEvent ...
+ return GDK_FILTER_CONTINUE;
+}
+
+void GtkSalDisplay::screenSizeChanged( GdkScreen const * pScreen )
+{
+ m_pSys->countScreenMonitors();
+ if (pScreen)
+ emitDisplayChanged();
+}
+
+void GtkSalDisplay::monitorsChanged( GdkScreen const * pScreen )
+{
+ m_pSys->countScreenMonitors();
+ if (pScreen)
+ emitDisplayChanged();
+}
+
+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;
+
+ 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);
+ GdkCursor* pCursor = gdk_cursor_new_from_pixbuf(m_pGdkDisplay, pScaledPixBuf,
+ nXHot * fScalefactor, nYHot * fScalefactor);
+ return pCursor;
+}
+
+#define MAKE_CURSOR( vcl_name, name, name2 ) \
+ case vcl_name: \
+ pCursor = getFromSvg(name2, name##curs_x_hot, name##curs_y_hot); \
+ break
+#define MAP_BUILTIN( vcl_name, gdk_name ) \
+ case vcl_name: \
+ pCursor = gdk_cursor_new_for_display( m_pGdkDisplay, gdk_name ); \
+ break
+
+GdkCursor *GtkSalDisplay::getCursor( PointerStyle ePointerStyle )
+{
+ if ( !m_aCursors[ ePointerStyle ] )
+ {
+ GdkCursor *pCursor = nullptr;
+
+ switch( ePointerStyle )
+ {
+ MAP_BUILTIN( PointerStyle::Arrow, GDK_LEFT_PTR );
+ MAP_BUILTIN( PointerStyle::Text, GDK_XTERM );
+ MAP_BUILTIN( PointerStyle::Help, GDK_QUESTION_ARROW );
+ MAP_BUILTIN( PointerStyle::Cross, GDK_CROSSHAIR );
+ MAP_BUILTIN( PointerStyle::Wait, GDK_WATCH );
+
+ MAP_BUILTIN( PointerStyle::NSize, GDK_SB_V_DOUBLE_ARROW );
+ MAP_BUILTIN( PointerStyle::SSize, GDK_SB_V_DOUBLE_ARROW );
+ MAP_BUILTIN( PointerStyle::WSize, GDK_SB_H_DOUBLE_ARROW );
+ MAP_BUILTIN( PointerStyle::ESize, GDK_SB_H_DOUBLE_ARROW );
+
+ MAP_BUILTIN( PointerStyle::NWSize, GDK_TOP_LEFT_CORNER );
+ MAP_BUILTIN( PointerStyle::NESize, GDK_TOP_RIGHT_CORNER );
+ MAP_BUILTIN( PointerStyle::SWSize, GDK_BOTTOM_LEFT_CORNER );
+ MAP_BUILTIN( PointerStyle::SESize, GDK_BOTTOM_RIGHT_CORNER );
+
+ MAP_BUILTIN( PointerStyle::WindowNSize, GDK_TOP_SIDE );
+ MAP_BUILTIN( PointerStyle::WindowSSize, GDK_BOTTOM_SIDE );
+ MAP_BUILTIN( PointerStyle::WindowWSize, GDK_LEFT_SIDE );
+ MAP_BUILTIN( PointerStyle::WindowESize, GDK_RIGHT_SIDE );
+
+ MAP_BUILTIN( PointerStyle::WindowNWSize, GDK_TOP_LEFT_CORNER );
+ MAP_BUILTIN( PointerStyle::WindowNESize, GDK_TOP_RIGHT_CORNER );
+ MAP_BUILTIN( PointerStyle::WindowSWSize, GDK_BOTTOM_LEFT_CORNER );
+ MAP_BUILTIN( PointerStyle::WindowSESize, GDK_BOTTOM_RIGHT_CORNER );
+
+ MAP_BUILTIN( PointerStyle::HSizeBar, GDK_SB_H_DOUBLE_ARROW );
+ MAP_BUILTIN( PointerStyle::VSizeBar, GDK_SB_V_DOUBLE_ARROW );
+
+ MAP_BUILTIN( PointerStyle::RefHand, GDK_HAND2 );
+ MAP_BUILTIN( PointerStyle::Hand, GDK_HAND2 );
+ MAP_BUILTIN( PointerStyle::Pen, GDK_PENCIL );
+
+ MAP_BUILTIN( PointerStyle::HSplit, GDK_SB_H_DOUBLE_ARROW );
+ MAP_BUILTIN( PointerStyle::VSplit, GDK_SB_V_DOUBLE_ARROW );
+
+ MAP_BUILTIN( PointerStyle::Move, GDK_FLEUR );
+
+ 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 );
+
+ default:
+ SAL_WARN( "vcl.gtk", "pointer " << static_cast<int>(ePointerStyle) << "not implemented" );
+ break;
+ }
+ if( !pCursor )
+ pCursor = gdk_cursor_new_for_display( m_pGdkDisplay, GDK_LEFT_PTR );
+
+ 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( SalInstance *pInstance )
+ : GenericUnixSalData( SAL_DATA_GTK3, pInstance )
+ , m_aDispatchMutex()
+ , m_aDispatchCondition()
+ , m_pDocumentFocusListener(nullptr)
+{
+ 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()
+{
+ Yield( true, true );
+ g_warning ("TESTME: We used to have a stop-timer here, but the central code should do this");
+
+ // 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;
+}
+
+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
+ gtk_init_check( &nParams, &pCmdLineAry );
+ gdk_error_trap_push();
+
+ 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);
+ }
+
+#if defined(GDK_WINDOWING_X11)
+ if (DLSYM_GDK_IS_X11_DISPLAY(pGdkDisp))
+ aOrigXIOErrorHandler = XSetIOErrorHandler(XIOErrorHdl);
+#endif
+
+ GtkSalDisplay *pDisplay = new GtkSalDisplay( pGdkDisp );
+ SetDisplay( pDisplay );
+
+ //FIXME: unwind keyboard extension bits
+
+ // add signal handler to notify screen size changes
+ 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 )
+ {
+ pDisplay->screenSizeChanged( pScreen );
+ pDisplay->monitorsChanged( pScreen );
+ g_signal_connect( G_OBJECT(pScreen), "size-changed",
+ G_CALLBACK(signalScreenSizeChanged), pDisplay );
+ g_signal_connect( G_OBJECT(pScreen), "monitors-changed",
+ G_CALLBACK(signalMonitorsChanged), GetGtkDisplay() );
+ }
+ }
+}
+
+void GtkSalData::ErrorTrapPush()
+{
+ gdk_error_trap_push ();
+}
+
+bool GtkSalData::ErrorTrapPop( bool bIgnoreError )
+{
+ if (bIgnoreError)
+ {
+ gdk_error_trap_pop_ignored (); // faster
+ return false;
+ }
+ return gdk_error_trap_pop () != 0;
+}
+
+#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()
+{
+ GtkInstance *pInstance = static_cast<GtkInstance *>(GetSalData()->m_pInstance);
+ pInstance->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->aWindow == 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 );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtk3gtkframe.cxx b/vcl/unx/gtk3/gtk3gtkframe.cxx
new file mode 100644
index 000000000..69a554108
--- /dev/null
+++ b/vcl/unx/gtk3/gtk3gtkframe.cxx
@@ -0,0 +1,4609 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <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/keycodes.hxx>
+#include <unx/geninst.h>
+#include <headless/svpgdi.hxx>
+#include <sal/log.hxx>
+#include <tools/diagnose_ex.h>
+#include <vcl/floatwin.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 <window.h>
+
+#include <basegfx/vector/b2ivector.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/accessibility/XAccessibleEditableText.hpp>
+#include <com/sun/star/awt/MouseButton.hpp>
+#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
+
+using namespace com::sun::star;
+
+int GtkSalFrame::m_nFloats = 0;
+
+static GDBusConnection* pSessionBus = 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_MOD1_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;
+}
+
+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( keyval >= GDK_KEY_F1 && keyval <= GDK_KEY_F26 )
+ // KEY_F26 is the last function key known to keycodes.hxx
+ {
+ 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;
+ // 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;
+}
+
+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;
+}
+
+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_pFixedContainer));
+ 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( 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);
+ }
+
+ aEvent.mnCode |= GetKeyModCode( state );
+
+ bool bStopProcessingKey;
+ if (bDown)
+ {
+ 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)
+{
+ 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)
+{
+ 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 );
+ }
+}
+
+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();
+ }
+}
+
+static bool ensure_dbus_setup( gpointer data )
+{
+ GtkSalFrame* pSalFrame = static_cast< GtkSalFrame* >( data );
+ GdkWindow* gdkWindow = gtk_widget_get_window( pSalFrame->getWindow() );
+
+ if ( gdkWindow != nullptr && g_object_get_data( G_OBJECT( gdkWindow ), "g-lo-menubar" ) == nullptr )
+ {
+ // Get a DBus session connection.
+ if(!pSessionBus)
+ pSessionBus = g_bus_get_sync (G_BUS_TYPE_SESSION, nullptr, nullptr);
+ if( !pSessionBus )
+ {
+ return false;
+ }
+
+ // 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());
+
+ // Generate menu paths.
+ sal_uIntPtr windowId = pSalFrame->GetNativeWindowHandle(pSalFrame->getWindow());
+ gchar* aDBusWindowPath = g_strdup_printf( "/org/libreoffice/window/%lu", windowId );
+ gchar* aDBusMenubarPath = g_strdup_printf( "/org/libreoffice/window/%lu/menus/menubar", windowId );
+
+ // 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 );
+
+ 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 );
+ }
+
+ return false;
+}
+
+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");
+
+ //pSessionBus = NULL;
+ 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 )
+ {
+ // Get a DBus session connection.
+ if ( pSessionBus == nullptr )
+ {
+ pSessionBus = g_bus_get_sync( G_BUS_TYPE_SESSION, nullptr, nullptr );
+
+ if ( pSessionBus == nullptr )
+ 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()
+{
+ m_aSmoothScrollIdle.Stop();
+ m_aSmoothScrollIdle.ClearInvokeHandler();
+
+ 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();
+
+ GtkWidget *pEventWidget = getMouseEventWidget();
+ for (auto handler_id : m_aMouseSignalIds)
+ g_signal_handler_disconnect(G_OBJECT(pEventWidget), handler_id);
+ 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) );
+ {
+ SolarMutexGuard aGuard;
+
+ if(m_nWatcherId)
+ g_bus_unwatch_name(m_nWatcherId);
+
+ 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 );
+ }
+ gtk_widget_destroy( m_pWindow );
+ }
+ }
+ if( m_pForeignParent )
+ g_object_unref( G_OBJECT( m_pForeignParent ) );
+ if( m_pForeignTopLevel )
+ g_object_unref( G_OBJECT( m_pForeignTopLevel) );
+
+ m_pGraphics.reset();
+
+ if (m_pSurface)
+ cairo_surface_destroy(m_pSurface);
+}
+
+void GtkSalFrame::moveWindow( long nX, long nY )
+{
+ if( isChild( false ) )
+ {
+ GtkWidget* pParent = m_pParent ? gtk_widget_get_parent(m_pWindow) : nullptr;
+ // tdf#130414 its 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.nX, nY - m_pParent->maGeometry.nY );
+ }
+ }
+ else
+ gtk_window_move( GTK_WINDOW(m_pWindow), nX, nY );
+}
+
+void GtkSalFrame::widget_set_size_request(long nWidth, long nHeight)
+{
+ gtk_widget_set_size_request(GTK_WIDGET(m_pFixedContainer), nWidth, nHeight );
+}
+
+void GtkSalFrame::window_resize(long nWidth, long nHeight)
+{
+ m_nWidthRequest = nWidth;
+ m_nHeightRequest = nHeight;
+ gtk_window_set_default_size(GTK_WINDOW(m_pWindow), nWidth, nHeight);
+ if (gtk_widget_get_visible(m_pWindow))
+ gtk_window_resize(GTK_WINDOW(m_pWindow), nWidth, nHeight);
+}
+
+void GtkSalFrame::resizeWindow( long nWidth, long nHeight )
+{
+ if( isChild( false ) )
+ {
+ widget_set_size_request(nWidth, nHeight);
+ }
+ else if( ! isChild( true, false ) )
+ window_resize(nWidth, nHeight);
+}
+
+// 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;
+}
+
+void GtkSalFrame::updateScreenNumber()
+{
+ int nScreen = 0;
+ GdkScreen *pScreen = gtk_widget_get_screen( m_pWindow );
+ if( pScreen )
+ nScreen = getDisplay()->getSystem()->getScreenMonitorIdx( pScreen, maGeometry.nX, maGeometry.nY );
+ maGeometry.nDisplayScreenNumber = nScreen;
+}
+
+GtkWidget *GtkSalFrame::getMouseEventWidget() const
+{
+ return GTK_WIDGET(m_pEventBox);
+}
+
+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_aDamageHandler.handle = this;
+ m_aDamageHandler.damaged = ::damaged;
+
+ m_aSmoothScrollIdle.SetInvokeHandler(LINK(this, GtkSalFrame, AsyncScroll));
+
+ m_pTopLevelGrid = GTK_GRID(gtk_grid_new());
+ gtk_container_add(GTK_CONTAINER(m_pWindow), GTK_WIDGET(m_pTopLevelGrid));
+
+ 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);
+
+ // add the fixed container child,
+ // fixed is needed since we have to position plugin windows
+ m_pFixedContainer = GTK_FIXED(g_object_new( ooo_fixed_get_type(), nullptr ));
+ gtk_widget_set_can_focus(GTK_WIDGET(m_pFixedContainer), true);
+ gtk_widget_set_size_request(GTK_WIDGET(m_pFixedContainer), 1, 1);
+ gtk_container_add( GTK_CONTAINER(m_pEventBox), GTK_WIDGET(m_pFixedContainer) );
+
+ GtkWidget *pEventWidget = getMouseEventWidget();
+
+ gtk_widget_set_app_paintable(GTK_WIDGET(m_pFixedContainer), true);
+ gtk_widget_set_redraw_on_allocate(GTK_WIDGET(m_pFixedContainer), false);
+
+ // connect signals
+ // 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 );
+ gtk_widget_set_has_tooltip(pEventWidget, true);
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "query-tooltip", G_CALLBACK(signalTooltipQuery), this ));
+ 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), "motion-notify-event", G_CALLBACK(signalMotion), 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), "leave-notify-event", G_CALLBACK(signalCrossing), this ));
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "enter-notify-event", G_CALLBACK(signalCrossing), this ));
+
+
+ //Drop Target Stuff
+ 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 ));
+
+ //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 ));
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "scroll-event", G_CALLBACK(signalScroll), this ));
+
+ g_signal_connect( G_OBJECT(m_pFixedContainer), "draw", G_CALLBACK(signalDraw), this );
+ g_signal_connect( G_OBJECT(m_pFixedContainer), "realize", G_CALLBACK(signalRealize), this );
+ g_signal_connect( G_OBJECT(m_pFixedContainer), "size-allocate", G_CALLBACK(sizeAllocated), this );
+
+ GtkGesture *pSwipe = gtk_gesture_swipe_new(pEventWidget);
+ g_signal_connect(pSwipe, "swipe", G_CALLBACK(gestureSwipe), this);
+ gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER (pSwipe), GTK_PHASE_TARGET);
+ g_object_weak_ref(G_OBJECT(pEventWidget), reinterpret_cast<GWeakNotify>(g_object_unref), pSwipe);
+
+ GtkGesture *pLongPress = gtk_gesture_long_press_new(pEventWidget);
+ g_signal_connect(pLongPress, "pressed", G_CALLBACK(gestureLongPress), this);
+ gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER (pLongPress), GTK_PHASE_TARGET);
+ g_object_weak_ref(G_OBJECT(pEventWidget), reinterpret_cast<GWeakNotify>(g_object_unref), pLongPress);
+
+ 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
+ g_signal_connect( G_OBJECT(m_pWindow), "set-focus", G_CALLBACK(signalSetFocus), this );
+ 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), "configure-event", G_CALLBACK(signalConfigure), this );
+ 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 );
+ g_signal_connect( G_OBJECT(m_pWindow), "delete-event", G_CALLBACK(signalDelete), this );
+ g_signal_connect( G_OBJECT(m_pWindow), "window-state-event", G_CALLBACK(signalWindowState), this );
+ g_signal_connect( G_OBJECT(m_pWindow), "visibility-notify-event", G_CALLBACK(signalVisibility), this );
+ g_signal_connect( G_OBJECT(m_pWindow), "destroy", G_CALLBACK(signalDestroy), this );
+
+ // init members
+ m_pCurrentCursor = nullptr;
+ m_nKeyModifiers = ModKeyFlags::NONE;
+ m_bFullscreen = false;
+ m_bSpanMonitorsWhenFullscreen = false;
+ m_nState = GDK_WINDOW_STATE_WITHDRAWN;
+ 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;
+
+ gtk_widget_add_events( m_pWindow,
+ GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
+ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK |
+ GDK_VISIBILITY_NOTIFY_MASK | GDK_SCROLL_MASK
+ );
+
+ // show the widgets
+ gtk_widget_show_all(GTK_WIDGET(m_pTopLevelGrid));
+
+ // realize the window, we need an XWindow id
+ gtk_widget_realize( m_pWindow );
+
+ //system data
+ m_aSystemData.aWindow = 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::Gtk3;
+
+#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;
+ 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
+#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.nX = -1;
+ maGeometry.nY = -1;
+ maGeometry.nWidth = aDefSize.Width();
+ maGeometry.nHeight = aDefSize.Height();
+ maGeometry.nTopDecoration = 0;
+ maGeometry.nBottomDecoration = 0;
+ maGeometry.nLeftDecoration = 0;
+ maGeometry.nRightDecoration = 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::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);
+ m_pForeignParent = nullptr;
+ m_aForeignParentWindow = None;
+ m_pForeignTopLevel = nullptr;
+ m_aForeignTopLevelWindow = None;
+ m_nStyle = nStyle;
+
+ GtkWindowType eWinType = ( (nStyle & SalFrameStyleFlags::FLOAT) &&
+ ! (nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION)
+ )
+ ? GTK_WINDOW_POPUP : GTK_WINDOW_TOPLEVEL;
+
+ if( nStyle & SalFrameStyleFlags::SYSTEMCHILD )
+ {
+ m_pWindow = gtk_event_box_new();
+ if( m_pParent )
+ {
+ // insert into container
+ gtk_fixed_put( m_pParent->getFixedContainer(),
+ m_pWindow, 0, 0 );
+ }
+ }
+ else
+ {
+ m_pWindow = gtk_window_new(eWinType);
+
+ // 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);
+ }
+
+ 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 = gtk_widget_get_toplevel(m_pParent->m_pWindow);
+ if (!isChild())
+ gtk_window_set_screen(GTK_WINDOW(m_pWindow), gtk_widget_get_screen(pTopLevel));
+
+ 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)));
+ }
+ }
+
+ // set window type
+ bool bDecoHandling =
+ ! isChild() &&
+ ( ! (nStyle & SalFrameStyleFlags::FLOAT) ||
+ (nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION) );
+
+ if( bDecoHandling )
+ {
+ GdkWindowTypeHint eType = GDK_WINDOW_TYPE_HINT_NORMAL;
+ if( (nStyle & SalFrameStyleFlags::DIALOG) && m_pParent != nullptr )
+ eType = GDK_WINDOW_TYPE_HINT_DIALOG;
+ if( nStyle & SalFrameStyleFlags::INTRO )
+ {
+ gtk_window_set_role( GTK_WINDOW(m_pWindow), "splashscreen" );
+ eType = GDK_WINDOW_TYPE_HINT_SPLASHSCREEN;
+ }
+ else if( nStyle & SalFrameStyleFlags::TOOLWINDOW )
+ {
+ eType = GDK_WINDOW_TYPE_HINT_DIALOG;
+ gtk_window_set_skip_taskbar_hint( GTK_WINDOW(m_pWindow), true );
+ }
+ else if( nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION )
+ {
+ eType = GDK_WINDOW_TYPE_HINT_TOOLBAR;
+ gtk_window_set_focus_on_map(GTK_WINDOW(m_pWindow), false);
+ gtk_window_set_decorated(GTK_WINDOW(m_pWindow), false);
+ }
+ gtk_window_set_type_hint( GTK_WINDOW(m_pWindow), eType );
+ gtk_window_set_gravity( GTK_WINDOW(m_pWindow), GDK_GRAVITY_STATIC );
+ gtk_window_set_resizable( GTK_WINDOW(m_pWindow), bool(nStyle & SalFrameStyleFlags::SIZEABLE) );
+
+#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::getSystemUILanguage());
+ 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
+ }
+ else if( nStyle & SalFrameStyleFlags::FLOAT )
+ gtk_window_set_type_hint( GTK_WINDOW(m_pWindow), GDK_WINDOW_TYPE_HINT_POPUP_MENU );
+
+ InitCommon();
+
+ if( eWinType == GTK_WINDOW_TOPLEVEL )
+ {
+ // Enable DBus native menu if available.
+ ensure_dbus_setup( this );
+
+ }
+}
+
+GdkNativeWindow GtkSalFrame::findTopLevelSystemWindow( GdkNativeWindow )
+{
+ //FIXME: no findToplevelSystemWindow
+ return 0;
+}
+
+void GtkSalFrame::Init( SystemParentData* pSysData )
+{
+ m_pParent = nullptr;
+ m_aForeignParentWindow = pSysData->aWindow;
+ m_pForeignParent = nullptr;
+ m_aForeignTopLevelWindow = findTopLevelSystemWindow(pSysData->aWindow);
+ m_pForeignTopLevel = gdk_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 );
+ }
+ m_nStyle = SalFrameStyleFlags::PLUG;
+ InitCommon();
+
+ m_pForeignParent = gdk_window_foreign_new_for_display( getGdkDisplay(), m_aForeignParentWindow );
+ gdk_window_set_events( m_pForeignParent, GDK_STRUCTURE_MASK );
+
+ //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 && ! isChild() )
+ {
+ OString sTitle(OUStringToOString(rTitle, RTL_TEXTENCODING_UTF8));
+ gtk_window_set_title(GTK_WINDOW(m_pWindow), sTitle.getStr());
+ if (m_pHeaderBar)
+ gtk_header_bar_set_title(m_pHeaderBar, sTitle.getStr());
+ }
+}
+
+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()))
+ {
+ static auto set_application_id = reinterpret_cast<void (*) (GdkWindow*, const char*)>(
+ dlsym(nullptr, "gdk_wayland_window_set_application_id"));
+ if (set_application_id)
+ {
+ GdkWindow* gdkWindow = gtk_widget_get_window(m_pWindow);
+ set_application_id(gdkWindow, appicon);
+
+ // 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::DrawMenuBar()
+{
+}
+
+void GtkSalFrame::Center()
+{
+ if (!GTK_IS_WINDOW(m_pWindow))
+ return;
+ 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);
+}
+
+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 )
+ {
+ 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)
+ /*
+ 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();
+ m_pParent->grabPointer(false, true, false);
+ }
+ }
+ 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() )
+ {
+ 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 ) );
+ }
+ }
+}
+
+void GtkSalFrame::SetMaxClientSize( long nWidth, long nHeight )
+{
+ if( ! isChild() )
+ {
+ m_aMaxSize = Size( nWidth, nHeight );
+ setMinMaxSize();
+ }
+}
+void GtkSalFrame::SetMinClientSize( long nWidth, 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.nWidth, maGeometry.nHeight );
+ if (!m_pSurface || m_aFrameSize.getX() != aFrameSize.getX() ||
+ m_aFrameSize.getY() != aFrameSize.getY() )
+ {
+ if( aFrameSize.getX() == 0 )
+ aFrameSize.setX( 1 );
+ if( aFrameSize.getY() == 0 )
+ aFrameSize.setY( 1 );
+
+ if (m_pSurface)
+ cairo_surface_destroy(m_pSurface);
+
+ m_pSurface = gdk_window_create_similar_surface(gtk_widget_get_window(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.nWidth << " x " << maGeometry.nHeight);
+
+ if (m_pGraphics)
+ m_pGraphics->setSurface(m_pSurface, m_aFrameSize);
+ }
+}
+
+void GtkSalFrame::SetPosSize( long nX, long nY, long nWidth, 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.nWidth = nWidth;
+ maGeometry.nHeight = nHeight;
+
+ if( isChild( false ) )
+ widget_set_size_request(nWidth, nHeight);
+ else if( ! ( m_nState & GDK_WINDOW_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.nWidth-m_nWidthRequest-1-nX;
+ nX += m_pParent->maGeometry.nX;
+ nY += m_pParent->maGeometry.nY;
+ }
+
+ if (nFlags & SAL_FRAME_POSSIZE_X)
+ maGeometry.nX = nX;
+ if (nFlags & SAL_FRAME_POSSIZE_Y)
+ maGeometry.nY = nY;
+ m_bGeometryIsProvisional = true;
+
+ m_bDefaultPos = false;
+
+ moveWindow(maGeometry.nX, maGeometry.nY);
+
+ updateScreenNumber();
+ }
+ else if( m_bDefaultPos )
+ Center();
+
+ m_bDefaultPos = false;
+}
+
+void GtkSalFrame::GetClientSize( long& rWidth, long& rHeight )
+{
+ if( m_pWindow && !(m_nState & GDK_WINDOW_STATE_ICONIFIED) )
+ {
+ rWidth = maGeometry.nWidth;
+ rHeight = maGeometry.nHeight;
+ }
+ else
+ rWidth = rHeight = 0;
+}
+
+void GtkSalFrame::GetWorkArea( tools::Rectangle& rRect )
+{
+ GdkScreen *pScreen = gtk_widget_get_screen(m_pWindow);
+ tools::Rectangle aRetRect;
+ int max = gdk_screen_get_n_monitors (pScreen);
+ for (int i = 0; i < max; ++i)
+ {
+ GdkRectangle aRect;
+ gdk_screen_get_monitor_workarea(pScreen, i, &aRect);
+ tools::Rectangle aMonitorRect(aRect.x, aRect.y, aRect.x+aRect.width, aRect.y+aRect.height);
+ aRetRect.Union(aMonitorRect);
+ }
+ rRect = aRetRect;
+}
+
+SalFrame* GtkSalFrame::GetParent() const
+{
+ return m_pParent;
+}
+
+void GtkSalFrame::SetWindowState( const SalFrameState* pState )
+{
+ if( ! m_pWindow || ! pState || isChild( true, false ) )
+ return;
+
+ const WindowStateMask nMaxGeometryMask =
+ WindowStateMask::X | WindowStateMask::Y |
+ WindowStateMask::Width | WindowStateMask::Height |
+ WindowStateMask::MaximizedX | WindowStateMask::MaximizedY |
+ WindowStateMask::MaximizedWidth | WindowStateMask::MaximizedHeight;
+
+ if( (pState->mnMask & WindowStateMask::State) &&
+ ! ( m_nState & GDK_WINDOW_STATE_MAXIMIZED ) &&
+ (pState->mnState & WindowStateState::Maximized) &&
+ (pState->mnMask & nMaxGeometryMask) == nMaxGeometryMask )
+ {
+ resizeWindow( pState->mnWidth, pState->mnHeight );
+ moveWindow( pState->mnX, pState->mnY );
+ m_bDefaultPos = m_bDefaultSize = false;
+
+ updateScreenNumber();
+
+ m_nState = GdkWindowState( m_nState | GDK_WINDOW_STATE_MAXIMIZED );
+ m_aRestorePosSize = tools::Rectangle( Point( pState->mnX, pState->mnY ),
+ Size( pState->mnWidth, pState->mnHeight ) );
+ }
+ else if( pState->mnMask & (WindowStateMask::X | WindowStateMask::Y |
+ WindowStateMask::Width | WindowStateMask::Height ) )
+ {
+ sal_uInt16 nPosSizeFlags = 0;
+ long nX = pState->mnX - (m_pParent ? m_pParent->maGeometry.nX : 0);
+ long nY = pState->mnY - (m_pParent ? m_pParent->maGeometry.nY : 0);
+ if( pState->mnMask & WindowStateMask::X )
+ nPosSizeFlags |= SAL_FRAME_POSSIZE_X;
+ else
+ nX = maGeometry.nX - (m_pParent ? m_pParent->maGeometry.nX : 0);
+ if( pState->mnMask & WindowStateMask::Y )
+ nPosSizeFlags |= SAL_FRAME_POSSIZE_Y;
+ else
+ nY = maGeometry.nY - (m_pParent ? m_pParent->maGeometry.nY : 0);
+ if( pState->mnMask & WindowStateMask::Width )
+ nPosSizeFlags |= SAL_FRAME_POSSIZE_WIDTH;
+ if( pState->mnMask & WindowStateMask::Height )
+ nPosSizeFlags |= SAL_FRAME_POSSIZE_HEIGHT;
+ SetPosSize( nX, nY, pState->mnWidth, pState->mnHeight, nPosSizeFlags );
+ }
+ if( pState->mnMask & WindowStateMask::State && ! isChild() )
+ {
+ if( pState->mnState & WindowStateState::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.
+ */
+ if( (pState->mnState & WindowStateState::Minimized)
+ && ! m_pParent )
+ gtk_window_iconify( GTK_WINDOW(m_pWindow) );
+ else
+ gtk_window_deiconify( GTK_WINDOW(m_pWindow) );
+ }
+ TriggerPaintEvent();
+}
+
+namespace
+{
+ void GetPosAndSize(GtkWindow *pWindow, long& rX, long &rY, long &rWidth, long &rHeight)
+ {
+ gint root_x, root_y;
+ gtk_window_get_position(GTK_WINDOW(pWindow), &root_x, &root_y);
+ rX = root_x;
+ rY = root_y;
+ gint width, height;
+ gtk_window_get_size(GTK_WINDOW(pWindow), &width, &height);
+ rWidth = width;
+ rHeight = height;
+ }
+
+ tools::Rectangle GetPosAndSize(GtkWindow *pWindow)
+ {
+ long nX, nY, nWidth, nHeight;
+ GetPosAndSize(pWindow, nX, nY, nWidth, nHeight);
+ return tools::Rectangle(nX, nY, nX + nWidth, nY + nHeight);
+ }
+}
+
+bool GtkSalFrame::GetWindowState( SalFrameState* pState )
+{
+ pState->mnState = WindowStateState::Normal;
+ pState->mnMask = WindowStateMask::State;
+ // rollup ? gtk 2.2 does not seem to support the shaded state
+ if( m_nState & GDK_WINDOW_STATE_ICONIFIED )
+ pState->mnState |= WindowStateState::Minimized;
+ if( m_nState & GDK_WINDOW_STATE_MAXIMIZED )
+ {
+ pState->mnState |= WindowStateState::Maximized;
+ pState->mnX = m_aRestorePosSize.Left();
+ pState->mnY = m_aRestorePosSize.Top();
+ pState->mnWidth = m_aRestorePosSize.GetWidth();
+ pState->mnHeight = m_aRestorePosSize.GetHeight();
+ GetPosAndSize(GTK_WINDOW(m_pWindow), pState->mnMaximizedX, pState->mnMaximizedY,
+ pState->mnMaximizedWidth, pState->mnMaximizedHeight);
+ pState->mnMask |= WindowStateMask::MaximizedX |
+ WindowStateMask::MaximizedY |
+ WindowStateMask::MaximizedWidth |
+ WindowStateMask::MaximizedHeight;
+ }
+ else
+ {
+ GetPosAndSize(GTK_WINDOW(m_pWindow), pState->mnX, pState->mnY,
+ pState->mnWidth, pState->mnHeight);
+ }
+ pState->mnMask |= WindowStateMask::X |
+ WindowStateMask::Y |
+ WindowStateMask::Width |
+ WindowStateMask::Height;
+
+ return true;
+}
+
+void GtkSalFrame::SetScreen( unsigned int nNewScreen, SetType eType, tools::Rectangle const *pSize )
+{
+ if( !m_pWindow )
+ return;
+
+ if (maGeometry.nDisplayScreenNumber == nNewScreen && eType == SetType::RetainSize)
+ return;
+
+ int nX = maGeometry.nX, nY = maGeometry.nY,
+ nWidth = maGeometry.nWidth, nHeight = maGeometry.nHeight;
+ GdkScreen *pScreen = nullptr;
+ GdkRectangle aNewMonitor;
+
+ bool bSpanAllScreens = nNewScreen == static_cast<unsigned int>(-1);
+ m_bSpanMonitorsWhenFullscreen = bSpanAllScreens && getDisplay()->getSystem()->GetDisplayScreenCount() > 1;
+ gint nMonitor = -1;
+ if (m_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, gtk_widget_get_window( 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;
+ m_nStyle |= SalFrameStyleFlags::PARTIAL_FULLSCREEN;
+ 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();
+ m_nStyle &= ~SalFrameStyleFlags::PARTIAL_FULLSCREEN;
+ 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);
+
+ gdk_window_set_fullscreen_mode( gtk_widget_get_window(m_pWindow), m_bSpanMonitorsWhenFullscreen
+ ? GDK_FULLSCREEN_ON_ALL_MONITORS : GDK_FULLSCREEN_ON_CURRENT_MONITOR );
+
+ GtkWidget* pMenuBarContainerWidget = m_pSalMenu ? m_pSalMenu->GetMenuBarContainerWidget() : nullptr;
+ if( eType == SetType::Fullscreen )
+ {
+ if (pMenuBarContainerWidget)
+ gtk_widget_hide(pMenuBarContainerWidget);
+ if (m_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 );
+}
+
+void GtkSalFrame::SetScreenNumber( unsigned int nNewScreen )
+{
+ SetScreen( nNewScreen, SetType::RetainSize );
+}
+
+void GtkSalFrame::updateWMClass()
+{
+ OString aResClass = OUStringToOString(m_sWMClass, RTL_TEXTENCODING_ASCII_US);
+ const char *pResClass = !aResClass.isEmpty() ? aResClass.getStr() :
+ SalGenericSystem::getFrameClassName();
+ Display *display;
+
+ if (!getDisplay()->IsX11Display())
+ return;
+
+ display = GDK_DISPLAY_XDISPLAY(getGdkDisplay());
+
+ if( gtk_widget_get_realized( m_pWindow ) )
+ {
+ XClassHint* pClass = XAllocClassHint();
+ OString aResName = SalGenericSystem::getFrameResName();
+ pClass->res_name = const_cast<char*>(aResName.getStr());
+ pClass->res_class = const_cast<char*>(pResClass);
+ XSetClassHint( display,
+ widget_get_xid(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::StartPresentation( bool bStart )
+{
+ std::optional<guint> aWindow;
+ std::optional<Display*> aDisplay;
+ if( getDisplay()->IsX11Display() )
+ {
+ aWindow = widget_get_xid(m_pWindow);
+ aDisplay = GDK_DISPLAY_XDISPLAY( getGdkDisplay() );
+ }
+
+ m_ScreenSaverInhibitor.inhibit( bStart,
+ "presentation",
+ getDisplay()->IsX11Display(),
+ aWindow,
+ aDisplay );
+}
+
+void GtkSalFrame::SetAlwaysOnTop( bool bOnTop )
+{
+ if( m_pWindow )
+ gtk_window_set_keep_above( GTK_WINDOW( m_pWindow ), bOnTop );
+}
+
+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 )
+ {
+ if( isChild( false ) )
+ GrabFocus();
+ else if( gtk_widget_get_mapped( m_pWindow ) )
+ {
+ if (!(nFlags & SalFrameToTop::GrabFocusOnly))
+ gtk_window_present_with_time(GTK_WINDOW(m_pWindow), GetLastInputEventTime());
+ else
+ gdk_window_focus(gtk_widget_get_window(m_pWindow), GetLastInputEventTime());
+ GrabFocus();
+ }
+ else
+ {
+ if( nFlags & SalFrameToTop::RestoreWhenMin )
+ gtk_window_present( GTK_WINDOW(m_pWindow) );
+ }
+ }
+}
+
+void GtkSalFrame::SetPointer( PointerStyle ePointerStyle )
+{
+ if( m_pWindow && ePointerStyle != m_ePointerStyle )
+ {
+ m_ePointerStyle = ePointerStyle;
+ GdkCursor *pCursor = getDisplay()->getCursor( ePointerStyle );
+ gdk_window_set_cursor( gtk_widget_get_window(m_pWindow), pCursor );
+ m_pCurrentCursor = pCursor;
+
+ // #i80791# use grabPointer the same way as CaptureMouse, respective float grab
+ if( getDisplay()->MouseCaptured( this ) )
+ grabPointer( true, false, false );
+ else if( m_nFloats > 0 )
+ grabPointer( true, false, true );
+ }
+}
+
+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(3, 20, 0)
+ if (gtk_check_version(3, 20, 0) == nullptr)
+ {
+ 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, gtk_widget_get_window(getMouseEventWidget()), eCapability,
+ bOwnerEvents, nullptr, nullptr, nullptr, nullptr);
+ }
+ else
+ {
+ gdk_seat_ungrab(pSeat);
+ }
+ return;
+ }
+#endif
+
+ //else older gtk3
+ GdkDeviceManager* pDeviceManager = gdk_display_get_device_manager(getGdkDisplay());
+ GdkDevice* pPointer = gdk_device_manager_get_client_pointer(pDeviceManager);
+ GdkDevice* pKeyboard = bKeyboardAlso ? gdk_device_get_associated_device(pPointer) : nullptr;
+ GdkWindow* pWindow = gtk_widget_get_window(getMouseEventWidget());
+ guint32 nCurrentTime = gtk_get_current_event_time();
+ if (bGrab)
+ {
+ gdk_device_grab(pPointer, pWindow, GDK_OWNERSHIP_NONE,
+ bOwnerEvents, GDK_ALL_EVENTS_MASK, m_pCurrentCursor, nCurrentTime);
+ if (pKeyboard)
+ gdk_device_grab(pKeyboard, pWindow, GDK_OWNERSHIP_NONE, true, GDK_ALL_EVENTS_MASK, nullptr, nCurrentTime);
+ }
+ else
+ {
+ gdk_device_ungrab(pPointer, nCurrentTime);
+ if (pKeyboard)
+ gdk_device_ungrab(pKeyboard, nCurrentTime);
+ }
+}
+
+void GtkSalFrame::CaptureMouse( bool bCapture )
+{
+ getDisplay()->CaptureMouse( bCapture ? this : nullptr );
+}
+
+void GtkSalFrame::SetPointerPos( long nX, long nY )
+{
+ 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.nX + nX;
+ unsigned int nWindowTop = maGeometry.nY + 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( gtk_widget_get_window(pFrame->m_pWindow) , &x, &y, &mask );
+}
+
+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_MOD1_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;
+
+ // 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(pName, rtl_str_getLength(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;
+ GdkScreen* pScreen;
+ gint x, y;
+ GdkModifierType aMask;
+ gdk_display_get_pointer( getGdkDisplay(), &pScreen, &x, &y, &aMask );
+ aState.maPos = Point( x - maGeometry.nX, y - maGeometry.nY );
+ aState.mnState = GetMouseModCode( aMask );
+ return aState;
+}
+
+KeyIndicatorState GtkSalFrame::GetIndicatorState()
+{
+ KeyIndicatorState nState = KeyIndicatorState::NONE;
+
+ 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;
+
+ 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::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
+ );
+}
+
+bool GtkSalFrame::SetPluginParent( SystemParentData* )
+{
+ //FIXME: no SetPluginParent impl. for gtk3
+ return false;
+}
+
+void GtkSalFrame::ResetClipRegion()
+{
+ if( m_pWindow )
+ gdk_window_shape_combine_region( gtk_widget_get_window( m_pWindow ), nullptr, 0, 0 );
+}
+
+void GtkSalFrame::BeginSetClipRegion( sal_uInt32 )
+{
+ if( m_pRegion )
+ cairo_region_destroy( m_pRegion );
+ m_pRegion = cairo_region_create();
+}
+
+void GtkSalFrame::UnionClipRegion( long nX, long nY, long nWidth, 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( m_pWindow && m_pRegion )
+ gdk_window_shape_combine_region( gtk_widget_get_window(m_pWindow), m_pRegion, 0, 0 );
+}
+
+void GtkSalFrame::PositionByToolkit(const tools::Rectangle& rRect, FloatWinPopupFlags nFlags)
+{
+ if (ImplGetSVData()->maNWFData.mbCanDetermineWindowPosition)
+ 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.nWidth-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.nX;
+ 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)
+{
+ GtkWidget *pWidget = gtk_popover_new(getMouseEventWidget());
+ OString sUTF = OUStringToOString(rHelpText, RTL_TEXTENCODING_UTF8);
+ GtkWidget *pLabel = gtk_label_new(sUTF.getStr());
+ gtk_container_add(GTK_CONTAINER(pWidget), pLabel);
+
+ 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);
+
+ gtk_popover_set_modal(GTK_POPOVER(pWidget), false);
+
+ gtk_widget_show_all(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);
+
+ GtkWidget *pLabel = gtk_bin_get_child(GTK_BIN(pWidget));
+ 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);
+ gtk_widget_destroy(pWidget);
+ return true;
+}
+
+void GtkSalFrame::addGrabLevel()
+{
+ if (m_nGrabLevel == 0)
+ gtk_grab_add(getMouseEventWidget());
+ ++m_nGrabLevel;
+}
+
+void GtkSalFrame::removeGrabLevel()
+{
+ if (m_nGrabLevel > 0)
+ {
+ --m_nGrabLevel;
+ if (m_nGrabLevel == 0)
+ gtk_grab_remove(getMouseEventWidget());
+ }
+}
+
+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);
+}
+
+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_widget_translate_coordinates(pRealEventWidget, pTargetWidget, rEventX, rEventY, &rEventX, &rEventY);
+ }
+ }
+}
+
+void GtkSalFrame::GrabFocus()
+{
+ GtkWidget* pGrabWidget;
+ if (GTK_IS_EVENT_BOX(m_pWindow))
+ pGrabWidget = GTK_WIDGET(m_pWindow);
+ else
+ pGrabWidget = GTK_WIDGET(m_pFixedContainer);
+ if (!gtk_widget_get_can_focus(pGrabWidget))
+ gtk_widget_set_can_focus(pGrabWidget, true);
+ if (!gtk_widget_has_focus(pGrabWidget))
+ gtk_widget_grab_focus(pGrabWidget);
+}
+
+gboolean GtkSalFrame::signalButton(GtkWidget*, GdkEventButton* pEvent, gpointer frame)
+{
+ UpdateLastInputEventTime(pEvent->time);
+
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ GtkWidget* pEventWidget = pThis->getMouseEventWidget();
+ bool bDifferentEventWindow = pEvent->window != gtk_widget_get_window(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();
+ }
+
+ SalMouseEvent aEvent;
+ 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;
+ }
+ switch( pEvent->button )
+ {
+ case 1: aEvent.mnButton = MOUSE_LEFT; break;
+ case 2: aEvent.mnButton = MOUSE_MIDDLE; break;
+ case 3: aEvent.mnButton = MOUSE_RIGHT; 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())
+ {
+ int frame_x = static_cast<int>(pEvent->x_root - nEventX);
+ int frame_y = static_cast<int>(pEvent->y_root - nEventY);
+ if (pThis->m_bGeometryIsProvisional || frame_x != pThis->maGeometry.nX || frame_y != pThis->maGeometry.nY)
+ {
+ pThis->m_bGeometryIsProvisional = false;
+ pThis->maGeometry.nX = frame_x;
+ pThis->maGeometry.nY = frame_y;
+ ImplSVData* pSVData = ImplGetSVData();
+ if (pSVData->maNWFData.mbCanDetermineWindowPosition)
+ pThis->CallCallbackExc(SalEvent::Move, nullptr);
+ }
+ }
+
+ if (!aDel.isDeleted())
+ {
+ aEvent.mnTime = pEvent->time;
+ aEvent.mnX = static_cast<long>(pEvent->x_root) - pThis->maGeometry.nX;
+ aEvent.mnY = static_cast<long>(pEvent->y_root) - pThis->maGeometry.nY;
+ aEvent.mnCode = GetMouseModCode( pEvent->state );
+
+ if( AllSettings::GetLayoutRTL() )
+ aEvent.mnX = pThis->maGeometry.nWidth-1-aEvent.mnX;
+
+ pThis->CallCallbackExc( nEventType, &aEvent );
+ }
+
+ return true;
+}
+
+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();
+}
+
+IMPL_LINK_NOARG(GtkSalFrame, AsyncScroll, Timer *, void)
+{
+ assert(!m_aPendingScrollEvents.empty());
+
+ SalWheelMouseEvent aEvent;
+
+ GdkEvent* pEvent = m_aPendingScrollEvents.back();
+
+ aEvent.mnTime = pEvent->scroll.time;
+ aEvent.mnX = static_cast<sal_uLong>(pEvent->scroll.x);
+ // --- RTL --- (mirror mouse pos)
+ if (AllSettings::GetLayoutRTL())
+ aEvent.mnX = maGeometry.nWidth - 1 - aEvent.mnX;
+ aEvent.mnY = static_cast<sal_uLong>(pEvent->scroll.y);
+ aEvent.mnCode = GetMouseModCode( 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();
+
+ // rhbz#1344042 "Traditionally" in gtk3 we tool 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);
+ }
+}
+
+SalWheelMouseEvent GtkSalFrame::GetWheelEvent(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.nWidth - 1 - aEvent.mnX;
+
+ pThis->CallCallbackExc(SalEvent::WheelMouse, &aEvent);
+
+ return true;
+}
+
+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))
+ {
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+
+ SalSwipeEvent aEvent;
+ aEvent.mnVelocityX = velocity_x;
+ aEvent.mnVelocityY = velocity_y;
+ aEvent.mnX = x;
+ aEvent.mnY = y;
+
+ pThis->CallCallbackExc(SalEvent::Swipe, &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))
+ {
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+
+ SalLongPressEvent aEvent;
+ aEvent.mnX = x;
+ aEvent.mnY = y;
+
+ pThis->CallCallbackExc(SalEvent::LongPress, &aEvent);
+ }
+}
+
+gboolean GtkSalFrame::signalMotion( GtkWidget*, GdkEventMotion* pEvent, gpointer frame )
+{
+ UpdateLastInputEventTime(pEvent->time);
+
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ GtkWidget* pEventWidget = pThis->getMouseEventWidget();
+ bool bDifferentEventWindow = pEvent->window != gtk_widget_get_window(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);
+
+ int frame_x = static_cast<int>(pEvent->x_root - nEventX);
+ int frame_y = static_cast<int>(pEvent->y_root - nEventY);
+
+ if (pThis->m_bGeometryIsProvisional || frame_x != pThis->maGeometry.nX || frame_y != pThis->maGeometry.nY)
+ {
+ pThis->m_bGeometryIsProvisional = false;
+ pThis->maGeometry.nX = frame_x;
+ pThis->maGeometry.nY = frame_y;
+ ImplSVData* pSVData = ImplGetSVData();
+ if (pSVData->maNWFData.mbCanDetermineWindowPosition)
+ pThis->CallCallbackExc(SalEvent::Move, nullptr);
+ }
+
+ if (!aDel.isDeleted())
+ {
+ SalMouseEvent aEvent;
+ aEvent.mnTime = pEvent->time;
+ aEvent.mnX = static_cast<long>(pEvent->x_root) - pThis->maGeometry.nX;
+ aEvent.mnY = static_cast<long>(pEvent->y_root) - pThis->maGeometry.nY;
+ aEvent.mnCode = GetMouseModCode( pEvent->state );
+ aEvent.mnButton = 0;
+
+ if( AllSettings::GetLayoutRTL() )
+ aEvent.mnX = pThis->maGeometry.nWidth-1-aEvent.mnX;
+
+ pThis->CallCallbackExc( SalEvent::MouseMove, &aEvent );
+ }
+
+ if (!aDel.isDeleted())
+ {
+ // ask for the next hint
+ gint x, y;
+ GdkModifierType mask;
+ gdk_window_get_pointer( gtk_widget_get_window(GTK_WIDGET(pThis->m_pWindow)), &x, &y, &mask );
+ }
+
+ return true;
+}
+
+gboolean GtkSalFrame::signalCrossing( GtkWidget*, GdkEventCrossing* pEvent, gpointer frame )
+{
+ UpdateLastInputEventTime(pEvent->time);
+
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ SalMouseEvent aEvent;
+ aEvent.mnTime = pEvent->time;
+ aEvent.mnX = static_cast<long>(pEvent->x_root) - pThis->maGeometry.nX;
+ aEvent.mnY = static_cast<long>(pEvent->y_root) - pThis->maGeometry.nY;
+ aEvent.mnCode = GetMouseModCode( pEvent->state );
+ aEvent.mnButton = 0;
+
+ if (AllSettings::GetLayoutRTL())
+ aEvent.mnX = pThis->maGeometry.nWidth-1-aEvent.mnX;
+
+ pThis->CallCallbackExc( (pEvent->type == GDK_ENTER_NOTIFY) ? SalEvent::MouseMove : SalEvent::MouseLeave, &aEvent );
+
+ return true;
+}
+
+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
+
+ gtk_widget_queue_draw_area(GTK_WIDGET(m_pFixedContainer),
+ nExtentsX, nExtentsY,
+ nExtentsWidth, nExtentsHeight);
+}
+
+// blit our backing cairo surface to the target cairo context
+gboolean GtkSalFrame::signalDraw(GtkWidget*, cairo_t *cr, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+
+ cairo_set_source_surface(cr, pThis->m_pSurface, 0, 0);
+ cairo_paint(cr);
+
+ return false;
+}
+
+void GtkSalFrame::sizeAllocated(GtkWidget* pWidget, GdkRectangle *pAllocation, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ // ignore size-allocations that occur during configuring an embedded SalObject
+ if (pThis->m_bSalObjectSetPosSize)
+ return;
+ pThis->maGeometry.nWidth = pAllocation->width;
+ pThis->maGeometry.nHeight = pAllocation->height;
+ bool bRealized = gtk_widget_get_realized(pWidget);
+ if (bRealized)
+ pThis->AllocateFrame();
+ pThis->CallCallbackExc( SalEvent::Resize, nullptr );
+ if (bRealized)
+ pThis->TriggerPaintEvent();
+}
+
+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;
+}
+
+}
+
+void GtkSalFrame::signalRealize(GtkWidget*, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->AllocateFrame();
+ if (pThis->m_bSalObjectSetPosSize)
+ return;
+ pThis->TriggerPaintEvent();
+
+ 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)
+ {
+ 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->HasMirroredGraphics() && pVclParent->IsRTLEnabled())
+ {
+ swapDirection(rect_anchor);
+ swapDirection(menu_anchor);
+ }
+
+ tools::Rectangle aFloatRect = FloatingWindow::ImplConvertToAbsPos(pVclParent, pThis->m_aFloatRect);
+ if (gdk_window_get_window_type(gtk_widget_get_window(pThis->m_pParent->m_pWindow)) != GDK_WINDOW_TOPLEVEL)
+ aFloatRect.Move(-pThis->m_pParent->maGeometry.nX, -pThis->m_pParent->maGeometry.nY);
+
+ GdkRectangle rect {static_cast<int>(aFloatRect.Left()), static_cast<int>(aFloatRect.Top()),
+ static_cast<int>(aFloatRect.GetWidth()), static_cast<int>(aFloatRect.GetHeight())};
+
+ GdkWindow* gdkWindow = gtk_widget_get_window(pThis->m_pWindow);
+ window_move_to_rect(gdkWindow, &rect, rect_anchor, menu_anchor, static_cast<GdkAnchorHints>(GDK_ANCHOR_FLIP | GDK_ANCHOR_SLIDE), 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.nX || y != pThis->maGeometry.nY )
+ {
+ bMoved = true;
+ pThis->m_bGeometryIsProvisional = false;
+ pThis->maGeometry.nX = x;
+ pThis->maGeometry.nY = y;
+ }
+
+ // update decoration hints
+ GdkRectangle aRect;
+ gdk_window_get_frame_extents( gtk_widget_get_window(GTK_WIDGET(pThis->m_pWindow)), &aRect );
+ pThis->maGeometry.nTopDecoration = y - aRect.y;
+ pThis->maGeometry.nBottomDecoration = aRect.y + aRect.height - y - pEvent->height;
+ pThis->maGeometry.nLeftDecoration = x - aRect.x;
+ pThis->maGeometry.nRightDecoration = 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;
+}
+
+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.nWidth << "x" << maGeometry.nHeight);
+ SalPaintEvent aPaintEvt(0, 0, maGeometry.nWidth, maGeometry.nHeight, true);
+ CallCallbackExc(SalEvent::Paint, &aPaintEvt);
+ gtk_widget_queue_draw(GTK_WIDGET(m_pFixedContainer));
+}
+
+gboolean GtkSalFrame::signalFocus( GtkWidget*, GdkEventFocus* pEvent, gpointer frame )
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+
+ SalGenericInstance *pSalInstance =
+ static_cast< SalGenericInstance* >(GetSalData()->m_pInstance);
+
+ // check if printers have changed (analogous to salframe focus handler)
+ pSalInstance->updatePrinterUpdate();
+
+ if( !pEvent->in )
+ pThis->m_nKeyModifiers = ModKeyFlags::NONE;
+
+ if( pThis->m_pIMHandler )
+ 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;
+}
+
+void GtkSalFrame::signalSetFocus(GtkWindow*, GtkWidget* pWidget, gpointer frame)
+{
+ // do not propagate focus get/lose if floats are open
+ if (m_nFloats)
+ return;
+ // change of focus between native widgets within the toplevel
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ // tdf#129634 ignore floating toolbars
+ if (pThis->m_nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION)
+ return;
+
+ // tdf#129634 interpret losing focus as focus passing explicitly to another widget
+ bool bLoseFocus = pWidget && pWidget != GTK_WIDGET(pThis->m_pFixedContainer);
+ pThis->CallCallbackExc(bLoseFocus ? SalEvent::LoseFocus : SalEvent::GetFocus, nullptr);
+ gtk_widget_set_can_focus(GTK_WIDGET(pThis->m_pFixedContainer), !bLoseFocus);
+}
+
+gboolean GtkSalFrame::signalMap(GtkWidget *, GdkEvent*, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+
+ if (pThis->m_bIconSetWhileUnmapped)
+ pThis->SetIcon(gtk_window_get_icon_name(GTK_WINDOW(pThis->m_pWindow)));
+
+ pThis->CallCallbackExc( SalEvent::Resize, nullptr );
+ pThis->TriggerPaintEvent();
+
+ return false;
+}
+
+gboolean GtkSalFrame::signalUnmap( GtkWidget*, GdkEvent*, gpointer frame )
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+
+ pThis->CallCallbackExc( SalEvent::Resize, nullptr );
+
+ if (pThis->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(pThis->m_pWindow);
+ pThis->m_bFloatPositioned = false;
+ }
+
+ return false;
+}
+
+gboolean GtkSalFrame::signalKey(GtkWidget* pWidget, GdkEventKey* pEvent, gpointer frame)
+{
+ UpdateLastInputEventTime(pEvent->time);
+
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+
+ 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)
+ {
+ gpointer pClass = g_type_class_ref(GTK_TYPE_WINDOW);
+ GtkWidgetClass* pWindowClass = GTK_WIDGET_CLASS(pClass);
+ // if the focus is not in our main widget, see if there is a handler
+ // for this key stroke in GtkWindow first
+ bool bHandled = pEvent->type == GDK_KEY_PRESS
+ ? pWindowClass->key_press_event(pThis->m_pWindow, pEvent)
+ : pWindowClass->key_release_event(pThis->m_pWindow, pEvent);
+ g_type_class_unref(pClass);
+ if (bHandled)
+ return true;
+ }
+ }
+
+ 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
+ {
+ 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);
+ if( ! aDel.isDeleted() )
+ pThis->m_nKeyModifiers = ModKeyFlags::NONE;
+ }
+
+ if (!bFocusInAnotherGtkWidget && !aDel.isDeleted() && pThis->m_pIMHandler)
+ pThis->m_pIMHandler->updateIMSpotLocation();
+
+ return bStopProcessingKey;
+}
+
+gboolean GtkSalFrame::signalDelete( GtkWidget*, GdkEvent*, gpointer frame )
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->CallCallbackExc( SalEvent::Close, nullptr );
+ return true;
+}
+
+void GtkSalFrame::signalStyleUpdated(GtkWidget*, gpointer frame)
+{
+ 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 );
+
+ // fire off font-changed when the system cairo font hints change
+ GtkInstance *pInstance = static_cast<GtkInstance*>(GetSalData()->m_pInstance);
+ const cairo_font_options_t* pLastCairoFontOptions = pInstance->GetLastSeenCairoFontOptions();
+ const cairo_font_options_t* pCurrentCairoFontOptions = gdk_screen_get_font_options(gdk_screen_get_default());
+ 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 );
+ }
+}
+
+gboolean GtkSalFrame::signalWindowState( GtkWidget*, GdkEvent* pEvent, gpointer frame )
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if( (pThis->m_nState & GDK_WINDOW_STATE_ICONIFIED) != (pEvent->window_state.new_window_state & GDK_WINDOW_STATE_ICONIFIED ) )
+ {
+ GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::Resize );
+ pThis->TriggerPaintEvent();
+ }
+
+ if ((pEvent->window_state.new_window_state & GDK_WINDOW_STATE_MAXIMIZED) &&
+ !(pThis->m_nState & GDK_WINDOW_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;
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO_IF((pEvent->window_state.changed_mask &
+ GDK_WINDOW_STATE_FULLSCREEN),
+ "vcl.gtk3", "window "
+ << pThis
+ << " "
+ << ((pEvent->window_state.new_window_state &
+ GDK_WINDOW_STATE_FULLSCREEN) ?
+ "enters" :
+ "leaves")
+ << " full screen state.");
+#endif
+
+ return false;
+}
+
+gboolean GtkSalFrame::signalVisibility( GtkWidget*, GdkEventVisibility* /*pEvent*/, gpointer /*frame*/ )
+{
+ return true;
+}
+
+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 {
+
+class GtkDropTargetDropContext : public cppu::WeakImplHelper<css::datatransfer::dnd::XDropTargetDropContext>
+{
+ GdkDragContext *m_pContext;
+ guint m_nTime;
+public:
+ GtkDropTargetDropContext(GdkDragContext *pContext, guint nTime)
+ : m_pContext(pContext)
+ , m_nTime(nTime)
+ {
+ }
+
+ // XDropTargetDropContext
+ virtual void SAL_CALL acceptDrop(sal_Int8 dragOperation) override
+ {
+ gdk_drag_status(m_pContext, getPreferredDragAction(dragOperation), m_nTime);
+ }
+
+ virtual void SAL_CALL rejectDrop() override
+ {
+ gdk_drag_status(m_pContext, static_cast<GdkDragAction>(0), m_nTime);
+ }
+
+ virtual void SAL_CALL dropComplete(sal_Bool bSuccess) override
+ {
+ gtk_drag_finish(m_pContext, bSuccess, false, m_nTime);
+ if (GtkDragSource::g_ActiveDragSource)
+ {
+ g_DropSuccessSet = true;
+ g_DropSuccess = bSuccess;
+ }
+ }
+};
+
+}
+
+class GtkDnDTransferable : public GtkTransferable
+{
+ GdkDragContext *m_pContext;
+ guint m_nTime;
+ GtkWidget *m_pWidget;
+ GtkDropTarget* m_pDropTarget;
+ GMainLoop *m_pLoop;
+ GtkSelectionData *m_pData;
+public:
+ GtkDnDTransferable(GdkDragContext *pContext, guint nTime, GtkWidget *pWidget, GtkDropTarget *pDropTarget)
+ : m_pContext(pContext)
+ , m_nTime(nTime)
+ , m_pWidget(pWidget)
+ , m_pDropTarget(pDropTarget)
+ , m_pLoop(nullptr)
+ , m_pData(nullptr)
+ {
+ }
+
+ 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_aMimeTypeToAtom.find(aFlavor.MimeType);
+ if (it == m_aMimeTypeToAtom.end())
+ return css::uno::Any();
+
+ /* 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))
+ {
+ gdk_threads_leave();
+ g_main_loop_run(m_pLoop);
+ gdk_threads_enter();
+ }
+
+ g_main_loop_unref(m_pLoop);
+ m_pLoop = nullptr;
+ m_pDropTarget->SetFormatConversionRequest(nullptr);
+ }
+
+ css::uno::Any aRet;
+
+ 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 = OUString(pText, rtl_str_getLength(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);
+
+ return aRet;
+ }
+
+ virtual std::vector<css::datatransfer::DataFlavor> getTransferDataFlavorsAsVector() override
+ {
+ 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());
+ }
+
+ void LoopEnd(GtkSelectionData *pData)
+ {
+ m_pData = pData;
+ g_main_loop_quit(m_pLoop);
+ }
+};
+
+// For LibreOffice internal D&D we provide the Transferable without Gtk
+// intermediaries as a shortcut, see tdf#100097 for how dbaccess depends on this
+GtkDragSource* GtkDragSource::g_ActiveDragSource;
+
+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);
+}
+
+gboolean GtkDropTarget::signalDragDrop(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, guint time)
+{
+ // remove the deferred dragExit, as we'll do a drop
+#ifndef NDEBUG
+ bool res =
+#endif
+ g_idle_remove_by_data(this);
+ assert(res);
+
+ css::datatransfer::dnd::DropTargetDropEvent aEvent;
+ aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(this);
+ aEvent.Context = new GtkDropTargetDropContext(context, time);
+ aEvent.LocationX = x;
+ aEvent.LocationY = y;
+ aEvent.DropAction = GdkToVcl(gdk_drag_context_get_selected_action(context));
+ // 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
+ GdkModifierType mask;
+ gdk_window_get_pointer(gtk_widget_get_window(pWidget), nullptr, nullptr, &mask);
+ if (!(mask & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)))
+ aEvent.DropAction |= css::datatransfer::dnd::DNDConstants::ACTION_DEFAULT;
+ aEvent.SourceActions = GdkToVcl(gdk_drag_context_get_actions(context));
+ 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 (GtkDragSource::g_ActiveDragSource)
+ xTransferable = GtkDragSource::g_ActiveDragSource->GetTransferrable();
+ else
+ xTransferable = new GtkDnDTransferable(context, time, pWidget, this);
+ aEvent.Transferable = xTransferable;
+
+ fire_drop(aEvent);
+
+ return true;
+}
+
+namespace {
+
+class GtkDropTargetDragContext : public cppu::WeakImplHelper<css::datatransfer::dnd::XDropTargetDragContext>
+{
+ GdkDragContext *m_pContext;
+ guint m_nTime;
+public:
+ GtkDropTargetDragContext(GdkDragContext *pContext, guint nTime)
+ : m_pContext(pContext)
+ , m_nTime(nTime)
+ {
+ }
+
+ virtual void SAL_CALL acceptDrag(sal_Int8 dragOperation) override
+ {
+ gdk_drag_status(m_pContext, getPreferredDragAction(dragOperation), m_nTime);
+ }
+
+ virtual void SAL_CALL rejectDrag() override
+ {
+ gdk_drag_status(m_pContext, static_cast<GdkDragAction>(0), m_nTime);
+ }
+};
+
+}
+
+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 GtkDropTarget::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));
+}
+
+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);
+}
+
+
+gboolean GtkDropTarget::signalDragMotion(GtkWidget *pWidget, GdkDragContext *context, gint x, gint y, guint time)
+{
+ if (!m_bInDrag)
+ gtk_drag_highlight(pWidget);
+
+ css::datatransfer::dnd::DropTargetDragEnterEvent aEvent;
+ aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(this);
+ GtkDropTargetDragContext* pContext = new GtkDropTargetDragContext(context, time);
+ //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
+ sal_Int8 nSourceActions = GdkToVcl(gdk_drag_context_get_actions(context));
+ GdkModifierType mask;
+ gdk_window_get_pointer(gtk_widget_get_window(pWidget), nullptr, nullptr, &mask);
+
+ // 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 = GtkDragSource::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);
+
+ gdk_drag_status(context, eAction, time);
+ 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 (GtkDragSource::g_ActiveDragSource)
+ xTransferable = GtkDragSource::g_ActiveDragSource->GetTransferrable();
+ else
+ xTransferable = new GtkDnDTransferable(context, time, pWidget, this);
+ css::uno::Sequence<css::datatransfer::DataFlavor> aFormats = xTransferable->getTransferDataFlavors();
+ aEvent.SupportedDataFlavors = aFormats;
+ fire_dragEnter(aEvent);
+ m_bInDrag = true;
+ }
+ else
+ {
+ fire_dragOver(aEvent);
+ }
+
+ return true;
+}
+
+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, context, time);
+}
+
+static gboolean lcl_deferred_dragExit(gpointer user_data)
+{
+ GtkDropTarget* pThis = static_cast<GtkDropTarget*>(user_data);
+ css::datatransfer::dnd::DropTargetEvent aEvent;
+ aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(pThis);
+ pThis->fire_dragExit(aEvent);
+ return FALSE;
+}
+
+void GtkDropTarget::signalDragLeave(GtkWidget* pWidget, GdkDragContext* /*context*/, guint /*time*/)
+{
+ m_bInDrag = false;
+ gtk_drag_unhighlight(pWidget);
+ // 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 )
+ {
+ 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_pEventBox = nullptr;
+ 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();
+ gtk_im_context_set_client_window(m_pIMContext, gtk_widget_get_window(m_pFrame->getMouseEventWidget()));
+ gtk_im_context_focus_in( m_pIMContext );
+ GetGenericUnixSalData()->ErrorTrapPop();
+ m_bFocused = true;
+
+}
+
+void GtkSalFrame::IMHandler::deleteIMContext()
+{
+ if( m_pIMContext )
+ {
+ // first give IC a chance to deinitialize
+ GetGenericUnixSalData()->ErrorTrapPush();
+ gtk_im_context_set_client_window( 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 )
+ {
+ 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 );
+ }
+}
+
+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();
+ }
+ }
+}
+
+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:
+ sal_attr |= ExtTextInputAttr::Underline;
+ break;
+ case PANGO_ATTR_STRIKETHROUGH:
+ sal_attr |= ExtTextInputAttr::RedText;
+ break;
+ default:
+ break;
+ }
+ pango_attribute_destroy (pango_attr);
+ tmp_list = tmp_list->next;
+ }
+ if (sal_attr == ExtTextInputAttr::NONE)
+ 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();
+}
+
+static 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;
+}
+
+gboolean GtkSalFrame::IMHandler::signalIMRetrieveSurrounding( GtkIMContext* pContext, gpointer /*im_handler*/ )
+{
+ vcl::Window *pFocusWin = Application::GetFocusWindow();
+ if (!pFocusWin)
+ return true;
+
+ uno::Reference<accessibility::XAccessibleEditableText> xText = lcl_GetxText(pFocusWin);
+ if (xText.is())
+ {
+ sal_Int32 nPosition = xText->getCaretPosition();
+ if (nPosition != -1)
+ {
+ OUString sAllText = xText->getText();
+ OString sUTF = OUStringToOString(sAllText, RTL_TEXTENCODING_UTF8);
+ OUString sCursorText(sAllText.copy(0, nPosition));
+ gtk_im_context_set_surrounding(pContext, sUTF.getStr(), sUTF.getLength(),
+ OUStringToOString(sCursorText, RTL_TEXTENCODING_UTF8).getLength());
+ return true;
+ }
+ }
+
+ return false;
+}
+
+gboolean GtkSalFrame::IMHandler::signalIMDeleteSurrounding( GtkIMContext*, gint offset, gint nchars,
+ gpointer /*im_handler*/ )
+{
+ vcl::Window *pFocusWin = Application::GetFocusWindow();
+ if (!pFocusWin)
+ return true;
+
+ uno::Reference<accessibility::XAccessibleEditableText> xText = lcl_GetxText(pFocusWin);
+ if (xText.is())
+ {
+ sal_Int32 nPosition = xText->getCaretPosition();
+ // #i111768# range checking
+ sal_Int32 nDeletePos = nPosition + offset;
+ sal_Int32 nDeleteEnd = nDeletePos + nchars;
+ 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;
+}
+
+Size GtkSalDisplay::GetScreenSize( int nDisplayScreen )
+{
+ tools::Rectangle aRect = m_pSys->GetDisplayScreenPosSizePixel( nDisplayScreen );
+ return Size( aRect.GetWidth(), aRect.GetHeight() );
+}
+
+sal_uIntPtr GtkSalFrame::GetNativeWindowHandle(GtkWidget *pWidget)
+{
+ (void) this; // Silence loplugin:staticmethods
+ GdkDisplay *pDisplay = getGdkDisplay();
+ GdkWindow *pWindow = gtk_widget_get_window(pWidget);
+
+#if defined(GDK_WINDOWING_X11)
+ if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
+ {
+ return GDK_WINDOW_XID(pWindow);
+ }
+#endif
+#if defined(GDK_WINDOWING_WAYLAND)
+ if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay))
+ {
+ return reinterpret_cast<sal_uIntPtr>(gdk_wayland_window_get_wl_surface(pWindow));
+ }
+#endif
+ return 0;
+}
+
+sal_uIntPtr GtkSalFrame::GetNativeWindowHandle()
+{
+ return GetNativeWindowHandle(m_pWindow);
+}
+
+void GtkDragSource::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 GtkDragSource::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;
+}
+
+std::vector<GtkTargetEntry> GtkDragSource::FormatsToGtk(const css::uno::Sequence<css::datatransfer::DataFlavor> &rFormats)
+{
+ return m_aConversionHelper.FormatsToGtk(rFormats);
+}
+
+void GtkDragSource::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)
+ {
+ auto aFormats = m_xTrans->getTransferDataFlavors();
+ std::vector<GtkTargetEntry> aGtkTargets(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;
+ }
+
+ setActiveDragSource();
+
+ m_pFrame->startDrag(nDragButton, rEvent.DragOriginX, rEvent.DragOriginY,
+ VclToGdk(sourceActions), pTargetList);
+
+ gtk_target_list_unref(pTargetList);
+ for (auto &a : aGtkTargets)
+ g_free(a.target);
+ }
+ else
+ dragFailed();
+}
+
+void GtkSalFrame::startDrag(gint nButton, gint nDragOriginX, gint nDragOriginY,
+ GdkDragAction sourceActions, GtkTargetList* pTargetList)
+{
+ SolarMutexGuard aGuard;
+
+ assert(m_pDragSource);
+
+ GdkEvent aFakeEvent;
+ memset(&aFakeEvent, 0, sizeof(GdkEvent));
+ aFakeEvent.type = GDK_BUTTON_PRESS;
+ aFakeEvent.button.window = gtk_widget_get_window(getMouseEventWidget());
+ aFakeEvent.button.time = GDK_CURRENT_TIME;
+ GdkDeviceManager* pDeviceManager = gdk_display_get_device_manager(getGdkDisplay());
+ aFakeEvent.button.device = gdk_device_manager_get_client_pointer(pDeviceManager);
+
+ GdkDragContext *pContext = gtk_drag_begin_with_coordinates(getMouseEventWidget(),
+ pTargetList,
+ sourceActions,
+ nButton,
+ &aFakeEvent,
+ nDragOriginX,
+ nDragOriginY);
+
+ if (!pContext)
+ m_pDragSource->dragFailed();
+}
+
+void GtkDragSource::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);
+ }
+}
+
+gboolean GtkSalFrame::signalDragFailed(GtkWidget* /*widget*/, GdkDragContext* /*context*/, GtkDragResult /*result*/, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if (!pThis->m_pDragSource)
+ return false;
+ pThis->m_pDragSource->dragFailed();
+ return false;
+}
+
+void GtkDragSource::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);
+ }
+}
+
+void GtkSalFrame::signalDragDelete(GtkWidget* /*widget*/, GdkDragContext* /*context*/, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if (!pThis->m_pDragSource)
+ return;
+ pThis->m_pDragSource->dragDelete();
+}
+
+void GtkDragSource::dragEnd(GdkDragContext* context)
+{
+ if (m_xListener.is())
+ {
+ datatransfer::dnd::DragSourceDropEvent aEv;
+ aEv.DropAction = GdkToVcl(gdk_drag_context_get_selected_action(context));
+ // 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;
+}
+
+void GtkSalFrame::signalDragEnd(GtkWidget* /*widget*/, GdkDragContext* context, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if (!pThis->m_pDragSource)
+ return;
+ pThis->m_pDragSource->dragEnd(context);
+}
+
+void GtkDragSource::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);
+}
+
+bool GtkSalFrame::CallCallbackExc(SalEvent nEvent, const void* pEvent) const
+{
+ bool nRet = false;
+ try
+ {
+ nRet = CallCallback(nEvent, pEvent);
+ }
+ catch (...)
+ {
+ GtkSalData *pSalData = static_cast<GtkSalData*>(GetSalData());
+ pSalData->setException(std::current_exception());
+ }
+ return nRet;
+}
+
+void GtkSalFrame::nopaint_container_resize_children(GtkContainer *pContainer)
+{
+ bool bOrigSalObjectSetPosSize = m_bSalObjectSetPosSize;
+ m_bSalObjectSetPosSize = true;
+ gtk_container_resize_children(pContainer);
+ m_bSalObjectSetPosSize = bOrigSalObjectSetPosSize;
+}
+
+GdkEvent* GtkSalFrame::makeFakeKeyPress(GtkWidget* pWidget)
+{
+ GdkEvent *event = gdk_event_new(GDK_KEY_PRESS);
+ event->key.window = GDK_WINDOW(g_object_ref(gtk_widget_get_window(pWidget)));
+
+#if GTK_CHECK_VERSION(3, 20, 0)
+ if (gtk_check_version(3, 20, 0) == nullptr)
+ {
+ GdkSeat *seat = gdk_display_get_default_seat(gtk_widget_get_display(pWidget));
+ gdk_event_set_device(event, gdk_seat_get_keyboard(seat));
+ }
+#endif
+
+ 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;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtk3gtkinst.cxx b/vcl/unx/gtk3/gtk3gtkinst.cxx
new file mode 100644
index 000000000..0290359be
--- /dev/null
+++ b/vcl/unx/gtk3/gtk3gtkinst.cxx
@@ -0,0 +1,16272 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 <stack>
+#include <string.h>
+#include <osl/process.h>
+#include <unx/gtk/gtkdata.hxx>
+#include <unx/gtk/gtkinst.hxx>
+#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/gtkprn.hxx>
+#include <unx/gtk/gtksalmenu.hxx>
+#include <headless/svpvd.hxx>
+#include <headless/svpbmp.hxx>
+#include <vcl/inputtypes.hxx>
+#include <vcl/transfer.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>
+
+#include <unx/gtk/gtkprintwrapper.hxx>
+
+#include "a11y/atkwrapper.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/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 <comphelper/lok.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/sequence.hxx>
+#include <cppuhelper/compbase.hxx>
+#include <comphelper/string.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <rtl/bootstrap.hxx>
+#include <o3tl/unreachable.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 <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/pngwrite.hxx>
+#include <vcl/stdtext.hxx>
+#include <vcl/syswin.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/weld.hxx>
+#include <vcl/wrkwin.hxx>
+#include <strings.hrc>
+#include <window.h>
+#include <numeric>
+
+#include <boost/property_tree/ptree.hpp>
+
+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*>(GetSalData()->m_pInstance->GetYieldMutex())
+ static void GdkThreadsEnter()
+ {
+ GtkYieldMutex *pYieldMutex = GET_YIELD_MUTEX();
+ pYieldMutex->ThreadsEnter();
+ }
+ static void GdkThreadsLeave()
+ {
+ GtkYieldMutex *pYieldMutex = GET_YIELD_MUTEX();
+ pYieldMutex->ThreadsLeave();
+ }
+
+ VCLPLUG_GTK_PUBLIC SalInstance* create_SalInstance()
+ {
+ SAL_INFO(
+ "vcl.gtk",
+ "create vcl plugin instance with gtk version " << gtk_major_version
+ << " " << gtk_minor_version << " " << gtk_micro_version);
+
+ if (gtk_major_version == 3 && gtk_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
+
+ // 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");
+
+ auto pYieldMutex = std::make_unique<GtkYieldMutex>();
+
+ gdk_threads_init();
+
+ GtkInstance* pInstance = new GtkInstance( std::move(pYieldMutex) );
+ SAL_INFO("vcl.gtk", "creating GtkInstance " << pInstance);
+
+ // Create SalData, this does not leak
+ new GtkSalData( pInstance );
+
+ return pInstance;
+ }
+}
+
+static VclInputFlags categorizeEvent(const GdkEvent *pEvent)
+{
+ VclInputFlags nType = VclInputFlags::NONE;
+ switch( pEvent->type )
+ {
+ case GDK_MOTION_NOTIFY:
+ case GDK_BUTTON_PRESS:
+ case GDK_2BUTTON_PRESS:
+ case GDK_3BUTTON_PRESS:
+ 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;
+ case GDK_EXPOSE:
+ nType = VclInputFlags::PAINT;
+ break;
+ default:
+ nType = VclInputFlags::OTHER;
+ break;
+ }
+ return nType;
+}
+
+GtkInstance::GtkInstance( std::unique_ptr<SalYieldMutex> pMutex )
+ : SvpSalInstance( std::move(pMutex) )
+ , m_pTimer(nullptr)
+ , bNeedsInit(true)
+ , m_pLastCairoFontOptions(nullptr)
+{
+}
+
+//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();
+
+ InitAtkBridge();
+
+ 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 );
+ DeInitAtkBridge();
+ 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 GtkSalInfoPrinter;
+ configurePspInfoPrinter(pPrinter, pQueueInfo, pSetupData);
+ return pPrinter;
+}
+
+std::unique_ptr<SalPrinter> GtkInstance::CreatePrinter( SalInfoPrinter* pInfoPrinter )
+{
+ EnsureInit();
+ mbPrinterInit = true;
+ return std::unique_ptr<SalPrinter>(new GtkSalPrinter( 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()) {
+ auto n = yieldCounts.top();
+ yieldCounts.pop();
+ assert(n > 0);
+ n--;
+ if (n > 0)
+ acquire(n);
+ }
+}
+
+void GtkYieldMutex::ThreadsLeave()
+{
+ assert(m_nCount != 0);
+ yieldCounts.push(m_nCount);
+ release(true);
+}
+
+std::unique_ptr<SalVirtualDevice> GtkInstance::CreateVirtualDevice( SalGraphics *pG,
+ long &nDX, long &nDY,
+ DeviceFormat eFormat,
+ const SystemGraphicsData* pGd )
+{
+ EnsureInit();
+ SvpSalGraphics *pSvpSalGraphics = dynamic_cast<SvpSalGraphics*>(pG);
+ 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> pNew(new SvpSalVirtualDevice(eFormat, pSvpSalGraphics->getSurface(), pPreExistingTarget));
+ pNew->SetSize( nDX, nDY );
+ return pNew;
+}
+
+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());
+}
+
+bool GtkInstance::AnyInput( VclInputFlags nType )
+{
+ EnsureInit();
+ if( (nType & VclInputFlags::TIMER) && IsTimerExpired() )
+ return true;
+ if (!gdk_events_pending())
+ return false;
+
+ if (nType == VCL_INPUT_ANY)
+ return true;
+
+ bool bRet = false;
+ std::deque<GdkEvent*> aEvents;
+ GdkEvent *pEvent = nullptr;
+ while ((pEvent = gdk_event_get()))
+ {
+ 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_event_put(pEvent);
+ gdk_event_free(pEvent);
+ aEvents.pop_front();
+ }
+ return bRet;
+}
+
+std::unique_ptr<GenPspGraphics> GtkInstance::CreatePrintGraphics()
+{
+ EnsureInit();
+ return std::make_unique<GenPspGraphics>();
+}
+
+std::shared_ptr<vcl::unx::GtkPrintWrapper> const &
+GtkInstance::getPrintWrapper() const
+{
+ if (!m_xPrintWrapper)
+ m_xPrintWrapper = std::make_shared<vcl::unx::GtkPrintWrapper>();
+ return m_xPrintWrapper;
+}
+
+const cairo_font_options_t* GtkInstance::GetCairoFontOptions()
+{
+ const cairo_font_options_t* pCairoFontOptions = gdk_screen_get_font_options(gdk_screen_get_default());
+ 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
+ };
+
+ static 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;
+ }
+ };
+}
+
+std::vector<css::datatransfer::DataFlavor> GtkTransferable::getTransferDataFlavorsAsVector(GdkAtom *targets, gint n_targets)
+{
+ std::vector<css::datatransfer::DataFlavor> aVector;
+
+ bool bHaveText = false, bHaveUTF16 = false;
+
+ for (gint i = 0; i < n_targets; ++i)
+ {
+ gchar* pName = gdk_atom_name(targets[i]);
+ 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)
+ {
+ g_free(pName);
+ 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)
+ {
+ g_free(pName);
+ continue;
+ }
+
+ aFlavor.MimeType = OUString(pFinalName,
+ strlen(pFinalName),
+ RTL_TEXTENCODING_UTF8);
+
+ m_aMimeTypeToAtom[aFlavor.MimeType] = targets[i];
+
+ aFlavor.DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
+
+ sal_Int32 nIndex(0);
+ if (aFlavor.MimeType.getToken(0, ';', nIndex) == "text/plain")
+ {
+ bHaveText = true;
+ OUString aToken(aFlavor.MimeType.getToken(0, ';', nIndex));
+ if (aToken == "charset=utf-16")
+ {
+ bHaveUTF16 = true;
+ aFlavor.DataType = cppu::UnoType<OUString>::get();
+ }
+ }
+ aVector.push_back(aFlavor);
+ g_free(pName);
+ }
+
+ //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));
+}
+
+namespace {
+
+class GtkClipboardTransferable : public GtkTransferable
+{
+private:
+ GdkAtom m_nSelection;
+public:
+
+ explicit GtkClipboardTransferable(GdkAtom nSelection)
+ : m_nSelection(nSelection)
+ {
+ }
+
+ /*
+ * XTransferable
+ */
+
+ virtual css::uno::Any SAL_CALL getTransferData(const css::datatransfer::DataFlavor& rFlavor) override
+ {
+ GtkClipboard* clipboard = gtk_clipboard_get(m_nSelection);
+ if (rFlavor.MimeType == "text/plain;charset=utf-16")
+ {
+ OUString aStr;
+ gchar *pText = gtk_clipboard_wait_for_text(clipboard);
+ if (pText)
+ aStr = OUString(pText, strlen(pText), RTL_TEXTENCODING_UTF8);
+ g_free(pText);
+ css::uno::Any aRet;
+ aRet <<= aStr.replaceAll("\r\n", "\n");
+ return aRet;
+ }
+
+ auto it = m_aMimeTypeToAtom.find(rFlavor.MimeType);
+ if (it == m_aMimeTypeToAtom.end())
+ return css::uno::Any();
+
+ css::uno::Any aRet;
+ 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;
+ return aRet;
+ }
+
+ std::vector<css::datatransfer::DataFlavor> getTransferDataFlavorsAsVector()
+ override
+ {
+ std::vector<css::datatransfer::DataFlavor> aVector;
+
+ GtkClipboard* clipboard = gtk_clipboard_get(m_nSelection);
+
+ GdkAtom *targets;
+ gint n_targets;
+ if (gtk_clipboard_wait_for_targets(clipboard, &targets, &n_targets))
+ {
+ aVector = GtkTransferable::getTransferDataFlavorsAsVector(targets, n_targets);
+ g_free(targets);
+ }
+
+ return aVector;
+ }
+};
+
+class VclGtkClipboard :
+ public cppu::WeakComponentImplHelper<
+ datatransfer::clipboard::XSystemClipboard,
+ datatransfer::clipboard::XFlushableClipboard,
+ XServiceInfo>
+{
+ GdkAtom m_nSelection;
+ 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;
+ std::vector<GtkTargetEntry> m_aGtkTargets;
+ VclToGtkHelper m_aConversionHelper;
+
+ DECL_LINK(AsyncSetGtkClipboard, void*, void);
+public:
+
+ explicit VclGtkClipboard(GdkAtom nSelection);
+ 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;
+
+ void ClipboardGet(GtkSelectionData *selection_data, guint info);
+ void ClipboardClear();
+ void OwnerPossiblyChanged(GtkClipboard *clipboard);
+ 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_nSelection);
+ }
+
+ return m_aContents;
+}
+
+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;
+ }
+}
+
+namespace
+{
+ void ClipboardGetFunc(GtkClipboard* /*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(GtkClipboard* /*clipboard*/, gpointer user_data_or_owner)
+ {
+ VclGtkClipboard* pThis = static_cast<VclGtkClipboard*>(user_data_or_owner);
+ pThis->ClipboardClear();
+ }
+
+ void handle_owner_change(GtkClipboard *clipboard, GdkEvent* /*event*/, gpointer user_data)
+ {
+ VclGtkClipboard* pThis = static_cast<VclGtkClipboard*>(user_data);
+ pThis->OwnerPossiblyChanged(clipboard);
+ }
+}
+
+void VclGtkClipboard::OwnerPossiblyChanged(GtkClipboard* clipboard)
+{
+ SyncGtkClipboard(); // tdf#138183 do any pending SetGtkClipboard calls
+ if (!m_aContents.is())
+ return;
+
+ //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);
+
+ 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;
+ }
+ for (auto &a : m_aGtkTargets)
+ g_free(a.target);
+ m_aGtkTargets.clear();
+}
+
+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;
+}
+
+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());
+}
+
+VclGtkClipboard::VclGtkClipboard(GdkAtom nSelection)
+ : cppu::WeakComponentImplHelper<datatransfer::clipboard::XSystemClipboard,
+ datatransfer::clipboard::XFlushableClipboard, XServiceInfo>
+ (m_aMutex)
+ , m_nSelection(nSelection)
+ , m_pSetClipboardEvent(nullptr)
+{
+ GtkClipboard* clipboard = gtk_clipboard_get(m_nSelection);
+ m_nOwnerChangedSignalId = g_signal_connect(clipboard, "owner-change",
+ G_CALLBACK(handle_owner_change), this);
+}
+
+void VclGtkClipboard::flushClipboard()
+{
+ SolarMutexGuard aGuard;
+
+ if (GDK_SELECTION_CLIPBOARD != m_nSelection)
+ return;
+
+ GtkClipboard* clipboard = gtk_clipboard_get(m_nSelection);
+ gtk_clipboard_store(clipboard);
+}
+
+VclGtkClipboard::~VclGtkClipboard()
+{
+ GtkClipboard* clipboard = gtk_clipboard_get(m_nSelection);
+ g_signal_handler_disconnect(clipboard, m_nOwnerChangedSignalId);
+ if (!m_aGtkTargets.empty())
+ {
+ gtk_clipboard_clear(clipboard);
+ ClipboardClear();
+ }
+ assert(!m_pSetClipboardEvent);
+ assert(m_aGtkTargets.empty());
+}
+
+std::vector<GtkTargetEntry> VclToGtkHelper::FormatsToGtk(const css::uno::Sequence<css::datatransfer::DataFlavor> &rFormats)
+{
+ std::vector<GtkTargetEntry> aGtkTargets;
+
+ bool bHaveText(false), bHaveUTF8(false);
+ for (const css::datatransfer::DataFlavor& rFlavor : rFormats)
+ {
+ sal_Int32 nIndex(0);
+ if (rFlavor.MimeType.getToken(0, ';', nIndex) == "text/plain")
+ {
+ bHaveText = true;
+ OUString aToken(rFlavor.MimeType.getToken(0, ';', nIndex));
+ if (aToken == "charset=utf-8")
+ {
+ bHaveUTF8 = true;
+ }
+ }
+ GtkTargetEntry aEntry(makeGtkTargetEntry(rFlavor));
+ aGtkTargets.push_back(aEntry);
+ }
+
+ 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::ClearableMutexGuard aGuard( m_aMutex );
+ m_pSetClipboardEvent = nullptr;
+ SetGtkClipboard();
+}
+
+void VclGtkClipboard::SyncGtkClipboard()
+{
+ osl::ClearableMutexGuard aGuard(m_aMutex);
+ if (m_pSetClipboardEvent)
+ {
+ Application::RemoveUserEvent(m_pSetClipboardEvent);
+ m_pSetClipboardEvent = nullptr;
+ SetGtkClipboard();
+ }
+}
+
+void VclGtkClipboard::SetGtkClipboard()
+{
+ GtkClipboard* clipboard = gtk_clipboard_get(m_nSelection);
+ 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());
+}
+
+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;
+ m_aOwner = xClipboardOwner;
+
+ std::vector< Reference< datatransfer::clipboard::XClipboardListener > > aListeners( m_aListeners );
+ datatransfer::clipboard::ClipboardEvent aEv;
+
+ GtkClipboard* clipboard = gtk_clipboard_get(m_nSelection);
+ if (!m_aGtkTargets.empty())
+ {
+ gtk_clipboard_clear(clipboard);
+ ClipboardClear();
+ }
+ assert(m_aGtkTargets.empty());
+ if (m_aContents.is())
+ {
+ std::vector<GtkTargetEntry> aGtkTargets(m_aConversionHelper.FormatsToGtk(aFormats));
+ if (!aGtkTargets.empty())
+ {
+ 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);
+
+ 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_nSelection == GDK_SELECTION_CLIPBOARD) ? OUString("CLIPBOARD") : OUString("PRIMARY");
+}
+
+sal_Int8 VclGtkClipboard::getRenderingCapabilities()
+{
+ return 0;
+}
+
+void VclGtkClipboard::addClipboardListener( const Reference< datatransfer::clipboard::XClipboardListener >& listener )
+{
+ osl::ClearableMutexGuard aGuard( m_aMutex );
+
+ m_aListeners.push_back( listener );
+}
+
+void VclGtkClipboard::removeClipboardListener( const Reference< datatransfer::clipboard::XClipboardListener >& listener )
+{
+ osl::ClearableMutexGuard aGuard( m_aMutex );
+
+ m_aListeners.erase(std::remove(m_aListeners.begin(), m_aListeners.end(), listener), m_aListeners.end());
+}
+
+Reference< XInterface > GtkInstance::CreateClipboard(const Sequence< Any >& 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);
+ }
+
+ GdkAtom nSelection = (sel == "CLIPBOARD") ? GDK_SELECTION_CLIPBOARD : GDK_SELECTION_PRIMARY;
+
+ auto it = m_aClipboards.find(nSelection);
+ if (it != m_aClipboards.end())
+ return it->second;
+
+ Reference<XInterface> xClipboard(static_cast<cppu::OWeakObject *>(new VclGtkClipboard(nSelection)));
+ m_aClipboards[nSelection] = xClipboard;
+
+ return xClipboard;
+}
+
+GtkDropTarget::GtkDropTarget()
+ : WeakComponentImplHelper(m_aMutex)
+ , m_pFrame(nullptr)
+ , m_pFormatConversionRequest(nullptr)
+ , m_bActive(false)
+ , m_bInDrag(false)
+ , m_nDefaultActions(0)
+{
+}
+
+OUString SAL_CALL GtkDropTarget::getImplementationName()
+{
+ return "com.sun.star.datatransfer.dnd.VclGtkDropTarget";
+}
+
+sal_Bool SAL_CALL GtkDropTarget::supportsService(OUString const & ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence<OUString> SAL_CALL GtkDropTarget::getSupportedServiceNames()
+{
+ Sequence<OUString> aRet { "com.sun.star.datatransfer.dnd.GtkDropTarget" };
+ return aRet;
+}
+
+GtkDropTarget::~GtkDropTarget()
+{
+ if (m_pFrame)
+ m_pFrame->deregisterDropTarget(this);
+}
+
+void GtkDropTarget::deinitialize()
+{
+ m_pFrame = nullptr;
+ m_bActive = false;
+}
+
+void GtkDropTarget::initialize(const Sequence<Any>& rArguments)
+{
+ if (rArguments.getLength() < 2)
+ {
+ throw RuntimeException("DropTarget::initialize: Cannot install window event handler",
+ static_cast<OWeakObject*>(this));
+ }
+
+ sal_IntPtr nFrame = 0;
+ rArguments.getConstArray()[1] >>= nFrame;
+
+ if (!nFrame)
+ {
+ throw RuntimeException("DropTarget::initialize: missing SalFrame",
+ static_cast<OWeakObject*>(this));
+ }
+
+ m_pFrame = reinterpret_cast<GtkSalFrame*>(nFrame);
+ m_pFrame->registerDropTarget(this);
+ m_bActive = true;
+}
+
+void GtkDropTarget::addDropTargetListener( const Reference< css::datatransfer::dnd::XDropTargetListener >& xListener)
+{
+ ::osl::Guard< ::osl::Mutex > aGuard( m_aMutex );
+
+ m_aListeners.push_back( xListener );
+}
+
+void GtkDropTarget::removeDropTargetListener( const Reference< css::datatransfer::dnd::XDropTargetListener >& xListener)
+{
+ ::osl::Guard< ::osl::Mutex > aGuard( m_aMutex );
+
+ m_aListeners.erase(std::remove(m_aListeners.begin(), m_aListeners.end(), xListener), m_aListeners.end());
+}
+
+void GtkDropTarget::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 GtkDropTarget::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 GtkDropTarget::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 GtkDropTarget::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 GtkDropTarget::isActive()
+{
+ return m_bActive;
+}
+
+void GtkDropTarget::setActive(sal_Bool bActive)
+{
+ m_bActive = bActive;
+}
+
+sal_Int8 GtkDropTarget::getDefaultActions()
+{
+ return m_nDefaultActions;
+}
+
+void GtkDropTarget::setDefaultActions(sal_Int8 nDefaultActions)
+{
+ m_nDefaultActions = nDefaultActions;
+}
+
+Reference< XInterface > GtkInstance::CreateDropTarget()
+{
+ return Reference<XInterface>(static_cast<cppu::OWeakObject*>(new GtkDropTarget));
+}
+
+GtkDragSource::~GtkDragSource()
+{
+ if (m_pFrame)
+ m_pFrame->deregisterDragSource(this);
+
+ if (GtkDragSource::g_ActiveDragSource == this)
+ {
+ SAL_WARN( "vcl.gtk", "dragEnd should have been called on GtkDragSource before dtor");
+ GtkDragSource::g_ActiveDragSource = nullptr;
+ }
+}
+
+void GtkDragSource::deinitialize()
+{
+ m_pFrame = nullptr;
+}
+
+sal_Bool GtkDragSource::isDragImageSupported()
+{
+ return true;
+}
+
+sal_Int32 GtkDragSource::getDefaultCursor( sal_Int8 )
+{
+ return 0;
+}
+
+void GtkDragSource::initialize(const css::uno::Sequence<css::uno::Any >& rArguments)
+{
+ if (rArguments.getLength() < 2)
+ {
+ throw RuntimeException("DragSource::initialize: Cannot install window event handler",
+ static_cast<OWeakObject*>(this));
+ }
+
+ sal_IntPtr nFrame = 0;
+ rArguments.getConstArray()[1] >>= nFrame;
+
+ if (!nFrame)
+ {
+ throw RuntimeException("DragSource::initialize: missing SalFrame",
+ static_cast<OWeakObject*>(this));
+ }
+
+ m_pFrame = reinterpret_cast<GtkSalFrame*>(nFrame);
+ m_pFrame->registerDragSource(this);
+}
+
+OUString SAL_CALL GtkDragSource::getImplementationName()
+{
+ return "com.sun.star.datatransfer.dnd.VclGtkDragSource";
+}
+
+sal_Bool SAL_CALL GtkDragSource::supportsService(OUString const & ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence<OUString> SAL_CALL GtkDragSource::getSupportedServiceNames()
+{
+ Sequence<OUString> aRet { "com.sun.star.datatransfer.dnd.GtkDragSource" };
+ return aRet;
+}
+
+Reference< XInterface > GtkInstance::CreateDragSource()
+{
+ return Reference< XInterface >( static_cast<cppu::OWeakObject *>(new GtkDragSource()) );
+}
+
+namespace {
+
+class GtkOpenGLContext : public OpenGLContext
+{
+ GLWindow m_aGLWin;
+ GtkWidget *m_pGLArea;
+ GdkGLContext *m_pContext;
+ guint m_nAreaFrameBuffer;
+ guint m_nFrameBuffer;
+ guint m_nRenderBuffer;
+ guint m_nDepthBuffer;
+ guint m_nFrameScratchBuffer;
+ guint m_nRenderScratchBuffer;
+ guint m_nDepthScratchBuffer;
+
+public:
+ GtkOpenGLContext()
+ : OpenGLContext()
+ , m_pGLArea(nullptr)
+ , m_pContext(nullptr)
+ , 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;
+ }
+
+ 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);
+ }
+
+ virtual bool ImplInit() override
+ {
+ const SystemEnvData* pEnvData = m_pChildWindow->GetSystemData();
+ GtkWidget *pParent = static_cast<GtkWidget*>(pEnvData->pWidget);
+ m_pGLArea = gtk_gl_area_new();
+ g_signal_connect(G_OBJECT(m_pGLArea), "destroy", G_CALLBACK(signalDestroy), this);
+ 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);
+ gtk_container_add(GTK_CONTAINER(pParent), m_pGLArea);
+ gtk_widget_show_all(pParent);
+
+ 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);
+
+ GdkWindow *pWindow = gtk_widget_get_window(pParent);
+ m_pContext = gdk_window_create_gl_context(pWindow, nullptr);
+ 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_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;
+ return G_TYPE_CHECK_INSTANCE_TYPE(pDisplay, get_type());
+}
+
+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;
+ return G_TYPE_CHECK_INSTANCE_TYPE(pDisplay, get_type());
+}
+
+namespace
+{
+
+class GtkInstanceBuilder;
+
+ void set_help_id(const GtkWidget *pWidget, const OString& rHelpId)
+ {
+ gchar *helpid = g_strdup(rHelpId.getStr());
+ g_object_set_data_full(G_OBJECT(pWidget), "g-lo-helpid", helpid, g_free);
+ }
+
+ OString 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 OString(pStr, pStr ? strlen(pStr) : 0);
+ }
+
+ KeyEvent GtkToVcl(const GdkEventKey& rEvent)
+ {
+ sal_uInt16 nKeyCode = GtkSalFrame::GetKeyCode(rEvent.keyval);
+ if (nKeyCode == 0)
+ {
+ guint updated_keyval = GtkSalFrame::GetKeyValFor(gdk_keymap_get_default(), rEvent.hardware_keycode, rEvent.group);
+ nKeyCode = GtkSalFrame::GetKeyCode(updated_keyval);
+ }
+ nKeyCode |= GtkSalFrame::GetKeyModCode(rEvent.state);
+ return KeyEvent(gdk_keyval_to_unicode(rEvent.keyval), nKeyCode, 0);
+ }
+}
+
+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();
+ }
+
+ 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))
+ {
+ gtk_container_child_get(GTK_CONTAINER(pParent), pWidget,
+ "left-attach", &nTopAttach,
+ "top-attach", &nLeftAttach,
+ "width", &nWidth,
+ "height", &nHeight,
+ nullptr);
+ }
+
+ 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);
+ }
+
+ gtk_container_remove(GTK_CONTAINER(pParent), pWidget);
+
+ gtk_widget_set_visible(pReplacement, gtk_widget_get_visible(pWidget));
+ gtk_widget_set_no_show_all(pReplacement, gtk_widget_get_no_show_all(pWidget));
+
+ 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);
+
+ gtk_container_add(GTK_CONTAINER(pParent), pReplacement);
+
+ if (GTK_IS_GRID(pParent))
+ {
+ gtk_container_child_set(GTK_CONTAINER(pParent), pReplacement,
+ "left-attach", nTopAttach,
+ "top-attach", nLeftAttach,
+ "width", nWidth,
+ "height", nHeight,
+ nullptr);
+ }
+
+ if (GTK_IS_BOX(pParent))
+ {
+ gtk_container_child_set(GTK_CONTAINER(pParent), pReplacement,
+ "expand", bExpand,
+ "fill", bFill,
+ "pack-type", ePackType,
+ "padding", nPadding,
+ "position", nPosition,
+ nullptr);
+ }
+
+ 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));
+
+ g_object_unref(pWidget);
+ }
+
+ void insertAsParent(GtkWidget* pWidget, GtkWidget* pReplacement)
+ {
+ g_object_ref(pWidget);
+
+ replaceWidget(pWidget, pReplacement);
+
+ gtk_container_add(GTK_CONTAINER(pReplacement), pWidget);
+
+ g_object_unref(pWidget);
+ }
+
+ GtkWidget* ensureEventWidget(GtkWidget* pWidget)
+ {
+ 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;
+ }
+}
+
+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;
+}
+
+class GtkInstanceWidget : public virtual weld::Widget
+{
+protected:
+ GtkWidget* m_pWidget;
+ GtkWidget* m_pMouseEventBox;
+ GtkInstanceBuilder* m_pBuilder;
+
+ DECL_LINK(async_signal_focus_in, void*, void);
+ DECL_LINK(async_signal_focus_out, void*, void);
+ DECL_LINK(async_drag_cancel, void*, void);
+
+ void launch_signal_focus_in()
+ {
+ // in e.g. function wizard RefEdits we want to select all when we get focus
+ // but there are pending gtk handlers which change selection after our handler
+ // post our focus in event to happen after those finish
+ if (m_pFocusInEvent)
+ Application::RemoveUserEvent(m_pFocusInEvent);
+ m_pFocusInEvent = Application::PostUserEvent(LINK(this, GtkInstanceWidget, async_signal_focus_in));
+ }
+
+ static gboolean signalFocusIn(GtkWidget*, GdkEvent*, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ pThis->launch_signal_focus_in();
+ return false;
+ }
+
+ void signal_focus_in()
+ {
+ 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);
+ }
+
+ void launch_signal_focus_out()
+ {
+ // tdf#127262 because focus in is async, focus out must not appear out
+ // of sequence to focus in
+ if (m_pFocusOutEvent)
+ Application::RemoveUserEvent(m_pFocusOutEvent);
+ m_pFocusOutEvent = Application::PostUserEvent(LINK(this, GtkInstanceWidget, async_signal_focus_out));
+ }
+
+ static gboolean signalFocusOut(GtkWidget*, GdkEvent*, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->launch_signal_focus_out();
+ return false;
+ }
+
+ 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);
+ }
+
+ void signal_focus_out()
+ {
+ m_aFocusOutHdl.Call(*this);
+ }
+
+ void ensureEventWidget()
+ {
+ if (!m_pMouseEventBox)
+ m_pMouseEventBox = ::ensureEventWidget(m_pWidget);
+ }
+
+ void ensureButtonPressSignal()
+ {
+ if (!m_nButtonPressSignalId)
+ {
+ ensureEventWidget();
+ m_nButtonPressSignalId = g_signal_connect(m_pMouseEventBox, "button-press-event", G_CALLBACK(signalButton), this);
+ }
+ }
+
+ 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);
+ }
+
+ bool SwapForRTL() const
+ {
+ return ::SwapForRTL(m_pWidget);
+ }
+
+ void do_enable_drag_source(rtl::Reference<TransferDataContainer>& rHelper, sal_uInt8 eDNDConstants)
+ {
+ css::uno::Reference<css::datatransfer::XTransferable> xTrans(rHelper.get());
+ css::uno::Reference<css::datatransfer::dnd::XDragSourceListener> xListener(rHelper.get());
+
+ ensure_drag_source();
+
+ auto aFormats = xTrans->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(xTrans, xListener);
+ }
+
+ 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())
+ m_nKeyPressSignalId = g_signal_connect(m_pWidget, "key-press-event", G_CALLBACK(signalKey), this);
+ }
+
+ void ensure_drag_begin_end()
+ {
+ if (!m_nDragBeginSignalId)
+ {
+ // using "after" due to https://gitlab.gnome.org/GNOME/pygobject/issues/251
+ m_nDragBeginSignalId = g_signal_connect_after(m_pWidget, "drag-begin", G_CALLBACK(signalDragBegin), this);
+ }
+ if (!m_nDragEndSignalId)
+ m_nDragEndSignalId = g_signal_connect(m_pWidget, "drag-end", G_CALLBACK(signalDragEnd), this);
+ }
+
+private:
+ bool m_bTakeOwnership;
+ bool m_bDraggedOver;
+ sal_uInt16 m_nLastMouseButton;
+ sal_uInt16 m_nLastMouseClicks;
+ int m_nPressedButton;
+ int m_nPressStartX;
+ int m_nPressStartY;
+ ImplSVEvent* m_pFocusInEvent;
+ ImplSVEvent* m_pFocusOutEvent;
+ ImplSVEvent* m_pDragCancelEvent;
+ GtkCssProvider* m_pBgCssProvider;
+ GdkDragAction m_eDragAction;
+ gulong m_nFocusInSignalId;
+ gulong m_nMnemonicActivateSignalId;
+ gulong m_nFocusOutSignalId;
+ gulong m_nKeyPressSignalId;
+ gulong m_nKeyReleaseSignalId;
+ gulong m_nSizeAllocateSignalId;
+ 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;
+
+ rtl::Reference<GtkDropTarget> m_xDropTarget;
+ rtl::Reference<GtkDragSource> 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);
+ }
+
+ static gboolean signalKey(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
+ {
+ // #i1820# use locale specific decimal separator
+ if (pEvent->keyval == GDK_KEY_KP_Decimal && Application::GetSettings().GetMiscSettings().GetEnableLocalizedDecimalSep())
+ {
+ OUString aSep(Application::GetSettings().GetLocaleDataWrapper().getNumDecimalSep());
+ pEvent->keyval = aSep[0];
+ }
+
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ return pThis->signal_key(pEvent);
+ }
+
+ virtual bool signal_popup_menu(const CommandEvent&)
+ {
+ return false;
+ }
+
+ static gboolean signalButton(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;
+ }
+
+ 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;
+ }
+
+ /* 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;
+ }
+
+ 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);
+ }
+
+ 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)
+ {
+ GtkTargetList* pDragData = (m_eDragAction != 0 && m_nPressedButton != -1 && m_xDragSource.is()) ? gtk_drag_source_get_target_list(m_pWidget) : nullptr;
+ bool bUnsetDragIcon(false);
+ if (pDragData && 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,
+ pDragData,
+ 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);
+ }
+
+ m_nPressedButton = -1;
+ return false;
+ }
+
+ if (!m_aMouseMotionHdl.IsSet())
+ return false;
+
+ Point aPos(pEvent->x, pEvent->y);
+ if (SwapForRTL())
+ aPos.setX(gtk_widget_get_allocated_width(m_pWidget) - 1 - aPos.X());
+ sal_uInt32 nModCode = GtkSalFrame::GetMouseModCode(pEvent->state);
+ MouseEvent aMEvt(aPos, 0, ImplGetMouseMoveMode(nModCode), nModCode, nModCode);
+
+ m_aMouseMotionHdl.Call(aMEvt);
+ return true;
+ }
+
+ static gboolean signalCrossing(GtkWidget*, GdkEventCrossing* pEvent, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ SolarMutexGuard aGuard;
+ return pThis->signal_crossing(pEvent);
+ }
+
+ bool signal_crossing(const GdkEventCrossing* pEvent)
+ {
+ if (!m_aMouseMotionHdl.IsSet())
+ return false;
+
+ Point aPos(pEvent->x, pEvent->y);
+ if (SwapForRTL())
+ aPos.setX(gtk_widget_get_allocated_width(m_pWidget) - 1 - aPos.X());
+ sal_uInt32 nModCode = GtkSalFrame::GetMouseModCode(pEvent->state);
+ MouseEventModifiers eModifiers = ImplGetMouseMoveMode(nModCode);
+ eModifiers = eModifiers | (pEvent->type == GDK_ENTER_NOTIFY ? MouseEventModifiers::ENTERWINDOW : MouseEventModifiers::LEAVEWINDOW);
+ MouseEvent aMEvt(aPos, 0, eModifiers, nModCode, nModCode);
+
+ m_aMouseMotionHdl.Call(aMEvt);
+ return true;
+ }
+
+ virtual void drag_started()
+ {
+ }
+
+ 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);
+ }
+
+ virtual void drag_ended()
+ {
+ }
+
+ static void signalDragLeave(GtkWidget *pWidget, GdkDragContext *context, guint time, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ pThis->m_xDropTarget->signalDragLeave(pWidget, context, time);
+ if (pThis->m_bDraggedOver)
+ {
+ pThis->m_bDraggedOver = false;
+ pThis->drag_ended();
+ }
+ }
+
+ static void signalDragBegin(GtkWidget*, GdkDragContext* context, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ pThis->signal_drag_begin(context);
+ }
+
+ void ensure_drag_source()
+ {
+ if (!m_xDragSource)
+ {
+ m_xDragSource.set(new GtkDragSource);
+
+ 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);
+
+ ensure_drag_begin_end();
+ }
+ }
+
+ virtual bool do_signal_drag_begin(bool& rUnsetDragIcon)
+ {
+ rUnsetDragIcon = false;
+ return false;
+ }
+
+ void signal_drag_begin(GdkDragContext* context)
+ {
+ bool bUnsetDragIcon(false);
+ if (do_signal_drag_begin(bUnsetDragIcon))
+ {
+ launch_drag_cancel(context);
+ return;
+ }
+ if (bUnsetDragIcon)
+ {
+ cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0);
+ gtk_drag_set_icon_surface(context, surface);
+ }
+ if (!m_xDragSource)
+ return;
+ m_xDragSource->setActiveDragSource();
+ }
+
+ virtual void do_signal_drag_end()
+ {
+ }
+
+ static void signalDragEnd(GtkWidget* /*widget*/, GdkDragContext* context, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ pThis->do_signal_drag_end();
+ if (pThis->m_xDragSource.is())
+ pThis->m_xDragSource->dragEnd(context);
+ }
+
+ 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);
+ }
+
+ virtual void drag_source_set(const std::vector<GtkTargetEntry>& rGtkTargets, GdkDragAction eDragAction)
+ {
+ gtk_drag_source_set(m_pWidget, GDK_BUTTON1_MASK, rGtkTargets.data(), rGtkTargets.size(), eDragAction);
+ }
+
+ void set_background(const OUString* pColor)
+ {
+ if (!pColor && !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 (!pColor)
+ return;
+ m_pBgCssProvider = gtk_css_provider_new();
+ OUString aBuffer = "* { background-color: #" + *pColor + "; }";
+ OString aResult = OUStringToOString(aBuffer, RTL_TEXTENCODING_UTF8);
+ gtk_css_provider_load_from_data(m_pBgCssProvider, aResult.getStr(), aResult.getLength(), nullptr);
+ gtk_style_context_add_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pBgCssProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+
+public:
+ GtkInstanceWidget(GtkWidget* pWidget, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : m_pWidget(pWidget)
+ , m_pMouseEventBox(nullptr)
+ , m_pBuilder(pBuilder)
+ , m_bTakeOwnership(bTakeOwnership)
+ , m_bDraggedOver(false)
+ , m_nLastMouseButton(0)
+ , m_nLastMouseClicks(0)
+ , m_nPressedButton(-1)
+ , m_nPressStartX(-1)
+ , m_nPressStartY(-1)
+ , m_pFocusInEvent(nullptr)
+ , m_pFocusOutEvent(nullptr)
+ , m_pDragCancelEvent(nullptr)
+ , m_pBgCssProvider(nullptr)
+ , m_eDragAction(GdkDragAction(0))
+ , 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 (!bTakeOwnership)
+ g_object_ref(m_pWidget);
+
+ localizeDecimalSeparator();
+ }
+
+ virtual void connect_key_press(const Link<const KeyEvent&, bool>& rLink) override
+ {
+ if (!m_nKeyPressSignalId)
+ m_nKeyPressSignalId = g_signal_connect(m_pWidget, "key-press-event", G_CALLBACK(signalKey), this);
+ weld::Widget::connect_key_press(rLink);
+ }
+
+ virtual void connect_key_release(const Link<const KeyEvent&, bool>& rLink) override
+ {
+ if (!m_nKeyReleaseSignalId)
+ m_nKeyReleaseSignalId = g_signal_connect(m_pWidget, "key-release-event", G_CALLBACK(signalKey), this);
+ 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
+ {
+ ensureEventWidget();
+ 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);
+ weld::Widget::connect_mouse_move(rLink);
+ }
+
+ virtual void connect_mouse_release(const Link<const MouseEvent&, bool>& rLink) override
+ {
+ ensureEventWidget();
+ if (!m_nButtonReleaseSignalId)
+ m_nButtonReleaseSignalId = g_signal_connect(m_pMouseEventBox, "button-release-event", G_CALLBACK(signalButton), this);
+ 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
+ {
+ disable_notify_events();
+ gtk_widget_grab_focus(m_pWidget);
+ enable_notify_events();
+ }
+
+ virtual bool has_focus() const override
+ {
+ return gtk_widget_has_focus(m_pWidget);
+ }
+
+ virtual bool is_active() const override
+ {
+ GtkWindow* pTopLevel = GTK_WINDOW(gtk_widget_get_toplevel(m_pWidget));
+ return pTopLevel && gtk_window_is_active(pTopLevel) && has_focus();
+ }
+
+ virtual void set_has_default(bool has_default) override
+ {
+ g_object_set(G_OBJECT(m_pWidget), "has-default", has_default, nullptr);
+ }
+
+ virtual bool get_has_default() const override
+ {
+ gboolean has_default(false);
+ g_object_get(G_OBJECT(m_pWidget), "has-default", &has_default, nullptr);
+ return has_default;
+ }
+
+ 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
+ {
+ PangoContext* pContext = gtk_widget_get_pango_context(m_pWidget);
+ return pango_to_vcl(pango_context_get_font_description(pContext),
+ Application::GetSettings().GetUILanguageTag().getLocale());
+ }
+
+ virtual void set_grid_left_attach(int nAttach) override
+ {
+ GtkContainer* pParent = GTK_CONTAINER(gtk_widget_get_parent(m_pWidget));
+ gtk_container_child_set(pParent, m_pWidget, "left-attach", nAttach, nullptr);
+ }
+
+ virtual int get_grid_left_attach() const override
+ {
+ GtkContainer* pParent = GTK_CONTAINER(gtk_widget_get_parent(m_pWidget));
+ gint nAttach(0);
+ gtk_container_child_get(pParent, m_pWidget, "left-attach", &nAttach, nullptr);
+ return nAttach;
+ }
+
+ virtual void set_grid_width(int nCols) override
+ {
+ GtkContainer* pParent = GTK_CONTAINER(gtk_widget_get_parent(m_pWidget));
+ gtk_container_child_set(pParent, m_pWidget, "width", nCols, nullptr);
+ }
+
+ virtual void set_grid_top_attach(int nAttach) override
+ {
+ GtkContainer* pParent = GTK_CONTAINER(gtk_widget_get_parent(m_pWidget));
+ gtk_container_child_set(pParent, m_pWidget, "top-attach", nAttach, nullptr);
+ }
+
+ virtual int get_grid_top_attach() const override
+ {
+ GtkContainer* pParent = GTK_CONTAINER(gtk_widget_get_parent(m_pWidget));
+ gint nAttach(0);
+ gtk_container_child_get(pParent, m_pWidget, "top-attach", &nAttach, nullptr);
+ 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_secondary(bool bSecondary) override
+ {
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+ if (pParent && GTK_IS_BUTTON_BOX(pParent))
+ gtk_button_box_set_child_secondary(GTK_BUTTON_BOX(pParent), m_pWidget, bSecondary);
+ }
+
+ 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_left(int nMargin) override
+ {
+ gtk_widget_set_margin_left(m_pWidget, nMargin);
+ }
+
+ virtual void set_margin_right(int nMargin) override
+ {
+ gtk_widget_set_margin_right(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_left() const override
+ {
+ return gtk_widget_get_margin_left(m_pWidget);
+ }
+
+ virtual int get_margin_right() const override
+ {
+ return gtk_widget_get_margin_right(m_pWidget);
+ }
+
+ virtual void set_accessible_name(const OUString& rName) override
+ {
+ AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
+ if (!pAtkObject)
+ return;
+ atk_object_set_name(pAtkObject, OUStringToOString(rName, RTL_TEXTENCODING_UTF8).getStr());
+ }
+
+ virtual OUString get_accessible_name() const override
+ {
+ 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);
+ }
+
+ virtual OUString get_accessible_description() const override
+ {
+ 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);
+ }
+
+ virtual void set_accessible_relation_labeled_by(weld::Widget* pLabel) override
+ {
+ AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
+ if (!pAtkObject)
+ return;
+ AtkObject *pAtkLabel = pLabel ? gtk_widget_get_accessible(dynamic_cast<GtkInstanceWidget&>(*pLabel).getWidget()) : 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)
+ atk_relation_set_remove(pRelationSet, pRelation);
+ if (pAtkLabel)
+ {
+ AtkObject *obj_array[1];
+ obj_array[0] = pAtkLabel;
+ pRelation = atk_relation_new(obj_array, 1, ATK_RELATION_LABELLED_BY);
+ atk_relation_set_add(pRelationSet, pRelation);
+ }
+ g_object_unref(pRelationSet);
+ }
+
+ virtual void set_accessible_relation_label_for(weld::Widget* pLabeled) override
+ {
+ AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
+ if (!pAtkObject)
+ return;
+ AtkObject *pAtkLabeled = pLabeled ? gtk_widget_get_accessible(dynamic_cast<GtkInstanceWidget&>(*pLabeled).getWidget()) : nullptr;
+ AtkRelationSet *pRelationSet = atk_object_ref_relation_set(pAtkObject);
+ AtkRelation *pRelation = atk_relation_set_get_relation_by_type(pRelationSet, ATK_RELATION_LABEL_FOR);
+ if (pRelation)
+ atk_relation_set_remove(pRelationSet, pRelation);
+ if (pAtkLabeled)
+ {
+ AtkObject *obj_array[1];
+ obj_array[0] = pAtkLabeled;
+ pRelation = atk_relation_new(obj_array, 1, ATK_RELATION_LABEL_FOR);
+ atk_relation_set_add(pRelationSet, pRelation);
+ }
+ g_object_unref(pRelationSet);
+ }
+
+ virtual bool get_extents_relative_to(weld::Widget& rRelative, int& x, int &y, int& width, int &height) 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
+ bool ret = gtk_widget_translate_coordinates(m_pWidget,
+ dynamic_cast<GtkInstanceWidget&>(rRelative).getWidget(),
+ 0, 0, &x, &y);
+ 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 std::unique_ptr<weld::Container> weld_parent() const override;
+
+ virtual OString get_buildable_name() const override
+ {
+ const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(m_pWidget));
+ return OString(pStr, pStr ? strlen(pStr) : 0);
+ }
+
+ virtual void set_help_id(const OString& rHelpId) override
+ {
+ ::set_help_id(m_pWidget, rHelpId);
+ }
+
+ virtual OString get_help_id() const override
+ {
+ OString sRet = ::get_help_id(m_pWidget);
+ if (sRet.isEmpty())
+ sRet = OString("null");
+ return sRet;
+ }
+
+ GtkWidget* getWidget()
+ {
+ return m_pWidget;
+ }
+
+ GtkWindow* getWindow()
+ {
+ return GTK_WINDOW(gtk_widget_get_toplevel(m_pWidget));
+ }
+
+ virtual void connect_focus_in(const Link<Widget&, void>& rLink) override
+ {
+ if (!m_nFocusInSignalId)
+ m_nFocusInSignalId = g_signal_connect(m_pWidget, "focus-in-event", G_CALLBACK(signalFocusIn), this);
+ 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)
+ m_nFocusOutSignalId = g_signal_connect(m_pWidget, "focus-out-event", G_CALLBACK(signalFocusOut), this);
+ 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));
+ }
+
+ bool signal_key(const GdkEventKey* pEvent)
+ {
+ if (pEvent->type == GDK_KEY_PRESS && m_aKeyPressHdl.IsSet())
+ {
+ SolarMutexGuard aGuard;
+ return m_aKeyPressHdl.Call(GtkToVcl(*pEvent));
+ }
+ if (pEvent->type == GDK_KEY_RELEASE && m_aKeyReleaseHdl.IsSet())
+ {
+ SolarMutexGuard aGuard;
+ return m_aKeyReleaseHdl.Call(GtkToVcl(*pEvent));
+ }
+ return false;
+ }
+
+ virtual void grab_add() override
+ {
+ gtk_grab_add(m_pWidget);
+ }
+
+ virtual bool has_grab() const override
+ {
+ return gtk_widget_has_grab(m_pWidget);
+ }
+
+ virtual void grab_remove() override
+ {
+ gtk_grab_remove(m_pWidget);
+ }
+
+ 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
+ {
+ gtk_widget_freeze_child_notify(m_pWidget);
+ }
+
+ virtual void thaw() override
+ {
+ gtk_widget_thaw_child_notify(m_pWidget);
+ }
+
+ virtual css::uno::Reference<css::datatransfer::dnd::XDropTarget> get_drop_target() override
+ {
+ if (!m_xDropTarget)
+ {
+ m_xDropTarget.set(new GtkDropTarget);
+ 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);
+ }
+ return m_xDropTarget.get();
+ }
+
+ virtual void connect_get_property_tree(const Link<boost::property_tree::ptree&, void>& /*rLink*/) override
+ {
+ //not implemented for the gtk variant
+ }
+
+ virtual void set_stack_background() override
+ {
+ OUString sColor = Application::GetSettings().GetStyleSettings().GetWindowColor().AsRGBHexString();
+ set_background(&sColor);
+ }
+
+ virtual void set_highlight_background() override
+ {
+ OUString sColor = Application::GetSettings().GetStyleSettings().GetHighlightColor().AsRGBHexString();
+ set_background(&sColor);
+ }
+
+ virtual void set_toolbar_background() override
+ {
+ // no-op
+ }
+
+ virtual ~GtkInstanceWidget() override
+ {
+ if (m_pFocusInEvent)
+ Application::RemoveUserEvent(m_pFocusInEvent);
+ if (m_pFocusOutEvent)
+ Application::RemoveUserEvent(m_pFocusOutEvent);
+ 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)
+ g_signal_handler_disconnect(m_pWidget, m_nDragEndSignalId);
+ if (m_nDragBeginSignalId)
+ g_signal_handler_disconnect(m_pWidget, m_nDragBeginSignalId);
+ 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)
+ g_signal_handler_disconnect(m_pWidget, m_nKeyPressSignalId);
+ if (m_nKeyReleaseSignalId)
+ g_signal_handler_disconnect(m_pWidget, m_nKeyReleaseSignalId);
+ if (m_nButtonPressSignalId)
+ g_signal_handler_disconnect(m_pMouseEventBox, m_nButtonPressSignalId);
+ if (m_nMotionSignalId)
+ g_signal_handler_disconnect(m_pMouseEventBox, m_nMotionSignalId);
+ if (m_nLeaveSignalId)
+ g_signal_handler_disconnect(m_pMouseEventBox, m_nLeaveSignalId);
+ if (m_nEnterSignalId)
+ g_signal_handler_disconnect(m_pMouseEventBox, m_nEnterSignalId);
+ if (m_nButtonReleaseSignalId)
+ g_signal_handler_disconnect(m_pMouseEventBox, m_nButtonReleaseSignalId);
+ if (m_nFocusInSignalId)
+ g_signal_handler_disconnect(m_pWidget, m_nFocusInSignalId);
+ if (m_nMnemonicActivateSignalId)
+ g_signal_handler_disconnect(m_pWidget, m_nMnemonicActivateSignalId);
+ if (m_nFocusOutSignalId)
+ g_signal_handler_disconnect(m_pWidget, m_nFocusOutSignalId);
+ if (m_nSizeAllocateSignalId)
+ g_signal_handler_disconnect(m_pWidget, m_nSizeAllocateSignalId);
+
+ set_background(nullptr);
+
+ if (m_pMouseEventBox && m_pMouseEventBox != m_pWidget)
+ {
+ // 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);
+ g_object_unref(m_pWidget);
+ }
+
+ if (m_bTakeOwnership)
+ gtk_widget_destroy(m_pWidget);
+ else
+ g_object_unref(m_pWidget);
+ }
+
+ virtual void disable_notify_events()
+ {
+ if (m_nFocusInSignalId)
+ g_signal_handler_block(m_pWidget, m_nFocusInSignalId);
+ if (m_nMnemonicActivateSignalId)
+ g_signal_handler_block(m_pWidget, m_nMnemonicActivateSignalId);
+ if (m_nFocusOutSignalId)
+ g_signal_handler_block(m_pWidget, m_nFocusOutSignalId);
+ 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)
+ g_signal_handler_unblock(m_pWidget, m_nFocusOutSignalId);
+ if (m_nMnemonicActivateSignalId)
+ g_signal_handler_unblock(m_pWidget, m_nMnemonicActivateSignalId);
+ if (m_nFocusInSignalId)
+ g_signal_handler_unblock(m_pWidget, m_nFocusInSignalId);
+ }
+
+ virtual void help_hierarchy_foreach(const std::function<bool(const OString&)>& 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(VirtualDevice& rOutput) 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 (!bAlreadyVisible)
+ gtk_widget_show(m_pWidget);
+
+ GtkAllocation allocation;
+
+ if (!bAlreadyRealized)
+ gtk_widget_realize(m_pWidget);
+
+ if (!bAlreadyMapped)
+ gtk_widget_map(m_pWidget);
+
+ if (GTK_IS_CONTAINER(m_pWidget))
+ gtk_container_resize_children(GTK_CONTAINER(m_pWidget));
+
+ gtk_widget_get_allocation(m_pWidget, &allocation);
+
+ rOutput.SetOutputSizePixel(Size(allocation.width, allocation.height));
+ cairo_surface_t* pSurface = get_underlying_cairo_surface(rOutput);
+ cairo_t* cr = cairo_create(pSurface);
+
+ gtk_widget_draw(m_pWidget, cr);
+
+ cairo_destroy(cr);
+
+ if (!bAlreadyVisible)
+ gtk_widget_hide(m_pWidget);
+ if (!bAlreadyMapped)
+ gtk_widget_unmap(m_pWidget);
+ if (!bAlreadyRealized)
+ gtk_widget_unrealize(m_pWidget);
+ }
+};
+
+}
+
+IMPL_LINK_NOARG(GtkInstanceWidget, async_signal_focus_in, void*, void)
+{
+ m_pFocusInEvent = nullptr;
+ signal_focus_in();
+}
+
+IMPL_LINK_NOARG(GtkInstanceWidget, async_signal_focus_out, void*, void)
+{
+ m_pFocusOutEvent = nullptr;
+ signal_focus_out();
+}
+
+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);
+}
+
+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());
+ }
+
+ OUString get_label(GtkButton* pButton)
+ {
+ const gchar* pStr = gtk_button_get_label(pButton);
+ return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+ }
+
+ void set_label(GtkButton* pButton, const OUString& rText)
+ {
+ gtk_button_set_label(pButton, MapToGtkAccelerator(rText).getStr());
+ }
+
+ 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, const OUString& 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, const OUString& rText)
+ {
+ g_object_set(G_OBJECT(pMessageDialog), "text",
+ OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr(),
+ nullptr);
+ }
+
+ void set_secondary_text(GtkMessageDialog* pMessageDialog, const OUString& 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;
+ }
+
+ GdkPixbuf* load_icon_by_name_theme_lang(const OUString& rIconName, const OUString& rIconTheme, const OUString& rUILang)
+ {
+ auto xMemStm = ImageTree::get().getImageStream(rIconName, rIconTheme, rUILang);
+ if (!xMemStm)
+ return nullptr;
+ return load_icon_from_stream(*xMemStm);
+ }
+}
+
+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
+{
+ GdkPixbuf* getPixbuf(const css::uno::Reference<css::graphic::XGraphic>& rImage)
+ {
+ Image aImage(rImage);
+
+ OUString sStock(aImage.GetStock());
+ if (!sStock.isEmpty())
+ return load_icon_by_name(sStock);
+
+ std::unique_ptr<SvMemoryStream> xMemStm(new SvMemoryStream);
+
+ css::uno::Sequence<css::beans::PropertyValue> aFilterData(1);
+ aFilterData[0].Name = "Compression";
+ // We "know" that this gets passed to zlib's deflateInit2_(). 1 means best speed.
+ aFilterData[0].Value <<= sal_Int32(1);
+
+ vcl::PNGWriter aWriter(aImage.GetBitmapEx(), &aFilterData);
+ aWriter.Write(*xMemStm);
+
+ return load_icon_from_stream(*xMemStm);
+ }
+
+ GdkPixbuf* getPixbuf(const VirtualDevice& rDevice)
+ {
+ Size aSize(rDevice.GetOutputSizePixel());
+ cairo_surface_t* orig_surface = get_underlying_cairo_surface(rDevice);
+ double m_fXScale, m_fYScale;
+ dl_cairo_surface_get_device_scale(orig_surface, &m_fXScale, &m_fYScale);
+
+ cairo_surface_t* surface;
+ if (m_fXScale != 1.0 || m_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;
+ }
+
+ 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");
+
+ GError *error = nullptr;
+ GtkIconTheme *icon_theme = gtk_icon_theme_get_default();
+ pixbuf = gtk_icon_theme_load_icon(icon_theme, OUStringToOString(rIconName, RTL_TEXTENCODING_UTF8).getStr(),
+ 16, GTK_ICON_LOOKUP_USE_BUILTIN, &error);
+ }
+ else
+ {
+ const AllSettings& rSettings = Application::GetSettings();
+ pixbuf = load_icon_by_name_theme_lang(rIconName,
+ rSettings.GetStyleSettings().DetermineIconTheme(),
+ rSettings.GetUILanguageTag().getBcp47());
+ }
+
+ return pixbuf;
+ }
+
+ GtkWidget* image_new_from_virtual_device(const VirtualDevice& rImageSurface)
+ {
+ GtkWidget* pImage = nullptr;
+ if (gtk_check_version(3, 20, 0) == 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);
+ }
+ else
+ {
+ GdkPixbuf* pixbuf = getPixbuf(rImageSurface);
+ pImage = gtk_image_new_from_pixbuf(pixbuf);
+ g_object_unref(pixbuf);
+ }
+ return pImage;
+ }
+
+class MenuHelper
+{
+protected:
+ GtkMenu* m_pMenu;
+ bool m_bTakeOwnership;
+ std::map<OString, GtkMenuItem*> m_aMap;
+private:
+
+ 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_activate(pItem);
+ }
+
+ virtual void signal_activate(GtkMenuItem* pItem) = 0;
+
+public:
+ MenuHelper(GtkMenu* pMenu, bool bTakeOwnership)
+ : m_pMenu(pMenu)
+ , m_bTakeOwnership(bTakeOwnership)
+ {
+ if (!m_pMenu)
+ return;
+ gtk_container_foreach(GTK_CONTAINER(m_pMenu), collect, this);
+ }
+
+ void add_to_map(GtkMenuItem* pMenuItem)
+ {
+ const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pMenuItem));
+ OString id(pStr, pStr ? strlen(pStr) : 0);
+ m_aMap[id] = pMenuItem;
+ g_signal_connect(pMenuItem, "activate", G_CALLBACK(signalActivate), this);
+ }
+
+ void remove_from_map(GtkMenuItem* pMenuItem)
+ {
+ const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pMenuItem));
+ OString id(pStr, pStr ? strlen(pStr) : 0);
+ 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);
+ }
+
+ void insert_item(int pos, const OUString& rId, const OUString& rStr,
+ const OUString* pIconName, const VirtualDevice* pImageSurface,
+ TriState eCheckRadioFalse)
+ {
+ GtkWidget* pImage = nullptr;
+ if (pIconName && !pIconName->isEmpty())
+ {
+ GdkPixbuf* pixbuf = load_icon_by_name(*pIconName);
+ if (!pixbuf)
+ {
+ pImage = gtk_image_new_from_pixbuf(pixbuf);
+ g_object_unref(pixbuf);
+ }
+ }
+ else if (pImageSurface)
+ pImage = image_new_from_virtual_device(*pImageSurface);
+
+ GtkWidget *pItem;
+ if (pImage)
+ {
+ GtkWidget *pBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
+ GtkWidget *pLabel = gtk_label_new(MapToGtkAccelerator(rStr).getStr());
+ pItem = eCheckRadioFalse != TRISTATE_INDET ? gtk_check_menu_item_new() : gtk_menu_item_new();
+ gtk_container_add(GTK_CONTAINER(pBox), pImage);
+ gtk_container_add(GTK_CONTAINER(pBox), pLabel);
+ gtk_container_add(GTK_CONTAINER(pItem), 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);
+
+ gtk_buildable_set_name(GTK_BUILDABLE(pItem), OUStringToOString(rId, RTL_TEXTENCODING_UTF8).getStr());
+ 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);
+ }
+
+ void insert_separator(int pos, const OUString& rId)
+ {
+ GtkWidget* pItem = gtk_separator_menu_item_new();
+ gtk_buildable_set_name(GTK_BUILDABLE(pItem), OUStringToOString(rId, RTL_TEXTENCODING_UTF8).getStr());
+ 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);
+ }
+
+ void remove_item(const OString& rIdent)
+ {
+ GtkMenuItem* pMenuItem = m_aMap[rIdent];
+ remove_from_map(pMenuItem);
+ gtk_widget_destroy(GTK_WIDGET(pMenuItem));
+ }
+
+ void set_item_sensitive(const OString& rIdent, bool bSensitive)
+ {
+ gtk_widget_set_sensitive(GTK_WIDGET(m_aMap[rIdent]), bSensitive);
+ }
+
+ void set_item_active(const OString& rIdent, bool bActive)
+ {
+ disable_item_notify_events();
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(m_aMap[rIdent]), bActive);
+ enable_item_notify_events();
+ }
+
+ bool get_item_active(const OString& rIdent) const
+ {
+ return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(m_aMap.find(rIdent)->second));
+ }
+
+ void set_item_label(const OString& rIdent, const OUString& rText)
+ {
+ gtk_menu_item_set_label(m_aMap[rIdent], MapToGtkAccelerator(rText).getStr());
+ }
+
+ OUString get_item_label(const OString& rIdent) const
+ {
+ const gchar* pText = gtk_menu_item_get_label(m_aMap.find(rIdent)->second);
+ return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
+ }
+
+ void set_item_help_id(const OString& rIdent, const OString& rHelpId)
+ {
+ set_help_id(GTK_WIDGET(m_aMap[rIdent]), rHelpId);
+ }
+
+ OString get_item_help_id(const OString& rIdent) const
+ {
+ return get_help_id(GTK_WIDGET(m_aMap.find(rIdent)->second));
+ }
+
+ void set_item_visible(const OString& rIdent, bool bShow)
+ {
+ GtkWidget* pWidget = GTK_WIDGET(m_aMap[rIdent]);
+ if (bShow)
+ gtk_widget_show(pWidget);
+ else
+ gtk_widget_hide(pWidget);
+ }
+
+ void clear_items()
+ {
+ 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();
+ }
+
+ GtkMenu* getMenu() const
+ {
+ return m_pMenu;
+ }
+
+ virtual ~MenuHelper()
+ {
+ for (auto& a : m_aMap)
+ g_signal_handlers_disconnect_by_data(a.second, this);
+ if (m_bTakeOwnership)
+ gtk_widget_destroy(GTK_WIDGET(m_pMenu));
+ }
+};
+
+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.SetPriority(TaskPriority::RESIZE);
+ maLayoutIdle.SetInvokeHandler( LINK( this, ChildFrame, ImplHandleLayoutTimerHdl ) );
+ maLayoutIdle.SetDebugName( "ChildFrame maLayoutIdle" );
+ }
+
+ 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();
+ }
+
+ virtual void Resize() override
+ {
+ WorkWindow::Resize();
+ queue_resize();
+ }
+};
+
+IMPL_LINK_NOARG(ChildFrame, ImplHandleLayoutTimerHdl, Timer*, void)
+{
+ if (vcl::Window *pChild = GetWindow(GetWindowType::FirstChild))
+ pChild->SetPosSizePixel(Point(0, 0), GetSizePixel());
+}
+
+class GtkInstanceContainer : public GtkInstanceWidget, public virtual weld::Container
+{
+private:
+ GtkContainer* m_pContainer;
+
+ 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);
+ }
+
+public:
+ GtkInstanceContainer(GtkContainer* pContainer, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pContainer), pBuilder, bTakeOwnership)
+ , m_pContainer(pContainer)
+ {
+ }
+
+ GtkContainer* getContainer() { return m_pContainer; }
+
+ 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);
+ gtk_container_remove(getContainer(), pChild);
+
+ GtkInstanceContainer* pNewGtkParent = dynamic_cast<GtkInstanceContainer*>(pNewParent);
+ assert(!pNewParent || pNewGtkParent);
+ if (pNewGtkParent)
+ gtk_container_add(pNewGtkParent->getContainer(), pChild);
+ g_object_unref(pChild);
+ }
+
+ virtual void recursively_unset_default_buttons() override
+ {
+ implResetDefault(GTK_WIDGET(m_pContainer), nullptr);
+ }
+
+ 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);
+ gtk_container_remove(GTK_CONTAINER(pParent), pWindow);
+ gtk_container_add(m_pContainer, pWindow);
+ gtk_container_child_set(m_pContainer, pWindow, "expand", true, "fill", true, nullptr);
+ 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;
+ }
+};
+
+}
+
+std::unique_ptr<weld::Container> GtkInstanceWidget::weld_parent() const
+{
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+ if (!pParent)
+ return nullptr;
+ return std::make_unique<GtkInstanceContainer>(GTK_CONTAINER(pParent), m_pBuilder, false);
+}
+
+namespace {
+
+class GtkInstanceBox : public GtkInstanceContainer, public virtual weld::Box
+{
+private:
+ GtkBox* m_pBox;
+
+public:
+ GtkInstanceBox(GtkBox* pBox, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceContainer(GTK_CONTAINER(pBox), pBuilder, bTakeOwnership)
+ , 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();
+ gtk_box_reorder_child(m_pBox, pChild, nNewPosition);
+ }
+};
+
+ 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);
+ GdkCursor *pCursor = pName ? gdk_cursor_new_from_name(pDisplay, pName) : nullptr;
+ gdk_window_set_cursor(gtk_widget_get_window(pWidget), pCursor);
+ gdk_display_flush(pDisplay);
+ if (pCursor)
+ g_object_unref(pCursor);
+ }
+}
+
+namespace
+{
+ struct ButtonOrder
+ {
+ const char * m_aType;
+ int m_nPriority;
+ };
+
+ int getButtonPriority(const OString &rType)
+ {
+ static const size_t N_TYPES = 7;
+ static const ButtonOrder aDiscardCancelSave[N_TYPES] =
+ {
+ { "/discard", 0 },
+ { "/cancel", 1 },
+ { "/no", 2 },
+ { "/open", 3 },
+ { "/save", 3 },
+ { "/yes", 3 },
+ { "/ok", 3 }
+ };
+
+ static const ButtonOrder aSaveDiscardCancel[N_TYPES] =
+ {
+ { "/open", 0 },
+ { "/save", 0 },
+ { "/yes", 0 },
+ { "/ok", 0 },
+ { "/discard", 1 },
+ { "/no", 1 },
+ { "/cancel", 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.endsWith(pOrder->m_aType))
+ return pOrder->m_nPriority;
+ }
+
+ return -1;
+ }
+
+ bool sortButtons(const GtkWidget* pA, const GtkWidget* pB)
+ {
+ //order within groups according to platform rules
+ return getButtonPriority(::get_help_id(pA)) < getButtonPriority(::get_help_id(pB));
+ }
+
+ void sort_native_button_order(GtkBox* pContainer)
+ {
+ std::vector<GtkWidget*> aChildren;
+ 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);
+
+ //sort child order within parent so that we match the platform button order
+ std::stable_sort(aChildren.begin(), aChildren.end(), sortButtons);
+
+ for (size_t pos = 0; pos < aChildren.size(); ++pos)
+ gtk_box_reorder_child(pContainer, aChildren[pos], pos);
+ }
+
+ Point get_csd_offset(GtkWidget* pTopLevel)
+ {
+ // try and omit drawing CSD under wayland
+ GList* pChildren = gtk_container_get_children(GTK_CONTAINER(pTopLevel));
+ GList* pChild = g_list_first(pChildren);
+
+ int x, y;
+ gtk_widget_translate_coordinates(GTK_WIDGET(pChild->data),
+ GTK_WIDGET(pTopLevel),
+ 0, 0, &x, &y);
+
+ int innerborder = gtk_container_get_border_width(GTK_CONTAINER(pChild->data));
+ g_list_free(pChildren);
+
+ int outerborder = gtk_container_get_border_width(GTK_CONTAINER(pTopLevel));
+ int totalborder = outerborder + innerborder;
+ x -= totalborder;
+ y -= totalborder;
+
+ return Point(x, y);
+ }
+
+ void do_collect_screenshot_data(GtkWidget* pItem, gpointer data)
+ {
+ GtkWidget* pTopLevel = gtk_widget_get_toplevel(pItem);
+
+ int 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_IS_CONTAINER(pItem))
+ gtk_container_forall(GTK_CONTAINER(pItem), do_collect_screenshot_data, data);
+ }
+
+ tools::Rectangle get_monitor_workarea(GtkWidget* pWindow)
+ {
+ GdkScreen* pScreen = gtk_widget_get_screen(pWindow);
+ gint nMonitor = gdk_screen_get_monitor_at_window(pScreen, gtk_widget_get_window(pWindow));
+ GdkRectangle aRect;
+ gdk_screen_get_monitor_workarea(pScreen, nMonitor, &aRect);
+ return tools::Rectangle(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;
+
+ static gboolean help_pressed(GtkAccelGroup*, GObject*, guint, GdkModifierType, gpointer widget)
+ {
+ GtkInstanceWindow* pThis = static_cast<GtkInstanceWindow*>(widget);
+ pThis->help();
+ return true;
+ }
+
+ static void signalToplevelFocusChanged(GtkWindow*, GParamSpec*, gpointer widget)
+ {
+ GtkInstanceWindow* pThis = static_cast<GtkInstanceWindow*>(widget);
+ pThis->signal_toplevel_focus_changed();
+ }
+
+protected:
+ void help();
+public:
+ GtkInstanceWindow(GtkWindow* pWindow, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceContainer(GTK_CONTAINER(pWindow), pBuilder, bTakeOwnership)
+ , m_pWindow(pWindow)
+ , m_nToplevelFocusChangedSignalId(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);
+ }
+ }
+
+ 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 css::uno::Reference<css::awt::XWindow>(m_xWindow.get());
+ }
+
+ virtual void set_busy_cursor(bool bBusy) override
+ {
+ set_cursor(m_pWidget, bBusy ? "progress" : nullptr);
+ }
+
+ 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
+ {
+ gtk_window_resize(m_pWindow, 1, 1);
+ }
+
+ virtual void window_move(int x, int y) override
+ {
+ gtk_window_move(m_pWindow, x, y);
+ }
+
+ virtual SystemEnvData get_system_data() const override
+ {
+ assert(false && "nothing should call this impl, yet anyway, if ever");
+ return SystemEnvData();
+ }
+
+ virtual Size get_size() const override
+ {
+ int current_width, current_height;
+ gtk_window_get_size(m_pWindow, &current_width, &current_height);
+ return Size(current_width, current_height);
+ }
+
+ virtual Point get_position() const override
+ {
+ int current_x, current_y;
+ gtk_window_get_position(m_pWindow, &current_x, &current_y);
+ return Point(current_x, current_y);
+ }
+
+ virtual tools::Rectangle get_monitor_workarea() const override
+ {
+ return ::get_monitor_workarea(GTK_WIDGET(m_pWindow));
+ }
+
+ virtual void set_centered_on_parent(bool bTrackGeometryRequests) override
+ {
+ 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);
+ }
+
+ virtual bool get_resizable() const override
+ {
+ return gtk_window_get_resizable(m_pWindow);
+ }
+
+ virtual bool has_toplevel_focus() const override
+ {
+ return gtk_window_has_toplevel_focus(m_pWindow);
+ }
+
+ virtual void present() override
+ {
+ gtk_window_present(m_pWindow);
+ }
+
+ virtual void set_window_state(const OString& rStr) override
+ {
+ WindowStateData aData;
+ ImplWindowStateFromStr( aData, rStr );
+
+ auto nMask = aData.GetMask();
+ auto nState = aData.GetState() & WindowStateState::SystemMask;
+
+ if (nMask & WindowStateMask::Width && nMask & WindowStateMask::Height)
+ {
+ gtk_window_set_default_size(m_pWindow, aData.GetWidth(), aData.GetHeight());
+ }
+ if (nMask & WindowStateMask::State)
+ {
+ if (nState & WindowStateState::Maximized)
+ gtk_window_maximize(m_pWindow);
+ else
+ gtk_window_unmaximize(m_pWindow);
+ }
+ }
+
+ virtual OString get_window_state(WindowStateMask nMask) const override
+ {
+ bool bPositioningAllowed = true;
+#if defined(GDK_WINDOWING_WAYLAND)
+ // drop x/y when under wayland
+ GdkDisplay *pDisplay = gtk_widget_get_display(m_pWidget);
+ bPositioningAllowed = !DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay);
+#endif
+
+ WindowStateData aData;
+ WindowStateMask nAvailable = WindowStateMask::State |
+ WindowStateMask::Width | WindowStateMask::Height;
+ if (bPositioningAllowed)
+ nAvailable |= WindowStateMask::X | WindowStateMask::Y;
+ aData.SetMask(nMask & nAvailable);
+
+ if (nMask & WindowStateMask::State)
+ {
+ WindowStateState nState = WindowStateState::Normal;
+ if (gtk_window_is_maximized(m_pWindow))
+ nState |= WindowStateState::Maximized;
+ aData.SetState(nState);
+ }
+
+ if (bPositioningAllowed && (nMask & (WindowStateMask::X | WindowStateMask::Y)))
+ {
+ auto aPos = get_position();
+ aData.SetX(aPos.X());
+ aData.SetY(aPos.Y());
+ }
+
+ if (nMask & (WindowStateMask::Width | WindowStateMask::Height))
+ {
+ auto aSize = get_size();
+ aData.SetWidth(aSize.Width());
+ aData.SetHeight(aSize.Height());
+ }
+
+ return aData.ToStr();
+ }
+
+ virtual void connect_toplevel_focus_changed(const Link<weld::Widget&, void>& rLink) override
+ {
+ assert(!m_nToplevelFocusChangedSignalId);
+ m_nToplevelFocusChangedSignalId = g_signal_connect(m_pWindow, "notify::has-toplevel-focus", G_CALLBACK(signalToplevelFocusChanged), this);
+ weld::Window::connect_toplevel_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 void draw(VirtualDevice& rOutput) 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 (!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));
+ }
+
+ if (!bAlreadyRealized)
+ {
+ GtkAllocation allocation;
+ gtk_widget_realize(GTK_WIDGET(m_pWindow));
+ gtk_widget_get_allocation(GTK_WIDGET(m_pWindow), &allocation);
+ gtk_widget_size_allocate(GTK_WIDGET(m_pWindow), &allocation);
+ }
+
+ rOutput.SetOutputSizePixel(get_size());
+ cairo_surface_t* pSurface = get_underlying_cairo_surface(rOutput);
+ cairo_t* cr = cairo_create(pSurface);
+
+ Point aOffset = get_csd_offset(GTK_WIDGET(m_pWindow));
+
+#if defined(GDK_WINDOWING_X11)
+ GdkDisplay *pDisplay = gtk_widget_get_display(GTK_WIDGET(m_pWindow));
+ if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
+ assert(aOffset.X() == 0 && aOffset.Y() == 0 && "expected offset of 0 under X");
+#endif
+
+ cairo_translate(cr, -aOffset.X(), -aOffset.Y());
+
+ gtk_widget_draw(GTK_WIDGET(m_pWindow), cr);
+
+ cairo_destroy(cr);
+
+ if (!bAlreadyVisible)
+ gtk_widget_hide(GTK_WIDGET(m_pWindow));
+ if (!bAlreadyRealized)
+ gtk_widget_unrealize(GTK_WIDGET(m_pWindow));
+ }
+
+ virtual weld::ScreenShotCollection collect_screenshot_data() override
+ {
+ weld::ScreenShotCollection aRet;
+
+ gtk_container_foreach(GTK_CONTAINER(m_pWindow), do_collect_screenshot_data, &aRet);
+
+ return aRet;
+ }
+
+ 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);
+
+ 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 */
+ }
+
+ 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;
+ gulong nSignalDeleteId = g_signal_connect(m_pDialog, "delete-event", G_CALLBACK(signal_delete), this);
+ 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;
+
+ gdk_threads_leave();
+ g_main_loop_run(m_pLoop);
+ gdk_threads_enter();
+
+ 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);
+ g_signal_handler_disconnect(m_pDialog, nSignalDeleteId);
+ 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
+{
+ void hideUnless(GtkContainer *pTop, const winset& rVisibleWidgets,
+ std::vector<GtkWidget*> &rWasVisibleWidgets)
+ {
+ GList* pChildren = gtk_container_get_children(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(GTK_CONTAINER(pChild), rVisibleWidgets, rWasVisibleWidgets);
+ }
+ }
+ g_list_free(pChildren);
+ }
+
+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
+ int m_nOldBorderWidth; // border width for expanded dialog
+
+ 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);
+ }
+
+ 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 */
+ }
+
+ 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);
+
+ static void signalActivate(GtkMenuItem*, gpointer data)
+ {
+ bool* pActivate = static_cast<bool*>(data);
+ *pActivate = true;
+ }
+
+ bool signal_screenshot_popup_menu(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))
+ {
+ gdk_threads_leave();
+ g_main_loop_run(pLoop);
+ gdk_threads_enter();
+ }
+
+ 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;
+ }
+
+ static gboolean signalScreenshotPopupMenu(GtkWidget*, gpointer widget)
+ {
+ GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
+ return pThis->signal_screenshot_popup_menu(nullptr);
+ }
+
+ 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;
+ }
+
+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)
+ , m_nOldBorderWidth(0)
+ {
+ 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);
+ g_signal_connect(m_pDialog, "button-press-event", G_CALLBACK(signalScreenshotButton), this);
+ }
+ }
+
+ 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;
+ m_nSignalDeleteId = g_signal_connect(m_pDialog, "delete-event", G_CALLBACK(signalAsyncDelete), this);
+
+ 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;
+ m_nSignalDeleteId = g_signal_connect(m_pDialog, "delete-event", G_CALLBACK(signalAsyncDelete), this);
+
+ 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_IS_DIALOG(m_pDialog))
+ sort_native_button_order(GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(m_pDialog))));
+ 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 OString& 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
+ {
+ return new GtkInstanceContainer(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(m_pDialog))), m_pBuilder, false);
+ }
+
+ 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);
+ }
+ //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(GTK_CONTAINER(pContentArea), aVisibleWidgets, m_aHiddenWidgets);
+
+ gtk_widget_set_size_request(pRefEdit, m_nOldEditWidth, -1);
+ 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);
+
+ // 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
+#if defined(GDK_WINDOWING_WAYLAND)
+ bool bWorkaroundSizeSpringingBack = DLSYM_GDK_IS_WAYLAND_DISPLAY(gtk_widget_get_display(m_pWidget));
+ if (bWorkaroundSizeSpringingBack)
+ gtk_widget_unmap(GTK_WIDGET(m_pDialog));
+#endif
+
+ resize_to_request();
+
+#if defined(GDK_WINDOWING_WAYLAND)
+ if (bWorkaroundSizeSpringingBack)
+ gtk_widget_map(GTK_WIDGET(m_pDialog));
+#endif
+
+ 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;
+ 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);
+ 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
+ {
+ return new GtkInstanceContainer(GTK_CONTAINER(gtk_message_dialog_get_message_area(m_pMessageDialog)), m_pBuilder, false);
+ }
+};
+
+class GtkInstanceAssistant : public GtkInstanceDialog, public virtual weld::Assistant
+{
+private:
+ GtkAssistant* m_pAssistant;
+ GtkWidget* m_pSidebar;
+ GtkWidget* m_pSidebarEventBox;
+ GtkButtonBox* m_pButtonBox;
+ 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<OString, bool> m_aNotClickable;
+
+ int find_page(const OString& rIdent) 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);
+ const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pPage));
+ if (g_strcmp0(pStr, rIdent.getStr()) == 0)
+ return i;
+ }
+ return -1;
+ }
+
+ static void wrap_sidebar_label(GtkWidget *pWidget, gpointer /*user_data*/)
+ {
+ if (GTK_IS_LABEL(pWidget))
+ {
+ gtk_label_set_line_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)
+ {
+ if (g_strcmp0(gtk_buildable_get_name(GTK_BUILDABLE(pWidget)), "sidebar") == 0)
+ {
+ GtkWidget **ppSidebar = static_cast<GtkWidget**>(user_data);
+ *ppSidebar = pWidget;
+ }
+ if (GTK_IS_CONTAINER(pWidget))
+ gtk_container_forall(GTK_CONTAINER(pWidget), find_sidebar, user_data);
+ }
+
+ static void signalHelpClicked(GtkButton*, gpointer widget)
+ {
+ GtkInstanceAssistant* pThis = static_cast<GtkInstanceAssistant*>(widget);
+ pThis->signal_help_clicked();
+ }
+
+ void signal_help_clicked()
+ {
+ help();
+ }
+
+ static gboolean signalButton(GtkWidget*, GdkEventButton* pEvent, gpointer widget)
+ {
+ GtkInstanceAssistant* pThis = static_cast<GtkInstanceAssistant*>(widget);
+ SolarMutexGuard aGuard;
+ return pThis->signal_button(pEvent);
+ }
+
+ bool signal_button(GdkEventButton* pEvent)
+ {
+ int nNewCurrentPage = -1;
+
+ GtkAllocation allocation;
+
+ int nPageIndex = 0;
+ 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);
+ if (!gtk_widget_get_visible(pWidget))
+ continue;
+
+ gtk_widget_get_allocation(pWidget, &allocation);
+
+ gint dest_x1, dest_y1;
+ gtk_widget_translate_coordinates(pWidget,
+ m_pSidebarEventBox,
+ 0,
+ 0,
+ &dest_x1,
+ &dest_y1);
+
+ gint dest_x2, dest_y2;
+ gtk_widget_translate_coordinates(pWidget,
+ m_pSidebarEventBox,
+ allocation.width,
+ allocation.height,
+ &dest_x2,
+ &dest_y2);
+
+
+ if (pEvent->x >= dest_x1 && pEvent->x <= dest_x2 && pEvent->y >= dest_y1 && pEvent->y <= dest_y2)
+ {
+ nNewCurrentPage = nPageIndex;
+ break;
+ }
+
+ ++nPageIndex;
+ }
+ g_list_free(pChildren);
+
+ if (nNewCurrentPage != -1 && nNewCurrentPage != get_current_page())
+ {
+ OString 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)
+ {
+ 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);
+
+ m_pBack = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Back)).getStr()));
+ gtk_widget_set_can_default(GTK_WIDGET(m_pBack), true);
+ gtk_buildable_set_name(GTK_BUILDABLE(m_pBack), "previous");
+ gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pBack), false, false, 0);
+
+ m_pNext = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Next)).getStr()));
+ gtk_widget_set_can_default(GTK_WIDGET(m_pNext), true);
+ gtk_buildable_set_name(GTK_BUILDABLE(m_pNext), "next");
+ gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pNext), false, false, 0);
+
+ m_pCancel = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Cancel)).getStr()));
+ gtk_widget_set_can_default(GTK_WIDGET(m_pCancel), true);
+ gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pCancel), false, false, 0);
+
+ m_pFinish = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Finish)).getStr()));
+ gtk_widget_set_can_default(GTK_WIDGET(m_pFinish), true);
+ gtk_buildable_set_name(GTK_BUILDABLE(m_pFinish), "finish");
+ gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pFinish), false, false, 0);
+
+ m_pHelp = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Help)).getStr()));
+ gtk_widget_set_can_default(GTK_WIDGET(m_pHelp), true);
+ g_signal_connect(m_pHelp, "clicked", G_CALLBACK(signalHelpClicked), this);
+ gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pHelp), false, false, 0);
+
+ gtk_assistant_add_action_widget(pAssistant, GTK_WIDGET(m_pButtonBox));
+ gtk_button_box_set_child_secondary(m_pButtonBox, GTK_WIDGET(m_pHelp), true);
+ gtk_widget_set_hexpand(GTK_WIDGET(m_pButtonBox), true);
+
+ GtkWidget* pParent = gtk_widget_get_parent(GTK_WIDGET(m_pButtonBox));
+ gtk_container_child_set(GTK_CONTAINER(pParent), GTK_WIDGET(m_pButtonBox), "expand", true, "fill", true, nullptr);
+ 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
+ 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);
+
+ gtk_widget_show_all(GTK_WIDGET(m_pButtonBox));
+
+ find_sidebar(GTK_WIDGET(m_pAssistant), &m_pSidebar);
+
+ m_pSidebarEventBox = ::ensureEventWidget(m_pSidebar);
+ m_nButtonPressSignalId = m_pSidebarEventBox ? g_signal_connect(m_pSidebarEventBox, "button-press-event", G_CALLBACK(signalButton), this) : 0;
+ }
+
+ 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 OString get_page_ident(int nPage) const override
+ {
+ const GtkWidget* pWidget = gtk_assistant_get_nth_page(m_pAssistant, nPage);
+ const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pWidget));
+ return OString(pStr, pStr ? strlen(pStr) : 0);
+ }
+
+ virtual OString 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 OString& rIdent) override
+ {
+ int nPage = find_page(rIdent);
+ if (nPage == -1)
+ return;
+ set_current_page(nPage);
+ }
+
+ virtual void set_page_title(const OString& 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());
+ gtk_container_forall(GTK_CONTAINER(m_pSidebar), wrap_sidebar_label, nullptr);
+ }
+
+ virtual OUString get_page_title(const OString& 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 OString& rIdent, bool bSensitive) override
+ {
+ m_aNotClickable[rIdent] = !bSensitive;
+ }
+
+ virtual void set_page_index(const OString& 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);
+ OString sTitle(gtk_assistant_get_page_title(m_pAssistant, pPage));
+ 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.getStr());
+ gtk_container_forall(GTK_CONTAINER(m_pSidebar), wrap_sidebar_label, nullptr);
+ g_object_unref(pPage);
+ }
+
+ virtual weld::Container* append_page(const OString& rIdent) override
+ {
+ disable_notify_events();
+
+ GtkWidget *pChild = gtk_grid_new();
+ gtk_buildable_set_name(GTK_BUILDABLE(pChild), rIdent.getStr());
+ 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();
+
+ m_aPages.emplace_back(new GtkInstanceContainer(GTK_CONTAINER(pChild), m_pBuilder, false));
+
+ return m_aPages.back().get();
+ }
+
+ virtual void set_page_side_help_id(const OString& 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 ~GtkInstanceAssistant() override
+ {
+ if (m_nButtonPressSignalId)
+ g_signal_handler_disconnect(m_pSidebarEventBox, m_nButtonPressSignalId);
+ }
+};
+
+class GtkInstanceFrame : public GtkInstanceContainer, public virtual weld::Frame
+{
+private:
+ GtkFrame* m_pFrame;
+public:
+ GtkInstanceFrame(GtkFrame* pFrame, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceContainer(GTK_CONTAINER(pFrame), pBuilder, bTakeOwnership)
+ , 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)
+ : GtkInstanceContainer(GTK_CONTAINER(pPaned), pBuilder, bTakeOwnership)
+ , 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 crippled_viewport_get_type();
+
+#define CRIPPLED_TYPE_VIEWPORT (crippled_viewport_get_type ())
+#define CRIPPLED_VIEWPORT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CRIPPLED_TYPE_VIEWPORT, CrippledViewport))
+#ifndef NDEBUG
+# define CRIPPLED_IS_VIEWPORT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CRIPPLED_TYPE_VIEWPORT))
+#endif
+
+namespace {
+
+struct CrippledViewport
+{
+ GtkViewport viewport;
+
+ GtkAdjustment *hadjustment;
+ GtkAdjustment *vadjustment;
+};
+
+}
+
+enum
+{
+ PROP_0,
+ PROP_HADJUSTMENT,
+ PROP_VADJUSTMENT,
+ PROP_HSCROLL_POLICY,
+ PROP_VSCROLL_POLICY,
+ PROP_SHADOW_TYPE
+};
+
+static void viewport_set_adjustment(CrippledViewport *viewport,
+ GtkOrientation orientation,
+ GtkAdjustment *adjustment)
+{
+ if (!adjustment)
+ adjustment = gtk_adjustment_new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ if (viewport->hadjustment)
+ g_object_unref(viewport->hadjustment);
+ viewport->hadjustment = adjustment;
+ }
+ else
+ {
+ if (viewport->vadjustment)
+ g_object_unref(viewport->vadjustment);
+ viewport->vadjustment = adjustment;
+ }
+
+ g_object_ref_sink(adjustment);
+}
+
+static void
+crippled_viewport_set_property(GObject* object,
+ guint prop_id,
+ const GValue* value,
+ GParamSpec* /*pspec*/)
+{
+ CrippledViewport *viewport = CRIPPLED_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
+crippled_viewport_get_property(GObject* object,
+ guint prop_id,
+ GValue* value,
+ GParamSpec* /*pspec*/)
+{
+ CrippledViewport *viewport = CRIPPLED_VIEWPORT(object);
+
+ switch (prop_id)
+ {
+ case PROP_HADJUSTMENT:
+ g_value_set_object(value, viewport->hadjustment);
+ break;
+ case PROP_VADJUSTMENT:
+ g_value_set_object(value, viewport->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 void crippled_viewport_class_init(GtkViewportClass *klass)
+{
+ GObjectClass* o_class = G_OBJECT_CLASS(klass);
+
+ /* GObject signals */
+ o_class->set_property = crippled_viewport_set_property;
+ o_class->get_property = crippled_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 crippled_viewport_get_type()
+{
+ static GType type = 0;
+
+ if (!type)
+ {
+ static const GTypeInfo tinfo =
+ {
+ sizeof (GtkViewportClass),
+ nullptr, /* base init */
+ nullptr, /* base finalize */
+ reinterpret_cast<GClassInitFunc>(crippled_viewport_class_init), /* class init */
+ nullptr, /* class finalize */
+ nullptr, /* class data */
+ sizeof (CrippledViewport), /* instance size */
+ 0, /* nb preallocs */
+ nullptr, /* instance init */
+ nullptr /* value table */
+ };
+
+ type = g_type_register_static( GTK_TYPE_VIEWPORT, "CrippledViewport",
+ &tinfo, GTypeFlags(0));
+ }
+
+ return type;
+}
+
+#define CUSTOM_TYPE_CELL_RENDERER_SURFACE (custom_cell_renderer_surface_get_type())
+#define CUSTOM_CELL_RENDERER_SURFACE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), CUSTOM_TYPE_CELL_RENDERER_SURFACE, CustomCellRendererSurface))
+#define CUSTOM_IS_CELL_RENDERER_SURFACE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CUSTOM_TYPE_CELL_RENDERER_SURFACE))
+
+namespace {
+
+ struct CustomCellRendererSurface
+ {
+ GtkCellRendererText parent;
+ VclPtr<VirtualDevice> device;
+ gchar *id;
+ gpointer instance;
+ };
+
+ struct CustomCellRendererSurfaceClass
+ {
+ GtkCellRendererTextClass parent_class;
+ };
+
+ enum
+ {
+ PROP_ID = 10000,
+ PROP_INSTANCE_TREE_VIEW = 10001
+ };
+}
+
+static gpointer custom_cell_renderer_surface_parent_class;
+
+static GType custom_cell_renderer_surface_get_type();
+static void custom_cell_renderer_surface_class_init(CustomCellRendererSurfaceClass *klass);
+
+GType custom_cell_renderer_surface_get_type()
+{
+ static GType type = 0;
+
+ if (!type)
+ {
+ static const GTypeInfo tinfo =
+ {
+ sizeof (CustomCellRendererSurfaceClass),
+ nullptr, /* base init */
+ nullptr, /* base finalize */
+ reinterpret_cast<GClassInitFunc>(custom_cell_renderer_surface_class_init), /* class init */
+ nullptr, /* class finalize */
+ nullptr, /* class data */
+ sizeof (CustomCellRendererSurface), /* instance size */
+ 0, /* nb preallocs */
+ nullptr, /* instance init */
+ nullptr /* value table */
+ };
+
+ // inherit from GtkCellRendererText so we can set the "text" property and get a11y support for that
+ type = g_type_register_static(GTK_TYPE_CELL_RENDERER_TEXT, "CustomCellRendererSurface",
+ &tinfo, GTypeFlags(0));
+ }
+
+ return type;
+}
+
+static void custom_cell_renderer_surface_get_property(GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ CustomCellRendererSurface *cellsurface = CUSTOM_CELL_RENDERER_SURFACE(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_surface_parent_class)->get_property(object, param_id, value, pspec);
+ break;
+ }
+}
+
+static void custom_cell_renderer_surface_set_property(GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CustomCellRendererSurface *cellsurface = CUSTOM_CELL_RENDERER_SURFACE(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_surface_parent_class)->set_property(object, param_id, value, pspec);
+ break;
+ }
+}
+
+static bool custom_cell_renderer_surface_get_preferred_size(GtkCellRenderer *cell,
+ GtkOrientation orientation,
+ gint *minimum_size,
+ gint *natural_size);
+
+static void custom_cell_renderer_surface_render(GtkCellRenderer* cell,
+ cairo_t* cr,
+ GtkWidget* widget,
+ const GdkRectangle* background_area,
+ const GdkRectangle* cell_area,
+ GtkCellRendererState flags);
+
+static void custom_cell_renderer_surface_finalize(GObject *object)
+{
+ CustomCellRendererSurface *cellsurface = CUSTOM_CELL_RENDERER_SURFACE(object);
+
+ g_free(cellsurface->id);
+ cellsurface->device.disposeAndClear();
+
+ G_OBJECT_CLASS(custom_cell_renderer_surface_parent_class)->finalize(object);
+}
+
+static void custom_cell_renderer_surface_get_preferred_width(GtkCellRenderer *cell,
+ GtkWidget *widget,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ if (!custom_cell_renderer_surface_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_surface_parent_class)->get_preferred_width(cell,
+ widget, minimum_size, natural_size);
+ }
+}
+
+static void custom_cell_renderer_surface_get_preferred_height(GtkCellRenderer *cell,
+ GtkWidget *widget,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ if (!custom_cell_renderer_surface_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_surface_parent_class)->get_preferred_height(cell,
+ widget, minimum_size, natural_size);
+ }
+
+}
+
+static void custom_cell_renderer_surface_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_surface_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_surface_class_init(CustomCellRendererSurfaceClass *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_surface_get_property;
+ object_class->set_property = custom_cell_renderer_surface_set_property;
+
+ custom_cell_renderer_surface_parent_class = g_type_class_peek_parent(klass);
+ object_class->finalize = custom_cell_renderer_surface_finalize;
+
+ cell_class->get_preferred_width = custom_cell_renderer_surface_get_preferred_width;
+ cell_class->get_preferred_height = custom_cell_renderer_surface_get_preferred_height;
+ cell_class->get_preferred_width_for_height = custom_cell_renderer_surface_get_preferred_width_for_height;
+ cell_class->get_preferred_height_for_width = custom_cell_renderer_surface_get_preferred_height_for_width;
+
+ cell_class->render = custom_cell_renderer_surface_render;
+
+ 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));
+
+ gtk_cell_renderer_class_set_accessible_type(cell_class, GTK_TYPE_TEXT_CELL_ACCESSIBLE);
+}
+
+static GtkCellRenderer* custom_cell_renderer_surface_new()
+{
+ return GTK_CELL_RENDERER(g_object_new(CUSTOM_TYPE_CELL_RENDERER_SURFACE, nullptr));
+}
+
+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;
+ 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)
+ : GtkInstanceContainer(GTK_CONTAINER(pScrolledWindow), pBuilder, bTakeOwnership)
+ , m_pScrolledWindow(pScrolledWindow)
+ , m_pOrigViewport(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))
+ {
+ }
+
+ virtual void set_user_managed_scrolling() override
+ {
+ 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);
+ GtkWidget *pViewport = gtk_bin_get_child(GTK_BIN(m_pScrolledWindow));
+ assert(GTK_IS_VIEWPORT(pViewport));
+ GtkWidget *pChild = gtk_bin_get_child(GTK_BIN(pViewport));
+ g_object_ref(pChild);
+ gtk_container_remove(GTK_CONTAINER(pViewport), pChild);
+ g_object_ref(pViewport);
+ gtk_container_remove(GTK_CONTAINER(m_pScrolledWindow), pViewport);
+ GtkWidget* pNewViewport = GTK_WIDGET(g_object_new(crippled_viewport_get_type(), nullptr));
+ gtk_widget_show(pNewViewport);
+ gtk_container_add(GTK_CONTAINER(m_pScrolledWindow), pNewViewport);
+ gtk_container_add(GTK_CONTAINER(pNewViewport), pChild);
+ 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, eGtkVPolicy, VclToGtk(eHPolicy));
+ }
+
+ virtual VclPolicyType get_hpolicy() const override
+ {
+ GtkPolicyType eGtkHPolicy;
+ gtk_scrolled_window_get_policy(m_pScrolledWindow, &eGtkHPolicy, nullptr);
+ return GtkToVcl(eGtkHPolicy);
+ }
+
+ virtual int get_hscroll_height() const override
+ {
+ if (gtk_scrolled_window_get_overlay_scrolling(m_pScrolledWindow))
+ return 0;
+ return gtk_widget_get_allocated_height(gtk_scrolled_window_get_hscrollbar(m_pScrolledWindow));
+ }
+
+ 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_vscroll_width() const override
+ {
+ if (gtk_scrolled_window_get_overlay_scrolling(m_pScrolledWindow))
+ return 0;
+ return gtk_widget_get_allocated_width(gtk_scrolled_window_get_vscrollbar(m_pScrolledWindow));
+ }
+
+ 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 ~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);
+
+ //put it back the way it was
+ if (m_pOrigViewport)
+ {
+ 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);
+
+ GtkWidget *pViewport = gtk_bin_get_child(GTK_BIN(m_pScrolledWindow));
+ assert(CRIPPLED_IS_VIEWPORT(pViewport));
+ GtkWidget *pChild = gtk_bin_get_child(GTK_BIN(pViewport));
+ g_object_ref(pChild);
+ gtk_container_remove(GTK_CONTAINER(pViewport), pChild);
+ g_object_ref(pViewport);
+ gtk_container_remove(GTK_CONTAINER(m_pScrolledWindow), pViewport);
+
+ gtk_container_add(GTK_CONTAINER(m_pScrolledWindow), m_pOrigViewport);
+ g_object_unref(m_pOrigViewport);
+ gtk_container_add(GTK_CONTAINER(m_pOrigViewport), pChild);
+ g_object_unref(pChild);
+ gtk_widget_destroy(pViewport);
+ g_object_unref(pViewport);
+ m_pOrigViewport = nullptr;
+ GtkInstanceContainer::enable_notify_events();
+ }
+ }
+};
+
+class GtkInstanceNotebook : public GtkInstanceContainer, public virtual weld::Notebook
+{
+private:
+ GtkNotebook* m_pNotebook;
+ GtkBox* m_pOverFlowBox;
+ GtkNotebook* m_pOverFlowNotebook;
+ gulong m_nSwitchPageSignalId;
+ gulong m_nOverFlowSwitchPageSignalId;
+ gulong m_nSizeAllocateSignalId;
+ gulong m_nFocusSignalId;
+ 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);
+ OString 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)
+ {
+ OString 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, "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();
+
+ gtk_notebook_set_current_page(m_pNotebook, nNewPage);
+
+ enable_notify_events();
+
+ // trigger main notebook switch-page callback
+ OString sNewIdent(get_page_ident(m_pNotebook, nNewPage));
+ m_aEnterPageHdl.Call(sNewIdent);
+ }
+
+ static OString get_page_ident(GtkNotebook *pNotebook, guint nPage)
+ {
+ const GtkWidget* pTabWidget = gtk_notebook_get_tab_label(pNotebook, gtk_notebook_get_nth_page(pNotebook, nPage));
+ const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pTabWidget));
+ return OString(pStr, pStr ? strlen(pStr) : 0);
+ }
+
+ static gint get_page_number(GtkNotebook *pNotebook, const OString& rIdent)
+ {
+ 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));
+ const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pTabWidget));
+ if (pStr && strcmp(pStr, rIdent.getStr()) == 0)
+ return i;
+ }
+ return -1;
+ }
+
+ int remove_page(GtkNotebook *pNotebook, const OString& rIdent)
+ {
+ disable_notify_events();
+ int nPageNumber = get_page_number(pNotebook, rIdent);
+ 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();
+ gtk_buildable_set_name(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 OString& rIdent, const OUString& rLabel, GtkWidget *pChild, int nPos)
+ {
+ disable_notify_events();
+
+ GtkWidget *pTabWidget = gtk_label_new(MapToGtkAccelerator(rLabel).getStr());
+ gtk_buildable_set_name(GTK_BUILDABLE(pTabWidget), rIdent.getStr());
+
+ gtk_notebook_insert_page(pNotebook, pChild, pTabWidget, nPos);
+ gtk_widget_show(pChild);
+ gtk_widget_show(pTabWidget);
+
+ enable_notify_events();
+ }
+
+ gint get_page_number(const OString& rIdent) const
+ {
+ 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;
+ }
+ }
+ }
+
+ 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));
+ gtk_container_add(GTK_CONTAINER(pParent), GTK_WIDGET(m_pOverFlowBox));
+ gtk_box_pack_start(m_pOverFlowBox, GTK_WIDGET(m_pOverFlowNotebook), false, false, 0);
+ g_object_ref(m_pNotebook);
+ gtk_container_remove(GTK_CONTAINER(pParent), GTK_WIDGET(m_pNotebook));
+ gtk_box_pack_start(m_pOverFlowBox, GTK_WIDGET(m_pNotebook), true, true, 0);
+ 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
+ gtk_notebook_set_scrollable(m_pNotebook, false);
+
+ gtk_widget_freeze_child_notify(GTK_WIDGET(m_pNotebook));
+ gtk_widget_freeze_child_notify(GTK_WIDGET(m_pOverFlowNotebook));
+
+ 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)
+ {
+ OString 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, "useless");
+
+ gtk_widget_thaw_child_notify(GTK_WIDGET(m_pOverFlowNotebook));
+ gtk_widget_thaw_child_notify(GTK_WIDGET(m_pNotebook));
+
+ 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_widget_get_child_visible(pTabWidget))
+ {
+ m_nLaunchSplitTimeoutId = g_timeout_add_full(G_PRIORITY_HIGH_IDLE, 0, reinterpret_cast<GSourceFunc>(launch_split_notebooks), this, nullptr);
+ break;
+ }
+ }
+ }
+ enable_notify_events();
+ }
+
+ static void signalSizeAllocate(GtkWidget*, GdkRectangle*, gpointer widget)
+ {
+ GtkInstanceNotebook* pThis = static_cast<GtkInstanceNotebook*>(widget);
+ pThis->signal_notebook_size_allocate();
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ // 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)
+ : GtkInstanceContainer(GTK_CONTAINER(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))
+ , m_nFocusSignalId(g_signal_connect(pNotebook, "focus", G_CALLBACK(signalFocus), this))
+ , 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)
+ {
+ gtk_widget_add_events(GTK_WIDGET(pNotebook), GDK_SCROLL_MASK);
+ if (get_n_pages() > 6)
+ m_nSizeAllocateSignalId = g_signal_connect_after(pNotebook, "size-allocate", G_CALLBACK(signalSizeAllocate), this);
+ else
+ m_nSizeAllocateSignalId = 0;
+ 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
+ GtkStyleContext *pNotebookContext = gtk_widget_get_style_context(GTK_WIDGET(m_pOverFlowNotebook));
+ GtkCssProvider *pProvider = gtk_css_provider_new();
+ static const gchar data[] = "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; }";
+ static const gchar olddata[] = "tab.top:active { 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; }";
+ gtk_css_provider_load_from_data(pProvider, gtk_check_version(3, 20, 0) == nullptr ? data : olddata, -1, nullptr);
+ gtk_style_context_add_provider(pNotebookContext, GTK_STYLE_PROVIDER(pProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+
+ 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 OString 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 OString get_current_page_ident() const override
+ {
+ const int nPage = get_current_page();
+ return nPage != -1 ? get_page_ident(nPage) : OString();
+ }
+
+ virtual weld::Container* get_page(const OString& rIdent) const override
+ {
+ int nPage = get_page_number(rIdent);
+ if (nPage < 0)
+ return nullptr;
+
+ GtkContainer* pChild;
+ if (m_bOverFlowBoxIsStart)
+ {
+ auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
+ if (nPage < nOverFlowLen)
+ pChild = GTK_CONTAINER(gtk_notebook_get_nth_page(m_pOverFlowNotebook, nPage));
+ else
+ {
+ nPage -= nOverFlowLen;
+ pChild = GTK_CONTAINER(gtk_notebook_get_nth_page(m_pNotebook, nPage));
+ }
+ }
+ else
+ {
+ auto nMainLen = gtk_notebook_get_n_pages(m_pNotebook);
+ if (nPage < nMainLen)
+ pChild = GTK_CONTAINER(gtk_notebook_get_nth_page(m_pNotebook, nPage));
+ else
+ {
+ nPage -= nMainLen;
+ pChild = GTK_CONTAINER(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 (!m_aPages[nPageIndex])
+ m_aPages[nPageIndex].reset(new GtkInstanceContainer(pChild, m_pBuilder, false));
+ 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 OString& rIdent) override
+ {
+ gint nPage = get_page_number(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 OString& 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 OString& 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 disable_notify_events() override
+ {
+ g_signal_handler_block(m_pNotebook, m_nSwitchPageSignalId);
+ g_signal_handler_block(m_pNotebook, m_nFocusSignalId);
+ g_signal_handler_block(m_pNotebook, m_nChangeCurrentPageId);
+ g_signal_handler_block(m_pOverFlowNotebook, m_nOverFlowSwitchPageSignalId);
+ gtk_widget_freeze_child_notify(GTK_WIDGET(m_pOverFlowNotebook));
+ GtkInstanceContainer::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceContainer::enable_notify_events();
+ gtk_widget_thaw_child_notify(GTK_WIDGET(m_pOverFlowNotebook));
+ g_signal_handler_unblock(m_pOverFlowNotebook, m_nOverFlowSwitchPageSignalId);
+ g_signal_handler_unblock(m_pNotebook, m_nSwitchPageSignalId);
+ g_signal_handler_unblock(m_pNotebook, m_nFocusSignalId);
+ 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 OString& 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 OString& 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 (m_nSizeAllocateSignalId)
+ g_signal_handler_disconnect(m_pNotebook, m_nSizeAllocateSignalId);
+ g_signal_handler_disconnect(m_pNotebook, m_nSwitchPageSignalId);
+ g_signal_handler_disconnect(m_pNotebook, m_nFocusSignalId);
+ g_signal_handler_disconnect(m_pNotebook, m_nChangeCurrentPageId);
+ g_signal_handler_disconnect(m_pOverFlowNotebook, m_nOverFlowSwitchPageSignalId);
+ gtk_widget_destroy(GTK_WIDGET(m_pOverFlowNotebook));
+ if (m_pOverFlowBox)
+ {
+ // put it back to how we found it initially
+ GtkWidget* pParent = gtk_widget_get_parent(GTK_WIDGET(m_pOverFlowBox));
+ g_object_ref(m_pNotebook);
+ gtk_container_remove(GTK_CONTAINER(m_pOverFlowBox), GTK_WIDGET(m_pNotebook));
+ gtk_container_add(GTK_CONTAINER(pParent), GTK_WIDGET(m_pNotebook));
+ g_object_unref(m_pNotebook);
+
+ gtk_widget_destroy(GTK_WIDGET(m_pOverFlowBox));
+ }
+ }
+};
+
+class GtkInstanceButton : public GtkInstanceContainer, public virtual weld::Button
+{
+private:
+ GtkButton* m_pButton;
+ gulong m_nSignalId;
+
+ static void signalClicked(GtkButton*, gpointer widget)
+ {
+ GtkInstanceButton* pThis = static_cast<GtkInstanceButton*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_clicked();
+ }
+
+public:
+ GtkInstanceButton(GtkButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceContainer(GTK_CONTAINER(pButton), pBuilder, bTakeOwnership)
+ , m_pButton(pButton)
+ , m_nSignalId(g_signal_connect(pButton, "clicked", G_CALLBACK(signalClicked), this))
+ {
+ g_object_set_data(G_OBJECT(m_pButton), "g-lo-GtkInstanceButton", this);
+ }
+
+ virtual void set_label(const OUString& rText) override
+ {
+ ::set_label(m_pButton, rText);
+ }
+
+ virtual void set_image(VirtualDevice* pDevice) override
+ {
+ gtk_button_set_always_show_image(m_pButton, true);
+ gtk_button_set_image_position(m_pButton, GTK_POS_LEFT);
+ if (pDevice)
+ gtk_button_set_image(m_pButton, image_new_from_virtual_device(*pDevice));
+ else
+ gtk_button_set_image(m_pButton, nullptr);
+ }
+
+ virtual void set_from_icon_name(const OUString& rIconName) override
+ {
+ GdkPixbuf* pixbuf = load_icon_by_name(rIconName);
+ if (!pixbuf)
+ gtk_button_set_image(m_pButton, nullptr);
+ else
+ {
+ gtk_button_set_image(m_pButton, gtk_image_new_from_pixbuf(pixbuf));
+ g_object_unref(pixbuf);
+ }
+ }
+
+ virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage) override
+ {
+ GdkPixbuf* pixbuf = getPixbuf(rImage);
+ if (!pixbuf)
+ gtk_button_set_image(m_pButton, nullptr);
+ else
+ {
+ gtk_button_set_image(m_pButton, gtk_image_new_from_pixbuf(pixbuf));
+ g_object_unref(pixbuf);
+ }
+ }
+
+ virtual OUString get_label() const override
+ {
+ return ::get_label(m_pButton);
+ }
+
+ virtual void set_label_line_wrap(bool wrap) override
+ {
+ GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pButton));
+ gtk_label_set_line_wrap(GTK_LABEL(pChild), wrap);
+ }
+
+ // 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);
+ GtkInstanceContainer::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceContainer::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)
+{
+ 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;
+
+ 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()
+{
+ if (GTK_IS_DIALOG(m_pDialog))
+ sort_native_button_order(GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(m_pDialog))));
+ 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
+{
+private:
+ GtkToggleButton* m_pToggleButton;
+ gulong m_nSignalId;
+
+ 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_nSignalId(g_signal_connect(m_pToggleButton, "toggled", G_CALLBACK(signalToggled), this))
+ {
+ }
+
+ virtual void set_active(bool active) override
+ {
+ disable_notify_events();
+ gtk_toggle_button_set_inconsistent(m_pToggleButton, 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
+ {
+ gtk_toggle_button_set_inconsistent(m_pToggleButton, inconsistent);
+ }
+
+ virtual bool get_inconsistent() const override
+ {
+ return gtk_toggle_button_get_inconsistent(m_pToggleButton);
+ }
+
+ virtual void disable_notify_events() override
+ {
+ g_signal_handler_block(m_pToggleButton, m_nSignalId);
+ GtkInstanceButton::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceButton::enable_notify_events();
+ g_signal_handler_unblock(m_pToggleButton, m_nSignalId);
+ }
+
+ virtual ~GtkInstanceToggleButton() override
+ {
+ g_signal_handler_disconnect(m_pToggleButton, m_nSignalId);
+ }
+};
+
+void do_grab(GtkWidget* pWidget)
+{
+ GdkDisplay *pDisplay = gtk_widget_get_display(pWidget);
+#if GTK_CHECK_VERSION(3, 20, 0)
+ if (gtk_check_version(3, 20, 0) == nullptr)
+ {
+ GdkSeat* pSeat = gdk_display_get_default_seat(pDisplay);
+ gdk_seat_grab(pSeat, gtk_widget_get_window(pWidget),
+ GDK_SEAT_CAPABILITY_ALL, true, nullptr, nullptr, nullptr, nullptr);
+ return;
+ }
+#endif
+ //else older gtk3
+ GdkDeviceManager* pDeviceManager = gdk_display_get_device_manager(pDisplay);
+ GdkDevice* pPointer = gdk_device_manager_get_client_pointer(pDeviceManager);
+ GdkWindow* pWindow = gtk_widget_get_window(pWidget);
+ guint32 nCurrentTime = gtk_get_current_event_time();
+ gdk_device_grab(pPointer, pWindow, GDK_OWNERSHIP_NONE, true, GDK_ALL_EVENTS_MASK, nullptr, nCurrentTime);
+ if (GdkDevice* pKeyboard = gdk_device_get_associated_device(pPointer))
+ gdk_device_grab(pKeyboard, pWindow, GDK_OWNERSHIP_NONE, true, GDK_ALL_EVENTS_MASK, nullptr, nCurrentTime);
+}
+
+void do_ungrab(GtkWidget* pWidget)
+{
+ GdkDisplay *pDisplay = gtk_widget_get_display(pWidget);
+#if GTK_CHECK_VERSION(3, 20, 0)
+ if (gtk_check_version(3, 20, 0) == nullptr)
+ {
+ GdkSeat* pSeat = gdk_display_get_default_seat(pDisplay);
+ gdk_seat_ungrab(pSeat);
+ return;
+ }
+#endif
+ //else older gtk3
+ GdkDeviceManager* pDeviceManager = gdk_display_get_device_manager(pDisplay);
+ GdkDevice* pPointer = gdk_device_manager_get_client_pointer(pDeviceManager);
+ guint32 nCurrentTime = gtk_get_current_event_time();
+ gdk_device_ungrab(pPointer, nCurrentTime);
+ if (GdkDevice* pKeyboard = gdk_device_get_associated_device(pPointer))
+ gdk_device_ungrab(pKeyboard, nCurrentTime);
+}
+
+GtkPositionType show_menu_older_gtk(GtkWidget* pMenuButton, GtkWindow* pMenu)
+{
+ //place the toplevel just below its launcher button
+ GtkWidget* pToplevel = gtk_widget_get_toplevel(pMenuButton);
+ gint x, y, absx, absy;
+ gtk_widget_translate_coordinates(pMenuButton, pToplevel, 0, 0, &x, &y);
+ GdkWindow *pWindow = gtk_widget_get_window(pToplevel);
+ gdk_window_get_position(pWindow, &absx, &absy);
+
+ x += absx;
+ y += absy;
+
+ gint nButtonHeight = gtk_widget_get_allocated_height(pMenuButton);
+ y += nButtonHeight;
+
+ gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(pToplevel)), pMenu);
+ gtk_window_set_transient_for(pMenu, GTK_WINDOW(pToplevel));
+
+ GtkRequisition req;
+ gtk_widget_get_preferred_size(GTK_WIDGET(pMenu), nullptr, &req);
+ gint nMenuWidth = req.width;
+ gint nMenuHeight = req.height;
+
+ 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);
+ gint endx = x + nMenuWidth;
+ if (endx > aWorkArea.Right())
+ {
+ x -= endx - aWorkArea.Right();
+ if (x < 0)
+ x = 0;
+ }
+
+ GtkPositionType ePosUsed = GTK_POS_BOTTOM;
+
+ gint endy = y + nMenuHeight;
+ gint nMissingBelow = endy - aWorkArea.Bottom();
+ if (nMissingBelow > 0)
+ {
+ gint nNewY = y - (nButtonHeight + nMenuHeight);
+ if (nNewY < aWorkArea.Top())
+ {
+ gint nMissingAbove = aWorkArea.Top() - nNewY;
+ 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
+ {
+ y = nNewY;
+ ePosUsed = GTK_POS_TOP;
+ }
+ }
+
+ gtk_window_move(pMenu, x, y);
+
+ return ePosUsed;
+}
+
+bool show_menu_newer_gtk(GtkWidget* pComboBox, GtkWindow* pMenu)
+{
+ 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;
+
+#if defined(GDK_WINDOWING_X11)
+ // 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;
+#endif
+
+ //place the toplevel just below its launcher button
+ GtkWidget* pToplevel = gtk_widget_get_toplevel(pComboBox);
+ gint x, y;
+ gtk_widget_translate_coordinates(pComboBox, pToplevel, 0, 0, &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));
+
+ gint nComboWidth = gtk_widget_get_allocated_width(pComboBox);
+ gint nComboHeight = gtk_widget_get_allocated_height(pComboBox);
+
+ bool bSwapForRTL = SwapForRTL(GTK_WIDGET(pComboBox));
+
+ GdkGravity rect_anchor = !bSwapForRTL ? GDK_GRAVITY_SOUTH_WEST : GDK_GRAVITY_SOUTH_EAST;
+ GdkGravity menu_anchor = !bSwapForRTL ? GDK_GRAVITY_NORTH_WEST : GDK_GRAVITY_NORTH_EAST;
+ GdkRectangle rect {x, y, nComboWidth, nComboHeight };
+ GdkWindow* toplevel = gtk_widget_get_window(GTK_WIDGET(pMenu));
+
+ window_move_to_rect(toplevel, &rect, rect_anchor, menu_anchor,
+ static_cast<GdkAnchorHints>(GDK_ANCHOR_FLIP_Y | GDK_ANCHOR_RESIZE_Y |
+ GDK_ANCHOR_SLIDE_X | GDK_ANCHOR_RESIZE_X),
+ 0, 0);
+
+ return true;
+}
+
+GtkPositionType show_menu(GtkWidget* pMenuButton, GtkWindow* pMenu)
+{
+ // 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 = gtk_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))
+ ePosUsed = show_menu_older_gtk(pMenuButton, pMenu);
+ gtk_widget_show_all(GTK_WIDGET(pMenu));
+ gtk_widget_grab_focus(GTK_WIDGET(pMenu));
+ do_grab(GTK_WIDGET(pMenu));
+
+ return ePosUsed;
+}
+
+class GtkInstanceMenuButton : public GtkInstanceToggleButton, public MenuHelper, public virtual weld::MenuButton
+{
+private:
+ GtkMenuButton* m_pMenuButton;
+ GtkBox* m_pBox;
+ GtkImage* m_pImage;
+ GtkWidget* m_pLabel;
+ //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;
+ GtkWidget* m_pPopover;
+ gulong m_nSignalId;
+
+ static void signalToggled(GtkWidget*, gpointer widget)
+ {
+ GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->toggle_menu();
+ }
+
+ void toggle_menu()
+ {
+ if (!m_pMenuHack)
+ return;
+ if (!get_active())
+ {
+ do_ungrab(GTK_WIDGET(m_pMenuHack));
+
+ gtk_widget_hide(GTK_WIDGET(m_pMenuHack));
+ //put contents back from where the came from
+ GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pMenuHack));
+ g_object_ref(pChild);
+ gtk_container_remove(GTK_CONTAINER(m_pMenuHack), pChild);
+ gtk_container_add(GTK_CONTAINER(m_pPopover), pChild);
+ g_object_unref(pChild);
+
+ // so gdk_window_move_to_rect will work again the next time
+ gtk_widget_unrealize(GTK_WIDGET(m_pMenuHack));
+
+ gtk_widget_set_size_request(GTK_WIDGET(m_pMenuHack), -1, -1);
+
+ // undo show_menu tooltip blocking
+ GtkWidget* pParent = gtk_widget_get_toplevel(GTK_WIDGET(m_pMenuButton));
+ GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(pParent) : nullptr;
+ if (pFrame)
+ pFrame->UnblockTooltip();
+ }
+ else
+ {
+ //set border width
+ gtk_container_set_border_width(GTK_CONTAINER(m_pMenuHack), gtk_container_get_border_width(GTK_CONTAINER(m_pPopover)));
+
+ //steal popover contents and smuggle into toplevel display window
+ GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pPopover));
+ g_object_ref(pChild);
+ gtk_container_remove(GTK_CONTAINER(m_pPopover), pChild);
+ gtk_container_add(GTK_CONTAINER(m_pMenuHack), pChild);
+ g_object_unref(pChild);
+
+ GtkPositionType ePosUsed = show_menu(m_pMenuHackAlign ? m_pMenuHackAlign : GTK_WIDGET(m_pMenuButton), m_pMenuHack);
+ // 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);
+ }
+ }
+
+ 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
+ {
+ //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 signalButtonRelease(GtkWidget* pWidget, GdkEventButton* pEvent, gpointer widget)
+ {
+ GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
+ return pThis->button_release(pWidget, pEvent);
+ }
+
+ bool button_release(GtkWidget* pWidget, 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 xoffset, yoffset;
+ gdk_window_get_root_origin(gtk_widget_get_window(pWidget), &xoffset, &yoffset);
+
+ GtkAllocation alloc;
+ gtk_widget_get_allocation(pWidget, &alloc);
+ xoffset += alloc.x;
+ yoffset += alloc.y;
+
+ gtk_widget_get_allocation(GTK_WIDGET(m_pMenuHack), &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;
+
+ 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;
+ }
+
+ void ensure_image_widget()
+ {
+ if (!m_pImage)
+ {
+ 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);
+ gtk_widget_show(GTK_WIDGET(m_pImage));
+ }
+ }
+
+public:
+ GtkInstanceMenuButton(GtkMenuButton* pMenuButton, GtkWidget* pMenuAlign, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceToggleButton(GTK_TOGGLE_BUTTON(pMenuButton), pBuilder, bTakeOwnership)
+ , MenuHelper(gtk_menu_button_get_popup(pMenuButton), false)
+ , m_pMenuButton(pMenuButton)
+ , m_pImage(nullptr)
+ , m_pMenuHack(nullptr)
+ , m_pMenuHackAlign(pMenuAlign)
+ , m_pPopover(nullptr)
+ , m_nSignalId(0)
+ {
+ m_pLabel = gtk_bin_get_child(GTK_BIN(m_pMenuButton));
+ //do it "manually" so we can have the dropdown image in GtkMenuButtons shown
+ //on the right at the same time as this image is shown on the left
+ g_object_ref(m_pLabel);
+ gtk_container_remove(GTK_CONTAINER(m_pMenuButton), m_pLabel);
+
+ gint nImageSpacing(2);
+ GtkStyleContext *pContext = gtk_widget_get_style_context(GTK_WIDGET(m_pMenuButton));
+ gtk_style_context_get_style(pContext, "image-spacing", &nImageSpacing, nullptr);
+ m_pBox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, nImageSpacing));
+
+ gtk_box_pack_start(m_pBox, m_pLabel, false, false, 0);
+ g_object_unref(m_pLabel);
+
+ if (gtk_toggle_button_get_mode(GTK_TOGGLE_BUTTON(m_pMenuButton)))
+ gtk_box_pack_end(m_pBox, gtk_image_new_from_icon_name("pan-down-symbolic", GTK_ICON_SIZE_BUTTON), false, false, 0);
+
+ gtk_container_add(GTK_CONTAINER(m_pMenuButton), GTK_WIDGET(m_pBox));
+ gtk_widget_show_all(GTK_WIDGET(m_pBox));
+ }
+
+ 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 (pDevice)
+ {
+ if (gtk_check_version(3, 20, 0) == nullptr)
+ gtk_image_set_from_surface(m_pImage, get_underlying_cairo_surface(*pDevice));
+ else
+ {
+ GdkPixbuf* pixbuf = getPixbuf(*pDevice);
+ gtk_image_set_from_pixbuf(m_pImage, pixbuf);
+ g_object_unref(pixbuf);
+ }
+ }
+ else
+ gtk_image_set_from_surface(m_pImage, nullptr);
+ }
+
+ virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage) override
+ {
+ ensure_image_widget();
+ GdkPixbuf* pixbuf = getPixbuf(rImage);
+ if (pixbuf)
+ {
+ gtk_image_set_from_pixbuf(m_pImage, pixbuf);
+ g_object_unref(pixbuf);
+ }
+ else
+ gtk_image_set_from_surface(m_pImage, nullptr);
+ }
+
+ 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 OString& rId) override
+ {
+ MenuHelper::remove_item(rId);
+ }
+
+ virtual void clear() override
+ {
+ clear_items();
+ }
+
+ virtual void set_item_active(const OString& rIdent, bool bActive) override
+ {
+ MenuHelper::set_item_active(rIdent, bActive);
+ }
+
+ virtual void set_item_sensitive(const OString& rIdent, bool bSensitive) override
+ {
+ MenuHelper::set_item_sensitive(rIdent, bSensitive);
+ }
+
+ virtual void set_item_label(const OString& rIdent, const OUString& rLabel) override
+ {
+ MenuHelper::set_item_label(rIdent, rLabel);
+ }
+
+ virtual OUString get_item_label(const OString& rIdent) const override
+ {
+ return MenuHelper::get_item_label(rIdent);
+ }
+
+ virtual void set_item_visible(const OString& rIdent, bool bVisible) override
+ {
+ MenuHelper::set_item_visible(rIdent, bVisible);
+ }
+
+ virtual void set_item_help_id(const OString& rIdent, const OString& rHelpId) override
+ {
+ MenuHelper::set_item_help_id(rIdent, rHelpId);
+ }
+
+ virtual OString get_item_help_id(const OString& rIdent) const override
+ {
+ return MenuHelper::get_item_help_id(rIdent);
+ }
+
+ virtual void signal_activate(GtkMenuItem* pItem) override
+ {
+ const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pItem));
+ signal_selected(OString(pStr, pStr ? strlen(pStr) : 0));
+ }
+
+ virtual void set_popover(weld::Widget* pPopover) override
+ {
+ GtkInstanceWidget* pPopoverWidget = dynamic_cast<GtkInstanceWidget*>(pPopover);
+ m_pPopover = pPopoverWidget ? pPopoverWidget->getWidget() : nullptr;
+
+#if defined(GDK_WINDOWING_X11)
+ 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))
+ {
+ 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_modal(m_pMenuHack, true);
+ gtk_window_set_resizable(m_pMenuHack, false);
+ m_nSignalId = g_signal_connect(GTK_TOGGLE_BUTTON(m_pMenuButton), "toggled", G_CALLBACK(signalToggled), this);
+ g_signal_connect(m_pMenuHack, "grab-broken-event", G_CALLBACK(signalGrabBroken), this);
+ g_signal_connect(m_pMenuHack, "button-release-event", G_CALLBACK(signalButtonRelease), this);
+ g_signal_connect(m_pMenuHack, "key-press-event", G_CALLBACK(keyPress), this);
+ }
+ }
+#endif
+
+ 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; }";
+ gtk_css_provider_load_from_data(pProvider, data, -1, nullptr);
+ 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);
+ if (m_pPopover)
+ gtk_widget_show_all(m_pPopover);
+ }
+ }
+
+ void set_menu(weld::Menu* pMenu);
+
+ virtual ~GtkInstanceMenuButton() override
+ {
+ 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));
+ }
+ }
+};
+
+class GtkInstanceMenu : public MenuHelper, public virtual weld::Menu
+{
+protected:
+ std::vector<GtkMenuItem*> m_aExtraItems;
+ OString m_sActivated;
+ MenuHelper* m_pTopLevelMenuHelper;
+
+private:
+ virtual void signal_activate(GtkMenuItem* pItem) override
+ {
+ const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pItem));
+ m_sActivated = OString(pStr, pStr ? strlen(pStr) : 0);
+ weld::Menu::signal_activate(m_sActivated);
+ }
+
+ 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();
+ }
+
+public:
+ GtkInstanceMenu(GtkMenu* pMenu, bool bTakeOwnership)
+ : MenuHelper(pMenu, bTakeOwnership)
+ , m_pTopLevelMenuHelper(nullptr)
+ {
+ g_object_set_data(G_OBJECT(m_pMenu), "g-lo-GtkInstanceMenu", this);
+ // 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)
+ {
+ // 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);
+ }
+ }
+ }
+
+ virtual OString popup_at_rect(weld::Widget* pParent, const tools::Rectangle &rRect) override
+ {
+ m_sActivated.clear();
+
+ GtkInstanceWidget* pGtkWidget = dynamic_cast<GtkInstanceWidget*>(pParent);
+ assert(pGtkWidget);
+
+ GtkWidget* pWidget = pGtkWidget->getWidget();
+ gtk_menu_attach_to_widget(m_pMenu, pWidget, nullptr);
+
+ //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);
+ 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{static_cast<int>(rRect.Left()), static_cast<int>(rRect.Top()),
+ static_cast<int>(rRect.GetWidth()), static_cast<int>(rRect.GetHeight())};
+ if (SwapForRTL(pWidget))
+ aRect.x = gtk_widget_get_allocated_width(pWidget) - aRect.width - 1 - aRect.x;
+
+ // 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
+ GdkEvent *event = GtkSalFrame::makeFakeKeyPress(pWidget);
+ gtk_main_do_event(event);
+ gdk_event_free(event);
+
+ gtk_menu_popup_at_rect(m_pMenu, gtk_widget_get_window(pWidget), &aRect, GDK_GRAVITY_NORTH_WEST, GDK_GRAVITY_NORTH_WEST, nullptr);
+ }
+ else
+#else
+ (void) rRect;
+#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);
+ }
+
+ if (g_main_loop_is_running(pLoop))
+ {
+ gdk_threads_leave();
+ g_main_loop_run(pLoop);
+ gdk_threads_enter();
+ }
+ g_main_loop_unref(pLoop);
+ g_signal_handler_disconnect(m_pMenu, nSignalId);
+ gtk_menu_detach(m_pMenu);
+
+ return m_sActivated;
+ }
+
+ virtual void set_sensitive(const OString& rIdent, bool bSensitive) override
+ {
+ set_item_sensitive(rIdent, bSensitive);
+ }
+
+ virtual void set_active(const OString& rIdent, bool bActive) override
+ {
+ set_item_active(rIdent, bActive);
+ }
+
+ virtual bool get_active(const OString& rIdent) const override
+ {
+ return get_item_active(rIdent);
+ }
+
+ virtual void set_visible(const OString& rIdent, bool bShow) override
+ {
+ set_item_visible(rIdent, bShow);
+ }
+
+ virtual void set_label(const OString& rIdent, const OUString& rLabel) override
+ {
+ set_item_label(rIdent, rLabel);
+ }
+
+ virtual OUString get_label(const OString& 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
+ {
+ clear_extras();
+ clear_items();
+ }
+
+ virtual void insert(int pos, const OUString& rId, const OUString& rStr,
+ const OUString* pIconName, VirtualDevice* pImageSurface,
+ TriState eCheckRadioFalse) override
+ {
+ GtkWidget* pImage = nullptr;
+ if (pIconName)
+ {
+ if (GdkPixbuf* pixbuf = load_icon_by_name(*pIconName))
+ {
+ pImage = gtk_image_new_from_pixbuf(pixbuf);
+ g_object_unref(pixbuf);
+ }
+ }
+ else if (pImageSurface)
+ {
+ pImage = image_new_from_virtual_device(*pImageSurface);
+ }
+
+ GtkWidget *pItem;
+ if (pImage)
+ {
+ GtkWidget *pBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
+ GtkWidget *pLabel = gtk_label_new(MapToGtkAccelerator(rStr).getStr());
+ pItem = eCheckRadioFalse != TRISTATE_INDET ? gtk_check_menu_item_new() : gtk_menu_item_new();
+ gtk_container_add(GTK_CONTAINER(pBox), pImage);
+ gtk_container_add(GTK_CONTAINER(pBox), pLabel);
+ gtk_container_add(GTK_CONTAINER(pItem), 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);
+
+ gtk_buildable_set_name(GTK_BUILDABLE(pItem), OUStringToOString(rId, RTL_TEXTENCODING_UTF8).getStr());
+ 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);
+ }
+
+ virtual int n_children() const override
+ {
+ GList* pChildren = gtk_container_get_children(GTK_CONTAINER(m_pMenu));
+ int nLen = g_list_length(pChildren);
+ g_list_free(pChildren);
+ return nLen;
+ }
+
+ void remove(const OString& rIdent) override
+ {
+ 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())
+ {
+ m_pTopLevelMenuHelper->remove_from_map(pMenuItem);
+ m_aExtraItems.erase(iter);
+ }
+ }
+ MenuHelper::remove_item(rIdent);
+ }
+
+ virtual ~GtkInstanceMenu() override
+ {
+ clear_extras();
+ g_object_steal_data(G_OBJECT(m_pMenu), "g-lo-GtkInstanceMenu");
+ }
+};
+
+ vcl::ImageType GtkToVcl(GtkIconSize eSize)
+ {
+ vcl::ImageType eRet;
+ switch (eSize)
+ {
+ 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;
+ }
+ return eRet;
+ }
+
+ GtkIconSize VclToGtk(vcl::ImageType eSize)
+ {
+ GtkIconSize eRet;
+ 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;
+ }
+ return eRet;
+ }
+}
+
+void GtkInstanceMenuButton::set_menu(weld::Menu* pMenu)
+{
+ GtkInstanceMenu* pPopoverWidget = dynamic_cast<GtkInstanceMenu*>(pMenu);
+ m_pPopover = nullptr;
+ GtkWidget* pMenuWidget = GTK_WIDGET(pPopoverWidget ? pPopoverWidget->getMenu() : nullptr);
+ gtk_menu_button_set_popup(m_pMenuButton, pMenuWidget);
+}
+
+namespace {
+
+class GtkInstanceToolbar : public GtkInstanceWidget, public virtual weld::Toolbar
+{
+private:
+ GtkToolbar* m_pToolbar;
+ GtkCssProvider *m_pMenuButtonProvider;
+
+ std::map<OString, GtkToolItem*> m_aMap;
+ std::map<OString, std::unique_ptr<GtkInstanceMenuButton>> m_aMenuButtonMap;
+
+ // 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);
+ }
+
+ static void collect(GtkWidget* pItem, gpointer widget)
+ {
+ if (GTK_IS_TOOL_ITEM(pItem))
+ {
+ GtkToolItem* pToolItem = GTK_TOOL_ITEM(pItem);
+ GtkInstanceToolbar* pThis = static_cast<GtkInstanceToolbar*>(widget);
+
+ GtkMenuButton* pMenuButton = nullptr;
+ if (GTK_IS_MENU_TOOL_BUTTON(pItem))
+ find_menu_button(pItem, &pMenuButton);
+
+ pThis->add_to_map(pToolItem, pMenuButton);
+ }
+ }
+
+ void add_to_map(GtkToolItem* pToolItem, GtkMenuButton* pMenuButton)
+ {
+ const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pToolItem));
+ OString id(pStr, pStr ? strlen(pStr) : 0);
+ 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
+ gtk_button_set_focus_on_click(GTK_BUTTON(pMenuButton), false);
+ g_signal_connect(pMenuButton, "toggled", G_CALLBACK(signalItemToggled), this);
+
+ if (pMenuButton)
+ {
+ // 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;"
+ "}";
+ const gchar olddata[] = "* { "
+ "padding: 0;"
+ "margin-left: 0px;"
+ "margin-right: 0px;"
+ "}";
+ gtk_css_provider_load_from_data(m_pMenuButtonProvider, gtk_check_version(3, 20, 0) == nullptr ? data : olddata, -1, nullptr);
+ }
+
+ gtk_style_context_add_provider(pButtonContext,
+ GTK_STYLE_PROVIDER(m_pMenuButtonProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ gtk_style_context_add_class(pButtonContext, "small-button");
+ }
+
+ }
+ if (!GTK_IS_TOOL_BUTTON(pToolItem))
+ return;
+ g_signal_connect(pToolItem, "clicked", G_CALLBACK(signalItemClicked), this);
+ }
+
+ static void signalItemClicked(GtkToolButton* pItem, gpointer widget)
+ {
+ GtkInstanceToolbar* pThis = static_cast<GtkInstanceToolbar*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_item_clicked(pItem);
+ }
+
+ void signal_item_clicked(GtkToolButton* pItem)
+ {
+ const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pItem));
+ signal_clicked(OString(pStr, pStr ? strlen(pStr) : 0));
+ }
+
+ static void signalItemToggled(GtkToggleButton* pItem, gpointer widget)
+ {
+ GtkInstanceToolbar* pThis = static_cast<GtkInstanceToolbar*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_item_toggled(pItem);
+ }
+
+ void signal_item_toggled(GtkToggleButton* pItem)
+ {
+ for (auto& a : m_aMenuButtonMap)
+ {
+ if (a.second->getWidget() == GTK_WIDGET(pItem))
+ {
+ signal_toggle_menu(a.first);
+ break;
+ }
+ }
+ }
+
+ static void set_item_image(GtkToolButton* pItem, const css::uno::Reference<css::graphic::XGraphic>& rIcon)
+ {
+ GtkWidget* pImage = nullptr;
+
+ if (GdkPixbuf* pixbuf = getPixbuf(rIcon))
+ {
+ pImage = gtk_image_new_from_pixbuf(pixbuf);
+ g_object_unref(pixbuf);
+ gtk_widget_show(pImage);
+ }
+
+ gtk_tool_button_set_icon_widget(pItem, pImage);
+ }
+
+ void set_item_image(GtkToolButton* pItem, VirtualDevice* pDevice)
+ {
+ GtkWidget* pImage = nullptr;
+
+ if (pDevice)
+ {
+ pImage = image_new_from_virtual_device(*pDevice);
+ gtk_widget_show(pImage);
+ }
+
+ gtk_tool_button_set_icon_widget(pItem, pImage);
+ gtk_widget_queue_draw(GTK_WIDGET(m_pToolbar));
+ }
+
+public:
+ GtkInstanceToolbar(GtkToolbar* pToolbar, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pToolbar), pBuilder, bTakeOwnership)
+ , m_pToolbar(pToolbar)
+ , m_pMenuButtonProvider(nullptr)
+ {
+ gtk_container_foreach(GTK_CONTAINER(pToolbar), collect, this);
+ }
+
+ 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 OString& 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 OString& rIdent) const override
+ {
+ return gtk_widget_get_sensitive(GTK_WIDGET(m_aMap.find(rIdent)->second));
+ }
+
+ virtual void set_item_visible(const OString& 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 OString& rIdent, const OString& rHelpId) override
+ {
+ ::set_help_id(GTK_WIDGET(m_aMap[rIdent]), rHelpId);
+ }
+
+ virtual bool get_item_visible(const OString& rIdent) const override
+ {
+ return gtk_widget_get_visible(GTK_WIDGET(m_aMap.find(rIdent)->second));
+ }
+
+ virtual void set_item_active(const OString& rIdent, bool bActive) override
+ {
+ disable_item_notify_events();
+
+ GtkToolItem* pToolButton = m_aMap.find(rIdent)->second;
+
+ 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);
+ }
+ }
+
+ enable_item_notify_events();
+ }
+
+ virtual bool get_item_active(const OString& rIdent) const override
+ {
+ GtkToolItem* pToolButton = m_aMap.find(rIdent)->second;
+
+ 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;
+ }
+ }
+
+ return false;
+ }
+
+ virtual void set_menu_item_active(const OString& 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 OString& rIdent) const override
+ {
+ auto aFind = m_aMenuButtonMap.find(rIdent);
+ assert (aFind != m_aMenuButtonMap.end());
+ return aFind->second->get_active();
+ }
+
+ virtual void insert_separator(int pos, const OUString& rId) override
+ {
+ GtkToolItem* pItem = gtk_separator_tool_item_new();
+ gtk_buildable_set_name(GTK_BUILDABLE(pItem), OUStringToOString(rId, RTL_TEXTENCODING_UTF8).getStr());
+ gtk_toolbar_insert(m_pToolbar, pItem, pos);
+ gtk_widget_show(GTK_WIDGET(pItem));
+ }
+
+ virtual void set_item_popover(const OString& rIdent, weld::Widget* pPopover) override
+ {
+ m_aMenuButtonMap[rIdent]->set_popover(pPopover);
+ }
+
+ virtual void set_item_menu(const OString& rIdent, weld::Menu* pMenu) override
+ {
+ m_aMenuButtonMap[rIdent]->set_menu(pMenu);
+ }
+
+ virtual int get_n_items() const override
+ {
+ return gtk_toolbar_get_n_items(m_pToolbar);
+ }
+
+ virtual OString get_item_ident(int nIndex) const override
+ {
+ GtkToolItem* pItem = gtk_toolbar_get_nth_item(m_pToolbar, nIndex);
+ const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pItem));
+ return OString(pStr, pStr ? strlen(pStr) : 0);
+ }
+
+ virtual void set_item_ident(int nIndex, const OString& rIdent) override
+ {
+ GtkToolItem* pItem = gtk_toolbar_get_nth_item(m_pToolbar, nIndex);
+ gtk_buildable_set_name(GTK_BUILDABLE(pItem), rIdent.getStr());
+ }
+
+ virtual void set_item_label(int nIndex, const OUString& rLabel) override
+ {
+ GtkToolItem* pItem = gtk_toolbar_get_nth_item(m_pToolbar, nIndex);
+ if (!GTK_IS_TOOL_BUTTON(pItem))
+ return;
+ gtk_tool_button_set_label(GTK_TOOL_BUTTON(pItem), MapToGtkAccelerator(rLabel).getStr());
+ }
+
+ virtual void set_item_label(const OString& rIdent, const OUString& rLabel) override
+ {
+ GtkToolItem* pItem = m_aMap[rIdent];
+ if (!pItem || !GTK_IS_TOOL_BUTTON(pItem))
+ return;
+ gtk_tool_button_set_label(GTK_TOOL_BUTTON(pItem), MapToGtkAccelerator(rLabel).getStr());
+ }
+
+ OUString get_item_label(const OString& rIdent) const override
+ {
+ const gchar* pText = gtk_tool_button_get_label(GTK_TOOL_BUTTON(m_aMap.find(rIdent)->second));
+ return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
+ }
+
+ virtual void set_item_icon_name(const OString& rIdent, const OUString& rIconName) override
+ {
+ GtkToolItem* pItem = m_aMap[rIdent];
+ if (!pItem || !GTK_IS_TOOL_BUTTON(pItem))
+ return;
+
+ GtkWidget* pImage = nullptr;
+
+ if (GdkPixbuf* pixbuf = getPixbuf(rIconName))
+ {
+ pImage = gtk_image_new_from_pixbuf(pixbuf);
+ g_object_unref(pixbuf);
+ gtk_widget_show(pImage);
+ }
+
+ gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(pItem), pImage);
+ }
+
+ virtual void set_item_image(const OString& rIdent, const css::uno::Reference<css::graphic::XGraphic>& rIcon) override
+ {
+ GtkToolItem* pItem = m_aMap[rIdent];
+ if (!pItem || !GTK_IS_TOOL_BUTTON(pItem))
+ return;
+ set_item_image(GTK_TOOL_BUTTON(pItem), rIcon);
+ }
+
+ virtual void set_item_image(const OString& rIdent, VirtualDevice* pDevice) override
+ {
+ GtkToolItem* pItem = m_aMap[rIdent];
+ if (!pItem || !GTK_IS_TOOL_BUTTON(pItem))
+ return;
+ set_item_image(GTK_TOOL_BUTTON(pItem), pDevice);
+ }
+
+ virtual void set_item_image(int nIndex, const css::uno::Reference<css::graphic::XGraphic>& rIcon) override
+ {
+ GtkToolItem* pItem = gtk_toolbar_get_nth_item(m_pToolbar, nIndex);
+ if (!GTK_IS_TOOL_BUTTON(pItem))
+ return;
+ set_item_image(GTK_TOOL_BUTTON(pItem), rIcon);
+ }
+
+ virtual void set_item_tooltip_text(int nIndex, const OUString& rTip) override
+ {
+ GtkToolItem* pItem = gtk_toolbar_get_nth_item(m_pToolbar, nIndex);
+ gtk_widget_set_tooltip_text(GTK_WIDGET(pItem), OUStringToOString(rTip, RTL_TEXTENCODING_UTF8).getStr());
+ }
+
+ virtual void set_item_tooltip_text(const OString& rIdent, const OUString& rTip) override
+ {
+ GtkToolItem* pItem = m_aMap[rIdent];
+ gtk_widget_set_tooltip_text(GTK_WIDGET(pItem), OUStringToOString(rTip, RTL_TEXTENCODING_UTF8).getStr());
+ }
+
+ virtual OUString get_item_tooltip_text(const OString& rIdent) const override
+ {
+ GtkToolItem* pItem = m_aMap.find(rIdent)->second;
+ const gchar* pStr = gtk_widget_get_tooltip_text(GTK_WIDGET(pItem));
+ return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+ }
+
+ virtual vcl::ImageType get_icon_size() const override
+ {
+ return GtkToVcl(gtk_toolbar_get_icon_size(m_pToolbar));
+ }
+
+ virtual void set_icon_size(vcl::ImageType eType) override
+ {
+ return gtk_toolbar_set_icon_size(m_pToolbar, VclToGtk(eType));
+ }
+
+ virtual sal_uInt16 get_modifier_state() const override
+ {
+ GdkKeymap* pKeymap = gdk_keymap_get_default();
+ guint nState = gdk_keymap_get_modifier_state(pKeymap);
+ return GtkSalFrame::GetKeyModCode(nState);
+ }
+
+ int get_drop_index(const Point& rPoint) const override
+ {
+ return gtk_toolbar_get_drop_index(m_pToolbar, rPoint.X(), rPoint.Y());
+ }
+
+ virtual ~GtkInstanceToolbar() override
+ {
+ for (auto& a : m_aMap)
+ g_signal_handlers_disconnect_by_data(a.second, this);
+ }
+};
+
+class GtkInstanceLinkButton : public GtkInstanceContainer, 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)
+ : GtkInstanceContainer(GTK_CONTAINER(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
+ {
+ ::set_label(GTK_BUTTON(m_pButton), rText);
+ }
+
+ virtual OUString get_label() const override
+ {
+ return ::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 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);
+ GtkInstanceContainer::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceContainer::enable_notify_events();
+ g_signal_handler_unblock(m_pButton, m_nSignalId);
+ }
+
+ virtual ~GtkInstanceLinkButton() override
+ {
+ g_signal_handler_disconnect(m_pButton, m_nSignalId);
+ }
+};
+
+class GtkInstanceRadioButton : public GtkInstanceToggleButton, public virtual weld::RadioButton
+{
+public:
+ GtkInstanceRadioButton(GtkRadioButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceToggleButton(GTK_TOGGLE_BUTTON(pButton), pBuilder, bTakeOwnership)
+ {
+ }
+};
+
+class GtkInstanceCheckButton : public GtkInstanceToggleButton, public virtual weld::CheckButton
+{
+public:
+ GtkInstanceCheckButton(GtkCheckButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceToggleButton(GTK_TOGGLE_BUTTON(pButton), pBuilder, bTakeOwnership)
+ {
+ }
+};
+
+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 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
+ {
+ GdkPixbuf* pixbuf = load_icon_by_name(rIconName);
+ if (!pixbuf)
+ return;
+ gtk_image_set_from_pixbuf(m_pImage, pixbuf);
+ g_object_unref(pixbuf);
+ }
+
+ virtual void set_image(VirtualDevice* pDevice) override
+ {
+ if (gtk_check_version(3, 20, 0) == nullptr)
+ {
+ if (pDevice)
+ gtk_image_set_from_surface(m_pImage, get_underlying_cairo_surface(*pDevice));
+ else
+ gtk_image_set_from_surface(m_pImage, nullptr);
+ return;
+ }
+
+ GdkPixbuf* pixbuf = pDevice ? getPixbuf(*pDevice) : nullptr;
+ gtk_image_set_from_pixbuf(m_pImage, pixbuf);
+ if (pixbuf)
+ g_object_unref(pixbuf);
+ }
+
+ virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage) override
+ {
+ GdkPixbuf* pixbuf = getPixbuf(rImage);
+ gtk_image_set_from_pixbuf(m_pImage, pixbuf);
+ if (pixbuf)
+ g_object_unref(pixbuf);
+ }
+};
+
+class GtkInstanceCalendar : public GtkInstanceWidget, public virtual weld::Calendar
+{
+private:
+ GtkCalendar* m_pCalendar;
+ gulong m_nDaySelectedSignalId;
+ gulong m_nDaySelectedDoubleClickSignalId;
+ gulong m_nKeyPressEventSignalId;
+
+ static void signalDaySelected(GtkCalendar*, gpointer widget)
+ {
+ GtkInstanceCalendar* pThis = static_cast<GtkInstanceCalendar*>(widget);
+ pThis->signal_selected();
+ }
+
+ static void signalDaySelectedDoubleClick(GtkCalendar*, gpointer widget)
+ {
+ GtkInstanceCalendar* pThis = static_cast<GtkInstanceCalendar*>(widget);
+ pThis->signal_activated();
+ }
+
+ bool signal_key_press(GdkEventKey* pEvent)
+ {
+ if (pEvent->keyval == GDK_KEY_Return || pEvent->keyval == GDK_KEY_KP_Enter)
+ {
+ signal_activated();
+ return true;
+ }
+ return false;
+ }
+
+ static gboolean signalKeyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
+ {
+ GtkInstanceCalendar* pThis = static_cast<GtkInstanceCalendar*>(widget);
+ return pThis->signal_key_press(pEvent);
+ }
+
+public:
+ GtkInstanceCalendar(GtkCalendar* pCalendar, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pCalendar), pBuilder, bTakeOwnership)
+ , m_pCalendar(pCalendar)
+ , 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))
+ , m_nKeyPressEventSignalId(g_signal_connect(pCalendar, "key-press-event", G_CALLBACK(signalKeyPress), this))
+ {
+ }
+
+ virtual void set_date(const Date& rDate) override
+ {
+ if (!rDate.IsValidAndGregorian())
+ return;
+
+ disable_notify_events();
+ gtk_calendar_select_month(m_pCalendar, rDate.GetMonth() - 1, rDate.GetYear());
+ gtk_calendar_select_day(m_pCalendar, rDate.GetDay());
+ enable_notify_events();
+ }
+
+ virtual Date get_date() const override
+ {
+ guint year, month, day;
+ gtk_calendar_get_date(m_pCalendar, &year, &month, &day);
+ return Date(day, month + 1, year);
+ }
+
+ 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
+ {
+ g_signal_handler_disconnect(m_pCalendar, m_nKeyPressEventSignalId);
+ g_signal_handler_disconnect(m_pCalendar, m_nDaySelectedDoubleClickSignalId);
+ g_signal_handler_disconnect(m_pCalendar, m_nDaySelectedSignalId);
+ }
+};
+
+ PangoAttrList* create_attr_list(const vcl::Font& rFont)
+ {
+ PangoAttrList* pAttrList = pango_attr_list_new();
+ pango_attr_list_insert(pAttrList, pango_attr_family_new(OUStringToOString(rFont.GetFamilyName(), RTL_TEXTENCODING_UTF8).getStr()));
+ pango_attr_list_insert(pAttrList, pango_attr_size_new(rFont.GetFontSize().Height() * PANGO_SCALE));
+ switch (rFont.GetItalic())
+ {
+ case ITALIC_NONE:
+ pango_attr_list_insert(pAttrList, pango_attr_style_new(PANGO_STYLE_NORMAL));
+ break;
+ case ITALIC_NORMAL:
+ pango_attr_list_insert(pAttrList, pango_attr_style_new(PANGO_STYLE_ITALIC));
+ break;
+ case ITALIC_OBLIQUE:
+ pango_attr_list_insert(pAttrList, pango_attr_style_new(PANGO_STYLE_OBLIQUE));
+ break;
+ default:
+ break;
+ }
+ switch (rFont.GetWeight())
+ {
+ case WEIGHT_ULTRALIGHT:
+ pango_attr_list_insert(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_ULTRALIGHT));
+ break;
+ case WEIGHT_LIGHT:
+ pango_attr_list_insert(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_LIGHT));
+ break;
+ case WEIGHT_NORMAL:
+ pango_attr_list_insert(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_NORMAL));
+ break;
+ case WEIGHT_BOLD:
+ pango_attr_list_insert(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_BOLD));
+ break;
+ case WEIGHT_ULTRABOLD:
+ pango_attr_list_insert(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_ULTRABOLD));
+ break;
+ default:
+ break;
+ }
+ switch (rFont.GetWidthType())
+ {
+ case WIDTH_ULTRA_CONDENSED:
+ pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_ULTRA_CONDENSED));
+ break;
+ case WIDTH_EXTRA_CONDENSED:
+ pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_EXTRA_CONDENSED));
+ break;
+ case WIDTH_CONDENSED:
+ pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_CONDENSED));
+ break;
+ case WIDTH_SEMI_CONDENSED:
+ pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_SEMI_CONDENSED));
+ break;
+ case WIDTH_NORMAL:
+ pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_NORMAL));
+ break;
+ case WIDTH_SEMI_EXPANDED:
+ pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_SEMI_EXPANDED));
+ break;
+ case WIDTH_EXPANDED:
+ pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_EXPANDED));
+ break;
+ case WIDTH_EXTRA_EXPANDED:
+ pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_EXTRA_EXPANDED));
+ break;
+ case WIDTH_ULTRA_EXPANDED:
+ pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_ULTRA_EXPANDED));
+ break;
+ default:
+ break;
+ }
+ return pAttrList;
+ }
+}
+
+namespace
+{
+ void set_entry_message_type(GtkEntry* pEntry, weld::EntryMessageType eType)
+ {
+ if (eType == weld::EntryMessageType::Error)
+ gtk_entry_set_icon_from_icon_name(pEntry, GTK_ENTRY_ICON_SECONDARY, "dialog-error");
+ else if (eType == weld::EntryMessageType::Warning)
+ gtk_entry_set_icon_from_icon_name(pEntry, GTK_ENTRY_ICON_SECONDARY, "dialog-warning");
+ else
+ gtk_entry_set_icon_from_icon_name(pEntry, GTK_ENTRY_ICON_SECONDARY, nullptr);
+ }
+
+class GtkInstanceEntry : public GtkInstanceWidget, public virtual weld::Entry
+{
+private:
+ GtkEntry* m_pEntry;
+ std::unique_ptr<vcl::Font> m_xFont;
+ gulong m_nChangedSignalId;
+ gulong m_nInsertTextSignalId;
+ gulong m_nCursorPosSignalId;
+ gulong m_nSelectionPosSignalId;
+ gulong m_nActivateSignalId;
+
+ static void signalChanged(GtkEntry*, gpointer widget)
+ {
+ GtkInstanceEntry* pThis = static_cast<GtkInstanceEntry*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_changed();
+ }
+
+ static void signalInsertText(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength,
+ gint* position, gpointer widget)
+ {
+ GtkInstanceEntry* pThis = static_cast<GtkInstanceEntry*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_insert_text(pEntry, pNewText, nNewTextLength, position);
+ }
+
+ void signal_insert_text(GtkEntry* pEntry, 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(pEntry, reinterpret_cast<gpointer>(signalInsertText), this);
+ gtk_editable_insert_text(GTK_EDITABLE(pEntry), sFinalText.getStr(), sFinalText.getLength(), position);
+ g_signal_handlers_unblock_by_func(pEntry, reinterpret_cast<gpointer>(signalInsertText), this);
+ }
+ g_signal_stop_emission_by_name(pEntry, "insert-text");
+ }
+
+ static void signalCursorPosition(GtkEntry*, GParamSpec*, gpointer widget)
+ {
+ GtkInstanceEntry* pThis = static_cast<GtkInstanceEntry*>(widget);
+ pThis->signal_cursor_position();
+ }
+
+ static void signalActivate(GtkEntry*, gpointer widget)
+ {
+ GtkInstanceEntry* pThis = static_cast<GtkInstanceEntry*>(widget);
+ pThis->signal_activate();
+ }
+
+protected:
+
+ virtual void signal_activate()
+ {
+ if (m_aActivateHdl.IsSet())
+ {
+ SolarMutexGuard aGuard;
+ if (m_aActivateHdl.Call(*this))
+ g_signal_stop_emission_by_name(m_pEntry, "activate");
+ }
+ }
+
+public:
+ GtkInstanceEntry(GtkEntry* pEntry, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pEntry), pBuilder, bTakeOwnership)
+ , m_pEntry(pEntry)
+ , m_nChangedSignalId(g_signal_connect(pEntry, "changed", G_CALLBACK(signalChanged), this))
+ , m_nInsertTextSignalId(g_signal_connect(pEntry, "insert-text", G_CALLBACK(signalInsertText), this))
+ , m_nCursorPosSignalId(g_signal_connect(pEntry, "notify::cursor-position", G_CALLBACK(signalCursorPosition), this))
+ , m_nSelectionPosSignalId(g_signal_connect(pEntry, "notify::selection-bound", G_CALLBACK(signalCursorPosition), this))
+ , m_nActivateSignalId(g_signal_connect(pEntry, "activate", G_CALLBACK(signalActivate), this))
+ {
+ }
+
+ virtual void set_text(const OUString& rText) override
+ {
+ disable_notify_events();
+ gtk_entry_set_text(m_pEntry, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
+ enable_notify_events();
+ }
+
+ virtual OUString get_text() const override
+ {
+ const gchar* pText = gtk_entry_get_text(m_pEntry);
+ OUString sRet(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
+ return sRet;
+ }
+
+ virtual void set_width_chars(int nChars) override
+ {
+ disable_notify_events();
+ gtk_entry_set_width_chars(m_pEntry, nChars);
+ gtk_entry_set_max_width_chars(m_pEntry, nChars);
+ enable_notify_events();
+ }
+
+ virtual int get_width_chars() const override
+ {
+ return gtk_entry_get_width_chars(m_pEntry);
+ }
+
+ virtual void set_max_length(int nChars) override
+ {
+ disable_notify_events();
+ gtk_entry_set_max_length(m_pEntry, nChars);
+ enable_notify_events();
+ }
+
+ virtual void select_region(int nStartPos, int nEndPos) override
+ {
+ disable_notify_events();
+ gtk_editable_select_region(GTK_EDITABLE(m_pEntry), nStartPos, nEndPos);
+ enable_notify_events();
+ }
+
+ bool get_selection_bounds(int& rStartPos, int& rEndPos) override
+ {
+ return gtk_editable_get_selection_bounds(GTK_EDITABLE(m_pEntry), &rStartPos, &rEndPos);
+ }
+
+ virtual void replace_selection(const OUString& rText) override
+ {
+ disable_notify_events();
+ gtk_editable_delete_selection(GTK_EDITABLE(m_pEntry));
+ OString sText(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
+ gint position = gtk_editable_get_position(GTK_EDITABLE(m_pEntry));
+ gtk_editable_insert_text(GTK_EDITABLE(m_pEntry), sText.getStr(), sText.getLength(),
+ &position);
+ enable_notify_events();
+ }
+
+ virtual void set_position(int nCursorPos) override
+ {
+ disable_notify_events();
+ gtk_editable_set_position(GTK_EDITABLE(m_pEntry), nCursorPos);
+ enable_notify_events();
+ }
+
+ virtual int get_position() const override
+ {
+ return gtk_editable_get_position(GTK_EDITABLE(m_pEntry));
+ }
+
+ virtual void set_editable(bool bEditable) override
+ {
+ gtk_editable_set_editable(GTK_EDITABLE(m_pEntry), bEditable);
+ }
+
+ virtual bool get_editable() const override
+ {
+ return gtk_editable_get_editable(GTK_EDITABLE(m_pEntry));
+ }
+
+ virtual void set_message_type(weld::EntryMessageType eType) override
+ {
+ ::set_entry_message_type(m_pEntry, eType);
+ }
+
+ virtual void disable_notify_events() override
+ {
+ g_signal_handler_block(m_pEntry, m_nActivateSignalId);
+ g_signal_handler_block(m_pEntry, m_nSelectionPosSignalId);
+ g_signal_handler_block(m_pEntry, m_nCursorPosSignalId);
+ g_signal_handler_block(m_pEntry, m_nInsertTextSignalId);
+ g_signal_handler_block(m_pEntry, m_nChangedSignalId);
+ GtkInstanceWidget::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceWidget::enable_notify_events();
+ g_signal_handler_unblock(m_pEntry, m_nChangedSignalId);
+ g_signal_handler_unblock(m_pEntry, m_nInsertTextSignalId);
+ g_signal_handler_unblock(m_pEntry, m_nCursorPosSignalId);
+ g_signal_handler_unblock(m_pEntry, m_nSelectionPosSignalId);
+ g_signal_handler_unblock(m_pEntry, m_nActivateSignalId);
+ }
+
+ virtual void set_font(const vcl::Font& rFont) override
+ {
+ m_xFont.reset(new vcl::Font(rFont));
+ PangoAttrList* pAttrList = create_attr_list(rFont);
+ gtk_entry_set_attributes(m_pEntry, pAttrList);
+ pango_attr_list_unref(pAttrList);
+ }
+
+ virtual vcl::Font get_font() override
+ {
+ if (m_xFont)
+ return *m_xFont;
+ return GtkInstanceWidget::get_font();
+ }
+
+ void fire_signal_changed()
+ {
+ signal_changed();
+ }
+
+ virtual void cut_clipboard() override
+ {
+ gtk_editable_cut_clipboard(GTK_EDITABLE(m_pEntry));
+ }
+
+ virtual void copy_clipboard() override
+ {
+ gtk_editable_copy_clipboard(GTK_EDITABLE(m_pEntry));
+ }
+
+ virtual void paste_clipboard() override
+ {
+ gtk_editable_paste_clipboard(GTK_EDITABLE(m_pEntry));
+ }
+
+ virtual void set_placeholder_text(const OUString& rText) override
+ {
+ gtk_entry_set_placeholder_text(m_pEntry, rText.toUtf8().getStr());
+ }
+
+ virtual void grab_focus() override
+ {
+ disable_notify_events();
+ gtk_entry_grab_focus_without_selecting(m_pEntry);
+ enable_notify_events();
+ }
+
+ virtual ~GtkInstanceEntry() override
+ {
+ g_signal_handler_disconnect(m_pEntry, m_nActivateSignalId);
+ g_signal_handler_disconnect(m_pEntry, m_nSelectionPosSignalId);
+ g_signal_handler_disconnect(m_pEntry, m_nCursorPosSignalId);
+ g_signal_handler_disconnect(m_pEntry, m_nInsertTextSignalId);
+ g_signal_handler_disconnect(m_pEntry, m_nChangedSignalId);
+ }
+};
+
+ struct Search
+ {
+ OString str;
+ int index;
+ int col;
+ Search(const OUString& 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, const OUString& 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)
+{
+ gint nVerticalSeparator;
+ gtk_widget_style_get(GTK_WIDGET(pTreeView), "vertical-separator", &nVerticalSeparator, nullptr);
+ 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 + 1));
+}
+
+int get_height_rows(int nRowHeight, int nSeparatorHeight, int nRows)
+{
+ return (nRowHeight * nRows) + (nSeparatorHeight * (nRows + 1));
+}
+
+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;
+}
+
+class GtkInstanceTreeView : public GtkInstanceContainer, public virtual weld::TreeView
+{
+private:
+ GtkTreeView* m_pTreeView;
+ GtkTreeStore* m_pTreeStore;
+ 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;
+ std::vector<GtkSortType> m_aSavedSortTypes;
+ std::vector<int> m_aSavedSortColumns;
+ std::vector<int> m_aViewColToModelCol;
+ std::vector<int> m_aModelColToViewCol;
+ bool m_bWorkAroundBadDragRegion;
+ bool m_bInDrag;
+ gint m_nTextCol;
+ gint m_nImageCol;
+ 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;
+ gulong m_nPopupMenuSignalId;
+ gulong m_nKeyPressSignalId;
+ 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);
+ 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(GTK_TREE_MODEL(m_pTreeStore), &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, const OUString* pExpanderName)
+ {
+ gtk_tree_store_insert_with_values(m_pTreeStore, &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(),
+ -1);
+ if (pIconName)
+ {
+ GdkPixbuf* pixbuf = getPixbuf(*pIconName);
+ gtk_tree_store_set(m_pTreeStore, &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);
+
+ gtk_tree_store_set(m_pTreeStore, &iter, m_nImageCol, target, -1);
+ cairo_surface_destroy(target);
+ }
+
+ if (pExpanderName)
+ {
+ GdkPixbuf* pixbuf = getPixbuf(*pExpanderName);
+ gtk_tree_store_set(m_pTreeStore, &iter, m_nExpanderImageCol, pixbuf, -1);
+ if (pixbuf)
+ g_object_unref(pixbuf);
+ }
+ }
+
+ 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;
+ }
+
+ OUString get(int pos, int col) const
+ {
+ OUString sRet;
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(pModel, &iter, nullptr, pos))
+ sRet = get(iter, col);
+ return sRet;
+ }
+
+ gint get_int(const GtkTreeIter& iter, int col) const
+ {
+ gint nRet(-1);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ gtk_tree_model_get(pModel, const_cast<GtkTreeIter*>(&iter), col, &nRet, -1);
+ return nRet;
+ }
+
+ gint get_int(int pos, int col) const
+ {
+ gint nRet(-1);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(pModel, &iter, nullptr, pos))
+ nRet = get_int(iter, col);
+ gtk_tree_model_get(pModel, &iter, col, &nRet, -1);
+ return nRet;
+ }
+
+ bool get_bool(const GtkTreeIter& iter, int col) const
+ {
+ gboolean bRet(false);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ gtk_tree_model_get(pModel, const_cast<GtkTreeIter*>(&iter), col, &bRet, -1);
+ return bRet;
+ }
+
+ bool get_bool(int pos, int col) const
+ {
+ bool bRet(false);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(pModel, &iter, nullptr, pos))
+ bRet = get_bool(iter, col);
+ return bRet;
+ }
+
+ void set_toggle(const GtkTreeIter& iter, TriState eState, int col)
+ {
+ col = get_model_col(col);
+ if (eState == TRISTATE_INDET)
+ {
+ gtk_tree_store_set(m_pTreeStore, const_cast<GtkTreeIter*>(&iter),
+ m_aToggleVisMap[col], true, // checkbuttons are invisible until toggled on or off
+ m_aToggleTriStateMap[col], true, // tristate on
+ -1);
+ }
+ else
+ {
+ gtk_tree_store_set(m_pTreeStore, const_cast<GtkTreeIter*>(&iter),
+ m_aToggleVisMap[col], true, // checkbuttons are invisible until toggled on or off
+ m_aToggleTriStateMap[col], false, // tristate off
+ col, eState == TRISTATE_TRUE, // set toggle state
+ -1);
+ }
+ }
+
+ void set(const GtkTreeIter& iter, int col, const OUString& rText)
+ {
+ OString aStr(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
+ gtk_tree_store_set(m_pTreeStore, const_cast<GtkTreeIter*>(&iter), col, aStr.getStr(), -1);
+ }
+
+ void set(int pos, int col, const OUString& rText)
+ {
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(pModel, &iter, nullptr, pos))
+ set(iter, col, rText);
+ }
+
+ void set(const GtkTreeIter& iter, int col, bool bOn)
+ {
+ gtk_tree_store_set(m_pTreeStore, const_cast<GtkTreeIter*>(&iter), col, bOn, -1);
+ }
+
+ void set(int pos, int col, bool bOn)
+ {
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(pModel, &iter, nullptr, pos))
+ set(iter, col, bOn);
+ }
+
+ void set(const GtkTreeIter& iter, int col, gint bInt)
+ {
+ gtk_tree_store_set(m_pTreeStore, const_cast<GtkTreeIter*>(&iter), col, bInt, -1);
+ }
+
+ void set(int pos, int col, gint bInt)
+ {
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(pModel, &iter, nullptr, pos))
+ set(iter, col, bInt);
+ }
+
+ void set(const GtkTreeIter& iter, int col, double fValue)
+ {
+ gtk_tree_store_set(m_pTreeStore, const_cast<GtkTreeIter*>(&iter), col, fValue, -1);
+ }
+
+ void set(int pos, int col, double fValue)
+ {
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(pModel, &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
+ {
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+
+ GtkTreePath* pPath = gtk_tree_model_get_path(pModel, &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(pModel, &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)
+ {
+ gtk_tree_store_remove(m_pTreeStore, &aIter.iter);
+
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ pPlaceHolderPath = gtk_tree_model_get_path(pModel, &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, nullptr);
+ }
+ m_aExpandingPlaceHolderParents.erase(pPlaceHolderPath);
+ gtk_tree_path_free(pPlaceHolderPath);
+ }
+
+ enable_notify_events();
+ return bRet;
+ }
+
+ bool signal_test_collapse_row(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);
+
+ // toggled signal handlers can query get_cursor to get which
+ // node was clicked
+ gtk_tree_view_set_cursor(m_pTreeView, tree_path, nullptr, false);
+
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreeIter iter;
+ gtk_tree_model_get_iter(pModel, &iter, tree_path);
+
+ gboolean bRet(false);
+ gtk_tree_model_get(pModel, &iter, nCol, &bRet, -1);
+ bRet = !bRet;
+ gtk_tree_store_set(m_pTreeStore, &iter, nCol, bRet, -1);
+
+ gint depth;
+ gint* indices = gtk_tree_path_get_indices_with_depth(tree_path, &depth);
+ int nRow = indices[depth-1];
+
+ set(iter, m_aToggleTriStateMap[nCol], false);
+
+ signal_toggled(std::make_pair(nRow, 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);
+
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkInstanceTreeIter aGtkIter(nullptr);
+ gtk_tree_model_get_iter(pModel, &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);
+
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkInstanceTreeIter aGtkIter(nullptr);
+ gtk_tree_model_get_iter(pModel, &aGtkIter.iter, tree_path);
+ gtk_tree_path_free(tree_path);
+
+ OUString sText(pNewText, pNewText ? strlen(pNewText) : 0, RTL_TEXTENCODING_UTF8);
+ if (signal_editing_done(std::pair<const weld::TreeIter&, OUString>(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();
+ }
+
+ int get_model_col(int viewcol) const
+ {
+ return m_aViewColToModelCol[viewcol];
+ }
+
+ int get_view_col(int modelcol) const
+ {
+ return m_aModelColToViewCol[modelcol];
+ }
+
+ void set_column_editable(int nCol, bool bEditable)
+ {
+ 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());
+ }
+
+ 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(GTK_TREE_MODEL(m_pTreeStore), &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);
+ }
+
+ 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_tree_view_get_tooltip_context(pTreeView, &x, &y, keyboard_tip, &pModel, &pPath, &iter))
+ return false;
+ OUString aTooltip = pThis->signal_query_tooltip(GtkInstanceTreeIter(iter));
+ if (aTooltip.isEmpty())
+ return false;
+ 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 true;
+ }
+
+ 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)
+ gtk_tree_store_set(m_pTreeStore, const_cast<GtkTreeIter*>(&iter), m_nIdCol + 1, nullptr, -1);
+ else
+ {
+ GdkRGBA aColor{rColor.GetRed()/255.0, rColor.GetGreen()/255.0, rColor.GetBlue()/255.0, 0};
+ gtk_tree_store_set(m_pTreeStore, const_cast<GtkTreeIter*>(&iter), m_nIdCol + 1, &aColor, -1);
+ }
+ }
+
+ int get_expander_size() const
+ {
+ gint nExpanderSize;
+ gint nHorizontalSeparator;
+
+ gtk_widget_style_get(GTK_WIDGET(m_pTreeView),
+ "expander-size", &nExpanderSize,
+ "horizontal-separator", &nHorizontalSeparator,
+ nullptr);
+
+ 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);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreeIter tmp;
+ GtkTreeIter iter = rGtkIter.iter;
+
+ bool ret = gtk_tree_model_iter_children(pModel, &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(pModel, &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(pModel, &tmp, &iter))
+ {
+ iter = tmp;
+ if (gtk_tree_model_iter_next(pModel, &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;
+ }
+
+public:
+ GtkInstanceTreeView(GtkTreeView* pTreeView, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceContainer(GTK_CONTAINER(pTreeView), pBuilder, bTakeOwnership)
+ , m_pTreeView(pTreeView)
+ , m_pTreeStore(GTK_TREE_STORE(gtk_tree_view_get_model(m_pTreeView)))
+ , m_bWorkAroundBadDragRegion(false)
+ , m_bInDrag(false)
+ , m_nTextCol(-1)
+ , m_nImageCol(-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)
+ , 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_nQueryTooltipSignalId(0)
+ , m_pVAdjustment(gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(pTreeView)))
+ , m_pChangeEvent(nullptr)
+ {
+ m_pColumns = gtk_tree_view_get_columns(m_pTreeView);
+ 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);
+ 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);
+ g_object_set_data(G_OBJECT(pCellRenderer), "g-lo-CellIndex", reinterpret_cast<gpointer>(nIndex));
+ if (GTK_IS_CELL_RENDERER_TEXT(pCellRenderer))
+ {
+ if (m_nTextCol == -1)
+ m_nTextCol = nIndex;
+ 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))
+ {
+ 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;
+ }
+ m_aModelColToViewCol.push_back(m_aViewColToModelCol.size());
+ ++nIndex;
+ }
+ g_list_free(pRenderers);
+ m_aViewColToModelCol.push_back(nIndex - 1);
+ }
+
+ 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();
+
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ m_nRowDeletedSignalId = g_signal_connect(pModel, "row-deleted", G_CALLBACK(signalRowDeleted), this);
+ m_nRowInsertedSignalId = g_signal_connect(pModel, "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");
+ gtk_cell_layout_clear(GTK_CELL_LAYOUT(pColumn));
+ if (bEnable)
+ {
+ GtkCellRenderer *pRenderer = custom_cell_renderer_surface_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);
+ }
+ }
+
+ virtual void insert(const weld::TreeIter* pParent, int pos, const OUString* pText, const OUString* pId, const OUString* pIconName,
+ VirtualDevice* pImageSurface, const OUString* pExpanderName,
+ 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, pExpanderName);
+ if (bChildrenOnDemand)
+ {
+ GtkTreeIter subiter;
+ OUString sDummy("<dummy>");
+ insert_row(subiter, &iter, -1, nullptr, &sDummy, nullptr, nullptr, nullptr);
+ }
+ if (pRet)
+ {
+ GtkInstanceTreeIter* pGtkRetIter = static_cast<GtkInstanceTreeIter*>(pRet);
+ pGtkRetIter->iter = iter;
+ }
+ enable_notify_events();
+ }
+
+ virtual void set_font_color(int pos, const Color& rColor) override
+ {
+ GtkTreeIter iter;
+ gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(m_pTreeStore), &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(GTK_TREE_MODEL(m_pTreeStore), &iter, nullptr, pos);
+ gtk_tree_store_remove(m_pTreeStore, &iter);
+ enable_notify_events();
+ }
+
+ virtual int find_text(const OUString& rText) const override
+ {
+ Search aSearch(rText, m_nTextCol);
+ gtk_tree_model_foreach(GTK_TREE_MODEL(m_pTreeStore), foreach_find, &aSearch);
+ return aSearch.index;
+ }
+
+ virtual int find_id(const OUString& rId) const override
+ {
+ Search aSearch(rId, m_nIdCol);
+ gtk_tree_model_foreach(GTK_TREE_MODEL(m_pTreeStore), foreach_find, &aSearch);
+ return aSearch.index;
+ }
+
+ virtual void bulk_insert_for_each(int nSourceCount, const std::function<void(weld::TreeIter&, int nSourceIndex)>& func,
+ const std::vector<int>* pFixedWidths) override
+ {
+ freeze();
+ clear();
+ GtkInstanceTreeIter aGtkIter(nullptr);
+
+ if (pFixedWidths)
+ set_column_fixed_widths(*pFixedWidths);
+
+ while (nSourceCount)
+ {
+ // tdf#125241 inserting backwards is massively faster
+ gtk_tree_store_prepend(m_pTreeStore, &aGtkIter.iter, nullptr);
+ func(aGtkIter, --nSourceCount);
+ }
+
+ thaw();
+ }
+
+ virtual void swap(int pos1, int pos2) override
+ {
+ disable_notify_events();
+
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+
+ GtkTreeIter iter1;
+ gtk_tree_model_iter_nth_child(pModel, &iter1, nullptr, pos1);
+
+ GtkTreeIter iter2;
+ gtk_tree_model_iter_nth_child(pModel, &iter2, nullptr, pos2);
+
+ gtk_tree_store_swap(m_pTreeStore, &iter1, &iter2);
+
+ enable_notify_events();
+ }
+
+ virtual void clear() override
+ {
+ disable_notify_events();
+ gtk_tree_store_clear(m_pTreeStore);
+ 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_pTreeStore);
+ 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_pTreeStore);
+ 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_pTreeStore);
+ 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
+ {
+ GtkSortType eSortType;
+
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeStore);
+ gtk_tree_sortable_get_sort_column_id(pSortable, nullptr, &eSortType);
+ return eSortType == GTK_SORT_ASCENDING;
+ }
+
+ virtual void set_sort_indicator(TriState eState, int col) override
+ {
+ if (col == -1)
+ col = get_view_col(m_nTextCol);
+
+ 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
+ {
+ if (col == -1)
+ col = get_view_col(m_nTextCol);
+
+ 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_pTreeStore);
+ gint sort_column_id(0);
+ if (!gtk_tree_sortable_get_sort_column_id(pSortable, &sort_column_id, nullptr))
+ return -1;
+ return get_view_col(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_pTreeStore);
+ gtk_tree_sortable_get_sort_column_id(pSortable, nullptr, &eSortType);
+ int nSortCol = get_model_col(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_pTreeStore);
+ gtk_tree_sortable_sort_column_changed(pSortable);
+ }
+
+ virtual int n_children() const override
+ {
+ return gtk_tree_model_iter_n_children(GTK_TREE_MODEL(m_pTreeStore), 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(GTK_TREE_MODEL(m_pTreeStore), 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 = gtk_tree_path_new_from_indices(pos, -1);
+ 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 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, false, 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(GTK_TREE_MODEL(m_pTreeStore), &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
+ {
+ GtkInstanceTreeIter aGtkIter(nullptr);
+ if (get_iter_first(aGtkIter))
+ {
+ do
+ {
+ if (func(aGtkIter))
+ break;
+ } while (iter_next(aGtkIter));
+ }
+ }
+
+ virtual void selected_foreach(const std::function<bool(weld::TreeIter&)>& func) override
+ {
+ 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));
+ }
+
+ virtual void visible_foreach(const std::function<bool(weld::TreeIter&)>& func) override
+ {
+ GtkTreePath* start_path;
+ GtkTreePath* end_path;
+
+ if (gtk_tree_view_get_visible_range(m_pTreeView, &start_path, &end_path))
+ {
+ GtkInstanceTreeIter aGtkIter(nullptr);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ gtk_tree_model_get_iter(pModel, &aGtkIter.iter, start_path);
+
+ do
+ {
+ if (func(aGtkIter))
+ break;
+ GtkTreePath* path = gtk_tree_model_get_path(pModel, &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);
+ }
+ }
+
+ 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)
+ return get(pos, m_nTextCol);
+ return get(pos, get_model_col(col));
+ }
+
+ virtual void set_text(int pos, const OUString& rText, int col) override
+ {
+ if (col == -1)
+ col = m_nTextCol;
+ else
+ col = get_model_col(col);
+ set(pos, col, rText);
+ }
+
+ virtual TriState get_toggle(int pos, int col) const override
+ {
+ col = get_model_col(col);
+ if (get_bool(pos, m_aToggleTriStateMap.find(col)->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
+ {
+ col = get_model_col(col);
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ if (get_bool(rGtkIter.iter, m_aToggleTriStateMap.find(col)->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);
+ col = get_model_col(col);
+ // checkbuttons are invisible until toggled on or off
+ set(rGtkIter.iter, m_aToggleVisMap[col], true);
+ if (eState == TRISTATE_INDET)
+ set(rGtkIter.iter, m_aToggleTriStateMap[col], true);
+ else
+ {
+ set(rGtkIter.iter, m_aToggleTriStateMap[col], false);
+ set(rGtkIter.iter, col, eState == TRISTATE_TRUE);
+ }
+ }
+
+ virtual void set_toggle(int pos, TriState eState, int col) override
+ {
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(pModel, &iter, nullptr, pos))
+ set_toggle(iter, eState, col);
+ }
+
+ 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);
+ col = get_model_col(col);
+ set(rGtkIter.iter, m_aWeightMap[col], bOn ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL);
+ }
+
+ virtual void set_text_emphasis(int pos, bool bOn, int col) override
+ {
+ col = get_model_col(col);
+ set(pos, m_aWeightMap[col], bOn ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL);
+ }
+
+ virtual bool get_text_emphasis(const weld::TreeIter& rIter, int col) const override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ col = get_model_col(col);
+ return get_int(rGtkIter.iter, m_aWeightMap.find(col)->second) == PANGO_WEIGHT_BOLD;
+ }
+
+ virtual bool get_text_emphasis(int pos, int col) const override
+ {
+ col = get_model_col(col);
+ return get_int(pos, m_aWeightMap.find(col)->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 = get_model_col(col);
+ set(rGtkIter.iter, m_aAlignMap[col], fAlign);
+ }
+
+ virtual void set_text_align(int pos, double fAlign, int col) override
+ {
+ col = get_model_col(col);
+ set(pos, m_aAlignMap[col], fAlign);
+ }
+
+ using GtkInstanceWidget::set_sensitive;
+
+ virtual void set_sensitive(int pos, bool bSensitive, int col) override
+ {
+ if (col == -1)
+ col = m_nTextCol;
+ else
+ col = get_model_col(col);
+ set(pos, m_aSensitiveMap[col], bSensitive);
+ }
+
+ virtual void set_sensitive(const weld::TreeIter& rIter, bool bSensitive, int col) override
+ {
+ if (col == -1)
+ col = m_nTextCol;
+ else
+ col = get_model_col(col);
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ set(rGtkIter.iter, m_aSensitiveMap[col], bSensitive);
+ }
+
+ void set_image(const GtkTreeIter& iter, int col, GdkPixbuf* pixbuf)
+ {
+ if (col == -1)
+ col = m_nExpanderImageCol;
+ else
+ col = get_model_col(col);
+ gtk_tree_store_set(m_pTreeStore, const_cast<GtkTreeIter*>(&iter), col, pixbuf, -1);
+ if (pixbuf)
+ g_object_unref(pixbuf);
+ }
+
+ void set_image(int pos, GdkPixbuf* pixbuf, int col)
+ {
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(pModel, &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);
+
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreePath* path = gtk_tree_model_get_path(pModel, 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);
+
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreePath* pathA = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIterA.iter));
+ GtkTreePath* pathB = gtk_tree_model_get_path(pModel, 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)
+ {
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+
+ int nCols = gtk_tree_model_get_n_columns(pModel);
+ GValue value;
+
+ GtkTreeIter toiter;
+ gtk_tree_store_insert(m_pTreeStore, &toiter, pGtkParentIter, nIndexInNewParent);
+
+ for (int i = 0; i < nCols; ++i)
+ {
+ memset(&value, 0, sizeof(GValue));
+ gtk_tree_model_get_value(pModel, &rFromIter, i, &value);
+ gtk_tree_store_set_value(m_pTreeStore, &toiter, i, &value);
+ g_value_unset(&value);
+ }
+
+ GtkTreeIter tmpfromiter;
+ if (gtk_tree_model_iter_children(pModel, &tmpfromiter, &rFromIter))
+ {
+ int j = 0;
+ do
+ {
+ move_subtree(tmpfromiter, &toiter, j++);
+ } while (gtk_tree_model_iter_next(pModel, &tmpfromiter));
+ }
+
+ gtk_tree_store_remove(m_pTreeStore, &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)
+ {
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ gtk_tree_model_get_iter(pModel, &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);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreeIter Iter;
+ if (gtk_tree_model_iter_parent(pModel, &Iter, const_cast<GtkTreeIter*>(&rGtkIter.iter)))
+ {
+ GtkTreePath* path = gtk_tree_model_get_path(pModel, &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(pModel, 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);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ return gtk_tree_model_get_iter_first(pModel, &rGtkIter.iter);
+ }
+
+ virtual bool iter_next_sibling(weld::TreeIter& rIter) const override
+ {
+ GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ return gtk_tree_model_iter_next(pModel, &rGtkIter.iter);
+ }
+
+ virtual bool iter_previous_sibling(weld::TreeIter& rIter) const override
+ {
+ GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ return gtk_tree_model_iter_previous(pModel, &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);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreeIter iter = rGtkIter.iter;
+ GtkTreeIter tmp = iter;
+ if (gtk_tree_model_iter_previous(pModel, &tmp))
+ {
+ // Move down level(s) until we find the level where the last node exists.
+ int nChildren = gtk_tree_model_iter_n_children(pModel, &tmp);
+ if (!nChildren)
+ rGtkIter.iter = tmp;
+ else
+ last_child(pModel, &rGtkIter.iter, &tmp, nChildren);
+ ret = true;
+ }
+ else
+ {
+ // Move up level
+ if (gtk_tree_model_iter_parent(pModel, &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_next_visible(weld::TreeIter& rIter) const override
+ {
+ return iter_next(rIter, true);
+ }
+
+ virtual bool iter_children(weld::TreeIter& rIter) const override
+ {
+ GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreeIter tmp;
+ bool ret = gtk_tree_model_iter_children(pModel, &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);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreeIter tmp;
+ auto ret = gtk_tree_model_iter_parent(pModel, &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);
+ gtk_tree_store_remove(m_pTreeStore, 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)
+ gtk_tree_store_remove(m_pTreeStore, &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);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreePath* path = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
+ gtk_tree_view_expand_to_path(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 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);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreePath* path = gtk_tree_model_get_path(pModel, 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
+ {
+ weld::TreeIter& rNonConstIter = const_cast<weld::TreeIter&>(rIter);
+ GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rNonConstIter);
+ GtkTreeIter restore(rGtkIter.iter);
+ bool ret = iter_children(rNonConstIter);
+ rGtkIter.iter = restore;
+ return ret;
+ }
+
+ virtual bool get_row_expanded(const weld::TreeIter& rIter) const override
+ {
+ 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));
+ 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, 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);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreePath* path = gtk_tree_model_get_path(pModel, 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);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreePath* path = gtk_tree_model_get_path(pModel, 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 = get_model_col(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 = get_model_col(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();
+ g_object_ref(m_pTreeStore);
+ GtkInstanceContainer::freeze();
+ gtk_tree_view_set_model(m_pTreeView, nullptr);
+ if (m_xSorter)
+ {
+ int nSortColumn;
+ GtkSortType eSortType;
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeStore);
+ 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 (m_xSorter)
+ {
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeStore);
+ gtk_tree_sortable_set_sort_column_id(pSortable, m_aSavedSortColumns.back(), m_aSavedSortTypes.back());
+ m_aSavedSortTypes.pop_back();
+ m_aSavedSortColumns.pop_back();
+ }
+ gtk_tree_view_set_model(m_pTreeView, GTK_TREE_MODEL(m_pTreeStore));
+ GtkInstanceContainer::thaw();
+ g_object_unref(m_pTreeStore);
+ 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;
+ 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);
+ }
+
+ virtual void drag_source_set(const std::vector<GtkTargetEntry>& rGtkTargets, GdkDragAction eDragAction) override
+ {
+ gtk_tree_view_enable_model_drag_source(m_pTreeView, GDK_BUTTON1_MASK, rGtkTargets.data(), rGtkTargets.size(), eDragAction);
+ }
+
+ 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 col, int nStartRow, bool bCaseSensitive)
+ {
+ return ::starts_with(GTK_TREE_MODEL(m_pTreeStore), rStr, get_model_col(col), 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);
+
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ g_signal_handler_block(pModel, m_nRowDeletedSignalId);
+ g_signal_handler_block(pModel, m_nRowInsertedSignalId);
+
+ GtkInstanceContainer::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceContainer::enable_notify_events();
+
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ g_signal_handler_unblock(pModel, m_nRowDeletedSignalId);
+ g_signal_handler_unblock(pModel, 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 bHighLightTarget) override
+ {
+ 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)
+ gtk_drag_unhighlight(GTK_WIDGET(m_pTreeView));
+
+ 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
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreePath *lastpath = get_path_of_last_entry(pModel);
+
+ 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 (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(pModel, &rGtkIter.iter, path);
+ }
+
+ if (m_bInDrag && bHighLightTarget) // bHighLightTarget alone might be sufficient
+ {
+ // 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);
+
+ // 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);
+ GtkTreeModel* pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreePath* pPath = gtk_tree_model_get_path(pModel, 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
+ {
+ int col = get_view_col(m_nTextCol);
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, col));
+ assert(pColumn && "wrong column");
+
+ 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));
+
+ // 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
+ 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))
+ {
+ gboolean is_editable(false);
+ g_object_get(pCellRenderer, "editable", &is_editable, nullptr);
+ if (!is_editable)
+ {
+ 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;
+ }
+
+ 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))
+ {
+ gtk_drag_unhighlight(pWidget);
+ gtk_drag_highlight(pParent);
+ 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);
+ gtk_drag_unhighlight(pParent);
+ 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 ~GtkInstanceTreeView() override
+ {
+ if (m_pChangeEvent)
+ Application::RemoveUserEvent(m_pChangeEvent);
+ if (m_nQueryTooltipSignalId)
+ g_signal_handler_disconnect(m_pTreeView, m_nQueryTooltipSignalId);
+ g_signal_handler_disconnect(m_pTreeView, m_nKeyPressSignalId);
+ g_signal_handler_disconnect(m_pTreeView, m_nPopupMenuSignalId);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ g_signal_handler_disconnect(pModel, m_nRowDeletedSignalId);
+ g_signal_handler_disconnect(pModel, 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);
+
+ 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_SURFACE(pCellRenderer))
+ continue;
+ g_object_set_property(G_OBJECT(pCellRenderer), "instance", &value);
+ }
+ g_list_free(pRenderers);
+ }
+ g_list_free(m_pColumns);
+ }
+};
+
+void ensure_device(CustomCellRendererSurface *cellsurface, weld::Widget* pWidget)
+{
+ if (!cellsurface->device)
+ {
+ cellsurface->device = VclPtr<VirtualDevice>::Create();
+ cellsurface->device->SetBackground(COL_TRANSPARENT);
+ // expand the point size of the desired font to the equivalent pixel size
+ if (vcl::Window* pDefaultDevice = dynamic_cast<vcl::Window*>(Application::GetDefaultDevice()))
+ pDefaultDevice->SetPointFont(*cellsurface->device, pWidget->get_font());
+ }
+}
+
+}
+
+IMPL_LINK_NOARG(GtkInstanceTreeView, async_signal_changed, void*, void)
+{
+ m_pChangeEvent = nullptr;
+ signal_changed();
+}
+
+IMPL_LINK_NOARG(GtkInstanceTreeView, async_stop_cell_editing, void*, void)
+{
+ end_editing();
+}
+
+namespace {
+
+class GtkInstanceIconView : public GtkInstanceContainer, 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;
+ ImplSVEvent* m_pSelectionChangeEvent;
+
+ DECL_LINK(async_signal_selection_changed, void*, void);
+
+ 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();
+ }
+
+ void insert_item(GtkTreeIter& iter, int pos, const OUString* pId, const OUString* pText, const OUString* pIconName)
+ {
+ gtk_tree_store_insert_with_values(m_pTreeStore, &iter, nullptr, pos,
+ m_nTextCol, !pText ? nullptr : OUStringToOString(*pText, RTL_TEXTENCODING_UTF8).getStr(),
+ m_nIdCol, !pId ? nullptr : OUStringToOString(*pId, 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);
+ }
+ }
+
+ 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)
+ : GtkInstanceContainer(GTK_CONTAINER(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))
+ , 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))
+ , m_pSelectionChangeEvent(nullptr)
+ {
+ m_nIdCol = m_nTextCol + 1;
+ }
+
+ 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 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();
+ g_object_ref(m_pTreeStore);
+ GtkInstanceContainer::freeze();
+ gtk_icon_view_set_model(m_pIconView, nullptr);
+ enable_notify_events();
+ }
+
+ virtual void thaw() override
+ {
+ disable_notify_events();
+ gtk_icon_view_set_model(m_pIconView, GTK_TREE_MODEL(m_pTreeStore));
+ GtkInstanceContainer::thaw();
+ g_object_unref(m_pTreeStore);
+ 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 void disable_notify_events() override
+ {
+ g_signal_handler_block(m_pIconView, m_nSelectionChangedSignalId);
+ g_signal_handler_block(m_pIconView, m_nItemActivatedSignalId);
+
+ GtkInstanceContainer::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceContainer::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);
+
+ g_signal_handler_disconnect(m_pIconView, m_nItemActivatedSignalId);
+ g_signal_handler_disconnect(m_pIconView, m_nSelectionChangedSignalId);
+ }
+};
+
+}
+
+IMPL_LINK_NOARG(GtkInstanceIconView, async_signal_selection_changed, void*, void)
+{
+ m_pSelectionChangeEvent = nullptr;
+ signal_selection_changed();
+}
+
+namespace {
+
+class GtkInstanceSpinButton : public GtkInstanceEntry, 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
+ {
+ gtk_spin_button_update(m_pButton);
+ GtkInstanceEntry::signal_activate();
+ }
+
+ double toGtk(int nValue) const
+ {
+ return static_cast<double>(nValue) / Power10(get_digits());
+ }
+
+ int fromGtk(double fValue) const
+ {
+ return FRound(fValue * Power10(get_digits()));
+ }
+
+public:
+ GtkInstanceSpinButton(GtkSpinButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceEntry(GTK_ENTRY(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)
+ {
+ }
+
+ virtual int get_value() const override
+ {
+ return fromGtk(gtk_spin_button_get_value(m_pButton));
+ }
+
+ virtual void set_value(int 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)
+ {
+ gtk_entry_set_text(GTK_ENTRY(m_pButton), OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
+
+ 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)
+ {
+ gtk_entry_set_text(GTK_ENTRY(m_pButton), OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
+ m_bBlank = false;
+ }
+ }
+ enable_notify_events();
+ }
+
+ virtual void set_range(int min, int max) override
+ {
+ disable_notify_events();
+ gtk_spin_button_set_range(m_pButton, toGtk(min), toGtk(max));
+ enable_notify_events();
+ }
+
+ virtual void get_range(int& min, int& 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 disable_notify_events() override
+ {
+ g_signal_handler_block(m_pButton, m_nValueChangedSignalId);
+ GtkInstanceEntry::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceEntry::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);
+ }
+};
+
+class GtkInstanceFormattedSpinButton : public GtkInstanceEntry, public virtual weld::FormattedSpinButton
+{
+private:
+ GtkSpinButton* m_pButton;
+ SvNumberFormatter* m_pFormatter;
+ Color* m_pLastOutputColor;
+ sal_uInt32 m_nFormatKey;
+ bool m_bTreatAsNumber;
+ gulong m_nValueChangedSignalId;
+ gulong m_nOutputSignalId;
+ gulong m_nInputSignalId;
+
+ bool signal_output()
+ {
+ if (!m_pFormatter)
+ return false;
+ double dVal = get_value();
+ OUString sNewText;
+ if (m_pFormatter->IsTextFormat(m_nFormatKey))
+ {
+ // first convert the number as string in standard format
+ OUString sTemp;
+ m_pFormatter->GetOutputString(dVal, 0, sTemp, &m_pLastOutputColor);
+ // then encode the string in the corresponding text format
+ m_pFormatter->GetOutputString(sTemp, m_nFormatKey, sNewText, &m_pLastOutputColor);
+ }
+ else
+ {
+ m_pFormatter->GetInputLineString(dVal, m_nFormatKey, sNewText);
+ }
+ set_text(sNewText);
+ 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)
+ {
+ if (!m_pFormatter)
+ return 0;
+
+ sal_uInt32 nFormatKey = m_nFormatKey; // IsNumberFormat changes the FormatKey!
+
+ if (m_pFormatter->IsTextFormat(nFormatKey) && m_bTreatAsNumber)
+ // for detection of values like "1,1" in fields that are formatted as text
+ nFormatKey = 0;
+
+ OUString sText(get_text());
+
+ // special treatment for percentage formatting
+ if (m_pFormatter->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 (!m_pFormatter->IsNumberFormat(sText, nFormatKey, *value))
+ return GTK_INPUT_ERROR;
+
+ 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)
+ : GtkInstanceEntry(GTK_ENTRY(pButton), pBuilder, bTakeOwnership)
+ , m_pButton(pButton)
+ , m_pFormatter(nullptr)
+ , m_pLastOutputColor(nullptr)
+ , m_nFormatKey(0)
+ , m_bTreatAsNumber(true)
+ , 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))
+ {
+ }
+
+ virtual double get_value() const override
+ {
+ return gtk_spin_button_get_value(m_pButton);
+ }
+
+ virtual void set_value(double value) override
+ {
+ disable_notify_events();
+ gtk_spin_button_set_value(m_pButton, value);
+ enable_notify_events();
+ }
+
+ virtual void set_range(double min, double max) override
+ {
+ disable_notify_events();
+ gtk_spin_button_set_range(m_pButton, min, max);
+ enable_notify_events();
+ }
+
+ virtual void get_range(double& min, double& max) const override
+ {
+ gtk_spin_button_get_range(m_pButton, &min, &max);
+ }
+
+ virtual void set_formatter(SvNumberFormatter* pFormatter) override
+ {
+ 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 = Application::GetSettings().GetUILanguageTag().getLanguageType( false);
+ // get the standard numeric format for this language
+ m_nFormatKey = m_pFormatter->GetStandardFormat( SvNumFormatType::NUMBER, eSysLanguage );
+ }
+ else
+ m_nFormatKey = 0;
+ signal_output();
+ }
+
+ virtual SvNumberFormatter* get_formatter() override
+ {
+ return m_pFormatter;
+ }
+
+ virtual sal_Int32 get_format_key() const override
+ {
+ return m_nFormatKey;
+ }
+
+ virtual void set_format_key(sal_Int32 nFormatKey) override
+ {
+ m_nFormatKey = nFormatKey;
+ }
+
+ virtual void treat_as_number(bool bSet) override
+ {
+ m_bTreatAsNumber = bSet;
+ }
+
+ virtual void set_digits(unsigned int digits) override
+ {
+ disable_notify_events();
+ gtk_spin_button_set_digits(m_pButton, digits);
+ enable_notify_events();
+ }
+
+ 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);
+ }
+};
+
+class GtkInstanceLabel : public GtkInstanceWidget, public virtual weld::Label
+{
+private:
+ GtkLabel* m_pLabel;
+
+ void set_text_color(const Color& rColor)
+ {
+ guint16 nRed = rColor.GetRed() << 8;
+ guint16 nGreen = rColor.GetRed() << 8;
+ guint16 nBlue = rColor.GetBlue() << 8;
+
+ PangoAttrList* pAttrs = pango_attr_list_new();
+ pango_attr_list_insert(pAttrs, pango_attr_background_new(nRed, nGreen, nBlue));
+ gtk_label_set_attributes(m_pLabel, pAttrs);
+ pango_attr_list_unref(pAttrs);
+ }
+
+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_message_type(weld::EntryMessageType eType) override
+ {
+ if (eType == weld::EntryMessageType::Error)
+ set_text_color(Application::GetSettings().GetStyleSettings().GetHighlightColor());
+ else if (eType == weld::EntryMessageType::Warning)
+ set_text_color(COL_YELLOW);
+ else
+ gtk_label_set_attributes(m_pLabel, nullptr);
+ }
+
+ virtual void set_font(const vcl::Font& rFont) override
+ {
+ PangoAttrList* pAttrList = create_attr_list(rFont);
+ gtk_label_set_attributes(m_pLabel, pAttrList);
+ pango_attr_list_unref(pAttrList);
+ }
+};
+
+}
+
+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 {
+
+class GtkInstanceTextView : public GtkInstanceContainer, public virtual weld::TextView
+{
+private:
+ GtkTextView* m_pTextView;
+ GtkTextBuffer* m_pTextBuffer;
+ GtkAdjustment* m_pVAdjustment;
+ gulong m_nChangedSignalId;
+ gulong m_nCursorPosSignalId;
+ gulong m_nVAdjustChangedSignalId;
+
+ static void signalChanged(GtkTextView*, gpointer widget)
+ {
+ GtkInstanceTextView* pThis = static_cast<GtkInstanceTextView*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_changed();
+ }
+
+ static void signalCursorPosition(GtkTextView*, GParamSpec*, gpointer widget)
+ {
+ GtkInstanceTextView* pThis = static_cast<GtkInstanceTextView*>(widget);
+ pThis->signal_cursor_position();
+ }
+
+ 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)
+ : GtkInstanceContainer(GTK_CONTAINER(pTextView), pBuilder, bTakeOwnership)
+ , m_pTextView(pTextView)
+ , m_pTextBuffer(gtk_text_view_get_buffer(pTextView))
+ , m_pVAdjustment(gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(pTextView)))
+ , m_nChangedSignalId(g_signal_connect(m_pTextBuffer, "changed", G_CALLBACK(signalChanged), this))
+ , m_nCursorPosSignalId(g_signal_connect(m_pTextBuffer, "notify::cursor-position", G_CALLBACK(signalCursorPosition), this))
+ , m_nVAdjustChangedSignalId(g_signal_connect(m_pVAdjustment, "value-changed", G_CALLBACK(signalVAdjustValueChanged), this))
+ {
+ }
+
+ 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();
+ GtkTextBuffer* pBuffer = gtk_text_view_get_buffer(m_pTextView);
+ OString sText(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
+ gtk_text_buffer_set_text(pBuffer, sText.getStr(), sText.getLength());
+ enable_notify_events();
+ }
+
+ virtual OUString get_text() const override
+ {
+ GtkTextBuffer* pBuffer = gtk_text_view_get_buffer(m_pTextView);
+ GtkTextIter start, end;
+ gtk_text_buffer_get_bounds(pBuffer, &start, &end);
+ char* pStr = gtk_text_buffer_get_text(pBuffer, &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();
+ GtkTextBuffer* pBuffer = gtk_text_view_get_buffer(m_pTextView);
+ gtk_text_buffer_delete_selection(pBuffer, false, gtk_text_view_get_editable(m_pTextView));
+ OString sText(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
+ gtk_text_buffer_insert_at_cursor(pBuffer, sText.getStr(), sText.getLength());
+ enable_notify_events();
+ }
+
+ virtual bool get_selection_bounds(int& rStartPos, int& rEndPos) override
+ {
+ GtkTextBuffer* pBuffer = gtk_text_view_get_buffer(m_pTextView);
+ GtkTextIter start, end;
+ gtk_text_buffer_get_selection_bounds(pBuffer, &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();
+ GtkTextBuffer* pBuffer = gtk_text_view_get_buffer(m_pTextView);
+ GtkTextIter start, end;
+ gtk_text_buffer_get_iter_at_offset(pBuffer, &start, nStartPos);
+ gtk_text_buffer_get_iter_at_offset(pBuffer, &end, nEndPos);
+ gtk_text_buffer_select_range(pBuffer, &start, &end);
+ GtkTextMark* mark = gtk_text_buffer_create_mark(pBuffer, "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 void set_monospace(bool bMonospace) override
+ {
+ gtk_text_view_set_monospace(m_pTextView, bMonospace);
+ }
+
+ 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);
+ GtkInstanceContainer::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceContainer::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);
+ }
+
+ 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
+ {
+ g_signal_handler_disconnect(m_pVAdjustment, m_nVAdjustChangedSignalId);
+ g_signal_handler_disconnect(m_pTextBuffer, m_nChangedSignalId);
+ g_signal_handler_disconnect(m_pTextBuffer, m_nCursorPosSignalId);
+ }
+};
+
+// IMHandler
+class IMHandler;
+
+AtkObject* (*default_drawing_area_get_accessible)(GtkWidget *widget);
+
+class GtkInstanceDrawingArea : public GtkInstanceWidget, public virtual weld::DrawingArea
+{
+private:
+ GtkDrawingArea* m_pDrawingArea;
+ a11yref m_xAccessible;
+ AtkObject *m_pAccessible;
+ ScopedVclPtrInstance<VirtualDevice> m_xDevice;
+ std::unique_ptr<IMHandler> m_xIMHandler;
+ cairo_surface_t* m_pSurface;
+ gulong m_nDrawSignalId;
+ gulong m_nStyleUpdatedSignalId;
+ gulong m_nQueryTooltip;
+ gulong m_nPopupMenu;
+ gulong m_nScrollEvent;
+
+ static gboolean signalDraw(GtkWidget*, cairo_t* cr, gpointer widget)
+ {
+ GtkInstanceDrawingArea* pThis = static_cast<GtkInstanceDrawingArea*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_draw(cr);
+ return false;
+ }
+ void signal_draw(cairo_t* cr)
+ {
+ GdkRectangle rect;
+ if (!m_pSurface || !gdk_cairo_get_clip_rectangle(cr, &rect))
+ return;
+ 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
+ {
+ m_xDevice->SetOutputSizePixel(Size(nWidth, nHeight));
+ m_pSurface = get_underlying_cairo_surface(*m_xDevice);
+ GtkInstanceWidget::signal_size_allocate(nWidth, nHeight);
+ }
+ static void signalStyleUpdated(GtkWidget*, gpointer widget)
+ {
+ GtkInstanceDrawingArea* pThis = static_cast<GtkInstanceDrawingArea*>(widget);
+ SolarMutexGuard aGuard;
+ return pThis->signal_style_updated();
+ }
+ void signal_style_updated()
+ {
+ m_aStyleUpdatedHdl.Call(*this);
+ }
+ 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);
+ }
+ bool signal_scroll(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);
+ }
+public:
+ GtkInstanceDrawingArea(GtkDrawingArea* pDrawingArea, GtkInstanceBuilder* pBuilder, const a11yref& rA11y, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pDrawingArea), pBuilder, bTakeOwnership)
+ , m_pDrawingArea(pDrawingArea)
+ , m_xAccessible(rA11y)
+ , m_pAccessible(nullptr)
+ , m_xDevice(DeviceFormat::DEFAULT)
+ , m_pSurface(nullptr)
+ , m_nDrawSignalId(g_signal_connect(m_pDrawingArea, "draw", G_CALLBACK(signalDraw), this))
+ , m_nStyleUpdatedSignalId(g_signal_connect(m_pDrawingArea,"style-updated", G_CALLBACK(signalStyleUpdated), this))
+ , m_nQueryTooltip(g_signal_connect(m_pDrawingArea, "query-tooltip", G_CALLBACK(signalQueryTooltip), this))
+ , 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))
+ {
+ 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());
+ }
+
+ 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;
+ }
+
+ 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));
+ gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(m_pDrawingArea)), pCursor);
+ }
+
+ 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);
+ }
+
+ 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
+ {
+ 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());
+ }
+
+ virtual void queue_resize() override
+ {
+ gtk_widget_queue_resize(GTK_WIDGET(m_pDrawingArea));
+ }
+
+ 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_parent should only be called on a vcl impl");
+ return uno::Reference<css::accessibility::XAccessibleRelationSet>();
+ }
+
+ virtual Point get_accessible_location() override
+ {
+ AtkObject* pAtkObject = default_drawing_area_get_accessible(m_pWidget);
+ gint x(0), y(0);
+ if (pAtkObject && ATK_IS_COMPONENT(pAtkObject))
+ atk_component_get_extents(ATK_COMPONENT(pAtkObject), &x, &y, nullptr, nullptr, ATK_XY_WINDOW);
+ return Point(x, y);
+ }
+
+ virtual void set_accessible_name(const OUString& rName) override
+ {
+ AtkObject* pAtkObject = default_drawing_area_get_accessible(m_pWidget);
+ if (!pAtkObject)
+ return;
+ atk_object_set_name(pAtkObject, OUStringToOString(rName, RTL_TEXTENCODING_UTF8).getStr());
+ }
+
+ virtual OUString get_accessible_name() const override
+ {
+ 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);
+ }
+
+ virtual OUString get_accessible_description() const override
+ {
+ 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);
+ }
+
+ 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
+ {
+ g_object_steal_data(G_OBJECT(m_pDrawingArea), "g-lo-GtkInstanceDrawingArea");
+ if (m_pAccessible)
+ g_object_unref(m_pAccessible);
+ css::uno::Reference<css::lang::XComponent> xComp(m_xAccessible, css::uno::UNO_QUERY);
+ if (xComp.is())
+ xComp->dispose();
+ g_signal_handler_disconnect(m_pDrawingArea, m_nScrollEvent);
+ g_signal_handler_disconnect(m_pDrawingArea, m_nPopupMenu);
+ g_signal_handler_disconnect(m_pDrawingArea, m_nQueryTooltip);
+ g_signal_handler_disconnect(m_pDrawingArea, m_nStyleUpdatedSignalId);
+ g_signal_handler_disconnect(m_pDrawingArea, m_nDrawSignalId);
+ }
+
+ virtual OutputDevice& get_ref_device() override
+ {
+ return *m_xDevice;
+ }
+
+ bool signal_command(const CommandEvent& rCEvt)
+ {
+ return m_aCommandHdl.Call(rCEvt);
+ }
+};
+
+class IMHandler
+{
+private:
+ GtkInstanceDrawingArea* m_pArea;
+ GtkIMContext* m_pIMContext;
+ OUString m_sPreeditText;
+ gulong m_nFocusInSignalId;
+ gulong m_nFocusOutSignalId;
+ bool m_bExtTextInput;
+
+public:
+ IMHandler(GtkInstanceDrawingArea* pArea)
+ : m_pArea(pArea)
+ , m_pIMContext(gtk_im_multicontext_new())
+ , m_nFocusInSignalId(g_signal_connect(m_pArea->getWidget(), "focus-in-event", G_CALLBACK(signalFocusIn), this))
+ , m_nFocusOutSignalId(g_signal_connect(m_pArea->getWidget(), "focus-out-event", G_CALLBACK(signalFocusOut), this))
+ , m_bExtTextInput(false)
+ {
+ 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);
+
+ GtkWidget* pWidget = m_pArea->getWidget();
+ if (!gtk_widget_get_realized(pWidget))
+ gtk_widget_realize(pWidget);
+ GdkWindow* pWin = gtk_widget_get_window(pWidget);
+ gtk_im_context_set_client_window(m_pIMContext, pWin);
+ gtk_im_context_focus_in(m_pIMContext);
+ }
+
+ void signalFocus(bool bIn)
+ {
+ if (bIn)
+ gtk_im_context_focus_in(m_pIMContext);
+ else
+ gtk_im_context_focus_out(m_pIMContext);
+ }
+
+ static gboolean signalFocusIn(GtkWidget*, GdkEvent*, gpointer im_handler)
+ {
+ IMHandler* pThis = static_cast<IMHandler*>(im_handler);
+ pThis->signalFocus(true);
+ return false;
+ }
+
+ static gboolean signalFocusOut(GtkWidget*, GdkEvent*, gpointer im_handler)
+ {
+ IMHandler* pThis = static_cast<IMHandler*>(im_handler);
+ pThis->signalFocus(false);
+ return false;
+ }
+
+ ~IMHandler()
+ {
+ EndExtTextInput();
+
+ g_signal_handler_disconnect(m_pArea->getWidget(), m_nFocusOutSignalId);
+ g_signal_handler_disconnect(m_pArea->getWidget(), m_nFocusInSignalId);
+
+ // first give IC a chance to deinitialize
+ gtk_im_context_set_client_window(m_pIMContext, nullptr);
+ // destroy old IC
+ g_object_unref(m_pIMContext);
+ }
+
+ void updateIMSpotLocation()
+ {
+ CommandEvent aCEvt(Point(), CommandEventId::CursorPos);
+ // we expect set_cursor_location to get triggered by this
+ m_pArea->signal_command(aCEvt);
+ }
+
+ void 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);
+ }
+
+ static void signalIMCommit(GtkIMContext* /*pContext*/, gchar* pText, gpointer im_handler)
+ {
+ IMHandler* pThis = static_cast<IMHandler*>(im_handler);
+
+ // 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();
+ }
+
+ static void signalIMPreeditChanged(GtkIMContext* pIMContext, gpointer im_handler)
+ {
+ IMHandler* pThis = static_cast<IMHandler*>(im_handler);
+
+ 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();
+ }
+
+ static gboolean signalIMRetrieveSurrounding(GtkIMContext* pContext, gpointer im_handler)
+ {
+ IMHandler* pThis = static_cast<IMHandler*>(im_handler);
+
+ OUString sSurroundingText;
+ int nCursorIndex = pThis->m_pArea->im_context_get_surrounding(sSurroundingText);
+
+ if (nCursorIndex != -1)
+ {
+ OString sUTF = OUStringToOString(sSurroundingText, RTL_TEXTENCODING_UTF8);
+ OUString sCursorText(sSurroundingText.copy(0, nCursorIndex));
+ gtk_im_context_set_surrounding(pContext, sUTF.getStr(), sUTF.getLength(),
+ OUStringToOString(sCursorText, RTL_TEXTENCODING_UTF8).getLength());
+ }
+
+ return true;
+ }
+
+ static gboolean signalIMDeleteSurrounding(GtkIMContext*, gint nOffset, gint nChars,
+ gpointer im_handler)
+ {
+ bool bRet = false;
+
+ IMHandler* pThis = static_cast<IMHandler*>(im_handler);
+
+ OUString sSurroundingText;
+ sal_Int32 nCursorIndex = pThis->m_pArea->im_context_get_surrounding(sSurroundingText);
+
+ if (nCursorIndex != -1)
+ {
+ // Note that offset and n_chars are in characters not in bytes
+ // which differs from the usage other places in GtkIMContext
+
+ if (nOffset > 0)
+ {
+ while (nCursorIndex < sSurroundingText.getLength())
+ sSurroundingText.iterateCodePoints(&nCursorIndex, 1);
+ }
+ else if (nOffset < 0)
+ {
+ while (nCursorIndex > 0)
+ sSurroundingText.iterateCodePoints(&nCursorIndex, -1);
+ }
+
+ sal_Int32 nCursorEndIndex(nCursorIndex);
+ sal_Int32 nCount(0);
+ while (nCount < nChars && nCursorEndIndex < sSurroundingText.getLength())
+ ++nCount;
+
+ bRet = pThis->m_pArea->im_context_delete_surrounding(Selection(nCursorIndex, nCursorEndIndex));
+ }
+
+ return bRet;
+ }
+
+ void StartExtTextInput()
+ {
+ if (m_bExtTextInput)
+ return;
+ CommandEvent aCEvt(Point(), CommandEventId::StartExtTextInput);
+ m_pArea->signal_command(aCEvt);
+ m_bExtTextInput = true;
+ }
+
+ static void signalIMPreeditStart(GtkIMContext*, gpointer im_handler)
+ {
+ IMHandler* pThis = static_cast<IMHandler*>(im_handler);
+ pThis->StartExtTextInput();
+ pThis->updateIMSpotLocation();
+ }
+
+ void EndExtTextInput()
+ {
+ if (!m_bExtTextInput)
+ return;
+ CommandEvent aCEvt(Point(), CommandEventId::EndExtTextInput);
+ m_pArea->signal_command(aCEvt);
+ m_bExtTextInput = false;
+ }
+
+ static void signalIMPreeditEnd(GtkIMContext*, gpointer im_handler)
+ {
+ IMHandler* pThis = static_cast<IMHandler*>(im_handler);
+ pThis->updateIMSpotLocation();
+ pThis->EndExtTextInput();
+ }
+};
+
+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);
+}
+
+}
+
+namespace {
+
+GtkBuilder* makeComboBoxBuilder()
+{
+ OUString aUri(VclBuilderContainer::getUIRootDir() + "vcl/ui/combobox.ui");
+ OUString aPath;
+ osl::FileBase::getSystemPathFromFileURL(aUri, aPath);
+ return gtk_builder_new_from_file(OUStringToOString(aPath, RTL_TEXTENCODING_UTF8).getStr());
+}
+
+struct GtkTreeRowReferenceDeleter
+{
+ void operator()(GtkTreeRowReference* p) const
+ {
+ gtk_tree_row_reference_free(p);
+ }
+};
+
+// 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_activate(GtkMenuItem*) override
+ {
+ gtk_toggle_button_set_active(m_pComboBox, false);
+ }
+};
+
+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;
+ std::unique_ptr<CustomRenderMenuButtonHelper> m_xCustomMenuButtonHelper;
+ std::unique_ptr<vcl::Font> m_xFont;
+ 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;
+ 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)
+ set_active_text(aText);
+ 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 toggle_menu()
+ {
+ 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;
+ }
+
+ do_ungrab(GTK_WIDGET(m_pMenuWindow));
+
+ gtk_widget_hide(GTK_WIDGET(m_pMenuWindow));
+
+ // 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 = gtk_widget_get_toplevel(m_pToggleButton);
+ GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(pParent) : nullptr;
+ if (pFrame)
+ pFrame->UnblockTooltip();
+ }
+ 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);
+
+ show_menu(pComboBox, m_pMenuWindow);
+ }
+ }
+
+ virtual void signal_popup_toggled() override
+ {
+ m_aQuickSelectionEngine.Reset();
+
+ toggle_menu();
+
+ bool bIsShown = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_pToggleButton));
+ if (m_bPopupActive != bIsShown)
+ {
+ m_bPopupActive = bIsShown;
+ ComboBox::signal_popup_toggled();
+ if (!m_bPopupActive)
+ {
+ //restore focus to the entry view when the popup is gone, which
+ //is what the vcl case does, to ease the transition a little
+ grab_focus();
+ }
+ }
+ }
+
+ 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, const OUString& 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(const OUString& 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).getStr());
+ 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(GtkTreePath* path)
+ {
+ bool bFound = false;
+ for (auto& a : m_aSeparatorRows)
+ {
+ 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;
+ }
+
+ 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 = gtk_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(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ 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 ? 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 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 deactive
+ 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)
+ {
+ if (pos == -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
+ {
+ GtkTreePath* 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);
+ gtk_tree_view_set_cursor(m_pTreeView, path, nullptr, false);
+ if (m_pCellView)
+ gtk_cell_view_set_displayed_row(m_pCellView, path);
+ 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
+ {
+ //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(gtk_widget_get_window(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;
+ }
+ }
+
+ 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)
+ {
+ 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(const OUString& rStr, bool bSearchMRU) const
+ {
+ return find(rStr, m_nTextCol, bSearchMRU);
+ }
+
+ int find_id_including_mru(const OUString& 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, const OUString& 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, "", 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, const OUString& rText, const OUString* pId, const OUString* pIconName, 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)
+ {
+ 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;
+ }
+
+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_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);
+ 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_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_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
+ {
+ if (m_nMRUCount && pos != -1)
+ pos += (m_nMRUCount + 1);
+ set_active_including_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();
+ if (!bKeepExisting)
+ clear();
+ GtkTreeIter iter;
+ for (const auto& rItem : rItems)
+ {
+ insert_row(GTK_LIST_STORE(m_pTreeModel), iter, -1, 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
+ {
+ if (m_nMRUCount && pos != -1)
+ pos += (m_nMRUCount + 1);
+ insert_including_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_entry_font(const vcl::Font& rFont) override
+ {
+ m_xFont.reset(new vcl::Font(rFont));
+ PangoAttrList* pAttrList = create_attr_list(rFont);
+ assert(m_pEntry);
+ gtk_entry_set_attributes(GTK_ENTRY(m_pEntry), pAttrList);
+ pango_attr_list_unref(pAttrList);
+ }
+
+ virtual vcl::Font get_entry_font() override
+ {
+ if (m_xFont)
+ return *m_xFont;
+ 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();
+ g_object_ref(m_pTreeModel);
+ GtkInstanceContainer::freeze();
+ gtk_tree_view_set_model(m_pTreeView, nullptr);
+ 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 (m_xSorter)
+ {
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
+ gtk_tree_sortable_set_sort_column_id(pSortable, m_nTextCol, GTK_SORT_ASCENDING);
+ }
+ gtk_tree_view_set_model(m_pTreeView, m_pTreeModel);
+
+ GtkInstanceContainer::thaw();
+ g_object_unref(m_pTreeModel);
+ 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(m_pToggleButton, "focus-in-event", G_CALLBACK(signalFocusIn), this);
+ weld::Widget::connect_focus_in(rLink);
+ }
+
+ virtual void connect_focus_out(const Link<Widget&, void>& rLink) override
+ {
+ if (!m_nToggleFocusOutSignalId)
+ m_nToggleFocusOutSignalId = g_signal_connect(m_pToggleButton, "focus-out-event", G_CALLBACK(signalFocusOut), this);
+ weld::Widget::connect_focus_out(rLink);
+ }
+
+ virtual void grab_focus() override
+ {
+ disable_notify_events();
+ if (m_pEntry)
+ gtk_widget_grab_focus(m_pEntry);
+ else
+ gtk_widget_grab_focus(m_pToggleButton);
+ enable_notify_events();
+ }
+
+ 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_surface_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 OString& 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 = OUString::fromUtf8(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);
+ }
+ 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
+ {
+ 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);
+ }
+};
+
+}
+
+bool custom_cell_renderer_surface_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);
+
+ CustomCellRendererSurface *cellsurface = CUSTOM_CELL_RENDERER_SURFACE(cell);
+
+ GtkInstanceWidget* pWidget = static_cast<GtkInstanceWidget*>(g_value_get_pointer(&value));
+
+ Size aSize;
+
+ if (pWidget)
+ {
+ ensure_device(cellsurface, pWidget);
+ if (GtkInstanceTreeView* pTreeView = dynamic_cast<GtkInstanceTreeView*>(pWidget))
+ aSize = pTreeView->call_signal_custom_get_size(*cellsurface->device, sId);
+ else if (GtkInstanceComboBox* pComboBox = dynamic_cast<GtkInstanceComboBox*>(pWidget))
+ aSize = pComboBox->call_signal_custom_get_size(*cellsurface->device);
+ }
+
+ 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_surface_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);
+
+ CustomCellRendererSurface *cellsurface = CUSTOM_CELL_RENDERER_SURFACE(cell);
+
+ GtkInstanceWidget* pWidget = static_cast<GtkInstanceWidget*>(g_value_get_pointer(&value));
+
+ if (!pWidget)
+ return;
+
+ 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);
+
+ if (GtkInstanceTreeView* pTreeView = dynamic_cast<GtkInstanceTreeView*>(pWidget))
+ pTreeView->call_signal_custom_render(*cellsurface->device, tools::Rectangle(Point(0, 0), aSize), flags & GTK_CELL_RENDERER_SELECTED, sId);
+ else if (GtkInstanceComboBox* pComboBox = dynamic_cast<GtkInstanceComboBox*>(pWidget))
+ pComboBox->call_signal_custom_render(*cellsurface->device, tools::Rectangle(Point(0, 0), aSize), flags & GTK_CELL_RENDERER_SELECTED, sId);
+ cairo_surface_mark_dirty(pSurface);
+
+ cairo_set_source_surface(cr, pSurface, cell_area->x, cell_area->y);
+ cairo_paint(cr);
+}
+
+namespace {
+
+class GtkInstanceEntryTreeView : public GtkInstanceContainer, public virtual weld::EntryTreeView
+{
+private:
+ GtkInstanceEntry* m_pEntry;
+ GtkInstanceTreeView* m_pTreeView;
+ gulong m_nKeyPressSignalId;
+ gulong m_nEntryInsertTextSignalId;
+ guint m_nAutoCompleteIdleId;
+ bool m_bAutoCompleteCaseSensitive;
+ bool m_bTreeChange;
+
+ 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);
+ }
+
+ 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, 0, nStart, true);
+ if (nPos == -1 && nStart != 0)
+ {
+ // Try match case insensitive, but from start
+ nPos = m_pTreeView->starts_with(aStartText, 0, 0, true);
+ }
+
+ if (!m_bAutoCompleteCaseSensitive)
+ {
+ // Try match case insensitive from current position
+ nPos = m_pTreeView->starts_with(aStartText, 0, nStart, false);
+ if (nPos == -1 && nStart != 0)
+ {
+ // Try match case insensitive, but from start
+ nPos = m_pTreeView->starts_with(aStartText, 0, 0, false);
+ }
+ }
+
+ if (nPos == -1)
+ {
+ // Try match case sensitive from current position
+ nPos = m_pTreeView->starts_with(aStartText, 0, nStart, true);
+ if (nPos == -1 && nStart != 0)
+ {
+ // Try match case sensitive, but from start
+ nPos = m_pTreeView->starts_with(aStartText, 0, 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:
+ GtkInstanceEntryTreeView(GtkContainer* pContainer, GtkInstanceBuilder* pBuilder, bool bTakeOwnership,
+ std::unique_ptr<weld::Entry> xEntry, std::unique_ptr<weld::TreeView> xTreeView)
+ : 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();
+ m_nKeyPressSignalId = g_signal_connect(pWidget, "key-press-event", G_CALLBACK(signalKeyPress), this);
+ 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_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);
+ g_signal_handler_block(pWidget, m_nKeyPressSignalId);
+ m_pTreeView->disable_notify_events();
+ GtkInstanceContainer::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkWidget* pWidget = m_pEntry->getWidget();
+ g_signal_handler_unblock(pWidget, m_nKeyPressSignalId);
+ g_signal_handler_unblock(pWidget, m_nEntryInsertTextSignalId);
+ m_pTreeView->enable_notify_events();
+ GtkInstanceContainer::disable_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 OString&, 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();
+ g_signal_handler_disconnect(pWidget, m_nKeyPressSignalId);
+ g_signal_handler_disconnect(pWidget, m_nEntryInsertTextSignalId);
+ }
+};
+
+class GtkInstanceExpander : public GtkInstanceContainer, public virtual weld::Expander
+{
+private:
+ GtkExpander* m_pExpander;
+ gulong m_nSignalId;
+
+ static void signalExpanded(GtkExpander* pExpander, GParamSpec*, gpointer widget)
+ {
+ GtkInstanceExpander* pThis = static_cast<GtkInstanceExpander*>(widget);
+ SolarMutexGuard aGuard;
+
+ GtkWidget *pToplevel = gtk_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 (pThis->get_expanded())
+ nToplevelHeight += nChildHeight;
+ else
+ nToplevelHeight -= nChildHeight;
+
+ gtk_window_resize(GTK_WINDOW(pToplevel), nToplevelWidth, nToplevelHeight);
+ }
+
+ pThis->signal_expanded();
+ }
+
+public:
+ GtkInstanceExpander(GtkExpander* pExpander, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceContainer(GTK_CONTAINER(pExpander), pBuilder, bTakeOwnership)
+ , m_pExpander(pExpander)
+ , m_nSignalId(g_signal_connect(m_pExpander, "notify::expanded", G_CALLBACK(signalExpanded), this))
+ {
+ }
+
+ 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
+ {
+ g_signal_handler_disconnect(m_pExpander, m_nSignalId);
+ }
+};
+
+ gboolean signalTooltipQuery(GtkWidget* pWidget, gint /*x*/, gint /*y*/,
+ gboolean /*keyboard_mode*/, GtkTooltip *tooltip)
+ {
+ const ImplSVHelpData& aHelpData = ImplGetSVHelpData();
+ if (aHelpData.mbBalloonHelp)
+ {
+ /*Current mechanism which needs help installed*/
+ OString sHelpId = ::get_help_id(pWidget);
+ Help* pHelp = !sHelpId.isEmpty() ? Application::GetHelp() : nullptr;
+ if (pHelp)
+ {
+ OUString sHelpText = pHelp->GetHelpText(OStringToOUString(sHelpId, RTL_TEXTENCODING_UTF8), static_cast<weld::Widget*>(nullptr));
+ if (!sHelpText.isEmpty())
+ {
+ gtk_tooltip_set_text(tooltip, OUStringToOString(sHelpText, RTL_TEXTENCODING_UTF8).getStr());
+ return true;
+ }
+ }
+
+ /*This is how I would prefer things to be, only a few like this though*/
+ 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;
+ }
+ }
+
+ const char* pDesc = gtk_widget_get_tooltip_text(pWidget);
+ if (pDesc && pDesc[0])
+ {
+ gtk_tooltip_set_text(tooltip, pDesc);
+ return true;
+ }
+
+ return false;
+ }
+}
+
+namespace
+{
+
+AtkObject* drawing_area_get_accessibity(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_accessibity;
+ 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;
+ }
+}
+
+class GtkInstanceBuilder : public weld::Builder
+{
+private:
+ ResHookProc m_pStringReplace;
+ OString m_aUtf8HelpRoot;
+ OUString m_aIconTheme;
+ OUString m_aUILang;
+ GtkBuilder* m_pBuilder;
+ GSList* m_pObjectList;
+ GtkWidget* m_pParentWidget;
+ gulong m_nNotifySignalId;
+ std::vector<GtkButton*> m_aMnemonicButtons;
+ std::vector<GtkLabel*> m_aMnemonicLabels;
+
+ VclPtr<SystemChildWindow> m_xInterimGlue;
+
+ 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);
+ const gchar* icon_name;
+ gtk_image_get_icon_name(pImage, &icon_name, nullptr);
+ if (icon_name)
+ {
+ OUString aIconName(icon_name, strlen(icon_name), RTL_TEXTENCODING_UTF8);
+ GdkPixbuf* pixbuf = load_icon_by_name_theme_lang(aIconName, m_aIconTheme, m_aUILang);
+ if (pixbuf)
+ {
+ gtk_image_set_from_pixbuf(pImage, pixbuf);
+ g_object_unref(pixbuf);
+ }
+ }
+ }
+ else if (GTK_IS_TOOL_BUTTON(pWidget))
+ {
+ GtkToolButton* pToolButton = GTK_TOOL_BUTTON(pWidget);
+ const gchar* icon_name = gtk_tool_button_get_icon_name(pToolButton);
+ if (icon_name)
+ {
+ OUString aIconName(icon_name, strlen(icon_name), RTL_TEXTENCODING_UTF8);
+ GdkPixbuf* pixbuf = load_icon_by_name_theme_lang(aIconName, m_aIconTheme, m_aUILang);
+ if (pixbuf)
+ {
+ GtkWidget* pImage = gtk_image_new_from_pixbuf(pixbuf);
+ g_object_unref(pixbuf);
+ 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);
+ }
+ }
+
+ //set helpids
+ const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pWidget));
+ size_t nLen = pStr ? strlen(pStr) : 0;
+ if (nLen)
+ {
+ OString sBuildableName(pStr, nLen);
+ OString sHelpId = m_aUtf8HelpRoot + 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);
+ }
+
+ // expand placeholder and collect potentially missing mnemonics
+ if (GTK_IS_BUTTON(pWidget))
+ {
+ GtkButton* pButton = GTK_BUTTON(pWidget);
+ if (m_pStringReplace != nullptr)
+ {
+ OUString aLabel(get_label(pButton));
+ if (!aLabel.isEmpty())
+ set_label(pButton, (*m_pStringReplace)(aLabel));
+ }
+ if (gtk_button_get_use_underline(pButton) && !gtk_button_get_use_stock(pButton))
+ m_aMnemonicButtons.push_back(pButton);
+ }
+ else if (GTK_IS_LABEL(pWidget))
+ {
+ GtkLabel* pLabel = GTK_LABEL(pWidget);
+ if (m_pStringReplace != nullptr)
+ {
+ 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 != nullptr)
+ {
+ 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);
+ }
+ }
+ else if (GTK_IS_WINDOW(pWidget))
+ {
+ if (m_pStringReplace != nullptr) {
+ 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));
+ }
+public:
+ GtkInstanceBuilder(GtkWidget* pParent, const OUString& rUIRoot, const OUString& rUIFile, SystemChildWindow* pInterimGlue)
+ : weld::Builder()
+ , m_pStringReplace(Translate::GetReadStringHook())
+ , m_pParentWidget(pParent)
+ , m_nNotifySignalId(0)
+ , m_xInterimGlue(pInterimGlue)
+ {
+ OUString sHelpRoot(rUIFile);
+ ensure_intercept_drawing_area_accessibility();
+ ensure_disable_ctrl_page_up_down_bindings();
+
+ sal_Int32 nIdx = sHelpRoot.lastIndexOf('.');
+ if (nIdx != -1)
+ sHelpRoot = sHelpRoot.copy(0, nIdx);
+ sHelpRoot += OUString('/');
+ m_aUtf8HelpRoot = OUStringToOString(sHelpRoot, RTL_TEXTENCODING_UTF8);
+ m_aIconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme();
+ m_aUILang = Application::GetSettings().GetUILanguageTag().getBcp47();
+
+ OUString aUri(rUIRoot + rUIFile);
+ OUString aPath;
+ osl::FileBase::getSystemPathFromFileURL(aUri, aPath);
+ m_pBuilder = gtk_builder_new();
+ m_nNotifySignalId = g_signal_connect_data(G_OBJECT(m_pBuilder), "notify", G_CALLBACK(signalNotify), this, nullptr, G_CONNECT_AFTER);
+ auto rc = gtk_builder_add_from_file(m_pBuilder, OUStringToOString(aPath, RTL_TEXTENCODING_UTF8).getStr(), nullptr);
+ assert(rc && "could not load UI file");
+ (void) rc;
+
+ 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());
+ }
+ }
+
+ void GenerateMissingMnemonics()
+ {
+ MnemonicGenerator aMnemonicGenerator('_');
+ for (const auto a : m_aMnemonicButtons)
+ aMnemonicGenerator.RegisterMnemonic(get_label(a));
+ for (const auto a : m_aMnemonicLabels)
+ aMnemonicGenerator.RegisterMnemonic(get_label(a));
+
+ for (const auto a : m_aMnemonicButtons)
+ {
+ OUString aLabel(get_label(a));
+ OUString aNewLabel = aMnemonicGenerator.CreateMnemonic(aLabel);
+ if (aLabel == aNewLabel)
+ continue;
+ set_label(a, aNewLabel);
+ }
+ 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();
+ m_aMnemonicButtons.clear();
+ }
+
+ OString get_current_page_help_id()
+ {
+ OString 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", false));
+ if (xNotebook)
+ {
+ if (GtkInstanceContainer* pPage = dynamic_cast<GtkInstanceContainer*>(xNotebook->get_page(xNotebook->get_current_page_ident())))
+ {
+ GtkWidget* pContainer = pPage->getWidget();
+ GList* pChildren = gtk_container_get_children(GTK_CONTAINER(pContainer));
+ GList* pChild = g_list_first(pChildren);
+ if (pChild)
+ {
+ GtkWidget* pPageWidget = static_cast<GtkWidget*>(pChild->data);
+ sPageHelpId = ::get_help_id(pPageWidget);
+ }
+ g_list_free(pChildren);
+ }
+ }
+ return sPageHelpId;
+ }
+
+ virtual ~GtkInstanceBuilder() override
+ {
+ g_slist_free(m_pObjectList);
+ g_object_unref(m_pBuilder);
+ 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_widget_get_toplevel(pWidget) == pWidget && !GTK_IS_POPOVER(pWidget))
+ gtk_container_add(GTK_CONTAINER(m_pParentWidget), pWidget);
+ }
+
+ virtual std::unique_ptr<weld::MessageDialog> weld_message_dialog(const OString &id, bool bTakeOwnership) override
+ {
+ GtkMessageDialog* pMessageDialog = GTK_MESSAGE_DIALOG(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pMessageDialog)
+ return nullptr;
+ gtk_window_set_transient_for(GTK_WINDOW(pMessageDialog), GTK_WINDOW(gtk_widget_get_toplevel(m_pParentWidget)));
+ return std::make_unique<GtkInstanceMessageDialog>(pMessageDialog, this, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::Assistant> weld_assistant(const OString &id, bool bTakeOwnership) override
+ {
+ GtkAssistant* pAssistant = GTK_ASSISTANT(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pAssistant)
+ return nullptr;
+ if (m_pParentWidget)
+ gtk_window_set_transient_for(GTK_WINDOW(pAssistant), GTK_WINDOW(gtk_widget_get_toplevel(m_pParentWidget)));
+ return std::make_unique<GtkInstanceAssistant>(pAssistant, this, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::Dialog> weld_dialog(const OString &id, bool bTakeOwnership) override
+ {
+ GtkWindow* pDialog = GTK_WINDOW(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pDialog)
+ return nullptr;
+ if (m_pParentWidget)
+ gtk_window_set_transient_for(pDialog, GTK_WINDOW(gtk_widget_get_toplevel(m_pParentWidget)));
+ return std::make_unique<GtkInstanceDialog>(pDialog, this, bTakeOwnership);
+ }
+
+ 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));
+ gtk_container_add(GTK_CONTAINER(pContentArea), pTopLevel);
+ gtk_widget_show_all(pTopLevel);
+ }
+
+ if (m_pParentWidget)
+ gtk_window_set_transient_for(pDialog, GTK_WINDOW(gtk_widget_get_toplevel(m_pParentWidget)));
+ return std::make_unique<GtkInstanceDialog>(pDialog, this, true);
+ }
+
+ virtual std::unique_ptr<weld::Window> weld_window(const OString &id, bool bTakeOwnership) override
+ {
+ GtkWindow* pWindow = GTK_WINDOW(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ return pWindow ? std::make_unique<GtkInstanceWindow>(pWindow, this, bTakeOwnership) : nullptr;
+ }
+
+ virtual std::unique_ptr<weld::Widget> weld_widget(const OString &id, bool bTakeOwnership) override
+ {
+ GtkWidget* pWidget = GTK_WIDGET(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pWidget)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(pWidget);
+ return std::make_unique<GtkInstanceWidget>(pWidget, this, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::Container> weld_container(const OString &id, bool bTakeOwnership) override
+ {
+ GtkContainer* pContainer = GTK_CONTAINER(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pContainer)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pContainer));
+ return std::make_unique<GtkInstanceContainer>(pContainer, this, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::Box> weld_box(const OString &id, bool bTakeOwnership) override
+ {
+ GtkBox* pBox = GTK_BOX(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pBox)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pBox));
+ return std::make_unique<GtkInstanceBox>(pBox, this, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::Paned> weld_paned(const OString &id, bool bTakeOwnership) override
+ {
+ GtkPaned* pPaned = GTK_PANED(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pPaned)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pPaned));
+ return std::make_unique<GtkInstancePaned>(pPaned, this, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::Frame> weld_frame(const OString &id, bool bTakeOwnership) override
+ {
+ GtkFrame* pFrame = GTK_FRAME(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pFrame)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pFrame));
+ return std::make_unique<GtkInstanceFrame>(pFrame, this, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::ScrolledWindow> weld_scrolled_window(const OString &id, bool bTakeOwnership) override
+ {
+ GtkScrolledWindow* pScrolledWindow = GTK_SCROLLED_WINDOW(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pScrolledWindow)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pScrolledWindow));
+ return std::make_unique<GtkInstanceScrolledWindow>(pScrolledWindow, this, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::Notebook> weld_notebook(const OString &id, bool bTakeOwnership) override
+ {
+ GtkNotebook* pNotebook = GTK_NOTEBOOK(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pNotebook)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pNotebook));
+ return std::make_unique<GtkInstanceNotebook>(pNotebook, this, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::Button> weld_button(const OString &id, bool bTakeOwnership) override
+ {
+ GtkButton* pButton = GTK_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pButton)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pButton));
+ return std::make_unique<GtkInstanceButton>(pButton, this, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::MenuButton> weld_menu_button(const OString &id, bool bTakeOwnership) override
+ {
+ GtkMenuButton* pButton = GTK_MENU_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pButton)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pButton));
+ return std::make_unique<GtkInstanceMenuButton>(pButton, nullptr, this, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::LinkButton> weld_link_button(const OString &id, bool bTakeOwnership) override
+ {
+ GtkLinkButton* pButton = GTK_LINK_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pButton)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pButton));
+ return std::make_unique<GtkInstanceLinkButton>(pButton, this, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::ToggleButton> weld_toggle_button(const OString &id, bool bTakeOwnership) override
+ {
+ GtkToggleButton* pToggleButton = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pToggleButton)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pToggleButton));
+ return std::make_unique<GtkInstanceToggleButton>(pToggleButton, this, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::RadioButton> weld_radio_button(const OString &id, bool bTakeOwnership) override
+ {
+ GtkRadioButton* pRadioButton = GTK_RADIO_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pRadioButton)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pRadioButton));
+ return std::make_unique<GtkInstanceRadioButton>(pRadioButton, this, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::CheckButton> weld_check_button(const OString &id, bool bTakeOwnership) override
+ {
+ GtkCheckButton* pCheckButton = GTK_CHECK_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pCheckButton)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pCheckButton));
+ return std::make_unique<GtkInstanceCheckButton>(pCheckButton, this, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::Scale> weld_scale(const OString &id, bool bTakeOwnership) override
+ {
+ GtkScale* pScale = GTK_SCALE(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pScale)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pScale));
+ return std::make_unique<GtkInstanceScale>(pScale, this, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::ProgressBar> weld_progress_bar(const OString &id, bool bTakeOwnership) override
+ {
+ GtkProgressBar* pProgressBar = GTK_PROGRESS_BAR(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pProgressBar)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pProgressBar));
+ return std::make_unique<GtkInstanceProgressBar>(pProgressBar, this, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::Spinner> weld_spinner(const OString &id, bool bTakeOwnership) override
+ {
+ GtkSpinner* pSpinner = GTK_SPINNER(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pSpinner)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pSpinner));
+ return std::make_unique<GtkInstanceSpinner>(pSpinner, this, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::Image> weld_image(const OString &id, bool bTakeOwnership) override
+ {
+ GtkImage* pImage = GTK_IMAGE(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pImage)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pImage));
+ return std::make_unique<GtkInstanceImage>(pImage, this, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::Calendar> weld_calendar(const OString &id, bool bTakeOwnership) override
+ {
+ GtkCalendar* pCalendar = GTK_CALENDAR(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pCalendar)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pCalendar));
+ return std::make_unique<GtkInstanceCalendar>(pCalendar, this, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::Entry> weld_entry(const OString &id, bool bTakeOwnership) override
+ {
+ GtkEntry* pEntry = GTK_ENTRY(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pEntry)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pEntry));
+ return std::make_unique<GtkInstanceEntry>(pEntry, this, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::SpinButton> weld_spin_button(const OString &id, bool bTakeOwnership) override
+ {
+ GtkSpinButton* pSpinButton = GTK_SPIN_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pSpinButton)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pSpinButton));
+ return std::make_unique<GtkInstanceSpinButton>(pSpinButton, this, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::MetricSpinButton> weld_metric_spin_button(const OString& id, FieldUnit eUnit,
+ bool bTakeOwnership) override
+ {
+ return std::make_unique<weld::MetricSpinButton>(weld_spin_button(id, bTakeOwnership), eUnit);
+ }
+
+ virtual std::unique_ptr<weld::FormattedSpinButton> weld_formatted_spin_button(const OString &id, bool bTakeOwnership) override
+ {
+ GtkSpinButton* pSpinButton = GTK_SPIN_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pSpinButton)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pSpinButton));
+ return std::make_unique<GtkInstanceFormattedSpinButton>(pSpinButton, this, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::TimeSpinButton> weld_time_spin_button(const OString& id, TimeFieldFormat eFormat,
+ bool bTakeOwnership) override
+ {
+ return std::make_unique<weld::TimeSpinButton>(weld_spin_button(id, bTakeOwnership), eFormat);
+ }
+
+ virtual std::unique_ptr<weld::ComboBox> weld_combo_box(const OString &id, bool bTakeOwnership) override
+ {
+ GtkComboBox* pComboBox = GTK_COMBO_BOX(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pComboBox)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pComboBox));
+
+ /* 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, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::TreeView> weld_tree_view(const OString &id, bool bTakeOwnership) override
+ {
+ GtkTreeView* pTreeView = GTK_TREE_VIEW(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pTreeView)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pTreeView));
+ return std::make_unique<GtkInstanceTreeView>(pTreeView, this, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::IconView> weld_icon_view(const OString &id, bool bTakeOwnership) override
+ {
+ GtkIconView* pIconView = GTK_ICON_VIEW(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pIconView)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pIconView));
+ return std::make_unique<GtkInstanceIconView>(pIconView, this, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::EntryTreeView> weld_entry_tree_view(const OString& containerid, const OString& entryid, const OString& treeviewid, bool bTakeOwnership) override
+ {
+ GtkContainer* pContainer = GTK_CONTAINER(gtk_builder_get_object(m_pBuilder, containerid.getStr()));
+ if (!pContainer)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pContainer));
+ return std::make_unique<GtkInstanceEntryTreeView>(pContainer, this, bTakeOwnership,
+ weld_entry(entryid, bTakeOwnership),
+ weld_tree_view(treeviewid, bTakeOwnership));
+ }
+
+ virtual std::unique_ptr<weld::Label> weld_label(const OString &id, bool bTakeOwnership) override
+ {
+ GtkLabel* pLabel = GTK_LABEL(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pLabel)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pLabel));
+ return std::make_unique<GtkInstanceLabel>(pLabel, this, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::TextView> weld_text_view(const OString &id, bool bTakeOwnership) override
+ {
+ GtkTextView* pTextView = GTK_TEXT_VIEW(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pTextView)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pTextView));
+ return std::make_unique<GtkInstanceTextView>(pTextView, this, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::Expander> weld_expander(const OString &id, bool bTakeOwnership) override
+ {
+ GtkExpander* pExpander = GTK_EXPANDER(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pExpander)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pExpander));
+ return std::make_unique<GtkInstanceExpander>(pExpander, this, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::DrawingArea> weld_drawing_area(const OString &id, const a11yref& rA11y,
+ FactoryFunction /*pUITestFactoryFunction*/, void* /*pUserData*/, bool bTakeOwnership) override
+ {
+ GtkDrawingArea* pDrawingArea = GTK_DRAWING_AREA(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pDrawingArea)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pDrawingArea));
+ return std::make_unique<GtkInstanceDrawingArea>(pDrawingArea, this, rA11y, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::Menu> weld_menu(const OString &id, bool bTakeOwnership) override
+ {
+ GtkMenu* pMenu = GTK_MENU(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pMenu)
+ return nullptr;
+ return std::make_unique<GtkInstanceMenu>(pMenu, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::Toolbar> weld_toolbar(const OString &id, bool bTakeOwnership) override
+ {
+ GtkToolbar* pToolbar = GTK_TOOLBAR(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pToolbar)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pToolbar));
+ return std::make_unique<GtkInstanceToolbar>(pToolbar, this, bTakeOwnership);
+ }
+
+ 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);
+ OString 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)
+ {
+ // tdf#126007, there's a nice fallback route for offline help where
+ // the current page of a notebook will get checked when the help
+ // button is pressed and there was no help for the dialog found.
+ //
+ // But for online help that route doesn't get taken, so bodge this here
+ // by using the page help id if available and if the help button itself
+ // was the original id
+ if (m_pBuilder && sHelpId.endsWith("/help"))
+ {
+ OString sPageId = m_pBuilder->get_current_page_help_id();
+ if (!sPageId.isEmpty())
+ sHelpId = sPageId;
+ else
+ {
+ // tdf#129068 likewise the help for the wrapping dialog is less
+ // helpful than the help for the content area could be
+ 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)
+ {
+ GList* pChildren = gtk_container_get_children(pContainer);
+ GList* pChild = g_list_first(pChildren);
+ if (pChild)
+ {
+ GtkWidget* pContentWidget = static_cast<GtkWidget*>(pChild->data);
+ sHelpId = ::get_help_id(pContentWidget);
+ }
+ g_list_free(pChildren);
+ }
+ }
+ }
+ pHelp->Start(OStringToOUString(sHelpId, RTL_TEXTENCODING_UTF8), pSource);
+ }
+}
+
+//iterate upwards through the hierarchy from this widgets through its parents
+//calling func with their helpid until func returns true or we run out of parents
+void GtkInstanceWidget::help_hierarchy_foreach(const std::function<bool(const OString&)>& func)
+{
+ GtkWidget* pParent = m_pWidget;
+ while ((pParent = gtk_widget_get_parent(pParent)))
+ {
+ if (func(::get_help_id(pParent)))
+ return;
+ }
+}
+
+weld::Builder* GtkInstance::CreateBuilder(weld::Widget* pParent, const OUString& rUIRoot, const OUString& rUIFile)
+{
+ GtkInstanceWidget* pParentWidget = dynamic_cast<GtkInstanceWidget*>(pParent);
+ if (pParent && !pParentWidget) //remove when complete
+ return SalInstance::CreateBuilder(pParent, rUIRoot, rUIFile);
+ GtkWidget* pBuilderParent = pParentWidget ? pParentWidget->getWidget() : nullptr;
+ return new GtkInstanceBuilder(pBuilderParent, rUIRoot, rUIFile, nullptr);
+}
+
+// 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);
+ OString 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(OStringToOUString(sHelpId, RTL_TEXTENCODING_UTF8), pChildWindow);
+ return true;
+ }
+
+ if (!pWidget)
+ return true;
+ std::unique_ptr<weld::Widget> xTemp(new GtkInstanceWidget(pWidget, nullptr, false));
+ pHelp->Start(OStringToOUString(sHelpId, RTL_TEXTENCODING_UTF8), xTemp.get());
+ return true;
+}
+
+weld::Builder* GtkInstance::CreateInterimBuilder(vcl::Window* pParent, const OUString& rUIRoot, const OUString& rUIFile)
+{
+ // 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);
+ gtk_widget_show_all(pWindow);
+
+ // build the widget tree as a child of the GtkEventBox GtkGrid parent
+ return new GtkInstanceBuilder(pWindow, rUIRoot, rUIFile, xEmbedWindow.get());
+}
+
+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(gtk_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);
+ gtk_container_add(GTK_CONTAINER(pParent), pGstWidget);
+ g_object_unref(pGstWidget);
+ gtk_widget_show_all(pParent);
+
+ return pVideosink;
+#else
+ (void)pWindow;
+ return nullptr;
+#endif
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtk3gtkobject.cxx b/vcl/unx/gtk3/gtk3gtkobject.cxx
new file mode 100644
index 000000000..cb60406ec
--- /dev/null
+++ b/vcl/unx/gtk3/gtk3gtkobject.cxx
@@ -0,0 +1,463 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifdef AIX
+#define _LINUX_SOURCE_COMPAT
+#include <sys/timer.h>
+#undef _LINUX_SOURCE_COMPAT
+#endif
+
+#include <unx/gtk/gtkbackend.hxx>
+#include <unx/gtk/gtkobject.hxx>
+#include <unx/gtk/gtkframe.hxx>
+#include <unx/gtk/gtkdata.hxx>
+
+GtkSalObjectBase::GtkSalObjectBase(GtkSalFrame* pParent)
+ : m_pSocket(nullptr)
+ , m_pParent(pParent)
+ , m_pRegion(nullptr)
+{
+ if (!m_pParent)
+ return;
+}
+
+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
+ m_aSystemData.aWindow = m_pParent->GetNativeWindowHandle(m_pSocket);
+ 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::Gtk3;
+ 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 );
+}
+
+GtkSalObjectBase::~GtkSalObjectBase()
+{
+ if( m_pRegion )
+ {
+ cairo_region_destroy( m_pRegion );
+ }
+}
+
+GtkSalObject::~GtkSalObject()
+{
+ if( m_pSocket )
+ {
+ // 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 );
+ }
+}
+
+void GtkSalObject::ResetClipRegion()
+{
+ if( m_pSocket )
+ gdk_window_shape_combine_region( gtk_widget_get_window(m_pSocket), nullptr, 0, 0 );
+}
+
+void GtkSalObjectBase::BeginSetClipRegion( sal_uInt32 )
+{
+ if (m_pRegion)
+ cairo_region_destroy(m_pRegion);
+ m_pRegion = cairo_region_create();
+}
+
+void GtkSalObjectBase::UnionClipRegion( long nX, long nY, long nWidth, 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( m_pSocket )
+ gdk_window_shape_combine_region( gtk_widget_get_window(m_pSocket), m_pRegion, 0, 0 );
+}
+
+void GtkSalObject::SetPosSize(long nX, long nY, long nWidth, 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 );
+ m_pParent->nopaint_container_resize_children(GTK_CONTAINER(pContainer));
+ }
+}
+
+void GtkSalObject::Reparent(SalFrame* pFrame)
+{
+ GtkSalFrame* pNewParent = static_cast<GtkSalFrame*>(pFrame);
+ if (m_pSocket)
+ {
+ GtkFixed* pContainer = GTK_FIXED(gtk_widget_get_parent(m_pSocket));
+
+ gint nX(0), nY(0);
+ gtk_container_child_get(GTK_CONTAINER(pContainer), m_pSocket,
+ "x", &nX,
+ "y", &nY,
+ nullptr);
+
+ g_object_ref(m_pSocket);
+ gtk_container_remove(GTK_CONTAINER(pContainer), m_pSocket);
+
+ 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);
+ GtkRequisition size;
+ gtk_widget_get_preferred_size(m_pSocket, nullptr, &size);
+ 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;
+}
+
+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;
+}
+
+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( 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 ) ) );
+}
+
+GtkSalObjectWidgetClip::GtkSalObjectWidgetClip(GtkSalFrame* pParent, bool bShow)
+ : GtkSalObjectBase(pParent)
+ , m_pScrolledWindow(nullptr)
+{
+ if( !pParent )
+ return;
+
+ m_pScrolledWindow = gtk_scrolled_window_new(nullptr, nullptr);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(m_pScrolledWindow),
+ GTK_POLICY_EXTERNAL, GTK_POLICY_EXTERNAL);
+ g_signal_connect(m_pScrolledWindow, "scroll-event", G_CALLBACK(signalScroll), this);
+
+ // insert into container
+ gtk_fixed_put( pParent->getFixedContainer(),
+ m_pScrolledWindow,
+ 0, 0 );
+
+ // deliberately without adjustments to avoid gtk's auto adjustment on changing focus
+ GtkWidget* pViewPort = gtk_viewport_new(nullptr, nullptr);
+
+ // force in a fake background of a suitable color
+ GtkStyleContext *pWidgetContext = gtk_widget_get_style_context(pViewPort);
+ GtkCssProvider* pBgCssProvider = gtk_css_provider_new();
+ OUString sColor = Application::GetSettings().GetStyleSettings().GetDialogColor().AsRGBHexString();
+ OUString aBuffer = "* { background-color: #" + sColor + "; }";
+ OString aResult = OUStringToOString(aBuffer, RTL_TEXTENCODING_UTF8);
+ gtk_css_provider_load_from_data(pBgCssProvider, aResult.getStr(), aResult.getLength(), nullptr);
+ gtk_style_context_add_provider(pWidgetContext, GTK_STYLE_PROVIDER(pBgCssProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+
+ gtk_container_add(GTK_CONTAINER(m_pScrolledWindow), pViewPort);
+ gtk_widget_show(pViewPort);
+
+ // our plug window
+ m_pSocket = gtk_grid_new();
+ gtk_container_add(GTK_CONTAINER(pViewPort), m_pSocket);
+ gtk_widget_show(m_pSocket);
+
+ Show(bShow);
+
+ Init();
+
+ g_signal_connect( G_OBJECT(m_pSocket), "destroy", G_CALLBACK(signalDestroy), this );
+}
+
+GtkSalObjectWidgetClip::~GtkSalObjectWidgetClip()
+{
+ if( m_pSocket )
+ {
+ // remove socket from parent frame's fixed container
+ 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 );
+ }
+}
+
+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 )
+ {
+ 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);
+ gtk_widget_size_allocate(m_pScrolledWindow, &allocation);
+
+ 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(long nX, long nY, long nWidth, long nHeight)
+{
+ m_aRect = tools::Rectangle(Point(nX, nY), Size(nWidth, nHeight));
+ if (m_pSocket)
+ {
+ GtkFixed* pContainer = GTK_FIXED(gtk_widget_get_parent(m_pScrolledWindow));
+ gtk_widget_set_size_request(m_pSocket, nWidth, nHeight);
+ ApplyClipRegion();
+ m_pParent->nopaint_container_resize_children(GTK_CONTAINER(pContainer));
+ }
+}
+
+void GtkSalObjectWidgetClip::Reparent(SalFrame* pFrame)
+{
+ GtkSalFrame* pNewParent = static_cast<GtkSalFrame*>(pFrame);
+ if (m_pSocket)
+ {
+ GtkFixed* pContainer = GTK_FIXED(gtk_widget_get_parent(m_pScrolledWindow));
+
+ gint nX(0), nY(0);
+ gtk_container_child_get(GTK_CONTAINER(pContainer), m_pScrolledWindow,
+ "x", &nX,
+ "y", &nY,
+ nullptr);
+
+ g_object_ref(m_pScrolledWindow);
+ gtk_container_remove(GTK_CONTAINER(pContainer), m_pScrolledWindow);
+
+ 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 )
+ {
+ if( bVisible )
+ gtk_widget_show(m_pScrolledWindow);
+ else
+ gtk_widget_hide(m_pScrolledWindow);
+ }
+}
+
+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;
+ }
+}
+
+gboolean GtkSalObjectWidgetClip::signalScroll(GtkWidget* pScrolledWindow, GdkEvent* pEvent, gpointer object)
+{
+ GtkSalObjectWidgetClip* pThis = static_cast<GtkSalObjectWidgetClip*>(object);
+ return pThis->signal_scroll(pScrolledWindow, pEvent);
+}
+
+// forward the wheel scroll events onto the main window instead
+bool GtkSalObjectWidgetClip::signal_scroll(GtkWidget*, GdkEvent* pEvent)
+{
+ GtkWidget* pEventWidget = gtk_get_event_widget(pEvent);
+
+ GtkWidget* pMouseEventWidget = m_pParent->getMouseEventWidget();
+
+ gint 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;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtk3gtkprintwrapper.cxx b/vcl/unx/gtk3/gtk3gtkprintwrapper.cxx
new file mode 100644
index 000000000..0066fa004
--- /dev/null
+++ b/vcl/unx/gtk3/gtk3gtkprintwrapper.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 <unx/gtk/gtkprintwrapper.hxx>
+
+namespace vcl::unx
+{
+
+GtkPrintWrapper::GtkPrintWrapper()
+{
+}
+
+GtkPrintWrapper::~GtkPrintWrapper()
+{
+}
+
+bool GtkPrintWrapper::supportsPrinting() const
+{
+ (void) this; // loplugin:staticmethods
+ return true;
+}
+
+bool GtkPrintWrapper::supportsPrintSelection() const
+{
+ (void) this; // loplugin:staticmethods
+ return true;
+}
+
+GtkPageSetup* GtkPrintWrapper::page_setup_new() const
+{
+ (void) this; // loplugin:staticmethods
+ return gtk_page_setup_new();
+}
+
+GtkPrintJob* GtkPrintWrapper::print_job_new(const gchar* title, GtkPrinter* printer, GtkPrintSettings* settings, GtkPageSetup* page_setup) const
+{
+ (void) this; // loplugin:staticmethods
+ return gtk_print_job_new(title, printer, settings, page_setup);
+}
+
+void GtkPrintWrapper::print_job_send(GtkPrintJob* job, GtkPrintJobCompleteFunc callback, gpointer user_data, GDestroyNotify dnotify) const
+{
+ (void) this; // loplugin:staticmethods
+ gtk_print_job_send(job, callback, user_data, dnotify);
+}
+
+gboolean GtkPrintWrapper::print_job_set_source_file(GtkPrintJob* job, const gchar* filename, GError** error) const
+{
+ (void) this; // loplugin:staticmethods
+ return gtk_print_job_set_source_file(job, filename, error);
+}
+
+const gchar* GtkPrintWrapper::print_settings_get(GtkPrintSettings* settings, const gchar* key) const
+{
+ (void) this; // loplugin:staticmethods
+ return gtk_print_settings_get(settings, key);
+}
+
+gboolean GtkPrintWrapper::print_settings_get_collate(GtkPrintSettings* settings) const
+{
+ (void) this; // loplugin:staticmethods
+ return gtk_print_settings_get_collate(settings);
+}
+
+void GtkPrintWrapper::print_settings_set_collate(GtkPrintSettings* settings, gboolean collate) const
+{
+ (void) this; // loplugin:staticmethods
+ gtk_print_settings_set_collate(settings, collate);
+}
+
+gint GtkPrintWrapper::print_settings_get_n_copies(GtkPrintSettings* settings) const
+{
+ (void) this; // loplugin:staticmethods
+ return gtk_print_settings_get_n_copies(settings);
+}
+
+void GtkPrintWrapper::print_settings_set_n_copies(GtkPrintSettings* settings, gint num_copies) const
+{
+ (void) this; // loplugin:staticmethods
+ gtk_print_settings_set_n_copies(settings, num_copies);
+}
+
+GtkPageRange* GtkPrintWrapper::print_settings_get_page_ranges(GtkPrintSettings* settings, gint* num_ranges) const
+{
+ (void) this; // loplugin:staticmethods
+ return gtk_print_settings_get_page_ranges(settings, num_ranges);
+}
+
+void GtkPrintWrapper::print_settings_set_print_pages(GtkPrintSettings* settings, GtkPrintPages pages) const
+{
+ (void) this; // loplugin:staticmethods
+ gtk_print_settings_set_print_pages(settings, pages);
+}
+
+GtkWidget* GtkPrintWrapper::print_unix_dialog_new() const
+{
+ (void) this; // loplugin:staticmethods
+ return gtk_print_unix_dialog_new(nullptr, nullptr);
+}
+
+void GtkPrintWrapper::print_unix_dialog_add_custom_tab(GtkPrintUnixDialog* dialog, GtkWidget* child, GtkWidget* tab_label) const
+{
+ (void) this; // loplugin:staticmethods
+ gtk_print_unix_dialog_add_custom_tab(dialog, child, tab_label);
+}
+
+GtkPrinter* GtkPrintWrapper::print_unix_dialog_get_selected_printer(GtkPrintUnixDialog* dialog) const
+{
+ (void) this; // loplugin:staticmethods
+ GtkPrinter* pRet = gtk_print_unix_dialog_get_selected_printer(dialog);
+ g_object_ref(G_OBJECT(pRet));
+ return pRet;
+}
+
+void GtkPrintWrapper::print_unix_dialog_set_manual_capabilities(GtkPrintUnixDialog* dialog, GtkPrintCapabilities capabilities) const
+{
+ (void) this; // loplugin:staticmethods
+ gtk_print_unix_dialog_set_manual_capabilities(dialog, capabilities);
+}
+
+GtkPrintSettings* GtkPrintWrapper::print_unix_dialog_get_settings(GtkPrintUnixDialog* dialog) const
+{
+ (void) this; // loplugin:staticmethods
+ return gtk_print_unix_dialog_get_settings(dialog);
+}
+
+void GtkPrintWrapper::print_unix_dialog_set_settings(GtkPrintUnixDialog* dialog, GtkPrintSettings* settings) const
+{
+ (void) this; // loplugin:staticmethods
+ gtk_print_unix_dialog_set_settings(dialog, settings);
+}
+
+void GtkPrintWrapper::print_unix_dialog_set_support_selection(GtkPrintUnixDialog* dialog, gboolean support_selection) const
+{
+ (void) this; // loplugin:staticmethods
+ gtk_print_unix_dialog_set_support_selection(dialog, support_selection);
+}
+
+void GtkPrintWrapper::print_unix_dialog_set_has_selection(GtkPrintUnixDialog* dialog, gboolean has_selection) const
+{
+ (void) this; // loplugin:staticmethods
+ gtk_print_unix_dialog_set_has_selection(dialog, has_selection);
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtk3gtksalmenu.cxx b/vcl/unx/gtk3/gtk3gtksalmenu.cxx
new file mode 100644
index 000000000..262187f16
--- /dev/null
+++ b/vcl/unx/gtk3/gtk3gtksalmenu.cxx
@@ -0,0 +1,1409 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/floatwin.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/pngwrite.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<unsigned long>(pParentMenu)) +
+ "-" + OString::number(nItemId);
+ return g_strdup(aCommand.getStr());
+}
+
+static gchar* GetCommandForItem(GtkSalMenuItem* pSalMenuItem)
+{
+ return GetCommandForItem(pSalMenuItem->mpParentMenu,
+ pSalMenuItem->mnId);
+}
+
+bool GtkSalMenu::PrepUpdate()
+{
+ 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)
+ {
+ OString sCommand(action_name);
+
+ sal_Int32 nIndex = 0;
+ OString sWindow = sCommand.getToken(0, '-', nIndex);
+ OString sGtkSalMenu = sCommand.getToken(0, '-', nIndex);
+ OString sItemId = sCommand.getToken(0, '-', nIndex);
+
+ GtkSalMenu* pSalSubMenu = reinterpret_cast<GtkSalMenu*>(sGtkSalMenu.toInt64());
+
+ assert(sWindow == "window" && pSalSubMenu);
+ (void) sWindow;
+
+ return MenuAndId(pSalSubMenu, sItemId.toInt32());
+ }
+}
+
+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( GetFrame()->GetWindow() ) );
+
+ 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 );
+ }
+
+ g_object_unref( 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_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 = true;
+ else
+ bAlwaysShowDisabledEntries = bool(mpVCLMenu->GetMenuFlags() & MenuFlags::AlwaysShowDisabledEntries);
+
+ ImplUpdate(false, !bAlwaysShowDisabledEntries);
+}
+
+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;
+}
+
+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();
+
+ GtkWidget *pWidget = gtk_menu_new_from_model(mpMenuModel);
+ gtk_menu_attach_to_widget(GTK_MENU(pWidget), mpFrame->getMouseEventWidget(), nullptr);
+ 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);
+ g_signal_connect_swapped(G_OBJECT(pWidget), "deactivate", G_CALLBACK(g_main_loop_quit), pLoop);
+
+#if GTK_CHECK_VERSION(3,22,0)
+ if (gtk_check_version(3, 22, 0) == nullptr)
+ {
+ 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;
+ }
+
+ tools::Rectangle aFloatRect = FloatingWindow::ImplConvertToAbsPos(xParent, rRect);
+ aFloatRect.Move(-mpFrame->maGeometry.nX, -mpFrame->maGeometry.nY);
+ GdkRectangle rect {static_cast<int>(aFloatRect.Left()), static_cast<int>(aFloatRect.Top()),
+ static_cast<int>(aFloatRect.GetWidth()), static_cast<int>(aFloatRect.GetHeight())};
+
+ GdkWindow* gdkWindow = gtk_widget_get_window(mpFrame->getMouseEventWidget());
+ gtk_menu_popup_at_rect(GTK_MENU(pWidget), 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
+ sal_uInt16 nArrangeIndex;
+ Point aPos = FloatingWindow::ImplCalcPos(pWin, rRect, nFlags, nArrangeIndex);
+ aPos = FloatingWindow::ImplConvertToAbsPos(xParent, aPos);
+
+ gtk_menu_popup(GTK_MENU(pWidget), nullptr, nullptr, MenuPositionFunc,
+ &aPos, nButton, nTime);
+ }
+
+ if (g_main_loop_is_running(pLoop))
+ {
+ gdk_threads_leave();
+ g_main_loop_run(pLoop);
+ gdk_threads_enter();
+ }
+ g_main_loop_unref(pLoop);
+
+ mpVCLMenu->Deactivate();
+
+ gtk_widget_insert_action_group(mpFrame->getMouseEventWidget(), "win", nullptr);
+
+ gtk_widget_destroy(pWidget);
+
+ g_object_unref(mpActionGroup);
+ ClearActionGroupAndMenuModel();
+
+ mpFrame = nullptr;
+
+ return true;
+}
+
+/*
+ * GtkSalMenu
+ */
+
+GtkSalMenu::GtkSalMenu( bool bMenuBar ) :
+ mbInActivateCallback( false ),
+ mbMenuBar( bMenuBar ),
+ mbNeedsUpdate( false ),
+ mbReturnFocusToDocument( false ),
+ mbAddedGrab( false ),
+ mpMenuBarContainerWidget( nullptr ),
+ mpMenuAllowShrinkWidget( nullptr ),
+ mpMenuBarWidget( 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));
+ maUpdateMenuBarIdle.SetDebugName("Native Gtk Menu Update Idle");
+}
+
+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;
+
+ DestroyMenuBarWidget();
+
+ if (mpMenuModel)
+ g_object_unref(mpMenuModel);
+
+ maItems.clear();
+
+ 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;
+ 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());
+}
+
+void GtkSalMenu::ShowCloseButton(bool bShow)
+{
+ assert(mbMenuBar);
+ if (!mpMenuBarContainerWidget)
+ return;
+
+ if (!bShow)
+ {
+ if (mpCloseButton)
+ {
+ gtk_widget_destroy(mpCloseButton);
+ mpCloseButton = nullptr;
+ }
+ return;
+ }
+
+ MenuBar *pVclMenuBar = static_cast<MenuBar*>(mpVCLMenu.get());
+ mpCloseButton = gtk_button_new();
+ g_signal_connect(mpCloseButton, "clicked", G_CALLBACK(CloseMenuBar), pVclMenuBar);
+
+ gtk_button_set_relief(GTK_BUTTON(mpCloseButton), GTK_RELIEF_NONE);
+ gtk_button_set_focus_on_click(GTK_BUTTON(mpCloseButton), false);
+ gtk_widget_set_can_focus(mpCloseButton, false);
+
+ GtkStyleContext *pButtonContext = gtk_widget_get_style_context(GTK_WIDGET(mpCloseButton));
+
+ GtkCssProvider *pProvider = gtk_css_provider_new();
+ static const gchar data[] = "* { "
+ "padding: 0;"
+ "margin-left: 8px;"
+ "margin-right: 8px;"
+ "min-width: 18px;"
+ "min-height: 18px;"
+ "}";
+ const gchar olddata[] = "* { "
+ "padding: 0;"
+ "margin-left: 8px;"
+ "margin-right: 8px;"
+ "}";
+ gtk_css_provider_load_from_data(pProvider, gtk_check_version(3, 20, 0) == nullptr ? data : olddata, -1, nullptr);
+ gtk_style_context_add_provider(pButtonContext,
+ GTK_STYLE_PROVIDER(pProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+
+ gtk_style_context_add_class(pButtonContext, "flat");
+ gtk_style_context_add_class(pButtonContext, "small-button");
+
+ GIcon* icon = g_themed_icon_new_with_default_fallbacks("window-close-symbolic");
+ GtkWidget* image = gtk_image_new_from_gicon(icon, GTK_ICON_SIZE_MENU);
+ gtk_widget_show(image);
+ g_object_unref(icon);
+
+ OUString sToolTip(VclResId(SV_HELPTEXT_CLOSEDOCUMENT));
+ gtk_widget_set_tooltip_text(mpCloseButton,
+ OUStringToOString(sToolTip, RTL_TEXTENCODING_UTF8).getStr());
+
+ gtk_widget_set_valign(mpCloseButton, GTK_ALIGN_CENTER);
+
+ gtk_container_add(GTK_CONTAINER(mpCloseButton), image);
+ gtk_grid_attach(GTK_GRID(mpMenuBarContainerWidget), GTK_WIDGET(mpCloseButton), 1, 0, 1, 1);
+ gtk_widget_show_all(mpCloseButton);
+}
+
+//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)
+ {
+ gtk_grab_remove(mpMenuBarWidget);
+ mbAddedGrab = false;
+ }
+ if (!mbReturnFocusToDocument)
+ gtk_widget_grab_focus(GTK_WIDGET(mpFrame->getEventBox()));
+ else
+ mpFrame->GetWindow()->GrabFocusToDocument();
+ mbReturnFocusToDocument = false;
+}
+
+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;
+}
+
+//The GtkSalMenu is owner 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;
+
+ //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));
+ mbReturnFocusToDocument = true;
+ return true;
+}
+
+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);
+}
+
+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);
+
+ mpMenuAllowShrinkWidget = gtk_scrolled_window_new(nullptr, nullptr);
+ // tdf#129634 don't allow this scrolled window as a candidate to tab into
+ gtk_widget_set_can_focus(GTK_WIDGET(mpMenuAllowShrinkWidget), false);
+ gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(mpMenuAllowShrinkWidget), GTK_SHADOW_NONE);
+ // 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);
+
+ mpMenuBarWidget = gtk_menu_bar_new_from_model(mpMenuModel);
+
+ gtk_widget_insert_action_group(mpMenuBarWidget, "win", mpActionGroup);
+ gtk_widget_set_hexpand(GTK_WIDGET(mpMenuBarWidget), true);
+ gtk_widget_set_hexpand(mpMenuAllowShrinkWidget, true);
+ gtk_container_add(GTK_CONTAINER(mpMenuAllowShrinkWidget), mpMenuBarWidget);
+
+ g_signal_connect(G_OBJECT(mpMenuBarWidget), "deactivate", G_CALLBACK(MenuBarReturnFocus), this);
+ g_signal_connect(G_OBJECT(mpMenuBarWidget), "key-press-event", G_CALLBACK(MenuBarSignalKey), this);
+
+ gtk_widget_show_all(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)
+ {
+ vcl::PNGWriter aPNGWriter(rPersonaBitmap);
+ mxPersonaImage.reset(new utl::TempFile);
+ mxPersonaImage->EnableKillingFile(true);
+ SvStream* pStream = mxPersonaImage->GetStream(StreamMode::WRITE);
+ aPNGWriter.Write(*pStream);
+ 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);
+ gtk_css_provider_load_from_data(mpMenuBarContainerProvider, aResult.getStr(), aResult.getLength(), nullptr);
+ 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;"
+ "}";
+ gtk_css_provider_load_from_data(mpMenuBarProvider, data, -1, nullptr);
+ gtk_style_context_add_provider(pMenuBarContext,
+ GTK_STYLE_PROVIDER(mpMenuBarProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+ maPersonaBitmap = rPersonaBitmap;
+}
+
+void GtkSalMenu::DestroyMenuBarWidget()
+{
+ if (mpMenuBarContainerWidget)
+ {
+ gtk_widget_destroy(mpMenuBarContainerWidget);
+ mpMenuBarContainerWidget = 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();
+ GdkWindow* gdkWindow = gtk_widget_get_window( 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 );
+}
+
+namespace
+{
+ void DestroyMemoryStream(gpointer data)
+ {
+ SvMemoryStream* pMemStm = static_cast<SvMemoryStream*>(data);
+ delete pMemStm;
+ }
+}
+
+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;
+ vcl::PNGWriter aWriter(rImage.GetBitmapEx());
+ aWriter.Write(*pMemStm);
+
+ 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, const OUString& rKeyName )
+{
+ SolarMutexGuard aGuard;
+
+ if ( rKeyName.isEmpty() )
+ 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();
+ if (pTopLevel->mpMenuBarWidget)
+ {
+ // 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
+ gtk_menu_shell_deactivate(GTK_MENU_SHELL(pTopLevel->mpMenuBarWidget));
+ }
+ pTopLevel->GetMenu()->HandleMenuCommandEvent(pSalSubMenu->GetMenu(), aMenuAndId.second);
+}
+
+void GtkSalMenu::ActivateAllSubmenus(Menu* pMenuBar)
+{
+ for (GtkSalMenuItem* pSalItem : maItems)
+ {
+ if ( pSalItem->mpSubMenu != nullptr )
+ {
+ // 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 (!pSalItem->mpSubMenu->mbInActivateCallback)
+ {
+ pSalItem->mpSubMenu->mbInActivateCallback = true;
+ pMenuBar->HandleMenuActivateEvent(pSalItem->mpSubMenu->GetMenu());
+ pSalItem->mpSubMenu->mbInActivateCallback = false;
+ pSalItem->mpSubMenu->ActivateAllSubmenus(pMenuBar);
+ pSalItem->mpSubMenu->Update();
+ pMenuBar->HandleMenuDeActivateEvent(pSalItem->mpSubMenu->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;
+ GtkSalMenu* pTopLevel = pSalMenu->GetTopLevel();
+ Menu* pVclMenu = pSalMenu->GetMenu();
+ 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;
+ GtkSalMenu* pTopLevel = pSalMenu->GetTopLevel();
+ Menu* pVclMenu = pSalMenu->GetMenu();
+ 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() ) )
+ {
+ 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/gtk3gtksys.cxx b/vcl/unx/gtk3/gtk3gtksys.cxx
new file mode 100644
index 000000000..379c87343
--- /dev/null
+++ b/vcl/unx/gtk3/gtk3gtksys.cxx
@@ -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 .
+ */
+
+#include <gtk/gtk.h>
+#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() : SalGenericSystem()
+{
+ mpDisplay = gdk_display_get_default();
+ countScreenMonitors();
+ // 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
+ ;
+ }
+};
+
+}
+
+/**
+ * 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);
+ }
+}
+
+SalX11Screen
+GtkSalSystem::getXScreenFromDisplayScreen(unsigned int nScreen)
+{
+ gint nMonitor;
+
+ GdkScreen *pScreen = getScreenMonitorFromIdx (nScreen, nMonitor);
+ if (!pScreen)
+ return SalX11Screen (0);
+ if (!DLSYM_GDK_IS_X11_DISPLAY(mpDisplay))
+ return SalX11Screen (0);
+ return SalX11Screen (gdk_x11_screen_get_screen_number (pScreen));
+}
+
+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);
+}
+
+unsigned int GtkSalSystem::GetDisplayScreenCount()
+{
+ gint nMonitor;
+ (void)getScreenMonitorFromIdx (G_MAXINT, nMonitor);
+ return G_MAXINT - nMonitor;
+}
+
+bool GtkSalSystem::IsUnifiedDisplay()
+{
+ return gdk_display_get_n_screens (mpDisplay) == 1;
+}
+
+namespace {
+int _fallback_get_primary_monitor (GdkScreen *pScreen)
+{
+ // Use monitor name as primacy heuristic
+ int max = gdk_screen_get_n_monitors (pScreen);
+ for (int i = 0; i < max; ++i)
+ {
+ char *name = gdk_screen_get_monitor_plug_name (pScreen, i);
+ bool bLaptop = (name && !g_ascii_strncasecmp (name, "LVDS", 4));
+ g_free (name);
+ if (bLaptop)
+ return i;
+ }
+ return 0;
+}
+
+int _get_primary_monitor (GdkScreen *pScreen)
+{
+ static int (*get_fn) (GdkScreen *) = nullptr;
+ get_fn = gdk_screen_get_primary_monitor;
+ // Perhaps we have a newer gtk+ with this symbol:
+ if (!get_fn)
+ {
+ get_fn = reinterpret_cast<int(*)(GdkScreen*)>(osl_getAsciiFunctionSymbol(nullptr,
+ "gdk_screen_get_primary_monitor"));
+ }
+ if (!get_fn)
+ get_fn = _fallback_get_primary_monitor;
+ if (get_fn)
+ return get_fn (pScreen);
+ else
+ return 0;
+}
+} // end anonymous namespace
+
+unsigned int GtkSalSystem::GetDisplayBuiltInScreen()
+{
+ GdkScreen *pDefault = gdk_display_get_default_screen (mpDisplay);
+ int idx = getScreenIdxFromPtr (pDefault);
+ return idx + _get_primary_monitor (pDefault);
+}
+
+tools::Rectangle GtkSalSystem::GetDisplayScreenPosSizePixel (unsigned int nScreen)
+{
+ gint nMonitor;
+ GdkScreen *pScreen;
+ GdkRectangle aRect;
+ pScreen = getScreenMonitorFromIdx (nScreen, nMonitor);
+ if (!pScreen)
+ return tools::Rectangle();
+ gdk_screen_get_monitor_geometry (pScreen, nMonitor, &aRect);
+ return tools::Rectangle (Point(aRect.x, aRect.y), Size(aRect.width, aRect.height));
+}
+
+// convert ~ to indicate mnemonic to '_'
+static OString MapToGtkAccelerator(const OUString &rStr)
+{
+ return OUStringToOString(rStr.replaceFirst("~", "_"), RTL_TEXTENCODING_UTF8);
+}
+
+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;
+
+ gtk_widget_destroy (GTK_WIDGET (pDialog));
+
+ return nButton;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtk3hudawareness.cxx b/vcl/unx/gtk3/gtk3hudawareness.cxx
new file mode 100644
index 000000000..dddad28da
--- /dev/null
+++ b/vcl/unx/gtk3/gtk3hudawareness.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
+{
+ GDBusConnection *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 = static_cast<GDBusConnection*>(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/gtk3salnativewidgets-gtk.cxx b/vcl/unx/gtk3/gtk3salnativewidgets-gtk.cxx
new file mode 100644
index 000000000..50e894f40
--- /dev/null
+++ b/vcl/unx/gtk3/gtk3salnativewidgets-gtk.cxx
@@ -0,0 +1,3731 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/decoview.hxx>
+#include <vcl/settings.hxx>
+#include <unx/fontmanager.hxx>
+
+#include "cairo_gtk3_cairo.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::mpToolbarSeperatorStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpCheckButtonStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpCheckButtonCheckStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpRadioButtonStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpRadioButtonRadioStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpSpinStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpSpinEntryStyle = 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;
+
+bool GtkSalGraphics::style_loaded = false;
+/************************************************************************
+ * State conversion
+ ************************************************************************/
+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;
+ gtk_style_context_get_padding(mpSpinUpStyle, gtk_style_context_get_state(mpSpinUpStyle), &padding);
+ gtk_style_context_get_border(mpSpinUpStyle, gtk_style_context_get_state(mpSpinUpStyle), &border);
+
+ gint buttonWidth = icon_size + padding.left + padding.right +
+ border.left + border.right;
+
+ gint buttonHeight = icon_size + padding.top + padding.bottom +
+ border.top + border.bottom;
+
+ tools::Rectangle buttonRect;
+ buttonRect.SetSize(Size(buttonWidth, buttonHeight));
+ buttonRect.setY(aAreaRect.Top());
+ buttonRect.SetBottom( buttonRect.Top() + aAreaRect.GetHeight() );
+ tools::Rectangle partRect(buttonRect);
+ if ( nPart == ControlPart::ButtonUp )
+ {
+ if (AllSettings::GetLayoutRTL())
+ partRect.setX(aAreaRect.Left());
+ else
+ partRect.setX(aAreaRect.Left() + (aAreaRect.GetWidth() - buttonRect.GetWidth()));
+ }
+ else if( nPart == ControlPart::ButtonDown )
+ {
+ if (AllSettings::GetLayoutRTL())
+ partRect.setX(aAreaRect.Left() + buttonRect.GetWidth());
+ else
+ partRect.setX(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;
+}
+
+namespace
+{
+ void QuerySize(GtkStyleContext *pContext, Size &rSize)
+ {
+ GtkBorder margin, border, padding;
+ GtkStateFlags stateflags = gtk_style_context_get_state (pContext);
+
+ gtk_style_context_get_margin(pContext, stateflags, &margin);
+ gtk_style_context_get_border(pContext, stateflags, &border);
+ gtk_style_context_get_padding(pContext, stateflags, &padding);
+
+ int nMinWidth, nMinHeight;
+ 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<long>(rSize.Width(), nMinWidth), std::max<long>(rSize.Height(), nMinHeight));
+ }
+}
+
+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 buttonWidth;
+ gint buttonHeight;
+
+ 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;
+
+ if (gtk_check_version(3, 20, 0) == nullptr)
+ {
+ 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.setX(aAreaRect.Left());
+ buttonRect.setY(aAreaRect.Top());
+ }
+ else if (nPart == ControlPart::ButtonLeft)
+ {
+ aSize.setWidth( aSize.Width() * nFirst );
+ buttonRect.setX(aAreaRect.Left());
+ buttonRect.setY(aAreaRect.Top());
+ }
+ else if (nPart == ControlPart::ButtonDown)
+ {
+ aSize.setHeight( aSize.Height() * nSecond );
+ buttonRect.setX(aAreaRect.Left());
+ buttonRect.setY(aAreaRect.Top() + aAreaRect.GetHeight() - aSize.Height());
+ }
+ else if (nPart == ControlPart::ButtonRight)
+ {
+ aSize.setWidth( aSize.Width() * nSecond );
+ buttonRect.setX(aAreaRect.Left() + aAreaRect.GetWidth() - aSize.Width());
+ buttonRect.setY(aAreaRect.Top());
+ }
+
+ buttonRect.SetSize(aSize);
+
+ return buttonRect;
+ }
+
+ gint slider_width;
+ gint stepper_size;
+ gint stepper_spacing;
+ gint trough_border;
+
+ // Grab some button style attributes
+ gtk_style_context_get_style( pScrollbarStyle,
+ "slider-width", &slider_width,
+ "stepper-size", &stepper_size,
+ "trough-border", &trough_border,
+ "stepper-spacing", &stepper_spacing, nullptr );
+
+ if ( ( nPart == ControlPart::ButtonUp ) || ( nPart == ControlPart::ButtonDown ) )
+ {
+ buttonWidth = slider_width + 2 * trough_border;
+ buttonHeight = stepper_size + trough_border + stepper_spacing;
+ }
+ else
+ {
+ buttonWidth = stepper_size + trough_border + stepper_spacing;
+ buttonHeight = slider_width + 2 * trough_border;
+ }
+
+ if ( nPart == ControlPart::ButtonUp )
+ {
+ buttonHeight *= nFirst;
+ buttonHeight -= 1;
+ buttonRect.setX( aAreaRect.Left() );
+ buttonRect.setY( aAreaRect.Top() );
+ }
+ else if ( nPart == ControlPart::ButtonLeft )
+ {
+ buttonWidth *= nFirst;
+ buttonWidth -= 1;
+ buttonRect.setX( aAreaRect.Left() );
+ buttonRect.setY( aAreaRect.Top() );
+ }
+ else if ( nPart == ControlPart::ButtonDown )
+ {
+ buttonHeight *= nSecond;
+ buttonRect.setX( aAreaRect.Left() );
+ buttonRect.setY( aAreaRect.Top() + aAreaRect.GetHeight() - buttonHeight );
+ }
+ else if ( nPart == ControlPart::ButtonRight )
+ {
+ buttonWidth *= nSecond;
+ buttonRect.setX( aAreaRect.Left() + aAreaRect.GetWidth() - buttonWidth );
+ buttonRect.setY( aAreaRect.Top() );
+ }
+
+ buttonRect.SetSize( Size( buttonWidth, buttonHeight ) );
+
+ return buttonRect;
+}
+
+static GtkWidget* gCacheWindow;
+static GtkWidget* gDumbContainer;
+static GtkWidget* gSpinBox;
+static GtkWidget* gEntryBox;
+static GtkWidget* gComboBox;
+static GtkWidget* gListBox;
+static GtkWidget* gMenuBarWidget;
+static GtkWidget* gMenuItemMenuBarWidget;
+static GtkWidget* gCheckMenuItemWidget;
+static GtkWidget* gTreeViewWidget;
+
+namespace
+{
+ void style_context_set_state(GtkStyleContext* context, GtkStateFlags flags)
+ {
+ do
+ {
+ gtk_style_context_set_state(context, flags);
+ }
+ while ((context = gtk_style_context_get_parent(context)));
+ }
+
+ class StyleContextSave
+ {
+ private:
+ std::vector<std::pair<GtkStyleContext*, GtkStateFlags>> m_aStates;
+ public:
+ void save(GtkStyleContext* context)
+ {
+ do
+ {
+ m_aStates.emplace_back(context, gtk_style_context_get_state(context));
+ }
+ while ((context = gtk_style_context_get_parent(context)));
+ }
+ 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();
+ }
+ };
+
+ 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;
+ gtk_style_context_get_margin(pContext, gtk_style_context_get_state(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;
+ gtk_style_context_get_border(pContext, gtk_style_context_get_state(pContext), &border);
+ gtk_style_context_get_padding(pContext, gtk_style_context_get_state(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;
+ }
+}
+
+void GtkSalGraphics::PaintScrollbar(GtkStyleContext *context,
+ cairo_t *cr,
+ const tools::Rectangle& rControlRectangle,
+ ControlPart nPart,
+ const ImplControlValue& rValue )
+{
+ if (gtk_check_version(3, 20, 0) == nullptr)
+ {
+ 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 )
+ {
+ 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;
+ gtk_style_context_get_margin(pScrollbarSliderStyle, stateFlags, &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);
+ }
+
+ return;
+ }
+
+ OSL_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;
+ gint trough_border = 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;
+ scrollbarRect.SetSize( Size( scrollbarRect.GetWidth() + 1,
+ scrollbarRect.GetHeight() + 1 ) );
+
+ if ( (scrollbarRect.GetWidth() <= 1) || (scrollbarRect.GetHeight() <= 1) )
+ return;
+
+ // Grab some button style attributes
+ gtk_style_context_get_style( context,
+ "slider_width", &slider_width,
+ "stepper_size", &stepper_size,
+ "trough_border", &trough_border, 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 );
+ gint magic = trough_border ? 1 : 0;
+ gint slider_side = slider_width + (trough_border * 2);
+
+ if ( nPart == ControlPart::DrawBackgroundHorz )
+ {
+ scrollbarRect.Move( 0, (scrollbarRect.GetHeight() - slider_side) / 2 );
+ scrollbarRect.SetSize( Size( scrollbarRect.GetWidth(), slider_side ) );
+
+ scrollbarOrientation = GTK_ORIENTATION_HORIZONTAL;
+ arrow1Angle = G_PI * 3 / 2;
+ arrow2Angle = G_PI / 2;
+
+ if ( has_backward )
+ {
+ button12BoundRect.Move( stepper_size - trough_border,
+ (scrollbarRect.GetHeight() - slider_width) / 2 );
+ }
+
+ button11BoundRect.Move( trough_border, (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+(trough_border+1)/2, (scrollbarRect.GetHeight() - slider_width) / 2 );
+ button21BoundRect.Move( (trough_border+1)/2, (scrollbarRect.GetHeight() - slider_width) / 2 );
+ }
+ else
+ {
+ button22BoundRect.Move( (trough_border+1)/2, (scrollbarRect.GetHeight() - slider_width) / 2 );
+ }
+
+ button21BoundRect.SetSize( Size( stepper_size, slider_width ) );
+ button22BoundRect.SetSize( Size( stepper_size, slider_width ) );
+
+ thumbRect.SetBottom( thumbRect.Top() + slider_width - 1 );
+ // Make sure the thumb is at least the default width (so we don't get tiny thumbs),
+ // but if the VCL gives us a size smaller than the theme's default thumb size,
+ // honor the VCL size
+ thumbRect.AdjustRight(magic );
+ // Center vertically in the track
+ thumbRect.Move( 0, (scrollbarRect.GetHeight() - slider_width) / 2 );
+ }
+ else
+ {
+ scrollbarRect.Move( (scrollbarRect.GetWidth() - slider_side) / 2, 0 );
+ scrollbarRect.SetSize( Size( slider_side, scrollbarRect.GetHeight() ) );
+
+ scrollbarOrientation = GTK_ORIENTATION_VERTICAL;
+ arrow1Angle = 0;
+ arrow2Angle = G_PI;
+
+ if ( has_backward )
+ {
+ button12BoundRect.Move( (scrollbarRect.GetWidth() - slider_width) / 2,
+ stepper_size + trough_border );
+ }
+ button11BoundRect.Move( (scrollbarRect.GetWidth() - slider_width) / 2, trough_border );
+ 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+(trough_border+1)/2 );
+ button21BoundRect.Move( (scrollbarRect.GetWidth() - slider_width) / 2, (trough_border+1)/2 );
+ }
+ else
+ {
+ button22BoundRect.Move( (scrollbarRect.GetWidth() - slider_width) / 2, (trough_border+1)/2 );
+ }
+
+ button21BoundRect.SetSize( Size( slider_width, stepper_size ) );
+ button22BoundRect.SetSize( Size( slider_width, stepper_size ) );
+
+ thumbRect.SetRight( thumbRect.Left() + slider_width - 1 );
+
+ thumbRect.AdjustBottom(magic );
+ // Center horizontally in the track
+ thumbRect.Move( (scrollbarRect.GetWidth() - slider_width) / 2, 0 );
+ }
+
+ 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() );
+
+ // ----------------- TROUGH
+ GtkStyleContext* pScrollbarTroughStyle = scrollbarOrientation == GTK_ORIENTATION_VERTICAL ?
+ mpVScrollbarTroughStyle : mpHScrollbarTroughStyle;
+ gtk_render_background(pScrollbarTroughStyle, cr, 0, 0,
+ scrollbarRect.GetWidth(), scrollbarRect.GetHeight() );
+ gtk_render_frame(pScrollbarTroughStyle, cr, 0, 0,
+ scrollbarRect.GetWidth(), scrollbarRect.GetHeight() );
+
+ // ----------------- THUMB
+ if ( has_slider )
+ {
+ 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;
+ gtk_style_context_get_margin(pScrollbarSliderStyle, stateFlags, &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);
+ }
+
+ 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_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()) );
+ }
+ 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()) );
+ }
+}
+
+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);
+ stateFlags = gtk_style_context_get_state(context);
+
+ gtk_style_context_get_padding(context, stateFlags, &padding);
+ gtk_style_context_get_border(context, stateFlags, &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;
+ arrowRect.SetSize(Size(iconWidth, iconHeight));
+ arrowRect.setX( buttonRect.Left() + (buttonRect.GetWidth() - arrowRect.GetWidth()) / 2 );
+ arrowRect.setY( buttonRect.Top() + (buttonRect.GetHeight() - arrowRect.GetHeight()) / 2 );
+
+ 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)
+ gtk_style_context_get_padding(mpListboxButtonStyle, gtk_style_context_get_state(mpListboxButtonStyle), &padding);
+ else
+ gtk_style_context_get_padding(mpButtonStyle, gtk_style_context_get_state(mpButtonStyle), &padding);
+
+ gint nArrowWidth = FALLBACK_ARROW_SIZE;
+ if (gtk_check_version(3, 20, 0) == nullptr)
+ {
+ gtk_style_context_get(mpComboboxButtonArrowStyle,
+ gtk_style_context_get_state(mpComboboxButtonArrowStyle),
+ "min-width", &nArrowWidth, nullptr);
+ }
+ else
+ {
+ nArrowWidth = nArrowWidth * gtk_style_context_get_scale (mpComboboxButtonArrowStyle);
+ }
+
+ 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 (gtk_check_version(3, 20, 0) == nullptr)
+ {
+ 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);
+ }
+ }
+ else
+ {
+ if (nType == ControlType::Combobox)
+ {
+ arrow_width = arrow_width * gtk_style_context_get_scale (mpComboboxButtonArrowStyle);
+ arrow_height = arrow_height * gtk_style_context_get_scale (mpComboboxButtonArrowStyle);
+ }
+ else if (nType == ControlType::Listbox)
+ {
+ arrow_width = arrow_width * gtk_style_context_get_scale (mpListboxButtonArrowStyle);
+ arrow_height = arrow_height * gtk_style_context_get_scale (mpListboxButtonArrowStyle);
+ }
+ }
+
+ 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(mpListboxBoxStyle, cr, aRect, flags);
+
+ render_common(mpListboxButtonStyle, 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_iter_set_object_nameFunc set_object_name)
+{
+ gtk_widget_path_append_type(pSiblingsPath, GTK_TYPE_ENTRY);
+ set_object_name(pSiblingsPath, -1, "entry");
+ gtk_widget_path_iter_add_class(pSiblingsPath, -1, "combo");
+}
+
+static void appendComboButton(GtkWidgetPath* pSiblingsPath, gtk_widget_path_iter_set_object_nameFunc set_object_name)
+{
+ gtk_widget_path_append_type(pSiblingsPath, GTK_TYPE_BUTTON);
+ set_object_name(pSiblingsPath, -1, "button");
+ gtk_widget_path_iter_add_class(pSiblingsPath, -1, "combo");
+}
+
+static GtkWidgetPath* buildLTRComboSiblingsPath(gtk_widget_path_iter_set_object_nameFunc set_object_name)
+{
+ GtkWidgetPath* pSiblingsPath = gtk_widget_path_new();
+
+ appendComboEntry(pSiblingsPath, set_object_name);
+ appendComboButton(pSiblingsPath, set_object_name);
+
+ return pSiblingsPath;
+}
+
+static GtkWidgetPath* buildRTLComboSiblingsPath(gtk_widget_path_iter_set_object_nameFunc set_object_name)
+{
+ GtkWidgetPath* pSiblingsPath = gtk_widget_path_new();
+
+ appendComboButton(pSiblingsPath, set_object_name);
+ appendComboEntry(pSiblingsPath, set_object_name);
+
+ 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 = gtk_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::createNewContext(GtkControlPart ePart, gtk_widget_path_iter_set_object_nameFunc set_object_name)
+{
+ switch (ePart)
+ {
+ case GtkControlPart::ToplevelWindow:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, G_TYPE_NONE);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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(set_object_name);
+ gtk_widget_path_append_with_siblings(path, pSiblingsPath, 1);
+ }
+ else
+ {
+ pSiblingsPath = buildLTRComboSiblingsPath(set_object_name);
+ 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(set_object_name);
+ gtk_widget_path_append_with_siblings(path, pSiblingsPath, 0);
+ }
+ else
+ {
+ pSiblingsPath = buildLTRComboSiblingsPath(set_object_name);
+ 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);
+ 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);
+ 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);
+ 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);
+ set_object_name(path, -1, "spinbutton");
+ gtk_widget_path_iter_add_class(path, -1, GTK_STYLE_CLASS_HORIZONTAL);
+ return makeContext(path, mpWindowStyle);
+ }
+ case GtkControlPart::SpinButtonEntry:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpSpinStyle));
+ gtk_widget_path_append_type(path, G_TYPE_NONE);
+ set_object_name(path, -1, "entry");
+ return makeContext(path, mpSpinStyle);
+ }
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ set_object_name(path, -1, "separator");
+ return makeContext(path, mpSeparatorMenuItemStyle);
+ }
+ }
+
+ return nullptr;
+}
+
+#ifndef GTK_STYLE_CLASS_POPUP
+#define GTK_STYLE_CLASS_POPUP "popup"
+#endif
+#ifndef GTK_STYLE_CLASS_LABEL
+#define GTK_STYLE_CLASS_LABEL "label"
+#endif
+
+GtkStyleContext* GtkSalGraphics::createOldContext(GtkControlPart ePart)
+{
+ switch (ePart)
+ {
+ case GtkControlPart::ToplevelWindow:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, GTK_TYPE_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_add_class(path, -1, GTK_STYLE_CLASS_BUTTON);
+ gtk_widget_path_iter_add_class(path, -1, "button");
+ gtk_widget_path_iter_add_class(path, -1, "text-button");
+ return makeContext(path, nullptr);
+ }
+ case GtkControlPart::LinkButton:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, GTK_TYPE_LINK_BUTTON);
+ gtk_widget_path_iter_add_class(path, -1, "text-button");
+ 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_add_class(path, -1, GTK_STYLE_CLASS_CHECK);
+ gtk_widget_path_iter_add_class(path, -1, "text-button");
+ return makeContext(path, nullptr);
+ }
+ case GtkControlPart::CheckButtonCheck:
+ return mpCheckButtonStyle;
+ case GtkControlPart::RadioButton:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, GTK_TYPE_RADIO_BUTTON);
+ gtk_widget_path_iter_add_class(path, -1, GTK_STYLE_CLASS_RADIO);
+ gtk_widget_path_iter_add_class(path, -1, "text-button");
+ return makeContext(path, nullptr);
+ }
+ case GtkControlPart::RadioButtonRadio:
+ return mpRadioButtonStyle;
+ case GtkControlPart::ComboboxBoxButtonBoxArrow:
+ case GtkControlPart::ListboxBoxButtonBoxArrow:
+ {
+ return (ePart == GtkControlPart::ComboboxBoxButtonBoxArrow)
+ ? mpComboboxButtonStyle : mpListboxButtonStyle;
+ }
+ case GtkControlPart::Entry:
+ case GtkControlPart::ComboboxBoxEntry:
+ case GtkControlPart::SpinButtonEntry:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, GTK_TYPE_ENTRY);
+ gtk_widget_path_iter_add_class(path, -1, GTK_STYLE_CLASS_ENTRY);
+ return makeContext(path, nullptr);
+ }
+ case GtkControlPart::Combobox:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, GTK_TYPE_COMBO_BOX_TEXT);
+ return makeContext(path, nullptr);
+ }
+ case GtkControlPart::Listbox:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, GTK_TYPE_COMBO_BOX);
+ return makeContext(path, nullptr);
+ }
+ case GtkControlPart::ComboboxBoxButton:
+ case GtkControlPart::ListboxBoxButton:
+ {
+ GtkStyleContext *pParent =
+ (ePart == GtkControlPart::ComboboxBoxButton ) ? mpComboboxStyle : mpListboxStyle;
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(pParent));
+ gtk_widget_path_append_type(path, GTK_TYPE_TOGGLE_BUTTON);
+ gtk_widget_path_iter_add_class(path, -1, GTK_STYLE_CLASS_BUTTON);
+ gtk_widget_path_iter_add_class(path, -1, "the-button-in-the-combobox");
+ return makeContext(path, pParent);
+ }
+ case GtkControlPart::SpinButton:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, GTK_TYPE_SPIN_BUTTON);
+ gtk_widget_path_iter_add_class(path, -1, GTK_STYLE_CLASS_ENTRY);
+ gtk_widget_path_iter_add_class(path, -1, GTK_STYLE_CLASS_HORIZONTAL);
+ gtk_widget_path_iter_add_class(path, -1, GTK_STYLE_CLASS_SPINBUTTON);
+ return makeContext(path, nullptr);
+ }
+ 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_add_class(path, -1, GTK_STYLE_CLASS_SPINBUTTON);
+ gtk_widget_path_iter_add_class(path, -1, GTK_STYLE_CLASS_BUTTON);
+ 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_add_class(path, -1, GTK_STYLE_CLASS_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_add_class(path, -1, GTK_STYLE_CLASS_SCROLLBAR);
+ gtk_widget_path_iter_add_class(path, -1, "contents");
+ return makeContext(path, pParent);
+ }
+ case GtkControlPart::ScrollbarHorizontalTrough:
+ case GtkControlPart::ScrollbarVerticalTrough:
+ {
+ 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_add_class(path, -1, GTK_STYLE_CLASS_SCROLLBAR);
+ gtk_widget_path_iter_add_class(path, -1, GTK_STYLE_CLASS_TROUGH);
+ return makeContext(path, pParent);
+ }
+ case GtkControlPart::ScrollbarHorizontalSlider:
+ case GtkControlPart::ScrollbarVerticalSlider:
+ {
+ GtkStyleContext *pParent =
+ (ePart == GtkControlPart::ScrollbarVerticalSlider) ? 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_add_class(path, -1, GTK_STYLE_CLASS_SCROLLBAR);
+ gtk_widget_path_iter_add_class(path, -1, GTK_STYLE_CLASS_SLIDER);
+ return makeContext(path, pParent);
+ }
+ case GtkControlPart::ScrollbarHorizontalButton:
+ case GtkControlPart::ScrollbarVerticalButton:
+ {
+ 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_add_class(path, -1, GTK_STYLE_CLASS_SCROLLBAR);
+ gtk_widget_path_iter_add_class(path, -1, GTK_STYLE_CLASS_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_add_class(path, -1, GTK_STYLE_CLASS_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_add_class(path, -1, GTK_STYLE_CLASS_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_add_class(path, -1, GTK_STYLE_CLASS_PROGRESSBAR);
+ 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_add_class(path, -1, GTK_STYLE_CLASS_NOTEBOOK);
+ gtk_widget_path_iter_add_class(path, -1, "frame");
+ return makeContext(path, mpWindowStyle);
+ }
+ case GtkControlPart::NotebookStack:
+ return mpNotebookStyle;
+ case GtkControlPart::NotebookHeader:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpNotebookStyle));
+ gtk_widget_path_iter_add_class(path, -1, GTK_STYLE_CLASS_HEADER);
+ gtk_widget_path_iter_add_class(path, -1, "top");
+ return makeContext(path, gtk_style_context_get_parent(mpNotebookStyle));
+ }
+ case GtkControlPart::NotebookHeaderTabs:
+ return 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_add_class(path, -1, GTK_STYLE_CLASS_HEADER);
+ gtk_widget_path_iter_add_class(path, -1, "top");
+ gtk_widget_path_iter_add_region(path, -1, GTK_STYLE_REGION_TAB, static_cast<GtkRegionFlags>(GTK_REGION_EVEN | GTK_REGION_FIRST));
+ 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, GTK_TYPE_LABEL);
+ gtk_widget_path_iter_add_class(path, -1, GTK_STYLE_CLASS_LABEL);
+ return makeContext(path, mpNotebookHeaderTabsTabStyle);
+ }
+ case GtkControlPart::NotebookHeaderTabsTabActiveLabel:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpNotebookHeaderTabsTabStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_LABEL);
+ gtk_widget_path_iter_add_class(path, -1, GTK_STYLE_CLASS_LABEL);
+ gtk_widget_path_iter_add_class(path, -1, "active-page");
+ return makeContext(path, mpNotebookHeaderTabsTabStyle);
+ }
+ case GtkControlPart::NotebookHeaderTabsTabHoverLabel:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpNotebookHeaderTabsTabStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_LABEL);
+ gtk_widget_path_iter_add_class(path, -1, GTK_STYLE_CLASS_LABEL);
+ gtk_widget_path_iter_add_class(path, -1, "prelight-page");
+ return makeContext(path, mpNotebookHeaderTabsTabStyle);
+ }
+ case GtkControlPart::FrameBorder:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, GTK_TYPE_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_add_class(path, -1, GTK_STYLE_CLASS_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_add_class(path, -1, GTK_STYLE_CLASS_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, GTK_TYPE_WINDOW);
+ gtk_widget_path_iter_add_class(path, -1, GTK_STYLE_CLASS_POPUP);
+ gtk_widget_path_iter_add_class(path, -1, "background");
+ 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_add_class(path, -1, GTK_STYLE_CLASS_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_add_class(path, -1, GTK_STYLE_CLASS_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, GTK_TYPE_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_add_class(path, -1, GTK_STYLE_CLASS_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_add_class(path, -1, GTK_STYLE_CLASS_CHECK);
+ 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_add_class(path, -1, GTK_STYLE_CLASS_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_add_class(path, -1, GTK_STYLE_CLASS_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_add_class(path, -1, GTK_STYLE_CLASS_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_add_class(path, -1, GTK_STYLE_CLASS_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_add_class(path, -1, GTK_STYLE_CLASS_SEPARATOR);
+ return makeContext(path, mpSeparatorMenuItemStyle);
+ }
+ case GtkControlPart::ComboboxBox:
+ case GtkControlPart::ListboxBox:
+ case GtkControlPart::ComboboxBoxButtonBox:
+ case GtkControlPart::ListboxBoxButtonBox:
+ return nullptr;
+ default:
+ break;
+ }
+
+ return mpButtonStyle;
+}
+
+GtkStyleContext* GtkSalGraphics::createStyleContext(gtk_widget_path_iter_set_object_nameFunc set_object_name,
+ GtkControlPart ePart)
+{
+ if (set_object_name)
+ return createNewContext(ePart, set_object_name);
+ return createOldContext(ePart);
+}
+
+namespace
+{
+ GtkStateFlags ACTIVE_TAB()
+ {
+#if GTK_CHECK_VERSION(3,20,0)
+ if (gtk_check_version(3, 20, 0) == nullptr)
+ return GTK_STATE_FLAG_CHECKED;
+#endif
+ return GTK_STATE_FLAG_ACTIVE;
+ }
+}
+
+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)
+{
+ if (gtk_check_version(3, 20, 0) == nullptr)
+ {
+ 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;
+ }
+ else
+ {
+ gfloat arrow_scaling = 1.0;
+ gtk_style_context_get_style(context, "arrow-scaling", &arrow_scaling, nullptr);
+ gfloat arrow_size = 11 * arrow_scaling;
+ return arrow_size;
+ }
+}
+
+namespace
+{
+ void draw_vertical_separator(GtkStyleContext *context, cairo_t *cr, const tools::Rectangle& rControlRegion)
+ {
+ long nX = 0;
+ long nY = 0;
+
+ const bool bNewStyle = gtk_check_version(3, 20, 0) == nullptr;
+
+ gint nSeparatorWidth = 1;
+
+ if (bNewStyle)
+ {
+ gtk_style_context_get(context,
+ gtk_style_context_get_state(context),
+ "min-width", &nSeparatorWidth, nullptr);
+ }
+
+ 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);
+
+ if (bNewStyle)
+ {
+ gtk_render_background(context, cr, nX, nY, nSeparatorWidth, nHeight);
+ gtk_render_frame(context, cr, nX, nY, nSeparatorWidth, nHeight);
+ }
+ else
+ {
+ gtk_render_line(context, cr, nX, nY, nX, nY + nHeight);
+ }
+ }
+
+ void draw_horizontal_separator(GtkStyleContext *context, cairo_t *cr, const tools::Rectangle& rControlRegion)
+ {
+ long nX = 0;
+ long nY = 0;
+
+ const bool bNewStyle = gtk_check_version(3, 20, 0) == nullptr;
+
+ gint nSeparatorHeight = 1;
+
+ if (bNewStyle)
+ {
+ 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);
+
+ if (bNewStyle)
+ {
+ gtk_render_background(context, cr, nX, nY, nWidth, nSeparatorHeight);
+ gtk_render_frame(context, cr, nX, nY, nWidth, nSeparatorHeight);
+ }
+ else
+ {
+ gtk_render_line(context, cr, nX, nY, nX + nWidth, nY);
+ }
+ }
+}
+
+void GtkSalGraphics::handleDamage(const tools::Rectangle& rDamagedRegion)
+{
+ assert(m_pWidgetDraw);
+ assert(!rDamagedRegion.IsEmpty());
+ mpFrame->damaged(rDamagedRegion.Left(), rDamagedRegion.Top(), rDamagedRegion.GetWidth(), rDamagedRegion.GetHeight());
+}
+
+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;
+ const gchar *styleClass = 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:
+ if (gtk_check_version(3, 20, 0) == nullptr)
+ context = mpCheckMenuItemCheckStyle;
+ else
+ {
+ context = gtk_widget_get_style_context(gCheckMenuItemWidget);
+ styleClass = GTK_STYLE_CLASS_CHECK;
+ }
+ renderType = RenderType::Check;
+ nType = ControlType::Checkbox;
+ if (nState & ControlState::PRESSED)
+ {
+ flags = static_cast<GtkStateFlags>(flags | GTK_STATE_FLAG_CHECKED);
+ }
+ break;
+ case ControlPart::MenuItemRadioMark:
+ if (gtk_check_version(3, 20, 0) == nullptr)
+ context = mpRadioMenuItemRadioStyle;
+ else
+ {
+ context = gtk_widget_get_style_context(gCheckMenuItemWidget);
+ styleClass = GTK_STYLE_CLASS_RADIO;
+ }
+ 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:
+ if (gtk_check_version(3, 20, 0) == nullptr)
+ context = mpMenuItemArrowStyle;
+ else
+ {
+ context = gtk_widget_get_style_context(gCheckMenuItemWidget);
+ styleClass = GTK_STYLE_CLASS_ARROW;
+ }
+ 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 = mpToolbarSeperatorStyle;
+ 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;
+ break;
+ 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 | ACTIVE_TAB());
+ renderType = RenderType::TabItem;
+ break;
+ case ControlType::WindowBackground:
+ context = gtk_widget_get_style_context(gtk_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(false);
+ clipRegion(cr);
+ cairo_translate(cr, rControlRegion.Left(), rControlRegion.Top());
+
+ long nX = 0;
+ long nY = 0;
+ long nWidth = rControlRegion.GetWidth();
+ long nHeight = rControlRegion.GetHeight();
+
+ StyleContextSave aContextState;
+ aContextState.save(context);
+ style_context_set_state(context, flags);
+
+ if (styleClass)
+ {
+ gtk_style_context_add_class(context, styleClass);
+ }
+
+ // 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();
+ gtk_css_provider_load_from_data(pBgCssProvider, aResult.getStr(), aResult.getLength(), nullptr);
+ 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);
+ break;
+ }
+ case RenderType::Separator:
+ if (nPart == ControlPart::SeparatorHorz)
+ draw_horizontal_separator(context, cr, rControlRegion);
+ else
+ draw_vertical_separator(context, cr, rControlRegion);
+ 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)
+ {
+ gtk_style_context_add_provider(mpComboboxEntryStyle, GTK_STYLE_PROVIDER(pBgCssProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+ PaintCombobox(flags, cr, rControlRegion, nType, nPart);
+ if (pBgCssProvider)
+ {
+ gtk_style_context_remove_provider(mpComboboxEntryStyle, 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;
+
+ gtk_style_context_get_border(context, flags, &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);
+
+ long nProgressWidth = rValue.getNumericVal();
+ if (nProgressWidth)
+ {
+ GtkBorder padding;
+ gtk_style_context_get_padding(context, gtk_style_context_get_state(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:
+ {
+ if (gtk_check_version(3, 20, 0) != nullptr)
+ {
+ 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 (styleClass)
+ {
+ gtk_style_context_remove_class(context, styleClass);
+ }
+ 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);
+ long nHeight = std::max<long>(rControlRegion.GetHeight(), aReq.height);
+ return tools::Rectangle(rControlRegion.TopLeft(), Size(rControlRegion.GetWidth(), nHeight));
+}
+
+static tools::Rectangle AdjustRectForTextBordersPadding(GtkStyleContext* pStyle, long nContentWidth, long nContentHeight, const tools::Rectangle& rControlRegion)
+{
+ GtkBorder border;
+ gtk_style_context_get_border(pStyle, gtk_style_context_get_state(pStyle), &border);
+
+ GtkBorder padding;
+ gtk_style_context_get_padding(pStyle, gtk_style_context_get_state(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, point;
+
+ 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;
+ gtk_style_context_get_border(pButtonStyle, gtk_style_context_get_state(pButtonStyle), &border);
+
+ GtkBorder padding;
+ gtk_style_context_get_padding(pButtonStyle, gtk_style_context_get_state(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 );
+
+ 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;
+ DrawFrameFlags nStyle = static_cast<DrawFrameFlags>(rValue.getNumericVal() & 0xfff0);
+ if (nStyle & DrawFrameFlags::NoDraw)
+ {
+ GtkBorder padding;
+ gtk_style_context_get_padding(mpFrameInStyle, gtk_style_context_get_state(mpFrameInStyle), &padding);
+
+ GtkBorder border;
+ gtk_style_context_get_border(mpFrameInStyle, gtk_style_context_get_state(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
+ rNativeContentRegion = rControlRegion;
+ }
+ else
+ {
+ return false;
+ }
+
+ rNativeBoundingRegion = aEditRect;
+ rNativeContentRegion = rNativeBoundingRegion;
+
+ return true;
+}
+/************************************************************************
+ * 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 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);
+}
+
+vcl::Font pango_to_vcl(const PangoFontDescription* font, const css::lang::Locale& rLocale)
+{
+ OString aFamily = pango_font_description_get_family( font );
+ int nPangoHeight = pango_font_description_get_size( 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 );
+
+ psp::FastPrintFontInfo aInfo;
+ // set family name
+ aInfo.m_aFamilyName = OStringToOUString( aFamily, RTL_TEXTENCODING_UTF8 );
+ // set italic
+ switch( eStyle )
+ {
+ case PANGO_STYLE_NORMAL: aInfo.m_eItalic = ITALIC_NONE;break;
+ case PANGO_STYLE_ITALIC: aInfo.m_eItalic = ITALIC_NORMAL;break;
+ case PANGO_STYLE_OBLIQUE: aInfo.m_eItalic = ITALIC_OBLIQUE;break;
+ }
+ // set weight
+ if( eWeight <= PANGO_WEIGHT_ULTRALIGHT )
+ aInfo.m_eWeight = WEIGHT_ULTRALIGHT;
+ else if( eWeight <= PANGO_WEIGHT_LIGHT )
+ aInfo.m_eWeight = WEIGHT_LIGHT;
+ else if( eWeight <= PANGO_WEIGHT_NORMAL )
+ aInfo.m_eWeight = WEIGHT_NORMAL;
+ else if( eWeight <= PANGO_WEIGHT_BOLD )
+ aInfo.m_eWeight = WEIGHT_BOLD;
+ else
+ aInfo.m_eWeight = WEIGHT_ULTRABOLD;
+ // set width
+ switch( eStretch )
+ {
+ case PANGO_STRETCH_ULTRA_CONDENSED: aInfo.m_eWidth = WIDTH_ULTRA_CONDENSED;break;
+ case PANGO_STRETCH_EXTRA_CONDENSED: aInfo.m_eWidth = WIDTH_EXTRA_CONDENSED;break;
+ case PANGO_STRETCH_CONDENSED: aInfo.m_eWidth = WIDTH_CONDENSED;break;
+ case PANGO_STRETCH_SEMI_CONDENSED: aInfo.m_eWidth = WIDTH_SEMI_CONDENSED;break;
+ case PANGO_STRETCH_NORMAL: aInfo.m_eWidth = WIDTH_NORMAL;break;
+ case PANGO_STRETCH_SEMI_EXPANDED: aInfo.m_eWidth = WIDTH_SEMI_EXPANDED;break;
+ case PANGO_STRETCH_EXPANDED: aInfo.m_eWidth = WIDTH_EXPANDED;break;
+ case PANGO_STRETCH_EXTRA_EXPANDED: aInfo.m_eWidth = WIDTH_EXTRA_EXPANDED;break;
+ case PANGO_STRETCH_ULTRA_EXPANDED: aInfo.m_eWidth = 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"
+ psp::PrintFontManager::get().matchFont(aInfo, rLocale);
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.gtk3", "font match "
+ << (aInfo.m_nID != 0 ? "succeeded" : "failed")
+ << ", name AFTER: \""
+ << aInfo.m_aFamilyName
+ << "\".");
+#endif
+
+ int nPointHeight = nPangoHeight/PANGO_SCALE;
+
+ vcl::Font aFont( aInfo.m_aFamilyName, Size( 0, nPointHeight ) );
+ if( aInfo.m_eWeight != WEIGHT_DONTKNOW )
+ aFont.SetWeight( aInfo.m_eWeight );
+ if( aInfo.m_eWidth != WIDTH_DONTKNOW )
+ aFont.SetWidthType( aInfo.m_eWidth );
+ if( aInfo.m_eItalic != ITALIC_DONTKNOW )
+ aFont.SetItalic( aInfo.m_eItalic );
+ if( aInfo.m_ePitch != PITCH_DONTKNOW )
+ aFont.SetPitch( aInfo.m_ePitch );
+ return aFont;
+}
+
+bool GtkSalGraphics::updateSettings(AllSettings& rSettings)
+{
+ GtkWidget* pTopLevel = gtk_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();
+ GdkRGBA color;
+
+ // text colors
+ GdkRGBA text_color;
+ style_context_set_state(pStyle, GTK_STATE_FLAG_NORMAL);
+ gtk_style_context_get_color(pStyle, gtk_style_context_get_state(pStyle), &text_color);
+ ::Color aTextColor = getColor( text_color );
+ aStyleSet.SetDialogTextColor( aTextColor );
+ aStyleSet.SetButtonTextColor( aTextColor );
+ aStyleSet.SetDefaultActionButtonTextColor(aTextColor);
+ aStyleSet.SetActionButtonTextColor(aTextColor);
+ aStyleSet.SetRadioCheckTextColor( aTextColor );
+ aStyleSet.SetGroupTextColor( aTextColor );
+ aStyleSet.SetLabelTextColor( aTextColor );
+ aStyleSet.SetWindowTextColor( aTextColor );
+ aStyleSet.SetFieldTextColor( aTextColor );
+
+ // background colors
+ GdkRGBA background_color;
+ gtk_style_context_get_background_color(pStyle, gtk_style_context_get_state(pStyle), &background_color);
+
+ ::Color aBackColor = getColor( background_color );
+ aStyleSet.BatchSetBackgrounds( aBackColor );
+
+ // UI font
+ vcl::Font aFont(getFont(pStyle, rSettings.GetUILanguageTag().getLocale()));
+
+ 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);
+ gtk_style_context_get_color(pStyle, gtk_style_context_get_state(pStyle), &text_color);
+ aTextColor = getColor(text_color);
+ aStyleSet.SetDefaultButtonRolloverTextColor(aTextColor);
+ aStyleSet.SetButtonRolloverTextColor(aTextColor);
+ aStyleSet.SetDefaultActionButtonRolloverTextColor(aTextColor);
+ aStyleSet.SetActionButtonRolloverTextColor(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);
+ gtk_style_context_get_color(mpButtonStyle, gtk_style_context_get_state(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));
+ gtk_style_context_get_color(mpButtonStyle, gtk_style_context_get_state(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();
+ }
+
+ // 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_bg_color, tooltip_fg_color;
+ style_context_set_state(pCStyle, GTK_STATE_FLAG_NORMAL);
+ gtk_style_context_get_color(pCStyle, gtk_style_context_get_state(pCStyle), &tooltip_fg_color);
+ gtk_style_context_get_background_color(pCStyle, gtk_style_context_get_state(pCStyle), &tooltip_bg_color);
+
+ aContextState.restore();
+ g_object_unref( pCStyle );
+
+ aStyleSet.SetHelpColor( getColor( tooltip_bg_color ));
+ aStyleSet.SetHelpTextColor( getColor( tooltip_fg_color ));
+ }
+
+ {
+ // 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 );
+ aContextState.save(pCStyle);
+
+ // highlighting colors
+ style_context_set_state(pCStyle, GTK_STATE_FLAG_SELECTED);
+ gtk_style_context_get_background_color(pCStyle, gtk_style_context_get_state(pCStyle), &text_color);
+ ::Color aHighlightColor = getColor( text_color );
+ gtk_style_context_get_color(pCStyle, gtk_style_context_get_state(pCStyle), &text_color);
+ ::Color aHighlightTextColor = getColor( text_color );
+ aStyleSet.SetHighlightColor( aHighlightColor );
+ aStyleSet.SetHighlightTextColor( aHighlightTextColor );
+
+ // field background color
+ GdkRGBA field_background_color;
+ style_context_set_state(pCStyle, GTK_STATE_FLAG_NORMAL);
+ gtk_style_context_get_background_color(pCStyle, gtk_style_context_get_state(pCStyle), &field_background_color);
+
+ ::Color aBackFieldColor = getColor( field_background_color );
+ aStyleSet.SetFieldColor( aBackFieldColor );
+ // This baby is the default page/paper color
+ aStyleSet.SetWindowColor( aBackFieldColor );
+
+ // Cursor width
+ gfloat caretAspectRatio = 0.04f;
+ gtk_style_context_get_style( pCStyle, "cursor-aspect-ratio", &caretAspectRatio, nullptr );
+ // 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);
+ gtk_style_context_get_color(pCStyle, gtk_style_context_get_state(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();
+ g_object_unref( pCStyle );
+
+ // Tab colors
+ aStyleSet.SetActiveTabColor( aBackFieldColor ); // same as the window color.
+ aStyleSet.SetInactiveTabColor( aBackColor );
+ }
+
+ // menu disabled entries handling
+ aStyleSet.SetSkipDisabledInMenus( true );
+ aStyleSet.SetPreferredContextMenuShortcuts( false );
+
+ aContextState.save(mpMenuItemLabelStyle);
+
+ // menu colors
+ style_context_set_state(mpMenuStyle, GTK_STATE_FLAG_NORMAL);
+ gtk_style_context_get_background_color( mpMenuStyle, gtk_style_context_get_state(mpMenuStyle), &background_color );
+ aBackColor = getColor( background_color );
+ aStyleSet.SetMenuColor( aBackColor );
+
+ // menu bar
+ style_context_set_state(mpMenuBarStyle, GTK_STATE_FLAG_NORMAL);
+ gtk_style_context_get_background_color( mpMenuBarStyle, gtk_style_context_get_state(mpMenuBarStyle), &background_color );
+ aBackColor = getColor( background_color );
+ aStyleSet.SetMenuBarColor( aBackColor );
+ aStyleSet.SetMenuBarRolloverColor( aBackColor );
+
+ style_context_set_state(mpMenuBarItemStyle, GTK_STATE_FLAG_NORMAL);
+ gtk_style_context_get_color( mpMenuBarItemStyle, gtk_style_context_get_state(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);
+ gtk_style_context_get_color( mpMenuBarItemStyle, gtk_style_context_get_state(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);
+ gtk_style_context_get_color(mpMenuItemLabelStyle, gtk_style_context_get_state(mpMenuItemLabelStyle), &color);
+ aTextColor = getColor(color);
+ aStyleSet.SetMenuTextColor(aTextColor);
+
+ style_context_set_state(mpMenuItemLabelStyle, GTK_STATE_FLAG_PRELIGHT);
+ gtk_style_context_get_background_color( mpMenuItemLabelStyle, gtk_style_context_get_state(mpMenuItemLabelStyle), &background_color );
+ ::Color aHighlightColor = getColor( background_color );
+ aStyleSet.SetMenuHighlightColor( aHighlightColor );
+
+ gtk_style_context_get_color( mpMenuItemLabelStyle, gtk_style_context_get_state(mpMenuItemLabelStyle), &color );
+ ::Color aHighlightTextColor = getColor( color );
+ aStyleSet.SetMenuHighlightTextColor( aHighlightTextColor );
+
+ aContextState.restore();
+
+ // hyperlink colors
+ aContextState.save(mpLinkButtonStyle);
+ style_context_set_state(mpLinkButtonStyle, GTK_STATE_FLAG_LINK);
+ gtk_style_context_get_color(mpLinkButtonStyle, gtk_style_context_get_state(mpLinkButtonStyle), &text_color);
+ aStyleSet.SetLinkColor(getColor(text_color));
+ style_context_set_state(mpLinkButtonStyle, GTK_STATE_FLAG_VISITED);
+ gtk_style_context_get_color(mpLinkButtonStyle, gtk_style_context_get_state(mpLinkButtonStyle), &text_color);
+ aStyleSet.SetVisitedLinkColor(getColor(text_color));
+ aContextState.restore();
+
+ {
+ GtkStyleContext *pCStyle = mpNotebookHeaderTabsTabLabelStyle;
+ aContextState.save(pCStyle);
+ style_context_set_state(pCStyle, GTK_STATE_FLAG_NORMAL);
+ gtk_style_context_get_color(pCStyle, gtk_style_context_get_state(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);
+ gtk_style_context_get_color(pCStyle, gtk_style_context_get_state(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);
+ gtk_style_context_get_color(pCStyle, gtk_style_context_get_state(pCStyle), &text_color);
+ aTextColor = getColor( text_color );
+ aStyleSet.SetTabRolloverTextColor(aTextColor);
+ aContextState.restore();
+ }
+
+ {
+ GtkStyleContext *pCStyle = mpNotebookHeaderTabsTabActiveLabelStyle;
+ aContextState.save(pCStyle);
+ style_context_set_state(pCStyle, ACTIVE_TAB());
+ gtk_style_context_get_color(pCStyle, gtk_style_context_get_state(pCStyle), &text_color);
+ aTextColor = getColor( text_color );
+ aStyleSet.SetTabHighlightTextColor(aTextColor);
+ aContextState.restore();
+ }
+
+ // 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;
+
+ // Grab some button style attributes
+ if (gtk_check_version(3, 20, 0) == nullptr)
+ {
+ 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);
+ aStyleSet.SetMinThumbSize(min_slider_length);
+ }
+ else
+ {
+ gint slider_width = 14;
+ gint trough_border = 1;
+
+ gtk_style_context_get_style(mpVScrollbarStyle,
+ "slider-width", &slider_width,
+ "trough-border", &trough_border,
+ "min-slider-length", &min_slider_length,
+ nullptr);
+ aStyleSet.SetScrollBarSize(slider_width + 2*trough_border);
+ gint magic = trough_border ? 1 : 0;
+ aStyleSet.SetMinThumbSize(min_slider_length - magic);
+ }
+
+ // 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 );
+
+ // finally update the collected settings
+ rSettings.SetStyleSettings( aStyleSet );
+#if OSL_DEBUG_LEVEL > 1
+ gchar* pThemeName = NULL;
+ g_object_get( pSettings, "gtk-theme-name", &pThemeName, nullptr );
+ SAL_INFO("vcl.gtk3", "Theme name is \""
+ << pThemeName
+ << "\".");
+ g_free(pThemeName);
+#endif
+
+ return true;
+}
+
+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;
+}
+
+#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
+ GtkWidget *pWidget = GTK_WIDGET(mpFrame->getFixedContainer());
+ gtk_widget_queue_draw(pWidget);
+}
+
+namespace {
+
+void getStyleContext(GtkStyleContext** style, GtkWidget* widget)
+{
+ gtk_container_add(GTK_CONTAINER(gDumbContainer), widget);
+ *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 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 (gCacheWindow)
+ gtk_widget_destroy(gCacheWindow);
+}
+
+GtkSalGraphics::GtkSalGraphics( GtkSalFrame *pFrame, GtkWidget *pWindow )
+ : SvpSalGraphics(),
+ 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 */
+ gtk_widget_path_iter_set_object_nameFunc set_object_name =
+ reinterpret_cast<gtk_widget_path_iter_set_object_nameFunc>(osl_getAsciiFunctionSymbol(nullptr,
+ "gtk_widget_path_iter_set_object_name"));
+
+ gCacheWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ gDumbContainer = gtk_fixed_new();
+ gtk_container_add(GTK_CONTAINER(gCacheWindow), gDumbContainer);
+ gtk_widget_realize(gDumbContainer);
+ gtk_widget_realize(gCacheWindow);
+
+ gEntryBox = gtk_entry_new();
+ gtk_container_add(GTK_CONTAINER(gDumbContainer), gEntryBox);
+
+ mpWindowStyle = createStyleContext(set_object_name, GtkControlPart::ToplevelWindow);
+ mpEntryStyle = createStyleContext(set_object_name, GtkControlPart::Entry);
+
+ getStyleContext(&mpTextViewStyle, gtk_text_view_new());
+
+ mpButtonStyle = createStyleContext(set_object_name, GtkControlPart::Button);
+ mpLinkButtonStyle = createStyleContext(set_object_name, GtkControlPart::LinkButton);
+
+ 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);
+ mpToolbarSeperatorStyle = gtk_widget_get_style_context(GTK_WIDGET(item));
+
+ 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));
+
+ mpVScrollbarStyle = createStyleContext(set_object_name, GtkControlPart::ScrollbarVertical);
+ mpVScrollbarContentsStyle = createStyleContext(set_object_name, GtkControlPart::ScrollbarVerticalContents);
+ mpVScrollbarTroughStyle = createStyleContext(set_object_name, GtkControlPart::ScrollbarVerticalTrough);
+ mpVScrollbarSliderStyle = createStyleContext(set_object_name, GtkControlPart::ScrollbarVerticalSlider);
+ mpVScrollbarButtonStyle = createStyleContext(set_object_name, GtkControlPart::ScrollbarVerticalButton);
+ mpHScrollbarStyle = createStyleContext(set_object_name, GtkControlPart::ScrollbarHorizontal);
+ mpHScrollbarContentsStyle = createStyleContext(set_object_name, GtkControlPart::ScrollbarHorizontalContents);
+ mpHScrollbarTroughStyle = createStyleContext(set_object_name, GtkControlPart::ScrollbarHorizontalTrough);
+ mpHScrollbarSliderStyle = createStyleContext(set_object_name, GtkControlPart::ScrollbarHorizontalSlider);
+ mpHScrollbarButtonStyle = createStyleContext(set_object_name, GtkControlPart::ScrollbarHorizontalButton);
+
+ mpCheckButtonStyle = createStyleContext(set_object_name, GtkControlPart::CheckButton);
+ mpCheckButtonCheckStyle = createStyleContext(set_object_name, GtkControlPart::CheckButtonCheck);
+
+ mpRadioButtonStyle = createStyleContext(set_object_name, GtkControlPart::RadioButton);
+ mpRadioButtonRadioStyle = createStyleContext(set_object_name, GtkControlPart::RadioButtonRadio);
+
+ /* Spinbutton */
+ gSpinBox = gtk_spin_button_new(nullptr, 0, 0);
+ gtk_container_add(GTK_CONTAINER(gDumbContainer), gSpinBox);
+ mpSpinStyle = createStyleContext(set_object_name, GtkControlPart::SpinButton);
+ mpSpinEntryStyle = createStyleContext(set_object_name, GtkControlPart::SpinButtonEntry);
+ mpSpinUpStyle = createStyleContext(set_object_name, GtkControlPart::SpinButtonUpButton);
+ mpSpinDownStyle = createStyleContext(set_object_name, GtkControlPart::SpinButtonDownButton);
+
+ /* NoteBook */
+ mpNotebookStyle = createStyleContext(set_object_name, GtkControlPart::Notebook);
+ mpNotebookStackStyle = createStyleContext(set_object_name, GtkControlPart::NotebookStack);
+ mpNotebookHeaderStyle = createStyleContext(set_object_name, GtkControlPart::NotebookHeader);
+ mpNotebookHeaderTabsStyle = createStyleContext(set_object_name, GtkControlPart::NotebookHeaderTabs);
+ mpNotebookHeaderTabsTabStyle = createStyleContext(set_object_name, GtkControlPart::NotebookHeaderTabsTab);
+ mpNotebookHeaderTabsTabLabelStyle = createStyleContext(set_object_name, GtkControlPart::NotebookHeaderTabsTabLabel);
+ mpNotebookHeaderTabsTabActiveLabelStyle = createStyleContext(set_object_name, GtkControlPart::NotebookHeaderTabsTabActiveLabel);
+ mpNotebookHeaderTabsTabHoverLabelStyle = createStyleContext(set_object_name, GtkControlPart::NotebookHeaderTabsTabHoverLabel);
+
+ /* Combobox */
+ gComboBox = gtk_combo_box_text_new_with_entry();
+ gtk_container_add(GTK_CONTAINER(gDumbContainer), gComboBox);
+ mpComboboxStyle = createStyleContext(set_object_name, GtkControlPart::Combobox);
+ mpComboboxBoxStyle = createStyleContext(set_object_name, GtkControlPart::ComboboxBox);
+ mpComboboxEntryStyle = createStyleContext(set_object_name, GtkControlPart::ComboboxBoxEntry);
+ mpComboboxButtonStyle = createStyleContext(set_object_name, GtkControlPart::ComboboxBoxButton);
+ mpComboboxButtonBoxStyle = createStyleContext(set_object_name, GtkControlPart::ComboboxBoxButtonBox);
+ mpComboboxButtonArrowStyle = createStyleContext(set_object_name, 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(set_object_name, GtkControlPart::Listbox);
+ mpListboxBoxStyle = createStyleContext(set_object_name, GtkControlPart::ListboxBox);
+ mpListboxButtonStyle = createStyleContext(set_object_name, GtkControlPart::ListboxBoxButton);
+ mpListboxButtonBoxStyle = createStyleContext(set_object_name, GtkControlPart::ListboxBoxButtonBox);
+ mpListboxButtonArrowStyle = createStyleContext(set_object_name, GtkControlPart::ListboxBoxButtonBoxArrow);
+
+ /* Menu bar */
+ gMenuBarWidget = gtk_menu_bar_new();
+ gMenuItemMenuBarWidget = gtk_menu_item_new_with_label( "b" );
+ gtk_menu_shell_append(GTK_MENU_SHELL(gMenuBarWidget), gMenuItemMenuBarWidget);
+ gtk_container_add(GTK_CONTAINER(gDumbContainer), gMenuBarWidget);
+
+ mpMenuBarStyle = createStyleContext(set_object_name, GtkControlPart::MenuBar);
+ mpMenuBarItemStyle = createStyleContext(set_object_name, GtkControlPart::MenuBarItem);
+
+ /* Menu */
+ mpMenuWindowStyle = createStyleContext(set_object_name, GtkControlPart::MenuWindow);
+ mpMenuStyle = createStyleContext(set_object_name, GtkControlPart::Menu);
+ GtkWidget *menu = gtk_menu_new();
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(gMenuItemMenuBarWidget), menu);
+
+ /* Menu Items */
+ gCheckMenuItemWidget = gtk_check_menu_item_new_with_label("M");
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), gCheckMenuItemWidget);
+
+ mpMenuItemStyle = createStyleContext(set_object_name, GtkControlPart::MenuItem);
+ mpMenuItemLabelStyle = createStyleContext(set_object_name, GtkControlPart::MenuItemLabel);
+ mpMenuItemArrowStyle = createStyleContext(set_object_name, GtkControlPart::MenuItemArrow);
+ mpCheckMenuItemStyle = createStyleContext(set_object_name, GtkControlPart::CheckMenuItem);
+ mpCheckMenuItemCheckStyle = createStyleContext(set_object_name, GtkControlPart::CheckMenuItemCheck);
+ mpRadioMenuItemStyle = createStyleContext(set_object_name, GtkControlPart::RadioMenuItem);
+ mpRadioMenuItemRadioStyle = createStyleContext(set_object_name, GtkControlPart::RadioMenuItemRadio);
+ mpSeparatorMenuItemStyle = createStyleContext(set_object_name, GtkControlPart::SeparatorMenuItem);
+ mpSeparatorMenuItemSeparatorStyle = createStyleContext(set_object_name, GtkControlPart::SeparatorMenuItemSeparator);
+
+ /* Frames */
+ mpFrameOutStyle = mpFrameInStyle = createStyleContext(set_object_name, 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(set_object_name, GtkControlPart::ProgressBar);
+ mpProgressBarTroughStyle = createStyleContext(set_object_name, GtkControlPart::ProgressBarTrough);
+ mpProgressBarProgressStyle = createStyleContext(set_object_name, GtkControlPart::ProgressBarProgress);
+
+ gtk_widget_show_all(gDumbContainer);
+}
+
+void GtkSalGraphics::GetResolution(sal_Int32& rDPIX, sal_Int32& rDPIY)
+{
+ char* pForceDpi;
+ if ((pForceDpi = getenv("SAL_FORCEDPI")))
+ {
+ OString sForceDPI(pForceDpi);
+ rDPIX = rDPIY = sForceDPI.toInt32();
+ return;
+ }
+
+ 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;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtk3salprn-gtk.cxx b/vcl/unx/gtk3/gtk3salprn-gtk.cxx
new file mode 100644
index 000000000..62e88c919
--- /dev/null
+++ b/vcl/unx/gtk3/gtk3salprn-gtk.cxx
@@ -0,0 +1,954 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/gtkprintwrapper.hxx>
+
+#include <unx/gtk/gtkdata.hxx>
+#include <unx/gtk/gtkframe.hxx>
+#include <unx/gtk/gtkinst.hxx>
+#include <unx/gtk/gtkprn.hxx>
+
+#include <configsettings.hxx>
+#include <vcl/help.hxx>
+#include <vcl/print.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/window.hxx>
+
+#include <gtk/gtk.h>
+
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/view/PrintableState.hpp>
+
+#include <officecfg/Office/Common.hxx>
+
+#include <rtl/ustrbuf.hxx>
+#include <rtl/ustring.hxx>
+#include <sal/log.hxx>
+
+#include <cstring>
+#include <map>
+
+namespace beans = com::sun::star::beans;
+namespace uno = com::sun::star::uno;
+namespace view = com::sun::star::view;
+
+using vcl::unx::GtkPrintWrapper;
+
+namespace {
+
+class GtkPrintDialog
+{
+public:
+ explicit GtkPrintDialog(vcl::PrinterController& io_rController);
+ bool run();
+ GtkPrinter* getPrinter() const
+ {
+ return m_xWrapper->print_unix_dialog_get_selected_printer(GTK_PRINT_UNIX_DIALOG(m_pDialog));
+ }
+ GtkPrintSettings* getSettings() const
+ {
+ return m_xWrapper->print_unix_dialog_get_settings(GTK_PRINT_UNIX_DIALOG(m_pDialog));
+ }
+ void updateControllerPrintRange();
+
+ ~GtkPrintDialog();
+
+ static void UIOption_CheckHdl(GtkWidget* i_pWidget, GtkPrintDialog* io_pThis)
+ {
+ io_pThis->impl_UIOption_CheckHdl(i_pWidget);
+ }
+ static void UIOption_RadioHdl(GtkWidget* i_pWidget, GtkPrintDialog* io_pThis)
+ {
+ io_pThis->impl_UIOption_RadioHdl(i_pWidget);
+ }
+ static void UIOption_SelectHdl(GtkWidget* i_pWidget, GtkPrintDialog* io_pThis)
+ {
+ io_pThis->impl_UIOption_SelectHdl(i_pWidget);
+ }
+
+private:
+ beans::PropertyValue* impl_queryPropertyValue(GtkWidget* i_pWidget) const;
+ void impl_checkOptionalControlDependencies();
+
+ void impl_UIOption_CheckHdl(GtkWidget* i_pWidget);
+ void impl_UIOption_RadioHdl(GtkWidget* i_pWidget);
+ void impl_UIOption_SelectHdl(GtkWidget* i_pWidget);
+
+ void impl_initDialog();
+ void impl_initCustomTab();
+ void impl_initPrintContent(uno::Sequence<sal_Bool> const& i_rDisabled);
+
+ void impl_readFromSettings();
+ void impl_storeToSettings() const;
+
+private:
+ GtkWidget* m_pDialog;
+ vcl::PrinterController& m_rController;
+ std::map<GtkWidget*, OUString> m_aControlToPropertyMap;
+ std::map<GtkWidget*, sal_Int32> m_aControlToNumValMap;
+ std::shared_ptr<GtkPrintWrapper> m_xWrapper;
+};
+
+}
+
+struct GtkSalPrinter_Impl
+{
+ OString m_sSpoolFile;
+ OUString m_sJobName;
+ GtkPrinter* m_pPrinter;
+ GtkPrintSettings* m_pSettings;
+
+ GtkSalPrinter_Impl();
+ ~GtkSalPrinter_Impl();
+};
+
+GtkSalPrinter_Impl::GtkSalPrinter_Impl()
+ : m_pPrinter(nullptr)
+ , m_pSettings(nullptr)
+{
+}
+
+GtkSalPrinter_Impl::~GtkSalPrinter_Impl()
+{
+ if (m_pPrinter)
+ {
+ g_object_unref(G_OBJECT(m_pPrinter));
+ m_pPrinter = nullptr;
+ }
+ if (m_pSettings)
+ {
+ g_object_unref(G_OBJECT(m_pSettings));
+ m_pSettings = nullptr;
+ }
+}
+
+namespace
+{
+
+GtkInstance const&
+lcl_getGtkSalInstance()
+{
+ // we _know_ this is GtkInstance
+ return *static_cast<GtkInstance*>(GetGtkSalData()->m_pInstance);
+}
+
+bool
+lcl_useSystemPrintDialog()
+{
+ return officecfg::Office::Common::Misc::UseSystemPrintDialog::get()
+ && officecfg::Office::Common::Misc::ExperimentalMode::get()
+ && lcl_getGtkSalInstance().getPrintWrapper()->supportsPrinting();
+}
+
+}
+
+GtkSalPrinter::GtkSalPrinter(SalInfoPrinter* const i_pInfoPrinter)
+ : PspSalPrinter(i_pInfoPrinter)
+{
+}
+
+GtkSalPrinter::~GtkSalPrinter() = default;
+
+bool
+GtkSalPrinter::impl_doJob(
+ const OUString* const i_pFileName,
+ const OUString& i_rJobName,
+ const OUString& i_rAppName,
+ ImplJobSetup* const io_pSetupData,
+ const bool i_bCollate,
+ vcl::PrinterController& io_rController)
+{
+ io_rController.setJobState(view::PrintableState_JOB_STARTED);
+ io_rController.jobStarted();
+ const bool bJobStarted(
+ PspSalPrinter::StartJob(i_pFileName, i_rJobName, i_rAppName,
+ 1/*i_nCopies*/, i_bCollate, true, io_pSetupData))
+ ;
+
+ if (bJobStarted)
+ {
+ io_rController.createProgressDialog();
+ const int nPages(io_rController.getFilteredPageCount());
+ for (int nPage(0); nPage != nPages; ++nPage)
+ {
+ if (nPage == nPages - 1)
+ io_rController.setLastPage(true);
+ io_rController.printFilteredPage(nPage);
+ }
+ io_rController.setJobState(view::PrintableState_JOB_COMPLETED);
+ }
+
+ return bJobStarted;
+}
+
+bool
+GtkSalPrinter::StartJob(
+ const OUString* const i_pFileName,
+ const OUString& i_rJobName,
+ const OUString& i_rAppName,
+ ImplJobSetup* io_pSetupData,
+ vcl::PrinterController& io_rController)
+{
+ if (!lcl_useSystemPrintDialog())
+ return PspSalPrinter::StartJob(i_pFileName, i_rJobName, i_rAppName, io_pSetupData, io_rController);
+
+ assert(!m_xImpl);
+
+ m_xImpl.reset(new GtkSalPrinter_Impl());
+ m_xImpl->m_sJobName = i_rJobName;
+
+ OString sFileName;
+ if (i_pFileName)
+ sFileName = OUStringToOString(*i_pFileName, osl_getThreadTextEncoding());
+
+ GtkPrintDialog aDialog(io_rController);
+ if (!aDialog.run())
+ {
+ io_rController.abortJob();
+ return false;
+ }
+ aDialog.updateControllerPrintRange();
+ m_xImpl->m_pPrinter = aDialog.getPrinter();
+ m_xImpl->m_pSettings = aDialog.getSettings();
+
+ //To-Do proper name, watch for encodings
+ sFileName = OString("/tmp/hacking.ps");
+ m_xImpl->m_sSpoolFile = sFileName;
+
+ OUString aFileName = OStringToOUString(sFileName, osl_getThreadTextEncoding());
+
+ //To-Do, swap ps/pdf for gtk_printer_accepts_ps()/gtk_printer_accepts_pdf() ?
+
+ return impl_doJob(&aFileName, i_rJobName, i_rAppName, io_pSetupData, /*bCollate*/false, io_rController);
+}
+
+bool
+GtkSalPrinter::EndJob()
+{
+ bool bRet = PspSalPrinter::EndJob();
+
+ if (!lcl_useSystemPrintDialog())
+ return bRet;
+
+ assert(m_xImpl);
+
+ if (!bRet || m_xImpl->m_sSpoolFile.isEmpty())
+ return bRet;
+
+ std::shared_ptr<GtkPrintWrapper> const xWrapper(lcl_getGtkSalInstance().getPrintWrapper());
+
+ GtkPageSetup* pPageSetup = xWrapper->page_setup_new();
+
+ GtkPrintJob* const pJob = xWrapper->print_job_new(
+ OUStringToOString(m_xImpl->m_sJobName, RTL_TEXTENCODING_UTF8).getStr(),
+ m_xImpl->m_pPrinter, m_xImpl->m_pSettings, pPageSetup);
+
+ GError* error = nullptr;
+ bRet = xWrapper->print_job_set_source_file(pJob, m_xImpl->m_sSpoolFile.getStr(), &error);
+ if (bRet)
+ xWrapper->print_job_send(pJob, nullptr, nullptr, nullptr);
+ else
+ {
+ //To-Do, do something with this
+ SAL_WARN("vcl.gtk3", "error was " << error->message);
+ g_error_free(error);
+ }
+
+ g_object_unref(pPageSetup);
+ m_xImpl.reset();
+
+ //To-Do, remove temp spool file
+
+ return bRet;
+}
+
+namespace
+{
+
+void
+lcl_setHelpText(
+ GtkWidget* const io_pWidget,
+ const uno::Sequence<OUString>& i_rHelpTexts,
+ const sal_Int32 i_nIndex)
+{
+ if (i_nIndex >= 0 && i_nIndex < i_rHelpTexts.getLength())
+ gtk_widget_set_tooltip_text(io_pWidget,
+ OUStringToOString(i_rHelpTexts.getConstArray()[i_nIndex], RTL_TEXTENCODING_UTF8).getStr());
+}
+
+GtkWidget*
+lcl_makeFrame(
+ GtkWidget* const i_pChild,
+ const OUString &i_rText,
+ const uno::Sequence<OUString> &i_rHelpTexts,
+ sal_Int32* const io_pCurHelpText)
+{
+ GtkWidget* const pLabel = gtk_label_new(nullptr);
+ lcl_setHelpText(pLabel, i_rHelpTexts, !io_pCurHelpText ? 0 : (*io_pCurHelpText)++);
+ gtk_misc_set_alignment(GTK_MISC(pLabel), 0.0, 0.5);
+
+ {
+ gchar* const pText = g_markup_printf_escaped("<b>%s</b>",
+ OUStringToOString(i_rText, RTL_TEXTENCODING_UTF8).getStr());
+ gtk_label_set_markup_with_mnemonic(GTK_LABEL(pLabel), pText);
+ g_free(pText);
+ }
+
+ GtkWidget* const pFrame = gtk_vbox_new(FALSE, 6);
+ gtk_box_pack_start(GTK_BOX(pFrame), pLabel, FALSE, FALSE, 0);
+
+ GtkWidget* const pAlignment = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
+ gtk_alignment_set_padding(GTK_ALIGNMENT(pAlignment), 0, 0, 12, 0);
+ gtk_box_pack_start(GTK_BOX(pFrame), pAlignment, FALSE, FALSE, 0);
+
+ gtk_container_add(GTK_CONTAINER(pAlignment), i_pChild);
+ return pFrame;
+}
+
+void
+lcl_extractHelpTextsOrIds(
+ const beans::PropertyValue& rEntry,
+ uno::Sequence<OUString>& rHelpStrings)
+{
+ if (!(rEntry.Value >>= rHelpStrings))
+ {
+ OUString aHelpString;
+ if (rEntry.Value >>= aHelpString)
+ {
+ rHelpStrings.realloc(1);
+ *rHelpStrings.getArray() = aHelpString;
+ }
+ }
+}
+
+GtkWidget*
+lcl_combo_box_text_new()
+{
+ return gtk_combo_box_text_new();
+}
+
+void
+lcl_combo_box_text_append(GtkWidget* const pWidget, gchar const* const pText)
+{
+ gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(pWidget), pText);
+}
+
+}
+
+GtkPrintDialog::GtkPrintDialog(vcl::PrinterController& io_rController)
+ : m_rController(io_rController)
+ , m_xWrapper(lcl_getGtkSalInstance().getPrintWrapper())
+{
+ assert(m_xWrapper->supportsPrinting());
+ impl_initDialog();
+ impl_initCustomTab();
+ impl_readFromSettings();
+}
+
+void
+GtkPrintDialog::impl_initDialog()
+{
+ //To-Do, like fpicker, set UI language
+ m_pDialog = m_xWrapper->print_unix_dialog_new();
+
+ vcl::Window* const pTopWindow(Application::GetActiveTopWindow());
+ if (pTopWindow)
+ {
+ GtkSalFrame* const pFrame(dynamic_cast<GtkSalFrame*>(pTopWindow->ImplGetFrame()));
+ if (pFrame)
+ {
+ GtkWindow* const pParent(GTK_WINDOW(pFrame->getWindow()));
+ if (pParent)
+ gtk_window_set_transient_for(GTK_WINDOW(m_pDialog), pParent);
+ }
+ }
+
+ m_xWrapper->print_unix_dialog_set_manual_capabilities(GTK_PRINT_UNIX_DIALOG(m_pDialog),
+ GtkPrintCapabilities(GTK_PRINT_CAPABILITY_COPIES
+ | GTK_PRINT_CAPABILITY_COLLATE
+ | GTK_PRINT_CAPABILITY_REVERSE
+ | GTK_PRINT_CAPABILITY_GENERATE_PS
+ | GTK_PRINT_CAPABILITY_NUMBER_UP
+ | GTK_PRINT_CAPABILITY_NUMBER_UP_LAYOUT
+ ));
+}
+
+void
+GtkPrintDialog::impl_initCustomTab()
+{
+ typedef std::vector<std::pair<GtkWidget*, OUString> > CustomTabs_t;
+
+ const uno::Sequence<beans::PropertyValue>& rOptions(m_rController.getUIOptions());
+ std::map<OUString, GtkWidget*> aPropertyToDependencyRowMap;
+ CustomTabs_t aCustomTabs;
+ GtkWidget* pCurParent = nullptr;
+ GtkWidget* pCurTabPage = nullptr;
+ GtkWidget* pCurSubGroup = nullptr;
+ bool bIgnoreSubgroup = false;
+ for (const auto& rOption : rOptions)
+ {
+ uno::Sequence<beans::PropertyValue> aOptProp;
+ rOption.Value >>= aOptProp;
+
+ OUString aCtrlType;
+ OUString aText;
+ OUString aPropertyName;
+ uno::Sequence<OUString> aChoices;
+ uno::Sequence<sal_Bool> aChoicesDisabled;
+ uno::Sequence<OUString> aHelpTexts;
+ sal_Int64 nMinValue = 0, nMaxValue = 0;
+ sal_Int32 nCurHelpText = 0;
+ OUString aDependsOnName;
+ sal_Int32 nDependsOnValue = 0;
+ bool bUseDependencyRow = false;
+ bool bIgnore = false;
+ GtkWidget* pGroup = nullptr;
+ bool bGtkInternal = false;
+
+ //Fix fdo#69381
+ //Next options if this one is empty
+ if (!aOptProp.hasElements())
+ continue;
+
+ for (const beans::PropertyValue& rEntry : std::as_const(aOptProp))
+ {
+ if ( rEntry.Name == "Text" )
+ {
+ OUString aValue;
+ rEntry.Value >>= aValue;
+ aText = aValue.replace('~', '_');
+ }
+ 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" )
+ {
+ beans::PropertyValue aVal;
+ rEntry.Value >>= aVal;
+ aPropertyName = aVal.Name;
+ }
+ else if ( rEntry.Name == "DependsOnName" )
+ rEntry.Value >>= aDependsOnName;
+ else if ( rEntry.Name == "DependsOnEntry" )
+ rEntry.Value >>= nDependsOnValue;
+ else if ( rEntry.Name == "AttachToDependency" )
+ rEntry.Value >>= bUseDependencyRow;
+ else if ( rEntry.Name == "MinValue" )
+ rEntry.Value >>= nMinValue;
+ else if ( rEntry.Name == "MaxValue" )
+ rEntry.Value >>= nMaxValue;
+ else if ( rEntry.Name == "HelpId" )
+ {
+ uno::Sequence<OUString> aHelpIds;
+ lcl_extractHelpTextsOrIds(rEntry, aHelpIds);
+ Help* const pHelp = Application::GetHelp();
+ if (pHelp)
+ {
+ const int nLen = aHelpIds.getLength();
+ aHelpTexts.realloc(nLen);
+ std::transform(aHelpIds.begin(), aHelpIds.end(), aHelpTexts.begin(),
+ [&pHelp](const OUString& rHelpId) { return pHelp->GetHelpText(rHelpId, static_cast<weld::Widget*>(nullptr)); });
+ }
+ else // fallback
+ aHelpTexts = aHelpIds;
+ }
+ else if ( rEntry.Name == "HelpText" )
+ lcl_extractHelpTextsOrIds(rEntry, aHelpTexts);
+ else if ( rEntry.Name == "InternalUIOnly" )
+ rEntry.Value >>= bIgnore;
+ else if ( rEntry.Name == "Enabled" )
+ {
+ // Ignore this. We use UIControlOptions::isUIOptionEnabled
+ // to check whether a control should be enabled.
+ }
+ else if ( rEntry.Name == "GroupingHint" )
+ {
+ // Ignore this. We cannot add/modify controls to/on existing
+ // tabs of the Gtk print dialog.
+ }
+ else
+ {
+ SAL_INFO("vcl.gtk", "unhandled UI option entry: " << rEntry.Name);
+ }
+ }
+
+ if ( aPropertyName == "PrintContent" )
+ bGtkInternal = true;
+
+ if (aCtrlType == "Group" || !pCurParent)
+ {
+ pCurTabPage = gtk_vbox_new(FALSE, 12);
+ gtk_container_set_border_width(GTK_CONTAINER(pCurTabPage), 6);
+ lcl_setHelpText(pCurTabPage, aHelpTexts, 0);
+
+ pCurParent = pCurTabPage;
+ aCustomTabs.emplace_back(pCurTabPage, aText);
+ }
+ else if (aCtrlType == "Subgroup")
+ {
+ bIgnoreSubgroup = bIgnore;
+ if (bIgnore)
+ continue;
+ pCurParent = gtk_vbox_new(FALSE, 12);
+ gtk_container_set_border_width(GTK_CONTAINER(pCurParent), 0);
+
+ pCurSubGroup = lcl_makeFrame(pCurParent, aText, aHelpTexts, nullptr);
+ gtk_box_pack_start(GTK_BOX(pCurTabPage), pCurSubGroup, FALSE, FALSE, 0);
+ }
+ // special case: we need to map these to controls of the gtk print dialog
+ else if (bGtkInternal)
+ {
+ if ( aPropertyName == "PrintContent" )
+ {
+ // What to print? And, more importantly, is there a selection?
+ impl_initPrintContent(aChoicesDisabled);
+ }
+ }
+ else if (bIgnoreSubgroup || bIgnore)
+ continue;
+ else
+ {
+ // change handlers for all the controls set up in this block
+ // should be set _after_ the control has been made (in)active,
+ // because:
+ // 1. value of the property is _known_--we are using it to
+ // _set_ the control, right?--no need to change it back .-)
+ // 2. it may cause warning because the widget may not
+ // have been placed in m_aControlToPropertyMap yet
+
+ GtkWidget* pWidget = nullptr;
+ beans::PropertyValue* pVal = nullptr;
+ if (aCtrlType == "Bool" && pCurParent)
+ {
+ pWidget = gtk_check_button_new_with_mnemonic(
+ OUStringToOString(aText, RTL_TEXTENCODING_UTF8).getStr());
+ lcl_setHelpText(pWidget, aHelpTexts, 0);
+ m_aControlToPropertyMap[pWidget] = aPropertyName;
+
+ bool bVal = false;
+ pVal = m_rController.getValue(aPropertyName);
+ if (pVal)
+ pVal->Value >>= bVal;
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pWidget), bVal);
+ gtk_widget_set_sensitive(pWidget,
+ m_rController.isUIOptionEnabled(aPropertyName) && pVal != nullptr);
+ g_signal_connect(pWidget, "toggled", G_CALLBACK(GtkPrintDialog::UIOption_CheckHdl), this);
+ }
+ else if (aCtrlType == "Radio" && pCurParent)
+ {
+ GtkWidget* const pVbox = gtk_vbox_new(FALSE, 12);
+ gtk_container_set_border_width(GTK_CONTAINER(pVbox), 0);
+
+ if (!aText.isEmpty())
+ pGroup = lcl_makeFrame(pVbox, aText, aHelpTexts, &nCurHelpText);
+
+ sal_Int32 nSelectVal = 0;
+ pVal = m_rController.getValue(aPropertyName);
+ if (pVal && pVal->Value.hasValue())
+ pVal->Value >>= nSelectVal;
+
+ for (sal_Int32 m = 0; m != aChoices.getLength(); m++)
+ {
+ pWidget = gtk_radio_button_new_with_mnemonic_from_widget(
+ GTK_RADIO_BUTTON(m == 0 ? nullptr : pWidget),
+ OUStringToOString(aChoices[m].replace('~', '_'), RTL_TEXTENCODING_UTF8).getStr());
+ lcl_setHelpText(pWidget, aHelpTexts, nCurHelpText++);
+ m_aControlToPropertyMap[pWidget] = aPropertyName;
+ m_aControlToNumValMap[pWidget] = m;
+ GtkWidget* const pRow = gtk_hbox_new(FALSE, 12);
+ gtk_box_pack_start(GTK_BOX(pVbox), pRow, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(pRow), pWidget, FALSE, FALSE, 0);
+ aPropertyToDependencyRowMap[aPropertyName + OUString::number(m)] = pRow;
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pWidget), m == nSelectVal);
+ gtk_widget_set_sensitive(pWidget,
+ m_rController.isUIOptionEnabled(aPropertyName) && pVal != nullptr);
+ g_signal_connect(pWidget, "toggled",
+ G_CALLBACK(GtkPrintDialog::UIOption_RadioHdl), this);
+ }
+
+ if (pGroup)
+ pWidget = pGroup;
+ else
+ pWidget = pVbox;
+ }
+ else if ((aCtrlType == "List" ||
+ aCtrlType == "Range" ||
+ aCtrlType == "Edit"
+ ) && pCurParent)
+ {
+ GtkWidget* const pHbox = gtk_hbox_new(FALSE, 12);
+ gtk_container_set_border_width(GTK_CONTAINER(pHbox), 0);
+
+ if ( aCtrlType == "List" )
+ {
+ pWidget = lcl_combo_box_text_new();
+
+ for (const auto& rChoice : std::as_const(aChoices))
+ {
+ lcl_combo_box_text_append(pWidget,
+ OUStringToOString(rChoice, RTL_TEXTENCODING_UTF8).getStr());
+ }
+
+ sal_Int32 nSelectVal = 0;
+ pVal = m_rController.getValue(aPropertyName);
+ if (pVal && pVal->Value.hasValue())
+ pVal->Value >>= nSelectVal;
+ gtk_combo_box_set_active(GTK_COMBO_BOX(pWidget), nSelectVal);
+ g_signal_connect(pWidget, "changed", G_CALLBACK(GtkPrintDialog::UIOption_SelectHdl), this);
+ }
+ else if (aCtrlType == "Edit" && pCurParent)
+ {
+ pWidget = gtk_entry_new();
+
+ OUString aCurVal;
+ pVal = m_rController.getValue(aPropertyName);
+ if (pVal && pVal->Value.hasValue())
+ pVal->Value >>= aCurVal;
+ gtk_entry_set_text(GTK_ENTRY(pWidget),
+ OUStringToOString(aCurVal, RTL_TEXTENCODING_UTF8).getStr());
+ }
+ else if (aCtrlType == "Range" && pCurParent)
+ {
+ pWidget = gtk_spin_button_new_with_range(nMinValue, nMaxValue, 1.0);
+
+ sal_Int64 nCurVal = 0;
+ pVal = m_rController.getValue(aPropertyName);
+ if (pVal && pVal->Value.hasValue())
+ pVal->Value >>= nCurVal;
+ gtk_spin_button_set_value(GTK_SPIN_BUTTON(pWidget), nCurVal);
+ }
+
+ lcl_setHelpText(pWidget, aHelpTexts, 0);
+ m_aControlToPropertyMap[pWidget] = aPropertyName;
+
+ gtk_widget_set_sensitive(pWidget,
+ m_rController.isUIOptionEnabled(aPropertyName) && pVal != nullptr);
+
+ if (!aText.isEmpty())
+ {
+ GtkWidget* const pLabel = gtk_label_new_with_mnemonic(
+ OUStringToOString(aText, RTL_TEXTENCODING_UTF8).getStr());
+ gtk_label_set_mnemonic_widget(GTK_LABEL(pLabel), pWidget);
+ gtk_box_pack_start(GTK_BOX(pHbox), pLabel, FALSE, FALSE, 0);
+ }
+
+ gtk_box_pack_start(GTK_BOX(pHbox), pWidget, FALSE, FALSE, 0);
+
+ pWidget = pHbox;
+
+ }
+ else
+ SAL_INFO("vcl.gtk", "unhandled option type: " << aCtrlType);
+
+ GtkWidget* pRow = nullptr;
+ if (pWidget)
+ {
+ if (bUseDependencyRow && !aDependsOnName.isEmpty())
+ {
+ pRow = aPropertyToDependencyRowMap[aDependsOnName + OUString::number(nDependsOnValue)];
+ if (!pRow)
+ {
+ gtk_widget_destroy(pWidget);
+ pWidget = nullptr;
+ }
+ }
+ }
+ if (pWidget)
+ {
+ if (!pRow)
+ {
+ pRow = gtk_hbox_new(FALSE, 12);
+ gtk_box_pack_start(GTK_BOX(pCurParent), pRow, FALSE, FALSE, 0);
+ }
+ if (!pGroup)
+ aPropertyToDependencyRowMap[aPropertyName + OUString::number(0)] = pRow;
+ gtk_box_pack_start(GTK_BOX(pRow), pWidget, FALSE, FALSE, 0);
+ }
+ }
+ }
+
+ CustomTabs_t::const_reverse_iterator aEnd = aCustomTabs.rend();
+ for (CustomTabs_t::const_reverse_iterator aI = aCustomTabs.rbegin(); aI != aEnd; ++aI)
+ {
+ gtk_widget_show_all(aI->first);
+ m_xWrapper->print_unix_dialog_add_custom_tab(GTK_PRINT_UNIX_DIALOG(m_pDialog), aI->first,
+ gtk_label_new(OUStringToOString(aI->second, RTL_TEXTENCODING_UTF8).getStr()));
+ }
+}
+
+void
+GtkPrintDialog::impl_initPrintContent(uno::Sequence<sal_Bool> const& i_rDisabled)
+{
+ SAL_WARN_IF(i_rDisabled.getLength() != 3, "vcl.gtk", "there is more choices than we expected");
+ if (i_rDisabled.getLength() != 3)
+ return;
+
+ GtkPrintUnixDialog* const pDialog(GTK_PRINT_UNIX_DIALOG(m_pDialog));
+
+ // XXX: This is a hack that depends on the number and the ordering of
+ // the controls in the rDisabled sequence (cf. the initialization of
+ // the "PrintContent" UI option in SwPrintUIOptions::SwPrintUIOptions,
+ // sw/source/core/view/printdata.cxx)
+ if (m_xWrapper->supportsPrintSelection() && !i_rDisabled[2])
+ {
+ m_xWrapper->print_unix_dialog_set_support_selection(pDialog, true);
+ m_xWrapper->print_unix_dialog_set_has_selection(pDialog, true);
+ }
+
+ beans::PropertyValue* const pPrintContent(
+ m_rController.getValue(OUString("PrintContent")));
+
+ if (pPrintContent)
+ {
+ sal_Int32 nSelectionType(0);
+ pPrintContent->Value >>= nSelectionType;
+ GtkPrintSettings* const pSettings(getSettings());
+ GtkPrintPages ePrintPages(GTK_PRINT_PAGES_ALL);
+ switch (nSelectionType)
+ {
+ case 0:
+ ePrintPages = GTK_PRINT_PAGES_ALL;
+ break;
+ case 1:
+ ePrintPages = GTK_PRINT_PAGES_RANGES;
+ break;
+ case 2:
+ if (m_xWrapper->supportsPrintSelection())
+ ePrintPages = GTK_PRINT_PAGES_SELECTION;
+ else
+ SAL_INFO("vcl.gtk", "the application wants to print a selection, but the present gtk version does not support it");
+ break;
+ default:
+ SAL_WARN("vcl.gtk", "unexpected selection type: " << nSelectionType);
+ }
+ m_xWrapper->print_settings_set_print_pages(pSettings, ePrintPages);
+ m_xWrapper->print_unix_dialog_set_settings(pDialog, pSettings);
+ g_object_unref(G_OBJECT(pSettings));
+ }
+}
+
+void
+GtkPrintDialog::impl_checkOptionalControlDependencies()
+{
+ for (auto& rEntry : m_aControlToPropertyMap)
+ {
+ gtk_widget_set_sensitive(rEntry.first, m_rController.isUIOptionEnabled(rEntry.second));
+ }
+}
+
+beans::PropertyValue*
+GtkPrintDialog::impl_queryPropertyValue(GtkWidget* const i_pWidget) const
+{
+ beans::PropertyValue* pVal(nullptr);
+ std::map<GtkWidget*, OUString>::const_iterator aIt(m_aControlToPropertyMap.find(i_pWidget));
+ if (aIt != m_aControlToPropertyMap.end())
+ {
+ pVal = m_rController.getValue(aIt->second);
+ SAL_WARN_IF(!pVal, "vcl.gtk", "property value not found");
+ }
+ else
+ {
+ SAL_WARN("vcl.gtk", "changed control not in property map");
+ }
+ return pVal;
+}
+
+void
+GtkPrintDialog::impl_UIOption_CheckHdl(GtkWidget* const i_pWidget)
+{
+ beans::PropertyValue* const pVal = impl_queryPropertyValue(i_pWidget);
+ if (pVal)
+ {
+ const bool bVal = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(i_pWidget));
+ pVal->Value <<= bVal;
+
+ impl_checkOptionalControlDependencies();
+ }
+}
+
+void
+GtkPrintDialog::impl_UIOption_RadioHdl(GtkWidget* const i_pWidget)
+{
+ if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(i_pWidget)))
+ {
+ beans::PropertyValue* const pVal = impl_queryPropertyValue(i_pWidget);
+ std::map<GtkWidget*, sal_Int32>::const_iterator it = m_aControlToNumValMap.find(i_pWidget);
+ if (pVal && it != m_aControlToNumValMap.end())
+ {
+
+ const sal_Int32 nVal = it->second;
+ pVal->Value <<= nVal;
+
+ impl_checkOptionalControlDependencies();
+ }
+ }
+}
+
+void
+GtkPrintDialog::impl_UIOption_SelectHdl(GtkWidget* const i_pWidget)
+{
+ beans::PropertyValue* const pVal = impl_queryPropertyValue(i_pWidget);
+ if (pVal)
+ {
+ const sal_Int32 nVal(gtk_combo_box_get_active(GTK_COMBO_BOX(i_pWidget)));
+ pVal->Value <<= nVal;
+
+ impl_checkOptionalControlDependencies();
+ }
+}
+
+bool
+GtkPrintDialog::run()
+{
+ bool bDoJob = false;
+ bool bContinue = true;
+ while (bContinue)
+ {
+ bContinue = false;
+ const gint nStatus = gtk_dialog_run(GTK_DIALOG(m_pDialog));
+ switch (nStatus)
+ {
+ case GTK_RESPONSE_HELP:
+ SAL_WARN("vcl.gtk3", "To-Do: Help ?");
+ bContinue = true;
+ break;
+ case GTK_RESPONSE_OK:
+ bDoJob = true;
+ break;
+ default:
+ break;
+ }
+ }
+ gtk_widget_hide(m_pDialog);
+ impl_storeToSettings();
+ return bDoJob;
+}
+
+void
+GtkPrintDialog::updateControllerPrintRange()
+{
+ GtkPrintSettings* const pSettings(getSettings());
+ // TODO: use get_print_pages
+ if (const gchar* const pStr = m_xWrapper->print_settings_get(pSettings, GTK_PRINT_SETTINGS_PRINT_PAGES))
+ {
+ beans::PropertyValue* pVal = m_rController.getValue(OUString("PrintRange"));
+ if (!pVal)
+ pVal = m_rController.getValue(OUString("PrintContent"));
+ SAL_WARN_IF(!pVal, "vcl.gtk", "Nothing to map standard print options to!");
+ if (pVal)
+ {
+ sal_Int32 nVal = 0;
+ if (!strcmp(pStr, "all"))
+ nVal = 0;
+ else if (!strcmp(pStr, "ranges"))
+ nVal = 1;
+ else if (!strcmp(pStr, "selection"))
+ nVal = 2;
+ pVal->Value <<= nVal;
+
+ if (nVal == 1)
+ {
+ pVal = m_rController.getValue(OUString("PageRange"));
+ SAL_WARN_IF(!pVal, "vcl.gtk", "PageRange doesn't exist!");
+ if (pVal)
+ {
+ OUStringBuffer sBuf;
+ gint num_ranges;
+ const GtkPageRange* const pRanges = m_xWrapper->print_settings_get_page_ranges(pSettings, &num_ranges);
+ for (gint i = 0; i != num_ranges && pRanges; ++i)
+ {
+ sBuf.append(sal_Int32(pRanges[i].start+1));
+ if (pRanges[i].start != pRanges[i].end)
+ {
+ sBuf.append('-');
+ sBuf.append(sal_Int32(pRanges[i].end+1));
+ }
+
+ if (i != num_ranges-1)
+ sBuf.append(',');
+ }
+ pVal->Value <<= sBuf.makeStringAndClear();
+ }
+ }
+ }
+ }
+ g_object_unref(G_OBJECT(pSettings));
+}
+
+GtkPrintDialog::~GtkPrintDialog()
+{
+ gtk_widget_destroy(m_pDialog);
+}
+
+void
+GtkPrintDialog::impl_readFromSettings()
+{
+ vcl::SettingsConfigItem* const pItem(vcl::SettingsConfigItem::get());
+ GtkPrintSettings* const pSettings(getSettings());
+
+ const OUString aPrintDialogStr("PrintDialog");
+ const OUString aCopyCount(pItem->getValue(aPrintDialogStr,
+ "CopyCount"));
+ const OUString aCollate(pItem->getValue(aPrintDialogStr,
+ "Collate"));
+
+ const gint nOldCopyCount(m_xWrapper->print_settings_get_n_copies(pSettings));
+ const sal_Int32 nCopyCount(aCopyCount.toInt32());
+ if (nCopyCount > 0 && nOldCopyCount != nCopyCount)
+ {
+ m_xWrapper->print_settings_set_n_copies(pSettings, sal::static_int_cast<gint>(nCopyCount));
+ }
+
+ const bool bOldCollate(m_xWrapper->print_settings_get_collate(pSettings));
+ const bool bCollate(aCollate.equalsIgnoreAsciiCase("true"));
+ if (bOldCollate != bCollate)
+ {
+ m_xWrapper->print_settings_set_collate(pSettings, bCollate);
+ }
+
+ m_xWrapper->print_unix_dialog_set_settings(GTK_PRINT_UNIX_DIALOG(m_pDialog), pSettings);
+ g_object_unref(G_OBJECT(pSettings));
+}
+
+void
+GtkPrintDialog::impl_storeToSettings()
+const
+{
+ vcl::SettingsConfigItem* const pItem(vcl::SettingsConfigItem::get());
+ GtkPrintSettings* const pSettings(getSettings());
+
+ const OUString aPrintDialogStr("PrintDialog");
+ pItem->setValue(aPrintDialogStr,
+ "CopyCount",
+ OUString::number(m_xWrapper->print_settings_get_n_copies(pSettings)));
+ pItem->setValue(aPrintDialogStr,
+ "Collate",
+ m_xWrapper->print_settings_get_collate(pSettings)
+ ? OUString("true")
+ : OUString("false"))
+ ;
+ // pItem->setValue(aPrintDialog, OUString("ToFile"), );
+ g_object_unref(G_OBJECT(pSettings));
+ pItem->Commit();
+}
+
+sal_uInt32
+GtkSalInfoPrinter::GetCapabilities(
+ const ImplJobSetup* const i_pSetupData,
+ const PrinterCapType i_nType)
+{
+ if (i_nType == PrinterCapType::ExternalDialog && lcl_useSystemPrintDialog())
+ return 1;
+ return PspSalInfoPrinter::GetCapabilities(i_pSetupData, i_nType);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtkprintwrapper.hxx b/vcl/unx/gtk3/gtkprintwrapper.hxx
new file mode 100644
index 000000000..9a0c8e3ee
--- /dev/null
+++ b/vcl/unx/gtk3/gtkprintwrapper.hxx
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_UNX_GTK3_INC_GTKPRINTWRAPPER_HXX
+#define INCLUDED_VCL_UNX_GTK3_INC_GTKPRINTWRAPPER_HXX
+
+#include "gtk/gtkprintwrapper.hxx"
+
+#endif // INCLUDED_VCL_UNX_GTK3_INC_GTKPRINTWRAPPER_HXX
+
+/* 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 000000000..1fbb8fd27
--- /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 000000000..b05929b90
--- /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/gtk3atkaction.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 000000000..d31e5e429
--- /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/gtk3atkbridge.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 000000000..63f44a3a1
--- /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/gtk3atkcomponent.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 000000000..064e72d98
--- /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/gtk3atkeditabletext.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 000000000..86d9ac43a
--- /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/gtk3atkfactory.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 000000000..d2ce059aa
--- /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/gtk3atkhypertext.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 000000000..3a1234e07
--- /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/gtk3atkimage.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 000000000..51d53bf91
--- /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/gtk3atklistener.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 000000000..f5e791720
--- /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/gtk3atkregistry.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 000000000..ab39c0e13
--- /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/gtk3atkselection.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 000000000..194791b68
--- /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/gtk3atktable.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 000000000..8d668e6e6
--- /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/gtk3atktext.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 000000000..c767a95d0
--- /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/gtk3atktextattributes.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 000000000..39eb5aeeb
--- /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/gtk3atkutil.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 000000000..5a526e1cf
--- /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/gtk3atkvalue.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 000000000..b0029f273
--- /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/gtk3atkwrapper.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 000000000..1d0925257
--- /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_a11y.cxx b/vcl/unx/gtk3_kde5/gtk3_kde5_a11y.cxx
new file mode 100644
index 000000000..02fd47a60
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_a11y.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 "../gtk3/a11y/gtk3atkaction.cxx"
+#include "../gtk3/a11y/gtk3atkbridge.cxx"
+#include "../gtk3/a11y/gtk3atkcomponent.cxx"
+#include "../gtk3/a11y/gtk3atkeditabletext.cxx"
+#include "../gtk3/a11y/gtk3atkfactory.cxx"
+#include "../gtk3/a11y/gtk3atkhypertext.cxx"
+#include "../gtk3/a11y/gtk3atkimage.cxx"
+#include "../gtk3/a11y/gtk3atklistener.cxx"
+#include "../gtk3/a11y/gtk3atkregistry.cxx"
+#include "../gtk3/a11y/gtk3atkselection.cxx"
+#include "../gtk3/a11y/gtk3atktable.cxx"
+#include "../gtk3/a11y/gtk3atktextattributes.cxx"
+#include "../gtk3/a11y/gtk3atktext.cxx"
+#include "../gtk3/a11y/gtk3atkutil.cxx"
+#include "../gtk3/a11y/gtk3atkvalue.cxx"
+#include "../gtk3/a11y/gtk3atkwindow.cxx"
+#include "../gtk3/a11y/gtk3atkwrapper.cxx"
+
+/* 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 000000000..fc271f160
--- /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/cairo_gtk3_cairo.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
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 000000000..eec532d40
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_filepicker.cxx
@@ -0,0 +1,456 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <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)
+{
+ const char* resId = nullptr;
+
+ switch (controlId)
+ {
+ case CHECKBOX_AUTOEXTENSION:
+ resId = STR_FPICKER_AUTO_EXTENSION;
+ break;
+ case CHECKBOX_PASSWORD:
+ resId = STR_FPICKER_PASSWORD;
+ break;
+ case CHECKBOX_FILTEROPTIONS:
+ resId = STR_FPICKER_FILTER_OPTIONS;
+ break;
+ case CHECKBOX_READONLY:
+ resId = STR_FPICKER_READONLY;
+ break;
+ case CHECKBOX_LINK:
+ resId = STR_FPICKER_INSERT_AS_LINK;
+ break;
+ case CHECKBOX_PREVIEW:
+ resId = STR_FPICKER_SHOW_PREVIEW;
+ break;
+ case CHECKBOX_SELECTION:
+ resId = STR_FPICKER_SELECTION;
+ break;
+ case CHECKBOX_GPGENCRYPTION:
+ resId = STR_FPICKER_GPGENCRYPT;
+ break;
+ case PUSHBUTTON_PLAY:
+ resId = STR_FPICKER_PLAY;
+ break;
+ case LISTBOX_VERSION:
+ resId = STR_FPICKER_VERSION;
+ break;
+ case LISTBOX_TEMPLATE:
+ resId = STR_FPICKER_TEMPLATES;
+ break;
+ case LISTBOX_IMAGE_TEMPLATE:
+ resId = STR_FPICKER_IMAGE_TEMPLATE;
+ break;
+ case LISTBOX_IMAGE_ANCHOR:
+ resId = STR_FPICKER_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_FPICKER_SAVE : STR_FPICKER_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 000000000..7ce391004
--- /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 000000000..80c15938b
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_filepicker_ipc.cxx
@@ -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 .
+ */
+
+#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/syswin.hxx>
+
+#include <osl/file.h>
+#include <osl/process.h>
+
+#include <gtk/gtk.h>
+
+#include <boost/filesystem/path.hpp>
+
+#include <svdata.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(OString(ret.c_str(), strlen(ret.c_str())));
+}
+
+OUString findPickerExecutable()
+{
+ const auto path = applicationDirPath();
+ const OUString app("lo_kde5filepicker");
+ OUString ret;
+ osl_searchFileURL(app.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(OString(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);
+ for (size_t i = 0; i < numFiles; ++i)
+ {
+ readIpcArg(stream, seq[i]);
+ }
+}
+
+void sendIpcArg(std::ostream& stream, const OUString& string)
+{
+ const auto utf8 = string.toUtf8();
+ sendIpcStringArg(stream, utf8.getLength(), utf8.getStr());
+}
+
+OUString getResString(const char* pResId)
+{
+ if (pResId == nullptr)
+ return {};
+
+ return VclResId(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()
+{
+ vcl::Window* pParentWin = Application::GetDefDialogParent();
+ if (!pParentWin)
+ return {};
+
+ const SystemEnvData* pSysData = static_cast<SystemWindow*>(pParentWin)->GetSystemData();
+ if (!pSysData)
+ return {};
+
+ sendCommand(Commands::SetWinId, pSysData->aWindow);
+
+ auto* pMainWindow = static_cast<GtkWidget*>(pSysData->pWidget);
+ if (!pMainWindow)
+ return {};
+
+ 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, pSysData->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 000000000..e24fb92de
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_filepicker_ipc.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 <osl/process.h>
+
+#include "filepicker_ipc_commands.hxx"
+
+#include <functional>
+#include <mutex>
+#include <thread>
+#include <sstream>
+
+OUString getResString(const char* 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 000000000..fa0b562cc
--- /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 <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_FPICKER_FOLDER_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 000000000..9801a072a
--- /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 000000000..fa71136f9
--- /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/gtk3gloactiongroup.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 000000000..de79c14c1
--- /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/gtk3glomenu.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 000000000..200a74131
--- /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/gtk3gtkdata.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 000000000..0b2eb38dc
--- /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/gtk3gtkframe.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 000000000..e25ec8b0e
--- /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/gtk3gtkinst.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 000000000..a2eb6b9e1
--- /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/gtk3gtkobject.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 000000000..9eb1c7975
--- /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/gtk3gtksalmenu.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 000000000..8f6b38843
--- /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/gtk3gtksys.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 000000000..eb1592389
--- /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/gtk3hudawareness.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_printwrapper.cxx b/vcl/unx/gtk3_kde5/gtk3_kde5_printwrapper.cxx
new file mode 100644
index 000000000..33768f8ca
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_printwrapper.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/gtk3gtkprintwrapper.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 000000000..108e41d47
--- /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/gtk3salnativewidgets-gtk.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_salprn-gtk.cxx b/vcl/unx/gtk3_kde5/gtk3_kde5_salprn-gtk.cxx
new file mode 100644
index 000000000..a9f8c076f
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_salprn-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/gtk3salprn-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 000000000..65953e4f2
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/kde5_filepicker.cxx
@@ -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 .
+ */
+
+#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("ftp"), 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)
+ {
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ _dialog->setConfirmOverwrite(true);
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ _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 000000000..74f94d222
--- /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 000000000..7f0bfff98
--- /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 000000000..fa9be696c
--- /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 000000000..4e73e9ee4
--- /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/kf5/KF5FilePicker.cxx b/vcl/unx/kf5/KF5FilePicker.cxx
new file mode 100644
index 000000000..20e64007b
--- /dev/null
+++ b/vcl/unx/kf5/KF5FilePicker.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "KF5FilePicker.hxx"
+#include <KF5FilePicker.moc>
+
+#include <com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.hpp>
+#include <cppuhelper/supportsservice.hxx>
+#include <osl/mutex.hxx>
+
+#include <qt5/Qt5Instance.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.KF5FilePicker", "com.sun.star.ui.dialogs.KF5FolderPicker" };
+}
+}
+
+// KF5FilePicker
+
+KF5FilePicker::KF5FilePicker(css::uno::Reference<css::uno::XComponentContext> const& context,
+ QFileDialog::FileMode eMode)
+ // Native kf5 filepicker does not add file extension automatically
+ : Qt5FilePicker(context, eMode, true)
+ , _layout(new QGridLayout(m_pExtraControls))
+{
+ // only columns 0 and 1 are used by controls (s. Qt5FilePicker::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("ftp"), 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 KF5FilePicker::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;
+
+ Qt5FilePicker::setValue(controlId, nControlAction, value);
+}
+
+uno::Any SAL_CALL KF5FilePicker::getValue(sal_Int16 controlId, sal_Int16 nControlAction)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ 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 Qt5FilePicker::getValue(controlId, nControlAction);
+}
+
+void SAL_CALL KF5FilePicker::enableControl(sal_Int16 controlId, sal_Bool enable)
+{
+ if (CHECKBOX_AUTOEXTENSION == controlId)
+ // We ignore this one and rely on QFileDialog to provide the functionality
+ return;
+
+ Qt5FilePicker::enableControl(controlId, enable);
+}
+
+void SAL_CALL KF5FilePicker::setLabel(sal_Int16 controlId, const OUString& label)
+{
+ if (CHECKBOX_AUTOEXTENSION == controlId)
+ // We ignore this one and rely on QFileDialog to provide the functionality
+ return;
+
+ Qt5FilePicker::setLabel(controlId, label);
+}
+
+OUString SAL_CALL KF5FilePicker::getLabel(sal_Int16 controlId)
+{
+ // We ignore this one and rely on QFileDialog to provide the functionality
+ if (CHECKBOX_AUTOEXTENSION == controlId)
+ return "";
+
+ return Qt5FilePicker::getLabel(controlId);
+}
+
+void KF5FilePicker::addCustomControl(sal_Int16 controlId)
+{
+ // native kf5 filepicker has its own autoextension checkbox,
+ // therefore avoid adding yet another one
+ if (controlId == CHECKBOX_AUTOEXTENSION)
+ return;
+
+ Qt5FilePicker::addCustomControl(controlId);
+}
+
+// XServiceInfo
+OUString SAL_CALL KF5FilePicker::getImplementationName()
+{
+ return "com.sun.star.ui.dialogs.KF5FilePicker";
+}
+
+sal_Bool SAL_CALL KF5FilePicker::supportsService(const OUString& ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+uno::Sequence<OUString> SAL_CALL KF5FilePicker::getSupportedServiceNames()
+{
+ return FilePicker_getSupportedServiceNames();
+}
+
+bool KF5FilePicker::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/KF5FilePicker.hxx b/vcl/unx/kf5/KF5FilePicker.hxx
new file mode 100644
index 000000000..4cf84c7fe
--- /dev/null
+++ b/vcl/unx/kf5/KF5FilePicker.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 <Qt5FilePicker.hxx>
+#include <memory>
+
+class QGridLayout;
+
+class KF5FilePicker final : public Qt5FilePicker
+{
+ Q_OBJECT
+
+private:
+ //layout for extra custom controls
+ std::unique_ptr<QGridLayout> _layout;
+
+public:
+ explicit KF5FilePicker(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 file picker has its own automatic extension handling
+ void updateAutomaticFileExtension() override {}
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/kf5/KF5SalFrame.cxx b/vcl/unx/kf5/KF5SalFrame.cxx
new file mode 100644
index 000000000..1aa0b9008
--- /dev/null
+++ b/vcl/unx/kf5/KF5SalFrame.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <QtGui/QColor>
+#include <QtWidgets/QStyle>
+#include <QtWidgets/QToolTip>
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QMenuBar>
+
+#include <KConfig>
+#include <KConfigGroup>
+#include <KSharedConfig>
+
+#include <Qt5FontFace.hxx>
+#include "KF5SalFrame.hxx"
+
+#include <tools/color.hxx>
+
+#include <vcl/font.hxx>
+#include <vcl/settings.hxx>
+#include <sal/log.hxx>
+
+#include <unx/fontmanager.hxx>
+
+#include <svdata.hxx>
+
+#include <optional>
+
+KF5SalFrame::KF5SalFrame(KF5SalFrame* pParent, SalFrameStyleFlags nState, bool bUseCairo)
+ : Qt5Frame(pParent, nState, bUseCairo)
+ , m_bGraphicsInUse(false)
+{
+}
+
+/** Helper function to add information to Font from QFont.
+
+ Mostly grabbed from the Gtk+ vclplug (salnativewidgets-gtk.cxx).
+*/
+static vcl::Font toFont(const QFont& rQFont, const css::lang::Locale& rLocale)
+{
+ psp::FastPrintFontInfo aInfo;
+ QFontInfo qFontInfo(rQFont);
+
+ // set family name
+ aInfo.m_aFamilyName = OUString(static_cast<const char*>(rQFont.family().toUtf8()),
+ strlen(static_cast<const char*>(rQFont.family().toUtf8())),
+ RTL_TEXTENCODING_UTF8);
+
+ aInfo.m_eItalic = Qt5FontFace::toFontItalic(qFontInfo.style());
+ aInfo.m_eWeight = Qt5FontFace::toFontWeight(qFontInfo.weight());
+ aInfo.m_eWidth = Qt5FontFace::toFontWidth(rQFont.stretch());
+
+ SAL_INFO("vcl.kf5", "font name BEFORE system match: \"" << aInfo.m_aFamilyName << "\"");
+
+ // match font to e.g. resolve "Sans"
+ psp::PrintFontManager::get().matchFont(aInfo, rLocale);
+
+ SAL_INFO("vcl.kf5", "font match " << (aInfo.m_nID != 0 ? "succeeded" : "failed")
+ << ", name AFTER: \"" << aInfo.m_aFamilyName << "\"");
+
+ // font height
+ int nPointHeight = qFontInfo.pointSize();
+ if (nPointHeight <= 0)
+ nPointHeight = rQFont.pointSize();
+
+ // Create the font
+ vcl::Font aFont(aInfo.m_aFamilyName, Size(0, nPointHeight));
+ if (aInfo.m_eWeight != WEIGHT_DONTKNOW)
+ aFont.SetWeight(aInfo.m_eWeight);
+ if (aInfo.m_eWidth != WIDTH_DONTKNOW)
+ aFont.SetWidthType(aInfo.m_eWidth);
+ if (aInfo.m_eItalic != ITALIC_DONTKNOW)
+ aFont.SetItalic(aInfo.m_eItalic);
+ if (aInfo.m_ePitch != PITCH_DONTKNOW)
+ aFont.SetPitch(aInfo.m_ePitch);
+
+ return aFont;
+}
+
+/** Implementation of KDE integration's main method.
+*/
+void KF5SalFrame::UpdateSettings(AllSettings& rSettings)
+{
+ Qt5Frame::UpdateSettings(rSettings);
+
+ StyleSettings style(rSettings.GetStyleSettings());
+ bool bSetTitleFont = false;
+
+ // WM settings
+ /*KConfig *pConfig = KGlobal::config().data();
+ if ( pConfig )
+ {
+ const char *pKey;
+
+ {
+ KConfigGroup aWMGroup = pConfig->group( "WM" );
+
+ pKey = "titleFont";
+ if (aWMGroup.hasKey(pKey))
+ {
+ vcl::Font aFont = toFont(aWMGroup.readEntry(pKey, QFont()),
+ rSettings.GetUILanguageTag().getLocale());
+ style.SetTitleFont( aFont );
+ bSetTitleFont = true;
+ }
+ }
+
+ KConfigGroup aIconsGroup = pConfig->group("Icons");
+
+ pKey = "Theme";
+ if (aIconsGroup.hasKey(pKey))
+ style.SetPreferredIconTheme( readEntryUntranslated(&aIconsGroup, pKey));
+
+ //toolbar
+ pKey = "toolbarFont";
+ if (aIconsGroup.hasKey(pKey))
+ {
+ vcl::Font aFont = toFont(aIconsGroup.readEntry(pKey, QFont()),
+ rSettings.GetUILanguageTag().getLocale());
+ style.SetToolFont( aFont );
+ }
+ }*/
+
+ // Font
+ vcl::Font aFont = toFont(QApplication::font(), rSettings.GetUILanguageTag().getLocale());
+
+ style.BatchSetFonts(aFont, aFont);
+
+ aFont.SetWeight(WEIGHT_BOLD);
+ if (!bSetTitleFont)
+ {
+ style.SetTitleFont(aFont);
+ }
+ style.SetFloatTitleFont(aFont);
+ style.SetHelpFont(toFont(QToolTip::font(), rSettings.GetUILanguageTag().getLocale()));
+
+ int flash_time = QApplication::cursorFlashTime();
+ style.SetCursorBlinkTime(flash_time != 0 ? flash_time / 2 : STYLE_CURSOR_NOBLINKTIME);
+
+ // Menu
+ std::unique_ptr<QMenuBar> pMenuBar = std::make_unique<QMenuBar>();
+ aFont = toFont(pMenuBar->font(), rSettings.GetUILanguageTag().getLocale());
+ style.SetMenuFont(aFont);
+
+ rSettings.SetStyleSettings(style);
+}
+
+SalGraphics* KF5SalFrame::AcquireGraphics()
+{
+ if (m_bGraphicsInUse)
+ return nullptr;
+
+ m_bGraphicsInUse = true;
+
+ if (!m_pKF5Graphics)
+ {
+ m_pKF5Graphics.reset(new Qt5SvpGraphics(this));
+ Qt5Frame::InitQt5SvpGraphics(m_pKF5Graphics.get());
+ }
+
+ return m_pKF5Graphics.get();
+}
+
+void KF5SalFrame::ReleaseGraphics(SalGraphics* pSalGraph)
+{
+ (void)pSalGraph;
+ assert(pSalGraph == m_pKF5Graphics.get());
+ m_bGraphicsInUse = false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/kf5/KF5SalFrame.hxx b/vcl/unx/kf5/KF5SalFrame.hxx
new file mode 100644
index 000000000..f757535c2
--- /dev/null
+++ b/vcl/unx/kf5/KF5SalFrame.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 .
+ */
+
+#pragma once
+
+#include <memory>
+
+#include <qt5/Qt5Frame.hxx>
+#include <qt5/Qt5SvpGraphics.hxx>
+
+class QWidget;
+
+class KF5SalFrame : public Qt5Frame
+{
+private:
+ std::unique_ptr<Qt5SvpGraphics> m_pKF5Graphics;
+ bool m_bGraphicsInUse;
+
+public:
+ KF5SalFrame(KF5SalFrame* pParent, SalFrameStyleFlags nStyle, bool bUseCairo);
+
+ virtual SalGraphics* AcquireGraphics() override;
+ virtual void ReleaseGraphics(SalGraphics* pGraphics) override;
+ virtual void UpdateSettings(AllSettings& rSettings) override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/kf5/KF5SalInstance.cxx b/vcl/unx/kf5/KF5SalInstance.cxx
new file mode 100644
index 000000000..5b95ff8df
--- /dev/null
+++ b/vcl/unx/kf5/KF5SalInstance.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 <sal/config.h>
+
+#include <utility>
+
+#include <QtWidgets/QApplication>
+
+#include <sal/log.hxx>
+
+#include <Qt5Data.hxx>
+
+#include "KF5FilePicker.hxx"
+#include "KF5SalFrame.hxx"
+#include "KF5SalInstance.hxx"
+
+using namespace com::sun::star;
+
+KF5SalInstance::KF5SalInstance(std::unique_ptr<QApplication>& pQApp)
+ : Qt5Instance(pQApp, true)
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maAppData.mxToolkitName = OUString("kf5");
+}
+
+SalFrame* KF5SalInstance::CreateFrame(SalFrame* pParent, SalFrameStyleFlags nState)
+{
+ SalFrame* pRet(nullptr);
+ RunInMainThread([&pRet, pParent, nState]() {
+ pRet = new KF5SalFrame(static_cast<KF5SalFrame*>(pParent), nState, true);
+ });
+ assert(pRet);
+ return pRet;
+}
+
+bool KF5SalInstance::hasNativeFileSelection() const
+{
+ if (Application::GetDesktopEnvironment() == "PLASMA5")
+ return true;
+ return Qt5Instance::hasNativeFileSelection();
+}
+
+Qt5FilePicker*
+KF5SalInstance::createPicker(css::uno::Reference<css::uno::XComponentContext> const& context,
+ QFileDialog::FileMode eMode)
+{
+ if (!IsMainThread())
+ {
+ SolarMutexGuard g;
+ Qt5FilePicker* pPicker;
+ RunInMainThread([&, this]() { pPicker = createPicker(context, eMode); });
+ assert(pPicker);
+ return pPicker;
+ }
+
+ // In order to insert custom controls, KF5FilePicker currently relies on KFileWidget
+ // being used in the native file picker, which is only the case for KDE Plasma.
+ // Therefore, return the plain qt5 one in order to not lose custom controls.
+ if (Application::GetDesktopEnvironment() == "PLASMA5")
+ return new KF5FilePicker(context, eMode);
+ return Qt5Instance::createPicker(context, eMode);
+}
+
+extern "C" {
+VCLPLUG_KF5_PUBLIC SalInstance* create_SalInstance()
+{
+ std::unique_ptr<char* []> pFakeArgv;
+ std::unique_ptr<int> pFakeArgc;
+ std::vector<FreeableCStr> aFakeArgvFreeable;
+ Qt5Instance::AllocFakeCmdlineArgs(pFakeArgv, pFakeArgc, aFakeArgvFreeable);
+
+ std::unique_ptr<QApplication> pQApp
+ = Qt5Instance::CreateQApplication(*pFakeArgc, pFakeArgv.get());
+
+ KF5SalInstance* pInstance = new KF5SalInstance(pQApp);
+ pInstance->MoveFakeCmdlineArgs(pFakeArgv, pFakeArgc, aFakeArgvFreeable);
+
+ new Qt5Data(pInstance);
+
+ return pInstance;
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/kf5/KF5SalInstance.hxx b/vcl/unx/kf5/KF5SalInstance.hxx
new file mode 100644
index 000000000..5dd306da5
--- /dev/null
+++ b/vcl/unx/kf5/KF5SalInstance.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 <qt5/Qt5Instance.hxx>
+
+class KF5SalInstance final : public Qt5Instance
+{
+ bool hasNativeFileSelection() const override;
+ Qt5FilePicker* createPicker(css::uno::Reference<css::uno::XComponentContext> const& context,
+ QFileDialog::FileMode) override;
+
+ SalFrame* CreateFrame(SalFrame* pParent, SalFrameStyleFlags nStyle) override;
+
+public:
+ explicit KF5SalInstance(std::unique_ptr<QApplication>& pQApp);
+};
+
+/* 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 000000000..c354269b3
--- /dev/null
+++ b/vcl/unx/x11/x11sys.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 <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();
+}
+
+bool X11SalSystem::IsUnifiedDisplay()
+{
+ SalDisplay* pSalDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData());
+ unsigned int nScreenCount = pSalDisp->GetXScreenCount();
+ return pSalDisp->IsXinerama() || (nScreenCount == 1);
+}
+
+unsigned int X11SalSystem::GetDisplayBuiltInScreen()
+{
+ SalDisplay* pSalDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData());
+ return pSalDisp->GetDefaultXScreen().getXScreen();
+}
+
+tools::Rectangle X11SalSystem::GetDisplayScreenPosSizePixel( unsigned int nScreen )
+{
+ tools::Rectangle aRet;
+ SalDisplay* pSalDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData());
+ if( pSalDisp->IsXinerama() )
+ {
+ const std::vector< tools::Rectangle >& 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 = tools::Rectangle( Point( 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 000000000..b8509cbd2
--- /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.android.component b/vcl/vcl.android.component
new file mode 100644
index 000000000..2c4c4a590
--- /dev/null
+++ b/vcl/vcl.android.component
@@ -0,0 +1,31 @@
+<?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.datatransfer.VCLGenericClipboard">
+ <service name="com.sun.star.datatransfer.clipboard.SystemClipboard"/>
+ </implementation>
+ <implementation name="com.sun.star.frame.VCLSessionManagerClient">
+ <service name="com.sun.star.frame.SessionManagerClient"/>
+ </implementation>
+ <implementation name="vcl::FontIdentificator">
+ <service name="com.sun.star.awt.FontIdentificator"/>
+ </implementation>
+</component>
diff --git a/vcl/vcl.common.component b/vcl/vcl.common.component
new file mode 100644
index 000000000..77fbf38a1
--- /dev/null
+++ b/vcl/vcl.common.component
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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.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.graphic.GraphicObject"
+ constructor="com_sun_star_graphic_GraphicObject_get_implementation">
+ <service name="com.sun.star.graphic.GraphicObject"/>
+ </implementation>
+</component>
diff --git a/vcl/vcl.headless.component b/vcl/vcl.headless.component
new file mode 100644
index 000000000..f2adfaa45
--- /dev/null
+++ b/vcl/vcl.headless.component
@@ -0,0 +1,28 @@
+<?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.frame.VCLSessionManagerClient">
+ <service name="com.sun.star.frame.SessionManagerClient"/>
+ </implementation>
+ <implementation name="vcl::FontIdentificator">
+ <service name="com.sun.star.awt.FontIdentificator"/>
+ </implementation>
+</component>
diff --git a/vcl/vcl.ios.component b/vcl/vcl.ios.component
new file mode 100644
index 000000000..65f690d2f
--- /dev/null
+++ b/vcl/vcl.ios.component
@@ -0,0 +1,31 @@
+<?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.datatransfer.clipboard.iOSClipboard">
+ <service name="com.sun.star.datatransfer.clipboard.SystemClipboard"/>
+ </implementation>
+ <implementation name="com.sun.star.frame.VCLSessionManagerClient">
+ <service name="com.sun.star.frame.SessionManagerClient"/>
+ </implementation>
+ <implementation name="vcl::FontIdentificator">
+ <service name="com.sun.star.awt.FontIdentificator"/>
+ </implementation>
+</component>
diff --git a/vcl/vcl.macosx.component b/vcl/vcl.macosx.component
new file mode 100644
index 000000000..3373b85f9
--- /dev/null
+++ b/vcl/vcl.macosx.component
@@ -0,0 +1,41 @@
+<?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"/>
+ </implementation>
+ <implementation name="com.sun.star.comp.datatransfer.dnd.OleDropTarget_V1">
+ <service name="com.sun.star.datatransfer.dnd.OleDropTarget"/>
+ </implementation>
+ <implementation name="com.sun.star.datatransfer.clipboard.AquaClipboard">
+ <service name="com.sun.star.datatransfer.clipboard.SystemClipboard"/>
+ </implementation>
+ <implementation name="com.sun.star.frame.VCLSessionManagerClient">
+ <service name="com.sun.star.frame.SessionManagerClient"/>
+ </implementation>
+ <implementation name="vcl::FontIdentificator">
+ <service name="com.sun.star.awt.FontIdentificator"/>
+ </implementation>
+ <implementation name="org.libreoffice.uitest.UITest"
+ constructor="UITest_get_implementation">
+ <service name="com.sun.star.ui.test.UITest"/>
+ </implementation>
+</component>
diff --git a/vcl/vcl.unx.component b/vcl/vcl.unx.component
new file mode 100644
index 000000000..3e84d352c
--- /dev/null
+++ b/vcl/vcl.unx.component
@@ -0,0 +1,41 @@
+<?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.datatransfer.X11ClipboardSupport">
+ <service name="com.sun.star.datatransfer.clipboard.SystemClipboard"/>
+ </implementation>
+ <implementation name="com.sun.star.datatransfer.dnd.XdndDropTarget">
+ <service name="com.sun.star.datatransfer.dnd.X11DropTarget"/>
+ </implementation>
+ <implementation name="com.sun.star.datatransfer.dnd.XdndSupport">
+ <service name="com.sun.star.datatransfer.dnd.X11DragSource"/>
+ </implementation>
+ <implementation name="com.sun.star.frame.VCLSessionManagerClient">
+ <service name="com.sun.star.frame.SessionManagerClient"/>
+ </implementation>
+ <implementation name="vcl::FontIdentificator">
+ <service name="com.sun.star.awt.FontIdentificator"/>
+ </implementation>
+ <implementation name="org.libreoffice.uitest.UITest"
+ constructor="UITest_get_implementation">
+ <service name="com.sun.star.ui.test.UITest"/>
+ </implementation>
+</component>
diff --git a/vcl/vcl.windows.component b/vcl/vcl.windows.component
new file mode 100644
index 000000000..2d5b8bb51
--- /dev/null
+++ b/vcl/vcl.windows.component
@@ -0,0 +1,32 @@
+<?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.frame.VCLSessionManagerClient">
+ <service name="com.sun.star.frame.SessionManagerClient"/>
+ </implementation>
+ <implementation name="vcl::FontIdentificator">
+ <service name="com.sun.star.awt.FontIdentificator"/>
+ </implementation>
+ <implementation name="org.libreoffice.uitest.UITest"
+ constructor="UITest_get_implementation">
+ <service name="com.sun.star.ui.test.UITest"/>
+ </implementation>
+</component>
diff --git a/vcl/win/app/saldata.cxx b/vcl/win/app/saldata.cxx
new file mode 100644
index 000000000..31fa66163
--- /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 000000000..2787797a6
--- /dev/null
+++ b/vcl/win/app/salinfo.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 <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 = tools::Rectangle( Point( aInfo.rcMonitor.left,
+ aInfo.rcMonitor.top ),
+ Size( 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 );
+ m_aMonitors.push_back( DisplayMonitor( OUString(),
+ tools::Rectangle( Point(), Size( w, h ) ) ) );
+ 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,
+ tools::Rectangle() ) );
+ }
+ }
+ 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 ) + ")";
+ }
+ }
+ }
+
+ return m_aMonitors.size() > 0;
+}
+
+unsigned int WinSalSystem::GetDisplayScreenCount()
+{
+ initMonitors();
+ return m_aMonitors.size();
+}
+
+unsigned int WinSalSystem::GetDisplayBuiltInScreen()
+{
+ initMonitors();
+ return m_nPrimary;
+}
+
+tools::Rectangle 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 tools::Rectangle();
+ }
+ 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 000000000..5ebb22226
--- /dev/null
+++ b/vcl/win/app/salinst.cxx
@@ -0,0 +1,1095 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/solarmutex.hxx>
+#include <o3tl/char16_t2wchar_t.hxx>
+
+#include <vcl/inputtypes.hxx>
+#include <vcl/opengl/OpenGLHelper.hxx>
+#include <vcl/opengl/OpenGLContext.hxx>
+#include <vcl/timer.hxx>
+#include <vclpluginapi.h>
+
+#include <opengl/salbmp.hxx>
+#include <opengl/win/gdiimpl.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>
+
+#if defined _MSC_VER
+#ifndef min
+#define min(a,b) (((a) < (b)) ? (a) : (b))
+#endif
+#ifndef max
+#define max(a,b) (((a) > (b)) ? (a) : (b))
+#endif
+#endif
+
+#include <prewin.h>
+
+#include <gdiplus.h>
+#include <shlobj.h>
+
+#include <postwin.h>
+
+void SalAbort( const OUString& rErrorText, bool )
+{
+ ImplFreeSalGDI();
+
+ 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()) );
+ }
+}
+
+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. 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 {
+ // 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);
+ }
+ 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 );
+}
+
+// SalData
+
+SalData::SalData()
+{
+ 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
+ mpMouseLeaveTimer = nullptr; // Timer for MouseLeave Test
+ 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
+ mpHDCCache = nullptr; // Cache for three DC's
+ 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
+ mbScrSvrEnabled = FALSE; // ScreenSaver enabled
+ 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();
+
+ CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); // put main thread in Single Threaded Apartment (STA)
+ static Gdiplus::GdiplusStartupInput gdiplusStartupInput;
+ Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr);
+}
+
+SalData::~SalData()
+{
+ deInitNWF();
+ SetSalData( nullptr );
+
+ CoUninitialize();
+
+ if (gdiplusToken)
+ Gdiplus::GdiplusShutdown(gdiplusToken);
+}
+
+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();
+
+ // 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");
+#if HAVE_FEATURE_SKIA
+ WinSkiaSalGraphicsImpl::prepareSkia();
+#endif
+}
+
+WinSalInstance::~WinSalInstance()
+{
+ ImplFreeSalGDI();
+ DestroyWindow( mhComWnd );
+#if HAVE_FEATURE_SKIA
+ SkiaHelper::cleanup();
+#endif
+}
+
+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;
+}
+
+bool ImplSalYield( bool bWait, bool bHandleAllCurrentEvents )
+{
+ static sal_uInt32 nLastTicks = 0;
+ MSG aMsg;
+ bool bWasMsg = false, bOneEvent = false, bWasTimeoutMsg = false;
+ ImplSVData *const pSVData = ImplGetSVData();
+ WinSalTimer* pTimer = static_cast<WinSalTimer*>( pSVData->maSchedCtx.mpSalTimer );
+ const bool bNoYieldLock = (GetSalData()->mpInstance->m_nNoYieldLock > 0);
+
+ assert( !bNoYieldLock );
+ if ( bNoYieldLock )
+ return false;
+
+ sal_uInt32 nCurTicks = 0;
+ if ( bHandleAllCurrentEvents )
+ nCurTicks = GetTickCount();
+
+ bool bHadNewerEvent = false;
+ do
+ {
+ bOneEvent = PeekMessageW( &aMsg, nullptr, 0, 0, PM_REMOVE );
+ if ( bOneEvent )
+ {
+ bWasMsg = true;
+ TranslateMessage( &aMsg );
+ LRESULT nRet = ImplSalDispatchMessage( &aMsg );
+
+ if ( !bWasTimeoutMsg )
+ bWasTimeoutMsg = (SAL_MSG_TIMER_CALLBACK == aMsg.message)
+ && static_cast<bool>( nRet );
+
+ if ( bHandleAllCurrentEvents
+ && !bHadNewerEvent && aMsg.time > nCurTicks
+ && (nLastTicks <= nCurTicks || aMsg.time < nLastTicks) )
+ bHadNewerEvent = true;
+ bOneEvent = !bHadNewerEvent;
+ }
+
+ if ( !(bHandleAllCurrentEvents && bOneEvent) )
+ 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;
+ }
+
+ if ( bHandleAllCurrentEvents )
+ nLastTicks = nCurTicks;
+
+ if ( bWait && !bWasMsg )
+ {
+ if ( GetMessageW( &aMsg, nullptr, 0, 0 ) )
+ {
+ bWasMsg = true;
+ TranslateMessage( &aMsg );
+ ImplSalDispatchMessage( &aMsg );
+ }
+ }
+
+ // we're back in the main loop after 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);
+
+ 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;
+}
+
+namespace {
+
+struct MsgRange
+{
+ UINT nStart;
+ UINT nEnd;
+};
+
+}
+
+static std::vector<MsgRange> GetOtherRanges( VclInputFlags nType )
+{
+ assert( nType != VCL_INPUT_ANY );
+
+ // this array must be kept sorted!
+ const UINT nExcludeMsgIds[] =
+ {
+ 0,
+
+ WM_MOVE, // 3
+ WM_SIZE, // 5
+ WM_PAINT, // 15
+ WM_KEYDOWN, // 256
+ WM_TIMER, // 275
+
+ WM_MOUSEFIRST, // 512
+ 513,
+ 514,
+ 515,
+ 516,
+ 517,
+ 518,
+ 519,
+ 520,
+ WM_MOUSELAST, // 521
+
+ SAL_MSG_POSTMOVE, // WM_USER+136
+ SAL_MSG_POSTCALLSIZE, // WM_USER+137
+
+ SAL_MSG_TIMER_CALLBACK, // WM_USER+162
+
+ UINT_MAX
+ };
+ const unsigned MAX_EXCL = SAL_N_ELEMENTS( nExcludeMsgIds );
+
+ bool aExcludeMsgList[ MAX_EXCL ] = { false, };
+ std::vector<MsgRange> aResult;
+
+ // set the excluded values
+ if ( !(nType & VclInputFlags::MOUSE) )
+ {
+ for ( unsigned i = 0; nExcludeMsgIds[ 6 + i ] <= WM_MOUSELAST; ++i )
+ aExcludeMsgList[ 6 + i ] = true;
+ }
+
+ if ( !(nType & VclInputFlags::KEYBOARD) )
+ aExcludeMsgList[ 4 ] = true;
+
+ if ( !(nType & VclInputFlags::PAINT) )
+ {
+ aExcludeMsgList[ 1 ] = true;
+ aExcludeMsgList[ 2 ] = true;
+ aExcludeMsgList[ 3 ] = true;
+ aExcludeMsgList[ 16 ] = true;
+ aExcludeMsgList[ 17 ] = true;
+ }
+
+ if ( !(nType & VclInputFlags::TIMER) )
+ {
+ aExcludeMsgList[ 5 ] = true;
+ aExcludeMsgList[ 18 ] = true;
+ }
+
+ // build the message ranges to check
+ MsgRange aRange = { 0, 0 };
+ bool doEnd = true;
+ for ( unsigned i = 1; i < MAX_EXCL; ++i )
+ {
+ if ( aExcludeMsgList[ i ] )
+ {
+ if ( !doEnd )
+ {
+ if ( nExcludeMsgIds[ i ] == aRange.nStart )
+ ++aRange.nStart;
+ else
+ doEnd = true;
+ }
+ if ( doEnd )
+ {
+ aRange.nEnd = nExcludeMsgIds[ i ] - 1;
+ aResult.push_back( aRange );
+ doEnd = false;
+ aRange.nStart = aRange.nEnd + 2;
+ }
+ }
+ }
+
+ if ( aRange.nStart != UINT_MAX )
+ {
+ aRange.nEnd = UINT_MAX;
+ aResult.push_back( aRange );
+ }
+
+ return aResult;
+}
+
+bool WinSalInstance::AnyInput( VclInputFlags nType )
+{
+ MSG aMsg;
+
+ if ( nType & VclInputFlags::TIMER )
+ {
+ const WinSalTimer* pTimer = static_cast<WinSalTimer*>( ImplGetSVData()->maSchedCtx.mpSalTimer );
+ if ( pTimer && pTimer->HasTimerElapsed() )
+ return true;
+ }
+
+ 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 ( PeekMessageW( &aMsg, nullptr, 0, 0, PM_NOREMOVE | PM_NOYIELD ) )
+ return true;
+ }
+ else
+ {
+ const bool bCheck_KEYBOARD (nType & VclInputFlags::KEYBOARD);
+ const bool bCheck_OTHER (nType & VclInputFlags::OTHER);
+
+ // If there is a modifier key event, it counts as OTHER
+ // Previously we were simply ignoring these events...
+ if ( bCheck_KEYBOARD || bCheck_OTHER )
+ {
+ if ( PeekMessageW( &aMsg, nullptr, WM_KEYDOWN, WM_KEYDOWN,
+ PM_NOREMOVE | PM_NOYIELD ) )
+ {
+ const bool bIsModifier = ( (aMsg.wParam == VK_SHIFT) ||
+ (aMsg.wParam == VK_CONTROL) || (aMsg.wParam == VK_MENU) );
+ if ( bCheck_KEYBOARD && !bIsModifier )
+ return true;
+ if ( bCheck_OTHER && bIsModifier )
+ return true;
+ }
+ }
+
+ // Other checks for all messages not excluded.
+ // The less we exclude, the less ranges have to be checked!
+ if ( bCheck_OTHER )
+ {
+ // TIMER and KEYBOARD are already handled, so always exclude them!
+ VclInputFlags nOtherType = nType &
+ ~VclInputFlags(VclInputFlags::KEYBOARD | VclInputFlags::TIMER);
+
+ std::vector<MsgRange> aMsgRangeList( GetOtherRanges( nOtherType ) );
+ for ( MsgRange const & aRange : aMsgRangeList )
+ if ( PeekMessageW( &aMsg, nullptr, aRange.nStart,
+ aRange.nEnd, PM_NOREMOVE | PM_NOYIELD ) )
+ return true;
+
+ // MOUSE and PAINT already handled, so skip further checks
+ return false;
+ }
+
+ if ( nType & VclInputFlags::MOUSE )
+ {
+ if ( PeekMessageW( &aMsg, nullptr, WM_MOUSEFIRST, WM_MOUSELAST,
+ PM_NOREMOVE | PM_NOYIELD ) )
+ return true;
+ }
+
+ if ( nType & VclInputFlags::PAINT )
+ {
+ if ( PeekMessageW( &aMsg, nullptr, WM_PAINT, WM_PAINT,
+ PM_NOREMOVE | PM_NOYIELD ) )
+ return true;
+
+ if ( PeekMessageW( &aMsg, nullptr, WM_SIZE, WM_SIZE,
+ PM_NOREMOVE | PM_NOYIELD ) )
+ return true;
+
+ if ( PeekMessageW( &aMsg, nullptr, SAL_MSG_POSTCALLSIZE, SAL_MSG_POSTCALLSIZE,
+ PM_NOREMOVE | PM_NOYIELD ) )
+ return true;
+
+ if ( PeekMessageW( &aMsg, nullptr, WM_MOVE, WM_MOVE,
+ PM_NOREMOVE | PM_NOYIELD ) )
+ return true;
+
+ if ( PeekMessageW( &aMsg, nullptr, SAL_MSG_POSTMOVE, SAL_MSG_POSTMOVE,
+ PM_NOREMOVE | PM_NOYIELD ) )
+ 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
+ if (OpenGLHelper::isVCLOpenGLEnabled())
+ return std::make_shared<OpenGLSalBitmap>();
+ else
+ 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(o3tl::toU(vi2.szCSDVersion)).append(" ");
+ aVer.append("Build " + OUString::number(vi2.dwBuildNumber));
+ bHaveVerFromRtlGetVersion = true;
+ }
+ }
+ }
+ if (!bHaveVerFromKernel32 && !bHaveVerFromRtlGetVersion)
+ aVer.append("unknown");
+ return aVer.makeStringAndClear();
+}
+
+std::shared_ptr<vcl::BackendCapabilities> WinSalInstance::GetBackendCapabilities()
+{
+ auto pBackendCapabilities = SalInstance::GetBackendCapabilities();
+#if HAVE_FEATURE_SKIA
+#if SKIA_USE_BITMAP32
+ if( SkiaHelper::isVCLSkiaEnabled())
+ pBackendCapabilities->mbSupportsBitmap32 = true;
+#endif
+#endif
+ return pBackendCapabilities;
+}
+
+/* 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 000000000..5619ece1f
--- /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 000000000..5a4760ad5
--- /dev/null
+++ b/vcl/win/app/saltimer.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 <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( GetSalData()->mpInstance->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 )
+{
+ SalData* pSalData = GetSalData();
+ assert( !pSalData->mpInstance || pSalData->mnAppThreadId == GetCurrentThreadId() );
+
+ // 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/gdi/DWriteTextRenderer.cxx b/vcl/win/gdi/DWriteTextRenderer.cxx
new file mode 100644
index 000000000..734b68b07
--- /dev/null
+++ b/vcl/win/gdi/DWriteTextRenderer.cxx
@@ -0,0 +1,413 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/salgdi.h>
+#include <win/saldata.hxx>
+#include <outdev.h>
+
+#include <win/DWriteTextRenderer.hxx>
+
+#include <sft.hxx>
+#include <sallayout.hxx>
+
+#include <shlwapi.h>
+#include <winver.h>
+
+#include <comphelper/windowserrorstring.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()
+ : mpD2DFactory(nullptr),
+ mpDWriteFactory(nullptr),
+ mpGdiInterop(nullptr),
+ mpRT(nullptr),
+ mRTProps(D2D1::RenderTargetProperties(D2D1_RENDER_TARGET_TYPE_DEFAULT,
+ D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED),
+ 0, 0)),
+ mpFontFace(nullptr),
+ mlfEmHeight(0.0f),
+ mhDC(nullptr),
+ meTextAntiAliasMode(D2DTextAntiAliasMode::Default)
+{
+ HRESULT hr = S_OK;
+ hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof(ID2D1Factory), nullptr, reinterpret_cast<void **>(&mpD2DFactory));
+ hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast<IUnknown**>(&mpDWriteFactory));
+ if (SUCCEEDED(hr))
+ {
+ hr = mpDWriteFactory->GetGdiInterop(&mpGdiInterop);
+ hr = CreateRenderTarget();
+ }
+ meTextAntiAliasMode = lclGetSystemTextAntiAliasMode();
+}
+
+D2DWriteTextOutRenderer::~D2DWriteTextOutRenderer()
+{
+ if (mpRT)
+ mpRT->Release();
+ if (mpGdiInterop)
+ mpGdiInterop->Release();
+ if (mpDWriteFactory)
+ mpDWriteFactory->Release();
+ if (mpD2DFactory)
+ mpD2DFactory->Release();
+}
+
+void D2DWriteTextOutRenderer::applyTextAntiAliasMode()
+{
+ 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;
+ }
+ mpRT->SetTextRenderingParams(lclSetRenderingMode(mpDWriteFactory, eRenderingMode));
+ mpRT->SetTextAntialiasMode(eTextAAMode);
+}
+
+HRESULT D2DWriteTextOutRenderer::CreateRenderTarget()
+{
+ if (mpRT)
+ {
+ mpRT->Release();
+ mpRT = nullptr;
+ }
+ HRESULT hr = CHECKHR(mpD2DFactory->CreateDCRenderTarget(&mRTProps, &mpRT));
+ if (SUCCEEDED(hr))
+ applyTextAntiAliasMode();
+ return hr;
+}
+
+void D2DWriteTextOutRenderer::changeTextAntiAliasMode(D2DTextAntiAliasMode eMode)
+{
+ if (meTextAntiAliasMode != eMode)
+ {
+ meTextAntiAliasMode = eMode;
+ applyTextAntiAliasMode();
+ }
+}
+
+bool D2DWriteTextOutRenderer::Ready() const
+{
+ return mpGdiInterop && mpRT;
+}
+
+HRESULT D2DWriteTextOutRenderer::BindDC(HDC hDC, tools::Rectangle const & rRect)
+{
+ RECT const rc = { rRect.Left(), rRect.Top(), rRect.Right(), rRect.Bottom() };
+ return CHECKHR(mpRT->BindDC(hDC, &rc));
+}
+
+bool D2DWriteTextOutRenderer::operator ()(GenericSalLayout const & rLayout, SalGraphics& rGraphics, HDC hDC)
+{
+ bool bRetry = false;
+ bool bResult = false;
+ int nCount = 0;
+ do
+ {
+ bRetry = false;
+ bResult = performRender(rLayout, rGraphics, hDC, bRetry);
+ nCount++;
+ } while (bRetry && nCount < 3);
+ return bResult;
+}
+
+bool D2DWriteTextOutRenderer::performRender(GenericSalLayout const & rLayout, SalGraphics& rGraphics, HDC hDC, bool& bRetry)
+{
+ if (!Ready())
+ return false;
+
+ HRESULT hr = S_OK;
+ hr = BindDC(hDC);
+
+ if (hr == D2DERR_RECREATE_TARGET)
+ {
+ CreateRenderTarget();
+ bRetry = true;
+ return false;
+ }
+ if (FAILED(hr))
+ {
+ // If for any reason we can't bind fallback to legacy APIs.
+ return ExTextOutRenderer()(rLayout, rGraphics, hDC);
+ }
+
+ mlfEmHeight = 0;
+ if (!GetDWriteFaceFromHDC(hDC, &mpFontFace, &mlfEmHeight))
+ return false;
+
+ const WinFontInstance& rWinFont = static_cast<const WinFontInstance&>(rLayout.GetFont());
+ float fHScale = rWinFont.getHScale();
+
+ 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;
+ Point aPos(0, 0);
+ const GlyphItem* pGlyph;
+ while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart))
+ {
+ UINT16 glyphIndices[] = { pGlyph->glyphId() };
+ FLOAT glyphAdvances[] = { static_cast<FLOAT>(pGlyph->m_nNewWidth) / fHScale };
+ DWRITE_GLYPH_OFFSET glyphOffsets[] = { { 0.0f, 0.0f }, };
+ D2D1_POINT_2F baseline = { static_cast<FLOAT>(aPos.X() - bounds.Left()) / fHScale,
+ static_cast<FLOAT>(aPos.Y() - bounds.Top()) };
+ WinFontTransformGuard aTransformGuard(mpRT, fHScale, rLayout, baseline);
+ DWRITE_GLYPH_RUN glyphs = {
+ mpFontFace,
+ mlfEmHeight,
+ 1,
+ glyphIndices,
+ glyphAdvances,
+ glyphOffsets,
+ false,
+ 0
+ };
+
+ mpRT->DrawGlyphRun(baseline, &glyphs, pBrush);
+ }
+
+ hr = CHECKHR(mpRT->EndDraw());
+ }
+
+ if (pBrush)
+ pBrush->Release();
+
+ ReleaseFont();
+
+ if (hr == D2DERR_RECREATE_TARGET)
+ {
+ CreateRenderTarget();
+ bRetry = true;
+ }
+
+ return succeeded;
+}
+
+bool D2DWriteTextOutRenderer::BindFont(HDC hDC)
+{
+ // A TextOutRender can only be bound to one font at a time, so the
+ assert(mpFontFace == nullptr);
+ if (mpFontFace)
+ {
+ ReleaseFont();
+ return false;
+ }
+
+ // Initially bind to an empty rectangle to get access to the font face,
+ // we'll update it once we've calculated a bounding rect in DrawGlyphs
+ if (FAILED(BindDC(mhDC = hDC)))
+ return false;
+
+ mlfEmHeight = 0;
+ return GetDWriteFaceFromHDC(hDC, &mpFontFace, &mlfEmHeight);
+}
+
+bool D2DWriteTextOutRenderer::ReleaseFont()
+{
+ mpFontFace->Release();
+ mpFontFace = nullptr;
+ mhDC = nullptr;
+
+ return true;
+}
+
+// GetGlyphInkBoxes
+// The inkboxes returned have their origin on the baseline, to a -ve value
+// of Top() means the glyph extends abs(Top()) many pixels above the
+// baseline, and +ve means the ink starts that many pixels below.
+std::vector<tools::Rectangle> D2DWriteTextOutRenderer::GetGlyphInkBoxes(uint16_t const * pGid, uint16_t const * pGidEnd) const
+{
+ ptrdiff_t nGlyphs = pGidEnd - pGid;
+ if (nGlyphs < 0)
+ return std::vector<tools::Rectangle>();
+
+ DWRITE_FONT_METRICS aFontMetrics;
+ mpFontFace->GetMetrics(&aFontMetrics);
+
+ std::vector<DWRITE_GLYPH_METRICS> metrics(nGlyphs);
+ if (!SUCCEEDED(CHECKHR(mpFontFace->GetDesignGlyphMetrics(pGid, nGlyphs, metrics.data()))))
+ return std::vector<tools::Rectangle>();
+
+ std::vector<tools::Rectangle> aOut(nGlyphs);
+ auto pOut = aOut.begin();
+ for (auto &m : metrics)
+ {
+ const long left = m.leftSideBearing,
+ top = m.topSideBearing - m.verticalOriginY,
+ right = m.advanceWidth - m.rightSideBearing,
+ bottom = INT32(m.advanceHeight) - m.verticalOriginY - m.bottomSideBearing;
+
+ // Scale to screen space.
+ pOut->SetLeft( std::floor(left * mlfEmHeight / aFontMetrics.designUnitsPerEm) );
+ pOut->SetTop( std::floor(top * mlfEmHeight / aFontMetrics.designUnitsPerEm) );
+ pOut->SetRight( std::ceil(right * mlfEmHeight / aFontMetrics.designUnitsPerEm) );
+ pOut->SetBottom( std::ceil(bottom * mlfEmHeight / aFontMetrics.designUnitsPerEm) );
+
+ ++pOut;
+ }
+
+ return aOut;
+}
+
+bool D2DWriteTextOutRenderer::GetDWriteFaceFromHDC(HDC hDC, IDWriteFontFace ** ppFontFace, float * lfSize) const
+{
+ bool succeeded = SUCCEEDED(CHECKHR(mpGdiInterop->CreateFontFaceFromHdc(hDC, ppFontFace)));
+
+ if (succeeded)
+ {
+ LOGFONTW aLogFont;
+ HFONT hFont = static_cast<HFONT>(::GetCurrentObject(hDC, OBJ_FONT));
+
+ GetObjectW(hFont, sizeof(LOGFONTW), &aLogFont);
+ float dpix, dpiy;
+ mpRT->GetDpi(&dpix, &dpiy);
+ *lfSize = aLogFont.lfHeight * 96.0f / dpiy;
+
+ assert(*lfSize < 0);
+ *lfSize *= -1;
+ }
+
+ return succeeded;
+}
+
+WinFontTransformGuard::WinFontTransformGuard(ID2D1RenderTarget* pRenderTarget, float fHScale,
+ const GenericSalLayout& rLayout,
+ const D2D1_POINT_2F& rBaseline)
+ : 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));
+ }
+
+ if (rLayout.GetOrientation() != 0)
+ {
+ // DWrite angle is in clockwise degrees, our orientation is in counter-clockwise 10th
+ // degrees.
+ aTransform = aTransform
+ * D2D1::Matrix3x2F::Rotation(
+ -static_cast<FLOAT>(rLayout.GetOrientation()) / 10, rBaseline);
+ }
+ mpRenderTarget->SetTransform(aTransform);
+}
+
+WinFontTransformGuard::~WinFontTransformGuard() { mpRenderTarget->SetTransform(maTransform); }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/gdi/gdiimpl.cxx b/vcl/win/gdi/gdiimpl.cxx
new file mode 100644
index 000000000..b01969272
--- /dev/null
+++ b/vcl/win/gdi/gdiimpl.cxx
@@ -0,0 +1,2710 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <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 <outdata.hxx>
+#include <win/salids.hrc>
+#include <ControlCacheKey.hxx>
+
+#if defined _MSC_VER
+#ifndef min
+#define min(a,b) (((a) < (b)) ? (a) : (b))
+#endif
+#ifndef max
+#define max(a,b) (((a) > (b)) ? (a) : (b))
+#endif
+#endif
+
+#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 SalPoint* const* pPtAry,
+ const PolyFlags* const* pFlgAry,
+ POINT* pWinPointAry,
+ BYTE* pWinFlagAry )
+{
+ sal_uLong nCurrPoly;
+ for( nCurrPoly=0; nCurrPoly<nPoly; ++nCurrPoly )
+ {
+ const POINT* pCurrPoint = reinterpret_cast<const POINT*>( *pPtAry++ );
+ const PolyFlags* pCurrFlag = *pFlgAry++;
+ const sal_uInt32 nCurrPoints = *pPoints++;
+ const bool bHaveFlagArray( pCurrFlag );
+ sal_uLong nCurrPoint;
+
+ if( nCurrPoints )
+ {
+ // start figure
+ *pWinPointAry++ = *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++ = *pCurrPoint++;
+ *pWinFlagAry++ = PT_BEZIERTO;
+
+ // control point two
+ *pWinPointAry++ = *pCurrPoint++;
+ *pWinFlagAry++ = PT_BEZIERTO;
+
+ // end point
+ *pWinPointAry++ = *pCurrPoint++;
+ *pWinFlagAry++ = PT_BEZIERTO;
+
+ nCurrPoint += 3;
+ pCurrFlag += 3;
+ continue;
+ }
+ }
+
+ // regular line point
+ *pWinPointAry++ = *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(long, long, long, 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( long nDestX, long nDestY,
+ long nSrcX, long nSrcY,
+ long nSrcWidth, 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, 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 ontop
+ }
+ 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( long nX, long nY, long nWidth,
+ 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( long nX, long nY, long nDX, long nDY )
+{
+ SAL_WARN_IF( mrParent.isPrinter(), "vcl", "No ::GetBitmap() from printer possible!" );
+
+ std::shared_ptr<WinSalBitmap> pSalBitmap;
+
+ nDX = labs( nDX );
+ nDY = labs( 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, false, false ) )
+ {
+ 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( long nX, 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( long nX, long nY, long nWidth, 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 SalPoint* 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 );
+
+ POINT const * pWinPtAry;
+ // for NT, we can handover the array directly
+ static_assert( sizeof( POINT ) == sizeof( SalPoint ), "must be the same size" );
+
+ pWinPtAry = reinterpret_cast<POINT const *>(pPtAry);
+ // for Windows 95 and its maximum number of points
+ if ( nSalFlags & SalInvert::TrackFrame )
+ {
+ if ( !Polyline( mrParent.getHDC(), pWinPtAry, static_cast<int>(nPoints) ) && (nPoints > MAX_64KSALPOINTS) )
+ Polyline( mrParent.getHDC(), pWinPtAry, MAX_64KSALPOINTS );
+ }
+ else
+ {
+ if ( !Polygon( mrParent.getHDC(), pWinPtAry, static_cast<int>(nPoints) ) && (nPoints > MAX_64KSALPOINTS) )
+ Polygon( mrParent.getHDC(), pWinPtAry, 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 ));
+}
+
+long WinSalGraphicsImpl::GetGraphicsWidth() const
+{
+ if( mrParent.gethWnd() && IsWindow( mrParent.gethWnd() ) )
+ {
+ WinSalFrame* pFrame = GetWindowPtr( mrParent.gethWnd() );
+ if( pFrame )
+ {
+ if( pFrame->maGeometry.nWidth )
+ return pFrame->maGeometry.nWidth;
+ 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;
+}
+
+bool 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 long nW(rectangle.GetWidth());
+ const long nH(rectangle.GetHeight());
+
+ if(nW && nH)
+ {
+ const long nRight(rectangle.Left() + nW);
+ const 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 );
+ }
+
+ // #i123585# retval no longer dependent of mrParent.mhRegion, see TaskId comments above
+ return true;
+}
+
+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( long nX, 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( long nX, long nY )
+{
+ DrawPixelImpl( nX, nY, mnPenColor );
+}
+
+void WinSalGraphicsImpl::drawPixel( long nX, 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( long nX1, long nY1, long nX2, 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( long nX, long nY, long nWidth, 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 SalPoint* pPtAry )
+{
+ // for NT, we can handover the array directly
+ static_assert( sizeof( POINT ) == sizeof( SalPoint ), "must be the same size" );
+
+ POINT const * pWinPtAry = reinterpret_cast<POINT const *>(pPtAry);
+
+ // for Windows 95 and its maximum number of points
+ if ( !Polyline( mrParent.getHDC(), pWinPtAry, static_cast<int>(nPoints) ) && (nPoints > MAX_64KSALPOINTS) )
+ Polyline( mrParent.getHDC(), pWinPtAry, 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 SalPoint* pPtAry )
+{
+ // for NT, we can handover the array directly
+ static_assert( sizeof( POINT ) == sizeof( SalPoint ), "must be the same size" );
+
+ POINT const * pWinPtAry = reinterpret_cast<POINT const *>(pPtAry);
+ // for Windows 95 and its maximum number of points
+ if ( !Polygon( mrParent.getHDC(), pWinPtAry, static_cast<int>(nPoints) ) && (nPoints > MAX_64KSALPOINTS) )
+ Polygon( mrParent.getHDC(), pWinPtAry, MAX_64KSALPOINTS );
+}
+
+void WinSalGraphicsImpl::drawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints,
+ PCONSTSALPOINT* 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];
+ // for NT, we can handover the array directly
+ static_assert( sizeof( POINT ) == sizeof( SalPoint ), "must be the same size" );
+ UINT n = 0;
+ for ( i = 0; i < static_cast<UINT>(nPoly); i++ )
+ {
+ nPoints = pWinPointAry[i];
+ const SalPoint* pPolyAry = pPtAry[i];
+ memcpy( pWinPointAryAry+n, pPolyAry, (nPoints-1)*sizeof(POINT) );
+ 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 SalPoint* pPtAry, const PolyFlags* pFlgAry )
+{
+ static_assert( sizeof( POINT ) == sizeof( SalPoint ), "must be the same size" );
+
+ // #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, pPtAry->mnX, pPtAry->mnY, nullptr);
+ ++pPtAry;
+ ++pFlgAry;
+
+ for(sal_uInt32 i = 1; i < nPoints; ++i)
+ {
+ if(*pFlgAry != PolyFlags::Control)
+ {
+ LineTo(hdc, pPtAry->mnX, pPtAry->mnY);
+ }
+ else if(nPoints - i > 2)
+ {
+ PolyBezierTo(hdc, reinterpret_cast<const POINT*>(pPtAry), 3);
+ i += 2;
+ pPtAry += 2;
+ pFlgAry += 2;
+ }
+
+ ++pPtAry;
+ ++pFlgAry;
+ }
+
+ return true;
+}
+
+bool WinSalGraphicsImpl::drawPolygonBezier( sal_uInt32 nPoints, const SalPoint* pPtAry, const PolyFlags* pFlgAry )
+{
+ static_assert( sizeof( POINT ) == sizeof( SalPoint ), "must be the same size" );
+
+ 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 SalPoint* const* pPtAry, const PolyFlags* const* pFlgAry )
+{
+ static_assert( sizeof( POINT ) == sizeof( SalPoint ), "must be the same size" );
+
+ 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(
+ basegfx::SystemDependentDataManager& rSystemDependentDataManager,
+ 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(
+ basegfx::SystemDependentDataManager& rSystemDependentDataManager,
+ std::shared_ptr<Gdiplus::GraphicsPath>& rpGraphicsPath,
+ bool bNoLineJoin,
+ const std::vector< double >* pStroke)
+: basegfx::SystemDependentData(rSystemDependentDataManager),
+ 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;
+}
+
+bool 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 true;
+ }
+
+ 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>(
+ ImplGetSystemDependentDataManager(),
+ pGraphicsPath,
+ false,
+ nullptr);
+ }
+
+ if(mrParent.getAntiAliasB2DDraw())
+ {
+ 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));
+
+ return true;
+}
+
+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);
+
+ for(size_t a(0); a < pStroke->size(); a++)
+ {
+ aDashArray[a] = Gdiplus::REAL((*pStroke)[a] * fFactor);
+ }
+
+ aPen.SetDashCap(Gdiplus::DashCapFlat);
+ 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>(
+ ImplGetSystemDependentDataManager(),
+ pGraphicsPath,
+ bNoLineJoin,
+ pStroke);
+ }
+ }
+
+ if(mrParent.getAntiAliasB2DDraw())
+ {
+ 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,
+ long rSrcWidth,
+ long rDestWidth,
+ long rSrcHeight,
+ 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.get())
+ {
+ 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.get())
+ {
+ 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)
+{
+ assert(dynamic_cast<const WinSalBitmap*>(&rSourceBitmap));
+ assert(!pAlphaBitmap || dynamic_cast<const WinSalBitmap*>(pAlphaBitmap));
+
+ 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.get())
+ {
+ const long nSrcWidth(aARGB->GetWidth());
+ const long nSrcHeight(aARGB->GetHeight());
+
+ if(nSrcWidth && nSrcHeight)
+ {
+ const long nDestWidth(basegfx::fround(basegfx::B2DVector(rX - rNull).getLength()));
+ const 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::drawGradient(const tools::PolyPolygon& /*rPolygon*/,
+ const Gradient& /*rGradient*/)
+{
+ return false;
+}
+
+bool WinSalGraphicsImpl::supportsOperation(OutDevSupportType eType) const
+{
+ static bool bAllowForTest(true);
+ bool bRet = false;
+
+ switch (eType)
+ {
+ case OutDevSupportType::TransparentRect:
+ bRet = mrParent.mbVirDev || mrParent.mbWindow;
+ break;
+ case OutDevSupportType::B2DDraw:
+ bRet = bAllowForTest;
+ 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 000000000..748afbcf0
--- /dev/null
+++ b/vcl/win/gdi/gdiimpl.hxx
@@ -0,0 +1,250 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_WIN_GDI_GDIIMPL_HXX
+#define INCLUDED_VCL_WIN_GDI_GDIIMPL_HXX
+
+#include <salgdiimpl.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(long nX, 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 bool setClipRegion( const vcl::Region& ) override;
+ //
+ // get the depth of the device
+ virtual sal_uInt16 GetBitCount() const override;
+
+ // get the width of the device
+ virtual 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( long nX, long nY ) override;
+ virtual void drawPixel( long nX, long nY, Color nColor ) override;
+
+ virtual void drawLine( long nX1, long nY1, long nX2, long nY2 ) override;
+
+ virtual void drawRect( long nX, long nY, long nWidth, long nHeight ) override;
+
+ virtual void drawPolyLine( sal_uInt32 nPoints, const SalPoint* pPtAry ) override;
+
+ virtual void drawPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry ) override;
+
+ virtual void drawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints, PCONSTSALPOINT* pPtAry ) override;
+
+ virtual bool 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 SalPoint* pPtAry,
+ const PolyFlags* pFlgAry ) override;
+
+ virtual bool drawPolygonBezier(
+ sal_uInt32 nPoints,
+ const SalPoint* pPtAry,
+ const PolyFlags* pFlgAry ) override;
+
+ virtual bool drawPolyPolygonBezier(
+ sal_uInt32 nPoly,
+ const sal_uInt32* pPoints,
+ const SalPoint* const* pPtAry,
+ const PolyFlags* const* pFlgAry ) override;
+
+ // CopyArea --> No RasterOp, but ClipRegion
+ virtual void copyArea(
+ long nDestX, long nDestY,
+ long nSrcX, long nSrcY,
+ long nSrcWidth, 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( long nX, long nY, long nWidth, long nHeight ) override;
+
+ virtual Color getPixel( long nX, long nY ) override;
+
+ // invert --> ClipRegion (only Windows or VirDevs)
+ virtual void invert(
+ long nX, long nY,
+ long nWidth, long nHeight,
+ SalInvert nFlags) override;
+
+ virtual void invert( sal_uInt32 nPoints, const SalPoint* pPtAry, SalInvert nFlags ) override;
+
+ virtual bool drawEPS(
+ long nX, long nY,
+ long nWidth, 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) 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(
+ long nX, long nY,
+ long nWidth, long nHeight,
+ sal_uInt8 nTransparency ) override;
+
+
+ virtual bool drawGradient(const tools::PolyPolygon& rPolygon,
+ const Gradient& rGradient) override;
+
+ virtual bool supportsOperation(OutDevSupportType eType) const override;
+};
+
+#endif // INCLUDED_VCL_WIN_GDI_GDIIMPL_HXX
+
+/* 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 000000000..f2b21a666
--- /dev/null
+++ b/vcl/win/gdi/salbmp.cxx
@@ -0,0 +1,1067 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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>
+
+#if defined _MSC_VER
+#ifndef min
+#define min(a,b) (((a) < (b)) ? (a) : (b))
+#endif
+#ifndef max
+#define max(a,b) (((a) > (b)) ? (a) : (b))
+#endif
+#endif
+
+#include <prewin.h>
+#include <gdiplus.h>
+#include <postwin.h>
+
+#if defined _MSC_VER
+#undef min
+#undef max
+#endif
+
+static void ImplSetPixel4( sal_uInt8* pScanline, long nX, const BYTE cIndex )
+{
+ BYTE& rByte = pScanline[ nX >> 1 ];
+
+ if ( nX & 1 )
+ {
+ rByte &= 0xf0;
+ rByte |= cIndex & 0x0f;
+ }
+ else
+ {
+ rByte &= 0x0f;
+ rByte |= cIndex << 4;
+ }
+}
+
+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(
+ basegfx::SystemDependentDataManager& rSystemDependentDataManager,
+ 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(
+ basegfx::SystemDependentDataManager& rSystemDependentDataManager,
+ const std::shared_ptr<Gdiplus::Bitmap>& rGdiPlusBitmap,
+ const WinSalBitmap* pAssociatedAlpha)
+: basegfx::SystemDependentData(rSystemDependentDataManager),
+ 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.reset(const_cast< WinSalBitmap* >(this)->ImplCreateGdiPlusBitmap(*pAlphaSource));
+ pAssociatedAlpha = pAlphaSource;
+ }
+ else
+ {
+ aRetval.reset(const_cast< WinSalBitmap* >(this)->ImplCreateGdiPlusBitmap());
+ pAssociatedAlpha = nullptr;
+ }
+
+ // add to buffering mechanism
+ addOrReplaceSystemDependentData<SystemDependentData_GdiPlusBitmap>(
+ ImplGetSystemDependentDataManager(),
+ aRetval,
+ pAssociatedAlpha);
+ }
+
+ return aRetval;
+}
+
+Gdiplus::Bitmap* WinSalBitmap::ImplCreateGdiPlusBitmap()
+{
+ Gdiplus::Bitmap* pRetval(nullptr);
+ WinSalBitmap* pSalRGB = this;
+ WinSalBitmap* pExtraWinSalRGB = nullptr;
+
+ if(!pSalRGB->ImplGethDIB())
+ {
+ // we need DIB for success with AcquireBuffer, create a replacement WinSalBitmap
+ pExtraWinSalRGB = new WinSalBitmap();
+ pExtraWinSalRGB->Create(*pSalRGB, pSalRGB->GetBitCount());
+ pSalRGB = pExtraWinSalRGB;
+ }
+
+ BitmapBuffer* pRGB = pSalRGB->AcquireBuffer(BitmapAccessMode::Read);
+ std::unique_ptr<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.get();
+ }
+
+ 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 = new 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
+ {
+ delete pRetval;
+ pRetval = nullptr;
+ }
+ }
+
+ 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);
+ }
+
+ if(pExtraWinSalRGB)
+ {
+ delete pExtraWinSalRGB;
+ }
+
+ return pRetval;
+}
+
+Gdiplus::Bitmap* WinSalBitmap::ImplCreateGdiPlusBitmap(const WinSalBitmap& rAlphaSource)
+{
+ Gdiplus::Bitmap* pRetval(nullptr);
+ WinSalBitmap* pSalRGB = this;
+ WinSalBitmap* pExtraWinSalRGB = nullptr;
+
+ if(!pSalRGB->ImplGethDIB())
+ {
+ // we need DIB for success with AcquireBuffer, create a replacement WinSalBitmap
+ pExtraWinSalRGB = new WinSalBitmap();
+ pExtraWinSalRGB->Create(*pSalRGB, pSalRGB->GetBitCount());
+ pSalRGB = pExtraWinSalRGB;
+ }
+
+ BitmapBuffer* pRGB = pSalRGB->AcquireBuffer(BitmapAccessMode::Read);
+ std::unique_ptr<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.get();
+ }
+
+ WinSalBitmap* pSalA = const_cast< WinSalBitmap* >(&rAlphaSource);
+ WinSalBitmap* pExtraWinSalA = nullptr;
+
+ if(!pSalA->ImplGethDIB())
+ {
+ // we need DIB for success with AcquireBuffer, create a replacement WinSalBitmap
+ pExtraWinSalA = new WinSalBitmap();
+ pExtraWinSalA->Create(*pSalA, pSalA->GetBitCount());
+ pSalA = pExtraWinSalA;
+ }
+
+ BitmapBuffer* pA = pSalA->AcquireBuffer(BitmapAccessMode::Read);
+ std::unique_ptr<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.get();
+ }
+
+ 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 = new 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++ = 0xff - *pSrcA++;
+ }
+
+ pSrcRGB += nExtraRGB;
+ pSrcA += nExtraA;
+ }
+
+ pRetval->UnlockBits(&aGdiPlusBitmapData);
+ }
+ else
+ {
+ delete pRetval;
+ pRetval = nullptr;
+ }
+ }
+
+ 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);
+ }
+
+ if(pExtraWinSalA)
+ {
+ delete pExtraWinSalA;
+ }
+
+ 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);
+ }
+
+ if(pExtraWinSalRGB)
+ {
+ delete pExtraWinSalRGB;
+ }
+
+ return pRetval;
+}
+
+bool WinSalBitmap::Create( HANDLE hBitmap, bool bDIB, bool bCopyHandle )
+{
+ bool bRet = true;
+
+ if( bDIB )
+ mhDIB = static_cast<HGLOBAL>( bCopyHandle ? ImplCopyDIBOrDDB( hBitmap, true ) : hBitmap );
+ else
+ mhDDB = static_cast<HBITMAP>( bCopyHandle ? ImplCopyDIBOrDDB( hBitmap, false ) : 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, sal_uInt16 nBitCount, const BitmapPalette& rPal )
+{
+ bool bRet = false;
+
+ mhDIB = ImplCreateDIB( rSize, nBitCount, rPal );
+
+ if( mhDIB )
+ {
+ maSize = rSize;
+ mnBitCount = nBitCount;
+ 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, sal_uInt16 nNewBitCount )
+{
+ bool bRet = false;
+
+ const WinSalBitmap& rSalBmp = static_cast<const WinSalBitmap&>(rSSalBmp);
+
+ if( rSalBmp.mhDDB )
+ {
+ mhDIB = ImplCreateDIB( rSalBmp.maSize, nNewBitCount, 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 = nNewBitCount;
+ 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.get() ) {
+ css::uno::Sequence< css::uno::Any > args;
+
+ if( xFastPropertySet->getFastPropertyValue(bMask ? 2 : 1) >>= args ) {
+ sal_Int64 aHBmp64;
+
+ if( args[0] >>= aHBmp64 ) {
+ return Create( HBITMAP(aHBmp64), false, false );
+ }
+ }
+ }
+ 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, sal_uInt16 nBits, const BitmapPalette& rPal )
+{
+ SAL_WARN_IF( nBits != 1 && nBits != 4 && nBits != 8 && nBits != 24, "vcl", "Unsupported BitCount!" );
+
+ HGLOBAL hDIB = nullptr;
+
+ if( rSize.IsEmpty() )
+ return hDIB;
+
+ // 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
+ const sal_uInt16 nColors = (nBits <= 8) ? (1 << nBits) : 0;
+ 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*/ )
+{
+ BitmapBuffer* pBuffer = nullptr;
+
+ if( mhDIB )
+ {
+ PBITMAPINFO pBI = static_cast<PBITMAPINFO>(GlobalLock( mhDIB ));
+ PBITMAPINFOHEADER pBIH = &pBI->bmiHeader;
+
+ if( ( pBIH->biCompression == BI_RLE4 ) || ( pBIH->biCompression == BI_RLE8 ) )
+ {
+ Size aSizePix( pBIH->biWidth, pBIH->biHeight );
+ HGLOBAL hNewDIB = ImplCreateDIB( aSizePix, pBIH->biBitCount, BitmapPalette() );
+
+ if( hNewDIB )
+ {
+ PBITMAPINFO pNewBI = static_cast<PBITMAPINFO>(GlobalLock( hNewDIB ));
+ PBITMAPINFOHEADER pNewBIH = &pNewBI->bmiHeader;
+ const sal_uInt16 nColorCount = ImplGetDIBColorCount( hNewDIB );
+ const sal_uLong nOffset = pBI->bmiHeader.biSize + nColorCount * sizeof( RGBQUAD );
+ BYTE* pOldBits = reinterpret_cast<PBYTE>(pBI) + nOffset;
+ BYTE* pNewBits = reinterpret_cast<PBYTE>(pNewBI) + nOffset;
+
+ memcpy( pNewBI, pBI, nOffset );
+ pNewBIH->biCompression = 0;
+ ImplDecodeRLEBuffer( pOldBits, pNewBits, aSizePix, pBIH->biCompression == BI_RLE4 );
+
+ GlobalUnlock( mhDIB );
+ GlobalFree( mhDIB );
+ mhDIB = hNewDIB;
+ pBI = pNewBI;
+ pBIH = pNewBIH;
+ }
+ }
+
+ if( pBIH->biPlanes == 1 )
+ {
+ pBuffer = new BitmapBuffer;
+
+ pBuffer->mnFormat = pBIH->biBitCount == 1 ? ScanlineFormat::N1BitMsbPal :
+ pBIH->biBitCount == 4 ? ScanlineFormat::N4BitMsnPal :
+ 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 );
+ delete pBuffer;
+ pBuffer = nullptr;
+ }
+ }
+ else
+ GlobalUnlock( mhDIB );
+ }
+
+ return pBuffer;
+}
+
+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();
+}
+
+void WinSalBitmap::ImplDecodeRLEBuffer( const BYTE* pSrcBuf, BYTE* pDstBuf,
+ const Size& rSizePixel, bool bRLE4 )
+{
+ sal_uInt8 const * pRLE = pSrcBuf;
+ sal_uInt8* pDIB = pDstBuf;
+ sal_uInt8* pRow = pDstBuf;
+ sal_uLong nWidthAl = AlignedWidth4Bytes( rSizePixel.Width() * ( bRLE4 ? 4UL : 8UL ) );
+ sal_uInt8* pLast = pDIB + rSizePixel.Height() * nWidthAl - 1;
+ sal_uLong nCountByte;
+ sal_uLong nRunByte;
+ sal_uLong i;
+ BYTE cTmp;
+ bool bEndDecoding = false;
+
+ if( pRLE && pDIB )
+ {
+ sal_uLong nX = 0;
+ do
+ {
+ if( ( nCountByte = *pRLE++ ) == 0 )
+ {
+ nRunByte = *pRLE++;
+
+ if( nRunByte > 2 )
+ {
+ if( bRLE4 )
+ {
+ nCountByte = nRunByte >> 1;
+
+ for( i = 0; i < nCountByte; i++ )
+ {
+ cTmp = *pRLE++;
+ ImplSetPixel4( pDIB, nX++, cTmp >> 4 );
+ ImplSetPixel4( pDIB, nX++, cTmp & 0x0f );
+ }
+
+ if( nRunByte & 1 )
+ ImplSetPixel4( pDIB, nX++, *pRLE++ >> 4 );
+
+ if( ( ( nRunByte + 1 ) >> 1 ) & 1 )
+ pRLE++;
+ }
+ else
+ {
+ memcpy( &pDIB[ nX ], pRLE, nRunByte );
+ pRLE += nRunByte;
+ nX += nRunByte;
+
+ if( nRunByte & 1 )
+ pRLE++;
+ }
+ }
+ else if( !nRunByte )
+ {
+ pDIB = ( pRow += nWidthAl );
+ nX = 0;
+ }
+ else if( nRunByte == 1 )
+ bEndDecoding = true;
+ else
+ {
+ nX += *pRLE++;
+ pDIB = ( pRow += ( *pRLE++ ) * nWidthAl );
+ }
+ }
+ else
+ {
+ cTmp = *pRLE++;
+
+ if( bRLE4 )
+ {
+ nRunByte = nCountByte >> 1;
+
+ for( i = 0; i < nRunByte; i++ )
+ {
+ ImplSetPixel4( pDIB, nX++, cTmp >> 4 );
+ ImplSetPixel4( pDIB, nX++, cTmp & 0x0f );
+ }
+
+ if( nCountByte & 1 )
+ ImplSetPixel4( pDIB, nX++, cTmp >> 4 );
+ }
+ else
+ {
+ for( i = 0; i < nCountByte; i++ )
+ pDIB[ nX++ ] = cTmp;
+ }
+ }
+ }
+ while( !bEndDecoding && ( pDIB <= pLast ) );
+ }
+}
+
+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;
+}
+
+/* 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 000000000..1ed293888
--- /dev/null
+++ b/vcl/win/gdi/salfont.cxx
@@ -0,0 +1,1766 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <config_folders.h>
+
+#include <algorithm>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <set>
+#include <string.h>
+#include <svsys.h>
+#include <vector>
+
+#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 <vcl/opengl/OpenGLWrapper.hxx>
+#include <comphelper/scopeguard.hxx>
+
+#include <fontsubset.hxx>
+#include <outdev.h>
+#include <PhysicalFontCollection.hxx>
+#include <PhysicalFontFace.hxx>
+#include <sft.hxx>
+#include <win/saldata.hxx>
+#include <win/salgdi.h>
+#include <win/winlayout.hxx>
+#include <win/wingdiimpl.hxx>
+#include <impfontcharmap.hxx>
+#include <impfontmetricdata.hxx>
+#include <impglyphitem.hxx>
+
+using namespace vcl;
+
+static FIXED FixedFromDouble( double d )
+{
+ const long l = static_cast<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;
+}
+
+namespace {
+
+// raw font data with a scoped lifetime
+class RawFontData
+{
+public:
+ explicit RawFontData( HDC, DWORD nTableTag=0 );
+ const unsigned char* get() const { return mpRawBytes.get(); }
+ const unsigned char* steal() { return mpRawBytes.release(); }
+ int size() const { return mnByteCount; }
+
+private:
+ std::unique_ptr<unsigned char[]> mpRawBytes;
+ unsigned mnByteCount;
+};
+
+}
+
+RawFontData::RawFontData( HDC hDC, DWORD nTableTag )
+: mnByteCount( 0 )
+{
+ // get required size in bytes
+ mnByteCount = ::GetFontData( hDC, nTableTag, 0, nullptr, 0 );
+ if (mnByteCount == GDI_ERROR)
+ mnByteCount = 0;
+ if (!mnByteCount)
+ return;
+
+ // allocate the array
+ mpRawBytes.reset(new unsigned char[ mnByteCount ]);
+
+ // get raw data in chunks small enough for GetFontData()
+ unsigned nRawDataOfs = 0;
+ DWORD nMaxChunkSize = 0x100000;
+ for(;;)
+ {
+ // calculate remaining raw data to get
+ DWORD nFDGet = mnByteCount - nRawDataOfs;
+ if( nFDGet <= 0 )
+ break;
+ // #i56745# limit GetFontData requests
+ if( nFDGet > nMaxChunkSize )
+ nFDGet = nMaxChunkSize;
+ const DWORD nFDGot = ::GetFontData( hDC, nTableTag, nRawDataOfs,
+ mpRawBytes.get() + nRawDataOfs, nFDGet );
+ if( !nFDGot )
+ break;
+ else if( nFDGot != GDI_ERROR )
+ nRawDataOfs += nFDGot;
+ else
+ {
+ // was the chunk too big? reduce it
+ nMaxChunkSize /= 2;
+ if( nMaxChunkSize < 0x10000 )
+ break;
+ }
+ }
+
+ // cleanup if the raw data is incomplete
+ if( nRawDataOfs != mnByteCount )
+ {
+ mpRawBytes.reset();
+ // mnByteCount must correspond to mpRawBytes length
+ SAL_WARN( "vcl", "Raw data of font is incomplete: " << nRawDataOfs << " byte(s) found whereas " << mnByteCount << " byte(s) expected!" );
+ mnByteCount = 0;
+ }
+}
+
+// platform specific font substitution hooks for glyph fallback enhancement
+
+namespace {
+
+class WinPreMatchFontSubstititution
+: public ImplPreMatchFontSubstitution
+{
+public:
+ bool FindFontSubstitute(FontSelectPattern&) const override;
+};
+
+class WinGlyphFallbackSubstititution
+: public ImplGlyphFallbackFontSubstitution
+{
+public:
+ explicit WinGlyphFallbackSubstititution()
+ : mhDC(GetDC(nullptr))
+ {
+ };
+
+ ~WinGlyphFallbackSubstititution() override
+ {
+ ReleaseDC(nullptr, mhDC);
+ };
+
+ bool FindFontSubstitute(FontSelectPattern&, LogicalFontInstance* pLogicalFont, OUString& rMissingChars) const override;
+private:
+ HDC mhDC;
+ bool HasMissingChars(PhysicalFontFace*, OUString& rMissingChars) const;
+};
+
+}
+
+// does a font face hold the given missing characters?
+bool WinGlyphFallbackSubstititution::HasMissingChars(PhysicalFontFace* pFace, OUString& rMissingChars) const
+{
+ WinFontFace* pWinFont = static_cast< WinFontFace* >(pFace);
+ FontCharMapRef xFontCharMap = pWinFont->GetFontCharMap();
+ if( !xFontCharMap.is() )
+ {
+ // construct a Size structure as the parameter of constructor of class FontSelectPattern
+ const Size aSize( pFace->GetWidth(), pFace->GetHeight() );
+ // create a FontSelectPattern object for getting s LOGFONT
+ const FontSelectPattern aFSD( *pFace, aSize, static_cast<float>(aSize.Height()), 0, false );
+ // construct log font
+ LOGFONTW aLogFont;
+ ImplGetLogFontFromFontSelect( mhDC, aFSD, pFace, aLogFont );
+
+ // create HFONT from log font
+ HFONT hNewFont = ::CreateFontIndirectW( &aLogFont );
+ // select the new font into device
+ HFONT hOldFont = ::SelectFont( mhDC, hNewFont );
+
+ // read CMAP table to update their xFontCharMap
+ pWinFont->UpdateFromHDC( mhDC );
+
+ // cleanup temporary font
+ ::SelectFont( mhDC, hOldFont );
+ ::DeleteFont( hNewFont );
+
+ // get the new charmap
+ xFontCharMap = pWinFont->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;
+}
+
+namespace
+{
+ //used by 2-level font fallback
+ PhysicalFontFamily* findDevFontListByLocale(const 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
+static 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(FontSelectPattern& rFontSelData) const
+{
+ if (rFontSelData.IsSymbolFont() || IsStarSymbol(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(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 PhysicalFontCollection* pFontCollection = ImplGetSVData()->maGDIData.mxScreenFontList.get();
+ PhysicalFontFamily* pFontFamily = findDevFontListByLocale(*pFontCollection, aLanguageTag);
+ if( pFontFamily )
+ {
+ 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 )
+ {
+ 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<ImplDeviceFontList> pTestFontList = pFontCollection->GetDeviceFontList();
+ // 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 )
+ {
+ PhysicalFontFace* pFace = pTestFontList->Get( i );
+ bFound = HasMissingChars( pFace, rMissingChars );
+ if( !bFound )
+ continue;
+ rFontSelData.maSearchName = pFace->GetFamilyName();
+ break;
+ }
+
+ return bFound;
+}
+
+namespace {
+
+struct ImplEnumInfo
+{
+ HDC mhDC;
+ 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.SetSymbolFlag(rLogFont.lfCharSet == SYMBOL_CHARSET);
+
+ // get the font face name
+ aDFA.SetFamilyName(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(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;
+}
+
+
+static rtl::Reference<WinFontFace> ImplLogMetricToDevFontDataW( const ENUMLOGFONTEXW* pLogFont,
+ const NEWTEXTMETRICW* pMetric)
+{
+ rtl::Reference<WinFontFace> pData = new WinFontFace(
+ WinFont2DevFontAttributes(*pLogFont, *pMetric),
+ pLogFont->elfLogFont.lfCharSet,
+ pMetric->tmPitchAndFamily );
+
+ return pData;
+}
+
+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 ) );
+
+ long nFontHeight = rLogFont.lfHeight;
+ if ( nFontHeight < 0 )
+ nFontHeight = -nFontHeight;
+ long nDPIY = GetDeviceCaps( hDC, LOGPIXELSY );
+ if( !nDPIY )
+ nDPIY = 600;
+ nFontHeight *= 72;
+ nFontHeight += nDPIY/2;
+ nFontHeight /= nDPIY;
+ rFont.SetFontSize( Size( 0, nFontHeight ) );
+ rFont.SetOrientation( static_cast<short>(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 FontAttributes& rDFS,
+ BYTE eWinCharSet, BYTE nPitchAndFamily )
+: PhysicalFontFace( rDFS ),
+ mnId( 0 ),
+ mbFontCapabilitiesRead( false ),
+ meWinCharSet( eWinCharSet ),
+ mnPitchAndFamily( nPitchAndFamily ),
+ mbAliasSymbolsHigh( false ),
+ mbAliasSymbolsLow( false )
+{
+ if( eWinCharSet == SYMBOL_CHARSET )
+ {
+ if( (nPitchAndFamily & TMPF_TRUETYPE) != 0 )
+ {
+ // truetype fonts need their symbols as U+F0xx
+ mbAliasSymbolsHigh = true;
+ }
+ else if( (nPitchAndFamily & (TMPF_VECTOR|TMPF_DEVICE))
+ == (TMPF_VECTOR|TMPF_DEVICE) )
+ {
+ // scalable device fonts (e.g. builtin printer fonts)
+ // need their symbols as U+00xx
+ mbAliasSymbolsLow = true;
+ }
+ else if( (nPitchAndFamily & (TMPF_VECTOR|TMPF_TRUETYPE)) == 0 )
+ {
+ // bitmap fonts need their symbols as U+F0xx
+ mbAliasSymbolsHigh = true;
+ }
+ }
+}
+
+WinFontFace::~WinFontFace()
+{
+ mxUnicodeMap.clear();
+}
+
+sal_IntPtr WinFontFace::GetFontId() const
+{
+ return mnId;
+}
+
+rtl::Reference<LogicalFontInstance> WinFontFace::CreateFontInstance(const FontSelectPattern& rFSD) const
+{
+ return new WinFontInstance(*this, rFSD);
+}
+
+static DWORD CalcTag( const char p[5]) { return (p[0]+(p[1]<<8)+(p[2]<<16)+(p[3]<<24)); }
+
+void WinFontFace::UpdateFromHDC( HDC hDC ) const
+{
+ // short circuit if already initialized
+ if( mxUnicodeMap.is() )
+ return;
+
+ ReadCmapTable( hDC );
+ GetFontCapabilities( hDC );
+}
+
+FontCharMapRef WinFontFace::GetFontCharMap() const
+{
+ return mxUnicodeMap;
+}
+
+bool WinFontFace::GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const
+{
+ rFontCapabilities = maFontCapabilities;
+ return rFontCapabilities.oUnicodeRange || rFontCapabilities.oCodePageRange;
+}
+
+void WinFontFace::ReadCmapTable( HDC hDC ) const
+{
+ if( mxUnicodeMap.is() )
+ return;
+
+ bool bIsSymbolFont = (meWinCharSet == SYMBOL_CHARSET);
+ // get the CMAP table from the font which is selected into the DC
+ const DWORD nCmapTag = CalcTag( "cmap" );
+ const RawFontData aRawFontData( hDC, nCmapTag );
+ // parse the CMAP table if available
+ if( aRawFontData.get() ) {
+ CmapResult aResult;
+ ParseCMAP( aRawFontData.get(), aRawFontData.size(), aResult );
+ aResult.mbSymbolic = bIsSymbolFont;
+ if( aResult.mnRangeCount > 0 )
+ {
+ FontCharMapRef pUnicodeMap(new FontCharMap(aResult));
+ mxUnicodeMap = pUnicodeMap;
+ }
+ }
+
+ if( !mxUnicodeMap.is() )
+ {
+ mxUnicodeMap = FontCharMap::GetDefaultMap( bIsSymbolFont );
+ }
+}
+
+void WinFontFace::GetFontCapabilities( HDC hDC ) const
+{
+ // read this only once per font
+ if( mbFontCapabilitiesRead )
+ return;
+
+ mbFontCapabilitiesRead = true;
+
+ // OS/2 table
+ const DWORD OS2Tag = CalcTag( "OS/2" );
+ DWORD nLength = ::GetFontData( hDC, OS2Tag, 0, nullptr, 0 );
+ if( (nLength != GDI_ERROR) && nLength )
+ {
+ std::vector<unsigned char> aTable( nLength );
+ unsigned char* pTable = aTable.data();
+ ::GetFontData( hDC, OS2Tag, 0, pTable, nLength );
+ vcl::getTTCoverage(maFontCapabilities.oUnicodeRange, maFontCapabilities.oCodePageRange, pTable, nLength);
+ }
+}
+
+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( HDC hDC,
+ const FontSelectPattern& rFont,
+ const 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.IsSymbolFont() ? SYMBOL_CHARSET : DEFAULT_CHARSET;
+ rLogFont.lfPitchAndFamily = ImplPitchToWin( rFont.GetPitch() )
+ | ImplFamilyToWin( rFont.GetFamilyType() );
+ }
+
+ static BYTE nDefaultQuality = NONANTIALIASED_QUALITY;
+ if (nDefaultQuality == NONANTIALIASED_QUALITY)
+ {
+ if (OpenGLWrapper::isVCLOpenGLEnabled())
+ nDefaultQuality = ANTIALIASED_QUALITY;
+ else
+ nDefaultQuality = DEFAULT_QUALITY;
+ }
+
+ 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;
+ rLogFont.lfOrientation = rLogFont.lfEscapement;
+ rLogFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
+ rLogFont.lfQuality = nDefaultQuality;
+ rLogFont.lfOutPrecision = OUT_TT_PRECIS;
+ if ( rFont.mnOrientation )
+ rLogFont.lfClipPrecision |= CLIP_LH_ANGLES;
+
+ // disable antialiasing if requested
+ if ( rFont.mbNonAntialiased )
+ rLogFont.lfQuality = NONANTIALIASED_QUALITY;
+
+ // select vertical mode if requested and available
+ if ( rFont.mbVertical && nNameLen )
+ {
+ // vertical fonts start with an '@'
+ memmove( &rLogFont.lfFaceName[1], &rLogFont.lfFaceName[0],
+ sizeof(rLogFont.lfFaceName)-sizeof(rLogFont.lfFaceName[0]) );
+ rLogFont.lfFaceName[0] = '@';
+
+ // check availability of vertical mode for this font
+ bool bAvailable = false;
+ EnumFontFamiliesExW( hDC, &rLogFont, SalEnumQueryFontProcExW,
+ reinterpret_cast<LPARAM>(&bAvailable), 0 );
+
+ if( !bAvailable )
+ {
+ // restore non-vertical name if not vertical mode isn't available
+ memcpy( &rLogFont.lfFaceName[0], aName.getStr(), nNameLen*sizeof(wchar_t) );
+ rLogFont.lfFaceName[nNameLen] = '\0';
+ // keep it upright and create the font for sideway glyphs later.
+ rLogFont.lfEscapement = rLogFont.lfEscapement - 2700;
+ rLogFont.lfOrientation = rLogFont.lfEscapement;
+ }
+ }
+}
+
+HFONT WinSalGraphics::ImplDoSetFont(FontSelectPattern const & i_rFont,
+ const PhysicalFontFace * i_pFontFace,
+ HFONT& o_rOldFont)
+{
+ HFONT hNewFont = nullptr;
+
+ LOGFONTW aLogFont;
+ ImplGetLogFontFromFontSelect( getHDC(), i_rFont, i_pFontFace, aLogFont );
+
+ 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( getHDC(), hNewFont );
+
+ TEXTMETRICW aTextMetricW;
+ if( !::GetTextMetricsW( getHDC(), &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( getHDC(), hNewFont2 );
+ DeleteFont( hNewFont );
+ hNewFont = hNewFont2;
+ }
+
+ if( hdcScreen )
+ ::ReleaseDC( nullptr, hdcScreen );
+
+ return hNewFont;
+}
+
+void WinSalGraphics::SetFont(LogicalFontInstance* pFont, int nFallbackLevel)
+{
+ // return early if there is no new font
+ if( !pFont )
+ {
+ if (!mpWinFontEntry[nFallbackLevel].is())
+ return;
+
+ // select original DC font
+ assert(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;
+ }
+
+ // now the font is live => update font face
+ const WinFontFace* pFontFace = pFontInstance->GetFontFace();
+ pFontFace->UpdateFromHDC(getHDC());
+}
+
+void WinSalGraphics::GetFontMetric( ImplFontMetricDataRef& 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(o3tl::toU(aFaceName));
+
+ rxFontMetric->SetMinKashida(pFontInstance->GetKashidaWidth());
+ rxFontMetric->ImplCalcLineSpacing(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->SetSymbolFlag(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(static_cast<int>(pFontInstance->GetScale() * 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 = ImplLogMetricToDevFontDataW(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(const OUString& 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(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( 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()
+{
+ WinSalGraphicsImplBase* pImpl = dynamic_cast<WinSalGraphicsImplBase*>(GetImpl());
+ assert(pImpl != nullptr);
+ pImpl->ClearDevFontCache();
+ ImplReleaseTempFonts(*GetSalData(), false);
+}
+
+bool WinFontInstance::ImplGetGlyphBoundRect(sal_GlyphId nId, tools::Rectangle& rRect, bool) const
+{
+ 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); });
+ const float fFontScale = GetScale();
+
+ // use unity matrix
+ MAT2 aMat;
+ aMat.eM11 = aMat.eM22 = FixedFromDouble( 1.0 );
+ aMat.eM12 = aMat.eM21 = FixedFromDouble( 0.0 );
+
+ UINT nGGOFlags = GGO_METRICS;
+ nGGOFlags |= GGO_GLYPH_INDEX;
+
+ GLYPHMETRICS aGM;
+ aGM.gmptGlyphOrigin.x = aGM.gmptGlyphOrigin.y = 0;
+ aGM.gmBlackBoxX = aGM.gmBlackBoxY = 0;
+ DWORD nSize = ::GetGlyphOutlineW(hDC, nId, nGGOFlags, &aGM, 0, nullptr, &aMat);
+ if (nSize == GDI_ERROR)
+ return false;
+
+ rRect = tools::Rectangle( Point( +aGM.gmptGlyphOrigin.x, -aGM.gmptGlyphOrigin.y ),
+ Size( aGM.gmBlackBoxX, aGM.gmBlackBoxY ) );
+ rRect.SetLeft(static_cast<int>( fFontScale * rRect.Left() ));
+ rRect.SetRight(static_cast<int>( fFontScale * rRect.Right() ) + 1);
+ rRect.SetTop(static_cast<int>( fFontScale * rRect.Top() ));
+ rRect.SetBottom(static_cast<int>( fFontScale * rRect.Bottom() ) + 1);
+ return true;
+}
+
+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;
+
+ long nX = IntTimes256FromFixed( pHeader->pfxStart.x );
+ 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(GetScale()/256);
+ rB2DPolyPoly.transform(basegfx::utils::createScaleB2DHomMatrix(fFactor, fFactor));
+ }
+
+ return true;
+}
+
+class ScopedFont
+{
+public:
+ explicit ScopedFont(WinSalGraphics & rData);
+
+ ~ScopedFont();
+
+private:
+ WinSalGraphics & m_rData;
+ HFONT m_hOrigFont;
+};
+
+ScopedFont::ScopedFont(WinSalGraphics & rData): m_rData(rData), m_hOrigFont(nullptr)
+{
+ if (m_rData.mpWinFontEntry[0])
+ {
+ m_hOrigFont = m_rData.mpWinFontEntry[0]->GetHFONT();
+ m_rData.mpWinFontEntry[0]->SetHFONT(nullptr);
+ }
+}
+
+ScopedFont::~ScopedFont()
+{
+ if( m_hOrigFont )
+ {
+ // restore original font, destroy temporary font
+ HFONT hTempFont = m_rData.mpWinFontEntry[0]->GetHFONT();
+ m_rData.mpWinFontEntry[0]->SetHFONT(m_hOrigFont);
+ SelectObject( m_rData.getHDC(), m_hOrigFont );
+ DeleteObject( hTempFont );
+ }
+}
+
+namespace {
+
+class ScopedTrueTypeFont
+{
+public:
+ ScopedTrueTypeFont(): m_pFont(nullptr) {}
+
+ ~ScopedTrueTypeFont();
+
+ SFErrCodes open(void const * pBuffer, sal_uInt32 nLen, sal_uInt32 nFaceNum);
+
+ TrueTypeFont * get() const { return m_pFont; }
+
+private:
+ TrueTypeFont * m_pFont;
+};
+
+}
+
+ScopedTrueTypeFont::~ScopedTrueTypeFont()
+{
+ if (m_pFont != nullptr)
+ CloseTTFont(m_pFont);
+}
+
+SFErrCodes ScopedTrueTypeFont::open(void const * pBuffer, sal_uInt32 nLen,
+ sal_uInt32 nFaceNum)
+{
+ OSL_ENSURE(m_pFont == nullptr, "already open");
+ return OpenTTFontBuffer(pBuffer, nLen, nFaceNum, &m_pFont);
+}
+
+bool WinSalGraphics::CreateFontSubset( const OUString& rToFile,
+ const PhysicalFontFace* pFont, const sal_GlyphId* pGlyphIds, const sal_uInt8* pEncoding,
+ sal_Int32* pGlyphWidths, int nGlyphCount, FontSubsetInfo& rInfo )
+{
+ // TODO: use more of the central font-subsetting code, move stuff there if needed
+
+ // create matching FontSelectPattern
+ // we need just enough to get to the font file data
+ // use height=1000 for easier debugging (to match psprint's font units)
+ FontSelectPattern aIFSD( *pFont, Size(0,1000), 1000.0, 0, false );
+
+ // TODO: much better solution: move SetFont and restoration of old font to caller
+ ScopedFont aOldFont(*this);
+ HFONT hOldFont = nullptr;
+ ImplDoSetFont(aIFSD, pFont, hOldFont);
+
+ WinFontFace const * pWinFontData = static_cast<WinFontFace const *>(pFont);
+
+#if OSL_DEBUG_LEVEL > 1
+ // get font metrics
+ TEXTMETRICW aWinMetric;
+ if( !::GetTextMetricsW( getHDC(), &aWinMetric ) )
+ return FALSE;
+
+ SAL_WARN_IF( (aWinMetric.tmPitchAndFamily & TMPF_DEVICE), "vcl", "cannot subset device font" );
+ SAL_WARN_IF( !(aWinMetric.tmPitchAndFamily & TMPF_TRUETYPE), "vcl", "can only subset TT font" );
+#endif
+
+ OUString aSysPath;
+ if( osl_File_E_None != osl_getSystemPathFromFileURL( rToFile.pData, &aSysPath.pData ) )
+ return false;
+ const rtl_TextEncoding aThreadEncoding = osl_getThreadTextEncoding();
+ const OString aToFile(OUStringToOString(aSysPath, aThreadEncoding));
+
+ // check if the font has a CFF-table
+ const DWORD nCffTag = CalcTag( "CFF " );
+ const RawFontData aRawCffData( getHDC(), nCffTag );
+ if( aRawCffData.get() )
+ {
+ pWinFontData->UpdateFromHDC( getHDC() );
+
+ // provide a font subset from the CFF-table
+ FILE* pOutFile = fopen( aToFile.getStr(), "wb" );
+ rInfo.LoadFont( FontType::CFF_FONT, aRawCffData.get(), aRawCffData.size() );
+ bool bRC = rInfo.CreateFontSubset( FontType::TYPE1_PFB, pOutFile, nullptr,
+ pGlyphIds, pEncoding, nGlyphCount, pGlyphWidths );
+ fclose( pOutFile );
+ return bRC;
+ }
+
+ // get raw font file data
+ const RawFontData xRawFontData( getHDC(), 0 );
+ if( !xRawFontData.get() )
+ return false;
+
+ // open font file
+ sal_uInt32 nFaceNum = 0;
+ if( !*xRawFontData.get() ) // TTC candidate
+ nFaceNum = ~0U; // indicate "TTC font extracts only"
+
+ ScopedTrueTypeFont aSftTTF;
+ SFErrCodes nRC = aSftTTF.open( xRawFontData.get(), xRawFontData.size(), nFaceNum );
+ if( nRC != SFErrCodes::Ok )
+ return false;
+
+ TTGlobalFontInfo aTTInfo;
+ ::GetTTGlobalFontInfo( aSftTTF.get(), &aTTInfo );
+ rInfo.m_nFontType = FontType::SFNT_TTF;
+ rInfo.m_aPSName = ImplSalGetUniString( aTTInfo.psname );
+ rInfo.m_nAscent = aTTInfo.winAscent;
+ rInfo.m_nDescent = aTTInfo.winDescent;
+ rInfo.m_aFontBBox = tools::Rectangle( Point( aTTInfo.xMin, aTTInfo.yMin ),
+ Point( aTTInfo.xMax, aTTInfo.yMax ) );
+ rInfo.m_nCapHeight = aTTInfo.yMax; // Well ...
+
+ // subset TTF-glyphs and get their properties
+ // take care that subset fonts require the NotDef glyph in pos 0
+ int nOrigCount = nGlyphCount;
+ sal_uInt16 aShortIDs[ 256 ];
+ sal_uInt8 aTempEncs[ 256 ];
+
+ 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; // first NotDef glyph found
+ }
+
+ if( nNotDef != 0 )
+ {
+ // add fake NotDef glyph if needed
+ if( nNotDef < 0 )
+ nNotDef = nGlyphCount++;
+
+ // NotDef glyph must be in pos 0 => swap glyphids
+ aShortIDs[ nNotDef ] = aShortIDs[0];
+ aTempEncs[ nNotDef ] = aTempEncs[0];
+ aShortIDs[0] = 0;
+ aTempEncs[0] = 0;
+ }
+ SAL_WARN_IF( nGlyphCount >= 257, "vcl", "too many glyphs for subsetting" );
+
+ // fill pWidth array
+ std::unique_ptr<sal_uInt16[]> pMetrics =
+ ::GetTTSimpleGlyphMetrics( aSftTTF.get(), aShortIDs, nGlyphCount, aIFSD.mbVertical );
+ if( !pMetrics )
+ return false;
+ sal_uInt16 nNotDefAdv = pMetrics[0];
+ pMetrics[0] = pMetrics[nNotDef];
+ pMetrics[nNotDef] = nNotDefAdv;
+ for( i = 0; i < nOrigCount; ++i )
+ pGlyphWidths[i] = pMetrics[i];
+ pMetrics.reset();
+
+ // write subset into destination file
+ nRC = ::CreateTTFromTTGlyphs( aSftTTF.get(), aToFile.getStr(), aShortIDs,
+ aTempEncs, nGlyphCount );
+ return (nRC == SFErrCodes::Ok);
+}
+
+const void* WinSalGraphics::GetEmbedFontData(const PhysicalFontFace* pFont, long* pDataLen)
+{
+ // create matching FontSelectPattern
+ // we need just enough to get to the font file data
+ FontSelectPattern aIFSD( *pFont, Size(0,1000), 1000.0, 0, false );
+
+ ScopedFont aOldFont(*this);
+
+ HFONT hOldFont = nullptr;
+ ImplDoSetFont(aIFSD, pFont, hOldFont);
+
+ // get the raw font file data
+ RawFontData aRawFontData( getHDC() );
+ *pDataLen = aRawFontData.size();
+ if( !aRawFontData.get() )
+ return nullptr;
+
+ const unsigned char* pData = aRawFontData.steal();
+ return pData;
+}
+
+void WinSalGraphics::FreeEmbedFontData( const void* pData, long /*nLen*/ )
+{
+ delete[] static_cast<char const *>(pData);
+}
+
+void WinSalGraphics::GetGlyphWidths( const PhysicalFontFace* pFont,
+ bool bVertical,
+ std::vector< sal_Int32 >& rWidths,
+ Ucs2UIntMap& rUnicodeEnc )
+{
+ // create matching FontSelectPattern
+ // we need just enough to get to the font file data
+ FontSelectPattern aIFSD( *pFont, Size(0,1000), 1000.0, 0, false );
+
+ // TODO: much better solution: move SetFont and restoration of old font to caller
+ ScopedFont aOldFont(*this);
+
+ HFONT hOldFont = nullptr;
+ ImplDoSetFont(aIFSD, pFont, hOldFont);
+
+ // get raw font file data
+ const RawFontData xRawFontData( getHDC() );
+ if( !xRawFontData.get() )
+ return;
+
+ // open font file
+ sal_uInt32 nFaceNum = 0;
+ if( !*xRawFontData.get() ) // TTC candidate
+ nFaceNum = ~0U; // indicate "TTC font extracts only"
+
+ ScopedTrueTypeFont aSftTTF;
+ SFErrCodes nRC = aSftTTF.open( xRawFontData.get(), xRawFontData.size(), nFaceNum );
+ if( nRC != SFErrCodes::Ok )
+ return;
+
+ int nGlyphs = GetTTGlyphCount( aSftTTF.get() );
+ if( nGlyphs > 0 )
+ {
+ rWidths.resize(nGlyphs);
+ std::vector<sal_uInt16> aGlyphIds(nGlyphs);
+ for( int i = 0; i < nGlyphs; i++ )
+ aGlyphIds[i] = sal_uInt16(i);
+ std::unique_ptr<sal_uInt16[]> pMetrics = ::GetTTSimpleGlyphMetrics( aSftTTF.get(),
+ aGlyphIds.data(),
+ nGlyphs,
+ bVertical );
+ if( pMetrics )
+ {
+ for( int i = 0; i< nGlyphs; i++ )
+ rWidths[i] = pMetrics[i];
+ pMetrics.reset();
+ rUnicodeEnc.clear();
+ }
+ const WinFontFace* pWinFont = static_cast<const WinFontFace*>(pFont);
+ FontCharMapRef xFCMap = pWinFont->GetFontCharMap();
+ SAL_WARN_IF( !xFCMap.is() || !xFCMap->GetCharCount(), "vcl", "no map" );
+
+ int nCharCount = xFCMap->GetCharCount();
+ sal_uInt32 nChar = xFCMap->GetFirstChar();
+ for( int i = 0; i < nCharCount; i++ )
+ {
+ if( nChar < 0x00010000 )
+ {
+ sal_uInt16 nGlyph = ::MapChar( aSftTTF.get(),
+ static_cast<sal_Ucs>(nChar));
+ if( nGlyph )
+ rUnicodeEnc[ static_cast<sal_Unicode>(nChar) ] = nGlyph;
+ }
+ nChar = xFCMap->GetNextChar( nChar );
+ }
+
+ xFCMap = nullptr;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/gdi/salgdi.cxx b/vcl/win/gdi/salgdi.cxx
new file mode 100644
index 000000000..4b47b10b8
--- /dev/null
+++ b/vcl/win/gdi/salgdi.cxx
@@ -0,0 +1,1058 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <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 <opengl/win/gdiimpl.hxx>
+
+#include <vcl/opengl/OpenGLHelper.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 cache of device contexts
+ pSalData->mpHDCCache = new HDCCache[ CACHESIZE_HDC ];
+ memset( pSalData->mpHDCCache, 0, CACHESIZE_HDC * sizeof( HDCCache ) );
+
+ // 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
+ long n;
+
+ pSalData->mhDitherDIB = GlobalAlloc( GMEM_FIXED, sizeof( BITMAPINFOHEADER ) + 192 );
+ pSalData->mpDitherDIB = static_cast<BYTE*>(GlobalLock( pSalData->mhDitherDIB ));
+ pSalData->mpDitherDiff = new long[ 256 ];
+ pSalData->mpDitherLow = new BYTE[ 256 ];
+ pSalData->mpDitherHigh = 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
+ long const nSize = sizeof( BITMAPINFOHEADER ) + ( 256 * sizeof( short ) ) + 64;
+ long n;
+
+ pSalData->mhDitherDIB = GlobalAlloc( GMEM_FIXED, nSize );
+ pSalData->mpDitherDIB = static_cast<BYTE*>(GlobalLock( pSalData->mhDitherDIB ));
+ pSalData->mpDitherDiff = new long[ 256 ];
+ pSalData->mpDitherLow = new BYTE[ 256 ];
+ pSalData->mpDitherHigh = 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[] pSalData->mpHDCCache;
+
+ // 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;
+ delete[] pSalData->mpDitherDiff;
+ delete[] pSalData->mpDitherLow;
+ delete[] pSalData->mpDitherHigh;
+ }
+
+ 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()
+{
+ // 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()
+{
+ // clear clip region
+ SelectClipRgn( getHDC(), nullptr );
+ // select default objects
+ if ( mhDefPen )
+ SelectPen( getHDC(), mhDefPen );
+ if ( mhDefBrush )
+ SelectBrush( getHDC(), mhDefBrush );
+ if ( mhDefFont )
+ SelectFont( getHDC(), mhDefFont );
+
+ mpImpl->DeInit();
+}
+
+HDC ImplGetCachedDC( sal_uLong nID, HBITMAP hBmp )
+{
+ SalData* pSalData = GetSalData();
+ HDCCache* pC = &pSalData->mpHDCCache[ 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->mpHDCCache[ 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->mpHDCCache[ 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
+ if (OpenGLHelper::isVCLOpenGLEnabled())
+ return std::make_unique< OpenGLCompatibleDC >( rGraphics, x, y, width, height );
+ 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 = dynamic_cast<WinSalGraphicsImplBase*>(rWinGraphics.GetImpl());
+ assert(mpImpl != nullptr);
+ 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, 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)
+ mpImpl.reset(new WinSkiaSalGraphicsImpl(*this, pProvider));
+ else
+#endif
+ if (OpenGLHelper::isVCLOpenGLEnabled() && !mbPrinter)
+ mpImpl.reset(new WinOpenGLSalGraphicsImpl(*this, pProvider));
+ else
+ mpImpl.reset(new WinSalGraphicsImpl(*this));
+}
+
+WinSalGraphics::~WinSalGraphics()
+{
+ // free obsolete GDI objects
+ ReleaseFonts();
+
+ if ( mhRegion )
+ {
+ DeleteRegion( mhRegion );
+ mhRegion = nullptr;
+ }
+
+ // delete cache data
+ delete [] reinterpret_cast<BYTE*>(mpStdClipRgnData);
+}
+
+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
+{
+ return mhDefPal;
+}
+
+void WinSalGraphics::setDefPal(HPALETTE hDefPal)
+{
+ mhDefPal = hDefPal;
+}
+
+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;
+}
+
+sal_uInt16 WinSalGraphics::GetBitCount() const
+{
+ return mpImpl->GetBitCount();
+}
+
+long WinSalGraphics::GetGraphicsWidth() const
+{
+ return mpImpl->GetGraphicsWidth();
+}
+
+void WinSalGraphics::ResetClipRegion()
+{
+ mpImpl->ResetClipRegion();
+}
+
+bool WinSalGraphics::setClipRegion( const vcl::Region& i_rClip )
+{
+ return 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( long nX, long nY )
+{
+ mpImpl->drawPixel( nX, nY );
+}
+
+void WinSalGraphics::drawPixel( long nX, long nY, Color nColor )
+{
+ mpImpl->drawPixel( nX, nY, nColor );
+}
+
+void WinSalGraphics::drawLine( long nX1, long nY1, long nX2, long nY2 )
+{
+ mpImpl->drawLine( nX1, nY1, nX2, nY2 );
+}
+
+void WinSalGraphics::drawRect( long nX, long nY, long nWidth, long nHeight )
+{
+ mpImpl->drawRect( nX, nY, nWidth, nHeight );
+}
+
+void WinSalGraphics::drawPolyLine( sal_uInt32 nPoints, const SalPoint* pPtAry )
+{
+ mpImpl->drawPolyLine( nPoints, pPtAry );
+}
+
+void WinSalGraphics::drawPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry )
+{
+ mpImpl->drawPolygon( nPoints, pPtAry );
+}
+
+void WinSalGraphics::drawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints,
+ PCONSTSALPOINT* pPtAry )
+{
+ mpImpl->drawPolyPolygon( nPoly, pPoints, pPtAry );
+}
+
+bool WinSalGraphics::drawPolyLineBezier( sal_uInt32 nPoints, const SalPoint* pPtAry, const PolyFlags* pFlgAry )
+{
+ return mpImpl->drawPolyLineBezier( nPoints, pPtAry, pFlgAry );
+}
+
+bool WinSalGraphics::drawPolygonBezier( sal_uInt32 nPoints, const SalPoint* pPtAry, const PolyFlags* pFlgAry )
+{
+ return mpImpl->drawPolygonBezier( nPoints, pPtAry, pFlgAry );
+}
+
+bool WinSalGraphics::drawPolyPolygonBezier( sal_uInt32 nPoly, const sal_uInt32* pPoints,
+ const SalPoint* const* pPtAry, const PolyFlags* const* pFlgAry )
+{
+ return mpImpl->drawPolyPolygonBezier( nPoly, pPoints, pPtAry, pFlgAry );
+}
+
+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( long nX, long nY, long nWidth, 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" );
+ aBuf.append( pRect->left );
+ aBuf.append( " " );
+ aBuf.append( pRect->top );
+ aBuf.append( " moveto\n" );
+ aBuf.append( pRect->right );
+ aBuf.append( " " );
+ aBuf.append( pRect->top );
+ aBuf.append( " lineto\n" );
+ aBuf.append( pRect->right );
+ aBuf.append( " " );
+ aBuf.append( pRect->bottom );
+ aBuf.append( " lineto\n" );
+ aBuf.append( pRect->left );
+ aBuf.append( " " );
+ aBuf.append( pRect->bottom );
+ aBuf.append( " 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[" );
+ aBuf.append( dM11 );
+ aBuf.append( " 0 0 " );
+ aBuf.append( dM22 );
+ aBuf.append( ' ' );
+ aBuf.append( nX - ( dM11 * nBoundingBox[0] ) );
+ aBuf.append( ' ' );
+ aBuf.append( nY - ( dM22 * nBoundingBox[3] ) );
+ aBuf.append( "] 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 000000000..fdac864d0
--- /dev/null
+++ b/vcl/win/gdi/salgdi2.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <opengl/salbmp.hxx>
+
+#include <vcl/BitmapAccessMode.hxx>
+#include <vcl/BitmapBuffer.hxx>
+#include <vcl/BitmapPalette.hxx>
+#include <vcl/Scanline.hxx>
+#include <vcl/bitmapaccess.hxx>
+#include <outdata.hxx>
+#include <salgdiimpl.hxx>
+#include <opengl/win/gdiimpl.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( long nDestX, long nDestY,
+ long nSrcX, long nSrcY,
+ long nSrcWidth, long nSrcHeight,
+ bool bWindowInvalidate )
+{
+ mpImpl->copyArea( nDestX, nDestY, nSrcX, nSrcY,
+ nSrcWidth, nSrcHeight, bWindowInvalidate );
+}
+
+namespace
+{
+
+class ColorScanlineConverter
+{
+public:
+ ScanlineFormat meSourceFormat;
+
+ int mnComponentSize;
+ int mnComponentExchangeIndex;
+
+ long mnScanlineSize;
+
+ ColorScanlineConverter(ScanlineFormat eSourceFormat, int nComponentSize, 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 (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;
+ OpenGLSalBitmap* pGLSalBitmap = dynamic_cast<OpenGLSalBitmap*>(&rSalBitmap);
+ if (pGLSalBitmap != nullptr)
+ {
+ aBitmapPalette = pGLSalBitmap->GetBitmapPalette();
+ }
+#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(), rSalBitmap.GetBitCount(), aBitmapPalette);
+ BitmapBuffer* pWrite = rWinSalBitmap.AcquireBuffer(BitmapAccessMode::Write);
+
+ sal_uInt8* pSource(pRead->mpBits);
+ sal_uInt8* pDestination(pWrite->mpBits);
+ 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 (long y = 0; y < pRead->mnHeight; y++)
+ {
+ pConverter->convertScanline(pSource, pDestination);
+ pSource += readRowChange;
+ pDestination += pWrite->mnScanlineSize;
+ }
+ }
+ else
+ {
+ for (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<WinOpenGLSalGraphicsImpl*>(mpImpl.get()) == nullptr &&
+#if HAVE_FEATURE_SKIA
+ dynamic_cast<WinSkiaSalGraphicsImpl*>(mpImpl.get()) == nullptr &&
+#endif
+ dynamic_cast<const WinSalBitmap*>(&rSalBitmap) == nullptr)
+ {
+ 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<WinOpenGLSalGraphicsImpl*>(mpImpl.get()) == nullptr &&
+#if HAVE_FEATURE_SKIA
+ dynamic_cast<WinSkiaSalGraphicsImpl*>(mpImpl.get()) == nullptr &&
+#endif
+ dynamic_cast<const WinSalBitmap*>(&rSSalBitmap) == nullptr)
+ {
+ 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( long nX, long nY, long nWidth,
+ 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( long nX, long nY, long nDX, long nDY )
+{
+ return mpImpl->getBitmap( nX, nY, nDX, nDY );
+}
+
+Color WinSalGraphics::getPixel( long nX, long nY )
+{
+ return mpImpl->getPixel( nX, nY );
+}
+
+void WinSalGraphics::invert( long nX, long nY, long nWidth, long nHeight, SalInvert nFlags )
+{
+ mpImpl->invert( nX, nY, nWidth, nHeight, nFlags );
+}
+
+void WinSalGraphics::invert( sal_uInt32 nPoints, const SalPoint* 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 000000000..f56a22760
--- /dev/null
+++ b/vcl/win/gdi/salgdi_gdiplus.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 <string.h>
+#include <svsys.h>
+#include <win/wincomp.hxx>
+#include <win/saldata.hxx>
+#include <win/salgdi.h>
+#include <win/salbmp.h>
+
+#include "gdiimpl.hxx"
+
+bool WinSalGraphics::drawPolyPolygon(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon& rPolyPolygon,
+ double fTransparency)
+{
+ return 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)
+{
+ return mpImpl->drawTransformedBitmap(rNull, rX, rY,
+ rSourceBitmap, pAlphaBitmap);
+}
+
+/* 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 000000000..68a18b020
--- /dev/null
+++ b/vcl/win/gdi/salnativewidgets-luna.cxx
@@ -0,0 +1,1559 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <opengl/win/gdiimpl.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+
+#include <win/svsys.h>
+#include <win/salgdi.h>
+#include <win/saldata.hxx>
+#include <win/scoped_gdi.hxx>
+
+#include <uxtheme.h>
+#include <vssym32.h>
+
+#include <map>
+#include <string>
+#include <optional>
+#include <ControlCacheKey.hxx>
+
+using namespace std;
+
+typedef map< wstring, HTHEME > ThemeMap;
+static ThemeMap aThemeMap;
+
+/****************************************************
+ wrap visual styles API to avoid linking against it
+ it is not available on all Windows platforms
+*****************************************************/
+
+namespace {
+
+class VisualStylesAPI
+{
+private:
+ typedef HTHEME (WINAPI * OpenThemeData_Proc_T) ( HWND hwnd, LPCWSTR pszClassList );
+ typedef HRESULT (WINAPI * CloseThemeData_Proc_T) ( HTHEME hTheme );
+ typedef HRESULT (WINAPI * GetThemeBackgroundContentRect_Proc_T) ( HTHEME hTheme, HDC hdc, int iPartId, int iStateId, const RECT *pBoundingRect, RECT *pContentRect );
+ typedef HRESULT (WINAPI * DrawThemeBackground_Proc_T) ( HTHEME hTheme, HDC hdc, int iPartId, int iStateId, const RECT *pRect, const RECT *pClipRect );
+ typedef HRESULT (WINAPI * DrawThemeText_Proc_T) ( HTHEME hTheme, HDC hdc, int iPartId, int iStateId, LPCWSTR pszText, int iCharCount, DWORD dwTextFlags, DWORD dwTextFlags2, const RECT *pRect );
+ typedef HRESULT (WINAPI * GetThemePartSize_Proc_T) ( HTHEME hTheme, HDC hdc, int iPartId, int iStateId, RECT *prc, THEMESIZE eSize, SIZE *psz );
+ typedef BOOL (WINAPI * IsThemeActive_Proc_T) ( void );
+
+ OpenThemeData_Proc_T lpfnOpenThemeData;
+ CloseThemeData_Proc_T lpfnCloseThemeData;
+ GetThemeBackgroundContentRect_Proc_T lpfnGetThemeBackgroundContentRect;
+ DrawThemeBackground_Proc_T lpfnDrawThemeBackground;
+ DrawThemeText_Proc_T lpfnDrawThemeText;
+ GetThemePartSize_Proc_T lpfnGetThemePartSize;
+ IsThemeActive_Proc_T lpfnIsThemeActive;
+
+ oslModule mhModule;
+
+public:
+ VisualStylesAPI();
+ ~VisualStylesAPI();
+
+ HTHEME OpenThemeData( HWND hwnd, LPCWSTR pszClassList );
+ HRESULT CloseThemeData( HTHEME hTheme );
+ HRESULT GetThemeBackgroundContentRect( HTHEME hTheme, HDC hdc, int iPartId, int iStateId, const RECT *pBoundingRect, RECT *pContentRect );
+ HRESULT DrawThemeBackground( HTHEME hTheme, HDC hdc, int iPartId, int iStateId, const RECT *pRect, const RECT *pClipRect );
+ HRESULT DrawThemeText( HTHEME hTheme, HDC hdc, int iPartId, int iStateId, LPCWSTR pszText, int iCharCount, DWORD dwTextFlags, DWORD dwTextFlags2, const RECT *pRect );
+ HRESULT GetThemePartSize( HTHEME hTheme, HDC hdc, int iPartId, int iStateId, RECT *prc, THEMESIZE eSize, SIZE *psz );
+ bool IsThemeActive();
+};
+
+}
+
+static VisualStylesAPI vsAPI;
+
+VisualStylesAPI::VisualStylesAPI()
+ : lpfnOpenThemeData( nullptr ),
+ lpfnCloseThemeData( nullptr ),
+ lpfnGetThemeBackgroundContentRect( nullptr ),
+ lpfnDrawThemeBackground( nullptr ),
+ lpfnDrawThemeText( nullptr ),
+ lpfnGetThemePartSize( nullptr ),
+ lpfnIsThemeActive( nullptr )
+{
+ OUString aLibraryName( "uxtheme.dll" );
+ mhModule = osl_loadModule( aLibraryName.pData, SAL_LOADMODULE_DEFAULT );
+
+ if ( mhModule )
+ {
+ lpfnOpenThemeData = reinterpret_cast<OpenThemeData_Proc_T>(osl_getAsciiFunctionSymbol( mhModule, "OpenThemeData" ));
+ lpfnCloseThemeData = reinterpret_cast<CloseThemeData_Proc_T>(osl_getAsciiFunctionSymbol( mhModule, "CloseThemeData" ));
+ lpfnGetThemeBackgroundContentRect = reinterpret_cast<GetThemeBackgroundContentRect_Proc_T>(osl_getAsciiFunctionSymbol( mhModule, "GetThemeBackgroundContentRect" ));
+ lpfnDrawThemeBackground = reinterpret_cast<DrawThemeBackground_Proc_T>(osl_getAsciiFunctionSymbol( mhModule, "DrawThemeBackground" ));
+ lpfnDrawThemeText = reinterpret_cast<DrawThemeText_Proc_T>(osl_getAsciiFunctionSymbol( mhModule, "DrawThemeText" ));
+ lpfnGetThemePartSize = reinterpret_cast<GetThemePartSize_Proc_T>(osl_getAsciiFunctionSymbol( mhModule, "GetThemePartSize" ));
+ lpfnIsThemeActive = reinterpret_cast<IsThemeActive_Proc_T>(osl_getAsciiFunctionSymbol( mhModule, "IsThemeActive" ));
+ }
+}
+
+VisualStylesAPI::~VisualStylesAPI()
+{
+ if( mhModule )
+ osl_unloadModule( mhModule );
+}
+
+HTHEME VisualStylesAPI::OpenThemeData( HWND hwnd, LPCWSTR pszClassList )
+{
+ if(lpfnOpenThemeData)
+ return (*lpfnOpenThemeData) (hwnd, pszClassList);
+ else
+ return nullptr;
+}
+
+HRESULT VisualStylesAPI::CloseThemeData( HTHEME hTheme )
+{
+ if(lpfnCloseThemeData)
+ return (*lpfnCloseThemeData) (hTheme);
+ else
+ return S_FALSE;
+}
+
+HRESULT VisualStylesAPI::GetThemeBackgroundContentRect( HTHEME hTheme, HDC hdc, int iPartId, int iStateId, const RECT *pBoundingRect, RECT *pContentRect )
+{
+ if(lpfnGetThemeBackgroundContentRect)
+ return (*lpfnGetThemeBackgroundContentRect) ( hTheme, hdc, iPartId, iStateId, pBoundingRect, pContentRect );
+ else
+ return S_FALSE;
+}
+
+HRESULT VisualStylesAPI::DrawThemeBackground( HTHEME hTheme, HDC hdc, int iPartId, int iStateId, const RECT *pRect, const RECT *pClipRect )
+{
+ if(lpfnDrawThemeBackground)
+ return (*lpfnDrawThemeBackground) (hTheme, hdc, iPartId, iStateId, pRect, pClipRect);
+ else
+ return S_FALSE;
+}
+
+HRESULT VisualStylesAPI::DrawThemeText( HTHEME hTheme, HDC hdc, int iPartId, int iStateId, LPCWSTR pszText, int iCharCount, DWORD dwTextFlags, DWORD dwTextFlags2, const RECT *pRect )
+{
+ if(lpfnDrawThemeText)
+ return (*lpfnDrawThemeText) (hTheme, hdc, iPartId, iStateId, pszText, iCharCount, dwTextFlags, dwTextFlags2, pRect);
+ else
+ return S_FALSE;
+}
+
+HRESULT VisualStylesAPI::GetThemePartSize( HTHEME hTheme, HDC hdc, int iPartId, int iStateId, RECT *prc, THEMESIZE eSize, SIZE *psz )
+{
+ if(lpfnGetThemePartSize)
+ return (*lpfnGetThemePartSize) (hTheme, hdc, iPartId, iStateId, prc, eSize, psz);
+ else
+ return S_FALSE;
+}
+
+bool VisualStylesAPI::IsThemeActive()
+{
+ if(lpfnIsThemeActive)
+ return (*lpfnIsThemeActive) ();
+ else
+ return false;
+}
+
+/*********************************************************
+ * 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 )
+ vsAPI.CloseThemeData(rEntry.second);
+ aThemeMap.clear();
+}
+
+static HTHEME getThemeHandle( HWND hWnd, LPCWSTR name )
+{
+ if( GetSalData()->mbThemeChanged )
+ {
+ // throw away invalid theme handles
+ SalData::deInitNWF();
+ 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 = vsAPI.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");
+ 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");
+ 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");
+ else if( nPart == ControlPart::ButtonDown )
+ hTheme = getThemeHandle( mhWnd, L"Combobox");
+ break;
+ case ControlType::Spinbox:
+ if( nPart == ControlPart::Entire )
+ hTheme = getThemeHandle( mhWnd, L"Edit");
+ else if( nPart == ControlPart::AllButtons ||
+ nPart == ControlPart::ButtonUp || nPart == ControlPart::ButtonDown ||
+ nPart == ControlPart::ButtonLeft|| nPart == ControlPart::ButtonRight )
+ hTheme = getThemeHandle( mhWnd, L"Spin");
+ break;
+ case ControlType::SpinButtons:
+ if( nPart == ControlPart::Entire || nPart == ControlPart::AllButtons )
+ hTheme = getThemeHandle( mhWnd, L"Spin");
+ 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");
+ 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");
+ else if( nPart == ControlPart::ButtonDown )
+ hTheme = getThemeHandle( mhWnd, L"Combobox");
+ break;
+ case ControlType::TabPane:
+ case ControlType::TabBody:
+ case ControlType::TabItem:
+ if( nPart == ControlPart::Entire )
+ hTheme = getThemeHandle( mhWnd, L"Tab");
+ break;
+ case ControlType::Toolbar:
+ if( nPart == ControlPart::Entire || nPart == ControlPart::Button )
+ hTheme = getThemeHandle( mhWnd, L"Toolbar");
+ else
+ // use rebar theme for grip and background
+ hTheme = getThemeHandle( mhWnd, L"Rebar");
+ break;
+ case ControlType::Menubar:
+ if( nPart == ControlPart::Entire )
+ hTheme = getThemeHandle( mhWnd, L"Rebar");
+ else if( GetSalData()->mbThemeMenuSupport )
+ {
+ if( nPart == ControlPart::MenuItem )
+ hTheme = getThemeHandle( mhWnd, L"Menu" );
+ }
+ 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" );
+ }
+ break;
+ case ControlType::Progress:
+ if( nPart == ControlPart::Entire )
+ hTheme = getThemeHandle( mhWnd, L"Progress");
+ break;
+ case ControlType::Slider:
+ if( nPart == ControlPart::TrackHorzArea || nPart == ControlPart::TrackVertArea )
+ hTheme = getThemeHandle( mhWnd, L"Trackbar" );
+ break;
+ case ControlType::ListNode:
+ if( nPart == ControlPart::Entire )
+ hTheme = getThemeHandle( mhWnd, L"TreeView" );
+ 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 = vsAPI.DrawThemeBackground( hTheme, hDC, iPart, iState, &rc, nullptr);
+
+ if( aStr.getLength() )
+ {
+ RECT rcContent;
+ hr = vsAPI.GetThemeBackgroundContentRect( hTheme, hDC, iPart, iState, &rc, &rcContent);
+ hr = vsAPI.DrawThemeText( hTheme, hDC, iPart, iState,
+ o3tl::toW(aStr.getStr()), -1,
+ DT_CENTER | DT_VCENTER | DT_SINGLELINE,
+ 0, &rcContent);
+ }
+ return (hr == S_OK);
+}
+
+static tools::Rectangle ImplGetThemeRect( HTHEME hTheme, HDC hDC, int iPart, int iState, const tools::Rectangle& /* aRect */, THEMESIZE eTS = TS_TRUE )
+{
+ SIZE aSz;
+ HRESULT hr = vsAPI.GetThemePartSize( hTheme, hDC, iPart, iState, nullptr, eTS, &aSz ); // TS_TRUE returns optimal size
+ if( hr == S_OK )
+ return tools::Rectangle( 0, 0, aSz.cx, aSz.cy );
+ 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 long 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 );
+ }
+ }
+}
+
+/**
+ * Gives the actual rectangle used for rendering by ControlType::MenuPopup's
+ * ControlPart::MenuItemCheckMark or ControlPart::MenuItemRadioMark.
+ */
+static tools::Rectangle GetMenuPopupMarkRegion(const ImplControlValue& rValue)
+{
+ tools::Rectangle aRet;
+
+ auto pMVal = dynamic_cast<const MenupopupValue*>(&rValue);
+ if (!pMVal)
+ return aRet;
+
+ aRet.SetTop(pMVal->maItemRect.Top());
+ aRet.SetBottom(pMVal->maItemRect.Bottom() + 1); // see below in drawNativeControl
+ if (AllSettings::GetLayoutRTL())
+ {
+ aRet.SetRight(pMVal->maItemRect.Right() + 1);
+ aRet.SetLeft(aRet.Right() - (pMVal->getNumericVal() - pMVal->maItemRect.Left()));
+ }
+ else
+ {
+ aRet.SetRight(pMVal->getNumericVal());
+ aRet.SetLeft(pMVal->maItemRect.Left());
+ }
+
+ return aRet;
+}
+
+static bool ImplDrawNativeControl( HDC hDC, HTHEME hTheme, RECT rc,
+ ControlType nType,
+ ControlPart nPart,
+ ControlState nState,
+ const ImplControlValue& aValue,
+ OUString const & aCaption )
+{
+ // 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 = vsAPI.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 = vsAPI.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 = vsAPI.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 = vsAPI.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;
+ vsAPI.GetThemePartSize(hTheme, hDC, iPart, iState, nullptr, TS_MIN, &sz);
+ vsAPI.GetThemePartSize(hTheme, hDC, iPart, iState, nullptr, TS_TRUE, &sz);
+ vsAPI.GetThemePartSize(hTheme, hDC, iPart, iState, nullptr, TS_DRAW, &sz);
+
+ hr = vsAPI.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;
+ vsAPI.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 = vsAPI.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
+ //vsAPI.GetThemePartSize( hTheme, hDC, iPart, iState, &rc, eSize, &sz);
+
+ return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption);
+ }
+
+ if( ( nType == ControlType::Editbox ) || ( 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 )
+ {
+ iPart = TABP_PANE;
+ return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption);
+ }
+
+ if( nType == ControlType::TabBody )
+ {
+ 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;
+ 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
+ }
+
+ // make it more compatible with Aero
+ if( ImplGetSVData()->maNWFData.mbDockingAreaAvoidTBFrames )
+ {
+ 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
+
+ // make it more compatible with Aero
+ if( ImplGetSVData()->maNWFData.mbDockingAreaAvoidTBFrames )
+ {
+ 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;
+ }
+ 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 )
+ {
+ if( nPart != ControlPart::Entire )
+ return false;
+
+ if( ! ImplDrawTheme( hTheme, hDC, PP_BAR, iState, rc, aCaption) )
+ return false;
+ RECT aProgressRect = rc;
+ if( vsAPI.GetThemeBackgroundContentRect( hTheme, hDC, PP_BAR, iState, &rc, &aProgressRect) != S_OK )
+ return false;
+
+ 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;
+
+ 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 )
+ {
+ long nH = aTrackRect.GetHeight();
+ aTRect.top += (rc.bottom - rc.top - nH)/2;
+ aTRect.bottom = aTRect.top + nH;
+ }
+ else
+ {
+ 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 )
+ {
+ RECT aBGRect = rc;
+ if( aValue.getType() == ControlType::MenuPopup )
+ {
+ tools::Rectangle aRectangle = GetMenuPopupMarkRegion(aValue);
+ aBGRect.top = aRectangle.Top();
+ aBGRect.left = aRectangle.Left();
+ aBGRect.bottom = aRectangle.Bottom();
+ aBGRect.right = aRectangle.Right();
+ rc = aBGRect;
+ }
+ iState = (nState & ControlState::ENABLED) ? MCB_NORMAL : MCB_DISABLED;
+ ImplDrawTheme( hTheme, hDC, MENU_POPUPCHECKBACKGROUND, iState, aBGRect, aCaption );
+ if( nPart == ControlPart::MenuItemCheckMark )
+ iState = (nState & ControlState::ENABLED) ? MC_CHECKMARKNORMAL : MC_CHECKMARKDISABLED;
+ else
+ iState = (nState & ControlState::ENABLED) ? MC_BULLETNORMAL : MC_BULLETDISABLED;
+ return ImplDrawTheme( hTheme, hDC, MENU_POPUPCHECK, iState, rc, 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
+ long 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 = dynamic_cast<WinSalGraphicsImplBase*>(mpImpl.get());
+ 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();
+ }
+ }
+
+ if (pImpl && nType == ControlType::MenuPopup && (nPart == ControlPart::MenuItemCheckMark || nPart == ControlPart::MenuItemRadioMark))
+ {
+ tools::Rectangle aRectangle = GetMenuPopupMarkRegion(aValue);
+ if (!aRectangle.IsEmpty())
+ {
+ cacheRect = GetMenuPopupMarkRegion(aValue);
+ buttonRect = cacheRect;
+ keySize = cacheRect.GetSize();
+ }
+ }
+
+
+ ControlCacheKey aControlCacheKey(nType, nPart, nState, keySize);
+ if (pImpl != nullptr && pImpl->TryRenderCachedNativeControl(aControlCacheKey, buttonRect.Left(), buttonRect.Top()))
+ {
+ return true;
+ }
+
+ switch( nType )
+ {
+ case ControlType::Pushbutton:
+ case ControlType::Radiobutton:
+ case ControlType::Checkbox:
+ hTheme = getThemeHandle( mhWnd, L"Button");
+ break;
+ case ControlType::Scrollbar:
+ hTheme = getThemeHandle( mhWnd, L"Scrollbar");
+ break;
+ case ControlType::Combobox:
+ if( nPart == ControlPart::Entire )
+ hTheme = getThemeHandle( mhWnd, L"Edit");
+ else if( nPart == ControlPart::ButtonDown )
+ hTheme = getThemeHandle( mhWnd, L"Combobox");
+ break;
+ case ControlType::Spinbox:
+ if( nPart == ControlPart::Entire )
+ hTheme = getThemeHandle( mhWnd, L"Edit");
+ else
+ hTheme = getThemeHandle( mhWnd, L"Spin");
+ break;
+ case ControlType::SpinButtons:
+ hTheme = getThemeHandle( mhWnd, L"Spin");
+ break;
+ case ControlType::Editbox:
+ case ControlType::MultilineEditbox:
+ hTheme = getThemeHandle( mhWnd, L"Edit");
+ break;
+ case ControlType::Listbox:
+ if( nPart == ControlPart::Entire || nPart == ControlPart::ListboxWindow )
+ hTheme = getThemeHandle( mhWnd, L"Listview");
+ else if( nPart == ControlPart::ButtonDown )
+ hTheme = getThemeHandle( mhWnd, L"Combobox");
+ break;
+ case ControlType::TabPane:
+ case ControlType::TabBody:
+ case ControlType::TabItem:
+ hTheme = getThemeHandle( mhWnd, L"Tab");
+ break;
+ case ControlType::Toolbar:
+ if( nPart == ControlPart::Entire || nPart == ControlPart::Button )
+ hTheme = getThemeHandle( mhWnd, L"Toolbar");
+ else
+ // use rebar for grip and background
+ hTheme = getThemeHandle( mhWnd, L"Rebar");
+ break;
+ case ControlType::Menubar:
+ if( nPart == ControlPart::Entire )
+ hTheme = getThemeHandle( mhWnd, L"Rebar");
+ else if( GetSalData()->mbThemeMenuSupport )
+ {
+ if( nPart == ControlPart::MenuItem )
+ hTheme = getThemeHandle( mhWnd, L"Menu" );
+ }
+ break;
+ case ControlType::Progress:
+ if( nPart == ControlPart::Entire )
+ hTheme = getThemeHandle( mhWnd, L"Progress");
+ break;
+ case ControlType::ListNode:
+ if( nPart == ControlPart::Entire )
+ hTheme = getThemeHandle( mhWnd, L"TreeView");
+ break;
+ case ControlType::Slider:
+ if( nPart == ControlPart::TrackHorzArea || nPart == ControlPart::TrackVertArea )
+ hTheme = getThemeHandle( mhWnd, L"Trackbar" );
+ 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" );
+ }
+ break;
+ default:
+ hTheme = nullptr;
+ break;
+ }
+
+ if( !hTheme )
+ 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);
+
+ // 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) &&
+ ImplDrawNativeControl(aWhiteDC->getCompatibleHDC(), hTheme, rc, nType, nPart, nState, aValue, aCaptionStr))
+ {
+ bOk = pImpl->RenderAndCacheNativeControl(*aWhiteDC, *aBlackDC, cacheRect.Left(), cacheRect.Top(), aControlCacheKey);
+ }
+ }
+
+ 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");
+ 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");
+ 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");
+ 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");
+ 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");
+ 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");
+ tools::Rectangle aBoxRect( rControlRegion );
+ tools::Rectangle aRect( ImplGetThemeRect( hTheme, hDC,
+ MENU_POPUPCHECK,
+ MC_CHECKMARKNORMAL,
+ aBoxRect ) );
+ if( aBoxRect.GetWidth() && aBoxRect.GetHeight() )
+ {
+ rNativeContentRegion = aRect;
+ rNativeBoundingRegion = rNativeContentRegion;
+ bRet = true;
+ }
+ }
+ }
+ }
+
+ if( nType == ControlType::Slider && ( (nPart == ControlPart::ThumbHorz) || (nPart == ControlPart::ThumbVert) ) )
+ {
+ HTHEME hTheme = getThemeHandle( mhWnd, L"Trackbar");
+ 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 )
+ {
+ long nW = aThumbRect.GetWidth();
+ tools::Rectangle aRect( rControlRegion );
+ aRect.SetRight( aRect.Left() + nW - 1 );
+ rNativeContentRegion = aRect;
+ rNativeBoundingRegion = rNativeContentRegion;
+ }
+ else
+ {
+ 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 ( !vsAPI.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
+ 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 000000000..98886c72a
--- /dev/null
+++ b/vcl/win/gdi/salprn.cxx
@@ -0,0 +1,1629 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <string.h>
+
+#include <svsys.h>
+
+#include <osl/module.h>
+#include <o3tl/char16_t2wchar_t.hxx>
+
+#include <tools/urlobj.hxx>
+
+#include <vcl/weld.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 <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 const wchar_t aImplWindows[] = L"windows";
+static const wchar_t aImplDevice[] = L"device";
+
+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->mpPortName )
+ pInfo->mpPortName.reset(new OUString(aPortName));
+ }
+ std::free(pWinInfo2);
+ }
+ ClosePrinter( hPrinter );
+ }
+}
+
+OUString WinSalInstance::GetDefaultPrinter()
+{
+ DWORD nChars = 0;
+ GetDefaultPrinterW( nullptr, &nChars );
+ if( nChars )
+ {
+ LPWSTR pStr = static_cast<LPWSTR>(std::malloc(nChars*sizeof(WCHAR)));
+ OUString aDefPrt;
+ if( GetDefaultPrinterW( pStr, &nChars ) )
+ {
+ aDefPrt = o3tl::toU(pStr);
+ }
+ std::free( pStr );
+ if( !aDefPrt.isEmpty() )
+ return aDefPrt;
+ }
+
+ // get default printer from win.ini
+ wchar_t szBuffer[256];
+ GetProfileStringW( aImplWindows, aImplDevice, L"", szBuffer, SAL_N_ELEMENTS( szBuffer ) );
+ if ( szBuffer[0] )
+ {
+ // search for printer name
+ wchar_t* pBuf = szBuffer;
+ wchar_t* pTmp = pBuf;
+ while ( *pTmp && (*pTmp != ',') )
+ pTmp++;
+ return OUString( o3tl::toU(pBuf), static_cast<sal_Int32>(pTmp-pBuf) );
+ }
+ else
+ 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<long>(pSetupData->GetDriverDataLen() - pSetupDriverData->mnDriverOffset) == nSysJobSize &&
+ pSetupDriverData->mnSysSignature == SAL_DRIVERDATA_SYSSIGN )
+ {
+ if( pDevModeW &&
+ (dmSpecVersion == pDevModeW->dmSpecVersion) &&
+ (dmDriverVersion == pDevModeW->dmDriverVersion) )
+ return true;
+ }
+ if ( bDelete )
+ {
+ std::free( const_cast<sal_uInt8*>(pSetupData->GetDriverData()) );
+ pSetupData->SetDriverData( nullptr );
+ pSetupData->SetDriverDataLen( 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;
+ 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;
+ pOutBuffer = static_cast<SalDriverData*>(rtl_allocateZeroMemory( nDriverDataLen ));
+ 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)) )
+ {
+ std::free( pOutBuffer );
+ 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
+ if ( pSetupData->GetDriverData() )
+ std::free( const_cast<sal_uInt8*>(pSetupData->GetDriverData()) );
+ pSetupData->SetDriverDataLen( nDriverDataLen );
+ pSetupData->SetDriverData(reinterpret_cast<BYTE*>(pOutBuffer));
+ 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);
+ pGraphics->InitGraphics();
+ return pGraphics;
+}
+
+static bool ImplUpdateSalPrnIC( WinSalInfoPrinter* pPrinter, const ImplJobSetup* pSetupData )
+{
+ HDC hNewDC = ImplCreateSalPrnIC( pPrinter, pSetupData );
+ if ( !hNewDC )
+ return false;
+
+ if ( pPrinter->mpGraphics )
+ {
+ pPrinter->mpGraphics->DeInitGraphics();
+ DeleteDC( pPrinter->mpGraphics->getHDC() );
+ delete pPrinter->mpGraphics;
+ }
+
+ pPrinter->mpGraphics = ImplCreateSalPrnGraphics( hNewDC );
+ pPrinter->mhDC = hNewDC;
+
+ return true;
+}
+
+
+SalInfoPrinter* WinSalInstance::CreateInfoPrinter( SalPrinterQueueInfo* pQueueInfo,
+ ImplJobSetup* pSetupData )
+{
+ WinSalInfoPrinter* pPrinter = new WinSalInfoPrinter;
+ if( ! pQueueInfo->mpPortName )
+ GetPrinterQueueState( pQueueInfo );
+ pPrinter->maDriverName = pQueueInfo->maDriver;
+ pPrinter->maDeviceName = pQueueInfo->maPrinterName;
+ pPrinter->maPortName = pQueueInfo->mpPortName ? *pQueueInfo->mpPortName : 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->mpGraphics = ImplCreateSalPrnGraphics( hDC );
+ pPrinter->mhDC = 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() :
+ mpGraphics( nullptr ),
+ mhDC( nullptr ),
+ mbGraphics( false )
+{
+ m_bPapersInit = false;
+}
+
+WinSalInfoPrinter::~WinSalInfoPrinter()
+{
+ if ( mpGraphics )
+ {
+ mpGraphics->DeInitGraphics();
+ DeleteDC( mpGraphics->getHDC() );
+ delete mpGraphics;
+ }
+}
+
+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 );
+ 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()
+{
+ if ( mbGraphics )
+ return nullptr;
+
+ if ( mpGraphics )
+ mbGraphics = true;
+
+ return mpGraphics;
+}
+
+void WinSalInfoPrinter::ReleaseGraphics( SalGraphics* )
+{
+ mbGraphics = 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*,
+ long& rOutWidth, long& rOutHeight,
+ Point& rPageOffset,
+ Size& rPaperSize )
+{
+ HDC hDC = mhDC;
+
+ 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_uLong 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() :
+ mpGraphics( nullptr ),
+ 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 )
+ {
+ if ( mpGraphics )
+ {
+ mpGraphics->DeInitGraphics();
+ delete mpGraphics;
+ }
+
+ 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;
+ }
+ mbValid = false;
+}
+
+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_StartDocW( 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;
+}
+
+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 )
+ {
+ long 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() && hDC )
+ {
+ if ( mpGraphics )
+ {
+ mpGraphics->DeInitGraphics();
+ delete mpGraphics;
+ mpGraphics = nullptr;
+ }
+
+ // #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() || mhDC == nullptr )
+ 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 );
+
+ mpGraphics = ImplCreateSalPrnGraphics( hDC );
+ return mpGraphics;
+}
+
+void WinSalPrinter::EndPage()
+{
+ HDC hDC = mhDC;
+ if ( hDC && mpGraphics )
+ {
+ mpGraphics->DeInitGraphics();
+ delete mpGraphics;
+ mpGraphics = nullptr;
+ }
+
+ if( ! isValid() )
+ return;
+
+ 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 000000000..e1cacb8b0
--- /dev/null
+++ b/vcl/win/gdi/salvd.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 <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 <opengl/win/gdiimpl.hxx>
+#include <sal/log.hxx>
+#include <o3tl/temporary.hxx>
+
+HBITMAP WinSalVirtualDevice::ImplCreateVirDevBitmap(HDC hDC, long nDX, 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* pSGraphics,
+ long &nDX, long &nDY,
+ DeviceFormat eFormat,
+ const SystemGraphicsData* pData )
+{
+ WinSalGraphics* pGraphics = static_cast<WinSalGraphics*>(pSGraphics);
+ 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( pGraphics->getHDC() );
+ SAL_WARN_IF( !hDC, "vcl", "CreateCompatibleDC failed: " << WindowsErrorString( GetLastError() ) );
+ }
+
+ if (!hDC)
+ return nullptr;
+
+ sal_uInt16 nBitCount = (eFormat == DeviceFormat::BITMASK) ? 1 : 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(pGraphics->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,
+ pGraphics->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->setDefPal(SelectPalette( hDC, pSalData->mhDitherPal, TRUE ));
+ RealizePalette( hDC );
+ }
+
+ pVirGraphics->InitGraphics();
+ pVDev->setGraphics(pVirGraphics);
+
+ return std::unique_ptr<SalVirtualDevice>(pVDev);
+}
+
+WinSalVirtualDevice::WinSalVirtualDevice(HDC hDC, HBITMAP hBMP, sal_uInt16 nBitCount, bool bForeignDC, long nWidth, 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;
+
+ // destroy saved DC
+ if( mpGraphics->getDefPal() )
+ SelectPalette( mpGraphics->getHDC(), mpGraphics->getDefPal(), TRUE );
+ mpGraphics->DeInitGraphics();
+ if( mhDefBmp )
+ SelectBitmap( mpGraphics->getHDC(), mhDefBmp );
+ if( !mbForeignDC )
+ DeleteDC( mpGraphics->getHDC() );
+}
+
+SalGraphics* WinSalVirtualDevice::AcquireGraphics()
+{
+ if ( mbGraphics )
+ return nullptr;
+
+ if ( mpGraphics )
+ mbGraphics = true;
+
+ return mpGraphics.get();
+}
+
+void WinSalVirtualDevice::ReleaseGraphics( SalGraphics* )
+{
+ mbGraphics = false;
+}
+
+bool WinSalVirtualDevice::SetSize( long nDX, 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 000000000..056540921
--- /dev/null
+++ b/vcl/win/gdi/winlayout.cxx
@@ -0,0 +1,635 @@
+
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <osl/module.h>
+#include <osl/file.h>
+#include <sal/log.hxx>
+
+#include <comphelper/windowserrorstring.hxx>
+#include <comphelper/scopeguard.hxx>
+
+#include <opengl/win/gdiimpl.hxx>
+#include <opengl/win/winlayout.hxx>
+
+#include <vcl/opengl/OpenGLHelper.hxx>
+#include <win/salgdi.h>
+#include <win/saldata.hxx>
+#include <win/wingdiimpl.hxx>
+#include <outdev.h>
+
+#include <win/DWriteTextRenderer.hxx>
+#include <win/scoped_gdi.hxx>
+
+#include <sft.hxx>
+#include <sallayout.hxx>
+
+#include <cstdio>
+#include <cstdlib>
+
+#include <rtl/character.hxx>
+
+#include <boost/functional/hash.hpp>
+#include <algorithm>
+
+#include <shlwapi.h>
+#include <winver.h>
+
+GlobalWinGlyphCache * GlobalWinGlyphCache::get()
+{
+ SalData *data = GetSalData();
+ if (!data->m_pGlobalWinGlyphCache)
+ {
+ if (OpenGLHelper::isVCLOpenGLEnabled())
+ data->m_pGlobalWinGlyphCache.reset(new OpenGLGlobalWinGlyphCache);
+ }
+ return data->m_pGlobalWinGlyphCache.get();
+}
+
+bool WinFontInstance::CacheGlyphToAtlas(HDC hDC, HFONT hFont, int nGlyphIndex,
+ SalGraphics& rGraphics, const GenericSalLayout& rLayout)
+{
+ WinGlyphDrawElement aElement;
+
+ ScopedHDC aHDC(CreateCompatibleDC(hDC));
+
+ if (!aHDC)
+ {
+ SAL_WARN("vcl.gdi", "CreateCompatibleDC failed: " << WindowsErrorString(GetLastError()));
+ return false;
+ }
+
+ const HFONT hOrigFont = static_cast<HFONT>(SelectObject(aHDC.get(), hFont));
+ if (hOrigFont == nullptr)
+ {
+ SAL_WARN("vcl.gdi", "SelectObject failed: " << WindowsErrorString(GetLastError()));
+ return false;
+ }
+ const ::comphelper::ScopeGuard aHFONTrestoreScopeGuard(
+ [&aHDC,hOrigFont]() { SelectFont(aHDC.get(), hOrigFont); });
+
+ // For now we assume DWrite is present and we won't bother with fallback paths.
+ D2DWriteTextOutRenderer * pTxt = dynamic_cast<D2DWriteTextOutRenderer *>(&TextOutRenderer::get(true));
+ if (!pTxt)
+ return false;
+
+ pTxt->changeTextAntiAliasMode(D2DTextAntiAliasMode::AntiAliased);
+
+ if (!pTxt->BindFont(aHDC.get()))
+ {
+ SAL_WARN("vcl.gdi", "Binding of font failed. The font might not be supported by DirectWrite.");
+ return false;
+ }
+ const ::comphelper::ScopeGuard aFontReleaseScopeGuard([&pTxt]() { pTxt->ReleaseFont(); });
+
+ std::vector<WORD> aGlyphIndices(1);
+ aGlyphIndices[0] = nGlyphIndex;
+ // Fetch the ink boxes and calculate the size of the atlas.
+ tools::Rectangle bounds(0, 0, 0, 0);
+ auto aInkBoxes = pTxt->GetGlyphInkBoxes(aGlyphIndices.data(), aGlyphIndices.data() + 1);
+ if (aInkBoxes.empty())
+ return false;
+
+ for (auto &box : aInkBoxes)
+ bounds.Union(box + Point(bounds.Right(), 0));
+
+ // bounds.Top() is the offset from the baseline at (0,0) to the top of the
+ // inkbox.
+ aElement.mnBaselineOffset = -bounds.Top();
+ aElement.mnHeight = bounds.getHeight();
+ aElement.mbVertical = false;
+
+ // Try hard to avoid overlap as we want to be able to use
+ // individual rectangles for each glyph. The ABC widths don't
+ // take anti-aliasing into consideration. Let's hope that leaving
+ // "extra" space between glyphs will help.
+ std::vector<float> aGlyphAdv(1); // offsets between glyphs
+ std::vector<DWRITE_GLYPH_OFFSET> aGlyphOffset(1, {0.0f, 0.0f});
+ std::vector<int> aEnds(1); // end of each glyph box
+ float fHScale = getHScale();
+ float totWidth = 0;
+ {
+ int overhang = aInkBoxes[0].Left();
+ int blackWidth = aInkBoxes[0].getWidth() * fHScale; // width of non-AA pixels
+ aElement.maLeftOverhangs = overhang;
+
+ aGlyphAdv[0] = blackWidth + aElement.getExtraSpace();
+ aGlyphOffset[0].advanceOffset = -overhang;
+
+ totWidth += aGlyphAdv[0];
+ aEnds[0] = totWidth;
+ }
+ // Leave extra space also at top and bottom
+ int nBitmapWidth = totWidth;
+ int nBitmapHeight = bounds.getHeight() + aElement.getExtraSpace();
+
+ UINT nPos = 0;
+
+ aElement.maLocation.SetLeft(nPos);
+ aElement.maLocation.SetRight(aEnds[0]);
+ aElement.maLocation.SetTop(0);
+ aElement.maLocation.SetBottom(bounds.getHeight() + aElement.getExtraSpace());
+ nPos = aEnds[0];
+
+ std::unique_ptr<CompatibleDC> aDC(CompatibleDC::create(rGraphics, 0, 0, nBitmapWidth, nBitmapHeight));
+
+ SetTextColor(aDC->getCompatibleHDC(), RGB(0, 0, 0));
+ SetBkColor(aDC->getCompatibleHDC(), RGB(255, 255, 255));
+
+ aDC->fill(RGB(0xff, 0xff, 0xff));
+
+ pTxt->BindDC(aDC->getCompatibleHDC(), tools::Rectangle(0, 0, nBitmapWidth, nBitmapHeight));
+ auto pRT = pTxt->GetRenderTarget();
+
+ ID2D1SolidColorBrush* pBrush = nullptr;
+ if (!SUCCEEDED(pRT->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Black), &pBrush)))
+ return false;
+
+ D2D1_POINT_2F baseline = {
+ static_cast<FLOAT>(aElement.getExtraOffset()),
+ static_cast<FLOAT>(aElement.getExtraOffset() + aElement.mnBaselineOffset)
+ };
+
+ DWRITE_GLYPH_RUN glyphs = {
+ pTxt->GetFontFace(),
+ pTxt->GetEmHeight(),
+ 1,
+ aGlyphIndices.data(),
+ aGlyphAdv.data(),
+ aGlyphOffset.data(),
+ false,
+ 0
+ };
+
+ WinFontTransformGuard aTransformGuard(pRT, fHScale, rLayout, baseline);
+ pRT->BeginDraw();
+ pRT->DrawGlyphRun(baseline, &glyphs, pBrush);
+ HRESULT hResult = pRT->EndDraw();
+
+ pBrush->Release();
+
+ switch (hResult)
+ {
+ case S_OK:
+ break;
+ case D2DERR_RECREATE_TARGET:
+ pTxt->CreateRenderTarget();
+ break;
+ default:
+ SAL_WARN("vcl.gdi", "DrawGlyphRun-EndDraw failed: " << WindowsErrorString(GetLastError()));
+ return false;
+ }
+
+ if (!GlobalWinGlyphCache::get()->AllocateTexture(aElement, aDC.get()))
+ return false;
+
+ maWinGlyphCache.PutDrawElementInCache(std::move(aElement), nGlyphIndex);
+
+ return true;
+}
+
+TextOutRenderer & TextOutRenderer::get(bool bUseDWrite)
+{
+ 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)
+ {
+ pSalData->m_pD2DWriteTextOutRenderer.reset(new D2DWriteTextOutRenderer());
+ }
+ 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)
+{
+ HFONT hFont = static_cast<HFONT>(GetCurrentObject( hDC, OBJ_FONT ));
+ ScopedHFONT hAltFont;
+ bool bUseAltFont = false;
+ bool bShift = false;
+ if (rLayout.GetFont().GetFontSelectPattern().mbVertical)
+ {
+ LOGFONTW aLogFont;
+ GetObjectW(hFont, sizeof(aLogFont), &aLogFont);
+ if (aLogFont.lfFaceName[0] == '@')
+ {
+ memmove(&aLogFont.lfFaceName[0], &aLogFont.lfFaceName[1],
+ sizeof(aLogFont.lfFaceName)-sizeof(aLogFont.lfFaceName[0]));
+ hAltFont.reset(CreateFontIndirectW(&aLogFont));
+ }
+ else
+ {
+ bShift = true;
+ aLogFont.lfEscapement += 2700;
+ aLogFont.lfOrientation = aLogFont.lfEscapement;
+ hAltFont.reset(CreateFontIndirectW(&aLogFont));
+ }
+ }
+
+ UINT nTextAlign = GetTextAlign ( hDC );
+ int nStart = 0;
+ Point aPos(0, 0);
+ const GlyphItem* pGlyph;
+ while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart))
+ {
+ WORD glyphWStr[] = { pGlyph->glyphId() };
+ if (hAltFont && pGlyph->IsVertical() == bUseAltFont)
+ {
+ bUseAltFont = !bUseAltFont;
+ SelectFont(hDC, bUseAltFont ? hAltFont.get() : hFont);
+ }
+ if (bShift && pGlyph->IsVertical())
+ SetTextAlign(hDC, TA_TOP|TA_LEFT);
+
+ ExtTextOutW(hDC, aPos.X(), aPos.Y(), ETO_GLYPH_INDEX, nullptr, LPCWSTR(&glyphWStr), 1, nullptr);
+
+ if (bShift && pGlyph->IsVertical())
+ SetTextAlign(hDC, nTextAlign);
+ }
+ if (hAltFont)
+ {
+ if (bUseAltFont)
+ SelectFont(hDC, hFont);
+ }
+
+ 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 FontSelectPattern& rFSP)
+ : LogicalFontInstance(rPFF, rFSP)
+ , m_pGraphics(nullptr)
+ , m_hFont(nullptr)
+ , m_fScale(1.0f)
+{
+}
+
+WinFontInstance::~WinFontInstance()
+{
+ if (m_hFont)
+ ::DeleteFont(m_hFont);
+}
+
+bool WinFontInstance::hasHScale() const
+{
+ const FontSelectPattern &rPattern = GetFontSelectPattern();
+ int nHeight(rPattern.mnHeight);
+ int nWidth(rPattern.mnWidth ? rPattern.mnWidth * GetAverageWidthFactor() : nHeight);
+ return nWidth != nHeight;
+}
+
+float WinFontInstance::getHScale() const
+{
+ const FontSelectPattern& rPattern = GetFontSelectPattern();
+ int nHeight(rPattern.mnHeight);
+ if (!nHeight)
+ return 1.0;
+ float nWidth(rPattern.mnWidth ? rPattern.mnWidth * GetAverageWidthFactor() : nHeight);
+ return nWidth / nHeight;
+}
+
+namespace {
+
+struct BlobReference
+{
+ hb_blob_t* mpBlob;
+ BlobReference(hb_blob_t* pBlob) : mpBlob(pBlob)
+ {
+ hb_blob_reference(mpBlob);
+ }
+ BlobReference(BlobReference const & other)
+ : mpBlob(other.mpBlob)
+ {
+ hb_blob_reference(mpBlob);
+ }
+ ~BlobReference() { hb_blob_destroy(mpBlob); }
+};
+
+}
+
+using BlobCacheKey = std::pair<rtl::Reference<PhysicalFontFace>, hb_tag_t>;
+
+namespace {
+
+struct BlobCacheKeyHash
+{
+ std::size_t operator()(BlobCacheKey const& rKey) const
+ {
+ std::size_t seed = 0;
+ boost::hash_combine(seed, rKey.first.get());
+ boost::hash_combine(seed, rKey.second);
+ return seed;
+ }
+};
+
+}
+
+static hb_blob_t* getFontTable(hb_face_t* /*face*/, hb_tag_t nTableTag, void* pUserData)
+{
+ static o3tl::lru_map<BlobCacheKey, BlobReference, BlobCacheKeyHash> gCache(50);
+
+ WinFontInstance* pFont = static_cast<WinFontInstance*>(pUserData);
+ HDC hDC = pFont->GetGraphics()->getHDC();
+ HFONT hFont = pFont->GetHFONT();
+ assert(hDC);
+ assert(hFont);
+
+ BlobCacheKey cacheKey { rtl::Reference<PhysicalFontFace>(pFont->GetFontFace()), nTableTag };
+ auto it = gCache.find(cacheKey);
+ if (it != gCache.end())
+ {
+ hb_blob_reference(it->second.mpBlob);
+ return it->second.mpBlob;
+ }
+
+ sal_uLong nLength = 0;
+ unsigned char* pBuffer = nullptr;
+
+ HGDIOBJ hOrigFont = SelectObject(hDC, hFont);
+ nLength = ::GetFontData(hDC, OSL_NETDWORD(nTableTag), 0, nullptr, 0);
+ if (nLength > 0 && nLength != GDI_ERROR)
+ {
+ pBuffer = new unsigned char[nLength];
+ ::GetFontData(hDC, OSL_NETDWORD(nTableTag), 0, pBuffer, nLength);
+ }
+ SelectObject(hDC, hOrigFont);
+
+ if (!pBuffer)
+ return nullptr;
+
+ hb_blob_t* pBlob = hb_blob_create(reinterpret_cast<const char*>(pBuffer), nLength, HB_MEMORY_MODE_READONLY,
+ pBuffer, [](void* data){ delete[] static_cast<unsigned char*>(data); });
+ if (!pBlob)
+ return pBlob;
+ gCache.insert({cacheKey, BlobReference(pBlob)});
+ return pBlob;
+}
+
+hb_font_t* WinFontInstance::ImplInitHbFont()
+{
+ assert(m_pGraphics);
+ hb_font_t* pHbFont = InitHbFont(hb_face_create_for_tables(getFontTable, this, nullptr));
+
+ // Calculate the AverageWidthFactor, see LogicalFontInstance::GetScale().
+ if (GetFontSelectPattern().mnWidth)
+ {
+ double nUPEM = hb_face_get_upem(hb_font_get_face(pHbFont));
+
+ 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);
+ }
+
+ return pHbFont;
+}
+
+void WinFontInstance::SetGraphics(WinSalGraphics *pGraphics)
+{
+ m_pGraphics = pGraphics;
+ if (m_hFont)
+ return;
+ HFONT hOrigFont;
+ m_hFont = m_pGraphics->ImplDoSetFont(GetFontSelectPattern(), GetFontFace(), hOrigFont);
+ SelectObject(m_pGraphics->getHDC(), hOrigFont);
+}
+
+bool WinSalGraphics::CacheGlyphs(const GenericSalLayout& rLayout)
+{
+ static bool bDoGlyphCaching = (std::getenv("SAL_DISABLE_GLYPH_CACHING") == nullptr);
+ if (!bDoGlyphCaching)
+ return false;
+
+ if (rLayout.GetOrientation())
+ // Our caching is incomplete, skip it for non-horizontal text.
+ return false;
+
+ HDC hDC = getHDC();
+ WinFontInstance& rFont = *static_cast<WinFontInstance*>(&rLayout.GetFont());
+ HFONT hFONT = rFont.GetHFONT();
+
+ int nStart = 0;
+ Point aPos(0, 0);
+ const GlyphItem* pGlyph;
+ while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart))
+ {
+ if (!rFont.GetWinGlyphCache().IsGlyphCached(pGlyph->glyphId()))
+ {
+ if (!rFont.CacheGlyphToAtlas(hDC, hFONT, pGlyph->glyphId(), *this, rLayout))
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool WinSalGraphics::DrawCachedGlyphs(const GenericSalLayout& rLayout)
+{
+ HDC hDC = getHDC();
+
+ tools::Rectangle aRect;
+ rLayout.GetBoundRect(aRect);
+
+ COLORREF color = GetTextColor(hDC);
+ Color salColor(GetRValue(color), GetGValue(color), GetBValue(color));
+
+ WinSalGraphicsImplBase *pImpl = dynamic_cast<WinSalGraphicsImplBase*>(mpImpl.get());
+ if (!pImpl->UseTextDraw())
+ return false;
+
+ WinFontInstance& rFont = *static_cast<WinFontInstance*>(&rLayout.GetFont());
+
+ int nStart = 0;
+ Point aPos(0, 0);
+ const GlyphItem* pGlyph;
+ while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart))
+ {
+ WinGlyphDrawElement& rElement(rFont.GetWinGlyphCache().GetDrawElement(pGlyph->glyphId()));
+ const CompatibleDC::Texture* texture = rElement.maTexture.get();
+
+ if (!texture || !texture->isValid())
+ return false;
+
+ SalTwoRect a2Rects(0, 0,
+ texture->GetWidth(), texture->GetHeight(),
+ aPos.X() - rElement.getExtraOffset() + rElement.maLeftOverhangs,
+ aPos.Y() - rElement.mnBaselineOffset - rElement.getExtraOffset(),
+ texture->GetWidth(), texture->GetHeight());
+
+ pImpl->DeferredTextDraw(texture, salColor, a2Rects);
+ }
+
+ return true;
+}
+
+static void PruneGlyphCache()
+{
+ GlobalWinGlyphCache::get()->Prune();
+}
+
+void WinSalGraphics::DrawTextLayout(const GenericSalLayout& rLayout, HDC hDC, bool bUseDWrite)
+{
+ TextOutRenderer &render = TextOutRenderer::get(bUseDWrite);
+ render(rLayout, *this, hDC);
+}
+
+void WinSalGraphics::DrawTextLayout(const GenericSalLayout& rLayout)
+{
+ WinSalGraphicsImplBase* pImpl = dynamic_cast<WinSalGraphicsImplBase*>(mpImpl.get());
+ if( !mbPrinter && pImpl->DrawTextLayout(rLayout))
+ return; // handled by pImpl
+
+ HDC hDC = getHDC();
+ const WinFontInstance* pWinFont = static_cast<const WinFontInstance*>(&rLayout.GetFont());
+ const HFONT hLayoutFont = pWinFont->GetHFONT();
+ bool bUseClassic = !pImpl->UseTextDraw() || mbPrinter;
+
+ // Our DirectWrite renderer is incomplete, skip it for vertical text where glyphs are not
+ // rotated.
+ bool bForceGDI = rLayout.GetFont().GetFontSelectPattern().mbVertical;
+
+ if (bUseClassic)
+ {
+ // no OpenGL, just classic rendering
+ const HFONT hOrigFont = ::SelectFont(hDC, hLayoutFont);
+ DrawTextLayout(rLayout, hDC, false);
+ ::SelectFont(hDC, hOrigFont);
+ }
+ // if we can't draw the cached OpenGL glyphs, try to draw a full OpenGL layout
+ else if (!bForceGDI && CacheGlyphs(rLayout) && DrawCachedGlyphs(rLayout))
+ {
+ PruneGlyphCache();
+ }
+ else
+ {
+ PruneGlyphCache(); // prune the cache from the failed calls above
+
+ // We have to render the text to a hidden texture, and draw it.
+ //
+ // Note that Windows GDI does not really support the alpha correctly
+ // when drawing - ie. it draws nothing to the alpha channel when
+ // rendering the text, even the antialiasing is done as 'real' pixels,
+ // not alpha...
+ //
+ // Luckily, this does not really limit us:
+ //
+ // To blend properly, we draw the texture, but then use it as an alpha
+ // channel for solid color (that will define the text color). This
+ // destroys the subpixel antialiasing - turns it into 'classic'
+ // antialiasing - but that is the best we can do, because the subpixel
+ // antialiasing needs to know what is in the background: When the
+ // background is white, or white-ish, it does the subpixel, but when
+ // there is a color, it just darkens the color (and does this even
+ // when part of the character is on a colored background, and part on
+ // white). It has to work this way, the results would look strange
+ // otherwise.
+ //
+ // For the GL rendering to work even with the subpixel antialiasing,
+ // we would need to get the current texture from the screen, let GDI
+ // draw the text to it (so that it can decide well where to use the
+ // subpixel and where not), and draw the result - but in that case we
+ // don't need alpha anyway.
+ //
+ // TODO: check the performance of this 2nd approach at some stage and
+ // switch to that if it performs well.
+
+ tools::Rectangle aRect;
+ rLayout.GetBoundRect(aRect);
+ if( aRect.IsEmpty())
+ return;
+
+ pImpl->PreDrawText();
+
+ std::unique_ptr<CompatibleDC> aDC(CompatibleDC::create(*this, aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight()));
+
+ // we are making changes to the DC, make sure we got a new one
+ assert(aDC->getCompatibleHDC() != hDC);
+
+ RECT aWinRect = { aRect.Left(), aRect.Top(), aRect.Left() + aRect.GetWidth(), aRect.Top() + aRect.GetHeight() };
+ ::FillRect(aDC->getCompatibleHDC(), &aWinRect, static_cast<HBRUSH>(::GetStockObject(WHITE_BRUSH)));
+
+ // setup the hidden DC with black color and white background, we will
+ // use the result of the text drawing later as a mask only
+ const HFONT hOrigFont = ::SelectFont(aDC->getCompatibleHDC(), hLayoutFont);
+
+ ::SetTextColor(aDC->getCompatibleHDC(), RGB(0, 0, 0));
+ ::SetBkColor(aDC->getCompatibleHDC(), RGB(255, 255, 255));
+
+ UINT nTextAlign = ::GetTextAlign(hDC);
+ ::SetTextAlign(aDC->getCompatibleHDC(), nTextAlign);
+
+ COLORREF color = ::GetTextColor(hDC);
+ Color salColor(GetRValue(color), GetGValue(color), GetBValue(color));
+
+ // the actual drawing
+ DrawTextLayout(rLayout, aDC->getCompatibleHDC(), !bForceGDI);
+
+ std::unique_ptr<CompatibleDC::Texture> xTexture(aDC->getAsMaskTexture());
+ if (xTexture)
+ pImpl->DrawTextMask(xTexture.get(), salColor, aDC->getTwoRect());
+
+ ::SelectFont(aDC->getCompatibleHDC(), hOrigFont);
+
+ pImpl->PostDrawText();
+ }
+}
+
+/* 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 000000000..b9d56fcd1
--- /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 000000000..8517d965f
--- /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 000000000..7634a7d34
--- /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 000000000..e444e42bf
--- /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 000000000..e92cc65e7
--- /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 000000000..04d0b09c3
--- /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 000000000..a0e25b16d
--- /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 000000000..20322bc97
--- /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 000000000..7166636a1
--- /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 000000000..8cb71234b
--- /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 000000000..46ee06d16
--- /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 000000000..0ccac50f4
--- /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 000000000..c238b7e10
--- /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 000000000..02abb7ab7
--- /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 000000000..938ece03f
--- /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 000000000..25fe85b76
--- /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 000000000..d3c4bc93a
--- /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 000000000..495fd5e17
--- /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 000000000..450c09443
--- /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 000000000..ac8de5da6
--- /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 000000000..e67f0539f
--- /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 000000000..c40cf591e
--- /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 000000000..327fb0697
--- /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 000000000..38504fa23
--- /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 000000000..f630b837d
--- /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 000000000..10dd5ba0d
--- /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 000000000..b19d3f825
--- /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 000000000..5318d8f22
--- /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 000000000..c489a6403
--- /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 000000000..30e5685b6
--- /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 000000000..3ff56d007
--- /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 000000000..623c33ac2
--- /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 000000000..3b911cd01
--- /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 000000000..9467f1e28
--- /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 000000000..60a5242c2
--- /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 000000000..01e7d31ea
--- /dev/null
+++ b/vcl/win/src/dtext.cur
Binary files differ
diff --git a/vcl/win/src/fill.cur b/vcl/win/src/fill.cur
new file mode 100644
index 000000000..78f5fad87
--- /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 000000000..5cf221145
--- /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 000000000..e47c1dea2
--- /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 000000000..6cc498a02
--- /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 000000000..1e32b9235
--- /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 000000000..e05eb836e
--- /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 000000000..d079eb9fe
--- /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 000000000..4d67cbe47
--- /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 000000000..1bb7b0306
--- /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 000000000..6abee2381
--- /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 000000000..d044981a3
--- /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 000000000..630fa1bc3
--- /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 000000000..81d3af5a0
--- /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 000000000..28dbb2a90
--- /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 000000000..061b3ba92
--- /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 000000000..4497dacd9
--- /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 000000000..efbbead89
--- /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 000000000..649444e9e
--- /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 000000000..43c2a54a1
--- /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 000000000..a4c5cf574
--- /dev/null
+++ b/vcl/win/src/salsrc.rc
@@ -0,0 +1,89 @@
+/* -*- 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_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 000000000..b2a0a07a6
--- /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 000000000..3683e20df
--- /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 000000000..007182d73
--- /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 000000000..986f01395
--- /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 000000000..adabba1a2
--- /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 000000000..a95eb85af
--- /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 000000000..a4bbf7e8e
--- /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 000000000..776177901
--- /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 000000000..bfa8fdfdb
--- /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 000000000..e0c210603
--- /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 000000000..0d4f12f82
--- /dev/null
+++ b/vcl/win/window/keynames.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 <string.h>
+#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!
+
+ static 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" },
+ };
+
+ static 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" },
+ };
+
+ static 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" },
+ };
+
+ static 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" },
+ };
+
+ static 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" },
+ };
+
+ static 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" },
+ };
+
+ static 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" },
+ };
+
+ static const struct KeyboardReplacements aKeyboards[] =
+ {
+ { "ast",aImplReplacements_Asturian, SAL_N_ELEMENTS(aImplReplacements_Asturian) },
+ { "ca", aImplReplacements_Catalan, SAL_N_ELEMENTS(aImplReplacements_Catalan) },
+ { "et", aImplReplacements_Estonian, SAL_N_ELEMENTS(aImplReplacements_Estonian) },
+ { "hu", aImplReplacements_Hungarian, SAL_N_ELEMENTS(aImplReplacements_Hungarian) },
+ { "lt", aImplReplacements_Lithuanian, SAL_N_ELEMENTS(aImplReplacements_Lithuanian) },
+ { "sl", aImplReplacements_Slovenian, SAL_N_ELEMENTS(aImplReplacements_Slovenian) },
+ { "es", aImplReplacements_Spanish, SAL_N_ELEMENTS(aImplReplacements_Spanish) },
+ };
+
+ // translate keycodes, used within the displayed menu shortcuts
+ OUString getKeysReplacementName( OUString const & pLang, LONG nSymbol )
+ {
+ for( unsigned int n = 0; n < SAL_N_ELEMENTS(aKeyboards); n++ )
+ {
+ if( pLang.equalsAscii( 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 000000000..789df0dd2
--- /dev/null
+++ b/vcl/win/window/salframe.cxx
@@ -0,0 +1,5925 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <unotools/misccfg.hxx>
+
+#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/string.h>
+#include <rtl/ustring.h>
+#include <sal/log.hxx>
+
+#include <osl/module.h>
+
+#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>
+
+#define COMPILE_MULTIMON_STUBS
+#pragma warning(push)
+#pragma warning(disable:4996) // 'GetVersionExA': was declared deprecated
+#include <multimon.h>
+#pragma warning(pop)
+#include <vector>
+
+#include <com/sun/star/uno/Exception.hpp>
+
+#include <oleacc.h>
+#include <com/sun/star/accessibility/XMSAAService.hpp>
+#ifndef WM_GETOBJECT // TESTME does this ever happen ?
+# define WM_GETOBJECT 0x003D
+#endif
+
+#include <time.h>
+
+#if !defined WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+#include <shobjidl.h>
+#include <propkey.h>
+#include <propvarutil.h>
+#include <shellapi.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 SPI_GETWHEELSCROLLCHARS
+# define SPI_GETWHEELSCROLLCHARS 0x006C
+#endif
+#ifndef SPI_SETWHEELSCROLLCHARS
+# define SPI_SETWHEELSCROLLCHARS 0x006D
+#endif
+#ifndef WM_MOUSEHWHEEL
+# define WM_MOUSEHWHEEL 0x020E
+#endif
+#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_UTF32ToSurrogate1(ch) (((unsigned long) (ch) - 0x10000) / 0x400 + 0xD800)
+#define Uni_UTF32ToSurrogate2(ch) ((static_cast<unsigned long>(ch) - 0x10000) % 0x400 + 0xDC00)
+#define Uni_SupplementaryPlanesStart 0x10000
+
+static void UpdateFrameGeometry( HWND hWnd, WinSalFrame* pFrame );
+static void SetMaximizedFrameGeometry( HWND hWnd, WinSalFrame* pFrame, RECT* pParentRect = nullptr );
+
+static void ImplSaveFrameState( WinSalFrame* pFrame )
+{
+ // save position, size and state for GetWindowState()
+ if ( !pFrame->mbFullScreen )
+ {
+ bool bVisible = (GetWindowStyle( pFrame->mhWnd ) & WS_VISIBLE) != 0;
+ if ( IsIconic( pFrame->mhWnd ) )
+ {
+ pFrame->maState.mnState |= WindowStateState::Minimized;
+ if ( bVisible )
+ pFrame->mnShowState = SW_SHOWMAXIMIZED;
+ }
+ else if ( IsZoomed( pFrame->mhWnd ) )
+ {
+ pFrame->maState.mnState &= ~WindowStateState::Minimized;
+ pFrame->maState.mnState |= WindowStateState::Maximized;
+ if ( bVisible )
+ pFrame->mnShowState = SW_SHOWMAXIMIZED;
+ pFrame->mbRestoreMaximize = true;
+
+ WINDOWPLACEMENT aPlacement;
+ aPlacement.length = sizeof(aPlacement);
+ if( GetWindowPlacement( pFrame->mhWnd, &aPlacement ) )
+ {
+ RECT aRect = aPlacement.rcNormalPosition;
+ RECT aRect2 = aRect;
+ AdjustWindowRectEx( &aRect2, GetWindowStyle( pFrame->mhWnd ),
+ FALSE, GetWindowExStyle( pFrame->mhWnd ) );
+ long nTopDeco = abs( aRect.top - aRect2.top );
+ long nLeftDeco = abs( aRect.left - aRect2.left );
+ long nBottomDeco = abs( aRect.bottom - aRect2.bottom );
+ long nRightDeco = abs( aRect.right - aRect2.right );
+
+ pFrame->maState.mnX = aRect.left + nLeftDeco;
+ pFrame->maState.mnY = aRect.top + nTopDeco;
+ pFrame->maState.mnWidth = aRect.right - aRect.left - nLeftDeco - nRightDeco;
+ pFrame->maState.mnHeight = aRect.bottom - aRect.top - nTopDeco - nBottomDeco;
+ }
+ }
+ else
+ {
+ RECT aRect;
+ GetWindowRect( pFrame->mhWnd, &aRect );
+
+ // to be consistent with Unix, the frame state is without(!) decoration
+ RECT aRect2 = aRect;
+ AdjustWindowRectEx( &aRect2, GetWindowStyle( pFrame->mhWnd ),
+ FALSE, GetWindowExStyle( pFrame->mhWnd ) );
+ long nTopDeco = abs( aRect.top - aRect2.top );
+ long nLeftDeco = abs( aRect.left - aRect2.left );
+ long nBottomDeco = abs( aRect.bottom - aRect2.bottom );
+ long nRightDeco = abs( aRect.right - aRect2.right );
+
+ pFrame->maState.mnState &= ~WindowStateState(WindowStateState::Minimized | WindowStateState::Maximized);
+ // subtract decoration
+ pFrame->maState.mnX = aRect.left+nLeftDeco;
+ pFrame->maState.mnY = aRect.top+nTopDeco;
+ pFrame->maState.mnWidth = aRect.right-aRect.left-nLeftDeco-nRightDeco;
+ pFrame->maState.mnHeight = aRect.bottom-aRect.top-nTopDeco-nBottomDeco;
+ if ( bVisible )
+ pFrame->mnShowState = SW_SHOWNORMAL;
+ pFrame->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 )
+{
+ // 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;
+ }
+ }
+ }
+}
+
+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->mnWidth = aRect.right;
+ pFrame->mnHeight = aRect.bottom;
+ ImplSaveFrameState( pFrame );
+ pFrame->mbDefPos = true;
+
+ UpdateFrameGeometry( hWnd, pFrame );
+
+ 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 146
+
+static 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
+};
+
+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
+ {
+ sal_Int32 nMonitors = Application::GetScreenCount();
+ if( (pFrame->mnDisplay >= 0) && (pFrame->mnDisplay < nMonitors) )
+ {
+ tools::Rectangle aRect = Application::GetScreenPosSizePixel( pFrame->mnDisplay );
+ nScreenX = aRect.Left();
+ nScreenY = aRect.Top();
+ nScreenDX = aRect.GetWidth();
+ nScreenDY = aRect.GetHeight();
+ }
+ else
+ {
+ tools::Rectangle aCombined = Application::GetScreenPosSizePixel( 0 );
+ for( sal_Int32 i = 1 ; i < nMonitors ; i++ )
+ {
+ aCombined.Union( Application::GetScreenPosSizePixel( i ) );
+ }
+ nScreenX = aCombined.Left();
+ nScreenY = aCombined.Top();
+ nScreenDX = aCombined.GetWidth();
+ nScreenDY = aCombined.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;
+ mnShowState = SW_SHOWNORMAL;
+ mnWidth = 0;
+ mnHeight = 0;
+ 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;
+ mbFullScreen = 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();
+ Point aPoint( maGeometry.nX, maGeometry.nY );
+ size_t nMon = rMonitors.size();
+ for( size_t i = 0; i < nMon; i++ )
+ {
+ if( rMonitors[i].m_aArea.IsInside( aPoint ) )
+ {
+ mnDisplay = static_cast<sal_Int32>(i);
+ maGeometry.nDisplayScreenNumber = static_cast<unsigned int>(i);
+ }
+ }
+ }
+}
+
+bool WinSalFrame::ReleaseFrameGraphicsDC( WinSalGraphics* pGraphics )
+{
+ assert( pGraphics );
+ SalData* pSalData = GetSalData();
+ HDC hDC = pGraphics->getHDC();
+ if ( !hDC )
+ return false;
+ if ( pGraphics->getDefPal() )
+ SelectPalette( hDC, pGraphics->getDefPal(), TRUE );
+ pGraphics->DeInitGraphics();
+ SendMessageW( pSalData->mpInstance->mhComWnd, SAL_MSG_RELEASEDC,
+ reinterpret_cast<WPARAM>(mhWnd), reinterpret_cast<LPARAM>(hDC) );
+ if ( pGraphics == mpThreadGraphics )
+ pSalData->mnCacheDCInUse--;
+ pGraphics->setHDC(nullptr);
+ 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;
+ if ( pSalData->mpMouseLeaveTimer )
+ {
+ delete pSalData->mpMouseLeaveTimer;
+ pSalData->mpMouseLeaveTimer = 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->setDefPal(SelectPalette( hDC, pSalData->mhDitherPal, TRUE ));
+ RealizePalette( hDC );
+ }
+ pGraphics->InitGraphics();
+
+ 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.release()));
+ SAL_WARN_IF(!ret, "vcl", "ERROR: PostMessage() failed!");
+ 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 );
+}
+
+void WinSalFrame::DrawMenuBar()
+{
+ ::DrawMenuBar( mhWnd );
+}
+
+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( long nWidth, long nHeight )
+{
+ mnMinWidth = nWidth;
+ mnMinHeight = nHeight;
+}
+
+void WinSalFrame::SetMaxClientSize( long nWidth, long nHeight )
+{
+ mnMaxWidth = nWidth;
+ mnMaxHeight = nHeight;
+}
+
+void WinSalFrame::SetPosSize( long nX, long nY, long nWidth, 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;
+
+ if ( !(nPosSize & SWP_NOMOVE) && ::GetParent( mhWnd ) )
+ {
+ RECT aParentRect;
+ GetClientRect( ImplGetParentHwnd( mhWnd ), &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;
+
+ HWND parentHwnd = ImplGetParentHwnd( mhWnd );
+ WinSalFrame* pParentFrame = GetWindowPtr( parentHwnd );
+ 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.nX;
+ aPt.y += pParentFrame->maGeometry.nY;
+ }
+ else
+ ClientToScreen( parentHwnd, &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 hWndParent = ::GetParent( mhWnd );
+ // Search for TopLevel Frame
+ while ( hWndParent && (GetWindowStyle( hWndParent ) & WS_CHILD) )
+ hWndParent = ::GetParent( hWndParent );
+ // if the Window has a Parent, then center the window to
+ // the parent, in the other case to the screen
+ if ( hWndParent && !IsIconic( hWndParent ) &&
+ (GetWindowStyle( hWndParent ) & WS_VISIBLE) )
+ {
+ RECT aParentRect;
+ GetWindowRect( hWndParent, &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( mhWnd, 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;
+}
+
+bool WinSalFrame::SetPluginParent( SystemParentData* pNewParent )
+{
+ if ( pNewParent->hWnd == nullptr )
+ {
+ pNewParent->hWnd = GetDesktopWindow();
+ }
+
+ WinSalFrame::mbInReparent = true;
+ ImplSetParentFrame( pNewParent->hWnd, true );
+ WinSalFrame::mbInReparent = false;
+ return true;
+}
+
+void WinSalFrame::GetWorkArea( tools::Rectangle &rRect )
+{
+ RECT aRect;
+ ImplSalGetWorkArea( mhWnd, &aRect, nullptr );
+ rRect.SetLeft( aRect.left );
+ rRect.SetRight( aRect.right-1 );
+ rRect.SetTop( aRect.top );
+ rRect.SetBottom( aRect.bottom-1 );
+}
+
+void WinSalFrame::GetClientSize( long& rWidth, long& rHeight )
+{
+ rWidth = maGeometry.nWidth;
+ rHeight = maGeometry.nHeight;
+}
+
+void WinSalFrame::SetWindowState( const SalFrameState* 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 ) );
+ long nTopDeco = abs( aWinRect.top - aRect2.top );
+ long nLeftDeco = abs( aWinRect.left - aRect2.left );
+ long nBottomDeco = abs( aWinRect.bottom - aRect2.bottom );
+ long nRightDeco = abs( aWinRect.right - aRect2.right );
+
+ // adjust window position/size to fit the screen
+ if ( !(pState->mnMask & (WindowStateMask::X | WindowStateMask::Y)) )
+ nPosSize |= SWP_NOMOVE;
+ if ( !(pState->mnMask & (WindowStateMask::Width | WindowStateMask::Height)) )
+ nPosSize |= SWP_NOSIZE;
+ if ( pState->mnMask & WindowStateMask::X )
+ nX = static_cast<int>(pState->mnX) - nLeftDeco;
+ else
+ nX = aWinRect.left;
+ if ( pState->mnMask & WindowStateMask::Y )
+ nY = static_cast<int>(pState->mnY) - nTopDeco;
+ else
+ nY = aWinRect.top;
+ if ( pState->mnMask & WindowStateMask::Width )
+ nWidth = static_cast<int>(pState->mnWidth) + nLeftDeco + nRightDeco;
+ else
+ nWidth = aWinRect.right-aWinRect.left;
+ if ( pState->mnMask & WindowStateMask::Height )
+ nHeight = static_cast<int>(pState->mnHeight) + 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
+ bool bVisible = (GetWindowStyle( mhWnd ) & WS_VISIBLE) != 0;
+ bool bUpdateHiddenFramePos = false;
+ if ( !bVisible )
+ {
+ aPlacement.showCmd = SW_HIDE;
+
+ if ( mbOverwriteState )
+ {
+ if ( pState->mnMask & WindowStateMask::State )
+ {
+ if ( pState->mnState & WindowStateState::Minimized )
+ mnShowState = SW_SHOWMINIMIZED;
+ else if ( pState->mnState & WindowStateState::Maximized )
+ {
+ mnShowState = SW_SHOWMAXIMIZED;
+ bUpdateHiddenFramePos = true;
+ }
+ else if ( pState->mnState & WindowStateState::Normal )
+ mnShowState = SW_SHOWNORMAL;
+ }
+ }
+ }
+ else
+ {
+ if ( pState->mnMask & WindowStateMask::State )
+ {
+ if ( pState->mnState & WindowStateState::Minimized )
+ {
+ if ( pState->mnState & WindowStateState::Maximized )
+ aPlacement.flags |= WPF_RESTORETOMAXIMIZED;
+ aPlacement.showCmd = SW_SHOWMINIMIZED;
+ }
+ else if ( pState->mnState & WindowStateState::Maximized )
+ aPlacement.showCmd = SW_SHOWMAXIMIZED;
+ else if ( pState->mnState & WindowStateState::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 ( !IsIconic( mhWnd ) && !IsZoomed( mhWnd ) &&
+ (!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.nX, maGeometry.nY, maGeometry.nWidth, maGeometry.nHeight,
+ 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( SalFrameState* pState )
+{
+ if ( maState.mnWidth && maState.mnHeight )
+ {
+ *pState = maState;
+ // #94144# allow Minimize again, should be masked out when read from configuration
+ // 91625 - Don't save minimize
+ //if ( !(pState->mnState & WindowStateState::Maximized) )
+ if ( !(pState->mnState & (WindowStateState::Minimized | WindowStateState::Maximized)) )
+ pState->mnState |= WindowStateState::Normal;
+ return true;
+ }
+
+ return false;
+}
+
+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 )
+ {
+ Point aOldMonPos, aNewMonPos( rMonitors[nNewScreen].m_aArea.TopLeft() );
+ Point aCurPos( maGeometry.nX, maGeometry.nY );
+ for( size_t i = 0; i < nMon; i++ )
+ {
+ if( rMonitors[i].m_aArea.IsInside( aCurPos ) )
+ {
+ aOldMonPos = rMonitors[i].m_aArea.TopLeft();
+ break;
+ }
+ }
+ mnDisplay = nNewScreen;
+ maGeometry.nDisplayScreenNumber = nNewScreen;
+ SetPosSize( aNewMonPos.X() + (maGeometry.nX - aOldMonPos.X()),
+ aNewMonPos.Y() + (maGeometry.nY - 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( bool bFullScreen, sal_Int32 nDisplay )
+{
+ if ( (mbFullScreen == bFullScreen) && (!bFullScreen || (mnDisplay == nDisplay)) )
+ return;
+
+ mbFullScreen = bFullScreen;
+ mnDisplay = nDisplay;
+
+ if ( bFullScreen )
+ {
+ // 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
+ {
+ // 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;
+
+ SalData* pSalData = GetSalData();
+ if ( bStart )
+ {
+ // turn off screen-saver when in Presentation mode
+ SystemParametersInfoW( SPI_GETSCREENSAVEACTIVE, 0,
+ &(pSalData->mbScrSvrEnabled), 0 );
+ if ( pSalData->mbScrSvrEnabled )
+ SystemParametersInfoW( SPI_SETSCREENSAVEACTIVE, FALSE, nullptr, 0 );
+ }
+ else
+ {
+ // turn on screen-saver
+ if ( pSalData->mbScrSvrEnabled )
+ SystemParametersInfoW( SPI_SETSCREENSAVEACTIVE, pSalData->mbScrSvrEnabled, nullptr, 0 );
+ }
+}
+
+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 =
+ {
+ ImplPtrData{ nullptr, IDC_ARROW, 0 }, // POINTER_ARROW
+ { nullptr, nullptr, SAL_RESID_POINTER_NULL }, // POINTER_NULL
+ { nullptr, IDC_WAIT, 0 }, // POINTER_WAIT
+ { nullptr, IDC_IBEAM, 0 }, // POINTER_TEXT
+ { nullptr, IDC_HELP, 0 }, // POINTER_HELP
+ { nullptr, IDC_CROSS, 0 }, // POINTER_CROSS
+ { nullptr, IDC_SIZEALL, 0 }, // POINTER_MOVE
+ { nullptr, IDC_SIZENS, 0 }, // POINTER_NSIZE
+ { nullptr, IDC_SIZENS, 0 }, // POINTER_SSIZE
+ { nullptr, IDC_SIZEWE, 0 }, // POINTER_WSIZE
+ { nullptr, IDC_SIZEWE, 0 }, // POINTER_ESIZE
+ { nullptr, IDC_SIZENWSE, 0 }, // POINTER_NWSIZE
+ { nullptr, IDC_SIZENESW, 0 }, // POINTER_NESIZE
+ { nullptr, IDC_SIZENESW, 0 }, // POINTER_SWSIZE
+ { nullptr, IDC_SIZENWSE, 0 }, // POINTER_SESIZE
+ { nullptr, IDC_SIZENS, 0 }, // POINTER_WINDOW_NSIZE
+ { nullptr, IDC_SIZENS, 0 }, // POINTER_WINDOW_SSIZE
+ { nullptr, IDC_SIZEWE, 0 }, // POINTER_WINDOW_WSIZE
+ { nullptr, IDC_SIZEWE, 0 }, // POINTER_WINDOW_ESIZE
+ { nullptr, IDC_SIZENWSE, 0 }, // POINTER_WINDOW_NWSIZE
+ { nullptr, IDC_SIZENESW, 0 }, // POINTER_WINDOW_NESIZE
+ { nullptr, IDC_SIZENESW, 0 }, // POINTER_WINDOW_SWSIZE
+ { nullptr, IDC_SIZENWSE, 0 }, // POINTER_WINDOW_SESIZE
+ { nullptr, IDC_SIZEWE, 0 }, // POINTER_HSPLIT
+ { nullptr, IDC_SIZENS, 0 }, // POINTER_VSPLIT
+ { nullptr, IDC_SIZEWE, 0 }, // POINTER_HSIZEBAR
+ { nullptr, IDC_SIZENS, 0 }, // POINTER_VSIZEBAR
+ { nullptr, IDC_HAND, 0 }, // POINTER_HAND
+ { nullptr, IDC_HAND, 0 }, // POINTER_REFHAND
+ { nullptr, IDC_PEN, 0 }, // POINTER_PEN
+ { nullptr, nullptr, SAL_RESID_POINTER_MAGNIFY }, // POINTER_MAGNIFY
+ { nullptr, nullptr, SAL_RESID_POINTER_FILL }, // POINTER_FILL
+ { nullptr, nullptr, SAL_RESID_POINTER_ROTATE }, // POINTER_ROTATE
+ { nullptr, nullptr, SAL_RESID_POINTER_HSHEAR }, // POINTER_HSHEAR
+ { nullptr, nullptr, SAL_RESID_POINTER_VSHEAR }, // POINTER_VSHEAR
+ { nullptr, nullptr, SAL_RESID_POINTER_MIRROR }, // POINTER_MIRROR
+ { nullptr, nullptr, SAL_RESID_POINTER_CROOK }, // POINTER_CROOK
+ { nullptr, nullptr, SAL_RESID_POINTER_CROP }, // POINTER_CROP
+ { nullptr, nullptr, SAL_RESID_POINTER_MOVEPOINT }, // POINTER_MOVEPOINT
+ { nullptr, nullptr, SAL_RESID_POINTER_MOVEBEZIERWEIGHT }, // POINTER_MOVEBEZIERWEIGHT
+ { nullptr, nullptr, SAL_RESID_POINTER_MOVEDATA }, // POINTER_MOVEDATA
+ { nullptr, nullptr, SAL_RESID_POINTER_COPYDATA }, // POINTER_COPYDATA
+ { nullptr, nullptr, SAL_RESID_POINTER_LINKDATA }, // POINTER_LINKDATA
+ { nullptr, nullptr, SAL_RESID_POINTER_MOVEDATALINK }, // POINTER_MOVEDATALINK
+ { nullptr, nullptr, SAL_RESID_POINTER_COPYDATALINK }, // POINTER_COPYDATALINK
+ { nullptr, nullptr, SAL_RESID_POINTER_MOVEFILE }, // POINTER_MOVEFILE
+ { nullptr, nullptr, SAL_RESID_POINTER_COPYFILE }, // POINTER_COPYFILE
+ { nullptr, nullptr, SAL_RESID_POINTER_LINKFILE }, // POINTER_LINKFILE
+ { nullptr, nullptr, SAL_RESID_POINTER_MOVEFILELINK }, // POINTER_MOVEFILELINK
+ { nullptr, nullptr, SAL_RESID_POINTER_COPYFILELINK }, // POINTER_COPYFILELINK
+ { nullptr, nullptr, SAL_RESID_POINTER_MOVEFILES }, // POINTER_MOVEFILES
+ { nullptr, nullptr, SAL_RESID_POINTER_COPYFILES }, // POINTER_COPYFILES
+ { nullptr, IDC_NO, 0 }, // POINTER_NOTALLOWED
+ { nullptr, nullptr, SAL_RESID_POINTER_DRAW_LINE }, // POINTER_DRAW_LINE
+ { nullptr, nullptr, SAL_RESID_POINTER_DRAW_RECT }, // POINTER_DRAW_RECT
+ { nullptr, nullptr, SAL_RESID_POINTER_DRAW_POLYGON }, // POINTER_DRAW_POLYGON
+ { nullptr, nullptr, SAL_RESID_POINTER_DRAW_BEZIER }, // POINTER_DRAW_BEZIER
+ { nullptr, nullptr, SAL_RESID_POINTER_DRAW_ARC }, // POINTER_DRAW_ARC
+ { nullptr, nullptr, SAL_RESID_POINTER_DRAW_PIE }, // POINTER_DRAW_PIE
+ { nullptr, nullptr, SAL_RESID_POINTER_DRAW_CIRCLECUT }, // POINTER_DRAW_CIRCLECUT
+ { nullptr, nullptr, SAL_RESID_POINTER_DRAW_ELLIPSE }, // POINTER_DRAW_ELLIPSE
+ { nullptr, nullptr, SAL_RESID_POINTER_DRAW_FREEHAND }, // POINTER_DRAW_FREEHAND
+ { nullptr, nullptr, SAL_RESID_POINTER_DRAW_CONNECT }, // POINTER_DRAW_CONNECT
+ { nullptr, nullptr, SAL_RESID_POINTER_DRAW_TEXT }, // POINTER_DRAW_TEXT
+ { nullptr, nullptr, SAL_RESID_POINTER_DRAW_CAPTION }, // POINTER_DRAW_CAPTION
+ { nullptr, nullptr, SAL_RESID_POINTER_CHART }, // POINTER_CHART
+ { nullptr, nullptr, SAL_RESID_POINTER_DETECTIVE }, // POINTER_DETECTIVE
+ { nullptr, nullptr, SAL_RESID_POINTER_PIVOT_COL }, // POINTER_PIVOT_COL
+ { nullptr, nullptr, SAL_RESID_POINTER_PIVOT_ROW }, // POINTER_PIVOT_ROW
+ { nullptr, nullptr, SAL_RESID_POINTER_PIVOT_FIELD }, // POINTER_PIVOT_FIELD
+ { nullptr, nullptr, SAL_RESID_POINTER_CHAIN }, // POINTER_CHAIN
+ { nullptr, nullptr, SAL_RESID_POINTER_CHAIN_NOTALLOWED }, // POINTER_CHAIN_NOTALLOWED
+ { nullptr, nullptr, SAL_RESID_POINTER_AUTOSCROLL_N }, // POINTER_AUTOSCROLL_N
+ { nullptr, nullptr, SAL_RESID_POINTER_AUTOSCROLL_S }, // POINTER_AUTOSCROLL_S
+ { nullptr, nullptr, SAL_RESID_POINTER_AUTOSCROLL_W }, // POINTER_AUTOSCROLL_W
+ { nullptr, nullptr, SAL_RESID_POINTER_AUTOSCROLL_E }, // POINTER_AUTOSCROLL_E
+ { nullptr, nullptr, SAL_RESID_POINTER_AUTOSCROLL_NW }, // POINTER_AUTOSCROLL_NW
+ { nullptr, nullptr, SAL_RESID_POINTER_AUTOSCROLL_NE }, // POINTER_AUTOSCROLL_NE
+ { nullptr, nullptr, SAL_RESID_POINTER_AUTOSCROLL_SW }, // POINTER_AUTOSCROLL_SW
+ { nullptr, nullptr, SAL_RESID_POINTER_AUTOSCROLL_SE }, // POINTER_AUTOSCROLL_SE
+ { nullptr, nullptr, SAL_RESID_POINTER_AUTOSCROLL_NS }, // POINTER_AUTOSCROLL_NS
+ { nullptr, nullptr, SAL_RESID_POINTER_AUTOSCROLL_WE }, // POINTER_AUTOSCROLL_WE
+ { nullptr, nullptr, SAL_RESID_POINTER_AUTOSCROLL_NSWE }, // POINTER_AUTOSCROLL_NSWE
+ { nullptr, nullptr, SAL_RESID_POINTER_TEXT_VERTICAL }, // POINTER_TEXT_VERTICAL
+ { nullptr, nullptr, SAL_RESID_POINTER_PIVOT_DELETE }, // POINTER_PIVOT_DELETE
+
+ // #i32329#
+ { nullptr, nullptr, SAL_RESID_POINTER_TAB_SELECT_S }, // POINTER_TAB_SELECT_S
+ { nullptr, nullptr, SAL_RESID_POINTER_TAB_SELECT_E }, // POINTER_TAB_SELECT_E
+ { nullptr, nullptr, SAL_RESID_POINTER_TAB_SELECT_SE }, // POINTER_TAB_SELECT_SE
+ { nullptr, nullptr, SAL_RESID_POINTER_TAB_SELECT_W }, // POINTER_TAB_SELECT_W
+ { nullptr, nullptr, SAL_RESID_POINTER_TAB_SELECT_SW }, // POINTER_TAB_SELECT_SW
+
+ { nullptr, nullptr, SAL_RESID_POINTER_HIDEWHITESPACE }, // POINTER_HIDEWHITESPACE
+ { nullptr, nullptr, SAL_RESID_POINTER_SHOWWHITESPACE } // POINTER_UNHIDEWHITESPACE
+ };
+
+ // 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( long nX, 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()
+{
+ 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;
+ HDC hDC = GetDC( pFrame->mhWnd );
+ // In case of vertical writing, always append a '@' to the
+ // Windows font name, not only if such a Windows font really is
+ // available (bTestVerticalAvail == false in the below call):
+ // The Windows IME's candidates window seems to always use a
+ // font that has all necessary glyphs, not necessarily the one
+ // specified by this font name; but it seems to decide whether
+ // to use that font's horizontal or vertical variant based on a
+ // '@' in front of this font name.
+ ImplGetLogFontFromFontSelect(hDC, pContext->mpFont->GetFontSelectPattern(),
+ nullptr, aLogFont);
+ ReleaseDC( pFrame->mhWnd, hDC );
+ 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_SEMICOLON:
+ cSVCode = ';';
+ break;
+ case KEY_QUOTERIGHT:
+ cSVCode = '\'';
+ break;
+ case KEY_BRACKETLEFT:
+ cSVCode = '[';
+ break;
+ case KEY_BRACKETRIGHT:
+ 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 long ImplW2I( const wchar_t* pStr )
+{
+ 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 ) );
+ long nDragWidth = GetSystemMetrics( SM_CXDRAG );
+ 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();
+
+ 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 ) ) );
+ aStyleSettings.SetFaceColor( ImplWinColorToSal( GetSysColor( COLOR_3DFACE ) ) );
+ aStyleSettings.SetInactiveTabColor( aStyleSettings.GetFaceColor() );
+ aStyleSettings.SetLightColor( ImplWinColorToSal( GetSysColor( COLOR_3DHILIGHT ) ) );
+ aStyleSettings.SetLightBorderColor( ImplWinColorToSal( GetSysColor( COLOR_3DLIGHT ) ) );
+ aStyleSettings.SetShadowColor( ImplWinColorToSal( GetSysColor( COLOR_3DSHADOW ) ) );
+ aStyleSettings.SetDarkShadowColor( ImplWinColorToSal( GetSysColor( COLOR_3DDKSHADOW ) ) );
+ aStyleSettings.SetHelpColor( ImplWinColorToSal( GetSysColor( COLOR_INFOBK ) ) );
+ aStyleSettings.SetHelpTextColor( ImplWinColorToSal( GetSysColor( COLOR_INFOTEXT ) ) );
+
+ Color aControlTextColor(ImplWinColorToSal(GetSysColor(COLOR_BTNTEXT)));
+
+ aStyleSettings.SetDialogColor(aStyleSettings.GetFaceColor());
+ aStyleSettings.SetDialogTextColor(aControlTextColor);
+
+ aStyleSettings.SetDefaultButtonTextColor(aControlTextColor);
+ aStyleSettings.SetButtonTextColor(aControlTextColor);
+ aStyleSettings.SetDefaultActionButtonTextColor(aControlTextColor);
+ aStyleSettings.SetActionButtonTextColor(aControlTextColor);
+ aStyleSettings.SetFlatButtonTextColor(aControlTextColor);
+ aStyleSettings.SetDefaultButtonRolloverTextColor(aControlTextColor);
+ aStyleSettings.SetButtonRolloverTextColor(aControlTextColor);
+ aStyleSettings.SetDefaultActionButtonRolloverTextColor(aControlTextColor);
+ aStyleSettings.SetActionButtonRolloverTextColor(aControlTextColor);
+ aStyleSettings.SetFlatButtonRolloverTextColor(aControlTextColor);
+ 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.SetRadioCheckTextColor( ImplWinColorToSal( GetSysColor( COLOR_WINDOWTEXT ) ) );
+ aStyleSettings.SetGroupTextColor( aStyleSettings.GetRadioCheckTextColor() );
+ aStyleSettings.SetLabelTextColor( aStyleSettings.GetRadioCheckTextColor() );
+ aStyleSettings.SetWindowColor( ImplWinColorToSal( GetSysColor( COLOR_WINDOW ) ) );
+ aStyleSettings.SetActiveTabColor( aStyleSettings.GetWindowColor() );
+ aStyleSettings.SetWindowTextColor( ImplWinColorToSal( GetSysColor( COLOR_WINDOWTEXT ) ) );
+ aStyleSettings.SetToolTextColor( ImplWinColorToSal( GetSysColor( COLOR_WINDOWTEXT ) ) );
+ aStyleSettings.SetFieldColor( aStyleSettings.GetWindowColor() );
+ aStyleSettings.SetFieldTextColor( aStyleSettings.GetWindowTextColor() );
+ aStyleSettings.SetFieldRolloverTextColor( aStyleSettings.GetFieldTextColor() );
+ aStyleSettings.SetHighlightColor( ImplWinColorToSal( GetSysColor( COLOR_HIGHLIGHT ) ) );
+ aStyleSettings.SetHighlightTextColor( ImplWinColorToSal( GetSysColor( COLOR_HIGHLIGHTTEXT ) ) );
+ aStyleSettings.SetMenuHighlightColor( aStyleSettings.GetHighlightColor() );
+ aStyleSettings.SetMenuHighlightTextColor( aStyleSettings.GetHighlightTextColor() );
+
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maNWFData.mnMenuFormatBorderX = 0;
+ pSVData->maNWFData.mnMenuFormatBorderY = 0;
+ pSVData->maNWFData.maMenuBarHighlightTextColor = COL_TRANSPARENT;
+ GetSalData()->mbThemeMenuSupport = false;
+ if (officecfg::Office::Common::Accessibility::AutoDetectSystemHC::get())
+ {
+ aStyleSettings.SetShadowColor( ImplWinColorToSal( GetSysColor( COLOR_ACTIVEBORDER ) ) );
+ aStyleSettings.SetWorkspaceColor( ImplWinColorToSal( GetSysColor( COLOR_MENU ) ) );
+ }
+ aStyleSettings.SetMenuColor( ImplWinColorToSal( GetSysColor( COLOR_MENU ) ) );
+ aStyleSettings.SetMenuBarColor( aStyleSettings.GetMenuColor() );
+ aStyleSettings.SetMenuBarRolloverColor( aStyleSettings.GetHighlightColor() );
+ aStyleSettings.SetMenuBorderColor( aStyleSettings.GetLightBorderColor() ); // overridden below for flat menus
+ aStyleSettings.SetUseFlatBorders( false );
+ aStyleSettings.SetUseFlatMenus( false );
+ aStyleSettings.SetMenuTextColor( ImplWinColorToSal( GetSysColor( COLOR_MENUTEXT ) ) );
+ if ( std::optional<Color> aColor = aStyleSettings.GetPersonaMenuBarTextColor() )
+ {
+ aStyleSettings.SetMenuBarTextColor( *aColor );
+ aStyleSettings.SetMenuBarRolloverTextColor( *aColor );
+ }
+ else
+ {
+ aStyleSettings.SetMenuBarTextColor( ImplWinColorToSal( GetSysColor( COLOR_MENUTEXT ) ) );
+ aStyleSettings.SetMenuBarRolloverTextColor( ImplWinColorToSal( GetSysColor( COLOR_HIGHLIGHTTEXT ) ) );
+ }
+ 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 ) ) );
+ BOOL bFlatMenus = FALSE;
+ SystemParametersInfoW( SPI_GETFLATMENU, 0, &bFlatMenus, 0);
+ if( bFlatMenus )
+ {
+ aStyleSettings.SetUseFlatMenus( true );
+ aStyleSettings.SetMenuBarColor( ImplWinColorToSal( GetSysColor( COLOR_MENUBAR ) ) );
+ aStyleSettings.SetMenuHighlightColor( ImplWinColorToSal( GetSysColor( COLOR_MENUHILIGHT ) ) );
+ aStyleSettings.SetMenuBarRolloverColor( ImplWinColorToSal( GetSysColor( COLOR_MENUHILIGHT ) ) );
+ aStyleSettings.SetMenuBorderColor( ImplWinColorToSal( GetSysColor( COLOR_3DSHADOW ) ) );
+
+ // 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 );
+ }
+ aStyleSettings.SetCheckedColorSpecialCase( );
+
+ // caret width
+ DWORD nCaretWidth = 2;
+ if( SystemParametersInfoW( SPI_GETCARETWIDTH, 0, &nCaretWidth, 0 ) )
+ aStyleSettings.SetCursorSize( nCaretWidth );
+
+ // 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 );
+
+ // 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) )
+ {
+ MiscSettings aMiscSettings = rSettings.GetMiscSettings();
+ utl::MiscCfg().SetYear2000( static_cast<sal_Int32>(nValue-99) );
+ rSettings.SetMiscSettings( aMiscSettings );
+ }
+ }
+ }
+
+ 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.nX, pt.y - maGeometry.nY );
+ 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( long nX, long nY, long nWidth, long nHeight )
+{
+ if( ! mpClipRgnData )
+ return;
+
+ RECT* pRect = mpNextClipRect;
+ RECT* pBoundRect = &(mpClipRgnData->rdh.rcBound);
+ long nRight = nX + nWidth;
+ 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 );
+ }
+}
+
+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;
+ // Start MouseLeave-Timer
+ if ( !pSalData->mpMouseLeaveTimer )
+ {
+ pSalData->mpMouseLeaveTimer = new AutoTimer;
+ pSalData->mpMouseLeaveTimer->SetDebugName( "ImplHandleMouseMsg SalData::mpMouseLeaveTimer" );
+ pSalData->mpMouseLeaveTimer->SetTimeout( SAL_MOUSELEAVE_TIMEOUT );
+ pSalData->mpMouseLeaveTimer->Start();
+ // We don't need to set a timeout handler, because we test
+ // for mouseleave in the timeout callback
+ }
+ 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.IsInside(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;
+ if ( pSalData->mpMouseLeaveTimer )
+ {
+ delete pSalData->mpMouseLeaveTimer;
+ pSalData->mpMouseLeaveTimer = 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.nWidth-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.nWidth-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 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;
+ 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
+ }
+
+ 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
+ // sal_Unicode ch = (sal_Unicode) Uni_UTF32ToSurrogate1(wParam);
+ nLastChar = 0;
+ nLastVKChar = 0;
+ pFrame->CallCallback( SalEvent::KeyInput, &aKeyEvt );
+ pFrame->CallCallback( SalEvent::KeyUp, &aKeyEvt );
+ wParam = static_cast<sal_Unicode>(Uni_UTF32ToSurrogate2( 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;
+
+ if ((nModCode & (KEY_MOD1 | KEY_MOD2)) == (KEY_MOD1 | KEY_MOD2) &&
+ aKeyEvt.mnCharCode)
+ {
+ // this is actually AltGr and should not be handled as Alt
+ aKeyEvt.mnCode &= ~(KEY_MOD1 | KEY_MOD2);
+ }
+
+ 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.nLeftDecoration = pFrame->maGeometry.nRightDecoration = pFrame->maGeometry.nBottomDecoration = 0;
+ pFrame->maGeometry.nTopDecoration = pFrame->mbCaption ? GetSystemMetrics( SM_CYCAPTION ) : 0;
+
+ aRect.top += pFrame->maGeometry.nTopDecoration;
+ pFrame->maGeometry.nX = aRect.left;
+ pFrame->maGeometry.nY = aRect.top;
+ pFrame->maGeometry.nWidth = aRect.right - aRect.left;
+ pFrame->maGeometry.nHeight = aRect.bottom - aRect.top;
+}
+
+static void UpdateFrameGeometry( HWND hWnd, WinSalFrame* pFrame )
+{
+ if( !pFrame )
+ return;
+
+ RECT aRect;
+ GetWindowRect( hWnd, &aRect );
+ pFrame->maGeometry.nX = 0;
+ pFrame->maGeometry.nY = 0;
+ pFrame->maGeometry.nWidth = 0;
+ pFrame->maGeometry.nHeight = 0;
+ pFrame->maGeometry.nLeftDecoration = 0;
+ pFrame->maGeometry.nTopDecoration = 0;
+ pFrame->maGeometry.nRightDecoration = 0;
+ pFrame->maGeometry.nBottomDecoration = 0;
+ pFrame->maGeometry.nDisplayScreenNumber = 0;
+
+ if ( IsIconic( hWnd ) )
+ return;
+
+ POINT aPt;
+ aPt.x=0;
+ aPt.y=0;
+ ClientToScreen(hWnd, &aPt);
+ int cx = aPt.x - aRect.left;
+ pFrame->maGeometry.nTopDecoration = aPt.y - aRect.top;
+
+ pFrame->maGeometry.nLeftDecoration = cx;
+ pFrame->maGeometry.nRightDecoration = cx;
+
+ pFrame->maGeometry.nX = aPt.x;
+ pFrame->maGeometry.nY = 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.nRightDecoration = aRect.right - aPt.x;
+ }
+ if( aInnerRect.bottom ) // may be zero if window was not shown yet
+ pFrame->maGeometry.nBottomDecoration += aRect.bottom - aPt.y - aInnerRect.bottom;
+ else
+ // bottom border is typically the same as left/right
+ pFrame->maGeometry.nBottomDecoration = pFrame->maGeometry.nLeftDecoration;
+
+ int nWidth = aRect.right - aRect.left
+ - pFrame->maGeometry.nRightDecoration - pFrame->maGeometry.nLeftDecoration;
+ int nHeight = aRect.bottom - aRect.top
+ - pFrame->maGeometry.nBottomDecoration - pFrame->maGeometry.nTopDecoration;
+ // clamp to zero
+ pFrame->maGeometry.nHeight = nHeight < 0 ? 0 : nHeight;
+ pFrame->maGeometry.nWidth = nWidth < 0 ? 0 : nWidth;
+ pFrame->updateScreenNumber();
+}
+
+static void ImplCallMoveHdl( HWND hWnd )
+{
+ WinSalFrame* pFrame = GetWindowPtr( hWnd );
+ if ( pFrame )
+ {
+ pFrame->CallCallback( SalEvent::Move, nullptr );
+ // to avoid doing Paint twice by VCL and SAL
+ //if ( IsWindowVisible( hWnd ) && !pFrame->mbInShow )
+ // UpdateWindow( hWnd );
+ }
+}
+
+static void ImplCallClosePopupsHdl( HWND hWnd )
+{
+ WinSalFrame* pFrame = GetWindowPtr( hWnd );
+ if ( pFrame )
+ {
+ pFrame->CallCallback( SalEvent::ClosePopups, nullptr );
+ }
+}
+
+static void ImplHandleMoveMsg( HWND hWnd )
+{
+ WinSalFrame* pFrame = ProcessOrDeferMessage( hWnd, SAL_MSG_POSTMOVE );
+ if ( pFrame )
+ {
+ UpdateFrameGeometry( hWnd, pFrame );
+
+ if ( GetWindowStyle( hWnd ) & WS_VISIBLE )
+ pFrame->mbDefPos = false;
+
+ // protect against recursion
+ if ( !pFrame->mbInMoveMsg )
+ {
+ // adjust window again for FullScreenMode
+ pFrame->mbInMoveMsg = true;
+ if ( pFrame->mbFullScreen )
+ ImplSalFrameFullScreenPos( pFrame );
+ pFrame->mbInMoveMsg = false;
+ }
+
+ // save state
+ ImplSaveFrameState( pFrame );
+
+ // Call Hdl
+ //#93851 if we call this handler, VCL floating windows are not updated correctly
+ ImplCallMoveHdl( hWnd );
+
+ ImplSalYieldMutexRelease();
+ }
+}
+
+static void ImplCallSizeHdl( HWND hWnd )
+{
+ // as Windows can send these messages also, we have to use
+ // the Solar semaphore
+ WinSalFrame* pFrame = ProcessOrDeferMessage( hWnd, SAL_MSG_POSTCALLSIZE );
+ if ( pFrame )
+ {
+ 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) )
+ {
+ WinSalFrame* pFrame = GetWindowPtr( hWnd );
+ if ( pFrame )
+ {
+ UpdateFrameGeometry( hWnd, pFrame );
+
+ pFrame->mnWidth = static_cast<int>(LOWORD(lParam));
+ pFrame->mnHeight = static_cast<int>(HIWORD(lParam));
+ // save state
+ ImplSaveFrameState( pFrame );
+ // Call Hdl
+ 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 )
+ {
+ if ( !WinSalFrame::mbInReparent )
+ {
+ 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 );
+ }
+ ImplSalYieldMutexRelease();
+ }
+}
+
+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();
+ }
+
+ if ( WM_SYSCOLORCHANGE == nMsg && GetSalData()->mhDitherPal )
+ ImplUpdateSysColorEntries();
+
+ WinSalFrame* pFrame = ProcessOrDeferMessage( hWnd, 0, 0, DeferPolicy::Blocked );
+ if ( pFrame )
+ {
+ if ( (nMsg == WM_DISPLAYCHANGE) || (nMsg == WM_WININICHANGE) )
+ {
+ if ( pFrame->mbFullScreen )
+ 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 )
+ {
+ WinSalFrame* pFrame = ProcessOrDeferMessage( hWnd, SAL_MSG_FORCEPALETTE );
+ if ( pFrame && pFrame->mpLocalGraphics && pFrame->mpLocalGraphics->getHDC() )
+ {
+ WinSalGraphics* pGraphics = pFrame->mpLocalGraphics;
+ if (pGraphics->getDefPal())
+ {
+ SelectPalette( pGraphics->getHDC(), hPal, FALSE );
+ if ( RealizePalette( pGraphics->getHDC() ) )
+ {
+ InvalidateRect( hWnd, nullptr, FALSE );
+ UpdateWindow( hWnd );
+ pFrame->CallCallback( SalEvent::DisplayChanged, nullptr );
+ }
+ }
+ }
+ if ( pFrame )
+ ImplSalYieldMutexRelease();
+ }
+}
+
+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;
+ UINT nCols;
+ bool bStdDC;
+ bool bUpdate;
+
+ pSalData->mbInPalChange = true;
+
+ // reset all palettes in VirDevs and Frames
+ pTempVD = pSalData->mpFirstVD;
+ while ( pTempVD )
+ {
+ pGraphics = pTempVD->getGraphics();
+ if ( pGraphics->getDefPal() )
+ {
+ SelectPalette( pGraphics->getHDC(),
+ pGraphics->getDefPal(),
+ TRUE );
+ }
+ pTempVD = pTempVD->getNext();
+ }
+ pTempFrame = pSalData->mpFirstFrame;
+ while ( pTempFrame )
+ {
+ pGraphics = pTempFrame->mpLocalGraphics;
+ if ( pGraphics && pGraphics->getHDC() && pGraphics->getDefPal() )
+ {
+ SelectPalette( pGraphics->getHDC(),
+ pGraphics->getDefPal(),
+ TRUE );
+ }
+ pTempFrame = pTempFrame->mpNextFrame;
+ }
+
+ // re-initialize palette
+ WinSalFrame* pFrame = nullptr;
+ if ( bFrame )
+ pFrame = GetWindowPtr( hWnd );
+ if ( pFrame && pFrame->mpLocalGraphics && pFrame->mpLocalGraphics->getHDC() )
+ {
+ hDC = pFrame->mpLocalGraphics->getHDC();
+ bStdDC = true;
+ }
+ else
+ {
+ hDC = GetDC( hWnd );
+ bStdDC = false;
+ }
+ UnrealizeObject( hPal );
+ hOldPal = SelectPalette( hDC, hPal, TRUE );
+ nCols = RealizePalette( hDC );
+ bUpdate = nCols != 0;
+ if ( !bStdDC )
+ {
+ 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() )
+ {
+ SelectPalette( pGraphics->getHDC(), hPal, TRUE );
+ RealizePalette( pGraphics->getHDC() );
+ }
+ pTempVD = pTempVD->getNext();
+ }
+ pTempFrame = pSalData->mpFirstFrame;
+ while ( pTempFrame )
+ {
+ if ( pTempFrame != pFrame )
+ {
+ pGraphics = pTempFrame->mpLocalGraphics;
+ if ( pGraphics && pGraphics->getHDC() && pGraphics->getDefPal() )
+ {
+ SelectPalette( pGraphics->getHDC(), hPal, TRUE );
+ if ( RealizePalette( pGraphics->getHDC() ) )
+ bUpdate = true;
+ }
+ }
+ pTempFrame = pTempFrame->mpNextFrame;
+ }
+
+ // if colors changed, update the window
+ if ( bUpdate )
+ {
+ pTempFrame = pSalData->mpFirstFrame;
+ while ( pTempFrame )
+ {
+ pGraphics = pTempFrame->mpLocalGraphics;
+ if ( pGraphics && pGraphics->getHDC() && 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->mbFullScreen )
+ {
+ 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() ), 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 )
+ {
+ 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->mbFullScreen )
+ {
+ 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;
+
+ ExtTextInputAttr* pSalAttrAry = nullptr;
+ 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 = new ExtTextInputAttr[nTextLen2];
+ memset( pSalAttrAry, 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;
+ }
+ }
+
+ // 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->mbCandidateMode )
+ {
+ 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 );
+ }
+
+ if ( pSalAttrAry )
+ delete [] pSalAttrAry;
+ }
+
+ 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 )
+ 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( MouseNotifyEvent::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)
+{
+ // IA2 should be enabled automatically
+ AllSettings aSettings = Application::GetSettings();
+ MiscSettings aMisc = aSettings.GetMiscSettings();
+ aMisc.SetEnableATToolSupport( true );
+ aSettings.SetMiscSettings( aMisc );
+ Application::SetSettings( aSettings );
+
+ 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;
+ }
+
+ uno::Reference< accessibility::XMSAAService > xMSAA( pSVData->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.mnCursorBoundX + aEvt.mnCursorBoundWidth;
+ pQueryCharPosition->pt.y = aEvt.mnCursorBoundY;
+ pQueryCharPosition->cLineHeight = aEvt.mnCursorBoundWidth;
+ }
+ 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.mnCursorBoundX;
+ pQueryCharPosition->pt.y = aEvt.mnCursorBoundY;
+ pQueryCharPosition->cLineHeight = aEvt.mnCursorBoundHeight;
+ }
+
+ // 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 );
+ 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 );
+ // 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:
+ case SAL_MSG_POSTMOVE:
+ ImplHandleMoveMsg( 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:
+ ImplSalYieldMutexAcquireWithWait();
+ if ( ImplHandleGetObject( hWnd, lParam, wParam, nRet ) )
+ {
+ rDef = false;
+ }
+ ImplSalYieldMutexRelease();
+ 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 000000000..6f8dc8bff
--- /dev/null
+++ b/vcl/win/window/salmenu.cxx
@@ -0,0 +1,355 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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);
+}
+
+static void ImplDrawMenuBar( SalMenu *pMenu )
+{
+ if( pMenu->VisibleMenuBar() )
+ {
+ // redrawing the menubar all the time actually seems to be unnecessary (it just flickers)
+ /*
+ WinSalMenu *pMenuBar = ImplFindMenuBar( pMenu );
+ if( pMenuBar && pMenuBar->mhWnd )
+ ::DrawMenuBar( pMenuBar->mhWnd );
+ */
+ }
+}
+
+/*
+ * 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;
+ ImplDrawMenuBar( 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;
+ ImplDrawMenuBar( this );
+ }
+ }
+}
+
+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;
+ ImplDrawMenuBar( pSalMenu );
+ }
+}
+
+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();
+ else
+ ImplDrawMenuBar( this );
+ }
+}
+
+void WinSalMenu::CheckItem( unsigned nPos, bool bCheck )
+{
+ if( static_cast<unsigned>( -1 ) != ::CheckMenuItem( mhMenu, nPos, MF_BYPOSITION|(bCheck ? MF_CHECKED : MF_UNCHECKED) ) )
+ ImplDrawMenuBar( this );
+}
+
+void WinSalMenu::EnableItem( unsigned nPos, bool bEnable )
+{
+ if( -1 != ::EnableMenuItem( mhMenu, nPos, MF_BYPOSITION|(bEnable ? MF_ENABLED : (MF_DISABLED|MF_GRAYED) ) ) )
+ ImplDrawMenuBar( this );
+}
+
+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();
+ else
+ ImplDrawMenuBar( this );
+ }
+}
+
+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();
+ else
+ ImplDrawMenuBar( this );
+ }
+}
+
+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 000000000..0cf2fa8ad
--- /dev/null
+++ b/vcl/win/window/salobj.cxx
@@ -0,0 +1,727 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 SalSysObjWndProc( HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam, int& rDef )
+{
+ WinSalObject* pSysObj;
+ LRESULT nRet = 0;
+
+ switch( nMsg )
+ {
+ case WM_ERASEBKGND:
+ nRet = 1;
+ rDef = FALSE;
+ break;
+ case WM_PAINT:
+ {
+ PAINTSTRUCT aPs;
+ BeginPaint( hWnd, &aPs );
+ EndPaint( hWnd, &aPs );
+ rDef = FALSE;
+ }
+ break;
+
+ case WM_PARENTNOTIFY:
+ {
+ UINT nNotifyMsg = LOWORD( wParam );
+ if ( (nNotifyMsg == WM_LBUTTONDOWN) ||
+ (nNotifyMsg == WM_RBUTTONDOWN) ||
+ (nNotifyMsg == WM_MBUTTONDOWN) )
+ {
+ ImplSalYieldMutexAcquireWithWait();
+ pSysObj = GetSalObjWindowPtr( hWnd );
+ if ( pSysObj && !pSysObj->IsMouseTransparent() )
+ pSysObj->CallCallback( SalObjEvent::ToTop );
+ ImplSalYieldMutexRelease();
+ }
+ }
+ break;
+
+ case WM_MOUSEACTIVATE:
+ {
+ ImplSalYieldMutexAcquireWithWait();
+ pSysObj = GetSalObjWindowPtr( hWnd );
+ if ( 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() )
+ {
+ pSysObj = GetSalObjWindowPtr( hWnd );
+ pSysObj->CallCallback( SalObjEvent::ToTop );
+ ImplSalYieldMutexRelease();
+ rDef = FALSE;
+ }
+ 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() )
+ {
+ 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!");
+ }
+ rDef = FALSE;
+ break;
+
+ case WM_SIZE:
+ {
+ HWND hWndChild = GetWindow( hWnd, GW_CHILD );
+ if ( hWndChild )
+ {
+ SetWindowPos( hWndChild,
+ nullptr, 0, 0, static_cast<int>(LOWORD( lParam )), static_cast<int>(HIWORD( lParam )),
+ SWP_NOZORDER | SWP_NOACTIVATE );
+ }
+ }
+ rDef = FALSE;
+ break;
+
+ case WM_CREATE:
+ {
+ // Save the window instance at the window handle.
+ // Can also be used for the A-Version, because the struct
+ // to access lpCreateParams is the same structure
+ CREATESTRUCTW* pStruct = reinterpret_cast<CREATESTRUCTW*>(lParam);
+ 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;
+ rDef = FALSE;
+ }
+ break;
+ }
+
+ return nRet;
+}
+
+static LRESULT CALLBACK SalSysObjWndProcW( HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam )
+{
+ int bDef = TRUE;
+ LRESULT nRet = SalSysObjWndProc( hWnd, nMsg, wParam, lParam, bDef );
+ if ( bDef )
+ nRet = DefWindowProcW( hWnd, nMsg, wParam, lParam );
+ return nRet;
+}
+
+static LRESULT CALLBACK SalSysObjChildWndProc( HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam, int& rDef )
+{
+ LRESULT nRet = 0;
+
+ switch( nMsg )
+ {
+ // clear background for plugins
+ case WM_ERASEBKGND:
+ {
+ WinSalObject* pSysObj = GetSalObjWindowPtr( ::GetParent( hWnd ) );
+
+ if( pSysObj && !pSysObj->IsEraseBackgroundEnabled() )
+ {
+ // do not erase background
+ nRet = 1;
+ rDef = FALSE;
+ }
+ }
+ break;
+
+ case WM_PAINT:
+ {
+ PAINTSTRUCT aPs;
+ BeginPaint( hWnd, &aPs );
+ EndPaint( hWnd, &aPs );
+ rDef = FALSE;
+ }
+ break;
+
+ case WM_MOUSEMOVE:
+ case WM_LBUTTONDOWN:
+ case WM_MBUTTONDOWN:
+ case WM_RBUTTONDOWN:
+ case WM_LBUTTONUP:
+ case WM_MBUTTONUP:
+ case WM_RBUTTONUP:
+ {
+ WinSalObject* pSysObj;
+ pSysObj = GetSalObjWindowPtr( ::GetParent( hWnd ) );
+
+ if( pSysObj && pSysObj->IsMouseTransparent() )
+ {
+ // forward mouse events to parent frame
+ HWND hWndParent = ::GetParent( pSysObj->mhWnd );
+
+ // transform coordinates
+ POINT pt;
+ pt.x = static_cast<long>(LOWORD( lParam ));
+ pt.y = static_cast<long>(HIWORD( lParam ));
+ MapWindowPoints( hWnd, hWndParent, &pt, 1 );
+ lParam = MAKELPARAM( static_cast<WORD>(pt.x), static_cast<WORD>(pt.y) );
+
+ nRet = SendMessageW( hWndParent, nMsg, wParam, lParam );
+ rDef = FALSE;
+ }
+ }
+ break;
+ }
+
+ return nRet;
+}
+
+static LRESULT CALLBACK SalSysObjChildWndProcW( HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam )
+{
+ int bDef = TRUE;
+ LRESULT nRet = SalSysObjChildWndProc( hWnd, nMsg, wParam, lParam, bDef );
+ if ( bDef )
+ nRet = DefWindowProcW( hWnd, nMsg, wParam, lParam );
+ return nRet;
+}
+
+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( long nX, long nY, long nWidth, long nHeight )
+{
+ RECT* pRect = mpNextClipRect;
+ RECT* pBoundRect = &(mpClipRgnData->rdh.rcBound);
+ long nRight = nX + nWidth;
+ 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( long nX, long nY, long nWidth, 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 000000000..6fb96a4c7
--- /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 000000000..678d526b1
--- /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 000000000..8e22aabaf
--- /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 000000000..678d526b1
--- /dev/null
+++ b/vcl/workben/bmpfuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 65536
diff --git a/vcl/workben/cgmfuzzer.cxx b/vcl/workben/cgmfuzzer.cxx
new file mode 100644
index 000000000..252d538bd
--- /dev/null
+++ b/vcl/workben/cgmfuzzer.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 <tools/stream.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+extern "C" {
+void * emfio_component_getFactory( const char* , void* , void* );
+void * sd_component_getFactory( const char* , void* , void* );
+void * i18npool_component_getFactory( const char* , void* , void* );
+void * ucb_component_getFactory( const char* , void* , void* );
+void * lng_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 * unoxml_component_getFactory( const char* , 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 * );
+}
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { "libemfiolo.a", emfio_component_getFactory },
+ { "libsdlo.a", sd_component_getFactory },
+ { "libunoxmllo.a", unoxml_component_getFactory },
+ { "libi18npoollo.a", i18npool_component_getFactory },
+ { "libucb1.a", ucb_component_getFactory },
+ { "liblnglo.a", lng_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 },
+ { 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 000000000..d851ad58c
--- /dev/null
+++ b/vcl/workben/cgmfuzzer.options
@@ -0,0 +1,4 @@
+[libfuzzer]
+max_len = 65536
+[env]
+AFL_DRIVER_DONT_DEFER=1
diff --git a/vcl/workben/commonfuzzer.hxx b/vcl/workben/commonfuzzer.hxx
new file mode 100644
index 000000000..d6c8d5cae
--- /dev/null
+++ b/vcl/workben/commonfuzzer.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/.
+ */
+
+#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 lastDirSeperatorPos = uri.lastIndexOf('/');
+ if (lastDirSeperatorPos >= 0) {
+ uri = uri.copy(0, lastDirSeperatorPos + 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 + getExecutableName() + ".fonts", osl_getThreadTextEncoding()));
+ 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("SC_MAX_MATRIX_ELEMENTS", "60000000", 1);
+ setenv("SC_NO_THREADED_CALCULATION", "1", 1);
+ setenv("SAL_WMF_COMPLEXCLIP_VIA_REGION", "1", 1);
+ setenv("SAL_DISABLE_PRINTERLIST", "1", 1);
+ setenv("SAL_DISABLE_DEFAULTPRINTER", "1", 1);
+ setenv("SAL_NO_FONT_LOOKUP", "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();
+}
+
+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/diffuzzer.cxx b/vcl/workben/diffuzzer.cxx
new file mode 100644
index 000000000..c9bc77a16
--- /dev/null
+++ b/vcl/workben/diffuzzer.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_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*);
+}
+
+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 },
+ { 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 000000000..d851ad58c
--- /dev/null
+++ b/vcl/workben/diffuzzer.options
@@ -0,0 +1,4 @@
+[libfuzzer]
+max_len = 65536
+[env]
+AFL_DRIVER_DONT_DEFER=1
diff --git a/vcl/workben/docxfuzzer.cxx b/vcl/workben/docxfuzzer.cxx
new file mode 100644
index 000000000..4e116c600
--- /dev/null
+++ b/vcl/workben/docxfuzzer.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 <tools/stream.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+
+extern "C" void* SwCreateDialogFactory()
+{
+ return nullptr;
+}
+
+extern "C" void* com_sun_star_comp_Writer_EPUBExportFilter_get_implementation()
+{
+ 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 000000000..db9a3e8c6
--- /dev/null
+++ b/vcl/workben/docxfuzzer.options
@@ -0,0 +1,4 @@
+[libfuzzer]
+max_len = 98304
+[env]
+AFL_DRIVER_DONT_DEFER=1
diff --git a/vcl/workben/dxffuzzer.cxx b/vcl/workben/dxffuzzer.cxx
new file mode 100644
index 000000000..6ca526a1d
--- /dev/null
+++ b/vcl/workben/dxffuzzer.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 <tools/stream.hxx>
+#include <vcl/FilterConfigItem.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 * );
+}
+
+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 },
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*)
+{
+ return nullptr;
+}
+
+extern "C" bool idxGraphicImport(SvStream& rStream, Graphic& rGraphic, FilterConfigItem* pConfigItem);
+
+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)idxGraphicImport(aStream, aGraphic, nullptr);
+ 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 000000000..678d526b1
--- /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 000000000..effb05193
--- /dev/null
+++ b/vcl/workben/epsfuzzer.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 <tools/stream.hxx>
+#include <vcl/FilterConfigItem.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 * );
+}
+
+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 },
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*)
+{
+ return nullptr;
+}
+
+extern "C" bool ipsGraphicImport(SvStream& rStream, Graphic& rGraphic, FilterConfigItem* pConfigItem);
+
+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)ipsGraphicImport(aStream, aGraphic, nullptr);
+ 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 000000000..678d526b1
--- /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 000000000..66b51a5d3
--- /dev/null
+++ b/vcl/workben/fftester.cxx
@@ -0,0 +1,692 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+ /* e.g.
+ make
+ cp workdir/LinkTarget/Executable/fftester instdir/program
+ LD_LIBRARY_PATH=`pwd`/instdir/program instdir/program/fftester <foo> png
+ */
+
+#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/pngread.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/wmf.hxx>
+#include <vcl/wrkwin.hxx>
+#include <fltcall.hxx>
+#include <osl/file.hxx>
+#include <osl/module.hxx>
+#include <tools/stream.hxx>
+#include <vcl/gdimtf.hxx>
+
+#include "../source/filter/igif/gifread.hxx"
+#include "../source/filter/ixbm/xbmread.hxx"
+#include "../source/filter/ixpm/xpmread.hxx"
+#include "../source/filter/jpeg/jpeg.hxx"
+
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::lang;
+using namespace cppu;
+
+#ifndef DISABLE_DYNLOADING
+extern "C" { static void thisModule() {} }
+#endif
+
+typedef bool (*FFilterCall)(SvStream &rStream);
+
+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_WMF_COMPLEXCLIP_VIA_REGION", "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(1);
+ aArgs[0] <<= 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::PNGReader aReader(aFileStream);
+ ret = static_cast<int>(!!aReader.Read());
+ }
+ 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], "svm") == 0)
+ {
+ GDIMetaFile aGDIMetaFile;
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ReadGDIMetaFile(aFileStream, aGDIMetaFile);
+ }
+#ifndef DISABLE_DYNLOADING
+ else if (strcmp(argv[2], "pcd") == 0)
+ {
+ static PFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ osl::Module aLibrary;
+ aLibrary.loadRelative(&thisModule, "libgielo.so");
+ pfnImport = reinterpret_cast<PFilterCall>(
+ aLibrary.getFunctionSymbol("icdGraphicImport"));
+ aLibrary.release();
+ }
+ Graphic aGraphic;
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream, aGraphic, nullptr));
+ }
+ else if (strcmp(argv[2], "dxf") == 0)
+ {
+ static PFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ osl::Module aLibrary;
+ aLibrary.loadRelative(&thisModule, "libgielo.so");
+ pfnImport = reinterpret_cast<PFilterCall>(
+ aLibrary.getFunctionSymbol("idxGraphicImport"));
+ aLibrary.release();
+ }
+ Graphic aGraphic;
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream, aGraphic, nullptr));
+ }
+ else if (strcmp(argv[2], "met") == 0)
+ {
+ static PFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ osl::Module aLibrary;
+ aLibrary.loadRelative(&thisModule, "libgielo.so");
+ pfnImport = reinterpret_cast<PFilterCall>(
+ aLibrary.getFunctionSymbol("imeGraphicImport"));
+ aLibrary.release();
+ }
+ Graphic aGraphic;
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream, aGraphic, nullptr));
+ }
+ else if ((strcmp(argv[2], "pbm") == 0) || strcmp(argv[2], "ppm") == 0)
+ {
+ static PFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ osl::Module aLibrary;
+ aLibrary.loadRelative(&thisModule, "libgielo.so");
+ pfnImport = reinterpret_cast<PFilterCall>(
+ aLibrary.getFunctionSymbol("ipbGraphicImport"));
+ aLibrary.release();
+ }
+ Graphic aGraphic;
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream, aGraphic, nullptr));
+ }
+ else if (strcmp(argv[2], "psd") == 0)
+ {
+ static PFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ osl::Module aLibrary;
+ aLibrary.loadRelative(&thisModule, "libgielo.so");
+ pfnImport = reinterpret_cast<PFilterCall>(
+ aLibrary.getFunctionSymbol("ipdGraphicImport"));
+ aLibrary.release();
+ }
+ Graphic aGraphic;
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream, aGraphic, nullptr));
+ }
+ else if (strcmp(argv[2], "eps") == 0)
+ {
+ static PFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ osl::Module aLibrary;
+ aLibrary.loadRelative(&thisModule, "libgielo.so");
+ pfnImport = reinterpret_cast<PFilterCall>(
+ aLibrary.getFunctionSymbol("ipsGraphicImport"));
+ aLibrary.release();
+ }
+ Graphic aGraphic;
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream, aGraphic, nullptr));
+ }
+ else if (strcmp(argv[2], "pct") == 0)
+ {
+ static PFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ osl::Module aLibrary;
+ aLibrary.loadRelative(&thisModule, "libgielo.so");
+ pfnImport = reinterpret_cast<PFilterCall>(
+ aLibrary.getFunctionSymbol("iptGraphicImport"));
+ aLibrary.release();
+ }
+ Graphic aGraphic;
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream, aGraphic, nullptr));
+ }
+ else if (strcmp(argv[2], "pcx") == 0)
+ {
+ static PFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ osl::Module aLibrary;
+ aLibrary.loadRelative(&thisModule, "libgielo.so");
+ pfnImport = reinterpret_cast<PFilterCall>(
+ aLibrary.getFunctionSymbol("ipxGraphicImport"));
+ aLibrary.release();
+ }
+ Graphic aGraphic;
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream, aGraphic, nullptr));
+ }
+ else if (strcmp(argv[2], "ras") == 0)
+ {
+ static PFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ osl::Module aLibrary;
+ aLibrary.loadRelative(&thisModule, "libgielo.so");
+ pfnImport = reinterpret_cast<PFilterCall>(
+ aLibrary.getFunctionSymbol("iraGraphicImport"));
+ aLibrary.release();
+ }
+ Graphic aGraphic;
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream, aGraphic, nullptr));
+ }
+ else if (strcmp(argv[2], "tga") == 0)
+ {
+ static PFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ osl::Module aLibrary;
+ aLibrary.loadRelative(&thisModule, "libgielo.so");
+ pfnImport = reinterpret_cast<PFilterCall>(
+ aLibrary.getFunctionSymbol("itgGraphicImport"));
+ aLibrary.release();
+ }
+ Graphic aGraphic;
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream, aGraphic, nullptr));
+ }
+ else if (strcmp(argv[2], "tif") == 0)
+ {
+ static PFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ osl::Module aLibrary;
+ aLibrary.loadRelative(&thisModule, "libgielo.so");
+ pfnImport = reinterpret_cast<PFilterCall>(
+ aLibrary.getFunctionSymbol("itiGraphicImport"));
+ aLibrary.release();
+ }
+ Graphic aGraphic;
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream, aGraphic, nullptr));
+ }
+ else if ((strcmp(argv[2], "doc") == 0) || (strcmp(argv[2], "ww8") == 0))
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ osl::Module aLibrary;
+ aLibrary.loadRelative(&thisModule, "libmswordlo.so", SAL_LOADMODULE_LAZY);
+ pfnImport = reinterpret_cast<FFilterCall>(
+ aLibrary.getFunctionSymbol("TestImportWW8"));
+ aLibrary.release();
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "ww6") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ osl::Module aLibrary;
+ aLibrary.loadRelative(&thisModule, "libmswordlo.so", SAL_LOADMODULE_LAZY);
+ pfnImport = reinterpret_cast<FFilterCall>(
+ aLibrary.getFunctionSymbol("TestImportWW6"));
+ aLibrary.release();
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "ww2") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ osl::Module aLibrary;
+ aLibrary.loadRelative(&thisModule, "libmswordlo.so", SAL_LOADMODULE_LAZY);
+ pfnImport = reinterpret_cast<FFilterCall>(
+ aLibrary.getFunctionSymbol("TestImportWW2"));
+ aLibrary.release();
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "rtf") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ osl::Module aLibrary;
+ aLibrary.loadRelative(&thisModule, "libmswordlo.so", SAL_LOADMODULE_LAZY);
+ pfnImport = reinterpret_cast<FFilterCall>(
+ aLibrary.getFunctionSymbol("TestImportRTF"));
+ aLibrary.release();
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "html") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ osl::Module aLibrary;
+ aLibrary.loadRelative(&thisModule, "libswlo.so", SAL_LOADMODULE_LAZY);
+ pfnImport = reinterpret_cast<FFilterCall>(
+ aLibrary.getFunctionSymbol("TestImportHTML"));
+ aLibrary.release();
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "fodt") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ osl::Module aLibrary;
+ aLibrary.loadRelative(&thisModule, "libswlo.so", SAL_LOADMODULE_LAZY);
+ pfnImport = reinterpret_cast<FFilterCall>(
+ aLibrary.getFunctionSymbol("TestImportFODT"));
+ aLibrary.release();
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "docx") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ osl::Module aLibrary;
+ aLibrary.loadRelative(&thisModule, "libswlo.so", SAL_LOADMODULE_LAZY);
+ pfnImport = reinterpret_cast<FFilterCall>(
+ aLibrary.getFunctionSymbol("TestImportDOCX"));
+ aLibrary.release();
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "fods") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ osl::Module aLibrary;
+ aLibrary.loadRelative(&thisModule, "libsclo.so", SAL_LOADMODULE_LAZY);
+ pfnImport = reinterpret_cast<FFilterCall>(
+ aLibrary.getFunctionSymbol("TestImportFODS"));
+ aLibrary.release();
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "xlsx") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ osl::Module aLibrary;
+ aLibrary.loadRelative(&thisModule, "libsclo.so", SAL_LOADMODULE_LAZY);
+ pfnImport = reinterpret_cast<FFilterCall>(
+ aLibrary.getFunctionSymbol("TestImportXLSX"));
+ aLibrary.release();
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "fodp") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ osl::Module aLibrary;
+ aLibrary.loadRelative(&thisModule, "libsdlo.so", SAL_LOADMODULE_LAZY);
+ pfnImport = reinterpret_cast<FFilterCall>(
+ aLibrary.getFunctionSymbol("TestImportFODP"));
+ aLibrary.release();
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "pptx") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ osl::Module aLibrary;
+ aLibrary.loadRelative(&thisModule, "libsdlo.so", SAL_LOADMODULE_LAZY);
+ pfnImport = reinterpret_cast<FFilterCall>(
+ aLibrary.getFunctionSymbol("TestImportPPTX"));
+ aLibrary.release();
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "xls") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ osl::Module aLibrary;
+ aLibrary.loadRelative(&thisModule, "libscfiltlo.so", SAL_LOADMODULE_LAZY);
+ pfnImport = reinterpret_cast<FFilterCall>(
+ aLibrary.getFunctionSymbol("TestImportXLS"));
+ aLibrary.release();
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "wks") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ osl::Module aLibrary;
+ aLibrary.loadRelative(&thisModule, "libscfiltlo.so", SAL_LOADMODULE_LAZY);
+ pfnImport = reinterpret_cast<FFilterCall>(
+ aLibrary.getFunctionSymbol("TestImportWKS"));
+ aLibrary.release();
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "hwp") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ osl::Module aLibrary;
+ aLibrary.loadRelative(&thisModule, "libhwplo.so", SAL_LOADMODULE_LAZY);
+ pfnImport = reinterpret_cast<FFilterCall>(
+ aLibrary.getFunctionSymbol("TestImportHWP"));
+ aLibrary.release();
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "602") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ osl::Module aLibrary;
+ aLibrary.loadRelative(&thisModule, "libt602filterlo.so", SAL_LOADMODULE_LAZY);
+ pfnImport = reinterpret_cast<FFilterCall>(
+ aLibrary.getFunctionSymbol("TestImport602"));
+ aLibrary.release();
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "lwp") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ osl::Module aLibrary;
+ aLibrary.loadRelative(&thisModule, "liblwpftlo.so", SAL_LOADMODULE_LAZY);
+ pfnImport = reinterpret_cast<FFilterCall>(
+ aLibrary.getFunctionSymbol("TestImportLWP"));
+ aLibrary.release();
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "ppt") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ osl::Module aLibrary;
+ aLibrary.loadRelative(&thisModule, "libsdfiltlo.so", SAL_LOADMODULE_LAZY);
+ pfnImport = reinterpret_cast<FFilterCall>(
+ aLibrary.getFunctionSymbol("TestImportPPT"));
+ aLibrary.release();
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "cgm") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ osl::Module aLibrary;
+ aLibrary.loadRelative(&thisModule, "libsdlo.so", SAL_LOADMODULE_LAZY);
+ pfnImport = reinterpret_cast<FFilterCall>(
+ aLibrary.getFunctionSymbol("TestImportCGM"));
+ aLibrary.release();
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "qpw") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ osl::Module aLibrary;
+ aLibrary.loadRelative(&thisModule, "libscfiltlo.so", SAL_LOADMODULE_LAZY);
+ pfnImport = reinterpret_cast<FFilterCall>(
+ aLibrary.getFunctionSymbol("TestImportQPW"));
+ aLibrary.release();
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "dif") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ osl::Module aLibrary;
+ aLibrary.loadRelative(&thisModule, "libscfiltlo.so", SAL_LOADMODULE_LAZY);
+ pfnImport = reinterpret_cast<FFilterCall>(
+ aLibrary.getFunctionSymbol("TestImportDIF"));
+ aLibrary.release();
+ }
+ 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)
+ {
+ osl::Module aLibrary;
+ aLibrary.loadRelative(&thisModule, "libscfiltlo.so", SAL_LOADMODULE_LAZY);
+ pfnImport = reinterpret_cast<FFilterCall>(
+ aLibrary.getFunctionSymbol("TestImportCalcRTF"));
+ aLibrary.release();
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "slk") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ osl::Module aLibrary;
+ aLibrary.loadRelative(&thisModule, "libsclo.so", SAL_LOADMODULE_LAZY);
+ pfnImport = reinterpret_cast<FFilterCall>(
+ aLibrary.getFunctionSymbol("TestImportSLK"));
+ aLibrary.release();
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "ole") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ osl::Module aLibrary;
+ aLibrary.loadRelative(&thisModule, "libsotlo.so", SAL_LOADMODULE_LAZY);
+ pfnImport = reinterpret_cast<FFilterCall>(
+ aLibrary.getFunctionSymbol("TestImportOLE2"));
+ aLibrary.release();
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "mml") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ osl::Module aLibrary;
+ aLibrary.loadRelative(&thisModule, "libsmlo.so", SAL_LOADMODULE_LAZY);
+ pfnImport = reinterpret_cast<FFilterCall>(
+ aLibrary.getFunctionSymbol("TestImportMML"));
+ aLibrary.release();
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "mtp") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ osl::Module aLibrary;
+ aLibrary.loadRelative(&thisModule, "libsmlo.so", SAL_LOADMODULE_LAZY);
+ pfnImport = reinterpret_cast<FFilterCall>(
+ aLibrary.getFunctionSymbol("TestImportMathType"));
+ aLibrary.release();
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ 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());
+ (void)vcl::Font::identifyFont(aData.data(), aData.size());
+ }
+
+#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 000000000..603849742
--- /dev/null
+++ b/vcl/workben/fodpfuzzer.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 <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" 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 000000000..120b6953b
--- /dev/null
+++ b/vcl/workben/fodpfuzzer.options
@@ -0,0 +1,5 @@
+[libfuzzer]
+max_len = 65536
+dict = xml.dict
+[env]
+AFL_DRIVER_DONT_DEFER=1
diff --git a/vcl/workben/fodsfuzzer.cxx b/vcl/workben/fodsfuzzer.cxx
new file mode 100644
index 000000000..ed9efd6e3
--- /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 000000000..120b6953b
--- /dev/null
+++ b/vcl/workben/fodsfuzzer.options
@@ -0,0 +1,5 @@
+[libfuzzer]
+max_len = 65536
+dict = xml.dict
+[env]
+AFL_DRIVER_DONT_DEFER=1
diff --git a/vcl/workben/fodtfuzzer.cxx b/vcl/workben/fodtfuzzer.cxx
new file mode 100644
index 000000000..54ae26622
--- /dev/null
+++ b/vcl/workben/fodtfuzzer.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 <tools/stream.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+
+extern "C" void* SwCreateDialogFactory()
+{
+ return nullptr;
+}
+
+extern "C" void* com_sun_star_comp_Writer_EPUBExportFilter_get_implementation()
+{
+ 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 000000000..120b6953b
--- /dev/null
+++ b/vcl/workben/fodtfuzzer.options
@@ -0,0 +1,5 @@
+[libfuzzer]
+max_len = 65536
+dict = xml.dict
+[env]
+AFL_DRIVER_DONT_DEFER=1
diff --git a/vcl/workben/giffuzzer.cxx b/vcl/workben/giffuzzer.cxx
new file mode 100644
index 000000000..7a2b9a9f0
--- /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 000000000..57d53b24e
--- /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 000000000..6da6c853f
--- /dev/null
+++ b/vcl/workben/htmlfuzzer.cxx
@@ -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/.
+ */
+
+#include <tools/stream.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+
+extern "C" void* SwCreateDialogFactory() { return nullptr; }
+
+extern "C" void* com_sun_star_comp_Writer_EPUBExportFilter_get_implementation() { 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 000000000..cd3dd2011
--- /dev/null
+++ b/vcl/workben/htmlfuzzer.options
@@ -0,0 +1,3 @@
+[libfuzzer]
+max_len = 65536
+dict = html_tags.dict
diff --git a/vcl/workben/hwpfuzzer.cxx b/vcl/workben/hwpfuzzer.cxx
new file mode 100644
index 000000000..430b1bdfc
--- /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 000000000..678d526b1
--- /dev/null
+++ b/vcl/workben/hwpfuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 65536
diff --git a/vcl/workben/icontest.cxx b/vcl/workben/icontest.cxx
new file mode 100644
index 000000000..4b2bb7303
--- /dev/null
+++ b/vcl/workben/icontest.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/.
+ */
+
+/*
+ * =======================================================================
+ *
+ * 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 <glm/gtx/bit.hpp>
+
+#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/fixed.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <vcl/image.hxx>
+#include <vcl/opengl/OpenGLContext.hxx>
+#include <vcl/opengl/OpenGLHelper.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 000000000..5850deec5
--- /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 000000000..dfcaf9cd9
--- /dev/null
+++ b/vcl/workben/jpgfuzzer.options
@@ -0,0 +1,3 @@
+[libfuzzer]
+max_len = 65536
+dict = jpeg.dict
diff --git a/vcl/workben/localestub/localedata_en_AU.cxx b/vcl/workben/localestub/localedata_en_AU.cxx
new file mode 100644
index 000000000..04742a8bb
--- /dev/null
+++ b/vcl/workben/localestub/localedata_en_AU.cxx
@@ -0,0 +1,1263 @@
+#include <sal/types.h>
+
+
+#include <stdio.h>
+
+extern "C" {
+
+static const sal_Unicode langID[] = {0x65, 0x6e, 0x0};
+static const sal_Unicode langDefaultName[] = {0x45, 0x6e, 0x67, 0x6c, 0x69, 0x73, 0x68, 0x0};
+static const sal_Unicode countryID[] = {0x41, 0x55, 0x0};
+static const sal_Unicode countryDefaultName[] = {0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x0};
+static const sal_Unicode Variant[] = {0x0};
+
+static const sal_Unicode* LCInfoArray[] = {
+ langID,
+ langDefaultName,
+ countryID,
+ countryDefaultName,
+ Variant
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLCInfo_en_AU(sal_Int16& count)
+{
+ count = 0;
+ return (sal_Unicode**)LCInfoArray;
+}
+
+
+static const sal_Unicode LC_CTYPE_Unoid[] = {0x0};
+static const sal_Unicode dateSeparator[] = {0x2f, 0x0};
+static const sal_Unicode thousandSeparator[] = {0x2c, 0x0};
+static const sal_Unicode decimalSeparator[] = {0x2e, 0x0};
+static const sal_Unicode timeSeparator[] = {0x3a, 0x0};
+static const sal_Unicode time100SecSeparator[] = {0x2e, 0x0};
+static const sal_Unicode listSeparator[] = {0x3b, 0x0};
+static const sal_Unicode LongDateDayOfWeekSeparator[] = {0x2c, 0x20, 0x0};
+static const sal_Unicode LongDateDaySeparator[] = {0x20, 0x0};
+static const sal_Unicode LongDateMonthSeparator[] = {0x20, 0x0};
+static const sal_Unicode LongDateYearSeparator[] = {0x20, 0x0};
+static const sal_Unicode quotationStart[] = {0x2018, 0x0};
+static const sal_Unicode quotationEnd[] = {0x2019, 0x0};
+static const sal_Unicode doubleQuotationStart[] = {0x201c, 0x0};
+static const sal_Unicode doubleQuotationEnd[] = {0x201d, 0x0};
+static const sal_Unicode timeAM[] = {0x41, 0x4d, 0x0};
+static const sal_Unicode timePM[] = {0x50, 0x4d, 0x0};
+static const sal_Unicode measurementSystem[] = {0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x0};
+
+static const sal_Unicode* LCType[] = {
+ LC_CTYPE_Unoid,
+ dateSeparator,
+ thousandSeparator,
+ decimalSeparator,
+ timeSeparator,
+ time100SecSeparator,
+ listSeparator,
+ quotationStart,
+ quotationEnd,
+ doubleQuotationStart,
+ doubleQuotationEnd,
+ timeAM,
+ timePM,
+ measurementSystem,
+ LongDateDayOfWeekSeparator,
+ LongDateDaySeparator,
+ LongDateMonthSeparator,
+ LongDateYearSeparator
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLocaleItem_en_AU(sal_Int16& count)
+{
+ count = 0;
+ return (sal_Unicode**)LCType;
+}
+static const sal_Unicode replaceFrom0[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x0};
+static const sal_Unicode replaceTo0[] = {0x5b, 0x24, 0x24, 0x2d, 0x43, 0x30, 0x39, 0x5d, 0x0};
+static const sal_Unicode FormatKey0[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement0[] = {1};
+static const sal_Unicode FormatType0[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage0[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex0[] = {0};
+static const sal_Unicode FormatCode0[] = {0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x0};
+static const sal_Unicode FormatDefaultName0[] = {0x0};
+static const sal_Unicode FormatKey1[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement1[] = {1};
+static const sal_Unicode FormatType1[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage1[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex1[] = {1};
+static const sal_Unicode FormatCode1[] = {0x30, 0x0};
+static const sal_Unicode FormatDefaultName1[] = {0x0};
+static const sal_Unicode FormatKey2[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement2[] = {0};
+static const sal_Unicode FormatType2[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage2[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex2[] = {2};
+static const sal_Unicode FormatCode2[] = {0x30, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName2[] = {0x0};
+static const sal_Unicode FormatKey3[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement3[] = {0};
+static const sal_Unicode FormatType3[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage3[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex3[] = {3};
+static const sal_Unicode FormatCode3[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName3[] = {0x0};
+static const sal_Unicode FormatKey4[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement4[] = {0};
+static const sal_Unicode FormatType4[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage4[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex4[] = {4};
+static const sal_Unicode FormatCode4[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName4[] = {0x0};
+static const sal_Unicode FormatKey5[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement5[] = {0};
+static const sal_Unicode FormatType5[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage5[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex5[] = {5};
+static const sal_Unicode FormatCode5[] = {0x23, 0x2c, 0x23, 0x23, 0x23, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName5[] = {0x0};
+static const sal_Unicode FormatKey6[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x37, 0x0};
+static const sal_Unicode defaultFormatElement6[] = {0};
+static const sal_Unicode FormatType6[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage6[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex6[] = {70};
+static const sal_Unicode FormatCode6[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x5f, 0x29, 0x3b, 0x28, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x29, 0x0};
+static const sal_Unicode FormatDefaultName6[] = {0x0};
+static const sal_Unicode FormatKey7[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x38, 0x0};
+static const sal_Unicode defaultFormatElement7[] = {0};
+static const sal_Unicode FormatType7[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage7[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex7[] = {71};
+static const sal_Unicode FormatCode7[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x5f, 0x29, 0x3b, 0x28, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x29, 0x0};
+static const sal_Unicode FormatDefaultName7[] = {0x0};
+static const sal_Unicode FormatKey8[] = {0x53, 0x63, 0x69, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x63, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement8[] = {0};
+static const sal_Unicode FormatType8[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage8[] = {0x53, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, 0x43, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex8[] = {6};
+static const sal_Unicode FormatCode8[] = {0x30, 0x2e, 0x30, 0x30, 0x45, 0x2b, 0x30, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName8[] = {0x0};
+static const sal_Unicode FormatKey9[] = {0x53, 0x63, 0x69, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x63, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement9[] = {1};
+static const sal_Unicode FormatType9[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage9[] = {0x53, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, 0x43, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex9[] = {7};
+static const sal_Unicode FormatCode9[] = {0x30, 0x2e, 0x30, 0x30, 0x45, 0x2b, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName9[] = {0x0};
+static const sal_Unicode FormatKey10[] = {0x53, 0x63, 0x69, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x63, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement10[] = {0};
+static const sal_Unicode FormatType10[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage10[] = {0x53, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, 0x43, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex10[] = {78};
+static const sal_Unicode FormatCode10[] = {0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x45, 0x2b, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName10[] = {0x0};
+static const sal_Unicode FormatKey11[] = {0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement11[] = {1};
+static const sal_Unicode FormatType11[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage11[] = {0x50, 0x45, 0x52, 0x43, 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex11[] = {8};
+static const sal_Unicode FormatCode11[] = {0x30, 0x25, 0x0};
+static const sal_Unicode FormatDefaultName11[] = {0x0};
+static const sal_Unicode FormatKey12[] = {0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement12[] = {1};
+static const sal_Unicode FormatType12[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage12[] = {0x50, 0x45, 0x52, 0x43, 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex12[] = {9};
+static const sal_Unicode FormatCode12[] = {0x30, 0x2e, 0x30, 0x30, 0x25, 0x0};
+static const sal_Unicode FormatDefaultName12[] = {0x0};
+static const sal_Unicode FormatKey13[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement13[] = {1};
+static const sal_Unicode FormatType13[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage13[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex13[] = {12};
+static const sal_Unicode FormatCode13[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x3b, 0x2d, 0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName13[] = {0x0};
+static const sal_Unicode FormatKey14[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement14[] = {0};
+static const sal_Unicode FormatType14[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage14[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex14[] = {13};
+static const sal_Unicode FormatCode14[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x3b, 0x2d, 0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName14[] = {0x0};
+static const sal_Unicode FormatKey15[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement15[] = {0};
+static const sal_Unicode FormatType15[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage15[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex15[] = {14};
+static const sal_Unicode FormatCode15[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x3b, 0x5b, 0x52, 0x45, 0x44, 0x5d, 0x2d, 0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName15[] = {0x0};
+static const sal_Unicode FormatKey16[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement16[] = {1};
+static const sal_Unicode FormatType16[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage16[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex16[] = {15};
+static const sal_Unicode FormatCode16[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x3b, 0x5b, 0x52, 0x45, 0x44, 0x5d, 0x2d, 0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName16[] = {0x0};
+static const sal_Unicode FormatKey17[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement17[] = {0};
+static const sal_Unicode FormatType17[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage17[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex17[] = {16};
+static const sal_Unicode FormatCode17[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x20, 0x43, 0x43, 0x43, 0x0};
+static const sal_Unicode FormatDefaultName17[] = {0x0};
+static const sal_Unicode FormatKey18[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement18[] = {0};
+static const sal_Unicode FormatType18[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage18[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex18[] = {17};
+static const sal_Unicode FormatCode18[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x2d, 0x2d, 0x3b, 0x5b, 0x52, 0x45, 0x44, 0x5d, 0x2d, 0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x2d, 0x2d, 0x0};
+static const sal_Unicode FormatDefaultName18[] = {0x0};
+static const sal_Unicode FormatKey19[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement19[] = {1};
+static const sal_Unicode FormatType19[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage19[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex19[] = {18};
+static const sal_Unicode FormatCode19[] = {0x44, 0x2f, 0x4d, 0x4d, 0x2f, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName19[] = {0x0};
+static const sal_Unicode FormatKey20[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x39, 0x0};
+static const sal_Unicode defaultFormatElement20[] = {1};
+static const sal_Unicode FormatType20[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage20[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex20[] = {19};
+static const sal_Unicode FormatCode20[] = {0x4e, 0x4e, 0x4e, 0x4e, 0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName20[] = {0x0};
+static const sal_Unicode FormatKey21[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x38, 0x0};
+static const sal_Unicode defaultFormatElement21[] = {1};
+static const sal_Unicode FormatType21[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage21[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex21[] = {20};
+static const sal_Unicode FormatCode21[] = {0x44, 0x44, 0x2f, 0x4d, 0x4d, 0x2f, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName21[] = {0x0};
+static const sal_Unicode FormatKey22[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x37, 0x0};
+static const sal_Unicode defaultFormatElement22[] = {0};
+static const sal_Unicode FormatType22[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage22[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex22[] = {21};
+static const sal_Unicode FormatCode22[] = {0x44, 0x44, 0x2f, 0x4d, 0x4d, 0x2f, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName22[] = {0x0};
+static const sal_Unicode FormatKey23[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x30, 0x0};
+static const sal_Unicode defaultFormatElement23[] = {0};
+static const sal_Unicode FormatType23[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage23[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex23[] = {22};
+static const sal_Unicode FormatCode23[] = {0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName23[] = {0x0};
+static const sal_Unicode FormatKey24[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement24[] = {0};
+static const sal_Unicode FormatType24[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage24[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex24[] = {23};
+static const sal_Unicode FormatCode24[] = {0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName24[] = {0x0};
+static const sal_Unicode FormatKey25[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement25[] = {0};
+static const sal_Unicode FormatType25[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage25[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex25[] = {24};
+static const sal_Unicode FormatCode25[] = {0x44, 0x2e, 0x20, 0x4d, 0x4d, 0x4d, 0x2e, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName25[] = {0x0};
+static const sal_Unicode FormatKey26[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement26[] = {0};
+static const sal_Unicode FormatType26[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage26[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex26[] = {25};
+static const sal_Unicode FormatCode26[] = {0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName26[] = {0x0};
+static const sal_Unicode FormatKey27[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x37, 0x0};
+static const sal_Unicode defaultFormatElement27[] = {0};
+static const sal_Unicode FormatType27[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage27[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex27[] = {26};
+static const sal_Unicode FormatCode27[] = {0x44, 0x2e, 0x20, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName27[] = {0x0};
+static const sal_Unicode FormatKey28[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement28[] = {0};
+static const sal_Unicode FormatType28[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage28[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex28[] = {27};
+static const sal_Unicode FormatCode28[] = {0x4e, 0x4e, 0x2c, 0x20, 0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName28[] = {0x0};
+static const sal_Unicode FormatKey29[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement29[] = {0};
+static const sal_Unicode FormatType29[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage29[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex29[] = {28};
+static const sal_Unicode FormatCode29[] = {0x4e, 0x4e, 0x20, 0x44, 0x44, 0x2f, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName29[] = {0x0};
+static const sal_Unicode FormatKey30[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement30[] = {0};
+static const sal_Unicode FormatType30[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage30[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex30[] = {29};
+static const sal_Unicode FormatCode30[] = {0x4e, 0x4e, 0x2c, 0x20, 0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName30[] = {0x0};
+static const sal_Unicode FormatKey31[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement31[] = {0};
+static const sal_Unicode FormatType31[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage31[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex31[] = {30};
+static const sal_Unicode FormatCode31[] = {0x4e, 0x4e, 0x4e, 0x4e, 0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName31[] = {0x0};
+static const sal_Unicode FormatKey32[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x38, 0x0};
+static const sal_Unicode defaultFormatElement32[] = {0};
+static const sal_Unicode FormatType32[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage32[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex32[] = {31};
+static const sal_Unicode FormatCode32[] = {0x4d, 0x4d, 0x2d, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName32[] = {0x0};
+static const sal_Unicode FormatKey33[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x39, 0x0};
+static const sal_Unicode defaultFormatElement33[] = {0};
+static const sal_Unicode FormatType33[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage33[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex33[] = {32};
+static const sal_Unicode FormatCode33[] = {0x59, 0x59, 0x2d, 0x4d, 0x4d, 0x2d, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName33[] = {0x0};
+static const sal_Unicode FormatKey34[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x30, 0x0};
+static const sal_Unicode defaultFormatElement34[] = {0};
+static const sal_Unicode FormatType34[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage34[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex34[] = {33};
+static const sal_Unicode FormatCode34[] = {0x59, 0x59, 0x59, 0x59, 0x2d, 0x4d, 0x4d, 0x2d, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName34[] = {0x49, 0x53, 0x4f, 0x20, 0x38, 0x36, 0x30, 0x31, 0x0};
+static const sal_Unicode FormatKey35[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement35[] = {0};
+static const sal_Unicode FormatType35[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage35[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex35[] = {34};
+static const sal_Unicode FormatCode35[] = {0x4d, 0x4d, 0x2f, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName35[] = {0x0};
+static const sal_Unicode FormatKey36[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement36[] = {0};
+static const sal_Unicode FormatType36[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage36[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex36[] = {35};
+static const sal_Unicode FormatCode36[] = {0x44, 0x44, 0x2f, 0x4d, 0x4d, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName36[] = {0x0};
+static const sal_Unicode FormatKey37[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement37[] = {0};
+static const sal_Unicode FormatType37[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage37[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex37[] = {36};
+static const sal_Unicode FormatCode37[] = {0x4d, 0x4d, 0x4d, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName37[] = {0x0};
+static const sal_Unicode FormatKey38[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement38[] = {0};
+static const sal_Unicode FormatType38[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage38[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex38[] = {37};
+static const sal_Unicode FormatCode38[] = {0x51, 0x51, 0x20, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName38[] = {0x0};
+static const sal_Unicode FormatKey39[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement39[] = {0};
+static const sal_Unicode FormatType39[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage39[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex39[] = {38};
+static const sal_Unicode FormatCode39[] = {0x57, 0x57, 0x0};
+static const sal_Unicode FormatDefaultName39[] = {0x0};
+static const sal_Unicode FormatKey40[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement40[] = {1};
+static const sal_Unicode FormatType40[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage40[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex40[] = {39};
+static const sal_Unicode FormatCode40[] = {0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName40[] = {0x0};
+static const sal_Unicode FormatKey41[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement41[] = {1};
+static const sal_Unicode FormatType41[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage41[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex41[] = {40};
+static const sal_Unicode FormatCode41[] = {0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x0};
+static const sal_Unicode FormatDefaultName41[] = {0x0};
+static const sal_Unicode FormatKey42[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement42[] = {0};
+static const sal_Unicode FormatType42[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage42[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex42[] = {41};
+static const sal_Unicode FormatCode42[] = {0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x20, 0x41, 0x4d, 0x2f, 0x50, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName42[] = {0x0};
+static const sal_Unicode FormatKey43[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement43[] = {0};
+static const sal_Unicode FormatType43[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage43[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex43[] = {42};
+static const sal_Unicode FormatCode43[] = {0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x20, 0x41, 0x4d, 0x2f, 0x50, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName43[] = {0x0};
+static const sal_Unicode FormatKey44[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement44[] = {0};
+static const sal_Unicode FormatType44[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage44[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex44[] = {43};
+static const sal_Unicode FormatCode44[] = {0x5b, 0x48, 0x48, 0x5d, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x0};
+static const sal_Unicode FormatDefaultName44[] = {0x0};
+static const sal_Unicode FormatKey45[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement45[] = {0};
+static const sal_Unicode FormatType45[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage45[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex45[] = {44};
+static const sal_Unicode FormatCode45[] = {0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName45[] = {0x0};
+static const sal_Unicode FormatKey46[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x37, 0x0};
+static const sal_Unicode defaultFormatElement46[] = {0};
+static const sal_Unicode FormatType46[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage46[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex46[] = {45};
+static const sal_Unicode FormatCode46[] = {0x5b, 0x48, 0x48, 0x5d, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName46[] = {0x0};
+static const sal_Unicode FormatKey47[] = {0x44, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement47[] = {1};
+static const sal_Unicode FormatType47[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage47[] = {0x44, 0x41, 0x54, 0x45, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex47[] = {46};
+static const sal_Unicode FormatCode47[] = {0x44, 0x44, 0x2f, 0x4d, 0x4d, 0x2f, 0x59, 0x59, 0x20, 0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName47[] = {0x0};
+static const sal_Unicode FormatKey48[] = {0x44, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement48[] = {0};
+static const sal_Unicode FormatType48[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage48[] = {0x44, 0x41, 0x54, 0x45, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex48[] = {47};
+static const sal_Unicode FormatCode48[] = {0x44, 0x44, 0x2f, 0x4d, 0x4d, 0x2f, 0x59, 0x59, 0x59, 0x59, 0x20, 0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x0};
+static const sal_Unicode FormatDefaultName48[] = {0x0};
+
+static const sal_Int16 FormatElementsCount0 = 49;
+static const sal_Unicode* FormatElementsArray0[] = {
+ FormatCode0,
+ FormatDefaultName0,
+ FormatKey0,
+ FormatType0,
+ FormatUsage0,
+ Formatindex0,
+ defaultFormatElement0,
+ FormatCode1,
+ FormatDefaultName1,
+ FormatKey1,
+ FormatType1,
+ FormatUsage1,
+ Formatindex1,
+ defaultFormatElement1,
+ FormatCode2,
+ FormatDefaultName2,
+ FormatKey2,
+ FormatType2,
+ FormatUsage2,
+ Formatindex2,
+ defaultFormatElement2,
+ FormatCode3,
+ FormatDefaultName3,
+ FormatKey3,
+ FormatType3,
+ FormatUsage3,
+ Formatindex3,
+ defaultFormatElement3,
+ FormatCode4,
+ FormatDefaultName4,
+ FormatKey4,
+ FormatType4,
+ FormatUsage4,
+ Formatindex4,
+ defaultFormatElement4,
+ FormatCode5,
+ FormatDefaultName5,
+ FormatKey5,
+ FormatType5,
+ FormatUsage5,
+ Formatindex5,
+ defaultFormatElement5,
+ FormatCode6,
+ FormatDefaultName6,
+ FormatKey6,
+ FormatType6,
+ FormatUsage6,
+ Formatindex6,
+ defaultFormatElement6,
+ FormatCode7,
+ FormatDefaultName7,
+ FormatKey7,
+ FormatType7,
+ FormatUsage7,
+ Formatindex7,
+ defaultFormatElement7,
+ FormatCode8,
+ FormatDefaultName8,
+ FormatKey8,
+ FormatType8,
+ FormatUsage8,
+ Formatindex8,
+ defaultFormatElement8,
+ FormatCode9,
+ FormatDefaultName9,
+ FormatKey9,
+ FormatType9,
+ FormatUsage9,
+ Formatindex9,
+ defaultFormatElement9,
+ FormatCode10,
+ FormatDefaultName10,
+ FormatKey10,
+ FormatType10,
+ FormatUsage10,
+ Formatindex10,
+ defaultFormatElement10,
+ FormatCode11,
+ FormatDefaultName11,
+ FormatKey11,
+ FormatType11,
+ FormatUsage11,
+ Formatindex11,
+ defaultFormatElement11,
+ FormatCode12,
+ FormatDefaultName12,
+ FormatKey12,
+ FormatType12,
+ FormatUsage12,
+ Formatindex12,
+ defaultFormatElement12,
+ FormatCode13,
+ FormatDefaultName13,
+ FormatKey13,
+ FormatType13,
+ FormatUsage13,
+ Formatindex13,
+ defaultFormatElement13,
+ FormatCode14,
+ FormatDefaultName14,
+ FormatKey14,
+ FormatType14,
+ FormatUsage14,
+ Formatindex14,
+ defaultFormatElement14,
+ FormatCode15,
+ FormatDefaultName15,
+ FormatKey15,
+ FormatType15,
+ FormatUsage15,
+ Formatindex15,
+ defaultFormatElement15,
+ FormatCode16,
+ FormatDefaultName16,
+ FormatKey16,
+ FormatType16,
+ FormatUsage16,
+ Formatindex16,
+ defaultFormatElement16,
+ FormatCode17,
+ FormatDefaultName17,
+ FormatKey17,
+ FormatType17,
+ FormatUsage17,
+ Formatindex17,
+ defaultFormatElement17,
+ FormatCode18,
+ FormatDefaultName18,
+ FormatKey18,
+ FormatType18,
+ FormatUsage18,
+ Formatindex18,
+ defaultFormatElement18,
+ FormatCode19,
+ FormatDefaultName19,
+ FormatKey19,
+ FormatType19,
+ FormatUsage19,
+ Formatindex19,
+ defaultFormatElement19,
+ FormatCode20,
+ FormatDefaultName20,
+ FormatKey20,
+ FormatType20,
+ FormatUsage20,
+ Formatindex20,
+ defaultFormatElement20,
+ FormatCode21,
+ FormatDefaultName21,
+ FormatKey21,
+ FormatType21,
+ FormatUsage21,
+ Formatindex21,
+ defaultFormatElement21,
+ FormatCode22,
+ FormatDefaultName22,
+ FormatKey22,
+ FormatType22,
+ FormatUsage22,
+ Formatindex22,
+ defaultFormatElement22,
+ FormatCode23,
+ FormatDefaultName23,
+ FormatKey23,
+ FormatType23,
+ FormatUsage23,
+ Formatindex23,
+ defaultFormatElement23,
+ FormatCode24,
+ FormatDefaultName24,
+ FormatKey24,
+ FormatType24,
+ FormatUsage24,
+ Formatindex24,
+ defaultFormatElement24,
+ FormatCode25,
+ FormatDefaultName25,
+ FormatKey25,
+ FormatType25,
+ FormatUsage25,
+ Formatindex25,
+ defaultFormatElement25,
+ FormatCode26,
+ FormatDefaultName26,
+ FormatKey26,
+ FormatType26,
+ FormatUsage26,
+ Formatindex26,
+ defaultFormatElement26,
+ FormatCode27,
+ FormatDefaultName27,
+ FormatKey27,
+ FormatType27,
+ FormatUsage27,
+ Formatindex27,
+ defaultFormatElement27,
+ FormatCode28,
+ FormatDefaultName28,
+ FormatKey28,
+ FormatType28,
+ FormatUsage28,
+ Formatindex28,
+ defaultFormatElement28,
+ FormatCode29,
+ FormatDefaultName29,
+ FormatKey29,
+ FormatType29,
+ FormatUsage29,
+ Formatindex29,
+ defaultFormatElement29,
+ FormatCode30,
+ FormatDefaultName30,
+ FormatKey30,
+ FormatType30,
+ FormatUsage30,
+ Formatindex30,
+ defaultFormatElement30,
+ FormatCode31,
+ FormatDefaultName31,
+ FormatKey31,
+ FormatType31,
+ FormatUsage31,
+ Formatindex31,
+ defaultFormatElement31,
+ FormatCode32,
+ FormatDefaultName32,
+ FormatKey32,
+ FormatType32,
+ FormatUsage32,
+ Formatindex32,
+ defaultFormatElement32,
+ FormatCode33,
+ FormatDefaultName33,
+ FormatKey33,
+ FormatType33,
+ FormatUsage33,
+ Formatindex33,
+ defaultFormatElement33,
+ FormatCode34,
+ FormatDefaultName34,
+ FormatKey34,
+ FormatType34,
+ FormatUsage34,
+ Formatindex34,
+ defaultFormatElement34,
+ FormatCode35,
+ FormatDefaultName35,
+ FormatKey35,
+ FormatType35,
+ FormatUsage35,
+ Formatindex35,
+ defaultFormatElement35,
+ FormatCode36,
+ FormatDefaultName36,
+ FormatKey36,
+ FormatType36,
+ FormatUsage36,
+ Formatindex36,
+ defaultFormatElement36,
+ FormatCode37,
+ FormatDefaultName37,
+ FormatKey37,
+ FormatType37,
+ FormatUsage37,
+ Formatindex37,
+ defaultFormatElement37,
+ FormatCode38,
+ FormatDefaultName38,
+ FormatKey38,
+ FormatType38,
+ FormatUsage38,
+ Formatindex38,
+ defaultFormatElement38,
+ FormatCode39,
+ FormatDefaultName39,
+ FormatKey39,
+ FormatType39,
+ FormatUsage39,
+ Formatindex39,
+ defaultFormatElement39,
+ FormatCode40,
+ FormatDefaultName40,
+ FormatKey40,
+ FormatType40,
+ FormatUsage40,
+ Formatindex40,
+ defaultFormatElement40,
+ FormatCode41,
+ FormatDefaultName41,
+ FormatKey41,
+ FormatType41,
+ FormatUsage41,
+ Formatindex41,
+ defaultFormatElement41,
+ FormatCode42,
+ FormatDefaultName42,
+ FormatKey42,
+ FormatType42,
+ FormatUsage42,
+ Formatindex42,
+ defaultFormatElement42,
+ FormatCode43,
+ FormatDefaultName43,
+ FormatKey43,
+ FormatType43,
+ FormatUsage43,
+ Formatindex43,
+ defaultFormatElement43,
+ FormatCode44,
+ FormatDefaultName44,
+ FormatKey44,
+ FormatType44,
+ FormatUsage44,
+ Formatindex44,
+ defaultFormatElement44,
+ FormatCode45,
+ FormatDefaultName45,
+ FormatKey45,
+ FormatType45,
+ FormatUsage45,
+ Formatindex45,
+ defaultFormatElement45,
+ FormatCode46,
+ FormatDefaultName46,
+ FormatKey46,
+ FormatType46,
+ FormatUsage46,
+ Formatindex46,
+ defaultFormatElement46,
+ FormatCode47,
+ FormatDefaultName47,
+ FormatKey47,
+ FormatType47,
+ FormatUsage47,
+ Formatindex47,
+ defaultFormatElement47,
+ FormatCode48,
+ FormatDefaultName48,
+ FormatKey48,
+ FormatType48,
+ FormatUsage48,
+ Formatindex48,
+ defaultFormatElement48,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode const * const * SAL_CALL getAllFormats0_en_AU(sal_Int16& count, const sal_Unicode*& from, const sal_Unicode*& to)
+{
+ count = FormatElementsCount0;
+ from = replaceFrom0;
+ to = replaceTo0;
+ return (sal_Unicode**)FormatElementsArray0;
+}
+static const sal_Unicode DateAcceptancePattern0[] = {0x44, 0x2f, 0x4d, 0x2f, 0x59, 0x0};
+static const sal_Unicode DateAcceptancePattern1[] = {0x44, 0x2f, 0x4d, 0x0};
+static const sal_Int16 DateAcceptancePatternsCount = 2;
+static const sal_Unicode* DateAcceptancePatternsArray[] = {
+ DateAcceptancePattern0,
+ DateAcceptancePattern1,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getDateAcceptancePatterns_en_AU(sal_Int16& count)
+{
+ count = DateAcceptancePatternsCount;
+ return (sal_Unicode**)DateAcceptancePatternsArray;
+}
+extern sal_Unicode ** SAL_CALL getCollatorImplementation_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollatorImplementation_en_AU(sal_Int16& count)
+{
+ return getCollatorImplementation_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getCollationOptions_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollationOptions_en_AU(sal_Int16& count)
+{
+ return getCollationOptions_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getSearchOptions_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getSearchOptions_en_AU(sal_Int16& count)
+{
+ return getSearchOptions_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getIndexAlgorithm_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getIndexAlgorithm_en_AU(sal_Int16& count)
+{
+ return getIndexAlgorithm_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getUnicodeScripts_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getUnicodeScripts_en_AU(sal_Int16& count)
+{
+ return getUnicodeScripts_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getFollowPageWords_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getFollowPageWords_en_AU(sal_Int16& count)
+{
+ return getFollowPageWords_en_US(count);
+}
+static const sal_Unicode calendarID0[] = {0x67, 0x72, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x61, 0x6e, 0x0};
+static const sal_Unicode defaultCalendar0[] = {1};
+static const sal_Unicode dayID00[] = {0x73, 0x75, 0x6e, 0x0};
+static const sal_Unicode dayDefaultAbbrvName00[] = {0x53, 0x75, 0x6e, 0x0};
+static const sal_Unicode dayDefaultFullName00[] = {0x53, 0x75, 0x6e, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName00[] = {0x53, 0x0};
+static const sal_Unicode dayID01[] = {0x6d, 0x6f, 0x6e, 0x0};
+static const sal_Unicode dayDefaultAbbrvName01[] = {0x4d, 0x6f, 0x6e, 0x0};
+static const sal_Unicode dayDefaultFullName01[] = {0x4d, 0x6f, 0x6e, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName01[] = {0x4d, 0x0};
+static const sal_Unicode dayID02[] = {0x74, 0x75, 0x65, 0x0};
+static const sal_Unicode dayDefaultAbbrvName02[] = {0x54, 0x75, 0x65, 0x0};
+static const sal_Unicode dayDefaultFullName02[] = {0x54, 0x75, 0x65, 0x73, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName02[] = {0x54, 0x0};
+static const sal_Unicode dayID03[] = {0x77, 0x65, 0x64, 0x0};
+static const sal_Unicode dayDefaultAbbrvName03[] = {0x57, 0x65, 0x64, 0x0};
+static const sal_Unicode dayDefaultFullName03[] = {0x57, 0x65, 0x64, 0x6e, 0x65, 0x73, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName03[] = {0x57, 0x0};
+static const sal_Unicode dayID04[] = {0x74, 0x68, 0x75, 0x0};
+static const sal_Unicode dayDefaultAbbrvName04[] = {0x54, 0x68, 0x75, 0x0};
+static const sal_Unicode dayDefaultFullName04[] = {0x54, 0x68, 0x75, 0x72, 0x73, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName04[] = {0x54, 0x0};
+static const sal_Unicode dayID05[] = {0x66, 0x72, 0x69, 0x0};
+static const sal_Unicode dayDefaultAbbrvName05[] = {0x46, 0x72, 0x69, 0x0};
+static const sal_Unicode dayDefaultFullName05[] = {0x46, 0x72, 0x69, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName05[] = {0x46, 0x0};
+static const sal_Unicode dayID06[] = {0x73, 0x61, 0x74, 0x0};
+static const sal_Unicode dayDefaultAbbrvName06[] = {0x53, 0x61, 0x74, 0x0};
+static const sal_Unicode dayDefaultFullName06[] = {0x53, 0x61, 0x74, 0x75, 0x72, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName06[] = {0x53, 0x0};
+static const sal_Unicode monthID00[] = {0x6a, 0x61, 0x6e, 0x0};
+static const sal_Unicode monthDefaultAbbrvName00[] = {0x4a, 0x61, 0x6e, 0x0};
+static const sal_Unicode monthDefaultFullName00[] = {0x4a, 0x61, 0x6e, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode monthDefaultNarrowName00[] = {0x4a, 0x0};
+static const sal_Unicode monthID01[] = {0x66, 0x65, 0x62, 0x0};
+static const sal_Unicode monthDefaultAbbrvName01[] = {0x46, 0x65, 0x62, 0x0};
+static const sal_Unicode monthDefaultFullName01[] = {0x46, 0x65, 0x62, 0x72, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode monthDefaultNarrowName01[] = {0x46, 0x0};
+static const sal_Unicode monthID02[] = {0x6d, 0x61, 0x72, 0x0};
+static const sal_Unicode monthDefaultAbbrvName02[] = {0x4d, 0x61, 0x72, 0x0};
+static const sal_Unicode monthDefaultFullName02[] = {0x4d, 0x61, 0x72, 0x63, 0x68, 0x0};
+static const sal_Unicode monthDefaultNarrowName02[] = {0x4d, 0x0};
+static const sal_Unicode monthID03[] = {0x61, 0x70, 0x72, 0x0};
+static const sal_Unicode monthDefaultAbbrvName03[] = {0x41, 0x70, 0x72, 0x0};
+static const sal_Unicode monthDefaultFullName03[] = {0x41, 0x70, 0x72, 0x69, 0x6c, 0x0};
+static const sal_Unicode monthDefaultNarrowName03[] = {0x41, 0x0};
+static const sal_Unicode monthID04[] = {0x6d, 0x61, 0x79, 0x0};
+static const sal_Unicode monthDefaultAbbrvName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode monthDefaultFullName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode monthDefaultNarrowName04[] = {0x4d, 0x0};
+static const sal_Unicode monthID05[] = {0x6a, 0x75, 0x6e, 0x0};
+static const sal_Unicode monthDefaultAbbrvName05[] = {0x4a, 0x75, 0x6e, 0x0};
+static const sal_Unicode monthDefaultFullName05[] = {0x4a, 0x75, 0x6e, 0x65, 0x0};
+static const sal_Unicode monthDefaultNarrowName05[] = {0x4a, 0x0};
+static const sal_Unicode monthID06[] = {0x6a, 0x75, 0x6c, 0x0};
+static const sal_Unicode monthDefaultAbbrvName06[] = {0x4a, 0x75, 0x6c, 0x0};
+static const sal_Unicode monthDefaultFullName06[] = {0x4a, 0x75, 0x6c, 0x79, 0x0};
+static const sal_Unicode monthDefaultNarrowName06[] = {0x4a, 0x0};
+static const sal_Unicode monthID07[] = {0x61, 0x75, 0x67, 0x0};
+static const sal_Unicode monthDefaultAbbrvName07[] = {0x41, 0x75, 0x67, 0x0};
+static const sal_Unicode monthDefaultFullName07[] = {0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x0};
+static const sal_Unicode monthDefaultNarrowName07[] = {0x41, 0x0};
+static const sal_Unicode monthID08[] = {0x73, 0x65, 0x70, 0x0};
+static const sal_Unicode monthDefaultAbbrvName08[] = {0x53, 0x65, 0x70, 0x0};
+static const sal_Unicode monthDefaultFullName08[] = {0x53, 0x65, 0x70, 0x74, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName08[] = {0x53, 0x0};
+static const sal_Unicode monthID09[] = {0x6f, 0x63, 0x74, 0x0};
+static const sal_Unicode monthDefaultAbbrvName09[] = {0x4f, 0x63, 0x74, 0x0};
+static const sal_Unicode monthDefaultFullName09[] = {0x4f, 0x63, 0x74, 0x6f, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName09[] = {0x4f, 0x0};
+static const sal_Unicode monthID010[] = {0x6e, 0x6f, 0x76, 0x0};
+static const sal_Unicode monthDefaultAbbrvName010[] = {0x4e, 0x6f, 0x76, 0x0};
+static const sal_Unicode monthDefaultFullName010[] = {0x4e, 0x6f, 0x76, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName010[] = {0x4e, 0x0};
+static const sal_Unicode monthID011[] = {0x64, 0x65, 0x63, 0x0};
+static const sal_Unicode monthDefaultAbbrvName011[] = {0x44, 0x65, 0x63, 0x0};
+static const sal_Unicode monthDefaultFullName011[] = {0x44, 0x65, 0x63, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName011[] = {0x44, 0x0};
+static const sal_Unicode genitiveMonthID00[] = {0x6a, 0x61, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName00[] = {0x4a, 0x61, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName00[] = {0x4a, 0x61, 0x6e, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName00[] = {0x4a, 0x0};
+static const sal_Unicode genitiveMonthID01[] = {0x66, 0x65, 0x62, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName01[] = {0x46, 0x65, 0x62, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName01[] = {0x46, 0x65, 0x62, 0x72, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName01[] = {0x46, 0x0};
+static const sal_Unicode genitiveMonthID02[] = {0x6d, 0x61, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName02[] = {0x4d, 0x61, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName02[] = {0x4d, 0x61, 0x72, 0x63, 0x68, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName02[] = {0x4d, 0x0};
+static const sal_Unicode genitiveMonthID03[] = {0x61, 0x70, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName03[] = {0x41, 0x70, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName03[] = {0x41, 0x70, 0x72, 0x69, 0x6c, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName03[] = {0x41, 0x0};
+static const sal_Unicode genitiveMonthID04[] = {0x6d, 0x61, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName04[] = {0x4d, 0x0};
+static const sal_Unicode genitiveMonthID05[] = {0x6a, 0x75, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName05[] = {0x4a, 0x75, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName05[] = {0x4a, 0x75, 0x6e, 0x65, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName05[] = {0x4a, 0x0};
+static const sal_Unicode genitiveMonthID06[] = {0x6a, 0x75, 0x6c, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName06[] = {0x4a, 0x75, 0x6c, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName06[] = {0x4a, 0x75, 0x6c, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName06[] = {0x4a, 0x0};
+static const sal_Unicode genitiveMonthID07[] = {0x61, 0x75, 0x67, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName07[] = {0x41, 0x75, 0x67, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName07[] = {0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName07[] = {0x41, 0x0};
+static const sal_Unicode genitiveMonthID08[] = {0x73, 0x65, 0x70, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName08[] = {0x53, 0x65, 0x70, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName08[] = {0x53, 0x65, 0x70, 0x74, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName08[] = {0x53, 0x0};
+static const sal_Unicode genitiveMonthID09[] = {0x6f, 0x63, 0x74, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName09[] = {0x4f, 0x63, 0x74, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName09[] = {0x4f, 0x63, 0x74, 0x6f, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName09[] = {0x4f, 0x0};
+static const sal_Unicode genitiveMonthID010[] = {0x6e, 0x6f, 0x76, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName010[] = {0x4e, 0x6f, 0x76, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName010[] = {0x4e, 0x6f, 0x76, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName010[] = {0x4e, 0x0};
+static const sal_Unicode genitiveMonthID011[] = {0x64, 0x65, 0x63, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName011[] = {0x44, 0x65, 0x63, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName011[] = {0x44, 0x65, 0x63, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName011[] = {0x44, 0x0};
+static const sal_Unicode partitiveMonthID00[] = {0x6a, 0x61, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName00[] = {0x4a, 0x61, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName00[] = {0x4a, 0x61, 0x6e, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName00[] = {0x4a, 0x0};
+static const sal_Unicode partitiveMonthID01[] = {0x66, 0x65, 0x62, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName01[] = {0x46, 0x65, 0x62, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName01[] = {0x46, 0x65, 0x62, 0x72, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName01[] = {0x46, 0x0};
+static const sal_Unicode partitiveMonthID02[] = {0x6d, 0x61, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName02[] = {0x4d, 0x61, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName02[] = {0x4d, 0x61, 0x72, 0x63, 0x68, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName02[] = {0x4d, 0x0};
+static const sal_Unicode partitiveMonthID03[] = {0x61, 0x70, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName03[] = {0x41, 0x70, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName03[] = {0x41, 0x70, 0x72, 0x69, 0x6c, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName03[] = {0x41, 0x0};
+static const sal_Unicode partitiveMonthID04[] = {0x6d, 0x61, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName04[] = {0x4d, 0x0};
+static const sal_Unicode partitiveMonthID05[] = {0x6a, 0x75, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName05[] = {0x4a, 0x75, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName05[] = {0x4a, 0x75, 0x6e, 0x65, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName05[] = {0x4a, 0x0};
+static const sal_Unicode partitiveMonthID06[] = {0x6a, 0x75, 0x6c, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName06[] = {0x4a, 0x75, 0x6c, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName06[] = {0x4a, 0x75, 0x6c, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName06[] = {0x4a, 0x0};
+static const sal_Unicode partitiveMonthID07[] = {0x61, 0x75, 0x67, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName07[] = {0x41, 0x75, 0x67, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName07[] = {0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName07[] = {0x41, 0x0};
+static const sal_Unicode partitiveMonthID08[] = {0x73, 0x65, 0x70, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName08[] = {0x53, 0x65, 0x70, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName08[] = {0x53, 0x65, 0x70, 0x74, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName08[] = {0x53, 0x0};
+static const sal_Unicode partitiveMonthID09[] = {0x6f, 0x63, 0x74, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName09[] = {0x4f, 0x63, 0x74, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName09[] = {0x4f, 0x63, 0x74, 0x6f, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName09[] = {0x4f, 0x0};
+static const sal_Unicode partitiveMonthID010[] = {0x6e, 0x6f, 0x76, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName010[] = {0x4e, 0x6f, 0x76, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName010[] = {0x4e, 0x6f, 0x76, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName010[] = {0x4e, 0x0};
+static const sal_Unicode partitiveMonthID011[] = {0x64, 0x65, 0x63, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName011[] = {0x44, 0x65, 0x63, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName011[] = {0x44, 0x65, 0x63, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName011[] = {0x44, 0x0};
+static const sal_Unicode eraID00[] = {0x62, 0x63, 0x0};
+
+static const sal_Unicode eraDefaultAbbrvName00[] = {0x42, 0x43, 0x0};
+static const sal_Unicode eraDefaultFullName00[] = {0x42, 0x43, 0x0};
+static const sal_Unicode eraID01[] = {0x61, 0x64, 0x0};
+
+static const sal_Unicode eraDefaultAbbrvName01[] = {0x41, 0x44, 0x0};
+static const sal_Unicode eraDefaultFullName01[] = {0x41, 0x44, 0x0};
+static const sal_Unicode startDayOfWeek0[] = {0x73, 0x75, 0x6e, 0x0};
+static const sal_Unicode minimalDaysInFirstWeek0[] = {1};
+static const sal_Int16 calendarsCount = 1;
+
+static const sal_Unicode nbOfDays[] = {7};
+static const sal_Unicode nbOfMonths[] = {12};
+static const sal_Unicode nbOfGenitiveMonths[] = {12};
+static const sal_Unicode nbOfPartitiveMonths[] = {12};
+static const sal_Unicode nbOfEras[] = {2};
+static const sal_Unicode* calendars[] = {
+ nbOfDays,
+ nbOfMonths,
+ nbOfGenitiveMonths,
+ nbOfPartitiveMonths,
+ nbOfEras,
+ calendarID0,
+ defaultCalendar0,
+ dayID00,
+ dayDefaultAbbrvName00,
+ dayDefaultFullName00,
+ dayDefaultNarrowName00,
+ dayID01,
+ dayDefaultAbbrvName01,
+ dayDefaultFullName01,
+ dayDefaultNarrowName01,
+ dayID02,
+ dayDefaultAbbrvName02,
+ dayDefaultFullName02,
+ dayDefaultNarrowName02,
+ dayID03,
+ dayDefaultAbbrvName03,
+ dayDefaultFullName03,
+ dayDefaultNarrowName03,
+ dayID04,
+ dayDefaultAbbrvName04,
+ dayDefaultFullName04,
+ dayDefaultNarrowName04,
+ dayID05,
+ dayDefaultAbbrvName05,
+ dayDefaultFullName05,
+ dayDefaultNarrowName05,
+ dayID06,
+ dayDefaultAbbrvName06,
+ dayDefaultFullName06,
+ dayDefaultNarrowName06,
+ monthID00,
+ monthDefaultAbbrvName00,
+ monthDefaultFullName00,
+ monthDefaultNarrowName00,
+ monthID01,
+ monthDefaultAbbrvName01,
+ monthDefaultFullName01,
+ monthDefaultNarrowName01,
+ monthID02,
+ monthDefaultAbbrvName02,
+ monthDefaultFullName02,
+ monthDefaultNarrowName02,
+ monthID03,
+ monthDefaultAbbrvName03,
+ monthDefaultFullName03,
+ monthDefaultNarrowName03,
+ monthID04,
+ monthDefaultAbbrvName04,
+ monthDefaultFullName04,
+ monthDefaultNarrowName04,
+ monthID05,
+ monthDefaultAbbrvName05,
+ monthDefaultFullName05,
+ monthDefaultNarrowName05,
+ monthID06,
+ monthDefaultAbbrvName06,
+ monthDefaultFullName06,
+ monthDefaultNarrowName06,
+ monthID07,
+ monthDefaultAbbrvName07,
+ monthDefaultFullName07,
+ monthDefaultNarrowName07,
+ monthID08,
+ monthDefaultAbbrvName08,
+ monthDefaultFullName08,
+ monthDefaultNarrowName08,
+ monthID09,
+ monthDefaultAbbrvName09,
+ monthDefaultFullName09,
+ monthDefaultNarrowName09,
+ monthID010,
+ monthDefaultAbbrvName010,
+ monthDefaultFullName010,
+ monthDefaultNarrowName010,
+ monthID011,
+ monthDefaultAbbrvName011,
+ monthDefaultFullName011,
+ monthDefaultNarrowName011,
+ genitiveMonthID00,
+ genitiveMonthDefaultAbbrvName00,
+ genitiveMonthDefaultFullName00,
+ genitiveMonthDefaultNarrowName00,
+ genitiveMonthID01,
+ genitiveMonthDefaultAbbrvName01,
+ genitiveMonthDefaultFullName01,
+ genitiveMonthDefaultNarrowName01,
+ genitiveMonthID02,
+ genitiveMonthDefaultAbbrvName02,
+ genitiveMonthDefaultFullName02,
+ genitiveMonthDefaultNarrowName02,
+ genitiveMonthID03,
+ genitiveMonthDefaultAbbrvName03,
+ genitiveMonthDefaultFullName03,
+ genitiveMonthDefaultNarrowName03,
+ genitiveMonthID04,
+ genitiveMonthDefaultAbbrvName04,
+ genitiveMonthDefaultFullName04,
+ genitiveMonthDefaultNarrowName04,
+ genitiveMonthID05,
+ genitiveMonthDefaultAbbrvName05,
+ genitiveMonthDefaultFullName05,
+ genitiveMonthDefaultNarrowName05,
+ genitiveMonthID06,
+ genitiveMonthDefaultAbbrvName06,
+ genitiveMonthDefaultFullName06,
+ genitiveMonthDefaultNarrowName06,
+ genitiveMonthID07,
+ genitiveMonthDefaultAbbrvName07,
+ genitiveMonthDefaultFullName07,
+ genitiveMonthDefaultNarrowName07,
+ genitiveMonthID08,
+ genitiveMonthDefaultAbbrvName08,
+ genitiveMonthDefaultFullName08,
+ genitiveMonthDefaultNarrowName08,
+ genitiveMonthID09,
+ genitiveMonthDefaultAbbrvName09,
+ genitiveMonthDefaultFullName09,
+ genitiveMonthDefaultNarrowName09,
+ genitiveMonthID010,
+ genitiveMonthDefaultAbbrvName010,
+ genitiveMonthDefaultFullName010,
+ genitiveMonthDefaultNarrowName010,
+ genitiveMonthID011,
+ genitiveMonthDefaultAbbrvName011,
+ genitiveMonthDefaultFullName011,
+ genitiveMonthDefaultNarrowName011,
+ partitiveMonthID00,
+ partitiveMonthDefaultAbbrvName00,
+ partitiveMonthDefaultFullName00,
+ partitiveMonthDefaultNarrowName00,
+ partitiveMonthID01,
+ partitiveMonthDefaultAbbrvName01,
+ partitiveMonthDefaultFullName01,
+ partitiveMonthDefaultNarrowName01,
+ partitiveMonthID02,
+ partitiveMonthDefaultAbbrvName02,
+ partitiveMonthDefaultFullName02,
+ partitiveMonthDefaultNarrowName02,
+ partitiveMonthID03,
+ partitiveMonthDefaultAbbrvName03,
+ partitiveMonthDefaultFullName03,
+ partitiveMonthDefaultNarrowName03,
+ partitiveMonthID04,
+ partitiveMonthDefaultAbbrvName04,
+ partitiveMonthDefaultFullName04,
+ partitiveMonthDefaultNarrowName04,
+ partitiveMonthID05,
+ partitiveMonthDefaultAbbrvName05,
+ partitiveMonthDefaultFullName05,
+ partitiveMonthDefaultNarrowName05,
+ partitiveMonthID06,
+ partitiveMonthDefaultAbbrvName06,
+ partitiveMonthDefaultFullName06,
+ partitiveMonthDefaultNarrowName06,
+ partitiveMonthID07,
+ partitiveMonthDefaultAbbrvName07,
+ partitiveMonthDefaultFullName07,
+ partitiveMonthDefaultNarrowName07,
+ partitiveMonthID08,
+ partitiveMonthDefaultAbbrvName08,
+ partitiveMonthDefaultFullName08,
+ partitiveMonthDefaultNarrowName08,
+ partitiveMonthID09,
+ partitiveMonthDefaultAbbrvName09,
+ partitiveMonthDefaultFullName09,
+ partitiveMonthDefaultNarrowName09,
+ partitiveMonthID010,
+ partitiveMonthDefaultAbbrvName010,
+ partitiveMonthDefaultFullName010,
+ partitiveMonthDefaultNarrowName010,
+ partitiveMonthID011,
+ partitiveMonthDefaultAbbrvName011,
+ partitiveMonthDefaultFullName011,
+ partitiveMonthDefaultNarrowName011,
+ eraID00,
+ eraDefaultAbbrvName00,
+ eraDefaultFullName00,
+ eraID01,
+ eraDefaultAbbrvName01,
+ eraDefaultFullName01,
+ startDayOfWeek0,
+ minimalDaysInFirstWeek0,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCalendars_en_AU(sal_Int16& count)
+{
+ count = calendarsCount;
+ return (sal_Unicode**)calendars;
+}
+static const sal_Unicode defaultCurrency0[] = {1};
+static const sal_Unicode defaultCurrencyUsedInCompatibleFormatCodes0[] = {1};
+static const sal_Unicode defaultCurrencyLegacyOnly0[] = {0};
+static const sal_Unicode currencyID0[] = {0x41, 0x55, 0x44, 0x0};
+static const sal_Unicode currencySymbol0[] = {0x24, 0x0};
+static const sal_Unicode bankSymbol0[] = {0x41, 0x55, 0x44, 0x0};
+static const sal_Unicode currencyName0[] = {0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x6e, 0x20, 0x44, 0x6f, 0x6c, 0x6c, 0x61, 0x72, 0x0};
+static const sal_Unicode currencyDecimalPlaces0[] = {2};
+
+static const sal_Int16 currencyCount = 1;
+
+static const sal_Unicode* currencies[] = {
+ currencyID0,
+ currencySymbol0,
+ bankSymbol0,
+ currencyName0,
+ defaultCurrency0,
+ defaultCurrencyUsedInCompatibleFormatCodes0,
+ currencyDecimalPlaces0,
+ defaultCurrencyLegacyOnly0,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCurrencies_en_AU(sal_Int16& count)
+{
+ count = currencyCount;
+ return (sal_Unicode**)currencies;
+}
+extern sal_Unicode ** SAL_CALL getTransliterations_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getTransliterations_en_AU(sal_Int16& count)
+{
+ return getTransliterations_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getForbiddenCharacters_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getForbiddenCharacters_en_AU(sal_Int16& count)
+{
+ return getForbiddenCharacters_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getBreakIteratorRules_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getBreakIteratorRules_en_AU(sal_Int16& count)
+{
+ return getBreakIteratorRules_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getReservedWords_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getReservedWords_en_AU(sal_Int16& count)
+{
+ return getReservedWords_en_US(count);
+}
+// ---> ContinuousNumbering
+extern const sal_Unicode *** SAL_CALL getContinuousNumberingLevels_en_US(sal_Int16& nStyles, sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode *** SAL_CALL getContinuousNumberingLevels_en_AU(sal_Int16& nStyles, sal_Int16& nAttributes)
+{
+ return getContinuousNumberingLevels_en_US(nStyles, nAttributes);
+}
+// ---> OutlineNumbering
+extern const sal_Unicode **** SAL_CALL getOutlineNumberingLevels_en_US(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode **** SAL_CALL getOutlineNumberingLevels_en_AU(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes)
+{
+ return getOutlineNumberingLevels_en_US(nStyles, nLevels, nAttributes);
+}
+} // extern "C"
+
diff --git a/vcl/workben/localestub/localedata_en_BW.cxx b/vcl/workben/localestub/localedata_en_BW.cxx
new file mode 100644
index 000000000..b79b8eaa7
--- /dev/null
+++ b/vcl/workben/localestub/localedata_en_BW.cxx
@@ -0,0 +1,140 @@
+#include <sal/types.h>
+
+
+#include <stdio.h>
+
+extern "C" {
+
+static const sal_Unicode langID[] = {0x65, 0x6e, 0x0};
+static const sal_Unicode langDefaultName[] = {0x45, 0x6e, 0x67, 0x6c, 0x69, 0x73, 0x68, 0x0};
+static const sal_Unicode countryID[] = {0x42, 0x57, 0x0};
+static const sal_Unicode countryDefaultName[] = {0x42, 0x6f, 0x74, 0x73, 0x77, 0x61, 0x6e, 0x61, 0x0};
+static const sal_Unicode Variant[] = {0x0};
+
+static const sal_Unicode* LCInfoArray[] = {
+ langID,
+ langDefaultName,
+ countryID,
+ countryDefaultName,
+ Variant
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLCInfo_en_BW(sal_Int16& count)
+{
+ count = 0;
+ return (sal_Unicode**)LCInfoArray;
+}
+extern sal_Unicode ** SAL_CALL getLocaleItem_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLocaleItem_en_BW(sal_Int16& count)
+{
+ return getLocaleItem_en_GB(count);
+}
+static const sal_Unicode replaceTo0[] = {0x5b, 0x24, 0x50, 0x2d, 0x39, 0x38, 0x30, 0x39, 0x5d, 0x0};
+extern sal_Unicode const * const * SAL_CALL getAllFormats0_en_GB(sal_Int16& count, const sal_Unicode*& from, const sal_Unicode*& to);
+SAL_DLLPUBLIC_EXPORT sal_Unicode const * const * SAL_CALL getAllFormats0_en_BW(sal_Int16& count, const sal_Unicode*& from, const sal_Unicode*& to)
+{
+ to = replaceTo0;
+ const sal_Unicode* tmp;
+ return getAllFormats0_en_GB(count, from, tmp);
+}
+extern sal_Unicode ** SAL_CALL getDateAcceptancePatterns_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getDateAcceptancePatterns_en_BW(sal_Int16& count)
+{
+ return getDateAcceptancePatterns_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getCollatorImplementation_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollatorImplementation_en_BW(sal_Int16& count)
+{
+ return getCollatorImplementation_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getCollationOptions_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollationOptions_en_BW(sal_Int16& count)
+{
+ return getCollationOptions_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getSearchOptions_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getSearchOptions_en_BW(sal_Int16& count)
+{
+ return getSearchOptions_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getIndexAlgorithm_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getIndexAlgorithm_en_BW(sal_Int16& count)
+{
+ return getIndexAlgorithm_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getUnicodeScripts_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getUnicodeScripts_en_BW(sal_Int16& count)
+{
+ return getUnicodeScripts_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getFollowPageWords_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getFollowPageWords_en_BW(sal_Int16& count)
+{
+ return getFollowPageWords_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getAllCalendars_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCalendars_en_BW(sal_Int16& count)
+{
+ return getAllCalendars_en_GB(count);
+}
+static const sal_Unicode defaultCurrency0[] = {1};
+static const sal_Unicode defaultCurrencyUsedInCompatibleFormatCodes0[] = {1};
+static const sal_Unicode defaultCurrencyLegacyOnly0[] = {0};
+static const sal_Unicode currencyID0[] = {0x42, 0x57, 0x50, 0x0};
+static const sal_Unicode currencySymbol0[] = {0x50, 0x0};
+static const sal_Unicode bankSymbol0[] = {0x42, 0x57, 0x50, 0x0};
+static const sal_Unicode currencyName0[] = {0x50, 0x75, 0x6c, 0x61, 0x0};
+static const sal_Unicode currencyDecimalPlaces0[] = {2};
+
+static const sal_Int16 currencyCount = 1;
+
+static const sal_Unicode* currencies[] = {
+ currencyID0,
+ currencySymbol0,
+ bankSymbol0,
+ currencyName0,
+ defaultCurrency0,
+ defaultCurrencyUsedInCompatibleFormatCodes0,
+ currencyDecimalPlaces0,
+ defaultCurrencyLegacyOnly0,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCurrencies_en_BW(sal_Int16& count)
+{
+ count = currencyCount;
+ return (sal_Unicode**)currencies;
+}
+extern sal_Unicode ** SAL_CALL getTransliterations_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getTransliterations_en_BW(sal_Int16& count)
+{
+ return getTransliterations_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getForbiddenCharacters_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getForbiddenCharacters_en_BW(sal_Int16& count)
+{
+ return getForbiddenCharacters_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getBreakIteratorRules_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getBreakIteratorRules_en_BW(sal_Int16& count)
+{
+ return getBreakIteratorRules_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getReservedWords_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getReservedWords_en_BW(sal_Int16& count)
+{
+ return getReservedWords_en_GB(count);
+}
+// ---> ContinuousNumbering
+extern const sal_Unicode *** SAL_CALL getContinuousNumberingLevels_en_GB(sal_Int16& nStyles, sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode *** SAL_CALL getContinuousNumberingLevels_en_BW(sal_Int16& nStyles, sal_Int16& nAttributes)
+{
+ return getContinuousNumberingLevels_en_GB(nStyles, nAttributes);
+}
+// ---> OutlineNumbering
+extern const sal_Unicode **** SAL_CALL getOutlineNumberingLevels_en_GB(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode **** SAL_CALL getOutlineNumberingLevels_en_BW(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes)
+{
+ return getOutlineNumberingLevels_en_GB(nStyles, nLevels, nAttributes);
+}
+} // extern "C"
+
diff --git a/vcl/workben/localestub/localedata_en_BZ.cxx b/vcl/workben/localestub/localedata_en_BZ.cxx
new file mode 100644
index 000000000..d8f15e4fe
--- /dev/null
+++ b/vcl/workben/localestub/localedata_en_BZ.cxx
@@ -0,0 +1,225 @@
+#include <sal/types.h>
+
+
+#include <stdio.h>
+
+extern "C" {
+
+static const sal_Unicode langID[] = {0x65, 0x6e, 0x0};
+static const sal_Unicode langDefaultName[] = {0x45, 0x6e, 0x67, 0x6c, 0x69, 0x73, 0x68, 0x0};
+static const sal_Unicode countryID[] = {0x42, 0x5a, 0x0};
+static const sal_Unicode countryDefaultName[] = {0x42, 0x65, 0x6c, 0x69, 0x7a, 0x65, 0x0};
+static const sal_Unicode Variant[] = {0x0};
+
+static const sal_Unicode* LCInfoArray[] = {
+ langID,
+ langDefaultName,
+ countryID,
+ countryDefaultName,
+ Variant
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLCInfo_en_BZ(sal_Int16& count)
+{
+ count = 0;
+ return (sal_Unicode**)LCInfoArray;
+}
+
+
+static const sal_Unicode LC_CTYPE_Unoid[] = {0x0};
+static const sal_Unicode dateSeparator[] = {0x2f, 0x0};
+static const sal_Unicode thousandSeparator[] = {0x2c, 0x0};
+static const sal_Unicode decimalSeparator[] = {0x2e, 0x0};
+static const sal_Unicode timeSeparator[] = {0x3a, 0x0};
+static const sal_Unicode time100SecSeparator[] = {0x2e, 0x0};
+static const sal_Unicode listSeparator[] = {0x3b, 0x0};
+static const sal_Unicode LongDateDayOfWeekSeparator[] = {0x2c, 0x20, 0x0};
+static const sal_Unicode LongDateDaySeparator[] = {0x2c, 0x20, 0x0};
+static const sal_Unicode LongDateMonthSeparator[] = {0x20, 0x0};
+static const sal_Unicode LongDateYearSeparator[] = {0x20, 0x0};
+static const sal_Unicode quotationStart[] = {0x2018, 0x0};
+static const sal_Unicode quotationEnd[] = {0x2019, 0x0};
+static const sal_Unicode doubleQuotationStart[] = {0x201c, 0x0};
+static const sal_Unicode doubleQuotationEnd[] = {0x201d, 0x0};
+static const sal_Unicode timeAM[] = {0x41, 0x4d, 0x0};
+static const sal_Unicode timePM[] = {0x50, 0x4d, 0x0};
+static const sal_Unicode measurementSystem[] = {0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x0};
+
+static const sal_Unicode* LCType[] = {
+ LC_CTYPE_Unoid,
+ dateSeparator,
+ thousandSeparator,
+ decimalSeparator,
+ timeSeparator,
+ time100SecSeparator,
+ listSeparator,
+ quotationStart,
+ quotationEnd,
+ doubleQuotationStart,
+ doubleQuotationEnd,
+ timeAM,
+ timePM,
+ measurementSystem,
+ LongDateDayOfWeekSeparator,
+ LongDateDaySeparator,
+ LongDateMonthSeparator,
+ LongDateYearSeparator
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLocaleItem_en_BZ(sal_Int16& count)
+{
+ count = 0;
+ return (sal_Unicode**)LCType;
+}
+static const sal_Unicode replaceTo0[] = {0x5b, 0x24, 0x42, 0x5a, 0x24, 0x2d, 0x32, 0x38, 0x30, 0x39, 0x5d, 0x0};
+extern sal_Unicode const * const * SAL_CALL getAllFormats0_en_US(sal_Int16& count, const sal_Unicode*& from, const sal_Unicode*& to);
+SAL_DLLPUBLIC_EXPORT sal_Unicode const * const * SAL_CALL getAllFormats0_en_BZ(sal_Int16& count, const sal_Unicode*& from, const sal_Unicode*& to)
+{
+ to = replaceTo0;
+ const sal_Unicode* tmp;
+ return getAllFormats0_en_US(count, from, tmp);
+}
+extern sal_Unicode ** SAL_CALL getDateAcceptancePatterns_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getDateAcceptancePatterns_en_BZ(sal_Int16& count)
+{
+ return getDateAcceptancePatterns_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getCollatorImplementation_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollatorImplementation_en_BZ(sal_Int16& count)
+{
+ return getCollatorImplementation_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getCollationOptions_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollationOptions_en_BZ(sal_Int16& count)
+{
+ return getCollationOptions_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getSearchOptions_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getSearchOptions_en_BZ(sal_Int16& count)
+{
+ return getSearchOptions_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getIndexAlgorithm_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getIndexAlgorithm_en_BZ(sal_Int16& count)
+{
+ return getIndexAlgorithm_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getUnicodeScripts_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getUnicodeScripts_en_BZ(sal_Int16& count)
+{
+ return getUnicodeScripts_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getFollowPageWords_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getFollowPageWords_en_BZ(sal_Int16& count)
+{
+ return getFollowPageWords_en_US(count);
+}
+static const sal_Unicode calendarID0[] = {0x67, 0x72, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x61, 0x6e, 0x0};
+static const sal_Unicode defaultCalendar0[] = {1};
+static const sal_Unicode dayRef0[] = {0x72, 0x65, 0x66, 0x0};
+static const sal_Unicode dayRefName0[] = {0x65, 0x6e, 0x5f, 0x55, 0x53, 0x5f, 0x67, 0x72, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x61, 0x6e, 0x0};
+static const sal_Unicode monthRef0[] = {0x72, 0x65, 0x66, 0x0};
+static const sal_Unicode monthRefName0[] = {0x65, 0x6e, 0x5f, 0x55, 0x53, 0x5f, 0x67, 0x72, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x61, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthRef0[] = {0x72, 0x65, 0x66, 0x0};
+static const sal_Unicode genitiveMonthRefName0[] = {0x65, 0x6e, 0x5f, 0x55, 0x53, 0x5f, 0x67, 0x72, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x61, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthRef0[] = {0x72, 0x65, 0x66, 0x0};
+static const sal_Unicode partitiveMonthRefName0[] = {0x65, 0x6e, 0x5f, 0x55, 0x53, 0x5f, 0x67, 0x72, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x61, 0x6e, 0x0};
+static const sal_Unicode eraRef0[] = {0x72, 0x65, 0x66, 0x0};
+static const sal_Unicode eraRefName0[] = {0x65, 0x6e, 0x5f, 0x55, 0x53, 0x5f, 0x67, 0x72, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x61, 0x6e, 0x0};
+static const sal_Unicode startDayOfWeek0[] = {0x6d, 0x6f, 0x6e, 0x0};
+static const sal_Unicode minimalDaysInFirstWeek0[] = {1};
+static const sal_Int16 calendarsCount = 1;
+
+static const sal_Unicode nbOfDays[] = {0};
+static const sal_Unicode nbOfMonths[] = {0};
+static const sal_Unicode nbOfGenitiveMonths[] = {0};
+static const sal_Unicode nbOfPartitiveMonths[] = {0};
+static const sal_Unicode nbOfEras[] = {0};
+static const sal_Unicode* calendars[] = {
+ nbOfDays,
+ nbOfMonths,
+ nbOfGenitiveMonths,
+ nbOfPartitiveMonths,
+ nbOfEras,
+ calendarID0,
+ defaultCalendar0,
+ dayRef0,
+ dayRefName0,
+ monthRef0,
+ monthRefName0,
+ genitiveMonthRef0,
+ genitiveMonthRefName0,
+ partitiveMonthRef0,
+ partitiveMonthRefName0,
+ eraRef0,
+ eraRefName0,
+ startDayOfWeek0,
+ minimalDaysInFirstWeek0,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCalendars_en_BZ(sal_Int16& count)
+{
+ count = calendarsCount;
+ return (sal_Unicode**)calendars;
+}
+static const sal_Unicode defaultCurrency0[] = {1};
+static const sal_Unicode defaultCurrencyUsedInCompatibleFormatCodes0[] = {1};
+static const sal_Unicode defaultCurrencyLegacyOnly0[] = {0};
+static const sal_Unicode currencyID0[] = {0x42, 0x5a, 0x44, 0x0};
+static const sal_Unicode currencySymbol0[] = {0x42, 0x5a, 0x24, 0x0};
+static const sal_Unicode bankSymbol0[] = {0x42, 0x5a, 0x44, 0x0};
+static const sal_Unicode currencyName0[] = {0x42, 0x65, 0x6c, 0x69, 0x7a, 0x65, 0x20, 0x44, 0x6f, 0x6c, 0x6c, 0x61, 0x72, 0x0};
+static const sal_Unicode currencyDecimalPlaces0[] = {2};
+
+static const sal_Int16 currencyCount = 1;
+
+static const sal_Unicode* currencies[] = {
+ currencyID0,
+ currencySymbol0,
+ bankSymbol0,
+ currencyName0,
+ defaultCurrency0,
+ defaultCurrencyUsedInCompatibleFormatCodes0,
+ currencyDecimalPlaces0,
+ defaultCurrencyLegacyOnly0,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCurrencies_en_BZ(sal_Int16& count)
+{
+ count = currencyCount;
+ return (sal_Unicode**)currencies;
+}
+extern sal_Unicode ** SAL_CALL getTransliterations_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getTransliterations_en_BZ(sal_Int16& count)
+{
+ return getTransliterations_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getForbiddenCharacters_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getForbiddenCharacters_en_BZ(sal_Int16& count)
+{
+ return getForbiddenCharacters_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getBreakIteratorRules_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getBreakIteratorRules_en_BZ(sal_Int16& count)
+{
+ return getBreakIteratorRules_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getReservedWords_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getReservedWords_en_BZ(sal_Int16& count)
+{
+ return getReservedWords_en_US(count);
+}
+// ---> ContinuousNumbering
+extern const sal_Unicode *** SAL_CALL getContinuousNumberingLevels_en_US(sal_Int16& nStyles, sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode *** SAL_CALL getContinuousNumberingLevels_en_BZ(sal_Int16& nStyles, sal_Int16& nAttributes)
+{
+ return getContinuousNumberingLevels_en_US(nStyles, nAttributes);
+}
+// ---> OutlineNumbering
+extern const sal_Unicode **** SAL_CALL getOutlineNumberingLevels_en_US(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode **** SAL_CALL getOutlineNumberingLevels_en_BZ(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes)
+{
+ return getOutlineNumberingLevels_en_US(nStyles, nLevels, nAttributes);
+}
+} // extern "C"
+
diff --git a/vcl/workben/localestub/localedata_en_CA.cxx b/vcl/workben/localestub/localedata_en_CA.cxx
new file mode 100644
index 000000000..5d7270595
--- /dev/null
+++ b/vcl/workben/localestub/localedata_en_CA.cxx
@@ -0,0 +1,929 @@
+#include <sal/types.h>
+
+
+#include <stdio.h>
+
+extern "C" {
+
+static const sal_Unicode langID[] = {0x65, 0x6e, 0x0};
+static const sal_Unicode langDefaultName[] = {0x45, 0x6e, 0x67, 0x6c, 0x69, 0x73, 0x68, 0x0};
+static const sal_Unicode countryID[] = {0x43, 0x41, 0x0};
+static const sal_Unicode countryDefaultName[] = {0x43, 0x61, 0x6e, 0x61, 0x64, 0x61, 0x0};
+static const sal_Unicode Variant[] = {0x0};
+
+static const sal_Unicode* LCInfoArray[] = {
+ langID,
+ langDefaultName,
+ countryID,
+ countryDefaultName,
+ Variant
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLCInfo_en_CA(sal_Int16& count)
+{
+ count = 0;
+ return (sal_Unicode**)LCInfoArray;
+}
+
+
+static const sal_Unicode LC_CTYPE_Unoid[] = {0x0};
+static const sal_Unicode dateSeparator[] = {0x2d, 0x0};
+static const sal_Unicode thousandSeparator[] = {0x2c, 0x0};
+static const sal_Unicode decimalSeparator[] = {0x2e, 0x0};
+static const sal_Unicode timeSeparator[] = {0x3a, 0x0};
+static const sal_Unicode time100SecSeparator[] = {0x2e, 0x0};
+static const sal_Unicode listSeparator[] = {0x3b, 0x0};
+static const sal_Unicode LongDateDayOfWeekSeparator[] = {0x2c, 0x20, 0x0};
+static const sal_Unicode LongDateDaySeparator[] = {0x2c, 0x20, 0x0};
+static const sal_Unicode LongDateMonthSeparator[] = {0x20, 0x0};
+static const sal_Unicode LongDateYearSeparator[] = {0x20, 0x0};
+static const sal_Unicode quotationStart[] = {0x2018, 0x0};
+static const sal_Unicode quotationEnd[] = {0x2019, 0x0};
+static const sal_Unicode doubleQuotationStart[] = {0x201c, 0x0};
+static const sal_Unicode doubleQuotationEnd[] = {0x201d, 0x0};
+static const sal_Unicode timeAM[] = {0x41, 0x4d, 0x0};
+static const sal_Unicode timePM[] = {0x50, 0x4d, 0x0};
+static const sal_Unicode measurementSystem[] = {0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x0};
+
+static const sal_Unicode* LCType[] = {
+ LC_CTYPE_Unoid,
+ dateSeparator,
+ thousandSeparator,
+ decimalSeparator,
+ timeSeparator,
+ time100SecSeparator,
+ listSeparator,
+ quotationStart,
+ quotationEnd,
+ doubleQuotationStart,
+ doubleQuotationEnd,
+ timeAM,
+ timePM,
+ measurementSystem,
+ LongDateDayOfWeekSeparator,
+ LongDateDaySeparator,
+ LongDateMonthSeparator,
+ LongDateYearSeparator
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLocaleItem_en_CA(sal_Int16& count)
+{
+ count = 0;
+ return (sal_Unicode**)LCType;
+}
+static const sal_Unicode replaceFrom0[] = {0x0};
+static const sal_Unicode replaceTo0[] = {0x0};
+static const sal_Unicode FormatKey0[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement0[] = {1};
+static const sal_Unicode FormatType0[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage0[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex0[] = {0};
+static const sal_Unicode FormatCode0[] = {0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x0};
+static const sal_Unicode FormatDefaultName0[] = {0x0};
+static const sal_Unicode FormatKey1[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement1[] = {1};
+static const sal_Unicode FormatType1[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage1[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex1[] = {1};
+static const sal_Unicode FormatCode1[] = {0x30, 0x0};
+static const sal_Unicode FormatDefaultName1[] = {0x0};
+static const sal_Unicode FormatKey2[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement2[] = {0};
+static const sal_Unicode FormatType2[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage2[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex2[] = {2};
+static const sal_Unicode FormatCode2[] = {0x30, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName2[] = {0x0};
+static const sal_Unicode FormatKey3[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement3[] = {0};
+static const sal_Unicode FormatType3[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage3[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex3[] = {3};
+static const sal_Unicode FormatCode3[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName3[] = {0x0};
+static const sal_Unicode FormatKey4[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement4[] = {0};
+static const sal_Unicode FormatType4[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage4[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex4[] = {4};
+static const sal_Unicode FormatCode4[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName4[] = {0x0};
+static const sal_Unicode FormatKey5[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement5[] = {0};
+static const sal_Unicode FormatType5[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage5[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex5[] = {5};
+static const sal_Unicode FormatCode5[] = {0x23, 0x2c, 0x23, 0x23, 0x23, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName5[] = {0x0};
+static const sal_Unicode FormatKey6[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x37, 0x0};
+static const sal_Unicode defaultFormatElement6[] = {0};
+static const sal_Unicode FormatType6[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage6[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex6[] = {70};
+static const sal_Unicode FormatCode6[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x5f, 0x29, 0x3b, 0x28, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x29, 0x0};
+static const sal_Unicode FormatDefaultName6[] = {0x0};
+static const sal_Unicode FormatKey7[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x38, 0x0};
+static const sal_Unicode defaultFormatElement7[] = {0};
+static const sal_Unicode FormatType7[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage7[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex7[] = {71};
+static const sal_Unicode FormatCode7[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x5f, 0x29, 0x3b, 0x28, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x29, 0x0};
+static const sal_Unicode FormatDefaultName7[] = {0x0};
+static const sal_Unicode FormatKey8[] = {0x53, 0x63, 0x69, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x63, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement8[] = {0};
+static const sal_Unicode FormatType8[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage8[] = {0x53, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, 0x43, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex8[] = {6};
+static const sal_Unicode FormatCode8[] = {0x30, 0x2e, 0x30, 0x30, 0x45, 0x2b, 0x30, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName8[] = {0x0};
+static const sal_Unicode FormatKey9[] = {0x53, 0x63, 0x69, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x63, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement9[] = {1};
+static const sal_Unicode FormatType9[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage9[] = {0x53, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, 0x43, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex9[] = {7};
+static const sal_Unicode FormatCode9[] = {0x30, 0x2e, 0x30, 0x30, 0x45, 0x2b, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName9[] = {0x0};
+static const sal_Unicode FormatKey10[] = {0x53, 0x63, 0x69, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x63, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement10[] = {0};
+static const sal_Unicode FormatType10[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage10[] = {0x53, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, 0x43, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex10[] = {78};
+static const sal_Unicode FormatCode10[] = {0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x45, 0x2b, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName10[] = {0x0};
+static const sal_Unicode FormatKey11[] = {0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement11[] = {1};
+static const sal_Unicode FormatType11[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage11[] = {0x50, 0x45, 0x52, 0x43, 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex11[] = {8};
+static const sal_Unicode FormatCode11[] = {0x30, 0x25, 0x0};
+static const sal_Unicode FormatDefaultName11[] = {0x0};
+static const sal_Unicode FormatKey12[] = {0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement12[] = {1};
+static const sal_Unicode FormatType12[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage12[] = {0x50, 0x45, 0x52, 0x43, 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex12[] = {9};
+static const sal_Unicode FormatCode12[] = {0x30, 0x2e, 0x30, 0x30, 0x25, 0x0};
+static const sal_Unicode FormatDefaultName12[] = {0x0};
+static const sal_Unicode FormatKey13[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement13[] = {1};
+static const sal_Unicode FormatType13[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage13[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex13[] = {12};
+static const sal_Unicode FormatCode13[] = {0x5b, 0x24, 0x24, 0x2d, 0x31, 0x30, 0x30, 0x39, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x3b, 0x2d, 0x5b, 0x24, 0x24, 0x2d, 0x31, 0x30, 0x30, 0x39, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName13[] = {0x0};
+static const sal_Unicode FormatKey14[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement14[] = {0};
+static const sal_Unicode FormatType14[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage14[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex14[] = {13};
+static const sal_Unicode FormatCode14[] = {0x5b, 0x24, 0x24, 0x2d, 0x31, 0x30, 0x30, 0x39, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x3b, 0x2d, 0x5b, 0x24, 0x24, 0x2d, 0x31, 0x30, 0x30, 0x39, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName14[] = {0x0};
+static const sal_Unicode FormatKey15[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement15[] = {0};
+static const sal_Unicode FormatType15[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage15[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex15[] = {14};
+static const sal_Unicode FormatCode15[] = {0x5b, 0x24, 0x24, 0x2d, 0x31, 0x30, 0x30, 0x39, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x3b, 0x5b, 0x52, 0x45, 0x44, 0x5d, 0x2d, 0x5b, 0x24, 0x24, 0x2d, 0x31, 0x30, 0x30, 0x39, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName15[] = {0x0};
+static const sal_Unicode FormatKey16[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement16[] = {1};
+static const sal_Unicode FormatType16[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage16[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex16[] = {15};
+static const sal_Unicode FormatCode16[] = {0x5b, 0x24, 0x24, 0x2d, 0x31, 0x30, 0x30, 0x39, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x3b, 0x5b, 0x52, 0x45, 0x44, 0x5d, 0x2d, 0x5b, 0x24, 0x24, 0x2d, 0x31, 0x30, 0x30, 0x39, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName16[] = {0x0};
+static const sal_Unicode FormatKey17[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement17[] = {0};
+static const sal_Unicode FormatType17[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage17[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex17[] = {16};
+static const sal_Unicode FormatCode17[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x20, 0x43, 0x43, 0x43, 0x0};
+static const sal_Unicode FormatDefaultName17[] = {0x0};
+static const sal_Unicode FormatKey18[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement18[] = {0};
+static const sal_Unicode FormatType18[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage18[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex18[] = {17};
+static const sal_Unicode FormatCode18[] = {0x5b, 0x24, 0x24, 0x2d, 0x31, 0x30, 0x30, 0x39, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x2d, 0x2d, 0x3b, 0x5b, 0x52, 0x45, 0x44, 0x5d, 0x2d, 0x5b, 0x24, 0x24, 0x2d, 0x31, 0x30, 0x30, 0x39, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x2d, 0x2d, 0x0};
+static const sal_Unicode FormatDefaultName18[] = {0x0};
+static const sal_Unicode FormatKey19[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement19[] = {1};
+static const sal_Unicode FormatType19[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage19[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex19[] = {18};
+static const sal_Unicode FormatCode19[] = {0x59, 0x59, 0x2d, 0x4d, 0x2d, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName19[] = {0x0};
+static const sal_Unicode FormatKey20[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x39, 0x0};
+static const sal_Unicode defaultFormatElement20[] = {1};
+static const sal_Unicode FormatType20[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage20[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex20[] = {19};
+static const sal_Unicode FormatCode20[] = {0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x44, 0x2c, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName20[] = {0x0};
+static const sal_Unicode FormatKey21[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x38, 0x0};
+static const sal_Unicode defaultFormatElement21[] = {0};
+static const sal_Unicode FormatType21[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage21[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex21[] = {20};
+static const sal_Unicode FormatCode21[] = {0x59, 0x59, 0x2d, 0x4d, 0x4d, 0x2d, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName21[] = {0x0};
+static const sal_Unicode FormatKey22[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x37, 0x0};
+static const sal_Unicode defaultFormatElement22[] = {1};
+static const sal_Unicode FormatType22[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage22[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex22[] = {21};
+static const sal_Unicode FormatCode22[] = {0x59, 0x59, 0x59, 0x59, 0x2d, 0x4d, 0x4d, 0x2d, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName22[] = {0x0};
+static const sal_Unicode FormatKey23[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x30, 0x0};
+static const sal_Unicode defaultFormatElement23[] = {0};
+static const sal_Unicode FormatType23[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage23[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex23[] = {22};
+static const sal_Unicode FormatCode23[] = {0x4d, 0x4d, 0x4d, 0x20, 0x44, 0x2c, 0x20, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName23[] = {0x0};
+static const sal_Unicode FormatKey24[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement24[] = {0};
+static const sal_Unicode FormatType24[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage24[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex24[] = {23};
+static const sal_Unicode FormatCode24[] = {0x4d, 0x4d, 0x4d, 0x20, 0x44, 0x2c, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName24[] = {0x0};
+static const sal_Unicode FormatKey25[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement25[] = {0};
+static const sal_Unicode FormatType25[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage25[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex25[] = {24};
+static const sal_Unicode FormatCode25[] = {0x44, 0x2e, 0x20, 0x4d, 0x4d, 0x4d, 0x2e, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName25[] = {0x0};
+static const sal_Unicode FormatKey26[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement26[] = {0};
+static const sal_Unicode FormatType26[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage26[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex26[] = {25};
+static const sal_Unicode FormatCode26[] = {0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x44, 0x2c, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName26[] = {0x0};
+static const sal_Unicode FormatKey27[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x37, 0x0};
+static const sal_Unicode defaultFormatElement27[] = {0};
+static const sal_Unicode FormatType27[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage27[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex27[] = {26};
+static const sal_Unicode FormatCode27[] = {0x44, 0x2e, 0x20, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName27[] = {0x0};
+static const sal_Unicode FormatKey28[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement28[] = {0};
+static const sal_Unicode FormatType28[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage28[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex28[] = {27};
+static const sal_Unicode FormatCode28[] = {0x4e, 0x4e, 0x2c, 0x20, 0x4d, 0x4d, 0x4d, 0x20, 0x44, 0x2c, 0x20, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName28[] = {0x0};
+static const sal_Unicode FormatKey29[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement29[] = {0};
+static const sal_Unicode FormatType29[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage29[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex29[] = {28};
+static const sal_Unicode FormatCode29[] = {0x4e, 0x4e, 0x20, 0x44, 0x44, 0x2d, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName29[] = {0x0};
+static const sal_Unicode FormatKey30[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement30[] = {0};
+static const sal_Unicode FormatType30[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage30[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex30[] = {29};
+static const sal_Unicode FormatCode30[] = {0x4e, 0x4e, 0x2c, 0x20, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x44, 0x2c, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName30[] = {0x0};
+static const sal_Unicode FormatKey31[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement31[] = {0};
+static const sal_Unicode FormatType31[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage31[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex31[] = {30};
+static const sal_Unicode FormatCode31[] = {0x4e, 0x4e, 0x4e, 0x4e, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x44, 0x2c, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName31[] = {0x0};
+static const sal_Unicode FormatKey32[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x38, 0x0};
+static const sal_Unicode defaultFormatElement32[] = {0};
+static const sal_Unicode FormatType32[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage32[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex32[] = {31};
+static const sal_Unicode FormatCode32[] = {0x4d, 0x4d, 0x2d, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName32[] = {0x0};
+static const sal_Unicode FormatKey33[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x39, 0x0};
+static const sal_Unicode defaultFormatElement33[] = {0};
+static const sal_Unicode FormatType33[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage33[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex33[] = {32};
+static const sal_Unicode FormatCode33[] = {0x59, 0x59, 0x2d, 0x4d, 0x4d, 0x2d, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName33[] = {0x0};
+static const sal_Unicode FormatKey34[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x30, 0x0};
+static const sal_Unicode defaultFormatElement34[] = {0};
+static const sal_Unicode FormatType34[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage34[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex34[] = {33};
+static const sal_Unicode FormatCode34[] = {0x59, 0x59, 0x59, 0x59, 0x2d, 0x4d, 0x4d, 0x2d, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName34[] = {0x49, 0x53, 0x4f, 0x20, 0x38, 0x36, 0x30, 0x31, 0x0};
+static const sal_Unicode FormatKey35[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement35[] = {0};
+static const sal_Unicode FormatType35[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage35[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex35[] = {34};
+static const sal_Unicode FormatCode35[] = {0x59, 0x59, 0x2d, 0x4d, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName35[] = {0x0};
+static const sal_Unicode FormatKey36[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement36[] = {0};
+static const sal_Unicode FormatType36[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage36[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex36[] = {35};
+static const sal_Unicode FormatCode36[] = {0x4d, 0x4d, 0x4d, 0x20, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName36[] = {0x0};
+static const sal_Unicode FormatKey37[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement37[] = {0};
+static const sal_Unicode FormatType37[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage37[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex37[] = {36};
+static const sal_Unicode FormatCode37[] = {0x4d, 0x4d, 0x4d, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName37[] = {0x0};
+static const sal_Unicode FormatKey38[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement38[] = {0};
+static const sal_Unicode FormatType38[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage38[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex38[] = {37};
+static const sal_Unicode FormatCode38[] = {0x51, 0x51, 0x20, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName38[] = {0x0};
+static const sal_Unicode FormatKey39[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement39[] = {0};
+static const sal_Unicode FormatType39[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage39[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex39[] = {38};
+static const sal_Unicode FormatCode39[] = {0x57, 0x57, 0x0};
+static const sal_Unicode FormatDefaultName39[] = {0x0};
+static const sal_Unicode FormatKey40[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement40[] = {1};
+static const sal_Unicode FormatType40[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage40[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex40[] = {39};
+static const sal_Unicode FormatCode40[] = {0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName40[] = {0x0};
+static const sal_Unicode FormatKey41[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement41[] = {1};
+static const sal_Unicode FormatType41[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage41[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex41[] = {40};
+static const sal_Unicode FormatCode41[] = {0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x0};
+static const sal_Unicode FormatDefaultName41[] = {0x0};
+static const sal_Unicode FormatKey42[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement42[] = {0};
+static const sal_Unicode FormatType42[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage42[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex42[] = {41};
+static const sal_Unicode FormatCode42[] = {0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x20, 0x41, 0x4d, 0x2f, 0x50, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName42[] = {0x0};
+static const sal_Unicode FormatKey43[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement43[] = {0};
+static const sal_Unicode FormatType43[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage43[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex43[] = {42};
+static const sal_Unicode FormatCode43[] = {0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x20, 0x41, 0x4d, 0x2f, 0x50, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName43[] = {0x0};
+static const sal_Unicode FormatKey44[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement44[] = {0};
+static const sal_Unicode FormatType44[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage44[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex44[] = {43};
+static const sal_Unicode FormatCode44[] = {0x5b, 0x48, 0x48, 0x5d, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x0};
+static const sal_Unicode FormatDefaultName44[] = {0x0};
+static const sal_Unicode FormatKey45[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement45[] = {0};
+static const sal_Unicode FormatType45[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage45[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex45[] = {44};
+static const sal_Unicode FormatCode45[] = {0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName45[] = {0x0};
+static const sal_Unicode FormatKey46[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x37, 0x0};
+static const sal_Unicode defaultFormatElement46[] = {0};
+static const sal_Unicode FormatType46[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage46[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex46[] = {45};
+static const sal_Unicode FormatCode46[] = {0x5b, 0x48, 0x48, 0x5d, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName46[] = {0x0};
+static const sal_Unicode FormatKey47[] = {0x44, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement47[] = {1};
+static const sal_Unicode FormatType47[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage47[] = {0x44, 0x41, 0x54, 0x45, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex47[] = {46};
+static const sal_Unicode FormatCode47[] = {0x59, 0x59, 0x59, 0x59, 0x2d, 0x4d, 0x4d, 0x2d, 0x44, 0x44, 0x20, 0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName47[] = {0x0};
+static const sal_Unicode FormatKey48[] = {0x44, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement48[] = {0};
+static const sal_Unicode FormatType48[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage48[] = {0x44, 0x41, 0x54, 0x45, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex48[] = {47};
+static const sal_Unicode FormatCode48[] = {0x59, 0x59, 0x59, 0x59, 0x2d, 0x4d, 0x4d, 0x2d, 0x44, 0x44, 0x20, 0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x0};
+static const sal_Unicode FormatDefaultName48[] = {0x0};
+
+static const sal_Int16 FormatElementsCount0 = 49;
+static const sal_Unicode* FormatElementsArray0[] = {
+ FormatCode0,
+ FormatDefaultName0,
+ FormatKey0,
+ FormatType0,
+ FormatUsage0,
+ Formatindex0,
+ defaultFormatElement0,
+ FormatCode1,
+ FormatDefaultName1,
+ FormatKey1,
+ FormatType1,
+ FormatUsage1,
+ Formatindex1,
+ defaultFormatElement1,
+ FormatCode2,
+ FormatDefaultName2,
+ FormatKey2,
+ FormatType2,
+ FormatUsage2,
+ Formatindex2,
+ defaultFormatElement2,
+ FormatCode3,
+ FormatDefaultName3,
+ FormatKey3,
+ FormatType3,
+ FormatUsage3,
+ Formatindex3,
+ defaultFormatElement3,
+ FormatCode4,
+ FormatDefaultName4,
+ FormatKey4,
+ FormatType4,
+ FormatUsage4,
+ Formatindex4,
+ defaultFormatElement4,
+ FormatCode5,
+ FormatDefaultName5,
+ FormatKey5,
+ FormatType5,
+ FormatUsage5,
+ Formatindex5,
+ defaultFormatElement5,
+ FormatCode6,
+ FormatDefaultName6,
+ FormatKey6,
+ FormatType6,
+ FormatUsage6,
+ Formatindex6,
+ defaultFormatElement6,
+ FormatCode7,
+ FormatDefaultName7,
+ FormatKey7,
+ FormatType7,
+ FormatUsage7,
+ Formatindex7,
+ defaultFormatElement7,
+ FormatCode8,
+ FormatDefaultName8,
+ FormatKey8,
+ FormatType8,
+ FormatUsage8,
+ Formatindex8,
+ defaultFormatElement8,
+ FormatCode9,
+ FormatDefaultName9,
+ FormatKey9,
+ FormatType9,
+ FormatUsage9,
+ Formatindex9,
+ defaultFormatElement9,
+ FormatCode10,
+ FormatDefaultName10,
+ FormatKey10,
+ FormatType10,
+ FormatUsage10,
+ Formatindex10,
+ defaultFormatElement10,
+ FormatCode11,
+ FormatDefaultName11,
+ FormatKey11,
+ FormatType11,
+ FormatUsage11,
+ Formatindex11,
+ defaultFormatElement11,
+ FormatCode12,
+ FormatDefaultName12,
+ FormatKey12,
+ FormatType12,
+ FormatUsage12,
+ Formatindex12,
+ defaultFormatElement12,
+ FormatCode13,
+ FormatDefaultName13,
+ FormatKey13,
+ FormatType13,
+ FormatUsage13,
+ Formatindex13,
+ defaultFormatElement13,
+ FormatCode14,
+ FormatDefaultName14,
+ FormatKey14,
+ FormatType14,
+ FormatUsage14,
+ Formatindex14,
+ defaultFormatElement14,
+ FormatCode15,
+ FormatDefaultName15,
+ FormatKey15,
+ FormatType15,
+ FormatUsage15,
+ Formatindex15,
+ defaultFormatElement15,
+ FormatCode16,
+ FormatDefaultName16,
+ FormatKey16,
+ FormatType16,
+ FormatUsage16,
+ Formatindex16,
+ defaultFormatElement16,
+ FormatCode17,
+ FormatDefaultName17,
+ FormatKey17,
+ FormatType17,
+ FormatUsage17,
+ Formatindex17,
+ defaultFormatElement17,
+ FormatCode18,
+ FormatDefaultName18,
+ FormatKey18,
+ FormatType18,
+ FormatUsage18,
+ Formatindex18,
+ defaultFormatElement18,
+ FormatCode19,
+ FormatDefaultName19,
+ FormatKey19,
+ FormatType19,
+ FormatUsage19,
+ Formatindex19,
+ defaultFormatElement19,
+ FormatCode20,
+ FormatDefaultName20,
+ FormatKey20,
+ FormatType20,
+ FormatUsage20,
+ Formatindex20,
+ defaultFormatElement20,
+ FormatCode21,
+ FormatDefaultName21,
+ FormatKey21,
+ FormatType21,
+ FormatUsage21,
+ Formatindex21,
+ defaultFormatElement21,
+ FormatCode22,
+ FormatDefaultName22,
+ FormatKey22,
+ FormatType22,
+ FormatUsage22,
+ Formatindex22,
+ defaultFormatElement22,
+ FormatCode23,
+ FormatDefaultName23,
+ FormatKey23,
+ FormatType23,
+ FormatUsage23,
+ Formatindex23,
+ defaultFormatElement23,
+ FormatCode24,
+ FormatDefaultName24,
+ FormatKey24,
+ FormatType24,
+ FormatUsage24,
+ Formatindex24,
+ defaultFormatElement24,
+ FormatCode25,
+ FormatDefaultName25,
+ FormatKey25,
+ FormatType25,
+ FormatUsage25,
+ Formatindex25,
+ defaultFormatElement25,
+ FormatCode26,
+ FormatDefaultName26,
+ FormatKey26,
+ FormatType26,
+ FormatUsage26,
+ Formatindex26,
+ defaultFormatElement26,
+ FormatCode27,
+ FormatDefaultName27,
+ FormatKey27,
+ FormatType27,
+ FormatUsage27,
+ Formatindex27,
+ defaultFormatElement27,
+ FormatCode28,
+ FormatDefaultName28,
+ FormatKey28,
+ FormatType28,
+ FormatUsage28,
+ Formatindex28,
+ defaultFormatElement28,
+ FormatCode29,
+ FormatDefaultName29,
+ FormatKey29,
+ FormatType29,
+ FormatUsage29,
+ Formatindex29,
+ defaultFormatElement29,
+ FormatCode30,
+ FormatDefaultName30,
+ FormatKey30,
+ FormatType30,
+ FormatUsage30,
+ Formatindex30,
+ defaultFormatElement30,
+ FormatCode31,
+ FormatDefaultName31,
+ FormatKey31,
+ FormatType31,
+ FormatUsage31,
+ Formatindex31,
+ defaultFormatElement31,
+ FormatCode32,
+ FormatDefaultName32,
+ FormatKey32,
+ FormatType32,
+ FormatUsage32,
+ Formatindex32,
+ defaultFormatElement32,
+ FormatCode33,
+ FormatDefaultName33,
+ FormatKey33,
+ FormatType33,
+ FormatUsage33,
+ Formatindex33,
+ defaultFormatElement33,
+ FormatCode34,
+ FormatDefaultName34,
+ FormatKey34,
+ FormatType34,
+ FormatUsage34,
+ Formatindex34,
+ defaultFormatElement34,
+ FormatCode35,
+ FormatDefaultName35,
+ FormatKey35,
+ FormatType35,
+ FormatUsage35,
+ Formatindex35,
+ defaultFormatElement35,
+ FormatCode36,
+ FormatDefaultName36,
+ FormatKey36,
+ FormatType36,
+ FormatUsage36,
+ Formatindex36,
+ defaultFormatElement36,
+ FormatCode37,
+ FormatDefaultName37,
+ FormatKey37,
+ FormatType37,
+ FormatUsage37,
+ Formatindex37,
+ defaultFormatElement37,
+ FormatCode38,
+ FormatDefaultName38,
+ FormatKey38,
+ FormatType38,
+ FormatUsage38,
+ Formatindex38,
+ defaultFormatElement38,
+ FormatCode39,
+ FormatDefaultName39,
+ FormatKey39,
+ FormatType39,
+ FormatUsage39,
+ Formatindex39,
+ defaultFormatElement39,
+ FormatCode40,
+ FormatDefaultName40,
+ FormatKey40,
+ FormatType40,
+ FormatUsage40,
+ Formatindex40,
+ defaultFormatElement40,
+ FormatCode41,
+ FormatDefaultName41,
+ FormatKey41,
+ FormatType41,
+ FormatUsage41,
+ Formatindex41,
+ defaultFormatElement41,
+ FormatCode42,
+ FormatDefaultName42,
+ FormatKey42,
+ FormatType42,
+ FormatUsage42,
+ Formatindex42,
+ defaultFormatElement42,
+ FormatCode43,
+ FormatDefaultName43,
+ FormatKey43,
+ FormatType43,
+ FormatUsage43,
+ Formatindex43,
+ defaultFormatElement43,
+ FormatCode44,
+ FormatDefaultName44,
+ FormatKey44,
+ FormatType44,
+ FormatUsage44,
+ Formatindex44,
+ defaultFormatElement44,
+ FormatCode45,
+ FormatDefaultName45,
+ FormatKey45,
+ FormatType45,
+ FormatUsage45,
+ Formatindex45,
+ defaultFormatElement45,
+ FormatCode46,
+ FormatDefaultName46,
+ FormatKey46,
+ FormatType46,
+ FormatUsage46,
+ Formatindex46,
+ defaultFormatElement46,
+ FormatCode47,
+ FormatDefaultName47,
+ FormatKey47,
+ FormatType47,
+ FormatUsage47,
+ Formatindex47,
+ defaultFormatElement47,
+ FormatCode48,
+ FormatDefaultName48,
+ FormatKey48,
+ FormatType48,
+ FormatUsage48,
+ Formatindex48,
+ defaultFormatElement48,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode const * const * SAL_CALL getAllFormats0_en_CA(sal_Int16& count, const sal_Unicode*& from, const sal_Unicode*& to)
+{
+ count = FormatElementsCount0;
+ from = replaceFrom0;
+ to = replaceTo0;
+ return (sal_Unicode**)FormatElementsArray0;
+}
+static const sal_Unicode DateAcceptancePattern0[] = {0x59, 0x2d, 0x4d, 0x2d, 0x44, 0x0};
+static const sal_Unicode DateAcceptancePattern1[] = {0x4d, 0x2d, 0x44, 0x0};
+static const sal_Unicode DateAcceptancePattern2[] = {0x4d, 0x2f, 0x44, 0x2f, 0x59, 0x0};
+static const sal_Unicode DateAcceptancePattern3[] = {0x4d, 0x2f, 0x44, 0x0};
+static const sal_Int16 DateAcceptancePatternsCount = 4;
+static const sal_Unicode* DateAcceptancePatternsArray[] = {
+ DateAcceptancePattern0,
+ DateAcceptancePattern1,
+ DateAcceptancePattern2,
+ DateAcceptancePattern3,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getDateAcceptancePatterns_en_CA(sal_Int16& count)
+{
+ count = DateAcceptancePatternsCount;
+ return (sal_Unicode**)DateAcceptancePatternsArray;
+}
+extern sal_Unicode ** SAL_CALL getCollatorImplementation_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollatorImplementation_en_CA(sal_Int16& count)
+{
+ return getCollatorImplementation_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getCollationOptions_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollationOptions_en_CA(sal_Int16& count)
+{
+ return getCollationOptions_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getSearchOptions_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getSearchOptions_en_CA(sal_Int16& count)
+{
+ return getSearchOptions_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getIndexAlgorithm_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getIndexAlgorithm_en_CA(sal_Int16& count)
+{
+ return getIndexAlgorithm_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getUnicodeScripts_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getUnicodeScripts_en_CA(sal_Int16& count)
+{
+ return getUnicodeScripts_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getFollowPageWords_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getFollowPageWords_en_CA(sal_Int16& count)
+{
+ return getFollowPageWords_en_US(count);
+}
+static const sal_Unicode calendarID0[] = {0x67, 0x72, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x61, 0x6e, 0x0};
+static const sal_Unicode defaultCalendar0[] = {1};
+static const sal_Unicode dayRef0[] = {0x72, 0x65, 0x66, 0x0};
+static const sal_Unicode dayRefName0[] = {0x65, 0x6e, 0x5f, 0x55, 0x53, 0x5f, 0x67, 0x72, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x61, 0x6e, 0x0};
+static const sal_Unicode monthRef0[] = {0x72, 0x65, 0x66, 0x0};
+static const sal_Unicode monthRefName0[] = {0x65, 0x6e, 0x5f, 0x55, 0x53, 0x5f, 0x67, 0x72, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x61, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthRef0[] = {0x72, 0x65, 0x66, 0x0};
+static const sal_Unicode genitiveMonthRefName0[] = {0x65, 0x6e, 0x5f, 0x55, 0x53, 0x5f, 0x67, 0x72, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x61, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthRef0[] = {0x72, 0x65, 0x66, 0x0};
+static const sal_Unicode partitiveMonthRefName0[] = {0x65, 0x6e, 0x5f, 0x55, 0x53, 0x5f, 0x67, 0x72, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x61, 0x6e, 0x0};
+static const sal_Unicode eraRef0[] = {0x72, 0x65, 0x66, 0x0};
+static const sal_Unicode eraRefName0[] = {0x65, 0x6e, 0x5f, 0x55, 0x53, 0x5f, 0x67, 0x72, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x61, 0x6e, 0x0};
+static const sal_Unicode startDayOfWeek0[] = {0x73, 0x75, 0x6e, 0x0};
+static const sal_Unicode minimalDaysInFirstWeek0[] = {4};
+static const sal_Int16 calendarsCount = 1;
+
+static const sal_Unicode nbOfDays[] = {0};
+static const sal_Unicode nbOfMonths[] = {0};
+static const sal_Unicode nbOfGenitiveMonths[] = {0};
+static const sal_Unicode nbOfPartitiveMonths[] = {0};
+static const sal_Unicode nbOfEras[] = {0};
+static const sal_Unicode* calendars[] = {
+ nbOfDays,
+ nbOfMonths,
+ nbOfGenitiveMonths,
+ nbOfPartitiveMonths,
+ nbOfEras,
+ calendarID0,
+ defaultCalendar0,
+ dayRef0,
+ dayRefName0,
+ monthRef0,
+ monthRefName0,
+ genitiveMonthRef0,
+ genitiveMonthRefName0,
+ partitiveMonthRef0,
+ partitiveMonthRefName0,
+ eraRef0,
+ eraRefName0,
+ startDayOfWeek0,
+ minimalDaysInFirstWeek0,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCalendars_en_CA(sal_Int16& count)
+{
+ count = calendarsCount;
+ return (sal_Unicode**)calendars;
+}
+static const sal_Unicode defaultCurrency0[] = {1};
+static const sal_Unicode defaultCurrencyUsedInCompatibleFormatCodes0[] = {1};
+static const sal_Unicode defaultCurrencyLegacyOnly0[] = {0};
+static const sal_Unicode currencyID0[] = {0x43, 0x41, 0x44, 0x0};
+static const sal_Unicode currencySymbol0[] = {0x24, 0x0};
+static const sal_Unicode bankSymbol0[] = {0x43, 0x41, 0x44, 0x0};
+static const sal_Unicode currencyName0[] = {0x43, 0x61, 0x6e, 0x61, 0x64, 0x69, 0x61, 0x6e, 0x20, 0x44, 0x6f, 0x6c, 0x6c, 0x61, 0x72, 0x0};
+static const sal_Unicode currencyDecimalPlaces0[] = {2};
+
+static const sal_Int16 currencyCount = 1;
+
+static const sal_Unicode* currencies[] = {
+ currencyID0,
+ currencySymbol0,
+ bankSymbol0,
+ currencyName0,
+ defaultCurrency0,
+ defaultCurrencyUsedInCompatibleFormatCodes0,
+ currencyDecimalPlaces0,
+ defaultCurrencyLegacyOnly0,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCurrencies_en_CA(sal_Int16& count)
+{
+ count = currencyCount;
+ return (sal_Unicode**)currencies;
+}
+extern sal_Unicode ** SAL_CALL getTransliterations_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getTransliterations_en_CA(sal_Int16& count)
+{
+ return getTransliterations_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getForbiddenCharacters_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getForbiddenCharacters_en_CA(sal_Int16& count)
+{
+ return getForbiddenCharacters_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getBreakIteratorRules_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getBreakIteratorRules_en_CA(sal_Int16& count)
+{
+ return getBreakIteratorRules_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getReservedWords_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getReservedWords_en_CA(sal_Int16& count)
+{
+ return getReservedWords_en_US(count);
+}
+// ---> ContinuousNumbering
+extern const sal_Unicode *** SAL_CALL getContinuousNumberingLevels_en_US(sal_Int16& nStyles, sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode *** SAL_CALL getContinuousNumberingLevels_en_CA(sal_Int16& nStyles, sal_Int16& nAttributes)
+{
+ return getContinuousNumberingLevels_en_US(nStyles, nAttributes);
+}
+// ---> OutlineNumbering
+extern const sal_Unicode **** SAL_CALL getOutlineNumberingLevels_en_US(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode **** SAL_CALL getOutlineNumberingLevels_en_CA(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes)
+{
+ return getOutlineNumberingLevels_en_US(nStyles, nLevels, nAttributes);
+}
+} // extern "C"
+
diff --git a/vcl/workben/localestub/localedata_en_GB.cxx b/vcl/workben/localestub/localedata_en_GB.cxx
new file mode 100644
index 000000000..4aea47251
--- /dev/null
+++ b/vcl/workben/localestub/localedata_en_GB.cxx
@@ -0,0 +1,912 @@
+#include <sal/types.h>
+
+
+#include <stdio.h>
+
+extern "C" {
+
+static const sal_Unicode langID[] = {0x65, 0x6e, 0x0};
+static const sal_Unicode langDefaultName[] = {0x45, 0x6e, 0x67, 0x6c, 0x69, 0x73, 0x68, 0x0};
+static const sal_Unicode countryID[] = {0x47, 0x42, 0x0};
+static const sal_Unicode countryDefaultName[] = {0x55, 0x6e, 0x69, 0x74, 0x65, 0x64, 0x20, 0x4b, 0x69, 0x6e, 0x67, 0x64, 0x6f, 0x6d, 0x0};
+static const sal_Unicode Variant[] = {0x0};
+
+static const sal_Unicode* LCInfoArray[] = {
+ langID,
+ langDefaultName,
+ countryID,
+ countryDefaultName,
+ Variant
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLCInfo_en_GB(sal_Int16& count)
+{
+ count = 0;
+ return (sal_Unicode**)LCInfoArray;
+}
+
+
+static const sal_Unicode LC_CTYPE_Unoid[] = {0x67, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x0};
+static const sal_Unicode dateSeparator[] = {0x2f, 0x0};
+static const sal_Unicode thousandSeparator[] = {0x2c, 0x0};
+static const sal_Unicode decimalSeparator[] = {0x2e, 0x0};
+static const sal_Unicode timeSeparator[] = {0x3a, 0x0};
+static const sal_Unicode time100SecSeparator[] = {0x2e, 0x0};
+static const sal_Unicode listSeparator[] = {0x3b, 0x0};
+static const sal_Unicode LongDateDayOfWeekSeparator[] = {0x20, 0x0};
+static const sal_Unicode LongDateDaySeparator[] = {0x20, 0x0};
+static const sal_Unicode LongDateMonthSeparator[] = {0x20, 0x0};
+static const sal_Unicode LongDateYearSeparator[] = {0x20, 0x0};
+static const sal_Unicode quotationStart[] = {0x2018, 0x0};
+static const sal_Unicode quotationEnd[] = {0x2019, 0x0};
+static const sal_Unicode doubleQuotationStart[] = {0x201c, 0x0};
+static const sal_Unicode doubleQuotationEnd[] = {0x201d, 0x0};
+static const sal_Unicode timeAM[] = {0x41, 0x4d, 0x0};
+static const sal_Unicode timePM[] = {0x50, 0x4d, 0x0};
+static const sal_Unicode measurementSystem[] = {0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x0};
+
+static const sal_Unicode* LCType[] = {
+ LC_CTYPE_Unoid,
+ dateSeparator,
+ thousandSeparator,
+ decimalSeparator,
+ timeSeparator,
+ time100SecSeparator,
+ listSeparator,
+ quotationStart,
+ quotationEnd,
+ doubleQuotationStart,
+ doubleQuotationEnd,
+ timeAM,
+ timePM,
+ measurementSystem,
+ LongDateDayOfWeekSeparator,
+ LongDateDaySeparator,
+ LongDateMonthSeparator,
+ LongDateYearSeparator
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLocaleItem_en_GB(sal_Int16& count)
+{
+ count = 0;
+ return (sal_Unicode**)LCType;
+}
+static const sal_Unicode replaceFrom0[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x0};
+static const sal_Unicode replaceTo0[] = {0x5b, 0x24, 0xa3, 0x2d, 0x38, 0x30, 0x39, 0x5d, 0x0};
+static const sal_Unicode FormatKey0[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement0[] = {1};
+static const sal_Unicode FormatType0[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage0[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex0[] = {18};
+static const sal_Unicode FormatCode0[] = {0x44, 0x44, 0x2f, 0x4d, 0x4d, 0x2f, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName0[] = {0x0};
+static const sal_Unicode FormatKey1[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement1[] = {0};
+static const sal_Unicode FormatType1[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage1[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex1[] = {28};
+static const sal_Unicode FormatCode1[] = {0x4e, 0x4e, 0x20, 0x44, 0x44, 0x2f, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName1[] = {0x0};
+static const sal_Unicode FormatKey2[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement2[] = {0};
+static const sal_Unicode FormatType2[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage2[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex2[] = {34};
+static const sal_Unicode FormatCode2[] = {0x4d, 0x4d, 0x2f, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName2[] = {0x0};
+static const sal_Unicode FormatKey3[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement3[] = {0};
+static const sal_Unicode FormatType3[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage3[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex3[] = {35};
+static const sal_Unicode FormatCode3[] = {0x44, 0x44, 0x2f, 0x4d, 0x4d, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName3[] = {0x0};
+static const sal_Unicode FormatKey4[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement4[] = {0};
+static const sal_Unicode FormatType4[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage4[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex4[] = {36};
+static const sal_Unicode FormatCode4[] = {0x4d, 0x4d, 0x4d, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName4[] = {0x0};
+static const sal_Unicode FormatKey5[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement5[] = {0};
+static const sal_Unicode FormatType5[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage5[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex5[] = {37};
+static const sal_Unicode FormatCode5[] = {0x51, 0x51, 0x20, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName5[] = {0x0};
+static const sal_Unicode FormatKey6[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x37, 0x0};
+static const sal_Unicode defaultFormatElement6[] = {0};
+static const sal_Unicode FormatType6[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage6[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex6[] = {21};
+static const sal_Unicode FormatCode6[] = {0x44, 0x44, 0x2f, 0x4d, 0x4d, 0x2f, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName6[] = {0x0};
+static const sal_Unicode FormatKey7[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x38, 0x0};
+static const sal_Unicode defaultFormatElement7[] = {1};
+static const sal_Unicode FormatType7[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage7[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex7[] = {20};
+static const sal_Unicode FormatCode7[] = {0x44, 0x44, 0x2f, 0x4d, 0x4d, 0x2f, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName7[] = {0x0};
+static const sal_Unicode FormatKey8[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x39, 0x0};
+static const sal_Unicode defaultFormatElement8[] = {1};
+static const sal_Unicode FormatType8[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage8[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex8[] = {19};
+static const sal_Unicode FormatCode8[] = {0x44, 0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName8[] = {0x0};
+static const sal_Unicode FormatKey9[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x30, 0x0};
+static const sal_Unicode defaultFormatElement9[] = {0};
+static const sal_Unicode FormatType9[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage9[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex9[] = {22};
+static const sal_Unicode FormatCode9[] = {0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName9[] = {0x0};
+static const sal_Unicode FormatKey10[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement10[] = {0};
+static const sal_Unicode FormatType10[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage10[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex10[] = {23};
+static const sal_Unicode FormatCode10[] = {0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName10[] = {0x0};
+static const sal_Unicode FormatKey11[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement11[] = {0};
+static const sal_Unicode FormatType11[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage11[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex11[] = {25};
+static const sal_Unicode FormatCode11[] = {0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName11[] = {0x0};
+static const sal_Unicode FormatKey12[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement12[] = {0};
+static const sal_Unicode FormatType12[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage12[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex12[] = {27};
+static const sal_Unicode FormatCode12[] = {0x4e, 0x4e, 0x20, 0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName12[] = {0x0};
+static const sal_Unicode FormatKey13[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement13[] = {0};
+static const sal_Unicode FormatType13[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage13[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex13[] = {29};
+static const sal_Unicode FormatCode13[] = {0x4e, 0x4e, 0x20, 0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName13[] = {0x0};
+static const sal_Unicode FormatKey14[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement14[] = {0};
+static const sal_Unicode FormatType14[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage14[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex14[] = {30};
+static const sal_Unicode FormatCode14[] = {0x4e, 0x4e, 0x4e, 0x4e, 0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName14[] = {0x0};
+static const sal_Unicode FormatKey15[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement15[] = {0};
+static const sal_Unicode FormatType15[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage15[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex15[] = {24};
+static const sal_Unicode FormatCode15[] = {0x44, 0x2e, 0x20, 0x4d, 0x4d, 0x4d, 0x2e, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName15[] = {0x0};
+static const sal_Unicode FormatKey16[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x37, 0x0};
+static const sal_Unicode defaultFormatElement16[] = {0};
+static const sal_Unicode FormatType16[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage16[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex16[] = {26};
+static const sal_Unicode FormatCode16[] = {0x44, 0x2e, 0x20, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName16[] = {0x0};
+static const sal_Unicode FormatKey17[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x38, 0x0};
+static const sal_Unicode defaultFormatElement17[] = {0};
+static const sal_Unicode FormatType17[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage17[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex17[] = {31};
+static const sal_Unicode FormatCode17[] = {0x4d, 0x4d, 0x2d, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName17[] = {0x0};
+static const sal_Unicode FormatKey18[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x39, 0x0};
+static const sal_Unicode defaultFormatElement18[] = {0};
+static const sal_Unicode FormatType18[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage18[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex18[] = {32};
+static const sal_Unicode FormatCode18[] = {0x59, 0x59, 0x2d, 0x4d, 0x4d, 0x2d, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName18[] = {0x0};
+static const sal_Unicode FormatKey19[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x30, 0x0};
+static const sal_Unicode defaultFormatElement19[] = {0};
+static const sal_Unicode FormatType19[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage19[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex19[] = {33};
+static const sal_Unicode FormatCode19[] = {0x59, 0x59, 0x59, 0x59, 0x2d, 0x4d, 0x4d, 0x2d, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName19[] = {0x49, 0x53, 0x4f, 0x20, 0x38, 0x36, 0x30, 0x31, 0x20, 0x28, 0x45, 0x4e, 0x20, 0x32, 0x38, 0x36, 0x30, 0x31, 0x29, 0x0};
+static const sal_Unicode FormatKey20[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement20[] = {0};
+static const sal_Unicode FormatType20[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage20[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex20[] = {38};
+static const sal_Unicode FormatCode20[] = {0x57, 0x57, 0x0};
+static const sal_Unicode FormatDefaultName20[] = {0x0};
+static const sal_Unicode FormatKey21[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement21[] = {1};
+static const sal_Unicode FormatType21[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage21[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex21[] = {39};
+static const sal_Unicode FormatCode21[] = {0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName21[] = {0x0};
+static const sal_Unicode FormatKey22[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement22[] = {1};
+static const sal_Unicode FormatType22[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage22[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex22[] = {40};
+static const sal_Unicode FormatCode22[] = {0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x0};
+static const sal_Unicode FormatDefaultName22[] = {0x0};
+static const sal_Unicode FormatKey23[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement23[] = {0};
+static const sal_Unicode FormatType23[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage23[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex23[] = {41};
+static const sal_Unicode FormatCode23[] = {0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x20, 0x41, 0x4d, 0x2f, 0x50, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName23[] = {0x0};
+static const sal_Unicode FormatKey24[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement24[] = {0};
+static const sal_Unicode FormatType24[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage24[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex24[] = {42};
+static const sal_Unicode FormatCode24[] = {0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x20, 0x41, 0x4d, 0x2f, 0x50, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName24[] = {0x0};
+static const sal_Unicode FormatKey25[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement25[] = {0};
+static const sal_Unicode FormatType25[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage25[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex25[] = {43};
+static const sal_Unicode FormatCode25[] = {0x5b, 0x48, 0x48, 0x5d, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x0};
+static const sal_Unicode FormatDefaultName25[] = {0x0};
+static const sal_Unicode FormatKey26[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement26[] = {0};
+static const sal_Unicode FormatType26[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage26[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex26[] = {44};
+static const sal_Unicode FormatCode26[] = {0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName26[] = {0x0};
+static const sal_Unicode FormatKey27[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x37, 0x0};
+static const sal_Unicode defaultFormatElement27[] = {0};
+static const sal_Unicode FormatType27[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage27[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex27[] = {45};
+static const sal_Unicode FormatCode27[] = {0x5b, 0x48, 0x48, 0x5d, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName27[] = {0x0};
+static const sal_Unicode FormatKey28[] = {0x44, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement28[] = {1};
+static const sal_Unicode FormatType28[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage28[] = {0x44, 0x41, 0x54, 0x45, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex28[] = {46};
+static const sal_Unicode FormatCode28[] = {0x44, 0x44, 0x2f, 0x4d, 0x4d, 0x2f, 0x59, 0x59, 0x20, 0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName28[] = {0x0};
+static const sal_Unicode FormatKey29[] = {0x44, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement29[] = {0};
+static const sal_Unicode FormatType29[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage29[] = {0x44, 0x41, 0x54, 0x45, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex29[] = {47};
+static const sal_Unicode FormatCode29[] = {0x44, 0x44, 0x2f, 0x4d, 0x4d, 0x2f, 0x59, 0x59, 0x59, 0x59, 0x20, 0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x0};
+static const sal_Unicode FormatDefaultName29[] = {0x0};
+static const sal_Unicode FormatKey30[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement30[] = {1};
+static const sal_Unicode FormatType30[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage30[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex30[] = {0};
+static const sal_Unicode FormatCode30[] = {0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x0};
+static const sal_Unicode FormatDefaultName30[] = {0x0};
+static const sal_Unicode FormatKey31[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement31[] = {1};
+static const sal_Unicode FormatType31[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage31[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex31[] = {1};
+static const sal_Unicode FormatCode31[] = {0x30, 0x0};
+static const sal_Unicode FormatDefaultName31[] = {0x0};
+static const sal_Unicode FormatKey32[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement32[] = {0};
+static const sal_Unicode FormatType32[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage32[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex32[] = {2};
+static const sal_Unicode FormatCode32[] = {0x30, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName32[] = {0x0};
+static const sal_Unicode FormatKey33[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement33[] = {0};
+static const sal_Unicode FormatType33[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage33[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex33[] = {3};
+static const sal_Unicode FormatCode33[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName33[] = {0x0};
+static const sal_Unicode FormatKey34[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement34[] = {0};
+static const sal_Unicode FormatType34[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage34[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex34[] = {4};
+static const sal_Unicode FormatCode34[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName34[] = {0x0};
+static const sal_Unicode FormatKey35[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement35[] = {0};
+static const sal_Unicode FormatType35[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage35[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex35[] = {5};
+static const sal_Unicode FormatCode35[] = {0x23, 0x2c, 0x23, 0x23, 0x23, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName35[] = {0x0};
+static const sal_Unicode FormatKey36[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x37, 0x0};
+static const sal_Unicode defaultFormatElement36[] = {0};
+static const sal_Unicode FormatType36[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage36[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex36[] = {70};
+static const sal_Unicode FormatCode36[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x5f, 0x29, 0x3b, 0x28, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x29, 0x0};
+static const sal_Unicode FormatDefaultName36[] = {0x0};
+static const sal_Unicode FormatKey37[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x38, 0x0};
+static const sal_Unicode defaultFormatElement37[] = {0};
+static const sal_Unicode FormatType37[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage37[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex37[] = {71};
+static const sal_Unicode FormatCode37[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x5f, 0x29, 0x3b, 0x28, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x29, 0x0};
+static const sal_Unicode FormatDefaultName37[] = {0x0};
+static const sal_Unicode FormatKey38[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement38[] = {1};
+static const sal_Unicode FormatType38[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage38[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex38[] = {12};
+static const sal_Unicode FormatCode38[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x3b, 0x2d, 0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName38[] = {0x0};
+static const sal_Unicode FormatKey39[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement39[] = {0};
+static const sal_Unicode FormatType39[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage39[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex39[] = {13};
+static const sal_Unicode FormatCode39[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x3b, 0x2d, 0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName39[] = {0x0};
+static const sal_Unicode FormatKey40[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement40[] = {0};
+static const sal_Unicode FormatType40[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage40[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex40[] = {14};
+static const sal_Unicode FormatCode40[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x3b, 0x5b, 0x52, 0x45, 0x44, 0x5d, 0x2d, 0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName40[] = {0x0};
+static const sal_Unicode FormatKey41[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement41[] = {1};
+static const sal_Unicode FormatType41[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage41[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex41[] = {15};
+static const sal_Unicode FormatCode41[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x3b, 0x5b, 0x52, 0x45, 0x44, 0x5d, 0x2d, 0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName41[] = {0x0};
+static const sal_Unicode FormatKey42[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement42[] = {0};
+static const sal_Unicode FormatType42[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage42[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex42[] = {16};
+static const sal_Unicode FormatCode42[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x20, 0x43, 0x43, 0x43, 0x0};
+static const sal_Unicode FormatDefaultName42[] = {0x0};
+static const sal_Unicode FormatKey43[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement43[] = {0};
+static const sal_Unicode FormatType43[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage43[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex43[] = {17};
+static const sal_Unicode FormatCode43[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x2d, 0x2d, 0x3b, 0x5b, 0x52, 0x45, 0x44, 0x5d, 0x2d, 0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x2d, 0x2d, 0x0};
+static const sal_Unicode FormatDefaultName43[] = {0x0};
+static const sal_Unicode FormatKey44[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x37, 0x0};
+static const sal_Unicode defaultFormatElement44[] = {0};
+static const sal_Unicode FormatType44[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage44[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex44[] = {72};
+static const sal_Unicode FormatCode44[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x2a, 0x20, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x3b, 0x2d, 0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x2a, 0x20, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName44[] = {0x0};
+static const sal_Unicode FormatKey45[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x38, 0x0};
+static const sal_Unicode defaultFormatElement45[] = {0};
+static const sal_Unicode FormatType45[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage45[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex45[] = {73};
+static const sal_Unicode FormatCode45[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x2a, 0x20, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x3b, 0x2d, 0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x2a, 0x20, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName45[] = {0x0};
+static const sal_Unicode FormatKey46[] = {0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement46[] = {1};
+static const sal_Unicode FormatType46[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage46[] = {0x50, 0x45, 0x52, 0x43, 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex46[] = {8};
+static const sal_Unicode FormatCode46[] = {0x30, 0x25, 0x0};
+static const sal_Unicode FormatDefaultName46[] = {0x0};
+static const sal_Unicode FormatKey47[] = {0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement47[] = {1};
+static const sal_Unicode FormatType47[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage47[] = {0x50, 0x45, 0x52, 0x43, 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex47[] = {9};
+static const sal_Unicode FormatCode47[] = {0x30, 0x2e, 0x30, 0x30, 0x25, 0x0};
+static const sal_Unicode FormatDefaultName47[] = {0x0};
+static const sal_Unicode FormatKey48[] = {0x53, 0x63, 0x69, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x63, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement48[] = {0};
+static const sal_Unicode FormatType48[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage48[] = {0x53, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, 0x43, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex48[] = {6};
+static const sal_Unicode FormatCode48[] = {0x30, 0x2e, 0x30, 0x30, 0x45, 0x2b, 0x30, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName48[] = {0x0};
+static const sal_Unicode FormatKey49[] = {0x53, 0x63, 0x69, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x63, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement49[] = {1};
+static const sal_Unicode FormatType49[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage49[] = {0x53, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, 0x43, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex49[] = {7};
+static const sal_Unicode FormatCode49[] = {0x30, 0x2e, 0x30, 0x30, 0x45, 0x2b, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName49[] = {0x0};
+static const sal_Unicode FormatKey50[] = {0x53, 0x63, 0x69, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x63, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement50[] = {0};
+static const sal_Unicode FormatType50[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage50[] = {0x53, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, 0x43, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex50[] = {78};
+static const sal_Unicode FormatCode50[] = {0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x45, 0x2b, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName50[] = {0x0};
+
+static const sal_Int16 FormatElementsCount0 = 51;
+static const sal_Unicode* FormatElementsArray0[] = {
+ FormatCode0,
+ FormatDefaultName0,
+ FormatKey0,
+ FormatType0,
+ FormatUsage0,
+ Formatindex0,
+ defaultFormatElement0,
+ FormatCode1,
+ FormatDefaultName1,
+ FormatKey1,
+ FormatType1,
+ FormatUsage1,
+ Formatindex1,
+ defaultFormatElement1,
+ FormatCode2,
+ FormatDefaultName2,
+ FormatKey2,
+ FormatType2,
+ FormatUsage2,
+ Formatindex2,
+ defaultFormatElement2,
+ FormatCode3,
+ FormatDefaultName3,
+ FormatKey3,
+ FormatType3,
+ FormatUsage3,
+ Formatindex3,
+ defaultFormatElement3,
+ FormatCode4,
+ FormatDefaultName4,
+ FormatKey4,
+ FormatType4,
+ FormatUsage4,
+ Formatindex4,
+ defaultFormatElement4,
+ FormatCode5,
+ FormatDefaultName5,
+ FormatKey5,
+ FormatType5,
+ FormatUsage5,
+ Formatindex5,
+ defaultFormatElement5,
+ FormatCode6,
+ FormatDefaultName6,
+ FormatKey6,
+ FormatType6,
+ FormatUsage6,
+ Formatindex6,
+ defaultFormatElement6,
+ FormatCode7,
+ FormatDefaultName7,
+ FormatKey7,
+ FormatType7,
+ FormatUsage7,
+ Formatindex7,
+ defaultFormatElement7,
+ FormatCode8,
+ FormatDefaultName8,
+ FormatKey8,
+ FormatType8,
+ FormatUsage8,
+ Formatindex8,
+ defaultFormatElement8,
+ FormatCode9,
+ FormatDefaultName9,
+ FormatKey9,
+ FormatType9,
+ FormatUsage9,
+ Formatindex9,
+ defaultFormatElement9,
+ FormatCode10,
+ FormatDefaultName10,
+ FormatKey10,
+ FormatType10,
+ FormatUsage10,
+ Formatindex10,
+ defaultFormatElement10,
+ FormatCode11,
+ FormatDefaultName11,
+ FormatKey11,
+ FormatType11,
+ FormatUsage11,
+ Formatindex11,
+ defaultFormatElement11,
+ FormatCode12,
+ FormatDefaultName12,
+ FormatKey12,
+ FormatType12,
+ FormatUsage12,
+ Formatindex12,
+ defaultFormatElement12,
+ FormatCode13,
+ FormatDefaultName13,
+ FormatKey13,
+ FormatType13,
+ FormatUsage13,
+ Formatindex13,
+ defaultFormatElement13,
+ FormatCode14,
+ FormatDefaultName14,
+ FormatKey14,
+ FormatType14,
+ FormatUsage14,
+ Formatindex14,
+ defaultFormatElement14,
+ FormatCode15,
+ FormatDefaultName15,
+ FormatKey15,
+ FormatType15,
+ FormatUsage15,
+ Formatindex15,
+ defaultFormatElement15,
+ FormatCode16,
+ FormatDefaultName16,
+ FormatKey16,
+ FormatType16,
+ FormatUsage16,
+ Formatindex16,
+ defaultFormatElement16,
+ FormatCode17,
+ FormatDefaultName17,
+ FormatKey17,
+ FormatType17,
+ FormatUsage17,
+ Formatindex17,
+ defaultFormatElement17,
+ FormatCode18,
+ FormatDefaultName18,
+ FormatKey18,
+ FormatType18,
+ FormatUsage18,
+ Formatindex18,
+ defaultFormatElement18,
+ FormatCode19,
+ FormatDefaultName19,
+ FormatKey19,
+ FormatType19,
+ FormatUsage19,
+ Formatindex19,
+ defaultFormatElement19,
+ FormatCode20,
+ FormatDefaultName20,
+ FormatKey20,
+ FormatType20,
+ FormatUsage20,
+ Formatindex20,
+ defaultFormatElement20,
+ FormatCode21,
+ FormatDefaultName21,
+ FormatKey21,
+ FormatType21,
+ FormatUsage21,
+ Formatindex21,
+ defaultFormatElement21,
+ FormatCode22,
+ FormatDefaultName22,
+ FormatKey22,
+ FormatType22,
+ FormatUsage22,
+ Formatindex22,
+ defaultFormatElement22,
+ FormatCode23,
+ FormatDefaultName23,
+ FormatKey23,
+ FormatType23,
+ FormatUsage23,
+ Formatindex23,
+ defaultFormatElement23,
+ FormatCode24,
+ FormatDefaultName24,
+ FormatKey24,
+ FormatType24,
+ FormatUsage24,
+ Formatindex24,
+ defaultFormatElement24,
+ FormatCode25,
+ FormatDefaultName25,
+ FormatKey25,
+ FormatType25,
+ FormatUsage25,
+ Formatindex25,
+ defaultFormatElement25,
+ FormatCode26,
+ FormatDefaultName26,
+ FormatKey26,
+ FormatType26,
+ FormatUsage26,
+ Formatindex26,
+ defaultFormatElement26,
+ FormatCode27,
+ FormatDefaultName27,
+ FormatKey27,
+ FormatType27,
+ FormatUsage27,
+ Formatindex27,
+ defaultFormatElement27,
+ FormatCode28,
+ FormatDefaultName28,
+ FormatKey28,
+ FormatType28,
+ FormatUsage28,
+ Formatindex28,
+ defaultFormatElement28,
+ FormatCode29,
+ FormatDefaultName29,
+ FormatKey29,
+ FormatType29,
+ FormatUsage29,
+ Formatindex29,
+ defaultFormatElement29,
+ FormatCode30,
+ FormatDefaultName30,
+ FormatKey30,
+ FormatType30,
+ FormatUsage30,
+ Formatindex30,
+ defaultFormatElement30,
+ FormatCode31,
+ FormatDefaultName31,
+ FormatKey31,
+ FormatType31,
+ FormatUsage31,
+ Formatindex31,
+ defaultFormatElement31,
+ FormatCode32,
+ FormatDefaultName32,
+ FormatKey32,
+ FormatType32,
+ FormatUsage32,
+ Formatindex32,
+ defaultFormatElement32,
+ FormatCode33,
+ FormatDefaultName33,
+ FormatKey33,
+ FormatType33,
+ FormatUsage33,
+ Formatindex33,
+ defaultFormatElement33,
+ FormatCode34,
+ FormatDefaultName34,
+ FormatKey34,
+ FormatType34,
+ FormatUsage34,
+ Formatindex34,
+ defaultFormatElement34,
+ FormatCode35,
+ FormatDefaultName35,
+ FormatKey35,
+ FormatType35,
+ FormatUsage35,
+ Formatindex35,
+ defaultFormatElement35,
+ FormatCode36,
+ FormatDefaultName36,
+ FormatKey36,
+ FormatType36,
+ FormatUsage36,
+ Formatindex36,
+ defaultFormatElement36,
+ FormatCode37,
+ FormatDefaultName37,
+ FormatKey37,
+ FormatType37,
+ FormatUsage37,
+ Formatindex37,
+ defaultFormatElement37,
+ FormatCode38,
+ FormatDefaultName38,
+ FormatKey38,
+ FormatType38,
+ FormatUsage38,
+ Formatindex38,
+ defaultFormatElement38,
+ FormatCode39,
+ FormatDefaultName39,
+ FormatKey39,
+ FormatType39,
+ FormatUsage39,
+ Formatindex39,
+ defaultFormatElement39,
+ FormatCode40,
+ FormatDefaultName40,
+ FormatKey40,
+ FormatType40,
+ FormatUsage40,
+ Formatindex40,
+ defaultFormatElement40,
+ FormatCode41,
+ FormatDefaultName41,
+ FormatKey41,
+ FormatType41,
+ FormatUsage41,
+ Formatindex41,
+ defaultFormatElement41,
+ FormatCode42,
+ FormatDefaultName42,
+ FormatKey42,
+ FormatType42,
+ FormatUsage42,
+ Formatindex42,
+ defaultFormatElement42,
+ FormatCode43,
+ FormatDefaultName43,
+ FormatKey43,
+ FormatType43,
+ FormatUsage43,
+ Formatindex43,
+ defaultFormatElement43,
+ FormatCode44,
+ FormatDefaultName44,
+ FormatKey44,
+ FormatType44,
+ FormatUsage44,
+ Formatindex44,
+ defaultFormatElement44,
+ FormatCode45,
+ FormatDefaultName45,
+ FormatKey45,
+ FormatType45,
+ FormatUsage45,
+ Formatindex45,
+ defaultFormatElement45,
+ FormatCode46,
+ FormatDefaultName46,
+ FormatKey46,
+ FormatType46,
+ FormatUsage46,
+ Formatindex46,
+ defaultFormatElement46,
+ FormatCode47,
+ FormatDefaultName47,
+ FormatKey47,
+ FormatType47,
+ FormatUsage47,
+ Formatindex47,
+ defaultFormatElement47,
+ FormatCode48,
+ FormatDefaultName48,
+ FormatKey48,
+ FormatType48,
+ FormatUsage48,
+ Formatindex48,
+ defaultFormatElement48,
+ FormatCode49,
+ FormatDefaultName49,
+ FormatKey49,
+ FormatType49,
+ FormatUsage49,
+ Formatindex49,
+ defaultFormatElement49,
+ FormatCode50,
+ FormatDefaultName50,
+ FormatKey50,
+ FormatType50,
+ FormatUsage50,
+ Formatindex50,
+ defaultFormatElement50,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode const * const * SAL_CALL getAllFormats0_en_GB(sal_Int16& count, const sal_Unicode*& from, const sal_Unicode*& to)
+{
+ count = FormatElementsCount0;
+ from = replaceFrom0;
+ to = replaceTo0;
+ return (sal_Unicode**)FormatElementsArray0;
+}
+static const sal_Unicode DateAcceptancePattern0[] = {0x44, 0x2f, 0x4d, 0x2f, 0x59, 0x0};
+static const sal_Unicode DateAcceptancePattern1[] = {0x44, 0x2f, 0x4d, 0x0};
+static const sal_Unicode DateAcceptancePattern2[] = {0x44, 0x2d, 0x4d, 0x0};
+static const sal_Int16 DateAcceptancePatternsCount = 3;
+static const sal_Unicode* DateAcceptancePatternsArray[] = {
+ DateAcceptancePattern0,
+ DateAcceptancePattern1,
+ DateAcceptancePattern2,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getDateAcceptancePatterns_en_GB(sal_Int16& count)
+{
+ count = DateAcceptancePatternsCount;
+ return (sal_Unicode**)DateAcceptancePatternsArray;
+}
+extern sal_Unicode ** SAL_CALL getCollatorImplementation_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollatorImplementation_en_GB(sal_Int16& count)
+{
+ return getCollatorImplementation_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getCollationOptions_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollationOptions_en_GB(sal_Int16& count)
+{
+ return getCollationOptions_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getSearchOptions_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getSearchOptions_en_GB(sal_Int16& count)
+{
+ return getSearchOptions_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getIndexAlgorithm_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getIndexAlgorithm_en_GB(sal_Int16& count)
+{
+ return getIndexAlgorithm_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getUnicodeScripts_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getUnicodeScripts_en_GB(sal_Int16& count)
+{
+ return getUnicodeScripts_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getFollowPageWords_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getFollowPageWords_en_GB(sal_Int16& count)
+{
+ return getFollowPageWords_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getAllCalendars_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCalendars_en_GB(sal_Int16& count)
+{
+ return getAllCalendars_en_US(count);
+}
+static const sal_Unicode defaultCurrency0[] = {1};
+static const sal_Unicode defaultCurrencyUsedInCompatibleFormatCodes0[] = {1};
+static const sal_Unicode defaultCurrencyLegacyOnly0[] = {0};
+static const sal_Unicode currencyID0[] = {0x47, 0x42, 0x50, 0x0};
+static const sal_Unicode currencySymbol0[] = {0xa3, 0x0};
+static const sal_Unicode bankSymbol0[] = {0x47, 0x42, 0x50, 0x0};
+static const sal_Unicode currencyName0[] = {0x50, 0x6f, 0x75, 0x6e, 0x64, 0x20, 0x53, 0x74, 0x65, 0x72, 0x6c, 0x69, 0x6e, 0x67, 0x0};
+static const sal_Unicode currencyDecimalPlaces0[] = {2};
+
+static const sal_Int16 currencyCount = 1;
+
+static const sal_Unicode* currencies[] = {
+ currencyID0,
+ currencySymbol0,
+ bankSymbol0,
+ currencyName0,
+ defaultCurrency0,
+ defaultCurrencyUsedInCompatibleFormatCodes0,
+ currencyDecimalPlaces0,
+ defaultCurrencyLegacyOnly0,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCurrencies_en_GB(sal_Int16& count)
+{
+ count = currencyCount;
+ return (sal_Unicode**)currencies;
+}
+extern sal_Unicode ** SAL_CALL getTransliterations_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getTransliterations_en_GB(sal_Int16& count)
+{
+ return getTransliterations_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getForbiddenCharacters_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getForbiddenCharacters_en_GB(sal_Int16& count)
+{
+ return getForbiddenCharacters_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getBreakIteratorRules_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getBreakIteratorRules_en_GB(sal_Int16& count)
+{
+ return getBreakIteratorRules_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getReservedWords_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getReservedWords_en_GB(sal_Int16& count)
+{
+ return getReservedWords_en_US(count);
+}
+// ---> ContinuousNumbering
+extern const sal_Unicode *** SAL_CALL getContinuousNumberingLevels_en_US(sal_Int16& nStyles, sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode *** SAL_CALL getContinuousNumberingLevels_en_GB(sal_Int16& nStyles, sal_Int16& nAttributes)
+{
+ return getContinuousNumberingLevels_en_US(nStyles, nAttributes);
+}
+// ---> OutlineNumbering
+extern const sal_Unicode **** SAL_CALL getOutlineNumberingLevels_en_US(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode **** SAL_CALL getOutlineNumberingLevels_en_GB(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes)
+{
+ return getOutlineNumberingLevels_en_US(nStyles, nLevels, nAttributes);
+}
+} // extern "C"
+
diff --git a/vcl/workben/localestub/localedata_en_GH.cxx b/vcl/workben/localestub/localedata_en_GH.cxx
new file mode 100644
index 000000000..237df2ca8
--- /dev/null
+++ b/vcl/workben/localestub/localedata_en_GH.cxx
@@ -0,0 +1,1526 @@
+#include <sal/types.h>
+
+
+#include <stdio.h>
+
+extern "C" {
+
+static const sal_Unicode langID[] = {0x65, 0x6e, 0x0};
+static const sal_Unicode langDefaultName[] = {0x45, 0x6e, 0x67, 0x6c, 0x69, 0x73, 0x68, 0x0};
+static const sal_Unicode countryID[] = {0x47, 0x48, 0x0};
+static const sal_Unicode countryDefaultName[] = {0x47, 0x68, 0x61, 0x6e, 0x61, 0x0};
+static const sal_Unicode Variant[] = {0x0};
+
+static const sal_Unicode* LCInfoArray[] = {
+ langID,
+ langDefaultName,
+ countryID,
+ countryDefaultName,
+ Variant
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLCInfo_en_GH(sal_Int16& count)
+{
+ count = 0;
+ return (sal_Unicode**)LCInfoArray;
+}
+
+
+static const sal_Unicode LC_CTYPE_Unoid[] = {0x67, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x0};
+static const sal_Unicode dateSeparator[] = {0x2f, 0x0};
+static const sal_Unicode thousandSeparator[] = {0x2c, 0x0};
+static const sal_Unicode decimalSeparator[] = {0x2e, 0x0};
+static const sal_Unicode timeSeparator[] = {0x3a, 0x0};
+static const sal_Unicode time100SecSeparator[] = {0x2e, 0x0};
+static const sal_Unicode listSeparator[] = {0x3b, 0x0};
+static const sal_Unicode LongDateDayOfWeekSeparator[] = {0x2c, 0x20, 0x0};
+static const sal_Unicode LongDateDaySeparator[] = {0x2c, 0x20, 0x0};
+static const sal_Unicode LongDateMonthSeparator[] = {0x20, 0x0};
+static const sal_Unicode LongDateYearSeparator[] = {0x20, 0x0};
+static const sal_Unicode quotationStart[] = {0x27, 0x0};
+static const sal_Unicode quotationEnd[] = {0x27, 0x0};
+static const sal_Unicode doubleQuotationStart[] = {0x22, 0x0};
+static const sal_Unicode doubleQuotationEnd[] = {0x22, 0x0};
+static const sal_Unicode timeAM[] = {0x61, 0x2e, 0x6d, 0x2e, 0x0};
+static const sal_Unicode timePM[] = {0x70, 0x2e, 0x6d, 0x2e, 0x0};
+static const sal_Unicode measurementSystem[] = {0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x0};
+
+static const sal_Unicode* LCType[] = {
+ LC_CTYPE_Unoid,
+ dateSeparator,
+ thousandSeparator,
+ decimalSeparator,
+ timeSeparator,
+ time100SecSeparator,
+ listSeparator,
+ quotationStart,
+ quotationEnd,
+ doubleQuotationStart,
+ doubleQuotationEnd,
+ timeAM,
+ timePM,
+ measurementSystem,
+ LongDateDayOfWeekSeparator,
+ LongDateDaySeparator,
+ LongDateMonthSeparator,
+ LongDateYearSeparator
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLocaleItem_en_GH(sal_Int16& count)
+{
+ count = 0;
+ return (sal_Unicode**)LCType;
+}
+static const sal_Unicode replaceFrom0[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x0};
+static const sal_Unicode replaceTo0[] = {0x5b, 0x24, 0x20b5, 0x2d, 0x38, 0x34, 0x30, 0x39, 0x5d, 0x0};
+static const sal_Unicode FormatKey0[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement0[] = {1};
+static const sal_Unicode FormatType0[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage0[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex0[] = {0};
+static const sal_Unicode FormatCode0[] = {0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x0};
+static const sal_Unicode FormatDefaultName0[] = {0x0};
+static const sal_Unicode FormatKey1[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement1[] = {1};
+static const sal_Unicode FormatType1[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage1[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex1[] = {1};
+static const sal_Unicode FormatCode1[] = {0x30, 0x0};
+static const sal_Unicode FormatDefaultName1[] = {0x0};
+static const sal_Unicode FormatKey2[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement2[] = {0};
+static const sal_Unicode FormatType2[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage2[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex2[] = {2};
+static const sal_Unicode FormatCode2[] = {0x30, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName2[] = {0x0};
+static const sal_Unicode FormatKey3[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement3[] = {0};
+static const sal_Unicode FormatType3[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage3[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex3[] = {3};
+static const sal_Unicode FormatCode3[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName3[] = {0x0};
+static const sal_Unicode FormatKey4[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement4[] = {0};
+static const sal_Unicode FormatType4[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage4[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex4[] = {4};
+static const sal_Unicode FormatCode4[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName4[] = {0x0};
+static const sal_Unicode FormatKey5[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement5[] = {0};
+static const sal_Unicode FormatType5[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage5[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex5[] = {5};
+static const sal_Unicode FormatCode5[] = {0x23, 0x2c, 0x23, 0x23, 0x23, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName5[] = {0x0};
+static const sal_Unicode FormatKey6[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x37, 0x0};
+static const sal_Unicode defaultFormatElement6[] = {0};
+static const sal_Unicode FormatType6[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage6[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex6[] = {70};
+static const sal_Unicode FormatCode6[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x5f, 0x29, 0x3b, 0x28, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x29, 0x0};
+static const sal_Unicode FormatDefaultName6[] = {0x0};
+static const sal_Unicode FormatKey7[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x38, 0x0};
+static const sal_Unicode defaultFormatElement7[] = {0};
+static const sal_Unicode FormatType7[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage7[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex7[] = {71};
+static const sal_Unicode FormatCode7[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x5f, 0x29, 0x3b, 0x28, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x29, 0x0};
+static const sal_Unicode FormatDefaultName7[] = {0x0};
+static const sal_Unicode FormatKey8[] = {0x53, 0x63, 0x69, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x63, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement8[] = {0};
+static const sal_Unicode FormatType8[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage8[] = {0x53, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, 0x43, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex8[] = {6};
+static const sal_Unicode FormatCode8[] = {0x30, 0x2e, 0x30, 0x30, 0x45, 0x2b, 0x30, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName8[] = {0x0};
+static const sal_Unicode FormatKey9[] = {0x53, 0x63, 0x69, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x63, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement9[] = {1};
+static const sal_Unicode FormatType9[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage9[] = {0x53, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, 0x43, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex9[] = {7};
+static const sal_Unicode FormatCode9[] = {0x30, 0x2e, 0x30, 0x30, 0x45, 0x2b, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName9[] = {0x0};
+static const sal_Unicode FormatKey10[] = {0x53, 0x63, 0x69, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x63, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement10[] = {0};
+static const sal_Unicode FormatType10[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage10[] = {0x53, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, 0x43, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex10[] = {78};
+static const sal_Unicode FormatCode10[] = {0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x45, 0x2b, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName10[] = {0x0};
+static const sal_Unicode FormatKey11[] = {0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement11[] = {1};
+static const sal_Unicode FormatType11[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage11[] = {0x50, 0x45, 0x52, 0x43, 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex11[] = {8};
+static const sal_Unicode FormatCode11[] = {0x30, 0x25, 0x0};
+static const sal_Unicode FormatDefaultName11[] = {0x0};
+static const sal_Unicode FormatKey12[] = {0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement12[] = {1};
+static const sal_Unicode FormatType12[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage12[] = {0x50, 0x45, 0x52, 0x43, 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex12[] = {9};
+static const sal_Unicode FormatCode12[] = {0x30, 0x2e, 0x30, 0x30, 0x25, 0x0};
+static const sal_Unicode FormatDefaultName12[] = {0x0};
+static const sal_Unicode FormatKey13[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement13[] = {1};
+static const sal_Unicode FormatType13[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage13[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex13[] = {12};
+static const sal_Unicode FormatCode13[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x3b, 0x2d, 0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName13[] = {0x0};
+static const sal_Unicode FormatKey14[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement14[] = {0};
+static const sal_Unicode FormatType14[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage14[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex14[] = {13};
+static const sal_Unicode FormatCode14[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x3b, 0x2d, 0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName14[] = {0x0};
+static const sal_Unicode FormatKey15[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement15[] = {0};
+static const sal_Unicode FormatType15[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage15[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex15[] = {14};
+static const sal_Unicode FormatCode15[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x3b, 0x5b, 0x52, 0x45, 0x44, 0x5d, 0x2d, 0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName15[] = {0x0};
+static const sal_Unicode FormatKey16[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement16[] = {1};
+static const sal_Unicode FormatType16[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage16[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex16[] = {15};
+static const sal_Unicode FormatCode16[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x3b, 0x5b, 0x52, 0x45, 0x44, 0x5d, 0x2d, 0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName16[] = {0x0};
+static const sal_Unicode FormatKey17[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement17[] = {0};
+static const sal_Unicode FormatType17[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage17[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex17[] = {16};
+static const sal_Unicode FormatCode17[] = {0x43, 0x43, 0x43, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName17[] = {0x0};
+static const sal_Unicode FormatKey18[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement18[] = {0};
+static const sal_Unicode FormatType18[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage18[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex18[] = {17};
+static const sal_Unicode FormatCode18[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x2d, 0x2d, 0x3b, 0x5b, 0x52, 0x45, 0x44, 0x5d, 0x2d, 0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x2d, 0x2d, 0x0};
+static const sal_Unicode FormatDefaultName18[] = {0x0};
+static const sal_Unicode FormatKey19[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement19[] = {1};
+static const sal_Unicode FormatType19[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage19[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex19[] = {18};
+static const sal_Unicode FormatCode19[] = {0x59, 0x59, 0x2f, 0x4d, 0x4d, 0x2f, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName19[] = {0x0};
+static const sal_Unicode FormatKey20[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x39, 0x0};
+static const sal_Unicode defaultFormatElement20[] = {1};
+static const sal_Unicode FormatType20[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage20[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex20[] = {19};
+static const sal_Unicode FormatCode20[] = {0x44, 0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName20[] = {0x0};
+static const sal_Unicode FormatKey21[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x38, 0x0};
+static const sal_Unicode defaultFormatElement21[] = {1};
+static const sal_Unicode FormatType21[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage21[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex21[] = {20};
+static const sal_Unicode FormatCode21[] = {0x59, 0x59, 0x2f, 0x4d, 0x4d, 0x2f, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName21[] = {0x0};
+static const sal_Unicode FormatKey22[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x37, 0x0};
+static const sal_Unicode defaultFormatElement22[] = {0};
+static const sal_Unicode FormatType22[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage22[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex22[] = {21};
+static const sal_Unicode FormatCode22[] = {0x59, 0x59, 0x59, 0x59, 0x2f, 0x4d, 0x4d, 0x2f, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName22[] = {0x0};
+static const sal_Unicode FormatKey23[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x30, 0x0};
+static const sal_Unicode defaultFormatElement23[] = {0};
+static const sal_Unicode FormatType23[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage23[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex23[] = {22};
+static const sal_Unicode FormatCode23[] = {0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName23[] = {0x0};
+static const sal_Unicode FormatKey24[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement24[] = {0};
+static const sal_Unicode FormatType24[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage24[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex24[] = {23};
+static const sal_Unicode FormatCode24[] = {0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName24[] = {0x0};
+static const sal_Unicode FormatKey25[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement25[] = {0};
+static const sal_Unicode FormatType25[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage25[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex25[] = {24};
+static const sal_Unicode FormatCode25[] = {0x44, 0x2e, 0x20, 0x4d, 0x4d, 0x4d, 0x2e, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName25[] = {0x0};
+static const sal_Unicode FormatKey26[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement26[] = {0};
+static const sal_Unicode FormatType26[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage26[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex26[] = {25};
+static const sal_Unicode FormatCode26[] = {0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName26[] = {0x0};
+static const sal_Unicode FormatKey27[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x37, 0x0};
+static const sal_Unicode defaultFormatElement27[] = {0};
+static const sal_Unicode FormatType27[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage27[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex27[] = {26};
+static const sal_Unicode FormatCode27[] = {0x44, 0x2e, 0x20, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName27[] = {0x0};
+static const sal_Unicode FormatKey28[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement28[] = {0};
+static const sal_Unicode FormatType28[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage28[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex28[] = {27};
+static const sal_Unicode FormatCode28[] = {0x4e, 0x4e, 0x20, 0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName28[] = {0x0};
+static const sal_Unicode FormatKey29[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement29[] = {0};
+static const sal_Unicode FormatType29[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage29[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex29[] = {28};
+static const sal_Unicode FormatCode29[] = {0x4e, 0x4e, 0x20, 0x44, 0x44, 0x2f, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName29[] = {0x0};
+static const sal_Unicode FormatKey30[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement30[] = {0};
+static const sal_Unicode FormatType30[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage30[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex30[] = {29};
+static const sal_Unicode FormatCode30[] = {0x4e, 0x4e, 0x20, 0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName30[] = {0x0};
+static const sal_Unicode FormatKey31[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement31[] = {0};
+static const sal_Unicode FormatType31[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage31[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex31[] = {30};
+static const sal_Unicode FormatCode31[] = {0x4e, 0x4e, 0x4e, 0x4e, 0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName31[] = {0x0};
+static const sal_Unicode FormatKey32[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x38, 0x0};
+static const sal_Unicode defaultFormatElement32[] = {0};
+static const sal_Unicode FormatType32[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage32[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex32[] = {31};
+static const sal_Unicode FormatCode32[] = {0x4d, 0x4d, 0x2d, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName32[] = {0x0};
+static const sal_Unicode FormatKey33[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x39, 0x0};
+static const sal_Unicode defaultFormatElement33[] = {0};
+static const sal_Unicode FormatType33[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage33[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex33[] = {32};
+static const sal_Unicode FormatCode33[] = {0x59, 0x59, 0x2d, 0x4d, 0x4d, 0x2d, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName33[] = {0x0};
+static const sal_Unicode FormatKey34[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x30, 0x0};
+static const sal_Unicode defaultFormatElement34[] = {0};
+static const sal_Unicode FormatType34[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage34[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex34[] = {33};
+static const sal_Unicode FormatCode34[] = {0x59, 0x59, 0x59, 0x59, 0x2d, 0x4d, 0x4d, 0x2d, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName34[] = {0x49, 0x53, 0x4f, 0x20, 0x38, 0x36, 0x30, 0x31, 0x0};
+static const sal_Unicode FormatKey35[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement35[] = {0};
+static const sal_Unicode FormatType35[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage35[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex35[] = {34};
+static const sal_Unicode FormatCode35[] = {0x59, 0x59, 0x2f, 0x4d, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName35[] = {0x0};
+static const sal_Unicode FormatKey36[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement36[] = {0};
+static const sal_Unicode FormatType36[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage36[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex36[] = {35};
+static const sal_Unicode FormatCode36[] = {0x4d, 0x4d, 0x4d, 0x20, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName36[] = {0x0};
+static const sal_Unicode FormatKey37[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement37[] = {0};
+static const sal_Unicode FormatType37[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage37[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex37[] = {36};
+static const sal_Unicode FormatCode37[] = {0x4d, 0x4d, 0x4d, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName37[] = {0x0};
+static const sal_Unicode FormatKey38[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement38[] = {0};
+static const sal_Unicode FormatType38[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage38[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex38[] = {37};
+static const sal_Unicode FormatCode38[] = {0x51, 0x51, 0x20, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName38[] = {0x0};
+static const sal_Unicode FormatKey39[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement39[] = {0};
+static const sal_Unicode FormatType39[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage39[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex39[] = {38};
+static const sal_Unicode FormatCode39[] = {0x57, 0x57, 0x0};
+static const sal_Unicode FormatDefaultName39[] = {0x0};
+static const sal_Unicode FormatKey40[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement40[] = {1};
+static const sal_Unicode FormatType40[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage40[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex40[] = {39};
+static const sal_Unicode FormatCode40[] = {0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName40[] = {0x0};
+static const sal_Unicode FormatKey41[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement41[] = {1};
+static const sal_Unicode FormatType41[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage41[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex41[] = {40};
+static const sal_Unicode FormatCode41[] = {0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x0};
+static const sal_Unicode FormatDefaultName41[] = {0x0};
+static const sal_Unicode FormatKey42[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement42[] = {0};
+static const sal_Unicode FormatType42[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage42[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex42[] = {41};
+static const sal_Unicode FormatCode42[] = {0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x20, 0x41, 0x4d, 0x2f, 0x50, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName42[] = {0x0};
+static const sal_Unicode FormatKey43[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement43[] = {0};
+static const sal_Unicode FormatType43[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage43[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex43[] = {42};
+static const sal_Unicode FormatCode43[] = {0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x20, 0x41, 0x4d, 0x2f, 0x50, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName43[] = {0x0};
+static const sal_Unicode FormatKey44[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement44[] = {0};
+static const sal_Unicode FormatType44[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage44[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex44[] = {43};
+static const sal_Unicode FormatCode44[] = {0x5b, 0x48, 0x48, 0x5d, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x0};
+static const sal_Unicode FormatDefaultName44[] = {0x0};
+static const sal_Unicode FormatKey45[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement45[] = {0};
+static const sal_Unicode FormatType45[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage45[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex45[] = {44};
+static const sal_Unicode FormatCode45[] = {0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName45[] = {0x0};
+static const sal_Unicode FormatKey46[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x37, 0x0};
+static const sal_Unicode defaultFormatElement46[] = {0};
+static const sal_Unicode FormatType46[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage46[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex46[] = {45};
+static const sal_Unicode FormatCode46[] = {0x5b, 0x48, 0x48, 0x5d, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName46[] = {0x0};
+static const sal_Unicode FormatKey47[] = {0x44, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement47[] = {1};
+static const sal_Unicode FormatType47[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage47[] = {0x44, 0x41, 0x54, 0x45, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex47[] = {46};
+static const sal_Unicode FormatCode47[] = {0x59, 0x59, 0x2f, 0x4d, 0x4d, 0x2f, 0x44, 0x44, 0x20, 0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName47[] = {0x0};
+static const sal_Unicode FormatKey48[] = {0x44, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement48[] = {0};
+static const sal_Unicode FormatType48[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage48[] = {0x44, 0x41, 0x54, 0x45, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex48[] = {47};
+static const sal_Unicode FormatCode48[] = {0x59, 0x59, 0x59, 0x59, 0x2f, 0x4d, 0x4d, 0x2f, 0x44, 0x44, 0x20, 0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x0};
+static const sal_Unicode FormatDefaultName48[] = {0x0};
+
+static const sal_Int16 FormatElementsCount0 = 49;
+static const sal_Unicode* FormatElementsArray0[] = {
+ FormatCode0,
+ FormatDefaultName0,
+ FormatKey0,
+ FormatType0,
+ FormatUsage0,
+ Formatindex0,
+ defaultFormatElement0,
+ FormatCode1,
+ FormatDefaultName1,
+ FormatKey1,
+ FormatType1,
+ FormatUsage1,
+ Formatindex1,
+ defaultFormatElement1,
+ FormatCode2,
+ FormatDefaultName2,
+ FormatKey2,
+ FormatType2,
+ FormatUsage2,
+ Formatindex2,
+ defaultFormatElement2,
+ FormatCode3,
+ FormatDefaultName3,
+ FormatKey3,
+ FormatType3,
+ FormatUsage3,
+ Formatindex3,
+ defaultFormatElement3,
+ FormatCode4,
+ FormatDefaultName4,
+ FormatKey4,
+ FormatType4,
+ FormatUsage4,
+ Formatindex4,
+ defaultFormatElement4,
+ FormatCode5,
+ FormatDefaultName5,
+ FormatKey5,
+ FormatType5,
+ FormatUsage5,
+ Formatindex5,
+ defaultFormatElement5,
+ FormatCode6,
+ FormatDefaultName6,
+ FormatKey6,
+ FormatType6,
+ FormatUsage6,
+ Formatindex6,
+ defaultFormatElement6,
+ FormatCode7,
+ FormatDefaultName7,
+ FormatKey7,
+ FormatType7,
+ FormatUsage7,
+ Formatindex7,
+ defaultFormatElement7,
+ FormatCode8,
+ FormatDefaultName8,
+ FormatKey8,
+ FormatType8,
+ FormatUsage8,
+ Formatindex8,
+ defaultFormatElement8,
+ FormatCode9,
+ FormatDefaultName9,
+ FormatKey9,
+ FormatType9,
+ FormatUsage9,
+ Formatindex9,
+ defaultFormatElement9,
+ FormatCode10,
+ FormatDefaultName10,
+ FormatKey10,
+ FormatType10,
+ FormatUsage10,
+ Formatindex10,
+ defaultFormatElement10,
+ FormatCode11,
+ FormatDefaultName11,
+ FormatKey11,
+ FormatType11,
+ FormatUsage11,
+ Formatindex11,
+ defaultFormatElement11,
+ FormatCode12,
+ FormatDefaultName12,
+ FormatKey12,
+ FormatType12,
+ FormatUsage12,
+ Formatindex12,
+ defaultFormatElement12,
+ FormatCode13,
+ FormatDefaultName13,
+ FormatKey13,
+ FormatType13,
+ FormatUsage13,
+ Formatindex13,
+ defaultFormatElement13,
+ FormatCode14,
+ FormatDefaultName14,
+ FormatKey14,
+ FormatType14,
+ FormatUsage14,
+ Formatindex14,
+ defaultFormatElement14,
+ FormatCode15,
+ FormatDefaultName15,
+ FormatKey15,
+ FormatType15,
+ FormatUsage15,
+ Formatindex15,
+ defaultFormatElement15,
+ FormatCode16,
+ FormatDefaultName16,
+ FormatKey16,
+ FormatType16,
+ FormatUsage16,
+ Formatindex16,
+ defaultFormatElement16,
+ FormatCode17,
+ FormatDefaultName17,
+ FormatKey17,
+ FormatType17,
+ FormatUsage17,
+ Formatindex17,
+ defaultFormatElement17,
+ FormatCode18,
+ FormatDefaultName18,
+ FormatKey18,
+ FormatType18,
+ FormatUsage18,
+ Formatindex18,
+ defaultFormatElement18,
+ FormatCode19,
+ FormatDefaultName19,
+ FormatKey19,
+ FormatType19,
+ FormatUsage19,
+ Formatindex19,
+ defaultFormatElement19,
+ FormatCode20,
+ FormatDefaultName20,
+ FormatKey20,
+ FormatType20,
+ FormatUsage20,
+ Formatindex20,
+ defaultFormatElement20,
+ FormatCode21,
+ FormatDefaultName21,
+ FormatKey21,
+ FormatType21,
+ FormatUsage21,
+ Formatindex21,
+ defaultFormatElement21,
+ FormatCode22,
+ FormatDefaultName22,
+ FormatKey22,
+ FormatType22,
+ FormatUsage22,
+ Formatindex22,
+ defaultFormatElement22,
+ FormatCode23,
+ FormatDefaultName23,
+ FormatKey23,
+ FormatType23,
+ FormatUsage23,
+ Formatindex23,
+ defaultFormatElement23,
+ FormatCode24,
+ FormatDefaultName24,
+ FormatKey24,
+ FormatType24,
+ FormatUsage24,
+ Formatindex24,
+ defaultFormatElement24,
+ FormatCode25,
+ FormatDefaultName25,
+ FormatKey25,
+ FormatType25,
+ FormatUsage25,
+ Formatindex25,
+ defaultFormatElement25,
+ FormatCode26,
+ FormatDefaultName26,
+ FormatKey26,
+ FormatType26,
+ FormatUsage26,
+ Formatindex26,
+ defaultFormatElement26,
+ FormatCode27,
+ FormatDefaultName27,
+ FormatKey27,
+ FormatType27,
+ FormatUsage27,
+ Formatindex27,
+ defaultFormatElement27,
+ FormatCode28,
+ FormatDefaultName28,
+ FormatKey28,
+ FormatType28,
+ FormatUsage28,
+ Formatindex28,
+ defaultFormatElement28,
+ FormatCode29,
+ FormatDefaultName29,
+ FormatKey29,
+ FormatType29,
+ FormatUsage29,
+ Formatindex29,
+ defaultFormatElement29,
+ FormatCode30,
+ FormatDefaultName30,
+ FormatKey30,
+ FormatType30,
+ FormatUsage30,
+ Formatindex30,
+ defaultFormatElement30,
+ FormatCode31,
+ FormatDefaultName31,
+ FormatKey31,
+ FormatType31,
+ FormatUsage31,
+ Formatindex31,
+ defaultFormatElement31,
+ FormatCode32,
+ FormatDefaultName32,
+ FormatKey32,
+ FormatType32,
+ FormatUsage32,
+ Formatindex32,
+ defaultFormatElement32,
+ FormatCode33,
+ FormatDefaultName33,
+ FormatKey33,
+ FormatType33,
+ FormatUsage33,
+ Formatindex33,
+ defaultFormatElement33,
+ FormatCode34,
+ FormatDefaultName34,
+ FormatKey34,
+ FormatType34,
+ FormatUsage34,
+ Formatindex34,
+ defaultFormatElement34,
+ FormatCode35,
+ FormatDefaultName35,
+ FormatKey35,
+ FormatType35,
+ FormatUsage35,
+ Formatindex35,
+ defaultFormatElement35,
+ FormatCode36,
+ FormatDefaultName36,
+ FormatKey36,
+ FormatType36,
+ FormatUsage36,
+ Formatindex36,
+ defaultFormatElement36,
+ FormatCode37,
+ FormatDefaultName37,
+ FormatKey37,
+ FormatType37,
+ FormatUsage37,
+ Formatindex37,
+ defaultFormatElement37,
+ FormatCode38,
+ FormatDefaultName38,
+ FormatKey38,
+ FormatType38,
+ FormatUsage38,
+ Formatindex38,
+ defaultFormatElement38,
+ FormatCode39,
+ FormatDefaultName39,
+ FormatKey39,
+ FormatType39,
+ FormatUsage39,
+ Formatindex39,
+ defaultFormatElement39,
+ FormatCode40,
+ FormatDefaultName40,
+ FormatKey40,
+ FormatType40,
+ FormatUsage40,
+ Formatindex40,
+ defaultFormatElement40,
+ FormatCode41,
+ FormatDefaultName41,
+ FormatKey41,
+ FormatType41,
+ FormatUsage41,
+ Formatindex41,
+ defaultFormatElement41,
+ FormatCode42,
+ FormatDefaultName42,
+ FormatKey42,
+ FormatType42,
+ FormatUsage42,
+ Formatindex42,
+ defaultFormatElement42,
+ FormatCode43,
+ FormatDefaultName43,
+ FormatKey43,
+ FormatType43,
+ FormatUsage43,
+ Formatindex43,
+ defaultFormatElement43,
+ FormatCode44,
+ FormatDefaultName44,
+ FormatKey44,
+ FormatType44,
+ FormatUsage44,
+ Formatindex44,
+ defaultFormatElement44,
+ FormatCode45,
+ FormatDefaultName45,
+ FormatKey45,
+ FormatType45,
+ FormatUsage45,
+ Formatindex45,
+ defaultFormatElement45,
+ FormatCode46,
+ FormatDefaultName46,
+ FormatKey46,
+ FormatType46,
+ FormatUsage46,
+ Formatindex46,
+ defaultFormatElement46,
+ FormatCode47,
+ FormatDefaultName47,
+ FormatKey47,
+ FormatType47,
+ FormatUsage47,
+ Formatindex47,
+ defaultFormatElement47,
+ FormatCode48,
+ FormatDefaultName48,
+ FormatKey48,
+ FormatType48,
+ FormatUsage48,
+ Formatindex48,
+ defaultFormatElement48,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode const * const * SAL_CALL getAllFormats0_en_GH(sal_Int16& count, const sal_Unicode*& from, const sal_Unicode*& to)
+{
+ count = FormatElementsCount0;
+ from = replaceFrom0;
+ to = replaceTo0;
+ return (sal_Unicode**)FormatElementsArray0;
+}
+static const sal_Unicode DateAcceptancePattern0[] = {0x59, 0x2f, 0x4d, 0x2f, 0x44, 0x0};
+static const sal_Unicode DateAcceptancePattern1[] = {0x4d, 0x2f, 0x44, 0x0};
+static const sal_Int16 DateAcceptancePatternsCount = 2;
+static const sal_Unicode* DateAcceptancePatternsArray[] = {
+ DateAcceptancePattern0,
+ DateAcceptancePattern1,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getDateAcceptancePatterns_en_GH(sal_Int16& count)
+{
+ count = DateAcceptancePatternsCount;
+ return (sal_Unicode**)DateAcceptancePatternsArray;
+}
+static const sal_Unicode CollatorID0[] = {0x61, 0x6c, 0x70, 0x68, 0x61, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x0};
+static const sal_Unicode CollatorRule0[] = {0x0};
+static const sal_Unicode defaultCollator0[] = {1};
+
+static const sal_Unicode collationOption0[] = {0x49, 0x47, 0x4e, 0x4f, 0x52, 0x45, 0x5f, 0x43, 0x41, 0x53, 0x45, 0x0};
+static const sal_Int16 nbOfCollationOptions = 1;
+
+static const sal_Int16 nbOfCollations = 1;
+
+
+static const sal_Unicode* LCCollatorArray[] = {
+ CollatorID0,
+ defaultCollator0,
+ CollatorRule0,
+};
+
+static const sal_Unicode* collationOptions[] = {collationOption0, NULL };
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollatorImplementation_en_GH(sal_Int16& count)
+{
+ count = nbOfCollations;
+ return (sal_Unicode**)LCCollatorArray;
+}
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollationOptions_en_GH(sal_Int16& count)
+{
+ count = nbOfCollationOptions;
+ return (sal_Unicode**)collationOptions;
+}
+static const sal_Unicode searchOption0[] = {0x49, 0x47, 0x4e, 0x4f, 0x52, 0x45, 0x5f, 0x43, 0x41, 0x53, 0x45, 0x0};
+static const sal_Int16 nbOfSearchOptions = 1;
+
+static const sal_Unicode* searchOptions[] = {searchOption0, NULL };
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getSearchOptions_en_GH(sal_Int16& count)
+{
+ count = nbOfSearchOptions;
+ return (sal_Unicode**)searchOptions;
+}
+static const sal_Unicode IndexID0[] = {0x61, 0x6c, 0x70, 0x68, 0x61, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x0};
+static const sal_Unicode IndexModule0[] = {0x0};
+static const sal_Unicode IndexKey0[] = {0x41, 0x2d, 0x5a, 0x0};
+static const sal_Unicode defaultIndex0[] = {1};
+static const sal_Unicode defaultPhonetic0[] = {0};
+
+static const sal_Unicode unicodeScript0[] = {0x30, 0x0};
+static const sal_Unicode unicodeScript1[] = {0x31, 0x0};
+static const sal_Unicode followPageWord0[] = {0x70, 0x0};
+static const sal_Unicode followPageWord1[] = {0x70, 0x70, 0x0};
+static const sal_Int16 nbOfIndexs = 1;
+
+
+static const sal_Unicode* IndexArray[] = {
+ IndexID0,
+ IndexModule0,
+ IndexKey0,
+ defaultIndex0,
+ defaultPhonetic0,
+};
+
+static const sal_Int16 nbOfUnicodeScripts = 2;
+
+static const sal_Unicode* UnicodeScriptArray[] = {unicodeScript0, unicodeScript1, NULL };
+
+static const sal_Int16 nbOfPageWords = 2;
+
+static const sal_Unicode* FollowPageWordArray[] = {
+ followPageWord0,
+ followPageWord1,
+ NULL
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getIndexAlgorithm_en_GH(sal_Int16& count)
+{
+ count = nbOfIndexs;
+ return (sal_Unicode**)IndexArray;
+}
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getUnicodeScripts_en_GH(sal_Int16& count)
+{
+ count = nbOfUnicodeScripts;
+ return (sal_Unicode**)UnicodeScriptArray;
+}
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getFollowPageWords_en_GH(sal_Int16& count)
+{
+ count = nbOfPageWords;
+ return (sal_Unicode**)FollowPageWordArray;
+}
+static const sal_Unicode calendarID0[] = {0x67, 0x72, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x61, 0x6e, 0x0};
+static const sal_Unicode defaultCalendar0[] = {1};
+static const sal_Unicode dayID00[] = {0x73, 0x75, 0x6e, 0x0};
+static const sal_Unicode dayDefaultAbbrvName00[] = {0x53, 0x75, 0x6e, 0x0};
+static const sal_Unicode dayDefaultFullName00[] = {0x53, 0x75, 0x6e, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName00[] = {0x53, 0x0};
+static const sal_Unicode dayID01[] = {0x6d, 0x6f, 0x6e, 0x0};
+static const sal_Unicode dayDefaultAbbrvName01[] = {0x4d, 0x6f, 0x6e, 0x0};
+static const sal_Unicode dayDefaultFullName01[] = {0x4d, 0x6f, 0x6e, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName01[] = {0x4d, 0x0};
+static const sal_Unicode dayID02[] = {0x74, 0x75, 0x65, 0x0};
+static const sal_Unicode dayDefaultAbbrvName02[] = {0x54, 0x75, 0x65, 0x0};
+static const sal_Unicode dayDefaultFullName02[] = {0x54, 0x75, 0x65, 0x73, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName02[] = {0x54, 0x0};
+static const sal_Unicode dayID03[] = {0x77, 0x65, 0x64, 0x0};
+static const sal_Unicode dayDefaultAbbrvName03[] = {0x57, 0x65, 0x64, 0x0};
+static const sal_Unicode dayDefaultFullName03[] = {0x57, 0x65, 0x64, 0x6e, 0x65, 0x73, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName03[] = {0x57, 0x0};
+static const sal_Unicode dayID04[] = {0x74, 0x68, 0x75, 0x0};
+static const sal_Unicode dayDefaultAbbrvName04[] = {0x54, 0x68, 0x75, 0x0};
+static const sal_Unicode dayDefaultFullName04[] = {0x54, 0x68, 0x75, 0x72, 0x73, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName04[] = {0x54, 0x0};
+static const sal_Unicode dayID05[] = {0x66, 0x72, 0x69, 0x0};
+static const sal_Unicode dayDefaultAbbrvName05[] = {0x46, 0x72, 0x69, 0x0};
+static const sal_Unicode dayDefaultFullName05[] = {0x46, 0x72, 0x69, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName05[] = {0x46, 0x0};
+static const sal_Unicode dayID06[] = {0x73, 0x61, 0x74, 0x0};
+static const sal_Unicode dayDefaultAbbrvName06[] = {0x53, 0x61, 0x74, 0x0};
+static const sal_Unicode dayDefaultFullName06[] = {0x53, 0x61, 0x74, 0x75, 0x72, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName06[] = {0x53, 0x0};
+static const sal_Unicode monthID00[] = {0x6a, 0x61, 0x6e, 0x0};
+static const sal_Unicode monthDefaultAbbrvName00[] = {0x4a, 0x61, 0x6e, 0x0};
+static const sal_Unicode monthDefaultFullName00[] = {0x4a, 0x61, 0x6e, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode monthDefaultNarrowName00[] = {0x4a, 0x0};
+static const sal_Unicode monthID01[] = {0x66, 0x65, 0x62, 0x0};
+static const sal_Unicode monthDefaultAbbrvName01[] = {0x46, 0x65, 0x62, 0x0};
+static const sal_Unicode monthDefaultFullName01[] = {0x46, 0x65, 0x62, 0x72, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode monthDefaultNarrowName01[] = {0x46, 0x0};
+static const sal_Unicode monthID02[] = {0x6d, 0x61, 0x72, 0x0};
+static const sal_Unicode monthDefaultAbbrvName02[] = {0x4d, 0x61, 0x72, 0x0};
+static const sal_Unicode monthDefaultFullName02[] = {0x4d, 0x61, 0x72, 0x63, 0x68, 0x0};
+static const sal_Unicode monthDefaultNarrowName02[] = {0x4d, 0x0};
+static const sal_Unicode monthID03[] = {0x61, 0x70, 0x72, 0x0};
+static const sal_Unicode monthDefaultAbbrvName03[] = {0x41, 0x70, 0x72, 0x0};
+static const sal_Unicode monthDefaultFullName03[] = {0x41, 0x70, 0x72, 0x69, 0x6c, 0x0};
+static const sal_Unicode monthDefaultNarrowName03[] = {0x41, 0x0};
+static const sal_Unicode monthID04[] = {0x6d, 0x61, 0x79, 0x0};
+static const sal_Unicode monthDefaultAbbrvName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode monthDefaultFullName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode monthDefaultNarrowName04[] = {0x4d, 0x0};
+static const sal_Unicode monthID05[] = {0x6a, 0x75, 0x6e, 0x0};
+static const sal_Unicode monthDefaultAbbrvName05[] = {0x4a, 0x75, 0x6e, 0x0};
+static const sal_Unicode monthDefaultFullName05[] = {0x4a, 0x75, 0x6e, 0x65, 0x0};
+static const sal_Unicode monthDefaultNarrowName05[] = {0x4a, 0x0};
+static const sal_Unicode monthID06[] = {0x6a, 0x75, 0x6c, 0x0};
+static const sal_Unicode monthDefaultAbbrvName06[] = {0x4a, 0x75, 0x6c, 0x0};
+static const sal_Unicode monthDefaultFullName06[] = {0x4a, 0x75, 0x6c, 0x79, 0x0};
+static const sal_Unicode monthDefaultNarrowName06[] = {0x4a, 0x0};
+static const sal_Unicode monthID07[] = {0x61, 0x75, 0x67, 0x0};
+static const sal_Unicode monthDefaultAbbrvName07[] = {0x41, 0x75, 0x67, 0x0};
+static const sal_Unicode monthDefaultFullName07[] = {0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x0};
+static const sal_Unicode monthDefaultNarrowName07[] = {0x41, 0x0};
+static const sal_Unicode monthID08[] = {0x73, 0x65, 0x70, 0x0};
+static const sal_Unicode monthDefaultAbbrvName08[] = {0x53, 0x65, 0x70, 0x0};
+static const sal_Unicode monthDefaultFullName08[] = {0x53, 0x65, 0x70, 0x74, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName08[] = {0x53, 0x0};
+static const sal_Unicode monthID09[] = {0x6f, 0x63, 0x74, 0x0};
+static const sal_Unicode monthDefaultAbbrvName09[] = {0x4f, 0x63, 0x74, 0x0};
+static const sal_Unicode monthDefaultFullName09[] = {0x4f, 0x63, 0x74, 0x6f, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName09[] = {0x4f, 0x0};
+static const sal_Unicode monthID010[] = {0x6e, 0x6f, 0x76, 0x0};
+static const sal_Unicode monthDefaultAbbrvName010[] = {0x4e, 0x6f, 0x76, 0x0};
+static const sal_Unicode monthDefaultFullName010[] = {0x4e, 0x6f, 0x76, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName010[] = {0x4e, 0x0};
+static const sal_Unicode monthID011[] = {0x64, 0x65, 0x63, 0x0};
+static const sal_Unicode monthDefaultAbbrvName011[] = {0x44, 0x65, 0x63, 0x0};
+static const sal_Unicode monthDefaultFullName011[] = {0x44, 0x65, 0x63, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName011[] = {0x44, 0x0};
+static const sal_Unicode genitiveMonthID00[] = {0x6a, 0x61, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName00[] = {0x4a, 0x61, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName00[] = {0x4a, 0x61, 0x6e, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName00[] = {0x4a, 0x0};
+static const sal_Unicode genitiveMonthID01[] = {0x66, 0x65, 0x62, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName01[] = {0x46, 0x65, 0x62, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName01[] = {0x46, 0x65, 0x62, 0x72, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName01[] = {0x46, 0x0};
+static const sal_Unicode genitiveMonthID02[] = {0x6d, 0x61, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName02[] = {0x4d, 0x61, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName02[] = {0x4d, 0x61, 0x72, 0x63, 0x68, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName02[] = {0x4d, 0x0};
+static const sal_Unicode genitiveMonthID03[] = {0x61, 0x70, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName03[] = {0x41, 0x70, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName03[] = {0x41, 0x70, 0x72, 0x69, 0x6c, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName03[] = {0x41, 0x0};
+static const sal_Unicode genitiveMonthID04[] = {0x6d, 0x61, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName04[] = {0x4d, 0x0};
+static const sal_Unicode genitiveMonthID05[] = {0x6a, 0x75, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName05[] = {0x4a, 0x75, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName05[] = {0x4a, 0x75, 0x6e, 0x65, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName05[] = {0x4a, 0x0};
+static const sal_Unicode genitiveMonthID06[] = {0x6a, 0x75, 0x6c, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName06[] = {0x4a, 0x75, 0x6c, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName06[] = {0x4a, 0x75, 0x6c, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName06[] = {0x4a, 0x0};
+static const sal_Unicode genitiveMonthID07[] = {0x61, 0x75, 0x67, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName07[] = {0x41, 0x75, 0x67, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName07[] = {0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName07[] = {0x41, 0x0};
+static const sal_Unicode genitiveMonthID08[] = {0x73, 0x65, 0x70, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName08[] = {0x53, 0x65, 0x70, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName08[] = {0x53, 0x65, 0x70, 0x74, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName08[] = {0x53, 0x0};
+static const sal_Unicode genitiveMonthID09[] = {0x6f, 0x63, 0x74, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName09[] = {0x4f, 0x63, 0x74, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName09[] = {0x4f, 0x63, 0x74, 0x6f, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName09[] = {0x4f, 0x0};
+static const sal_Unicode genitiveMonthID010[] = {0x6e, 0x6f, 0x76, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName010[] = {0x4e, 0x6f, 0x76, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName010[] = {0x4e, 0x6f, 0x76, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName010[] = {0x4e, 0x0};
+static const sal_Unicode genitiveMonthID011[] = {0x64, 0x65, 0x63, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName011[] = {0x44, 0x65, 0x63, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName011[] = {0x44, 0x65, 0x63, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName011[] = {0x44, 0x0};
+static const sal_Unicode partitiveMonthID00[] = {0x6a, 0x61, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName00[] = {0x4a, 0x61, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName00[] = {0x4a, 0x61, 0x6e, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName00[] = {0x4a, 0x0};
+static const sal_Unicode partitiveMonthID01[] = {0x66, 0x65, 0x62, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName01[] = {0x46, 0x65, 0x62, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName01[] = {0x46, 0x65, 0x62, 0x72, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName01[] = {0x46, 0x0};
+static const sal_Unicode partitiveMonthID02[] = {0x6d, 0x61, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName02[] = {0x4d, 0x61, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName02[] = {0x4d, 0x61, 0x72, 0x63, 0x68, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName02[] = {0x4d, 0x0};
+static const sal_Unicode partitiveMonthID03[] = {0x61, 0x70, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName03[] = {0x41, 0x70, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName03[] = {0x41, 0x70, 0x72, 0x69, 0x6c, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName03[] = {0x41, 0x0};
+static const sal_Unicode partitiveMonthID04[] = {0x6d, 0x61, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName04[] = {0x4d, 0x0};
+static const sal_Unicode partitiveMonthID05[] = {0x6a, 0x75, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName05[] = {0x4a, 0x75, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName05[] = {0x4a, 0x75, 0x6e, 0x65, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName05[] = {0x4a, 0x0};
+static const sal_Unicode partitiveMonthID06[] = {0x6a, 0x75, 0x6c, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName06[] = {0x4a, 0x75, 0x6c, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName06[] = {0x4a, 0x75, 0x6c, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName06[] = {0x4a, 0x0};
+static const sal_Unicode partitiveMonthID07[] = {0x61, 0x75, 0x67, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName07[] = {0x41, 0x75, 0x67, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName07[] = {0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName07[] = {0x41, 0x0};
+static const sal_Unicode partitiveMonthID08[] = {0x73, 0x65, 0x70, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName08[] = {0x53, 0x65, 0x70, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName08[] = {0x53, 0x65, 0x70, 0x74, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName08[] = {0x53, 0x0};
+static const sal_Unicode partitiveMonthID09[] = {0x6f, 0x63, 0x74, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName09[] = {0x4f, 0x63, 0x74, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName09[] = {0x4f, 0x63, 0x74, 0x6f, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName09[] = {0x4f, 0x0};
+static const sal_Unicode partitiveMonthID010[] = {0x6e, 0x6f, 0x76, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName010[] = {0x4e, 0x6f, 0x76, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName010[] = {0x4e, 0x6f, 0x76, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName010[] = {0x4e, 0x0};
+static const sal_Unicode partitiveMonthID011[] = {0x64, 0x65, 0x63, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName011[] = {0x44, 0x65, 0x63, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName011[] = {0x44, 0x65, 0x63, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName011[] = {0x44, 0x0};
+static const sal_Unicode eraID00[] = {0x62, 0x63, 0x0};
+
+static const sal_Unicode eraDefaultAbbrvName00[] = {0x42, 0x43, 0x0};
+static const sal_Unicode eraDefaultFullName00[] = {0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x20, 0x43, 0x68, 0x72, 0x69, 0x73, 0x74, 0x0};
+static const sal_Unicode eraID01[] = {0x61, 0x64, 0x0};
+
+static const sal_Unicode eraDefaultAbbrvName01[] = {0x41, 0x44, 0x0};
+static const sal_Unicode eraDefaultFullName01[] = {0x41, 0x6e, 0x6e, 0x6f, 0x20, 0x44, 0x6f, 0x6d, 0x69, 0x6e, 0x69, 0x0};
+static const sal_Unicode startDayOfWeek0[] = {0x73, 0x75, 0x6e, 0x0};
+static const sal_Unicode minimalDaysInFirstWeek0[] = {1};
+static const sal_Int16 calendarsCount = 1;
+
+static const sal_Unicode nbOfDays[] = {7};
+static const sal_Unicode nbOfMonths[] = {12};
+static const sal_Unicode nbOfGenitiveMonths[] = {12};
+static const sal_Unicode nbOfPartitiveMonths[] = {12};
+static const sal_Unicode nbOfEras[] = {2};
+static const sal_Unicode* calendars[] = {
+ nbOfDays,
+ nbOfMonths,
+ nbOfGenitiveMonths,
+ nbOfPartitiveMonths,
+ nbOfEras,
+ calendarID0,
+ defaultCalendar0,
+ dayID00,
+ dayDefaultAbbrvName00,
+ dayDefaultFullName00,
+ dayDefaultNarrowName00,
+ dayID01,
+ dayDefaultAbbrvName01,
+ dayDefaultFullName01,
+ dayDefaultNarrowName01,
+ dayID02,
+ dayDefaultAbbrvName02,
+ dayDefaultFullName02,
+ dayDefaultNarrowName02,
+ dayID03,
+ dayDefaultAbbrvName03,
+ dayDefaultFullName03,
+ dayDefaultNarrowName03,
+ dayID04,
+ dayDefaultAbbrvName04,
+ dayDefaultFullName04,
+ dayDefaultNarrowName04,
+ dayID05,
+ dayDefaultAbbrvName05,
+ dayDefaultFullName05,
+ dayDefaultNarrowName05,
+ dayID06,
+ dayDefaultAbbrvName06,
+ dayDefaultFullName06,
+ dayDefaultNarrowName06,
+ monthID00,
+ monthDefaultAbbrvName00,
+ monthDefaultFullName00,
+ monthDefaultNarrowName00,
+ monthID01,
+ monthDefaultAbbrvName01,
+ monthDefaultFullName01,
+ monthDefaultNarrowName01,
+ monthID02,
+ monthDefaultAbbrvName02,
+ monthDefaultFullName02,
+ monthDefaultNarrowName02,
+ monthID03,
+ monthDefaultAbbrvName03,
+ monthDefaultFullName03,
+ monthDefaultNarrowName03,
+ monthID04,
+ monthDefaultAbbrvName04,
+ monthDefaultFullName04,
+ monthDefaultNarrowName04,
+ monthID05,
+ monthDefaultAbbrvName05,
+ monthDefaultFullName05,
+ monthDefaultNarrowName05,
+ monthID06,
+ monthDefaultAbbrvName06,
+ monthDefaultFullName06,
+ monthDefaultNarrowName06,
+ monthID07,
+ monthDefaultAbbrvName07,
+ monthDefaultFullName07,
+ monthDefaultNarrowName07,
+ monthID08,
+ monthDefaultAbbrvName08,
+ monthDefaultFullName08,
+ monthDefaultNarrowName08,
+ monthID09,
+ monthDefaultAbbrvName09,
+ monthDefaultFullName09,
+ monthDefaultNarrowName09,
+ monthID010,
+ monthDefaultAbbrvName010,
+ monthDefaultFullName010,
+ monthDefaultNarrowName010,
+ monthID011,
+ monthDefaultAbbrvName011,
+ monthDefaultFullName011,
+ monthDefaultNarrowName011,
+ genitiveMonthID00,
+ genitiveMonthDefaultAbbrvName00,
+ genitiveMonthDefaultFullName00,
+ genitiveMonthDefaultNarrowName00,
+ genitiveMonthID01,
+ genitiveMonthDefaultAbbrvName01,
+ genitiveMonthDefaultFullName01,
+ genitiveMonthDefaultNarrowName01,
+ genitiveMonthID02,
+ genitiveMonthDefaultAbbrvName02,
+ genitiveMonthDefaultFullName02,
+ genitiveMonthDefaultNarrowName02,
+ genitiveMonthID03,
+ genitiveMonthDefaultAbbrvName03,
+ genitiveMonthDefaultFullName03,
+ genitiveMonthDefaultNarrowName03,
+ genitiveMonthID04,
+ genitiveMonthDefaultAbbrvName04,
+ genitiveMonthDefaultFullName04,
+ genitiveMonthDefaultNarrowName04,
+ genitiveMonthID05,
+ genitiveMonthDefaultAbbrvName05,
+ genitiveMonthDefaultFullName05,
+ genitiveMonthDefaultNarrowName05,
+ genitiveMonthID06,
+ genitiveMonthDefaultAbbrvName06,
+ genitiveMonthDefaultFullName06,
+ genitiveMonthDefaultNarrowName06,
+ genitiveMonthID07,
+ genitiveMonthDefaultAbbrvName07,
+ genitiveMonthDefaultFullName07,
+ genitiveMonthDefaultNarrowName07,
+ genitiveMonthID08,
+ genitiveMonthDefaultAbbrvName08,
+ genitiveMonthDefaultFullName08,
+ genitiveMonthDefaultNarrowName08,
+ genitiveMonthID09,
+ genitiveMonthDefaultAbbrvName09,
+ genitiveMonthDefaultFullName09,
+ genitiveMonthDefaultNarrowName09,
+ genitiveMonthID010,
+ genitiveMonthDefaultAbbrvName010,
+ genitiveMonthDefaultFullName010,
+ genitiveMonthDefaultNarrowName010,
+ genitiveMonthID011,
+ genitiveMonthDefaultAbbrvName011,
+ genitiveMonthDefaultFullName011,
+ genitiveMonthDefaultNarrowName011,
+ partitiveMonthID00,
+ partitiveMonthDefaultAbbrvName00,
+ partitiveMonthDefaultFullName00,
+ partitiveMonthDefaultNarrowName00,
+ partitiveMonthID01,
+ partitiveMonthDefaultAbbrvName01,
+ partitiveMonthDefaultFullName01,
+ partitiveMonthDefaultNarrowName01,
+ partitiveMonthID02,
+ partitiveMonthDefaultAbbrvName02,
+ partitiveMonthDefaultFullName02,
+ partitiveMonthDefaultNarrowName02,
+ partitiveMonthID03,
+ partitiveMonthDefaultAbbrvName03,
+ partitiveMonthDefaultFullName03,
+ partitiveMonthDefaultNarrowName03,
+ partitiveMonthID04,
+ partitiveMonthDefaultAbbrvName04,
+ partitiveMonthDefaultFullName04,
+ partitiveMonthDefaultNarrowName04,
+ partitiveMonthID05,
+ partitiveMonthDefaultAbbrvName05,
+ partitiveMonthDefaultFullName05,
+ partitiveMonthDefaultNarrowName05,
+ partitiveMonthID06,
+ partitiveMonthDefaultAbbrvName06,
+ partitiveMonthDefaultFullName06,
+ partitiveMonthDefaultNarrowName06,
+ partitiveMonthID07,
+ partitiveMonthDefaultAbbrvName07,
+ partitiveMonthDefaultFullName07,
+ partitiveMonthDefaultNarrowName07,
+ partitiveMonthID08,
+ partitiveMonthDefaultAbbrvName08,
+ partitiveMonthDefaultFullName08,
+ partitiveMonthDefaultNarrowName08,
+ partitiveMonthID09,
+ partitiveMonthDefaultAbbrvName09,
+ partitiveMonthDefaultFullName09,
+ partitiveMonthDefaultNarrowName09,
+ partitiveMonthID010,
+ partitiveMonthDefaultAbbrvName010,
+ partitiveMonthDefaultFullName010,
+ partitiveMonthDefaultNarrowName010,
+ partitiveMonthID011,
+ partitiveMonthDefaultAbbrvName011,
+ partitiveMonthDefaultFullName011,
+ partitiveMonthDefaultNarrowName011,
+ eraID00,
+ eraDefaultAbbrvName00,
+ eraDefaultFullName00,
+ eraID01,
+ eraDefaultAbbrvName01,
+ eraDefaultFullName01,
+ startDayOfWeek0,
+ minimalDaysInFirstWeek0,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCalendars_en_GH(sal_Int16& count)
+{
+ count = calendarsCount;
+ return (sal_Unicode**)calendars;
+}
+static const sal_Unicode defaultCurrency0[] = {0};
+static const sal_Unicode defaultCurrencyUsedInCompatibleFormatCodes0[] = {1};
+static const sal_Unicode defaultCurrencyLegacyOnly0[] = {0};
+static const sal_Unicode currencyID0[] = {0x47, 0x48, 0x43, 0x0};
+static const sal_Unicode currencySymbol0[] = {0x20b5, 0x0};
+static const sal_Unicode bankSymbol0[] = {0x47, 0x48, 0x43, 0x0};
+static const sal_Unicode currencyName0[] = {0x43, 0x65, 0x64, 0x69, 0x0};
+static const sal_Unicode currencyDecimalPlaces0[] = {2};
+
+static const sal_Unicode defaultCurrency1[] = {1};
+static const sal_Unicode defaultCurrencyUsedInCompatibleFormatCodes1[] = {0};
+static const sal_Unicode defaultCurrencyLegacyOnly1[] = {0};
+static const sal_Unicode currencyID1[] = {0x47, 0x48, 0x53, 0x0};
+static const sal_Unicode currencySymbol1[] = {0x47, 0x48, 0x20b5, 0x0};
+static const sal_Unicode bankSymbol1[] = {0x47, 0x48, 0x53, 0x0};
+static const sal_Unicode currencyName1[] = {0x47, 0x68, 0x61, 0x6e, 0x61, 0x20, 0x43, 0x65, 0x64, 0x69, 0x0};
+static const sal_Unicode currencyDecimalPlaces1[] = {2};
+
+static const sal_Int16 currencyCount = 2;
+
+static const sal_Unicode* currencies[] = {
+ currencyID0,
+ currencySymbol0,
+ bankSymbol0,
+ currencyName0,
+ defaultCurrency0,
+ defaultCurrencyUsedInCompatibleFormatCodes0,
+ currencyDecimalPlaces0,
+ defaultCurrencyLegacyOnly0,
+ currencyID1,
+ currencySymbol1,
+ bankSymbol1,
+ currencyName1,
+ defaultCurrency1,
+ defaultCurrencyUsedInCompatibleFormatCodes1,
+ currencyDecimalPlaces1,
+ defaultCurrencyLegacyOnly1,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCurrencies_en_GH(sal_Int16& count)
+{
+ count = currencyCount;
+ return (sal_Unicode**)currencies;
+}
+extern sal_Unicode ** SAL_CALL getTransliterations_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getTransliterations_en_GH(sal_Int16& count)
+{
+ return getTransliterations_en_US(count);
+}
+static const sal_Unicode ReservedWord0[] = {0x54, 0x72, 0x75, 0x65, 0x0};
+static const sal_Unicode ReservedWord1[] = {0x46, 0x61, 0x6c, 0x73, 0x65, 0x0};
+static const sal_Unicode ReservedWord2[] = {0x51, 0x75, 0x61, 0x72, 0x74, 0x65, 0x72, 0x20, 0x31, 0x0};
+static const sal_Unicode ReservedWord3[] = {0x51, 0x75, 0x61, 0x72, 0x74, 0x65, 0x72, 0x20, 0x32, 0x0};
+static const sal_Unicode ReservedWord4[] = {0x51, 0x75, 0x61, 0x72, 0x74, 0x65, 0x72, 0x20, 0x33, 0x0};
+static const sal_Unicode ReservedWord5[] = {0x51, 0x75, 0x61, 0x72, 0x74, 0x65, 0x72, 0x20, 0x34, 0x0};
+static const sal_Unicode ReservedWord6[] = {0x41, 0x62, 0x6f, 0x76, 0x65, 0x0};
+static const sal_Unicode ReservedWord7[] = {0x42, 0x65, 0x6c, 0x6f, 0x77, 0x0};
+static const sal_Unicode ReservedWord8[] = {0x51, 0x31, 0x0};
+static const sal_Unicode ReservedWord9[] = {0x51, 0x32, 0x0};
+static const sal_Unicode ReservedWord10[] = {0x51, 0x33, 0x0};
+static const sal_Unicode ReservedWord11[] = {0x51, 0x34, 0x0};
+static const sal_Int16 nbOfReservedWords = 12;
+
+
+static const sal_Unicode* LCReservedWordsArray[] = {
+ ReservedWord0,
+ ReservedWord1,
+ ReservedWord2,
+ ReservedWord3,
+ ReservedWord4,
+ ReservedWord5,
+ ReservedWord6,
+ ReservedWord7,
+ ReservedWord8,
+ ReservedWord9,
+ ReservedWord10,
+ ReservedWord11,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getReservedWords_en_GH(sal_Int16& count)
+{
+ count = nbOfReservedWords;
+ return (sal_Unicode**)LCReservedWordsArray;
+}
+static const sal_Unicode forbiddenBegin[] = {0x0};
+static const sal_Unicode forbiddenEnd[] = {0x0};
+static const sal_Unicode hangingChars[] = {0x0};
+
+static const sal_Unicode* LCForbiddenCharactersArray[] = {
+ forbiddenBegin,
+ forbiddenEnd,
+ hangingChars
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getForbiddenCharacters_en_GH(sal_Int16& count)
+{
+ count = 3;
+ return (sal_Unicode**)LCForbiddenCharactersArray;
+}
+static const sal_Unicode EditMode[] = {0x0};
+static const sal_Unicode DictionaryMode[] = {0x0};
+static const sal_Unicode WordCountMode[] = {0x0};
+static const sal_Unicode CharacterMode[] = {0x0};
+static const sal_Unicode LineMode[] = {0x0};
+
+static const sal_Unicode* LCBreakIteratorRulesArray[] = {
+ EditMode,
+ DictionaryMode,
+ WordCountMode,
+ CharacterMode,
+ LineMode
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getBreakIteratorRules_en_GH(sal_Int16& count)
+{
+ count = 5;
+ return (sal_Unicode**)LCBreakIteratorRulesArray;
+}
+// ---> ContinuousNumbering
+static const sal_Unicode continuousPrefix0[] = {0x0};
+static const sal_Unicode continuousNumType0[] = {0x34, 0x0};
+static const sal_Unicode continuousSuffix0[] = {0x29, 0x0};
+static const sal_Unicode continuousTransliteration0[] = {0x0};
+static const sal_Unicode continuousNatNum0[] = {0x0};
+static const sal_Unicode continuousPrefix1[] = {0x0};
+static const sal_Unicode continuousNumType1[] = {0x34, 0x0};
+static const sal_Unicode continuousSuffix1[] = {0x2e, 0x0};
+static const sal_Unicode continuousTransliteration1[] = {0x0};
+static const sal_Unicode continuousNatNum1[] = {0x0};
+static const sal_Unicode continuousPrefix2[] = {0x28, 0x0};
+static const sal_Unicode continuousNumType2[] = {0x34, 0x0};
+static const sal_Unicode continuousSuffix2[] = {0x29, 0x0};
+static const sal_Unicode continuousTransliteration2[] = {0x0};
+static const sal_Unicode continuousNatNum2[] = {0x0};
+static const sal_Unicode continuousPrefix3[] = {0x0};
+static const sal_Unicode continuousNumType3[] = {0x32, 0x0};
+static const sal_Unicode continuousSuffix3[] = {0x2e, 0x0};
+static const sal_Unicode continuousTransliteration3[] = {0x0};
+static const sal_Unicode continuousNatNum3[] = {0x0};
+static const sal_Unicode continuousPrefix4[] = {0x0};
+static const sal_Unicode continuousNumType4[] = {0x30, 0x0};
+static const sal_Unicode continuousSuffix4[] = {0x29, 0x0};
+static const sal_Unicode continuousTransliteration4[] = {0x0};
+static const sal_Unicode continuousNatNum4[] = {0x0};
+static const sal_Unicode continuousPrefix5[] = {0x0};
+static const sal_Unicode continuousNumType5[] = {0x31, 0x0};
+static const sal_Unicode continuousSuffix5[] = {0x29, 0x0};
+static const sal_Unicode continuousTransliteration5[] = {0x0};
+static const sal_Unicode continuousNatNum5[] = {0x0};
+static const sal_Unicode continuousPrefix6[] = {0x28, 0x0};
+static const sal_Unicode continuousNumType6[] = {0x31, 0x0};
+static const sal_Unicode continuousSuffix6[] = {0x29, 0x0};
+static const sal_Unicode continuousTransliteration6[] = {0x0};
+static const sal_Unicode continuousNatNum6[] = {0x0};
+static const sal_Unicode continuousPrefix7[] = {0x0};
+static const sal_Unicode continuousNumType7[] = {0x33, 0x0};
+static const sal_Unicode continuousSuffix7[] = {0x2e, 0x0};
+static const sal_Unicode continuousTransliteration7[] = {0x0};
+static const sal_Unicode continuousNatNum7[] = {0x0};
+static const sal_Int16 continuousNbOfStyles = 8;
+
+static const sal_Int16 continuousNbOfAttributesPerStyle = 5;
+
+
+static const sal_Unicode* continuousStyle0[] = {
+ continuousPrefix0,
+ continuousNumType0,
+ continuousSuffix0,
+ continuousTransliteration0,
+ continuousNatNum0,
+ 0
+};
+
+
+static const sal_Unicode* continuousStyle1[] = {
+ continuousPrefix1,
+ continuousNumType1,
+ continuousSuffix1,
+ continuousTransliteration1,
+ continuousNatNum1,
+ 0
+};
+
+
+static const sal_Unicode* continuousStyle2[] = {
+ continuousPrefix2,
+ continuousNumType2,
+ continuousSuffix2,
+ continuousTransliteration2,
+ continuousNatNum2,
+ 0
+};
+
+
+static const sal_Unicode* continuousStyle3[] = {
+ continuousPrefix3,
+ continuousNumType3,
+ continuousSuffix3,
+ continuousTransliteration3,
+ continuousNatNum3,
+ 0
+};
+
+
+static const sal_Unicode* continuousStyle4[] = {
+ continuousPrefix4,
+ continuousNumType4,
+ continuousSuffix4,
+ continuousTransliteration4,
+ continuousNatNum4,
+ 0
+};
+
+
+static const sal_Unicode* continuousStyle5[] = {
+ continuousPrefix5,
+ continuousNumType5,
+ continuousSuffix5,
+ continuousTransliteration5,
+ continuousNatNum5,
+ 0
+};
+
+
+static const sal_Unicode* continuousStyle6[] = {
+ continuousPrefix6,
+ continuousNumType6,
+ continuousSuffix6,
+ continuousTransliteration6,
+ continuousNatNum6,
+ 0
+};
+
+
+static const sal_Unicode* continuousStyle7[] = {
+ continuousPrefix7,
+ continuousNumType7,
+ continuousSuffix7,
+ continuousTransliteration7,
+ continuousNatNum7,
+ 0
+};
+
+
+static const sal_Unicode** LCContinuousNumberingLevelsArray[] = {
+ continuousStyle0,
+ continuousStyle1,
+ continuousStyle2,
+ continuousStyle3,
+ continuousStyle4,
+ continuousStyle5,
+ continuousStyle6,
+ continuousStyle7,
+ 0
+};
+
+SAL_DLLPUBLIC_EXPORT const sal_Unicode *** SAL_CALL getContinuousNumberingLevels_en_GH( sal_Int16& nStyles, sal_Int16& nAttributes )
+{
+ nStyles = continuousNbOfStyles;
+ nAttributes = continuousNbOfAttributesPerStyle;
+ return LCContinuousNumberingLevelsArray;
+}
+// ---> OutlineNumbering
+extern const sal_Unicode **** SAL_CALL getOutlineNumberingLevels_en_US(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode **** SAL_CALL getOutlineNumberingLevels_en_GH(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes)
+{
+ return getOutlineNumberingLevels_en_US(nStyles, nLevels, nAttributes);
+}
+} // extern "C"
+
diff --git a/vcl/workben/localestub/localedata_en_GM.cxx b/vcl/workben/localestub/localedata_en_GM.cxx
new file mode 100644
index 000000000..318e82e3f
--- /dev/null
+++ b/vcl/workben/localestub/localedata_en_GM.cxx
@@ -0,0 +1,140 @@
+#include <sal/types.h>
+
+
+#include <stdio.h>
+
+extern "C" {
+
+static const sal_Unicode langID[] = {0x65, 0x6e, 0x0};
+static const sal_Unicode langDefaultName[] = {0x45, 0x6e, 0x67, 0x6c, 0x69, 0x73, 0x68, 0x0};
+static const sal_Unicode countryID[] = {0x47, 0x4d, 0x0};
+static const sal_Unicode countryDefaultName[] = {0x47, 0x61, 0x6d, 0x62, 0x69, 0x61, 0x0};
+static const sal_Unicode Variant[] = {0x0};
+
+static const sal_Unicode* LCInfoArray[] = {
+ langID,
+ langDefaultName,
+ countryID,
+ countryDefaultName,
+ Variant
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLCInfo_en_GM(sal_Int16& count)
+{
+ count = 0;
+ return (sal_Unicode**)LCInfoArray;
+}
+extern sal_Unicode ** SAL_CALL getLocaleItem_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLocaleItem_en_GM(sal_Int16& count)
+{
+ return getLocaleItem_en_GB(count);
+}
+static const sal_Unicode replaceTo0[] = {0x5b, 0x24, 0x44, 0x2d, 0x39, 0x30, 0x30, 0x39, 0x5d, 0x0};
+extern sal_Unicode const * const * SAL_CALL getAllFormats0_en_GB(sal_Int16& count, const sal_Unicode*& from, const sal_Unicode*& to);
+SAL_DLLPUBLIC_EXPORT sal_Unicode const * const * SAL_CALL getAllFormats0_en_GM(sal_Int16& count, const sal_Unicode*& from, const sal_Unicode*& to)
+{
+ to = replaceTo0;
+ const sal_Unicode* tmp;
+ return getAllFormats0_en_GB(count, from, tmp);
+}
+extern sal_Unicode ** SAL_CALL getDateAcceptancePatterns_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getDateAcceptancePatterns_en_GM(sal_Int16& count)
+{
+ return getDateAcceptancePatterns_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getCollatorImplementation_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollatorImplementation_en_GM(sal_Int16& count)
+{
+ return getCollatorImplementation_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getCollationOptions_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollationOptions_en_GM(sal_Int16& count)
+{
+ return getCollationOptions_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getSearchOptions_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getSearchOptions_en_GM(sal_Int16& count)
+{
+ return getSearchOptions_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getIndexAlgorithm_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getIndexAlgorithm_en_GM(sal_Int16& count)
+{
+ return getIndexAlgorithm_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getUnicodeScripts_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getUnicodeScripts_en_GM(sal_Int16& count)
+{
+ return getUnicodeScripts_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getFollowPageWords_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getFollowPageWords_en_GM(sal_Int16& count)
+{
+ return getFollowPageWords_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getAllCalendars_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCalendars_en_GM(sal_Int16& count)
+{
+ return getAllCalendars_en_GB(count);
+}
+static const sal_Unicode defaultCurrency0[] = {1};
+static const sal_Unicode defaultCurrencyUsedInCompatibleFormatCodes0[] = {1};
+static const sal_Unicode defaultCurrencyLegacyOnly0[] = {0};
+static const sal_Unicode currencyID0[] = {0x47, 0x4d, 0x44, 0x0};
+static const sal_Unicode currencySymbol0[] = {0x44, 0x0};
+static const sal_Unicode bankSymbol0[] = {0x47, 0x4d, 0x44, 0x0};
+static const sal_Unicode currencyName0[] = {0x44, 0x61, 0x6c, 0x61, 0x73, 0x69, 0x0};
+static const sal_Unicode currencyDecimalPlaces0[] = {2};
+
+static const sal_Int16 currencyCount = 1;
+
+static const sal_Unicode* currencies[] = {
+ currencyID0,
+ currencySymbol0,
+ bankSymbol0,
+ currencyName0,
+ defaultCurrency0,
+ defaultCurrencyUsedInCompatibleFormatCodes0,
+ currencyDecimalPlaces0,
+ defaultCurrencyLegacyOnly0,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCurrencies_en_GM(sal_Int16& count)
+{
+ count = currencyCount;
+ return (sal_Unicode**)currencies;
+}
+extern sal_Unicode ** SAL_CALL getTransliterations_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getTransliterations_en_GM(sal_Int16& count)
+{
+ return getTransliterations_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getForbiddenCharacters_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getForbiddenCharacters_en_GM(sal_Int16& count)
+{
+ return getForbiddenCharacters_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getBreakIteratorRules_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getBreakIteratorRules_en_GM(sal_Int16& count)
+{
+ return getBreakIteratorRules_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getReservedWords_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getReservedWords_en_GM(sal_Int16& count)
+{
+ return getReservedWords_en_GB(count);
+}
+// ---> ContinuousNumbering
+extern const sal_Unicode *** SAL_CALL getContinuousNumberingLevels_en_GB(sal_Int16& nStyles, sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode *** SAL_CALL getContinuousNumberingLevels_en_GM(sal_Int16& nStyles, sal_Int16& nAttributes)
+{
+ return getContinuousNumberingLevels_en_GB(nStyles, nAttributes);
+}
+// ---> OutlineNumbering
+extern const sal_Unicode **** SAL_CALL getOutlineNumberingLevels_en_GB(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode **** SAL_CALL getOutlineNumberingLevels_en_GM(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes)
+{
+ return getOutlineNumberingLevels_en_GB(nStyles, nLevels, nAttributes);
+}
+} // extern "C"
+
diff --git a/vcl/workben/localestub/localedata_en_IE.cxx b/vcl/workben/localestub/localedata_en_IE.cxx
new file mode 100644
index 000000000..358890fdd
--- /dev/null
+++ b/vcl/workben/localestub/localedata_en_IE.cxx
@@ -0,0 +1,259 @@
+#include <sal/types.h>
+
+
+#include <stdio.h>
+
+extern "C" {
+
+static const sal_Unicode langID[] = {0x65, 0x6e, 0x0};
+static const sal_Unicode langDefaultName[] = {0x45, 0x6e, 0x67, 0x6c, 0x69, 0x73, 0x68, 0x0};
+static const sal_Unicode countryID[] = {0x49, 0x45, 0x0};
+static const sal_Unicode countryDefaultName[] = {0x49, 0x72, 0x65, 0x6c, 0x61, 0x6e, 0x64, 0x0};
+static const sal_Unicode Variant[] = {0x0};
+
+static const sal_Unicode* LCInfoArray[] = {
+ langID,
+ langDefaultName,
+ countryID,
+ countryDefaultName,
+ Variant
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLCInfo_en_IE(sal_Int16& count)
+{
+ count = 0;
+ return (sal_Unicode**)LCInfoArray;
+}
+
+
+static const sal_Unicode LC_CTYPE_Unoid[] = {0x0};
+static const sal_Unicode dateSeparator[] = {0x2f, 0x0};
+static const sal_Unicode thousandSeparator[] = {0x2c, 0x0};
+static const sal_Unicode decimalSeparator[] = {0x2e, 0x0};
+static const sal_Unicode timeSeparator[] = {0x3a, 0x0};
+static const sal_Unicode time100SecSeparator[] = {0x2e, 0x0};
+static const sal_Unicode listSeparator[] = {0x3b, 0x0};
+static const sal_Unicode LongDateDayOfWeekSeparator[] = {0x20, 0x0};
+static const sal_Unicode LongDateDaySeparator[] = {0x20, 0x0};
+static const sal_Unicode LongDateMonthSeparator[] = {0x20, 0x0};
+static const sal_Unicode LongDateYearSeparator[] = {0x20, 0x0};
+static const sal_Unicode quotationStart[] = {0x2018, 0x0};
+static const sal_Unicode quotationEnd[] = {0x2019, 0x0};
+static const sal_Unicode doubleQuotationStart[] = {0x201c, 0x0};
+static const sal_Unicode doubleQuotationEnd[] = {0x201d, 0x0};
+static const sal_Unicode timeAM[] = {0x61, 0x2e, 0x6d, 0x2e, 0x0};
+static const sal_Unicode timePM[] = {0x70, 0x2e, 0x6d, 0x2e, 0x0};
+static const sal_Unicode measurementSystem[] = {0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x0};
+
+static const sal_Unicode* LCType[] = {
+ LC_CTYPE_Unoid,
+ dateSeparator,
+ thousandSeparator,
+ decimalSeparator,
+ timeSeparator,
+ time100SecSeparator,
+ listSeparator,
+ quotationStart,
+ quotationEnd,
+ doubleQuotationStart,
+ doubleQuotationEnd,
+ timeAM,
+ timePM,
+ measurementSystem,
+ LongDateDayOfWeekSeparator,
+ LongDateDaySeparator,
+ LongDateMonthSeparator,
+ LongDateYearSeparator
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLocaleItem_en_IE(sal_Int16& count)
+{
+ count = 0;
+ return (sal_Unicode**)LCType;
+}
+static const sal_Unicode replaceTo0[] = {0x5b, 0x24, 0x49, 0x52, 0xa3, 0x2d, 0x31, 0x38, 0x30, 0x39, 0x5d, 0x0};
+extern sal_Unicode const * const * SAL_CALL getAllFormats0_en_GB(sal_Int16& count, const sal_Unicode*& from, const sal_Unicode*& to);
+SAL_DLLPUBLIC_EXPORT sal_Unicode const * const * SAL_CALL getAllFormats0_en_IE(sal_Int16& count, const sal_Unicode*& from, const sal_Unicode*& to)
+{
+ to = replaceTo0;
+ const sal_Unicode* tmp;
+ return getAllFormats0_en_GB(count, from, tmp);
+}
+extern sal_Unicode ** SAL_CALL getDateAcceptancePatterns_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getDateAcceptancePatterns_en_IE(sal_Int16& count)
+{
+ return getDateAcceptancePatterns_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getCollatorImplementation_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollatorImplementation_en_IE(sal_Int16& count)
+{
+ return getCollatorImplementation_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getCollationOptions_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollationOptions_en_IE(sal_Int16& count)
+{
+ return getCollationOptions_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getSearchOptions_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getSearchOptions_en_IE(sal_Int16& count)
+{
+ return getSearchOptions_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getIndexAlgorithm_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getIndexAlgorithm_en_IE(sal_Int16& count)
+{
+ return getIndexAlgorithm_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getUnicodeScripts_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getUnicodeScripts_en_IE(sal_Int16& count)
+{
+ return getUnicodeScripts_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getFollowPageWords_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getFollowPageWords_en_IE(sal_Int16& count)
+{
+ return getFollowPageWords_en_US(count);
+}
+static const sal_Unicode calendarID0[] = {0x67, 0x72, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x61, 0x6e, 0x0};
+static const sal_Unicode defaultCalendar0[] = {1};
+static const sal_Unicode dayRef0[] = {0x72, 0x65, 0x66, 0x0};
+static const sal_Unicode dayRefName0[] = {0x65, 0x6e, 0x5f, 0x55, 0x53, 0x5f, 0x67, 0x72, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x61, 0x6e, 0x0};
+static const sal_Unicode monthRef0[] = {0x72, 0x65, 0x66, 0x0};
+static const sal_Unicode monthRefName0[] = {0x65, 0x6e, 0x5f, 0x55, 0x53, 0x5f, 0x67, 0x72, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x61, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthRef0[] = {0x72, 0x65, 0x66, 0x0};
+static const sal_Unicode genitiveMonthRefName0[] = {0x65, 0x6e, 0x5f, 0x55, 0x53, 0x5f, 0x67, 0x72, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x61, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthRef0[] = {0x72, 0x65, 0x66, 0x0};
+static const sal_Unicode partitiveMonthRefName0[] = {0x65, 0x6e, 0x5f, 0x55, 0x53, 0x5f, 0x67, 0x72, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x61, 0x6e, 0x0};
+static const sal_Unicode eraRef0[] = {0x72, 0x65, 0x66, 0x0};
+static const sal_Unicode eraRefName0[] = {0x65, 0x6e, 0x5f, 0x55, 0x53, 0x5f, 0x67, 0x72, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x61, 0x6e, 0x0};
+static const sal_Unicode startDayOfWeek0[] = {0x73, 0x75, 0x6e, 0x0};
+static const sal_Unicode minimalDaysInFirstWeek0[] = {1};
+static const sal_Int16 calendarsCount = 1;
+
+static const sal_Unicode nbOfDays[] = {0};
+static const sal_Unicode nbOfMonths[] = {0};
+static const sal_Unicode nbOfGenitiveMonths[] = {0};
+static const sal_Unicode nbOfPartitiveMonths[] = {0};
+static const sal_Unicode nbOfEras[] = {0};
+static const sal_Unicode* calendars[] = {
+ nbOfDays,
+ nbOfMonths,
+ nbOfGenitiveMonths,
+ nbOfPartitiveMonths,
+ nbOfEras,
+ calendarID0,
+ defaultCalendar0,
+ dayRef0,
+ dayRefName0,
+ monthRef0,
+ monthRefName0,
+ genitiveMonthRef0,
+ genitiveMonthRefName0,
+ partitiveMonthRef0,
+ partitiveMonthRefName0,
+ eraRef0,
+ eraRefName0,
+ startDayOfWeek0,
+ minimalDaysInFirstWeek0,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCalendars_en_IE(sal_Int16& count)
+{
+ count = calendarsCount;
+ return (sal_Unicode**)calendars;
+}
+static const sal_Unicode defaultCurrency0[] = {1};
+static const sal_Unicode defaultCurrencyUsedInCompatibleFormatCodes0[] = {0};
+static const sal_Unicode defaultCurrencyLegacyOnly0[] = {0};
+static const sal_Unicode currencyID0[] = {0x45, 0x55, 0x52, 0x0};
+static const sal_Unicode currencySymbol0[] = {0x20ac, 0x0};
+static const sal_Unicode bankSymbol0[] = {0x45, 0x55, 0x52, 0x0};
+static const sal_Unicode currencyName0[] = {0x45, 0x75, 0x72, 0x6f, 0x0};
+static const sal_Unicode currencyDecimalPlaces0[] = {2};
+
+static const sal_Unicode defaultCurrency1[] = {0};
+static const sal_Unicode defaultCurrencyUsedInCompatibleFormatCodes1[] = {1};
+static const sal_Unicode defaultCurrencyLegacyOnly1[] = {0};
+static const sal_Unicode currencyID1[] = {0x49, 0x45, 0x50, 0x0};
+static const sal_Unicode currencySymbol1[] = {0x49, 0x52, 0xa3, 0x0};
+static const sal_Unicode bankSymbol1[] = {0x49, 0x45, 0x50, 0x0};
+static const sal_Unicode currencyName1[] = {0x49, 0x72, 0x69, 0x73, 0x68, 0x20, 0x50, 0x6f, 0x75, 0x6e, 0x64, 0x0};
+static const sal_Unicode currencyDecimalPlaces1[] = {2};
+
+static const sal_Unicode defaultCurrency2[] = {0};
+static const sal_Unicode defaultCurrencyUsedInCompatibleFormatCodes2[] = {0};
+static const sal_Unicode defaultCurrencyLegacyOnly2[] = {0};
+static const sal_Unicode currencyID2[] = {0x47, 0x42, 0x50, 0x0};
+static const sal_Unicode currencySymbol2[] = {0xa3, 0x0};
+static const sal_Unicode bankSymbol2[] = {0x47, 0x42, 0x50, 0x0};
+static const sal_Unicode currencyName2[] = {0x42, 0x72, 0x69, 0x74, 0x69, 0x73, 0x68, 0x20, 0x50, 0x6f, 0x75, 0x6e, 0x64, 0x20, 0x53, 0x74, 0x65, 0x72, 0x6c, 0x69, 0x6e, 0x67, 0x0};
+static const sal_Unicode currencyDecimalPlaces2[] = {2};
+
+static const sal_Int16 currencyCount = 3;
+
+static const sal_Unicode* currencies[] = {
+ currencyID0,
+ currencySymbol0,
+ bankSymbol0,
+ currencyName0,
+ defaultCurrency0,
+ defaultCurrencyUsedInCompatibleFormatCodes0,
+ currencyDecimalPlaces0,
+ defaultCurrencyLegacyOnly0,
+ currencyID1,
+ currencySymbol1,
+ bankSymbol1,
+ currencyName1,
+ defaultCurrency1,
+ defaultCurrencyUsedInCompatibleFormatCodes1,
+ currencyDecimalPlaces1,
+ defaultCurrencyLegacyOnly1,
+ currencyID2,
+ currencySymbol2,
+ bankSymbol2,
+ currencyName2,
+ defaultCurrency2,
+ defaultCurrencyUsedInCompatibleFormatCodes2,
+ currencyDecimalPlaces2,
+ defaultCurrencyLegacyOnly2,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCurrencies_en_IE(sal_Int16& count)
+{
+ count = currencyCount;
+ return (sal_Unicode**)currencies;
+}
+extern sal_Unicode ** SAL_CALL getTransliterations_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getTransliterations_en_IE(sal_Int16& count)
+{
+ return getTransliterations_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getForbiddenCharacters_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getForbiddenCharacters_en_IE(sal_Int16& count)
+{
+ return getForbiddenCharacters_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getBreakIteratorRules_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getBreakIteratorRules_en_IE(sal_Int16& count)
+{
+ return getBreakIteratorRules_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getReservedWords_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getReservedWords_en_IE(sal_Int16& count)
+{
+ return getReservedWords_en_US(count);
+}
+// ---> ContinuousNumbering
+extern const sal_Unicode *** SAL_CALL getContinuousNumberingLevels_en_US(sal_Int16& nStyles, sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode *** SAL_CALL getContinuousNumberingLevels_en_IE(sal_Int16& nStyles, sal_Int16& nAttributes)
+{
+ return getContinuousNumberingLevels_en_US(nStyles, nAttributes);
+}
+// ---> OutlineNumbering
+extern const sal_Unicode **** SAL_CALL getOutlineNumberingLevels_en_US(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode **** SAL_CALL getOutlineNumberingLevels_en_IE(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes)
+{
+ return getOutlineNumberingLevels_en_US(nStyles, nLevels, nAttributes);
+}
+} // extern "C"
+
diff --git a/vcl/workben/localestub/localedata_en_IN.cxx b/vcl/workben/localestub/localedata_en_IN.cxx
new file mode 100644
index 000000000..2fdd21e71
--- /dev/null
+++ b/vcl/workben/localestub/localedata_en_IN.cxx
@@ -0,0 +1,200 @@
+#include <sal/types.h>
+
+
+#include <stdio.h>
+
+extern "C" {
+
+static const sal_Unicode langID[] = {0x65, 0x6e, 0x0};
+static const sal_Unicode langDefaultName[] = {0x45, 0x6e, 0x67, 0x6c, 0x69, 0x73, 0x68, 0x0};
+static const sal_Unicode countryID[] = {0x49, 0x4e, 0x0};
+static const sal_Unicode countryDefaultName[] = {0x49, 0x6e, 0x64, 0x69, 0x61, 0x0};
+static const sal_Unicode Variant[] = {0x0};
+
+static const sal_Unicode* LCInfoArray[] = {
+ langID,
+ langDefaultName,
+ countryID,
+ countryDefaultName,
+ Variant
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLCInfo_en_IN(sal_Int16& count)
+{
+ count = 0;
+ return (sal_Unicode**)LCInfoArray;
+}
+extern sal_Unicode ** SAL_CALL getLocaleItem_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLocaleItem_en_IN(sal_Int16& count)
+{
+ return getLocaleItem_en_GB(count);
+}
+static const sal_Unicode replaceTo0[] = {0x5b, 0x24, 0x20b9, 0x2d, 0x34, 0x30, 0x30, 0x39, 0x5d, 0x0};
+extern sal_Unicode const * const * SAL_CALL getAllFormats0_en_GB(sal_Int16& count, const sal_Unicode*& from, const sal_Unicode*& to);
+SAL_DLLPUBLIC_EXPORT sal_Unicode const * const * SAL_CALL getAllFormats0_en_IN(sal_Int16& count, const sal_Unicode*& from, const sal_Unicode*& to)
+{
+ to = replaceTo0;
+ const sal_Unicode* tmp;
+ return getAllFormats0_en_GB(count, from, tmp);
+}
+extern sal_Unicode ** SAL_CALL getDateAcceptancePatterns_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getDateAcceptancePatterns_en_IN(sal_Int16& count)
+{
+ return getDateAcceptancePatterns_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getCollatorImplementation_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollatorImplementation_en_IN(sal_Int16& count)
+{
+ return getCollatorImplementation_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getCollationOptions_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollationOptions_en_IN(sal_Int16& count)
+{
+ return getCollationOptions_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getSearchOptions_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getSearchOptions_en_IN(sal_Int16& count)
+{
+ return getSearchOptions_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getIndexAlgorithm_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getIndexAlgorithm_en_IN(sal_Int16& count)
+{
+ return getIndexAlgorithm_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getUnicodeScripts_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getUnicodeScripts_en_IN(sal_Int16& count)
+{
+ return getUnicodeScripts_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getFollowPageWords_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getFollowPageWords_en_IN(sal_Int16& count)
+{
+ return getFollowPageWords_en_US(count);
+}
+static const sal_Unicode calendarID0[] = {0x67, 0x72, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x61, 0x6e, 0x0};
+static const sal_Unicode defaultCalendar0[] = {1};
+static const sal_Unicode dayRef0[] = {0x72, 0x65, 0x66, 0x0};
+static const sal_Unicode dayRefName0[] = {0x65, 0x6e, 0x5f, 0x55, 0x53, 0x5f, 0x67, 0x72, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x61, 0x6e, 0x0};
+static const sal_Unicode monthRef0[] = {0x72, 0x65, 0x66, 0x0};
+static const sal_Unicode monthRefName0[] = {0x65, 0x6e, 0x5f, 0x55, 0x53, 0x5f, 0x67, 0x72, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x61, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthRef0[] = {0x72, 0x65, 0x66, 0x0};
+static const sal_Unicode genitiveMonthRefName0[] = {0x65, 0x6e, 0x5f, 0x55, 0x53, 0x5f, 0x67, 0x72, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x61, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthRef0[] = {0x72, 0x65, 0x66, 0x0};
+static const sal_Unicode partitiveMonthRefName0[] = {0x65, 0x6e, 0x5f, 0x55, 0x53, 0x5f, 0x67, 0x72, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x61, 0x6e, 0x0};
+static const sal_Unicode eraRef0[] = {0x72, 0x65, 0x66, 0x0};
+static const sal_Unicode eraRefName0[] = {0x65, 0x6e, 0x5f, 0x55, 0x53, 0x5f, 0x67, 0x72, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x61, 0x6e, 0x0};
+static const sal_Unicode startDayOfWeek0[] = {0x6d, 0x6f, 0x6e, 0x0};
+static const sal_Unicode minimalDaysInFirstWeek0[] = {1};
+static const sal_Int16 calendarsCount = 1;
+
+static const sal_Unicode nbOfDays[] = {0};
+static const sal_Unicode nbOfMonths[] = {0};
+static const sal_Unicode nbOfGenitiveMonths[] = {0};
+static const sal_Unicode nbOfPartitiveMonths[] = {0};
+static const sal_Unicode nbOfEras[] = {0};
+static const sal_Unicode* calendars[] = {
+ nbOfDays,
+ nbOfMonths,
+ nbOfGenitiveMonths,
+ nbOfPartitiveMonths,
+ nbOfEras,
+ calendarID0,
+ defaultCalendar0,
+ dayRef0,
+ dayRefName0,
+ monthRef0,
+ monthRefName0,
+ genitiveMonthRef0,
+ genitiveMonthRefName0,
+ partitiveMonthRef0,
+ partitiveMonthRefName0,
+ eraRef0,
+ eraRefName0,
+ startDayOfWeek0,
+ minimalDaysInFirstWeek0,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCalendars_en_IN(sal_Int16& count)
+{
+ count = calendarsCount;
+ return (sal_Unicode**)calendars;
+}
+static const sal_Unicode defaultCurrency0[] = {1};
+static const sal_Unicode defaultCurrencyUsedInCompatibleFormatCodes0[] = {1};
+static const sal_Unicode defaultCurrencyLegacyOnly0[] = {0};
+static const sal_Unicode currencyID0[] = {0x49, 0x4e, 0x52, 0x0};
+static const sal_Unicode currencySymbol0[] = {0x20b9, 0x0};
+static const sal_Unicode bankSymbol0[] = {0x49, 0x4e, 0x52, 0x0};
+static const sal_Unicode currencyName0[] = {0x52, 0x75, 0x70, 0x65, 0x65, 0x0};
+static const sal_Unicode currencyDecimalPlaces0[] = {2};
+
+static const sal_Unicode defaultCurrency1[] = {0};
+static const sal_Unicode defaultCurrencyUsedInCompatibleFormatCodes1[] = {0};
+static const sal_Unicode defaultCurrencyLegacyOnly1[] = {1};
+static const sal_Unicode currencyID1[] = {0x49, 0x4e, 0x52, 0x0};
+static const sal_Unicode currencySymbol1[] = {0x52, 0x73, 0x2e, 0x0};
+static const sal_Unicode bankSymbol1[] = {0x49, 0x4e, 0x52, 0x0};
+static const sal_Unicode currencyName1[] = {0x49, 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x20, 0x52, 0x75, 0x70, 0x65, 0x65, 0x0};
+static const sal_Unicode currencyDecimalPlaces1[] = {2};
+
+static const sal_Int16 currencyCount = 2;
+
+static const sal_Unicode* currencies[] = {
+ currencyID0,
+ currencySymbol0,
+ bankSymbol0,
+ currencyName0,
+ defaultCurrency0,
+ defaultCurrencyUsedInCompatibleFormatCodes0,
+ currencyDecimalPlaces0,
+ defaultCurrencyLegacyOnly0,
+ currencyID1,
+ currencySymbol1,
+ bankSymbol1,
+ currencyName1,
+ defaultCurrency1,
+ defaultCurrencyUsedInCompatibleFormatCodes1,
+ currencyDecimalPlaces1,
+ defaultCurrencyLegacyOnly1,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCurrencies_en_IN(sal_Int16& count)
+{
+ count = currencyCount;
+ return (sal_Unicode**)currencies;
+}
+extern sal_Unicode ** SAL_CALL getTransliterations_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getTransliterations_en_IN(sal_Int16& count)
+{
+ return getTransliterations_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getForbiddenCharacters_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getForbiddenCharacters_en_IN(sal_Int16& count)
+{
+ return getForbiddenCharacters_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getBreakIteratorRules_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getBreakIteratorRules_en_IN(sal_Int16& count)
+{
+ return getBreakIteratorRules_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getReservedWords_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getReservedWords_en_IN(sal_Int16& count)
+{
+ return getReservedWords_en_US(count);
+}
+// ---> ContinuousNumbering
+extern const sal_Unicode *** SAL_CALL getContinuousNumberingLevels_en_US(sal_Int16& nStyles, sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode *** SAL_CALL getContinuousNumberingLevels_en_IN(sal_Int16& nStyles, sal_Int16& nAttributes)
+{
+ return getContinuousNumberingLevels_en_US(nStyles, nAttributes);
+}
+// ---> OutlineNumbering
+extern const sal_Unicode **** SAL_CALL getOutlineNumberingLevels_en_US(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode **** SAL_CALL getOutlineNumberingLevels_en_IN(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes)
+{
+ return getOutlineNumberingLevels_en_US(nStyles, nLevels, nAttributes);
+}
+} // extern "C"
+
diff --git a/vcl/workben/localestub/localedata_en_JM.cxx b/vcl/workben/localestub/localedata_en_JM.cxx
new file mode 100644
index 000000000..bcd6245e6
--- /dev/null
+++ b/vcl/workben/localestub/localedata_en_JM.cxx
@@ -0,0 +1,1263 @@
+#include <sal/types.h>
+
+
+#include <stdio.h>
+
+extern "C" {
+
+static const sal_Unicode langID[] = {0x65, 0x6e, 0x0};
+static const sal_Unicode langDefaultName[] = {0x45, 0x6e, 0x67, 0x6c, 0x69, 0x73, 0x68, 0x0};
+static const sal_Unicode countryID[] = {0x4a, 0x4d, 0x0};
+static const sal_Unicode countryDefaultName[] = {0x4a, 0x61, 0x6d, 0x61, 0x69, 0x63, 0x61, 0x0};
+static const sal_Unicode Variant[] = {0x0};
+
+static const sal_Unicode* LCInfoArray[] = {
+ langID,
+ langDefaultName,
+ countryID,
+ countryDefaultName,
+ Variant
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLCInfo_en_JM(sal_Int16& count)
+{
+ count = 0;
+ return (sal_Unicode**)LCInfoArray;
+}
+
+
+static const sal_Unicode LC_CTYPE_Unoid[] = {0x0};
+static const sal_Unicode dateSeparator[] = {0x2f, 0x0};
+static const sal_Unicode thousandSeparator[] = {0x2c, 0x0};
+static const sal_Unicode decimalSeparator[] = {0x2e, 0x0};
+static const sal_Unicode timeSeparator[] = {0x3a, 0x0};
+static const sal_Unicode time100SecSeparator[] = {0x2e, 0x0};
+static const sal_Unicode listSeparator[] = {0x3b, 0x0};
+static const sal_Unicode LongDateDayOfWeekSeparator[] = {0x2c, 0x20, 0x0};
+static const sal_Unicode LongDateDaySeparator[] = {0x20, 0x0};
+static const sal_Unicode LongDateMonthSeparator[] = {0x2c, 0x0};
+static const sal_Unicode LongDateYearSeparator[] = {0x20, 0x0};
+static const sal_Unicode quotationStart[] = {0x2018, 0x0};
+static const sal_Unicode quotationEnd[] = {0x2019, 0x0};
+static const sal_Unicode doubleQuotationStart[] = {0x201c, 0x0};
+static const sal_Unicode doubleQuotationEnd[] = {0x201d, 0x0};
+static const sal_Unicode timeAM[] = {0x41, 0x4d, 0x0};
+static const sal_Unicode timePM[] = {0x50, 0x4d, 0x0};
+static const sal_Unicode measurementSystem[] = {0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x0};
+
+static const sal_Unicode* LCType[] = {
+ LC_CTYPE_Unoid,
+ dateSeparator,
+ thousandSeparator,
+ decimalSeparator,
+ timeSeparator,
+ time100SecSeparator,
+ listSeparator,
+ quotationStart,
+ quotationEnd,
+ doubleQuotationStart,
+ doubleQuotationEnd,
+ timeAM,
+ timePM,
+ measurementSystem,
+ LongDateDayOfWeekSeparator,
+ LongDateDaySeparator,
+ LongDateMonthSeparator,
+ LongDateYearSeparator
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLocaleItem_en_JM(sal_Int16& count)
+{
+ count = 0;
+ return (sal_Unicode**)LCType;
+}
+static const sal_Unicode replaceFrom0[] = {0x0};
+static const sal_Unicode replaceTo0[] = {0x0};
+static const sal_Unicode FormatKey0[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement0[] = {1};
+static const sal_Unicode FormatType0[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage0[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex0[] = {0};
+static const sal_Unicode FormatCode0[] = {0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x0};
+static const sal_Unicode FormatDefaultName0[] = {0x0};
+static const sal_Unicode FormatKey1[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement1[] = {1};
+static const sal_Unicode FormatType1[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage1[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex1[] = {1};
+static const sal_Unicode FormatCode1[] = {0x30, 0x0};
+static const sal_Unicode FormatDefaultName1[] = {0x0};
+static const sal_Unicode FormatKey2[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement2[] = {0};
+static const sal_Unicode FormatType2[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage2[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex2[] = {2};
+static const sal_Unicode FormatCode2[] = {0x30, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName2[] = {0x0};
+static const sal_Unicode FormatKey3[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement3[] = {0};
+static const sal_Unicode FormatType3[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage3[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex3[] = {3};
+static const sal_Unicode FormatCode3[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName3[] = {0x0};
+static const sal_Unicode FormatKey4[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement4[] = {0};
+static const sal_Unicode FormatType4[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage4[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex4[] = {4};
+static const sal_Unicode FormatCode4[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName4[] = {0x0};
+static const sal_Unicode FormatKey5[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement5[] = {0};
+static const sal_Unicode FormatType5[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage5[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex5[] = {5};
+static const sal_Unicode FormatCode5[] = {0x23, 0x2c, 0x23, 0x23, 0x23, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName5[] = {0x0};
+static const sal_Unicode FormatKey6[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x37, 0x0};
+static const sal_Unicode defaultFormatElement6[] = {0};
+static const sal_Unicode FormatType6[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage6[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex6[] = {70};
+static const sal_Unicode FormatCode6[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x5f, 0x29, 0x3b, 0x28, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x29, 0x0};
+static const sal_Unicode FormatDefaultName6[] = {0x0};
+static const sal_Unicode FormatKey7[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x38, 0x0};
+static const sal_Unicode defaultFormatElement7[] = {0};
+static const sal_Unicode FormatType7[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage7[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex7[] = {71};
+static const sal_Unicode FormatCode7[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x5f, 0x29, 0x3b, 0x28, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x29, 0x0};
+static const sal_Unicode FormatDefaultName7[] = {0x0};
+static const sal_Unicode FormatKey8[] = {0x53, 0x63, 0x69, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x63, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement8[] = {0};
+static const sal_Unicode FormatType8[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage8[] = {0x53, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, 0x43, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex8[] = {6};
+static const sal_Unicode FormatCode8[] = {0x30, 0x2e, 0x30, 0x30, 0x45, 0x2b, 0x30, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName8[] = {0x0};
+static const sal_Unicode FormatKey9[] = {0x53, 0x63, 0x69, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x63, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement9[] = {1};
+static const sal_Unicode FormatType9[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage9[] = {0x53, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, 0x43, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex9[] = {7};
+static const sal_Unicode FormatCode9[] = {0x30, 0x2e, 0x30, 0x30, 0x45, 0x2b, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName9[] = {0x0};
+static const sal_Unicode FormatKey10[] = {0x53, 0x63, 0x69, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x63, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement10[] = {0};
+static const sal_Unicode FormatType10[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage10[] = {0x53, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, 0x43, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex10[] = {78};
+static const sal_Unicode FormatCode10[] = {0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x45, 0x2b, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName10[] = {0x0};
+static const sal_Unicode FormatKey11[] = {0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement11[] = {1};
+static const sal_Unicode FormatType11[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage11[] = {0x50, 0x45, 0x52, 0x43, 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex11[] = {8};
+static const sal_Unicode FormatCode11[] = {0x30, 0x25, 0x0};
+static const sal_Unicode FormatDefaultName11[] = {0x0};
+static const sal_Unicode FormatKey12[] = {0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement12[] = {1};
+static const sal_Unicode FormatType12[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage12[] = {0x50, 0x45, 0x52, 0x43, 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex12[] = {9};
+static const sal_Unicode FormatCode12[] = {0x30, 0x2e, 0x30, 0x30, 0x25, 0x0};
+static const sal_Unicode FormatDefaultName12[] = {0x0};
+static const sal_Unicode FormatKey13[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement13[] = {1};
+static const sal_Unicode FormatType13[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage13[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex13[] = {12};
+static const sal_Unicode FormatCode13[] = {0x5b, 0x24, 0x4a, 0x24, 0x2d, 0x32, 0x30, 0x30, 0x39, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x3b, 0x2d, 0x5b, 0x24, 0x4a, 0x24, 0x2d, 0x32, 0x30, 0x30, 0x39, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName13[] = {0x0};
+static const sal_Unicode FormatKey14[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement14[] = {0};
+static const sal_Unicode FormatType14[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage14[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex14[] = {13};
+static const sal_Unicode FormatCode14[] = {0x5b, 0x24, 0x4a, 0x24, 0x2d, 0x32, 0x30, 0x30, 0x39, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x3b, 0x2d, 0x5b, 0x24, 0x4a, 0x24, 0x2d, 0x32, 0x30, 0x30, 0x39, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName14[] = {0x0};
+static const sal_Unicode FormatKey15[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement15[] = {0};
+static const sal_Unicode FormatType15[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage15[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex15[] = {14};
+static const sal_Unicode FormatCode15[] = {0x5b, 0x24, 0x4a, 0x24, 0x2d, 0x32, 0x30, 0x30, 0x39, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x3b, 0x5b, 0x52, 0x45, 0x44, 0x5d, 0x2d, 0x5b, 0x24, 0x4a, 0x24, 0x2d, 0x32, 0x30, 0x30, 0x39, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName15[] = {0x0};
+static const sal_Unicode FormatKey16[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement16[] = {1};
+static const sal_Unicode FormatType16[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage16[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex16[] = {15};
+static const sal_Unicode FormatCode16[] = {0x5b, 0x24, 0x4a, 0x24, 0x2d, 0x32, 0x30, 0x30, 0x39, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x3b, 0x5b, 0x52, 0x45, 0x44, 0x5d, 0x2d, 0x5b, 0x24, 0x4a, 0x24, 0x2d, 0x32, 0x30, 0x30, 0x39, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName16[] = {0x0};
+static const sal_Unicode FormatKey17[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement17[] = {0};
+static const sal_Unicode FormatType17[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage17[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex17[] = {16};
+static const sal_Unicode FormatCode17[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x20, 0x43, 0x43, 0x43, 0x0};
+static const sal_Unicode FormatDefaultName17[] = {0x0};
+static const sal_Unicode FormatKey18[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement18[] = {0};
+static const sal_Unicode FormatType18[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage18[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex18[] = {17};
+static const sal_Unicode FormatCode18[] = {0x5b, 0x24, 0x4a, 0x24, 0x2d, 0x32, 0x30, 0x30, 0x39, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x2d, 0x2d, 0x3b, 0x5b, 0x52, 0x45, 0x44, 0x5d, 0x2d, 0x5b, 0x24, 0x4a, 0x24, 0x2d, 0x32, 0x30, 0x30, 0x39, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x2d, 0x2d, 0x0};
+static const sal_Unicode FormatDefaultName18[] = {0x0};
+static const sal_Unicode FormatKey19[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement19[] = {1};
+static const sal_Unicode FormatType19[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage19[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex19[] = {18};
+static const sal_Unicode FormatCode19[] = {0x44, 0x44, 0x2f, 0x4d, 0x4d, 0x2f, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName19[] = {0x0};
+static const sal_Unicode FormatKey20[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x39, 0x0};
+static const sal_Unicode defaultFormatElement20[] = {1};
+static const sal_Unicode FormatType20[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage20[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex20[] = {19};
+static const sal_Unicode FormatCode20[] = {0x4e, 0x4e, 0x4e, 0x4e, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x44, 0x44, 0x2c, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName20[] = {0x0};
+static const sal_Unicode FormatKey21[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x38, 0x0};
+static const sal_Unicode defaultFormatElement21[] = {1};
+static const sal_Unicode FormatType21[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage21[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex21[] = {20};
+static const sal_Unicode FormatCode21[] = {0x44, 0x44, 0x2f, 0x4d, 0x4d, 0x2f, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName21[] = {0x0};
+static const sal_Unicode FormatKey22[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x37, 0x0};
+static const sal_Unicode defaultFormatElement22[] = {0};
+static const sal_Unicode FormatType22[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage22[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex22[] = {21};
+static const sal_Unicode FormatCode22[] = {0x44, 0x44, 0x2f, 0x4d, 0x4d, 0x2f, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName22[] = {0x0};
+static const sal_Unicode FormatKey23[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x30, 0x0};
+static const sal_Unicode defaultFormatElement23[] = {0};
+static const sal_Unicode FormatType23[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage23[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex23[] = {22};
+static const sal_Unicode FormatCode23[] = {0x4d, 0x4d, 0x4d, 0x20, 0x44, 0x2c, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName23[] = {0x0};
+static const sal_Unicode FormatKey24[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement24[] = {0};
+static const sal_Unicode FormatType24[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage24[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex24[] = {23};
+static const sal_Unicode FormatCode24[] = {0x4d, 0x4d, 0x4d, 0x20, 0x44, 0x2c, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName24[] = {0x0};
+static const sal_Unicode FormatKey25[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement25[] = {0};
+static const sal_Unicode FormatType25[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage25[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex25[] = {24};
+static const sal_Unicode FormatCode25[] = {0x44, 0x2e, 0x20, 0x4d, 0x4d, 0x4d, 0x2e, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName25[] = {0x0};
+static const sal_Unicode FormatKey26[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement26[] = {0};
+static const sal_Unicode FormatType26[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage26[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex26[] = {25};
+static const sal_Unicode FormatCode26[] = {0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x44, 0x2c, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName26[] = {0x0};
+static const sal_Unicode FormatKey27[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x37, 0x0};
+static const sal_Unicode defaultFormatElement27[] = {0};
+static const sal_Unicode FormatType27[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage27[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex27[] = {26};
+static const sal_Unicode FormatCode27[] = {0x44, 0x2e, 0x20, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName27[] = {0x0};
+static const sal_Unicode FormatKey28[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement28[] = {0};
+static const sal_Unicode FormatType28[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage28[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex28[] = {27};
+static const sal_Unicode FormatCode28[] = {0x4e, 0x4e, 0x2c, 0x4d, 0x4d, 0x4d, 0x20, 0x44, 0x2c, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName28[] = {0x0};
+static const sal_Unicode FormatKey29[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement29[] = {0};
+static const sal_Unicode FormatType29[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage29[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex29[] = {28};
+static const sal_Unicode FormatCode29[] = {0x4e, 0x4e, 0x20, 0x44, 0x44, 0x2f, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName29[] = {0x0};
+static const sal_Unicode FormatKey30[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement30[] = {0};
+static const sal_Unicode FormatType30[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage30[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex30[] = {29};
+static const sal_Unicode FormatCode30[] = {0x4e, 0x4e, 0x2c, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x44, 0x2c, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName30[] = {0x0};
+static const sal_Unicode FormatKey31[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement31[] = {0};
+static const sal_Unicode FormatType31[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage31[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex31[] = {30};
+static const sal_Unicode FormatCode31[] = {0x4e, 0x4e, 0x4e, 0x4e, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x44, 0x2c, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName31[] = {0x0};
+static const sal_Unicode FormatKey32[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x38, 0x0};
+static const sal_Unicode defaultFormatElement32[] = {0};
+static const sal_Unicode FormatType32[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage32[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex32[] = {31};
+static const sal_Unicode FormatCode32[] = {0x4d, 0x4d, 0x2d, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName32[] = {0x0};
+static const sal_Unicode FormatKey33[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x39, 0x0};
+static const sal_Unicode defaultFormatElement33[] = {0};
+static const sal_Unicode FormatType33[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage33[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex33[] = {32};
+static const sal_Unicode FormatCode33[] = {0x59, 0x59, 0x2d, 0x4d, 0x4d, 0x2d, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName33[] = {0x0};
+static const sal_Unicode FormatKey34[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x30, 0x0};
+static const sal_Unicode defaultFormatElement34[] = {0};
+static const sal_Unicode FormatType34[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage34[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex34[] = {33};
+static const sal_Unicode FormatCode34[] = {0x59, 0x59, 0x59, 0x59, 0x2d, 0x4d, 0x4d, 0x2d, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName34[] = {0x49, 0x53, 0x4f, 0x20, 0x38, 0x36, 0x30, 0x31, 0x0};
+static const sal_Unicode FormatKey35[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement35[] = {0};
+static const sal_Unicode FormatType35[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage35[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex35[] = {34};
+static const sal_Unicode FormatCode35[] = {0x4d, 0x4d, 0x2f, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName35[] = {0x0};
+static const sal_Unicode FormatKey36[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement36[] = {0};
+static const sal_Unicode FormatType36[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage36[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex36[] = {35};
+static const sal_Unicode FormatCode36[] = {0x44, 0x44, 0x2f, 0x4d, 0x4d, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName36[] = {0x0};
+static const sal_Unicode FormatKey37[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement37[] = {0};
+static const sal_Unicode FormatType37[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage37[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex37[] = {36};
+static const sal_Unicode FormatCode37[] = {0x4d, 0x4d, 0x4d, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName37[] = {0x0};
+static const sal_Unicode FormatKey38[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement38[] = {0};
+static const sal_Unicode FormatType38[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage38[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex38[] = {37};
+static const sal_Unicode FormatCode38[] = {0x51, 0x51, 0x20, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName38[] = {0x0};
+static const sal_Unicode FormatKey39[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement39[] = {0};
+static const sal_Unicode FormatType39[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage39[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex39[] = {38};
+static const sal_Unicode FormatCode39[] = {0x57, 0x57, 0x0};
+static const sal_Unicode FormatDefaultName39[] = {0x0};
+static const sal_Unicode FormatKey40[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement40[] = {1};
+static const sal_Unicode FormatType40[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage40[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex40[] = {39};
+static const sal_Unicode FormatCode40[] = {0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName40[] = {0x0};
+static const sal_Unicode FormatKey41[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement41[] = {1};
+static const sal_Unicode FormatType41[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage41[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex41[] = {40};
+static const sal_Unicode FormatCode41[] = {0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x0};
+static const sal_Unicode FormatDefaultName41[] = {0x0};
+static const sal_Unicode FormatKey42[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement42[] = {0};
+static const sal_Unicode FormatType42[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage42[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex42[] = {41};
+static const sal_Unicode FormatCode42[] = {0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x20, 0x41, 0x4d, 0x2f, 0x50, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName42[] = {0x0};
+static const sal_Unicode FormatKey43[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement43[] = {0};
+static const sal_Unicode FormatType43[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage43[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex43[] = {42};
+static const sal_Unicode FormatCode43[] = {0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x20, 0x41, 0x4d, 0x2f, 0x50, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName43[] = {0x0};
+static const sal_Unicode FormatKey44[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement44[] = {0};
+static const sal_Unicode FormatType44[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage44[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex44[] = {43};
+static const sal_Unicode FormatCode44[] = {0x5b, 0x48, 0x48, 0x5d, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x0};
+static const sal_Unicode FormatDefaultName44[] = {0x0};
+static const sal_Unicode FormatKey45[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement45[] = {0};
+static const sal_Unicode FormatType45[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage45[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex45[] = {44};
+static const sal_Unicode FormatCode45[] = {0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName45[] = {0x0};
+static const sal_Unicode FormatKey46[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x37, 0x0};
+static const sal_Unicode defaultFormatElement46[] = {0};
+static const sal_Unicode FormatType46[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage46[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex46[] = {45};
+static const sal_Unicode FormatCode46[] = {0x5b, 0x48, 0x48, 0x5d, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName46[] = {0x0};
+static const sal_Unicode FormatKey47[] = {0x44, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement47[] = {1};
+static const sal_Unicode FormatType47[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage47[] = {0x44, 0x41, 0x54, 0x45, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex47[] = {46};
+static const sal_Unicode FormatCode47[] = {0x44, 0x44, 0x2f, 0x4d, 0x4d, 0x2f, 0x59, 0x59, 0x20, 0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName47[] = {0x0};
+static const sal_Unicode FormatKey48[] = {0x44, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement48[] = {0};
+static const sal_Unicode FormatType48[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage48[] = {0x44, 0x41, 0x54, 0x45, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex48[] = {47};
+static const sal_Unicode FormatCode48[] = {0x44, 0x44, 0x2f, 0x4d, 0x4d, 0x2f, 0x59, 0x59, 0x59, 0x59, 0x20, 0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x0};
+static const sal_Unicode FormatDefaultName48[] = {0x0};
+
+static const sal_Int16 FormatElementsCount0 = 49;
+static const sal_Unicode* FormatElementsArray0[] = {
+ FormatCode0,
+ FormatDefaultName0,
+ FormatKey0,
+ FormatType0,
+ FormatUsage0,
+ Formatindex0,
+ defaultFormatElement0,
+ FormatCode1,
+ FormatDefaultName1,
+ FormatKey1,
+ FormatType1,
+ FormatUsage1,
+ Formatindex1,
+ defaultFormatElement1,
+ FormatCode2,
+ FormatDefaultName2,
+ FormatKey2,
+ FormatType2,
+ FormatUsage2,
+ Formatindex2,
+ defaultFormatElement2,
+ FormatCode3,
+ FormatDefaultName3,
+ FormatKey3,
+ FormatType3,
+ FormatUsage3,
+ Formatindex3,
+ defaultFormatElement3,
+ FormatCode4,
+ FormatDefaultName4,
+ FormatKey4,
+ FormatType4,
+ FormatUsage4,
+ Formatindex4,
+ defaultFormatElement4,
+ FormatCode5,
+ FormatDefaultName5,
+ FormatKey5,
+ FormatType5,
+ FormatUsage5,
+ Formatindex5,
+ defaultFormatElement5,
+ FormatCode6,
+ FormatDefaultName6,
+ FormatKey6,
+ FormatType6,
+ FormatUsage6,
+ Formatindex6,
+ defaultFormatElement6,
+ FormatCode7,
+ FormatDefaultName7,
+ FormatKey7,
+ FormatType7,
+ FormatUsage7,
+ Formatindex7,
+ defaultFormatElement7,
+ FormatCode8,
+ FormatDefaultName8,
+ FormatKey8,
+ FormatType8,
+ FormatUsage8,
+ Formatindex8,
+ defaultFormatElement8,
+ FormatCode9,
+ FormatDefaultName9,
+ FormatKey9,
+ FormatType9,
+ FormatUsage9,
+ Formatindex9,
+ defaultFormatElement9,
+ FormatCode10,
+ FormatDefaultName10,
+ FormatKey10,
+ FormatType10,
+ FormatUsage10,
+ Formatindex10,
+ defaultFormatElement10,
+ FormatCode11,
+ FormatDefaultName11,
+ FormatKey11,
+ FormatType11,
+ FormatUsage11,
+ Formatindex11,
+ defaultFormatElement11,
+ FormatCode12,
+ FormatDefaultName12,
+ FormatKey12,
+ FormatType12,
+ FormatUsage12,
+ Formatindex12,
+ defaultFormatElement12,
+ FormatCode13,
+ FormatDefaultName13,
+ FormatKey13,
+ FormatType13,
+ FormatUsage13,
+ Formatindex13,
+ defaultFormatElement13,
+ FormatCode14,
+ FormatDefaultName14,
+ FormatKey14,
+ FormatType14,
+ FormatUsage14,
+ Formatindex14,
+ defaultFormatElement14,
+ FormatCode15,
+ FormatDefaultName15,
+ FormatKey15,
+ FormatType15,
+ FormatUsage15,
+ Formatindex15,
+ defaultFormatElement15,
+ FormatCode16,
+ FormatDefaultName16,
+ FormatKey16,
+ FormatType16,
+ FormatUsage16,
+ Formatindex16,
+ defaultFormatElement16,
+ FormatCode17,
+ FormatDefaultName17,
+ FormatKey17,
+ FormatType17,
+ FormatUsage17,
+ Formatindex17,
+ defaultFormatElement17,
+ FormatCode18,
+ FormatDefaultName18,
+ FormatKey18,
+ FormatType18,
+ FormatUsage18,
+ Formatindex18,
+ defaultFormatElement18,
+ FormatCode19,
+ FormatDefaultName19,
+ FormatKey19,
+ FormatType19,
+ FormatUsage19,
+ Formatindex19,
+ defaultFormatElement19,
+ FormatCode20,
+ FormatDefaultName20,
+ FormatKey20,
+ FormatType20,
+ FormatUsage20,
+ Formatindex20,
+ defaultFormatElement20,
+ FormatCode21,
+ FormatDefaultName21,
+ FormatKey21,
+ FormatType21,
+ FormatUsage21,
+ Formatindex21,
+ defaultFormatElement21,
+ FormatCode22,
+ FormatDefaultName22,
+ FormatKey22,
+ FormatType22,
+ FormatUsage22,
+ Formatindex22,
+ defaultFormatElement22,
+ FormatCode23,
+ FormatDefaultName23,
+ FormatKey23,
+ FormatType23,
+ FormatUsage23,
+ Formatindex23,
+ defaultFormatElement23,
+ FormatCode24,
+ FormatDefaultName24,
+ FormatKey24,
+ FormatType24,
+ FormatUsage24,
+ Formatindex24,
+ defaultFormatElement24,
+ FormatCode25,
+ FormatDefaultName25,
+ FormatKey25,
+ FormatType25,
+ FormatUsage25,
+ Formatindex25,
+ defaultFormatElement25,
+ FormatCode26,
+ FormatDefaultName26,
+ FormatKey26,
+ FormatType26,
+ FormatUsage26,
+ Formatindex26,
+ defaultFormatElement26,
+ FormatCode27,
+ FormatDefaultName27,
+ FormatKey27,
+ FormatType27,
+ FormatUsage27,
+ Formatindex27,
+ defaultFormatElement27,
+ FormatCode28,
+ FormatDefaultName28,
+ FormatKey28,
+ FormatType28,
+ FormatUsage28,
+ Formatindex28,
+ defaultFormatElement28,
+ FormatCode29,
+ FormatDefaultName29,
+ FormatKey29,
+ FormatType29,
+ FormatUsage29,
+ Formatindex29,
+ defaultFormatElement29,
+ FormatCode30,
+ FormatDefaultName30,
+ FormatKey30,
+ FormatType30,
+ FormatUsage30,
+ Formatindex30,
+ defaultFormatElement30,
+ FormatCode31,
+ FormatDefaultName31,
+ FormatKey31,
+ FormatType31,
+ FormatUsage31,
+ Formatindex31,
+ defaultFormatElement31,
+ FormatCode32,
+ FormatDefaultName32,
+ FormatKey32,
+ FormatType32,
+ FormatUsage32,
+ Formatindex32,
+ defaultFormatElement32,
+ FormatCode33,
+ FormatDefaultName33,
+ FormatKey33,
+ FormatType33,
+ FormatUsage33,
+ Formatindex33,
+ defaultFormatElement33,
+ FormatCode34,
+ FormatDefaultName34,
+ FormatKey34,
+ FormatType34,
+ FormatUsage34,
+ Formatindex34,
+ defaultFormatElement34,
+ FormatCode35,
+ FormatDefaultName35,
+ FormatKey35,
+ FormatType35,
+ FormatUsage35,
+ Formatindex35,
+ defaultFormatElement35,
+ FormatCode36,
+ FormatDefaultName36,
+ FormatKey36,
+ FormatType36,
+ FormatUsage36,
+ Formatindex36,
+ defaultFormatElement36,
+ FormatCode37,
+ FormatDefaultName37,
+ FormatKey37,
+ FormatType37,
+ FormatUsage37,
+ Formatindex37,
+ defaultFormatElement37,
+ FormatCode38,
+ FormatDefaultName38,
+ FormatKey38,
+ FormatType38,
+ FormatUsage38,
+ Formatindex38,
+ defaultFormatElement38,
+ FormatCode39,
+ FormatDefaultName39,
+ FormatKey39,
+ FormatType39,
+ FormatUsage39,
+ Formatindex39,
+ defaultFormatElement39,
+ FormatCode40,
+ FormatDefaultName40,
+ FormatKey40,
+ FormatType40,
+ FormatUsage40,
+ Formatindex40,
+ defaultFormatElement40,
+ FormatCode41,
+ FormatDefaultName41,
+ FormatKey41,
+ FormatType41,
+ FormatUsage41,
+ Formatindex41,
+ defaultFormatElement41,
+ FormatCode42,
+ FormatDefaultName42,
+ FormatKey42,
+ FormatType42,
+ FormatUsage42,
+ Formatindex42,
+ defaultFormatElement42,
+ FormatCode43,
+ FormatDefaultName43,
+ FormatKey43,
+ FormatType43,
+ FormatUsage43,
+ Formatindex43,
+ defaultFormatElement43,
+ FormatCode44,
+ FormatDefaultName44,
+ FormatKey44,
+ FormatType44,
+ FormatUsage44,
+ Formatindex44,
+ defaultFormatElement44,
+ FormatCode45,
+ FormatDefaultName45,
+ FormatKey45,
+ FormatType45,
+ FormatUsage45,
+ Formatindex45,
+ defaultFormatElement45,
+ FormatCode46,
+ FormatDefaultName46,
+ FormatKey46,
+ FormatType46,
+ FormatUsage46,
+ Formatindex46,
+ defaultFormatElement46,
+ FormatCode47,
+ FormatDefaultName47,
+ FormatKey47,
+ FormatType47,
+ FormatUsage47,
+ Formatindex47,
+ defaultFormatElement47,
+ FormatCode48,
+ FormatDefaultName48,
+ FormatKey48,
+ FormatType48,
+ FormatUsage48,
+ Formatindex48,
+ defaultFormatElement48,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode const * const * SAL_CALL getAllFormats0_en_JM(sal_Int16& count, const sal_Unicode*& from, const sal_Unicode*& to)
+{
+ count = FormatElementsCount0;
+ from = replaceFrom0;
+ to = replaceTo0;
+ return (sal_Unicode**)FormatElementsArray0;
+}
+static const sal_Unicode DateAcceptancePattern0[] = {0x44, 0x2f, 0x4d, 0x2f, 0x59, 0x0};
+static const sal_Unicode DateAcceptancePattern1[] = {0x44, 0x2f, 0x4d, 0x0};
+static const sal_Int16 DateAcceptancePatternsCount = 2;
+static const sal_Unicode* DateAcceptancePatternsArray[] = {
+ DateAcceptancePattern0,
+ DateAcceptancePattern1,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getDateAcceptancePatterns_en_JM(sal_Int16& count)
+{
+ count = DateAcceptancePatternsCount;
+ return (sal_Unicode**)DateAcceptancePatternsArray;
+}
+extern sal_Unicode ** SAL_CALL getCollatorImplementation_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollatorImplementation_en_JM(sal_Int16& count)
+{
+ return getCollatorImplementation_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getCollationOptions_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollationOptions_en_JM(sal_Int16& count)
+{
+ return getCollationOptions_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getSearchOptions_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getSearchOptions_en_JM(sal_Int16& count)
+{
+ return getSearchOptions_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getIndexAlgorithm_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getIndexAlgorithm_en_JM(sal_Int16& count)
+{
+ return getIndexAlgorithm_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getUnicodeScripts_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getUnicodeScripts_en_JM(sal_Int16& count)
+{
+ return getUnicodeScripts_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getFollowPageWords_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getFollowPageWords_en_JM(sal_Int16& count)
+{
+ return getFollowPageWords_en_US(count);
+}
+static const sal_Unicode calendarID0[] = {0x67, 0x72, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x61, 0x6e, 0x0};
+static const sal_Unicode defaultCalendar0[] = {1};
+static const sal_Unicode dayID00[] = {0x73, 0x75, 0x6e, 0x0};
+static const sal_Unicode dayDefaultAbbrvName00[] = {0x53, 0x75, 0x6e, 0x0};
+static const sal_Unicode dayDefaultFullName00[] = {0x53, 0x75, 0x6e, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName00[] = {0x53, 0x0};
+static const sal_Unicode dayID01[] = {0x6d, 0x6f, 0x6e, 0x0};
+static const sal_Unicode dayDefaultAbbrvName01[] = {0x4d, 0x6f, 0x6e, 0x0};
+static const sal_Unicode dayDefaultFullName01[] = {0x4d, 0x6f, 0x6e, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName01[] = {0x4d, 0x0};
+static const sal_Unicode dayID02[] = {0x74, 0x75, 0x65, 0x0};
+static const sal_Unicode dayDefaultAbbrvName02[] = {0x54, 0x75, 0x65, 0x0};
+static const sal_Unicode dayDefaultFullName02[] = {0x54, 0x75, 0x65, 0x73, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName02[] = {0x54, 0x0};
+static const sal_Unicode dayID03[] = {0x77, 0x65, 0x64, 0x0};
+static const sal_Unicode dayDefaultAbbrvName03[] = {0x57, 0x65, 0x64, 0x0};
+static const sal_Unicode dayDefaultFullName03[] = {0x57, 0x65, 0x64, 0x6e, 0x65, 0x73, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName03[] = {0x57, 0x0};
+static const sal_Unicode dayID04[] = {0x74, 0x68, 0x75, 0x0};
+static const sal_Unicode dayDefaultAbbrvName04[] = {0x54, 0x68, 0x75, 0x0};
+static const sal_Unicode dayDefaultFullName04[] = {0x54, 0x68, 0x75, 0x72, 0x73, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName04[] = {0x54, 0x0};
+static const sal_Unicode dayID05[] = {0x66, 0x72, 0x69, 0x0};
+static const sal_Unicode dayDefaultAbbrvName05[] = {0x46, 0x72, 0x69, 0x0};
+static const sal_Unicode dayDefaultFullName05[] = {0x46, 0x72, 0x69, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName05[] = {0x46, 0x0};
+static const sal_Unicode dayID06[] = {0x73, 0x61, 0x74, 0x0};
+static const sal_Unicode dayDefaultAbbrvName06[] = {0x53, 0x61, 0x74, 0x0};
+static const sal_Unicode dayDefaultFullName06[] = {0x53, 0x61, 0x74, 0x75, 0x72, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName06[] = {0x53, 0x0};
+static const sal_Unicode monthID00[] = {0x6a, 0x61, 0x6e, 0x0};
+static const sal_Unicode monthDefaultAbbrvName00[] = {0x4a, 0x61, 0x6e, 0x0};
+static const sal_Unicode monthDefaultFullName00[] = {0x4a, 0x61, 0x6e, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode monthDefaultNarrowName00[] = {0x4a, 0x0};
+static const sal_Unicode monthID01[] = {0x66, 0x65, 0x62, 0x0};
+static const sal_Unicode monthDefaultAbbrvName01[] = {0x46, 0x65, 0x62, 0x0};
+static const sal_Unicode monthDefaultFullName01[] = {0x46, 0x65, 0x62, 0x72, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode monthDefaultNarrowName01[] = {0x46, 0x0};
+static const sal_Unicode monthID02[] = {0x6d, 0x61, 0x72, 0x0};
+static const sal_Unicode monthDefaultAbbrvName02[] = {0x4d, 0x61, 0x72, 0x0};
+static const sal_Unicode monthDefaultFullName02[] = {0x4d, 0x61, 0x72, 0x63, 0x68, 0x0};
+static const sal_Unicode monthDefaultNarrowName02[] = {0x4d, 0x0};
+static const sal_Unicode monthID03[] = {0x61, 0x70, 0x72, 0x0};
+static const sal_Unicode monthDefaultAbbrvName03[] = {0x41, 0x70, 0x72, 0x0};
+static const sal_Unicode monthDefaultFullName03[] = {0x41, 0x70, 0x72, 0x69, 0x6c, 0x0};
+static const sal_Unicode monthDefaultNarrowName03[] = {0x41, 0x0};
+static const sal_Unicode monthID04[] = {0x6d, 0x61, 0x79, 0x0};
+static const sal_Unicode monthDefaultAbbrvName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode monthDefaultFullName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode monthDefaultNarrowName04[] = {0x4d, 0x0};
+static const sal_Unicode monthID05[] = {0x6a, 0x75, 0x6e, 0x0};
+static const sal_Unicode monthDefaultAbbrvName05[] = {0x4a, 0x75, 0x6e, 0x0};
+static const sal_Unicode monthDefaultFullName05[] = {0x4a, 0x75, 0x6e, 0x65, 0x0};
+static const sal_Unicode monthDefaultNarrowName05[] = {0x4a, 0x0};
+static const sal_Unicode monthID06[] = {0x6a, 0x75, 0x6c, 0x0};
+static const sal_Unicode monthDefaultAbbrvName06[] = {0x4a, 0x75, 0x6c, 0x0};
+static const sal_Unicode monthDefaultFullName06[] = {0x4a, 0x75, 0x6c, 0x79, 0x0};
+static const sal_Unicode monthDefaultNarrowName06[] = {0x4a, 0x0};
+static const sal_Unicode monthID07[] = {0x61, 0x75, 0x67, 0x0};
+static const sal_Unicode monthDefaultAbbrvName07[] = {0x41, 0x75, 0x67, 0x0};
+static const sal_Unicode monthDefaultFullName07[] = {0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x0};
+static const sal_Unicode monthDefaultNarrowName07[] = {0x41, 0x0};
+static const sal_Unicode monthID08[] = {0x73, 0x65, 0x70, 0x0};
+static const sal_Unicode monthDefaultAbbrvName08[] = {0x53, 0x65, 0x70, 0x0};
+static const sal_Unicode monthDefaultFullName08[] = {0x53, 0x65, 0x70, 0x74, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName08[] = {0x53, 0x0};
+static const sal_Unicode monthID09[] = {0x6f, 0x63, 0x74, 0x0};
+static const sal_Unicode monthDefaultAbbrvName09[] = {0x4f, 0x63, 0x74, 0x0};
+static const sal_Unicode monthDefaultFullName09[] = {0x4f, 0x63, 0x74, 0x6f, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName09[] = {0x4f, 0x0};
+static const sal_Unicode monthID010[] = {0x6e, 0x6f, 0x76, 0x0};
+static const sal_Unicode monthDefaultAbbrvName010[] = {0x4e, 0x6f, 0x76, 0x0};
+static const sal_Unicode monthDefaultFullName010[] = {0x4e, 0x6f, 0x76, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName010[] = {0x4e, 0x0};
+static const sal_Unicode monthID011[] = {0x64, 0x65, 0x63, 0x0};
+static const sal_Unicode monthDefaultAbbrvName011[] = {0x44, 0x65, 0x63, 0x0};
+static const sal_Unicode monthDefaultFullName011[] = {0x44, 0x65, 0x63, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName011[] = {0x44, 0x0};
+static const sal_Unicode genitiveMonthID00[] = {0x6a, 0x61, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName00[] = {0x4a, 0x61, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName00[] = {0x4a, 0x61, 0x6e, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName00[] = {0x4a, 0x0};
+static const sal_Unicode genitiveMonthID01[] = {0x66, 0x65, 0x62, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName01[] = {0x46, 0x65, 0x62, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName01[] = {0x46, 0x65, 0x62, 0x72, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName01[] = {0x46, 0x0};
+static const sal_Unicode genitiveMonthID02[] = {0x6d, 0x61, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName02[] = {0x4d, 0x61, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName02[] = {0x4d, 0x61, 0x72, 0x63, 0x68, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName02[] = {0x4d, 0x0};
+static const sal_Unicode genitiveMonthID03[] = {0x61, 0x70, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName03[] = {0x41, 0x70, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName03[] = {0x41, 0x70, 0x72, 0x69, 0x6c, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName03[] = {0x41, 0x0};
+static const sal_Unicode genitiveMonthID04[] = {0x6d, 0x61, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName04[] = {0x4d, 0x0};
+static const sal_Unicode genitiveMonthID05[] = {0x6a, 0x75, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName05[] = {0x4a, 0x75, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName05[] = {0x4a, 0x75, 0x6e, 0x65, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName05[] = {0x4a, 0x0};
+static const sal_Unicode genitiveMonthID06[] = {0x6a, 0x75, 0x6c, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName06[] = {0x4a, 0x75, 0x6c, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName06[] = {0x4a, 0x75, 0x6c, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName06[] = {0x4a, 0x0};
+static const sal_Unicode genitiveMonthID07[] = {0x61, 0x75, 0x67, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName07[] = {0x41, 0x75, 0x67, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName07[] = {0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName07[] = {0x41, 0x0};
+static const sal_Unicode genitiveMonthID08[] = {0x73, 0x65, 0x70, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName08[] = {0x53, 0x65, 0x70, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName08[] = {0x53, 0x65, 0x70, 0x74, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName08[] = {0x53, 0x0};
+static const sal_Unicode genitiveMonthID09[] = {0x6f, 0x63, 0x74, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName09[] = {0x4f, 0x63, 0x74, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName09[] = {0x4f, 0x63, 0x74, 0x6f, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName09[] = {0x4f, 0x0};
+static const sal_Unicode genitiveMonthID010[] = {0x6e, 0x6f, 0x76, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName010[] = {0x4e, 0x6f, 0x76, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName010[] = {0x4e, 0x6f, 0x76, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName010[] = {0x4e, 0x0};
+static const sal_Unicode genitiveMonthID011[] = {0x64, 0x65, 0x63, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName011[] = {0x44, 0x65, 0x63, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName011[] = {0x44, 0x65, 0x63, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName011[] = {0x44, 0x0};
+static const sal_Unicode partitiveMonthID00[] = {0x6a, 0x61, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName00[] = {0x4a, 0x61, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName00[] = {0x4a, 0x61, 0x6e, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName00[] = {0x4a, 0x0};
+static const sal_Unicode partitiveMonthID01[] = {0x66, 0x65, 0x62, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName01[] = {0x46, 0x65, 0x62, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName01[] = {0x46, 0x65, 0x62, 0x72, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName01[] = {0x46, 0x0};
+static const sal_Unicode partitiveMonthID02[] = {0x6d, 0x61, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName02[] = {0x4d, 0x61, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName02[] = {0x4d, 0x61, 0x72, 0x63, 0x68, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName02[] = {0x4d, 0x0};
+static const sal_Unicode partitiveMonthID03[] = {0x61, 0x70, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName03[] = {0x41, 0x70, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName03[] = {0x41, 0x70, 0x72, 0x69, 0x6c, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName03[] = {0x41, 0x0};
+static const sal_Unicode partitiveMonthID04[] = {0x6d, 0x61, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName04[] = {0x4d, 0x0};
+static const sal_Unicode partitiveMonthID05[] = {0x6a, 0x75, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName05[] = {0x4a, 0x75, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName05[] = {0x4a, 0x75, 0x6e, 0x65, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName05[] = {0x4a, 0x0};
+static const sal_Unicode partitiveMonthID06[] = {0x6a, 0x75, 0x6c, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName06[] = {0x4a, 0x75, 0x6c, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName06[] = {0x4a, 0x75, 0x6c, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName06[] = {0x4a, 0x0};
+static const sal_Unicode partitiveMonthID07[] = {0x61, 0x75, 0x67, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName07[] = {0x41, 0x75, 0x67, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName07[] = {0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName07[] = {0x41, 0x0};
+static const sal_Unicode partitiveMonthID08[] = {0x73, 0x65, 0x70, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName08[] = {0x53, 0x65, 0x70, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName08[] = {0x53, 0x65, 0x70, 0x74, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName08[] = {0x53, 0x0};
+static const sal_Unicode partitiveMonthID09[] = {0x6f, 0x63, 0x74, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName09[] = {0x4f, 0x63, 0x74, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName09[] = {0x4f, 0x63, 0x74, 0x6f, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName09[] = {0x4f, 0x0};
+static const sal_Unicode partitiveMonthID010[] = {0x6e, 0x6f, 0x76, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName010[] = {0x4e, 0x6f, 0x76, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName010[] = {0x4e, 0x6f, 0x76, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName010[] = {0x4e, 0x0};
+static const sal_Unicode partitiveMonthID011[] = {0x64, 0x65, 0x63, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName011[] = {0x44, 0x65, 0x63, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName011[] = {0x44, 0x65, 0x63, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName011[] = {0x44, 0x0};
+static const sal_Unicode eraID00[] = {0x62, 0x63, 0x0};
+
+static const sal_Unicode eraDefaultAbbrvName00[] = {0x42, 0x43, 0x0};
+static const sal_Unicode eraDefaultFullName00[] = {0x42, 0x43, 0x0};
+static const sal_Unicode eraID01[] = {0x61, 0x64, 0x0};
+
+static const sal_Unicode eraDefaultAbbrvName01[] = {0x41, 0x44, 0x0};
+static const sal_Unicode eraDefaultFullName01[] = {0x41, 0x44, 0x0};
+static const sal_Unicode startDayOfWeek0[] = {0x73, 0x75, 0x6e, 0x0};
+static const sal_Unicode minimalDaysInFirstWeek0[] = {1};
+static const sal_Int16 calendarsCount = 1;
+
+static const sal_Unicode nbOfDays[] = {7};
+static const sal_Unicode nbOfMonths[] = {12};
+static const sal_Unicode nbOfGenitiveMonths[] = {12};
+static const sal_Unicode nbOfPartitiveMonths[] = {12};
+static const sal_Unicode nbOfEras[] = {2};
+static const sal_Unicode* calendars[] = {
+ nbOfDays,
+ nbOfMonths,
+ nbOfGenitiveMonths,
+ nbOfPartitiveMonths,
+ nbOfEras,
+ calendarID0,
+ defaultCalendar0,
+ dayID00,
+ dayDefaultAbbrvName00,
+ dayDefaultFullName00,
+ dayDefaultNarrowName00,
+ dayID01,
+ dayDefaultAbbrvName01,
+ dayDefaultFullName01,
+ dayDefaultNarrowName01,
+ dayID02,
+ dayDefaultAbbrvName02,
+ dayDefaultFullName02,
+ dayDefaultNarrowName02,
+ dayID03,
+ dayDefaultAbbrvName03,
+ dayDefaultFullName03,
+ dayDefaultNarrowName03,
+ dayID04,
+ dayDefaultAbbrvName04,
+ dayDefaultFullName04,
+ dayDefaultNarrowName04,
+ dayID05,
+ dayDefaultAbbrvName05,
+ dayDefaultFullName05,
+ dayDefaultNarrowName05,
+ dayID06,
+ dayDefaultAbbrvName06,
+ dayDefaultFullName06,
+ dayDefaultNarrowName06,
+ monthID00,
+ monthDefaultAbbrvName00,
+ monthDefaultFullName00,
+ monthDefaultNarrowName00,
+ monthID01,
+ monthDefaultAbbrvName01,
+ monthDefaultFullName01,
+ monthDefaultNarrowName01,
+ monthID02,
+ monthDefaultAbbrvName02,
+ monthDefaultFullName02,
+ monthDefaultNarrowName02,
+ monthID03,
+ monthDefaultAbbrvName03,
+ monthDefaultFullName03,
+ monthDefaultNarrowName03,
+ monthID04,
+ monthDefaultAbbrvName04,
+ monthDefaultFullName04,
+ monthDefaultNarrowName04,
+ monthID05,
+ monthDefaultAbbrvName05,
+ monthDefaultFullName05,
+ monthDefaultNarrowName05,
+ monthID06,
+ monthDefaultAbbrvName06,
+ monthDefaultFullName06,
+ monthDefaultNarrowName06,
+ monthID07,
+ monthDefaultAbbrvName07,
+ monthDefaultFullName07,
+ monthDefaultNarrowName07,
+ monthID08,
+ monthDefaultAbbrvName08,
+ monthDefaultFullName08,
+ monthDefaultNarrowName08,
+ monthID09,
+ monthDefaultAbbrvName09,
+ monthDefaultFullName09,
+ monthDefaultNarrowName09,
+ monthID010,
+ monthDefaultAbbrvName010,
+ monthDefaultFullName010,
+ monthDefaultNarrowName010,
+ monthID011,
+ monthDefaultAbbrvName011,
+ monthDefaultFullName011,
+ monthDefaultNarrowName011,
+ genitiveMonthID00,
+ genitiveMonthDefaultAbbrvName00,
+ genitiveMonthDefaultFullName00,
+ genitiveMonthDefaultNarrowName00,
+ genitiveMonthID01,
+ genitiveMonthDefaultAbbrvName01,
+ genitiveMonthDefaultFullName01,
+ genitiveMonthDefaultNarrowName01,
+ genitiveMonthID02,
+ genitiveMonthDefaultAbbrvName02,
+ genitiveMonthDefaultFullName02,
+ genitiveMonthDefaultNarrowName02,
+ genitiveMonthID03,
+ genitiveMonthDefaultAbbrvName03,
+ genitiveMonthDefaultFullName03,
+ genitiveMonthDefaultNarrowName03,
+ genitiveMonthID04,
+ genitiveMonthDefaultAbbrvName04,
+ genitiveMonthDefaultFullName04,
+ genitiveMonthDefaultNarrowName04,
+ genitiveMonthID05,
+ genitiveMonthDefaultAbbrvName05,
+ genitiveMonthDefaultFullName05,
+ genitiveMonthDefaultNarrowName05,
+ genitiveMonthID06,
+ genitiveMonthDefaultAbbrvName06,
+ genitiveMonthDefaultFullName06,
+ genitiveMonthDefaultNarrowName06,
+ genitiveMonthID07,
+ genitiveMonthDefaultAbbrvName07,
+ genitiveMonthDefaultFullName07,
+ genitiveMonthDefaultNarrowName07,
+ genitiveMonthID08,
+ genitiveMonthDefaultAbbrvName08,
+ genitiveMonthDefaultFullName08,
+ genitiveMonthDefaultNarrowName08,
+ genitiveMonthID09,
+ genitiveMonthDefaultAbbrvName09,
+ genitiveMonthDefaultFullName09,
+ genitiveMonthDefaultNarrowName09,
+ genitiveMonthID010,
+ genitiveMonthDefaultAbbrvName010,
+ genitiveMonthDefaultFullName010,
+ genitiveMonthDefaultNarrowName010,
+ genitiveMonthID011,
+ genitiveMonthDefaultAbbrvName011,
+ genitiveMonthDefaultFullName011,
+ genitiveMonthDefaultNarrowName011,
+ partitiveMonthID00,
+ partitiveMonthDefaultAbbrvName00,
+ partitiveMonthDefaultFullName00,
+ partitiveMonthDefaultNarrowName00,
+ partitiveMonthID01,
+ partitiveMonthDefaultAbbrvName01,
+ partitiveMonthDefaultFullName01,
+ partitiveMonthDefaultNarrowName01,
+ partitiveMonthID02,
+ partitiveMonthDefaultAbbrvName02,
+ partitiveMonthDefaultFullName02,
+ partitiveMonthDefaultNarrowName02,
+ partitiveMonthID03,
+ partitiveMonthDefaultAbbrvName03,
+ partitiveMonthDefaultFullName03,
+ partitiveMonthDefaultNarrowName03,
+ partitiveMonthID04,
+ partitiveMonthDefaultAbbrvName04,
+ partitiveMonthDefaultFullName04,
+ partitiveMonthDefaultNarrowName04,
+ partitiveMonthID05,
+ partitiveMonthDefaultAbbrvName05,
+ partitiveMonthDefaultFullName05,
+ partitiveMonthDefaultNarrowName05,
+ partitiveMonthID06,
+ partitiveMonthDefaultAbbrvName06,
+ partitiveMonthDefaultFullName06,
+ partitiveMonthDefaultNarrowName06,
+ partitiveMonthID07,
+ partitiveMonthDefaultAbbrvName07,
+ partitiveMonthDefaultFullName07,
+ partitiveMonthDefaultNarrowName07,
+ partitiveMonthID08,
+ partitiveMonthDefaultAbbrvName08,
+ partitiveMonthDefaultFullName08,
+ partitiveMonthDefaultNarrowName08,
+ partitiveMonthID09,
+ partitiveMonthDefaultAbbrvName09,
+ partitiveMonthDefaultFullName09,
+ partitiveMonthDefaultNarrowName09,
+ partitiveMonthID010,
+ partitiveMonthDefaultAbbrvName010,
+ partitiveMonthDefaultFullName010,
+ partitiveMonthDefaultNarrowName010,
+ partitiveMonthID011,
+ partitiveMonthDefaultAbbrvName011,
+ partitiveMonthDefaultFullName011,
+ partitiveMonthDefaultNarrowName011,
+ eraID00,
+ eraDefaultAbbrvName00,
+ eraDefaultFullName00,
+ eraID01,
+ eraDefaultAbbrvName01,
+ eraDefaultFullName01,
+ startDayOfWeek0,
+ minimalDaysInFirstWeek0,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCalendars_en_JM(sal_Int16& count)
+{
+ count = calendarsCount;
+ return (sal_Unicode**)calendars;
+}
+static const sal_Unicode defaultCurrency0[] = {1};
+static const sal_Unicode defaultCurrencyUsedInCompatibleFormatCodes0[] = {1};
+static const sal_Unicode defaultCurrencyLegacyOnly0[] = {0};
+static const sal_Unicode currencyID0[] = {0x4a, 0x4d, 0x44, 0x0};
+static const sal_Unicode currencySymbol0[] = {0x4a, 0x24, 0x0};
+static const sal_Unicode bankSymbol0[] = {0x4a, 0x4d, 0x44, 0x0};
+static const sal_Unicode currencyName0[] = {0x4a, 0x61, 0x6d, 0x61, 0x69, 0x63, 0x61, 0x6e, 0x20, 0x44, 0x6f, 0x6c, 0x6c, 0x61, 0x72, 0x0};
+static const sal_Unicode currencyDecimalPlaces0[] = {2};
+
+static const sal_Int16 currencyCount = 1;
+
+static const sal_Unicode* currencies[] = {
+ currencyID0,
+ currencySymbol0,
+ bankSymbol0,
+ currencyName0,
+ defaultCurrency0,
+ defaultCurrencyUsedInCompatibleFormatCodes0,
+ currencyDecimalPlaces0,
+ defaultCurrencyLegacyOnly0,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCurrencies_en_JM(sal_Int16& count)
+{
+ count = currencyCount;
+ return (sal_Unicode**)currencies;
+}
+extern sal_Unicode ** SAL_CALL getTransliterations_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getTransliterations_en_JM(sal_Int16& count)
+{
+ return getTransliterations_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getForbiddenCharacters_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getForbiddenCharacters_en_JM(sal_Int16& count)
+{
+ return getForbiddenCharacters_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getBreakIteratorRules_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getBreakIteratorRules_en_JM(sal_Int16& count)
+{
+ return getBreakIteratorRules_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getReservedWords_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getReservedWords_en_JM(sal_Int16& count)
+{
+ return getReservedWords_en_US(count);
+}
+// ---> ContinuousNumbering
+extern const sal_Unicode *** SAL_CALL getContinuousNumberingLevels_en_US(sal_Int16& nStyles, sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode *** SAL_CALL getContinuousNumberingLevels_en_JM(sal_Int16& nStyles, sal_Int16& nAttributes)
+{
+ return getContinuousNumberingLevels_en_US(nStyles, nAttributes);
+}
+// ---> OutlineNumbering
+extern const sal_Unicode **** SAL_CALL getOutlineNumberingLevels_en_US(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode **** SAL_CALL getOutlineNumberingLevels_en_JM(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes)
+{
+ return getOutlineNumberingLevels_en_US(nStyles, nLevels, nAttributes);
+}
+} // extern "C"
+
diff --git a/vcl/workben/localestub/localedata_en_LK.cxx b/vcl/workben/localestub/localedata_en_LK.cxx
new file mode 100644
index 000000000..aa71f3abd
--- /dev/null
+++ b/vcl/workben/localestub/localedata_en_LK.cxx
@@ -0,0 +1,144 @@
+#include <sal/types.h>
+
+#include <stdio.h>
+
+extern "C" {
+
+static const sal_Unicode langID[] = { 0x65, 0x6e, 0x0 };
+static const sal_Unicode langDefaultName[] = { 0x45, 0x6e, 0x67, 0x6c, 0x69, 0x73, 0x68, 0x0 };
+static const sal_Unicode countryID[] = { 0x4c, 0x4b, 0x0 };
+static const sal_Unicode countryDefaultName[]
+ = { 0x53, 0x72, 0x69, 0x20, 0x4c, 0x61, 0x6e, 0x6b, 0x61, 0x0 };
+static const sal_Unicode Variant[] = { 0x0 };
+
+static const sal_Unicode* LCInfoArray[]
+ = { langID, langDefaultName, countryID, countryDefaultName, Variant };
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getLCInfo_en_LK(sal_Int16& count)
+{
+ count = SAL_N_ELEMENTS(LCInfoArray);
+ return (sal_Unicode**)LCInfoArray;
+}
+extern sal_Unicode** SAL_CALL getLocaleItem_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getLocaleItem_en_LK(sal_Int16& count)
+{
+ return getLocaleItem_en_GB(count);
+}
+static const sal_Unicode replaceTo0[]
+ = { 0x5b, 0x24, 0x52, 0x73, 0x2d, 0x41, 0x34, 0x30, 0x39, 0x5d, 0x0 };
+extern sal_Unicode const* const* SAL_CALL getAllFormats0_en_GB(sal_Int16& count,
+ const sal_Unicode*& from,
+ const sal_Unicode*& to);
+SAL_DLLPUBLIC_EXPORT sal_Unicode const* const* SAL_CALL
+getAllFormats0_en_LK(sal_Int16& count, const sal_Unicode*& from, const sal_Unicode*& to)
+{
+ to = replaceTo0;
+ const sal_Unicode* tmp;
+ return getAllFormats0_en_GB(count, from, tmp);
+}
+extern sal_Unicode** SAL_CALL getDateAcceptancePatterns_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getDateAcceptancePatterns_en_LK(sal_Int16& count)
+{
+ return getDateAcceptancePatterns_en_GB(count);
+}
+extern sal_Unicode** SAL_CALL getCollatorImplementation_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getCollatorImplementation_en_LK(sal_Int16& count)
+{
+ return getCollatorImplementation_en_GB(count);
+}
+extern sal_Unicode** SAL_CALL getCollationOptions_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getCollationOptions_en_LK(sal_Int16& count)
+{
+ return getCollationOptions_en_GB(count);
+}
+extern sal_Unicode** SAL_CALL getSearchOptions_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getSearchOptions_en_LK(sal_Int16& count)
+{
+ return getSearchOptions_en_GB(count);
+}
+extern sal_Unicode** SAL_CALL getIndexAlgorithm_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getIndexAlgorithm_en_LK(sal_Int16& count)
+{
+ return getIndexAlgorithm_en_GB(count);
+}
+extern sal_Unicode** SAL_CALL getUnicodeScripts_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getUnicodeScripts_en_LK(sal_Int16& count)
+{
+ return getUnicodeScripts_en_GB(count);
+}
+extern sal_Unicode** SAL_CALL getFollowPageWords_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getFollowPageWords_en_LK(sal_Int16& count)
+{
+ return getFollowPageWords_en_GB(count);
+}
+extern sal_Unicode** SAL_CALL getAllCalendars_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getAllCalendars_en_LK(sal_Int16& count)
+{
+ return getAllCalendars_en_GB(count);
+}
+static const sal_Unicode defaultCurrency0[] = { 1 };
+static const sal_Unicode defaultCurrencyUsedInCompatibleFormatCodes0[] = { 1 };
+static const sal_Unicode defaultCurrencyLegacyOnly0[] = { 0 };
+static const sal_Unicode currencyID0[] = { 0x4c, 0x4b, 0x52, 0x0 };
+static const sal_Unicode currencySymbol0[] = { 0x52, 0x73, 0x0 };
+static const sal_Unicode bankSymbol0[] = { 0x4c, 0x4b, 0x52, 0x0 };
+static const sal_Unicode currencyName0[] = { 0x53, 0x72, 0x69, 0x20, 0x4c, 0x61, 0x6e, 0x6b, 0x61,
+ 0x6e, 0x20, 0x72, 0x75, 0x70, 0x65, 0x65, 0x0 };
+static const sal_Unicode currencyDecimalPlaces0[] = { 2 };
+
+static const sal_Int16 currencyCount = 1;
+
+static const sal_Unicode* currencies[] = {
+ currencyID0,
+ currencySymbol0,
+ bankSymbol0,
+ currencyName0,
+ defaultCurrency0,
+ defaultCurrencyUsedInCompatibleFormatCodes0,
+ currencyDecimalPlaces0,
+ defaultCurrencyLegacyOnly0,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getAllCurrencies_en_LK(sal_Int16& count)
+{
+ count = currencyCount;
+ return (sal_Unicode**)currencies;
+}
+extern sal_Unicode** SAL_CALL getTransliterations_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getTransliterations_en_LK(sal_Int16& count)
+{
+ return getTransliterations_en_GB(count);
+}
+extern sal_Unicode** SAL_CALL getForbiddenCharacters_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getForbiddenCharacters_en_LK(sal_Int16& count)
+{
+ return getForbiddenCharacters_en_GB(count);
+}
+extern sal_Unicode** SAL_CALL getBreakIteratorRules_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getBreakIteratorRules_en_LK(sal_Int16& count)
+{
+ return getBreakIteratorRules_en_GB(count);
+}
+extern sal_Unicode** SAL_CALL getReservedWords_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getReservedWords_en_LK(sal_Int16& count)
+{
+ return getReservedWords_en_GB(count);
+}
+// ---> ContinuousNumbering
+extern const sal_Unicode*** SAL_CALL getContinuousNumberingLevels_en_GB(sal_Int16& nStyles,
+ sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode*** SAL_CALL
+getContinuousNumberingLevels_en_LK(sal_Int16& nStyles, sal_Int16& nAttributes)
+{
+ return getContinuousNumberingLevels_en_GB(nStyles, nAttributes);
+}
+// ---> OutlineNumbering
+extern const sal_Unicode**** SAL_CALL getOutlineNumberingLevels_en_GB(sal_Int16& nStyles,
+ sal_Int16& nLevels,
+ sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode**** SAL_CALL
+getOutlineNumberingLevels_en_LK(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes)
+{
+ return getOutlineNumberingLevels_en_GB(nStyles, nLevels, nAttributes);
+}
+} // extern "C"
diff --git a/vcl/workben/localestub/localedata_en_MW.cxx b/vcl/workben/localestub/localedata_en_MW.cxx
new file mode 100644
index 000000000..5f21ab3e9
--- /dev/null
+++ b/vcl/workben/localestub/localedata_en_MW.cxx
@@ -0,0 +1,140 @@
+#include <sal/types.h>
+
+
+#include <stdio.h>
+
+extern "C" {
+
+static const sal_Unicode langID[] = {0x65, 0x6e, 0x0};
+static const sal_Unicode langDefaultName[] = {0x45, 0x6e, 0x67, 0x6c, 0x69, 0x73, 0x68, 0x0};
+static const sal_Unicode countryID[] = {0x4d, 0x57, 0x0};
+static const sal_Unicode countryDefaultName[] = {0x4d, 0x61, 0x6c, 0x61, 0x77, 0x69, 0x0};
+static const sal_Unicode Variant[] = {0x0};
+
+static const sal_Unicode* LCInfoArray[] = {
+ langID,
+ langDefaultName,
+ countryID,
+ countryDefaultName,
+ Variant
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLCInfo_en_MW(sal_Int16& count)
+{
+ count = 0;
+ return (sal_Unicode**)LCInfoArray;
+}
+extern sal_Unicode ** SAL_CALL getLocaleItem_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLocaleItem_en_MW(sal_Int16& count)
+{
+ return getLocaleItem_en_GB(count);
+}
+static const sal_Unicode replaceTo0[] = {0x5b, 0x24, 0x4d, 0x4b, 0x2d, 0x38, 0x38, 0x30, 0x39, 0x5d, 0x0};
+extern sal_Unicode const * const * SAL_CALL getAllFormats0_en_GB(sal_Int16& count, const sal_Unicode*& from, const sal_Unicode*& to);
+SAL_DLLPUBLIC_EXPORT sal_Unicode const * const * SAL_CALL getAllFormats0_en_MW(sal_Int16& count, const sal_Unicode*& from, const sal_Unicode*& to)
+{
+ to = replaceTo0;
+ const sal_Unicode* tmp;
+ return getAllFormats0_en_GB(count, from, tmp);
+}
+extern sal_Unicode ** SAL_CALL getDateAcceptancePatterns_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getDateAcceptancePatterns_en_MW(sal_Int16& count)
+{
+ return getDateAcceptancePatterns_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getCollatorImplementation_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollatorImplementation_en_MW(sal_Int16& count)
+{
+ return getCollatorImplementation_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getCollationOptions_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollationOptions_en_MW(sal_Int16& count)
+{
+ return getCollationOptions_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getSearchOptions_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getSearchOptions_en_MW(sal_Int16& count)
+{
+ return getSearchOptions_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getIndexAlgorithm_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getIndexAlgorithm_en_MW(sal_Int16& count)
+{
+ return getIndexAlgorithm_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getUnicodeScripts_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getUnicodeScripts_en_MW(sal_Int16& count)
+{
+ return getUnicodeScripts_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getFollowPageWords_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getFollowPageWords_en_MW(sal_Int16& count)
+{
+ return getFollowPageWords_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getAllCalendars_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCalendars_en_MW(sal_Int16& count)
+{
+ return getAllCalendars_en_GB(count);
+}
+static const sal_Unicode defaultCurrency0[] = {1};
+static const sal_Unicode defaultCurrencyUsedInCompatibleFormatCodes0[] = {1};
+static const sal_Unicode defaultCurrencyLegacyOnly0[] = {0};
+static const sal_Unicode currencyID0[] = {0x4d, 0x57, 0x4b, 0x0};
+static const sal_Unicode currencySymbol0[] = {0x4d, 0x4b, 0x0};
+static const sal_Unicode bankSymbol0[] = {0x4d, 0x57, 0x4b, 0x0};
+static const sal_Unicode currencyName0[] = {0x4d, 0x61, 0x6c, 0x61, 0x77, 0x69, 0x61, 0x6e, 0x20, 0x6b, 0x77, 0x61, 0x63, 0x68, 0x61, 0x0};
+static const sal_Unicode currencyDecimalPlaces0[] = {2};
+
+static const sal_Int16 currencyCount = 1;
+
+static const sal_Unicode* currencies[] = {
+ currencyID0,
+ currencySymbol0,
+ bankSymbol0,
+ currencyName0,
+ defaultCurrency0,
+ defaultCurrencyUsedInCompatibleFormatCodes0,
+ currencyDecimalPlaces0,
+ defaultCurrencyLegacyOnly0,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCurrencies_en_MW(sal_Int16& count)
+{
+ count = currencyCount;
+ return (sal_Unicode**)currencies;
+}
+extern sal_Unicode ** SAL_CALL getTransliterations_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getTransliterations_en_MW(sal_Int16& count)
+{
+ return getTransliterations_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getForbiddenCharacters_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getForbiddenCharacters_en_MW(sal_Int16& count)
+{
+ return getForbiddenCharacters_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getBreakIteratorRules_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getBreakIteratorRules_en_MW(sal_Int16& count)
+{
+ return getBreakIteratorRules_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getReservedWords_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getReservedWords_en_MW(sal_Int16& count)
+{
+ return getReservedWords_en_GB(count);
+}
+// ---> ContinuousNumbering
+extern const sal_Unicode *** SAL_CALL getContinuousNumberingLevels_en_GB(sal_Int16& nStyles, sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode *** SAL_CALL getContinuousNumberingLevels_en_MW(sal_Int16& nStyles, sal_Int16& nAttributes)
+{
+ return getContinuousNumberingLevels_en_GB(nStyles, nAttributes);
+}
+// ---> OutlineNumbering
+extern const sal_Unicode **** SAL_CALL getOutlineNumberingLevels_en_GB(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode **** SAL_CALL getOutlineNumberingLevels_en_MW(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes)
+{
+ return getOutlineNumberingLevels_en_GB(nStyles, nLevels, nAttributes);
+}
+} // extern "C"
+
diff --git a/vcl/workben/localestub/localedata_en_MY.cxx b/vcl/workben/localestub/localedata_en_MY.cxx
new file mode 100644
index 000000000..b24ff2363
--- /dev/null
+++ b/vcl/workben/localestub/localedata_en_MY.cxx
@@ -0,0 +1,118 @@
+#include <sal/types.h>
+
+
+#include <stdio.h>
+
+extern "C" {
+
+static const sal_Unicode langID[] = {0x65, 0x6e, 0x0};
+static const sal_Unicode langDefaultName[] = {0x45, 0x6e, 0x67, 0x6c, 0x69, 0x73, 0x68, 0x0};
+static const sal_Unicode countryID[] = {0x4d, 0x59, 0x0};
+static const sal_Unicode countryDefaultName[] = {0x4d, 0x61, 0x6c, 0x61, 0x79, 0x73, 0x69, 0x61, 0x0};
+static const sal_Unicode Variant[] = {0x0};
+
+static const sal_Unicode* LCInfoArray[] = {
+ langID,
+ langDefaultName,
+ countryID,
+ countryDefaultName,
+ Variant
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLCInfo_en_MY(sal_Int16& count)
+{
+ count = 0;
+ return (sal_Unicode**)LCInfoArray;
+}
+extern sal_Unicode ** SAL_CALL getLocaleItem_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLocaleItem_en_MY(sal_Int16& count)
+{
+ return getLocaleItem_en_GB(count);
+}
+static const sal_Unicode replaceTo0[] = {0x5b, 0x24, 0x52, 0x4d, 0x2d, 0x34, 0x34, 0x30, 0x39, 0x5d, 0x0};
+extern sal_Unicode const * const * SAL_CALL getAllFormats0_en_GB(sal_Int16& count, const sal_Unicode*& from, const sal_Unicode*& to);
+SAL_DLLPUBLIC_EXPORT sal_Unicode const * const * SAL_CALL getAllFormats0_en_MY(sal_Int16& count, const sal_Unicode*& from, const sal_Unicode*& to)
+{
+ to = replaceTo0;
+ const sal_Unicode* tmp;
+ return getAllFormats0_en_GB(count, from, tmp);
+}
+extern sal_Unicode ** SAL_CALL getDateAcceptancePatterns_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getDateAcceptancePatterns_en_MY(sal_Int16& count)
+{
+ return getDateAcceptancePatterns_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getCollatorImplementation_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollatorImplementation_en_MY(sal_Int16& count)
+{
+ return getCollatorImplementation_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getCollationOptions_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollationOptions_en_MY(sal_Int16& count)
+{
+ return getCollationOptions_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getSearchOptions_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getSearchOptions_en_MY(sal_Int16& count)
+{
+ return getSearchOptions_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getIndexAlgorithm_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getIndexAlgorithm_en_MY(sal_Int16& count)
+{
+ return getIndexAlgorithm_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getUnicodeScripts_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getUnicodeScripts_en_MY(sal_Int16& count)
+{
+ return getUnicodeScripts_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getFollowPageWords_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getFollowPageWords_en_MY(sal_Int16& count)
+{
+ return getFollowPageWords_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getAllCalendars_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCalendars_en_MY(sal_Int16& count)
+{
+ return getAllCalendars_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getAllCurrencies_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCurrencies_en_MY(sal_Int16& count)
+{
+ return getAllCurrencies_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getTransliterations_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getTransliterations_en_MY(sal_Int16& count)
+{
+ return getTransliterations_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getForbiddenCharacters_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getForbiddenCharacters_en_MY(sal_Int16& count)
+{
+ return getForbiddenCharacters_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getBreakIteratorRules_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getBreakIteratorRules_en_MY(sal_Int16& count)
+{
+ return getBreakIteratorRules_en_GB(count);
+}
+extern sal_Unicode ** SAL_CALL getReservedWords_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getReservedWords_en_MY(sal_Int16& count)
+{
+ return getReservedWords_en_GB(count);
+}
+// ---> ContinuousNumbering
+extern const sal_Unicode *** SAL_CALL getContinuousNumberingLevels_en_GB(sal_Int16& nStyles, sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode *** SAL_CALL getContinuousNumberingLevels_en_MY(sal_Int16& nStyles, sal_Int16& nAttributes)
+{
+ return getContinuousNumberingLevels_en_GB(nStyles, nAttributes);
+}
+// ---> OutlineNumbering
+extern const sal_Unicode **** SAL_CALL getOutlineNumberingLevels_en_GB(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode **** SAL_CALL getOutlineNumberingLevels_en_MY(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes)
+{
+ return getOutlineNumberingLevels_en_GB(nStyles, nLevels, nAttributes);
+}
+} // extern "C"
+
diff --git a/vcl/workben/localestub/localedata_en_NA.cxx b/vcl/workben/localestub/localedata_en_NA.cxx
new file mode 100644
index 000000000..970c7547a
--- /dev/null
+++ b/vcl/workben/localestub/localedata_en_NA.cxx
@@ -0,0 +1,1280 @@
+#include <sal/types.h>
+
+
+#include <stdio.h>
+
+extern "C" {
+
+static const sal_Unicode langID[] = {0x65, 0x6e, 0x0};
+static const sal_Unicode langDefaultName[] = {0x45, 0x6e, 0x67, 0x6c, 0x69, 0x73, 0x68, 0x0};
+static const sal_Unicode countryID[] = {0x4e, 0x41, 0x0};
+static const sal_Unicode countryDefaultName[] = {0x4e, 0x61, 0x6d, 0x69, 0x62, 0x69, 0x61, 0x0};
+static const sal_Unicode Variant[] = {0x0};
+
+static const sal_Unicode* LCInfoArray[] = {
+ langID,
+ langDefaultName,
+ countryID,
+ countryDefaultName,
+ Variant
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLCInfo_en_NA(sal_Int16& count)
+{
+ count = 0;
+ return (sal_Unicode**)LCInfoArray;
+}
+
+
+static const sal_Unicode LC_CTYPE_Unoid[] = {0x0};
+static const sal_Unicode dateSeparator[] = {0x2f, 0x0};
+static const sal_Unicode thousandSeparator[] = {0x2c, 0x0};
+static const sal_Unicode decimalSeparator[] = {0x2e, 0x0};
+static const sal_Unicode timeSeparator[] = {0x3a, 0x0};
+static const sal_Unicode time100SecSeparator[] = {0x2e, 0x0};
+static const sal_Unicode listSeparator[] = {0x3b, 0x0};
+static const sal_Unicode LongDateDayOfWeekSeparator[] = {0x20, 0x0};
+static const sal_Unicode LongDateDaySeparator[] = {0x20, 0x0};
+static const sal_Unicode LongDateMonthSeparator[] = {0x20, 0x0};
+static const sal_Unicode LongDateYearSeparator[] = {0x20, 0x0};
+static const sal_Unicode quotationStart[] = {0x2018, 0x0};
+static const sal_Unicode quotationEnd[] = {0x2019, 0x0};
+static const sal_Unicode doubleQuotationStart[] = {0x201c, 0x0};
+static const sal_Unicode doubleQuotationEnd[] = {0x201d, 0x0};
+static const sal_Unicode timeAM[] = {0x41, 0x4d, 0x0};
+static const sal_Unicode timePM[] = {0x50, 0x4d, 0x0};
+static const sal_Unicode measurementSystem[] = {0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x0};
+
+static const sal_Unicode* LCType[] = {
+ LC_CTYPE_Unoid,
+ dateSeparator,
+ thousandSeparator,
+ decimalSeparator,
+ timeSeparator,
+ time100SecSeparator,
+ listSeparator,
+ quotationStart,
+ quotationEnd,
+ doubleQuotationStart,
+ doubleQuotationEnd,
+ timeAM,
+ timePM,
+ measurementSystem,
+ LongDateDayOfWeekSeparator,
+ LongDateDaySeparator,
+ LongDateMonthSeparator,
+ LongDateYearSeparator
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLocaleItem_en_NA(sal_Int16& count)
+{
+ count = 0;
+ return (sal_Unicode**)LCType;
+}
+static const sal_Unicode replaceFrom0[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x0};
+static const sal_Unicode replaceTo0[] = {0x5b, 0x24, 0x24, 0x2d, 0x38, 0x30, 0x30, 0x39, 0x5d, 0x0};
+static const sal_Unicode FormatKey0[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement0[] = {1};
+static const sal_Unicode FormatType0[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage0[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex0[] = {0};
+static const sal_Unicode FormatCode0[] = {0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x0};
+static const sal_Unicode FormatDefaultName0[] = {0x0};
+static const sal_Unicode FormatKey1[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement1[] = {1};
+static const sal_Unicode FormatType1[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage1[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex1[] = {1};
+static const sal_Unicode FormatCode1[] = {0x30, 0x0};
+static const sal_Unicode FormatDefaultName1[] = {0x0};
+static const sal_Unicode FormatKey2[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement2[] = {0};
+static const sal_Unicode FormatType2[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage2[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex2[] = {2};
+static const sal_Unicode FormatCode2[] = {0x30, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName2[] = {0x0};
+static const sal_Unicode FormatKey3[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement3[] = {0};
+static const sal_Unicode FormatType3[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage3[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex3[] = {3};
+static const sal_Unicode FormatCode3[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName3[] = {0x0};
+static const sal_Unicode FormatKey4[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement4[] = {0};
+static const sal_Unicode FormatType4[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage4[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex4[] = {4};
+static const sal_Unicode FormatCode4[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName4[] = {0x0};
+static const sal_Unicode FormatKey5[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement5[] = {0};
+static const sal_Unicode FormatType5[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage5[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex5[] = {5};
+static const sal_Unicode FormatCode5[] = {0x23, 0x2c, 0x23, 0x23, 0x23, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName5[] = {0x0};
+static const sal_Unicode FormatKey6[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x37, 0x0};
+static const sal_Unicode defaultFormatElement6[] = {0};
+static const sal_Unicode FormatType6[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage6[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex6[] = {70};
+static const sal_Unicode FormatCode6[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x5f, 0x29, 0x3b, 0x28, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x29, 0x0};
+static const sal_Unicode FormatDefaultName6[] = {0x0};
+static const sal_Unicode FormatKey7[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x38, 0x0};
+static const sal_Unicode defaultFormatElement7[] = {0};
+static const sal_Unicode FormatType7[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage7[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex7[] = {71};
+static const sal_Unicode FormatCode7[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x5f, 0x29, 0x3b, 0x28, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x29, 0x0};
+static const sal_Unicode FormatDefaultName7[] = {0x0};
+static const sal_Unicode FormatKey8[] = {0x53, 0x63, 0x69, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x63, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement8[] = {0};
+static const sal_Unicode FormatType8[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage8[] = {0x53, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, 0x43, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex8[] = {6};
+static const sal_Unicode FormatCode8[] = {0x30, 0x2e, 0x30, 0x30, 0x45, 0x2b, 0x30, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName8[] = {0x0};
+static const sal_Unicode FormatKey9[] = {0x53, 0x63, 0x69, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x63, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement9[] = {1};
+static const sal_Unicode FormatType9[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage9[] = {0x53, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, 0x43, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex9[] = {7};
+static const sal_Unicode FormatCode9[] = {0x30, 0x2e, 0x30, 0x30, 0x45, 0x2b, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName9[] = {0x0};
+static const sal_Unicode FormatKey10[] = {0x53, 0x63, 0x69, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x63, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement10[] = {0};
+static const sal_Unicode FormatType10[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage10[] = {0x53, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, 0x43, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex10[] = {78};
+static const sal_Unicode FormatCode10[] = {0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x45, 0x2b, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName10[] = {0x0};
+static const sal_Unicode FormatKey11[] = {0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement11[] = {1};
+static const sal_Unicode FormatType11[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage11[] = {0x50, 0x45, 0x52, 0x43, 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex11[] = {8};
+static const sal_Unicode FormatCode11[] = {0x30, 0x25, 0x0};
+static const sal_Unicode FormatDefaultName11[] = {0x0};
+static const sal_Unicode FormatKey12[] = {0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement12[] = {1};
+static const sal_Unicode FormatType12[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage12[] = {0x50, 0x45, 0x52, 0x43, 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex12[] = {9};
+static const sal_Unicode FormatCode12[] = {0x30, 0x2e, 0x30, 0x30, 0x25, 0x0};
+static const sal_Unicode FormatDefaultName12[] = {0x0};
+static const sal_Unicode FormatKey13[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement13[] = {1};
+static const sal_Unicode FormatType13[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage13[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex13[] = {12};
+static const sal_Unicode FormatCode13[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x20, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x3b, 0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x2d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName13[] = {0x0};
+static const sal_Unicode FormatKey14[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement14[] = {0};
+static const sal_Unicode FormatType14[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage14[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex14[] = {13};
+static const sal_Unicode FormatCode14[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x20, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x3b, 0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x2d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName14[] = {0x0};
+static const sal_Unicode FormatKey15[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement15[] = {0};
+static const sal_Unicode FormatType15[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage15[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex15[] = {14};
+static const sal_Unicode FormatCode15[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x20, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x3b, 0x5b, 0x52, 0x45, 0x44, 0x5d, 0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x2d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName15[] = {0x0};
+static const sal_Unicode FormatKey16[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement16[] = {1};
+static const sal_Unicode FormatType16[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage16[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex16[] = {15};
+static const sal_Unicode FormatCode16[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x20, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x3b, 0x5b, 0x52, 0x45, 0x44, 0x5d, 0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x2d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName16[] = {0x0};
+static const sal_Unicode FormatKey17[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement17[] = {0};
+static const sal_Unicode FormatType17[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage17[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex17[] = {16};
+static const sal_Unicode FormatCode17[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x20, 0x43, 0x43, 0x43, 0x0};
+static const sal_Unicode FormatDefaultName17[] = {0x0};
+static const sal_Unicode FormatKey18[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement18[] = {0};
+static const sal_Unicode FormatType18[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage18[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex18[] = {17};
+static const sal_Unicode FormatCode18[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x20, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x2d, 0x2d, 0x3b, 0x5b, 0x52, 0x45, 0x44, 0x5d, 0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x2d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x2d, 0x2d, 0x0};
+static const sal_Unicode FormatDefaultName18[] = {0x0};
+static const sal_Unicode FormatKey19[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement19[] = {1};
+static const sal_Unicode FormatType19[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage19[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex19[] = {18};
+static const sal_Unicode FormatCode19[] = {0x59, 0x59, 0x2f, 0x4d, 0x4d, 0x2f, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName19[] = {0x0};
+static const sal_Unicode FormatKey20[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x39, 0x0};
+static const sal_Unicode defaultFormatElement20[] = {1};
+static const sal_Unicode FormatType20[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage20[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex20[] = {19};
+static const sal_Unicode FormatCode20[] = {0x44, 0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName20[] = {0x0};
+static const sal_Unicode FormatKey21[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x38, 0x0};
+static const sal_Unicode defaultFormatElement21[] = {1};
+static const sal_Unicode FormatType21[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage21[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex21[] = {20};
+static const sal_Unicode FormatCode21[] = {0x59, 0x59, 0x2f, 0x4d, 0x4d, 0x2f, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName21[] = {0x0};
+static const sal_Unicode FormatKey22[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x37, 0x0};
+static const sal_Unicode defaultFormatElement22[] = {0};
+static const sal_Unicode FormatType22[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage22[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex22[] = {21};
+static const sal_Unicode FormatCode22[] = {0x59, 0x59, 0x59, 0x59, 0x2f, 0x4d, 0x4d, 0x2f, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName22[] = {0x0};
+static const sal_Unicode FormatKey23[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x30, 0x0};
+static const sal_Unicode defaultFormatElement23[] = {0};
+static const sal_Unicode FormatType23[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage23[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex23[] = {22};
+static const sal_Unicode FormatCode23[] = {0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName23[] = {0x0};
+static const sal_Unicode FormatKey24[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement24[] = {0};
+static const sal_Unicode FormatType24[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage24[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex24[] = {23};
+static const sal_Unicode FormatCode24[] = {0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName24[] = {0x0};
+static const sal_Unicode FormatKey25[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement25[] = {0};
+static const sal_Unicode FormatType25[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage25[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex25[] = {24};
+static const sal_Unicode FormatCode25[] = {0x44, 0x2e, 0x20, 0x4d, 0x4d, 0x4d, 0x2e, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName25[] = {0x0};
+static const sal_Unicode FormatKey26[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement26[] = {0};
+static const sal_Unicode FormatType26[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage26[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex26[] = {25};
+static const sal_Unicode FormatCode26[] = {0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName26[] = {0x0};
+static const sal_Unicode FormatKey27[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x37, 0x0};
+static const sal_Unicode defaultFormatElement27[] = {0};
+static const sal_Unicode FormatType27[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage27[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex27[] = {26};
+static const sal_Unicode FormatCode27[] = {0x44, 0x2e, 0x20, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName27[] = {0x0};
+static const sal_Unicode FormatKey28[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement28[] = {0};
+static const sal_Unicode FormatType28[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage28[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex28[] = {27};
+static const sal_Unicode FormatCode28[] = {0x4e, 0x4e, 0x20, 0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName28[] = {0x0};
+static const sal_Unicode FormatKey29[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement29[] = {0};
+static const sal_Unicode FormatType29[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage29[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex29[] = {28};
+static const sal_Unicode FormatCode29[] = {0x4e, 0x4e, 0x20, 0x44, 0x44, 0x2f, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName29[] = {0x0};
+static const sal_Unicode FormatKey30[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement30[] = {0};
+static const sal_Unicode FormatType30[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage30[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex30[] = {29};
+static const sal_Unicode FormatCode30[] = {0x4e, 0x4e, 0x20, 0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName30[] = {0x0};
+static const sal_Unicode FormatKey31[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement31[] = {0};
+static const sal_Unicode FormatType31[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage31[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex31[] = {30};
+static const sal_Unicode FormatCode31[] = {0x4e, 0x4e, 0x4e, 0x4e, 0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName31[] = {0x0};
+static const sal_Unicode FormatKey32[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x38, 0x0};
+static const sal_Unicode defaultFormatElement32[] = {0};
+static const sal_Unicode FormatType32[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage32[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex32[] = {31};
+static const sal_Unicode FormatCode32[] = {0x4d, 0x4d, 0x2d, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName32[] = {0x0};
+static const sal_Unicode FormatKey33[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x39, 0x0};
+static const sal_Unicode defaultFormatElement33[] = {0};
+static const sal_Unicode FormatType33[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage33[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex33[] = {32};
+static const sal_Unicode FormatCode33[] = {0x59, 0x59, 0x2d, 0x4d, 0x4d, 0x2d, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName33[] = {0x0};
+static const sal_Unicode FormatKey34[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x30, 0x0};
+static const sal_Unicode defaultFormatElement34[] = {0};
+static const sal_Unicode FormatType34[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage34[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex34[] = {33};
+static const sal_Unicode FormatCode34[] = {0x59, 0x59, 0x59, 0x59, 0x2d, 0x4d, 0x4d, 0x2d, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName34[] = {0x49, 0x53, 0x4f, 0x20, 0x38, 0x36, 0x30, 0x31, 0x0};
+static const sal_Unicode FormatKey35[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement35[] = {0};
+static const sal_Unicode FormatType35[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage35[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex35[] = {34};
+static const sal_Unicode FormatCode35[] = {0x59, 0x59, 0x2f, 0x4d, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName35[] = {0x0};
+static const sal_Unicode FormatKey36[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement36[] = {0};
+static const sal_Unicode FormatType36[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage36[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex36[] = {35};
+static const sal_Unicode FormatCode36[] = {0x4d, 0x4d, 0x4d, 0x20, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName36[] = {0x0};
+static const sal_Unicode FormatKey37[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement37[] = {0};
+static const sal_Unicode FormatType37[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage37[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex37[] = {36};
+static const sal_Unicode FormatCode37[] = {0x4d, 0x4d, 0x4d, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName37[] = {0x0};
+static const sal_Unicode FormatKey38[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement38[] = {0};
+static const sal_Unicode FormatType38[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage38[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex38[] = {37};
+static const sal_Unicode FormatCode38[] = {0x51, 0x51, 0x20, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName38[] = {0x0};
+static const sal_Unicode FormatKey39[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement39[] = {0};
+static const sal_Unicode FormatType39[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage39[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex39[] = {38};
+static const sal_Unicode FormatCode39[] = {0x57, 0x57, 0x0};
+static const sal_Unicode FormatDefaultName39[] = {0x0};
+static const sal_Unicode FormatKey40[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement40[] = {1};
+static const sal_Unicode FormatType40[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage40[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex40[] = {39};
+static const sal_Unicode FormatCode40[] = {0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName40[] = {0x0};
+static const sal_Unicode FormatKey41[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement41[] = {1};
+static const sal_Unicode FormatType41[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage41[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex41[] = {40};
+static const sal_Unicode FormatCode41[] = {0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x0};
+static const sal_Unicode FormatDefaultName41[] = {0x0};
+static const sal_Unicode FormatKey42[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement42[] = {0};
+static const sal_Unicode FormatType42[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage42[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex42[] = {41};
+static const sal_Unicode FormatCode42[] = {0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x20, 0x41, 0x4d, 0x2f, 0x50, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName42[] = {0x0};
+static const sal_Unicode FormatKey43[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement43[] = {0};
+static const sal_Unicode FormatType43[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage43[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex43[] = {42};
+static const sal_Unicode FormatCode43[] = {0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x20, 0x41, 0x4d, 0x2f, 0x50, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName43[] = {0x0};
+static const sal_Unicode FormatKey44[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement44[] = {0};
+static const sal_Unicode FormatType44[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage44[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex44[] = {43};
+static const sal_Unicode FormatCode44[] = {0x5b, 0x48, 0x48, 0x5d, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x0};
+static const sal_Unicode FormatDefaultName44[] = {0x0};
+static const sal_Unicode FormatKey45[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement45[] = {0};
+static const sal_Unicode FormatType45[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage45[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex45[] = {44};
+static const sal_Unicode FormatCode45[] = {0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName45[] = {0x0};
+static const sal_Unicode FormatKey46[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x37, 0x0};
+static const sal_Unicode defaultFormatElement46[] = {0};
+static const sal_Unicode FormatType46[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage46[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex46[] = {45};
+static const sal_Unicode FormatCode46[] = {0x5b, 0x48, 0x48, 0x5d, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName46[] = {0x0};
+static const sal_Unicode FormatKey47[] = {0x44, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement47[] = {1};
+static const sal_Unicode FormatType47[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage47[] = {0x44, 0x41, 0x54, 0x45, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex47[] = {46};
+static const sal_Unicode FormatCode47[] = {0x59, 0x59, 0x2f, 0x4d, 0x4d, 0x2f, 0x44, 0x44, 0x20, 0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName47[] = {0x0};
+static const sal_Unicode FormatKey48[] = {0x44, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement48[] = {0};
+static const sal_Unicode FormatType48[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage48[] = {0x44, 0x41, 0x54, 0x45, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex48[] = {47};
+static const sal_Unicode FormatCode48[] = {0x59, 0x59, 0x59, 0x59, 0x2f, 0x4d, 0x4d, 0x2f, 0x44, 0x44, 0x20, 0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x0};
+static const sal_Unicode FormatDefaultName48[] = {0x0};
+
+static const sal_Int16 FormatElementsCount0 = 49;
+static const sal_Unicode* FormatElementsArray0[] = {
+ FormatCode0,
+ FormatDefaultName0,
+ FormatKey0,
+ FormatType0,
+ FormatUsage0,
+ Formatindex0,
+ defaultFormatElement0,
+ FormatCode1,
+ FormatDefaultName1,
+ FormatKey1,
+ FormatType1,
+ FormatUsage1,
+ Formatindex1,
+ defaultFormatElement1,
+ FormatCode2,
+ FormatDefaultName2,
+ FormatKey2,
+ FormatType2,
+ FormatUsage2,
+ Formatindex2,
+ defaultFormatElement2,
+ FormatCode3,
+ FormatDefaultName3,
+ FormatKey3,
+ FormatType3,
+ FormatUsage3,
+ Formatindex3,
+ defaultFormatElement3,
+ FormatCode4,
+ FormatDefaultName4,
+ FormatKey4,
+ FormatType4,
+ FormatUsage4,
+ Formatindex4,
+ defaultFormatElement4,
+ FormatCode5,
+ FormatDefaultName5,
+ FormatKey5,
+ FormatType5,
+ FormatUsage5,
+ Formatindex5,
+ defaultFormatElement5,
+ FormatCode6,
+ FormatDefaultName6,
+ FormatKey6,
+ FormatType6,
+ FormatUsage6,
+ Formatindex6,
+ defaultFormatElement6,
+ FormatCode7,
+ FormatDefaultName7,
+ FormatKey7,
+ FormatType7,
+ FormatUsage7,
+ Formatindex7,
+ defaultFormatElement7,
+ FormatCode8,
+ FormatDefaultName8,
+ FormatKey8,
+ FormatType8,
+ FormatUsage8,
+ Formatindex8,
+ defaultFormatElement8,
+ FormatCode9,
+ FormatDefaultName9,
+ FormatKey9,
+ FormatType9,
+ FormatUsage9,
+ Formatindex9,
+ defaultFormatElement9,
+ FormatCode10,
+ FormatDefaultName10,
+ FormatKey10,
+ FormatType10,
+ FormatUsage10,
+ Formatindex10,
+ defaultFormatElement10,
+ FormatCode11,
+ FormatDefaultName11,
+ FormatKey11,
+ FormatType11,
+ FormatUsage11,
+ Formatindex11,
+ defaultFormatElement11,
+ FormatCode12,
+ FormatDefaultName12,
+ FormatKey12,
+ FormatType12,
+ FormatUsage12,
+ Formatindex12,
+ defaultFormatElement12,
+ FormatCode13,
+ FormatDefaultName13,
+ FormatKey13,
+ FormatType13,
+ FormatUsage13,
+ Formatindex13,
+ defaultFormatElement13,
+ FormatCode14,
+ FormatDefaultName14,
+ FormatKey14,
+ FormatType14,
+ FormatUsage14,
+ Formatindex14,
+ defaultFormatElement14,
+ FormatCode15,
+ FormatDefaultName15,
+ FormatKey15,
+ FormatType15,
+ FormatUsage15,
+ Formatindex15,
+ defaultFormatElement15,
+ FormatCode16,
+ FormatDefaultName16,
+ FormatKey16,
+ FormatType16,
+ FormatUsage16,
+ Formatindex16,
+ defaultFormatElement16,
+ FormatCode17,
+ FormatDefaultName17,
+ FormatKey17,
+ FormatType17,
+ FormatUsage17,
+ Formatindex17,
+ defaultFormatElement17,
+ FormatCode18,
+ FormatDefaultName18,
+ FormatKey18,
+ FormatType18,
+ FormatUsage18,
+ Formatindex18,
+ defaultFormatElement18,
+ FormatCode19,
+ FormatDefaultName19,
+ FormatKey19,
+ FormatType19,
+ FormatUsage19,
+ Formatindex19,
+ defaultFormatElement19,
+ FormatCode20,
+ FormatDefaultName20,
+ FormatKey20,
+ FormatType20,
+ FormatUsage20,
+ Formatindex20,
+ defaultFormatElement20,
+ FormatCode21,
+ FormatDefaultName21,
+ FormatKey21,
+ FormatType21,
+ FormatUsage21,
+ Formatindex21,
+ defaultFormatElement21,
+ FormatCode22,
+ FormatDefaultName22,
+ FormatKey22,
+ FormatType22,
+ FormatUsage22,
+ Formatindex22,
+ defaultFormatElement22,
+ FormatCode23,
+ FormatDefaultName23,
+ FormatKey23,
+ FormatType23,
+ FormatUsage23,
+ Formatindex23,
+ defaultFormatElement23,
+ FormatCode24,
+ FormatDefaultName24,
+ FormatKey24,
+ FormatType24,
+ FormatUsage24,
+ Formatindex24,
+ defaultFormatElement24,
+ FormatCode25,
+ FormatDefaultName25,
+ FormatKey25,
+ FormatType25,
+ FormatUsage25,
+ Formatindex25,
+ defaultFormatElement25,
+ FormatCode26,
+ FormatDefaultName26,
+ FormatKey26,
+ FormatType26,
+ FormatUsage26,
+ Formatindex26,
+ defaultFormatElement26,
+ FormatCode27,
+ FormatDefaultName27,
+ FormatKey27,
+ FormatType27,
+ FormatUsage27,
+ Formatindex27,
+ defaultFormatElement27,
+ FormatCode28,
+ FormatDefaultName28,
+ FormatKey28,
+ FormatType28,
+ FormatUsage28,
+ Formatindex28,
+ defaultFormatElement28,
+ FormatCode29,
+ FormatDefaultName29,
+ FormatKey29,
+ FormatType29,
+ FormatUsage29,
+ Formatindex29,
+ defaultFormatElement29,
+ FormatCode30,
+ FormatDefaultName30,
+ FormatKey30,
+ FormatType30,
+ FormatUsage30,
+ Formatindex30,
+ defaultFormatElement30,
+ FormatCode31,
+ FormatDefaultName31,
+ FormatKey31,
+ FormatType31,
+ FormatUsage31,
+ Formatindex31,
+ defaultFormatElement31,
+ FormatCode32,
+ FormatDefaultName32,
+ FormatKey32,
+ FormatType32,
+ FormatUsage32,
+ Formatindex32,
+ defaultFormatElement32,
+ FormatCode33,
+ FormatDefaultName33,
+ FormatKey33,
+ FormatType33,
+ FormatUsage33,
+ Formatindex33,
+ defaultFormatElement33,
+ FormatCode34,
+ FormatDefaultName34,
+ FormatKey34,
+ FormatType34,
+ FormatUsage34,
+ Formatindex34,
+ defaultFormatElement34,
+ FormatCode35,
+ FormatDefaultName35,
+ FormatKey35,
+ FormatType35,
+ FormatUsage35,
+ Formatindex35,
+ defaultFormatElement35,
+ FormatCode36,
+ FormatDefaultName36,
+ FormatKey36,
+ FormatType36,
+ FormatUsage36,
+ Formatindex36,
+ defaultFormatElement36,
+ FormatCode37,
+ FormatDefaultName37,
+ FormatKey37,
+ FormatType37,
+ FormatUsage37,
+ Formatindex37,
+ defaultFormatElement37,
+ FormatCode38,
+ FormatDefaultName38,
+ FormatKey38,
+ FormatType38,
+ FormatUsage38,
+ Formatindex38,
+ defaultFormatElement38,
+ FormatCode39,
+ FormatDefaultName39,
+ FormatKey39,
+ FormatType39,
+ FormatUsage39,
+ Formatindex39,
+ defaultFormatElement39,
+ FormatCode40,
+ FormatDefaultName40,
+ FormatKey40,
+ FormatType40,
+ FormatUsage40,
+ Formatindex40,
+ defaultFormatElement40,
+ FormatCode41,
+ FormatDefaultName41,
+ FormatKey41,
+ FormatType41,
+ FormatUsage41,
+ Formatindex41,
+ defaultFormatElement41,
+ FormatCode42,
+ FormatDefaultName42,
+ FormatKey42,
+ FormatType42,
+ FormatUsage42,
+ Formatindex42,
+ defaultFormatElement42,
+ FormatCode43,
+ FormatDefaultName43,
+ FormatKey43,
+ FormatType43,
+ FormatUsage43,
+ Formatindex43,
+ defaultFormatElement43,
+ FormatCode44,
+ FormatDefaultName44,
+ FormatKey44,
+ FormatType44,
+ FormatUsage44,
+ Formatindex44,
+ defaultFormatElement44,
+ FormatCode45,
+ FormatDefaultName45,
+ FormatKey45,
+ FormatType45,
+ FormatUsage45,
+ Formatindex45,
+ defaultFormatElement45,
+ FormatCode46,
+ FormatDefaultName46,
+ FormatKey46,
+ FormatType46,
+ FormatUsage46,
+ Formatindex46,
+ defaultFormatElement46,
+ FormatCode47,
+ FormatDefaultName47,
+ FormatKey47,
+ FormatType47,
+ FormatUsage47,
+ Formatindex47,
+ defaultFormatElement47,
+ FormatCode48,
+ FormatDefaultName48,
+ FormatKey48,
+ FormatType48,
+ FormatUsage48,
+ Formatindex48,
+ defaultFormatElement48,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode const * const * SAL_CALL getAllFormats0_en_NA(sal_Int16& count, const sal_Unicode*& from, const sal_Unicode*& to)
+{
+ count = FormatElementsCount0;
+ from = replaceFrom0;
+ to = replaceTo0;
+ return (sal_Unicode**)FormatElementsArray0;
+}
+static const sal_Unicode DateAcceptancePattern0[] = {0x59, 0x2f, 0x4d, 0x2f, 0x44, 0x0};
+static const sal_Unicode DateAcceptancePattern1[] = {0x4d, 0x2f, 0x44, 0x0};
+static const sal_Int16 DateAcceptancePatternsCount = 2;
+static const sal_Unicode* DateAcceptancePatternsArray[] = {
+ DateAcceptancePattern0,
+ DateAcceptancePattern1,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getDateAcceptancePatterns_en_NA(sal_Int16& count)
+{
+ count = DateAcceptancePatternsCount;
+ return (sal_Unicode**)DateAcceptancePatternsArray;
+}
+extern sal_Unicode ** SAL_CALL getCollatorImplementation_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollatorImplementation_en_NA(sal_Int16& count)
+{
+ return getCollatorImplementation_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getCollationOptions_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollationOptions_en_NA(sal_Int16& count)
+{
+ return getCollationOptions_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getSearchOptions_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getSearchOptions_en_NA(sal_Int16& count)
+{
+ return getSearchOptions_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getIndexAlgorithm_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getIndexAlgorithm_en_NA(sal_Int16& count)
+{
+ return getIndexAlgorithm_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getUnicodeScripts_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getUnicodeScripts_en_NA(sal_Int16& count)
+{
+ return getUnicodeScripts_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getFollowPageWords_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getFollowPageWords_en_NA(sal_Int16& count)
+{
+ return getFollowPageWords_en_US(count);
+}
+static const sal_Unicode calendarID0[] = {0x67, 0x72, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x61, 0x6e, 0x0};
+static const sal_Unicode defaultCalendar0[] = {1};
+static const sal_Unicode dayID00[] = {0x73, 0x75, 0x6e, 0x0};
+static const sal_Unicode dayDefaultAbbrvName00[] = {0x53, 0x75, 0x6e, 0x0};
+static const sal_Unicode dayDefaultFullName00[] = {0x53, 0x75, 0x6e, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName00[] = {0x53, 0x0};
+static const sal_Unicode dayID01[] = {0x6d, 0x6f, 0x6e, 0x0};
+static const sal_Unicode dayDefaultAbbrvName01[] = {0x4d, 0x6f, 0x6e, 0x0};
+static const sal_Unicode dayDefaultFullName01[] = {0x4d, 0x6f, 0x6e, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName01[] = {0x4d, 0x0};
+static const sal_Unicode dayID02[] = {0x74, 0x75, 0x65, 0x0};
+static const sal_Unicode dayDefaultAbbrvName02[] = {0x54, 0x75, 0x65, 0x0};
+static const sal_Unicode dayDefaultFullName02[] = {0x54, 0x75, 0x65, 0x73, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName02[] = {0x54, 0x0};
+static const sal_Unicode dayID03[] = {0x77, 0x65, 0x64, 0x0};
+static const sal_Unicode dayDefaultAbbrvName03[] = {0x57, 0x65, 0x64, 0x0};
+static const sal_Unicode dayDefaultFullName03[] = {0x57, 0x65, 0x64, 0x6e, 0x65, 0x73, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName03[] = {0x57, 0x0};
+static const sal_Unicode dayID04[] = {0x74, 0x68, 0x75, 0x0};
+static const sal_Unicode dayDefaultAbbrvName04[] = {0x54, 0x68, 0x75, 0x0};
+static const sal_Unicode dayDefaultFullName04[] = {0x54, 0x68, 0x75, 0x72, 0x73, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName04[] = {0x54, 0x0};
+static const sal_Unicode dayID05[] = {0x66, 0x72, 0x69, 0x0};
+static const sal_Unicode dayDefaultAbbrvName05[] = {0x46, 0x72, 0x69, 0x0};
+static const sal_Unicode dayDefaultFullName05[] = {0x46, 0x72, 0x69, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName05[] = {0x46, 0x0};
+static const sal_Unicode dayID06[] = {0x73, 0x61, 0x74, 0x0};
+static const sal_Unicode dayDefaultAbbrvName06[] = {0x53, 0x61, 0x74, 0x0};
+static const sal_Unicode dayDefaultFullName06[] = {0x53, 0x61, 0x74, 0x75, 0x72, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName06[] = {0x53, 0x0};
+static const sal_Unicode monthID00[] = {0x6a, 0x61, 0x6e, 0x0};
+static const sal_Unicode monthDefaultAbbrvName00[] = {0x4a, 0x61, 0x6e, 0x0};
+static const sal_Unicode monthDefaultFullName00[] = {0x4a, 0x61, 0x6e, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode monthDefaultNarrowName00[] = {0x4a, 0x0};
+static const sal_Unicode monthID01[] = {0x66, 0x65, 0x62, 0x0};
+static const sal_Unicode monthDefaultAbbrvName01[] = {0x46, 0x65, 0x62, 0x0};
+static const sal_Unicode monthDefaultFullName01[] = {0x46, 0x65, 0x62, 0x72, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode monthDefaultNarrowName01[] = {0x46, 0x0};
+static const sal_Unicode monthID02[] = {0x6d, 0x61, 0x72, 0x0};
+static const sal_Unicode monthDefaultAbbrvName02[] = {0x4d, 0x61, 0x72, 0x0};
+static const sal_Unicode monthDefaultFullName02[] = {0x4d, 0x61, 0x72, 0x63, 0x68, 0x0};
+static const sal_Unicode monthDefaultNarrowName02[] = {0x4d, 0x0};
+static const sal_Unicode monthID03[] = {0x61, 0x70, 0x72, 0x0};
+static const sal_Unicode monthDefaultAbbrvName03[] = {0x41, 0x70, 0x72, 0x0};
+static const sal_Unicode monthDefaultFullName03[] = {0x41, 0x70, 0x72, 0x69, 0x6c, 0x0};
+static const sal_Unicode monthDefaultNarrowName03[] = {0x41, 0x0};
+static const sal_Unicode monthID04[] = {0x6d, 0x61, 0x79, 0x0};
+static const sal_Unicode monthDefaultAbbrvName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode monthDefaultFullName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode monthDefaultNarrowName04[] = {0x4d, 0x0};
+static const sal_Unicode monthID05[] = {0x6a, 0x75, 0x6e, 0x0};
+static const sal_Unicode monthDefaultAbbrvName05[] = {0x4a, 0x75, 0x6e, 0x0};
+static const sal_Unicode monthDefaultFullName05[] = {0x4a, 0x75, 0x6e, 0x65, 0x0};
+static const sal_Unicode monthDefaultNarrowName05[] = {0x4a, 0x0};
+static const sal_Unicode monthID06[] = {0x6a, 0x75, 0x6c, 0x0};
+static const sal_Unicode monthDefaultAbbrvName06[] = {0x4a, 0x75, 0x6c, 0x0};
+static const sal_Unicode monthDefaultFullName06[] = {0x4a, 0x75, 0x6c, 0x79, 0x0};
+static const sal_Unicode monthDefaultNarrowName06[] = {0x4a, 0x0};
+static const sal_Unicode monthID07[] = {0x61, 0x75, 0x67, 0x0};
+static const sal_Unicode monthDefaultAbbrvName07[] = {0x41, 0x75, 0x67, 0x0};
+static const sal_Unicode monthDefaultFullName07[] = {0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x0};
+static const sal_Unicode monthDefaultNarrowName07[] = {0x41, 0x0};
+static const sal_Unicode monthID08[] = {0x73, 0x65, 0x70, 0x0};
+static const sal_Unicode monthDefaultAbbrvName08[] = {0x53, 0x65, 0x70, 0x0};
+static const sal_Unicode monthDefaultFullName08[] = {0x53, 0x65, 0x70, 0x74, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName08[] = {0x53, 0x0};
+static const sal_Unicode monthID09[] = {0x6f, 0x63, 0x74, 0x0};
+static const sal_Unicode monthDefaultAbbrvName09[] = {0x4f, 0x63, 0x74, 0x0};
+static const sal_Unicode monthDefaultFullName09[] = {0x4f, 0x63, 0x74, 0x6f, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName09[] = {0x4f, 0x0};
+static const sal_Unicode monthID010[] = {0x6e, 0x6f, 0x76, 0x0};
+static const sal_Unicode monthDefaultAbbrvName010[] = {0x4e, 0x6f, 0x76, 0x0};
+static const sal_Unicode monthDefaultFullName010[] = {0x4e, 0x6f, 0x76, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName010[] = {0x4e, 0x0};
+static const sal_Unicode monthID011[] = {0x64, 0x65, 0x63, 0x0};
+static const sal_Unicode monthDefaultAbbrvName011[] = {0x44, 0x65, 0x63, 0x0};
+static const sal_Unicode monthDefaultFullName011[] = {0x44, 0x65, 0x63, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName011[] = {0x44, 0x0};
+static const sal_Unicode genitiveMonthID00[] = {0x6a, 0x61, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName00[] = {0x4a, 0x61, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName00[] = {0x4a, 0x61, 0x6e, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName00[] = {0x4a, 0x0};
+static const sal_Unicode genitiveMonthID01[] = {0x66, 0x65, 0x62, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName01[] = {0x46, 0x65, 0x62, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName01[] = {0x46, 0x65, 0x62, 0x72, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName01[] = {0x46, 0x0};
+static const sal_Unicode genitiveMonthID02[] = {0x6d, 0x61, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName02[] = {0x4d, 0x61, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName02[] = {0x4d, 0x61, 0x72, 0x63, 0x68, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName02[] = {0x4d, 0x0};
+static const sal_Unicode genitiveMonthID03[] = {0x61, 0x70, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName03[] = {0x41, 0x70, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName03[] = {0x41, 0x70, 0x72, 0x69, 0x6c, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName03[] = {0x41, 0x0};
+static const sal_Unicode genitiveMonthID04[] = {0x6d, 0x61, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName04[] = {0x4d, 0x0};
+static const sal_Unicode genitiveMonthID05[] = {0x6a, 0x75, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName05[] = {0x4a, 0x75, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName05[] = {0x4a, 0x75, 0x6e, 0x65, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName05[] = {0x4a, 0x0};
+static const sal_Unicode genitiveMonthID06[] = {0x6a, 0x75, 0x6c, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName06[] = {0x4a, 0x75, 0x6c, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName06[] = {0x4a, 0x75, 0x6c, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName06[] = {0x4a, 0x0};
+static const sal_Unicode genitiveMonthID07[] = {0x61, 0x75, 0x67, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName07[] = {0x41, 0x75, 0x67, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName07[] = {0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName07[] = {0x41, 0x0};
+static const sal_Unicode genitiveMonthID08[] = {0x73, 0x65, 0x70, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName08[] = {0x53, 0x65, 0x70, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName08[] = {0x53, 0x65, 0x70, 0x74, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName08[] = {0x53, 0x0};
+static const sal_Unicode genitiveMonthID09[] = {0x6f, 0x63, 0x74, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName09[] = {0x4f, 0x63, 0x74, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName09[] = {0x4f, 0x63, 0x74, 0x6f, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName09[] = {0x4f, 0x0};
+static const sal_Unicode genitiveMonthID010[] = {0x6e, 0x6f, 0x76, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName010[] = {0x4e, 0x6f, 0x76, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName010[] = {0x4e, 0x6f, 0x76, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName010[] = {0x4e, 0x0};
+static const sal_Unicode genitiveMonthID011[] = {0x64, 0x65, 0x63, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName011[] = {0x44, 0x65, 0x63, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName011[] = {0x44, 0x65, 0x63, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName011[] = {0x44, 0x0};
+static const sal_Unicode partitiveMonthID00[] = {0x6a, 0x61, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName00[] = {0x4a, 0x61, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName00[] = {0x4a, 0x61, 0x6e, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName00[] = {0x4a, 0x0};
+static const sal_Unicode partitiveMonthID01[] = {0x66, 0x65, 0x62, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName01[] = {0x46, 0x65, 0x62, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName01[] = {0x46, 0x65, 0x62, 0x72, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName01[] = {0x46, 0x0};
+static const sal_Unicode partitiveMonthID02[] = {0x6d, 0x61, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName02[] = {0x4d, 0x61, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName02[] = {0x4d, 0x61, 0x72, 0x63, 0x68, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName02[] = {0x4d, 0x0};
+static const sal_Unicode partitiveMonthID03[] = {0x61, 0x70, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName03[] = {0x41, 0x70, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName03[] = {0x41, 0x70, 0x72, 0x69, 0x6c, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName03[] = {0x41, 0x0};
+static const sal_Unicode partitiveMonthID04[] = {0x6d, 0x61, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName04[] = {0x4d, 0x0};
+static const sal_Unicode partitiveMonthID05[] = {0x6a, 0x75, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName05[] = {0x4a, 0x75, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName05[] = {0x4a, 0x75, 0x6e, 0x65, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName05[] = {0x4a, 0x0};
+static const sal_Unicode partitiveMonthID06[] = {0x6a, 0x75, 0x6c, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName06[] = {0x4a, 0x75, 0x6c, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName06[] = {0x4a, 0x75, 0x6c, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName06[] = {0x4a, 0x0};
+static const sal_Unicode partitiveMonthID07[] = {0x61, 0x75, 0x67, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName07[] = {0x41, 0x75, 0x67, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName07[] = {0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName07[] = {0x41, 0x0};
+static const sal_Unicode partitiveMonthID08[] = {0x73, 0x65, 0x70, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName08[] = {0x53, 0x65, 0x70, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName08[] = {0x53, 0x65, 0x70, 0x74, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName08[] = {0x53, 0x0};
+static const sal_Unicode partitiveMonthID09[] = {0x6f, 0x63, 0x74, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName09[] = {0x4f, 0x63, 0x74, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName09[] = {0x4f, 0x63, 0x74, 0x6f, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName09[] = {0x4f, 0x0};
+static const sal_Unicode partitiveMonthID010[] = {0x6e, 0x6f, 0x76, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName010[] = {0x4e, 0x6f, 0x76, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName010[] = {0x4e, 0x6f, 0x76, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName010[] = {0x4e, 0x0};
+static const sal_Unicode partitiveMonthID011[] = {0x64, 0x65, 0x63, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName011[] = {0x44, 0x65, 0x63, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName011[] = {0x44, 0x65, 0x63, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName011[] = {0x44, 0x0};
+static const sal_Unicode eraID00[] = {0x62, 0x63, 0x0};
+
+static const sal_Unicode eraDefaultAbbrvName00[] = {0x42, 0x43, 0x0};
+static const sal_Unicode eraDefaultFullName00[] = {0x42, 0x43, 0x0};
+static const sal_Unicode eraID01[] = {0x61, 0x64, 0x0};
+
+static const sal_Unicode eraDefaultAbbrvName01[] = {0x41, 0x44, 0x0};
+static const sal_Unicode eraDefaultFullName01[] = {0x41, 0x44, 0x0};
+static const sal_Unicode startDayOfWeek0[] = {0x73, 0x75, 0x6e, 0x0};
+static const sal_Unicode minimalDaysInFirstWeek0[] = {1};
+static const sal_Int16 calendarsCount = 1;
+
+static const sal_Unicode nbOfDays[] = {7};
+static const sal_Unicode nbOfMonths[] = {12};
+static const sal_Unicode nbOfGenitiveMonths[] = {12};
+static const sal_Unicode nbOfPartitiveMonths[] = {12};
+static const sal_Unicode nbOfEras[] = {2};
+static const sal_Unicode* calendars[] = {
+ nbOfDays,
+ nbOfMonths,
+ nbOfGenitiveMonths,
+ nbOfPartitiveMonths,
+ nbOfEras,
+ calendarID0,
+ defaultCalendar0,
+ dayID00,
+ dayDefaultAbbrvName00,
+ dayDefaultFullName00,
+ dayDefaultNarrowName00,
+ dayID01,
+ dayDefaultAbbrvName01,
+ dayDefaultFullName01,
+ dayDefaultNarrowName01,
+ dayID02,
+ dayDefaultAbbrvName02,
+ dayDefaultFullName02,
+ dayDefaultNarrowName02,
+ dayID03,
+ dayDefaultAbbrvName03,
+ dayDefaultFullName03,
+ dayDefaultNarrowName03,
+ dayID04,
+ dayDefaultAbbrvName04,
+ dayDefaultFullName04,
+ dayDefaultNarrowName04,
+ dayID05,
+ dayDefaultAbbrvName05,
+ dayDefaultFullName05,
+ dayDefaultNarrowName05,
+ dayID06,
+ dayDefaultAbbrvName06,
+ dayDefaultFullName06,
+ dayDefaultNarrowName06,
+ monthID00,
+ monthDefaultAbbrvName00,
+ monthDefaultFullName00,
+ monthDefaultNarrowName00,
+ monthID01,
+ monthDefaultAbbrvName01,
+ monthDefaultFullName01,
+ monthDefaultNarrowName01,
+ monthID02,
+ monthDefaultAbbrvName02,
+ monthDefaultFullName02,
+ monthDefaultNarrowName02,
+ monthID03,
+ monthDefaultAbbrvName03,
+ monthDefaultFullName03,
+ monthDefaultNarrowName03,
+ monthID04,
+ monthDefaultAbbrvName04,
+ monthDefaultFullName04,
+ monthDefaultNarrowName04,
+ monthID05,
+ monthDefaultAbbrvName05,
+ monthDefaultFullName05,
+ monthDefaultNarrowName05,
+ monthID06,
+ monthDefaultAbbrvName06,
+ monthDefaultFullName06,
+ monthDefaultNarrowName06,
+ monthID07,
+ monthDefaultAbbrvName07,
+ monthDefaultFullName07,
+ monthDefaultNarrowName07,
+ monthID08,
+ monthDefaultAbbrvName08,
+ monthDefaultFullName08,
+ monthDefaultNarrowName08,
+ monthID09,
+ monthDefaultAbbrvName09,
+ monthDefaultFullName09,
+ monthDefaultNarrowName09,
+ monthID010,
+ monthDefaultAbbrvName010,
+ monthDefaultFullName010,
+ monthDefaultNarrowName010,
+ monthID011,
+ monthDefaultAbbrvName011,
+ monthDefaultFullName011,
+ monthDefaultNarrowName011,
+ genitiveMonthID00,
+ genitiveMonthDefaultAbbrvName00,
+ genitiveMonthDefaultFullName00,
+ genitiveMonthDefaultNarrowName00,
+ genitiveMonthID01,
+ genitiveMonthDefaultAbbrvName01,
+ genitiveMonthDefaultFullName01,
+ genitiveMonthDefaultNarrowName01,
+ genitiveMonthID02,
+ genitiveMonthDefaultAbbrvName02,
+ genitiveMonthDefaultFullName02,
+ genitiveMonthDefaultNarrowName02,
+ genitiveMonthID03,
+ genitiveMonthDefaultAbbrvName03,
+ genitiveMonthDefaultFullName03,
+ genitiveMonthDefaultNarrowName03,
+ genitiveMonthID04,
+ genitiveMonthDefaultAbbrvName04,
+ genitiveMonthDefaultFullName04,
+ genitiveMonthDefaultNarrowName04,
+ genitiveMonthID05,
+ genitiveMonthDefaultAbbrvName05,
+ genitiveMonthDefaultFullName05,
+ genitiveMonthDefaultNarrowName05,
+ genitiveMonthID06,
+ genitiveMonthDefaultAbbrvName06,
+ genitiveMonthDefaultFullName06,
+ genitiveMonthDefaultNarrowName06,
+ genitiveMonthID07,
+ genitiveMonthDefaultAbbrvName07,
+ genitiveMonthDefaultFullName07,
+ genitiveMonthDefaultNarrowName07,
+ genitiveMonthID08,
+ genitiveMonthDefaultAbbrvName08,
+ genitiveMonthDefaultFullName08,
+ genitiveMonthDefaultNarrowName08,
+ genitiveMonthID09,
+ genitiveMonthDefaultAbbrvName09,
+ genitiveMonthDefaultFullName09,
+ genitiveMonthDefaultNarrowName09,
+ genitiveMonthID010,
+ genitiveMonthDefaultAbbrvName010,
+ genitiveMonthDefaultFullName010,
+ genitiveMonthDefaultNarrowName010,
+ genitiveMonthID011,
+ genitiveMonthDefaultAbbrvName011,
+ genitiveMonthDefaultFullName011,
+ genitiveMonthDefaultNarrowName011,
+ partitiveMonthID00,
+ partitiveMonthDefaultAbbrvName00,
+ partitiveMonthDefaultFullName00,
+ partitiveMonthDefaultNarrowName00,
+ partitiveMonthID01,
+ partitiveMonthDefaultAbbrvName01,
+ partitiveMonthDefaultFullName01,
+ partitiveMonthDefaultNarrowName01,
+ partitiveMonthID02,
+ partitiveMonthDefaultAbbrvName02,
+ partitiveMonthDefaultFullName02,
+ partitiveMonthDefaultNarrowName02,
+ partitiveMonthID03,
+ partitiveMonthDefaultAbbrvName03,
+ partitiveMonthDefaultFullName03,
+ partitiveMonthDefaultNarrowName03,
+ partitiveMonthID04,
+ partitiveMonthDefaultAbbrvName04,
+ partitiveMonthDefaultFullName04,
+ partitiveMonthDefaultNarrowName04,
+ partitiveMonthID05,
+ partitiveMonthDefaultAbbrvName05,
+ partitiveMonthDefaultFullName05,
+ partitiveMonthDefaultNarrowName05,
+ partitiveMonthID06,
+ partitiveMonthDefaultAbbrvName06,
+ partitiveMonthDefaultFullName06,
+ partitiveMonthDefaultNarrowName06,
+ partitiveMonthID07,
+ partitiveMonthDefaultAbbrvName07,
+ partitiveMonthDefaultFullName07,
+ partitiveMonthDefaultNarrowName07,
+ partitiveMonthID08,
+ partitiveMonthDefaultAbbrvName08,
+ partitiveMonthDefaultFullName08,
+ partitiveMonthDefaultNarrowName08,
+ partitiveMonthID09,
+ partitiveMonthDefaultAbbrvName09,
+ partitiveMonthDefaultFullName09,
+ partitiveMonthDefaultNarrowName09,
+ partitiveMonthID010,
+ partitiveMonthDefaultAbbrvName010,
+ partitiveMonthDefaultFullName010,
+ partitiveMonthDefaultNarrowName010,
+ partitiveMonthID011,
+ partitiveMonthDefaultAbbrvName011,
+ partitiveMonthDefaultFullName011,
+ partitiveMonthDefaultNarrowName011,
+ eraID00,
+ eraDefaultAbbrvName00,
+ eraDefaultFullName00,
+ eraID01,
+ eraDefaultAbbrvName01,
+ eraDefaultFullName01,
+ startDayOfWeek0,
+ minimalDaysInFirstWeek0,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCalendars_en_NA(sal_Int16& count)
+{
+ count = calendarsCount;
+ return (sal_Unicode**)calendars;
+}
+static const sal_Unicode defaultCurrency0[] = {1};
+static const sal_Unicode defaultCurrencyUsedInCompatibleFormatCodes0[] = {1};
+static const sal_Unicode defaultCurrencyLegacyOnly0[] = {0};
+static const sal_Unicode currencyID0[] = {0x4e, 0x41, 0x44, 0x0};
+static const sal_Unicode currencySymbol0[] = {0x24, 0x0};
+static const sal_Unicode bankSymbol0[] = {0x4e, 0x41, 0x44, 0x0};
+static const sal_Unicode currencyName0[] = {0x4e, 0x61, 0x6d, 0x69, 0x62, 0x69, 0x61, 0x6e, 0x20, 0x44, 0x6f, 0x6c, 0x6c, 0x61, 0x72, 0x0};
+static const sal_Unicode currencyDecimalPlaces0[] = {2};
+
+static const sal_Unicode defaultCurrency1[] = {0};
+static const sal_Unicode defaultCurrencyUsedInCompatibleFormatCodes1[] = {0};
+static const sal_Unicode defaultCurrencyLegacyOnly1[] = {0};
+static const sal_Unicode currencyID1[] = {0x5a, 0x41, 0x52, 0x0};
+static const sal_Unicode currencySymbol1[] = {0x52, 0x0};
+static const sal_Unicode bankSymbol1[] = {0x5a, 0x41, 0x52, 0x0};
+static const sal_Unicode currencyName1[] = {0x53, 0x6f, 0x75, 0x74, 0x68, 0x20, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x6e, 0x20, 0x52, 0x61, 0x6e, 0x64, 0x0};
+static const sal_Unicode currencyDecimalPlaces1[] = {2};
+
+static const sal_Int16 currencyCount = 2;
+
+static const sal_Unicode* currencies[] = {
+ currencyID0,
+ currencySymbol0,
+ bankSymbol0,
+ currencyName0,
+ defaultCurrency0,
+ defaultCurrencyUsedInCompatibleFormatCodes0,
+ currencyDecimalPlaces0,
+ defaultCurrencyLegacyOnly0,
+ currencyID1,
+ currencySymbol1,
+ bankSymbol1,
+ currencyName1,
+ defaultCurrency1,
+ defaultCurrencyUsedInCompatibleFormatCodes1,
+ currencyDecimalPlaces1,
+ defaultCurrencyLegacyOnly1,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCurrencies_en_NA(sal_Int16& count)
+{
+ count = currencyCount;
+ return (sal_Unicode**)currencies;
+}
+extern sal_Unicode ** SAL_CALL getTransliterations_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getTransliterations_en_NA(sal_Int16& count)
+{
+ return getTransliterations_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getForbiddenCharacters_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getForbiddenCharacters_en_NA(sal_Int16& count)
+{
+ return getForbiddenCharacters_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getBreakIteratorRules_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getBreakIteratorRules_en_NA(sal_Int16& count)
+{
+ return getBreakIteratorRules_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getReservedWords_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getReservedWords_en_NA(sal_Int16& count)
+{
+ return getReservedWords_en_US(count);
+}
+// ---> ContinuousNumbering
+extern const sal_Unicode *** SAL_CALL getContinuousNumberingLevels_en_US(sal_Int16& nStyles, sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode *** SAL_CALL getContinuousNumberingLevels_en_NA(sal_Int16& nStyles, sal_Int16& nAttributes)
+{
+ return getContinuousNumberingLevels_en_US(nStyles, nAttributes);
+}
+// ---> OutlineNumbering
+extern const sal_Unicode **** SAL_CALL getOutlineNumberingLevels_en_US(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode **** SAL_CALL getOutlineNumberingLevels_en_NA(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes)
+{
+ return getOutlineNumberingLevels_en_US(nStyles, nLevels, nAttributes);
+}
+} // extern "C"
+
diff --git a/vcl/workben/localestub/localedata_en_NG.cxx b/vcl/workben/localestub/localedata_en_NG.cxx
new file mode 100644
index 000000000..5d3514516
--- /dev/null
+++ b/vcl/workben/localestub/localedata_en_NG.cxx
@@ -0,0 +1,142 @@
+#include <sal/types.h>
+
+#include <stdio.h>
+
+extern "C" {
+
+static const sal_Unicode langID[] = { 0x65, 0x6e, 0x0 };
+static const sal_Unicode langDefaultName[] = { 0x45, 0x6e, 0x67, 0x6c, 0x69, 0x73, 0x68, 0x0 };
+static const sal_Unicode countryID[] = { 0x4e, 0x47, 0x0 };
+static const sal_Unicode countryDefaultName[] = { 0x4e, 0x69, 0x67, 0x65, 0x72, 0x69, 0x61, 0x0 };
+static const sal_Unicode Variant[] = { 0x0 };
+
+static const sal_Unicode* LCInfoArray[]
+ = { langID, langDefaultName, countryID, countryDefaultName, Variant };
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getLCInfo_en_NG(sal_Int16& count)
+{
+ count = SAL_N_ELEMENTS(LCInfoArray);
+ return (sal_Unicode**)LCInfoArray;
+}
+extern sal_Unicode** SAL_CALL getLocaleItem_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getLocaleItem_en_NG(sal_Int16& count)
+{
+ return getLocaleItem_en_GB(count);
+}
+static const sal_Unicode replaceTo0[]
+ = { 0x5b, 0x24, 0x20a6, 0x2d, 0x41, 0x38, 0x30, 0x39, 0x5d, 0x0 };
+extern sal_Unicode const* const* SAL_CALL getAllFormats0_en_GB(sal_Int16& count,
+ const sal_Unicode*& from,
+ const sal_Unicode*& to);
+SAL_DLLPUBLIC_EXPORT sal_Unicode const* const* SAL_CALL
+getAllFormats0_en_NG(sal_Int16& count, const sal_Unicode*& from, const sal_Unicode*& to)
+{
+ to = replaceTo0;
+ const sal_Unicode* tmp;
+ return getAllFormats0_en_GB(count, from, tmp);
+}
+extern sal_Unicode** SAL_CALL getDateAcceptancePatterns_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getDateAcceptancePatterns_en_NG(sal_Int16& count)
+{
+ return getDateAcceptancePatterns_en_GB(count);
+}
+extern sal_Unicode** SAL_CALL getCollatorImplementation_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getCollatorImplementation_en_NG(sal_Int16& count)
+{
+ return getCollatorImplementation_en_GB(count);
+}
+extern sal_Unicode** SAL_CALL getCollationOptions_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getCollationOptions_en_NG(sal_Int16& count)
+{
+ return getCollationOptions_en_GB(count);
+}
+extern sal_Unicode** SAL_CALL getSearchOptions_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getSearchOptions_en_NG(sal_Int16& count)
+{
+ return getSearchOptions_en_GB(count);
+}
+extern sal_Unicode** SAL_CALL getIndexAlgorithm_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getIndexAlgorithm_en_NG(sal_Int16& count)
+{
+ return getIndexAlgorithm_en_GB(count);
+}
+extern sal_Unicode** SAL_CALL getUnicodeScripts_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getUnicodeScripts_en_NG(sal_Int16& count)
+{
+ return getUnicodeScripts_en_GB(count);
+}
+extern sal_Unicode** SAL_CALL getFollowPageWords_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getFollowPageWords_en_NG(sal_Int16& count)
+{
+ return getFollowPageWords_en_GB(count);
+}
+extern sal_Unicode** SAL_CALL getAllCalendars_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getAllCalendars_en_NG(sal_Int16& count)
+{
+ return getAllCalendars_en_GB(count);
+}
+static const sal_Unicode defaultCurrency0[] = { 1 };
+static const sal_Unicode defaultCurrencyUsedInCompatibleFormatCodes0[] = { 1 };
+static const sal_Unicode defaultCurrencyLegacyOnly0[] = { 0 };
+static const sal_Unicode currencyID0[] = { 0x4e, 0x47, 0x4e, 0x0 };
+static const sal_Unicode currencySymbol0[] = { 0x20a6, 0x0 };
+static const sal_Unicode bankSymbol0[] = { 0x4e, 0x47, 0x4e, 0x0 };
+static const sal_Unicode currencyName0[] = { 0x4e, 0x61, 0x69, 0x72, 0x61, 0x0 };
+static const sal_Unicode currencyDecimalPlaces0[] = { 2 };
+
+static const sal_Int16 currencyCount = 1;
+
+static const sal_Unicode* currencies[] = {
+ currencyID0,
+ currencySymbol0,
+ bankSymbol0,
+ currencyName0,
+ defaultCurrency0,
+ defaultCurrencyUsedInCompatibleFormatCodes0,
+ currencyDecimalPlaces0,
+ defaultCurrencyLegacyOnly0,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getAllCurrencies_en_NG(sal_Int16& count)
+{
+ count = currencyCount;
+ return (sal_Unicode**)currencies;
+}
+extern sal_Unicode** SAL_CALL getTransliterations_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getTransliterations_en_NG(sal_Int16& count)
+{
+ return getTransliterations_en_GB(count);
+}
+extern sal_Unicode** SAL_CALL getForbiddenCharacters_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getForbiddenCharacters_en_NG(sal_Int16& count)
+{
+ return getForbiddenCharacters_en_GB(count);
+}
+extern sal_Unicode** SAL_CALL getBreakIteratorRules_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getBreakIteratorRules_en_NG(sal_Int16& count)
+{
+ return getBreakIteratorRules_en_GB(count);
+}
+extern sal_Unicode** SAL_CALL getReservedWords_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getReservedWords_en_NG(sal_Int16& count)
+{
+ return getReservedWords_en_GB(count);
+}
+// ---> ContinuousNumbering
+extern const sal_Unicode*** SAL_CALL getContinuousNumberingLevels_en_GB(sal_Int16& nStyles,
+ sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode*** SAL_CALL
+getContinuousNumberingLevels_en_NG(sal_Int16& nStyles, sal_Int16& nAttributes)
+{
+ return getContinuousNumberingLevels_en_GB(nStyles, nAttributes);
+}
+// ---> OutlineNumbering
+extern const sal_Unicode**** SAL_CALL getOutlineNumberingLevels_en_GB(sal_Int16& nStyles,
+ sal_Int16& nLevels,
+ sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode**** SAL_CALL
+getOutlineNumberingLevels_en_NG(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes)
+{
+ return getOutlineNumberingLevels_en_GB(nStyles, nLevels, nAttributes);
+}
+} // extern "C"
diff --git a/vcl/workben/localestub/localedata_en_NZ.cxx b/vcl/workben/localestub/localedata_en_NZ.cxx
new file mode 100644
index 000000000..fc63a44ff
--- /dev/null
+++ b/vcl/workben/localestub/localedata_en_NZ.cxx
@@ -0,0 +1,563 @@
+#include <sal/types.h>
+
+
+#include <stdio.h>
+
+extern "C" {
+
+static const sal_Unicode langID[] = {0x65, 0x6e, 0x0};
+static const sal_Unicode langDefaultName[] = {0x45, 0x6e, 0x67, 0x6c, 0x69, 0x73, 0x68, 0x0};
+static const sal_Unicode countryID[] = {0x4e, 0x5a, 0x0};
+static const sal_Unicode countryDefaultName[] = {0x4e, 0x65, 0x77, 0x20, 0x5a, 0x65, 0x61, 0x6c, 0x61, 0x6e, 0x64, 0x0};
+static const sal_Unicode Variant[] = {0x0};
+
+static const sal_Unicode* LCInfoArray[] = {
+ langID,
+ langDefaultName,
+ countryID,
+ countryDefaultName,
+ Variant
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLCInfo_en_NZ(sal_Int16& count)
+{
+ count = 0;
+ return (sal_Unicode**)LCInfoArray;
+}
+
+
+static const sal_Unicode LC_CTYPE_Unoid[] = {0x0};
+static const sal_Unicode dateSeparator[] = {0x2f, 0x0};
+static const sal_Unicode thousandSeparator[] = {0x2c, 0x0};
+static const sal_Unicode decimalSeparator[] = {0x2e, 0x0};
+static const sal_Unicode timeSeparator[] = {0x3a, 0x0};
+static const sal_Unicode time100SecSeparator[] = {0x2e, 0x0};
+static const sal_Unicode listSeparator[] = {0x3b, 0x0};
+static const sal_Unicode LongDateDayOfWeekSeparator[] = {0x2c, 0x20, 0x0};
+static const sal_Unicode LongDateDaySeparator[] = {0x20, 0x0};
+static const sal_Unicode LongDateMonthSeparator[] = {0x20, 0x0};
+static const sal_Unicode LongDateYearSeparator[] = {0x20, 0x0};
+static const sal_Unicode quotationStart[] = {0x2018, 0x0};
+static const sal_Unicode quotationEnd[] = {0x2019, 0x0};
+static const sal_Unicode doubleQuotationStart[] = {0x201c, 0x0};
+static const sal_Unicode doubleQuotationEnd[] = {0x201d, 0x0};
+static const sal_Unicode timeAM[] = {0x41, 0x4d, 0x0};
+static const sal_Unicode timePM[] = {0x50, 0x4d, 0x0};
+static const sal_Unicode measurementSystem[] = {0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x0};
+
+static const sal_Unicode* LCType[] = {
+ LC_CTYPE_Unoid,
+ dateSeparator,
+ thousandSeparator,
+ decimalSeparator,
+ timeSeparator,
+ time100SecSeparator,
+ listSeparator,
+ quotationStart,
+ quotationEnd,
+ doubleQuotationStart,
+ doubleQuotationEnd,
+ timeAM,
+ timePM,
+ measurementSystem,
+ LongDateDayOfWeekSeparator,
+ LongDateDaySeparator,
+ LongDateMonthSeparator,
+ LongDateYearSeparator
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLocaleItem_en_NZ(sal_Int16& count)
+{
+ count = 0;
+ return (sal_Unicode**)LCType;
+}
+static const sal_Unicode replaceTo0[] = {0x5b, 0x24, 0x24, 0x2d, 0x31, 0x34, 0x30, 0x39, 0x5d, 0x0};
+extern sal_Unicode const * const * SAL_CALL getAllFormats0_en_AU(sal_Int16& count, const sal_Unicode*& from, const sal_Unicode*& to);
+SAL_DLLPUBLIC_EXPORT sal_Unicode const * const * SAL_CALL getAllFormats0_en_NZ(sal_Int16& count, const sal_Unicode*& from, const sal_Unicode*& to)
+{
+ to = replaceTo0;
+ const sal_Unicode* tmp;
+ return getAllFormats0_en_AU(count, from, tmp);
+}
+extern sal_Unicode ** SAL_CALL getDateAcceptancePatterns_en_AU(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getDateAcceptancePatterns_en_NZ(sal_Int16& count)
+{
+ return getDateAcceptancePatterns_en_AU(count);
+}
+extern sal_Unicode ** SAL_CALL getCollatorImplementation_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollatorImplementation_en_NZ(sal_Int16& count)
+{
+ return getCollatorImplementation_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getCollationOptions_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollationOptions_en_NZ(sal_Int16& count)
+{
+ return getCollationOptions_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getSearchOptions_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getSearchOptions_en_NZ(sal_Int16& count)
+{
+ return getSearchOptions_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getIndexAlgorithm_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getIndexAlgorithm_en_NZ(sal_Int16& count)
+{
+ return getIndexAlgorithm_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getUnicodeScripts_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getUnicodeScripts_en_NZ(sal_Int16& count)
+{
+ return getUnicodeScripts_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getFollowPageWords_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getFollowPageWords_en_NZ(sal_Int16& count)
+{
+ return getFollowPageWords_en_US(count);
+}
+static const sal_Unicode calendarID0[] = {0x67, 0x72, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x61, 0x6e, 0x0};
+static const sal_Unicode defaultCalendar0[] = {1};
+static const sal_Unicode dayID00[] = {0x73, 0x75, 0x6e, 0x0};
+static const sal_Unicode dayDefaultAbbrvName00[] = {0x53, 0x75, 0x6e, 0x0};
+static const sal_Unicode dayDefaultFullName00[] = {0x53, 0x75, 0x6e, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName00[] = {0x53, 0x0};
+static const sal_Unicode dayID01[] = {0x6d, 0x6f, 0x6e, 0x0};
+static const sal_Unicode dayDefaultAbbrvName01[] = {0x4d, 0x6f, 0x6e, 0x0};
+static const sal_Unicode dayDefaultFullName01[] = {0x4d, 0x6f, 0x6e, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName01[] = {0x4d, 0x0};
+static const sal_Unicode dayID02[] = {0x74, 0x75, 0x65, 0x0};
+static const sal_Unicode dayDefaultAbbrvName02[] = {0x54, 0x75, 0x65, 0x0};
+static const sal_Unicode dayDefaultFullName02[] = {0x54, 0x75, 0x65, 0x73, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName02[] = {0x54, 0x0};
+static const sal_Unicode dayID03[] = {0x77, 0x65, 0x64, 0x0};
+static const sal_Unicode dayDefaultAbbrvName03[] = {0x57, 0x65, 0x64, 0x0};
+static const sal_Unicode dayDefaultFullName03[] = {0x57, 0x65, 0x64, 0x6e, 0x65, 0x73, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName03[] = {0x57, 0x0};
+static const sal_Unicode dayID04[] = {0x74, 0x68, 0x75, 0x0};
+static const sal_Unicode dayDefaultAbbrvName04[] = {0x54, 0x68, 0x75, 0x0};
+static const sal_Unicode dayDefaultFullName04[] = {0x54, 0x68, 0x75, 0x72, 0x73, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName04[] = {0x54, 0x0};
+static const sal_Unicode dayID05[] = {0x66, 0x72, 0x69, 0x0};
+static const sal_Unicode dayDefaultAbbrvName05[] = {0x46, 0x72, 0x69, 0x0};
+static const sal_Unicode dayDefaultFullName05[] = {0x46, 0x72, 0x69, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName05[] = {0x46, 0x0};
+static const sal_Unicode dayID06[] = {0x73, 0x61, 0x74, 0x0};
+static const sal_Unicode dayDefaultAbbrvName06[] = {0x53, 0x61, 0x74, 0x0};
+static const sal_Unicode dayDefaultFullName06[] = {0x53, 0x61, 0x74, 0x75, 0x72, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName06[] = {0x53, 0x0};
+static const sal_Unicode monthID00[] = {0x6a, 0x61, 0x6e, 0x0};
+static const sal_Unicode monthDefaultAbbrvName00[] = {0x4a, 0x61, 0x6e, 0x0};
+static const sal_Unicode monthDefaultFullName00[] = {0x4a, 0x61, 0x6e, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode monthDefaultNarrowName00[] = {0x4a, 0x0};
+static const sal_Unicode monthID01[] = {0x66, 0x65, 0x62, 0x0};
+static const sal_Unicode monthDefaultAbbrvName01[] = {0x46, 0x65, 0x62, 0x0};
+static const sal_Unicode monthDefaultFullName01[] = {0x46, 0x65, 0x62, 0x72, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode monthDefaultNarrowName01[] = {0x46, 0x0};
+static const sal_Unicode monthID02[] = {0x6d, 0x61, 0x72, 0x0};
+static const sal_Unicode monthDefaultAbbrvName02[] = {0x4d, 0x61, 0x72, 0x0};
+static const sal_Unicode monthDefaultFullName02[] = {0x4d, 0x61, 0x72, 0x63, 0x68, 0x0};
+static const sal_Unicode monthDefaultNarrowName02[] = {0x4d, 0x0};
+static const sal_Unicode monthID03[] = {0x61, 0x70, 0x72, 0x0};
+static const sal_Unicode monthDefaultAbbrvName03[] = {0x41, 0x70, 0x72, 0x0};
+static const sal_Unicode monthDefaultFullName03[] = {0x41, 0x70, 0x72, 0x69, 0x6c, 0x0};
+static const sal_Unicode monthDefaultNarrowName03[] = {0x41, 0x0};
+static const sal_Unicode monthID04[] = {0x6d, 0x61, 0x79, 0x0};
+static const sal_Unicode monthDefaultAbbrvName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode monthDefaultFullName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode monthDefaultNarrowName04[] = {0x4d, 0x0};
+static const sal_Unicode monthID05[] = {0x6a, 0x75, 0x6e, 0x0};
+static const sal_Unicode monthDefaultAbbrvName05[] = {0x4a, 0x75, 0x6e, 0x0};
+static const sal_Unicode monthDefaultFullName05[] = {0x4a, 0x75, 0x6e, 0x65, 0x0};
+static const sal_Unicode monthDefaultNarrowName05[] = {0x4a, 0x0};
+static const sal_Unicode monthID06[] = {0x6a, 0x75, 0x6c, 0x0};
+static const sal_Unicode monthDefaultAbbrvName06[] = {0x4a, 0x75, 0x6c, 0x0};
+static const sal_Unicode monthDefaultFullName06[] = {0x4a, 0x75, 0x6c, 0x79, 0x0};
+static const sal_Unicode monthDefaultNarrowName06[] = {0x4a, 0x0};
+static const sal_Unicode monthID07[] = {0x61, 0x75, 0x67, 0x0};
+static const sal_Unicode monthDefaultAbbrvName07[] = {0x41, 0x75, 0x67, 0x0};
+static const sal_Unicode monthDefaultFullName07[] = {0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x0};
+static const sal_Unicode monthDefaultNarrowName07[] = {0x41, 0x0};
+static const sal_Unicode monthID08[] = {0x73, 0x65, 0x70, 0x0};
+static const sal_Unicode monthDefaultAbbrvName08[] = {0x53, 0x65, 0x70, 0x0};
+static const sal_Unicode monthDefaultFullName08[] = {0x53, 0x65, 0x70, 0x74, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName08[] = {0x53, 0x0};
+static const sal_Unicode monthID09[] = {0x6f, 0x63, 0x74, 0x0};
+static const sal_Unicode monthDefaultAbbrvName09[] = {0x4f, 0x63, 0x74, 0x0};
+static const sal_Unicode monthDefaultFullName09[] = {0x4f, 0x63, 0x74, 0x6f, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName09[] = {0x4f, 0x0};
+static const sal_Unicode monthID010[] = {0x6e, 0x6f, 0x76, 0x0};
+static const sal_Unicode monthDefaultAbbrvName010[] = {0x4e, 0x6f, 0x76, 0x0};
+static const sal_Unicode monthDefaultFullName010[] = {0x4e, 0x6f, 0x76, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName010[] = {0x4e, 0x0};
+static const sal_Unicode monthID011[] = {0x64, 0x65, 0x63, 0x0};
+static const sal_Unicode monthDefaultAbbrvName011[] = {0x44, 0x65, 0x63, 0x0};
+static const sal_Unicode monthDefaultFullName011[] = {0x44, 0x65, 0x63, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName011[] = {0x44, 0x0};
+static const sal_Unicode genitiveMonthID00[] = {0x6a, 0x61, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName00[] = {0x4a, 0x61, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName00[] = {0x4a, 0x61, 0x6e, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName00[] = {0x4a, 0x0};
+static const sal_Unicode genitiveMonthID01[] = {0x66, 0x65, 0x62, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName01[] = {0x46, 0x65, 0x62, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName01[] = {0x46, 0x65, 0x62, 0x72, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName01[] = {0x46, 0x0};
+static const sal_Unicode genitiveMonthID02[] = {0x6d, 0x61, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName02[] = {0x4d, 0x61, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName02[] = {0x4d, 0x61, 0x72, 0x63, 0x68, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName02[] = {0x4d, 0x0};
+static const sal_Unicode genitiveMonthID03[] = {0x61, 0x70, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName03[] = {0x41, 0x70, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName03[] = {0x41, 0x70, 0x72, 0x69, 0x6c, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName03[] = {0x41, 0x0};
+static const sal_Unicode genitiveMonthID04[] = {0x6d, 0x61, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName04[] = {0x4d, 0x0};
+static const sal_Unicode genitiveMonthID05[] = {0x6a, 0x75, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName05[] = {0x4a, 0x75, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName05[] = {0x4a, 0x75, 0x6e, 0x65, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName05[] = {0x4a, 0x0};
+static const sal_Unicode genitiveMonthID06[] = {0x6a, 0x75, 0x6c, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName06[] = {0x4a, 0x75, 0x6c, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName06[] = {0x4a, 0x75, 0x6c, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName06[] = {0x4a, 0x0};
+static const sal_Unicode genitiveMonthID07[] = {0x61, 0x75, 0x67, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName07[] = {0x41, 0x75, 0x67, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName07[] = {0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName07[] = {0x41, 0x0};
+static const sal_Unicode genitiveMonthID08[] = {0x73, 0x65, 0x70, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName08[] = {0x53, 0x65, 0x70, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName08[] = {0x53, 0x65, 0x70, 0x74, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName08[] = {0x53, 0x0};
+static const sal_Unicode genitiveMonthID09[] = {0x6f, 0x63, 0x74, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName09[] = {0x4f, 0x63, 0x74, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName09[] = {0x4f, 0x63, 0x74, 0x6f, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName09[] = {0x4f, 0x0};
+static const sal_Unicode genitiveMonthID010[] = {0x6e, 0x6f, 0x76, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName010[] = {0x4e, 0x6f, 0x76, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName010[] = {0x4e, 0x6f, 0x76, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName010[] = {0x4e, 0x0};
+static const sal_Unicode genitiveMonthID011[] = {0x64, 0x65, 0x63, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName011[] = {0x44, 0x65, 0x63, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName011[] = {0x44, 0x65, 0x63, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName011[] = {0x44, 0x0};
+static const sal_Unicode partitiveMonthID00[] = {0x6a, 0x61, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName00[] = {0x4a, 0x61, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName00[] = {0x4a, 0x61, 0x6e, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName00[] = {0x4a, 0x0};
+static const sal_Unicode partitiveMonthID01[] = {0x66, 0x65, 0x62, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName01[] = {0x46, 0x65, 0x62, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName01[] = {0x46, 0x65, 0x62, 0x72, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName01[] = {0x46, 0x0};
+static const sal_Unicode partitiveMonthID02[] = {0x6d, 0x61, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName02[] = {0x4d, 0x61, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName02[] = {0x4d, 0x61, 0x72, 0x63, 0x68, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName02[] = {0x4d, 0x0};
+static const sal_Unicode partitiveMonthID03[] = {0x61, 0x70, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName03[] = {0x41, 0x70, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName03[] = {0x41, 0x70, 0x72, 0x69, 0x6c, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName03[] = {0x41, 0x0};
+static const sal_Unicode partitiveMonthID04[] = {0x6d, 0x61, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName04[] = {0x4d, 0x0};
+static const sal_Unicode partitiveMonthID05[] = {0x6a, 0x75, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName05[] = {0x4a, 0x75, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName05[] = {0x4a, 0x75, 0x6e, 0x65, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName05[] = {0x4a, 0x0};
+static const sal_Unicode partitiveMonthID06[] = {0x6a, 0x75, 0x6c, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName06[] = {0x4a, 0x75, 0x6c, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName06[] = {0x4a, 0x75, 0x6c, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName06[] = {0x4a, 0x0};
+static const sal_Unicode partitiveMonthID07[] = {0x61, 0x75, 0x67, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName07[] = {0x41, 0x75, 0x67, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName07[] = {0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName07[] = {0x41, 0x0};
+static const sal_Unicode partitiveMonthID08[] = {0x73, 0x65, 0x70, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName08[] = {0x53, 0x65, 0x70, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName08[] = {0x53, 0x65, 0x70, 0x74, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName08[] = {0x53, 0x0};
+static const sal_Unicode partitiveMonthID09[] = {0x6f, 0x63, 0x74, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName09[] = {0x4f, 0x63, 0x74, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName09[] = {0x4f, 0x63, 0x74, 0x6f, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName09[] = {0x4f, 0x0};
+static const sal_Unicode partitiveMonthID010[] = {0x6e, 0x6f, 0x76, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName010[] = {0x4e, 0x6f, 0x76, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName010[] = {0x4e, 0x6f, 0x76, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName010[] = {0x4e, 0x0};
+static const sal_Unicode partitiveMonthID011[] = {0x64, 0x65, 0x63, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName011[] = {0x44, 0x65, 0x63, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName011[] = {0x44, 0x65, 0x63, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName011[] = {0x44, 0x0};
+static const sal_Unicode eraID00[] = {0x62, 0x63, 0x0};
+
+static const sal_Unicode eraDefaultAbbrvName00[] = {0x42, 0x43, 0x0};
+static const sal_Unicode eraDefaultFullName00[] = {0x42, 0x43, 0x0};
+static const sal_Unicode eraID01[] = {0x61, 0x64, 0x0};
+
+static const sal_Unicode eraDefaultAbbrvName01[] = {0x41, 0x44, 0x0};
+static const sal_Unicode eraDefaultFullName01[] = {0x41, 0x44, 0x0};
+static const sal_Unicode startDayOfWeek0[] = {0x73, 0x75, 0x6e, 0x0};
+static const sal_Unicode minimalDaysInFirstWeek0[] = {1};
+static const sal_Int16 calendarsCount = 1;
+
+static const sal_Unicode nbOfDays[] = {7};
+static const sal_Unicode nbOfMonths[] = {12};
+static const sal_Unicode nbOfGenitiveMonths[] = {12};
+static const sal_Unicode nbOfPartitiveMonths[] = {12};
+static const sal_Unicode nbOfEras[] = {2};
+static const sal_Unicode* calendars[] = {
+ nbOfDays,
+ nbOfMonths,
+ nbOfGenitiveMonths,
+ nbOfPartitiveMonths,
+ nbOfEras,
+ calendarID0,
+ defaultCalendar0,
+ dayID00,
+ dayDefaultAbbrvName00,
+ dayDefaultFullName00,
+ dayDefaultNarrowName00,
+ dayID01,
+ dayDefaultAbbrvName01,
+ dayDefaultFullName01,
+ dayDefaultNarrowName01,
+ dayID02,
+ dayDefaultAbbrvName02,
+ dayDefaultFullName02,
+ dayDefaultNarrowName02,
+ dayID03,
+ dayDefaultAbbrvName03,
+ dayDefaultFullName03,
+ dayDefaultNarrowName03,
+ dayID04,
+ dayDefaultAbbrvName04,
+ dayDefaultFullName04,
+ dayDefaultNarrowName04,
+ dayID05,
+ dayDefaultAbbrvName05,
+ dayDefaultFullName05,
+ dayDefaultNarrowName05,
+ dayID06,
+ dayDefaultAbbrvName06,
+ dayDefaultFullName06,
+ dayDefaultNarrowName06,
+ monthID00,
+ monthDefaultAbbrvName00,
+ monthDefaultFullName00,
+ monthDefaultNarrowName00,
+ monthID01,
+ monthDefaultAbbrvName01,
+ monthDefaultFullName01,
+ monthDefaultNarrowName01,
+ monthID02,
+ monthDefaultAbbrvName02,
+ monthDefaultFullName02,
+ monthDefaultNarrowName02,
+ monthID03,
+ monthDefaultAbbrvName03,
+ monthDefaultFullName03,
+ monthDefaultNarrowName03,
+ monthID04,
+ monthDefaultAbbrvName04,
+ monthDefaultFullName04,
+ monthDefaultNarrowName04,
+ monthID05,
+ monthDefaultAbbrvName05,
+ monthDefaultFullName05,
+ monthDefaultNarrowName05,
+ monthID06,
+ monthDefaultAbbrvName06,
+ monthDefaultFullName06,
+ monthDefaultNarrowName06,
+ monthID07,
+ monthDefaultAbbrvName07,
+ monthDefaultFullName07,
+ monthDefaultNarrowName07,
+ monthID08,
+ monthDefaultAbbrvName08,
+ monthDefaultFullName08,
+ monthDefaultNarrowName08,
+ monthID09,
+ monthDefaultAbbrvName09,
+ monthDefaultFullName09,
+ monthDefaultNarrowName09,
+ monthID010,
+ monthDefaultAbbrvName010,
+ monthDefaultFullName010,
+ monthDefaultNarrowName010,
+ monthID011,
+ monthDefaultAbbrvName011,
+ monthDefaultFullName011,
+ monthDefaultNarrowName011,
+ genitiveMonthID00,
+ genitiveMonthDefaultAbbrvName00,
+ genitiveMonthDefaultFullName00,
+ genitiveMonthDefaultNarrowName00,
+ genitiveMonthID01,
+ genitiveMonthDefaultAbbrvName01,
+ genitiveMonthDefaultFullName01,
+ genitiveMonthDefaultNarrowName01,
+ genitiveMonthID02,
+ genitiveMonthDefaultAbbrvName02,
+ genitiveMonthDefaultFullName02,
+ genitiveMonthDefaultNarrowName02,
+ genitiveMonthID03,
+ genitiveMonthDefaultAbbrvName03,
+ genitiveMonthDefaultFullName03,
+ genitiveMonthDefaultNarrowName03,
+ genitiveMonthID04,
+ genitiveMonthDefaultAbbrvName04,
+ genitiveMonthDefaultFullName04,
+ genitiveMonthDefaultNarrowName04,
+ genitiveMonthID05,
+ genitiveMonthDefaultAbbrvName05,
+ genitiveMonthDefaultFullName05,
+ genitiveMonthDefaultNarrowName05,
+ genitiveMonthID06,
+ genitiveMonthDefaultAbbrvName06,
+ genitiveMonthDefaultFullName06,
+ genitiveMonthDefaultNarrowName06,
+ genitiveMonthID07,
+ genitiveMonthDefaultAbbrvName07,
+ genitiveMonthDefaultFullName07,
+ genitiveMonthDefaultNarrowName07,
+ genitiveMonthID08,
+ genitiveMonthDefaultAbbrvName08,
+ genitiveMonthDefaultFullName08,
+ genitiveMonthDefaultNarrowName08,
+ genitiveMonthID09,
+ genitiveMonthDefaultAbbrvName09,
+ genitiveMonthDefaultFullName09,
+ genitiveMonthDefaultNarrowName09,
+ genitiveMonthID010,
+ genitiveMonthDefaultAbbrvName010,
+ genitiveMonthDefaultFullName010,
+ genitiveMonthDefaultNarrowName010,
+ genitiveMonthID011,
+ genitiveMonthDefaultAbbrvName011,
+ genitiveMonthDefaultFullName011,
+ genitiveMonthDefaultNarrowName011,
+ partitiveMonthID00,
+ partitiveMonthDefaultAbbrvName00,
+ partitiveMonthDefaultFullName00,
+ partitiveMonthDefaultNarrowName00,
+ partitiveMonthID01,
+ partitiveMonthDefaultAbbrvName01,
+ partitiveMonthDefaultFullName01,
+ partitiveMonthDefaultNarrowName01,
+ partitiveMonthID02,
+ partitiveMonthDefaultAbbrvName02,
+ partitiveMonthDefaultFullName02,
+ partitiveMonthDefaultNarrowName02,
+ partitiveMonthID03,
+ partitiveMonthDefaultAbbrvName03,
+ partitiveMonthDefaultFullName03,
+ partitiveMonthDefaultNarrowName03,
+ partitiveMonthID04,
+ partitiveMonthDefaultAbbrvName04,
+ partitiveMonthDefaultFullName04,
+ partitiveMonthDefaultNarrowName04,
+ partitiveMonthID05,
+ partitiveMonthDefaultAbbrvName05,
+ partitiveMonthDefaultFullName05,
+ partitiveMonthDefaultNarrowName05,
+ partitiveMonthID06,
+ partitiveMonthDefaultAbbrvName06,
+ partitiveMonthDefaultFullName06,
+ partitiveMonthDefaultNarrowName06,
+ partitiveMonthID07,
+ partitiveMonthDefaultAbbrvName07,
+ partitiveMonthDefaultFullName07,
+ partitiveMonthDefaultNarrowName07,
+ partitiveMonthID08,
+ partitiveMonthDefaultAbbrvName08,
+ partitiveMonthDefaultFullName08,
+ partitiveMonthDefaultNarrowName08,
+ partitiveMonthID09,
+ partitiveMonthDefaultAbbrvName09,
+ partitiveMonthDefaultFullName09,
+ partitiveMonthDefaultNarrowName09,
+ partitiveMonthID010,
+ partitiveMonthDefaultAbbrvName010,
+ partitiveMonthDefaultFullName010,
+ partitiveMonthDefaultNarrowName010,
+ partitiveMonthID011,
+ partitiveMonthDefaultAbbrvName011,
+ partitiveMonthDefaultFullName011,
+ partitiveMonthDefaultNarrowName011,
+ eraID00,
+ eraDefaultAbbrvName00,
+ eraDefaultFullName00,
+ eraID01,
+ eraDefaultAbbrvName01,
+ eraDefaultFullName01,
+ startDayOfWeek0,
+ minimalDaysInFirstWeek0,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCalendars_en_NZ(sal_Int16& count)
+{
+ count = calendarsCount;
+ return (sal_Unicode**)calendars;
+}
+static const sal_Unicode defaultCurrency0[] = {1};
+static const sal_Unicode defaultCurrencyUsedInCompatibleFormatCodes0[] = {1};
+static const sal_Unicode defaultCurrencyLegacyOnly0[] = {0};
+static const sal_Unicode currencyID0[] = {0x4e, 0x5a, 0x44, 0x0};
+static const sal_Unicode currencySymbol0[] = {0x24, 0x0};
+static const sal_Unicode bankSymbol0[] = {0x4e, 0x5a, 0x44, 0x0};
+static const sal_Unicode currencyName0[] = {0x4e, 0x65, 0x77, 0x20, 0x5a, 0x65, 0x61, 0x6c, 0x61, 0x6e, 0x64, 0x20, 0x44, 0x6f, 0x6c, 0x6c, 0x61, 0x72, 0x0};
+static const sal_Unicode currencyDecimalPlaces0[] = {2};
+
+static const sal_Int16 currencyCount = 1;
+
+static const sal_Unicode* currencies[] = {
+ currencyID0,
+ currencySymbol0,
+ bankSymbol0,
+ currencyName0,
+ defaultCurrency0,
+ defaultCurrencyUsedInCompatibleFormatCodes0,
+ currencyDecimalPlaces0,
+ defaultCurrencyLegacyOnly0,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCurrencies_en_NZ(sal_Int16& count)
+{
+ count = currencyCount;
+ return (sal_Unicode**)currencies;
+}
+extern sal_Unicode ** SAL_CALL getTransliterations_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getTransliterations_en_NZ(sal_Int16& count)
+{
+ return getTransliterations_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getForbiddenCharacters_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getForbiddenCharacters_en_NZ(sal_Int16& count)
+{
+ return getForbiddenCharacters_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getBreakIteratorRules_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getBreakIteratorRules_en_NZ(sal_Int16& count)
+{
+ return getBreakIteratorRules_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getReservedWords_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getReservedWords_en_NZ(sal_Int16& count)
+{
+ return getReservedWords_en_US(count);
+}
+// ---> ContinuousNumbering
+extern const sal_Unicode *** SAL_CALL getContinuousNumberingLevels_en_US(sal_Int16& nStyles, sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode *** SAL_CALL getContinuousNumberingLevels_en_NZ(sal_Int16& nStyles, sal_Int16& nAttributes)
+{
+ return getContinuousNumberingLevels_en_US(nStyles, nAttributes);
+}
+// ---> OutlineNumbering
+extern const sal_Unicode **** SAL_CALL getOutlineNumberingLevels_en_US(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode **** SAL_CALL getOutlineNumberingLevels_en_NZ(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes)
+{
+ return getOutlineNumberingLevels_en_US(nStyles, nLevels, nAttributes);
+}
+} // extern "C"
+
diff --git a/vcl/workben/localestub/localedata_en_PH.cxx b/vcl/workben/localestub/localedata_en_PH.cxx
new file mode 100644
index 000000000..07c1b72a2
--- /dev/null
+++ b/vcl/workben/localestub/localedata_en_PH.cxx
@@ -0,0 +1,140 @@
+#include <sal/types.h>
+
+
+#include <stdio.h>
+
+extern "C" {
+
+static const sal_Unicode langID[] = {0x65, 0x6e, 0x0};
+static const sal_Unicode langDefaultName[] = {0x45, 0x6e, 0x67, 0x6c, 0x69, 0x73, 0x68, 0x0};
+static const sal_Unicode countryID[] = {0x50, 0x48, 0x0};
+static const sal_Unicode countryDefaultName[] = {0x50, 0x68, 0x69, 0x6c, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x65, 0x73, 0x0};
+static const sal_Unicode Variant[] = {0x0};
+
+static const sal_Unicode* LCInfoArray[] = {
+ langID,
+ langDefaultName,
+ countryID,
+ countryDefaultName,
+ Variant
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLCInfo_en_PH(sal_Int16& count)
+{
+ count = 0;
+ return (sal_Unicode**)LCInfoArray;
+}
+extern sal_Unicode ** SAL_CALL getLocaleItem_en_CA(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLocaleItem_en_PH(sal_Int16& count)
+{
+ return getLocaleItem_en_CA(count);
+}
+static const sal_Unicode replaceTo0[] = {0x5b, 0x24, 0x50, 0x68, 0x70, 0x2d, 0x33, 0x34, 0x30, 0x39, 0x5d, 0x0};
+extern sal_Unicode const * const * SAL_CALL getAllFormats0_en_US(sal_Int16& count, const sal_Unicode*& from, const sal_Unicode*& to);
+SAL_DLLPUBLIC_EXPORT sal_Unicode const * const * SAL_CALL getAllFormats0_en_PH(sal_Int16& count, const sal_Unicode*& from, const sal_Unicode*& to)
+{
+ to = replaceTo0;
+ const sal_Unicode* tmp;
+ return getAllFormats0_en_US(count, from, tmp);
+}
+extern sal_Unicode ** SAL_CALL getDateAcceptancePatterns_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getDateAcceptancePatterns_en_PH(sal_Int16& count)
+{
+ return getDateAcceptancePatterns_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getCollatorImplementation_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollatorImplementation_en_PH(sal_Int16& count)
+{
+ return getCollatorImplementation_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getCollationOptions_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollationOptions_en_PH(sal_Int16& count)
+{
+ return getCollationOptions_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getSearchOptions_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getSearchOptions_en_PH(sal_Int16& count)
+{
+ return getSearchOptions_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getIndexAlgorithm_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getIndexAlgorithm_en_PH(sal_Int16& count)
+{
+ return getIndexAlgorithm_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getUnicodeScripts_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getUnicodeScripts_en_PH(sal_Int16& count)
+{
+ return getUnicodeScripts_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getFollowPageWords_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getFollowPageWords_en_PH(sal_Int16& count)
+{
+ return getFollowPageWords_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getAllCalendars_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCalendars_en_PH(sal_Int16& count)
+{
+ return getAllCalendars_en_US(count);
+}
+static const sal_Unicode defaultCurrency0[] = {1};
+static const sal_Unicode defaultCurrencyUsedInCompatibleFormatCodes0[] = {1};
+static const sal_Unicode defaultCurrencyLegacyOnly0[] = {0};
+static const sal_Unicode currencyID0[] = {0x50, 0x48, 0x50, 0x0};
+static const sal_Unicode currencySymbol0[] = {0x50, 0x68, 0x70, 0x0};
+static const sal_Unicode bankSymbol0[] = {0x50, 0x48, 0x50, 0x0};
+static const sal_Unicode currencyName0[] = {0x50, 0x65, 0x73, 0x6f, 0x0};
+static const sal_Unicode currencyDecimalPlaces0[] = {2};
+
+static const sal_Int16 currencyCount = 1;
+
+static const sal_Unicode* currencies[] = {
+ currencyID0,
+ currencySymbol0,
+ bankSymbol0,
+ currencyName0,
+ defaultCurrency0,
+ defaultCurrencyUsedInCompatibleFormatCodes0,
+ currencyDecimalPlaces0,
+ defaultCurrencyLegacyOnly0,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCurrencies_en_PH(sal_Int16& count)
+{
+ count = currencyCount;
+ return (sal_Unicode**)currencies;
+}
+extern sal_Unicode ** SAL_CALL getTransliterations_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getTransliterations_en_PH(sal_Int16& count)
+{
+ return getTransliterations_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getForbiddenCharacters_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getForbiddenCharacters_en_PH(sal_Int16& count)
+{
+ return getForbiddenCharacters_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getBreakIteratorRules_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getBreakIteratorRules_en_PH(sal_Int16& count)
+{
+ return getBreakIteratorRules_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getReservedWords_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getReservedWords_en_PH(sal_Int16& count)
+{
+ return getReservedWords_en_US(count);
+}
+// ---> ContinuousNumbering
+extern const sal_Unicode *** SAL_CALL getContinuousNumberingLevels_en_US(sal_Int16& nStyles, sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode *** SAL_CALL getContinuousNumberingLevels_en_PH(sal_Int16& nStyles, sal_Int16& nAttributes)
+{
+ return getContinuousNumberingLevels_en_US(nStyles, nAttributes);
+}
+// ---> OutlineNumbering
+extern const sal_Unicode **** SAL_CALL getOutlineNumberingLevels_en_US(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode **** SAL_CALL getOutlineNumberingLevels_en_PH(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes)
+{
+ return getOutlineNumberingLevels_en_US(nStyles, nLevels, nAttributes);
+}
+} // extern "C"
+
diff --git a/vcl/workben/localestub/localedata_en_TT.cxx b/vcl/workben/localestub/localedata_en_TT.cxx
new file mode 100644
index 000000000..0a99e9491
--- /dev/null
+++ b/vcl/workben/localestub/localedata_en_TT.cxx
@@ -0,0 +1,563 @@
+#include <sal/types.h>
+
+
+#include <stdio.h>
+
+extern "C" {
+
+static const sal_Unicode langID[] = {0x65, 0x6e, 0x0};
+static const sal_Unicode langDefaultName[] = {0x45, 0x6e, 0x67, 0x6c, 0x69, 0x73, 0x68, 0x0};
+static const sal_Unicode countryID[] = {0x54, 0x54, 0x0};
+static const sal_Unicode countryDefaultName[] = {0x54, 0x72, 0x69, 0x6e, 0x69, 0x64, 0x61, 0x64, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x54, 0x6f, 0x62, 0x61, 0x67, 0x6f, 0x0};
+static const sal_Unicode Variant[] = {0x0};
+
+static const sal_Unicode* LCInfoArray[] = {
+ langID,
+ langDefaultName,
+ countryID,
+ countryDefaultName,
+ Variant
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLCInfo_en_TT(sal_Int16& count)
+{
+ count = 0;
+ return (sal_Unicode**)LCInfoArray;
+}
+
+
+static const sal_Unicode LC_CTYPE_Unoid[] = {0x0};
+static const sal_Unicode dateSeparator[] = {0x2f, 0x0};
+static const sal_Unicode thousandSeparator[] = {0x2c, 0x0};
+static const sal_Unicode decimalSeparator[] = {0x2e, 0x0};
+static const sal_Unicode timeSeparator[] = {0x3a, 0x0};
+static const sal_Unicode time100SecSeparator[] = {0x2e, 0x0};
+static const sal_Unicode listSeparator[] = {0x3b, 0x0};
+static const sal_Unicode LongDateDayOfWeekSeparator[] = {0x2c, 0x20, 0x0};
+static const sal_Unicode LongDateDaySeparator[] = {0x20, 0x0};
+static const sal_Unicode LongDateMonthSeparator[] = {0x20, 0x0};
+static const sal_Unicode LongDateYearSeparator[] = {0x20, 0x0};
+static const sal_Unicode quotationStart[] = {0x2018, 0x0};
+static const sal_Unicode quotationEnd[] = {0x2019, 0x0};
+static const sal_Unicode doubleQuotationStart[] = {0x201c, 0x0};
+static const sal_Unicode doubleQuotationEnd[] = {0x201d, 0x0};
+static const sal_Unicode timeAM[] = {0x41, 0x4d, 0x0};
+static const sal_Unicode timePM[] = {0x50, 0x4d, 0x0};
+static const sal_Unicode measurementSystem[] = {0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x0};
+
+static const sal_Unicode* LCType[] = {
+ LC_CTYPE_Unoid,
+ dateSeparator,
+ thousandSeparator,
+ decimalSeparator,
+ timeSeparator,
+ time100SecSeparator,
+ listSeparator,
+ quotationStart,
+ quotationEnd,
+ doubleQuotationStart,
+ doubleQuotationEnd,
+ timeAM,
+ timePM,
+ measurementSystem,
+ LongDateDayOfWeekSeparator,
+ LongDateDaySeparator,
+ LongDateMonthSeparator,
+ LongDateYearSeparator
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLocaleItem_en_TT(sal_Int16& count)
+{
+ count = 0;
+ return (sal_Unicode**)LCType;
+}
+static const sal_Unicode replaceTo0[] = {0x5b, 0x24, 0x54, 0x54, 0x24, 0x2d, 0x32, 0x43, 0x30, 0x39, 0x5d, 0x0};
+extern sal_Unicode const * const * SAL_CALL getAllFormats0_en_US(sal_Int16& count, const sal_Unicode*& from, const sal_Unicode*& to);
+SAL_DLLPUBLIC_EXPORT sal_Unicode const * const * SAL_CALL getAllFormats0_en_TT(sal_Int16& count, const sal_Unicode*& from, const sal_Unicode*& to)
+{
+ to = replaceTo0;
+ const sal_Unicode* tmp;
+ return getAllFormats0_en_US(count, from, tmp);
+}
+extern sal_Unicode ** SAL_CALL getDateAcceptancePatterns_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getDateAcceptancePatterns_en_TT(sal_Int16& count)
+{
+ return getDateAcceptancePatterns_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getCollatorImplementation_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollatorImplementation_en_TT(sal_Int16& count)
+{
+ return getCollatorImplementation_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getCollationOptions_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollationOptions_en_TT(sal_Int16& count)
+{
+ return getCollationOptions_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getSearchOptions_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getSearchOptions_en_TT(sal_Int16& count)
+{
+ return getSearchOptions_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getIndexAlgorithm_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getIndexAlgorithm_en_TT(sal_Int16& count)
+{
+ return getIndexAlgorithm_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getUnicodeScripts_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getUnicodeScripts_en_TT(sal_Int16& count)
+{
+ return getUnicodeScripts_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getFollowPageWords_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getFollowPageWords_en_TT(sal_Int16& count)
+{
+ return getFollowPageWords_en_US(count);
+}
+static const sal_Unicode calendarID0[] = {0x67, 0x72, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x61, 0x6e, 0x0};
+static const sal_Unicode defaultCalendar0[] = {1};
+static const sal_Unicode dayID00[] = {0x73, 0x75, 0x6e, 0x0};
+static const sal_Unicode dayDefaultAbbrvName00[] = {0x53, 0x75, 0x6e, 0x0};
+static const sal_Unicode dayDefaultFullName00[] = {0x53, 0x75, 0x6e, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName00[] = {0x53, 0x0};
+static const sal_Unicode dayID01[] = {0x6d, 0x6f, 0x6e, 0x0};
+static const sal_Unicode dayDefaultAbbrvName01[] = {0x4d, 0x6f, 0x6e, 0x0};
+static const sal_Unicode dayDefaultFullName01[] = {0x4d, 0x6f, 0x6e, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName01[] = {0x4d, 0x0};
+static const sal_Unicode dayID02[] = {0x74, 0x75, 0x65, 0x0};
+static const sal_Unicode dayDefaultAbbrvName02[] = {0x54, 0x75, 0x65, 0x0};
+static const sal_Unicode dayDefaultFullName02[] = {0x54, 0x75, 0x65, 0x73, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName02[] = {0x54, 0x0};
+static const sal_Unicode dayID03[] = {0x77, 0x65, 0x64, 0x0};
+static const sal_Unicode dayDefaultAbbrvName03[] = {0x57, 0x65, 0x64, 0x0};
+static const sal_Unicode dayDefaultFullName03[] = {0x57, 0x65, 0x64, 0x6e, 0x65, 0x73, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName03[] = {0x57, 0x0};
+static const sal_Unicode dayID04[] = {0x74, 0x68, 0x75, 0x0};
+static const sal_Unicode dayDefaultAbbrvName04[] = {0x54, 0x68, 0x75, 0x0};
+static const sal_Unicode dayDefaultFullName04[] = {0x54, 0x68, 0x75, 0x72, 0x73, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName04[] = {0x54, 0x0};
+static const sal_Unicode dayID05[] = {0x66, 0x72, 0x69, 0x0};
+static const sal_Unicode dayDefaultAbbrvName05[] = {0x46, 0x72, 0x69, 0x0};
+static const sal_Unicode dayDefaultFullName05[] = {0x46, 0x72, 0x69, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName05[] = {0x46, 0x0};
+static const sal_Unicode dayID06[] = {0x73, 0x61, 0x74, 0x0};
+static const sal_Unicode dayDefaultAbbrvName06[] = {0x53, 0x61, 0x74, 0x0};
+static const sal_Unicode dayDefaultFullName06[] = {0x53, 0x61, 0x74, 0x75, 0x72, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName06[] = {0x53, 0x0};
+static const sal_Unicode monthID00[] = {0x6a, 0x61, 0x6e, 0x0};
+static const sal_Unicode monthDefaultAbbrvName00[] = {0x4a, 0x61, 0x6e, 0x0};
+static const sal_Unicode monthDefaultFullName00[] = {0x4a, 0x61, 0x6e, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode monthDefaultNarrowName00[] = {0x4a, 0x0};
+static const sal_Unicode monthID01[] = {0x66, 0x65, 0x62, 0x0};
+static const sal_Unicode monthDefaultAbbrvName01[] = {0x46, 0x65, 0x62, 0x0};
+static const sal_Unicode monthDefaultFullName01[] = {0x46, 0x65, 0x62, 0x72, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode monthDefaultNarrowName01[] = {0x46, 0x0};
+static const sal_Unicode monthID02[] = {0x6d, 0x61, 0x72, 0x0};
+static const sal_Unicode monthDefaultAbbrvName02[] = {0x4d, 0x61, 0x72, 0x0};
+static const sal_Unicode monthDefaultFullName02[] = {0x4d, 0x61, 0x72, 0x63, 0x68, 0x0};
+static const sal_Unicode monthDefaultNarrowName02[] = {0x4d, 0x0};
+static const sal_Unicode monthID03[] = {0x61, 0x70, 0x72, 0x0};
+static const sal_Unicode monthDefaultAbbrvName03[] = {0x41, 0x70, 0x72, 0x0};
+static const sal_Unicode monthDefaultFullName03[] = {0x41, 0x70, 0x72, 0x69, 0x6c, 0x0};
+static const sal_Unicode monthDefaultNarrowName03[] = {0x41, 0x0};
+static const sal_Unicode monthID04[] = {0x6d, 0x61, 0x79, 0x0};
+static const sal_Unicode monthDefaultAbbrvName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode monthDefaultFullName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode monthDefaultNarrowName04[] = {0x4d, 0x0};
+static const sal_Unicode monthID05[] = {0x6a, 0x75, 0x6e, 0x0};
+static const sal_Unicode monthDefaultAbbrvName05[] = {0x4a, 0x75, 0x6e, 0x0};
+static const sal_Unicode monthDefaultFullName05[] = {0x4a, 0x75, 0x6e, 0x65, 0x0};
+static const sal_Unicode monthDefaultNarrowName05[] = {0x4a, 0x0};
+static const sal_Unicode monthID06[] = {0x6a, 0x75, 0x6c, 0x0};
+static const sal_Unicode monthDefaultAbbrvName06[] = {0x4a, 0x75, 0x6c, 0x0};
+static const sal_Unicode monthDefaultFullName06[] = {0x4a, 0x75, 0x6c, 0x79, 0x0};
+static const sal_Unicode monthDefaultNarrowName06[] = {0x4a, 0x0};
+static const sal_Unicode monthID07[] = {0x61, 0x75, 0x67, 0x0};
+static const sal_Unicode monthDefaultAbbrvName07[] = {0x41, 0x75, 0x67, 0x0};
+static const sal_Unicode monthDefaultFullName07[] = {0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x0};
+static const sal_Unicode monthDefaultNarrowName07[] = {0x41, 0x0};
+static const sal_Unicode monthID08[] = {0x73, 0x65, 0x70, 0x0};
+static const sal_Unicode monthDefaultAbbrvName08[] = {0x53, 0x65, 0x70, 0x0};
+static const sal_Unicode monthDefaultFullName08[] = {0x53, 0x65, 0x70, 0x74, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName08[] = {0x53, 0x0};
+static const sal_Unicode monthID09[] = {0x6f, 0x63, 0x74, 0x0};
+static const sal_Unicode monthDefaultAbbrvName09[] = {0x4f, 0x63, 0x74, 0x0};
+static const sal_Unicode monthDefaultFullName09[] = {0x4f, 0x63, 0x74, 0x6f, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName09[] = {0x4f, 0x0};
+static const sal_Unicode monthID010[] = {0x6e, 0x6f, 0x76, 0x0};
+static const sal_Unicode monthDefaultAbbrvName010[] = {0x4e, 0x6f, 0x76, 0x0};
+static const sal_Unicode monthDefaultFullName010[] = {0x4e, 0x6f, 0x76, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName010[] = {0x4e, 0x0};
+static const sal_Unicode monthID011[] = {0x64, 0x65, 0x63, 0x0};
+static const sal_Unicode monthDefaultAbbrvName011[] = {0x44, 0x65, 0x63, 0x0};
+static const sal_Unicode monthDefaultFullName011[] = {0x44, 0x65, 0x63, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName011[] = {0x44, 0x0};
+static const sal_Unicode genitiveMonthID00[] = {0x6a, 0x61, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName00[] = {0x4a, 0x61, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName00[] = {0x4a, 0x61, 0x6e, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName00[] = {0x4a, 0x0};
+static const sal_Unicode genitiveMonthID01[] = {0x66, 0x65, 0x62, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName01[] = {0x46, 0x65, 0x62, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName01[] = {0x46, 0x65, 0x62, 0x72, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName01[] = {0x46, 0x0};
+static const sal_Unicode genitiveMonthID02[] = {0x6d, 0x61, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName02[] = {0x4d, 0x61, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName02[] = {0x4d, 0x61, 0x72, 0x63, 0x68, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName02[] = {0x4d, 0x0};
+static const sal_Unicode genitiveMonthID03[] = {0x61, 0x70, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName03[] = {0x41, 0x70, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName03[] = {0x41, 0x70, 0x72, 0x69, 0x6c, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName03[] = {0x41, 0x0};
+static const sal_Unicode genitiveMonthID04[] = {0x6d, 0x61, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName04[] = {0x4d, 0x0};
+static const sal_Unicode genitiveMonthID05[] = {0x6a, 0x75, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName05[] = {0x4a, 0x75, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName05[] = {0x4a, 0x75, 0x6e, 0x65, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName05[] = {0x4a, 0x0};
+static const sal_Unicode genitiveMonthID06[] = {0x6a, 0x75, 0x6c, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName06[] = {0x4a, 0x75, 0x6c, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName06[] = {0x4a, 0x75, 0x6c, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName06[] = {0x4a, 0x0};
+static const sal_Unicode genitiveMonthID07[] = {0x61, 0x75, 0x67, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName07[] = {0x41, 0x75, 0x67, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName07[] = {0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName07[] = {0x41, 0x0};
+static const sal_Unicode genitiveMonthID08[] = {0x73, 0x65, 0x70, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName08[] = {0x53, 0x65, 0x70, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName08[] = {0x53, 0x65, 0x70, 0x74, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName08[] = {0x53, 0x0};
+static const sal_Unicode genitiveMonthID09[] = {0x6f, 0x63, 0x74, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName09[] = {0x4f, 0x63, 0x74, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName09[] = {0x4f, 0x63, 0x74, 0x6f, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName09[] = {0x4f, 0x0};
+static const sal_Unicode genitiveMonthID010[] = {0x6e, 0x6f, 0x76, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName010[] = {0x4e, 0x6f, 0x76, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName010[] = {0x4e, 0x6f, 0x76, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName010[] = {0x4e, 0x0};
+static const sal_Unicode genitiveMonthID011[] = {0x64, 0x65, 0x63, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName011[] = {0x44, 0x65, 0x63, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName011[] = {0x44, 0x65, 0x63, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName011[] = {0x44, 0x0};
+static const sal_Unicode partitiveMonthID00[] = {0x6a, 0x61, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName00[] = {0x4a, 0x61, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName00[] = {0x4a, 0x61, 0x6e, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName00[] = {0x4a, 0x0};
+static const sal_Unicode partitiveMonthID01[] = {0x66, 0x65, 0x62, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName01[] = {0x46, 0x65, 0x62, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName01[] = {0x46, 0x65, 0x62, 0x72, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName01[] = {0x46, 0x0};
+static const sal_Unicode partitiveMonthID02[] = {0x6d, 0x61, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName02[] = {0x4d, 0x61, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName02[] = {0x4d, 0x61, 0x72, 0x63, 0x68, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName02[] = {0x4d, 0x0};
+static const sal_Unicode partitiveMonthID03[] = {0x61, 0x70, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName03[] = {0x41, 0x70, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName03[] = {0x41, 0x70, 0x72, 0x69, 0x6c, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName03[] = {0x41, 0x0};
+static const sal_Unicode partitiveMonthID04[] = {0x6d, 0x61, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName04[] = {0x4d, 0x0};
+static const sal_Unicode partitiveMonthID05[] = {0x6a, 0x75, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName05[] = {0x4a, 0x75, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName05[] = {0x4a, 0x75, 0x6e, 0x65, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName05[] = {0x4a, 0x0};
+static const sal_Unicode partitiveMonthID06[] = {0x6a, 0x75, 0x6c, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName06[] = {0x4a, 0x75, 0x6c, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName06[] = {0x4a, 0x75, 0x6c, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName06[] = {0x4a, 0x0};
+static const sal_Unicode partitiveMonthID07[] = {0x61, 0x75, 0x67, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName07[] = {0x41, 0x75, 0x67, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName07[] = {0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName07[] = {0x41, 0x0};
+static const sal_Unicode partitiveMonthID08[] = {0x73, 0x65, 0x70, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName08[] = {0x53, 0x65, 0x70, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName08[] = {0x53, 0x65, 0x70, 0x74, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName08[] = {0x53, 0x0};
+static const sal_Unicode partitiveMonthID09[] = {0x6f, 0x63, 0x74, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName09[] = {0x4f, 0x63, 0x74, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName09[] = {0x4f, 0x63, 0x74, 0x6f, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName09[] = {0x4f, 0x0};
+static const sal_Unicode partitiveMonthID010[] = {0x6e, 0x6f, 0x76, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName010[] = {0x4e, 0x6f, 0x76, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName010[] = {0x4e, 0x6f, 0x76, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName010[] = {0x4e, 0x0};
+static const sal_Unicode partitiveMonthID011[] = {0x64, 0x65, 0x63, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName011[] = {0x44, 0x65, 0x63, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName011[] = {0x44, 0x65, 0x63, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName011[] = {0x44, 0x0};
+static const sal_Unicode eraID00[] = {0x62, 0x63, 0x0};
+
+static const sal_Unicode eraDefaultAbbrvName00[] = {0x42, 0x43, 0x0};
+static const sal_Unicode eraDefaultFullName00[] = {0x42, 0x43, 0x0};
+static const sal_Unicode eraID01[] = {0x61, 0x64, 0x0};
+
+static const sal_Unicode eraDefaultAbbrvName01[] = {0x41, 0x44, 0x0};
+static const sal_Unicode eraDefaultFullName01[] = {0x41, 0x44, 0x0};
+static const sal_Unicode startDayOfWeek0[] = {0x73, 0x75, 0x6e, 0x0};
+static const sal_Unicode minimalDaysInFirstWeek0[] = {1};
+static const sal_Int16 calendarsCount = 1;
+
+static const sal_Unicode nbOfDays[] = {7};
+static const sal_Unicode nbOfMonths[] = {12};
+static const sal_Unicode nbOfGenitiveMonths[] = {12};
+static const sal_Unicode nbOfPartitiveMonths[] = {12};
+static const sal_Unicode nbOfEras[] = {2};
+static const sal_Unicode* calendars[] = {
+ nbOfDays,
+ nbOfMonths,
+ nbOfGenitiveMonths,
+ nbOfPartitiveMonths,
+ nbOfEras,
+ calendarID0,
+ defaultCalendar0,
+ dayID00,
+ dayDefaultAbbrvName00,
+ dayDefaultFullName00,
+ dayDefaultNarrowName00,
+ dayID01,
+ dayDefaultAbbrvName01,
+ dayDefaultFullName01,
+ dayDefaultNarrowName01,
+ dayID02,
+ dayDefaultAbbrvName02,
+ dayDefaultFullName02,
+ dayDefaultNarrowName02,
+ dayID03,
+ dayDefaultAbbrvName03,
+ dayDefaultFullName03,
+ dayDefaultNarrowName03,
+ dayID04,
+ dayDefaultAbbrvName04,
+ dayDefaultFullName04,
+ dayDefaultNarrowName04,
+ dayID05,
+ dayDefaultAbbrvName05,
+ dayDefaultFullName05,
+ dayDefaultNarrowName05,
+ dayID06,
+ dayDefaultAbbrvName06,
+ dayDefaultFullName06,
+ dayDefaultNarrowName06,
+ monthID00,
+ monthDefaultAbbrvName00,
+ monthDefaultFullName00,
+ monthDefaultNarrowName00,
+ monthID01,
+ monthDefaultAbbrvName01,
+ monthDefaultFullName01,
+ monthDefaultNarrowName01,
+ monthID02,
+ monthDefaultAbbrvName02,
+ monthDefaultFullName02,
+ monthDefaultNarrowName02,
+ monthID03,
+ monthDefaultAbbrvName03,
+ monthDefaultFullName03,
+ monthDefaultNarrowName03,
+ monthID04,
+ monthDefaultAbbrvName04,
+ monthDefaultFullName04,
+ monthDefaultNarrowName04,
+ monthID05,
+ monthDefaultAbbrvName05,
+ monthDefaultFullName05,
+ monthDefaultNarrowName05,
+ monthID06,
+ monthDefaultAbbrvName06,
+ monthDefaultFullName06,
+ monthDefaultNarrowName06,
+ monthID07,
+ monthDefaultAbbrvName07,
+ monthDefaultFullName07,
+ monthDefaultNarrowName07,
+ monthID08,
+ monthDefaultAbbrvName08,
+ monthDefaultFullName08,
+ monthDefaultNarrowName08,
+ monthID09,
+ monthDefaultAbbrvName09,
+ monthDefaultFullName09,
+ monthDefaultNarrowName09,
+ monthID010,
+ monthDefaultAbbrvName010,
+ monthDefaultFullName010,
+ monthDefaultNarrowName010,
+ monthID011,
+ monthDefaultAbbrvName011,
+ monthDefaultFullName011,
+ monthDefaultNarrowName011,
+ genitiveMonthID00,
+ genitiveMonthDefaultAbbrvName00,
+ genitiveMonthDefaultFullName00,
+ genitiveMonthDefaultNarrowName00,
+ genitiveMonthID01,
+ genitiveMonthDefaultAbbrvName01,
+ genitiveMonthDefaultFullName01,
+ genitiveMonthDefaultNarrowName01,
+ genitiveMonthID02,
+ genitiveMonthDefaultAbbrvName02,
+ genitiveMonthDefaultFullName02,
+ genitiveMonthDefaultNarrowName02,
+ genitiveMonthID03,
+ genitiveMonthDefaultAbbrvName03,
+ genitiveMonthDefaultFullName03,
+ genitiveMonthDefaultNarrowName03,
+ genitiveMonthID04,
+ genitiveMonthDefaultAbbrvName04,
+ genitiveMonthDefaultFullName04,
+ genitiveMonthDefaultNarrowName04,
+ genitiveMonthID05,
+ genitiveMonthDefaultAbbrvName05,
+ genitiveMonthDefaultFullName05,
+ genitiveMonthDefaultNarrowName05,
+ genitiveMonthID06,
+ genitiveMonthDefaultAbbrvName06,
+ genitiveMonthDefaultFullName06,
+ genitiveMonthDefaultNarrowName06,
+ genitiveMonthID07,
+ genitiveMonthDefaultAbbrvName07,
+ genitiveMonthDefaultFullName07,
+ genitiveMonthDefaultNarrowName07,
+ genitiveMonthID08,
+ genitiveMonthDefaultAbbrvName08,
+ genitiveMonthDefaultFullName08,
+ genitiveMonthDefaultNarrowName08,
+ genitiveMonthID09,
+ genitiveMonthDefaultAbbrvName09,
+ genitiveMonthDefaultFullName09,
+ genitiveMonthDefaultNarrowName09,
+ genitiveMonthID010,
+ genitiveMonthDefaultAbbrvName010,
+ genitiveMonthDefaultFullName010,
+ genitiveMonthDefaultNarrowName010,
+ genitiveMonthID011,
+ genitiveMonthDefaultAbbrvName011,
+ genitiveMonthDefaultFullName011,
+ genitiveMonthDefaultNarrowName011,
+ partitiveMonthID00,
+ partitiveMonthDefaultAbbrvName00,
+ partitiveMonthDefaultFullName00,
+ partitiveMonthDefaultNarrowName00,
+ partitiveMonthID01,
+ partitiveMonthDefaultAbbrvName01,
+ partitiveMonthDefaultFullName01,
+ partitiveMonthDefaultNarrowName01,
+ partitiveMonthID02,
+ partitiveMonthDefaultAbbrvName02,
+ partitiveMonthDefaultFullName02,
+ partitiveMonthDefaultNarrowName02,
+ partitiveMonthID03,
+ partitiveMonthDefaultAbbrvName03,
+ partitiveMonthDefaultFullName03,
+ partitiveMonthDefaultNarrowName03,
+ partitiveMonthID04,
+ partitiveMonthDefaultAbbrvName04,
+ partitiveMonthDefaultFullName04,
+ partitiveMonthDefaultNarrowName04,
+ partitiveMonthID05,
+ partitiveMonthDefaultAbbrvName05,
+ partitiveMonthDefaultFullName05,
+ partitiveMonthDefaultNarrowName05,
+ partitiveMonthID06,
+ partitiveMonthDefaultAbbrvName06,
+ partitiveMonthDefaultFullName06,
+ partitiveMonthDefaultNarrowName06,
+ partitiveMonthID07,
+ partitiveMonthDefaultAbbrvName07,
+ partitiveMonthDefaultFullName07,
+ partitiveMonthDefaultNarrowName07,
+ partitiveMonthID08,
+ partitiveMonthDefaultAbbrvName08,
+ partitiveMonthDefaultFullName08,
+ partitiveMonthDefaultNarrowName08,
+ partitiveMonthID09,
+ partitiveMonthDefaultAbbrvName09,
+ partitiveMonthDefaultFullName09,
+ partitiveMonthDefaultNarrowName09,
+ partitiveMonthID010,
+ partitiveMonthDefaultAbbrvName010,
+ partitiveMonthDefaultFullName010,
+ partitiveMonthDefaultNarrowName010,
+ partitiveMonthID011,
+ partitiveMonthDefaultAbbrvName011,
+ partitiveMonthDefaultFullName011,
+ partitiveMonthDefaultNarrowName011,
+ eraID00,
+ eraDefaultAbbrvName00,
+ eraDefaultFullName00,
+ eraID01,
+ eraDefaultAbbrvName01,
+ eraDefaultFullName01,
+ startDayOfWeek0,
+ minimalDaysInFirstWeek0,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCalendars_en_TT(sal_Int16& count)
+{
+ count = calendarsCount;
+ return (sal_Unicode**)calendars;
+}
+static const sal_Unicode defaultCurrency0[] = {1};
+static const sal_Unicode defaultCurrencyUsedInCompatibleFormatCodes0[] = {1};
+static const sal_Unicode defaultCurrencyLegacyOnly0[] = {0};
+static const sal_Unicode currencyID0[] = {0x54, 0x54, 0x44, 0x0};
+static const sal_Unicode currencySymbol0[] = {0x54, 0x54, 0x24, 0x0};
+static const sal_Unicode bankSymbol0[] = {0x54, 0x54, 0x44, 0x0};
+static const sal_Unicode currencyName0[] = {0x54, 0x72, 0x69, 0x6e, 0x69, 0x64, 0x61, 0x64, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x54, 0x6f, 0x62, 0x61, 0x67, 0x6f, 0x20, 0x44, 0x6f, 0x6c, 0x6c, 0x61, 0x72, 0x0};
+static const sal_Unicode currencyDecimalPlaces0[] = {2};
+
+static const sal_Int16 currencyCount = 1;
+
+static const sal_Unicode* currencies[] = {
+ currencyID0,
+ currencySymbol0,
+ bankSymbol0,
+ currencyName0,
+ defaultCurrency0,
+ defaultCurrencyUsedInCompatibleFormatCodes0,
+ currencyDecimalPlaces0,
+ defaultCurrencyLegacyOnly0,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCurrencies_en_TT(sal_Int16& count)
+{
+ count = currencyCount;
+ return (sal_Unicode**)currencies;
+}
+extern sal_Unicode ** SAL_CALL getTransliterations_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getTransliterations_en_TT(sal_Int16& count)
+{
+ return getTransliterations_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getForbiddenCharacters_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getForbiddenCharacters_en_TT(sal_Int16& count)
+{
+ return getForbiddenCharacters_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getBreakIteratorRules_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getBreakIteratorRules_en_TT(sal_Int16& count)
+{
+ return getBreakIteratorRules_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getReservedWords_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getReservedWords_en_TT(sal_Int16& count)
+{
+ return getReservedWords_en_US(count);
+}
+// ---> ContinuousNumbering
+extern const sal_Unicode *** SAL_CALL getContinuousNumberingLevels_en_US(sal_Int16& nStyles, sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode *** SAL_CALL getContinuousNumberingLevels_en_TT(sal_Int16& nStyles, sal_Int16& nAttributes)
+{
+ return getContinuousNumberingLevels_en_US(nStyles, nAttributes);
+}
+// ---> OutlineNumbering
+extern const sal_Unicode **** SAL_CALL getOutlineNumberingLevels_en_US(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode **** SAL_CALL getOutlineNumberingLevels_en_TT(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes)
+{
+ return getOutlineNumberingLevels_en_US(nStyles, nLevels, nAttributes);
+}
+} // extern "C"
+
diff --git a/vcl/workben/localestub/localedata_en_US.cxx b/vcl/workben/localestub/localedata_en_US.cxx
new file mode 100644
index 000000000..6f09c68ab
--- /dev/null
+++ b/vcl/workben/localestub/localedata_en_US.cxx
@@ -0,0 +1,2609 @@
+#include <sal/types.h>
+
+
+#include <stdio.h>
+
+extern "C" {
+
+static const sal_Unicode langID[] = {0x65, 0x6e, 0x0};
+static const sal_Unicode langDefaultName[] = {0x45, 0x6e, 0x67, 0x6c, 0x69, 0x73, 0x68, 0x0};
+static const sal_Unicode countryID[] = {0x55, 0x53, 0x0};
+static const sal_Unicode countryDefaultName[] = {0x55, 0x6e, 0x69, 0x74, 0x65, 0x64, 0x20, 0x53, 0x74, 0x61, 0x74, 0x65, 0x73, 0x0};
+static const sal_Unicode Variant[] = {0x0};
+
+static const sal_Unicode* LCInfoArray[] = {
+ langID,
+ langDefaultName,
+ countryID,
+ countryDefaultName,
+ Variant
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLCInfo_en_US(sal_Int16& count)
+{
+ count = 0;
+ return (sal_Unicode**)LCInfoArray;
+}
+
+
+static const sal_Unicode LC_CTYPE_Unoid[] = {0x0};
+static const sal_Unicode dateSeparator[] = {0x2f, 0x0};
+static const sal_Unicode thousandSeparator[] = {0x2c, 0x0};
+static const sal_Unicode decimalSeparator[] = {0x2e, 0x0};
+static const sal_Unicode timeSeparator[] = {0x3a, 0x0};
+static const sal_Unicode time100SecSeparator[] = {0x2e, 0x0};
+static const sal_Unicode listSeparator[] = {0x3b, 0x0};
+static const sal_Unicode LongDateDayOfWeekSeparator[] = {0x2c, 0x20, 0x0};
+static const sal_Unicode LongDateDaySeparator[] = {0x2c, 0x20, 0x0};
+static const sal_Unicode LongDateMonthSeparator[] = {0x20, 0x0};
+static const sal_Unicode LongDateYearSeparator[] = {0x20, 0x0};
+static const sal_Unicode quotationStart[] = {0x2018, 0x0};
+static const sal_Unicode quotationEnd[] = {0x2019, 0x0};
+static const sal_Unicode doubleQuotationStart[] = {0x201c, 0x0};
+static const sal_Unicode doubleQuotationEnd[] = {0x201d, 0x0};
+static const sal_Unicode timeAM[] = {0x41, 0x4d, 0x0};
+static const sal_Unicode timePM[] = {0x50, 0x4d, 0x0};
+static const sal_Unicode measurementSystem[] = {0x55, 0x53, 0x0};
+
+static const sal_Unicode* LCType[] = {
+ LC_CTYPE_Unoid,
+ dateSeparator,
+ thousandSeparator,
+ decimalSeparator,
+ timeSeparator,
+ time100SecSeparator,
+ listSeparator,
+ quotationStart,
+ quotationEnd,
+ doubleQuotationStart,
+ doubleQuotationEnd,
+ timeAM,
+ timePM,
+ measurementSystem,
+ LongDateDayOfWeekSeparator,
+ LongDateDaySeparator,
+ LongDateMonthSeparator,
+ LongDateYearSeparator
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLocaleItem_en_US(sal_Int16& count)
+{
+ count = 0;
+ return (sal_Unicode**)LCType;
+}
+static const sal_Unicode replaceFrom0[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x0};
+static const sal_Unicode replaceTo0[] = {0x5b, 0x24, 0x24, 0x2d, 0x34, 0x30, 0x39, 0x5d, 0x0};
+static const sal_Unicode FormatKey0[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement0[] = {1};
+static const sal_Unicode FormatType0[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage0[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex0[] = {0};
+static const sal_Unicode FormatCode0[] = {0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x0};
+static const sal_Unicode FormatDefaultName0[] = {0x0};
+static const sal_Unicode FormatKey1[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement1[] = {1};
+static const sal_Unicode FormatType1[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage1[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex1[] = {1};
+static const sal_Unicode FormatCode1[] = {0x30, 0x0};
+static const sal_Unicode FormatDefaultName1[] = {0x0};
+static const sal_Unicode FormatKey2[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement2[] = {0};
+static const sal_Unicode FormatType2[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage2[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex2[] = {2};
+static const sal_Unicode FormatCode2[] = {0x30, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName2[] = {0x0};
+static const sal_Unicode FormatKey3[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement3[] = {0};
+static const sal_Unicode FormatType3[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage3[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex3[] = {3};
+static const sal_Unicode FormatCode3[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName3[] = {0x0};
+static const sal_Unicode FormatKey4[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement4[] = {0};
+static const sal_Unicode FormatType4[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage4[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex4[] = {4};
+static const sal_Unicode FormatCode4[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName4[] = {0x0};
+static const sal_Unicode FormatKey5[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement5[] = {0};
+static const sal_Unicode FormatType5[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage5[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex5[] = {5};
+static const sal_Unicode FormatCode5[] = {0x23, 0x2c, 0x23, 0x23, 0x23, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName5[] = {0x0};
+static const sal_Unicode FormatKey6[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x37, 0x0};
+static const sal_Unicode defaultFormatElement6[] = {0};
+static const sal_Unicode FormatType6[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage6[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex6[] = {80};
+static const sal_Unicode FormatCode6[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x5f, 0x29, 0x3b, 0x28, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x29, 0x0};
+static const sal_Unicode FormatDefaultName6[] = {0x0};
+static const sal_Unicode FormatKey7[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x38, 0x0};
+static const sal_Unicode defaultFormatElement7[] = {0};
+static const sal_Unicode FormatType7[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage7[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex7[] = {81};
+static const sal_Unicode FormatCode7[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x5f, 0x29, 0x3b, 0x28, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x29, 0x0};
+static const sal_Unicode FormatDefaultName7[] = {0x0};
+static const sal_Unicode FormatKey8[] = {0x53, 0x63, 0x69, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x63, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement8[] = {0};
+static const sal_Unicode FormatType8[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage8[] = {0x53, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, 0x43, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex8[] = {6};
+static const sal_Unicode FormatCode8[] = {0x30, 0x2e, 0x30, 0x30, 0x45, 0x2b, 0x30, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName8[] = {0x0};
+static const sal_Unicode FormatKey9[] = {0x53, 0x63, 0x69, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x63, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement9[] = {1};
+static const sal_Unicode FormatType9[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage9[] = {0x53, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, 0x43, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex9[] = {7};
+static const sal_Unicode FormatCode9[] = {0x30, 0x2e, 0x30, 0x30, 0x45, 0x2b, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName9[] = {0x0};
+static const sal_Unicode FormatKey10[] = {0x53, 0x63, 0x69, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x63, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement10[] = {0};
+static const sal_Unicode FormatType10[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage10[] = {0x53, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, 0x43, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex10[] = {78};
+static const sal_Unicode FormatCode10[] = {0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x45, 0x2b, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName10[] = {0x0};
+static const sal_Unicode FormatKey11[] = {0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement11[] = {1};
+static const sal_Unicode FormatType11[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage11[] = {0x50, 0x45, 0x52, 0x43, 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex11[] = {8};
+static const sal_Unicode FormatCode11[] = {0x30, 0x25, 0x0};
+static const sal_Unicode FormatDefaultName11[] = {0x0};
+static const sal_Unicode FormatKey12[] = {0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement12[] = {1};
+static const sal_Unicode FormatType12[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage12[] = {0x50, 0x45, 0x52, 0x43, 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex12[] = {9};
+static const sal_Unicode FormatCode12[] = {0x30, 0x2e, 0x30, 0x30, 0x25, 0x0};
+static const sal_Unicode FormatDefaultName12[] = {0x0};
+static const sal_Unicode FormatKey13[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement13[] = {1};
+static const sal_Unicode FormatType13[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage13[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex13[] = {12};
+static const sal_Unicode FormatCode13[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x3b, 0x2d, 0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName13[] = {0x0};
+static const sal_Unicode FormatKey14[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement14[] = {0};
+static const sal_Unicode FormatType14[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage14[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex14[] = {13};
+static const sal_Unicode FormatCode14[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x3b, 0x2d, 0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName14[] = {0x0};
+static const sal_Unicode FormatKey15[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement15[] = {0};
+static const sal_Unicode FormatType15[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage15[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex15[] = {14};
+static const sal_Unicode FormatCode15[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x3b, 0x5b, 0x52, 0x45, 0x44, 0x5d, 0x2d, 0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName15[] = {0x0};
+static const sal_Unicode FormatKey16[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement16[] = {1};
+static const sal_Unicode FormatType16[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage16[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex16[] = {15};
+static const sal_Unicode FormatCode16[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x3b, 0x5b, 0x52, 0x45, 0x44, 0x5d, 0x2d, 0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName16[] = {0x0};
+static const sal_Unicode FormatKey17[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement17[] = {0};
+static const sal_Unicode FormatType17[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage17[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex17[] = {16};
+static const sal_Unicode FormatCode17[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x20, 0x43, 0x43, 0x43, 0x0};
+static const sal_Unicode FormatDefaultName17[] = {0x0};
+static const sal_Unicode FormatKey18[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement18[] = {0};
+static const sal_Unicode FormatType18[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage18[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex18[] = {17};
+static const sal_Unicode FormatCode18[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x2d, 0x2d, 0x3b, 0x5b, 0x52, 0x45, 0x44, 0x5d, 0x2d, 0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x2d, 0x2d, 0x0};
+static const sal_Unicode FormatDefaultName18[] = {0x0};
+static const sal_Unicode FormatKey19[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x37, 0x0};
+static const sal_Unicode defaultFormatElement19[] = {0};
+static const sal_Unicode FormatType19[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage19[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex19[] = {82};
+static const sal_Unicode FormatCode19[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x2a, 0x20, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x3b, 0x2d, 0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x2a, 0x20, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName19[] = {0x0};
+static const sal_Unicode FormatKey20[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x38, 0x0};
+static const sal_Unicode defaultFormatElement20[] = {0};
+static const sal_Unicode FormatType20[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage20[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex20[] = {83};
+static const sal_Unicode FormatCode20[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x2a, 0x20, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x3b, 0x2d, 0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x2a, 0x20, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName20[] = {0x0};
+static const sal_Unicode FormatKey21[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement21[] = {1};
+static const sal_Unicode FormatType21[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage21[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex21[] = {18};
+static const sal_Unicode FormatCode21[] = {0x4d, 0x2f, 0x44, 0x2f, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName21[] = {0x0};
+static const sal_Unicode FormatKey22[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x39, 0x0};
+static const sal_Unicode defaultFormatElement22[] = {1};
+static const sal_Unicode FormatType22[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage22[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex22[] = {19};
+static const sal_Unicode FormatCode22[] = {0x4e, 0x4e, 0x4e, 0x4e, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x44, 0x44, 0x2c, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName22[] = {0x0};
+static const sal_Unicode FormatKey23[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x38, 0x0};
+static const sal_Unicode defaultFormatElement23[] = {1};
+static const sal_Unicode FormatType23[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage23[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex23[] = {20};
+static const sal_Unicode FormatCode23[] = {0x4d, 0x4d, 0x2f, 0x44, 0x44, 0x2f, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName23[] = {0x0};
+static const sal_Unicode FormatKey24[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x37, 0x0};
+static const sal_Unicode defaultFormatElement24[] = {0};
+static const sal_Unicode FormatType24[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage24[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex24[] = {21};
+static const sal_Unicode FormatCode24[] = {0x4d, 0x4d, 0x2f, 0x44, 0x44, 0x2f, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName24[] = {0x0};
+static const sal_Unicode FormatKey25[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x30, 0x0};
+static const sal_Unicode defaultFormatElement25[] = {0};
+static const sal_Unicode FormatType25[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage25[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex25[] = {22};
+static const sal_Unicode FormatCode25[] = {0x4d, 0x4d, 0x4d, 0x20, 0x44, 0x2c, 0x20, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName25[] = {0x0};
+static const sal_Unicode FormatKey26[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement26[] = {0};
+static const sal_Unicode FormatType26[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage26[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex26[] = {23};
+static const sal_Unicode FormatCode26[] = {0x4d, 0x4d, 0x4d, 0x20, 0x44, 0x2c, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName26[] = {0x0};
+static const sal_Unicode FormatKey27[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement27[] = {0};
+static const sal_Unicode FormatType27[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage27[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex27[] = {24};
+static const sal_Unicode FormatCode27[] = {0x44, 0x2e, 0x20, 0x4d, 0x4d, 0x4d, 0x2e, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName27[] = {0x0};
+static const sal_Unicode FormatKey28[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement28[] = {0};
+static const sal_Unicode FormatType28[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage28[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex28[] = {25};
+static const sal_Unicode FormatCode28[] = {0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x44, 0x2c, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName28[] = {0x0};
+static const sal_Unicode FormatKey29[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x37, 0x0};
+static const sal_Unicode defaultFormatElement29[] = {0};
+static const sal_Unicode FormatType29[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage29[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex29[] = {26};
+static const sal_Unicode FormatCode29[] = {0x44, 0x2e, 0x20, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName29[] = {0x0};
+static const sal_Unicode FormatKey30[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement30[] = {0};
+static const sal_Unicode FormatType30[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage30[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex30[] = {27};
+static const sal_Unicode FormatCode30[] = {0x4e, 0x4e, 0x2c, 0x20, 0x4d, 0x4d, 0x4d, 0x20, 0x44, 0x2c, 0x20, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName30[] = {0x0};
+static const sal_Unicode FormatKey31[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement31[] = {0};
+static const sal_Unicode FormatType31[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage31[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex31[] = {28};
+static const sal_Unicode FormatCode31[] = {0x4e, 0x4e, 0x20, 0x44, 0x44, 0x2f, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName31[] = {0x0};
+static const sal_Unicode FormatKey32[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement32[] = {0};
+static const sal_Unicode FormatType32[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage32[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex32[] = {29};
+static const sal_Unicode FormatCode32[] = {0x4e, 0x4e, 0x2c, 0x20, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x44, 0x2c, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName32[] = {0x0};
+static const sal_Unicode FormatKey33[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement33[] = {0};
+static const sal_Unicode FormatType33[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage33[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex33[] = {30};
+static const sal_Unicode FormatCode33[] = {0x4e, 0x4e, 0x4e, 0x4e, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x44, 0x2c, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName33[] = {0x0};
+static const sal_Unicode FormatKey34[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x38, 0x0};
+static const sal_Unicode defaultFormatElement34[] = {0};
+static const sal_Unicode FormatType34[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage34[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex34[] = {31};
+static const sal_Unicode FormatCode34[] = {0x4d, 0x4d, 0x2d, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName34[] = {0x0};
+static const sal_Unicode FormatKey35[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x39, 0x0};
+static const sal_Unicode defaultFormatElement35[] = {0};
+static const sal_Unicode FormatType35[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage35[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex35[] = {32};
+static const sal_Unicode FormatCode35[] = {0x59, 0x59, 0x2d, 0x4d, 0x4d, 0x2d, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName35[] = {0x0};
+static const sal_Unicode FormatKey36[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x30, 0x0};
+static const sal_Unicode defaultFormatElement36[] = {0};
+static const sal_Unicode FormatType36[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage36[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex36[] = {33};
+static const sal_Unicode FormatCode36[] = {0x59, 0x59, 0x59, 0x59, 0x2d, 0x4d, 0x4d, 0x2d, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName36[] = {0x49, 0x53, 0x4f, 0x20, 0x38, 0x36, 0x30, 0x31, 0x0};
+static const sal_Unicode FormatKey37[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement37[] = {0};
+static const sal_Unicode FormatType37[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage37[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex37[] = {34};
+static const sal_Unicode FormatCode37[] = {0x4d, 0x4d, 0x2f, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName37[] = {0x0};
+static const sal_Unicode FormatKey38[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement38[] = {0};
+static const sal_Unicode FormatType38[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage38[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex38[] = {35};
+static const sal_Unicode FormatCode38[] = {0x4d, 0x4d, 0x4d, 0x20, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName38[] = {0x0};
+static const sal_Unicode FormatKey39[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement39[] = {0};
+static const sal_Unicode FormatType39[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage39[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex39[] = {36};
+static const sal_Unicode FormatCode39[] = {0x4d, 0x4d, 0x4d, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName39[] = {0x0};
+static const sal_Unicode FormatKey40[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement40[] = {0};
+static const sal_Unicode FormatType40[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage40[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex40[] = {37};
+static const sal_Unicode FormatCode40[] = {0x51, 0x51, 0x20, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName40[] = {0x0};
+static const sal_Unicode FormatKey41[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement41[] = {0};
+static const sal_Unicode FormatType41[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage41[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex41[] = {38};
+static const sal_Unicode FormatCode41[] = {0x57, 0x57, 0x0};
+static const sal_Unicode FormatDefaultName41[] = {0x0};
+static const sal_Unicode FormatKey42[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement42[] = {0};
+static const sal_Unicode FormatType42[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage42[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex42[] = {39};
+static const sal_Unicode FormatCode42[] = {0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName42[] = {0x0};
+static const sal_Unicode FormatKey43[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement43[] = {0};
+static const sal_Unicode FormatType43[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage43[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex43[] = {40};
+static const sal_Unicode FormatCode43[] = {0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x0};
+static const sal_Unicode FormatDefaultName43[] = {0x0};
+static const sal_Unicode FormatKey44[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement44[] = {1};
+static const sal_Unicode FormatType44[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage44[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex44[] = {41};
+static const sal_Unicode FormatCode44[] = {0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x20, 0x41, 0x4d, 0x2f, 0x50, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName44[] = {0x0};
+static const sal_Unicode FormatKey45[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement45[] = {1};
+static const sal_Unicode FormatType45[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage45[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex45[] = {42};
+static const sal_Unicode FormatCode45[] = {0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x20, 0x41, 0x4d, 0x2f, 0x50, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName45[] = {0x0};
+static const sal_Unicode FormatKey46[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement46[] = {0};
+static const sal_Unicode FormatType46[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage46[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex46[] = {43};
+static const sal_Unicode FormatCode46[] = {0x5b, 0x48, 0x48, 0x5d, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x0};
+static const sal_Unicode FormatDefaultName46[] = {0x0};
+static const sal_Unicode FormatKey47[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement47[] = {0};
+static const sal_Unicode FormatType47[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage47[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex47[] = {44};
+static const sal_Unicode FormatCode47[] = {0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName47[] = {0x0};
+static const sal_Unicode FormatKey48[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x37, 0x0};
+static const sal_Unicode defaultFormatElement48[] = {0};
+static const sal_Unicode FormatType48[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage48[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex48[] = {45};
+static const sal_Unicode FormatCode48[] = {0x5b, 0x48, 0x48, 0x5d, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName48[] = {0x0};
+static const sal_Unicode FormatKey49[] = {0x44, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement49[] = {1};
+static const sal_Unicode FormatType49[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage49[] = {0x44, 0x41, 0x54, 0x45, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex49[] = {46};
+static const sal_Unicode FormatCode49[] = {0x4d, 0x4d, 0x2f, 0x44, 0x44, 0x2f, 0x59, 0x59, 0x20, 0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x20, 0x41, 0x4d, 0x2f, 0x50, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName49[] = {0x0};
+static const sal_Unicode FormatKey50[] = {0x44, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement50[] = {0};
+static const sal_Unicode FormatType50[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage50[] = {0x44, 0x41, 0x54, 0x45, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex50[] = {47};
+static const sal_Unicode FormatCode50[] = {0x4d, 0x4d, 0x2f, 0x44, 0x44, 0x2f, 0x59, 0x59, 0x59, 0x59, 0x20, 0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x0};
+static const sal_Unicode FormatDefaultName50[] = {0x0};
+
+static const sal_Int16 FormatElementsCount0 = 51;
+static const sal_Unicode* FormatElementsArray0[] = {
+ FormatCode0,
+ FormatDefaultName0,
+ FormatKey0,
+ FormatType0,
+ FormatUsage0,
+ Formatindex0,
+ defaultFormatElement0,
+ FormatCode1,
+ FormatDefaultName1,
+ FormatKey1,
+ FormatType1,
+ FormatUsage1,
+ Formatindex1,
+ defaultFormatElement1,
+ FormatCode2,
+ FormatDefaultName2,
+ FormatKey2,
+ FormatType2,
+ FormatUsage2,
+ Formatindex2,
+ defaultFormatElement2,
+ FormatCode3,
+ FormatDefaultName3,
+ FormatKey3,
+ FormatType3,
+ FormatUsage3,
+ Formatindex3,
+ defaultFormatElement3,
+ FormatCode4,
+ FormatDefaultName4,
+ FormatKey4,
+ FormatType4,
+ FormatUsage4,
+ Formatindex4,
+ defaultFormatElement4,
+ FormatCode5,
+ FormatDefaultName5,
+ FormatKey5,
+ FormatType5,
+ FormatUsage5,
+ Formatindex5,
+ defaultFormatElement5,
+ FormatCode6,
+ FormatDefaultName6,
+ FormatKey6,
+ FormatType6,
+ FormatUsage6,
+ Formatindex6,
+ defaultFormatElement6,
+ FormatCode7,
+ FormatDefaultName7,
+ FormatKey7,
+ FormatType7,
+ FormatUsage7,
+ Formatindex7,
+ defaultFormatElement7,
+ FormatCode8,
+ FormatDefaultName8,
+ FormatKey8,
+ FormatType8,
+ FormatUsage8,
+ Formatindex8,
+ defaultFormatElement8,
+ FormatCode9,
+ FormatDefaultName9,
+ FormatKey9,
+ FormatType9,
+ FormatUsage9,
+ Formatindex9,
+ defaultFormatElement9,
+ FormatCode10,
+ FormatDefaultName10,
+ FormatKey10,
+ FormatType10,
+ FormatUsage10,
+ Formatindex10,
+ defaultFormatElement10,
+ FormatCode11,
+ FormatDefaultName11,
+ FormatKey11,
+ FormatType11,
+ FormatUsage11,
+ Formatindex11,
+ defaultFormatElement11,
+ FormatCode12,
+ FormatDefaultName12,
+ FormatKey12,
+ FormatType12,
+ FormatUsage12,
+ Formatindex12,
+ defaultFormatElement12,
+ FormatCode13,
+ FormatDefaultName13,
+ FormatKey13,
+ FormatType13,
+ FormatUsage13,
+ Formatindex13,
+ defaultFormatElement13,
+ FormatCode14,
+ FormatDefaultName14,
+ FormatKey14,
+ FormatType14,
+ FormatUsage14,
+ Formatindex14,
+ defaultFormatElement14,
+ FormatCode15,
+ FormatDefaultName15,
+ FormatKey15,
+ FormatType15,
+ FormatUsage15,
+ Formatindex15,
+ defaultFormatElement15,
+ FormatCode16,
+ FormatDefaultName16,
+ FormatKey16,
+ FormatType16,
+ FormatUsage16,
+ Formatindex16,
+ defaultFormatElement16,
+ FormatCode17,
+ FormatDefaultName17,
+ FormatKey17,
+ FormatType17,
+ FormatUsage17,
+ Formatindex17,
+ defaultFormatElement17,
+ FormatCode18,
+ FormatDefaultName18,
+ FormatKey18,
+ FormatType18,
+ FormatUsage18,
+ Formatindex18,
+ defaultFormatElement18,
+ FormatCode19,
+ FormatDefaultName19,
+ FormatKey19,
+ FormatType19,
+ FormatUsage19,
+ Formatindex19,
+ defaultFormatElement19,
+ FormatCode20,
+ FormatDefaultName20,
+ FormatKey20,
+ FormatType20,
+ FormatUsage20,
+ Formatindex20,
+ defaultFormatElement20,
+ FormatCode21,
+ FormatDefaultName21,
+ FormatKey21,
+ FormatType21,
+ FormatUsage21,
+ Formatindex21,
+ defaultFormatElement21,
+ FormatCode22,
+ FormatDefaultName22,
+ FormatKey22,
+ FormatType22,
+ FormatUsage22,
+ Formatindex22,
+ defaultFormatElement22,
+ FormatCode23,
+ FormatDefaultName23,
+ FormatKey23,
+ FormatType23,
+ FormatUsage23,
+ Formatindex23,
+ defaultFormatElement23,
+ FormatCode24,
+ FormatDefaultName24,
+ FormatKey24,
+ FormatType24,
+ FormatUsage24,
+ Formatindex24,
+ defaultFormatElement24,
+ FormatCode25,
+ FormatDefaultName25,
+ FormatKey25,
+ FormatType25,
+ FormatUsage25,
+ Formatindex25,
+ defaultFormatElement25,
+ FormatCode26,
+ FormatDefaultName26,
+ FormatKey26,
+ FormatType26,
+ FormatUsage26,
+ Formatindex26,
+ defaultFormatElement26,
+ FormatCode27,
+ FormatDefaultName27,
+ FormatKey27,
+ FormatType27,
+ FormatUsage27,
+ Formatindex27,
+ defaultFormatElement27,
+ FormatCode28,
+ FormatDefaultName28,
+ FormatKey28,
+ FormatType28,
+ FormatUsage28,
+ Formatindex28,
+ defaultFormatElement28,
+ FormatCode29,
+ FormatDefaultName29,
+ FormatKey29,
+ FormatType29,
+ FormatUsage29,
+ Formatindex29,
+ defaultFormatElement29,
+ FormatCode30,
+ FormatDefaultName30,
+ FormatKey30,
+ FormatType30,
+ FormatUsage30,
+ Formatindex30,
+ defaultFormatElement30,
+ FormatCode31,
+ FormatDefaultName31,
+ FormatKey31,
+ FormatType31,
+ FormatUsage31,
+ Formatindex31,
+ defaultFormatElement31,
+ FormatCode32,
+ FormatDefaultName32,
+ FormatKey32,
+ FormatType32,
+ FormatUsage32,
+ Formatindex32,
+ defaultFormatElement32,
+ FormatCode33,
+ FormatDefaultName33,
+ FormatKey33,
+ FormatType33,
+ FormatUsage33,
+ Formatindex33,
+ defaultFormatElement33,
+ FormatCode34,
+ FormatDefaultName34,
+ FormatKey34,
+ FormatType34,
+ FormatUsage34,
+ Formatindex34,
+ defaultFormatElement34,
+ FormatCode35,
+ FormatDefaultName35,
+ FormatKey35,
+ FormatType35,
+ FormatUsage35,
+ Formatindex35,
+ defaultFormatElement35,
+ FormatCode36,
+ FormatDefaultName36,
+ FormatKey36,
+ FormatType36,
+ FormatUsage36,
+ Formatindex36,
+ defaultFormatElement36,
+ FormatCode37,
+ FormatDefaultName37,
+ FormatKey37,
+ FormatType37,
+ FormatUsage37,
+ Formatindex37,
+ defaultFormatElement37,
+ FormatCode38,
+ FormatDefaultName38,
+ FormatKey38,
+ FormatType38,
+ FormatUsage38,
+ Formatindex38,
+ defaultFormatElement38,
+ FormatCode39,
+ FormatDefaultName39,
+ FormatKey39,
+ FormatType39,
+ FormatUsage39,
+ Formatindex39,
+ defaultFormatElement39,
+ FormatCode40,
+ FormatDefaultName40,
+ FormatKey40,
+ FormatType40,
+ FormatUsage40,
+ Formatindex40,
+ defaultFormatElement40,
+ FormatCode41,
+ FormatDefaultName41,
+ FormatKey41,
+ FormatType41,
+ FormatUsage41,
+ Formatindex41,
+ defaultFormatElement41,
+ FormatCode42,
+ FormatDefaultName42,
+ FormatKey42,
+ FormatType42,
+ FormatUsage42,
+ Formatindex42,
+ defaultFormatElement42,
+ FormatCode43,
+ FormatDefaultName43,
+ FormatKey43,
+ FormatType43,
+ FormatUsage43,
+ Formatindex43,
+ defaultFormatElement43,
+ FormatCode44,
+ FormatDefaultName44,
+ FormatKey44,
+ FormatType44,
+ FormatUsage44,
+ Formatindex44,
+ defaultFormatElement44,
+ FormatCode45,
+ FormatDefaultName45,
+ FormatKey45,
+ FormatType45,
+ FormatUsage45,
+ Formatindex45,
+ defaultFormatElement45,
+ FormatCode46,
+ FormatDefaultName46,
+ FormatKey46,
+ FormatType46,
+ FormatUsage46,
+ Formatindex46,
+ defaultFormatElement46,
+ FormatCode47,
+ FormatDefaultName47,
+ FormatKey47,
+ FormatType47,
+ FormatUsage47,
+ Formatindex47,
+ defaultFormatElement47,
+ FormatCode48,
+ FormatDefaultName48,
+ FormatKey48,
+ FormatType48,
+ FormatUsage48,
+ Formatindex48,
+ defaultFormatElement48,
+ FormatCode49,
+ FormatDefaultName49,
+ FormatKey49,
+ FormatType49,
+ FormatUsage49,
+ Formatindex49,
+ defaultFormatElement49,
+ FormatCode50,
+ FormatDefaultName50,
+ FormatKey50,
+ FormatType50,
+ FormatUsage50,
+ Formatindex50,
+ defaultFormatElement50,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode const * const * SAL_CALL getAllFormats0_en_US(sal_Int16& count, const sal_Unicode*& from, const sal_Unicode*& to)
+{
+ count = FormatElementsCount0;
+ from = replaceFrom0;
+ to = replaceTo0;
+ return (sal_Unicode**)FormatElementsArray0;
+}
+static const sal_Unicode DateAcceptancePattern0[] = {0x4d, 0x2f, 0x44, 0x2f, 0x59, 0x0};
+static const sal_Unicode DateAcceptancePattern1[] = {0x4d, 0x2f, 0x44, 0x0};
+static const sal_Int16 DateAcceptancePatternsCount = 2;
+static const sal_Unicode* DateAcceptancePatternsArray[] = {
+ DateAcceptancePattern0,
+ DateAcceptancePattern1,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getDateAcceptancePatterns_en_US(sal_Int16& count)
+{
+ count = DateAcceptancePatternsCount;
+ return (sal_Unicode**)DateAcceptancePatternsArray;
+}
+static const sal_Unicode replaceFrom1[] = {0x0};
+static const sal_Unicode replaceTo1[] = {0x0};
+static const sal_Unicode FormatKey51[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement51[] = {0};
+static const sal_Unicode FormatType51[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage51[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex51[] = {60};
+static const sal_Unicode FormatCode51[] = {0x5b, 0x7e, 0x6a, 0x65, 0x77, 0x69, 0x73, 0x68, 0x5d, 0x4e, 0x4e, 0x4e, 0x4e, 0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName51[] = {0x0};
+static const sal_Unicode FormatKey52[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement52[] = {0};
+static const sal_Unicode FormatType52[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage52[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex52[] = {61};
+static const sal_Unicode FormatCode52[] = {0x5b, 0x7e, 0x6a, 0x65, 0x77, 0x69, 0x73, 0x68, 0x5d, 0x4e, 0x4e, 0x4e, 0x4e, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x44, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName52[] = {0x0};
+static const sal_Unicode FormatKey53[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement53[] = {0};
+static const sal_Unicode FormatType53[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage53[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex53[] = {62};
+static const sal_Unicode FormatCode53[] = {0x5b, 0x7e, 0x6a, 0x65, 0x77, 0x69, 0x73, 0x68, 0x5d, 0x4e, 0x4e, 0x20, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x44, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName53[] = {0x0};
+static const sal_Unicode FormatKey54[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement54[] = {0};
+static const sal_Unicode FormatType54[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage54[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex54[] = {63};
+static const sal_Unicode FormatCode54[] = {0x5b, 0x7e, 0x6a, 0x65, 0x77, 0x69, 0x73, 0x68, 0x5d, 0x4e, 0x4e, 0x20, 0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName54[] = {0x0};
+static const sal_Unicode FormatKey55[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement55[] = {0};
+static const sal_Unicode FormatType55[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage55[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex55[] = {64};
+static const sal_Unicode FormatCode55[] = {0x5b, 0x7e, 0x6a, 0x65, 0x77, 0x69, 0x73, 0x68, 0x5d, 0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName55[] = {0x0};
+static const sal_Unicode FormatKey56[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x37, 0x0};
+static const sal_Unicode defaultFormatElement56[] = {0};
+static const sal_Unicode FormatType56[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage56[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex56[] = {65};
+static const sal_Unicode FormatCode56[] = {0x5b, 0x7e, 0x6a, 0x65, 0x77, 0x69, 0x73, 0x68, 0x5d, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x44, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName56[] = {0x0};
+static const sal_Unicode FormatKey57[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x38, 0x0};
+static const sal_Unicode defaultFormatElement57[] = {0};
+static const sal_Unicode FormatType57[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage57[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex57[] = {66};
+static const sal_Unicode FormatCode57[] = {0x5b, 0x7e, 0x6a, 0x65, 0x77, 0x69, 0x73, 0x68, 0x5d, 0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName57[] = {0x0};
+static const sal_Unicode FormatKey58[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x39, 0x0};
+static const sal_Unicode defaultFormatElement58[] = {0};
+static const sal_Unicode FormatType58[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage58[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex58[] = {67};
+static const sal_Unicode FormatCode58[] = {0x5b, 0x7e, 0x6a, 0x65, 0x77, 0x69, 0x73, 0x68, 0x5d, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName58[] = {0x0};
+static const sal_Unicode FormatKey59[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x30, 0x0};
+static const sal_Unicode defaultFormatElement59[] = {0};
+static const sal_Unicode FormatType59[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage59[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex59[] = {68};
+static const sal_Unicode FormatCode59[] = {0x5b, 0x7e, 0x6a, 0x65, 0x77, 0x69, 0x73, 0x68, 0x5d, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName59[] = {0x0};
+static const sal_Unicode FormatKey60[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement60[] = {0};
+static const sal_Unicode FormatType60[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage60[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex60[] = {69};
+static const sal_Unicode FormatCode60[] = {0x5b, 0x7e, 0x6a, 0x65, 0x77, 0x69, 0x73, 0x68, 0x5d, 0x4d, 0x4d, 0x4d, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName60[] = {0x0};
+
+static const sal_Int16 FormatElementsCount1 = 10;
+static const sal_Unicode* FormatElementsArray1[] = {
+ FormatCode51,
+ FormatDefaultName51,
+ FormatKey51,
+ FormatType51,
+ FormatUsage51,
+ Formatindex51,
+ defaultFormatElement51,
+ FormatCode52,
+ FormatDefaultName52,
+ FormatKey52,
+ FormatType52,
+ FormatUsage52,
+ Formatindex52,
+ defaultFormatElement52,
+ FormatCode53,
+ FormatDefaultName53,
+ FormatKey53,
+ FormatType53,
+ FormatUsage53,
+ Formatindex53,
+ defaultFormatElement53,
+ FormatCode54,
+ FormatDefaultName54,
+ FormatKey54,
+ FormatType54,
+ FormatUsage54,
+ Formatindex54,
+ defaultFormatElement54,
+ FormatCode55,
+ FormatDefaultName55,
+ FormatKey55,
+ FormatType55,
+ FormatUsage55,
+ Formatindex55,
+ defaultFormatElement55,
+ FormatCode56,
+ FormatDefaultName56,
+ FormatKey56,
+ FormatType56,
+ FormatUsage56,
+ Formatindex56,
+ defaultFormatElement56,
+ FormatCode57,
+ FormatDefaultName57,
+ FormatKey57,
+ FormatType57,
+ FormatUsage57,
+ Formatindex57,
+ defaultFormatElement57,
+ FormatCode58,
+ FormatDefaultName58,
+ FormatKey58,
+ FormatType58,
+ FormatUsage58,
+ Formatindex58,
+ defaultFormatElement58,
+ FormatCode59,
+ FormatDefaultName59,
+ FormatKey59,
+ FormatType59,
+ FormatUsage59,
+ Formatindex59,
+ defaultFormatElement59,
+ FormatCode60,
+ FormatDefaultName60,
+ FormatKey60,
+ FormatType60,
+ FormatUsage60,
+ Formatindex60,
+ defaultFormatElement60,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode const * const * SAL_CALL getAllFormats1_en_US(sal_Int16& count, const sal_Unicode*& from, const sal_Unicode*& to)
+{
+ count = FormatElementsCount1;
+ from = replaceFrom1;
+ to = replaceTo1;
+ return (sal_Unicode**)FormatElementsArray1;
+}
+static const sal_Unicode CollatorID0[] = {0x61, 0x6c, 0x70, 0x68, 0x61, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x0};
+static const sal_Unicode CollatorRule0[] = {0x0};
+static const sal_Unicode defaultCollator0[] = {1};
+
+static const sal_Unicode collationOption0[] = {0x49, 0x47, 0x4e, 0x4f, 0x52, 0x45, 0x5f, 0x43, 0x41, 0x53, 0x45, 0x0};
+static const sal_Int16 nbOfCollationOptions = 1;
+
+static const sal_Int16 nbOfCollations = 1;
+
+
+static const sal_Unicode* LCCollatorArray[] = {
+ CollatorID0,
+ defaultCollator0,
+ CollatorRule0,
+};
+
+static const sal_Unicode* collationOptions[] = {collationOption0, NULL };
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollatorImplementation_en_US(sal_Int16& count)
+{
+ count = nbOfCollations;
+ return (sal_Unicode**)LCCollatorArray;
+}
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollationOptions_en_US(sal_Int16& count)
+{
+ count = nbOfCollationOptions;
+ return (sal_Unicode**)collationOptions;
+}
+static const sal_Unicode searchOption0[] = {0x49, 0x47, 0x4e, 0x4f, 0x52, 0x45, 0x5f, 0x43, 0x41, 0x53, 0x45, 0x0};
+static const sal_Int16 nbOfSearchOptions = 1;
+
+static const sal_Unicode* searchOptions[] = {searchOption0, NULL };
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getSearchOptions_en_US(sal_Int16& count)
+{
+ count = nbOfSearchOptions;
+ return (sal_Unicode**)searchOptions;
+}
+static const sal_Unicode IndexID0[] = {0x61, 0x6c, 0x70, 0x68, 0x61, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x0};
+static const sal_Unicode IndexModule0[] = {0x0};
+static const sal_Unicode IndexKey0[] = {0x41, 0x2d, 0x5a, 0x0};
+static const sal_Unicode defaultIndex0[] = {1};
+static const sal_Unicode defaultPhonetic0[] = {0};
+
+static const sal_Unicode unicodeScript0[] = {0x30, 0x0};
+static const sal_Unicode unicodeScript1[] = {0x31, 0x0};
+static const sal_Unicode followPageWord0[] = {0x70, 0x2e, 0x0};
+static const sal_Unicode followPageWord1[] = {0x70, 0x70, 0x2e, 0x0};
+static const sal_Int16 nbOfIndexs = 1;
+
+
+static const sal_Unicode* IndexArray[] = {
+ IndexID0,
+ IndexModule0,
+ IndexKey0,
+ defaultIndex0,
+ defaultPhonetic0,
+};
+
+static const sal_Int16 nbOfUnicodeScripts = 2;
+
+static const sal_Unicode* UnicodeScriptArray[] = {unicodeScript0, unicodeScript1, NULL };
+
+static const sal_Int16 nbOfPageWords = 2;
+
+static const sal_Unicode* FollowPageWordArray[] = {
+ followPageWord0,
+ followPageWord1,
+ NULL
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getIndexAlgorithm_en_US(sal_Int16& count)
+{
+ count = nbOfIndexs;
+ return (sal_Unicode**)IndexArray;
+}
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getUnicodeScripts_en_US(sal_Int16& count)
+{
+ count = nbOfUnicodeScripts;
+ return (sal_Unicode**)UnicodeScriptArray;
+}
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getFollowPageWords_en_US(sal_Int16& count)
+{
+ count = nbOfPageWords;
+ return (sal_Unicode**)FollowPageWordArray;
+}
+static const sal_Unicode calendarID0[] = {0x67, 0x72, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x61, 0x6e, 0x0};
+static const sal_Unicode defaultCalendar0[] = {1};
+static const sal_Unicode dayID00[] = {0x73, 0x75, 0x6e, 0x0};
+static const sal_Unicode dayDefaultAbbrvName00[] = {0x53, 0x75, 0x6e, 0x0};
+static const sal_Unicode dayDefaultFullName00[] = {0x53, 0x75, 0x6e, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName00[] = {0x53, 0x0};
+static const sal_Unicode dayID01[] = {0x6d, 0x6f, 0x6e, 0x0};
+static const sal_Unicode dayDefaultAbbrvName01[] = {0x4d, 0x6f, 0x6e, 0x0};
+static const sal_Unicode dayDefaultFullName01[] = {0x4d, 0x6f, 0x6e, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName01[] = {0x4d, 0x0};
+static const sal_Unicode dayID02[] = {0x74, 0x75, 0x65, 0x0};
+static const sal_Unicode dayDefaultAbbrvName02[] = {0x54, 0x75, 0x65, 0x0};
+static const sal_Unicode dayDefaultFullName02[] = {0x54, 0x75, 0x65, 0x73, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName02[] = {0x54, 0x0};
+static const sal_Unicode dayID03[] = {0x77, 0x65, 0x64, 0x0};
+static const sal_Unicode dayDefaultAbbrvName03[] = {0x57, 0x65, 0x64, 0x0};
+static const sal_Unicode dayDefaultFullName03[] = {0x57, 0x65, 0x64, 0x6e, 0x65, 0x73, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName03[] = {0x57, 0x0};
+static const sal_Unicode dayID04[] = {0x74, 0x68, 0x75, 0x0};
+static const sal_Unicode dayDefaultAbbrvName04[] = {0x54, 0x68, 0x75, 0x0};
+static const sal_Unicode dayDefaultFullName04[] = {0x54, 0x68, 0x75, 0x72, 0x73, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName04[] = {0x54, 0x0};
+static const sal_Unicode dayID05[] = {0x66, 0x72, 0x69, 0x0};
+static const sal_Unicode dayDefaultAbbrvName05[] = {0x46, 0x72, 0x69, 0x0};
+static const sal_Unicode dayDefaultFullName05[] = {0x46, 0x72, 0x69, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName05[] = {0x46, 0x0};
+static const sal_Unicode dayID06[] = {0x73, 0x61, 0x74, 0x0};
+static const sal_Unicode dayDefaultAbbrvName06[] = {0x53, 0x61, 0x74, 0x0};
+static const sal_Unicode dayDefaultFullName06[] = {0x53, 0x61, 0x74, 0x75, 0x72, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName06[] = {0x53, 0x0};
+static const sal_Unicode monthID00[] = {0x6a, 0x61, 0x6e, 0x0};
+static const sal_Unicode monthDefaultAbbrvName00[] = {0x4a, 0x61, 0x6e, 0x0};
+static const sal_Unicode monthDefaultFullName00[] = {0x4a, 0x61, 0x6e, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode monthDefaultNarrowName00[] = {0x4a, 0x0};
+static const sal_Unicode monthID01[] = {0x66, 0x65, 0x62, 0x0};
+static const sal_Unicode monthDefaultAbbrvName01[] = {0x46, 0x65, 0x62, 0x0};
+static const sal_Unicode monthDefaultFullName01[] = {0x46, 0x65, 0x62, 0x72, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode monthDefaultNarrowName01[] = {0x46, 0x0};
+static const sal_Unicode monthID02[] = {0x6d, 0x61, 0x72, 0x0};
+static const sal_Unicode monthDefaultAbbrvName02[] = {0x4d, 0x61, 0x72, 0x0};
+static const sal_Unicode monthDefaultFullName02[] = {0x4d, 0x61, 0x72, 0x63, 0x68, 0x0};
+static const sal_Unicode monthDefaultNarrowName02[] = {0x4d, 0x0};
+static const sal_Unicode monthID03[] = {0x61, 0x70, 0x72, 0x0};
+static const sal_Unicode monthDefaultAbbrvName03[] = {0x41, 0x70, 0x72, 0x0};
+static const sal_Unicode monthDefaultFullName03[] = {0x41, 0x70, 0x72, 0x69, 0x6c, 0x0};
+static const sal_Unicode monthDefaultNarrowName03[] = {0x41, 0x0};
+static const sal_Unicode monthID04[] = {0x6d, 0x61, 0x79, 0x0};
+static const sal_Unicode monthDefaultAbbrvName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode monthDefaultFullName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode monthDefaultNarrowName04[] = {0x4d, 0x0};
+static const sal_Unicode monthID05[] = {0x6a, 0x75, 0x6e, 0x0};
+static const sal_Unicode monthDefaultAbbrvName05[] = {0x4a, 0x75, 0x6e, 0x0};
+static const sal_Unicode monthDefaultFullName05[] = {0x4a, 0x75, 0x6e, 0x65, 0x0};
+static const sal_Unicode monthDefaultNarrowName05[] = {0x4a, 0x0};
+static const sal_Unicode monthID06[] = {0x6a, 0x75, 0x6c, 0x0};
+static const sal_Unicode monthDefaultAbbrvName06[] = {0x4a, 0x75, 0x6c, 0x0};
+static const sal_Unicode monthDefaultFullName06[] = {0x4a, 0x75, 0x6c, 0x79, 0x0};
+static const sal_Unicode monthDefaultNarrowName06[] = {0x4a, 0x0};
+static const sal_Unicode monthID07[] = {0x61, 0x75, 0x67, 0x0};
+static const sal_Unicode monthDefaultAbbrvName07[] = {0x41, 0x75, 0x67, 0x0};
+static const sal_Unicode monthDefaultFullName07[] = {0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x0};
+static const sal_Unicode monthDefaultNarrowName07[] = {0x41, 0x0};
+static const sal_Unicode monthID08[] = {0x73, 0x65, 0x70, 0x0};
+static const sal_Unicode monthDefaultAbbrvName08[] = {0x53, 0x65, 0x70, 0x0};
+static const sal_Unicode monthDefaultFullName08[] = {0x53, 0x65, 0x70, 0x74, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName08[] = {0x53, 0x0};
+static const sal_Unicode monthID09[] = {0x6f, 0x63, 0x74, 0x0};
+static const sal_Unicode monthDefaultAbbrvName09[] = {0x4f, 0x63, 0x74, 0x0};
+static const sal_Unicode monthDefaultFullName09[] = {0x4f, 0x63, 0x74, 0x6f, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName09[] = {0x4f, 0x0};
+static const sal_Unicode monthID010[] = {0x6e, 0x6f, 0x76, 0x0};
+static const sal_Unicode monthDefaultAbbrvName010[] = {0x4e, 0x6f, 0x76, 0x0};
+static const sal_Unicode monthDefaultFullName010[] = {0x4e, 0x6f, 0x76, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName010[] = {0x4e, 0x0};
+static const sal_Unicode monthID011[] = {0x64, 0x65, 0x63, 0x0};
+static const sal_Unicode monthDefaultAbbrvName011[] = {0x44, 0x65, 0x63, 0x0};
+static const sal_Unicode monthDefaultFullName011[] = {0x44, 0x65, 0x63, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName011[] = {0x44, 0x0};
+static const sal_Unicode genitiveMonthID00[] = {0x6a, 0x61, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName00[] = {0x4a, 0x61, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName00[] = {0x4a, 0x61, 0x6e, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName00[] = {0x4a, 0x0};
+static const sal_Unicode genitiveMonthID01[] = {0x66, 0x65, 0x62, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName01[] = {0x46, 0x65, 0x62, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName01[] = {0x46, 0x65, 0x62, 0x72, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName01[] = {0x46, 0x0};
+static const sal_Unicode genitiveMonthID02[] = {0x6d, 0x61, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName02[] = {0x4d, 0x61, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName02[] = {0x4d, 0x61, 0x72, 0x63, 0x68, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName02[] = {0x4d, 0x0};
+static const sal_Unicode genitiveMonthID03[] = {0x61, 0x70, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName03[] = {0x41, 0x70, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName03[] = {0x41, 0x70, 0x72, 0x69, 0x6c, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName03[] = {0x41, 0x0};
+static const sal_Unicode genitiveMonthID04[] = {0x6d, 0x61, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName04[] = {0x4d, 0x0};
+static const sal_Unicode genitiveMonthID05[] = {0x6a, 0x75, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName05[] = {0x4a, 0x75, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName05[] = {0x4a, 0x75, 0x6e, 0x65, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName05[] = {0x4a, 0x0};
+static const sal_Unicode genitiveMonthID06[] = {0x6a, 0x75, 0x6c, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName06[] = {0x4a, 0x75, 0x6c, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName06[] = {0x4a, 0x75, 0x6c, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName06[] = {0x4a, 0x0};
+static const sal_Unicode genitiveMonthID07[] = {0x61, 0x75, 0x67, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName07[] = {0x41, 0x75, 0x67, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName07[] = {0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName07[] = {0x41, 0x0};
+static const sal_Unicode genitiveMonthID08[] = {0x73, 0x65, 0x70, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName08[] = {0x53, 0x65, 0x70, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName08[] = {0x53, 0x65, 0x70, 0x74, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName08[] = {0x53, 0x0};
+static const sal_Unicode genitiveMonthID09[] = {0x6f, 0x63, 0x74, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName09[] = {0x4f, 0x63, 0x74, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName09[] = {0x4f, 0x63, 0x74, 0x6f, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName09[] = {0x4f, 0x0};
+static const sal_Unicode genitiveMonthID010[] = {0x6e, 0x6f, 0x76, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName010[] = {0x4e, 0x6f, 0x76, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName010[] = {0x4e, 0x6f, 0x76, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName010[] = {0x4e, 0x0};
+static const sal_Unicode genitiveMonthID011[] = {0x64, 0x65, 0x63, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName011[] = {0x44, 0x65, 0x63, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName011[] = {0x44, 0x65, 0x63, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName011[] = {0x44, 0x0};
+static const sal_Unicode partitiveMonthID00[] = {0x6a, 0x61, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName00[] = {0x4a, 0x61, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName00[] = {0x4a, 0x61, 0x6e, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName00[] = {0x4a, 0x0};
+static const sal_Unicode partitiveMonthID01[] = {0x66, 0x65, 0x62, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName01[] = {0x46, 0x65, 0x62, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName01[] = {0x46, 0x65, 0x62, 0x72, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName01[] = {0x46, 0x0};
+static const sal_Unicode partitiveMonthID02[] = {0x6d, 0x61, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName02[] = {0x4d, 0x61, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName02[] = {0x4d, 0x61, 0x72, 0x63, 0x68, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName02[] = {0x4d, 0x0};
+static const sal_Unicode partitiveMonthID03[] = {0x61, 0x70, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName03[] = {0x41, 0x70, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName03[] = {0x41, 0x70, 0x72, 0x69, 0x6c, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName03[] = {0x41, 0x0};
+static const sal_Unicode partitiveMonthID04[] = {0x6d, 0x61, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName04[] = {0x4d, 0x0};
+static const sal_Unicode partitiveMonthID05[] = {0x6a, 0x75, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName05[] = {0x4a, 0x75, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName05[] = {0x4a, 0x75, 0x6e, 0x65, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName05[] = {0x4a, 0x0};
+static const sal_Unicode partitiveMonthID06[] = {0x6a, 0x75, 0x6c, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName06[] = {0x4a, 0x75, 0x6c, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName06[] = {0x4a, 0x75, 0x6c, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName06[] = {0x4a, 0x0};
+static const sal_Unicode partitiveMonthID07[] = {0x61, 0x75, 0x67, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName07[] = {0x41, 0x75, 0x67, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName07[] = {0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName07[] = {0x41, 0x0};
+static const sal_Unicode partitiveMonthID08[] = {0x73, 0x65, 0x70, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName08[] = {0x53, 0x65, 0x70, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName08[] = {0x53, 0x65, 0x70, 0x74, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName08[] = {0x53, 0x0};
+static const sal_Unicode partitiveMonthID09[] = {0x6f, 0x63, 0x74, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName09[] = {0x4f, 0x63, 0x74, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName09[] = {0x4f, 0x63, 0x74, 0x6f, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName09[] = {0x4f, 0x0};
+static const sal_Unicode partitiveMonthID010[] = {0x6e, 0x6f, 0x76, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName010[] = {0x4e, 0x6f, 0x76, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName010[] = {0x4e, 0x6f, 0x76, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName010[] = {0x4e, 0x0};
+static const sal_Unicode partitiveMonthID011[] = {0x64, 0x65, 0x63, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName011[] = {0x44, 0x65, 0x63, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName011[] = {0x44, 0x65, 0x63, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName011[] = {0x44, 0x0};
+static const sal_Unicode eraID00[] = {0x62, 0x63, 0x0};
+
+static const sal_Unicode eraDefaultAbbrvName00[] = {0x42, 0x43, 0x0};
+static const sal_Unicode eraDefaultFullName00[] = {0x42, 0x43, 0x0};
+static const sal_Unicode eraID01[] = {0x61, 0x64, 0x0};
+
+static const sal_Unicode eraDefaultAbbrvName01[] = {0x41, 0x44, 0x0};
+static const sal_Unicode eraDefaultFullName01[] = {0x41, 0x44, 0x0};
+static const sal_Unicode startDayOfWeek0[] = {0x73, 0x75, 0x6e, 0x0};
+static const sal_Unicode minimalDaysInFirstWeek0[] = {1};
+static const sal_Unicode calendarID1[] = {0x6a, 0x65, 0x77, 0x69, 0x73, 0x68, 0x0};
+static const sal_Unicode defaultCalendar1[] = {0};
+static const sal_Unicode dayID10[] = {0x73, 0x75, 0x6e, 0x0};
+static const sal_Unicode dayDefaultAbbrvName10[] = {0x53, 0x75, 0x6e, 0x0};
+static const sal_Unicode dayDefaultFullName10[] = {0x53, 0x75, 0x6e, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName10[] = {0x53, 0x0};
+static const sal_Unicode dayID11[] = {0x6d, 0x6f, 0x6e, 0x0};
+static const sal_Unicode dayDefaultAbbrvName11[] = {0x4d, 0x6f, 0x6e, 0x0};
+static const sal_Unicode dayDefaultFullName11[] = {0x4d, 0x6f, 0x6e, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName11[] = {0x4d, 0x0};
+static const sal_Unicode dayID12[] = {0x74, 0x75, 0x65, 0x0};
+static const sal_Unicode dayDefaultAbbrvName12[] = {0x54, 0x75, 0x65, 0x0};
+static const sal_Unicode dayDefaultFullName12[] = {0x54, 0x75, 0x65, 0x73, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName12[] = {0x54, 0x0};
+static const sal_Unicode dayID13[] = {0x77, 0x65, 0x64, 0x0};
+static const sal_Unicode dayDefaultAbbrvName13[] = {0x57, 0x65, 0x64, 0x0};
+static const sal_Unicode dayDefaultFullName13[] = {0x57, 0x65, 0x64, 0x6e, 0x65, 0x73, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName13[] = {0x57, 0x0};
+static const sal_Unicode dayID14[] = {0x74, 0x68, 0x75, 0x0};
+static const sal_Unicode dayDefaultAbbrvName14[] = {0x54, 0x68, 0x75, 0x0};
+static const sal_Unicode dayDefaultFullName14[] = {0x54, 0x68, 0x75, 0x72, 0x73, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName14[] = {0x54, 0x0};
+static const sal_Unicode dayID15[] = {0x66, 0x72, 0x69, 0x0};
+static const sal_Unicode dayDefaultAbbrvName15[] = {0x46, 0x72, 0x69, 0x0};
+static const sal_Unicode dayDefaultFullName15[] = {0x46, 0x72, 0x69, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName15[] = {0x46, 0x0};
+static const sal_Unicode dayID16[] = {0x73, 0x61, 0x74, 0x0};
+static const sal_Unicode dayDefaultAbbrvName16[] = {0x53, 0x61, 0x74, 0x0};
+static const sal_Unicode dayDefaultFullName16[] = {0x53, 0x68, 0x61, 0x62, 0x62, 0x61, 0x74, 0x0};
+static const sal_Unicode dayDefaultNarrowName16[] = {0x53, 0x0};
+static const sal_Unicode monthID10[] = {0x4e, 0x69, 0x73, 0x73, 0x61, 0x6e, 0x0};
+static const sal_Unicode monthDefaultAbbrvName10[] = {0x4e, 0x69, 0x73, 0x73, 0x61, 0x6e, 0x0};
+static const sal_Unicode monthDefaultFullName10[] = {0x4e, 0x69, 0x73, 0x73, 0x61, 0x6e, 0x0};
+static const sal_Unicode monthDefaultNarrowName10[] = {0x4e, 0x0};
+static const sal_Unicode monthID11[] = {0x49, 0x79, 0x61, 0x72, 0x0};
+static const sal_Unicode monthDefaultAbbrvName11[] = {0x49, 0x79, 0x61, 0x72, 0x0};
+static const sal_Unicode monthDefaultFullName11[] = {0x49, 0x79, 0x61, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName11[] = {0x49, 0x0};
+static const sal_Unicode monthID12[] = {0x53, 0x69, 0x76, 0x61, 0x6e, 0x0};
+static const sal_Unicode monthDefaultAbbrvName12[] = {0x53, 0x69, 0x76, 0x61, 0x6e, 0x0};
+static const sal_Unicode monthDefaultFullName12[] = {0x53, 0x69, 0x76, 0x61, 0x6e, 0x0};
+static const sal_Unicode monthDefaultNarrowName12[] = {0x53, 0x0};
+static const sal_Unicode monthID13[] = {0x54, 0x61, 0x6d, 0x6d, 0x75, 0x7a, 0x0};
+static const sal_Unicode monthDefaultAbbrvName13[] = {0x54, 0x61, 0x6d, 0x6d, 0x75, 0x7a, 0x0};
+static const sal_Unicode monthDefaultFullName13[] = {0x54, 0x61, 0x6d, 0x6d, 0x75, 0x7a, 0x0};
+static const sal_Unicode monthDefaultNarrowName13[] = {0x54, 0x0};
+static const sal_Unicode monthID14[] = {0x41, 0x76, 0x0};
+static const sal_Unicode monthDefaultAbbrvName14[] = {0x41, 0x76, 0x0};
+static const sal_Unicode monthDefaultFullName14[] = {0x41, 0x76, 0x0};
+static const sal_Unicode monthDefaultNarrowName14[] = {0x41, 0x0};
+static const sal_Unicode monthID15[] = {0x45, 0x6c, 0x75, 0x6c, 0x0};
+static const sal_Unicode monthDefaultAbbrvName15[] = {0x45, 0x6c, 0x75, 0x6c, 0x0};
+static const sal_Unicode monthDefaultFullName15[] = {0x45, 0x6c, 0x75, 0x6c, 0x0};
+static const sal_Unicode monthDefaultNarrowName15[] = {0x45, 0x0};
+static const sal_Unicode monthID16[] = {0x54, 0x69, 0x73, 0x68, 0x72, 0x69, 0x0};
+static const sal_Unicode monthDefaultAbbrvName16[] = {0x54, 0x69, 0x73, 0x68, 0x72, 0x69, 0x0};
+static const sal_Unicode monthDefaultFullName16[] = {0x54, 0x69, 0x73, 0x68, 0x72, 0x69, 0x0};
+static const sal_Unicode monthDefaultNarrowName16[] = {0x54, 0x0};
+static const sal_Unicode monthID17[] = {0x48, 0x65, 0x73, 0x68, 0x76, 0x61, 0x6e, 0x0};
+static const sal_Unicode monthDefaultAbbrvName17[] = {0x48, 0x65, 0x73, 0x68, 0x76, 0x61, 0x6e, 0x0};
+static const sal_Unicode monthDefaultFullName17[] = {0x48, 0x65, 0x73, 0x68, 0x76, 0x61, 0x6e, 0x0};
+static const sal_Unicode monthDefaultNarrowName17[] = {0x48, 0x0};
+static const sal_Unicode monthID18[] = {0x4b, 0x69, 0x73, 0x6c, 0x65, 0x76, 0x0};
+static const sal_Unicode monthDefaultAbbrvName18[] = {0x4b, 0x69, 0x73, 0x6c, 0x65, 0x76, 0x0};
+static const sal_Unicode monthDefaultFullName18[] = {0x4b, 0x69, 0x73, 0x6c, 0x65, 0x76, 0x0};
+static const sal_Unicode monthDefaultNarrowName18[] = {0x4b, 0x0};
+static const sal_Unicode monthID19[] = {0x54, 0x65, 0x76, 0x65, 0x74, 0x0};
+static const sal_Unicode monthDefaultAbbrvName19[] = {0x54, 0x65, 0x76, 0x65, 0x74, 0x0};
+static const sal_Unicode monthDefaultFullName19[] = {0x54, 0x65, 0x76, 0x65, 0x74, 0x0};
+static const sal_Unicode monthDefaultNarrowName19[] = {0x54, 0x0};
+static const sal_Unicode monthID110[] = {0x53, 0x68, 0x65, 0x76, 0x61, 0x74, 0x0};
+static const sal_Unicode monthDefaultAbbrvName110[] = {0x53, 0x68, 0x65, 0x76, 0x61, 0x74, 0x0};
+static const sal_Unicode monthDefaultFullName110[] = {0x53, 0x68, 0x65, 0x76, 0x61, 0x74, 0x0};
+static const sal_Unicode monthDefaultNarrowName110[] = {0x53, 0x0};
+static const sal_Unicode monthID111[] = {0x41, 0x64, 0x61, 0x72, 0x0};
+static const sal_Unicode monthDefaultAbbrvName111[] = {0x41, 0x64, 0x61, 0x72, 0x0};
+static const sal_Unicode monthDefaultFullName111[] = {0x41, 0x64, 0x61, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName111[] = {0x41, 0x0};
+static const sal_Unicode monthID112[] = {0x76, 0x65, 0x2d, 0x41, 0x64, 0x61, 0x72, 0x0};
+static const sal_Unicode monthDefaultAbbrvName112[] = {0x41, 0x64, 0x61, 0x72, 0x20, 0x42, 0x0};
+static const sal_Unicode monthDefaultFullName112[] = {0x41, 0x64, 0x61, 0x72, 0x20, 0x42, 0x0};
+static const sal_Unicode monthDefaultNarrowName112[] = {0x41, 0x0};
+static const sal_Unicode genitiveMonthID10[] = {0x4e, 0x69, 0x73, 0x73, 0x61, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName10[] = {0x4e, 0x69, 0x73, 0x73, 0x61, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName10[] = {0x4e, 0x69, 0x73, 0x73, 0x61, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName10[] = {0x4e, 0x0};
+static const sal_Unicode genitiveMonthID11[] = {0x49, 0x79, 0x61, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName11[] = {0x49, 0x79, 0x61, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName11[] = {0x49, 0x79, 0x61, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName11[] = {0x49, 0x0};
+static const sal_Unicode genitiveMonthID12[] = {0x53, 0x69, 0x76, 0x61, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName12[] = {0x53, 0x69, 0x76, 0x61, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName12[] = {0x53, 0x69, 0x76, 0x61, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName12[] = {0x53, 0x0};
+static const sal_Unicode genitiveMonthID13[] = {0x54, 0x61, 0x6d, 0x6d, 0x75, 0x7a, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName13[] = {0x54, 0x61, 0x6d, 0x6d, 0x75, 0x7a, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName13[] = {0x54, 0x61, 0x6d, 0x6d, 0x75, 0x7a, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName13[] = {0x54, 0x0};
+static const sal_Unicode genitiveMonthID14[] = {0x41, 0x76, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName14[] = {0x41, 0x76, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName14[] = {0x41, 0x76, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName14[] = {0x41, 0x0};
+static const sal_Unicode genitiveMonthID15[] = {0x45, 0x6c, 0x75, 0x6c, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName15[] = {0x45, 0x6c, 0x75, 0x6c, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName15[] = {0x45, 0x6c, 0x75, 0x6c, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName15[] = {0x45, 0x0};
+static const sal_Unicode genitiveMonthID16[] = {0x54, 0x69, 0x73, 0x68, 0x72, 0x69, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName16[] = {0x54, 0x69, 0x73, 0x68, 0x72, 0x69, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName16[] = {0x54, 0x69, 0x73, 0x68, 0x72, 0x69, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName16[] = {0x54, 0x0};
+static const sal_Unicode genitiveMonthID17[] = {0x48, 0x65, 0x73, 0x68, 0x76, 0x61, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName17[] = {0x48, 0x65, 0x73, 0x68, 0x76, 0x61, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName17[] = {0x48, 0x65, 0x73, 0x68, 0x76, 0x61, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName17[] = {0x48, 0x0};
+static const sal_Unicode genitiveMonthID18[] = {0x4b, 0x69, 0x73, 0x6c, 0x65, 0x76, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName18[] = {0x4b, 0x69, 0x73, 0x6c, 0x65, 0x76, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName18[] = {0x4b, 0x69, 0x73, 0x6c, 0x65, 0x76, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName18[] = {0x4b, 0x0};
+static const sal_Unicode genitiveMonthID19[] = {0x54, 0x65, 0x76, 0x65, 0x74, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName19[] = {0x54, 0x65, 0x76, 0x65, 0x74, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName19[] = {0x54, 0x65, 0x76, 0x65, 0x74, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName19[] = {0x54, 0x0};
+static const sal_Unicode genitiveMonthID110[] = {0x53, 0x68, 0x65, 0x76, 0x61, 0x74, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName110[] = {0x53, 0x68, 0x65, 0x76, 0x61, 0x74, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName110[] = {0x53, 0x68, 0x65, 0x76, 0x61, 0x74, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName110[] = {0x53, 0x0};
+static const sal_Unicode genitiveMonthID111[] = {0x41, 0x64, 0x61, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName111[] = {0x41, 0x64, 0x61, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName111[] = {0x41, 0x64, 0x61, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName111[] = {0x41, 0x0};
+static const sal_Unicode genitiveMonthID112[] = {0x76, 0x65, 0x2d, 0x41, 0x64, 0x61, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName112[] = {0x41, 0x64, 0x61, 0x72, 0x20, 0x42, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName112[] = {0x41, 0x64, 0x61, 0x72, 0x20, 0x42, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName112[] = {0x41, 0x0};
+static const sal_Unicode partitiveMonthID10[] = {0x4e, 0x69, 0x73, 0x73, 0x61, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName10[] = {0x4e, 0x69, 0x73, 0x73, 0x61, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName10[] = {0x4e, 0x69, 0x73, 0x73, 0x61, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName10[] = {0x4e, 0x0};
+static const sal_Unicode partitiveMonthID11[] = {0x49, 0x79, 0x61, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName11[] = {0x49, 0x79, 0x61, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName11[] = {0x49, 0x79, 0x61, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName11[] = {0x49, 0x0};
+static const sal_Unicode partitiveMonthID12[] = {0x53, 0x69, 0x76, 0x61, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName12[] = {0x53, 0x69, 0x76, 0x61, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName12[] = {0x53, 0x69, 0x76, 0x61, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName12[] = {0x53, 0x0};
+static const sal_Unicode partitiveMonthID13[] = {0x54, 0x61, 0x6d, 0x6d, 0x75, 0x7a, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName13[] = {0x54, 0x61, 0x6d, 0x6d, 0x75, 0x7a, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName13[] = {0x54, 0x61, 0x6d, 0x6d, 0x75, 0x7a, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName13[] = {0x54, 0x0};
+static const sal_Unicode partitiveMonthID14[] = {0x41, 0x76, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName14[] = {0x41, 0x76, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName14[] = {0x41, 0x76, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName14[] = {0x41, 0x0};
+static const sal_Unicode partitiveMonthID15[] = {0x45, 0x6c, 0x75, 0x6c, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName15[] = {0x45, 0x6c, 0x75, 0x6c, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName15[] = {0x45, 0x6c, 0x75, 0x6c, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName15[] = {0x45, 0x0};
+static const sal_Unicode partitiveMonthID16[] = {0x54, 0x69, 0x73, 0x68, 0x72, 0x69, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName16[] = {0x54, 0x69, 0x73, 0x68, 0x72, 0x69, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName16[] = {0x54, 0x69, 0x73, 0x68, 0x72, 0x69, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName16[] = {0x54, 0x0};
+static const sal_Unicode partitiveMonthID17[] = {0x48, 0x65, 0x73, 0x68, 0x76, 0x61, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName17[] = {0x48, 0x65, 0x73, 0x68, 0x76, 0x61, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName17[] = {0x48, 0x65, 0x73, 0x68, 0x76, 0x61, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName17[] = {0x48, 0x0};
+static const sal_Unicode partitiveMonthID18[] = {0x4b, 0x69, 0x73, 0x6c, 0x65, 0x76, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName18[] = {0x4b, 0x69, 0x73, 0x6c, 0x65, 0x76, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName18[] = {0x4b, 0x69, 0x73, 0x6c, 0x65, 0x76, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName18[] = {0x4b, 0x0};
+static const sal_Unicode partitiveMonthID19[] = {0x54, 0x65, 0x76, 0x65, 0x74, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName19[] = {0x54, 0x65, 0x76, 0x65, 0x74, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName19[] = {0x54, 0x65, 0x76, 0x65, 0x74, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName19[] = {0x54, 0x0};
+static const sal_Unicode partitiveMonthID110[] = {0x53, 0x68, 0x65, 0x76, 0x61, 0x74, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName110[] = {0x53, 0x68, 0x65, 0x76, 0x61, 0x74, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName110[] = {0x53, 0x68, 0x65, 0x76, 0x61, 0x74, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName110[] = {0x53, 0x0};
+static const sal_Unicode partitiveMonthID111[] = {0x41, 0x64, 0x61, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName111[] = {0x41, 0x64, 0x61, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName111[] = {0x41, 0x64, 0x61, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName111[] = {0x41, 0x0};
+static const sal_Unicode partitiveMonthID112[] = {0x76, 0x65, 0x2d, 0x41, 0x64, 0x61, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName112[] = {0x41, 0x64, 0x61, 0x72, 0x20, 0x42, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName112[] = {0x41, 0x64, 0x61, 0x72, 0x20, 0x42, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName112[] = {0x41, 0x0};
+static const sal_Unicode eraID10[] = {0x62, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x0};
+
+static const sal_Unicode eraDefaultAbbrvName10[] = {0x42, 0x43, 0x45, 0x0};
+static const sal_Unicode eraDefaultFullName10[] = {0x42, 0x43, 0x45, 0x0};
+static const sal_Unicode eraID11[] = {0x61, 0x66, 0x74, 0x65, 0x72, 0x0};
+
+static const sal_Unicode eraDefaultAbbrvName11[] = {0x43, 0x45, 0x0};
+static const sal_Unicode eraDefaultFullName11[] = {0x43, 0x45, 0x0};
+static const sal_Unicode startDayOfWeek1[] = {0x73, 0x75, 0x6e, 0x0};
+static const sal_Unicode minimalDaysInFirstWeek1[] = {1};
+static const sal_Int16 calendarsCount = 2;
+
+static const sal_Unicode nbOfDays[] = {7, 7};
+static const sal_Unicode nbOfMonths[] = {12, 13};
+static const sal_Unicode nbOfGenitiveMonths[] = {12, 13};
+static const sal_Unicode nbOfPartitiveMonths[] = {12, 13};
+static const sal_Unicode nbOfEras[] = {2, 2};
+static const sal_Unicode* calendars[] = {
+ nbOfDays,
+ nbOfMonths,
+ nbOfGenitiveMonths,
+ nbOfPartitiveMonths,
+ nbOfEras,
+ calendarID0,
+ defaultCalendar0,
+ dayID00,
+ dayDefaultAbbrvName00,
+ dayDefaultFullName00,
+ dayDefaultNarrowName00,
+ dayID01,
+ dayDefaultAbbrvName01,
+ dayDefaultFullName01,
+ dayDefaultNarrowName01,
+ dayID02,
+ dayDefaultAbbrvName02,
+ dayDefaultFullName02,
+ dayDefaultNarrowName02,
+ dayID03,
+ dayDefaultAbbrvName03,
+ dayDefaultFullName03,
+ dayDefaultNarrowName03,
+ dayID04,
+ dayDefaultAbbrvName04,
+ dayDefaultFullName04,
+ dayDefaultNarrowName04,
+ dayID05,
+ dayDefaultAbbrvName05,
+ dayDefaultFullName05,
+ dayDefaultNarrowName05,
+ dayID06,
+ dayDefaultAbbrvName06,
+ dayDefaultFullName06,
+ dayDefaultNarrowName06,
+ monthID00,
+ monthDefaultAbbrvName00,
+ monthDefaultFullName00,
+ monthDefaultNarrowName00,
+ monthID01,
+ monthDefaultAbbrvName01,
+ monthDefaultFullName01,
+ monthDefaultNarrowName01,
+ monthID02,
+ monthDefaultAbbrvName02,
+ monthDefaultFullName02,
+ monthDefaultNarrowName02,
+ monthID03,
+ monthDefaultAbbrvName03,
+ monthDefaultFullName03,
+ monthDefaultNarrowName03,
+ monthID04,
+ monthDefaultAbbrvName04,
+ monthDefaultFullName04,
+ monthDefaultNarrowName04,
+ monthID05,
+ monthDefaultAbbrvName05,
+ monthDefaultFullName05,
+ monthDefaultNarrowName05,
+ monthID06,
+ monthDefaultAbbrvName06,
+ monthDefaultFullName06,
+ monthDefaultNarrowName06,
+ monthID07,
+ monthDefaultAbbrvName07,
+ monthDefaultFullName07,
+ monthDefaultNarrowName07,
+ monthID08,
+ monthDefaultAbbrvName08,
+ monthDefaultFullName08,
+ monthDefaultNarrowName08,
+ monthID09,
+ monthDefaultAbbrvName09,
+ monthDefaultFullName09,
+ monthDefaultNarrowName09,
+ monthID010,
+ monthDefaultAbbrvName010,
+ monthDefaultFullName010,
+ monthDefaultNarrowName010,
+ monthID011,
+ monthDefaultAbbrvName011,
+ monthDefaultFullName011,
+ monthDefaultNarrowName011,
+ genitiveMonthID00,
+ genitiveMonthDefaultAbbrvName00,
+ genitiveMonthDefaultFullName00,
+ genitiveMonthDefaultNarrowName00,
+ genitiveMonthID01,
+ genitiveMonthDefaultAbbrvName01,
+ genitiveMonthDefaultFullName01,
+ genitiveMonthDefaultNarrowName01,
+ genitiveMonthID02,
+ genitiveMonthDefaultAbbrvName02,
+ genitiveMonthDefaultFullName02,
+ genitiveMonthDefaultNarrowName02,
+ genitiveMonthID03,
+ genitiveMonthDefaultAbbrvName03,
+ genitiveMonthDefaultFullName03,
+ genitiveMonthDefaultNarrowName03,
+ genitiveMonthID04,
+ genitiveMonthDefaultAbbrvName04,
+ genitiveMonthDefaultFullName04,
+ genitiveMonthDefaultNarrowName04,
+ genitiveMonthID05,
+ genitiveMonthDefaultAbbrvName05,
+ genitiveMonthDefaultFullName05,
+ genitiveMonthDefaultNarrowName05,
+ genitiveMonthID06,
+ genitiveMonthDefaultAbbrvName06,
+ genitiveMonthDefaultFullName06,
+ genitiveMonthDefaultNarrowName06,
+ genitiveMonthID07,
+ genitiveMonthDefaultAbbrvName07,
+ genitiveMonthDefaultFullName07,
+ genitiveMonthDefaultNarrowName07,
+ genitiveMonthID08,
+ genitiveMonthDefaultAbbrvName08,
+ genitiveMonthDefaultFullName08,
+ genitiveMonthDefaultNarrowName08,
+ genitiveMonthID09,
+ genitiveMonthDefaultAbbrvName09,
+ genitiveMonthDefaultFullName09,
+ genitiveMonthDefaultNarrowName09,
+ genitiveMonthID010,
+ genitiveMonthDefaultAbbrvName010,
+ genitiveMonthDefaultFullName010,
+ genitiveMonthDefaultNarrowName010,
+ genitiveMonthID011,
+ genitiveMonthDefaultAbbrvName011,
+ genitiveMonthDefaultFullName011,
+ genitiveMonthDefaultNarrowName011,
+ partitiveMonthID00,
+ partitiveMonthDefaultAbbrvName00,
+ partitiveMonthDefaultFullName00,
+ partitiveMonthDefaultNarrowName00,
+ partitiveMonthID01,
+ partitiveMonthDefaultAbbrvName01,
+ partitiveMonthDefaultFullName01,
+ partitiveMonthDefaultNarrowName01,
+ partitiveMonthID02,
+ partitiveMonthDefaultAbbrvName02,
+ partitiveMonthDefaultFullName02,
+ partitiveMonthDefaultNarrowName02,
+ partitiveMonthID03,
+ partitiveMonthDefaultAbbrvName03,
+ partitiveMonthDefaultFullName03,
+ partitiveMonthDefaultNarrowName03,
+ partitiveMonthID04,
+ partitiveMonthDefaultAbbrvName04,
+ partitiveMonthDefaultFullName04,
+ partitiveMonthDefaultNarrowName04,
+ partitiveMonthID05,
+ partitiveMonthDefaultAbbrvName05,
+ partitiveMonthDefaultFullName05,
+ partitiveMonthDefaultNarrowName05,
+ partitiveMonthID06,
+ partitiveMonthDefaultAbbrvName06,
+ partitiveMonthDefaultFullName06,
+ partitiveMonthDefaultNarrowName06,
+ partitiveMonthID07,
+ partitiveMonthDefaultAbbrvName07,
+ partitiveMonthDefaultFullName07,
+ partitiveMonthDefaultNarrowName07,
+ partitiveMonthID08,
+ partitiveMonthDefaultAbbrvName08,
+ partitiveMonthDefaultFullName08,
+ partitiveMonthDefaultNarrowName08,
+ partitiveMonthID09,
+ partitiveMonthDefaultAbbrvName09,
+ partitiveMonthDefaultFullName09,
+ partitiveMonthDefaultNarrowName09,
+ partitiveMonthID010,
+ partitiveMonthDefaultAbbrvName010,
+ partitiveMonthDefaultFullName010,
+ partitiveMonthDefaultNarrowName010,
+ partitiveMonthID011,
+ partitiveMonthDefaultAbbrvName011,
+ partitiveMonthDefaultFullName011,
+ partitiveMonthDefaultNarrowName011,
+ eraID00,
+ eraDefaultAbbrvName00,
+ eraDefaultFullName00,
+ eraID01,
+ eraDefaultAbbrvName01,
+ eraDefaultFullName01,
+ startDayOfWeek0,
+ minimalDaysInFirstWeek0,
+ calendarID1,
+ defaultCalendar1,
+ dayID10,
+ dayDefaultAbbrvName10,
+ dayDefaultFullName10,
+ dayDefaultNarrowName10,
+ dayID11,
+ dayDefaultAbbrvName11,
+ dayDefaultFullName11,
+ dayDefaultNarrowName11,
+ dayID12,
+ dayDefaultAbbrvName12,
+ dayDefaultFullName12,
+ dayDefaultNarrowName12,
+ dayID13,
+ dayDefaultAbbrvName13,
+ dayDefaultFullName13,
+ dayDefaultNarrowName13,
+ dayID14,
+ dayDefaultAbbrvName14,
+ dayDefaultFullName14,
+ dayDefaultNarrowName14,
+ dayID15,
+ dayDefaultAbbrvName15,
+ dayDefaultFullName15,
+ dayDefaultNarrowName15,
+ dayID16,
+ dayDefaultAbbrvName16,
+ dayDefaultFullName16,
+ dayDefaultNarrowName16,
+ monthID10,
+ monthDefaultAbbrvName10,
+ monthDefaultFullName10,
+ monthDefaultNarrowName10,
+ monthID11,
+ monthDefaultAbbrvName11,
+ monthDefaultFullName11,
+ monthDefaultNarrowName11,
+ monthID12,
+ monthDefaultAbbrvName12,
+ monthDefaultFullName12,
+ monthDefaultNarrowName12,
+ monthID13,
+ monthDefaultAbbrvName13,
+ monthDefaultFullName13,
+ monthDefaultNarrowName13,
+ monthID14,
+ monthDefaultAbbrvName14,
+ monthDefaultFullName14,
+ monthDefaultNarrowName14,
+ monthID15,
+ monthDefaultAbbrvName15,
+ monthDefaultFullName15,
+ monthDefaultNarrowName15,
+ monthID16,
+ monthDefaultAbbrvName16,
+ monthDefaultFullName16,
+ monthDefaultNarrowName16,
+ monthID17,
+ monthDefaultAbbrvName17,
+ monthDefaultFullName17,
+ monthDefaultNarrowName17,
+ monthID18,
+ monthDefaultAbbrvName18,
+ monthDefaultFullName18,
+ monthDefaultNarrowName18,
+ monthID19,
+ monthDefaultAbbrvName19,
+ monthDefaultFullName19,
+ monthDefaultNarrowName19,
+ monthID110,
+ monthDefaultAbbrvName110,
+ monthDefaultFullName110,
+ monthDefaultNarrowName110,
+ monthID111,
+ monthDefaultAbbrvName111,
+ monthDefaultFullName111,
+ monthDefaultNarrowName111,
+ monthID112,
+ monthDefaultAbbrvName112,
+ monthDefaultFullName112,
+ monthDefaultNarrowName112,
+ genitiveMonthID10,
+ genitiveMonthDefaultAbbrvName10,
+ genitiveMonthDefaultFullName10,
+ genitiveMonthDefaultNarrowName10,
+ genitiveMonthID11,
+ genitiveMonthDefaultAbbrvName11,
+ genitiveMonthDefaultFullName11,
+ genitiveMonthDefaultNarrowName11,
+ genitiveMonthID12,
+ genitiveMonthDefaultAbbrvName12,
+ genitiveMonthDefaultFullName12,
+ genitiveMonthDefaultNarrowName12,
+ genitiveMonthID13,
+ genitiveMonthDefaultAbbrvName13,
+ genitiveMonthDefaultFullName13,
+ genitiveMonthDefaultNarrowName13,
+ genitiveMonthID14,
+ genitiveMonthDefaultAbbrvName14,
+ genitiveMonthDefaultFullName14,
+ genitiveMonthDefaultNarrowName14,
+ genitiveMonthID15,
+ genitiveMonthDefaultAbbrvName15,
+ genitiveMonthDefaultFullName15,
+ genitiveMonthDefaultNarrowName15,
+ genitiveMonthID16,
+ genitiveMonthDefaultAbbrvName16,
+ genitiveMonthDefaultFullName16,
+ genitiveMonthDefaultNarrowName16,
+ genitiveMonthID17,
+ genitiveMonthDefaultAbbrvName17,
+ genitiveMonthDefaultFullName17,
+ genitiveMonthDefaultNarrowName17,
+ genitiveMonthID18,
+ genitiveMonthDefaultAbbrvName18,
+ genitiveMonthDefaultFullName18,
+ genitiveMonthDefaultNarrowName18,
+ genitiveMonthID19,
+ genitiveMonthDefaultAbbrvName19,
+ genitiveMonthDefaultFullName19,
+ genitiveMonthDefaultNarrowName19,
+ genitiveMonthID110,
+ genitiveMonthDefaultAbbrvName110,
+ genitiveMonthDefaultFullName110,
+ genitiveMonthDefaultNarrowName110,
+ genitiveMonthID111,
+ genitiveMonthDefaultAbbrvName111,
+ genitiveMonthDefaultFullName111,
+ genitiveMonthDefaultNarrowName111,
+ genitiveMonthID112,
+ genitiveMonthDefaultAbbrvName112,
+ genitiveMonthDefaultFullName112,
+ genitiveMonthDefaultNarrowName112,
+ partitiveMonthID10,
+ partitiveMonthDefaultAbbrvName10,
+ partitiveMonthDefaultFullName10,
+ partitiveMonthDefaultNarrowName10,
+ partitiveMonthID11,
+ partitiveMonthDefaultAbbrvName11,
+ partitiveMonthDefaultFullName11,
+ partitiveMonthDefaultNarrowName11,
+ partitiveMonthID12,
+ partitiveMonthDefaultAbbrvName12,
+ partitiveMonthDefaultFullName12,
+ partitiveMonthDefaultNarrowName12,
+ partitiveMonthID13,
+ partitiveMonthDefaultAbbrvName13,
+ partitiveMonthDefaultFullName13,
+ partitiveMonthDefaultNarrowName13,
+ partitiveMonthID14,
+ partitiveMonthDefaultAbbrvName14,
+ partitiveMonthDefaultFullName14,
+ partitiveMonthDefaultNarrowName14,
+ partitiveMonthID15,
+ partitiveMonthDefaultAbbrvName15,
+ partitiveMonthDefaultFullName15,
+ partitiveMonthDefaultNarrowName15,
+ partitiveMonthID16,
+ partitiveMonthDefaultAbbrvName16,
+ partitiveMonthDefaultFullName16,
+ partitiveMonthDefaultNarrowName16,
+ partitiveMonthID17,
+ partitiveMonthDefaultAbbrvName17,
+ partitiveMonthDefaultFullName17,
+ partitiveMonthDefaultNarrowName17,
+ partitiveMonthID18,
+ partitiveMonthDefaultAbbrvName18,
+ partitiveMonthDefaultFullName18,
+ partitiveMonthDefaultNarrowName18,
+ partitiveMonthID19,
+ partitiveMonthDefaultAbbrvName19,
+ partitiveMonthDefaultFullName19,
+ partitiveMonthDefaultNarrowName19,
+ partitiveMonthID110,
+ partitiveMonthDefaultAbbrvName110,
+ partitiveMonthDefaultFullName110,
+ partitiveMonthDefaultNarrowName110,
+ partitiveMonthID111,
+ partitiveMonthDefaultAbbrvName111,
+ partitiveMonthDefaultFullName111,
+ partitiveMonthDefaultNarrowName111,
+ partitiveMonthID112,
+ partitiveMonthDefaultAbbrvName112,
+ partitiveMonthDefaultFullName112,
+ partitiveMonthDefaultNarrowName112,
+ eraID10,
+ eraDefaultAbbrvName10,
+ eraDefaultFullName10,
+ eraID11,
+ eraDefaultAbbrvName11,
+ eraDefaultFullName11,
+ startDayOfWeek1,
+ minimalDaysInFirstWeek1,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCalendars_en_US(sal_Int16& count)
+{
+ count = calendarsCount;
+ return (sal_Unicode**)calendars;
+}
+static const sal_Unicode defaultCurrency0[] = {1};
+static const sal_Unicode defaultCurrencyUsedInCompatibleFormatCodes0[] = {1};
+static const sal_Unicode defaultCurrencyLegacyOnly0[] = {0};
+static const sal_Unicode currencyID0[] = {0x55, 0x53, 0x44, 0x0};
+static const sal_Unicode currencySymbol0[] = {0x24, 0x0};
+static const sal_Unicode bankSymbol0[] = {0x55, 0x53, 0x44, 0x0};
+static const sal_Unicode currencyName0[] = {0x55, 0x53, 0x20, 0x44, 0x6f, 0x6c, 0x6c, 0x61, 0x72, 0x0};
+static const sal_Unicode currencyDecimalPlaces0[] = {2};
+
+static const sal_Int16 currencyCount = 1;
+
+static const sal_Unicode* currencies[] = {
+ currencyID0,
+ currencySymbol0,
+ bankSymbol0,
+ currencyName0,
+ defaultCurrency0,
+ defaultCurrencyUsedInCompatibleFormatCodes0,
+ currencyDecimalPlaces0,
+ defaultCurrencyLegacyOnly0,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCurrencies_en_US(sal_Int16& count)
+{
+ count = currencyCount;
+ return (sal_Unicode**)currencies;
+}
+static const sal_Unicode Transliteration0[] = {0x53, 0x45, 0x4e, 0x54, 0x45, 0x4e, 0x43, 0x45, 0x5f, 0x43, 0x41, 0x53, 0x45, 0x0};
+static const sal_Unicode Transliteration1[] = {0x4c, 0x4f, 0x57, 0x45, 0x52, 0x43, 0x41, 0x53, 0x45, 0x5f, 0x55, 0x50, 0x50, 0x45, 0x52, 0x43, 0x41, 0x53, 0x45, 0x0};
+static const sal_Unicode Transliteration2[] = {0x55, 0x50, 0x50, 0x45, 0x52, 0x43, 0x41, 0x53, 0x45, 0x5f, 0x4c, 0x4f, 0x57, 0x45, 0x52, 0x43, 0x41, 0x53, 0x45, 0x0};
+static const sal_Unicode Transliteration3[] = {0x54, 0x49, 0x54, 0x4c, 0x45, 0x5f, 0x43, 0x41, 0x53, 0x45, 0x0};
+static const sal_Unicode Transliteration4[] = {0x54, 0x4f, 0x47, 0x47, 0x4c, 0x45, 0x5f, 0x43, 0x41, 0x53, 0x45, 0x0};
+static const sal_Unicode Transliteration5[] = {0x49, 0x47, 0x4e, 0x4f, 0x52, 0x45, 0x5f, 0x43, 0x41, 0x53, 0x45, 0x0};
+static const sal_Int16 nbOfTransliterations = 6;
+
+
+static const sal_Unicode* LCTransliterationsArray[] = {
+ Transliteration0,
+ Transliteration1,
+ Transliteration2,
+ Transliteration3,
+ Transliteration4,
+ Transliteration5,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getTransliterations_en_US(sal_Int16& count)
+{
+ count = nbOfTransliterations;
+ return (sal_Unicode**)LCTransliterationsArray;
+}
+static const sal_Unicode ReservedWord0[] = {0x74, 0x72, 0x75, 0x65, 0x0};
+static const sal_Unicode ReservedWord1[] = {0x66, 0x61, 0x6c, 0x73, 0x65, 0x0};
+static const sal_Unicode ReservedWord2[] = {0x31, 0x73, 0x74, 0x20, 0x71, 0x75, 0x61, 0x72, 0x74, 0x65, 0x72, 0x0};
+static const sal_Unicode ReservedWord3[] = {0x32, 0x6e, 0x64, 0x20, 0x71, 0x75, 0x61, 0x72, 0x74, 0x65, 0x72, 0x0};
+static const sal_Unicode ReservedWord4[] = {0x33, 0x72, 0x64, 0x20, 0x71, 0x75, 0x61, 0x72, 0x74, 0x65, 0x72, 0x0};
+static const sal_Unicode ReservedWord5[] = {0x34, 0x74, 0x68, 0x20, 0x71, 0x75, 0x61, 0x72, 0x74, 0x65, 0x72, 0x0};
+static const sal_Unicode ReservedWord6[] = {0x61, 0x62, 0x6f, 0x76, 0x65, 0x0};
+static const sal_Unicode ReservedWord7[] = {0x62, 0x65, 0x6c, 0x6f, 0x77, 0x0};
+static const sal_Unicode ReservedWord8[] = {0x51, 0x31, 0x0};
+static const sal_Unicode ReservedWord9[] = {0x51, 0x32, 0x0};
+static const sal_Unicode ReservedWord10[] = {0x51, 0x33, 0x0};
+static const sal_Unicode ReservedWord11[] = {0x51, 0x34, 0x0};
+static const sal_Int16 nbOfReservedWords = 12;
+
+
+static const sal_Unicode* LCReservedWordsArray[] = {
+ ReservedWord0,
+ ReservedWord1,
+ ReservedWord2,
+ ReservedWord3,
+ ReservedWord4,
+ ReservedWord5,
+ ReservedWord6,
+ ReservedWord7,
+ ReservedWord8,
+ ReservedWord9,
+ ReservedWord10,
+ ReservedWord11,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getReservedWords_en_US(sal_Int16& count)
+{
+ count = nbOfReservedWords;
+ return (sal_Unicode**)LCReservedWordsArray;
+}
+static const sal_Unicode forbiddenBegin[] = {0x0};
+static const sal_Unicode forbiddenEnd[] = {0x0};
+static const sal_Unicode hangingChars[] = {0x0};
+
+static const sal_Unicode* LCForbiddenCharactersArray[] = {
+ forbiddenBegin,
+ forbiddenEnd,
+ hangingChars
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getForbiddenCharacters_en_US(sal_Int16& count)
+{
+ count = 3;
+ return (sal_Unicode**)LCForbiddenCharactersArray;
+}
+static const sal_Unicode EditMode[] = {0x0};
+static const sal_Unicode DictionaryMode[] = {0x0};
+static const sal_Unicode WordCountMode[] = {0x0};
+static const sal_Unicode CharacterMode[] = {0x0};
+static const sal_Unicode LineMode[] = {0x0};
+
+static const sal_Unicode* LCBreakIteratorRulesArray[] = {
+ EditMode,
+ DictionaryMode,
+ WordCountMode,
+ CharacterMode,
+ LineMode
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getBreakIteratorRules_en_US(sal_Int16& count)
+{
+ count = 5;
+ return (sal_Unicode**)LCBreakIteratorRulesArray;
+}
+// ---> ContinuousNumbering
+static const sal_Unicode continuousPrefix0[] = {0x20, 0x0};
+static const sal_Unicode continuousNumType0[] = {0x34, 0x0};
+static const sal_Unicode continuousSuffix0[] = {0x29, 0x0};
+static const sal_Unicode continuousTransliteration0[] = {0x0};
+static const sal_Unicode continuousNatNum0[] = {0x0};
+static const sal_Unicode continuousPrefix1[] = {0x20, 0x0};
+static const sal_Unicode continuousNumType1[] = {0x34, 0x0};
+static const sal_Unicode continuousSuffix1[] = {0x2e, 0x0};
+static const sal_Unicode continuousTransliteration1[] = {0x0};
+static const sal_Unicode continuousNatNum1[] = {0x0};
+static const sal_Unicode continuousPrefix2[] = {0x28, 0x0};
+static const sal_Unicode continuousNumType2[] = {0x34, 0x0};
+static const sal_Unicode continuousSuffix2[] = {0x29, 0x0};
+static const sal_Unicode continuousTransliteration2[] = {0x0};
+static const sal_Unicode continuousNatNum2[] = {0x0};
+static const sal_Unicode continuousPrefix3[] = {0x20, 0x0};
+static const sal_Unicode continuousNumType3[] = {0x32, 0x0};
+static const sal_Unicode continuousSuffix3[] = {0x2e, 0x0};
+static const sal_Unicode continuousTransliteration3[] = {0x0};
+static const sal_Unicode continuousNatNum3[] = {0x0};
+static const sal_Unicode continuousPrefix4[] = {0x20, 0x0};
+static const sal_Unicode continuousNumType4[] = {0x30, 0x0};
+static const sal_Unicode continuousSuffix4[] = {0x29, 0x0};
+static const sal_Unicode continuousTransliteration4[] = {0x0};
+static const sal_Unicode continuousNatNum4[] = {0x0};
+static const sal_Unicode continuousPrefix5[] = {0x20, 0x0};
+static const sal_Unicode continuousNumType5[] = {0x31, 0x0};
+static const sal_Unicode continuousSuffix5[] = {0x29, 0x0};
+static const sal_Unicode continuousTransliteration5[] = {0x0};
+static const sal_Unicode continuousNatNum5[] = {0x0};
+static const sal_Unicode continuousPrefix6[] = {0x28, 0x0};
+static const sal_Unicode continuousNumType6[] = {0x31, 0x0};
+static const sal_Unicode continuousSuffix6[] = {0x29, 0x0};
+static const sal_Unicode continuousTransliteration6[] = {0x0};
+static const sal_Unicode continuousNatNum6[] = {0x0};
+static const sal_Unicode continuousPrefix7[] = {0x20, 0x0};
+static const sal_Unicode continuousNumType7[] = {0x33, 0x0};
+static const sal_Unicode continuousSuffix7[] = {0x2e, 0x0};
+static const sal_Unicode continuousTransliteration7[] = {0x0};
+static const sal_Unicode continuousNatNum7[] = {0x0};
+static const sal_Int16 continuousNbOfStyles = 8;
+
+static const sal_Int16 continuousNbOfAttributesPerStyle = 5;
+
+
+static const sal_Unicode* continuousStyle0[] = {
+ continuousPrefix0,
+ continuousNumType0,
+ continuousSuffix0,
+ continuousTransliteration0,
+ continuousNatNum0,
+ 0
+};
+
+
+static const sal_Unicode* continuousStyle1[] = {
+ continuousPrefix1,
+ continuousNumType1,
+ continuousSuffix1,
+ continuousTransliteration1,
+ continuousNatNum1,
+ 0
+};
+
+
+static const sal_Unicode* continuousStyle2[] = {
+ continuousPrefix2,
+ continuousNumType2,
+ continuousSuffix2,
+ continuousTransliteration2,
+ continuousNatNum2,
+ 0
+};
+
+
+static const sal_Unicode* continuousStyle3[] = {
+ continuousPrefix3,
+ continuousNumType3,
+ continuousSuffix3,
+ continuousTransliteration3,
+ continuousNatNum3,
+ 0
+};
+
+
+static const sal_Unicode* continuousStyle4[] = {
+ continuousPrefix4,
+ continuousNumType4,
+ continuousSuffix4,
+ continuousTransliteration4,
+ continuousNatNum4,
+ 0
+};
+
+
+static const sal_Unicode* continuousStyle5[] = {
+ continuousPrefix5,
+ continuousNumType5,
+ continuousSuffix5,
+ continuousTransliteration5,
+ continuousNatNum5,
+ 0
+};
+
+
+static const sal_Unicode* continuousStyle6[] = {
+ continuousPrefix6,
+ continuousNumType6,
+ continuousSuffix6,
+ continuousTransliteration6,
+ continuousNatNum6,
+ 0
+};
+
+
+static const sal_Unicode* continuousStyle7[] = {
+ continuousPrefix7,
+ continuousNumType7,
+ continuousSuffix7,
+ continuousTransliteration7,
+ continuousNatNum7,
+ 0
+};
+
+
+static const sal_Unicode** LCContinuousNumberingLevelsArray[] = {
+ continuousStyle0,
+ continuousStyle1,
+ continuousStyle2,
+ continuousStyle3,
+ continuousStyle4,
+ continuousStyle5,
+ continuousStyle6,
+ continuousStyle7,
+ 0
+};
+
+SAL_DLLPUBLIC_EXPORT const sal_Unicode *** SAL_CALL getContinuousNumberingLevels_en_US( sal_Int16& nStyles, sal_Int16& nAttributes )
+{
+ nStyles = continuousNbOfStyles;
+ nAttributes = continuousNbOfAttributesPerStyle;
+ return LCContinuousNumberingLevelsArray;
+}
+// ---> OutlineNumbering
+static const sal_Unicode outlinePrefix00[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType00[] = {0x34, 0x0};
+static const sal_Unicode outlineSuffix00[] = {0x2e, 0x0};
+static const sal_Unicode outlineBulletChar00[] = {0x30, 0x30, 0x32, 0x30, 0x0};
+static const sal_Unicode outlineBulletFontName00[] = {0x0};
+static const sal_Unicode outlineParentNumbering00[] = {0x30, 0x0};
+static const sal_Unicode outlineLeftMargin00[] = {0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance00[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset00[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration00[] = {0x0};
+static const sal_Unicode outlineNatNum00[] = {0x0};
+static const sal_Unicode outlinePrefix01[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType01[] = {0x34, 0x0};
+static const sal_Unicode outlineSuffix01[] = {0x2e, 0x0};
+static const sal_Unicode outlineBulletChar01[] = {0x30, 0x30, 0x32, 0x30, 0x0};
+static const sal_Unicode outlineBulletFontName01[] = {0x0};
+static const sal_Unicode outlineParentNumbering01[] = {0x31, 0x0};
+static const sal_Unicode outlineLeftMargin01[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance01[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset01[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration01[] = {0x0};
+static const sal_Unicode outlineNatNum01[] = {0x0};
+static const sal_Unicode outlinePrefix02[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType02[] = {0x31, 0x0};
+static const sal_Unicode outlineSuffix02[] = {0x29, 0x0};
+static const sal_Unicode outlineBulletChar02[] = {0x30, 0x30, 0x32, 0x30, 0x0};
+static const sal_Unicode outlineBulletFontName02[] = {0x0};
+static const sal_Unicode outlineParentNumbering02[] = {0x30, 0x0};
+static const sal_Unicode outlineLeftMargin02[] = {0x31, 0x30, 0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance02[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset02[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration02[] = {0x0};
+static const sal_Unicode outlineNatNum02[] = {0x0};
+static const sal_Unicode outlinePrefix03[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType03[] = {0x36, 0x0};
+static const sal_Unicode outlineSuffix03[] = {0x20, 0x0};
+static const sal_Unicode outlineBulletChar03[] = {0x32, 0x30, 0x32, 0x32, 0x0};
+static const sal_Unicode outlineBulletFontName03[] = {0x53, 0x74, 0x61, 0x72, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x0};
+static const sal_Unicode outlineParentNumbering03[] = {0x30, 0x0};
+static const sal_Unicode outlineLeftMargin03[] = {0x31, 0x35, 0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance03[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset03[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration03[] = {0x0};
+static const sal_Unicode outlineNatNum03[] = {0x0};
+static const sal_Unicode outlinePrefix04[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType04[] = {0x36, 0x0};
+static const sal_Unicode outlineSuffix04[] = {0x20, 0x0};
+static const sal_Unicode outlineBulletChar04[] = {0x32, 0x30, 0x32, 0x32, 0x0};
+static const sal_Unicode outlineBulletFontName04[] = {0x53, 0x74, 0x61, 0x72, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x0};
+static const sal_Unicode outlineParentNumbering04[] = {0x30, 0x0};
+static const sal_Unicode outlineLeftMargin04[] = {0x32, 0x30, 0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance04[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset04[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration04[] = {0x0};
+static const sal_Unicode outlineNatNum04[] = {0x0};
+static const sal_Unicode outlinePrefix10[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType10[] = {0x34, 0x0};
+static const sal_Unicode outlineSuffix10[] = {0x2e, 0x0};
+static const sal_Unicode outlineBulletChar10[] = {0x30, 0x30, 0x32, 0x30, 0x0};
+static const sal_Unicode outlineBulletFontName10[] = {0x0};
+static const sal_Unicode outlineParentNumbering10[] = {0x30, 0x0};
+static const sal_Unicode outlineLeftMargin10[] = {0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance10[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset10[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration10[] = {0x0};
+static const sal_Unicode outlineNatNum10[] = {0x0};
+static const sal_Unicode outlinePrefix11[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType11[] = {0x31, 0x0};
+static const sal_Unicode outlineSuffix11[] = {0x29, 0x0};
+static const sal_Unicode outlineBulletChar11[] = {0x30, 0x30, 0x32, 0x30, 0x0};
+static const sal_Unicode outlineBulletFontName11[] = {0x0};
+static const sal_Unicode outlineParentNumbering11[] = {0x30, 0x0};
+static const sal_Unicode outlineLeftMargin11[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance11[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset11[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration11[] = {0x0};
+static const sal_Unicode outlineNatNum11[] = {0x0};
+static const sal_Unicode outlinePrefix12[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType12[] = {0x36, 0x0};
+static const sal_Unicode outlineSuffix12[] = {0x20, 0x0};
+static const sal_Unicode outlineBulletChar12[] = {0x32, 0x30, 0x32, 0x32, 0x0};
+static const sal_Unicode outlineBulletFontName12[] = {0x53, 0x74, 0x61, 0x72, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x0};
+static const sal_Unicode outlineParentNumbering12[] = {0x30, 0x0};
+static const sal_Unicode outlineLeftMargin12[] = {0x31, 0x30, 0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance12[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset12[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration12[] = {0x0};
+static const sal_Unicode outlineNatNum12[] = {0x0};
+static const sal_Unicode outlinePrefix13[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType13[] = {0x36, 0x0};
+static const sal_Unicode outlineSuffix13[] = {0x20, 0x0};
+static const sal_Unicode outlineBulletChar13[] = {0x32, 0x30, 0x32, 0x32, 0x0};
+static const sal_Unicode outlineBulletFontName13[] = {0x53, 0x74, 0x61, 0x72, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x0};
+static const sal_Unicode outlineParentNumbering13[] = {0x30, 0x0};
+static const sal_Unicode outlineLeftMargin13[] = {0x31, 0x35, 0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance13[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset13[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration13[] = {0x0};
+static const sal_Unicode outlineNatNum13[] = {0x0};
+static const sal_Unicode outlinePrefix14[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType14[] = {0x36, 0x0};
+static const sal_Unicode outlineSuffix14[] = {0x20, 0x0};
+static const sal_Unicode outlineBulletChar14[] = {0x32, 0x30, 0x32, 0x32, 0x0};
+static const sal_Unicode outlineBulletFontName14[] = {0x53, 0x74, 0x61, 0x72, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x0};
+static const sal_Unicode outlineParentNumbering14[] = {0x30, 0x0};
+static const sal_Unicode outlineLeftMargin14[] = {0x32, 0x30, 0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance14[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset14[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration14[] = {0x0};
+static const sal_Unicode outlineNatNum14[] = {0x0};
+static const sal_Unicode outlinePrefix20[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType20[] = {0x34, 0x0};
+static const sal_Unicode outlineSuffix20[] = {0x2e, 0x0};
+static const sal_Unicode outlineBulletChar20[] = {0x30, 0x30, 0x32, 0x30, 0x0};
+static const sal_Unicode outlineBulletFontName20[] = {0x0};
+static const sal_Unicode outlineParentNumbering20[] = {0x30, 0x0};
+static const sal_Unicode outlineLeftMargin20[] = {0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance20[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset20[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration20[] = {0x0};
+static const sal_Unicode outlineNatNum20[] = {0x0};
+static const sal_Unicode outlinePrefix21[] = {0x28, 0x0};
+static const sal_Unicode outlineNumType21[] = {0x31, 0x0};
+static const sal_Unicode outlineSuffix21[] = {0x29, 0x0};
+static const sal_Unicode outlineBulletChar21[] = {0x30, 0x30, 0x32, 0x30, 0x0};
+static const sal_Unicode outlineBulletFontName21[] = {0x0};
+static const sal_Unicode outlineParentNumbering21[] = {0x30, 0x0};
+static const sal_Unicode outlineLeftMargin21[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance21[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset21[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration21[] = {0x0};
+static const sal_Unicode outlineNatNum21[] = {0x0};
+static const sal_Unicode outlinePrefix22[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType22[] = {0x33, 0x0};
+static const sal_Unicode outlineSuffix22[] = {0x2e, 0x0};
+static const sal_Unicode outlineBulletChar22[] = {0x30, 0x30, 0x32, 0x30, 0x0};
+static const sal_Unicode outlineBulletFontName22[] = {0x0};
+static const sal_Unicode outlineParentNumbering22[] = {0x30, 0x0};
+static const sal_Unicode outlineLeftMargin22[] = {0x31, 0x30, 0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance22[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset22[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration22[] = {0x0};
+static const sal_Unicode outlineNatNum22[] = {0x0};
+static const sal_Unicode outlinePrefix23[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType23[] = {0x30, 0x0};
+static const sal_Unicode outlineSuffix23[] = {0x2e, 0x0};
+static const sal_Unicode outlineBulletChar23[] = {0x30, 0x30, 0x32, 0x30, 0x0};
+static const sal_Unicode outlineBulletFontName23[] = {0x0};
+static const sal_Unicode outlineParentNumbering23[] = {0x30, 0x0};
+static const sal_Unicode outlineLeftMargin23[] = {0x31, 0x35, 0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance23[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset23[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration23[] = {0x0};
+static const sal_Unicode outlineNatNum23[] = {0x0};
+static const sal_Unicode outlinePrefix24[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType24[] = {0x36, 0x0};
+static const sal_Unicode outlineSuffix24[] = {0x2e, 0x0};
+static const sal_Unicode outlineBulletChar24[] = {0x32, 0x30, 0x32, 0x32, 0x0};
+static const sal_Unicode outlineBulletFontName24[] = {0x53, 0x74, 0x61, 0x72, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x0};
+static const sal_Unicode outlineParentNumbering24[] = {0x30, 0x0};
+static const sal_Unicode outlineLeftMargin24[] = {0x32, 0x30, 0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance24[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset24[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration24[] = {0x0};
+static const sal_Unicode outlineNatNum24[] = {0x0};
+static const sal_Unicode outlinePrefix30[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType30[] = {0x34, 0x0};
+static const sal_Unicode outlineSuffix30[] = {0x2e, 0x0};
+static const sal_Unicode outlineBulletChar30[] = {0x30, 0x30, 0x32, 0x30, 0x0};
+static const sal_Unicode outlineBulletFontName30[] = {0x0};
+static const sal_Unicode outlineParentNumbering30[] = {0x30, 0x0};
+static const sal_Unicode outlineLeftMargin30[] = {0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance30[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset30[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration30[] = {0x0};
+static const sal_Unicode outlineNatNum30[] = {0x0};
+static const sal_Unicode outlinePrefix31[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType31[] = {0x34, 0x0};
+static const sal_Unicode outlineSuffix31[] = {0x2e, 0x0};
+static const sal_Unicode outlineBulletChar31[] = {0x30, 0x30, 0x32, 0x30, 0x0};
+static const sal_Unicode outlineBulletFontName31[] = {0x0};
+static const sal_Unicode outlineParentNumbering31[] = {0x30, 0x0};
+static const sal_Unicode outlineLeftMargin31[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance31[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset31[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration31[] = {0x0};
+static const sal_Unicode outlineNatNum31[] = {0x0};
+static const sal_Unicode outlinePrefix32[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType32[] = {0x34, 0x0};
+static const sal_Unicode outlineSuffix32[] = {0x2e, 0x0};
+static const sal_Unicode outlineBulletChar32[] = {0x30, 0x30, 0x32, 0x30, 0x0};
+static const sal_Unicode outlineBulletFontName32[] = {0x0};
+static const sal_Unicode outlineParentNumbering32[] = {0x30, 0x0};
+static const sal_Unicode outlineLeftMargin32[] = {0x31, 0x30, 0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance32[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset32[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration32[] = {0x0};
+static const sal_Unicode outlineNatNum32[] = {0x0};
+static const sal_Unicode outlinePrefix33[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType33[] = {0x34, 0x0};
+static const sal_Unicode outlineSuffix33[] = {0x2e, 0x0};
+static const sal_Unicode outlineBulletChar33[] = {0x30, 0x30, 0x32, 0x30, 0x0};
+static const sal_Unicode outlineBulletFontName33[] = {0x0};
+static const sal_Unicode outlineParentNumbering33[] = {0x30, 0x0};
+static const sal_Unicode outlineLeftMargin33[] = {0x31, 0x35, 0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance33[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset33[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration33[] = {0x0};
+static const sal_Unicode outlineNatNum33[] = {0x0};
+static const sal_Unicode outlinePrefix34[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType34[] = {0x34, 0x0};
+static const sal_Unicode outlineSuffix34[] = {0x2e, 0x0};
+static const sal_Unicode outlineBulletChar34[] = {0x30, 0x30, 0x32, 0x30, 0x0};
+static const sal_Unicode outlineBulletFontName34[] = {0x0};
+static const sal_Unicode outlineParentNumbering34[] = {0x30, 0x0};
+static const sal_Unicode outlineLeftMargin34[] = {0x32, 0x30, 0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance34[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset34[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration34[] = {0x0};
+static const sal_Unicode outlineNatNum34[] = {0x0};
+static const sal_Unicode outlinePrefix40[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType40[] = {0x32, 0x0};
+static const sal_Unicode outlineSuffix40[] = {0x2e, 0x0};
+static const sal_Unicode outlineBulletChar40[] = {0x30, 0x30, 0x32, 0x30, 0x0};
+static const sal_Unicode outlineBulletFontName40[] = {0x0};
+static const sal_Unicode outlineParentNumbering40[] = {0x30, 0x0};
+static const sal_Unicode outlineLeftMargin40[] = {0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance40[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset40[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration40[] = {0x0};
+static const sal_Unicode outlineNatNum40[] = {0x0};
+static const sal_Unicode outlinePrefix41[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType41[] = {0x30, 0x0};
+static const sal_Unicode outlineSuffix41[] = {0x2e, 0x0};
+static const sal_Unicode outlineBulletChar41[] = {0x30, 0x30, 0x32, 0x30, 0x0};
+static const sal_Unicode outlineBulletFontName41[] = {0x0};
+static const sal_Unicode outlineParentNumbering41[] = {0x30, 0x0};
+static const sal_Unicode outlineLeftMargin41[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance41[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset41[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration41[] = {0x0};
+static const sal_Unicode outlineNatNum41[] = {0x0};
+static const sal_Unicode outlinePrefix42[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType42[] = {0x33, 0x0};
+static const sal_Unicode outlineSuffix42[] = {0x2e, 0x0};
+static const sal_Unicode outlineBulletChar42[] = {0x30, 0x30, 0x32, 0x30, 0x0};
+static const sal_Unicode outlineBulletFontName42[] = {0x0};
+static const sal_Unicode outlineParentNumbering42[] = {0x30, 0x0};
+static const sal_Unicode outlineLeftMargin42[] = {0x31, 0x30, 0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance42[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset42[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration42[] = {0x0};
+static const sal_Unicode outlineNatNum42[] = {0x0};
+static const sal_Unicode outlinePrefix43[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType43[] = {0x31, 0x0};
+static const sal_Unicode outlineSuffix43[] = {0x29, 0x0};
+static const sal_Unicode outlineBulletChar43[] = {0x30, 0x30, 0x32, 0x30, 0x0};
+static const sal_Unicode outlineBulletFontName43[] = {0x0};
+static const sal_Unicode outlineParentNumbering43[] = {0x30, 0x0};
+static const sal_Unicode outlineLeftMargin43[] = {0x31, 0x35, 0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance43[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset43[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration43[] = {0x0};
+static const sal_Unicode outlineNatNum43[] = {0x0};
+static const sal_Unicode outlinePrefix44[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType44[] = {0x36, 0x0};
+static const sal_Unicode outlineSuffix44[] = {0x20, 0x0};
+static const sal_Unicode outlineBulletChar44[] = {0x32, 0x30, 0x32, 0x32, 0x0};
+static const sal_Unicode outlineBulletFontName44[] = {0x53, 0x74, 0x61, 0x72, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x0};
+static const sal_Unicode outlineParentNumbering44[] = {0x30, 0x0};
+static const sal_Unicode outlineLeftMargin44[] = {0x32, 0x30, 0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance44[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset44[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration44[] = {0x0};
+static const sal_Unicode outlineNatNum44[] = {0x0};
+static const sal_Unicode outlinePrefix50[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType50[] = {0x30, 0x0};
+static const sal_Unicode outlineSuffix50[] = {0x2e, 0x0};
+static const sal_Unicode outlineBulletChar50[] = {0x30, 0x30, 0x32, 0x30, 0x0};
+static const sal_Unicode outlineBulletFontName50[] = {0x0};
+static const sal_Unicode outlineParentNumbering50[] = {0x30, 0x0};
+static const sal_Unicode outlineLeftMargin50[] = {0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance50[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset50[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration50[] = {0x0};
+static const sal_Unicode outlineNatNum50[] = {0x0};
+static const sal_Unicode outlinePrefix51[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType51[] = {0x32, 0x0};
+static const sal_Unicode outlineSuffix51[] = {0x2e, 0x0};
+static const sal_Unicode outlineBulletChar51[] = {0x30, 0x30, 0x32, 0x30, 0x0};
+static const sal_Unicode outlineBulletFontName51[] = {0x0};
+static const sal_Unicode outlineParentNumbering51[] = {0x30, 0x0};
+static const sal_Unicode outlineLeftMargin51[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance51[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset51[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration51[] = {0x0};
+static const sal_Unicode outlineNatNum51[] = {0x0};
+static const sal_Unicode outlinePrefix52[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType52[] = {0x31, 0x0};
+static const sal_Unicode outlineSuffix52[] = {0x2e, 0x0};
+static const sal_Unicode outlineBulletChar52[] = {0x30, 0x30, 0x32, 0x30, 0x0};
+static const sal_Unicode outlineBulletFontName52[] = {0x0};
+static const sal_Unicode outlineParentNumbering52[] = {0x30, 0x0};
+static const sal_Unicode outlineLeftMargin52[] = {0x31, 0x30, 0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance52[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset52[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration52[] = {0x0};
+static const sal_Unicode outlineNatNum52[] = {0x0};
+static const sal_Unicode outlinePrefix53[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType53[] = {0x33, 0x0};
+static const sal_Unicode outlineSuffix53[] = {0x2e, 0x0};
+static const sal_Unicode outlineBulletChar53[] = {0x30, 0x30, 0x32, 0x30, 0x0};
+static const sal_Unicode outlineBulletFontName53[] = {0x0};
+static const sal_Unicode outlineParentNumbering53[] = {0x30, 0x0};
+static const sal_Unicode outlineLeftMargin53[] = {0x31, 0x35, 0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance53[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset53[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration53[] = {0x0};
+static const sal_Unicode outlineNatNum53[] = {0x0};
+static const sal_Unicode outlinePrefix54[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType54[] = {0x36, 0x0};
+static const sal_Unicode outlineSuffix54[] = {0x20, 0x0};
+static const sal_Unicode outlineBulletChar54[] = {0x32, 0x30, 0x32, 0x32, 0x0};
+static const sal_Unicode outlineBulletFontName54[] = {0x53, 0x74, 0x61, 0x72, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x0};
+static const sal_Unicode outlineParentNumbering54[] = {0x30, 0x0};
+static const sal_Unicode outlineLeftMargin54[] = {0x32, 0x30, 0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance54[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset54[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration54[] = {0x0};
+static const sal_Unicode outlineNatNum54[] = {0x0};
+static const sal_Unicode outlinePrefix60[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType60[] = {0x34, 0x0};
+static const sal_Unicode outlineSuffix60[] = {0x20, 0x0};
+static const sal_Unicode outlineBulletChar60[] = {0x30, 0x30, 0x32, 0x30, 0x0};
+static const sal_Unicode outlineBulletFontName60[] = {0x0};
+static const sal_Unicode outlineParentNumbering60[] = {0x30, 0x0};
+static const sal_Unicode outlineLeftMargin60[] = {0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance60[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset60[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration60[] = {0x0};
+static const sal_Unicode outlineNatNum60[] = {0x0};
+static const sal_Unicode outlinePrefix61[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType61[] = {0x34, 0x0};
+static const sal_Unicode outlineSuffix61[] = {0x20, 0x0};
+static const sal_Unicode outlineBulletChar61[] = {0x30, 0x30, 0x32, 0x30, 0x0};
+static const sal_Unicode outlineBulletFontName61[] = {0x0};
+static const sal_Unicode outlineParentNumbering61[] = {0x31, 0x0};
+static const sal_Unicode outlineLeftMargin61[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance61[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset61[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration61[] = {0x0};
+static const sal_Unicode outlineNatNum61[] = {0x0};
+static const sal_Unicode outlinePrefix62[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType62[] = {0x34, 0x0};
+static const sal_Unicode outlineSuffix62[] = {0x20, 0x0};
+static const sal_Unicode outlineBulletChar62[] = {0x30, 0x30, 0x32, 0x30, 0x0};
+static const sal_Unicode outlineBulletFontName62[] = {0x0};
+static const sal_Unicode outlineParentNumbering62[] = {0x32, 0x0};
+static const sal_Unicode outlineLeftMargin62[] = {0x31, 0x30, 0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance62[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset62[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration62[] = {0x0};
+static const sal_Unicode outlineNatNum62[] = {0x0};
+static const sal_Unicode outlinePrefix63[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType63[] = {0x34, 0x0};
+static const sal_Unicode outlineSuffix63[] = {0x20, 0x0};
+static const sal_Unicode outlineBulletChar63[] = {0x30, 0x30, 0x32, 0x30, 0x0};
+static const sal_Unicode outlineBulletFontName63[] = {0x0};
+static const sal_Unicode outlineParentNumbering63[] = {0x33, 0x0};
+static const sal_Unicode outlineLeftMargin63[] = {0x31, 0x35, 0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance63[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset63[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration63[] = {0x0};
+static const sal_Unicode outlineNatNum63[] = {0x0};
+static const sal_Unicode outlinePrefix64[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType64[] = {0x34, 0x0};
+static const sal_Unicode outlineSuffix64[] = {0x20, 0x0};
+static const sal_Unicode outlineBulletChar64[] = {0x30, 0x30, 0x32, 0x30, 0x0};
+static const sal_Unicode outlineBulletFontName64[] = {0x0};
+static const sal_Unicode outlineParentNumbering64[] = {0x34, 0x0};
+static const sal_Unicode outlineLeftMargin64[] = {0x32, 0x30, 0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance64[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset64[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration64[] = {0x0};
+static const sal_Unicode outlineNatNum64[] = {0x0};
+static const sal_Unicode outlinePrefix70[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType70[] = {0x36, 0x0};
+static const sal_Unicode outlineSuffix70[] = {0x20, 0x0};
+static const sal_Unicode outlineBulletChar70[] = {0x32, 0x37, 0x41, 0x32, 0x0};
+static const sal_Unicode outlineBulletFontName70[] = {0x53, 0x74, 0x61, 0x72, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x0};
+static const sal_Unicode outlineParentNumbering70[] = {0x30, 0x0};
+static const sal_Unicode outlineLeftMargin70[] = {0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance70[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset70[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration70[] = {0x0};
+static const sal_Unicode outlineNatNum70[] = {0x0};
+static const sal_Unicode outlinePrefix71[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType71[] = {0x36, 0x0};
+static const sal_Unicode outlineSuffix71[] = {0x20, 0x0};
+static const sal_Unicode outlineBulletChar71[] = {0x45, 0x30, 0x30, 0x36, 0x0};
+static const sal_Unicode outlineBulletFontName71[] = {0x53, 0x74, 0x61, 0x72, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x0};
+static const sal_Unicode outlineParentNumbering71[] = {0x30, 0x0};
+static const sal_Unicode outlineLeftMargin71[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance71[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset71[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration71[] = {0x0};
+static const sal_Unicode outlineNatNum71[] = {0x0};
+static const sal_Unicode outlinePrefix72[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType72[] = {0x36, 0x0};
+static const sal_Unicode outlineSuffix72[] = {0x29, 0x0};
+static const sal_Unicode outlineBulletChar72[] = {0x45, 0x30, 0x30, 0x34, 0x0};
+static const sal_Unicode outlineBulletFontName72[] = {0x53, 0x74, 0x61, 0x72, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x0};
+static const sal_Unicode outlineParentNumbering72[] = {0x30, 0x0};
+static const sal_Unicode outlineLeftMargin72[] = {0x31, 0x30, 0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance72[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset72[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration72[] = {0x0};
+static const sal_Unicode outlineNatNum72[] = {0x0};
+static const sal_Unicode outlinePrefix73[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType73[] = {0x36, 0x0};
+static const sal_Unicode outlineSuffix73[] = {0x20, 0x0};
+static const sal_Unicode outlineBulletChar73[] = {0x32, 0x30, 0x32, 0x32, 0x0};
+static const sal_Unicode outlineBulletFontName73[] = {0x53, 0x74, 0x61, 0x72, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x0};
+static const sal_Unicode outlineParentNumbering73[] = {0x30, 0x0};
+static const sal_Unicode outlineLeftMargin73[] = {0x31, 0x35, 0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance73[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset73[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration73[] = {0x0};
+static const sal_Unicode outlineNatNum73[] = {0x0};
+static const sal_Unicode outlinePrefix74[] = {0x20, 0x0};
+static const sal_Unicode outlineNumType74[] = {0x36, 0x0};
+static const sal_Unicode outlineSuffix74[] = {0x20, 0x0};
+static const sal_Unicode outlineBulletChar74[] = {0x32, 0x30, 0x32, 0x32, 0x0};
+static const sal_Unicode outlineBulletFontName74[] = {0x53, 0x74, 0x61, 0x72, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x0};
+static const sal_Unicode outlineParentNumbering74[] = {0x30, 0x0};
+static const sal_Unicode outlineLeftMargin74[] = {0x32, 0x30, 0x30, 0x0};
+static const sal_Unicode outlineSymbolTextDistance74[] = {0x35, 0x30, 0x0};
+static const sal_Unicode outlineFirstLineOffset74[] = {0x30, 0x0};
+static const sal_Unicode outlineTransliteration74[] = {0x0};
+static const sal_Unicode outlineNatNum74[] = {0x0};
+static const sal_Int16 outlineNbOfStyles = 8;
+
+static const sal_Int16 outlineNbOfLevelsPerStyle = 5;
+
+static const sal_Int16 outlineNbOfAttributesPerLevel = 11;
+
+static const sal_Unicode* outlineStyle0Level0[] = { outlinePrefix00, outlineNumType00, outlineSuffix00, outlineBulletChar00, outlineBulletFontName00, outlineParentNumbering00, outlineLeftMargin00, outlineSymbolTextDistance00, outlineFirstLineOffset00, outlineTransliteration00, outlineNatNum00, NULL };
+static const sal_Unicode* outlineStyle0Level1[] = { outlinePrefix01, outlineNumType01, outlineSuffix01, outlineBulletChar01, outlineBulletFontName01, outlineParentNumbering01, outlineLeftMargin01, outlineSymbolTextDistance01, outlineFirstLineOffset01, outlineTransliteration01, outlineNatNum01, NULL };
+static const sal_Unicode* outlineStyle0Level2[] = { outlinePrefix02, outlineNumType02, outlineSuffix02, outlineBulletChar02, outlineBulletFontName02, outlineParentNumbering02, outlineLeftMargin02, outlineSymbolTextDistance02, outlineFirstLineOffset02, outlineTransliteration02, outlineNatNum02, NULL };
+static const sal_Unicode* outlineStyle0Level3[] = { outlinePrefix03, outlineNumType03, outlineSuffix03, outlineBulletChar03, outlineBulletFontName03, outlineParentNumbering03, outlineLeftMargin03, outlineSymbolTextDistance03, outlineFirstLineOffset03, outlineTransliteration03, outlineNatNum03, NULL };
+static const sal_Unicode* outlineStyle0Level4[] = { outlinePrefix04, outlineNumType04, outlineSuffix04, outlineBulletChar04, outlineBulletFontName04, outlineParentNumbering04, outlineLeftMargin04, outlineSymbolTextDistance04, outlineFirstLineOffset04, outlineTransliteration04, outlineNatNum04, NULL };
+static const sal_Unicode* outlineStyle1Level0[] = { outlinePrefix10, outlineNumType10, outlineSuffix10, outlineBulletChar10, outlineBulletFontName10, outlineParentNumbering10, outlineLeftMargin10, outlineSymbolTextDistance10, outlineFirstLineOffset10, outlineTransliteration10, outlineNatNum10, NULL };
+static const sal_Unicode* outlineStyle1Level1[] = { outlinePrefix11, outlineNumType11, outlineSuffix11, outlineBulletChar11, outlineBulletFontName11, outlineParentNumbering11, outlineLeftMargin11, outlineSymbolTextDistance11, outlineFirstLineOffset11, outlineTransliteration11, outlineNatNum11, NULL };
+static const sal_Unicode* outlineStyle1Level2[] = { outlinePrefix12, outlineNumType12, outlineSuffix12, outlineBulletChar12, outlineBulletFontName12, outlineParentNumbering12, outlineLeftMargin12, outlineSymbolTextDistance12, outlineFirstLineOffset12, outlineTransliteration12, outlineNatNum12, NULL };
+static const sal_Unicode* outlineStyle1Level3[] = { outlinePrefix13, outlineNumType13, outlineSuffix13, outlineBulletChar13, outlineBulletFontName13, outlineParentNumbering13, outlineLeftMargin13, outlineSymbolTextDistance13, outlineFirstLineOffset13, outlineTransliteration13, outlineNatNum13, NULL };
+static const sal_Unicode* outlineStyle1Level4[] = { outlinePrefix14, outlineNumType14, outlineSuffix14, outlineBulletChar14, outlineBulletFontName14, outlineParentNumbering14, outlineLeftMargin14, outlineSymbolTextDistance14, outlineFirstLineOffset14, outlineTransliteration14, outlineNatNum14, NULL };
+static const sal_Unicode* outlineStyle2Level0[] = { outlinePrefix20, outlineNumType20, outlineSuffix20, outlineBulletChar20, outlineBulletFontName20, outlineParentNumbering20, outlineLeftMargin20, outlineSymbolTextDistance20, outlineFirstLineOffset20, outlineTransliteration20, outlineNatNum20, NULL };
+static const sal_Unicode* outlineStyle2Level1[] = { outlinePrefix21, outlineNumType21, outlineSuffix21, outlineBulletChar21, outlineBulletFontName21, outlineParentNumbering21, outlineLeftMargin21, outlineSymbolTextDistance21, outlineFirstLineOffset21, outlineTransliteration21, outlineNatNum21, NULL };
+static const sal_Unicode* outlineStyle2Level2[] = { outlinePrefix22, outlineNumType22, outlineSuffix22, outlineBulletChar22, outlineBulletFontName22, outlineParentNumbering22, outlineLeftMargin22, outlineSymbolTextDistance22, outlineFirstLineOffset22, outlineTransliteration22, outlineNatNum22, NULL };
+static const sal_Unicode* outlineStyle2Level3[] = { outlinePrefix23, outlineNumType23, outlineSuffix23, outlineBulletChar23, outlineBulletFontName23, outlineParentNumbering23, outlineLeftMargin23, outlineSymbolTextDistance23, outlineFirstLineOffset23, outlineTransliteration23, outlineNatNum23, NULL };
+static const sal_Unicode* outlineStyle2Level4[] = { outlinePrefix24, outlineNumType24, outlineSuffix24, outlineBulletChar24, outlineBulletFontName24, outlineParentNumbering24, outlineLeftMargin24, outlineSymbolTextDistance24, outlineFirstLineOffset24, outlineTransliteration24, outlineNatNum24, NULL };
+static const sal_Unicode* outlineStyle3Level0[] = { outlinePrefix30, outlineNumType30, outlineSuffix30, outlineBulletChar30, outlineBulletFontName30, outlineParentNumbering30, outlineLeftMargin30, outlineSymbolTextDistance30, outlineFirstLineOffset30, outlineTransliteration30, outlineNatNum30, NULL };
+static const sal_Unicode* outlineStyle3Level1[] = { outlinePrefix31, outlineNumType31, outlineSuffix31, outlineBulletChar31, outlineBulletFontName31, outlineParentNumbering31, outlineLeftMargin31, outlineSymbolTextDistance31, outlineFirstLineOffset31, outlineTransliteration31, outlineNatNum31, NULL };
+static const sal_Unicode* outlineStyle3Level2[] = { outlinePrefix32, outlineNumType32, outlineSuffix32, outlineBulletChar32, outlineBulletFontName32, outlineParentNumbering32, outlineLeftMargin32, outlineSymbolTextDistance32, outlineFirstLineOffset32, outlineTransliteration32, outlineNatNum32, NULL };
+static const sal_Unicode* outlineStyle3Level3[] = { outlinePrefix33, outlineNumType33, outlineSuffix33, outlineBulletChar33, outlineBulletFontName33, outlineParentNumbering33, outlineLeftMargin33, outlineSymbolTextDistance33, outlineFirstLineOffset33, outlineTransliteration33, outlineNatNum33, NULL };
+static const sal_Unicode* outlineStyle3Level4[] = { outlinePrefix34, outlineNumType34, outlineSuffix34, outlineBulletChar34, outlineBulletFontName34, outlineParentNumbering34, outlineLeftMargin34, outlineSymbolTextDistance34, outlineFirstLineOffset34, outlineTransliteration34, outlineNatNum34, NULL };
+static const sal_Unicode* outlineStyle4Level0[] = { outlinePrefix40, outlineNumType40, outlineSuffix40, outlineBulletChar40, outlineBulletFontName40, outlineParentNumbering40, outlineLeftMargin40, outlineSymbolTextDistance40, outlineFirstLineOffset40, outlineTransliteration40, outlineNatNum40, NULL };
+static const sal_Unicode* outlineStyle4Level1[] = { outlinePrefix41, outlineNumType41, outlineSuffix41, outlineBulletChar41, outlineBulletFontName41, outlineParentNumbering41, outlineLeftMargin41, outlineSymbolTextDistance41, outlineFirstLineOffset41, outlineTransliteration41, outlineNatNum41, NULL };
+static const sal_Unicode* outlineStyle4Level2[] = { outlinePrefix42, outlineNumType42, outlineSuffix42, outlineBulletChar42, outlineBulletFontName42, outlineParentNumbering42, outlineLeftMargin42, outlineSymbolTextDistance42, outlineFirstLineOffset42, outlineTransliteration42, outlineNatNum42, NULL };
+static const sal_Unicode* outlineStyle4Level3[] = { outlinePrefix43, outlineNumType43, outlineSuffix43, outlineBulletChar43, outlineBulletFontName43, outlineParentNumbering43, outlineLeftMargin43, outlineSymbolTextDistance43, outlineFirstLineOffset43, outlineTransliteration43, outlineNatNum43, NULL };
+static const sal_Unicode* outlineStyle4Level4[] = { outlinePrefix44, outlineNumType44, outlineSuffix44, outlineBulletChar44, outlineBulletFontName44, outlineParentNumbering44, outlineLeftMargin44, outlineSymbolTextDistance44, outlineFirstLineOffset44, outlineTransliteration44, outlineNatNum44, NULL };
+static const sal_Unicode* outlineStyle5Level0[] = { outlinePrefix50, outlineNumType50, outlineSuffix50, outlineBulletChar50, outlineBulletFontName50, outlineParentNumbering50, outlineLeftMargin50, outlineSymbolTextDistance50, outlineFirstLineOffset50, outlineTransliteration50, outlineNatNum50, NULL };
+static const sal_Unicode* outlineStyle5Level1[] = { outlinePrefix51, outlineNumType51, outlineSuffix51, outlineBulletChar51, outlineBulletFontName51, outlineParentNumbering51, outlineLeftMargin51, outlineSymbolTextDistance51, outlineFirstLineOffset51, outlineTransliteration51, outlineNatNum51, NULL };
+static const sal_Unicode* outlineStyle5Level2[] = { outlinePrefix52, outlineNumType52, outlineSuffix52, outlineBulletChar52, outlineBulletFontName52, outlineParentNumbering52, outlineLeftMargin52, outlineSymbolTextDistance52, outlineFirstLineOffset52, outlineTransliteration52, outlineNatNum52, NULL };
+static const sal_Unicode* outlineStyle5Level3[] = { outlinePrefix53, outlineNumType53, outlineSuffix53, outlineBulletChar53, outlineBulletFontName53, outlineParentNumbering53, outlineLeftMargin53, outlineSymbolTextDistance53, outlineFirstLineOffset53, outlineTransliteration53, outlineNatNum53, NULL };
+static const sal_Unicode* outlineStyle5Level4[] = { outlinePrefix54, outlineNumType54, outlineSuffix54, outlineBulletChar54, outlineBulletFontName54, outlineParentNumbering54, outlineLeftMargin54, outlineSymbolTextDistance54, outlineFirstLineOffset54, outlineTransliteration54, outlineNatNum54, NULL };
+static const sal_Unicode* outlineStyle6Level0[] = { outlinePrefix60, outlineNumType60, outlineSuffix60, outlineBulletChar60, outlineBulletFontName60, outlineParentNumbering60, outlineLeftMargin60, outlineSymbolTextDistance60, outlineFirstLineOffset60, outlineTransliteration60, outlineNatNum60, NULL };
+static const sal_Unicode* outlineStyle6Level1[] = { outlinePrefix61, outlineNumType61, outlineSuffix61, outlineBulletChar61, outlineBulletFontName61, outlineParentNumbering61, outlineLeftMargin61, outlineSymbolTextDistance61, outlineFirstLineOffset61, outlineTransliteration61, outlineNatNum61, NULL };
+static const sal_Unicode* outlineStyle6Level2[] = { outlinePrefix62, outlineNumType62, outlineSuffix62, outlineBulletChar62, outlineBulletFontName62, outlineParentNumbering62, outlineLeftMargin62, outlineSymbolTextDistance62, outlineFirstLineOffset62, outlineTransliteration62, outlineNatNum62, NULL };
+static const sal_Unicode* outlineStyle6Level3[] = { outlinePrefix63, outlineNumType63, outlineSuffix63, outlineBulletChar63, outlineBulletFontName63, outlineParentNumbering63, outlineLeftMargin63, outlineSymbolTextDistance63, outlineFirstLineOffset63, outlineTransliteration63, outlineNatNum63, NULL };
+static const sal_Unicode* outlineStyle6Level4[] = { outlinePrefix64, outlineNumType64, outlineSuffix64, outlineBulletChar64, outlineBulletFontName64, outlineParentNumbering64, outlineLeftMargin64, outlineSymbolTextDistance64, outlineFirstLineOffset64, outlineTransliteration64, outlineNatNum64, NULL };
+static const sal_Unicode* outlineStyle7Level0[] = { outlinePrefix70, outlineNumType70, outlineSuffix70, outlineBulletChar70, outlineBulletFontName70, outlineParentNumbering70, outlineLeftMargin70, outlineSymbolTextDistance70, outlineFirstLineOffset70, outlineTransliteration70, outlineNatNum70, NULL };
+static const sal_Unicode* outlineStyle7Level1[] = { outlinePrefix71, outlineNumType71, outlineSuffix71, outlineBulletChar71, outlineBulletFontName71, outlineParentNumbering71, outlineLeftMargin71, outlineSymbolTextDistance71, outlineFirstLineOffset71, outlineTransliteration71, outlineNatNum71, NULL };
+static const sal_Unicode* outlineStyle7Level2[] = { outlinePrefix72, outlineNumType72, outlineSuffix72, outlineBulletChar72, outlineBulletFontName72, outlineParentNumbering72, outlineLeftMargin72, outlineSymbolTextDistance72, outlineFirstLineOffset72, outlineTransliteration72, outlineNatNum72, NULL };
+static const sal_Unicode* outlineStyle7Level3[] = { outlinePrefix73, outlineNumType73, outlineSuffix73, outlineBulletChar73, outlineBulletFontName73, outlineParentNumbering73, outlineLeftMargin73, outlineSymbolTextDistance73, outlineFirstLineOffset73, outlineTransliteration73, outlineNatNum73, NULL };
+static const sal_Unicode* outlineStyle7Level4[] = { outlinePrefix74, outlineNumType74, outlineSuffix74, outlineBulletChar74, outlineBulletFontName74, outlineParentNumbering74, outlineLeftMargin74, outlineSymbolTextDistance74, outlineFirstLineOffset74, outlineTransliteration74, outlineNatNum74, NULL };
+
+static const sal_Unicode** outlineStyle0[] = { outlineStyle0Level0, outlineStyle0Level1, outlineStyle0Level2, outlineStyle0Level3, outlineStyle0Level4, NULL };
+static const sal_Unicode** outlineStyle1[] = { outlineStyle1Level0, outlineStyle1Level1, outlineStyle1Level2, outlineStyle1Level3, outlineStyle1Level4, NULL };
+static const sal_Unicode** outlineStyle2[] = { outlineStyle2Level0, outlineStyle2Level1, outlineStyle2Level2, outlineStyle2Level3, outlineStyle2Level4, NULL };
+static const sal_Unicode** outlineStyle3[] = { outlineStyle3Level0, outlineStyle3Level1, outlineStyle3Level2, outlineStyle3Level3, outlineStyle3Level4, NULL };
+static const sal_Unicode** outlineStyle4[] = { outlineStyle4Level0, outlineStyle4Level1, outlineStyle4Level2, outlineStyle4Level3, outlineStyle4Level4, NULL };
+static const sal_Unicode** outlineStyle5[] = { outlineStyle5Level0, outlineStyle5Level1, outlineStyle5Level2, outlineStyle5Level3, outlineStyle5Level4, NULL };
+static const sal_Unicode** outlineStyle6[] = { outlineStyle6Level0, outlineStyle6Level1, outlineStyle6Level2, outlineStyle6Level3, outlineStyle6Level4, NULL };
+static const sal_Unicode** outlineStyle7[] = { outlineStyle7Level0, outlineStyle7Level1, outlineStyle7Level2, outlineStyle7Level3, outlineStyle7Level4, NULL };
+
+static const sal_Unicode*** LCOutlineNumberingLevelsArray[] = {
+ outlineStyle0,
+ outlineStyle1,
+ outlineStyle2,
+ outlineStyle3,
+ outlineStyle4,
+ outlineStyle5,
+ outlineStyle6,
+ outlineStyle7,
+ NULL
+};
+
+SAL_DLLPUBLIC_EXPORT const sal_Unicode **** SAL_CALL getOutlineNumberingLevels_en_US( sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes )
+{
+ nStyles = outlineNbOfStyles;
+ nLevels = outlineNbOfLevelsPerStyle;
+ nAttributes = outlineNbOfAttributesPerLevel;
+ return LCOutlineNumberingLevelsArray;
+}
+} // extern "C"
+
diff --git a/vcl/workben/localestub/localedata_en_ZA.cxx b/vcl/workben/localestub/localedata_en_ZA.cxx
new file mode 100644
index 000000000..ecd9a9cde
--- /dev/null
+++ b/vcl/workben/localestub/localedata_en_ZA.cxx
@@ -0,0 +1,1263 @@
+#include <sal/types.h>
+
+
+#include <stdio.h>
+
+extern "C" {
+
+static const sal_Unicode langID[] = {0x65, 0x6e, 0x0};
+static const sal_Unicode langDefaultName[] = {0x45, 0x6e, 0x67, 0x6c, 0x69, 0x73, 0x68, 0x0};
+static const sal_Unicode countryID[] = {0x5a, 0x41, 0x0};
+static const sal_Unicode countryDefaultName[] = {0x53, 0x6f, 0x75, 0x74, 0x68, 0x20, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x0};
+static const sal_Unicode Variant[] = {0x0};
+
+static const sal_Unicode* LCInfoArray[] = {
+ langID,
+ langDefaultName,
+ countryID,
+ countryDefaultName,
+ Variant
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLCInfo_en_ZA(sal_Int16& count)
+{
+ count = 0;
+ return (sal_Unicode**)LCInfoArray;
+}
+
+
+static const sal_Unicode LC_CTYPE_Unoid[] = {0x0};
+static const sal_Unicode dateSeparator[] = {0x2f, 0x0};
+static const sal_Unicode thousandSeparator[] = {0x2c, 0x0};
+static const sal_Unicode decimalSeparator[] = {0x2e, 0x0};
+static const sal_Unicode timeSeparator[] = {0x3a, 0x0};
+static const sal_Unicode time100SecSeparator[] = {0x2e, 0x0};
+static const sal_Unicode listSeparator[] = {0x3b, 0x0};
+static const sal_Unicode LongDateDayOfWeekSeparator[] = {0x20, 0x0};
+static const sal_Unicode LongDateDaySeparator[] = {0x20, 0x0};
+static const sal_Unicode LongDateMonthSeparator[] = {0x20, 0x0};
+static const sal_Unicode LongDateYearSeparator[] = {0x20, 0x0};
+static const sal_Unicode quotationStart[] = {0x2018, 0x0};
+static const sal_Unicode quotationEnd[] = {0x2019, 0x0};
+static const sal_Unicode doubleQuotationStart[] = {0x201c, 0x0};
+static const sal_Unicode doubleQuotationEnd[] = {0x201d, 0x0};
+static const sal_Unicode timeAM[] = {0x41, 0x4d, 0x0};
+static const sal_Unicode timePM[] = {0x50, 0x4d, 0x0};
+static const sal_Unicode measurementSystem[] = {0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x0};
+
+static const sal_Unicode* LCType[] = {
+ LC_CTYPE_Unoid,
+ dateSeparator,
+ thousandSeparator,
+ decimalSeparator,
+ timeSeparator,
+ time100SecSeparator,
+ listSeparator,
+ quotationStart,
+ quotationEnd,
+ doubleQuotationStart,
+ doubleQuotationEnd,
+ timeAM,
+ timePM,
+ measurementSystem,
+ LongDateDayOfWeekSeparator,
+ LongDateDaySeparator,
+ LongDateMonthSeparator,
+ LongDateYearSeparator
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLocaleItem_en_ZA(sal_Int16& count)
+{
+ count = 0;
+ return (sal_Unicode**)LCType;
+}
+static const sal_Unicode replaceFrom0[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x0};
+static const sal_Unicode replaceTo0[] = {0x5b, 0x24, 0x52, 0x2d, 0x31, 0x43, 0x30, 0x39, 0x5d, 0x0};
+static const sal_Unicode FormatKey0[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement0[] = {1};
+static const sal_Unicode FormatType0[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage0[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex0[] = {0};
+static const sal_Unicode FormatCode0[] = {0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x0};
+static const sal_Unicode FormatDefaultName0[] = {0x0};
+static const sal_Unicode FormatKey1[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement1[] = {1};
+static const sal_Unicode FormatType1[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage1[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex1[] = {1};
+static const sal_Unicode FormatCode1[] = {0x30, 0x0};
+static const sal_Unicode FormatDefaultName1[] = {0x0};
+static const sal_Unicode FormatKey2[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement2[] = {0};
+static const sal_Unicode FormatType2[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage2[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex2[] = {2};
+static const sal_Unicode FormatCode2[] = {0x30, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName2[] = {0x0};
+static const sal_Unicode FormatKey3[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement3[] = {0};
+static const sal_Unicode FormatType3[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage3[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex3[] = {3};
+static const sal_Unicode FormatCode3[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName3[] = {0x0};
+static const sal_Unicode FormatKey4[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement4[] = {0};
+static const sal_Unicode FormatType4[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage4[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex4[] = {4};
+static const sal_Unicode FormatCode4[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName4[] = {0x0};
+static const sal_Unicode FormatKey5[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement5[] = {0};
+static const sal_Unicode FormatType5[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage5[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex5[] = {5};
+static const sal_Unicode FormatCode5[] = {0x23, 0x2c, 0x23, 0x23, 0x23, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName5[] = {0x0};
+static const sal_Unicode FormatKey6[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x37, 0x0};
+static const sal_Unicode defaultFormatElement6[] = {0};
+static const sal_Unicode FormatType6[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage6[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex6[] = {70};
+static const sal_Unicode FormatCode6[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x5f, 0x29, 0x3b, 0x28, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x29, 0x0};
+static const sal_Unicode FormatDefaultName6[] = {0x0};
+static const sal_Unicode FormatKey7[] = {0x46, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x38, 0x0};
+static const sal_Unicode defaultFormatElement7[] = {0};
+static const sal_Unicode FormatType7[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage7[] = {0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex7[] = {71};
+static const sal_Unicode FormatCode7[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x5f, 0x29, 0x3b, 0x28, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x29, 0x0};
+static const sal_Unicode FormatDefaultName7[] = {0x0};
+static const sal_Unicode FormatKey8[] = {0x53, 0x63, 0x69, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x63, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement8[] = {0};
+static const sal_Unicode FormatType8[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage8[] = {0x53, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, 0x43, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex8[] = {6};
+static const sal_Unicode FormatCode8[] = {0x30, 0x2e, 0x30, 0x30, 0x45, 0x2b, 0x30, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName8[] = {0x0};
+static const sal_Unicode FormatKey9[] = {0x53, 0x63, 0x69, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x63, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement9[] = {1};
+static const sal_Unicode FormatType9[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage9[] = {0x53, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, 0x43, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex9[] = {7};
+static const sal_Unicode FormatCode9[] = {0x30, 0x2e, 0x30, 0x30, 0x45, 0x2b, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName9[] = {0x0};
+static const sal_Unicode FormatKey10[] = {0x53, 0x63, 0x69, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x63, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement10[] = {0};
+static const sal_Unicode FormatType10[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage10[] = {0x53, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x49, 0x46, 0x49, 0x43, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex10[] = {78};
+static const sal_Unicode FormatCode10[] = {0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x45, 0x2b, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName10[] = {0x0};
+static const sal_Unicode FormatKey11[] = {0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement11[] = {1};
+static const sal_Unicode FormatType11[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage11[] = {0x50, 0x45, 0x52, 0x43, 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex11[] = {8};
+static const sal_Unicode FormatCode11[] = {0x30, 0x25, 0x0};
+static const sal_Unicode FormatDefaultName11[] = {0x0};
+static const sal_Unicode FormatKey12[] = {0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement12[] = {1};
+static const sal_Unicode FormatType12[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage12[] = {0x50, 0x45, 0x52, 0x43, 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x0};
+static const sal_Unicode Formatindex12[] = {9};
+static const sal_Unicode FormatCode12[] = {0x30, 0x2e, 0x30, 0x30, 0x25, 0x0};
+static const sal_Unicode FormatDefaultName12[] = {0x0};
+static const sal_Unicode FormatKey13[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement13[] = {1};
+static const sal_Unicode FormatType13[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage13[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex13[] = {12};
+static const sal_Unicode FormatCode13[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x20, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x3b, 0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x2d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName13[] = {0x0};
+static const sal_Unicode FormatKey14[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement14[] = {0};
+static const sal_Unicode FormatType14[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage14[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex14[] = {13};
+static const sal_Unicode FormatCode14[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x20, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x3b, 0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x2d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName14[] = {0x0};
+static const sal_Unicode FormatKey15[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement15[] = {0};
+static const sal_Unicode FormatType15[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage15[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex15[] = {14};
+static const sal_Unicode FormatCode15[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x20, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x3b, 0x5b, 0x52, 0x45, 0x44, 0x5d, 0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x2d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName15[] = {0x0};
+static const sal_Unicode FormatKey16[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement16[] = {1};
+static const sal_Unicode FormatType16[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage16[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex16[] = {15};
+static const sal_Unicode FormatCode16[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x20, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x3b, 0x5b, 0x52, 0x45, 0x44, 0x5d, 0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x2d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName16[] = {0x0};
+static const sal_Unicode FormatKey17[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement17[] = {0};
+static const sal_Unicode FormatType17[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage17[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex17[] = {16};
+static const sal_Unicode FormatCode17[] = {0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x30, 0x30, 0x20, 0x43, 0x43, 0x43, 0x0};
+static const sal_Unicode FormatDefaultName17[] = {0x0};
+static const sal_Unicode FormatKey18[] = {0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement18[] = {0};
+static const sal_Unicode FormatType18[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage18[] = {0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x0};
+static const sal_Unicode Formatindex18[] = {17};
+static const sal_Unicode FormatCode18[] = {0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x20, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x2d, 0x2d, 0x3b, 0x5b, 0x52, 0x45, 0x44, 0x5d, 0x5b, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x5d, 0x2d, 0x23, 0x2c, 0x23, 0x23, 0x30, 0x2e, 0x2d, 0x2d, 0x0};
+static const sal_Unicode FormatDefaultName18[] = {0x0};
+static const sal_Unicode FormatKey19[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement19[] = {1};
+static const sal_Unicode FormatType19[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage19[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex19[] = {18};
+static const sal_Unicode FormatCode19[] = {0x59, 0x59, 0x2f, 0x4d, 0x4d, 0x2f, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName19[] = {0x0};
+static const sal_Unicode FormatKey20[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x39, 0x0};
+static const sal_Unicode defaultFormatElement20[] = {1};
+static const sal_Unicode FormatType20[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage20[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex20[] = {19};
+static const sal_Unicode FormatCode20[] = {0x44, 0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName20[] = {0x0};
+static const sal_Unicode FormatKey21[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x38, 0x0};
+static const sal_Unicode defaultFormatElement21[] = {1};
+static const sal_Unicode FormatType21[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage21[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex21[] = {20};
+static const sal_Unicode FormatCode21[] = {0x59, 0x59, 0x2f, 0x4d, 0x4d, 0x2f, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName21[] = {0x0};
+static const sal_Unicode FormatKey22[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x37, 0x0};
+static const sal_Unicode defaultFormatElement22[] = {0};
+static const sal_Unicode FormatType22[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage22[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex22[] = {21};
+static const sal_Unicode FormatCode22[] = {0x59, 0x59, 0x59, 0x59, 0x2f, 0x4d, 0x4d, 0x2f, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName22[] = {0x0};
+static const sal_Unicode FormatKey23[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x30, 0x0};
+static const sal_Unicode defaultFormatElement23[] = {0};
+static const sal_Unicode FormatType23[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage23[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex23[] = {22};
+static const sal_Unicode FormatCode23[] = {0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName23[] = {0x0};
+static const sal_Unicode FormatKey24[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement24[] = {0};
+static const sal_Unicode FormatType24[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage24[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex24[] = {23};
+static const sal_Unicode FormatCode24[] = {0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName24[] = {0x0};
+static const sal_Unicode FormatKey25[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement25[] = {0};
+static const sal_Unicode FormatType25[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage25[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex25[] = {24};
+static const sal_Unicode FormatCode25[] = {0x44, 0x2e, 0x20, 0x4d, 0x4d, 0x4d, 0x2e, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName25[] = {0x0};
+static const sal_Unicode FormatKey26[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement26[] = {0};
+static const sal_Unicode FormatType26[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage26[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex26[] = {25};
+static const sal_Unicode FormatCode26[] = {0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName26[] = {0x0};
+static const sal_Unicode FormatKey27[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x37, 0x0};
+static const sal_Unicode defaultFormatElement27[] = {0};
+static const sal_Unicode FormatType27[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage27[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex27[] = {26};
+static const sal_Unicode FormatCode27[] = {0x44, 0x2e, 0x20, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName27[] = {0x0};
+static const sal_Unicode FormatKey28[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement28[] = {0};
+static const sal_Unicode FormatType28[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage28[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex28[] = {27};
+static const sal_Unicode FormatCode28[] = {0x4e, 0x4e, 0x20, 0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName28[] = {0x0};
+static const sal_Unicode FormatKey29[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement29[] = {0};
+static const sal_Unicode FormatType29[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage29[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex29[] = {28};
+static const sal_Unicode FormatCode29[] = {0x4e, 0x4e, 0x20, 0x44, 0x44, 0x2f, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName29[] = {0x0};
+static const sal_Unicode FormatKey30[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement30[] = {0};
+static const sal_Unicode FormatType30[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage30[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex30[] = {29};
+static const sal_Unicode FormatCode30[] = {0x4e, 0x4e, 0x20, 0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName30[] = {0x0};
+static const sal_Unicode FormatKey31[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement31[] = {0};
+static const sal_Unicode FormatType31[] = {0x6c, 0x6f, 0x6e, 0x67, 0x0};
+static const sal_Unicode FormatUsage31[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex31[] = {30};
+static const sal_Unicode FormatCode31[] = {0x4e, 0x4e, 0x4e, 0x4e, 0x44, 0x20, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x59, 0x59, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName31[] = {0x0};
+static const sal_Unicode FormatKey32[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x38, 0x0};
+static const sal_Unicode defaultFormatElement32[] = {0};
+static const sal_Unicode FormatType32[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage32[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex32[] = {31};
+static const sal_Unicode FormatCode32[] = {0x4d, 0x4d, 0x2d, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName32[] = {0x0};
+static const sal_Unicode FormatKey33[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x39, 0x0};
+static const sal_Unicode defaultFormatElement33[] = {0};
+static const sal_Unicode FormatType33[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage33[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex33[] = {32};
+static const sal_Unicode FormatCode33[] = {0x59, 0x59, 0x2d, 0x4d, 0x4d, 0x2d, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName33[] = {0x0};
+static const sal_Unicode FormatKey34[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x30, 0x0};
+static const sal_Unicode defaultFormatElement34[] = {0};
+static const sal_Unicode FormatType34[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage34[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex34[] = {33};
+static const sal_Unicode FormatCode34[] = {0x59, 0x59, 0x59, 0x59, 0x2d, 0x4d, 0x4d, 0x2d, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName34[] = {0x49, 0x53, 0x4f, 0x20, 0x38, 0x36, 0x30, 0x31, 0x0};
+static const sal_Unicode FormatKey35[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement35[] = {0};
+static const sal_Unicode FormatType35[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage35[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex35[] = {34};
+static const sal_Unicode FormatCode35[] = {0x59, 0x59, 0x2f, 0x4d, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName35[] = {0x0};
+static const sal_Unicode FormatKey36[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement36[] = {0};
+static const sal_Unicode FormatType36[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage36[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex36[] = {35};
+static const sal_Unicode FormatCode36[] = {0x4d, 0x4d, 0x4d, 0x20, 0x44, 0x44, 0x0};
+static const sal_Unicode FormatDefaultName36[] = {0x0};
+static const sal_Unicode FormatKey37[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement37[] = {0};
+static const sal_Unicode FormatType37[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage37[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex37[] = {36};
+static const sal_Unicode FormatCode37[] = {0x4d, 0x4d, 0x4d, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName37[] = {0x0};
+static const sal_Unicode FormatKey38[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement38[] = {0};
+static const sal_Unicode FormatType38[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage38[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex38[] = {37};
+static const sal_Unicode FormatCode38[] = {0x51, 0x51, 0x20, 0x59, 0x59, 0x0};
+static const sal_Unicode FormatDefaultName38[] = {0x0};
+static const sal_Unicode FormatKey39[] = {0x44, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement39[] = {0};
+static const sal_Unicode FormatType39[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage39[] = {0x44, 0x41, 0x54, 0x45, 0x0};
+static const sal_Unicode Formatindex39[] = {38};
+static const sal_Unicode FormatCode39[] = {0x57, 0x57, 0x0};
+static const sal_Unicode FormatDefaultName39[] = {0x0};
+static const sal_Unicode FormatKey40[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement40[] = {1};
+static const sal_Unicode FormatType40[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage40[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex40[] = {39};
+static const sal_Unicode FormatCode40[] = {0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName40[] = {0x0};
+static const sal_Unicode FormatKey41[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement41[] = {1};
+static const sal_Unicode FormatType41[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage41[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex41[] = {40};
+static const sal_Unicode FormatCode41[] = {0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x0};
+static const sal_Unicode FormatDefaultName41[] = {0x0};
+static const sal_Unicode FormatKey42[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x33, 0x0};
+static const sal_Unicode defaultFormatElement42[] = {0};
+static const sal_Unicode FormatType42[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage42[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex42[] = {41};
+static const sal_Unicode FormatCode42[] = {0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x20, 0x41, 0x4d, 0x2f, 0x50, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName42[] = {0x0};
+static const sal_Unicode FormatKey43[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x34, 0x0};
+static const sal_Unicode defaultFormatElement43[] = {0};
+static const sal_Unicode FormatType43[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage43[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex43[] = {42};
+static const sal_Unicode FormatCode43[] = {0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x20, 0x41, 0x4d, 0x2f, 0x50, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName43[] = {0x0};
+static const sal_Unicode FormatKey44[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x35, 0x0};
+static const sal_Unicode defaultFormatElement44[] = {0};
+static const sal_Unicode FormatType44[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage44[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex44[] = {43};
+static const sal_Unicode FormatCode44[] = {0x5b, 0x48, 0x48, 0x5d, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x0};
+static const sal_Unicode FormatDefaultName44[] = {0x0};
+static const sal_Unicode FormatKey45[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x36, 0x0};
+static const sal_Unicode defaultFormatElement45[] = {0};
+static const sal_Unicode FormatType45[] = {0x73, 0x68, 0x6f, 0x72, 0x74, 0x0};
+static const sal_Unicode FormatUsage45[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex45[] = {44};
+static const sal_Unicode FormatCode45[] = {0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName45[] = {0x0};
+static const sal_Unicode FormatKey46[] = {0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x37, 0x0};
+static const sal_Unicode defaultFormatElement46[] = {0};
+static const sal_Unicode FormatType46[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage46[] = {0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex46[] = {45};
+static const sal_Unicode FormatCode46[] = {0x5b, 0x48, 0x48, 0x5d, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x2e, 0x30, 0x30, 0x0};
+static const sal_Unicode FormatDefaultName46[] = {0x0};
+static const sal_Unicode FormatKey47[] = {0x44, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x31, 0x0};
+static const sal_Unicode defaultFormatElement47[] = {1};
+static const sal_Unicode FormatType47[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage47[] = {0x44, 0x41, 0x54, 0x45, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex47[] = {46};
+static const sal_Unicode FormatCode47[] = {0x59, 0x59, 0x2f, 0x4d, 0x4d, 0x2f, 0x44, 0x44, 0x20, 0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x0};
+static const sal_Unicode FormatDefaultName47[] = {0x0};
+static const sal_Unicode FormatKey48[] = {0x44, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x6b, 0x65, 0x79, 0x32, 0x0};
+static const sal_Unicode defaultFormatElement48[] = {0};
+static const sal_Unicode FormatType48[] = {0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x0};
+static const sal_Unicode FormatUsage48[] = {0x44, 0x41, 0x54, 0x45, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x0};
+static const sal_Unicode Formatindex48[] = {47};
+static const sal_Unicode FormatCode48[] = {0x59, 0x59, 0x59, 0x59, 0x2f, 0x4d, 0x4d, 0x2f, 0x44, 0x44, 0x20, 0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x3a, 0x53, 0x53, 0x0};
+static const sal_Unicode FormatDefaultName48[] = {0x0};
+
+static const sal_Int16 FormatElementsCount0 = 49;
+static const sal_Unicode* FormatElementsArray0[] = {
+ FormatCode0,
+ FormatDefaultName0,
+ FormatKey0,
+ FormatType0,
+ FormatUsage0,
+ Formatindex0,
+ defaultFormatElement0,
+ FormatCode1,
+ FormatDefaultName1,
+ FormatKey1,
+ FormatType1,
+ FormatUsage1,
+ Formatindex1,
+ defaultFormatElement1,
+ FormatCode2,
+ FormatDefaultName2,
+ FormatKey2,
+ FormatType2,
+ FormatUsage2,
+ Formatindex2,
+ defaultFormatElement2,
+ FormatCode3,
+ FormatDefaultName3,
+ FormatKey3,
+ FormatType3,
+ FormatUsage3,
+ Formatindex3,
+ defaultFormatElement3,
+ FormatCode4,
+ FormatDefaultName4,
+ FormatKey4,
+ FormatType4,
+ FormatUsage4,
+ Formatindex4,
+ defaultFormatElement4,
+ FormatCode5,
+ FormatDefaultName5,
+ FormatKey5,
+ FormatType5,
+ FormatUsage5,
+ Formatindex5,
+ defaultFormatElement5,
+ FormatCode6,
+ FormatDefaultName6,
+ FormatKey6,
+ FormatType6,
+ FormatUsage6,
+ Formatindex6,
+ defaultFormatElement6,
+ FormatCode7,
+ FormatDefaultName7,
+ FormatKey7,
+ FormatType7,
+ FormatUsage7,
+ Formatindex7,
+ defaultFormatElement7,
+ FormatCode8,
+ FormatDefaultName8,
+ FormatKey8,
+ FormatType8,
+ FormatUsage8,
+ Formatindex8,
+ defaultFormatElement8,
+ FormatCode9,
+ FormatDefaultName9,
+ FormatKey9,
+ FormatType9,
+ FormatUsage9,
+ Formatindex9,
+ defaultFormatElement9,
+ FormatCode10,
+ FormatDefaultName10,
+ FormatKey10,
+ FormatType10,
+ FormatUsage10,
+ Formatindex10,
+ defaultFormatElement10,
+ FormatCode11,
+ FormatDefaultName11,
+ FormatKey11,
+ FormatType11,
+ FormatUsage11,
+ Formatindex11,
+ defaultFormatElement11,
+ FormatCode12,
+ FormatDefaultName12,
+ FormatKey12,
+ FormatType12,
+ FormatUsage12,
+ Formatindex12,
+ defaultFormatElement12,
+ FormatCode13,
+ FormatDefaultName13,
+ FormatKey13,
+ FormatType13,
+ FormatUsage13,
+ Formatindex13,
+ defaultFormatElement13,
+ FormatCode14,
+ FormatDefaultName14,
+ FormatKey14,
+ FormatType14,
+ FormatUsage14,
+ Formatindex14,
+ defaultFormatElement14,
+ FormatCode15,
+ FormatDefaultName15,
+ FormatKey15,
+ FormatType15,
+ FormatUsage15,
+ Formatindex15,
+ defaultFormatElement15,
+ FormatCode16,
+ FormatDefaultName16,
+ FormatKey16,
+ FormatType16,
+ FormatUsage16,
+ Formatindex16,
+ defaultFormatElement16,
+ FormatCode17,
+ FormatDefaultName17,
+ FormatKey17,
+ FormatType17,
+ FormatUsage17,
+ Formatindex17,
+ defaultFormatElement17,
+ FormatCode18,
+ FormatDefaultName18,
+ FormatKey18,
+ FormatType18,
+ FormatUsage18,
+ Formatindex18,
+ defaultFormatElement18,
+ FormatCode19,
+ FormatDefaultName19,
+ FormatKey19,
+ FormatType19,
+ FormatUsage19,
+ Formatindex19,
+ defaultFormatElement19,
+ FormatCode20,
+ FormatDefaultName20,
+ FormatKey20,
+ FormatType20,
+ FormatUsage20,
+ Formatindex20,
+ defaultFormatElement20,
+ FormatCode21,
+ FormatDefaultName21,
+ FormatKey21,
+ FormatType21,
+ FormatUsage21,
+ Formatindex21,
+ defaultFormatElement21,
+ FormatCode22,
+ FormatDefaultName22,
+ FormatKey22,
+ FormatType22,
+ FormatUsage22,
+ Formatindex22,
+ defaultFormatElement22,
+ FormatCode23,
+ FormatDefaultName23,
+ FormatKey23,
+ FormatType23,
+ FormatUsage23,
+ Formatindex23,
+ defaultFormatElement23,
+ FormatCode24,
+ FormatDefaultName24,
+ FormatKey24,
+ FormatType24,
+ FormatUsage24,
+ Formatindex24,
+ defaultFormatElement24,
+ FormatCode25,
+ FormatDefaultName25,
+ FormatKey25,
+ FormatType25,
+ FormatUsage25,
+ Formatindex25,
+ defaultFormatElement25,
+ FormatCode26,
+ FormatDefaultName26,
+ FormatKey26,
+ FormatType26,
+ FormatUsage26,
+ Formatindex26,
+ defaultFormatElement26,
+ FormatCode27,
+ FormatDefaultName27,
+ FormatKey27,
+ FormatType27,
+ FormatUsage27,
+ Formatindex27,
+ defaultFormatElement27,
+ FormatCode28,
+ FormatDefaultName28,
+ FormatKey28,
+ FormatType28,
+ FormatUsage28,
+ Formatindex28,
+ defaultFormatElement28,
+ FormatCode29,
+ FormatDefaultName29,
+ FormatKey29,
+ FormatType29,
+ FormatUsage29,
+ Formatindex29,
+ defaultFormatElement29,
+ FormatCode30,
+ FormatDefaultName30,
+ FormatKey30,
+ FormatType30,
+ FormatUsage30,
+ Formatindex30,
+ defaultFormatElement30,
+ FormatCode31,
+ FormatDefaultName31,
+ FormatKey31,
+ FormatType31,
+ FormatUsage31,
+ Formatindex31,
+ defaultFormatElement31,
+ FormatCode32,
+ FormatDefaultName32,
+ FormatKey32,
+ FormatType32,
+ FormatUsage32,
+ Formatindex32,
+ defaultFormatElement32,
+ FormatCode33,
+ FormatDefaultName33,
+ FormatKey33,
+ FormatType33,
+ FormatUsage33,
+ Formatindex33,
+ defaultFormatElement33,
+ FormatCode34,
+ FormatDefaultName34,
+ FormatKey34,
+ FormatType34,
+ FormatUsage34,
+ Formatindex34,
+ defaultFormatElement34,
+ FormatCode35,
+ FormatDefaultName35,
+ FormatKey35,
+ FormatType35,
+ FormatUsage35,
+ Formatindex35,
+ defaultFormatElement35,
+ FormatCode36,
+ FormatDefaultName36,
+ FormatKey36,
+ FormatType36,
+ FormatUsage36,
+ Formatindex36,
+ defaultFormatElement36,
+ FormatCode37,
+ FormatDefaultName37,
+ FormatKey37,
+ FormatType37,
+ FormatUsage37,
+ Formatindex37,
+ defaultFormatElement37,
+ FormatCode38,
+ FormatDefaultName38,
+ FormatKey38,
+ FormatType38,
+ FormatUsage38,
+ Formatindex38,
+ defaultFormatElement38,
+ FormatCode39,
+ FormatDefaultName39,
+ FormatKey39,
+ FormatType39,
+ FormatUsage39,
+ Formatindex39,
+ defaultFormatElement39,
+ FormatCode40,
+ FormatDefaultName40,
+ FormatKey40,
+ FormatType40,
+ FormatUsage40,
+ Formatindex40,
+ defaultFormatElement40,
+ FormatCode41,
+ FormatDefaultName41,
+ FormatKey41,
+ FormatType41,
+ FormatUsage41,
+ Formatindex41,
+ defaultFormatElement41,
+ FormatCode42,
+ FormatDefaultName42,
+ FormatKey42,
+ FormatType42,
+ FormatUsage42,
+ Formatindex42,
+ defaultFormatElement42,
+ FormatCode43,
+ FormatDefaultName43,
+ FormatKey43,
+ FormatType43,
+ FormatUsage43,
+ Formatindex43,
+ defaultFormatElement43,
+ FormatCode44,
+ FormatDefaultName44,
+ FormatKey44,
+ FormatType44,
+ FormatUsage44,
+ Formatindex44,
+ defaultFormatElement44,
+ FormatCode45,
+ FormatDefaultName45,
+ FormatKey45,
+ FormatType45,
+ FormatUsage45,
+ Formatindex45,
+ defaultFormatElement45,
+ FormatCode46,
+ FormatDefaultName46,
+ FormatKey46,
+ FormatType46,
+ FormatUsage46,
+ Formatindex46,
+ defaultFormatElement46,
+ FormatCode47,
+ FormatDefaultName47,
+ FormatKey47,
+ FormatType47,
+ FormatUsage47,
+ Formatindex47,
+ defaultFormatElement47,
+ FormatCode48,
+ FormatDefaultName48,
+ FormatKey48,
+ FormatType48,
+ FormatUsage48,
+ Formatindex48,
+ defaultFormatElement48,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode const * const * SAL_CALL getAllFormats0_en_ZA(sal_Int16& count, const sal_Unicode*& from, const sal_Unicode*& to)
+{
+ count = FormatElementsCount0;
+ from = replaceFrom0;
+ to = replaceTo0;
+ return (sal_Unicode**)FormatElementsArray0;
+}
+static const sal_Unicode DateAcceptancePattern0[] = {0x59, 0x2f, 0x4d, 0x2f, 0x44, 0x0};
+static const sal_Unicode DateAcceptancePattern1[] = {0x4d, 0x2f, 0x44, 0x0};
+static const sal_Int16 DateAcceptancePatternsCount = 2;
+static const sal_Unicode* DateAcceptancePatternsArray[] = {
+ DateAcceptancePattern0,
+ DateAcceptancePattern1,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getDateAcceptancePatterns_en_ZA(sal_Int16& count)
+{
+ count = DateAcceptancePatternsCount;
+ return (sal_Unicode**)DateAcceptancePatternsArray;
+}
+extern sal_Unicode ** SAL_CALL getCollatorImplementation_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollatorImplementation_en_ZA(sal_Int16& count)
+{
+ return getCollatorImplementation_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getCollationOptions_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollationOptions_en_ZA(sal_Int16& count)
+{
+ return getCollationOptions_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getSearchOptions_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getSearchOptions_en_ZA(sal_Int16& count)
+{
+ return getSearchOptions_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getIndexAlgorithm_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getIndexAlgorithm_en_ZA(sal_Int16& count)
+{
+ return getIndexAlgorithm_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getUnicodeScripts_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getUnicodeScripts_en_ZA(sal_Int16& count)
+{
+ return getUnicodeScripts_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getFollowPageWords_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getFollowPageWords_en_ZA(sal_Int16& count)
+{
+ return getFollowPageWords_en_US(count);
+}
+static const sal_Unicode calendarID0[] = {0x67, 0x72, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x61, 0x6e, 0x0};
+static const sal_Unicode defaultCalendar0[] = {1};
+static const sal_Unicode dayID00[] = {0x73, 0x75, 0x6e, 0x0};
+static const sal_Unicode dayDefaultAbbrvName00[] = {0x53, 0x75, 0x6e, 0x0};
+static const sal_Unicode dayDefaultFullName00[] = {0x53, 0x75, 0x6e, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName00[] = {0x53, 0x0};
+static const sal_Unicode dayID01[] = {0x6d, 0x6f, 0x6e, 0x0};
+static const sal_Unicode dayDefaultAbbrvName01[] = {0x4d, 0x6f, 0x6e, 0x0};
+static const sal_Unicode dayDefaultFullName01[] = {0x4d, 0x6f, 0x6e, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName01[] = {0x4d, 0x0};
+static const sal_Unicode dayID02[] = {0x74, 0x75, 0x65, 0x0};
+static const sal_Unicode dayDefaultAbbrvName02[] = {0x54, 0x75, 0x65, 0x0};
+static const sal_Unicode dayDefaultFullName02[] = {0x54, 0x75, 0x65, 0x73, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName02[] = {0x54, 0x0};
+static const sal_Unicode dayID03[] = {0x77, 0x65, 0x64, 0x0};
+static const sal_Unicode dayDefaultAbbrvName03[] = {0x57, 0x65, 0x64, 0x0};
+static const sal_Unicode dayDefaultFullName03[] = {0x57, 0x65, 0x64, 0x6e, 0x65, 0x73, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName03[] = {0x57, 0x0};
+static const sal_Unicode dayID04[] = {0x74, 0x68, 0x75, 0x0};
+static const sal_Unicode dayDefaultAbbrvName04[] = {0x54, 0x68, 0x75, 0x0};
+static const sal_Unicode dayDefaultFullName04[] = {0x54, 0x68, 0x75, 0x72, 0x73, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName04[] = {0x54, 0x0};
+static const sal_Unicode dayID05[] = {0x66, 0x72, 0x69, 0x0};
+static const sal_Unicode dayDefaultAbbrvName05[] = {0x46, 0x72, 0x69, 0x0};
+static const sal_Unicode dayDefaultFullName05[] = {0x46, 0x72, 0x69, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName05[] = {0x46, 0x0};
+static const sal_Unicode dayID06[] = {0x73, 0x61, 0x74, 0x0};
+static const sal_Unicode dayDefaultAbbrvName06[] = {0x53, 0x61, 0x74, 0x0};
+static const sal_Unicode dayDefaultFullName06[] = {0x53, 0x61, 0x74, 0x75, 0x72, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName06[] = {0x53, 0x0};
+static const sal_Unicode monthID00[] = {0x6a, 0x61, 0x6e, 0x0};
+static const sal_Unicode monthDefaultAbbrvName00[] = {0x4a, 0x61, 0x6e, 0x0};
+static const sal_Unicode monthDefaultFullName00[] = {0x4a, 0x61, 0x6e, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode monthDefaultNarrowName00[] = {0x4a, 0x0};
+static const sal_Unicode monthID01[] = {0x66, 0x65, 0x62, 0x0};
+static const sal_Unicode monthDefaultAbbrvName01[] = {0x46, 0x65, 0x62, 0x0};
+static const sal_Unicode monthDefaultFullName01[] = {0x46, 0x65, 0x62, 0x72, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode monthDefaultNarrowName01[] = {0x46, 0x0};
+static const sal_Unicode monthID02[] = {0x6d, 0x61, 0x72, 0x0};
+static const sal_Unicode monthDefaultAbbrvName02[] = {0x4d, 0x61, 0x72, 0x0};
+static const sal_Unicode monthDefaultFullName02[] = {0x4d, 0x61, 0x72, 0x63, 0x68, 0x0};
+static const sal_Unicode monthDefaultNarrowName02[] = {0x4d, 0x0};
+static const sal_Unicode monthID03[] = {0x61, 0x70, 0x72, 0x0};
+static const sal_Unicode monthDefaultAbbrvName03[] = {0x41, 0x70, 0x72, 0x0};
+static const sal_Unicode monthDefaultFullName03[] = {0x41, 0x70, 0x72, 0x69, 0x6c, 0x0};
+static const sal_Unicode monthDefaultNarrowName03[] = {0x41, 0x0};
+static const sal_Unicode monthID04[] = {0x6d, 0x61, 0x79, 0x0};
+static const sal_Unicode monthDefaultAbbrvName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode monthDefaultFullName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode monthDefaultNarrowName04[] = {0x4d, 0x0};
+static const sal_Unicode monthID05[] = {0x6a, 0x75, 0x6e, 0x0};
+static const sal_Unicode monthDefaultAbbrvName05[] = {0x4a, 0x75, 0x6e, 0x0};
+static const sal_Unicode monthDefaultFullName05[] = {0x4a, 0x75, 0x6e, 0x65, 0x0};
+static const sal_Unicode monthDefaultNarrowName05[] = {0x4a, 0x0};
+static const sal_Unicode monthID06[] = {0x6a, 0x75, 0x6c, 0x0};
+static const sal_Unicode monthDefaultAbbrvName06[] = {0x4a, 0x75, 0x6c, 0x0};
+static const sal_Unicode monthDefaultFullName06[] = {0x4a, 0x75, 0x6c, 0x79, 0x0};
+static const sal_Unicode monthDefaultNarrowName06[] = {0x4a, 0x0};
+static const sal_Unicode monthID07[] = {0x61, 0x75, 0x67, 0x0};
+static const sal_Unicode monthDefaultAbbrvName07[] = {0x41, 0x75, 0x67, 0x0};
+static const sal_Unicode monthDefaultFullName07[] = {0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x0};
+static const sal_Unicode monthDefaultNarrowName07[] = {0x41, 0x0};
+static const sal_Unicode monthID08[] = {0x73, 0x65, 0x70, 0x0};
+static const sal_Unicode monthDefaultAbbrvName08[] = {0x53, 0x65, 0x70, 0x0};
+static const sal_Unicode monthDefaultFullName08[] = {0x53, 0x65, 0x70, 0x74, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName08[] = {0x53, 0x0};
+static const sal_Unicode monthID09[] = {0x6f, 0x63, 0x74, 0x0};
+static const sal_Unicode monthDefaultAbbrvName09[] = {0x4f, 0x63, 0x74, 0x0};
+static const sal_Unicode monthDefaultFullName09[] = {0x4f, 0x63, 0x74, 0x6f, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName09[] = {0x4f, 0x0};
+static const sal_Unicode monthID010[] = {0x6e, 0x6f, 0x76, 0x0};
+static const sal_Unicode monthDefaultAbbrvName010[] = {0x4e, 0x6f, 0x76, 0x0};
+static const sal_Unicode monthDefaultFullName010[] = {0x4e, 0x6f, 0x76, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName010[] = {0x4e, 0x0};
+static const sal_Unicode monthID011[] = {0x64, 0x65, 0x63, 0x0};
+static const sal_Unicode monthDefaultAbbrvName011[] = {0x44, 0x65, 0x63, 0x0};
+static const sal_Unicode monthDefaultFullName011[] = {0x44, 0x65, 0x63, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName011[] = {0x44, 0x0};
+static const sal_Unicode genitiveMonthID00[] = {0x6a, 0x61, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName00[] = {0x4a, 0x61, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName00[] = {0x4a, 0x61, 0x6e, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName00[] = {0x4a, 0x0};
+static const sal_Unicode genitiveMonthID01[] = {0x66, 0x65, 0x62, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName01[] = {0x46, 0x65, 0x62, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName01[] = {0x46, 0x65, 0x62, 0x72, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName01[] = {0x46, 0x0};
+static const sal_Unicode genitiveMonthID02[] = {0x6d, 0x61, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName02[] = {0x4d, 0x61, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName02[] = {0x4d, 0x61, 0x72, 0x63, 0x68, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName02[] = {0x4d, 0x0};
+static const sal_Unicode genitiveMonthID03[] = {0x61, 0x70, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName03[] = {0x41, 0x70, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName03[] = {0x41, 0x70, 0x72, 0x69, 0x6c, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName03[] = {0x41, 0x0};
+static const sal_Unicode genitiveMonthID04[] = {0x6d, 0x61, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName04[] = {0x4d, 0x0};
+static const sal_Unicode genitiveMonthID05[] = {0x6a, 0x75, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName05[] = {0x4a, 0x75, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName05[] = {0x4a, 0x75, 0x6e, 0x65, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName05[] = {0x4a, 0x0};
+static const sal_Unicode genitiveMonthID06[] = {0x6a, 0x75, 0x6c, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName06[] = {0x4a, 0x75, 0x6c, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName06[] = {0x4a, 0x75, 0x6c, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName06[] = {0x4a, 0x0};
+static const sal_Unicode genitiveMonthID07[] = {0x61, 0x75, 0x67, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName07[] = {0x41, 0x75, 0x67, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName07[] = {0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName07[] = {0x41, 0x0};
+static const sal_Unicode genitiveMonthID08[] = {0x73, 0x65, 0x70, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName08[] = {0x53, 0x65, 0x70, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName08[] = {0x53, 0x65, 0x70, 0x74, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName08[] = {0x53, 0x0};
+static const sal_Unicode genitiveMonthID09[] = {0x6f, 0x63, 0x74, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName09[] = {0x4f, 0x63, 0x74, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName09[] = {0x4f, 0x63, 0x74, 0x6f, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName09[] = {0x4f, 0x0};
+static const sal_Unicode genitiveMonthID010[] = {0x6e, 0x6f, 0x76, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName010[] = {0x4e, 0x6f, 0x76, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName010[] = {0x4e, 0x6f, 0x76, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName010[] = {0x4e, 0x0};
+static const sal_Unicode genitiveMonthID011[] = {0x64, 0x65, 0x63, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName011[] = {0x44, 0x65, 0x63, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName011[] = {0x44, 0x65, 0x63, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName011[] = {0x44, 0x0};
+static const sal_Unicode partitiveMonthID00[] = {0x6a, 0x61, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName00[] = {0x4a, 0x61, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName00[] = {0x4a, 0x61, 0x6e, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName00[] = {0x4a, 0x0};
+static const sal_Unicode partitiveMonthID01[] = {0x66, 0x65, 0x62, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName01[] = {0x46, 0x65, 0x62, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName01[] = {0x46, 0x65, 0x62, 0x72, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName01[] = {0x46, 0x0};
+static const sal_Unicode partitiveMonthID02[] = {0x6d, 0x61, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName02[] = {0x4d, 0x61, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName02[] = {0x4d, 0x61, 0x72, 0x63, 0x68, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName02[] = {0x4d, 0x0};
+static const sal_Unicode partitiveMonthID03[] = {0x61, 0x70, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName03[] = {0x41, 0x70, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName03[] = {0x41, 0x70, 0x72, 0x69, 0x6c, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName03[] = {0x41, 0x0};
+static const sal_Unicode partitiveMonthID04[] = {0x6d, 0x61, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName04[] = {0x4d, 0x0};
+static const sal_Unicode partitiveMonthID05[] = {0x6a, 0x75, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName05[] = {0x4a, 0x75, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName05[] = {0x4a, 0x75, 0x6e, 0x65, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName05[] = {0x4a, 0x0};
+static const sal_Unicode partitiveMonthID06[] = {0x6a, 0x75, 0x6c, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName06[] = {0x4a, 0x75, 0x6c, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName06[] = {0x4a, 0x75, 0x6c, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName06[] = {0x4a, 0x0};
+static const sal_Unicode partitiveMonthID07[] = {0x61, 0x75, 0x67, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName07[] = {0x41, 0x75, 0x67, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName07[] = {0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName07[] = {0x41, 0x0};
+static const sal_Unicode partitiveMonthID08[] = {0x73, 0x65, 0x70, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName08[] = {0x53, 0x65, 0x70, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName08[] = {0x53, 0x65, 0x70, 0x74, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName08[] = {0x53, 0x0};
+static const sal_Unicode partitiveMonthID09[] = {0x6f, 0x63, 0x74, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName09[] = {0x4f, 0x63, 0x74, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName09[] = {0x4f, 0x63, 0x74, 0x6f, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName09[] = {0x4f, 0x0};
+static const sal_Unicode partitiveMonthID010[] = {0x6e, 0x6f, 0x76, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName010[] = {0x4e, 0x6f, 0x76, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName010[] = {0x4e, 0x6f, 0x76, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName010[] = {0x4e, 0x0};
+static const sal_Unicode partitiveMonthID011[] = {0x64, 0x65, 0x63, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName011[] = {0x44, 0x65, 0x63, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName011[] = {0x44, 0x65, 0x63, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName011[] = {0x44, 0x0};
+static const sal_Unicode eraID00[] = {0x62, 0x63, 0x0};
+
+static const sal_Unicode eraDefaultAbbrvName00[] = {0x42, 0x43, 0x0};
+static const sal_Unicode eraDefaultFullName00[] = {0x42, 0x43, 0x0};
+static const sal_Unicode eraID01[] = {0x61, 0x64, 0x0};
+
+static const sal_Unicode eraDefaultAbbrvName01[] = {0x41, 0x44, 0x0};
+static const sal_Unicode eraDefaultFullName01[] = {0x41, 0x44, 0x0};
+static const sal_Unicode startDayOfWeek0[] = {0x73, 0x75, 0x6e, 0x0};
+static const sal_Unicode minimalDaysInFirstWeek0[] = {1};
+static const sal_Int16 calendarsCount = 1;
+
+static const sal_Unicode nbOfDays[] = {7};
+static const sal_Unicode nbOfMonths[] = {12};
+static const sal_Unicode nbOfGenitiveMonths[] = {12};
+static const sal_Unicode nbOfPartitiveMonths[] = {12};
+static const sal_Unicode nbOfEras[] = {2};
+static const sal_Unicode* calendars[] = {
+ nbOfDays,
+ nbOfMonths,
+ nbOfGenitiveMonths,
+ nbOfPartitiveMonths,
+ nbOfEras,
+ calendarID0,
+ defaultCalendar0,
+ dayID00,
+ dayDefaultAbbrvName00,
+ dayDefaultFullName00,
+ dayDefaultNarrowName00,
+ dayID01,
+ dayDefaultAbbrvName01,
+ dayDefaultFullName01,
+ dayDefaultNarrowName01,
+ dayID02,
+ dayDefaultAbbrvName02,
+ dayDefaultFullName02,
+ dayDefaultNarrowName02,
+ dayID03,
+ dayDefaultAbbrvName03,
+ dayDefaultFullName03,
+ dayDefaultNarrowName03,
+ dayID04,
+ dayDefaultAbbrvName04,
+ dayDefaultFullName04,
+ dayDefaultNarrowName04,
+ dayID05,
+ dayDefaultAbbrvName05,
+ dayDefaultFullName05,
+ dayDefaultNarrowName05,
+ dayID06,
+ dayDefaultAbbrvName06,
+ dayDefaultFullName06,
+ dayDefaultNarrowName06,
+ monthID00,
+ monthDefaultAbbrvName00,
+ monthDefaultFullName00,
+ monthDefaultNarrowName00,
+ monthID01,
+ monthDefaultAbbrvName01,
+ monthDefaultFullName01,
+ monthDefaultNarrowName01,
+ monthID02,
+ monthDefaultAbbrvName02,
+ monthDefaultFullName02,
+ monthDefaultNarrowName02,
+ monthID03,
+ monthDefaultAbbrvName03,
+ monthDefaultFullName03,
+ monthDefaultNarrowName03,
+ monthID04,
+ monthDefaultAbbrvName04,
+ monthDefaultFullName04,
+ monthDefaultNarrowName04,
+ monthID05,
+ monthDefaultAbbrvName05,
+ monthDefaultFullName05,
+ monthDefaultNarrowName05,
+ monthID06,
+ monthDefaultAbbrvName06,
+ monthDefaultFullName06,
+ monthDefaultNarrowName06,
+ monthID07,
+ monthDefaultAbbrvName07,
+ monthDefaultFullName07,
+ monthDefaultNarrowName07,
+ monthID08,
+ monthDefaultAbbrvName08,
+ monthDefaultFullName08,
+ monthDefaultNarrowName08,
+ monthID09,
+ monthDefaultAbbrvName09,
+ monthDefaultFullName09,
+ monthDefaultNarrowName09,
+ monthID010,
+ monthDefaultAbbrvName010,
+ monthDefaultFullName010,
+ monthDefaultNarrowName010,
+ monthID011,
+ monthDefaultAbbrvName011,
+ monthDefaultFullName011,
+ monthDefaultNarrowName011,
+ genitiveMonthID00,
+ genitiveMonthDefaultAbbrvName00,
+ genitiveMonthDefaultFullName00,
+ genitiveMonthDefaultNarrowName00,
+ genitiveMonthID01,
+ genitiveMonthDefaultAbbrvName01,
+ genitiveMonthDefaultFullName01,
+ genitiveMonthDefaultNarrowName01,
+ genitiveMonthID02,
+ genitiveMonthDefaultAbbrvName02,
+ genitiveMonthDefaultFullName02,
+ genitiveMonthDefaultNarrowName02,
+ genitiveMonthID03,
+ genitiveMonthDefaultAbbrvName03,
+ genitiveMonthDefaultFullName03,
+ genitiveMonthDefaultNarrowName03,
+ genitiveMonthID04,
+ genitiveMonthDefaultAbbrvName04,
+ genitiveMonthDefaultFullName04,
+ genitiveMonthDefaultNarrowName04,
+ genitiveMonthID05,
+ genitiveMonthDefaultAbbrvName05,
+ genitiveMonthDefaultFullName05,
+ genitiveMonthDefaultNarrowName05,
+ genitiveMonthID06,
+ genitiveMonthDefaultAbbrvName06,
+ genitiveMonthDefaultFullName06,
+ genitiveMonthDefaultNarrowName06,
+ genitiveMonthID07,
+ genitiveMonthDefaultAbbrvName07,
+ genitiveMonthDefaultFullName07,
+ genitiveMonthDefaultNarrowName07,
+ genitiveMonthID08,
+ genitiveMonthDefaultAbbrvName08,
+ genitiveMonthDefaultFullName08,
+ genitiveMonthDefaultNarrowName08,
+ genitiveMonthID09,
+ genitiveMonthDefaultAbbrvName09,
+ genitiveMonthDefaultFullName09,
+ genitiveMonthDefaultNarrowName09,
+ genitiveMonthID010,
+ genitiveMonthDefaultAbbrvName010,
+ genitiveMonthDefaultFullName010,
+ genitiveMonthDefaultNarrowName010,
+ genitiveMonthID011,
+ genitiveMonthDefaultAbbrvName011,
+ genitiveMonthDefaultFullName011,
+ genitiveMonthDefaultNarrowName011,
+ partitiveMonthID00,
+ partitiveMonthDefaultAbbrvName00,
+ partitiveMonthDefaultFullName00,
+ partitiveMonthDefaultNarrowName00,
+ partitiveMonthID01,
+ partitiveMonthDefaultAbbrvName01,
+ partitiveMonthDefaultFullName01,
+ partitiveMonthDefaultNarrowName01,
+ partitiveMonthID02,
+ partitiveMonthDefaultAbbrvName02,
+ partitiveMonthDefaultFullName02,
+ partitiveMonthDefaultNarrowName02,
+ partitiveMonthID03,
+ partitiveMonthDefaultAbbrvName03,
+ partitiveMonthDefaultFullName03,
+ partitiveMonthDefaultNarrowName03,
+ partitiveMonthID04,
+ partitiveMonthDefaultAbbrvName04,
+ partitiveMonthDefaultFullName04,
+ partitiveMonthDefaultNarrowName04,
+ partitiveMonthID05,
+ partitiveMonthDefaultAbbrvName05,
+ partitiveMonthDefaultFullName05,
+ partitiveMonthDefaultNarrowName05,
+ partitiveMonthID06,
+ partitiveMonthDefaultAbbrvName06,
+ partitiveMonthDefaultFullName06,
+ partitiveMonthDefaultNarrowName06,
+ partitiveMonthID07,
+ partitiveMonthDefaultAbbrvName07,
+ partitiveMonthDefaultFullName07,
+ partitiveMonthDefaultNarrowName07,
+ partitiveMonthID08,
+ partitiveMonthDefaultAbbrvName08,
+ partitiveMonthDefaultFullName08,
+ partitiveMonthDefaultNarrowName08,
+ partitiveMonthID09,
+ partitiveMonthDefaultAbbrvName09,
+ partitiveMonthDefaultFullName09,
+ partitiveMonthDefaultNarrowName09,
+ partitiveMonthID010,
+ partitiveMonthDefaultAbbrvName010,
+ partitiveMonthDefaultFullName010,
+ partitiveMonthDefaultNarrowName010,
+ partitiveMonthID011,
+ partitiveMonthDefaultAbbrvName011,
+ partitiveMonthDefaultFullName011,
+ partitiveMonthDefaultNarrowName011,
+ eraID00,
+ eraDefaultAbbrvName00,
+ eraDefaultFullName00,
+ eraID01,
+ eraDefaultAbbrvName01,
+ eraDefaultFullName01,
+ startDayOfWeek0,
+ minimalDaysInFirstWeek0,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCalendars_en_ZA(sal_Int16& count)
+{
+ count = calendarsCount;
+ return (sal_Unicode**)calendars;
+}
+static const sal_Unicode defaultCurrency0[] = {1};
+static const sal_Unicode defaultCurrencyUsedInCompatibleFormatCodes0[] = {1};
+static const sal_Unicode defaultCurrencyLegacyOnly0[] = {0};
+static const sal_Unicode currencyID0[] = {0x5a, 0x41, 0x52, 0x0};
+static const sal_Unicode currencySymbol0[] = {0x52, 0x0};
+static const sal_Unicode bankSymbol0[] = {0x5a, 0x41, 0x52, 0x0};
+static const sal_Unicode currencyName0[] = {0x52, 0x61, 0x6e, 0x64, 0x0};
+static const sal_Unicode currencyDecimalPlaces0[] = {2};
+
+static const sal_Int16 currencyCount = 1;
+
+static const sal_Unicode* currencies[] = {
+ currencyID0,
+ currencySymbol0,
+ bankSymbol0,
+ currencyName0,
+ defaultCurrency0,
+ defaultCurrencyUsedInCompatibleFormatCodes0,
+ currencyDecimalPlaces0,
+ defaultCurrencyLegacyOnly0,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCurrencies_en_ZA(sal_Int16& count)
+{
+ count = currencyCount;
+ return (sal_Unicode**)currencies;
+}
+extern sal_Unicode ** SAL_CALL getTransliterations_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getTransliterations_en_ZA(sal_Int16& count)
+{
+ return getTransliterations_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getForbiddenCharacters_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getForbiddenCharacters_en_ZA(sal_Int16& count)
+{
+ return getForbiddenCharacters_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getBreakIteratorRules_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getBreakIteratorRules_en_ZA(sal_Int16& count)
+{
+ return getBreakIteratorRules_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getReservedWords_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getReservedWords_en_ZA(sal_Int16& count)
+{
+ return getReservedWords_en_US(count);
+}
+// ---> ContinuousNumbering
+extern const sal_Unicode *** SAL_CALL getContinuousNumberingLevels_en_US(sal_Int16& nStyles, sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode *** SAL_CALL getContinuousNumberingLevels_en_ZA(sal_Int16& nStyles, sal_Int16& nAttributes)
+{
+ return getContinuousNumberingLevels_en_US(nStyles, nAttributes);
+}
+// ---> OutlineNumbering
+extern const sal_Unicode **** SAL_CALL getOutlineNumberingLevels_en_US(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode **** SAL_CALL getOutlineNumberingLevels_en_ZA(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes)
+{
+ return getOutlineNumberingLevels_en_US(nStyles, nLevels, nAttributes);
+}
+} // extern "C"
+
diff --git a/vcl/workben/localestub/localedata_en_ZM.cxx b/vcl/workben/localestub/localedata_en_ZM.cxx
new file mode 100644
index 000000000..0e61cd7bf
--- /dev/null
+++ b/vcl/workben/localestub/localedata_en_ZM.cxx
@@ -0,0 +1,142 @@
+#include <sal/types.h>
+
+#include <stdio.h>
+
+extern "C" {
+
+static const sal_Unicode langID[] = { 0x65, 0x6e, 0x0 };
+static const sal_Unicode langDefaultName[] = { 0x45, 0x6e, 0x67, 0x6c, 0x69, 0x73, 0x68, 0x0 };
+static const sal_Unicode countryID[] = { 0x5a, 0x4d, 0x0 };
+static const sal_Unicode countryDefaultName[] = { 0x5a, 0x61, 0x6d, 0x62, 0x69, 0x61, 0x0 };
+static const sal_Unicode Variant[] = { 0x0 };
+
+static const sal_Unicode* LCInfoArray[]
+ = { langID, langDefaultName, countryID, countryDefaultName, Variant };
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getLCInfo_en_ZM(sal_Int16& count)
+{
+ count = SAL_N_ELEMENTS(LCInfoArray);
+ return (sal_Unicode**)LCInfoArray;
+}
+extern sal_Unicode** SAL_CALL getLocaleItem_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getLocaleItem_en_ZM(sal_Int16& count)
+{
+ return getLocaleItem_en_GB(count);
+}
+static const sal_Unicode replaceTo0[]
+ = { 0x5b, 0x24, 0x4b, 0x2d, 0x41, 0x30, 0x30, 0x39, 0x5d, 0x0 };
+extern sal_Unicode const* const* SAL_CALL getAllFormats0_en_GB(sal_Int16& count,
+ const sal_Unicode*& from,
+ const sal_Unicode*& to);
+SAL_DLLPUBLIC_EXPORT sal_Unicode const* const* SAL_CALL
+getAllFormats0_en_ZM(sal_Int16& count, const sal_Unicode*& from, const sal_Unicode*& to)
+{
+ to = replaceTo0;
+ const sal_Unicode* tmp;
+ return getAllFormats0_en_GB(count, from, tmp);
+}
+extern sal_Unicode** SAL_CALL getDateAcceptancePatterns_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getDateAcceptancePatterns_en_ZM(sal_Int16& count)
+{
+ return getDateAcceptancePatterns_en_GB(count);
+}
+extern sal_Unicode** SAL_CALL getCollatorImplementation_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getCollatorImplementation_en_ZM(sal_Int16& count)
+{
+ return getCollatorImplementation_en_GB(count);
+}
+extern sal_Unicode** SAL_CALL getCollationOptions_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getCollationOptions_en_ZM(sal_Int16& count)
+{
+ return getCollationOptions_en_GB(count);
+}
+extern sal_Unicode** SAL_CALL getSearchOptions_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getSearchOptions_en_ZM(sal_Int16& count)
+{
+ return getSearchOptions_en_GB(count);
+}
+extern sal_Unicode** SAL_CALL getIndexAlgorithm_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getIndexAlgorithm_en_ZM(sal_Int16& count)
+{
+ return getIndexAlgorithm_en_GB(count);
+}
+extern sal_Unicode** SAL_CALL getUnicodeScripts_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getUnicodeScripts_en_ZM(sal_Int16& count)
+{
+ return getUnicodeScripts_en_GB(count);
+}
+extern sal_Unicode** SAL_CALL getFollowPageWords_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getFollowPageWords_en_ZM(sal_Int16& count)
+{
+ return getFollowPageWords_en_GB(count);
+}
+extern sal_Unicode** SAL_CALL getAllCalendars_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getAllCalendars_en_ZM(sal_Int16& count)
+{
+ return getAllCalendars_en_GB(count);
+}
+static const sal_Unicode defaultCurrency0[] = { 1 };
+static const sal_Unicode defaultCurrencyUsedInCompatibleFormatCodes0[] = { 1 };
+static const sal_Unicode defaultCurrencyLegacyOnly0[] = { 0 };
+static const sal_Unicode currencyID0[] = { 0x5a, 0x4d, 0x57, 0x0 };
+static const sal_Unicode currencySymbol0[] = { 0x4b, 0x0 };
+static const sal_Unicode bankSymbol0[] = { 0x5a, 0x4d, 0x57, 0x0 };
+static const sal_Unicode currencyName0[] = { 0x4b, 0x77, 0x61, 0x63, 0x68, 0x61, 0x0 };
+static const sal_Unicode currencyDecimalPlaces0[] = { 2 };
+
+static const sal_Int16 currencyCount = 1;
+
+static const sal_Unicode* currencies[] = {
+ currencyID0,
+ currencySymbol0,
+ bankSymbol0,
+ currencyName0,
+ defaultCurrency0,
+ defaultCurrencyUsedInCompatibleFormatCodes0,
+ currencyDecimalPlaces0,
+ defaultCurrencyLegacyOnly0,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getAllCurrencies_en_ZM(sal_Int16& count)
+{
+ count = currencyCount;
+ return (sal_Unicode**)currencies;
+}
+extern sal_Unicode** SAL_CALL getTransliterations_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getTransliterations_en_ZM(sal_Int16& count)
+{
+ return getTransliterations_en_GB(count);
+}
+extern sal_Unicode** SAL_CALL getForbiddenCharacters_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getForbiddenCharacters_en_ZM(sal_Int16& count)
+{
+ return getForbiddenCharacters_en_GB(count);
+}
+extern sal_Unicode** SAL_CALL getBreakIteratorRules_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getBreakIteratorRules_en_ZM(sal_Int16& count)
+{
+ return getBreakIteratorRules_en_GB(count);
+}
+extern sal_Unicode** SAL_CALL getReservedWords_en_GB(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode** SAL_CALL getReservedWords_en_ZM(sal_Int16& count)
+{
+ return getReservedWords_en_GB(count);
+}
+// ---> ContinuousNumbering
+extern const sal_Unicode*** SAL_CALL getContinuousNumberingLevels_en_GB(sal_Int16& nStyles,
+ sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode*** SAL_CALL
+getContinuousNumberingLevels_en_ZM(sal_Int16& nStyles, sal_Int16& nAttributes)
+{
+ return getContinuousNumberingLevels_en_GB(nStyles, nAttributes);
+}
+// ---> OutlineNumbering
+extern const sal_Unicode**** SAL_CALL getOutlineNumberingLevels_en_GB(sal_Int16& nStyles,
+ sal_Int16& nLevels,
+ sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode**** SAL_CALL
+getOutlineNumberingLevels_en_ZM(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes)
+{
+ return getOutlineNumberingLevels_en_GB(nStyles, nLevels, nAttributes);
+}
+} // extern "C"
diff --git a/vcl/workben/localestub/localedata_en_ZW.cxx b/vcl/workben/localestub/localedata_en_ZW.cxx
new file mode 100644
index 000000000..be6797418
--- /dev/null
+++ b/vcl/workben/localestub/localedata_en_ZW.cxx
@@ -0,0 +1,563 @@
+#include <sal/types.h>
+
+
+#include <stdio.h>
+
+extern "C" {
+
+static const sal_Unicode langID[] = {0x65, 0x6e, 0x0};
+static const sal_Unicode langDefaultName[] = {0x45, 0x6e, 0x67, 0x6c, 0x69, 0x73, 0x68, 0x0};
+static const sal_Unicode countryID[] = {0x5a, 0x57, 0x0};
+static const sal_Unicode countryDefaultName[] = {0x5a, 0x69, 0x6d, 0x62, 0x61, 0x62, 0x77, 0x65, 0x0};
+static const sal_Unicode Variant[] = {0x0};
+
+static const sal_Unicode* LCInfoArray[] = {
+ langID,
+ langDefaultName,
+ countryID,
+ countryDefaultName,
+ Variant
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLCInfo_en_ZW(sal_Int16& count)
+{
+ count = 0;
+ return (sal_Unicode**)LCInfoArray;
+}
+
+
+static const sal_Unicode LC_CTYPE_Unoid[] = {0x0};
+static const sal_Unicode dateSeparator[] = {0x2f, 0x0};
+static const sal_Unicode thousandSeparator[] = {0x2c, 0x0};
+static const sal_Unicode decimalSeparator[] = {0x2e, 0x0};
+static const sal_Unicode timeSeparator[] = {0x3a, 0x0};
+static const sal_Unicode time100SecSeparator[] = {0x2e, 0x0};
+static const sal_Unicode listSeparator[] = {0x3b, 0x0};
+static const sal_Unicode LongDateDayOfWeekSeparator[] = {0x20, 0x0};
+static const sal_Unicode LongDateDaySeparator[] = {0x20, 0x0};
+static const sal_Unicode LongDateMonthSeparator[] = {0x20, 0x0};
+static const sal_Unicode LongDateYearSeparator[] = {0x20, 0x0};
+static const sal_Unicode quotationStart[] = {0x2018, 0x0};
+static const sal_Unicode quotationEnd[] = {0x2019, 0x0};
+static const sal_Unicode doubleQuotationStart[] = {0x201c, 0x0};
+static const sal_Unicode doubleQuotationEnd[] = {0x201d, 0x0};
+static const sal_Unicode timeAM[] = {0x41, 0x4d, 0x0};
+static const sal_Unicode timePM[] = {0x50, 0x4d, 0x0};
+static const sal_Unicode measurementSystem[] = {0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x0};
+
+static const sal_Unicode* LCType[] = {
+ LC_CTYPE_Unoid,
+ dateSeparator,
+ thousandSeparator,
+ decimalSeparator,
+ timeSeparator,
+ time100SecSeparator,
+ listSeparator,
+ quotationStart,
+ quotationEnd,
+ doubleQuotationStart,
+ doubleQuotationEnd,
+ timeAM,
+ timePM,
+ measurementSystem,
+ LongDateDayOfWeekSeparator,
+ LongDateDaySeparator,
+ LongDateMonthSeparator,
+ LongDateYearSeparator
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getLocaleItem_en_ZW(sal_Int16& count)
+{
+ count = 0;
+ return (sal_Unicode**)LCType;
+}
+static const sal_Unicode replaceTo0[] = {0x5b, 0x24, 0x5a, 0x24, 0x2d, 0x33, 0x30, 0x30, 0x39, 0x5d, 0x0};
+extern sal_Unicode const * const * SAL_CALL getAllFormats0_en_US(sal_Int16& count, const sal_Unicode*& from, const sal_Unicode*& to);
+SAL_DLLPUBLIC_EXPORT sal_Unicode const * const * SAL_CALL getAllFormats0_en_ZW(sal_Int16& count, const sal_Unicode*& from, const sal_Unicode*& to)
+{
+ to = replaceTo0;
+ const sal_Unicode* tmp;
+ return getAllFormats0_en_US(count, from, tmp);
+}
+extern sal_Unicode ** SAL_CALL getDateAcceptancePatterns_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getDateAcceptancePatterns_en_ZW(sal_Int16& count)
+{
+ return getDateAcceptancePatterns_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getCollatorImplementation_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollatorImplementation_en_ZW(sal_Int16& count)
+{
+ return getCollatorImplementation_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getCollationOptions_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getCollationOptions_en_ZW(sal_Int16& count)
+{
+ return getCollationOptions_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getSearchOptions_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getSearchOptions_en_ZW(sal_Int16& count)
+{
+ return getSearchOptions_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getIndexAlgorithm_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getIndexAlgorithm_en_ZW(sal_Int16& count)
+{
+ return getIndexAlgorithm_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getUnicodeScripts_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getUnicodeScripts_en_ZW(sal_Int16& count)
+{
+ return getUnicodeScripts_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getFollowPageWords_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getFollowPageWords_en_ZW(sal_Int16& count)
+{
+ return getFollowPageWords_en_US(count);
+}
+static const sal_Unicode calendarID0[] = {0x67, 0x72, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x61, 0x6e, 0x0};
+static const sal_Unicode defaultCalendar0[] = {1};
+static const sal_Unicode dayID00[] = {0x73, 0x75, 0x6e, 0x0};
+static const sal_Unicode dayDefaultAbbrvName00[] = {0x53, 0x75, 0x6e, 0x0};
+static const sal_Unicode dayDefaultFullName00[] = {0x53, 0x75, 0x6e, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName00[] = {0x53, 0x0};
+static const sal_Unicode dayID01[] = {0x6d, 0x6f, 0x6e, 0x0};
+static const sal_Unicode dayDefaultAbbrvName01[] = {0x4d, 0x6f, 0x6e, 0x0};
+static const sal_Unicode dayDefaultFullName01[] = {0x4d, 0x6f, 0x6e, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName01[] = {0x4d, 0x0};
+static const sal_Unicode dayID02[] = {0x74, 0x75, 0x65, 0x0};
+static const sal_Unicode dayDefaultAbbrvName02[] = {0x54, 0x75, 0x65, 0x0};
+static const sal_Unicode dayDefaultFullName02[] = {0x54, 0x75, 0x65, 0x73, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName02[] = {0x54, 0x0};
+static const sal_Unicode dayID03[] = {0x77, 0x65, 0x64, 0x0};
+static const sal_Unicode dayDefaultAbbrvName03[] = {0x57, 0x65, 0x64, 0x0};
+static const sal_Unicode dayDefaultFullName03[] = {0x57, 0x65, 0x64, 0x6e, 0x65, 0x73, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName03[] = {0x57, 0x0};
+static const sal_Unicode dayID04[] = {0x74, 0x68, 0x75, 0x0};
+static const sal_Unicode dayDefaultAbbrvName04[] = {0x54, 0x68, 0x75, 0x0};
+static const sal_Unicode dayDefaultFullName04[] = {0x54, 0x68, 0x75, 0x72, 0x73, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName04[] = {0x54, 0x0};
+static const sal_Unicode dayID05[] = {0x66, 0x72, 0x69, 0x0};
+static const sal_Unicode dayDefaultAbbrvName05[] = {0x46, 0x72, 0x69, 0x0};
+static const sal_Unicode dayDefaultFullName05[] = {0x46, 0x72, 0x69, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName05[] = {0x46, 0x0};
+static const sal_Unicode dayID06[] = {0x73, 0x61, 0x74, 0x0};
+static const sal_Unicode dayDefaultAbbrvName06[] = {0x53, 0x61, 0x74, 0x0};
+static const sal_Unicode dayDefaultFullName06[] = {0x53, 0x61, 0x74, 0x75, 0x72, 0x64, 0x61, 0x79, 0x0};
+static const sal_Unicode dayDefaultNarrowName06[] = {0x53, 0x0};
+static const sal_Unicode monthID00[] = {0x6a, 0x61, 0x6e, 0x0};
+static const sal_Unicode monthDefaultAbbrvName00[] = {0x4a, 0x61, 0x6e, 0x0};
+static const sal_Unicode monthDefaultFullName00[] = {0x4a, 0x61, 0x6e, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode monthDefaultNarrowName00[] = {0x4a, 0x0};
+static const sal_Unicode monthID01[] = {0x66, 0x65, 0x62, 0x0};
+static const sal_Unicode monthDefaultAbbrvName01[] = {0x46, 0x65, 0x62, 0x0};
+static const sal_Unicode monthDefaultFullName01[] = {0x46, 0x65, 0x62, 0x72, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode monthDefaultNarrowName01[] = {0x46, 0x0};
+static const sal_Unicode monthID02[] = {0x6d, 0x61, 0x72, 0x0};
+static const sal_Unicode monthDefaultAbbrvName02[] = {0x4d, 0x61, 0x72, 0x0};
+static const sal_Unicode monthDefaultFullName02[] = {0x4d, 0x61, 0x72, 0x63, 0x68, 0x0};
+static const sal_Unicode monthDefaultNarrowName02[] = {0x4d, 0x0};
+static const sal_Unicode monthID03[] = {0x61, 0x70, 0x72, 0x0};
+static const sal_Unicode monthDefaultAbbrvName03[] = {0x41, 0x70, 0x72, 0x0};
+static const sal_Unicode monthDefaultFullName03[] = {0x41, 0x70, 0x72, 0x69, 0x6c, 0x0};
+static const sal_Unicode monthDefaultNarrowName03[] = {0x41, 0x0};
+static const sal_Unicode monthID04[] = {0x6d, 0x61, 0x79, 0x0};
+static const sal_Unicode monthDefaultAbbrvName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode monthDefaultFullName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode monthDefaultNarrowName04[] = {0x4d, 0x0};
+static const sal_Unicode monthID05[] = {0x6a, 0x75, 0x6e, 0x0};
+static const sal_Unicode monthDefaultAbbrvName05[] = {0x4a, 0x75, 0x6e, 0x0};
+static const sal_Unicode monthDefaultFullName05[] = {0x4a, 0x75, 0x6e, 0x65, 0x0};
+static const sal_Unicode monthDefaultNarrowName05[] = {0x4a, 0x0};
+static const sal_Unicode monthID06[] = {0x6a, 0x75, 0x6c, 0x0};
+static const sal_Unicode monthDefaultAbbrvName06[] = {0x4a, 0x75, 0x6c, 0x0};
+static const sal_Unicode monthDefaultFullName06[] = {0x4a, 0x75, 0x6c, 0x79, 0x0};
+static const sal_Unicode monthDefaultNarrowName06[] = {0x4a, 0x0};
+static const sal_Unicode monthID07[] = {0x61, 0x75, 0x67, 0x0};
+static const sal_Unicode monthDefaultAbbrvName07[] = {0x41, 0x75, 0x67, 0x0};
+static const sal_Unicode monthDefaultFullName07[] = {0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x0};
+static const sal_Unicode monthDefaultNarrowName07[] = {0x41, 0x0};
+static const sal_Unicode monthID08[] = {0x73, 0x65, 0x70, 0x0};
+static const sal_Unicode monthDefaultAbbrvName08[] = {0x53, 0x65, 0x70, 0x0};
+static const sal_Unicode monthDefaultFullName08[] = {0x53, 0x65, 0x70, 0x74, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName08[] = {0x53, 0x0};
+static const sal_Unicode monthID09[] = {0x6f, 0x63, 0x74, 0x0};
+static const sal_Unicode monthDefaultAbbrvName09[] = {0x4f, 0x63, 0x74, 0x0};
+static const sal_Unicode monthDefaultFullName09[] = {0x4f, 0x63, 0x74, 0x6f, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName09[] = {0x4f, 0x0};
+static const sal_Unicode monthID010[] = {0x6e, 0x6f, 0x76, 0x0};
+static const sal_Unicode monthDefaultAbbrvName010[] = {0x4e, 0x6f, 0x76, 0x0};
+static const sal_Unicode monthDefaultFullName010[] = {0x4e, 0x6f, 0x76, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName010[] = {0x4e, 0x0};
+static const sal_Unicode monthID011[] = {0x64, 0x65, 0x63, 0x0};
+static const sal_Unicode monthDefaultAbbrvName011[] = {0x44, 0x65, 0x63, 0x0};
+static const sal_Unicode monthDefaultFullName011[] = {0x44, 0x65, 0x63, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode monthDefaultNarrowName011[] = {0x44, 0x0};
+static const sal_Unicode genitiveMonthID00[] = {0x6a, 0x61, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName00[] = {0x4a, 0x61, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName00[] = {0x4a, 0x61, 0x6e, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName00[] = {0x4a, 0x0};
+static const sal_Unicode genitiveMonthID01[] = {0x66, 0x65, 0x62, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName01[] = {0x46, 0x65, 0x62, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName01[] = {0x46, 0x65, 0x62, 0x72, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName01[] = {0x46, 0x0};
+static const sal_Unicode genitiveMonthID02[] = {0x6d, 0x61, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName02[] = {0x4d, 0x61, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName02[] = {0x4d, 0x61, 0x72, 0x63, 0x68, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName02[] = {0x4d, 0x0};
+static const sal_Unicode genitiveMonthID03[] = {0x61, 0x70, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName03[] = {0x41, 0x70, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName03[] = {0x41, 0x70, 0x72, 0x69, 0x6c, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName03[] = {0x41, 0x0};
+static const sal_Unicode genitiveMonthID04[] = {0x6d, 0x61, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName04[] = {0x4d, 0x0};
+static const sal_Unicode genitiveMonthID05[] = {0x6a, 0x75, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName05[] = {0x4a, 0x75, 0x6e, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName05[] = {0x4a, 0x75, 0x6e, 0x65, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName05[] = {0x4a, 0x0};
+static const sal_Unicode genitiveMonthID06[] = {0x6a, 0x75, 0x6c, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName06[] = {0x4a, 0x75, 0x6c, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName06[] = {0x4a, 0x75, 0x6c, 0x79, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName06[] = {0x4a, 0x0};
+static const sal_Unicode genitiveMonthID07[] = {0x61, 0x75, 0x67, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName07[] = {0x41, 0x75, 0x67, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName07[] = {0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName07[] = {0x41, 0x0};
+static const sal_Unicode genitiveMonthID08[] = {0x73, 0x65, 0x70, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName08[] = {0x53, 0x65, 0x70, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName08[] = {0x53, 0x65, 0x70, 0x74, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName08[] = {0x53, 0x0};
+static const sal_Unicode genitiveMonthID09[] = {0x6f, 0x63, 0x74, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName09[] = {0x4f, 0x63, 0x74, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName09[] = {0x4f, 0x63, 0x74, 0x6f, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName09[] = {0x4f, 0x0};
+static const sal_Unicode genitiveMonthID010[] = {0x6e, 0x6f, 0x76, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName010[] = {0x4e, 0x6f, 0x76, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName010[] = {0x4e, 0x6f, 0x76, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName010[] = {0x4e, 0x0};
+static const sal_Unicode genitiveMonthID011[] = {0x64, 0x65, 0x63, 0x0};
+static const sal_Unicode genitiveMonthDefaultAbbrvName011[] = {0x44, 0x65, 0x63, 0x0};
+static const sal_Unicode genitiveMonthDefaultFullName011[] = {0x44, 0x65, 0x63, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode genitiveMonthDefaultNarrowName011[] = {0x44, 0x0};
+static const sal_Unicode partitiveMonthID00[] = {0x6a, 0x61, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName00[] = {0x4a, 0x61, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName00[] = {0x4a, 0x61, 0x6e, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName00[] = {0x4a, 0x0};
+static const sal_Unicode partitiveMonthID01[] = {0x66, 0x65, 0x62, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName01[] = {0x46, 0x65, 0x62, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName01[] = {0x46, 0x65, 0x62, 0x72, 0x75, 0x61, 0x72, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName01[] = {0x46, 0x0};
+static const sal_Unicode partitiveMonthID02[] = {0x6d, 0x61, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName02[] = {0x4d, 0x61, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName02[] = {0x4d, 0x61, 0x72, 0x63, 0x68, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName02[] = {0x4d, 0x0};
+static const sal_Unicode partitiveMonthID03[] = {0x61, 0x70, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName03[] = {0x41, 0x70, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName03[] = {0x41, 0x70, 0x72, 0x69, 0x6c, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName03[] = {0x41, 0x0};
+static const sal_Unicode partitiveMonthID04[] = {0x6d, 0x61, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName04[] = {0x4d, 0x61, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName04[] = {0x4d, 0x0};
+static const sal_Unicode partitiveMonthID05[] = {0x6a, 0x75, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName05[] = {0x4a, 0x75, 0x6e, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName05[] = {0x4a, 0x75, 0x6e, 0x65, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName05[] = {0x4a, 0x0};
+static const sal_Unicode partitiveMonthID06[] = {0x6a, 0x75, 0x6c, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName06[] = {0x4a, 0x75, 0x6c, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName06[] = {0x4a, 0x75, 0x6c, 0x79, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName06[] = {0x4a, 0x0};
+static const sal_Unicode partitiveMonthID07[] = {0x61, 0x75, 0x67, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName07[] = {0x41, 0x75, 0x67, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName07[] = {0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName07[] = {0x41, 0x0};
+static const sal_Unicode partitiveMonthID08[] = {0x73, 0x65, 0x70, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName08[] = {0x53, 0x65, 0x70, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName08[] = {0x53, 0x65, 0x70, 0x74, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName08[] = {0x53, 0x0};
+static const sal_Unicode partitiveMonthID09[] = {0x6f, 0x63, 0x74, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName09[] = {0x4f, 0x63, 0x74, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName09[] = {0x4f, 0x63, 0x74, 0x6f, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName09[] = {0x4f, 0x0};
+static const sal_Unicode partitiveMonthID010[] = {0x6e, 0x6f, 0x76, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName010[] = {0x4e, 0x6f, 0x76, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName010[] = {0x4e, 0x6f, 0x76, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName010[] = {0x4e, 0x0};
+static const sal_Unicode partitiveMonthID011[] = {0x64, 0x65, 0x63, 0x0};
+static const sal_Unicode partitiveMonthDefaultAbbrvName011[] = {0x44, 0x65, 0x63, 0x0};
+static const sal_Unicode partitiveMonthDefaultFullName011[] = {0x44, 0x65, 0x63, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x0};
+static const sal_Unicode partitiveMonthDefaultNarrowName011[] = {0x44, 0x0};
+static const sal_Unicode eraID00[] = {0x62, 0x63, 0x0};
+
+static const sal_Unicode eraDefaultAbbrvName00[] = {0x42, 0x43, 0x0};
+static const sal_Unicode eraDefaultFullName00[] = {0x42, 0x43, 0x0};
+static const sal_Unicode eraID01[] = {0x61, 0x64, 0x0};
+
+static const sal_Unicode eraDefaultAbbrvName01[] = {0x41, 0x44, 0x0};
+static const sal_Unicode eraDefaultFullName01[] = {0x41, 0x44, 0x0};
+static const sal_Unicode startDayOfWeek0[] = {0x73, 0x75, 0x6e, 0x0};
+static const sal_Unicode minimalDaysInFirstWeek0[] = {1};
+static const sal_Int16 calendarsCount = 1;
+
+static const sal_Unicode nbOfDays[] = {7};
+static const sal_Unicode nbOfMonths[] = {12};
+static const sal_Unicode nbOfGenitiveMonths[] = {12};
+static const sal_Unicode nbOfPartitiveMonths[] = {12};
+static const sal_Unicode nbOfEras[] = {2};
+static const sal_Unicode* calendars[] = {
+ nbOfDays,
+ nbOfMonths,
+ nbOfGenitiveMonths,
+ nbOfPartitiveMonths,
+ nbOfEras,
+ calendarID0,
+ defaultCalendar0,
+ dayID00,
+ dayDefaultAbbrvName00,
+ dayDefaultFullName00,
+ dayDefaultNarrowName00,
+ dayID01,
+ dayDefaultAbbrvName01,
+ dayDefaultFullName01,
+ dayDefaultNarrowName01,
+ dayID02,
+ dayDefaultAbbrvName02,
+ dayDefaultFullName02,
+ dayDefaultNarrowName02,
+ dayID03,
+ dayDefaultAbbrvName03,
+ dayDefaultFullName03,
+ dayDefaultNarrowName03,
+ dayID04,
+ dayDefaultAbbrvName04,
+ dayDefaultFullName04,
+ dayDefaultNarrowName04,
+ dayID05,
+ dayDefaultAbbrvName05,
+ dayDefaultFullName05,
+ dayDefaultNarrowName05,
+ dayID06,
+ dayDefaultAbbrvName06,
+ dayDefaultFullName06,
+ dayDefaultNarrowName06,
+ monthID00,
+ monthDefaultAbbrvName00,
+ monthDefaultFullName00,
+ monthDefaultNarrowName00,
+ monthID01,
+ monthDefaultAbbrvName01,
+ monthDefaultFullName01,
+ monthDefaultNarrowName01,
+ monthID02,
+ monthDefaultAbbrvName02,
+ monthDefaultFullName02,
+ monthDefaultNarrowName02,
+ monthID03,
+ monthDefaultAbbrvName03,
+ monthDefaultFullName03,
+ monthDefaultNarrowName03,
+ monthID04,
+ monthDefaultAbbrvName04,
+ monthDefaultFullName04,
+ monthDefaultNarrowName04,
+ monthID05,
+ monthDefaultAbbrvName05,
+ monthDefaultFullName05,
+ monthDefaultNarrowName05,
+ monthID06,
+ monthDefaultAbbrvName06,
+ monthDefaultFullName06,
+ monthDefaultNarrowName06,
+ monthID07,
+ monthDefaultAbbrvName07,
+ monthDefaultFullName07,
+ monthDefaultNarrowName07,
+ monthID08,
+ monthDefaultAbbrvName08,
+ monthDefaultFullName08,
+ monthDefaultNarrowName08,
+ monthID09,
+ monthDefaultAbbrvName09,
+ monthDefaultFullName09,
+ monthDefaultNarrowName09,
+ monthID010,
+ monthDefaultAbbrvName010,
+ monthDefaultFullName010,
+ monthDefaultNarrowName010,
+ monthID011,
+ monthDefaultAbbrvName011,
+ monthDefaultFullName011,
+ monthDefaultNarrowName011,
+ genitiveMonthID00,
+ genitiveMonthDefaultAbbrvName00,
+ genitiveMonthDefaultFullName00,
+ genitiveMonthDefaultNarrowName00,
+ genitiveMonthID01,
+ genitiveMonthDefaultAbbrvName01,
+ genitiveMonthDefaultFullName01,
+ genitiveMonthDefaultNarrowName01,
+ genitiveMonthID02,
+ genitiveMonthDefaultAbbrvName02,
+ genitiveMonthDefaultFullName02,
+ genitiveMonthDefaultNarrowName02,
+ genitiveMonthID03,
+ genitiveMonthDefaultAbbrvName03,
+ genitiveMonthDefaultFullName03,
+ genitiveMonthDefaultNarrowName03,
+ genitiveMonthID04,
+ genitiveMonthDefaultAbbrvName04,
+ genitiveMonthDefaultFullName04,
+ genitiveMonthDefaultNarrowName04,
+ genitiveMonthID05,
+ genitiveMonthDefaultAbbrvName05,
+ genitiveMonthDefaultFullName05,
+ genitiveMonthDefaultNarrowName05,
+ genitiveMonthID06,
+ genitiveMonthDefaultAbbrvName06,
+ genitiveMonthDefaultFullName06,
+ genitiveMonthDefaultNarrowName06,
+ genitiveMonthID07,
+ genitiveMonthDefaultAbbrvName07,
+ genitiveMonthDefaultFullName07,
+ genitiveMonthDefaultNarrowName07,
+ genitiveMonthID08,
+ genitiveMonthDefaultAbbrvName08,
+ genitiveMonthDefaultFullName08,
+ genitiveMonthDefaultNarrowName08,
+ genitiveMonthID09,
+ genitiveMonthDefaultAbbrvName09,
+ genitiveMonthDefaultFullName09,
+ genitiveMonthDefaultNarrowName09,
+ genitiveMonthID010,
+ genitiveMonthDefaultAbbrvName010,
+ genitiveMonthDefaultFullName010,
+ genitiveMonthDefaultNarrowName010,
+ genitiveMonthID011,
+ genitiveMonthDefaultAbbrvName011,
+ genitiveMonthDefaultFullName011,
+ genitiveMonthDefaultNarrowName011,
+ partitiveMonthID00,
+ partitiveMonthDefaultAbbrvName00,
+ partitiveMonthDefaultFullName00,
+ partitiveMonthDefaultNarrowName00,
+ partitiveMonthID01,
+ partitiveMonthDefaultAbbrvName01,
+ partitiveMonthDefaultFullName01,
+ partitiveMonthDefaultNarrowName01,
+ partitiveMonthID02,
+ partitiveMonthDefaultAbbrvName02,
+ partitiveMonthDefaultFullName02,
+ partitiveMonthDefaultNarrowName02,
+ partitiveMonthID03,
+ partitiveMonthDefaultAbbrvName03,
+ partitiveMonthDefaultFullName03,
+ partitiveMonthDefaultNarrowName03,
+ partitiveMonthID04,
+ partitiveMonthDefaultAbbrvName04,
+ partitiveMonthDefaultFullName04,
+ partitiveMonthDefaultNarrowName04,
+ partitiveMonthID05,
+ partitiveMonthDefaultAbbrvName05,
+ partitiveMonthDefaultFullName05,
+ partitiveMonthDefaultNarrowName05,
+ partitiveMonthID06,
+ partitiveMonthDefaultAbbrvName06,
+ partitiveMonthDefaultFullName06,
+ partitiveMonthDefaultNarrowName06,
+ partitiveMonthID07,
+ partitiveMonthDefaultAbbrvName07,
+ partitiveMonthDefaultFullName07,
+ partitiveMonthDefaultNarrowName07,
+ partitiveMonthID08,
+ partitiveMonthDefaultAbbrvName08,
+ partitiveMonthDefaultFullName08,
+ partitiveMonthDefaultNarrowName08,
+ partitiveMonthID09,
+ partitiveMonthDefaultAbbrvName09,
+ partitiveMonthDefaultFullName09,
+ partitiveMonthDefaultNarrowName09,
+ partitiveMonthID010,
+ partitiveMonthDefaultAbbrvName010,
+ partitiveMonthDefaultFullName010,
+ partitiveMonthDefaultNarrowName010,
+ partitiveMonthID011,
+ partitiveMonthDefaultAbbrvName011,
+ partitiveMonthDefaultFullName011,
+ partitiveMonthDefaultNarrowName011,
+ eraID00,
+ eraDefaultAbbrvName00,
+ eraDefaultFullName00,
+ eraID01,
+ eraDefaultAbbrvName01,
+ eraDefaultFullName01,
+ startDayOfWeek0,
+ minimalDaysInFirstWeek0,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCalendars_en_ZW(sal_Int16& count)
+{
+ count = calendarsCount;
+ return (sal_Unicode**)calendars;
+}
+static const sal_Unicode defaultCurrency0[] = {1};
+static const sal_Unicode defaultCurrencyUsedInCompatibleFormatCodes0[] = {1};
+static const sal_Unicode defaultCurrencyLegacyOnly0[] = {0};
+static const sal_Unicode currencyID0[] = {0x5a, 0x57, 0x44, 0x0};
+static const sal_Unicode currencySymbol0[] = {0x5a, 0x24, 0x0};
+static const sal_Unicode bankSymbol0[] = {0x5a, 0x57, 0x44, 0x0};
+static const sal_Unicode currencyName0[] = {0x5a, 0x69, 0x6d, 0x62, 0x61, 0x62, 0x77, 0x65, 0x61, 0x6e, 0x20, 0x44, 0x6f, 0x6c, 0x6c, 0x61, 0x72, 0x0};
+static const sal_Unicode currencyDecimalPlaces0[] = {2};
+
+static const sal_Int16 currencyCount = 1;
+
+static const sal_Unicode* currencies[] = {
+ currencyID0,
+ currencySymbol0,
+ bankSymbol0,
+ currencyName0,
+ defaultCurrency0,
+ defaultCurrencyUsedInCompatibleFormatCodes0,
+ currencyDecimalPlaces0,
+ defaultCurrencyLegacyOnly0,
+};
+
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getAllCurrencies_en_ZW(sal_Int16& count)
+{
+ count = currencyCount;
+ return (sal_Unicode**)currencies;
+}
+extern sal_Unicode ** SAL_CALL getTransliterations_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getTransliterations_en_ZW(sal_Int16& count)
+{
+ return getTransliterations_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getForbiddenCharacters_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getForbiddenCharacters_en_ZW(sal_Int16& count)
+{
+ return getForbiddenCharacters_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getBreakIteratorRules_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getBreakIteratorRules_en_ZW(sal_Int16& count)
+{
+ return getBreakIteratorRules_en_US(count);
+}
+extern sal_Unicode ** SAL_CALL getReservedWords_en_US(sal_Int16& count);
+SAL_DLLPUBLIC_EXPORT sal_Unicode ** SAL_CALL getReservedWords_en_ZW(sal_Int16& count)
+{
+ return getReservedWords_en_US(count);
+}
+// ---> ContinuousNumbering
+extern const sal_Unicode *** SAL_CALL getContinuousNumberingLevels_en_US(sal_Int16& nStyles, sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode *** SAL_CALL getContinuousNumberingLevels_en_ZW(sal_Int16& nStyles, sal_Int16& nAttributes)
+{
+ return getContinuousNumberingLevels_en_US(nStyles, nAttributes);
+}
+// ---> OutlineNumbering
+extern const sal_Unicode **** SAL_CALL getOutlineNumberingLevels_en_US(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes);
+SAL_DLLPUBLIC_EXPORT const sal_Unicode **** SAL_CALL getOutlineNumberingLevels_en_ZW(sal_Int16& nStyles, sal_Int16& nLevels, sal_Int16& nAttributes)
+{
+ return getOutlineNumberingLevels_en_US(nStyles, nLevels, nAttributes);
+}
+} // extern "C"
+
diff --git a/vcl/workben/localestub/localestub.cxx b/vcl/workben/localestub/localestub.cxx
new file mode 100644
index 000000000..66942f1c6
--- /dev/null
+++ b/vcl/workben/localestub/localestub.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 <sal/types.h>
+
+extern "C" const sal_Unicode* getSTC_CharData_T2S() { return nullptr; }
+extern "C" const sal_uInt16* getSTC_CharIndex_T2S() { return nullptr; }
+extern "C" const sal_Unicode* getSTC_CharData_S2V() { return nullptr; }
+extern "C" const sal_uInt16* getSTC_CharIndex_S2V() { return nullptr; }
+extern "C" const sal_Unicode* getSTC_CharData_S2T() { return nullptr; }
+extern "C" const sal_uInt16* getSTC_CharIndex_S2T() { return nullptr; }
+extern "C" const sal_Unicode *getSTC_WordData(sal_Int32&) { return nullptr; }
+extern "C" const sal_uInt16 *getSTC_WordIndex_T2S(sal_Int32&) { return nullptr; }
+extern "C" const sal_uInt16 *getSTC_WordEntry_T2S() { return nullptr; }
+extern "C" const sal_uInt16 *getSTC_WordIndex_S2T(sal_Int32&) { return nullptr; }
+extern "C" const sal_uInt16 *getSTC_WordEntry_S2T() { return nullptr; }
+extern "C" sal_uInt16** get_zh_zhuyin() { return nullptr; }
+extern "C" sal_uInt16** get_zh_pinyin() { return nullptr; }
+extern "C" const sal_Unicode* getHangul2HanjaData() { return nullptr; }
+extern "C" const void* getHangul2HanjaIndex() { return nullptr; }
+extern "C" sal_Int16 getHangul2HanjaIndexCount() { return 0; }
+extern "C" const sal_uInt16* getHanja2HangulIndex() { return nullptr; }
+extern "C" const sal_Unicode* getHanja2HangulData() { return 0; }
+extern "C" sal_uInt16** get_indexdata_ko_dict(sal_Int16*) { return nullptr; }
+extern "C" sal_uInt16** get_indexdata_zh_TW_radical(sal_Int16*) { return nullptr; }
+extern "C" sal_uInt16** get_indexdata_zh_TW_stroke(sal_Int16*) { return nullptr; }
+extern "C" sal_uInt16** get_indexdata_zh_radical(sal_Int16*) { return nullptr; }
+extern "C" sal_uInt16** get_indexdata_zh_pinyin(sal_Int16*) { return nullptr; }
+extern "C" sal_uInt16** get_indexdata_zh_stroke(sal_Int16*) { return nullptr; }
+extern "C" sal_uInt16** get_indexdata_zh_zhuyin(sal_Int16*) { return nullptr; }
+extern "C" sal_uInt16** get_ko_phonetic(sal_Int16*) { return nullptr; }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/lwpfuzzer.cxx b/vcl/workben/lwpfuzzer.cxx
new file mode 100644
index 000000000..fc0704542
--- /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 000000000..678d526b1
--- /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 000000000..fd5064329
--- /dev/null
+++ b/vcl/workben/metfuzzer.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 <tools/stream.hxx>
+#include <vcl/FilterConfigItem.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 * );
+}
+
+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 },
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*)
+{
+ return nullptr;
+}
+
+extern "C" bool imeGraphicImport(SvStream& rStream, Graphic& rGraphic, FilterConfigItem* pConfigItem);
+
+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)imeGraphicImport(aStream, aGraphic, nullptr);
+ 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 000000000..84ac4eb4e
--- /dev/null
+++ b/vcl/workben/metfuzzer.options
@@ -0,0 +1,4 @@
+[libfuzzer]
+max_len = 49152
+[env]
+AFL_DRIVER_DONT_DEFER=1
diff --git a/vcl/workben/mmlfuzzer.cxx b/vcl/workben/mmlfuzzer.cxx
new file mode 100644
index 000000000..8811c2743
--- /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 000000000..120b6953b
--- /dev/null
+++ b/vcl/workben/mmlfuzzer.options
@@ -0,0 +1,5 @@
+[libfuzzer]
+max_len = 65536
+dict = xml.dict
+[env]
+AFL_DRIVER_DONT_DEFER=1
diff --git a/vcl/workben/mtfdemo.cxx b/vcl/workben/mtfdemo.cxx
new file mode 100644
index 000000000..ea987186c
--- /dev/null
+++ b/vcl/workben/mtfdemo.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/.
+ */
+
+#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 <vcl/vclmain.hxx>
+#include <vcl/layout.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/wmf.hxx>
+
+#include <tools/diagnose_ex.h>
+#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 <cstdlib>
+
+using namespace css;
+
+namespace {
+
+class DemoMtfWin : public WorkWindow
+{
+ GDIMetaFile maMtf;
+
+public:
+ explicit DemoMtfWin(const OUString& rFileName)
+ : WorkWindow(nullptr, WB_APP | WB_STDWORK)
+ {
+ SvFileStream aFileStream(rFileName, StreamMode::READ);
+
+ if (aFileStream.IsOpen())
+ {
+ ReadWindowMetafile(aFileStream, maMtf);
+ }
+ else
+ {
+ Application::Abort("Can't read metafile");
+ }
+ }
+
+ virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) override;
+};
+
+}
+
+void DemoMtfWin::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ maMtf.Play(this, maMtf.GetActionSize());
+
+ WorkWindow::Paint(rRenderContext, rRect);
+}
+
+namespace {
+
+class DemoMtfApp : public Application
+{
+ VclPtr<DemoMtfWin> mpWin;
+ OUString maFileName;
+
+ static void showHelp()
+ {
+ fprintf(stderr, "Usage: mtfdemo --help | FILE\n");
+ fprintf(stderr, "A VCL test app that displays Windows metafiles\n");
+ 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();
+ }
+ 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
+ {
+ try
+ {
+ const sal_uInt16 nCmdParams = GetCommandLineParamCount();
+
+ if (nCmdParams == 0)
+ showHelp();
+ else
+ {
+ OUString aArg = GetCommandLineParam(0);
+
+ if (aArg == "--help" || aArg == "-h")
+ showHelp();
+ else
+ maFileName = aArg;
+ }
+
+ 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);
+ }
+ catch (const uno::Exception &e)
+ {
+ Application::Abort("Bootstrap exception " + e.Message);
+ }
+ }
+
+ void DeInit() override
+ {
+ uno::Reference< lang::XComponent >(
+ comphelper::getProcessComponentContext(),
+ uno::UNO_QUERY_THROW)-> dispose();
+ ::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 000000000..8ed271ced
--- /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 000000000..9e9bf3455
--- /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 000000000..4d67ab57e
--- /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 000000000..678d526b1
--- /dev/null
+++ b/vcl/workben/olefuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 65536
diff --git a/vcl/workben/pcdfuzzer.cxx b/vcl/workben/pcdfuzzer.cxx
new file mode 100644
index 000000000..ccd036753
--- /dev/null
+++ b/vcl/workben/pcdfuzzer.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 <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 icdGraphicImport(SvStream& rStream, Graphic& rGraphic, FilterConfigItem* pConfigItem);
+
+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)icdGraphicImport(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 000000000..678d526b1
--- /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 000000000..a916920c4
--- /dev/null
+++ b/vcl/workben/pctfuzzer.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 <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 iptGraphicImport(SvStream& rStream, Graphic& rGraphic, FilterConfigItem* pConfigItem);
+
+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)iptGraphicImport(aStream, aGraphic, nullptr);
+ 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 000000000..678d526b1
--- /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 000000000..e2b72fb7e
--- /dev/null
+++ b/vcl/workben/pcxfuzzer.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 <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 ipxGraphicImport(SvStream& rStream, Graphic& rGraphic, FilterConfigItem* pConfigItem);
+
+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)ipxGraphicImport(aStream, aGraphic, nullptr);
+ 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 000000000..678d526b1
--- /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 000000000..2a3357830
--- /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/pngread.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::PNGReader 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 000000000..d62b77341
--- /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 000000000..854d457c5
--- /dev/null
+++ b/vcl/workben/ppmfuzzer.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 <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 ipbGraphicImport(SvStream& rStream, Graphic& rGraphic, FilterConfigItem* pConfigItem);
+
+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)ipbGraphicImport(aStream, aGraphic, nullptr);
+ 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 000000000..678d526b1
--- /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 000000000..d7f6b73f9
--- /dev/null
+++ b/vcl/workben/pptfuzzer.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/.
+ */
+
+#include <tools/stream.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+extern "C" {
+void * emfio_component_getFactory( const char* , void* , void* );
+void * sd_component_getFactory( const char* , void* , void* );
+void * i18npool_component_getFactory( const char* , void* , void* );
+void * ucb_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 * unoxml_component_getFactory( const char* , 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 * );
+}
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { "libemfiolo.a", emfio_component_getFactory },
+ { "libsdlo.a", sd_component_getFactory },
+ { "libunoxmllo.a", unoxml_component_getFactory },
+ { "libi18npoollo.a", i18npool_component_getFactory },
+ { "libucb1.a", ucb_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 },
+ { 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);
+
+ 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 000000000..678d526b1
--- /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 000000000..976b53d9b
--- /dev/null
+++ b/vcl/workben/pptxfuzzer.cxx
@@ -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/.
+ */
+
+#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" 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 000000000..db9a3e8c6
--- /dev/null
+++ b/vcl/workben/pptxfuzzer.options
@@ -0,0 +1,4 @@
+[libfuzzer]
+max_len = 98304
+[env]
+AFL_DRIVER_DONT_DEFER=1
diff --git a/vcl/workben/psdfuzzer.cxx b/vcl/workben/psdfuzzer.cxx
new file mode 100644
index 000000000..a1bb1823d
--- /dev/null
+++ b/vcl/workben/psdfuzzer.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 <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 ipdGraphicImport(SvStream& rStream, Graphic& rGraphic, FilterConfigItem* pConfigItem);
+
+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)ipdGraphicImport(aStream, aGraphic, nullptr);
+ 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 000000000..678d526b1
--- /dev/null
+++ b/vcl/workben/psdfuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 65536
diff --git a/vcl/workben/qpwfuzzer.cxx b/vcl/workben/qpwfuzzer.cxx
new file mode 100644
index 000000000..0b1fe6362
--- /dev/null
+++ b/vcl/workben/qpwfuzzer.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 * emfio_component_getFactory( const char* , void* , void* );
+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 * );
+}
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { "libemfiolo.a", emfio_component_getFactory },
+ { "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 },
+ { 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 000000000..d851ad58c
--- /dev/null
+++ b/vcl/workben/qpwfuzzer.options
@@ -0,0 +1,4 @@
+[libfuzzer]
+max_len = 65536
+[env]
+AFL_DRIVER_DONT_DEFER=1
diff --git a/vcl/workben/rasfuzzer.cxx b/vcl/workben/rasfuzzer.cxx
new file mode 100644
index 000000000..734180b59
--- /dev/null
+++ b/vcl/workben/rasfuzzer.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 <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 iraGraphicImport(SvStream& rStream, Graphic& rGraphic, FilterConfigItem* pConfigItem);
+
+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)iraGraphicImport(aStream, aGraphic, nullptr);
+ 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 000000000..678d526b1
--- /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 000000000..5f2ac75e8
--- /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 * emfio_component_getFactory( const char* , void* , void* );
+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 * );
+}
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { "libemfiolo.a", emfio_component_getFactory },
+ { "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 },
+ { 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 000000000..d851ad58c
--- /dev/null
+++ b/vcl/workben/rtffuzzer.options
@@ -0,0 +1,4 @@
+[libfuzzer]
+max_len = 65536
+[env]
+AFL_DRIVER_DONT_DEFER=1
diff --git a/vcl/workben/scrtffuzzer.cxx b/vcl/workben/scrtffuzzer.cxx
new file mode 100644
index 000000000..779519f34
--- /dev/null
+++ b/vcl/workben/scrtffuzzer.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" {
+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*);
+}
+
+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 },
+ { 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 000000000..d851ad58c
--- /dev/null
+++ b/vcl/workben/scrtffuzzer.options
@@ -0,0 +1,4 @@
+[libfuzzer]
+max_len = 65536
+[env]
+AFL_DRIVER_DONT_DEFER=1
diff --git a/vcl/workben/sftfuzzer.cxx b/vcl/workben/sftfuzzer.cxx
new file mode 100644
index 000000000..fc951ddc1
--- /dev/null
+++ b/vcl/workben/sftfuzzer.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/font.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)
+{
+ (void)vcl::Font::identifyFont(data, size);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/sftfuzzer.options b/vcl/workben/sftfuzzer.options
new file mode 100644
index 000000000..678d526b1
--- /dev/null
+++ b/vcl/workben/sftfuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 65536
diff --git a/vcl/workben/slkfuzzer.cxx b/vcl/workben/slkfuzzer.cxx
new file mode 100644
index 000000000..9b8463bdc
--- /dev/null
+++ b/vcl/workben/slkfuzzer.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 * emfio_component_getFactory( const char* , void* , void* );
+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 * );
+}
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { "libemfiolo.a", emfio_component_getFactory },
+ { "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 },
+ { 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 000000000..d851ad58c
--- /dev/null
+++ b/vcl/workben/slkfuzzer.options
@@ -0,0 +1,4 @@
+[libfuzzer]
+max_len = 65536
+[env]
+AFL_DRIVER_DONT_DEFER=1
diff --git a/vcl/workben/svdem.cxx b/vcl/workben/svdem.cxx
new file mode 100644
index 000000000..399a3683f
--- /dev/null
+++ b/vcl/workben/svdem.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 <sal/main.h>
+#include <sal/log.hxx>
+#include <tools/diagnose_ex.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 <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 );
+
+ 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
+{
+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/svmfuzzer.cxx b/vcl/workben/svmfuzzer.cxx
new file mode 100644
index 000000000..5ef7366c1
--- /dev/null
+++ b/vcl/workben/svmfuzzer.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/gdimtf.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);
+ GDIMetaFile aGDIMetaFile;
+ ReadGDIMetaFile(aStream, aGDIMetaFile);
+ 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 000000000..678d526b1
--- /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 000000000..5d7cf8b9a
--- /dev/null
+++ b/vcl/workben/svpclient.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/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/button.hxx>
+#include <vcl/lstbox.hxx>
+#include <vcl/fixed.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <vcl/graph.hxx>
+#include <tools/diagnose_ex.h>
+#include <tools/extendapplicationenvironment.hxx>
+#include <tools/stream.hxx>
+
+#include <rtl/strbuf.hxx>
+#include <rtl/ustrbuf.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( const OString& 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( const OString& rList )
+{
+ sal_Int32 nTokenPos = 0;
+ OUString aElementType;
+ m_aSvpBitmaps->Clear();
+ while( nTokenPos >= 0 )
+ {
+ OString aLine = rList.getToken( 0, '\n', nTokenPos );
+ if( ! aLine.getLength() || *aLine.getStr() == '#' )
+ continue;
+
+ if( aLine.startsWith( "ElementType: " ) )
+ aElementType = OStringToOUString( aLine.copy( 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" ) );
+}
+
+IMPL_STATIC_LINK_NOARG( MyWin, QuitHdl, Button*, void)
+{
+ processCommand( "quit" );
+}
+
+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.copy( 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, OUString("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 000000000..06937d225
--- /dev/null
+++ b/vcl/workben/svptest.cxx
@@ -0,0 +1,328 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <tools/diagnose_ex.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 <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/bitmapaccess.hxx>
+#include <vcl/metric.hxx>
+#include <vcl/vclptr.hxx>
+#include <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 ), 32 )
+{
+ // prepare an alpha mask
+ BitmapWriteAccess* pAcc = m_aBitmap.AcquireWriteAccess();
+ for( int nX = 0; nX < 256; nX++ )
+ {
+ for( int nY = 0; nY < 256; nY++ )
+ {
+ double fRed = 255.0-1.5*sqrt(static_cast<double>(nX*nX+nY*nY));
+ if( fRed < 0.0 )
+ fRed = 0.0;
+ double fGreen = 255.0-1.5*sqrt(static_cast<double>((255-nX)*(255-nX)+nY*nY));
+ if( fGreen < 0.0 )
+ fGreen = 0.0;
+ double fBlue = 255.0-1.5*sqrt(static_cast<double>((128-nX)*(128-nX)+(255-nY)*(255-nY)));
+ if( fBlue < 0.0 )
+ fBlue = 0.0;
+ pAcc->SetPixel( nY, nX, BitmapColor( sal_uInt8(fRed), sal_uInt8(fGreen), sal_uInt8(fBlue) ) );
+ }
+ }
+ Bitmap::ReleaseAccess( pAcc );
+}
+
+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.GetDevFontCount();
+ const int nFontSamples = (nFontCount < 15) ? nFontCount : 15;
+ for (int i = 0; i < nFontSamples; ++i)
+ {
+
+ FontMetric aFont = rRenderContext.GetDevFont((i * nFontCount) / nFontSamples);
+ aFont.SetFontHeight(400 + (i % 7) * 100);
+ aFont.SetOrientation(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));
+
+ OUString aPrintText = "SVP test program";
+
+ rRenderContext.DrawText(tools::Rectangle(Point((aPaperSize.Width() - 4000) / 2, 2000),
+ Size(aPaperSize.Width() - 2100, aPaperSize.Height() - 4000)),
+ aPrintText,
+ DrawTextFlags::MultiLine);
+ }
+
+ rRenderContext.SetFillColor();
+ 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(GradientStyle::Linear, aBlack, aWhite);
+ aGradient.SetAngle(900);
+ 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 000000000..47ba63b71
--- /dev/null
+++ b/vcl/workben/tgafuzzer.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 <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 itgGraphicImport(SvStream& rStream, Graphic& rGraphic, FilterConfigItem* pConfigItem);
+
+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)itgGraphicImport(aStream, aGraphic, nullptr);
+ 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 000000000..678d526b1
--- /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 000000000..2ca83d7b0
--- /dev/null
+++ b/vcl/workben/tiffuzzer.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"
+
+#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 itiGraphicImport(SvStream& rStream, Graphic& rGraphic, FilterConfigItem* pConfigItem);
+
+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)itiGraphicImport(aStream, aGraphic, nullptr);
+ }
+ 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 000000000..f0ed890b6
--- /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 000000000..13bdfed95
--- /dev/null
+++ b/vcl/workben/vcldemo.cxx
@@ -0,0 +1,2450 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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_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 <tools/diagnose_ex.h>
+#include <tools/urlobj.hxx>
+#include <tools/stream.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/pngread.hxx>
+#include <vcl/wrkwin.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <vcl/button.hxx>
+#include <vcl/toolkit/combobox.hxx>
+#include <vcl/toolbox.hxx>
+#include <vcl/pngwrite.hxx>
+#include <vcl/floatwin.hxx>
+#include <vcl/bitmapaccess.hxx>
+#include <vcl/help.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/ImageTree.hxx>
+#include <vcl/BitmapEmbossGreyFilter.hxx>
+#include <bitmapwriteaccess.hxx>
+
+#include <basegfx/numeric/ftools.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <opengl/zone.hxx>
+
+// internal headers for OpenGLTests class.
+#if HAVE_FEATURE_OPENGL
+#include <salgdi.hxx>
+#include <salframe.hxx>
+#include <opengl/gdiimpl.hxx>
+#include <opengl/texture.hxx>
+#include <opengl/framebuffer.hxx>
+#include <vcl/opengl/OpenGLHelper.hxx>
+#endif
+
+#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("intro", maIntro))
+ Application::Abort("Failed to load intro image");
+
+ maIntroBW = maIntro.GetBitmap();
+
+ BitmapEx aTmpBmpEx(maIntroBW);
+ BitmapFilter::Filter(aTmpBmpEx, BitmapEmbossGreyFilter(0, 0));
+ 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(const OUString &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
+ long nBorderSize = std::min(aSize.Height() / 32, aSize.Width() / 32);
+ long nBoxWidth = (aSize.Width() - nBorderSize*(nX+1)) / nX;
+ 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(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::EnableB2dDraw);
+
+ 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 (size_t j = 0; j < SAL_N_ELEMENTS(aPoints); j++)
+ {
+ aPoly.append(basegfx::B2DPoint(aSub.Left() + aSub.GetWidth() * aPoints[j].nX,
+ aSub.Top() + aSub.GetHeight() * aPoints[j].nY));
+ }
+ rDev.DrawPolyLine(aPoly, aLineWidths[i], eJoins[i], eLineCaps[i]);
+ }
+ }
+ else
+ {
+ rDev.SetFillColor(COL_LIGHTRED);
+ rDev.SetLineColor(COL_BLACK);
+ rDev.DrawRect(r);
+
+ for(long i=0; i<r.GetHeight(); i+=15)
+ rDev.DrawLine(Point(r.Left(), r.Top()+i), Point(r.Right(), r.Bottom()-i));
+ for(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 < SAL_N_ELEMENTS(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) );
+
+ OUString const aLatinText("Click any rect to zoom!!!!");
+
+ 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 = aLatinText;
+
+ 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(45 * 10); // 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);
+
+ long nMaxTextHeight = 0;
+ for (size_t i = 0; i < SAL_N_ELEMENTS(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 < SAL_N_ELEMENTS(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);
+ 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 long drawStringBox(OutputDevice &rDev, Point aPos,
+ const OUString &aText,
+ 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
+ std::unique_ptr<long[]> pItems(new long[aText.getLength()+10]);
+ rDev.GetTextArray(aText, pItems.get());
+ for (long j = 0; j < aText.getLength(); ++j)
+ {
+ Point aTop = aTextRect.TopLeft();
+ Point aBottom = aTop;
+ aTop.Move(pItems[j], 0);
+ aBottom.Move(pItems[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.getWidth()-20);
+ aSmaller.setHeight(aSmaller.getHeight()-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);
+ 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);
+
+ long nDx = r.GetWidth()/20;
+ 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
+ };
+ GradientStyle eStyles[] = {
+ GradientStyle::Linear, GradientStyle::Axial, GradientStyle::Radial, GradientStyle::Elliptical, GradientStyle::Square,
+ GradientStyle::Rect, GradientStyle::FORCE_EQUAL_SIZE, GradientStyle::Linear, GradientStyle::Radial, GradientStyle::Linear,
+ GradientStyle::Linear, GradientStyle::Axial, GradientStyle::Radial, GradientStyle::Elliptical, GradientStyle::Square,
+ GradientStyle::Rect, GradientStyle::FORCE_EQUAL_SIZE, GradientStyle::Linear, GradientStyle::Radial, 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(nAngles[i]);
+ aGradient.SetBorder(nBorders[i]);
+ rDev.DrawGradient(aSub, aGradient);
+ }
+ }
+ else
+ {
+ Gradient aGradient;
+ aGradient.SetStartColor(COL_YELLOW);
+ aGradient.SetEndColor(COL_RED);
+ aGradient.SetStyle(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(), 24);
+ aBlockColor.Erase(COL_RED);
+ BitmapEx aShadowStretch(aBlockColor, aAlphaMask);
+
+ Point aRenderPt(r.TopLeft());
+
+ long aSizes[] = { 200, 100, 200, 100, 50, 5, 2 };
+
+ // and yes - we really do this in the page border rendering code ...
+ for (size_t i = 0; i < SAL_N_ELEMENTS(aSizes); i++)
+ {
+ aShadowStretch.Scale(Size(aShadowStretch.GetSizePixel().Width(), aSizes[i]),
+ BmpScaleFlag::Fast);
+
+ rDev.DrawBitmapEx(aRenderPt, aShadowStretch);
+ aRenderPt.Move(aShadowStretch.GetSizePixel().Width() + 4, 0);
+ }
+
+ AlphaMask aWholeMask(aPageShadowMask.GetBitmap());
+ aBlockColor = Bitmap(aPageShadowMask.GetSizePixel(), 24);
+ 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(SAL_N_ELEMENTS(aPoints));
+ for (size_t v = 0; v < SAL_N_ELEMENTS(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(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(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;
+ for (int j = 0; !bDone; ++j)
+ {
+ rDev.Push(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);
+ 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);
+ 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::DEFAULT,DeviceFormat::DEFAULT).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::EnableB2dDraw);
+ 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)
+ {
+ 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 * F_2PI * 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(), 24);
+ {
+ AlphaScopedWriteAccess pMaskAcc(aMask);
+ BitmapScopedWriteAccess pRecAcc(aRecovered);
+ Bitmap::ScopedReadAccess pAccW(aWhiteBmp); // a * pix + (1-a)
+ Bitmap::ScopedReadAccess 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);
+ long nAR = static_cast<long>(aColW.GetRed() - aColB.GetRed()); // (1-a)
+ long nAG = static_cast<long>(aColW.GetGreen() - aColB.GetGreen()); // (1-a)
+ long nAB = static_cast<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
+ long nInverseAlpha = std::max(nAR, std::max(nAG, nAB)); // (1-a)
+ nInverseAlpha = CLAMP(nInverseAlpha, 0, 255);
+ long nAlpha = 255 - nInverseAlpha;
+
+ pMaskAcc->SetPixelOnData(pScanlineMask,x,BitmapColor(static_cast<sal_Int8>(CLAMP(nInverseAlpha,0,255))));
+ // now recover the pixels
+ long nR = (aColW.GetRed() + aColB.GetRed() - nInverseAlpha) * 128;
+ long nG = (aColW.GetGreen() + aColB.GetGreen() - nInverseAlpha) * 128;
+ 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.GetMask());
+
+ // 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.GetBitmap(),
+ AlphaMask(aSrc.GetMask()));
+ 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].IsInside(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(const OUString &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
+ {
+ wait(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))
+ {
+ 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(%ld,%ld,%ld,%ld)\n", rRect.getX(), rRect.getY(), rRect.getWidth(), rRect.getHeight());
+ 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;
+};
+
+}
+
+static 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" },
+};
+
+namespace {
+
+class DemoWidgets : public WorkWindow
+{
+ VclPtr<MenuBar> mpBar;
+ VclPtr<VclBox> mpBox;
+ VclPtr<ToolBox> mpToolbox;
+ VclPtr<PushButton> mpButton;
+ VclPtr<VclHBox> mpHBox;
+ VclPtr<CheckBox> mpGLCheck;
+ VclPtr<ComboBox> mpGLCombo;
+ VclPtr<PushButton> mpGLButton;
+ std::vector<VclPtr<VclHBox>> mvCursorBoxes;
+ std::vector<VclPtr<PushButton>> mvCursorButtons;
+
+ DECL_LINK(GLTestClick, Button*, void);
+ 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())),
+ mpHBox(VclPtrInstance<VclHBox>(mpBox.get(), true, 3)),
+ mpGLCheck(VclPtrInstance<CheckBox>(mpHBox.get())),
+ mpGLCombo(VclPtrInstance<ComboBox>(mpHBox.get())),
+ mpGLButton(VclPtrInstance<PushButton>(mpHBox.get()))
+ {
+ SetText("VCL widget demo");
+
+ Wallpaper aWallpaper(BitmapEx("sfx2/res/startcenter-logo.png"));
+ aWallpaper.SetStyle(WallpaperStyle::BottomRight);
+ aWallpaper.SetColor(COL_RED);
+
+ mpBox->SetBackground(aWallpaper);
+ mpBox->Show();
+
+ Help::EnableBalloonHelp();
+ mpToolbox->SetHelpText("Help text");
+ mpToolbox->InsertItem(0, "Toolbar item");
+ mpToolbox->SetQuickHelpText(0, "This is a tooltip popup");
+ mpToolbox->InsertSeparator();
+ mpToolbox->Show();
+
+ mpButton->SetText("Click me; go on");
+ mpButton->Show();
+
+ mpGLCheck->SetText("Test in OGL zone");
+ mpGLCheck->Show();
+ mpGLCombo->InsertEntry("sleep 1 second");
+ mpGLCombo->InsertEntry("sleep 3 seconds");
+ mpGLCombo->InsertEntry("sleep 7 seconds");
+ mpGLCombo->SelectEntryPos(2);
+ mpGLCombo->Show();
+ mpGLButton->SetText("Execute test");
+ mpGLButton->SetClickHdl(LINK(this,DemoWidgets,GLTestClick));
+ mpGLButton->Show();
+ mpHBox->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
+ {
+ mpGLButton.disposeAndClear();
+ mpGLCombo.disposeAndClear();
+ mpGLCheck.disposeAndClear();
+ mpHBox.disposeAndClear();
+ 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(PushFlags::CLIPREGION);
+ rRenderContext.IntersectClipRegion(aClip);
+ rRenderContext.DrawWallpaper(aWholeSize, aWallpaper);
+ rRenderContext.Pop();
+
+ ScopedVclPtrInstance< VirtualDevice > pDev(*this);
+ 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_NOARG(DemoWidgets, GLTestClick, Button*, void)
+{
+ sal_Int32 nSelected = mpGLCombo->GetSelectedEntryPos();
+ sal_uInt32 nDelaySeconds = 0;
+
+ switch (nSelected)
+ {
+ case 0:
+ nDelaySeconds = 1;
+ break;
+ case 1:
+ nDelaySeconds = 3;
+ break;
+ case 2:
+ nDelaySeconds = 7;
+ break;
+ default:
+ break;
+ }
+
+ // Only create OpenGLZone RAII object if asked for:
+ std::unique_ptr<OpenGLZone> zone;
+ if (mpGLCheck->IsChecked()) {
+ zone.reset(new OpenGLZone);
+ }
+
+ osl::Thread::wait(std::chrono::seconds(nDelaySeconds));
+}
+
+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
+ {
+ // Interestingly in GL mode on Windows, this doesn't render.
+
+ Size aSize = GetOutputSizePixel();
+ tools::Rectangle aTextRect(Point(6, 6), aSize);
+
+ SetTextColor(COL_BLACK);
+ SetTextAlign(ALIGN_TOP);
+ DrawText(aTextRect, "This is a standalone help text test",
+ DrawTextFlags::MultiLine|DrawTextFlags::WordBreak|
+ DrawTextFlags::Left|DrawTextFlags::Top);
+
+ SetLineColor(COL_BLACK);
+ SetFillColor();
+ DrawRect( tools::Rectangle( Point(), aSize ) );
+ aSize.AdjustWidth( -2 );
+ aSize.AdjustHeight( -2 );
+ Color aColor( GetLineColor() );
+ SetLineColor( COL_GRAY );
+ DrawRect( tools::Rectangle( Point( 1, 1 ), aSize ) );
+ SetLineColor( aColor );
+ }
+
+ virtual void MouseButtonDown( const MouseEvent & ) override
+ {
+ Application::Quit();
+ }
+};
+
+}
+
+class OpenGLTests
+{
+ VclPtr<WorkWindow> mxWinA;
+ VclPtr<WorkWindow> mxWinB;
+ rtl::Reference<OpenGLContext> mpA;
+ rtl::Reference<OpenGLContext> mpB;
+
+ static OpenGLSalGraphicsImpl *getImpl(const VclPtr<OutputDevice> &xOut)
+ {
+ SalGraphics *pGraphics = xOut->GetGraphics();
+ return dynamic_cast<OpenGLSalGraphicsImpl *>(pGraphics->GetImpl());
+ }
+public:
+ OpenGLTests() :
+ mxWinA(VclPtr<WorkWindow>::Create(nullptr, WB_APP | WB_STDWORK)),
+ mxWinB(VclPtr<WorkWindow>::Create(nullptr, WB_APP | WB_STDWORK))
+ {
+ OpenGLSalGraphicsImpl *pImplA;
+ OpenGLSalGraphicsImpl *pImplB;
+ if (!OpenGLHelper::isVCLOpenGLEnabled())
+ {
+ pImplA = pImplB = nullptr;
+ fprintf (stderr, "OpenGL is not enabled: try SAL_FORCEGL=1\n");
+ return;
+ }
+
+ pImplA = getImpl(mxWinA);
+ pImplB = getImpl(mxWinB);
+ assert (pImplA && pImplB);
+ mpA = pImplA->GetOpenGLContext();
+ mpB = pImplB->GetOpenGLContext();
+
+ assert (mpA.is() && mpB.is());
+ assert (mpA != mpB);
+ }
+ ~OpenGLTests()
+ {
+ mxWinB.disposeAndClear();
+ mxWinA.disposeAndClear();
+ }
+
+ void testCurrentFramebuffer()
+ {
+ fprintf(stderr,"test OpenGLContext's framebuffer association.\n");
+ mpA->makeCurrent();
+ OpenGLFramebuffer *pBuffer;
+ {
+ OpenGLTexture aTexture(256,128);
+ pBuffer = mpA->AcquireFramebuffer(aTexture);
+ }
+ assert (pBuffer->IsFree()); (void)pBuffer;
+ mpB->makeCurrent();
+ assert (mpA->mpCurrentFramebuffer == nullptr);
+ }
+
+ void testVirtualDevice()
+ {
+ fprintf(stderr, "test sharing OpenGLContexts with virtual-devices reference counting\n");
+ VclPtrInstance<WorkWindow> xTempWin(nullptr, WB_STDWORK);
+ xTempWin->Show();
+ // forcibly make this context current by rendering
+ xTempWin->DrawPixel(Point(0, 0), COL_RED);
+
+ // get some other guys to leach off this context
+ VclPtrInstance<VirtualDevice> xVDev;
+ OpenGLSalGraphicsImpl* pImpl = getImpl(xVDev);
+ assert(pImpl);
+ rtl::Reference<OpenGLContext> pContext = pImpl->GetOpenGLContext();
+ VclPtrInstance<VirtualDevice> xVDev2;
+ OpenGLSalGraphicsImpl* pImpl2 = getImpl(xVDev2);
+ assert(pImpl2);
+ rtl::Reference<OpenGLContext> pContext2 = pImpl2->GetOpenGLContext();
+
+ // sharing the same off-screen context.
+ assert(pContext == pContext2);
+ assert(pContext == getImpl(xTempWin)->GetOpenGLContext());
+ assert(pContext != mpA && pContext != mpB);
+ (void)pContext; (void)pContext2;
+
+ // Kill the parent we free-ride on ...
+ xTempWin.disposeAndClear();
+
+ // This appears to continue working; fun.
+ Point aPt(0, 0);
+ xVDev->DrawPixel(aPt, COL_GREEN);
+ assert(xVDev->GetPixel(aPt) == COL_GREEN);
+ xVDev.disposeAndClear();
+
+ // Switch context to see if we can switch back.
+ mxWinA->DrawPixel(aPt, COL_WHITE);
+
+ // Now try switching back to this guy ...
+ xVDev2->DrawPixel(aPt, COL_BLUE);
+ assert(xVDev2->GetPixel(aPt) == COL_BLUE);
+ xVDev2.disposeAndClear();
+ }
+
+ int execute()
+ {
+ if (!OpenGLHelper::isVCLOpenGLEnabled())
+ return 1;
+
+ testCurrentFramebuffer();
+ testVirtualDevice();
+
+ return 0;
+ }
+};
+
+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," --gltest - run openGL regression tests.\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, bThreads = false;
+ bool bPopup = false, bGLTest = 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 == "--gltest")
+ bGLTest = 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 HAVE_FEATURE_OPENGL
+ if (bGLTest)
+ {
+ OpenGLTests aTests;
+ return aTests.execute();
+ }
+ else
+#endif
+ 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
+ {
+ 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
+ {
+ 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/wksfuzzer.cxx b/vcl/workben/wksfuzzer.cxx
new file mode 100644
index 000000000..3edacb484
--- /dev/null
+++ b/vcl/workben/wksfuzzer.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 <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(1);
+ aArgs[0] <<= 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 000000000..678d526b1
--- /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 000000000..7ce9ff90d
--- /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 * emfio_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 * );
+}
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { "libemfiolo.a", emfio_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 },
+ { 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 000000000..678d526b1
--- /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 000000000..24038ae16
--- /dev/null
+++ b/vcl/workben/ww2fuzzer.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/.
+ */
+
+#include <tools/stream.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+extern "C" {
+void * unoxml_component_getFactory( const char* , void* , void* );
+void * i18npool_component_getFactory( const char* , void* , void* );
+void * ucb_component_getFactory( const char* , void* , void* );
+void * emfio_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 * );
+}
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { "libunoxmllo.a", unoxml_component_getFactory },
+ { "libi18npoollo.a", i18npool_component_getFactory },
+ { "libemfiolo.a", emfio_component_getFactory },
+ { "libucb1.a", ucb_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},
+ { 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 000000000..d851ad58c
--- /dev/null
+++ b/vcl/workben/ww2fuzzer.options
@@ -0,0 +1,4 @@
+[libfuzzer]
+max_len = 65536
+[env]
+AFL_DRIVER_DONT_DEFER=1
diff --git a/vcl/workben/ww6fuzzer.cxx b/vcl/workben/ww6fuzzer.cxx
new file mode 100644
index 000000000..56db11981
--- /dev/null
+++ b/vcl/workben/ww6fuzzer.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 <tools/stream.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+extern "C" {
+void * unoxml_component_getFactory( const char* , void* , void* );
+void * i18npool_component_getFactory( const char* , void* , void* );
+void * ucb_component_getFactory( const char* , void* , void* );
+void * emfio_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 * );
+}
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { "libunoxmllo.a", unoxml_component_getFactory },
+ { "libi18npoollo.a", i18npool_component_getFactory },
+ { "libemfiolo.a", emfio_component_getFactory },
+ { "libucb1.a", ucb_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},
+ { 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 000000000..d851ad58c
--- /dev/null
+++ b/vcl/workben/ww6fuzzer.options
@@ -0,0 +1,4 @@
+[libfuzzer]
+max_len = 65536
+[env]
+AFL_DRIVER_DONT_DEFER=1
diff --git a/vcl/workben/ww8fuzzer.cxx b/vcl/workben/ww8fuzzer.cxx
new file mode 100644
index 000000000..d8613afcd
--- /dev/null
+++ b/vcl/workben/ww8fuzzer.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 <tools/stream.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+extern "C" {
+void * unoxml_component_getFactory( const char* , void* , void* );
+void * i18npool_component_getFactory( const char* , void* , void* );
+void * ucb_component_getFactory( const char* , void* , void* );
+void * emfio_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 * );
+}
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { "libunoxmllo.a", unoxml_component_getFactory },
+ { "libi18npoollo.a", i18npool_component_getFactory },
+ { "libemfiolo.a", emfio_component_getFactory },
+ { "libucb1.a", ucb_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},
+ { 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 000000000..d851ad58c
--- /dev/null
+++ b/vcl/workben/ww8fuzzer.options
@@ -0,0 +1,4 @@
+[libfuzzer]
+max_len = 65536
+[env]
+AFL_DRIVER_DONT_DEFER=1
diff --git a/vcl/workben/xbmfuzzer.cxx b/vcl/workben/xbmfuzzer.cxx
new file mode 100644
index 000000000..bb261f60a
--- /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 000000000..678d526b1
--- /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 000000000..893110988
--- /dev/null
+++ b/vcl/workben/xlsfuzzer.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 <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(1);
+ aArgs[0] <<= 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 000000000..d851ad58c
--- /dev/null
+++ b/vcl/workben/xlsfuzzer.options
@@ -0,0 +1,4 @@
+[libfuzzer]
+max_len = 65536
+[env]
+AFL_DRIVER_DONT_DEFER=1
diff --git a/vcl/workben/xlsxfuzzer.cxx b/vcl/workben/xlsxfuzzer.cxx
new file mode 100644
index 000000000..a325bd69f
--- /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 000000000..db9a3e8c6
--- /dev/null
+++ b/vcl/workben/xlsxfuzzer.options
@@ -0,0 +1,4 @@
+[libfuzzer]
+max_len = 98304
+[env]
+AFL_DRIVER_DONT_DEFER=1
diff --git a/vcl/workben/xpmfuzzer.cxx b/vcl/workben/xpmfuzzer.cxx
new file mode 100644
index 000000000..700761d17
--- /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 <../source/filter/ixpm/xpmread.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 000000000..678d526b1
--- /dev/null
+++ b/vcl/workben/xpmfuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 65536